From 93c83845c665568cf82214973aa0d2225cb159af Mon Sep 17 00:00:00 2001 From: devfive Date: Tue, 1 Sep 2026 03:28:32 +0900 Subject: [PATCH 01/77] Improve NIKL corpus Roman text accuracy --- docs/corpus-analysis/NIKL_2025_V1.md | 218 ++++++ .../braillify/examples/nikl_corpus_analyze.rs | 681 ++++++++++++++++++ libs/braillify/src/rules/emit.rs | 114 ++- .../braillify/src/rules/english_ueb/engine.rs | 53 ++ .../src/rules/english_ueb/rule_10_9.rs | 46 ++ libs/braillify/src/rules/english_ueb/span.rs | 23 + libs/braillify/src/rules/korean/rule_28.rs | 110 ++- 7 files changed, 1243 insertions(+), 2 deletions(-) create mode 100644 docs/corpus-analysis/NIKL_2025_V1.md create mode 100644 libs/braillify/examples/nikl_corpus_analyze.rs diff --git a/docs/corpus-analysis/NIKL_2025_V1.md b/docs/corpus-analysis/NIKL_2025_V1.md new file mode 100644 index 00000000..fd92d1e9 --- /dev/null +++ b/docs/corpus-analysis/NIKL_2025_V1.md @@ -0,0 +1,218 @@ +# NIKL 2025 v1.0 corpus analysis + +> Generated by `cargo run --release -p braillify --example nikl_corpus_analyze`. The tool reads only `input` and `unicode`; it never loads or compares `world`. + +## Current measurement + +| Metric | Count | +|---|---:| +| Total | 83528 | +| Exact | 63239 | +| Mismatch | 20289 | +| Exact accuracy | 75.71% | +| Duplicate records | 0 | +| Inputs with conflicting references | 0 | + +## Classification policy + +Primary classes are evidence gates, not permissions to change the engine. `implementation_defect` is restricted to defects independently confirmed from the PDF (the rules 28/29 roman-indicator ordering signature) and actual encoding errors. `pending_rule_review` contains foreign-text, number, punctuation, and Korean candidates that have not yet been resolved against the PDF. `corpus_suspect` is reserved for independently detectable contradictions such as one input having multiple references. `comparison_method` requires equality after a named normalization. + +| Primary class | Count | +|---|---:| +| `comparison_method` | 394 | +| `exact` | 63239 | +| `implementation_defect` | 434 | +| `pending_rule_review` | 19461 | + +| Reproducible reason | Count | +|---|---:| +| `encoding_error` | 434 | +| `exact` | 63239 | +| `foreign_text_rule_review` | 19272 | +| `nfkc_input_equivalent` | 394 | +| `number_rule_review` | 176 | +| `punctuation_rule_review` | 13 | + +## Encoding-error diagnostics + +These are overlapping diagnostics for `implementation_defect` encoding errors, not additional primary classes. A singleton error character is a character that also fails when encoded by itself. + +| Error message | Cases | +|---|---:| +| `Invalid character` | 26 | +| `Invalid symbol character` | 408 | + +| Singleton error character | Cases containing it | +|---|---:| +| `U+02D1 ˑ` | 1 | +| `U+2025 ‥` | 1 | +| `U+2113 ℓ` | 12 | +| `U+2160 Ⅰ` | 4 | +| `U+2161 Ⅱ` | 14 | +| `U+2162 Ⅲ` | 6 | +| `U+2163 Ⅳ` | 5 | +| `U+2166 Ⅶ` | 1 | +| `U+2502 │` | 1 | +| `U+25B2 ▲` | 1 | +| `U+25B4 ▴` | 1 | +| `U+260F ☏` | 3 | +| `U+2665 ♥` | 1 | +| `U+2E31 ⸱` | 2 | +| `U+302E 〮` | 1 | +| `U+3214 ㈔` | 2 | +| `U+321C ㈜` | 179 | +| `U+338D ㎍` | 12 | +| `U+338F ㎏` | 50 | +| `U+3390 ㎐` | 1 | +| `U+3393 ㎓` | 24 | +| `U+3394 ㎔` | 1 | +| `U+3396 ㎖` | 9 | +| `U+339A ㎚` | 14 | +| `U+339B ㎛` | 4 | +| `U+339C ㎜` | 12 | +| `U+339D ㎝` | 12 | +| `U+33A2 ㎢` | 14 | +| `U+33A5 ㎥` | 42 | +| `U+33A7 ㎧` | 2 | +| `U+33B8 ㎸` | 3 | +| `U+33BD ㎽` | 1 | +| `U+33BE ㎾` | 9 | +| `U+33BF ㎿` | 9 | +| `U+33C3 ㏃` | 4 | +| `U+33DC ㏜` | 1 | + +## Shards + +| Shard | Exact | Total | Accuracy | +|---|---:|---:|---:| +| `sentence_01.json` | 18860 | 25000 | 75.44% | +| `sentence_02.json` | 18286 | 25000 | 73.14% | +| `sentence_03.json` | 19364 | 25000 | 77.46% | +| `sentence_04.json` | 6729 | 8528 | 78.90% | + +## Overlapping mismatch traits + +| Trait | Count | +|---|---:| +| `contains_ascii_digits` | 15849 | +| `contains_ascii_letters` | 20076 | +| `contains_delimiter_or_quote` | 20289 | +| `input_not_nfkc` | 1009 | + +## Samples + +### `encoding_error` + +- `sentence_01.json` #50: 부산광역시는 3일 오후 부산광역시청 영상회의실에서 종합화학소재기업 ㈜금양과 이차전지 생산기지 건립을 위한 8천억원 규모의 투자 양해각서(MOU)를 체결한다고 밝혔다. + - expected: `⠘⠍⠇⠒⠈⠧⠶⠱⠁⠠⠕⠉⠵⠀⠼⠉⠕⠂⠀⠥⠚⠍⠀⠘` + - actual: `` + - error: `Invalid symbol character` +- `sentence_01.json` #1765: 신안군은 공모사업에 앞서 지난 1월 북부권김생산자협의회, ㈜신안천사김과 미국 수출김 전용단지 지정을 위한 업무협약(MOU)을 체결하고 북부권 김 양식장 1천118ha를 공모사업 지구로 신청했다. + - expected: `⠠⠟⠣⠒⠈⠛⠵⠀⠈⠿⠑⠥⠇⠎⠃⠝⠀⠣⠲⠠⠎⠀⠨⠕` + - actual: `` + - error: `Invalid symbol character` +- `sentence_01.json` #1901: 이를 위해 자산운용사 ㈜피아나인을 통해 ‘크러쉬 (CRUSH)’의 해외 진출을 위한 추가 씨드(Seed) 투자를 유치한데 이어 글로벌 마케팅 에이전시 ‘넥썸그룹 (The NEXUM Group)’과 업무협약(MOU)를 체결했다. + - expected: `⠕⠐⠮⠀⠍⠗⠚⠗⠀⠨⠇⠒⠛⠬⠶⠇⠀⠦⠄⠨⠍⠠⠴⠙` + - actual: `` + - error: `Invalid symbol character` +- `sentence_01.json` #2320: 대구광역시(시장 홍준표)는 30일 대구시청 산격청사에서 국내 유망 임플란트 기업 중 하나인 ㈜덴티스(대표 심기봉)와 투자협약 양해각서(MOU)를 체결했다고 밝혔다. + - expected: `⠊⠗⠈⠍⠈⠧⠶⠱⠁⠠⠕⠦⠄⠠⠕⠨⠶⠀⠚⠿⠨⠛⠙⠬` + - actual: `` + - error: `Invalid symbol character` +- `sentence_01.json` #2336: 한편, 한국팀 경기를 포함한 아시아컵 주요 경기는 세계야구소프트볼연맹(WBSC) 공식 유튜브에서 확인할 수 있다. 이번 대회 운영 대행은 ㈜에스피에스 코리아(SPS KOREA)가 맡아 진행한다. + - expected: `⠚⠒⠙⠡⠐⠀⠚⠒⠈⠍⠁⠓⠕⠢⠀⠈⠻⠈⠕⠐⠮⠀⠙⠥` + - actual: `` + - error: `Invalid symbol character` + +### `foreign_text_rule_review` + +- `sentence_01.json` #12: 특히 미국 헬스탭(HealthTap), 국내 굿닥(Goodoc)과 협업해 병원에 갈 필요 없이 집 안에서도 편리하게 진료를 받을 수 있는 원격 헬스케어 서비스를 시연한다. + - expected: `⠚⠝⠂⠠⠪⠓⠗⠃⠦⠄⠴⠠⠓⠂⠇⠹⠠⠞⠁⠏⠠⠴⠐⠀` + - actual: `⠚⠝⠂⠠⠪⠓⠗⠃⠀⠀⠦⠠⠓⠑⠁⠇⠞⠓⠠⠞⠁⠏⠴⠐` +- `sentence_01.json` #14: 새롭게 선보인 DRX 브랜딩과 팀 컬러는 2023년 1월 1일부터 DRX의 온오프라인 콘텐츠, 엠디(MD), SNS, 유튜브 등 모든 제작물에 적용될 예정이다. + - expected: `⠰⠪⠐⠀⠝⠢⠊⠕⠴⠐⠣⠠⠠⠍⠙⠐⠜⠂⠀⠠⠠⠎⠝⠎` + - actual: `⠰⠪⠐⠀⠝⠢⠊⠕⠀⠀⠦⠠⠠⠍⠙⠴⠐⠀⠴⠠⠠⠎⠝⠎` +- `sentence_01.json` #17: 지난달 30일(현지시각) 뉴욕증권거래소(NYSE)에서 다우존스30산업평균지수는 전 거래일보다 73.55포인트(0.22%) 하락한 3만3147.25로 마감했다. 대형주 중심의 S&P500지수는 9.78포인트(0.25%) 하락한 3839.50으로, 기술주 중심의 나스닥지수는 11.61포인트(0.11%) 하락한 1만0466.4 + - expected: `⠶⠠⠕⠢⠺⠀⠴⠠⠎⠈⠯⠠⠏⠼⠑⠚⠚⠨⠕⠠⠍⠉⠵⠀` + - actual: `⠶⠠⠕⠢⠺⠀⠴⠠⠠⠎⠲⠴⠈⠯⠲⠴⠏⠼⠑⠚⠚⠨⠕⠠` +- `sentence_01.json` #18: 2023년형 신제품은 매터(Matter)와 HCA(Home Connectivity Alliance) 표준을 지원하는 스마트싱스 허브(SmartThings Hub)를 기반으로 다양한 기기를 자동으로 연결하고, 제어·관리할 수 있는 것이 특징이다. + - expected: `⠁⠞⠞⠻⠠⠴⠧⠀⠴⠠⠠⠓⠉⠁⠐⠣⠠⠓⠕⠍⠑⠀⠠⠒` + - actual: `⠁⠞⠞⠻⠠⠴⠧⠀⠀⠠⠠⠓⠉⠁⠦⠠⠓⠕⠍⠑⠀⠴⠠⠒` +- `sentence_01.json` #25: 세븐일레븐은 올해 말 정식 버전 오픈을 목표로 하며, 이를 통해 주력상품을 구매하고 배송까지 받을 수 있는 차세대 오프라인을 위한 온라인(O4O) 시스템을 구축해 나갈 계획이다. + - expected: `⠗⠚⠒⠀⠷⠐⠣⠟⠦⠄⠴⠠⠕⠼⠙⠠⠕⠠⠴⠀⠠⠕⠠⠪` + - actual: `⠗⠚⠒⠀⠷⠐⠣⠟⠀⠀⠦⠠⠕⠼⠙⠠⠕⠴⠀⠠⠕⠠⠪⠓` + +### `nfkc_input_equivalent` + +- `sentence_01.json` #937: 위원들은 ㈜광주글로벌모터스(GGM)와 협력 중소기업 근로자의 실질적 복지 증진 필요성에 대해 공감하고, (가칭)상생형 공동근로복지기금법인의 기금 조성 계획안을 최종 의결했다. + - expected: `⠍⠗⠏⠒⠊⠮⠵⠀⠦⠄⠨⠍⠠⠴⠈⠧⠶⠨⠍⠈⠮⠐⠥⠘` + - actual: `` + - error: `Invalid symbol character` +- `sentence_01.json` #1558: 레저용차량(RV)은 카니발 6천39대, 스포티지 5천689대, 셀토스 5천552대, 쏘렌토 4천745대등 총 2만6천360대가 판매됐다. 봉고Ⅲ를 비롯한 상용은 버스를 합쳐 총 9천73대가 판매됐다. + - expected: `⠐⠝⠨⠎⠬⠶⠰⠣⠐⠜⠶⠦⠄⠴⠠⠠⠗⠧⠠⠴⠵⠀⠋⠉` + - actual: `` + - error: `Invalid character` +- `sentence_01.json` #2276: 김무환 SK㈜ 그린(Green)투자센터장은 “글로벌 탈산소 전문 가치 투자자를 목표로 탄소 배출 축소 및 제거 관련 사업기회를 발굴하고 투자하는데 역점을 두고 있다”고 밝혔다. + - expected: `⠈⠕⠢⠑⠍⠚⠧⠒⠀⠴⠠⠠⠎⠅⠦⠄⠨⠍⠠⠴⠀⠈⠪⠐` + - actual: `` + - error: `Invalid symbol character` +- `sentence_01.json` #2662: 교육 프로그램은 ‘구글 클라우드 기반 인공지능 이해·빅쿼리(BigQuery) 전문가 과정’으로, 구글 클라우드의 공식 교육파트너사인 ㈜아이코어이앤씨가 맡는다. + - expected: `⠈⠬⠩⠁⠀⠙⠪⠐⠥⠈⠪⠐⠗⠢⠵⠀⠠⠦⠈⠍⠈⠮⠀⠋` + - actual: `` + - error: `Invalid symbol character` +- `sentence_01.json` #2898: 한국농수산식품유통공사의 농수산유통정보(KAMIS)에 따르면 주키니 호박의 20일 평균 가격은 상품(도매가) 기준 10㎏당 1만580원으로 형성돼 있다. 출하가 재개된 지난 3일 평균가격인 1만9천500원에서 9천원 가까이 하락했다. + - expected: `⠚⠒⠈⠍⠁⠉⠿⠠⠍⠇⠒⠠⠕⠁⠙⠍⠢⠩⠓⠿⠈⠿⠇⠺` + - actual: `` + - error: `Invalid symbol character` + +### `number_rule_review` + +- `sentence_01.json` #788: 1일 발란은 고객 신뢰를 강화하기 위한 일환으로 파트너사 대상 판매 상품 사전 검수 기준을 대폭 강화한 ‘발란 케어 플러스(+)’를 이 달부터 실시한다고 밝혔다. + - expected: `⠙⠮⠐⠎⠠⠪⠦⠄⠢⠠⠴⠴⠄⠐⠮⠀⠕⠀⠊⠂⠘⠍⠓⠎` + - actual: `⠙⠮⠐⠎⠠⠪⠦⠄⠀⠢⠀⠠⠴⠴⠄⠐⠮⠀⠕⠀⠊⠂⠘⠍` +- `sentence_01.json` #845: 부산광역시는 3일 오전 부산광역시청에서 초록우산어린이재단 부산본부, 월드비전 부산사업본부, 굿네이버스 영남본부와 ‘자립+(더하기) 동행 프로젝트’ 업무협약을 체결했다고 밝혔다. + - expected: `⠠⠦⠨⠐⠕⠃⠀⠢⠦⠄⠊⠎⠚⠈⠕⠠⠴⠀⠊⠿⠚⠗⠶⠀` + - actual: `⠠⠦⠨⠐⠕⠃⠀⠢⠀⠦⠄⠊⠎⠚⠈⠕⠠⠴⠀⠊⠿⠚⠗⠶` +- `sentence_01.json` #6594: 이날 발대식에는 △보성군 청년협의체 1(팀), △행안부형 청년공동체 1(팀), △전남형 청년공동체 5(팀), △전남형 청년 마을 1(팀), △모닥모닥 동아리 5(팀), 총 13개 팀 60여명의 회원이 참석한다. + - expected: `⠕⠁⠝⠉⠵⠀⠸⠬⠀⠘⠥⠠⠻⠈⠛⠀⠰⠻⠉⠡⠚⠱⠃⠺` + - actual: `⠕⠁⠝⠉⠵⠀⠸⠬⠇⠘⠥⠠⠻⠈⠛⠀⠰⠻⠉⠡⠚⠱⠃⠺` +- `sentence_01.json` #6854: 학생부위주(교과)에서 학생부 교과 100%로 선발하는 △교과전형은 876명 △지역인재(교과)전형은 48명 △불교추천인재 전형으로 59명[(한)의예과 및 간호학과 모집]을 선발하며 △교과성적 외 면접점수를 40% 반영하는 면접전형은 226명을 모집한다. + - expected: `⠘⠂⠚⠉⠵⠀⠸⠬⠀⠈⠬⠈⠧⠨⠾⠚⠻⠵⠀⠼⠓⠛⠋⠀` + - actual: `⠘⠂⠚⠉⠵⠀⠸⠬⠇⠈⠬⠈⠧⠨⠾⠚⠻⠵⠀⠼⠓⠛⠋⠀` +- `sentence_01.json` #9055: 한국건설기술연구원에 따르면 주거용 건물 건설공사비지수는 올해 9월 기준 152.76(잠정치)으로 전년 동월(147.31) 대비 약 3.7%가량 증가했다. + - expected: `⠾⠉⠡⠀⠊⠿⠏⠂⠦⠄⠼⠁⠙⠛⠲⠉⠁⠠⠴⠀⠊⠗⠘⠕` + - actual: `⠾⠉⠡⠀⠊⠿⠏⠂⠀⠀⠦⠼⠁⠙⠛⠲⠉⠁⠴⠀⠊⠗⠘⠕` + +### `punctuation_rule_review` + +- `sentence_01.json` #18647: 배우 박성웅, 오대환, 오달수, 주석태 주연 정통 하드보일드 액션 영화 ‘더와일드:야수들의 전쟁’(감독:김봉한/제작:(주)아센디오, (주)제이앤씨미디어그룹/이하 더와일드)의 개봉 소식과 함께 보도스틸이 공개돼 눈길을 끈다. + - expected: `⠎⠧⠕⠂⠊⠪⠐⠂⠀⠜⠠⠍⠊⠮⠺⠀⠨⠾⠨⠗⠶⠴⠄⠦` + - actual: `⠎⠧⠕⠂⠊⠪⠐⠂⠜⠠⠍⠊⠮⠺⠀⠨⠾⠨⠗⠶⠴⠄⠦⠄` +- `sentence_01.json` #21108: 전고체 이차전지는 양(+)극과 음(-)극 사이에서 이온을 전달하는 ‘전해질’을 액체가 아닌, 화재나 폭발 위험성이 낮은 고체로 대체한 것이다. + - expected: `⠕⠉⠵⠀⠜⠶⠦⠄⠢⠠⠴⠈⠪⠁⠈⠧⠀⠪⠢⠦⠄⠔⠠⠴` + - actual: `⠕⠉⠵⠀⠜⠶⠦⠄⠀⠢⠀⠠⠴⠈⠪⠁⠈⠧⠀⠪⠢⠦⠄⠤` +- `sentence_02.json` #417: 시는 세종고용복지플러스(+)센터, 세종여성새로일하기센터, 세종과학기술인협회 등과 공동으로 기업지원사업안내는 물론, 청년들이 면접 부담을 덜 수 있도록 청년면접비지원사업도 동시 진행하고 있다. + - expected: `⠙⠮⠐⠎⠠⠪⠦⠄⠢⠠⠴⠠⠝⠒⠓⠎⠐⠀⠠⠝⠨⠿⠱⠠` + - actual: `⠙⠮⠐⠎⠠⠪⠦⠄⠀⠢⠀⠠⠴⠠⠝⠒⠓⠎⠐⠀⠠⠝⠨⠿` +- `sentence_02.json` #4622: 시는 세종고용복지플러스(+)센터, 세종여성새로일하기센터, 세종과학기술인협회 등과 공동으로 기업지원 사업안내는 물론, 청년들이 면접부담을 덜수 있도록 청년면접비지원사업도 동시 진행하고 있다. + - expected: `⠙⠮⠐⠎⠠⠪⠦⠄⠢⠠⠴⠠⠝⠒⠓⠎⠐⠀⠠⠝⠨⠿⠱⠠` + - actual: `⠙⠮⠐⠎⠠⠪⠦⠄⠀⠢⠀⠠⠴⠠⠝⠒⠓⠎⠐⠀⠠⠝⠨⠿` +- `sentence_02.json` #6472: 시는 세종고용복지플러스(+)센터, 세종여성새로일하기센터, 세종과학기술인협회 등과 공동으로 기업지원사업안내는 물론 청년들이 면접 부담을 덜 수 있도록 청년면접비지원사업도 동시 진행하고 있다. + - expected: `⠙⠮⠐⠎⠠⠪⠦⠄⠢⠠⠴⠠⠝⠒⠓⠎⠐⠀⠠⠝⠨⠿⠱⠠` + - actual: `⠙⠮⠐⠎⠠⠪⠦⠄⠀⠢⠀⠠⠴⠠⠝⠒⠓⠎⠐⠀⠠⠝⠨⠿` + +## PDF-derived state gates + +The rule 37 example `그는 Can you help me?라고 도움을 요청했다.` distinguishes the first word after the roman indicator (`Can`, whose whole-word sign is suppressed) from the interior word `you` in the uninterrupted ASCII phrase (whose UEB wordsign is retained). The `prev_is_ascii_word && next_is_ascii_word` gate expresses that phrase-interior position rather than matching an input string. + +The rule 39 example `What is 김치 in English?` resumes the surrounding English passage after the Korean span. The `english_dominant_wrap_active` gate therefore retains the UEB wordsign for the resumed `in`, instead of treating it as a fresh rule 37 entry word. + +## Rule evidence and change log + +| Stage | Standard cases | Corpus exact | Corpus accuracy | Evidence | +|---|---:|---:|---:|---| +| Parent commit `3cfeae0` | 5,141/5,141 | 57,732/83,528 | 69.12% | Reproduced with release tests | +| Rules 28/29 indicator ordering | 5,141/5,141 | 61,652/83,528 | 73.81% | Roman indicator now precedes UEB grade-1/capital indicators; rule 35 roman-number continuity retained | +| Rules 37/39 shared UEB groupsign algorithm | 5,141/5,141 | 63,239/83,528 | 75.71% | Korean Roman sections reuse UEB preference/morphology rules; entry wordsigns and English-dominant resume are state-gated | + +Engine changes must add a row only after both the 5,141-case standard suite and this full analysis have been rerun. Suspect-reference clusters stay in this report; they are not engine targets without independent PDF evidence. diff --git a/libs/braillify/examples/nikl_corpus_analyze.rs b/libs/braillify/examples/nikl_corpus_analyze.rs new file mode 100644 index 00000000..80bfe2a6 --- /dev/null +++ b/libs/braillify/examples/nikl_corpus_analyze.rs @@ -0,0 +1,681 @@ +//! Reproducible NIKL Korean–Korean Braille Parallel Corpus 2025 v1.0 analysis. +//! +//! Run from the workspace root: +//! `cargo run --release -p braillify --example nikl_corpus_analyze` +//! +//! This is an offline evaluation tool. It deliberately deserializes only `input` and +//! `unicode`; the read-only competitor `world` field is neither loaded nor compared. + +use std::collections::{BTreeMap, BTreeSet}; +use std::fs::{self, File}; +use std::io::BufReader; +use std::path::{Path, PathBuf}; +use std::thread; + +use serde::{Deserialize, Serialize}; +use unicode_normalization::UnicodeNormalization; + +#[derive(Clone, Deserialize)] +struct CorpusCase { + input: String, + unicode: String, +} + +#[derive(Clone)] +struct LocatedCase { + shard: String, + index: usize, + case: CorpusCase, +} + +#[derive(Clone)] +struct EncodedCase { + located: LocatedCase, + actual: Result, + nfc_actual: Option>, + nfkc_actual: Option>, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize)] +#[serde(rename_all = "snake_case")] +enum PrimaryClass { + Exact, + ImplementationDefect, + CorpusSuspect, + ComparisonMethod, + PendingRuleReview, +} + +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize)] +#[serde(rename_all = "snake_case")] +enum Reason { + Exact, + ConflictingDuplicateReference, + BrailleWhitespaceEquivalent, + NfcInputEquivalent, + NfkcInputEquivalent, + RomanIndicatorAfterCapitalIndicator, + EncodingError, + ForeignTextRuleReview, + NumberRuleReview, + PunctuationRuleReview, + KoreanRuleReview, +} + +#[derive(Debug, Serialize)] +struct Sample { + shard: String, + index: usize, + input: String, + expected_excerpt: String, + actual_excerpt: String, + error: Option, +} + +#[derive(Debug, Default, Serialize)] +struct ShardStats { + total: usize, + exact: usize, +} + +#[derive(Debug, Serialize)] +struct AnalysisReport { + corpus: &'static str, + total: usize, + exact: usize, + mismatch: usize, + exact_percent: f64, + duplicate_inputs: usize, + conflicting_duplicate_inputs: usize, + primary_classes: BTreeMap, + reasons: BTreeMap, + encoding_error_messages: BTreeMap, + singleton_error_characters: BTreeMap, + overlapping_traits: BTreeMap, + shards: BTreeMap, + samples: BTreeMap>, +} + +#[derive(Debug)] +struct Config { + report_path: PathBuf, + json_path: PathBuf, + sample_limit: usize, + threads: usize, +} + +impl Config { + fn parse() -> Result { + let workspace = Path::new(env!("CARGO_MANIFEST_DIR")).join("../.."); + let mut config = Self { + report_path: workspace.join("docs/corpus-analysis/NIKL_2025_V1.md"), + json_path: workspace.join("target/nikl-corpus-analysis.json"), + sample_limit: 5, + threads: thread::available_parallelism() + .map_or(1, usize::from) + .min(8), + }; + + let mut args = std::env::args().skip(1); + while let Some(arg) = args.next() { + match arg.as_str() { + "--report" => { + config.report_path = + PathBuf::from(args.next().ok_or("--report requires a path")?); + } + "--json" => { + config.json_path = PathBuf::from(args.next().ok_or("--json requires a path")?); + } + "--sample-limit" => { + config.sample_limit = args + .next() + .ok_or("--sample-limit requires a number")? + .parse() + .map_err(|_| "--sample-limit must be a positive integer")?; + } + "--threads" => { + config.threads = args + .next() + .ok_or("--threads requires a number")? + .parse() + .map_err(|_| "--threads must be a positive integer")?; + if config.threads == 0 { + return Err("--threads must be at least 1".to_string()); + } + } + "--help" | "-h" => { + println!( + "nikl_corpus_analyze [--report PATH] [--json PATH] \ + [--sample-limit N] [--threads N]" + ); + std::process::exit(0); + } + _ => return Err(format!("unknown argument: {arg}")), + } + } + Ok(config) + } +} + +fn load_cases() -> Result, String> { + let corpus_dir = Path::new(env!("CARGO_MANIFEST_DIR")).join("../../test_cases/corpus"); + let mut paths = fs::read_dir(&corpus_dir) + .map_err(|error| format!("cannot read {}: {error}", corpus_dir.display()))? + .map(|entry| entry.map(|entry| entry.path())) + .collect::, _>>() + .map_err(|error| format!("cannot enumerate corpus shards: {error}"))?; + paths.retain(|path| { + path.file_name() + .and_then(|name| name.to_str()) + .is_some_and(|name| name.starts_with("sentence_") && name.ends_with(".json")) + }); + paths.sort(); + let shard_count = paths.len(); + + let mut located = Vec::new(); + for path in paths { + let shard = path + .file_name() + .and_then(|name| name.to_str()) + .ok_or_else(|| format!("non-Unicode shard path: {}", path.display()))? + .to_string(); + let cases: Vec = serde_json::from_reader(BufReader::new( + File::open(&path) + .map_err(|error| format!("cannot open {}: {error}", path.display()))?, + )) + .map_err(|error| format!("invalid JSON in {}: {error}", path.display()))?; + located.extend( + cases + .into_iter() + .enumerate() + .map(|(index, case)| LocatedCase { + shard: shard.clone(), + index: index + 1, + case, + }), + ); + } + validate_corpus_shape(shard_count, located.len())?; + Ok(located) +} + +fn validate_corpus_shape(shard_count: usize, case_count: usize) -> Result<(), String> { + if shard_count == 0 { + return Err("no NIKL corpus shards matched sentence_*.json".to_string()); + } + if case_count == 0 { + return Err("NIKL corpus shards contained zero cases".to_string()); + } + Ok(()) +} + +fn encode_cases(cases: &[LocatedCase], thread_count: usize) -> Vec { + let chunk_size = cases.len().div_ceil(thread_count); + let mut chunks = thread::scope(|scope| { + cases + .chunks(chunk_size.max(1)) + .map(|chunk| { + scope.spawn(move || { + chunk + .iter() + .cloned() + .map(|located| { + let actual = braillify::encode_to_unicode(&located.case.input); + let nfc: String = located.case.input.nfc().collect(); + let nfc_actual = (nfc != located.case.input) + .then(|| braillify::encode_to_unicode(&nfc)); + let nfkc: String = located.case.input.nfkc().collect(); + let nfkc_actual = (nfkc != located.case.input) + .then(|| braillify::encode_to_unicode(&nfkc)); + EncodedCase { + located, + actual, + nfc_actual, + nfkc_actual, + } + }) + .collect::>() + }) + }) + .collect::>() + .into_iter() + .map(|handle| handle.join().expect("analysis worker panicked")) + .collect::>() + }); + chunks.drain(..).flatten().collect() +} + +fn normalized_braille_whitespace(text: &str) -> String { + text.chars() + .map(|ch| match ch { + ' ' | '\t' | '\r' | '\n' | '\u{00a0}' | '\u{3000}' => '\u{2800}', + _ => ch, + }) + .collect() +} + +/// Correct only the ordering defect justified by Korean rules 28 appendix and 29: +/// the Korean roman indicator must precede UEB grade-1/capital indicators. +fn roman_before_capital_order(text: &str) -> String { + text.replace("⠠⠠⠠⠴", "⠴⠠⠠⠠") + .replace("⠰⠠⠠⠴", "⠴⠰⠠⠠") + .replace("⠠⠠⠴", "⠴⠠⠠") +} + +fn conflicting_inputs(cases: &[LocatedCase]) -> (usize, BTreeSet) { + let mut references = BTreeMap::>::new(); + for located in cases { + references + .entry(located.case.input.clone()) + .or_default() + .insert(located.case.unicode.clone()); + } + let duplicate_count = cases.len().saturating_sub(references.len()); + let conflicting = references + .into_iter() + .filter_map(|(input, values)| (values.len() > 1).then_some(input)) + .collect(); + (duplicate_count, conflicting) +} + +fn classify(encoded: &EncodedCase, conflicting: &BTreeSet) -> (PrimaryClass, Reason) { + let expected = &encoded.located.case.unicode; + match &encoded.actual { + Ok(actual) if actual == expected => (PrimaryClass::Exact, Reason::Exact), + _ if conflicting.contains(&encoded.located.case.input) => ( + PrimaryClass::CorpusSuspect, + Reason::ConflictingDuplicateReference, + ), + Ok(actual) + if normalized_braille_whitespace(actual) == normalized_braille_whitespace(expected) => + { + ( + PrimaryClass::ComparisonMethod, + Reason::BrailleWhitespaceEquivalent, + ) + } + _ if encoded + .nfc_actual + .as_ref() + .is_some_and(|result| result.as_ref().is_ok_and(|actual| actual == expected)) => + { + (PrimaryClass::ComparisonMethod, Reason::NfcInputEquivalent) + } + _ if encoded + .nfkc_actual + .as_ref() + .is_some_and(|result| result.as_ref().is_ok_and(|actual| actual == expected)) => + { + (PrimaryClass::ComparisonMethod, Reason::NfkcInputEquivalent) + } + Ok(actual) if roman_before_capital_order(actual) == *expected => ( + PrimaryClass::ImplementationDefect, + Reason::RomanIndicatorAfterCapitalIndicator, + ), + Err(_) => (PrimaryClass::ImplementationDefect, Reason::EncodingError), + Ok(_) + if encoded + .located + .case + .input + .chars() + .any(|ch| ch.is_ascii_alphabetic()) => + { + ( + PrimaryClass::PendingRuleReview, + Reason::ForeignTextRuleReview, + ) + } + Ok(_) + if encoded + .located + .case + .input + .chars() + .any(|ch| ch.is_ascii_digit()) => + { + (PrimaryClass::PendingRuleReview, Reason::NumberRuleReview) + } + Ok(_) + if encoded + .located + .case + .input + .chars() + .any(is_delimiter_or_quote) => + { + ( + PrimaryClass::PendingRuleReview, + Reason::PunctuationRuleReview, + ) + } + Ok(_) => (PrimaryClass::PendingRuleReview, Reason::KoreanRuleReview), + } +} + +fn is_delimiter_or_quote(ch: char) -> bool { + matches!( + ch, + '(' | ')' | '[' | ']' | '{' | '}' | '“' | '”' | '‘' | '’' | '"' | '\'' + ) +} + +fn enum_key(value: &T) -> String { + serde_json::to_value(value) + .expect("enum serialization must succeed") + .as_str() + .expect("enum must serialize as a string") + .to_string() +} + +fn excerpt_pair(expected: &str, actual: &str) -> (String, String) { + let expected_chars = expected.chars().collect::>(); + let actual_chars = actual.chars().collect::>(); + let first_diff = expected_chars + .iter() + .zip(&actual_chars) + .position(|(left, right)| left != right) + .unwrap_or(expected_chars.len().min(actual_chars.len())); + let start = first_diff.saturating_sub(8); + let expected_excerpt = expected_chars.iter().skip(start).take(24).collect(); + let actual_excerpt = actual_chars.iter().skip(start).take(24).collect(); + (expected_excerpt, actual_excerpt) +} + +fn analyze( + cases: Vec, + encoded: Vec, + sample_limit: usize, +) -> AnalysisReport { + let (duplicate_inputs, conflicting) = conflicting_inputs(&cases); + let mut primary_classes = BTreeMap::new(); + let mut reasons = BTreeMap::new(); + let mut encoding_error_messages = BTreeMap::new(); + let mut singleton_error_characters = BTreeMap::new(); + let mut singleton_error_cache = BTreeMap::::new(); + let mut traits = BTreeMap::new(); + let mut shards = BTreeMap::::new(); + let mut samples = BTreeMap::>::new(); + let mut exact = 0usize; + + for item in &encoded { + let (primary, reason) = classify(item, &conflicting); + let primary_key = enum_key(&primary); + let reason_key = enum_key(&reason); + *primary_classes.entry(primary_key).or_insert(0) += 1; + *reasons.entry(reason_key.clone()).or_insert(0) += 1; + + let shard = shards.entry(item.located.shard.clone()).or_default(); + shard.total += 1; + if primary == PrimaryClass::Exact { + exact += 1; + shard.exact += 1; + continue; + } + + let input = &item.located.case.input; + if primary == PrimaryClass::ImplementationDefect + && let Err(error) = &item.actual + { + *encoding_error_messages.entry(error.clone()).or_insert(0) += 1; + let unique_chars = input.chars().collect::>(); + for ch in unique_chars { + let fails_alone = *singleton_error_cache + .entry(ch) + .or_insert_with(|| braillify::encode_to_unicode(&ch.to_string()).is_err()); + if fails_alone { + let key = format!("U+{:04X} {ch}", ch as u32); + *singleton_error_characters.entry(key).or_insert(0) += 1; + } + } + } + for (name, present) in [ + ( + "contains_ascii_letters", + input.chars().any(|ch| ch.is_ascii_alphabetic()), + ), + ( + "contains_ascii_digits", + input.chars().any(|ch| ch.is_ascii_digit()), + ), + ( + "contains_delimiter_or_quote", + input.chars().any(is_delimiter_or_quote), + ), + ( + "contains_non_ascii_whitespace", + input.chars().any(|ch| ch.is_whitespace() && ch != ' '), + ), + ("input_not_nfc", input.nfc().ne(input.chars())), + ("input_not_nfkc", input.nfkc().ne(input.chars())), + ] { + if present { + *traits.entry(name.to_string()).or_insert(0) += 1; + } + } + + let bucket = samples.entry(reason_key).or_default(); + if bucket.len() < sample_limit { + let (actual_excerpt, error) = match &item.actual { + Ok(actual) => (actual.as_str(), None), + Err(error) => ("", Some(error.clone())), + }; + let (expected_excerpt, actual_excerpt) = + excerpt_pair(&item.located.case.unicode, actual_excerpt); + bucket.push(Sample { + shard: item.located.shard.clone(), + index: item.located.index, + input: input.clone(), + expected_excerpt, + actual_excerpt, + error, + }); + } + } + + let total = encoded.len(); + AnalysisReport { + corpus: "NIKL Korean-Korean Braille Parallel Corpus 2025 v1.0", + total, + exact, + mismatch: total - exact, + exact_percent: exact as f64 / total as f64 * 100.0, + duplicate_inputs, + conflicting_duplicate_inputs: conflicting.len(), + primary_classes, + reasons, + encoding_error_messages, + singleton_error_characters, + overlapping_traits: traits, + shards, + samples, + } +} + +fn markdown(report: &AnalysisReport) -> String { + let mut text = String::new(); + text.push_str("# NIKL 2025 v1.0 corpus analysis\n\n"); + text.push_str( + "> Generated by `cargo run --release -p braillify --example nikl_corpus_analyze`. \ + The tool reads only `input` and `unicode`; it never loads or compares `world`.\n\n", + ); + text.push_str("## Current measurement\n\n"); + text.push_str("| Metric | Count |\n|---|---:|\n"); + text.push_str(&format!("| Total | {} |\n", report.total)); + text.push_str(&format!("| Exact | {} |\n", report.exact)); + text.push_str(&format!("| Mismatch | {} |\n", report.mismatch)); + text.push_str(&format!( + "| Exact accuracy | {:.2}% |\n", + report.exact_percent + )); + text.push_str(&format!( + "| Duplicate records | {} |\n", + report.duplicate_inputs + )); + text.push_str(&format!( + "| Inputs with conflicting references | {} |\n\n", + report.conflicting_duplicate_inputs + )); + + text.push_str("## Classification policy\n\n"); + text.push_str( + "Primary classes are evidence gates, not permissions to change the engine. \ + `implementation_defect` is restricted to defects independently confirmed from the PDF \ + (the rules 28/29 roman-indicator ordering signature) and actual encoding errors. \ + `pending_rule_review` contains foreign-text, number, punctuation, and Korean candidates \ + that have not yet been resolved against the PDF. `corpus_suspect` is reserved for \ + independently detectable contradictions such as one input having multiple references. \ + `comparison_method` requires equality after a named normalization.\n\n", + ); + text.push_str("| Primary class | Count |\n|---|---:|\n"); + for (name, count) in &report.primary_classes { + text.push_str(&format!("| `{name}` | {count} |\n")); + } + text.push_str("\n| Reproducible reason | Count |\n|---|---:|\n"); + for (name, count) in &report.reasons { + text.push_str(&format!("| `{name}` | {count} |\n")); + } + + text.push_str("\n## Encoding-error diagnostics\n\n"); + text.push_str( + "These are overlapping diagnostics for `implementation_defect` encoding errors, not additional primary classes. \ + A singleton error character is a character that also fails when encoded by itself.\n\n", + ); + text.push_str("| Error message | Cases |\n|---|---:|\n"); + for (name, count) in &report.encoding_error_messages { + text.push_str(&format!("| `{name}` | {count} |\n")); + } + text.push_str("\n| Singleton error character | Cases containing it |\n|---|---:|\n"); + for (name, count) in &report.singleton_error_characters { + text.push_str(&format!("| `{name}` | {count} |\n")); + } + + text.push_str("\n## Shards\n\n| Shard | Exact | Total | Accuracy |\n|---|---:|---:|---:|\n"); + for (name, stats) in &report.shards { + text.push_str(&format!( + "| `{name}` | {} | {} | {:.2}% |\n", + stats.exact, + stats.total, + stats.exact as f64 / stats.total as f64 * 100.0 + )); + } + + text.push_str("\n## Overlapping mismatch traits\n\n| Trait | Count |\n|---|---:|\n"); + for (name, count) in &report.overlapping_traits { + text.push_str(&format!("| `{name}` | {count} |\n")); + } + + text.push_str("\n## Samples\n\n"); + for (reason, samples) in &report.samples { + text.push_str(&format!("### `{reason}`\n\n")); + for sample in samples { + let input = sample.input.chars().take(180).collect::(); + text.push_str(&format!( + "- `{}` #{}: {}\n - expected: `{}`\n - actual: `{}`{}\n", + sample.shard, + sample.index, + input.replace('`', "\\`"), + sample.expected_excerpt, + sample.actual_excerpt, + sample + .error + .as_ref() + .map_or_else(String::new, |error| format!("\n - error: `{error}`")) + )); + } + text.push('\n'); + } + + text.push_str("## PDF-derived state gates\n\n"); + text.push_str( + "The rule 37 example `그는 Can you help me?라고 도움을 요청했다.` distinguishes the first \ + word after the roman indicator (`Can`, whose whole-word sign is suppressed) from the \ + interior word `you` in the uninterrupted ASCII phrase (whose UEB wordsign is retained). \ + The `prev_is_ascii_word && next_is_ascii_word` gate expresses that phrase-interior \ + position rather than matching an input string.\n\n\ + The rule 39 example `What is 김치 in English?` resumes the surrounding English passage \ + after the Korean span. The `english_dominant_wrap_active` gate therefore retains the UEB \ + wordsign for the resumed `in`, instead of treating it as a fresh rule 37 entry word.\n\n", + ); + + text.push_str("## Rule evidence and change log\n\n"); + text.push_str( + "| Stage | Standard cases | Corpus exact | Corpus accuracy | Evidence |\n\ + |---|---:|---:|---:|---|\n\ + | Parent commit `3cfeae0` | 5,141/5,141 | 57,732/83,528 | 69.12% | Reproduced with release tests |\n\ + | Rules 28/29 indicator ordering | 5,141/5,141 | 61,652/83,528 | 73.81% | Roman indicator now precedes UEB grade-1/capital indicators; rule 35 roman-number continuity retained |\n\ + | Rules 37/39 shared UEB groupsign algorithm | 5,141/5,141 | 63,239/83,528 | 75.71% | Korean Roman sections reuse UEB preference/morphology rules; entry wordsigns and English-dominant resume are state-gated |\n", + ); + text.push_str( + "\nEngine changes must add a row only after both the 5,141-case standard suite and \ + this full analysis have been rerun. Suspect-reference clusters stay in this report; \ + they are not engine targets without independent PDF evidence.\n", + ); + text +} + +fn write_file(path: &Path, contents: &str) -> Result<(), String> { + if let Some(parent) = path.parent() { + fs::create_dir_all(parent) + .map_err(|error| format!("cannot create {}: {error}", parent.display()))?; + } + fs::write(path, contents).map_err(|error| format!("cannot write {}: {error}", path.display())) +} + +fn run() -> Result<(), String> { + let config = Config::parse()?; + let cases = load_cases()?; + let encoded = encode_cases(&cases, config.threads); + let report = analyze(cases, encoded, config.sample_limit); + let json = serde_json::to_string_pretty(&report) + .map_err(|error| format!("cannot serialize analysis JSON: {error}"))?; + write_file(&config.json_path, &json)?; + write_file(&config.report_path, &markdown(&report))?; + println!( + "NIKL corpus: {}/{} exact ({:.2}%), report={}, json={}", + report.exact, + report.total, + report.exact_percent, + config.report_path.display(), + config.json_path.display() + ); + Ok(()) +} + +fn main() { + if let Err(error) = run() { + eprintln!("nikl_corpus_analyze: {error}"); + std::process::exit(1); + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[rstest::rstest] + #[case::zero_shards(0, 0, Some("no NIKL corpus shards"))] + #[case::zero_cases(1, 0, Some("zero cases"))] + #[case::nonempty_corpus(4, 83_528, None)] + fn corpus_shape_must_be_nonempty( + #[case] shard_count: usize, + #[case] case_count: usize, + #[case] expected_error: Option<&str>, + ) { + let result = validate_corpus_shape(shard_count, case_count); + match expected_error { + Some(expected) => assert!(result.unwrap_err().contains(expected)), + None => assert_eq!(result, Ok(())), + } + } + + #[test] + fn whitespace_normalization_does_not_change_braille_cells() { + assert_eq!(normalized_braille_whitespace("⠁ ⠃"), "⠁⠀⠃"); + } + + #[test] + fn roman_indicator_moves_before_capital_word_indicator() { + assert_eq!(roman_before_capital_order("⠠⠠⠴⠁⠃"), "⠴⠠⠠⠁⠃"); + } +} diff --git a/libs/braillify/src/rules/emit.rs b/libs/braillify/src/rules/emit.rs index 69ee7d52..40d96ced 100644 --- a/libs/braillify/src/rules/emit.rs +++ b/libs/braillify/src/rules/emit.rs @@ -88,6 +88,60 @@ fn token_is_math_word(token: Option<&Token<'_>>) -> bool { } } +/// Find the word governed by a run of UEB grade-1/capital mode markers. +/// +/// Korean rule 29 requires the roman indicator before the roman text, while +/// rule 28 appendix places UEB capitalization indicators immediately before +/// the capitalized roman word. Token rewriting may discover capitalization +/// before the character emitter discovers a new roman section, so the emitter +/// must establish roman mode before it emits these UEB prefix markers. +fn roman_word_after_prefix<'a>( + tokens: &'a [Token<'a>], + prefix_index: usize, +) -> Option<&'a WordToken<'a>> { + for token in tokens.iter().skip(prefix_index + 1) { + match token { + Token::Mode( + ModeEvent::Grade1Indicator | ModeEvent::CapsWord | ModeEvent::CapsPassageStart, + ) => continue, + Token::Word(word) => return Some(word), + _ => return None, + } + } + None +} + +fn enter_roman_before_ueb_prefix( + tokens: &[Token<'_>], + prefix_index: usize, + event: ModeEvent, + state: &mut EncoderState, + result: &mut Vec, +) { + let is_ueb_prefix = matches!( + event, + ModeEvent::Grade1Indicator | ModeEvent::CapsWord | ModeEvent::CapsPassageStart + ); + let roman_word = + roman_word_after_prefix(tokens, prefix_index).filter(|word| word.meta.starts_with_ascii); + + if is_ueb_prefix + && state.english_indicator + && !state.is_english + && let Some(word) = roman_word + { + // Use the shared rule-29/35 transition so a capital word after a number + // in the same roman section (`KBS 1 TV`) resumes without a second + // roman indicator, while a genuinely new section receives one. + roman_mode::enter_english_if_starting( + state, + &word.chars, + word.meta.has_ascii_alphabetic, + result, + ); + } +} + /// PDF 수학 — `Word(math)+Space+Word(=/==/관계)+Space+Word(math)` 패턴에서 /// 등호 양옆 Space 토큰을 묵음 처리한다. 점역 결과는 `expr⠒⠒expr`로 인접한다. fn is_math_operator_space_suppression<'a>(tokens: &'a [Token<'a>], space_idx: usize) -> bool { @@ -170,7 +224,10 @@ pub fn emit(ir: &mut DocumentIR, char_engine: &mut RuleEngine) -> Result result.push(0); } } - Token::Mode(event) => emit_mode_event(*event, &mut ir.state, &mut result), + Token::Mode(event) => { + enter_roman_before_ueb_prefix(&ir.tokens, idx, *event, &mut ir.state, &mut result); + emit_mode_event(*event, &mut ir.state, &mut result); + } Token::Fraction(frac) => { if let Some(ref w) = frac.whole { result.extend(fraction::encode_mixed_fraction( @@ -769,6 +826,61 @@ mod tests { assert_eq!(out, vec![52, 48, 32, 32, 32, 32, 32, 32, 4, 48]); } + /// Korean rules 28 appendix and 29: in Korean prose the roman indicator + /// precedes the UEB capital-word indicator (`0,,KTX`, not `,,0KTX`). + #[test] + fn roman_indicator_precedes_capital_prefix_without_explicit_entry_token() { + let chars = "KTX".chars().collect::>(); + let mut ir = DocumentIR { + tokens: vec![ + Token::Mode(ModeEvent::CapsWord), + Token::Word(WordToken { + text: Cow::Borrowed("KTX"), + chars: chars.clone(), + meta: super::super::token::WordMeta::from_chars(&chars), + }), + ], + state: EncoderState::new(true), + }; + let mut engine = make_char_engine(); + + let out = emit(&mut ir, &mut engine).unwrap(); + + assert!(out.starts_with(&[52, 32, 32])); + } + + #[test] + fn explicit_roman_entry_is_not_duplicated_before_capital_prefix() { + let chars = "KTX".chars().collect::>(); + let mut ir = DocumentIR { + tokens: vec![ + Token::Mode(ModeEvent::EnterEnglish), + Token::Mode(ModeEvent::CapsWord), + Token::Word(WordToken { + text: Cow::Borrowed("KTX"), + chars: chars.clone(), + meta: super::super::token::WordMeta::from_chars(&chars), + }), + ], + state: EncoderState::new(true), + }; + let mut engine = make_char_engine(); + + let out = emit(&mut ir, &mut engine).unwrap(); + + assert!(out.starts_with(&[52, 32, 32])); + assert_eq!(out.iter().filter(|byte| **byte == 52).count(), 1); + } + + /// Korean rule 35 PDF example: numbers do not split a roman section, so + /// the later capital word resumes without another roman indicator. + #[test] + fn capital_prefix_after_roman_number_chain_does_not_reenter_roman_mode() { + let out = encode("KBS 1 TV 좀 켜 주세요.").unwrap(); + + assert_eq!(out.iter().filter(|byte| **byte == 52).count(), 1); + } + #[test] fn fraction_token_encodes() { let mut ir = DocumentIR { diff --git a/libs/braillify/src/rules/english_ueb/engine.rs b/libs/braillify/src/rules/english_ueb/engine.rs index b091d8cb..cb218366 100644 --- a/libs/braillify/src/rules/english_ueb/engine.rs +++ b/libs/braillify/src/rules/english_ueb/engine.rs @@ -193,6 +193,59 @@ impl EnglishUebEngine { Self { contractions } } + /// Encode one Roman word embedded in Korean text according to Korean rule 37. + /// + /// At a rule-37 Roman entry, whole-word signs/shortforms are suppressed while + /// UEB multi-letter groupsigns remain. Subsequent standalone words and a + /// rule-39 return to English-dominant context may use wordsigns. Keeping both + /// paths on the same contraction engine makes rule 10 preference and + /// morphology gates identical. Roman mode transitions remain the Korean + /// engine's job. + pub(crate) fn encode_korean_word( + &self, + chars: &[char], + suppress_caps: bool, + standing_alone: bool, + word_initial: bool, + digit_adjacent: bool, + ) -> Option> { + let mut out = Vec::new(); + let lower: Vec = chars.iter().flat_map(|ch| ch.to_lowercase()).collect(); + let lower_word: String = lower.iter().collect(); + if !standing_alone && super::rule_10_5::wordsign(&lower_word).is_some() { + if !suppress_caps { + match classify_caps(chars)? { + Caps::None => {} + Caps::Single => out.push(CAPITAL), + Caps::Word => out.extend([CAPITAL, CAPITAL]), + } + } + out.extend(super::rule_10_9::encode_korean_groupsigns( + &lower, + &self.contractions, + word_initial, + word_initial, + )?); + return Some(out); + } + self.encode_word( + chars, + WordContext { + standing_alone, + upper_usable: standing_alone, + shortform_usable: false, + allow_longer_shortforms: false, + lower_usable: standing_alone, + suppress_caps, + word_initial, + restricted_prefix_boundary: word_initial, + digit_adjacent, + }, + &mut out, + )?; + Some(out) + } + /// Encode a token stream. Returns `None` if any token is unsupported /// (a number, a symbol, or a mixed-case word), so the legacy path — which /// handles those — takes over. `explicit_english` is true only under an diff --git a/libs/braillify/src/rules/english_ueb/rule_10_9.rs b/libs/braillify/src/rules/english_ueb/rule_10_9.rs index 3ca0a74b..8721da76 100644 --- a/libs/braillify/src/rules/english_ueb/rule_10_9.rs +++ b/libs/braillify/src/rules/english_ueb/rule_10_9.rs @@ -77,6 +77,7 @@ pub fn encode_with_longer_shortforms( false, true, false, + false, ) } @@ -96,6 +97,30 @@ pub fn encode_with_optional_longer_shortforms( false, allow_longer_shortforms, false, + false, + ) +} + +/// Korean rule 37 word body: use UEB multi-letter groupsigns, but do not let a +/// groupsign that is also a lower wordsign consume the entire first Roman word. +/// This is a structural gate, not a word-output table: inner groupsigns such as +/// `en` in `enough` remain available. +pub(crate) fn encode_korean_groupsigns( + word: &[char], + contractions: &ContractionEngine, + suppress_initial_ing: bool, + restricted_prefix_boundary: bool, +) -> Option> { + encode_with_constraints( + word, + contractions, + suppress_initial_ing, + restricted_prefix_boundary, + None, + false, + false, + false, + true, ) } @@ -119,6 +144,7 @@ pub fn encode_anglicised_word( false, true, true, + false, ) } @@ -159,6 +185,7 @@ pub fn encode_with_division( first_line_has_upper_prefix, true, false, + false, )?); return Some(out); } @@ -171,6 +198,7 @@ pub fn encode_with_division( first_line_has_upper_prefix, true, false, + false, ) } @@ -187,6 +215,7 @@ fn encode_with_constraints( first_line_has_upper_prefix: bool, allow_longer_shortforms: bool, relax_shortforms: bool, + suppress_whole_word_wordsign: bool, ) -> Option> { let n = word.len(); // §10.11.1: a contraction must not bridge the seam of a compound word. Look up @@ -240,6 +269,7 @@ fn encode_with_constraints( first_line_has_upper_prefix, allow_longer_shortforms, relax_shortforms, + suppress_whole_word_wordsign, ) { let next = pos + consumed; let total = cells.len() + cost[next]; @@ -294,6 +324,7 @@ fn candidate_moves( first_line_has_upper_prefix: bool, allow_longer_shortforms: bool, relax_shortforms: bool, + suppress_whole_word_wordsign: bool, ) -> Vec<(Vec, usize, u16)> { let mut moves = Vec::new(); // §10.9 longer-word shortform placement (preferred on a cost tie → priority 0). @@ -316,6 +347,17 @@ fn candidate_moves( } let protected_here = inside_protected[pos]; for m in contractions.matches_at(word, pos) { + // Korean rule 37: immediately after the Roman indicator, a lower + // wordsign is written with alphabet/multi-letter groupsigns instead. + // Reject only a contraction consuming the complete wordsign; inner + // groupsigns remain candidates (`enough` keeps `en` and `gh`). + if suppress_whole_word_wordsign + && pos == 0 + && m.consumed == word.len() + && super::rule_10_5::wordsign(&word.iter().collect::()).is_some() + { + continue; + } // §10.11.1: a GROUPSIGN must not bridge a compound-word seam — // `an[t·h]ill`, `cart[·h]orse`, `nor[the]ast` spell the bridging digraph // out. An initial-letter contraction (§10.7 `upon`, priority 55) and a @@ -854,6 +896,7 @@ mod tests { false, false, false, + false, ); assert!(moves.iter().all(|(cells, consumed, _)| { *consumed != pattern.len() || cells != &vec![decode_unicode('⠆')] @@ -982,6 +1025,7 @@ mod tests { false, false, false, + false, ); assert!(moves.iter().any(|(cells, consumed, priority)| { @@ -1175,6 +1219,7 @@ mod tests { false, false, false, + false, ); assert!(moves.iter().any(|(cells_, consumed, priority)| { *cells_ == cells("⠼⠮") && *consumed == 1 && *priority == u16::MAX @@ -1196,6 +1241,7 @@ mod tests { false, true, false, + false, ); assert_eq!(result, None); } diff --git a/libs/braillify/src/rules/english_ueb/span.rs b/libs/braillify/src/rules/english_ueb/span.rs index 70bf66df..ceb52737 100644 --- a/libs/braillify/src/rules/english_ueb/span.rs +++ b/libs/braillify/src/rules/english_ueb/span.rs @@ -14,6 +14,10 @@ use super::korean_context::{KoreanPrefixInput, match_korean_prefix}; use crate::english::encode_english; +use std::sync::LazyLock; + +static KOREAN_WORD_ENGINE: LazyLock = + LazyLock::new(super::engine::EnglishUebEngine::new); /// One unit of Korean-context English output: a 제37항-restricted UEB contraction /// when one begins at `input.pos`, otherwise the single §28 letter cell. @@ -29,6 +33,25 @@ pub(crate) struct KoreanSpanUnit { pub(crate) contracted: bool, } +/// Encode a complete ASCII Roman run in Korean context with the shared UEB +/// contraction engine. Korean rule 37 disables wordsigns and shortforms while +/// retaining multi-letter groupsigns; the engine entry point enforces that gate. +pub(crate) fn encode_korean_word( + chars: &[char], + suppress_caps: bool, + standing_alone: bool, + word_initial: bool, + digit_adjacent: bool, +) -> Option> { + KOREAN_WORD_ENGINE.encode_korean_word( + chars, + suppress_caps, + standing_alone, + word_initial, + digit_adjacent, + ) +} + /// Encode the English unit beginning at `input.pos` to UEB cells. /// /// Returns `Err` only when the position is not an encodable English letter diff --git a/libs/braillify/src/rules/korean/rule_28.rs b/libs/braillify/src/rules/korean/rule_28.rs index e681e4d7..9cdfebd5 100644 --- a/libs/braillify/src/rules/korean/rule_28.rs +++ b/libs/braillify/src/rules/korean/rule_28.rs @@ -11,7 +11,7 @@ use crate::char_struct::CharType; use crate::rules::RuleMeta; use crate::rules::context::RuleContext; use crate::rules::english_ueb::korean_context::KoreanPrefixInput; -use crate::rules::english_ueb::span::encode_korean_unit; +use crate::rules::english_ueb::span::{encode_korean_unit, encode_korean_word}; use crate::rules::traits::{BrailleRule, Phase, RuleResult}; pub static META: RuleMeta = RuleMeta { @@ -88,6 +88,81 @@ impl BrailleRule for Rule28 { } } + // 제37항: a Roman section in Korean text spells the word with UEB + // alphabet signs and multi-letter groupsigns, while suppressing UEB + // whole-word contractions. Encode each contiguous ASCII letter run in + // one pass so the shared UEB preference/morphology algorithm can choose + // contractions across the whole word. Apostrophe continuations retain + // the legacy position-aware path because they are not fresh word starts. + let starts_ascii_run = c.is_ascii_alphabetic() + && ctx + .index + .checked_sub(1) + .and_then(|index| ctx.word_chars.get(index)) + .is_none_or(|previous| !previous.is_ascii_alphabetic()); + let follows_apostrophe = ctx + .index + .checked_sub(1) + .and_then(|index| ctx.word_chars.get(index)) + .is_some_and(|previous| matches!(previous, '\'' | '\u{2019}')); + if starts_ascii_run && !follows_apostrophe { + let run_end = ctx.index + + ctx.word_chars[ctx.index..] + .iter() + .take_while(|ch| ch.is_ascii_alphabetic()) + .count(); + let run = &ctx.word_chars[ctx.index..run_end]; + let caps_already_emitted = + ctx.is_all_uppercase && ctx.word_len() >= 2 && ctx.ascii_starts_at_beginning; + let is_whole_lowercase_word = ctx.index == 0 + && run_end == ctx.word_chars.len() + && run.iter().all(|ch| ch.is_ascii_lowercase()); + let prev_is_ascii_word = !ctx.prev_word.is_empty() + && ctx.prev_word.chars().all(|ch| ch.is_ascii_alphabetic()); + let next_is_ascii_word = ctx.remaining_words.first().is_some_and(|word| { + !word.is_empty() && word.chars().all(|ch| ch.is_ascii_alphabetic()) + }); + // Rule 37's PDF example, "그는 Can you help me?라고 도움을 요청했다.", + // suppresses a whole-word sign for the first Roman word (`Can`) but retains + // the UEB wordsign for the phrase-interior `you`. The adjacent-ASCII-word + // gate models that structural position. Rule 39's "What is 김치 in English?" + // resumes the surrounding English passage after Korean, so the persistent + // English-dominant gate retains the resumed `in` wordsign. Neither gate + // depends on the example's literal words. + let standalone_wordsign = is_whole_lowercase_word + && (ctx.state.english_dominant_wrap_active + || (prev_is_ascii_word && next_is_ascii_word)); + let word_initial = ctx.index == 0 + || ctx.word_chars.get(ctx.index - 1).is_some_and(|previous| { + matches!( + previous, + '(' | '[' | '{' | '\u{2018}' | '\u{201c}' | '"' | '-' + ) + }); + let digit_adjacent = ctx + .index + .checked_sub(1) + .and_then(|index| ctx.word_chars.get(index)) + .is_some_and(|ch| ch.is_ascii_digit()) + || ctx + .word_chars + .get(run_end) + .is_some_and(|ch| ch.is_ascii_digit()); + if let Some(cells) = encode_korean_word( + run, + caps_already_emitted, + standalone_wordsign, + word_initial, + digit_adjacent, + ) { + ctx.emit_slice(&cells); + *ctx.skip_count = run.len().saturating_sub(1); + ctx.state.is_english = true; + ctx.state.needs_english_continuation = false; + return Ok(RuleResult::Consumed); + } + } + // Uppercase indicators (single/consecutive uppercase run) if (!ctx.is_all_uppercase || ctx.word_len() < 2 || !ctx.ascii_starts_at_beginning) && !ctx.state.is_big_english @@ -139,7 +214,9 @@ impl BrailleRule for Rule28 { #[cfg(test)] mod tests { use super::*; + use crate::rules::context::EncodingMode; use crate::unicode::decode_unicode; + use crate::{EncodeOptions, encode_with_options}; /// 제28항 — 영문자 점역. 소문자/대문자 모두 동일 점형으로 인코딩. #[rstest::rstest] @@ -173,6 +250,37 @@ mod tests { assert_eq!(uppercase_indicators(single, is_word, run), expected); } + /// 제37항 PDF examples: Korean-context Roman words suppress whole-word + /// contractions while retaining their applicable multi-letter groupsigns. + #[rstest::rstest] + #[case::initial_letter_groupsign("every", &[52, 16, 17, 61, 50])] + #[case::lower_and_strong_groupsigns("enough", &[52, 34, 51, 35, 50])] + #[case::strong_contraction_inside_word("rather", &[52, 23, 1, 46, 23, 50])] + #[case::entry_lower_wordsign_spelled_as_letters("in", &[52, 10, 29, 50])] + fn korean_roman_words_share_ueb_groupsign_algorithm( + #[case] input: &str, + #[case] expected: &[u8], + ) { + let options = EncodeOptions { + default_mode: Some(EncodingMode::Korean), + }; + assert_eq!(encode_with_options(input, &options).unwrap(), expected); + } + + #[test] + fn english_dominant_wrap_resumes_ueb_wordsigns_after_korean_span() { + let mut owned = crate::test_helpers::CtxOwned::for_text("in", true); + owned.state.is_english = true; + owned.state.english_dominant_wrap_active = true; + let mut ctx = owned.ctx_at(0); + + assert!(matches!( + Rule28.apply(&mut ctx).unwrap(), + RuleResult::Consumed + )); + assert_eq!(owned.result, vec![20]); + } + #[test] fn apply_skips_non_korean() { let mut owned = crate::test_helpers::CtxOwned::for_text("A", false); From 751dc24d10f93b52aa374254be7695e975aa4151 Mon Sep 17 00:00:00 2001 From: devfive Date: Tue, 1 Sep 2026 04:07:59 +0900 Subject: [PATCH 02/77] Support Unicode compatibility unit symbols --- docs/corpus-analysis/NIKL_2025_V1.md | 115 ++++---- .../braillify/examples/nikl_corpus_analyze.rs | 128 ++++++++- libs/braillify/src/rules/korean/rule_69.rs | 265 +++++++++++++++++- 3 files changed, 436 insertions(+), 72 deletions(-) diff --git a/docs/corpus-analysis/NIKL_2025_V1.md b/docs/corpus-analysis/NIKL_2025_V1.md index fd92d1e9..37d28afd 100644 --- a/docs/corpus-analysis/NIKL_2025_V1.md +++ b/docs/corpus-analysis/NIKL_2025_V1.md @@ -7,9 +7,9 @@ | Metric | Count | |---|---:| | Total | 83528 | -| Exact | 63239 | -| Mismatch | 20289 | -| Exact accuracy | 75.71% | +| Exact | 63388 | +| Mismatch | 20140 | +| Exact accuracy | 75.89% | | Duplicate records | 0 | | Inputs with conflicting references | 0 | @@ -19,18 +19,18 @@ Primary classes are evidence gates, not permissions to change the engine. `imple | Primary class | Count | |---|---:| -| `comparison_method` | 394 | -| `exact` | 63239 | -| `implementation_defect` | 434 | -| `pending_rule_review` | 19461 | +| `comparison_method` | 301 | +| `exact` | 63388 | +| `implementation_defect` | 226 | +| `pending_rule_review` | 19613 | | Reproducible reason | Count | |---|---:| -| `encoding_error` | 434 | -| `exact` | 63239 | -| `foreign_text_rule_review` | 19272 | -| `nfkc_input_equivalent` | 394 | -| `number_rule_review` | 176 | +| `encoding_error` | 226 | +| `exact` | 63388 | +| `foreign_text_rule_review` | 19422 | +| `nfkc_input_equivalent` | 301 | +| `number_rule_review` | 178 | | `punctuation_rule_review` | 13 | ## Encoding-error diagnostics @@ -40,64 +40,54 @@ These are overlapping diagnostics for `implementation_defect` encoding errors, n | Error message | Cases | |---|---:| | `Invalid character` | 26 | -| `Invalid symbol character` | 408 | +| `Invalid symbol character` | 200 | -| Singleton error character | Cases containing it | +| Error family | Cases | |---|---:| -| `U+02D1 ˑ` | 1 | -| `U+2025 ‥` | 1 | -| `U+2113 ℓ` | 12 | -| `U+2160 Ⅰ` | 4 | -| `U+2161 Ⅱ` | 14 | -| `U+2162 Ⅲ` | 6 | -| `U+2163 Ⅳ` | 5 | -| `U+2166 Ⅶ` | 1 | -| `U+2502 │` | 1 | -| `U+25B2 ▲` | 1 | -| `U+25B4 ▴` | 1 | -| `U+260F ☏` | 3 | -| `U+2665 ♥` | 1 | -| `U+2E31 ⸱` | 2 | -| `U+302E 〮` | 1 | -| `U+3214 ㈔` | 2 | -| `U+321C ㈜` | 179 | -| `U+338D ㎍` | 12 | -| `U+338F ㎏` | 50 | -| `U+3390 ㎐` | 1 | -| `U+3393 ㎓` | 24 | -| `U+3394 ㎔` | 1 | -| `U+3396 ㎖` | 9 | -| `U+339A ㎚` | 14 | -| `U+339B ㎛` | 4 | -| `U+339C ㎜` | 12 | -| `U+339D ㎝` | 12 | -| `U+33A2 ㎢` | 14 | -| `U+33A5 ㎥` | 42 | -| `U+33A7 ㎧` | 2 | -| `U+33B8 ㎸` | 3 | -| `U+33BD ㎽` | 1 | -| `U+33BE ㎾` | 9 | -| `U+33BF ㎿` | 9 | -| `U+33C3 ㏃` | 4 | -| `U+33DC ㏜` | 1 | +| `enclosed_organization_mark` | 181 | +| `letterlike_unit_symbol` | 12 | +| `punctuation_or_layout_symbol` | 12 | +| `roman_numeral_presentation` | 26 | + +Families are diagnostics, not automatic normalization permissions. Rules 68/69 compatibility-unit support removed that error family from the current run; `enclosed_organization_mark` and layout symbols still have no confirmed rule. + +| Singleton error character | Cases containing it | NFKC decomposition | Family | +|---|---:|---|---| +| `U+02D1 ˑ` | 1 | `ˑ` | `punctuation_or_layout_symbol` | +| `U+2025 ‥` | 1 | `..` | `punctuation_or_layout_symbol` | +| `U+2113 ℓ` | 12 | `l` | `letterlike_unit_symbol` | +| `U+2160 Ⅰ` | 4 | `I` | `roman_numeral_presentation` | +| `U+2161 Ⅱ` | 14 | `II` | `roman_numeral_presentation` | +| `U+2162 Ⅲ` | 6 | `III` | `roman_numeral_presentation` | +| `U+2163 Ⅳ` | 5 | `IV` | `roman_numeral_presentation` | +| `U+2166 Ⅶ` | 1 | `VII` | `roman_numeral_presentation` | +| `U+2502 │` | 1 | `│` | `punctuation_or_layout_symbol` | +| `U+25B2 ▲` | 1 | `▲` | `punctuation_or_layout_symbol` | +| `U+25B4 ▴` | 1 | `▴` | `punctuation_or_layout_symbol` | +| `U+260F ☏` | 3 | `☏` | `punctuation_or_layout_symbol` | +| `U+2665 ♥` | 1 | `♥` | `punctuation_or_layout_symbol` | +| `U+2E31 ⸱` | 2 | `⸱` | `punctuation_or_layout_symbol` | +| `U+302E 〮` | 1 | `〮` | `punctuation_or_layout_symbol` | +| `U+3214 ㈔` | 2 | `(사)` | `enclosed_organization_mark` | +| `U+321C ㈜` | 179 | `(주)` | `enclosed_organization_mark` | ## Shards | Shard | Exact | Total | Accuracy | |---|---:|---:|---:| -| `sentence_01.json` | 18860 | 25000 | 75.44% | -| `sentence_02.json` | 18286 | 25000 | 73.14% | -| `sentence_03.json` | 19364 | 25000 | 77.46% | -| `sentence_04.json` | 6729 | 8528 | 78.90% | +| `sentence_01.json` | 18887 | 25000 | 75.55% | +| `sentence_02.json` | 18348 | 25000 | 73.39% | +| `sentence_03.json` | 19406 | 25000 | 77.62% | +| `sentence_04.json` | 6747 | 8528 | 79.12% | ## Overlapping mismatch traits | Trait | Count | |---|---:| -| `contains_ascii_digits` | 15849 | -| `contains_ascii_letters` | 20076 | -| `contains_delimiter_or_quote` | 20289 | -| `input_not_nfkc` | 1009 | +| `contains_ascii_digits` | 15700 | +| `contains_ascii_letters` | 19938 | +| `contains_delimiter_or_quote` | 20140 | +| `input_not_nfkc` | 860 | ## Samples @@ -160,8 +150,8 @@ These are overlapping diagnostics for `implementation_defect` encoding errors, n - expected: `⠈⠬⠩⠁⠀⠙⠪⠐⠥⠈⠪⠐⠗⠢⠵⠀⠠⠦⠈⠍⠈⠮⠀⠋` - actual: `` - error: `Invalid symbol character` -- `sentence_01.json` #2898: 한국농수산식품유통공사의 농수산유통정보(KAMIS)에 따르면 주키니 호박의 20일 평균 가격은 상품(도매가) 기준 10㎏당 1만580원으로 형성돼 있다. 출하가 재개된 지난 3일 평균가격인 1만9천500원에서 9천원 가까이 하락했다. - - expected: `⠚⠒⠈⠍⠁⠉⠿⠠⠍⠇⠒⠠⠕⠁⠙⠍⠢⠩⠓⠿⠈⠿⠇⠺` +- `sentence_01.json` #2929: 경상남도 김해시에 소재한 기업인 ㈜하톤(hearton)이 MBTI를 활용, 세계최초로 컬러테라피 조명을 통해 개인의 정신적인 건강에 도움을 줄 수 있는 ‘하톤’이라는 제품을 출시해 주목받고 있다. + - expected: `⠈⠻⠇⠶⠉⠢⠊⠥⠀⠈⠕⠢⠚⠗⠠⠕⠝⠀⠠⠥⠨⠗⠚⠒` - actual: `` - error: `Invalid symbol character` @@ -207,6 +197,10 @@ The rule 37 example `그는 Can you help me?라고 도움을 요청했다.` dist The rule 39 example `What is 김치 in English?` resumes the surrounding English passage after the Korean span. The `english_dominant_wrap_active` gate therefore retains the UEB wordsign for the resumed `in`, instead of treating it as a fresh rule 37 entry word. +## Rule 69 compatibility-unit scope + +The engine accepts 96 scientific/measurement glyphs from Unicode CJK Compatibility, derives their Roman spelling with NFKC, and applies rules 68/69 rather than whole-word UEB. The accepted glyph set and panic-free encoding property are fixed by inline tests. The official Unicode names distinguish `U+337A ㍺` SQUARE IU (accepted) from `U+33D1 ㏑` SQUARE LN, `U+33D2 ㏒` SQUARE LOG, and `U+33DA ㏚` SQUARE PR (not units, rejected). See the [Unicode CJK Compatibility names list](https://www.unicode.org/charts/nameslist/n_3300.html). + ## Rule evidence and change log | Stage | Standard cases | Corpus exact | Corpus accuracy | Evidence | @@ -214,5 +208,6 @@ The rule 39 example `What is 김치 in English?` resumes the surrounding English | Parent commit `3cfeae0` | 5,141/5,141 | 57,732/83,528 | 69.12% | Reproduced with release tests | | Rules 28/29 indicator ordering | 5,141/5,141 | 61,652/83,528 | 73.81% | Roman indicator now precedes UEB grade-1/capital indicators; rule 35 roman-number continuity retained | | Rules 37/39 shared UEB groupsign algorithm | 5,141/5,141 | 63,239/83,528 | 75.71% | Korean Roman sections reuse UEB preference/morphology rules; entry wordsigns and English-dominant resume are state-gated | +| Rules 68/69 compatibility unit algorithm | 5,141/5,141 | 63,388/83,528 | 75.89% | 96 Unicode unit presentation forms use one decomposition/letter-run/attachment algorithm; encoding errors fell from 434 to 226 | Engine changes must add a row only after both the 5,141-case standard suite and this full analysis have been rerun. Suspect-reference clusters stay in this report; they are not engine targets without independent PDF evidence. diff --git a/libs/braillify/examples/nikl_corpus_analyze.rs b/libs/braillify/examples/nikl_corpus_analyze.rs index 80bfe2a6..5952c9ef 100644 --- a/libs/braillify/examples/nikl_corpus_analyze.rs +++ b/libs/braillify/examples/nikl_corpus_analyze.rs @@ -78,6 +78,13 @@ struct ShardStats { exact: usize, } +#[derive(Debug, Serialize)] +struct ErrorCharacterStats { + cases: usize, + nfkc: String, + family: &'static str, +} + #[derive(Debug, Serialize)] struct AnalysisReport { corpus: &'static str, @@ -90,7 +97,8 @@ struct AnalysisReport { primary_classes: BTreeMap, reasons: BTreeMap, encoding_error_messages: BTreeMap, - singleton_error_characters: BTreeMap, + encoding_error_families: BTreeMap, + singleton_error_characters: BTreeMap, overlapping_traits: BTreeMap, shards: BTreeMap, samples: BTreeMap>, @@ -382,6 +390,58 @@ fn excerpt_pair(expected: &str, actual: &str) -> (String, String) { (expected_excerpt, actual_excerpt) } +fn is_compatibility_unit_decomposition(ch: char, nfkc: &str) -> bool { + matches!( + ch as u32, + 0x3371..=0x337a + | 0x3380..=0x33c6 + | 0x33c8..=0x33cc + | 0x33ce..=0x33d0 + | 0x33d3..=0x33d9 + | 0x33db..=0x33df + | 0x33ff + ) && nfkc.chars().any(|part| part.is_ascii_alphabetic()) + && nfkc.chars().all(|part| { + part.is_ascii_alphabetic() + || matches!(part, '2' | '3' | '/' | '\u{2044}' | '\u{2215}' | 'μ') + }) +} + +fn encoding_error_family(ch: char) -> &'static str { + let nfkc = ch.to_string().nfkc().collect::(); + if is_compatibility_unit_decomposition(ch, &nfkc) { + "compatibility_unit_symbol" + } else if (0x2160..=0x217f).contains(&(ch as u32)) + && nfkc.chars().all(|part| { + matches!( + part.to_ascii_uppercase(), + 'I' | 'V' | 'X' | 'L' | 'C' | 'D' | 'M' + ) + }) + { + "roman_numeral_presentation" + } else if matches!(ch, '\u{3214}' | '\u{321c}') { + "enclosed_organization_mark" + } else if ch == '\u{2113}' { + "letterlike_unit_symbol" + } else if matches!( + ch, + '\u{02d1}' + | '\u{2025}' + | '\u{2502}' + | '\u{25b2}' + | '\u{25b4}' + | '\u{260f}' + | '\u{2665}' + | '\u{2e31}' + | '\u{302e}' + ) { + "punctuation_or_layout_symbol" + } else { + "other_unsupported_symbol" + } +} + fn analyze( cases: Vec, encoded: Vec, @@ -391,7 +451,8 @@ fn analyze( let mut primary_classes = BTreeMap::new(); let mut reasons = BTreeMap::new(); let mut encoding_error_messages = BTreeMap::new(); - let mut singleton_error_characters = BTreeMap::new(); + let mut encoding_error_families = BTreeMap::new(); + let mut singleton_error_characters = BTreeMap::::new(); let mut singleton_error_cache = BTreeMap::::new(); let mut traits = BTreeMap::new(); let mut shards = BTreeMap::::new(); @@ -419,15 +480,30 @@ fn analyze( { *encoding_error_messages.entry(error.clone()).or_insert(0) += 1; let unique_chars = input.chars().collect::>(); + let mut case_families = BTreeSet::new(); for ch in unique_chars { let fails_alone = *singleton_error_cache .entry(ch) .or_insert_with(|| braillify::encode_to_unicode(&ch.to_string()).is_err()); if fails_alone { let key = format!("U+{:04X} {ch}", ch as u32); - *singleton_error_characters.entry(key).or_insert(0) += 1; + let family = encoding_error_family(ch); + case_families.insert(family); + singleton_error_characters + .entry(key) + .and_modify(|stats| stats.cases += 1) + .or_insert_with(|| ErrorCharacterStats { + cases: 1, + nfkc: ch.to_string().nfkc().collect(), + family, + }); } } + for family in case_families { + *encoding_error_families + .entry(family.to_string()) + .or_insert(0) += 1; + } } for (name, present) in [ ( @@ -485,6 +561,7 @@ fn analyze( primary_classes, reasons, encoding_error_messages, + encoding_error_families, singleton_error_characters, overlapping_traits: traits, shards, @@ -545,10 +622,27 @@ fn markdown(report: &AnalysisReport) -> String { for (name, count) in &report.encoding_error_messages { text.push_str(&format!("| `{name}` | {count} |\n")); } - text.push_str("\n| Singleton error character | Cases containing it |\n|---|---:|\n"); - for (name, count) in &report.singleton_error_characters { + text.push_str("\n| Error family | Cases |\n|---|---:|\n"); + for (name, count) in &report.encoding_error_families { text.push_str(&format!("| `{name}` | {count} |\n")); } + text.push_str( + "\nFamilies are diagnostics, not automatic normalization permissions. \ + Rules 68/69 compatibility-unit support removed that error family from the current run; \ + `enclosed_organization_mark` and layout symbols still have no confirmed rule.\n\n", + ); + text.push_str( + "| Singleton error character | Cases containing it | NFKC decomposition | Family |\n\ + |---|---:|---|---|\n", + ); + for (name, stats) in &report.singleton_error_characters { + text.push_str(&format!( + "| `{name}` | {} | `{}` | `{}` |\n", + stats.cases, + stats.nfkc.replace('`', "\\`"), + stats.family + )); + } text.push_str("\n## Shards\n\n| Shard | Exact | Total | Accuracy |\n|---|---:|---:|---:|\n"); for (name, stats) in &report.shards { @@ -598,6 +692,17 @@ fn markdown(report: &AnalysisReport) -> String { wordsign for the resumed `in`, instead of treating it as a fresh rule 37 entry word.\n\n", ); + text.push_str("## Rule 69 compatibility-unit scope\n\n"); + text.push_str( + "The engine accepts 96 scientific/measurement glyphs from Unicode CJK Compatibility, \ + derives their Roman spelling with NFKC, and applies rules 68/69 rather than whole-word \ + UEB. The accepted glyph set and panic-free encoding property are fixed by inline tests. \ + The official Unicode names distinguish `U+337A ㍺` SQUARE IU (accepted) from \ + `U+33D1 ㏑` SQUARE LN, `U+33D2 ㏒` SQUARE LOG, and `U+33DA ㏚` SQUARE PR \ + (not units, rejected). See the \ + [Unicode CJK Compatibility names list](https://www.unicode.org/charts/nameslist/n_3300.html).\n\n", + ); + text.push_str("## Rule evidence and change log\n\n"); text.push_str( "| Stage | Standard cases | Corpus exact | Corpus accuracy | Evidence |\n\ @@ -606,6 +711,9 @@ fn markdown(report: &AnalysisReport) -> String { | Rules 28/29 indicator ordering | 5,141/5,141 | 61,652/83,528 | 73.81% | Roman indicator now precedes UEB grade-1/capital indicators; rule 35 roman-number continuity retained |\n\ | Rules 37/39 shared UEB groupsign algorithm | 5,141/5,141 | 63,239/83,528 | 75.71% | Korean Roman sections reuse UEB preference/morphology rules; entry wordsigns and English-dominant resume are state-gated |\n", ); + text.push_str( + "| Rules 68/69 compatibility unit algorithm | 5,141/5,141 | 63,388/83,528 | 75.89% | 96 Unicode unit presentation forms use one decomposition/letter-run/attachment algorithm; encoding errors fell from 434 to 226 |\n", + ); text.push_str( "\nEngine changes must add a row only after both the 5,141-case standard suite and \ this full analysis have been rerun. Suspect-reference clusters stay in this report; \ @@ -669,6 +777,16 @@ mod tests { } } + #[rstest::rstest] + #[case::compatibility_unit('㎏', "compatibility_unit_symbol")] + #[case::roman_numeral('Ⅱ', "roman_numeral_presentation")] + #[case::company_mark('㈜', "enclosed_organization_mark")] + #[case::layout_symbol('▲', "punctuation_or_layout_symbol")] + #[case::non_unit_square_log('㏒', "other_unsupported_symbol")] + fn clusters_encoding_error_characters(#[case] input: char, #[case] expected_family: &str) { + assert_eq!(encoding_error_family(input), expected_family); + } + #[test] fn whitespace_normalization_does_not_change_braille_cells() { assert_eq!(normalized_braille_whitespace("⠁ ⠃"), "⠁⠀⠃"); diff --git a/libs/braillify/src/rules/korean/rule_69.rs b/libs/braillify/src/rules/korean/rule_69.rs index 722dde5d..df080777 100644 --- a/libs/braillify/src/rules/korean/rule_69.rs +++ b/libs/braillify/src/rules/korean/rule_69.rs @@ -1,8 +1,10 @@ use crate::char_struct::CharType; use crate::rules::RuleMeta; use crate::rules::context::RuleContext; +use crate::rules::english_ueb::span::encode_korean_word; use crate::rules::korean::rule_29::{ENGLISH_CONTINUATION, ROMAN_INDICATOR}; use crate::rules::traits::{BrailleRule, Phase, RuleResult}; +use unicode_normalization::UnicodeNormalization; pub static META: RuleMeta = RuleMeta { section: "69", @@ -13,11 +15,6 @@ pub static META: RuleMeta = RuleMeta { }; const SINGLE_MAPPINGS: &[(char, &str)] = &[ - ('㎎', "⠴⠍⠛"), - ('㎗', "⠙⠇⠲"), - ('㎠', "⠉⠍⠘⠼⠃"), - ('㎞', "⠴⠅⠍⠲"), - ('㎒', "⠴⠠⠍⠠⠓⠵⠲"), ('Ω', "⠴⠠⠨⠺⠲"), ('%', "⠴⠏"), ('‰', "⠴⠏⠍"), @@ -52,8 +49,132 @@ fn encode_unicode_cells(unicode: &str) -> Vec { .collect() } +/// Unicode's CJK compatibility block contains square presentation forms for +/// Roman unit symbols (`㎏` → `kg`, `㎓` → `GHz`, `㎥` → `m3`). Rules 68/69 +/// define the transcription from the semantic Roman unit, so recognize the +/// whole Unicode family from its compatibility decomposition instead of adding +/// one input-specific mapping per glyph. Japanese square words and other CJK +/// compatibility characters are rejected by the component grammar. +fn compatibility_unit_decomposition(c: char) -> Option> { + // Unicode CJK Compatibility contains non-unit square abbreviations too + // (`㏑` ln, `㏒` log, `㏚` PR). Keep the accepted ranges to scientific and + // measurement symbols; the component grammar is an additional guard, not + // the sole evidence that a square abbreviation is a unit. + let is_unit_codepoint = matches!( + c as u32, + 0x3371..=0x337a + | 0x3380..=0x33c6 + | 0x33c8..=0x33cc + | 0x33ce..=0x33d0 + | 0x33d3..=0x33d9 + | 0x33db..=0x33df + | 0x33ff + ); + if !is_unit_codepoint || super::rule_68::is_rule_68_symbol(c) { + return None; + } + let parts = c.to_string().nfkc().collect::>(); + (parts.iter().any(|part| part.is_ascii_alphabetic()) + && parts.iter().all(|part| { + part.is_ascii_alphabetic() + || matches!(part, '2' | '3' | '/' | '\u{2044}' | '\u{2215}' | 'μ') + })) + .then_some(parts) +} + +/// Rule 69 delegates only to rule 37's multi-letter groupsigns. This is not +/// ordinary UEB word encoding: whole-word signs and shortforms are disabled, +/// and a lower groupsign cannot consume the whole entry run (`in` is spelled +/// `i`-`n`, while the same `in` may contract inside `min`). +fn encode_rule_69_unit_letters(letters: &[char]) -> Result, String> { + encode_korean_word(letters, false, false, true, false).ok_or_else(|| { + format!( + "cannot encode rule 69 Roman unit letters: {}", + letters.iter().collect::() + ) + }) +} + +fn encode_compatibility_unit( + parts: &[char], + needs_roman_indicator: bool, + needs_roman_terminator: bool, +) -> Result, String> { + let mut encoded = Vec::new(); + if needs_roman_indicator { + encoded.push(ROMAN_INDICATOR); + } + + let mut index = 0usize; + while index < parts.len() { + match parts[index] { + 'μ' => { + encoded.extend(encode_unicode_cells("⠨⠍")); + index += 1; + } + '2' | '3' => { + encoded.extend(encode_unicode_cells("⠘⠼")); + encoded.push(crate::number::encode_number(parts[index])?); + index += 1; + } + '/' | '\u{2044}' | '\u{2215}' => { + encoded.extend(encode_unicode_cells("⠸⠌")); + index += 1; + } + ch if ch.is_ascii_alphabetic() => { + let end = index + + parts[index..] + .iter() + .take_while(|part| part.is_ascii_alphabetic()) + .count(); + let letters = &parts[index..end]; + let unit = encode_rule_69_unit_letters(letters)?; + encoded.extend(unit); + index = end; + } + unsupported => { + return Err(format!( + "unsupported compatibility unit component: U+{:04X}", + unsupported as u32 + )); + } + } + } + + // Rule 68's superscript closes the compact unit without a Roman terminator + // (`㎡` → `0m^#b`). Otherwise rule 69 terminates the Roman unit unless the + // same Roman unit chain continues through a slash. + if needs_roman_terminator && !matches!(parts.last(), Some('2' | '3')) { + encoded.push(crate::unicode::decode_unicode('⠲')); + } + Ok(encoded) +} + +fn is_roman_unit_component(ch: char) -> bool { + ch.is_ascii_alphabetic() || ch == 'μ' || compatibility_unit_decomposition(ch).is_some() +} + +fn roman_unit_chain_continues_before(ctx: &RuleContext) -> bool { + ctx.index >= 2 + && ctx.word_chars.get(ctx.index - 1) == Some(&'/') + && ctx + .word_chars + .get(ctx.index - 2) + .is_some_and(|previous| is_roman_unit_component(*previous)) +} + +fn roman_unit_chain_continues_after(ctx: &RuleContext) -> bool { + ctx.word_chars.get(ctx.index + 1) == Some(&'/') + && ctx + .word_chars + .get(ctx.index + 2) + .is_some_and(|next| is_roman_unit_component(*next)) +} + pub fn is_rule_69_symbol(c: char) -> bool { - SINGLE_MAPPINGS.iter().any(|(candidate, _)| *candidate == c) || c == 'μ' + SINGLE_MAPPINGS.iter().any(|(candidate, _)| *candidate == c) + || c == 'μ' + || compatibility_unit_decomposition(c).is_some() } fn is_numeric_or_unit_context(ctx: &RuleContext) -> bool { @@ -256,6 +377,17 @@ impl BrailleRule for Rule69 { return Ok(RuleResult::Consumed); } + if let Some(parts) = compatibility_unit_decomposition(ctx.current_char()) { + let continues_from_previous = roman_unit_chain_continues_before(ctx); + let continues_after = roman_unit_chain_continues_after(ctx); + let encoded = + encode_compatibility_unit(&parts, !continues_from_previous, !continues_after)?; + ctx.emit_slice(&encoded); + ctx.state.is_english = false; + ctx.state.needs_english_continuation = false; + return Ok(RuleResult::Consumed); + } + // `matches()` guard `is_rule_69_symbol(c)` is a `SINGLE_MAPPINGS` lookup, // so reaching here without the prior μ/ASCII-unit/`%`-shortcut paths // means the char is guaranteed to be in `SINGLE_MAPPINGS`. @@ -275,7 +407,8 @@ impl BrailleRule for Rule69 { #[cfg(test)] mod tests { use super::{ - Rule69, encode_ascii_unit, encode_percent_abbreviation, parse_numeric_ascii_unit_prefix, + Rule69, compatibility_unit_decomposition, encode_ascii_unit, encode_compatibility_unit, + encode_percent_abbreviation, encode_rule_69_unit_letters, parse_numeric_ascii_unit_prefix, word_looks_like_unit_chain, }; @@ -290,6 +423,124 @@ mod tests { assert_eq!(word_looks_like_unit_chain(&chars), expected); } + #[rstest::rstest] + #[case::kilogram('㎏', "kg")] + #[case::gigahertz('㎓', "GHz")] + #[case::cubic_metre('㎥', "m3")] + #[case::metres_per_second('㎧', "m∕s")] + #[case::milliwatt('㎽', "mW")] + #[case::kilowatt('㎾', "kW")] + #[case::sievert('㏜', "Sv")] + fn decomposes_compatibility_unit_symbols(#[case] input: char, #[case] expected: &str) { + assert_eq!( + compatibility_unit_decomposition(input), + Some(expected.chars().collect()) + ); + } + + /// Unicode CJK Compatibility names distinguish the accepted SQUARE IU + /// (U+337A) from non-unit square abbreviations LN, LOG, and PR. In + /// particular, U+33DA is SQUARE PR, not SQUARE IU. + #[rstest::rstest] + #[case::international_unit('㍺', Some("IU"))] + #[case::natural_logarithm('㏑', None)] + #[case::logarithm('㏒', None)] + #[case::public_relations('㏚', None)] + fn accepts_only_unit_semantics(#[case] input: char, #[case] expected: Option<&str>) { + assert_eq!( + compatibility_unit_decomposition(input), + expected.map(|text| text.chars().collect()) + ); + } + + const ACCEPTED_GLYPHS: &str = "㍱㍲㍳㍴㍵㍶㍷㍸㍹㍺㎀㎁㎂㎃㎄㎅㎆㎇㎈㎉㎊㎋㎌㎍㎎㎏㎐㎑㎒㎓㎔㎕㎖㎗㎘㎙㎚㎛㎜㎝㎞㎟㎠㎢㎣㎤㎥㎦㎧㎨㎩㎪㎫㎬㎭㎮㎯㎰㎱㎲㎳㎴㎵㎶㎷㎸㎹㎺㎻㎼㎽㎾㎿㏃㏄㏅㏆㏈㏉㏋㏌㏎㏏㏐㏓㏔㏕㏖㏗㏙㏛㏜㏝㏞㏟㏿"; + + #[test] + fn accepted_compatibility_unit_set_is_stable() { + let actual = (0x3300..=0x33ff) + .filter_map(char::from_u32) + .filter(|ch| compatibility_unit_decomposition(*ch).is_some()) + .collect::(); + + assert_eq!(actual, ACCEPTED_GLYPHS); + } + + #[test] + fn every_accepted_compatibility_unit_encodes_without_panicking() { + // Generated property check: the set identity is asserted separately, + // while this loop only proves that every accepted decomposition and + // each of its ASCII letter runs reaches the fallible Rule 69 encoder. + for glyph in ACCEPTED_GLYPHS.chars() { + let parts = compatibility_unit_decomposition(glyph).unwrap(); + let mut index = 0usize; + while index < parts.len() { + if !parts[index].is_ascii_alphabetic() { + index += 1; + continue; + } + let end = index + + parts[index..] + .iter() + .take_while(|part| part.is_ascii_alphabetic()) + .count(); + encode_rule_69_unit_letters(&parts[index..end]).unwrap(); + index = end; + } + encode_compatibility_unit(&parts, true, true).unwrap(); + } + } + + #[rstest::rstest] + #[case::inch('㏌', "in")] + #[case::centimetre('㎝', "cm")] + #[case::millimetre('㎜', "mm")] + #[case::gigabyte('㎇', "GB")] + fn compatibility_units_match_existing_ascii_unit_spelling( + #[case] glyph: char, + #[case] ascii: &str, + ) { + let ascii_chars = ascii.chars().collect::>(); + let expected = encode_ascii_unit(&ascii_chars, 0) + .expect("existing rule 69 ASCII unit") + .0; + let decomposition = compatibility_unit_decomposition(glyph).unwrap(); + let actual = encode_compatibility_unit(&decomposition, true, true).unwrap(); + assert_eq!(actual, expected); + } + + /// Rules 68/69: a compatibility presentation form follows the same general + /// Roman-unit and superscript algorithm as its Unicode decomposition. + #[rstest::rstest] + #[case::kilogram("㎏", "⠴⠅⠛⠲")] + #[case::gigahertz("㎓", "⠴⠠⠛⠠⠓⠵⠲")] + #[case::cubic_metre("㎥", "⠴⠍⠘⠼⠉")] + #[case::milliwatt("㎽", "⠴⠍⠠⠺⠲")] + #[case::kilowatt("㎾", "⠴⠅⠠⠺⠲")] + #[case::sievert("㏜", "⠴⠠⠎⠧⠲")] + fn encodes_compatibility_unit_symbols(#[case] input: &str, #[case] expected: &str) { + assert_eq!(crate::encode_to_unicode(input).unwrap(), expected); + } + + #[test] + fn slash_after_korean_starts_a_new_roman_unit_chain() { + let encoded = crate::encode_to_unicode("시/㎏").unwrap(); + assert!( + encoded.ends_with("⠸⠌⠴⠅⠛⠲"), + "the Roman indicator must not be suppressed after a Korean component: {encoded}" + ); + } + + /// Exact PDF examples exercise both Roman-unit continuation through `/` + /// and termination before a slash followed by a Korean unit. + #[rstest::rstest] + #[case::milligram_per_decilitre("160㎎/㎗", "⠼⠁⠋⠚⠴⠍⠛⠸⠌⠙⠇⠲")] + #[case::calorie_per_square_centimetre_per_minute("cal/㎠/min", "⠴⠉⠁⠇⠸⠌⠉⠍⠘⠼⠃⠸⠌⠍⠔⠲")] + #[case::megahertz("96.7 ㎒", "⠼⠊⠋⠲⠛⠀⠴⠠⠍⠠⠓⠵⠲")] + #[case::kilometres_per_hour("80 ㎞/시", "⠼⠓⠚⠀⠴⠅⠍⠲⠸⠌⠠⠕")] + fn preserves_pdf_unit_examples(#[case] input: &str, #[case] expected: &str) { + assert_eq!(crate::encode_to_unicode(input).unwrap(), expected); + } + #[test] fn parses_compact_number_unit_word() { let chars: Vec = "180cm".chars().collect(); From 61ce571a0f569945cf70ca90d95a90a9355d570a Mon Sep 17 00:00:00 2001 From: devfive Date: Tue, 1 Sep 2026 04:22:03 +0900 Subject: [PATCH 03/77] Normalize Unicode Roman numeral presentations --- docs/corpus-analysis/NIKL_2025_V1.md | 53 +++++++++------------ libs/braillify/src/lib.rs | 70 ++++++++++++++++++++++++++++ 2 files changed, 93 insertions(+), 30 deletions(-) diff --git a/docs/corpus-analysis/NIKL_2025_V1.md b/docs/corpus-analysis/NIKL_2025_V1.md index 37d28afd..b000532b 100644 --- a/docs/corpus-analysis/NIKL_2025_V1.md +++ b/docs/corpus-analysis/NIKL_2025_V1.md @@ -7,9 +7,9 @@ | Metric | Count | |---|---:| | Total | 83528 | -| Exact | 63388 | -| Mismatch | 20140 | -| Exact accuracy | 75.89% | +| Exact | 63399 | +| Mismatch | 20129 | +| Exact accuracy | 75.90% | | Duplicate records | 0 | | Inputs with conflicting references | 0 | @@ -19,17 +19,17 @@ Primary classes are evidence gates, not permissions to change the engine. `imple | Primary class | Count | |---|---:| -| `comparison_method` | 301 | -| `exact` | 63388 | -| `implementation_defect` | 226 | -| `pending_rule_review` | 19613 | +| `comparison_method` | 290 | +| `exact` | 63399 | +| `implementation_defect` | 203 | +| `pending_rule_review` | 19636 | | Reproducible reason | Count | |---|---:| -| `encoding_error` | 226 | -| `exact` | 63388 | -| `foreign_text_rule_review` | 19422 | -| `nfkc_input_equivalent` | 301 | +| `encoding_error` | 203 | +| `exact` | 63399 | +| `foreign_text_rule_review` | 19445 | +| `nfkc_input_equivalent` | 290 | | `number_rule_review` | 178 | | `punctuation_rule_review` | 13 | @@ -39,15 +39,13 @@ These are overlapping diagnostics for `implementation_defect` encoding errors, n | Error message | Cases | |---|---:| -| `Invalid character` | 26 | -| `Invalid symbol character` | 200 | +| `Invalid symbol character` | 203 | | Error family | Cases | |---|---:| | `enclosed_organization_mark` | 181 | | `letterlike_unit_symbol` | 12 | | `punctuation_or_layout_symbol` | 12 | -| `roman_numeral_presentation` | 26 | Families are diagnostics, not automatic normalization permissions. Rules 68/69 compatibility-unit support removed that error family from the current run; `enclosed_organization_mark` and layout symbols still have no confirmed rule. @@ -56,11 +54,6 @@ Families are diagnostics, not automatic normalization permissions. Rules 68/69 c | `U+02D1 ˑ` | 1 | `ˑ` | `punctuation_or_layout_symbol` | | `U+2025 ‥` | 1 | `..` | `punctuation_or_layout_symbol` | | `U+2113 ℓ` | 12 | `l` | `letterlike_unit_symbol` | -| `U+2160 Ⅰ` | 4 | `I` | `roman_numeral_presentation` | -| `U+2161 Ⅱ` | 14 | `II` | `roman_numeral_presentation` | -| `U+2162 Ⅲ` | 6 | `III` | `roman_numeral_presentation` | -| `U+2163 Ⅳ` | 5 | `IV` | `roman_numeral_presentation` | -| `U+2166 Ⅶ` | 1 | `VII` | `roman_numeral_presentation` | | `U+2502 │` | 1 | `│` | `punctuation_or_layout_symbol` | | `U+25B2 ▲` | 1 | `▲` | `punctuation_or_layout_symbol` | | `U+25B4 ▴` | 1 | `▴` | `punctuation_or_layout_symbol` | @@ -75,19 +68,19 @@ Families are diagnostics, not automatic normalization permissions. Rules 68/69 c | Shard | Exact | Total | Accuracy | |---|---:|---:|---:| -| `sentence_01.json` | 18887 | 25000 | 75.55% | -| `sentence_02.json` | 18348 | 25000 | 73.39% | +| `sentence_01.json` | 18889 | 25000 | 75.56% | +| `sentence_02.json` | 18355 | 25000 | 73.42% | | `sentence_03.json` | 19406 | 25000 | 77.62% | -| `sentence_04.json` | 6747 | 8528 | 79.12% | +| `sentence_04.json` | 6749 | 8528 | 79.14% | ## Overlapping mismatch traits | Trait | Count | |---|---:| -| `contains_ascii_digits` | 15700 | -| `contains_ascii_letters` | 19938 | -| `contains_delimiter_or_quote` | 20140 | -| `input_not_nfkc` | 860 | +| `contains_ascii_digits` | 15691 | +| `contains_ascii_letters` | 19927 | +| `contains_delimiter_or_quote` | 20129 | +| `input_not_nfkc` | 849 | ## Samples @@ -138,10 +131,6 @@ Families are diagnostics, not automatic normalization permissions. Rules 68/69 c - expected: `⠍⠗⠏⠒⠊⠮⠵⠀⠦⠄⠨⠍⠠⠴⠈⠧⠶⠨⠍⠈⠮⠐⠥⠘` - actual: `` - error: `Invalid symbol character` -- `sentence_01.json` #1558: 레저용차량(RV)은 카니발 6천39대, 스포티지 5천689대, 셀토스 5천552대, 쏘렌토 4천745대등 총 2만6천360대가 판매됐다. 봉고Ⅲ를 비롯한 상용은 버스를 합쳐 총 9천73대가 판매됐다. - - expected: `⠐⠝⠨⠎⠬⠶⠰⠣⠐⠜⠶⠦⠄⠴⠠⠠⠗⠧⠠⠴⠵⠀⠋⠉` - - actual: `` - - error: `Invalid character` - `sentence_01.json` #2276: 김무환 SK㈜ 그린(Green)투자센터장은 “글로벌 탈산소 전문 가치 투자자를 목표로 탄소 배출 축소 및 제거 관련 사업기회를 발굴하고 투자하는데 역점을 두고 있다”고 밝혔다. - expected: `⠈⠕⠢⠑⠍⠚⠧⠒⠀⠴⠠⠠⠎⠅⠦⠄⠨⠍⠠⠴⠀⠈⠪⠐` - actual: `` @@ -154,6 +143,10 @@ Families are diagnostics, not automatic normalization permissions. Rules 68/69 c - expected: `⠈⠻⠇⠶⠉⠢⠊⠥⠀⠈⠕⠢⠚⠗⠠⠕⠝⠀⠠⠥⠨⠗⠚⠒` - actual: `` - error: `Invalid symbol character` +- `sentence_01.json` #3149: 한편 공동연구개발기관인 농업회사법인 ㈜영풍은 대구시 스타기업으로 현재 떡볶이, 전, 스낵류 등 가정간편식(HMR) 제품을 80개국에 수출하고 있다. + - expected: `⠚⠒⠙⠡⠀⠈⠿⠊⠿⠡⠈⠍⠈⠗⠘⠂⠈⠕⠈⠧⠒⠟⠀⠉` + - actual: `` + - error: `Invalid symbol character` ### `number_rule_review` diff --git a/libs/braillify/src/lib.rs b/libs/braillify/src/lib.rs index 27f0fe57..ff71261d 100644 --- a/libs/braillify/src/lib.rs +++ b/libs/braillify/src/lib.rs @@ -256,6 +256,29 @@ fn normalize_math_alphanumeric_string(text: &str) -> Cow<'_, str> { Cow::Owned(text.chars().map(normalize_math_alphanumeric_char).collect()) } +fn may_normalize_roman_numeral_presentation(c: char) -> bool { + (0x2160..=0x217f).contains(&(c as u32)) +} + +/// Korean Braille rule 36 transcribes a Roman numeral with its corresponding +/// Roman letters. Unicode U+2160–U+217F are presentation forms whose NFKC +/// decomposition is exactly that Roman-letter spelling (`Ⅱ` → `II`). Normalize +/// only this block; the existing rule-36 token logic remains responsible for +/// numeral validity, case indicators, context, and Roman termination. +fn normalize_roman_numeral_presentation<'a>(text: Cow<'a, str>) -> Cow<'a, str> { + use unicode_normalization::UnicodeNormalization; + + let mut out = String::with_capacity(text.len()); + for ch in text.chars() { + if may_normalize_roman_numeral_presentation(ch) { + out.extend(std::iter::once(ch).nfkc()); + } else { + out.push(ch); + } + } + Cow::Owned(out) +} + /// Default-route whole expressions that contain math-only relational/grouping /// glyphs which cannot be encoded correctly one space-separated token at a time. /// @@ -318,6 +341,7 @@ fn combining_mark_on_single_letter(chars: &[char], i: usize) -> bool { #[derive(Clone, Copy, Default)] struct NormalizationTriggers { has_math_alphanumeric: bool, + has_roman_numeral_presentation: bool, has_decomposable_latin: bool, has_negation_combiner: bool, has_vector_mark: bool, @@ -331,6 +355,7 @@ impl NormalizationTriggers { let mut triggers = Self::default(); for c in text.chars() { triggers.has_math_alphanumeric |= may_normalize_math_alphanumeric(c); + triggers.has_roman_numeral_presentation |= may_normalize_roman_numeral_presentation(c); triggers.has_decomposable_latin |= may_decompose_accented_latin(c); triggers.has_negation_combiner |= c == '\u{0338}'; triggers.has_vector_mark |= is_vector_mark(c); @@ -722,6 +747,11 @@ pub fn encode_with_options(text: &str, options: &EncodeOptions) -> Result Date: Tue, 1 Sep 2026 04:30:16 +0900 Subject: [PATCH 04/77] Make Rule 36 corpus transitions reproducible --- docs/corpus-analysis/NIKL_2025_V1.md | 24 +++ .../braillify/examples/nikl_corpus_analyze.rs | 193 +++++++++++++++++- 2 files changed, 216 insertions(+), 1 deletion(-) diff --git a/docs/corpus-analysis/NIKL_2025_V1.md b/docs/corpus-analysis/NIKL_2025_V1.md index b000532b..ab6216eb 100644 --- a/docs/corpus-analysis/NIKL_2025_V1.md +++ b/docs/corpus-analysis/NIKL_2025_V1.md @@ -194,6 +194,29 @@ The rule 39 example `What is 김치 in English?` resumes the surrounding English The engine accepts 96 scientific/measurement glyphs from Unicode CJK Compatibility, derives their Roman spelling with NFKC, and applies rules 68/69 rather than whole-word UEB. The accepted glyph set and panic-free encoding property are fixed by inline tests. The official Unicode names distinguish `U+337A ㍺` SQUARE IU (accepted) from `U+33D1 ㏑` SQUARE LN, `U+33D2 ㏒` SQUARE LOG, and `U+33DA ㏚` SQUARE PR (not units, rejected). See the [Unicode CJK Compatibility names list](https://www.unicode.org/charts/nameslist/n_3300.html). +## Rule 36 Roman-numeral presentation forms + +Rule 36 says that a Roman numeral is written with the corresponding Roman letters. The encoder therefore applies compatibility decomposition only to Unicode Roman Numerals U+2160–U+217F and sends the ASCII spelling through the existing rule-36 algorithm. Encoder regressions compare Unicode presentations with ASCII equivalents in the PDF sentence and in attached-Korean, particle-adjacent, and lower-case contexts. U+2180 `ↀ` and unrelated NFKC characters such as `㈜` are explicit non-targets. + +The transition audit reconstructs the immediately preceding engine behavior: direct and NFC encoding rejected U+2160–U+217F, while the analyzer's existing NFKC comparison path already used the same ASCII Roman spelling. This avoids a saved-output lookup and keeps the transition reproducible from the current corpus. + +Presentation-form cases audited: 37. + +| Previous primary → current primary | Cases | +|---|---:| +| `comparison_method -> exact` | 11 | +| `implementation_defect -> implementation_defect` | 3 | +| `implementation_defect -> pending_rule_review` | 23 | + +Remaining complex encoding errors: 3. These cases still contain another character that fails independently, so disappearance of the `roman_numeral_presentation` family does not imply that every former error case now encodes successfully. + +- `sentence_02.json` #718: 한국폴리텍Ⅳ대학 대전캠퍼스(학장 박봉순) 메타버스콘텐츠과(학과장 이종탁)는 지난 9월 26일 실감콘텐츠 전문기업 ㈜스파이더룹과 메타버스분야 인재양성과 취업 등에 관한 업무협약(MOU)을 체결했다. + - other independently unsupported: `U+321C ㈜` +- `sentence_02.json` #723: 한국폴리텍Ⅳ대학 대전캠퍼스(학장 박봉순) 메타버스콘텐츠과(학과장 이종탁)는 4일 XR(현실확장) 전문기업 이루다플래닛㈜과 메타버스분야 인재양성과 취업 등에 관한 업무협약(MOU)을 체결했다. + - other independently unsupported: `U+321C ㈜` +- `sentence_02.json` #740: 한국폴리텍Ⅳ대학 대전캠퍼스(학장 박봉순) 메타버스콘텐츠과(학과장 이종탁)는 지난 10월 5일 3D Scan & Solution 전문기업 위프코㈜와 메타버스분야 인재양성과 취업 등에 관한 업무협약(MOU)을 체결했다고 알렸다. + - other independently unsupported: `U+321C ㈜` + ## Rule evidence and change log | Stage | Standard cases | Corpus exact | Corpus accuracy | Evidence | @@ -202,5 +225,6 @@ The engine accepts 96 scientific/measurement glyphs from Unicode CJK Compatibili | Rules 28/29 indicator ordering | 5,141/5,141 | 61,652/83,528 | 73.81% | Roman indicator now precedes UEB grade-1/capital indicators; rule 35 roman-number continuity retained | | Rules 37/39 shared UEB groupsign algorithm | 5,141/5,141 | 63,239/83,528 | 75.71% | Korean Roman sections reuse UEB preference/morphology rules; entry wordsigns and English-dominant resume are state-gated | | Rules 68/69 compatibility unit algorithm | 5,141/5,141 | 63,388/83,528 | 75.89% | 96 Unicode unit presentation forms use one decomposition/letter-run/attachment algorithm; encoding errors fell from 434 to 226 | +| Rule 36 Unicode Roman-numeral presentation normalization | 5,141/5,141 | 63,399/83,528 | 75.90% | U+2160–U+217F use the corresponding Roman-letter spelling; 11 NFKC-comparison cases became exact, 23 encoding errors became pending review, and 3 compound errors remain | Engine changes must add a row only after both the 5,141-case standard suite and this full analysis have been rerun. Suspect-reference clusters stay in this report; they are not engine targets without independent PDF evidence. diff --git a/libs/braillify/examples/nikl_corpus_analyze.rs b/libs/braillify/examples/nikl_corpus_analyze.rs index 5952c9ef..c5845c96 100644 --- a/libs/braillify/examples/nikl_corpus_analyze.rs +++ b/libs/braillify/examples/nikl_corpus_analyze.rs @@ -85,6 +85,22 @@ struct ErrorCharacterStats { family: &'static str, } +#[derive(Debug, Serialize)] +struct Rule36ComplexErrorSample { + shard: String, + index: usize, + input: String, + other_unsupported_characters: Vec, +} + +#[derive(Debug, Default, Serialize)] +struct Rule36TransitionAudit { + presentation_cases: usize, + primary_transitions: BTreeMap, + remaining_complex_errors: usize, + remaining_complex_error_samples: Vec, +} + #[derive(Debug, Serialize)] struct AnalysisReport { corpus: &'static str, @@ -99,6 +115,7 @@ struct AnalysisReport { encoding_error_messages: BTreeMap, encoding_error_families: BTreeMap, singleton_error_characters: BTreeMap, + rule_36_transition_audit: Rule36TransitionAudit, overlapping_traits: BTreeMap, shards: BTreeMap, samples: BTreeMap>, @@ -361,6 +378,37 @@ fn classify(encoded: &EncodedCase, conflicting: &BTreeSet) -> (PrimaryCl } } +fn is_roman_numeral_presentation(ch: char) -> bool { + (0x2160..=0x217f).contains(&(ch as u32)) +} + +/// Reconstruct the immediately preceding rule-36 behavior without running a +/// second engine or reading a saved expected-output lookup. Before targeted +/// normalization, any U+2160–U+217F character made the direct and NFC paths +/// fail. The NFKC comparison path already contains the corresponding ASCII +/// Roman letters and is unaffected by the engine change, so it remains valid +/// for reproducing the former `comparison_method` classification. +fn classify_before_rule_36( + encoded: &EncodedCase, + conflicting: &BTreeSet, +) -> Option<(PrimaryClass, Reason)> { + encoded + .located + .case + .input + .chars() + .any(is_roman_numeral_presentation) + .then(|| { + let mut legacy = encoded.clone(); + legacy.actual = Err("legacy unsupported Roman-numeral presentation".to_string()); + legacy.nfc_actual = legacy + .nfc_actual + .as_ref() + .map(|_| Err("legacy unsupported Roman-numeral presentation".to_string())); + classify(&legacy, conflicting) + }) +} + fn is_delimiter_or_quote(ch: char) -> bool { matches!( ch, @@ -411,7 +459,7 @@ fn encoding_error_family(ch: char) -> &'static str { let nfkc = ch.to_string().nfkc().collect::(); if is_compatibility_unit_decomposition(ch, &nfkc) { "compatibility_unit_symbol" - } else if (0x2160..=0x217f).contains(&(ch as u32)) + } else if is_roman_numeral_presentation(ch) && nfkc.chars().all(|part| { matches!( part.to_ascii_uppercase(), @@ -457,10 +505,42 @@ fn analyze( let mut traits = BTreeMap::new(); let mut shards = BTreeMap::::new(); let mut samples = BTreeMap::>::new(); + let mut rule_36_transition_audit = Rule36TransitionAudit::default(); let mut exact = 0usize; for item in &encoded { let (primary, reason) = classify(item, &conflicting); + if let Some((before_primary, _)) = classify_before_rule_36(item, &conflicting) { + rule_36_transition_audit.presentation_cases += 1; + let transition = format!("{} -> {}", enum_key(&before_primary), enum_key(&primary)); + *rule_36_transition_audit + .primary_transitions + .entry(transition) + .or_insert(0) += 1; + + if primary == PrimaryClass::ImplementationDefect && item.actual.is_err() { + let other_unsupported_characters = item + .located + .case + .input + .chars() + .collect::>() + .into_iter() + .filter(|ch| !is_roman_numeral_presentation(*ch)) + .filter(|ch| braillify::encode_to_unicode(&ch.to_string()).is_err()) + .map(|ch| format!("U+{:04X} {ch}", ch as u32)) + .collect::>(); + rule_36_transition_audit.remaining_complex_errors += 1; + rule_36_transition_audit + .remaining_complex_error_samples + .push(Rule36ComplexErrorSample { + shard: item.located.shard.clone(), + index: item.located.index, + input: item.located.case.input.clone(), + other_unsupported_characters, + }); + } + } let primary_key = enum_key(&primary); let reason_key = enum_key(&reason); *primary_classes.entry(primary_key).or_insert(0) += 1; @@ -563,6 +643,7 @@ fn analyze( encoding_error_messages, encoding_error_families, singleton_error_characters, + rule_36_transition_audit, overlapping_traits: traits, shards, samples, @@ -703,6 +784,55 @@ fn markdown(report: &AnalysisReport) -> String { [Unicode CJK Compatibility names list](https://www.unicode.org/charts/nameslist/n_3300.html).\n\n", ); + text.push_str("## Rule 36 Roman-numeral presentation forms\n\n"); + text.push_str( + "Rule 36 says that a Roman numeral is written with the corresponding Roman letters. \ + The encoder therefore applies compatibility decomposition only to Unicode Roman \ + Numerals U+2160–U+217F and sends the ASCII spelling through the existing rule-36 \ + algorithm. Encoder regressions compare Unicode presentations with ASCII equivalents \ + in the PDF sentence and in attached-Korean, particle-adjacent, and lower-case contexts. \ + U+2180 `ↀ` and unrelated NFKC characters such as `㈜` are explicit non-targets.\n\n", + ); + text.push_str( + "The transition audit reconstructs the immediately preceding engine behavior: direct \ + and NFC encoding rejected U+2160–U+217F, while the analyzer's existing NFKC comparison \ + path already used the same ASCII Roman spelling. This avoids a saved-output lookup and \ + keeps the transition reproducible from the current corpus.\n\n", + ); + text.push_str(&format!( + "Presentation-form cases audited: {}.\n\n", + report.rule_36_transition_audit.presentation_cases + )); + text.push_str("| Previous primary → current primary | Cases |\n|---|---:|\n"); + for (transition, count) in &report.rule_36_transition_audit.primary_transitions { + text.push_str(&format!("| `{transition}` | {count} |\n")); + } + text.push_str(&format!( + "\nRemaining complex encoding errors: {}. These cases still contain another character \ + that fails independently, so disappearance of the `roman_numeral_presentation` family \ + does not imply that every former error case now encodes successfully.\n\n", + report.rule_36_transition_audit.remaining_complex_errors + )); + for sample in &report + .rule_36_transition_audit + .remaining_complex_error_samples + { + let input = sample.input.chars().take(180).collect::(); + let unsupported = if sample.other_unsupported_characters.is_empty() { + "none detected".to_string() + } else { + sample.other_unsupported_characters.join(", ") + }; + text.push_str(&format!( + "- `{}` #{}: {}\n - other independently unsupported: `{}`\n", + sample.shard, + sample.index, + input.replace('`', "\\`"), + unsupported + )); + } + text.push('\n'); + text.push_str("## Rule evidence and change log\n\n"); text.push_str( "| Stage | Standard cases | Corpus exact | Corpus accuracy | Evidence |\n\ @@ -714,6 +844,9 @@ fn markdown(report: &AnalysisReport) -> String { text.push_str( "| Rules 68/69 compatibility unit algorithm | 5,141/5,141 | 63,388/83,528 | 75.89% | 96 Unicode unit presentation forms use one decomposition/letter-run/attachment algorithm; encoding errors fell from 434 to 226 |\n", ); + text.push_str( + "| Rule 36 Unicode Roman-numeral presentation normalization | 5,141/5,141 | 63,399/83,528 | 75.90% | U+2160–U+217F use the corresponding Roman-letter spelling; 11 NFKC-comparison cases became exact, 23 encoding errors became pending review, and 3 compound errors remain |\n", + ); text.push_str( "\nEngine changes must add a row only after both the 5,141-case standard suite and \ this full analysis have been rerun. Suspect-reference clusters stay in this report; \ @@ -787,6 +920,64 @@ mod tests { assert_eq!(encoding_error_family(input), expected_family); } + #[rstest::rstest] + #[case::nfkc_comparison_becomes_exact( + "Ⅲ", + "same", + Some("same"), + "same", + PrimaryClass::ComparisonMethod, + PrimaryClass::Exact + )] + #[case::encoding_error_becomes_pending( + "Ⅳ장", + "expected", + Some("different"), + "different", + PrimaryClass::ImplementationDefect, + PrimaryClass::PendingRuleReview + )] + #[case::compound_encoding_error_remains( + "Ⅳ㈜", + "expected", + None, + "different", + PrimaryClass::ImplementationDefect, + PrimaryClass::ImplementationDefect + )] + fn reconstructs_rule_36_primary_transition( + #[case] input: &str, + #[case] expected: &str, + #[case] actual: Option<&str>, + #[case] nfkc_actual: &str, + #[case] expected_before: PrimaryClass, + #[case] expected_after: PrimaryClass, + ) { + let encoded = EncodedCase { + located: LocatedCase { + shard: "synthetic.json".to_string(), + index: 1, + case: CorpusCase { + input: input.to_string(), + unicode: expected.to_string(), + }, + }, + actual: actual.map_or_else( + || Err("another unsupported symbol".to_string()), + |value| Ok(value.to_string()), + ), + nfc_actual: None, + nfkc_actual: Some(Ok(nfkc_actual.to_string())), + }; + let conflicts = BTreeSet::new(); + + let (before, _) = classify_before_rule_36(&encoded, &conflicts).unwrap(); + let (after, _) = classify(&encoded, &conflicts); + + assert_eq!(before, expected_before); + assert_eq!(after, expected_after); + } + #[test] fn whitespace_normalization_does_not_change_braille_cells() { assert_eq!(normalized_braille_whitespace("⠁ ⠃"), "⠁⠀⠃"); From 2d6642f4980b3808f12d948eae9e2e5b121a6805 Mon Sep 17 00:00:00 2001 From: devfive Date: Tue, 1 Sep 2026 04:47:16 +0900 Subject: [PATCH 05/77] Separate unsupported corpus character reviews --- docs/corpus-analysis/NIKL_2025_V1.md | 79 ++-- .../braillify/examples/nikl_corpus_analyze.rs | 354 ++++++++++++++---- 2 files changed, 327 insertions(+), 106 deletions(-) diff --git a/docs/corpus-analysis/NIKL_2025_V1.md b/docs/corpus-analysis/NIKL_2025_V1.md index ab6216eb..7c27bbec 100644 --- a/docs/corpus-analysis/NIKL_2025_V1.md +++ b/docs/corpus-analysis/NIKL_2025_V1.md @@ -15,27 +15,42 @@ ## Classification policy -Primary classes are evidence gates, not permissions to change the engine. `implementation_defect` is restricted to defects independently confirmed from the PDF (the rules 28/29 roman-indicator ordering signature) and actual encoding errors. `pending_rule_review` contains foreign-text, number, punctuation, and Korean candidates that have not yet been resolved against the PDF. `corpus_suspect` is reserved for independently detectable contradictions such as one input having multiple references. `comparison_method` requires equality after a named normalization. +Primary classes are evidence gates, not permissions to change the engine. `implementation_defect` is restricted to defects independently confirmed from the PDF (currently the rules 28/29 roman-indicator ordering signature). `unsupported_character_review` contains encoding failures fully explained by one or more singleton characters whose support obligation has not been confirmed from the PDF. `unclassified_encoding_error_review` contains other encoding failures until a PDF-backed implementation obligation or a reproducible comparison/corpus issue is established. `pending_rule_review` contains foreign-text, number, punctuation, and Korean candidates that have not yet been resolved against the PDF. `corpus_suspect` is reserved for independently detectable contradictions such as one input having multiple references. `comparison_method` requires equality after a named normalization. | Primary class | Count | |---|---:| | `comparison_method` | 290 | | `exact` | 63399 | -| `implementation_defect` | 203 | | `pending_rule_review` | 19636 | +| `unsupported_character_review` | 203 | | Reproducible reason | Count | |---|---:| -| `encoding_error` | 203 | | `exact` | 63399 | | `foreign_text_rule_review` | 19445 | | `nfkc_input_equivalent` | 290 | | `number_rule_review` | 178 | | `punctuation_rule_review` | 13 | +| `unsupported_character_review` | 203 | ## Encoding-error diagnostics -These are overlapping diagnostics for `implementation_defect` encoding errors, not additional primary classes. A singleton error character is a character that also fails when encoded by itself. +The audit starts from all raw encoding errors, then separates cases already resolved by a comparison method or corpus contradiction. The message, family, and singleton tables below count only unresolved encoding-error review cases. These diagnostics are not additional primary classes. A singleton unsupported character is a character that also fails when encoded by itself. Such a failure remains a review candidate until the PDF independently establishes support. + +| Encoding-error audit | Cases | +|---|---:| +| Raw encoding errors | 450 | +| Resolved by comparison method | 247 | +| Excluded as corpus suspect | 0 | +| Unresolved encoding-error review cases | 203 | +| Explained by singleton unsupported character(s) | 203 | +| Multiple singleton unsupported characters | 2 | +| Unclassified without a singleton explanation | 0 | + +- compound `sentence_01.json` #17370: 배우 유라가 영화 ‘가문의 영광: 리턴즈’(배급: ㈜넥스트엔터테인먼트월드(NEW) │ 제작: ㈜태원엔터테인먼트│감독: 정태원, 정용기)에서 장씨 가문의 막내딸 ‘진경’으로 스크린을 찾는다. + - singleton unsupported: `U+2502 │, U+321C ㈜` +- compound `sentence_01.json` #21555: (주)바론교육, ESG baron 경제연구소, ㈜지인누리, ㈜지역창의연구소 대표 및 임원진이 참석한 가운데 강원도 소재 ‘농산어촌 ESG 원주교육장’에서 ‘농산어촌 ESG 인증⸱실천⸱교육을 위한 업무 협약식(MOU)’을 지난 14일 개최했다. + - singleton unsupported: `U+2E31 ⸱, U+321C ㈜` | Error message | Cases | |---|---:| @@ -84,29 +99,6 @@ Families are diagnostics, not automatic normalization permissions. Rules 68/69 c ## Samples -### `encoding_error` - -- `sentence_01.json` #50: 부산광역시는 3일 오후 부산광역시청 영상회의실에서 종합화학소재기업 ㈜금양과 이차전지 생산기지 건립을 위한 8천억원 규모의 투자 양해각서(MOU)를 체결한다고 밝혔다. - - expected: `⠘⠍⠇⠒⠈⠧⠶⠱⠁⠠⠕⠉⠵⠀⠼⠉⠕⠂⠀⠥⠚⠍⠀⠘` - - actual: `` - - error: `Invalid symbol character` -- `sentence_01.json` #1765: 신안군은 공모사업에 앞서 지난 1월 북부권김생산자협의회, ㈜신안천사김과 미국 수출김 전용단지 지정을 위한 업무협약(MOU)을 체결하고 북부권 김 양식장 1천118ha를 공모사업 지구로 신청했다. - - expected: `⠠⠟⠣⠒⠈⠛⠵⠀⠈⠿⠑⠥⠇⠎⠃⠝⠀⠣⠲⠠⠎⠀⠨⠕` - - actual: `` - - error: `Invalid symbol character` -- `sentence_01.json` #1901: 이를 위해 자산운용사 ㈜피아나인을 통해 ‘크러쉬 (CRUSH)’의 해외 진출을 위한 추가 씨드(Seed) 투자를 유치한데 이어 글로벌 마케팅 에이전시 ‘넥썸그룹 (The NEXUM Group)’과 업무협약(MOU)를 체결했다. - - expected: `⠕⠐⠮⠀⠍⠗⠚⠗⠀⠨⠇⠒⠛⠬⠶⠇⠀⠦⠄⠨⠍⠠⠴⠙` - - actual: `` - - error: `Invalid symbol character` -- `sentence_01.json` #2320: 대구광역시(시장 홍준표)는 30일 대구시청 산격청사에서 국내 유망 임플란트 기업 중 하나인 ㈜덴티스(대표 심기봉)와 투자협약 양해각서(MOU)를 체결했다고 밝혔다. - - expected: `⠊⠗⠈⠍⠈⠧⠶⠱⠁⠠⠕⠦⠄⠠⠕⠨⠶⠀⠚⠿⠨⠛⠙⠬` - - actual: `` - - error: `Invalid symbol character` -- `sentence_01.json` #2336: 한편, 한국팀 경기를 포함한 아시아컵 주요 경기는 세계야구소프트볼연맹(WBSC) 공식 유튜브에서 확인할 수 있다. 이번 대회 운영 대행은 ㈜에스피에스 코리아(SPS KOREA)가 맡아 진행한다. - - expected: `⠚⠒⠙⠡⠐⠀⠚⠒⠈⠍⠁⠓⠕⠢⠀⠈⠻⠈⠕⠐⠮⠀⠙⠥` - - actual: `` - - error: `Invalid symbol character` - ### `foreign_text_rule_review` - `sentence_01.json` #12: 특히 미국 헬스탭(HealthTap), 국내 굿닥(Goodoc)과 협업해 병원에 갈 필요 없이 집 안에서도 편리하게 진료를 받을 수 있는 원격 헬스케어 서비스를 시연한다. @@ -184,6 +176,29 @@ Families are diagnostics, not automatic normalization permissions. Rules 68/69 c - expected: `⠙⠮⠐⠎⠠⠪⠦⠄⠢⠠⠴⠠⠝⠒⠓⠎⠐⠀⠠⠝⠨⠿⠱⠠` - actual: `⠙⠮⠐⠎⠠⠪⠦⠄⠀⠢⠀⠠⠴⠠⠝⠒⠓⠎⠐⠀⠠⠝⠨⠿` +### `unsupported_character_review` + +- `sentence_01.json` #50: 부산광역시는 3일 오후 부산광역시청 영상회의실에서 종합화학소재기업 ㈜금양과 이차전지 생산기지 건립을 위한 8천억원 규모의 투자 양해각서(MOU)를 체결한다고 밝혔다. + - expected: `⠘⠍⠇⠒⠈⠧⠶⠱⠁⠠⠕⠉⠵⠀⠼⠉⠕⠂⠀⠥⠚⠍⠀⠘` + - actual: `` + - error: `Invalid symbol character` +- `sentence_01.json` #1765: 신안군은 공모사업에 앞서 지난 1월 북부권김생산자협의회, ㈜신안천사김과 미국 수출김 전용단지 지정을 위한 업무협약(MOU)을 체결하고 북부권 김 양식장 1천118ha를 공모사업 지구로 신청했다. + - expected: `⠠⠟⠣⠒⠈⠛⠵⠀⠈⠿⠑⠥⠇⠎⠃⠝⠀⠣⠲⠠⠎⠀⠨⠕` + - actual: `` + - error: `Invalid symbol character` +- `sentence_01.json` #1901: 이를 위해 자산운용사 ㈜피아나인을 통해 ‘크러쉬 (CRUSH)’의 해외 진출을 위한 추가 씨드(Seed) 투자를 유치한데 이어 글로벌 마케팅 에이전시 ‘넥썸그룹 (The NEXUM Group)’과 업무협약(MOU)를 체결했다. + - expected: `⠕⠐⠮⠀⠍⠗⠚⠗⠀⠨⠇⠒⠛⠬⠶⠇⠀⠦⠄⠨⠍⠠⠴⠙` + - actual: `` + - error: `Invalid symbol character` +- `sentence_01.json` #2320: 대구광역시(시장 홍준표)는 30일 대구시청 산격청사에서 국내 유망 임플란트 기업 중 하나인 ㈜덴티스(대표 심기봉)와 투자협약 양해각서(MOU)를 체결했다고 밝혔다. + - expected: `⠊⠗⠈⠍⠈⠧⠶⠱⠁⠠⠕⠦⠄⠠⠕⠨⠶⠀⠚⠿⠨⠛⠙⠬` + - actual: `` + - error: `Invalid symbol character` +- `sentence_01.json` #2336: 한편, 한국팀 경기를 포함한 아시아컵 주요 경기는 세계야구소프트볼연맹(WBSC) 공식 유튜브에서 확인할 수 있다. 이번 대회 운영 대행은 ㈜에스피에스 코리아(SPS KOREA)가 맡아 진행한다. + - expected: `⠚⠒⠙⠡⠐⠀⠚⠒⠈⠍⠁⠓⠕⠢⠀⠈⠻⠈⠕⠐⠮⠀⠙⠥` + - actual: `` + - error: `Invalid symbol character` + ## PDF-derived state gates The rule 37 example `그는 Can you help me?라고 도움을 요청했다.` distinguishes the first word after the roman indicator (`Can`, whose whole-word sign is suppressed) from the interior word `you` in the uninterrupted ASCII phrase (whose UEB wordsign is retained). The `prev_is_ascii_word && next_is_ascii_word` gate expresses that phrase-interior position rather than matching an input string. @@ -202,11 +217,11 @@ The transition audit reconstructs the immediately preceding engine behavior: dir Presentation-form cases audited: 37. -| Previous primary → current primary | Cases | +| Previous observation → current observation | Cases | |---|---:| -| `comparison_method -> exact` | 11 | -| `implementation_defect -> implementation_defect` | 3 | -| `implementation_defect -> pending_rule_review` | 23 | +| `encoding_error -> encoded_mismatch_pending_rule_review` | 23 | +| `encoding_error -> unsupported_character_review` | 3 | +| `nfkc_input_equivalent -> exact` | 11 | Remaining complex encoding errors: 3. These cases still contain another character that fails independently, so disappearance of the `roman_numeral_presentation` family does not imply that every former error case now encodes successfully. @@ -225,6 +240,6 @@ Remaining complex encoding errors: 3. These cases still contain another characte | Rules 28/29 indicator ordering | 5,141/5,141 | 61,652/83,528 | 73.81% | Roman indicator now precedes UEB grade-1/capital indicators; rule 35 roman-number continuity retained | | Rules 37/39 shared UEB groupsign algorithm | 5,141/5,141 | 63,239/83,528 | 75.71% | Korean Roman sections reuse UEB preference/morphology rules; entry wordsigns and English-dominant resume are state-gated | | Rules 68/69 compatibility unit algorithm | 5,141/5,141 | 63,388/83,528 | 75.89% | 96 Unicode unit presentation forms use one decomposition/letter-run/attachment algorithm; encoding errors fell from 434 to 226 | -| Rule 36 Unicode Roman-numeral presentation normalization | 5,141/5,141 | 63,399/83,528 | 75.90% | U+2160–U+217F use the corresponding Roman-letter spelling; 11 NFKC-comparison cases became exact, 23 encoding errors became pending review, and 3 compound errors remain | +| Rule 36 Unicode Roman-numeral presentation normalization | 5,141/5,141 | 63,399/83,528 | 75.90% | U+2160–U+217F use the corresponding Roman-letter spelling; 11 NFKC-equivalent observations became exact, 23 errors became encoded mismatches pending review, and 3 remain blocked by `㈜` | Engine changes must add a row only after both the 5,141-case standard suite and this full analysis have been rerun. Suspect-reference clusters stay in this report; they are not engine targets without independent PDF evidence. diff --git a/libs/braillify/examples/nikl_corpus_analyze.rs b/libs/braillify/examples/nikl_corpus_analyze.rs index c5845c96..7def10e0 100644 --- a/libs/braillify/examples/nikl_corpus_analyze.rs +++ b/libs/braillify/examples/nikl_corpus_analyze.rs @@ -34,6 +34,7 @@ struct EncodedCase { actual: Result, nfc_actual: Option>, nfkc_actual: Option>, + singleton_unsupported_characters: Vec, } #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize)] @@ -41,6 +42,8 @@ struct EncodedCase { enum PrimaryClass { Exact, ImplementationDefect, + UnsupportedCharacterReview, + UnclassifiedEncodingErrorReview, CorpusSuspect, ComparisonMethod, PendingRuleReview, @@ -55,7 +58,8 @@ enum Reason { NfcInputEquivalent, NfkcInputEquivalent, RomanIndicatorAfterCapitalIndicator, - EncodingError, + UnsupportedCharacterReview, + UnclassifiedEncodingErrorReview, ForeignTextRuleReview, NumberRuleReview, PunctuationRuleReview, @@ -96,11 +100,40 @@ struct Rule36ComplexErrorSample { #[derive(Debug, Default, Serialize)] struct Rule36TransitionAudit { presentation_cases: usize, - primary_transitions: BTreeMap, + observed_transitions: BTreeMap, remaining_complex_errors: usize, remaining_complex_error_samples: Vec, } +#[derive(Debug, Serialize)] +struct UnclassifiedEncodingErrorSample { + shard: String, + index: usize, + input: String, + error: String, +} + +#[derive(Debug, Serialize)] +struct MultipleSingletonErrorSample { + shard: String, + index: usize, + input: String, + unsupported_characters: Vec, +} + +#[derive(Debug, Default, Serialize)] +struct EncodingErrorAudit { + raw_total: usize, + resolved_by_comparison_method: usize, + excluded_as_corpus_suspect: usize, + unresolved_review_total: usize, + explained_by_singleton_unsupported: usize, + multiple_singleton_unsupported: usize, + multiple_singleton_samples: Vec, + unclassified_without_singleton: usize, + unclassified_samples: Vec, +} + #[derive(Debug, Serialize)] struct AnalysisReport { corpus: &'static str, @@ -115,6 +148,7 @@ struct AnalysisReport { encoding_error_messages: BTreeMap, encoding_error_families: BTreeMap, singleton_error_characters: BTreeMap, + encoding_error_audit: EncodingErrorAudit, rule_36_transition_audit: Rule36TransitionAudit, overlapping_traits: BTreeMap, shards: BTreeMap, @@ -246,6 +280,20 @@ fn encode_cases(cases: &[LocatedCase], thread_count: usize) -> Vec .cloned() .map(|located| { let actual = braillify::encode_to_unicode(&located.case.input); + let singleton_unsupported_characters = if actual.is_err() { + located + .case + .input + .chars() + .collect::>() + .into_iter() + .filter(|ch| { + braillify::encode_to_unicode(&ch.to_string()).is_err() + }) + .collect() + } else { + Vec::new() + }; let nfc: String = located.case.input.nfc().collect(); let nfc_actual = (nfc != located.case.input) .then(|| braillify::encode_to_unicode(&nfc)); @@ -257,6 +305,7 @@ fn encode_cases(cases: &[LocatedCase], thread_count: usize) -> Vec actual, nfc_actual, nfkc_actual, + singleton_unsupported_characters, } }) .collect::>() @@ -337,7 +386,14 @@ fn classify(encoded: &EncodedCase, conflicting: &BTreeSet) -> (PrimaryCl PrimaryClass::ImplementationDefect, Reason::RomanIndicatorAfterCapitalIndicator, ), - Err(_) => (PrimaryClass::ImplementationDefect, Reason::EncodingError), + Err(_) if !encoded.singleton_unsupported_characters.is_empty() => ( + PrimaryClass::UnsupportedCharacterReview, + Reason::UnsupportedCharacterReview, + ), + Err(_) => ( + PrimaryClass::UnclassifiedEncodingErrorReview, + Reason::UnclassifiedEncodingErrorReview, + ), Ok(_) if encoded .located @@ -382,31 +438,54 @@ fn is_roman_numeral_presentation(ch: char) -> bool { (0x2160..=0x217f).contains(&(ch as u32)) } -/// Reconstruct the immediately preceding rule-36 behavior without running a -/// second engine or reading a saved expected-output lookup. Before targeted -/// normalization, any U+2160–U+217F character made the direct and NFC paths -/// fail. The NFKC comparison path already contains the corresponding ASCII -/// Roman letters and is unaffected by the engine change, so it remains valid -/// for reproducing the former `comparison_method` classification. -fn classify_before_rule_36( - encoded: &EncodedCase, - conflicting: &BTreeSet, -) -> Option<(PrimaryClass, Reason)> { - encoded +/// Reconstruct an observable before/after transition for the rule-36 cohort +/// without assigning today's primary-class policy to the historical run. +/// Before targeted normalization, direct/NFC encoding failed; an exact NFKC +/// path was observable separately. The current side reports only whether the +/// case is exact, an encoded mismatch awaiting rule review, or still blocked by +/// another independently unsupported singleton character. +fn rule_36_observed_transition(encoded: &EncodedCase) -> Option<&'static str> { + if !encoded .located .case .input .chars() .any(is_roman_numeral_presentation) - .then(|| { - let mut legacy = encoded.clone(); - legacy.actual = Err("legacy unsupported Roman-numeral presentation".to_string()); - legacy.nfc_actual = legacy - .nfc_actual - .as_ref() - .map(|_| Err("legacy unsupported Roman-numeral presentation".to_string())); - classify(&legacy, conflicting) - }) + { + return None; + } + + let expected = &encoded.located.case.unicode; + let before = if encoded + .nfkc_actual + .as_ref() + .is_some_and(|result| result.as_ref().is_ok_and(|actual| actual == expected)) + { + "nfkc_input_equivalent" + } else { + "encoding_error" + }; + let after = match &encoded.actual { + Ok(actual) if actual == expected => "exact", + Ok(_) => "encoded_mismatch_pending_rule_review", + Err(_) if !encoded.singleton_unsupported_characters.is_empty() => { + "unsupported_character_review" + } + Err(_) => "unclassified_encoding_error_review", + }; + Some(match (before, after) { + ("nfkc_input_equivalent", "exact") => "nfkc_input_equivalent -> exact", + ("encoding_error", "encoded_mismatch_pending_rule_review") => { + "encoding_error -> encoded_mismatch_pending_rule_review" + } + ("encoding_error", "unsupported_character_review") => { + "encoding_error -> unsupported_character_review" + } + ("encoding_error", "unclassified_encoding_error_review") => { + "encoding_error -> unclassified_encoding_error_review" + } + _ => "other_observed_transition", + }) } fn is_delimiter_or_quote(ch: char) -> bool { @@ -501,7 +580,7 @@ fn analyze( let mut encoding_error_messages = BTreeMap::new(); let mut encoding_error_families = BTreeMap::new(); let mut singleton_error_characters = BTreeMap::::new(); - let mut singleton_error_cache = BTreeMap::::new(); + let mut encoding_error_audit = EncodingErrorAudit::default(); let mut traits = BTreeMap::new(); let mut shards = BTreeMap::::new(); let mut samples = BTreeMap::>::new(); @@ -510,24 +589,19 @@ fn analyze( for item in &encoded { let (primary, reason) = classify(item, &conflicting); - if let Some((before_primary, _)) = classify_before_rule_36(item, &conflicting) { + if let Some(transition) = rule_36_observed_transition(item) { rule_36_transition_audit.presentation_cases += 1; - let transition = format!("{} -> {}", enum_key(&before_primary), enum_key(&primary)); *rule_36_transition_audit - .primary_transitions - .entry(transition) + .observed_transitions + .entry(transition.to_string()) .or_insert(0) += 1; - if primary == PrimaryClass::ImplementationDefect && item.actual.is_err() { + if item.actual.is_err() { let other_unsupported_characters = item - .located - .case - .input - .chars() - .collect::>() - .into_iter() + .singleton_unsupported_characters + .iter() + .copied() .filter(|ch| !is_roman_numeral_presentation(*ch)) - .filter(|ch| braillify::encode_to_unicode(&ch.to_string()).is_err()) .map(|ch| format!("U+{:04X} {ch}", ch as u32)) .collect::>(); rule_36_transition_audit.remaining_complex_errors += 1; @@ -555,19 +629,49 @@ fn analyze( } let input = &item.located.case.input; - if primary == PrimaryClass::ImplementationDefect - && let Err(error) = &item.actual - { - *encoding_error_messages.entry(error.clone()).or_insert(0) += 1; - let unique_chars = input.chars().collect::>(); - let mut case_families = BTreeSet::new(); - for ch in unique_chars { - let fails_alone = *singleton_error_cache - .entry(ch) - .or_insert_with(|| braillify::encode_to_unicode(&ch.to_string()).is_err()); - if fails_alone { - let key = format!("U+{:04X} {ch}", ch as u32); - let family = encoding_error_family(ch); + if let Err(error) = &item.actual { + encoding_error_audit.raw_total += 1; + if primary == PrimaryClass::ComparisonMethod { + encoding_error_audit.resolved_by_comparison_method += 1; + } else if primary == PrimaryClass::CorpusSuspect { + encoding_error_audit.excluded_as_corpus_suspect += 1; + } else { + encoding_error_audit.unresolved_review_total += 1; + if item.singleton_unsupported_characters.is_empty() { + encoding_error_audit.unclassified_without_singleton += 1; + if encoding_error_audit.unclassified_samples.len() < sample_limit { + encoding_error_audit.unclassified_samples.push( + UnclassifiedEncodingErrorSample { + shard: item.located.shard.clone(), + index: item.located.index, + input: item.located.case.input.clone(), + error: error.clone(), + }, + ); + } + } else { + encoding_error_audit.explained_by_singleton_unsupported += 1; + if item.singleton_unsupported_characters.len() > 1 { + encoding_error_audit.multiple_singleton_unsupported += 1; + encoding_error_audit.multiple_singleton_samples.push( + MultipleSingletonErrorSample { + shard: item.located.shard.clone(), + index: item.located.index, + input: item.located.case.input.clone(), + unsupported_characters: item + .singleton_unsupported_characters + .iter() + .map(|ch| format!("U+{:04X} {ch}", *ch as u32)) + .collect(), + }, + ); + } + } + *encoding_error_messages.entry(error.clone()).or_insert(0) += 1; + let mut case_families = BTreeSet::new(); + for ch in &item.singleton_unsupported_characters { + let key = format!("U+{:04X} {ch}", *ch as u32); + let family = encoding_error_family(*ch); case_families.insert(family); singleton_error_characters .entry(key) @@ -578,11 +682,11 @@ fn analyze( family, }); } - } - for family in case_families { - *encoding_error_families - .entry(family.to_string()) - .or_insert(0) += 1; + for family in case_families { + *encoding_error_families + .entry(family.to_string()) + .or_insert(0) += 1; + } } } for (name, present) in [ @@ -643,6 +747,7 @@ fn analyze( encoding_error_messages, encoding_error_families, singleton_error_characters, + encoding_error_audit, rule_36_transition_audit, overlapping_traits: traits, shards, @@ -679,7 +784,11 @@ fn markdown(report: &AnalysisReport) -> String { text.push_str( "Primary classes are evidence gates, not permissions to change the engine. \ `implementation_defect` is restricted to defects independently confirmed from the PDF \ - (the rules 28/29 roman-indicator ordering signature) and actual encoding errors. \ + (currently the rules 28/29 roman-indicator ordering signature). \ + `unsupported_character_review` contains encoding failures fully explained by one or more \ + singleton characters whose support obligation has not been confirmed from the PDF. \ + `unclassified_encoding_error_review` contains other encoding failures until a PDF-backed \ + implementation obligation or a reproducible comparison/corpus issue is established. \ `pending_rule_review` contains foreign-text, number, punctuation, and Korean candidates \ that have not yet been resolved against the PDF. `corpus_suspect` is reserved for \ independently detectable contradictions such as one input having multiple references. \ @@ -696,9 +805,70 @@ fn markdown(report: &AnalysisReport) -> String { text.push_str("\n## Encoding-error diagnostics\n\n"); text.push_str( - "These are overlapping diagnostics for `implementation_defect` encoding errors, not additional primary classes. \ - A singleton error character is a character that also fails when encoded by itself.\n\n", + "The audit starts from all raw encoding errors, then separates cases already resolved by \ + a comparison method or corpus contradiction. The message, family, and singleton tables \ + below count only unresolved encoding-error review cases. These diagnostics are not \ + additional primary classes. \ + A singleton unsupported character is a character that also fails when encoded by itself. \ + Such a failure remains a review candidate until the PDF independently establishes support.\n\n", ); + text.push_str("| Encoding-error audit | Cases |\n|---|---:|\n"); + text.push_str(&format!( + "| Raw encoding errors | {} |\n", + report.encoding_error_audit.raw_total + )); + text.push_str(&format!( + "| Resolved by comparison method | {} |\n", + report.encoding_error_audit.resolved_by_comparison_method + )); + text.push_str(&format!( + "| Excluded as corpus suspect | {} |\n", + report.encoding_error_audit.excluded_as_corpus_suspect + )); + text.push_str(&format!( + "| Unresolved encoding-error review cases | {} |\n", + report.encoding_error_audit.unresolved_review_total + )); + text.push_str(&format!( + "| Explained by singleton unsupported character(s) | {} |\n", + report + .encoding_error_audit + .explained_by_singleton_unsupported + )); + text.push_str(&format!( + "| Multiple singleton unsupported characters | {} |\n", + report.encoding_error_audit.multiple_singleton_unsupported + )); + text.push_str(&format!( + "| Unclassified without a singleton explanation | {} |\n\n", + report.encoding_error_audit.unclassified_without_singleton + )); + for sample in &report.encoding_error_audit.multiple_singleton_samples { + text.push_str(&format!( + "- compound `{}` #{}: {}\n - singleton unsupported: `{}`\n", + sample.shard, + sample.index, + sample.input.chars().take(180).collect::(), + sample.unsupported_characters.join(", ") + )); + } + for sample in &report.encoding_error_audit.unclassified_samples { + text.push_str(&format!( + "- unclassified `{}` #{}: {} (`{}`)\n", + sample.shard, + sample.index, + sample.input.chars().take(180).collect::(), + sample.error + )); + } + if !report + .encoding_error_audit + .multiple_singleton_samples + .is_empty() + || !report.encoding_error_audit.unclassified_samples.is_empty() + { + text.push('\n'); + } text.push_str("| Error message | Cases |\n|---|---:|\n"); for (name, count) in &report.encoding_error_messages { text.push_str(&format!("| `{name}` | {count} |\n")); @@ -803,8 +973,8 @@ fn markdown(report: &AnalysisReport) -> String { "Presentation-form cases audited: {}.\n\n", report.rule_36_transition_audit.presentation_cases )); - text.push_str("| Previous primary → current primary | Cases |\n|---|---:|\n"); - for (transition, count) in &report.rule_36_transition_audit.primary_transitions { + text.push_str("| Previous observation → current observation | Cases |\n|---|---:|\n"); + for (transition, count) in &report.rule_36_transition_audit.observed_transitions { text.push_str(&format!("| `{transition}` | {count} |\n")); } text.push_str(&format!( @@ -845,7 +1015,7 @@ fn markdown(report: &AnalysisReport) -> String { "| Rules 68/69 compatibility unit algorithm | 5,141/5,141 | 63,388/83,528 | 75.89% | 96 Unicode unit presentation forms use one decomposition/letter-run/attachment algorithm; encoding errors fell from 434 to 226 |\n", ); text.push_str( - "| Rule 36 Unicode Roman-numeral presentation normalization | 5,141/5,141 | 63,399/83,528 | 75.90% | U+2160–U+217F use the corresponding Roman-letter spelling; 11 NFKC-comparison cases became exact, 23 encoding errors became pending review, and 3 compound errors remain |\n", + "| Rule 36 Unicode Roman-numeral presentation normalization | 5,141/5,141 | 63,399/83,528 | 75.90% | U+2160–U+217F use the corresponding Roman-letter spelling; 11 NFKC-equivalent observations became exact, 23 errors became encoded mismatches pending review, and 3 remain blocked by `㈜` |\n", ); text.push_str( "\nEngine changes must add a row only after both the 5,141-case standard suite and \ @@ -926,32 +1096,32 @@ mod tests { "same", Some("same"), "same", - PrimaryClass::ComparisonMethod, - PrimaryClass::Exact + &[], + "nfkc_input_equivalent -> exact" )] #[case::encoding_error_becomes_pending( "Ⅳ장", "expected", Some("different"), "different", - PrimaryClass::ImplementationDefect, - PrimaryClass::PendingRuleReview + &[], + "encoding_error -> encoded_mismatch_pending_rule_review" )] #[case::compound_encoding_error_remains( "Ⅳ㈜", "expected", None, "different", - PrimaryClass::ImplementationDefect, - PrimaryClass::ImplementationDefect + &['㈜'], + "encoding_error -> unsupported_character_review" )] - fn reconstructs_rule_36_primary_transition( + fn reconstructs_rule_36_observed_transition( #[case] input: &str, #[case] expected: &str, #[case] actual: Option<&str>, #[case] nfkc_actual: &str, - #[case] expected_before: PrimaryClass, - #[case] expected_after: PrimaryClass, + #[case] singleton_unsupported_characters: &[char], + #[case] expected_transition: &str, ) { let encoded = EncodedCase { located: LocatedCase { @@ -968,14 +1138,50 @@ mod tests { ), nfc_actual: None, nfkc_actual: Some(Ok(nfkc_actual.to_string())), + singleton_unsupported_characters: singleton_unsupported_characters.to_vec(), }; - let conflicts = BTreeSet::new(); - let (before, _) = classify_before_rule_36(&encoded, &conflicts).unwrap(); - let (after, _) = classify(&encoded, &conflicts); + assert_eq!( + rule_36_observed_transition(&encoded), + Some(expected_transition) + ); + } - assert_eq!(before, expected_before); - assert_eq!(after, expected_after); + #[rstest::rstest] + #[case::singleton_explained( + &['㈜'], + PrimaryClass::UnsupportedCharacterReview, + Reason::UnsupportedCharacterReview + )] + #[case::unclassified( + &[], + PrimaryClass::UnclassifiedEncodingErrorReview, + Reason::UnclassifiedEncodingErrorReview + )] + fn encoding_error_primary_requires_independent_pdf_evidence( + #[case] singleton_unsupported_characters: &[char], + #[case] expected_primary: PrimaryClass, + #[case] expected_reason: Reason, + ) { + let encoded = EncodedCase { + located: LocatedCase { + shard: "synthetic.json".to_string(), + index: 1, + case: CorpusCase { + input: "입력".to_string(), + unicode: "expected".to_string(), + }, + }, + actual: Err("encoding failed".to_string()), + nfc_actual: None, + nfkc_actual: None, + singleton_unsupported_characters: singleton_unsupported_characters.to_vec(), + }; + + assert_eq!( + classify(&encoded, &BTreeSet::new()), + (expected_primary, expected_reason) + ); } #[test] From edea889eb07f4b52ce77165f4b64a25381630a13 Mon Sep 17 00:00:00 2001 From: devfive Date: Tue, 1 Sep 2026 04:51:25 +0900 Subject: [PATCH 06/77] Cache singleton corpus character probes --- docs/corpus-analysis/NIKL_2025_V1.md | 1 + .../braillify/examples/nikl_corpus_analyze.rs | 69 +++++++++++++++++-- 2 files changed, 65 insertions(+), 5 deletions(-) diff --git a/docs/corpus-analysis/NIKL_2025_V1.md b/docs/corpus-analysis/NIKL_2025_V1.md index 7c27bbec..0be18b9a 100644 --- a/docs/corpus-analysis/NIKL_2025_V1.md +++ b/docs/corpus-analysis/NIKL_2025_V1.md @@ -10,6 +10,7 @@ | Exact | 63399 | | Mismatch | 20129 | | Exact accuracy | 75.90% | +| Analysis wall time | 2.072 s | | Duplicate records | 0 | | Inputs with conflicting references | 0 | diff --git a/libs/braillify/examples/nikl_corpus_analyze.rs b/libs/braillify/examples/nikl_corpus_analyze.rs index 7def10e0..f44b111d 100644 --- a/libs/braillify/examples/nikl_corpus_analyze.rs +++ b/libs/braillify/examples/nikl_corpus_analyze.rs @@ -11,6 +11,7 @@ use std::fs::{self, File}; use std::io::BufReader; use std::path::{Path, PathBuf}; use std::thread; +use std::time::Instant; use serde::{Deserialize, Serialize}; use unicode_normalization::UnicodeNormalization; @@ -141,6 +142,7 @@ struct AnalysisReport { exact: usize, mismatch: usize, exact_percent: f64, + analysis_wall_time_ms: u128, duplicate_inputs: usize, conflicting_duplicate_inputs: usize, primary_classes: BTreeMap, @@ -268,9 +270,34 @@ fn validate_corpus_shape(shard_count: usize, case_count: usize) -> Result<(), St Ok(()) } +fn singleton_unsupported_set_with( + cases: &[LocatedCase], + mut fails_alone: impl FnMut(char) -> bool, +) -> BTreeSet { + cases + .iter() + .flat_map(|located| located.case.input.chars()) + .collect::>() + .into_iter() + .filter(|ch| fails_alone(*ch)) + .collect() +} + +fn singleton_unsupported_set(cases: &[LocatedCase]) -> BTreeSet { + singleton_unsupported_set_with(cases, |ch| { + braillify::encode_to_unicode(&ch.to_string()).is_err() + }) +} + fn encode_cases(cases: &[LocatedCase], thread_count: usize) -> Vec { + // Compute singleton support exactly once per distinct corpus character. + // `BTreeSet` keeps both probing and report assignment deterministic; workers + // only perform membership lookups instead of re-encoding common marks such + // as `㈜` hundreds of times across error sentences. + let singleton_unsupported = singleton_unsupported_set(cases); let chunk_size = cases.len().div_ceil(thread_count); let mut chunks = thread::scope(|scope| { + let singleton_unsupported = &singleton_unsupported; cases .chunks(chunk_size.max(1)) .map(|chunk| { @@ -287,9 +314,7 @@ fn encode_cases(cases: &[LocatedCase], thread_count: usize) -> Vec .chars() .collect::>() .into_iter() - .filter(|ch| { - braillify::encode_to_unicode(&ch.to_string()).is_err() - }) + .filter(|ch| singleton_unsupported.contains(ch)) .collect() } else { Vec::new() @@ -740,6 +765,7 @@ fn analyze( exact, mismatch: total - exact, exact_percent: exact as f64 / total as f64 * 100.0, + analysis_wall_time_ms: 0, duplicate_inputs, conflicting_duplicate_inputs: conflicting.len(), primary_classes, @@ -771,6 +797,10 @@ fn markdown(report: &AnalysisReport) -> String { "| Exact accuracy | {:.2}% |\n", report.exact_percent )); + text.push_str(&format!( + "| Analysis wall time | {:.3} s |\n", + report.analysis_wall_time_ms as f64 / 1000.0 + )); text.push_str(&format!( "| Duplicate records | {} |\n", report.duplicate_inputs @@ -1034,19 +1064,22 @@ fn write_file(path: &Path, contents: &str) -> Result<(), String> { } fn run() -> Result<(), String> { + let started = Instant::now(); let config = Config::parse()?; let cases = load_cases()?; let encoded = encode_cases(&cases, config.threads); - let report = analyze(cases, encoded, config.sample_limit); + let mut report = analyze(cases, encoded, config.sample_limit); + report.analysis_wall_time_ms = started.elapsed().as_millis(); let json = serde_json::to_string_pretty(&report) .map_err(|error| format!("cannot serialize analysis JSON: {error}"))?; write_file(&config.json_path, &json)?; write_file(&config.report_path, &markdown(&report))?; println!( - "NIKL corpus: {}/{} exact ({:.2}%), report={}, json={}", + "NIKL corpus: {}/{} exact ({:.2}%), wall={:.3}s, report={}, json={}", report.exact, report.total, report.exact_percent, + report.analysis_wall_time_ms as f64 / 1000.0, config.report_path.display(), config.json_path.display() ); @@ -1080,6 +1113,32 @@ mod tests { } } + #[test] + fn singleton_cache_probes_each_distinct_corpus_character_once() { + let cases = ["㈜Aℓ", "㈜Bℓ"] + .into_iter() + .enumerate() + .map(|(index, input)| LocatedCase { + shard: "synthetic.json".to_string(), + index, + case: CorpusCase { + input: input.to_string(), + unicode: String::new(), + }, + }) + .collect::>(); + let mut calls = BTreeMap::::new(); + + let unsupported = singleton_unsupported_set_with(&cases, |ch| { + *calls.entry(ch).or_insert(0) += 1; + matches!(ch, '㈜' | 'ℓ') + }); + + assert_eq!(unsupported, BTreeSet::from(['ℓ', '㈜'])); + assert_eq!(calls.len(), 4); + assert!(calls.values().all(|count| *count == 1)); + } + #[rstest::rstest] #[case::compatibility_unit('㎏', "compatibility_unit_symbol")] #[case::roman_numeral('Ⅱ', "roman_numeral_presentation")] From d664001d04e048b3f420cca53d5a91c7f670e2de Mon Sep 17 00:00:00 2001 From: devfive Date: Tue, 1 Sep 2026 04:57:00 +0900 Subject: [PATCH 07/77] Keep corpus analysis artifacts deterministic --- docs/corpus-analysis/NIKL_2025_V1.md | 1 - libs/braillify/examples/nikl_corpus_analyze.rs | 11 ++--------- 2 files changed, 2 insertions(+), 10 deletions(-) diff --git a/docs/corpus-analysis/NIKL_2025_V1.md b/docs/corpus-analysis/NIKL_2025_V1.md index 0be18b9a..7c27bbec 100644 --- a/docs/corpus-analysis/NIKL_2025_V1.md +++ b/docs/corpus-analysis/NIKL_2025_V1.md @@ -10,7 +10,6 @@ | Exact | 63399 | | Mismatch | 20129 | | Exact accuracy | 75.90% | -| Analysis wall time | 2.072 s | | Duplicate records | 0 | | Inputs with conflicting references | 0 | diff --git a/libs/braillify/examples/nikl_corpus_analyze.rs b/libs/braillify/examples/nikl_corpus_analyze.rs index f44b111d..2fede669 100644 --- a/libs/braillify/examples/nikl_corpus_analyze.rs +++ b/libs/braillify/examples/nikl_corpus_analyze.rs @@ -142,7 +142,6 @@ struct AnalysisReport { exact: usize, mismatch: usize, exact_percent: f64, - analysis_wall_time_ms: u128, duplicate_inputs: usize, conflicting_duplicate_inputs: usize, primary_classes: BTreeMap, @@ -765,7 +764,6 @@ fn analyze( exact, mismatch: total - exact, exact_percent: exact as f64 / total as f64 * 100.0, - analysis_wall_time_ms: 0, duplicate_inputs, conflicting_duplicate_inputs: conflicting.len(), primary_classes, @@ -797,10 +795,6 @@ fn markdown(report: &AnalysisReport) -> String { "| Exact accuracy | {:.2}% |\n", report.exact_percent )); - text.push_str(&format!( - "| Analysis wall time | {:.3} s |\n", - report.analysis_wall_time_ms as f64 / 1000.0 - )); text.push_str(&format!( "| Duplicate records | {} |\n", report.duplicate_inputs @@ -1068,8 +1062,7 @@ fn run() -> Result<(), String> { let config = Config::parse()?; let cases = load_cases()?; let encoded = encode_cases(&cases, config.threads); - let mut report = analyze(cases, encoded, config.sample_limit); - report.analysis_wall_time_ms = started.elapsed().as_millis(); + let report = analyze(cases, encoded, config.sample_limit); let json = serde_json::to_string_pretty(&report) .map_err(|error| format!("cannot serialize analysis JSON: {error}"))?; write_file(&config.json_path, &json)?; @@ -1079,7 +1072,7 @@ fn run() -> Result<(), String> { report.exact, report.total, report.exact_percent, - report.analysis_wall_time_ms as f64 / 1000.0, + started.elapsed().as_secs_f64(), config.report_path.display(), config.json_path.display() ); From 3948b0ce761588ea9c719dad3820ed988b20a279 Mon Sep 17 00:00:00 2001 From: devfive Date: Tue, 1 Sep 2026 05:21:53 +0900 Subject: [PATCH 08/77] Keep Korean Roman annotations out of math splitting --- docs/corpus-analysis/NIKL_2025_V1.md | 71 +++++++++------- .../braillify/examples/nikl_corpus_analyze.rs | 25 ++++++ .../src/rules/token_rules/math_expression.rs | 82 +++++++++++++++++++ .../token_rules/math_expression/helpers.rs | 33 ++++++++ 4 files changed, 180 insertions(+), 31 deletions(-) diff --git a/docs/corpus-analysis/NIKL_2025_V1.md b/docs/corpus-analysis/NIKL_2025_V1.md index 7c27bbec..a5706b9b 100644 --- a/docs/corpus-analysis/NIKL_2025_V1.md +++ b/docs/corpus-analysis/NIKL_2025_V1.md @@ -7,9 +7,9 @@ | Metric | Count | |---|---:| | Total | 83528 | -| Exact | 63399 | -| Mismatch | 20129 | -| Exact accuracy | 75.90% | +| Exact | 65491 | +| Mismatch | 18037 | +| Exact accuracy | 78.41% | | Duplicate records | 0 | | Inputs with conflicting references | 0 | @@ -19,19 +19,19 @@ Primary classes are evidence gates, not permissions to change the engine. `imple | Primary class | Count | |---|---:| -| `comparison_method` | 290 | -| `exact` | 63399 | -| `pending_rule_review` | 19636 | -| `unsupported_character_review` | 203 | +| `comparison_method` | 303 | +| `exact` | 65491 | +| `pending_rule_review` | 17543 | +| `unsupported_character_review` | 191 | | Reproducible reason | Count | |---|---:| -| `exact` | 63399 | -| `foreign_text_rule_review` | 19445 | -| `nfkc_input_equivalent` | 290 | +| `exact` | 65491 | +| `foreign_text_rule_review` | 17352 | +| `nfkc_input_equivalent` | 303 | | `number_rule_review` | 178 | | `punctuation_rule_review` | 13 | -| `unsupported_character_review` | 203 | +| `unsupported_character_review` | 191 | ## Encoding-error diagnostics @@ -40,10 +40,10 @@ The audit starts from all raw encoding errors, then separates cases already reso | Encoding-error audit | Cases | |---|---:| | Raw encoding errors | 450 | -| Resolved by comparison method | 247 | +| Resolved by comparison method | 259 | | Excluded as corpus suspect | 0 | -| Unresolved encoding-error review cases | 203 | -| Explained by singleton unsupported character(s) | 203 | +| Unresolved encoding-error review cases | 191 | +| Explained by singleton unsupported character(s) | 191 | | Multiple singleton unsupported characters | 2 | | Unclassified without a singleton explanation | 0 | @@ -54,11 +54,11 @@ The audit starts from all raw encoding errors, then separates cases already reso | Error message | Cases | |---|---:| -| `Invalid symbol character` | 203 | +| `Invalid symbol character` | 191 | | Error family | Cases | |---|---:| -| `enclosed_organization_mark` | 181 | +| `enclosed_organization_mark` | 169 | | `letterlike_unit_symbol` | 12 | | `punctuation_or_layout_symbol` | 12 | @@ -77,36 +77,33 @@ Families are diagnostics, not automatic normalization permissions. Rules 68/69 c | `U+2E31 ⸱` | 2 | `⸱` | `punctuation_or_layout_symbol` | | `U+302E 〮` | 1 | `〮` | `punctuation_or_layout_symbol` | | `U+3214 ㈔` | 2 | `(사)` | `enclosed_organization_mark` | -| `U+321C ㈜` | 179 | `(주)` | `enclosed_organization_mark` | +| `U+321C ㈜` | 167 | `(주)` | `enclosed_organization_mark` | ## Shards | Shard | Exact | Total | Accuracy | |---|---:|---:|---:| -| `sentence_01.json` | 18889 | 25000 | 75.56% | -| `sentence_02.json` | 18355 | 25000 | 73.42% | -| `sentence_03.json` | 19406 | 25000 | 77.62% | -| `sentence_04.json` | 6749 | 8528 | 79.14% | +| `sentence_01.json` | 19563 | 25000 | 78.25% | +| `sentence_02.json` | 19059 | 25000 | 76.24% | +| `sentence_03.json` | 19914 | 25000 | 79.66% | +| `sentence_04.json` | 6955 | 8528 | 81.55% | ## Overlapping mismatch traits | Trait | Count | |---|---:| -| `contains_ascii_digits` | 15691 | -| `contains_ascii_letters` | 19927 | -| `contains_delimiter_or_quote` | 20129 | -| `input_not_nfkc` | 849 | +| `contains_ascii_digits` | 14375 | +| `contains_ascii_letters` | 17835 | +| `contains_delimiter_or_quote` | 18037 | +| `input_not_nfkc` | 826 | ## Samples ### `foreign_text_rule_review` -- `sentence_01.json` #12: 특히 미국 헬스탭(HealthTap), 국내 굿닥(Goodoc)과 협업해 병원에 갈 필요 없이 집 안에서도 편리하게 진료를 받을 수 있는 원격 헬스케어 서비스를 시연한다. - - expected: `⠚⠝⠂⠠⠪⠓⠗⠃⠦⠄⠴⠠⠓⠂⠇⠹⠠⠞⠁⠏⠠⠴⠐⠀` - - actual: `⠚⠝⠂⠠⠪⠓⠗⠃⠀⠀⠦⠠⠓⠑⠁⠇⠞⠓⠠⠞⠁⠏⠴⠐` - `sentence_01.json` #14: 새롭게 선보인 DRX 브랜딩과 팀 컬러는 2023년 1월 1일부터 DRX의 온오프라인 콘텐츠, 엠디(MD), SNS, 유튜브 등 모든 제작물에 적용될 예정이다. - expected: `⠰⠪⠐⠀⠝⠢⠊⠕⠴⠐⠣⠠⠠⠍⠙⠐⠜⠂⠀⠠⠠⠎⠝⠎` - - actual: `⠰⠪⠐⠀⠝⠢⠊⠕⠀⠀⠦⠠⠠⠍⠙⠴⠐⠀⠴⠠⠠⠎⠝⠎` + - actual: `⠰⠪⠐⠀⠝⠢⠊⠕⠦⠄⠴⠠⠠⠍⠙⠠⠴⠐⠀⠰⠠⠠⠎⠝` - `sentence_01.json` #17: 지난달 30일(현지시각) 뉴욕증권거래소(NYSE)에서 다우존스30산업평균지수는 전 거래일보다 73.55포인트(0.22%) 하락한 3만3147.25로 마감했다. 대형주 중심의 S&P500지수는 9.78포인트(0.25%) 하락한 3839.50으로, 기술주 중심의 나스닥지수는 11.61포인트(0.11%) 하락한 1만0466.4 - expected: `⠶⠠⠕⠢⠺⠀⠴⠠⠎⠈⠯⠠⠏⠼⠑⠚⠚⠨⠕⠠⠍⠉⠵⠀` - actual: `⠶⠠⠕⠢⠺⠀⠴⠠⠠⠎⠲⠴⠈⠯⠲⠴⠏⠼⠑⠚⠚⠨⠕⠠` @@ -114,8 +111,11 @@ Families are diagnostics, not automatic normalization permissions. Rules 68/69 c - expected: `⠁⠞⠞⠻⠠⠴⠧⠀⠴⠠⠠⠓⠉⠁⠐⠣⠠⠓⠕⠍⠑⠀⠠⠒` - actual: `⠁⠞⠞⠻⠠⠴⠧⠀⠀⠠⠠⠓⠉⠁⠦⠠⠓⠕⠍⠑⠀⠴⠠⠒` - `sentence_01.json` #25: 세븐일레븐은 올해 말 정식 버전 오픈을 목표로 하며, 이를 통해 주력상품을 구매하고 배송까지 받을 수 있는 차세대 오프라인을 위한 온라인(O4O) 시스템을 구축해 나갈 계획이다. - - expected: `⠗⠚⠒⠀⠷⠐⠣⠟⠦⠄⠴⠠⠕⠼⠙⠠⠕⠠⠴⠀⠠⠕⠠⠪` - - actual: `⠗⠚⠒⠀⠷⠐⠣⠟⠀⠀⠦⠠⠕⠼⠙⠠⠕⠴⠀⠠⠕⠠⠪⠓` + - expected: `⠟⠦⠄⠴⠠⠕⠼⠙⠠⠕⠠⠴⠀⠠⠕⠠⠪⠓⠝⠢⠮⠀⠈⠍` + - actual: `⠟⠦⠄⠴⠠⠕⠼⠙⠰⠠⠕⠠⠴⠀⠠⠕⠠⠪⠓⠝⠢⠮⠀⠈` +- `sentence_01.json` #35: 삼성전자는 올해 CES에서 77인치 OLED TV를 처음으로 공개할 예정이다. 이는 지난 8월 ‘국제정보디스플레이학술대회(IMID) 2022’에서 삼성디스플레이가 공개한 77인치 퀀텀닷(QD)-OLED 패널을 탑재한 제품이다. + - expected: `⠋⠏⠒⠓⠎⠢⠊⠄⠴⠐⠣⠟⠙⠐⠜⠤⠠⠠⠕⠇⠫⠲⠀⠙` + - actual: `⠋⠏⠒⠓⠎⠢⠊⠄⠀⠀⠦⠠⠠⠟⠙⠴⠔⠠⠠⠕⠇⠑⠙⠀` ### `nfkc_input_equivalent` @@ -232,6 +232,14 @@ Remaining complex encoding errors: 3. These cases still contain another characte - `sentence_02.json` #740: 한국폴리텍Ⅳ대학 대전캠퍼스(학장 박봉순) 메타버스콘텐츠과(학과장 이종탁)는 지난 10월 5일 3D Scan & Solution 전문기업 위프코㈜와 메타버스분야 인재양성과 취업 등에 관한 업무협약(MOU)을 체결했다고 알렸다. - other independently unsupported: `U+321C ㈜` +## Rules 34/54 Korean-prefixed Roman annotations + +Rule 34 says that when Roman text is enclosed by quotation marks or brackets, the Roman terminator is omitted; its PDF example is `링컨(Lincoln)은 미국의 제16대 대통령이다.` Rule 54 says that text immediately after an opening bracket and immediately before a closing bracket is attached. Together these establish the Korean-prefix + closed-Roman-annotation context independently of corpus expected values. A following comma or period is outside the already closed annotation and must not cause its Roman contents to be rerouted as mathematics. + +The implementation gate exists only inside `split_mixed_math_word`, after the prefix has been proved entirely Korean. It accepts a fully closed parenthesized Roman word (including ASCII digits such as `O4O`) plus ordinary trailing prose punctuation. The global math detector is byte-for-byte unchanged; regression tests preserve its existing standalone results for `(x)`, `(A)`, and `(abc)`, while explicit forms such as `(x+1)`, `(a/b)`, and `(x₁)` remain math candidates. + +Against the immediately preceding 63,399-exact run, exact matches increased by 2,092. The observable primary totals changed as follows: `comparison_method` 290→303, `pending_rule_review` 19,636→17,543, and `unsupported_character_review` 203→191. Raw encoding errors stayed at 450; errors resolved by a comparison method changed 247→259 and unresolved review errors changed 203→191. + ## Rule evidence and change log | Stage | Standard cases | Corpus exact | Corpus accuracy | Evidence | @@ -241,5 +249,6 @@ Remaining complex encoding errors: 3. These cases still contain another characte | Rules 37/39 shared UEB groupsign algorithm | 5,141/5,141 | 63,239/83,528 | 75.71% | Korean Roman sections reuse UEB preference/morphology rules; entry wordsigns and English-dominant resume are state-gated | | Rules 68/69 compatibility unit algorithm | 5,141/5,141 | 63,388/83,528 | 75.89% | 96 Unicode unit presentation forms use one decomposition/letter-run/attachment algorithm; encoding errors fell from 434 to 226 | | Rule 36 Unicode Roman-numeral presentation normalization | 5,141/5,141 | 63,399/83,528 | 75.90% | U+2160–U+217F use the corresponding Roman-letter spelling; 11 NFKC-equivalent observations became exact, 23 errors became encoded mismatches pending review, and 3 remain blocked by `㈜` | +| Rules 34/54 Korean-prefixed closed Roman annotation routing | 5,141/5,141 | 65,491/83,528 | 78.41% | A fully closed Roman annotation after an all-Korean prefix stays on the prose encoder path, including attached comma/period and alphanumeric forms such as `O4O`; exact matches increased by 2,092 while the global math detector remained unchanged | Engine changes must add a row only after both the 5,141-case standard suite and this full analysis have been rerun. Suspect-reference clusters stay in this report; they are not engine targets without independent PDF evidence. diff --git a/libs/braillify/examples/nikl_corpus_analyze.rs b/libs/braillify/examples/nikl_corpus_analyze.rs index 2fede669..47f5a6f1 100644 --- a/libs/braillify/examples/nikl_corpus_analyze.rs +++ b/libs/braillify/examples/nikl_corpus_analyze.rs @@ -1027,6 +1027,28 @@ fn markdown(report: &AnalysisReport) -> String { } text.push('\n'); + text.push_str("## Rules 34/54 Korean-prefixed Roman annotations\n\n"); + text.push_str( + "Rule 34 says that when Roman text is enclosed by quotation marks or brackets, the \ + Roman terminator is omitted; its PDF example is `링컨(Lincoln)은 미국의 제16대 \ + 대통령이다.` Rule 54 says that text immediately after an opening bracket and \ + immediately before a closing bracket is attached. Together these establish the \ + Korean-prefix + closed-Roman-annotation context independently of corpus expected \ + values. A following comma or period is outside the already closed annotation and \ + must not cause its Roman contents to be rerouted as mathematics.\n\n\ + The implementation gate exists only inside `split_mixed_math_word`, after the prefix \ + has been proved entirely Korean. It accepts a fully closed parenthesized Roman word \ + (including ASCII digits such as `O4O`) plus ordinary trailing prose punctuation. \ + The global math detector is byte-for-byte unchanged; regression tests preserve its \ + existing standalone results for `(x)`, `(A)`, and `(abc)`, while explicit forms such \ + as `(x+1)`, `(a/b)`, and `(x₁)` remain math candidates.\n\n\ + Against the immediately preceding 63,399-exact run, exact matches increased by 2,092. \ + The observable primary totals changed as follows: `comparison_method` 290→303, \ + `pending_rule_review` 19,636→17,543, and `unsupported_character_review` 203→191. \ + Raw encoding errors stayed at 450; errors resolved by a comparison method changed \ + 247→259 and unresolved review errors changed 203→191.\n\n", + ); + text.push_str("## Rule evidence and change log\n\n"); text.push_str( "| Stage | Standard cases | Corpus exact | Corpus accuracy | Evidence |\n\ @@ -1041,6 +1063,9 @@ fn markdown(report: &AnalysisReport) -> String { text.push_str( "| Rule 36 Unicode Roman-numeral presentation normalization | 5,141/5,141 | 63,399/83,528 | 75.90% | U+2160–U+217F use the corresponding Roman-letter spelling; 11 NFKC-equivalent observations became exact, 23 errors became encoded mismatches pending review, and 3 remain blocked by `㈜` |\n", ); + text.push_str( + "| Rules 34/54 Korean-prefixed closed Roman annotation routing | 5,141/5,141 | 65,491/83,528 | 78.41% | A fully closed Roman annotation after an all-Korean prefix stays on the prose encoder path, including attached comma/period and alphanumeric forms such as `O4O`; exact matches increased by 2,092 while the global math detector remained unchanged |\n", + ); text.push_str( "\nEngine changes must add a row only after both the 5,141-case standard suite and \ this full analysis have been rerun. Suspect-reference clusters stay in this report; \ diff --git a/libs/braillify/src/rules/token_rules/math_expression.rs b/libs/braillify/src/rules/token_rules/math_expression.rs index 9fe4a38f..c4afe015 100644 --- a/libs/braillify/src/rules/token_rules/math_expression.rs +++ b/libs/braillify/src/rules/token_rules/math_expression.rs @@ -72,6 +72,73 @@ mod tests { assert!(!is_math_expression(&chars, "hello")); } + #[rstest::rstest] + #[case::lower_list_item("(x)", false)] + #[case::upper_list_item("(A)", false)] + #[case::plain_parenthesized_word("(abc)", false)] + fn standalone_parenthesized_inputs_keep_baseline_detector_result( + #[case] input: &str, + #[case] expected: bool, + ) { + let chars: Vec = input.chars().collect(); + assert_eq!(is_math_expression(&chars, input), expected, "input={input}"); + } + + #[rstest::rstest] + #[case::addition("(x+1)")] + #[case::fraction("(a/b)")] + #[case::subscript("(x₁)")] + fn parenthesized_explicit_expressions_keep_existing_math_result(#[case] input: &str) { + let chars: Vec = input.chars().collect(); + assert!(is_math_expression(&chars, input)); + } + + #[test] + fn rule_34_bare_roman_parenthesis_has_exact_particle_suffix() { + let bare = crate::encode("링컨(Lincoln)").expect("bare rule 34 form must encode"); + let attached = + crate::encode("링컨(Lincoln)은").expect("particle-attached rule 34 form must encode"); + let particle = crate::encode("은").expect("particle must encode"); + + assert_eq!( + attached.strip_prefix(bare.as_slice()), + Some(particle.as_slice()) + ); + } + + #[rstest::rstest] + #[case::comma("링컨(Lincoln)", "링컨(Lincoln),", ",")] + #[case::period("링컨(Lincoln)", "링컨(Lincoln).", ".")] + fn rule_54_punctuation_follows_closed_roman_parenthesis_without_resplitting( + #[case] bare_input: &str, + #[case] with_punctuation: &str, + #[case] punctuation: &str, + ) { + let bare = crate::encode(bare_input).expect("bare rule 34 form must encode"); + let punctuated = + crate::encode(with_punctuation).expect("punctuated rule 54 form must encode"); + let punctuation = crate::encode(punctuation).expect("punctuation must encode"); + + assert_eq!( + punctuated.strip_prefix(bare.as_slice()), + Some(punctuation.as_slice()) + ); + } + + #[test] + fn rule_34_alphanumeric_o4o_uses_the_same_bare_and_particle_path() { + let bare = crate::encode("표기(O4O)").expect("alphanumeric Roman form must encode"); + let attached = + crate::encode("표기(O4O)는").expect("particle-attached Roman form must encode"); + let particle = crate::encode("는").expect("particle must encode"); + + assert_eq!( + attached.strip_prefix(bare.as_slice()), + Some(particle.as_slice()) + ); + assert!(!bare.windows(2).any(|cells| cells == [0, 0])); + } + #[test] fn test_is_math_with_superscript() { let chars: Vec = "x²".chars().collect(); @@ -208,6 +275,21 @@ mod tests { assert!(split_mixed_math_word(&word, 2, MathContext::default()).is_none()); } + #[rstest::rstest] + #[case::rule_34_particle("링컨(Lincoln)은")] + #[case::rule_54_comma("링컨(Lincoln),")] + #[case::alphanumeric_roman("표기(O4O).")] + fn split_mixed_math_word_keeps_korean_prefixed_closed_roman_annotation(#[case] input: &str) { + let chars: Vec = input.chars().collect(); + let word = crate::rules::token::WordToken { + text: Cow::Borrowed(input), + chars: chars.clone(), + meta: WordMeta::from_chars(&chars), + }; + + assert!(split_mixed_math_word(&word, 0, MathContext::default()).is_none()); + } + fn enc(input: &str) -> Vec { crate::encode(input).unwrap_or_default() } diff --git a/libs/braillify/src/rules/token_rules/math_expression/helpers.rs b/libs/braillify/src/rules/token_rules/math_expression/helpers.rs index 32d70b0f..e52a6246 100644 --- a/libs/braillify/src/rules/token_rules/math_expression/helpers.rs +++ b/libs/braillify/src/rules/token_rules/math_expression/helpers.rs @@ -345,6 +345,36 @@ fn build_korean_prefix_math_suffix(prefix: String, bytes: Vec) -> Vec bool { + if chars.first() != Some(&'(') { + return false; + } + + let Some(close) = chars.iter().position(|c| *c == ')') else { + return false; + }; + let body = &chars[1..close]; + let trailing = &chars[close + 1..]; + + !body.is_empty() + && body.iter().any(|c| c.is_ascii_alphabetic()) + && body + .iter() + .all(|c| c.is_ascii_alphanumeric() || matches!(*c, '-' | '\'' | '.')) + && trailing + .iter() + .all(|c| matches!(*c, ',' | '.' | ';' | ':' | '!' | '?' | '\'' | '"')) +} + pub(super) fn split_mixed_math_word( word: &crate::rules::token::WordToken<'_>, leading_delimiter_len: usize, @@ -391,6 +421,9 @@ pub(super) fn split_mixed_math_word( if !prefix_all_korean || !suffix_no_korean { return None; } + if is_closed_roman_annotation_suffix(suffix_chars) { + return None; + } let suffix_text: String = suffix_chars.iter().collect(); let suffix_is_math = is_mixed_math_expression(suffix_chars, &suffix_text) || is_math_expression(suffix_chars, &suffix_text); From 5d7e174fb64f22cbb535fae02d4eb5bcd568e7f8 Mon Sep 17 00:00:00 2001 From: devfive Date: Tue, 1 Sep 2026 05:56:48 +0900 Subject: [PATCH 09/77] Audit uppercase Roman expansion corpus cohort --- docs/corpus-analysis/NIKL_2025_V1.md | 46 ++++ .../braillify/examples/nikl_corpus_analyze.rs | 219 +++++++++++++++++- 2 files changed, 264 insertions(+), 1 deletion(-) diff --git a/docs/corpus-analysis/NIKL_2025_V1.md b/docs/corpus-analysis/NIKL_2025_V1.md index a5706b9b..139df16f 100644 --- a/docs/corpus-analysis/NIKL_2025_V1.md +++ b/docs/corpus-analysis/NIKL_2025_V1.md @@ -33,6 +33,52 @@ Primary classes are evidence gates, not permissions to change the engine. `imple | `punctuation_rule_review` | 13 | | `unsupported_character_review` | 191 | +## Cross-cutting input-only structural cohorts + +These are cross-cutting input-only structural cohorts, not new primary classes and not engine routing rules. Candidate selection never changes a case's existing primary class. Only cohort members already classified as `pending_rule_review` form a pending subcluster; exact and other-primary members are controls that retain their existing outcomes. The `uppercase_roman_headword_closed_multiword_parenthetical` gate requires a two-or-more character uppercase ASCII headword immediately followed by a closed parenthesis whose contents are two or more ASCII Roman words separated only by spaces. Because the contents admit only letters and spaces, visible operators, subscript/superscript notation, and nested parentheses are excluded deterministically. + +| Cluster | Candidates | Exact | Mismatch | +|---|---:|---:|---:| +| `uppercase_roman_headword_closed_multiword_parenthetical` | 175 | 15 | 160 | + +### `uppercase_roman_headword_closed_multiword_parenthetical` + +Of the 175 candidates, 153 are the actual `pending_rule_review` subcluster. The other 22 candidates are exact or existing non-pending-primary controls; this cohort does not reclassify them. + +Mismatch primary-class distribution: + +- `comparison_method`: 1 +- `pending_rule_review`: 153 +- `unsupported_character_review`: 6 + +Representative `exact` samples: + +- `sentence_01.json` #3647: 넥슨(대표 이정헌)은 게임 서브 브랜드 ‘민트로켓’의 차기 신작 프로젝트 2종 ‘TB(Top View Battle)’와 ‘NAKWON(낙원)’의 개발 영상을 19일 공개했다. + - current primary/reason: `exact` / `exact` +- `sentence_01.json` #5097: 롯데렌탈은 10일 지난해 환경·사회·지배구조(ESG) 주요 성과를 담은 지속가능경영보고서를 발간했다고 밝혔다. 작년에 이은 두 번째 지속가능경영보고서로 국제 표준 가이드라인 ‘GRI(Global Reporting Initiative) 스탠다드 2021’에 맞춰 작성했다. + - current primary/reason: `exact` / `exact` +- `sentence_01.json` #5513: 김하일 한국과학기술원(KAIST) 의과학연구센터소장은 의사과학자에 대해 이렇게 비유했다. 김 소장은 “MD(Medical Doctor)는 ‘의사’이고 학위가 아니라 국가자격증”이라며 “반면 Ph.D는 자격증이 아니라 학위과정”이라고 설명했다. + - current primary/reason: `exact` / `exact` +- `sentence_01.json` #17249: 김민재(27) 팀 동료이자 바이에른 뮌헨 전설인 토마스 뮐러(34)가 리오넬 메시(36, 인터 마이애미)를 역사상 최고 선수인 ‘GOAT(Greatest Of All Time)’라고 인정했다. + - current primary/reason: `exact` / `exact` +- `sentence_01.json` #20716: KAI(한국항공우주산업)가 오는 27일까지 5일간 말레이시아 랑카위에서 열리는 ‘LIMA(Langkawi International Maritime and Aerospace Exhibition) 2023’에 참가, K-방산 텃밭인 동남아시아 시장의 전략적 협력 강화에 나섰다. + - current primary/reason: `exact` / `exact` + +Representative `mismatch` samples: + +- `sentence_01.json` #18: 2023년형 신제품은 매터(Matter)와 HCA(Home Connectivity Alliance) 표준을 지원하는 스마트싱스 허브(SmartThings Hub)를 기반으로 다양한 기기를 자동으로 연결하고, 제어·관리할 수 있는 것이 특징이다. + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_01.json` #168: 제주항공은 국제항공운송협회(IATA)가 주관하는 국제 항공운송 표준 감사 제도 ‘IOSA(IATA Operation Safety Audit) ISM 14th Edition’ 인증을 마쳐 세계 기준의 안전 관리시스템을 입증받았다고 6일 밝혔다. + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_01.json` #191: 소프트웨어 측면에서는 스마트 TV 독자 운영체제 웹(web)OS의 진화를 앞세워 맞춤형 고객경험과 CDX(Cross Device eXperience) 경험을 강화한다. + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_01.json` #508: 두산에너빌리티는 미국 NAC(Nuclear Assurance Corporation)와 공동개발한 사용후핵연료 금속 저장용기가 미국 원자력규제위원회(NRC) 설계승인을 취득했다고 19일 밝혔다. + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_01.json` #761: 샌즈랩은 국내에서 정보보호 분야로 최초이자 유일하게 2021년, 2022년 연속으로 국가신기술(NET) 인증을 획득했다. 또한 신기술로 최근 미국 직접 특허 출원·PCT(Patent Cooperation Treaty)특허 출원을 완료했다. + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` + +This shape is not an engine implementation premise. The 2024 PDF's math rule 6 defines parentheses and grouping parentheses, rule 11 defines mathematical-expression spacing, rule 12 covers Roman letters in formulas as well as Korean sentences, and rule 45 shows Roman-letter function notation followed by parentheses. Excluding visible operators, scripts, and nesting narrows this corpus cohort, but the PDF does not make the remaining surface shape sufficient to rule out every mathematical counterexample. The cluster therefore remains conservative pending-review evidence only. + ## Encoding-error diagnostics The audit starts from all raw encoding errors, then separates cases already resolved by a comparison method or corpus contradiction. The message, family, and singleton tables below count only unresolved encoding-error review cases. These diagnostics are not additional primary classes. A singleton unsupported character is a character that also fails when encoded by itself. Such a failure remains a review candidate until the PDF independently establishes support. diff --git a/libs/braillify/examples/nikl_corpus_analyze.rs b/libs/braillify/examples/nikl_corpus_analyze.rs index 47f5a6f1..9520a7e8 100644 --- a/libs/braillify/examples/nikl_corpus_analyze.rs +++ b/libs/braillify/examples/nikl_corpus_analyze.rs @@ -135,6 +135,24 @@ struct EncodingErrorAudit { unclassified_samples: Vec, } +#[derive(Debug, Serialize)] +struct PendingRuleReviewClusterSample { + shard: String, + index: usize, + input: String, + primary_class: String, + reason: String, +} + +#[derive(Debug, Default, Serialize)] +struct PendingRuleReviewClusterStats { + candidates: usize, + exact: usize, + mismatch: usize, + mismatch_primary_classes: BTreeMap, + samples: BTreeMap>, +} + #[derive(Debug, Serialize)] struct AnalysisReport { corpus: &'static str, @@ -151,6 +169,9 @@ struct AnalysisReport { singleton_error_characters: BTreeMap, encoding_error_audit: EncodingErrorAudit, rule_36_transition_audit: Rule36TransitionAudit, + // Cross-cutting input cohorts; only members whose existing primary class + // is PendingRuleReview are pending-rule-review subclusters. + pending_rule_review_clusters: BTreeMap, overlapping_traits: BTreeMap, shards: BTreeMap, samples: BTreeMap>, @@ -519,6 +540,48 @@ fn is_delimiter_or_quote(ch: char) -> bool { ) } +const UPPERCASE_ROMAN_HEADWORD_EXPANSION: &str = + "uppercase_roman_headword_closed_multiword_parenthetical"; + +/// Input-only candidate gate for acronym expansions such as +/// `HCA(Home Connectivity Alliance)`. +/// +/// This is deliberately an analyzer diagnostic, not an engine rule. Requiring +/// only ASCII letters and spaces inside the closed parenthesis also excludes +/// visible operators, subscript/superscript notation, and nested parentheses. +fn has_uppercase_roman_headword_expansion(input: &str) -> bool { + let bytes = input.as_bytes(); + for (open, _) in input.match_indices('(') { + let mut headword_start = open; + while headword_start > 0 && bytes[headword_start - 1].is_ascii_alphabetic() { + headword_start -= 1; + } + let headword = &input[headword_start..open]; + if headword.len() < 2 || !headword.bytes().all(|byte| byte.is_ascii_uppercase()) { + continue; + } + + let parenthetical_tail = &input[open + 1..]; + let Some(close) = parenthetical_tail.find(')') else { + continue; + }; + let contents = &parenthetical_tail[..close]; + if contents.is_empty() + || contents.trim_matches(' ') != contents + || !contents + .bytes() + .all(|byte| byte.is_ascii_alphabetic() || byte == b' ') + { + continue; + } + + if contents.split_ascii_whitespace().count() >= 2 { + return true; + } + } + false +} + fn enum_key(value: &T) -> String { serde_json::to_value(value) .expect("enum serialization must succeed") @@ -609,6 +672,10 @@ fn analyze( let mut shards = BTreeMap::::new(); let mut samples = BTreeMap::>::new(); let mut rule_36_transition_audit = Rule36TransitionAudit::default(); + let mut pending_rule_review_clusters = BTreeMap::from([( + UPPERCASE_ROMAN_HEADWORD_EXPANSION.to_string(), + PendingRuleReviewClusterStats::default(), + )]); let mut exact = 0usize; for item in &encoded { @@ -641,9 +708,37 @@ fn analyze( } let primary_key = enum_key(&primary); let reason_key = enum_key(&reason); - *primary_classes.entry(primary_key).or_insert(0) += 1; + *primary_classes.entry(primary_key.clone()).or_insert(0) += 1; *reasons.entry(reason_key.clone()).or_insert(0) += 1; + if has_uppercase_roman_headword_expansion(&item.located.case.input) { + let stats = pending_rule_review_clusters + .get_mut(UPPERCASE_ROMAN_HEADWORD_EXPANSION) + .expect("registered pending-rule-review cluster must exist"); + stats.candidates += 1; + let outcome = if primary == PrimaryClass::Exact { + stats.exact += 1; + "exact" + } else { + stats.mismatch += 1; + *stats + .mismatch_primary_classes + .entry(primary_key.clone()) + .or_insert(0) += 1; + "mismatch" + }; + let bucket = stats.samples.entry(outcome.to_string()).or_default(); + if bucket.len() < sample_limit { + bucket.push(PendingRuleReviewClusterSample { + shard: item.located.shard.clone(), + index: item.located.index, + input: item.located.case.input.clone(), + primary_class: primary_key.clone(), + reason: reason_key.clone(), + }); + } + } + let shard = shards.entry(item.located.shard.clone()).or_default(); shard.total += 1; if primary == PrimaryClass::Exact { @@ -773,6 +868,7 @@ fn analyze( singleton_error_characters, encoding_error_audit, rule_36_transition_audit, + pending_rule_review_clusters, overlapping_traits: traits, shards, samples, @@ -827,6 +923,73 @@ fn markdown(report: &AnalysisReport) -> String { text.push_str(&format!("| `{name}` | {count} |\n")); } + text.push_str("\n## Cross-cutting input-only structural cohorts\n\n"); + text.push_str( + "These are cross-cutting input-only structural cohorts, not new primary classes and not \ + engine routing rules. Candidate selection never changes a case's existing primary \ + class. Only cohort members already classified as `pending_rule_review` form a pending \ + subcluster; exact and other-primary members are controls that retain their existing \ + outcomes. The \ + `uppercase_roman_headword_closed_multiword_parenthetical` gate requires a two-or-more \ + character uppercase ASCII headword immediately followed by a closed parenthesis whose \ + contents are two or more ASCII Roman words separated only by spaces. Because the \ + contents admit only letters and spaces, visible operators, subscript/superscript \ + notation, and nested parentheses are excluded deterministically.\n\n", + ); + text.push_str("| Cluster | Candidates | Exact | Mismatch |\n|---|---:|---:|---:|\n"); + for (name, stats) in &report.pending_rule_review_clusters { + text.push_str(&format!( + "| `{name}` | {} | {} | {} |\n", + stats.candidates, stats.exact, stats.mismatch + )); + } + for (name, stats) in &report.pending_rule_review_clusters { + text.push_str(&format!("\n### `{name}`\n\n")); + let pending = stats + .mismatch_primary_classes + .get("pending_rule_review") + .copied() + .unwrap_or(0); + text.push_str(&format!( + "Of the {} candidates, {pending} are the actual `pending_rule_review` subcluster. \ + The other {} candidates are exact or existing non-pending-primary controls; this \ + cohort does not reclassify them.\n\n", + stats.candidates, + stats.candidates - pending + )); + text.push_str("Mismatch primary-class distribution:\n\n"); + for (primary, count) in &stats.mismatch_primary_classes { + text.push_str(&format!("- `{primary}`: {count}\n")); + } + for (outcome, samples) in &stats.samples { + text.push_str(&format!("\nRepresentative `{outcome}` samples:\n\n")); + for sample in samples { + text.push_str(&format!( + "- `{}` #{}: {}\n - current primary/reason: `{}` / `{}`\n", + sample.shard, + sample.index, + sample + .input + .chars() + .take(180) + .collect::() + .replace('`', "\\`"), + sample.primary_class, + sample.reason + )); + } + } + } + text.push_str( + "\nThis shape is not an engine implementation premise. The 2024 PDF's math rule 6 \ + defines parentheses and grouping parentheses, rule 11 defines mathematical-expression \ + spacing, rule 12 covers Roman letters in formulas as well as Korean sentences, and \ + rule 45 shows Roman-letter function notation followed by parentheses. Excluding visible \ + operators, scripts, and nesting narrows this corpus cohort, but the PDF does not make \ + the remaining surface shape sufficient to rule out every mathematical counterexample. \ + The cluster therefore remains conservative pending-review evidence only.\n", + ); + text.push_str("\n## Encoding-error diagnostics\n\n"); text.push_str( "The audit starts from all raw encoding errors, then separates cases already resolved by \ @@ -1270,4 +1433,58 @@ mod tests { fn roman_indicator_moves_before_capital_word_indicator() { assert_eq!(roman_before_capital_order("⠠⠠⠴⠁⠃"), "⠴⠠⠠⠁⠃"); } + + #[rstest::rstest] + #[case::hca("HCA(Home Connectivity Alliance)", true)] + #[case::embedded_in_korean("협회 HCA(Home Connectivity Alliance)는", true)] + #[case::lowercase_headword("Hca(Home Connectivity Alliance)", false)] + #[case::single_word_parenthetical("HCA(Alliance)", false)] + #[case::unclosed_parenthetical("HCA(Home Connectivity Alliance", false)] + #[case::operator_inside("AB(C + D)", false)] + #[case::subscript_inside("AB(C D_1)", false)] + #[case::nested_parenthetical("AB(C (D E))", false)] + fn detects_uppercase_roman_headword_expansion(#[case] input: &str, #[case] expected: bool) { + assert_eq!(has_uppercase_roman_headword_expansion(input), expected); + } + + #[test] + fn aggregates_headword_expansion_outcomes_without_reclassification() { + let cases = [ + ("HCA(Home Connectivity Alliance)", "exact", "exact"), + ("WHO(World Health Organization)", "expected", "actual"), + ] + .into_iter() + .enumerate() + .map(|(offset, (input, expected, actual))| { + let located = LocatedCase { + shard: "synthetic.json".to_string(), + index: offset + 1, + case: CorpusCase { + input: input.to_string(), + unicode: expected.to_string(), + }, + }; + let encoded = EncodedCase { + located: located.clone(), + actual: Ok(actual.to_string()), + nfc_actual: None, + nfkc_actual: None, + singleton_unsupported_characters: Vec::new(), + }; + (located, encoded) + }) + .collect::>(); + let (located, encoded): (Vec<_>, Vec<_>) = cases.into_iter().unzip(); + + let report = analyze(located, encoded, 5); + let stats = report + .pending_rule_review_clusters + .get(UPPERCASE_ROMAN_HEADWORD_EXPANSION) + .unwrap(); + + assert_eq!((stats.candidates, stats.exact, stats.mismatch), (2, 1, 1)); + assert_eq!(stats.mismatch_primary_classes["pending_rule_review"], 1); + assert_eq!(report.primary_classes["exact"], 1); + assert_eq!(report.primary_classes["pending_rule_review"], 1); + } } From d6ad44a76034a0276d33fc6d225a67fd007f03a1 Mon Sep 17 00:00:00 2001 From: devfive Date: Tue, 1 Sep 2026 06:09:09 +0900 Subject: [PATCH 10/77] Audit standalone uppercase Roman corpus cohort --- docs/corpus-analysis/NIKL_2025_V1.md | 103 +++++++- .../braillify/examples/nikl_corpus_analyze.rs | 250 +++++++++++++++--- 2 files changed, 300 insertions(+), 53 deletions(-) diff --git a/docs/corpus-analysis/NIKL_2025_V1.md b/docs/corpus-analysis/NIKL_2025_V1.md index 139df16f..2bd864b6 100644 --- a/docs/corpus-analysis/NIKL_2025_V1.md +++ b/docs/corpus-analysis/NIKL_2025_V1.md @@ -35,11 +35,64 @@ Primary classes are evidence gates, not permissions to change the engine. `imple ## Cross-cutting input-only structural cohorts -These are cross-cutting input-only structural cohorts, not new primary classes and not engine routing rules. Candidate selection never changes a case's existing primary class. Only cohort members already classified as `pending_rule_review` form a pending subcluster; exact and other-primary members are controls that retain their existing outcomes. The `uppercase_roman_headword_closed_multiword_parenthetical` gate requires a two-or-more character uppercase ASCII headword immediately followed by a closed parenthesis whose contents are two or more ASCII Roman words separated only by spaces. Because the contents admit only letters and spaces, visible operators, subscript/superscript notation, and nested parentheses are excluded deterministically. +These are cross-cutting input-only structural cohorts, not new primary classes and not engine routing rules. Candidate selection never changes a case's existing primary class. Only cohort members already classified as `pending_rule_review` form a pending subcluster; exact and other-primary members are controls that retain their existing outcomes. The `uppercase_roman_headword_closed_multiword_parenthetical` gate requires a two-or-more character uppercase ASCII headword immediately followed by a closed parenthesis whose contents are two or more ASCII Roman words separated only by spaces. Because the contents admit only letters and spaces, visible operators, subscript/superscript notation, and nested parentheses are excluded deterministically. The `standalone_multi_character_uppercase_roman_word` gate finds maximal ASCII-letter runs of two or more capitals with non-alphanumeric boundaries; a run immediately followed by `(` is excluded so the HCA-style headword itself is not counted by both gates. -| Cluster | Candidates | Exact | Mismatch | -|---|---:|---:|---:| -| `uppercase_roman_headword_closed_multiword_parenthetical` | 175 | 15 | 160 | +| Cluster | Candidates | Exact | Mismatch | Conflicting-reference cases | +|---|---:|---:|---:|---:| +| `standalone_multi_character_uppercase_roman_word` | 62411 | 48749 | 13662 | 0 | +| `uppercase_roman_headword_closed_multiword_parenthetical` | 175 | 15 | 160 | 0 | + +### `standalone_multi_character_uppercase_roman_word` + +Of the 62411 candidates, 13276 are the actual `pending_rule_review` subcluster. The other 49135 candidates are exact or existing non-pending-primary controls; this cohort does not reclassify them. + +Mismatch primary-class distribution: + +- `comparison_method`: 223 +- `pending_rule_review`: 13276 +- `unsupported_character_review`: 163 + +Representative `exact` samples: + +- `sentence_01.json` #1: LG전자는 ‘업(UP)가전’을 무기로 내세우고 있다. 이번 CES에서도 LG 씽큐 앱에서 터치만으로 제품 색상을 바꿀 수 있는 무드업 냉장고를 비롯한 다양한 업가전을 선보일 예정이다. + - expected: `⠴⠠⠠⠇⠛⠲⠨⠾⠨⠉⠵⠀⠠⠦⠎⠃⠦⠄⠴⠠⠠⠥⠏⠠` + - actual: `⠴⠠⠠⠇⠛⠲⠨⠾⠨⠉⠵⠀⠠⠦⠎⠃⠦⠄⠴⠠⠠⠥⠏⠠` + - current primary/reason: `exact` / `exact` +- `sentence_02.json` #1: 세종시 주최, 세종시탄소중립지원센터 주관으로 열린 이번 포럼의 주요의제는 기업의 이에스지(ESG) 경영과 사회적 책임, 국내 온실가스 배출권거래제 현황과 대응 방안을 마련하기 위해 개최됐다. + - expected: `⠠⠝⠨⠿⠠⠕⠀⠨⠍⠰⠽⠐⠀⠠⠝⠨⠿⠠⠕⠓⠒⠠⠥⠨` + - actual: `⠠⠝⠨⠿⠠⠕⠀⠨⠍⠰⠽⠐⠀⠠⠝⠨⠿⠠⠕⠓⠒⠠⠥⠨` + - current primary/reason: `exact` / `exact` +- `sentence_03.json` #4: 18일(현지시간) 월스트리트저널(WSJ) 등 미 외신은 투·개표기 업체 도미니언 투표시스템이 2021년 1월 제기한 16억 달러(약 2조원) 명예훼손 소송과 관련, 폭스뉴스와 도미니언 측이 이 같이 합의했다고 보도했다. + - expected: `⠼⠁⠓⠕⠂⠦⠄⠚⠡⠨⠕⠠⠕⠫⠒⠠⠴⠀⠏⠂⠠⠪⠓⠪` + - actual: `⠼⠁⠓⠕⠂⠦⠄⠚⠡⠨⠕⠠⠕⠫⠒⠠⠴⠀⠏⠂⠠⠪⠓⠪` + - current primary/reason: `exact` / `exact` +- `sentence_04.json` #1: 일본 정부는 이날 국가안전보장회의(NSC)를 열어 대응 방침을 논의했다. 이어 북한의 미사일 발사에 대해 “엄중히 항의하고 강하게 비난한다”고 밝혔다. + - expected: `⠕⠂⠘⠷⠀⠨⠻⠘⠍⠉⠵⠀⠕⠉⠂⠀⠈⠍⠁⠫⠣⠒⠨⠾` + - actual: `⠕⠂⠘⠷⠀⠨⠻⠘⠍⠉⠵⠀⠕⠉⠂⠀⠈⠍⠁⠫⠣⠒⠨⠾` + - current primary/reason: `exact` / `exact` + +Representative `mismatch` samples: + +- `sentence_01.json` #14: 새롭게 선보인 DRX 브랜딩과 팀 컬러는 2023년 1월 1일부터 DRX의 온오프라인 콘텐츠, 엠디(MD), SNS, 유튜브 등 모든 제작물에 적용될 예정이다. + - expected: `⠰⠪⠐⠀⠝⠢⠊⠕⠴⠐⠣⠠⠠⠍⠙⠐⠜⠂⠀⠠⠠⠎⠝⠎` + - actual: `⠰⠪⠐⠀⠝⠢⠊⠕⠦⠄⠴⠠⠠⠍⠙⠠⠴⠐⠀⠰⠠⠠⠎⠝` + - first differing cell (zero-based): 97 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_02.json` #10: 설동호 대전교육감은 “학교 교육과정 및 여건에 맞는 인공지능(AI)교육 선도학교 운영을 통해 AI·SW교육 기반 구축과 학생들과 교원들의 미래 디지털 역량이 함양될 것이다.”라고 말했다. + - expected: `⠚⠗⠀⠴⠠⠠⠁⠊⠐⠆⠴⠠⠠⠎⠺⠲⠈⠬⠩⠁⠀⠈⠕⠘` + - actual: `⠚⠗⠀⠴⠠⠠⠁⠊⠲⠐⠆⠴⠎⠺⠲⠈⠬⠩⠁⠀⠈⠕⠘⠒` + - first differing cell (zero-based): 93 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_03.json` #1: 김 지사는 18일(이하 현지시각) 미국 코네티컷주 댄버리 린데 본사에서 산지브 람바(Sanjiv Lamba) 린데 회장, 성백석 린데코리아 회장, 조일교 아산시 부시장과 투자양해각서(MOU)을 체결했다. + - expected: `⠠⠎⠦⠄⠴⠠⠠⠍⠕⠥⠠⠴⠮⠀⠰⠝⠈⠳⠚⠗⠌⠊⠲` + - actual: `⠠⠎⠦⠄⠴⠠⠠⠍⠳⠠⠴⠮⠀⠰⠝⠈⠳⠚⠗⠌⠊⠲` + - first differing cell (zero-based): 175 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_04.json` #19: 이날 행사에는 구 대표 외에 권봉석 (주)LG 최고운영책임자(COO), 박일평 LG사이언스파크대표(사장)를 비롯해 각 계열사 최고기술책임자(CTO), 최고데이터책임자(CDO), 최고인사책임자(CHO) 등이 참석해 국내 이공계 R&D 인재 400여명과 만났다. + - expected: `⠁⠕⠢⠨⠦⠄⠴⠠⠠⠉⠕⠕⠠⠴⠐⠀⠘⠁⠕⠂⠙⠻⠀⠴` + - actual: `⠁⠕⠢⠨⠦⠄⠴⠠⠉⠠⠕⠠⠕⠠⠴⠐⠀⠘⠁⠕⠂⠙⠻⠀` + - first differing cell (zero-based): 60 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` ### `uppercase_roman_headword_closed_multiword_parenthetical` @@ -54,31 +107,53 @@ Mismatch primary-class distribution: Representative `exact` samples: - `sentence_01.json` #3647: 넥슨(대표 이정헌)은 게임 서브 브랜드 ‘민트로켓’의 차기 신작 프로젝트 2종 ‘TB(Top View Battle)’와 ‘NAKWON(낙원)’의 개발 영상을 19일 공개했다. + - expected: `⠉⠝⠁⠠⠵⠦⠄⠊⠗⠙⠬⠀⠕⠨⠻⠚⠾⠠⠴⠵⠀⠈⠝⠕` + - actual: `⠉⠝⠁⠠⠵⠦⠄⠊⠗⠙⠬⠀⠕⠨⠻⠚⠾⠠⠴⠵⠀⠈⠝⠕` - current primary/reason: `exact` / `exact` -- `sentence_01.json` #5097: 롯데렌탈은 10일 지난해 환경·사회·지배구조(ESG) 주요 성과를 담은 지속가능경영보고서를 발간했다고 밝혔다. 작년에 이은 두 번째 지속가능경영보고서로 국제 표준 가이드라인 ‘GRI(Global Reporting Initiative) 스탠다드 2021’에 맞춰 작성했다. +- `sentence_02.json` #1700: 태광그룹 섬유·석유화학 계열사인 태광산업은 국제품질표준 자동차 품질경영시스템 인증인 ‘IATF(International Automotive Task Force) 16949’를 영국왕립표준협회(BSI) 인증원으로부터 획득했다고 13일 밝혔다. + - expected: `⠓⠗⠈⠧⠶⠈⠪⠐⠍⠃⠀⠠⠎⠢⠩⠐⠆⠠⠹⠩⠚⠧⠚⠁` + - actual: `⠓⠗⠈⠧⠶⠈⠪⠐⠍⠃⠀⠠⠎⠢⠩⠐⠆⠠⠹⠩⠚⠧⠚⠁` - current primary/reason: `exact` / `exact` -- `sentence_01.json` #5513: 김하일 한국과학기술원(KAIST) 의과학연구센터소장은 의사과학자에 대해 이렇게 비유했다. 김 소장은 “MD(Medical Doctor)는 ‘의사’이고 학위가 아니라 국가자격증”이라며 “반면 Ph.D는 자격증이 아니라 학위과정”이라고 설명했다. +- `sentence_03.json` #20370: 포르테나는 지난 20일 1theK(원더케이) 공식 유튜브 채널의 ‘MR은 거들 뿐’ 콘텐츠를 통해 첫 번째 미니앨범 ‘KINGDOM’의 타이틀곡 ‘KINGDOM(Moonlight Tears)’라이브 영상을 공개했다. + - expected: `⠙⠥⠐⠪⠓⠝⠉⠉⠵⠀⠨⠕⠉⠒⠀⠼⠃⠚⠕⠂⠀⠼⠁⠴` + - actual: `⠙⠥⠐⠪⠓⠝⠉⠉⠵⠀⠨⠕⠉⠒⠀⠼⠃⠚⠕⠂⠀⠼⠁⠴` - current primary/reason: `exact` / `exact` -- `sentence_01.json` #17249: 김민재(27) 팀 동료이자 바이에른 뮌헨 전설인 토마스 뮐러(34)가 리오넬 메시(36, 인터 마이애미)를 역사상 최고 선수인 ‘GOAT(Greatest Of All Time)’라고 인정했다. - - current primary/reason: `exact` / `exact` -- `sentence_01.json` #20716: KAI(한국항공우주산업)가 오는 27일까지 5일간 말레이시아 랑카위에서 열리는 ‘LIMA(Langkawi International Maritime and Aerospace Exhibition) 2023’에 참가, K-방산 텃밭인 동남아시아 시장의 전략적 협력 강화에 나섰다. +- `sentence_04.json` #2928: 이밖에 미국에서 많은 구독자와 공신력 있는 외식 전문지인 ‘QSR(Quick Service Restaurant)’ 매거진과 ‘매쉬드(Mashed)’를 통해 K-치킨의 대표 브랜드로 소개된 바 있다. + - expected: `⠕⠘⠁⠁⠝⠀⠑⠕⠈⠍⠁⠝⠠⠎⠀⠑⠒⠴⠵⠀⠈⠍⠊⠭` + - actual: `⠕⠘⠁⠁⠝⠀⠑⠕⠈⠍⠁⠝⠠⠎⠀⠑⠒⠴⠵⠀⠈⠍⠊⠭` - current primary/reason: `exact` / `exact` Representative `mismatch` samples: - `sentence_01.json` #18: 2023년형 신제품은 매터(Matter)와 HCA(Home Connectivity Alliance) 표준을 지원하는 스마트싱스 허브(SmartThings Hub)를 기반으로 다양한 기기를 자동으로 연결하고, 제어·관리할 수 있는 것이 특징이다. + - expected: `⠁⠞⠞⠻⠠⠴⠧⠀⠴⠠⠠⠓⠉⠁⠐⠣⠠⠓⠕⠍⠑⠀⠠⠒` + - actual: `⠁⠞⠞⠻⠠⠴⠧⠀⠀⠠⠠⠓⠉⠁⠦⠠⠓⠕⠍⠑⠀⠴⠠⠒` + - first differing cell (zero-based): 37 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_01.json` #168: 제주항공은 국제항공운송협회(IATA)가 주관하는 국제 항공운송 표준 감사 제도 ‘IOSA(IATA Operation Safety Audit) ISM 14th Edition’ 인증을 마쳐 세계 기준의 안전 관리시스템을 입증받았다고 6일 밝혔다. - - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_01.json` #191: 소프트웨어 측면에서는 스마트 TV 독자 운영체제 웹(web)OS의 진화를 앞세워 맞춤형 고객경험과 CDX(Cross Device eXperience) 경험을 강화한다. +- `sentence_02.json` #313: 순천향대(총장 김승우)는 지난 3일 한국국제협력단(KOICA)과 함께 우즈베키스탄의 수도 타슈겐트에서 스타트업 지원센터 ‘U-ENTER(Uzbekistan Entrepreneurship Innovation Center)’ 준공식을 개최했다고 밝혔다. + - expected: `⠎⠀⠠⠦⠴⠠⠥⠤⠠⠠⠢⠞⠻⠐⠣⠠⠥⠵⠃⠑⠅⠊⠌⠁` + - actual: `⠎⠀⠠⠦⠴⠠⠥⠤⠰⠠⠠⠢⠞⠻⠐⠣⠠⠥⠵⠃⠑⠅⠊⠌` + - first differing cell (zero-based): 131 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_01.json` #508: 두산에너빌리티는 미국 NAC(Nuclear Assurance Corporation)와 공동개발한 사용후핵연료 금속 저장용기가 미국 원자력규제위원회(NRC) 설계승인을 취득했다고 19일 밝혔다. +- `sentence_03.json` #3330: 앞서 양사는 지난 3월 그린수소·암모니아의 원활한 생산·공급·활용을 위한 특수목적법인(SPC) 알 파탄 엘텍유브이씨 그린에너지 LLC(AL FATTAN LTechUVC Green Energy LLC)를 설립한 바 있다. + - expected: `⠐⠟⠝⠉⠎⠨⠕⠀⠴⠰⠠⠠⠇⠇⠉⠐⠣⠰⠠⠠⠁⠇⠀⠠` + - actual: `⠐⠟⠝⠉⠎⠨⠕⠀⠀⠠⠠⠇⠇⠉⠦⠠⠠⠁⠇⠀⠴⠠⠠⠠` + - first differing cell (zero-based): 128 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_01.json` #761: 샌즈랩은 국내에서 정보보호 분야로 최초이자 유일하게 2021년, 2022년 연속으로 국가신기술(NET) 인증을 획득했다. 또한 신기술로 최근 미국 직접 특허 출원·PCT(Patent Cooperation Treaty)특허 출원을 완료했다. +- `sentence_04.json` #2384: 마술사들의 등용문인 국제마술대회는 국내 최초로 세계마술연맹(FISM)의 인증을 받은 ‘FISM QC(Qualified Contest) BIMF’라는 이름으로 더욱 특별하게 진행된다. + - expected: `⠊⠎⠍⠀⠠⠠⠟⠉⠐⠣⠠⠟⠥⠁⠇⠊⠋⠊⠫⠀⠠⠒⠞⠑` + - actual: `⠊⠎⠍⠀⠠⠠⠟⠉⠦⠠⠟⠥⠁⠇⠊⠋⠊⠑⠙⠀⠠⠒⠞⠑` + - first differing cell (zero-based): 93 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` This shape is not an engine implementation premise. The 2024 PDF's math rule 6 defines parentheses and grouping parentheses, rule 11 defines mathematical-expression spacing, rule 12 covers Roman letters in formulas as well as Korean sentences, and rule 45 shows Roman-letter function notation followed by parentheses. Excluding visible operators, scripts, and nesting narrows this corpus cohort, but the PDF does not make the remaining surface shape sufficient to rule out every mathematical counterexample. The cluster therefore remains conservative pending-review evidence only. +The standalone-uppercase cohort is similarly ambiguous. Hangeul rule 28's appendix defines the capital-word indicator for two or more consecutive capitals, and rule 29 defines Roman indicators around Roman text in a Korean sentence. But math rule 12 also uses uppercase Roman variables, while science rule 7 uses uppercase runs in chemical formulas. The input gate cannot determine which semantic regime applies, so its output differences are observations to review, not permission to infer an engine rule from the corpus reference. + +Corpus contradictions remain a separate gate: identical inputs with conflicting references are classified as `corpus_suspect` before these cohorts are recorded and would appear explicitly in each mismatch primary-class distribution. Their absence does not prove a reference correct; it only means that this deterministic contradiction test did not fire. + +Current standalone-uppercase measurement: 62411 candidates, 48749 exact controls, 13662 mismatches, and 13276 members in the actual `pending_rule_review` subcluster. Its high frequency does not make it causal: the same input shape is exact in many cases, and a sentence containing the shape may first differ at another Roman, numeric, or punctuation structure. No engine change is inferred from this cohort. + ## Encoding-error diagnostics The audit starts from all raw encoding errors, then separates cases already resolved by a comparison method or corpus contradiction. The message, family, and singleton tables below count only unresolved encoding-error review cases. These diagnostics are not additional primary classes. A singleton unsupported character is a character that also fails when encoded by itself. Such a failure remains a review candidate until the PDF independently establishes support. diff --git a/libs/braillify/examples/nikl_corpus_analyze.rs b/libs/braillify/examples/nikl_corpus_analyze.rs index 9520a7e8..50a0f916 100644 --- a/libs/braillify/examples/nikl_corpus_analyze.rs +++ b/libs/braillify/examples/nikl_corpus_analyze.rs @@ -140,6 +140,10 @@ struct PendingRuleReviewClusterSample { shard: String, index: usize, input: String, + expected_excerpt: String, + actual_excerpt: String, + first_difference_cell: Option, + error: Option, primary_class: String, reason: String, } @@ -149,6 +153,7 @@ struct PendingRuleReviewClusterStats { candidates: usize, exact: usize, mismatch: usize, + conflicting_reference_cases: usize, mismatch_primary_classes: BTreeMap, samples: BTreeMap>, } @@ -542,6 +547,7 @@ fn is_delimiter_or_quote(ch: char) -> bool { const UPPERCASE_ROMAN_HEADWORD_EXPANSION: &str = "uppercase_roman_headword_closed_multiword_parenthetical"; +const STANDALONE_UPPERCASE_ROMAN_WORD: &str = "standalone_multi_character_uppercase_roman_word"; /// Input-only candidate gate for acronym expansions such as /// `HCA(Home Connectivity Alliance)`. @@ -582,6 +588,42 @@ fn has_uppercase_roman_headword_expansion(input: &str) -> bool { false } +/// Finds a maximal, alphanumeric-delimited ASCII letter run of two or more +/// capitals, excluding the headword of an immediately following parenthetical +/// expansion already covered by `UPPERCASE_ROMAN_HEADWORD_EXPANSION`. +fn has_standalone_uppercase_roman_word(input: &str) -> bool { + let bytes = input.as_bytes(); + let mut cursor = 0; + while cursor < bytes.len() { + if !bytes[cursor].is_ascii_alphabetic() { + cursor += input[cursor..] + .chars() + .next() + .expect("cursor must remain on a character boundary") + .len_utf8(); + continue; + } + + let start = cursor; + while cursor < bytes.len() && bytes[cursor].is_ascii_alphabetic() { + cursor += 1; + } + let run = &input[start..cursor]; + let previous = input[..start].chars().next_back(); + let next = input[cursor..].chars().next(); + let is_alphanumeric_delimited = previous.is_none_or(|ch| !ch.is_ascii_alphanumeric()) + && next.is_none_or(|ch| !ch.is_ascii_alphanumeric()); + if run.len() >= 2 + && run.bytes().all(|byte| byte.is_ascii_uppercase()) + && is_alphanumeric_delimited + && next != Some('(') + { + return true; + } + } + false +} + fn enum_key(value: &T) -> String { serde_json::to_value(value) .expect("enum serialization must succeed") @@ -593,17 +635,21 @@ fn enum_key(value: &T) -> String { fn excerpt_pair(expected: &str, actual: &str) -> (String, String) { let expected_chars = expected.chars().collect::>(); let actual_chars = actual.chars().collect::>(); - let first_diff = expected_chars - .iter() - .zip(&actual_chars) - .position(|(left, right)| left != right) - .unwrap_or(expected_chars.len().min(actual_chars.len())); + let first_diff = first_difference_cell(expected, actual); let start = first_diff.saturating_sub(8); let expected_excerpt = expected_chars.iter().skip(start).take(24).collect(); let actual_excerpt = actual_chars.iter().skip(start).take(24).collect(); (expected_excerpt, actual_excerpt) } +fn first_difference_cell(expected: &str, actual: &str) -> usize { + expected + .chars() + .zip(actual.chars()) + .position(|(left, right)| left != right) + .unwrap_or_else(|| expected.chars().count().min(actual.chars().count())) +} + fn is_compatibility_unit_decomposition(ch: char, nfkc: &str) -> bool { matches!( ch as u32, @@ -656,6 +702,66 @@ fn encoding_error_family(ch: char) -> &'static str { } } +fn record_structural_cohort_case( + stats: &mut PendingRuleReviewClusterStats, + item: &EncodedCase, + primary: PrimaryClass, + primary_key: &str, + reason_key: &str, + sample_limit: usize, +) { + stats.candidates += 1; + let outcome = if primary == PrimaryClass::Exact { + stats.exact += 1; + "exact" + } else { + stats.mismatch += 1; + if primary == PrimaryClass::CorpusSuspect { + stats.conflicting_reference_cases += 1; + } + *stats + .mismatch_primary_classes + .entry(primary_key.to_string()) + .or_insert(0) += 1; + "mismatch" + }; + let bucket = stats.samples.entry(outcome.to_string()).or_default(); + if bucket.len() >= sample_limit + || bucket + .iter() + .any(|sample| sample.shard == item.located.shard) + { + return; + } + + let expected = &item.located.case.unicode; + let (actual, error) = match &item.actual { + Ok(actual) => (actual.as_str(), None), + Err(error) => ("", Some(error.clone())), + }; + let (expected_excerpt, actual_excerpt) = if actual == expected { + ( + expected.chars().take(24).collect(), + actual.chars().take(24).collect(), + ) + } else { + excerpt_pair(expected, actual) + }; + let first_difference_cell = + (actual != expected).then(|| first_difference_cell(expected, actual)); + bucket.push(PendingRuleReviewClusterSample { + shard: item.located.shard.clone(), + index: item.located.index, + input: item.located.case.input.clone(), + expected_excerpt, + actual_excerpt, + first_difference_cell, + error, + primary_class: primary_key.to_string(), + reason: reason_key.to_string(), + }); +} + fn analyze( cases: Vec, encoded: Vec, @@ -672,10 +778,16 @@ fn analyze( let mut shards = BTreeMap::::new(); let mut samples = BTreeMap::>::new(); let mut rule_36_transition_audit = Rule36TransitionAudit::default(); - let mut pending_rule_review_clusters = BTreeMap::from([( - UPPERCASE_ROMAN_HEADWORD_EXPANSION.to_string(), - PendingRuleReviewClusterStats::default(), - )]); + let mut pending_rule_review_clusters = BTreeMap::from([ + ( + STANDALONE_UPPERCASE_ROMAN_WORD.to_string(), + PendingRuleReviewClusterStats::default(), + ), + ( + UPPERCASE_ROMAN_HEADWORD_EXPANSION.to_string(), + PendingRuleReviewClusterStats::default(), + ), + ]); let mut exact = 0usize; for item in &encoded { @@ -711,32 +823,30 @@ fn analyze( *primary_classes.entry(primary_key.clone()).or_insert(0) += 1; *reasons.entry(reason_key.clone()).or_insert(0) += 1; - if has_uppercase_roman_headword_expansion(&item.located.case.input) { + for (cluster, present) in [ + ( + STANDALONE_UPPERCASE_ROMAN_WORD, + has_standalone_uppercase_roman_word(&item.located.case.input), + ), + ( + UPPERCASE_ROMAN_HEADWORD_EXPANSION, + has_uppercase_roman_headword_expansion(&item.located.case.input), + ), + ] { + if !present { + continue; + } let stats = pending_rule_review_clusters - .get_mut(UPPERCASE_ROMAN_HEADWORD_EXPANSION) + .get_mut(cluster) .expect("registered pending-rule-review cluster must exist"); - stats.candidates += 1; - let outcome = if primary == PrimaryClass::Exact { - stats.exact += 1; - "exact" - } else { - stats.mismatch += 1; - *stats - .mismatch_primary_classes - .entry(primary_key.clone()) - .or_insert(0) += 1; - "mismatch" - }; - let bucket = stats.samples.entry(outcome.to_string()).or_default(); - if bucket.len() < sample_limit { - bucket.push(PendingRuleReviewClusterSample { - shard: item.located.shard.clone(), - index: item.located.index, - input: item.located.case.input.clone(), - primary_class: primary_key.clone(), - reason: reason_key.clone(), - }); - } + record_structural_cohort_case( + stats, + item, + primary, + &primary_key, + &reason_key, + sample_limit, + ); } let shard = shards.entry(item.located.shard.clone()).or_default(); @@ -934,13 +1044,19 @@ fn markdown(report: &AnalysisReport) -> String { character uppercase ASCII headword immediately followed by a closed parenthesis whose \ contents are two or more ASCII Roman words separated only by spaces. Because the \ contents admit only letters and spaces, visible operators, subscript/superscript \ - notation, and nested parentheses are excluded deterministically.\n\n", + notation, and nested parentheses are excluded deterministically. The \ + `standalone_multi_character_uppercase_roman_word` gate finds maximal ASCII-letter runs \ + of two or more capitals with non-alphanumeric boundaries; a run immediately followed \ + by `(` is excluded so the HCA-style headword itself is not counted by both gates.\n\n", + ); + text.push_str( + "| Cluster | Candidates | Exact | Mismatch | Conflicting-reference cases |\n\ + |---|---:|---:|---:|---:|\n", ); - text.push_str("| Cluster | Candidates | Exact | Mismatch |\n|---|---:|---:|---:|\n"); for (name, stats) in &report.pending_rule_review_clusters { text.push_str(&format!( - "| `{name}` | {} | {} | {} |\n", - stats.candidates, stats.exact, stats.mismatch + "| `{name}` | {} | {} | {} | {} |\n", + stats.candidates, stats.exact, stats.mismatch, stats.conflicting_reference_cases )); } for (name, stats) in &report.pending_rule_review_clusters { @@ -965,7 +1081,7 @@ fn markdown(report: &AnalysisReport) -> String { text.push_str(&format!("\nRepresentative `{outcome}` samples:\n\n")); for sample in samples { text.push_str(&format!( - "- `{}` #{}: {}\n - current primary/reason: `{}` / `{}`\n", + "- `{}` #{}: {}\n - expected: `{}`\n - actual: `{}`{}{}\n - current primary/reason: `{}` / `{}`\n", sample.shard, sample.index, sample @@ -974,6 +1090,15 @@ fn markdown(report: &AnalysisReport) -> String { .take(180) .collect::() .replace('`', "\\`"), + sample.expected_excerpt, + sample.actual_excerpt, + sample + .error + .as_ref() + .map_or_else(String::new, |error| format!("\n - error: `{error}`")), + sample.first_difference_cell.map_or_else(String::new, |cell| { + format!("\n - first differing cell (zero-based): {cell}") + }), sample.primary_class, sample.reason )); @@ -987,8 +1112,38 @@ fn markdown(report: &AnalysisReport) -> String { rule 45 shows Roman-letter function notation followed by parentheses. Excluding visible \ operators, scripts, and nesting narrows this corpus cohort, but the PDF does not make \ the remaining surface shape sufficient to rule out every mathematical counterexample. \ - The cluster therefore remains conservative pending-review evidence only.\n", + The cluster therefore remains conservative pending-review evidence only.\n\n\ + The standalone-uppercase cohort is similarly ambiguous. Hangeul rule 28's appendix \ + defines the capital-word indicator for two or more consecutive capitals, and rule 29 \ + defines Roman indicators around Roman text in a Korean sentence. But math rule 12 also \ + uses uppercase Roman variables, while science rule 7 uses uppercase runs in chemical \ + formulas. The input gate cannot determine which semantic regime applies, so its output \ + differences are observations to review, not permission to infer an engine rule from the \ + corpus reference.\n\n\ + Corpus contradictions remain a separate gate: identical inputs with conflicting \ + references are classified as `corpus_suspect` before these cohorts are recorded and \ + would appear explicitly in each mismatch primary-class distribution. Their absence does \ + not prove a reference correct; it only means that this deterministic contradiction test \ + did not fire.\n", ); + if let Some(stats) = report + .pending_rule_review_clusters + .get(STANDALONE_UPPERCASE_ROMAN_WORD) + { + let pending = stats + .mismatch_primary_classes + .get("pending_rule_review") + .copied() + .unwrap_or(0); + text.push_str(&format!( + "\nCurrent standalone-uppercase measurement: {} candidates, {} exact controls, {} \ + mismatches, and {pending} members in the actual `pending_rule_review` subcluster. \ + Its high frequency does not make it causal: the same input shape is exact in many \ + cases, and a sentence containing the shape may first differ at another Roman, \ + numeric, or punctuation structure. No engine change is inferred from this cohort.\n", + stats.candidates, stats.exact, stats.mismatch + )); + } text.push_str("\n## Encoding-error diagnostics\n\n"); text.push_str( @@ -1447,6 +1602,19 @@ mod tests { assert_eq!(has_uppercase_roman_headword_expansion(input), expected); } + #[rstest::rstest] + #[case::standalone("새로운 DRX 브랜드", true)] + #[case::inside_parentheses("엠디(MD), SNS", true)] + #[case::expansion_headword("HCA(Home Connectivity Alliance)", false)] + #[case::single_capital("점 A가 있다", false)] + #[case::mixed_case("SmartThings Hub", false)] + #[case::alphanumeric("O4O 시스템", false)] + #[case::chemical_formula("PETCO2이다", false)] + #[case::lowercase("web service", false)] + fn detects_standalone_uppercase_roman_word(#[case] input: &str, #[case] expected: bool) { + assert_eq!(has_standalone_uppercase_roman_word(input), expected); + } + #[test] fn aggregates_headword_expansion_outcomes_without_reclassification() { let cases = [ @@ -1483,7 +1651,11 @@ mod tests { .unwrap(); assert_eq!((stats.candidates, stats.exact, stats.mismatch), (2, 1, 1)); + assert_eq!(stats.conflicting_reference_cases, 0); assert_eq!(stats.mismatch_primary_classes["pending_rule_review"], 1); + assert_eq!(stats.samples["mismatch"][0].expected_excerpt, "expected"); + assert_eq!(stats.samples["mismatch"][0].actual_excerpt, "actual"); + assert_eq!(stats.samples["mismatch"][0].first_difference_cell, Some(0)); assert_eq!(report.primary_classes["exact"], 1); assert_eq!(report.primary_classes["pending_rule_review"], 1); } From 34561e3b7d3147414bc9f0d0eb4f58ed85fdac19 Mon Sep 17 00:00:00 2001 From: devfive Date: Tue, 1 Sep 2026 06:17:39 +0900 Subject: [PATCH 11/77] Audit Korean-prefixed all-caps parentheticals --- docs/corpus-analysis/NIKL_2025_V1.md | 59 +++++++++++++- .../braillify/examples/nikl_corpus_analyze.rs | 79 ++++++++++++++++++- 2 files changed, 136 insertions(+), 2 deletions(-) diff --git a/docs/corpus-analysis/NIKL_2025_V1.md b/docs/corpus-analysis/NIKL_2025_V1.md index 2bd864b6..2704272e 100644 --- a/docs/corpus-analysis/NIKL_2025_V1.md +++ b/docs/corpus-analysis/NIKL_2025_V1.md @@ -35,13 +35,66 @@ Primary classes are evidence gates, not permissions to change the engine. `imple ## Cross-cutting input-only structural cohorts -These are cross-cutting input-only structural cohorts, not new primary classes and not engine routing rules. Candidate selection never changes a case's existing primary class. Only cohort members already classified as `pending_rule_review` form a pending subcluster; exact and other-primary members are controls that retain their existing outcomes. The `uppercase_roman_headword_closed_multiword_parenthetical` gate requires a two-or-more character uppercase ASCII headword immediately followed by a closed parenthesis whose contents are two or more ASCII Roman words separated only by spaces. Because the contents admit only letters and spaces, visible operators, subscript/superscript notation, and nested parentheses are excluded deterministically. The `standalone_multi_character_uppercase_roman_word` gate finds maximal ASCII-letter runs of two or more capitals with non-alphanumeric boundaries; a run immediately followed by `(` is excluded so the HCA-style headword itself is not counted by both gates. +These are cross-cutting input-only structural cohorts, not new primary classes and not engine routing rules. Candidate selection never changes a case's existing primary class. Only cohort members already classified as `pending_rule_review` form a pending subcluster; exact and other-primary members are controls that retain their existing outcomes. The `uppercase_roman_headword_closed_multiword_parenthetical` gate requires a two-or-more character uppercase ASCII headword immediately followed by a closed parenthesis whose contents are two or more ASCII Roman words separated only by spaces. Because the contents admit only letters and spaces, visible operators, subscript/superscript notation, and nested parentheses are excluded deterministically. The `standalone_multi_character_uppercase_roman_word` gate finds maximal ASCII-letter runs of two or more capitals with non-alphanumeric boundaries; a run immediately followed by `(` is excluded so the HCA-style headword itself is not counted by both gates. The `korean_prefixed_closed_allcaps_parenthetical` gate requires an immediately preceding Korean character and a closed body of two or more uppercase ASCII letters. It intentionally contains both acronym annotations (`책임자(COO)`) and scientific formulae (`일산화탄소(CO)`) so their semantic collision remains measurable. | Cluster | Candidates | Exact | Mismatch | Conflicting-reference cases | |---|---:|---:|---:|---:| +| `korean_prefixed_closed_allcaps_parenthetical` | 54492 | 43775 | 10717 | 0 | | `standalone_multi_character_uppercase_roman_word` | 62411 | 48749 | 13662 | 0 | | `uppercase_roman_headword_closed_multiword_parenthetical` | 175 | 15 | 160 | 0 | +### `korean_prefixed_closed_allcaps_parenthetical` + +Of the 54492 candidates, 10392 are the actual `pending_rule_review` subcluster. The other 44100 candidates are exact or existing non-pending-primary controls; this cohort does not reclassify them. + +Mismatch primary-class distribution: + +- `comparison_method`: 185 +- `pending_rule_review`: 10392 +- `unsupported_character_review`: 140 + +Representative `exact` samples: + +- `sentence_01.json` #1: LG전자는 ‘업(UP)가전’을 무기로 내세우고 있다. 이번 CES에서도 LG 씽큐 앱에서 터치만으로 제품 색상을 바꿀 수 있는 무드업 냉장고를 비롯한 다양한 업가전을 선보일 예정이다. + - expected: `⠴⠠⠠⠇⠛⠲⠨⠾⠨⠉⠵⠀⠠⠦⠎⠃⠦⠄⠴⠠⠠⠥⠏⠠` + - actual: `⠴⠠⠠⠇⠛⠲⠨⠾⠨⠉⠵⠀⠠⠦⠎⠃⠦⠄⠴⠠⠠⠥⠏⠠` + - current primary/reason: `exact` / `exact` +- `sentence_02.json` #1: 세종시 주최, 세종시탄소중립지원센터 주관으로 열린 이번 포럼의 주요의제는 기업의 이에스지(ESG) 경영과 사회적 책임, 국내 온실가스 배출권거래제 현황과 대응 방안을 마련하기 위해 개최됐다. + - expected: `⠠⠝⠨⠿⠠⠕⠀⠨⠍⠰⠽⠐⠀⠠⠝⠨⠿⠠⠕⠓⠒⠠⠥⠨` + - actual: `⠠⠝⠨⠿⠠⠕⠀⠨⠍⠰⠽⠐⠀⠠⠝⠨⠿⠠⠕⠓⠒⠠⠥⠨` + - current primary/reason: `exact` / `exact` +- `sentence_03.json` #4: 18일(현지시간) 월스트리트저널(WSJ) 등 미 외신은 투·개표기 업체 도미니언 투표시스템이 2021년 1월 제기한 16억 달러(약 2조원) 명예훼손 소송과 관련, 폭스뉴스와 도미니언 측이 이 같이 합의했다고 보도했다. + - expected: `⠼⠁⠓⠕⠂⠦⠄⠚⠡⠨⠕⠠⠕⠫⠒⠠⠴⠀⠏⠂⠠⠪⠓⠪` + - actual: `⠼⠁⠓⠕⠂⠦⠄⠚⠡⠨⠕⠠⠕⠫⠒⠠⠴⠀⠏⠂⠠⠪⠓⠪` + - current primary/reason: `exact` / `exact` +- `sentence_04.json` #1: 일본 정부는 이날 국가안전보장회의(NSC)를 열어 대응 방침을 논의했다. 이어 북한의 미사일 발사에 대해 “엄중히 항의하고 강하게 비난한다”고 밝혔다. + - expected: `⠕⠂⠘⠷⠀⠨⠻⠘⠍⠉⠵⠀⠕⠉⠂⠀⠈⠍⠁⠫⠣⠒⠨⠾` + - actual: `⠕⠂⠘⠷⠀⠨⠻⠘⠍⠉⠵⠀⠕⠉⠂⠀⠈⠍⠁⠫⠣⠒⠨⠾` + - current primary/reason: `exact` / `exact` + +Representative `mismatch` samples: + +- `sentence_01.json` #14: 새롭게 선보인 DRX 브랜딩과 팀 컬러는 2023년 1월 1일부터 DRX의 온오프라인 콘텐츠, 엠디(MD), SNS, 유튜브 등 모든 제작물에 적용될 예정이다. + - expected: `⠰⠪⠐⠀⠝⠢⠊⠕⠴⠐⠣⠠⠠⠍⠙⠐⠜⠂⠀⠠⠠⠎⠝⠎` + - actual: `⠰⠪⠐⠀⠝⠢⠊⠕⠦⠄⠴⠠⠠⠍⠙⠠⠴⠐⠀⠰⠠⠠⠎⠝` + - first differing cell (zero-based): 97 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_02.json` #10: 설동호 대전교육감은 “학교 교육과정 및 여건에 맞는 인공지능(AI)교육 선도학교 운영을 통해 AI·SW교육 기반 구축과 학생들과 교원들의 미래 디지털 역량이 함양될 것이다.”라고 말했다. + - expected: `⠚⠗⠀⠴⠠⠠⠁⠊⠐⠆⠴⠠⠠⠎⠺⠲⠈⠬⠩⠁⠀⠈⠕⠘` + - actual: `⠚⠗⠀⠴⠠⠠⠁⠊⠲⠐⠆⠴⠎⠺⠲⠈⠬⠩⠁⠀⠈⠕⠘⠒` + - first differing cell (zero-based): 93 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_03.json` #1: 김 지사는 18일(이하 현지시각) 미국 코네티컷주 댄버리 린데 본사에서 산지브 람바(Sanjiv Lamba) 린데 회장, 성백석 린데코리아 회장, 조일교 아산시 부시장과 투자양해각서(MOU)을 체결했다. + - expected: `⠠⠎⠦⠄⠴⠠⠠⠍⠕⠥⠠⠴⠮⠀⠰⠝⠈⠳⠚⠗⠌⠊⠲` + - actual: `⠠⠎⠦⠄⠴⠠⠠⠍⠳⠠⠴⠮⠀⠰⠝⠈⠳⠚⠗⠌⠊⠲` + - first differing cell (zero-based): 175 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_04.json` #19: 이날 행사에는 구 대표 외에 권봉석 (주)LG 최고운영책임자(COO), 박일평 LG사이언스파크대표(사장)를 비롯해 각 계열사 최고기술책임자(CTO), 최고데이터책임자(CDO), 최고인사책임자(CHO) 등이 참석해 국내 이공계 R&D 인재 400여명과 만났다. + - expected: `⠁⠕⠢⠨⠦⠄⠴⠠⠠⠉⠕⠕⠠⠴⠐⠀⠘⠁⠕⠂⠙⠻⠀⠴` + - actual: `⠁⠕⠢⠨⠦⠄⠴⠠⠉⠠⠕⠠⠕⠠⠴⠐⠀⠘⠁⠕⠂⠙⠻⠀` + - first differing cell (zero-based): 60 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` + ### `standalone_multi_character_uppercase_roman_word` Of the 62411 candidates, 13276 are the actual `pending_rule_review` subcluster. The other 49135 candidates are exact or existing non-pending-primary controls; this cohort does not reclassify them. @@ -150,10 +203,14 @@ This shape is not an engine implementation premise. The 2024 PDF's math rule 6 d The standalone-uppercase cohort is similarly ambiguous. Hangeul rule 28's appendix defines the capital-word indicator for two or more consecutive capitals, and rule 29 defines Roman indicators around Roman text in a Korean sentence. But math rule 12 also uses uppercase Roman variables, while science rule 7 uses uppercase runs in chemical formulas. The input gate cannot determine which semantic regime applies, so its output differences are observations to review, not permission to infer an engine rule from the corpus reference. +The Korean-prefixed all-caps parenthetical cohort isolates that ambiguity more narrowly. Hangeul rules 28/29 require Roman and capitalization indicators for prose acronyms, while science rule 7 requires element-by-element capitals for chemical formulae. Both meanings can have the same input surface form. The observed `COO`/`NSC`/`MOU` output differences therefore do not justify disabling either algorithm without independent semantic evidence. + Corpus contradictions remain a separate gate: identical inputs with conflicting references are classified as `corpus_suspect` before these cohorts are recorded and would appear explicitly in each mismatch primary-class distribution. Their absence does not prove a reference correct; it only means that this deterministic contradiction test did not fire. Current standalone-uppercase measurement: 62411 candidates, 48749 exact controls, 13662 mismatches, and 13276 members in the actual `pending_rule_review` subcluster. Its high frequency does not make it causal: the same input shape is exact in many cases, and a sentence containing the shape may first differ at another Roman, numeric, or punctuation structure. No engine change is inferred from this cohort. +Current Korean-prefixed all-caps parenthetical measurement: 54492 candidates, 43775 exact controls, 10717 mismatches, and 10392 members in the actual `pending_rule_review` subcluster. This is a semantic-collision audit, not an engine routing rule; no implementation change is inferred from its reference outputs. + ## Encoding-error diagnostics The audit starts from all raw encoding errors, then separates cases already resolved by a comparison method or corpus contradiction. The message, family, and singleton tables below count only unresolved encoding-error review cases. These diagnostics are not additional primary classes. A singleton unsupported character is a character that also fails when encoded by itself. Such a failure remains a review candidate until the PDF independently establishes support. diff --git a/libs/braillify/examples/nikl_corpus_analyze.rs b/libs/braillify/examples/nikl_corpus_analyze.rs index 50a0f916..f570850e 100644 --- a/libs/braillify/examples/nikl_corpus_analyze.rs +++ b/libs/braillify/examples/nikl_corpus_analyze.rs @@ -548,6 +548,7 @@ fn is_delimiter_or_quote(ch: char) -> bool { const UPPERCASE_ROMAN_HEADWORD_EXPANSION: &str = "uppercase_roman_headword_closed_multiword_parenthetical"; const STANDALONE_UPPERCASE_ROMAN_WORD: &str = "standalone_multi_character_uppercase_roman_word"; +const KOREAN_PREFIXED_ALLCAPS_PARENTHETICAL: &str = "korean_prefixed_closed_allcaps_parenthetical"; /// Input-only candidate gate for acronym expansions such as /// `HCA(Home Connectivity Alliance)`. @@ -624,6 +625,34 @@ fn has_standalone_uppercase_roman_word(input: &str) -> bool { false } +fn is_korean_script(ch: char) -> bool { + matches!(ch as u32, 0x3131..=0x318e | 0xac00..=0xd7a3) +} + +/// Cross-cutting shape shared by prose acronyms and scientific formulae: +/// an immediate Korean prefix followed by a closed, two-or-more-letter +/// all-caps ASCII parenthetical such as `책임자(COO)` or `일산화탄소(CO)`. +fn has_korean_prefixed_allcaps_parenthetical(input: &str) -> bool { + for (open, _) in input.match_indices('(') { + if !input[..open] + .chars() + .next_back() + .is_some_and(is_korean_script) + { + continue; + } + let tail = &input[open + 1..]; + let Some(close) = tail.find(')') else { + continue; + }; + let body = &tail[..close]; + if body.len() >= 2 && body.bytes().all(|byte| byte.is_ascii_uppercase()) { + return true; + } + } + false +} + fn enum_key(value: &T) -> String { serde_json::to_value(value) .expect("enum serialization must succeed") @@ -779,6 +808,10 @@ fn analyze( let mut samples = BTreeMap::>::new(); let mut rule_36_transition_audit = Rule36TransitionAudit::default(); let mut pending_rule_review_clusters = BTreeMap::from([ + ( + KOREAN_PREFIXED_ALLCAPS_PARENTHETICAL.to_string(), + PendingRuleReviewClusterStats::default(), + ), ( STANDALONE_UPPERCASE_ROMAN_WORD.to_string(), PendingRuleReviewClusterStats::default(), @@ -824,6 +857,10 @@ fn analyze( *reasons.entry(reason_key.clone()).or_insert(0) += 1; for (cluster, present) in [ + ( + KOREAN_PREFIXED_ALLCAPS_PARENTHETICAL, + has_korean_prefixed_allcaps_parenthetical(&item.located.case.input), + ), ( STANDALONE_UPPERCASE_ROMAN_WORD, has_standalone_uppercase_roman_word(&item.located.case.input), @@ -1047,7 +1084,11 @@ fn markdown(report: &AnalysisReport) -> String { notation, and nested parentheses are excluded deterministically. The \ `standalone_multi_character_uppercase_roman_word` gate finds maximal ASCII-letter runs \ of two or more capitals with non-alphanumeric boundaries; a run immediately followed \ - by `(` is excluded so the HCA-style headword itself is not counted by both gates.\n\n", + by `(` is excluded so the HCA-style headword itself is not counted by both gates. The \ + `korean_prefixed_closed_allcaps_parenthetical` gate requires an immediately preceding \ + Korean character and a closed body of two or more uppercase ASCII letters. It \ + intentionally contains both acronym annotations (`책임자(COO)`) and scientific \ + formulae (`일산화탄소(CO)`) so their semantic collision remains measurable.\n\n", ); text.push_str( "| Cluster | Candidates | Exact | Mismatch | Conflicting-reference cases |\n\ @@ -1120,6 +1161,12 @@ fn markdown(report: &AnalysisReport) -> String { formulas. The input gate cannot determine which semantic regime applies, so its output \ differences are observations to review, not permission to infer an engine rule from the \ corpus reference.\n\n\ + The Korean-prefixed all-caps parenthetical cohort isolates that ambiguity more narrowly. \ + Hangeul rules 28/29 require Roman and capitalization indicators for prose acronyms, \ + while science rule 7 requires element-by-element capitals for chemical formulae. Both \ + meanings can have the same input surface form. The observed `COO`/`NSC`/`MOU` output \ + differences therefore do not justify disabling either algorithm without independent \ + semantic evidence.\n\n\ Corpus contradictions remain a separate gate: identical inputs with conflicting \ references are classified as `corpus_suspect` before these cohorts are recorded and \ would appear explicitly in each mismatch primary-class distribution. Their absence does \ @@ -1144,6 +1191,23 @@ fn markdown(report: &AnalysisReport) -> String { stats.candidates, stats.exact, stats.mismatch )); } + if let Some(stats) = report + .pending_rule_review_clusters + .get(KOREAN_PREFIXED_ALLCAPS_PARENTHETICAL) + { + let pending = stats + .mismatch_primary_classes + .get("pending_rule_review") + .copied() + .unwrap_or(0); + text.push_str(&format!( + "\nCurrent Korean-prefixed all-caps parenthetical measurement: {} candidates, {} \ + exact controls, {} mismatches, and {pending} members in the actual \ + `pending_rule_review` subcluster. This is a semantic-collision audit, not an engine \ + routing rule; no implementation change is inferred from its reference outputs.\n", + stats.candidates, stats.exact, stats.mismatch + )); + } text.push_str("\n## Encoding-error diagnostics\n\n"); text.push_str( @@ -1615,6 +1679,19 @@ mod tests { assert_eq!(has_standalone_uppercase_roman_word(input), expected); } + #[rstest::rstest] + #[case::acronym("최고운영책임자(COO)", true)] + #[case::organization("국가안전보장회의(NSC)를", true)] + #[case::chemical_formula("일산화탄소(CO)는", true)] + #[case::space_before_parenthesis("책임자 (COO)", false)] + #[case::roman_prefix("HCA(COO)", false)] + #[case::mixed_case("책임자(Ceo)", false)] + #[case::digit_inside("규격(CO2)", false)] + #[case::unclosed("책임자(COO", false)] + fn detects_korean_prefixed_allcaps_parenthetical(#[case] input: &str, #[case] expected: bool) { + assert_eq!(has_korean_prefixed_allcaps_parenthetical(input), expected); + } + #[test] fn aggregates_headword_expansion_outcomes_without_reclassification() { let cases = [ From d260b5700ed039e300caa8024a08d6b3d4ee6ab2 Mon Sep 17 00:00:00 2001 From: devfive Date: Tue, 1 Sep 2026 06:27:13 +0900 Subject: [PATCH 12/77] Audit all-caps Roman middle-dot corpus cohort --- docs/corpus-analysis/NIKL_2025_V1.md | 38 +++++++- .../braillify/examples/nikl_corpus_analyze.rs | 94 ++++++++++++++++++- 2 files changed, 130 insertions(+), 2 deletions(-) diff --git a/docs/corpus-analysis/NIKL_2025_V1.md b/docs/corpus-analysis/NIKL_2025_V1.md index 2704272e..9ab09a33 100644 --- a/docs/corpus-analysis/NIKL_2025_V1.md +++ b/docs/corpus-analysis/NIKL_2025_V1.md @@ -35,11 +35,12 @@ Primary classes are evidence gates, not permissions to change the engine. `imple ## Cross-cutting input-only structural cohorts -These are cross-cutting input-only structural cohorts, not new primary classes and not engine routing rules. Candidate selection never changes a case's existing primary class. Only cohort members already classified as `pending_rule_review` form a pending subcluster; exact and other-primary members are controls that retain their existing outcomes. The `uppercase_roman_headword_closed_multiword_parenthetical` gate requires a two-or-more character uppercase ASCII headword immediately followed by a closed parenthesis whose contents are two or more ASCII Roman words separated only by spaces. Because the contents admit only letters and spaces, visible operators, subscript/superscript notation, and nested parentheses are excluded deterministically. The `standalone_multi_character_uppercase_roman_word` gate finds maximal ASCII-letter runs of two or more capitals with non-alphanumeric boundaries; a run immediately followed by `(` is excluded so the HCA-style headword itself is not counted by both gates. The `korean_prefixed_closed_allcaps_parenthetical` gate requires an immediately preceding Korean character and a closed body of two or more uppercase ASCII letters. It intentionally contains both acronym annotations (`책임자(COO)`) and scientific formulae (`일산화탄소(CO)`) so their semantic collision remains measurable. +These are cross-cutting input-only structural cohorts, not new primary classes and not engine routing rules. Candidate selection never changes a case's existing primary class. Only cohort members already classified as `pending_rule_review` form a pending subcluster; exact and other-primary members are controls that retain their existing outcomes. The `uppercase_roman_headword_closed_multiword_parenthetical` gate requires a two-or-more character uppercase ASCII headword immediately followed by a closed parenthesis whose contents are two or more ASCII Roman words separated only by spaces. Because the contents admit only letters and spaces, visible operators, subscript/superscript notation, and nested parentheses are excluded deterministically. The `standalone_multi_character_uppercase_roman_word` gate finds maximal ASCII-letter runs of two or more capitals with non-alphanumeric boundaries; a run immediately followed by `(` is excluded so the HCA-style headword itself is not counted by both gates. The `korean_prefixed_closed_allcaps_parenthetical` gate requires an immediately preceding Korean character and a closed body of two or more uppercase ASCII letters. It intentionally contains both acronym annotations (`책임자(COO)`) and scientific formulae (`일산화탄소(CO)`) so their semantic collision remains measurable. The `multi_character_allcaps_roman_runs_joined_by_middle_dot` gate requires two maximal ASCII-letter runs of at least two capitals joined directly by U+00B7, with non-alphanumeric outer boundaries. It records shapes such as `AI·SW` without assigning prose, mathematics, or science semantics. | Cluster | Candidates | Exact | Mismatch | Conflicting-reference cases | |---|---:|---:|---:|---:| | `korean_prefixed_closed_allcaps_parenthetical` | 54492 | 43775 | 10717 | 0 | +| `multi_character_allcaps_roman_runs_joined_by_middle_dot` | 97 | 0 | 97 | 0 | | `standalone_multi_character_uppercase_roman_word` | 62411 | 48749 | 13662 | 0 | | `uppercase_roman_headword_closed_multiword_parenthetical` | 175 | 15 | 160 | 0 | @@ -95,6 +96,37 @@ Representative `mismatch` samples: - first differing cell (zero-based): 60 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +### `multi_character_allcaps_roman_runs_joined_by_middle_dot` + +Of the 97 candidates, 97 are the actual `pending_rule_review` subcluster. The other 0 candidates are exact or existing non-pending-primary controls; this cohort does not reclassify them. + +Mismatch primary-class distribution: + +- `pending_rule_review`: 97 + +Representative `mismatch` samples: + +- `sentence_01.json` #398: 방송통신위원회는 이동통신 3사(SK텔레콤·KT·LG유플러스), 한국정보통신진흥협회(KAIT)와 협력한다. 오는 16일부터 각 통신사 명의로 가입자에게 ‘스미싱 문자 주의 안내’ 문자 메시지를 순차 발송할 예정이다. + - expected: `⠇⠦⠄⠴⠠⠠⠎⠅⠲⠓⠝⠂⠐⠝⠋⠥⠢⠐⠆⠴⠠⠠⠅⠞` + - actual: `⠇⠦⠄⠴⠠⠠⠎⠅⠸⠷⠓⠝⠂⠐⠝⠋⠥⠢⠸⠾⠲⠸⠂⠴` + - first differing cell (zero-based): 35 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_02.json` #10: 설동호 대전교육감은 “학교 교육과정 및 여건에 맞는 인공지능(AI)교육 선도학교 운영을 통해 AI·SW교육 기반 구축과 학생들과 교원들의 미래 디지털 역량이 함양될 것이다.”라고 말했다. + - expected: `⠚⠗⠀⠴⠠⠠⠁⠊⠐⠆⠴⠠⠠⠎⠺⠲⠈⠬⠩⠁⠀⠈⠕⠘` + - actual: `⠚⠗⠀⠴⠠⠠⠁⠊⠲⠐⠆⠴⠎⠺⠲⠈⠬⠩⠁⠀⠈⠕⠘⠒` + - first differing cell (zero-based): 93 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_03.json` #1112: 12일(현지시간) 미국 ABC·NBC방송 등에 따르면 유튜버이자 스카이다이버인 트레버 제이컵(29)는 지난 2021년 12월 자신의 유튜브 채널에 12분 47초짜리 비행 영상을 올렸다. + - expected: `⠁⠀⠴⠠⠠⠁⠃⠉⠐⠆⠴⠠⠠⠝⠃⠉⠲⠘⠶⠠⠿⠀⠊⠪` + - actual: `⠁⠀⠴⠠⠠⠁⠃⠉⠲⠐⠆⠴⠝⠃⠉⠲⠘⠶⠠⠿⠀⠊⠪⠶` + - first differing cell (zero-based): 30 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_04.json` #2998: 독일 빌트암존탁이 여론조사기관에 의뢰해 지난 17~21일 유권자 1266명을 대상으로 실시한 여론조사에서 지지율 22%를 기록해 1위인 CDU·CSU(기독사회당) 지지율(26%)과 불과 4%포인트 차이를 나타냈다. + - expected: `⠟⠀⠴⠠⠠⠉⠙⠥⠐⠆⠴⠠⠠⠉⠎⠥⠦⠄⠈⠕⠊⠭⠇⠚` + - actual: `⠟⠀⠴⠠⠠⠉⠙⠥⠲⠐⠆⠴⠉⠎⠥⠦⠄⠈⠕⠊⠭⠇⠚⠽` + - first differing cell (zero-based): 130 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` + ### `standalone_multi_character_uppercase_roman_word` Of the 62411 candidates, 13276 are the actual `pending_rule_review` subcluster. The other 49135 candidates are exact or existing non-pending-primary controls; this cohort does not reclassify them. @@ -205,12 +237,16 @@ The standalone-uppercase cohort is similarly ambiguous. Hangeul rule 28's append The Korean-prefixed all-caps parenthetical cohort isolates that ambiguity more narrowly. Hangeul rules 28/29 require Roman and capitalization indicators for prose acronyms, while science rule 7 requires element-by-element capitals for chemical formulae. Both meanings can have the same input surface form. The observed `COO`/`NSC`/`MOU` output differences therefore do not justify disabling either algorithm without independent semantic evidence. +The all-caps Roman middle-dot cohort is also semantically underdetermined. Hangeul rule 29 defines Roman indicators around Roman text in a Korean sentence, and Hangeul rule 50 requires U+00B7 to be attached on both sides, but neither rule says that the punctuation joins the adjacent Roman runs into one Roman span. Math rule 2 separately defines the same printed dot as multiplication, and science rule 4 uses it inside chemical formulae. An input-only `AI·SW` gate therefore cannot prove which mode transition is required. Exact cases remain controls, mismatches retain their existing primary class, and no engine rule is inferred from their references. Representative samples are sentence-level evidence: when the reported first difference precedes the detected middle-dot span, the cohort must not be treated as the cause of that mismatch. + Corpus contradictions remain a separate gate: identical inputs with conflicting references are classified as `corpus_suspect` before these cohorts are recorded and would appear explicitly in each mismatch primary-class distribution. Their absence does not prove a reference correct; it only means that this deterministic contradiction test did not fire. Current standalone-uppercase measurement: 62411 candidates, 48749 exact controls, 13662 mismatches, and 13276 members in the actual `pending_rule_review` subcluster. Its high frequency does not make it causal: the same input shape is exact in many cases, and a sentence containing the shape may first differ at another Roman, numeric, or punctuation structure. No engine change is inferred from this cohort. Current Korean-prefixed all-caps parenthetical measurement: 54492 candidates, 43775 exact controls, 10717 mismatches, and 10392 members in the actual `pending_rule_review` subcluster. This is a semantic-collision audit, not an engine routing rule; no implementation change is inferred from its reference outputs. +Current all-caps Roman middle-dot measurement: 97 candidates, 0 exact controls, 97 mismatches, and 97 members in the actual `pending_rule_review` subcluster. This cross-cutting cohort preserves every primary class and is not an engine routing rule. + ## Encoding-error diagnostics The audit starts from all raw encoding errors, then separates cases already resolved by a comparison method or corpus contradiction. The message, family, and singleton tables below count only unresolved encoding-error review cases. These diagnostics are not additional primary classes. A singleton unsupported character is a character that also fails when encoded by itself. Such a failure remains a review candidate until the PDF independently establishes support. diff --git a/libs/braillify/examples/nikl_corpus_analyze.rs b/libs/braillify/examples/nikl_corpus_analyze.rs index f570850e..fa014e34 100644 --- a/libs/braillify/examples/nikl_corpus_analyze.rs +++ b/libs/braillify/examples/nikl_corpus_analyze.rs @@ -549,6 +549,8 @@ const UPPERCASE_ROMAN_HEADWORD_EXPANSION: &str = "uppercase_roman_headword_closed_multiword_parenthetical"; const STANDALONE_UPPERCASE_ROMAN_WORD: &str = "standalone_multi_character_uppercase_roman_word"; const KOREAN_PREFIXED_ALLCAPS_PARENTHETICAL: &str = "korean_prefixed_closed_allcaps_parenthetical"; +const ALLCAPS_ROMAN_MIDDLE_DOT_RUNS: &str = + "multi_character_allcaps_roman_runs_joined_by_middle_dot"; /// Input-only candidate gate for acronym expansions such as /// `HCA(Home Connectivity Alliance)`. @@ -653,6 +655,44 @@ fn has_korean_prefixed_allcaps_parenthetical(input: &str) -> bool { false } +/// Cross-cutting input-only shape such as `AI·SW`: two maximal ASCII-letter +/// runs of at least two capitals joined directly by U+00B7 MIDDLE DOT. +/// +/// This deliberately does not assign prose, mathematics, or science +/// semantics. The 2024 rules use the same character as Korean punctuation +/// and as a multiplication mark, so the shape remains an analyzer cohort. +fn has_allcaps_roman_middle_dot_runs(input: &str) -> bool { + let bytes = input.as_bytes(); + for (middle_dot, _) in input.match_indices('·') { + let mut left_start = middle_dot; + while left_start > 0 && bytes[left_start - 1].is_ascii_alphabetic() { + left_start -= 1; + } + + let right_start = middle_dot + '·'.len_utf8(); + let mut right_end = right_start; + while right_end < bytes.len() && bytes[right_end].is_ascii_alphabetic() { + right_end += 1; + } + + let left = &input[left_start..middle_dot]; + let right = &input[right_start..right_end]; + let previous = input[..left_start].chars().next_back(); + let next = input[right_end..].chars().next(); + let has_alphanumeric_boundaries = previous.is_none_or(|ch| !ch.is_ascii_alphanumeric()) + && next.is_none_or(|ch| !ch.is_ascii_alphanumeric()); + if left.len() >= 2 + && right.len() >= 2 + && left.bytes().all(|byte| byte.is_ascii_uppercase()) + && right.bytes().all(|byte| byte.is_ascii_uppercase()) + && has_alphanumeric_boundaries + { + return true; + } + } + false +} + fn enum_key(value: &T) -> String { serde_json::to_value(value) .expect("enum serialization must succeed") @@ -808,6 +848,10 @@ fn analyze( let mut samples = BTreeMap::>::new(); let mut rule_36_transition_audit = Rule36TransitionAudit::default(); let mut pending_rule_review_clusters = BTreeMap::from([ + ( + ALLCAPS_ROMAN_MIDDLE_DOT_RUNS.to_string(), + PendingRuleReviewClusterStats::default(), + ), ( KOREAN_PREFIXED_ALLCAPS_PARENTHETICAL.to_string(), PendingRuleReviewClusterStats::default(), @@ -857,6 +901,10 @@ fn analyze( *reasons.entry(reason_key.clone()).or_insert(0) += 1; for (cluster, present) in [ + ( + ALLCAPS_ROMAN_MIDDLE_DOT_RUNS, + has_allcaps_roman_middle_dot_runs(&item.located.case.input), + ), ( KOREAN_PREFIXED_ALLCAPS_PARENTHETICAL, has_korean_prefixed_allcaps_parenthetical(&item.located.case.input), @@ -1088,7 +1136,11 @@ fn markdown(report: &AnalysisReport) -> String { `korean_prefixed_closed_allcaps_parenthetical` gate requires an immediately preceding \ Korean character and a closed body of two or more uppercase ASCII letters. It \ intentionally contains both acronym annotations (`책임자(COO)`) and scientific \ - formulae (`일산화탄소(CO)`) so their semantic collision remains measurable.\n\n", + formulae (`일산화탄소(CO)`) so their semantic collision remains measurable. The \ + `multi_character_allcaps_roman_runs_joined_by_middle_dot` gate requires two maximal \ + ASCII-letter runs of at least two capitals joined directly by U+00B7, with \ + non-alphanumeric outer boundaries. It records shapes such as `AI·SW` without assigning \ + prose, mathematics, or science semantics.\n\n", ); text.push_str( "| Cluster | Candidates | Exact | Mismatch | Conflicting-reference cases |\n\ @@ -1167,6 +1219,16 @@ fn markdown(report: &AnalysisReport) -> String { meanings can have the same input surface form. The observed `COO`/`NSC`/`MOU` output \ differences therefore do not justify disabling either algorithm without independent \ semantic evidence.\n\n\ + The all-caps Roman middle-dot cohort is also semantically underdetermined. Hangeul rule \ + 29 defines Roman indicators around Roman text in a Korean sentence, and Hangeul rule 50 \ + requires U+00B7 to be attached on both sides, but neither rule says that the punctuation \ + joins the adjacent Roman runs into one Roman span. Math rule 2 separately defines the \ + same printed dot as multiplication, and science rule 4 uses it inside chemical \ + formulae. An input-only `AI·SW` gate therefore cannot prove which mode transition is \ + required. Exact cases remain controls, mismatches retain their existing primary class, \ + and no engine rule is inferred from their references. Representative samples are \ + sentence-level evidence: when the reported first difference precedes the detected \ + middle-dot span, the cohort must not be treated as the cause of that mismatch.\n\n\ Corpus contradictions remain a separate gate: identical inputs with conflicting \ references are classified as `corpus_suspect` before these cohorts are recorded and \ would appear explicitly in each mismatch primary-class distribution. Their absence does \ @@ -1208,6 +1270,23 @@ fn markdown(report: &AnalysisReport) -> String { stats.candidates, stats.exact, stats.mismatch )); } + if let Some(stats) = report + .pending_rule_review_clusters + .get(ALLCAPS_ROMAN_MIDDLE_DOT_RUNS) + { + let pending = stats + .mismatch_primary_classes + .get("pending_rule_review") + .copied() + .unwrap_or(0); + text.push_str(&format!( + "\nCurrent all-caps Roman middle-dot measurement: {} candidates, {} exact controls, \ + {} mismatches, and {pending} members in the actual `pending_rule_review` subcluster. \ + This cross-cutting cohort preserves every primary class and is not an engine routing \ + rule.\n", + stats.candidates, stats.exact, stats.mismatch + )); + } text.push_str("\n## Encoding-error diagnostics\n\n"); text.push_str( @@ -1692,6 +1771,19 @@ mod tests { assert_eq!(has_korean_prefixed_allcaps_parenthetical(input), expected); } + #[rstest::rstest] + #[case::embedded_in_korean("AI·SW교육", true)] + #[case::standalone("DRX·SNS", true)] + #[case::lowercase("a·b", false)] + #[case::single_letters("A·B", false)] + #[case::korean("가·나", false)] + #[case::numeric("3·1 운동", false)] + #[case::space_separated("AI · SW", false)] + #[case::alphanumeric_boundary("1AI·SW2", false)] + fn detects_allcaps_roman_middle_dot_runs(#[case] input: &str, #[case] expected: bool) { + assert_eq!(has_allcaps_roman_middle_dot_runs(input), expected); + } + #[test] fn aggregates_headword_expansion_outcomes_without_reclassification() { let cases = [ From 30a9b10eab73e346c824e643265da6061dec3f77 Mon Sep 17 00:00:00 2001 From: devfive Date: Tue, 1 Sep 2026 06:36:10 +0900 Subject: [PATCH 13/77] Localize inline parenthesized operator mismatches --- docs/corpus-analysis/NIKL_2025_V1.md | 40 +++- .../braillify/examples/nikl_corpus_analyze.rs | 194 +++++++++++++++++- 2 files changed, 231 insertions(+), 3 deletions(-) diff --git a/docs/corpus-analysis/NIKL_2025_V1.md b/docs/corpus-analysis/NIKL_2025_V1.md index 9ab09a33..6e54287c 100644 --- a/docs/corpus-analysis/NIKL_2025_V1.md +++ b/docs/corpus-analysis/NIKL_2025_V1.md @@ -35,15 +35,49 @@ Primary classes are evidence gates, not permissions to change the engine. `imple ## Cross-cutting input-only structural cohorts -These are cross-cutting input-only structural cohorts, not new primary classes and not engine routing rules. Candidate selection never changes a case's existing primary class. Only cohort members already classified as `pending_rule_review` form a pending subcluster; exact and other-primary members are controls that retain their existing outcomes. The `uppercase_roman_headword_closed_multiword_parenthetical` gate requires a two-or-more character uppercase ASCII headword immediately followed by a closed parenthesis whose contents are two or more ASCII Roman words separated only by spaces. Because the contents admit only letters and spaces, visible operators, subscript/superscript notation, and nested parentheses are excluded deterministically. The `standalone_multi_character_uppercase_roman_word` gate finds maximal ASCII-letter runs of two or more capitals with non-alphanumeric boundaries; a run immediately followed by `(` is excluded so the HCA-style headword itself is not counted by both gates. The `korean_prefixed_closed_allcaps_parenthetical` gate requires an immediately preceding Korean character and a closed body of two or more uppercase ASCII letters. It intentionally contains both acronym annotations (`책임자(COO)`) and scientific formulae (`일산화탄소(CO)`) so their semantic collision remains measurable. The `multi_character_allcaps_roman_runs_joined_by_middle_dot` gate requires two maximal ASCII-letter runs of at least two capitals joined directly by U+00B7, with non-alphanumeric outer boundaries. It records shapes such as `AI·SW` without assigning prose, mathematics, or science semantics. +These are cross-cutting input-only structural cohorts, not new primary classes and not engine routing rules. Candidate selection never changes a case's existing primary class. Only cohort members already classified as `pending_rule_review` form a pending subcluster; exact and other-primary members are controls that retain their existing outcomes. The `uppercase_roman_headword_closed_multiword_parenthetical` gate requires a two-or-more character uppercase ASCII headword immediately followed by a closed parenthesis whose contents are two or more ASCII Roman words separated only by spaces. Because the contents admit only letters and spaces, visible operators, subscript/superscript notation, and nested parentheses are excluded deterministically. The `standalone_multi_character_uppercase_roman_word` gate finds maximal ASCII-letter runs of two or more capitals with non-alphanumeric boundaries; a run immediately followed by `(` is excluded so the HCA-style headword itself is not counted by both gates. The `korean_prefixed_closed_allcaps_parenthetical` gate requires an immediately preceding Korean character and a closed body of two or more uppercase ASCII letters. It intentionally contains both acronym annotations (`책임자(COO)`) and scientific formulae (`일산화탄소(CO)`) so their semantic collision remains measurable. The `multi_character_allcaps_roman_runs_joined_by_middle_dot` gate requires two maximal ASCII-letter runs of at least two capitals joined directly by U+00B7, with non-alphanumeric outer boundaries. It records shapes such as `AI·SW` without assigning prose, mathematics, or science semantics. The `korean_inline_parenthesized_single_arithmetic_operator` gate requires an immediate `Korean(` + one rule-45 arithmetic operator + `)Korean` span. Unlike broad coexistence traits, it also locates the current engine's emitted structure and counts a mismatch as signature-local only when the sentence's first differing cell falls inside that output range. | Cluster | Candidates | Exact | Mismatch | Conflicting-reference cases | |---|---:|---:|---:|---:| +| `korean_inline_parenthesized_single_arithmetic_operator` | 23 | 0 | 23 | 0 | | `korean_prefixed_closed_allcaps_parenthetical` | 54492 | 43775 | 10717 | 0 | | `multi_character_allcaps_roman_runs_joined_by_middle_dot` | 97 | 0 | 97 | 0 | | `standalone_multi_character_uppercase_roman_word` | 62411 | 48749 | 13662 | 0 | | `uppercase_roman_headword_closed_multiword_parenthetical` | 175 | 15 | 160 | 0 | +### `korean_inline_parenthesized_single_arithmetic_operator` + +Of the 23 candidates, 23 are the actual `pending_rule_review` subcluster. The other 0 candidates are exact or existing non-pending-primary controls; this cohort does not reclassify them. + +For this output-signature audit, 23 mismatches were evaluable and 19 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. + +Mismatch primary-class distribution: + +- `pending_rule_review`: 23 + +Representative `mismatch` samples: + +- `sentence_01.json` #2921: 경제자유도가 높아지면 1인당 GDP도 개선되는 것으로 나타났다. OECD 회원국의 2021년 경제자유도와 1인당 GDP간 관계를 분석한 결과, 경제자유도와 1인당 GDP 간에는 정(+)의 상관관계(상관계수 +0.46)를 보였다. + - expected: `⠀⠼⠁⠟⠊⠶⠀⠴⠰⠠⠠⠛⠙⠏⠲⠊⠥⠀⠈⠗⠠⠾⠊⠽` + - actual: `⠀⠼⠁⠟⠊⠶⠀⠴⠠⠠⠛⠙⠏⠲⠊⠥⠀⠈⠗⠠⠾⠊⠽⠉` + - first differing cell (zero-based): 27 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_02.json` #417: 시는 세종고용복지플러스(+)센터, 세종여성새로일하기센터, 세종과학기술인협회 등과 공동으로 기업지원사업안내는 물론, 청년들이 면접 부담을 덜 수 있도록 청년면접비지원사업도 동시 진행하고 있다. + - expected: `⠙⠮⠐⠎⠠⠪⠦⠄⠢⠠⠴⠠⠝⠒⠓⠎⠐⠀⠠⠝⠨⠿⠱⠠` + - actual: `⠙⠮⠐⠎⠠⠪⠦⠄⠀⠢⠀⠠⠴⠠⠝⠒⠓⠎⠐⠀⠠⠝⠨⠿` + - first differing cell (zero-based): 25 + - current primary/reason: `pending_rule_review` / `punctuation_rule_review` +- `sentence_03.json` #12396: “2027년까지 수출과 매출에서 우리 중소기업이 차지하는 비율이 50% 이상이 되는 K-중소기업 50플러스(+)를 달성하는 데 있어서 메인비즈 기업이 선봉장으로 나서주기를 바랍니다.” + - expected: `⠙⠮⠐⠎⠠⠪⠦⠄⠢⠠⠴⠐⠮⠀⠊⠂⠠⠻⠚⠉⠵⠀⠊⠝` + - actual: `⠙⠮⠐⠎⠠⠪⠦⠄⠀⠢⠀⠠⠴⠐⠮⠀⠊⠂⠠⠻⠚⠉⠵⠀` + - first differing cell (zero-based): 101 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_04.json` #798: 여기에 주요 산유국 협의체인 OPEC 플러스(+)가 미국의 직간접적인 압박에도 추가 감산을 결정하면서 중동에 대한 미국의 영향력이 약화하고 있다는 분석도 힘을 얻고 있다. + - expected: `⠙⠮⠐⠎⠠⠪⠦⠄⠢⠠⠴⠫⠀⠑⠕⠈⠍⠁⠺⠀⠨⠕⠁⠫` + - actual: `⠙⠮⠐⠎⠠⠪⠦⠄⠀⠢⠀⠠⠴⠫⠀⠑⠕⠈⠍⠁⠺⠀⠨⠕` + - first differing cell (zero-based): 41 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` + ### `korean_prefixed_closed_allcaps_parenthetical` Of the 54492 candidates, 10392 are the actual `pending_rule_review` subcluster. The other 44100 candidates are exact or existing non-pending-primary controls; this cohort does not reclassify them. @@ -239,6 +273,8 @@ The Korean-prefixed all-caps parenthetical cohort isolates that ambiguity more n The all-caps Roman middle-dot cohort is also semantically underdetermined. Hangeul rule 29 defines Roman indicators around Roman text in a Korean sentence, and Hangeul rule 50 requires U+00B7 to be attached on both sides, but neither rule says that the punctuation joins the adjacent Roman runs into one Roman span. Math rule 2 separately defines the same printed dot as multiplication, and science rule 4 uses it inside chemical formulae. An input-only `AI·SW` gate therefore cannot prove which mode transition is required. Exact cases remain controls, mismatches retain their existing primary class, and no engine rule is inferred from their references. Representative samples are sentence-level evidence: when the reported first difference precedes the detected middle-dot span, the cohort must not be treated as the cause of that mismatch. +The inline parenthesized-operator cohort has an independently checkable spacing boundary. Hangeul rule 46 inserts spaces only when an operation or comparison sign is between Korean text, while the literal parentheses intervene in this gate. Hangeul rule 49 says punctuation spacing follows the print, and science rule 21 prints and brailles `(-)` and `(+)` with no spaces inside the parentheses. The output-signature count is therefore the implementation-candidate subset; mere sentence-level coexistence is retained only as a control. + Corpus contradictions remain a separate gate: identical inputs with conflicting references are classified as `corpus_suspect` before these cohorts are recorded and would appear explicitly in each mismatch primary-class distribution. Their absence does not prove a reference correct; it only means that this deterministic contradiction test did not fire. Current standalone-uppercase measurement: 62411 candidates, 48749 exact controls, 13662 mismatches, and 13276 members in the actual `pending_rule_review` subcluster. Its high frequency does not make it causal: the same input shape is exact in many cases, and a sentence containing the shape may first differ at another Roman, numeric, or punctuation structure. No engine change is inferred from this cohort. @@ -247,6 +283,8 @@ Current Korean-prefixed all-caps parenthetical measurement: 54492 candidates, 43 Current all-caps Roman middle-dot measurement: 97 candidates, 0 exact controls, 97 mismatches, and 97 members in the actual `pending_rule_review` subcluster. This cross-cutting cohort preserves every primary class and is not an engine routing rule. +Current inline parenthesized-operator measurement: 23 candidates, 0 exact controls, 23 mismatches, 23 members in the actual `pending_rule_review` subcluster, and 19/23 evaluable mismatches whose first differing cell is inside the emitted structure. Primary classes are preserved. + ## Encoding-error diagnostics The audit starts from all raw encoding errors, then separates cases already resolved by a comparison method or corpus contradiction. The message, family, and singleton tables below count only unresolved encoding-error review cases. These diagnostics are not additional primary classes. A singleton unsupported character is a character that also fails when encoded by itself. Such a failure remains a review candidate until the PDF independently establishes support. diff --git a/libs/braillify/examples/nikl_corpus_analyze.rs b/libs/braillify/examples/nikl_corpus_analyze.rs index fa014e34..4633f922 100644 --- a/libs/braillify/examples/nikl_corpus_analyze.rs +++ b/libs/braillify/examples/nikl_corpus_analyze.rs @@ -154,6 +154,8 @@ struct PendingRuleReviewClusterStats { exact: usize, mismatch: usize, conflicting_reference_cases: usize, + output_signature_mismatches_evaluated: usize, + first_difference_in_output_signature: usize, mismatch_primary_classes: BTreeMap, samples: BTreeMap>, } @@ -551,6 +553,8 @@ const STANDALONE_UPPERCASE_ROMAN_WORD: &str = "standalone_multi_character_upperc const KOREAN_PREFIXED_ALLCAPS_PARENTHETICAL: &str = "korean_prefixed_closed_allcaps_parenthetical"; const ALLCAPS_ROMAN_MIDDLE_DOT_RUNS: &str = "multi_character_allcaps_roman_runs_joined_by_middle_dot"; +const KOREAN_INLINE_PARENTHESIZED_OPERATOR: &str = + "korean_inline_parenthesized_single_arithmetic_operator"; /// Input-only candidate gate for acronym expansions such as /// `HCA(Home Connectivity Alliance)`. @@ -693,6 +697,98 @@ fn has_allcaps_roman_middle_dot_runs(input: &str) -> bool { false } +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +struct InlineParenthesizedOperator { + open_byte: usize, + operator: char, +} + +/// Finds `한글()한글` spans without assigning a meaning from the +/// corpus reference. The operator set is exactly the arithmetic set named by +/// Hangeul rules 45/46; parentheses remain visible input boundaries. +fn inline_parenthesized_operators(input: &str) -> Vec { + let mut matches = Vec::new(); + for (open_byte, _) in input.match_indices('(') { + if !input[..open_byte] + .chars() + .next_back() + .is_some_and(is_korean_script) + { + continue; + } + + let tail = &input[open_byte + 1..]; + let Some(operator) = tail.chars().next() else { + continue; + }; + if !matches!( + operator, + '+' | '-' | '\u{2212}' | '\u{00d7}' | '\u{00f7}' | '=' + ) { + continue; + } + let after_operator = &tail[operator.len_utf8()..]; + let Some(after_close) = after_operator.strip_prefix(')') else { + continue; + }; + if after_close.chars().next().is_some_and(is_korean_script) { + matches.push(InlineParenthesizedOperator { + open_byte, + operator, + }); + } + } + matches +} + +/// Returns the current engine's actual cell ranges for the detected input +/// structures. This is deliberately derived from encoding a neutral Korean +/// probe rather than from the corpus reference. A range is retained only when +/// the full sentence has the same current-engine signature at the cell offset +/// produced by the prefix ending immediately before `(`. +fn inline_parenthesized_operator_actual_ranges( + input: &str, + actual: &str, +) -> Vec> { + let actual_cells = actual.chars().collect::>(); + let left_probe_cells = braillify::encode_to_unicode("가") + .expect("neutral Korean probe must encode") + .chars() + .count(); + let right_probe_cells = braillify::encode_to_unicode("나") + .expect("neutral Korean probe must encode") + .chars() + .count(); + + inline_parenthesized_operators(input) + .into_iter() + .filter_map(|candidate| { + let prefix = braillify::encode_to_unicode(&input[..candidate.open_byte]).ok()?; + let start = prefix.chars().count(); + let probe = + braillify::encode_to_unicode(&format!("가({})나", candidate.operator)).ok()?; + let probe_cells = probe.chars().collect::>(); + let end = probe_cells.len().checked_sub(right_probe_cells)?; + let signature = probe_cells.get(left_probe_cells..end)?; + let actual_end = start.checked_add(signature.len())?; + (actual_cells.get(start..actual_end) == Some(signature)).then_some(start..actual_end) + }) + .collect() +} + +fn first_difference_in_inline_parenthesized_operator(item: &EncodedCase) -> bool { + let Ok(actual) = &item.actual else { + return false; + }; + if actual == &item.located.case.unicode { + return false; + } + let first_difference = first_difference_cell(&item.located.case.unicode, actual); + inline_parenthesized_operator_actual_ranges(&item.located.case.input, actual) + .into_iter() + .any(|range| range.contains(&first_difference)) +} + fn enum_key(value: &T) -> String { serde_json::to_value(value) .expect("enum serialization must succeed") @@ -778,6 +874,7 @@ fn record_structural_cohort_case( primary_key: &str, reason_key: &str, sample_limit: usize, + first_difference_in_output_signature: Option, ) { stats.candidates += 1; let outcome = if primary == PrimaryClass::Exact { @@ -785,6 +882,10 @@ fn record_structural_cohort_case( "exact" } else { stats.mismatch += 1; + if let Some(is_in_signature) = first_difference_in_output_signature { + stats.output_signature_mismatches_evaluated += 1; + stats.first_difference_in_output_signature += usize::from(is_in_signature); + } if primary == PrimaryClass::CorpusSuspect { stats.conflicting_reference_cases += 1; } @@ -856,6 +957,10 @@ fn analyze( KOREAN_PREFIXED_ALLCAPS_PARENTHETICAL.to_string(), PendingRuleReviewClusterStats::default(), ), + ( + KOREAN_INLINE_PARENTHESIZED_OPERATOR.to_string(), + PendingRuleReviewClusterStats::default(), + ), ( STANDALONE_UPPERCASE_ROMAN_WORD.to_string(), PendingRuleReviewClusterStats::default(), @@ -900,22 +1005,31 @@ fn analyze( *primary_classes.entry(primary_key.clone()).or_insert(0) += 1; *reasons.entry(reason_key.clone()).or_insert(0) += 1; - for (cluster, present) in [ + for (cluster, present, localized_first_difference) in [ ( ALLCAPS_ROMAN_MIDDLE_DOT_RUNS, has_allcaps_roman_middle_dot_runs(&item.located.case.input), + None, ), ( KOREAN_PREFIXED_ALLCAPS_PARENTHETICAL, has_korean_prefixed_allcaps_parenthetical(&item.located.case.input), + None, + ), + ( + KOREAN_INLINE_PARENTHESIZED_OPERATOR, + !inline_parenthesized_operators(&item.located.case.input).is_empty(), + Some(first_difference_in_inline_parenthesized_operator(item)), ), ( STANDALONE_UPPERCASE_ROMAN_WORD, has_standalone_uppercase_roman_word(&item.located.case.input), + None, ), ( UPPERCASE_ROMAN_HEADWORD_EXPANSION, has_uppercase_roman_headword_expansion(&item.located.case.input), + None, ), ] { if !present { @@ -931,6 +1045,7 @@ fn analyze( &primary_key, &reason_key, sample_limit, + localized_first_difference, ); } @@ -1140,7 +1255,12 @@ fn markdown(report: &AnalysisReport) -> String { `multi_character_allcaps_roman_runs_joined_by_middle_dot` gate requires two maximal \ ASCII-letter runs of at least two capitals joined directly by U+00B7, with \ non-alphanumeric outer boundaries. It records shapes such as `AI·SW` without assigning \ - prose, mathematics, or science semantics.\n\n", + prose, mathematics, or science semantics. The \ + `korean_inline_parenthesized_single_arithmetic_operator` gate requires an immediate \ + `Korean(` + one rule-45 arithmetic operator + `)Korean` span. Unlike broad coexistence \ + traits, it also locates the current engine's emitted structure and counts a mismatch as \ + signature-local only when the sentence's first differing cell falls inside that output \ + range.\n\n", ); text.push_str( "| Cluster | Candidates | Exact | Mismatch | Conflicting-reference cases |\n\ @@ -1166,6 +1286,15 @@ fn markdown(report: &AnalysisReport) -> String { stats.candidates, stats.candidates - pending )); + if stats.output_signature_mismatches_evaluated > 0 { + text.push_str(&format!( + "For this output-signature audit, {} mismatches were evaluable and {} have their \ + first differing cell inside the detected structure's current-engine output \ + range. The remaining mismatches are controls against causal over-attribution.\n\n", + stats.output_signature_mismatches_evaluated, + stats.first_difference_in_output_signature + )); + } text.push_str("Mismatch primary-class distribution:\n\n"); for (primary, count) in &stats.mismatch_primary_classes { text.push_str(&format!("- `{primary}`: {count}\n")); @@ -1229,6 +1358,13 @@ fn markdown(report: &AnalysisReport) -> String { and no engine rule is inferred from their references. Representative samples are \ sentence-level evidence: when the reported first difference precedes the detected \ middle-dot span, the cohort must not be treated as the cause of that mismatch.\n\n\ + The inline parenthesized-operator cohort has an independently checkable spacing boundary. \ + Hangeul rule 46 inserts spaces only when an operation or comparison sign is between \ + Korean text, while the literal parentheses intervene in this gate. Hangeul rule 49 says \ + punctuation spacing follows the print, and science rule 21 prints and brailles `(-)` and \ + `(+)` with no spaces inside the parentheses. The output-signature count is therefore the \ + implementation-candidate subset; mere sentence-level coexistence is retained only as a \ + control.\n\n\ Corpus contradictions remain a separate gate: identical inputs with conflicting \ references are classified as `corpus_suspect` before these cohorts are recorded and \ would appear explicitly in each mismatch primary-class distribution. Their absence does \ @@ -1287,6 +1423,27 @@ fn markdown(report: &AnalysisReport) -> String { stats.candidates, stats.exact, stats.mismatch )); } + if let Some(stats) = report + .pending_rule_review_clusters + .get(KOREAN_INLINE_PARENTHESIZED_OPERATOR) + { + let pending = stats + .mismatch_primary_classes + .get("pending_rule_review") + .copied() + .unwrap_or(0); + text.push_str(&format!( + "\nCurrent inline parenthesized-operator measurement: {} candidates, {} exact \ + controls, {} mismatches, {pending} members in the actual `pending_rule_review` \ + subcluster, and {}/{} evaluable mismatches whose first differing cell is inside the \ + emitted structure. Primary classes are preserved.\n", + stats.candidates, + stats.exact, + stats.mismatch, + stats.first_difference_in_output_signature, + stats.output_signature_mismatches_evaluated + )); + } text.push_str("\n## Encoding-error diagnostics\n\n"); text.push_str( @@ -1784,6 +1941,39 @@ mod tests { assert_eq!(has_allcaps_roman_middle_dot_runs(input), expected); } + #[rstest::rstest] + #[case::plus("양(+)극", vec!['+'])] + #[case::hyphen_minus("음(-)극", vec!['-'])] + #[case::unicode_minus("음(−)극", vec!['−'])] + #[case::times("항(×)목", vec!['×'])] + #[case::division("항(÷)목", vec!['÷'])] + #[case::equals("항(=)목", vec!['='])] + #[case::standalone("(+) 전극", vec![])] + #[case::space_inside("양( + )극", vec![])] + fn detects_inline_parenthesized_operators( + #[case] input: &str, + #[case] expected_operators: Vec, + ) { + assert_eq!( + inline_parenthesized_operators(input) + .into_iter() + .map(|candidate| candidate.operator) + .collect::>(), + expected_operators + ); + } + + #[test] + fn locates_current_engine_parenthesized_operator_output() { + let input = "양(+)극"; + let actual = braillify::encode_to_unicode(input).expect("probe must encode"); + let ranges = inline_parenthesized_operator_actual_ranges(input, &actual); + + assert_eq!(ranges.len(), 1); + assert!(ranges[0].start < ranges[0].end); + assert!(ranges[0].end <= actual.chars().count()); + } + #[test] fn aggregates_headword_expansion_outcomes_without_reclassification() { let cases = [ From d4acaeaee79236704a62e80cf3cccacd7f32f203 Mon Sep 17 00:00:00 2001 From: devfive Date: Tue, 1 Sep 2026 06:50:23 +0900 Subject: [PATCH 14/77] Preserve spacing inside parenthesized operators --- docs/corpus-analysis/NIKL_2025_V1.md | 126 ++++++++++-------- .../braillify/examples/nikl_corpus_analyze.rs | 17 ++- libs/braillify/src/rules/korean/rule_math.rs | 64 ++++++++- 3 files changed, 147 insertions(+), 60 deletions(-) diff --git a/docs/corpus-analysis/NIKL_2025_V1.md b/docs/corpus-analysis/NIKL_2025_V1.md index 6e54287c..8ab23aae 100644 --- a/docs/corpus-analysis/NIKL_2025_V1.md +++ b/docs/corpus-analysis/NIKL_2025_V1.md @@ -7,9 +7,9 @@ | Metric | Count | |---|---:| | Total | 83528 | -| Exact | 65491 | -| Mismatch | 18037 | -| Exact accuracy | 78.41% | +| Exact | 65514 | +| Mismatch | 18014 | +| Exact accuracy | 78.43% | | Duplicate records | 0 | | Inputs with conflicting references | 0 | @@ -20,17 +20,17 @@ Primary classes are evidence gates, not permissions to change the engine. `imple | Primary class | Count | |---|---:| | `comparison_method` | 303 | -| `exact` | 65491 | -| `pending_rule_review` | 17543 | +| `exact` | 65514 | +| `pending_rule_review` | 17520 | | `unsupported_character_review` | 191 | | Reproducible reason | Count | |---|---:| -| `exact` | 65491 | -| `foreign_text_rule_review` | 17352 | +| `exact` | 65514 | +| `foreign_text_rule_review` | 17341 | | `nfkc_input_equivalent` | 303 | -| `number_rule_review` | 178 | -| `punctuation_rule_review` | 13 | +| `number_rule_review` | 171 | +| `punctuation_rule_review` | 8 | | `unsupported_character_review` | 191 | ## Cross-cutting input-only structural cohorts @@ -39,21 +39,40 @@ These are cross-cutting input-only structural cohorts, not new primary classes a | Cluster | Candidates | Exact | Mismatch | Conflicting-reference cases | |---|---:|---:|---:|---:| -| `korean_inline_parenthesized_single_arithmetic_operator` | 23 | 0 | 23 | 0 | -| `korean_prefixed_closed_allcaps_parenthetical` | 54492 | 43775 | 10717 | 0 | +| `korean_inline_parenthesized_single_arithmetic_operator` | 23 | 17 | 6 | 0 | +| `korean_prefixed_closed_allcaps_parenthetical` | 54492 | 43779 | 10713 | 0 | | `multi_character_allcaps_roman_runs_joined_by_middle_dot` | 97 | 0 | 97 | 0 | -| `standalone_multi_character_uppercase_roman_word` | 62411 | 48749 | 13662 | 0 | +| `standalone_multi_character_uppercase_roman_word` | 62411 | 48757 | 13654 | 0 | | `uppercase_roman_headword_closed_multiword_parenthetical` | 175 | 15 | 160 | 0 | ### `korean_inline_parenthesized_single_arithmetic_operator` -Of the 23 candidates, 23 are the actual `pending_rule_review` subcluster. The other 0 candidates are exact or existing non-pending-primary controls; this cohort does not reclassify them. +Of the 23 candidates, 6 are the actual `pending_rule_review` subcluster. The other 17 candidates are exact or existing non-pending-primary controls; this cohort does not reclassify them. -For this output-signature audit, 23 mismatches were evaluable and 19 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. +For this output-signature audit, 6 mismatches were evaluable and 2 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. Mismatch primary-class distribution: -- `pending_rule_review`: 23 +- `pending_rule_review`: 6 + +Representative `exact` samples: + +- `sentence_01.json` #2922: 실제로 OECD 38개국 중 경제자유도 상위 10개국과 하위 10개국을 대상으로 경제자유도와 삶의 질(BLI)을 분석해보면, 경제자유도와 삶의 질 간에는 뚜렷한 정(+)의 상관관계(상관계수 +0.80)가 나타났다. + - expected: `⠠⠕⠂⠨⠝⠐⠥⠀⠴⠠⠠⠕⠑⠉⠙⠀⠼⠉⠓⠈⠗⠈⠍⠁` + - actual: `⠠⠕⠂⠨⠝⠐⠥⠀⠴⠠⠠⠕⠑⠉⠙⠀⠼⠉⠓⠈⠗⠈⠍⠁` + - current primary/reason: `exact` / `exact` +- `sentence_02.json` #417: 시는 세종고용복지플러스(+)센터, 세종여성새로일하기센터, 세종과학기술인협회 등과 공동으로 기업지원사업안내는 물론, 청년들이 면접 부담을 덜 수 있도록 청년면접비지원사업도 동시 진행하고 있다. + - expected: `⠠⠕⠉⠵⠀⠠⠝⠨⠿⠈⠥⠬⠶⠘⠭⠨⠕⠙⠮⠐⠎⠠⠪⠦` + - actual: `⠠⠕⠉⠵⠀⠠⠝⠨⠿⠈⠥⠬⠶⠘⠭⠨⠕⠙⠮⠐⠎⠠⠪⠦` + - current primary/reason: `exact` / `exact` +- `sentence_03.json` #12396: “2027년까지 수출과 매출에서 우리 중소기업이 차지하는 비율이 50% 이상이 되는 K-중소기업 50플러스(+)를 달성하는 데 있어서 메인비즈 기업이 선봉장으로 나서주기를 바랍니다.” + - expected: `⠦⠼⠃⠚⠃⠛⠀⠉⠡⠠⠫⠨⠕⠀⠠⠍⠰⠯⠈⠧⠀⠑⠗⠰` + - actual: `⠦⠼⠃⠚⠃⠛⠀⠉⠡⠠⠫⠨⠕⠀⠠⠍⠰⠯⠈⠧⠀⠑⠗⠰` + - current primary/reason: `exact` / `exact` +- `sentence_04.json` #798: 여기에 주요 산유국 협의체인 OPEC 플러스(+)가 미국의 직간접적인 압박에도 추가 감산을 결정하면서 중동에 대한 미국의 영향력이 약화하고 있다는 분석도 힘을 얻고 있다. + - expected: `⠱⠈⠕⠝⠀⠨⠍⠬⠀⠇⠒⠩⠈⠍⠁⠀⠚⠱⠃⠺⠰⠝⠟⠀` + - actual: `⠱⠈⠕⠝⠀⠨⠍⠬⠀⠇⠒⠩⠈⠍⠁⠀⠚⠱⠃⠺⠰⠝⠟⠀` + - current primary/reason: `exact` / `exact` Representative `mismatch` samples: @@ -62,30 +81,20 @@ Representative `mismatch` samples: - actual: `⠀⠼⠁⠟⠊⠶⠀⠴⠠⠠⠛⠙⠏⠲⠊⠥⠀⠈⠗⠠⠾⠊⠽⠉` - first differing cell (zero-based): 27 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_02.json` #417: 시는 세종고용복지플러스(+)센터, 세종여성새로일하기센터, 세종과학기술인협회 등과 공동으로 기업지원사업안내는 물론, 청년들이 면접 부담을 덜 수 있도록 청년면접비지원사업도 동시 진행하고 있다. - - expected: `⠙⠮⠐⠎⠠⠪⠦⠄⠢⠠⠴⠠⠝⠒⠓⠎⠐⠀⠠⠝⠨⠿⠱⠠` - - actual: `⠙⠮⠐⠎⠠⠪⠦⠄⠀⠢⠀⠠⠴⠠⠝⠒⠓⠎⠐⠀⠠⠝⠨⠿` - - first differing cell (zero-based): 25 - - current primary/reason: `pending_rule_review` / `punctuation_rule_review` -- `sentence_03.json` #12396: “2027년까지 수출과 매출에서 우리 중소기업이 차지하는 비율이 50% 이상이 되는 K-중소기업 50플러스(+)를 달성하는 데 있어서 메인비즈 기업이 선봉장으로 나서주기를 바랍니다.” - - expected: `⠙⠮⠐⠎⠠⠪⠦⠄⠢⠠⠴⠐⠮⠀⠊⠂⠠⠻⠚⠉⠵⠀⠊⠝` - - actual: `⠙⠮⠐⠎⠠⠪⠦⠄⠀⠢⠀⠠⠴⠐⠮⠀⠊⠂⠠⠻⠚⠉⠵⠀` - - first differing cell (zero-based): 101 - - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_04.json` #798: 여기에 주요 산유국 협의체인 OPEC 플러스(+)가 미국의 직간접적인 압박에도 추가 감산을 결정하면서 중동에 대한 미국의 영향력이 약화하고 있다는 분석도 힘을 얻고 있다. - - expected: `⠙⠮⠐⠎⠠⠪⠦⠄⠢⠠⠴⠫⠀⠑⠕⠈⠍⠁⠺⠀⠨⠕⠁⠫` - - actual: `⠙⠮⠐⠎⠠⠪⠦⠄⠀⠢⠀⠠⠴⠫⠀⠑⠕⠈⠍⠁⠺⠀⠨⠕` - - first differing cell (zero-based): 41 +- `sentence_02.json` #948: 유니브시티(UNIV+CITY)는 대학교(UNIVERSITY)와 시(CITY)의 합성어로 더하기(+)는 시와 대학, 기업 등과의 상생을 의미한다. 파란과 빨간, 노란색으로 자유롭고 간편함을 표현한 손 글씨(캘리그라피)를 통해 대학도시 천안의 젊음을 상징한다. + - expected: `⠄⠴⠠⠠⠥⠝⠊⠧⠐⠖⠠⠠⠉⠰⠽⠠⠴⠉⠵⠀⠊⠗⠚⠁` + - actual: `⠄⠴⠠⠠⠥⠝⠊⠧⠲⠀⠢⠀⠴⠠⠠⠉⠰⠽⠠⠴⠉⠵⠀⠊` + - first differing cell (zero-based): 18 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` ### `korean_prefixed_closed_allcaps_parenthetical` -Of the 54492 candidates, 10392 are the actual `pending_rule_review` subcluster. The other 44100 candidates are exact or existing non-pending-primary controls; this cohort does not reclassify them. +Of the 54492 candidates, 10388 are the actual `pending_rule_review` subcluster. The other 44104 candidates are exact or existing non-pending-primary controls; this cohort does not reclassify them. Mismatch primary-class distribution: - `comparison_method`: 185 -- `pending_rule_review`: 10392 +- `pending_rule_review`: 10388 - `unsupported_character_review`: 140 Representative `exact` samples: @@ -163,12 +172,12 @@ Representative `mismatch` samples: ### `standalone_multi_character_uppercase_roman_word` -Of the 62411 candidates, 13276 are the actual `pending_rule_review` subcluster. The other 49135 candidates are exact or existing non-pending-primary controls; this cohort does not reclassify them. +Of the 62411 candidates, 13268 are the actual `pending_rule_review` subcluster. The other 49143 candidates are exact or existing non-pending-primary controls; this cohort does not reclassify them. Mismatch primary-class distribution: - `comparison_method`: 223 -- `pending_rule_review`: 13276 +- `pending_rule_review`: 13268 - `unsupported_character_review`: 163 Representative `exact` samples: @@ -273,17 +282,18 @@ The Korean-prefixed all-caps parenthetical cohort isolates that ambiguity more n The all-caps Roman middle-dot cohort is also semantically underdetermined. Hangeul rule 29 defines Roman indicators around Roman text in a Korean sentence, and Hangeul rule 50 requires U+00B7 to be attached on both sides, but neither rule says that the punctuation joins the adjacent Roman runs into one Roman span. Math rule 2 separately defines the same printed dot as multiplication, and science rule 4 uses it inside chemical formulae. An input-only `AI·SW` gate therefore cannot prove which mode transition is required. Exact cases remain controls, mismatches retain their existing primary class, and no engine rule is inferred from their references. Representative samples are sentence-level evidence: when the reported first difference precedes the detected middle-dot span, the cohort must not be treated as the cause of that mismatch. -The inline parenthesized-operator cohort has an independently checkable spacing boundary. Hangeul rule 46 inserts spaces only when an operation or comparison sign is between Korean text, while the literal parentheses intervene in this gate. Hangeul rule 49 says punctuation spacing follows the print, and science rule 21 prints and brailles `(-)` and `(+)` with no spaces inside the parentheses. The output-signature count is therefore the implementation-candidate subset; mere sentence-level coexistence is retained only as a control. +The inline parenthesized-operator cohort has an independently checkable spacing boundary. Hangeul rule 46 inserts spaces only when an operation or comparison sign is between Korean text, while the literal parentheses intervene in this gate. Hangeul rule 49 says punctuation spacing follows the print, and science rule 21 prints and brailles `(-)` and `(+)` with no spaces inside the parentheses. The output-signature count is therefore the implementation-candidate subset; mere sentence-level coexistence is retained only as a control. ASCII hyphen-minus is independently supported through the `Symbol`/rule-49 punctuation path, while `+`, `×`, `÷`, and `=` reach the `MathSymbol` spacing rule; end-to-end tests preserve tight parentheses on both paths. Analyzer checkpoint `30a9b10` recorded the pre-fix baseline as 23 candidates, 0 exact, 23 mismatches, and 19 mismatches whose first difference was signature-local. The generalized rule-46/49 fix is evaluated below against that immutable baseline rather than inferred from a reference string. Corpus contradictions remain a separate gate: identical inputs with conflicting references are classified as `corpus_suspect` before these cohorts are recorded and would appear explicitly in each mismatch primary-class distribution. Their absence does not prove a reference correct; it only means that this deterministic contradiction test did not fire. -Current standalone-uppercase measurement: 62411 candidates, 48749 exact controls, 13662 mismatches, and 13276 members in the actual `pending_rule_review` subcluster. Its high frequency does not make it causal: the same input shape is exact in many cases, and a sentence containing the shape may first differ at another Roman, numeric, or punctuation structure. No engine change is inferred from this cohort. +Current standalone-uppercase measurement: 62411 candidates, 48757 exact controls, 13654 mismatches, and 13268 members in the actual `pending_rule_review` subcluster. Its high frequency does not make it causal: the same input shape is exact in many cases, and a sentence containing the shape may first differ at another Roman, numeric, or punctuation structure. No engine change is inferred from this cohort. -Current Korean-prefixed all-caps parenthetical measurement: 54492 candidates, 43775 exact controls, 10717 mismatches, and 10392 members in the actual `pending_rule_review` subcluster. This is a semantic-collision audit, not an engine routing rule; no implementation change is inferred from its reference outputs. +Current Korean-prefixed all-caps parenthetical measurement: 54492 candidates, 43779 exact controls, 10713 mismatches, and 10388 members in the actual `pending_rule_review` subcluster. This is a semantic-collision audit, not an engine routing rule; no implementation change is inferred from its reference outputs. Current all-caps Roman middle-dot measurement: 97 candidates, 0 exact controls, 97 mismatches, and 97 members in the actual `pending_rule_review` subcluster. This cross-cutting cohort preserves every primary class and is not an engine routing rule. -Current inline parenthesized-operator measurement: 23 candidates, 0 exact controls, 23 mismatches, 23 members in the actual `pending_rule_review` subcluster, and 19/23 evaluable mismatches whose first differing cell is inside the emitted structure. Primary classes are preserved. +Current inline parenthesized-operator measurement: 23 candidates, 17 exact controls, 6 mismatches, 6 members in the actual `pending_rule_review` subcluster, and 2/6 evaluable mismatches whose first differing cell is inside the emitted structure. Primary classes are preserved. + At this implementation checkpoint, the strict cohort moved from 0 to 17 exact cases; the corpus-wide total moved from 65,491 to 65514 (+23 exact) because the same PDF-backed spacing rule also applies outside the stricter Korean-boundary audit gate. The complete standard suite remains 5,141/5,141. ## Encoding-error diagnostics @@ -335,18 +345,18 @@ Families are diagnostics, not automatic normalization permissions. Rules 68/69 c | Shard | Exact | Total | Accuracy | |---|---:|---:|---:| -| `sentence_01.json` | 19563 | 25000 | 78.25% | -| `sentence_02.json` | 19059 | 25000 | 76.24% | -| `sentence_03.json` | 19914 | 25000 | 79.66% | -| `sentence_04.json` | 6955 | 8528 | 81.55% | +| `sentence_01.json` | 19567 | 25000 | 78.27% | +| `sentence_02.json` | 19070 | 25000 | 76.28% | +| `sentence_03.json` | 19917 | 25000 | 79.67% | +| `sentence_04.json` | 6960 | 8528 | 81.61% | ## Overlapping mismatch traits | Trait | Count | |---|---:| -| `contains_ascii_digits` | 14375 | -| `contains_ascii_letters` | 17835 | -| `contains_delimiter_or_quote` | 18037 | +| `contains_ascii_digits` | 14362 | +| `contains_ascii_letters` | 17824 | +| `contains_delimiter_or_quote` | 18014 | | `input_not_nfkc` | 826 | ## Samples @@ -394,9 +404,6 @@ Families are diagnostics, not automatic normalization permissions. Rules 68/69 c ### `number_rule_review` -- `sentence_01.json` #788: 1일 발란은 고객 신뢰를 강화하기 위한 일환으로 파트너사 대상 판매 상품 사전 검수 기준을 대폭 강화한 ‘발란 케어 플러스(+)’를 이 달부터 실시한다고 밝혔다. - - expected: `⠙⠮⠐⠎⠠⠪⠦⠄⠢⠠⠴⠴⠄⠐⠮⠀⠕⠀⠊⠂⠘⠍⠓⠎` - - actual: `⠙⠮⠐⠎⠠⠪⠦⠄⠀⠢⠀⠠⠴⠴⠄⠐⠮⠀⠕⠀⠊⠂⠘⠍` - `sentence_01.json` #845: 부산광역시는 3일 오전 부산광역시청에서 초록우산어린이재단 부산본부, 월드비전 부산사업본부, 굿네이버스 영남본부와 ‘자립+(더하기) 동행 프로젝트’ 업무협약을 체결했다고 밝혔다. - expected: `⠠⠦⠨⠐⠕⠃⠀⠢⠦⠄⠊⠎⠚⠈⠕⠠⠴⠀⠊⠿⠚⠗⠶⠀` - actual: `⠠⠦⠨⠐⠕⠃⠀⠢⠀⠦⠄⠊⠎⠚⠈⠕⠠⠴⠀⠊⠿⠚⠗⠶` @@ -409,6 +416,9 @@ Families are diagnostics, not automatic normalization permissions. Rules 68/69 c - `sentence_01.json` #9055: 한국건설기술연구원에 따르면 주거용 건물 건설공사비지수는 올해 9월 기준 152.76(잠정치)으로 전년 동월(147.31) 대비 약 3.7%가량 증가했다. - expected: `⠾⠉⠡⠀⠊⠿⠏⠂⠦⠄⠼⠁⠙⠛⠲⠉⠁⠠⠴⠀⠊⠗⠘⠕` - actual: `⠾⠉⠡⠀⠊⠿⠏⠂⠀⠀⠦⠼⠁⠙⠛⠲⠉⠁⠴⠀⠊⠗⠘⠕` +- `sentence_01.json` #10139: ‘서울 반려견 순찰대’는 8개월 동안의 올해 성과 보고를 통해 △범죄예방(112) 신고 317건 △생활위험 관련(120) 신고 2187건 등으로 지역사회 안전망을 촘촘하게 구축하는 데 이바지했다고 발표했다. + - expected: `⠀⠓⠿⠚⠗⠀⠸⠬⠀⠘⠎⠢⠨⠽⠤⠌⠘⠶⠦⠄⠼⠁⠁⠃` + - actual: `⠀⠓⠿⠚⠗⠀⠸⠬⠇⠘⠎⠢⠨⠽⠤⠌⠘⠶⠦⠄⠼⠁⠁⠃` ### `punctuation_rule_review` @@ -416,17 +426,17 @@ Families are diagnostics, not automatic normalization permissions. Rules 68/69 c - expected: `⠎⠧⠕⠂⠊⠪⠐⠂⠀⠜⠠⠍⠊⠮⠺⠀⠨⠾⠨⠗⠶⠴⠄⠦` - actual: `⠎⠧⠕⠂⠊⠪⠐⠂⠜⠠⠍⠊⠮⠺⠀⠨⠾⠨⠗⠶⠴⠄⠦⠄` - `sentence_01.json` #21108: 전고체 이차전지는 양(+)극과 음(-)극 사이에서 이온을 전달하는 ‘전해질’을 액체가 아닌, 화재나 폭발 위험성이 낮은 고체로 대체한 것이다. - - expected: `⠕⠉⠵⠀⠜⠶⠦⠄⠢⠠⠴⠈⠪⠁⠈⠧⠀⠪⠢⠦⠄⠔⠠⠴` - - actual: `⠕⠉⠵⠀⠜⠶⠦⠄⠀⠢⠀⠠⠴⠈⠪⠁⠈⠧⠀⠪⠢⠦⠄⠤` -- `sentence_02.json` #417: 시는 세종고용복지플러스(+)센터, 세종여성새로일하기센터, 세종과학기술인협회 등과 공동으로 기업지원사업안내는 물론, 청년들이 면접 부담을 덜 수 있도록 청년면접비지원사업도 동시 진행하고 있다. - - expected: `⠙⠮⠐⠎⠠⠪⠦⠄⠢⠠⠴⠠⠝⠒⠓⠎⠐⠀⠠⠝⠨⠿⠱⠠` - - actual: `⠙⠮⠐⠎⠠⠪⠦⠄⠀⠢⠀⠠⠴⠠⠝⠒⠓⠎⠐⠀⠠⠝⠨⠿` -- `sentence_02.json` #4622: 시는 세종고용복지플러스(+)센터, 세종여성새로일하기센터, 세종과학기술인협회 등과 공동으로 기업지원 사업안내는 물론, 청년들이 면접부담을 덜수 있도록 청년면접비지원사업도 동시 진행하고 있다. - - expected: `⠙⠮⠐⠎⠠⠪⠦⠄⠢⠠⠴⠠⠝⠒⠓⠎⠐⠀⠠⠝⠨⠿⠱⠠` - - actual: `⠙⠮⠐⠎⠠⠪⠦⠄⠀⠢⠀⠠⠴⠠⠝⠒⠓⠎⠐⠀⠠⠝⠨⠿` -- `sentence_02.json` #6472: 시는 세종고용복지플러스(+)센터, 세종여성새로일하기센터, 세종과학기술인협회 등과 공동으로 기업지원사업안내는 물론 청년들이 면접 부담을 덜 수 있도록 청년면접비지원사업도 동시 진행하고 있다. - - expected: `⠙⠮⠐⠎⠠⠪⠦⠄⠢⠠⠴⠠⠝⠒⠓⠎⠐⠀⠠⠝⠨⠿⠱⠠` - - actual: `⠙⠮⠐⠎⠠⠪⠦⠄⠀⠢⠀⠠⠴⠠⠝⠒⠓⠎⠐⠀⠠⠝⠨⠿` + - expected: `⠁⠈⠧⠀⠪⠢⠦⠄⠔⠠⠴⠈⠪⠁⠀⠇⠕⠝⠠⠎⠀⠕⠷⠮` + - actual: `⠁⠈⠧⠀⠪⠢⠦⠄⠤⠠⠴⠈⠪⠁⠀⠇⠕⠝⠠⠎⠀⠕⠷⠮` +- `sentence_03.json` #18547: 남태우는 디즈니+(플러스)의 오리지널 시리즈 ‘한강’(연출/극본 김상철)에서 국제범죄 수사대 고형민 경사 역할을 맡아 연기 변신을 선보인다. + - expected: `⠊⠕⠨⠪⠉⠕⠀⠢⠦⠄⠙⠮⠐⠎⠠⠪⠠⠴⠺⠀⠥⠐⠕⠨` + - actual: `⠊⠕⠨⠪⠉⠕⠀⠢⠀⠦⠄⠙⠮⠐⠎⠠⠪⠠⠴⠺⠀⠥⠐⠕` +- `sentence_03.json` #19763: 디즈니+ 드라마 ‘무빙’을 언급하던 한효주 곁에 다가온 조인성은 “굉장히 무서운 와이프였어요~”라고 극 중 아내 자랑(?)을 하며 남편 손님과 눈빛 교환을 하는 모습으로 폭소를 안긴다. + - expected: `⠊⠕⠨⠪⠉⠕⠀⠢⠀⠊⠪⠐⠣⠑⠀⠠⠦⠑⠍⠘⠕⠶⠴⠄` + - actual: `⠊⠕⠨⠪⠉⠕⠀⠀⠢⠀⠊⠪⠐⠣⠑⠀⠠⠦⠑⠍⠘⠕⠶⠴` +- `sentence_03.json` #21665: 그러자 김숙은 “그러면 우재야. ‘홍김동전’ 잠깐 쉬어라”며 급 하차 권유(?)를 하고, 홍진경은 “김치 없냐”면서 느닷없이 김치를 찾는 등 총체적 난국이 펼쳐졌다. + - expected: `⠕⠢⠠⠍⠁⠵⠀⠦⠁⠒⠀⠍⠨⠗⠜⠲⠀⠠⠦⠚⠿⠈⠕⠢` + - actual: `⠕⠢⠠⠍⠁⠵⠀⠦⠈⠪⠐⠎⠑⠡⠀⠍⠨⠗⠜⠲⠀⠠⠦⠚` ### `unsupported_character_review` diff --git a/libs/braillify/examples/nikl_corpus_analyze.rs b/libs/braillify/examples/nikl_corpus_analyze.rs index 4633f922..f6a61fd3 100644 --- a/libs/braillify/examples/nikl_corpus_analyze.rs +++ b/libs/braillify/examples/nikl_corpus_analyze.rs @@ -1364,7 +1364,13 @@ fn markdown(report: &AnalysisReport) -> String { punctuation spacing follows the print, and science rule 21 prints and brailles `(-)` and \ `(+)` with no spaces inside the parentheses. The output-signature count is therefore the \ implementation-candidate subset; mere sentence-level coexistence is retained only as a \ - control.\n\n\ + control. ASCII hyphen-minus is independently supported through the `Symbol`/rule-49 \ + punctuation path, while `+`, `×`, `÷`, and `=` reach the `MathSymbol` spacing rule; \ + end-to-end tests preserve tight parentheses on both paths. Analyzer checkpoint `30a9b10` \ + recorded the pre-fix baseline as 23 candidates, 0 exact, 23 mismatches, and 19 \ + mismatches whose first difference was signature-local. The generalized rule-46/49 fix \ + is evaluated below against that immutable baseline rather than inferred from a reference \ + string.\n\n\ Corpus contradictions remain a separate gate: identical inputs with conflicting \ references are classified as `corpus_suspect` before these cohorts are recorded and \ would appear explicitly in each mismatch primary-class distribution. Their absence does \ @@ -1443,6 +1449,15 @@ fn markdown(report: &AnalysisReport) -> String { stats.first_difference_in_output_signature, stats.output_signature_mismatches_evaluated )); + text.push_str(&format!( + " At this implementation checkpoint, the strict cohort moved from 0 to {} exact \ + cases; the corpus-wide total moved from 65,491 to {} ({:+} exact) because the same \ + PDF-backed spacing rule also applies outside the stricter Korean-boundary audit \ + gate. The complete standard suite remains 5,141/5,141.\n", + stats.exact, + report.exact, + report.exact as isize - 65_491 + )); } text.push_str("\n## Encoding-error diagnostics\n\n"); diff --git a/libs/braillify/src/rules/korean/rule_math.rs b/libs/braillify/src/rules/korean/rule_math.rs index 51fa40c5..39c53e47 100644 --- a/libs/braillify/src/rules/korean/rule_math.rs +++ b/libs/braillify/src/rules/korean/rule_math.rs @@ -73,7 +73,14 @@ impl BrailleRule for RuleMath { } }; - let pad_spaces = prev_has_korean && next_korean_is_non_josa; + // PDF 한글 제49항 — 문장 부호의 띄어쓰기는 묵자를 따른다. + // `한글(+)한글`처럼 연산 기호가 소괄호에 직접 둘러싸인 경우 기호는 + // 한글 사이에 직접 놓인 것이 아니므로 제46항의 양옆 공백을 삽입하지 않는다. + // 과학 제21항의 `(-)`·`(+)` 예제도 괄호 안을 붙여 적는다. + let immediately_parenthesized = ctx.index > 0 + && ctx.word_chars.get(ctx.index - 1) == Some(&'(') + && ctx.word_chars.get(ctx.index + 1) == Some(&')'); + let pad_spaces = prev_has_korean && next_korean_is_non_josa && !immediately_parenthesized; if pad_spaces { ctx.emit(0); @@ -120,4 +127,59 @@ mod tests { assert!(owned.result.starts_with(&[0])); assert!(owned.result.ends_with(&[0])); } + + #[rstest::rstest] + #[case::plus('+')] + #[case::times('×')] + #[case::division('÷')] + #[case::equals('=')] + fn parenthesized_math_symbol_does_not_gain_inner_spaces(#[case] operator: char) { + let input = format!("가({operator})나"); + let mut owned = crate::test_helpers::CtxOwned::for_text(&input, false); + let mut ctx = owned.ctx_at(2); + + let outcome = RuleMath.apply(&mut ctx).expect("math rule should apply"); + + assert!(matches!(outcome, RuleResult::Consumed)); + assert!(!owned.result.is_empty()); + assert_ne!(owned.result.first(), Some(&0)); + assert_ne!(owned.result.last(), Some(&0)); + } + + #[rstest::rstest] + #[case::plus_math_symbol("양", "+", "극")] + #[case::ascii_hyphen_minus_symbol("음", "-", "극")] + fn full_encoder_preserves_tight_parenthesized_operator( + #[case] left: &str, + #[case] operator: &str, + #[case] right: &str, + ) { + let input = format!("{left}({operator}){right}"); + let expected = [left, &format!("({operator})"), right] + .into_iter() + .map(|part| crate::encode_to_unicode(part).expect("component must encode")) + .collect::>() + .concat(); + + assert_eq!( + crate::encode_to_unicode(&input).expect("full input must encode"), + expected + ); + } + + #[test] + fn ascii_hyphen_minus_is_supported_by_punctuation_rule_49_path() { + assert!(matches!( + crate::char_struct::CharType::new('-').expect("hyphen-minus must classify"), + crate::char_struct::CharType::Symbol('-') + )); + assert_eq!( + crate::encode_to_unicode("음(-)극").expect("full input must encode"), + ["음", "(-)", "극"] + .into_iter() + .map(|part| crate::encode_to_unicode(part).expect("component must encode")) + .collect::>() + .concat() + ); + } } From 2203ea3b556e01a970d1712df0a2ef92624842cd Mon Sep 17 00:00:00 2001 From: devfive Date: Tue, 1 Sep 2026 06:55:14 +0900 Subject: [PATCH 15/77] Localize tight triangle corpus mismatches --- docs/corpus-analysis/NIKL_2025_V1.md | 40 ++++++- .../braillify/examples/nikl_corpus_analyze.rs | 113 +++++++++++++++++- 2 files changed, 151 insertions(+), 2 deletions(-) diff --git a/docs/corpus-analysis/NIKL_2025_V1.md b/docs/corpus-analysis/NIKL_2025_V1.md index 8ab23aae..21096cb8 100644 --- a/docs/corpus-analysis/NIKL_2025_V1.md +++ b/docs/corpus-analysis/NIKL_2025_V1.md @@ -35,7 +35,7 @@ Primary classes are evidence gates, not permissions to change the engine. `imple ## Cross-cutting input-only structural cohorts -These are cross-cutting input-only structural cohorts, not new primary classes and not engine routing rules. Candidate selection never changes a case's existing primary class. Only cohort members already classified as `pending_rule_review` form a pending subcluster; exact and other-primary members are controls that retain their existing outcomes. The `uppercase_roman_headword_closed_multiword_parenthetical` gate requires a two-or-more character uppercase ASCII headword immediately followed by a closed parenthesis whose contents are two or more ASCII Roman words separated only by spaces. Because the contents admit only letters and spaces, visible operators, subscript/superscript notation, and nested parentheses are excluded deterministically. The `standalone_multi_character_uppercase_roman_word` gate finds maximal ASCII-letter runs of two or more capitals with non-alphanumeric boundaries; a run immediately followed by `(` is excluded so the HCA-style headword itself is not counted by both gates. The `korean_prefixed_closed_allcaps_parenthetical` gate requires an immediately preceding Korean character and a closed body of two or more uppercase ASCII letters. It intentionally contains both acronym annotations (`책임자(COO)`) and scientific formulae (`일산화탄소(CO)`) so their semantic collision remains measurable. The `multi_character_allcaps_roman_runs_joined_by_middle_dot` gate requires two maximal ASCII-letter runs of at least two capitals joined directly by U+00B7, with non-alphanumeric outer boundaries. It records shapes such as `AI·SW` without assigning prose, mathematics, or science semantics. The `korean_inline_parenthesized_single_arithmetic_operator` gate requires an immediate `Korean(` + one rule-45 arithmetic operator + `)Korean` span. Unlike broad coexistence traits, it also locates the current engine's emitted structure and counts a mismatch as signature-local only when the sentence's first differing cell falls inside that output range. +These are cross-cutting input-only structural cohorts, not new primary classes and not engine routing rules. Candidate selection never changes a case's existing primary class. Only cohort members already classified as `pending_rule_review` form a pending subcluster; exact and other-primary members are controls that retain their existing outcomes. The `uppercase_roman_headword_closed_multiword_parenthetical` gate requires a two-or-more character uppercase ASCII headword immediately followed by a closed parenthesis whose contents are two or more ASCII Roman words separated only by spaces. Because the contents admit only letters and spaces, visible operators, subscript/superscript notation, and nested parentheses are excluded deterministically. The `standalone_multi_character_uppercase_roman_word` gate finds maximal ASCII-letter runs of two or more capitals with non-alphanumeric boundaries; a run immediately followed by `(` is excluded so the HCA-style headword itself is not counted by both gates. The `korean_prefixed_closed_allcaps_parenthetical` gate requires an immediately preceding Korean character and a closed body of two or more uppercase ASCII letters. It intentionally contains both acronym annotations (`책임자(COO)`) and scientific formulae (`일산화탄소(CO)`) so their semantic collision remains measurable. The `multi_character_allcaps_roman_runs_joined_by_middle_dot` gate requires two maximal ASCII-letter runs of at least two capitals joined directly by U+00B7, with non-alphanumeric outer boundaries. It records shapes such as `AI·SW` without assigning prose, mathematics, or science semantics. The `korean_inline_parenthesized_single_arithmetic_operator` gate requires an immediate `Korean(` + one rule-45 arithmetic operator + `)Korean` span. Unlike broad coexistence traits, it also locates the current engine's emitted structure and counts a mismatch as signature-local only when the sentence's first differing cell falls inside that output range. The `tight_triangle_mark_immediately_before_korean` gate requires literal `△한글` with no input space and includes the first following Korean cell in its localized output range, so an observed missing-space difference is measured at the mark boundary. | Cluster | Candidates | Exact | Mismatch | Conflicting-reference cases | |---|---:|---:|---:|---:| @@ -43,6 +43,7 @@ These are cross-cutting input-only structural cohorts, not new primary classes a | `korean_prefixed_closed_allcaps_parenthetical` | 54492 | 43779 | 10713 | 0 | | `multi_character_allcaps_roman_runs_joined_by_middle_dot` | 97 | 0 | 97 | 0 | | `standalone_multi_character_uppercase_roman_word` | 62411 | 48757 | 13654 | 0 | +| `tight_triangle_mark_immediately_before_korean` | 377 | 0 | 377 | 0 | | `uppercase_roman_headword_closed_multiword_parenthetical` | 175 | 15 | 160 | 0 | ### `korean_inline_parenthesized_single_arithmetic_operator` @@ -222,6 +223,39 @@ Representative `mismatch` samples: - first differing cell (zero-based): 60 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +### `tight_triangle_mark_immediately_before_korean` + +Of the 377 candidates, 377 are the actual `pending_rule_review` subcluster. The other 0 candidates are exact or existing non-pending-primary controls; this cohort does not reclassify them. + +For this output-signature audit, 377 mismatches were evaluable and 13 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. + +Mismatch primary-class distribution: + +- `pending_rule_review`: 377 + +Representative `mismatch` samples: + +- `sentence_01.json` #3907: 웹케시그룹은 △청구·결제·수납 솔루션 ‘위빌(WeBILL)’ △글로벌 중견·대기업 자금관리 솔루션 ‘위엠비에이(WeMBA)’ △전자세금계산서 발행 솔루션 ‘위택스(WeTAX)’ △글로벌 통합 자금관리 시스템 ‘위지엠비에이(WeGMBA)’ 등의 글로벌 전략 상품도 순차적으로 출시 예정이다. + - expected: `⠪⠐⠍⠃⠵⠀⠸⠬⠀⠰⠻⠈⠍⠐⠆⠈⠳⠨⠝⠐⠆⠠⠍⠉` + - actual: `⠪⠐⠍⠃⠵⠀⠸⠬⠇⠰⠻⠈⠍⠐⠆⠈⠳⠨⠝⠐⠆⠠⠍⠉` + - first differing cell (zero-based): 16 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_02.json` #17: 시범사업은 서해안권·백제문화권·서부내륙권을 대표하는 4개 시군의 관광자원 특성을 반영한 △문화치유 △해양치유 △마을맞춤 △엠지(MZ)맞춤 등 유형별 코스를 집중 발굴해 워케이션 상품을 기획했다. + - expected: `⠘⠒⠻⠚⠒⠀⠸⠬⠀⠑⠛⠚⠧⠰⠕⠩⠀⠸⠬⠀⠚⠗⠜⠶` + - actual: `⠘⠒⠻⠚⠒⠀⠸⠬⠇⠑⠛⠚⠧⠰⠕⠩⠀⠸⠬⠇⠚⠗⠜⠶` + - first differing cell (zero-based): 94 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_03.json` #244: 올해 모집 분야는 △에듀테크&콘텐츠 △라이프스타일 △정보통신기술(ICT)&디지털 기반 혁신기술 등이다. 시리즈A 단계까지 법인 등록 스타트업이면 지원할 수 있다. + - expected: `⠘⠛⠜⠉⠵⠀⠸⠬⠀⠝⠊⠩⠓⠝⠋⠪⠴⠈⠯⠲⠋⠷⠓⠝` + - actual: `⠘⠛⠜⠉⠵⠀⠸⠬⠇⠝⠊⠩⠓⠝⠋⠪⠴⠈⠯⠲⠋⠷⠓⠝` + - first differing cell (zero-based): 19 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_04.json` #7059: 현재 남양주시의 놀이체험시설은 △놀자람(화도) △까꿍놀이터(진접) △도르르(호평) △북(Book)놀이터(별내) △아이꿈놀이터(와부) 총 5개소로, 기존에 무료로 운영 중인 아이꿈놀이터를 제외한 유료 시설 4개소에 대해 무료 서비스가 제공된다. + - expected: `⠠⠕⠠⠞⠵⠀⠸⠬⠀⠉⠥⠂⠨⠐⠣⠢⠦⠄⠚⠧⠊⠥⠠⠴` + - actual: `⠠⠕⠠⠞⠵⠀⠸⠬⠇⠉⠥⠂⠨⠐⠣⠢⠦⠄⠚⠧⠊⠥⠠⠴` + - first differing cell (zero-based): 32 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` + ### `uppercase_roman_headword_closed_multiword_parenthetical` Of the 175 candidates, 153 are the actual `pending_rule_review` subcluster. The other 22 candidates are exact or existing non-pending-primary controls; this cohort does not reclassify them. @@ -284,6 +318,8 @@ The all-caps Roman middle-dot cohort is also semantically underdetermined. Hange The inline parenthesized-operator cohort has an independently checkable spacing boundary. Hangeul rule 46 inserts spaces only when an operation or comparison sign is between Korean text, while the literal parentheses intervene in this gate. Hangeul rule 49 says punctuation spacing follows the print, and science rule 21 prints and brailles `(-)` and `(+)` with no spaces inside the parentheses. The output-signature count is therefore the implementation-candidate subset; mere sentence-level coexistence is retained only as a control. ASCII hyphen-minus is independently supported through the `Symbol`/rule-49 punctuation path, while `+`, `×`, `÷`, and `=` reach the `MathSymbol` spacing rule; end-to-end tests preserve tight parentheses on both paths. Analyzer checkpoint `30a9b10` recorded the pre-fix baseline as 23 candidates, 0 exact, 23 mismatches, and 19 mismatches whose first difference was signature-local. The generalized rule-46/49 fix is evaluated below against that immutable baseline rather than inferred from a reference string. +The tight-triangle cohort is not an implementation premise. Hangeul rule 49 assigns `△` the omission-mark role and requires print spacing to be followed, while rule 72 also assigns the same glyph a bullet role but shows a print space after every bullet. A tight corpus input does not identify which role was intended, and adding a space absent from the input would contradict rule 49 unless independent layout evidence establishes a bullet. Localized mismatches are therefore corpus/layout review evidence only. + Corpus contradictions remain a separate gate: identical inputs with conflicting references are classified as `corpus_suspect` before these cohorts are recorded and would appear explicitly in each mismatch primary-class distribution. Their absence does not prove a reference correct; it only means that this deterministic contradiction test did not fire. Current standalone-uppercase measurement: 62411 candidates, 48757 exact controls, 13654 mismatches, and 13268 members in the actual `pending_rule_review` subcluster. Its high frequency does not make it causal: the same input shape is exact in many cases, and a sentence containing the shape may first differ at another Roman, numeric, or punctuation structure. No engine change is inferred from this cohort. @@ -295,6 +331,8 @@ Current all-caps Roman middle-dot measurement: 97 candidates, 0 exact controls, Current inline parenthesized-operator measurement: 23 candidates, 17 exact controls, 6 mismatches, 6 members in the actual `pending_rule_review` subcluster, and 2/6 evaluable mismatches whose first differing cell is inside the emitted structure. Primary classes are preserved. At this implementation checkpoint, the strict cohort moved from 0 to 17 exact cases; the corpus-wide total moved from 65,491 to 65514 (+23 exact) because the same PDF-backed spacing rule also applies outside the stricter Korean-boundary audit gate. The complete standard suite remains 5,141/5,141. +Current tight-triangle measurement: 377 candidates, 0 exact controls, 377 mismatches, 377 members in the actual `pending_rule_review` subcluster, and 13/377 evaluable mismatches whose first difference is inside the `△` plus first-Korean output range. No engine change is inferred. + ## Encoding-error diagnostics The audit starts from all raw encoding errors, then separates cases already resolved by a comparison method or corpus contradiction. The message, family, and singleton tables below count only unresolved encoding-error review cases. These diagnostics are not additional primary classes. A singleton unsupported character is a character that also fails when encoded by itself. Such a failure remains a review candidate until the PDF independently establishes support. diff --git a/libs/braillify/examples/nikl_corpus_analyze.rs b/libs/braillify/examples/nikl_corpus_analyze.rs index f6a61fd3..1f31fe7b 100644 --- a/libs/braillify/examples/nikl_corpus_analyze.rs +++ b/libs/braillify/examples/nikl_corpus_analyze.rs @@ -555,6 +555,7 @@ const ALLCAPS_ROMAN_MIDDLE_DOT_RUNS: &str = "multi_character_allcaps_roman_runs_joined_by_middle_dot"; const KOREAN_INLINE_PARENTHESIZED_OPERATOR: &str = "korean_inline_parenthesized_single_arithmetic_operator"; +const TIGHT_TRIANGLE_BEFORE_KOREAN: &str = "tight_triangle_mark_immediately_before_korean"; /// Input-only candidate gate for acronym expansions such as /// `HCA(Home Connectivity Alliance)`. @@ -789,6 +790,57 @@ fn first_difference_in_inline_parenthesized_operator(item: &EncodedCase) -> bool .any(|range| range.contains(&first_difference)) } +fn tight_triangle_positions(input: &str) -> Vec { + input + .match_indices('△') + .filter_map(|(byte, mark)| { + input[byte + mark.len()..] + .chars() + .next() + .is_some_and(is_korean_script) + .then_some(byte) + }) + .collect() +} + +/// Current-engine ranges for `△한글`, including the first Korean cell after +/// the mark. A missing reference space therefore differs inside this range, +/// while unrelated earlier sentence differences do not count as causal. +fn tight_triangle_actual_ranges(input: &str, actual: &str) -> Vec> { + let actual_cells = actual.chars().collect::>(); + let marker = braillify::encode_to_unicode("△") + .expect("triangle probe must encode") + .chars() + .collect::>(); + tight_triangle_positions(input) + .into_iter() + .filter_map(|byte| { + let start = braillify::encode_to_unicode(&input[..byte]) + .ok()? + .chars() + .count(); + let marker_end = start.checked_add(marker.len())?; + let range_end = marker_end.checked_add(1)?; + (actual_cells.get(start..marker_end) == Some(marker.as_slice()) + && actual_cells.get(marker_end).is_some()) + .then_some(start..range_end) + }) + .collect() +} + +fn first_difference_in_tight_triangle(item: &EncodedCase) -> bool { + let Ok(actual) = &item.actual else { + return false; + }; + if actual == &item.located.case.unicode { + return false; + } + let first_difference = first_difference_cell(&item.located.case.unicode, actual); + tight_triangle_actual_ranges(&item.located.case.input, actual) + .into_iter() + .any(|range| range.contains(&first_difference)) +} + fn enum_key(value: &T) -> String { serde_json::to_value(value) .expect("enum serialization must succeed") @@ -965,6 +1017,10 @@ fn analyze( STANDALONE_UPPERCASE_ROMAN_WORD.to_string(), PendingRuleReviewClusterStats::default(), ), + ( + TIGHT_TRIANGLE_BEFORE_KOREAN.to_string(), + PendingRuleReviewClusterStats::default(), + ), ( UPPERCASE_ROMAN_HEADWORD_EXPANSION.to_string(), PendingRuleReviewClusterStats::default(), @@ -1026,6 +1082,11 @@ fn analyze( has_standalone_uppercase_roman_word(&item.located.case.input), None, ), + ( + TIGHT_TRIANGLE_BEFORE_KOREAN, + !tight_triangle_positions(&item.located.case.input).is_empty(), + Some(first_difference_in_tight_triangle(item)), + ), ( UPPERCASE_ROMAN_HEADWORD_EXPANSION, has_uppercase_roman_headword_expansion(&item.located.case.input), @@ -1260,7 +1321,9 @@ fn markdown(report: &AnalysisReport) -> String { `Korean(` + one rule-45 arithmetic operator + `)Korean` span. Unlike broad coexistence \ traits, it also locates the current engine's emitted structure and counts a mismatch as \ signature-local only when the sentence's first differing cell falls inside that output \ - range.\n\n", + range. The `tight_triangle_mark_immediately_before_korean` gate requires literal \ + `△한글` with no input space and includes the first following Korean cell in its localized \ + output range, so an observed missing-space difference is measured at the mark boundary.\n\n", ); text.push_str( "| Cluster | Candidates | Exact | Mismatch | Conflicting-reference cases |\n\ @@ -1371,6 +1434,12 @@ fn markdown(report: &AnalysisReport) -> String { mismatches whose first difference was signature-local. The generalized rule-46/49 fix \ is evaluated below against that immutable baseline rather than inferred from a reference \ string.\n\n\ + The tight-triangle cohort is not an implementation premise. Hangeul rule 49 assigns `△` \ + the omission-mark role and requires print spacing to be followed, while rule 72 also \ + assigns the same glyph a bullet role but shows a print space after every bullet. A tight \ + corpus input does not identify which role was intended, and adding a space absent from \ + the input would contradict rule 49 unless independent layout evidence establishes a \ + bullet. Localized mismatches are therefore corpus/layout review evidence only.\n\n\ Corpus contradictions remain a separate gate: identical inputs with conflicting \ references are classified as `corpus_suspect` before these cohorts are recorded and \ would appear explicitly in each mismatch primary-class distribution. Their absence does \ @@ -1459,6 +1528,27 @@ fn markdown(report: &AnalysisReport) -> String { report.exact as isize - 65_491 )); } + if let Some(stats) = report + .pending_rule_review_clusters + .get(TIGHT_TRIANGLE_BEFORE_KOREAN) + { + let pending = stats + .mismatch_primary_classes + .get("pending_rule_review") + .copied() + .unwrap_or(0); + text.push_str(&format!( + "\nCurrent tight-triangle measurement: {} candidates, {} exact controls, {} \ + mismatches, {pending} members in the actual `pending_rule_review` subcluster, and \ + {}/{} evaluable mismatches whose first difference is inside the `△` plus first-Korean \ + output range. No engine change is inferred.\n", + stats.candidates, + stats.exact, + stats.mismatch, + stats.first_difference_in_output_signature, + stats.output_signature_mismatches_evaluated + )); + } text.push_str("\n## Encoding-error diagnostics\n\n"); text.push_str( @@ -1989,6 +2079,27 @@ mod tests { assert!(ranges[0].end <= actual.chars().count()); } + #[rstest::rstest] + #[case::tight("△보성군", 1)] + #[case::embedded("목록 △교과전형", 1)] + #[case::spaced("△ 보성군", 0)] + #[case::repeated_omission("△△ 종목", 0)] + #[case::square_bullet("□2021", 0)] + fn detects_tight_triangle_before_korean(#[case] input: &str, #[case] expected: usize) { + assert_eq!(tight_triangle_positions(input).len(), expected); + } + + #[test] + fn locates_tight_triangle_and_first_korean_output() { + let input = "△보성군"; + let actual = braillify::encode_to_unicode(input).expect("probe must encode"); + let ranges = tight_triangle_actual_ranges(input, &actual); + + assert_eq!(ranges.len(), 1); + assert!(ranges[0].start < ranges[0].end); + assert!(ranges[0].end <= actual.chars().count()); + } + #[test] fn aggregates_headword_expansion_outcomes_without_reclassification() { let cases = [ From 88c3dea7f53a1ceb7e1ad94e44b59e545dd98953 Mon Sep 17 00:00:00 2001 From: devfive Date: Tue, 1 Sep 2026 07:15:31 +0900 Subject: [PATCH 16/77] Localize pronunciation-sensitive OU corpus mismatches --- docs/corpus-analysis/NIKL_2025_V1.md | 334 +++++++++++++++++- .../braillify/examples/nikl_corpus_analyze.rs | 320 ++++++++++++++++- 2 files changed, 649 insertions(+), 5 deletions(-) diff --git a/docs/corpus-analysis/NIKL_2025_V1.md b/docs/corpus-analysis/NIKL_2025_V1.md index 21096cb8..92050e91 100644 --- a/docs/corpus-analysis/NIKL_2025_V1.md +++ b/docs/corpus-analysis/NIKL_2025_V1.md @@ -1,6 +1,6 @@ # NIKL 2025 v1.0 corpus analysis -> Generated by `cargo run --release -p braillify --example nikl_corpus_analyze`. The tool reads only `input` and `unicode`; it never loads or compares `world`. +> Generated by `cargo run --release -p braillify --example nikl_corpus_analyze`. The tool reads only `input` and `unicode`; it never loads or compares the read-only `world` or `jeomsarang` fields. ## Current measurement @@ -33,12 +33,270 @@ Primary classes are evidence gates, not permissions to change the engine. `imple | `punctuation_rule_review` | 8 | | `unsupported_character_review` | 191 | +## Pending first-difference cell transitions + +This ranking is a diagnostic selector, not an implementation rule. It counts only current `pending_rule_review` cases whose encoder call succeeded, keyed by the expected and actual cell at the sentence's first differing position. Candidate implementation work must still bind a transition to a localized input structure, exact controls, and independent PDF evidence. + +| Rank | Expected → actual first cell | Cases | +|---:|---|---:| +| 1 | `U+2834 ⠴ -> U+2800 ⠀` | 2137 | +| 2 | `U+2815 ⠕ -> U+2833 ⠳` | 1311 | +| 3 | `U+2834 ⠴ -> U+2826 ⠦` | 1119 | +| 4 | `U+2830 ⠰ -> U+2820 ⠠` | 1015 | +| 5 | `U+2820 ⠠ -> U+2830 ⠰` | 790 | +| 6 | `U+2810 ⠐ -> U+2832 ⠲` | 656 | +| 7 | `U+2832 ⠲ -> U+2834 ⠴` | 654 | +| 8 | `U+283C ⠼ -> U+2800 ⠀` | 466 | +| 9 | `U+2820 ⠠ -> U+2834 ⠴` | 453 | +| 10 | `U+2826 ⠦ -> U+2834 ⠴` | 446 | +| 11 | `U+280E ⠎ -> U+280C ⠌` | 443 | +| 12 | `U+2826 ⠦ -> U+2800 ⠀` | 422 | +| 13 | `U+2808 ⠈ -> U+2832 ⠲` | 409 | +| 14 | `U+2801 ⠁ -> U+281C ⠜` | 381 | +| 15 | `U+2800 ⠀ -> U+2807 ⠇` | 360 | +| 16 | `U+2820 ⠠ -> U+2832 ⠲` | 357 | +| 17 | `U+2834 ⠴ -> U+2830 ⠰` | 321 | +| 18 | `U+2811 ⠑ -> U+282B ⠫` | 320 | +| 19 | `U+2832 ⠲ -> U+2838 ⠸` | 275 | +| 20 | `U+2800 ⠀ -> U+2832 ⠲` | 252 | + +### `U+2834 ⠴ -> U+2800 ⠀` + +- `sentence_01.json` #18: 2023년형 신제품은 매터(Matter)와 HCA(Home Connectivity Alliance) 표준을 지원하는 스마트싱스 허브(SmartThings Hub)를 기반으로 다양한 기기를 자동으로 연결하고, 제어·관리할 수 있는 것이 특징이다. + - expected: `⠁⠞⠞⠻⠠⠴⠧⠀⠴⠠⠠⠓⠉⠁⠐⠣⠠⠓⠕⠍⠑⠀⠠⠒` + - actual: `⠁⠞⠞⠻⠠⠴⠧⠀⠀⠠⠠⠓⠉⠁⠦⠠⠓⠕⠍⠑⠀⠴⠠⠒` + - first differing cell (zero-based): 37 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_02.json` #186: 3일 사회관계망서비스(SNS)를 통해 유포된 동영상에는 중학생인 A(14)양이 지난달 30일 태안의 한 지하 주차장에서 B(15)양으로부터 일방적으로 폭행을 당하고 주변에 있던 학생들은 이를 웃으며 방관하는 장면이 담겼다. + - expected: `⠶⠚⠁⠠⠗⠶⠟⠀⠴⠠⠁⠦⠄⠼⠁⠙⠠⠴⠜⠶⠕⠀⠨⠕` + - actual: `⠶⠚⠁⠠⠗⠶⠟⠀⠀⠁⠦⠼⠁⠙⠴⠀⠀⠜⠶⠕⠀⠨⠕⠉` + - first differing cell (zero-based): 65 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_03.json` #51: 20일 한국장기조직기증원에 따르면 A(11)군은 지난 3일 학교에 가기 위해 횡단보도를 건너다가 시내버스에 치여 병원으로 이송돼 치료받았지만 회복하지 못하고 뇌사 상태에 빠졌다. + - expected: `⠀⠠⠊⠐⠪⠑⠡⠀⠴⠠⠁⠦⠄⠼⠁⠁⠠⠴⠈⠛⠵⠀⠨⠕` + - actual: `⠀⠠⠊⠐⠪⠑⠡⠀⠀⠁⠦⠼⠁⠁⠴⠀⠀⠈⠛⠵⠀⠨⠕⠉` + - first differing cell (zero-based): 36 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_04.json` #323: 사단급 규모의 상륙군과 대형수송함(LPH) 독도함, 미 강습상륙함(LHD) 마킨 아일랜드함 등 30여척, 미 F-35 전투기·육군 아파치 공격헬기(AH-64)·마린온 상륙기동헬기 등 항공기 70여대, 상륙돌격장갑차(KAAV) 50여대 등 연합·합동전력이 투입됐다. + - expected: `⠱⠰⠹⠐⠀⠑⠕⠀⠴⠠⠋⠤⠼⠉⠑⠀⠨⠾⠓⠍⠈⠕⠐⠆` + - actual: `⠱⠰⠹⠐⠀⠑⠕⠀⠀⠠⠋⠔⠼⠉⠑⠀⠨⠾⠓⠍⠈⠕⠐⠆` + - first differing cell (zero-based): 108 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` + +### `U+2815 ⠕ -> U+2833 ⠳` + +- `sentence_01.json` #47: 다날은 계열사 ‘제프’가 국내 대체불가토큰(NFT) 거래소를 운영하는 ‘팔라’와 메타버스·NFT 협력 관련 협약(MOU)을 맺고 메타버스 플랫폼 ‘제프월드’의 인프라 확대를 추진한다고 3일 밝혔다. + - expected: `⠜⠁⠦⠄⠴⠠⠠⠍⠕⠥⠠⠴⠮⠀⠑⠗⠅⠈⠥⠀⠑⠝⠓⠘` + - actual: `⠜⠁⠦⠄⠴⠠⠠⠍⠳⠠⠴⠮⠀⠑⠗⠅⠈⠥⠀⠑⠝⠓⠘⠎` + - first differing cell (zero-based): 114 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_02.json` #128: 이번 방문에서 대표단은 우수 외투기업과 투자협약(MOU)을 체결하고 투자 상담, 기업정보 교류 등 적극적인 외자 유치 활동을 펼칠 계획이다. + - expected: `⠜⠁⠦⠄⠴⠠⠠⠍⠕⠥⠠⠴⠮⠀⠰⠝⠈⠳⠚⠈⠥⠀⠓⠍` + - actual: `⠜⠁⠦⠄⠴⠠⠠⠍⠳⠠⠴⠮⠀⠰⠝⠈⠳⠚⠈⠥⠀⠓⠍⠨` + - first differing cell (zero-based): 48 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_03.json` #1: 김 지사는 18일(이하 현지시각) 미국 코네티컷주 댄버리 린데 본사에서 산지브 람바(Sanjiv Lamba) 린데 회장, 성백석 린데코리아 회장, 조일교 아산시 부시장과 투자양해각서(MOU)을 체결했다. + - expected: `⠠⠎⠦⠄⠴⠠⠠⠍⠕⠥⠠⠴⠮⠀⠰⠝⠈⠳⠚⠗⠌⠊⠲` + - actual: `⠠⠎⠦⠄⠴⠠⠠⠍⠳⠠⠴⠮⠀⠰⠝⠈⠳⠚⠗⠌⠊⠲` + - first differing cell (zero-based): 175 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_04.json` #195: HD현대는 23일 서울시 성북구 고려대학교 본관에서 HD현대 정기선 사장, 고려대학교 김동원 총장 등 관계자가 참석한 가운데 고려대학교와 ‘미래 인재 육성 산학협력을 위한 업무협약(MOU)’을 체결했다고 밝혔다. + - expected: `⠜⠁⠦⠄⠴⠠⠠⠍⠕⠥⠠⠴⠴⠄⠮⠀⠰⠝⠈⠳⠚⠗⠌⠊` + - actual: `⠜⠁⠦⠄⠴⠠⠠⠍⠳⠠⠴⠴⠄⠮⠀⠰⠝⠈⠳⠚⠗⠌⠊⠈` + - first differing cell (zero-based): 185 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` + +### `U+2834 ⠴ -> U+2826 ⠦` + +- `sentence_01.json` #14: 새롭게 선보인 DRX 브랜딩과 팀 컬러는 2023년 1월 1일부터 DRX의 온오프라인 콘텐츠, 엠디(MD), SNS, 유튜브 등 모든 제작물에 적용될 예정이다. + - expected: `⠰⠪⠐⠀⠝⠢⠊⠕⠴⠐⠣⠠⠠⠍⠙⠐⠜⠂⠀⠠⠠⠎⠝⠎` + - actual: `⠰⠪⠐⠀⠝⠢⠊⠕⠦⠄⠴⠠⠠⠍⠙⠠⠴⠐⠀⠰⠠⠠⠎⠝` + - first differing cell (zero-based): 97 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_02.json` #605: 전국적으로 강력범죄가 연이어 발생하면서 천안시가 시민 안전을 위해 공원, 산책로, 등산로 등에 설치된 방범용 폐쇄회로(CC)TV 전수조사를 실시한다. + - expected: `⠌⠠⠧⠗⠚⠽⠐⠥⠴⠐⠣⠠⠠⠉⠉⠐⠜⠠⠠⠞⠧⠲⠀⠨` + - actual: `⠌⠠⠧⠗⠚⠽⠐⠥⠦⠄⠴⠠⠉⠠⠉⠠⠴⠰⠠⠠⠞⠧⠲⠀` + - first differing cell (zero-based): 115 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_03.json` #37: 경찰은 주변 폐쇄회로(CC)TV 등을 분석해 A씨 동선을 추적했고 14일 오후 경기도 양주의 한 주택에서 A씨를 긴급체포했다. + - expected: `⠌⠠⠧⠗⠚⠽⠐⠥⠴⠐⠣⠠⠠⠉⠉⠐⠜⠠⠠⠞⠧⠲⠀⠊` + - actual: `⠌⠠⠧⠗⠚⠽⠐⠥⠦⠄⠴⠠⠉⠠⠉⠠⠴⠰⠠⠠⠞⠧⠲⠀` + - first differing cell (zero-based): 21 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_04.json` #42: 경찰은 폐쇄회로(CC)TV 분석과 쇠구슬 판매 업체 탐문 수사, 국과수 발사지점 방향성 감정 등을 통해 발사 의심 세대를 특정해 이날 피의자 A씨를 자택에서 검거했다. + - expected: `⠌⠠⠧⠗⠚⠽⠐⠥⠴⠐⠣⠠⠠⠉⠉⠐⠜⠠⠠⠞⠧⠲⠀⠘` + - actual: `⠌⠠⠧⠗⠚⠽⠐⠥⠦⠄⠴⠠⠉⠠⠉⠠⠴⠰⠠⠠⠞⠧⠲⠀` + - first differing cell (zero-based): 16 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` + +### `U+2830 ⠰ -> U+2820 ⠠` + +- `sentence_01.json` #310: 낸드플래시 반도체 세계 2위인 일본 키옥시아와 4위인 미국 웨스턴디지털(WD) 합병설에 반도체 업계가 촉각을 곤두 세우고 있다. + - expected: `⠕⠨⠕⠓⠞⠦⠄⠴⠰⠠⠠⠺⠙⠠⠴⠀⠚⠃⠘⠻⠠⠞⠝⠀` + - actual: `⠕⠨⠕⠓⠞⠦⠄⠴⠠⠠⠺⠙⠠⠴⠀⠚⠃⠘⠻⠠⠞⠝⠀⠘` + - first differing cell (zero-based): 70 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_02.json` #99: 황승주 씨는 “기능성 소화불량증, 특히 PDS(식후불편감증후군) 복부팽만감 유형으로 고통 받는 환자들에게 적용가능한 안전하고 높은 치료효율의 한약소재를 개발해 국민건강에 이바지하고자 한다”고 말했다. + - expected: `⠀⠓⠪⠁⠚⠕⠀⠴⠰⠠⠠⠏⠙⠎⠦⠄⠠⠕⠁⠚⠍⠘⠯⠙` + - actual: `⠀⠓⠪⠁⠚⠕⠀⠴⠠⠠⠏⠙⠎⠦⠄⠠⠕⠁⠚⠍⠘⠯⠙⠡` + - first differing cell (zero-based): 45 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_03.json` #30: “챗GPT보다 가벼운 여러 대규모언어모델(LLM)이 출현해 오픈AI 독점 구도에 균열을 내고 있다. 한국어 특화 LLM으로 글로벌 경쟁에 대응하면서 세부 서비스(튜닝) 모델에서 차별성을 확보하는 전략이 중요하다.” + - expected: `⠑⠥⠊⠝⠂⠦⠄⠴⠰⠠⠠⠇⠇⠍⠠⠴⠕⠀⠰⠯⠚⠡⠚⠗` + - actual: `⠑⠥⠊⠝⠂⠦⠄⠴⠠⠠⠇⠇⠍⠠⠴⠕⠀⠰⠯⠚⠡⠚⠗⠀` + - first differing cell (zero-based): 40 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_04.json` #81: SGI는 “산업연관분석을 활용해 우리나라의 대일 수출 증가가 국내총생산(GDP)에 미치는 영향을 계산해 보면 경제성장률은 0.1%포인트 높아질 것”이라고 밝혔다. + - expected: `⠠⠗⠶⠇⠒⠦⠄⠴⠰⠠⠠⠛⠙⠏⠠⠴⠝⠀⠑⠕⠰⠕⠉⠵` + - actual: `⠠⠗⠶⠇⠒⠦⠄⠴⠠⠠⠛⠙⠏⠠⠴⠝⠀⠑⠕⠰⠕⠉⠵⠀` + - first differing cell (zero-based): 72 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` + +### `U+2820 ⠠ -> U+2830 ⠰` + +- `sentence_01.json` #25: 세븐일레븐은 올해 말 정식 버전 오픈을 목표로 하며, 이를 통해 주력상품을 구매하고 배송까지 받을 수 있는 차세대 오프라인을 위한 온라인(O4O) 시스템을 구축해 나갈 계획이다. + - expected: `⠟⠦⠄⠴⠠⠕⠼⠙⠠⠕⠠⠴⠀⠠⠕⠠⠪⠓⠝⠢⠮⠀⠈⠍` + - actual: `⠟⠦⠄⠴⠠⠕⠼⠙⠰⠠⠕⠠⠴⠀⠠⠕⠠⠪⠓⠝⠢⠮⠀⠈` + - first differing cell (zero-based): 129 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_02.json` #313: 순천향대(총장 김승우)는 지난 3일 한국국제협력단(KOICA)과 함께 우즈베키스탄의 수도 타슈겐트에서 스타트업 지원센터 ‘U-ENTER(Uzbekistan Entrepreneurship Innovation Center)’ 준공식을 개최했다고 밝혔다. + - expected: `⠎⠀⠠⠦⠴⠠⠥⠤⠠⠠⠢⠞⠻⠐⠣⠠⠥⠵⠃⠑⠅⠊⠌⠁` + - actual: `⠎⠀⠠⠦⠴⠠⠥⠤⠰⠠⠠⠢⠞⠻⠐⠣⠠⠥⠵⠃⠑⠅⠊⠌` + - first differing cell (zero-based): 131 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_03.json` #47: 이수화학이 미국 과학 기술 엔지니어링 회사 KBR과 전고체 배터리 소재 황화리튬(Li2S)의 상업공정 공동 개발에 나선다. 양사는 이를 위해 공동개발 계약을 체결했다고 20일 밝혔다. + - expected: `⠦⠄⠴⠠⠇⠊⠼⠃⠠⠎⠠⠴⠺⠀⠇⠶⠎⠃⠈⠿⠨⠻⠀⠈` + - actual: `⠦⠄⠴⠠⠇⠊⠼⠃⠰⠠⠎⠠⠴⠺⠀⠇⠶⠎⠃⠈⠿⠨⠻⠀` + - first differing cell (zero-based): 87 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_04.json` #1097: LG유플러스는 5세대 이동통신(5G)과 차량·사물통신(V2X)을 바탕으로 도시환경관리에 최적화된 자율주행 차량 관제시스템을 개발한 뒤 자율주행 기술을 실증할 도시인 ‘리빙랩’에서 4만㎞ 이상 서비스 실증에 나선다. + - expected: `⠟⠦⠄⠴⠠⠧⠼⠃⠠⠭⠠⠴⠮⠀⠘⠓⠶⠪⠐⠥⠀⠊⠥⠠` + - actual: `⠟⠦⠄⠴⠠⠧⠼⠃⠰⠠⠭⠠⠴⠮⠀⠘⠓⠶⠪⠐⠥⠀⠊⠥` + - first differing cell (zero-based): 63 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` + +### `U+2810 ⠐ -> U+2832 ⠲` + +- `sentence_01.json` #57: 아키에이지 워는 원작 아키에이지 개발사 엑스엘게임즈(각자대표 송재경, 최관호)가 개발 중인 신작 PC·모바일 크로스플랫폼 대규모다중접속역할수행게임(MMORPG)이다. 원작의 향수를 자극하는 스토리와 캐릭터, 언리얼 엔진4를 활용한 고퀄리티 그래픽이 특징이다. + - expected: `⠨⠁⠀⠴⠠⠠⠏⠉⠐⠆⠑⠥⠘⠣⠕⠂⠀⠋⠪⠐⠥⠠⠪⠙` + - actual: `⠨⠁⠀⠴⠠⠠⠏⠉⠲⠐⠆⠑⠥⠘⠣⠕⠂⠀⠋⠪⠐⠥⠠⠪` + - first differing cell (zero-based): 92 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_02.json` #10: 설동호 대전교육감은 “학교 교육과정 및 여건에 맞는 인공지능(AI)교육 선도학교 운영을 통해 AI·SW교육 기반 구축과 학생들과 교원들의 미래 디지털 역량이 함양될 것이다.”라고 말했다. + - expected: `⠚⠗⠀⠴⠠⠠⠁⠊⠐⠆⠴⠠⠠⠎⠺⠲⠈⠬⠩⠁⠀⠈⠕⠘` + - actual: `⠚⠗⠀⠴⠠⠠⠁⠊⠲⠐⠆⠴⠎⠺⠲⠈⠬⠩⠁⠀⠈⠕⠘⠒` + - first differing cell (zero-based): 93 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_03.json` #17: 재테크 시장에서 온·오프라인 플랫폼 ‘매경 엠플러스(M+)’가 주목받고 있다. M+는 거시경제, 주식, 부동산, 가상자산 등 재테크의 모든 것을 온라인 사이트를 통해 제공하고 있다. + - expected: `⠎⠠⠪⠦⠄⠴⠠⠍⠐⠖⠠⠴⠴⠄⠫⠀⠨⠍⠑⠭⠘⠔⠈⠥` + - actual: `⠎⠠⠪⠦⠄⠴⠠⠍⠲⠀⠢⠀⠠⠴⠴⠄⠫⠀⠨⠍⠑⠭⠘⠔` + - first differing cell (zero-based): 54 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_04.json` #49: 유럽중앙은행(ECB)이 크레디트스위스(CS)의 유동성 위기에도 ‘빅스텝’(기준금리를 한 번에 0.5%포인트 인상)을 단행하자, 미국 연방준비제도(Fed·연준) 역시 금리 인상을 멈추지 않을 것이라는 전망이 힘을 얻고 있다. + - expected: `⠊⠥⠦⠄⠴⠠⠋⠫⠐⠆⠡⠨⠛⠠⠴⠀⠱⠁⠠⠕⠀⠈⠪⠢` + - actual: `⠊⠥⠦⠄⠴⠠⠋⠫⠲⠐⠆⠡⠨⠛⠠⠴⠀⠱⠁⠠⠕⠀⠈⠪` + - first differing cell (zero-based): 149 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` + +### `U+2832 ⠲ -> U+2834 ⠴` + +- `sentence_01.json` #75: 특히 MKT의 매출이익률이 42.2%에 달했는데, 이는 경쟁사 대비 12.6%포인트(P) 높은 수준이다. 검찰은 한국타이어가 MKT에 몰아준 이익이 한국타이어 총수 일가에 흘러간 것으로 의심하고 있다. + - expected: `⠊⠗⠘⠕⠀⠼⠁⠃⠲⠋⠴⠏⠀⠙⠥⠟⠓⠪⠦⠄⠴⠠⠏⠠` + - actual: `⠊⠗⠘⠕⠀⠼⠁⠃⠴⠲⠋⠴⠏⠀⠙⠥⠟⠓⠪⠦⠄⠴⠠⠏` + - first differing cell (zero-based): 67 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_02.json` #561: 훈련 동안 대전경찰청 주관으로 충무로4가~동부서 3가(1.0km), 큰 마을 4가~용문역(2km), 사이언스빌 3가~읍내 4가(2.2km) 총 3개 구간이 통제된다. + - expected: `⠀⠼⠉⠫⠦⠄⠼⠁⠲⠚⠴⠅⠍⠠⠴⠐⠀⠋⠵⠀⠑⠣⠮⠀` + - actual: `⠀⠼⠉⠫⠦⠄⠼⠁⠴⠲⠚⠰⠅⠍⠠⠴⠐⠀⠋⠵⠀⠑⠣⠮` + - first differing cell (zero-based): 57 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_03.json` #366: 하루 최대 34.3리터(L)의 대용량 제습이 가능하며, 자연 증발 시스템이 적용돼 물통에 모인 물을 비워야 하는 번거로움이 없다. + - expected: `⠰⠽⠊⠗⠀⠼⠉⠙⠲⠉⠐⠕⠓⠎⠦⠄⠴⠠⠇⠠⠴⠺⠀⠊` + - actual: `⠰⠽⠊⠗⠀⠼⠉⠙⠴⠲⠉⠐⠕⠓⠎⠦⠄⠴⠠⠇⠠⠴⠺⠀` + - first differing cell (zero-based): 12 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_04.json` #48: 올트먼은 GPT-4가 기존 ‘GPT-3.5’보다 정확도가 40% 이상 높아졌지만, 이를 정보의 주요 출처로 사용해서는 안된다고도 지적했다. 거짓을 사실처럼 보이게 하는 이른바 ‘환각(hallucination) 현상’에 주의해야 한다는 입장이다. + - expected: `⠠⠠⠛⠏⠞⠤⠼⠉⠲⠑⠴⠄⠘⠥⠊⠀⠨⠻⠚⠧⠁⠊⠥⠫` + - actual: `⠠⠠⠛⠏⠞⠤⠼⠉⠴⠲⠑⠴⠄⠘⠥⠊⠀⠨⠻⠚⠧⠁⠊⠥` + - first differing cell (zero-based): 35 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` + +### `U+283C ⠼ -> U+2800 ⠀` + +- `sentence_01.json` #426: LG에너지솔루션은 혼다에 앞서 미국 제너럴모터스(GM)과의 배터리 합작법인 ‘얼티엄셀즈’를 설립해 투자를 진행 중이다. 이를 통해 북미 생산 능력을 올해 13GWh 규모에서 2025년 255GWh까지 확대할 예정이다. + - expected: `⠁⠮⠀⠥⠂⠚⠗⠀⠼⠁⠉⠴⠠⠛⠠⠺⠓⠲⠀⠈⠩⠑⠥⠝` + - actual: `⠁⠮⠀⠥⠂⠚⠗⠀⠀⠼⠁⠉⠠⠠⠛⠺⠓⠀⠀⠈⠩⠑⠥⠝` + - first differing cell (zero-based): 156 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_02.json` #68: 조성된 무장애나눔길은 통행이 많은 숲속의집~생태숲학습관 0.73km 구간에 평균 경사도 5% 이하 데크산책로와 황토포장길로 조성됐으며 전국 무장애나눔길 최초로 장애물 없는 생활환경(BF)인증을 추진하고 있다. + - expected: `⠁⠠⠪⠃⠈⠧⠒⠀⠼⠚⠲⠛⠉⠴⠅⠍⠲⠀⠈⠍⠫⠒⠝⠀` + - actual: `⠁⠠⠪⠃⠈⠧⠒⠀⠀⠼⠚⠲⠛⠉⠅⠍⠀⠀⠈⠍⠫⠒⠝⠀` + - first differing cell (zero-based): 62 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_03.json` #59: 이들 중 A씨(31) 등 2명은 지난 3월 25일 김해공항으로 필로폰 968g 등을 팬티 속에 숨겨 입국한 혐의를 받고 있다. + - expected: `⠙⠕⠂⠐⠥⠙⠷⠀⠼⠊⠋⠓⠴⠛⠲⠀⠊⠪⠶⠮⠀⠙⠗⠒` + - actual: `⠙⠕⠂⠐⠥⠙⠷⠀⠀⠼⠊⠋⠓⠛⠀⠀⠊⠪⠶⠮⠀⠙⠗⠒` + - first differing cell (zero-based): 71 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_04.json` #57: 미국지질조사국(USGS)에 따르면 이날 에콰도르 항구도시 과야킬에서 남쪽으로 약 80km 떨어진 지점에서 규모 6.8의 지진이 발생했다. 지진의 깊이는 66㎞다. + - expected: `⠭⠪⠐⠥⠀⠜⠁⠀⠼⠓⠚⠴⠅⠍⠲⠀⠠⠊⠞⠎⠨⠟⠀⠨` + - actual: `⠭⠪⠐⠥⠀⠜⠁⠀⠀⠼⠓⠚⠐⠅⠍⠀⠀⠠⠊⠞⠎⠨⠟⠀` + - first differing cell (zero-based): 79 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` + +### `U+2820 ⠠ -> U+2834 ⠴` + +- `sentence_01.json` #78: 2023년형 Neo QLED는 스마트싱스뿐 아니라 매터(Matter) 기기까지 지원하는 원칩 모듈을 탑재해 별도의 동글을 사용하지 않아도, 다양한 사물인터넷(IoT) 기기를 직접 TV에 연결해 사용할 수 있다. + - expected: `⠻⠀⠴⠠⠝⠑⠕⠀⠠⠠⠟⠇⠑⠙⠲⠉⠵⠀⠠⠪⠑⠓⠪⠠` + - actual: `⠻⠀⠴⠠⠝⠑⠕⠀⠴⠠⠠⠟⠇⠫⠲⠉⠵⠀⠠⠪⠑⠓⠪⠠` + - first differing cell (zero-based): 17 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_02.json` #332: 홍성군청소년수련관(관장 피기용) 진로체험지원센터가 오는 16일 청운대학교 운동장에서 관내 중학교 1학년 대상으로 제9회 홍성군 청소년 진로박람회 ‘꿈(JOB)고 두드림(DO DREAM)’을 진행한다. + - expected: `⠦⠄⠴⠠⠠⠙⠕⠀⠠⠠⠙⠗⠂⠍⠠⠴⠴⠄⠮⠀⠨⠟⠚⠗` + - actual: `⠦⠄⠴⠠⠠⠙⠕⠀⠴⠠⠠⠙⠗⠂⠍⠠⠴⠴⠄⠮⠀⠨⠟⠚` + - first differing cell (zero-based): 183 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_03.json` #302: 건국대 경영전문대학원(KU MBA)은 2015년부터 국제경영대학발전협의회(AACSB) 인증을 보유하고 있다. 고등교육법 시행령에 따라 교육부가 인가한 13개 경영전문대학원 중 하나다. 경영전문대학원인 만큼 엄격하게 교육 품질을 관리한다. + - expected: `⠦⠄⠴⠠⠠⠅⠥⠀⠠⠠⠍⠃⠁⠠⠴⠵⠀⠼⠃⠚⠁⠑⠀⠉` + - actual: `⠦⠄⠴⠠⠠⠅⠥⠀⠴⠠⠠⠍⠃⠁⠠⠴⠵⠀⠼⠃⠚⠁⠑⠀` + - first differing cell (zero-based): 29 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_04.json` #363: 켈리(KELLY)는 ‘KEEP NATUALLY’의 줄임말로 인위적인 것을 최소화하고 자연주의적인 원료, 공법, 맛을 추구한다는 의미를 담고 있다. + - expected: `⠴⠠⠠⠅⠑⠑⠏⠀⠠⠠⠝⠁⠞⠥⠁⠇⠇⠽⠴⠄⠺⠀⠨⠯` + - actual: `⠴⠠⠠⠅⠑⠑⠏⠀⠴⠠⠠⠝⠁⠞⠥⠁⠇⠇⠽⠴⠄⠺⠀⠨` + - first differing cell (zero-based): 30 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` + +### `U+2826 ⠦ -> U+2834 ⠴` + +- `sentence_01.json` #38: 올해 소프트웨어(SW) 업계는 국내 인공지능(AI) 제품·서비스 고도화와 함께 전 산업영역에서 SW영역 확대가 가시화될 것으로 보인다. 또한 블록체인이 웹3(Web3)와 연계해 탈중앙화·탈독점화 경제체계로 진화할 것으로 예상된다. + - expected: `⠟⠕⠀⠏⠗⠃⠼⠉⠦⠄⠴⠠⠺⠑⠃⠼⠉⠠⠴⠧⠀⠡⠈⠌` + - actual: `⠟⠕⠀⠏⠗⠃⠼⠉⠴⠐⠣⠠⠺⠑⠃⠼⠉⠴⠐⠜⠲⠧⠀⠡` + - first differing cell (zero-based): 155 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_02.json` #38: 중국금형전시회(DMC) 및 독일금형전시회 (Moulding Expo)와 함께 세계 3대 규모의 금형산업 전문 전시회이며, 연관산업 전문 전시회를 KOPLAS와 동시 개최를 통한 시너지를 창출했다. + - expected: `⠻⠨⠾⠠⠕⠚⠽⠀⠦⠄⠴⠠⠍⠳⠇⠙⠬⠀⠠⠑⠭⠏⠕⠠` + - actual: `⠻⠨⠾⠠⠕⠚⠽⠀⠴⠐⠣⠠⠍⠳⠇⠙⠬⠀⠠⠑⠭⠏⠕⠐` + - first differing cell (zero-based): 48 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_03.json` #536: 뿐만아니라 윤 대통령은 조 바이든 미국 대통령과 26일(현지시간) 한미 정상회담에서 ‘워싱턴 선언’(Washington Declaration)을 통해 핵협의그룹(NCG)을 신설하는 등 확장억제력을 대폭 강화하는 데 합의했다. + - expected: `⠓⠾⠀⠠⠾⠾⠴⠄⠦⠄⠴⠠⠺⠁⠩⠬⠞⠕⠝⠀⠠⠙⠑⠉` + - actual: `⠓⠾⠀⠠⠾⠾⠴⠄⠴⠐⠣⠠⠺⠁⠩⠬⠞⠕⠝⠀⠠⠙⠑⠉` + - first differing cell (zero-based): 95 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_04.json` #582: 반면 마이크로소프트(MS) 공동창업자인 빌 게이츠는 “(AI 개발 일시 중단은) 문제를 해결하지 못할 것”이라며 “우리가 해야 할 일은 AI의 까다로운 문제들을 파악하는 것”이라고 말했다. + - expected: `⠝⠕⠰⠪⠉⠵⠀⠦⠦⠄⠴⠠⠠⠁⠊⠲⠀⠈⠗⠘⠂⠀⠕⠂` + - actual: `⠝⠕⠰⠪⠉⠵⠀⠦⠴⠐⠣⠠⠠⠁⠊⠲⠀⠈⠗⠘⠂⠀⠕⠂` + - first differing cell (zero-based): 54 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` + ## Cross-cutting input-only structural cohorts -These are cross-cutting input-only structural cohorts, not new primary classes and not engine routing rules. Candidate selection never changes a case's existing primary class. Only cohort members already classified as `pending_rule_review` form a pending subcluster; exact and other-primary members are controls that retain their existing outcomes. The `uppercase_roman_headword_closed_multiword_parenthetical` gate requires a two-or-more character uppercase ASCII headword immediately followed by a closed parenthesis whose contents are two or more ASCII Roman words separated only by spaces. Because the contents admit only letters and spaces, visible operators, subscript/superscript notation, and nested parentheses are excluded deterministically. The `standalone_multi_character_uppercase_roman_word` gate finds maximal ASCII-letter runs of two or more capitals with non-alphanumeric boundaries; a run immediately followed by `(` is excluded so the HCA-style headword itself is not counted by both gates. The `korean_prefixed_closed_allcaps_parenthetical` gate requires an immediately preceding Korean character and a closed body of two or more uppercase ASCII letters. It intentionally contains both acronym annotations (`책임자(COO)`) and scientific formulae (`일산화탄소(CO)`) so their semantic collision remains measurable. The `multi_character_allcaps_roman_runs_joined_by_middle_dot` gate requires two maximal ASCII-letter runs of at least two capitals joined directly by U+00B7, with non-alphanumeric outer boundaries. It records shapes such as `AI·SW` without assigning prose, mathematics, or science semantics. The `korean_inline_parenthesized_single_arithmetic_operator` gate requires an immediate `Korean(` + one rule-45 arithmetic operator + `)Korean` span. Unlike broad coexistence traits, it also locates the current engine's emitted structure and counts a mismatch as signature-local only when the sentence's first differing cell falls inside that output range. The `tight_triangle_mark_immediately_before_korean` gate requires literal `△한글` with no input space and includes the first following Korean cell in its localized output range, so an observed missing-space difference is measured at the mark boundary. +These are cross-cutting input-only structural cohorts, not new primary classes and not engine routing rules. Candidate selection never changes a case's existing primary class. Only cohort members already classified as `pending_rule_review` form a pending subcluster; exact and other-primary members are controls that retain their existing outcomes. The `uppercase_roman_headword_closed_multiword_parenthetical` gate requires a two-or-more character uppercase ASCII headword immediately followed by a closed parenthesis whose contents are two or more ASCII Roman words separated only by spaces. Because the contents admit only letters and spaces, visible operators, subscript/superscript notation, and nested parentheses are excluded deterministically. The `standalone_multi_character_uppercase_roman_word` gate finds maximal ASCII-letter runs of two or more capitals with non-alphanumeric boundaries; a run immediately followed by `(` is excluded so the HCA-style headword itself is not counted by both gates. The `korean_prefixed_closed_allcaps_parenthetical` gate requires an immediately preceding Korean character and a closed body of two or more uppercase ASCII letters. It intentionally contains both acronym annotations (`책임자(COO)`) and scientific formulae (`일산화탄소(CO)`) so their semantic collision remains measurable. The `multi_character_allcaps_roman_runs_joined_by_middle_dot` gate requires two maximal ASCII-letter runs of at least two capitals joined directly by U+00B7, with non-alphanumeric outer boundaries. It records shapes such as `AI·SW` without assigning prose, mathematics, or science semantics. The `korean_inline_parenthesized_single_arithmetic_operator` gate requires an immediate `Korean(` + one rule-45 arithmetic operator + `)Korean` span. Unlike broad coexistence traits, it also locates the current engine's emitted structure and counts a mismatch as signature-local only when the sentence's first differing cell falls inside that output range. The `allcaps_roman_run_containing_ou` gate finds maximal, alphanumeric-delimited uppercase ASCII runs containing adjacent `OU`. It locates the independently encoded run signature in the complete current output and counts only first differences inside that signature as localized. The `tight_triangle_mark_immediately_before_korean` gate requires literal `△한글` with no input space and includes the first following Korean cell in its localized output range, so an observed missing-space difference is measured at the mark boundary. | Cluster | Candidates | Exact | Mismatch | Conflicting-reference cases | |---|---:|---:|---:|---:| +| `allcaps_roman_run_containing_ou` | 1816 | 55 | 1761 | 0 | | `korean_inline_parenthesized_single_arithmetic_operator` | 23 | 17 | 6 | 0 | | `korean_prefixed_closed_allcaps_parenthetical` | 54492 | 43779 | 10713 | 0 | | `multi_character_allcaps_roman_runs_joined_by_middle_dot` | 97 | 0 | 97 | 0 | @@ -46,12 +304,76 @@ These are cross-cutting input-only structural cohorts, not new primary classes a | `tight_triangle_mark_immediately_before_korean` | 377 | 0 | 377 | 0 | | `uppercase_roman_headword_closed_multiword_parenthetical` | 175 | 15 | 160 | 0 | +### `allcaps_roman_run_containing_ou` + +Of the 1816 candidates, 1684 are the actual `pending_rule_review` subcluster. The other 132 candidates are exact or existing non-pending-primary controls; this cohort does not reclassify them. + +For this output-signature audit, 1761 mismatches were evaluable and 1307 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. + +Localized first-difference transitions: + +- `U+2815 ⠕ -> U+2833 ⠳`: 1306 +- `U+2810 ⠐ -> U+283D ⠽`: 1 + +Mismatch primary-class distribution: + +- `comparison_method`: 2 +- `pending_rule_review`: 1684 +- `unsupported_character_review`: 75 + +Representative `exact` samples: + +- `sentence_01.json` #850: 신규 보급 단말기는 앞서 국내 신용카드사들이 합작해 만든 근거리 무선 통신(NFC) 단말기 결제 규격인 ‘저스터치(JUSTOUCH)’와 호환성을 갖춰야 한다. + - expected: `⠠⠟⠈⠩⠀⠘⠥⠈⠪⠃⠀⠊⠒⠑⠂⠈⠕⠉⠵⠀⠣⠲⠠⠎` + - actual: `⠠⠟⠈⠩⠀⠘⠥⠈⠪⠃⠀⠊⠒⠑⠂⠈⠕⠉⠵⠀⠣⠲⠠⠎` + - current primary/reason: `exact` / `exact` +- `sentence_02.json` #727: 또 천안 살펴유(YOU) 모바일 앱 시행, 1인가구 밀키트 지원사업을 실시해 1인 가구 등의 고독사 취약계층 모니터링 사업을 추진하는 등 고독사 예방에도 총력을 기울이고 있다. + - expected: `⠠⠊⠥⠀⠰⠾⠣⠒⠀⠇⠂⠙⠱⠩⠦⠄⠴⠠⠠⠽⠳⠠⠴⠀` + - actual: `⠠⠊⠥⠀⠰⠾⠣⠒⠀⠇⠂⠙⠱⠩⠦⠄⠴⠠⠠⠽⠳⠠⠴⠀` + - current primary/reason: `exact` / `exact` +- `sentence_03.json` #4357: 신세계인터내셔날이 수입·판매하는 비건 지향 메이크업 브랜드 아워글래스(HOURGLASS)가 스킨케어와 메이크업 기능이 결합된 베이스 메이크업 ‘베일 하이드레이팅 스킨 틴트(이하 스킨 틴트)’를 출시한다고 19일 밝혔다. + - expected: `⠠⠟⠠⠝⠈⠌⠟⠓⠎⠉⠗⠠⠱⠉⠂⠕⠀⠠⠍⠕⠃⠐⠆⠙` + - actual: `⠠⠟⠠⠝⠈⠌⠟⠓⠎⠉⠗⠠⠱⠉⠂⠕⠀⠠⠍⠕⠃⠐⠆⠙` + - current primary/reason: `exact` / `exact` +- `sentence_04.json` #6975: 이 밖에 포시즌스 호텔 바 ‘오울(OUL)’에서는 후 환유 라인의 대표 성분인 ‘삼’을 비롯해 구기자, 식초, 청귤 등을 활용해 만든 칵테일 3종도 판매한다. + - expected: `⠕⠀⠘⠁⠁⠝⠀⠙⠥⠠⠕⠨⠵⠠⠪⠀⠚⠥⠓⠝⠂⠀⠘⠀` + - actual: `⠕⠀⠘⠁⠁⠝⠀⠙⠥⠠⠕⠨⠵⠠⠪⠀⠚⠥⠓⠝⠂⠀⠘⠀` + - current primary/reason: `exact` / `exact` + +Representative `mismatch` samples: + +- `sentence_01.json` #47: 다날은 계열사 ‘제프’가 국내 대체불가토큰(NFT) 거래소를 운영하는 ‘팔라’와 메타버스·NFT 협력 관련 협약(MOU)을 맺고 메타버스 플랫폼 ‘제프월드’의 인프라 확대를 추진한다고 3일 밝혔다. + - expected: `⠜⠁⠦⠄⠴⠠⠠⠍⠕⠥⠠⠴⠮⠀⠑⠗⠅⠈⠥⠀⠑⠝⠓⠘` + - actual: `⠜⠁⠦⠄⠴⠠⠠⠍⠳⠠⠴⠮⠀⠑⠗⠅⠈⠥⠀⠑⠝⠓⠘⠎` + - first differing cell (zero-based): 114 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_02.json` #128: 이번 방문에서 대표단은 우수 외투기업과 투자협약(MOU)을 체결하고 투자 상담, 기업정보 교류 등 적극적인 외자 유치 활동을 펼칠 계획이다. + - expected: `⠜⠁⠦⠄⠴⠠⠠⠍⠕⠥⠠⠴⠮⠀⠰⠝⠈⠳⠚⠈⠥⠀⠓⠍` + - actual: `⠜⠁⠦⠄⠴⠠⠠⠍⠳⠠⠴⠮⠀⠰⠝⠈⠳⠚⠈⠥⠀⠓⠍⠨` + - first differing cell (zero-based): 48 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_03.json` #1: 김 지사는 18일(이하 현지시각) 미국 코네티컷주 댄버리 린데 본사에서 산지브 람바(Sanjiv Lamba) 린데 회장, 성백석 린데코리아 회장, 조일교 아산시 부시장과 투자양해각서(MOU)을 체결했다. + - expected: `⠠⠎⠦⠄⠴⠠⠠⠍⠕⠥⠠⠴⠮⠀⠰⠝⠈⠳⠚⠗⠌⠊⠲` + - actual: `⠠⠎⠦⠄⠴⠠⠠⠍⠳⠠⠴⠮⠀⠰⠝⠈⠳⠚⠗⠌⠊⠲` + - first differing cell (zero-based): 175 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_04.json` #191: SK온은 23일 서울 종로구 SK서린사옥에서 에코프로머티리얼즈, 중국의 GEM(거린메이)과 전구체 생산을 위한 3자 합작법인인 지이엠코리아뉴에너지머티리얼즈㈜(이하 지이엠코리아) 설립 양해각서(MOU)를 체결했다고 밝혔다. + - expected: `⠴⠠⠠⠎⠅⠲⠷⠵⠀⠼⠃⠉⠕⠂⠀⠠⠎⠯⠀⠨⠿⠐⠥⠈` + - actual: `` + - error: `Invalid symbol character` + - first differing cell (zero-based): 0 + - current primary/reason: `unsupported_character_review` / `unsupported_character_review` + ### `korean_inline_parenthesized_single_arithmetic_operator` Of the 23 candidates, 6 are the actual `pending_rule_review` subcluster. The other 17 candidates are exact or existing non-pending-primary controls; this cohort does not reclassify them. For this output-signature audit, 6 mismatches were evaluable and 2 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. +Localized first-difference transitions: + +- `U+2814 ⠔ -> U+2824 ⠤`: 2 + Mismatch primary-class distribution: - `pending_rule_review`: 6 @@ -229,6 +551,10 @@ Of the 377 candidates, 377 are the actual `pending_rule_review` subcluster. The For this output-signature audit, 377 mismatches were evaluable and 13 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. +Localized first-difference transitions: + +- `U+2800 ⠀ -> U+2807 ⠇`: 13 + Mismatch primary-class distribution: - `pending_rule_review`: 377 @@ -314,6 +640,8 @@ The standalone-uppercase cohort is similarly ambiguous. Hangeul rule 28's append The Korean-prefixed all-caps parenthetical cohort isolates that ambiguity more narrowly. Hangeul rules 28/29 require Roman and capitalization indicators for prose acronyms, while science rule 7 requires element-by-element capitals for chemical formulae. Both meanings can have the same input surface form. The observed `COO`/`NSC`/`MOU` output differences therefore do not justify disabling either algorithm without independent semantic evidence. +The all-caps `OU` cohort isolates a frequent output transition without treating the reference as a rule. Hangeul rules 28, 29, and 32 delegate Roman-letter content to UEB (2024 Korean-rules PDF p.25 and following rules). UEB 10.12.1 says not to use a contraction when it is known or can be determined that an abbreviation or acronym's letters are pronounced separately, but says to use the contraction when that pronunciation is in doubt; UEB 10.12.2 otherwise uses contractions in abbreviations and acronyms (UEB 2024 PDF pp.191-192; Korean UEB translation PDF pp.182-183). Thus an expected `o` + `u` versus the current `ou` groupsign can be localized to an uppercase run, yet the surface run alone cannot distinguish a letter-by-letter initialism from a pronounceable word or acronym. That distinction needs lexical or semantic evidence absent from this input gate. Exact members are controls, identical-input conflicting references remain `corpus_suspect`, and no engine change is inferred. + The all-caps Roman middle-dot cohort is also semantically underdetermined. Hangeul rule 29 defines Roman indicators around Roman text in a Korean sentence, and Hangeul rule 50 requires U+00B7 to be attached on both sides, but neither rule says that the punctuation joins the adjacent Roman runs into one Roman span. Math rule 2 separately defines the same printed dot as multiplication, and science rule 4 uses it inside chemical formulae. An input-only `AI·SW` gate therefore cannot prove which mode transition is required. Exact cases remain controls, mismatches retain their existing primary class, and no engine rule is inferred from their references. Representative samples are sentence-level evidence: when the reported first difference precedes the detected middle-dot span, the cohort must not be treated as the cause of that mismatch. The inline parenthesized-operator cohort has an independently checkable spacing boundary. Hangeul rule 46 inserts spaces only when an operation or comparison sign is between Korean text, while the literal parentheses intervene in this gate. Hangeul rule 49 says punctuation spacing follows the print, and science rule 21 prints and brailles `(-)` and `(+)` with no spaces inside the parentheses. The output-signature count is therefore the implementation-candidate subset; mere sentence-level coexistence is retained only as a control. ASCII hyphen-minus is independently supported through the `Symbol`/rule-49 punctuation path, while `+`, `×`, `÷`, and `=` reach the `MathSymbol` spacing rule; end-to-end tests preserve tight parentheses on both paths. Analyzer checkpoint `30a9b10` recorded the pre-fix baseline as 23 candidates, 0 exact, 23 mismatches, and 19 mismatches whose first difference was signature-local. The generalized rule-46/49 fix is evaluated below against that immutable baseline rather than inferred from a reference string. @@ -322,6 +650,8 @@ The tight-triangle cohort is not an implementation premise. Hangeul rule 49 assi Corpus contradictions remain a separate gate: identical inputs with conflicting references are classified as `corpus_suspect` before these cohorts are recorded and would appear explicitly in each mismatch primary-class distribution. Their absence does not prove a reference correct; it only means that this deterministic contradiction test did not fire. +Current all-caps `OU` measurement: 1816 candidates, 55 exact controls, 1761 mismatches, 1684 members in the actual `pending_rule_review` subcluster, and 1307/1761 evaluable mismatches whose first difference is inside the current-engine output signature for the detected run. This is a pronunciation-sensitive UEB review cohort, not an engine routing rule. + Current standalone-uppercase measurement: 62411 candidates, 48757 exact controls, 13654 mismatches, and 13268 members in the actual `pending_rule_review` subcluster. Its high frequency does not make it causal: the same input shape is exact in many cases, and a sentence containing the shape may first differ at another Roman, numeric, or punctuation structure. No engine change is inferred from this cohort. Current Korean-prefixed all-caps parenthetical measurement: 54492 candidates, 43779 exact controls, 10713 mismatches, and 10388 members in the actual `pending_rule_review` subcluster. This is a semantic-collision audit, not an engine routing rule; no implementation change is inferred from its reference outputs. diff --git a/libs/braillify/examples/nikl_corpus_analyze.rs b/libs/braillify/examples/nikl_corpus_analyze.rs index 1f31fe7b..5199c537 100644 --- a/libs/braillify/examples/nikl_corpus_analyze.rs +++ b/libs/braillify/examples/nikl_corpus_analyze.rs @@ -4,7 +4,8 @@ //! `cargo run --release -p braillify --example nikl_corpus_analyze` //! //! This is an offline evaluation tool. It deliberately deserializes only `input` and -//! `unicode`; the read-only competitor `world` field is neither loaded nor compared. +//! `unicode`; the read-only competitor fields `world` and `jeomsarang` are neither loaded nor +//! compared. use std::collections::{BTreeMap, BTreeSet}; use std::fs::{self, File}; @@ -156,10 +157,17 @@ struct PendingRuleReviewClusterStats { conflicting_reference_cases: usize, output_signature_mismatches_evaluated: usize, first_difference_in_output_signature: usize, + first_difference_in_output_signature_transitions: BTreeMap, mismatch_primary_classes: BTreeMap, samples: BTreeMap>, } +#[derive(Debug, Default, Serialize)] +struct FirstDifferenceTransitionStats { + cases: usize, + samples: Vec, +} + #[derive(Debug, Serialize)] struct AnalysisReport { corpus: &'static str, @@ -179,6 +187,7 @@ struct AnalysisReport { // Cross-cutting input cohorts; only members whose existing primary class // is PendingRuleReview are pending-rule-review subclusters. pending_rule_review_clusters: BTreeMap, + pending_first_difference_cell_transitions: BTreeMap, overlapping_traits: BTreeMap, shards: BTreeMap, samples: BTreeMap>, @@ -556,6 +565,87 @@ const ALLCAPS_ROMAN_MIDDLE_DOT_RUNS: &str = const KOREAN_INLINE_PARENTHESIZED_OPERATOR: &str = "korean_inline_parenthesized_single_arithmetic_operator"; const TIGHT_TRIANGLE_BEFORE_KOREAN: &str = "tight_triangle_mark_immediately_before_korean"; +const ALLCAPS_ROMAN_RUN_CONTAINING_OU: &str = "allcaps_roman_run_containing_ou"; + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +struct AllcapsRomanRun { + start_byte: usize, + end_byte: usize, +} + +/// Finds maximal all-caps ASCII runs containing the adjacent letters `OU`. +/// +/// This is an input gate for a pronunciation-sensitive UEB diagnostic, not a +/// claim that the run is an initialism. Alphanumeric outer boundaries exclude +/// fragments of identifiers while retaining parenthesized and standalone runs. +fn allcaps_roman_runs_containing_ou(input: &str) -> Vec { + let bytes = input.as_bytes(); + let mut runs = Vec::new(); + let mut cursor = 0; + while cursor < bytes.len() { + if !bytes[cursor].is_ascii_alphabetic() { + cursor += input[cursor..] + .chars() + .next() + .expect("cursor must remain on a character boundary") + .len_utf8(); + continue; + } + + let start_byte = cursor; + while cursor < bytes.len() && bytes[cursor].is_ascii_alphabetic() { + cursor += 1; + } + let end_byte = cursor; + let run = &input[start_byte..end_byte]; + let previous = input[..start_byte].chars().next_back(); + let next = input[end_byte..].chars().next(); + if run.len() >= 2 + && run.bytes().all(|byte| byte.is_ascii_uppercase()) + && run.as_bytes().windows(2).any(|pair| pair == b"OU") + && previous.is_none_or(|ch| !ch.is_ascii_alphanumeric()) + && next.is_none_or(|ch| !ch.is_ascii_alphanumeric()) + { + runs.push(AllcapsRomanRun { + start_byte, + end_byte, + }); + } + } + runs +} + +/// Locates each detected run in the full current-engine output by searching +/// for that run's independently encoded signature. This uses neither the +/// corpus reference nor a hard-coded braille value. +fn allcaps_ou_actual_ranges(input: &str, actual: &str) -> Vec> { + let mut ranges = BTreeSet::new(); + for candidate in allcaps_roman_runs_containing_ou(input) { + let run = &input[candidate.start_byte..candidate.end_byte]; + let Ok(signature) = braillify::encode_to_unicode(run) else { + continue; + }; + let signature_cells = signature.chars().count(); + for (start_byte, _) in actual.match_indices(&signature) { + let start = actual[..start_byte].chars().count(); + ranges.insert((start, start + signature_cells)); + } + } + ranges.into_iter().map(|(start, end)| start..end).collect() +} + +fn first_difference_in_allcaps_ou_run(item: &EncodedCase) -> bool { + let Ok(actual) = &item.actual else { + return false; + }; + if actual == &item.located.case.unicode { + return false; + } + let first_difference = first_difference_cell(&item.located.case.unicode, actual); + allcaps_ou_actual_ranges(&item.located.case.input, actual) + .into_iter() + .any(|range| range.contains(&first_difference)) +} /// Input-only candidate gate for acronym expansions such as /// `HCA(Home Connectivity Alliance)`. @@ -867,6 +957,56 @@ fn first_difference_cell(expected: &str, actual: &str) -> usize { .unwrap_or_else(|| expected.chars().count().min(actual.chars().count())) } +fn cell_transition_key(expected: &str, actual: &str, index: usize) -> String { + let label = |text: &str| { + text.chars().nth(index).map_or_else( + || "".to_string(), + |cell| format!("U+{:04X} {cell}", cell as u32), + ) + }; + format!("{} -> {}", label(expected), label(actual)) +} + +fn record_pending_first_difference_transition( + transitions: &mut BTreeMap, + item: &EncodedCase, + primary_key: &str, + reason_key: &str, + sample_limit: usize, +) { + let Ok(actual) = &item.actual else { + return; + }; + let expected = &item.located.case.unicode; + if actual == expected { + return; + } + let first_difference = first_difference_cell(expected, actual); + let key = cell_transition_key(expected, actual, first_difference); + let stats = transitions.entry(key).or_default(); + stats.cases += 1; + if stats.samples.len() >= sample_limit + || stats + .samples + .iter() + .any(|sample| sample.shard == item.located.shard) + { + return; + } + let (expected_excerpt, actual_excerpt) = excerpt_pair(expected, actual); + stats.samples.push(PendingRuleReviewClusterSample { + shard: item.located.shard.clone(), + index: item.located.index, + input: item.located.case.input.clone(), + expected_excerpt, + actual_excerpt, + first_difference_cell: Some(first_difference), + error: None, + primary_class: primary_key.to_string(), + reason: reason_key.to_string(), + }); +} + fn is_compatibility_unit_decomposition(ch: char, nfkc: &str) -> bool { matches!( ch as u32, @@ -937,6 +1077,14 @@ fn record_structural_cohort_case( if let Some(is_in_signature) = first_difference_in_output_signature { stats.output_signature_mismatches_evaluated += 1; stats.first_difference_in_output_signature += usize::from(is_in_signature); + if is_in_signature && let Ok(actual) = &item.actual { + let expected = &item.located.case.unicode; + let first_difference = first_difference_cell(expected, actual); + *stats + .first_difference_in_output_signature_transitions + .entry(cell_transition_key(expected, actual, first_difference)) + .or_insert(0) += 1; + } } if primary == PrimaryClass::CorpusSuspect { stats.conflicting_reference_cases += 1; @@ -1001,6 +1149,10 @@ fn analyze( let mut samples = BTreeMap::>::new(); let mut rule_36_transition_audit = Rule36TransitionAudit::default(); let mut pending_rule_review_clusters = BTreeMap::from([ + ( + ALLCAPS_ROMAN_RUN_CONTAINING_OU.to_string(), + PendingRuleReviewClusterStats::default(), + ), ( ALLCAPS_ROMAN_MIDDLE_DOT_RUNS.to_string(), PendingRuleReviewClusterStats::default(), @@ -1026,6 +1178,7 @@ fn analyze( PendingRuleReviewClusterStats::default(), ), ]); + let mut pending_first_difference_cell_transitions = BTreeMap::new(); let mut exact = 0usize; for item in &encoded { @@ -1061,7 +1214,22 @@ fn analyze( *primary_classes.entry(primary_key.clone()).or_insert(0) += 1; *reasons.entry(reason_key.clone()).or_insert(0) += 1; + if primary == PrimaryClass::PendingRuleReview { + record_pending_first_difference_transition( + &mut pending_first_difference_cell_transitions, + item, + &primary_key, + &reason_key, + sample_limit, + ); + } + for (cluster, present, localized_first_difference) in [ + ( + ALLCAPS_ROMAN_RUN_CONTAINING_OU, + !allcaps_roman_runs_containing_ou(&item.located.case.input).is_empty(), + Some(first_difference_in_allcaps_ou_run(item)), + ), ( ALLCAPS_ROMAN_MIDDLE_DOT_RUNS, has_allcaps_roman_middle_dot_runs(&item.located.case.input), @@ -1240,6 +1408,7 @@ fn analyze( encoding_error_audit, rule_36_transition_audit, pending_rule_review_clusters, + pending_first_difference_cell_transitions, overlapping_traits: traits, shards, samples, @@ -1251,7 +1420,8 @@ fn markdown(report: &AnalysisReport) -> String { text.push_str("# NIKL 2025 v1.0 corpus analysis\n\n"); text.push_str( "> Generated by `cargo run --release -p braillify --example nikl_corpus_analyze`. \ - The tool reads only `input` and `unicode`; it never loads or compares `world`.\n\n", + The tool reads only `input` and `unicode`; it never loads or compares the read-only \ + `world` or `jeomsarang` fields.\n\n", ); text.push_str("## Current measurement\n\n"); text.push_str("| Metric | Count |\n|---|---:|\n"); @@ -1294,6 +1464,54 @@ fn markdown(report: &AnalysisReport) -> String { text.push_str(&format!("| `{name}` | {count} |\n")); } + text.push_str("\n## Pending first-difference cell transitions\n\n"); + text.push_str( + "This ranking is a diagnostic selector, not an implementation rule. It counts only \ + current `pending_rule_review` cases whose encoder call succeeded, keyed by the expected \ + and actual cell at the sentence's first differing position. Candidate implementation \ + work must still bind a transition to a localized input structure, exact controls, and \ + independent PDF evidence.\n\n", + ); + let mut ranked_transitions = report + .pending_first_difference_cell_transitions + .iter() + .collect::>(); + ranked_transitions.sort_by(|(left_key, left), (right_key, right)| { + right + .cases + .cmp(&left.cases) + .then_with(|| left_key.cmp(right_key)) + }); + text.push_str("| Rank | Expected → actual first cell | Cases |\n|---:|---|---:|\n"); + for (rank, (transition, stats)) in ranked_transitions.iter().take(20).enumerate() { + text.push_str(&format!( + "| {} | `{transition}` | {} |\n", + rank + 1, + stats.cases + )); + } + for (transition, stats) in ranked_transitions.iter().take(10) { + text.push_str(&format!("\n### `{transition}`\n\n")); + for sample in &stats.samples { + text.push_str(&format!( + "- `{}` #{}: {}\n - expected: `{}`\n - actual: `{}`\n - first differing cell (zero-based): {}\n - current primary/reason: `{}` / `{}`\n", + sample.shard, + sample.index, + sample + .input + .chars() + .take(180) + .collect::() + .replace('`', "\\`"), + sample.expected_excerpt, + sample.actual_excerpt, + sample.first_difference_cell.unwrap_or(0), + sample.primary_class, + sample.reason + )); + } + } + text.push_str("\n## Cross-cutting input-only structural cohorts\n\n"); text.push_str( "These are cross-cutting input-only structural cohorts, not new primary classes and not \ @@ -1321,7 +1539,10 @@ fn markdown(report: &AnalysisReport) -> String { `Korean(` + one rule-45 arithmetic operator + `)Korean` span. Unlike broad coexistence \ traits, it also locates the current engine's emitted structure and counts a mismatch as \ signature-local only when the sentence's first differing cell falls inside that output \ - range. The `tight_triangle_mark_immediately_before_korean` gate requires literal \ + range. The `allcaps_roman_run_containing_ou` gate finds maximal, alphanumeric-delimited \ + uppercase ASCII runs containing adjacent `OU`. It locates the independently encoded run \ + signature in the complete current output and counts only first differences inside that \ + signature as localized. The `tight_triangle_mark_immediately_before_korean` gate requires literal \ `△한글` with no input space and includes the first following Korean cell in its localized \ output range, so an observed missing-space difference is measured at the mark boundary.\n\n", ); @@ -1357,6 +1578,23 @@ fn markdown(report: &AnalysisReport) -> String { stats.output_signature_mismatches_evaluated, stats.first_difference_in_output_signature )); + if !stats + .first_difference_in_output_signature_transitions + .is_empty() + { + let mut transitions = stats + .first_difference_in_output_signature_transitions + .iter() + .collect::>(); + transitions.sort_by(|(left_key, left), (right_key, right)| { + right.cmp(left).then_with(|| left_key.cmp(right_key)) + }); + text.push_str("Localized first-difference transitions:\n\n"); + for (transition, count) in transitions.into_iter().take(5) { + text.push_str(&format!("- `{transition}`: {count}\n")); + } + text.push('\n'); + } } text.push_str("Mismatch primary-class distribution:\n\n"); for (primary, count) in &stats.mismatch_primary_classes { @@ -1411,6 +1649,19 @@ fn markdown(report: &AnalysisReport) -> String { meanings can have the same input surface form. The observed `COO`/`NSC`/`MOU` output \ differences therefore do not justify disabling either algorithm without independent \ semantic evidence.\n\n\ + The all-caps `OU` cohort isolates a frequent output transition without treating the \ + reference as a rule. Hangeul rules 28, 29, and 32 delegate Roman-letter content to UEB \ + (2024 Korean-rules PDF p.25 and following rules). \ + UEB 10.12.1 says not to use a contraction when it is known or can be determined that an \ + abbreviation or acronym's letters are pronounced separately, but says to use the \ + contraction when that pronunciation is in doubt; UEB 10.12.2 otherwise uses \ + contractions in abbreviations and acronyms (UEB 2024 PDF pp.191-192; Korean UEB \ + translation PDF pp.182-183). Thus an expected `o` + `u` versus the \ + current `ou` groupsign can be localized to an uppercase run, yet the surface run alone \ + cannot distinguish a letter-by-letter initialism from a pronounceable word or acronym. \ + That distinction needs lexical or semantic evidence absent from this input gate. Exact \ + members are controls, identical-input conflicting references remain `corpus_suspect`, \ + and no engine change is inferred.\n\n\ The all-caps Roman middle-dot cohort is also semantically underdetermined. Hangeul rule \ 29 defines Roman indicators around Roman text in a Korean sentence, and Hangeul rule 50 \ requires U+00B7 to be attached on both sides, but neither rule says that the punctuation \ @@ -1446,6 +1697,28 @@ fn markdown(report: &AnalysisReport) -> String { not prove a reference correct; it only means that this deterministic contradiction test \ did not fire.\n", ); + if let Some(stats) = report + .pending_rule_review_clusters + .get(ALLCAPS_ROMAN_RUN_CONTAINING_OU) + { + let pending = stats + .mismatch_primary_classes + .get("pending_rule_review") + .copied() + .unwrap_or(0); + text.push_str(&format!( + "\nCurrent all-caps `OU` measurement: {} candidates, {} exact controls, {} \ + mismatches, {pending} members in the actual `pending_rule_review` subcluster, and \ + {}/{} evaluable mismatches whose first difference is inside the current-engine \ + output signature for the detected run. This is a pronunciation-sensitive UEB \ + review cohort, not an engine routing rule.\n", + stats.candidates, + stats.exact, + stats.mismatch, + stats.first_difference_in_output_signature, + stats.output_signature_mismatches_evaluated + )); + } if let Some(stats) = report .pending_rule_review_clusters .get(STANDALONE_UPPERCASE_ROMAN_WORD) @@ -1989,6 +2262,19 @@ mod tests { assert_eq!(normalized_braille_whitespace("⠁ ⠃"), "⠁⠀⠃"); } + #[rstest::rstest] + #[case::different_cells("⠁", "⠃", 0, "U+2801 ⠁ -> U+2803 ⠃")] + #[case::expected_ended("", "⠃", 0, " -> U+2803 ⠃")] + #[case::actual_ended("⠁", "", 0, "U+2801 ⠁ -> ")] + fn formats_first_difference_transition_key( + #[case] expected: &str, + #[case] actual: &str, + #[case] index: usize, + #[case] transition: &str, + ) { + assert_eq!(cell_transition_key(expected, actual, index), transition); + } + #[test] fn roman_indicator_moves_before_capital_word_indicator() { assert_eq!(roman_before_capital_order("⠠⠠⠴⠁⠃"), "⠴⠠⠠⠁⠃"); @@ -2007,6 +2293,34 @@ mod tests { assert_eq!(has_uppercase_roman_headword_expansion(input), expected); } + #[rstest::rstest] + #[case::parenthesized_initialism("업무협약(MOU)을", vec!["MOU"])] + #[case::standalone_word("SOUTH KOREA", vec!["SOUTH"])] + #[case::multiple_runs("MOU와 YOUTH", vec!["MOU", "YOUTH"])] + #[case::lowercase("Mou", vec![])] + #[case::mixed_case("MoU", vec![])] + #[case::no_ou("WHO", vec![])] + #[case::digit_boundary("1MOU", vec![])] + #[case::identifier_suffix("MOU2", vec![])] + fn detects_allcaps_roman_runs_containing_ou(#[case] input: &str, #[case] expected: Vec<&str>) { + let actual = allcaps_roman_runs_containing_ou(input) + .into_iter() + .map(|run| &input[run.start_byte..run.end_byte]) + .collect::>(); + assert_eq!(actual, expected); + } + + #[test] + fn locates_allcaps_ou_signature_in_complete_output() { + let input = "업무협약(MOU)을 체결했다."; + let actual = braillify::encode_to_unicode(input).expect("probe must encode"); + let ranges = allcaps_ou_actual_ranges(input, &actual); + + assert_eq!(ranges.len(), 1); + assert!(ranges[0].start < ranges[0].end); + assert!(ranges[0].end <= actual.chars().count()); + } + #[rstest::rstest] #[case::standalone("새로운 DRX 브랜드", true)] #[case::inside_parentheses("엠디(MD), SNS", true)] From daeec606a818a014f6eba4ae6eebd0a1935f7e7c Mon Sep 17 00:00:00 2001 From: devfive Date: Tue, 1 Sep 2026 07:39:48 +0900 Subject: [PATCH 17/77] Separate Roman entry routing corpus cohorts --- docs/corpus-analysis/NIKL_2025_V1.md | 140 +++++- .../braillify/examples/nikl_corpus_analyze.rs | 475 +++++++++++++++++- 2 files changed, 604 insertions(+), 11 deletions(-) diff --git a/docs/corpus-analysis/NIKL_2025_V1.md b/docs/corpus-analysis/NIKL_2025_V1.md index 92050e91..5851c969 100644 --- a/docs/corpus-analysis/NIKL_2025_V1.md +++ b/docs/corpus-analysis/NIKL_2025_V1.md @@ -292,17 +292,20 @@ This ranking is a diagnostic selector, not an implementation rule. It counts onl ## Cross-cutting input-only structural cohorts -These are cross-cutting input-only structural cohorts, not new primary classes and not engine routing rules. Candidate selection never changes a case's existing primary class. Only cohort members already classified as `pending_rule_review` form a pending subcluster; exact and other-primary members are controls that retain their existing outcomes. The `uppercase_roman_headword_closed_multiword_parenthetical` gate requires a two-or-more character uppercase ASCII headword immediately followed by a closed parenthesis whose contents are two or more ASCII Roman words separated only by spaces. Because the contents admit only letters and spaces, visible operators, subscript/superscript notation, and nested parentheses are excluded deterministically. The `standalone_multi_character_uppercase_roman_word` gate finds maximal ASCII-letter runs of two or more capitals with non-alphanumeric boundaries; a run immediately followed by `(` is excluded so the HCA-style headword itself is not counted by both gates. The `korean_prefixed_closed_allcaps_parenthetical` gate requires an immediately preceding Korean character and a closed body of two or more uppercase ASCII letters. It intentionally contains both acronym annotations (`책임자(COO)`) and scientific formulae (`일산화탄소(CO)`) so their semantic collision remains measurable. The `multi_character_allcaps_roman_runs_joined_by_middle_dot` gate requires two maximal ASCII-letter runs of at least two capitals joined directly by U+00B7, with non-alphanumeric outer boundaries. It records shapes such as `AI·SW` without assigning prose, mathematics, or science semantics. The `korean_inline_parenthesized_single_arithmetic_operator` gate requires an immediate `Korean(` + one rule-45 arithmetic operator + `)Korean` span. Unlike broad coexistence traits, it also locates the current engine's emitted structure and counts a mismatch as signature-local only when the sentence's first differing cell falls inside that output range. The `allcaps_roman_run_containing_ou` gate finds maximal, alphanumeric-delimited uppercase ASCII runs containing adjacent `OU`. It locates the independently encoded run signature in the complete current output and counts only first differences inside that signature as localized. The `tight_triangle_mark_immediately_before_korean` gate requires literal `△한글` with no input space and includes the first following Korean cell in its localized output range, so an observed missing-space difference is measured at the mark boundary. +These are cross-cutting input-only structural cohorts, not new primary classes and not engine routing rules. Candidate selection never changes a case's existing primary class. Only cohort members already classified as `pending_rule_review` form a pending subcluster; exact and other-primary members are controls that retain their existing outcomes. The `uppercase_roman_headword_closed_multiword_parenthetical` gate requires a two-or-more character uppercase ASCII headword immediately followed by a closed parenthesis whose contents are two or more ASCII Roman words separated only by spaces. Because the contents admit only letters and spaces, visible operators, subscript/superscript notation, and nested parentheses are excluded deterministically. The `mixed_roman_korean_word_before_uppercase_headword_expansion` gate further requires a whitespace-delimited preceding word that contains both ASCII Roman and Korean and ends in Korean. It locates the following uppercase headword itself, including its immediately preceding emitted cell, so an earlier Roman entry in the same sentence cannot satisfy the output audit. The `single_capital_followed_by_parenthesized_digits` gate requires a standalone capital, a non-empty closed ASCII-digit parenthetical, and alphanumeric outer boundaries. It likewise includes the emitted entry-boundary cell in localization. The `uppercase_roman_run_followed_by_hyphen_digits` gate requires a maximal uppercase ASCII run, literal ASCII hyphen-minus, a non-empty digit run, and alphanumeric outer boundaries. Its localized range includes the current encoded run and its immediately preceding cell, keeping `F-35`-style routing separate from both parenthesized digits and headword expansions. The `standalone_multi_character_uppercase_roman_word` gate finds maximal ASCII-letter runs of two or more capitals with non-alphanumeric boundaries; a run immediately followed by `(` is excluded so the HCA-style headword itself is not counted by both gates. The `korean_prefixed_closed_allcaps_parenthetical` gate requires an immediately preceding Korean character and a closed body of two or more uppercase ASCII letters. It intentionally contains both acronym annotations (`책임자(COO)`) and scientific formulae (`일산화탄소(CO)`) so their semantic collision remains measurable. The `multi_character_allcaps_roman_runs_joined_by_middle_dot` gate requires two maximal ASCII-letter runs of at least two capitals joined directly by U+00B7, with non-alphanumeric outer boundaries. It records shapes such as `AI·SW` without assigning prose, mathematics, or science semantics. The `korean_inline_parenthesized_single_arithmetic_operator` gate requires an immediate `Korean(` + one rule-45 arithmetic operator + `)Korean` span. Unlike broad coexistence traits, it also locates the current engine's emitted structure and counts a mismatch as signature-local only when the sentence's first differing cell falls inside that output range. The `allcaps_roman_run_containing_ou` gate finds maximal, alphanumeric-delimited uppercase ASCII runs containing adjacent `OU`. It locates the independently encoded run signature in the complete current output and counts only first differences inside that signature as localized. The `tight_triangle_mark_immediately_before_korean` gate requires literal `△한글` with no input space and includes the first following Korean cell in its localized output range, so an observed missing-space difference is measured at the mark boundary. | Cluster | Candidates | Exact | Mismatch | Conflicting-reference cases | |---|---:|---:|---:|---:| | `allcaps_roman_run_containing_ou` | 1816 | 55 | 1761 | 0 | | `korean_inline_parenthesized_single_arithmetic_operator` | 23 | 17 | 6 | 0 | | `korean_prefixed_closed_allcaps_parenthetical` | 54492 | 43779 | 10713 | 0 | +| `mixed_roman_korean_word_before_uppercase_headword_expansion` | 10 | 0 | 10 | 0 | | `multi_character_allcaps_roman_runs_joined_by_middle_dot` | 97 | 0 | 97 | 0 | +| `single_capital_followed_by_parenthesized_digits` | 1361 | 0 | 1361 | 0 | | `standalone_multi_character_uppercase_roman_word` | 62411 | 48757 | 13654 | 0 | | `tight_triangle_mark_immediately_before_korean` | 377 | 0 | 377 | 0 | | `uppercase_roman_headword_closed_multiword_parenthetical` | 175 | 15 | 160 | 0 | +| `uppercase_roman_run_followed_by_hyphen_digits` | 571 | 178 | 393 | 0 | ### `allcaps_roman_run_containing_ou` @@ -462,6 +465,33 @@ Representative `mismatch` samples: - first differing cell (zero-based): 60 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +### `mixed_roman_korean_word_before_uppercase_headword_expansion` + +Of the 10 candidates, 10 are the actual `pending_rule_review` subcluster. The other 0 candidates are exact or existing non-pending-primary controls; this cohort does not reclassify them. + +For this output-signature audit, 10 mismatches were evaluable and 8 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. + +Localized first-difference transitions: + +- `U+2834 ⠴ -> U+2800 ⠀`: 8 + +Mismatch primary-class distribution: + +- `pending_rule_review`: 10 + +Representative `mismatch` samples: + +- `sentence_01.json` #18: 2023년형 신제품은 매터(Matter)와 HCA(Home Connectivity Alliance) 표준을 지원하는 스마트싱스 허브(SmartThings Hub)를 기반으로 다양한 기기를 자동으로 연결하고, 제어·관리할 수 있는 것이 특징이다. + - expected: `⠁⠞⠞⠻⠠⠴⠧⠀⠴⠠⠠⠓⠉⠁⠐⠣⠠⠓⠕⠍⠑⠀⠠⠒` + - actual: `⠁⠞⠞⠻⠠⠴⠧⠀⠀⠠⠠⠓⠉⠁⠦⠠⠓⠕⠍⠑⠀⠴⠠⠒` + - first differing cell (zero-based): 37 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_02.json` #9391: 이지스자산운용이 사옥인 여의도 세우빌딩의 2층을 리모델링하고 미국 그린빌딩위원회(USGBC)의 LEED(Leadership in Energy and Environmental Design) 골드(Gold) 등급 인증을 획득했다고 6일 밝혔다. + - expected: `⠎⠛⠃⠉⠠⠴⠺⠀⠴⠠⠠⠇⠑⠑⠙⠐⠣⠠⠇⠂⠙⠻⠩⠊` + - actual: `⠎⠛⠃⠉⠠⠴⠺⠀⠀⠠⠠⠇⠑⠑⠙⠦⠠⠇⠑⠁⠙⠑⠗⠎` + - first differing cell (zero-based): 90 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` + ### `multi_character_allcaps_roman_runs_joined_by_middle_dot` Of the 97 candidates, 97 are the actual `pending_rule_review` subcluster. The other 0 candidates are exact or existing non-pending-primary controls; this cohort does not reclassify them. @@ -493,6 +523,46 @@ Representative `mismatch` samples: - first differing cell (zero-based): 130 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +### `single_capital_followed_by_parenthesized_digits` + +Of the 1361 candidates, 1361 are the actual `pending_rule_review` subcluster. The other 0 candidates are exact or existing non-pending-primary controls; this cohort does not reclassify them. + +For this output-signature audit, 1361 mismatches were evaluable and 1341 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. + +Localized first-difference transitions: + +- `U+2834 ⠴ -> U+2800 ⠀`: 1329 +- `U+2834 ⠴ -> U+2801 ⠁`: 9 +- `U+2834 ⠴ -> U+2803 ⠃`: 2 +- `U+2834 ⠴ -> U+2813 ⠓`: 1 + +Mismatch primary-class distribution: + +- `pending_rule_review`: 1361 + +Representative `mismatch` samples: + +- `sentence_01.json` #80: 4일 경기 안성경찰서 등에 따르면 남성 A(54)씨는 지난 2일 오후 9시53분께 경기 안성의 주차장 인근에서 전처인 B(53)씨 흉기로 찔러 살해했다. + - expected: `⠑⠡⠀⠉⠢⠠⠻⠀⠴⠠⠁⠦⠄⠼⠑⠙⠠⠴⠠⠠⠕⠉⠵⠀` + - actual: `⠑⠡⠀⠉⠢⠠⠻⠀⠀⠁⠦⠼⠑⠙⠴⠀⠀⠠⠠⠕⠉⠵⠀⠨` + - first differing cell (zero-based): 39 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_02.json` #186: 3일 사회관계망서비스(SNS)를 통해 유포된 동영상에는 중학생인 A(14)양이 지난달 30일 태안의 한 지하 주차장에서 B(15)양으로부터 일방적으로 폭행을 당하고 주변에 있던 학생들은 이를 웃으며 방관하는 장면이 담겼다. + - expected: `⠶⠚⠁⠠⠗⠶⠟⠀⠴⠠⠁⠦⠄⠼⠁⠙⠠⠴⠜⠶⠕⠀⠨⠕` + - actual: `⠶⠚⠁⠠⠗⠶⠟⠀⠀⠁⠦⠼⠁⠙⠴⠀⠀⠜⠶⠕⠀⠨⠕⠉` + - first differing cell (zero-based): 65 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_03.json` #51: 20일 한국장기조직기증원에 따르면 A(11)군은 지난 3일 학교에 가기 위해 횡단보도를 건너다가 시내버스에 치여 병원으로 이송돼 치료받았지만 회복하지 못하고 뇌사 상태에 빠졌다. + - expected: `⠀⠠⠊⠐⠪⠑⠡⠀⠴⠠⠁⠦⠄⠼⠁⠁⠠⠴⠈⠛⠵⠀⠨⠕` + - actual: `⠀⠠⠊⠐⠪⠑⠡⠀⠀⠁⠦⠼⠁⠁⠴⠀⠀⠈⠛⠵⠀⠨⠕⠉` + - first differing cell (zero-based): 36 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_04.json` #1629: 23일 법조계에 따르면 전주지법 제12형사부(김도형 부장판사)는 살인, 공갈, 성매매 알선 행위 등 처벌에 관한 법률 위반 혐의로 기소된 A(28)씨에게 징역 17년을 선고했다. + - expected: `⠈⠕⠠⠥⠊⠽⠒⠀⠴⠠⠁⠦⠄⠼⠃⠓⠠⠴⠠⠠⠕⠝⠈⠝` + - actual: `⠈⠕⠠⠥⠊⠽⠒⠀⠀⠁⠦⠼⠃⠓⠴⠀⠀⠠⠠⠕⠝⠈⠝⠀` + - first differing cell (zero-based): 137 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` + ### `standalone_multi_character_uppercase_roman_word` Of the 62411 candidates, 13268 are the actual `pending_rule_review` subcluster. The other 49143 candidates are exact or existing non-pending-primary controls; this cohort does not reclassify them. @@ -634,12 +704,74 @@ Representative `mismatch` samples: - first differing cell (zero-based): 93 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +### `uppercase_roman_run_followed_by_hyphen_digits` + +Of the 571 candidates, 390 are the actual `pending_rule_review` subcluster. The other 181 candidates are exact or existing non-pending-primary controls; this cohort does not reclassify them. + +For this output-signature audit, 393 mismatches were evaluable and 259 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. + +Localized first-difference transitions: + +- `U+2834 ⠴ -> U+2800 ⠀`: 257 +- `U+2834 ⠴ -> U+2820 ⠠`: 2 + +Mismatch primary-class distribution: + +- `pending_rule_review`: 390 +- `unsupported_character_review`: 3 + +Representative `exact` samples: + +- `sentence_01.json` #88: 진원생명과학은 현재 미국에서 코로나19에 관한 mRNA 또는 아데노바이러스(Ad26) 벡터 백신 접종자들을 대상으로 GLS-5310의 부스터 샷 임상연구를 진행 중이다. + - expected: `⠨⠟⠏⠒⠠⠗⠶⠑⠻⠈⠧⠚⠁⠵⠀⠚⠡⠨⠗⠀⠑⠕⠈⠍` + - actual: `⠨⠟⠏⠒⠠⠗⠶⠑⠻⠈⠧⠚⠁⠵⠀⠚⠡⠨⠗⠀⠑⠕⠈⠍` + - current primary/reason: `exact` / `exact` +- `sentence_02.json` #167: 2023년 제1회 상담사례 워크숍은 정신건강 임상심리사인 김한우 수퍼바이저(월덴3 아카데미 대표)가 ‘기질 및 성격검사(TCI), 미네소타 다면적 인성 검사(MMPI-2), 문장완성검사(SCT) 활용을 위한 심리평가 슈퍼비전’이라는 주제로 진행하였다. + - expected: `⠼⠃⠚⠃⠉⠀⠉⠡⠀⠨⠝⠼⠁⠀⠚⠽⠀⠇⠶⠊⠢⠇⠐⠌` + - actual: `⠼⠃⠚⠃⠉⠀⠉⠡⠀⠨⠝⠼⠁⠀⠚⠽⠀⠇⠶⠊⠢⠇⠐⠌` + - current primary/reason: `exact` / `exact` +- `sentence_03.json` #217: LG이노텍은 인공지능(AI)을 활용한 시뮬레이션을 통해 일반 자성소재 대비 에너지 손실은 최대 40% 줄이고, 파워 밀도는 3배 높아진 ‘고효율 페라이트’ 자성소재(X-2)를 독자적으로 개발해 넥슬림에 적용했다고 설명했다. + - expected: `⠴⠠⠠⠇⠛⠲⠕⠉⠥⠓⠝⠁⠵⠀⠟⠈⠿⠨⠕⠉⠪⠶⠦⠄` + - actual: `⠴⠠⠠⠇⠛⠲⠕⠉⠥⠓⠝⠁⠵⠀⠟⠈⠿⠨⠕⠉⠪⠶⠦⠄` + - current primary/reason: `exact` / `exact` +- `sentence_04.json` #358: 세계 최대 이슬람 국가 인도네시아가 이스라엘 선수단을 거부해야 한다는 거센 국내 여론으로 논란을 빚다가 2023 국제축구연맹(FIFA) 20세 이하(U-20) 월드컵 개최국 자격을 박탈당했다. + - expected: `⠠⠝⠈⠌⠀⠰⠽⠊⠗⠀⠕⠠⠮⠐⠣⠢⠀⠈⠍⠁⠫⠀⠟⠊` + - actual: `⠠⠝⠈⠌⠀⠰⠽⠊⠗⠀⠕⠠⠮⠐⠣⠢⠀⠈⠍⠁⠫⠀⠟⠊` + - current primary/reason: `exact` / `exact` + +Representative `mismatch` samples: + +- `sentence_01.json` #87: 4일 진원생명과학은 자체 개발 흡인작용 피내 접종기 진덤(GeneDerm)을 이용한 코로나19 DNA 백신(GLS-5310)의 안전성·면역원성에 대한 임상1상 결과를 국제감염병학회 (ISID) 학술지인 국제감염질환저널에 발표했다고 4일 밝혔다. + - expected: `⠢⠘⠻⠚⠁⠚⠽⠀⠦⠄⠴⠠⠠⠊⠎⠊⠙⠠⠴⠀⠚⠁⠠⠯` + - actual: `⠢⠘⠻⠚⠁⠚⠽⠀⠴⠐⠣⠠⠠⠊⠎⠊⠙⠐⠜⠲⠀⠚⠁⠠` + - first differing cell (zero-based): 172 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_02.json` #1355: 최신 한국형 화물창 기술(KC-2)을 적용한 국내 최초 LNG(액화천연가스) 벙커링 전용 선박인 ‘블루 웨일호’ (Blue Whale)가 10일 운항을 시작했다. + - expected: `⠗⠕⠂⠚⠥⠴⠄⠀⠦⠄⠴⠠⠃⠇⠥⠑⠀⠠⠱⠁⠇⠑⠠⠴` + - actual: `⠗⠕⠂⠚⠥⠴⠄⠀⠴⠐⠣⠠⠃⠇⠥⠑⠀⠠⠱⠁⠇⠑⠐⠜` + - first differing cell (zero-based): 114 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_03.json` #921: 미국 외교정책연구소(FPRI)의 선임 연구원 로버트 리는 “러시아가 T-55 전차 일부를 우선 후방 지역에 배치하고, 장거리 포사격을 하는 데 이용할 것”이라고 예상했다. + - expected: `⠦⠐⠎⠠⠕⠣⠫⠀⠴⠠⠞⠤⠼⠑⠑⠀⠨⠾⠰⠣⠀⠕⠂⠘` + - actual: `⠦⠐⠎⠠⠕⠣⠫⠀⠀⠠⠞⠔⠼⠑⠑⠀⠀⠨⠾⠰⠣⠀⠕⠂` + - first differing cell (zero-based): 63 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_04.json` #48: 올트먼은 GPT-4가 기존 ‘GPT-3.5’보다 정확도가 40% 이상 높아졌지만, 이를 정보의 주요 출처로 사용해서는 안된다고도 지적했다. 거짓을 사실처럼 보이게 하는 이른바 ‘환각(hallucination) 현상’에 주의해야 한다는 입장이다. + - expected: `⠠⠠⠛⠏⠞⠤⠼⠉⠲⠑⠴⠄⠘⠥⠊⠀⠨⠻⠚⠧⠁⠊⠥⠫` + - actual: `⠠⠠⠛⠏⠞⠤⠼⠉⠴⠲⠑⠴⠄⠘⠥⠊⠀⠨⠻⠚⠧⠁⠊⠥` + - first differing cell (zero-based): 35 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` + This shape is not an engine implementation premise. The 2024 PDF's math rule 6 defines parentheses and grouping parentheses, rule 11 defines mathematical-expression spacing, rule 12 covers Roman letters in formulas as well as Korean sentences, and rule 45 shows Roman-letter function notation followed by parentheses. Excluding visible operators, scripts, and nesting narrows this corpus cohort, but the PDF does not make the remaining surface shape sufficient to rule out every mathematical counterexample. The cluster therefore remains conservative pending-review evidence only. The standalone-uppercase cohort is similarly ambiguous. Hangeul rule 28's appendix defines the capital-word indicator for two or more consecutive capitals, and rule 29 defines Roman indicators around Roman text in a Korean sentence. But math rule 12 also uses uppercase Roman variables, while science rule 7 uses uppercase runs in chemical formulas. The input gate cannot determine which semantic regime applies, so its output differences are observations to review, not permission to infer an engine rule from the corpus reference. The Korean-prefixed all-caps parenthetical cohort isolates that ambiguity more narrowly. Hangeul rules 28/29 require Roman and capitalization indicators for prose acronyms, while science rule 7 requires element-by-element capitals for chemical formulae. Both meanings can have the same input surface form. The observed `COO`/`NSC`/`MOU` output differences therefore do not justify disabling either algorithm without independent semantic evidence. +Two narrower cohorts separate causes hidden by the frequent `U+2834 -> U+2800` cell transition. `single_capital_followed_by_parenthesized_digits` reproduces the current math-token routing of forms such as `A(14)`: Hangeul rules 29 and 34 govern a Roman section and a parenthesized Roman form, while math rule 6 independently defines parenthesized function notation such as `f(x)`. A capital and numeric argument do not remove that mathematical counterexample, so this localized routing difference remains pending rather than authorizing an input-shape exception. `mixed_roman_korean_word_before_uppercase_headword_expansion` separately targets the next Roman headword after a mixed Roman+Korean word (for example, a Korean particle attached to the previous Roman name). Its range is anchored to that later headword, not to the earlier Roman entry. Nevertheless, the closed multiword parenthetical shape still cannot exclude every mathematical interpretation under math rules 6, 11, 12, and 45, as recorded for the broader HCA-style cohort. The headword shape is therefore not added to engine routing; the two causes and their controls remain separately measurable. + +The uppercase-Roman hyphen-digits cohort is a third independent cause. Hangeul rule 35 explicitly shows `D-100` as a Roman-and-number continuation (2024 Korean-rules PDF p.29), while math rule 2 defines subtraction and the math chapters allow uppercase Roman variables. The surface form alone therefore does not prove whether `F-35` is an identifier or a subtraction expression. This cohort records the current operator-routing signature and exact controls without merging it into either `A(14)` or HCA-style diagnostics. No engine change is made without both a safe semantic boundary and exact controls. + The all-caps `OU` cohort isolates a frequent output transition without treating the reference as a rule. Hangeul rules 28, 29, and 32 delegate Roman-letter content to UEB (2024 Korean-rules PDF p.25 and following rules). UEB 10.12.1 says not to use a contraction when it is known or can be determined that an abbreviation or acronym's letters are pronounced separately, but says to use the contraction when that pronunciation is in doubt; UEB 10.12.2 otherwise uses contractions in abbreviations and acronyms (UEB 2024 PDF pp.191-192; Korean UEB translation PDF pp.182-183). Thus an expected `o` + `u` versus the current `ou` groupsign can be localized to an uppercase run, yet the surface run alone cannot distinguish a letter-by-letter initialism from a pronounceable word or acronym. That distinction needs lexical or semantic evidence absent from this input gate. Exact members are controls, identical-input conflicting references remain `corpus_suspect`, and no engine change is inferred. The all-caps Roman middle-dot cohort is also semantically underdetermined. Hangeul rule 29 defines Roman indicators around Roman text in a Korean sentence, and Hangeul rule 50 requires U+00B7 to be attached on both sides, but neither rule says that the punctuation joins the adjacent Roman runs into one Roman span. Math rule 2 separately defines the same printed dot as multiplication, and science rule 4 uses it inside chemical formulae. An input-only `AI·SW` gate therefore cannot prove which mode transition is required. Exact cases remain controls, mismatches retain their existing primary class, and no engine rule is inferred from their references. Representative samples are sentence-level evidence: when the reported first difference precedes the detected middle-dot span, the cohort must not be treated as the cause of that mismatch. @@ -650,6 +782,12 @@ The tight-triangle cohort is not an implementation premise. Hangeul rule 49 assi Corpus contradictions remain a separate gate: identical inputs with conflicting references are classified as `corpus_suspect` before these cohorts are recorded and would appear explicitly in each mismatch primary-class distribution. Their absence does not prove a reference correct; it only means that this deterministic contradiction test did not fire. +Current uppercase-Roman hyphen-digits measurement: 571 candidates, 178 exact controls, 393 mismatches, 390 members in the actual `pending_rule_review` subcluster, and 259/393 evaluable mismatches whose first difference is inside the target run plus its entry boundary. It remains distinct from parenthesized digits and headword expansions; no engine change is inferred. + +Current single-capital parenthesized-digits measurement: 1361 candidates, 0 exact controls, 1361 mismatches, 1361 members in the actual `pending_rule_review` subcluster, and 1341/1361 evaluable mismatches whose first difference is inside the target run plus its entry boundary. No engine change is inferred from the ambiguous prose/function surface form. + +Current mixed Roman+Korean boundary before uppercase headword-expansion measurement: 10 candidates, 0 exact controls, 10 mismatches, 10 members in the actual `pending_rule_review` subcluster, and 8/10 evaluable mismatches whose first difference is localized to the later headword's entry boundary/output. The detector cannot be satisfied by the earlier Roman entry. No HCA-shaped engine routing rule is introduced. + Current all-caps `OU` measurement: 1816 candidates, 55 exact controls, 1761 mismatches, 1684 members in the actual `pending_rule_review` subcluster, and 1307/1761 evaluable mismatches whose first difference is inside the current-engine output signature for the detected run. This is a pronunciation-sensitive UEB review cohort, not an engine routing rule. Current standalone-uppercase measurement: 62411 candidates, 48757 exact controls, 13654 mismatches, and 13268 members in the actual `pending_rule_review` subcluster. Its high frequency does not make it causal: the same input shape is exact in many cases, and a sentence containing the shape may first differ at another Roman, numeric, or punctuation structure. No engine change is inferred from this cohort. diff --git a/libs/braillify/examples/nikl_corpus_analyze.rs b/libs/braillify/examples/nikl_corpus_analyze.rs index 5199c537..935c3157 100644 --- a/libs/braillify/examples/nikl_corpus_analyze.rs +++ b/libs/braillify/examples/nikl_corpus_analyze.rs @@ -566,9 +566,13 @@ const KOREAN_INLINE_PARENTHESIZED_OPERATOR: &str = "korean_inline_parenthesized_single_arithmetic_operator"; const TIGHT_TRIANGLE_BEFORE_KOREAN: &str = "tight_triangle_mark_immediately_before_korean"; const ALLCAPS_ROMAN_RUN_CONTAINING_OU: &str = "allcaps_roman_run_containing_ou"; +const SINGLE_CAPITAL_PARENTHESIZED_DIGITS: &str = "single_capital_followed_by_parenthesized_digits"; +const MIXED_ROMAN_KOREAN_BEFORE_HEADWORD_EXPANSION: &str = + "mixed_roman_korean_word_before_uppercase_headword_expansion"; +const UPPERCASE_ROMAN_HYPHEN_DIGITS: &str = "uppercase_roman_run_followed_by_hyphen_digits"; #[derive(Clone, Copy, Debug, PartialEq, Eq)] -struct AllcapsRomanRun { +struct InputSpan { start_byte: usize, end_byte: usize, } @@ -578,7 +582,7 @@ struct AllcapsRomanRun { /// This is an input gate for a pronunciation-sensitive UEB diagnostic, not a /// claim that the run is an initialism. Alphanumeric outer boundaries exclude /// fragments of identifiers while retaining parenthesized and standalone runs. -fn allcaps_roman_runs_containing_ou(input: &str) -> Vec { +fn allcaps_roman_runs_containing_ou(input: &str) -> Vec { let bytes = input.as_bytes(); let mut runs = Vec::new(); let mut cursor = 0; @@ -606,7 +610,7 @@ fn allcaps_roman_runs_containing_ou(input: &str) -> Vec { && previous.is_none_or(|ch| !ch.is_ascii_alphanumeric()) && next.is_none_or(|ch| !ch.is_ascii_alphanumeric()) { - runs.push(AllcapsRomanRun { + runs.push(InputSpan { start_byte, end_byte, }); @@ -618,22 +622,72 @@ fn allcaps_roman_runs_containing_ou(input: &str) -> Vec { /// Locates each detected run in the full current-engine output by searching /// for that run's independently encoded signature. This uses neither the /// corpus reference nor a hard-coded braille value. -fn allcaps_ou_actual_ranges(input: &str, actual: &str) -> Vec> { +fn current_engine_signature_ranges( + input: &str, + actual: &str, + spans: &[InputSpan], + leading_boundary_cells: usize, +) -> Vec> { let mut ranges = BTreeSet::new(); - for candidate in allcaps_roman_runs_containing_ou(input) { + for candidate in spans { let run = &input[candidate.start_byte..candidate.end_byte]; let Ok(signature) = braillify::encode_to_unicode(run) else { continue; }; let signature_cells = signature.chars().count(); for (start_byte, _) in actual.match_indices(&signature) { - let start = actual[..start_byte].chars().count(); - ranges.insert((start, start + signature_cells)); + let signature_start = actual[..start_byte].chars().count(); + let start = signature_start.saturating_sub(leading_boundary_cells); + ranges.insert((start, signature_start + signature_cells)); } } ranges.into_iter().map(|(start, end)| start..end).collect() } +/// Produces the current mixed-Korean routing signature for a candidate. This +/// differs from encoding the candidate in isolation when the pure-English UEB +/// preflight owns the isolated text but the mixed document routes it as math. +fn korean_context_signature(run: &str) -> Option { + let left = braillify::encode_to_unicode("가").ok()?; + let right = braillify::encode_to_unicode("나").ok()?; + let probe = braillify::encode_to_unicode(&format!("가 {run} 나")).ok()?; + let probe_cells = probe.chars().collect::>(); + let start = left.chars().count(); + let end = probe_cells.len().checked_sub(right.chars().count())?; + let middle = probe_cells.get(start..end)?; + let first_content = middle.iter().position(|cell| *cell != '⠀')?; + let last_content = middle.iter().rposition(|cell| *cell != '⠀')?; + Some(middle[first_content..=last_content].iter().collect()) +} + +fn korean_context_signature_ranges( + input: &str, + actual: &str, + spans: &[InputSpan], + leading_boundary_cells: usize, +) -> Vec> { + let mut ranges = BTreeSet::new(); + for candidate in spans { + let run = &input[candidate.start_byte..candidate.end_byte]; + let Some(signature) = korean_context_signature(run) else { + continue; + }; + let signature_cells = signature.chars().count(); + for (start_byte, _) in actual.match_indices(&signature) { + let signature_start = actual[..start_byte].chars().count(); + ranges.insert(( + signature_start.saturating_sub(leading_boundary_cells), + signature_start + signature_cells, + )); + } + } + ranges.into_iter().map(|(start, end)| start..end).collect() +} + +fn allcaps_ou_actual_ranges(input: &str, actual: &str) -> Vec> { + current_engine_signature_ranges(input, actual, &allcaps_roman_runs_containing_ou(input), 0) +} + fn first_difference_in_allcaps_ou_run(item: &EncodedCase) -> bool { let Ok(actual) = &item.actual else { return false; @@ -647,14 +701,152 @@ fn first_difference_in_allcaps_ou_run(item: &EncodedCase) -> bool { .any(|range| range.contains(&first_difference)) } +/// Finds a standalone single capital immediately followed by a non-empty, +/// closed ASCII-digit parenthetical, such as `A(14)`. The span is deliberately +/// semantic-neutral: prose labels and mathematical function notation can share +/// this surface form. +fn single_capital_parenthesized_digit_spans(input: &str) -> Vec { + let bytes = input.as_bytes(); + let mut spans = Vec::new(); + for (start_byte, ch) in input.char_indices() { + if !ch.is_ascii_uppercase() + || input[..start_byte] + .chars() + .next_back() + .is_some_and(|previous| previous.is_ascii_alphanumeric()) + { + continue; + } + let open = start_byte + 1; + if bytes.get(open) != Some(&b'(') { + continue; + } + let mut cursor = open + 1; + let digit_start = cursor; + while bytes.get(cursor).is_some_and(u8::is_ascii_digit) { + cursor += 1; + } + if cursor == digit_start || bytes.get(cursor) != Some(&b')') { + continue; + } + let end_byte = cursor + 1; + if input[end_byte..] + .chars() + .next() + .is_some_and(|next| next.is_ascii_alphanumeric()) + { + continue; + } + spans.push(InputSpan { + start_byte, + end_byte, + }); + } + spans +} + +/// Finds a maximal uppercase ASCII run followed by ASCII hyphen-minus and a +/// non-empty digit run, such as `D-100`, `F-35`, or `AH-64`. Identifier and +/// subtraction readings deliberately remain separate semantic possibilities. +fn uppercase_roman_hyphen_digit_spans(input: &str) -> Vec { + let bytes = input.as_bytes(); + let mut spans = Vec::new(); + let mut cursor = 0; + while cursor < bytes.len() { + if !bytes[cursor].is_ascii_uppercase() { + cursor += input[cursor..] + .chars() + .next() + .expect("cursor must remain on a character boundary") + .len_utf8(); + continue; + } + let start_byte = cursor; + while bytes.get(cursor).is_some_and(u8::is_ascii_uppercase) { + cursor += 1; + } + if input[..start_byte] + .chars() + .next_back() + .is_some_and(|previous| previous.is_ascii_alphanumeric()) + || bytes.get(cursor) != Some(&b'-') + { + continue; + } + cursor += 1; + let digit_start = cursor; + while bytes.get(cursor).is_some_and(u8::is_ascii_digit) { + cursor += 1; + } + if cursor == digit_start + || input[cursor..] + .chars() + .next() + .is_some_and(|next| next.is_ascii_alphanumeric()) + { + continue; + } + spans.push(InputSpan { + start_byte, + end_byte: cursor, + }); + } + spans +} + +fn first_difference_in_signature_spans( + item: &EncodedCase, + spans: &[InputSpan], + leading_boundary_cells: usize, +) -> bool { + let Ok(actual) = &item.actual else { + return false; + }; + if actual == &item.located.case.unicode { + return false; + } + let first_difference = first_difference_cell(&item.located.case.unicode, actual); + current_engine_signature_ranges( + &item.located.case.input, + actual, + spans, + leading_boundary_cells, + ) + .into_iter() + .any(|range| range.contains(&first_difference)) +} + +fn first_difference_in_korean_context_signature_spans( + item: &EncodedCase, + spans: &[InputSpan], + leading_boundary_cells: usize, +) -> bool { + let Ok(actual) = &item.actual else { + return false; + }; + if actual == &item.located.case.unicode { + return false; + } + let first_difference = first_difference_cell(&item.located.case.unicode, actual); + korean_context_signature_ranges( + &item.located.case.input, + actual, + spans, + leading_boundary_cells, + ) + .into_iter() + .any(|range| range.contains(&first_difference)) +} + /// Input-only candidate gate for acronym expansions such as /// `HCA(Home Connectivity Alliance)`. /// /// This is deliberately an analyzer diagnostic, not an engine rule. Requiring /// only ASCII letters and spaces inside the closed parenthesis also excludes /// visible operators, subscript/superscript notation, and nested parentheses. -fn has_uppercase_roman_headword_expansion(input: &str) -> bool { +fn uppercase_roman_headword_expansion_spans(input: &str) -> Vec { let bytes = input.as_bytes(); + let mut spans = Vec::new(); for (open, _) in input.match_indices('(') { let mut headword_start = open; while headword_start > 0 && bytes[headword_start - 1].is_ascii_alphabetic() { @@ -680,10 +872,44 @@ fn has_uppercase_roman_headword_expansion(input: &str) -> bool { } if contents.split_ascii_whitespace().count() >= 2 { - return true; + spans.push(InputSpan { + start_byte: headword_start, + end_byte: open, + }); } } - false + spans +} + +fn has_uppercase_roman_headword_expansion(input: &str) -> bool { + !uppercase_roman_headword_expansion_spans(input).is_empty() +} + +/// Narrows the HCA-style diagnostic to a position-sensitive mode boundary: +/// a preceding whitespace-delimited word contains Roman letters and ends in +/// Korean, followed by an uppercase headword expansion. This identifies a +/// mixed Roman+Korean particle boundary without naming a particular particle. +fn mixed_roman_korean_before_headword_expansion_spans(input: &str) -> Vec { + uppercase_roman_headword_expansion_spans(input) + .into_iter() + .filter(|span| { + let before = &input[..span.start_byte]; + if !before.chars().next_back().is_some_and(char::is_whitespace) { + return false; + } + let previous_word = before + .trim_end_matches(char::is_whitespace) + .rsplit(char::is_whitespace) + .next() + .unwrap_or(""); + previous_word.chars().any(|ch| ch.is_ascii_alphabetic()) + && previous_word.chars().any(is_korean_script) + && previous_word + .chars() + .next_back() + .is_some_and(is_korean_script) + }) + .collect() } /// Finds a maximal, alphanumeric-delimited ASCII letter run of two or more @@ -1165,6 +1391,14 @@ fn analyze( KOREAN_INLINE_PARENTHESIZED_OPERATOR.to_string(), PendingRuleReviewClusterStats::default(), ), + ( + MIXED_ROMAN_KOREAN_BEFORE_HEADWORD_EXPANSION.to_string(), + PendingRuleReviewClusterStats::default(), + ), + ( + SINGLE_CAPITAL_PARENTHESIZED_DIGITS.to_string(), + PendingRuleReviewClusterStats::default(), + ), ( STANDALONE_UPPERCASE_ROMAN_WORD.to_string(), PendingRuleReviewClusterStats::default(), @@ -1177,6 +1411,10 @@ fn analyze( UPPERCASE_ROMAN_HEADWORD_EXPANSION.to_string(), PendingRuleReviewClusterStats::default(), ), + ( + UPPERCASE_ROMAN_HYPHEN_DIGITS.to_string(), + PendingRuleReviewClusterStats::default(), + ), ]); let mut pending_first_difference_cell_transitions = BTreeMap::new(); let mut exact = 0usize; @@ -1245,6 +1483,25 @@ fn analyze( !inline_parenthesized_operators(&item.located.case.input).is_empty(), Some(first_difference_in_inline_parenthesized_operator(item)), ), + ( + MIXED_ROMAN_KOREAN_BEFORE_HEADWORD_EXPANSION, + !mixed_roman_korean_before_headword_expansion_spans(&item.located.case.input) + .is_empty(), + Some(first_difference_in_signature_spans( + item, + &mixed_roman_korean_before_headword_expansion_spans(&item.located.case.input), + 1, + )), + ), + ( + SINGLE_CAPITAL_PARENTHESIZED_DIGITS, + !single_capital_parenthesized_digit_spans(&item.located.case.input).is_empty(), + Some(first_difference_in_signature_spans( + item, + &single_capital_parenthesized_digit_spans(&item.located.case.input), + 1, + )), + ), ( STANDALONE_UPPERCASE_ROMAN_WORD, has_standalone_uppercase_roman_word(&item.located.case.input), @@ -1260,6 +1517,15 @@ fn analyze( has_uppercase_roman_headword_expansion(&item.located.case.input), None, ), + ( + UPPERCASE_ROMAN_HYPHEN_DIGITS, + !uppercase_roman_hyphen_digit_spans(&item.located.case.input).is_empty(), + Some(first_difference_in_korean_context_signature_spans( + item, + &uppercase_roman_hyphen_digit_spans(&item.located.case.input), + 1, + )), + ), ] { if !present { continue; @@ -1524,6 +1790,18 @@ fn markdown(report: &AnalysisReport) -> String { contents are two or more ASCII Roman words separated only by spaces. Because the \ contents admit only letters and spaces, visible operators, subscript/superscript \ notation, and nested parentheses are excluded deterministically. The \ + `mixed_roman_korean_word_before_uppercase_headword_expansion` gate further requires a \ + whitespace-delimited preceding word that contains both ASCII Roman and Korean and ends \ + in Korean. It locates the following uppercase headword itself, including its immediately \ + preceding emitted cell, so an earlier Roman entry in the same sentence cannot satisfy \ + the output audit. The `single_capital_followed_by_parenthesized_digits` gate requires a \ + standalone capital, a non-empty closed ASCII-digit parenthetical, and alphanumeric outer \ + boundaries. It likewise includes the emitted entry-boundary cell in localization. The \ + `uppercase_roman_run_followed_by_hyphen_digits` gate requires a maximal uppercase ASCII \ + run, literal ASCII hyphen-minus, a non-empty digit run, and alphanumeric outer \ + boundaries. Its localized range includes the current encoded run and its immediately \ + preceding cell, keeping `F-35`-style routing separate from both parenthesized digits and \ + headword expansions. The \ `standalone_multi_character_uppercase_roman_word` gate finds maximal ASCII-letter runs \ of two or more capitals with non-alphanumeric boundaries; a run immediately followed \ by `(` is excluded so the HCA-style headword itself is not counted by both gates. The \ @@ -1649,6 +1927,28 @@ fn markdown(report: &AnalysisReport) -> String { meanings can have the same input surface form. The observed `COO`/`NSC`/`MOU` output \ differences therefore do not justify disabling either algorithm without independent \ semantic evidence.\n\n\ + Two narrower cohorts separate causes hidden by the frequent `U+2834 -> U+2800` cell \ + transition. `single_capital_followed_by_parenthesized_digits` reproduces the current \ + math-token routing of forms such as `A(14)`: Hangeul rules 29 and 34 govern a Roman \ + section and a parenthesized Roman form, while math rule 6 independently defines \ + parenthesized function notation such as `f(x)`. A capital and numeric argument do not \ + remove that mathematical counterexample, so this localized routing difference remains \ + pending rather than authorizing an input-shape exception. \ + `mixed_roman_korean_word_before_uppercase_headword_expansion` separately targets the \ + next Roman headword after a mixed Roman+Korean word (for example, a Korean particle \ + attached to the previous Roman name). Its range is anchored to that later headword, not \ + to the earlier Roman entry. Nevertheless, the closed multiword parenthetical shape still \ + cannot exclude every mathematical interpretation under math rules 6, 11, 12, and 45, \ + as recorded for the broader HCA-style cohort. The headword shape is therefore not added \ + to engine routing; the two causes and their controls remain separately measurable.\n\n\ + The uppercase-Roman hyphen-digits cohort is a third independent cause. Hangeul rule 35 \ + explicitly shows `D-100` as a Roman-and-number continuation (2024 Korean-rules PDF \ + p.29), while math rule 2 defines subtraction and the math chapters allow uppercase Roman \ + variables. The surface form alone therefore does not prove whether `F-35` is an \ + identifier or a subtraction expression. This cohort records the current operator-routing \ + signature and exact controls without merging it into either `A(14)` or HCA-style \ + diagnostics. No engine change is made without both a safe semantic boundary and exact \ + controls.\n\n\ The all-caps `OU` cohort isolates a frequent output transition without treating the \ reference as a rule. Hangeul rules 28, 29, and 32 delegate Roman-letter content to UEB \ (2024 Korean-rules PDF p.25 and following rules). \ @@ -1697,6 +1997,73 @@ fn markdown(report: &AnalysisReport) -> String { not prove a reference correct; it only means that this deterministic contradiction test \ did not fire.\n", ); + if let Some(stats) = report + .pending_rule_review_clusters + .get(UPPERCASE_ROMAN_HYPHEN_DIGITS) + { + let pending = stats + .mismatch_primary_classes + .get("pending_rule_review") + .copied() + .unwrap_or(0); + text.push_str(&format!( + "\nCurrent uppercase-Roman hyphen-digits measurement: {} candidates, {} exact \ + controls, {} mismatches, {pending} members in the actual `pending_rule_review` \ + subcluster, and {}/{} evaluable mismatches whose first difference is inside the \ + target run plus its entry boundary. It remains distinct from parenthesized digits \ + and headword expansions; no engine change is inferred.\n", + stats.candidates, + stats.exact, + stats.mismatch, + stats.first_difference_in_output_signature, + stats.output_signature_mismatches_evaluated + )); + } + if let Some(stats) = report + .pending_rule_review_clusters + .get(SINGLE_CAPITAL_PARENTHESIZED_DIGITS) + { + let pending = stats + .mismatch_primary_classes + .get("pending_rule_review") + .copied() + .unwrap_or(0); + text.push_str(&format!( + "\nCurrent single-capital parenthesized-digits measurement: {} candidates, {} \ + exact controls, {} mismatches, {pending} members in the actual \ + `pending_rule_review` subcluster, and {}/{} evaluable mismatches whose first \ + difference is inside the target run plus its entry boundary. No engine change is \ + inferred from the ambiguous prose/function surface form.\n", + stats.candidates, + stats.exact, + stats.mismatch, + stats.first_difference_in_output_signature, + stats.output_signature_mismatches_evaluated + )); + } + if let Some(stats) = report + .pending_rule_review_clusters + .get(MIXED_ROMAN_KOREAN_BEFORE_HEADWORD_EXPANSION) + { + let pending = stats + .mismatch_primary_classes + .get("pending_rule_review") + .copied() + .unwrap_or(0); + text.push_str(&format!( + "\nCurrent mixed Roman+Korean boundary before uppercase headword-expansion \ + measurement: {} candidates, {} exact controls, {} mismatches, {pending} members in \ + the actual `pending_rule_review` subcluster, and {}/{} evaluable mismatches whose \ + first difference is localized to the later headword's entry boundary/output. The \ + detector cannot be satisfied by the earlier Roman entry. No HCA-shaped engine \ + routing rule is introduced.\n", + stats.candidates, + stats.exact, + stats.mismatch, + stats.first_difference_in_output_signature, + stats.output_signature_mismatches_evaluated + )); + } if let Some(stats) = report .pending_rule_review_clusters .get(ALLCAPS_ROMAN_RUN_CONTAINING_OU) @@ -2293,6 +2660,94 @@ mod tests { assert_eq!(has_uppercase_roman_headword_expansion(input), expected); } + #[rstest::rstest] + #[case::person_label("학생 A(14)양", vec!["A(14)"])] + #[case::standalone("A(1)", vec!["A(1)"])] + #[case::multiple("A(11)과 B(15)", vec!["A(11)", "B(15)"])] + #[case::lowercase("a(14)", vec![])] + #[case::multi_capital("AB(14)", vec![])] + #[case::empty_parenthetical("A()", vec![])] + #[case::letter_argument("A(x)", vec![])] + #[case::ascii_suffix("A(14)b", vec![])] + fn detects_single_capital_parenthesized_digits( + #[case] input: &str, + #[case] expected: Vec<&str>, + ) { + let actual = single_capital_parenthesized_digit_spans(input) + .into_iter() + .map(|span| &input[span.start_byte..span.end_byte]) + .collect::>(); + assert_eq!(actual, expected); + } + + #[rstest::rstest] + #[case::single_capital("미 F-35 전투기", vec!["F-35"])] + #[case::multi_capital("육군 AH-64 헬기", vec!["AH-64"])] + #[case::rule_35_shape("수능 D-100일", vec!["D-100"])] + #[case::multiple("F-35와 AH-64", vec!["F-35", "AH-64"])] + #[case::lowercase("x-1", vec![])] + #[case::missing_digits("F-", vec![])] + #[case::unicode_minus("F−35", vec![])] + #[case::ascii_suffix("F-35A", vec![])] + fn detects_uppercase_roman_hyphen_digits(#[case] input: &str, #[case] expected: Vec<&str>) { + let actual = uppercase_roman_hyphen_digit_spans(input) + .into_iter() + .map(|span| &input[span.start_byte..span.end_byte]) + .collect::>(); + assert_eq!(actual, expected); + } + + #[test] + fn locates_hyphen_digit_run_through_mixed_korean_routing() { + let input = "한글 F-35 전투기"; + let spans = uppercase_roman_hyphen_digit_spans(input); + let actual = braillify::encode_to_unicode(input).expect("probe must encode"); + let ranges = korean_context_signature_ranges(input, &actual, &spans, 1); + + assert_eq!(spans.len(), 1); + assert_eq!(&input[spans[0].start_byte..spans[0].end_byte], "F-35"); + assert_eq!(ranges.len(), 1); + assert!(ranges[0].start < ranges[0].end); + } + + #[rstest::rstest] + #[case::mixed_particle_before_expansion( + "Matter와 HCA(Home Connectivity Alliance) 표준", + vec!["HCA"] + )] + #[case::another_korean_suffix("Device는 ABC(Alpha Beta Company) 규격", vec!["ABC"])] + #[case::korean_only_previous("기기와 HCA(Home Connectivity Alliance) 표준", vec![])] + #[case::roman_only_previous("Matter HCA(Home Connectivity Alliance) 표준", vec![])] + #[case::no_space_boundary("Matter와HCA(Home Connectivity Alliance)", vec![])] + #[case::single_parenthetical_word("Matter와 HCA(Alliance)", vec![])] + fn detects_mixed_roman_korean_boundary_before_headword_expansion( + #[case] input: &str, + #[case] expected: Vec<&str>, + ) { + let actual = mixed_roman_korean_before_headword_expansion_spans(input) + .into_iter() + .map(|span| &input[span.start_byte..span.end_byte]) + .collect::>(); + assert_eq!(actual, expected); + } + + #[test] + fn locates_later_headword_instead_of_earlier_roman_entry() { + let input = "한글 Matter와 HCA(Home Connectivity Alliance) 표준"; + let spans = mixed_roman_korean_before_headword_expansion_spans(input); + let actual = braillify::encode_to_unicode(input).expect("probe must encode"); + let ranges = current_engine_signature_ranges(input, &actual, &spans, 1); + let first_roman_entry = actual + .chars() + .position(|cell| cell == '⠴') + .expect("Matter must have an earlier Roman entry"); + + assert_eq!(spans.len(), 1); + assert_eq!(&input[spans[0].start_byte..spans[0].end_byte], "HCA"); + assert_eq!(ranges.len(), 1); + assert!(first_roman_entry < ranges[0].start); + } + #[rstest::rstest] #[case::parenthesized_initialism("업무협약(MOU)을", vec!["MOU"])] #[case::standalone_word("SOUTH KOREA", vec!["SOUTH"])] From 0cff759e97c83ec573b547d194f206891e99dc4a Mon Sep 17 00:00:00 2001 From: devfive Date: Tue, 1 Sep 2026 07:55:10 +0900 Subject: [PATCH 18/77] Classify rule 34 corpus reference ordering --- docs/corpus-analysis/NIKL_2025_V1.md | 529 +++++++++++++++--- .../braillify/examples/nikl_corpus_analyze.rs | 373 +++++++++++- 2 files changed, 791 insertions(+), 111 deletions(-) diff --git a/docs/corpus-analysis/NIKL_2025_V1.md b/docs/corpus-analysis/NIKL_2025_V1.md index 5851c969..4ff5c1fe 100644 --- a/docs/corpus-analysis/NIKL_2025_V1.md +++ b/docs/corpus-analysis/NIKL_2025_V1.md @@ -20,17 +20,19 @@ Primary classes are evidence gates, not permissions to change the engine. `imple | Primary class | Count | |---|---:| | `comparison_method` | 303 | +| `corpus_suspect` | 1091 | | `exact` | 65514 | -| `pending_rule_review` | 17520 | +| `pending_rule_review` | 16429 | | `unsupported_character_review` | 191 | | Reproducible reason | Count | |---|---:| | `exact` | 65514 | -| `foreign_text_rule_review` | 17341 | +| `foreign_text_rule_review` | 16250 | | `nfkc_input_equivalent` | 303 | | `number_rule_review` | 171 | | `punctuation_rule_review` | 8 | +| `rule34_roman_indicator_before_opening_parenthesis` | 1091 | | `unsupported_character_review` | 191 | ## Pending first-difference cell transitions @@ -41,24 +43,24 @@ This ranking is a diagnostic selector, not an implementation rule. It counts onl |---:|---|---:| | 1 | `U+2834 ⠴ -> U+2800 ⠀` | 2137 | | 2 | `U+2815 ⠕ -> U+2833 ⠳` | 1311 | -| 3 | `U+2834 ⠴ -> U+2826 ⠦` | 1119 | -| 4 | `U+2830 ⠰ -> U+2820 ⠠` | 1015 | -| 5 | `U+2820 ⠠ -> U+2830 ⠰` | 790 | -| 6 | `U+2810 ⠐ -> U+2832 ⠲` | 656 | -| 7 | `U+2832 ⠲ -> U+2834 ⠴` | 654 | -| 8 | `U+283C ⠼ -> U+2800 ⠀` | 466 | -| 9 | `U+2820 ⠠ -> U+2834 ⠴` | 453 | -| 10 | `U+2826 ⠦ -> U+2834 ⠴` | 446 | -| 11 | `U+280E ⠎ -> U+280C ⠌` | 443 | -| 12 | `U+2826 ⠦ -> U+2800 ⠀` | 422 | -| 13 | `U+2808 ⠈ -> U+2832 ⠲` | 409 | -| 14 | `U+2801 ⠁ -> U+281C ⠜` | 381 | -| 15 | `U+2800 ⠀ -> U+2807 ⠇` | 360 | -| 16 | `U+2820 ⠠ -> U+2832 ⠲` | 357 | -| 17 | `U+2834 ⠴ -> U+2830 ⠰` | 321 | -| 18 | `U+2811 ⠑ -> U+282B ⠫` | 320 | -| 19 | `U+2832 ⠲ -> U+2838 ⠸` | 275 | -| 20 | `U+2800 ⠀ -> U+2832 ⠲` | 252 | +| 3 | `U+2830 ⠰ -> U+2820 ⠠` | 1015 | +| 4 | `U+2820 ⠠ -> U+2830 ⠰` | 790 | +| 5 | `U+2810 ⠐ -> U+2832 ⠲` | 656 | +| 6 | `U+2832 ⠲ -> U+2834 ⠴` | 654 | +| 7 | `U+283C ⠼ -> U+2800 ⠀` | 466 | +| 8 | `U+2820 ⠠ -> U+2834 ⠴` | 453 | +| 9 | `U+2826 ⠦ -> U+2834 ⠴` | 446 | +| 10 | `U+280E ⠎ -> U+280C ⠌` | 443 | +| 11 | `U+2826 ⠦ -> U+2800 ⠀` | 422 | +| 12 | `U+2808 ⠈ -> U+2832 ⠲` | 409 | +| 13 | `U+2801 ⠁ -> U+281C ⠜` | 381 | +| 14 | `U+2800 ⠀ -> U+2807 ⠇` | 360 | +| 15 | `U+2820 ⠠ -> U+2832 ⠲` | 357 | +| 16 | `U+2834 ⠴ -> U+2830 ⠰` | 321 | +| 17 | `U+2811 ⠑ -> U+282B ⠫` | 320 | +| 18 | `U+2832 ⠲ -> U+2838 ⠸` | 275 | +| 19 | `U+2800 ⠀ -> U+2832 ⠲` | 252 | +| 20 | `U+2810 ⠐ -> U+2802 ⠂` | 197 | ### `U+2834 ⠴ -> U+2800 ⠀` @@ -106,29 +108,6 @@ This ranking is a diagnostic selector, not an implementation rule. It counts onl - first differing cell (zero-based): 185 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -### `U+2834 ⠴ -> U+2826 ⠦` - -- `sentence_01.json` #14: 새롭게 선보인 DRX 브랜딩과 팀 컬러는 2023년 1월 1일부터 DRX의 온오프라인 콘텐츠, 엠디(MD), SNS, 유튜브 등 모든 제작물에 적용될 예정이다. - - expected: `⠰⠪⠐⠀⠝⠢⠊⠕⠴⠐⠣⠠⠠⠍⠙⠐⠜⠂⠀⠠⠠⠎⠝⠎` - - actual: `⠰⠪⠐⠀⠝⠢⠊⠕⠦⠄⠴⠠⠠⠍⠙⠠⠴⠐⠀⠰⠠⠠⠎⠝` - - first differing cell (zero-based): 97 - - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_02.json` #605: 전국적으로 강력범죄가 연이어 발생하면서 천안시가 시민 안전을 위해 공원, 산책로, 등산로 등에 설치된 방범용 폐쇄회로(CC)TV 전수조사를 실시한다. - - expected: `⠌⠠⠧⠗⠚⠽⠐⠥⠴⠐⠣⠠⠠⠉⠉⠐⠜⠠⠠⠞⠧⠲⠀⠨` - - actual: `⠌⠠⠧⠗⠚⠽⠐⠥⠦⠄⠴⠠⠉⠠⠉⠠⠴⠰⠠⠠⠞⠧⠲⠀` - - first differing cell (zero-based): 115 - - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_03.json` #37: 경찰은 주변 폐쇄회로(CC)TV 등을 분석해 A씨 동선을 추적했고 14일 오후 경기도 양주의 한 주택에서 A씨를 긴급체포했다. - - expected: `⠌⠠⠧⠗⠚⠽⠐⠥⠴⠐⠣⠠⠠⠉⠉⠐⠜⠠⠠⠞⠧⠲⠀⠊` - - actual: `⠌⠠⠧⠗⠚⠽⠐⠥⠦⠄⠴⠠⠉⠠⠉⠠⠴⠰⠠⠠⠞⠧⠲⠀` - - first differing cell (zero-based): 21 - - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_04.json` #42: 경찰은 폐쇄회로(CC)TV 분석과 쇠구슬 판매 업체 탐문 수사, 국과수 발사지점 방향성 감정 등을 통해 발사 의심 세대를 특정해 이날 피의자 A씨를 자택에서 검거했다. - - expected: `⠌⠠⠧⠗⠚⠽⠐⠥⠴⠐⠣⠠⠠⠉⠉⠐⠜⠠⠠⠞⠧⠲⠀⠘` - - actual: `⠌⠠⠧⠗⠚⠽⠐⠥⠦⠄⠴⠠⠉⠠⠉⠠⠴⠰⠠⠠⠞⠧⠲⠀` - - first differing cell (zero-based): 16 - - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` - ### `U+2830 ⠰ -> U+2820 ⠠` - `sentence_01.json` #310: 낸드플래시 반도체 세계 2위인 일본 키옥시아와 4위인 미국 웨스턴디지털(WD) 합병설에 반도체 업계가 촉각을 곤두 세우고 있다. @@ -290,15 +269,296 @@ This ranking is a diagnostic selector, not an implementation rule. It counts onl - first differing cell (zero-based): 54 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +### `U+280E ⠎ -> U+280C ⠌` + +- `sentence_01.json` #167: 올해 전 세계 반도체 시장 전망도 암울하다. 세계반도체시장통계기구(WSTS)에 따르면 올해 글로벌 반도체 매출은 5천565억 달러(약 706조5천880억원)로 지난해(5천801억달러) 대비 4.1% 감소할 것으로 조사됐다. + - expected: `⠈⠍⠦⠄⠴⠠⠠⠺⠎⠞⠎⠠⠴⠝⠀⠠⠊⠐⠪⠑⠡⠀⠥⠂` + - actual: `⠈⠍⠦⠄⠴⠠⠠⠺⠌⠎⠠⠴⠝⠀⠠⠊⠐⠪⠑⠡⠀⠥⠂⠚` + - first differing cell (zero-based): 67 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_02.json` #244: 충남대학교 이진숙 총장을 비롯한 관계자들은 5월 19일, 베트남 최고의 국립대인 하노이과학기술대학(HUST), 베트남국립농업대학(VNUA)을 연이어 방문해 ‘오픈 캠퍼스(open campus)’ 설립을 골자로 한 협정을 체결했다. + - expected: `⠁⠦⠄⠴⠠⠠⠓⠥⠎⠞⠠⠴⠐⠀⠘⠝⠓⠪⠉⠢⠈⠍⠁⠐` + - actual: `⠁⠦⠄⠴⠠⠠⠓⠥⠌⠠⠴⠐⠀⠘⠝⠓⠪⠉⠢⠈⠍⠁⠐⠕` + - first differing cell (zero-based): 101 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_03.json` #142: 수많은 작품 OST를 책임지며 실력파 프로듀서로 인정받고 있는 필승불패W, 리디아(Lydia), 장석원이 힘을 합쳐 만든 곡으로 웰메이드 OST 탄생을 예감케 한다. + - expected: `⠙⠍⠢⠀⠴⠠⠠⠕⠎⠞⠲⠐⠮⠀⠰⠗⠁⠕⠢⠨⠕⠑⠱⠀` + - actual: `⠙⠍⠢⠀⠴⠠⠠⠕⠌⠲⠐⠮⠀⠰⠗⠁⠕⠢⠨⠕⠑⠱⠀⠠` + - first differing cell (zero-based): 17 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_04.json` #375: 윤석열 대통령은 30일 캐서린 타이 미국 무역대표부(USTR) 대표를 만나 미 인플레이션감축법(IRA)·반도체지원법(CHIPS Act)과 관련해 “미국에 진출하는 한국 기업들이 어려움을 겪지 않도록 우호적인 방향으로 배려해 달라”고 당부했다. + - expected: `⠘⠍⠦⠄⠴⠠⠠⠥⠎⠞⠗⠠⠴⠀⠊⠗⠙⠬⠐⠮⠀⠑⠒⠉` + - actual: `⠘⠍⠦⠄⠴⠠⠠⠥⠌⠗⠠⠴⠀⠊⠗⠙⠬⠐⠮⠀⠑⠒⠉⠀` + - first differing cell (zero-based): 53 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` + +## Residual first-difference transitions after localized cohorts + +This ranking removes only cases whose first difference is inside an existing output-localized cohort. Broad input-only traits are not exclusion masks. The residual table therefore prioritizes new causes without hiding a mismatch merely because an unrelated structure coexists elsewhere in its sentence. + +| Rank | Expected → actual first cell | Residual cases | +|---:|---|---:| +| 1 | `U+2830 ⠰ -> U+2820 ⠠` | 1015 | +| 2 | `U+2820 ⠠ -> U+2830 ⠰` | 790 | +| 3 | `U+2810 ⠐ -> U+2832 ⠲` | 656 | +| 4 | `U+2832 ⠲ -> U+2834 ⠴` | 654 | +| 5 | `U+2834 ⠴ -> U+2800 ⠀` | 543 | +| 6 | `U+283C ⠼ -> U+2800 ⠀` | 466 | +| 7 | `U+2820 ⠠ -> U+2834 ⠴` | 453 | +| 8 | `U+2826 ⠦ -> U+2834 ⠴` | 446 | +| 9 | `U+280E ⠎ -> U+280C ⠌` | 443 | +| 10 | `U+2826 ⠦ -> U+2800 ⠀` | 422 | +| 11 | `U+2808 ⠈ -> U+2832 ⠲` | 409 | +| 12 | `U+2801 ⠁ -> U+281C ⠜` | 381 | +| 13 | `U+2820 ⠠ -> U+2832 ⠲` | 357 | +| 14 | `U+2800 ⠀ -> U+2807 ⠇` | 347 | +| 15 | `U+2834 ⠴ -> U+2830 ⠰` | 321 | +| 16 | `U+2811 ⠑ -> U+282B ⠫` | 320 | +| 17 | `U+2832 ⠲ -> U+2838 ⠸` | 275 | +| 18 | `U+2800 ⠀ -> U+2832 ⠲` | 252 | +| 19 | `U+2810 ⠐ -> U+2802 ⠂` | 197 | +| 20 | `U+2817 ⠗ -> U+2820 ⠠` | 160 | + +### Residual `U+2830 ⠰ -> U+2820 ⠠` + +- `sentence_01.json` #310: 낸드플래시 반도체 세계 2위인 일본 키옥시아와 4위인 미국 웨스턴디지털(WD) 합병설에 반도체 업계가 촉각을 곤두 세우고 있다. + - expected: `⠕⠨⠕⠓⠞⠦⠄⠴⠰⠠⠠⠺⠙⠠⠴⠀⠚⠃⠘⠻⠠⠞⠝⠀` + - actual: `⠕⠨⠕⠓⠞⠦⠄⠴⠠⠠⠺⠙⠠⠴⠀⠚⠃⠘⠻⠠⠞⠝⠀⠘` + - first differing cell (zero-based): 70 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_02.json` #99: 황승주 씨는 “기능성 소화불량증, 특히 PDS(식후불편감증후군) 복부팽만감 유형으로 고통 받는 환자들에게 적용가능한 안전하고 높은 치료효율의 한약소재를 개발해 국민건강에 이바지하고자 한다”고 말했다. + - expected: `⠀⠓⠪⠁⠚⠕⠀⠴⠰⠠⠠⠏⠙⠎⠦⠄⠠⠕⠁⠚⠍⠘⠯⠙` + - actual: `⠀⠓⠪⠁⠚⠕⠀⠴⠠⠠⠏⠙⠎⠦⠄⠠⠕⠁⠚⠍⠘⠯⠙⠡` + - first differing cell (zero-based): 45 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_03.json` #30: “챗GPT보다 가벼운 여러 대규모언어모델(LLM)이 출현해 오픈AI 독점 구도에 균열을 내고 있다. 한국어 특화 LLM으로 글로벌 경쟁에 대응하면서 세부 서비스(튜닝) 모델에서 차별성을 확보하는 전략이 중요하다.” + - expected: `⠑⠥⠊⠝⠂⠦⠄⠴⠰⠠⠠⠇⠇⠍⠠⠴⠕⠀⠰⠯⠚⠡⠚⠗` + - actual: `⠑⠥⠊⠝⠂⠦⠄⠴⠠⠠⠇⠇⠍⠠⠴⠕⠀⠰⠯⠚⠡⠚⠗⠀` + - first differing cell (zero-based): 40 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_04.json` #81: SGI는 “산업연관분석을 활용해 우리나라의 대일 수출 증가가 국내총생산(GDP)에 미치는 영향을 계산해 보면 경제성장률은 0.1%포인트 높아질 것”이라고 밝혔다. + - expected: `⠠⠗⠶⠇⠒⠦⠄⠴⠰⠠⠠⠛⠙⠏⠠⠴⠝⠀⠑⠕⠰⠕⠉⠵` + - actual: `⠠⠗⠶⠇⠒⠦⠄⠴⠠⠠⠛⠙⠏⠠⠴⠝⠀⠑⠕⠰⠕⠉⠵⠀` + - first differing cell (zero-based): 72 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` + +### Residual `U+2820 ⠠ -> U+2830 ⠰` + +- `sentence_01.json` #25: 세븐일레븐은 올해 말 정식 버전 오픈을 목표로 하며, 이를 통해 주력상품을 구매하고 배송까지 받을 수 있는 차세대 오프라인을 위한 온라인(O4O) 시스템을 구축해 나갈 계획이다. + - expected: `⠟⠦⠄⠴⠠⠕⠼⠙⠠⠕⠠⠴⠀⠠⠕⠠⠪⠓⠝⠢⠮⠀⠈⠍` + - actual: `⠟⠦⠄⠴⠠⠕⠼⠙⠰⠠⠕⠠⠴⠀⠠⠕⠠⠪⠓⠝⠢⠮⠀⠈` + - first differing cell (zero-based): 129 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_02.json` #313: 순천향대(총장 김승우)는 지난 3일 한국국제협력단(KOICA)과 함께 우즈베키스탄의 수도 타슈겐트에서 스타트업 지원센터 ‘U-ENTER(Uzbekistan Entrepreneurship Innovation Center)’ 준공식을 개최했다고 밝혔다. + - expected: `⠎⠀⠠⠦⠴⠠⠥⠤⠠⠠⠢⠞⠻⠐⠣⠠⠥⠵⠃⠑⠅⠊⠌⠁` + - actual: `⠎⠀⠠⠦⠴⠠⠥⠤⠰⠠⠠⠢⠞⠻⠐⠣⠠⠥⠵⠃⠑⠅⠊⠌` + - first differing cell (zero-based): 131 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_03.json` #47: 이수화학이 미국 과학 기술 엔지니어링 회사 KBR과 전고체 배터리 소재 황화리튬(Li2S)의 상업공정 공동 개발에 나선다. 양사는 이를 위해 공동개발 계약을 체결했다고 20일 밝혔다. + - expected: `⠦⠄⠴⠠⠇⠊⠼⠃⠠⠎⠠⠴⠺⠀⠇⠶⠎⠃⠈⠿⠨⠻⠀⠈` + - actual: `⠦⠄⠴⠠⠇⠊⠼⠃⠰⠠⠎⠠⠴⠺⠀⠇⠶⠎⠃⠈⠿⠨⠻⠀` + - first differing cell (zero-based): 87 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_04.json` #1097: LG유플러스는 5세대 이동통신(5G)과 차량·사물통신(V2X)을 바탕으로 도시환경관리에 최적화된 자율주행 차량 관제시스템을 개발한 뒤 자율주행 기술을 실증할 도시인 ‘리빙랩’에서 4만㎞ 이상 서비스 실증에 나선다. + - expected: `⠟⠦⠄⠴⠠⠧⠼⠃⠠⠭⠠⠴⠮⠀⠘⠓⠶⠪⠐⠥⠀⠊⠥⠠` + - actual: `⠟⠦⠄⠴⠠⠧⠼⠃⠰⠠⠭⠠⠴⠮⠀⠘⠓⠶⠪⠐⠥⠀⠊⠥` + - first differing cell (zero-based): 63 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` + +### Residual `U+2810 ⠐ -> U+2832 ⠲` + +- `sentence_01.json` #57: 아키에이지 워는 원작 아키에이지 개발사 엑스엘게임즈(각자대표 송재경, 최관호)가 개발 중인 신작 PC·모바일 크로스플랫폼 대규모다중접속역할수행게임(MMORPG)이다. 원작의 향수를 자극하는 스토리와 캐릭터, 언리얼 엔진4를 활용한 고퀄리티 그래픽이 특징이다. + - expected: `⠨⠁⠀⠴⠠⠠⠏⠉⠐⠆⠑⠥⠘⠣⠕⠂⠀⠋⠪⠐⠥⠠⠪⠙` + - actual: `⠨⠁⠀⠴⠠⠠⠏⠉⠲⠐⠆⠑⠥⠘⠣⠕⠂⠀⠋⠪⠐⠥⠠⠪` + - first differing cell (zero-based): 92 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_02.json` #10: 설동호 대전교육감은 “학교 교육과정 및 여건에 맞는 인공지능(AI)교육 선도학교 운영을 통해 AI·SW교육 기반 구축과 학생들과 교원들의 미래 디지털 역량이 함양될 것이다.”라고 말했다. + - expected: `⠚⠗⠀⠴⠠⠠⠁⠊⠐⠆⠴⠠⠠⠎⠺⠲⠈⠬⠩⠁⠀⠈⠕⠘` + - actual: `⠚⠗⠀⠴⠠⠠⠁⠊⠲⠐⠆⠴⠎⠺⠲⠈⠬⠩⠁⠀⠈⠕⠘⠒` + - first differing cell (zero-based): 93 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_03.json` #17: 재테크 시장에서 온·오프라인 플랫폼 ‘매경 엠플러스(M+)’가 주목받고 있다. M+는 거시경제, 주식, 부동산, 가상자산 등 재테크의 모든 것을 온라인 사이트를 통해 제공하고 있다. + - expected: `⠎⠠⠪⠦⠄⠴⠠⠍⠐⠖⠠⠴⠴⠄⠫⠀⠨⠍⠑⠭⠘⠔⠈⠥` + - actual: `⠎⠠⠪⠦⠄⠴⠠⠍⠲⠀⠢⠀⠠⠴⠴⠄⠫⠀⠨⠍⠑⠭⠘⠔` + - first differing cell (zero-based): 54 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_04.json` #49: 유럽중앙은행(ECB)이 크레디트스위스(CS)의 유동성 위기에도 ‘빅스텝’(기준금리를 한 번에 0.5%포인트 인상)을 단행하자, 미국 연방준비제도(Fed·연준) 역시 금리 인상을 멈추지 않을 것이라는 전망이 힘을 얻고 있다. + - expected: `⠊⠥⠦⠄⠴⠠⠋⠫⠐⠆⠡⠨⠛⠠⠴⠀⠱⠁⠠⠕⠀⠈⠪⠢` + - actual: `⠊⠥⠦⠄⠴⠠⠋⠫⠲⠐⠆⠡⠨⠛⠠⠴⠀⠱⠁⠠⠕⠀⠈⠪` + - first differing cell (zero-based): 149 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` + +### Residual `U+2832 ⠲ -> U+2834 ⠴` + +- `sentence_01.json` #75: 특히 MKT의 매출이익률이 42.2%에 달했는데, 이는 경쟁사 대비 12.6%포인트(P) 높은 수준이다. 검찰은 한국타이어가 MKT에 몰아준 이익이 한국타이어 총수 일가에 흘러간 것으로 의심하고 있다. + - expected: `⠊⠗⠘⠕⠀⠼⠁⠃⠲⠋⠴⠏⠀⠙⠥⠟⠓⠪⠦⠄⠴⠠⠏⠠` + - actual: `⠊⠗⠘⠕⠀⠼⠁⠃⠴⠲⠋⠴⠏⠀⠙⠥⠟⠓⠪⠦⠄⠴⠠⠏` + - first differing cell (zero-based): 67 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_02.json` #561: 훈련 동안 대전경찰청 주관으로 충무로4가~동부서 3가(1.0km), 큰 마을 4가~용문역(2km), 사이언스빌 3가~읍내 4가(2.2km) 총 3개 구간이 통제된다. + - expected: `⠀⠼⠉⠫⠦⠄⠼⠁⠲⠚⠴⠅⠍⠠⠴⠐⠀⠋⠵⠀⠑⠣⠮⠀` + - actual: `⠀⠼⠉⠫⠦⠄⠼⠁⠴⠲⠚⠰⠅⠍⠠⠴⠐⠀⠋⠵⠀⠑⠣⠮` + - first differing cell (zero-based): 57 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_03.json` #366: 하루 최대 34.3리터(L)의 대용량 제습이 가능하며, 자연 증발 시스템이 적용돼 물통에 모인 물을 비워야 하는 번거로움이 없다. + - expected: `⠰⠽⠊⠗⠀⠼⠉⠙⠲⠉⠐⠕⠓⠎⠦⠄⠴⠠⠇⠠⠴⠺⠀⠊` + - actual: `⠰⠽⠊⠗⠀⠼⠉⠙⠴⠲⠉⠐⠕⠓⠎⠦⠄⠴⠠⠇⠠⠴⠺⠀` + - first differing cell (zero-based): 12 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_04.json` #48: 올트먼은 GPT-4가 기존 ‘GPT-3.5’보다 정확도가 40% 이상 높아졌지만, 이를 정보의 주요 출처로 사용해서는 안된다고도 지적했다. 거짓을 사실처럼 보이게 하는 이른바 ‘환각(hallucination) 현상’에 주의해야 한다는 입장이다. + - expected: `⠠⠠⠛⠏⠞⠤⠼⠉⠲⠑⠴⠄⠘⠥⠊⠀⠨⠻⠚⠧⠁⠊⠥⠫` + - actual: `⠠⠠⠛⠏⠞⠤⠼⠉⠴⠲⠑⠴⠄⠘⠥⠊⠀⠨⠻⠚⠧⠁⠊⠥` + - first differing cell (zero-based): 35 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` + +### Residual `U+2834 ⠴ -> U+2800 ⠀` + +- `sentence_01.json` #35: 삼성전자는 올해 CES에서 77인치 OLED TV를 처음으로 공개할 예정이다. 이는 지난 8월 ‘국제정보디스플레이학술대회(IMID) 2022’에서 삼성디스플레이가 공개한 77인치 퀀텀닷(QD)-OLED 패널을 탑재한 제품이다. + - expected: `⠋⠏⠒⠓⠎⠢⠊⠄⠴⠐⠣⠟⠙⠐⠜⠤⠠⠠⠕⠇⠫⠲⠀⠙` + - actual: `⠋⠏⠒⠓⠎⠢⠊⠄⠀⠀⠦⠠⠠⠟⠙⠴⠔⠠⠠⠕⠇⠑⠙⠀` + - first differing cell (zero-based): 172 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_02.json` #344: 한편 이장우 대전시장은 이틀간의 대만 방문을 마무리하고 싱가포르로 이동해 18일까지 Merck(머크) 사, 국립싱가포르대학 바이오연구단, A-STAR 및 바이오폴리스 등 바이오산업 관련 해외 기업과 연구기관을 방문할 예정이다. + - expected: `⠥⠡⠈⠍⠊⠒⠐⠀⠴⠠⠁⠤⠠⠠⠌⠜⠲⠀⠑⠕⠆⠀⠘⠣` + - actual: `⠥⠡⠈⠍⠊⠒⠐⠀⠀⠠⠁⠔⠠⠠⠎⠞⠁⠗⠀⠀⠑⠕⠆⠀` + - first differing cell (zero-based): 122 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_03.json` #381: 인포테인먼트 시스템 ccNC(connected car Navigation Cockpit)를 최초 탑재하고 무선 소프트웨어 업데이트(OTA) 대상 범위를 주요 편의 기능까지 대폭 확대했다. + - expected: `⠠⠕⠠⠪⠓⠝⠢⠀⠴⠉⠉⠠⠠⠝⠉⠐⠣⠒⠝⠑⠉⠞⠫⠀` + - actual: `⠠⠕⠠⠪⠓⠝⠢⠀⠀⠉⠉⠠⠠⠝⠉⠦⠉⠕⠝⠝⠑⠉⠞⠑` + - first differing cell (zero-based): 19 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_04.json` #1075: 접속 방법은 ‘안심전세포털’ 및 국토부 홈페이지에 안내돼 있다. 줌 URL(ID : 393 490 1984)을 입력하거나 주택도시보증공사(HUG) 유튜브로 접속하면 된다. + - expected: `⠌⠊⠲⠀⠨⠍⠢⠀⠴⠠⠠⠥⠗⠇⠐⠣⠠⠠⠊⠙⠀⠒⠀⠼` + - actual: `⠌⠊⠲⠀⠨⠍⠢⠀⠀⠠⠠⠥⠗⠇⠦⠠⠠⠊⠙⠀⠐⠂⠀⠼` + - first differing cell (zero-based): 70 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` + +### Residual `U+283C ⠼ -> U+2800 ⠀` + +- `sentence_01.json` #426: LG에너지솔루션은 혼다에 앞서 미국 제너럴모터스(GM)과의 배터리 합작법인 ‘얼티엄셀즈’를 설립해 투자를 진행 중이다. 이를 통해 북미 생산 능력을 올해 13GWh 규모에서 2025년 255GWh까지 확대할 예정이다. + - expected: `⠁⠮⠀⠥⠂⠚⠗⠀⠼⠁⠉⠴⠠⠛⠠⠺⠓⠲⠀⠈⠩⠑⠥⠝` + - actual: `⠁⠮⠀⠥⠂⠚⠗⠀⠀⠼⠁⠉⠠⠠⠛⠺⠓⠀⠀⠈⠩⠑⠥⠝` + - first differing cell (zero-based): 156 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_02.json` #68: 조성된 무장애나눔길은 통행이 많은 숲속의집~생태숲학습관 0.73km 구간에 평균 경사도 5% 이하 데크산책로와 황토포장길로 조성됐으며 전국 무장애나눔길 최초로 장애물 없는 생활환경(BF)인증을 추진하고 있다. + - expected: `⠁⠠⠪⠃⠈⠧⠒⠀⠼⠚⠲⠛⠉⠴⠅⠍⠲⠀⠈⠍⠫⠒⠝⠀` + - actual: `⠁⠠⠪⠃⠈⠧⠒⠀⠀⠼⠚⠲⠛⠉⠅⠍⠀⠀⠈⠍⠫⠒⠝⠀` + - first differing cell (zero-based): 62 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_03.json` #59: 이들 중 A씨(31) 등 2명은 지난 3월 25일 김해공항으로 필로폰 968g 등을 팬티 속에 숨겨 입국한 혐의를 받고 있다. + - expected: `⠙⠕⠂⠐⠥⠙⠷⠀⠼⠊⠋⠓⠴⠛⠲⠀⠊⠪⠶⠮⠀⠙⠗⠒` + - actual: `⠙⠕⠂⠐⠥⠙⠷⠀⠀⠼⠊⠋⠓⠛⠀⠀⠊⠪⠶⠮⠀⠙⠗⠒` + - first differing cell (zero-based): 71 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_04.json` #57: 미국지질조사국(USGS)에 따르면 이날 에콰도르 항구도시 과야킬에서 남쪽으로 약 80km 떨어진 지점에서 규모 6.8의 지진이 발생했다. 지진의 깊이는 66㎞다. + - expected: `⠭⠪⠐⠥⠀⠜⠁⠀⠼⠓⠚⠴⠅⠍⠲⠀⠠⠊⠞⠎⠨⠟⠀⠨` + - actual: `⠭⠪⠐⠥⠀⠜⠁⠀⠀⠼⠓⠚⠐⠅⠍⠀⠀⠠⠊⠞⠎⠨⠟⠀` + - first differing cell (zero-based): 79 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` + +### Residual `U+2820 ⠠ -> U+2834 ⠴` + +- `sentence_01.json` #78: 2023년형 Neo QLED는 스마트싱스뿐 아니라 매터(Matter) 기기까지 지원하는 원칩 모듈을 탑재해 별도의 동글을 사용하지 않아도, 다양한 사물인터넷(IoT) 기기를 직접 TV에 연결해 사용할 수 있다. + - expected: `⠻⠀⠴⠠⠝⠑⠕⠀⠠⠠⠟⠇⠑⠙⠲⠉⠵⠀⠠⠪⠑⠓⠪⠠` + - actual: `⠻⠀⠴⠠⠝⠑⠕⠀⠴⠠⠠⠟⠇⠫⠲⠉⠵⠀⠠⠪⠑⠓⠪⠠` + - first differing cell (zero-based): 17 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_02.json` #332: 홍성군청소년수련관(관장 피기용) 진로체험지원센터가 오는 16일 청운대학교 운동장에서 관내 중학교 1학년 대상으로 제9회 홍성군 청소년 진로박람회 ‘꿈(JOB)고 두드림(DO DREAM)’을 진행한다. + - expected: `⠦⠄⠴⠠⠠⠙⠕⠀⠠⠠⠙⠗⠂⠍⠠⠴⠴⠄⠮⠀⠨⠟⠚⠗` + - actual: `⠦⠄⠴⠠⠠⠙⠕⠀⠴⠠⠠⠙⠗⠂⠍⠠⠴⠴⠄⠮⠀⠨⠟⠚` + - first differing cell (zero-based): 183 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_03.json` #302: 건국대 경영전문대학원(KU MBA)은 2015년부터 국제경영대학발전협의회(AACSB) 인증을 보유하고 있다. 고등교육법 시행령에 따라 교육부가 인가한 13개 경영전문대학원 중 하나다. 경영전문대학원인 만큼 엄격하게 교육 품질을 관리한다. + - expected: `⠦⠄⠴⠠⠠⠅⠥⠀⠠⠠⠍⠃⠁⠠⠴⠵⠀⠼⠃⠚⠁⠑⠀⠉` + - actual: `⠦⠄⠴⠠⠠⠅⠥⠀⠴⠠⠠⠍⠃⠁⠠⠴⠵⠀⠼⠃⠚⠁⠑⠀` + - first differing cell (zero-based): 29 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_04.json` #363: 켈리(KELLY)는 ‘KEEP NATUALLY’의 줄임말로 인위적인 것을 최소화하고 자연주의적인 원료, 공법, 맛을 추구한다는 의미를 담고 있다. + - expected: `⠴⠠⠠⠅⠑⠑⠏⠀⠠⠠⠝⠁⠞⠥⠁⠇⠇⠽⠴⠄⠺⠀⠨⠯` + - actual: `⠴⠠⠠⠅⠑⠑⠏⠀⠴⠠⠠⠝⠁⠞⠥⠁⠇⠇⠽⠴⠄⠺⠀⠨` + - first differing cell (zero-based): 30 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` + +### Residual `U+2826 ⠦ -> U+2834 ⠴` + +- `sentence_01.json` #38: 올해 소프트웨어(SW) 업계는 국내 인공지능(AI) 제품·서비스 고도화와 함께 전 산업영역에서 SW영역 확대가 가시화될 것으로 보인다. 또한 블록체인이 웹3(Web3)와 연계해 탈중앙화·탈독점화 경제체계로 진화할 것으로 예상된다. + - expected: `⠟⠕⠀⠏⠗⠃⠼⠉⠦⠄⠴⠠⠺⠑⠃⠼⠉⠠⠴⠧⠀⠡⠈⠌` + - actual: `⠟⠕⠀⠏⠗⠃⠼⠉⠴⠐⠣⠠⠺⠑⠃⠼⠉⠴⠐⠜⠲⠧⠀⠡` + - first differing cell (zero-based): 155 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_02.json` #38: 중국금형전시회(DMC) 및 독일금형전시회 (Moulding Expo)와 함께 세계 3대 규모의 금형산업 전문 전시회이며, 연관산업 전문 전시회를 KOPLAS와 동시 개최를 통한 시너지를 창출했다. + - expected: `⠻⠨⠾⠠⠕⠚⠽⠀⠦⠄⠴⠠⠍⠳⠇⠙⠬⠀⠠⠑⠭⠏⠕⠠` + - actual: `⠻⠨⠾⠠⠕⠚⠽⠀⠴⠐⠣⠠⠍⠳⠇⠙⠬⠀⠠⠑⠭⠏⠕⠐` + - first differing cell (zero-based): 48 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_03.json` #536: 뿐만아니라 윤 대통령은 조 바이든 미국 대통령과 26일(현지시간) 한미 정상회담에서 ‘워싱턴 선언’(Washington Declaration)을 통해 핵협의그룹(NCG)을 신설하는 등 확장억제력을 대폭 강화하는 데 합의했다. + - expected: `⠓⠾⠀⠠⠾⠾⠴⠄⠦⠄⠴⠠⠺⠁⠩⠬⠞⠕⠝⠀⠠⠙⠑⠉` + - actual: `⠓⠾⠀⠠⠾⠾⠴⠄⠴⠐⠣⠠⠺⠁⠩⠬⠞⠕⠝⠀⠠⠙⠑⠉` + - first differing cell (zero-based): 95 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_04.json` #582: 반면 마이크로소프트(MS) 공동창업자인 빌 게이츠는 “(AI 개발 일시 중단은) 문제를 해결하지 못할 것”이라며 “우리가 해야 할 일은 AI의 까다로운 문제들을 파악하는 것”이라고 말했다. + - expected: `⠝⠕⠰⠪⠉⠵⠀⠦⠦⠄⠴⠠⠠⠁⠊⠲⠀⠈⠗⠘⠂⠀⠕⠂` + - actual: `⠝⠕⠰⠪⠉⠵⠀⠦⠴⠐⠣⠠⠠⠁⠊⠲⠀⠈⠗⠘⠂⠀⠕⠂` + - first differing cell (zero-based): 54 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` + +### Residual `U+280E ⠎ -> U+280C ⠌` + +- `sentence_01.json` #167: 올해 전 세계 반도체 시장 전망도 암울하다. 세계반도체시장통계기구(WSTS)에 따르면 올해 글로벌 반도체 매출은 5천565억 달러(약 706조5천880억원)로 지난해(5천801억달러) 대비 4.1% 감소할 것으로 조사됐다. + - expected: `⠈⠍⠦⠄⠴⠠⠠⠺⠎⠞⠎⠠⠴⠝⠀⠠⠊⠐⠪⠑⠡⠀⠥⠂` + - actual: `⠈⠍⠦⠄⠴⠠⠠⠺⠌⠎⠠⠴⠝⠀⠠⠊⠐⠪⠑⠡⠀⠥⠂⠚` + - first differing cell (zero-based): 67 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_02.json` #244: 충남대학교 이진숙 총장을 비롯한 관계자들은 5월 19일, 베트남 최고의 국립대인 하노이과학기술대학(HUST), 베트남국립농업대학(VNUA)을 연이어 방문해 ‘오픈 캠퍼스(open campus)’ 설립을 골자로 한 협정을 체결했다. + - expected: `⠁⠦⠄⠴⠠⠠⠓⠥⠎⠞⠠⠴⠐⠀⠘⠝⠓⠪⠉⠢⠈⠍⠁⠐` + - actual: `⠁⠦⠄⠴⠠⠠⠓⠥⠌⠠⠴⠐⠀⠘⠝⠓⠪⠉⠢⠈⠍⠁⠐⠕` + - first differing cell (zero-based): 101 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_03.json` #142: 수많은 작품 OST를 책임지며 실력파 프로듀서로 인정받고 있는 필승불패W, 리디아(Lydia), 장석원이 힘을 합쳐 만든 곡으로 웰메이드 OST 탄생을 예감케 한다. + - expected: `⠙⠍⠢⠀⠴⠠⠠⠕⠎⠞⠲⠐⠮⠀⠰⠗⠁⠕⠢⠨⠕⠑⠱⠀` + - actual: `⠙⠍⠢⠀⠴⠠⠠⠕⠌⠲⠐⠮⠀⠰⠗⠁⠕⠢⠨⠕⠑⠱⠀⠠` + - first differing cell (zero-based): 17 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_04.json` #375: 윤석열 대통령은 30일 캐서린 타이 미국 무역대표부(USTR) 대표를 만나 미 인플레이션감축법(IRA)·반도체지원법(CHIPS Act)과 관련해 “미국에 진출하는 한국 기업들이 어려움을 겪지 않도록 우호적인 방향으로 배려해 달라”고 당부했다. + - expected: `⠘⠍⠦⠄⠴⠠⠠⠥⠎⠞⠗⠠⠴⠀⠊⠗⠙⠬⠐⠮⠀⠑⠒⠉` + - actual: `⠘⠍⠦⠄⠴⠠⠠⠥⠌⠗⠠⠴⠀⠊⠗⠙⠬⠐⠮⠀⠑⠒⠉⠀` + - first differing cell (zero-based): 53 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` + +### Residual `U+2826 ⠦ -> U+2800 ⠀` + +- `sentence_01.json` #234: 안랩은 블록체인 자회사 안랩블록체인컴퍼니가 웹(Web)3 지갑인 ‘ABC 월렛(Wallet)’ 모바일 버전을 구글플레이와 애플 앱스토어에 출시했다고 10일 발표했다. + - expected: `⠎⠉⠕⠫⠀⠏⠗⠃⠦⠄⠴⠠⠺⠑⠃⠠⠴⠼⠉⠀⠨⠕⠫⠃` + - actual: `⠎⠉⠕⠫⠀⠏⠗⠃⠀⠀⠦⠠⠺⠑⠃⠴⠼⠉⠀⠨⠕⠫⠃⠟` + - first differing cell (zero-based): 44 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_02.json` #168: 5주 과정인 ‘도전’ 프로그램과 5개월 과정인 ‘도전+(플러스)’ 프로그램으로 나뉘어 진행되며, 프로그램 이수 시 1개월에 50만 원씩 최대 300만 원까지 참여 수당과 인센티브를 지원한다. + - expected: `⠠⠦⠊⠥⠨⠾⠀⠢⠦⠄⠙⠮⠐⠎⠠⠪⠠⠴⠴⠄⠀⠙⠪⠐` + - actual: `⠠⠦⠊⠥⠨⠾⠀⠢⠀⠦⠄⠙⠮⠐⠎⠠⠪⠠⠴⠴⠄⠀⠙⠪` + - first differing cell (zero-based): 53 + - current primary/reason: `pending_rule_review` / `number_rule_review` +- `sentence_03.json` #64: SK에코플랜트는 20일 종로구 수송동 본사에서 현대엔지니어링, USNC와 ‘수소 마이크로 허브(H2 Micro Hub)’ 구축을 위한 3자간 업무협약(MOU)을 체결했다고 밝혔다. + - expected: `⠪⠐⠥⠀⠚⠎⠘⠪⠦⠄⠴⠠⠓⠼⠃⠀⠠⠍⠊⠉⠗⠕⠀⠠` + - actual: `⠪⠐⠥⠀⠚⠎⠘⠪⠀⠀⠦⠠⠓⠼⠃⠀⠴⠠⠍⠊⠉⠗⠕⠀` + - first differing cell (zero-based): 91 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_04.json` #185: 과기정통부가 같은 날 발표한 ‘2022년 디지털정보격차 실태조사’ 결과에서는 지난해 저소득층·장애인·농어민·고령층 등 취약계층의 디지털정보화(온라인·모바일·PC 활용) 수준이 국민 평균(100) 대비 76.2 정도로 나타났다. 전년(75.4) 대비 소폭 올랐다. + - expected: `⠌⠊⠲⠀⠨⠾⠉⠡⠦⠄⠼⠛⠑⠲⠙⠠⠴⠀⠊⠗⠘⠕⠀⠠` + - actual: `⠌⠊⠲⠀⠨⠾⠉⠡⠀⠀⠦⠼⠛⠑⠲⠙⠴⠀⠊⠗⠘⠕⠀⠠` + - first differing cell (zero-based): 228 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` + ## Cross-cutting input-only structural cohorts -These are cross-cutting input-only structural cohorts, not new primary classes and not engine routing rules. Candidate selection never changes a case's existing primary class. Only cohort members already classified as `pending_rule_review` form a pending subcluster; exact and other-primary members are controls that retain their existing outcomes. The `uppercase_roman_headword_closed_multiword_parenthetical` gate requires a two-or-more character uppercase ASCII headword immediately followed by a closed parenthesis whose contents are two or more ASCII Roman words separated only by spaces. Because the contents admit only letters and spaces, visible operators, subscript/superscript notation, and nested parentheses are excluded deterministically. The `mixed_roman_korean_word_before_uppercase_headword_expansion` gate further requires a whitespace-delimited preceding word that contains both ASCII Roman and Korean and ends in Korean. It locates the following uppercase headword itself, including its immediately preceding emitted cell, so an earlier Roman entry in the same sentence cannot satisfy the output audit. The `single_capital_followed_by_parenthesized_digits` gate requires a standalone capital, a non-empty closed ASCII-digit parenthetical, and alphanumeric outer boundaries. It likewise includes the emitted entry-boundary cell in localization. The `uppercase_roman_run_followed_by_hyphen_digits` gate requires a maximal uppercase ASCII run, literal ASCII hyphen-minus, a non-empty digit run, and alphanumeric outer boundaries. Its localized range includes the current encoded run and its immediately preceding cell, keeping `F-35`-style routing separate from both parenthesized digits and headword expansions. The `standalone_multi_character_uppercase_roman_word` gate finds maximal ASCII-letter runs of two or more capitals with non-alphanumeric boundaries; a run immediately followed by `(` is excluded so the HCA-style headword itself is not counted by both gates. The `korean_prefixed_closed_allcaps_parenthetical` gate requires an immediately preceding Korean character and a closed body of two or more uppercase ASCII letters. It intentionally contains both acronym annotations (`책임자(COO)`) and scientific formulae (`일산화탄소(CO)`) so their semantic collision remains measurable. The `multi_character_allcaps_roman_runs_joined_by_middle_dot` gate requires two maximal ASCII-letter runs of at least two capitals joined directly by U+00B7, with non-alphanumeric outer boundaries. It records shapes such as `AI·SW` without assigning prose, mathematics, or science semantics. The `korean_inline_parenthesized_single_arithmetic_operator` gate requires an immediate `Korean(` + one rule-45 arithmetic operator + `)Korean` span. Unlike broad coexistence traits, it also locates the current engine's emitted structure and counts a mismatch as signature-local only when the sentence's first differing cell falls inside that output range. The `allcaps_roman_run_containing_ou` gate finds maximal, alphanumeric-delimited uppercase ASCII runs containing adjacent `OU`. It locates the independently encoded run signature in the complete current output and counts only first differences inside that signature as localized. The `tight_triangle_mark_immediately_before_korean` gate requires literal `△한글` with no input space and includes the first following Korean cell in its localized output range, so an observed missing-space difference is measured at the mark boundary. +These are cross-cutting input-only structural cohorts, not new primary classes and not engine routing rules. Candidate selection never changes a case's existing primary class by itself. Only cohort members already classified as `pending_rule_review` form a pending subcluster; exact and other-primary members are controls that retain their existing outcomes. A separate classifier may use independently justified, output-localized PDF evidence, as in the rule-34 three-cell contradiction below. The `uppercase_roman_headword_closed_multiword_parenthetical` gate requires a two-or-more character uppercase ASCII headword immediately followed by a closed parenthesis whose contents are two or more ASCII Roman words separated only by spaces. Because the contents admit only letters and spaces, visible operators, subscript/superscript notation, and nested parentheses are excluded deterministically. The `mixed_roman_korean_word_before_uppercase_headword_expansion` gate further requires a whitespace-delimited preceding word that contains both ASCII Roman and Korean and ends in Korean. It locates the following uppercase headword itself, including its immediately preceding emitted cell, so an earlier Roman entry in the same sentence cannot satisfy the output audit. The `single_capital_followed_by_parenthesized_digits` gate requires a standalone capital, a non-empty closed ASCII-digit parenthetical, and alphanumeric outer boundaries. It likewise includes the emitted entry-boundary cell in localization. The `uppercase_roman_run_followed_by_hyphen_digits` gate requires a maximal uppercase ASCII run, literal ASCII hyphen-minus, a non-empty digit run, and alphanumeric outer boundaries. Its localized range includes the current encoded run and its immediately preceding cell, keeping `F-35`-style routing separate from both parenthesized digits and headword expansions. The `standalone_multi_character_uppercase_roman_word` gate finds maximal ASCII-letter runs of two or more capitals with non-alphanumeric boundaries; a run immediately followed by `(` is excluded so the HCA-style headword itself is not counted by both gates. The `korean_prefixed_closed_allcaps_parenthetical` gate requires an immediately preceding Korean character and a closed body of two or more uppercase ASCII letters. It intentionally contains both acronym annotations (`책임자(COO)`) and scientific formulae (`일산화탄소(CO)`) so their semantic collision remains measurable. The `korean_prefixed_closed_roman_annotation_rule_34_order` gate accepts the narrower rule-34 body grammar after an immediately preceding Korean character and localizes only the current engine's Korean opening-parenthesis cells. This distinguishes the PDF's parenthesis-before-Roman-indicator order from unrelated differences later in the same sentence. The `multi_character_allcaps_roman_runs_joined_by_middle_dot` gate requires two maximal ASCII-letter runs of at least two capitals joined directly by U+00B7, with non-alphanumeric outer boundaries. It records shapes such as `AI·SW` without assigning prose, mathematics, or science semantics. The `korean_inline_parenthesized_single_arithmetic_operator` gate requires an immediate `Korean(` + one rule-45 arithmetic operator + `)Korean` span. Unlike broad coexistence traits, it also locates the current engine's emitted structure and counts a mismatch as signature-local only when the sentence's first differing cell falls inside that output range. The `allcaps_roman_run_containing_ou` gate finds maximal, alphanumeric-delimited uppercase ASCII runs containing adjacent `OU`. It locates the independently encoded run signature in the complete current output and counts only first differences inside that signature as localized. The `tight_triangle_mark_immediately_before_korean` gate requires literal `△한글` with no input space and includes the first following Korean cell in its localized output range, so an observed missing-space difference is measured at the mark boundary. | Cluster | Candidates | Exact | Mismatch | Conflicting-reference cases | |---|---:|---:|---:|---:| | `allcaps_roman_run_containing_ou` | 1816 | 55 | 1761 | 0 | | `korean_inline_parenthesized_single_arithmetic_operator` | 23 | 17 | 6 | 0 | | `korean_prefixed_closed_allcaps_parenthetical` | 54492 | 43779 | 10713 | 0 | +| `korean_prefixed_closed_roman_annotation_rule_34_order` | 64382 | 50479 | 13903 | 0 | | `mixed_roman_korean_word_before_uppercase_headword_expansion` | 10 | 0 | 10 | 0 | | `multi_character_allcaps_roman_runs_joined_by_middle_dot` | 97 | 0 | 97 | 0 | | `single_capital_followed_by_parenthesized_digits` | 1361 | 0 | 1361 | 0 | @@ -309,7 +569,7 @@ These are cross-cutting input-only structural cohorts, not new primary classes a ### `allcaps_roman_run_containing_ou` -Of the 1816 candidates, 1684 are the actual `pending_rule_review` subcluster. The other 132 candidates are exact or existing non-pending-primary controls; this cohort does not reclassify them. +Of the 1816 candidates, 1676 are the actual `pending_rule_review` subcluster. The other 140 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. For this output-signature audit, 1761 mismatches were evaluable and 1307 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. @@ -321,7 +581,8 @@ Localized first-difference transitions: Mismatch primary-class distribution: - `comparison_method`: 2 -- `pending_rule_review`: 1684 +- `corpus_suspect`: 8 +- `pending_rule_review`: 1676 - `unsupported_character_review`: 75 Representative `exact` samples: @@ -369,7 +630,7 @@ Representative `mismatch` samples: ### `korean_inline_parenthesized_single_arithmetic_operator` -Of the 23 candidates, 6 are the actual `pending_rule_review` subcluster. The other 17 candidates are exact or existing non-pending-primary controls; this cohort does not reclassify them. +Of the 23 candidates, 6 are the actual `pending_rule_review` subcluster. The other 17 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. For this output-signature audit, 6 mismatches were evaluable and 2 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. @@ -415,12 +676,13 @@ Representative `mismatch` samples: ### `korean_prefixed_closed_allcaps_parenthetical` -Of the 54492 candidates, 10388 are the actual `pending_rule_review` subcluster. The other 44104 candidates are exact or existing non-pending-primary controls; this cohort does not reclassify them. +Of the 54492 candidates, 9409 are the actual `pending_rule_review` subcluster. The other 45083 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. Mismatch primary-class distribution: - `comparison_method`: 185 -- `pending_rule_review`: 10388 +- `corpus_suspect`: 979 +- `pending_rule_review`: 9409 - `unsupported_character_review`: 140 Representative `exact` samples: @@ -448,7 +710,90 @@ Representative `mismatch` samples: - expected: `⠰⠪⠐⠀⠝⠢⠊⠕⠴⠐⠣⠠⠠⠍⠙⠐⠜⠂⠀⠠⠠⠎⠝⠎` - actual: `⠰⠪⠐⠀⠝⠢⠊⠕⠦⠄⠴⠠⠠⠍⠙⠠⠴⠐⠀⠰⠠⠠⠎⠝` - first differing cell (zero-based): 97 + - current primary/reason: `corpus_suspect` / `rule34_roman_indicator_before_opening_parenthesis` +- `sentence_02.json` #10: 설동호 대전교육감은 “학교 교육과정 및 여건에 맞는 인공지능(AI)교육 선도학교 운영을 통해 AI·SW교육 기반 구축과 학생들과 교원들의 미래 디지털 역량이 함양될 것이다.”라고 말했다. + - expected: `⠚⠗⠀⠴⠠⠠⠁⠊⠐⠆⠴⠠⠠⠎⠺⠲⠈⠬⠩⠁⠀⠈⠕⠘` + - actual: `⠚⠗⠀⠴⠠⠠⠁⠊⠲⠐⠆⠴⠎⠺⠲⠈⠬⠩⠁⠀⠈⠕⠘⠒` + - first differing cell (zero-based): 93 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_03.json` #1: 김 지사는 18일(이하 현지시각) 미국 코네티컷주 댄버리 린데 본사에서 산지브 람바(Sanjiv Lamba) 린데 회장, 성백석 린데코리아 회장, 조일교 아산시 부시장과 투자양해각서(MOU)을 체결했다. + - expected: `⠠⠎⠦⠄⠴⠠⠠⠍⠕⠥⠠⠴⠮⠀⠰⠝⠈⠳⠚⠗⠌⠊⠲` + - actual: `⠠⠎⠦⠄⠴⠠⠠⠍⠳⠠⠴⠮⠀⠰⠝⠈⠳⠚⠗⠌⠊⠲` + - first differing cell (zero-based): 175 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_04.json` #19: 이날 행사에는 구 대표 외에 권봉석 (주)LG 최고운영책임자(COO), 박일평 LG사이언스파크대표(사장)를 비롯해 각 계열사 최고기술책임자(CTO), 최고데이터책임자(CDO), 최고인사책임자(CHO) 등이 참석해 국내 이공계 R&D 인재 400여명과 만났다. + - expected: `⠁⠕⠢⠨⠦⠄⠴⠠⠠⠉⠕⠕⠠⠴⠐⠀⠘⠁⠕⠂⠙⠻⠀⠴` + - actual: `⠁⠕⠢⠨⠦⠄⠴⠠⠉⠠⠕⠠⠕⠠⠴⠐⠀⠘⠁⠕⠂⠙⠻⠀` + - first differing cell (zero-based): 60 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` + +### `korean_prefixed_closed_roman_annotation_rule_34_order` + +Of the 64382 candidates, 12405 are the actual `pending_rule_review` subcluster. The other 51977 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. + +For this output-signature audit, 13903 mismatches were evaluable and 1095 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. + +Localized first-difference transitions: + +- `U+2834 ⠴ -> U+2826 ⠦`: 1094 +- `U+2810 ⠐ -> U+2826 ⠦`: 1 + +Mismatch primary-class distribution: + +- `comparison_method`: 239 +- `corpus_suspect`: 1091 +- `pending_rule_review`: 12405 +- `unsupported_character_review`: 168 + +Representative `exact` samples: + +- `sentence_01.json` #1: LG전자는 ‘업(UP)가전’을 무기로 내세우고 있다. 이번 CES에서도 LG 씽큐 앱에서 터치만으로 제품 색상을 바꿀 수 있는 무드업 냉장고를 비롯한 다양한 업가전을 선보일 예정이다. + - expected: `⠴⠠⠠⠇⠛⠲⠨⠾⠨⠉⠵⠀⠠⠦⠎⠃⠦⠄⠴⠠⠠⠥⠏⠠` + - actual: `⠴⠠⠠⠇⠛⠲⠨⠾⠨⠉⠵⠀⠠⠦⠎⠃⠦⠄⠴⠠⠠⠥⠏⠠` + - current primary/reason: `exact` / `exact` +- `sentence_02.json` #1: 세종시 주최, 세종시탄소중립지원센터 주관으로 열린 이번 포럼의 주요의제는 기업의 이에스지(ESG) 경영과 사회적 책임, 국내 온실가스 배출권거래제 현황과 대응 방안을 마련하기 위해 개최됐다. + - expected: `⠠⠝⠨⠿⠠⠕⠀⠨⠍⠰⠽⠐⠀⠠⠝⠨⠿⠠⠕⠓⠒⠠⠥⠨` + - actual: `⠠⠝⠨⠿⠠⠕⠀⠨⠍⠰⠽⠐⠀⠠⠝⠨⠿⠠⠕⠓⠒⠠⠥⠨` + - current primary/reason: `exact` / `exact` +- `sentence_03.json` #4: 18일(현지시간) 월스트리트저널(WSJ) 등 미 외신은 투·개표기 업체 도미니언 투표시스템이 2021년 1월 제기한 16억 달러(약 2조원) 명예훼손 소송과 관련, 폭스뉴스와 도미니언 측이 이 같이 합의했다고 보도했다. + - expected: `⠼⠁⠓⠕⠂⠦⠄⠚⠡⠨⠕⠠⠕⠫⠒⠠⠴⠀⠏⠂⠠⠪⠓⠪` + - actual: `⠼⠁⠓⠕⠂⠦⠄⠚⠡⠨⠕⠠⠕⠫⠒⠠⠴⠀⠏⠂⠠⠪⠓⠪` + - current primary/reason: `exact` / `exact` +- `sentence_04.json` #1: 일본 정부는 이날 국가안전보장회의(NSC)를 열어 대응 방침을 논의했다. 이어 북한의 미사일 발사에 대해 “엄중히 항의하고 강하게 비난한다”고 밝혔다. + - expected: `⠕⠂⠘⠷⠀⠨⠻⠘⠍⠉⠵⠀⠕⠉⠂⠀⠈⠍⠁⠫⠣⠒⠨⠾` + - actual: `⠕⠂⠘⠷⠀⠨⠻⠘⠍⠉⠵⠀⠕⠉⠂⠀⠈⠍⠁⠫⠣⠒⠨⠾` + - current primary/reason: `exact` / `exact` + +Representative `localized_mismatch` samples: + +- `sentence_01.json` #14: 새롭게 선보인 DRX 브랜딩과 팀 컬러는 2023년 1월 1일부터 DRX의 온오프라인 콘텐츠, 엠디(MD), SNS, 유튜브 등 모든 제작물에 적용될 예정이다. + - expected: `⠰⠪⠐⠀⠝⠢⠊⠕⠴⠐⠣⠠⠠⠍⠙⠐⠜⠂⠀⠠⠠⠎⠝⠎` + - actual: `⠰⠪⠐⠀⠝⠢⠊⠕⠦⠄⠴⠠⠠⠍⠙⠠⠴⠐⠀⠰⠠⠠⠎⠝` + - first differing cell (zero-based): 97 + - current primary/reason: `corpus_suspect` / `rule34_roman_indicator_before_opening_parenthesis` +- `sentence_02.json` #605: 전국적으로 강력범죄가 연이어 발생하면서 천안시가 시민 안전을 위해 공원, 산책로, 등산로 등에 설치된 방범용 폐쇄회로(CC)TV 전수조사를 실시한다. + - expected: `⠌⠠⠧⠗⠚⠽⠐⠥⠴⠐⠣⠠⠠⠉⠉⠐⠜⠠⠠⠞⠧⠲⠀⠨` + - actual: `⠌⠠⠧⠗⠚⠽⠐⠥⠦⠄⠴⠠⠉⠠⠉⠠⠴⠰⠠⠠⠞⠧⠲⠀` + - first differing cell (zero-based): 115 + - current primary/reason: `corpus_suspect` / `rule34_roman_indicator_before_opening_parenthesis` +- `sentence_03.json` #37: 경찰은 주변 폐쇄회로(CC)TV 등을 분석해 A씨 동선을 추적했고 14일 오후 경기도 양주의 한 주택에서 A씨를 긴급체포했다. + - expected: `⠌⠠⠧⠗⠚⠽⠐⠥⠴⠐⠣⠠⠠⠉⠉⠐⠜⠠⠠⠞⠧⠲⠀⠊` + - actual: `⠌⠠⠧⠗⠚⠽⠐⠥⠦⠄⠴⠠⠉⠠⠉⠠⠴⠰⠠⠠⠞⠧⠲⠀` + - first differing cell (zero-based): 21 + - current primary/reason: `corpus_suspect` / `rule34_roman_indicator_before_opening_parenthesis` +- `sentence_04.json` #42: 경찰은 폐쇄회로(CC)TV 분석과 쇠구슬 판매 업체 탐문 수사, 국과수 발사지점 방향성 감정 등을 통해 발사 의심 세대를 특정해 이날 피의자 A씨를 자택에서 검거했다. + - expected: `⠌⠠⠧⠗⠚⠽⠐⠥⠴⠐⠣⠠⠠⠉⠉⠐⠜⠠⠠⠞⠧⠲⠀⠘` + - actual: `⠌⠠⠧⠗⠚⠽⠐⠥⠦⠄⠴⠠⠉⠠⠉⠠⠴⠰⠠⠠⠞⠧⠲⠀` + - first differing cell (zero-based): 16 + - current primary/reason: `corpus_suspect` / `rule34_roman_indicator_before_opening_parenthesis` + +Representative `mismatch` samples: + +- `sentence_01.json` #14: 새롭게 선보인 DRX 브랜딩과 팀 컬러는 2023년 1월 1일부터 DRX의 온오프라인 콘텐츠, 엠디(MD), SNS, 유튜브 등 모든 제작물에 적용될 예정이다. + - expected: `⠰⠪⠐⠀⠝⠢⠊⠕⠴⠐⠣⠠⠠⠍⠙⠐⠜⠂⠀⠠⠠⠎⠝⠎` + - actual: `⠰⠪⠐⠀⠝⠢⠊⠕⠦⠄⠴⠠⠠⠍⠙⠠⠴⠐⠀⠰⠠⠠⠎⠝` + - first differing cell (zero-based): 97 + - current primary/reason: `corpus_suspect` / `rule34_roman_indicator_before_opening_parenthesis` - `sentence_02.json` #10: 설동호 대전교육감은 “학교 교육과정 및 여건에 맞는 인공지능(AI)교육 선도학교 운영을 통해 AI·SW교육 기반 구축과 학생들과 교원들의 미래 디지털 역량이 함양될 것이다.”라고 말했다. - expected: `⠚⠗⠀⠴⠠⠠⠁⠊⠐⠆⠴⠠⠠⠎⠺⠲⠈⠬⠩⠁⠀⠈⠕⠘` - actual: `⠚⠗⠀⠴⠠⠠⠁⠊⠲⠐⠆⠴⠎⠺⠲⠈⠬⠩⠁⠀⠈⠕⠘⠒` @@ -467,7 +812,7 @@ Representative `mismatch` samples: ### `mixed_roman_korean_word_before_uppercase_headword_expansion` -Of the 10 candidates, 10 are the actual `pending_rule_review` subcluster. The other 0 candidates are exact or existing non-pending-primary controls; this cohort does not reclassify them. +Of the 10 candidates, 10 are the actual `pending_rule_review` subcluster. The other 0 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. For this output-signature audit, 10 mismatches were evaluable and 8 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. @@ -494,11 +839,12 @@ Representative `mismatch` samples: ### `multi_character_allcaps_roman_runs_joined_by_middle_dot` -Of the 97 candidates, 97 are the actual `pending_rule_review` subcluster. The other 0 candidates are exact or existing non-pending-primary controls; this cohort does not reclassify them. +Of the 97 candidates, 96 are the actual `pending_rule_review` subcluster. The other 1 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. Mismatch primary-class distribution: -- `pending_rule_review`: 97 +- `corpus_suspect`: 1 +- `pending_rule_review`: 96 Representative `mismatch` samples: @@ -525,7 +871,7 @@ Representative `mismatch` samples: ### `single_capital_followed_by_parenthesized_digits` -Of the 1361 candidates, 1361 are the actual `pending_rule_review` subcluster. The other 0 candidates are exact or existing non-pending-primary controls; this cohort does not reclassify them. +Of the 1361 candidates, 1357 are the actual `pending_rule_review` subcluster. The other 4 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. For this output-signature audit, 1361 mismatches were evaluable and 1341 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. @@ -538,7 +884,8 @@ Localized first-difference transitions: Mismatch primary-class distribution: -- `pending_rule_review`: 1361 +- `corpus_suspect`: 4 +- `pending_rule_review`: 1357 Representative `mismatch` samples: @@ -565,12 +912,13 @@ Representative `mismatch` samples: ### `standalone_multi_character_uppercase_roman_word` -Of the 62411 candidates, 13268 are the actual `pending_rule_review` subcluster. The other 49143 candidates are exact or existing non-pending-primary controls; this cohort does not reclassify them. +Of the 62411 candidates, 12190 are the actual `pending_rule_review` subcluster. The other 50221 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. Mismatch primary-class distribution: - `comparison_method`: 223 -- `pending_rule_review`: 13268 +- `corpus_suspect`: 1078 +- `pending_rule_review`: 12190 - `unsupported_character_review`: 163 Representative `exact` samples: @@ -598,7 +946,7 @@ Representative `mismatch` samples: - expected: `⠰⠪⠐⠀⠝⠢⠊⠕⠴⠐⠣⠠⠠⠍⠙⠐⠜⠂⠀⠠⠠⠎⠝⠎` - actual: `⠰⠪⠐⠀⠝⠢⠊⠕⠦⠄⠴⠠⠠⠍⠙⠠⠴⠐⠀⠰⠠⠠⠎⠝` - first differing cell (zero-based): 97 - - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` + - current primary/reason: `corpus_suspect` / `rule34_roman_indicator_before_opening_parenthesis` - `sentence_02.json` #10: 설동호 대전교육감은 “학교 교육과정 및 여건에 맞는 인공지능(AI)교육 선도학교 운영을 통해 AI·SW교육 기반 구축과 학생들과 교원들의 미래 디지털 역량이 함양될 것이다.”라고 말했다. - expected: `⠚⠗⠀⠴⠠⠠⠁⠊⠐⠆⠴⠠⠠⠎⠺⠲⠈⠬⠩⠁⠀⠈⠕⠘` - actual: `⠚⠗⠀⠴⠠⠠⠁⠊⠲⠐⠆⠴⠎⠺⠲⠈⠬⠩⠁⠀⠈⠕⠘⠒` @@ -617,7 +965,7 @@ Representative `mismatch` samples: ### `tight_triangle_mark_immediately_before_korean` -Of the 377 candidates, 377 are the actual `pending_rule_review` subcluster. The other 0 candidates are exact or existing non-pending-primary controls; this cohort does not reclassify them. +Of the 377 candidates, 376 are the actual `pending_rule_review` subcluster. The other 1 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. For this output-signature audit, 377 mismatches were evaluable and 13 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. @@ -627,7 +975,8 @@ Localized first-difference transitions: Mismatch primary-class distribution: -- `pending_rule_review`: 377 +- `corpus_suspect`: 1 +- `pending_rule_review`: 376 Representative `mismatch` samples: @@ -654,12 +1003,13 @@ Representative `mismatch` samples: ### `uppercase_roman_headword_closed_multiword_parenthetical` -Of the 175 candidates, 153 are the actual `pending_rule_review` subcluster. The other 22 candidates are exact or existing non-pending-primary controls; this cohort does not reclassify them. +Of the 175 candidates, 149 are the actual `pending_rule_review` subcluster. The other 26 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. Mismatch primary-class distribution: - `comparison_method`: 1 -- `pending_rule_review`: 153 +- `corpus_suspect`: 4 +- `pending_rule_review`: 149 - `unsupported_character_review`: 6 Representative `exact` samples: @@ -706,7 +1056,7 @@ Representative `mismatch` samples: ### `uppercase_roman_run_followed_by_hyphen_digits` -Of the 571 candidates, 390 are the actual `pending_rule_review` subcluster. The other 181 candidates are exact or existing non-pending-primary controls; this cohort does not reclassify them. +Of the 571 candidates, 378 are the actual `pending_rule_review` subcluster. The other 193 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. For this output-signature audit, 393 mismatches were evaluable and 259 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. @@ -717,7 +1067,8 @@ Localized first-difference transitions: Mismatch primary-class distribution: -- `pending_rule_review`: 390 +- `corpus_suspect`: 12 +- `pending_rule_review`: 378 - `unsupported_character_review`: 3 Representative `exact` samples: @@ -782,24 +1133,26 @@ The tight-triangle cohort is not an implementation premise. Hangeul rule 49 assi Corpus contradictions remain a separate gate: identical inputs with conflicting references are classified as `corpus_suspect` before these cohorts are recorded and would appear explicitly in each mismatch primary-class distribution. Their absence does not prove a reference correct; it only means that this deterministic contradiction test did not fire. -Current uppercase-Roman hyphen-digits measurement: 571 candidates, 178 exact controls, 393 mismatches, 390 members in the actual `pending_rule_review` subcluster, and 259/393 evaluable mismatches whose first difference is inside the target run plus its entry boundary. It remains distinct from parenthesized digits and headword expansions; no engine change is inferred. +Current uppercase-Roman hyphen-digits measurement: 571 candidates, 178 exact controls, 393 mismatches, 378 members in the actual `pending_rule_review` subcluster, and 259/393 evaluable mismatches whose first difference is inside the target run plus its entry boundary. It remains distinct from parenthesized digits and headword expansions; no engine change is inferred. -Current single-capital parenthesized-digits measurement: 1361 candidates, 0 exact controls, 1361 mismatches, 1361 members in the actual `pending_rule_review` subcluster, and 1341/1361 evaluable mismatches whose first difference is inside the target run plus its entry boundary. No engine change is inferred from the ambiguous prose/function surface form. +Current single-capital parenthesized-digits measurement: 1361 candidates, 0 exact controls, 1361 mismatches, 1357 members in the actual `pending_rule_review` subcluster, and 1341/1361 evaluable mismatches whose first difference is inside the target run plus its entry boundary. No engine change is inferred from the ambiguous prose/function surface form. Current mixed Roman+Korean boundary before uppercase headword-expansion measurement: 10 candidates, 0 exact controls, 10 mismatches, 10 members in the actual `pending_rule_review` subcluster, and 8/10 evaluable mismatches whose first difference is localized to the later headword's entry boundary/output. The detector cannot be satisfied by the earlier Roman entry. No HCA-shaped engine routing rule is introduced. -Current all-caps `OU` measurement: 1816 candidates, 55 exact controls, 1761 mismatches, 1684 members in the actual `pending_rule_review` subcluster, and 1307/1761 evaluable mismatches whose first difference is inside the current-engine output signature for the detected run. This is a pronunciation-sensitive UEB review cohort, not an engine routing rule. +Current all-caps `OU` measurement: 1816 candidates, 55 exact controls, 1761 mismatches, 1676 members in the actual `pending_rule_review` subcluster, and 1307/1761 evaluable mismatches whose first difference is inside the current-engine output signature for the detected run. This is a pronunciation-sensitive UEB review cohort, not an engine routing rule. -Current standalone-uppercase measurement: 62411 candidates, 48757 exact controls, 13654 mismatches, and 13268 members in the actual `pending_rule_review` subcluster. Its high frequency does not make it causal: the same input shape is exact in many cases, and a sentence containing the shape may first differ at another Roman, numeric, or punctuation structure. No engine change is inferred from this cohort. +Current standalone-uppercase measurement: 62411 candidates, 48757 exact controls, 13654 mismatches, and 12190 members in the actual `pending_rule_review` subcluster. Its high frequency does not make it causal: the same input shape is exact in many cases, and a sentence containing the shape may first differ at another Roman, numeric, or punctuation structure. No engine change is inferred from this cohort. -Current Korean-prefixed all-caps parenthetical measurement: 54492 candidates, 43779 exact controls, 10713 mismatches, and 10388 members in the actual `pending_rule_review` subcluster. This is a semantic-collision audit, not an engine routing rule; no implementation change is inferred from its reference outputs. +Current Korean-prefixed all-caps parenthetical measurement: 54492 candidates, 43779 exact controls, 10713 mismatches, and 9409 members in the actual `pending_rule_review` subcluster. This is a semantic-collision audit, not an engine routing rule; no implementation change is inferred from its reference outputs. -Current all-caps Roman middle-dot measurement: 97 candidates, 0 exact controls, 97 mismatches, and 97 members in the actual `pending_rule_review` subcluster. This cross-cutting cohort preserves every primary class and is not an engine routing rule. +Current rule-34 opening-order measurement: 64382 structural candidates, 50479 exact controls, 13903 mismatches, and 1095/13903 evaluable mismatches whose first difference is inside the current engine's Korean opening-parenthesis cells. Only the 1094 localized first-cell transitions have the reference/current order `⠴` versus `⠦`. After requiring the complete reference `⠴⠐⠣` versus current/PDF `⠦⠄⠴` three-cell signature and preserving higher-priority comparison classifications, 1091 are classified as `corpus_suspect`; mere coexistence with a Korean-prefixed Roman annotation does not change a primary class. + +Current all-caps Roman middle-dot measurement: 97 candidates, 0 exact controls, 97 mismatches, and 96 members in the actual `pending_rule_review` subcluster. This cross-cutting cohort preserves every primary class and is not an engine routing rule. Current inline parenthesized-operator measurement: 23 candidates, 17 exact controls, 6 mismatches, 6 members in the actual `pending_rule_review` subcluster, and 2/6 evaluable mismatches whose first differing cell is inside the emitted structure. Primary classes are preserved. At this implementation checkpoint, the strict cohort moved from 0 to 17 exact cases; the corpus-wide total moved from 65,491 to 65514 (+23 exact) because the same PDF-backed spacing rule also applies outside the stricter Korean-boundary audit gate. The complete standard suite remains 5,141/5,141. -Current tight-triangle measurement: 377 candidates, 0 exact controls, 377 mismatches, 377 members in the actual `pending_rule_review` subcluster, and 13/377 evaluable mismatches whose first difference is inside the `△` plus first-Korean output range. No engine change is inferred. +Current tight-triangle measurement: 377 candidates, 0 exact controls, 377 mismatches, 376 members in the actual `pending_rule_review` subcluster, and 13/377 evaluable mismatches whose first difference is inside the `△` plus first-Korean output range. No engine change is inferred. ## Encoding-error diagnostics @@ -869,9 +1222,6 @@ Families are diagnostics, not automatic normalization permissions. Rules 68/69 c ### `foreign_text_rule_review` -- `sentence_01.json` #14: 새롭게 선보인 DRX 브랜딩과 팀 컬러는 2023년 1월 1일부터 DRX의 온오프라인 콘텐츠, 엠디(MD), SNS, 유튜브 등 모든 제작물에 적용될 예정이다. - - expected: `⠰⠪⠐⠀⠝⠢⠊⠕⠴⠐⠣⠠⠠⠍⠙⠐⠜⠂⠀⠠⠠⠎⠝⠎` - - actual: `⠰⠪⠐⠀⠝⠢⠊⠕⠦⠄⠴⠠⠠⠍⠙⠠⠴⠐⠀⠰⠠⠠⠎⠝` - `sentence_01.json` #17: 지난달 30일(현지시각) 뉴욕증권거래소(NYSE)에서 다우존스30산업평균지수는 전 거래일보다 73.55포인트(0.22%) 하락한 3만3147.25로 마감했다. 대형주 중심의 S&P500지수는 9.78포인트(0.25%) 하락한 3839.50으로, 기술주 중심의 나스닥지수는 11.61포인트(0.11%) 하락한 1만0466.4 - expected: `⠶⠠⠕⠢⠺⠀⠴⠠⠎⠈⠯⠠⠏⠼⠑⠚⠚⠨⠕⠠⠍⠉⠵⠀` - actual: `⠶⠠⠕⠢⠺⠀⠴⠠⠠⠎⠲⠴⠈⠯⠲⠴⠏⠼⠑⠚⠚⠨⠕⠠` @@ -903,10 +1253,6 @@ Families are diagnostics, not automatic normalization permissions. Rules 68/69 c - expected: `⠈⠻⠇⠶⠉⠢⠊⠥⠀⠈⠕⠢⠚⠗⠠⠕⠝⠀⠠⠥⠨⠗⠚⠒` - actual: `` - error: `Invalid symbol character` -- `sentence_01.json` #3149: 한편 공동연구개발기관인 농업회사법인 ㈜영풍은 대구시 스타기업으로 현재 떡볶이, 전, 스낵류 등 가정간편식(HMR) 제품을 80개국에 수출하고 있다. - - expected: `⠚⠒⠙⠡⠀⠈⠿⠊⠿⠡⠈⠍⠈⠗⠘⠂⠈⠕⠈⠧⠒⠟⠀⠉` - - actual: `` - - error: `Invalid symbol character` ### `number_rule_review` @@ -922,9 +1268,6 @@ Families are diagnostics, not automatic normalization permissions. Rules 68/69 c - `sentence_01.json` #9055: 한국건설기술연구원에 따르면 주거용 건물 건설공사비지수는 올해 9월 기준 152.76(잠정치)으로 전년 동월(147.31) 대비 약 3.7%가량 증가했다. - expected: `⠾⠉⠡⠀⠊⠿⠏⠂⠦⠄⠼⠁⠙⠛⠲⠉⠁⠠⠴⠀⠊⠗⠘⠕` - actual: `⠾⠉⠡⠀⠊⠿⠏⠂⠀⠀⠦⠼⠁⠙⠛⠲⠉⠁⠴⠀⠊⠗⠘⠕` -- `sentence_01.json` #10139: ‘서울 반려견 순찰대’는 8개월 동안의 올해 성과 보고를 통해 △범죄예방(112) 신고 317건 △생활위험 관련(120) 신고 2187건 등으로 지역사회 안전망을 촘촘하게 구축하는 데 이바지했다고 발표했다. - - expected: `⠀⠓⠿⠚⠗⠀⠸⠬⠀⠘⠎⠢⠨⠽⠤⠌⠘⠶⠦⠄⠼⠁⠁⠃` - - actual: `⠀⠓⠿⠚⠗⠀⠸⠬⠇⠘⠎⠢⠨⠽⠤⠌⠘⠶⠦⠄⠼⠁⠁⠃` ### `punctuation_rule_review` @@ -940,9 +1283,21 @@ Families are diagnostics, not automatic normalization permissions. Rules 68/69 c - `sentence_03.json` #19763: 디즈니+ 드라마 ‘무빙’을 언급하던 한효주 곁에 다가온 조인성은 “굉장히 무서운 와이프였어요~”라고 극 중 아내 자랑(?)을 하며 남편 손님과 눈빛 교환을 하는 모습으로 폭소를 안긴다. - expected: `⠊⠕⠨⠪⠉⠕⠀⠢⠀⠊⠪⠐⠣⠑⠀⠠⠦⠑⠍⠘⠕⠶⠴⠄` - actual: `⠊⠕⠨⠪⠉⠕⠀⠀⠢⠀⠊⠪⠐⠣⠑⠀⠠⠦⠑⠍⠘⠕⠶⠴` -- `sentence_03.json` #21665: 그러자 김숙은 “그러면 우재야. ‘홍김동전’ 잠깐 쉬어라”며 급 하차 권유(?)를 하고, 홍진경은 “김치 없냐”면서 느닷없이 김치를 찾는 등 총체적 난국이 펼쳐졌다. - - expected: `⠕⠢⠠⠍⠁⠵⠀⠦⠁⠒⠀⠍⠨⠗⠜⠲⠀⠠⠦⠚⠿⠈⠕⠢` - - actual: `⠕⠢⠠⠍⠁⠵⠀⠦⠈⠪⠐⠎⠑⠡⠀⠍⠨⠗⠜⠲⠀⠠⠦⠚` + +### `rule34_roman_indicator_before_opening_parenthesis` + +- `sentence_01.json` #14: 새롭게 선보인 DRX 브랜딩과 팀 컬러는 2023년 1월 1일부터 DRX의 온오프라인 콘텐츠, 엠디(MD), SNS, 유튜브 등 모든 제작물에 적용될 예정이다. + - expected: `⠰⠪⠐⠀⠝⠢⠊⠕⠴⠐⠣⠠⠠⠍⠙⠐⠜⠂⠀⠠⠠⠎⠝⠎` + - actual: `⠰⠪⠐⠀⠝⠢⠊⠕⠦⠄⠴⠠⠠⠍⠙⠠⠴⠐⠀⠰⠠⠠⠎⠝` +- `sentence_01.json` #77: 삼성전자가 미국 라스베이거스에서 열리는 세계 최대 전자 전시회 ‘CES 2023’ 개막을 앞두고 77인치 유기발광다이오드(OLED) TV 등 2023년형 TV 신제품을 대거 공개했다. + - expected: `⠧⠶⠊⠣⠕⠥⠊⠪⠴⠐⠣⠠⠠⠕⠇⠫⠐⠜⠀⠠⠠⠞⠧⠲` + - actual: `⠧⠶⠊⠣⠕⠥⠊⠪⠦⠄⠴⠠⠠⠕⠇⠫⠠⠴⠀⠴⠠⠠⠞⠧` +- `sentence_01.json` #79: B씨의 신고로 수사에 착수한 경찰은 인근 폐쇄회로(CC)TV와 탐문수색 등을 바탕으로 A씨를 특정해 지난달 29일 검거해 31일 구속했다. + - expected: `⠌⠠⠧⠗⠚⠽⠐⠥⠴⠐⠣⠠⠠⠉⠉⠐⠜⠠⠠⠞⠧⠲⠧⠀` + - actual: `⠌⠠⠧⠗⠚⠽⠐⠥⠦⠄⠴⠠⠉⠠⠉⠠⠴⠰⠠⠠⠞⠧⠲⠧` +- `sentence_01.json` #83: 삼성전자는 이번 행사에서 77형 유기발광다이오드(OLED) TV를 첫 공개하기도 했다. 지난해 처음 출시한 삼성 OLED TV는 55형, 65형과 함께 초대형 77형 모델이 추가된 셈이다. + - expected: `⠧⠶⠊⠣⠕⠥⠊⠪⠴⠐⠣⠠⠠⠕⠇⠫⠐⠜⠀⠠⠠⠞⠧⠲` + - actual: `⠧⠶⠊⠣⠕⠥⠊⠪⠦⠄⠴⠠⠠⠕⠇⠫⠠⠴⠀⠴⠠⠠⠞⠧` ### `unsupported_character_review` @@ -962,10 +1317,6 @@ Families are diagnostics, not automatic normalization permissions. Rules 68/69 c - expected: `⠊⠗⠈⠍⠈⠧⠶⠱⠁⠠⠕⠦⠄⠠⠕⠨⠶⠀⠚⠿⠨⠛⠙⠬` - actual: `` - error: `Invalid symbol character` -- `sentence_01.json` #2336: 한편, 한국팀 경기를 포함한 아시아컵 주요 경기는 세계야구소프트볼연맹(WBSC) 공식 유튜브에서 확인할 수 있다. 이번 대회 운영 대행은 ㈜에스피에스 코리아(SPS KOREA)가 맡아 진행한다. - - expected: `⠚⠒⠙⠡⠐⠀⠚⠒⠈⠍⠁⠓⠕⠢⠀⠈⠻⠈⠕⠐⠮⠀⠙⠥` - - actual: `` - - error: `Invalid symbol character` ## PDF-derived state gates @@ -1002,9 +1353,9 @@ Remaining complex encoding errors: 3. These cases still contain another characte ## Rules 34/54 Korean-prefixed Roman annotations -Rule 34 says that when Roman text is enclosed by quotation marks or brackets, the Roman terminator is omitted; its PDF example is `링컨(Lincoln)은 미국의 제16대 대통령이다.` Rule 54 says that text immediately after an opening bracket and immediately before a closing bracket is attached. Together these establish the Korean-prefix + closed-Roman-annotation context independently of corpus expected values. A following comma or period is outside the already closed annotation and must not cause its Roman contents to be rerouted as mathematics. +Rule 34 says that when Roman text is enclosed by quotation marks or brackets, the Roman terminator is omitted; its PDF example is `링컨(Lincoln)은 미국의 제16대 대통령이다.` The example's cells put the printed Korean opening parenthesis (`⠦⠄`) before the Roman indicator (`⠴`). Rule 54 says that text immediately after an opening bracket and immediately before a closing bracket is attached. Together these establish the Korean-prefix + closed-Roman-annotation context independently of corpus expected values. A following comma or period is outside the already closed annotation and must not cause its Roman contents to be rerouted as mathematics. -The implementation gate exists only inside `split_mixed_math_word`, after the prefix has been proved entirely Korean. It accepts a fully closed parenthesized Roman word (including ASCII digits such as `O4O`) plus ordinary trailing prose punctuation. The global math detector is byte-for-byte unchanged; regression tests preserve its existing standalone results for `(x)`, `(A)`, and `(abc)`, while explicit forms such as `(x+1)`, `(a/b)`, and `(x₁)` remain math candidates. +The implementation gate exists only inside `split_mixed_math_word`, after the prefix has been proved entirely Korean. It accepts a fully closed parenthesized Roman word (including ASCII digits such as `O4O`) plus ordinary trailing prose punctuation. The corpus audit treats the opposite localized reference prefix (`⠴⠐⠣`, Roman indicator plus UEB opening parenthesis) as a data-reference contradiction only when all three cells and the real input position agree. Broad sentence-level coexistence is retained as an exact or existing-primary control. This audit does not alter engine routing. The global math detector is byte-for-byte unchanged; regression tests preserve its existing standalone results for `(x)`, `(A)`, and `(abc)`, while explicit forms such as `(x+1)`, `(a/b)`, and `(x₁)` remain math candidates. Against the immediately preceding 63,399-exact run, exact matches increased by 2,092. The observable primary totals changed as follows: `comparison_method` 290→303, `pending_rule_review` 19,636→17,543, and `unsupported_character_review` 203→191. Raw encoding errors stayed at 450; errors resolved by a comparison method changed 247→259 and unresolved review errors changed 203→191. diff --git a/libs/braillify/examples/nikl_corpus_analyze.rs b/libs/braillify/examples/nikl_corpus_analyze.rs index 935c3157..946afd19 100644 --- a/libs/braillify/examples/nikl_corpus_analyze.rs +++ b/libs/braillify/examples/nikl_corpus_analyze.rs @@ -56,6 +56,7 @@ enum PrimaryClass { enum Reason { Exact, ConflictingDuplicateReference, + Rule34RomanIndicatorBeforeOpeningParenthesis, BrailleWhitespaceEquivalent, NfcInputEquivalent, NfkcInputEquivalent, @@ -136,7 +137,7 @@ struct EncodingErrorAudit { unclassified_samples: Vec, } -#[derive(Debug, Serialize)] +#[derive(Clone, Debug, Serialize)] struct PendingRuleReviewClusterSample { shard: String, index: usize, @@ -188,6 +189,8 @@ struct AnalysisReport { // is PendingRuleReview are pending-rule-review subclusters. pending_rule_review_clusters: BTreeMap, pending_first_difference_cell_transitions: BTreeMap, + pending_first_difference_transitions_after_localized_cohorts: + BTreeMap, overlapping_traits: BTreeMap, shards: BTreeMap, samples: BTreeMap>, @@ -443,6 +446,10 @@ fn classify(encoded: &EncodedCase, conflicting: &BTreeSet) -> (PrimaryCl { (PrimaryClass::ComparisonMethod, Reason::NfkcInputEquivalent) } + _ if is_rule_34_reference_order_contradiction(encoded) => ( + PrimaryClass::CorpusSuspect, + Reason::Rule34RomanIndicatorBeforeOpeningParenthesis, + ), Ok(actual) if roman_before_capital_order(actual) == *expected => ( PrimaryClass::ImplementationDefect, Reason::RomanIndicatorAfterCapitalIndicator, @@ -560,6 +567,8 @@ const UPPERCASE_ROMAN_HEADWORD_EXPANSION: &str = "uppercase_roman_headword_closed_multiword_parenthetical"; const STANDALONE_UPPERCASE_ROMAN_WORD: &str = "standalone_multi_character_uppercase_roman_word"; const KOREAN_PREFIXED_ALLCAPS_PARENTHETICAL: &str = "korean_prefixed_closed_allcaps_parenthetical"; +const KOREAN_PREFIXED_CLOSED_ROMAN_ANNOTATION: &str = + "korean_prefixed_closed_roman_annotation_rule_34_order"; const ALLCAPS_ROMAN_MIDDLE_DOT_RUNS: &str = "multi_character_allcaps_roman_runs_joined_by_middle_dot"; const KOREAN_INLINE_PARENTHESIZED_OPERATOR: &str = @@ -577,6 +586,109 @@ struct InputSpan { end_byte: usize, } +/// Finds rule-34-shaped annotations whose opening parenthesis immediately +/// follows Korean script and whose closed body contains only ordinary Roman +/// letters, digits, apostrophes, periods, or hyphens. This is an input gate; +/// the separate output locator decides whether a first difference is at the +/// opening-parenthesis order established by the PDF example. +fn korean_prefixed_closed_roman_annotation_spans(input: &str) -> Vec { + let mut spans = Vec::new(); + for (open_byte, _) in input.match_indices('(') { + if !input[..open_byte] + .chars() + .next_back() + .is_some_and(is_korean_script) + { + continue; + } + + let body_start = open_byte + 1; + let Some(close_offset) = input[body_start..].find(')') else { + continue; + }; + let close_byte = body_start + close_offset; + let body = &input[body_start..close_byte]; + if !body.is_empty() + && body.chars().any(|ch| ch.is_ascii_alphabetic()) + && body + .chars() + .all(|ch| ch.is_ascii_alphanumeric() || matches!(ch, '-' | '\'' | '.')) + { + spans.push(InputSpan { + start_byte: open_byte, + end_byte: close_byte + 1, + }); + } + } + spans +} + +/// Locate only the current engine's Korean opening-parenthesis cells. The +/// signature is derived from a neutral Korean probe and verified at the cell +/// offset obtained by encoding the real prefix, never from corpus expected. +fn korean_prefixed_annotation_opening_ranges( + input: &str, + actual: &str, +) -> Vec> { + let actual_cells = actual.chars().collect::>(); + let korean = braillify::encode_to_unicode("가").expect("neutral Korean probe must encode"); + let korean_with_open = + braillify::encode_to_unicode("가(").expect("Korean opening-parenthesis probe must encode"); + let korean_cells = korean.chars().count(); + let opening = korean_with_open + .chars() + .skip(korean_cells) + .collect::>(); + + korean_prefixed_closed_roman_annotation_spans(input) + .into_iter() + .filter_map(|span| { + let prefix = braillify::encode_to_unicode(&input[..span.start_byte]).ok()?; + let start = prefix.chars().count(); + let end = start.checked_add(opening.len())?; + (actual_cells.get(start..end) == Some(opening.as_slice())).then_some(start..end) + }) + .collect() +} + +fn first_difference_in_korean_prefixed_annotation_opening(item: &EncodedCase) -> bool { + let Ok(actual) = &item.actual else { + return false; + }; + if actual == &item.located.case.unicode { + return false; + } + let first_difference = first_difference_cell(&item.located.case.unicode, actual); + korean_prefixed_annotation_opening_ranges(&item.located.case.input, actual) + .into_iter() + .any(|range| range.contains(&first_difference)) +} + +/// The PDF's rule-34 example emits the printed Korean opening parenthesis +/// before entering Roman mode: `⠦⠄⠴`. A corpus reference that instead starts +/// this same localized input structure with Roman mode plus the UEB opening +/// parenthesis (`⠴⠐⠣`) contradicts that explicit order. Requiring both +/// three-cell signatures avoids reclassifying unrelated mismatches in the +/// broad input cohort. +fn is_rule_34_reference_order_contradiction(item: &EncodedCase) -> bool { + let Ok(actual) = &item.actual else { + return false; + }; + let expected = &item.located.case.unicode; + if actual == expected { + return false; + } + + let difference = first_difference_cell(expected, actual); + let expected_cells = expected.chars().collect::>(); + let actual_cells = actual.chars().collect::>(); + expected_cells.get(difference..difference + 3) == Some(&['⠴', '⠐', '⠣']) + && actual_cells.get(difference..difference + 3) == Some(&['⠦', '⠄', '⠴']) + && korean_prefixed_annotation_opening_ranges(&item.located.case.input, actual) + .into_iter() + .any(|range| range.start == difference) +} + /// Finds maximal all-caps ASCII runs containing the adjacent letters `OU`. /// /// This is an input gate for a pronunciation-sensitive UEB diagnostic, not a @@ -838,6 +950,31 @@ fn first_difference_in_korean_context_signature_spans( .any(|range| range.contains(&first_difference)) } +/// Only output-localized cohorts may claim a first difference. Broad input-only +/// coexistence traits are intentionally absent: excluding them would hide +/// unrelated causes merely because a sentence also contains Roman text. +fn first_difference_claimed_by_localized_cohort(item: &EncodedCase) -> bool { + first_difference_in_allcaps_ou_run(item) + || first_difference_in_korean_prefixed_annotation_opening(item) + || first_difference_in_inline_parenthesized_operator(item) + || first_difference_in_tight_triangle(item) + || first_difference_in_signature_spans( + item, + &single_capital_parenthesized_digit_spans(&item.located.case.input), + 1, + ) + || first_difference_in_signature_spans( + item, + &mixed_roman_korean_before_headword_expansion_spans(&item.located.case.input), + 1, + ) + || first_difference_in_korean_context_signature_spans( + item, + &uppercase_roman_hyphen_digit_spans(&item.located.case.input), + 1, + ) +} + /// Input-only candidate gate for acronym expansions such as /// `HCA(Home Connectivity Alliance)`. /// @@ -1288,14 +1425,14 @@ fn encoding_error_family(ch: char) -> &'static str { fn record_structural_cohort_case( stats: &mut PendingRuleReviewClusterStats, item: &EncodedCase, - primary: PrimaryClass, primary_key: &str, reason_key: &str, sample_limit: usize, first_difference_in_output_signature: Option, + include_localized_sample_bucket: bool, ) { stats.candidates += 1; - let outcome = if primary == PrimaryClass::Exact { + let outcome = if primary_key == "exact" { stats.exact += 1; "exact" } else { @@ -1312,7 +1449,7 @@ fn record_structural_cohort_case( .or_insert(0) += 1; } } - if primary == PrimaryClass::CorpusSuspect { + if reason_key == "conflicting_duplicate_reference" { stats.conflicting_reference_cases += 1; } *stats @@ -1321,15 +1458,6 @@ fn record_structural_cohort_case( .or_insert(0) += 1; "mismatch" }; - let bucket = stats.samples.entry(outcome.to_string()).or_default(); - if bucket.len() >= sample_limit - || bucket - .iter() - .any(|sample| sample.shard == item.located.shard) - { - return; - } - let expected = &item.located.case.unicode; let (actual, error) = match &item.actual { Ok(actual) => (actual.as_str(), None), @@ -1345,7 +1473,7 @@ fn record_structural_cohort_case( }; let first_difference_cell = (actual != expected).then(|| first_difference_cell(expected, actual)); - bucket.push(PendingRuleReviewClusterSample { + let sample = PendingRuleReviewClusterSample { shard: item.located.shard.clone(), index: item.located.index, input: item.located.case.input.clone(), @@ -1355,7 +1483,21 @@ fn record_structural_cohort_case( error, primary_class: primary_key.to_string(), reason: reason_key.to_string(), - }); + }; + let mut bucket_names = vec![outcome]; + if include_localized_sample_bucket && first_difference_in_output_signature == Some(true) { + bucket_names.push("localized_mismatch"); + } + for bucket_name in bucket_names { + let bucket = stats.samples.entry(bucket_name.to_string()).or_default(); + if bucket.len() < sample_limit + && !bucket + .iter() + .any(|existing| existing.shard == item.located.shard) + { + bucket.push(sample.clone()); + } + } } fn analyze( @@ -1387,6 +1529,10 @@ fn analyze( KOREAN_PREFIXED_ALLCAPS_PARENTHETICAL.to_string(), PendingRuleReviewClusterStats::default(), ), + ( + KOREAN_PREFIXED_CLOSED_ROMAN_ANNOTATION.to_string(), + PendingRuleReviewClusterStats::default(), + ), ( KOREAN_INLINE_PARENTHESIZED_OPERATOR.to_string(), PendingRuleReviewClusterStats::default(), @@ -1417,6 +1563,7 @@ fn analyze( ), ]); let mut pending_first_difference_cell_transitions = BTreeMap::new(); + let mut pending_first_difference_transitions_after_localized_cohorts = BTreeMap::new(); let mut exact = 0usize; for item in &encoded { @@ -1460,28 +1607,47 @@ fn analyze( &reason_key, sample_limit, ); + if !first_difference_claimed_by_localized_cohort(item) { + record_pending_first_difference_transition( + &mut pending_first_difference_transitions_after_localized_cohorts, + item, + &primary_key, + &reason_key, + sample_limit, + ); + } } - for (cluster, present, localized_first_difference) in [ + for (cluster, present, localized_first_difference, localized_samples) in [ ( ALLCAPS_ROMAN_RUN_CONTAINING_OU, !allcaps_roman_runs_containing_ou(&item.located.case.input).is_empty(), Some(first_difference_in_allcaps_ou_run(item)), + false, ), ( ALLCAPS_ROMAN_MIDDLE_DOT_RUNS, has_allcaps_roman_middle_dot_runs(&item.located.case.input), None, + false, ), ( KOREAN_PREFIXED_ALLCAPS_PARENTHETICAL, has_korean_prefixed_allcaps_parenthetical(&item.located.case.input), None, + false, + ), + ( + KOREAN_PREFIXED_CLOSED_ROMAN_ANNOTATION, + !korean_prefixed_closed_roman_annotation_spans(&item.located.case.input).is_empty(), + Some(first_difference_in_korean_prefixed_annotation_opening(item)), + true, ), ( KOREAN_INLINE_PARENTHESIZED_OPERATOR, !inline_parenthesized_operators(&item.located.case.input).is_empty(), Some(first_difference_in_inline_parenthesized_operator(item)), + false, ), ( MIXED_ROMAN_KOREAN_BEFORE_HEADWORD_EXPANSION, @@ -1492,6 +1658,7 @@ fn analyze( &mixed_roman_korean_before_headword_expansion_spans(&item.located.case.input), 1, )), + false, ), ( SINGLE_CAPITAL_PARENTHESIZED_DIGITS, @@ -1501,21 +1668,25 @@ fn analyze( &single_capital_parenthesized_digit_spans(&item.located.case.input), 1, )), + false, ), ( STANDALONE_UPPERCASE_ROMAN_WORD, has_standalone_uppercase_roman_word(&item.located.case.input), None, + false, ), ( TIGHT_TRIANGLE_BEFORE_KOREAN, !tight_triangle_positions(&item.located.case.input).is_empty(), Some(first_difference_in_tight_triangle(item)), + false, ), ( UPPERCASE_ROMAN_HEADWORD_EXPANSION, has_uppercase_roman_headword_expansion(&item.located.case.input), None, + false, ), ( UPPERCASE_ROMAN_HYPHEN_DIGITS, @@ -1525,6 +1696,7 @@ fn analyze( &uppercase_roman_hyphen_digit_spans(&item.located.case.input), 1, )), + false, ), ] { if !present { @@ -1536,11 +1708,11 @@ fn analyze( record_structural_cohort_case( stats, item, - primary, &primary_key, &reason_key, sample_limit, localized_first_difference, + localized_samples, ); } @@ -1675,6 +1847,7 @@ fn analyze( rule_36_transition_audit, pending_rule_review_clusters, pending_first_difference_cell_transitions, + pending_first_difference_transitions_after_localized_cohorts, overlapping_traits: traits, shards, samples, @@ -1778,13 +1951,61 @@ fn markdown(report: &AnalysisReport) -> String { } } + text.push_str("\n## Residual first-difference transitions after localized cohorts\n\n"); + text.push_str( + "This ranking removes only cases whose first difference is inside an existing \ + output-localized cohort. Broad input-only traits are not exclusion masks. The residual \ + table therefore prioritizes new causes without hiding a mismatch merely because an \ + unrelated structure coexists elsewhere in its sentence.\n\n", + ); + let mut residual_transitions = report + .pending_first_difference_transitions_after_localized_cohorts + .iter() + .collect::>(); + residual_transitions.sort_by(|(left_key, left), (right_key, right)| { + right + .cases + .cmp(&left.cases) + .then_with(|| left_key.cmp(right_key)) + }); + text.push_str("| Rank | Expected → actual first cell | Residual cases |\n|---:|---|---:|\n"); + for (rank, (transition, stats)) in residual_transitions.iter().take(20).enumerate() { + text.push_str(&format!( + "| {} | `{transition}` | {} |\n", + rank + 1, + stats.cases + )); + } + for (transition, stats) in residual_transitions.iter().take(10) { + text.push_str(&format!("\n### Residual `{transition}`\n\n")); + for sample in &stats.samples { + text.push_str(&format!( + "- `{}` #{}: {}\n - expected: `{}`\n - actual: `{}`\n - first differing cell (zero-based): {}\n - current primary/reason: `{}` / `{}`\n", + sample.shard, + sample.index, + sample + .input + .chars() + .take(180) + .collect::() + .replace('`', "\\`"), + sample.expected_excerpt, + sample.actual_excerpt, + sample.first_difference_cell.unwrap_or(0), + sample.primary_class, + sample.reason + )); + } + } + text.push_str("\n## Cross-cutting input-only structural cohorts\n\n"); text.push_str( "These are cross-cutting input-only structural cohorts, not new primary classes and not \ engine routing rules. Candidate selection never changes a case's existing primary \ - class. Only cohort members already classified as `pending_rule_review` form a pending \ + class by itself. Only cohort members already classified as `pending_rule_review` form a pending \ subcluster; exact and other-primary members are controls that retain their existing \ - outcomes. The \ + outcomes. A separate classifier may use independently justified, output-localized PDF \ + evidence, as in the rule-34 three-cell contradiction below. The \ `uppercase_roman_headword_closed_multiword_parenthetical` gate requires a two-or-more \ character uppercase ASCII headword immediately followed by a closed parenthesis whose \ contents are two or more ASCII Roman words separated only by spaces. Because the \ @@ -1809,6 +2030,11 @@ fn markdown(report: &AnalysisReport) -> String { Korean character and a closed body of two or more uppercase ASCII letters. It \ intentionally contains both acronym annotations (`책임자(COO)`) and scientific \ formulae (`일산화탄소(CO)`) so their semantic collision remains measurable. The \ + `korean_prefixed_closed_roman_annotation_rule_34_order` gate accepts the narrower \ + rule-34 body grammar after an immediately preceding Korean character and localizes only \ + the current engine's Korean opening-parenthesis cells. This distinguishes the PDF's \ + parenthesis-before-Roman-indicator order from unrelated differences later in the same \ + sentence. The \ `multi_character_allcaps_roman_runs_joined_by_middle_dot` gate requires two maximal \ ASCII-letter runs of at least two capitals joined directly by U+00B7, with \ non-alphanumeric outer boundaries. It records shapes such as `AI·SW` without assigning \ @@ -1844,7 +2070,7 @@ fn markdown(report: &AnalysisReport) -> String { text.push_str(&format!( "Of the {} candidates, {pending} are the actual `pending_rule_review` subcluster. \ The other {} candidates are exact or existing non-pending-primary controls; this \ - cohort does not reclassify them.\n\n", + membership alone does not reclassify them.\n\n", stats.candidates, stats.candidates - pending )); @@ -2121,6 +2347,37 @@ fn markdown(report: &AnalysisReport) -> String { stats.candidates, stats.exact, stats.mismatch )); } + if let Some(stats) = report + .pending_rule_review_clusters + .get(KOREAN_PREFIXED_CLOSED_ROMAN_ANNOTATION) + { + let corpus_suspect = stats + .mismatch_primary_classes + .get("corpus_suspect") + .copied() + .unwrap_or(0); + let opposite_order = stats + .first_difference_in_output_signature_transitions + .get("U+2834 ⠴ -> U+2826 ⠦") + .copied() + .unwrap_or(0); + text.push_str(&format!( + "\nCurrent rule-34 opening-order measurement: {} structural candidates, {} exact \ + controls, {} mismatches, and {}/{} evaluable mismatches whose first difference is \ + inside the current engine's Korean opening-parenthesis cells. Only the \ + {opposite_order} localized first-cell transitions have the reference/current order \ + `⠴` versus `⠦`. After requiring the complete reference `⠴⠐⠣` versus current/PDF \ + `⠦⠄⠴` three-cell signature and preserving higher-priority comparison \ + classifications, {corpus_suspect} are classified as `corpus_suspect`; mere \ + coexistence with a Korean-prefixed Roman annotation does not change a primary \ + class.\n", + stats.candidates, + stats.exact, + stats.mismatch, + stats.first_difference_in_output_signature, + stats.output_signature_mismatches_evaluated + )); + } if let Some(stats) = report .pending_rule_review_clusters .get(ALLCAPS_ROMAN_MIDDLE_DOT_RUNS) @@ -2394,7 +2651,8 @@ fn markdown(report: &AnalysisReport) -> String { text.push_str( "Rule 34 says that when Roman text is enclosed by quotation marks or brackets, the \ Roman terminator is omitted; its PDF example is `링컨(Lincoln)은 미국의 제16대 \ - 대통령이다.` Rule 54 says that text immediately after an opening bracket and \ + 대통령이다.` The example's cells put the printed Korean opening parenthesis \ + (`⠦⠄`) before the Roman indicator (`⠴`). Rule 54 says that text immediately after an opening bracket and \ immediately before a closing bracket is attached. Together these establish the \ Korean-prefix + closed-Roman-annotation context independently of corpus expected \ values. A following comma or period is outside the already closed annotation and \ @@ -2402,7 +2660,11 @@ fn markdown(report: &AnalysisReport) -> String { The implementation gate exists only inside `split_mixed_math_word`, after the prefix \ has been proved entirely Korean. It accepts a fully closed parenthesized Roman word \ (including ASCII digits such as `O4O`) plus ordinary trailing prose punctuation. \ - The global math detector is byte-for-byte unchanged; regression tests preserve its \ + The corpus audit treats the opposite localized reference prefix (`⠴⠐⠣`, Roman \ + indicator plus UEB opening parenthesis) as a data-reference contradiction only when \ + all three cells and the real input position agree. Broad sentence-level coexistence is \ + retained as an exact or existing-primary control. This audit does not alter engine \ + routing. The global math detector is byte-for-byte unchanged; regression tests preserve its \ existing standalone results for `(x)`, `(A)`, and `(abc)`, while explicit forms such \ as `(x+1)`, `(a/b)`, and `(x₁)` remain math candidates.\n\n\ Against the immediately preceding 63,399-exact run, exact matches increased by 2,092. \ @@ -2802,6 +3064,73 @@ mod tests { assert_eq!(has_korean_prefixed_allcaps_parenthetical(input), expected); } + #[rstest::rstest] + #[case::pdf_example("링컨(Lincoln)은", vec!["(Lincoln)"])] + #[case::allcaps_annotation("엠디(MD),", vec!["(MD)"])] + #[case::roman_suffix("폐쇄회로(CC)TV", vec!["(CC)"])] + #[case::alphanumeric("표기(O4O)는", vec!["(O4O)"])] + #[case::space_in_body("표기(Home Alliance)는", vec![])] + #[case::operator_in_body("수식(x+1)은", vec![])] + #[case::roman_prefix("HCA(Home)는", vec![])] + #[case::space_before_open("표기 (MD)는", vec![])] + #[case::unclosed("표기(MD", vec![])] + fn detects_korean_prefixed_closed_roman_annotations( + #[case] input: &str, + #[case] expected: Vec<&str>, + ) { + let actual = korean_prefixed_closed_roman_annotation_spans(input) + .into_iter() + .map(|span| &input[span.start_byte..span.end_byte]) + .collect::>(); + assert_eq!(actual, expected); + } + + #[test] + fn locates_rule_34_opening_after_the_real_korean_prefix() { + let input = "앞말 링컨(Lincoln)은"; + let actual = braillify::encode_to_unicode(input).expect("rule-34 probe must encode"); + let ranges = korean_prefixed_annotation_opening_ranges(input, &actual); + + assert_eq!(ranges.len(), 1); + assert_eq!(actual.chars().nth(ranges[0].start), Some('⠦')); + assert_eq!(actual.chars().nth(ranges[0].start + 1), Some('⠄')); + } + + #[test] + fn classifies_only_the_rule_34_three_cell_reference_order_as_corpus_suspect() { + let input = "링컨(Lincoln)은"; + let actual = braillify::encode_to_unicode(input).expect("rule-34 probe must encode"); + let opening = korean_prefixed_annotation_opening_ranges(input, &actual) + .into_iter() + .next() + .expect("opening must be localized"); + let mut expected = actual.chars().collect::>(); + expected.splice(opening.start..opening.start + 3, ['⠴', '⠐', '⠣']); + let encoded = EncodedCase { + located: LocatedCase { + shard: "synthetic.json".to_string(), + index: 1, + case: CorpusCase { + input: input.to_string(), + unicode: expected.into_iter().collect(), + }, + }, + actual: Ok(actual), + nfc_actual: None, + nfkc_actual: None, + singleton_unsupported_characters: Vec::new(), + }; + + assert!(is_rule_34_reference_order_contradiction(&encoded)); + assert_eq!( + classify(&encoded, &BTreeSet::new()), + ( + PrimaryClass::CorpusSuspect, + Reason::Rule34RomanIndicatorBeforeOpeningParenthesis + ) + ); + } + #[rstest::rstest] #[case::embedded_in_korean("AI·SW교육", true)] #[case::standalone("DRX·SNS", true)] From ed20f995138b207be5a4b4bad72587c2be8411d4 Mon Sep 17 00:00:00 2001 From: devfive Date: Tue, 1 Sep 2026 07:59:46 +0900 Subject: [PATCH 19/77] Localize decimal point corpus transitions --- docs/corpus-analysis/NIKL_2025_V1.md | 229 ++++++++++++------ .../braillify/examples/nikl_corpus_analyze.rs | 122 +++++++++- 2 files changed, 280 insertions(+), 71 deletions(-) diff --git a/docs/corpus-analysis/NIKL_2025_V1.md b/docs/corpus-analysis/NIKL_2025_V1.md index 4ff5c1fe..29e051b6 100644 --- a/docs/corpus-analysis/NIKL_2025_V1.md +++ b/docs/corpus-analysis/NIKL_2025_V1.md @@ -298,26 +298,26 @@ This ranking removes only cases whose first difference is inside an existing out | Rank | Expected → actual first cell | Residual cases | |---:|---|---:| -| 1 | `U+2830 ⠰ -> U+2820 ⠠` | 1015 | +| 1 | `U+2830 ⠰ -> U+2820 ⠠` | 1014 | | 2 | `U+2820 ⠠ -> U+2830 ⠰` | 790 | -| 3 | `U+2810 ⠐ -> U+2832 ⠲` | 656 | -| 4 | `U+2832 ⠲ -> U+2834 ⠴` | 654 | -| 5 | `U+2834 ⠴ -> U+2800 ⠀` | 543 | -| 6 | `U+283C ⠼ -> U+2800 ⠀` | 466 | -| 7 | `U+2820 ⠠ -> U+2834 ⠴` | 453 | -| 8 | `U+2826 ⠦ -> U+2834 ⠴` | 446 | -| 9 | `U+280E ⠎ -> U+280C ⠌` | 443 | -| 10 | `U+2826 ⠦ -> U+2800 ⠀` | 422 | -| 11 | `U+2808 ⠈ -> U+2832 ⠲` | 409 | -| 12 | `U+2801 ⠁ -> U+281C ⠜` | 381 | -| 13 | `U+2820 ⠠ -> U+2832 ⠲` | 357 | -| 14 | `U+2800 ⠀ -> U+2807 ⠇` | 347 | -| 15 | `U+2834 ⠴ -> U+2830 ⠰` | 321 | -| 16 | `U+2811 ⠑ -> U+282B ⠫` | 320 | -| 17 | `U+2832 ⠲ -> U+2838 ⠸` | 275 | -| 18 | `U+2800 ⠀ -> U+2832 ⠲` | 252 | -| 19 | `U+2810 ⠐ -> U+2802 ⠂` | 197 | -| 20 | `U+2817 ⠗ -> U+2820 ⠠` | 160 | +| 3 | `U+2810 ⠐ -> U+2832 ⠲` | 644 | +| 4 | `U+2834 ⠴ -> U+2800 ⠀` | 542 | +| 5 | `U+2820 ⠠ -> U+2834 ⠴` | 453 | +| 6 | `U+283C ⠼ -> U+2800 ⠀` | 452 | +| 7 | `U+2826 ⠦ -> U+2834 ⠴` | 444 | +| 8 | `U+280E ⠎ -> U+280C ⠌` | 443 | +| 9 | `U+2808 ⠈ -> U+2832 ⠲` | 407 | +| 10 | `U+2826 ⠦ -> U+2800 ⠀` | 387 | +| 11 | `U+2801 ⠁ -> U+281C ⠜` | 381 | +| 12 | `U+2820 ⠠ -> U+2832 ⠲` | 346 | +| 13 | `U+2800 ⠀ -> U+2807 ⠇` | 343 | +| 14 | `U+2834 ⠴ -> U+2830 ⠰` | 321 | +| 15 | `U+2811 ⠑ -> U+282B ⠫` | 320 | +| 16 | `U+2832 ⠲ -> U+2838 ⠸` | 275 | +| 17 | `U+2800 ⠀ -> U+2832 ⠲` | 250 | +| 18 | `U+2810 ⠐ -> U+2802 ⠂` | 197 | +| 19 | `U+2817 ⠗ -> U+2820 ⠠` | 160 | +| 20 | `U+2820 ⠠ -> U+2809 ⠉` | 152 | ### Residual `U+2830 ⠰ -> U+2820 ⠠` @@ -388,29 +388,6 @@ This ranking removes only cases whose first difference is inside an existing out - first differing cell (zero-based): 149 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -### Residual `U+2832 ⠲ -> U+2834 ⠴` - -- `sentence_01.json` #75: 특히 MKT의 매출이익률이 42.2%에 달했는데, 이는 경쟁사 대비 12.6%포인트(P) 높은 수준이다. 검찰은 한국타이어가 MKT에 몰아준 이익이 한국타이어 총수 일가에 흘러간 것으로 의심하고 있다. - - expected: `⠊⠗⠘⠕⠀⠼⠁⠃⠲⠋⠴⠏⠀⠙⠥⠟⠓⠪⠦⠄⠴⠠⠏⠠` - - actual: `⠊⠗⠘⠕⠀⠼⠁⠃⠴⠲⠋⠴⠏⠀⠙⠥⠟⠓⠪⠦⠄⠴⠠⠏` - - first differing cell (zero-based): 67 - - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_02.json` #561: 훈련 동안 대전경찰청 주관으로 충무로4가~동부서 3가(1.0km), 큰 마을 4가~용문역(2km), 사이언스빌 3가~읍내 4가(2.2km) 총 3개 구간이 통제된다. - - expected: `⠀⠼⠉⠫⠦⠄⠼⠁⠲⠚⠴⠅⠍⠠⠴⠐⠀⠋⠵⠀⠑⠣⠮⠀` - - actual: `⠀⠼⠉⠫⠦⠄⠼⠁⠴⠲⠚⠰⠅⠍⠠⠴⠐⠀⠋⠵⠀⠑⠣⠮` - - first differing cell (zero-based): 57 - - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_03.json` #366: 하루 최대 34.3리터(L)의 대용량 제습이 가능하며, 자연 증발 시스템이 적용돼 물통에 모인 물을 비워야 하는 번거로움이 없다. - - expected: `⠰⠽⠊⠗⠀⠼⠉⠙⠲⠉⠐⠕⠓⠎⠦⠄⠴⠠⠇⠠⠴⠺⠀⠊` - - actual: `⠰⠽⠊⠗⠀⠼⠉⠙⠴⠲⠉⠐⠕⠓⠎⠦⠄⠴⠠⠇⠠⠴⠺⠀` - - first differing cell (zero-based): 12 - - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_04.json` #48: 올트먼은 GPT-4가 기존 ‘GPT-3.5’보다 정확도가 40% 이상 높아졌지만, 이를 정보의 주요 출처로 사용해서는 안된다고도 지적했다. 거짓을 사실처럼 보이게 하는 이른바 ‘환각(hallucination) 현상’에 주의해야 한다는 입장이다. - - expected: `⠠⠠⠛⠏⠞⠤⠼⠉⠲⠑⠴⠄⠘⠥⠊⠀⠨⠻⠚⠧⠁⠊⠥⠫` - - actual: `⠠⠠⠛⠏⠞⠤⠼⠉⠴⠲⠑⠴⠄⠘⠥⠊⠀⠨⠻⠚⠧⠁⠊⠥` - - first differing cell (zero-based): 35 - - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` - ### Residual `U+2834 ⠴ -> U+2800 ⠀` - `sentence_01.json` #35: 삼성전자는 올해 CES에서 77인치 OLED TV를 처음으로 공개할 예정이다. 이는 지난 8월 ‘국제정보디스플레이학술대회(IMID) 2022’에서 삼성디스플레이가 공개한 77인치 퀀텀닷(QD)-OLED 패널을 탑재한 제품이다. @@ -434,29 +411,6 @@ This ranking removes only cases whose first difference is inside an existing out - first differing cell (zero-based): 70 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -### Residual `U+283C ⠼ -> U+2800 ⠀` - -- `sentence_01.json` #426: LG에너지솔루션은 혼다에 앞서 미국 제너럴모터스(GM)과의 배터리 합작법인 ‘얼티엄셀즈’를 설립해 투자를 진행 중이다. 이를 통해 북미 생산 능력을 올해 13GWh 규모에서 2025년 255GWh까지 확대할 예정이다. - - expected: `⠁⠮⠀⠥⠂⠚⠗⠀⠼⠁⠉⠴⠠⠛⠠⠺⠓⠲⠀⠈⠩⠑⠥⠝` - - actual: `⠁⠮⠀⠥⠂⠚⠗⠀⠀⠼⠁⠉⠠⠠⠛⠺⠓⠀⠀⠈⠩⠑⠥⠝` - - first differing cell (zero-based): 156 - - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_02.json` #68: 조성된 무장애나눔길은 통행이 많은 숲속의집~생태숲학습관 0.73km 구간에 평균 경사도 5% 이하 데크산책로와 황토포장길로 조성됐으며 전국 무장애나눔길 최초로 장애물 없는 생활환경(BF)인증을 추진하고 있다. - - expected: `⠁⠠⠪⠃⠈⠧⠒⠀⠼⠚⠲⠛⠉⠴⠅⠍⠲⠀⠈⠍⠫⠒⠝⠀` - - actual: `⠁⠠⠪⠃⠈⠧⠒⠀⠀⠼⠚⠲⠛⠉⠅⠍⠀⠀⠈⠍⠫⠒⠝⠀` - - first differing cell (zero-based): 62 - - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_03.json` #59: 이들 중 A씨(31) 등 2명은 지난 3월 25일 김해공항으로 필로폰 968g 등을 팬티 속에 숨겨 입국한 혐의를 받고 있다. - - expected: `⠙⠕⠂⠐⠥⠙⠷⠀⠼⠊⠋⠓⠴⠛⠲⠀⠊⠪⠶⠮⠀⠙⠗⠒` - - actual: `⠙⠕⠂⠐⠥⠙⠷⠀⠀⠼⠊⠋⠓⠛⠀⠀⠊⠪⠶⠮⠀⠙⠗⠒` - - first differing cell (zero-based): 71 - - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_04.json` #57: 미국지질조사국(USGS)에 따르면 이날 에콰도르 항구도시 과야킬에서 남쪽으로 약 80km 떨어진 지점에서 규모 6.8의 지진이 발생했다. 지진의 깊이는 66㎞다. - - expected: `⠭⠪⠐⠥⠀⠜⠁⠀⠼⠓⠚⠴⠅⠍⠲⠀⠠⠊⠞⠎⠨⠟⠀⠨` - - actual: `⠭⠪⠐⠥⠀⠜⠁⠀⠀⠼⠓⠚⠐⠅⠍⠀⠀⠠⠊⠞⠎⠨⠟⠀` - - first differing cell (zero-based): 79 - - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` - ### Residual `U+2820 ⠠ -> U+2834 ⠴` - `sentence_01.json` #78: 2023년형 Neo QLED는 스마트싱스뿐 아니라 매터(Matter) 기기까지 지원하는 원칩 모듈을 탑재해 별도의 동글을 사용하지 않아도, 다양한 사물인터넷(IoT) 기기를 직접 TV에 연결해 사용할 수 있다. @@ -480,6 +434,29 @@ This ranking removes only cases whose first difference is inside an existing out - first differing cell (zero-based): 30 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +### Residual `U+283C ⠼ -> U+2800 ⠀` + +- `sentence_01.json` #426: LG에너지솔루션은 혼다에 앞서 미국 제너럴모터스(GM)과의 배터리 합작법인 ‘얼티엄셀즈’를 설립해 투자를 진행 중이다. 이를 통해 북미 생산 능력을 올해 13GWh 규모에서 2025년 255GWh까지 확대할 예정이다. + - expected: `⠁⠮⠀⠥⠂⠚⠗⠀⠼⠁⠉⠴⠠⠛⠠⠺⠓⠲⠀⠈⠩⠑⠥⠝` + - actual: `⠁⠮⠀⠥⠂⠚⠗⠀⠀⠼⠁⠉⠠⠠⠛⠺⠓⠀⠀⠈⠩⠑⠥⠝` + - first differing cell (zero-based): 156 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_02.json` #68: 조성된 무장애나눔길은 통행이 많은 숲속의집~생태숲학습관 0.73km 구간에 평균 경사도 5% 이하 데크산책로와 황토포장길로 조성됐으며 전국 무장애나눔길 최초로 장애물 없는 생활환경(BF)인증을 추진하고 있다. + - expected: `⠁⠠⠪⠃⠈⠧⠒⠀⠼⠚⠲⠛⠉⠴⠅⠍⠲⠀⠈⠍⠫⠒⠝⠀` + - actual: `⠁⠠⠪⠃⠈⠧⠒⠀⠀⠼⠚⠲⠛⠉⠅⠍⠀⠀⠈⠍⠫⠒⠝⠀` + - first differing cell (zero-based): 62 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_03.json` #59: 이들 중 A씨(31) 등 2명은 지난 3월 25일 김해공항으로 필로폰 968g 등을 팬티 속에 숨겨 입국한 혐의를 받고 있다. + - expected: `⠙⠕⠂⠐⠥⠙⠷⠀⠼⠊⠋⠓⠴⠛⠲⠀⠊⠪⠶⠮⠀⠙⠗⠒` + - actual: `⠙⠕⠂⠐⠥⠙⠷⠀⠀⠼⠊⠋⠓⠛⠀⠀⠊⠪⠶⠮⠀⠙⠗⠒` + - first differing cell (zero-based): 71 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_04.json` #57: 미국지질조사국(USGS)에 따르면 이날 에콰도르 항구도시 과야킬에서 남쪽으로 약 80km 떨어진 지점에서 규모 6.8의 지진이 발생했다. 지진의 깊이는 66㎞다. + - expected: `⠭⠪⠐⠥⠀⠜⠁⠀⠼⠓⠚⠴⠅⠍⠲⠀⠠⠊⠞⠎⠨⠟⠀⠨` + - actual: `⠭⠪⠐⠥⠀⠜⠁⠀⠀⠼⠓⠚⠐⠅⠍⠀⠀⠠⠊⠞⠎⠨⠟⠀` + - first differing cell (zero-based): 79 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` + ### Residual `U+2826 ⠦ -> U+2834 ⠴` - `sentence_01.json` #38: 올해 소프트웨어(SW) 업계는 국내 인공지능(AI) 제품·서비스 고도화와 함께 전 산업영역에서 SW영역 확대가 가시화될 것으로 보인다. 또한 블록체인이 웹3(Web3)와 연계해 탈중앙화·탈독점화 경제체계로 진화할 것으로 예상된다. @@ -526,6 +503,29 @@ This ranking removes only cases whose first difference is inside an existing out - first differing cell (zero-based): 53 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +### Residual `U+2808 ⠈ -> U+2832 ⠲` + +- `sentence_01.json` #207: 우리금융지주가 약 7조원의 자본 여력을 등에 업고 인수·합병(M&A) 시장에 출격했다. 벤처캐피탈(VC) 인수를 시작으로 증권사와 보험사까지 인수한다는 계획이다. + - expected: `⠃⠘⠻⠦⠄⠴⠠⠍⠈⠯⠠⠁⠠⠴⠀⠠⠕⠨⠶⠝⠀⠰⠯⠈` + - actual: `⠃⠘⠻⠦⠄⠴⠠⠍⠲⠴⠈⠯⠲⠴⠠⠁⠠⠴⠀⠠⠕⠨⠶⠝` + - first differing cell (zero-based): 59 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_02.json` #146: KT&G(사장 백복인)가 산업정책연구원(IPS)이 주관하고 윤경 ESG 포럼이 주최하는 ‘윤경 ESG 포럼 CEO 서약식’에 참여해 윤리경영 의지를 다졌다. + - expected: `⠴⠠⠠⠅⠞⠈⠯⠠⠛⠦⠄⠇⠨⠶⠀⠘⠗⠁⠘⠭⠟⠠⠴⠫` + - actual: `⠴⠠⠠⠅⠞⠲⠴⠈⠯⠲⠴⠛⠦⠄⠇⠨⠶⠀⠘⠗⠁⠘⠭⠟` + - first differing cell (zero-based): 5 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_03.json` #191: 한국투자신탁운용의 에이스(ACE) 글로벌브랜드TOP10블룸버그 ETF는 연초 이후 22.6% 상승했다. 명품 기업 루이비통모에헤네시(LVMH), 대표 가치주로 꼽히는 프록터앤드갬블(P&G) 편입 비중이 높은 것이 특징이다. + - expected: `⠢⠘⠮⠦⠄⠴⠠⠏⠈⠯⠠⠛⠠⠴⠀⠙⠡⠕⠃⠀⠘⠕⠨⠍` + - actual: `⠢⠘⠮⠦⠄⠴⠠⠏⠲⠴⠈⠯⠲⠴⠠⠛⠠⠴⠀⠙⠡⠕⠃⠀` + - first differing cell (zero-based): 183 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_04.json` #159: 또 액화천연가스(LNG) 분야 협력을 확대하는 한편 수소환원제철 관련 공동 연구·개발(R&D)을 추진해 유럽연합(EU)의 탄소국경조정세(CBAM)와 같은 글로벌 규제와 자원 무기화에 공동 대응한다는 전략이다. + - expected: `⠗⠘⠂⠦⠄⠴⠠⠗⠈⠯⠠⠙⠠⠴⠮⠀⠰⠍⠨⠟⠚⠗⠀⠩` + - actual: `⠗⠘⠂⠦⠄⠴⠠⠗⠲⠴⠈⠯⠲⠴⠠⠙⠠⠴⠮⠀⠰⠍⠨⠟` + - first differing cell (zero-based): 90 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` + ### Residual `U+2826 ⠦ -> U+2800 ⠀` - `sentence_01.json` #234: 안랩은 블록체인 자회사 안랩블록체인컴퍼니가 웹(Web)3 지갑인 ‘ABC 월렛(Wallet)’ 모바일 버전을 구글플레이와 애플 앱스토어에 출시했다고 10일 발표했다. @@ -543,19 +543,20 @@ This ranking removes only cases whose first difference is inside an existing out - actual: `⠪⠐⠥⠀⠚⠎⠘⠪⠀⠀⠦⠠⠓⠼⠃⠀⠴⠠⠍⠊⠉⠗⠕⠀` - first differing cell (zero-based): 91 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_04.json` #185: 과기정통부가 같은 날 발표한 ‘2022년 디지털정보격차 실태조사’ 결과에서는 지난해 저소득층·장애인·농어민·고령층 등 취약계층의 디지털정보화(온라인·모바일·PC 활용) 수준이 국민 평균(100) 대비 76.2 정도로 나타났다. 전년(75.4) 대비 소폭 올랐다. - - expected: `⠌⠊⠲⠀⠨⠾⠉⠡⠦⠄⠼⠛⠑⠲⠙⠠⠴⠀⠊⠗⠘⠕⠀⠠` - - actual: `⠌⠊⠲⠀⠨⠾⠉⠡⠀⠀⠦⠼⠛⠑⠲⠙⠴⠀⠊⠗⠘⠕⠀⠠` - - first differing cell (zero-based): 228 +- `sentence_04.json` #441: 10~11일에는 지역 주민들과 함께 하는 전야제를 포함해 아주대 50년사 출판 기념보고회, 인공지능(AI)·6G 융합 콜로키움 시리즈가 열린다. + - expected: `⠟⠈⠿⠨⠕⠉⠪⠶⠦⠄⠴⠠⠠⠁⠊⠠⠴⠐⠆⠼⠋⠴⠠⠛` + - actual: `⠟⠈⠿⠨⠕⠉⠪⠶⠀⠀⠦⠠⠠⠁⠊⠴⠐⠼⠋⠠⠛⠀⠩⠶` + - first differing cell (zero-based): 93 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` ## Cross-cutting input-only structural cohorts -These are cross-cutting input-only structural cohorts, not new primary classes and not engine routing rules. Candidate selection never changes a case's existing primary class by itself. Only cohort members already classified as `pending_rule_review` form a pending subcluster; exact and other-primary members are controls that retain their existing outcomes. A separate classifier may use independently justified, output-localized PDF evidence, as in the rule-34 three-cell contradiction below. The `uppercase_roman_headword_closed_multiword_parenthetical` gate requires a two-or-more character uppercase ASCII headword immediately followed by a closed parenthesis whose contents are two or more ASCII Roman words separated only by spaces. Because the contents admit only letters and spaces, visible operators, subscript/superscript notation, and nested parentheses are excluded deterministically. The `mixed_roman_korean_word_before_uppercase_headword_expansion` gate further requires a whitespace-delimited preceding word that contains both ASCII Roman and Korean and ends in Korean. It locates the following uppercase headword itself, including its immediately preceding emitted cell, so an earlier Roman entry in the same sentence cannot satisfy the output audit. The `single_capital_followed_by_parenthesized_digits` gate requires a standalone capital, a non-empty closed ASCII-digit parenthetical, and alphanumeric outer boundaries. It likewise includes the emitted entry-boundary cell in localization. The `uppercase_roman_run_followed_by_hyphen_digits` gate requires a maximal uppercase ASCII run, literal ASCII hyphen-minus, a non-empty digit run, and alphanumeric outer boundaries. Its localized range includes the current encoded run and its immediately preceding cell, keeping `F-35`-style routing separate from both parenthesized digits and headword expansions. The `standalone_multi_character_uppercase_roman_word` gate finds maximal ASCII-letter runs of two or more capitals with non-alphanumeric boundaries; a run immediately followed by `(` is excluded so the HCA-style headword itself is not counted by both gates. The `korean_prefixed_closed_allcaps_parenthetical` gate requires an immediately preceding Korean character and a closed body of two or more uppercase ASCII letters. It intentionally contains both acronym annotations (`책임자(COO)`) and scientific formulae (`일산화탄소(CO)`) so their semantic collision remains measurable. The `korean_prefixed_closed_roman_annotation_rule_34_order` gate accepts the narrower rule-34 body grammar after an immediately preceding Korean character and localizes only the current engine's Korean opening-parenthesis cells. This distinguishes the PDF's parenthesis-before-Roman-indicator order from unrelated differences later in the same sentence. The `multi_character_allcaps_roman_runs_joined_by_middle_dot` gate requires two maximal ASCII-letter runs of at least two capitals joined directly by U+00B7, with non-alphanumeric outer boundaries. It records shapes such as `AI·SW` without assigning prose, mathematics, or science semantics. The `korean_inline_parenthesized_single_arithmetic_operator` gate requires an immediate `Korean(` + one rule-45 arithmetic operator + `)Korean` span. Unlike broad coexistence traits, it also locates the current engine's emitted structure and counts a mismatch as signature-local only when the sentence's first differing cell falls inside that output range. The `allcaps_roman_run_containing_ou` gate finds maximal, alphanumeric-delimited uppercase ASCII runs containing adjacent `OU`. It locates the independently encoded run signature in the complete current output and counts only first differences inside that signature as localized. The `tight_triangle_mark_immediately_before_korean` gate requires literal `△한글` with no input space and includes the first following Korean cell in its localized output range, so an observed missing-space difference is measured at the mark boundary. +These are cross-cutting input-only structural cohorts, not new primary classes and not engine routing rules. Candidate selection never changes a case's existing primary class by itself. Only cohort members already classified as `pending_rule_review` form a pending subcluster; exact and other-primary members are controls that retain their existing outcomes. A separate classifier may use independently justified, output-localized PDF evidence, as in the rule-34 three-cell contradiction below. The `uppercase_roman_headword_closed_multiword_parenthetical` gate requires a two-or-more character uppercase ASCII headword immediately followed by a closed parenthesis whose contents are two or more ASCII Roman words separated only by spaces. Because the contents admit only letters and spaces, visible operators, subscript/superscript notation, and nested parentheses are excluded deterministically. The `mixed_roman_korean_word_before_uppercase_headword_expansion` gate further requires a whitespace-delimited preceding word that contains both ASCII Roman and Korean and ends in Korean. It locates the following uppercase headword itself, including its immediately preceding emitted cell, so an earlier Roman entry in the same sentence cannot satisfy the output audit. The `single_capital_followed_by_parenthesized_digits` gate requires a standalone capital, a non-empty closed ASCII-digit parenthetical, and alphanumeric outer boundaries. It likewise includes the emitted entry-boundary cell in localization. The `uppercase_roman_run_followed_by_hyphen_digits` gate requires a maximal uppercase ASCII run, literal ASCII hyphen-minus, a non-empty digit run, and alphanumeric outer boundaries. Its localized range includes the current encoded run and its immediately preceding cell, keeping `F-35`-style routing separate from both parenthesized digits and headword expansions. The `standalone_multi_character_uppercase_roman_word` gate finds maximal ASCII-letter runs of two or more capitals with non-alphanumeric boundaries; a run immediately followed by `(` is excluded so the HCA-style headword itself is not counted by both gates. The `korean_prefixed_closed_allcaps_parenthetical` gate requires an immediately preceding Korean character and a closed body of two or more uppercase ASCII letters. It intentionally contains both acronym annotations (`책임자(COO)`) and scientific formulae (`일산화탄소(CO)`) so their semantic collision remains measurable. The `korean_prefixed_closed_roman_annotation_rule_34_order` gate accepts the narrower rule-34 body grammar after an immediately preceding Korean character and localizes only the current engine's Korean opening-parenthesis cells. This distinguishes the PDF's parenthesis-before-Roman-indicator order from unrelated differences later in the same sentence. The `multi_character_allcaps_roman_runs_joined_by_middle_dot` gate requires two maximal ASCII-letter runs of at least two capitals joined directly by U+00B7, with non-alphanumeric outer boundaries. It records shapes such as `AI·SW` without assigning prose, mathematics, or science semantics. The `korean_inline_parenthesized_single_arithmetic_operator` gate requires an immediate `Korean(` + one rule-45 arithmetic operator + `)Korean` span. Unlike broad coexistence traits, it also locates the current engine's emitted structure and counts a mismatch as signature-local only when the sentence's first differing cell falls inside that output range. The `allcaps_roman_run_containing_ou` gate finds maximal, alphanumeric-delimited uppercase ASCII runs containing adjacent `OU`. It locates the independently encoded run signature in the complete current output and counts only first differences inside that signature as localized. The `decimal_point_between_ascii_digits` gate finds whitespace-delimited words containing `digit.digit` and reproduces each whole word in a neutral Korean context, so suffixes and punctuation remain part of the current-engine signature. The `tight_triangle_mark_immediately_before_korean` gate requires literal `△한글` with no input space and includes the first following Korean cell in its localized output range, so an observed missing-space difference is measured at the mark boundary. | Cluster | Candidates | Exact | Mismatch | Conflicting-reference cases | |---|---:|---:|---:|---:| | `allcaps_roman_run_containing_ou` | 1816 | 55 | 1761 | 0 | +| `decimal_point_between_ascii_digits` | 4546 | 2584 | 1962 | 0 | | `korean_inline_parenthesized_single_arithmetic_operator` | 23 | 17 | 6 | 0 | | `korean_prefixed_closed_allcaps_parenthetical` | 54492 | 43779 | 10713 | 0 | | `korean_prefixed_closed_roman_annotation_rule_34_order` | 64382 | 50479 | 13903 | 0 | @@ -628,6 +629,92 @@ Representative `mismatch` samples: - first differing cell (zero-based): 0 - current primary/reason: `unsupported_character_review` / `unsupported_character_review` +### `decimal_point_between_ascii_digits` + +Of the 4546 candidates, 1905 are the actual `pending_rule_review` subcluster. The other 2641 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. + +For this output-signature audit, 1962 mismatches were evaluable and 875 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. + +Localized first-difference transitions: + +- `U+2832 ⠲ -> U+2834 ⠴`: 647 +- `U+2814 ⠔ -> U+2824 ⠤`: 53 +- `U+2826 ⠦ -> U+2800 ⠀`: 35 +- `U+2800 ⠀ -> U+280A ⠊`: 16 +- `U+2826 ⠦ -> U+2810 ⠐`: 15 + +Mismatch primary-class distribution: + +- `comparison_method`: 16 +- `corpus_suspect`: 26 +- `pending_rule_review`: 1905 +- `unsupported_character_review`: 15 + +Representative `exact` samples: + +- `sentence_01.json` #2: 이번 종합시행계획은 과학기술분야 연구개발 예산 5조2천418억, 정보통신방송기술(ICT) 분야 연구개발 예산 1조4천308억원을 대상으로 하며 지원 예산은 지난해(6조4천161억원) 보다 약 3.9% 증가한 규모이다. + - expected: `⠕⠘⠾⠀⠨⠿⠚⠃⠠⠕⠚⠗⠶⠈⠌⠚⠽⠁⠵⠀⠈⠧⠚⠁` + - actual: `⠕⠘⠾⠀⠨⠿⠚⠃⠠⠕⠚⠗⠶⠈⠌⠚⠽⠁⠵⠀⠈⠧⠚⠁` + - current primary/reason: `exact` / `exact` +- `sentence_02.json` #47: 정 의원은 지난 2월 통계청에서 발표한 2022년 합계출산율이 0.78명으로 OECD(경제협력개발기구)에 가입한 38개국 중 유일하게 출산율 1명대 이하를 기록하는 등 초저출생 현상이 가속화되고 있다고 지적했다. + - expected: `⠨⠻⠀⠺⠏⠒⠵⠀⠨⠕⠉⠒⠀⠼⠃⠏⠂⠀⠓⠿⠈⠌⠰⠻` + - actual: `⠨⠻⠀⠺⠏⠒⠵⠀⠨⠕⠉⠒⠀⠼⠃⠏⠂⠀⠓⠿⠈⠌⠰⠻` + - current primary/reason: `exact` / `exact` +- `sentence_03.json` #8: LG전자의 1분기 영업이익은 전년동기대비 22.9% 감소한 1조4974억원이다. 특히 2009년 국제회계기준(IFRS) 도입 이후 처음으로 삼성전자의 영업이익을 넘어섰다. + - expected: `⠴⠠⠠⠇⠛⠲⠨⠾⠨⠣⠺⠀⠼⠁⠘⠛⠈⠕⠀⠻⠎⠃⠕⠕` + - actual: `⠴⠠⠠⠇⠛⠲⠨⠾⠨⠣⠺⠀⠼⠁⠘⠛⠈⠕⠀⠻⠎⠃⠕⠕` + - current primary/reason: `exact` / `exact` +- `sentence_04.json` #43: 시카고상품거래소(CME) 페드워치에 따르면 연준이 이번 FOMC에서 금리를 동결할 가능성은 지난 15일 45.4%에서 17일 18.1%로 줄었다. 반면 0.25%포인트 인상할 가능성은 54.6%에서 81.9%로 상승했다. + - expected: `⠠⠕⠋⠈⠥⠇⠶⠙⠍⠢⠈⠎⠐⠗⠠⠥⠦⠄⠴⠠⠠⠉⠍⠑` + - actual: `⠠⠕⠋⠈⠥⠇⠶⠙⠍⠢⠈⠎⠐⠗⠠⠥⠦⠄⠴⠠⠠⠉⠍⠑` + - current primary/reason: `exact` / `exact` + +Representative `localized_mismatch` samples: + +- `sentence_01.json` #75: 특히 MKT의 매출이익률이 42.2%에 달했는데, 이는 경쟁사 대비 12.6%포인트(P) 높은 수준이다. 검찰은 한국타이어가 MKT에 몰아준 이익이 한국타이어 총수 일가에 흘러간 것으로 의심하고 있다. + - expected: `⠊⠗⠘⠕⠀⠼⠁⠃⠲⠋⠴⠏⠀⠙⠥⠟⠓⠪⠦⠄⠴⠠⠏⠠` + - actual: `⠊⠗⠘⠕⠀⠼⠁⠃⠴⠲⠋⠴⠏⠀⠙⠥⠟⠓⠪⠦⠄⠴⠠⠏` + - first differing cell (zero-based): 67 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_02.json` #5: 현재의 경기상황을 보여주는 동행지수(1월 기준, 99.8→99.4)·앞으로의 흐름을 보여주는 선행지수(98.8→98.5)는 모두 기준(100)보다 낮은 수준에서 하락세를 지속하면서 경기 부진을 반영하는 모양새다. + - expected: `⠛⠐⠀⠼⠊⠊⠲⠓⠀⠒⠕⠀⠼⠊⠊⠲⠙⠠⠴⠐⠆⠣⠲⠪` + - actual: `⠛⠐⠀⠼⠊⠊⠲⠓⠒⠕⠼⠊⠊⠲⠙⠠⠴⠐⠆⠣⠲⠪⠐⠥` + - first differing cell (zero-based): 52 + - current primary/reason: `pending_rule_review` / `number_rule_review` +- `sentence_03.json` #366: 하루 최대 34.3리터(L)의 대용량 제습이 가능하며, 자연 증발 시스템이 적용돼 물통에 모인 물을 비워야 하는 번거로움이 없다. + - expected: `⠰⠽⠊⠗⠀⠼⠉⠙⠲⠉⠐⠕⠓⠎⠦⠄⠴⠠⠇⠠⠴⠺⠀⠊` + - actual: `⠰⠽⠊⠗⠀⠼⠉⠙⠴⠲⠉⠐⠕⠓⠎⠦⠄⠴⠠⠇⠠⠴⠺⠀` + - first differing cell (zero-based): 12 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_04.json` #48: 올트먼은 GPT-4가 기존 ‘GPT-3.5’보다 정확도가 40% 이상 높아졌지만, 이를 정보의 주요 출처로 사용해서는 안된다고도 지적했다. 거짓을 사실처럼 보이게 하는 이른바 ‘환각(hallucination) 현상’에 주의해야 한다는 입장이다. + - expected: `⠠⠠⠛⠏⠞⠤⠼⠉⠲⠑⠴⠄⠘⠥⠊⠀⠨⠻⠚⠧⠁⠊⠥⠫` + - actual: `⠠⠠⠛⠏⠞⠤⠼⠉⠴⠲⠑⠴⠄⠘⠥⠊⠀⠨⠻⠚⠧⠁⠊⠥` + - first differing cell (zero-based): 35 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` + +Representative `mismatch` samples: + +- `sentence_01.json` #17: 지난달 30일(현지시각) 뉴욕증권거래소(NYSE)에서 다우존스30산업평균지수는 전 거래일보다 73.55포인트(0.22%) 하락한 3만3147.25로 마감했다. 대형주 중심의 S&P500지수는 9.78포인트(0.25%) 하락한 3839.50으로, 기술주 중심의 나스닥지수는 11.61포인트(0.11%) 하락한 1만0466.4 + - expected: `⠶⠠⠕⠢⠺⠀⠴⠠⠎⠈⠯⠠⠏⠼⠑⠚⠚⠨⠕⠠⠍⠉⠵⠀` + - actual: `⠶⠠⠕⠢⠺⠀⠴⠠⠠⠎⠲⠴⠈⠯⠲⠴⠏⠼⠑⠚⠚⠨⠕⠠` + - first differing cell (zero-based): 168 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_02.json` #5: 현재의 경기상황을 보여주는 동행지수(1월 기준, 99.8→99.4)·앞으로의 흐름을 보여주는 선행지수(98.8→98.5)는 모두 기준(100)보다 낮은 수준에서 하락세를 지속하면서 경기 부진을 반영하는 모양새다. + - expected: `⠛⠐⠀⠼⠊⠊⠲⠓⠀⠒⠕⠀⠼⠊⠊⠲⠙⠠⠴⠐⠆⠣⠲⠪` + - actual: `⠛⠐⠀⠼⠊⠊⠲⠓⠒⠕⠼⠊⠊⠲⠙⠠⠴⠐⠆⠣⠲⠪⠐⠥` + - first differing cell (zero-based): 52 + - current primary/reason: `pending_rule_review` / `number_rule_review` +- `sentence_03.json` #147: 20일(현지시간) 미국부동산중개인협회(NAR)는 지난 3월 기존 주택 매매 건수가 444만건으로 전월보다 2.4% 줄었다고 밝혔다. 전년 동기와 비교하면 22% 급감했다. + - expected: `⠚⠽⠦⠄⠴⠠⠠⠝⠁⠗⠠⠴⠉⠵⠀⠨⠕⠉⠒⠀⠼⠉⠏⠂` + - actual: `⠚⠽⠦⠄⠴⠠⠠⠝⠜⠠⠴⠉⠵⠀⠨⠕⠉⠒⠀⠼⠉⠏⠂⠀` + - first differing cell (zero-based): 46 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_04.json` #48: 올트먼은 GPT-4가 기존 ‘GPT-3.5’보다 정확도가 40% 이상 높아졌지만, 이를 정보의 주요 출처로 사용해서는 안된다고도 지적했다. 거짓을 사실처럼 보이게 하는 이른바 ‘환각(hallucination) 현상’에 주의해야 한다는 입장이다. + - expected: `⠠⠠⠛⠏⠞⠤⠼⠉⠲⠑⠴⠄⠘⠥⠊⠀⠨⠻⠚⠧⠁⠊⠥⠫` + - actual: `⠠⠠⠛⠏⠞⠤⠼⠉⠴⠲⠑⠴⠄⠘⠥⠊⠀⠨⠻⠚⠧⠁⠊⠥` + - first differing cell (zero-based): 35 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` + ### `korean_inline_parenthesized_single_arithmetic_operator` Of the 23 candidates, 6 are the actual `pending_rule_review` subcluster. The other 17 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. @@ -1139,6 +1226,8 @@ Current single-capital parenthesized-digits measurement: 1361 candidates, 0 exac Current mixed Roman+Korean boundary before uppercase headword-expansion measurement: 10 candidates, 0 exact controls, 10 mismatches, 10 members in the actual `pending_rule_review` subcluster, and 8/10 evaluable mismatches whose first difference is localized to the later headword's entry boundary/output. The detector cannot be satisfied by the earlier Roman entry. No HCA-shaped engine routing rule is introduced. +Current decimal-point measurement: 4546 candidates, 2584 exact controls, 1962 mismatches, 1905 members in the actual `pending_rule_review` subcluster, and 875/1962 evaluable mismatches whose first difference is inside the complete decimal-containing word's current-engine signature. Hangeul rules 43 and 48 keep an ASCII point between digits in the numeric sequence and encode it as the decimal point; rules 35 and 69 supply controls for adjacent Roman-number chains and Roman units. This is an implementation-candidate audit, not permission to specialize on a corpus reference. + Current all-caps `OU` measurement: 1816 candidates, 55 exact controls, 1761 mismatches, 1676 members in the actual `pending_rule_review` subcluster, and 1307/1761 evaluable mismatches whose first difference is inside the current-engine output signature for the detected run. This is a pronunciation-sensitive UEB review cohort, not an engine routing rule. Current standalone-uppercase measurement: 62411 candidates, 48757 exact controls, 13654 mismatches, and 12190 members in the actual `pending_rule_review` subcluster. Its high frequency does not make it causal: the same input shape is exact in many cases, and a sentence containing the shape may first differ at another Roman, numeric, or punctuation structure. No engine change is inferred from this cohort. diff --git a/libs/braillify/examples/nikl_corpus_analyze.rs b/libs/braillify/examples/nikl_corpus_analyze.rs index 946afd19..1cc68015 100644 --- a/libs/braillify/examples/nikl_corpus_analyze.rs +++ b/libs/braillify/examples/nikl_corpus_analyze.rs @@ -579,6 +579,7 @@ const SINGLE_CAPITAL_PARENTHESIZED_DIGITS: &str = "single_capital_followed_by_pa const MIXED_ROMAN_KOREAN_BEFORE_HEADWORD_EXPANSION: &str = "mixed_roman_korean_word_before_uppercase_headword_expansion"; const UPPERCASE_ROMAN_HYPHEN_DIGITS: &str = "uppercase_roman_run_followed_by_hyphen_digits"; +const DECIMAL_POINT_BETWEEN_DIGITS: &str = "decimal_point_between_ascii_digits"; #[derive(Clone, Copy, Debug, PartialEq, Eq)] struct InputSpan { @@ -586,6 +587,60 @@ struct InputSpan { end_byte: usize, } +/// Finds whitespace-delimited words containing an ASCII decimal point between +/// digits. Rule 43 explicitly keeps punctuation between digits in the same +/// numeric sequence, and rule 48 assigns the decimal-point cell. The whole +/// word is retained so the output locator reproduces suffix contexts such as +/// `%`, Roman units, Korean text, and closing punctuation. +fn decimal_word_spans(input: &str) -> Vec { + let mut spans = BTreeSet::new(); + for (dot_byte, _) in input.match_indices('.') { + let previous = input[..dot_byte].chars().next_back(); + let next = input[dot_byte + 1..].chars().next(); + if !previous.is_some_and(|ch| ch.is_ascii_digit()) + || !next.is_some_and(|ch| ch.is_ascii_digit()) + { + continue; + } + + let start_byte = input[..dot_byte] + .char_indices() + .rev() + .find_map(|(byte, ch)| ch.is_whitespace().then_some(byte + ch.len_utf8())) + .unwrap_or(0); + let end_byte = input[dot_byte + 1..] + .char_indices() + .find_map(|(offset, ch)| ch.is_whitespace().then_some(dot_byte + 1 + offset)) + .unwrap_or(input.len()); + spans.insert((start_byte, end_byte)); + } + spans + .into_iter() + .map(|(start_byte, end_byte)| InputSpan { + start_byte, + end_byte, + }) + .collect() +} + +fn first_difference_in_decimal_word(item: &EncodedCase) -> bool { + let Ok(actual) = &item.actual else { + return false; + }; + if actual == &item.located.case.unicode { + return false; + } + let first_difference = first_difference_cell(&item.located.case.unicode, actual); + korean_context_signature_ranges( + &item.located.case.input, + actual, + &decimal_word_spans(&item.located.case.input), + 0, + ) + .into_iter() + .any(|range| range.contains(&first_difference)) +} + /// Finds rule-34-shaped annotations whose opening parenthesis immediately /// follows Korean script and whose closed body contains only ordinary Roman /// letters, digits, apostrophes, periods, or hyphens. This is an input gate; @@ -955,6 +1010,7 @@ fn first_difference_in_korean_context_signature_spans( /// unrelated causes merely because a sentence also contains Roman text. fn first_difference_claimed_by_localized_cohort(item: &EncodedCase) -> bool { first_difference_in_allcaps_ou_run(item) + || first_difference_in_decimal_word(item) || first_difference_in_korean_prefixed_annotation_opening(item) || first_difference_in_inline_parenthesized_operator(item) || first_difference_in_tight_triangle(item) @@ -1517,6 +1573,10 @@ fn analyze( let mut samples = BTreeMap::>::new(); let mut rule_36_transition_audit = Rule36TransitionAudit::default(); let mut pending_rule_review_clusters = BTreeMap::from([ + ( + DECIMAL_POINT_BETWEEN_DIGITS.to_string(), + PendingRuleReviewClusterStats::default(), + ), ( ALLCAPS_ROMAN_RUN_CONTAINING_OU.to_string(), PendingRuleReviewClusterStats::default(), @@ -1619,6 +1679,12 @@ fn analyze( } for (cluster, present, localized_first_difference, localized_samples) in [ + ( + DECIMAL_POINT_BETWEEN_DIGITS, + !decimal_word_spans(&item.located.case.input).is_empty(), + Some(first_difference_in_decimal_word(item)), + true, + ), ( ALLCAPS_ROMAN_RUN_CONTAINING_OU, !allcaps_roman_runs_containing_ou(&item.located.case.input).is_empty(), @@ -2046,7 +2112,10 @@ fn markdown(report: &AnalysisReport) -> String { range. The `allcaps_roman_run_containing_ou` gate finds maximal, alphanumeric-delimited \ uppercase ASCII runs containing adjacent `OU`. It locates the independently encoded run \ signature in the complete current output and counts only first differences inside that \ - signature as localized. The `tight_triangle_mark_immediately_before_korean` gate requires literal \ + signature as localized. The `decimal_point_between_ascii_digits` gate finds \ + whitespace-delimited words containing `digit.digit` and reproduces each whole word in a \ + neutral Korean context, so suffixes and punctuation remain part of the current-engine \ + signature. The `tight_triangle_mark_immediately_before_korean` gate requires literal \ `△한글` with no input space and includes the first following Korean cell in its localized \ output range, so an observed missing-space difference is measured at the mark boundary.\n\n", ); @@ -2290,6 +2359,31 @@ fn markdown(report: &AnalysisReport) -> String { stats.output_signature_mismatches_evaluated )); } + if let Some(stats) = report + .pending_rule_review_clusters + .get(DECIMAL_POINT_BETWEEN_DIGITS) + { + let pending = stats + .mismatch_primary_classes + .get("pending_rule_review") + .copied() + .unwrap_or(0); + text.push_str(&format!( + "\nCurrent decimal-point measurement: {} candidates, {} exact controls, {} \ + mismatches, {pending} members in the actual `pending_rule_review` subcluster, and \ + {}/{} evaluable mismatches whose first difference is inside the complete \ + decimal-containing word's current-engine signature. Hangeul rules 43 and 48 keep \ + an ASCII point between digits in the numeric sequence and encode it as the decimal \ + point; rules 35 and 69 supply controls for adjacent Roman-number chains and Roman \ + units. This is an implementation-candidate audit, not permission to specialize on \ + a corpus reference.\n", + stats.candidates, + stats.exact, + stats.mismatch, + stats.first_difference_in_output_signature, + stats.output_signature_mismatches_evaluated + )); + } if let Some(stats) = report .pending_rule_review_clusters .get(ALLCAPS_ROMAN_RUN_CONTAINING_OU) @@ -2904,6 +2998,32 @@ mod tests { assert_eq!(cell_transition_key(expected, actual, index), transition); } + #[rstest::rstest] + #[case::korean_suffix("값은 3.14이다.", vec!["3.14이다."])] + #[case::roman_identifier("GPT-3.5보다", vec!["GPT-3.5보다"])] + #[case::unit_and_punctuation("구간(1.0km), 종료", vec!["구간(1.0km),"])] + #[case::multiple_points("주소 1.2.3 확인", vec!["1.2.3"])] + #[case::period_not_between_digits("제3. 항목", vec![])] + #[case::leading_decimal("값 .48", vec![])] + fn detects_decimal_words(#[case] input: &str, #[case] expected: Vec<&str>) { + let actual = decimal_word_spans(input) + .into_iter() + .map(|span| &input[span.start_byte..span.end_byte]) + .collect::>(); + assert_eq!(actual, expected); + } + + #[test] + fn locates_decimal_word_in_current_korean_context_output() { + let input = "수치는 34.3리터(L)이다."; + let actual = braillify::encode_to_unicode(input).expect("decimal probe must encode"); + let ranges = korean_context_signature_ranges(input, &actual, &decimal_word_spans(input), 0); + + assert_eq!(ranges.len(), 1); + assert!(ranges[0].start < ranges[0].end); + assert!(ranges[0].end <= actual.chars().count()); + } + #[test] fn roman_indicator_moves_before_capital_word_indicator() { assert_eq!(roman_before_capital_order("⠠⠠⠴⠁⠃"), "⠴⠠⠠⠁⠃"); From a7473da61b2938a2866bc425878c0e8516049878 Mon Sep 17 00:00:00 2001 From: devfive Date: Tue, 1 Sep 2026 08:08:01 +0900 Subject: [PATCH 20/77] Keep decimal points on numeric path --- docs/corpus-analysis/NIKL_2025_V1.md | 340 +++++++++--------- .../braillify/examples/nikl_corpus_analyze.rs | 11 + .../src/rules/korean/rule_english_symbol.rs | 42 +++ 3 files changed, 224 insertions(+), 169 deletions(-) diff --git a/docs/corpus-analysis/NIKL_2025_V1.md b/docs/corpus-analysis/NIKL_2025_V1.md index 29e051b6..663f75cb 100644 --- a/docs/corpus-analysis/NIKL_2025_V1.md +++ b/docs/corpus-analysis/NIKL_2025_V1.md @@ -7,9 +7,9 @@ | Metric | Count | |---|---:| | Total | 83528 | -| Exact | 65514 | -| Mismatch | 18014 | -| Exact accuracy | 78.43% | +| Exact | 66039 | +| Mismatch | 17489 | +| Exact accuracy | 79.06% | | Duplicate records | 0 | | Inputs with conflicting references | 0 | @@ -19,21 +19,21 @@ Primary classes are evidence gates, not permissions to change the engine. `imple | Primary class | Count | |---|---:| -| `comparison_method` | 303 | -| `corpus_suspect` | 1091 | -| `exact` | 65514 | -| `pending_rule_review` | 16429 | -| `unsupported_character_review` | 191 | +| `comparison_method` | 312 | +| `corpus_suspect` | 1093 | +| `exact` | 66039 | +| `pending_rule_review` | 15896 | +| `unsupported_character_review` | 188 | | Reproducible reason | Count | |---|---:| -| `exact` | 65514 | -| `foreign_text_rule_review` | 16250 | -| `nfkc_input_equivalent` | 303 | -| `number_rule_review` | 171 | +| `exact` | 66039 | +| `foreign_text_rule_review` | 15719 | +| `nfkc_input_equivalent` | 312 | +| `number_rule_review` | 169 | | `punctuation_rule_review` | 8 | -| `rule34_roman_indicator_before_opening_parenthesis` | 1091 | -| `unsupported_character_review` | 191 | +| `rule34_roman_indicator_before_opening_parenthesis` | 1093 | +| `unsupported_character_review` | 188 | ## Pending first-difference cell transitions @@ -41,26 +41,26 @@ This ranking is a diagnostic selector, not an implementation rule. It counts onl | Rank | Expected → actual first cell | Cases | |---:|---|---:| -| 1 | `U+2834 ⠴ -> U+2800 ⠀` | 2137 | +| 1 | `U+2834 ⠴ -> U+2800 ⠀` | 2138 | | 2 | `U+2815 ⠕ -> U+2833 ⠳` | 1311 | -| 3 | `U+2830 ⠰ -> U+2820 ⠠` | 1015 | -| 4 | `U+2820 ⠠ -> U+2830 ⠰` | 790 | -| 5 | `U+2810 ⠐ -> U+2832 ⠲` | 656 | -| 6 | `U+2832 ⠲ -> U+2834 ⠴` | 654 | -| 7 | `U+283C ⠼ -> U+2800 ⠀` | 466 | -| 8 | `U+2820 ⠠ -> U+2834 ⠴` | 453 | -| 9 | `U+2826 ⠦ -> U+2834 ⠴` | 446 | -| 10 | `U+280E ⠎ -> U+280C ⠌` | 443 | -| 11 | `U+2826 ⠦ -> U+2800 ⠀` | 422 | -| 12 | `U+2808 ⠈ -> U+2832 ⠲` | 409 | -| 13 | `U+2801 ⠁ -> U+281C ⠜` | 381 | +| 3 | `U+2830 ⠰ -> U+2820 ⠠` | 1018 | +| 4 | `U+2820 ⠠ -> U+2830 ⠰` | 796 | +| 5 | `U+2810 ⠐ -> U+2832 ⠲` | 658 | +| 6 | `U+283C ⠼ -> U+2800 ⠀` | 480 | +| 7 | `U+2820 ⠠ -> U+2834 ⠴` | 464 | +| 8 | `U+2826 ⠦ -> U+2834 ⠴` | 451 | +| 9 | `U+280E ⠎ -> U+280C ⠌` | 443 | +| 10 | `U+2826 ⠦ -> U+2800 ⠀` | 422 | +| 11 | `U+2808 ⠈ -> U+2832 ⠲` | 409 | +| 12 | `U+2801 ⠁ -> U+281C ⠜` | 386 | +| 13 | `U+2820 ⠠ -> U+2832 ⠲` | 372 | | 14 | `U+2800 ⠀ -> U+2807 ⠇` | 360 | -| 15 | `U+2820 ⠠ -> U+2832 ⠲` | 357 | -| 16 | `U+2834 ⠴ -> U+2830 ⠰` | 321 | -| 17 | `U+2811 ⠑ -> U+282B ⠫` | 320 | -| 18 | `U+2832 ⠲ -> U+2838 ⠸` | 275 | -| 19 | `U+2800 ⠀ -> U+2832 ⠲` | 252 | -| 20 | `U+2810 ⠐ -> U+2802 ⠂` | 197 | +| 15 | `U+2834 ⠴ -> U+2830 ⠰` | 324 | +| 16 | `U+2811 ⠑ -> U+282B ⠫` | 320 | +| 17 | `U+2832 ⠲ -> U+2838 ⠸` | 275 | +| 18 | `U+2800 ⠀ -> U+2832 ⠲` | 252 | +| 19 | `U+2810 ⠐ -> U+2802 ⠂` | 206 | +| 20 | `U+2817 ⠗ -> U+2820 ⠠` | 161 | ### `U+2834 ⠴ -> U+2800 ⠀` @@ -177,29 +177,6 @@ This ranking is a diagnostic selector, not an implementation rule. It counts onl - first differing cell (zero-based): 149 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -### `U+2832 ⠲ -> U+2834 ⠴` - -- `sentence_01.json` #75: 특히 MKT의 매출이익률이 42.2%에 달했는데, 이는 경쟁사 대비 12.6%포인트(P) 높은 수준이다. 검찰은 한국타이어가 MKT에 몰아준 이익이 한국타이어 총수 일가에 흘러간 것으로 의심하고 있다. - - expected: `⠊⠗⠘⠕⠀⠼⠁⠃⠲⠋⠴⠏⠀⠙⠥⠟⠓⠪⠦⠄⠴⠠⠏⠠` - - actual: `⠊⠗⠘⠕⠀⠼⠁⠃⠴⠲⠋⠴⠏⠀⠙⠥⠟⠓⠪⠦⠄⠴⠠⠏` - - first differing cell (zero-based): 67 - - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_02.json` #561: 훈련 동안 대전경찰청 주관으로 충무로4가~동부서 3가(1.0km), 큰 마을 4가~용문역(2km), 사이언스빌 3가~읍내 4가(2.2km) 총 3개 구간이 통제된다. - - expected: `⠀⠼⠉⠫⠦⠄⠼⠁⠲⠚⠴⠅⠍⠠⠴⠐⠀⠋⠵⠀⠑⠣⠮⠀` - - actual: `⠀⠼⠉⠫⠦⠄⠼⠁⠴⠲⠚⠰⠅⠍⠠⠴⠐⠀⠋⠵⠀⠑⠣⠮` - - first differing cell (zero-based): 57 - - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_03.json` #366: 하루 최대 34.3리터(L)의 대용량 제습이 가능하며, 자연 증발 시스템이 적용돼 물통에 모인 물을 비워야 하는 번거로움이 없다. - - expected: `⠰⠽⠊⠗⠀⠼⠉⠙⠲⠉⠐⠕⠓⠎⠦⠄⠴⠠⠇⠠⠴⠺⠀⠊` - - actual: `⠰⠽⠊⠗⠀⠼⠉⠙⠴⠲⠉⠐⠕⠓⠎⠦⠄⠴⠠⠇⠠⠴⠺⠀` - - first differing cell (zero-based): 12 - - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_04.json` #48: 올트먼은 GPT-4가 기존 ‘GPT-3.5’보다 정확도가 40% 이상 높아졌지만, 이를 정보의 주요 출처로 사용해서는 안된다고도 지적했다. 거짓을 사실처럼 보이게 하는 이른바 ‘환각(hallucination) 현상’에 주의해야 한다는 입장이다. - - expected: `⠠⠠⠛⠏⠞⠤⠼⠉⠲⠑⠴⠄⠘⠥⠊⠀⠨⠻⠚⠧⠁⠊⠥⠫` - - actual: `⠠⠠⠛⠏⠞⠤⠼⠉⠴⠲⠑⠴⠄⠘⠥⠊⠀⠨⠻⠚⠧⠁⠊⠥` - - first differing cell (zero-based): 35 - - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` - ### `U+283C ⠼ -> U+2800 ⠀` - `sentence_01.json` #426: LG에너지솔루션은 혼다에 앞서 미국 제너럴모터스(GM)과의 배터리 합작법인 ‘얼티엄셀즈’를 설립해 투자를 진행 중이다. 이를 통해 북미 생산 능력을 올해 13GWh 규모에서 2025년 255GWh까지 확대할 예정이다. @@ -292,31 +269,54 @@ This ranking is a diagnostic selector, not an implementation rule. It counts onl - first differing cell (zero-based): 53 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +### `U+2826 ⠦ -> U+2800 ⠀` + +- `sentence_01.json` #234: 안랩은 블록체인 자회사 안랩블록체인컴퍼니가 웹(Web)3 지갑인 ‘ABC 월렛(Wallet)’ 모바일 버전을 구글플레이와 애플 앱스토어에 출시했다고 10일 발표했다. + - expected: `⠎⠉⠕⠫⠀⠏⠗⠃⠦⠄⠴⠠⠺⠑⠃⠠⠴⠼⠉⠀⠨⠕⠫⠃` + - actual: `⠎⠉⠕⠫⠀⠏⠗⠃⠀⠀⠦⠠⠺⠑⠃⠴⠼⠉⠀⠨⠕⠫⠃⠟` + - first differing cell (zero-based): 44 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_02.json` #168: 5주 과정인 ‘도전’ 프로그램과 5개월 과정인 ‘도전+(플러스)’ 프로그램으로 나뉘어 진행되며, 프로그램 이수 시 1개월에 50만 원씩 최대 300만 원까지 참여 수당과 인센티브를 지원한다. + - expected: `⠠⠦⠊⠥⠨⠾⠀⠢⠦⠄⠙⠮⠐⠎⠠⠪⠠⠴⠴⠄⠀⠙⠪⠐` + - actual: `⠠⠦⠊⠥⠨⠾⠀⠢⠀⠦⠄⠙⠮⠐⠎⠠⠪⠠⠴⠴⠄⠀⠙⠪` + - first differing cell (zero-based): 53 + - current primary/reason: `pending_rule_review` / `number_rule_review` +- `sentence_03.json` #64: SK에코플랜트는 20일 종로구 수송동 본사에서 현대엔지니어링, USNC와 ‘수소 마이크로 허브(H2 Micro Hub)’ 구축을 위한 3자간 업무협약(MOU)을 체결했다고 밝혔다. + - expected: `⠪⠐⠥⠀⠚⠎⠘⠪⠦⠄⠴⠠⠓⠼⠃⠀⠠⠍⠊⠉⠗⠕⠀⠠` + - actual: `⠪⠐⠥⠀⠚⠎⠘⠪⠀⠀⠦⠠⠓⠼⠃⠀⠴⠠⠍⠊⠉⠗⠕⠀` + - first differing cell (zero-based): 91 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_04.json` #185: 과기정통부가 같은 날 발표한 ‘2022년 디지털정보격차 실태조사’ 결과에서는 지난해 저소득층·장애인·농어민·고령층 등 취약계층의 디지털정보화(온라인·모바일·PC 활용) 수준이 국민 평균(100) 대비 76.2 정도로 나타났다. 전년(75.4) 대비 소폭 올랐다. + - expected: `⠌⠊⠲⠀⠨⠾⠉⠡⠦⠄⠼⠛⠑⠲⠙⠠⠴⠀⠊⠗⠘⠕⠀⠠` + - actual: `⠌⠊⠲⠀⠨⠾⠉⠡⠀⠀⠦⠼⠛⠑⠲⠙⠴⠀⠊⠗⠘⠕⠀⠠` + - first differing cell (zero-based): 228 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` + ## Residual first-difference transitions after localized cohorts This ranking removes only cases whose first difference is inside an existing output-localized cohort. Broad input-only traits are not exclusion masks. The residual table therefore prioritizes new causes without hiding a mismatch merely because an unrelated structure coexists elsewhere in its sentence. | Rank | Expected → actual first cell | Residual cases | |---:|---|---:| -| 1 | `U+2830 ⠰ -> U+2820 ⠠` | 1014 | -| 2 | `U+2820 ⠠ -> U+2830 ⠰` | 790 | -| 3 | `U+2810 ⠐ -> U+2832 ⠲` | 644 | +| 1 | `U+2830 ⠰ -> U+2820 ⠠` | 1016 | +| 2 | `U+2820 ⠠ -> U+2830 ⠰` | 795 | +| 3 | `U+2810 ⠐ -> U+2832 ⠲` | 645 | | 4 | `U+2834 ⠴ -> U+2800 ⠀` | 542 | -| 5 | `U+2820 ⠠ -> U+2834 ⠴` | 453 | -| 6 | `U+283C ⠼ -> U+2800 ⠀` | 452 | -| 7 | `U+2826 ⠦ -> U+2834 ⠴` | 444 | +| 5 | `U+283C ⠼ -> U+2800 ⠀` | 466 | +| 6 | `U+2820 ⠠ -> U+2834 ⠴` | 453 | +| 7 | `U+2826 ⠦ -> U+2834 ⠴` | 445 | | 8 | `U+280E ⠎ -> U+280C ⠌` | 443 | | 9 | `U+2808 ⠈ -> U+2832 ⠲` | 407 | | 10 | `U+2826 ⠦ -> U+2800 ⠀` | 387 | -| 11 | `U+2801 ⠁ -> U+281C ⠜` | 381 | -| 12 | `U+2820 ⠠ -> U+2832 ⠲` | 346 | +| 11 | `U+2801 ⠁ -> U+281C ⠜` | 386 | +| 12 | `U+2820 ⠠ -> U+2832 ⠲` | 353 | | 13 | `U+2800 ⠀ -> U+2807 ⠇` | 343 | -| 14 | `U+2834 ⠴ -> U+2830 ⠰` | 321 | +| 14 | `U+2834 ⠴ -> U+2830 ⠰` | 324 | | 15 | `U+2811 ⠑ -> U+282B ⠫` | 320 | | 16 | `U+2832 ⠲ -> U+2838 ⠸` | 275 | | 17 | `U+2800 ⠀ -> U+2832 ⠲` | 250 | -| 18 | `U+2810 ⠐ -> U+2802 ⠂` | 197 | -| 19 | `U+2817 ⠗ -> U+2820 ⠠` | 160 | +| 18 | `U+2810 ⠐ -> U+2802 ⠂` | 206 | +| 19 | `U+2817 ⠗ -> U+2820 ⠠` | 161 | | 20 | `U+2820 ⠠ -> U+2809 ⠉` | 152 | ### Residual `U+2830 ⠰ -> U+2820 ⠠` @@ -411,29 +411,6 @@ This ranking removes only cases whose first difference is inside an existing out - first differing cell (zero-based): 70 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -### Residual `U+2820 ⠠ -> U+2834 ⠴` - -- `sentence_01.json` #78: 2023년형 Neo QLED는 스마트싱스뿐 아니라 매터(Matter) 기기까지 지원하는 원칩 모듈을 탑재해 별도의 동글을 사용하지 않아도, 다양한 사물인터넷(IoT) 기기를 직접 TV에 연결해 사용할 수 있다. - - expected: `⠻⠀⠴⠠⠝⠑⠕⠀⠠⠠⠟⠇⠑⠙⠲⠉⠵⠀⠠⠪⠑⠓⠪⠠` - - actual: `⠻⠀⠴⠠⠝⠑⠕⠀⠴⠠⠠⠟⠇⠫⠲⠉⠵⠀⠠⠪⠑⠓⠪⠠` - - first differing cell (zero-based): 17 - - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_02.json` #332: 홍성군청소년수련관(관장 피기용) 진로체험지원센터가 오는 16일 청운대학교 운동장에서 관내 중학교 1학년 대상으로 제9회 홍성군 청소년 진로박람회 ‘꿈(JOB)고 두드림(DO DREAM)’을 진행한다. - - expected: `⠦⠄⠴⠠⠠⠙⠕⠀⠠⠠⠙⠗⠂⠍⠠⠴⠴⠄⠮⠀⠨⠟⠚⠗` - - actual: `⠦⠄⠴⠠⠠⠙⠕⠀⠴⠠⠠⠙⠗⠂⠍⠠⠴⠴⠄⠮⠀⠨⠟⠚` - - first differing cell (zero-based): 183 - - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_03.json` #302: 건국대 경영전문대학원(KU MBA)은 2015년부터 국제경영대학발전협의회(AACSB) 인증을 보유하고 있다. 고등교육법 시행령에 따라 교육부가 인가한 13개 경영전문대학원 중 하나다. 경영전문대학원인 만큼 엄격하게 교육 품질을 관리한다. - - expected: `⠦⠄⠴⠠⠠⠅⠥⠀⠠⠠⠍⠃⠁⠠⠴⠵⠀⠼⠃⠚⠁⠑⠀⠉` - - actual: `⠦⠄⠴⠠⠠⠅⠥⠀⠴⠠⠠⠍⠃⠁⠠⠴⠵⠀⠼⠃⠚⠁⠑⠀` - - first differing cell (zero-based): 29 - - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_04.json` #363: 켈리(KELLY)는 ‘KEEP NATUALLY’의 줄임말로 인위적인 것을 최소화하고 자연주의적인 원료, 공법, 맛을 추구한다는 의미를 담고 있다. - - expected: `⠴⠠⠠⠅⠑⠑⠏⠀⠠⠠⠝⠁⠞⠥⠁⠇⠇⠽⠴⠄⠺⠀⠨⠯` - - actual: `⠴⠠⠠⠅⠑⠑⠏⠀⠴⠠⠠⠝⠁⠞⠥⠁⠇⠇⠽⠴⠄⠺⠀⠨` - - first differing cell (zero-based): 30 - - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` - ### Residual `U+283C ⠼ -> U+2800 ⠀` - `sentence_01.json` #426: LG에너지솔루션은 혼다에 앞서 미국 제너럴모터스(GM)과의 배터리 합작법인 ‘얼티엄셀즈’를 설립해 투자를 진행 중이다. 이를 통해 북미 생산 능력을 올해 13GWh 규모에서 2025년 255GWh까지 확대할 예정이다. @@ -457,6 +434,29 @@ This ranking removes only cases whose first difference is inside an existing out - first differing cell (zero-based): 79 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +### Residual `U+2820 ⠠ -> U+2834 ⠴` + +- `sentence_01.json` #78: 2023년형 Neo QLED는 스마트싱스뿐 아니라 매터(Matter) 기기까지 지원하는 원칩 모듈을 탑재해 별도의 동글을 사용하지 않아도, 다양한 사물인터넷(IoT) 기기를 직접 TV에 연결해 사용할 수 있다. + - expected: `⠻⠀⠴⠠⠝⠑⠕⠀⠠⠠⠟⠇⠑⠙⠲⠉⠵⠀⠠⠪⠑⠓⠪⠠` + - actual: `⠻⠀⠴⠠⠝⠑⠕⠀⠴⠠⠠⠟⠇⠫⠲⠉⠵⠀⠠⠪⠑⠓⠪⠠` + - first differing cell (zero-based): 17 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_02.json` #332: 홍성군청소년수련관(관장 피기용) 진로체험지원센터가 오는 16일 청운대학교 운동장에서 관내 중학교 1학년 대상으로 제9회 홍성군 청소년 진로박람회 ‘꿈(JOB)고 두드림(DO DREAM)’을 진행한다. + - expected: `⠦⠄⠴⠠⠠⠙⠕⠀⠠⠠⠙⠗⠂⠍⠠⠴⠴⠄⠮⠀⠨⠟⠚⠗` + - actual: `⠦⠄⠴⠠⠠⠙⠕⠀⠴⠠⠠⠙⠗⠂⠍⠠⠴⠴⠄⠮⠀⠨⠟⠚` + - first differing cell (zero-based): 183 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_03.json` #302: 건국대 경영전문대학원(KU MBA)은 2015년부터 국제경영대학발전협의회(AACSB) 인증을 보유하고 있다. 고등교육법 시행령에 따라 교육부가 인가한 13개 경영전문대학원 중 하나다. 경영전문대학원인 만큼 엄격하게 교육 품질을 관리한다. + - expected: `⠦⠄⠴⠠⠠⠅⠥⠀⠠⠠⠍⠃⠁⠠⠴⠵⠀⠼⠃⠚⠁⠑⠀⠉` + - actual: `⠦⠄⠴⠠⠠⠅⠥⠀⠴⠠⠠⠍⠃⠁⠠⠴⠵⠀⠼⠃⠚⠁⠑⠀` + - first differing cell (zero-based): 29 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_04.json` #363: 켈리(KELLY)는 ‘KEEP NATUALLY’의 줄임말로 인위적인 것을 최소화하고 자연주의적인 원료, 공법, 맛을 추구한다는 의미를 담고 있다. + - expected: `⠴⠠⠠⠅⠑⠑⠏⠀⠠⠠⠝⠁⠞⠥⠁⠇⠇⠽⠴⠄⠺⠀⠨⠯` + - actual: `⠴⠠⠠⠅⠑⠑⠏⠀⠴⠠⠠⠝⠁⠞⠥⠁⠇⠇⠽⠴⠄⠺⠀⠨` + - first differing cell (zero-based): 30 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` + ### Residual `U+2826 ⠦ -> U+2834 ⠴` - `sentence_01.json` #38: 올해 소프트웨어(SW) 업계는 국내 인공지능(AI) 제품·서비스 고도화와 함께 전 산업영역에서 SW영역 확대가 가시화될 것으로 보인다. 또한 블록체인이 웹3(Web3)와 연계해 탈중앙화·탈독점화 경제체계로 진화할 것으로 예상된다. @@ -556,17 +556,17 @@ These are cross-cutting input-only structural cohorts, not new primary classes a | Cluster | Candidates | Exact | Mismatch | Conflicting-reference cases | |---|---:|---:|---:|---:| | `allcaps_roman_run_containing_ou` | 1816 | 55 | 1761 | 0 | -| `decimal_point_between_ascii_digits` | 4546 | 2584 | 1962 | 0 | +| `decimal_point_between_ascii_digits` | 4546 | 3109 | 1437 | 0 | | `korean_inline_parenthesized_single_arithmetic_operator` | 23 | 17 | 6 | 0 | -| `korean_prefixed_closed_allcaps_parenthetical` | 54492 | 43779 | 10713 | 0 | -| `korean_prefixed_closed_roman_annotation_rule_34_order` | 64382 | 50479 | 13903 | 0 | +| `korean_prefixed_closed_allcaps_parenthetical` | 54492 | 43894 | 10598 | 0 | +| `korean_prefixed_closed_roman_annotation_rule_34_order` | 64382 | 50945 | 13437 | 0 | | `mixed_roman_korean_word_before_uppercase_headword_expansion` | 10 | 0 | 10 | 0 | | `multi_character_allcaps_roman_runs_joined_by_middle_dot` | 97 | 0 | 97 | 0 | | `single_capital_followed_by_parenthesized_digits` | 1361 | 0 | 1361 | 0 | -| `standalone_multi_character_uppercase_roman_word` | 62411 | 48757 | 13654 | 0 | +| `standalone_multi_character_uppercase_roman_word` | 62411 | 48933 | 13478 | 0 | | `tight_triangle_mark_immediately_before_korean` | 377 | 0 | 377 | 0 | | `uppercase_roman_headword_closed_multiword_parenthetical` | 175 | 15 | 160 | 0 | -| `uppercase_roman_run_followed_by_hyphen_digits` | 571 | 178 | 393 | 0 | +| `uppercase_roman_run_followed_by_hyphen_digits` | 571 | 182 | 389 | 0 | ### `allcaps_roman_run_containing_ou` @@ -631,24 +631,24 @@ Representative `mismatch` samples: ### `decimal_point_between_ascii_digits` -Of the 4546 candidates, 1905 are the actual `pending_rule_review` subcluster. The other 2641 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 4546 candidates, 1372 are the actual `pending_rule_review` subcluster. The other 3174 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. -For this output-signature audit, 1962 mismatches were evaluable and 875 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. +For this output-signature audit, 1437 mismatches were evaluable and 294 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. Localized first-difference transitions: -- `U+2832 ⠲ -> U+2834 ⠴`: 647 -- `U+2814 ⠔ -> U+2824 ⠤`: 53 +- `U+2814 ⠔ -> U+2824 ⠤`: 55 - `U+2826 ⠦ -> U+2800 ⠀`: 35 +- `U+2820 ⠠ -> U+2832 ⠲`: 24 - `U+2800 ⠀ -> U+280A ⠊`: 16 -- `U+2826 ⠦ -> U+2810 ⠐`: 15 +- `U+2826 ⠦ -> U+2810 ⠐`: 16 Mismatch primary-class distribution: -- `comparison_method`: 16 -- `corpus_suspect`: 26 -- `pending_rule_review`: 1905 -- `unsupported_character_review`: 15 +- `comparison_method`: 25 +- `corpus_suspect`: 28 +- `pending_rule_review`: 1372 +- `unsupported_character_review`: 12 Representative `exact` samples: @@ -671,25 +671,25 @@ Representative `exact` samples: Representative `localized_mismatch` samples: -- `sentence_01.json` #75: 특히 MKT의 매출이익률이 42.2%에 달했는데, 이는 경쟁사 대비 12.6%포인트(P) 높은 수준이다. 검찰은 한국타이어가 MKT에 몰아준 이익이 한국타이어 총수 일가에 흘러간 것으로 의심하고 있다. - - expected: `⠊⠗⠘⠕⠀⠼⠁⠃⠲⠋⠴⠏⠀⠙⠥⠟⠓⠪⠦⠄⠴⠠⠏⠠` - - actual: `⠊⠗⠘⠕⠀⠼⠁⠃⠴⠲⠋⠴⠏⠀⠙⠥⠟⠓⠪⠦⠄⠴⠠⠏` - - first differing cell (zero-based): 67 +- `sentence_01.json` #414: 셀트리온은 지난 5일 유럽의약품청(EMA)에 유플라이마의 20㎎/0.2ml(이하 20㎎) 제형을 추가하는 품목 변경 허가 신청을 했다고 16일 밝혔다. + - expected: `⠺⠀⠼⠃⠚⠴⠍⠛⠸⠌⠼⠚⠲⠃⠍⠇⠦⠄⠕⠚⠀⠼⠃⠚` + - actual: `⠺⠀⠼⠃⠚⠴⠍⠛⠲⠸⠌⠼⠚⠲⠃⠴⠍⠲⠴⠇⠦⠄⠕⠚` + - first differing cell (zero-based): 60 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` - `sentence_02.json` #5: 현재의 경기상황을 보여주는 동행지수(1월 기준, 99.8→99.4)·앞으로의 흐름을 보여주는 선행지수(98.8→98.5)는 모두 기준(100)보다 낮은 수준에서 하락세를 지속하면서 경기 부진을 반영하는 모양새다. - expected: `⠛⠐⠀⠼⠊⠊⠲⠓⠀⠒⠕⠀⠼⠊⠊⠲⠙⠠⠴⠐⠆⠣⠲⠪` - actual: `⠛⠐⠀⠼⠊⠊⠲⠓⠒⠕⠼⠊⠊⠲⠙⠠⠴⠐⠆⠣⠲⠪⠐⠥` - first differing cell (zero-based): 52 - current primary/reason: `pending_rule_review` / `number_rule_review` -- `sentence_03.json` #366: 하루 최대 34.3리터(L)의 대용량 제습이 가능하며, 자연 증발 시스템이 적용돼 물통에 모인 물을 비워야 하는 번거로움이 없다. - - expected: `⠰⠽⠊⠗⠀⠼⠉⠙⠲⠉⠐⠕⠓⠎⠦⠄⠴⠠⠇⠠⠴⠺⠀⠊` - - actual: `⠰⠽⠊⠗⠀⠼⠉⠙⠴⠲⠉⠐⠕⠓⠎⠦⠄⠴⠠⠇⠠⠴⠺⠀` - - first differing cell (zero-based): 12 +- `sentence_03.json` #413: 이날 ‘올해 1분기(1~3월) 분기 실적’을 발표한 주요 기업은 사회연결망(SNS) 기업인 메타(META↑0.89%)와 글로벌 호텔 체인 힐튼 월드와이드 홀딩스(HLT↓3.41%), 세계 최대 항공기 제조업체 보잉(BA↑0.42%) 입니다. + - expected: `⠄⠴⠠⠠⠍⠑⠞⠁⠀⠰⠒⠕⠀⠼⠚⠲⠓⠊⠴⠏⠠⠴⠧⠀` + - actual: `⠄⠴⠠⠠⠍⠑⠞⠁⠲⠰⠒⠕⠼⠚⠲⠓⠊⠴⠏⠠⠴⠧⠀⠈` + - first differing cell (zero-based): 98 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_04.json` #48: 올트먼은 GPT-4가 기존 ‘GPT-3.5’보다 정확도가 40% 이상 높아졌지만, 이를 정보의 주요 출처로 사용해서는 안된다고도 지적했다. 거짓을 사실처럼 보이게 하는 이른바 ‘환각(hallucination) 현상’에 주의해야 한다는 입장이다. - - expected: `⠠⠠⠛⠏⠞⠤⠼⠉⠲⠑⠴⠄⠘⠥⠊⠀⠨⠻⠚⠧⠁⠊⠥⠫` - - actual: `⠠⠠⠛⠏⠞⠤⠼⠉⠴⠲⠑⠴⠄⠘⠥⠊⠀⠨⠻⠚⠧⠁⠊⠥` - - first differing cell (zero-based): 35 +- `sentence_04.json` #185: 과기정통부가 같은 날 발표한 ‘2022년 디지털정보격차 실태조사’ 결과에서는 지난해 저소득층·장애인·농어민·고령층 등 취약계층의 디지털정보화(온라인·모바일·PC 활용) 수준이 국민 평균(100) 대비 76.2 정도로 나타났다. 전년(75.4) 대비 소폭 올랐다. + - expected: `⠌⠊⠲⠀⠨⠾⠉⠡⠦⠄⠼⠛⠑⠲⠙⠠⠴⠀⠊⠗⠘⠕⠀⠠` + - actual: `⠌⠊⠲⠀⠨⠾⠉⠡⠀⠀⠦⠼⠛⠑⠲⠙⠴⠀⠊⠗⠘⠕⠀⠠` + - first differing cell (zero-based): 228 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` Representative `mismatch` samples: @@ -709,10 +709,10 @@ Representative `mismatch` samples: - actual: `⠚⠽⠦⠄⠴⠠⠠⠝⠜⠠⠴⠉⠵⠀⠨⠕⠉⠒⠀⠼⠉⠏⠂⠀` - first differing cell (zero-based): 46 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_04.json` #48: 올트먼은 GPT-4가 기존 ‘GPT-3.5’보다 정확도가 40% 이상 높아졌지만, 이를 정보의 주요 출처로 사용해서는 안된다고도 지적했다. 거짓을 사실처럼 보이게 하는 이른바 ‘환각(hallucination) 현상’에 주의해야 한다는 입장이다. - - expected: `⠠⠠⠛⠏⠞⠤⠼⠉⠲⠑⠴⠄⠘⠥⠊⠀⠨⠻⠚⠧⠁⠊⠥⠫` - - actual: `⠠⠠⠛⠏⠞⠤⠼⠉⠴⠲⠑⠴⠄⠘⠥⠊⠀⠨⠻⠚⠧⠁⠊⠥` - - first differing cell (zero-based): 35 +- `sentence_04.json` #49: 유럽중앙은행(ECB)이 크레디트스위스(CS)의 유동성 위기에도 ‘빅스텝’(기준금리를 한 번에 0.5%포인트 인상)을 단행하자, 미국 연방준비제도(Fed·연준) 역시 금리 인상을 멈추지 않을 것이라는 전망이 힘을 얻고 있다. + - expected: `⠊⠥⠦⠄⠴⠠⠋⠫⠐⠆⠡⠨⠛⠠⠴⠀⠱⠁⠠⠕⠀⠈⠪⠢` + - actual: `⠊⠥⠦⠄⠴⠠⠋⠫⠲⠐⠆⠡⠨⠛⠠⠴⠀⠱⠁⠠⠕⠀⠈⠪` + - first differing cell (zero-based): 149 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` ### `korean_inline_parenthesized_single_arithmetic_operator` @@ -763,14 +763,14 @@ Representative `mismatch` samples: ### `korean_prefixed_closed_allcaps_parenthetical` -Of the 54492 candidates, 9409 are the actual `pending_rule_review` subcluster. The other 45083 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 54492 candidates, 9290 are the actual `pending_rule_review` subcluster. The other 45202 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. Mismatch primary-class distribution: -- `comparison_method`: 185 -- `corpus_suspect`: 979 -- `pending_rule_review`: 9409 -- `unsupported_character_review`: 140 +- `comparison_method`: 191 +- `corpus_suspect`: 980 +- `pending_rule_review`: 9290 +- `unsupported_character_review`: 137 Representative `exact` samples: @@ -816,21 +816,21 @@ Representative `mismatch` samples: ### `korean_prefixed_closed_roman_annotation_rule_34_order` -Of the 64382 candidates, 12405 are the actual `pending_rule_review` subcluster. The other 51977 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 64382 candidates, 11933 are the actual `pending_rule_review` subcluster. The other 52449 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. -For this output-signature audit, 13903 mismatches were evaluable and 1095 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. +For this output-signature audit, 13437 mismatches were evaluable and 1097 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. Localized first-difference transitions: -- `U+2834 ⠴ -> U+2826 ⠦`: 1094 +- `U+2834 ⠴ -> U+2826 ⠦`: 1096 - `U+2810 ⠐ -> U+2826 ⠦`: 1 Mismatch primary-class distribution: -- `comparison_method`: 239 -- `corpus_suspect`: 1091 -- `pending_rule_review`: 12405 -- `unsupported_character_review`: 168 +- `comparison_method`: 246 +- `corpus_suspect`: 1093 +- `pending_rule_review`: 11933 +- `unsupported_character_review`: 165 Representative `exact` samples: @@ -960,11 +960,11 @@ Representative `mismatch` samples: Of the 1361 candidates, 1357 are the actual `pending_rule_review` subcluster. The other 4 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. -For this output-signature audit, 1361 mismatches were evaluable and 1341 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. +For this output-signature audit, 1361 mismatches were evaluable and 1342 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. Localized first-difference transitions: -- `U+2834 ⠴ -> U+2800 ⠀`: 1329 +- `U+2834 ⠴ -> U+2800 ⠀`: 1330 - `U+2834 ⠴ -> U+2801 ⠁`: 9 - `U+2834 ⠴ -> U+2803 ⠃`: 2 - `U+2834 ⠴ -> U+2813 ⠓`: 1 @@ -999,14 +999,14 @@ Representative `mismatch` samples: ### `standalone_multi_character_uppercase_roman_word` -Of the 62411 candidates, 12190 are the actual `pending_rule_review` subcluster. The other 50221 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 62411 candidates, 12009 are the actual `pending_rule_review` subcluster. The other 50402 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. Mismatch primary-class distribution: -- `comparison_method`: 223 -- `corpus_suspect`: 1078 -- `pending_rule_review`: 12190 -- `unsupported_character_review`: 163 +- `comparison_method`: 229 +- `corpus_suspect`: 1080 +- `pending_rule_review`: 12009 +- `unsupported_character_review`: 160 Representative `exact` samples: @@ -1143,9 +1143,9 @@ Representative `mismatch` samples: ### `uppercase_roman_run_followed_by_hyphen_digits` -Of the 571 candidates, 378 are the actual `pending_rule_review` subcluster. The other 193 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 571 candidates, 374 are the actual `pending_rule_review` subcluster. The other 197 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. -For this output-signature audit, 393 mismatches were evaluable and 259 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. +For this output-signature audit, 389 mismatches were evaluable and 259 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. Localized first-difference transitions: @@ -1155,7 +1155,7 @@ Localized first-difference transitions: Mismatch primary-class distribution: - `corpus_suspect`: 12 -- `pending_rule_review`: 378 +- `pending_rule_review`: 374 - `unsupported_character_review`: 3 Representative `exact` samples: @@ -1172,9 +1172,9 @@ Representative `exact` samples: - expected: `⠴⠠⠠⠇⠛⠲⠕⠉⠥⠓⠝⠁⠵⠀⠟⠈⠿⠨⠕⠉⠪⠶⠦⠄` - actual: `⠴⠠⠠⠇⠛⠲⠕⠉⠥⠓⠝⠁⠵⠀⠟⠈⠿⠨⠕⠉⠪⠶⠦⠄` - current primary/reason: `exact` / `exact` -- `sentence_04.json` #358: 세계 최대 이슬람 국가 인도네시아가 이스라엘 선수단을 거부해야 한다는 거센 국내 여론으로 논란을 빚다가 2023 국제축구연맹(FIFA) 20세 이하(U-20) 월드컵 개최국 자격을 박탈당했다. - - expected: `⠠⠝⠈⠌⠀⠰⠽⠊⠗⠀⠕⠠⠮⠐⠣⠢⠀⠈⠍⠁⠫⠀⠟⠊` - - actual: `⠠⠝⠈⠌⠀⠰⠽⠊⠗⠀⠕⠠⠮⠐⠣⠢⠀⠈⠍⠁⠫⠀⠟⠊` +- `sentence_04.json` #48: 올트먼은 GPT-4가 기존 ‘GPT-3.5’보다 정확도가 40% 이상 높아졌지만, 이를 정보의 주요 출처로 사용해서는 안된다고도 지적했다. 거짓을 사실처럼 보이게 하는 이른바 ‘환각(hallucination) 현상’에 주의해야 한다는 입장이다. + - expected: `⠥⠂⠓⠪⠑⠾⠵⠀⠴⠠⠠⠛⠏⠞⠤⠼⠙⠫⠀⠈⠕⠨⠷⠀` + - actual: `⠥⠂⠓⠪⠑⠾⠵⠀⠴⠠⠠⠛⠏⠞⠤⠼⠙⠫⠀⠈⠕⠨⠷⠀` - current primary/reason: `exact` / `exact` Representative `mismatch` samples: @@ -1194,10 +1194,10 @@ Representative `mismatch` samples: - actual: `⠦⠐⠎⠠⠕⠣⠫⠀⠀⠠⠞⠔⠼⠑⠑⠀⠀⠨⠾⠰⠣⠀⠕⠂` - first differing cell (zero-based): 63 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_04.json` #48: 올트먼은 GPT-4가 기존 ‘GPT-3.5’보다 정확도가 40% 이상 높아졌지만, 이를 정보의 주요 출처로 사용해서는 안된다고도 지적했다. 거짓을 사실처럼 보이게 하는 이른바 ‘환각(hallucination) 현상’에 주의해야 한다는 입장이다. - - expected: `⠠⠠⠛⠏⠞⠤⠼⠉⠲⠑⠴⠄⠘⠥⠊⠀⠨⠻⠚⠧⠁⠊⠥⠫` - - actual: `⠠⠠⠛⠏⠞⠤⠼⠉⠴⠲⠑⠴⠄⠘⠥⠊⠀⠨⠻⠚⠧⠁⠊⠥` - - first differing cell (zero-based): 35 +- `sentence_04.json` #323: 사단급 규모의 상륙군과 대형수송함(LPH) 독도함, 미 강습상륙함(LHD) 마킨 아일랜드함 등 30여척, 미 F-35 전투기·육군 아파치 공격헬기(AH-64)·마린온 상륙기동헬기 등 항공기 70여대, 상륙돌격장갑차(KAAV) 50여대 등 연합·합동전력이 투입됐다. + - expected: `⠱⠰⠹⠐⠀⠑⠕⠀⠴⠠⠋⠤⠼⠉⠑⠀⠨⠾⠓⠍⠈⠕⠐⠆` + - actual: `⠱⠰⠹⠐⠀⠑⠕⠀⠀⠠⠋⠔⠼⠉⠑⠀⠨⠾⠓⠍⠈⠕⠐⠆` + - first differing cell (zero-based): 108 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` This shape is not an engine implementation premise. The 2024 PDF's math rule 6 defines parentheses and grouping parentheses, rule 11 defines mathematical-expression spacing, rule 12 covers Roman letters in formulas as well as Korean sentences, and rule 45 shows Roman-letter function notation followed by parentheses. Excluding visible operators, scripts, and nesting narrows this corpus cohort, but the PDF does not make the remaining surface shape sufficient to rule out every mathematical counterexample. The cluster therefore remains conservative pending-review evidence only. @@ -1220,26 +1220,27 @@ The tight-triangle cohort is not an implementation premise. Hangeul rule 49 assi Corpus contradictions remain a separate gate: identical inputs with conflicting references are classified as `corpus_suspect` before these cohorts are recorded and would appear explicitly in each mismatch primary-class distribution. Their absence does not prove a reference correct; it only means that this deterministic contradiction test did not fire. -Current uppercase-Roman hyphen-digits measurement: 571 candidates, 178 exact controls, 393 mismatches, 378 members in the actual `pending_rule_review` subcluster, and 259/393 evaluable mismatches whose first difference is inside the target run plus its entry boundary. It remains distinct from parenthesized digits and headword expansions; no engine change is inferred. +Current uppercase-Roman hyphen-digits measurement: 571 candidates, 182 exact controls, 389 mismatches, 374 members in the actual `pending_rule_review` subcluster, and 259/389 evaluable mismatches whose first difference is inside the target run plus its entry boundary. It remains distinct from parenthesized digits and headword expansions; no engine change is inferred. -Current single-capital parenthesized-digits measurement: 1361 candidates, 0 exact controls, 1361 mismatches, 1357 members in the actual `pending_rule_review` subcluster, and 1341/1361 evaluable mismatches whose first difference is inside the target run plus its entry boundary. No engine change is inferred from the ambiguous prose/function surface form. +Current single-capital parenthesized-digits measurement: 1361 candidates, 0 exact controls, 1361 mismatches, 1357 members in the actual `pending_rule_review` subcluster, and 1342/1361 evaluable mismatches whose first difference is inside the target run plus its entry boundary. No engine change is inferred from the ambiguous prose/function surface form. Current mixed Roman+Korean boundary before uppercase headword-expansion measurement: 10 candidates, 0 exact controls, 10 mismatches, 10 members in the actual `pending_rule_review` subcluster, and 8/10 evaluable mismatches whose first difference is localized to the later headword's entry boundary/output. The detector cannot be satisfied by the earlier Roman entry. No HCA-shaped engine routing rule is introduced. -Current decimal-point measurement: 4546 candidates, 2584 exact controls, 1962 mismatches, 1905 members in the actual `pending_rule_review` subcluster, and 875/1962 evaluable mismatches whose first difference is inside the complete decimal-containing word's current-engine signature. Hangeul rules 43 and 48 keep an ASCII point between digits in the numeric sequence and encode it as the decimal point; rules 35 and 69 supply controls for adjacent Roman-number chains and Roman units. This is an implementation-candidate audit, not permission to specialize on a corpus reference. +Current decimal-point measurement: 4546 candidates, 3109 exact controls, 1437 mismatches, 1372 members in the actual `pending_rule_review` subcluster, and 294/1437 evaluable mismatches whose first difference is inside the complete decimal-containing word's current-engine signature. Hangeul rules 43 and 48 keep an ASCII point between digits in the numeric sequence and encode it as the decimal point; rules 35 and 69 supply controls for adjacent Roman-number chains and Roman units. This is an implementation-candidate audit, not permission to specialize on a corpus reference. +Analyzer checkpoint `ed20f99` recorded the pre-fix baseline as 4,546 candidates, 2,584 exact controls, 1,962 mismatches, 1,905 pending members, and 875 localized first differences. Its dominant localized transition was reference decimal point `U+2832 ⠲` versus current Roman indicator `U+2834 ⠴` in 647 cases. After the generalized rule-43/48 guard, that transition is absent: 525 cases become exact and the other corrected prefixes expose later independent mismatches. Current all-caps `OU` measurement: 1816 candidates, 55 exact controls, 1761 mismatches, 1676 members in the actual `pending_rule_review` subcluster, and 1307/1761 evaluable mismatches whose first difference is inside the current-engine output signature for the detected run. This is a pronunciation-sensitive UEB review cohort, not an engine routing rule. -Current standalone-uppercase measurement: 62411 candidates, 48757 exact controls, 13654 mismatches, and 12190 members in the actual `pending_rule_review` subcluster. Its high frequency does not make it causal: the same input shape is exact in many cases, and a sentence containing the shape may first differ at another Roman, numeric, or punctuation structure. No engine change is inferred from this cohort. +Current standalone-uppercase measurement: 62411 candidates, 48933 exact controls, 13478 mismatches, and 12009 members in the actual `pending_rule_review` subcluster. Its high frequency does not make it causal: the same input shape is exact in many cases, and a sentence containing the shape may first differ at another Roman, numeric, or punctuation structure. No engine change is inferred from this cohort. -Current Korean-prefixed all-caps parenthetical measurement: 54492 candidates, 43779 exact controls, 10713 mismatches, and 9409 members in the actual `pending_rule_review` subcluster. This is a semantic-collision audit, not an engine routing rule; no implementation change is inferred from its reference outputs. +Current Korean-prefixed all-caps parenthetical measurement: 54492 candidates, 43894 exact controls, 10598 mismatches, and 9290 members in the actual `pending_rule_review` subcluster. This is a semantic-collision audit, not an engine routing rule; no implementation change is inferred from its reference outputs. -Current rule-34 opening-order measurement: 64382 structural candidates, 50479 exact controls, 13903 mismatches, and 1095/13903 evaluable mismatches whose first difference is inside the current engine's Korean opening-parenthesis cells. Only the 1094 localized first-cell transitions have the reference/current order `⠴` versus `⠦`. After requiring the complete reference `⠴⠐⠣` versus current/PDF `⠦⠄⠴` three-cell signature and preserving higher-priority comparison classifications, 1091 are classified as `corpus_suspect`; mere coexistence with a Korean-prefixed Roman annotation does not change a primary class. +Current rule-34 opening-order measurement: 64382 structural candidates, 50945 exact controls, 13437 mismatches, and 1097/13437 evaluable mismatches whose first difference is inside the current engine's Korean opening-parenthesis cells. Only the 1096 localized first-cell transitions have the reference/current order `⠴` versus `⠦`. After requiring the complete reference `⠴⠐⠣` versus current/PDF `⠦⠄⠴` three-cell signature and preserving higher-priority comparison classifications, 1093 are classified as `corpus_suspect`; mere coexistence with a Korean-prefixed Roman annotation does not change a primary class. Current all-caps Roman middle-dot measurement: 97 candidates, 0 exact controls, 97 mismatches, and 96 members in the actual `pending_rule_review` subcluster. This cross-cutting cohort preserves every primary class and is not an engine routing rule. Current inline parenthesized-operator measurement: 23 candidates, 17 exact controls, 6 mismatches, 6 members in the actual `pending_rule_review` subcluster, and 2/6 evaluable mismatches whose first differing cell is inside the emitted structure. Primary classes are preserved. - At this implementation checkpoint, the strict cohort moved from 0 to 17 exact cases; the corpus-wide total moved from 65,491 to 65514 (+23 exact) because the same PDF-backed spacing rule also applies outside the stricter Korean-boundary audit gate. The complete standard suite remains 5,141/5,141. + At this implementation checkpoint, the strict cohort moved from 0 to 17 exact cases; the corpus-wide total moved from 65,491 to 66039 (+548 exact) because the same PDF-backed spacing rule also applies outside the stricter Korean-boundary audit gate. The complete standard suite remains 5,141/5,141. Current tight-triangle measurement: 377 candidates, 0 exact controls, 377 mismatches, 376 members in the actual `pending_rule_review` subcluster, and 13/377 evaluable mismatches whose first difference is inside the `△` plus first-Korean output range. No engine change is inferred. @@ -1250,10 +1251,10 @@ The audit starts from all raw encoding errors, then separates cases already reso | Encoding-error audit | Cases | |---|---:| | Raw encoding errors | 450 | -| Resolved by comparison method | 259 | +| Resolved by comparison method | 262 | | Excluded as corpus suspect | 0 | -| Unresolved encoding-error review cases | 191 | -| Explained by singleton unsupported character(s) | 191 | +| Unresolved encoding-error review cases | 188 | +| Explained by singleton unsupported character(s) | 188 | | Multiple singleton unsupported characters | 2 | | Unclassified without a singleton explanation | 0 | @@ -1264,12 +1265,12 @@ The audit starts from all raw encoding errors, then separates cases already reso | Error message | Cases | |---|---:| -| `Invalid symbol character` | 191 | +| `Invalid symbol character` | 188 | | Error family | Cases | |---|---:| | `enclosed_organization_mark` | 169 | -| `letterlike_unit_symbol` | 12 | +| `letterlike_unit_symbol` | 9 | | `punctuation_or_layout_symbol` | 12 | Families are diagnostics, not automatic normalization permissions. Rules 68/69 compatibility-unit support removed that error family from the current run; `enclosed_organization_mark` and layout symbols still have no confirmed rule. @@ -1278,7 +1279,7 @@ Families are diagnostics, not automatic normalization permissions. Rules 68/69 c |---|---:|---|---| | `U+02D1 ˑ` | 1 | `ˑ` | `punctuation_or_layout_symbol` | | `U+2025 ‥` | 1 | `..` | `punctuation_or_layout_symbol` | -| `U+2113 ℓ` | 12 | `l` | `letterlike_unit_symbol` | +| `U+2113 ℓ` | 9 | `l` | `letterlike_unit_symbol` | | `U+2502 │` | 1 | `│` | `punctuation_or_layout_symbol` | | `U+25B2 ▲` | 1 | `▲` | `punctuation_or_layout_symbol` | | `U+25B4 ▴` | 1 | `▴` | `punctuation_or_layout_symbol` | @@ -1293,19 +1294,19 @@ Families are diagnostics, not automatic normalization permissions. Rules 68/69 c | Shard | Exact | Total | Accuracy | |---|---:|---:|---:| -| `sentence_01.json` | 19567 | 25000 | 78.27% | -| `sentence_02.json` | 19070 | 25000 | 76.28% | -| `sentence_03.json` | 19917 | 25000 | 79.67% | -| `sentence_04.json` | 6960 | 8528 | 81.61% | +| `sentence_01.json` | 19768 | 25000 | 79.07% | +| `sentence_02.json` | 19315 | 25000 | 77.26% | +| `sentence_03.json` | 19962 | 25000 | 79.85% | +| `sentence_04.json` | 6994 | 8528 | 82.01% | ## Overlapping mismatch traits | Trait | Count | |---|---:| -| `contains_ascii_digits` | 14362 | -| `contains_ascii_letters` | 17824 | -| `contains_delimiter_or_quote` | 18014 | -| `input_not_nfkc` | 826 | +| `contains_ascii_digits` | 13837 | +| `contains_ascii_letters` | 17299 | +| `contains_delimiter_or_quote` | 17489 | +| `input_not_nfkc` | 819 | ## Samples @@ -1458,5 +1459,6 @@ Against the immediately preceding 63,399-exact run, exact matches increased by 2 | Rules 68/69 compatibility unit algorithm | 5,141/5,141 | 63,388/83,528 | 75.89% | 96 Unicode unit presentation forms use one decomposition/letter-run/attachment algorithm; encoding errors fell from 434 to 226 | | Rule 36 Unicode Roman-numeral presentation normalization | 5,141/5,141 | 63,399/83,528 | 75.90% | U+2160–U+217F use the corresponding Roman-letter spelling; 11 NFKC-equivalent observations became exact, 23 errors became encoded mismatches pending review, and 3 remain blocked by `㈜` | | Rules 34/54 Korean-prefixed closed Roman annotation routing | 5,141/5,141 | 65,491/83,528 | 78.41% | A fully closed Roman annotation after an all-Korean prefix stays on the prose encoder path, including attached comma/period and alphanumeric forms such as `O4O`; exact matches increased by 2,092 while the global math detector remained unchanged | +| Rules 43/48 decimal-point ownership | 5,141/5,141 | 66,039/83,528 | 79.06% | A period directly between ASCII digits remains on the numeric punctuation path even when its word or sentence also contains Roman text; 647 localized Roman-entry differences were removed and 525 cases became exact | Engine changes must add a row only after both the 5,141-case standard suite and this full analysis have been rerun. Suspect-reference clusters stay in this report; they are not engine targets without independent PDF evidence. diff --git a/libs/braillify/examples/nikl_corpus_analyze.rs b/libs/braillify/examples/nikl_corpus_analyze.rs index 1cc68015..3e9eb874 100644 --- a/libs/braillify/examples/nikl_corpus_analyze.rs +++ b/libs/braillify/examples/nikl_corpus_analyze.rs @@ -2383,6 +2383,14 @@ fn markdown(report: &AnalysisReport) -> String { stats.first_difference_in_output_signature, stats.output_signature_mismatches_evaluated )); + text.push_str( + "Analyzer checkpoint `ed20f99` recorded the pre-fix baseline as 4,546 candidates, \ + 2,584 exact controls, 1,962 mismatches, 1,905 pending members, and 875 localized \ + first differences. Its dominant localized transition was reference decimal point \ + `U+2832 ⠲` versus current Roman indicator `U+2834 ⠴` in 647 cases. After the \ + generalized rule-43/48 guard, that transition is absent: 525 cases become exact \ + and the other corrected prefixes expose later independent mismatches.\n", + ); } if let Some(stats) = report .pending_rule_review_clusters @@ -2785,6 +2793,9 @@ fn markdown(report: &AnalysisReport) -> String { text.push_str( "| Rules 34/54 Korean-prefixed closed Roman annotation routing | 5,141/5,141 | 65,491/83,528 | 78.41% | A fully closed Roman annotation after an all-Korean prefix stays on the prose encoder path, including attached comma/period and alphanumeric forms such as `O4O`; exact matches increased by 2,092 while the global math detector remained unchanged |\n", ); + text.push_str( + "| Rules 43/48 decimal-point ownership | 5,141/5,141 | 66,039/83,528 | 79.06% | A period directly between ASCII digits remains on the numeric punctuation path even when its word or sentence also contains Roman text; 647 localized Roman-entry differences were removed and 525 cases became exact |\n", + ); text.push_str( "\nEngine changes must add a row only after both the 5,141-case standard suite and \ this full analysis have been rerun. Suspect-reference clusters stay in this report; \ diff --git a/libs/braillify/src/rules/korean/rule_english_symbol.rs b/libs/braillify/src/rules/korean/rule_english_symbol.rs index 3b8133ae..ccee6105 100644 --- a/libs/braillify/src/rules/korean/rule_english_symbol.rs +++ b/libs/braillify/src/rules/korean/rule_english_symbol.rs @@ -45,6 +45,18 @@ impl BrailleRule for RuleEnglishSymbol { return Ok(RuleResult::Skip); }; + // 한글 점자 제43항·제48항: ASCII 숫자 사이의 마침표는 숫자 흐름의 + // 소수점이다. 같은 어절 뒤쪽에 로마자가 있다는 이유만으로 이 위치에서 + // 로마자 모드에 재진입하면 `⠴⠲`가 되어 수표 뒤의 올바른 `⠲` 앞에 + // 불필요한 로마자표가 붙는다. 이 기호는 아래의 일반 한글 문장부호 + // 규칙이 처리하도록 넘기고, 접미사 종류에는 관여하지 않는다. + if *sym == '.' + && ctx.prev_char().is_some_and(|ch| ch.is_ascii_digit()) + && ctx.next_char().is_some_and(|ch| ch.is_ascii_digit()) + { + return Ok(RuleResult::Continue); + } + let mut use_english_symbol = english_logic::should_render_symbol_as_english( ctx.state.english_indicator, ctx.state.is_english, @@ -166,4 +178,34 @@ mod tests { assert!(ctx.state.parenthesis_stack.is_empty()); } + + #[test] + fn decimal_point_between_digits_is_not_an_english_entry_symbol() { + let mut owned = crate::test_helpers::CtxOwned::for_text("3.5P", false); + let mut ctx = owned.ctx_at(1); + + let outcome = RuleEnglishSymbol.apply(&mut ctx).unwrap(); + + assert_eq!(outcome, RuleResult::Continue); + assert!(owned.result.is_empty()); + } + + #[rstest::rstest] + #[case::percent_with_later_roman("42.2%포인트(P)", "42.2")] + #[case::korean_unit_with_annotation("34.3리터(L)", "34.3")] + #[case::two_decimals_with_arrow("99.8→99.4", "99.8")] + #[case::roman_identifier("GPT-3.5", "3.5")] + fn decimal_subsequence_matches_the_standalone_rule_48_encoding( + #[case] input: &str, + #[case] decimal: &str, + ) { + let actual = crate::encode_to_unicode(input).expect("mixed decimal context must encode"); + let standalone = + crate::encode_to_unicode(decimal).expect("standalone rule-48 decimal must encode"); + + assert!( + actual.contains(&standalone), + "input={input}, decimal={decimal}, actual={actual}, standalone={standalone}" + ); + } } From 9ecec2c148cb19b9c9649944d19945bd7de18069 Mon Sep 17 00:00:00 2001 From: devfive Date: Tue, 1 Sep 2026 08:42:33 +0900 Subject: [PATCH 21/77] Honor Roman unit punctuation boundaries Apply Rules 33, 34, and 69 generically at Roman-unit punctuation and enclosing boundaries, while retaining ordinary terminators elsewhere. Keep compact supported units off the math path only at the documented boundary shape and record deterministic analyzer cohorts. Standard custom summary: total 5141, success 5141, failure 0, skip 0. NIKL: 66436/83528 exact (79.54%, +397). Analyzer: 130 tests; report/JSON hashes equal across two runs; clippy clean. --- docs/corpus-analysis/NIKL_2025_V1.md | 477 +++++++++++++----- .../braillify/examples/nikl_corpus_analyze.rs | 351 ++++++++++++- libs/braillify/src/rules/korean/rule_69.rs | 96 +++- .../token_rules/math_expression/detect.rs | 34 +- 4 files changed, 798 insertions(+), 160 deletions(-) diff --git a/docs/corpus-analysis/NIKL_2025_V1.md b/docs/corpus-analysis/NIKL_2025_V1.md index 663f75cb..c2a00b81 100644 --- a/docs/corpus-analysis/NIKL_2025_V1.md +++ b/docs/corpus-analysis/NIKL_2025_V1.md @@ -7,9 +7,9 @@ | Metric | Count | |---|---:| | Total | 83528 | -| Exact | 66039 | -| Mismatch | 17489 | -| Exact accuracy | 79.06% | +| Exact | 66436 | +| Mismatch | 17092 | +| Exact accuracy | 79.54% | | Duplicate records | 0 | | Inputs with conflicting references | 0 | @@ -19,21 +19,21 @@ Primary classes are evidence gates, not permissions to change the engine. `imple | Primary class | Count | |---|---:| -| `comparison_method` | 312 | +| `comparison_method` | 283 | | `corpus_suspect` | 1093 | -| `exact` | 66039 | -| `pending_rule_review` | 15896 | -| `unsupported_character_review` | 188 | +| `exact` | 66436 | +| `pending_rule_review` | 15531 | +| `unsupported_character_review` | 185 | | Reproducible reason | Count | |---|---:| -| `exact` | 66039 | -| `foreign_text_rule_review` | 15719 | -| `nfkc_input_equivalent` | 312 | -| `number_rule_review` | 169 | +| `exact` | 66436 | +| `foreign_text_rule_review` | 15356 | +| `nfkc_input_equivalent` | 283 | +| `number_rule_review` | 167 | | `punctuation_rule_review` | 8 | | `rule34_roman_indicator_before_opening_parenthesis` | 1093 | -| `unsupported_character_review` | 188 | +| `unsupported_character_review` | 185 | ## Pending first-difference cell transitions @@ -41,26 +41,26 @@ This ranking is a diagnostic selector, not an implementation rule. It counts onl | Rank | Expected → actual first cell | Cases | |---:|---|---:| -| 1 | `U+2834 ⠴ -> U+2800 ⠀` | 2138 | +| 1 | `U+2834 ⠴ -> U+2800 ⠀` | 2140 | | 2 | `U+2815 ⠕ -> U+2833 ⠳` | 1311 | | 3 | `U+2830 ⠰ -> U+2820 ⠠` | 1018 | -| 4 | `U+2820 ⠠ -> U+2830 ⠰` | 796 | -| 5 | `U+2810 ⠐ -> U+2832 ⠲` | 658 | -| 6 | `U+283C ⠼ -> U+2800 ⠀` | 480 | -| 7 | `U+2820 ⠠ -> U+2834 ⠴` | 464 | +| 4 | `U+2820 ⠠ -> U+2830 ⠰` | 799 | +| 5 | `U+2810 ⠐ -> U+2832 ⠲` | 642 | +| 6 | `U+2820 ⠠ -> U+2834 ⠴` | 466 | +| 7 | `U+283C ⠼ -> U+2800 ⠀` | 458 | | 8 | `U+2826 ⠦ -> U+2834 ⠴` | 451 | -| 9 | `U+280E ⠎ -> U+280C ⠌` | 443 | -| 10 | `U+2826 ⠦ -> U+2800 ⠀` | 422 | +| 9 | `U+280E ⠎ -> U+280C ⠌` | 444 | +| 10 | `U+2826 ⠦ -> U+2800 ⠀` | 423 | | 11 | `U+2808 ⠈ -> U+2832 ⠲` | 409 | | 12 | `U+2801 ⠁ -> U+281C ⠜` | 386 | -| 13 | `U+2820 ⠠ -> U+2832 ⠲` | 372 | -| 14 | `U+2800 ⠀ -> U+2807 ⠇` | 360 | -| 15 | `U+2834 ⠴ -> U+2830 ⠰` | 324 | -| 16 | `U+2811 ⠑ -> U+282B ⠫` | 320 | -| 17 | `U+2832 ⠲ -> U+2838 ⠸` | 275 | -| 18 | `U+2800 ⠀ -> U+2832 ⠲` | 252 | -| 19 | `U+2810 ⠐ -> U+2802 ⠂` | 206 | -| 20 | `U+2817 ⠗ -> U+2820 ⠠` | 161 | +| 13 | `U+2800 ⠀ -> U+2807 ⠇` | 360 | +| 14 | `U+2834 ⠴ -> U+2830 ⠰` | 325 | +| 15 | `U+2811 ⠑ -> U+282B ⠫` | 322 | +| 16 | `U+2832 ⠲ -> U+2838 ⠸` | 275 | +| 17 | `U+2800 ⠀ -> U+2832 ⠲` | 251 | +| 18 | `U+2810 ⠐ -> U+2802 ⠂` | 209 | +| 19 | `U+2817 ⠗ -> U+2820 ⠠` | 161 | +| 20 | `U+2826 ⠦ -> U+2810 ⠐` | 156 | ### `U+2834 ⠴ -> U+2800 ⠀` @@ -177,29 +177,6 @@ This ranking is a diagnostic selector, not an implementation rule. It counts onl - first differing cell (zero-based): 149 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -### `U+283C ⠼ -> U+2800 ⠀` - -- `sentence_01.json` #426: LG에너지솔루션은 혼다에 앞서 미국 제너럴모터스(GM)과의 배터리 합작법인 ‘얼티엄셀즈’를 설립해 투자를 진행 중이다. 이를 통해 북미 생산 능력을 올해 13GWh 규모에서 2025년 255GWh까지 확대할 예정이다. - - expected: `⠁⠮⠀⠥⠂⠚⠗⠀⠼⠁⠉⠴⠠⠛⠠⠺⠓⠲⠀⠈⠩⠑⠥⠝` - - actual: `⠁⠮⠀⠥⠂⠚⠗⠀⠀⠼⠁⠉⠠⠠⠛⠺⠓⠀⠀⠈⠩⠑⠥⠝` - - first differing cell (zero-based): 156 - - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_02.json` #68: 조성된 무장애나눔길은 통행이 많은 숲속의집~생태숲학습관 0.73km 구간에 평균 경사도 5% 이하 데크산책로와 황토포장길로 조성됐으며 전국 무장애나눔길 최초로 장애물 없는 생활환경(BF)인증을 추진하고 있다. - - expected: `⠁⠠⠪⠃⠈⠧⠒⠀⠼⠚⠲⠛⠉⠴⠅⠍⠲⠀⠈⠍⠫⠒⠝⠀` - - actual: `⠁⠠⠪⠃⠈⠧⠒⠀⠀⠼⠚⠲⠛⠉⠅⠍⠀⠀⠈⠍⠫⠒⠝⠀` - - first differing cell (zero-based): 62 - - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_03.json` #59: 이들 중 A씨(31) 등 2명은 지난 3월 25일 김해공항으로 필로폰 968g 등을 팬티 속에 숨겨 입국한 혐의를 받고 있다. - - expected: `⠙⠕⠂⠐⠥⠙⠷⠀⠼⠊⠋⠓⠴⠛⠲⠀⠊⠪⠶⠮⠀⠙⠗⠒` - - actual: `⠙⠕⠂⠐⠥⠙⠷⠀⠀⠼⠊⠋⠓⠛⠀⠀⠊⠪⠶⠮⠀⠙⠗⠒` - - first differing cell (zero-based): 71 - - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_04.json` #57: 미국지질조사국(USGS)에 따르면 이날 에콰도르 항구도시 과야킬에서 남쪽으로 약 80km 떨어진 지점에서 규모 6.8의 지진이 발생했다. 지진의 깊이는 66㎞다. - - expected: `⠭⠪⠐⠥⠀⠜⠁⠀⠼⠓⠚⠴⠅⠍⠲⠀⠠⠊⠞⠎⠨⠟⠀⠨` - - actual: `⠭⠪⠐⠥⠀⠜⠁⠀⠀⠼⠓⠚⠐⠅⠍⠀⠀⠠⠊⠞⠎⠨⠟⠀` - - first differing cell (zero-based): 79 - - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` - ### `U+2820 ⠠ -> U+2834 ⠴` - `sentence_01.json` #78: 2023년형 Neo QLED는 스마트싱스뿐 아니라 매터(Matter) 기기까지 지원하는 원칩 모듈을 탑재해 별도의 동글을 사용하지 않아도, 다양한 사물인터넷(IoT) 기기를 직접 TV에 연결해 사용할 수 있다. @@ -223,6 +200,29 @@ This ranking is a diagnostic selector, not an implementation rule. It counts onl - first differing cell (zero-based): 30 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +### `U+283C ⠼ -> U+2800 ⠀` + +- `sentence_01.json` #426: LG에너지솔루션은 혼다에 앞서 미국 제너럴모터스(GM)과의 배터리 합작법인 ‘얼티엄셀즈’를 설립해 투자를 진행 중이다. 이를 통해 북미 생산 능력을 올해 13GWh 규모에서 2025년 255GWh까지 확대할 예정이다. + - expected: `⠁⠮⠀⠥⠂⠚⠗⠀⠼⠁⠉⠴⠠⠛⠠⠺⠓⠲⠀⠈⠩⠑⠥⠝` + - actual: `⠁⠮⠀⠥⠂⠚⠗⠀⠀⠼⠁⠉⠠⠠⠛⠺⠓⠀⠀⠈⠩⠑⠥⠝` + - first differing cell (zero-based): 156 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_02.json` #68: 조성된 무장애나눔길은 통행이 많은 숲속의집~생태숲학습관 0.73km 구간에 평균 경사도 5% 이하 데크산책로와 황토포장길로 조성됐으며 전국 무장애나눔길 최초로 장애물 없는 생활환경(BF)인증을 추진하고 있다. + - expected: `⠁⠠⠪⠃⠈⠧⠒⠀⠼⠚⠲⠛⠉⠴⠅⠍⠲⠀⠈⠍⠫⠒⠝⠀` + - actual: `⠁⠠⠪⠃⠈⠧⠒⠀⠀⠼⠚⠲⠛⠉⠅⠍⠀⠀⠈⠍⠫⠒⠝⠀` + - first differing cell (zero-based): 62 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_03.json` #59: 이들 중 A씨(31) 등 2명은 지난 3월 25일 김해공항으로 필로폰 968g 등을 팬티 속에 숨겨 입국한 혐의를 받고 있다. + - expected: `⠙⠕⠂⠐⠥⠙⠷⠀⠼⠊⠋⠓⠴⠛⠲⠀⠊⠪⠶⠮⠀⠙⠗⠒` + - actual: `⠙⠕⠂⠐⠥⠙⠷⠀⠀⠼⠊⠋⠓⠛⠀⠀⠊⠪⠶⠮⠀⠙⠗⠒` + - first differing cell (zero-based): 71 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_04.json` #57: 미국지질조사국(USGS)에 따르면 이날 에콰도르 항구도시 과야킬에서 남쪽으로 약 80km 떨어진 지점에서 규모 6.8의 지진이 발생했다. 지진의 깊이는 66㎞다. + - expected: `⠭⠪⠐⠥⠀⠜⠁⠀⠼⠓⠚⠴⠅⠍⠲⠀⠠⠊⠞⠎⠨⠟⠀⠨` + - actual: `⠭⠪⠐⠥⠀⠜⠁⠀⠀⠼⠓⠚⠐⠅⠍⠀⠀⠠⠊⠞⠎⠨⠟⠀` + - first differing cell (zero-based): 79 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` + ### `U+2826 ⠦ -> U+2834 ⠴` - `sentence_01.json` #38: 올해 소프트웨어(SW) 업계는 국내 인공지능(AI) 제품·서비스 고도화와 함께 전 산업영역에서 SW영역 확대가 가시화될 것으로 보인다. 또한 블록체인이 웹3(Web3)와 연계해 탈중앙화·탈독점화 경제체계로 진화할 것으로 예상된다. @@ -298,26 +298,26 @@ This ranking removes only cases whose first difference is inside an existing out | Rank | Expected → actual first cell | Residual cases | |---:|---|---:| -| 1 | `U+2830 ⠰ -> U+2820 ⠠` | 1016 | -| 2 | `U+2820 ⠠ -> U+2830 ⠰` | 795 | -| 3 | `U+2810 ⠐ -> U+2832 ⠲` | 645 | -| 4 | `U+2834 ⠴ -> U+2800 ⠀` | 542 | -| 5 | `U+283C ⠼ -> U+2800 ⠀` | 466 | -| 6 | `U+2820 ⠠ -> U+2834 ⠴` | 453 | -| 7 | `U+2826 ⠦ -> U+2834 ⠴` | 445 | -| 8 | `U+280E ⠎ -> U+280C ⠌` | 443 | +| 1 | `U+2830 ⠰ -> U+2820 ⠠` | 1015 | +| 2 | `U+2820 ⠠ -> U+2830 ⠰` | 798 | +| 3 | `U+2810 ⠐ -> U+2832 ⠲` | 626 | +| 4 | `U+2834 ⠴ -> U+2800 ⠀` | 544 | +| 5 | `U+2820 ⠠ -> U+2834 ⠴` | 454 | +| 6 | `U+2826 ⠦ -> U+2834 ⠴` | 445 | +| 7 | `U+280E ⠎ -> U+280C ⠌` | 444 | +| 8 | `U+283C ⠼ -> U+2800 ⠀` | 444 | | 9 | `U+2808 ⠈ -> U+2832 ⠲` | 407 | -| 10 | `U+2826 ⠦ -> U+2800 ⠀` | 387 | +| 10 | `U+2826 ⠦ -> U+2800 ⠀` | 388 | | 11 | `U+2801 ⠁ -> U+281C ⠜` | 386 | -| 12 | `U+2820 ⠠ -> U+2832 ⠲` | 353 | -| 13 | `U+2800 ⠀ -> U+2807 ⠇` | 343 | -| 14 | `U+2834 ⠴ -> U+2830 ⠰` | 324 | -| 15 | `U+2811 ⠑ -> U+282B ⠫` | 320 | -| 16 | `U+2832 ⠲ -> U+2838 ⠸` | 275 | -| 17 | `U+2800 ⠀ -> U+2832 ⠲` | 250 | -| 18 | `U+2810 ⠐ -> U+2802 ⠂` | 206 | -| 19 | `U+2817 ⠗ -> U+2820 ⠠` | 161 | -| 20 | `U+2820 ⠠ -> U+2809 ⠉` | 152 | +| 12 | `U+2800 ⠀ -> U+2807 ⠇` | 343 | +| 13 | `U+2834 ⠴ -> U+2830 ⠰` | 325 | +| 14 | `U+2811 ⠑ -> U+282B ⠫` | 322 | +| 15 | `U+2832 ⠲ -> U+2838 ⠸` | 275 | +| 16 | `U+2800 ⠀ -> U+2832 ⠲` | 225 | +| 17 | `U+2810 ⠐ -> U+2802 ⠂` | 209 | +| 18 | `U+2817 ⠗ -> U+2820 ⠠` | 161 | +| 19 | `U+2820 ⠠ -> U+2809 ⠉` | 152 | +| 20 | `U+281B ⠛ -> U+2823 ⠣` | 144 | ### Residual `U+2830 ⠰ -> U+2820 ⠠` @@ -411,29 +411,6 @@ This ranking removes only cases whose first difference is inside an existing out - first differing cell (zero-based): 70 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -### Residual `U+283C ⠼ -> U+2800 ⠀` - -- `sentence_01.json` #426: LG에너지솔루션은 혼다에 앞서 미국 제너럴모터스(GM)과의 배터리 합작법인 ‘얼티엄셀즈’를 설립해 투자를 진행 중이다. 이를 통해 북미 생산 능력을 올해 13GWh 규모에서 2025년 255GWh까지 확대할 예정이다. - - expected: `⠁⠮⠀⠥⠂⠚⠗⠀⠼⠁⠉⠴⠠⠛⠠⠺⠓⠲⠀⠈⠩⠑⠥⠝` - - actual: `⠁⠮⠀⠥⠂⠚⠗⠀⠀⠼⠁⠉⠠⠠⠛⠺⠓⠀⠀⠈⠩⠑⠥⠝` - - first differing cell (zero-based): 156 - - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_02.json` #68: 조성된 무장애나눔길은 통행이 많은 숲속의집~생태숲학습관 0.73km 구간에 평균 경사도 5% 이하 데크산책로와 황토포장길로 조성됐으며 전국 무장애나눔길 최초로 장애물 없는 생활환경(BF)인증을 추진하고 있다. - - expected: `⠁⠠⠪⠃⠈⠧⠒⠀⠼⠚⠲⠛⠉⠴⠅⠍⠲⠀⠈⠍⠫⠒⠝⠀` - - actual: `⠁⠠⠪⠃⠈⠧⠒⠀⠀⠼⠚⠲⠛⠉⠅⠍⠀⠀⠈⠍⠫⠒⠝⠀` - - first differing cell (zero-based): 62 - - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_03.json` #59: 이들 중 A씨(31) 등 2명은 지난 3월 25일 김해공항으로 필로폰 968g 등을 팬티 속에 숨겨 입국한 혐의를 받고 있다. - - expected: `⠙⠕⠂⠐⠥⠙⠷⠀⠼⠊⠋⠓⠴⠛⠲⠀⠊⠪⠶⠮⠀⠙⠗⠒` - - actual: `⠙⠕⠂⠐⠥⠙⠷⠀⠀⠼⠊⠋⠓⠛⠀⠀⠊⠪⠶⠮⠀⠙⠗⠒` - - first differing cell (zero-based): 71 - - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_04.json` #57: 미국지질조사국(USGS)에 따르면 이날 에콰도르 항구도시 과야킬에서 남쪽으로 약 80km 떨어진 지점에서 규모 6.8의 지진이 발생했다. 지진의 깊이는 66㎞다. - - expected: `⠭⠪⠐⠥⠀⠜⠁⠀⠼⠓⠚⠴⠅⠍⠲⠀⠠⠊⠞⠎⠨⠟⠀⠨` - - actual: `⠭⠪⠐⠥⠀⠜⠁⠀⠀⠼⠓⠚⠐⠅⠍⠀⠀⠠⠊⠞⠎⠨⠟⠀` - - first differing cell (zero-based): 79 - - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` - ### Residual `U+2820 ⠠ -> U+2834 ⠴` - `sentence_01.json` #78: 2023년형 Neo QLED는 스마트싱스뿐 아니라 매터(Matter) 기기까지 지원하는 원칩 모듈을 탑재해 별도의 동글을 사용하지 않아도, 다양한 사물인터넷(IoT) 기기를 직접 TV에 연결해 사용할 수 있다. @@ -503,6 +480,29 @@ This ranking removes only cases whose first difference is inside an existing out - first differing cell (zero-based): 53 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +### Residual `U+283C ⠼ -> U+2800 ⠀` + +- `sentence_01.json` #426: LG에너지솔루션은 혼다에 앞서 미국 제너럴모터스(GM)과의 배터리 합작법인 ‘얼티엄셀즈’를 설립해 투자를 진행 중이다. 이를 통해 북미 생산 능력을 올해 13GWh 규모에서 2025년 255GWh까지 확대할 예정이다. + - expected: `⠁⠮⠀⠥⠂⠚⠗⠀⠼⠁⠉⠴⠠⠛⠠⠺⠓⠲⠀⠈⠩⠑⠥⠝` + - actual: `⠁⠮⠀⠥⠂⠚⠗⠀⠀⠼⠁⠉⠠⠠⠛⠺⠓⠀⠀⠈⠩⠑⠥⠝` + - first differing cell (zero-based): 156 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_02.json` #68: 조성된 무장애나눔길은 통행이 많은 숲속의집~생태숲학습관 0.73km 구간에 평균 경사도 5% 이하 데크산책로와 황토포장길로 조성됐으며 전국 무장애나눔길 최초로 장애물 없는 생활환경(BF)인증을 추진하고 있다. + - expected: `⠁⠠⠪⠃⠈⠧⠒⠀⠼⠚⠲⠛⠉⠴⠅⠍⠲⠀⠈⠍⠫⠒⠝⠀` + - actual: `⠁⠠⠪⠃⠈⠧⠒⠀⠀⠼⠚⠲⠛⠉⠅⠍⠀⠀⠈⠍⠫⠒⠝⠀` + - first differing cell (zero-based): 62 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_03.json` #59: 이들 중 A씨(31) 등 2명은 지난 3월 25일 김해공항으로 필로폰 968g 등을 팬티 속에 숨겨 입국한 혐의를 받고 있다. + - expected: `⠙⠕⠂⠐⠥⠙⠷⠀⠼⠊⠋⠓⠴⠛⠲⠀⠊⠪⠶⠮⠀⠙⠗⠒` + - actual: `⠙⠕⠂⠐⠥⠙⠷⠀⠀⠼⠊⠋⠓⠛⠀⠀⠊⠪⠶⠮⠀⠙⠗⠒` + - first differing cell (zero-based): 71 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_04.json` #57: 미국지질조사국(USGS)에 따르면 이날 에콰도르 항구도시 과야킬에서 남쪽으로 약 80km 떨어진 지점에서 규모 6.8의 지진이 발생했다. 지진의 깊이는 66㎞다. + - expected: `⠭⠪⠐⠥⠀⠜⠁⠀⠼⠓⠚⠴⠅⠍⠲⠀⠠⠊⠞⠎⠨⠟⠀⠨` + - actual: `⠭⠪⠐⠥⠀⠜⠁⠀⠀⠼⠓⠚⠐⠅⠍⠀⠀⠠⠊⠞⠎⠨⠟⠀` + - first differing cell (zero-based): 79 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` + ### Residual `U+2808 ⠈ -> U+2832 ⠲` - `sentence_01.json` #207: 우리금융지주가 약 7조원의 자본 여력을 등에 업고 인수·합병(M&A) 시장에 출격했다. 벤처캐피탈(VC) 인수를 시작으로 증권사와 보험사까지 인수한다는 계획이다. @@ -551,22 +551,24 @@ This ranking removes only cases whose first difference is inside an existing out ## Cross-cutting input-only structural cohorts -These are cross-cutting input-only structural cohorts, not new primary classes and not engine routing rules. Candidate selection never changes a case's existing primary class by itself. Only cohort members already classified as `pending_rule_review` form a pending subcluster; exact and other-primary members are controls that retain their existing outcomes. A separate classifier may use independently justified, output-localized PDF evidence, as in the rule-34 three-cell contradiction below. The `uppercase_roman_headword_closed_multiword_parenthetical` gate requires a two-or-more character uppercase ASCII headword immediately followed by a closed parenthesis whose contents are two or more ASCII Roman words separated only by spaces. Because the contents admit only letters and spaces, visible operators, subscript/superscript notation, and nested parentheses are excluded deterministically. The `mixed_roman_korean_word_before_uppercase_headword_expansion` gate further requires a whitespace-delimited preceding word that contains both ASCII Roman and Korean and ends in Korean. It locates the following uppercase headword itself, including its immediately preceding emitted cell, so an earlier Roman entry in the same sentence cannot satisfy the output audit. The `single_capital_followed_by_parenthesized_digits` gate requires a standalone capital, a non-empty closed ASCII-digit parenthetical, and alphanumeric outer boundaries. It likewise includes the emitted entry-boundary cell in localization. The `uppercase_roman_run_followed_by_hyphen_digits` gate requires a maximal uppercase ASCII run, literal ASCII hyphen-minus, a non-empty digit run, and alphanumeric outer boundaries. Its localized range includes the current encoded run and its immediately preceding cell, keeping `F-35`-style routing separate from both parenthesized digits and headword expansions. The `standalone_multi_character_uppercase_roman_word` gate finds maximal ASCII-letter runs of two or more capitals with non-alphanumeric boundaries; a run immediately followed by `(` is excluded so the HCA-style headword itself is not counted by both gates. The `korean_prefixed_closed_allcaps_parenthetical` gate requires an immediately preceding Korean character and a closed body of two or more uppercase ASCII letters. It intentionally contains both acronym annotations (`책임자(COO)`) and scientific formulae (`일산화탄소(CO)`) so their semantic collision remains measurable. The `korean_prefixed_closed_roman_annotation_rule_34_order` gate accepts the narrower rule-34 body grammar after an immediately preceding Korean character and localizes only the current engine's Korean opening-parenthesis cells. This distinguishes the PDF's parenthesis-before-Roman-indicator order from unrelated differences later in the same sentence. The `multi_character_allcaps_roman_runs_joined_by_middle_dot` gate requires two maximal ASCII-letter runs of at least two capitals joined directly by U+00B7, with non-alphanumeric outer boundaries. It records shapes such as `AI·SW` without assigning prose, mathematics, or science semantics. The `korean_inline_parenthesized_single_arithmetic_operator` gate requires an immediate `Korean(` + one rule-45 arithmetic operator + `)Korean` span. Unlike broad coexistence traits, it also locates the current engine's emitted structure and counts a mismatch as signature-local only when the sentence's first differing cell falls inside that output range. The `allcaps_roman_run_containing_ou` gate finds maximal, alphanumeric-delimited uppercase ASCII runs containing adjacent `OU`. It locates the independently encoded run signature in the complete current output and counts only first differences inside that signature as localized. The `decimal_point_between_ascii_digits` gate finds whitespace-delimited words containing `digit.digit` and reproduces each whole word in a neutral Korean context, so suffixes and punctuation remain part of the current-engine signature. The `tight_triangle_mark_immediately_before_korean` gate requires literal `△한글` with no input space and includes the first following Korean cell in its localized output range, so an observed missing-space difference is measured at the mark boundary. +These are cross-cutting input-only structural cohorts, not new primary classes and not engine routing rules. Candidate selection never changes a case's existing primary class by itself. Only cohort members already classified as `pending_rule_review` form a pending subcluster; exact and other-primary members are controls that retain their existing outcomes. A separate classifier may use independently justified, output-localized PDF evidence, as in the rule-34 three-cell contradiction below. The `uppercase_roman_headword_closed_multiword_parenthetical` gate requires a two-or-more character uppercase ASCII headword immediately followed by a closed parenthesis whose contents are two or more ASCII Roman words separated only by spaces. Because the contents admit only letters and spaces, visible operators, subscript/superscript notation, and nested parentheses are excluded deterministically. The `mixed_roman_korean_word_before_uppercase_headword_expansion` gate further requires a whitespace-delimited preceding word that contains both ASCII Roman and Korean and ends in Korean. It locates the following uppercase headword itself, including its immediately preceding emitted cell, so an earlier Roman entry in the same sentence cannot satisfy the output audit. The `single_capital_followed_by_parenthesized_digits` gate requires a standalone capital, a non-empty closed ASCII-digit parenthetical, and alphanumeric outer boundaries. It likewise includes the emitted entry-boundary cell in localization. The `uppercase_roman_run_followed_by_hyphen_digits` gate requires a maximal uppercase ASCII run, literal ASCII hyphen-minus, a non-empty digit run, and alphanumeric outer boundaries. Its localized range includes the current encoded run and its immediately preceding cell, keeping `F-35`-style routing separate from both parenthesized digits and headword expansions. The `standalone_multi_character_uppercase_roman_word` gate finds maximal ASCII-letter runs of two or more capitals with non-alphanumeric boundaries; a run immediately followed by `(` is excluded so the HCA-style headword itself is not counted by both gates. The `korean_prefixed_closed_allcaps_parenthetical` gate requires an immediately preceding Korean character and a closed body of two or more uppercase ASCII letters. It intentionally contains both acronym annotations (`책임자(COO)`) and scientific formulae (`일산화탄소(CO)`) so their semantic collision remains measurable. The `korean_prefixed_closed_roman_annotation_rule_34_order` gate accepts the narrower rule-34 body grammar after an immediately preceding Korean character and localizes only the current engine's Korean opening-parenthesis cells. This distinguishes the PDF's parenthesis-before-Roman-indicator order from unrelated differences later in the same sentence. The `multi_character_allcaps_roman_runs_joined_by_middle_dot` gate requires two maximal ASCII-letter runs of at least two capitals joined directly by U+00B7, with non-alphanumeric outer boundaries. It records shapes such as `AI·SW` without assigning prose, mathematics, or science semantics. The `korean_inline_parenthesized_single_arithmetic_operator` gate requires an immediate `Korean(` + one rule-45 arithmetic operator + `)Korean` span. Unlike broad coexistence traits, it also locates the current engine's emitted structure and counts a mismatch as signature-local only when the sentence's first differing cell falls inside that output range. The `allcaps_roman_run_containing_ou` gate finds maximal, alphanumeric-delimited uppercase ASCII runs containing adjacent `OU`. It locates the independently encoded run signature in the complete current output and counts only first differences inside that signature as localized. The `decimal_point_between_ascii_digits` gate finds whitespace-delimited words containing `digit.digit` and reproduces each whole word in a neutral Korean context, so suffixes and punctuation remain part of the current-engine signature. The `compact_numeric_ascii_letter_suffix` gate finds a numeric prefix immediately followed by ASCII letters and retains suffix-specific outcome counts. It intentionally includes both possible rule-69 units and ambiguous variable/identifier forms; membership alone does not assign unit semantics. The `rule69_ascii_unit_before_terminator_skipping_symbol` gate is narrower: it accepts only ASCII unit spellings already supported by rule 69, includes the immediately following rule-33/34 punctuation cell in the localized signature, and does not infer new units. The `tight_triangle_mark_immediately_before_korean` gate requires literal `△한글` with no input space and includes the first following Korean cell in its localized output range, so an observed missing-space difference is measured at the mark boundary. | Cluster | Candidates | Exact | Mismatch | Conflicting-reference cases | |---|---:|---:|---:|---:| | `allcaps_roman_run_containing_ou` | 1816 | 55 | 1761 | 0 | -| `decimal_point_between_ascii_digits` | 4546 | 3109 | 1437 | 0 | +| `compact_numeric_ascii_letter_suffix` | 2975 | 1649 | 1326 | 0 | +| `decimal_point_between_ascii_digits` | 4546 | 3187 | 1359 | 0 | | `korean_inline_parenthesized_single_arithmetic_operator` | 23 | 17 | 6 | 0 | -| `korean_prefixed_closed_allcaps_parenthetical` | 54492 | 43894 | 10598 | 0 | -| `korean_prefixed_closed_roman_annotation_rule_34_order` | 64382 | 50945 | 13437 | 0 | +| `korean_prefixed_closed_allcaps_parenthetical` | 54492 | 43953 | 10539 | 0 | +| `korean_prefixed_closed_roman_annotation_rule_34_order` | 64382 | 51305 | 13077 | 0 | | `mixed_roman_korean_word_before_uppercase_headword_expansion` | 10 | 0 | 10 | 0 | | `multi_character_allcaps_roman_runs_joined_by_middle_dot` | 97 | 0 | 97 | 0 | +| `rule69_ascii_unit_before_terminator_skipping_symbol` | 440 | 325 | 115 | 0 | | `single_capital_followed_by_parenthesized_digits` | 1361 | 0 | 1361 | 0 | -| `standalone_multi_character_uppercase_roman_word` | 62411 | 48933 | 13478 | 0 | +| `standalone_multi_character_uppercase_roman_word` | 62411 | 49035 | 13376 | 0 | | `tight_triangle_mark_immediately_before_korean` | 377 | 0 | 377 | 0 | | `uppercase_roman_headword_closed_multiword_parenthetical` | 175 | 15 | 160 | 0 | -| `uppercase_roman_run_followed_by_hyphen_digits` | 571 | 182 | 389 | 0 | +| `uppercase_roman_run_followed_by_hyphen_digits` | 571 | 183 | 388 | 0 | ### `allcaps_roman_run_containing_ou` @@ -629,25 +631,111 @@ Representative `mismatch` samples: - first differing cell (zero-based): 0 - current primary/reason: `unsupported_character_review` / `unsupported_character_review` +### `compact_numeric_ascii_letter_suffix` + +Of the 2975 candidates, 1259 are the actual `pending_rule_review` subcluster. The other 1716 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. + +For this output-signature audit, 1326 mismatches were evaluable and 60 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. + +Localized first-difference transitions: + +- `U+2800 ⠀ -> U+2832 ⠲`: 24 +- `U+2810 ⠐ -> U+2832 ⠲`: 10 +- `U+281E ⠞ -> U+2810 ⠐`: 7 +- `U+2805 ⠅ -> U+2834 ⠴`: 3 +- `U+2838 ⠸ -> U+2832 ⠲`: 3 + +Mismatch primary-class distribution: + +- `comparison_method`: 11 +- `corpus_suspect`: 48 +- `pending_rule_review`: 1259 +- `unsupported_character_review`: 8 + +Representative `exact` samples: + +- `sentence_01.json` #343: 확정안을 보면 정부는 2036년 최대전력 수요(목표 수요)를 118.0GW(기가와트)로 예측했다. 1GW는 보통 원전 1기의 발전량으로 보고 있다. + - expected: `⠚⠧⠁⠨⠻⠣⠒⠮⠀⠘⠥⠑⠡⠀⠨⠻⠘⠍⠉⠵⠀⠼⠃⠚` + - actual: `⠚⠧⠁⠨⠻⠣⠒⠮⠀⠘⠥⠑⠡⠀⠨⠻⠘⠍⠉⠵⠀⠼⠃⠚` + - current primary/reason: `exact` / `exact` +- `sentence_02.json` #29: 어획량이 감소하면서 지난해 12월 기준 1상자(20kg)당 위판가가 24만 원까지 치솟으면서 자원 증강 필요성이 끊임없이 제기돼 왔다. + - expected: `⠎⠚⠽⠁⠐⠜⠶⠕⠀⠫⠢⠠⠥⠚⠑⠡⠠⠎⠀⠨⠕⠉⠒⠚` + - actual: `⠎⠚⠽⠁⠐⠜⠶⠕⠀⠫⠢⠠⠥⠚⠑⠡⠠⠎⠀⠨⠕⠉⠒⠚` + - current primary/reason: `exact` / `exact` +- `sentence_03.json` #60: B씨(30) 등 2명은 지난 3월 27일 김해공항으로 필로폰 692g을 속옷 등에 은닉해 밀반입한 혐의를 받는다. + - expected: `⠴⠠⠃⠲⠠⠠⠕⠦⠄⠼⠉⠚⠠⠴⠀⠊⠪⠶⠀⠼⠃⠀⠑⠻` + - actual: `⠴⠠⠃⠲⠠⠠⠕⠦⠄⠼⠉⠚⠠⠴⠀⠊⠪⠶⠀⠼⠃⠀⠑⠻` + - current primary/reason: `exact` / `exact` +- `sentence_04.json` #78: 차체 중량은 55㎏인데, 체중 100㎏의 사람을 태울 수 있다. 배터리 출력은 1000W(와트)로, 도심 길거리에서 쓰는 킥보드와 비슷한 출력이다. + - expected: `⠰⠣⠰⠝⠀⠨⠍⠶⠐⠜⠶⠵⠀⠼⠑⠑⠴⠅⠛⠲⠟⠊⠝⠐` + - actual: `⠰⠣⠰⠝⠀⠨⠍⠶⠐⠜⠶⠵⠀⠼⠑⠑⠴⠅⠛⠲⠟⠊⠝⠐` + - current primary/reason: `exact` / `exact` + +Representative `localized_mismatch` samples: + +- `sentence_01.json` #1720: LG헬로비전 헬로모바일이 월 5만원대의 31GB 5세대 이동통신(5G) 알뜰폰 요금제를 선보인다. 알뜰폰업체가 데이터 30GB 구간의 알뜰 요금제를 선보이는 건 이번이 처음이다. + - expected: `⠼⠉⠁⠴⠠⠠⠛⠃⠀⠼⠑⠠⠝⠊⠗⠀⠕⠊⠿⠓⠿⠠⠟⠦` + - actual: `⠼⠉⠁⠴⠠⠠⠛⠃⠲⠀⠼⠑⠠⠝⠊⠗⠀⠕⠊⠿⠓⠿⠠⠟` + - first differing cell (zero-based): 51 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_02.json` #431: 173cm, 68kg의 다소 왜소한 체격이지만 스크럼 하프(SH) 포지션을 맡아 포워드와 백스 사이에서 날카로운 볼 연결과 공격방향을 결정하는 타고난 판단력이 강점이다. + - expected: `⠴⠉⠍⠂⠀⠼⠋⠓⠅⠛⠲⠺⠀⠊⠠⠥⠀⠧⠗⠠⠥⠚⠒⠀` + - actual: `⠴⠉⠍⠂⠀⠼⠋⠓⠴⠅⠛⠲⠺⠀⠊⠠⠥⠀⠧⠗⠠⠥⠚⠒` + - first differing cell (zero-based): 12 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_03.json` #4252: 파워트레인은 최고 출력 197마력, 최대 토크 30.6kg·m의 가솔린 기반 마일드 하이브리드(MHEV)와 상시 사륜구동(AWD) 시스템 조합으로 구성된다. + - expected: `⠼⠉⠚⠲⠋⠴⠅⠛⠐⠆⠴⠍⠲⠺⠀⠫⠠⠥⠂⠐⠟⠀⠈⠕` + - actual: `⠼⠉⠚⠲⠋⠴⠅⠛⠲⠐⠆⠴⠍⠲⠺⠀⠫⠠⠥⠂⠐⠟⠀⠈` + - first differing cell (zero-based): 50 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_04.json` #2020: 실제 생활 주변 방사선 안전관리 실태조사 결과보고서를 보면 2017~2021년 5년간 항공 승무원의 최대 피폭선량은 평균 5.42밀리시버트(mSv)로 일반인 선량한도인 1m㏜보다 5배 이상 높다. + - expected: `⠊⠥⠟⠀⠼⠁⠴⠍⠠⠎⠧⠲⠘⠥⠊⠀⠼⠑⠘⠗⠀⠕⠇⠶` + - actual: `⠊⠥⠟⠀⠼⠁⠴⠍⠲⠴⠠⠎⠧⠲⠘⠥⠊⠀⠼⠑⠘⠗⠀⠕` + - first differing cell (zero-based): 171 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` + +Representative `mismatch` samples: + +- `sentence_01.json` #89: “인공지능(AI), 6G 등 핵심 기술을 위한 투자도 늘리는 동시에 전기차 충전, 디지털 헬스, 웹OS 기반의 콘텐츠 서비스 등 많은 영역으로 사업 포트폴리오를 확장하고 있습니다.” + - expected: `⠟⠈⠿⠨⠕⠉⠪⠶⠴⠐⠣⠠⠠⠁⠊⠐⠜⠂⠀⠼⠋⠠⠛⠲` + - actual: `⠟⠈⠿⠨⠕⠉⠪⠶⠦⠄⠴⠠⠠⠁⠊⠠⠴⠐⠀⠼⠋⠰⠠⠛` + - first differing cell (zero-based): 9 + - current primary/reason: `corpus_suspect` / `rule34_roman_indicator_before_opening_parenthesis` +- `sentence_02.json` #61: 사업 대상은 주차단위구획 50개 이상의 충전기 의무설치 대상 시설인 공동주택과 공중이용시설로 완속충전기(7 ~ 11kw 미만) 약 83기, 콘센트형(3kw) 약 285기를 지원한다. + - expected: `⠨⠾⠈⠕⠦⠄⠼⠛⠈⠔⠼⠁⠁⠴⠅⠺⠲⠀⠑⠕⠑⠒⠠⠴` + - actual: `⠨⠾⠈⠕⠦⠄⠼⠛⠀⠈⠔⠀⠼⠁⠁⠐⠅⠺⠀⠑⠕⠑⠒⠠` + - first differing cell (zero-based): 104 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_03.json` #59: 이들 중 A씨(31) 등 2명은 지난 3월 25일 김해공항으로 필로폰 968g 등을 팬티 속에 숨겨 입국한 혐의를 받고 있다. + - expected: `⠙⠕⠂⠐⠥⠙⠷⠀⠼⠊⠋⠓⠴⠛⠲⠀⠊⠪⠶⠮⠀⠙⠗⠒` + - actual: `⠙⠕⠂⠐⠥⠙⠷⠀⠀⠼⠊⠋⠓⠛⠀⠀⠊⠪⠶⠮⠀⠙⠗⠒` + - first differing cell (zero-based): 71 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_04.json` #57: 미국지질조사국(USGS)에 따르면 이날 에콰도르 항구도시 과야킬에서 남쪽으로 약 80km 떨어진 지점에서 규모 6.8의 지진이 발생했다. 지진의 깊이는 66㎞다. + - expected: `⠭⠪⠐⠥⠀⠜⠁⠀⠼⠓⠚⠴⠅⠍⠲⠀⠠⠊⠞⠎⠨⠟⠀⠨` + - actual: `⠭⠪⠐⠥⠀⠜⠁⠀⠀⠼⠓⠚⠐⠅⠍⠀⠀⠠⠊⠞⠎⠨⠟⠀` + - first differing cell (zero-based): 79 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` + ### `decimal_point_between_ascii_digits` -Of the 4546 candidates, 1372 are the actual `pending_rule_review` subcluster. The other 3174 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 4546 candidates, 1304 are the actual `pending_rule_review` subcluster. The other 3242 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. -For this output-signature audit, 1437 mismatches were evaluable and 294 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. +For this output-signature audit, 1359 mismatches were evaluable and 265 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. Localized first-difference transitions: - `U+2814 ⠔ -> U+2824 ⠤`: 55 - `U+2826 ⠦ -> U+2800 ⠀`: 35 -- `U+2820 ⠠ -> U+2832 ⠲`: 24 - `U+2800 ⠀ -> U+280A ⠊`: 16 - `U+2826 ⠦ -> U+2810 ⠐`: 16 +- `U+283C ⠼ -> U+2800 ⠀`: 14 Mismatch primary-class distribution: -- `comparison_method`: 25 +- `comparison_method`: 15 - `corpus_suspect`: 28 -- `pending_rule_review`: 1372 +- `pending_rule_review`: 1304 - `unsupported_character_review`: 12 Representative `exact` samples: @@ -763,13 +851,13 @@ Representative `mismatch` samples: ### `korean_prefixed_closed_allcaps_parenthetical` -Of the 54492 candidates, 9290 are the actual `pending_rule_review` subcluster. The other 45202 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 54492 candidates, 9251 are the actual `pending_rule_review` subcluster. The other 45241 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. Mismatch primary-class distribution: -- `comparison_method`: 191 +- `comparison_method`: 171 - `corpus_suspect`: 980 -- `pending_rule_review`: 9290 +- `pending_rule_review`: 9251 - `unsupported_character_review`: 137 Representative `exact` samples: @@ -816,9 +904,9 @@ Representative `mismatch` samples: ### `korean_prefixed_closed_roman_annotation_rule_34_order` -Of the 64382 candidates, 11933 are the actual `pending_rule_review` subcluster. The other 52449 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 64382 candidates, 11601 are the actual `pending_rule_review` subcluster. The other 52781 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. -For this output-signature audit, 13437 mismatches were evaluable and 1097 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. +For this output-signature audit, 13077 mismatches were evaluable and 1097 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. Localized first-difference transitions: @@ -827,10 +915,10 @@ Localized first-difference transitions: Mismatch primary-class distribution: -- `comparison_method`: 246 +- `comparison_method`: 221 - `corpus_suspect`: 1093 -- `pending_rule_review`: 11933 -- `unsupported_character_review`: 165 +- `pending_rule_review`: 11601 +- `unsupported_character_review`: 162 Representative `exact` samples: @@ -956,6 +1044,85 @@ Representative `mismatch` samples: - first differing cell (zero-based): 130 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +### `rule69_ascii_unit_before_terminator_skipping_symbol` + +Of the 440 candidates, 110 are the actual `pending_rule_review` subcluster. The other 330 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. + +For this output-signature audit, 115 mismatches were evaluable and 5 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. + +Localized first-difference transitions: + +- `U+2834 ⠴ -> U+280D ⠍`: 3 +- `U+2805 ⠅ -> U+2834 ⠴`: 1 +- `U+2810 ⠐ -> U+2802 ⠂`: 1 + +Mismatch primary-class distribution: + +- `comparison_method`: 3 +- `corpus_suspect`: 1 +- `pending_rule_review`: 110 +- `unsupported_character_review`: 1 + +Representative `exact` samples: + +- `sentence_01.json` #1168: 16일 리튬플러스는 충남 금산군 추부공장에서 생산된 배터리급 초고순도 수산화리튬 1.4톤(ton)을 판매했다고 밝혔다. 지난달 31일 600kg(킬로그램)에 이은 두번째 출하다. + - expected: `⠼⠁⠋⠕⠂⠀⠐⠕⠓⠩⠢⠙⠮⠐⠎⠠⠪⠉⠵⠀⠰⠍⠶⠉` + - actual: `⠼⠁⠋⠕⠂⠀⠐⠕⠓⠩⠢⠙⠮⠐⠎⠠⠪⠉⠵⠀⠰⠍⠶⠉` + - current primary/reason: `exact` / `exact` +- `sentence_02.json` #29: 어획량이 감소하면서 지난해 12월 기준 1상자(20kg)당 위판가가 24만 원까지 치솟으면서 자원 증강 필요성이 끊임없이 제기돼 왔다. + - expected: `⠎⠚⠽⠁⠐⠜⠶⠕⠀⠫⠢⠠⠥⠚⠑⠡⠠⠎⠀⠨⠕⠉⠒⠚` + - actual: `⠎⠚⠽⠁⠐⠜⠶⠕⠀⠫⠢⠠⠥⠚⠑⠡⠠⠎⠀⠨⠕⠉⠒⠚` + - current primary/reason: `exact` / `exact` +- `sentence_03.json` #81: 이번에 개발한 신제품의 용량은 기존 제품(16GB)보다 50% 높아졌다. D램 용량이 높아지면 시스템이 데이터를 더 빠르고 효율적으로 처리할 수 있게 된다. + - expected: `⠕⠘⠾⠝⠀⠈⠗⠘⠂⠚⠒⠀⠠⠟⠨⠝⠙⠍⠢⠺⠀⠬⠶⠐` + - actual: `⠕⠘⠾⠝⠀⠈⠗⠘⠂⠚⠒⠀⠠⠟⠨⠝⠙⠍⠢⠺⠀⠬⠶⠐` + - current primary/reason: `exact` / `exact` +- `sentence_04.json` #996: 기존에 월 데이터 이용량이 60GB인 20대 고객의 경우 기존에는 6만9000원(110GB)짜리 요금제만 선택이 가능했다. 하지만 이제 6만1000원(60GB)짜리 요금제 이용이 가능해 매달 8000원을 절약할 수 있다. + - expected: `⠈⠕⠨⠷⠝⠀⠏⠂⠀⠊⠝⠕⠓⠎⠀⠕⠬⠶⠐⠜⠶⠕⠀⠼` + - actual: `⠈⠕⠨⠷⠝⠀⠏⠂⠀⠊⠝⠕⠓⠎⠀⠕⠬⠶⠐⠜⠶⠕⠀⠼` + - current primary/reason: `exact` / `exact` + +Representative `localized_mismatch` samples: + +- `sentence_01.json` #15758: 22일(한국시간) 영국 ‘90min’에 따르면 베일은 ‘BT스포츠’와 인터뷰에서 오는 7월부터 미국 메이저리그사커(MLS)에 뛰어들 메시에게 조언을 해달라는 말에 “좀 더 여유가 있을 것”이라고 밝혔다. + - expected: `⠍⠁⠀⠠⠦⠼⠊⠚⠴⠍⠔⠴⠄⠝⠀⠠⠊⠐⠪⠑⠡⠀⠘⠝` + - actual: `⠍⠁⠀⠠⠦⠼⠊⠚⠍⠔⠴⠄⠝⠀⠠⠊⠐⠪⠑⠡⠀⠘⠝⠕` + - first differing cell (zero-based): 29 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_02.json` #16417: 이번 대회에선 스프린트 1000m, ‘제외+포인트(EP) 10000M’, 계주 3000m 등 3개 세부 종목으로 확대됐으나 2026년 열릴 아이치·나고야 대회에서 또다시 존립이 위태로울 전망이다. + - expected: `⠀⠼⠁⠚⠚⠚⠴⠍⠐⠀⠠⠦⠨⠝⠽⠀⠢⠀⠙⠥⠟⠓⠪⠴` + - actual: `⠀⠼⠁⠚⠚⠚⠴⠍⠂⠀⠠⠦⠨⠝⠽⠀⠢⠀⠙⠥⠟⠓⠪⠦` + - first differing cell (zero-based): 28 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_03.json` #17113: 6피트 9인치, 210파운드(206cm, 95kg)의 신체 사이즈를 지닌 폴 리드는 7피트 2인치(218cm)에 달하는 긴 윙스팬과 더불어 수비력을 인정받은 빅맨이다. + - expected: `⠴⠉⠍⠂⠀⠼⠊⠑⠅⠛⠠⠴⠺⠀⠠⠟⠰⠝⠀⠇⠕⠨⠪⠐` + - actual: `⠴⠉⠍⠂⠀⠼⠊⠑⠴⠅⠛⠠⠴⠺⠀⠠⠟⠰⠝⠀⠇⠕⠨⠪` + - first differing cell (zero-based): 39 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` + +Representative `mismatch` samples: + +- `sentence_01.json` #322: 삼성전자는 5나노미터(nm, 1nm는 10억 분의 1m) 기반 신규 컨트롤러를 탑재한 PC용 고성능 비휘발성 메모리 익스프레스(NVMe) SSD ‘PM9C1a’를 양산한다고 12일 밝혔다. + - expected: `⠄⠴⠝⠍⠂⠀⠼⠁⠝⠍⠲⠉⠵⠀⠼⠁⠚⠹⠀⠘⠛⠺⠀⠼` + - actual: `⠄⠴⠝⠍⠂⠀⠼⠁⠰⠝⠍⠲⠉⠵⠀⠼⠁⠚⠹⠀⠘⠛⠺⠀` + - first differing cell (zero-based): 29 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_02.json` #327: 또한, ‘도가(Dog+Yoga)’ 등 반려동물과 함께 즐길 수 있는 다양한 프로그램이 준비돼 있으며, 참가자 전원에게는 태안산 황토 호박고구마(3kg) 및 요가매트가 제공된다. + - expected: `⠫⠦⠄⠴⠠⠙⠕⠛⠐⠖⠠⠽⠕⠛⠁⠠⠴⠴⠄⠀⠊⠪⠶⠀` + - actual: `⠫⠦⠄⠴⠠⠙⠕⠛⠲⠢⠴⠠⠽⠕⠛⠁⠠⠴⠴⠄⠀⠊⠪⠶` + - first differing cell (zero-based): 19 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_03.json` #1399: 삼성전자가 양산을 시작한 12nm(나노미터·1nm는 10억분의 1m)급 공정 16기가비트(Gb) 더블데이터레이트(DDR)5 D램은 글로벌 메모리반도체 1위 기업인 삼성전자의 기술력이 총동원된 ‘야심작’이다. + - expected: `⠼⠁⠃⠴⠝⠍⠦⠄⠉⠉⠥⠑⠕⠓⠎⠐⠆⠼⠁⠴⠝⠍⠲⠉` + - actual: `⠼⠁⠃⠴⠝⠍⠦⠄⠸⠷⠉⠉⠥⠑⠕⠓⠎⠸⠾⠲⠸⠂⠼⠁` + - first differing cell (zero-based): 30 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_04.json` #2527: 삼성전자가 선폭 2·3나노(㎚·10억분의 1m) 수준의 반도체 설계에 필요한 ‘공정설계키트(PDK)’를 국내 팹리스(반도체 설계 업체)에 제공하는 등 파운드리(위탁 생산) 생태계 키우기에 나선다. + - expected: `⠉⠉⠥⠦⠄⠴⠝⠍⠐⠆⠼⠁⠚⠹⠘⠛⠺⠀⠼⠁⠴⠍⠠⠴` + - actual: `⠉⠉⠥⠦⠄⠴⠝⠍⠲⠐⠆⠼⠁⠚⠹⠘⠛⠺⠀⠼⠁⠴⠍⠠` + - first differing cell (zero-based): 29 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` + ### `single_capital_followed_by_parenthesized_digits` Of the 1361 candidates, 1357 are the actual `pending_rule_review` subcluster. The other 4 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. @@ -999,14 +1166,14 @@ Representative `mismatch` samples: ### `standalone_multi_character_uppercase_roman_word` -Of the 62411 candidates, 12009 are the actual `pending_rule_review` subcluster. The other 50402 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 62411 candidates, 11932 are the actual `pending_rule_review` subcluster. The other 50479 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. Mismatch primary-class distribution: -- `comparison_method`: 229 +- `comparison_method`: 205 - `corpus_suspect`: 1080 -- `pending_rule_review`: 12009 -- `unsupported_character_review`: 160 +- `pending_rule_review`: 11932 +- `unsupported_character_review`: 159 Representative `exact` samples: @@ -1143,9 +1310,9 @@ Representative `mismatch` samples: ### `uppercase_roman_run_followed_by_hyphen_digits` -Of the 571 candidates, 374 are the actual `pending_rule_review` subcluster. The other 197 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 571 candidates, 373 are the actual `pending_rule_review` subcluster. The other 198 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. -For this output-signature audit, 389 mismatches were evaluable and 259 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. +For this output-signature audit, 388 mismatches were evaluable and 259 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. Localized first-difference transitions: @@ -1155,7 +1322,7 @@ Localized first-difference transitions: Mismatch primary-class distribution: - `corpus_suspect`: 12 -- `pending_rule_review`: 374 +- `pending_rule_review`: 373 - `unsupported_character_review`: 3 Representative `exact` samples: @@ -1220,27 +1387,60 @@ The tight-triangle cohort is not an implementation premise. Hangeul rule 49 assi Corpus contradictions remain a separate gate: identical inputs with conflicting references are classified as `corpus_suspect` before these cohorts are recorded and would appear explicitly in each mismatch primary-class distribution. Their absence does not prove a reference correct; it only means that this deterministic contradiction test did not fire. -Current uppercase-Roman hyphen-digits measurement: 571 candidates, 182 exact controls, 389 mismatches, 374 members in the actual `pending_rule_review` subcluster, and 259/389 evaluable mismatches whose first difference is inside the target run plus its entry boundary. It remains distinct from parenthesized digits and headword expansions; no engine change is inferred. +Current uppercase-Roman hyphen-digits measurement: 571 candidates, 183 exact controls, 388 mismatches, 373 members in the actual `pending_rule_review` subcluster, and 259/388 evaluable mismatches whose first difference is inside the target run plus its entry boundary. It remains distinct from parenthesized digits and headword expansions; no engine change is inferred. Current single-capital parenthesized-digits measurement: 1361 candidates, 0 exact controls, 1361 mismatches, 1357 members in the actual `pending_rule_review` subcluster, and 1342/1361 evaluable mismatches whose first difference is inside the target run plus its entry boundary. No engine change is inferred from the ambiguous prose/function surface form. Current mixed Roman+Korean boundary before uppercase headword-expansion measurement: 10 candidates, 0 exact controls, 10 mismatches, 10 members in the actual `pending_rule_review` subcluster, and 8/10 evaluable mismatches whose first difference is localized to the later headword's entry boundary/output. The detector cannot be satisfied by the earlier Roman entry. No HCA-shaped engine routing rule is introduced. -Current decimal-point measurement: 4546 candidates, 3109 exact controls, 1437 mismatches, 1372 members in the actual `pending_rule_review` subcluster, and 294/1437 evaluable mismatches whose first difference is inside the complete decimal-containing word's current-engine signature. Hangeul rules 43 and 48 keep an ASCII point between digits in the numeric sequence and encode it as the decimal point; rules 35 and 69 supply controls for adjacent Roman-number chains and Roman units. This is an implementation-candidate audit, not permission to specialize on a corpus reference. +Current compact numeric+ASCII-suffix measurement: 2975 candidates, 1649 exact controls, 1326 mismatches, 1259 members in the actual `pending_rule_review` subcluster, and 60/1326 evaluable mismatches whose first difference is inside the complete current-engine output signature. Rule 40 requires the numeric indicator and rule 69 requires Roman indicators around a Roman-written unit, but the input shape alone cannot prove that every ASCII suffix is a unit. + +| ASCII suffix | Candidates | Exact | Mismatch | Localized first diff | +|---|---:|---:|---:|---:| +| `m` | 364 | 251 | 113 | 25 | +| `km` | 308 | 157 | 151 | 1 | +| `G` | 237 | 160 | 77 | 3 | +| `kg` | 209 | 168 | 41 | 6 | +| `D` | 167 | 110 | 57 | 3 | +| `p` | 140 | 19 | 121 | 0 | +| `g` | 138 | 95 | 43 | 0 | +| `t` | 128 | 82 | 46 | 0 | +| `M` | 104 | 77 | 27 | 0 | +| `cm` | 69 | 51 | 18 | 0 | +| `B` | 62 | 12 | 50 | 0 | +| `GB` | 61 | 25 | 36 | 4 | +| `GWh` | 57 | 42 | 15 | 0 | +| `ha` | 53 | 0 | 53 | 0 | +| `TV` | 52 | 2 | 50 | 1 | +| `S` | 45 | 5 | 40 | 0 | +| `GW` | 44 | 35 | 9 | 0 | +| `X` | 39 | 12 | 27 | 0 | +| `bp` | 39 | 3 | 36 | 0 | +| `K` | 38 | 10 | 28 | 1 | +| `MW` | 37 | 33 | 4 | 0 | +| `TURN` | 33 | 16 | 17 | 0 | +| `L` | 31 | 20 | 11 | 1 | +| `mm` | 29 | 19 | 10 | 1 | +| `egin` | 28 | 27 | 1 | 0 | + +Current rule-69 ASCII-unit punctuation-boundary measurement: 440 candidates, 325 exact controls, 115 mismatches, 110 members in the actual `pending_rule_review` subcluster, and 5/115 evaluable mismatches whose first difference is localized to the unit-plus-punctuation output signature. PDF rule 69 requires a Roman terminator after a Roman-written unit in the ordinary case, while rules 33/34 omit it at the listed punctuation or enclosing-mark boundary; the rule 46 PDF example `체중(kg)` is the minimal parenthesized-unit control. Membership is restricted to rule-69 spellings already supported by the engine and does not infer unit semantics for arbitrary ASCII suffixes. + Analyzer pre-fix baseline: 440 candidates, 0 exact controls, 440 mismatches, 435 pending members, and 9 signature-local first differences. After the generalized rule-33/34 boundary override and the matching non-math routing guard, the same cohort has 325 exact controls and 115 mismatches. Corpus-wide exact matches moved from 66,039 to 66,436 (+397); the additional gains are applications of the same boundary rule outside this strict ASCII detector, including compatibility-unit forms. The complete standard suite remains 5,141/5,141. + +Current decimal-point measurement: 4546 candidates, 3187 exact controls, 1359 mismatches, 1304 members in the actual `pending_rule_review` subcluster, and 265/1359 evaluable mismatches whose first difference is inside the complete decimal-containing word's current-engine signature. Hangeul rules 43 and 48 keep an ASCII point between digits in the numeric sequence and encode it as the decimal point; rules 35 and 69 supply controls for adjacent Roman-number chains and Roman units. This is an implementation-candidate audit, not permission to specialize on a corpus reference. Analyzer checkpoint `ed20f99` recorded the pre-fix baseline as 4,546 candidates, 2,584 exact controls, 1,962 mismatches, 1,905 pending members, and 875 localized first differences. Its dominant localized transition was reference decimal point `U+2832 ⠲` versus current Roman indicator `U+2834 ⠴` in 647 cases. After the generalized rule-43/48 guard, that transition is absent: 525 cases become exact and the other corrected prefixes expose later independent mismatches. Current all-caps `OU` measurement: 1816 candidates, 55 exact controls, 1761 mismatches, 1676 members in the actual `pending_rule_review` subcluster, and 1307/1761 evaluable mismatches whose first difference is inside the current-engine output signature for the detected run. This is a pronunciation-sensitive UEB review cohort, not an engine routing rule. -Current standalone-uppercase measurement: 62411 candidates, 48933 exact controls, 13478 mismatches, and 12009 members in the actual `pending_rule_review` subcluster. Its high frequency does not make it causal: the same input shape is exact in many cases, and a sentence containing the shape may first differ at another Roman, numeric, or punctuation structure. No engine change is inferred from this cohort. +Current standalone-uppercase measurement: 62411 candidates, 49035 exact controls, 13376 mismatches, and 11932 members in the actual `pending_rule_review` subcluster. Its high frequency does not make it causal: the same input shape is exact in many cases, and a sentence containing the shape may first differ at another Roman, numeric, or punctuation structure. No engine change is inferred from this cohort. -Current Korean-prefixed all-caps parenthetical measurement: 54492 candidates, 43894 exact controls, 10598 mismatches, and 9290 members in the actual `pending_rule_review` subcluster. This is a semantic-collision audit, not an engine routing rule; no implementation change is inferred from its reference outputs. +Current Korean-prefixed all-caps parenthetical measurement: 54492 candidates, 43953 exact controls, 10539 mismatches, and 9251 members in the actual `pending_rule_review` subcluster. This is a semantic-collision audit, not an engine routing rule; no implementation change is inferred from its reference outputs. -Current rule-34 opening-order measurement: 64382 structural candidates, 50945 exact controls, 13437 mismatches, and 1097/13437 evaluable mismatches whose first difference is inside the current engine's Korean opening-parenthesis cells. Only the 1096 localized first-cell transitions have the reference/current order `⠴` versus `⠦`. After requiring the complete reference `⠴⠐⠣` versus current/PDF `⠦⠄⠴` three-cell signature and preserving higher-priority comparison classifications, 1093 are classified as `corpus_suspect`; mere coexistence with a Korean-prefixed Roman annotation does not change a primary class. +Current rule-34 opening-order measurement: 64382 structural candidates, 51305 exact controls, 13077 mismatches, and 1097/13077 evaluable mismatches whose first difference is inside the current engine's Korean opening-parenthesis cells. Only the 1096 localized first-cell transitions have the reference/current order `⠴` versus `⠦`. After requiring the complete reference `⠴⠐⠣` versus current/PDF `⠦⠄⠴` three-cell signature and preserving higher-priority comparison classifications, 1093 are classified as `corpus_suspect`; mere coexistence with a Korean-prefixed Roman annotation does not change a primary class. Current all-caps Roman middle-dot measurement: 97 candidates, 0 exact controls, 97 mismatches, and 96 members in the actual `pending_rule_review` subcluster. This cross-cutting cohort preserves every primary class and is not an engine routing rule. Current inline parenthesized-operator measurement: 23 candidates, 17 exact controls, 6 mismatches, 6 members in the actual `pending_rule_review` subcluster, and 2/6 evaluable mismatches whose first differing cell is inside the emitted structure. Primary classes are preserved. - At this implementation checkpoint, the strict cohort moved from 0 to 17 exact cases; the corpus-wide total moved from 65,491 to 66039 (+548 exact) because the same PDF-backed spacing rule also applies outside the stricter Korean-boundary audit gate. The complete standard suite remains 5,141/5,141. + At that implementation checkpoint, the strict cohort moved from 0 to 17 exact cases; the corpus-wide total moved from 65,491 to 65,514 (+23 exact) because the same PDF-backed spacing rule also applied outside the stricter Korean-boundary audit gate. These are immutable checkpoint counts rather than the report's later cumulative total. The complete standard suite remained 5,141/5,141. Current tight-triangle measurement: 377 candidates, 0 exact controls, 377 mismatches, 376 members in the actual `pending_rule_review` subcluster, and 13/377 evaluable mismatches whose first difference is inside the `△` plus first-Korean output range. No engine change is inferred. @@ -1251,10 +1451,10 @@ The audit starts from all raw encoding errors, then separates cases already reso | Encoding-error audit | Cases | |---|---:| | Raw encoding errors | 450 | -| Resolved by comparison method | 262 | +| Resolved by comparison method | 265 | | Excluded as corpus suspect | 0 | -| Unresolved encoding-error review cases | 188 | -| Explained by singleton unsupported character(s) | 188 | +| Unresolved encoding-error review cases | 185 | +| Explained by singleton unsupported character(s) | 185 | | Multiple singleton unsupported characters | 2 | | Unclassified without a singleton explanation | 0 | @@ -1265,11 +1465,11 @@ The audit starts from all raw encoding errors, then separates cases already reso | Error message | Cases | |---|---:| -| `Invalid symbol character` | 188 | +| `Invalid symbol character` | 185 | | Error family | Cases | |---|---:| -| `enclosed_organization_mark` | 169 | +| `enclosed_organization_mark` | 166 | | `letterlike_unit_symbol` | 9 | | `punctuation_or_layout_symbol` | 12 | @@ -1288,25 +1488,25 @@ Families are diagnostics, not automatic normalization permissions. Rules 68/69 c | `U+2E31 ⸱` | 2 | `⸱` | `punctuation_or_layout_symbol` | | `U+302E 〮` | 1 | `〮` | `punctuation_or_layout_symbol` | | `U+3214 ㈔` | 2 | `(사)` | `enclosed_organization_mark` | -| `U+321C ㈜` | 167 | `(주)` | `enclosed_organization_mark` | +| `U+321C ㈜` | 164 | `(주)` | `enclosed_organization_mark` | ## Shards | Shard | Exact | Total | Accuracy | |---|---:|---:|---:| -| `sentence_01.json` | 19768 | 25000 | 79.07% | -| `sentence_02.json` | 19315 | 25000 | 77.26% | -| `sentence_03.json` | 19962 | 25000 | 79.85% | -| `sentence_04.json` | 6994 | 8528 | 82.01% | +| `sentence_01.json` | 19896 | 25000 | 79.58% | +| `sentence_02.json` | 19438 | 25000 | 77.75% | +| `sentence_03.json` | 20050 | 25000 | 80.20% | +| `sentence_04.json` | 7052 | 8528 | 82.69% | ## Overlapping mismatch traits | Trait | Count | |---|---:| -| `contains_ascii_digits` | 13837 | -| `contains_ascii_letters` | 17299 | -| `contains_delimiter_or_quote` | 17489 | -| `input_not_nfkc` | 819 | +| `contains_ascii_digits` | 13441 | +| `contains_ascii_letters` | 16906 | +| `contains_delimiter_or_quote` | 17092 | +| `input_not_nfkc` | 735 | ## Samples @@ -1460,5 +1660,8 @@ Against the immediately preceding 63,399-exact run, exact matches increased by 2 | Rule 36 Unicode Roman-numeral presentation normalization | 5,141/5,141 | 63,399/83,528 | 75.90% | U+2160–U+217F use the corresponding Roman-letter spelling; 11 NFKC-equivalent observations became exact, 23 errors became encoded mismatches pending review, and 3 remain blocked by `㈜` | | Rules 34/54 Korean-prefixed closed Roman annotation routing | 5,141/5,141 | 65,491/83,528 | 78.41% | A fully closed Roman annotation after an all-Korean prefix stays on the prose encoder path, including attached comma/period and alphanumeric forms such as `O4O`; exact matches increased by 2,092 while the global math detector remained unchanged | | Rules 43/48 decimal-point ownership | 5,141/5,141 | 66,039/83,528 | 79.06% | A period directly between ASCII digits remains on the numeric punctuation path even when its word or sentence also contains Roman text; 647 localized Roman-entry differences were removed and 525 cases became exact | +| Rules 33/34/69 Roman-unit punctuation boundary | 5,141/5,141 | 66,436/83,528 | 79.54% | Rule-69 units retain their ordinary terminator at end/Korean/slash boundaries but omit it before rule-33/34 punctuation or enclosing marks; compact unit tokens with that boundary stay off the math path; 397 cases became exact | + +The latest full `cargo test -p braillify test_by_testcase --release -- --nocapture` run was accepted from its custom testcase summary, not the trailing filtered harness: `총 테스트 케이스: 5141`, `성공: 5141`, `실패: 0`, and `Skip (limitation): 0`. Engine changes must add a row only after both the 5,141-case standard suite and this full analysis have been rerun. Suspect-reference clusters stay in this report; they are not engine targets without independent PDF evidence. diff --git a/libs/braillify/examples/nikl_corpus_analyze.rs b/libs/braillify/examples/nikl_corpus_analyze.rs index 3e9eb874..54a4a711 100644 --- a/libs/braillify/examples/nikl_corpus_analyze.rs +++ b/libs/braillify/examples/nikl_corpus_analyze.rs @@ -191,6 +191,7 @@ struct AnalysisReport { pending_first_difference_cell_transitions: BTreeMap, pending_first_difference_transitions_after_localized_cohorts: BTreeMap, + compact_numeric_ascii_suffixes: BTreeMap, overlapping_traits: BTreeMap, shards: BTreeMap, samples: BTreeMap>, @@ -580,6 +581,9 @@ const MIXED_ROMAN_KOREAN_BEFORE_HEADWORD_EXPANSION: &str = "mixed_roman_korean_word_before_uppercase_headword_expansion"; const UPPERCASE_ROMAN_HYPHEN_DIGITS: &str = "uppercase_roman_run_followed_by_hyphen_digits"; const DECIMAL_POINT_BETWEEN_DIGITS: &str = "decimal_point_between_ascii_digits"; +const COMPACT_NUMERIC_ASCII_SUFFIX: &str = "compact_numeric_ascii_letter_suffix"; +const RULE69_ASCII_UNIT_BEFORE_TERMINATOR_SKIPPING_SYMBOL: &str = + "rule69_ascii_unit_before_terminator_skipping_symbol"; #[derive(Clone, Copy, Debug, PartialEq, Eq)] struct InputSpan { @@ -623,6 +627,134 @@ fn decimal_word_spans(input: &str) -> Vec { .collect() } +/// Finds a compact numeric prefix followed immediately by one or more ASCII +/// letters, with alphanumeric outer boundaries. The shape includes rule-69 +/// units but deliberately does not declare every suffix a unit: mathematical +/// variables and identifiers can share the same surface form. +fn compact_numeric_ascii_suffix_spans(input: &str) -> Vec { + let bytes = input.as_bytes(); + let mut spans = Vec::new(); + let mut cursor = 0usize; + while cursor < bytes.len() { + if !bytes[cursor].is_ascii_digit() + || input[..cursor] + .chars() + .next_back() + .is_some_and(|ch| ch.is_ascii_alphanumeric()) + { + cursor += input[cursor..] + .chars() + .next() + .expect("cursor must remain on a character boundary") + .len_utf8(); + continue; + } + + let start_byte = cursor; + while bytes + .get(cursor) + .is_some_and(|byte| byte.is_ascii_digit() || matches!(*byte, b',' | b'.')) + { + cursor += 1; + } + let suffix_start = cursor; + while bytes.get(cursor).is_some_and(u8::is_ascii_alphabetic) { + cursor += 1; + } + if cursor > suffix_start + && input[cursor..] + .chars() + .next() + .is_none_or(|ch| !ch.is_ascii_alphanumeric()) + { + spans.push(InputSpan { + start_byte, + end_byte: cursor, + }); + } + } + spans +} + +fn compact_numeric_ascii_suffix(span: InputSpan, input: &str) -> &str { + input[span.start_byte..span.end_byte] + .trim_start_matches(|ch: char| ch.is_ascii_digit() || matches!(ch, ',' | '.')) +} + +/// Refines the broad compact-suffix cohort to spellings already recognized by +/// rule 69, immediately followed by punctuation for which rules 33/34 omit the +/// Roman terminator. The span includes that punctuation so the output locator +/// measures the exact unit-to-punctuation boundary rather than mere coexistence. +fn rule69_ascii_unit_before_terminator_skipping_symbol_spans(input: &str) -> Vec { + const RULE69_ASCII_UNITS: &[&str] = &["min", "cal", "cm", "kg", "in", "mm", "GB", "m", "h"]; + + compact_numeric_ascii_suffix_spans(input) + .into_iter() + .filter_map(|span| { + let suffix = compact_numeric_ascii_suffix(span, input); + let symbol = input[span.end_byte..].chars().next()?; + (RULE69_ASCII_UNITS.contains(&suffix) + && matches!( + symbol, + '.' | '?' + | '!' + | '…' + | '⋯' + | '"' + | '\'' + | '”' + | '’' + | '」' + | '』' + | '〉' + | '》' + | '(' + | ')' + | ']' + | '}' + | ',' + | ':' + | ';' + | '―' + )) + .then_some(InputSpan { + start_byte: span.start_byte, + end_byte: span.end_byte + symbol.len_utf8(), + }) + }) + .collect() +} + +fn first_difference_at_rule69_ascii_unit_terminator_boundary(item: &EncodedCase) -> bool { + first_difference_in_compact_numeric_ascii_suffix_spans( + item, + &rule69_ascii_unit_before_terminator_skipping_symbol_spans(&item.located.case.input), + ) +} + +fn first_difference_in_compact_numeric_ascii_suffix(item: &EncodedCase) -> bool { + first_difference_in_compact_numeric_ascii_suffix_spans( + item, + &compact_numeric_ascii_suffix_spans(&item.located.case.input), + ) +} + +fn first_difference_in_compact_numeric_ascii_suffix_spans( + item: &EncodedCase, + spans: &[InputSpan], +) -> bool { + let Ok(actual) = &item.actual else { + return false; + }; + if actual == &item.located.case.unicode { + return false; + } + let first_difference = first_difference_cell(&item.located.case.unicode, actual); + korean_context_signature_ranges(&item.located.case.input, actual, spans, 0) + .into_iter() + .any(|range| range.contains(&first_difference)) +} + fn first_difference_in_decimal_word(item: &EncodedCase) -> bool { let Ok(actual) = &item.actual else { return false; @@ -1010,6 +1142,7 @@ fn first_difference_in_korean_context_signature_spans( /// unrelated causes merely because a sentence also contains Roman text. fn first_difference_claimed_by_localized_cohort(item: &EncodedCase) -> bool { first_difference_in_allcaps_ou_run(item) + || first_difference_in_compact_numeric_ascii_suffix(item) || first_difference_in_decimal_word(item) || first_difference_in_korean_prefixed_annotation_opening(item) || first_difference_in_inline_parenthesized_operator(item) @@ -1573,6 +1706,14 @@ fn analyze( let mut samples = BTreeMap::>::new(); let mut rule_36_transition_audit = Rule36TransitionAudit::default(); let mut pending_rule_review_clusters = BTreeMap::from([ + ( + RULE69_ASCII_UNIT_BEFORE_TERMINATOR_SKIPPING_SYMBOL.to_string(), + PendingRuleReviewClusterStats::default(), + ), + ( + COMPACT_NUMERIC_ASCII_SUFFIX.to_string(), + PendingRuleReviewClusterStats::default(), + ), ( DECIMAL_POINT_BETWEEN_DIGITS.to_string(), PendingRuleReviewClusterStats::default(), @@ -1624,6 +1765,7 @@ fn analyze( ]); let mut pending_first_difference_cell_transitions = BTreeMap::new(); let mut pending_first_difference_transitions_after_localized_cohorts = BTreeMap::new(); + let mut compact_numeric_ascii_suffixes = BTreeMap::new(); let mut exact = 0usize; for item in &encoded { @@ -1679,6 +1821,23 @@ fn analyze( } for (cluster, present, localized_first_difference, localized_samples) in [ + ( + RULE69_ASCII_UNIT_BEFORE_TERMINATOR_SKIPPING_SYMBOL, + !rule69_ascii_unit_before_terminator_skipping_symbol_spans( + &item.located.case.input, + ) + .is_empty(), + Some(first_difference_at_rule69_ascii_unit_terminator_boundary( + item, + )), + true, + ), + ( + COMPACT_NUMERIC_ASCII_SUFFIX, + !compact_numeric_ascii_suffix_spans(&item.located.case.input).is_empty(), + Some(first_difference_in_compact_numeric_ascii_suffix(item)), + true, + ), ( DECIMAL_POINT_BETWEEN_DIGITS, !decimal_word_spans(&item.located.case.input).is_empty(), @@ -1782,6 +1941,26 @@ fn analyze( ); } + let mut suffix_spans = BTreeMap::>::new(); + for span in compact_numeric_ascii_suffix_spans(&item.located.case.input) { + suffix_spans + .entry(compact_numeric_ascii_suffix(span, &item.located.case.input).to_string()) + .or_default() + .push(span); + } + for (suffix, spans) in suffix_spans { + let localized = first_difference_in_compact_numeric_ascii_suffix_spans(item, &spans); + record_structural_cohort_case( + compact_numeric_ascii_suffixes.entry(suffix).or_default(), + item, + &primary_key, + &reason_key, + sample_limit, + Some(localized), + false, + ); + } + let shard = shards.entry(item.located.shard.clone()).or_default(); shard.total += 1; if primary == PrimaryClass::Exact { @@ -1914,6 +2093,7 @@ fn analyze( pending_rule_review_clusters, pending_first_difference_cell_transitions, pending_first_difference_transitions_after_localized_cohorts, + compact_numeric_ascii_suffixes, overlapping_traits: traits, shards, samples, @@ -2115,7 +2295,14 @@ fn markdown(report: &AnalysisReport) -> String { signature as localized. The `decimal_point_between_ascii_digits` gate finds \ whitespace-delimited words containing `digit.digit` and reproduces each whole word in a \ neutral Korean context, so suffixes and punctuation remain part of the current-engine \ - signature. The `tight_triangle_mark_immediately_before_korean` gate requires literal \ + signature. The `compact_numeric_ascii_letter_suffix` gate finds a numeric prefix \ + immediately followed by ASCII letters and retains suffix-specific outcome counts. It \ + intentionally includes both possible rule-69 units and ambiguous variable/identifier \ + forms; membership alone does not assign unit semantics. The \ + `rule69_ascii_unit_before_terminator_skipping_symbol` gate is narrower: it accepts only \ + ASCII unit spellings already supported by rule 69, includes the immediately following \ + rule-33/34 punctuation cell in the localized signature, and does not infer new units. The \ + `tight_triangle_mark_immediately_before_korean` gate requires literal \ `△한글` with no input space and includes the first following Korean cell in its localized \ output range, so an observed missing-space difference is measured at the mark boundary.\n\n", ); @@ -2359,6 +2546,88 @@ fn markdown(report: &AnalysisReport) -> String { stats.output_signature_mismatches_evaluated )); } + if let Some(stats) = report + .pending_rule_review_clusters + .get(COMPACT_NUMERIC_ASCII_SUFFIX) + { + let pending = stats + .mismatch_primary_classes + .get("pending_rule_review") + .copied() + .unwrap_or(0); + text.push_str(&format!( + "\nCurrent compact numeric+ASCII-suffix measurement: {} candidates, {} exact \ + controls, {} mismatches, {pending} members in the actual `pending_rule_review` \ + subcluster, and {}/{} evaluable mismatches whose first difference is inside the \ + complete current-engine output signature. Rule 40 requires the numeric indicator \ + and rule 69 requires Roman indicators around a Roman-written unit, but the input \ + shape alone cannot prove that every ASCII suffix is a unit.\n\n", + stats.candidates, + stats.exact, + stats.mismatch, + stats.first_difference_in_output_signature, + stats.output_signature_mismatches_evaluated + )); + + let mut suffixes = report + .compact_numeric_ascii_suffixes + .iter() + .collect::>(); + suffixes.sort_by(|(left_key, left), (right_key, right)| { + right + .candidates + .cmp(&left.candidates) + .then_with(|| left_key.cmp(right_key)) + }); + text.push_str( + "| ASCII suffix | Candidates | Exact | Mismatch | Localized first diff |\n\ + |---|---:|---:|---:|---:|\n", + ); + for (suffix, suffix_stats) in suffixes.into_iter().take(25) { + text.push_str(&format!( + "| `{suffix}` | {} | {} | {} | {} |\n", + suffix_stats.candidates, + suffix_stats.exact, + suffix_stats.mismatch, + suffix_stats.first_difference_in_output_signature + )); + } + } + if let Some(stats) = report + .pending_rule_review_clusters + .get(RULE69_ASCII_UNIT_BEFORE_TERMINATOR_SKIPPING_SYMBOL) + { + let pending = stats + .mismatch_primary_classes + .get("pending_rule_review") + .copied() + .unwrap_or(0); + text.push_str(&format!( + "\nCurrent rule-69 ASCII-unit punctuation-boundary measurement: {} candidates, {} \ + exact controls, {} mismatches, {pending} members in the actual \ + `pending_rule_review` subcluster, and {}/{} evaluable mismatches whose first \ + difference is localized to the unit-plus-punctuation output signature. PDF rule 69 \ + requires a Roman terminator after a Roman-written unit in the ordinary case, while \ + rules 33/34 omit it at the listed punctuation or enclosing-mark boundary; the rule \ + 46 PDF example `체중(kg)` is the minimal parenthesized-unit control. Membership is \ + restricted to rule-69 spellings already supported by the engine and does not infer \ + unit semantics for arbitrary ASCII suffixes.\n", + stats.candidates, + stats.exact, + stats.mismatch, + stats.first_difference_in_output_signature, + stats.output_signature_mismatches_evaluated + )); + text.push_str( + " Analyzer pre-fix baseline: 440 candidates, 0 exact controls, 440 mismatches, 435 \ + pending members, and 9 signature-local first differences. After the generalized \ + rule-33/34 boundary override and the matching non-math routing guard, the same cohort \ + has 325 exact controls and 115 mismatches. Corpus-wide exact matches moved from \ + 66,039 to 66,436 (+397); the additional gains are applications of the same boundary \ + rule outside this strict ASCII detector, including compatibility-unit forms. The \ + complete standard suite remains 5,141/5,141.\n", + ); + } if let Some(stats) = report .pending_rule_review_clusters .get(DECIMAL_POINT_BETWEEN_DIGITS) @@ -2517,15 +2786,13 @@ fn markdown(report: &AnalysisReport) -> String { stats.first_difference_in_output_signature, stats.output_signature_mismatches_evaluated )); - text.push_str(&format!( - " At this implementation checkpoint, the strict cohort moved from 0 to {} exact \ - cases; the corpus-wide total moved from 65,491 to {} ({:+} exact) because the same \ - PDF-backed spacing rule also applies outside the stricter Korean-boundary audit \ - gate. The complete standard suite remains 5,141/5,141.\n", - stats.exact, - report.exact, - report.exact as isize - 65_491 - )); + text.push_str( + " At that implementation checkpoint, the strict cohort moved from 0 to 17 exact \ + cases; the corpus-wide total moved from 65,491 to 65,514 (+23 exact) because the same \ + PDF-backed spacing rule also applied outside the stricter Korean-boundary audit \ + gate. These are immutable checkpoint counts rather than the report's later cumulative \ + total. The complete standard suite remained 5,141/5,141.\n", + ); } if let Some(stats) = report .pending_rule_review_clusters @@ -2797,7 +3064,14 @@ fn markdown(report: &AnalysisReport) -> String { "| Rules 43/48 decimal-point ownership | 5,141/5,141 | 66,039/83,528 | 79.06% | A period directly between ASCII digits remains on the numeric punctuation path even when its word or sentence also contains Roman text; 647 localized Roman-entry differences were removed and 525 cases became exact |\n", ); text.push_str( - "\nEngine changes must add a row only after both the 5,141-case standard suite and \ + "| Rules 33/34/69 Roman-unit punctuation boundary | 5,141/5,141 | 66,436/83,528 | 79.54% | Rule-69 units retain their ordinary terminator at end/Korean/slash boundaries but omit it before rule-33/34 punctuation or enclosing marks; compact unit tokens with that boundary stay off the math path; 397 cases became exact |\n", + ); + text.push_str( + "\nThe latest full `cargo test -p braillify test_by_testcase --release -- --nocapture` \ + run was accepted from its custom testcase summary, not the trailing filtered harness: \ + `총 테스트 케이스: 5141`, `성공: 5141`, `실패: 0`, and \ + `Skip (limitation): 0`.\n\n\ + Engine changes must add a row only after both the 5,141-case standard suite and \ this full analysis have been rerun. Suspect-reference clusters stay in this report; \ they are not engine targets without independent PDF evidence.\n", ); @@ -3035,6 +3309,61 @@ mod tests { assert!(ranges[0].end <= actual.chars().count()); } + #[rstest::rstest] + #[case::energy("용량 13GWh 규모", vec![("13GWh", "GWh")])] + #[case::distance("구간 0.73km", vec![("0.73km", "km")])] + #[case::mass("필로폰 968g 등", vec![("968g", "g")])] + #[case::ambiguous_variable("값 3x", vec![("3x", "x")])] + #[case::letter_prefix("GPT3 모델", vec![])] + #[case::alphanumeric_suffix("13GWh2", vec![])] + fn detects_compact_numeric_ascii_suffixes( + #[case] input: &str, + #[case] expected: Vec<(&str, &str)>, + ) { + let actual = compact_numeric_ascii_suffix_spans(input) + .into_iter() + .map(|span| { + ( + &input[span.start_byte..span.end_byte], + compact_numeric_ascii_suffix(span, input), + ) + }) + .collect::>(); + assert_eq!(actual, expected); + } + + #[test] + fn locates_compact_numeric_ascii_suffix_in_current_output() { + let input = "용량은 13GWh 규모다."; + let actual = braillify::encode_to_unicode(input).expect("compact suffix probe must encode"); + let ranges = korean_context_signature_ranges( + input, + &actual, + &compact_numeric_ascii_suffix_spans(input), + 0, + ); + + assert_eq!(ranges.len(), 1); + assert!(ranges[0].start < ranges[0].end); + } + + #[rstest::rstest] + #[case::kilogram_parenthesis("상자(20kg)당", vec!["20kg)"])] + #[case::metre_quote("길이는 3m”라고", vec!["3m”"])] + #[case::ordinary_unit_boundary("무게는 3kg이다", vec![])] + #[case::ambiguous_suffix("값은 3x)이다", vec![])] + #[case::forced_slash_boundary("속도는 3m/시", vec![])] + fn detects_rule69_ascii_units_before_terminator_skipping_symbols( + #[case] input: &str, + #[case] expected: Vec<&str>, + ) { + let actual = rule69_ascii_unit_before_terminator_skipping_symbol_spans(input) + .into_iter() + .map(|span| &input[span.start_byte..span.end_byte]) + .collect::>(); + assert_eq!(actual, expected); + } + #[test] fn roman_indicator_moves_before_capital_word_indicator() { assert_eq!(roman_before_capital_order("⠠⠠⠴⠁⠃"), "⠴⠠⠠⠁⠃"); diff --git a/libs/braillify/src/rules/korean/rule_69.rs b/libs/braillify/src/rules/korean/rule_69.rs index df080777..333811fe 100644 --- a/libs/braillify/src/rules/korean/rule_69.rs +++ b/libs/braillify/src/rules/korean/rule_69.rs @@ -281,6 +281,25 @@ fn trim_recent_english_indicator(result: &mut Vec) { } } +/// Rules 33/34 override rule 69's ordinary trailing Roman terminator when a +/// listed Korean punctuation mark or an enclosing mark closes the Roman run. +/// Unit encoders include their ordinary terminator so standalone/end/Korean +/// boundaries stay unchanged; this helper applies only at the actual following +/// input boundary. +fn omit_roman_terminator_before_boundary( + encoded: &mut Vec, + word: &[char], + boundary_index: usize, +) { + if word + .get(boundary_index) + .is_some_and(|symbol| crate::english_logic::should_skip_terminator_for_symbol(*symbol)) + && encoded.last() == Some(&crate::unicode::decode_unicode('⠲')) + { + encoded.pop(); + } +} + fn should_insert_separator_after_symbol(symbol: char, next: Option) -> bool { SEPARATED_SYMBOLS.contains(&symbol) && next.is_some_and(crate::utils::is_korean_char) } @@ -313,8 +332,10 @@ impl BrailleRule for Rule69 { fn apply(&self, ctx: &mut RuleContext) -> Result { if matches!(ctx.char_type, CharType::Number(_)) && ctx.index == 0 - && let Some((numeric, unit, consumed)) = parse_numeric_ascii_unit_prefix(ctx.word_chars) + && let Some((numeric, mut unit, consumed)) = + parse_numeric_ascii_unit_prefix(ctx.word_chars) { + omit_roman_terminator_before_boundary(&mut unit, ctx.word_chars, consumed); let mut encoded = crate::encode(&numeric)?; encoded.extend(unit); ctx.emit_slice(&encoded); @@ -327,8 +348,13 @@ impl BrailleRule for Rule69 { if matches!(ctx.char_type, CharType::English(_)) && (is_numeric_or_unit_context(ctx) || (ctx.index == 0 && word_looks_like_unit_chain(ctx.word_chars))) - && let Some((encoded, consumed)) = encode_ascii_unit(ctx.word_chars, ctx.index) + && let Some((mut encoded, consumed)) = encode_ascii_unit(ctx.word_chars, ctx.index) { + omit_roman_terminator_before_boundary( + &mut encoded, + ctx.word_chars, + ctx.index + consumed, + ); trim_recent_english_indicator(ctx.result); ctx.emit_slice(&encoded); ctx.state.is_english = false; @@ -370,6 +396,12 @@ impl BrailleRule for Rule69 { encoded.extend(encode_unicode_cells("⠍")); } + omit_roman_terminator_before_boundary( + &mut encoded, + ctx.word_chars, + ctx.index + consumed, + ); + ctx.emit_slice(&encoded); ctx.state.is_english = false; ctx.state.needs_english_continuation = false; @@ -380,8 +412,9 @@ impl BrailleRule for Rule69 { if let Some(parts) = compatibility_unit_decomposition(ctx.current_char()) { let continues_from_previous = roman_unit_chain_continues_before(ctx); let continues_after = roman_unit_chain_continues_after(ctx); - let encoded = + let mut encoded = encode_compatibility_unit(&parts, !continues_from_previous, !continues_after)?; + omit_roman_terminator_before_boundary(&mut encoded, ctx.word_chars, ctx.index + 1); ctx.emit_slice(&encoded); ctx.state.is_english = false; ctx.state.needs_english_continuation = false; @@ -395,7 +428,8 @@ impl BrailleRule for Rule69 { .iter() .find(|(candidate, _)| *candidate == ctx.current_char()) .expect("matches() guarantees the char is in SINGLE_MAPPINGS"); - let encoded = encode_unicode_cells(unicode); + let mut encoded = encode_unicode_cells(unicode); + omit_roman_terminator_before_boundary(&mut encoded, ctx.word_chars, ctx.index + 1); ctx.emit_slice(&encoded); if should_insert_separator_after_symbol(ctx.current_char(), ctx.next_char()) { ctx.emit(0); @@ -408,7 +442,8 @@ impl BrailleRule for Rule69 { mod tests { use super::{ Rule69, compatibility_unit_decomposition, encode_ascii_unit, encode_compatibility_unit, - encode_percent_abbreviation, encode_rule_69_unit_letters, parse_numeric_ascii_unit_prefix, + encode_percent_abbreviation, encode_rule_69_unit_letters, encode_unicode_cells, + omit_roman_terminator_before_boundary, parse_numeric_ascii_unit_prefix, word_looks_like_unit_chain, }; @@ -541,6 +576,57 @@ mod tests { assert_eq!(crate::encode_to_unicode(input).unwrap(), expected); } + /// Rules 33/34/69: the ordinary unit terminator is omitted only when the + /// actual following boundary is one of the standard's punctuation/enclosing + /// marks. These are full-encoder checks, including numeric-prefix routing. + #[rstest::rstest] + #[case::kilogram_in_parentheses("상자(20kg)당", "⠼⠃⠚⠴⠅⠛⠠⠴", "⠴⠅⠛⠲⠠⠴")] + #[case::centimetre_before_korean_comma("키는 173cm, 몸무게는", "⠼⠁⠛⠉⠴⠉⠍⠐", "⠴⠉⠍⠲⠐")] + #[case::centimetre_before_next_measurement("키 173cm, 68kg", "⠼⠁⠛⠉⠴⠉⠍⠂", "⠴⠉⠍⠲⠂")] + #[case::metre_before_sentence_period("비거리 130m.", "⠼⠁⠉⠚⠴⠍⠲", "⠴⠍⠲⠲")] + #[case::compatibility_kilogram_in_parentheses("상자(20㎏)당", "⠼⠃⠚⠴⠅⠛⠠⠴", "⠴⠅⠛⠲⠠⠴")] + fn omits_unit_terminator_at_rule_33_or_34_boundary( + #[case] input: &str, + #[case] expected_segment: &str, + #[case] forbidden_segment: &str, + ) { + let actual = crate::encode_to_unicode(input).unwrap(); + assert!( + actual.contains(expected_segment), + "missing rule-33/34 unit boundary {expected_segment:?} in {actual:?}" + ); + assert!( + !actual.contains(forbidden_segment), + "unexpected rule-69 terminator at rule-33/34 boundary {forbidden_segment:?} in {actual:?}" + ); + } + + /// Rule 69 remains the default outside the rule-33/34 override. End of + /// input, a following Korean syllable, and forced slash boundaries retain + /// the ordinary Roman terminator. + #[rstest::rstest] + #[case::end_of_input("180cm", "⠴⠉⠍⠲")] + #[case::before_korean("1m는", "⠴⠍⠲")] + #[case::before_forced_slash("3m/시", "⠴⠍⠲⠸⠌")] + fn retains_unit_terminator_at_ordinary_rule_69_boundary( + #[case] input: &str, + #[case] expected_segment: &str, + ) { + let actual = crate::encode_to_unicode(input).unwrap(); + assert!( + actual.contains(expected_segment), + "missing ordinary rule-69 unit boundary {expected_segment:?} in {actual:?}" + ); + } + + #[test] + fn boundary_helper_does_not_remove_non_terminator_cells() { + let word = "kg)".chars().collect::>(); + let mut encoded = encode_unicode_cells("⠴⠅⠛"); + omit_roman_terminator_before_boundary(&mut encoded, &word, 2); + assert_eq!(encoded, encode_unicode_cells("⠴⠅⠛")); + } + #[test] fn parses_compact_number_unit_word() { let chars: Vec = "180cm".chars().collect(); diff --git a/libs/braillify/src/rules/token_rules/math_expression/detect.rs b/libs/braillify/src/rules/token_rules/math_expression/detect.rs index fcda3d30..ea48dc2e 100644 --- a/libs/braillify/src/rules/token_rules/math_expression/detect.rs +++ b/libs/braillify/src/rules/token_rules/math_expression/detect.rs @@ -100,6 +100,20 @@ pub(super) fn is_math_expression(chars: &[char], text: &str) -> bool { return false; } + // PDF 제33·34·69항: 숫자+로마자 단위와 바로 뒤의 종료표 생략 문장부호는 + // 수식이 아니라 하나의 국어 문장 내 단위 표기다. 일반 operator/symbol 판정보다 + // 먼저 배제해야 `173cm,` 같은 토큰이 comma 때문에 수식 경로로 우회하지 않는다. + if let Some((_, _, consumed)) = + crate::rules::korean::rule_69::parse_numeric_ascii_unit_prefix(chars) + && (consumed == chars.len() + || (consumed + 1 == chars.len() + && chars.get(consumed).is_some_and(|symbol| { + crate::english_logic::should_skip_terminator_for_symbol(*symbol) + }))) + { + return false; + } + // Slash-only numeric tokens: 2-part (N/M) is a fraction expression for any digit count; // 3-or-more parts (e.g. 2024/12/31) is a date/range and stays non-math. if !has_letters && chars.contains(&'/') && chars.iter().all(|c| c.is_ascii_digit() || *c == '/') @@ -242,13 +256,6 @@ pub(super) fn is_math_expression(chars: &[char], text: &str) -> bool { // Digit-then-letter transition at start of word (like "3ab" → math multiplication) // But NOT letter-then-digit (like "MP3" which is NOT math) if chars.len() >= 2 && chars[0].is_ascii_digit() { - // PDF 제69항: 숫자+단위 (180cm, 5kg, 1in 등)은 math가 아닌 단위 표기로 처리. - if let Some((_, _, consumed)) = - crate::rules::korean::rule_69::parse_numeric_ascii_unit_prefix(chars) - && consumed == chars.len() - { - return false; - } // PDF 제33항 — 학술 인용 형식: `YYYYa`, `YYYYa,`, `YYYYa;` (4자리+년도+단일 // 알파벳 suffix + 구두점). 이런 토큰은 수학 곱셈이 아닌 영어 모드 인용 // 표기이므로 math expression이 아니다. @@ -305,4 +312,17 @@ mod tests { let chars: Vec = "3}".chars().collect(); assert!(super::is_math_expression(&chars, "3}")); } + + /// Rules 33/34/69: punctuation which suppresses a Roman terminator remains + /// attached to the compact unit token without turning the unit into math. + #[rstest::rstest] + #[case::ordinary_unit("180cm", false)] + #[case::unit_before_comma("173cm,", false)] + #[case::unit_before_closing_parenthesis("20kg)", false)] + #[case::unit_before_period("130m.", false)] + #[case::ambiguous_product_before_comma("3ab,", true)] + fn compact_rule69_unit_with_boundary_is_not_math(#[case] input: &str, #[case] expected: bool) { + let chars = input.chars().collect::>(); + assert_eq!(super::is_math_expression(&chars, input), expected); + } } From 12a84fe680753c3ad9034f782113398ee5eb430d Mon Sep 17 00:00:00 2001 From: devfive Date: Tue, 1 Sep 2026 08:47:37 +0900 Subject: [PATCH 22/77] Localize tight triangle diagnostics in actual output Find the neutral-Korean current-engine signature in the complete actual output instead of deriving a context-sensitive offset from an isolated prefix. The conservative Rule 49/72 pending decision remains unchanged; ASCII/digit followers stay outside the gate. Tight-triangle cohort: 377 candidates, 0 exact, 377 mismatch, 330 localized. Its dominant residual transition falls from 343 to 32 cases. Analyzer: 132 tests; report/JSON hashes equal across two runs; clippy clean. Engine accuracy remains 66436/83528. --- docs/corpus-analysis/NIKL_2025_V1.md | 29 +++++----- .../braillify/examples/nikl_corpus_analyze.rs | 53 +++++++++---------- 2 files changed, 40 insertions(+), 42 deletions(-) diff --git a/docs/corpus-analysis/NIKL_2025_V1.md b/docs/corpus-analysis/NIKL_2025_V1.md index c2a00b81..33890f7f 100644 --- a/docs/corpus-analysis/NIKL_2025_V1.md +++ b/docs/corpus-analysis/NIKL_2025_V1.md @@ -309,15 +309,15 @@ This ranking removes only cases whose first difference is inside an existing out | 9 | `U+2808 ⠈ -> U+2832 ⠲` | 407 | | 10 | `U+2826 ⠦ -> U+2800 ⠀` | 388 | | 11 | `U+2801 ⠁ -> U+281C ⠜` | 386 | -| 12 | `U+2800 ⠀ -> U+2807 ⠇` | 343 | -| 13 | `U+2834 ⠴ -> U+2830 ⠰` | 325 | -| 14 | `U+2811 ⠑ -> U+282B ⠫` | 322 | -| 15 | `U+2832 ⠲ -> U+2838 ⠸` | 275 | -| 16 | `U+2800 ⠀ -> U+2832 ⠲` | 225 | -| 17 | `U+2810 ⠐ -> U+2802 ⠂` | 209 | -| 18 | `U+2817 ⠗ -> U+2820 ⠠` | 161 | -| 19 | `U+2820 ⠠ -> U+2809 ⠉` | 152 | -| 20 | `U+281B ⠛ -> U+2823 ⠣` | 144 | +| 12 | `U+2834 ⠴ -> U+2830 ⠰` | 325 | +| 13 | `U+2811 ⠑ -> U+282B ⠫` | 322 | +| 14 | `U+2832 ⠲ -> U+2838 ⠸` | 275 | +| 15 | `U+2800 ⠀ -> U+2832 ⠲` | 225 | +| 16 | `U+2810 ⠐ -> U+2802 ⠂` | 209 | +| 17 | `U+2817 ⠗ -> U+2820 ⠠` | 161 | +| 18 | `U+2820 ⠠ -> U+2809 ⠉` | 152 | +| 19 | `U+281B ⠛ -> U+2823 ⠣` | 144 | +| 20 | `U+2826 ⠦ -> U+2810 ⠐` | 140 | ### Residual `U+2830 ⠰ -> U+2820 ⠠` @@ -1221,11 +1221,14 @@ Representative `mismatch` samples: Of the 377 candidates, 376 are the actual `pending_rule_review` subcluster. The other 1 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. -For this output-signature audit, 377 mismatches were evaluable and 13 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. +For this output-signature audit, 377 mismatches were evaluable and 330 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. Localized first-difference transitions: -- `U+2800 ⠀ -> U+2807 ⠇`: 13 +- `U+2800 ⠀ -> U+2807 ⠇`: 327 +- `U+280A ⠊ -> U+2807 ⠇`: 1 +- `U+2818 ⠘ -> U+2807 ⠇`: 1 +- `U+2829 ⠩ -> U+2807 ⠇`: 1 Mismatch primary-class distribution: @@ -1383,7 +1386,7 @@ The all-caps Roman middle-dot cohort is also semantically underdetermined. Hange The inline parenthesized-operator cohort has an independently checkable spacing boundary. Hangeul rule 46 inserts spaces only when an operation or comparison sign is between Korean text, while the literal parentheses intervene in this gate. Hangeul rule 49 says punctuation spacing follows the print, and science rule 21 prints and brailles `(-)` and `(+)` with no spaces inside the parentheses. The output-signature count is therefore the implementation-candidate subset; mere sentence-level coexistence is retained only as a control. ASCII hyphen-minus is independently supported through the `Symbol`/rule-49 punctuation path, while `+`, `×`, `÷`, and `=` reach the `MathSymbol` spacing rule; end-to-end tests preserve tight parentheses on both paths. Analyzer checkpoint `30a9b10` recorded the pre-fix baseline as 23 candidates, 0 exact, 23 mismatches, and 19 mismatches whose first difference was signature-local. The generalized rule-46/49 fix is evaluated below against that immutable baseline rather than inferred from a reference string. -The tight-triangle cohort is not an implementation premise. Hangeul rule 49 assigns `△` the omission-mark role and requires print spacing to be followed, while rule 72 also assigns the same glyph a bullet role but shows a print space after every bullet. A tight corpus input does not identify which role was intended, and adding a space absent from the input would contradict rule 49 unless independent layout evidence establishes a bullet. Localized mismatches are therefore corpus/layout review evidence only. +The tight-triangle cohort is not an implementation premise. Hangeul rule 49 assigns `△` the omission-mark role and requires print spacing to be followed, while rule 72 also assigns the same glyph a bullet role but shows a print space after every bullet. A tight corpus input does not identify which role was intended, and adding a space absent from the input would contradict rule 49 unless independent layout evidence establishes a bullet. The localizer searches the complete actual output for a neutral-Korean, current-engine signature covering the mark and its first following Korean character; it neither encodes a context-sensitive sentence prefix in isolation nor reads the reference output. Tight marks followed by ASCII letters or digits remain outside this gate. Localized mismatches are therefore corpus/layout review evidence only. Corpus contradictions remain a separate gate: identical inputs with conflicting references are classified as `corpus_suspect` before these cohorts are recorded and would appear explicitly in each mismatch primary-class distribution. Their absence does not prove a reference correct; it only means that this deterministic contradiction test did not fire. @@ -1442,7 +1445,7 @@ Current all-caps Roman middle-dot measurement: 97 candidates, 0 exact controls, Current inline parenthesized-operator measurement: 23 candidates, 17 exact controls, 6 mismatches, 6 members in the actual `pending_rule_review` subcluster, and 2/6 evaluable mismatches whose first differing cell is inside the emitted structure. Primary classes are preserved. At that implementation checkpoint, the strict cohort moved from 0 to 17 exact cases; the corpus-wide total moved from 65,491 to 65,514 (+23 exact) because the same PDF-backed spacing rule also applied outside the stricter Korean-boundary audit gate. These are immutable checkpoint counts rather than the report's later cumulative total. The complete standard suite remained 5,141/5,141. -Current tight-triangle measurement: 377 candidates, 0 exact controls, 377 mismatches, 376 members in the actual `pending_rule_review` subcluster, and 13/377 evaluable mismatches whose first difference is inside the `△` plus first-Korean output range. No engine change is inferred. +Current tight-triangle measurement: 377 candidates, 0 exact controls, 377 mismatches, 376 members in the actual `pending_rule_review` subcluster, and 330/377 evaluable mismatches whose first difference is inside the `△` plus first-Korean output range. No engine change is inferred. ## Encoding-error diagnostics diff --git a/libs/braillify/examples/nikl_corpus_analyze.rs b/libs/braillify/examples/nikl_corpus_analyze.rs index 54a4a711..1c8a53a9 100644 --- a/libs/braillify/examples/nikl_corpus_analyze.rs +++ b/libs/braillify/examples/nikl_corpus_analyze.rs @@ -1432,42 +1432,31 @@ fn first_difference_in_inline_parenthesized_operator(item: &EncodedCase) -> bool .any(|range| range.contains(&first_difference)) } -fn tight_triangle_positions(input: &str) -> Vec { +fn tight_triangle_spans(input: &str) -> Vec { input .match_indices('△') .filter_map(|(byte, mark)| { - input[byte + mark.len()..] - .chars() - .next() - .is_some_and(is_korean_script) - .then_some(byte) + let next = input[byte + mark.len()..].chars().next()?; + is_korean_script(next).then_some(InputSpan { + start_byte: byte, + end_byte: byte + mark.len() + next.len_utf8(), + }) }) .collect() } +fn tight_triangle_positions(input: &str) -> Vec { + tight_triangle_spans(input) + .into_iter() + .map(|span| span.start_byte) + .collect() +} + /// Current-engine ranges for `△한글`, including the first Korean cell after /// the mark. A missing reference space therefore differs inside this range, /// while unrelated earlier sentence differences do not count as causal. fn tight_triangle_actual_ranges(input: &str, actual: &str) -> Vec> { - let actual_cells = actual.chars().collect::>(); - let marker = braillify::encode_to_unicode("△") - .expect("triangle probe must encode") - .chars() - .collect::>(); - tight_triangle_positions(input) - .into_iter() - .filter_map(|byte| { - let start = braillify::encode_to_unicode(&input[..byte]) - .ok()? - .chars() - .count(); - let marker_end = start.checked_add(marker.len())?; - let range_end = marker_end.checked_add(1)?; - (actual_cells.get(start..marker_end) == Some(marker.as_slice()) - && actual_cells.get(marker_end).is_some()) - .then_some(start..range_end) - }) - .collect() + korean_context_signature_ranges(input, actual, &tight_triangle_spans(input), 0) } fn first_difference_in_tight_triangle(item: &EncodedCase) -> bool { @@ -2472,7 +2461,11 @@ fn markdown(report: &AnalysisReport) -> String { assigns the same glyph a bullet role but shows a print space after every bullet. A tight \ corpus input does not identify which role was intended, and adding a space absent from \ the input would contradict rule 49 unless independent layout evidence establishes a \ - bullet. Localized mismatches are therefore corpus/layout review evidence only.\n\n\ + bullet. The localizer searches the complete actual output for a neutral-Korean, \ + current-engine signature covering the mark and its first following Korean character; \ + it neither encodes a context-sensitive sentence prefix in isolation nor reads the \ + reference output. Tight marks followed by ASCII letters or digits remain outside this \ + gate. Localized mismatches are therefore corpus/layout review evidence only.\n\n\ Corpus contradictions remain a separate gate: identical inputs with conflicting \ references are classified as `corpus_suspect` before these cohorts are recorded and \ would appear explicitly in each mismatch primary-class distribution. Their absence does \ @@ -3647,9 +3640,11 @@ mod tests { assert_eq!(tight_triangle_positions(input).len(), expected); } - #[test] - fn locates_tight_triangle_and_first_korean_output() { - let input = "△보성군"; + #[rstest::rstest] + #[case::leading("△보성군")] + #[case::embedded("목록 △교과전형")] + #[case::after_roman_context("MOU 협약 뒤 △항목")] + fn locates_tight_triangle_and_first_korean_output(#[case] input: &str) { let actual = braillify::encode_to_unicode(input).expect("probe must encode"); let ranges = tight_triangle_actual_ranges(input, &actual); From 1276841beb549813cda922deec152e01d1470f5d Mon Sep 17 00:00:00 2001 From: devfive Date: Tue, 1 Sep 2026 08:52:24 +0900 Subject: [PATCH 23/77] Audit Roman middle-dot terminator boundaries Add an output-localized cohort for attached ASCII Roman runs before U+00B7. Rules 29 and 33 explain the current terminator, while Rule 50 adds attachment but no terminator exception, so reference omissions remain conservative corpus/PDF review rather than an engine rule. Cohort: 577 candidates, 0 exact, 577 mismatch, 357 localized; 341 localized cases share the dominant terminator transition. Analyzer: 141 tests; report/JSON hashes equal across two runs; clippy clean. NIKL remains 66436/83528. --- docs/corpus-analysis/NIKL_2025_V1.md | 141 +++++++++++++----- .../braillify/examples/nikl_corpus_analyze.rs | 138 +++++++++++++++++ 2 files changed, 244 insertions(+), 35 deletions(-) diff --git a/docs/corpus-analysis/NIKL_2025_V1.md b/docs/corpus-analysis/NIKL_2025_V1.md index 33890f7f..084c75ec 100644 --- a/docs/corpus-analysis/NIKL_2025_V1.md +++ b/docs/corpus-analysis/NIKL_2025_V1.md @@ -300,17 +300,17 @@ This ranking removes only cases whose first difference is inside an existing out |---:|---|---:| | 1 | `U+2830 ⠰ -> U+2820 ⠠` | 1015 | | 2 | `U+2820 ⠠ -> U+2830 ⠰` | 798 | -| 3 | `U+2810 ⠐ -> U+2832 ⠲` | 626 | -| 4 | `U+2834 ⠴ -> U+2800 ⠀` | 544 | -| 5 | `U+2820 ⠠ -> U+2834 ⠴` | 454 | -| 6 | `U+2826 ⠦ -> U+2834 ⠴` | 445 | -| 7 | `U+280E ⠎ -> U+280C ⠌` | 444 | -| 8 | `U+283C ⠼ -> U+2800 ⠀` | 444 | -| 9 | `U+2808 ⠈ -> U+2832 ⠲` | 407 | -| 10 | `U+2826 ⠦ -> U+2800 ⠀` | 388 | -| 11 | `U+2801 ⠁ -> U+281C ⠜` | 386 | -| 12 | `U+2834 ⠴ -> U+2830 ⠰` | 325 | -| 13 | `U+2811 ⠑ -> U+282B ⠫` | 322 | +| 3 | `U+2834 ⠴ -> U+2800 ⠀` | 544 | +| 4 | `U+2820 ⠠ -> U+2834 ⠴` | 454 | +| 5 | `U+2826 ⠦ -> U+2834 ⠴` | 445 | +| 6 | `U+280E ⠎ -> U+280C ⠌` | 444 | +| 7 | `U+283C ⠼ -> U+2800 ⠀` | 444 | +| 8 | `U+2808 ⠈ -> U+2832 ⠲` | 407 | +| 9 | `U+2826 ⠦ -> U+2800 ⠀` | 388 | +| 10 | `U+2801 ⠁ -> U+281C ⠜` | 384 | +| 11 | `U+2834 ⠴ -> U+2830 ⠰` | 325 | +| 12 | `U+2811 ⠑ -> U+282B ⠫` | 321 | +| 13 | `U+2810 ⠐ -> U+2832 ⠲` | 285 | | 14 | `U+2832 ⠲ -> U+2838 ⠸` | 275 | | 15 | `U+2800 ⠀ -> U+2832 ⠲` | 225 | | 16 | `U+2810 ⠐ -> U+2802 ⠂` | 209 | @@ -365,29 +365,6 @@ This ranking removes only cases whose first difference is inside an existing out - first differing cell (zero-based): 63 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -### Residual `U+2810 ⠐ -> U+2832 ⠲` - -- `sentence_01.json` #57: 아키에이지 워는 원작 아키에이지 개발사 엑스엘게임즈(각자대표 송재경, 최관호)가 개발 중인 신작 PC·모바일 크로스플랫폼 대규모다중접속역할수행게임(MMORPG)이다. 원작의 향수를 자극하는 스토리와 캐릭터, 언리얼 엔진4를 활용한 고퀄리티 그래픽이 특징이다. - - expected: `⠨⠁⠀⠴⠠⠠⠏⠉⠐⠆⠑⠥⠘⠣⠕⠂⠀⠋⠪⠐⠥⠠⠪⠙` - - actual: `⠨⠁⠀⠴⠠⠠⠏⠉⠲⠐⠆⠑⠥⠘⠣⠕⠂⠀⠋⠪⠐⠥⠠⠪` - - first differing cell (zero-based): 92 - - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_02.json` #10: 설동호 대전교육감은 “학교 교육과정 및 여건에 맞는 인공지능(AI)교육 선도학교 운영을 통해 AI·SW교육 기반 구축과 학생들과 교원들의 미래 디지털 역량이 함양될 것이다.”라고 말했다. - - expected: `⠚⠗⠀⠴⠠⠠⠁⠊⠐⠆⠴⠠⠠⠎⠺⠲⠈⠬⠩⠁⠀⠈⠕⠘` - - actual: `⠚⠗⠀⠴⠠⠠⠁⠊⠲⠐⠆⠴⠎⠺⠲⠈⠬⠩⠁⠀⠈⠕⠘⠒` - - first differing cell (zero-based): 93 - - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_03.json` #17: 재테크 시장에서 온·오프라인 플랫폼 ‘매경 엠플러스(M+)’가 주목받고 있다. M+는 거시경제, 주식, 부동산, 가상자산 등 재테크의 모든 것을 온라인 사이트를 통해 제공하고 있다. - - expected: `⠎⠠⠪⠦⠄⠴⠠⠍⠐⠖⠠⠴⠴⠄⠫⠀⠨⠍⠑⠭⠘⠔⠈⠥` - - actual: `⠎⠠⠪⠦⠄⠴⠠⠍⠲⠀⠢⠀⠠⠴⠴⠄⠫⠀⠨⠍⠑⠭⠘⠔` - - first differing cell (zero-based): 54 - - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_04.json` #49: 유럽중앙은행(ECB)이 크레디트스위스(CS)의 유동성 위기에도 ‘빅스텝’(기준금리를 한 번에 0.5%포인트 인상)을 단행하자, 미국 연방준비제도(Fed·연준) 역시 금리 인상을 멈추지 않을 것이라는 전망이 힘을 얻고 있다. - - expected: `⠊⠥⠦⠄⠴⠠⠋⠫⠐⠆⠡⠨⠛⠠⠴⠀⠱⠁⠠⠕⠀⠈⠪⠢` - - actual: `⠊⠥⠦⠄⠴⠠⠋⠫⠲⠐⠆⠡⠨⠛⠠⠴⠀⠱⠁⠠⠕⠀⠈⠪` - - first differing cell (zero-based): 149 - - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` - ### Residual `U+2834 ⠴ -> U+2800 ⠀` - `sentence_01.json` #35: 삼성전자는 올해 CES에서 77인치 OLED TV를 처음으로 공개할 예정이다. 이는 지난 8월 ‘국제정보디스플레이학술대회(IMID) 2022’에서 삼성디스플레이가 공개한 77인치 퀀텀닷(QD)-OLED 패널을 탑재한 제품이다. @@ -549,9 +526,32 @@ This ranking removes only cases whose first difference is inside an existing out - first differing cell (zero-based): 93 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +### Residual `U+2801 ⠁ -> U+281C ⠜` + +- `sentence_01.json` #142: ‘메타버스존’에서는 증강현실(AR)·가상현실(VR)에 적용되는 혁신기술이 소개된다. 글라스를 착용하고 최첨단 3D 센싱모듈이 구현하는 가상현실을 관람객이 직접 체험할 수 있도록 했다. + - expected: `⠠⠕⠂⠦⠄⠴⠠⠠⠁⠗⠠⠴⠐⠆⠫⠇⠶⠚⠡⠠⠕⠂⠦⠄` + - actual: `⠠⠕⠂⠦⠄⠴⠠⠠⠜⠠⠴⠐⠆⠸⠷⠫⠇⠶⠚⠡⠠⠕⠂⠸` + - first differing cell (zero-based): 34 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_02.json` #28: 체납된 세금은 전국 어디서나 은행 현금자동인출기(ATM)를 이용해 고지서 없이 현금과 신용카드로 납부가 가능하며, 자동응답시스템(ARS) 지방세 납부서비스(044-300-7114)를 이용해 가상계좌를 확인해 계좌이체를 하거나 신용카드로 납부할 수도 있다. + - expected: `⠓⠝⠢⠦⠄⠴⠠⠠⠁⠗⠎⠠⠴⠀⠨⠕⠘⠶⠠⠝⠀⠉⠃⠘` + - actual: `⠓⠝⠢⠦⠄⠴⠠⠠⠜⠎⠠⠴⠀⠨⠕⠘⠶⠠⠝⠀⠉⠃⠘⠍` + - first differing cell (zero-based): 129 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_03.json` #52: 종합식품기업 하림이 공룡 모양으로 생긴 동그랑땡 ‘용가리 땡’을 20일 출시했다. 용가리땡에는 특별 제작한 증강현실(AR) 공룡 카드까지 한 장씩 들어 있어 아이들의 많은 관심이 예상된다. + - expected: `⠠⠕⠂⠦⠄⠴⠠⠠⠁⠗⠠⠴⠀⠈⠿⠐⠬⠶⠀⠋⠊⠪⠠⠫` + - actual: `⠠⠕⠂⠦⠄⠴⠠⠠⠜⠠⠴⠀⠈⠿⠐⠬⠶⠀⠋⠊⠪⠠⠫⠨` + - first differing cell (zero-based): 126 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_04.json` #187: 주정차단속 ARS 알림서비스는 기존 불법 주정차 문자 알림서비스를 업그레이드 해 문자와 함께 자동응답서비스(ARS)로도 주정차단속을 알려주는 시스템이다. + - expected: `⠊⠒⠠⠭⠀⠴⠠⠠⠁⠗⠎⠲⠀⠣⠂⠐⠕⠢⠠⠎⠘⠕⠠⠪` + - actual: `⠊⠒⠠⠭⠀⠴⠠⠠⠜⠎⠲⠀⠣⠂⠐⠕⠢⠠⠎⠘⠕⠠⠪⠉` + - first differing cell (zero-based): 14 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` + ## Cross-cutting input-only structural cohorts -These are cross-cutting input-only structural cohorts, not new primary classes and not engine routing rules. Candidate selection never changes a case's existing primary class by itself. Only cohort members already classified as `pending_rule_review` form a pending subcluster; exact and other-primary members are controls that retain their existing outcomes. A separate classifier may use independently justified, output-localized PDF evidence, as in the rule-34 three-cell contradiction below. The `uppercase_roman_headword_closed_multiword_parenthetical` gate requires a two-or-more character uppercase ASCII headword immediately followed by a closed parenthesis whose contents are two or more ASCII Roman words separated only by spaces. Because the contents admit only letters and spaces, visible operators, subscript/superscript notation, and nested parentheses are excluded deterministically. The `mixed_roman_korean_word_before_uppercase_headword_expansion` gate further requires a whitespace-delimited preceding word that contains both ASCII Roman and Korean and ends in Korean. It locates the following uppercase headword itself, including its immediately preceding emitted cell, so an earlier Roman entry in the same sentence cannot satisfy the output audit. The `single_capital_followed_by_parenthesized_digits` gate requires a standalone capital, a non-empty closed ASCII-digit parenthetical, and alphanumeric outer boundaries. It likewise includes the emitted entry-boundary cell in localization. The `uppercase_roman_run_followed_by_hyphen_digits` gate requires a maximal uppercase ASCII run, literal ASCII hyphen-minus, a non-empty digit run, and alphanumeric outer boundaries. Its localized range includes the current encoded run and its immediately preceding cell, keeping `F-35`-style routing separate from both parenthesized digits and headword expansions. The `standalone_multi_character_uppercase_roman_word` gate finds maximal ASCII-letter runs of two or more capitals with non-alphanumeric boundaries; a run immediately followed by `(` is excluded so the HCA-style headword itself is not counted by both gates. The `korean_prefixed_closed_allcaps_parenthetical` gate requires an immediately preceding Korean character and a closed body of two or more uppercase ASCII letters. It intentionally contains both acronym annotations (`책임자(COO)`) and scientific formulae (`일산화탄소(CO)`) so their semantic collision remains measurable. The `korean_prefixed_closed_roman_annotation_rule_34_order` gate accepts the narrower rule-34 body grammar after an immediately preceding Korean character and localizes only the current engine's Korean opening-parenthesis cells. This distinguishes the PDF's parenthesis-before-Roman-indicator order from unrelated differences later in the same sentence. The `multi_character_allcaps_roman_runs_joined_by_middle_dot` gate requires two maximal ASCII-letter runs of at least two capitals joined directly by U+00B7, with non-alphanumeric outer boundaries. It records shapes such as `AI·SW` without assigning prose, mathematics, or science semantics. The `korean_inline_parenthesized_single_arithmetic_operator` gate requires an immediate `Korean(` + one rule-45 arithmetic operator + `)Korean` span. Unlike broad coexistence traits, it also locates the current engine's emitted structure and counts a mismatch as signature-local only when the sentence's first differing cell falls inside that output range. The `allcaps_roman_run_containing_ou` gate finds maximal, alphanumeric-delimited uppercase ASCII runs containing adjacent `OU`. It locates the independently encoded run signature in the complete current output and counts only first differences inside that signature as localized. The `decimal_point_between_ascii_digits` gate finds whitespace-delimited words containing `digit.digit` and reproduces each whole word in a neutral Korean context, so suffixes and punctuation remain part of the current-engine signature. The `compact_numeric_ascii_letter_suffix` gate finds a numeric prefix immediately followed by ASCII letters and retains suffix-specific outcome counts. It intentionally includes both possible rule-69 units and ambiguous variable/identifier forms; membership alone does not assign unit semantics. The `rule69_ascii_unit_before_terminator_skipping_symbol` gate is narrower: it accepts only ASCII unit spellings already supported by rule 69, includes the immediately following rule-33/34 punctuation cell in the localized signature, and does not infer new units. The `tight_triangle_mark_immediately_before_korean` gate requires literal `△한글` with no input space and includes the first following Korean cell in its localized output range, so an observed missing-space difference is measured at the mark boundary. +These are cross-cutting input-only structural cohorts, not new primary classes and not engine routing rules. Candidate selection never changes a case's existing primary class by itself. Only cohort members already classified as `pending_rule_review` form a pending subcluster; exact and other-primary members are controls that retain their existing outcomes. A separate classifier may use independently justified, output-localized PDF evidence, as in the rule-34 three-cell contradiction below. The `uppercase_roman_headword_closed_multiword_parenthetical` gate requires a two-or-more character uppercase ASCII headword immediately followed by a closed parenthesis whose contents are two or more ASCII Roman words separated only by spaces. Because the contents admit only letters and spaces, visible operators, subscript/superscript notation, and nested parentheses are excluded deterministically. The `mixed_roman_korean_word_before_uppercase_headword_expansion` gate further requires a whitespace-delimited preceding word that contains both ASCII Roman and Korean and ends in Korean. It locates the following uppercase headword itself, including its immediately preceding emitted cell, so an earlier Roman entry in the same sentence cannot satisfy the output audit. The `single_capital_followed_by_parenthesized_digits` gate requires a standalone capital, a non-empty closed ASCII-digit parenthetical, and alphanumeric outer boundaries. It likewise includes the emitted entry-boundary cell in localization. The `uppercase_roman_run_followed_by_hyphen_digits` gate requires a maximal uppercase ASCII run, literal ASCII hyphen-minus, a non-empty digit run, and alphanumeric outer boundaries. Its localized range includes the current encoded run and its immediately preceding cell, keeping `F-35`-style routing separate from both parenthesized digits and headword expansions. The `standalone_multi_character_uppercase_roman_word` gate finds maximal ASCII-letter runs of two or more capitals with non-alphanumeric boundaries; a run immediately followed by `(` is excluded so the HCA-style headword itself is not counted by both gates. The `korean_prefixed_closed_allcaps_parenthetical` gate requires an immediately preceding Korean character and a closed body of two or more uppercase ASCII letters. It intentionally contains both acronym annotations (`책임자(COO)`) and scientific formulae (`일산화탄소(CO)`) so their semantic collision remains measurable. The `korean_prefixed_closed_roman_annotation_rule_34_order` gate accepts the narrower rule-34 body grammar after an immediately preceding Korean character and localizes only the current engine's Korean opening-parenthesis cells. This distinguishes the PDF's parenthesis-before-Roman-indicator order from unrelated differences later in the same sentence. The `multi_character_allcaps_roman_runs_joined_by_middle_dot` gate requires two maximal ASCII-letter runs of at least two capitals joined directly by U+00B7, with non-alphanumeric outer boundaries. It records shapes such as `AI·SW` without assigning prose, mathematics, or science semantics. The `roman_run_immediately_before_attached_middle_dot_boundary` gate is output-localized: it requires a maximal ASCII-letter run immediately before U+00B7 and an attached Korean character or ASCII-letter run after it, then searches for that whole current-engine signature in the actual output. It therefore isolates the Roman terminator boundary without treating unrelated middle dots elsewhere in the sentence as causal. The `korean_inline_parenthesized_single_arithmetic_operator` gate requires an immediate `Korean(` + one rule-45 arithmetic operator + `)Korean` span. Unlike broad coexistence traits, it also locates the current engine's emitted structure and counts a mismatch as signature-local only when the sentence's first differing cell falls inside that output range. The `allcaps_roman_run_containing_ou` gate finds maximal, alphanumeric-delimited uppercase ASCII runs containing adjacent `OU`. It locates the independently encoded run signature in the complete current output and counts only first differences inside that signature as localized. The `decimal_point_between_ascii_digits` gate finds whitespace-delimited words containing `digit.digit` and reproduces each whole word in a neutral Korean context, so suffixes and punctuation remain part of the current-engine signature. The `compact_numeric_ascii_letter_suffix` gate finds a numeric prefix immediately followed by ASCII letters and retains suffix-specific outcome counts. It intentionally includes both possible rule-69 units and ambiguous variable/identifier forms; membership alone does not assign unit semantics. The `rule69_ascii_unit_before_terminator_skipping_symbol` gate is narrower: it accepts only ASCII unit spellings already supported by rule 69, includes the immediately following rule-33/34 punctuation cell in the localized signature, and does not infer new units. The `tight_triangle_mark_immediately_before_korean` gate requires literal `△한글` with no input space and includes the first following Korean cell in its localized output range, so an observed missing-space difference is measured at the mark boundary. | Cluster | Candidates | Exact | Mismatch | Conflicting-reference cases | |---|---:|---:|---:|---:| @@ -563,6 +563,7 @@ These are cross-cutting input-only structural cohorts, not new primary classes a | `korean_prefixed_closed_roman_annotation_rule_34_order` | 64382 | 51305 | 13077 | 0 | | `mixed_roman_korean_word_before_uppercase_headword_expansion` | 10 | 0 | 10 | 0 | | `multi_character_allcaps_roman_runs_joined_by_middle_dot` | 97 | 0 | 97 | 0 | +| `roman_run_immediately_before_attached_middle_dot_boundary` | 577 | 0 | 577 | 0 | | `rule69_ascii_unit_before_terminator_skipping_symbol` | 440 | 325 | 115 | 0 | | `single_capital_followed_by_parenthesized_digits` | 1361 | 0 | 1361 | 0 | | `standalone_multi_character_uppercase_roman_word` | 62411 | 49035 | 13376 | 0 | @@ -1044,6 +1045,72 @@ Representative `mismatch` samples: - first differing cell (zero-based): 130 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +### `roman_run_immediately_before_attached_middle_dot_boundary` + +Of the 577 candidates, 565 are the actual `pending_rule_review` subcluster. The other 12 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. + +For this output-signature audit, 577 mismatches were evaluable and 357 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. + +Localized first-difference transitions: + +- `U+2810 ⠐ -> U+2832 ⠲`: 341 +- `U+2801 ⠁ -> U+2820 ⠠`: 5 +- `U+2803 ⠃ -> U+2820 ⠠`: 4 +- `U+2801 ⠁ -> U+281C ⠜`: 2 +- `U+2815 ⠕ -> U+2820 ⠠`: 2 + +Mismatch primary-class distribution: + +- `corpus_suspect`: 7 +- `pending_rule_review`: 565 +- `unsupported_character_review`: 5 + +Representative `localized_mismatch` samples: + +- `sentence_01.json` #57: 아키에이지 워는 원작 아키에이지 개발사 엑스엘게임즈(각자대표 송재경, 최관호)가 개발 중인 신작 PC·모바일 크로스플랫폼 대규모다중접속역할수행게임(MMORPG)이다. 원작의 향수를 자극하는 스토리와 캐릭터, 언리얼 엔진4를 활용한 고퀄리티 그래픽이 특징이다. + - expected: `⠨⠁⠀⠴⠠⠠⠏⠉⠐⠆⠑⠥⠘⠣⠕⠂⠀⠋⠪⠐⠥⠠⠪⠙` + - actual: `⠨⠁⠀⠴⠠⠠⠏⠉⠲⠐⠆⠑⠥⠘⠣⠕⠂⠀⠋⠪⠐⠥⠠⠪` + - first differing cell (zero-based): 92 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_02.json` #10: 설동호 대전교육감은 “학교 교육과정 및 여건에 맞는 인공지능(AI)교육 선도학교 운영을 통해 AI·SW교육 기반 구축과 학생들과 교원들의 미래 디지털 역량이 함양될 것이다.”라고 말했다. + - expected: `⠚⠗⠀⠴⠠⠠⠁⠊⠐⠆⠴⠠⠠⠎⠺⠲⠈⠬⠩⠁⠀⠈⠕⠘` + - actual: `⠚⠗⠀⠴⠠⠠⠁⠊⠲⠐⠆⠴⠎⠺⠲⠈⠬⠩⠁⠀⠈⠕⠘⠒` + - first differing cell (zero-based): 93 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_03.json` #91: 지난 13일 브라질에서 우수제조관리기준(BGMP) 인증을 받으며 중남미시장 진출에도 첫발을 내디뎠다. BGMP 인증으로 HA·Ca필러가 브라질을 비롯한 남미 국가들에 진입하는 데 속도가 붙을 것이란 전망이다. + - expected: `⠐⠥⠀⠴⠠⠠⠓⠁⠐⠆⠴⠠⠉⠁⠲⠙⠕⠂⠐⠎⠫⠀⠘⠪` + - actual: `⠐⠥⠀⠴⠠⠠⠓⠁⠲⠐⠆⠴⠠⠉⠁⠲⠙⠕⠂⠐⠎⠫⠀⠘` + - first differing cell (zero-based): 121 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_04.json` #49: 유럽중앙은행(ECB)이 크레디트스위스(CS)의 유동성 위기에도 ‘빅스텝’(기준금리를 한 번에 0.5%포인트 인상)을 단행하자, 미국 연방준비제도(Fed·연준) 역시 금리 인상을 멈추지 않을 것이라는 전망이 힘을 얻고 있다. + - expected: `⠊⠥⠦⠄⠴⠠⠋⠫⠐⠆⠡⠨⠛⠠⠴⠀⠱⠁⠠⠕⠀⠈⠪⠢` + - actual: `⠊⠥⠦⠄⠴⠠⠋⠫⠲⠐⠆⠡⠨⠛⠠⠴⠀⠱⠁⠠⠕⠀⠈⠪` + - first differing cell (zero-based): 149 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` + +Representative `mismatch` samples: + +- `sentence_01.json` #57: 아키에이지 워는 원작 아키에이지 개발사 엑스엘게임즈(각자대표 송재경, 최관호)가 개발 중인 신작 PC·모바일 크로스플랫폼 대규모다중접속역할수행게임(MMORPG)이다. 원작의 향수를 자극하는 스토리와 캐릭터, 언리얼 엔진4를 활용한 고퀄리티 그래픽이 특징이다. + - expected: `⠨⠁⠀⠴⠠⠠⠏⠉⠐⠆⠑⠥⠘⠣⠕⠂⠀⠋⠪⠐⠥⠠⠪⠙` + - actual: `⠨⠁⠀⠴⠠⠠⠏⠉⠲⠐⠆⠑⠥⠘⠣⠕⠂⠀⠋⠪⠐⠥⠠⠪` + - first differing cell (zero-based): 92 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_02.json` #10: 설동호 대전교육감은 “학교 교육과정 및 여건에 맞는 인공지능(AI)교육 선도학교 운영을 통해 AI·SW교육 기반 구축과 학생들과 교원들의 미래 디지털 역량이 함양될 것이다.”라고 말했다. + - expected: `⠚⠗⠀⠴⠠⠠⠁⠊⠐⠆⠴⠠⠠⠎⠺⠲⠈⠬⠩⠁⠀⠈⠕⠘` + - actual: `⠚⠗⠀⠴⠠⠠⠁⠊⠲⠐⠆⠴⠎⠺⠲⠈⠬⠩⠁⠀⠈⠕⠘⠒` + - first differing cell (zero-based): 93 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_03.json` #91: 지난 13일 브라질에서 우수제조관리기준(BGMP) 인증을 받으며 중남미시장 진출에도 첫발을 내디뎠다. BGMP 인증으로 HA·Ca필러가 브라질을 비롯한 남미 국가들에 진입하는 데 속도가 붙을 것이란 전망이다. + - expected: `⠐⠥⠀⠴⠠⠠⠓⠁⠐⠆⠴⠠⠉⠁⠲⠙⠕⠂⠐⠎⠫⠀⠘⠪` + - actual: `⠐⠥⠀⠴⠠⠠⠓⠁⠲⠐⠆⠴⠠⠉⠁⠲⠙⠕⠂⠐⠎⠫⠀⠘` + - first differing cell (zero-based): 121 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_04.json` #49: 유럽중앙은행(ECB)이 크레디트스위스(CS)의 유동성 위기에도 ‘빅스텝’(기준금리를 한 번에 0.5%포인트 인상)을 단행하자, 미국 연방준비제도(Fed·연준) 역시 금리 인상을 멈추지 않을 것이라는 전망이 힘을 얻고 있다. + - expected: `⠊⠥⠦⠄⠴⠠⠋⠫⠐⠆⠡⠨⠛⠠⠴⠀⠱⠁⠠⠕⠀⠈⠪⠢` + - actual: `⠊⠥⠦⠄⠴⠠⠋⠫⠲⠐⠆⠡⠨⠛⠠⠴⠀⠱⠁⠠⠕⠀⠈⠪` + - first differing cell (zero-based): 149 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` + ### `rule69_ascii_unit_before_terminator_skipping_symbol` Of the 440 candidates, 110 are the actual `pending_rule_review` subcluster. The other 330 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. @@ -1384,6 +1451,8 @@ The all-caps `OU` cohort isolates a frequent output transition without treating The all-caps Roman middle-dot cohort is also semantically underdetermined. Hangeul rule 29 defines Roman indicators around Roman text in a Korean sentence, and Hangeul rule 50 requires U+00B7 to be attached on both sides, but neither rule says that the punctuation joins the adjacent Roman runs into one Roman span. Math rule 2 separately defines the same printed dot as multiplication, and science rule 4 uses it inside chemical formulae. An input-only `AI·SW` gate therefore cannot prove which mode transition is required. Exact cases remain controls, mismatches retain their existing primary class, and no engine rule is inferred from their references. Representative samples are sentence-level evidence: when the reported first difference precedes the detected middle-dot span, the cohort must not be treated as the cause of that mismatch. +The narrower Roman-before-middle-dot boundary cohort separates that semantic question from a checkable indicator boundary. Hangeul rule 29 requires a Roman terminator after Roman text. Rule 33 enumerates the punctuation that suppresses or moves that terminator, but does not include U+00B7; rule 50 requires the middle dot to be attached on both sides and does not state a Roman-terminator exception. Thus a localized reference that omits the terminator conflicts with the current rule-29/33 path on the available PDF text. This is conservative corpus/PDF-reference review evidence, not permission to remove the terminator or to reclassify non-localized cases. + The inline parenthesized-operator cohort has an independently checkable spacing boundary. Hangeul rule 46 inserts spaces only when an operation or comparison sign is between Korean text, while the literal parentheses intervene in this gate. Hangeul rule 49 says punctuation spacing follows the print, and science rule 21 prints and brailles `(-)` and `(+)` with no spaces inside the parentheses. The output-signature count is therefore the implementation-candidate subset; mere sentence-level coexistence is retained only as a control. ASCII hyphen-minus is independently supported through the `Symbol`/rule-49 punctuation path, while `+`, `×`, `÷`, and `=` reach the `MathSymbol` spacing rule; end-to-end tests preserve tight parentheses on both paths. Analyzer checkpoint `30a9b10` recorded the pre-fix baseline as 23 candidates, 0 exact, 23 mismatches, and 19 mismatches whose first difference was signature-local. The generalized rule-46/49 fix is evaluated below against that immutable baseline rather than inferred from a reference string. The tight-triangle cohort is not an implementation premise. Hangeul rule 49 assigns `△` the omission-mark role and requires print spacing to be followed, while rule 72 also assigns the same glyph a bullet role but shows a print space after every bullet. A tight corpus input does not identify which role was intended, and adding a space absent from the input would contradict rule 49 unless independent layout evidence establishes a bullet. The localizer searches the complete actual output for a neutral-Korean, current-engine signature covering the mark and its first following Korean character; it neither encodes a context-sensitive sentence prefix in isolation nor reads the reference output. Tight marks followed by ASCII letters or digits remain outside this gate. Localized mismatches are therefore corpus/layout review evidence only. @@ -1442,6 +1511,8 @@ Current rule-34 opening-order measurement: 64382 structural candidates, 51305 ex Current all-caps Roman middle-dot measurement: 97 candidates, 0 exact controls, 97 mismatches, and 96 members in the actual `pending_rule_review` subcluster. This cross-cutting cohort preserves every primary class and is not an engine routing rule. +Current Roman-before-middle-dot boundary measurement: 577 candidates, 0 exact controls, 577 mismatches, 565 members in the actual `pending_rule_review` subcluster, and 357/577 evaluable mismatches whose first difference is localized to the attached Roman/middle-dot output signature. Rules 29, 33, and 50 support the current terminator path but do not support the localized reference omission; no engine change or primary-class rewrite is inferred. + Current inline parenthesized-operator measurement: 23 candidates, 17 exact controls, 6 mismatches, 6 members in the actual `pending_rule_review` subcluster, and 2/6 evaluable mismatches whose first differing cell is inside the emitted structure. Primary classes are preserved. At that implementation checkpoint, the strict cohort moved from 0 to 17 exact cases; the corpus-wide total moved from 65,491 to 65,514 (+23 exact) because the same PDF-backed spacing rule also applied outside the stricter Korean-boundary audit gate. These are immutable checkpoint counts rather than the report's later cumulative total. The complete standard suite remained 5,141/5,141. diff --git a/libs/braillify/examples/nikl_corpus_analyze.rs b/libs/braillify/examples/nikl_corpus_analyze.rs index 1c8a53a9..3db76382 100644 --- a/libs/braillify/examples/nikl_corpus_analyze.rs +++ b/libs/braillify/examples/nikl_corpus_analyze.rs @@ -572,6 +572,8 @@ const KOREAN_PREFIXED_CLOSED_ROMAN_ANNOTATION: &str = "korean_prefixed_closed_roman_annotation_rule_34_order"; const ALLCAPS_ROMAN_MIDDLE_DOT_RUNS: &str = "multi_character_allcaps_roman_runs_joined_by_middle_dot"; +const ROMAN_RUN_BEFORE_MIDDLE_DOT_BOUNDARY: &str = + "roman_run_immediately_before_attached_middle_dot_boundary"; const KOREAN_INLINE_PARENTHESIZED_OPERATOR: &str = "korean_inline_parenthesized_single_arithmetic_operator"; const TIGHT_TRIANGLE_BEFORE_KOREAN: &str = "tight_triangle_mark_immediately_before_korean"; @@ -1147,6 +1149,7 @@ fn first_difference_claimed_by_localized_cohort(item: &EncodedCase) -> bool { || first_difference_in_korean_prefixed_annotation_opening(item) || first_difference_in_inline_parenthesized_operator(item) || first_difference_in_tight_triangle(item) + || first_difference_at_roman_middle_dot_boundary(item) || first_difference_in_signature_spans( item, &single_capital_parenthesized_digit_spans(&item.located.case.input), @@ -1340,6 +1343,58 @@ fn has_allcaps_roman_middle_dot_runs(input: &str) -> bool { false } +/// Finds an ASCII-letter run followed immediately by U+00B7 and either the +/// first following Korean character or the complete following ASCII-letter +/// run. The span is syntactic only: it does not infer punctuation, product-name, +/// or mathematical semantics from the middle dot. +fn roman_run_before_middle_dot_boundary_spans(input: &str) -> Vec { + let bytes = input.as_bytes(); + let mut spans = Vec::new(); + for (middle_dot, mark) in input.match_indices('·') { + let mut left_start = middle_dot; + while left_start > 0 && bytes[left_start - 1].is_ascii_alphabetic() { + left_start -= 1; + } + if left_start == middle_dot + || input[..left_start] + .chars() + .next_back() + .is_some_and(|ch| ch.is_ascii_alphanumeric()) + { + continue; + } + + let right_start = middle_dot + mark.len(); + let Some(first_right) = input[right_start..].chars().next() else { + continue; + }; + let right_end = if first_right.is_ascii_alphabetic() { + let mut end = right_start; + while end < bytes.len() && bytes[end].is_ascii_alphabetic() { + end += 1; + } + end + } else if is_korean_script(first_right) { + right_start + first_right.len_utf8() + } else { + continue; + }; + spans.push(InputSpan { + start_byte: left_start, + end_byte: right_end, + }); + } + spans +} + +fn first_difference_at_roman_middle_dot_boundary(item: &EncodedCase) -> bool { + first_difference_in_korean_context_signature_spans( + item, + &roman_run_before_middle_dot_boundary_spans(&item.located.case.input), + 0, + ) +} + #[derive(Clone, Copy, Debug, PartialEq, Eq)] struct InlineParenthesizedOperator { open_byte: usize, @@ -1715,6 +1770,10 @@ fn analyze( ALLCAPS_ROMAN_MIDDLE_DOT_RUNS.to_string(), PendingRuleReviewClusterStats::default(), ), + ( + ROMAN_RUN_BEFORE_MIDDLE_DOT_BOUNDARY.to_string(), + PendingRuleReviewClusterStats::default(), + ), ( KOREAN_PREFIXED_ALLCAPS_PARENTHETICAL.to_string(), PendingRuleReviewClusterStats::default(), @@ -1845,6 +1904,12 @@ fn analyze( None, false, ), + ( + ROMAN_RUN_BEFORE_MIDDLE_DOT_BOUNDARY, + !roman_run_before_middle_dot_boundary_spans(&item.located.case.input).is_empty(), + Some(first_difference_at_roman_middle_dot_boundary(item)), + true, + ), ( KOREAN_PREFIXED_ALLCAPS_PARENTHETICAL, has_korean_prefixed_allcaps_parenthetical(&item.located.case.input), @@ -2274,6 +2339,12 @@ fn markdown(report: &AnalysisReport) -> String { ASCII-letter runs of at least two capitals joined directly by U+00B7, with \ non-alphanumeric outer boundaries. It records shapes such as `AI·SW` without assigning \ prose, mathematics, or science semantics. The \ + `roman_run_immediately_before_attached_middle_dot_boundary` gate is output-localized: \ + it requires a maximal ASCII-letter run immediately before U+00B7 and an attached \ + Korean character or ASCII-letter run after it, then searches for that whole \ + current-engine signature in the actual output. It therefore isolates the Roman \ + terminator boundary without treating unrelated middle dots elsewhere in the sentence \ + as causal. The \ `korean_inline_parenthesized_single_arithmetic_operator` gate requires an immediate \ `Korean(` + one rule-45 arithmetic operator + `)Korean` span. Unlike broad coexistence \ traits, it also locates the current engine's emitted structure and counts a mismatch as \ @@ -2443,6 +2514,14 @@ fn markdown(report: &AnalysisReport) -> String { and no engine rule is inferred from their references. Representative samples are \ sentence-level evidence: when the reported first difference precedes the detected \ middle-dot span, the cohort must not be treated as the cause of that mismatch.\n\n\ + The narrower Roman-before-middle-dot boundary cohort separates that semantic question \ + from a checkable indicator boundary. Hangeul rule 29 requires a Roman terminator after \ + Roman text. Rule 33 enumerates the punctuation that suppresses or moves that terminator, \ + but does not include U+00B7; rule 50 requires the middle dot to be attached on both sides \ + and does not state a Roman-terminator exception. Thus a localized reference that omits \ + the terminator conflicts with the current rule-29/33 path on the available PDF text. \ + This is conservative corpus/PDF-reference review evidence, not permission to remove the \ + terminator or to reclassify non-localized cases.\n\n\ The inline parenthesized-operator cohort has an independently checkable spacing boundary. \ Hangeul rule 46 inserts spaces only when an operation or comparison sign is between \ Korean text, while the literal parentheses intervene in this gate. Hangeul rule 49 says \ @@ -2759,6 +2838,29 @@ fn markdown(report: &AnalysisReport) -> String { stats.candidates, stats.exact, stats.mismatch )); } + if let Some(stats) = report + .pending_rule_review_clusters + .get(ROMAN_RUN_BEFORE_MIDDLE_DOT_BOUNDARY) + { + let pending = stats + .mismatch_primary_classes + .get("pending_rule_review") + .copied() + .unwrap_or(0); + text.push_str(&format!( + "\nCurrent Roman-before-middle-dot boundary measurement: {} candidates, {} exact \ + controls, {} mismatches, {pending} members in the actual `pending_rule_review` \ + subcluster, and {}/{} evaluable mismatches whose first difference is localized to \ + the attached Roman/middle-dot output signature. Rules 29, 33, and 50 support the \ + current terminator path but do not support the localized reference omission; no \ + engine change or primary-class rewrite is inferred.\n", + stats.candidates, + stats.exact, + stats.mismatch, + stats.first_difference_in_output_signature, + stats.output_signature_mismatches_evaluated + )); + } if let Some(stats) = report .pending_rule_review_clusters .get(KOREAN_INLINE_PARENTHESIZED_OPERATOR) @@ -3597,6 +3699,42 @@ mod tests { assert_eq!(has_allcaps_roman_middle_dot_runs(input), expected); } + #[rstest::rstest] + #[case::roman_korean("신작 PC·모바일", vec!["PC·모"])] + #[case::roman_roman("AI·SW교육", vec!["AI·SW"])] + #[case::mixed_case("기관(Fed·연준)", vec!["Fed·연"])] + #[case::korean_only("온·오프라인", vec![])] + #[case::numeric("3·1 운동", vec![])] + #[case::spaced("AI · SW", vec![])] + fn detects_roman_run_before_middle_dot_boundary( + #[case] input: &str, + #[case] expected: Vec<&str>, + ) { + let actual = roman_run_before_middle_dot_boundary_spans(input) + .into_iter() + .map(|span| &input[span.start_byte..span.end_byte]) + .collect::>(); + assert_eq!(actual, expected); + } + + #[rstest::rstest] + #[case::roman_korean("신작 PC·모바일")] + #[case::roman_roman("AI·SW교육")] + #[case::mixed_case("기관(Fed·연준)")] + fn localizes_roman_middle_dot_boundary_in_complete_output(#[case] input: &str) { + let actual = braillify::encode_to_unicode(input).expect("probe must encode"); + let ranges = korean_context_signature_ranges( + input, + &actual, + &roman_run_before_middle_dot_boundary_spans(input), + 0, + ); + + assert_eq!(ranges.len(), 1); + assert!(ranges[0].start < ranges[0].end); + assert!(ranges[0].end <= actual.chars().count()); + } + #[rstest::rstest] #[case::plus("양(+)극", vec!['+'])] #[case::hyphen_minus("음(-)극", vec!['-'])] From 2009d1b4a235fdd2753b3bce45469549932339cd Mon Sep 17 00:00:00 2001 From: devfive Date: Tue, 1 Sep 2026 09:39:17 +0900 Subject: [PATCH 24/77] Derive compact ASCII unit spellings safely --- docs/corpus-analysis/NIKL_2025_V1.md | 371 ++++++++++-------- .../braillify/examples/nikl_corpus_analyze.rs | 65 ++- libs/braillify/src/rules/korean/rule_68.rs | 16 +- libs/braillify/src/rules/korean/rule_69.rs | 219 ++++++++++- .../token_rules/math_expression/detect.rs | 5 + 5 files changed, 475 insertions(+), 201 deletions(-) diff --git a/docs/corpus-analysis/NIKL_2025_V1.md b/docs/corpus-analysis/NIKL_2025_V1.md index 084c75ec..0f59320e 100644 --- a/docs/corpus-analysis/NIKL_2025_V1.md +++ b/docs/corpus-analysis/NIKL_2025_V1.md @@ -7,9 +7,9 @@ | Metric | Count | |---|---:| | Total | 83528 | -| Exact | 66436 | -| Mismatch | 17092 | -| Exact accuracy | 79.54% | +| Exact | 66546 | +| Mismatch | 16982 | +| Exact accuracy | 79.67% | | Duplicate records | 0 | | Inputs with conflicting references | 0 | @@ -19,20 +19,20 @@ Primary classes are evidence gates, not permissions to change the engine. `imple | Primary class | Count | |---|---:| -| `comparison_method` | 283 | -| `corpus_suspect` | 1093 | -| `exact` | 66436 | -| `pending_rule_review` | 15531 | +| `comparison_method` | 284 | +| `corpus_suspect` | 1094 | +| `exact` | 66546 | +| `pending_rule_review` | 15419 | | `unsupported_character_review` | 185 | | Reproducible reason | Count | |---|---:| -| `exact` | 66436 | -| `foreign_text_rule_review` | 15356 | -| `nfkc_input_equivalent` | 283 | +| `exact` | 66546 | +| `foreign_text_rule_review` | 15244 | +| `nfkc_input_equivalent` | 284 | | `number_rule_review` | 167 | | `punctuation_rule_review` | 8 | -| `rule34_roman_indicator_before_opening_parenthesis` | 1093 | +| `rule34_roman_indicator_before_opening_parenthesis` | 1094 | | `unsupported_character_review` | 185 | ## Pending first-difference cell transitions @@ -41,26 +41,26 @@ This ranking is a diagnostic selector, not an implementation rule. It counts onl | Rank | Expected → actual first cell | Cases | |---:|---|---:| -| 1 | `U+2834 ⠴ -> U+2800 ⠀` | 2140 | +| 1 | `U+2834 ⠴ -> U+2800 ⠀` | 2142 | | 2 | `U+2815 ⠕ -> U+2833 ⠳` | 1311 | | 3 | `U+2830 ⠰ -> U+2820 ⠠` | 1018 | -| 4 | `U+2820 ⠠ -> U+2830 ⠰` | 799 | +| 4 | `U+2820 ⠠ -> U+2830 ⠰` | 800 | | 5 | `U+2810 ⠐ -> U+2832 ⠲` | 642 | -| 6 | `U+2820 ⠠ -> U+2834 ⠴` | 466 | -| 7 | `U+283C ⠼ -> U+2800 ⠀` | 458 | -| 8 | `U+2826 ⠦ -> U+2834 ⠴` | 451 | -| 9 | `U+280E ⠎ -> U+280C ⠌` | 444 | -| 10 | `U+2826 ⠦ -> U+2800 ⠀` | 423 | -| 11 | `U+2808 ⠈ -> U+2832 ⠲` | 409 | -| 12 | `U+2801 ⠁ -> U+281C ⠜` | 386 | -| 13 | `U+2800 ⠀ -> U+2807 ⠇` | 360 | +| 6 | `U+2820 ⠠ -> U+2834 ⠴` | 467 | +| 7 | `U+2826 ⠦ -> U+2834 ⠴` | 453 | +| 8 | `U+280E ⠎ -> U+280C ⠌` | 444 | +| 9 | `U+2826 ⠦ -> U+2800 ⠀` | 424 | +| 10 | `U+2808 ⠈ -> U+2832 ⠲` | 409 | +| 11 | `U+2801 ⠁ -> U+281C ⠜` | 386 | +| 12 | `U+2800 ⠀ -> U+2807 ⠇` | 360 | +| 13 | `U+283C ⠼ -> U+2800 ⠀` | 357 | | 14 | `U+2834 ⠴ -> U+2830 ⠰` | 325 | | 15 | `U+2811 ⠑ -> U+282B ⠫` | 322 | | 16 | `U+2832 ⠲ -> U+2838 ⠸` | 275 | -| 17 | `U+2800 ⠀ -> U+2832 ⠲` | 251 | -| 18 | `U+2810 ⠐ -> U+2802 ⠂` | 209 | +| 17 | `U+2800 ⠀ -> U+2832 ⠲` | 254 | +| 18 | `U+2810 ⠐ -> U+2802 ⠂` | 210 | | 19 | `U+2817 ⠗ -> U+2820 ⠠` | 161 | -| 20 | `U+2826 ⠦ -> U+2810 ⠐` | 156 | +| 20 | `U+2826 ⠦ -> U+2810 ⠐` | 155 | ### `U+2834 ⠴ -> U+2800 ⠀` @@ -200,29 +200,6 @@ This ranking is a diagnostic selector, not an implementation rule. It counts onl - first differing cell (zero-based): 30 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -### `U+283C ⠼ -> U+2800 ⠀` - -- `sentence_01.json` #426: LG에너지솔루션은 혼다에 앞서 미국 제너럴모터스(GM)과의 배터리 합작법인 ‘얼티엄셀즈’를 설립해 투자를 진행 중이다. 이를 통해 북미 생산 능력을 올해 13GWh 규모에서 2025년 255GWh까지 확대할 예정이다. - - expected: `⠁⠮⠀⠥⠂⠚⠗⠀⠼⠁⠉⠴⠠⠛⠠⠺⠓⠲⠀⠈⠩⠑⠥⠝` - - actual: `⠁⠮⠀⠥⠂⠚⠗⠀⠀⠼⠁⠉⠠⠠⠛⠺⠓⠀⠀⠈⠩⠑⠥⠝` - - first differing cell (zero-based): 156 - - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_02.json` #68: 조성된 무장애나눔길은 통행이 많은 숲속의집~생태숲학습관 0.73km 구간에 평균 경사도 5% 이하 데크산책로와 황토포장길로 조성됐으며 전국 무장애나눔길 최초로 장애물 없는 생활환경(BF)인증을 추진하고 있다. - - expected: `⠁⠠⠪⠃⠈⠧⠒⠀⠼⠚⠲⠛⠉⠴⠅⠍⠲⠀⠈⠍⠫⠒⠝⠀` - - actual: `⠁⠠⠪⠃⠈⠧⠒⠀⠀⠼⠚⠲⠛⠉⠅⠍⠀⠀⠈⠍⠫⠒⠝⠀` - - first differing cell (zero-based): 62 - - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_03.json` #59: 이들 중 A씨(31) 등 2명은 지난 3월 25일 김해공항으로 필로폰 968g 등을 팬티 속에 숨겨 입국한 혐의를 받고 있다. - - expected: `⠙⠕⠂⠐⠥⠙⠷⠀⠼⠊⠋⠓⠴⠛⠲⠀⠊⠪⠶⠮⠀⠙⠗⠒` - - actual: `⠙⠕⠂⠐⠥⠙⠷⠀⠀⠼⠊⠋⠓⠛⠀⠀⠊⠪⠶⠮⠀⠙⠗⠒` - - first differing cell (zero-based): 71 - - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_04.json` #57: 미국지질조사국(USGS)에 따르면 이날 에콰도르 항구도시 과야킬에서 남쪽으로 약 80km 떨어진 지점에서 규모 6.8의 지진이 발생했다. 지진의 깊이는 66㎞다. - - expected: `⠭⠪⠐⠥⠀⠜⠁⠀⠼⠓⠚⠴⠅⠍⠲⠀⠠⠊⠞⠎⠨⠟⠀⠨` - - actual: `⠭⠪⠐⠥⠀⠜⠁⠀⠀⠼⠓⠚⠐⠅⠍⠀⠀⠠⠊⠞⠎⠨⠟⠀` - - first differing cell (zero-based): 79 - - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` - ### `U+2826 ⠦ -> U+2834 ⠴` - `sentence_01.json` #38: 올해 소프트웨어(SW) 업계는 국내 인공지능(AI) 제품·서비스 고도화와 함께 전 산업영역에서 SW영역 확대가 가시화될 것으로 보인다. 또한 블록체인이 웹3(Web3)와 연계해 탈중앙화·탈독점화 경제체계로 진화할 것으로 예상된다. @@ -292,6 +269,29 @@ This ranking is a diagnostic selector, not an implementation rule. It counts onl - first differing cell (zero-based): 228 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +### `U+2808 ⠈ -> U+2832 ⠲` + +- `sentence_01.json` #207: 우리금융지주가 약 7조원의 자본 여력을 등에 업고 인수·합병(M&A) 시장에 출격했다. 벤처캐피탈(VC) 인수를 시작으로 증권사와 보험사까지 인수한다는 계획이다. + - expected: `⠃⠘⠻⠦⠄⠴⠠⠍⠈⠯⠠⠁⠠⠴⠀⠠⠕⠨⠶⠝⠀⠰⠯⠈` + - actual: `⠃⠘⠻⠦⠄⠴⠠⠍⠲⠴⠈⠯⠲⠴⠠⠁⠠⠴⠀⠠⠕⠨⠶⠝` + - first differing cell (zero-based): 59 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_02.json` #146: KT&G(사장 백복인)가 산업정책연구원(IPS)이 주관하고 윤경 ESG 포럼이 주최하는 ‘윤경 ESG 포럼 CEO 서약식’에 참여해 윤리경영 의지를 다졌다. + - expected: `⠴⠠⠠⠅⠞⠈⠯⠠⠛⠦⠄⠇⠨⠶⠀⠘⠗⠁⠘⠭⠟⠠⠴⠫` + - actual: `⠴⠠⠠⠅⠞⠲⠴⠈⠯⠲⠴⠛⠦⠄⠇⠨⠶⠀⠘⠗⠁⠘⠭⠟` + - first differing cell (zero-based): 5 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_03.json` #191: 한국투자신탁운용의 에이스(ACE) 글로벌브랜드TOP10블룸버그 ETF는 연초 이후 22.6% 상승했다. 명품 기업 루이비통모에헤네시(LVMH), 대표 가치주로 꼽히는 프록터앤드갬블(P&G) 편입 비중이 높은 것이 특징이다. + - expected: `⠢⠘⠮⠦⠄⠴⠠⠏⠈⠯⠠⠛⠠⠴⠀⠙⠡⠕⠃⠀⠘⠕⠨⠍` + - actual: `⠢⠘⠮⠦⠄⠴⠠⠏⠲⠴⠈⠯⠲⠴⠠⠛⠠⠴⠀⠙⠡⠕⠃⠀` + - first differing cell (zero-based): 183 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_04.json` #159: 또 액화천연가스(LNG) 분야 협력을 확대하는 한편 수소환원제철 관련 공동 연구·개발(R&D)을 추진해 유럽연합(EU)의 탄소국경조정세(CBAM)와 같은 글로벌 규제와 자원 무기화에 공동 대응한다는 전략이다. + - expected: `⠗⠘⠂⠦⠄⠴⠠⠗⠈⠯⠠⠙⠠⠴⠮⠀⠰⠍⠨⠟⠚⠗⠀⠩` + - actual: `⠗⠘⠂⠦⠄⠴⠠⠗⠲⠴⠈⠯⠲⠴⠠⠙⠠⠴⠮⠀⠰⠍⠨⠟` + - first differing cell (zero-based): 90 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` + ## Residual first-difference transitions after localized cohorts This ranking removes only cases whose first difference is inside an existing output-localized cohort. Broad input-only traits are not exclusion masks. The residual table therefore prioritizes new causes without hiding a mismatch merely because an unrelated structure coexists elsewhere in its sentence. @@ -299,25 +299,25 @@ This ranking removes only cases whose first difference is inside an existing out | Rank | Expected → actual first cell | Residual cases | |---:|---|---:| | 1 | `U+2830 ⠰ -> U+2820 ⠠` | 1015 | -| 2 | `U+2820 ⠠ -> U+2830 ⠰` | 798 | -| 3 | `U+2834 ⠴ -> U+2800 ⠀` | 544 | +| 2 | `U+2820 ⠠ -> U+2830 ⠰` | 799 | +| 3 | `U+2834 ⠴ -> U+2800 ⠀` | 545 | | 4 | `U+2820 ⠠ -> U+2834 ⠴` | 454 | -| 5 | `U+2826 ⠦ -> U+2834 ⠴` | 445 | +| 5 | `U+2826 ⠦ -> U+2834 ⠴` | 447 | | 6 | `U+280E ⠎ -> U+280C ⠌` | 444 | -| 7 | `U+283C ⠼ -> U+2800 ⠀` | 444 | -| 8 | `U+2808 ⠈ -> U+2832 ⠲` | 407 | -| 9 | `U+2826 ⠦ -> U+2800 ⠀` | 388 | -| 10 | `U+2801 ⠁ -> U+281C ⠜` | 384 | -| 11 | `U+2834 ⠴ -> U+2830 ⠰` | 325 | -| 12 | `U+2811 ⠑ -> U+282B ⠫` | 321 | -| 13 | `U+2810 ⠐ -> U+2832 ⠲` | 285 | -| 14 | `U+2832 ⠲ -> U+2838 ⠸` | 275 | -| 15 | `U+2800 ⠀ -> U+2832 ⠲` | 225 | -| 16 | `U+2810 ⠐ -> U+2802 ⠂` | 209 | -| 17 | `U+2817 ⠗ -> U+2820 ⠠` | 161 | +| 7 | `U+2808 ⠈ -> U+2832 ⠲` | 407 | +| 8 | `U+2826 ⠦ -> U+2800 ⠀` | 389 | +| 9 | `U+2801 ⠁ -> U+281C ⠜` | 384 | +| 10 | `U+2834 ⠴ -> U+2830 ⠰` | 325 | +| 11 | `U+2811 ⠑ -> U+282B ⠫` | 321 | +| 12 | `U+2810 ⠐ -> U+2832 ⠲` | 285 | +| 13 | `U+2832 ⠲ -> U+2838 ⠸` | 275 | +| 14 | `U+2800 ⠀ -> U+2832 ⠲` | 225 | +| 15 | `U+2810 ⠐ -> U+2802 ⠂` | 210 | +| 16 | `U+2817 ⠗ -> U+2820 ⠠` | 161 | +| 17 | `U+283C ⠼ -> U+2800 ⠀` | 153 | | 18 | `U+2820 ⠠ -> U+2809 ⠉` | 152 | | 19 | `U+281B ⠛ -> U+2823 ⠣` | 144 | -| 20 | `U+2826 ⠦ -> U+2810 ⠐` | 140 | +| 20 | `U+2826 ⠦ -> U+2810 ⠐` | 139 | ### Residual `U+2830 ⠰ -> U+2820 ⠠` @@ -457,29 +457,6 @@ This ranking removes only cases whose first difference is inside an existing out - first differing cell (zero-based): 53 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -### Residual `U+283C ⠼ -> U+2800 ⠀` - -- `sentence_01.json` #426: LG에너지솔루션은 혼다에 앞서 미국 제너럴모터스(GM)과의 배터리 합작법인 ‘얼티엄셀즈’를 설립해 투자를 진행 중이다. 이를 통해 북미 생산 능력을 올해 13GWh 규모에서 2025년 255GWh까지 확대할 예정이다. - - expected: `⠁⠮⠀⠥⠂⠚⠗⠀⠼⠁⠉⠴⠠⠛⠠⠺⠓⠲⠀⠈⠩⠑⠥⠝` - - actual: `⠁⠮⠀⠥⠂⠚⠗⠀⠀⠼⠁⠉⠠⠠⠛⠺⠓⠀⠀⠈⠩⠑⠥⠝` - - first differing cell (zero-based): 156 - - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_02.json` #68: 조성된 무장애나눔길은 통행이 많은 숲속의집~생태숲학습관 0.73km 구간에 평균 경사도 5% 이하 데크산책로와 황토포장길로 조성됐으며 전국 무장애나눔길 최초로 장애물 없는 생활환경(BF)인증을 추진하고 있다. - - expected: `⠁⠠⠪⠃⠈⠧⠒⠀⠼⠚⠲⠛⠉⠴⠅⠍⠲⠀⠈⠍⠫⠒⠝⠀` - - actual: `⠁⠠⠪⠃⠈⠧⠒⠀⠀⠼⠚⠲⠛⠉⠅⠍⠀⠀⠈⠍⠫⠒⠝⠀` - - first differing cell (zero-based): 62 - - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_03.json` #59: 이들 중 A씨(31) 등 2명은 지난 3월 25일 김해공항으로 필로폰 968g 등을 팬티 속에 숨겨 입국한 혐의를 받고 있다. - - expected: `⠙⠕⠂⠐⠥⠙⠷⠀⠼⠊⠋⠓⠴⠛⠲⠀⠊⠪⠶⠮⠀⠙⠗⠒` - - actual: `⠙⠕⠂⠐⠥⠙⠷⠀⠀⠼⠊⠋⠓⠛⠀⠀⠊⠪⠶⠮⠀⠙⠗⠒` - - first differing cell (zero-based): 71 - - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_04.json` #57: 미국지질조사국(USGS)에 따르면 이날 에콰도르 항구도시 과야킬에서 남쪽으로 약 80km 떨어진 지점에서 규모 6.8의 지진이 발생했다. 지진의 깊이는 66㎞다. - - expected: `⠭⠪⠐⠥⠀⠜⠁⠀⠼⠓⠚⠴⠅⠍⠲⠀⠠⠊⠞⠎⠨⠟⠀⠨` - - actual: `⠭⠪⠐⠥⠀⠜⠁⠀⠀⠼⠓⠚⠐⠅⠍⠀⠀⠠⠊⠞⠎⠨⠟⠀` - - first differing cell (zero-based): 79 - - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` - ### Residual `U+2808 ⠈ -> U+2832 ⠲` - `sentence_01.json` #207: 우리금융지주가 약 7조원의 자본 여력을 등에 업고 인수·합병(M&A) 시장에 출격했다. 벤처캐피탈(VC) 인수를 시작으로 증권사와 보험사까지 인수한다는 계획이다. @@ -549,24 +526,47 @@ This ranking removes only cases whose first difference is inside an existing out - first differing cell (zero-based): 14 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +### Residual `U+2834 ⠴ -> U+2830 ⠰` + +- `sentence_01.json` #337: ‘아리송(ARISONG)’, ‘Boyfriend’에 이어 ‘오로라’에도 인기 안무가 리정이 안무 메이킹에 참여해 기대를 모은다. 리정과 7인 7색 매력의 시그니처가 만나 어떤 색다른 퍼포먼스를 선사할지 이목이 집중된다. + - expected: `⠠⠴⠴⠄⠐⠀⠠⠦⠴⠠⠃⠕⠽⠋⠗⠊⠢⠙⠴⠄⠝⠀⠕⠎` + - actual: `⠠⠴⠴⠄⠐⠀⠠⠦⠰⠠⠃⠕⠽⠋⠗⠊⠢⠙⠴⠄⠝⠀⠕⠎` + - first differing cell (zero-based): 25 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_02.json` #461: 주요 연수 내용은 교육현장의 요구를 적극적으로 반영해 인공지능(AI), ChatGTP, 영어그림책 등을 활용한 다양한 교수학습 방법, 세계시민교육 실천사례, 영미권 원어민 강사와의 협력수업 방법 등으로 구성했다. + - expected: `⠠⠠⠁⠊⠠⠴⠐⠀⠴⠠⠡⠁⠞⠠⠠⠛⠞⠏⠐⠀⠻⠎⠈⠪` + - actual: `⠠⠠⠁⠊⠠⠴⠐⠀⠰⠠⠡⠁⠞⠠⠠⠛⠞⠏⠐⠀⠻⠎⠈⠪` + - first differing cell (zero-based): 66 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_03.json` #57: ACE 미국30년국채액티브(H) ETF는 한국투자신탁운용이 첫 번째로 상장한 월배당형 상품이다. 예상 배당률은 연 3%다. 첫 번째 분배금은 5월 초 지급될 예정이다. + - expected: `⠦⠄⠴⠠⠓⠠⠴⠀⠴⠠⠠⠑⠞⠋⠲⠉⠵⠀⠚⠒⠈⠍⠁⠓` + - actual: `⠦⠄⠴⠠⠓⠠⠴⠀⠰⠠⠠⠑⠞⠋⠲⠉⠵⠀⠚⠒⠈⠍⠁⠓` + - first differing cell (zero-based): 38 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_04.json` #298: HMM은 카타르 하마드에서 당초 수량보다 16개를 추가한 586개의 임시주택 컨테이너를 다목적선(MPV) ‘HMM 울산호’에 선적했다. 이 선박은 27일 출항, 다음달 10일경 튀르키예 이스켄데룬에 도착할 예정이다. + - expected: `⠍⠏⠧⠠⠴⠀⠠⠦⠴⠠⠠⠓⠍⠍⠲⠀⠯⠇⠒⠚⠥⠴⠄⠝` + - actual: `⠍⠏⠧⠠⠴⠀⠠⠦⠰⠠⠠⠓⠍⠍⠲⠀⠯⠇⠒⠚⠥⠴⠄⠝` + - first differing cell (zero-based): 98 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` + ## Cross-cutting input-only structural cohorts -These are cross-cutting input-only structural cohorts, not new primary classes and not engine routing rules. Candidate selection never changes a case's existing primary class by itself. Only cohort members already classified as `pending_rule_review` form a pending subcluster; exact and other-primary members are controls that retain their existing outcomes. A separate classifier may use independently justified, output-localized PDF evidence, as in the rule-34 three-cell contradiction below. The `uppercase_roman_headword_closed_multiword_parenthetical` gate requires a two-or-more character uppercase ASCII headword immediately followed by a closed parenthesis whose contents are two or more ASCII Roman words separated only by spaces. Because the contents admit only letters and spaces, visible operators, subscript/superscript notation, and nested parentheses are excluded deterministically. The `mixed_roman_korean_word_before_uppercase_headword_expansion` gate further requires a whitespace-delimited preceding word that contains both ASCII Roman and Korean and ends in Korean. It locates the following uppercase headword itself, including its immediately preceding emitted cell, so an earlier Roman entry in the same sentence cannot satisfy the output audit. The `single_capital_followed_by_parenthesized_digits` gate requires a standalone capital, a non-empty closed ASCII-digit parenthetical, and alphanumeric outer boundaries. It likewise includes the emitted entry-boundary cell in localization. The `uppercase_roman_run_followed_by_hyphen_digits` gate requires a maximal uppercase ASCII run, literal ASCII hyphen-minus, a non-empty digit run, and alphanumeric outer boundaries. Its localized range includes the current encoded run and its immediately preceding cell, keeping `F-35`-style routing separate from both parenthesized digits and headword expansions. The `standalone_multi_character_uppercase_roman_word` gate finds maximal ASCII-letter runs of two or more capitals with non-alphanumeric boundaries; a run immediately followed by `(` is excluded so the HCA-style headword itself is not counted by both gates. The `korean_prefixed_closed_allcaps_parenthetical` gate requires an immediately preceding Korean character and a closed body of two or more uppercase ASCII letters. It intentionally contains both acronym annotations (`책임자(COO)`) and scientific formulae (`일산화탄소(CO)`) so their semantic collision remains measurable. The `korean_prefixed_closed_roman_annotation_rule_34_order` gate accepts the narrower rule-34 body grammar after an immediately preceding Korean character and localizes only the current engine's Korean opening-parenthesis cells. This distinguishes the PDF's parenthesis-before-Roman-indicator order from unrelated differences later in the same sentence. The `multi_character_allcaps_roman_runs_joined_by_middle_dot` gate requires two maximal ASCII-letter runs of at least two capitals joined directly by U+00B7, with non-alphanumeric outer boundaries. It records shapes such as `AI·SW` without assigning prose, mathematics, or science semantics. The `roman_run_immediately_before_attached_middle_dot_boundary` gate is output-localized: it requires a maximal ASCII-letter run immediately before U+00B7 and an attached Korean character or ASCII-letter run after it, then searches for that whole current-engine signature in the actual output. It therefore isolates the Roman terminator boundary without treating unrelated middle dots elsewhere in the sentence as causal. The `korean_inline_parenthesized_single_arithmetic_operator` gate requires an immediate `Korean(` + one rule-45 arithmetic operator + `)Korean` span. Unlike broad coexistence traits, it also locates the current engine's emitted structure and counts a mismatch as signature-local only when the sentence's first differing cell falls inside that output range. The `allcaps_roman_run_containing_ou` gate finds maximal, alphanumeric-delimited uppercase ASCII runs containing adjacent `OU`. It locates the independently encoded run signature in the complete current output and counts only first differences inside that signature as localized. The `decimal_point_between_ascii_digits` gate finds whitespace-delimited words containing `digit.digit` and reproduces each whole word in a neutral Korean context, so suffixes and punctuation remain part of the current-engine signature. The `compact_numeric_ascii_letter_suffix` gate finds a numeric prefix immediately followed by ASCII letters and retains suffix-specific outcome counts. It intentionally includes both possible rule-69 units and ambiguous variable/identifier forms; membership alone does not assign unit semantics. The `rule69_ascii_unit_before_terminator_skipping_symbol` gate is narrower: it accepts only ASCII unit spellings already supported by rule 69, includes the immediately following rule-33/34 punctuation cell in the localized signature, and does not infer new units. The `tight_triangle_mark_immediately_before_korean` gate requires literal `△한글` with no input space and includes the first following Korean cell in its localized output range, so an observed missing-space difference is measured at the mark boundary. +These are cross-cutting input-only structural cohorts, not new primary classes and not engine routing rules. Candidate selection never changes a case's existing primary class by itself. Only cohort members already classified as `pending_rule_review` form a pending subcluster; exact and other-primary members are controls that retain their existing outcomes. A separate classifier may use independently justified, output-localized PDF evidence, as in the rule-34 three-cell contradiction below. The `uppercase_roman_headword_closed_multiword_parenthetical` gate requires a two-or-more character uppercase ASCII headword immediately followed by a closed parenthesis whose contents are two or more ASCII Roman words separated only by spaces. Because the contents admit only letters and spaces, visible operators, subscript/superscript notation, and nested parentheses are excluded deterministically. The `mixed_roman_korean_word_before_uppercase_headword_expansion` gate further requires a whitespace-delimited preceding word that contains both ASCII Roman and Korean and ends in Korean. It locates the following uppercase headword itself, including its immediately preceding emitted cell, so an earlier Roman entry in the same sentence cannot satisfy the output audit. The `single_capital_followed_by_parenthesized_digits` gate requires a standalone capital, a non-empty closed ASCII-digit parenthetical, and alphanumeric outer boundaries. It likewise includes the emitted entry-boundary cell in localization. The `uppercase_roman_run_followed_by_hyphen_digits` gate requires a maximal uppercase ASCII run, literal ASCII hyphen-minus, a non-empty digit run, and alphanumeric outer boundaries. Its localized range includes the current encoded run and its immediately preceding cell, keeping `F-35`-style routing separate from both parenthesized digits and headword expansions. The `standalone_multi_character_uppercase_roman_word` gate finds maximal ASCII-letter runs of two or more capitals with non-alphanumeric boundaries; a run immediately followed by `(` is excluded so the HCA-style headword itself is not counted by both gates. The `korean_prefixed_closed_allcaps_parenthetical` gate requires an immediately preceding Korean character and a closed body of two or more uppercase ASCII letters. It intentionally contains both acronym annotations (`책임자(COO)`) and scientific formulae (`일산화탄소(CO)`) so their semantic collision remains measurable. The `korean_prefixed_closed_roman_annotation_rule_34_order` gate accepts the narrower rule-34 body grammar after an immediately preceding Korean character and localizes only the current engine's Korean opening-parenthesis cells. This distinguishes the PDF's parenthesis-before-Roman-indicator order from unrelated differences later in the same sentence. The `multi_character_allcaps_roman_runs_joined_by_middle_dot` gate requires two maximal ASCII-letter runs of at least two capitals joined directly by U+00B7, with non-alphanumeric outer boundaries. It records shapes such as `AI·SW` without assigning prose, mathematics, or science semantics. The `roman_run_immediately_before_attached_middle_dot_boundary` gate is output-localized: it requires a maximal ASCII-letter run immediately before U+00B7 and an attached Korean character or ASCII-letter run after it, then searches for that whole current-engine signature in the actual output. It therefore isolates the Roman terminator boundary without treating unrelated middle dots elsewhere in the sentence as causal. The `korean_inline_parenthesized_single_arithmetic_operator` gate requires an immediate `Korean(` + one rule-45 arithmetic operator + `)Korean` span. Unlike broad coexistence traits, it also locates the current engine's emitted structure and counts a mismatch as signature-local only when the sentence's first differing cell falls inside that output range. The `allcaps_roman_run_containing_ou` gate finds maximal, alphanumeric-delimited uppercase ASCII runs containing adjacent `OU`. It locates the independently encoded run signature in the complete current output and counts only first differences inside that signature as localized. The `decimal_point_between_ascii_digits` gate finds whitespace-delimited words containing `digit.digit` and reproduces each whole word in a neutral Korean context, so suffixes and punctuation remain part of the current-engine signature. The `compact_numeric_ascii_letter_suffix` gate finds a numeric prefix immediately followed by ASCII letters, includes the immediately preceding output cell as its entry boundary, and retains suffix-specific outcome counts. It intentionally includes both possible rule-69 units and ambiguous variable/identifier forms; membership alone does not assign unit semantics. The `rule69_ascii_unit_before_terminator_skipping_symbol` gate is narrower: it accepts only ASCII unit spellings already supported by rule 69, includes the immediately following rule-33/34 punctuation cell in the localized signature, and does not infer new units. The `tight_triangle_mark_immediately_before_korean` gate requires literal `△한글` with no input space and includes the first following Korean cell in its localized output range, so an observed missing-space difference is measured at the mark boundary. | Cluster | Candidates | Exact | Mismatch | Conflicting-reference cases | |---|---:|---:|---:|---:| | `allcaps_roman_run_containing_ou` | 1816 | 55 | 1761 | 0 | -| `compact_numeric_ascii_letter_suffix` | 2975 | 1649 | 1326 | 0 | -| `decimal_point_between_ascii_digits` | 4546 | 3187 | 1359 | 0 | +| `compact_numeric_ascii_letter_suffix` | 2975 | 1759 | 1216 | 0 | +| `decimal_point_between_ascii_digits` | 4546 | 3209 | 1337 | 0 | | `korean_inline_parenthesized_single_arithmetic_operator` | 23 | 17 | 6 | 0 | -| `korean_prefixed_closed_allcaps_parenthetical` | 54492 | 43953 | 10539 | 0 | -| `korean_prefixed_closed_roman_annotation_rule_34_order` | 64382 | 51305 | 13077 | 0 | +| `korean_prefixed_closed_allcaps_parenthetical` | 54492 | 43994 | 10498 | 0 | +| `korean_prefixed_closed_roman_annotation_rule_34_order` | 64382 | 51368 | 13014 | 0 | | `mixed_roman_korean_word_before_uppercase_headword_expansion` | 10 | 0 | 10 | 0 | | `multi_character_allcaps_roman_runs_joined_by_middle_dot` | 97 | 0 | 97 | 0 | | `roman_run_immediately_before_attached_middle_dot_boundary` | 577 | 0 | 577 | 0 | -| `rule69_ascii_unit_before_terminator_skipping_symbol` | 440 | 325 | 115 | 0 | +| `rule69_ascii_unit_before_terminator_skipping_symbol` | 440 | 329 | 111 | 0 | | `single_capital_followed_by_parenthesized_digits` | 1361 | 0 | 1361 | 0 | -| `standalone_multi_character_uppercase_roman_word` | 62411 | 49035 | 13376 | 0 | +| `standalone_multi_character_uppercase_roman_word` | 62411 | 49088 | 13323 | 0 | | `tight_triangle_mark_immediately_before_korean` | 377 | 0 | 377 | 0 | | `uppercase_roman_headword_closed_multiword_parenthetical` | 175 | 15 | 160 | 0 | | `uppercase_roman_run_followed_by_hyphen_digits` | 571 | 183 | 388 | 0 | @@ -634,23 +634,23 @@ Representative `mismatch` samples: ### `compact_numeric_ascii_letter_suffix` -Of the 2975 candidates, 1259 are the actual `pending_rule_review` subcluster. The other 1716 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 2975 candidates, 1148 are the actual `pending_rule_review` subcluster. The other 1827 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. -For this output-signature audit, 1326 mismatches were evaluable and 60 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. +For this output-signature audit, 1216 mismatches were evaluable and 251 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. Localized first-difference transitions: -- `U+2800 ⠀ -> U+2832 ⠲`: 24 +- `U+283C ⠼ -> U+2800 ⠀`: 190 +- `U+2800 ⠀ -> U+2832 ⠲`: 27 - `U+2810 ⠐ -> U+2832 ⠲`: 10 - `U+281E ⠞ -> U+2810 ⠐`: 7 -- `U+2805 ⠅ -> U+2834 ⠴`: 3 -- `U+2838 ⠸ -> U+2832 ⠲`: 3 +- `U+2805 ⠅ -> U+2834 ⠴`: 4 Mismatch primary-class distribution: - `comparison_method`: 11 -- `corpus_suspect`: 48 -- `pending_rule_review`: 1259 +- `corpus_suspect`: 49 +- `pending_rule_review`: 1148 - `unsupported_character_review`: 8 Representative `exact` samples: @@ -667,32 +667,32 @@ Representative `exact` samples: - expected: `⠴⠠⠃⠲⠠⠠⠕⠦⠄⠼⠉⠚⠠⠴⠀⠊⠪⠶⠀⠼⠃⠀⠑⠻` - actual: `⠴⠠⠃⠲⠠⠠⠕⠦⠄⠼⠉⠚⠠⠴⠀⠊⠪⠶⠀⠼⠃⠀⠑⠻` - current primary/reason: `exact` / `exact` -- `sentence_04.json` #78: 차체 중량은 55㎏인데, 체중 100㎏의 사람을 태울 수 있다. 배터리 출력은 1000W(와트)로, 도심 길거리에서 쓰는 킥보드와 비슷한 출력이다. - - expected: `⠰⠣⠰⠝⠀⠨⠍⠶⠐⠜⠶⠵⠀⠼⠑⠑⠴⠅⠛⠲⠟⠊⠝⠐` - - actual: `⠰⠣⠰⠝⠀⠨⠍⠶⠐⠜⠶⠵⠀⠼⠑⠑⠴⠅⠛⠲⠟⠊⠝⠐` +- `sentence_04.json` #57: 미국지질조사국(USGS)에 따르면 이날 에콰도르 항구도시 과야킬에서 남쪽으로 약 80km 떨어진 지점에서 규모 6.8의 지진이 발생했다. 지진의 깊이는 66㎞다. + - expected: `⠑⠕⠈⠍⠁⠨⠕⠨⠕⠂⠨⠥⠇⠈⠍⠁⠦⠄⠴⠠⠠⠥⠎⠛` + - actual: `⠑⠕⠈⠍⠁⠨⠕⠨⠕⠂⠨⠥⠇⠈⠍⠁⠦⠄⠴⠠⠠⠥⠎⠛` - current primary/reason: `exact` / `exact` Representative `localized_mismatch` samples: -- `sentence_01.json` #1720: LG헬로비전 헬로모바일이 월 5만원대의 31GB 5세대 이동통신(5G) 알뜰폰 요금제를 선보인다. 알뜰폰업체가 데이터 30GB 구간의 알뜰 요금제를 선보이는 건 이번이 처음이다. - - expected: `⠼⠉⠁⠴⠠⠠⠛⠃⠀⠼⠑⠠⠝⠊⠗⠀⠕⠊⠿⠓⠿⠠⠟⠦` - - actual: `⠼⠉⠁⠴⠠⠠⠛⠃⠲⠀⠼⠑⠠⠝⠊⠗⠀⠕⠊⠿⠓⠿⠠⠟` - - first differing cell (zero-based): 51 +- `sentence_01.json` #322: 삼성전자는 5나노미터(nm, 1nm는 10억 분의 1m) 기반 신규 컨트롤러를 탑재한 PC용 고성능 비휘발성 메모리 익스프레스(NVMe) SSD ‘PM9C1a’를 양산한다고 12일 밝혔다. + - expected: `⠄⠴⠝⠍⠂⠀⠼⠁⠝⠍⠲⠉⠵⠀⠼⠁⠚⠹⠀⠘⠛⠺⠀⠼` + - actual: `⠄⠴⠝⠍⠂⠀⠼⠁⠴⠝⠍⠲⠉⠵⠀⠼⠁⠚⠹⠀⠘⠛⠺⠀` + - first differing cell (zero-based): 29 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` - `sentence_02.json` #431: 173cm, 68kg의 다소 왜소한 체격이지만 스크럼 하프(SH) 포지션을 맡아 포워드와 백스 사이에서 날카로운 볼 연결과 공격방향을 결정하는 타고난 판단력이 강점이다. - expected: `⠴⠉⠍⠂⠀⠼⠋⠓⠅⠛⠲⠺⠀⠊⠠⠥⠀⠧⠗⠠⠥⠚⠒⠀` - actual: `⠴⠉⠍⠂⠀⠼⠋⠓⠴⠅⠛⠲⠺⠀⠊⠠⠥⠀⠧⠗⠠⠥⠚⠒` - first differing cell (zero-based): 12 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_03.json` #4252: 파워트레인은 최고 출력 197마력, 최대 토크 30.6kg·m의 가솔린 기반 마일드 하이브리드(MHEV)와 상시 사륜구동(AWD) 시스템 조합으로 구성된다. - - expected: `⠼⠉⠚⠲⠋⠴⠅⠛⠐⠆⠴⠍⠲⠺⠀⠫⠠⠥⠂⠐⠟⠀⠈⠕` - - actual: `⠼⠉⠚⠲⠋⠴⠅⠛⠲⠐⠆⠴⠍⠲⠺⠀⠫⠠⠥⠂⠐⠟⠀⠈` - - first differing cell (zero-based): 50 +- `sentence_03.json` #59: 이들 중 A씨(31) 등 2명은 지난 3월 25일 김해공항으로 필로폰 968g 등을 팬티 속에 숨겨 입국한 혐의를 받고 있다. + - expected: `⠙⠕⠂⠐⠥⠙⠷⠀⠼⠊⠋⠓⠴⠛⠲⠀⠊⠪⠶⠮⠀⠙⠗⠒` + - actual: `⠙⠕⠂⠐⠥⠙⠷⠀⠀⠼⠊⠋⠓⠛⠀⠀⠊⠪⠶⠮⠀⠙⠗⠒` + - first differing cell (zero-based): 71 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_04.json` #2020: 실제 생활 주변 방사선 안전관리 실태조사 결과보고서를 보면 2017~2021년 5년간 항공 승무원의 최대 피폭선량은 평균 5.42밀리시버트(mSv)로 일반인 선량한도인 1m㏜보다 5배 이상 높다. - - expected: `⠊⠥⠟⠀⠼⠁⠴⠍⠠⠎⠧⠲⠘⠥⠊⠀⠼⠑⠘⠗⠀⠕⠇⠶` - - actual: `⠊⠥⠟⠀⠼⠁⠴⠍⠲⠴⠠⠎⠧⠲⠘⠥⠊⠀⠼⠑⠘⠗⠀⠕` - - first differing cell (zero-based): 171 +- `sentence_04.json` #538: 중국 전기차 제조업체 비야디(BYD)가 6일 1t 전기트럭 ‘T4K(티포케이)’을 국내에 출시했다. BYD는 전 세계 1위 전기차 업체다. + - expected: `⠴⠫⠀⠼⠋⠕⠂⠀⠼⠁⠴⠞⠲⠀⠨⠾⠈⠕⠓⠪⠐⠹⠀⠠` + - actual: `⠴⠫⠀⠼⠋⠕⠂⠀⠀⠼⠁⠞⠀⠀⠨⠾⠈⠕⠓⠪⠐⠹⠀⠠` + - first differing cell (zero-based): 45 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` Representative `mismatch` samples: @@ -712,17 +712,17 @@ Representative `mismatch` samples: - actual: `⠙⠕⠂⠐⠥⠙⠷⠀⠀⠼⠊⠋⠓⠛⠀⠀⠊⠪⠶⠮⠀⠙⠗⠒` - first differing cell (zero-based): 71 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_04.json` #57: 미국지질조사국(USGS)에 따르면 이날 에콰도르 항구도시 과야킬에서 남쪽으로 약 80km 떨어진 지점에서 규모 6.8의 지진이 발생했다. 지진의 깊이는 66㎞다. - - expected: `⠭⠪⠐⠥⠀⠜⠁⠀⠼⠓⠚⠴⠅⠍⠲⠀⠠⠊⠞⠎⠨⠟⠀⠨` - - actual: `⠭⠪⠐⠥⠀⠜⠁⠀⠀⠼⠓⠚⠐⠅⠍⠀⠀⠠⠊⠞⠎⠨⠟⠀` - - first differing cell (zero-based): 79 +- `sentence_04.json` #115: ‘Busan is Good(부산이라 좋다)’이라는 새로운 도시 표어의 조형물을 공개하고, 3차원(3D)으로 표현한 도시상징 표지(CI) 영상을 상영한다. + - expected: `⠁⠝⠀⠊⠎⠀⠠⠛⠙⠦⠄⠘⠍⠇⠒⠕⠐⠣⠀⠨⠥⠴⠊⠠` + - actual: `⠁⠝⠀⠊⠎⠀⠠⠛⠕⠕⠙⠦⠄⠘⠍⠇⠒⠕⠐⠣⠀⠨⠥⠴` + - first differing cell (zero-based): 15 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` ### `decimal_point_between_ascii_digits` -Of the 4546 candidates, 1304 are the actual `pending_rule_review` subcluster. The other 3242 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 4546 candidates, 1281 are the actual `pending_rule_review` subcluster. The other 3265 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. -For this output-signature audit, 1359 mismatches were evaluable and 265 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. +For this output-signature audit, 1337 mismatches were evaluable and 262 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. Localized first-difference transitions: @@ -734,9 +734,9 @@ Localized first-difference transitions: Mismatch primary-class distribution: -- `comparison_method`: 15 +- `comparison_method`: 16 - `corpus_suspect`: 28 -- `pending_rule_review`: 1304 +- `pending_rule_review`: 1281 - `unsupported_character_review`: 12 Representative `exact` samples: @@ -852,13 +852,13 @@ Representative `mismatch` samples: ### `korean_prefixed_closed_allcaps_parenthetical` -Of the 54492 candidates, 9251 are the actual `pending_rule_review` subcluster. The other 45241 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 54492 candidates, 9209 are the actual `pending_rule_review` subcluster. The other 45283 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. Mismatch primary-class distribution: - `comparison_method`: 171 -- `corpus_suspect`: 980 -- `pending_rule_review`: 9251 +- `corpus_suspect`: 981 +- `pending_rule_review`: 9209 - `unsupported_character_review`: 137 Representative `exact` samples: @@ -905,20 +905,20 @@ Representative `mismatch` samples: ### `korean_prefixed_closed_roman_annotation_rule_34_order` -Of the 64382 candidates, 11601 are the actual `pending_rule_review` subcluster. The other 52781 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 64382 candidates, 11537 are the actual `pending_rule_review` subcluster. The other 52845 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. -For this output-signature audit, 13077 mismatches were evaluable and 1097 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. +For this output-signature audit, 13014 mismatches were evaluable and 1098 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. Localized first-difference transitions: -- `U+2834 ⠴ -> U+2826 ⠦`: 1096 +- `U+2834 ⠴ -> U+2826 ⠦`: 1097 - `U+2810 ⠐ -> U+2826 ⠦`: 1 Mismatch primary-class distribution: - `comparison_method`: 221 -- `corpus_suspect`: 1093 -- `pending_rule_review`: 11601 +- `corpus_suspect`: 1094 +- `pending_rule_review`: 11537 - `unsupported_character_review`: 162 Representative `exact` samples: @@ -1113,21 +1113,23 @@ Representative `mismatch` samples: ### `rule69_ascii_unit_before_terminator_skipping_symbol` -Of the 440 candidates, 110 are the actual `pending_rule_review` subcluster. The other 330 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 440 candidates, 106 are the actual `pending_rule_review` subcluster. The other 334 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. -For this output-signature audit, 115 mismatches were evaluable and 5 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. +For this output-signature audit, 111 mismatches were evaluable and 10 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. Localized first-difference transitions: +- `U+2814 ⠔ -> U+2824 ⠤`: 4 - `U+2834 ⠴ -> U+280D ⠍`: 3 - `U+2805 ⠅ -> U+2834 ⠴`: 1 - `U+2810 ⠐ -> U+2802 ⠂`: 1 +- `U+283C ⠼ -> U+2800 ⠀`: 1 Mismatch primary-class distribution: - `comparison_method`: 3 - `corpus_suspect`: 1 -- `pending_rule_review`: 110 +- `pending_rule_review`: 106 - `unsupported_character_review`: 1 Representative `exact` samples: @@ -1156,10 +1158,10 @@ Representative `localized_mismatch` samples: - actual: `⠍⠁⠀⠠⠦⠼⠊⠚⠍⠔⠴⠄⠝⠀⠠⠊⠐⠪⠑⠡⠀⠘⠝⠕` - first differing cell (zero-based): 29 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_02.json` #16417: 이번 대회에선 스프린트 1000m, ‘제외+포인트(EP) 10000M’, 계주 3000m 등 3개 세부 종목으로 확대됐으나 2026년 열릴 아이치·나고야 대회에서 또다시 존립이 위태로울 전망이다. - - expected: `⠀⠼⠁⠚⠚⠚⠴⠍⠐⠀⠠⠦⠨⠝⠽⠀⠢⠀⠙⠥⠟⠓⠪⠴` - - actual: `⠀⠼⠁⠚⠚⠚⠴⠍⠂⠀⠠⠦⠨⠝⠽⠀⠢⠀⠙⠥⠟⠓⠪⠦` - - first differing cell (zero-based): 28 +- `sentence_02.json` #4083: 이후 처음 출전한 이번 대회에서 곽범서(-48kg), 송화평(+92kg) 선수가 정상에 올랐고 안성호(-51kg) 선수가 동메달을 목에 걸었다. + - expected: `⠁⠘⠎⠢⠠⠎⠦⠄⠔⠼⠙⠓⠴⠅⠛⠠⠴⠐⠀⠠⠿⠚⠧⠙` + - actual: `⠁⠘⠎⠢⠠⠎⠦⠄⠤⠼⠙⠓⠴⠅⠛⠠⠴⠐⠀⠠⠿⠚⠧⠙` + - first differing cell (zero-based): 38 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` - `sentence_03.json` #17113: 6피트 9인치, 210파운드(206cm, 95kg)의 신체 사이즈를 지닌 폴 리드는 7피트 2인치(218cm)에 달하는 긴 윙스팬과 더불어 수비력을 인정받은 빅맨이다. - expected: `⠴⠉⠍⠂⠀⠼⠊⠑⠅⠛⠠⠴⠺⠀⠠⠟⠰⠝⠀⠇⠕⠨⠪⠐` @@ -1171,7 +1173,7 @@ Representative `mismatch` samples: - `sentence_01.json` #322: 삼성전자는 5나노미터(nm, 1nm는 10억 분의 1m) 기반 신규 컨트롤러를 탑재한 PC용 고성능 비휘발성 메모리 익스프레스(NVMe) SSD ‘PM9C1a’를 양산한다고 12일 밝혔다. - expected: `⠄⠴⠝⠍⠂⠀⠼⠁⠝⠍⠲⠉⠵⠀⠼⠁⠚⠹⠀⠘⠛⠺⠀⠼` - - actual: `⠄⠴⠝⠍⠂⠀⠼⠁⠰⠝⠍⠲⠉⠵⠀⠼⠁⠚⠹⠀⠘⠛⠺⠀` + - actual: `⠄⠴⠝⠍⠂⠀⠼⠁⠴⠝⠍⠲⠉⠵⠀⠼⠁⠚⠹⠀⠘⠛⠺⠀` - first differing cell (zero-based): 29 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` - `sentence_02.json` #327: 또한, ‘도가(Dog+Yoga)’ 등 반려동물과 함께 즐길 수 있는 다양한 프로그램이 준비돼 있으며, 참가자 전원에게는 태안산 황토 호박고구마(3kg) 및 요가매트가 제공된다. @@ -1233,13 +1235,13 @@ Representative `mismatch` samples: ### `standalone_multi_character_uppercase_roman_word` -Of the 62411 candidates, 11932 are the actual `pending_rule_review` subcluster. The other 50479 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 62411 candidates, 11878 are the actual `pending_rule_review` subcluster. The other 50533 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. Mismatch primary-class distribution: - `comparison_method`: 205 -- `corpus_suspect`: 1080 -- `pending_rule_review`: 11932 +- `corpus_suspect`: 1081 +- `pending_rule_review`: 11878 - `unsupported_character_review`: 159 Representative `exact` samples: @@ -1382,11 +1384,11 @@ Representative `mismatch` samples: Of the 571 candidates, 373 are the actual `pending_rule_review` subcluster. The other 198 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. -For this output-signature audit, 388 mismatches were evaluable and 259 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. +For this output-signature audit, 388 mismatches were evaluable and 260 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. Localized first-difference transitions: -- `U+2834 ⠴ -> U+2800 ⠀`: 257 +- `U+2834 ⠴ -> U+2800 ⠀`: 258 - `U+2834 ⠴ -> U+2820 ⠠`: 2 Mismatch primary-class distribution: @@ -1459,55 +1461,57 @@ The tight-triangle cohort is not an implementation premise. Hangeul rule 49 assi Corpus contradictions remain a separate gate: identical inputs with conflicting references are classified as `corpus_suspect` before these cohorts are recorded and would appear explicitly in each mismatch primary-class distribution. Their absence does not prove a reference correct; it only means that this deterministic contradiction test did not fire. -Current uppercase-Roman hyphen-digits measurement: 571 candidates, 183 exact controls, 388 mismatches, 373 members in the actual `pending_rule_review` subcluster, and 259/388 evaluable mismatches whose first difference is inside the target run plus its entry boundary. It remains distinct from parenthesized digits and headword expansions; no engine change is inferred. +Current uppercase-Roman hyphen-digits measurement: 571 candidates, 183 exact controls, 388 mismatches, 373 members in the actual `pending_rule_review` subcluster, and 260/388 evaluable mismatches whose first difference is inside the target run plus its entry boundary. It remains distinct from parenthesized digits and headword expansions; no engine change is inferred. Current single-capital parenthesized-digits measurement: 1361 candidates, 0 exact controls, 1361 mismatches, 1357 members in the actual `pending_rule_review` subcluster, and 1342/1361 evaluable mismatches whose first difference is inside the target run plus its entry boundary. No engine change is inferred from the ambiguous prose/function surface form. Current mixed Roman+Korean boundary before uppercase headword-expansion measurement: 10 candidates, 0 exact controls, 10 mismatches, 10 members in the actual `pending_rule_review` subcluster, and 8/10 evaluable mismatches whose first difference is localized to the later headword's entry boundary/output. The detector cannot be satisfied by the earlier Roman entry. No HCA-shaped engine routing rule is introduced. -Current compact numeric+ASCII-suffix measurement: 2975 candidates, 1649 exact controls, 1326 mismatches, 1259 members in the actual `pending_rule_review` subcluster, and 60/1326 evaluable mismatches whose first difference is inside the complete current-engine output signature. Rule 40 requires the numeric indicator and rule 69 requires Roman indicators around a Roman-written unit, but the input shape alone cannot prove that every ASCII suffix is a unit. +Current compact numeric+ASCII-suffix measurement: 2975 candidates, 1759 exact controls, 1216 mismatches, 1148 members in the actual `pending_rule_review` subcluster, and 251/1216 evaluable mismatches whose first difference is inside the complete current-engine output signature or its immediate entry boundary. Rule 40 requires the numeric indicator and rule 69 requires Roman indicators around a Roman-written unit, but the input shape alone cannot prove that every ASCII suffix is a unit. | ASCII suffix | Candidates | Exact | Mismatch | Localized first diff | |---|---:|---:|---:|---:| -| `m` | 364 | 251 | 113 | 25 | -| `km` | 308 | 157 | 151 | 1 | +| `m` | 364 | 255 | 109 | 22 | +| `km` | 308 | 225 | 83 | 3 | | `G` | 237 | 160 | 77 | 3 | -| `kg` | 209 | 168 | 41 | 6 | -| `D` | 167 | 110 | 57 | 3 | -| `p` | 140 | 19 | 121 | 0 | -| `g` | 138 | 95 | 43 | 0 | -| `t` | 128 | 82 | 46 | 0 | -| `M` | 104 | 77 | 27 | 0 | +| `kg` | 209 | 172 | 37 | 6 | +| `D` | 167 | 110 | 57 | 5 | +| `p` | 140 | 19 | 121 | 84 | +| `g` | 138 | 95 | 43 | 8 | +| `t` | 128 | 83 | 45 | 16 | +| `M` | 104 | 79 | 25 | 6 | | `cm` | 69 | 51 | 18 | 0 | | `B` | 62 | 12 | 50 | 0 | | `GB` | 61 | 25 | 36 | 4 | -| `GWh` | 57 | 42 | 15 | 0 | -| `ha` | 53 | 0 | 53 | 0 | +| `GWh` | 57 | 42 | 15 | 8 | +| `ha` | 53 | 18 | 35 | 0 | | `TV` | 52 | 2 | 50 | 1 | | `S` | 45 | 5 | 40 | 0 | -| `GW` | 44 | 35 | 9 | 0 | -| `X` | 39 | 12 | 27 | 0 | -| `bp` | 39 | 3 | 36 | 0 | +| `GW` | 44 | 35 | 9 | 6 | +| `X` | 39 | 12 | 27 | 2 | +| `bp` | 39 | 3 | 36 | 17 | | `K` | 38 | 10 | 28 | 1 | -| `MW` | 37 | 33 | 4 | 0 | +| `MW` | 37 | 35 | 2 | 0 | | `TURN` | 33 | 16 | 17 | 0 | -| `L` | 31 | 20 | 11 | 1 | +| `L` | 31 | 21 | 10 | 5 | | `mm` | 29 | 19 | 10 | 1 | | `egin` | 28 | 27 | 1 | 0 | -Current rule-69 ASCII-unit punctuation-boundary measurement: 440 candidates, 325 exact controls, 115 mismatches, 110 members in the actual `pending_rule_review` subcluster, and 5/115 evaluable mismatches whose first difference is localized to the unit-plus-punctuation output signature. PDF rule 69 requires a Roman terminator after a Roman-written unit in the ordinary case, while rules 33/34 omit it at the listed punctuation or enclosing-mark boundary; the rule 46 PDF example `체중(kg)` is the minimal parenthesized-unit control. Membership is restricted to rule-69 spellings already supported by the engine and does not infer unit semantics for arbitrary ASCII suffixes. +Entry-boundary pre-fix baseline: 2,975 candidates, 1,649 exact controls, 1,326 mismatches, 1,259 pending members, and 356 localized first differences; the dominant reference number-sign versus current space transition accounted for 296 cases. Rules 68 and 69 already accept semantic Unicode compatibility-unit forms. The implementation derives compact ASCII spellings only from their all-letter NFKC decompositions, reuses the owning rule's PDF-defined cells, chooses the longest complete spelling, and rejects partial suffix matches. It does not extend recognition to separated English words or arbitrary corpus suffixes. The same cohort now has 1,759 exact controls, 1,216 mismatches, 1,148 pending members, and 261 localized first differences; corpus-wide exact matches moved from 66,436 to 66,546 (+110). Rule 69's printed `160㎎/㎗` example directly controls `160mg`; the additional full-encoder `240mg`/`240㎎` pair proves that recognition is invariant to the numeric value and also passes. Rule 68's printed `10,000㎡는 1㏊이다` example controls the `ha` spelling through U+33CA's NFKC decomposition; `15.2ha`/`15.2㏊` now passes without a spelling-specific output branch. The 53-case `ha` suffix control moved from 0 exact to 18 exact; its remaining 35 mismatches have independent later or surrounding differences. A full U+3300..U+33FF owner audit found 73 Rules 68/69 glyphs with pure-ASCII NFKC decompositions, 73 distinct spellings, and therefore no current duplicate-spelling owner collision. Production nevertheless groups every owner before resolution and excludes a spelling if any owner cells differ; a synthetic collision test proves that this is not first-wins behavior. An exhaustive test also compares every one of the 73 derived spellings with every owner glyph at both the unit-cell and full-encoder boundaries in a neutral Korean measurement context. That audit exposed the pre-existing explicit `cal` mapping's missing ordinary terminator; rule 69 requires the terminator, while the existing slash-boundary function removes it for the printed `cal/㎠/min` context. Restoring the ordinary terminator and extending that slash-continuation boundary made the audit pass without changing the corpus-wide 66,546 exact total. Ambiguous pure-English inputs remain on UEB: the standard controls `3m` and `4.m` are retained and pass, rather than being globally forced into a Korean unit route. + +Current rule-69 ASCII-unit punctuation-boundary measurement: 440 candidates, 329 exact controls, 111 mismatches, 106 members in the actual `pending_rule_review` subcluster, and 10/111 evaluable mismatches whose first difference is localized to the unit-plus-punctuation output signature. PDF rule 69 requires a Roman terminator after a Roman-written unit in the ordinary case, while rules 33/34 omit it at the listed punctuation or enclosing-mark boundary; the rule 46 PDF example `체중(kg)` is the minimal parenthesized-unit control. Membership is restricted to rule-69 spellings already supported by the engine and does not infer unit semantics for arbitrary ASCII suffixes. Analyzer pre-fix baseline: 440 candidates, 0 exact controls, 440 mismatches, 435 pending members, and 9 signature-local first differences. After the generalized rule-33/34 boundary override and the matching non-math routing guard, the same cohort has 325 exact controls and 115 mismatches. Corpus-wide exact matches moved from 66,039 to 66,436 (+397); the additional gains are applications of the same boundary rule outside this strict ASCII detector, including compatibility-unit forms. The complete standard suite remains 5,141/5,141. -Current decimal-point measurement: 4546 candidates, 3187 exact controls, 1359 mismatches, 1304 members in the actual `pending_rule_review` subcluster, and 265/1359 evaluable mismatches whose first difference is inside the complete decimal-containing word's current-engine signature. Hangeul rules 43 and 48 keep an ASCII point between digits in the numeric sequence and encode it as the decimal point; rules 35 and 69 supply controls for adjacent Roman-number chains and Roman units. This is an implementation-candidate audit, not permission to specialize on a corpus reference. +Current decimal-point measurement: 4546 candidates, 3209 exact controls, 1337 mismatches, 1281 members in the actual `pending_rule_review` subcluster, and 262/1337 evaluable mismatches whose first difference is inside the complete decimal-containing word's current-engine signature. Hangeul rules 43 and 48 keep an ASCII point between digits in the numeric sequence and encode it as the decimal point; rules 35 and 69 supply controls for adjacent Roman-number chains and Roman units. This is an implementation-candidate audit, not permission to specialize on a corpus reference. Analyzer checkpoint `ed20f99` recorded the pre-fix baseline as 4,546 candidates, 2,584 exact controls, 1,962 mismatches, 1,905 pending members, and 875 localized first differences. Its dominant localized transition was reference decimal point `U+2832 ⠲` versus current Roman indicator `U+2834 ⠴` in 647 cases. After the generalized rule-43/48 guard, that transition is absent: 525 cases become exact and the other corrected prefixes expose later independent mismatches. Current all-caps `OU` measurement: 1816 candidates, 55 exact controls, 1761 mismatches, 1676 members in the actual `pending_rule_review` subcluster, and 1307/1761 evaluable mismatches whose first difference is inside the current-engine output signature for the detected run. This is a pronunciation-sensitive UEB review cohort, not an engine routing rule. -Current standalone-uppercase measurement: 62411 candidates, 49035 exact controls, 13376 mismatches, and 11932 members in the actual `pending_rule_review` subcluster. Its high frequency does not make it causal: the same input shape is exact in many cases, and a sentence containing the shape may first differ at another Roman, numeric, or punctuation structure. No engine change is inferred from this cohort. +Current standalone-uppercase measurement: 62411 candidates, 49088 exact controls, 13323 mismatches, and 11878 members in the actual `pending_rule_review` subcluster. Its high frequency does not make it causal: the same input shape is exact in many cases, and a sentence containing the shape may first differ at another Roman, numeric, or punctuation structure. No engine change is inferred from this cohort. -Current Korean-prefixed all-caps parenthetical measurement: 54492 candidates, 43953 exact controls, 10539 mismatches, and 9251 members in the actual `pending_rule_review` subcluster. This is a semantic-collision audit, not an engine routing rule; no implementation change is inferred from its reference outputs. +Current Korean-prefixed all-caps parenthetical measurement: 54492 candidates, 43994 exact controls, 10498 mismatches, and 9209 members in the actual `pending_rule_review` subcluster. This is a semantic-collision audit, not an engine routing rule; no implementation change is inferred from its reference outputs. -Current rule-34 opening-order measurement: 64382 structural candidates, 51305 exact controls, 13077 mismatches, and 1097/13077 evaluable mismatches whose first difference is inside the current engine's Korean opening-parenthesis cells. Only the 1096 localized first-cell transitions have the reference/current order `⠴` versus `⠦`. After requiring the complete reference `⠴⠐⠣` versus current/PDF `⠦⠄⠴` three-cell signature and preserving higher-priority comparison classifications, 1093 are classified as `corpus_suspect`; mere coexistence with a Korean-prefixed Roman annotation does not change a primary class. +Current rule-34 opening-order measurement: 64382 structural candidates, 51368 exact controls, 13014 mismatches, and 1098/13014 evaluable mismatches whose first difference is inside the current engine's Korean opening-parenthesis cells. Only the 1097 localized first-cell transitions have the reference/current order `⠴` versus `⠦`. After requiring the complete reference `⠴⠐⠣` versus current/PDF `⠦⠄⠴` three-cell signature and preserving higher-priority comparison classifications, 1094 are classified as `corpus_suspect`; mere coexistence with a Korean-prefixed Roman annotation does not change a primary class. Current all-caps Roman middle-dot measurement: 97 candidates, 0 exact controls, 97 mismatches, and 96 members in the actual `pending_rule_review` subcluster. This cross-cutting cohort preserves every primary class and is not an engine routing rule. @@ -1568,19 +1572,19 @@ Families are diagnostics, not automatic normalization permissions. Rules 68/69 c | Shard | Exact | Total | Accuracy | |---|---:|---:|---:| -| `sentence_01.json` | 19896 | 25000 | 79.58% | -| `sentence_02.json` | 19438 | 25000 | 77.75% | -| `sentence_03.json` | 20050 | 25000 | 80.20% | -| `sentence_04.json` | 7052 | 8528 | 82.69% | +| `sentence_01.json` | 19944 | 25000 | 79.78% | +| `sentence_02.json` | 19463 | 25000 | 77.85% | +| `sentence_03.json` | 20072 | 25000 | 80.29% | +| `sentence_04.json` | 7067 | 8528 | 82.87% | ## Overlapping mismatch traits | Trait | Count | |---|---:| -| `contains_ascii_digits` | 13441 | -| `contains_ascii_letters` | 16906 | -| `contains_delimiter_or_quote` | 17092 | -| `input_not_nfkc` | 735 | +| `contains_ascii_digits` | 13331 | +| `contains_ascii_letters` | 16796 | +| `contains_delimiter_or_quote` | 16982 | +| `input_not_nfkc` | 727 | ## Samples @@ -1598,6 +1602,9 @@ Families are diagnostics, not automatic normalization permissions. Rules 68/69 c - `sentence_01.json` #35: 삼성전자는 올해 CES에서 77인치 OLED TV를 처음으로 공개할 예정이다. 이는 지난 8월 ‘국제정보디스플레이학술대회(IMID) 2022’에서 삼성디스플레이가 공개한 77인치 퀀텀닷(QD)-OLED 패널을 탑재한 제품이다. - expected: `⠋⠏⠒⠓⠎⠢⠊⠄⠴⠐⠣⠟⠙⠐⠜⠤⠠⠠⠕⠇⠫⠲⠀⠙` - actual: `⠋⠏⠒⠓⠎⠢⠊⠄⠀⠀⠦⠠⠠⠟⠙⠴⠔⠠⠠⠕⠇⠑⠙⠀` +- `sentence_01.json` #38: 올해 소프트웨어(SW) 업계는 국내 인공지능(AI) 제품·서비스 고도화와 함께 전 산업영역에서 SW영역 확대가 가시화될 것으로 보인다. 또한 블록체인이 웹3(Web3)와 연계해 탈중앙화·탈독점화 경제체계로 진화할 것으로 예상된다. + - expected: `⠟⠕⠀⠏⠗⠃⠼⠉⠦⠄⠴⠠⠺⠑⠃⠼⠉⠠⠴⠧⠀⠡⠈⠌` + - actual: `⠟⠕⠀⠏⠗⠃⠼⠉⠴⠐⠣⠠⠺⠑⠃⠼⠉⠴⠐⠜⠲⠧⠀⠡` ### `nfkc_input_equivalent` @@ -1617,6 +1624,10 @@ Families are diagnostics, not automatic normalization permissions. Rules 68/69 c - expected: `⠈⠻⠇⠶⠉⠢⠊⠥⠀⠈⠕⠢⠚⠗⠠⠕⠝⠀⠠⠥⠨⠗⠚⠒` - actual: `` - error: `Invalid symbol character` +- `sentence_01.json` #3149: 한편 공동연구개발기관인 농업회사법인 ㈜영풍은 대구시 스타기업으로 현재 떡볶이, 전, 스낵류 등 가정간편식(HMR) 제품을 80개국에 수출하고 있다. + - expected: `⠚⠒⠙⠡⠀⠈⠿⠊⠿⠡⠈⠍⠈⠗⠘⠂⠈⠕⠈⠧⠒⠟⠀⠉` + - actual: `` + - error: `Invalid symbol character` ### `number_rule_review` @@ -1632,6 +1643,9 @@ Families are diagnostics, not automatic normalization permissions. Rules 68/69 c - `sentence_01.json` #9055: 한국건설기술연구원에 따르면 주거용 건물 건설공사비지수는 올해 9월 기준 152.76(잠정치)으로 전년 동월(147.31) 대비 약 3.7%가량 증가했다. - expected: `⠾⠉⠡⠀⠊⠿⠏⠂⠦⠄⠼⠁⠙⠛⠲⠉⠁⠠⠴⠀⠊⠗⠘⠕` - actual: `⠾⠉⠡⠀⠊⠿⠏⠂⠀⠀⠦⠼⠁⠙⠛⠲⠉⠁⠴⠀⠊⠗⠘⠕` +- `sentence_01.json` #10139: ‘서울 반려견 순찰대’는 8개월 동안의 올해 성과 보고를 통해 △범죄예방(112) 신고 317건 △생활위험 관련(120) 신고 2187건 등으로 지역사회 안전망을 촘촘하게 구축하는 데 이바지했다고 발표했다. + - expected: `⠀⠓⠿⠚⠗⠀⠸⠬⠀⠘⠎⠢⠨⠽⠤⠌⠘⠶⠦⠄⠼⠁⠁⠃` + - actual: `⠀⠓⠿⠚⠗⠀⠸⠬⠇⠘⠎⠢⠨⠽⠤⠌⠘⠶⠦⠄⠼⠁⠁⠃` ### `punctuation_rule_review` @@ -1647,6 +1661,9 @@ Families are diagnostics, not automatic normalization permissions. Rules 68/69 c - `sentence_03.json` #19763: 디즈니+ 드라마 ‘무빙’을 언급하던 한효주 곁에 다가온 조인성은 “굉장히 무서운 와이프였어요~”라고 극 중 아내 자랑(?)을 하며 남편 손님과 눈빛 교환을 하는 모습으로 폭소를 안긴다. - expected: `⠊⠕⠨⠪⠉⠕⠀⠢⠀⠊⠪⠐⠣⠑⠀⠠⠦⠑⠍⠘⠕⠶⠴⠄` - actual: `⠊⠕⠨⠪⠉⠕⠀⠀⠢⠀⠊⠪⠐⠣⠑⠀⠠⠦⠑⠍⠘⠕⠶⠴` +- `sentence_03.json` #21665: 그러자 김숙은 “그러면 우재야. ‘홍김동전’ 잠깐 쉬어라”며 급 하차 권유(?)를 하고, 홍진경은 “김치 없냐”면서 느닷없이 김치를 찾는 등 총체적 난국이 펼쳐졌다. + - expected: `⠕⠢⠠⠍⠁⠵⠀⠦⠁⠒⠀⠍⠨⠗⠜⠲⠀⠠⠦⠚⠿⠈⠕⠢` + - actual: `⠕⠢⠠⠍⠁⠵⠀⠦⠈⠪⠐⠎⠑⠡⠀⠍⠨⠗⠜⠲⠀⠠⠦⠚` ### `rule34_roman_indicator_before_opening_parenthesis` @@ -1662,6 +1679,9 @@ Families are diagnostics, not automatic normalization permissions. Rules 68/69 c - `sentence_01.json` #83: 삼성전자는 이번 행사에서 77형 유기발광다이오드(OLED) TV를 첫 공개하기도 했다. 지난해 처음 출시한 삼성 OLED TV는 55형, 65형과 함께 초대형 77형 모델이 추가된 셈이다. - expected: `⠧⠶⠊⠣⠕⠥⠊⠪⠴⠐⠣⠠⠠⠕⠇⠫⠐⠜⠀⠠⠠⠞⠧⠲` - actual: `⠧⠶⠊⠣⠕⠥⠊⠪⠦⠄⠴⠠⠠⠕⠇⠫⠠⠴⠀⠴⠠⠠⠞⠧` +- `sentence_01.json` #85: 클레이튼 커쇼와 함께 메이저리그(MLB) LA 다저스 선발 마운드를 든든하게 지키고 있는 훌리오 우리아스가 2023 월드베이스볼클래식(WBC)에서 멕시코대표팀 주장을 맡는다. + - expected: `⠝⠕⠨⠎⠐⠕⠈⠪⠴⠐⠣⠠⠠⠍⠇⠃⠐⠜⠀⠠⠠⠇⠁⠲` + - actual: `⠝⠕⠨⠎⠐⠕⠈⠪⠦⠄⠴⠠⠠⠍⠇⠃⠠⠴⠀⠴⠠⠠⠇⠁` ### `unsupported_character_review` @@ -1681,6 +1701,10 @@ Families are diagnostics, not automatic normalization permissions. Rules 68/69 c - expected: `⠊⠗⠈⠍⠈⠧⠶⠱⠁⠠⠕⠦⠄⠠⠕⠨⠶⠀⠚⠿⠨⠛⠙⠬` - actual: `` - error: `Invalid symbol character` +- `sentence_01.json` #2336: 한편, 한국팀 경기를 포함한 아시아컵 주요 경기는 세계야구소프트볼연맹(WBSC) 공식 유튜브에서 확인할 수 있다. 이번 대회 운영 대행은 ㈜에스피에스 코리아(SPS KOREA)가 맡아 진행한다. + - expected: `⠚⠒⠙⠡⠐⠀⠚⠒⠈⠍⠁⠓⠕⠢⠀⠈⠻⠈⠕⠐⠮⠀⠙⠥` + - actual: `` + - error: `Invalid symbol character` ## PDF-derived state gates @@ -1735,6 +1759,7 @@ Against the immediately preceding 63,399-exact run, exact matches increased by 2 | Rules 34/54 Korean-prefixed closed Roman annotation routing | 5,141/5,141 | 65,491/83,528 | 78.41% | A fully closed Roman annotation after an all-Korean prefix stays on the prose encoder path, including attached comma/period and alphanumeric forms such as `O4O`; exact matches increased by 2,092 while the global math detector remained unchanged | | Rules 43/48 decimal-point ownership | 5,141/5,141 | 66,039/83,528 | 79.06% | A period directly between ASCII digits remains on the numeric punctuation path even when its word or sentence also contains Roman text; 647 localized Roman-entry differences were removed and 525 cases became exact | | Rules 33/34/69 Roman-unit punctuation boundary | 5,141/5,141 | 66,436/83,528 | 79.54% | Rule-69 units retain their ordinary terminator at end/Korean/slash boundaries but omit it before rule-33/34 punctuation or enclosing marks; compact unit tokens with that boundary stay off the math path; 397 cases became exact | +| Rules 68/69 compact compatibility-derived ASCII units | 5,141/5,141 | 66,546/83,528 | 79.67% | Compact ASCII unit spellings are derived from the engine's already accepted Unicode compatibility-unit forms and reuse their owning-rule cells, with longest-complete matching and no expansion to separated English words; `160mg`, numeric-invariance control `240mg`, and Rule-68 `ha` controls are retained; 110 cases became exact | The latest full `cargo test -p braillify test_by_testcase --release -- --nocapture` run was accepted from its custom testcase summary, not the trailing filtered harness: `총 테스트 케이스: 5141`, `성공: 5141`, `실패: 0`, and `Skip (limitation): 0`. diff --git a/libs/braillify/examples/nikl_corpus_analyze.rs b/libs/braillify/examples/nikl_corpus_analyze.rs index 3db76382..bf594e37 100644 --- a/libs/braillify/examples/nikl_corpus_analyze.rs +++ b/libs/braillify/examples/nikl_corpus_analyze.rs @@ -752,7 +752,7 @@ fn first_difference_in_compact_numeric_ascii_suffix_spans( return false; } let first_difference = first_difference_cell(&item.located.case.unicode, actual); - korean_context_signature_ranges(&item.located.case.input, actual, spans, 0) + korean_context_signature_ranges(&item.located.case.input, actual, spans, 1) .into_iter() .any(|range| range.contains(&first_difference)) } @@ -2356,7 +2356,8 @@ fn markdown(report: &AnalysisReport) -> String { whitespace-delimited words containing `digit.digit` and reproduces each whole word in a \ neutral Korean context, so suffixes and punctuation remain part of the current-engine \ signature. The `compact_numeric_ascii_letter_suffix` gate finds a numeric prefix \ - immediately followed by ASCII letters and retains suffix-specific outcome counts. It \ + immediately followed by ASCII letters, includes the immediately preceding output cell \ + as its entry boundary, and retains suffix-specific outcome counts. It \ intentionally includes both possible rule-69 units and ambiguous variable/identifier \ forms; membership alone does not assign unit semantics. The \ `rule69_ascii_unit_before_terminator_skipping_symbol` gate is narrower: it accepts only \ @@ -2631,8 +2632,9 @@ fn markdown(report: &AnalysisReport) -> String { "\nCurrent compact numeric+ASCII-suffix measurement: {} candidates, {} exact \ controls, {} mismatches, {pending} members in the actual `pending_rule_review` \ subcluster, and {}/{} evaluable mismatches whose first difference is inside the \ - complete current-engine output signature. Rule 40 requires the numeric indicator \ - and rule 69 requires Roman indicators around a Roman-written unit, but the input \ + complete current-engine output signature or its immediate entry boundary. Rule 40 \ + requires the numeric indicator and rule 69 requires Roman indicators around a \ + Roman-written unit, but the input \ shape alone cannot prove that every ASCII suffix is a unit.\n\n", stats.candidates, stats.exact, @@ -2664,6 +2666,39 @@ fn markdown(report: &AnalysisReport) -> String { suffix_stats.first_difference_in_output_signature )); } + text.push_str( + "\nEntry-boundary pre-fix baseline: 2,975 candidates, 1,649 exact controls, 1,326 \ + mismatches, 1,259 pending members, and 356 localized first differences; the \ + dominant reference number-sign versus current space transition accounted for 296 \ + cases. Rules 68 and 69 already accept semantic Unicode compatibility-unit forms. \ + The implementation derives compact ASCII spellings only from their all-letter NFKC \ + decompositions, reuses the owning rule's PDF-defined cells, chooses the longest \ + complete spelling, and rejects partial suffix matches. It does not extend \ + recognition to separated English words or arbitrary corpus suffixes. The same \ + cohort now has 1,759 exact controls, 1,216 mismatches, 1,148 pending members, and \ + 261 localized first differences; corpus-wide exact matches moved from 66,436 to \ + 66,546 (+110). Rule 69's printed `160㎎/㎗` example directly controls `160mg`; the \ + additional full-encoder `240mg`/`240㎎` pair proves that recognition is invariant \ + to the numeric value and also passes. Rule 68's printed `10,000㎡는 1㏊이다` example \ + controls the `ha` spelling through U+33CA's NFKC decomposition; `15.2ha`/`15.2㏊` \ + now passes without a spelling-specific output branch. The 53-case `ha` suffix \ + control moved from 0 exact to 18 exact; its remaining 35 mismatches have independent \ + later or surrounding differences. A full U+3300..U+33FF owner audit found 73 \ + Rules 68/69 glyphs with pure-ASCII NFKC decompositions, 73 distinct spellings, and \ + therefore no current duplicate-spelling owner collision. Production nevertheless \ + groups every owner before resolution and excludes a spelling if any owner cells \ + differ; a synthetic collision test proves that this is not first-wins behavior. An \ + exhaustive test also compares every one of the 73 derived spellings with every \ + owner glyph at both the unit-cell and full-encoder boundaries in a neutral Korean \ + measurement context. That audit exposed \ + the pre-existing explicit `cal` mapping's missing ordinary terminator; rule 69 \ + requires the terminator, while the existing slash-boundary function removes it for \ + the printed `cal/㎠/min` context. Restoring the ordinary terminator and extending \ + that slash-continuation boundary made the audit pass without changing the \ + corpus-wide 66,546 exact total. Ambiguous pure-English inputs remain on UEB: the \ + standard controls `3m` and `4.m` are retained and pass, rather than being globally \ + forced into a Korean unit route.\n", + ); } if let Some(stats) = report .pending_rule_review_clusters @@ -3161,6 +3196,9 @@ fn markdown(report: &AnalysisReport) -> String { text.push_str( "| Rules 33/34/69 Roman-unit punctuation boundary | 5,141/5,141 | 66,436/83,528 | 79.54% | Rule-69 units retain their ordinary terminator at end/Korean/slash boundaries but omit it before rule-33/34 punctuation or enclosing marks; compact unit tokens with that boundary stay off the math path; 397 cases became exact |\n", ); + text.push_str( + "| Rules 68/69 compact compatibility-derived ASCII units | 5,141/5,141 | 66,546/83,528 | 79.67% | Compact ASCII unit spellings are derived from the engine's already accepted Unicode compatibility-unit forms and reuse their owning-rule cells, with longest-complete matching and no expansion to separated English words; `160mg`, numeric-invariance control `240mg`, and Rule-68 `ha` controls are retained; 110 cases became exact |\n", + ); text.push_str( "\nThe latest full `cargo test -p braillify test_by_testcase --release -- --nocapture` \ run was accepted from its custom testcase summary, not the trailing filtered harness: \ @@ -3428,18 +3466,21 @@ mod tests { } #[test] - fn locates_compact_numeric_ascii_suffix_in_current_output() { + fn locates_compact_numeric_ascii_suffix_and_entry_boundary_in_current_output() { let input = "용량은 13GWh 규모다."; let actual = braillify::encode_to_unicode(input).expect("compact suffix probe must encode"); - let ranges = korean_context_signature_ranges( - input, - &actual, - &compact_numeric_ascii_suffix_spans(input), - 0, - ); + let spans = compact_numeric_ascii_suffix_spans(input); + let ranges = korean_context_signature_ranges(input, &actual, &spans, 1); + let signature = korean_context_signature(&input[spans[0].start_byte..spans[0].end_byte]) + .expect("compact suffix signature must encode"); + let signature_start_byte = actual + .find(&signature) + .expect("current output must contain compact suffix signature"); + let signature_start = actual[..signature_start_byte].chars().count(); assert_eq!(ranges.len(), 1); - assert!(ranges[0].start < ranges[0].end); + assert_eq!(ranges[0].start + 1, signature_start); + assert_eq!(ranges[0].end, signature_start + signature.chars().count()); } #[rstest::rstest] diff --git a/libs/braillify/src/rules/korean/rule_68.rs b/libs/braillify/src/rules/korean/rule_68.rs index d7e18bb2..5b484629 100644 --- a/libs/braillify/src/rules/korean/rule_68.rs +++ b/libs/braillify/src/rules/korean/rule_68.rs @@ -47,6 +47,16 @@ pub fn is_rule_68_symbol(c: char) -> bool { MAPPINGS.iter().any(|(candidate, _)| *candidate == c) } +/// Return the PDF-defined cells for a single Rule 68 symbol. Rule 69 reuses +/// this owning-rule encoding when a supported compatibility unit has a pure +/// ASCII NFKC spelling (for example, the `ha` spelling of `㏊`). +pub(crate) fn encode_rule_68_symbol(c: char) -> Option> { + MAPPINGS + .iter() + .find(|(candidate, _)| *candidate == c) + .map(|(_, unicode)| encode_unicode_cells(unicode)) +} + fn is_superscript_symbol(c: char) -> bool { matches!(c, '⁺' | '⁻') } @@ -214,13 +224,9 @@ impl BrailleRule for Rule68 { return Ok(RuleResult::Consumed); } - let Some((_, unicode)) = MAPPINGS - .iter() - .find(|(candidate, _)| *candidate == ctx.current_char()) - else { + let Some(encoded) = encode_rule_68_symbol(ctx.current_char()) else { return Ok(RuleResult::Skip); }; - let encoded = encode_unicode_cells(unicode); ctx.emit_slice(&encoded); if should_insert_separator_after_symbol(ctx) { ctx.emit(0); diff --git a/libs/braillify/src/rules/korean/rule_69.rs b/libs/braillify/src/rules/korean/rule_69.rs index 333811fe..fdbe6ac7 100644 --- a/libs/braillify/src/rules/korean/rule_69.rs +++ b/libs/braillify/src/rules/korean/rule_69.rs @@ -1,3 +1,6 @@ +use std::collections::BTreeMap; +use std::sync::OnceLock; + use crate::char_struct::CharType; use crate::rules::RuleMeta; use crate::rules::context::RuleContext; @@ -32,7 +35,7 @@ const ASCII_UNIT_MAPPINGS: &[(&str, &str)] = &[ ("in", "⠴⠊⠝⠲"), ("mm", "⠴⠍⠍⠲"), ("min", "⠍⠔⠲"), - ("cal", "⠴⠉⠁⠇"), + ("cal", "⠴⠉⠁⠇⠲"), ("GB", "⠴⠠⠠⠛⠃⠲"), ("m", "⠴⠍⠲"), ("h", "⠴⠓⠲"), @@ -229,15 +232,98 @@ fn chars_start_with_ascii(tail: &[char], s: &str) -> bool { s.bytes().zip(tail.iter()).all(|(b, c)| (b as char) == *c) } +/// ASCII spellings that are canonically exposed by the same Unicode +/// compatibility-unit family already accepted above. This derives the unit +/// lexicon from semantic unit code points instead of maintaining a second +/// corpus-shaped list (`㎞` -> `km`, `㎎` -> `mg`, `㎾` -> `kW`, ...). +fn compatibility_ascii_unit_candidate(glyph: char) -> Option<(String, Vec)> { + let parts = glyph.to_string().nfkc().collect::>(); + if !parts.iter().all(char::is_ascii_alphabetic) { + return None; + } + let encoded = if compatibility_unit_decomposition(glyph).is_some() { + encode_compatibility_unit(&parts, true, true).ok()? + } else { + super::rule_68::encode_rule_68_symbol(glyph)? + }; + Some((parts.into_iter().collect(), encoded)) +} + +fn compatibility_ascii_unit_owners() -> BTreeMap)>> { + let mut by_spelling = BTreeMap::)>>::new(); + for glyph in (0x3300..=0x33ff).filter_map(char::from_u32) { + if let Some((spelling, encoded)) = compatibility_ascii_unit_candidate(glyph) { + by_spelling + .entry(spelling) + .or_default() + .push((glyph, encoded)); + } + } + by_spelling +} + +fn retain_unambiguous_ascii_unit_spellings( + owners_by_spelling: BTreeMap)>>, +) -> Vec<(String, Vec)> { + let mut spellings = owners_by_spelling + .into_iter() + .filter_map(|(spelling, owners)| { + let first = &owners.first()?.1; + owners + .iter() + .all(|(_, encoded)| encoded == first) + .then(|| (spelling, first.clone())) + }) + .collect::>(); + spellings.sort_by(|left, right| { + right + .0 + .len() + .cmp(&left.0.len()) + .then_with(|| left.0.cmp(&right.0)) + }); + spellings +} + +fn compatibility_ascii_unit_spellings() -> &'static [(String, Vec)] { + static SPELLINGS: OnceLock)>> = OnceLock::new(); + SPELLINGS + .get_or_init(|| retain_unambiguous_ascii_unit_spellings(compatibility_ascii_unit_owners())) +} + pub(crate) fn encode_ascii_unit(word: &[char], index: usize) -> Option<(Vec, usize)> { let tail = &word[index..]; - for (unit, unicode) in ASCII_UNIT_MAPPINGS { - if !chars_start_with_ascii(tail, unit) { - continue; + ASCII_UNIT_MAPPINGS + .iter() + .filter(|(unit, _)| chars_start_with_ascii(tail, unit)) + .max_by_key(|(unit, _)| unit.len()) + .map(|(unit, unicode)| (encode_unicode_cells(unicode), unit.len())) +} + +/// Numeric-compact Rule 69 path. Compatibility-derived spellings are limited +/// to this measured boundary so an unrelated English word after a separated +/// number cannot become a unit merely because it starts with a unit spelling. +fn encode_numeric_ascii_unit(word: &[char], index: usize) -> Option<(Vec, usize)> { + let tail = &word[index..]; + let explicit = encode_ascii_unit(word, index); + let derived = compatibility_ascii_unit_spellings() + .iter() + .filter(|(unit, _)| chars_start_with_ascii(tail, unit)) + .max_by_key(|(unit, _)| unit.len()); + + if let Some((encoded, consumed)) = explicit { + match derived { + Some((candidate, derived_encoded)) if consumed == candidate.len() => { + return (encoded.as_slice() == derived_encoded.as_slice()) + .then_some((encoded, consumed)); + } + Some((candidate, _)) if consumed < candidate.len() => {} + _ => return Some((encoded, consumed)), } - return Some((encode_unicode_cells(unicode), unit.len())); } - None + + let (unit, encoded) = derived?; + Some((encoded.clone(), unit.len())) } fn encode_percent_abbreviation(word: &[char], index: usize) -> Option<(Vec, usize)> { @@ -268,7 +354,13 @@ pub(crate) fn parse_numeric_ascii_unit_prefix(word: &[char]) -> Option<(String, } let numeric = word[..numeric_len].iter().collect::(); - let (unit, consumed) = encode_ascii_unit(word, numeric_len)?; + let (unit, consumed) = encode_numeric_ascii_unit(word, numeric_len)?; + if word + .get(numeric_len + consumed) + .is_some_and(|ch| ch.is_ascii_alphabetic()) + { + return None; + } Some((numeric, unit, numeric_len + consumed)) } @@ -291,9 +383,14 @@ fn omit_roman_terminator_before_boundary( word: &[char], boundary_index: usize, ) { - if word + let skips_for_punctuation = word .get(boundary_index) - .is_some_and(|symbol| crate::english_logic::should_skip_terminator_for_symbol(*symbol)) + .is_some_and(|symbol| crate::english_logic::should_skip_terminator_for_symbol(*symbol)); + let continues_through_slash = word.get(boundary_index) == Some(&'/') + && word + .get(boundary_index + 1) + .is_some_and(|next| is_roman_unit_component(*next)); + if (skips_for_punctuation || continues_through_slash) && encoded.last() == Some(&crate::unicode::decode_unicode('⠲')) { encoded.pop(); @@ -441,10 +538,11 @@ impl BrailleRule for Rule69 { #[cfg(test)] mod tests { use super::{ - Rule69, compatibility_unit_decomposition, encode_ascii_unit, encode_compatibility_unit, + Rule69, compatibility_ascii_unit_owners, compatibility_unit_decomposition, + encode_ascii_unit, encode_compatibility_unit, encode_numeric_ascii_unit, encode_percent_abbreviation, encode_rule_69_unit_letters, encode_unicode_cells, omit_roman_terminator_before_boundary, parse_numeric_ascii_unit_prefix, - word_looks_like_unit_chain, + retain_unambiguous_ascii_unit_spellings, word_looks_like_unit_chain, }; #[rstest::rstest] @@ -525,6 +623,104 @@ mod tests { } } + #[test] + fn every_rule_68_or_69_ascii_derivation_matches_every_owner_glyph() { + for (spelling, owners) in compatibility_ascii_unit_owners() { + let first = &owners[0].1; + for (glyph, owner_encoding) in &owners { + assert_eq!( + owner_encoding, first, + "conflicting owner cells for NFKC spelling {spelling:?}: U+{:04X}", + *glyph as u32 + ); + + let chars = spelling.chars().collect::>(); + let (derived, consumed) = encode_numeric_ascii_unit(&chars, 0) + .unwrap_or_else(|| { + panic!( + "unambiguous ASCII compatibility-unit spelling {spelling:?} from U+{:04X} must be recognized", + *glyph as u32 + ) + }); + assert_eq!(consumed, spelling.len(), "partial match for {spelling}"); + assert_eq!( + &derived, owner_encoding, + "derived cells differ from owner U+{:04X} for {spelling}", + *glyph as u32 + ); + + let ascii_input = format!("값은 1{spelling}이다"); + let glyph_input = format!("값은 1{glyph}이다"); + assert_eq!( + crate::encode_to_unicode(&ascii_input).unwrap(), + crate::encode_to_unicode(&glyph_input).unwrap(), + "full encoder differs for {spelling} and owner U+{:04X}", + *glyph as u32 + ); + } + } + } + + #[test] + fn conflicting_nfkc_owner_cells_are_excluded_instead_of_first_wins() { + let owners = std::collections::BTreeMap::from([ + ( + "safe".to_string(), + vec![('A', vec![1, 2]), ('B', vec![1, 2])], + ), + ("conflict".to_string(), vec![('C', vec![3]), ('D', vec![4])]), + ]); + + let resolved = retain_unambiguous_ascii_unit_spellings(owners); + + assert!(resolved.iter().any(|(spelling, _)| spelling == "safe")); + assert!(resolved.iter().all(|(spelling, _)| spelling != "conflict")); + } + + #[rstest::rstest] + #[case::kilometre("80km", "80㎞")] + #[case::pdf_milligram("160mg", "160㎎")] + #[case::numeric_invariance_milligram("240mg", "240㎎")] + #[case::kilowatt("30kW", "30㎾")] + #[case::megahertz("96.7MHz", "96.7㎒")] + #[case::hectare("15.2ha", "15.2㏊")] + fn compact_ascii_units_match_supported_compatibility_forms( + #[case] ascii: &str, + #[case] compatibility: &str, + ) { + let ascii = format!("값은 {ascii}이다"); + let compatibility = format!("값은 {compatibility}이다"); + assert_eq!( + crate::encode_to_unicode(&ascii).unwrap(), + crate::encode_to_unicode(&compatibility).unwrap() + ); + } + + #[rstest::rstest] + #[case::letter_after_digit("3m", "⠼⠉⠍")] + #[case::letter_after_decimal_punctuation("4.m", "⠼⠙⠲⠍")] + fn pure_english_ambiguous_suffixes_remain_on_ueb_path( + #[case] input: &str, + #[case] expected: &str, + ) { + assert_eq!(crate::encode_to_unicode(input).unwrap(), expected); + } + + #[rstest::rstest] + #[case::longest_derived("30mW", 4)] + #[case::hectare_derived("15.2ha", 6)] + #[case::reject_partial_suffix("30kWh", 0)] + fn parses_only_complete_compatibility_derived_units( + #[case] input: &str, + #[case] expected_consumed: usize, + ) { + let chars = input.chars().collect::>(); + assert_eq!( + parse_numeric_ascii_unit_prefix(&chars).map_or(0, |(_, _, consumed)| consumed), + expected_consumed + ); + } + #[rstest::rstest] #[case::inch('㏌', "in")] #[case::centimetre('㎝', "cm")] @@ -606,6 +802,7 @@ mod tests { /// the ordinary Roman terminator. #[rstest::rstest] #[case::end_of_input("180cm", "⠴⠉⠍⠲")] + #[case::calorie_at_end("열량은 3cal", "⠴⠉⠁⠇⠲")] #[case::before_korean("1m는", "⠴⠍⠲")] #[case::before_forced_slash("3m/시", "⠴⠍⠲⠸⠌")] fn retains_unit_terminator_at_ordinary_rule_69_boundary( diff --git a/libs/braillify/src/rules/token_rules/math_expression/detect.rs b/libs/braillify/src/rules/token_rules/math_expression/detect.rs index ea48dc2e..3dff5afa 100644 --- a/libs/braillify/src/rules/token_rules/math_expression/detect.rs +++ b/libs/braillify/src/rules/token_rules/math_expression/detect.rs @@ -317,6 +317,11 @@ mod tests { /// attached to the compact unit token without turning the unit into math. #[rstest::rstest] #[case::ordinary_unit("180cm", false)] + #[case::derived_kilometre("80km", false)] + #[case::derived_milligram("240mg", false)] + #[case::derived_kilowatt("30kW", false)] + #[case::derived_megahertz("96.7MHz", false)] + #[case::derived_hectare("15.2ha", false)] #[case::unit_before_comma("173cm,", false)] #[case::unit_before_closing_parenthesis("20kg)", false)] #[case::unit_before_period("130m.", false)] From 3c81dadff1a6401d8fa0b8ae22ed2d5052854358 Mon Sep 17 00:00:00 2001 From: devfive Date: Tue, 1 Sep 2026 10:06:40 +0900 Subject: [PATCH 25/77] Diagnose UEB grade-1 transition cohorts --- docs/corpus-analysis/NIKL_2025_V1.md | 427 +++++++++++--- .../braillify/examples/nikl_corpus_analyze.rs | 526 +++++++++++++++++- 2 files changed, 886 insertions(+), 67 deletions(-) diff --git a/docs/corpus-analysis/NIKL_2025_V1.md b/docs/corpus-analysis/NIKL_2025_V1.md index 0f59320e..6d0187ac 100644 --- a/docs/corpus-analysis/NIKL_2025_V1.md +++ b/docs/corpus-analysis/NIKL_2025_V1.md @@ -298,72 +298,26 @@ This ranking removes only cases whose first difference is inside an existing out | Rank | Expected → actual first cell | Residual cases | |---:|---|---:| -| 1 | `U+2830 ⠰ -> U+2820 ⠠` | 1015 | -| 2 | `U+2820 ⠠ -> U+2830 ⠰` | 799 | -| 3 | `U+2834 ⠴ -> U+2800 ⠀` | 545 | -| 4 | `U+2820 ⠠ -> U+2834 ⠴` | 454 | -| 5 | `U+2826 ⠦ -> U+2834 ⠴` | 447 | -| 6 | `U+280E ⠎ -> U+280C ⠌` | 444 | -| 7 | `U+2808 ⠈ -> U+2832 ⠲` | 407 | -| 8 | `U+2826 ⠦ -> U+2800 ⠀` | 389 | -| 9 | `U+2801 ⠁ -> U+281C ⠜` | 384 | -| 10 | `U+2834 ⠴ -> U+2830 ⠰` | 325 | -| 11 | `U+2811 ⠑ -> U+282B ⠫` | 321 | -| 12 | `U+2810 ⠐ -> U+2832 ⠲` | 285 | -| 13 | `U+2832 ⠲ -> U+2838 ⠸` | 275 | -| 14 | `U+2800 ⠀ -> U+2832 ⠲` | 225 | -| 15 | `U+2810 ⠐ -> U+2802 ⠂` | 210 | -| 16 | `U+2817 ⠗ -> U+2820 ⠠` | 161 | -| 17 | `U+283C ⠼ -> U+2800 ⠀` | 153 | -| 18 | `U+2820 ⠠ -> U+2809 ⠉` | 152 | -| 19 | `U+281B ⠛ -> U+2823 ⠣` | 144 | -| 20 | `U+2826 ⠦ -> U+2810 ⠐` | 139 | - -### Residual `U+2830 ⠰ -> U+2820 ⠠` - -- `sentence_01.json` #310: 낸드플래시 반도체 세계 2위인 일본 키옥시아와 4위인 미국 웨스턴디지털(WD) 합병설에 반도체 업계가 촉각을 곤두 세우고 있다. - - expected: `⠕⠨⠕⠓⠞⠦⠄⠴⠰⠠⠠⠺⠙⠠⠴⠀⠚⠃⠘⠻⠠⠞⠝⠀` - - actual: `⠕⠨⠕⠓⠞⠦⠄⠴⠠⠠⠺⠙⠠⠴⠀⠚⠃⠘⠻⠠⠞⠝⠀⠘` - - first differing cell (zero-based): 70 - - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_02.json` #99: 황승주 씨는 “기능성 소화불량증, 특히 PDS(식후불편감증후군) 복부팽만감 유형으로 고통 받는 환자들에게 적용가능한 안전하고 높은 치료효율의 한약소재를 개발해 국민건강에 이바지하고자 한다”고 말했다. - - expected: `⠀⠓⠪⠁⠚⠕⠀⠴⠰⠠⠠⠏⠙⠎⠦⠄⠠⠕⠁⠚⠍⠘⠯⠙` - - actual: `⠀⠓⠪⠁⠚⠕⠀⠴⠠⠠⠏⠙⠎⠦⠄⠠⠕⠁⠚⠍⠘⠯⠙⠡` - - first differing cell (zero-based): 45 - - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_03.json` #30: “챗GPT보다 가벼운 여러 대규모언어모델(LLM)이 출현해 오픈AI 독점 구도에 균열을 내고 있다. 한국어 특화 LLM으로 글로벌 경쟁에 대응하면서 세부 서비스(튜닝) 모델에서 차별성을 확보하는 전략이 중요하다.” - - expected: `⠑⠥⠊⠝⠂⠦⠄⠴⠰⠠⠠⠇⠇⠍⠠⠴⠕⠀⠰⠯⠚⠡⠚⠗` - - actual: `⠑⠥⠊⠝⠂⠦⠄⠴⠠⠠⠇⠇⠍⠠⠴⠕⠀⠰⠯⠚⠡⠚⠗⠀` - - first differing cell (zero-based): 40 - - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_04.json` #81: SGI는 “산업연관분석을 활용해 우리나라의 대일 수출 증가가 국내총생산(GDP)에 미치는 영향을 계산해 보면 경제성장률은 0.1%포인트 높아질 것”이라고 밝혔다. - - expected: `⠠⠗⠶⠇⠒⠦⠄⠴⠰⠠⠠⠛⠙⠏⠠⠴⠝⠀⠑⠕⠰⠕⠉⠵` - - actual: `⠠⠗⠶⠇⠒⠦⠄⠴⠠⠠⠛⠙⠏⠠⠴⠝⠀⠑⠕⠰⠕⠉⠵⠀` - - first differing cell (zero-based): 72 - - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` - -### Residual `U+2820 ⠠ -> U+2830 ⠰` - -- `sentence_01.json` #25: 세븐일레븐은 올해 말 정식 버전 오픈을 목표로 하며, 이를 통해 주력상품을 구매하고 배송까지 받을 수 있는 차세대 오프라인을 위한 온라인(O4O) 시스템을 구축해 나갈 계획이다. - - expected: `⠟⠦⠄⠴⠠⠕⠼⠙⠠⠕⠠⠴⠀⠠⠕⠠⠪⠓⠝⠢⠮⠀⠈⠍` - - actual: `⠟⠦⠄⠴⠠⠕⠼⠙⠰⠠⠕⠠⠴⠀⠠⠕⠠⠪⠓⠝⠢⠮⠀⠈` - - first differing cell (zero-based): 129 - - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_02.json` #313: 순천향대(총장 김승우)는 지난 3일 한국국제협력단(KOICA)과 함께 우즈베키스탄의 수도 타슈겐트에서 스타트업 지원센터 ‘U-ENTER(Uzbekistan Entrepreneurship Innovation Center)’ 준공식을 개최했다고 밝혔다. - - expected: `⠎⠀⠠⠦⠴⠠⠥⠤⠠⠠⠢⠞⠻⠐⠣⠠⠥⠵⠃⠑⠅⠊⠌⠁` - - actual: `⠎⠀⠠⠦⠴⠠⠥⠤⠰⠠⠠⠢⠞⠻⠐⠣⠠⠥⠵⠃⠑⠅⠊⠌` - - first differing cell (zero-based): 131 - - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_03.json` #47: 이수화학이 미국 과학 기술 엔지니어링 회사 KBR과 전고체 배터리 소재 황화리튬(Li2S)의 상업공정 공동 개발에 나선다. 양사는 이를 위해 공동개발 계약을 체결했다고 20일 밝혔다. - - expected: `⠦⠄⠴⠠⠇⠊⠼⠃⠠⠎⠠⠴⠺⠀⠇⠶⠎⠃⠈⠿⠨⠻⠀⠈` - - actual: `⠦⠄⠴⠠⠇⠊⠼⠃⠰⠠⠎⠠⠴⠺⠀⠇⠶⠎⠃⠈⠿⠨⠻⠀` - - first differing cell (zero-based): 87 - - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_04.json` #1097: LG유플러스는 5세대 이동통신(5G)과 차량·사물통신(V2X)을 바탕으로 도시환경관리에 최적화된 자율주행 차량 관제시스템을 개발한 뒤 자율주행 기술을 실증할 도시인 ‘리빙랩’에서 4만㎞ 이상 서비스 실증에 나선다. - - expected: `⠟⠦⠄⠴⠠⠧⠼⠃⠠⠭⠠⠴⠮⠀⠘⠓⠶⠪⠐⠥⠀⠊⠥⠠` - - actual: `⠟⠦⠄⠴⠠⠧⠼⠃⠰⠠⠭⠠⠴⠮⠀⠘⠓⠶⠪⠐⠥⠀⠊⠥` - - first differing cell (zero-based): 63 - - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +| 1 | `U+2834 ⠴ -> U+2800 ⠀` | 545 | +| 2 | `U+2820 ⠠ -> U+2834 ⠴` | 454 | +| 3 | `U+2826 ⠦ -> U+2834 ⠴` | 447 | +| 4 | `U+280E ⠎ -> U+280C ⠌` | 444 | +| 5 | `U+2808 ⠈ -> U+2832 ⠲` | 407 | +| 6 | `U+2826 ⠦ -> U+2800 ⠀` | 389 | +| 7 | `U+2801 ⠁ -> U+281C ⠜` | 384 | +| 8 | `U+2834 ⠴ -> U+2830 ⠰` | 325 | +| 9 | `U+2811 ⠑ -> U+282B ⠫` | 321 | +| 10 | `U+2810 ⠐ -> U+2832 ⠲` | 285 | +| 11 | `U+2832 ⠲ -> U+2838 ⠸` | 275 | +| 12 | `U+2800 ⠀ -> U+2832 ⠲` | 225 | +| 13 | `U+2810 ⠐ -> U+2802 ⠂` | 210 | +| 14 | `U+2817 ⠗ -> U+2820 ⠠` | 161 | +| 15 | `U+2820 ⠠ -> U+2830 ⠰` | 156 | +| 16 | `U+283C ⠼ -> U+2800 ⠀` | 153 | +| 17 | `U+2820 ⠠ -> U+2809 ⠉` | 152 | +| 18 | `U+281B ⠛ -> U+2823 ⠣` | 144 | +| 19 | `U+2826 ⠦ -> U+2810 ⠐` | 139 | +| 20 | `U+2810 ⠐ -> U+2800 ⠀` | 129 | ### Residual `U+2834 ⠴ -> U+2800 ⠀` @@ -549,12 +503,59 @@ This ranking removes only cases whose first difference is inside an existing out - first differing cell (zero-based): 98 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +### Residual `U+2811 ⠑ -> U+282B ⠫` + +- `sentence_01.json` #51: 와이캅 픽셀은 와이어·패키지·렌즈가 필요 없는 ‘와이캅’ 기술을 기반으로 한다. 적녹청(RGB) 3개의 마이크로 LED를 수직 방향으로 적층한 세계 최초의 풀 컬러 원칩 기술이다. + - expected: `⠪⠐⠥⠀⠴⠠⠠⠇⠑⠙⠲⠐⠮⠀⠠⠍⠨⠕⠁⠀⠘⠶⠚⠜` + - actual: `⠪⠐⠥⠀⠴⠠⠠⠇⠫⠲⠐⠮⠀⠠⠍⠨⠕⠁⠀⠘⠶⠚⠜⠶` + - first differing cell (zero-based): 107 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_02.json` #33: 지원은 학생이 영재교육종합데이터베이스(GED)를 통해 원서 접수하고 교사의 추천을 받아 진행하고, 자세한 내용은 학교 및 영재교육 기관별 홈페이지에 탑재되어 있다. + - expected: `⠠⠪⠦⠄⠴⠠⠠⠛⠑⠙⠠⠴⠐⠮⠀⠓⠿⠚⠗⠀⠏⠒⠠⠎` + - actual: `⠠⠪⠦⠄⠴⠠⠠⠛⠫⠠⠴⠐⠮⠀⠓⠿⠚⠗⠀⠏⠒⠠⠎⠀` + - first differing cell (zero-based): 40 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_03.json` #209: 포드의 글로벌 트럭 디자인 DNA를 토대로 강인하면서도 다양한 사용 목적에 부합하는 실용적인 내·외부를 구현했다. 전면부 시그니처 C클램프 헤드라이트, 매트릭스 LED 헤드라이트, 포드(FORD) 레터링이 특징이다. + - expected: `⠁⠠⠪⠀⠴⠠⠠⠇⠑⠙⠲⠀⠚⠝⠊⠪⠐⠣⠕⠓⠪⠐⠀⠙` + - actual: `⠁⠠⠪⠀⠴⠠⠠⠇⠫⠲⠀⠚⠝⠊⠪⠐⠣⠕⠓⠪⠐⠀⠙⠥` + - first differing cell (zero-based): 159 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_04.json` #388: 김 신부는 그동안 제작한 스테인드글라스 작품은 물론 회화·LED(발광다이오드)조명작품·도자기 등 60여점의 작품을 전시한다. 그는 “형상을 떠난 자유로움과 원초적인 아름다움에 대한 깊이를 관람객들에게 전달하고 싶다”고 밝혔다. + - expected: `⠚⠧⠐⠆⠴⠠⠠⠇⠑⠙⠦⠄⠘⠂⠈⠧⠶⠊⠣⠕⠥⠊⠪⠠` + - actual: `⠚⠧⠐⠆⠴⠠⠠⠇⠫⠦⠄⠘⠂⠈⠧⠶⠊⠣⠕⠥⠊⠪⠠⠴` + - first differing cell (zero-based): 61 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` + +### Residual `U+2810 ⠐ -> U+2832 ⠲` + +- `sentence_01.json` #1011: 앞으로 다주택자도 주택담보대출비율(LTV) 30%까지 규제지역 내 주택담보대출(주담대)을 받을 수 있다. 그간 6억원까지였던 서민·실수요자의 주담대 한도는 사라지고, LTV·총부채원리금상환비율(DSR) 내에서 대출이 가능하다. + - expected: `⠐⠀⠴⠠⠠⠇⠞⠧⠐⠆⠰⠿⠘⠍⠰⠗⠏⠒⠐⠕⠈⠪⠢⠇` + - actual: `⠐⠀⠴⠠⠠⠇⠞⠧⠲⠐⠆⠸⠷⠰⠿⠘⠍⠰⠗⠏⠒⠐⠕⠈` + - first differing cell (zero-based): 168 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_02.json` #327: 또한, ‘도가(Dog+Yoga)’ 등 반려동물과 함께 즐길 수 있는 다양한 프로그램이 준비돼 있으며, 참가자 전원에게는 태안산 황토 호박고구마(3kg) 및 요가매트가 제공된다. + - expected: `⠫⠦⠄⠴⠠⠙⠕⠛⠐⠖⠠⠽⠕⠛⠁⠠⠴⠴⠄⠀⠊⠪⠶⠀` + - actual: `⠫⠦⠄⠴⠠⠙⠕⠛⠲⠢⠴⠠⠽⠕⠛⠁⠠⠴⠴⠄⠀⠊⠪⠶` + - first differing cell (zero-based): 19 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_03.json` #17: 재테크 시장에서 온·오프라인 플랫폼 ‘매경 엠플러스(M+)’가 주목받고 있다. M+는 거시경제, 주식, 부동산, 가상자산 등 재테크의 모든 것을 온라인 사이트를 통해 제공하고 있다. + - expected: `⠎⠠⠪⠦⠄⠴⠠⠍⠐⠖⠠⠴⠴⠄⠫⠀⠨⠍⠑⠭⠘⠔⠈⠥` + - actual: `⠎⠠⠪⠦⠄⠴⠠⠍⠲⠀⠢⠀⠠⠴⠴⠄⠫⠀⠨⠍⠑⠭⠘⠔` + - first differing cell (zero-based): 54 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_04.json` #329: 학생 맞춤형 학습 지원(Teaching), 체육활동 지원(Health), 사회성·심리정서 지원(Emotion)으로 구성된 ‘더(T·H·E) 자람 프로젝트’인 특별 지원은 이번 학기부터 이뤄진다. + - expected: `⠦⠊⠎⠦⠄⠴⠠⠞⠐⠆⠴⠠⠓⠐⠆⠴⠠⠑⠠⠴⠀⠨⠐⠣` + - actual: `⠦⠊⠎⠦⠄⠴⠠⠞⠲⠐⠆⠴⠠⠓⠲⠐⠆⠴⠠⠑⠠⠴⠀⠨` + - first differing cell (zero-based): 115 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` + ## Cross-cutting input-only structural cohorts These are cross-cutting input-only structural cohorts, not new primary classes and not engine routing rules. Candidate selection never changes a case's existing primary class by itself. Only cohort members already classified as `pending_rule_review` form a pending subcluster; exact and other-primary members are controls that retain their existing outcomes. A separate classifier may use independently justified, output-localized PDF evidence, as in the rule-34 three-cell contradiction below. The `uppercase_roman_headword_closed_multiword_parenthetical` gate requires a two-or-more character uppercase ASCII headword immediately followed by a closed parenthesis whose contents are two or more ASCII Roman words separated only by spaces. Because the contents admit only letters and spaces, visible operators, subscript/superscript notation, and nested parentheses are excluded deterministically. The `mixed_roman_korean_word_before_uppercase_headword_expansion` gate further requires a whitespace-delimited preceding word that contains both ASCII Roman and Korean and ends in Korean. It locates the following uppercase headword itself, including its immediately preceding emitted cell, so an earlier Roman entry in the same sentence cannot satisfy the output audit. The `single_capital_followed_by_parenthesized_digits` gate requires a standalone capital, a non-empty closed ASCII-digit parenthetical, and alphanumeric outer boundaries. It likewise includes the emitted entry-boundary cell in localization. The `uppercase_roman_run_followed_by_hyphen_digits` gate requires a maximal uppercase ASCII run, literal ASCII hyphen-minus, a non-empty digit run, and alphanumeric outer boundaries. Its localized range includes the current encoded run and its immediately preceding cell, keeping `F-35`-style routing separate from both parenthesized digits and headword expansions. The `standalone_multi_character_uppercase_roman_word` gate finds maximal ASCII-letter runs of two or more capitals with non-alphanumeric boundaries; a run immediately followed by `(` is excluded so the HCA-style headword itself is not counted by both gates. The `korean_prefixed_closed_allcaps_parenthetical` gate requires an immediately preceding Korean character and a closed body of two or more uppercase ASCII letters. It intentionally contains both acronym annotations (`책임자(COO)`) and scientific formulae (`일산화탄소(CO)`) so their semantic collision remains measurable. The `korean_prefixed_closed_roman_annotation_rule_34_order` gate accepts the narrower rule-34 body grammar after an immediately preceding Korean character and localizes only the current engine's Korean opening-parenthesis cells. This distinguishes the PDF's parenthesis-before-Roman-indicator order from unrelated differences later in the same sentence. The `multi_character_allcaps_roman_runs_joined_by_middle_dot` gate requires two maximal ASCII-letter runs of at least two capitals joined directly by U+00B7, with non-alphanumeric outer boundaries. It records shapes such as `AI·SW` without assigning prose, mathematics, or science semantics. The `roman_run_immediately_before_attached_middle_dot_boundary` gate is output-localized: it requires a maximal ASCII-letter run immediately before U+00B7 and an attached Korean character or ASCII-letter run after it, then searches for that whole current-engine signature in the actual output. It therefore isolates the Roman terminator boundary without treating unrelated middle dots elsewhere in the sentence as causal. The `korean_inline_parenthesized_single_arithmetic_operator` gate requires an immediate `Korean(` + one rule-45 arithmetic operator + `)Korean` span. Unlike broad coexistence traits, it also locates the current engine's emitted structure and counts a mismatch as signature-local only when the sentence's first differing cell falls inside that output range. The `allcaps_roman_run_containing_ou` gate finds maximal, alphanumeric-delimited uppercase ASCII runs containing adjacent `OU`. It locates the independently encoded run signature in the complete current output and counts only first differences inside that signature as localized. The `decimal_point_between_ascii_digits` gate finds whitespace-delimited words containing `digit.digit` and reproduces each whole word in a neutral Korean context, so suffixes and punctuation remain part of the current-engine signature. The `compact_numeric_ascii_letter_suffix` gate finds a numeric prefix immediately followed by ASCII letters, includes the immediately preceding output cell as its entry boundary, and retains suffix-specific outcome counts. It intentionally includes both possible rule-69 units and ambiguous variable/identifier forms; membership alone does not assign unit semantics. The `rule69_ascii_unit_before_terminator_skipping_symbol` gate is narrower: it accepts only ASCII unit spellings already supported by rule 69, includes the immediately following rule-33/34 punctuation cell in the localized signature, and does not infer new units. The `tight_triangle_mark_immediately_before_korean` gate requires literal `△한글` with no input space and includes the first following Korean cell in its localized output range, so an observed missing-space difference is measured at the mark boundary. | Cluster | Candidates | Exact | Mismatch | Conflicting-reference cases | |---|---:|---:|---:|---:| +| `allcaps_roman_run_beginning_with_pure_letter_shortform` | 4120 | 2239 | 1881 | 0 | | `allcaps_roman_run_containing_ou` | 1816 | 55 | 1761 | 0 | | `compact_numeric_ascii_letter_suffix` | 2975 | 1759 | 1216 | 0 | | `decimal_point_between_ascii_digits` | 4546 | 3209 | 1337 | 0 | @@ -568,9 +569,94 @@ These are cross-cutting input-only structural cohorts, not new primary classes a | `single_capital_followed_by_parenthesized_digits` | 1361 | 0 | 1361 | 0 | | `standalone_multi_character_uppercase_roman_word` | 62411 | 49088 | 13323 | 0 | | `tight_triangle_mark_immediately_before_korean` | 377 | 0 | 377 | 0 | +| `uppercase_ascii_run_immediately_after_digit_in_roman_sequence` | 1896 | 756 | 1140 | 0 | +| `uppercase_ascii_run_immediately_after_hyphen_in_roman_sequence` | 952 | 31 | 921 | 0 | | `uppercase_roman_headword_closed_multiword_parenthetical` | 175 | 15 | 160 | 0 | | `uppercase_roman_run_followed_by_hyphen_digits` | 571 | 183 | 388 | 0 | +### `allcaps_roman_run_beginning_with_pure_letter_shortform` + +Of the 4120 candidates, 1813 are the actual `pending_rule_review` subcluster. The other 2307 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. + +For this output-signature audit, 1881 mismatches were evaluable and 963 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. + +Localized first-difference transitions: + +- `U+2830 ⠰ -> U+2820 ⠠`: 962 +- `U+2820 ⠠ -> U+2830 ⠰`: 1 + +Mismatch primary-class distribution: + +- `comparison_method`: 11 +- `corpus_suspect`: 47 +- `pending_rule_review`: 1813 +- `unsupported_character_review`: 10 + +Representative `exact` samples: + +- `sentence_01.json` #8: SK증권은 2일 삼성SDI에 대해 증설 투자를 위한 자금 여력이 가장 우수한 기업이라고 설명하며 완성차와 추가적인 조인트벤처(JV) 설립이 기대된다고 분석했다. 투자의견 ‘매수’와 목표주가 91만원을 유지했다. + - expected: `⠴⠠⠠⠎⠅⠲⠨⠪⠶⠈⠏⠒⠵⠀⠼⠃⠕⠂⠀⠇⠢⠠⠻⠴` + - actual: `⠴⠠⠠⠎⠅⠲⠨⠪⠶⠈⠏⠒⠵⠀⠼⠃⠕⠂⠀⠇⠢⠠⠻⠴` + - current primary/reason: `exact` / `exact` +- `sentence_02.json` #4: KDI에 따르면, 비제조업 업황BSI(기업경기실사지수) 전망치는 2월 72에서 3월 74로 개선되고 있다. 중국 관광객 유입에 대한 기대감이 확산된 영향으로 분석된다. + - expected: `⠴⠠⠠⠅⠙⠊⠲⠝⠀⠠⠊⠐⠪⠑⠡⠐⠀⠘⠕⠨⠝⠨⠥⠎` + - actual: `⠴⠠⠠⠅⠙⠊⠲⠝⠀⠠⠊⠐⠪⠑⠡⠐⠀⠘⠕⠨⠝⠨⠥⠎` + - current primary/reason: `exact` / `exact` +- `sentence_03.json` #56: 20일 한국투자신탁운용은 ‘ACE 미국30년국채액티브(H)’ ETF의 순자산액이 지난 19일 511억원을 기록했다고 밝혔다. 지난달 14일 해당 ETF가 상장한 이후 약 한 달 만이다. + - expected: `⠼⠃⠚⠕⠂⠀⠚⠒⠈⠍⠁⠓⠍⠨⠠⠟⠓⠁⠛⠬⠶⠵⠀⠠` + - actual: `⠼⠃⠚⠕⠂⠀⠚⠒⠈⠍⠁⠓⠍⠨⠠⠟⠓⠁⠛⠬⠶⠵⠀⠠` + - current primary/reason: `exact` / `exact` +- `sentence_04.json` #30: 뱅크오브아메리카(BOA), 시티그룹, JP모건체이스, 웰스파고가 각각 50억달러를 예치하고, 골드만삭스와 모건스탠리가 각각 25억달러를 넣기로 했다. BNY멜론, PNC뱅크, 스테이트스트리트, 트루이스트, US뱅크는 각각 10억달러를 예치한다. + - expected: `⠘⠗⠶⠋⠪⠥⠘⠪⠣⠑⠝⠐⠕⠋⠦⠄⠴⠠⠠⠃⠕⠁⠠⠴` + - actual: `⠘⠗⠶⠋⠪⠥⠘⠪⠣⠑⠝⠐⠕⠋⠦⠄⠴⠠⠠⠃⠕⠁⠠⠴` + - current primary/reason: `exact` / `exact` + +Representative `localized_mismatch` samples: + +- `sentence_01.json` #310: 낸드플래시 반도체 세계 2위인 일본 키옥시아와 4위인 미국 웨스턴디지털(WD) 합병설에 반도체 업계가 촉각을 곤두 세우고 있다. + - expected: `⠕⠨⠕⠓⠞⠦⠄⠴⠰⠠⠠⠺⠙⠠⠴⠀⠚⠃⠘⠻⠠⠞⠝⠀` + - actual: `⠕⠨⠕⠓⠞⠦⠄⠴⠠⠠⠺⠙⠠⠴⠀⠚⠃⠘⠻⠠⠞⠝⠀⠘` + - first differing cell (zero-based): 70 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_02.json` #99: 황승주 씨는 “기능성 소화불량증, 특히 PDS(식후불편감증후군) 복부팽만감 유형으로 고통 받는 환자들에게 적용가능한 안전하고 높은 치료효율의 한약소재를 개발해 국민건강에 이바지하고자 한다”고 말했다. + - expected: `⠀⠓⠪⠁⠚⠕⠀⠴⠰⠠⠠⠏⠙⠎⠦⠄⠠⠕⠁⠚⠍⠘⠯⠙` + - actual: `⠀⠓⠪⠁⠚⠕⠀⠴⠠⠠⠏⠙⠎⠦⠄⠠⠕⠁⠚⠍⠘⠯⠙⠡` + - first differing cell (zero-based): 45 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_03.json` #30: “챗GPT보다 가벼운 여러 대규모언어모델(LLM)이 출현해 오픈AI 독점 구도에 균열을 내고 있다. 한국어 특화 LLM으로 글로벌 경쟁에 대응하면서 세부 서비스(튜닝) 모델에서 차별성을 확보하는 전략이 중요하다.” + - expected: `⠑⠥⠊⠝⠂⠦⠄⠴⠰⠠⠠⠇⠇⠍⠠⠴⠕⠀⠰⠯⠚⠡⠚⠗` + - actual: `⠑⠥⠊⠝⠂⠦⠄⠴⠠⠠⠇⠇⠍⠠⠴⠕⠀⠰⠯⠚⠡⠚⠗⠀` + - first differing cell (zero-based): 40 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_04.json` #81: SGI는 “산업연관분석을 활용해 우리나라의 대일 수출 증가가 국내총생산(GDP)에 미치는 영향을 계산해 보면 경제성장률은 0.1%포인트 높아질 것”이라고 밝혔다. + - expected: `⠠⠗⠶⠇⠒⠦⠄⠴⠰⠠⠠⠛⠙⠏⠠⠴⠝⠀⠑⠕⠰⠕⠉⠵` + - actual: `⠠⠗⠶⠇⠒⠦⠄⠴⠠⠠⠛⠙⠏⠠⠴⠝⠀⠑⠕⠰⠕⠉⠵⠀` + - first differing cell (zero-based): 72 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` + +Representative `mismatch` samples: + +- `sentence_01.json` #54: 에버소울은 출시 전부터 지스타(G-STAR) 2022, 에이지에프(AGF) 2022 등 굵직한 국내 게임 및 서브컬처 행사에 출품해 기대를 모았다. 12월 말에는 사전 예약자 150만 명을 모객했다. + - expected: `⠪⠓⠦⠄⠴⠠⠛⠤⠠⠠⠌⠜⠠⠴⠀⠼⠃⠚⠃⠃⠐⠀⠝⠕` + - actual: `⠪⠓⠦⠄⠴⠠⠛⠤⠰⠠⠠⠌⠜⠠⠴⠀⠼⠃⠚⠃⠃⠐⠀⠝` + - first differing cell (zero-based): 31 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_02.json` #99: 황승주 씨는 “기능성 소화불량증, 특히 PDS(식후불편감증후군) 복부팽만감 유형으로 고통 받는 환자들에게 적용가능한 안전하고 높은 치료효율의 한약소재를 개발해 국민건강에 이바지하고자 한다”고 말했다. + - expected: `⠀⠓⠪⠁⠚⠕⠀⠴⠰⠠⠠⠏⠙⠎⠦⠄⠠⠕⠁⠚⠍⠘⠯⠙` + - actual: `⠀⠓⠪⠁⠚⠕⠀⠴⠠⠠⠏⠙⠎⠦⠄⠠⠕⠁⠚⠍⠘⠯⠙⠡` + - first differing cell (zero-based): 45 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_03.json` #6: 지난해 9월 HMM과 파나시아가 선박용 탄소포집 시스템 공동연구에 대한 업무협약(MOU)을 체결한 뒤 7개월 만에 내놓은 연구성과다. + - expected: `⠜⠁⠦⠄⠴⠠⠠⠍⠕⠥⠠⠴⠮⠀⠰⠝⠈⠳⠚⠒⠀⠊⠍⠗` + - actual: `⠜⠁⠦⠄⠴⠠⠠⠍⠳⠠⠴⠮⠀⠰⠝⠈⠳⠚⠒⠀⠊⠍⠗⠀` + - first differing cell (zero-based): 83 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_04.json` #19: 이날 행사에는 구 대표 외에 권봉석 (주)LG 최고운영책임자(COO), 박일평 LG사이언스파크대표(사장)를 비롯해 각 계열사 최고기술책임자(CTO), 최고데이터책임자(CDO), 최고인사책임자(CHO) 등이 참석해 국내 이공계 R&D 인재 400여명과 만났다. + - expected: `⠁⠕⠢⠨⠦⠄⠴⠠⠠⠉⠕⠕⠠⠴⠐⠀⠘⠁⠕⠂⠙⠻⠀⠴` + - actual: `⠁⠕⠢⠨⠦⠄⠴⠠⠉⠠⠕⠠⠕⠠⠴⠐⠀⠘⠁⠕⠂⠙⠻⠀` + - first differing cell (zero-based): 60 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` + ### `allcaps_roman_run_containing_ou` Of the 1816 candidates, 1676 are the actual `pending_rule_review` subcluster. The other 140 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. @@ -1327,6 +1413,171 @@ Representative `mismatch` samples: - first differing cell (zero-based): 32 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +### `uppercase_ascii_run_immediately_after_digit_in_roman_sequence` + +Of the 1896 candidates, 1086 are the actual `pending_rule_review` subcluster. The other 810 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. + +For this output-signature audit, 1140 mismatches were evaluable and 331 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. + +Localized first-difference transitions: + +- `U+2820 ⠠ -> U+2830 ⠰`: 330 +- `U+2830 ⠰ -> U+2820 ⠠`: 1 + +Mismatch primary-class distribution: + +- `comparison_method`: 7 +- `corpus_suspect`: 38 +- `pending_rule_review`: 1086 +- `unsupported_character_review`: 9 + +Representative `exact` samples: + +- `sentence_01.json` #343: 확정안을 보면 정부는 2036년 최대전력 수요(목표 수요)를 118.0GW(기가와트)로 예측했다. 1GW는 보통 원전 1기의 발전량으로 보고 있다. + - expected: `⠚⠧⠁⠨⠻⠣⠒⠮⠀⠘⠥⠑⠡⠀⠨⠻⠘⠍⠉⠵⠀⠼⠃⠚` + - actual: `⠚⠧⠁⠨⠻⠣⠒⠮⠀⠘⠥⠑⠡⠀⠨⠻⠘⠍⠉⠵⠀⠼⠃⠚` + - current primary/reason: `exact` / `exact` +- `sentence_02.json` #51: 먼저, 차세대 비디오 압축표준(VVC) 분야 64건과 5G 이동통신(NR) 분야 48건 등 시장 수요가 큰 상용표준특허가 다수 포함, 향후 상당한 특허 기술료가 전망된다. + - expected: `⠑⠾⠨⠎⠐⠀⠰⠣⠠⠝⠊⠗⠀⠘⠕⠊⠕⠥⠀⠣⠃⠰⠍⠁` + - actual: `⠑⠾⠨⠎⠐⠀⠰⠣⠠⠝⠊⠗⠀⠘⠕⠊⠕⠥⠀⠣⠃⠰⠍⠁` + - current primary/reason: `exact` / `exact` +- `sentence_03.json` #81: 이번에 개발한 신제품의 용량은 기존 제품(16GB)보다 50% 높아졌다. D램 용량이 높아지면 시스템이 데이터를 더 빠르고 효율적으로 처리할 수 있게 된다. + - expected: `⠕⠘⠾⠝⠀⠈⠗⠘⠂⠚⠒⠀⠠⠟⠨⠝⠙⠍⠢⠺⠀⠬⠶⠐` + - actual: `⠕⠘⠾⠝⠀⠈⠗⠘⠂⠚⠒⠀⠠⠟⠨⠝⠙⠍⠢⠺⠀⠬⠶⠐` + - current primary/reason: `exact` / `exact` +- `sentence_04.json` #78: 차체 중량은 55㎏인데, 체중 100㎏의 사람을 태울 수 있다. 배터리 출력은 1000W(와트)로, 도심 길거리에서 쓰는 킥보드와 비슷한 출력이다. + - expected: `⠰⠣⠰⠝⠀⠨⠍⠶⠐⠜⠶⠵⠀⠼⠑⠑⠴⠅⠛⠲⠟⠊⠝⠐` + - actual: `⠰⠣⠰⠝⠀⠨⠍⠶⠐⠜⠶⠵⠀⠼⠑⠑⠴⠅⠛⠲⠟⠊⠝⠐` + - current primary/reason: `exact` / `exact` + +Representative `localized_mismatch` samples: + +- `sentence_01.json` #25: 세븐일레븐은 올해 말 정식 버전 오픈을 목표로 하며, 이를 통해 주력상품을 구매하고 배송까지 받을 수 있는 차세대 오프라인을 위한 온라인(O4O) 시스템을 구축해 나갈 계획이다. + - expected: `⠟⠦⠄⠴⠠⠕⠼⠙⠠⠕⠠⠴⠀⠠⠕⠠⠪⠓⠝⠢⠮⠀⠈⠍` + - actual: `⠟⠦⠄⠴⠠⠕⠼⠙⠰⠠⠕⠠⠴⠀⠠⠕⠠⠪⠓⠝⠢⠮⠀⠈` + - first differing cell (zero-based): 129 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_02.json` #1138: DL이앤씨 관계자는 “설계·조달·시공(EPC) 수행 역량을 집중해 세계 최초로 TC2C 기술을 상용화하는 데 최선의 노력을 다하겠다”고 했다. + - expected: `⠀⠴⠠⠠⠞⠉⠼⠃⠠⠉⠲⠀⠈⠕⠠⠯⠮⠀⠇⠶⠬⠶⠚⠧` + - actual: `⠀⠴⠠⠠⠞⠉⠼⠃⠰⠉⠲⠀⠈⠕⠠⠯⠮⠀⠇⠶⠬⠶⠚⠧` + - first differing cell (zero-based): 91 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_03.json` #47: 이수화학이 미국 과학 기술 엔지니어링 회사 KBR과 전고체 배터리 소재 황화리튬(Li2S)의 상업공정 공동 개발에 나선다. 양사는 이를 위해 공동개발 계약을 체결했다고 20일 밝혔다. + - expected: `⠦⠄⠴⠠⠇⠊⠼⠃⠠⠎⠠⠴⠺⠀⠇⠶⠎⠃⠈⠿⠨⠻⠀⠈` + - actual: `⠦⠄⠴⠠⠇⠊⠼⠃⠰⠠⠎⠠⠴⠺⠀⠇⠶⠎⠃⠈⠿⠨⠻⠀` + - first differing cell (zero-based): 87 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_04.json` #1097: LG유플러스는 5세대 이동통신(5G)과 차량·사물통신(V2X)을 바탕으로 도시환경관리에 최적화된 자율주행 차량 관제시스템을 개발한 뒤 자율주행 기술을 실증할 도시인 ‘리빙랩’에서 4만㎞ 이상 서비스 실증에 나선다. + - expected: `⠟⠦⠄⠴⠠⠧⠼⠃⠠⠭⠠⠴⠮⠀⠘⠓⠶⠪⠐⠥⠀⠊⠥⠠` + - actual: `⠟⠦⠄⠴⠠⠧⠼⠃⠰⠠⠭⠠⠴⠮⠀⠘⠓⠶⠪⠐⠥⠀⠊⠥` + - first differing cell (zero-based): 63 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` + +Representative `mismatch` samples: + +- `sentence_01.json` #25: 세븐일레븐은 올해 말 정식 버전 오픈을 목표로 하며, 이를 통해 주력상품을 구매하고 배송까지 받을 수 있는 차세대 오프라인을 위한 온라인(O4O) 시스템을 구축해 나갈 계획이다. + - expected: `⠟⠦⠄⠴⠠⠕⠼⠙⠠⠕⠠⠴⠀⠠⠕⠠⠪⠓⠝⠢⠮⠀⠈⠍` + - actual: `⠟⠦⠄⠴⠠⠕⠼⠙⠰⠠⠕⠠⠴⠀⠠⠕⠠⠪⠓⠝⠢⠮⠀⠈` + - first differing cell (zero-based): 129 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_02.json` #425: ‘제로(Zero)’는 ‘대전 0시 축제(Daejeon Zero O'clock Festival)’의 영문 이름에서 따왔으며, 축제장에서 3D홀로그램 미디어 콘텐츠 운영을 맡고 있는 애드테크 전문기업 코스윌에서 개발했다. + - expected: `⠀⠠⠵⠻⠕⠀⠠⠕⠄⠉⠇⠕⠉⠅⠀⠠⠋⠑⠌⠊⠧⠁⠇⠠` + - actual: `⠀⠠⠵⠻⠕⠀⠠⠕⠴⠄⠰⠉⠇⠕⠉⠅⠀⠠⠋⠑⠌⠊⠧⠁` + - first differing cell (zero-based): 56 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_03.json` #47: 이수화학이 미국 과학 기술 엔지니어링 회사 KBR과 전고체 배터리 소재 황화리튬(Li2S)의 상업공정 공동 개발에 나선다. 양사는 이를 위해 공동개발 계약을 체결했다고 20일 밝혔다. + - expected: `⠦⠄⠴⠠⠇⠊⠼⠃⠠⠎⠠⠴⠺⠀⠇⠶⠎⠃⠈⠿⠨⠻⠀⠈` + - actual: `⠦⠄⠴⠠⠇⠊⠼⠃⠰⠠⠎⠠⠴⠺⠀⠇⠶⠎⠃⠈⠿⠨⠻⠀` + - first differing cell (zero-based): 87 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_04.json` #115: ‘Busan is Good(부산이라 좋다)’이라는 새로운 도시 표어의 조형물을 공개하고, 3차원(3D)으로 표현한 도시상징 표지(CI) 영상을 상영한다. + - expected: `⠁⠝⠀⠊⠎⠀⠠⠛⠙⠦⠄⠘⠍⠇⠒⠕⠐⠣⠀⠨⠥⠴⠊⠠` + - actual: `⠁⠝⠀⠊⠎⠀⠠⠛⠕⠕⠙⠦⠄⠘⠍⠇⠒⠕⠐⠣⠀⠨⠥⠴` + - first differing cell (zero-based): 15 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` + +### `uppercase_ascii_run_immediately_after_hyphen_in_roman_sequence` + +Of the 952 candidates, 902 are the actual `pending_rule_review` subcluster. The other 50 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. + +For this output-signature audit, 921 mismatches were evaluable and 315 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. + +Localized first-difference transitions: + +- `U+2820 ⠠ -> U+2830 ⠰`: 312 +- `U+2830 ⠰ -> U+2820 ⠠`: 3 + +Mismatch primary-class distribution: + +- `corpus_suspect`: 10 +- `pending_rule_review`: 902 +- `unsupported_character_review`: 9 + +Representative `exact` samples: + +- `sentence_01.json` #3590: SK매직이 지난해 7월 도입한 ‘차세대 IT 전산 시스템’에 대해 과학기술정보통신부 산하 한국인터넷진흥원(KISA)으로부터 ‘정보보호 및 개인 정보보호 관리체계(ISMS-P) 인증’을 획득했다. + - expected: `⠴⠠⠠⠎⠅⠲⠑⠗⠨⠕⠁⠕⠀⠨⠕⠉⠒⠚⠗⠀⠼⠛⠏⠂` + - actual: `⠴⠠⠠⠎⠅⠲⠑⠗⠨⠕⠁⠕⠀⠨⠕⠉⠒⠚⠗⠀⠼⠛⠏⠂` + - current primary/reason: `exact` / `exact` +- `sentence_02.json` #1035: 사업단은 ‘어라운드유(Around-U)’ 중 U라는 글자에 당신(You)과 울산(Ulsan)의 의미를 담아 학생들이 울산지역 문제를 주도적으로 발굴해 다양한 부문과 공유·협업하면서 문제를 해결하는 기회를 제공했다. + - expected: `⠇⠎⠃⠊⠒⠵⠀⠠⠦⠎⠐⠣⠛⠊⠪⠩⠦⠄⠴⠠⠜⠨⠙⠤` + - actual: `⠇⠎⠃⠊⠒⠵⠀⠠⠦⠎⠐⠣⠛⠊⠪⠩⠦⠄⠴⠠⠜⠨⠙⠤` + - current primary/reason: `exact` / `exact` +- `sentence_03.json` #4701: 또한 자비스앤빌런즈는 삼쩜삼 서비스 시작 2년 만에 세무 플랫폼 최초로 정보보호관리체계(ISMS) 인증을 받았다. 하반기에도 정보보호·개인정보보호 관리체계(ISMS-P) 인증을 준비하고 있다. + - expected: `⠠⠊⠥⠚⠒⠀⠨⠘⠕⠠⠪⠗⠒⠘⠕⠂⠐⠾⠨⠪⠉⠵⠀⠇` + - actual: `⠠⠊⠥⠚⠒⠀⠨⠘⠕⠠⠪⠗⠒⠘⠕⠂⠐⠾⠨⠪⠉⠵⠀⠇` + - current primary/reason: `exact` / `exact` +- `sentence_04.json` #3338: ‘분당 흉기 난동’ 사건의 피의자인 최원종(22)에 대한 사이코패스 진단검사(PCL-R)를 실시한 경찰이 측정 자체가 불가능하다는 결론을 냈다. + - expected: `⠠⠦⠘⠛⠊⠶⠀⠚⠩⠶⠈⠕⠀⠉⠒⠊⠿⠴⠄⠀⠇⠈⠾⠺` + - actual: `⠠⠦⠘⠛⠊⠶⠀⠚⠩⠶⠈⠕⠀⠉⠒⠊⠿⠴⠄⠀⠇⠈⠾⠺` + - current primary/reason: `exact` / `exact` + +Representative `localized_mismatch` samples: + +- `sentence_01.json` #54: 에버소울은 출시 전부터 지스타(G-STAR) 2022, 에이지에프(AGF) 2022 등 굵직한 국내 게임 및 서브컬처 행사에 출품해 기대를 모았다. 12월 말에는 사전 예약자 150만 명을 모객했다. + - expected: `⠪⠓⠦⠄⠴⠠⠛⠤⠠⠠⠌⠜⠠⠴⠀⠼⠃⠚⠃⠃⠐⠀⠝⠕` + - actual: `⠪⠓⠦⠄⠴⠠⠛⠤⠰⠠⠠⠌⠜⠠⠴⠀⠼⠃⠚⠃⠃⠐⠀⠝` + - first differing cell (zero-based): 31 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_02.json` #313: 순천향대(총장 김승우)는 지난 3일 한국국제협력단(KOICA)과 함께 우즈베키스탄의 수도 타슈겐트에서 스타트업 지원센터 ‘U-ENTER(Uzbekistan Entrepreneurship Innovation Center)’ 준공식을 개최했다고 밝혔다. + - expected: `⠎⠀⠠⠦⠴⠠⠥⠤⠠⠠⠢⠞⠻⠐⠣⠠⠥⠵⠃⠑⠅⠊⠌⠁` + - actual: `⠎⠀⠠⠦⠴⠠⠥⠤⠰⠠⠠⠢⠞⠻⠐⠣⠠⠥⠵⠃⠑⠅⠊⠌` + - first differing cell (zero-based): 131 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_03.json` #74: 아울러 제주도개발공사는 국내 생수업계에서는 처음으로 재활용 페트(CR-PET)를 적용한 화학적 재활용 페트 ‘제주삼다수 리본(RE:Born)’을 개발하는 등 소재혁신을 통한 친환경 라인업도 확대하고있다. + - expected: `⠦⠄⠴⠠⠠⠉⠗⠤⠠⠠⠏⠑⠞⠠⠴⠐⠮⠀⠨⠹⠬⠶⠚⠒` + - actual: `⠦⠄⠴⠠⠠⠉⠗⠤⠰⠠⠠⠏⠑⠞⠠⠴⠐⠮⠀⠨⠹⠬⠶⠚` + - first differing cell (zero-based): 70 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_04.json` #1164: 3일 서울시에 따르면 서울시와 국토교통부는 이러한 내용을 담은 ‘K-UAM(도심항공교통) 그랜드챌린지 실증운행계획’을 이달 중순 발표한다. UAM은 드론 택시처럼 하늘을 이용한 교통수단이다. + - expected: `⠵⠀⠠⠦⠴⠠⠅⠤⠠⠠⠥⠁⠍⠦⠄⠊⠥⠠⠕⠢⠚⠶⠈⠿` + - actual: `⠵⠀⠠⠦⠴⠠⠅⠤⠰⠠⠠⠥⠁⠍⠦⠄⠊⠥⠠⠕⠢⠚⠶⠈` + - first differing cell (zero-based): 62 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` + +Representative `mismatch` samples: + +- `sentence_01.json` #52: 과학기술정보통신부와 개인정보보호위원회의 공동 고시 기준에 따라 한국인터넷진흥원(KISA)이 인증하는 ISMS-P는 고객 정보보호 및 개인정보보호를 위한 일련의 조치와 활동이 국가공인 인증기준에 적합함을 증명하는 제도다. + - expected: `⠠⠠⠊⠎⠍⠎⠤⠰⠠⠏⠲⠉⠵⠀⠈⠥⠈⠗⠁⠀⠨⠻⠘⠥` + - actual: `⠠⠠⠊⠎⠍⠎⠤⠰⠏⠲⠉⠵⠀⠈⠥⠈⠗⠁⠀⠨⠻⠘⠥⠘` + - first differing cell (zero-based): 108 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_02.json` #313: 순천향대(총장 김승우)는 지난 3일 한국국제협력단(KOICA)과 함께 우즈베키스탄의 수도 타슈겐트에서 스타트업 지원센터 ‘U-ENTER(Uzbekistan Entrepreneurship Innovation Center)’ 준공식을 개최했다고 밝혔다. + - expected: `⠎⠀⠠⠦⠴⠠⠥⠤⠠⠠⠢⠞⠻⠐⠣⠠⠥⠵⠃⠑⠅⠊⠌⠁` + - actual: `⠎⠀⠠⠦⠴⠠⠥⠤⠰⠠⠠⠢⠞⠻⠐⠣⠠⠥⠵⠃⠑⠅⠊⠌` + - first differing cell (zero-based): 131 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_03.json` #74: 아울러 제주도개발공사는 국내 생수업계에서는 처음으로 재활용 페트(CR-PET)를 적용한 화학적 재활용 페트 ‘제주삼다수 리본(RE:Born)’을 개발하는 등 소재혁신을 통한 친환경 라인업도 확대하고있다. + - expected: `⠦⠄⠴⠠⠠⠉⠗⠤⠠⠠⠏⠑⠞⠠⠴⠐⠮⠀⠨⠹⠬⠶⠚⠒` + - actual: `⠦⠄⠴⠠⠠⠉⠗⠤⠰⠠⠠⠏⠑⠞⠠⠴⠐⠮⠀⠨⠹⠬⠶⠚` + - first differing cell (zero-based): 70 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_04.json` #883: 그룹 방탄소년단(BTS) 슈가의 첫 공식 솔로 음반 ‘D-데이’(D-DAY)가 발매 당일 한터차트 기준 107만장이 넘게 팔려나가며 첫날 판매량으로는 K팝 솔로 가수 최고 기록을 경신했다. + - expected: `⠘⠒⠀⠠⠦⠴⠠⠙⠲⠤⠊⠝⠕⠴⠄⠦⠄⠴⠠⠙⠤⠠⠠⠐` + - actual: `⠘⠒⠀⠠⠦⠴⠠⠙⠘⠔⠸⠷⠊⠝⠕⠸⠾⠴⠄⠐⠣⠰⠠⠙` + - first differing cell (zero-based): 58 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` + ### `uppercase_roman_headword_closed_multiword_parenthetical` Of the 175 candidates, 149 are the actual `pending_rule_review` subcluster. The other 26 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. @@ -1439,6 +1690,50 @@ Representative `mismatch` samples: - first differing cell (zero-based): 108 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +## UEB grade-1 first-difference cohorts + +These three cohorts are defined by both an input boundary and the sentence's actual first-difference transition. They therefore do not claim every mismatch merely coexisting with an ASCII run. Candidate, exact, mismatch, and primary-class counts remain cross-cutting controls; only the reported target transition is the localized residual under review. The reverse transition is retained separately rather than folded into the target. + +| Cohort | Candidates | Exact controls | Mismatch | Target localized | Reverse | +|---|---:|---:|---:|---:|---:| +| `allcaps_roman_run_beginning_with_pure_letter_shortform` | 4120 | 2239 | 1881 | 962 | 1 | +| `uppercase_ascii_run_immediately_after_digit_in_roman_sequence` | 1896 | 756 | 1140 | 330 | 1 | +| `uppercase_ascii_run_immediately_after_hyphen_in_roman_sequence` | 952 | 31 | 921 | 312 | 3 | + +### All-caps shortform prefix at an attached Roman entry + +UEB 2024 rule 5.7.2 requires grade-1 mode when a letters-sequence could be read as a shortform or as containing one. Rule 10.9.7 covers a standing-alone shortform-shaped sequence, rule 10.9.8 covers a sequence at the beginning of a longer word (the PDF example is `LLC`), and rule 5.8.1 places grade 1 before capitalization. The current standalone ASCII-token route already supplies that guard for complete shortform-shaped controls such as `AC`, `CD`, `IMM`, and `AG`; the attached Korean-word/parenthetical route enters directly at the capital marker and accounts for the localized `⠰ -> ⠠` signature. This is a routing distinction supported independently by the PDF, not an expected-output lookup. Longer runs such as `GDP` and `LLM` are admitted only when a pure-letter shortform occupies the beginning of the run; a shortform appearing later would require the distinct grade-1 word rule 10.9.9 and is outside this implementation candidate. + +Same-surface controls demonstrate why primary classes must not be changed by cohort membership: + +| Surface | Candidates | Exact | Mismatch | Target-localized | +|---|---:|---:|---:|---:| +| `AC` | 159 | 67 | 92 | 86 | +| `LLM` | 176 | 1 | 175 | 147 | +| `CD` | 65 | 3 | 62 | 36 | +| `IMM` | 27 | 3 | 24 | 22 | +| `AG` | 18 | 2 | 16 | 10 | +| `GDP` | 359 | 0 | 359 | 350 | +| `WD` | 10 | 0 | 10 | 10 | + +- `AC` exact control: `sentence_01.json` #1122 — 손흥민(토트넘)이 선발 출전했지만 소속팀 패배를 지켜봤다. 토트넘은 15일(이하 한국시간) 이탈리아 밀라노에 있는 산시로에서 AC 밀란(이탈리아)과 유럽축구연맹(UEFA) 주최 2022-2023시즌 챔피언스리그(UCL) 16강 1차전 원정 경기를 치렀다. +- `AC` localized mismatch: `sentence_01.json` #1707 — 토트넘은 9일(한국시간) 영국 런던의 토트넘 홋스퍼 스타디움에서 열린 2022-2023 유럽축구연맹(UEFA) 챔피언스리그(UCL) 16강 2차전에서 AC밀란(이탈리아)과 득점 없이 0-0으로 비겼다. + +- `LLM` exact control: `sentence_03.json` #19586 — ‘미디어그룹사람과숲’은 한국정보화진흥원(NIA)과 업스테이지가 주최하는 한국어 ‘초거대 언어 모델’ 리더보드 ‘Open Ko-LLM’에서 1위를 차지했다고 11월13일 밝혔다. +- `LLM` localized mismatch: `sentence_01.json` #312 — 엔비디아가 대규모 언어 모델(LLM)과 음성 AI를 주제로 엔비디아 AI 개발자 밋업을 개최한다. 이번 개발자 밋업은 오는 17일 오후 2시부터 5시까지 온라인으로 진행된다. + +- `CD` exact control: `sentence_02.json` #7087 — 여기에 이 국제표준 개정안의 문서를 오는 2024년 1월까지 CD 단계 문서로 개발하고, 2024년 내 국제표준 문서 출판 단계(IS)로 개발하기로 하는 합의점을 이끌어냈다. +- `CD` localized mismatch: `sentence_01.json` #11231 — 이날 고아인은 PT 성공률이 가장 높은 CD(팀장)라는 별명처럼 여유로운 태도로 정재훈(이기우 분) 대표의 게임회사 광고 PT를 마쳤다. + +### Uppercase immediately after a digit + +UEB rule 5.6.1 says the numeric indicator establishes grade-1 mode, and rule 5.6.2 ends that mode at a space, hyphen, dash, or grade-1 terminator; a capitalization indicator is not a terminator. Korean rule 35 likewise keeps Roman letters and an adjacent number in one Roman section. The 330 localized `⠠ -> ⠰` cases therefore identify a separate possible redundant continuation at the digit-to-capital boundary. This cohort is not merged with the shortform-prefix route: the one reverse case and the absence of same-surface exact controls among frequent forms such as `B2B`, `V2X`, `Li2S`, and `O4O` require an independent state-machine audit before any engine change. + +### Uppercase immediately after a hyphen + +UEB rule 5.7.2 prints `CD-ROM` with one grade-1 indicator before `CD` and no second grade-1 indicator after the hyphen. Korean rule 29 similarly uses one Roman span for consecutive Roman text. The 312 localized `⠠ -> ⠰` cases are therefore tracked as a distinct hyphen-continuation candidate. The three reverse cases, only 31 exact controls in the broad cohort, and surfaces whose first difference lies elsewhere remain controls. This route is judged separately from digit-hyphen forms such as `F-35`, which this detector excludes, and separately from the shortform guard that legitimately precedes `CD` in `CD-ROM`. + + This shape is not an engine implementation premise. The 2024 PDF's math rule 6 defines parentheses and grouping parentheses, rule 11 defines mathematical-expression spacing, rule 12 covers Roman letters in formulas as well as Korean sentences, and rule 45 shows Roman-letter function notation followed by parentheses. Excluding visible operators, scripts, and nesting narrows this corpus cohort, but the PDF does not make the remaining surface shape sufficient to rule out every mathematical counterexample. The cluster therefore remains conservative pending-review evidence only. The standalone-uppercase cohort is similarly ambiguous. Hangeul rule 28's appendix defines the capital-word indicator for two or more consecutive capitals, and rule 29 defines Roman indicators around Roman text in a Korean sentence. But math rule 12 also uses uppercase Roman variables, while science rule 7 uses uppercase runs in chemical formulas. The input gate cannot determine which semantic regime applies, so its output differences are observations to review, not permission to infer an engine rule from the corpus reference. diff --git a/libs/braillify/examples/nikl_corpus_analyze.rs b/libs/braillify/examples/nikl_corpus_analyze.rs index bf594e37..b4f2fabd 100644 --- a/libs/braillify/examples/nikl_corpus_analyze.rs +++ b/libs/braillify/examples/nikl_corpus_analyze.rs @@ -192,6 +192,9 @@ struct AnalysisReport { pending_first_difference_transitions_after_localized_cohorts: BTreeMap, compact_numeric_ascii_suffixes: BTreeMap, + grade1_shortform_prefix_surfaces: BTreeMap, + grade1_numeric_continuation_surfaces: BTreeMap, + grade1_hyphen_continuation_surfaces: BTreeMap, overlapping_traits: BTreeMap, shards: BTreeMap, samples: BTreeMap>, @@ -586,6 +589,12 @@ const DECIMAL_POINT_BETWEEN_DIGITS: &str = "decimal_point_between_ascii_digits"; const COMPACT_NUMERIC_ASCII_SUFFIX: &str = "compact_numeric_ascii_letter_suffix"; const RULE69_ASCII_UNIT_BEFORE_TERMINATOR_SKIPPING_SYMBOL: &str = "rule69_ascii_unit_before_terminator_skipping_symbol"; +const ALLCAPS_SHORTFORM_PREFIX_COLLISION: &str = + "allcaps_roman_run_beginning_with_pure_letter_shortform"; +const ROMAN_UPPERCASE_AFTER_DIGIT: &str = + "uppercase_ascii_run_immediately_after_digit_in_roman_sequence"; +const ROMAN_UPPERCASE_AFTER_HYPHEN: &str = + "uppercase_ascii_run_immediately_after_hyphen_in_roman_sequence"; #[derive(Clone, Copy, Debug, PartialEq, Eq)] struct InputSpan { @@ -920,6 +929,137 @@ fn allcaps_roman_runs_containing_ou(input: &str) -> Vec { runs } +/// Finds maximal pure-uppercase ASCII letter runs whose beginning is itself a +/// pure-letter UEB shortform abbreviation. UEB 5.7.2 and 10.9.7-10.9.8 require +/// grade 1 both for a complete shortform-shaped letters-sequence (`WD`) and for +/// a longer word beginning with one (`PDS`, `LLM`, `GDP`). +fn allcaps_shortform_prefix_spans(input: &str) -> Vec { + let bytes = input.as_bytes(); + let mut spans = Vec::new(); + let mut cursor = 0usize; + while cursor < bytes.len() { + if !bytes[cursor].is_ascii_alphabetic() { + cursor += input[cursor..] + .chars() + .next() + .expect("cursor must remain on a character boundary") + .len_utf8(); + continue; + } + + let start_byte = cursor; + while bytes.get(cursor).is_some_and(u8::is_ascii_alphabetic) { + cursor += 1; + } + let end_byte = cursor; + let run = &input[start_byte..end_byte]; + // The isolated prefix is current-engine evidence only: no corpus + // expected/reference value participates in this candidate gate. For a + // pure all-caps letters-sequence, a leading ⠰ is the engine's existing + // UEB 5.7.2/10.9.7 shortform-collision decision. + let has_shortform_prefix = (2..=run.len()).any(|end| { + braillify::encode_to_unicode(&run[..end]).is_ok_and(|encoded| encoded.starts_with('⠰')) + }); + if run.len() >= 2 + && run.bytes().all(|byte| byte.is_ascii_uppercase()) + && has_shortform_prefix + && input[..start_byte] + .chars() + .next_back() + .is_none_or(|previous| !previous.is_ascii_alphanumeric()) + && input[end_byte..] + .chars() + .next() + .is_none_or(|next| !next.is_ascii_alphanumeric()) + { + spans.push(InputSpan { + start_byte, + end_byte, + }); + } + } + spans +} + +/// Finds maximal ASCII alphanumeric identifiers containing an immediate +/// digit-to-uppercase transition (`O4O`, `Li2S`, `V2X`). The numeric indicator +/// itself sets grade-1 mode under UEB 5.6.1, and 5.6.2 does not terminate that +/// mode at a capital indicator; the cohort measures whether an extra `⠰` is +/// nevertheless emitted at this exact boundary. +fn roman_uppercase_after_digit_spans(input: &str) -> Vec { + let bytes = input.as_bytes(); + let mut spans = Vec::new(); + let mut cursor = 0usize; + while cursor < bytes.len() { + if !bytes[cursor].is_ascii_alphanumeric() { + cursor += input[cursor..] + .chars() + .next() + .expect("cursor must remain on a character boundary") + .len_utf8(); + continue; + } + let start_byte = cursor; + while bytes.get(cursor).is_some_and(u8::is_ascii_alphanumeric) { + cursor += 1; + } + let end_byte = cursor; + let run = &bytes[start_byte..end_byte]; + if run.iter().any(u8::is_ascii_alphabetic) + && run + .windows(2) + .any(|pair| pair[0].is_ascii_digit() && pair[1].is_ascii_uppercase()) + { + spans.push(InputSpan { + start_byte, + end_byte, + }); + } + } + spans +} + +/// Finds maximal ASCII Roman identifiers containing a hyphen immediately +/// followed by an uppercase run (`U-ENTER`, `CD-ROM`). This is independent of +/// the digit transition above: Korean rule 29 keeps one Roman section around +/// consecutive Roman text, while UEB 5.6.2 gives a hyphen separate significance +/// only when terminating numeric grade-1 mode. +fn roman_uppercase_after_hyphen_spans(input: &str) -> Vec { + let bytes = input.as_bytes(); + let mut spans = Vec::new(); + let mut cursor = 0usize; + while cursor < bytes.len() { + if !bytes[cursor].is_ascii_alphanumeric() { + cursor += input[cursor..] + .chars() + .next() + .expect("cursor must remain on a character boundary") + .len_utf8(); + continue; + } + let start_byte = cursor; + while bytes + .get(cursor) + .is_some_and(|byte| byte.is_ascii_alphanumeric() || *byte == b'-') + { + cursor += 1; + } + let end_byte = cursor; + let run = &bytes[start_byte..end_byte]; + if run.iter().any(u8::is_ascii_alphabetic) + && run + .windows(2) + .any(|pair| pair[0] == b'-' && pair[1].is_ascii_uppercase()) + { + spans.push(InputSpan { + start_byte, + end_byte, + }); + } + } + spans +} + /// Locates each detected run in the full current-engine output by searching /// for that run's independently encoded signature. This uses neither the /// corpus reference nor a hard-coded braille value. @@ -985,6 +1125,85 @@ fn korean_context_signature_ranges( ranges.into_iter().map(|(start, end)| start..end).collect() } +/// Produces the current signature of a Roman candidate embedded inside one +/// mixed Korean word. This intentionally complements the space-delimited probe +/// above: token-level capitalization can add grade 1 to standalone `WD`, while +/// the residual under review occurs in attached forms such as `한글(WD)`. +fn mixed_korean_word_signature(run: &str) -> Option { + let left = braillify::encode_to_unicode("가").ok()?; + let right = braillify::encode_to_unicode("나").ok()?; + let probe = braillify::encode_to_unicode(&format!("가{run}나")).ok()?; + let probe_cells = probe.chars().collect::>(); + let start = left.chars().count(); + let end = probe_cells.len().checked_sub(right.chars().count())?; + Some(probe_cells.get(start..end)?.iter().collect()) +} + +fn grade1_cohort_signature_ranges( + input: &str, + actual: &str, + spans: &[InputSpan], + leading_boundary_cells: usize, +) -> Vec> { + let mut ranges = BTreeSet::new(); + for candidate in spans { + let run = &input[candidate.start_byte..candidate.end_byte]; + for signature in [ + korean_context_signature(run), + mixed_korean_word_signature(run), + ] + .into_iter() + .flatten() + { + // The neutral Korean probe appends its own current-engine exit cell. + // A corpus candidate followed by a closing parenthesis can suppress + // that exit under Korean rule 34, so search both the complete probe + // and the same current-engine signature without only that generated + // trailing boundary. Candidate letters and indicators are untouched. + let without_probe_exit = signature + .char_indices() + .next_back() + .map(|(last, _)| signature[..last].to_string()); + for searchable in [Some(signature), without_probe_exit] + .into_iter() + .flatten() + .filter(|candidate| !candidate.is_empty()) + { + let signature_cells = searchable.chars().count(); + for (start_byte, _) in actual.match_indices(&searchable) { + let signature_start = actual[..start_byte].chars().count(); + ranges.insert(( + signature_start.saturating_sub(leading_boundary_cells), + signature_start + signature_cells, + )); + } + } + } + } + ranges.into_iter().map(|(start, end)| start..end).collect() +} + +fn first_difference_in_grade1_cohort_spans(item: &EncodedCase, spans: &[InputSpan]) -> bool { + let Ok(actual) = &item.actual else { + return false; + }; + if actual == &item.located.case.unicode { + return false; + } + let first_difference = first_difference_cell(&item.located.case.unicode, actual); + let expected_cell = item.located.case.unicode.chars().nth(first_difference); + let actual_cell = actual.chars().nth(first_difference); + if !matches!( + (expected_cell, actual_cell), + (Some('\u{2830}'), Some('\u{2820}')) | (Some('\u{2820}'), Some('\u{2830}')) + ) { + return false; + } + grade1_cohort_signature_ranges(&item.located.case.input, actual, spans, 1) + .into_iter() + .any(|range| range.contains(&first_difference)) +} + fn allcaps_ou_actual_ranges(input: &str, actual: &str) -> Vec> { current_engine_signature_ranges(input, actual, &allcaps_roman_runs_containing_ou(input), 0) } @@ -1142,7 +1361,7 @@ fn first_difference_in_korean_context_signature_spans( /// Only output-localized cohorts may claim a first difference. Broad input-only /// coexistence traits are intentionally absent: excluding them would hide /// unrelated causes merely because a sentence also contains Roman text. -fn first_difference_claimed_by_localized_cohort(item: &EncodedCase) -> bool { +fn first_difference_claimed_by_prior_localized_cohort(item: &EncodedCase) -> bool { first_difference_in_allcaps_ou_run(item) || first_difference_in_compact_numeric_ascii_suffix(item) || first_difference_in_decimal_word(item) @@ -1167,6 +1386,22 @@ fn first_difference_claimed_by_localized_cohort(item: &EncodedCase) -> bool { ) } +fn first_difference_claimed_by_localized_cohort(item: &EncodedCase) -> bool { + first_difference_claimed_by_prior_localized_cohort(item) + || first_difference_in_grade1_cohort_spans( + item, + &allcaps_shortform_prefix_spans(&item.located.case.input), + ) + || first_difference_in_grade1_cohort_spans( + item, + &roman_uppercase_after_digit_spans(&item.located.case.input), + ) + || first_difference_in_grade1_cohort_spans( + item, + &roman_uppercase_after_hyphen_spans(&item.located.case.input), + ) +} + /// Input-only candidate gate for acronym expansions such as /// `HCA(Home Connectivity Alliance)`. /// @@ -1810,10 +2045,25 @@ fn analyze( UPPERCASE_ROMAN_HYPHEN_DIGITS.to_string(), PendingRuleReviewClusterStats::default(), ), + ( + ALLCAPS_SHORTFORM_PREFIX_COLLISION.to_string(), + PendingRuleReviewClusterStats::default(), + ), + ( + ROMAN_UPPERCASE_AFTER_DIGIT.to_string(), + PendingRuleReviewClusterStats::default(), + ), + ( + ROMAN_UPPERCASE_AFTER_HYPHEN.to_string(), + PendingRuleReviewClusterStats::default(), + ), ]); let mut pending_first_difference_cell_transitions = BTreeMap::new(); let mut pending_first_difference_transitions_after_localized_cohorts = BTreeMap::new(); let mut compact_numeric_ascii_suffixes = BTreeMap::new(); + let mut grade1_shortform_prefix_surfaces = BTreeMap::new(); + let mut grade1_numeric_continuation_surfaces = BTreeMap::new(); + let mut grade1_hyphen_continuation_surfaces = BTreeMap::new(); let mut exact = 0usize; for item in &encoded { @@ -1977,6 +2227,42 @@ fn analyze( )), false, ), + ( + ALLCAPS_SHORTFORM_PREFIX_COLLISION, + !allcaps_shortform_prefix_spans(&item.located.case.input).is_empty(), + Some( + !first_difference_claimed_by_prior_localized_cohort(item) + && first_difference_in_grade1_cohort_spans( + item, + &allcaps_shortform_prefix_spans(&item.located.case.input), + ), + ), + true, + ), + ( + ROMAN_UPPERCASE_AFTER_DIGIT, + !roman_uppercase_after_digit_spans(&item.located.case.input).is_empty(), + Some( + !first_difference_claimed_by_prior_localized_cohort(item) + && first_difference_in_grade1_cohort_spans( + item, + &roman_uppercase_after_digit_spans(&item.located.case.input), + ), + ), + true, + ), + ( + ROMAN_UPPERCASE_AFTER_HYPHEN, + !roman_uppercase_after_hyphen_spans(&item.located.case.input).is_empty(), + Some( + !first_difference_claimed_by_prior_localized_cohort(item) + && first_difference_in_grade1_cohort_spans( + item, + &roman_uppercase_after_hyphen_spans(&item.located.case.input), + ), + ), + true, + ), ] { if !present { continue; @@ -2015,6 +2301,42 @@ fn analyze( ); } + for (target, spans) in [ + ( + &mut grade1_shortform_prefix_surfaces, + allcaps_shortform_prefix_spans(&item.located.case.input), + ), + ( + &mut grade1_numeric_continuation_surfaces, + roman_uppercase_after_digit_spans(&item.located.case.input), + ), + ( + &mut grade1_hyphen_continuation_surfaces, + roman_uppercase_after_hyphen_spans(&item.located.case.input), + ), + ] { + let mut surface_spans = BTreeMap::>::new(); + for span in spans { + surface_spans + .entry(item.located.case.input[span.start_byte..span.end_byte].to_string()) + .or_default() + .push(span); + } + for (surface, matching_spans) in surface_spans { + let localized = !first_difference_claimed_by_prior_localized_cohort(item) + && first_difference_in_grade1_cohort_spans(item, &matching_spans); + record_structural_cohort_case( + target.entry(surface).or_default(), + item, + &primary_key, + &reason_key, + sample_limit, + Some(localized), + true, + ); + } + } + let shard = shards.entry(item.located.shard.clone()).or_default(); shard.total += 1; if primary == PrimaryClass::Exact { @@ -2148,6 +2470,9 @@ fn analyze( pending_first_difference_cell_transitions, pending_first_difference_transitions_after_localized_cohorts, compact_numeric_ascii_suffixes, + grade1_shortform_prefix_surfaces, + grade1_numeric_continuation_surfaces, + grade1_hyphen_continuation_surfaces, overlapping_traits: traits, shards, samples, @@ -2449,6 +2774,138 @@ fn markdown(report: &AnalysisReport) -> String { } } } + text.push_str("\n## UEB grade-1 first-difference cohorts\n\n"); + text.push_str( + "These three cohorts are defined by both an input boundary and the sentence's actual \ + first-difference transition. They therefore do not claim every mismatch merely \ + coexisting with an ASCII run. Candidate, exact, mismatch, and primary-class counts \ + remain cross-cutting controls; only the reported target transition is the localized \ + residual under review. The reverse transition is retained separately rather than \ + folded into the target.\n\n", + ); + text.push_str( + "| Cohort | Candidates | Exact controls | Mismatch | Target localized | Reverse |\n\ + |---|---:|---:|---:|---:|---:|\n", + ); + for (name, target, reverse) in [ + ( + ALLCAPS_SHORTFORM_PREFIX_COLLISION, + "U+2830 ⠰ -> U+2820 ⠠", + "U+2820 ⠠ -> U+2830 ⠰", + ), + ( + ROMAN_UPPERCASE_AFTER_DIGIT, + "U+2820 ⠠ -> U+2830 ⠰", + "U+2830 ⠰ -> U+2820 ⠠", + ), + ( + ROMAN_UPPERCASE_AFTER_HYPHEN, + "U+2820 ⠠ -> U+2830 ⠰", + "U+2830 ⠰ -> U+2820 ⠠", + ), + ] { + let stats = report + .pending_rule_review_clusters + .get(name) + .expect("registered grade-1 cohort must exist"); + let target_count = stats + .first_difference_in_output_signature_transitions + .get(target) + .copied() + .unwrap_or(0); + let reverse_count = stats + .first_difference_in_output_signature_transitions + .get(reverse) + .copied() + .unwrap_or(0); + text.push_str(&format!( + "| `{name}` | {} | {} | {} | {target_count} | {reverse_count} |\n", + stats.candidates, stats.exact, stats.mismatch + )); + } + text.push_str( + "\n### All-caps shortform prefix at an attached Roman entry\n\n\ + UEB 2024 rule 5.7.2 requires grade-1 mode when a letters-sequence could \ + be read as a shortform or as containing one. Rule 10.9.7 covers a \ + standing-alone shortform-shaped sequence, rule 10.9.8 covers a sequence at \ + the beginning of a longer word (the PDF example is `LLC`), and rule 5.8.1 \ + places grade 1 before capitalization. The current standalone ASCII-token \ + route already supplies that guard for complete shortform-shaped controls \ + such as `AC`, `CD`, `IMM`, and `AG`; the attached Korean-word/parenthetical \ + route enters directly at the capital marker and accounts for the localized \ + `⠰ -> ⠠` signature. This is a routing distinction supported independently \ + by the PDF, not an expected-output lookup. Longer runs such as `GDP` and \ + `LLM` are admitted only when a pure-letter shortform occupies the beginning \ + of the run; a shortform appearing later would require the distinct grade-1 \ + word rule 10.9.9 and is outside this implementation candidate.\n\n", + ); + text.push_str( + "Same-surface controls demonstrate why primary classes must not be changed \ + by cohort membership:\n\n\ + | Surface | Candidates | Exact | Mismatch | Target-localized |\n\ + |---|---:|---:|---:|---:|\n", + ); + for surface in ["AC", "LLM", "CD", "IMM", "AG", "GDP", "WD"] { + if let Some(stats) = report.grade1_shortform_prefix_surfaces.get(surface) { + let localized = stats + .first_difference_in_output_signature_transitions + .get("U+2830 ⠰ -> U+2820 ⠠") + .copied() + .unwrap_or(0); + text.push_str(&format!( + "| `{surface}` | {} | {} | {} | {localized} |\n", + stats.candidates, stats.exact, stats.mismatch + )); + } + } + for surface in ["AC", "LLM", "CD"] { + let Some(stats) = report.grade1_shortform_prefix_surfaces.get(surface) else { + continue; + }; + let exact = stats + .samples + .get("exact") + .and_then(|samples| samples.first()); + let localized = stats + .samples + .get("localized_mismatch") + .and_then(|samples| samples.first()); + if let (Some(exact), Some(localized)) = (exact, localized) { + text.push_str(&format!( + "\n- `{surface}` exact control: `{}` #{} — {}\n\ + - `{surface}` localized mismatch: `{}` #{} — {}\n", + exact.shard, + exact.index, + exact.input.chars().take(180).collect::(), + localized.shard, + localized.index, + localized.input.chars().take(180).collect::() + )); + } + } + text.push_str( + "\n### Uppercase immediately after a digit\n\n\ + UEB rule 5.6.1 says the numeric indicator establishes grade-1 mode, and \ + rule 5.6.2 ends that mode at a space, hyphen, dash, or grade-1 terminator; \ + a capitalization indicator is not a terminator. Korean rule 35 likewise \ + keeps Roman letters and an adjacent number in one Roman section. The 330 \ + localized `⠠ -> ⠰` cases therefore identify a separate possible redundant \ + continuation at the digit-to-capital boundary. This cohort is not merged \ + with the shortform-prefix route: the one reverse case and the absence of \ + same-surface exact controls among frequent forms such as `B2B`, `V2X`, \ + `Li2S`, and `O4O` require an independent state-machine audit before any \ + engine change.\n\n\ + ### Uppercase immediately after a hyphen\n\n\ + UEB rule 5.7.2 prints `CD-ROM` with one grade-1 indicator before `CD` and \ + no second grade-1 indicator after the hyphen. Korean rule 29 similarly \ + uses one Roman span for consecutive Roman text. The 312 localized \ + `⠠ -> ⠰` cases are therefore tracked as a distinct hyphen-continuation \ + candidate. The three reverse cases, only 31 exact controls in the broad \ + cohort, and surfaces whose first difference lies elsewhere remain \ + controls. This route is judged separately from digit-hyphen forms such as \ + `F-35`, which this detector excludes, and separately from the shortform \ + guard that legitimately precedes `CD` in `CD-ROM`.\n\n", + ); text.push_str( "\nThis shape is not an engine implementation premise. The 2024 PDF's math rule 6 \ defines parentheses and grouping parentheses, rule 11 defines mathematical-expression \ @@ -3623,6 +4080,73 @@ mod tests { assert_eq!(actual, expected); } + #[rstest::rstest] + #[case::whole_shortform("가(WD) 나", vec!["WD"])] + #[case::longer_prefixes("PDS LLM GDP", vec!["PDS", "LLM", "GDP"])] + #[case::ueb_examples("ALT NEC LLC", vec!["ALT", "NEC", "LLC"])] + #[case::noncolliding_controls("US KBS MCH", vec![])] + #[case::alphanumeric_excluded("O4O Li2S V2X", vec![])] + fn detects_allcaps_shortform_prefix_collisions( + #[case] input: &str, + #[case] expected: Vec<&str>, + ) { + let actual = allcaps_shortform_prefix_spans(input) + .into_iter() + .map(|span| &input[span.start_byte..span.end_byte]) + .collect::>(); + assert_eq!(actual, expected); + } + + #[rstest::rstest] + #[case::mixed_identifiers("O4O Li2S V2X", vec!["O4O", "Li2S", "V2X"])] + #[case::multiple_boundaries("A1B2C", vec!["A1B2C"])] + #[case::lowercase_after_digit("240mg", vec![])] + #[case::hyphenated_identifier("U-ENTER", vec![])] + fn detects_uppercase_after_digit_in_roman_sequence( + #[case] input: &str, + #[case] expected: Vec<&str>, + ) { + let actual = roman_uppercase_after_digit_spans(input) + .into_iter() + .map(|span| &input[span.start_byte..span.end_byte]) + .collect::>(); + assert_eq!(actual, expected); + } + + #[rstest::rstest] + #[case::uppercase_segments("U-ENTER CD-ROM", vec!["U-ENTER", "CD-ROM"])] + #[case::digit_after_hyphen("F-35", vec![])] + #[case::lowercase_after_hyphen("U-enter", vec![])] + fn detects_uppercase_after_hyphen_in_roman_sequence( + #[case] input: &str, + #[case] expected: Vec<&str>, + ) { + let actual = roman_uppercase_after_hyphen_spans(input) + .into_iter() + .map(|span| &input[span.start_byte..span.end_byte]) + .collect::>(); + assert_eq!(actual, expected); + } + + #[rstest::rstest] + #[case::shortform_prefix("가(WD) 나", allcaps_shortform_prefix_spans("가(WD) 나"))] + #[case::numeric_continuation("가(Li2S) 나", roman_uppercase_after_digit_spans("가(Li2S) 나"))] + #[case::hyphen_continuation( + "가(U-ENTER) 나", + roman_uppercase_after_hyphen_spans("가(U-ENTER) 나") + )] + fn locates_grade1_cohort_signature_in_korean_context( + #[case] input: &str, + #[case] spans: Vec, + ) { + let actual = braillify::encode_to_unicode(input).expect("grade-1 probe must encode"); + let ranges = grade1_cohort_signature_ranges(input, &actual, &spans, 1); + + assert_eq!(ranges.len(), 1); + assert!(ranges[0].start < ranges[0].end); + assert!(ranges[0].end <= actual.chars().count()); + } + #[test] fn locates_allcaps_ou_signature_in_complete_output() { let input = "업무협약(MOU)을 체결했다."; From c0f35074c183c4e34e58a975ed93c717be894162 Mon Sep 17 00:00:00 2001 From: devfive Date: Tue, 1 Sep 2026 10:35:27 +0900 Subject: [PATCH 26/77] Guard attached complete shortforms with grade 1 --- docs/corpus-analysis/NIKL_2025_V1.md | 284 +++++++++--------- .../braillify/examples/nikl_corpus_analyze.rs | 28 +- .../braillify/src/rules/english_ueb/engine.rs | 4 + libs/braillify/src/rules/english_ueb/span.rs | 2 + libs/braillify/src/rules/korean/rule_28.rs | 33 ++ libs/braillify/src/rules/korean/rule_69.rs | 2 +- 6 files changed, 210 insertions(+), 143 deletions(-) diff --git a/docs/corpus-analysis/NIKL_2025_V1.md b/docs/corpus-analysis/NIKL_2025_V1.md index 6d0187ac..65984ee8 100644 --- a/docs/corpus-analysis/NIKL_2025_V1.md +++ b/docs/corpus-analysis/NIKL_2025_V1.md @@ -7,9 +7,9 @@ | Metric | Count | |---|---:| | Total | 83528 | -| Exact | 66546 | -| Mismatch | 16982 | -| Exact accuracy | 79.67% | +| Exact | 66683 | +| Mismatch | 16845 | +| Exact accuracy | 79.83% | | Duplicate records | 0 | | Inputs with conflicting references | 0 | @@ -20,19 +20,19 @@ Primary classes are evidence gates, not permissions to change the engine. `imple | Primary class | Count | |---|---:| | `comparison_method` | 284 | -| `corpus_suspect` | 1094 | -| `exact` | 66546 | -| `pending_rule_review` | 15419 | +| `corpus_suspect` | 1095 | +| `exact` | 66683 | +| `pending_rule_review` | 15281 | | `unsupported_character_review` | 185 | | Reproducible reason | Count | |---|---:| -| `exact` | 66546 | -| `foreign_text_rule_review` | 15244 | +| `exact` | 66683 | +| `foreign_text_rule_review` | 15106 | | `nfkc_input_equivalent` | 284 | | `number_rule_review` | 167 | | `punctuation_rule_review` | 8 | -| `rule34_roman_indicator_before_opening_parenthesis` | 1094 | +| `rule34_roman_indicator_before_opening_parenthesis` | 1095 | | `unsupported_character_review` | 185 | ## Pending first-difference cell transitions @@ -42,24 +42,24 @@ This ranking is a diagnostic selector, not an implementation rule. It counts onl | Rank | Expected → actual first cell | Cases | |---:|---|---:| | 1 | `U+2834 ⠴ -> U+2800 ⠀` | 2142 | -| 2 | `U+2815 ⠕ -> U+2833 ⠳` | 1311 | -| 3 | `U+2830 ⠰ -> U+2820 ⠠` | 1018 | -| 4 | `U+2820 ⠠ -> U+2830 ⠰` | 800 | +| 2 | `U+2815 ⠕ -> U+2833 ⠳` | 1312 | +| 3 | `U+2820 ⠠ -> U+2830 ⠰` | 843 | +| 4 | `U+2830 ⠰ -> U+2820 ⠠` | 834 | | 5 | `U+2810 ⠐ -> U+2832 ⠲` | 642 | -| 6 | `U+2820 ⠠ -> U+2834 ⠴` | 467 | +| 6 | `U+2820 ⠠ -> U+2834 ⠴` | 466 | | 7 | `U+2826 ⠦ -> U+2834 ⠴` | 453 | | 8 | `U+280E ⠎ -> U+280C ⠌` | 444 | | 9 | `U+2826 ⠦ -> U+2800 ⠀` | 424 | | 10 | `U+2808 ⠈ -> U+2832 ⠲` | 409 | | 11 | `U+2801 ⠁ -> U+281C ⠜` | 386 | | 12 | `U+2800 ⠀ -> U+2807 ⠇` | 360 | -| 13 | `U+283C ⠼ -> U+2800 ⠀` | 357 | -| 14 | `U+2834 ⠴ -> U+2830 ⠰` | 325 | +| 13 | `U+283C ⠼ -> U+2800 ⠀` | 358 | +| 14 | `U+2834 ⠴ -> U+2830 ⠰` | 327 | | 15 | `U+2811 ⠑ -> U+282B ⠫` | 322 | | 16 | `U+2832 ⠲ -> U+2838 ⠸` | 275 | -| 17 | `U+2800 ⠀ -> U+2832 ⠲` | 254 | +| 17 | `U+2800 ⠀ -> U+2832 ⠲` | 253 | | 18 | `U+2810 ⠐ -> U+2802 ⠂` | 210 | -| 19 | `U+2817 ⠗ -> U+2820 ⠠` | 161 | +| 19 | `U+2817 ⠗ -> U+2820 ⠠` | 162 | | 20 | `U+2826 ⠦ -> U+2810 ⠐` | 155 | ### `U+2834 ⠴ -> U+2800 ⠀` @@ -108,29 +108,6 @@ This ranking is a diagnostic selector, not an implementation rule. It counts onl - first differing cell (zero-based): 185 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -### `U+2830 ⠰ -> U+2820 ⠠` - -- `sentence_01.json` #310: 낸드플래시 반도체 세계 2위인 일본 키옥시아와 4위인 미국 웨스턴디지털(WD) 합병설에 반도체 업계가 촉각을 곤두 세우고 있다. - - expected: `⠕⠨⠕⠓⠞⠦⠄⠴⠰⠠⠠⠺⠙⠠⠴⠀⠚⠃⠘⠻⠠⠞⠝⠀` - - actual: `⠕⠨⠕⠓⠞⠦⠄⠴⠠⠠⠺⠙⠠⠴⠀⠚⠃⠘⠻⠠⠞⠝⠀⠘` - - first differing cell (zero-based): 70 - - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_02.json` #99: 황승주 씨는 “기능성 소화불량증, 특히 PDS(식후불편감증후군) 복부팽만감 유형으로 고통 받는 환자들에게 적용가능한 안전하고 높은 치료효율의 한약소재를 개발해 국민건강에 이바지하고자 한다”고 말했다. - - expected: `⠀⠓⠪⠁⠚⠕⠀⠴⠰⠠⠠⠏⠙⠎⠦⠄⠠⠕⠁⠚⠍⠘⠯⠙` - - actual: `⠀⠓⠪⠁⠚⠕⠀⠴⠠⠠⠏⠙⠎⠦⠄⠠⠕⠁⠚⠍⠘⠯⠙⠡` - - first differing cell (zero-based): 45 - - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_03.json` #30: “챗GPT보다 가벼운 여러 대규모언어모델(LLM)이 출현해 오픈AI 독점 구도에 균열을 내고 있다. 한국어 특화 LLM으로 글로벌 경쟁에 대응하면서 세부 서비스(튜닝) 모델에서 차별성을 확보하는 전략이 중요하다.” - - expected: `⠑⠥⠊⠝⠂⠦⠄⠴⠰⠠⠠⠇⠇⠍⠠⠴⠕⠀⠰⠯⠚⠡⠚⠗` - - actual: `⠑⠥⠊⠝⠂⠦⠄⠴⠠⠠⠇⠇⠍⠠⠴⠕⠀⠰⠯⠚⠡⠚⠗⠀` - - first differing cell (zero-based): 40 - - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_04.json` #81: SGI는 “산업연관분석을 활용해 우리나라의 대일 수출 증가가 국내총생산(GDP)에 미치는 영향을 계산해 보면 경제성장률은 0.1%포인트 높아질 것”이라고 밝혔다. - - expected: `⠠⠗⠶⠇⠒⠦⠄⠴⠰⠠⠠⠛⠙⠏⠠⠴⠝⠀⠑⠕⠰⠕⠉⠵` - - actual: `⠠⠗⠶⠇⠒⠦⠄⠴⠠⠠⠛⠙⠏⠠⠴⠝⠀⠑⠕⠰⠕⠉⠵⠀` - - first differing cell (zero-based): 72 - - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` - ### `U+2820 ⠠ -> U+2830 ⠰` - `sentence_01.json` #25: 세븐일레븐은 올해 말 정식 버전 오픈을 목표로 하며, 이를 통해 주력상품을 구매하고 배송까지 받을 수 있는 차세대 오프라인을 위한 온라인(O4O) 시스템을 구축해 나갈 계획이다. @@ -138,10 +115,10 @@ This ranking is a diagnostic selector, not an implementation rule. It counts onl - actual: `⠟⠦⠄⠴⠠⠕⠼⠙⠰⠠⠕⠠⠴⠀⠠⠕⠠⠪⠓⠝⠢⠮⠀⠈` - first differing cell (zero-based): 129 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_02.json` #313: 순천향대(총장 김승우)는 지난 3일 한국국제협력단(KOICA)과 함께 우즈베키스탄의 수도 타슈겐트에서 스타트업 지원센터 ‘U-ENTER(Uzbekistan Entrepreneurship Innovation Center)’ 준공식을 개최했다고 밝혔다. - - expected: `⠎⠀⠠⠦⠴⠠⠥⠤⠠⠠⠢⠞⠻⠐⠣⠠⠥⠵⠃⠑⠅⠊⠌⠁` - - actual: `⠎⠀⠠⠦⠴⠠⠥⠤⠰⠠⠠⠢⠞⠻⠐⠣⠠⠥⠵⠃⠑⠅⠊⠌` - - first differing cell (zero-based): 131 +- `sentence_02.json` #68: 조성된 무장애나눔길은 통행이 많은 숲속의집~생태숲학습관 0.73km 구간에 평균 경사도 5% 이하 데크산책로와 황토포장길로 조성됐으며 전국 무장애나눔길 최초로 장애물 없는 생활환경(BF)인증을 추진하고 있다. + - expected: `⠚⠧⠒⠈⠻⠦⠄⠴⠠⠠⠃⠋⠠⠴⠟⠨⠪⠶⠮⠀⠰⠍⠨⠟` + - actual: `⠚⠧⠒⠈⠻⠦⠄⠴⠰⠠⠠⠃⠋⠠⠴⠟⠨⠪⠶⠮⠀⠰⠍⠨` + - first differing cell (zero-based): 190 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` - `sentence_03.json` #47: 이수화학이 미국 과학 기술 엔지니어링 회사 KBR과 전고체 배터리 소재 황화리튬(Li2S)의 상업공정 공동 개발에 나선다. 양사는 이를 위해 공동개발 계약을 체결했다고 20일 밝혔다. - expected: `⠦⠄⠴⠠⠇⠊⠼⠃⠠⠎⠠⠴⠺⠀⠇⠶⠎⠃⠈⠿⠨⠻⠀⠈` @@ -154,6 +131,29 @@ This ranking is a diagnostic selector, not an implementation rule. It counts onl - first differing cell (zero-based): 63 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +### `U+2830 ⠰ -> U+2820 ⠠` + +- `sentence_01.json` #312: 엔비디아가 대규모 언어 모델(LLM)과 음성 AI를 주제로 엔비디아 AI 개발자 밋업을 개최한다. 이번 개발자 밋업은 오는 17일 오후 2시부터 5시까지 온라인으로 진행된다. + - expected: `⠑⠥⠊⠝⠂⠦⠄⠴⠰⠠⠠⠇⠇⠍⠠⠴⠈⠧⠀⠪⠢⠠⠻⠀` + - actual: `⠑⠥⠊⠝⠂⠦⠄⠴⠠⠠⠇⠇⠍⠠⠴⠈⠧⠀⠪⠢⠠⠻⠀⠴` + - first differing cell (zero-based): 27 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_02.json` #99: 황승주 씨는 “기능성 소화불량증, 특히 PDS(식후불편감증후군) 복부팽만감 유형으로 고통 받는 환자들에게 적용가능한 안전하고 높은 치료효율의 한약소재를 개발해 국민건강에 이바지하고자 한다”고 말했다. + - expected: `⠀⠓⠪⠁⠚⠕⠀⠴⠰⠠⠠⠏⠙⠎⠦⠄⠠⠕⠁⠚⠍⠘⠯⠙` + - actual: `⠀⠓⠪⠁⠚⠕⠀⠴⠠⠠⠏⠙⠎⠦⠄⠠⠕⠁⠚⠍⠘⠯⠙⠡` + - first differing cell (zero-based): 45 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_03.json` #30: “챗GPT보다 가벼운 여러 대규모언어모델(LLM)이 출현해 오픈AI 독점 구도에 균열을 내고 있다. 한국어 특화 LLM으로 글로벌 경쟁에 대응하면서 세부 서비스(튜닝) 모델에서 차별성을 확보하는 전략이 중요하다.” + - expected: `⠑⠥⠊⠝⠂⠦⠄⠴⠰⠠⠠⠇⠇⠍⠠⠴⠕⠀⠰⠯⠚⠡⠚⠗` + - actual: `⠑⠥⠊⠝⠂⠦⠄⠴⠠⠠⠇⠇⠍⠠⠴⠕⠀⠰⠯⠚⠡⠚⠗⠀` + - first differing cell (zero-based): 40 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_04.json` #81: SGI는 “산업연관분석을 활용해 우리나라의 대일 수출 증가가 국내총생산(GDP)에 미치는 영향을 계산해 보면 경제성장률은 0.1%포인트 높아질 것”이라고 밝혔다. + - expected: `⠠⠗⠶⠇⠒⠦⠄⠴⠰⠠⠠⠛⠙⠏⠠⠴⠝⠀⠑⠕⠰⠕⠉⠵` + - actual: `⠠⠗⠶⠇⠒⠦⠄⠴⠠⠠⠛⠙⠏⠠⠴⠝⠀⠑⠕⠰⠕⠉⠵⠀` + - first differing cell (zero-based): 72 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` + ### `U+2810 ⠐ -> U+2832 ⠲` - `sentence_01.json` #57: 아키에이지 워는 원작 아키에이지 개발사 엑스엘게임즈(각자대표 송재경, 최관호)가 개발 중인 신작 PC·모바일 크로스플랫폼 대규모다중접속역할수행게임(MMORPG)이다. 원작의 향수를 자극하는 스토리와 캐릭터, 언리얼 엔진4를 활용한 고퀄리티 그래픽이 특징이다. @@ -299,20 +299,20 @@ This ranking removes only cases whose first difference is inside an existing out | Rank | Expected → actual first cell | Residual cases | |---:|---|---:| | 1 | `U+2834 ⠴ -> U+2800 ⠀` | 545 | -| 2 | `U+2820 ⠠ -> U+2834 ⠴` | 454 | +| 2 | `U+2820 ⠠ -> U+2834 ⠴` | 453 | | 3 | `U+2826 ⠦ -> U+2834 ⠴` | 447 | | 4 | `U+280E ⠎ -> U+280C ⠌` | 444 | | 5 | `U+2808 ⠈ -> U+2832 ⠲` | 407 | | 6 | `U+2826 ⠦ -> U+2800 ⠀` | 389 | | 7 | `U+2801 ⠁ -> U+281C ⠜` | 384 | -| 8 | `U+2834 ⠴ -> U+2830 ⠰` | 325 | +| 8 | `U+2834 ⠴ -> U+2830 ⠰` | 327 | | 9 | `U+2811 ⠑ -> U+282B ⠫` | 321 | -| 10 | `U+2810 ⠐ -> U+2832 ⠲` | 285 | +| 10 | `U+2810 ⠐ -> U+2832 ⠲` | 284 | | 11 | `U+2832 ⠲ -> U+2838 ⠸` | 275 | -| 12 | `U+2800 ⠀ -> U+2832 ⠲` | 225 | +| 12 | `U+2800 ⠀ -> U+2832 ⠲` | 224 | | 13 | `U+2810 ⠐ -> U+2802 ⠂` | 210 | -| 14 | `U+2817 ⠗ -> U+2820 ⠠` | 161 | -| 15 | `U+2820 ⠠ -> U+2830 ⠰` | 156 | +| 14 | `U+2817 ⠗ -> U+2820 ⠠` | 162 | +| 15 | `U+2820 ⠠ -> U+2830 ⠰` | 158 | | 16 | `U+283C ⠼ -> U+2800 ⠀` | 153 | | 17 | `U+2820 ⠠ -> U+2809 ⠉` | 152 | | 18 | `U+281B ⠛ -> U+2823 ⠣` | 144 | @@ -555,41 +555,41 @@ These are cross-cutting input-only structural cohorts, not new primary classes a | Cluster | Candidates | Exact | Mismatch | Conflicting-reference cases | |---|---:|---:|---:|---:| -| `allcaps_roman_run_beginning_with_pure_letter_shortform` | 4120 | 2239 | 1881 | 0 | +| `allcaps_roman_run_beginning_with_pure_letter_shortform` | 4120 | 2376 | 1744 | 0 | | `allcaps_roman_run_containing_ou` | 1816 | 55 | 1761 | 0 | -| `compact_numeric_ascii_letter_suffix` | 2975 | 1759 | 1216 | 0 | -| `decimal_point_between_ascii_digits` | 4546 | 3209 | 1337 | 0 | +| `compact_numeric_ascii_letter_suffix` | 2975 | 1758 | 1217 | 0 | +| `decimal_point_between_ascii_digits` | 4546 | 3213 | 1333 | 0 | | `korean_inline_parenthesized_single_arithmetic_operator` | 23 | 17 | 6 | 0 | -| `korean_prefixed_closed_allcaps_parenthetical` | 54492 | 43994 | 10498 | 0 | -| `korean_prefixed_closed_roman_annotation_rule_34_order` | 64382 | 51368 | 13014 | 0 | +| `korean_prefixed_closed_allcaps_parenthetical` | 54492 | 44130 | 10362 | 0 | +| `korean_prefixed_closed_roman_annotation_rule_34_order` | 64382 | 51505 | 12877 | 0 | | `mixed_roman_korean_word_before_uppercase_headword_expansion` | 10 | 0 | 10 | 0 | | `multi_character_allcaps_roman_runs_joined_by_middle_dot` | 97 | 0 | 97 | 0 | | `roman_run_immediately_before_attached_middle_dot_boundary` | 577 | 0 | 577 | 0 | | `rule69_ascii_unit_before_terminator_skipping_symbol` | 440 | 329 | 111 | 0 | | `single_capital_followed_by_parenthesized_digits` | 1361 | 0 | 1361 | 0 | -| `standalone_multi_character_uppercase_roman_word` | 62411 | 49088 | 13323 | 0 | +| `standalone_multi_character_uppercase_roman_word` | 62411 | 49225 | 13186 | 0 | | `tight_triangle_mark_immediately_before_korean` | 377 | 0 | 377 | 0 | | `uppercase_ascii_run_immediately_after_digit_in_roman_sequence` | 1896 | 756 | 1140 | 0 | -| `uppercase_ascii_run_immediately_after_hyphen_in_roman_sequence` | 952 | 31 | 921 | 0 | -| `uppercase_roman_headword_closed_multiword_parenthetical` | 175 | 15 | 160 | 0 | -| `uppercase_roman_run_followed_by_hyphen_digits` | 571 | 183 | 388 | 0 | +| `uppercase_ascii_run_immediately_after_hyphen_in_roman_sequence` | 952 | 32 | 920 | 0 | +| `uppercase_roman_headword_closed_multiword_parenthetical` | 175 | 14 | 161 | 0 | +| `uppercase_roman_run_followed_by_hyphen_digits` | 571 | 185 | 386 | 0 | ### `allcaps_roman_run_beginning_with_pure_letter_shortform` -Of the 4120 candidates, 1813 are the actual `pending_rule_review` subcluster. The other 2307 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 4120 candidates, 1675 are the actual `pending_rule_review` subcluster. The other 2445 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. -For this output-signature audit, 1881 mismatches were evaluable and 963 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. +For this output-signature audit, 1744 mismatches were evaluable and 820 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. Localized first-difference transitions: -- `U+2830 ⠰ -> U+2820 ⠠`: 962 -- `U+2820 ⠠ -> U+2830 ⠰`: 1 +- `U+2830 ⠰ -> U+2820 ⠠`: 778 +- `U+2820 ⠠ -> U+2830 ⠰`: 42 Mismatch primary-class distribution: - `comparison_method`: 11 -- `corpus_suspect`: 47 -- `pending_rule_review`: 1813 +- `corpus_suspect`: 48 +- `pending_rule_review`: 1675 - `unsupported_character_review`: 10 Representative `exact` samples: @@ -613,15 +613,15 @@ Representative `exact` samples: Representative `localized_mismatch` samples: -- `sentence_01.json` #310: 낸드플래시 반도체 세계 2위인 일본 키옥시아와 4위인 미국 웨스턴디지털(WD) 합병설에 반도체 업계가 촉각을 곤두 세우고 있다. - - expected: `⠕⠨⠕⠓⠞⠦⠄⠴⠰⠠⠠⠺⠙⠠⠴⠀⠚⠃⠘⠻⠠⠞⠝⠀` - - actual: `⠕⠨⠕⠓⠞⠦⠄⠴⠠⠠⠺⠙⠠⠴⠀⠚⠃⠘⠻⠠⠞⠝⠀⠘` - - first differing cell (zero-based): 70 +- `sentence_01.json` #312: 엔비디아가 대규모 언어 모델(LLM)과 음성 AI를 주제로 엔비디아 AI 개발자 밋업을 개최한다. 이번 개발자 밋업은 오는 17일 오후 2시부터 5시까지 온라인으로 진행된다. + - expected: `⠑⠥⠊⠝⠂⠦⠄⠴⠰⠠⠠⠇⠇⠍⠠⠴⠈⠧⠀⠪⠢⠠⠻⠀` + - actual: `⠑⠥⠊⠝⠂⠦⠄⠴⠠⠠⠇⠇⠍⠠⠴⠈⠧⠀⠪⠢⠠⠻⠀⠴` + - first differing cell (zero-based): 27 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_02.json` #99: 황승주 씨는 “기능성 소화불량증, 특히 PDS(식후불편감증후군) 복부팽만감 유형으로 고통 받는 환자들에게 적용가능한 안전하고 높은 치료효율의 한약소재를 개발해 국민건강에 이바지하고자 한다”고 말했다. - - expected: `⠀⠓⠪⠁⠚⠕⠀⠴⠰⠠⠠⠏⠙⠎⠦⠄⠠⠕⠁⠚⠍⠘⠯⠙` - - actual: `⠀⠓⠪⠁⠚⠕⠀⠴⠠⠠⠏⠙⠎⠦⠄⠠⠕⠁⠚⠍⠘⠯⠙⠡` - - first differing cell (zero-based): 45 +- `sentence_02.json` #68: 조성된 무장애나눔길은 통행이 많은 숲속의집~생태숲학습관 0.73km 구간에 평균 경사도 5% 이하 데크산책로와 황토포장길로 조성됐으며 전국 무장애나눔길 최초로 장애물 없는 생활환경(BF)인증을 추진하고 있다. + - expected: `⠚⠧⠒⠈⠻⠦⠄⠴⠠⠠⠃⠋⠠⠴⠟⠨⠪⠶⠮⠀⠰⠍⠨⠟` + - actual: `⠚⠧⠒⠈⠻⠦⠄⠴⠰⠠⠠⠃⠋⠠⠴⠟⠨⠪⠶⠮⠀⠰⠍⠨` + - first differing cell (zero-based): 190 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` - `sentence_03.json` #30: “챗GPT보다 가벼운 여러 대규모언어모델(LLM)이 출현해 오픈AI 독점 구도에 균열을 내고 있다. 한국어 특화 LLM으로 글로벌 경쟁에 대응하면서 세부 서비스(튜닝) 모델에서 차별성을 확보하는 전략이 중요하다.” - expected: `⠑⠥⠊⠝⠂⠦⠄⠴⠰⠠⠠⠇⠇⠍⠠⠴⠕⠀⠰⠯⠚⠡⠚⠗` @@ -641,10 +641,10 @@ Representative `mismatch` samples: - actual: `⠪⠓⠦⠄⠴⠠⠛⠤⠰⠠⠠⠌⠜⠠⠴⠀⠼⠃⠚⠃⠃⠐⠀⠝` - first differing cell (zero-based): 31 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_02.json` #99: 황승주 씨는 “기능성 소화불량증, 특히 PDS(식후불편감증후군) 복부팽만감 유형으로 고통 받는 환자들에게 적용가능한 안전하고 높은 치료효율의 한약소재를 개발해 국민건강에 이바지하고자 한다”고 말했다. - - expected: `⠀⠓⠪⠁⠚⠕⠀⠴⠰⠠⠠⠏⠙⠎⠦⠄⠠⠕⠁⠚⠍⠘⠯⠙` - - actual: `⠀⠓⠪⠁⠚⠕⠀⠴⠠⠠⠏⠙⠎⠦⠄⠠⠕⠁⠚⠍⠘⠯⠙⠡` - - first differing cell (zero-based): 45 +- `sentence_02.json` #68: 조성된 무장애나눔길은 통행이 많은 숲속의집~생태숲학습관 0.73km 구간에 평균 경사도 5% 이하 데크산책로와 황토포장길로 조성됐으며 전국 무장애나눔길 최초로 장애물 없는 생활환경(BF)인증을 추진하고 있다. + - expected: `⠚⠧⠒⠈⠻⠦⠄⠴⠠⠠⠃⠋⠠⠴⠟⠨⠪⠶⠮⠀⠰⠍⠨⠟` + - actual: `⠚⠧⠒⠈⠻⠦⠄⠴⠰⠠⠠⠃⠋⠠⠴⠟⠨⠪⠶⠮⠀⠰⠍⠨` + - first differing cell (zero-based): 190 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` - `sentence_03.json` #6: 지난해 9월 HMM과 파나시아가 선박용 탄소포집 시스템 공동연구에 대한 업무협약(MOU)을 체결한 뒤 7개월 만에 내놓은 연구성과다. - expected: `⠜⠁⠦⠄⠴⠠⠠⠍⠕⠥⠠⠴⠮⠀⠰⠝⠈⠳⠚⠒⠀⠊⠍⠗` @@ -659,20 +659,20 @@ Representative `mismatch` samples: ### `allcaps_roman_run_containing_ou` -Of the 1816 candidates, 1676 are the actual `pending_rule_review` subcluster. The other 140 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 1816 candidates, 1675 are the actual `pending_rule_review` subcluster. The other 141 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. -For this output-signature audit, 1761 mismatches were evaluable and 1307 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. +For this output-signature audit, 1761 mismatches were evaluable and 1308 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. Localized first-difference transitions: -- `U+2815 ⠕ -> U+2833 ⠳`: 1306 +- `U+2815 ⠕ -> U+2833 ⠳`: 1307 - `U+2810 ⠐ -> U+283D ⠽`: 1 Mismatch primary-class distribution: - `comparison_method`: 2 -- `corpus_suspect`: 8 -- `pending_rule_review`: 1676 +- `corpus_suspect`: 9 +- `pending_rule_review`: 1675 - `unsupported_character_review`: 75 Representative `exact` samples: @@ -720,13 +720,13 @@ Representative `mismatch` samples: ### `compact_numeric_ascii_letter_suffix` -Of the 2975 candidates, 1148 are the actual `pending_rule_review` subcluster. The other 1827 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 2975 candidates, 1149 are the actual `pending_rule_review` subcluster. The other 1826 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. -For this output-signature audit, 1216 mismatches were evaluable and 251 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. +For this output-signature audit, 1217 mismatches were evaluable and 252 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. Localized first-difference transitions: -- `U+283C ⠼ -> U+2800 ⠀`: 190 +- `U+283C ⠼ -> U+2800 ⠀`: 191 - `U+2800 ⠀ -> U+2832 ⠲`: 27 - `U+2810 ⠐ -> U+2832 ⠲`: 10 - `U+281E ⠞ -> U+2810 ⠐`: 7 @@ -736,7 +736,7 @@ Mismatch primary-class distribution: - `comparison_method`: 11 - `corpus_suspect`: 49 -- `pending_rule_review`: 1148 +- `pending_rule_review`: 1149 - `unsupported_character_review`: 8 Representative `exact` samples: @@ -806,9 +806,9 @@ Representative `mismatch` samples: ### `decimal_point_between_ascii_digits` -Of the 4546 candidates, 1281 are the actual `pending_rule_review` subcluster. The other 3265 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 4546 candidates, 1277 are the actual `pending_rule_review` subcluster. The other 3269 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. -For this output-signature audit, 1337 mismatches were evaluable and 262 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. +For this output-signature audit, 1333 mismatches were evaluable and 262 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. Localized first-difference transitions: @@ -822,7 +822,7 @@ Mismatch primary-class distribution: - `comparison_method`: 16 - `corpus_suspect`: 28 -- `pending_rule_review`: 1281 +- `pending_rule_review`: 1277 - `unsupported_character_review`: 12 Representative `exact` samples: @@ -938,13 +938,13 @@ Representative `mismatch` samples: ### `korean_prefixed_closed_allcaps_parenthetical` -Of the 54492 candidates, 9209 are the actual `pending_rule_review` subcluster. The other 45283 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 54492 candidates, 9072 are the actual `pending_rule_review` subcluster. The other 45420 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. Mismatch primary-class distribution: - `comparison_method`: 171 -- `corpus_suspect`: 981 -- `pending_rule_review`: 9209 +- `corpus_suspect`: 982 +- `pending_rule_review`: 9072 - `unsupported_character_review`: 137 Representative `exact` samples: @@ -991,20 +991,20 @@ Representative `mismatch` samples: ### `korean_prefixed_closed_roman_annotation_rule_34_order` -Of the 64382 candidates, 11537 are the actual `pending_rule_review` subcluster. The other 52845 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 64382 candidates, 11399 are the actual `pending_rule_review` subcluster. The other 52983 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. -For this output-signature audit, 13014 mismatches were evaluable and 1098 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. +For this output-signature audit, 12877 mismatches were evaluable and 1099 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. Localized first-difference transitions: -- `U+2834 ⠴ -> U+2826 ⠦`: 1097 +- `U+2834 ⠴ -> U+2826 ⠦`: 1098 - `U+2810 ⠐ -> U+2826 ⠦`: 1 Mismatch primary-class distribution: - `comparison_method`: 221 -- `corpus_suspect`: 1094 -- `pending_rule_review`: 11537 +- `corpus_suspect`: 1095 +- `pending_rule_review`: 11399 - `unsupported_character_review`: 162 Representative `exact` samples: @@ -1139,9 +1139,9 @@ For this output-signature audit, 577 mismatches were evaluable and 357 have thei Localized first-difference transitions: -- `U+2810 ⠐ -> U+2832 ⠲`: 341 +- `U+2810 ⠐ -> U+2832 ⠲`: 342 - `U+2801 ⠁ -> U+2820 ⠠`: 5 -- `U+2803 ⠃ -> U+2820 ⠠`: 4 +- `U+2803 ⠃ -> U+2820 ⠠`: 3 - `U+2801 ⠁ -> U+281C ⠜`: 2 - `U+2815 ⠕ -> U+2820 ⠠`: 2 @@ -1321,13 +1321,13 @@ Representative `mismatch` samples: ### `standalone_multi_character_uppercase_roman_word` -Of the 62411 candidates, 11878 are the actual `pending_rule_review` subcluster. The other 50533 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 62411 candidates, 11740 are the actual `pending_rule_review` subcluster. The other 50671 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. Mismatch primary-class distribution: - `comparison_method`: 205 -- `corpus_suspect`: 1081 -- `pending_rule_review`: 11878 +- `corpus_suspect`: 1082 +- `pending_rule_review`: 11740 - `unsupported_character_review`: 159 Representative `exact` samples: @@ -1498,19 +1498,19 @@ Representative `mismatch` samples: ### `uppercase_ascii_run_immediately_after_hyphen_in_roman_sequence` -Of the 952 candidates, 902 are the actual `pending_rule_review` subcluster. The other 50 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 952 candidates, 901 are the actual `pending_rule_review` subcluster. The other 51 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. -For this output-signature audit, 921 mismatches were evaluable and 315 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. +For this output-signature audit, 920 mismatches were evaluable and 314 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. Localized first-difference transitions: - `U+2820 ⠠ -> U+2830 ⠰`: 312 -- `U+2830 ⠰ -> U+2820 ⠠`: 3 +- `U+2830 ⠰ -> U+2820 ⠠`: 2 Mismatch primary-class distribution: - `corpus_suspect`: 10 -- `pending_rule_review`: 902 +- `pending_rule_review`: 901 - `unsupported_character_review`: 9 Representative `exact` samples: @@ -1580,13 +1580,13 @@ Representative `mismatch` samples: ### `uppercase_roman_headword_closed_multiword_parenthetical` -Of the 175 candidates, 149 are the actual `pending_rule_review` subcluster. The other 26 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 175 candidates, 150 are the actual `pending_rule_review` subcluster. The other 25 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. Mismatch primary-class distribution: - `comparison_method`: 1 - `corpus_suspect`: 4 -- `pending_rule_review`: 149 +- `pending_rule_review`: 150 - `unsupported_character_review`: 6 Representative `exact` samples: @@ -1633,9 +1633,9 @@ Representative `mismatch` samples: ### `uppercase_roman_run_followed_by_hyphen_digits` -Of the 571 candidates, 373 are the actual `pending_rule_review` subcluster. The other 198 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 571 candidates, 371 are the actual `pending_rule_review` subcluster. The other 200 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. -For this output-signature audit, 388 mismatches were evaluable and 260 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. +For this output-signature audit, 386 mismatches were evaluable and 260 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. Localized first-difference transitions: @@ -1645,7 +1645,7 @@ Localized first-difference transitions: Mismatch primary-class distribution: - `corpus_suspect`: 12 -- `pending_rule_review`: 373 +- `pending_rule_review`: 371 - `unsupported_character_review`: 3 Representative `exact` samples: @@ -1696,25 +1696,37 @@ These three cohorts are defined by both an input boundary and the sentence's act | Cohort | Candidates | Exact controls | Mismatch | Target localized | Reverse | |---|---:|---:|---:|---:|---:| -| `allcaps_roman_run_beginning_with_pure_letter_shortform` | 4120 | 2239 | 1881 | 962 | 1 | +| `allcaps_roman_run_beginning_with_pure_letter_shortform` | 4120 | 2376 | 1744 | 778 | 42 | | `uppercase_ascii_run_immediately_after_digit_in_roman_sequence` | 1896 | 756 | 1140 | 330 | 1 | -| `uppercase_ascii_run_immediately_after_hyphen_in_roman_sequence` | 952 | 31 | 921 | 312 | 3 | +| `uppercase_ascii_run_immediately_after_hyphen_in_roman_sequence` | 952 | 32 | 920 | 312 | 2 | ### All-caps shortform prefix at an attached Roman entry -UEB 2024 rule 5.7.2 requires grade-1 mode when a letters-sequence could be read as a shortform or as containing one. Rule 10.9.7 covers a standing-alone shortform-shaped sequence, rule 10.9.8 covers a sequence at the beginning of a longer word (the PDF example is `LLC`), and rule 5.8.1 places grade 1 before capitalization. The current standalone ASCII-token route already supplies that guard for complete shortform-shaped controls such as `AC`, `CD`, `IMM`, and `AG`; the attached Korean-word/parenthetical route enters directly at the capital marker and accounts for the localized `⠰ -> ⠠` signature. This is a routing distinction supported independently by the PDF, not an expected-output lookup. Longer runs such as `GDP` and `LLM` are admitted only when a pure-letter shortform occupies the beginning of the run; a shortform appearing later would require the distinct grade-1 word rule 10.9.9 and is outside this implementation candidate. +UEB 2024 rule 5.7.2 requires grade-1 mode when a letters-sequence could be read as a shortform or as containing one. Rule 10.9.7 covers a standing-alone shortform-shaped sequence, rule 10.9.8 covers a sequence at the beginning of a longer word (the PDF example is `LLC`), and rule 5.8.1 places grade 1 before capitalization. The current standalone ASCII-token route already supplies that guard for complete shortform-shaped controls such as `AC`, `CD`, `IMM`, and `AG`; the attached Korean-word/parenthetical route enters directly at the capital marker and accounts for part of the localized `⠰ -> ⠠` signature. This is a routing distinction supported independently by the PDF, not an expected-output lookup. The implemented boundary is only rule 10.9.7's complete pure-letter shortform. Longer runs such as `GDP`, `LLM`, and the PDF's rule-10.9.8 `LLC` example remain in the broad diagnostic cohort but are not generalized in Korean routing: that broader experiment regressed its exact controls. A shortform appearing later would require the still-distinct grade-1 word rule 10.9.9. + +Implementation-boundary experiment (all numbers are full-corpus exact matches, with the committed analyzer-only checkpoint as baseline): + +| Boundary | Exact / 83,528 | Change | Decision | +|---|---:|---:|---| +| Analyzer-only baseline | 66,546 | — | control | +| Prefix guard extended through the uppercase token route | 65,264 | -1,282 | rejected | +| Same-token token route narrowed, rule-28 prefix retained | 65,355 | -1,191 | rejected | +| Uppercase token route restored, rule-28 prefix retained | 65,474 | -1,072 | rejected | +| Rule-28 complete shortform only | 66,683 | +137 | retained | + +At the retained boundary the broad cohort moves from 2,239 exact / 1,881 mismatch / 962 target-localized / 1 reverse to 2,376 exact / 1,744 mismatch / 778 target-localized / 42 reverse. These figures do not turn the remaining longer-prefix members into an engine rule; they preserve the failed broader trials as evidence that input shape alone is unsafe. Same-surface controls demonstrate why primary classes must not be changed by cohort membership: | Surface | Candidates | Exact | Mismatch | Target-localized | |---|---:|---:|---:|---:| -| `AC` | 159 | 67 | 92 | 86 | +| `AC` | 159 | 85 | 74 | 66 | | `LLM` | 176 | 1 | 175 | 147 | -| `CD` | 65 | 3 | 62 | 36 | +| `CD` | 65 | 26 | 39 | 12 | | `IMM` | 27 | 3 | 24 | 22 | -| `AG` | 18 | 2 | 16 | 10 | +| `AG` | 18 | 9 | 9 | 0 | | `GDP` | 359 | 0 | 359 | 350 | -| `WD` | 10 | 0 | 10 | 10 | +| `WD` | 10 | 10 | 0 | 0 | - `AC` exact control: `sentence_01.json` #1122 — 손흥민(토트넘)이 선발 출전했지만 소속팀 패배를 지켜봤다. 토트넘은 15일(이하 한국시간) 이탈리아 밀라노에 있는 산시로에서 AC 밀란(이탈리아)과 유럽축구연맹(UEFA) 주최 2022-2023시즌 챔피언스리그(UCL) 16강 1차전 원정 경기를 치렀다. - `AC` localized mismatch: `sentence_01.json` #1707 — 토트넘은 9일(한국시간) 영국 런던의 토트넘 홋스퍼 스타디움에서 열린 2022-2023 유럽축구연맹(UEFA) 챔피언스리그(UCL) 16강 2차전에서 AC밀란(이탈리아)과 득점 없이 0-0으로 비겼다. @@ -1722,7 +1734,7 @@ Same-surface controls demonstrate why primary classes must not be changed by coh - `LLM` exact control: `sentence_03.json` #19586 — ‘미디어그룹사람과숲’은 한국정보화진흥원(NIA)과 업스테이지가 주최하는 한국어 ‘초거대 언어 모델’ 리더보드 ‘Open Ko-LLM’에서 1위를 차지했다고 11월13일 밝혔다. - `LLM` localized mismatch: `sentence_01.json` #312 — 엔비디아가 대규모 언어 모델(LLM)과 음성 AI를 주제로 엔비디아 AI 개발자 밋업을 개최한다. 이번 개발자 밋업은 오는 17일 오후 2시부터 5시까지 온라인으로 진행된다. -- `CD` exact control: `sentence_02.json` #7087 — 여기에 이 국제표준 개정안의 문서를 오는 2024년 1월까지 CD 단계 문서로 개발하고, 2024년 내 국제표준 문서 출판 단계(IS)로 개발하기로 하는 합의점을 이끌어냈다. +- `CD` exact control: `sentence_01.json` #4788 — 국제표준 제정절차는 신규작업표준안(NP) → 작업반 초안(WD) → 위원회안(CD) → 국제표준안(DIS) → 최종국제표준안(FDIS) → 국제표준(IS) 제정 순으로 진행된다. - `CD` localized mismatch: `sentence_01.json` #11231 — 이날 고아인은 PT 성공률이 가장 높은 CD(팀장)라는 별명처럼 여유로운 태도로 정재훈(이기우 분) 대표의 게임회사 광고 PT를 마쳤다. ### Uppercase immediately after a digit @@ -1756,18 +1768,18 @@ The tight-triangle cohort is not an implementation premise. Hangeul rule 49 assi Corpus contradictions remain a separate gate: identical inputs with conflicting references are classified as `corpus_suspect` before these cohorts are recorded and would appear explicitly in each mismatch primary-class distribution. Their absence does not prove a reference correct; it only means that this deterministic contradiction test did not fire. -Current uppercase-Roman hyphen-digits measurement: 571 candidates, 183 exact controls, 388 mismatches, 373 members in the actual `pending_rule_review` subcluster, and 260/388 evaluable mismatches whose first difference is inside the target run plus its entry boundary. It remains distinct from parenthesized digits and headword expansions; no engine change is inferred. +Current uppercase-Roman hyphen-digits measurement: 571 candidates, 185 exact controls, 386 mismatches, 371 members in the actual `pending_rule_review` subcluster, and 260/386 evaluable mismatches whose first difference is inside the target run plus its entry boundary. It remains distinct from parenthesized digits and headword expansions; no engine change is inferred. Current single-capital parenthesized-digits measurement: 1361 candidates, 0 exact controls, 1361 mismatches, 1357 members in the actual `pending_rule_review` subcluster, and 1342/1361 evaluable mismatches whose first difference is inside the target run plus its entry boundary. No engine change is inferred from the ambiguous prose/function surface form. Current mixed Roman+Korean boundary before uppercase headword-expansion measurement: 10 candidates, 0 exact controls, 10 mismatches, 10 members in the actual `pending_rule_review` subcluster, and 8/10 evaluable mismatches whose first difference is localized to the later headword's entry boundary/output. The detector cannot be satisfied by the earlier Roman entry. No HCA-shaped engine routing rule is introduced. -Current compact numeric+ASCII-suffix measurement: 2975 candidates, 1759 exact controls, 1216 mismatches, 1148 members in the actual `pending_rule_review` subcluster, and 251/1216 evaluable mismatches whose first difference is inside the complete current-engine output signature or its immediate entry boundary. Rule 40 requires the numeric indicator and rule 69 requires Roman indicators around a Roman-written unit, but the input shape alone cannot prove that every ASCII suffix is a unit. +Current compact numeric+ASCII-suffix measurement: 2975 candidates, 1758 exact controls, 1217 mismatches, 1149 members in the actual `pending_rule_review` subcluster, and 252/1217 evaluable mismatches whose first difference is inside the complete current-engine output signature or its immediate entry boundary. Rule 40 requires the numeric indicator and rule 69 requires Roman indicators around a Roman-written unit, but the input shape alone cannot prove that every ASCII suffix is a unit. | ASCII suffix | Candidates | Exact | Mismatch | Localized first diff | |---|---:|---:|---:|---:| | `m` | 364 | 255 | 109 | 22 | -| `km` | 308 | 225 | 83 | 3 | +| `km` | 308 | 224 | 84 | 3 | | `G` | 237 | 160 | 77 | 3 | | `kg` | 209 | 172 | 37 | 6 | | `D` | 167 | 110 | 57 | 5 | @@ -1784,7 +1796,7 @@ Current compact numeric+ASCII-suffix measurement: 2975 candidates, 1759 exact co | `S` | 45 | 5 | 40 | 0 | | `GW` | 44 | 35 | 9 | 6 | | `X` | 39 | 12 | 27 | 2 | -| `bp` | 39 | 3 | 36 | 17 | +| `bp` | 39 | 3 | 36 | 18 | | `K` | 38 | 10 | 28 | 1 | | `MW` | 37 | 35 | 2 | 0 | | `TURN` | 33 | 16 | 17 | 0 | @@ -1797,16 +1809,16 @@ Entry-boundary pre-fix baseline: 2,975 candidates, 1,649 exact controls, 1,326 m Current rule-69 ASCII-unit punctuation-boundary measurement: 440 candidates, 329 exact controls, 111 mismatches, 106 members in the actual `pending_rule_review` subcluster, and 10/111 evaluable mismatches whose first difference is localized to the unit-plus-punctuation output signature. PDF rule 69 requires a Roman terminator after a Roman-written unit in the ordinary case, while rules 33/34 omit it at the listed punctuation or enclosing-mark boundary; the rule 46 PDF example `체중(kg)` is the minimal parenthesized-unit control. Membership is restricted to rule-69 spellings already supported by the engine and does not infer unit semantics for arbitrary ASCII suffixes. Analyzer pre-fix baseline: 440 candidates, 0 exact controls, 440 mismatches, 435 pending members, and 9 signature-local first differences. After the generalized rule-33/34 boundary override and the matching non-math routing guard, the same cohort has 325 exact controls and 115 mismatches. Corpus-wide exact matches moved from 66,039 to 66,436 (+397); the additional gains are applications of the same boundary rule outside this strict ASCII detector, including compatibility-unit forms. The complete standard suite remains 5,141/5,141. -Current decimal-point measurement: 4546 candidates, 3209 exact controls, 1337 mismatches, 1281 members in the actual `pending_rule_review` subcluster, and 262/1337 evaluable mismatches whose first difference is inside the complete decimal-containing word's current-engine signature. Hangeul rules 43 and 48 keep an ASCII point between digits in the numeric sequence and encode it as the decimal point; rules 35 and 69 supply controls for adjacent Roman-number chains and Roman units. This is an implementation-candidate audit, not permission to specialize on a corpus reference. +Current decimal-point measurement: 4546 candidates, 3213 exact controls, 1333 mismatches, 1277 members in the actual `pending_rule_review` subcluster, and 262/1333 evaluable mismatches whose first difference is inside the complete decimal-containing word's current-engine signature. Hangeul rules 43 and 48 keep an ASCII point between digits in the numeric sequence and encode it as the decimal point; rules 35 and 69 supply controls for adjacent Roman-number chains and Roman units. This is an implementation-candidate audit, not permission to specialize on a corpus reference. Analyzer checkpoint `ed20f99` recorded the pre-fix baseline as 4,546 candidates, 2,584 exact controls, 1,962 mismatches, 1,905 pending members, and 875 localized first differences. Its dominant localized transition was reference decimal point `U+2832 ⠲` versus current Roman indicator `U+2834 ⠴` in 647 cases. After the generalized rule-43/48 guard, that transition is absent: 525 cases become exact and the other corrected prefixes expose later independent mismatches. -Current all-caps `OU` measurement: 1816 candidates, 55 exact controls, 1761 mismatches, 1676 members in the actual `pending_rule_review` subcluster, and 1307/1761 evaluable mismatches whose first difference is inside the current-engine output signature for the detected run. This is a pronunciation-sensitive UEB review cohort, not an engine routing rule. +Current all-caps `OU` measurement: 1816 candidates, 55 exact controls, 1761 mismatches, 1675 members in the actual `pending_rule_review` subcluster, and 1308/1761 evaluable mismatches whose first difference is inside the current-engine output signature for the detected run. This is a pronunciation-sensitive UEB review cohort, not an engine routing rule. -Current standalone-uppercase measurement: 62411 candidates, 49088 exact controls, 13323 mismatches, and 11878 members in the actual `pending_rule_review` subcluster. Its high frequency does not make it causal: the same input shape is exact in many cases, and a sentence containing the shape may first differ at another Roman, numeric, or punctuation structure. No engine change is inferred from this cohort. +Current standalone-uppercase measurement: 62411 candidates, 49225 exact controls, 13186 mismatches, and 11740 members in the actual `pending_rule_review` subcluster. Its high frequency does not make it causal: the same input shape is exact in many cases, and a sentence containing the shape may first differ at another Roman, numeric, or punctuation structure. No engine change is inferred from this cohort. -Current Korean-prefixed all-caps parenthetical measurement: 54492 candidates, 43994 exact controls, 10498 mismatches, and 9209 members in the actual `pending_rule_review` subcluster. This is a semantic-collision audit, not an engine routing rule; no implementation change is inferred from its reference outputs. +Current Korean-prefixed all-caps parenthetical measurement: 54492 candidates, 44130 exact controls, 10362 mismatches, and 9072 members in the actual `pending_rule_review` subcluster. This is a semantic-collision audit, not an engine routing rule; no implementation change is inferred from its reference outputs. -Current rule-34 opening-order measurement: 64382 structural candidates, 51368 exact controls, 13014 mismatches, and 1098/13014 evaluable mismatches whose first difference is inside the current engine's Korean opening-parenthesis cells. Only the 1097 localized first-cell transitions have the reference/current order `⠴` versus `⠦`. After requiring the complete reference `⠴⠐⠣` versus current/PDF `⠦⠄⠴` three-cell signature and preserving higher-priority comparison classifications, 1094 are classified as `corpus_suspect`; mere coexistence with a Korean-prefixed Roman annotation does not change a primary class. +Current rule-34 opening-order measurement: 64382 structural candidates, 51505 exact controls, 12877 mismatches, and 1099/12877 evaluable mismatches whose first difference is inside the current engine's Korean opening-parenthesis cells. Only the 1098 localized first-cell transitions have the reference/current order `⠴` versus `⠦`. After requiring the complete reference `⠴⠐⠣` versus current/PDF `⠦⠄⠴` three-cell signature and preserving higher-priority comparison classifications, 1095 are classified as `corpus_suspect`; mere coexistence with a Korean-prefixed Roman annotation does not change a primary class. Current all-caps Roman middle-dot measurement: 97 candidates, 0 exact controls, 97 mismatches, and 96 members in the actual `pending_rule_review` subcluster. This cross-cutting cohort preserves every primary class and is not an engine routing rule. @@ -1867,19 +1879,19 @@ Families are diagnostics, not automatic normalization permissions. Rules 68/69 c | Shard | Exact | Total | Accuracy | |---|---:|---:|---:| -| `sentence_01.json` | 19944 | 25000 | 79.78% | -| `sentence_02.json` | 19463 | 25000 | 77.85% | -| `sentence_03.json` | 20072 | 25000 | 80.29% | -| `sentence_04.json` | 7067 | 8528 | 82.87% | +| `sentence_01.json` | 19994 | 25000 | 79.98% | +| `sentence_02.json` | 19494 | 25000 | 77.98% | +| `sentence_03.json` | 20118 | 25000 | 80.47% | +| `sentence_04.json` | 7077 | 8528 | 82.99% | ## Overlapping mismatch traits | Trait | Count | |---|---:| -| `contains_ascii_digits` | 13331 | -| `contains_ascii_letters` | 16796 | -| `contains_delimiter_or_quote` | 16982 | -| `input_not_nfkc` | 727 | +| `contains_ascii_digits` | 13233 | +| `contains_ascii_letters` | 16659 | +| `contains_delimiter_or_quote` | 16845 | +| `input_not_nfkc` | 724 | ## Samples diff --git a/libs/braillify/examples/nikl_corpus_analyze.rs b/libs/braillify/examples/nikl_corpus_analyze.rs index b4f2fabd..6b0367d7 100644 --- a/libs/braillify/examples/nikl_corpus_analyze.rs +++ b/libs/braillify/examples/nikl_corpus_analyze.rs @@ -2832,12 +2832,28 @@ fn markdown(report: &AnalysisReport) -> String { places grade 1 before capitalization. The current standalone ASCII-token \ route already supplies that guard for complete shortform-shaped controls \ such as `AC`, `CD`, `IMM`, and `AG`; the attached Korean-word/parenthetical \ - route enters directly at the capital marker and accounts for the localized \ - `⠰ -> ⠠` signature. This is a routing distinction supported independently \ - by the PDF, not an expected-output lookup. Longer runs such as `GDP` and \ - `LLM` are admitted only when a pure-letter shortform occupies the beginning \ - of the run; a shortform appearing later would require the distinct grade-1 \ - word rule 10.9.9 and is outside this implementation candidate.\n\n", + route enters directly at the capital marker and accounts for part of the \ + localized `⠰ -> ⠠` signature. This is a routing distinction supported \ + independently by the PDF, not an expected-output lookup. The implemented \ + boundary is only rule 10.9.7's complete pure-letter shortform. Longer runs \ + such as `GDP`, `LLM`, and the PDF's rule-10.9.8 `LLC` example remain in the \ + broad diagnostic cohort but are not generalized in Korean routing: that \ + broader experiment regressed its exact controls. A shortform appearing \ + later would require the still-distinct grade-1 word rule 10.9.9.\n\n\ + Implementation-boundary experiment (all numbers are full-corpus exact \ + matches, with the committed analyzer-only checkpoint as baseline):\n\n\ + | Boundary | Exact / 83,528 | Change | Decision |\n\ + |---|---:|---:|---|\n\ + | Analyzer-only baseline | 66,546 | — | control |\n\ + | Prefix guard extended through the uppercase token route | 65,264 | -1,282 | rejected |\n\ + | Same-token token route narrowed, rule-28 prefix retained | 65,355 | -1,191 | rejected |\n\ + | Uppercase token route restored, rule-28 prefix retained | 65,474 | -1,072 | rejected |\n\ + | Rule-28 complete shortform only | 66,683 | +137 | retained |\n\n\ + At the retained boundary the broad cohort moves from 2,239 exact / 1,881 \ + mismatch / 962 target-localized / 1 reverse to 2,376 exact / 1,744 \ + mismatch / 778 target-localized / 42 reverse. These figures do not turn \ + the remaining longer-prefix members into an engine rule; they preserve \ + the failed broader trials as evidence that input shape alone is unsafe.\n\n", ); text.push_str( "Same-surface controls demonstrate why primary classes must not be changed \ diff --git a/libs/braillify/src/rules/english_ueb/engine.rs b/libs/braillify/src/rules/english_ueb/engine.rs index cb218366..f380515f 100644 --- a/libs/braillify/src/rules/english_ueb/engine.rs +++ b/libs/braillify/src/rules/english_ueb/engine.rs @@ -205,11 +205,15 @@ impl EnglishUebEngine { &self, chars: &[char], suppress_caps: bool, + prepend_grade1_indicator: bool, standing_alone: bool, word_initial: bool, digit_adjacent: bool, ) -> Option> { let mut out = Vec::new(); + if prepend_grade1_indicator { + out.push(GRADE1); + } let lower: Vec = chars.iter().flat_map(|ch| ch.to_lowercase()).collect(); let lower_word: String = lower.iter().collect(); if !standing_alone && super::rule_10_5::wordsign(&lower_word).is_some() { diff --git a/libs/braillify/src/rules/english_ueb/span.rs b/libs/braillify/src/rules/english_ueb/span.rs index ceb52737..eccfd034 100644 --- a/libs/braillify/src/rules/english_ueb/span.rs +++ b/libs/braillify/src/rules/english_ueb/span.rs @@ -39,6 +39,7 @@ pub(crate) struct KoreanSpanUnit { pub(crate) fn encode_korean_word( chars: &[char], suppress_caps: bool, + prepend_grade1_indicator: bool, standing_alone: bool, word_initial: bool, digit_adjacent: bool, @@ -46,6 +47,7 @@ pub(crate) fn encode_korean_word( KOREAN_WORD_ENGINE.encode_korean_word( chars, suppress_caps, + prepend_grade1_indicator, standing_alone, word_initial, digit_adjacent, diff --git a/libs/braillify/src/rules/korean/rule_28.rs b/libs/braillify/src/rules/korean/rule_28.rs index 9cdfebd5..5ec1919f 100644 --- a/libs/braillify/src/rules/korean/rule_28.rs +++ b/libs/braillify/src/rules/korean/rule_28.rs @@ -10,6 +10,7 @@ use crate::char_struct::CharType; use crate::rules::RuleMeta; use crate::rules::context::RuleContext; +use crate::rules::english_shortform::requires_grade1_indicator; use crate::rules::english_ueb::korean_context::KoreanPrefixInput; use crate::rules::english_ueb::span::{encode_korean_unit, encode_korean_word}; use crate::rules::traits::{BrailleRule, Phase, RuleResult}; @@ -148,9 +149,27 @@ impl BrailleRule for Rule28 { .word_chars .get(run_end) .is_some_and(|ch| ch.is_ascii_digit()); + // UEB 5.7.2, 5.8.1, and 10.9.7: when an attached Roman entry is a + // complete pure-letter shortform, grade 1 precedes its capitalization + // marker. Standalone ASCII tokens have already been handled by + // `UppercasePassageRule`. Digit and hyphen continuations retain their + // independent rule-35/state-machine paths. + let follows_hyphen = ctx + .index + .checked_sub(1) + .and_then(|index| ctx.word_chars.get(index)) + .is_some_and(|ch| *ch == '-'); + let uppercase_run = run.iter().collect::(); + let prepend_grade1_indicator = !caps_already_emitted + && word_initial + && !digit_adjacent + && !follows_hyphen + && run.iter().all(|ch| ch.is_ascii_uppercase()) + && requires_grade1_indicator(&uppercase_run); if let Some(cells) = encode_korean_word( run, caps_already_emitted, + prepend_grade1_indicator, standalone_wordsign, word_initial, digit_adjacent, @@ -267,6 +286,20 @@ mod tests { assert_eq!(encode_with_options(input, &options).unwrap(), expected); } + /// UEB 5.7.2/5.8.1/10.9.7 complete-shortform handling through the complete + /// Korean encoder. Every Roman surface comes directly from the PDF examples + /// (`CD`, `ALT`, `NEC`); the Korean wrapper exercises only rule 28/29/34 routing. + #[rstest::rstest] + #[case::standing_alone_could("가(CD)", "⠫⠦⠄⠴⠰⠠⠠⠉⠙⠠⠴")] + #[case::alt_example("가(ALT)", "⠫⠦⠄⠴⠰⠠⠠⠁⠇⠞⠠⠴")] + #[case::nec_example("가(NEC)", "⠫⠦⠄⠴⠰⠠⠠⠝⠑⠉⠠⠴")] + fn attached_allcaps_complete_shortform_uses_grade1( + #[case] input: &str, + #[case] expected: &str, + ) { + assert_eq!(crate::encode_to_unicode(input).unwrap(), expected); + } + #[test] fn english_dominant_wrap_resumes_ueb_wordsigns_after_korean_span() { let mut owned = crate::test_helpers::CtxOwned::for_text("in", true); diff --git a/libs/braillify/src/rules/korean/rule_69.rs b/libs/braillify/src/rules/korean/rule_69.rs index fdbe6ac7..2b3a4044 100644 --- a/libs/braillify/src/rules/korean/rule_69.rs +++ b/libs/braillify/src/rules/korean/rule_69.rs @@ -90,7 +90,7 @@ fn compatibility_unit_decomposition(c: char) -> Option> { /// and a lower groupsign cannot consume the whole entry run (`in` is spelled /// `i`-`n`, while the same `in` may contract inside `min`). fn encode_rule_69_unit_letters(letters: &[char]) -> Result, String> { - encode_korean_word(letters, false, false, true, false).ok_or_else(|| { + encode_korean_word(letters, false, false, false, true, false).ok_or_else(|| { format!( "cannot encode rule 69 Roman unit letters: {}", letters.iter().collect::() From 7585622d9a98dc1f1eadb6b208cbb9d0cf6a739b Mon Sep 17 00:00:00 2001 From: devfive Date: Tue, 1 Sep 2026 11:06:26 +0900 Subject: [PATCH 27/77] Preserve numeric grade 1 letter classes Apply UEB 6.5.2 after digits: retain grade 1 only for lowercase a-j, while capitals and lowercase k-z rely on their own disambiguation. Keep letter-prefixed numeric chains on the Roman identifier route instead of treating them as compact Rule 69 units. Verified: standard custom summary 5141/5141, analyzer 156/156, clippy, fmt, deterministic report/JSON hashes, NIKL 67012/83528. --- docs/corpus-analysis/NIKL_2025_V1.md | 354 +++++++++--------- .../braillify/examples/nikl_corpus_analyze.rs | 28 +- libs/braillify/src/rules/emit.rs | 55 ++- libs/braillify/src/rules/korean/rule_69.rs | 22 +- 4 files changed, 262 insertions(+), 197 deletions(-) diff --git a/docs/corpus-analysis/NIKL_2025_V1.md b/docs/corpus-analysis/NIKL_2025_V1.md index 65984ee8..1da883e5 100644 --- a/docs/corpus-analysis/NIKL_2025_V1.md +++ b/docs/corpus-analysis/NIKL_2025_V1.md @@ -7,9 +7,9 @@ | Metric | Count | |---|---:| | Total | 83528 | -| Exact | 66683 | -| Mismatch | 16845 | -| Exact accuracy | 79.83% | +| Exact | 67012 | +| Mismatch | 16516 | +| Exact accuracy | 80.23% | | Duplicate records | 0 | | Inputs with conflicting references | 0 | @@ -19,21 +19,21 @@ Primary classes are evidence gates, not permissions to change the engine. `imple | Primary class | Count | |---|---:| -| `comparison_method` | 284 | +| `comparison_method` | 286 | | `corpus_suspect` | 1095 | -| `exact` | 66683 | -| `pending_rule_review` | 15281 | -| `unsupported_character_review` | 185 | +| `exact` | 67012 | +| `pending_rule_review` | 14952 | +| `unsupported_character_review` | 183 | | Reproducible reason | Count | |---|---:| -| `exact` | 66683 | -| `foreign_text_rule_review` | 15106 | -| `nfkc_input_equivalent` | 284 | +| `exact` | 67012 | +| `foreign_text_rule_review` | 14777 | +| `nfkc_input_equivalent` | 286 | | `number_rule_review` | 167 | | `punctuation_rule_review` | 8 | | `rule34_roman_indicator_before_opening_parenthesis` | 1095 | -| `unsupported_character_review` | 185 | +| `unsupported_character_review` | 183 | ## Pending first-difference cell transitions @@ -41,26 +41,26 @@ This ranking is a diagnostic selector, not an implementation rule. It counts onl | Rank | Expected → actual first cell | Cases | |---:|---|---:| -| 1 | `U+2834 ⠴ -> U+2800 ⠀` | 2142 | -| 2 | `U+2815 ⠕ -> U+2833 ⠳` | 1312 | -| 3 | `U+2820 ⠠ -> U+2830 ⠰` | 843 | -| 4 | `U+2830 ⠰ -> U+2820 ⠠` | 834 | -| 5 | `U+2810 ⠐ -> U+2832 ⠲` | 642 | -| 6 | `U+2820 ⠠ -> U+2834 ⠴` | 466 | -| 7 | `U+2826 ⠦ -> U+2834 ⠴` | 453 | -| 8 | `U+280E ⠎ -> U+280C ⠌` | 444 | -| 9 | `U+2826 ⠦ -> U+2800 ⠀` | 424 | -| 10 | `U+2808 ⠈ -> U+2832 ⠲` | 409 | -| 11 | `U+2801 ⠁ -> U+281C ⠜` | 386 | +| 1 | `U+2834 ⠴ -> U+2800 ⠀` | 2145 | +| 2 | `U+2815 ⠕ -> U+2833 ⠳` | 1314 | +| 3 | `U+2830 ⠰ -> U+2820 ⠠` | 837 | +| 4 | `U+2810 ⠐ -> U+2832 ⠲` | 642 | +| 5 | `U+2820 ⠠ -> U+2834 ⠴` | 467 | +| 6 | `U+2826 ⠦ -> U+2834 ⠴` | 457 | +| 7 | `U+280E ⠎ -> U+280C ⠌` | 444 | +| 8 | `U+2826 ⠦ -> U+2800 ⠀` | 428 | +| 9 | `U+2820 ⠠ -> U+2830 ⠰` | 421 | +| 10 | `U+2808 ⠈ -> U+2832 ⠲` | 410 | +| 11 | `U+2801 ⠁ -> U+281C ⠜` | 393 | | 12 | `U+2800 ⠀ -> U+2807 ⠇` | 360 | | 13 | `U+283C ⠼ -> U+2800 ⠀` | 358 | | 14 | `U+2834 ⠴ -> U+2830 ⠰` | 327 | | 15 | `U+2811 ⠑ -> U+282B ⠫` | 322 | | 16 | `U+2832 ⠲ -> U+2838 ⠸` | 275 | -| 17 | `U+2800 ⠀ -> U+2832 ⠲` | 253 | +| 17 | `U+2800 ⠀ -> U+2832 ⠲` | 254 | | 18 | `U+2810 ⠐ -> U+2802 ⠂` | 210 | -| 19 | `U+2817 ⠗ -> U+2820 ⠠` | 162 | -| 20 | `U+2826 ⠦ -> U+2810 ⠐` | 155 | +| 19 | `U+2817 ⠗ -> U+2820 ⠠` | 163 | +| 20 | `U+2820 ⠠ -> U+2809 ⠉` | 155 | ### `U+2834 ⠴ -> U+2800 ⠀` @@ -108,29 +108,6 @@ This ranking is a diagnostic selector, not an implementation rule. It counts onl - first differing cell (zero-based): 185 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -### `U+2820 ⠠ -> U+2830 ⠰` - -- `sentence_01.json` #25: 세븐일레븐은 올해 말 정식 버전 오픈을 목표로 하며, 이를 통해 주력상품을 구매하고 배송까지 받을 수 있는 차세대 오프라인을 위한 온라인(O4O) 시스템을 구축해 나갈 계획이다. - - expected: `⠟⠦⠄⠴⠠⠕⠼⠙⠠⠕⠠⠴⠀⠠⠕⠠⠪⠓⠝⠢⠮⠀⠈⠍` - - actual: `⠟⠦⠄⠴⠠⠕⠼⠙⠰⠠⠕⠠⠴⠀⠠⠕⠠⠪⠓⠝⠢⠮⠀⠈` - - first differing cell (zero-based): 129 - - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_02.json` #68: 조성된 무장애나눔길은 통행이 많은 숲속의집~생태숲학습관 0.73km 구간에 평균 경사도 5% 이하 데크산책로와 황토포장길로 조성됐으며 전국 무장애나눔길 최초로 장애물 없는 생활환경(BF)인증을 추진하고 있다. - - expected: `⠚⠧⠒⠈⠻⠦⠄⠴⠠⠠⠃⠋⠠⠴⠟⠨⠪⠶⠮⠀⠰⠍⠨⠟` - - actual: `⠚⠧⠒⠈⠻⠦⠄⠴⠰⠠⠠⠃⠋⠠⠴⠟⠨⠪⠶⠮⠀⠰⠍⠨` - - first differing cell (zero-based): 190 - - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_03.json` #47: 이수화학이 미국 과학 기술 엔지니어링 회사 KBR과 전고체 배터리 소재 황화리튬(Li2S)의 상업공정 공동 개발에 나선다. 양사는 이를 위해 공동개발 계약을 체결했다고 20일 밝혔다. - - expected: `⠦⠄⠴⠠⠇⠊⠼⠃⠠⠎⠠⠴⠺⠀⠇⠶⠎⠃⠈⠿⠨⠻⠀⠈` - - actual: `⠦⠄⠴⠠⠇⠊⠼⠃⠰⠠⠎⠠⠴⠺⠀⠇⠶⠎⠃⠈⠿⠨⠻⠀` - - first differing cell (zero-based): 87 - - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_04.json` #1097: LG유플러스는 5세대 이동통신(5G)과 차량·사물통신(V2X)을 바탕으로 도시환경관리에 최적화된 자율주행 차량 관제시스템을 개발한 뒤 자율주행 기술을 실증할 도시인 ‘리빙랩’에서 4만㎞ 이상 서비스 실증에 나선다. - - expected: `⠟⠦⠄⠴⠠⠧⠼⠃⠠⠭⠠⠴⠮⠀⠘⠓⠶⠪⠐⠥⠀⠊⠥⠠` - - actual: `⠟⠦⠄⠴⠠⠧⠼⠃⠰⠠⠭⠠⠴⠮⠀⠘⠓⠶⠪⠐⠥⠀⠊⠥` - - first differing cell (zero-based): 63 - - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` - ### `U+2830 ⠰ -> U+2820 ⠠` - `sentence_01.json` #312: 엔비디아가 대규모 언어 모델(LLM)과 음성 AI를 주제로 엔비디아 AI 개발자 밋업을 개최한다. 이번 개발자 밋업은 오는 17일 오후 2시부터 5시까지 온라인으로 진행된다. @@ -269,6 +246,29 @@ This ranking is a diagnostic selector, not an implementation rule. It counts onl - first differing cell (zero-based): 228 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +### `U+2820 ⠠ -> U+2830 ⠰` + +- `sentence_01.json` #54: 에버소울은 출시 전부터 지스타(G-STAR) 2022, 에이지에프(AGF) 2022 등 굵직한 국내 게임 및 서브컬처 행사에 출품해 기대를 모았다. 12월 말에는 사전 예약자 150만 명을 모객했다. + - expected: `⠪⠓⠦⠄⠴⠠⠛⠤⠠⠠⠌⠜⠠⠴⠀⠼⠃⠚⠃⠃⠐⠀⠝⠕` + - actual: `⠪⠓⠦⠄⠴⠠⠛⠤⠰⠠⠠⠌⠜⠠⠴⠀⠼⠃⠚⠃⠃⠐⠀⠝` + - first differing cell (zero-based): 31 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_02.json` #68: 조성된 무장애나눔길은 통행이 많은 숲속의집~생태숲학습관 0.73km 구간에 평균 경사도 5% 이하 데크산책로와 황토포장길로 조성됐으며 전국 무장애나눔길 최초로 장애물 없는 생활환경(BF)인증을 추진하고 있다. + - expected: `⠚⠧⠒⠈⠻⠦⠄⠴⠠⠠⠃⠋⠠⠴⠟⠨⠪⠶⠮⠀⠰⠍⠨⠟` + - actual: `⠚⠧⠒⠈⠻⠦⠄⠴⠰⠠⠠⠃⠋⠠⠴⠟⠨⠪⠶⠮⠀⠰⠍⠨` + - first differing cell (zero-based): 190 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_03.json` #74: 아울러 제주도개발공사는 국내 생수업계에서는 처음으로 재활용 페트(CR-PET)를 적용한 화학적 재활용 페트 ‘제주삼다수 리본(RE:Born)’을 개발하는 등 소재혁신을 통한 친환경 라인업도 확대하고있다. + - expected: `⠦⠄⠴⠠⠠⠉⠗⠤⠠⠠⠏⠑⠞⠠⠴⠐⠮⠀⠨⠹⠬⠶⠚⠒` + - actual: `⠦⠄⠴⠠⠠⠉⠗⠤⠰⠠⠠⠏⠑⠞⠠⠴⠐⠮⠀⠨⠹⠬⠶⠚` + - first differing cell (zero-based): 70 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_04.json` #1164: 3일 서울시에 따르면 서울시와 국토교통부는 이러한 내용을 담은 ‘K-UAM(도심항공교통) 그랜드챌린지 실증운행계획’을 이달 중순 발표한다. UAM은 드론 택시처럼 하늘을 이용한 교통수단이다. + - expected: `⠵⠀⠠⠦⠴⠠⠅⠤⠠⠠⠥⠁⠍⠦⠄⠊⠥⠠⠕⠢⠚⠶⠈⠿` + - actual: `⠵⠀⠠⠦⠴⠠⠅⠤⠰⠠⠠⠥⠁⠍⠦⠄⠊⠥⠠⠕⠢⠚⠶⠈` + - first differing cell (zero-based): 62 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` + ### `U+2808 ⠈ -> U+2832 ⠲` - `sentence_01.json` #207: 우리금융지주가 약 7조원의 자본 여력을 등에 업고 인수·합병(M&A) 시장에 출격했다. 벤처캐피탈(VC) 인수를 시작으로 증권사와 보험사까지 인수한다는 계획이다. @@ -298,26 +298,26 @@ This ranking removes only cases whose first difference is inside an existing out | Rank | Expected → actual first cell | Residual cases | |---:|---|---:| -| 1 | `U+2834 ⠴ -> U+2800 ⠀` | 545 | -| 2 | `U+2820 ⠠ -> U+2834 ⠴` | 453 | -| 3 | `U+2826 ⠦ -> U+2834 ⠴` | 447 | +| 1 | `U+2834 ⠴ -> U+2800 ⠀` | 548 | +| 2 | `U+2820 ⠠ -> U+2834 ⠴` | 454 | +| 3 | `U+2826 ⠦ -> U+2834 ⠴` | 451 | | 4 | `U+280E ⠎ -> U+280C ⠌` | 444 | -| 5 | `U+2808 ⠈ -> U+2832 ⠲` | 407 | -| 6 | `U+2826 ⠦ -> U+2800 ⠀` | 389 | -| 7 | `U+2801 ⠁ -> U+281C ⠜` | 384 | +| 5 | `U+2808 ⠈ -> U+2832 ⠲` | 408 | +| 6 | `U+2826 ⠦ -> U+2800 ⠀` | 392 | +| 7 | `U+2801 ⠁ -> U+281C ⠜` | 391 | | 8 | `U+2834 ⠴ -> U+2830 ⠰` | 327 | | 9 | `U+2811 ⠑ -> U+282B ⠫` | 321 | | 10 | `U+2810 ⠐ -> U+2832 ⠲` | 284 | | 11 | `U+2832 ⠲ -> U+2838 ⠸` | 275 | -| 12 | `U+2800 ⠀ -> U+2832 ⠲` | 224 | +| 12 | `U+2800 ⠀ -> U+2832 ⠲` | 225 | | 13 | `U+2810 ⠐ -> U+2802 ⠂` | 210 | -| 14 | `U+2817 ⠗ -> U+2820 ⠠` | 162 | -| 15 | `U+2820 ⠠ -> U+2830 ⠰` | 158 | +| 14 | `U+2817 ⠗ -> U+2820 ⠠` | 163 | +| 15 | `U+2820 ⠠ -> U+2809 ⠉` | 155 | | 16 | `U+283C ⠼ -> U+2800 ⠀` | 153 | -| 17 | `U+2820 ⠠ -> U+2809 ⠉` | 152 | -| 18 | `U+281B ⠛ -> U+2823 ⠣` | 144 | -| 19 | `U+2826 ⠦ -> U+2810 ⠐` | 139 | -| 20 | `U+2810 ⠐ -> U+2800 ⠀` | 129 | +| 17 | `U+281B ⠛ -> U+2823 ⠣` | 144 | +| 18 | `U+2826 ⠦ -> U+2810 ⠐` | 139 | +| 19 | `U+2810 ⠐ -> U+2800 ⠀` | 129 | +| 20 | `U+2811 ⠑ -> U+283B ⠻` | 100 | ### Residual `U+2834 ⠴ -> U+2800 ⠀` @@ -555,41 +555,41 @@ These are cross-cutting input-only structural cohorts, not new primary classes a | Cluster | Candidates | Exact | Mismatch | Conflicting-reference cases | |---|---:|---:|---:|---:| -| `allcaps_roman_run_beginning_with_pure_letter_shortform` | 4120 | 2376 | 1744 | 0 | +| `allcaps_roman_run_beginning_with_pure_letter_shortform` | 4120 | 2384 | 1736 | 0 | | `allcaps_roman_run_containing_ou` | 1816 | 55 | 1761 | 0 | -| `compact_numeric_ascii_letter_suffix` | 2975 | 1758 | 1217 | 0 | -| `decimal_point_between_ascii_digits` | 4546 | 3213 | 1333 | 0 | +| `compact_numeric_ascii_letter_suffix` | 2975 | 1840 | 1135 | 0 | +| `decimal_point_between_ascii_digits` | 4546 | 3253 | 1293 | 0 | | `korean_inline_parenthesized_single_arithmetic_operator` | 23 | 17 | 6 | 0 | -| `korean_prefixed_closed_allcaps_parenthetical` | 54492 | 44130 | 10362 | 0 | -| `korean_prefixed_closed_roman_annotation_rule_34_order` | 64382 | 51505 | 12877 | 0 | +| `korean_prefixed_closed_allcaps_parenthetical` | 54492 | 44220 | 10272 | 0 | +| `korean_prefixed_closed_roman_annotation_rule_34_order` | 64382 | 51811 | 12571 | 0 | | `mixed_roman_korean_word_before_uppercase_headword_expansion` | 10 | 0 | 10 | 0 | | `multi_character_allcaps_roman_runs_joined_by_middle_dot` | 97 | 0 | 97 | 0 | | `roman_run_immediately_before_attached_middle_dot_boundary` | 577 | 0 | 577 | 0 | -| `rule69_ascii_unit_before_terminator_skipping_symbol` | 440 | 329 | 111 | 0 | +| `rule69_ascii_unit_before_terminator_skipping_symbol` | 440 | 335 | 105 | 0 | | `single_capital_followed_by_parenthesized_digits` | 1361 | 0 | 1361 | 0 | -| `standalone_multi_character_uppercase_roman_word` | 62411 | 49225 | 13186 | 0 | +| `standalone_multi_character_uppercase_roman_word` | 62411 | 49394 | 13017 | 0 | | `tight_triangle_mark_immediately_before_korean` | 377 | 0 | 377 | 0 | -| `uppercase_ascii_run_immediately_after_digit_in_roman_sequence` | 1896 | 756 | 1140 | 0 | +| `uppercase_ascii_run_immediately_after_digit_in_roman_sequence` | 1896 | 1078 | 818 | 0 | | `uppercase_ascii_run_immediately_after_hyphen_in_roman_sequence` | 952 | 32 | 920 | 0 | | `uppercase_roman_headword_closed_multiword_parenthetical` | 175 | 14 | 161 | 0 | -| `uppercase_roman_run_followed_by_hyphen_digits` | 571 | 185 | 386 | 0 | +| `uppercase_roman_run_followed_by_hyphen_digits` | 571 | 186 | 385 | 0 | ### `allcaps_roman_run_beginning_with_pure_letter_shortform` -Of the 4120 candidates, 1675 are the actual `pending_rule_review` subcluster. The other 2445 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 4120 candidates, 1667 are the actual `pending_rule_review` subcluster. The other 2453 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. -For this output-signature audit, 1744 mismatches were evaluable and 820 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. +For this output-signature audit, 1736 mismatches were evaluable and 823 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. Localized first-difference transitions: -- `U+2830 ⠰ -> U+2820 ⠠`: 778 +- `U+2830 ⠰ -> U+2820 ⠠`: 781 - `U+2820 ⠠ -> U+2830 ⠰`: 42 Mismatch primary-class distribution: - `comparison_method`: 11 - `corpus_suspect`: 48 -- `pending_rule_review`: 1675 +- `pending_rule_review`: 1667 - `unsupported_character_review`: 10 Representative `exact` samples: @@ -661,11 +661,11 @@ Representative `mismatch` samples: Of the 1816 candidates, 1675 are the actual `pending_rule_review` subcluster. The other 141 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. -For this output-signature audit, 1761 mismatches were evaluable and 1308 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. +For this output-signature audit, 1761 mismatches were evaluable and 1310 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. Localized first-difference transitions: -- `U+2815 ⠕ -> U+2833 ⠳`: 1307 +- `U+2815 ⠕ -> U+2833 ⠳`: 1309 - `U+2810 ⠐ -> U+283D ⠽`: 1 Mismatch primary-class distribution: @@ -720,9 +720,9 @@ Representative `mismatch` samples: ### `compact_numeric_ascii_letter_suffix` -Of the 2975 candidates, 1149 are the actual `pending_rule_review` subcluster. The other 1826 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 2975 candidates, 1067 are the actual `pending_rule_review` subcluster. The other 1908 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. -For this output-signature audit, 1217 mismatches were evaluable and 252 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. +For this output-signature audit, 1135 mismatches were evaluable and 252 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. Localized first-difference transitions: @@ -734,10 +734,10 @@ Localized first-difference transitions: Mismatch primary-class distribution: -- `comparison_method`: 11 +- `comparison_method`: 12 - `corpus_suspect`: 49 -- `pending_rule_review`: 1149 -- `unsupported_character_review`: 8 +- `pending_rule_review`: 1067 +- `unsupported_character_review`: 7 Representative `exact` samples: @@ -806,14 +806,14 @@ Representative `mismatch` samples: ### `decimal_point_between_ascii_digits` -Of the 4546 candidates, 1277 are the actual `pending_rule_review` subcluster. The other 3269 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 4546 candidates, 1237 are the actual `pending_rule_review` subcluster. The other 3309 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. -For this output-signature audit, 1333 mismatches were evaluable and 262 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. +For this output-signature audit, 1293 mismatches were evaluable and 262 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. Localized first-difference transitions: - `U+2814 ⠔ -> U+2824 ⠤`: 55 -- `U+2826 ⠦ -> U+2800 ⠀`: 35 +- `U+2826 ⠦ -> U+2800 ⠀`: 36 - `U+2800 ⠀ -> U+280A ⠊`: 16 - `U+2826 ⠦ -> U+2810 ⠐`: 16 - `U+283C ⠼ -> U+2800 ⠀`: 14 @@ -822,7 +822,7 @@ Mismatch primary-class distribution: - `comparison_method`: 16 - `corpus_suspect`: 28 -- `pending_rule_review`: 1277 +- `pending_rule_review`: 1237 - `unsupported_character_review`: 12 Representative `exact` samples: @@ -938,14 +938,14 @@ Representative `mismatch` samples: ### `korean_prefixed_closed_allcaps_parenthetical` -Of the 54492 candidates, 9072 are the actual `pending_rule_review` subcluster. The other 45420 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 54492 candidates, 8982 are the actual `pending_rule_review` subcluster. The other 45510 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. Mismatch primary-class distribution: -- `comparison_method`: 171 +- `comparison_method`: 173 - `corpus_suspect`: 982 -- `pending_rule_review`: 9072 -- `unsupported_character_review`: 137 +- `pending_rule_review`: 8982 +- `unsupported_character_review`: 135 Representative `exact` samples: @@ -991,9 +991,9 @@ Representative `mismatch` samples: ### `korean_prefixed_closed_roman_annotation_rule_34_order` -Of the 64382 candidates, 11399 are the actual `pending_rule_review` subcluster. The other 52983 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 64382 candidates, 11093 are the actual `pending_rule_review` subcluster. The other 53289 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. -For this output-signature audit, 12877 mismatches were evaluable and 1099 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. +For this output-signature audit, 12571 mismatches were evaluable and 1099 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. Localized first-difference transitions: @@ -1002,10 +1002,10 @@ Localized first-difference transitions: Mismatch primary-class distribution: -- `comparison_method`: 221 +- `comparison_method`: 223 - `corpus_suspect`: 1095 -- `pending_rule_review`: 11399 -- `unsupported_character_review`: 162 +- `pending_rule_review`: 11093 +- `unsupported_character_review`: 160 Representative `exact` samples: @@ -1199,9 +1199,9 @@ Representative `mismatch` samples: ### `rule69_ascii_unit_before_terminator_skipping_symbol` -Of the 440 candidates, 106 are the actual `pending_rule_review` subcluster. The other 334 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 440 candidates, 100 are the actual `pending_rule_review` subcluster. The other 340 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. -For this output-signature audit, 111 mismatches were evaluable and 10 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. +For this output-signature audit, 105 mismatches were evaluable and 10 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. Localized first-difference transitions: @@ -1215,7 +1215,7 @@ Mismatch primary-class distribution: - `comparison_method`: 3 - `corpus_suspect`: 1 -- `pending_rule_review`: 106 +- `pending_rule_review`: 100 - `unsupported_character_review`: 1 Representative `exact` samples: @@ -1321,14 +1321,14 @@ Representative `mismatch` samples: ### `standalone_multi_character_uppercase_roman_word` -Of the 62411 candidates, 11740 are the actual `pending_rule_review` subcluster. The other 50671 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 62411 candidates, 11571 are the actual `pending_rule_review` subcluster. The other 50840 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. Mismatch primary-class distribution: -- `comparison_method`: 205 +- `comparison_method`: 207 - `corpus_suspect`: 1082 -- `pending_rule_review`: 11740 -- `unsupported_character_review`: 159 +- `pending_rule_review`: 11571 +- `unsupported_character_review`: 157 Representative `exact` samples: @@ -1415,35 +1415,34 @@ Representative `mismatch` samples: ### `uppercase_ascii_run_immediately_after_digit_in_roman_sequence` -Of the 1896 candidates, 1086 are the actual `pending_rule_review` subcluster. The other 810 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 1896 candidates, 764 are the actual `pending_rule_review` subcluster. The other 1132 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. -For this output-signature audit, 1140 mismatches were evaluable and 331 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. +For this output-signature audit, 818 mismatches were evaluable and 1 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. Localized first-difference transitions: -- `U+2820 ⠠ -> U+2830 ⠰`: 330 - `U+2830 ⠰ -> U+2820 ⠠`: 1 Mismatch primary-class distribution: -- `comparison_method`: 7 +- `comparison_method`: 9 - `corpus_suspect`: 38 -- `pending_rule_review`: 1086 -- `unsupported_character_review`: 9 +- `pending_rule_review`: 764 +- `unsupported_character_review`: 7 Representative `exact` samples: -- `sentence_01.json` #343: 확정안을 보면 정부는 2036년 최대전력 수요(목표 수요)를 118.0GW(기가와트)로 예측했다. 1GW는 보통 원전 1기의 발전량으로 보고 있다. - - expected: `⠚⠧⠁⠨⠻⠣⠒⠮⠀⠘⠥⠑⠡⠀⠨⠻⠘⠍⠉⠵⠀⠼⠃⠚` - - actual: `⠚⠧⠁⠨⠻⠣⠒⠮⠀⠘⠥⠑⠡⠀⠨⠻⠘⠍⠉⠵⠀⠼⠃⠚` +- `sentence_01.json` #25: 세븐일레븐은 올해 말 정식 버전 오픈을 목표로 하며, 이를 통해 주력상품을 구매하고 배송까지 받을 수 있는 차세대 오프라인을 위한 온라인(O4O) 시스템을 구축해 나갈 계획이다. + - expected: `⠠⠝⠘⠵⠕⠂⠐⠝⠘⠵⠵⠀⠥⠂⠚⠗⠀⠑⠂⠀⠨⠻⠠⠕` + - actual: `⠠⠝⠘⠵⠕⠂⠐⠝⠘⠵⠵⠀⠥⠂⠚⠗⠀⠑⠂⠀⠨⠻⠠⠕` - current primary/reason: `exact` / `exact` - `sentence_02.json` #51: 먼저, 차세대 비디오 압축표준(VVC) 분야 64건과 5G 이동통신(NR) 분야 48건 등 시장 수요가 큰 상용표준특허가 다수 포함, 향후 상당한 특허 기술료가 전망된다. - expected: `⠑⠾⠨⠎⠐⠀⠰⠣⠠⠝⠊⠗⠀⠘⠕⠊⠕⠥⠀⠣⠃⠰⠍⠁` - actual: `⠑⠾⠨⠎⠐⠀⠰⠣⠠⠝⠊⠗⠀⠘⠕⠊⠕⠥⠀⠣⠃⠰⠍⠁` - current primary/reason: `exact` / `exact` -- `sentence_03.json` #81: 이번에 개발한 신제품의 용량은 기존 제품(16GB)보다 50% 높아졌다. D램 용량이 높아지면 시스템이 데이터를 더 빠르고 효율적으로 처리할 수 있게 된다. - - expected: `⠕⠘⠾⠝⠀⠈⠗⠘⠂⠚⠒⠀⠠⠟⠨⠝⠙⠍⠢⠺⠀⠬⠶⠐` - - actual: `⠕⠘⠾⠝⠀⠈⠗⠘⠂⠚⠒⠀⠠⠟⠨⠝⠙⠍⠢⠺⠀⠬⠶⠐` +- `sentence_03.json` #47: 이수화학이 미국 과학 기술 엔지니어링 회사 KBR과 전고체 배터리 소재 황화리튬(Li2S)의 상업공정 공동 개발에 나선다. 양사는 이를 위해 공동개발 계약을 체결했다고 20일 밝혔다. + - expected: `⠕⠠⠍⠚⠧⠚⠁⠕⠀⠑⠕⠈⠍⠁⠀⠈⠧⠚⠁⠀⠈⠕⠠⠯` + - actual: `⠕⠠⠍⠚⠧⠚⠁⠕⠀⠑⠕⠈⠍⠁⠀⠈⠧⠚⠁⠀⠈⠕⠠⠯` - current primary/reason: `exact` / `exact` - `sentence_04.json` #78: 차체 중량은 55㎏인데, 체중 100㎏의 사람을 태울 수 있다. 배터리 출력은 1000W(와트)로, 도심 길거리에서 쓰는 킥보드와 비슷한 출력이다. - expected: `⠰⠣⠰⠝⠀⠨⠍⠶⠐⠜⠶⠵⠀⠼⠑⠑⠴⠅⠛⠲⠟⠊⠝⠐` @@ -1452,43 +1451,28 @@ Representative `exact` samples: Representative `localized_mismatch` samples: -- `sentence_01.json` #25: 세븐일레븐은 올해 말 정식 버전 오픈을 목표로 하며, 이를 통해 주력상품을 구매하고 배송까지 받을 수 있는 차세대 오프라인을 위한 온라인(O4O) 시스템을 구축해 나갈 계획이다. - - expected: `⠟⠦⠄⠴⠠⠕⠼⠙⠠⠕⠠⠴⠀⠠⠕⠠⠪⠓⠝⠢⠮⠀⠈⠍` - - actual: `⠟⠦⠄⠴⠠⠕⠼⠙⠰⠠⠕⠠⠴⠀⠠⠕⠠⠪⠓⠝⠢⠮⠀⠈` - - first differing cell (zero-based): 129 - - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_02.json` #1138: DL이앤씨 관계자는 “설계·조달·시공(EPC) 수행 역량을 집중해 세계 최초로 TC2C 기술을 상용화하는 데 최선의 노력을 다하겠다”고 했다. - - expected: `⠀⠴⠠⠠⠞⠉⠼⠃⠠⠉⠲⠀⠈⠕⠠⠯⠮⠀⠇⠶⠬⠶⠚⠧` - - actual: `⠀⠴⠠⠠⠞⠉⠼⠃⠰⠉⠲⠀⠈⠕⠠⠯⠮⠀⠇⠶⠬⠶⠚⠧` - - first differing cell (zero-based): 91 - - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_03.json` #47: 이수화학이 미국 과학 기술 엔지니어링 회사 KBR과 전고체 배터리 소재 황화리튬(Li2S)의 상업공정 공동 개발에 나선다. 양사는 이를 위해 공동개발 계약을 체결했다고 20일 밝혔다. - - expected: `⠦⠄⠴⠠⠇⠊⠼⠃⠠⠎⠠⠴⠺⠀⠇⠶⠎⠃⠈⠿⠨⠻⠀⠈` - - actual: `⠦⠄⠴⠠⠇⠊⠼⠃⠰⠠⠎⠠⠴⠺⠀⠇⠶⠎⠃⠈⠿⠨⠻⠀` - - first differing cell (zero-based): 87 - - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_04.json` #1097: LG유플러스는 5세대 이동통신(5G)과 차량·사물통신(V2X)을 바탕으로 도시환경관리에 최적화된 자율주행 차량 관제시스템을 개발한 뒤 자율주행 기술을 실증할 도시인 ‘리빙랩’에서 4만㎞ 이상 서비스 실증에 나선다. - - expected: `⠟⠦⠄⠴⠠⠧⠼⠃⠠⠭⠠⠴⠮⠀⠘⠓⠶⠪⠐⠥⠀⠊⠥⠠` - - actual: `⠟⠦⠄⠴⠠⠧⠼⠃⠰⠠⠭⠠⠴⠮⠀⠘⠓⠶⠪⠐⠥⠀⠊⠥` - - first differing cell (zero-based): 63 +- `sentence_03.json` #24404: 또 가상통화 헤지펀드 스리애로우스캐피털(3AC), 코인 중개·대부업체 보이저 디지털, 대형 가상통화 거래소 FTX 등의 연쇄 파산으로 이어졌다. + - expected: `⠕⠓⠞⠦⠄⠼⠉⠴⠰⠠⠠⠁⠉⠠⠴⠐⠀⠋⠥⠟⠀⠨⠍⠶` + - actual: `⠕⠓⠞⠦⠄⠼⠉⠴⠠⠠⠁⠉⠠⠴⠐⠀⠋⠥⠟⠀⠨⠍⠶⠈` + - first differing cell (zero-based): 42 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` Representative `mismatch` samples: -- `sentence_01.json` #25: 세븐일레븐은 올해 말 정식 버전 오픈을 목표로 하며, 이를 통해 주력상품을 구매하고 배송까지 받을 수 있는 차세대 오프라인을 위한 온라인(O4O) 시스템을 구축해 나갈 계획이다. - - expected: `⠟⠦⠄⠴⠠⠕⠼⠙⠠⠕⠠⠴⠀⠠⠕⠠⠪⠓⠝⠢⠮⠀⠈⠍` - - actual: `⠟⠦⠄⠴⠠⠕⠼⠙⠰⠠⠕⠠⠴⠀⠠⠕⠠⠪⠓⠝⠢⠮⠀⠈` - - first differing cell (zero-based): 129 - - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_01.json` #89: “인공지능(AI), 6G 등 핵심 기술을 위한 투자도 늘리는 동시에 전기차 충전, 디지털 헬스, 웹OS 기반의 콘텐츠 서비스 등 많은 영역으로 사업 포트폴리오를 확장하고 있습니다.” + - expected: `⠟⠈⠿⠨⠕⠉⠪⠶⠴⠐⠣⠠⠠⠁⠊⠐⠜⠂⠀⠼⠋⠠⠛⠲` + - actual: `⠟⠈⠿⠨⠕⠉⠪⠶⠦⠄⠴⠠⠠⠁⠊⠠⠴⠐⠀⠼⠋⠰⠠⠛` + - first differing cell (zero-based): 9 + - current primary/reason: `corpus_suspect` / `rule34_roman_indicator_before_opening_parenthesis` - `sentence_02.json` #425: ‘제로(Zero)’는 ‘대전 0시 축제(Daejeon Zero O'clock Festival)’의 영문 이름에서 따왔으며, 축제장에서 3D홀로그램 미디어 콘텐츠 운영을 맡고 있는 애드테크 전문기업 코스윌에서 개발했다. - expected: `⠀⠠⠵⠻⠕⠀⠠⠕⠄⠉⠇⠕⠉⠅⠀⠠⠋⠑⠌⠊⠧⠁⠇⠠` - actual: `⠀⠠⠵⠻⠕⠀⠠⠕⠴⠄⠰⠉⠇⠕⠉⠅⠀⠠⠋⠑⠌⠊⠧⠁` - first differing cell (zero-based): 56 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_03.json` #47: 이수화학이 미국 과학 기술 엔지니어링 회사 KBR과 전고체 배터리 소재 황화리튬(Li2S)의 상업공정 공동 개발에 나선다. 양사는 이를 위해 공동개발 계약을 체결했다고 20일 밝혔다. - - expected: `⠦⠄⠴⠠⠇⠊⠼⠃⠠⠎⠠⠴⠺⠀⠇⠶⠎⠃⠈⠿⠨⠻⠀⠈` - - actual: `⠦⠄⠴⠠⠇⠊⠼⠃⠰⠠⠎⠠⠴⠺⠀⠇⠶⠎⠃⠈⠿⠨⠻⠀` - - first differing cell (zero-based): 87 +- `sentence_03.json` #123: 이날 이네오스는 자동차 위탁 생산 업체인 마그나슈타이어와 함께 새로운 4X4 전기차(EV)를 개발한다고 발표했다. 이네오스는 신차 양산 시점을 2026년으로 목표하고 있다. + - expected: `⠠⠗⠐⠥⠛⠀⠼⠙⠡⠼⠙⠀⠨⠾⠈⠕⠰⠣⠦⠄⠴⠠⠠⠑` + - actual: `⠠⠗⠐⠥⠛⠀⠼⠙⠴⠠⠭⠼⠙⠀⠨⠾⠈⠕⠰⠣⠦⠄⠴⠠` + - first differing cell (zero-based): 62 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` - `sentence_04.json` #115: ‘Busan is Good(부산이라 좋다)’이라는 새로운 도시 표어의 조형물을 공개하고, 3차원(3D)으로 표현한 도시상징 표지(CI) 영상을 상영한다. - expected: `⠁⠝⠀⠊⠎⠀⠠⠛⠙⠦⠄⠘⠍⠇⠒⠕⠐⠣⠀⠨⠥⠴⠊⠠` @@ -1633,9 +1617,9 @@ Representative `mismatch` samples: ### `uppercase_roman_run_followed_by_hyphen_digits` -Of the 571 candidates, 371 are the actual `pending_rule_review` subcluster. The other 200 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 571 candidates, 370 are the actual `pending_rule_review` subcluster. The other 201 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. -For this output-signature audit, 386 mismatches were evaluable and 260 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. +For this output-signature audit, 385 mismatches were evaluable and 260 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. Localized first-difference transitions: @@ -1645,7 +1629,7 @@ Localized first-difference transitions: Mismatch primary-class distribution: - `corpus_suspect`: 12 -- `pending_rule_review`: 371 +- `pending_rule_review`: 370 - `unsupported_character_review`: 3 Representative `exact` samples: @@ -1696,8 +1680,8 @@ These three cohorts are defined by both an input boundary and the sentence's act | Cohort | Candidates | Exact controls | Mismatch | Target localized | Reverse | |---|---:|---:|---:|---:|---:| -| `allcaps_roman_run_beginning_with_pure_letter_shortform` | 4120 | 2376 | 1744 | 778 | 42 | -| `uppercase_ascii_run_immediately_after_digit_in_roman_sequence` | 1896 | 756 | 1140 | 330 | 1 | +| `allcaps_roman_run_beginning_with_pure_letter_shortform` | 4120 | 2384 | 1736 | 781 | 42 | +| `uppercase_ascii_run_immediately_after_digit_in_roman_sequence` | 1896 | 1078 | 818 | 0 | 1 | | `uppercase_ascii_run_immediately_after_hyphen_in_roman_sequence` | 952 | 32 | 920 | 312 | 2 | ### All-caps shortform prefix at an attached Roman entry @@ -1721,7 +1705,7 @@ Same-surface controls demonstrate why primary classes must not be changed by coh | Surface | Candidates | Exact | Mismatch | Target-localized | |---|---:|---:|---:|---:| | `AC` | 159 | 85 | 74 | 66 | -| `LLM` | 176 | 1 | 175 | 147 | +| `LLM` | 176 | 1 | 175 | 149 | | `CD` | 65 | 26 | 39 | 12 | | `IMM` | 27 | 3 | 24 | 22 | | `AG` | 18 | 9 | 9 | 0 | @@ -1739,7 +1723,7 @@ Same-surface controls demonstrate why primary classes must not be changed by coh ### Uppercase immediately after a digit -UEB rule 5.6.1 says the numeric indicator establishes grade-1 mode, and rule 5.6.2 ends that mode at a space, hyphen, dash, or grade-1 terminator; a capitalization indicator is not a terminator. Korean rule 35 likewise keeps Roman letters and an adjacent number in one Roman section. The 330 localized `⠠ -> ⠰` cases therefore identify a separate possible redundant continuation at the digit-to-capital boundary. This cohort is not merged with the shortform-prefix route: the one reverse case and the absence of same-surface exact controls among frequent forms such as `B2B`, `V2X`, `Li2S`, and `O4O` require an independent state-machine audit before any engine change. +UEB rule 5.6.1 says the numeric indicator establishes grade-1 mode, and rule 5.6.2 ends that mode at a space, hyphen, dash, or grade-1 terminator; a capitalization indicator is not a terminator. Korean rule 35 likewise keeps Roman letters and an adjacent number in one Roman section. The PDF's printed `3b`, `3B`, and `3m` examples distinguish the three following-letter classes: lowercase `a`-`j` retains `⠰` because its cells are numeric, a capital uses its capitalization indicator, and lowercase `k`-`z` needs no extra indicator. `Braille4All`, `M4G`, and `W1N` independently confirm the capital boundary inside longer alphanumeric strings. Before the engine change this cohort contained 330 localized `⠠ -> ⠰` cases. A blanket digit-to-letter removal reached 67,000/83,528 (+317) but was rejected: retaining `⠰` only for lowercase `a`-`j` recovers 10 exact cases and raises the result to 67,010. The wrapper control also exposes a separate routing boundary: a numeric run already preceded by an ASCII letter is part of the Roman identifier, not a fresh rule-69 compact unit. Preserving the rule-69 path for genuinely numeric-leading units while excluding that identifier boundary adds 2 more exact cases, for a final 67,012 (+329). The uppercase cohort moves from 756 exact / 1,140 mismatch / 330 target-localized / 1 reverse to 1,078 exact / 818 mismatch / 0 target-localized / 1 reverse. The remaining non-exact members are not attributed to the removed uppercase transition: their sentence-level first difference may lie in another structure and remains under its existing primary class. This numeric state change remains separate from both the complete-shortform guard and the still-unimplemented hyphen continuation cohort. ### Uppercase immediately after a hyphen @@ -1768,36 +1752,36 @@ The tight-triangle cohort is not an implementation premise. Hangeul rule 49 assi Corpus contradictions remain a separate gate: identical inputs with conflicting references are classified as `corpus_suspect` before these cohorts are recorded and would appear explicitly in each mismatch primary-class distribution. Their absence does not prove a reference correct; it only means that this deterministic contradiction test did not fire. -Current uppercase-Roman hyphen-digits measurement: 571 candidates, 185 exact controls, 386 mismatches, 371 members in the actual `pending_rule_review` subcluster, and 260/386 evaluable mismatches whose first difference is inside the target run plus its entry boundary. It remains distinct from parenthesized digits and headword expansions; no engine change is inferred. +Current uppercase-Roman hyphen-digits measurement: 571 candidates, 186 exact controls, 385 mismatches, 370 members in the actual `pending_rule_review` subcluster, and 260/385 evaluable mismatches whose first difference is inside the target run plus its entry boundary. It remains distinct from parenthesized digits and headword expansions; no engine change is inferred. Current single-capital parenthesized-digits measurement: 1361 candidates, 0 exact controls, 1361 mismatches, 1357 members in the actual `pending_rule_review` subcluster, and 1342/1361 evaluable mismatches whose first difference is inside the target run plus its entry boundary. No engine change is inferred from the ambiguous prose/function surface form. Current mixed Roman+Korean boundary before uppercase headword-expansion measurement: 10 candidates, 0 exact controls, 10 mismatches, 10 members in the actual `pending_rule_review` subcluster, and 8/10 evaluable mismatches whose first difference is localized to the later headword's entry boundary/output. The detector cannot be satisfied by the earlier Roman entry. No HCA-shaped engine routing rule is introduced. -Current compact numeric+ASCII-suffix measurement: 2975 candidates, 1758 exact controls, 1217 mismatches, 1149 members in the actual `pending_rule_review` subcluster, and 252/1217 evaluable mismatches whose first difference is inside the complete current-engine output signature or its immediate entry boundary. Rule 40 requires the numeric indicator and rule 69 requires Roman indicators around a Roman-written unit, but the input shape alone cannot prove that every ASCII suffix is a unit. +Current compact numeric+ASCII-suffix measurement: 2975 candidates, 1840 exact controls, 1135 mismatches, 1067 members in the actual `pending_rule_review` subcluster, and 252/1135 evaluable mismatches whose first difference is inside the complete current-engine output signature or its immediate entry boundary. Rule 40 requires the numeric indicator and rule 69 requires Roman indicators around a Roman-written unit, but the input shape alone cannot prove that every ASCII suffix is a unit. | ASCII suffix | Candidates | Exact | Mismatch | Localized first diff | |---|---:|---:|---:|---:| -| `m` | 364 | 255 | 109 | 22 | -| `km` | 308 | 224 | 84 | 3 | -| `G` | 237 | 160 | 77 | 3 | -| `kg` | 209 | 172 | 37 | 6 | -| `D` | 167 | 110 | 57 | 5 | -| `p` | 140 | 19 | 121 | 84 | +| `m` | 364 | 258 | 106 | 22 | +| `km` | 308 | 249 | 59 | 3 | +| `G` | 237 | 162 | 75 | 3 | +| `kg` | 209 | 173 | 36 | 6 | +| `D` | 167 | 113 | 54 | 5 | +| `p` | 140 | 20 | 120 | 84 | | `g` | 138 | 95 | 43 | 8 | | `t` | 128 | 83 | 45 | 16 | | `M` | 104 | 79 | 25 | 6 | | `cm` | 69 | 51 | 18 | 0 | -| `B` | 62 | 12 | 50 | 0 | -| `GB` | 61 | 25 | 36 | 4 | +| `B` | 62 | 37 | 25 | 0 | +| `GB` | 61 | 27 | 34 | 4 | | `GWh` | 57 | 42 | 15 | 8 | | `ha` | 53 | 18 | 35 | 0 | -| `TV` | 52 | 2 | 50 | 1 | -| `S` | 45 | 5 | 40 | 0 | +| `TV` | 52 | 38 | 14 | 1 | +| `S` | 45 | 29 | 16 | 0 | | `GW` | 44 | 35 | 9 | 6 | -| `X` | 39 | 12 | 27 | 2 | +| `X` | 39 | 14 | 25 | 2 | | `bp` | 39 | 3 | 36 | 18 | -| `K` | 38 | 10 | 28 | 1 | +| `K` | 38 | 11 | 27 | 1 | | `MW` | 37 | 35 | 2 | 0 | | `TURN` | 33 | 16 | 17 | 0 | | `L` | 31 | 21 | 10 | 5 | @@ -1806,19 +1790,19 @@ Current compact numeric+ASCII-suffix measurement: 2975 candidates, 1758 exact co Entry-boundary pre-fix baseline: 2,975 candidates, 1,649 exact controls, 1,326 mismatches, 1,259 pending members, and 356 localized first differences; the dominant reference number-sign versus current space transition accounted for 296 cases. Rules 68 and 69 already accept semantic Unicode compatibility-unit forms. The implementation derives compact ASCII spellings only from their all-letter NFKC decompositions, reuses the owning rule's PDF-defined cells, chooses the longest complete spelling, and rejects partial suffix matches. It does not extend recognition to separated English words or arbitrary corpus suffixes. The same cohort now has 1,759 exact controls, 1,216 mismatches, 1,148 pending members, and 261 localized first differences; corpus-wide exact matches moved from 66,436 to 66,546 (+110). Rule 69's printed `160㎎/㎗` example directly controls `160mg`; the additional full-encoder `240mg`/`240㎎` pair proves that recognition is invariant to the numeric value and also passes. Rule 68's printed `10,000㎡는 1㏊이다` example controls the `ha` spelling through U+33CA's NFKC decomposition; `15.2ha`/`15.2㏊` now passes without a spelling-specific output branch. The 53-case `ha` suffix control moved from 0 exact to 18 exact; its remaining 35 mismatches have independent later or surrounding differences. A full U+3300..U+33FF owner audit found 73 Rules 68/69 glyphs with pure-ASCII NFKC decompositions, 73 distinct spellings, and therefore no current duplicate-spelling owner collision. Production nevertheless groups every owner before resolution and excludes a spelling if any owner cells differ; a synthetic collision test proves that this is not first-wins behavior. An exhaustive test also compares every one of the 73 derived spellings with every owner glyph at both the unit-cell and full-encoder boundaries in a neutral Korean measurement context. That audit exposed the pre-existing explicit `cal` mapping's missing ordinary terminator; rule 69 requires the terminator, while the existing slash-boundary function removes it for the printed `cal/㎠/min` context. Restoring the ordinary terminator and extending that slash-continuation boundary made the audit pass without changing the corpus-wide 66,546 exact total. Ambiguous pure-English inputs remain on UEB: the standard controls `3m` and `4.m` are retained and pass, rather than being globally forced into a Korean unit route. -Current rule-69 ASCII-unit punctuation-boundary measurement: 440 candidates, 329 exact controls, 111 mismatches, 106 members in the actual `pending_rule_review` subcluster, and 10/111 evaluable mismatches whose first difference is localized to the unit-plus-punctuation output signature. PDF rule 69 requires a Roman terminator after a Roman-written unit in the ordinary case, while rules 33/34 omit it at the listed punctuation or enclosing-mark boundary; the rule 46 PDF example `체중(kg)` is the minimal parenthesized-unit control. Membership is restricted to rule-69 spellings already supported by the engine and does not infer unit semantics for arbitrary ASCII suffixes. +Current rule-69 ASCII-unit punctuation-boundary measurement: 440 candidates, 335 exact controls, 105 mismatches, 100 members in the actual `pending_rule_review` subcluster, and 10/105 evaluable mismatches whose first difference is localized to the unit-plus-punctuation output signature. PDF rule 69 requires a Roman terminator after a Roman-written unit in the ordinary case, while rules 33/34 omit it at the listed punctuation or enclosing-mark boundary; the rule 46 PDF example `체중(kg)` is the minimal parenthesized-unit control. Membership is restricted to rule-69 spellings already supported by the engine and does not infer unit semantics for arbitrary ASCII suffixes. Analyzer pre-fix baseline: 440 candidates, 0 exact controls, 440 mismatches, 435 pending members, and 9 signature-local first differences. After the generalized rule-33/34 boundary override and the matching non-math routing guard, the same cohort has 325 exact controls and 115 mismatches. Corpus-wide exact matches moved from 66,039 to 66,436 (+397); the additional gains are applications of the same boundary rule outside this strict ASCII detector, including compatibility-unit forms. The complete standard suite remains 5,141/5,141. -Current decimal-point measurement: 4546 candidates, 3213 exact controls, 1333 mismatches, 1277 members in the actual `pending_rule_review` subcluster, and 262/1333 evaluable mismatches whose first difference is inside the complete decimal-containing word's current-engine signature. Hangeul rules 43 and 48 keep an ASCII point between digits in the numeric sequence and encode it as the decimal point; rules 35 and 69 supply controls for adjacent Roman-number chains and Roman units. This is an implementation-candidate audit, not permission to specialize on a corpus reference. +Current decimal-point measurement: 4546 candidates, 3253 exact controls, 1293 mismatches, 1237 members in the actual `pending_rule_review` subcluster, and 262/1293 evaluable mismatches whose first difference is inside the complete decimal-containing word's current-engine signature. Hangeul rules 43 and 48 keep an ASCII point between digits in the numeric sequence and encode it as the decimal point; rules 35 and 69 supply controls for adjacent Roman-number chains and Roman units. This is an implementation-candidate audit, not permission to specialize on a corpus reference. Analyzer checkpoint `ed20f99` recorded the pre-fix baseline as 4,546 candidates, 2,584 exact controls, 1,962 mismatches, 1,905 pending members, and 875 localized first differences. Its dominant localized transition was reference decimal point `U+2832 ⠲` versus current Roman indicator `U+2834 ⠴` in 647 cases. After the generalized rule-43/48 guard, that transition is absent: 525 cases become exact and the other corrected prefixes expose later independent mismatches. -Current all-caps `OU` measurement: 1816 candidates, 55 exact controls, 1761 mismatches, 1675 members in the actual `pending_rule_review` subcluster, and 1308/1761 evaluable mismatches whose first difference is inside the current-engine output signature for the detected run. This is a pronunciation-sensitive UEB review cohort, not an engine routing rule. +Current all-caps `OU` measurement: 1816 candidates, 55 exact controls, 1761 mismatches, 1675 members in the actual `pending_rule_review` subcluster, and 1310/1761 evaluable mismatches whose first difference is inside the current-engine output signature for the detected run. This is a pronunciation-sensitive UEB review cohort, not an engine routing rule. -Current standalone-uppercase measurement: 62411 candidates, 49225 exact controls, 13186 mismatches, and 11740 members in the actual `pending_rule_review` subcluster. Its high frequency does not make it causal: the same input shape is exact in many cases, and a sentence containing the shape may first differ at another Roman, numeric, or punctuation structure. No engine change is inferred from this cohort. +Current standalone-uppercase measurement: 62411 candidates, 49394 exact controls, 13017 mismatches, and 11571 members in the actual `pending_rule_review` subcluster. Its high frequency does not make it causal: the same input shape is exact in many cases, and a sentence containing the shape may first differ at another Roman, numeric, or punctuation structure. No engine change is inferred from this cohort. -Current Korean-prefixed all-caps parenthetical measurement: 54492 candidates, 44130 exact controls, 10362 mismatches, and 9072 members in the actual `pending_rule_review` subcluster. This is a semantic-collision audit, not an engine routing rule; no implementation change is inferred from its reference outputs. +Current Korean-prefixed all-caps parenthetical measurement: 54492 candidates, 44220 exact controls, 10272 mismatches, and 8982 members in the actual `pending_rule_review` subcluster. This is a semantic-collision audit, not an engine routing rule; no implementation change is inferred from its reference outputs. -Current rule-34 opening-order measurement: 64382 structural candidates, 51505 exact controls, 12877 mismatches, and 1099/12877 evaluable mismatches whose first difference is inside the current engine's Korean opening-parenthesis cells. Only the 1098 localized first-cell transitions have the reference/current order `⠴` versus `⠦`. After requiring the complete reference `⠴⠐⠣` versus current/PDF `⠦⠄⠴` three-cell signature and preserving higher-priority comparison classifications, 1095 are classified as `corpus_suspect`; mere coexistence with a Korean-prefixed Roman annotation does not change a primary class. +Current rule-34 opening-order measurement: 64382 structural candidates, 51811 exact controls, 12571 mismatches, and 1099/12571 evaluable mismatches whose first difference is inside the current engine's Korean opening-parenthesis cells. Only the 1098 localized first-cell transitions have the reference/current order `⠴` versus `⠦`. After requiring the complete reference `⠴⠐⠣` versus current/PDF `⠦⠄⠴` three-cell signature and preserving higher-priority comparison classifications, 1095 are classified as `corpus_suspect`; mere coexistence with a Korean-prefixed Roman annotation does not change a primary class. Current all-caps Roman middle-dot measurement: 97 candidates, 0 exact controls, 97 mismatches, and 96 members in the actual `pending_rule_review` subcluster. This cross-cutting cohort preserves every primary class and is not an engine routing rule. @@ -1836,10 +1820,10 @@ The audit starts from all raw encoding errors, then separates cases already reso | Encoding-error audit | Cases | |---|---:| | Raw encoding errors | 450 | -| Resolved by comparison method | 265 | +| Resolved by comparison method | 267 | | Excluded as corpus suspect | 0 | -| Unresolved encoding-error review cases | 185 | -| Explained by singleton unsupported character(s) | 185 | +| Unresolved encoding-error review cases | 183 | +| Explained by singleton unsupported character(s) | 183 | | Multiple singleton unsupported characters | 2 | | Unclassified without a singleton explanation | 0 | @@ -1850,11 +1834,11 @@ The audit starts from all raw encoding errors, then separates cases already reso | Error message | Cases | |---|---:| -| `Invalid symbol character` | 185 | +| `Invalid symbol character` | 183 | | Error family | Cases | |---|---:| -| `enclosed_organization_mark` | 166 | +| `enclosed_organization_mark` | 164 | | `letterlike_unit_symbol` | 9 | | `punctuation_or_layout_symbol` | 12 | @@ -1873,25 +1857,25 @@ Families are diagnostics, not automatic normalization permissions. Rules 68/69 c | `U+2E31 ⸱` | 2 | `⸱` | `punctuation_or_layout_symbol` | | `U+302E 〮` | 1 | `〮` | `punctuation_or_layout_symbol` | | `U+3214 ㈔` | 2 | `(사)` | `enclosed_organization_mark` | -| `U+321C ㈜` | 164 | `(주)` | `enclosed_organization_mark` | +| `U+321C ㈜` | 162 | `(주)` | `enclosed_organization_mark` | ## Shards | Shard | Exact | Total | Accuracy | |---|---:|---:|---:| -| `sentence_01.json` | 19994 | 25000 | 79.98% | -| `sentence_02.json` | 19494 | 25000 | 77.98% | -| `sentence_03.json` | 20118 | 25000 | 80.47% | -| `sentence_04.json` | 7077 | 8528 | 82.99% | +| `sentence_01.json` | 20124 | 25000 | 80.50% | +| `sentence_02.json` | 19577 | 25000 | 78.31% | +| `sentence_03.json` | 20208 | 25000 | 80.83% | +| `sentence_04.json` | 7103 | 8528 | 83.29% | ## Overlapping mismatch traits | Trait | Count | |---|---:| -| `contains_ascii_digits` | 13233 | -| `contains_ascii_letters` | 16659 | -| `contains_delimiter_or_quote` | 16845 | -| `input_not_nfkc` | 724 | +| `contains_ascii_digits` | 12904 | +| `contains_ascii_letters` | 16330 | +| `contains_delimiter_or_quote` | 16516 | +| `input_not_nfkc` | 720 | ## Samples @@ -1903,15 +1887,15 @@ Families are diagnostics, not automatic normalization permissions. Rules 68/69 c - `sentence_01.json` #18: 2023년형 신제품은 매터(Matter)와 HCA(Home Connectivity Alliance) 표준을 지원하는 스마트싱스 허브(SmartThings Hub)를 기반으로 다양한 기기를 자동으로 연결하고, 제어·관리할 수 있는 것이 특징이다. - expected: `⠁⠞⠞⠻⠠⠴⠧⠀⠴⠠⠠⠓⠉⠁⠐⠣⠠⠓⠕⠍⠑⠀⠠⠒` - actual: `⠁⠞⠞⠻⠠⠴⠧⠀⠀⠠⠠⠓⠉⠁⠦⠠⠓⠕⠍⠑⠀⠴⠠⠒` -- `sentence_01.json` #25: 세븐일레븐은 올해 말 정식 버전 오픈을 목표로 하며, 이를 통해 주력상품을 구매하고 배송까지 받을 수 있는 차세대 오프라인을 위한 온라인(O4O) 시스템을 구축해 나갈 계획이다. - - expected: `⠟⠦⠄⠴⠠⠕⠼⠙⠠⠕⠠⠴⠀⠠⠕⠠⠪⠓⠝⠢⠮⠀⠈⠍` - - actual: `⠟⠦⠄⠴⠠⠕⠼⠙⠰⠠⠕⠠⠴⠀⠠⠕⠠⠪⠓⠝⠢⠮⠀⠈` - `sentence_01.json` #35: 삼성전자는 올해 CES에서 77인치 OLED TV를 처음으로 공개할 예정이다. 이는 지난 8월 ‘국제정보디스플레이학술대회(IMID) 2022’에서 삼성디스플레이가 공개한 77인치 퀀텀닷(QD)-OLED 패널을 탑재한 제품이다. - expected: `⠋⠏⠒⠓⠎⠢⠊⠄⠴⠐⠣⠟⠙⠐⠜⠤⠠⠠⠕⠇⠫⠲⠀⠙` - actual: `⠋⠏⠒⠓⠎⠢⠊⠄⠀⠀⠦⠠⠠⠟⠙⠴⠔⠠⠠⠕⠇⠑⠙⠀` - `sentence_01.json` #38: 올해 소프트웨어(SW) 업계는 국내 인공지능(AI) 제품·서비스 고도화와 함께 전 산업영역에서 SW영역 확대가 가시화될 것으로 보인다. 또한 블록체인이 웹3(Web3)와 연계해 탈중앙화·탈독점화 경제체계로 진화할 것으로 예상된다. - expected: `⠟⠕⠀⠏⠗⠃⠼⠉⠦⠄⠴⠠⠺⠑⠃⠼⠉⠠⠴⠧⠀⠡⠈⠌` - actual: `⠟⠕⠀⠏⠗⠃⠼⠉⠴⠐⠣⠠⠺⠑⠃⠼⠉⠴⠐⠜⠲⠧⠀⠡` +- `sentence_01.json` #39: 소프트웨어정책연구소(SPRi)는 ‘2023년 SW산업 10대 이슈 전망’을 통해 올해 가장 주요한 이슈로 인공지능 기반 모델 고도화를 1위로 선정했다. + - expected: `⠈⠍⠠⠥⠦⠄⠴⠠⠠⠎⠏⠠⠗⠊⠠⠴⠉⠵⠀⠠⠦⠼⠃⠚` + - actual: `⠈⠍⠠⠥⠦⠄⠴⠠⠎⠠⠏⠠⠗⠊⠠⠴⠉⠵⠀⠠⠦⠼⠃⠚` ### `nfkc_input_equivalent` diff --git a/libs/braillify/examples/nikl_corpus_analyze.rs b/libs/braillify/examples/nikl_corpus_analyze.rs index 6b0367d7..676a61eb 100644 --- a/libs/braillify/examples/nikl_corpus_analyze.rs +++ b/libs/braillify/examples/nikl_corpus_analyze.rs @@ -2904,13 +2904,27 @@ fn markdown(report: &AnalysisReport) -> String { UEB rule 5.6.1 says the numeric indicator establishes grade-1 mode, and \ rule 5.6.2 ends that mode at a space, hyphen, dash, or grade-1 terminator; \ a capitalization indicator is not a terminator. Korean rule 35 likewise \ - keeps Roman letters and an adjacent number in one Roman section. The 330 \ - localized `⠠ -> ⠰` cases therefore identify a separate possible redundant \ - continuation at the digit-to-capital boundary. This cohort is not merged \ - with the shortform-prefix route: the one reverse case and the absence of \ - same-surface exact controls among frequent forms such as `B2B`, `V2X`, \ - `Li2S`, and `O4O` require an independent state-machine audit before any \ - engine change.\n\n\ + keeps Roman letters and an adjacent number in one Roman section. The PDF's \ + printed `3b`, `3B`, and `3m` examples distinguish the three following-letter \ + classes: lowercase `a`-`j` retains `⠰` because its cells are numeric, a \ + capital uses its capitalization indicator, and lowercase `k`-`z` needs no \ + extra indicator. `Braille4All`, `M4G`, and `W1N` independently confirm the \ + capital boundary inside longer alphanumeric strings. Before the engine \ + change this cohort contained 330 localized `⠠ -> ⠰` cases. A blanket \ + digit-to-letter removal reached 67,000/83,528 (+317) but was rejected: \ + retaining `⠰` only for lowercase `a`-`j` recovers 10 exact cases and raises \ + the result to 67,010. The wrapper control also exposes a separate routing \ + boundary: a numeric run already preceded by an ASCII letter is part of the \ + Roman identifier, not a fresh rule-69 compact unit. Preserving the rule-69 \ + path for genuinely numeric-leading units while excluding that identifier \ + boundary adds 2 more exact cases, for a final 67,012 (+329). The uppercase \ + cohort moves from 756 exact / 1,140 mismatch / 330 target-localized / 1 \ + reverse to 1,078 exact / 818 mismatch / 0 target-localized / 1 reverse. The remaining \ + non-exact members are not attributed to the removed uppercase transition: \ + their sentence-level first difference may lie in another structure and \ + remains under its existing primary class. This numeric state change remains \ + separate from both the complete-shortform guard and the still-unimplemented \ + hyphen continuation cohort.\n\n\ ### Uppercase immediately after a hyphen\n\n\ UEB rule 5.7.2 prints `CD-ROM` with one grade-1 indicator before `CD` and \ no second grade-1 indicator after the hyphen. Korean rule 29 similarly \ diff --git a/libs/braillify/src/rules/emit.rs b/libs/braillify/src/rules/emit.rs index 40d96ced..c83397c3 100644 --- a/libs/braillify/src/rules/emit.rs +++ b/libs/braillify/src/rules/emit.rs @@ -520,9 +520,14 @@ fn emit_word( if state.roman_number_chain && !state.is_english { match &char_type { CharType::English(_) => { - // PDF — roman_number_chain 안 digit 뒤 letter는 영어 연속 표지(⠰)를 - // 부착해 letter임을 명시한다 (digit과 혼동 방지). - result.push(48); + // Korean rule 35 keeps adjacent Roman letters and digits in + // one Roman section. Under UEB 6.5.2, lowercase a-j still + // need grade 1 after a digit because their cells are numeric; + // a capital indicator or a lowercase k-z cell is sufficient + // for every other Roman letter class. + if matches!(*c, 'a'..='j') { + result.push(crate::rules::korean::rule_29::ENGLISH_CONTINUATION); + } roman_mode::resume_english_from_roman_number_chain(state); } CharType::Number(_) => {} @@ -881,6 +886,50 @@ mod tests { assert_eq!(out.iter().filter(|byte| **byte == 52).count(), 1); } + /// UEB 5.6.1/6.5.1-6.5.2 through the rule-29/35 character route. `A` is only + /// the Roman-chain routing scaffold for the PDF's exact `3b`, `3B`, and `3m` + /// suffixes; those cases directly cover lowercase a-j grade 1, capitalization, + /// and unmarked lowercase k-z. Comparison starts immediately after the route's + /// single Roman indicator, so another occurrence cannot satisfy the assertion. + #[rstest::rstest] + #[case::braille4all("Braille4All", "⠠⠃⠗⠁⠊⠇⠇⠑⠼⠙⠠⠁⠇⠇")] + #[case::m4g("M4G", "⠠⠍⠼⠙⠠⠛")] + #[case::w1n("W1N", "⠠⠺⠼⠁⠠⠝")] + #[case::lower_a_to_j("A3b", "⠠⠁⠼⠉⠰⠃")] + #[case::uppercase("A3B", "⠠⠁⠼⠉⠠⠃")] + #[case::lower_k_to_z("A3m", "⠠⠁⠼⠉⠍")] + fn numeric_grade1_mode_continues_into_pdf_roman_examples( + #[case] surface: &str, + #[case] expected_ueb: &str, + ) { + let output = encode(&format!("가({surface})")).unwrap(); + let expected_ueb = expected_ueb + .chars() + .map(crate::unicode::decode_unicode) + .collect::>(); + let roman_start = output + .iter() + .position(|cell| *cell == crate::rules::korean::rule_29::ROMAN_INDICATOR) + .expect("Korean wrapper must enter one Roman section") + + 1; + + assert_eq!( + output.get(roman_start..roman_start + expected_ueb.len()), + Some(expected_ueb.as_slice()) + ); + } + + /// UEB 6.5.2 full-encoder controls for the three Roman letter classes after + /// a digit: lowercase a-j needs grade 1, capitals use their capital indicator, + /// and lowercase k-z needs no additional indicator. + #[rstest::rstest] + #[case::lower_a_to_j("3b", "⠼⠉⠰⠃")] + #[case::uppercase("3B", "⠼⠉⠠⠃")] + #[case::lower_k_to_z("3m", "⠼⠉⠍")] + fn numeric_grade1_letter_class_pdf_controls(#[case] input: &str, #[case] expected: &str) { + assert_eq!(crate::encode_to_unicode(input).unwrap(), expected); + } + #[test] fn fraction_token_encodes() { let mut ir = DocumentIR { diff --git a/libs/braillify/src/rules/korean/rule_69.rs b/libs/braillify/src/rules/korean/rule_69.rs index 2b3a4044..4d9ca861 100644 --- a/libs/braillify/src/rules/korean/rule_69.rs +++ b/libs/braillify/src/rules/korean/rule_69.rs @@ -181,8 +181,26 @@ pub fn is_rule_69_symbol(c: char) -> bool { } fn is_numeric_or_unit_context(ctx: &RuleContext) -> bool { - ctx.prev_char() - .is_some_and(|prev| prev.is_ascii_digit() || matches!(prev, '/' | 'μ')) + let mut numeric_start = ctx.index; + while numeric_start > 0 + && (ctx.word_chars[numeric_start - 1].is_ascii_digit() + || matches!(ctx.word_chars[numeric_start - 1], ',' | '.')) + { + numeric_start -= 1; + } + let compact_numeric_prefix = numeric_start < ctx.index + && ctx.word_chars[numeric_start..ctx.index] + .iter() + .any(char::is_ascii_digit) + && numeric_start + .checked_sub(1) + .and_then(|index| ctx.word_chars.get(index)) + .is_none_or(|previous| !previous.is_ascii_alphabetic()); + + compact_numeric_prefix + || ctx + .prev_char() + .is_some_and(|prev| matches!(prev, '/' | 'μ')) || ctx.prev_word.chars().next().is_some() && ctx .prev_word From 7119ccff3feea2b25143a40237d2b6fc6c7b25a2 Mon Sep 17 00:00:00 2001 From: devfive Date: Tue, 1 Sep 2026 11:30:09 +0900 Subject: [PATCH 28/77] Preserve grade 1 across allcaps hyphens Follow UEB 5.7.2 CD-ROM only for pure-uppercase segments with a multi-capital suffix. Keep single-capital, mixed-case, digit-hyphen, and broader corpus shapes on their existing routes. Verified: standard custom summary 5141/5141, analyzer 160/160, clippy, fmt, deterministic report/JSON hashes, NIKL 67138/83528. --- docs/corpus-analysis/NIKL_2025_V1.md | 426 ++++++++++-------- .../braillify/examples/nikl_corpus_analyze.rs | 108 ++++- .../src/rules/korean/rule_english_symbol.rs | 53 ++- 3 files changed, 390 insertions(+), 197 deletions(-) diff --git a/docs/corpus-analysis/NIKL_2025_V1.md b/docs/corpus-analysis/NIKL_2025_V1.md index 1da883e5..db3c9d4e 100644 --- a/docs/corpus-analysis/NIKL_2025_V1.md +++ b/docs/corpus-analysis/NIKL_2025_V1.md @@ -7,9 +7,9 @@ | Metric | Count | |---|---:| | Total | 83528 | -| Exact | 67012 | -| Mismatch | 16516 | -| Exact accuracy | 80.23% | +| Exact | 67138 | +| Mismatch | 16390 | +| Exact accuracy | 80.38% | | Duplicate records | 0 | | Inputs with conflicting references | 0 | @@ -20,19 +20,19 @@ Primary classes are evidence gates, not permissions to change the engine. `imple | Primary class | Count | |---|---:| | `comparison_method` | 286 | -| `corpus_suspect` | 1095 | -| `exact` | 67012 | -| `pending_rule_review` | 14952 | +| `corpus_suspect` | 1097 | +| `exact` | 67138 | +| `pending_rule_review` | 14824 | | `unsupported_character_review` | 183 | | Reproducible reason | Count | |---|---:| -| `exact` | 67012 | -| `foreign_text_rule_review` | 14777 | +| `exact` | 67138 | +| `foreign_text_rule_review` | 14649 | | `nfkc_input_equivalent` | 286 | | `number_rule_review` | 167 | | `punctuation_rule_review` | 8 | -| `rule34_roman_indicator_before_opening_parenthesis` | 1095 | +| `rule34_roman_indicator_before_opening_parenthesis` | 1097 | | `unsupported_character_review` | 183 | ## Pending first-difference cell transitions @@ -41,26 +41,26 @@ This ranking is a diagnostic selector, not an implementation rule. It counts onl | Rank | Expected → actual first cell | Cases | |---:|---|---:| -| 1 | `U+2834 ⠴ -> U+2800 ⠀` | 2145 | -| 2 | `U+2815 ⠕ -> U+2833 ⠳` | 1314 | -| 3 | `U+2830 ⠰ -> U+2820 ⠠` | 837 | -| 4 | `U+2810 ⠐ -> U+2832 ⠲` | 642 | -| 5 | `U+2820 ⠠ -> U+2834 ⠴` | 467 | -| 6 | `U+2826 ⠦ -> U+2834 ⠴` | 457 | +| 1 | `U+2834 ⠴ -> U+2800 ⠀` | 2146 | +| 2 | `U+2815 ⠕ -> U+2833 ⠳` | 1319 | +| 3 | `U+2830 ⠰ -> U+2820 ⠠` | 838 | +| 4 | `U+2810 ⠐ -> U+2832 ⠲` | 646 | +| 5 | `U+2820 ⠠ -> U+2834 ⠴` | 469 | +| 6 | `U+2826 ⠦ -> U+2834 ⠴` | 458 | | 7 | `U+280E ⠎ -> U+280C ⠌` | 444 | -| 8 | `U+2826 ⠦ -> U+2800 ⠀` | 428 | -| 9 | `U+2820 ⠠ -> U+2830 ⠰` | 421 | -| 10 | `U+2808 ⠈ -> U+2832 ⠲` | 410 | -| 11 | `U+2801 ⠁ -> U+281C ⠜` | 393 | -| 12 | `U+2800 ⠀ -> U+2807 ⠇` | 360 | -| 13 | `U+283C ⠼ -> U+2800 ⠀` | 358 | -| 14 | `U+2834 ⠴ -> U+2830 ⠰` | 327 | -| 15 | `U+2811 ⠑ -> U+282B ⠫` | 322 | -| 16 | `U+2832 ⠲ -> U+2838 ⠸` | 275 | -| 17 | `U+2800 ⠀ -> U+2832 ⠲` | 254 | +| 8 | `U+2826 ⠦ -> U+2800 ⠀` | 429 | +| 9 | `U+2808 ⠈ -> U+2832 ⠲` | 411 | +| 10 | `U+2801 ⠁ -> U+281C ⠜` | 393 | +| 11 | `U+2800 ⠀ -> U+2807 ⠇` | 361 | +| 12 | `U+283C ⠼ -> U+2800 ⠀` | 358 | +| 13 | `U+2834 ⠴ -> U+2830 ⠰` | 328 | +| 14 | `U+2811 ⠑ -> U+282B ⠫` | 324 | +| 15 | `U+2832 ⠲ -> U+2838 ⠸` | 276 | +| 16 | `U+2800 ⠀ -> U+2832 ⠲` | 254 | +| 17 | `U+2820 ⠠ -> U+2830 ⠰` | 230 | | 18 | `U+2810 ⠐ -> U+2802 ⠂` | 210 | | 19 | `U+2817 ⠗ -> U+2820 ⠠` | 163 | -| 20 | `U+2820 ⠠ -> U+2809 ⠉` | 155 | +| 20 | `U+2820 ⠠ -> U+2809 ⠉` | 159 | ### `U+2834 ⠴ -> U+2800 ⠀` @@ -246,29 +246,6 @@ This ranking is a diagnostic selector, not an implementation rule. It counts onl - first differing cell (zero-based): 228 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -### `U+2820 ⠠ -> U+2830 ⠰` - -- `sentence_01.json` #54: 에버소울은 출시 전부터 지스타(G-STAR) 2022, 에이지에프(AGF) 2022 등 굵직한 국내 게임 및 서브컬처 행사에 출품해 기대를 모았다. 12월 말에는 사전 예약자 150만 명을 모객했다. - - expected: `⠪⠓⠦⠄⠴⠠⠛⠤⠠⠠⠌⠜⠠⠴⠀⠼⠃⠚⠃⠃⠐⠀⠝⠕` - - actual: `⠪⠓⠦⠄⠴⠠⠛⠤⠰⠠⠠⠌⠜⠠⠴⠀⠼⠃⠚⠃⠃⠐⠀⠝` - - first differing cell (zero-based): 31 - - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_02.json` #68: 조성된 무장애나눔길은 통행이 많은 숲속의집~생태숲학습관 0.73km 구간에 평균 경사도 5% 이하 데크산책로와 황토포장길로 조성됐으며 전국 무장애나눔길 최초로 장애물 없는 생활환경(BF)인증을 추진하고 있다. - - expected: `⠚⠧⠒⠈⠻⠦⠄⠴⠠⠠⠃⠋⠠⠴⠟⠨⠪⠶⠮⠀⠰⠍⠨⠟` - - actual: `⠚⠧⠒⠈⠻⠦⠄⠴⠰⠠⠠⠃⠋⠠⠴⠟⠨⠪⠶⠮⠀⠰⠍⠨` - - first differing cell (zero-based): 190 - - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_03.json` #74: 아울러 제주도개발공사는 국내 생수업계에서는 처음으로 재활용 페트(CR-PET)를 적용한 화학적 재활용 페트 ‘제주삼다수 리본(RE:Born)’을 개발하는 등 소재혁신을 통한 친환경 라인업도 확대하고있다. - - expected: `⠦⠄⠴⠠⠠⠉⠗⠤⠠⠠⠏⠑⠞⠠⠴⠐⠮⠀⠨⠹⠬⠶⠚⠒` - - actual: `⠦⠄⠴⠠⠠⠉⠗⠤⠰⠠⠠⠏⠑⠞⠠⠴⠐⠮⠀⠨⠹⠬⠶⠚` - - first differing cell (zero-based): 70 - - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_04.json` #1164: 3일 서울시에 따르면 서울시와 국토교통부는 이러한 내용을 담은 ‘K-UAM(도심항공교통) 그랜드챌린지 실증운행계획’을 이달 중순 발표한다. UAM은 드론 택시처럼 하늘을 이용한 교통수단이다. - - expected: `⠵⠀⠠⠦⠴⠠⠅⠤⠠⠠⠥⠁⠍⠦⠄⠊⠥⠠⠕⠢⠚⠶⠈⠿` - - actual: `⠵⠀⠠⠦⠴⠠⠅⠤⠰⠠⠠⠥⠁⠍⠦⠄⠊⠥⠠⠕⠢⠚⠶⠈` - - first differing cell (zero-based): 62 - - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` - ### `U+2808 ⠈ -> U+2832 ⠲` - `sentence_01.json` #207: 우리금융지주가 약 7조원의 자본 여력을 등에 업고 인수·합병(M&A) 시장에 출격했다. 벤처캐피탈(VC) 인수를 시작으로 증권사와 보험사까지 인수한다는 계획이다. @@ -292,30 +269,53 @@ This ranking is a diagnostic selector, not an implementation rule. It counts onl - first differing cell (zero-based): 90 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +### `U+2801 ⠁ -> U+281C ⠜` + +- `sentence_01.json` #142: ‘메타버스존’에서는 증강현실(AR)·가상현실(VR)에 적용되는 혁신기술이 소개된다. 글라스를 착용하고 최첨단 3D 센싱모듈이 구현하는 가상현실을 관람객이 직접 체험할 수 있도록 했다. + - expected: `⠠⠕⠂⠦⠄⠴⠠⠠⠁⠗⠠⠴⠐⠆⠫⠇⠶⠚⠡⠠⠕⠂⠦⠄` + - actual: `⠠⠕⠂⠦⠄⠴⠠⠠⠜⠠⠴⠐⠆⠸⠷⠫⠇⠶⠚⠡⠠⠕⠂⠸` + - first differing cell (zero-based): 34 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_02.json` #28: 체납된 세금은 전국 어디서나 은행 현금자동인출기(ATM)를 이용해 고지서 없이 현금과 신용카드로 납부가 가능하며, 자동응답시스템(ARS) 지방세 납부서비스(044-300-7114)를 이용해 가상계좌를 확인해 계좌이체를 하거나 신용카드로 납부할 수도 있다. + - expected: `⠓⠝⠢⠦⠄⠴⠠⠠⠁⠗⠎⠠⠴⠀⠨⠕⠘⠶⠠⠝⠀⠉⠃⠘` + - actual: `⠓⠝⠢⠦⠄⠴⠠⠠⠜⠎⠠⠴⠀⠨⠕⠘⠶⠠⠝⠀⠉⠃⠘⠍` + - first differing cell (zero-based): 129 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_03.json` #52: 종합식품기업 하림이 공룡 모양으로 생긴 동그랑땡 ‘용가리 땡’을 20일 출시했다. 용가리땡에는 특별 제작한 증강현실(AR) 공룡 카드까지 한 장씩 들어 있어 아이들의 많은 관심이 예상된다. + - expected: `⠠⠕⠂⠦⠄⠴⠠⠠⠁⠗⠠⠴⠀⠈⠿⠐⠬⠶⠀⠋⠊⠪⠠⠫` + - actual: `⠠⠕⠂⠦⠄⠴⠠⠠⠜⠠⠴⠀⠈⠿⠐⠬⠶⠀⠋⠊⠪⠠⠫⠨` + - first differing cell (zero-based): 126 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_04.json` #187: 주정차단속 ARS 알림서비스는 기존 불법 주정차 문자 알림서비스를 업그레이드 해 문자와 함께 자동응답서비스(ARS)로도 주정차단속을 알려주는 시스템이다. + - expected: `⠊⠒⠠⠭⠀⠴⠠⠠⠁⠗⠎⠲⠀⠣⠂⠐⠕⠢⠠⠎⠘⠕⠠⠪` + - actual: `⠊⠒⠠⠭⠀⠴⠠⠠⠜⠎⠲⠀⠣⠂⠐⠕⠢⠠⠎⠘⠕⠠⠪⠉` + - first differing cell (zero-based): 14 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` + ## Residual first-difference transitions after localized cohorts This ranking removes only cases whose first difference is inside an existing output-localized cohort. Broad input-only traits are not exclusion masks. The residual table therefore prioritizes new causes without hiding a mismatch merely because an unrelated structure coexists elsewhere in its sentence. | Rank | Expected → actual first cell | Residual cases | |---:|---|---:| -| 1 | `U+2834 ⠴ -> U+2800 ⠀` | 548 | -| 2 | `U+2820 ⠠ -> U+2834 ⠴` | 454 | +| 1 | `U+2834 ⠴ -> U+2800 ⠀` | 549 | +| 2 | `U+2820 ⠠ -> U+2834 ⠴` | 456 | | 3 | `U+2826 ⠦ -> U+2834 ⠴` | 451 | | 4 | `U+280E ⠎ -> U+280C ⠌` | 444 | -| 5 | `U+2808 ⠈ -> U+2832 ⠲` | 408 | -| 6 | `U+2826 ⠦ -> U+2800 ⠀` | 392 | +| 5 | `U+2808 ⠈ -> U+2832 ⠲` | 409 | +| 6 | `U+2826 ⠦ -> U+2800 ⠀` | 393 | | 7 | `U+2801 ⠁ -> U+281C ⠜` | 391 | -| 8 | `U+2834 ⠴ -> U+2830 ⠰` | 327 | -| 9 | `U+2811 ⠑ -> U+282B ⠫` | 321 | -| 10 | `U+2810 ⠐ -> U+2832 ⠲` | 284 | -| 11 | `U+2832 ⠲ -> U+2838 ⠸` | 275 | +| 8 | `U+2834 ⠴ -> U+2830 ⠰` | 328 | +| 9 | `U+2811 ⠑ -> U+282B ⠫` | 323 | +| 10 | `U+2810 ⠐ -> U+2832 ⠲` | 288 | +| 11 | `U+2832 ⠲ -> U+2838 ⠸` | 276 | | 12 | `U+2800 ⠀ -> U+2832 ⠲` | 225 | | 13 | `U+2810 ⠐ -> U+2802 ⠂` | 210 | | 14 | `U+2817 ⠗ -> U+2820 ⠠` | 163 | -| 15 | `U+2820 ⠠ -> U+2809 ⠉` | 155 | +| 15 | `U+2820 ⠠ -> U+2809 ⠉` | 159 | | 16 | `U+283C ⠼ -> U+2800 ⠀` | 153 | | 17 | `U+281B ⠛ -> U+2823 ⠣` | 144 | -| 18 | `U+2826 ⠦ -> U+2810 ⠐` | 139 | +| 18 | `U+2826 ⠦ -> U+2810 ⠐` | 141 | | 19 | `U+2810 ⠐ -> U+2800 ⠀` | 129 | | 20 | `U+2811 ⠑ -> U+283B ⠻` | 100 | @@ -528,10 +528,10 @@ This ranking removes only cases whose first difference is inside an existing out ### Residual `U+2810 ⠐ -> U+2832 ⠲` -- `sentence_01.json` #1011: 앞으로 다주택자도 주택담보대출비율(LTV) 30%까지 규제지역 내 주택담보대출(주담대)을 받을 수 있다. 그간 6억원까지였던 서민·실수요자의 주담대 한도는 사라지고, LTV·총부채원리금상환비율(DSR) 내에서 대출이 가능하다. - - expected: `⠐⠀⠴⠠⠠⠇⠞⠧⠐⠆⠰⠿⠘⠍⠰⠗⠏⠒⠐⠕⠈⠪⠢⠇` - - actual: `⠐⠀⠴⠠⠠⠇⠞⠧⠲⠐⠆⠸⠷⠰⠿⠘⠍⠰⠗⠏⠒⠐⠕⠈` - - first differing cell (zero-based): 168 +- `sentence_01.json` #521: 이 밖에 정 회장은 올해부터 시행하는 새 국제회계기준(IFRS17)·새 지급여력제도(K-ICS·킥스)의 연착륙을 지원하며서 ‘신제도 안정화 협의체’와 같은 소통 채널을 운영하기로 했다. + - expected: `⠠⠅⠤⠠⠠⠊⠉⠎⠐⠆⠋⠕⠁⠠⠪⠠⠴⠺⠀⠡⠰⠣⠁⠐` + - actual: `⠠⠅⠤⠠⠠⠊⠉⠎⠲⠐⠆⠋⠕⠁⠠⠪⠠⠴⠺⠀⠡⠰⠣⠁` + - first differing cell (zero-based): 93 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` - `sentence_02.json` #327: 또한, ‘도가(Dog+Yoga)’ 등 반려동물과 함께 즐길 수 있는 다양한 프로그램이 준비돼 있으며, 참가자 전원에게는 태안산 황토 호박고구마(3kg) 및 요가매트가 제공된다. - expected: `⠫⠦⠄⠴⠠⠙⠕⠛⠐⠖⠠⠽⠕⠛⠁⠠⠴⠴⠄⠀⠊⠪⠶⠀` @@ -555,30 +555,31 @@ These are cross-cutting input-only structural cohorts, not new primary classes a | Cluster | Candidates | Exact | Mismatch | Conflicting-reference cases | |---|---:|---:|---:|---:| -| `allcaps_roman_run_beginning_with_pure_letter_shortform` | 4120 | 2384 | 1736 | 0 | +| `allcaps_roman_run_beginning_with_pure_letter_shortform` | 4120 | 2395 | 1725 | 0 | | `allcaps_roman_run_containing_ou` | 1816 | 55 | 1761 | 0 | -| `compact_numeric_ascii_letter_suffix` | 2975 | 1840 | 1135 | 0 | -| `decimal_point_between_ascii_digits` | 4546 | 3253 | 1293 | 0 | +| `compact_numeric_ascii_letter_suffix` | 2975 | 1842 | 1133 | 0 | +| `decimal_point_between_ascii_digits` | 4546 | 3257 | 1289 | 0 | | `korean_inline_parenthesized_single_arithmetic_operator` | 23 | 17 | 6 | 0 | -| `korean_prefixed_closed_allcaps_parenthetical` | 54492 | 44220 | 10272 | 0 | -| `korean_prefixed_closed_roman_annotation_rule_34_order` | 64382 | 51811 | 12571 | 0 | +| `korean_prefixed_closed_allcaps_parenthetical` | 54492 | 44300 | 10192 | 0 | +| `korean_prefixed_closed_roman_annotation_rule_34_order` | 64382 | 51907 | 12475 | 0 | | `mixed_roman_korean_word_before_uppercase_headword_expansion` | 10 | 0 | 10 | 0 | | `multi_character_allcaps_roman_runs_joined_by_middle_dot` | 97 | 0 | 97 | 0 | +| `pure_allcaps_segment_before_hyphen_and_multi_allcaps_segment_after` | 448 | 126 | 322 | 0 | | `roman_run_immediately_before_attached_middle_dot_boundary` | 577 | 0 | 577 | 0 | | `rule69_ascii_unit_before_terminator_skipping_symbol` | 440 | 335 | 105 | 0 | | `single_capital_followed_by_parenthesized_digits` | 1361 | 0 | 1361 | 0 | -| `standalone_multi_character_uppercase_roman_word` | 62411 | 49394 | 13017 | 0 | +| `standalone_multi_character_uppercase_roman_word` | 62411 | 49518 | 12893 | 0 | | `tight_triangle_mark_immediately_before_korean` | 377 | 0 | 377 | 0 | -| `uppercase_ascii_run_immediately_after_digit_in_roman_sequence` | 1896 | 1078 | 818 | 0 | -| `uppercase_ascii_run_immediately_after_hyphen_in_roman_sequence` | 952 | 32 | 920 | 0 | -| `uppercase_roman_headword_closed_multiword_parenthetical` | 175 | 14 | 161 | 0 | +| `uppercase_ascii_run_immediately_after_digit_in_roman_sequence` | 1896 | 1080 | 816 | 0 | +| `uppercase_ascii_run_immediately_after_hyphen_in_roman_sequence` | 952 | 158 | 794 | 0 | +| `uppercase_roman_headword_closed_multiword_parenthetical` | 175 | 16 | 159 | 0 | | `uppercase_roman_run_followed_by_hyphen_digits` | 571 | 186 | 385 | 0 | ### `allcaps_roman_run_beginning_with_pure_letter_shortform` -Of the 4120 candidates, 1667 are the actual `pending_rule_review` subcluster. The other 2453 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 4120 candidates, 1656 are the actual `pending_rule_review` subcluster. The other 2464 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. -For this output-signature audit, 1736 mismatches were evaluable and 823 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. +For this output-signature audit, 1725 mismatches were evaluable and 823 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. Localized first-difference transitions: @@ -589,7 +590,7 @@ Mismatch primary-class distribution: - `comparison_method`: 11 - `corpus_suspect`: 48 -- `pending_rule_review`: 1667 +- `pending_rule_review`: 1656 - `unsupported_character_review`: 10 Representative `exact` samples: @@ -636,11 +637,11 @@ Representative `localized_mismatch` samples: Representative `mismatch` samples: -- `sentence_01.json` #54: 에버소울은 출시 전부터 지스타(G-STAR) 2022, 에이지에프(AGF) 2022 등 굵직한 국내 게임 및 서브컬처 행사에 출품해 기대를 모았다. 12월 말에는 사전 예약자 150만 명을 모객했다. - - expected: `⠪⠓⠦⠄⠴⠠⠛⠤⠠⠠⠌⠜⠠⠴⠀⠼⠃⠚⠃⠃⠐⠀⠝⠕` - - actual: `⠪⠓⠦⠄⠴⠠⠛⠤⠰⠠⠠⠌⠜⠠⠴⠀⠼⠃⠚⠃⠃⠐⠀⠝` - - first differing cell (zero-based): 31 - - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_01.json` #191: 소프트웨어 측면에서는 스마트 TV 독자 운영체제 웹(web)OS의 진화를 앞세워 맞춤형 고객경험과 CDX(Cross Device eXperience) 경험을 강화한다. + - expected: `⠰⠝⠨⠝⠀⠏⠗⠃⠴⠐⠣⠺⠑⠃⠐⠜⠠⠠⠕⠎⠲⠺⠀⠨` + - actual: `⠰⠝⠨⠝⠀⠏⠗⠃⠦⠄⠴⠺⠑⠃⠠⠴⠰⠠⠠⠕⠎⠲⠺⠀` + - first differing cell (zero-based): 48 + - current primary/reason: `corpus_suspect` / `rule34_roman_indicator_before_opening_parenthesis` - `sentence_02.json` #68: 조성된 무장애나눔길은 통행이 많은 숲속의집~생태숲학습관 0.73km 구간에 평균 경사도 5% 이하 데크산책로와 황토포장길로 조성됐으며 전국 무장애나눔길 최초로 장애물 없는 생활환경(BF)인증을 추진하고 있다. - expected: `⠚⠧⠒⠈⠻⠦⠄⠴⠠⠠⠃⠋⠠⠴⠟⠨⠪⠶⠮⠀⠰⠍⠨⠟` - actual: `⠚⠧⠒⠈⠻⠦⠄⠴⠰⠠⠠⠃⠋⠠⠴⠟⠨⠪⠶⠮⠀⠰⠍⠨` @@ -661,11 +662,11 @@ Representative `mismatch` samples: Of the 1816 candidates, 1675 are the actual `pending_rule_review` subcluster. The other 141 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. -For this output-signature audit, 1761 mismatches were evaluable and 1310 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. +For this output-signature audit, 1761 mismatches were evaluable and 1315 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. Localized first-difference transitions: -- `U+2815 ⠕ -> U+2833 ⠳`: 1309 +- `U+2815 ⠕ -> U+2833 ⠳`: 1314 - `U+2810 ⠐ -> U+283D ⠽`: 1 Mismatch primary-class distribution: @@ -720,9 +721,9 @@ Representative `mismatch` samples: ### `compact_numeric_ascii_letter_suffix` -Of the 2975 candidates, 1067 are the actual `pending_rule_review` subcluster. The other 1908 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 2975 candidates, 1065 are the actual `pending_rule_review` subcluster. The other 1910 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. -For this output-signature audit, 1135 mismatches were evaluable and 252 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. +For this output-signature audit, 1133 mismatches were evaluable and 252 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. Localized first-difference transitions: @@ -736,7 +737,7 @@ Mismatch primary-class distribution: - `comparison_method`: 12 - `corpus_suspect`: 49 -- `pending_rule_review`: 1067 +- `pending_rule_review`: 1065 - `unsupported_character_review`: 7 Representative `exact` samples: @@ -806,9 +807,9 @@ Representative `mismatch` samples: ### `decimal_point_between_ascii_digits` -Of the 4546 candidates, 1237 are the actual `pending_rule_review` subcluster. The other 3309 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 4546 candidates, 1233 are the actual `pending_rule_review` subcluster. The other 3313 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. -For this output-signature audit, 1293 mismatches were evaluable and 262 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. +For this output-signature audit, 1289 mismatches were evaluable and 264 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. Localized first-difference transitions: @@ -822,7 +823,7 @@ Mismatch primary-class distribution: - `comparison_method`: 16 - `corpus_suspect`: 28 -- `pending_rule_review`: 1237 +- `pending_rule_review`: 1233 - `unsupported_character_review`: 12 Representative `exact` samples: @@ -938,13 +939,13 @@ Representative `mismatch` samples: ### `korean_prefixed_closed_allcaps_parenthetical` -Of the 54492 candidates, 8982 are the actual `pending_rule_review` subcluster. The other 45510 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 54492 candidates, 8900 are the actual `pending_rule_review` subcluster. The other 45592 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. Mismatch primary-class distribution: - `comparison_method`: 173 -- `corpus_suspect`: 982 -- `pending_rule_review`: 8982 +- `corpus_suspect`: 984 +- `pending_rule_review`: 8900 - `unsupported_character_review`: 135 Representative `exact` samples: @@ -991,20 +992,20 @@ Representative `mismatch` samples: ### `korean_prefixed_closed_roman_annotation_rule_34_order` -Of the 64382 candidates, 11093 are the actual `pending_rule_review` subcluster. The other 53289 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 64382 candidates, 10995 are the actual `pending_rule_review` subcluster. The other 53387 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. -For this output-signature audit, 12571 mismatches were evaluable and 1099 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. +For this output-signature audit, 12475 mismatches were evaluable and 1101 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. Localized first-difference transitions: -- `U+2834 ⠴ -> U+2826 ⠦`: 1098 +- `U+2834 ⠴ -> U+2826 ⠦`: 1100 - `U+2810 ⠐ -> U+2826 ⠦`: 1 Mismatch primary-class distribution: - `comparison_method`: 223 -- `corpus_suspect`: 1095 -- `pending_rule_review`: 11093 +- `corpus_suspect`: 1097 +- `pending_rule_review`: 10995 - `unsupported_character_review`: 160 Representative `exact` samples: @@ -1131,6 +1132,72 @@ Representative `mismatch` samples: - first differing cell (zero-based): 130 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +### `pure_allcaps_segment_before_hyphen_and_multi_allcaps_segment_after` + +Of the 448 candidates, 312 are the actual `pending_rule_review` subcluster. The other 136 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. + +For this output-signature audit, 322 mismatches were evaluable and 1 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. + +Localized first-difference transitions: + +- `U+2830 ⠰ -> U+2820 ⠠`: 1 + +Mismatch primary-class distribution: + +- `corpus_suspect`: 8 +- `pending_rule_review`: 312 +- `unsupported_character_review`: 2 + +Representative `exact` samples: + +- `sentence_01.json` #54: 에버소울은 출시 전부터 지스타(G-STAR) 2022, 에이지에프(AGF) 2022 등 굵직한 국내 게임 및 서브컬처 행사에 출품해 기대를 모았다. 12월 말에는 사전 예약자 150만 명을 모객했다. + - expected: `⠝⠘⠎⠠⠥⠯⠵⠀⠰⠯⠠⠕⠀⠨⠾⠘⠍⠓⠎⠀⠨⠕⠠⠪` + - actual: `⠝⠘⠎⠠⠥⠯⠵⠀⠰⠯⠠⠕⠀⠨⠾⠘⠍⠓⠎⠀⠨⠕⠠⠪` + - current primary/reason: `exact` / `exact` +- `sentence_02.json` #313: 순천향대(총장 김승우)는 지난 3일 한국국제협력단(KOICA)과 함께 우즈베키스탄의 수도 타슈겐트에서 스타트업 지원센터 ‘U-ENTER(Uzbekistan Entrepreneurship Innovation Center)’ 준공식을 개최했다고 밝혔다. + - expected: `⠠⠛⠰⠾⠚⠜⠶⠊⠗⠦⠄⠰⠿⠨⠶⠀⠈⠕⠢⠠⠪⠶⠍⠠` + - actual: `⠠⠛⠰⠾⠚⠜⠶⠊⠗⠦⠄⠰⠿⠨⠶⠀⠈⠕⠢⠠⠪⠶⠍⠠` + - current primary/reason: `exact` / `exact` +- `sentence_03.json` #1297: 삼성디스플레이의 퀀텀닷-유기발광다이오드(QD-OLED) 제품이 세계 최대 디스플레이 학회인 정보디스플레이학회(SID)로부터 ‘올해의 디스플레이’ 상을 받았다고 삼성디스플레이가 17일 밝혔다. + - expected: `⠇⠢⠠⠻⠊⠕⠠⠪⠙⠮⠐⠝⠕⠺⠀⠋⠏⠒⠓⠎⠢⠊⠄⠤` + - actual: `⠇⠢⠠⠻⠊⠕⠠⠪⠙⠮⠐⠝⠕⠺⠀⠋⠏⠒⠓⠎⠢⠊⠄⠤` + - current primary/reason: `exact` / `exact` +- `sentence_04.json` #1164: 3일 서울시에 따르면 서울시와 국토교통부는 이러한 내용을 담은 ‘K-UAM(도심항공교통) 그랜드챌린지 실증운행계획’을 이달 중순 발표한다. UAM은 드론 택시처럼 하늘을 이용한 교통수단이다. + - expected: `⠼⠉⠕⠂⠀⠠⠎⠯⠠⠕⠝⠀⠠⠊⠐⠪⠑⠡⠀⠠⠎⠯⠠⠕` + - actual: `⠼⠉⠕⠂⠀⠠⠎⠯⠠⠕⠝⠀⠠⠊⠐⠪⠑⠡⠀⠠⠎⠯⠠⠕` + - current primary/reason: `exact` / `exact` + +Representative `localized_mismatch` samples: + +- `sentence_02.json` #15537: 한국투자증권은 자체 개발한 자산운용전략 시스템(K-ALM)을 기반으로 개별기업의 특성을 고려한 맞춤형 적립금운용 컨설팅(K-IPS)을 제공하는 등 확정급여(DB)형 퇴직연금의 자산운용 성과를 높여왔다. + - expected: `⠝⠢⠦⠄⠴⠠⠅⠤⠰⠠⠠⠁⠇⠍⠠⠴⠮⠀⠈⠕⠘⠒⠪⠐` + - actual: `⠝⠢⠦⠄⠴⠠⠅⠤⠠⠠⠁⠇⠍⠠⠴⠮⠀⠈⠕⠘⠒⠪⠐⠥` + - first differing cell (zero-based): 52 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` + +Representative `mismatch` samples: + +- `sentence_01.json` #149: 탠덤 OLED를 탄성있는 플라스틱 기판에 결합한 것이 LG디스플레이의 차량용 플라스틱(P)-OLED다. 차량용 P-OLED는 LCD 대비 소비전력을 60% 줄이고, 무게는 80%나 저감해 전기차 시대에도 적합한 디스플레이라는 평가다. + - expected: `⠮⠐⠣⠠⠪⠓⠕⠁⠴⠐⠣⠠⠏⠐⠜⠤⠠⠠⠕⠇⠫⠲⠊⠲` + - actual: `⠮⠐⠣⠠⠪⠓⠕⠁⠦⠄⠴⠠⠏⠠⠴⠤⠰⠠⠠⠕⠇⠫⠲⠊` + - first differing cell (zero-based): 87 + - current primary/reason: `corpus_suspect` / `rule34_roman_indicator_before_opening_parenthesis` +- `sentence_02.json` #344: 한편 이장우 대전시장은 이틀간의 대만 방문을 마무리하고 싱가포르로 이동해 18일까지 Merck(머크) 사, 국립싱가포르대학 바이오연구단, A-STAR 및 바이오폴리스 등 바이오산업 관련 해외 기업과 연구기관을 방문할 예정이다. + - expected: `⠥⠡⠈⠍⠊⠒⠐⠀⠴⠠⠁⠤⠠⠠⠌⠜⠲⠀⠑⠕⠆⠀⠘⠣` + - actual: `⠥⠡⠈⠍⠊⠒⠐⠀⠀⠠⠁⠔⠠⠠⠎⠞⠁⠗⠀⠀⠑⠕⠆⠀` + - first differing cell (zero-based): 122 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_03.json` #74: 아울러 제주도개발공사는 국내 생수업계에서는 처음으로 재활용 페트(CR-PET)를 적용한 화학적 재활용 페트 ‘제주삼다수 리본(RE:Born)’을 개발하는 등 소재혁신을 통한 친환경 라인업도 확대하고있다. + - expected: `⠚⠧⠁⠊⠗⠚⠈⠥⠕⠌⠊⠲` + - actual: `⠚⠧⠁⠊⠗⠚⠈⠥⠀⠕⠌⠊⠲` + - first differing cell (zero-based): 194 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_04.json` #883: 그룹 방탄소년단(BTS) 슈가의 첫 공식 솔로 음반 ‘D-데이’(D-DAY)가 발매 당일 한터차트 기준 107만장이 넘게 팔려나가며 첫날 판매량으로는 K팝 솔로 가수 최고 기록을 경신했다. + - expected: `⠘⠒⠀⠠⠦⠴⠠⠙⠲⠤⠊⠝⠕⠴⠄⠦⠄⠴⠠⠙⠤⠠⠠⠐` + - actual: `⠘⠒⠀⠠⠦⠴⠠⠙⠘⠔⠸⠷⠊⠝⠕⠸⠾⠴⠄⠐⠣⠰⠠⠙` + - first differing cell (zero-based): 58 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` + ### `roman_run_immediately_before_attached_middle_dot_boundary` Of the 577 candidates, 565 are the actual `pending_rule_review` subcluster. The other 12 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. @@ -1321,13 +1388,13 @@ Representative `mismatch` samples: ### `standalone_multi_character_uppercase_roman_word` -Of the 62411 candidates, 11571 are the actual `pending_rule_review` subcluster. The other 50840 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 62411 candidates, 11445 are the actual `pending_rule_review` subcluster. The other 50966 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. Mismatch primary-class distribution: - `comparison_method`: 207 -- `corpus_suspect`: 1082 -- `pending_rule_review`: 11571 +- `corpus_suspect`: 1084 +- `pending_rule_review`: 11445 - `unsupported_character_review`: 157 Representative `exact` samples: @@ -1376,11 +1443,11 @@ Representative `mismatch` samples: Of the 377 candidates, 376 are the actual `pending_rule_review` subcluster. The other 1 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. -For this output-signature audit, 377 mismatches were evaluable and 330 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. +For this output-signature audit, 377 mismatches were evaluable and 331 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. Localized first-difference transitions: -- `U+2800 ⠀ -> U+2807 ⠇`: 327 +- `U+2800 ⠀ -> U+2807 ⠇`: 328 - `U+280A ⠊ -> U+2807 ⠇`: 1 - `U+2818 ⠘ -> U+2807 ⠇`: 1 - `U+2829 ⠩ -> U+2807 ⠇`: 1 @@ -1415,9 +1482,9 @@ Representative `mismatch` samples: ### `uppercase_ascii_run_immediately_after_digit_in_roman_sequence` -Of the 1896 candidates, 764 are the actual `pending_rule_review` subcluster. The other 1132 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 1896 candidates, 762 are the actual `pending_rule_review` subcluster. The other 1134 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. -For this output-signature audit, 818 mismatches were evaluable and 1 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. +For this output-signature audit, 816 mismatches were evaluable and 1 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. Localized first-difference transitions: @@ -1427,7 +1494,7 @@ Mismatch primary-class distribution: - `comparison_method`: 9 - `corpus_suspect`: 38 -- `pending_rule_review`: 764 +- `pending_rule_review`: 762 - `unsupported_character_review`: 7 Representative `exact` samples: @@ -1482,61 +1549,61 @@ Representative `mismatch` samples: ### `uppercase_ascii_run_immediately_after_hyphen_in_roman_sequence` -Of the 952 candidates, 901 are the actual `pending_rule_review` subcluster. The other 51 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 952 candidates, 773 are the actual `pending_rule_review` subcluster. The other 179 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. -For this output-signature audit, 920 mismatches were evaluable and 314 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. +For this output-signature audit, 794 mismatches were evaluable and 160 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. Localized first-difference transitions: -- `U+2820 ⠠ -> U+2830 ⠰`: 312 -- `U+2830 ⠰ -> U+2820 ⠠`: 2 +- `U+2820 ⠠ -> U+2830 ⠰`: 157 +- `U+2830 ⠰ -> U+2820 ⠠`: 3 Mismatch primary-class distribution: -- `corpus_suspect`: 10 -- `pending_rule_review`: 901 +- `corpus_suspect`: 12 +- `pending_rule_review`: 773 - `unsupported_character_review`: 9 Representative `exact` samples: -- `sentence_01.json` #3590: SK매직이 지난해 7월 도입한 ‘차세대 IT 전산 시스템’에 대해 과학기술정보통신부 산하 한국인터넷진흥원(KISA)으로부터 ‘정보보호 및 개인 정보보호 관리체계(ISMS-P) 인증’을 획득했다. - - expected: `⠴⠠⠠⠎⠅⠲⠑⠗⠨⠕⠁⠕⠀⠨⠕⠉⠒⠚⠗⠀⠼⠛⠏⠂` - - actual: `⠴⠠⠠⠎⠅⠲⠑⠗⠨⠕⠁⠕⠀⠨⠕⠉⠒⠚⠗⠀⠼⠛⠏⠂` +- `sentence_01.json` #54: 에버소울은 출시 전부터 지스타(G-STAR) 2022, 에이지에프(AGF) 2022 등 굵직한 국내 게임 및 서브컬처 행사에 출품해 기대를 모았다. 12월 말에는 사전 예약자 150만 명을 모객했다. + - expected: `⠝⠘⠎⠠⠥⠯⠵⠀⠰⠯⠠⠕⠀⠨⠾⠘⠍⠓⠎⠀⠨⠕⠠⠪` + - actual: `⠝⠘⠎⠠⠥⠯⠵⠀⠰⠯⠠⠕⠀⠨⠾⠘⠍⠓⠎⠀⠨⠕⠠⠪` - current primary/reason: `exact` / `exact` -- `sentence_02.json` #1035: 사업단은 ‘어라운드유(Around-U)’ 중 U라는 글자에 당신(You)과 울산(Ulsan)의 의미를 담아 학생들이 울산지역 문제를 주도적으로 발굴해 다양한 부문과 공유·협업하면서 문제를 해결하는 기회를 제공했다. - - expected: `⠇⠎⠃⠊⠒⠵⠀⠠⠦⠎⠐⠣⠛⠊⠪⠩⠦⠄⠴⠠⠜⠨⠙⠤` - - actual: `⠇⠎⠃⠊⠒⠵⠀⠠⠦⠎⠐⠣⠛⠊⠪⠩⠦⠄⠴⠠⠜⠨⠙⠤` +- `sentence_02.json` #313: 순천향대(총장 김승우)는 지난 3일 한국국제협력단(KOICA)과 함께 우즈베키스탄의 수도 타슈겐트에서 스타트업 지원센터 ‘U-ENTER(Uzbekistan Entrepreneurship Innovation Center)’ 준공식을 개최했다고 밝혔다. + - expected: `⠠⠛⠰⠾⠚⠜⠶⠊⠗⠦⠄⠰⠿⠨⠶⠀⠈⠕⠢⠠⠪⠶⠍⠠` + - actual: `⠠⠛⠰⠾⠚⠜⠶⠊⠗⠦⠄⠰⠿⠨⠶⠀⠈⠕⠢⠠⠪⠶⠍⠠` - current primary/reason: `exact` / `exact` -- `sentence_03.json` #4701: 또한 자비스앤빌런즈는 삼쩜삼 서비스 시작 2년 만에 세무 플랫폼 최초로 정보보호관리체계(ISMS) 인증을 받았다. 하반기에도 정보보호·개인정보보호 관리체계(ISMS-P) 인증을 준비하고 있다. - - expected: `⠠⠊⠥⠚⠒⠀⠨⠘⠕⠠⠪⠗⠒⠘⠕⠂⠐⠾⠨⠪⠉⠵⠀⠇` - - actual: `⠠⠊⠥⠚⠒⠀⠨⠘⠕⠠⠪⠗⠒⠘⠕⠂⠐⠾⠨⠪⠉⠵⠀⠇` +- `sentence_03.json` #1297: 삼성디스플레이의 퀀텀닷-유기발광다이오드(QD-OLED) 제품이 세계 최대 디스플레이 학회인 정보디스플레이학회(SID)로부터 ‘올해의 디스플레이’ 상을 받았다고 삼성디스플레이가 17일 밝혔다. + - expected: `⠇⠢⠠⠻⠊⠕⠠⠪⠙⠮⠐⠝⠕⠺⠀⠋⠏⠒⠓⠎⠢⠊⠄⠤` + - actual: `⠇⠢⠠⠻⠊⠕⠠⠪⠙⠮⠐⠝⠕⠺⠀⠋⠏⠒⠓⠎⠢⠊⠄⠤` - current primary/reason: `exact` / `exact` -- `sentence_04.json` #3338: ‘분당 흉기 난동’ 사건의 피의자인 최원종(22)에 대한 사이코패스 진단검사(PCL-R)를 실시한 경찰이 측정 자체가 불가능하다는 결론을 냈다. - - expected: `⠠⠦⠘⠛⠊⠶⠀⠚⠩⠶⠈⠕⠀⠉⠒⠊⠿⠴⠄⠀⠇⠈⠾⠺` - - actual: `⠠⠦⠘⠛⠊⠶⠀⠚⠩⠶⠈⠕⠀⠉⠒⠊⠿⠴⠄⠀⠇⠈⠾⠺` +- `sentence_04.json` #1164: 3일 서울시에 따르면 서울시와 국토교통부는 이러한 내용을 담은 ‘K-UAM(도심항공교통) 그랜드챌린지 실증운행계획’을 이달 중순 발표한다. UAM은 드론 택시처럼 하늘을 이용한 교통수단이다. + - expected: `⠼⠉⠕⠂⠀⠠⠎⠯⠠⠕⠝⠀⠠⠊⠐⠪⠑⠡⠀⠠⠎⠯⠠⠕` + - actual: `⠼⠉⠕⠂⠀⠠⠎⠯⠠⠕⠝⠀⠠⠊⠐⠪⠑⠡⠀⠠⠎⠯⠠⠕` - current primary/reason: `exact` / `exact` Representative `localized_mismatch` samples: -- `sentence_01.json` #54: 에버소울은 출시 전부터 지스타(G-STAR) 2022, 에이지에프(AGF) 2022 등 굵직한 국내 게임 및 서브컬처 행사에 출품해 기대를 모았다. 12월 말에는 사전 예약자 150만 명을 모객했다. - - expected: `⠪⠓⠦⠄⠴⠠⠛⠤⠠⠠⠌⠜⠠⠴⠀⠼⠃⠚⠃⠃⠐⠀⠝⠕` - - actual: `⠪⠓⠦⠄⠴⠠⠛⠤⠰⠠⠠⠌⠜⠠⠴⠀⠼⠃⠚⠃⠃⠐⠀⠝` - - first differing cell (zero-based): 31 +- `sentence_01.json` #342: MBX DAY는 쟁글(Xangle) 블록체인 파운데이션 위크의 일환으로 진행된 프로그램으로 마브렉스의 게임파이(Game-Fi) 전략 및 신규 프로젝트 ‘메타월드: 모두의마블’ 소개, 바이낸스와의 패널 토론 등 다양한 세션이 열렸다. + - expected: `⠄⠴⠠⠛⠁⠍⠑⠤⠠⠋⠊⠠⠴⠀⠨⠾⠐⠜⠁⠀⠑⠕⠆⠀` + - actual: `⠄⠴⠠⠛⠁⠍⠑⠤⠰⠠⠋⠊⠠⠴⠀⠨⠾⠐⠜⠁⠀⠑⠕⠆` + - first differing cell (zero-based): 113 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_02.json` #313: 순천향대(총장 김승우)는 지난 3일 한국국제협력단(KOICA)과 함께 우즈베키스탄의 수도 타슈겐트에서 스타트업 지원센터 ‘U-ENTER(Uzbekistan Entrepreneurship Innovation Center)’ 준공식을 개최했다고 밝혔다. - - expected: `⠎⠀⠠⠦⠴⠠⠥⠤⠠⠠⠢⠞⠻⠐⠣⠠⠥⠵⠃⠑⠅⠊⠌⠁` - - actual: `⠎⠀⠠⠦⠴⠠⠥⠤⠰⠠⠠⠢⠞⠻⠐⠣⠠⠥⠵⠃⠑⠅⠊⠌` - - first differing cell (zero-based): 131 +- `sentence_02.json` #639: 충남대학교(총장 이진숙)가 대학이 보유한 기술과 인력을 지역 산업체에 맞춤형으로 제공해 기술사업화 플랫폼을 구축하는 ‘2023년도 지역산업 연계 대학 Open-Lab(오픈랩) 육성지원 사업’에 최종 선정됐다. + - expected: `⠁⠀⠴⠠⠕⠏⠢⠤⠠⠇⠁⠃⠦⠄⠥⠙⠵⠐⠗⠃⠠⠴⠀⠩` + - actual: `⠁⠀⠴⠠⠕⠏⠢⠤⠰⠠⠇⠁⠃⠦⠄⠥⠙⠵⠐⠗⠃⠠⠴⠀` + - first differing cell (zero-based): 150 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_03.json` #74: 아울러 제주도개발공사는 국내 생수업계에서는 처음으로 재활용 페트(CR-PET)를 적용한 화학적 재활용 페트 ‘제주삼다수 리본(RE:Born)’을 개발하는 등 소재혁신을 통한 친환경 라인업도 확대하고있다. - - expected: `⠦⠄⠴⠠⠠⠉⠗⠤⠠⠠⠏⠑⠞⠠⠴⠐⠮⠀⠨⠹⠬⠶⠚⠒` - - actual: `⠦⠄⠴⠠⠠⠉⠗⠤⠰⠠⠠⠏⠑⠞⠠⠴⠐⠮⠀⠨⠹⠬⠶⠚` - - first differing cell (zero-based): 70 +- `sentence_03.json` #669: 환자들은 수술 후 컴퓨터 단층촬영(CT)이나 자기공명영상(MRI)을 통해 검사를 진행했다. 이후 추적관찰에선 X선 촬영(X-Ray)을 활용했고 시각통증척도(VAS)를 비롯한 설문조사도 함께 실시했다. + - expected: `⠂⠻⠦⠄⠴⠠⠭⠤⠠⠗⠁⠽⠠⠴⠮⠀⠚⠧⠂⠬⠶⠚⠗⠌` + - actual: `⠂⠻⠦⠄⠴⠠⠭⠤⠰⠠⠗⠁⠽⠠⠴⠮⠀⠚⠧⠂⠬⠶⠚⠗` + - first differing cell (zero-based): 125 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_04.json` #1164: 3일 서울시에 따르면 서울시와 국토교통부는 이러한 내용을 담은 ‘K-UAM(도심항공교통) 그랜드챌린지 실증운행계획’을 이달 중순 발표한다. UAM은 드론 택시처럼 하늘을 이용한 교통수단이다. - - expected: `⠵⠀⠠⠦⠴⠠⠅⠤⠠⠠⠥⠁⠍⠦⠄⠊⠥⠠⠕⠢⠚⠶⠈⠿` - - actual: `⠵⠀⠠⠦⠴⠠⠅⠤⠰⠠⠠⠥⠁⠍⠦⠄⠊⠥⠠⠕⠢⠚⠶⠈` - - first differing cell (zero-based): 62 +- `sentence_04.json` #1793: 선다이오드(Sundiode)는 ‘SID 디스플레이 위크 2023’의 아이-존(i-Zone) 전시회에서 ‘수직적층형 마이크로 LED’ 기술로 최우수 시제품상(Best Prototype Award)을 수상했다고 30일 밝혔다. + - expected: `⠤⠨⠷⠦⠄⠴⠊⠤⠠⠵⠐⠕⠠⠴⠀⠨⠾⠠⠕⠚⠽⠝⠠⠎` + - actual: `⠤⠨⠷⠦⠄⠴⠊⠤⠰⠠⠵⠐⠕⠠⠴⠀⠨⠾⠠⠕⠚⠽⠝⠠` + - first differing cell (zero-based): 69 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` Representative `mismatch` samples: @@ -1546,15 +1613,15 @@ Representative `mismatch` samples: - actual: `⠠⠠⠊⠎⠍⠎⠤⠰⠏⠲⠉⠵⠀⠈⠥⠈⠗⠁⠀⠨⠻⠘⠥⠘` - first differing cell (zero-based): 108 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_02.json` #313: 순천향대(총장 김승우)는 지난 3일 한국국제협력단(KOICA)과 함께 우즈베키스탄의 수도 타슈겐트에서 스타트업 지원센터 ‘U-ENTER(Uzbekistan Entrepreneurship Innovation Center)’ 준공식을 개최했다고 밝혔다. - - expected: `⠎⠀⠠⠦⠴⠠⠥⠤⠠⠠⠢⠞⠻⠐⠣⠠⠥⠵⠃⠑⠅⠊⠌⠁` - - actual: `⠎⠀⠠⠦⠴⠠⠥⠤⠰⠠⠠⠢⠞⠻⠐⠣⠠⠥⠵⠃⠑⠅⠊⠌` - - first differing cell (zero-based): 131 +- `sentence_02.json` #344: 한편 이장우 대전시장은 이틀간의 대만 방문을 마무리하고 싱가포르로 이동해 18일까지 Merck(머크) 사, 국립싱가포르대학 바이오연구단, A-STAR 및 바이오폴리스 등 바이오산업 관련 해외 기업과 연구기관을 방문할 예정이다. + - expected: `⠥⠡⠈⠍⠊⠒⠐⠀⠴⠠⠁⠤⠠⠠⠌⠜⠲⠀⠑⠕⠆⠀⠘⠣` + - actual: `⠥⠡⠈⠍⠊⠒⠐⠀⠀⠠⠁⠔⠠⠠⠎⠞⠁⠗⠀⠀⠑⠕⠆⠀` + - first differing cell (zero-based): 122 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` - `sentence_03.json` #74: 아울러 제주도개발공사는 국내 생수업계에서는 처음으로 재활용 페트(CR-PET)를 적용한 화학적 재활용 페트 ‘제주삼다수 리본(RE:Born)’을 개발하는 등 소재혁신을 통한 친환경 라인업도 확대하고있다. - - expected: `⠦⠄⠴⠠⠠⠉⠗⠤⠠⠠⠏⠑⠞⠠⠴⠐⠮⠀⠨⠹⠬⠶⠚⠒` - - actual: `⠦⠄⠴⠠⠠⠉⠗⠤⠰⠠⠠⠏⠑⠞⠠⠴⠐⠮⠀⠨⠹⠬⠶⠚` - - first differing cell (zero-based): 70 + - expected: `⠚⠧⠁⠊⠗⠚⠈⠥⠕⠌⠊⠲` + - actual: `⠚⠧⠁⠊⠗⠚⠈⠥⠀⠕⠌⠊⠲` + - first differing cell (zero-based): 194 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` - `sentence_04.json` #883: 그룹 방탄소년단(BTS) 슈가의 첫 공식 솔로 음반 ‘D-데이’(D-DAY)가 발매 당일 한터차트 기준 107만장이 넘게 팔려나가며 첫날 판매량으로는 K팝 솔로 가수 최고 기록을 경신했다. - expected: `⠘⠒⠀⠠⠦⠴⠠⠙⠲⠤⠊⠝⠕⠴⠄⠦⠄⠴⠠⠙⠤⠠⠠⠐` @@ -1564,13 +1631,13 @@ Representative `mismatch` samples: ### `uppercase_roman_headword_closed_multiword_parenthetical` -Of the 175 candidates, 150 are the actual `pending_rule_review` subcluster. The other 25 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 175 candidates, 148 are the actual `pending_rule_review` subcluster. The other 27 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. Mismatch primary-class distribution: - `comparison_method`: 1 - `corpus_suspect`: 4 -- `pending_rule_review`: 150 +- `pending_rule_review`: 148 - `unsupported_character_review`: 6 Representative `exact` samples: @@ -1579,9 +1646,9 @@ Representative `exact` samples: - expected: `⠉⠝⠁⠠⠵⠦⠄⠊⠗⠙⠬⠀⠕⠨⠻⠚⠾⠠⠴⠵⠀⠈⠝⠕` - actual: `⠉⠝⠁⠠⠵⠦⠄⠊⠗⠙⠬⠀⠕⠨⠻⠚⠾⠠⠴⠵⠀⠈⠝⠕` - current primary/reason: `exact` / `exact` -- `sentence_02.json` #1700: 태광그룹 섬유·석유화학 계열사인 태광산업은 국제품질표준 자동차 품질경영시스템 인증인 ‘IATF(International Automotive Task Force) 16949’를 영국왕립표준협회(BSI) 인증원으로부터 획득했다고 13일 밝혔다. - - expected: `⠓⠗⠈⠧⠶⠈⠪⠐⠍⠃⠀⠠⠎⠢⠩⠐⠆⠠⠹⠩⠚⠧⠚⠁` - - actual: `⠓⠗⠈⠧⠶⠈⠪⠐⠍⠃⠀⠠⠎⠢⠩⠐⠆⠠⠹⠩⠚⠧⠚⠁` +- `sentence_02.json` #313: 순천향대(총장 김승우)는 지난 3일 한국국제협력단(KOICA)과 함께 우즈베키스탄의 수도 타슈겐트에서 스타트업 지원센터 ‘U-ENTER(Uzbekistan Entrepreneurship Innovation Center)’ 준공식을 개최했다고 밝혔다. + - expected: `⠠⠛⠰⠾⠚⠜⠶⠊⠗⠦⠄⠰⠿⠨⠶⠀⠈⠕⠢⠠⠪⠶⠍⠠` + - actual: `⠠⠛⠰⠾⠚⠜⠶⠊⠗⠦⠄⠰⠿⠨⠶⠀⠈⠕⠢⠠⠪⠶⠍⠠` - current primary/reason: `exact` / `exact` - `sentence_03.json` #20370: 포르테나는 지난 20일 1theK(원더케이) 공식 유튜브 채널의 ‘MR은 거들 뿐’ 콘텐츠를 통해 첫 번째 미니앨범 ‘KINGDOM’의 타이틀곡 ‘KINGDOM(Moonlight Tears)’라이브 영상을 공개했다. - expected: `⠙⠥⠐⠪⠓⠝⠉⠉⠵⠀⠨⠕⠉⠒⠀⠼⠃⠚⠕⠂⠀⠼⠁⠴` @@ -1599,10 +1666,10 @@ Representative `mismatch` samples: - actual: `⠁⠞⠞⠻⠠⠴⠧⠀⠀⠠⠠⠓⠉⠁⠦⠠⠓⠕⠍⠑⠀⠴⠠⠒` - first differing cell (zero-based): 37 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_02.json` #313: 순천향대(총장 김승우)는 지난 3일 한국국제협력단(KOICA)과 함께 우즈베키스탄의 수도 타슈겐트에서 스타트업 지원센터 ‘U-ENTER(Uzbekistan Entrepreneurship Innovation Center)’ 준공식을 개최했다고 밝혔다. - - expected: `⠎⠀⠠⠦⠴⠠⠥⠤⠠⠠⠢⠞⠻⠐⠣⠠⠥⠵⠃⠑⠅⠊⠌⠁` - - actual: `⠎⠀⠠⠦⠴⠠⠥⠤⠰⠠⠠⠢⠞⠻⠐⠣⠠⠥⠵⠃⠑⠅⠊⠌` - - first differing cell (zero-based): 131 +- `sentence_02.json` #733: 아산시는 6일, 자매도시인 말레이시아 현지 최대 신선 과실류 수입업체인 CTG(Chop Tong Guan)와 농특산물 수출 확대를 위한 업무협약(MOU)을 체결했다. + - expected: `⠕⠃⠎⠃⠰⠝⠟⠀⠴⠠⠠⠉⠞⠛⠐⠣⠠⠡⠕⠏⠀⠠⠞⠰` + - actual: `⠕⠃⠎⠃⠰⠝⠟⠀⠀⠠⠠⠉⠞⠛⠦⠠⠉⠓⠕⠏⠀⠴⠠⠞` + - first differing cell (zero-based): 65 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` - `sentence_03.json` #3330: 앞서 양사는 지난 3월 그린수소·암모니아의 원활한 생산·공급·활용을 위한 특수목적법인(SPC) 알 파탄 엘텍유브이씨 그린에너지 LLC(AL FATTAN LTechUVC Green Energy LLC)를 설립한 바 있다. - expected: `⠐⠟⠝⠉⠎⠨⠕⠀⠴⠰⠠⠠⠇⠇⠉⠐⠣⠰⠠⠠⠁⠇⠀⠠` @@ -1676,13 +1743,14 @@ Representative `mismatch` samples: ## UEB grade-1 first-difference cohorts -These three cohorts are defined by both an input boundary and the sentence's actual first-difference transition. They therefore do not claim every mismatch merely coexisting with an ASCII run. Candidate, exact, mismatch, and primary-class counts remain cross-cutting controls; only the reported target transition is the localized residual under review. The reverse transition is retained separately rather than folded into the target. +These cohorts are defined by both an input boundary and the sentence's actual first-difference transition. They therefore do not claim every mismatch merely coexisting with an ASCII run. Candidate, exact, mismatch, and primary-class counts remain cross-cutting controls; only the reported target transition is the localized residual under review. The reverse transition is retained separately rather than folded into the target. | Cohort | Candidates | Exact controls | Mismatch | Target localized | Reverse | |---|---:|---:|---:|---:|---:| -| `allcaps_roman_run_beginning_with_pure_letter_shortform` | 4120 | 2384 | 1736 | 781 | 42 | -| `uppercase_ascii_run_immediately_after_digit_in_roman_sequence` | 1896 | 1078 | 818 | 0 | 1 | -| `uppercase_ascii_run_immediately_after_hyphen_in_roman_sequence` | 952 | 32 | 920 | 312 | 2 | +| `allcaps_roman_run_beginning_with_pure_letter_shortform` | 4120 | 2395 | 1725 | 781 | 42 | +| `uppercase_ascii_run_immediately_after_digit_in_roman_sequence` | 1896 | 1080 | 816 | 0 | 1 | +| `uppercase_ascii_run_immediately_after_hyphen_in_roman_sequence` | 952 | 158 | 794 | 157 | 3 | +| `pure_allcaps_segment_before_hyphen_and_multi_allcaps_segment_after` | 448 | 126 | 322 | 0 | 1 | ### All-caps shortform prefix at an attached Roman entry @@ -1723,14 +1791,14 @@ Same-surface controls demonstrate why primary classes must not be changed by coh ### Uppercase immediately after a digit -UEB rule 5.6.1 says the numeric indicator establishes grade-1 mode, and rule 5.6.2 ends that mode at a space, hyphen, dash, or grade-1 terminator; a capitalization indicator is not a terminator. Korean rule 35 likewise keeps Roman letters and an adjacent number in one Roman section. The PDF's printed `3b`, `3B`, and `3m` examples distinguish the three following-letter classes: lowercase `a`-`j` retains `⠰` because its cells are numeric, a capital uses its capitalization indicator, and lowercase `k`-`z` needs no extra indicator. `Braille4All`, `M4G`, and `W1N` independently confirm the capital boundary inside longer alphanumeric strings. Before the engine change this cohort contained 330 localized `⠠ -> ⠰` cases. A blanket digit-to-letter removal reached 67,000/83,528 (+317) but was rejected: retaining `⠰` only for lowercase `a`-`j` recovers 10 exact cases and raises the result to 67,010. The wrapper control also exposes a separate routing boundary: a numeric run already preceded by an ASCII letter is part of the Roman identifier, not a fresh rule-69 compact unit. Preserving the rule-69 path for genuinely numeric-leading units while excluding that identifier boundary adds 2 more exact cases, for a final 67,012 (+329). The uppercase cohort moves from 756 exact / 1,140 mismatch / 330 target-localized / 1 reverse to 1,078 exact / 818 mismatch / 0 target-localized / 1 reverse. The remaining non-exact members are not attributed to the removed uppercase transition: their sentence-level first difference may lie in another structure and remains under its existing primary class. This numeric state change remains separate from both the complete-shortform guard and the still-unimplemented hyphen continuation cohort. +UEB rule 5.6.1 says the numeric indicator establishes grade-1 mode, and rule 5.6.2 ends that mode at a space, hyphen, dash, or grade-1 terminator; a capitalization indicator is not a terminator. Korean rule 35 likewise keeps Roman letters and an adjacent number in one Roman section. The PDF's printed `3b`, `3B`, and `3m` examples distinguish the three following-letter classes: lowercase `a`-`j` retains `⠰` because its cells are numeric, a capital uses its capitalization indicator, and lowercase `k`-`z` needs no extra indicator. `Braille4All`, `M4G`, and `W1N` independently confirm the capital boundary inside longer alphanumeric strings. Before the engine change this cohort contained 330 localized `⠠ -> ⠰` cases. A blanket digit-to-letter removal reached 67,000/83,528 (+317) but was rejected: retaining `⠰` only for lowercase `a`-`j` recovers 10 exact cases and raises the result to 67,010. The wrapper control also exposes a separate routing boundary: a numeric run already preceded by an ASCII letter is part of the Roman identifier, not a fresh rule-69 compact unit. Preserving the rule-69 path for genuinely numeric-leading units while excluding that identifier boundary adds 2 more exact cases, for a final 67,012 (+329). The uppercase cohort moves from 756 exact / 1,140 mismatch / 330 target-localized / 1 reverse to 1,078 exact / 818 mismatch / 0 target-localized / 1 reverse. The remaining non-exact members are not attributed to the removed uppercase transition: their sentence-level first difference may lie in another structure and remains under its existing primary class. This numeric state change remains separate from both the complete-shortform guard and the hyphen continuation boundary below. ### Uppercase immediately after a hyphen -UEB rule 5.7.2 prints `CD-ROM` with one grade-1 indicator before `CD` and no second grade-1 indicator after the hyphen. Korean rule 29 similarly uses one Roman span for consecutive Roman text. The 312 localized `⠠ -> ⠰` cases are therefore tracked as a distinct hyphen-continuation candidate. The three reverse cases, only 31 exact controls in the broad cohort, and surfaces whose first difference lies elsewhere remain controls. This route is judged separately from digit-hyphen forms such as `F-35`, which this detector excludes, and separately from the shortform guard that legitimately precedes `CD` in `CD-ROM`. +UEB rule 5.7.2 prints `CD-ROM` with one grade-1 indicator before `CD` and no second grade-1 indicator after the hyphen. Korean rule 29 similarly uses one Roman span for consecutive Roman text. Before the engine change, the broad diagnostic contained 952 candidates / 32 exact / 920 mismatch, with 312 localized `⠠ -> ⠰` and 2 reverse transitions. A blanket uppercase-suffix removal reached 67,222 (+210) but made the broad cohort's single-capital controls such as `Around-U`, `DALL-E`, `ISMS-P`, and `USB-C` non-exact; it was rejected. Requiring only a two-letter uppercase suffix reached 67,162 (+150) but regressed the mixed-prefix exact control `Ko-LLM`; it was also rejected. The retained boundary matches the complete PDF shape: the immediately adjacent prefix is a pure-uppercase letter segment and the immediately adjacent suffix is a pure-uppercase segment of at least two letters. It reaches 67,138 (+126) while preserving all 32 baseline exact controls. The broad diagnostic now contains 952 candidates / 158 exact / 794 mismatch, with 157 localized `⠠ -> ⠰` and 3 reverse transitions. The dedicated `pure_allcaps_segment_before_hyphen_and_multi_allcaps_segment_after` row reports only the implemented subset; broad mixed-case and single-capital members remain controls or pending review. `K-ALM` is the one new reverse surface but was already a mismatch before this change, not an exact regression. Digit-hyphen forms such as `F-35` remain excluded, and the complete-shortform guard still legitimately precedes `CD` in `CD-ROM`. -This shape is not an engine implementation premise. The 2024 PDF's math rule 6 defines parentheses and grouping parentheses, rule 11 defines mathematical-expression spacing, rule 12 covers Roman letters in formulas as well as Korean sentences, and rule 45 shows Roman-letter function notation followed by parentheses. Excluding visible operators, scripts, and nesting narrows this corpus cohort, but the PDF does not make the remaining surface shape sufficient to rule out every mathematical counterexample. The cluster therefore remains conservative pending-review evidence only. +The HCA-style headword-expansion shape described above is not an engine implementation premise. The 2024 PDF's math rule 6 defines parentheses and grouping parentheses, rule 11 defines mathematical-expression spacing, rule 12 covers Roman letters in formulas as well as Korean sentences, and rule 45 shows Roman-letter function notation followed by parentheses. Excluding visible operators, scripts, and nesting narrows this corpus cohort, but the PDF does not make the remaining surface shape sufficient to rule out every mathematical counterexample. The cluster therefore remains conservative pending-review evidence only. The standalone-uppercase cohort is similarly ambiguous. Hangeul rule 28's appendix defines the capital-word indicator for two or more consecutive capitals, and rule 29 defines Roman indicators around Roman text in a Korean sentence. But math rule 12 also uses uppercase Roman variables, while science rule 7 uses uppercase runs in chemical formulas. The input gate cannot determine which semantic regime applies, so its output differences are observations to review, not permission to infer an engine rule from the corpus reference. @@ -1758,7 +1826,7 @@ Current single-capital parenthesized-digits measurement: 1361 candidates, 0 exac Current mixed Roman+Korean boundary before uppercase headword-expansion measurement: 10 candidates, 0 exact controls, 10 mismatches, 10 members in the actual `pending_rule_review` subcluster, and 8/10 evaluable mismatches whose first difference is localized to the later headword's entry boundary/output. The detector cannot be satisfied by the earlier Roman entry. No HCA-shaped engine routing rule is introduced. -Current compact numeric+ASCII-suffix measurement: 2975 candidates, 1840 exact controls, 1135 mismatches, 1067 members in the actual `pending_rule_review` subcluster, and 252/1135 evaluable mismatches whose first difference is inside the complete current-engine output signature or its immediate entry boundary. Rule 40 requires the numeric indicator and rule 69 requires Roman indicators around a Roman-written unit, but the input shape alone cannot prove that every ASCII suffix is a unit. +Current compact numeric+ASCII-suffix measurement: 2975 candidates, 1842 exact controls, 1133 mismatches, 1065 members in the actual `pending_rule_review` subcluster, and 252/1133 evaluable mismatches whose first difference is inside the complete current-engine output signature or its immediate entry boundary. Rule 40 requires the numeric indicator and rule 69 requires Roman indicators around a Roman-written unit, but the input shape alone cannot prove that every ASCII suffix is a unit. | ASCII suffix | Candidates | Exact | Mismatch | Localized first diff | |---|---:|---:|---:|---:| @@ -1783,7 +1851,7 @@ Current compact numeric+ASCII-suffix measurement: 2975 candidates, 1840 exact co | `bp` | 39 | 3 | 36 | 18 | | `K` | 38 | 11 | 27 | 1 | | `MW` | 37 | 35 | 2 | 0 | -| `TURN` | 33 | 16 | 17 | 0 | +| `TURN` | 33 | 18 | 15 | 0 | | `L` | 31 | 21 | 10 | 5 | | `mm` | 29 | 19 | 10 | 1 | | `egin` | 28 | 27 | 1 | 0 | @@ -1793,16 +1861,16 @@ Entry-boundary pre-fix baseline: 2,975 candidates, 1,649 exact controls, 1,326 m Current rule-69 ASCII-unit punctuation-boundary measurement: 440 candidates, 335 exact controls, 105 mismatches, 100 members in the actual `pending_rule_review` subcluster, and 10/105 evaluable mismatches whose first difference is localized to the unit-plus-punctuation output signature. PDF rule 69 requires a Roman terminator after a Roman-written unit in the ordinary case, while rules 33/34 omit it at the listed punctuation or enclosing-mark boundary; the rule 46 PDF example `체중(kg)` is the minimal parenthesized-unit control. Membership is restricted to rule-69 spellings already supported by the engine and does not infer unit semantics for arbitrary ASCII suffixes. Analyzer pre-fix baseline: 440 candidates, 0 exact controls, 440 mismatches, 435 pending members, and 9 signature-local first differences. After the generalized rule-33/34 boundary override and the matching non-math routing guard, the same cohort has 325 exact controls and 115 mismatches. Corpus-wide exact matches moved from 66,039 to 66,436 (+397); the additional gains are applications of the same boundary rule outside this strict ASCII detector, including compatibility-unit forms. The complete standard suite remains 5,141/5,141. -Current decimal-point measurement: 4546 candidates, 3253 exact controls, 1293 mismatches, 1237 members in the actual `pending_rule_review` subcluster, and 262/1293 evaluable mismatches whose first difference is inside the complete decimal-containing word's current-engine signature. Hangeul rules 43 and 48 keep an ASCII point between digits in the numeric sequence and encode it as the decimal point; rules 35 and 69 supply controls for adjacent Roman-number chains and Roman units. This is an implementation-candidate audit, not permission to specialize on a corpus reference. +Current decimal-point measurement: 4546 candidates, 3257 exact controls, 1289 mismatches, 1233 members in the actual `pending_rule_review` subcluster, and 264/1289 evaluable mismatches whose first difference is inside the complete decimal-containing word's current-engine signature. Hangeul rules 43 and 48 keep an ASCII point between digits in the numeric sequence and encode it as the decimal point; rules 35 and 69 supply controls for adjacent Roman-number chains and Roman units. This is an implementation-candidate audit, not permission to specialize on a corpus reference. Analyzer checkpoint `ed20f99` recorded the pre-fix baseline as 4,546 candidates, 2,584 exact controls, 1,962 mismatches, 1,905 pending members, and 875 localized first differences. Its dominant localized transition was reference decimal point `U+2832 ⠲` versus current Roman indicator `U+2834 ⠴` in 647 cases. After the generalized rule-43/48 guard, that transition is absent: 525 cases become exact and the other corrected prefixes expose later independent mismatches. -Current all-caps `OU` measurement: 1816 candidates, 55 exact controls, 1761 mismatches, 1675 members in the actual `pending_rule_review` subcluster, and 1310/1761 evaluable mismatches whose first difference is inside the current-engine output signature for the detected run. This is a pronunciation-sensitive UEB review cohort, not an engine routing rule. +Current all-caps `OU` measurement: 1816 candidates, 55 exact controls, 1761 mismatches, 1675 members in the actual `pending_rule_review` subcluster, and 1315/1761 evaluable mismatches whose first difference is inside the current-engine output signature for the detected run. This is a pronunciation-sensitive UEB review cohort, not an engine routing rule. -Current standalone-uppercase measurement: 62411 candidates, 49394 exact controls, 13017 mismatches, and 11571 members in the actual `pending_rule_review` subcluster. Its high frequency does not make it causal: the same input shape is exact in many cases, and a sentence containing the shape may first differ at another Roman, numeric, or punctuation structure. No engine change is inferred from this cohort. +Current standalone-uppercase measurement: 62411 candidates, 49518 exact controls, 12893 mismatches, and 11445 members in the actual `pending_rule_review` subcluster. Its high frequency does not make it causal: the same input shape is exact in many cases, and a sentence containing the shape may first differ at another Roman, numeric, or punctuation structure. No engine change is inferred from this cohort. -Current Korean-prefixed all-caps parenthetical measurement: 54492 candidates, 44220 exact controls, 10272 mismatches, and 8982 members in the actual `pending_rule_review` subcluster. This is a semantic-collision audit, not an engine routing rule; no implementation change is inferred from its reference outputs. +Current Korean-prefixed all-caps parenthetical measurement: 54492 candidates, 44300 exact controls, 10192 mismatches, and 8900 members in the actual `pending_rule_review` subcluster. This is a semantic-collision audit, not an engine routing rule; no implementation change is inferred from its reference outputs. -Current rule-34 opening-order measurement: 64382 structural candidates, 51811 exact controls, 12571 mismatches, and 1099/12571 evaluable mismatches whose first difference is inside the current engine's Korean opening-parenthesis cells. Only the 1098 localized first-cell transitions have the reference/current order `⠴` versus `⠦`. After requiring the complete reference `⠴⠐⠣` versus current/PDF `⠦⠄⠴` three-cell signature and preserving higher-priority comparison classifications, 1095 are classified as `corpus_suspect`; mere coexistence with a Korean-prefixed Roman annotation does not change a primary class. +Current rule-34 opening-order measurement: 64382 structural candidates, 51907 exact controls, 12475 mismatches, and 1101/12475 evaluable mismatches whose first difference is inside the current engine's Korean opening-parenthesis cells. Only the 1100 localized first-cell transitions have the reference/current order `⠴` versus `⠦`. After requiring the complete reference `⠴⠐⠣` versus current/PDF `⠦⠄⠴` three-cell signature and preserving higher-priority comparison classifications, 1097 are classified as `corpus_suspect`; mere coexistence with a Korean-prefixed Roman annotation does not change a primary class. Current all-caps Roman middle-dot measurement: 97 candidates, 0 exact controls, 97 mismatches, and 96 members in the actual `pending_rule_review` subcluster. This cross-cutting cohort preserves every primary class and is not an engine routing rule. @@ -1811,7 +1879,7 @@ Current Roman-before-middle-dot boundary measurement: 577 candidates, 0 exact co Current inline parenthesized-operator measurement: 23 candidates, 17 exact controls, 6 mismatches, 6 members in the actual `pending_rule_review` subcluster, and 2/6 evaluable mismatches whose first differing cell is inside the emitted structure. Primary classes are preserved. At that implementation checkpoint, the strict cohort moved from 0 to 17 exact cases; the corpus-wide total moved from 65,491 to 65,514 (+23 exact) because the same PDF-backed spacing rule also applied outside the stricter Korean-boundary audit gate. These are immutable checkpoint counts rather than the report's later cumulative total. The complete standard suite remained 5,141/5,141. -Current tight-triangle measurement: 377 candidates, 0 exact controls, 377 mismatches, 376 members in the actual `pending_rule_review` subcluster, and 330/377 evaluable mismatches whose first difference is inside the `△` plus first-Korean output range. No engine change is inferred. +Current tight-triangle measurement: 377 candidates, 0 exact controls, 377 mismatches, 376 members in the actual `pending_rule_review` subcluster, and 331/377 evaluable mismatches whose first difference is inside the `△` plus first-Korean output range. No engine change is inferred. ## Encoding-error diagnostics @@ -1863,19 +1931,19 @@ Families are diagnostics, not automatic normalization permissions. Rules 68/69 c | Shard | Exact | Total | Accuracy | |---|---:|---:|---:| -| `sentence_01.json` | 20124 | 25000 | 80.50% | -| `sentence_02.json` | 19577 | 25000 | 78.31% | -| `sentence_03.json` | 20208 | 25000 | 80.83% | -| `sentence_04.json` | 7103 | 8528 | 83.29% | +| `sentence_01.json` | 20166 | 25000 | 80.66% | +| `sentence_02.json` | 19620 | 25000 | 78.48% | +| `sentence_03.json` | 20238 | 25000 | 80.95% | +| `sentence_04.json` | 7114 | 8528 | 83.42% | ## Overlapping mismatch traits | Trait | Count | |---|---:| -| `contains_ascii_digits` | 12904 | -| `contains_ascii_letters` | 16330 | -| `contains_delimiter_or_quote` | 16516 | -| `input_not_nfkc` | 720 | +| `contains_ascii_digits` | 12818 | +| `contains_ascii_letters` | 16204 | +| `contains_delimiter_or_quote` | 16390 | +| `input_not_nfkc` | 718 | ## Samples @@ -2017,9 +2085,9 @@ Presentation-form cases audited: 37. | Previous observation → current observation | Cases | |---|---:| -| `encoding_error -> encoded_mismatch_pending_rule_review` | 23 | +| `encoding_error -> encoded_mismatch_pending_rule_review` | 22 | | `encoding_error -> unsupported_character_review` | 3 | -| `nfkc_input_equivalent -> exact` | 11 | +| `nfkc_input_equivalent -> exact` | 12 | Remaining complex encoding errors: 3. These cases still contain another character that fails independently, so disappearance of the `roman_numeral_presentation` family does not imply that every former error case now encodes successfully. diff --git a/libs/braillify/examples/nikl_corpus_analyze.rs b/libs/braillify/examples/nikl_corpus_analyze.rs index 676a61eb..ecf65eba 100644 --- a/libs/braillify/examples/nikl_corpus_analyze.rs +++ b/libs/braillify/examples/nikl_corpus_analyze.rs @@ -595,6 +595,8 @@ const ROMAN_UPPERCASE_AFTER_DIGIT: &str = "uppercase_ascii_run_immediately_after_digit_in_roman_sequence"; const ROMAN_UPPERCASE_AFTER_HYPHEN: &str = "uppercase_ascii_run_immediately_after_hyphen_in_roman_sequence"; +const PURE_ALLCAPS_HYPHEN_MULTI_ALLCAPS: &str = + "pure_allcaps_segment_before_hyphen_and_multi_allcaps_segment_after"; #[derive(Clone, Copy, Debug, PartialEq, Eq)] struct InputSpan { @@ -1060,6 +1062,40 @@ fn roman_uppercase_after_hyphen_spans(input: &str) -> Vec { spans } +/// Narrows the broad hyphen continuation diagnostic to the UEB 5.7.2 +/// `CD-ROM` boundary implemented by the engine: the immediately adjacent +/// letter segment before the hyphen is pure uppercase, and the immediately +/// adjacent segment after it is pure uppercase with at least two letters. +fn pure_allcaps_hyphen_multi_allcaps_spans(input: &str) -> Vec { + roman_uppercase_after_hyphen_spans(input) + .into_iter() + .filter(|span| { + let run = &input.as_bytes()[span.start_byte..span.end_byte]; + run.iter().enumerate().any(|(hyphen, byte)| { + if *byte != b'-' { + return false; + } + let prefix_start = run[..hyphen] + .iter() + .rposition(|byte| !byte.is_ascii_alphabetic()) + .map_or(0, |index| index + 1); + let prefix = &run[prefix_start..hyphen]; + let suffix = &run[hyphen + 1..]; + let suffix_len = suffix + .iter() + .take_while(|byte| byte.is_ascii_alphabetic()) + .count(); + let suffix_letters = &suffix[..suffix_len]; + + !prefix.is_empty() + && prefix.iter().all(u8::is_ascii_uppercase) + && suffix_letters.len() >= 2 + && suffix_letters.iter().all(u8::is_ascii_uppercase) + }) + }) + .collect() +} + /// Locates each detected run in the full current-engine output by searching /// for that run's independently encoded signature. This uses neither the /// corpus reference nor a hard-coded braille value. @@ -2057,6 +2093,10 @@ fn analyze( ROMAN_UPPERCASE_AFTER_HYPHEN.to_string(), PendingRuleReviewClusterStats::default(), ), + ( + PURE_ALLCAPS_HYPHEN_MULTI_ALLCAPS.to_string(), + PendingRuleReviewClusterStats::default(), + ), ]); let mut pending_first_difference_cell_transitions = BTreeMap::new(); let mut pending_first_difference_transitions_after_localized_cohorts = BTreeMap::new(); @@ -2263,6 +2303,18 @@ fn analyze( ), true, ), + ( + PURE_ALLCAPS_HYPHEN_MULTI_ALLCAPS, + !pure_allcaps_hyphen_multi_allcaps_spans(&item.located.case.input).is_empty(), + Some( + !first_difference_claimed_by_prior_localized_cohort(item) + && first_difference_in_grade1_cohort_spans( + item, + &pure_allcaps_hyphen_multi_allcaps_spans(&item.located.case.input), + ), + ), + true, + ), ] { if !present { continue; @@ -2776,7 +2828,7 @@ fn markdown(report: &AnalysisReport) -> String { } text.push_str("\n## UEB grade-1 first-difference cohorts\n\n"); text.push_str( - "These three cohorts are defined by both an input boundary and the sentence's actual \ + "These cohorts are defined by both an input boundary and the sentence's actual \ first-difference transition. They therefore do not claim every mismatch merely \ coexisting with an ASCII run. Candidate, exact, mismatch, and primary-class counts \ remain cross-cutting controls; only the reported target transition is the localized \ @@ -2803,6 +2855,11 @@ fn markdown(report: &AnalysisReport) -> String { "U+2820 ⠠ -> U+2830 ⠰", "U+2830 ⠰ -> U+2820 ⠠", ), + ( + PURE_ALLCAPS_HYPHEN_MULTI_ALLCAPS, + "U+2820 ⠠ -> U+2830 ⠰", + "U+2830 ⠰ -> U+2820 ⠠", + ), ] { let stats = report .pending_rule_review_clusters @@ -2923,21 +2980,34 @@ fn markdown(report: &AnalysisReport) -> String { non-exact members are not attributed to the removed uppercase transition: \ their sentence-level first difference may lie in another structure and \ remains under its existing primary class. This numeric state change remains \ - separate from both the complete-shortform guard and the still-unimplemented \ - hyphen continuation cohort.\n\n\ + separate from both the complete-shortform guard and the hyphen continuation \ + boundary below.\n\n\ ### Uppercase immediately after a hyphen\n\n\ UEB rule 5.7.2 prints `CD-ROM` with one grade-1 indicator before `CD` and \ no second grade-1 indicator after the hyphen. Korean rule 29 similarly \ - uses one Roman span for consecutive Roman text. The 312 localized \ - `⠠ -> ⠰` cases are therefore tracked as a distinct hyphen-continuation \ - candidate. The three reverse cases, only 31 exact controls in the broad \ - cohort, and surfaces whose first difference lies elsewhere remain \ - controls. This route is judged separately from digit-hyphen forms such as \ - `F-35`, which this detector excludes, and separately from the shortform \ - guard that legitimately precedes `CD` in `CD-ROM`.\n\n", + uses one Roman span for consecutive Roman text. Before the engine change, \ + the broad diagnostic contained 952 candidates / 32 exact / 920 mismatch, \ + with 312 localized `⠠ -> ⠰` and 2 reverse transitions. A blanket \ + uppercase-suffix removal reached 67,222 (+210) but made the broad cohort's \ + single-capital controls such as `Around-U`, `DALL-E`, `ISMS-P`, and `USB-C` \ + non-exact; it was rejected. Requiring only a two-letter uppercase suffix \ + reached 67,162 (+150) but regressed the mixed-prefix exact control `Ko-LLM`; \ + it was also rejected. The retained boundary matches the complete PDF shape: \ + the immediately adjacent prefix is a pure-uppercase letter segment and the \ + immediately adjacent suffix is a pure-uppercase segment of at least two \ + letters. It reaches 67,138 (+126) while preserving all 32 baseline exact \ + controls. The broad diagnostic now contains 952 candidates / 158 exact / \ + 794 mismatch, with 157 localized `⠠ -> ⠰` and 3 reverse transitions. The \ + dedicated `pure_allcaps_segment_before_hyphen_and_multi_allcaps_segment_after` \ + row reports only the implemented subset; broad mixed-case and single-capital \ + members remain controls or pending review. `K-ALM` is the one new reverse \ + surface but was already a mismatch before this change, not an exact \ + regression. Digit-hyphen forms such as `F-35` remain excluded, and the \ + complete-shortform guard still legitimately precedes `CD` in `CD-ROM`.\n\n", ); text.push_str( - "\nThis shape is not an engine implementation premise. The 2024 PDF's math rule 6 \ + "\nThe HCA-style headword-expansion shape described above is not an engine \ + implementation premise. The 2024 PDF's math rule 6 \ defines parentheses and grouping parentheses, rule 11 defines mathematical-expression \ spacing, rule 12 covers Roman letters in formulas as well as Korean sentences, and \ rule 45 shows Roman-letter function notation followed by parentheses. Excluding visible \ @@ -4158,6 +4228,22 @@ mod tests { assert_eq!(actual, expected); } + #[rstest::rstest] + #[case::pdf_complete_letters_sequence("CD-ROM", vec!["CD-ROM"])] + #[case::single_capital_control("Around-U", vec![])] + #[case::mixed_prefix_control("Ko-LLM", vec![])] + #[case::digit_hyphen_control("F-35", vec![])] + fn detects_only_pure_allcaps_hyphen_multi_allcaps_engine_boundary( + #[case] input: &str, + #[case] expected: Vec<&str>, + ) { + let actual = pure_allcaps_hyphen_multi_allcaps_spans(input) + .into_iter() + .map(|span| &input[span.start_byte..span.end_byte]) + .collect::>(); + assert_eq!(actual, expected); + } + #[rstest::rstest] #[case::shortform_prefix("가(WD) 나", allcaps_shortform_prefix_spans("가(WD) 나"))] #[case::numeric_continuation("가(Li2S) 나", roman_uppercase_after_digit_spans("가(Li2S) 나"))] diff --git a/libs/braillify/src/rules/korean/rule_english_symbol.rs b/libs/braillify/src/rules/korean/rule_english_symbol.rs index ccee6105..6b09a44a 100644 --- a/libs/braillify/src/rules/korean/rule_english_symbol.rs +++ b/libs/braillify/src/rules/korean/rule_english_symbol.rs @@ -103,13 +103,29 @@ impl BrailleRule for RuleEnglishSymbol { if let Some(encoded) = symbol_shortcut::encode_english_char_symbol_shortcut(*sym) { ctx.emit_slice(&encoded); if *sym == '-' && ctx.state.is_english { - // 다음 글자가 숫자이면 수표(⠼)가 emit되므로 연속표(⠰)는 - // 불필요하다 (제35항 D-100 같은 영문-숫자 인접 패턴). - let next_is_digit = ctx - .word_chars - .get(ctx.index + 1) - .is_some_and(|c| c.is_ascii_digit()); - if !next_is_digit { + // UEB 5.7.2의 `CD-ROM`은 순수 대문자 segment 사이의 하이픈 + // 뒤에서 대문자 단어표 앞에 1급 점자 기호표를 다시 적지 + // 않는다. 숫자는 제35항 `D-100`처럼 수표가 나오므로 역시 + // 로마자 연속표(⠰)가 불필요하다. 혼합 대소문자 prefix와 단일 + // 대문자 suffix는 이 근거 범위 밖이므로 기존 경계를 보존한다. + let prefix_len = ctx.word_chars[..ctx.index] + .iter() + .rev() + .take_while(|c| c.is_ascii_alphabetic()) + .count(); + let prefix = &ctx.word_chars[ctx.index - prefix_len..ctx.index]; + let suffix = &ctx.word_chars[ctx.index + 1..]; + let suffix_len = suffix + .iter() + .take_while(|c| c.is_ascii_alphabetic()) + .count(); + let suffix_letters = &suffix[..suffix_len]; + let next_has_own_indicator = suffix.first().is_some_and(char::is_ascii_digit) + || (!prefix.is_empty() + && prefix.iter().all(char::is_ascii_uppercase) + && suffix_letters.len() >= 2 + && suffix_letters.iter().all(char::is_ascii_uppercase)); + if !next_has_own_indicator { ctx.emit(crate::rules::korean::rule_29::ENGLISH_CONTINUATION); } } @@ -208,4 +224,27 @@ mod tests { "input={input}, decimal={decimal}, actual={actual}, standalone={standalone}" ); } + + /// UEB 5.7.2 prints `CD-ROM` with one grade-1 indicator before the complete + /// letters-sequence and no second grade-1 indicator after the hyphen. This + /// full-encoder wrapper exercises the Korean rule-29 character route rather + /// than the standalone-English token route used by the standard PDF case. + #[test] + fn korean_wrapper_keeps_pdf_cd_rom_as_one_grade1_letters_sequence() { + let output = crate::encode("가(CD-ROM)나").expect("Korean wrapper must encode"); + let expected_ueb = "⠰⠠⠠⠉⠙⠤⠠⠠⠗⠕⠍" + .chars() + .map(crate::unicode::decode_unicode) + .collect::>(); + let roman_start = output + .iter() + .position(|cell| *cell == crate::rules::korean::rule_29::ROMAN_INDICATOR) + .expect("Korean wrapper must enter one Roman section") + + 1; + + assert_eq!( + output.get(roman_start..roman_start + expected_ueb.len()), + Some(expected_ueb.as_slice()) + ); + } } From 6fd4b3b00159b4b17151ded5a480f57e18a9283e Mon Sep 17 00:00:00 2001 From: devfive Date: Tue, 1 Sep 2026 11:47:45 +0900 Subject: [PATCH 29/77] Diagnose Roman entry residual boundaries --- docs/corpus-analysis/NIKL_2025_V1.md | 285 +++++++++++-- .../braillify/examples/nikl_corpus_analyze.rs | 394 +++++++++++++++++- 2 files changed, 641 insertions(+), 38 deletions(-) diff --git a/docs/corpus-analysis/NIKL_2025_V1.md b/docs/corpus-analysis/NIKL_2025_V1.md index db3c9d4e..12f55307 100644 --- a/docs/corpus-analysis/NIKL_2025_V1.md +++ b/docs/corpus-analysis/NIKL_2025_V1.md @@ -298,17 +298,17 @@ This ranking removes only cases whose first difference is inside an existing out | Rank | Expected → actual first cell | Residual cases | |---:|---|---:| -| 1 | `U+2834 ⠴ -> U+2800 ⠀` | 549 | -| 2 | `U+2820 ⠠ -> U+2834 ⠴` | 456 | -| 3 | `U+2826 ⠦ -> U+2834 ⠴` | 451 | -| 4 | `U+280E ⠎ -> U+280C ⠌` | 444 | -| 5 | `U+2808 ⠈ -> U+2832 ⠲` | 409 | -| 6 | `U+2826 ⠦ -> U+2800 ⠀` | 393 | -| 7 | `U+2801 ⠁ -> U+281C ⠜` | 391 | -| 8 | `U+2834 ⠴ -> U+2830 ⠰` | 328 | -| 9 | `U+2811 ⠑ -> U+282B ⠫` | 323 | -| 10 | `U+2810 ⠐ -> U+2832 ⠲` | 288 | -| 11 | `U+2832 ⠲ -> U+2838 ⠸` | 276 | +| 1 | `U+2820 ⠠ -> U+2834 ⠴` | 455 | +| 2 | `U+2826 ⠦ -> U+2834 ⠴` | 451 | +| 3 | `U+280E ⠎ -> U+280C ⠌` | 444 | +| 4 | `U+2808 ⠈ -> U+2832 ⠲` | 409 | +| 5 | `U+2826 ⠦ -> U+2800 ⠀` | 393 | +| 6 | `U+2801 ⠁ -> U+281C ⠜` | 391 | +| 7 | `U+2834 ⠴ -> U+2830 ⠰` | 326 | +| 8 | `U+2811 ⠑ -> U+282B ⠫` | 323 | +| 9 | `U+2810 ⠐ -> U+2832 ⠲` | 288 | +| 10 | `U+2832 ⠲ -> U+2838 ⠸` | 276 | +| 11 | `U+2834 ⠴ -> U+2800 ⠀` | 256 | | 12 | `U+2800 ⠀ -> U+2832 ⠲` | 225 | | 13 | `U+2810 ⠐ -> U+2802 ⠂` | 210 | | 14 | `U+2817 ⠗ -> U+2820 ⠠` | 163 | @@ -319,29 +319,6 @@ This ranking removes only cases whose first difference is inside an existing out | 19 | `U+2810 ⠐ -> U+2800 ⠀` | 129 | | 20 | `U+2811 ⠑ -> U+283B ⠻` | 100 | -### Residual `U+2834 ⠴ -> U+2800 ⠀` - -- `sentence_01.json` #35: 삼성전자는 올해 CES에서 77인치 OLED TV를 처음으로 공개할 예정이다. 이는 지난 8월 ‘국제정보디스플레이학술대회(IMID) 2022’에서 삼성디스플레이가 공개한 77인치 퀀텀닷(QD)-OLED 패널을 탑재한 제품이다. - - expected: `⠋⠏⠒⠓⠎⠢⠊⠄⠴⠐⠣⠟⠙⠐⠜⠤⠠⠠⠕⠇⠫⠲⠀⠙` - - actual: `⠋⠏⠒⠓⠎⠢⠊⠄⠀⠀⠦⠠⠠⠟⠙⠴⠔⠠⠠⠕⠇⠑⠙⠀` - - first differing cell (zero-based): 172 - - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_02.json` #344: 한편 이장우 대전시장은 이틀간의 대만 방문을 마무리하고 싱가포르로 이동해 18일까지 Merck(머크) 사, 국립싱가포르대학 바이오연구단, A-STAR 및 바이오폴리스 등 바이오산업 관련 해외 기업과 연구기관을 방문할 예정이다. - - expected: `⠥⠡⠈⠍⠊⠒⠐⠀⠴⠠⠁⠤⠠⠠⠌⠜⠲⠀⠑⠕⠆⠀⠘⠣` - - actual: `⠥⠡⠈⠍⠊⠒⠐⠀⠀⠠⠁⠔⠠⠠⠎⠞⠁⠗⠀⠀⠑⠕⠆⠀` - - first differing cell (zero-based): 122 - - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_03.json` #381: 인포테인먼트 시스템 ccNC(connected car Navigation Cockpit)를 최초 탑재하고 무선 소프트웨어 업데이트(OTA) 대상 범위를 주요 편의 기능까지 대폭 확대했다. - - expected: `⠠⠕⠠⠪⠓⠝⠢⠀⠴⠉⠉⠠⠠⠝⠉⠐⠣⠒⠝⠑⠉⠞⠫⠀` - - actual: `⠠⠕⠠⠪⠓⠝⠢⠀⠀⠉⠉⠠⠠⠝⠉⠦⠉⠕⠝⠝⠑⠉⠞⠑` - - first differing cell (zero-based): 19 - - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_04.json` #1075: 접속 방법은 ‘안심전세포털’ 및 국토부 홈페이지에 안내돼 있다. 줌 URL(ID : 393 490 1984)을 입력하거나 주택도시보증공사(HUG) 유튜브로 접속하면 된다. - - expected: `⠌⠊⠲⠀⠨⠍⠢⠀⠴⠠⠠⠥⠗⠇⠐⠣⠠⠠⠊⠙⠀⠒⠀⠼` - - actual: `⠌⠊⠲⠀⠨⠍⠢⠀⠀⠠⠠⠥⠗⠇⠦⠠⠠⠊⠙⠀⠐⠂⠀⠼` - - first differing cell (zero-based): 70 - - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` - ### Residual `U+2820 ⠠ -> U+2834 ⠴` - `sentence_01.json` #78: 2023년형 Neo QLED는 스마트싱스뿐 아니라 매터(Matter) 기기까지 지원하는 원칩 모듈을 탑재해 별도의 동글을 사용하지 않아도, 다양한 사물인터넷(IoT) 기기를 직접 TV에 연결해 사용할 수 있다. @@ -549,6 +526,29 @@ This ranking removes only cases whose first difference is inside an existing out - first differing cell (zero-based): 115 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +### Residual `U+2832 ⠲ -> U+2838 ⠸` + +- `sentence_01.json` #314: 이와 관련해서 FAA는 “FAA항공정보(NOTAMS) 업데이트에 관여하는 시스템이 작동하지 않고 있다”며 “복구작업을 벌이고 있지만 현재로서는 복구 시점을 예상하기 어렵다”고 말했다. + - expected: `⠀⠦⠴⠠⠠⠋⠁⠁⠲⠚⠶⠈⠿⠨⠻⠘⠥⠦⠄⠴⠠⠠⠝⠕` + - actual: `⠀⠦⠴⠠⠠⠋⠁⠁⠸⠷⠚⠶⠈⠿⠨⠻⠘⠥⠸⠾⠐⠣⠠⠠` + - first differing cell (zero-based): 30 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_02.json` #483: 충남교육청(교육감 김지철)은 7월 31일부터 오는 11일까지 도내 Wee센터·Wee클래스 전문상담교사, 전문상담사, 임상심리사 150명을 대상으로 ‘2023 하반기 위(Wee) 프로젝트 역량강화 직무연수’를 추진하고 있다고 밝혔다. + - expected: `⠉⠗⠀⠴⠠⠺⠑⠑⠲⠠⠝⠒⠓⠎⠐⠆⠴⠠⠺⠑⠑⠲⠋⠮` + - actual: `⠉⠗⠀⠴⠠⠺⠑⠑⠸⠷⠠⠝⠒⠓⠎⠸⠾⠲⠸⠂⠴⠠⠺⠑` + - first differing cell (zero-based): 70 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_03.json` #402: 블룸버그에 따르면, BAT와 자회사인 BAT마케팅싱가포르(BATMS)는 은행사기법과 국제비상경제권법(IEEPA)을 위반하며 싱가포르의 제3자 회사를 통해 북한에서 수년간 담배를 팔아온 것으로 알려졌다. + - expected: `⠟⠀⠴⠠⠠⠃⠁⠞⠲⠑⠋⠝⠓⠕⠶⠠⠕⠶⠫⠙⠥⠐⠪⠦` + - actual: `⠟⠀⠴⠠⠠⠃⠁⠞⠸⠷⠑⠋⠝⠓⠕⠶⠠⠕⠶⠫⠙⠥⠐⠪` + - first differing cell (zero-based): 40 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_04.json` #1660: 공공운수노조 더불어사는희망연대본부는 KT 인터넷이나 IPTV 개통과 수리를 담당하는 KT 그룹사 KT서비스(KTS) 북부·남부 소속 기사들로 구성된 노조다. + - expected: `⠃⠇⠀⠴⠠⠠⠅⠞⠲⠠⠎⠘⠕⠠⠪⠦⠄⠴⠠⠠⠅⠞⠎⠠` + - actual: `⠃⠇⠀⠴⠠⠠⠅⠞⠸⠷⠠⠎⠘⠕⠠⠪⠸⠾⠐⠣⠠⠠⠅⠞` + - first differing cell (zero-based): 100 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` + ## Cross-cutting input-only structural cohorts These are cross-cutting input-only structural cohorts, not new primary classes and not engine routing rules. Candidate selection never changes a case's existing primary class by itself. Only cohort members already classified as `pending_rule_review` form a pending subcluster; exact and other-primary members are controls that retain their existing outcomes. A separate classifier may use independently justified, output-localized PDF evidence, as in the rule-34 three-cell contradiction below. The `uppercase_roman_headword_closed_multiword_parenthetical` gate requires a two-or-more character uppercase ASCII headword immediately followed by a closed parenthesis whose contents are two or more ASCII Roman words separated only by spaces. Because the contents admit only letters and spaces, visible operators, subscript/superscript notation, and nested parentheses are excluded deterministically. The `mixed_roman_korean_word_before_uppercase_headword_expansion` gate further requires a whitespace-delimited preceding word that contains both ASCII Roman and Korean and ends in Korean. It locates the following uppercase headword itself, including its immediately preceding emitted cell, so an earlier Roman entry in the same sentence cannot satisfy the output audit. The `single_capital_followed_by_parenthesized_digits` gate requires a standalone capital, a non-empty closed ASCII-digit parenthetical, and alphanumeric outer boundaries. It likewise includes the emitted entry-boundary cell in localization. The `uppercase_roman_run_followed_by_hyphen_digits` gate requires a maximal uppercase ASCII run, literal ASCII hyphen-minus, a non-empty digit run, and alphanumeric outer boundaries. Its localized range includes the current encoded run and its immediately preceding cell, keeping `F-35`-style routing separate from both parenthesized digits and headword expansions. The `standalone_multi_character_uppercase_roman_word` gate finds maximal ASCII-letter runs of two or more capitals with non-alphanumeric boundaries; a run immediately followed by `(` is excluded so the HCA-style headword itself is not counted by both gates. The `korean_prefixed_closed_allcaps_parenthetical` gate requires an immediately preceding Korean character and a closed body of two or more uppercase ASCII letters. It intentionally contains both acronym annotations (`책임자(COO)`) and scientific formulae (`일산화탄소(CO)`) so their semantic collision remains measurable. The `korean_prefixed_closed_roman_annotation_rule_34_order` gate accepts the narrower rule-34 body grammar after an immediately preceding Korean character and localizes only the current engine's Korean opening-parenthesis cells. This distinguishes the PDF's parenthesis-before-Roman-indicator order from unrelated differences later in the same sentence. The `multi_character_allcaps_roman_runs_joined_by_middle_dot` gate requires two maximal ASCII-letter runs of at least two capitals joined directly by U+00B7, with non-alphanumeric outer boundaries. It records shapes such as `AI·SW` without assigning prose, mathematics, or science semantics. The `roman_run_immediately_before_attached_middle_dot_boundary` gate is output-localized: it requires a maximal ASCII-letter run immediately before U+00B7 and an attached Korean character or ASCII-letter run after it, then searches for that whole current-engine signature in the actual output. It therefore isolates the Roman terminator boundary without treating unrelated middle dots elsewhere in the sentence as causal. The `korean_inline_parenthesized_single_arithmetic_operator` gate requires an immediate `Korean(` + one rule-45 arithmetic operator + `)Korean` span. Unlike broad coexistence traits, it also locates the current engine's emitted structure and counts a mismatch as signature-local only when the sentence's first differing cell falls inside that output range. The `allcaps_roman_run_containing_ou` gate finds maximal, alphanumeric-delimited uppercase ASCII runs containing adjacent `OU`. It locates the independently encoded run signature in the complete current output and counts only first differences inside that signature as localized. The `decimal_point_between_ascii_digits` gate finds whitespace-delimited words containing `digit.digit` and reproduces each whole word in a neutral Korean context, so suffixes and punctuation remain part of the current-engine signature. The `compact_numeric_ascii_letter_suffix` gate finds a numeric prefix immediately followed by ASCII letters, includes the immediately preceding output cell as its entry boundary, and retains suffix-specific outcome counts. It intentionally includes both possible rule-69 units and ambiguous variable/identifier forms; membership alone does not assign unit semantics. The `rule69_ascii_unit_before_terminator_skipping_symbol` gate is narrower: it accepts only ASCII unit spellings already supported by rule 69, includes the immediately following rule-33/34 punctuation cell in the localized signature, and does not infer new units. The `tight_triangle_mark_immediately_before_korean` gate requires literal `△한글` with no input space and includes the first following Korean cell in its localized output range, so an observed missing-space difference is measured at the mark boundary. @@ -562,9 +562,12 @@ These are cross-cutting input-only structural cohorts, not new primary classes a | `korean_inline_parenthesized_single_arithmetic_operator` | 23 | 17 | 6 | 0 | | `korean_prefixed_closed_allcaps_parenthetical` | 54492 | 44300 | 10192 | 0 | | `korean_prefixed_closed_roman_annotation_rule_34_order` | 64382 | 51907 | 12475 | 0 | +| `korean_prefixed_roman_parenthetical_followed_by_allcaps_hyphen_suffix` | 11 | 0 | 11 | 0 | | `mixed_roman_korean_word_before_uppercase_headword_expansion` | 10 | 0 | 10 | 0 | | `multi_character_allcaps_roman_runs_joined_by_middle_dot` | 97 | 0 | 97 | 0 | | `pure_allcaps_segment_before_hyphen_and_multi_allcaps_segment_after` | 448 | 126 | 322 | 0 | +| `roman_hyphenated_word_after_whitespace_following_korean_word` | 361 | 0 | 361 | 0 | +| `roman_parenthetical_headword_after_whitespace_following_korean_word` | 4695 | 3342 | 1353 | 0 | | `roman_run_immediately_before_attached_middle_dot_boundary` | 577 | 0 | 577 | 0 | | `rule69_ascii_unit_before_terminator_skipping_symbol` | 440 | 335 | 105 | 0 | | `single_capital_followed_by_parenthesized_digits` | 1361 | 0 | 1361 | 0 | @@ -1073,6 +1076,57 @@ Representative `mismatch` samples: - first differing cell (zero-based): 60 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +### `korean_prefixed_roman_parenthetical_followed_by_allcaps_hyphen_suffix` + +Of the 11 candidates, 6 are the actual `pending_rule_review` subcluster. The other 5 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. + +For this output-signature audit, 11 mismatches were evaluable and 5 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. + +Localized first-difference transitions: + +- `U+2834 ⠴ -> U+2800 ⠀`: 5 + +Mismatch primary-class distribution: + +- `corpus_suspect`: 5 +- `pending_rule_review`: 6 + +Representative `localized_mismatch` samples: + +- `sentence_01.json` #35: 삼성전자는 올해 CES에서 77인치 OLED TV를 처음으로 공개할 예정이다. 이는 지난 8월 ‘국제정보디스플레이학술대회(IMID) 2022’에서 삼성디스플레이가 공개한 77인치 퀀텀닷(QD)-OLED 패널을 탑재한 제품이다. + - expected: `⠋⠏⠒⠓⠎⠢⠊⠄⠴⠐⠣⠟⠙⠐⠜⠤⠠⠠⠕⠇⠫⠲⠀⠙` + - actual: `⠋⠏⠒⠓⠎⠢⠊⠄⠀⠀⠦⠠⠠⠟⠙⠴⠔⠠⠠⠕⠇⠑⠙⠀` + - first differing cell (zero-based): 172 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_02.json` #7731: 카젬 전 사장은 지난 2017년 9월 한국GM 사장으로 취임했으며, 작년 6월 중국 상하이자동차(SAIC)-GM 총괄 부사장을 맡고 있다. + - expected: `⠚⠣⠕⠨⠊⠿⠰⠣⠴⠐⠣⠠⠠⠎⠁⠊⠉⠐⠜⠤⠠⠠⠛⠍` + - actual: `⠚⠣⠕⠨⠊⠿⠰⠣⠀⠀⠦⠠⠠⠎⠁⠊⠉⠴⠔⠠⠠⠛⠍⠀` + - first differing cell (zero-based): 91 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_03.json` #4952: 1일 시장조사기관 옴디아에 따르면 LG전자는 지난 1분기 퀀텀닷(QD)-LCD TV 부문 집계에 처음으로 포함되면서 바로 글로벌 2위(17.5%)에 올랐다. + - expected: `⠋⠏⠒⠓⠎⠢⠊⠄⠴⠐⠣⠠⠠⠟⠙⠐⠜⠤⠠⠠⠇⠉⠙⠀` + - actual: `⠋⠏⠒⠓⠎⠢⠊⠄⠀⠀⠦⠠⠠⠟⠙⠴⠔⠠⠠⠇⠉⠙⠀⠴` + - first differing cell (zero-based): 64 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` + +Representative `mismatch` samples: + +- `sentence_01.json` #35: 삼성전자는 올해 CES에서 77인치 OLED TV를 처음으로 공개할 예정이다. 이는 지난 8월 ‘국제정보디스플레이학술대회(IMID) 2022’에서 삼성디스플레이가 공개한 77인치 퀀텀닷(QD)-OLED 패널을 탑재한 제품이다. + - expected: `⠋⠏⠒⠓⠎⠢⠊⠄⠴⠐⠣⠟⠙⠐⠜⠤⠠⠠⠕⠇⠫⠲⠀⠙` + - actual: `⠋⠏⠒⠓⠎⠢⠊⠄⠀⠀⠦⠠⠠⠟⠙⠴⠔⠠⠠⠕⠇⠑⠙⠀` + - first differing cell (zero-based): 172 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_02.json` #7731: 카젬 전 사장은 지난 2017년 9월 한국GM 사장으로 취임했으며, 작년 6월 중국 상하이자동차(SAIC)-GM 총괄 부사장을 맡고 있다. + - expected: `⠚⠣⠕⠨⠊⠿⠰⠣⠴⠐⠣⠠⠠⠎⠁⠊⠉⠐⠜⠤⠠⠠⠛⠍` + - actual: `⠚⠣⠕⠨⠊⠿⠰⠣⠀⠀⠦⠠⠠⠎⠁⠊⠉⠴⠔⠠⠠⠛⠍⠀` + - first differing cell (zero-based): 91 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_03.json` #866: 제주삼다수 리본은 화학적 재활용 페트인 ‘스카이펫(SKYPET)-CR’을 사용한 제품으로, 제주개발공사가 SK케미칼과 손잡고 2021년 10월 업계 최초로 개발했다. + - expected: `⠠⠪⠋⠣⠕⠙⠝⠄⠴⠐⠣⠠⠠⠎⠅⠽⠏⠑⠞⠐⠜⠤⠠⠠` + - actual: `⠠⠪⠋⠣⠕⠙⠝⠄⠦⠄⠴⠠⠠⠎⠅⠽⠏⠑⠞⠠⠴⠤⠰⠠` + - first differing cell (zero-based): 47 + - current primary/reason: `corpus_suspect` / `rule34_roman_indicator_before_opening_parenthesis` + ### `mixed_roman_korean_word_before_uppercase_headword_expansion` Of the 10 candidates, 10 are the actual `pending_rule_review` subcluster. The other 0 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. @@ -1198,6 +1252,154 @@ Representative `mismatch` samples: - first differing cell (zero-based): 58 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +### `roman_hyphenated_word_after_whitespace_following_korean_word` + +Of the 361 candidates, 350 are the actual `pending_rule_review` subcluster. The other 11 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. + +For this output-signature audit, 361 mismatches were evaluable and 135 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. + +Localized first-difference transitions: + +- `U+2834 ⠴ -> U+2800 ⠀`: 135 + +Mismatch primary-class distribution: + +- `corpus_suspect`: 6 +- `pending_rule_review`: 350 +- `unsupported_character_review`: 5 + +Representative `localized_mismatch` samples: + +- `sentence_01.json` #567: KBO는 2월 8일부터 제주 서귀포시에서 대한야구소프트볼협회(KBSA)가 선발한 고교 입학 예정 우수선수 30명을 대상으로 Next-Level Training Camp 2차 훈련을 실시할 예정이다. + - expected: `⠊⠗⠇⠶⠪⠐⠥⠀⠴⠠⠝⠑⠭⠞⠤⠠⠇⠑⠧⠑⠇⠀⠠⠞` + - actual: `⠊⠗⠇⠶⠪⠐⠥⠀⠀⠠⠝⠑⠭⠞⠔⠠⠇⠑⠧⠑⠇⠀⠴⠠` + - first differing cell (zero-based): 121 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_02.json` #344: 한편 이장우 대전시장은 이틀간의 대만 방문을 마무리하고 싱가포르로 이동해 18일까지 Merck(머크) 사, 국립싱가포르대학 바이오연구단, A-STAR 및 바이오폴리스 등 바이오산업 관련 해외 기업과 연구기관을 방문할 예정이다. + - expected: `⠥⠡⠈⠍⠊⠒⠐⠀⠴⠠⠁⠤⠠⠠⠌⠜⠲⠀⠑⠕⠆⠀⠘⠣` + - actual: `⠥⠡⠈⠍⠊⠒⠐⠀⠀⠠⠁⠔⠠⠠⠎⠞⠁⠗⠀⠀⠑⠕⠆⠀` + - first differing cell (zero-based): 122 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_03.json` #3485: GC녹십자는 최근 IVIG-SN 10%의 미국 진출에도 박차를 가하고 있다. 지난 4월 오창공장 실사 결과를 바탕으로 이달 내 미국 식품의약국(FDA)에 품목허가 신청서(BLA) 제출을 준비하고 있다. + - expected: `⠉⠵⠀⠰⠽⠈⠵⠀⠴⠠⠠⠊⠧⠊⠛⠤⠠⠠⠎⠝⠀⠼⠁⠚` + - actual: `⠉⠵⠀⠰⠽⠈⠵⠀⠀⠠⠠⠊⠧⠊⠛⠔⠠⠠⠎⠝⠀⠼⠁⠚` + - first differing cell (zero-based): 20 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_04.json` #3969: 동구는 사업 첫해인 지난해 관광객과 시민들의 방문을 유도하기 위해 K-POP 거리·도깨비 골목 등을 체험형 공간으로 꾸미고 골목여행을 위한 여행자의집(ZIP) 등을 새롭게 조성했다. + - expected: `⠈⠕⠀⠍⠗⠚⠗⠀⠴⠠⠅⠤⠠⠠⠏⠕⠏⠲⠀⠈⠎⠐⠕⠐` + - actual: `⠈⠕⠀⠍⠗⠚⠗⠀⠀⠠⠅⠔⠠⠠⠏⠕⠏⠀⠈⠎⠐⠕⠐⠆` + - first differing cell (zero-based): 63 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` + +Representative `mismatch` samples: + +- `sentence_01.json` #52: 과학기술정보통신부와 개인정보보호위원회의 공동 고시 기준에 따라 한국인터넷진흥원(KISA)이 인증하는 ISMS-P는 고객 정보보호 및 개인정보보호를 위한 일련의 조치와 활동이 국가공인 인증기준에 적합함을 증명하는 제도다. + - expected: `⠠⠠⠊⠎⠍⠎⠤⠰⠠⠏⠲⠉⠵⠀⠈⠥⠈⠗⠁⠀⠨⠻⠘⠥` + - actual: `⠠⠠⠊⠎⠍⠎⠤⠰⠏⠲⠉⠵⠀⠈⠥⠈⠗⠁⠀⠨⠻⠘⠥⠘` + - first differing cell (zero-based): 108 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_02.json` #344: 한편 이장우 대전시장은 이틀간의 대만 방문을 마무리하고 싱가포르로 이동해 18일까지 Merck(머크) 사, 국립싱가포르대학 바이오연구단, A-STAR 및 바이오폴리스 등 바이오산업 관련 해외 기업과 연구기관을 방문할 예정이다. + - expected: `⠥⠡⠈⠍⠊⠒⠐⠀⠴⠠⠁⠤⠠⠠⠌⠜⠲⠀⠑⠕⠆⠀⠘⠣` + - actual: `⠥⠡⠈⠍⠊⠒⠐⠀⠀⠠⠁⠔⠠⠠⠎⠞⠁⠗⠀⠀⠑⠕⠆⠀` + - first differing cell (zero-based): 122 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_03.json` #569: 새울원전 관계자는 “APR 1400의 유럽 수출 노형인 EU-APR은 유럽사업자요건(EUR) 인증을 취득했고, 미국 이외 국가로는 처음으로 미국 원자력규제위원회 설계인증을 받아 안전성과 기술력을 국제적으로 인증받고 있다”고 밝혔다. + - expected: `⠟⠀⠴⠠⠠⠑⠥⠤⠠⠠⠁⠏⠗⠲⠵⠀⠩⠐⠎⠃⠇⠎⠃⠨` + - actual: `⠟⠀⠴⠠⠠⠑⠥⠤⠁⠏⠗⠲⠵⠀⠩⠐⠎⠃⠇⠎⠃⠨⠣⠬` + - first differing cell (zero-based): 54 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_04.json` #937: 혼다 CR-V는 예상보다 크고 강한 차다. 준중형 스포츠유틸리티차(SUV)로 규정되지만, 동급에선 공간도 넉넉하고 충분한 힘도 갖췄다. 6년 만에 6세대 완전변경 모델로 힘과 덩치를 모두 키워서 돌아왔다. + - expected: `⠀⠴⠠⠠⠉⠗⠤⠰⠠⠧⠲⠉⠵⠀⠌⠇⠶⠘⠥⠊⠀⠋⠪⠈` + - actual: `⠀⠴⠠⠠⠉⠗⠤⠰⠧⠲⠉⠵⠀⠌⠇⠶⠘⠥⠊⠀⠋⠪⠈⠥` + - first differing cell (zero-based): 11 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` + +### `roman_parenthetical_headword_after_whitespace_following_korean_word` + +Of the 4695 candidates, 1264 are the actual `pending_rule_review` subcluster. The other 3431 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. + +For this output-signature audit, 1353 mismatches were evaluable and 156 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. + +Localized first-difference transitions: + +- `U+2834 ⠴ -> U+2800 ⠀`: 153 +- `U+2834 ⠴ -> U+2830 ⠰`: 2 +- `U+2820 ⠠ -> U+2834 ⠴`: 1 + +Mismatch primary-class distribution: + +- `comparison_method`: 29 +- `corpus_suspect`: 36 +- `pending_rule_review`: 1264 +- `unsupported_character_review`: 24 + +Representative `exact` samples: + +- `sentence_01.json` #4: LG전자가 AI(인공지능) 전문가인 김정희 전무를 인공지능연구소 수장으로 영입해 고객 상황에 최적화된 솔루션을 먼저 제안할 수 있는 AI 기술 고도화에 박차를 가한다. + - expected: `⠴⠠⠠⠇⠛⠲⠨⠾⠨⠫⠀⠴⠠⠠⠁⠊⠦⠄⠟⠈⠿⠨⠕⠉` + - actual: `⠴⠠⠠⠇⠛⠲⠨⠾⠨⠫⠀⠴⠠⠠⠁⠊⠦⠄⠟⠈⠿⠨⠕⠉` + - current primary/reason: `exact` / `exact` +- `sentence_02.json` #47: 정 의원은 지난 2월 통계청에서 발표한 2022년 합계출산율이 0.78명으로 OECD(경제협력개발기구)에 가입한 38개국 중 유일하게 출산율 1명대 이하를 기록하는 등 초저출생 현상이 가속화되고 있다고 지적했다. + - expected: `⠨⠻⠀⠺⠏⠒⠵⠀⠨⠕⠉⠒⠀⠼⠃⠏⠂⠀⠓⠿⠈⠌⠰⠻` + - actual: `⠨⠻⠀⠺⠏⠒⠵⠀⠨⠕⠉⠒⠀⠼⠃⠏⠂⠀⠓⠿⠈⠌⠰⠻` + - current primary/reason: `exact` / `exact` +- `sentence_03.json` #39: 앞서 지난 15일 수원역에서 무궁화 열차에 탑승하려던 장애인 승객 A씨가 자신의 휠체어 탑승이 거부당한 사연이 SNS(사회관계망서비스)를 통해 확산했다. + - expected: `⠣⠲⠠⠎⠀⠨⠕⠉⠒⠀⠼⠁⠑⠕⠂⠀⠠⠍⠏⠒⠱⠁⠝⠠` + - actual: `⠣⠲⠠⠎⠀⠨⠕⠉⠒⠀⠼⠁⠑⠕⠂⠀⠠⠍⠏⠒⠱⠁⠝⠠` + - current primary/reason: `exact` / `exact` +- `sentence_04.json` #146: 인천시가 지역 내 교량과 터널에 설치된 방음시설 중 화재에 취약한 PMMA(폴리메타크릴산메틸) 소재를 불연성 재질인 유리로 교체하기로 했다. + - expected: `⠟⠰⠾⠠⠕⠫⠀⠨⠕⠱⠁⠀⠉⠗⠀⠈⠬⠐⠜⠶⠈⠧⠀⠓` + - actual: `⠟⠰⠾⠠⠕⠫⠀⠨⠕⠱⠁⠀⠉⠗⠀⠈⠬⠐⠜⠶⠈⠧⠀⠓` + - current primary/reason: `exact` / `exact` + +Representative `localized_mismatch` samples: + +- `sentence_01.json` #202: 제이스코홀딩스는 필리핀 니켈 광산사업을 공동 추진중인 EVM(EV Mining &Development)이 광산지질국(MGB)에 4천700헥타르(약 1천400만평)에 대한 탐사허가(EP)를 신청했다고 9일 밝혔다. + - expected: `⠍⠨⠟⠨⠍⠶⠟⠀⠴⠠⠠⠑⠧⠍⠐⠣⠠⠠⠑⠧⠀⠠⠍⠔` + - actual: `⠍⠨⠟⠨⠍⠶⠟⠀⠀⠠⠠⠑⠧⠍⠦⠠⠠⠑⠧⠀⠴⠠⠍⠔` + - first differing cell (zero-based): 56 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_02.json` #733: 아산시는 6일, 자매도시인 말레이시아 현지 최대 신선 과실류 수입업체인 CTG(Chop Tong Guan)와 농특산물 수출 확대를 위한 업무협약(MOU)을 체결했다. + - expected: `⠕⠃⠎⠃⠰⠝⠟⠀⠴⠠⠠⠉⠞⠛⠐⠣⠠⠡⠕⠏⠀⠠⠞⠰` + - actual: `⠕⠃⠎⠃⠰⠝⠟⠀⠀⠠⠠⠉⠞⠛⠦⠠⠉⠓⠕⠏⠀⠴⠠⠞` + - first differing cell (zero-based): 65 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_03.json` #381: 인포테인먼트 시스템 ccNC(connected car Navigation Cockpit)를 최초 탑재하고 무선 소프트웨어 업데이트(OTA) 대상 범위를 주요 편의 기능까지 대폭 확대했다. + - expected: `⠠⠕⠠⠪⠓⠝⠢⠀⠴⠉⠉⠠⠠⠝⠉⠐⠣⠒⠝⠑⠉⠞⠫⠀` + - actual: `⠠⠕⠠⠪⠓⠝⠢⠀⠀⠉⠉⠠⠠⠝⠉⠦⠉⠕⠝⠝⠑⠉⠞⠑` + - first differing cell (zero-based): 19 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_04.json` #1075: 접속 방법은 ‘안심전세포털’ 및 국토부 홈페이지에 안내돼 있다. 줌 URL(ID : 393 490 1984)을 입력하거나 주택도시보증공사(HUG) 유튜브로 접속하면 된다. + - expected: `⠌⠊⠲⠀⠨⠍⠢⠀⠴⠠⠠⠥⠗⠇⠐⠣⠠⠠⠊⠙⠀⠒⠀⠼` + - actual: `⠌⠊⠲⠀⠨⠍⠢⠀⠀⠠⠠⠥⠗⠇⠦⠠⠠⠊⠙⠀⠐⠂⠀⠼` + - first differing cell (zero-based): 70 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` + +Representative `mismatch` samples: + +- `sentence_01.json` #18: 2023년형 신제품은 매터(Matter)와 HCA(Home Connectivity Alliance) 표준을 지원하는 스마트싱스 허브(SmartThings Hub)를 기반으로 다양한 기기를 자동으로 연결하고, 제어·관리할 수 있는 것이 특징이다. + - expected: `⠁⠞⠞⠻⠠⠴⠧⠀⠴⠠⠠⠓⠉⠁⠐⠣⠠⠓⠕⠍⠑⠀⠠⠒` + - actual: `⠁⠞⠞⠻⠠⠴⠧⠀⠀⠠⠠⠓⠉⠁⠦⠠⠓⠕⠍⠑⠀⠴⠠⠒` + - first differing cell (zero-based): 37 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_02.json` #14: 한국타이어앤테크놀로지㈜(대표이사 이수일, 이하 한국타이어)가 국제자동차연맹(FIA)이 주관하는 ‘FIA 주니어 ERC(FIA Junior ERC, 이하 주니어 ERC)’ 대회에 레이싱 타이어를 독점 공급한다. + - expected: `⠚⠒⠈⠍⠁⠓⠣⠕⠎⠗⠒⠓⠝⠋⠪⠉⠥⠂⠐⠥⠨⠕⠦⠄` + - actual: `` + - error: `Invalid symbol character` + - first differing cell (zero-based): 0 + - current primary/reason: `unsupported_character_review` / `unsupported_character_review` +- `sentence_03.json` #50: 당시 CC(폐쇄)TV에 찍힌 장면을 보면 A씨는 엘리베이터를 기다리는 B씨를 발견하자 몰래 뒤로 다가가 갑자기 피해 여성의 머리를 뒤에서 돌려차기로 가격하는 등 폭행했다. + - expected: `⠀⠴⠠⠠⠉⠉⠦⠄⠙⠌⠠⠧⠗⠠⠴⠴⠠⠠⠞⠧⠲⠝⠀⠠` + - actual: `⠀⠴⠠⠠⠉⠉⠦⠄⠸⠷⠙⠌⠠⠧⠗⠸⠾⠠⠴⠰⠠⠠⠞⠧` + - first differing cell (zero-based): 12 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_04.json` #191: SK온은 23일 서울 종로구 SK서린사옥에서 에코프로머티리얼즈, 중국의 GEM(거린메이)과 전구체 생산을 위한 3자 합작법인인 지이엠코리아뉴에너지머티리얼즈㈜(이하 지이엠코리아) 설립 양해각서(MOU)를 체결했다고 밝혔다. + - expected: `⠴⠠⠠⠎⠅⠲⠷⠵⠀⠼⠃⠉⠕⠂⠀⠠⠎⠯⠀⠨⠿⠐⠥⠈` + - actual: `` + - error: `Invalid symbol character` + - first differing cell (zero-based): 0 + - current primary/reason: `unsupported_character_review` / `unsupported_character_review` + ### `roman_run_immediately_before_attached_middle_dot_boundary` Of the 577 candidates, 565 are the actual `pending_rule_review` subcluster. The other 12 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. @@ -1798,6 +2000,21 @@ UEB rule 5.6.1 says the numeric indicator establishes grade-1 mode, and rule 5.6 UEB rule 5.7.2 prints `CD-ROM` with one grade-1 indicator before `CD` and no second grade-1 indicator after the hyphen. Korean rule 29 similarly uses one Roman span for consecutive Roman text. Before the engine change, the broad diagnostic contained 952 candidates / 32 exact / 920 mismatch, with 312 localized `⠠ -> ⠰` and 2 reverse transitions. A blanket uppercase-suffix removal reached 67,222 (+210) but made the broad cohort's single-capital controls such as `Around-U`, `DALL-E`, `ISMS-P`, and `USB-C` non-exact; it was rejected. Requiring only a two-letter uppercase suffix reached 67,162 (+150) but regressed the mixed-prefix exact control `Ko-LLM`; it was also rejected. The retained boundary matches the complete PDF shape: the immediately adjacent prefix is a pure-uppercase letter segment and the immediately adjacent suffix is a pure-uppercase segment of at least two letters. It reaches 67,138 (+126) while preserving all 32 baseline exact controls. The broad diagnostic now contains 952 candidates / 158 exact / 794 mismatch, with 157 localized `⠠ -> ⠰` and 3 reverse transitions. The dedicated `pure_allcaps_segment_before_hyphen_and_multi_allcaps_segment_after` row reports only the implemented subset; broad mixed-case and single-capital members remain controls or pending review. `K-ALM` is the one new reverse surface but was already a mismatch before this change, not an exact regression. Digit-hyphen forms such as `F-35` remain excluded, and the complete-shortform guard still legitimately precedes `CD` in `CD-ROM`. +## Roman-entry residual cohorts after grade-1 localization + +These three cohorts split the former dominant `⠴ -> blank` residual by the input structure at the actual first-difference location. Their entry boundary is anchored by independently encoding the input prefix and requiring it to equal the full current-engine output prefix; repeated Roman text elsewhere cannot satisfy the locator. A localized count is recorded only when no earlier output-localized cohort already claims that first difference. Candidate membership remains cross-cutting and never changes a primary class. + +Korean rule 29 requires a Roman indicator before Roman text in a Korean sentence. Rules 33-35 define the relevant hyphen, enclosure, and number boundaries. Independently, math rules 2, 6, 11, 12, and 45 permit subtraction, parentheses, Roman variables, and function notation with overlapping ASCII surface forms. The surface gates below therefore cannot by themselves exclude a mathematical reading. + +| Cohort | Candidates | Exact controls | Mismatch | Pending | Corpus suspect | Localized `⠴ -> blank` | Reverse `blank -> ⠴` | +|---|---:|---:|---:|---:|---:|---:|---:| +| `roman_hyphenated_word_after_whitespace_following_korean_word` | 361 | 0 | 361 | 350 | 6 | 135 | 0 | +| `roman_parenthetical_headword_after_whitespace_following_korean_word` | 4695 | 3342 | 1353 | 1264 | 36 | 153 | 0 | +| `korean_prefixed_roman_parenthetical_followed_by_allcaps_hyphen_suffix` | 11 | 0 | 11 | 6 | 5 | 5 | 0 | + +The whitespace parenthetical-headword cohort also retains 1 localized `⠠ -> ⠴` and 2 localized `⠴ -> ⠰` cases as separate transitions; they are not folded into the target. The exact controls demonstrate that the broad structure is already correct in many sentences, while the attached parenthetical-hyphen cohort has no exact control and includes cases already classified by the stricter rule-34 reference-order contradiction. Consequently none of these measurements authorizes an engine change; they are deterministic pending/corpus-review diagnostics only. + + The HCA-style headword-expansion shape described above is not an engine implementation premise. The 2024 PDF's math rule 6 defines parentheses and grouping parentheses, rule 11 defines mathematical-expression spacing, rule 12 covers Roman letters in formulas as well as Korean sentences, and rule 45 shows Roman-letter function notation followed by parentheses. Excluding visible operators, scripts, and nesting narrows this corpus cohort, but the PDF does not make the remaining surface shape sufficient to rule out every mathematical counterexample. The cluster therefore remains conservative pending-review evidence only. The standalone-uppercase cohort is similarly ambiguous. Hangeul rule 28's appendix defines the capital-word indicator for two or more consecutive capitals, and rule 29 defines Roman indicators around Roman text in a Korean sentence. But math rule 12 also uses uppercase Roman variables, while science rule 7 uses uppercase runs in chemical formulas. The input gate cannot determine which semantic regime applies, so its output differences are observations to review, not permission to infer an engine rule from the corpus reference. diff --git a/libs/braillify/examples/nikl_corpus_analyze.rs b/libs/braillify/examples/nikl_corpus_analyze.rs index ecf65eba..4b509085 100644 --- a/libs/braillify/examples/nikl_corpus_analyze.rs +++ b/libs/braillify/examples/nikl_corpus_analyze.rs @@ -597,6 +597,12 @@ const ROMAN_UPPERCASE_AFTER_HYPHEN: &str = "uppercase_ascii_run_immediately_after_hyphen_in_roman_sequence"; const PURE_ALLCAPS_HYPHEN_MULTI_ALLCAPS: &str = "pure_allcaps_segment_before_hyphen_and_multi_allcaps_segment_after"; +const ROMAN_HYPHENATED_WORD_AFTER_KOREAN_WORD: &str = + "roman_hyphenated_word_after_whitespace_following_korean_word"; +const ROMAN_PARENTHETICAL_HEADWORD_AFTER_KOREAN_WORD: &str = + "roman_parenthetical_headword_after_whitespace_following_korean_word"; +const KOREAN_PREFIXED_ROMAN_PARENTHETICAL_HYPHEN_SUFFIX: &str = + "korean_prefixed_roman_parenthetical_followed_by_allcaps_hyphen_suffix"; #[derive(Clone, Copy, Debug, PartialEq, Eq)] struct InputSpan { @@ -1096,6 +1102,113 @@ fn pure_allcaps_hyphen_multi_allcaps_spans(input: &str) -> Vec { .collect() } +fn preceding_whitespace_word_contains_korean(input: &str, start_byte: usize) -> bool { + let before = &input[..start_byte]; + if !before.chars().next_back().is_some_and(char::is_whitespace) { + return false; + } + before + .trim_end_matches(char::is_whitespace) + .rsplit(char::is_whitespace) + .next() + .is_some_and(|word| word.chars().any(is_korean_script)) +} + +/// Narrows the broad hyphen-continuation trait to a new Roman word after a +/// whitespace-delimited Korean-containing word. This separates entry-mode +/// routing (`A-STAR`) from the already measured grade-1 boundary inside a +/// Roman run (`CD-ROM`). It remains diagnostic because math rule 2 gives the +/// same hyphen-minus a subtraction reading. +fn roman_hyphenated_word_after_korean_word_spans(input: &str) -> Vec { + roman_uppercase_after_hyphen_spans(input) + .into_iter() + .filter(|span| { + input.as_bytes()[span.start_byte].is_ascii_alphabetic() + && preceding_whitespace_word_contains_korean(input, span.start_byte) + }) + .collect() +} + +/// Finds a two-or-more-letter Roman headword immediately after a whitespace- +/// delimited Korean-containing word and immediately before a non-empty closed +/// parenthetical. The parenthetical may contain prose or notation; no semantic +/// choice between Korean rule 29 and math rules 6/12/45 is inferred. +fn roman_parenthetical_headword_after_korean_word_spans(input: &str) -> Vec { + let bytes = input.as_bytes(); + let mut spans = Vec::new(); + for (open, _) in input.match_indices('(') { + let mut start_byte = open; + while start_byte > 0 && bytes[start_byte - 1].is_ascii_alphabetic() { + start_byte -= 1; + } + if open - start_byte < 2 || !preceding_whitespace_word_contains_korean(input, start_byte) { + continue; + } + let body_start = open + 1; + let Some((close_offset, close)) = input[body_start..] + .char_indices() + .find(|(_, ch)| matches!(ch, '(' | ')')) + else { + continue; + }; + if close == ')' && close_offset > 0 { + spans.push(InputSpan { + start_byte, + end_byte: open, + }); + } + } + spans +} + +/// Separates an attached rule-34-shaped Roman parenthetical followed by a +/// hyphenated all-caps suffix from both whitespace Roman entry and ordinary +/// all-caps hyphen continuation. The strict body/suffix gate is structural +/// evidence only; rule 34 and math rules 6/12 still permit competing modes. +fn korean_prefixed_roman_parenthetical_hyphen_suffix_spans(input: &str) -> Vec { + let bytes = input.as_bytes(); + let mut spans = Vec::new(); + for (open, _) in input.match_indices('(') { + if !input[..open] + .chars() + .next_back() + .is_some_and(is_korean_script) + { + continue; + } + let body_start = open + 1; + let Some(close_offset) = input[body_start..].find(')') else { + continue; + }; + let close = body_start + close_offset; + let body = &input[body_start..close]; + if body.len() < 2 || !body.bytes().all(|byte| byte.is_ascii_uppercase()) { + continue; + } + if bytes.get(close + 1) != Some(&b'-') { + continue; + } + let suffix_start = close + 2; + let mut end_byte = suffix_start; + while bytes.get(end_byte).is_some_and(u8::is_ascii_uppercase) { + end_byte += 1; + } + if end_byte - suffix_start < 2 + || input[end_byte..] + .chars() + .next() + .is_some_and(|next| next.is_ascii_alphanumeric()) + { + continue; + } + spans.push(InputSpan { + start_byte: open, + end_byte, + }); + } + spans +} + /// Locates each detected run in the full current-engine output by searching /// for that run's independently encoded signature. This uses neither the /// corpus reference nor a hard-coded braille value. @@ -1175,7 +1288,7 @@ fn mixed_korean_word_signature(run: &str) -> Option { Some(probe_cells.get(start..end)?.iter().collect()) } -fn grade1_cohort_signature_ranges( +fn roman_entry_signature_ranges( input: &str, actual: &str, spans: &[InputSpan], @@ -1235,7 +1348,52 @@ fn first_difference_in_grade1_cohort_spans(item: &EncodedCase, spans: &[InputSpa ) { return false; } - grade1_cohort_signature_ranges(&item.located.case.input, actual, spans, 1) + roman_entry_signature_ranges(&item.located.case.input, actual, spans, 1) + .into_iter() + .any(|range| range.contains(&first_difference)) +} + +/// Locates the actual output boundary corresponding to an input span start. +/// The prefix is encoded independently and must be byte-for-byte equal to the +/// full output prefix, so a repeated Roman surface elsewhere cannot satisfy +/// the locator. The short range covers only mode-entry cells, not the run. +fn current_engine_input_entry_ranges( + input: &str, + actual: &str, + spans: &[InputSpan], + entry_cells: usize, +) -> Vec> { + let actual_cells = actual.chars().collect::>(); + let mut ranges = BTreeSet::new(); + for span in spans { + let Ok(prefix) = braillify::encode_to_unicode(&input[..span.start_byte]) else { + continue; + }; + let prefix_cells = prefix.chars().collect::>(); + if actual_cells.starts_with(&prefix_cells) { + let start = prefix_cells.len(); + let end = start.saturating_add(entry_cells).min(actual_cells.len()); + if start < end { + ranges.insert((start, end)); + } + } + } + ranges.into_iter().map(|(start, end)| start..end).collect() +} + +fn first_difference_at_input_span_entry( + item: &EncodedCase, + spans: &[InputSpan], + entry_cells: usize, +) -> bool { + let Ok(actual) = &item.actual else { + return false; + }; + if actual == &item.located.case.unicode { + return false; + } + let first_difference = first_difference_cell(&item.located.case.unicode, actual); + current_engine_input_entry_ranges(&item.located.case.input, actual, spans, entry_cells) .into_iter() .any(|range| range.contains(&first_difference)) } @@ -1422,7 +1580,7 @@ fn first_difference_claimed_by_prior_localized_cohort(item: &EncodedCase) -> boo ) } -fn first_difference_claimed_by_localized_cohort(item: &EncodedCase) -> bool { +fn first_difference_claimed_before_roman_entry_residual(item: &EncodedCase) -> bool { first_difference_claimed_by_prior_localized_cohort(item) || first_difference_in_grade1_cohort_spans( item, @@ -1438,6 +1596,25 @@ fn first_difference_claimed_by_localized_cohort(item: &EncodedCase) -> bool { ) } +fn first_difference_claimed_by_localized_cohort(item: &EncodedCase) -> bool { + first_difference_claimed_before_roman_entry_residual(item) + || first_difference_at_input_span_entry( + item, + &roman_hyphenated_word_after_korean_word_spans(&item.located.case.input), + 2, + ) + || first_difference_at_input_span_entry( + item, + &roman_parenthetical_headword_after_korean_word_spans(&item.located.case.input), + 2, + ) + || first_difference_at_input_span_entry( + item, + &korean_prefixed_roman_parenthetical_hyphen_suffix_spans(&item.located.case.input), + 2, + ) +} + /// Input-only candidate gate for acronym expansions such as /// `HCA(Home Connectivity Alliance)`. /// @@ -2097,6 +2274,18 @@ fn analyze( PURE_ALLCAPS_HYPHEN_MULTI_ALLCAPS.to_string(), PendingRuleReviewClusterStats::default(), ), + ( + ROMAN_HYPHENATED_WORD_AFTER_KOREAN_WORD.to_string(), + PendingRuleReviewClusterStats::default(), + ), + ( + ROMAN_PARENTHETICAL_HEADWORD_AFTER_KOREAN_WORD.to_string(), + PendingRuleReviewClusterStats::default(), + ), + ( + KOREAN_PREFIXED_ROMAN_PARENTHETICAL_HYPHEN_SUFFIX.to_string(), + PendingRuleReviewClusterStats::default(), + ), ]); let mut pending_first_difference_cell_transitions = BTreeMap::new(); let mut pending_first_difference_transitions_after_localized_cohorts = BTreeMap::new(); @@ -2315,6 +2504,53 @@ fn analyze( ), true, ), + ( + ROMAN_HYPHENATED_WORD_AFTER_KOREAN_WORD, + !roman_hyphenated_word_after_korean_word_spans(&item.located.case.input).is_empty(), + Some( + !first_difference_claimed_before_roman_entry_residual(item) + && first_difference_at_input_span_entry( + item, + &roman_hyphenated_word_after_korean_word_spans( + &item.located.case.input, + ), + 2, + ), + ), + true, + ), + ( + ROMAN_PARENTHETICAL_HEADWORD_AFTER_KOREAN_WORD, + !roman_parenthetical_headword_after_korean_word_spans(&item.located.case.input) + .is_empty(), + Some( + !first_difference_claimed_before_roman_entry_residual(item) + && first_difference_at_input_span_entry( + item, + &roman_parenthetical_headword_after_korean_word_spans( + &item.located.case.input, + ), + 2, + ), + ), + true, + ), + ( + KOREAN_PREFIXED_ROMAN_PARENTHETICAL_HYPHEN_SUFFIX, + !korean_prefixed_roman_parenthetical_hyphen_suffix_spans(&item.located.case.input) + .is_empty(), + Some( + !first_difference_claimed_before_roman_entry_residual(item) + && first_difference_at_input_span_entry( + item, + &korean_prefixed_roman_parenthetical_hyphen_suffix_spans( + &item.located.case.input, + ), + 2, + ), + ), + true, + ), ] { if !present { continue; @@ -3005,6 +3241,83 @@ fn markdown(report: &AnalysisReport) -> String { regression. Digit-hyphen forms such as `F-35` remain excluded, and the \ complete-shortform guard still legitimately precedes `CD` in `CD-ROM`.\n\n", ); + text.push_str("\n## Roman-entry residual cohorts after grade-1 localization\n\n"); + text.push_str( + "These three cohorts split the former dominant `⠴ -> blank` residual by the input \ + structure at the actual first-difference location. Their entry boundary is anchored by \ + independently encoding the input prefix and requiring it to equal the full current-engine \ + output prefix; repeated Roman text elsewhere cannot satisfy the locator. A localized count \ + is recorded only when no earlier output-localized cohort already claims that first \ + difference. Candidate membership remains cross-cutting and never changes a primary class.\n\n\ + Korean rule 29 requires a Roman indicator before Roman text in a Korean sentence. Rules \ + 33-35 define the relevant hyphen, enclosure, and number boundaries. Independently, math \ + rules 2, 6, 11, 12, and 45 permit subtraction, parentheses, Roman variables, and function \ + notation with overlapping ASCII surface forms. The surface gates below therefore cannot \ + by themselves exclude a mathematical reading.\n\n", + ); + text.push_str( + "| Cohort | Candidates | Exact controls | Mismatch | Pending | Corpus suspect | \ + Localized `⠴ -> blank` | Reverse `blank -> ⠴` |\n\ + |---|---:|---:|---:|---:|---:|---:|---:|\n", + ); + for name in [ + ROMAN_HYPHENATED_WORD_AFTER_KOREAN_WORD, + ROMAN_PARENTHETICAL_HEADWORD_AFTER_KOREAN_WORD, + KOREAN_PREFIXED_ROMAN_PARENTHETICAL_HYPHEN_SUFFIX, + ] { + let stats = report + .pending_rule_review_clusters + .get(name) + .expect("registered Roman-entry cohort must exist"); + let pending = stats + .mismatch_primary_classes + .get("pending_rule_review") + .copied() + .unwrap_or(0); + let corpus_suspect = stats + .mismatch_primary_classes + .get("corpus_suspect") + .copied() + .unwrap_or(0); + let target = stats + .first_difference_in_output_signature_transitions + .get("U+2834 ⠴ -> U+2800 ⠀") + .copied() + .unwrap_or(0); + let reverse = stats + .first_difference_in_output_signature_transitions + .get("U+2800 ⠀ -> U+2834 ⠴") + .copied() + .unwrap_or(0); + text.push_str(&format!( + "| `{name}` | {} | {} | {} | {pending} | {corpus_suspect} | {target} | \ + {reverse} |\n", + stats.candidates, stats.exact, stats.mismatch + )); + } + let parenthetical = report + .pending_rule_review_clusters + .get(ROMAN_PARENTHETICAL_HEADWORD_AFTER_KOREAN_WORD) + .expect("registered parenthetical-headword cohort must exist"); + let capital_to_roman = parenthetical + .first_difference_in_output_signature_transitions + .get("U+2820 ⠠ -> U+2834 ⠴") + .copied() + .unwrap_or(0); + let roman_to_grade1 = parenthetical + .first_difference_in_output_signature_transitions + .get("U+2834 ⠴ -> U+2830 ⠰") + .copied() + .unwrap_or(0); + text.push_str(&format!( + "\nThe whitespace parenthetical-headword cohort also retains {capital_to_roman} localized \ + `⠠ -> ⠴` and {roman_to_grade1} localized `⠴ -> ⠰` cases as separate transitions; they \ + are not folded into the target. The exact controls demonstrate that the broad structure \ + is already correct in many sentences, while the attached parenthetical-hyphen cohort \ + has no exact control and includes cases already classified by the stricter rule-34 \ + reference-order contradiction. Consequently none of these measurements authorizes an \ + engine change; they are deterministic pending/corpus-review diagnostics only.\n\n" + )); text.push_str( "\nThe HCA-style headword-expansion shape described above is not an engine \ implementation premise. The 2024 PDF's math rule 6 \ @@ -4244,6 +4557,56 @@ mod tests { assert_eq!(actual, expected); } + #[rstest::rstest] + #[case::after_korean_word("연구단, A-STAR 방문", vec!["A-STAR"])] + #[case::after_ascii_word("research A-STAR 방문", vec![])] + #[case::without_whitespace("연구단,A-STAR 방문", vec![])] + #[case::digit_hyphen_is_separate("연구단 F-35 방문", vec![])] + #[case::numeric_leading_run("지표 2-CE 결과", vec![])] + fn detects_hyphenated_roman_word_after_korean_word( + #[case] input: &str, + #[case] expected: Vec<&str>, + ) { + let actual = roman_hyphenated_word_after_korean_word_spans(input) + .into_iter() + .map(|span| &input[span.start_byte..span.end_byte]) + .collect::>(); + assert_eq!(actual, expected); + } + + #[rstest::rstest] + #[case::mixed_case_headword("시스템 ccNC(connected car) 탑재", vec!["ccNC"])] + #[case::allcaps_headword("줌 URL(ID : 3) 입력", vec!["URL"])] + #[case::after_ascii_word("system URL(ID) 입력", vec![])] + #[case::single_letter("시스템 A(x) 입력", vec![])] + #[case::nested_parenthetical("시스템 URL(ID(x)) 입력", vec![])] + fn detects_parenthetical_roman_headword_after_korean_word( + #[case] input: &str, + #[case] expected: Vec<&str>, + ) { + let actual = roman_parenthetical_headword_after_korean_word_spans(input) + .into_iter() + .map(|span| &input[span.start_byte..span.end_byte]) + .collect::>(); + assert_eq!(actual, expected); + } + + #[rstest::rstest] + #[case::attached_structure("퀀텀닷(QD)-OLED 패널", vec!["(QD)-OLED"])] + #[case::without_korean_prefix("(QD)-OLED 패널", vec![])] + #[case::mixed_case_body("퀀텀닷(Qd)-OLED 패널", vec![])] + #[case::single_cap_suffix("퀀텀닷(QD)-O 패널", vec![])] + fn detects_korean_prefixed_parenthetical_hyphen_suffix( + #[case] input: &str, + #[case] expected: Vec<&str>, + ) { + let actual = korean_prefixed_roman_parenthetical_hyphen_suffix_spans(input) + .into_iter() + .map(|span| &input[span.start_byte..span.end_byte]) + .collect::>(); + assert_eq!(actual, expected); + } + #[rstest::rstest] #[case::shortform_prefix("가(WD) 나", allcaps_shortform_prefix_spans("가(WD) 나"))] #[case::numeric_continuation("가(Li2S) 나", roman_uppercase_after_digit_spans("가(Li2S) 나"))] @@ -4256,8 +4619,31 @@ mod tests { #[case] spans: Vec, ) { let actual = braillify::encode_to_unicode(input).expect("grade-1 probe must encode"); - let ranges = grade1_cohort_signature_ranges(input, &actual, &spans, 1); + let ranges = roman_entry_signature_ranges(input, &actual, &spans, 1); + + assert_eq!(ranges.len(), 1); + assert!(ranges[0].start < ranges[0].end); + assert!(ranges[0].end <= actual.chars().count()); + } + #[rstest::rstest] + #[case::hyphenated_word( + "연구단, A-STAR 방문", + roman_hyphenated_word_after_korean_word_spans("연구단, A-STAR 방문") + )] + #[case::parenthetical_headword( + "시스템 ccNC(connected car) 탑재", + roman_parenthetical_headword_after_korean_word_spans("시스템 ccNC(connected car) 탑재") + )] + #[case::attached_parenthetical_suffix( + "퀀텀닷(QD)-OLED 패널", + korean_prefixed_roman_parenthetical_hyphen_suffix_spans("퀀텀닷(QD)-OLED 패널") + )] + fn locates_roman_entry_residual_boundary(#[case] input: &str, #[case] spans: Vec) { + let actual = braillify::encode_to_unicode(input).expect("Roman entry probe must encode"); + let ranges = current_engine_input_entry_ranges(input, &actual, &spans, 2); + + assert_eq!(spans.len(), 1); assert_eq!(ranges.len(), 1); assert!(ranges[0].start < ranges[0].end); assert!(ranges[0].end <= actual.chars().count()); From 266e70cb550a824387166f1eb732b5abbe96f1a9 Mon Sep 17 00:00:00 2001 From: devfive Date: Tue, 1 Sep 2026 11:57:05 +0900 Subject: [PATCH 30/77] Diagnose consecutive Roman reentry --- docs/corpus-analysis/NIKL_2025_V1.md | 180 +++++++++++++----- .../braillify/examples/nikl_corpus_analyze.rs | 173 ++++++++++++++++- 2 files changed, 309 insertions(+), 44 deletions(-) diff --git a/docs/corpus-analysis/NIKL_2025_V1.md b/docs/corpus-analysis/NIKL_2025_V1.md index 12f55307..9a77e71e 100644 --- a/docs/corpus-analysis/NIKL_2025_V1.md +++ b/docs/corpus-analysis/NIKL_2025_V1.md @@ -298,49 +298,26 @@ This ranking removes only cases whose first difference is inside an existing out | Rank | Expected → actual first cell | Residual cases | |---:|---|---:| -| 1 | `U+2820 ⠠ -> U+2834 ⠴` | 455 | -| 2 | `U+2826 ⠦ -> U+2834 ⠴` | 451 | -| 3 | `U+280E ⠎ -> U+280C ⠌` | 444 | -| 4 | `U+2808 ⠈ -> U+2832 ⠲` | 409 | -| 5 | `U+2826 ⠦ -> U+2800 ⠀` | 393 | -| 6 | `U+2801 ⠁ -> U+281C ⠜` | 391 | -| 7 | `U+2834 ⠴ -> U+2830 ⠰` | 326 | -| 8 | `U+2811 ⠑ -> U+282B ⠫` | 323 | -| 9 | `U+2810 ⠐ -> U+2832 ⠲` | 288 | -| 10 | `U+2832 ⠲ -> U+2838 ⠸` | 276 | -| 11 | `U+2834 ⠴ -> U+2800 ⠀` | 256 | -| 12 | `U+2800 ⠀ -> U+2832 ⠲` | 225 | -| 13 | `U+2810 ⠐ -> U+2802 ⠂` | 210 | -| 14 | `U+2817 ⠗ -> U+2820 ⠠` | 163 | -| 15 | `U+2820 ⠠ -> U+2809 ⠉` | 159 | -| 16 | `U+283C ⠼ -> U+2800 ⠀` | 153 | -| 17 | `U+281B ⠛ -> U+2823 ⠣` | 144 | -| 18 | `U+2826 ⠦ -> U+2810 ⠐` | 141 | -| 19 | `U+2810 ⠐ -> U+2800 ⠀` | 129 | -| 20 | `U+2811 ⠑ -> U+283B ⠻` | 100 | - -### Residual `U+2820 ⠠ -> U+2834 ⠴` - -- `sentence_01.json` #78: 2023년형 Neo QLED는 스마트싱스뿐 아니라 매터(Matter) 기기까지 지원하는 원칩 모듈을 탑재해 별도의 동글을 사용하지 않아도, 다양한 사물인터넷(IoT) 기기를 직접 TV에 연결해 사용할 수 있다. - - expected: `⠻⠀⠴⠠⠝⠑⠕⠀⠠⠠⠟⠇⠑⠙⠲⠉⠵⠀⠠⠪⠑⠓⠪⠠` - - actual: `⠻⠀⠴⠠⠝⠑⠕⠀⠴⠠⠠⠟⠇⠫⠲⠉⠵⠀⠠⠪⠑⠓⠪⠠` - - first differing cell (zero-based): 17 - - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_02.json` #332: 홍성군청소년수련관(관장 피기용) 진로체험지원센터가 오는 16일 청운대학교 운동장에서 관내 중학교 1학년 대상으로 제9회 홍성군 청소년 진로박람회 ‘꿈(JOB)고 두드림(DO DREAM)’을 진행한다. - - expected: `⠦⠄⠴⠠⠠⠙⠕⠀⠠⠠⠙⠗⠂⠍⠠⠴⠴⠄⠮⠀⠨⠟⠚⠗` - - actual: `⠦⠄⠴⠠⠠⠙⠕⠀⠴⠠⠠⠙⠗⠂⠍⠠⠴⠴⠄⠮⠀⠨⠟⠚` - - first differing cell (zero-based): 183 - - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_03.json` #302: 건국대 경영전문대학원(KU MBA)은 2015년부터 국제경영대학발전협의회(AACSB) 인증을 보유하고 있다. 고등교육법 시행령에 따라 교육부가 인가한 13개 경영전문대학원 중 하나다. 경영전문대학원인 만큼 엄격하게 교육 품질을 관리한다. - - expected: `⠦⠄⠴⠠⠠⠅⠥⠀⠠⠠⠍⠃⠁⠠⠴⠵⠀⠼⠃⠚⠁⠑⠀⠉` - - actual: `⠦⠄⠴⠠⠠⠅⠥⠀⠴⠠⠠⠍⠃⠁⠠⠴⠵⠀⠼⠃⠚⠁⠑⠀` - - first differing cell (zero-based): 29 - - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_04.json` #363: 켈리(KELLY)는 ‘KEEP NATUALLY’의 줄임말로 인위적인 것을 최소화하고 자연주의적인 원료, 공법, 맛을 추구한다는 의미를 담고 있다. - - expected: `⠴⠠⠠⠅⠑⠑⠏⠀⠠⠠⠝⠁⠞⠥⠁⠇⠇⠽⠴⠄⠺⠀⠨⠯` - - actual: `⠴⠠⠠⠅⠑⠑⠏⠀⠴⠠⠠⠝⠁⠞⠥⠁⠇⠇⠽⠴⠄⠺⠀⠨` - - first differing cell (zero-based): 30 - - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +| 1 | `U+2826 ⠦ -> U+2834 ⠴` | 451 | +| 2 | `U+280E ⠎ -> U+280C ⠌` | 444 | +| 3 | `U+2808 ⠈ -> U+2832 ⠲` | 409 | +| 4 | `U+2826 ⠦ -> U+2800 ⠀` | 393 | +| 5 | `U+2801 ⠁ -> U+281C ⠜` | 390 | +| 6 | `U+2834 ⠴ -> U+2830 ⠰` | 326 | +| 7 | `U+2811 ⠑ -> U+282B ⠫` | 323 | +| 8 | `U+2810 ⠐ -> U+2832 ⠲` | 288 | +| 9 | `U+2832 ⠲ -> U+2838 ⠸` | 276 | +| 10 | `U+2834 ⠴ -> U+2800 ⠀` | 256 | +| 11 | `U+2800 ⠀ -> U+2832 ⠲` | 223 | +| 12 | `U+2810 ⠐ -> U+2802 ⠂` | 210 | +| 13 | `U+2817 ⠗ -> U+2820 ⠠` | 163 | +| 14 | `U+2820 ⠠ -> U+2809 ⠉` | 159 | +| 15 | `U+283C ⠼ -> U+2800 ⠀` | 153 | +| 16 | `U+281B ⠛ -> U+2823 ⠣` | 144 | +| 17 | `U+2826 ⠦ -> U+2810 ⠐` | 141 | +| 18 | `U+2810 ⠐ -> U+2800 ⠀` | 129 | +| 19 | `U+2811 ⠑ -> U+283B ⠻` | 100 | +| 20 | `U+2810 ⠐ -> U+2814 ⠔` | 98 | ### Residual `U+2826 ⠦ -> U+2834 ⠴` @@ -549,6 +526,29 @@ This ranking removes only cases whose first difference is inside an existing out - first differing cell (zero-based): 100 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +### Residual `U+2834 ⠴ -> U+2800 ⠀` + +- `sentence_01.json` #735: 로이드 오스틴 미국 국방성 장관이 30일 오후 일명 ‘최후 심판의 날 항공기’로 불리는 E-4B 나이트워치(NightWatch)를 타고 방한했다. + - expected: `⠀⠘⠯⠐⠕⠉⠵⠀⠴⠠⠑⠤⠼⠙⠠⠃⠲⠀⠉⠣⠕⠓⠪⠏` + - actual: `⠀⠘⠯⠐⠕⠉⠵⠀⠀⠠⠑⠔⠼⠙⠠⠃⠀⠉⠣⠕⠓⠪⠏⠰` + - first differing cell (zero-based): 83 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_02.json` #633: 두 학과는 올해 2학기부터 지방대학 특성화 사업 및 마이크로디그리(MD) 융합전공인 ‘빅데이터 기반 영상문화콘텐츠 개발’ 과정을 함께 운영하고 있으며, LINC3.0 사업에도 참여하고 있다. + - expected: `⠀⠕⠌⠪⠑⠱⠐⠀⠴⠠⠠⠇⠔⠉⠼⠉⠲⠚⠀⠇⠎⠃⠝⠊` + - actual: `⠀⠕⠌⠪⠑⠱⠐⠀⠀⠠⠠⠇⠊⠝⠉⠼⠉⠲⠚⠀⠀⠇⠎⠃` + - first differing cell (zero-based): 148 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_03.json` #1285: 한편, 디앤지컴퍼니본은 테마파크와 외식시설 개발운영에 특화된 종합 테마파크 기업이다. 월미테마파크를 운영하고 있으며 너티차일드 키즈테마파크, 트라이아스 패밀리레스토랑, C27, 도넛킬러, 번트커피 등 다양한 F&B(식음) 브랜드를 보유하고 있다. + - expected: `⠪⠓⠥⠐⠣⠶⠐⠀⠴⠠⠉⠼⠃⠛⠐⠀⠊⠥⠉⠎⠄⠋⠕⠂` + - actual: `⠪⠓⠥⠐⠣⠶⠐⠀⠀⠠⠉⠼⠃⠛⠐⠀⠀⠊⠥⠉⠎⠄⠋⠕` + - first differing cell (zero-based): 152 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_04.json` #1633: KAI는 이번 ‘LIMA 2023’에 말레이시아 공군이 운용할 FA-50M 버전과 함께 세계 시장이 관심을 갖는 국산 초음속 전투기 KF-21, 수리온 헬기, 소형무장헬기(LAH) 등 주력 제품을 전시했다. + - expected: `⠕⠀⠛⠬⠶⠚⠂⠀⠴⠠⠠⠋⠁⠤⠼⠑⠚⠠⠍⠲⠀⠘⠎⠨` + - actual: `⠕⠀⠛⠬⠶⠚⠂⠀⠀⠠⠠⠋⠁⠔⠼⠑⠚⠠⠍⠀⠀⠘⠎⠨` + - first differing cell (zero-based): 54 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` + ## Cross-cutting input-only structural cohorts These are cross-cutting input-only structural cohorts, not new primary classes and not engine routing rules. Candidate selection never changes a case's existing primary class by itself. Only cohort members already classified as `pending_rule_review` form a pending subcluster; exact and other-primary members are controls that retain their existing outcomes. A separate classifier may use independently justified, output-localized PDF evidence, as in the rule-34 three-cell contradiction below. The `uppercase_roman_headword_closed_multiword_parenthetical` gate requires a two-or-more character uppercase ASCII headword immediately followed by a closed parenthesis whose contents are two or more ASCII Roman words separated only by spaces. Because the contents admit only letters and spaces, visible operators, subscript/superscript notation, and nested parentheses are excluded deterministically. The `mixed_roman_korean_word_before_uppercase_headword_expansion` gate further requires a whitespace-delimited preceding word that contains both ASCII Roman and Korean and ends in Korean. It locates the following uppercase headword itself, including its immediately preceding emitted cell, so an earlier Roman entry in the same sentence cannot satisfy the output audit. The `single_capital_followed_by_parenthesized_digits` gate requires a standalone capital, a non-empty closed ASCII-digit parenthetical, and alphanumeric outer boundaries. It likewise includes the emitted entry-boundary cell in localization. The `uppercase_roman_run_followed_by_hyphen_digits` gate requires a maximal uppercase ASCII run, literal ASCII hyphen-minus, a non-empty digit run, and alphanumeric outer boundaries. Its localized range includes the current encoded run and its immediately preceding cell, keeping `F-35`-style routing separate from both parenthesized digits and headword expansions. The `standalone_multi_character_uppercase_roman_word` gate finds maximal ASCII-letter runs of two or more capitals with non-alphanumeric boundaries; a run immediately followed by `(` is excluded so the HCA-style headword itself is not counted by both gates. The `korean_prefixed_closed_allcaps_parenthetical` gate requires an immediately preceding Korean character and a closed body of two or more uppercase ASCII letters. It intentionally contains both acronym annotations (`책임자(COO)`) and scientific formulae (`일산화탄소(CO)`) so their semantic collision remains measurable. The `korean_prefixed_closed_roman_annotation_rule_34_order` gate accepts the narrower rule-34 body grammar after an immediately preceding Korean character and localizes only the current engine's Korean opening-parenthesis cells. This distinguishes the PDF's parenthesis-before-Roman-indicator order from unrelated differences later in the same sentence. The `multi_character_allcaps_roman_runs_joined_by_middle_dot` gate requires two maximal ASCII-letter runs of at least two capitals joined directly by U+00B7, with non-alphanumeric outer boundaries. It records shapes such as `AI·SW` without assigning prose, mathematics, or science semantics. The `roman_run_immediately_before_attached_middle_dot_boundary` gate is output-localized: it requires a maximal ASCII-letter run immediately before U+00B7 and an attached Korean character or ASCII-letter run after it, then searches for that whole current-engine signature in the actual output. It therefore isolates the Roman terminator boundary without treating unrelated middle dots elsewhere in the sentence as causal. The `korean_inline_parenthesized_single_arithmetic_operator` gate requires an immediate `Korean(` + one rule-45 arithmetic operator + `)Korean` span. Unlike broad coexistence traits, it also locates the current engine's emitted structure and counts a mismatch as signature-local only when the sentence's first differing cell falls inside that output range. The `allcaps_roman_run_containing_ou` gate finds maximal, alphanumeric-delimited uppercase ASCII runs containing adjacent `OU`. It locates the independently encoded run signature in the complete current output and counts only first differences inside that signature as localized. The `decimal_point_between_ascii_digits` gate finds whitespace-delimited words containing `digit.digit` and reproduces each whole word in a neutral Korean context, so suffixes and punctuation remain part of the current-engine signature. The `compact_numeric_ascii_letter_suffix` gate finds a numeric prefix immediately followed by ASCII letters, includes the immediately preceding output cell as its entry boundary, and retains suffix-specific outcome counts. It intentionally includes both possible rule-69 units and ambiguous variable/identifier forms; membership alone does not assign unit semantics. The `rule69_ascii_unit_before_terminator_skipping_symbol` gate is narrower: it accepts only ASCII unit spellings already supported by rule 69, includes the immediately following rule-33/34 punctuation cell in the localized signature, and does not infer new units. The `tight_triangle_mark_immediately_before_korean` gate requires literal `△한글` with no input space and includes the first following Korean cell in its localized output range, so an observed missing-space difference is measured at the mark boundary. @@ -577,6 +577,7 @@ These are cross-cutting input-only structural cohorts, not new primary classes a | `uppercase_ascii_run_immediately_after_hyphen_in_roman_sequence` | 952 | 158 | 794 | 0 | | `uppercase_roman_headword_closed_multiword_parenthetical` | 175 | 16 | 159 | 0 | | `uppercase_roman_run_followed_by_hyphen_digits` | 571 | 186 | 385 | 0 | +| `uppercase_word_after_whitespace_continuing_ascii_roman_text` | 1729 | 386 | 1343 | 0 | ### `allcaps_roman_run_beginning_with_pure_letter_shortform` @@ -1943,6 +1944,93 @@ Representative `mismatch` samples: - first differing cell (zero-based): 108 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +### `uppercase_word_after_whitespace_continuing_ascii_roman_text` + +Of the 1729 candidates, 1272 are the actual `pending_rule_review` subcluster. The other 457 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. + +For this output-signature audit, 1343 mismatches were evaluable and 385 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. + +Localized first-difference transitions: + +- `U+2820 ⠠ -> U+2834 ⠴`: 365 +- `U+2830 ⠰ -> U+2834 ⠴`: 16 +- `U+2800 ⠀ -> U+2832 ⠲`: 2 +- `U+2801 ⠁ -> U+281C ⠜`: 1 +- `U+2820 ⠠ -> U+2825 ⠥`: 1 + +Mismatch primary-class distribution: + +- `comparison_method`: 5 +- `corpus_suspect`: 52 +- `pending_rule_review`: 1272 +- `unsupported_character_review`: 14 + +Representative `exact` samples: + +- `sentence_01.json` #32: 노스홀 메인 부스에서는 신기술인 ‘메타(META) 테크놀로지’를 적용해 화질을 혁신한 3세대 OLED TV 패널을 발표할 예정이다. + - expected: `⠉⠥⠠⠪⠚⠥⠂⠀⠑⠝⠟⠀⠘⠍⠠⠪⠝⠠⠎⠉⠵⠀⠠⠟` + - actual: `⠉⠥⠠⠪⠚⠥⠂⠀⠑⠝⠟⠀⠘⠍⠠⠪⠝⠠⠎⠉⠵⠀⠠⠟` + - current primary/reason: `exact` / `exact` +- `sentence_02.json` #41: 올해로 창립 10주년을 맞이한 IWPG는 유엔 경제사회이사회(UN ECOSOC)와 글로벌소통국(DGC)에 등록된 국제 NGO로서, 전쟁 반대와 실질적인 평화의 바람을 일으키고 있다. + - expected: `⠥⠂⠚⠗⠐⠥⠀⠰⠣⠶⠐⠕⠃⠀⠼⠁⠚⠨⠍⠉⠡⠮⠀⠑` + - actual: `⠥⠂⠚⠗⠐⠥⠀⠰⠣⠶⠐⠕⠃⠀⠼⠁⠚⠨⠍⠉⠡⠮⠀⠑` + - current primary/reason: `exact` / `exact` +- `sentence_03.json` #303: KU MBA 프로그램은 최고경영자(CEO)·현업 전문가 특강, 해외 학술 세미나, 현장 과제 연구 등 다양한 경로를 활용해 실무 전문가들과 교류하고 생생한 사례를 접할 폭넓은 기회를 제공한다. + - expected: `⠴⠠⠠⠅⠥⠀⠠⠠⠍⠃⠁⠲⠀⠙⠪⠐⠥⠈⠪⠐⠗⠢⠵⠀` + - actual: `⠴⠠⠠⠅⠥⠀⠠⠠⠍⠃⠁⠲⠀⠙⠪⠐⠥⠈⠪⠐⠗⠢⠵⠀` + - current primary/reason: `exact` / `exact` +- `sentence_04.json` #2194: LG AI연구원은 18일(현지시간) 캐나다 밴쿠버에서 열리는 세계 최대 비전(이미지 인식) 인공지능(AI) 학회인 ‘국제 컴퓨터 비전 및 패턴 인식 학회(CVPR) 2023’에서 ‘캡셔닝 AI’를 공개했다고 밝혔다. + - expected: `⠴⠠⠠⠇⠛⠀⠠⠠⠁⠊⠲⠡⠈⠍⠏⠒⠵⠀⠼⠁⠓⠕⠂⠦` + - actual: `⠴⠠⠠⠇⠛⠀⠠⠠⠁⠊⠲⠡⠈⠍⠏⠒⠵⠀⠼⠁⠓⠕⠂⠦` + - current primary/reason: `exact` / `exact` + +Representative `localized_mismatch` samples: + +- `sentence_01.json` #78: 2023년형 Neo QLED는 스마트싱스뿐 아니라 매터(Matter) 기기까지 지원하는 원칩 모듈을 탑재해 별도의 동글을 사용하지 않아도, 다양한 사물인터넷(IoT) 기기를 직접 TV에 연결해 사용할 수 있다. + - expected: `⠻⠀⠴⠠⠝⠑⠕⠀⠠⠠⠟⠇⠑⠙⠲⠉⠵⠀⠠⠪⠑⠓⠪⠠` + - actual: `⠻⠀⠴⠠⠝⠑⠕⠀⠴⠠⠠⠟⠇⠫⠲⠉⠵⠀⠠⠪⠑⠓⠪⠠` + - first differing cell (zero-based): 17 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_02.json` #332: 홍성군청소년수련관(관장 피기용) 진로체험지원센터가 오는 16일 청운대학교 운동장에서 관내 중학교 1학년 대상으로 제9회 홍성군 청소년 진로박람회 ‘꿈(JOB)고 두드림(DO DREAM)’을 진행한다. + - expected: `⠦⠄⠴⠠⠠⠙⠕⠀⠠⠠⠙⠗⠂⠍⠠⠴⠴⠄⠮⠀⠨⠟⠚⠗` + - actual: `⠦⠄⠴⠠⠠⠙⠕⠀⠴⠠⠠⠙⠗⠂⠍⠠⠴⠴⠄⠮⠀⠨⠟⠚` + - first differing cell (zero-based): 183 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_03.json` #302: 건국대 경영전문대학원(KU MBA)은 2015년부터 국제경영대학발전협의회(AACSB) 인증을 보유하고 있다. 고등교육법 시행령에 따라 교육부가 인가한 13개 경영전문대학원 중 하나다. 경영전문대학원인 만큼 엄격하게 교육 품질을 관리한다. + - expected: `⠦⠄⠴⠠⠠⠅⠥⠀⠠⠠⠍⠃⠁⠠⠴⠵⠀⠼⠃⠚⠁⠑⠀⠉` + - actual: `⠦⠄⠴⠠⠠⠅⠥⠀⠴⠠⠠⠍⠃⠁⠠⠴⠵⠀⠼⠃⠚⠁⠑⠀` + - first differing cell (zero-based): 29 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_04.json` #363: 켈리(KELLY)는 ‘KEEP NATUALLY’의 줄임말로 인위적인 것을 최소화하고 자연주의적인 원료, 공법, 맛을 추구한다는 의미를 담고 있다. + - expected: `⠴⠠⠠⠅⠑⠑⠏⠀⠠⠠⠝⠁⠞⠥⠁⠇⠇⠽⠴⠄⠺⠀⠨⠯` + - actual: `⠴⠠⠠⠅⠑⠑⠏⠀⠴⠠⠠⠝⠁⠞⠥⠁⠇⠇⠽⠴⠄⠺⠀⠨` + - first differing cell (zero-based): 30 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` + +Representative `mismatch` samples: + +- `sentence_01.json` #35: 삼성전자는 올해 CES에서 77인치 OLED TV를 처음으로 공개할 예정이다. 이는 지난 8월 ‘국제정보디스플레이학술대회(IMID) 2022’에서 삼성디스플레이가 공개한 77인치 퀀텀닷(QD)-OLED 패널을 탑재한 제품이다. + - expected: `⠋⠏⠒⠓⠎⠢⠊⠄⠴⠐⠣⠟⠙⠐⠜⠤⠠⠠⠕⠇⠫⠲⠀⠙` + - actual: `⠋⠏⠒⠓⠎⠢⠊⠄⠀⠀⠦⠠⠠⠟⠙⠴⠔⠠⠠⠕⠇⠑⠙⠀` + - first differing cell (zero-based): 172 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_02.json` #14: 한국타이어앤테크놀로지㈜(대표이사 이수일, 이하 한국타이어)가 국제자동차연맹(FIA)이 주관하는 ‘FIA 주니어 ERC(FIA Junior ERC, 이하 주니어 ERC)’ 대회에 레이싱 타이어를 독점 공급한다. + - expected: `⠚⠒⠈⠍⠁⠓⠣⠕⠎⠗⠒⠓⠝⠋⠪⠉⠥⠂⠐⠥⠨⠕⠦⠄` + - actual: `` + - error: `Invalid symbol character` + - first differing cell (zero-based): 0 + - current primary/reason: `unsupported_character_review` / `unsupported_character_review` +- `sentence_03.json` #48: WIS 전시장 내 KT DS 전시관에서는 실제 현장에 적용되는 통합 대시보드 화면과 위험 구역을 감지·경고하는 지능형 폐쇄회로(CC)TV를 통해 에스패스를 체험할 수 있다. + - expected: `⠌⠠⠧⠗⠚⠽⠐⠥⠴⠐⠣⠠⠠⠉⠉⠐⠜⠠⠠⠞⠧⠲⠐⠮` + - actual: `⠌⠠⠧⠗⠚⠽⠐⠥⠦⠄⠴⠠⠉⠠⠉⠠⠴⠰⠠⠠⠞⠧⠲⠐` + - first differing cell (zero-based): 128 + - current primary/reason: `corpus_suspect` / `rule34_roman_indicator_before_opening_parenthesis` +- `sentence_04.json` #363: 켈리(KELLY)는 ‘KEEP NATUALLY’의 줄임말로 인위적인 것을 최소화하고 자연주의적인 원료, 공법, 맛을 추구한다는 의미를 담고 있다. + - expected: `⠴⠠⠠⠅⠑⠑⠏⠀⠠⠠⠝⠁⠞⠥⠁⠇⠇⠽⠴⠄⠺⠀⠨⠯` + - actual: `⠴⠠⠠⠅⠑⠑⠏⠀⠴⠠⠠⠝⠁⠞⠥⠁⠇⠇⠽⠴⠄⠺⠀⠨` + - first differing cell (zero-based): 30 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` + ## UEB grade-1 first-difference cohorts These cohorts are defined by both an input boundary and the sentence's actual first-difference transition. They therefore do not claim every mismatch merely coexisting with an ASCII run. Candidate, exact, mismatch, and primary-class counts remain cross-cutting controls; only the reported target transition is the localized residual under review. The reverse transition is retained separately rather than folded into the target. @@ -2014,6 +2102,12 @@ Korean rule 29 requires a Roman indicator before Roman text in a Korean sentence The whitespace parenthetical-headword cohort also retains 1 localized `⠠ -> ⠴` and 2 localized `⠴ -> ⠰` cases as separate transitions; they are not folded into the target. The exact controls demonstrate that the broad structure is already correct in many sentences, while the attached parenthetical-hyphen cohort has no exact control and includes cases already classified by the stricter rule-34 reference-order contradiction. Consequently none of these measurements authorizes an engine change; they are deterministic pending/corpus-review diagnostics only. +### Consecutive Roman uppercase-word re-entry + +Korean rule 29 explicitly says that when two or more Roman items occur consecutively, the Roman indicator is placed only before the first and the terminator only after the last. Its printed `Los Angeles` and `Table of Contents` examples exercise multiword Roman sections; the rule-28 appendix independently supplies capitalization indicators inside that section. The current token phase can nevertheless insert an explicit Roman-entry event before a later uppercase word when the preceding Roman run began inside a mixed Korean/punctuation word. The character emitter is still in Roman mode at that point, so this is a candidate duplicate-event boundary rather than permission to rewrite arbitrary multiword ASCII text. + +Baseline measurement: 1729 candidates, 386 exact controls, 1343 mismatches, 1272 pending members, and 385/1343 evaluable mismatches localized to the current re-entry signature. The localized transitions are 365 `⠠ -> ⠴`, 16 `⠰ -> ⠴`, and 0 reverse `⠴ -> ⠠`; the remaining localized transitions stay separate. Exact controls include contexts where the first Roman word already opened token-level mode, while parenthesized/mixed-token examples expose the duplicate event. Any engine experiment must therefore suppress only an explicit entry encountered while final emit state is already Roman, then audit all exact regressions and the complete 5,141-case standard suite. + The HCA-style headword-expansion shape described above is not an engine implementation premise. The 2024 PDF's math rule 6 defines parentheses and grouping parentheses, rule 11 defines mathematical-expression spacing, rule 12 covers Roman letters in formulas as well as Korean sentences, and rule 45 shows Roman-letter function notation followed by parentheses. Excluding visible operators, scripts, and nesting narrows this corpus cohort, but the PDF does not make the remaining surface shape sufficient to rule out every mathematical counterexample. The cluster therefore remains conservative pending-review evidence only. diff --git a/libs/braillify/examples/nikl_corpus_analyze.rs b/libs/braillify/examples/nikl_corpus_analyze.rs index 4b509085..44d7238f 100644 --- a/libs/braillify/examples/nikl_corpus_analyze.rs +++ b/libs/braillify/examples/nikl_corpus_analyze.rs @@ -603,6 +603,8 @@ const ROMAN_PARENTHETICAL_HEADWORD_AFTER_KOREAN_WORD: &str = "roman_parenthetical_headword_after_whitespace_following_korean_word"; const KOREAN_PREFIXED_ROMAN_PARENTHETICAL_HYPHEN_SUFFIX: &str = "korean_prefixed_roman_parenthetical_followed_by_allcaps_hyphen_suffix"; +const CONSECUTIVE_ROMAN_UPPERCASE_WORD_REENTRY: &str = + "uppercase_word_after_whitespace_continuing_ascii_roman_text"; #[derive(Clone, Copy, Debug, PartialEq, Eq)] struct InputSpan { @@ -1209,6 +1211,50 @@ fn korean_prefixed_roman_parenthetical_hyphen_suffix_spans(input: &str) -> Vec Vec { + let bytes = input.as_bytes(); + let mut spans = Vec::new(); + for (start_byte, ch) in input.char_indices() { + if !ch.is_ascii_uppercase() + || !input[..start_byte] + .chars() + .next_back() + .is_some_and(char::is_whitespace) + { + continue; + } + let before = input[..start_byte].trim_end_matches(char::is_whitespace); + if !before + .chars() + .next_back() + .is_some_and(|ch| ch.is_ascii_alphabetic()) + { + continue; + } + let mut end_byte = start_byte; + while bytes.get(end_byte).is_some_and(u8::is_ascii_uppercase) { + end_byte += 1; + } + if end_byte - start_byte >= 2 + && !input[end_byte..] + .chars() + .next() + .is_some_and(|next| next.is_ascii_alphabetic()) + { + spans.push(InputSpan { + start_byte, + end_byte, + }); + } + } + spans +} + /// Locates each detected run in the full current-engine output by searching /// for that run's independently encoded signature. This uses neither the /// corpus reference nor a hard-coded braille value. @@ -1596,7 +1642,23 @@ fn first_difference_claimed_before_roman_entry_residual(item: &EncodedCase) -> b ) } -fn first_difference_claimed_by_localized_cohort(item: &EncodedCase) -> bool { +fn first_difference_in_current_roman_entry_signature( + item: &EncodedCase, + spans: &[InputSpan], +) -> bool { + let Ok(actual) = &item.actual else { + return false; + }; + if actual == &item.located.case.unicode { + return false; + } + let first_difference = first_difference_cell(&item.located.case.unicode, actual); + roman_entry_signature_ranges(&item.located.case.input, actual, spans, 0) + .into_iter() + .any(|range| range.contains(&first_difference)) +} + +fn first_difference_claimed_before_consecutive_roman_reentry(item: &EncodedCase) -> bool { first_difference_claimed_before_roman_entry_residual(item) || first_difference_at_input_span_entry( item, @@ -1615,6 +1677,14 @@ fn first_difference_claimed_by_localized_cohort(item: &EncodedCase) -> bool { ) } +fn first_difference_claimed_by_localized_cohort(item: &EncodedCase) -> bool { + first_difference_claimed_before_consecutive_roman_reentry(item) + || first_difference_in_current_roman_entry_signature( + item, + &consecutive_roman_uppercase_word_spans(&item.located.case.input), + ) +} + /// Input-only candidate gate for acronym expansions such as /// `HCA(Home Connectivity Alliance)`. /// @@ -2286,6 +2356,10 @@ fn analyze( KOREAN_PREFIXED_ROMAN_PARENTHETICAL_HYPHEN_SUFFIX.to_string(), PendingRuleReviewClusterStats::default(), ), + ( + CONSECUTIVE_ROMAN_UPPERCASE_WORD_REENTRY.to_string(), + PendingRuleReviewClusterStats::default(), + ), ]); let mut pending_first_difference_cell_transitions = BTreeMap::new(); let mut pending_first_difference_transitions_after_localized_cohorts = BTreeMap::new(); @@ -2551,6 +2625,18 @@ fn analyze( ), true, ), + ( + CONSECUTIVE_ROMAN_UPPERCASE_WORD_REENTRY, + !consecutive_roman_uppercase_word_spans(&item.located.case.input).is_empty(), + Some( + !first_difference_claimed_before_consecutive_roman_reentry(item) + && first_difference_in_current_roman_entry_signature( + item, + &consecutive_roman_uppercase_word_spans(&item.located.case.input), + ), + ), + true, + ), ] { if !present { continue; @@ -3318,6 +3404,57 @@ fn markdown(report: &AnalysisReport) -> String { reference-order contradiction. Consequently none of these measurements authorizes an \ engine change; they are deterministic pending/corpus-review diagnostics only.\n\n" )); + if let Some(stats) = report + .pending_rule_review_clusters + .get(CONSECUTIVE_ROMAN_UPPERCASE_WORD_REENTRY) + { + let pending = stats + .mismatch_primary_classes + .get("pending_rule_review") + .copied() + .unwrap_or(0); + let target = stats + .first_difference_in_output_signature_transitions + .get("U+2820 ⠠ -> U+2834 ⠴") + .copied() + .unwrap_or(0); + let grade1_to_roman = stats + .first_difference_in_output_signature_transitions + .get("U+2830 ⠰ -> U+2834 ⠴") + .copied() + .unwrap_or(0); + let reverse = stats + .first_difference_in_output_signature_transitions + .get("U+2834 ⠴ -> U+2820 ⠠") + .copied() + .unwrap_or(0); + text.push_str(&format!( + "### Consecutive Roman uppercase-word re-entry\n\n\ + Korean rule 29 explicitly says that when two or more Roman items occur \ + consecutively, the Roman indicator is placed only before the first and the \ + terminator only after the last. Its printed `Los Angeles` and `Table of Contents` \ + examples exercise multiword Roman sections; the rule-28 appendix independently \ + supplies capitalization indicators inside that section. The current token phase can \ + nevertheless insert an explicit Roman-entry event before a later uppercase word when \ + the preceding Roman run began inside a mixed Korean/punctuation word. The character \ + emitter is still in Roman mode at that point, so this is a candidate duplicate-event \ + boundary rather than permission to rewrite arbitrary multiword ASCII text.\n\n\ + Baseline measurement: {} candidates, {} exact controls, {} mismatches, {pending} \ + pending members, and {}/{} evaluable mismatches localized to the current re-entry \ + signature. The localized transitions are {target} `⠠ -> ⠴`, \ + {grade1_to_roman} `⠰ -> ⠴`, and {reverse} reverse `⠴ -> ⠠`; the remaining localized \ + transitions stay separate. Exact controls include contexts where the first Roman word \ + already opened token-level mode, while parenthesized/mixed-token examples expose the \ + duplicate event. Any engine experiment must therefore suppress only an explicit entry \ + encountered while final emit state is already Roman, then audit all exact regressions \ + and the complete 5,141-case standard suite.\n\n", + stats.candidates, + stats.exact, + stats.mismatch, + stats.first_difference_in_output_signature, + stats.output_signature_mismatches_evaluated + )); + } text.push_str( "\nThe HCA-style headword-expansion shape described above is not an engine \ implementation premise. The 2024 PDF's math rule 6 \ @@ -4607,6 +4744,40 @@ mod tests { assert_eq!(actual, expected); } + #[rstest::rstest] + #[case::mixed_then_allcaps("Neo QLED는", vec!["QLED"])] + #[case::allcaps_pair("DO DREAM)", vec!["DREAM"])] + #[case::pdf_capital_passage("WELCOME TO KOREA", vec!["TO", "KOREA"])] + #[case::punctuation_break("Neo. QLED는", vec![])] + #[case::mixed_case_second("Neo Qled는", vec![])] + #[case::korean_previous("한글 QLED는", vec![])] + fn detects_consecutive_roman_uppercase_word_reentry( + #[case] input: &str, + #[case] expected: Vec<&str>, + ) { + let actual = consecutive_roman_uppercase_word_spans(input) + .into_iter() + .map(|span| &input[span.start_byte..span.end_byte]) + .collect::>(); + assert_eq!(actual, expected); + } + + #[test] + fn locates_current_reentry_before_consecutive_uppercase_word() { + let input = "가 Neo QLED는"; + let spans = consecutive_roman_uppercase_word_spans(input); + let actual = braillify::encode_to_unicode(input).expect("Roman reentry probe must encode"); + let ranges = roman_entry_signature_ranges(input, &actual, &spans, 0); + let starts = ranges + .iter() + .map(|range| range.start) + .collect::>(); + + assert_eq!(spans.len(), 1); + assert_eq!(starts.len(), 1); + assert_eq!(actual.chars().nth(*starts.first().unwrap()), Some('⠴')); + } + #[rstest::rstest] #[case::shortform_prefix("가(WD) 나", allcaps_shortform_prefix_spans("가(WD) 나"))] #[case::numeric_continuation("가(Li2S) 나", roman_uppercase_after_digit_spans("가(Li2S) 나"))] From 24ab5d0f7677c3556767a99c514dfe658bd90e9d Mon Sep 17 00:00:00 2001 From: devfive Date: Tue, 1 Sep 2026 12:09:16 +0900 Subject: [PATCH 31/77] Keep consecutive Roman text in one section --- docs/corpus-analysis/NIKL_2025_V1.md | 438 +++++++++--------- .../braillify/examples/nikl_corpus_analyze.rs | 38 +- libs/braillify/src/rules/emit.rs | 43 +- 3 files changed, 279 insertions(+), 240 deletions(-) diff --git a/docs/corpus-analysis/NIKL_2025_V1.md b/docs/corpus-analysis/NIKL_2025_V1.md index 9a77e71e..c5a05303 100644 --- a/docs/corpus-analysis/NIKL_2025_V1.md +++ b/docs/corpus-analysis/NIKL_2025_V1.md @@ -7,9 +7,9 @@ | Metric | Count | |---|---:| | Total | 83528 | -| Exact | 67138 | -| Mismatch | 16390 | -| Exact accuracy | 80.38% | +| Exact | 67442 | +| Mismatch | 16086 | +| Exact accuracy | 80.74% | | Duplicate records | 0 | | Inputs with conflicting references | 0 | @@ -19,21 +19,21 @@ Primary classes are evidence gates, not permissions to change the engine. `imple | Primary class | Count | |---|---:| -| `comparison_method` | 286 | -| `corpus_suspect` | 1097 | -| `exact` | 67138 | -| `pending_rule_review` | 14824 | -| `unsupported_character_review` | 183 | +| `comparison_method` | 290 | +| `corpus_suspect` | 1099 | +| `exact` | 67442 | +| `pending_rule_review` | 14517 | +| `unsupported_character_review` | 180 | | Reproducible reason | Count | |---|---:| -| `exact` | 67138 | -| `foreign_text_rule_review` | 14649 | -| `nfkc_input_equivalent` | 286 | +| `exact` | 67442 | +| `foreign_text_rule_review` | 14342 | +| `nfkc_input_equivalent` | 290 | | `number_rule_review` | 167 | | `punctuation_rule_review` | 8 | -| `rule34_roman_indicator_before_opening_parenthesis` | 1097 | -| `unsupported_character_review` | 183 | +| `rule34_roman_indicator_before_opening_parenthesis` | 1099 | +| `unsupported_character_review` | 180 | ## Pending first-difference cell transitions @@ -41,26 +41,26 @@ This ranking is a diagnostic selector, not an implementation rule. It counts onl | Rank | Expected → actual first cell | Cases | |---:|---|---:| -| 1 | `U+2834 ⠴ -> U+2800 ⠀` | 2146 | -| 2 | `U+2815 ⠕ -> U+2833 ⠳` | 1319 | -| 3 | `U+2830 ⠰ -> U+2820 ⠠` | 838 | -| 4 | `U+2810 ⠐ -> U+2832 ⠲` | 646 | -| 5 | `U+2820 ⠠ -> U+2834 ⠴` | 469 | -| 6 | `U+2826 ⠦ -> U+2834 ⠴` | 458 | -| 7 | `U+280E ⠎ -> U+280C ⠌` | 444 | -| 8 | `U+2826 ⠦ -> U+2800 ⠀` | 429 | -| 9 | `U+2808 ⠈ -> U+2832 ⠲` | 411 | -| 10 | `U+2801 ⠁ -> U+281C ⠜` | 393 | -| 11 | `U+2800 ⠀ -> U+2807 ⠇` | 361 | -| 12 | `U+283C ⠼ -> U+2800 ⠀` | 358 | -| 13 | `U+2834 ⠴ -> U+2830 ⠰` | 328 | -| 14 | `U+2811 ⠑ -> U+282B ⠫` | 324 | -| 15 | `U+2832 ⠲ -> U+2838 ⠸` | 276 | -| 16 | `U+2800 ⠀ -> U+2832 ⠲` | 254 | -| 17 | `U+2820 ⠠ -> U+2830 ⠰` | 230 | -| 18 | `U+2810 ⠐ -> U+2802 ⠂` | 210 | -| 19 | `U+2817 ⠗ -> U+2820 ⠠` | 163 | -| 20 | `U+2820 ⠠ -> U+2809 ⠉` | 159 | +| 1 | `U+2834 ⠴ -> U+2800 ⠀` | 2149 | +| 2 | `U+2815 ⠕ -> U+2833 ⠳` | 1325 | +| 3 | `U+2830 ⠰ -> U+2820 ⠠` | 855 | +| 4 | `U+2810 ⠐ -> U+2832 ⠲` | 648 | +| 5 | `U+2826 ⠦ -> U+2834 ⠴` | 461 | +| 6 | `U+280E ⠎ -> U+280C ⠌` | 448 | +| 7 | `U+2826 ⠦ -> U+2800 ⠀` | 430 | +| 8 | `U+2808 ⠈ -> U+2832 ⠲` | 411 | +| 9 | `U+2801 ⠁ -> U+281C ⠜` | 393 | +| 10 | `U+2800 ⠀ -> U+2807 ⠇` | 362 | +| 11 | `U+283C ⠼ -> U+2800 ⠀` | 358 | +| 12 | `U+2834 ⠴ -> U+2830 ⠰` | 335 | +| 13 | `U+2811 ⠑ -> U+282B ⠫` | 326 | +| 14 | `U+2832 ⠲ -> U+2838 ⠸` | 281 | +| 15 | `U+2800 ⠀ -> U+2832 ⠲` | 258 | +| 16 | `U+2820 ⠠ -> U+2830 ⠰` | 230 | +| 17 | `U+2810 ⠐ -> U+2802 ⠂` | 210 | +| 18 | `U+2817 ⠗ -> U+2820 ⠠` | 163 | +| 19 | `U+2820 ⠠ -> U+2809 ⠉` | 160 | +| 20 | `U+2826 ⠦ -> U+2810 ⠐` | 160 | ### `U+2834 ⠴ -> U+2800 ⠀` @@ -154,29 +154,6 @@ This ranking is a diagnostic selector, not an implementation rule. It counts onl - first differing cell (zero-based): 149 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -### `U+2820 ⠠ -> U+2834 ⠴` - -- `sentence_01.json` #78: 2023년형 Neo QLED는 스마트싱스뿐 아니라 매터(Matter) 기기까지 지원하는 원칩 모듈을 탑재해 별도의 동글을 사용하지 않아도, 다양한 사물인터넷(IoT) 기기를 직접 TV에 연결해 사용할 수 있다. - - expected: `⠻⠀⠴⠠⠝⠑⠕⠀⠠⠠⠟⠇⠑⠙⠲⠉⠵⠀⠠⠪⠑⠓⠪⠠` - - actual: `⠻⠀⠴⠠⠝⠑⠕⠀⠴⠠⠠⠟⠇⠫⠲⠉⠵⠀⠠⠪⠑⠓⠪⠠` - - first differing cell (zero-based): 17 - - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_02.json` #332: 홍성군청소년수련관(관장 피기용) 진로체험지원센터가 오는 16일 청운대학교 운동장에서 관내 중학교 1학년 대상으로 제9회 홍성군 청소년 진로박람회 ‘꿈(JOB)고 두드림(DO DREAM)’을 진행한다. - - expected: `⠦⠄⠴⠠⠠⠙⠕⠀⠠⠠⠙⠗⠂⠍⠠⠴⠴⠄⠮⠀⠨⠟⠚⠗` - - actual: `⠦⠄⠴⠠⠠⠙⠕⠀⠴⠠⠠⠙⠗⠂⠍⠠⠴⠴⠄⠮⠀⠨⠟⠚` - - first differing cell (zero-based): 183 - - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_03.json` #302: 건국대 경영전문대학원(KU MBA)은 2015년부터 국제경영대학발전협의회(AACSB) 인증을 보유하고 있다. 고등교육법 시행령에 따라 교육부가 인가한 13개 경영전문대학원 중 하나다. 경영전문대학원인 만큼 엄격하게 교육 품질을 관리한다. - - expected: `⠦⠄⠴⠠⠠⠅⠥⠀⠠⠠⠍⠃⠁⠠⠴⠵⠀⠼⠃⠚⠁⠑⠀⠉` - - actual: `⠦⠄⠴⠠⠠⠅⠥⠀⠴⠠⠠⠍⠃⠁⠠⠴⠵⠀⠼⠃⠚⠁⠑⠀` - - first differing cell (zero-based): 29 - - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_04.json` #363: 켈리(KELLY)는 ‘KEEP NATUALLY’의 줄임말로 인위적인 것을 최소화하고 자연주의적인 원료, 공법, 맛을 추구한다는 의미를 담고 있다. - - expected: `⠴⠠⠠⠅⠑⠑⠏⠀⠠⠠⠝⠁⠞⠥⠁⠇⠇⠽⠴⠄⠺⠀⠨⠯` - - actual: `⠴⠠⠠⠅⠑⠑⠏⠀⠴⠠⠠⠝⠁⠞⠥⠁⠇⠇⠽⠴⠄⠺⠀⠨` - - first differing cell (zero-based): 30 - - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` - ### `U+2826 ⠦ -> U+2834 ⠴` - `sentence_01.json` #38: 올해 소프트웨어(SW) 업계는 국내 인공지능(AI) 제품·서비스 고도화와 함께 전 산업영역에서 SW영역 확대가 가시화될 것으로 보인다. 또한 블록체인이 웹3(Web3)와 연계해 탈중앙화·탈독점화 경제체계로 진화할 것으로 예상된다. @@ -292,31 +269,54 @@ This ranking is a diagnostic selector, not an implementation rule. It counts onl - first differing cell (zero-based): 14 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +### `U+2800 ⠀ -> U+2807 ⠇` + +- `sentence_01.json` #3907: 웹케시그룹은 △청구·결제·수납 솔루션 ‘위빌(WeBILL)’ △글로벌 중견·대기업 자금관리 솔루션 ‘위엠비에이(WeMBA)’ △전자세금계산서 발행 솔루션 ‘위택스(WeTAX)’ △글로벌 통합 자금관리 시스템 ‘위지엠비에이(WeGMBA)’ 등의 글로벌 전략 상품도 순차적으로 출시 예정이다. + - expected: `⠪⠐⠍⠃⠵⠀⠸⠬⠀⠰⠻⠈⠍⠐⠆⠈⠳⠨⠝⠐⠆⠠⠍⠉` + - actual: `⠪⠐⠍⠃⠵⠀⠸⠬⠇⠰⠻⠈⠍⠐⠆⠈⠳⠨⠝⠐⠆⠠⠍⠉` + - first differing cell (zero-based): 16 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_02.json` #17: 시범사업은 서해안권·백제문화권·서부내륙권을 대표하는 4개 시군의 관광자원 특성을 반영한 △문화치유 △해양치유 △마을맞춤 △엠지(MZ)맞춤 등 유형별 코스를 집중 발굴해 워케이션 상품을 기획했다. + - expected: `⠘⠒⠻⠚⠒⠀⠸⠬⠀⠑⠛⠚⠧⠰⠕⠩⠀⠸⠬⠀⠚⠗⠜⠶` + - actual: `⠘⠒⠻⠚⠒⠀⠸⠬⠇⠑⠛⠚⠧⠰⠕⠩⠀⠸⠬⠇⠚⠗⠜⠶` + - first differing cell (zero-based): 94 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_03.json` #244: 올해 모집 분야는 △에듀테크&콘텐츠 △라이프스타일 △정보통신기술(ICT)&디지털 기반 혁신기술 등이다. 시리즈A 단계까지 법인 등록 스타트업이면 지원할 수 있다. + - expected: `⠘⠛⠜⠉⠵⠀⠸⠬⠀⠝⠊⠩⠓⠝⠋⠪⠴⠈⠯⠲⠋⠷⠓⠝` + - actual: `⠘⠛⠜⠉⠵⠀⠸⠬⠇⠝⠊⠩⠓⠝⠋⠪⠴⠈⠯⠲⠋⠷⠓⠝` + - first differing cell (zero-based): 19 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_04.json` #7059: 현재 남양주시의 놀이체험시설은 △놀자람(화도) △까꿍놀이터(진접) △도르르(호평) △북(Book)놀이터(별내) △아이꿈놀이터(와부) 총 5개소로, 기존에 무료로 운영 중인 아이꿈놀이터를 제외한 유료 시설 4개소에 대해 무료 서비스가 제공된다. + - expected: `⠠⠕⠠⠞⠵⠀⠸⠬⠀⠉⠥⠂⠨⠐⠣⠢⠦⠄⠚⠧⠊⠥⠠⠴` + - actual: `⠠⠕⠠⠞⠵⠀⠸⠬⠇⠉⠥⠂⠨⠐⠣⠢⠦⠄⠚⠧⠊⠥⠠⠴` + - first differing cell (zero-based): 32 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` + ## Residual first-difference transitions after localized cohorts This ranking removes only cases whose first difference is inside an existing output-localized cohort. Broad input-only traits are not exclusion masks. The residual table therefore prioritizes new causes without hiding a mismatch merely because an unrelated structure coexists elsewhere in its sentence. | Rank | Expected → actual first cell | Residual cases | |---:|---|---:| -| 1 | `U+2826 ⠦ -> U+2834 ⠴` | 451 | -| 2 | `U+280E ⠎ -> U+280C ⠌` | 444 | +| 1 | `U+2826 ⠦ -> U+2834 ⠴` | 454 | +| 2 | `U+280E ⠎ -> U+280C ⠌` | 448 | | 3 | `U+2808 ⠈ -> U+2832 ⠲` | 409 | -| 4 | `U+2826 ⠦ -> U+2800 ⠀` | 393 | +| 4 | `U+2826 ⠦ -> U+2800 ⠀` | 394 | | 5 | `U+2801 ⠁ -> U+281C ⠜` | 390 | -| 6 | `U+2834 ⠴ -> U+2830 ⠰` | 326 | -| 7 | `U+2811 ⠑ -> U+282B ⠫` | 323 | -| 8 | `U+2810 ⠐ -> U+2832 ⠲` | 288 | -| 9 | `U+2832 ⠲ -> U+2838 ⠸` | 276 | -| 10 | `U+2834 ⠴ -> U+2800 ⠀` | 256 | -| 11 | `U+2800 ⠀ -> U+2832 ⠲` | 223 | +| 6 | `U+2834 ⠴ -> U+2830 ⠰` | 332 | +| 7 | `U+2811 ⠑ -> U+282B ⠫` | 325 | +| 8 | `U+2810 ⠐ -> U+2832 ⠲` | 290 | +| 9 | `U+2832 ⠲ -> U+2838 ⠸` | 281 | +| 10 | `U+2834 ⠴ -> U+2800 ⠀` | 258 | +| 11 | `U+2800 ⠀ -> U+2832 ⠲` | 227 | | 12 | `U+2810 ⠐ -> U+2802 ⠂` | 210 | | 13 | `U+2817 ⠗ -> U+2820 ⠠` | 163 | -| 14 | `U+2820 ⠠ -> U+2809 ⠉` | 159 | +| 14 | `U+2820 ⠠ -> U+2809 ⠉` | 160 | | 15 | `U+283C ⠼ -> U+2800 ⠀` | 153 | | 16 | `U+281B ⠛ -> U+2823 ⠣` | 144 | -| 17 | `U+2826 ⠦ -> U+2810 ⠐` | 141 | -| 18 | `U+2810 ⠐ -> U+2800 ⠀` | 129 | -| 19 | `U+2811 ⠑ -> U+283B ⠻` | 100 | +| 17 | `U+2826 ⠦ -> U+2810 ⠐` | 144 | +| 18 | `U+2810 ⠐ -> U+2800 ⠀` | 130 | +| 19 | `U+2811 ⠑ -> U+283B ⠻` | 101 | | 20 | `U+2810 ⠐ -> U+2814 ⠔` | 98 | ### Residual `U+2826 ⠦ -> U+2834 ⠴` @@ -555,35 +555,35 @@ These are cross-cutting input-only structural cohorts, not new primary classes a | Cluster | Candidates | Exact | Mismatch | Conflicting-reference cases | |---|---:|---:|---:|---:| -| `allcaps_roman_run_beginning_with_pure_letter_shortform` | 4120 | 2395 | 1725 | 0 | -| `allcaps_roman_run_containing_ou` | 1816 | 55 | 1761 | 0 | -| `compact_numeric_ascii_letter_suffix` | 2975 | 1842 | 1133 | 0 | -| `decimal_point_between_ascii_digits` | 4546 | 3257 | 1289 | 0 | +| `allcaps_roman_run_beginning_with_pure_letter_shortform` | 4120 | 2425 | 1695 | 0 | +| `allcaps_roman_run_containing_ou` | 1816 | 64 | 1752 | 0 | +| `compact_numeric_ascii_letter_suffix` | 2975 | 1855 | 1120 | 0 | +| `decimal_point_between_ascii_digits` | 4546 | 3267 | 1279 | 0 | | `korean_inline_parenthesized_single_arithmetic_operator` | 23 | 17 | 6 | 0 | -| `korean_prefixed_closed_allcaps_parenthetical` | 54492 | 44300 | 10192 | 0 | -| `korean_prefixed_closed_roman_annotation_rule_34_order` | 64382 | 51907 | 12475 | 0 | +| `korean_prefixed_closed_allcaps_parenthetical` | 54492 | 44483 | 10009 | 0 | +| `korean_prefixed_closed_roman_annotation_rule_34_order` | 64382 | 52156 | 12226 | 0 | | `korean_prefixed_roman_parenthetical_followed_by_allcaps_hyphen_suffix` | 11 | 0 | 11 | 0 | | `mixed_roman_korean_word_before_uppercase_headword_expansion` | 10 | 0 | 10 | 0 | | `multi_character_allcaps_roman_runs_joined_by_middle_dot` | 97 | 0 | 97 | 0 | -| `pure_allcaps_segment_before_hyphen_and_multi_allcaps_segment_after` | 448 | 126 | 322 | 0 | +| `pure_allcaps_segment_before_hyphen_and_multi_allcaps_segment_after` | 448 | 127 | 321 | 0 | | `roman_hyphenated_word_after_whitespace_following_korean_word` | 361 | 0 | 361 | 0 | -| `roman_parenthetical_headword_after_whitespace_following_korean_word` | 4695 | 3342 | 1353 | 0 | +| `roman_parenthetical_headword_after_whitespace_following_korean_word` | 4695 | 3358 | 1337 | 0 | | `roman_run_immediately_before_attached_middle_dot_boundary` | 577 | 0 | 577 | 0 | -| `rule69_ascii_unit_before_terminator_skipping_symbol` | 440 | 335 | 105 | 0 | +| `rule69_ascii_unit_before_terminator_skipping_symbol` | 440 | 336 | 104 | 0 | | `single_capital_followed_by_parenthesized_digits` | 1361 | 0 | 1361 | 0 | -| `standalone_multi_character_uppercase_roman_word` | 62411 | 49518 | 12893 | 0 | +| `standalone_multi_character_uppercase_roman_word` | 62411 | 49811 | 12600 | 0 | | `tight_triangle_mark_immediately_before_korean` | 377 | 0 | 377 | 0 | -| `uppercase_ascii_run_immediately_after_digit_in_roman_sequence` | 1896 | 1080 | 816 | 0 | -| `uppercase_ascii_run_immediately_after_hyphen_in_roman_sequence` | 952 | 158 | 794 | 0 | +| `uppercase_ascii_run_immediately_after_digit_in_roman_sequence` | 1896 | 1092 | 804 | 0 | +| `uppercase_ascii_run_immediately_after_hyphen_in_roman_sequence` | 952 | 160 | 792 | 0 | | `uppercase_roman_headword_closed_multiword_parenthetical` | 175 | 16 | 159 | 0 | -| `uppercase_roman_run_followed_by_hyphen_digits` | 571 | 186 | 385 | 0 | -| `uppercase_word_after_whitespace_continuing_ascii_roman_text` | 1729 | 386 | 1343 | 0 | +| `uppercase_roman_run_followed_by_hyphen_digits` | 571 | 189 | 382 | 0 | +| `uppercase_word_after_whitespace_continuing_ascii_roman_text` | 1729 | 661 | 1068 | 0 | ### `allcaps_roman_run_beginning_with_pure_letter_shortform` -Of the 4120 candidates, 1656 are the actual `pending_rule_review` subcluster. The other 2464 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 4120 candidates, 1626 are the actual `pending_rule_review` subcluster. The other 2494 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. -For this output-signature audit, 1725 mismatches were evaluable and 823 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. +For this output-signature audit, 1695 mismatches were evaluable and 823 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. Localized first-difference transitions: @@ -594,7 +594,7 @@ Mismatch primary-class distribution: - `comparison_method`: 11 - `corpus_suspect`: 48 -- `pending_rule_review`: 1656 +- `pending_rule_review`: 1626 - `unsupported_character_review`: 10 Representative `exact` samples: @@ -664,20 +664,20 @@ Representative `mismatch` samples: ### `allcaps_roman_run_containing_ou` -Of the 1816 candidates, 1675 are the actual `pending_rule_review` subcluster. The other 141 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 1816 candidates, 1666 are the actual `pending_rule_review` subcluster. The other 150 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. -For this output-signature audit, 1761 mismatches were evaluable and 1315 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. +For this output-signature audit, 1752 mismatches were evaluable and 1321 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. Localized first-difference transitions: -- `U+2815 ⠕ -> U+2833 ⠳`: 1314 +- `U+2815 ⠕ -> U+2833 ⠳`: 1320 - `U+2810 ⠐ -> U+283D ⠽`: 1 Mismatch primary-class distribution: - `comparison_method`: 2 - `corpus_suspect`: 9 -- `pending_rule_review`: 1675 +- `pending_rule_review`: 1666 - `unsupported_character_review`: 75 Representative `exact` samples: @@ -725,23 +725,23 @@ Representative `mismatch` samples: ### `compact_numeric_ascii_letter_suffix` -Of the 2975 candidates, 1065 are the actual `pending_rule_review` subcluster. The other 1910 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 2975 candidates, 1052 are the actual `pending_rule_review` subcluster. The other 1923 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. -For this output-signature audit, 1133 mismatches were evaluable and 252 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. +For this output-signature audit, 1120 mismatches were evaluable and 253 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. Localized first-difference transitions: - `U+283C ⠼ -> U+2800 ⠀`: 191 - `U+2800 ⠀ -> U+2832 ⠲`: 27 - `U+2810 ⠐ -> U+2832 ⠲`: 10 -- `U+281E ⠞ -> U+2810 ⠐`: 7 +- `U+281E ⠞ -> U+2810 ⠐`: 8 - `U+2805 ⠅ -> U+2834 ⠴`: 4 Mismatch primary-class distribution: - `comparison_method`: 12 - `corpus_suspect`: 49 -- `pending_rule_review`: 1065 +- `pending_rule_review`: 1052 - `unsupported_character_review`: 7 Representative `exact` samples: @@ -765,10 +765,10 @@ Representative `exact` samples: Representative `localized_mismatch` samples: -- `sentence_01.json` #322: 삼성전자는 5나노미터(nm, 1nm는 10억 분의 1m) 기반 신규 컨트롤러를 탑재한 PC용 고성능 비휘발성 메모리 익스프레스(NVMe) SSD ‘PM9C1a’를 양산한다고 12일 밝혔다. - - expected: `⠄⠴⠝⠍⠂⠀⠼⠁⠝⠍⠲⠉⠵⠀⠼⠁⠚⠹⠀⠘⠛⠺⠀⠼` - - actual: `⠄⠴⠝⠍⠂⠀⠼⠁⠴⠝⠍⠲⠉⠵⠀⠼⠁⠚⠹⠀⠘⠛⠺⠀` - - first differing cell (zero-based): 29 +- `sentence_01.json` #168: 제주항공은 국제항공운송협회(IATA)가 주관하는 국제 항공운송 표준 감사 제도 ‘IOSA(IATA Operation Safety Audit) ISM 14th Edition’ 인증을 마쳐 세계 기준의 안전 관리시스템을 입증받았다고 6일 밝혔다. + - expected: `⠠⠊⠎⠍⠀⠼⠁⠙⠞⠓⠀⠠⠫⠊⠰⠝⠴⠄⠀⠟⠨⠪⠶⠮` + - actual: `⠠⠊⠎⠍⠀⠼⠁⠙⠐⠞⠓⠀⠠⠫⠊⠰⠝⠴⠄⠀⠟⠨⠪⠶` + - first differing cell (zero-based): 129 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` - `sentence_02.json` #431: 173cm, 68kg의 다소 왜소한 체격이지만 스크럼 하프(SH) 포지션을 맡아 포워드와 백스 사이에서 날카로운 볼 연결과 공격방향을 결정하는 타고난 판단력이 강점이다. - expected: `⠴⠉⠍⠂⠀⠼⠋⠓⠅⠛⠲⠺⠀⠊⠠⠥⠀⠧⠗⠠⠥⠚⠒⠀` @@ -811,9 +811,9 @@ Representative `mismatch` samples: ### `decimal_point_between_ascii_digits` -Of the 4546 candidates, 1233 are the actual `pending_rule_review` subcluster. The other 3313 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 4546 candidates, 1223 are the actual `pending_rule_review` subcluster. The other 3323 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. -For this output-signature audit, 1289 mismatches were evaluable and 264 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. +For this output-signature audit, 1279 mismatches were evaluable and 265 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. Localized first-difference transitions: @@ -827,7 +827,7 @@ Mismatch primary-class distribution: - `comparison_method`: 16 - `corpus_suspect`: 28 -- `pending_rule_review`: 1233 +- `pending_rule_review`: 1223 - `unsupported_character_review`: 12 Representative `exact` samples: @@ -943,14 +943,14 @@ Representative `mismatch` samples: ### `korean_prefixed_closed_allcaps_parenthetical` -Of the 54492 candidates, 8900 are the actual `pending_rule_review` subcluster. The other 45592 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 54492 candidates, 8714 are the actual `pending_rule_review` subcluster. The other 45778 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. Mismatch primary-class distribution: -- `comparison_method`: 173 -- `corpus_suspect`: 984 -- `pending_rule_review`: 8900 -- `unsupported_character_review`: 135 +- `comparison_method`: 177 +- `corpus_suspect`: 986 +- `pending_rule_review`: 8714 +- `unsupported_character_review`: 132 Representative `exact` samples: @@ -996,21 +996,21 @@ Representative `mismatch` samples: ### `korean_prefixed_closed_roman_annotation_rule_34_order` -Of the 64382 candidates, 10995 are the actual `pending_rule_review` subcluster. The other 53387 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 64382 candidates, 10743 are the actual `pending_rule_review` subcluster. The other 53639 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. -For this output-signature audit, 12475 mismatches were evaluable and 1101 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. +For this output-signature audit, 12226 mismatches were evaluable and 1103 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. Localized first-difference transitions: -- `U+2834 ⠴ -> U+2826 ⠦`: 1100 +- `U+2834 ⠴ -> U+2826 ⠦`: 1102 - `U+2810 ⠐ -> U+2826 ⠦`: 1 Mismatch primary-class distribution: -- `comparison_method`: 223 -- `corpus_suspect`: 1097 -- `pending_rule_review`: 10995 -- `unsupported_character_review`: 160 +- `comparison_method`: 227 +- `corpus_suspect`: 1099 +- `pending_rule_review`: 10743 +- `unsupported_character_review`: 157 Representative `exact` samples: @@ -1189,9 +1189,9 @@ Representative `mismatch` samples: ### `pure_allcaps_segment_before_hyphen_and_multi_allcaps_segment_after` -Of the 448 candidates, 312 are the actual `pending_rule_review` subcluster. The other 136 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 448 candidates, 311 are the actual `pending_rule_review` subcluster. The other 137 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. -For this output-signature audit, 322 mismatches were evaluable and 1 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. +For this output-signature audit, 321 mismatches were evaluable and 1 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. Localized first-difference transitions: @@ -1200,7 +1200,7 @@ Localized first-difference transitions: Mismatch primary-class distribution: - `corpus_suspect`: 8 -- `pending_rule_review`: 312 +- `pending_rule_review`: 311 - `unsupported_character_review`: 2 Representative `exact` samples: @@ -1317,21 +1317,21 @@ Representative `mismatch` samples: ### `roman_parenthetical_headword_after_whitespace_following_korean_word` -Of the 4695 candidates, 1264 are the actual `pending_rule_review` subcluster. The other 3431 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 4695 candidates, 1247 are the actual `pending_rule_review` subcluster. The other 3448 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. -For this output-signature audit, 1353 mismatches were evaluable and 156 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. +For this output-signature audit, 1337 mismatches were evaluable and 157 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. Localized first-difference transitions: - `U+2834 ⠴ -> U+2800 ⠀`: 153 -- `U+2834 ⠴ -> U+2830 ⠰`: 2 +- `U+2834 ⠴ -> U+2830 ⠰`: 3 - `U+2820 ⠠ -> U+2834 ⠴`: 1 Mismatch primary-class distribution: - `comparison_method`: 29 -- `corpus_suspect`: 36 -- `pending_rule_review`: 1264 +- `corpus_suspect`: 37 +- `pending_rule_review`: 1247 - `unsupported_character_review`: 24 Representative `exact` samples: @@ -1469,9 +1469,9 @@ Representative `mismatch` samples: ### `rule69_ascii_unit_before_terminator_skipping_symbol` -Of the 440 candidates, 100 are the actual `pending_rule_review` subcluster. The other 340 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 440 candidates, 99 are the actual `pending_rule_review` subcluster. The other 341 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. -For this output-signature audit, 105 mismatches were evaluable and 10 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. +For this output-signature audit, 104 mismatches were evaluable and 10 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. Localized first-difference transitions: @@ -1485,7 +1485,7 @@ Mismatch primary-class distribution: - `comparison_method`: 3 - `corpus_suspect`: 1 -- `pending_rule_review`: 100 +- `pending_rule_review`: 99 - `unsupported_character_review`: 1 Representative `exact` samples: @@ -1591,14 +1591,14 @@ Representative `mismatch` samples: ### `standalone_multi_character_uppercase_roman_word` -Of the 62411 candidates, 11445 are the actual `pending_rule_review` subcluster. The other 50966 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 62411 candidates, 11149 are the actual `pending_rule_review` subcluster. The other 51262 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. Mismatch primary-class distribution: -- `comparison_method`: 207 -- `corpus_suspect`: 1084 -- `pending_rule_review`: 11445 -- `unsupported_character_review`: 157 +- `comparison_method`: 211 +- `corpus_suspect`: 1086 +- `pending_rule_review`: 11149 +- `unsupported_character_review`: 154 Representative `exact` samples: @@ -1646,11 +1646,11 @@ Representative `mismatch` samples: Of the 377 candidates, 376 are the actual `pending_rule_review` subcluster. The other 1 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. -For this output-signature audit, 377 mismatches were evaluable and 331 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. +For this output-signature audit, 377 mismatches were evaluable and 332 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. Localized first-difference transitions: -- `U+2800 ⠀ -> U+2807 ⠇`: 328 +- `U+2800 ⠀ -> U+2807 ⠇`: 329 - `U+280A ⠊ -> U+2807 ⠇`: 1 - `U+2818 ⠘ -> U+2807 ⠇`: 1 - `U+2829 ⠩ -> U+2807 ⠇`: 1 @@ -1685,9 +1685,9 @@ Representative `mismatch` samples: ### `uppercase_ascii_run_immediately_after_digit_in_roman_sequence` -Of the 1896 candidates, 762 are the actual `pending_rule_review` subcluster. The other 1134 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 1896 candidates, 750 are the actual `pending_rule_review` subcluster. The other 1146 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. -For this output-signature audit, 816 mismatches were evaluable and 1 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. +For this output-signature audit, 804 mismatches were evaluable and 1 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. Localized first-difference transitions: @@ -1697,7 +1697,7 @@ Mismatch primary-class distribution: - `comparison_method`: 9 - `corpus_suspect`: 38 -- `pending_rule_review`: 762 +- `pending_rule_review`: 750 - `unsupported_character_review`: 7 Representative `exact` samples: @@ -1752,9 +1752,9 @@ Representative `mismatch` samples: ### `uppercase_ascii_run_immediately_after_hyphen_in_roman_sequence` -Of the 952 candidates, 773 are the actual `pending_rule_review` subcluster. The other 179 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 952 candidates, 771 are the actual `pending_rule_review` subcluster. The other 181 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. -For this output-signature audit, 794 mismatches were evaluable and 160 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. +For this output-signature audit, 792 mismatches were evaluable and 160 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. Localized first-difference transitions: @@ -1764,7 +1764,7 @@ Localized first-difference transitions: Mismatch primary-class distribution: - `corpus_suspect`: 12 -- `pending_rule_review`: 773 +- `pending_rule_review`: 771 - `unsupported_character_review`: 9 Representative `exact` samples: @@ -1887,19 +1887,19 @@ Representative `mismatch` samples: ### `uppercase_roman_run_followed_by_hyphen_digits` -Of the 571 candidates, 370 are the actual `pending_rule_review` subcluster. The other 201 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 571 candidates, 367 are the actual `pending_rule_review` subcluster. The other 204 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. -For this output-signature audit, 385 mismatches were evaluable and 260 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. +For this output-signature audit, 382 mismatches were evaluable and 261 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. Localized first-difference transitions: -- `U+2834 ⠴ -> U+2800 ⠀`: 258 +- `U+2834 ⠴ -> U+2800 ⠀`: 259 - `U+2834 ⠴ -> U+2820 ⠠`: 2 Mismatch primary-class distribution: - `corpus_suspect`: 12 -- `pending_rule_review`: 370 +- `pending_rule_review`: 367 - `unsupported_character_review`: 3 Representative `exact` samples: @@ -1946,24 +1946,22 @@ Representative `mismatch` samples: ### `uppercase_word_after_whitespace_continuing_ascii_roman_text` -Of the 1729 candidates, 1272 are the actual `pending_rule_review` subcluster. The other 457 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 1729 candidates, 994 are the actual `pending_rule_review` subcluster. The other 735 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. -For this output-signature audit, 1343 mismatches were evaluable and 385 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. +For this output-signature audit, 1068 mismatches were evaluable and 4 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. Localized first-difference transitions: -- `U+2820 ⠠ -> U+2834 ⠴`: 365 -- `U+2830 ⠰ -> U+2834 ⠴`: 16 - `U+2800 ⠀ -> U+2832 ⠲`: 2 - `U+2801 ⠁ -> U+281C ⠜`: 1 - `U+2820 ⠠ -> U+2825 ⠥`: 1 Mismatch primary-class distribution: -- `comparison_method`: 5 -- `corpus_suspect`: 52 -- `pending_rule_review`: 1272 -- `unsupported_character_review`: 14 +- `comparison_method`: 9 +- `corpus_suspect`: 54 +- `pending_rule_review`: 994 +- `unsupported_character_review`: 11 Representative `exact` samples: @@ -1975,36 +1973,31 @@ Representative `exact` samples: - expected: `⠥⠂⠚⠗⠐⠥⠀⠰⠣⠶⠐⠕⠃⠀⠼⠁⠚⠨⠍⠉⠡⠮⠀⠑` - actual: `⠥⠂⠚⠗⠐⠥⠀⠰⠣⠶⠐⠕⠃⠀⠼⠁⠚⠨⠍⠉⠡⠮⠀⠑` - current primary/reason: `exact` / `exact` -- `sentence_03.json` #303: KU MBA 프로그램은 최고경영자(CEO)·현업 전문가 특강, 해외 학술 세미나, 현장 과제 연구 등 다양한 경로를 활용해 실무 전문가들과 교류하고 생생한 사례를 접할 폭넓은 기회를 제공한다. - - expected: `⠴⠠⠠⠅⠥⠀⠠⠠⠍⠃⠁⠲⠀⠙⠪⠐⠥⠈⠪⠐⠗⠢⠵⠀` - - actual: `⠴⠠⠠⠅⠥⠀⠠⠠⠍⠃⠁⠲⠀⠙⠪⠐⠥⠈⠪⠐⠗⠢⠵⠀` +- `sentence_03.json` #302: 건국대 경영전문대학원(KU MBA)은 2015년부터 국제경영대학발전협의회(AACSB) 인증을 보유하고 있다. 고등교육법 시행령에 따라 교육부가 인가한 13개 경영전문대학원 중 하나다. 경영전문대학원인 만큼 엄격하게 교육 품질을 관리한다. + - expected: `⠈⠾⠈⠍⠁⠊⠗⠀⠈⠻⠻⠨⠾⠑⠛⠊⠗⠚⠁⠏⠒⠦⠄⠴` + - actual: `⠈⠾⠈⠍⠁⠊⠗⠀⠈⠻⠻⠨⠾⠑⠛⠊⠗⠚⠁⠏⠒⠦⠄⠴` - current primary/reason: `exact` / `exact` -- `sentence_04.json` #2194: LG AI연구원은 18일(현지시간) 캐나다 밴쿠버에서 열리는 세계 최대 비전(이미지 인식) 인공지능(AI) 학회인 ‘국제 컴퓨터 비전 및 패턴 인식 학회(CVPR) 2023’에서 ‘캡셔닝 AI’를 공개했다고 밝혔다. - - expected: `⠴⠠⠠⠇⠛⠀⠠⠠⠁⠊⠲⠡⠈⠍⠏⠒⠵⠀⠼⠁⠓⠕⠂⠦` - - actual: `⠴⠠⠠⠇⠛⠀⠠⠠⠁⠊⠲⠡⠈⠍⠏⠒⠵⠀⠼⠁⠓⠕⠂⠦` +- `sentence_04.json` #363: 켈리(KELLY)는 ‘KEEP NATUALLY’의 줄임말로 인위적인 것을 최소화하고 자연주의적인 원료, 공법, 맛을 추구한다는 의미를 담고 있다. + - expected: `⠋⠝⠂⠐⠕⠦⠄⠴⠠⠠⠅⠑⠇⠇⠽⠠⠴⠉⠵⠀⠠⠦⠴⠠` + - actual: `⠋⠝⠂⠐⠕⠦⠄⠴⠠⠠⠅⠑⠇⠇⠽⠠⠴⠉⠵⠀⠠⠦⠴⠠` - current primary/reason: `exact` / `exact` Representative `localized_mismatch` samples: -- `sentence_01.json` #78: 2023년형 Neo QLED는 스마트싱스뿐 아니라 매터(Matter) 기기까지 지원하는 원칩 모듈을 탑재해 별도의 동글을 사용하지 않아도, 다양한 사물인터넷(IoT) 기기를 직접 TV에 연결해 사용할 수 있다. - - expected: `⠻⠀⠴⠠⠝⠑⠕⠀⠠⠠⠟⠇⠑⠙⠲⠉⠵⠀⠠⠪⠑⠓⠪⠠` - - actual: `⠻⠀⠴⠠⠝⠑⠕⠀⠴⠠⠠⠟⠇⠫⠲⠉⠵⠀⠠⠪⠑⠓⠪⠠` - - first differing cell (zero-based): 17 - - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_02.json` #332: 홍성군청소년수련관(관장 피기용) 진로체험지원센터가 오는 16일 청운대학교 운동장에서 관내 중학교 1학년 대상으로 제9회 홍성군 청소년 진로박람회 ‘꿈(JOB)고 두드림(DO DREAM)’을 진행한다. - - expected: `⠦⠄⠴⠠⠠⠙⠕⠀⠠⠠⠙⠗⠂⠍⠠⠴⠴⠄⠮⠀⠨⠟⠚⠗` - - actual: `⠦⠄⠴⠠⠠⠙⠕⠀⠴⠠⠠⠙⠗⠂⠍⠠⠴⠴⠄⠮⠀⠨⠟⠚` - - first differing cell (zero-based): 183 +- `sentence_01.json` #23829: 증강현실(AR)과 인공지능(AI) 기술을 접목한 3D AR 아바타 제작 애플리케이션인 ‘제페토’를 활용해 비대면 동아리 소모임과 심뇌혈관질환 예방사업 운영 및 홍보 등을 한다. + - expected: `⠠⠕⠂⠦⠄⠴⠠⠠⠁⠗⠠⠴⠈⠧⠀⠟⠈⠿⠨⠕⠉⠪⠶⠦` + - actual: `⠠⠕⠂⠦⠄⠴⠠⠠⠜⠠⠴⠈⠧⠀⠟⠈⠿⠨⠕⠉⠪⠶⠦⠄` + - first differing cell (zero-based): 15 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_03.json` #302: 건국대 경영전문대학원(KU MBA)은 2015년부터 국제경영대학발전협의회(AACSB) 인증을 보유하고 있다. 고등교육법 시행령에 따라 교육부가 인가한 13개 경영전문대학원 중 하나다. 경영전문대학원인 만큼 엄격하게 교육 품질을 관리한다. - - expected: `⠦⠄⠴⠠⠠⠅⠥⠀⠠⠠⠍⠃⠁⠠⠴⠵⠀⠼⠃⠚⠁⠑⠀⠉` - - actual: `⠦⠄⠴⠠⠠⠅⠥⠀⠴⠠⠠⠍⠃⠁⠠⠴⠵⠀⠼⠃⠚⠁⠑⠀` - - first differing cell (zero-based): 29 +- `sentence_02.json` #5670: 친구청소년쉼터의 대표적인 ‘UP UP CAMP’ 프로그램은 ‘청소년들이 정상에 오르며(UP) 인내와 성취감, 자신감을 더한다(UP)’는 목적에서 붙여진 이름으로, 올해로 8회째를 운영되고 있는 시즌제 프로그램이다. + - expected: `⠹⠟⠀⠠⠦⠴⠠⠠⠠⠥⠏⠀⠥⠏⠀⠉⠁⠍⠏⠠⠄⠴⠄⠀` + - actual: `⠹⠟⠀⠠⠦⠴⠠⠠⠥⠏⠀⠠⠠⠥⠏⠀⠠⠠⠉⠁⠍⠏⠴⠄` + - first differing cell (zero-based): 31 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_04.json` #363: 켈리(KELLY)는 ‘KEEP NATUALLY’의 줄임말로 인위적인 것을 최소화하고 자연주의적인 원료, 공법, 맛을 추구한다는 의미를 담고 있다. - - expected: `⠴⠠⠠⠅⠑⠑⠏⠀⠠⠠⠝⠁⠞⠥⠁⠇⠇⠽⠴⠄⠺⠀⠨⠯` - - actual: `⠴⠠⠠⠅⠑⠑⠏⠀⠴⠠⠠⠝⠁⠞⠥⠁⠇⠇⠽⠴⠄⠺⠀⠨` - - first differing cell (zero-based): 30 +- `sentence_04.json` #3324: 삼성전자는 이날 생성형 인공지능(AI) 서버에 적용되는 서버용 SSD ‘PM1743’과 쿼드러플 레벨 셀(QLC) 낸드 기반 256TB SSD도 선보였다. + - expected: `⠶⠀⠴⠠⠠⠎⠎⠙⠀⠠⠦⠠⠠⠏⠍⠼⠁⠛⠙⠉⠠⠴⠲⠈` + - actual: `⠶⠀⠴⠠⠠⠎⠎⠙⠲⠀⠠⠦⠴⠠⠠⠏⠍⠼⠁⠛⠙⠉⠴⠄` + - first differing cell (zero-based): 68 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` Representative `mismatch` samples: @@ -2025,10 +2018,10 @@ Representative `mismatch` samples: - actual: `⠌⠠⠧⠗⠚⠽⠐⠥⠦⠄⠴⠠⠉⠠⠉⠠⠴⠰⠠⠠⠞⠧⠲⠐` - first differing cell (zero-based): 128 - current primary/reason: `corpus_suspect` / `rule34_roman_indicator_before_opening_parenthesis` -- `sentence_04.json` #363: 켈리(KELLY)는 ‘KEEP NATUALLY’의 줄임말로 인위적인 것을 최소화하고 자연주의적인 원료, 공법, 맛을 추구한다는 의미를 담고 있다. - - expected: `⠴⠠⠠⠅⠑⠑⠏⠀⠠⠠⠝⠁⠞⠥⠁⠇⠇⠽⠴⠄⠺⠀⠨⠯` - - actual: `⠴⠠⠠⠅⠑⠑⠏⠀⠴⠠⠠⠝⠁⠞⠥⠁⠇⠇⠽⠴⠄⠺⠀⠨` - - first differing cell (zero-based): 30 +- `sentence_04.json` #1304: 미국 시장에서 한화큐셀의 주력 제품은 ‘큐피크 듀오(Q.PEAK DUO)’ 시리즈다. 최신 제품인 큐피크 듀오 G11S는 대형 웨이퍼를 사용하여 출력을 극대화한 제품으로 최대 출력이 605Wp(와트피크)에 달한다. + - expected: `⠪⠀⠊⠩⠥⠀⠴⠠⠛⠼⠁⠁⠠⠎⠲⠉⠵⠀⠊⠗⠚⠻⠀⠏` + - actual: `⠪⠀⠊⠩⠥⠀⠴⠠⠠⠛⠼⠁⠁⠎⠲⠉⠵⠀⠊⠗⠚⠻⠀⠏` + - first differing cell (zero-based): 106 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` ## UEB grade-1 first-difference cohorts @@ -2037,10 +2030,10 @@ These cohorts are defined by both an input boundary and the sentence's actual fi | Cohort | Candidates | Exact controls | Mismatch | Target localized | Reverse | |---|---:|---:|---:|---:|---:| -| `allcaps_roman_run_beginning_with_pure_letter_shortform` | 4120 | 2395 | 1725 | 781 | 42 | -| `uppercase_ascii_run_immediately_after_digit_in_roman_sequence` | 1896 | 1080 | 816 | 0 | 1 | -| `uppercase_ascii_run_immediately_after_hyphen_in_roman_sequence` | 952 | 158 | 794 | 157 | 3 | -| `pure_allcaps_segment_before_hyphen_and_multi_allcaps_segment_after` | 448 | 126 | 322 | 0 | 1 | +| `allcaps_roman_run_beginning_with_pure_letter_shortform` | 4120 | 2425 | 1695 | 781 | 42 | +| `uppercase_ascii_run_immediately_after_digit_in_roman_sequence` | 1896 | 1092 | 804 | 0 | 1 | +| `uppercase_ascii_run_immediately_after_hyphen_in_roman_sequence` | 952 | 160 | 792 | 157 | 3 | +| `pure_allcaps_segment_before_hyphen_and_multi_allcaps_segment_after` | 448 | 127 | 321 | 0 | 1 | ### All-caps shortform prefix at an attached Roman entry @@ -2062,7 +2055,7 @@ Same-surface controls demonstrate why primary classes must not be changed by coh | Surface | Candidates | Exact | Mismatch | Target-localized | |---|---:|---:|---:|---:| -| `AC` | 159 | 85 | 74 | 66 | +| `AC` | 159 | 86 | 73 | 66 | | `LLM` | 176 | 1 | 175 | 149 | | `CD` | 65 | 26 | 39 | 12 | | `IMM` | 27 | 3 | 24 | 22 | @@ -2097,16 +2090,18 @@ Korean rule 29 requires a Roman indicator before Roman text in a Korean sentence | Cohort | Candidates | Exact controls | Mismatch | Pending | Corpus suspect | Localized `⠴ -> blank` | Reverse `blank -> ⠴` | |---|---:|---:|---:|---:|---:|---:|---:| | `roman_hyphenated_word_after_whitespace_following_korean_word` | 361 | 0 | 361 | 350 | 6 | 135 | 0 | -| `roman_parenthetical_headword_after_whitespace_following_korean_word` | 4695 | 3342 | 1353 | 1264 | 36 | 153 | 0 | +| `roman_parenthetical_headword_after_whitespace_following_korean_word` | 4695 | 3358 | 1337 | 1247 | 37 | 153 | 0 | | `korean_prefixed_roman_parenthetical_followed_by_allcaps_hyphen_suffix` | 11 | 0 | 11 | 6 | 5 | 5 | 0 | -The whitespace parenthetical-headword cohort also retains 1 localized `⠠ -> ⠴` and 2 localized `⠴ -> ⠰` cases as separate transitions; they are not folded into the target. The exact controls demonstrate that the broad structure is already correct in many sentences, while the attached parenthetical-hyphen cohort has no exact control and includes cases already classified by the stricter rule-34 reference-order contradiction. Consequently none of these measurements authorizes an engine change; they are deterministic pending/corpus-review diagnostics only. +The whitespace parenthetical-headword cohort also retains 1 localized `⠠ -> ⠴` and 3 localized `⠴ -> ⠰` cases as separate transitions; they are not folded into the target. The exact controls demonstrate that the broad structure is already correct in many sentences, while the attached parenthetical-hyphen cohort has no exact control and includes cases already classified by the stricter rule-34 reference-order contradiction. Consequently none of these measurements authorizes an engine change; they are deterministic pending/corpus-review diagnostics only. ### Consecutive Roman uppercase-word re-entry Korean rule 29 explicitly says that when two or more Roman items occur consecutively, the Roman indicator is placed only before the first and the terminator only after the last. Its printed `Los Angeles` and `Table of Contents` examples exercise multiword Roman sections; the rule-28 appendix independently supplies capitalization indicators inside that section. The current token phase can nevertheless insert an explicit Roman-entry event before a later uppercase word when the preceding Roman run began inside a mixed Korean/punctuation word. The character emitter is still in Roman mode at that point, so this is a candidate duplicate-event boundary rather than permission to rewrite arbitrary multiword ASCII text. -Baseline measurement: 1729 candidates, 386 exact controls, 1343 mismatches, 1272 pending members, and 385/1343 evaluable mismatches localized to the current re-entry signature. The localized transitions are 365 `⠠ -> ⠴`, 16 `⠰ -> ⠴`, and 0 reverse `⠴ -> ⠠`; the remaining localized transitions stay separate. Exact controls include contexts where the first Roman word already opened token-level mode, while parenthesized/mixed-token examples expose the duplicate event. Any engine experiment must therefore suppress only an explicit entry encountered while final emit state is already Roman, then audit all exact regressions and the complete 5,141-case standard suite. +Analyzer checkpoint `266e70c` fixed the pre-change baseline at 1,729 candidates, 386 exact controls, 1,343 mismatches, 1,272 pending members, and 385/1,343 evaluable mismatches localized to the current re-entry signature. Those localized transitions were 365 `⠠ -> ⠴`, 16 `⠰ -> ⠴`, no reverse `⠴ -> ⠠`, and four other transitions. Exact controls include contexts where the first Roman word already opened token-level mode, while parenthesized/mixed-token examples expose the duplicate event. + +The generalized fix makes an explicit `EnterEnglish` event idempotent when final emit state is already inside a Roman section; it neither names an input nor changes a new section's entry. The current measurement is 1729 candidates, 661 exact controls, 1068 mismatches, 994 pending members, and 4/1068 localized mismatches. Current target counts are 0 `⠠ -> ⠴`, 0 `⠰ -> ⠴`, and 0 reverse `⠴ -> ⠠`. Cohort exact controls increase by 275, while corpus-wide exact matches rise from 67,138 to 67,442 (+304); corrected prefixes that still differ later remain mismatches, and applications outside this strict input gate account for the remaining net gain. The raw/residual reverse `⠴ -> ⠠` totals remain 68/65 before and after the change. The complete custom standard summary is 5,141 total, 5,141 success, 0 failure, and 0 skipped. The HCA-style headword-expansion shape described above is not an engine implementation premise. The 2024 PDF's math rule 6 defines parentheses and grouping parentheses, rule 11 defines mathematical-expression spacing, rule 12 covers Roman letters in formulas as well as Korean sentences, and rule 45 shows Roman-letter function notation followed by parentheses. Excluding visible operators, scripts, and nesting narrows this corpus cohort, but the PDF does not make the remaining surface shape sufficient to rule out every mathematical counterexample. The cluster therefore remains conservative pending-review evidence only. @@ -2131,24 +2126,24 @@ The tight-triangle cohort is not an implementation premise. Hangeul rule 49 assi Corpus contradictions remain a separate gate: identical inputs with conflicting references are classified as `corpus_suspect` before these cohorts are recorded and would appear explicitly in each mismatch primary-class distribution. Their absence does not prove a reference correct; it only means that this deterministic contradiction test did not fire. -Current uppercase-Roman hyphen-digits measurement: 571 candidates, 186 exact controls, 385 mismatches, 370 members in the actual `pending_rule_review` subcluster, and 260/385 evaluable mismatches whose first difference is inside the target run plus its entry boundary. It remains distinct from parenthesized digits and headword expansions; no engine change is inferred. +Current uppercase-Roman hyphen-digits measurement: 571 candidates, 189 exact controls, 382 mismatches, 367 members in the actual `pending_rule_review` subcluster, and 261/382 evaluable mismatches whose first difference is inside the target run plus its entry boundary. It remains distinct from parenthesized digits and headword expansions; no engine change is inferred. Current single-capital parenthesized-digits measurement: 1361 candidates, 0 exact controls, 1361 mismatches, 1357 members in the actual `pending_rule_review` subcluster, and 1342/1361 evaluable mismatches whose first difference is inside the target run plus its entry boundary. No engine change is inferred from the ambiguous prose/function surface form. Current mixed Roman+Korean boundary before uppercase headword-expansion measurement: 10 candidates, 0 exact controls, 10 mismatches, 10 members in the actual `pending_rule_review` subcluster, and 8/10 evaluable mismatches whose first difference is localized to the later headword's entry boundary/output. The detector cannot be satisfied by the earlier Roman entry. No HCA-shaped engine routing rule is introduced. -Current compact numeric+ASCII-suffix measurement: 2975 candidates, 1842 exact controls, 1133 mismatches, 1065 members in the actual `pending_rule_review` subcluster, and 252/1133 evaluable mismatches whose first difference is inside the complete current-engine output signature or its immediate entry boundary. Rule 40 requires the numeric indicator and rule 69 requires Roman indicators around a Roman-written unit, but the input shape alone cannot prove that every ASCII suffix is a unit. +Current compact numeric+ASCII-suffix measurement: 2975 candidates, 1855 exact controls, 1120 mismatches, 1052 members in the actual `pending_rule_review` subcluster, and 253/1120 evaluable mismatches whose first difference is inside the complete current-engine output signature or its immediate entry boundary. Rule 40 requires the numeric indicator and rule 69 requires Roman indicators around a Roman-written unit, but the input shape alone cannot prove that every ASCII suffix is a unit. | ASCII suffix | Candidates | Exact | Mismatch | Localized first diff | |---|---:|---:|---:|---:| | `m` | 364 | 258 | 106 | 22 | | `km` | 308 | 249 | 59 | 3 | -| `G` | 237 | 162 | 75 | 3 | -| `kg` | 209 | 173 | 36 | 6 | -| `D` | 167 | 113 | 54 | 5 | +| `G` | 237 | 163 | 74 | 3 | +| `kg` | 209 | 174 | 35 | 6 | +| `D` | 167 | 115 | 52 | 5 | | `p` | 140 | 20 | 120 | 84 | | `g` | 138 | 95 | 43 | 8 | -| `t` | 128 | 83 | 45 | 16 | +| `t` | 128 | 84 | 44 | 16 | | `M` | 104 | 79 | 25 | 6 | | `cm` | 69 | 51 | 18 | 0 | | `B` | 62 | 37 | 25 | 0 | @@ -2160,28 +2155,28 @@ Current compact numeric+ASCII-suffix measurement: 2975 candidates, 1842 exact co | `GW` | 44 | 35 | 9 | 6 | | `X` | 39 | 14 | 25 | 2 | | `bp` | 39 | 3 | 36 | 18 | -| `K` | 38 | 11 | 27 | 1 | +| `K` | 38 | 13 | 25 | 1 | | `MW` | 37 | 35 | 2 | 0 | -| `TURN` | 33 | 18 | 15 | 0 | +| `TURN` | 33 | 19 | 14 | 0 | | `L` | 31 | 21 | 10 | 5 | | `mm` | 29 | 19 | 10 | 1 | | `egin` | 28 | 27 | 1 | 0 | Entry-boundary pre-fix baseline: 2,975 candidates, 1,649 exact controls, 1,326 mismatches, 1,259 pending members, and 356 localized first differences; the dominant reference number-sign versus current space transition accounted for 296 cases. Rules 68 and 69 already accept semantic Unicode compatibility-unit forms. The implementation derives compact ASCII spellings only from their all-letter NFKC decompositions, reuses the owning rule's PDF-defined cells, chooses the longest complete spelling, and rejects partial suffix matches. It does not extend recognition to separated English words or arbitrary corpus suffixes. The same cohort now has 1,759 exact controls, 1,216 mismatches, 1,148 pending members, and 261 localized first differences; corpus-wide exact matches moved from 66,436 to 66,546 (+110). Rule 69's printed `160㎎/㎗` example directly controls `160mg`; the additional full-encoder `240mg`/`240㎎` pair proves that recognition is invariant to the numeric value and also passes. Rule 68's printed `10,000㎡는 1㏊이다` example controls the `ha` spelling through U+33CA's NFKC decomposition; `15.2ha`/`15.2㏊` now passes without a spelling-specific output branch. The 53-case `ha` suffix control moved from 0 exact to 18 exact; its remaining 35 mismatches have independent later or surrounding differences. A full U+3300..U+33FF owner audit found 73 Rules 68/69 glyphs with pure-ASCII NFKC decompositions, 73 distinct spellings, and therefore no current duplicate-spelling owner collision. Production nevertheless groups every owner before resolution and excludes a spelling if any owner cells differ; a synthetic collision test proves that this is not first-wins behavior. An exhaustive test also compares every one of the 73 derived spellings with every owner glyph at both the unit-cell and full-encoder boundaries in a neutral Korean measurement context. That audit exposed the pre-existing explicit `cal` mapping's missing ordinary terminator; rule 69 requires the terminator, while the existing slash-boundary function removes it for the printed `cal/㎠/min` context. Restoring the ordinary terminator and extending that slash-continuation boundary made the audit pass without changing the corpus-wide 66,546 exact total. Ambiguous pure-English inputs remain on UEB: the standard controls `3m` and `4.m` are retained and pass, rather than being globally forced into a Korean unit route. -Current rule-69 ASCII-unit punctuation-boundary measurement: 440 candidates, 335 exact controls, 105 mismatches, 100 members in the actual `pending_rule_review` subcluster, and 10/105 evaluable mismatches whose first difference is localized to the unit-plus-punctuation output signature. PDF rule 69 requires a Roman terminator after a Roman-written unit in the ordinary case, while rules 33/34 omit it at the listed punctuation or enclosing-mark boundary; the rule 46 PDF example `체중(kg)` is the minimal parenthesized-unit control. Membership is restricted to rule-69 spellings already supported by the engine and does not infer unit semantics for arbitrary ASCII suffixes. +Current rule-69 ASCII-unit punctuation-boundary measurement: 440 candidates, 336 exact controls, 104 mismatches, 99 members in the actual `pending_rule_review` subcluster, and 10/104 evaluable mismatches whose first difference is localized to the unit-plus-punctuation output signature. PDF rule 69 requires a Roman terminator after a Roman-written unit in the ordinary case, while rules 33/34 omit it at the listed punctuation or enclosing-mark boundary; the rule 46 PDF example `체중(kg)` is the minimal parenthesized-unit control. Membership is restricted to rule-69 spellings already supported by the engine and does not infer unit semantics for arbitrary ASCII suffixes. Analyzer pre-fix baseline: 440 candidates, 0 exact controls, 440 mismatches, 435 pending members, and 9 signature-local first differences. After the generalized rule-33/34 boundary override and the matching non-math routing guard, the same cohort has 325 exact controls and 115 mismatches. Corpus-wide exact matches moved from 66,039 to 66,436 (+397); the additional gains are applications of the same boundary rule outside this strict ASCII detector, including compatibility-unit forms. The complete standard suite remains 5,141/5,141. -Current decimal-point measurement: 4546 candidates, 3257 exact controls, 1289 mismatches, 1233 members in the actual `pending_rule_review` subcluster, and 264/1289 evaluable mismatches whose first difference is inside the complete decimal-containing word's current-engine signature. Hangeul rules 43 and 48 keep an ASCII point between digits in the numeric sequence and encode it as the decimal point; rules 35 and 69 supply controls for adjacent Roman-number chains and Roman units. This is an implementation-candidate audit, not permission to specialize on a corpus reference. +Current decimal-point measurement: 4546 candidates, 3267 exact controls, 1279 mismatches, 1223 members in the actual `pending_rule_review` subcluster, and 265/1279 evaluable mismatches whose first difference is inside the complete decimal-containing word's current-engine signature. Hangeul rules 43 and 48 keep an ASCII point between digits in the numeric sequence and encode it as the decimal point; rules 35 and 69 supply controls for adjacent Roman-number chains and Roman units. This is an implementation-candidate audit, not permission to specialize on a corpus reference. Analyzer checkpoint `ed20f99` recorded the pre-fix baseline as 4,546 candidates, 2,584 exact controls, 1,962 mismatches, 1,905 pending members, and 875 localized first differences. Its dominant localized transition was reference decimal point `U+2832 ⠲` versus current Roman indicator `U+2834 ⠴` in 647 cases. After the generalized rule-43/48 guard, that transition is absent: 525 cases become exact and the other corrected prefixes expose later independent mismatches. -Current all-caps `OU` measurement: 1816 candidates, 55 exact controls, 1761 mismatches, 1675 members in the actual `pending_rule_review` subcluster, and 1315/1761 evaluable mismatches whose first difference is inside the current-engine output signature for the detected run. This is a pronunciation-sensitive UEB review cohort, not an engine routing rule. +Current all-caps `OU` measurement: 1816 candidates, 64 exact controls, 1752 mismatches, 1666 members in the actual `pending_rule_review` subcluster, and 1321/1752 evaluable mismatches whose first difference is inside the current-engine output signature for the detected run. This is a pronunciation-sensitive UEB review cohort, not an engine routing rule. -Current standalone-uppercase measurement: 62411 candidates, 49518 exact controls, 12893 mismatches, and 11445 members in the actual `pending_rule_review` subcluster. Its high frequency does not make it causal: the same input shape is exact in many cases, and a sentence containing the shape may first differ at another Roman, numeric, or punctuation structure. No engine change is inferred from this cohort. +Current standalone-uppercase measurement: 62411 candidates, 49811 exact controls, 12600 mismatches, and 11149 members in the actual `pending_rule_review` subcluster. Its high frequency does not make it causal: the same input shape is exact in many cases, and a sentence containing the shape may first differ at another Roman, numeric, or punctuation structure. No engine change is inferred from this cohort. -Current Korean-prefixed all-caps parenthetical measurement: 54492 candidates, 44300 exact controls, 10192 mismatches, and 8900 members in the actual `pending_rule_review` subcluster. This is a semantic-collision audit, not an engine routing rule; no implementation change is inferred from its reference outputs. +Current Korean-prefixed all-caps parenthetical measurement: 54492 candidates, 44483 exact controls, 10009 mismatches, and 8714 members in the actual `pending_rule_review` subcluster. This is a semantic-collision audit, not an engine routing rule; no implementation change is inferred from its reference outputs. -Current rule-34 opening-order measurement: 64382 structural candidates, 51907 exact controls, 12475 mismatches, and 1101/12475 evaluable mismatches whose first difference is inside the current engine's Korean opening-parenthesis cells. Only the 1100 localized first-cell transitions have the reference/current order `⠴` versus `⠦`. After requiring the complete reference `⠴⠐⠣` versus current/PDF `⠦⠄⠴` three-cell signature and preserving higher-priority comparison classifications, 1097 are classified as `corpus_suspect`; mere coexistence with a Korean-prefixed Roman annotation does not change a primary class. +Current rule-34 opening-order measurement: 64382 structural candidates, 52156 exact controls, 12226 mismatches, and 1103/12226 evaluable mismatches whose first difference is inside the current engine's Korean opening-parenthesis cells. Only the 1102 localized first-cell transitions have the reference/current order `⠴` versus `⠦`. After requiring the complete reference `⠴⠐⠣` versus current/PDF `⠦⠄⠴` three-cell signature and preserving higher-priority comparison classifications, 1099 are classified as `corpus_suspect`; mere coexistence with a Korean-prefixed Roman annotation does not change a primary class. Current all-caps Roman middle-dot measurement: 97 candidates, 0 exact controls, 97 mismatches, and 96 members in the actual `pending_rule_review` subcluster. This cross-cutting cohort preserves every primary class and is not an engine routing rule. @@ -2190,7 +2185,7 @@ Current Roman-before-middle-dot boundary measurement: 577 candidates, 0 exact co Current inline parenthesized-operator measurement: 23 candidates, 17 exact controls, 6 mismatches, 6 members in the actual `pending_rule_review` subcluster, and 2/6 evaluable mismatches whose first differing cell is inside the emitted structure. Primary classes are preserved. At that implementation checkpoint, the strict cohort moved from 0 to 17 exact cases; the corpus-wide total moved from 65,491 to 65,514 (+23 exact) because the same PDF-backed spacing rule also applied outside the stricter Korean-boundary audit gate. These are immutable checkpoint counts rather than the report's later cumulative total. The complete standard suite remained 5,141/5,141. -Current tight-triangle measurement: 377 candidates, 0 exact controls, 377 mismatches, 376 members in the actual `pending_rule_review` subcluster, and 331/377 evaluable mismatches whose first difference is inside the `△` plus first-Korean output range. No engine change is inferred. +Current tight-triangle measurement: 377 candidates, 0 exact controls, 377 mismatches, 376 members in the actual `pending_rule_review` subcluster, and 332/377 evaluable mismatches whose first difference is inside the `△` plus first-Korean output range. No engine change is inferred. ## Encoding-error diagnostics @@ -2199,10 +2194,10 @@ The audit starts from all raw encoding errors, then separates cases already reso | Encoding-error audit | Cases | |---|---:| | Raw encoding errors | 450 | -| Resolved by comparison method | 267 | +| Resolved by comparison method | 270 | | Excluded as corpus suspect | 0 | -| Unresolved encoding-error review cases | 183 | -| Explained by singleton unsupported character(s) | 183 | +| Unresolved encoding-error review cases | 180 | +| Explained by singleton unsupported character(s) | 180 | | Multiple singleton unsupported characters | 2 | | Unclassified without a singleton explanation | 0 | @@ -2213,11 +2208,11 @@ The audit starts from all raw encoding errors, then separates cases already reso | Error message | Cases | |---|---:| -| `Invalid symbol character` | 183 | +| `Invalid symbol character` | 180 | | Error family | Cases | |---|---:| -| `enclosed_organization_mark` | 164 | +| `enclosed_organization_mark` | 161 | | `letterlike_unit_symbol` | 9 | | `punctuation_or_layout_symbol` | 12 | @@ -2236,24 +2231,24 @@ Families are diagnostics, not automatic normalization permissions. Rules 68/69 c | `U+2E31 ⸱` | 2 | `⸱` | `punctuation_or_layout_symbol` | | `U+302E 〮` | 1 | `〮` | `punctuation_or_layout_symbol` | | `U+3214 ㈔` | 2 | `(사)` | `enclosed_organization_mark` | -| `U+321C ㈜` | 162 | `(주)` | `enclosed_organization_mark` | +| `U+321C ㈜` | 159 | `(주)` | `enclosed_organization_mark` | ## Shards | Shard | Exact | Total | Accuracy | |---|---:|---:|---:| -| `sentence_01.json` | 20166 | 25000 | 80.66% | -| `sentence_02.json` | 19620 | 25000 | 78.48% | -| `sentence_03.json` | 20238 | 25000 | 80.95% | -| `sentence_04.json` | 7114 | 8528 | 83.42% | +| `sentence_01.json` | 20260 | 25000 | 81.04% | +| `sentence_02.json` | 19693 | 25000 | 78.77% | +| `sentence_03.json` | 20359 | 25000 | 81.44% | +| `sentence_04.json` | 7130 | 8528 | 83.61% | ## Overlapping mismatch traits | Trait | Count | |---|---:| -| `contains_ascii_digits` | 12818 | -| `contains_ascii_letters` | 16204 | -| `contains_delimiter_or_quote` | 16390 | +| `contains_ascii_digits` | 12587 | +| `contains_ascii_letters` | 15900 | +| `contains_delimiter_or_quote` | 16086 | | `input_not_nfkc` | 718 | ## Samples @@ -2286,16 +2281,15 @@ Families are diagnostics, not automatic normalization permissions. Rules 68/69 c - expected: `⠈⠕⠢⠑⠍⠚⠧⠒⠀⠴⠠⠠⠎⠅⠦⠄⠨⠍⠠⠴⠀⠈⠪⠐` - actual: `` - error: `Invalid symbol character` -- `sentence_01.json` #2662: 교육 프로그램은 ‘구글 클라우드 기반 인공지능 이해·빅쿼리(BigQuery) 전문가 과정’으로, 구글 클라우드의 공식 교육파트너사인 ㈜아이코어이앤씨가 맡는다. - - expected: `⠈⠬⠩⠁⠀⠙⠪⠐⠥⠈⠪⠐⠗⠢⠵⠀⠠⠦⠈⠍⠈⠮⠀⠋` - - actual: `` - - error: `Invalid symbol character` -- `sentence_01.json` #2929: 경상남도 김해시에 소재한 기업인 ㈜하톤(hearton)이 MBTI를 활용, 세계최초로 컬러테라피 조명을 통해 개인의 정신적인 건강에 도움을 줄 수 있는 ‘하톤’이라는 제품을 출시해 주목받고 있다. - - expected: `⠈⠻⠇⠶⠉⠢⠊⠥⠀⠈⠕⠢⠚⠗⠠⠕⠝⠀⠠⠥⠨⠗⠚⠒` +- `sentence_01.json` #2336: 한편, 한국팀 경기를 포함한 아시아컵 주요 경기는 세계야구소프트볼연맹(WBSC) 공식 유튜브에서 확인할 수 있다. 이번 대회 운영 대행은 ㈜에스피에스 코리아(SPS KOREA)가 맡아 진행한다. + - expected: `⠚⠒⠙⠡⠐⠀⠚⠒⠈⠍⠁⠓⠕⠢⠀⠈⠻⠈⠕⠐⠮⠀⠙⠥` - actual: `` - error: `Invalid symbol character` -- `sentence_01.json` #3149: 한편 공동연구개발기관인 농업회사법인 ㈜영풍은 대구시 스타기업으로 현재 떡볶이, 전, 스낵류 등 가정간편식(HMR) 제품을 80개국에 수출하고 있다. - - expected: `⠚⠒⠙⠡⠀⠈⠿⠊⠿⠡⠈⠍⠈⠗⠘⠂⠈⠕⠈⠧⠒⠟⠀⠉` +- `sentence_01.json` #2530: 신세계인터내셔날의 자체 헤어케어 브랜드 저스트 에즈 아이엠이 이달 7일 브랜드 전속 모델인 가수 ‘비비(BIBI)’와 함께 음원 ‘안녕하세오 샴푸애요(I AM…)’를 공식 발매한다고 밝혔다. + - expected: `⠴⠠⠊⠀⠠⠠⠁⠍⠲⠲⠲⠠⠴⠴⠄⠐⠮⠀⠈⠿⠠⠕⠁⠀` + - actual: `⠴⠠⠊⠀⠠⠠⠁⠍⠠⠠⠠⠠⠴⠴⠄⠐⠮⠀⠈⠿⠠⠕⠁⠀` +- `sentence_01.json` #2662: 교육 프로그램은 ‘구글 클라우드 기반 인공지능 이해·빅쿼리(BigQuery) 전문가 과정’으로, 구글 클라우드의 공식 교육파트너사인 ㈜아이코어이앤씨가 맡는다. + - expected: `⠈⠬⠩⠁⠀⠙⠪⠐⠥⠈⠪⠐⠗⠢⠵⠀⠠⠦⠈⠍⠈⠮⠀⠋` - actual: `` - error: `Invalid symbol character` @@ -2371,8 +2365,8 @@ Families are diagnostics, not automatic normalization permissions. Rules 68/69 c - expected: `⠊⠗⠈⠍⠈⠧⠶⠱⠁⠠⠕⠦⠄⠠⠕⠨⠶⠀⠚⠿⠨⠛⠙⠬` - actual: `` - error: `Invalid symbol character` -- `sentence_01.json` #2336: 한편, 한국팀 경기를 포함한 아시아컵 주요 경기는 세계야구소프트볼연맹(WBSC) 공식 유튜브에서 확인할 수 있다. 이번 대회 운영 대행은 ㈜에스피에스 코리아(SPS KOREA)가 맡아 진행한다. - - expected: `⠚⠒⠙⠡⠐⠀⠚⠒⠈⠍⠁⠓⠕⠢⠀⠈⠻⠈⠕⠐⠮⠀⠙⠥` +- `sentence_01.json` #2946: 투자 기업은 ㈜신아(INC)로 수도권, 대전, 부산 지역에 선도적으로 지식산업센터를 보급해 민간의 자발적 후속 투자를 이끌어 내는 성과를 거두는 등 전문성을 갖춘 기업으로 평가된다. + - expected: `⠓⠍⠨⠀⠈⠕⠎⠃⠵⠀⠦⠄⠨⠍⠠⠴⠠⠟⠣⠦⠄⠴⠠⠠` - actual: `` - error: `Invalid symbol character` diff --git a/libs/braillify/examples/nikl_corpus_analyze.rs b/libs/braillify/examples/nikl_corpus_analyze.rs index 44d7238f..3e036245 100644 --- a/libs/braillify/examples/nikl_corpus_analyze.rs +++ b/libs/braillify/examples/nikl_corpus_analyze.rs @@ -3439,15 +3439,23 @@ fn markdown(report: &AnalysisReport) -> String { the preceding Roman run began inside a mixed Korean/punctuation word. The character \ emitter is still in Roman mode at that point, so this is a candidate duplicate-event \ boundary rather than permission to rewrite arbitrary multiword ASCII text.\n\n\ - Baseline measurement: {} candidates, {} exact controls, {} mismatches, {pending} \ - pending members, and {}/{} evaluable mismatches localized to the current re-entry \ - signature. The localized transitions are {target} `⠠ -> ⠴`, \ - {grade1_to_roman} `⠰ -> ⠴`, and {reverse} reverse `⠴ -> ⠠`; the remaining localized \ - transitions stay separate. Exact controls include contexts where the first Roman word \ - already opened token-level mode, while parenthesized/mixed-token examples expose the \ - duplicate event. Any engine experiment must therefore suppress only an explicit entry \ - encountered while final emit state is already Roman, then audit all exact regressions \ - and the complete 5,141-case standard suite.\n\n", + Analyzer checkpoint `266e70c` fixed the pre-change baseline at 1,729 candidates, 386 \ + exact controls, 1,343 mismatches, 1,272 pending members, and 385/1,343 evaluable \ + mismatches localized to the current re-entry signature. Those localized transitions \ + were 365 `⠠ -> ⠴`, 16 `⠰ -> ⠴`, no reverse `⠴ -> ⠠`, and four other transitions. \ + Exact controls include contexts where the first Roman word already opened token-level \ + mode, while parenthesized/mixed-token examples expose the duplicate event.\n\n\ + The generalized fix makes an explicit `EnterEnglish` event idempotent when final emit \ + state is already inside a Roman section; it neither names an input nor changes a new \ + section's entry. The current measurement is {} candidates, {} exact controls, {} \ + mismatches, {pending} pending members, and {}/{} localized mismatches. Current target \ + counts are {target} `⠠ -> ⠴`, {grade1_to_roman} `⠰ -> ⠴`, and {reverse} reverse \ + `⠴ -> ⠠`. Cohort exact controls increase by 275, while corpus-wide exact matches rise \ + from 67,138 to 67,442 (+304); corrected prefixes that still differ later remain \ + mismatches, and applications outside this strict input gate account for the remaining \ + net gain. The raw/residual reverse `⠴ -> ⠠` totals remain 68/65 before and after the \ + change. The complete custom standard summary is 5,141 total, 5,141 success, 0 failure, \ + and 0 skipped.\n\n", stats.candidates, stats.exact, stats.mismatch, @@ -4763,19 +4771,15 @@ mod tests { } #[test] - fn locates_current_reentry_before_consecutive_uppercase_word() { - let input = "가 Neo QLED는"; + fn full_encoder_does_not_reenter_before_consecutive_uppercase_word() { + let input = "가(NEW YORK)"; let spans = consecutive_roman_uppercase_word_spans(input); let actual = braillify::encode_to_unicode(input).expect("Roman reentry probe must encode"); let ranges = roman_entry_signature_ranges(input, &actual, &spans, 0); - let starts = ranges - .iter() - .map(|range| range.start) - .collect::>(); assert_eq!(spans.len(), 1); - assert_eq!(starts.len(), 1); - assert_eq!(actual.chars().nth(*starts.first().unwrap()), Some('⠴')); + assert!(actual.contains("⠦⠄⠴⠠⠠")); + assert!(ranges.is_empty()); } #[rstest::rstest] diff --git a/libs/braillify/src/rules/emit.rs b/libs/braillify/src/rules/emit.rs index c83397c3..aa2389d4 100644 --- a/libs/braillify/src/rules/emit.rs +++ b/libs/braillify/src/rules/emit.rs @@ -298,7 +298,14 @@ fn word_context<'a>(word_texts: &'a [&'a str], word_index: usize) -> WordContext fn emit_mode_event(event: ModeEvent, state: &mut EncoderState, result: &mut Vec) { match event { ModeEvent::EnterEnglish => { - result.push(52); + // Korean rule 29 uses one Roman section for consecutive Roman + // text. Token-level capitalization can discover a later word and + // request entry again after the character emitter has already kept + // that section open; make the explicit event idempotent at the + // authoritative emit-state boundary. + if !state.is_english { + result.push(52); + } state.is_english = true; state.needs_english_continuation = false; state.roman_number_chain = false; @@ -877,6 +884,40 @@ mod tests { assert_eq!(out.iter().filter(|byte| **byte == 52).count(), 1); } + /// Korean rule 29: consecutive Roman text shares one Roman section. A + /// token-level rediscovery of capitalization must not emit a second entry + /// when the final character emitter is still in that section. + #[test] + fn repeated_explicit_roman_entry_is_idempotent_in_active_section() { + let new_chars = "NEW".chars().collect::>(); + let york_chars = "YORK".chars().collect::>(); + let mut ir = DocumentIR { + tokens: vec![ + Token::Mode(ModeEvent::EnterEnglish), + Token::Mode(ModeEvent::CapsWord), + Token::Word(WordToken { + text: Cow::Borrowed("NEW"), + chars: new_chars.clone(), + meta: super::super::token::WordMeta::from_chars(&new_chars), + }), + Token::Space(SpaceKind::Regular), + Token::Mode(ModeEvent::EnterEnglish), + Token::Mode(ModeEvent::CapsWord), + Token::Word(WordToken { + text: Cow::Borrowed("YORK"), + chars: york_chars.clone(), + meta: super::super::token::WordMeta::from_chars(&york_chars), + }), + ], + state: EncoderState::new(true), + }; + let mut engine = make_char_engine(); + + let out = emit(&mut ir, &mut engine).unwrap(); + + assert_eq!(out.iter().filter(|byte| **byte == 52).count(), 1); + } + /// Korean rule 35 PDF example: numbers do not split a roman section, so /// the later capital word resumes without another roman indicator. #[test] From 16483f130ed1799b0c4b961f370461420b407b49 Mon Sep 17 00:00:00 2001 From: devfive Date: Tue, 1 Sep 2026 12:25:17 +0900 Subject: [PATCH 32/77] Diagnose Roman parenthetical opening residual --- docs/corpus-analysis/NIKL_2025_V1.md | 225 ++++++++++++----- .../braillify/examples/nikl_corpus_analyze.rs | 226 +++++++++++++++++- 2 files changed, 384 insertions(+), 67 deletions(-) diff --git a/docs/corpus-analysis/NIKL_2025_V1.md b/docs/corpus-analysis/NIKL_2025_V1.md index c5a05303..b098bea8 100644 --- a/docs/corpus-analysis/NIKL_2025_V1.md +++ b/docs/corpus-analysis/NIKL_2025_V1.md @@ -298,49 +298,26 @@ This ranking removes only cases whose first difference is inside an existing out | Rank | Expected → actual first cell | Residual cases | |---:|---|---:| -| 1 | `U+2826 ⠦ -> U+2834 ⠴` | 454 | -| 2 | `U+280E ⠎ -> U+280C ⠌` | 448 | -| 3 | `U+2808 ⠈ -> U+2832 ⠲` | 409 | -| 4 | `U+2826 ⠦ -> U+2800 ⠀` | 394 | -| 5 | `U+2801 ⠁ -> U+281C ⠜` | 390 | -| 6 | `U+2834 ⠴ -> U+2830 ⠰` | 332 | -| 7 | `U+2811 ⠑ -> U+282B ⠫` | 325 | -| 8 | `U+2810 ⠐ -> U+2832 ⠲` | 290 | -| 9 | `U+2832 ⠲ -> U+2838 ⠸` | 281 | -| 10 | `U+2834 ⠴ -> U+2800 ⠀` | 258 | -| 11 | `U+2800 ⠀ -> U+2832 ⠲` | 227 | -| 12 | `U+2810 ⠐ -> U+2802 ⠂` | 210 | -| 13 | `U+2817 ⠗ -> U+2820 ⠠` | 163 | -| 14 | `U+2820 ⠠ -> U+2809 ⠉` | 160 | -| 15 | `U+283C ⠼ -> U+2800 ⠀` | 153 | -| 16 | `U+281B ⠛ -> U+2823 ⠣` | 144 | -| 17 | `U+2826 ⠦ -> U+2810 ⠐` | 144 | -| 18 | `U+2810 ⠐ -> U+2800 ⠀` | 130 | -| 19 | `U+2811 ⠑ -> U+283B ⠻` | 101 | -| 20 | `U+2810 ⠐ -> U+2814 ⠔` | 98 | - -### Residual `U+2826 ⠦ -> U+2834 ⠴` - -- `sentence_01.json` #38: 올해 소프트웨어(SW) 업계는 국내 인공지능(AI) 제품·서비스 고도화와 함께 전 산업영역에서 SW영역 확대가 가시화될 것으로 보인다. 또한 블록체인이 웹3(Web3)와 연계해 탈중앙화·탈독점화 경제체계로 진화할 것으로 예상된다. - - expected: `⠟⠕⠀⠏⠗⠃⠼⠉⠦⠄⠴⠠⠺⠑⠃⠼⠉⠠⠴⠧⠀⠡⠈⠌` - - actual: `⠟⠕⠀⠏⠗⠃⠼⠉⠴⠐⠣⠠⠺⠑⠃⠼⠉⠴⠐⠜⠲⠧⠀⠡` - - first differing cell (zero-based): 155 - - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_02.json` #38: 중국금형전시회(DMC) 및 독일금형전시회 (Moulding Expo)와 함께 세계 3대 규모의 금형산업 전문 전시회이며, 연관산업 전문 전시회를 KOPLAS와 동시 개최를 통한 시너지를 창출했다. - - expected: `⠻⠨⠾⠠⠕⠚⠽⠀⠦⠄⠴⠠⠍⠳⠇⠙⠬⠀⠠⠑⠭⠏⠕⠠` - - actual: `⠻⠨⠾⠠⠕⠚⠽⠀⠴⠐⠣⠠⠍⠳⠇⠙⠬⠀⠠⠑⠭⠏⠕⠐` - - first differing cell (zero-based): 48 - - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_03.json` #536: 뿐만아니라 윤 대통령은 조 바이든 미국 대통령과 26일(현지시간) 한미 정상회담에서 ‘워싱턴 선언’(Washington Declaration)을 통해 핵협의그룹(NCG)을 신설하는 등 확장억제력을 대폭 강화하는 데 합의했다. - - expected: `⠓⠾⠀⠠⠾⠾⠴⠄⠦⠄⠴⠠⠺⠁⠩⠬⠞⠕⠝⠀⠠⠙⠑⠉` - - actual: `⠓⠾⠀⠠⠾⠾⠴⠄⠴⠐⠣⠠⠺⠁⠩⠬⠞⠕⠝⠀⠠⠙⠑⠉` - - first differing cell (zero-based): 95 - - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_04.json` #582: 반면 마이크로소프트(MS) 공동창업자인 빌 게이츠는 “(AI 개발 일시 중단은) 문제를 해결하지 못할 것”이라며 “우리가 해야 할 일은 AI의 까다로운 문제들을 파악하는 것”이라고 말했다. - - expected: `⠝⠕⠰⠪⠉⠵⠀⠦⠦⠄⠴⠠⠠⠁⠊⠲⠀⠈⠗⠘⠂⠀⠕⠂` - - actual: `⠝⠕⠰⠪⠉⠵⠀⠦⠴⠐⠣⠠⠠⠁⠊⠲⠀⠈⠗⠘⠂⠀⠕⠂` - - first differing cell (zero-based): 54 - - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +| 1 | `U+280E ⠎ -> U+280C ⠌` | 448 | +| 2 | `U+2808 ⠈ -> U+2832 ⠲` | 409 | +| 3 | `U+2801 ⠁ -> U+281C ⠜` | 390 | +| 4 | `U+2834 ⠴ -> U+2830 ⠰` | 332 | +| 5 | `U+2811 ⠑ -> U+282B ⠫` | 325 | +| 6 | `U+2810 ⠐ -> U+2832 ⠲` | 290 | +| 7 | `U+2832 ⠲ -> U+2838 ⠸` | 281 | +| 8 | `U+2834 ⠴ -> U+2800 ⠀` | 258 | +| 9 | `U+2800 ⠀ -> U+2832 ⠲` | 227 | +| 10 | `U+2826 ⠦ -> U+2800 ⠀` | 219 | +| 11 | `U+2810 ⠐ -> U+2802 ⠂` | 210 | +| 12 | `U+2817 ⠗ -> U+2820 ⠠` | 163 | +| 13 | `U+2820 ⠠ -> U+2809 ⠉` | 160 | +| 14 | `U+283C ⠼ -> U+2800 ⠀` | 153 | +| 15 | `U+281B ⠛ -> U+2823 ⠣` | 144 | +| 16 | `U+2826 ⠦ -> U+2810 ⠐` | 144 | +| 17 | `U+2810 ⠐ -> U+2800 ⠀` | 130 | +| 18 | `U+2811 ⠑ -> U+283B ⠻` | 101 | +| 19 | `U+2810 ⠐ -> U+2814 ⠔` | 98 | +| 20 | `U+280E ⠎ -> U+2820 ⠠` | 91 | ### Residual `U+280E ⠎ -> U+280C ⠌` @@ -388,29 +365,6 @@ This ranking removes only cases whose first difference is inside an existing out - first differing cell (zero-based): 90 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -### Residual `U+2826 ⠦ -> U+2800 ⠀` - -- `sentence_01.json` #234: 안랩은 블록체인 자회사 안랩블록체인컴퍼니가 웹(Web)3 지갑인 ‘ABC 월렛(Wallet)’ 모바일 버전을 구글플레이와 애플 앱스토어에 출시했다고 10일 발표했다. - - expected: `⠎⠉⠕⠫⠀⠏⠗⠃⠦⠄⠴⠠⠺⠑⠃⠠⠴⠼⠉⠀⠨⠕⠫⠃` - - actual: `⠎⠉⠕⠫⠀⠏⠗⠃⠀⠀⠦⠠⠺⠑⠃⠴⠼⠉⠀⠨⠕⠫⠃⠟` - - first differing cell (zero-based): 44 - - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_02.json` #168: 5주 과정인 ‘도전’ 프로그램과 5개월 과정인 ‘도전+(플러스)’ 프로그램으로 나뉘어 진행되며, 프로그램 이수 시 1개월에 50만 원씩 최대 300만 원까지 참여 수당과 인센티브를 지원한다. - - expected: `⠠⠦⠊⠥⠨⠾⠀⠢⠦⠄⠙⠮⠐⠎⠠⠪⠠⠴⠴⠄⠀⠙⠪⠐` - - actual: `⠠⠦⠊⠥⠨⠾⠀⠢⠀⠦⠄⠙⠮⠐⠎⠠⠪⠠⠴⠴⠄⠀⠙⠪` - - first differing cell (zero-based): 53 - - current primary/reason: `pending_rule_review` / `number_rule_review` -- `sentence_03.json` #64: SK에코플랜트는 20일 종로구 수송동 본사에서 현대엔지니어링, USNC와 ‘수소 마이크로 허브(H2 Micro Hub)’ 구축을 위한 3자간 업무협약(MOU)을 체결했다고 밝혔다. - - expected: `⠪⠐⠥⠀⠚⠎⠘⠪⠦⠄⠴⠠⠓⠼⠃⠀⠠⠍⠊⠉⠗⠕⠀⠠` - - actual: `⠪⠐⠥⠀⠚⠎⠘⠪⠀⠀⠦⠠⠓⠼⠃⠀⠴⠠⠍⠊⠉⠗⠕⠀` - - first differing cell (zero-based): 91 - - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_04.json` #441: 10~11일에는 지역 주민들과 함께 하는 전야제를 포함해 아주대 50년사 출판 기념보고회, 인공지능(AI)·6G 융합 콜로키움 시리즈가 열린다. - - expected: `⠟⠈⠿⠨⠕⠉⠪⠶⠦⠄⠴⠠⠠⠁⠊⠠⠴⠐⠆⠼⠋⠴⠠⠛` - - actual: `⠟⠈⠿⠨⠕⠉⠪⠶⠀⠀⠦⠠⠠⠁⠊⠴⠐⠼⠋⠠⠛⠀⠩⠶` - - first differing cell (zero-based): 93 - - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` - ### Residual `U+2801 ⠁ -> U+281C ⠜` - `sentence_01.json` #142: ‘메타버스존’에서는 증강현실(AR)·가상현실(VR)에 적용되는 혁신기술이 소개된다. 글라스를 착용하고 최첨단 3D 센싱모듈이 구현하는 가상현실을 관람객이 직접 체험할 수 있도록 했다. @@ -549,6 +503,52 @@ This ranking removes only cases whose first difference is inside an existing out - first differing cell (zero-based): 54 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +### Residual `U+2800 ⠀ -> U+2832 ⠲` + +- `sentence_01.json` #119: LCK 글로벌 중계진은 지난 해와 마찬가지로 ‘아틀러스(Atlus)’ 맥스 앤더슨(Max Anderson), ‘발데스(Valdes)’ 브랜든 발데스(Brendan Valdes), ‘울프(Wolf)’ 울프 슈뢰더(Wolf Schroeder)와 ‘크로니클러(Chronicler)’ 모리츠 뮈센(Maurits Meeusen)이 + - expected: `⠵⠦⠄⠴⠠⠍⠁⠭⠀⠠⠯⠻⠎⠕⠝⠠⠴⠐⠀⠠⠦⠘⠂⠊` + - actual: `⠵⠦⠄⠴⠠⠍⠁⠭⠲⠀⠀⠠⠁⠝⠙⠑⠗⠎⠕⠝⠴⠐⠀⠠` + - first differing cell (zero-based): 85 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_02.json` #1172: 최근 지속가능성 공시 지침(EU CSRD), 국제지속가능성기준위원회(ISSB) 등에서 스코프 3 배출량 공시를 의무화하고 있는데 이에 대해 국내 조선사들이 공동으로 대응에 나선 셈이다. + - expected: `⠢⠦⠄⠴⠠⠠⠑⠥⠀⠠⠠⠉⠎⠗⠙⠠⠴⠐⠀⠈⠍⠁⠨⠝` + - actual: `⠢⠦⠄⠴⠠⠠⠑⠥⠲⠀⠀⠠⠠⠉⠎⠗⠙⠴⠐⠀⠈⠍⠁⠨` + - first differing cell (zero-based): 33 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_03.json` #19: 해당 펀드는 전기차와 2차전지 및 2차전지 연관산업인 ESS /VPP(가상발전소)(14%) 등 전세계 친환경 기술 기업에 투자하는 상품이다. + - expected: `⠟⠀⠴⠠⠠⠑⠎⠎⠀⠸⠌⠠⠠⠧⠏⠏⠦⠄⠫⠇⠶⠘⠂⠨` + - actual: `⠟⠀⠴⠠⠠⠑⠎⠎⠲⠀⠸⠌⠴⠠⠠⠧⠏⠏⠦⠄⠫⠇⠶⠘` + - first differing cell (zero-based): 58 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_04.json` #447: 무디스 측은 미국의 반도체 법(CHIPs Act), 인플레이션 감축법(IRA) 등에 대응한 한국 정부와 관련 업계의 중장기 산업 전망과 전략에 대해 관심을 보였다. + - expected: `⠠⠠⠡⠊⠏⠠⠄⠎⠀⠠⠁⠉⠞⠠⠴⠐⠀⠟⠙⠮⠐⠝⠕⠠` + - actual: `⠠⠠⠡⠊⠏⠠⠄⠎⠲⠀⠀⠠⠁⠉⠞⠴⠐⠀⠀⠟⠙⠮⠐⠝` + - first differing cell (zero-based): 40 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` + +### Residual `U+2826 ⠦ -> U+2800 ⠀` + +- `sentence_01.json` #234: 안랩은 블록체인 자회사 안랩블록체인컴퍼니가 웹(Web)3 지갑인 ‘ABC 월렛(Wallet)’ 모바일 버전을 구글플레이와 애플 앱스토어에 출시했다고 10일 발표했다. + - expected: `⠎⠉⠕⠫⠀⠏⠗⠃⠦⠄⠴⠠⠺⠑⠃⠠⠴⠼⠉⠀⠨⠕⠫⠃` + - actual: `⠎⠉⠕⠫⠀⠏⠗⠃⠀⠀⠦⠠⠺⠑⠃⠴⠼⠉⠀⠨⠕⠫⠃⠟` + - first differing cell (zero-based): 44 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_02.json` #168: 5주 과정인 ‘도전’ 프로그램과 5개월 과정인 ‘도전+(플러스)’ 프로그램으로 나뉘어 진행되며, 프로그램 이수 시 1개월에 50만 원씩 최대 300만 원까지 참여 수당과 인센티브를 지원한다. + - expected: `⠠⠦⠊⠥⠨⠾⠀⠢⠦⠄⠙⠮⠐⠎⠠⠪⠠⠴⠴⠄⠀⠙⠪⠐` + - actual: `⠠⠦⠊⠥⠨⠾⠀⠢⠀⠦⠄⠙⠮⠐⠎⠠⠪⠠⠴⠴⠄⠀⠙⠪` + - first differing cell (zero-based): 53 + - current primary/reason: `pending_rule_review` / `number_rule_review` +- `sentence_03.json` #352: 인천시는 경제성 향상을 위해 유정복 인천시장의 민선8기 1호 공약인 제물포르네상스와 3기 신도시인 광명·시흥 공공주택지구 등 신규 개발계획을 반영하고, 수도권광역급행철도(GTX)-D Y자(인천공항행)와 연계 방안 등을 중점 검토한다. + - expected: `⠃⠚⠗⠶⠰⠞⠊⠥⠦⠄⠴⠠⠠⠛⠞⠭⠠⠴⠤⠴⠠⠙⠀⠰` + - actual: `⠃⠚⠗⠶⠰⠞⠊⠥⠀⠀⠦⠠⠠⠛⠞⠭⠴⠔⠠⠙⠀⠴⠠⠽` + - first differing cell (zero-based): 169 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_04.json` #441: 10~11일에는 지역 주민들과 함께 하는 전야제를 포함해 아주대 50년사 출판 기념보고회, 인공지능(AI)·6G 융합 콜로키움 시리즈가 열린다. + - expected: `⠟⠈⠿⠨⠕⠉⠪⠶⠦⠄⠴⠠⠠⠁⠊⠠⠴⠐⠆⠼⠋⠴⠠⠛` + - actual: `⠟⠈⠿⠨⠕⠉⠪⠶⠀⠀⠦⠠⠠⠁⠊⠴⠐⠼⠋⠠⠛⠀⠩⠶` + - first differing cell (zero-based): 93 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` + ## Cross-cutting input-only structural cohorts These are cross-cutting input-only structural cohorts, not new primary classes and not engine routing rules. Candidate selection never changes a case's existing primary class by itself. Only cohort members already classified as `pending_rule_review` form a pending subcluster; exact and other-primary members are controls that retain their existing outcomes. A separate classifier may use independently justified, output-localized PDF evidence, as in the rule-34 three-cell contradiction below. The `uppercase_roman_headword_closed_multiword_parenthetical` gate requires a two-or-more character uppercase ASCII headword immediately followed by a closed parenthesis whose contents are two or more ASCII Roman words separated only by spaces. Because the contents admit only letters and spaces, visible operators, subscript/superscript notation, and nested parentheses are excluded deterministically. The `mixed_roman_korean_word_before_uppercase_headword_expansion` gate further requires a whitespace-delimited preceding word that contains both ASCII Roman and Korean and ends in Korean. It locates the following uppercase headword itself, including its immediately preceding emitted cell, so an earlier Roman entry in the same sentence cannot satisfy the output audit. The `single_capital_followed_by_parenthesized_digits` gate requires a standalone capital, a non-empty closed ASCII-digit parenthetical, and alphanumeric outer boundaries. It likewise includes the emitted entry-boundary cell in localization. The `uppercase_roman_run_followed_by_hyphen_digits` gate requires a maximal uppercase ASCII run, literal ASCII hyphen-minus, a non-empty digit run, and alphanumeric outer boundaries. Its localized range includes the current encoded run and its immediately preceding cell, keeping `F-35`-style routing separate from both parenthesized digits and headword expansions. The `standalone_multi_character_uppercase_roman_word` gate finds maximal ASCII-letter runs of two or more capitals with non-alphanumeric boundaries; a run immediately followed by `(` is excluded so the HCA-style headword itself is not counted by both gates. The `korean_prefixed_closed_allcaps_parenthetical` gate requires an immediately preceding Korean character and a closed body of two or more uppercase ASCII letters. It intentionally contains both acronym annotations (`책임자(COO)`) and scientific formulae (`일산화탄소(CO)`) so their semantic collision remains measurable. The `korean_prefixed_closed_roman_annotation_rule_34_order` gate accepts the narrower rule-34 body grammar after an immediately preceding Korean character and localizes only the current engine's Korean opening-parenthesis cells. This distinguishes the PDF's parenthesis-before-Roman-indicator order from unrelated differences later in the same sentence. The `multi_character_allcaps_roman_runs_joined_by_middle_dot` gate requires two maximal ASCII-letter runs of at least two capitals joined directly by U+00B7, with non-alphanumeric outer boundaries. It records shapes such as `AI·SW` without assigning prose, mathematics, or science semantics. The `roman_run_immediately_before_attached_middle_dot_boundary` gate is output-localized: it requires a maximal ASCII-letter run immediately before U+00B7 and an attached Korean character or ASCII-letter run after it, then searches for that whole current-engine signature in the actual output. It therefore isolates the Roman terminator boundary without treating unrelated middle dots elsewhere in the sentence as causal. The `korean_inline_parenthesized_single_arithmetic_operator` gate requires an immediate `Korean(` + one rule-45 arithmetic operator + `)Korean` span. Unlike broad coexistence traits, it also locates the current engine's emitted structure and counts a mismatch as signature-local only when the sentence's first differing cell falls inside that output range. The `allcaps_roman_run_containing_ou` gate finds maximal, alphanumeric-delimited uppercase ASCII runs containing adjacent `OU`. It locates the independently encoded run signature in the complete current output and counts only first differences inside that signature as localized. The `decimal_point_between_ascii_digits` gate finds whitespace-delimited words containing `digit.digit` and reproduces each whole word in a neutral Korean context, so suffixes and punctuation remain part of the current-engine signature. The `compact_numeric_ascii_letter_suffix` gate finds a numeric prefix immediately followed by ASCII letters, includes the immediately preceding output cell as its entry boundary, and retains suffix-specific outcome counts. It intentionally includes both possible rule-69 units and ambiguous variable/identifier forms; membership alone does not assign unit semantics. The `rule69_ascii_unit_before_terminator_skipping_symbol` gate is narrower: it accepts only ASCII unit spellings already supported by rule 69, includes the immediately following rule-33/34 punctuation cell in the localized signature, and does not infer new units. The `tight_triangle_mark_immediately_before_korean` gate requires literal `△한글` with no input space and includes the first following Korean cell in its localized output range, so an observed missing-space difference is measured at the mark boundary. @@ -557,6 +557,7 @@ These are cross-cutting input-only structural cohorts, not new primary classes a |---|---:|---:|---:|---:| | `allcaps_roman_run_beginning_with_pure_letter_shortform` | 4120 | 2425 | 1695 | 0 | | `allcaps_roman_run_containing_ou` | 1816 | 64 | 1752 | 0 | +| `closed_roman_parenthetical_after_non_ascii_letter_boundary` | 63959 | 51554 | 12405 | 0 | | `compact_numeric_ascii_letter_suffix` | 2975 | 1855 | 1120 | 0 | | `decimal_point_between_ascii_digits` | 4546 | 3267 | 1279 | 0 | | `korean_inline_parenthesized_single_arithmetic_operator` | 23 | 17 | 6 | 0 | @@ -723,6 +724,92 @@ Representative `mismatch` samples: - first differing cell (zero-based): 0 - current primary/reason: `unsupported_character_review` / `unsupported_character_review` +### `closed_roman_parenthetical_after_non_ascii_letter_boundary` + +Of the 63959 candidates, 10926 are the actual `pending_rule_review` subcluster. The other 53033 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. + +For this output-signature audit, 12405 mismatches were evaluable and 581 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. + +Localized first-difference transitions: + +- `U+2826 ⠦ -> U+2834 ⠴`: 378 +- `U+2826 ⠦ -> U+2800 ⠀`: 175 +- `U+2834 ⠴ -> U+2826 ⠦`: 13 +- `U+2810 ⠐ -> U+2834 ⠴`: 10 +- `U+2800 ⠀ -> U+2834 ⠴`: 4 + +Mismatch primary-class distribution: + +- `comparison_method`: 223 +- `corpus_suspect`: 1099 +- `pending_rule_review`: 10926 +- `unsupported_character_review`: 157 + +Representative `exact` samples: + +- `sentence_01.json` #1: LG전자는 ‘업(UP)가전’을 무기로 내세우고 있다. 이번 CES에서도 LG 씽큐 앱에서 터치만으로 제품 색상을 바꿀 수 있는 무드업 냉장고를 비롯한 다양한 업가전을 선보일 예정이다. + - expected: `⠴⠠⠠⠇⠛⠲⠨⠾⠨⠉⠵⠀⠠⠦⠎⠃⠦⠄⠴⠠⠠⠥⠏⠠` + - actual: `⠴⠠⠠⠇⠛⠲⠨⠾⠨⠉⠵⠀⠠⠦⠎⠃⠦⠄⠴⠠⠠⠥⠏⠠` + - current primary/reason: `exact` / `exact` +- `sentence_02.json` #1: 세종시 주최, 세종시탄소중립지원센터 주관으로 열린 이번 포럼의 주요의제는 기업의 이에스지(ESG) 경영과 사회적 책임, 국내 온실가스 배출권거래제 현황과 대응 방안을 마련하기 위해 개최됐다. + - expected: `⠠⠝⠨⠿⠠⠕⠀⠨⠍⠰⠽⠐⠀⠠⠝⠨⠿⠠⠕⠓⠒⠠⠥⠨` + - actual: `⠠⠝⠨⠿⠠⠕⠀⠨⠍⠰⠽⠐⠀⠠⠝⠨⠿⠠⠕⠓⠒⠠⠥⠨` + - current primary/reason: `exact` / `exact` +- `sentence_03.json` #4: 18일(현지시간) 월스트리트저널(WSJ) 등 미 외신은 투·개표기 업체 도미니언 투표시스템이 2021년 1월 제기한 16억 달러(약 2조원) 명예훼손 소송과 관련, 폭스뉴스와 도미니언 측이 이 같이 합의했다고 보도했다. + - expected: `⠼⠁⠓⠕⠂⠦⠄⠚⠡⠨⠕⠠⠕⠫⠒⠠⠴⠀⠏⠂⠠⠪⠓⠪` + - actual: `⠼⠁⠓⠕⠂⠦⠄⠚⠡⠨⠕⠠⠕⠫⠒⠠⠴⠀⠏⠂⠠⠪⠓⠪` + - current primary/reason: `exact` / `exact` +- `sentence_04.json` #1: 일본 정부는 이날 국가안전보장회의(NSC)를 열어 대응 방침을 논의했다. 이어 북한의 미사일 발사에 대해 “엄중히 항의하고 강하게 비난한다”고 밝혔다. + - expected: `⠕⠂⠘⠷⠀⠨⠻⠘⠍⠉⠵⠀⠕⠉⠂⠀⠈⠍⠁⠫⠣⠒⠨⠾` + - actual: `⠕⠂⠘⠷⠀⠨⠻⠘⠍⠉⠵⠀⠕⠉⠂⠀⠈⠍⠁⠫⠣⠒⠨⠾` + - current primary/reason: `exact` / `exact` + +Representative `localized_mismatch` samples: + +- `sentence_01.json` #87: 4일 진원생명과학은 자체 개발 흡인작용 피내 접종기 진덤(GeneDerm)을 이용한 코로나19 DNA 백신(GLS-5310)의 안전성·면역원성에 대한 임상1상 결과를 국제감염병학회 (ISID) 학술지인 국제감염질환저널에 발표했다고 4일 밝혔다. + - expected: `⠢⠘⠻⠚⠁⠚⠽⠀⠦⠄⠴⠠⠠⠊⠎⠊⠙⠠⠴⠀⠚⠁⠠⠯` + - actual: `⠢⠘⠻⠚⠁⠚⠽⠀⠴⠐⠣⠠⠠⠊⠎⠊⠙⠐⠜⠲⠀⠚⠁⠠` + - first differing cell (zero-based): 172 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_02.json` #38: 중국금형전시회(DMC) 및 독일금형전시회 (Moulding Expo)와 함께 세계 3대 규모의 금형산업 전문 전시회이며, 연관산업 전문 전시회를 KOPLAS와 동시 개최를 통한 시너지를 창출했다. + - expected: `⠻⠨⠾⠠⠕⠚⠽⠀⠦⠄⠴⠠⠍⠳⠇⠙⠬⠀⠠⠑⠭⠏⠕⠠` + - actual: `⠻⠨⠾⠠⠕⠚⠽⠀⠴⠐⠣⠠⠍⠳⠇⠙⠬⠀⠠⠑⠭⠏⠕⠐` + - first differing cell (zero-based): 48 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_03.json` #64: SK에코플랜트는 20일 종로구 수송동 본사에서 현대엔지니어링, USNC와 ‘수소 마이크로 허브(H2 Micro Hub)’ 구축을 위한 3자간 업무협약(MOU)을 체결했다고 밝혔다. + - expected: `⠪⠐⠥⠀⠚⠎⠘⠪⠦⠄⠴⠠⠓⠼⠃⠀⠠⠍⠊⠉⠗⠕⠀⠠` + - actual: `⠪⠐⠥⠀⠚⠎⠘⠪⠀⠀⠦⠠⠓⠼⠃⠀⠴⠠⠍⠊⠉⠗⠕⠀` + - first differing cell (zero-based): 91 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_04.json` #578: 항공무선표지소는 무선전파를 이용해 항공기에 위치정보를 제공하는 전방향표지시설(VOR, TACAN)과 거리를 알려주는 거리측정시설(DME), 관제사와 조종사간의 통신을 위한 무선통신시설(AG) 등을 갖추고 있다. + - expected: `⠙⠬⠨⠕⠠⠕⠠⠞⠦⠄⠴⠠⠠⠧⠕⠗⠂⠀⠠⠠⠞⠁⠉⠁` + - actual: `⠙⠬⠨⠕⠠⠕⠠⠞⠀⠀⠦⠠⠠⠧⠕⠗⠐⠀⠴⠠⠠⠞⠁⠉` + - first differing cell (zero-based): 75 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` + +Representative `mismatch` samples: + +- `sentence_01.json` #14: 새롭게 선보인 DRX 브랜딩과 팀 컬러는 2023년 1월 1일부터 DRX의 온오프라인 콘텐츠, 엠디(MD), SNS, 유튜브 등 모든 제작물에 적용될 예정이다. + - expected: `⠰⠪⠐⠀⠝⠢⠊⠕⠴⠐⠣⠠⠠⠍⠙⠐⠜⠂⠀⠠⠠⠎⠝⠎` + - actual: `⠰⠪⠐⠀⠝⠢⠊⠕⠦⠄⠴⠠⠠⠍⠙⠠⠴⠐⠀⠰⠠⠠⠎⠝` + - first differing cell (zero-based): 97 + - current primary/reason: `corpus_suspect` / `rule34_roman_indicator_before_opening_parenthesis` +- `sentence_02.json` #10: 설동호 대전교육감은 “학교 교육과정 및 여건에 맞는 인공지능(AI)교육 선도학교 운영을 통해 AI·SW교육 기반 구축과 학생들과 교원들의 미래 디지털 역량이 함양될 것이다.”라고 말했다. + - expected: `⠚⠗⠀⠴⠠⠠⠁⠊⠐⠆⠴⠠⠠⠎⠺⠲⠈⠬⠩⠁⠀⠈⠕⠘` + - actual: `⠚⠗⠀⠴⠠⠠⠁⠊⠲⠐⠆⠴⠎⠺⠲⠈⠬⠩⠁⠀⠈⠕⠘⠒` + - first differing cell (zero-based): 93 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_03.json` #1: 김 지사는 18일(이하 현지시각) 미국 코네티컷주 댄버리 린데 본사에서 산지브 람바(Sanjiv Lamba) 린데 회장, 성백석 린데코리아 회장, 조일교 아산시 부시장과 투자양해각서(MOU)을 체결했다. + - expected: `⠠⠎⠦⠄⠴⠠⠠⠍⠕⠥⠠⠴⠮⠀⠰⠝⠈⠳⠚⠗⠌⠊⠲` + - actual: `⠠⠎⠦⠄⠴⠠⠠⠍⠳⠠⠴⠮⠀⠰⠝⠈⠳⠚⠗⠌⠊⠲` + - first differing cell (zero-based): 175 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_04.json` #19: 이날 행사에는 구 대표 외에 권봉석 (주)LG 최고운영책임자(COO), 박일평 LG사이언스파크대표(사장)를 비롯해 각 계열사 최고기술책임자(CTO), 최고데이터책임자(CDO), 최고인사책임자(CHO) 등이 참석해 국내 이공계 R&D 인재 400여명과 만났다. + - expected: `⠁⠕⠢⠨⠦⠄⠴⠠⠠⠉⠕⠕⠠⠴⠐⠀⠘⠁⠕⠂⠙⠻⠀⠴` + - actual: `⠁⠕⠢⠨⠦⠄⠴⠠⠉⠠⠕⠠⠕⠠⠴⠐⠀⠘⠁⠕⠂⠙⠻⠀` + - first differing cell (zero-based): 60 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` + ### `compact_numeric_ascii_letter_suffix` Of the 2975 candidates, 1052 are the actual `pending_rule_review` subcluster. The other 1923 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. @@ -2103,6 +2190,12 @@ Analyzer checkpoint `266e70c` fixed the pre-change baseline at 1,729 candidates, The generalized fix makes an explicit `EnterEnglish` event idempotent when final emit state is already inside a Roman section; it neither names an input nor changes a new section's entry. The current measurement is 1729 candidates, 661 exact controls, 1068 mismatches, 994 pending members, and 4/1068 localized mismatches. Current target counts are 0 `⠠ -> ⠴`, 0 `⠰ -> ⠴`, and 0 reverse `⠴ -> ⠠`. Cohort exact controls increase by 275, while corpus-wide exact matches rise from 67,138 to 67,442 (+304); corrected prefixes that still differ later remain mismatches, and applications outside this strict input gate account for the remaining net gain. The raw/residual reverse `⠴ -> ⠠` totals remain 68/65 before and after the change. The complete custom standard summary is 5,141 total, 5,141 success, 0 failure, and 0 skipped. +### Closed Roman parenthetical after a non-ASCII-letter boundary + +Korean rule 34 (2024 Korean-rules PDF p.29) prints the opening enclosure before Roman entry in `링컨(Lincoln)`: the opening sequence is `⠦⠄⠴`. This output-position cohort finds a closed, non-nested parenthetical whose body begins with an ASCII letter and whose opening does not immediately follow another ASCII letter, then locates its complete current-engine signature without consulting the reference. Direct function-call shapes such as `f(x)` are excluded. The PDF's math rule 6 (p.59), rule 12 (pp.63-64), and rule 45 (p.75) nevertheless leave standalone `(x)` and other Roman-letter parenthetical mathematics as counterexamples, so the surface gate is not an engine-routing predicate. + +The cross-cutting input cohort contains 63959 candidates: 51554 exact controls and 12405 mismatches. Mismatch primary classes remain unchanged: 10926 `pending_rule_review`, 1099 `corpus_suspect`, 223 `comparison_method`, and 157 `unsupported_character_review`. Of 12405 evaluable mismatches, 581 have the first difference at the detected opening; these include 378 `U+2826 ⠦ -> U+2834 ⠴`, 13 `U+2834 ⠴ -> U+2826 ⠦`, and 175 `U+2826 ⠦ -> U+2800 ⠀` transitions. After all earlier localized cohorts and this cohort are excluded, the raw-to-residual target count is 461 -> 76 and the reverse count is 28 -> 12. The short full-encoder form `웹3(Web3)` emits the PDF opening order as a routing control, while whitespace and quote boundaries reproduce the current Roman-first opening. Representative localized samples, with shard and index, are retained in the generated cluster sample table. Because exact controls are abundant and the PDF does not make this input shape semantically sufficient to exclude mathematics, no engine change or primary reclassification is inferred. + The HCA-style headword-expansion shape described above is not an engine implementation premise. The 2024 PDF's math rule 6 defines parentheses and grouping parentheses, rule 11 defines mathematical-expression spacing, rule 12 covers Roman letters in formulas as well as Korean sentences, and rule 45 shows Roman-letter function notation followed by parentheses. Excluding visible operators, scripts, and nesting narrows this corpus cohort, but the PDF does not make the remaining surface shape sufficient to rule out every mathematical counterexample. The cluster therefore remains conservative pending-review evidence only. diff --git a/libs/braillify/examples/nikl_corpus_analyze.rs b/libs/braillify/examples/nikl_corpus_analyze.rs index 3e036245..7dd09880 100644 --- a/libs/braillify/examples/nikl_corpus_analyze.rs +++ b/libs/braillify/examples/nikl_corpus_analyze.rs @@ -605,6 +605,8 @@ const KOREAN_PREFIXED_ROMAN_PARENTHETICAL_HYPHEN_SUFFIX: &str = "korean_prefixed_roman_parenthetical_followed_by_allcaps_hyphen_suffix"; const CONSECUTIVE_ROMAN_UPPERCASE_WORD_REENTRY: &str = "uppercase_word_after_whitespace_continuing_ascii_roman_text"; +const ROMAN_PARENTHETICAL_AFTER_NONLETTER_BOUNDARY: &str = + "closed_roman_parenthetical_after_non_ascii_letter_boundary"; #[derive(Clone, Copy, Debug, PartialEq, Eq)] struct InputSpan { @@ -1255,6 +1257,46 @@ fn consecutive_roman_uppercase_word_spans(input: &str) -> Vec { spans } +/// Finds a closed, non-nested parenthetical whose body starts with a Roman +/// letter and whose opening does not immediately follow another ASCII letter. +/// This includes rule-34 enclosure contexts after Korean, digits, whitespace, +/// or quotes while excluding direct function-call shapes such as `f(x)`. +/// Standalone `(x)` remains an intentional math-rule-6 control, so this is an +/// analyzer cohort rather than an engine routing predicate. +fn roman_parenthetical_after_nonletter_boundary_spans(input: &str) -> Vec { + let mut spans = Vec::new(); + for (open, _) in input.match_indices('(') { + if input[..open] + .chars() + .next_back() + .is_some_and(|previous| previous.is_ascii_alphabetic()) + { + continue; + } + let body_start = open + 1; + if !input[body_start..] + .chars() + .next() + .is_some_and(|first| first.is_ascii_alphabetic()) + { + continue; + } + let Some((close_offset, close)) = input[body_start..] + .char_indices() + .find(|(_, ch)| matches!(ch, '(' | ')')) + else { + continue; + }; + if close == ')' { + spans.push(InputSpan { + start_byte: open, + end_byte: body_start + close_offset + 1, + }); + } + } + spans +} + /// Locates each detected run in the full current-engine output by searching /// for that run's independently encoded signature. This uses neither the /// corpus reference nor a hard-coded braille value. @@ -1444,6 +1486,44 @@ fn first_difference_at_input_span_entry( .any(|range| range.contains(&first_difference)) } +/// Locates the opening of a complete parenthetical through its independently +/// encoded current-engine signature. This complements prefix offsets: a +/// trailing digit can change how an isolated prefix exits Roman/number mode, +/// and whitespace belongs before rather than inside the parenthetical entry. +fn current_engine_parenthetical_entry_ranges( + input: &str, + actual: &str, + spans: &[InputSpan], + entry_cells: usize, +) -> Vec> { + roman_entry_signature_ranges(input, actual, spans, 0) + .into_iter() + .filter_map(|signature| { + let end = signature + .start + .saturating_add(entry_cells) + .min(signature.end); + (signature.start < end).then_some((signature.start, end)) + }) + .collect::>() + .into_iter() + .map(|(start, end)| start..end) + .collect() +} + +fn first_difference_at_parenthetical_entry(item: &EncodedCase, spans: &[InputSpan]) -> bool { + let Ok(actual) = &item.actual else { + return false; + }; + if actual == &item.located.case.unicode { + return false; + } + let first_difference = first_difference_cell(&item.located.case.unicode, actual); + current_engine_parenthetical_entry_ranges(&item.located.case.input, actual, spans, 3) + .into_iter() + .any(|range| range.contains(&first_difference)) +} + fn allcaps_ou_actual_ranges(input: &str, actual: &str) -> Vec> { current_engine_signature_ranges(input, actual, &allcaps_roman_runs_containing_ou(input), 0) } @@ -1677,7 +1757,7 @@ fn first_difference_claimed_before_consecutive_roman_reentry(item: &EncodedCase) ) } -fn first_difference_claimed_by_localized_cohort(item: &EncodedCase) -> bool { +fn first_difference_claimed_before_nonletter_parenthetical(item: &EncodedCase) -> bool { first_difference_claimed_before_consecutive_roman_reentry(item) || first_difference_in_current_roman_entry_signature( item, @@ -1685,6 +1765,14 @@ fn first_difference_claimed_by_localized_cohort(item: &EncodedCase) -> bool { ) } +fn first_difference_claimed_by_localized_cohort(item: &EncodedCase) -> bool { + first_difference_claimed_before_nonletter_parenthetical(item) + || first_difference_at_parenthetical_entry( + item, + &roman_parenthetical_after_nonletter_boundary_spans(&item.located.case.input), + ) +} + /// Input-only candidate gate for acronym expansions such as /// `HCA(Home Connectivity Alliance)`. /// @@ -2360,6 +2448,10 @@ fn analyze( CONSECUTIVE_ROMAN_UPPERCASE_WORD_REENTRY.to_string(), PendingRuleReviewClusterStats::default(), ), + ( + ROMAN_PARENTHETICAL_AFTER_NONLETTER_BOUNDARY.to_string(), + PendingRuleReviewClusterStats::default(), + ), ]); let mut pending_first_difference_cell_transitions = BTreeMap::new(); let mut pending_first_difference_transitions_after_localized_cohorts = BTreeMap::new(); @@ -2637,6 +2729,21 @@ fn analyze( ), true, ), + ( + ROMAN_PARENTHETICAL_AFTER_NONLETTER_BOUNDARY, + !roman_parenthetical_after_nonletter_boundary_spans(&item.located.case.input) + .is_empty(), + Some( + !first_difference_claimed_before_nonletter_parenthetical(item) + && first_difference_at_parenthetical_entry( + item, + &roman_parenthetical_after_nonletter_boundary_spans( + &item.located.case.input, + ), + ), + ), + true, + ), ] { if !present { continue; @@ -2940,6 +3047,7 @@ fn markdown(report: &AnalysisReport) -> String { .chars() .take(180) .collect::() + .trim_end() .replace('`', "\\`"), sample.expected_excerpt, sample.actual_excerpt, @@ -2987,6 +3095,7 @@ fn markdown(report: &AnalysisReport) -> String { .chars() .take(180) .collect::() + .trim_end() .replace('`', "\\`"), sample.expected_excerpt, sample.actual_excerpt, @@ -3132,6 +3241,7 @@ fn markdown(report: &AnalysisReport) -> String { .chars() .take(180) .collect::() + .trim_end() .replace('`', "\\`"), sample.expected_excerpt, sample.actual_excerpt, @@ -3463,6 +3573,83 @@ fn markdown(report: &AnalysisReport) -> String { stats.output_signature_mismatches_evaluated )); } + if let Some(stats) = report + .pending_rule_review_clusters + .get(ROMAN_PARENTHETICAL_AFTER_NONLETTER_BOUNDARY) + { + let primary_count = |name: &str| { + stats + .mismatch_primary_classes + .get(name) + .copied() + .unwrap_or(0) + }; + let localized_transition = |name: &str| { + stats + .first_difference_in_output_signature_transitions + .get(name) + .copied() + .unwrap_or(0) + }; + let residual_transition = |name: &str| { + report + .pending_first_difference_transitions_after_localized_cohorts + .get(name) + .map(|transition| transition.cases) + .unwrap_or(0) + }; + let raw_transition = |name: &str| { + report + .pending_first_difference_cell_transitions + .get(name) + .map(|transition| transition.cases) + .unwrap_or(0) + }; + let target = "U+2826 ⠦ -> U+2834 ⠴"; + let reverse = "U+2834 ⠴ -> U+2826 ⠦"; + let open_to_space = "U+2826 ⠦ -> U+2800 ⠀"; + text.push_str(&format!( + "### Closed Roman parenthetical after a non-ASCII-letter boundary\n\n\ + Korean rule 34 (2024 Korean-rules PDF p.29) prints the opening enclosure before \ + Roman entry in `링컨(Lincoln)`: the opening sequence is `⠦⠄⠴`. This output-position \ + cohort finds a closed, non-nested parenthetical whose body begins with an ASCII \ + letter and whose opening does not immediately follow another ASCII letter, then \ + locates its complete current-engine signature without consulting the reference. \ + Direct function-call shapes such as `f(x)` are excluded. The PDF's math rule 6 \ + (p.59), rule 12 (pp.63-64), and rule 45 (p.75) nevertheless leave standalone `(x)` \ + and other Roman-letter parenthetical mathematics as counterexamples, so the \ + surface gate is not an engine-routing predicate.\n\n\ + The cross-cutting input cohort contains {} candidates: {} exact controls and {} \ + mismatches. Mismatch primary classes remain unchanged: {} `pending_rule_review`, \ + {} `corpus_suspect`, {} `comparison_method`, and {} \ + `unsupported_character_review`. Of {} evaluable mismatches, {} have the first \ + difference at the detected opening; these include {} `{target}`, {} `{reverse}`, \ + and {} `{open_to_space}` transitions. After all earlier localized cohorts and this \ + cohort are excluded, the raw-to-residual target count is {} -> {} and the reverse \ + count is {} -> {}. The short full-encoder form `웹3(Web3)` emits the PDF opening \ + order as a routing control, while whitespace and quote boundaries reproduce the current Roman-first \ + opening. Representative localized samples, with shard and index, are retained in \ + the generated cluster sample table. Because exact controls are abundant and the PDF \ + does not make this input shape semantically sufficient to exclude mathematics, no \ + engine change or primary reclassification is inferred.\n\n", + stats.candidates, + stats.exact, + stats.mismatch, + primary_count("pending_rule_review"), + primary_count("corpus_suspect"), + primary_count("comparison_method"), + primary_count("unsupported_character_review"), + stats.output_signature_mismatches_evaluated, + stats.first_difference_in_output_signature, + localized_transition(target), + localized_transition(reverse), + localized_transition(open_to_space), + raw_transition(target), + residual_transition(target), + raw_transition(reverse), + residual_transition(reverse), + )); + } text.push_str( "\nThe HCA-style headword-expansion shape described above is not an engine \ implementation premise. The 2024 PDF's math rule 6 \ @@ -4770,6 +4957,26 @@ mod tests { assert_eq!(actual, expected); } + #[rstest::rstest] + #[case::pdf_rule_34("링컨(Lincoln)은", vec!["(Lincoln)"])] + #[case::after_digit("웹3(Web3)", vec!["(Web3)"])] + #[case::after_whitespace("전시회 (Moulding Expo)", vec!["(Moulding Expo)"])] + #[case::after_quote("선언’(Washington Declaration)", vec!["(Washington Declaration)"])] + #[case::function_call("f(x)", vec![])] + #[case::standalone_math_control("(x)", vec!["(x)"])] + #[case::nested_math_control("(f(x))", vec![])] + #[case::korean_first_body("(한글 AI)", vec![])] + fn detects_closed_roman_parenthetical_after_non_ascii_letter_boundary( + #[case] input: &str, + #[case] expected: Vec<&str>, + ) { + let actual = roman_parenthetical_after_nonletter_boundary_spans(input) + .into_iter() + .map(|span| &input[span.start_byte..span.end_byte]) + .collect::>(); + assert_eq!(actual, expected); + } + #[test] fn full_encoder_does_not_reenter_before_consecutive_uppercase_word() { let input = "가(NEW YORK)"; @@ -4824,6 +5031,23 @@ mod tests { assert!(ranges[0].end <= actual.chars().count()); } + #[rstest::rstest] + #[case::short_digit_exact_control("웹3(Web3)", '⠦')] + #[case::after_whitespace("전시회 (Moulding Expo)", '⠴')] + #[case::after_quote("선언’(Washington Declaration)", '⠴')] + fn locates_nonletter_parenthetical_opening_in_current_output( + #[case] input: &str, + #[case] expected_opening: char, + ) { + let spans = roman_parenthetical_after_nonletter_boundary_spans(input); + let actual = braillify::encode_to_unicode(input).expect("parenthetical probe must encode"); + let ranges = current_engine_parenthetical_entry_ranges(input, &actual, &spans, 3); + + assert_eq!(spans.len(), 1); + assert_eq!(ranges.len(), 1); + assert_eq!(actual.chars().nth(ranges[0].start), Some(expected_opening)); + } + #[test] fn locates_allcaps_ou_signature_in_complete_output() { let input = "업무협약(MOU)을 체결했다."; From 7ef977923b42a6362cb55410e390c1872c8e5e7c Mon Sep 17 00:00:00 2001 From: devfive Date: Tue, 1 Sep 2026 12:31:25 +0900 Subject: [PATCH 33/77] Diagnose uppercase ST contraction residual --- docs/corpus-analysis/NIKL_2025_V1.md | 179 +++++++++++++----- .../braillify/examples/nikl_corpus_analyze.rs | 148 ++++++++++++++- 2 files changed, 279 insertions(+), 48 deletions(-) diff --git a/docs/corpus-analysis/NIKL_2025_V1.md b/docs/corpus-analysis/NIKL_2025_V1.md index b098bea8..81fc352f 100644 --- a/docs/corpus-analysis/NIKL_2025_V1.md +++ b/docs/corpus-analysis/NIKL_2025_V1.md @@ -298,49 +298,26 @@ This ranking removes only cases whose first difference is inside an existing out | Rank | Expected → actual first cell | Residual cases | |---:|---|---:| -| 1 | `U+280E ⠎ -> U+280C ⠌` | 448 | -| 2 | `U+2808 ⠈ -> U+2832 ⠲` | 409 | -| 3 | `U+2801 ⠁ -> U+281C ⠜` | 390 | -| 4 | `U+2834 ⠴ -> U+2830 ⠰` | 332 | -| 5 | `U+2811 ⠑ -> U+282B ⠫` | 325 | -| 6 | `U+2810 ⠐ -> U+2832 ⠲` | 290 | -| 7 | `U+2832 ⠲ -> U+2838 ⠸` | 281 | -| 8 | `U+2834 ⠴ -> U+2800 ⠀` | 258 | -| 9 | `U+2800 ⠀ -> U+2832 ⠲` | 227 | -| 10 | `U+2826 ⠦ -> U+2800 ⠀` | 219 | -| 11 | `U+2810 ⠐ -> U+2802 ⠂` | 210 | -| 12 | `U+2817 ⠗ -> U+2820 ⠠` | 163 | -| 13 | `U+2820 ⠠ -> U+2809 ⠉` | 160 | -| 14 | `U+283C ⠼ -> U+2800 ⠀` | 153 | -| 15 | `U+281B ⠛ -> U+2823 ⠣` | 144 | -| 16 | `U+2826 ⠦ -> U+2810 ⠐` | 144 | -| 17 | `U+2810 ⠐ -> U+2800 ⠀` | 130 | -| 18 | `U+2811 ⠑ -> U+283B ⠻` | 101 | -| 19 | `U+2810 ⠐ -> U+2814 ⠔` | 98 | -| 20 | `U+280E ⠎ -> U+2820 ⠠` | 91 | - -### Residual `U+280E ⠎ -> U+280C ⠌` - -- `sentence_01.json` #167: 올해 전 세계 반도체 시장 전망도 암울하다. 세계반도체시장통계기구(WSTS)에 따르면 올해 글로벌 반도체 매출은 5천565억 달러(약 706조5천880억원)로 지난해(5천801억달러) 대비 4.1% 감소할 것으로 조사됐다. - - expected: `⠈⠍⠦⠄⠴⠠⠠⠺⠎⠞⠎⠠⠴⠝⠀⠠⠊⠐⠪⠑⠡⠀⠥⠂` - - actual: `⠈⠍⠦⠄⠴⠠⠠⠺⠌⠎⠠⠴⠝⠀⠠⠊⠐⠪⠑⠡⠀⠥⠂⠚` - - first differing cell (zero-based): 67 - - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_02.json` #244: 충남대학교 이진숙 총장을 비롯한 관계자들은 5월 19일, 베트남 최고의 국립대인 하노이과학기술대학(HUST), 베트남국립농업대학(VNUA)을 연이어 방문해 ‘오픈 캠퍼스(open campus)’ 설립을 골자로 한 협정을 체결했다. - - expected: `⠁⠦⠄⠴⠠⠠⠓⠥⠎⠞⠠⠴⠐⠀⠘⠝⠓⠪⠉⠢⠈⠍⠁⠐` - - actual: `⠁⠦⠄⠴⠠⠠⠓⠥⠌⠠⠴⠐⠀⠘⠝⠓⠪⠉⠢⠈⠍⠁⠐⠕` - - first differing cell (zero-based): 101 - - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_03.json` #142: 수많은 작품 OST를 책임지며 실력파 프로듀서로 인정받고 있는 필승불패W, 리디아(Lydia), 장석원이 힘을 합쳐 만든 곡으로 웰메이드 OST 탄생을 예감케 한다. - - expected: `⠙⠍⠢⠀⠴⠠⠠⠕⠎⠞⠲⠐⠮⠀⠰⠗⠁⠕⠢⠨⠕⠑⠱⠀` - - actual: `⠙⠍⠢⠀⠴⠠⠠⠕⠌⠲⠐⠮⠀⠰⠗⠁⠕⠢⠨⠕⠑⠱⠀⠠` - - first differing cell (zero-based): 17 - - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_04.json` #375: 윤석열 대통령은 30일 캐서린 타이 미국 무역대표부(USTR) 대표를 만나 미 인플레이션감축법(IRA)·반도체지원법(CHIPS Act)과 관련해 “미국에 진출하는 한국 기업들이 어려움을 겪지 않도록 우호적인 방향으로 배려해 달라”고 당부했다. - - expected: `⠘⠍⠦⠄⠴⠠⠠⠥⠎⠞⠗⠠⠴⠀⠊⠗⠙⠬⠐⠮⠀⠑⠒⠉` - - actual: `⠘⠍⠦⠄⠴⠠⠠⠥⠌⠗⠠⠴⠀⠊⠗⠙⠬⠐⠮⠀⠑⠒⠉⠀` - - first differing cell (zero-based): 53 - - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +| 1 | `U+2808 ⠈ -> U+2832 ⠲` | 409 | +| 2 | `U+2801 ⠁ -> U+281C ⠜` | 390 | +| 3 | `U+2834 ⠴ -> U+2830 ⠰` | 332 | +| 4 | `U+2811 ⠑ -> U+282B ⠫` | 325 | +| 5 | `U+2810 ⠐ -> U+2832 ⠲` | 290 | +| 6 | `U+2832 ⠲ -> U+2838 ⠸` | 281 | +| 7 | `U+2834 ⠴ -> U+2800 ⠀` | 258 | +| 8 | `U+2800 ⠀ -> U+2832 ⠲` | 227 | +| 9 | `U+2826 ⠦ -> U+2800 ⠀` | 219 | +| 10 | `U+2810 ⠐ -> U+2802 ⠂` | 210 | +| 11 | `U+2817 ⠗ -> U+2820 ⠠` | 163 | +| 12 | `U+2820 ⠠ -> U+2809 ⠉` | 160 | +| 13 | `U+283C ⠼ -> U+2800 ⠀` | 153 | +| 14 | `U+281B ⠛ -> U+2823 ⠣` | 144 | +| 15 | `U+2826 ⠦ -> U+2810 ⠐` | 144 | +| 16 | `U+2810 ⠐ -> U+2800 ⠀` | 130 | +| 17 | `U+2811 ⠑ -> U+283B ⠻` | 101 | +| 18 | `U+2810 ⠐ -> U+2814 ⠔` | 98 | +| 19 | `U+280E ⠎ -> U+2820 ⠠` | 91 | +| 20 | `U+2804 ⠄ -> U+2834 ⠴` | 86 | ### Residual `U+2808 ⠈ -> U+2832 ⠲` @@ -549,14 +526,38 @@ This ranking removes only cases whose first difference is inside an existing out - first differing cell (zero-based): 93 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +### Residual `U+2810 ⠐ -> U+2802 ⠂` + +- `sentence_01.json` #1725: 호주는 7회초 경기를 뒤집었다. 한국 4번째 투수 소형준(KT)이 1사 2, 3루로 몰린 가운데 마운드를 김원중(롯데 자이언츠)으로 교체됐다. 김원중은 첫 타자인 홀을 스트라이크 낫아웃으로 돌려세웠다. + - expected: `⠕⠀⠼⠁⠇⠀⠼⠃⠐⠀⠼⠉⠐⠍⠐⠥⠀⠑⠥⠂⠐⠟⠀⠫` + - actual: `⠕⠀⠼⠁⠇⠀⠼⠃⠂⠀⠼⠉⠐⠍⠐⠥⠀⠑⠥⠂⠐⠟⠀⠫` + - first differing cell (zero-based): 75 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_02.json` #239: 세계 금연의 날(World No Tabacco Day, 2023년 5월 31일)은 세계보건기구(WHO)가 담배가 전 세계적으로 심각한 문제임을 인식시키고 ‘담배 연기 없는 사회’를 만들기 위하여 1987년 제정한 기념일이다. + - expected: `⠃⠁⠒⠕⠀⠠⠐⠙⠐⠀⠼⠃⠚⠃⠉⠀⠉⠡⠀⠼⠑⠏⠂⠀` + - actual: `⠃⠁⠒⠕⠀⠠⠐⠙⠂⠀⠼⠃⠚⠃⠉⠀⠉⠡⠀⠼⠑⠏⠂⠀` + - first differing cell (zero-based): 35 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_03.json` #2147: 용량은 512GB, 1테라바이트(TB), 두 가지 종류로 출시됐다. USB 연결 케이블 2종(C-to-C, C-to-A)과 함께 전용 범퍼케이스가 제공된다. + - expected: `⠑⠁⠃⠴⠠⠠⠛⠃⠐⠀⠼⠁⠀⠓⠝⠐⠣⠘⠣⠕⠓⠪⠦⠄` + - actual: `⠑⠁⠃⠴⠠⠠⠛⠃⠂⠀⠼⠁⠀⠓⠝⠐⠣⠘⠣⠕⠓⠪⠦⠄` + - first differing cell (zero-based): 16 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_04.json` #299: 국책연구기관인 한국환경연구원(KEI)이 한국수력원자력(한수원)이 낸 ‘신한울 원전 3, 4호기 환경영향평가 재협의 초안’에 부정적인 의견을 밝혔다. KEI는 한수원 조사에서 해산어류(바닷물고기류) 영향이 ‘매우 형식적’으로 이뤄졌다고 지적했다. + - expected: `⠀⠏⠒⠨⠾⠀⠼⠉⠐⠀⠼⠙⠀⠚⠥⠈⠕⠀⠚⠧⠒⠈⠻⠻` + - actual: `⠀⠏⠒⠨⠾⠀⠼⠉⠂⠀⠼⠙⠀⠚⠥⠈⠕⠀⠚⠧⠒⠈⠻⠻` + - first differing cell (zero-based): 90 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` + ## Cross-cutting input-only structural cohorts -These are cross-cutting input-only structural cohorts, not new primary classes and not engine routing rules. Candidate selection never changes a case's existing primary class by itself. Only cohort members already classified as `pending_rule_review` form a pending subcluster; exact and other-primary members are controls that retain their existing outcomes. A separate classifier may use independently justified, output-localized PDF evidence, as in the rule-34 three-cell contradiction below. The `uppercase_roman_headword_closed_multiword_parenthetical` gate requires a two-or-more character uppercase ASCII headword immediately followed by a closed parenthesis whose contents are two or more ASCII Roman words separated only by spaces. Because the contents admit only letters and spaces, visible operators, subscript/superscript notation, and nested parentheses are excluded deterministically. The `mixed_roman_korean_word_before_uppercase_headword_expansion` gate further requires a whitespace-delimited preceding word that contains both ASCII Roman and Korean and ends in Korean. It locates the following uppercase headword itself, including its immediately preceding emitted cell, so an earlier Roman entry in the same sentence cannot satisfy the output audit. The `single_capital_followed_by_parenthesized_digits` gate requires a standalone capital, a non-empty closed ASCII-digit parenthetical, and alphanumeric outer boundaries. It likewise includes the emitted entry-boundary cell in localization. The `uppercase_roman_run_followed_by_hyphen_digits` gate requires a maximal uppercase ASCII run, literal ASCII hyphen-minus, a non-empty digit run, and alphanumeric outer boundaries. Its localized range includes the current encoded run and its immediately preceding cell, keeping `F-35`-style routing separate from both parenthesized digits and headword expansions. The `standalone_multi_character_uppercase_roman_word` gate finds maximal ASCII-letter runs of two or more capitals with non-alphanumeric boundaries; a run immediately followed by `(` is excluded so the HCA-style headword itself is not counted by both gates. The `korean_prefixed_closed_allcaps_parenthetical` gate requires an immediately preceding Korean character and a closed body of two or more uppercase ASCII letters. It intentionally contains both acronym annotations (`책임자(COO)`) and scientific formulae (`일산화탄소(CO)`) so their semantic collision remains measurable. The `korean_prefixed_closed_roman_annotation_rule_34_order` gate accepts the narrower rule-34 body grammar after an immediately preceding Korean character and localizes only the current engine's Korean opening-parenthesis cells. This distinguishes the PDF's parenthesis-before-Roman-indicator order from unrelated differences later in the same sentence. The `multi_character_allcaps_roman_runs_joined_by_middle_dot` gate requires two maximal ASCII-letter runs of at least two capitals joined directly by U+00B7, with non-alphanumeric outer boundaries. It records shapes such as `AI·SW` without assigning prose, mathematics, or science semantics. The `roman_run_immediately_before_attached_middle_dot_boundary` gate is output-localized: it requires a maximal ASCII-letter run immediately before U+00B7 and an attached Korean character or ASCII-letter run after it, then searches for that whole current-engine signature in the actual output. It therefore isolates the Roman terminator boundary without treating unrelated middle dots elsewhere in the sentence as causal. The `korean_inline_parenthesized_single_arithmetic_operator` gate requires an immediate `Korean(` + one rule-45 arithmetic operator + `)Korean` span. Unlike broad coexistence traits, it also locates the current engine's emitted structure and counts a mismatch as signature-local only when the sentence's first differing cell falls inside that output range. The `allcaps_roman_run_containing_ou` gate finds maximal, alphanumeric-delimited uppercase ASCII runs containing adjacent `OU`. It locates the independently encoded run signature in the complete current output and counts only first differences inside that signature as localized. The `decimal_point_between_ascii_digits` gate finds whitespace-delimited words containing `digit.digit` and reproduces each whole word in a neutral Korean context, so suffixes and punctuation remain part of the current-engine signature. The `compact_numeric_ascii_letter_suffix` gate finds a numeric prefix immediately followed by ASCII letters, includes the immediately preceding output cell as its entry boundary, and retains suffix-specific outcome counts. It intentionally includes both possible rule-69 units and ambiguous variable/identifier forms; membership alone does not assign unit semantics. The `rule69_ascii_unit_before_terminator_skipping_symbol` gate is narrower: it accepts only ASCII unit spellings already supported by rule 69, includes the immediately following rule-33/34 punctuation cell in the localized signature, and does not infer new units. The `tight_triangle_mark_immediately_before_korean` gate requires literal `△한글` with no input space and includes the first following Korean cell in its localized output range, so an observed missing-space difference is measured at the mark boundary. +These are cross-cutting input-only structural cohorts, not new primary classes and not engine routing rules. Candidate selection never changes a case's existing primary class by itself. Only cohort members already classified as `pending_rule_review` form a pending subcluster; exact and other-primary members are controls that retain their existing outcomes. A separate classifier may use independently justified, output-localized PDF evidence, as in the rule-34 three-cell contradiction below. The `uppercase_roman_headword_closed_multiword_parenthetical` gate requires a two-or-more character uppercase ASCII headword immediately followed by a closed parenthesis whose contents are two or more ASCII Roman words separated only by spaces. Because the contents admit only letters and spaces, visible operators, subscript/superscript notation, and nested parentheses are excluded deterministically. The `mixed_roman_korean_word_before_uppercase_headword_expansion` gate further requires a whitespace-delimited preceding word that contains both ASCII Roman and Korean and ends in Korean. It locates the following uppercase headword itself, including its immediately preceding emitted cell, so an earlier Roman entry in the same sentence cannot satisfy the output audit. The `single_capital_followed_by_parenthesized_digits` gate requires a standalone capital, a non-empty closed ASCII-digit parenthetical, and alphanumeric outer boundaries. It likewise includes the emitted entry-boundary cell in localization. The `uppercase_roman_run_followed_by_hyphen_digits` gate requires a maximal uppercase ASCII run, literal ASCII hyphen-minus, a non-empty digit run, and alphanumeric outer boundaries. Its localized range includes the current encoded run and its immediately preceding cell, keeping `F-35`-style routing separate from both parenthesized digits and headword expansions. The `standalone_multi_character_uppercase_roman_word` gate finds maximal ASCII-letter runs of two or more capitals with non-alphanumeric boundaries; a run immediately followed by `(` is excluded so the HCA-style headword itself is not counted by both gates. The `korean_prefixed_closed_allcaps_parenthetical` gate requires an immediately preceding Korean character and a closed body of two or more uppercase ASCII letters. It intentionally contains both acronym annotations (`책임자(COO)`) and scientific formulae (`일산화탄소(CO)`) so their semantic collision remains measurable. The `korean_prefixed_closed_roman_annotation_rule_34_order` gate accepts the narrower rule-34 body grammar after an immediately preceding Korean character and localizes only the current engine's Korean opening-parenthesis cells. This distinguishes the PDF's parenthesis-before-Roman-indicator order from unrelated differences later in the same sentence. The `multi_character_allcaps_roman_runs_joined_by_middle_dot` gate requires two maximal ASCII-letter runs of at least two capitals joined directly by U+00B7, with non-alphanumeric outer boundaries. It records shapes such as `AI·SW` without assigning prose, mathematics, or science semantics. The `roman_run_immediately_before_attached_middle_dot_boundary` gate is output-localized: it requires a maximal ASCII-letter run immediately before U+00B7 and an attached Korean character or ASCII-letter run after it, then searches for that whole current-engine signature in the actual output. It therefore isolates the Roman terminator boundary without treating unrelated middle dots elsewhere in the sentence as causal. The `korean_inline_parenthesized_single_arithmetic_operator` gate requires an immediate `Korean(` + one rule-45 arithmetic operator + `)Korean` span. Unlike broad coexistence traits, it also locates the current engine's emitted structure and counts a mismatch as signature-local only when the sentence's first differing cell falls inside that output range. The `allcaps_roman_run_containing_ou` gate finds maximal, alphanumeric-delimited uppercase ASCII runs containing adjacent `OU`. It locates the independently encoded run signature in the complete current output and counts only first differences inside that signature as localized. The `allcaps_roman_run_containing_st` gate applies the same output-position requirement to adjacent `ST`; it is kept separate because UEB 10.12 makes contraction use depend on how an abbreviation or acronym is pronounced. The `decimal_point_between_ascii_digits` gate finds whitespace-delimited words containing `digit.digit` and reproduces each whole word in a neutral Korean context, so suffixes and punctuation remain part of the current-engine signature. The `compact_numeric_ascii_letter_suffix` gate finds a numeric prefix immediately followed by ASCII letters, includes the immediately preceding output cell as its entry boundary, and retains suffix-specific outcome counts. It intentionally includes both possible rule-69 units and ambiguous variable/identifier forms; membership alone does not assign unit semantics. The `rule69_ascii_unit_before_terminator_skipping_symbol` gate is narrower: it accepts only ASCII unit spellings already supported by rule 69, includes the immediately following rule-33/34 punctuation cell in the localized signature, and does not infer new units. The `tight_triangle_mark_immediately_before_korean` gate requires literal `△한글` with no input space and includes the first following Korean cell in its localized output range, so an observed missing-space difference is measured at the mark boundary. | Cluster | Candidates | Exact | Mismatch | Conflicting-reference cases | |---|---:|---:|---:|---:| | `allcaps_roman_run_beginning_with_pure_letter_shortform` | 4120 | 2425 | 1695 | 0 | | `allcaps_roman_run_containing_ou` | 1816 | 64 | 1752 | 0 | +| `allcaps_roman_run_containing_st` | 1479 | 675 | 804 | 0 | | `closed_roman_parenthetical_after_non_ascii_letter_boundary` | 63959 | 51554 | 12405 | 0 | | `compact_numeric_ascii_letter_suffix` | 2975 | 1855 | 1120 | 0 | | `decimal_point_between_ascii_digits` | 4546 | 3267 | 1279 | 0 | @@ -724,6 +725,92 @@ Representative `mismatch` samples: - first differing cell (zero-based): 0 - current primary/reason: `unsupported_character_review` / `unsupported_character_review` +### `allcaps_roman_run_containing_st` + +Of the 1479 candidates, 755 are the actual `pending_rule_review` subcluster. The other 724 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. + +For this output-signature audit, 804 mismatches were evaluable and 443 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. + +Localized first-difference transitions: + +- `U+280E ⠎ -> U+280C ⠌`: 435 +- `U+2820 ⠠ -> U+280C ⠌`: 4 +- `U+280C ⠌ -> U+280E ⠎`: 1 +- `U+2810 ⠐ -> U+2811 ⠑`: 1 +- `U+2810 ⠐ -> U+2825 ⠥`: 1 + +Mismatch primary-class distribution: + +- `comparison_method`: 13 +- `corpus_suspect`: 31 +- `pending_rule_review`: 755 +- `unsupported_character_review`: 5 + +Representative `exact` samples: + +- `sentence_01.json` #44: 지니타임티켓은 지니뮤직 실시간 라이브 공연 플랫폼 ‘스테이지(STAYG)’를 통해 오프라인 공연을 특별가에 구매·감상할 수 있는 서비스다. + - expected: `⠨⠕⠉⠕⠓⠣⠕⠢⠓⠕⠋⠝⠄⠵⠀⠨⠕⠉⠕⠑⠩⠨⠕⠁` + - actual: `⠨⠕⠉⠕⠓⠣⠕⠢⠓⠕⠋⠝⠄⠵⠀⠨⠕⠉⠕⠑⠩⠨⠕⠁` + - current primary/reason: `exact` / `exact` +- `sentence_02.json` #82: 대전 이전을 진행 중인 방위사업청 과장급 직원 110여 명이 한국과학기술원(KAIST) 을지연구소가 주관한 ‘국방연구개발 아카데미’에 참석하기 위해 대전을 찾았다. + - expected: `⠊⠗⠨⠾⠀⠕⠨⠾⠮⠀⠨⠟⠚⠗⠶⠀⠨⠍⠶⠟⠀⠘⠶⠍` + - actual: `⠊⠗⠨⠾⠀⠕⠨⠾⠮⠀⠨⠟⠚⠗⠶⠀⠨⠍⠶⠟⠀⠘⠶⠍` + - current primary/reason: `exact` / `exact` +- `sentence_03.json` #281: 대구경북과학기술원(DGIST) 소속 구성원들이 ‘4월 과학의 달’을 맞아 과학연구 성과 등에 기여한 공로로 표창 수상자를 대거 배출했다. + - expected: `⠊⠗⠈⠍⠈⠻⠘⠍⠁⠈⠧⠚⠁⠈⠕⠠⠯⠏⠒⠦⠄⠴⠠⠠` + - actual: `⠊⠗⠈⠍⠈⠻⠘⠍⠁⠈⠧⠚⠁⠈⠕⠠⠯⠏⠒⠦⠄⠴⠠⠠` + - current primary/reason: `exact` / `exact` +- `sentence_04.json` #615: 지난달 연세대와도 인력 육성 업무협약을 체결한 지 2주 만이다. 앞서 포스코퓨처엠은 지난해부터 포스텍(POSTECH), 울산과학기술원(UNIST), 한양대, 대구경북과학기술원(DGIST) 등과도 배터리소재 인재 양성을 위한 협약을 맺은 바 있다. + - expected: `⠨⠕⠉⠒⠊⠂⠀⠡⠠⠝⠊⠗⠧⠊⠥⠀⠟⠐⠱⠁⠀⠩⠁⠠` + - actual: `⠨⠕⠉⠒⠊⠂⠀⠡⠠⠝⠊⠗⠧⠊⠥⠀⠟⠐⠱⠁⠀⠩⠁⠠` + - current primary/reason: `exact` / `exact` + +Representative `localized_mismatch` samples: + +- `sentence_01.json` #167: 올해 전 세계 반도체 시장 전망도 암울하다. 세계반도체시장통계기구(WSTS)에 따르면 올해 글로벌 반도체 매출은 5천565억 달러(약 706조5천880억원)로 지난해(5천801억달러) 대비 4.1% 감소할 것으로 조사됐다. + - expected: `⠈⠍⠦⠄⠴⠠⠠⠺⠎⠞⠎⠠⠴⠝⠀⠠⠊⠐⠪⠑⠡⠀⠥⠂` + - actual: `⠈⠍⠦⠄⠴⠠⠠⠺⠌⠎⠠⠴⠝⠀⠠⠊⠐⠪⠑⠡⠀⠥⠂⠚` + - first differing cell (zero-based): 67 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_02.json` #244: 충남대학교 이진숙 총장을 비롯한 관계자들은 5월 19일, 베트남 최고의 국립대인 하노이과학기술대학(HUST), 베트남국립농업대학(VNUA)을 연이어 방문해 ‘오픈 캠퍼스(open campus)’ 설립을 골자로 한 협정을 체결했다. + - expected: `⠁⠦⠄⠴⠠⠠⠓⠥⠎⠞⠠⠴⠐⠀⠘⠝⠓⠪⠉⠢⠈⠍⠁⠐` + - actual: `⠁⠦⠄⠴⠠⠠⠓⠥⠌⠠⠴⠐⠀⠘⠝⠓⠪⠉⠢⠈⠍⠁⠐⠕` + - first differing cell (zero-based): 101 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_03.json` #142: 수많은 작품 OST를 책임지며 실력파 프로듀서로 인정받고 있는 필승불패W, 리디아(Lydia), 장석원이 힘을 합쳐 만든 곡으로 웰메이드 OST 탄생을 예감케 한다. + - expected: `⠙⠍⠢⠀⠴⠠⠠⠕⠎⠞⠲⠐⠮⠀⠰⠗⠁⠕⠢⠨⠕⠑⠱⠀` + - actual: `⠙⠍⠢⠀⠴⠠⠠⠕⠌⠲⠐⠮⠀⠰⠗⠁⠕⠢⠨⠕⠑⠱⠀⠠` + - first differing cell (zero-based): 17 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_04.json` #375: 윤석열 대통령은 30일 캐서린 타이 미국 무역대표부(USTR) 대표를 만나 미 인플레이션감축법(IRA)·반도체지원법(CHIPS Act)과 관련해 “미국에 진출하는 한국 기업들이 어려움을 겪지 않도록 우호적인 방향으로 배려해 달라”고 당부했다. + - expected: `⠘⠍⠦⠄⠴⠠⠠⠥⠎⠞⠗⠠⠴⠀⠊⠗⠙⠬⠐⠮⠀⠑⠒⠉` + - actual: `⠘⠍⠦⠄⠴⠠⠠⠥⠌⠗⠠⠴⠀⠊⠗⠙⠬⠐⠮⠀⠑⠒⠉⠀` + - first differing cell (zero-based): 53 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` + +Representative `mismatch` samples: + +- `sentence_01.json` #167: 올해 전 세계 반도체 시장 전망도 암울하다. 세계반도체시장통계기구(WSTS)에 따르면 올해 글로벌 반도체 매출은 5천565억 달러(약 706조5천880억원)로 지난해(5천801억달러) 대비 4.1% 감소할 것으로 조사됐다. + - expected: `⠈⠍⠦⠄⠴⠠⠠⠺⠎⠞⠎⠠⠴⠝⠀⠠⠊⠐⠪⠑⠡⠀⠥⠂` + - actual: `⠈⠍⠦⠄⠴⠠⠠⠺⠌⠎⠠⠴⠝⠀⠠⠊⠐⠪⠑⠡⠀⠥⠂⠚` + - first differing cell (zero-based): 67 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_02.json` #132: 이번 업무협약에는 대전시를 비롯해 △한국과학기술원(KAIST) △한국표준과학연구원(KRISS) △한국과학기술연구원(KIST) △한국과학기술정보연구원(KISTI) △한국전자통신연구원(ETRI) △한국원자력연구원(KAERI) △국방과학연구소(ADD) △나노종합기술원(NNFC) △국가보안기술연구소(NSR) 등 양자산업 관련 관 + - expected: `⠐⠥⠄⠚⠗⠀⠸⠬⠀⠚⠒⠈⠍⠁⠈⠧⠚⠁⠈⠕⠠⠯⠏⠒` + - actual: `⠐⠥⠄⠚⠗⠀⠸⠬⠇⠚⠒⠈⠍⠁⠈⠧⠚⠁⠈⠕⠠⠯⠏⠒` + - first differing cell (zero-based): 36 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_03.json` #142: 수많은 작품 OST를 책임지며 실력파 프로듀서로 인정받고 있는 필승불패W, 리디아(Lydia), 장석원이 힘을 합쳐 만든 곡으로 웰메이드 OST 탄생을 예감케 한다. + - expected: `⠙⠍⠢⠀⠴⠠⠠⠕⠎⠞⠲⠐⠮⠀⠰⠗⠁⠕⠢⠨⠕⠑⠱⠀` + - actual: `⠙⠍⠢⠀⠴⠠⠠⠕⠌⠲⠐⠮⠀⠰⠗⠁⠕⠢⠨⠕⠑⠱⠀⠠` + - first differing cell (zero-based): 17 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_04.json` #375: 윤석열 대통령은 30일 캐서린 타이 미국 무역대표부(USTR) 대표를 만나 미 인플레이션감축법(IRA)·반도체지원법(CHIPS Act)과 관련해 “미국에 진출하는 한국 기업들이 어려움을 겪지 않도록 우호적인 방향으로 배려해 달라”고 당부했다. + - expected: `⠘⠍⠦⠄⠴⠠⠠⠥⠎⠞⠗⠠⠴⠀⠊⠗⠙⠬⠐⠮⠀⠑⠒⠉` + - actual: `⠘⠍⠦⠄⠴⠠⠠⠥⠌⠗⠠⠴⠀⠊⠗⠙⠬⠐⠮⠀⠑⠒⠉⠀` + - first differing cell (zero-based): 53 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` + ### `closed_roman_parenthetical_after_non_ascii_letter_boundary` Of the 63959 candidates, 10926 are the actual `pending_rule_review` subcluster. The other 53033 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. @@ -2219,6 +2306,10 @@ The tight-triangle cohort is not an implementation premise. Hangeul rule 49 assi Corpus contradictions remain a separate gate: identical inputs with conflicting references are classified as `corpus_suspect` before these cohorts are recorded and would appear explicitly in each mismatch primary-class distribution. Their absence does not prove a reference correct; it only means that this deterministic contradiction test did not fire. +### Uppercase Roman runs containing `ST` + +UEB 10.12.1 (2024 UEB PDF pp.191-192, printed pp.163-164) says not to use a contraction when letters within an abbreviation or acronym are known to be pronounced separately, but to use the contraction in case of doubt. Rule 10.12.2 requires contractions in other abbreviations and acronyms. Consequently an uppercase `ST` surface alone cannot determine whether `s` + `t` or the `st` groupsign is required. The current cohort contains 1479 candidates, 675 exact controls, and 804 mismatches; primary classes remain 755 `pending_rule_review`, 31 `corpus_suspect`, 13 `comparison_method`, and 5 `unsupported_character_review`. Of 804 evaluable mismatches, 443 are localized to the detected current-engine run: 435 `U+280E ⠎ -> U+280C ⠌` and 1 `U+280C ⠌ -> U+280E ⠎`. The target's raw-to-residual count is 448 -> 13, and the reverse is 2 -> 1. Exact controls such as `STAYG`, `KAIST`, `DGIST`, and `POSTECH` coexist with localized mismatches such as `WSTS`, `HUST`, `OST`, and `USTR`; representative shard/index samples are preserved in the cluster table. This lexical/pronunciation distinction cannot be inferred from the input-only spelling, so no engine change or primary reclassification is made. + Current uppercase-Roman hyphen-digits measurement: 571 candidates, 189 exact controls, 382 mismatches, 367 members in the actual `pending_rule_review` subcluster, and 261/382 evaluable mismatches whose first difference is inside the target run plus its entry boundary. It remains distinct from parenthesized digits and headword expansions; no engine change is inferred. Current single-capital parenthesized-digits measurement: 1361 candidates, 0 exact controls, 1361 mismatches, 1357 members in the actual `pending_rule_review` subcluster, and 1342/1361 evaluable mismatches whose first difference is inside the target run plus its entry boundary. No engine change is inferred from the ambiguous prose/function surface form. diff --git a/libs/braillify/examples/nikl_corpus_analyze.rs b/libs/braillify/examples/nikl_corpus_analyze.rs index 7dd09880..2459c798 100644 --- a/libs/braillify/examples/nikl_corpus_analyze.rs +++ b/libs/braillify/examples/nikl_corpus_analyze.rs @@ -581,6 +581,7 @@ const KOREAN_INLINE_PARENTHESIZED_OPERATOR: &str = "korean_inline_parenthesized_single_arithmetic_operator"; const TIGHT_TRIANGLE_BEFORE_KOREAN: &str = "tight_triangle_mark_immediately_before_korean"; const ALLCAPS_ROMAN_RUN_CONTAINING_OU: &str = "allcaps_roman_run_containing_ou"; +const ALLCAPS_ROMAN_RUN_CONTAINING_ST: &str = "allcaps_roman_run_containing_st"; const SINGLE_CAPITAL_PARENTHESIZED_DIGITS: &str = "single_capital_followed_by_parenthesized_digits"; const MIXED_ROMAN_KOREAN_BEFORE_HEADWORD_EXPANSION: &str = "mixed_roman_korean_word_before_uppercase_headword_expansion"; @@ -904,7 +905,7 @@ fn is_rule_34_reference_order_contradiction(item: &EncodedCase) -> bool { /// This is an input gate for a pronunciation-sensitive UEB diagnostic, not a /// claim that the run is an initialism. Alphanumeric outer boundaries exclude /// fragments of identifiers while retaining parenthesized and standalone runs. -fn allcaps_roman_runs_containing_ou(input: &str) -> Vec { +fn allcaps_roman_runs_containing_pair(input: &str, pair: &[u8; 2]) -> Vec { let bytes = input.as_bytes(); let mut runs = Vec::new(); let mut cursor = 0; @@ -928,7 +929,7 @@ fn allcaps_roman_runs_containing_ou(input: &str) -> Vec { let next = input[end_byte..].chars().next(); if run.len() >= 2 && run.bytes().all(|byte| byte.is_ascii_uppercase()) - && run.as_bytes().windows(2).any(|pair| pair == b"OU") + && run.as_bytes().windows(2).any(|window| window == pair) && previous.is_none_or(|ch| !ch.is_ascii_alphanumeric()) && next.is_none_or(|ch| !ch.is_ascii_alphanumeric()) { @@ -941,6 +942,14 @@ fn allcaps_roman_runs_containing_ou(input: &str) -> Vec { runs } +fn allcaps_roman_runs_containing_ou(input: &str) -> Vec { + allcaps_roman_runs_containing_pair(input, b"OU") +} + +fn allcaps_roman_runs_containing_st(input: &str) -> Vec { + allcaps_roman_runs_containing_pair(input, b"ST") +} + /// Finds maximal pure-uppercase ASCII letter runs whose beginning is itself a /// pure-letter UEB shortform abbreviation. UEB 5.7.2 and 10.9.7-10.9.8 require /// grade 1 both for a complete shortform-shaped letters-sequence (`WD`) and for @@ -1541,6 +1550,23 @@ fn first_difference_in_allcaps_ou_run(item: &EncodedCase) -> bool { .any(|range| range.contains(&first_difference)) } +fn allcaps_st_actual_ranges(input: &str, actual: &str) -> Vec> { + current_engine_signature_ranges(input, actual, &allcaps_roman_runs_containing_st(input), 0) +} + +fn first_difference_in_allcaps_st_run(item: &EncodedCase) -> bool { + let Ok(actual) = &item.actual else { + return false; + }; + if actual == &item.located.case.unicode { + return false; + } + let first_difference = first_difference_cell(&item.located.case.unicode, actual); + allcaps_st_actual_ranges(&item.located.case.input, actual) + .into_iter() + .any(|range| range.contains(&first_difference)) +} + /// Finds a standalone single capital immediately followed by a non-empty, /// closed ASCII-digit parenthetical, such as `A(14)`. The span is deliberately /// semantic-neutral: prose labels and mathematical function notation can share @@ -1765,7 +1791,7 @@ fn first_difference_claimed_before_nonletter_parenthetical(item: &EncodedCase) - ) } -fn first_difference_claimed_by_localized_cohort(item: &EncodedCase) -> bool { +fn first_difference_claimed_before_allcaps_st(item: &EncodedCase) -> bool { first_difference_claimed_before_nonletter_parenthetical(item) || first_difference_at_parenthetical_entry( item, @@ -1773,6 +1799,10 @@ fn first_difference_claimed_by_localized_cohort(item: &EncodedCase) -> bool { ) } +fn first_difference_claimed_by_localized_cohort(item: &EncodedCase) -> bool { + first_difference_claimed_before_allcaps_st(item) || first_difference_in_allcaps_st_run(item) +} + /// Input-only candidate gate for acronym expansions such as /// `HCA(Home Connectivity Alliance)`. /// @@ -2372,6 +2402,10 @@ fn analyze( ALLCAPS_ROMAN_RUN_CONTAINING_OU.to_string(), PendingRuleReviewClusterStats::default(), ), + ( + ALLCAPS_ROMAN_RUN_CONTAINING_ST.to_string(), + PendingRuleReviewClusterStats::default(), + ), ( ALLCAPS_ROMAN_MIDDLE_DOT_RUNS.to_string(), PendingRuleReviewClusterStats::default(), @@ -2543,6 +2577,15 @@ fn analyze( Some(first_difference_in_allcaps_ou_run(item)), false, ), + ( + ALLCAPS_ROMAN_RUN_CONTAINING_ST, + !allcaps_roman_runs_containing_st(&item.located.case.input).is_empty(), + Some( + !first_difference_claimed_before_allcaps_st(item) + && first_difference_in_allcaps_st_run(item), + ), + true, + ), ( ALLCAPS_ROMAN_MIDDLE_DOT_RUNS, has_allcaps_roman_middle_dot_runs(&item.located.case.input), @@ -3160,7 +3203,10 @@ fn markdown(report: &AnalysisReport) -> String { range. The `allcaps_roman_run_containing_ou` gate finds maximal, alphanumeric-delimited \ uppercase ASCII runs containing adjacent `OU`. It locates the independently encoded run \ signature in the complete current output and counts only first differences inside that \ - signature as localized. The `decimal_point_between_ascii_digits` gate finds \ + signature as localized. The `allcaps_roman_run_containing_st` gate applies the same \ + output-position requirement to adjacent `ST`; it is kept separate because UEB 10.12 \ + makes contraction use depend on how an abbreviation or acronym is pronounced. The \ + `decimal_point_between_ascii_digits` gate finds \ whitespace-delimited words containing `digit.digit` and reproduces each whole word in a \ neutral Korean context, so suffixes and punctuation remain part of the current-engine \ signature. The `compact_numeric_ascii_letter_suffix` gate finds a numeric prefix \ @@ -3754,6 +3800,74 @@ fn markdown(report: &AnalysisReport) -> String { not prove a reference correct; it only means that this deterministic contradiction test \ did not fire.\n", ); + if let Some(stats) = report + .pending_rule_review_clusters + .get(ALLCAPS_ROMAN_RUN_CONTAINING_ST) + { + let primary_count = |name: &str| { + stats + .mismatch_primary_classes + .get(name) + .copied() + .unwrap_or(0) + }; + let localized_transition = |name: &str| { + stats + .first_difference_in_output_signature_transitions + .get(name) + .copied() + .unwrap_or(0) + }; + let raw_transition = |name: &str| { + report + .pending_first_difference_cell_transitions + .get(name) + .map(|transition| transition.cases) + .unwrap_or(0) + }; + let residual_transition = |name: &str| { + report + .pending_first_difference_transitions_after_localized_cohorts + .get(name) + .map(|transition| transition.cases) + .unwrap_or(0) + }; + let target = "U+280E ⠎ -> U+280C ⠌"; + let reverse = "U+280C ⠌ -> U+280E ⠎"; + text.push_str(&format!( + "\n### Uppercase Roman runs containing `ST`\n\n\ + UEB 10.12.1 (2024 UEB PDF pp.191-192, printed pp.163-164) says not to \ + use a contraction when letters within an abbreviation or acronym are known to be \ + pronounced separately, but to use the contraction in case of doubt. Rule 10.12.2 \ + requires contractions in other abbreviations and acronyms. Consequently an \ + uppercase `ST` surface alone cannot determine whether `s` + `t` or the `st` \ + groupsign is required. The current cohort contains {} candidates, {} exact controls, \ + and {} mismatches; primary classes remain {} `pending_rule_review`, {} \ + `corpus_suspect`, {} `comparison_method`, and {} \ + `unsupported_character_review`. Of {} evaluable mismatches, {} are localized to the \ + detected current-engine run: {} `{target}` and {} `{reverse}`. The target's \ + raw-to-residual count is {} -> {}, and the reverse is {} -> {}. Exact controls such \ + as `STAYG`, `KAIST`, `DGIST`, and `POSTECH` coexist with localized mismatches such as \ + `WSTS`, `HUST`, `OST`, and `USTR`; representative shard/index samples are preserved \ + in the cluster table. This lexical/pronunciation distinction cannot be inferred from \ + the input-only spelling, so no engine change or primary reclassification is made.\n", + stats.candidates, + stats.exact, + stats.mismatch, + primary_count("pending_rule_review"), + primary_count("corpus_suspect"), + primary_count("comparison_method"), + primary_count("unsupported_character_review"), + stats.output_signature_mismatches_evaluated, + stats.first_difference_in_output_signature, + localized_transition(target), + localized_transition(reverse), + raw_transition(target), + residual_transition(target), + raw_transition(reverse), + residual_transition(reverse), + )); + } if let Some(stats) = report .pending_rule_review_clusters .get(UPPERCASE_ROMAN_HYPHEN_DIGITS) @@ -4825,6 +4939,21 @@ mod tests { assert_eq!(actual, expected); } + #[rstest::rstest] + #[case::internal_pair("WSTS HUST OST USTR", vec!["WSTS", "HUST", "OST", "USTR"])] + #[case::lowercase("West", vec![])] + #[case::mixed_case("WStS", vec![])] + #[case::no_st("WHO", vec![])] + #[case::digit_prefix("1WSTS", vec![])] + #[case::digit_suffix("WSTS2", vec![])] + fn detects_allcaps_roman_runs_containing_st(#[case] input: &str, #[case] expected: Vec<&str>) { + let actual = allcaps_roman_runs_containing_st(input) + .into_iter() + .map(|run| &input[run.start_byte..run.end_byte]) + .collect::>(); + assert_eq!(actual, expected); + } + #[rstest::rstest] #[case::whole_shortform("가(WD) 나", vec!["WD"])] #[case::longer_prefixes("PDS LLM GDP", vec!["PDS", "LLM", "GDP"])] @@ -5059,6 +5188,17 @@ mod tests { assert!(ranges[0].end <= actual.chars().count()); } + #[test] + fn locates_allcaps_st_signature_in_complete_output() { + let input = "통계기구(WSTS)에 따르면"; + let actual = braillify::encode_to_unicode(input).expect("probe must encode"); + let ranges = allcaps_st_actual_ranges(input, &actual); + + assert_eq!(ranges.len(), 1); + assert!(ranges[0].start < ranges[0].end); + assert!(ranges[0].end <= actual.chars().count()); + } + #[rstest::rstest] #[case::standalone("새로운 DRX 브랜드", true)] #[case::inside_parentheses("엠디(MD), SNS", true)] From fdf73e534a61b99112594a9334f72c7764746fdc Mon Sep 17 00:00:00 2001 From: devfive Date: Tue, 1 Sep 2026 12:42:53 +0900 Subject: [PATCH 34/77] Diagnose attached Roman ampersand boundary --- docs/corpus-analysis/NIKL_2025_V1.md | 157 +++++++++---- .../braillify/examples/nikl_corpus_analyze.rs | 219 +++++++++++++++++- 2 files changed, 331 insertions(+), 45 deletions(-) diff --git a/docs/corpus-analysis/NIKL_2025_V1.md b/docs/corpus-analysis/NIKL_2025_V1.md index 81fc352f..93249b52 100644 --- a/docs/corpus-analysis/NIKL_2025_V1.md +++ b/docs/corpus-analysis/NIKL_2025_V1.md @@ -298,49 +298,26 @@ This ranking removes only cases whose first difference is inside an existing out | Rank | Expected → actual first cell | Residual cases | |---:|---|---:| -| 1 | `U+2808 ⠈ -> U+2832 ⠲` | 409 | -| 2 | `U+2801 ⠁ -> U+281C ⠜` | 390 | -| 3 | `U+2834 ⠴ -> U+2830 ⠰` | 332 | -| 4 | `U+2811 ⠑ -> U+282B ⠫` | 325 | -| 5 | `U+2810 ⠐ -> U+2832 ⠲` | 290 | -| 6 | `U+2832 ⠲ -> U+2838 ⠸` | 281 | -| 7 | `U+2834 ⠴ -> U+2800 ⠀` | 258 | -| 8 | `U+2800 ⠀ -> U+2832 ⠲` | 227 | -| 9 | `U+2826 ⠦ -> U+2800 ⠀` | 219 | -| 10 | `U+2810 ⠐ -> U+2802 ⠂` | 210 | -| 11 | `U+2817 ⠗ -> U+2820 ⠠` | 163 | -| 12 | `U+2820 ⠠ -> U+2809 ⠉` | 160 | -| 13 | `U+283C ⠼ -> U+2800 ⠀` | 153 | -| 14 | `U+281B ⠛ -> U+2823 ⠣` | 144 | -| 15 | `U+2826 ⠦ -> U+2810 ⠐` | 144 | -| 16 | `U+2810 ⠐ -> U+2800 ⠀` | 130 | -| 17 | `U+2811 ⠑ -> U+283B ⠻` | 101 | -| 18 | `U+2810 ⠐ -> U+2814 ⠔` | 98 | -| 19 | `U+280E ⠎ -> U+2820 ⠠` | 91 | -| 20 | `U+2804 ⠄ -> U+2834 ⠴` | 86 | - -### Residual `U+2808 ⠈ -> U+2832 ⠲` - -- `sentence_01.json` #207: 우리금융지주가 약 7조원의 자본 여력을 등에 업고 인수·합병(M&A) 시장에 출격했다. 벤처캐피탈(VC) 인수를 시작으로 증권사와 보험사까지 인수한다는 계획이다. - - expected: `⠃⠘⠻⠦⠄⠴⠠⠍⠈⠯⠠⠁⠠⠴⠀⠠⠕⠨⠶⠝⠀⠰⠯⠈` - - actual: `⠃⠘⠻⠦⠄⠴⠠⠍⠲⠴⠈⠯⠲⠴⠠⠁⠠⠴⠀⠠⠕⠨⠶⠝` - - first differing cell (zero-based): 59 - - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_02.json` #146: KT&G(사장 백복인)가 산업정책연구원(IPS)이 주관하고 윤경 ESG 포럼이 주최하는 ‘윤경 ESG 포럼 CEO 서약식’에 참여해 윤리경영 의지를 다졌다. - - expected: `⠴⠠⠠⠅⠞⠈⠯⠠⠛⠦⠄⠇⠨⠶⠀⠘⠗⠁⠘⠭⠟⠠⠴⠫` - - actual: `⠴⠠⠠⠅⠞⠲⠴⠈⠯⠲⠴⠛⠦⠄⠇⠨⠶⠀⠘⠗⠁⠘⠭⠟` - - first differing cell (zero-based): 5 - - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_03.json` #191: 한국투자신탁운용의 에이스(ACE) 글로벌브랜드TOP10블룸버그 ETF는 연초 이후 22.6% 상승했다. 명품 기업 루이비통모에헤네시(LVMH), 대표 가치주로 꼽히는 프록터앤드갬블(P&G) 편입 비중이 높은 것이 특징이다. - - expected: `⠢⠘⠮⠦⠄⠴⠠⠏⠈⠯⠠⠛⠠⠴⠀⠙⠡⠕⠃⠀⠘⠕⠨⠍` - - actual: `⠢⠘⠮⠦⠄⠴⠠⠏⠲⠴⠈⠯⠲⠴⠠⠛⠠⠴⠀⠙⠡⠕⠃⠀` - - first differing cell (zero-based): 183 - - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_04.json` #159: 또 액화천연가스(LNG) 분야 협력을 확대하는 한편 수소환원제철 관련 공동 연구·개발(R&D)을 추진해 유럽연합(EU)의 탄소국경조정세(CBAM)와 같은 글로벌 규제와 자원 무기화에 공동 대응한다는 전략이다. - - expected: `⠗⠘⠂⠦⠄⠴⠠⠗⠈⠯⠠⠙⠠⠴⠮⠀⠰⠍⠨⠟⠚⠗⠀⠩` - - actual: `⠗⠘⠂⠦⠄⠴⠠⠗⠲⠴⠈⠯⠲⠴⠠⠙⠠⠴⠮⠀⠰⠍⠨⠟` - - first differing cell (zero-based): 90 - - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +| 1 | `U+2801 ⠁ -> U+281C ⠜` | 390 | +| 2 | `U+2834 ⠴ -> U+2830 ⠰` | 332 | +| 3 | `U+2811 ⠑ -> U+282B ⠫` | 325 | +| 4 | `U+2810 ⠐ -> U+2832 ⠲` | 290 | +| 5 | `U+2832 ⠲ -> U+2838 ⠸` | 281 | +| 6 | `U+2834 ⠴ -> U+2800 ⠀` | 258 | +| 7 | `U+2800 ⠀ -> U+2832 ⠲` | 227 | +| 8 | `U+2826 ⠦ -> U+2800 ⠀` | 219 | +| 9 | `U+2810 ⠐ -> U+2802 ⠂` | 210 | +| 10 | `U+2817 ⠗ -> U+2820 ⠠` | 163 | +| 11 | `U+2820 ⠠ -> U+2809 ⠉` | 160 | +| 12 | `U+283C ⠼ -> U+2800 ⠀` | 153 | +| 13 | `U+281B ⠛ -> U+2823 ⠣` | 144 | +| 14 | `U+2826 ⠦ -> U+2810 ⠐` | 144 | +| 15 | `U+2810 ⠐ -> U+2800 ⠀` | 130 | +| 16 | `U+2811 ⠑ -> U+283B ⠻` | 101 | +| 17 | `U+2810 ⠐ -> U+2814 ⠔` | 98 | +| 18 | `U+280E ⠎ -> U+2820 ⠠` | 91 | +| 19 | `U+2804 ⠄ -> U+2834 ⠴` | 86 | +| 20 | `U+2803 ⠃ -> U+2820 ⠠` | 85 | ### Residual `U+2801 ⠁ -> U+281C ⠜` @@ -549,15 +526,39 @@ This ranking removes only cases whose first difference is inside an existing out - first differing cell (zero-based): 90 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +### Residual `U+2817 ⠗ -> U+2820 ⠠` + +- `sentence_01.json` #1294: 한편 엔씨소프트는 오픈형 R&D 개발 문화 ‘엔씽(NCing)’의 일환으로 개발 중인 다양한 장르의 신규 프로젝트를 공개하고 있다. + - expected: `⠥⠙⠵⠚⠻⠀⠴⠠⠗⠈⠯⠠⠙⠲⠀⠈⠗⠘⠂⠀⠑⠛⠚⠧` + - actual: `⠥⠙⠵⠚⠻⠀⠴⠠⠠⠗⠲⠴⠈⠯⠲⠴⠙⠲⠀⠈⠗⠘⠂⠀` + - first differing cell (zero-based): 27 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_02.json` #226: 대한민국 드론·UAM 박람회는 UAM(도심항공교통) 등 차세대 국토교통 R&D와 주요 지자체의 관련 사업을 살펴볼 수 있는 의미 있는 행사로 국토교통부와 인천광역시가 공동 주최한다. + - expected: `⠥⠈⠬⠓⠿⠀⠴⠠⠗⠈⠯⠠⠙⠲⠧⠀⠨⠍⠬⠀⠨⠕⠨⠰` + - actual: `⠥⠈⠬⠓⠿⠀⠴⠠⠠⠗⠲⠴⠈⠯⠲⠴⠙⠲⠧⠀⠨⠍⠬⠀` + - first differing cell (zero-based): 81 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_03.json` #647: 온유는 지난 3월 6일 첫 정규 앨범 ‘써클(Circle)’로 컴백한다. 이번 앨범은 몽환적인 R&B 장르의 타이틀곡 ‘O(Circle)’(써클)을 비롯한 다채로운 분위기의 10곡으로 구성됐다. + - expected: `⠧⠒⠨⠹⠟⠀⠴⠠⠗⠈⠯⠠⠃⠲⠀⠨⠶⠐⠪⠺⠀⠓⠣⠕` + - actual: `⠧⠒⠨⠹⠟⠀⠴⠠⠠⠗⠲⠴⠈⠯⠲⠴⠃⠲⠀⠨⠶⠐⠪⠺` + - first differing cell (zero-based): 92 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_04.json` #2491: 앞서 정보통신기술(ICT) 분야를 담당하는 박윤규 과기정통부 2차관도 지난 1일 산하 정보통신정책실·네트워크정책실 소속 실·국장을 대상으로 간부회의를 소집해 ICT 분야 R&D 예산으로 잡힌 사업들을 원점에서 재검토하도록 했다. + - expected: `⠲⠀⠘⠛⠜⠀⠴⠠⠗⠈⠯⠠⠙⠲⠀⠌⠇⠒⠪⠐⠥⠀⠨⠃` + - actual: `⠲⠀⠘⠛⠜⠀⠴⠠⠠⠗⠲⠴⠈⠯⠲⠴⠙⠲⠀⠌⠇⠒⠪⠐` + - first differing cell (zero-based): 175 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` + ## Cross-cutting input-only structural cohorts -These are cross-cutting input-only structural cohorts, not new primary classes and not engine routing rules. Candidate selection never changes a case's existing primary class by itself. Only cohort members already classified as `pending_rule_review` form a pending subcluster; exact and other-primary members are controls that retain their existing outcomes. A separate classifier may use independently justified, output-localized PDF evidence, as in the rule-34 three-cell contradiction below. The `uppercase_roman_headword_closed_multiword_parenthetical` gate requires a two-or-more character uppercase ASCII headword immediately followed by a closed parenthesis whose contents are two or more ASCII Roman words separated only by spaces. Because the contents admit only letters and spaces, visible operators, subscript/superscript notation, and nested parentheses are excluded deterministically. The `mixed_roman_korean_word_before_uppercase_headword_expansion` gate further requires a whitespace-delimited preceding word that contains both ASCII Roman and Korean and ends in Korean. It locates the following uppercase headword itself, including its immediately preceding emitted cell, so an earlier Roman entry in the same sentence cannot satisfy the output audit. The `single_capital_followed_by_parenthesized_digits` gate requires a standalone capital, a non-empty closed ASCII-digit parenthetical, and alphanumeric outer boundaries. It likewise includes the emitted entry-boundary cell in localization. The `uppercase_roman_run_followed_by_hyphen_digits` gate requires a maximal uppercase ASCII run, literal ASCII hyphen-minus, a non-empty digit run, and alphanumeric outer boundaries. Its localized range includes the current encoded run and its immediately preceding cell, keeping `F-35`-style routing separate from both parenthesized digits and headword expansions. The `standalone_multi_character_uppercase_roman_word` gate finds maximal ASCII-letter runs of two or more capitals with non-alphanumeric boundaries; a run immediately followed by `(` is excluded so the HCA-style headword itself is not counted by both gates. The `korean_prefixed_closed_allcaps_parenthetical` gate requires an immediately preceding Korean character and a closed body of two or more uppercase ASCII letters. It intentionally contains both acronym annotations (`책임자(COO)`) and scientific formulae (`일산화탄소(CO)`) so their semantic collision remains measurable. The `korean_prefixed_closed_roman_annotation_rule_34_order` gate accepts the narrower rule-34 body grammar after an immediately preceding Korean character and localizes only the current engine's Korean opening-parenthesis cells. This distinguishes the PDF's parenthesis-before-Roman-indicator order from unrelated differences later in the same sentence. The `multi_character_allcaps_roman_runs_joined_by_middle_dot` gate requires two maximal ASCII-letter runs of at least two capitals joined directly by U+00B7, with non-alphanumeric outer boundaries. It records shapes such as `AI·SW` without assigning prose, mathematics, or science semantics. The `roman_run_immediately_before_attached_middle_dot_boundary` gate is output-localized: it requires a maximal ASCII-letter run immediately before U+00B7 and an attached Korean character or ASCII-letter run after it, then searches for that whole current-engine signature in the actual output. It therefore isolates the Roman terminator boundary without treating unrelated middle dots elsewhere in the sentence as causal. The `korean_inline_parenthesized_single_arithmetic_operator` gate requires an immediate `Korean(` + one rule-45 arithmetic operator + `)Korean` span. Unlike broad coexistence traits, it also locates the current engine's emitted structure and counts a mismatch as signature-local only when the sentence's first differing cell falls inside that output range. The `allcaps_roman_run_containing_ou` gate finds maximal, alphanumeric-delimited uppercase ASCII runs containing adjacent `OU`. It locates the independently encoded run signature in the complete current output and counts only first differences inside that signature as localized. The `allcaps_roman_run_containing_st` gate applies the same output-position requirement to adjacent `ST`; it is kept separate because UEB 10.12 makes contraction use depend on how an abbreviation or acronym is pronounced. The `decimal_point_between_ascii_digits` gate finds whitespace-delimited words containing `digit.digit` and reproduces each whole word in a neutral Korean context, so suffixes and punctuation remain part of the current-engine signature. The `compact_numeric_ascii_letter_suffix` gate finds a numeric prefix immediately followed by ASCII letters, includes the immediately preceding output cell as its entry boundary, and retains suffix-specific outcome counts. It intentionally includes both possible rule-69 units and ambiguous variable/identifier forms; membership alone does not assign unit semantics. The `rule69_ascii_unit_before_terminator_skipping_symbol` gate is narrower: it accepts only ASCII unit spellings already supported by rule 69, includes the immediately following rule-33/34 punctuation cell in the localized signature, and does not infer new units. The `tight_triangle_mark_immediately_before_korean` gate requires literal `△한글` with no input space and includes the first following Korean cell in its localized output range, so an observed missing-space difference is measured at the mark boundary. +These are cross-cutting input-only structural cohorts, not new primary classes and not engine routing rules. Candidate selection never changes a case's existing primary class by itself. Only cohort members already classified as `pending_rule_review` form a pending subcluster; exact and other-primary members are controls that retain their existing outcomes. A separate classifier may use independently justified, output-localized PDF evidence, as in the rule-34 three-cell contradiction below. The `uppercase_roman_headword_closed_multiword_parenthetical` gate requires a two-or-more character uppercase ASCII headword immediately followed by a closed parenthesis whose contents are two or more ASCII Roman words separated only by spaces. Because the contents admit only letters and spaces, visible operators, subscript/superscript notation, and nested parentheses are excluded deterministically. The `mixed_roman_korean_word_before_uppercase_headword_expansion` gate further requires a whitespace-delimited preceding word that contains both ASCII Roman and Korean and ends in Korean. It locates the following uppercase headword itself, including its immediately preceding emitted cell, so an earlier Roman entry in the same sentence cannot satisfy the output audit. The `single_capital_followed_by_parenthesized_digits` gate requires a standalone capital, a non-empty closed ASCII-digit parenthetical, and alphanumeric outer boundaries. It likewise includes the emitted entry-boundary cell in localization. The `uppercase_roman_run_followed_by_hyphen_digits` gate requires a maximal uppercase ASCII run, literal ASCII hyphen-minus, a non-empty digit run, and alphanumeric outer boundaries. Its localized range includes the current encoded run and its immediately preceding cell, keeping `F-35`-style routing separate from both parenthesized digits and headword expansions. The `standalone_multi_character_uppercase_roman_word` gate finds maximal ASCII-letter runs of two or more capitals with non-alphanumeric boundaries; a run immediately followed by `(` is excluded so the HCA-style headword itself is not counted by both gates. The `korean_prefixed_closed_allcaps_parenthetical` gate requires an immediately preceding Korean character and a closed body of two or more uppercase ASCII letters. It intentionally contains both acronym annotations (`책임자(COO)`) and scientific formulae (`일산화탄소(CO)`) so their semantic collision remains measurable. The `korean_prefixed_closed_roman_annotation_rule_34_order` gate accepts the narrower rule-34 body grammar after an immediately preceding Korean character and localizes only the current engine's Korean opening-parenthesis cells. This distinguishes the PDF's parenthesis-before-Roman-indicator order from unrelated differences later in the same sentence. The `multi_character_allcaps_roman_runs_joined_by_middle_dot` gate requires two maximal ASCII-letter runs of at least two capitals joined directly by U+00B7, with non-alphanumeric outer boundaries. It records shapes such as `AI·SW` without assigning prose, mathematics, or science semantics. The `roman_run_immediately_before_attached_middle_dot_boundary` gate is output-localized: it requires a maximal ASCII-letter run immediately before U+00B7 and an attached Korean character or ASCII-letter run after it, then searches for that whole current-engine signature in the actual output. It therefore isolates the Roman terminator boundary without treating unrelated middle dots elsewhere in the sentence as causal. The `korean_inline_parenthesized_single_arithmetic_operator` gate requires an immediate `Korean(` + one rule-45 arithmetic operator + `)Korean` span. Unlike broad coexistence traits, it also locates the current engine's emitted structure and counts a mismatch as signature-local only when the sentence's first differing cell falls inside that output range. The `allcaps_roman_run_containing_ou` gate finds maximal, alphanumeric-delimited uppercase ASCII runs containing adjacent `OU`. It locates the independently encoded run signature in the complete current output and counts only first differences inside that signature as localized. The `allcaps_roman_run_containing_st` gate applies the same output-position requirement to adjacent `ST`; it is kept separate because UEB 10.12 makes contraction use depend on how an abbreviation or acronym is pronounced. The `attached_ascii_roman_segments_joined_by_ampersand` gate requires non-empty ASCII-letter segments joined directly by `&`, excludes spaced/Korean/alphanumeric continuations, and localizes only the current output cell immediately before the ampersand through an independently encoded real-input prefix. The `decimal_point_between_ascii_digits` gate finds whitespace-delimited words containing `digit.digit` and reproduces each whole word in a neutral Korean context, so suffixes and punctuation remain part of the current-engine signature. The `compact_numeric_ascii_letter_suffix` gate finds a numeric prefix immediately followed by ASCII letters, includes the immediately preceding output cell as its entry boundary, and retains suffix-specific outcome counts. It intentionally includes both possible rule-69 units and ambiguous variable/identifier forms; membership alone does not assign unit semantics. The `rule69_ascii_unit_before_terminator_skipping_symbol` gate is narrower: it accepts only ASCII unit spellings already supported by rule 69, includes the immediately following rule-33/34 punctuation cell in the localized signature, and does not infer new units. The `tight_triangle_mark_immediately_before_korean` gate requires literal `△한글` with no input space and includes the first following Korean cell in its localized output range, so an observed missing-space difference is measured at the mark boundary. | Cluster | Candidates | Exact | Mismatch | Conflicting-reference cases | |---|---:|---:|---:|---:| | `allcaps_roman_run_beginning_with_pure_letter_shortform` | 4120 | 2425 | 1695 | 0 | | `allcaps_roman_run_containing_ou` | 1816 | 64 | 1752 | 0 | | `allcaps_roman_run_containing_st` | 1479 | 675 | 804 | 0 | +| `attached_ascii_roman_segments_joined_by_ampersand` | 802 | 0 | 802 | 0 | | `closed_roman_parenthetical_after_non_ascii_letter_boundary` | 63959 | 51554 | 12405 | 0 | | `compact_numeric_ascii_letter_suffix` | 2975 | 1855 | 1120 | 0 | | `decimal_point_between_ascii_digits` | 4546 | 3267 | 1279 | 0 | @@ -811,6 +812,68 @@ Representative `mismatch` samples: - first differing cell (zero-based): 53 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +### `attached_ascii_roman_segments_joined_by_ampersand` + +Of the 802 candidates, 776 are the actual `pending_rule_review` subcluster. The other 26 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. + +For this output-signature audit, 802 mismatches were evaluable and 356 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. + +Localized first-difference transitions: + +- `U+2808 ⠈ -> U+2832 ⠲`: 356 + +Mismatch primary-class distribution: + +- `corpus_suspect`: 10 +- `pending_rule_review`: 776 +- `unsupported_character_review`: 16 + +Representative `localized_mismatch` samples: + +- `sentence_01.json` #207: 우리금융지주가 약 7조원의 자본 여력을 등에 업고 인수·합병(M&A) 시장에 출격했다. 벤처캐피탈(VC) 인수를 시작으로 증권사와 보험사까지 인수한다는 계획이다. + - expected: `⠃⠘⠻⠦⠄⠴⠠⠍⠈⠯⠠⠁⠠⠴⠀⠠⠕⠨⠶⠝⠀⠰⠯⠈` + - actual: `⠃⠘⠻⠦⠄⠴⠠⠍⠲⠴⠈⠯⠲⠴⠠⠁⠠⠴⠀⠠⠕⠨⠶⠝` + - first differing cell (zero-based): 59 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_02.json` #782: 천안시는 지난 5월 국토부의 ‘인공지능(AI) 도시계획 연구개발(R&D)’ 기술 시범 적용을 위한 실증 대상 지자체에 선정됐으며, 주관 연구기관인 국토연구원과 업무협약도 체결했다. + - expected: `⠗⠘⠂⠦⠄⠴⠠⠗⠈⠯⠠⠙⠠⠴⠴⠄⠀⠈⠕⠠⠯⠀⠠⠕` + - actual: `⠗⠘⠂⠦⠄⠴⠠⠗⠲⠴⠈⠯⠲⠴⠠⠙⠠⠴⠴⠄⠀⠈⠕⠠` + - first differing cell (zero-based): 70 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_03.json` #191: 한국투자신탁운용의 에이스(ACE) 글로벌브랜드TOP10블룸버그 ETF는 연초 이후 22.6% 상승했다. 명품 기업 루이비통모에헤네시(LVMH), 대표 가치주로 꼽히는 프록터앤드갬블(P&G) 편입 비중이 높은 것이 특징이다. + - expected: `⠢⠘⠮⠦⠄⠴⠠⠏⠈⠯⠠⠛⠠⠴⠀⠙⠡⠕⠃⠀⠘⠕⠨⠍` + - actual: `⠢⠘⠮⠦⠄⠴⠠⠏⠲⠴⠈⠯⠲⠴⠠⠛⠠⠴⠀⠙⠡⠕⠃⠀` + - first differing cell (zero-based): 183 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_04.json` #159: 또 액화천연가스(LNG) 분야 협력을 확대하는 한편 수소환원제철 관련 공동 연구·개발(R&D)을 추진해 유럽연합(EU)의 탄소국경조정세(CBAM)와 같은 글로벌 규제와 자원 무기화에 공동 대응한다는 전략이다. + - expected: `⠗⠘⠂⠦⠄⠴⠠⠗⠈⠯⠠⠙⠠⠴⠮⠀⠰⠍⠨⠟⠚⠗⠀⠩` + - actual: `⠗⠘⠂⠦⠄⠴⠠⠗⠲⠴⠈⠯⠲⠴⠠⠙⠠⠴⠮⠀⠰⠍⠨⠟` + - first differing cell (zero-based): 90 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` + +Representative `mismatch` samples: + +- `sentence_01.json` #207: 우리금융지주가 약 7조원의 자본 여력을 등에 업고 인수·합병(M&A) 시장에 출격했다. 벤처캐피탈(VC) 인수를 시작으로 증권사와 보험사까지 인수한다는 계획이다. + - expected: `⠃⠘⠻⠦⠄⠴⠠⠍⠈⠯⠠⠁⠠⠴⠀⠠⠕⠨⠶⠝⠀⠰⠯⠈` + - actual: `⠃⠘⠻⠦⠄⠴⠠⠍⠲⠴⠈⠯⠲⠴⠠⠁⠠⠴⠀⠠⠕⠨⠶⠝` + - first differing cell (zero-based): 59 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_02.json` #146: KT&G(사장 백복인)가 산업정책연구원(IPS)이 주관하고 윤경 ESG 포럼이 주최하는 ‘윤경 ESG 포럼 CEO 서약식’에 참여해 윤리경영 의지를 다졌다. + - expected: `⠴⠠⠠⠅⠞⠈⠯⠠⠛⠦⠄⠇⠨⠶⠀⠘⠗⠁⠘⠭⠟⠠⠴⠫` + - actual: `⠴⠠⠠⠅⠞⠲⠴⠈⠯⠲⠴⠛⠦⠄⠇⠨⠶⠀⠘⠗⠁⠘⠭⠟` + - first differing cell (zero-based): 5 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_03.json` #191: 한국투자신탁운용의 에이스(ACE) 글로벌브랜드TOP10블룸버그 ETF는 연초 이후 22.6% 상승했다. 명품 기업 루이비통모에헤네시(LVMH), 대표 가치주로 꼽히는 프록터앤드갬블(P&G) 편입 비중이 높은 것이 특징이다. + - expected: `⠢⠘⠮⠦⠄⠴⠠⠏⠈⠯⠠⠛⠠⠴⠀⠙⠡⠕⠃⠀⠘⠕⠨⠍` + - actual: `⠢⠘⠮⠦⠄⠴⠠⠏⠲⠴⠈⠯⠲⠴⠠⠛⠠⠴⠀⠙⠡⠕⠃⠀` + - first differing cell (zero-based): 183 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_04.json` #19: 이날 행사에는 구 대표 외에 권봉석 (주)LG 최고운영책임자(COO), 박일평 LG사이언스파크대표(사장)를 비롯해 각 계열사 최고기술책임자(CTO), 최고데이터책임자(CDO), 최고인사책임자(CHO) 등이 참석해 국내 이공계 R&D 인재 400여명과 만났다. + - expected: `⠁⠕⠢⠨⠦⠄⠴⠠⠠⠉⠕⠕⠠⠴⠐⠀⠘⠁⠕⠂⠙⠻⠀⠴` + - actual: `⠁⠕⠢⠨⠦⠄⠴⠠⠉⠠⠕⠠⠕⠠⠴⠐⠀⠘⠁⠕⠂⠙⠻⠀` + - first differing cell (zero-based): 60 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` + ### `closed_roman_parenthetical_after_non_ascii_letter_boundary` Of the 63959 candidates, 10926 are the actual `pending_rule_review` subcluster. The other 53033 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. @@ -2310,6 +2373,12 @@ Corpus contradictions remain a separate gate: identical inputs with conflicting UEB 10.12.1 (2024 UEB PDF pp.191-192, printed pp.163-164) says not to use a contraction when letters within an abbreviation or acronym are known to be pronounced separately, but to use the contraction in case of doubt. Rule 10.12.2 requires contractions in other abbreviations and acronyms. Consequently an uppercase `ST` surface alone cannot determine whether `s` + `t` or the `st` groupsign is required. The current cohort contains 1479 candidates, 675 exact controls, and 804 mismatches; primary classes remain 755 `pending_rule_review`, 31 `corpus_suspect`, 13 `comparison_method`, and 5 `unsupported_character_review`. Of 804 evaluable mismatches, 443 are localized to the detected current-engine run: 435 `U+280E ⠎ -> U+280C ⠌` and 1 `U+280C ⠌ -> U+280E ⠎`. The target's raw-to-residual count is 448 -> 13, and the reverse is 2 -> 1. Exact controls such as `STAYG`, `KAIST`, `DGIST`, and `POSTECH` coexist with localized mismatches such as `WSTS`, `HUST`, `OST`, and `USTR`; representative shard/index samples are preserved in the cluster table. This lexical/pronunciation distinction cannot be inferred from the input-only spelling, so no engine change or primary reclassification is made. +### Attached Roman segments joined by ampersand + +UEB 3.1.1 (2024 UEB PDF p.51, printed p.23) directly prints `AT&T` and `B&B` with the attached ampersand `⠈⠯` and no mode boundary around it. Korean rule 71 (2024 Korean-rules PDF p.51, printed p.45) assigns the same `⠈⠯` cells, while rule 29 places Roman entry before a Roman section and termination after its last item. The current Korean-context path instead exits before `&`, wraps the information symbol as a separate Roman section, and re-enters for the following letters. + +The baseline cohort contains 802 candidates, 0 corpus-exact members, and 802 mismatches. Existing primary classes are preserved: 776 `pending_rule_review`, 10 `corpus_suspect`, and 16 `unsupported_character_review`. The real-prefix output localizer evaluates all 802 mismatches but assigns only the single cell immediately before `&`; 356 are localized and all 356 are `U+2808 ⠈ -> U+2832 ⠲`. The raw-to-residual target count is 411 -> 53; `U+2832 ⠲ -> U+2808 ⠈` is 0 raw / 0 residual. Official full-encoder controls `AT&T` and `B&B` pass, while spaced `Marks & Spencer`, Korean `가&나`, empty segments, and alphanumeric continuations are excluded controls. With no corpus exact member, no reverse transition, and direct matching rules in both standards, this is a high-confidence general implementation candidate rather than an expected-derived branch. Representative shard/index samples are retained above. + Current uppercase-Roman hyphen-digits measurement: 571 candidates, 189 exact controls, 382 mismatches, 367 members in the actual `pending_rule_review` subcluster, and 261/382 evaluable mismatches whose first difference is inside the target run plus its entry boundary. It remains distinct from parenthesized digits and headword expansions; no engine change is inferred. Current single-capital parenthesized-digits measurement: 1361 candidates, 0 exact controls, 1361 mismatches, 1357 members in the actual `pending_rule_review` subcluster, and 1342/1361 evaluable mismatches whose first difference is inside the target run plus its entry boundary. No engine change is inferred from the ambiguous prose/function surface form. diff --git a/libs/braillify/examples/nikl_corpus_analyze.rs b/libs/braillify/examples/nikl_corpus_analyze.rs index 2459c798..d76ec0b1 100644 --- a/libs/braillify/examples/nikl_corpus_analyze.rs +++ b/libs/braillify/examples/nikl_corpus_analyze.rs @@ -582,6 +582,8 @@ const KOREAN_INLINE_PARENTHESIZED_OPERATOR: &str = const TIGHT_TRIANGLE_BEFORE_KOREAN: &str = "tight_triangle_mark_immediately_before_korean"; const ALLCAPS_ROMAN_RUN_CONTAINING_OU: &str = "allcaps_roman_run_containing_ou"; const ALLCAPS_ROMAN_RUN_CONTAINING_ST: &str = "allcaps_roman_run_containing_st"; +const ATTACHED_ASCII_ROMAN_SEGMENTS_JOINED_BY_AMPERSAND: &str = + "attached_ascii_roman_segments_joined_by_ampersand"; const SINGLE_CAPITAL_PARENTHESIZED_DIGITS: &str = "single_capital_followed_by_parenthesized_digits"; const MIXED_ROMAN_KOREAN_BEFORE_HEADWORD_EXPANSION: &str = "mixed_roman_korean_word_before_uppercase_headword_expansion"; @@ -950,6 +952,52 @@ fn allcaps_roman_runs_containing_st(input: &str) -> Vec { allcaps_roman_runs_containing_pair(input, b"ST") } +/// Finds complete ASCII-letter sequences joined directly by one or more +/// ampersands, such as the UEB §3.1.1 examples `AT&T` and `B&B`. Whitespace, +/// Korean text, empty segments, and alphanumeric outer continuations are +/// excluded so the cohort is an attached Roman-symbol boundary, not broad +/// sentence-level ampersand coexistence. +fn attached_ascii_roman_ampersand_spans(input: &str) -> Vec { + let bytes = input.as_bytes(); + let mut spans = Vec::new(); + let mut cursor = 0usize; + while cursor < bytes.len() { + if !bytes[cursor].is_ascii_alphabetic() { + cursor += input[cursor..] + .chars() + .next() + .expect("cursor must remain on a character boundary") + .len_utf8(); + continue; + } + + let start_byte = cursor; + while bytes + .get(cursor) + .is_some_and(|byte| byte.is_ascii_alphabetic() || *byte == b'&') + { + cursor += 1; + } + let end_byte = cursor; + let run = &input[start_byte..end_byte]; + let previous = input[..start_byte].chars().next_back(); + let next = input[end_byte..].chars().next(); + if run.contains('&') + && run.split('&').all(|segment| { + !segment.is_empty() && segment.bytes().all(|byte| byte.is_ascii_alphabetic()) + }) + && previous.is_none_or(|ch| !ch.is_ascii_alphanumeric()) + && next.is_none_or(|ch| !ch.is_ascii_alphanumeric()) + { + spans.push(InputSpan { + start_byte, + end_byte, + }); + } + } + spans +} + /// Finds maximal pure-uppercase ASCII letter runs whose beginning is itself a /// pure-letter UEB shortform abbreviation. UEB 5.7.2 and 10.9.7-10.9.8 require /// grade 1 both for a complete shortform-shaped letters-sequence (`WD`) and for @@ -1567,6 +1615,41 @@ fn first_difference_in_allcaps_st_run(item: &EncodedCase) -> bool { .any(|range| range.contains(&first_difference)) } +fn attached_roman_ampersand_boundary_ranges( + input: &str, + actual: &str, +) -> Vec> { + let actual_cells = actual.chars().collect::>(); + let mut ranges = BTreeSet::new(); + for span in attached_ascii_roman_ampersand_spans(input) { + for (offset, _) in input[span.start_byte..span.end_byte].match_indices('&') { + let ampersand_byte = span.start_byte + offset; + let Ok(prefix) = braillify::encode_to_unicode(&input[..ampersand_byte]) else { + continue; + }; + let prefix_cells = prefix.chars().collect::>(); + if actual_cells.starts_with(&prefix_cells) && !prefix_cells.is_empty() { + let boundary = prefix_cells.len() - 1; + ranges.insert((boundary, boundary + 1)); + } + } + } + ranges.into_iter().map(|(start, end)| start..end).collect() +} + +fn first_difference_in_attached_roman_ampersand(item: &EncodedCase) -> bool { + let Ok(actual) = &item.actual else { + return false; + }; + if actual == &item.located.case.unicode { + return false; + } + let first_difference = first_difference_cell(&item.located.case.unicode, actual); + attached_roman_ampersand_boundary_ranges(&item.located.case.input, actual) + .into_iter() + .any(|range| range.contains(&first_difference)) +} + /// Finds a standalone single capital immediately followed by a non-empty, /// closed ASCII-digit parenthetical, such as `A(14)`. The span is deliberately /// semantic-neutral: prose labels and mathematical function notation can share @@ -1799,10 +1882,15 @@ fn first_difference_claimed_before_allcaps_st(item: &EncodedCase) -> bool { ) } -fn first_difference_claimed_by_localized_cohort(item: &EncodedCase) -> bool { +fn first_difference_claimed_before_attached_roman_ampersand(item: &EncodedCase) -> bool { first_difference_claimed_before_allcaps_st(item) || first_difference_in_allcaps_st_run(item) } +fn first_difference_claimed_by_localized_cohort(item: &EncodedCase) -> bool { + first_difference_claimed_before_attached_roman_ampersand(item) + || first_difference_in_attached_roman_ampersand(item) +} + /// Input-only candidate gate for acronym expansions such as /// `HCA(Home Connectivity Alliance)`. /// @@ -2406,6 +2494,10 @@ fn analyze( ALLCAPS_ROMAN_RUN_CONTAINING_ST.to_string(), PendingRuleReviewClusterStats::default(), ), + ( + ATTACHED_ASCII_ROMAN_SEGMENTS_JOINED_BY_AMPERSAND.to_string(), + PendingRuleReviewClusterStats::default(), + ), ( ALLCAPS_ROMAN_MIDDLE_DOT_RUNS.to_string(), PendingRuleReviewClusterStats::default(), @@ -2586,6 +2678,15 @@ fn analyze( ), true, ), + ( + ATTACHED_ASCII_ROMAN_SEGMENTS_JOINED_BY_AMPERSAND, + !attached_ascii_roman_ampersand_spans(&item.located.case.input).is_empty(), + Some( + !first_difference_claimed_before_attached_roman_ampersand(item) + && first_difference_in_attached_roman_ampersand(item), + ), + true, + ), ( ALLCAPS_ROMAN_MIDDLE_DOT_RUNS, has_allcaps_roman_middle_dot_runs(&item.located.case.input), @@ -3206,6 +3307,10 @@ fn markdown(report: &AnalysisReport) -> String { signature as localized. The `allcaps_roman_run_containing_st` gate applies the same \ output-position requirement to adjacent `ST`; it is kept separate because UEB 10.12 \ makes contraction use depend on how an abbreviation or acronym is pronounced. The \ + `attached_ascii_roman_segments_joined_by_ampersand` gate requires non-empty ASCII-letter \ + segments joined directly by `&`, excludes spaced/Korean/alphanumeric continuations, and \ + localizes only the current output cell immediately before the ampersand through an \ + independently encoded real-input prefix. The \ `decimal_point_between_ascii_digits` gate finds \ whitespace-delimited words containing `digit.digit` and reproduces each whole word in a \ neutral Korean context, so suffixes and punctuation remain part of the current-engine \ @@ -3868,6 +3973,75 @@ fn markdown(report: &AnalysisReport) -> String { residual_transition(reverse), )); } + if let Some(stats) = report + .pending_rule_review_clusters + .get(ATTACHED_ASCII_ROMAN_SEGMENTS_JOINED_BY_AMPERSAND) + { + let primary_count = |name: &str| { + stats + .mismatch_primary_classes + .get(name) + .copied() + .unwrap_or(0) + }; + let transition_count = |name: &str| { + stats + .first_difference_in_output_signature_transitions + .get(name) + .copied() + .unwrap_or(0) + }; + let raw_count = |name: &str| { + report + .pending_first_difference_cell_transitions + .get(name) + .map(|transition| transition.cases) + .unwrap_or(0) + }; + let residual_count = |name: &str| { + report + .pending_first_difference_transitions_after_localized_cohorts + .get(name) + .map(|transition| transition.cases) + .unwrap_or(0) + }; + let target = "U+2808 ⠈ -> U+2832 ⠲"; + let reverse = "U+2832 ⠲ -> U+2808 ⠈"; + text.push_str(&format!( + "\n### Attached Roman segments joined by ampersand\n\n\ + UEB 3.1.1 (2024 UEB PDF p.51, printed p.23) directly prints `AT&T` and \ + `B&B` with the attached ampersand `⠈⠯` and no mode boundary around it. Korean \ + rule 71 (2024 Korean-rules PDF p.51, printed p.45) assigns the same `⠈⠯` \ + cells, while rule 29 places Roman entry before a Roman section and termination after \ + its last item. The current Korean-context path instead exits before `&`, wraps the \ + information symbol as a separate Roman section, and re-enters for the following \ + letters.\n\n\ + The baseline cohort contains {} candidates, {} corpus-exact members, and {} \ + mismatches. Existing primary classes are preserved: {} `pending_rule_review`, {} \ + `corpus_suspect`, and {} `unsupported_character_review`. The real-prefix output \ + localizer evaluates all {} mismatches but assigns only the single cell immediately \ + before `&`; {} are localized and all {} are `{target}`. The raw-to-residual target \ + count is {} -> {}; `{reverse}` is {} raw / {} residual. Official full-encoder \ + controls `AT&T` and `B&B` pass, while spaced `Marks & Spencer`, Korean `가&나`, empty \ + segments, and alphanumeric continuations are excluded controls. With no corpus exact \ + member, no reverse transition, and direct matching rules in both standards, this is a \ + high-confidence general implementation candidate rather than an expected-derived \ + branch. Representative shard/index samples are retained above.\n", + stats.candidates, + stats.exact, + stats.mismatch, + primary_count("pending_rule_review"), + primary_count("corpus_suspect"), + primary_count("unsupported_character_review"), + stats.output_signature_mismatches_evaluated, + stats.first_difference_in_output_signature, + transition_count(target), + raw_count(target), + residual_count(target), + raw_count(reverse), + residual_count(reverse), + )); + } if let Some(stats) = report .pending_rule_review_clusters .get(UPPERCASE_ROMAN_HYPHEN_DIGITS) @@ -4954,6 +5128,35 @@ mod tests { assert_eq!(actual, expected); } + #[rstest::rstest] + #[case::common_initialisms("M&A P&G R&D", vec!["M&A", "P&G", "R&D"])] + #[case::ueb_examples("AT&T B&B", vec!["AT&T", "B&B"])] + #[case::multiple_ampersands("A&B&C", vec!["A&B&C"])] + #[case::spaced_symbol("Marks & Spencer", vec![])] + #[case::korean_segments("가&나", vec![])] + #[case::empty_segment("A&&B", vec![])] + #[case::digit_continuation("R&D2", vec![])] + fn detects_attached_ascii_roman_ampersand_spans( + #[case] input: &str, + #[case] expected: Vec<&str>, + ) { + let actual = attached_ascii_roman_ampersand_spans(input) + .into_iter() + .map(|span| &input[span.start_byte..span.end_byte]) + .collect::>(); + assert_eq!(actual, expected); + } + + #[rstest::rstest] + #[case::at_and_t("AT&T", "⠠⠠⠁⠞⠈⠯⠠⠞")] + #[case::b_and_b("B&B", "⠠⠃⠈⠯⠠⠃")] + fn full_encoder_matches_ueb_3_1_1_ampersand_examples( + #[case] input: &str, + #[case] expected: &str, + ) { + assert_eq!(braillify::encode_to_unicode(input).as_deref(), Ok(expected)); + } + #[rstest::rstest] #[case::whole_shortform("가(WD) 나", vec!["WD"])] #[case::longer_prefixes("PDS LLM GDP", vec!["PDS", "LLM", "GDP"])] @@ -5199,6 +5402,20 @@ mod tests { assert!(ranges[0].end <= actual.chars().count()); } + #[rstest::rstest] + #[case::merger("인수·합병(M&A) 시장")] + #[case::brand("브랜드(P&G) 편입")] + #[case::research("연구·개발(R&D)을 추진")] + fn locates_attached_roman_ampersand_in_korean_output(#[case] input: &str) { + let actual = braillify::encode_to_unicode(input).expect("ampersand probe must encode"); + let ranges = attached_roman_ampersand_boundary_ranges(input, &actual); + + assert_eq!(ranges.len(), 1); + assert_eq!(ranges[0].end - ranges[0].start, 1); + assert_eq!(actual.chars().nth(ranges[0].start), Some('⠲')); + assert!(ranges[0].end <= actual.chars().count()); + } + #[rstest::rstest] #[case::standalone("새로운 DRX 브랜드", true)] #[case::inside_parentheses("엠디(MD), SNS", true)] From 31ea2d0626c09254664d4b0725378b288891c8d1 Mon Sep 17 00:00:00 2001 From: devfive Date: Tue, 1 Sep 2026 13:06:58 +0900 Subject: [PATCH 35/77] Keep attached Roman ampersands in one section --- docs/corpus-analysis/NIKL_2025_V1.md | 379 +++++++++--------- .../braillify/examples/nikl_corpus_analyze.rs | 58 ++- libs/braillify/src/english_logic.rs | 72 ++++ libs/braillify/src/rules/korean/rule_71.rs | 46 +++ 4 files changed, 347 insertions(+), 208 deletions(-) diff --git a/docs/corpus-analysis/NIKL_2025_V1.md b/docs/corpus-analysis/NIKL_2025_V1.md index 93249b52..ee039004 100644 --- a/docs/corpus-analysis/NIKL_2025_V1.md +++ b/docs/corpus-analysis/NIKL_2025_V1.md @@ -7,9 +7,9 @@ | Metric | Count | |---|---:| | Total | 83528 | -| Exact | 67442 | -| Mismatch | 16086 | -| Exact accuracy | 80.74% | +| Exact | 67715 | +| Mismatch | 15813 | +| Exact accuracy | 81.07% | | Duplicate records | 0 | | Inputs with conflicting references | 0 | @@ -19,21 +19,21 @@ Primary classes are evidence gates, not permissions to change the engine. `imple | Primary class | Count | |---|---:| -| `comparison_method` | 290 | +| `comparison_method` | 293 | | `corpus_suspect` | 1099 | -| `exact` | 67442 | -| `pending_rule_review` | 14517 | -| `unsupported_character_review` | 180 | +| `exact` | 67715 | +| `pending_rule_review` | 14243 | +| `unsupported_character_review` | 178 | | Reproducible reason | Count | |---|---:| -| `exact` | 67442 | -| `foreign_text_rule_review` | 14342 | -| `nfkc_input_equivalent` | 290 | +| `exact` | 67715 | +| `foreign_text_rule_review` | 14068 | +| `nfkc_input_equivalent` | 293 | | `number_rule_review` | 167 | | `punctuation_rule_review` | 8 | | `rule34_roman_indicator_before_opening_parenthesis` | 1099 | -| `unsupported_character_review` | 180 | +| `unsupported_character_review` | 178 | ## Pending first-difference cell transitions @@ -41,26 +41,26 @@ This ranking is a diagnostic selector, not an implementation rule. It counts onl | Rank | Expected → actual first cell | Cases | |---:|---|---:| -| 1 | `U+2834 ⠴ -> U+2800 ⠀` | 2149 | -| 2 | `U+2815 ⠕ -> U+2833 ⠳` | 1325 | -| 3 | `U+2830 ⠰ -> U+2820 ⠠` | 855 | -| 4 | `U+2810 ⠐ -> U+2832 ⠲` | 648 | +| 1 | `U+2834 ⠴ -> U+2800 ⠀` | 2152 | +| 2 | `U+2815 ⠕ -> U+2833 ⠳` | 1332 | +| 3 | `U+2830 ⠰ -> U+2820 ⠠` | 858 | +| 4 | `U+2810 ⠐ -> U+2832 ⠲` | 655 | | 5 | `U+2826 ⠦ -> U+2834 ⠴` | 461 | | 6 | `U+280E ⠎ -> U+280C ⠌` | 448 | -| 7 | `U+2826 ⠦ -> U+2800 ⠀` | 430 | -| 8 | `U+2808 ⠈ -> U+2832 ⠲` | 411 | -| 9 | `U+2801 ⠁ -> U+281C ⠜` | 393 | -| 10 | `U+2800 ⠀ -> U+2807 ⠇` | 362 | -| 11 | `U+283C ⠼ -> U+2800 ⠀` | 358 | -| 12 | `U+2834 ⠴ -> U+2830 ⠰` | 335 | -| 13 | `U+2811 ⠑ -> U+282B ⠫` | 326 | -| 14 | `U+2832 ⠲ -> U+2838 ⠸` | 281 | -| 15 | `U+2800 ⠀ -> U+2832 ⠲` | 258 | -| 16 | `U+2820 ⠠ -> U+2830 ⠰` | 230 | -| 17 | `U+2810 ⠐ -> U+2802 ⠂` | 210 | -| 18 | `U+2817 ⠗ -> U+2820 ⠠` | 163 | -| 19 | `U+2820 ⠠ -> U+2809 ⠉` | 160 | -| 20 | `U+2826 ⠦ -> U+2810 ⠐` | 160 | +| 7 | `U+2826 ⠦ -> U+2800 ⠀` | 434 | +| 8 | `U+2801 ⠁ -> U+281C ⠜` | 393 | +| 9 | `U+2800 ⠀ -> U+2807 ⠇` | 362 | +| 10 | `U+283C ⠼ -> U+2800 ⠀` | 358 | +| 11 | `U+2834 ⠴ -> U+2830 ⠰` | 336 | +| 12 | `U+2811 ⠑ -> U+282B ⠫` | 326 | +| 13 | `U+2832 ⠲ -> U+2838 ⠸` | 290 | +| 14 | `U+2800 ⠀ -> U+2832 ⠲` | 258 | +| 15 | `U+2820 ⠠ -> U+2830 ⠰` | 231 | +| 16 | `U+2810 ⠐ -> U+2802 ⠂` | 211 | +| 17 | `U+2817 ⠗ -> U+2820 ⠠` | 165 | +| 18 | `U+2826 ⠦ -> U+2810 ⠐` | 164 | +| 19 | `U+2820 ⠠ -> U+2809 ⠉` | 162 | +| 20 | `U+281B ⠛ -> U+2823 ⠣` | 144 | ### `U+2834 ⠴ -> U+2800 ⠀` @@ -223,29 +223,6 @@ This ranking is a diagnostic selector, not an implementation rule. It counts onl - first differing cell (zero-based): 228 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -### `U+2808 ⠈ -> U+2832 ⠲` - -- `sentence_01.json` #207: 우리금융지주가 약 7조원의 자본 여력을 등에 업고 인수·합병(M&A) 시장에 출격했다. 벤처캐피탈(VC) 인수를 시작으로 증권사와 보험사까지 인수한다는 계획이다. - - expected: `⠃⠘⠻⠦⠄⠴⠠⠍⠈⠯⠠⠁⠠⠴⠀⠠⠕⠨⠶⠝⠀⠰⠯⠈` - - actual: `⠃⠘⠻⠦⠄⠴⠠⠍⠲⠴⠈⠯⠲⠴⠠⠁⠠⠴⠀⠠⠕⠨⠶⠝` - - first differing cell (zero-based): 59 - - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_02.json` #146: KT&G(사장 백복인)가 산업정책연구원(IPS)이 주관하고 윤경 ESG 포럼이 주최하는 ‘윤경 ESG 포럼 CEO 서약식’에 참여해 윤리경영 의지를 다졌다. - - expected: `⠴⠠⠠⠅⠞⠈⠯⠠⠛⠦⠄⠇⠨⠶⠀⠘⠗⠁⠘⠭⠟⠠⠴⠫` - - actual: `⠴⠠⠠⠅⠞⠲⠴⠈⠯⠲⠴⠛⠦⠄⠇⠨⠶⠀⠘⠗⠁⠘⠭⠟` - - first differing cell (zero-based): 5 - - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_03.json` #191: 한국투자신탁운용의 에이스(ACE) 글로벌브랜드TOP10블룸버그 ETF는 연초 이후 22.6% 상승했다. 명품 기업 루이비통모에헤네시(LVMH), 대표 가치주로 꼽히는 프록터앤드갬블(P&G) 편입 비중이 높은 것이 특징이다. - - expected: `⠢⠘⠮⠦⠄⠴⠠⠏⠈⠯⠠⠛⠠⠴⠀⠙⠡⠕⠃⠀⠘⠕⠨⠍` - - actual: `⠢⠘⠮⠦⠄⠴⠠⠏⠲⠴⠈⠯⠲⠴⠠⠛⠠⠴⠀⠙⠡⠕⠃⠀` - - first differing cell (zero-based): 183 - - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_04.json` #159: 또 액화천연가스(LNG) 분야 협력을 확대하는 한편 수소환원제철 관련 공동 연구·개발(R&D)을 추진해 유럽연합(EU)의 탄소국경조정세(CBAM)와 같은 글로벌 규제와 자원 무기화에 공동 대응한다는 전략이다. - - expected: `⠗⠘⠂⠦⠄⠴⠠⠗⠈⠯⠠⠙⠠⠴⠮⠀⠰⠍⠨⠟⠚⠗⠀⠩` - - actual: `⠗⠘⠂⠦⠄⠴⠠⠗⠲⠴⠈⠯⠲⠴⠠⠙⠠⠴⠮⠀⠰⠍⠨⠟` - - first differing cell (zero-based): 90 - - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` - ### `U+2801 ⠁ -> U+281C ⠜` - `sentence_01.json` #142: ‘메타버스존’에서는 증강현실(AR)·가상현실(VR)에 적용되는 혁신기술이 소개된다. 글라스를 착용하고 최첨단 3D 센싱모듈이 구현하는 가상현실을 관람객이 직접 체험할 수 있도록 했다. @@ -292,6 +269,29 @@ This ranking is a diagnostic selector, not an implementation rule. It counts onl - first differing cell (zero-based): 32 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +### `U+283C ⠼ -> U+2800 ⠀` + +- `sentence_01.json` #426: LG에너지솔루션은 혼다에 앞서 미국 제너럴모터스(GM)과의 배터리 합작법인 ‘얼티엄셀즈’를 설립해 투자를 진행 중이다. 이를 통해 북미 생산 능력을 올해 13GWh 규모에서 2025년 255GWh까지 확대할 예정이다. + - expected: `⠁⠮⠀⠥⠂⠚⠗⠀⠼⠁⠉⠴⠠⠛⠠⠺⠓⠲⠀⠈⠩⠑⠥⠝` + - actual: `⠁⠮⠀⠥⠂⠚⠗⠀⠀⠼⠁⠉⠠⠠⠛⠺⠓⠀⠀⠈⠩⠑⠥⠝` + - first differing cell (zero-based): 156 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_02.json` #893: 그러나, 이 아파트들은 금강 제1철교 반경 50~800m 내에 위치해 있어 철도소음 영향을 많이 받고 있다. 특히, 금강센트럴파크서희스타힐스는 야간소음이 67dB로 기준치(60dB)를 초과하고 있다. + - expected: `⠈⠬⠀⠘⠒⠈⠻⠀⠼⠑⠚⠈⠔⠼⠓⠚⠚⠴⠍⠲⠀⠉⠗⠝` + - actual: `⠈⠬⠀⠘⠒⠈⠻⠀⠀⠼⠑⠚⠈⠔⠼⠓⠚⠚⠍⠀⠀⠉⠗⠝` + - first differing cell (zero-based): 34 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_03.json` #59: 이들 중 A씨(31) 등 2명은 지난 3월 25일 김해공항으로 필로폰 968g 등을 팬티 속에 숨겨 입국한 혐의를 받고 있다. + - expected: `⠙⠕⠂⠐⠥⠙⠷⠀⠼⠊⠋⠓⠴⠛⠲⠀⠊⠪⠶⠮⠀⠙⠗⠒` + - actual: `⠙⠕⠂⠐⠥⠙⠷⠀⠀⠼⠊⠋⠓⠛⠀⠀⠊⠪⠶⠮⠀⠙⠗⠒` + - first differing cell (zero-based): 71 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_04.json` #538: 중국 전기차 제조업체 비야디(BYD)가 6일 1t 전기트럭 ‘T4K(티포케이)’을 국내에 출시했다. BYD는 전 세계 1위 전기차 업체다. + - expected: `⠴⠫⠀⠼⠋⠕⠂⠀⠼⠁⠴⠞⠲⠀⠨⠾⠈⠕⠓⠪⠐⠹⠀⠠` + - actual: `⠴⠫⠀⠼⠋⠕⠂⠀⠀⠼⠁⠞⠀⠀⠨⠾⠈⠕⠓⠪⠐⠹⠀⠠` + - first differing cell (zero-based): 45 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` + ## Residual first-difference transitions after localized cohorts This ranking removes only cases whose first difference is inside an existing output-localized cohort. Broad input-only traits are not exclusion masks. The residual table therefore prioritizes new causes without hiding a mismatch merely because an unrelated structure coexists elsewhere in its sentence. @@ -299,23 +299,23 @@ This ranking removes only cases whose first difference is inside an existing out | Rank | Expected → actual first cell | Residual cases | |---:|---|---:| | 1 | `U+2801 ⠁ -> U+281C ⠜` | 390 | -| 2 | `U+2834 ⠴ -> U+2830 ⠰` | 332 | +| 2 | `U+2834 ⠴ -> U+2830 ⠰` | 333 | | 3 | `U+2811 ⠑ -> U+282B ⠫` | 325 | -| 4 | `U+2810 ⠐ -> U+2832 ⠲` | 290 | -| 5 | `U+2832 ⠲ -> U+2838 ⠸` | 281 | -| 6 | `U+2834 ⠴ -> U+2800 ⠀` | 258 | +| 4 | `U+2810 ⠐ -> U+2832 ⠲` | 297 | +| 5 | `U+2832 ⠲ -> U+2838 ⠸` | 290 | +| 6 | `U+2834 ⠴ -> U+2800 ⠀` | 261 | | 7 | `U+2800 ⠀ -> U+2832 ⠲` | 227 | -| 8 | `U+2826 ⠦ -> U+2800 ⠀` | 219 | -| 9 | `U+2810 ⠐ -> U+2802 ⠂` | 210 | -| 10 | `U+2817 ⠗ -> U+2820 ⠠` | 163 | -| 11 | `U+2820 ⠠ -> U+2809 ⠉` | 160 | +| 8 | `U+2826 ⠦ -> U+2800 ⠀` | 223 | +| 9 | `U+2810 ⠐ -> U+2802 ⠂` | 211 | +| 10 | `U+2817 ⠗ -> U+2820 ⠠` | 165 | +| 11 | `U+2820 ⠠ -> U+2809 ⠉` | 162 | | 12 | `U+283C ⠼ -> U+2800 ⠀` | 153 | -| 13 | `U+281B ⠛ -> U+2823 ⠣` | 144 | -| 14 | `U+2826 ⠦ -> U+2810 ⠐` | 144 | +| 13 | `U+2826 ⠦ -> U+2810 ⠐` | 147 | +| 14 | `U+281B ⠛ -> U+2823 ⠣` | 144 | | 15 | `U+2810 ⠐ -> U+2800 ⠀` | 130 | | 16 | `U+2811 ⠑ -> U+283B ⠻` | 101 | | 17 | `U+2810 ⠐ -> U+2814 ⠔` | 98 | -| 18 | `U+280E ⠎ -> U+2820 ⠠` | 91 | +| 18 | `U+280E ⠎ -> U+2820 ⠠` | 92 | | 19 | `U+2804 ⠄ -> U+2834 ⠴` | 86 | | 20 | `U+2803 ⠃ -> U+2820 ⠠` | 85 | @@ -530,22 +530,22 @@ This ranking removes only cases whose first difference is inside an existing out - `sentence_01.json` #1294: 한편 엔씨소프트는 오픈형 R&D 개발 문화 ‘엔씽(NCing)’의 일환으로 개발 중인 다양한 장르의 신규 프로젝트를 공개하고 있다. - expected: `⠥⠙⠵⠚⠻⠀⠴⠠⠗⠈⠯⠠⠙⠲⠀⠈⠗⠘⠂⠀⠑⠛⠚⠧` - - actual: `⠥⠙⠵⠚⠻⠀⠴⠠⠠⠗⠲⠴⠈⠯⠲⠴⠙⠲⠀⠈⠗⠘⠂⠀` + - actual: `⠥⠙⠵⠚⠻⠀⠴⠠⠠⠗⠈⠯⠙⠲⠀⠈⠗⠘⠂⠀⠑⠛⠚⠧` - first differing cell (zero-based): 27 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` - `sentence_02.json` #226: 대한민국 드론·UAM 박람회는 UAM(도심항공교통) 등 차세대 국토교통 R&D와 주요 지자체의 관련 사업을 살펴볼 수 있는 의미 있는 행사로 국토교통부와 인천광역시가 공동 주최한다. - expected: `⠥⠈⠬⠓⠿⠀⠴⠠⠗⠈⠯⠠⠙⠲⠧⠀⠨⠍⠬⠀⠨⠕⠨⠰` - - actual: `⠥⠈⠬⠓⠿⠀⠴⠠⠠⠗⠲⠴⠈⠯⠲⠴⠙⠲⠧⠀⠨⠍⠬⠀` + - actual: `⠥⠈⠬⠓⠿⠀⠴⠠⠠⠗⠈⠯⠙⠲⠧⠀⠨⠍⠬⠀⠨⠕⠨⠰` - first differing cell (zero-based): 81 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` - `sentence_03.json` #647: 온유는 지난 3월 6일 첫 정규 앨범 ‘써클(Circle)’로 컴백한다. 이번 앨범은 몽환적인 R&B 장르의 타이틀곡 ‘O(Circle)’(써클)을 비롯한 다채로운 분위기의 10곡으로 구성됐다. - expected: `⠧⠒⠨⠹⠟⠀⠴⠠⠗⠈⠯⠠⠃⠲⠀⠨⠶⠐⠪⠺⠀⠓⠣⠕` - - actual: `⠧⠒⠨⠹⠟⠀⠴⠠⠠⠗⠲⠴⠈⠯⠲⠴⠃⠲⠀⠨⠶⠐⠪⠺` + - actual: `⠧⠒⠨⠹⠟⠀⠴⠠⠠⠗⠈⠯⠃⠲⠀⠨⠶⠐⠪⠺⠀⠓⠣⠕` - first differing cell (zero-based): 92 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` - `sentence_04.json` #2491: 앞서 정보통신기술(ICT) 분야를 담당하는 박윤규 과기정통부 2차관도 지난 1일 산하 정보통신정책실·네트워크정책실 소속 실·국장을 대상으로 간부회의를 소집해 ICT 분야 R&D 예산으로 잡힌 사업들을 원점에서 재검토하도록 했다. - expected: `⠲⠀⠘⠛⠜⠀⠴⠠⠗⠈⠯⠠⠙⠲⠀⠌⠇⠒⠪⠐⠥⠀⠨⠃` - - actual: `⠲⠀⠘⠛⠜⠀⠴⠠⠠⠗⠲⠴⠈⠯⠲⠴⠙⠲⠀⠌⠇⠒⠪⠐` + - actual: `⠲⠀⠘⠛⠜⠀⠴⠠⠠⠗⠈⠯⠙⠲⠀⠌⠇⠒⠪⠐⠥⠀⠨⠃` - first differing cell (zero-based): 175 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` @@ -555,49 +555,49 @@ These are cross-cutting input-only structural cohorts, not new primary classes a | Cluster | Candidates | Exact | Mismatch | Conflicting-reference cases | |---|---:|---:|---:|---:| -| `allcaps_roman_run_beginning_with_pure_letter_shortform` | 4120 | 2425 | 1695 | 0 | +| `allcaps_roman_run_beginning_with_pure_letter_shortform` | 4120 | 2441 | 1679 | 0 | | `allcaps_roman_run_containing_ou` | 1816 | 64 | 1752 | 0 | -| `allcaps_roman_run_containing_st` | 1479 | 675 | 804 | 0 | -| `attached_ascii_roman_segments_joined_by_ampersand` | 802 | 0 | 802 | 0 | -| `closed_roman_parenthetical_after_non_ascii_letter_boundary` | 63959 | 51554 | 12405 | 0 | -| `compact_numeric_ascii_letter_suffix` | 2975 | 1855 | 1120 | 0 | -| `decimal_point_between_ascii_digits` | 4546 | 3267 | 1279 | 0 | +| `allcaps_roman_run_containing_st` | 1479 | 678 | 801 | 0 | +| `attached_ascii_roman_segments_joined_by_ampersand` | 802 | 273 | 529 | 0 | +| `closed_roman_parenthetical_after_non_ascii_letter_boundary` | 63959 | 51806 | 12153 | 0 | +| `compact_numeric_ascii_letter_suffix` | 2975 | 1859 | 1116 | 0 | +| `decimal_point_between_ascii_digits` | 4546 | 3351 | 1195 | 0 | | `korean_inline_parenthesized_single_arithmetic_operator` | 23 | 17 | 6 | 0 | -| `korean_prefixed_closed_allcaps_parenthetical` | 54492 | 44483 | 10009 | 0 | -| `korean_prefixed_closed_roman_annotation_rule_34_order` | 64382 | 52156 | 12226 | 0 | +| `korean_prefixed_closed_allcaps_parenthetical` | 54492 | 44713 | 9779 | 0 | +| `korean_prefixed_closed_roman_annotation_rule_34_order` | 64382 | 52408 | 11974 | 0 | | `korean_prefixed_roman_parenthetical_followed_by_allcaps_hyphen_suffix` | 11 | 0 | 11 | 0 | | `mixed_roman_korean_word_before_uppercase_headword_expansion` | 10 | 0 | 10 | 0 | | `multi_character_allcaps_roman_runs_joined_by_middle_dot` | 97 | 0 | 97 | 0 | | `pure_allcaps_segment_before_hyphen_and_multi_allcaps_segment_after` | 448 | 127 | 321 | 0 | | `roman_hyphenated_word_after_whitespace_following_korean_word` | 361 | 0 | 361 | 0 | -| `roman_parenthetical_headword_after_whitespace_following_korean_word` | 4695 | 3358 | 1337 | 0 | +| `roman_parenthetical_headword_after_whitespace_following_korean_word` | 4695 | 3363 | 1332 | 0 | | `roman_run_immediately_before_attached_middle_dot_boundary` | 577 | 0 | 577 | 0 | | `rule69_ascii_unit_before_terminator_skipping_symbol` | 440 | 336 | 104 | 0 | | `single_capital_followed_by_parenthesized_digits` | 1361 | 0 | 1361 | 0 | -| `standalone_multi_character_uppercase_roman_word` | 62411 | 49811 | 12600 | 0 | +| `standalone_multi_character_uppercase_roman_word` | 62411 | 50059 | 12352 | 0 | | `tight_triangle_mark_immediately_before_korean` | 377 | 0 | 377 | 0 | -| `uppercase_ascii_run_immediately_after_digit_in_roman_sequence` | 1896 | 1092 | 804 | 0 | +| `uppercase_ascii_run_immediately_after_digit_in_roman_sequence` | 1896 | 1094 | 802 | 0 | | `uppercase_ascii_run_immediately_after_hyphen_in_roman_sequence` | 952 | 160 | 792 | 0 | | `uppercase_roman_headword_closed_multiword_parenthetical` | 175 | 16 | 159 | 0 | -| `uppercase_roman_run_followed_by_hyphen_digits` | 571 | 189 | 382 | 0 | -| `uppercase_word_after_whitespace_continuing_ascii_roman_text` | 1729 | 661 | 1068 | 0 | +| `uppercase_roman_run_followed_by_hyphen_digits` | 571 | 190 | 381 | 0 | +| `uppercase_word_after_whitespace_continuing_ascii_roman_text` | 1729 | 664 | 1065 | 0 | ### `allcaps_roman_run_beginning_with_pure_letter_shortform` -Of the 4120 candidates, 1626 are the actual `pending_rule_review` subcluster. The other 2494 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 4120 candidates, 1610 are the actual `pending_rule_review` subcluster. The other 2510 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. -For this output-signature audit, 1695 mismatches were evaluable and 823 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. +For this output-signature audit, 1679 mismatches were evaluable and 826 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. Localized first-difference transitions: -- `U+2830 ⠰ -> U+2820 ⠠`: 781 -- `U+2820 ⠠ -> U+2830 ⠰`: 42 +- `U+2830 ⠰ -> U+2820 ⠠`: 783 +- `U+2820 ⠠ -> U+2830 ⠰`: 43 Mismatch primary-class distribution: - `comparison_method`: 11 - `corpus_suspect`: 48 -- `pending_rule_review`: 1626 +- `pending_rule_review`: 1610 - `unsupported_character_review`: 10 Representative `exact` samples: @@ -669,11 +669,11 @@ Representative `mismatch` samples: Of the 1816 candidates, 1666 are the actual `pending_rule_review` subcluster. The other 150 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. -For this output-signature audit, 1752 mismatches were evaluable and 1321 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. +For this output-signature audit, 1752 mismatches were evaluable and 1328 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. Localized first-difference transitions: -- `U+2815 ⠕ -> U+2833 ⠳`: 1320 +- `U+2815 ⠕ -> U+2833 ⠳`: 1327 - `U+2810 ⠐ -> U+283D ⠽`: 1 Mismatch primary-class distribution: @@ -728,9 +728,9 @@ Representative `mismatch` samples: ### `allcaps_roman_run_containing_st` -Of the 1479 candidates, 755 are the actual `pending_rule_review` subcluster. The other 724 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 1479 candidates, 752 are the actual `pending_rule_review` subcluster. The other 727 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. -For this output-signature audit, 804 mismatches were evaluable and 443 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. +For this output-signature audit, 801 mismatches were evaluable and 443 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. Localized first-difference transitions: @@ -744,7 +744,7 @@ Mismatch primary-class distribution: - `comparison_method`: 13 - `corpus_suspect`: 31 -- `pending_rule_review`: 755 +- `pending_rule_review`: 752 - `unsupported_character_review`: 5 Representative `exact` samples: @@ -814,59 +814,52 @@ Representative `mismatch` samples: ### `attached_ascii_roman_segments_joined_by_ampersand` -Of the 802 candidates, 776 are the actual `pending_rule_review` subcluster. The other 26 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. - -For this output-signature audit, 802 mismatches were evaluable and 356 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. +Of the 802 candidates, 502 are the actual `pending_rule_review` subcluster. The other 300 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. -Localized first-difference transitions: - -- `U+2808 ⠈ -> U+2832 ⠲`: 356 +For this output-signature audit, 529 mismatches were evaluable and 0 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. Mismatch primary-class distribution: +- `comparison_method`: 3 - `corpus_suspect`: 10 -- `pending_rule_review`: 776 -- `unsupported_character_review`: 16 +- `pending_rule_review`: 502 +- `unsupported_character_review`: 14 -Representative `localized_mismatch` samples: +Representative `exact` samples: - `sentence_01.json` #207: 우리금융지주가 약 7조원의 자본 여력을 등에 업고 인수·합병(M&A) 시장에 출격했다. 벤처캐피탈(VC) 인수를 시작으로 증권사와 보험사까지 인수한다는 계획이다. - - expected: `⠃⠘⠻⠦⠄⠴⠠⠍⠈⠯⠠⠁⠠⠴⠀⠠⠕⠨⠶⠝⠀⠰⠯⠈` - - actual: `⠃⠘⠻⠦⠄⠴⠠⠍⠲⠴⠈⠯⠲⠴⠠⠁⠠⠴⠀⠠⠕⠨⠶⠝` - - first differing cell (zero-based): 59 - - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_02.json` #782: 천안시는 지난 5월 국토부의 ‘인공지능(AI) 도시계획 연구개발(R&D)’ 기술 시범 적용을 위한 실증 대상 지자체에 선정됐으며, 주관 연구기관인 국토연구원과 업무협약도 체결했다. - - expected: `⠗⠘⠂⠦⠄⠴⠠⠗⠈⠯⠠⠙⠠⠴⠴⠄⠀⠈⠕⠠⠯⠀⠠⠕` - - actual: `⠗⠘⠂⠦⠄⠴⠠⠗⠲⠴⠈⠯⠲⠴⠠⠙⠠⠴⠴⠄⠀⠈⠕⠠` - - first differing cell (zero-based): 70 - - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` + - expected: `⠍⠐⠕⠈⠪⠢⠩⠶⠨⠕⠨⠍⠫⠀⠜⠁⠀⠼⠛⠨⠥⠏⠒⠺` + - actual: `⠍⠐⠕⠈⠪⠢⠩⠶⠨⠕⠨⠍⠫⠀⠜⠁⠀⠼⠛⠨⠥⠏⠒⠺` + - current primary/reason: `exact` / `exact` +- `sentence_02.json` #195: 대전광역시교육청(교육감 설동호)은 직업계고 학생들의 우수기업 취업을 위한 「2023학년도 DJ(Dream&Job) 일자리 NEW 365 매칭데이」채용박람회를 10일 대전컨벤션센터(DCC) 제2전시관에서 성공리에 마쳤다고 밝혔다. + - expected: `⠊⠗⠨⠾⠈⠧⠶⠱⠁⠠⠕⠈⠬⠩⠁⠰⠻⠦⠄⠈⠬⠩⠁⠫` + - actual: `⠊⠗⠨⠾⠈⠧⠶⠱⠁⠠⠕⠈⠬⠩⠁⠰⠻⠦⠄⠈⠬⠩⠁⠫` + - current primary/reason: `exact` / `exact` - `sentence_03.json` #191: 한국투자신탁운용의 에이스(ACE) 글로벌브랜드TOP10블룸버그 ETF는 연초 이후 22.6% 상승했다. 명품 기업 루이비통모에헤네시(LVMH), 대표 가치주로 꼽히는 프록터앤드갬블(P&G) 편입 비중이 높은 것이 특징이다. - - expected: `⠢⠘⠮⠦⠄⠴⠠⠏⠈⠯⠠⠛⠠⠴⠀⠙⠡⠕⠃⠀⠘⠕⠨⠍` - - actual: `⠢⠘⠮⠦⠄⠴⠠⠏⠲⠴⠈⠯⠲⠴⠠⠛⠠⠴⠀⠙⠡⠕⠃⠀` - - first differing cell (zero-based): 183 - - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` + - expected: `⠚⠒⠈⠍⠁⠓⠍⠨⠠⠟⠓⠁⠛⠬⠶⠺⠀⠝⠕⠠⠪⠦⠄⠴` + - actual: `⠚⠒⠈⠍⠁⠓⠍⠨⠠⠟⠓⠁⠛⠬⠶⠺⠀⠝⠕⠠⠪⠦⠄⠴` + - current primary/reason: `exact` / `exact` - `sentence_04.json` #159: 또 액화천연가스(LNG) 분야 협력을 확대하는 한편 수소환원제철 관련 공동 연구·개발(R&D)을 추진해 유럽연합(EU)의 탄소국경조정세(CBAM)와 같은 글로벌 규제와 자원 무기화에 공동 대응한다는 전략이다. - - expected: `⠗⠘⠂⠦⠄⠴⠠⠗⠈⠯⠠⠙⠠⠴⠮⠀⠰⠍⠨⠟⠚⠗⠀⠩` - - actual: `⠗⠘⠂⠦⠄⠴⠠⠗⠲⠴⠈⠯⠲⠴⠠⠙⠠⠴⠮⠀⠰⠍⠨⠟` - - first differing cell (zero-based): 90 - - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` + - expected: `⠠⠊⠥⠀⠗⠁⠚⠧⠰⠾⠡⠫⠠⠪⠦⠄⠴⠠⠠⠇⠝⠛⠠⠴` + - actual: `⠠⠊⠥⠀⠗⠁⠚⠧⠰⠾⠡⠫⠠⠪⠦⠄⠴⠠⠠⠇⠝⠛⠠⠴` + - current primary/reason: `exact` / `exact` Representative `mismatch` samples: -- `sentence_01.json` #207: 우리금융지주가 약 7조원의 자본 여력을 등에 업고 인수·합병(M&A) 시장에 출격했다. 벤처캐피탈(VC) 인수를 시작으로 증권사와 보험사까지 인수한다는 계획이다. - - expected: `⠃⠘⠻⠦⠄⠴⠠⠍⠈⠯⠠⠁⠠⠴⠀⠠⠕⠨⠶⠝⠀⠰⠯⠈` - - actual: `⠃⠘⠻⠦⠄⠴⠠⠍⠲⠴⠈⠯⠲⠴⠠⠁⠠⠴⠀⠠⠕⠨⠶⠝` - - first differing cell (zero-based): 59 +- `sentence_01.json` #425: 대다수의 업종이 상승세였으며 디지털(4.53%), 정보기술(IT) 소프트웨어&서비스(S/W&SVC)(3.09%), 소프트웨어(2.09%)가 큰 폭으로 올랐다. 운송·부품(0.12%), 운송(0.18%), 종이·목재(0.26%), 유통(0.39%), 건설(0.50%)은 떨어졌다. + - expected: `⠯⠠⠠⠎⠧⠉⠠⠴⠦⠄⠼⠉⠲⠚⠊⠴⠏⠠⠴⠐⠀⠠⠥⠙` + - actual: `⠯⠠⠠⠎⠧⠉⠠⠴⠐⠣⠼⠉⠲⠚⠊⠴⠏⠐⠜⠐⠀⠠⠥⠙` + - first differing cell (zero-based): 100 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` - `sentence_02.json` #146: KT&G(사장 백복인)가 산업정책연구원(IPS)이 주관하고 윤경 ESG 포럼이 주최하는 ‘윤경 ESG 포럼 CEO 서약식’에 참여해 윤리경영 의지를 다졌다. - expected: `⠴⠠⠠⠅⠞⠈⠯⠠⠛⠦⠄⠇⠨⠶⠀⠘⠗⠁⠘⠭⠟⠠⠴⠫` - - actual: `⠴⠠⠠⠅⠞⠲⠴⠈⠯⠲⠴⠛⠦⠄⠇⠨⠶⠀⠘⠗⠁⠘⠭⠟` - - first differing cell (zero-based): 5 + - actual: `⠴⠠⠠⠅⠞⠈⠯⠛⠦⠄⠇⠨⠶⠀⠘⠗⠁⠘⠭⠟⠠⠴⠫⠀` + - first differing cell (zero-based): 7 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_03.json` #191: 한국투자신탁운용의 에이스(ACE) 글로벌브랜드TOP10블룸버그 ETF는 연초 이후 22.6% 상승했다. 명품 기업 루이비통모에헤네시(LVMH), 대표 가치주로 꼽히는 프록터앤드갬블(P&G) 편입 비중이 높은 것이 특징이다. - - expected: `⠢⠘⠮⠦⠄⠴⠠⠏⠈⠯⠠⠛⠠⠴⠀⠙⠡⠕⠃⠀⠘⠕⠨⠍` - - actual: `⠢⠘⠮⠦⠄⠴⠠⠏⠲⠴⠈⠯⠲⠴⠠⠛⠠⠴⠀⠙⠡⠕⠃⠀` - - first differing cell (zero-based): 183 +- `sentence_03.json` #370: 26일 GS칼텍스는 서울 강남구 GS타워에서 이승훈 GS칼텍스 S&T본부장, 박진기 HMM 총괄부사장 등이 참석한 가운데 친환경 바이오선박유 사업 업무협약(MOU)을 체결했다고 밝혔다. + - expected: `⠓⠝⠁⠠⠪⠀⠴⠠⠎⠈⠯⠠⠞⠲⠘⠷⠘⠍⠨⠶⠐⠀⠘⠁` + - actual: `⠓⠝⠁⠠⠪⠀⠴⠠⠠⠎⠈⠯⠞⠲⠘⠷⠘⠍⠨⠶⠐⠀⠘⠁` + - first differing cell (zero-based): 69 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` - `sentence_04.json` #19: 이날 행사에는 구 대표 외에 권봉석 (주)LG 최고운영책임자(COO), 박일평 LG사이언스파크대표(사장)를 비롯해 각 계열사 최고기술책임자(CTO), 최고데이터책임자(CDO), 최고인사책임자(CHO) 등이 참석해 국내 이공계 R&D 인재 400여명과 만났다. - expected: `⠁⠕⠢⠨⠦⠄⠴⠠⠠⠉⠕⠕⠠⠴⠐⠀⠘⠁⠕⠂⠙⠻⠀⠴` @@ -876,9 +869,9 @@ Representative `mismatch` samples: ### `closed_roman_parenthetical_after_non_ascii_letter_boundary` -Of the 63959 candidates, 10926 are the actual `pending_rule_review` subcluster. The other 53033 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 63959 candidates, 10674 are the actual `pending_rule_review` subcluster. The other 53285 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. -For this output-signature audit, 12405 mismatches were evaluable and 581 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. +For this output-signature audit, 12153 mismatches were evaluable and 581 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. Localized first-difference transitions: @@ -892,7 +885,7 @@ Mismatch primary-class distribution: - `comparison_method`: 223 - `corpus_suspect`: 1099 -- `pending_rule_review`: 10926 +- `pending_rule_review`: 10674 - `unsupported_character_review`: 157 Representative `exact` samples: @@ -962,9 +955,9 @@ Representative `mismatch` samples: ### `compact_numeric_ascii_letter_suffix` -Of the 2975 candidates, 1052 are the actual `pending_rule_review` subcluster. The other 1923 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 2975 candidates, 1048 are the actual `pending_rule_review` subcluster. The other 1927 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. -For this output-signature audit, 1120 mismatches were evaluable and 253 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. +For this output-signature audit, 1116 mismatches were evaluable and 253 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. Localized first-difference transitions: @@ -978,7 +971,7 @@ Mismatch primary-class distribution: - `comparison_method`: 12 - `corpus_suspect`: 49 -- `pending_rule_review`: 1052 +- `pending_rule_review`: 1048 - `unsupported_character_review`: 7 Representative `exact` samples: @@ -1048,23 +1041,23 @@ Representative `mismatch` samples: ### `decimal_point_between_ascii_digits` -Of the 4546 candidates, 1223 are the actual `pending_rule_review` subcluster. The other 3323 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 4546 candidates, 1138 are the actual `pending_rule_review` subcluster. The other 3408 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. -For this output-signature audit, 1279 mismatches were evaluable and 265 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. +For this output-signature audit, 1195 mismatches were evaluable and 265 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. Localized first-difference transitions: - `U+2814 ⠔ -> U+2824 ⠤`: 55 - `U+2826 ⠦ -> U+2800 ⠀`: 36 +- `U+2826 ⠦ -> U+2810 ⠐`: 17 - `U+2800 ⠀ -> U+280A ⠊`: 16 -- `U+2826 ⠦ -> U+2810 ⠐`: 16 - `U+283C ⠼ -> U+2800 ⠀`: 14 Mismatch primary-class distribution: -- `comparison_method`: 16 +- `comparison_method`: 17 - `corpus_suspect`: 28 -- `pending_rule_review`: 1223 +- `pending_rule_review`: 1138 - `unsupported_character_review`: 12 Representative `exact` samples: @@ -1180,13 +1173,13 @@ Representative `mismatch` samples: ### `korean_prefixed_closed_allcaps_parenthetical` -Of the 54492 candidates, 8714 are the actual `pending_rule_review` subcluster. The other 45778 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 54492 candidates, 8484 are the actual `pending_rule_review` subcluster. The other 46008 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. Mismatch primary-class distribution: - `comparison_method`: 177 - `corpus_suspect`: 986 -- `pending_rule_review`: 8714 +- `pending_rule_review`: 8484 - `unsupported_character_review`: 132 Representative `exact` samples: @@ -1233,9 +1226,9 @@ Representative `mismatch` samples: ### `korean_prefixed_closed_roman_annotation_rule_34_order` -Of the 64382 candidates, 10743 are the actual `pending_rule_review` subcluster. The other 53639 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 64382 candidates, 10491 are the actual `pending_rule_review` subcluster. The other 53891 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. -For this output-signature audit, 12226 mismatches were evaluable and 1103 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. +For this output-signature audit, 11974 mismatches were evaluable and 1103 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. Localized first-difference transitions: @@ -1246,7 +1239,7 @@ Mismatch primary-class distribution: - `comparison_method`: 227 - `corpus_suspect`: 1099 -- `pending_rule_review`: 10743 +- `pending_rule_review`: 10491 - `unsupported_character_review`: 157 Representative `exact` samples: @@ -1554,9 +1547,9 @@ Representative `mismatch` samples: ### `roman_parenthetical_headword_after_whitespace_following_korean_word` -Of the 4695 candidates, 1247 are the actual `pending_rule_review` subcluster. The other 3448 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 4695 candidates, 1242 are the actual `pending_rule_review` subcluster. The other 3453 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. -For this output-signature audit, 1337 mismatches were evaluable and 157 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. +For this output-signature audit, 1332 mismatches were evaluable and 157 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. Localized first-difference transitions: @@ -1568,7 +1561,7 @@ Mismatch primary-class distribution: - `comparison_method`: 29 - `corpus_suspect`: 37 -- `pending_rule_review`: 1247 +- `pending_rule_review`: 1242 - `unsupported_character_review`: 24 Representative `exact` samples: @@ -1828,13 +1821,13 @@ Representative `mismatch` samples: ### `standalone_multi_character_uppercase_roman_word` -Of the 62411 candidates, 11149 are the actual `pending_rule_review` subcluster. The other 51262 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 62411 candidates, 10901 are the actual `pending_rule_review` subcluster. The other 51510 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. Mismatch primary-class distribution: - `comparison_method`: 211 - `corpus_suspect`: 1086 -- `pending_rule_review`: 11149 +- `pending_rule_review`: 10901 - `unsupported_character_review`: 154 Representative `exact` samples: @@ -1922,9 +1915,9 @@ Representative `mismatch` samples: ### `uppercase_ascii_run_immediately_after_digit_in_roman_sequence` -Of the 1896 candidates, 750 are the actual `pending_rule_review` subcluster. The other 1146 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 1896 candidates, 748 are the actual `pending_rule_review` subcluster. The other 1148 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. -For this output-signature audit, 804 mismatches were evaluable and 1 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. +For this output-signature audit, 802 mismatches were evaluable and 1 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. Localized first-difference transitions: @@ -1934,7 +1927,7 @@ Mismatch primary-class distribution: - `comparison_method`: 9 - `corpus_suspect`: 38 -- `pending_rule_review`: 750 +- `pending_rule_review`: 748 - `unsupported_character_review`: 7 Representative `exact` samples: @@ -1991,12 +1984,12 @@ Representative `mismatch` samples: Of the 952 candidates, 771 are the actual `pending_rule_review` subcluster. The other 181 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. -For this output-signature audit, 792 mismatches were evaluable and 160 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. +For this output-signature audit, 792 mismatches were evaluable and 161 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. Localized first-difference transitions: - `U+2820 ⠠ -> U+2830 ⠰`: 157 -- `U+2830 ⠰ -> U+2820 ⠠`: 3 +- `U+2830 ⠰ -> U+2820 ⠠`: 4 Mismatch primary-class distribution: @@ -2124,9 +2117,9 @@ Representative `mismatch` samples: ### `uppercase_roman_run_followed_by_hyphen_digits` -Of the 571 candidates, 367 are the actual `pending_rule_review` subcluster. The other 204 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 571 candidates, 366 are the actual `pending_rule_review` subcluster. The other 205 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. -For this output-signature audit, 382 mismatches were evaluable and 261 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. +For this output-signature audit, 381 mismatches were evaluable and 261 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. Localized first-difference transitions: @@ -2136,7 +2129,7 @@ Localized first-difference transitions: Mismatch primary-class distribution: - `corpus_suspect`: 12 -- `pending_rule_review`: 367 +- `pending_rule_review`: 366 - `unsupported_character_review`: 3 Representative `exact` samples: @@ -2183,9 +2176,9 @@ Representative `mismatch` samples: ### `uppercase_word_after_whitespace_continuing_ascii_roman_text` -Of the 1729 candidates, 994 are the actual `pending_rule_review` subcluster. The other 735 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 1729 candidates, 991 are the actual `pending_rule_review` subcluster. The other 738 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. -For this output-signature audit, 1068 mismatches were evaluable and 4 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. +For this output-signature audit, 1065 mismatches were evaluable and 4 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. Localized first-difference transitions: @@ -2197,7 +2190,7 @@ Mismatch primary-class distribution: - `comparison_method`: 9 - `corpus_suspect`: 54 -- `pending_rule_review`: 994 +- `pending_rule_review`: 991 - `unsupported_character_review`: 11 Representative `exact` samples: @@ -2267,9 +2260,9 @@ These cohorts are defined by both an input boundary and the sentence's actual fi | Cohort | Candidates | Exact controls | Mismatch | Target localized | Reverse | |---|---:|---:|---:|---:|---:| -| `allcaps_roman_run_beginning_with_pure_letter_shortform` | 4120 | 2425 | 1695 | 781 | 42 | -| `uppercase_ascii_run_immediately_after_digit_in_roman_sequence` | 1896 | 1092 | 804 | 0 | 1 | -| `uppercase_ascii_run_immediately_after_hyphen_in_roman_sequence` | 952 | 160 | 792 | 157 | 3 | +| `allcaps_roman_run_beginning_with_pure_letter_shortform` | 4120 | 2441 | 1679 | 783 | 43 | +| `uppercase_ascii_run_immediately_after_digit_in_roman_sequence` | 1896 | 1094 | 802 | 0 | 1 | +| `uppercase_ascii_run_immediately_after_hyphen_in_roman_sequence` | 952 | 160 | 792 | 157 | 4 | | `pure_allcaps_segment_before_hyphen_and_multi_allcaps_segment_after` | 448 | 127 | 321 | 0 | 1 | ### All-caps shortform prefix at an attached Roman entry @@ -2297,7 +2290,7 @@ Same-surface controls demonstrate why primary classes must not be changed by coh | `CD` | 65 | 26 | 39 | 12 | | `IMM` | 27 | 3 | 24 | 22 | | `AG` | 18 | 9 | 9 | 0 | -| `GDP` | 359 | 0 | 359 | 350 | +| `GDP` | 359 | 0 | 359 | 351 | | `WD` | 10 | 10 | 0 | 0 | - `AC` exact control: `sentence_01.json` #1122 — 손흥민(토트넘)이 선발 출전했지만 소속팀 패배를 지켜봤다. 토트넘은 15일(이하 한국시간) 이탈리아 밀라노에 있는 산시로에서 AC 밀란(이탈리아)과 유럽축구연맹(UEFA) 주최 2022-2023시즌 챔피언스리그(UCL) 16강 1차전 원정 경기를 치렀다. @@ -2327,7 +2320,7 @@ Korean rule 29 requires a Roman indicator before Roman text in a Korean sentence | Cohort | Candidates | Exact controls | Mismatch | Pending | Corpus suspect | Localized `⠴ -> blank` | Reverse `blank -> ⠴` | |---|---:|---:|---:|---:|---:|---:|---:| | `roman_hyphenated_word_after_whitespace_following_korean_word` | 361 | 0 | 361 | 350 | 6 | 135 | 0 | -| `roman_parenthetical_headword_after_whitespace_following_korean_word` | 4695 | 3358 | 1337 | 1247 | 37 | 153 | 0 | +| `roman_parenthetical_headword_after_whitespace_following_korean_word` | 4695 | 3363 | 1332 | 1242 | 37 | 153 | 0 | | `korean_prefixed_roman_parenthetical_followed_by_allcaps_hyphen_suffix` | 11 | 0 | 11 | 6 | 5 | 5 | 0 | The whitespace parenthetical-headword cohort also retains 1 localized `⠠ -> ⠴` and 3 localized `⠴ -> ⠰` cases as separate transitions; they are not folded into the target. The exact controls demonstrate that the broad structure is already correct in many sentences, while the attached parenthetical-hyphen cohort has no exact control and includes cases already classified by the stricter rule-34 reference-order contradiction. Consequently none of these measurements authorizes an engine change; they are deterministic pending/corpus-review diagnostics only. @@ -2338,13 +2331,13 @@ Korean rule 29 explicitly says that when two or more Roman items occur consecuti Analyzer checkpoint `266e70c` fixed the pre-change baseline at 1,729 candidates, 386 exact controls, 1,343 mismatches, 1,272 pending members, and 385/1,343 evaluable mismatches localized to the current re-entry signature. Those localized transitions were 365 `⠠ -> ⠴`, 16 `⠰ -> ⠴`, no reverse `⠴ -> ⠠`, and four other transitions. Exact controls include contexts where the first Roman word already opened token-level mode, while parenthesized/mixed-token examples expose the duplicate event. -The generalized fix makes an explicit `EnterEnglish` event idempotent when final emit state is already inside a Roman section; it neither names an input nor changes a new section's entry. The current measurement is 1729 candidates, 661 exact controls, 1068 mismatches, 994 pending members, and 4/1068 localized mismatches. Current target counts are 0 `⠠ -> ⠴`, 0 `⠰ -> ⠴`, and 0 reverse `⠴ -> ⠠`. Cohort exact controls increase by 275, while corpus-wide exact matches rise from 67,138 to 67,442 (+304); corrected prefixes that still differ later remain mismatches, and applications outside this strict input gate account for the remaining net gain. The raw/residual reverse `⠴ -> ⠠` totals remain 68/65 before and after the change. The complete custom standard summary is 5,141 total, 5,141 success, 0 failure, and 0 skipped. +The generalized fix makes an explicit `EnterEnglish` event idempotent when final emit state is already inside a Roman section; it neither names an input nor changes a new section's entry. The current measurement is 1729 candidates, 664 exact controls, 1065 mismatches, 991 pending members, and 4/1065 localized mismatches. Current target counts are 0 `⠠ -> ⠴`, 0 `⠰ -> ⠴`, and 0 reverse `⠴ -> ⠠`. Cohort exact controls increase by 275, while corpus-wide exact matches rise from 67,138 to 67,442 (+304); corrected prefixes that still differ later remain mismatches, and applications outside this strict input gate account for the remaining net gain. The raw/residual reverse `⠴ -> ⠠` totals remain 68/65 before and after the change. The complete custom standard summary is 5,141 total, 5,141 success, 0 failure, and 0 skipped. ### Closed Roman parenthetical after a non-ASCII-letter boundary Korean rule 34 (2024 Korean-rules PDF p.29) prints the opening enclosure before Roman entry in `링컨(Lincoln)`: the opening sequence is `⠦⠄⠴`. This output-position cohort finds a closed, non-nested parenthetical whose body begins with an ASCII letter and whose opening does not immediately follow another ASCII letter, then locates its complete current-engine signature without consulting the reference. Direct function-call shapes such as `f(x)` are excluded. The PDF's math rule 6 (p.59), rule 12 (pp.63-64), and rule 45 (p.75) nevertheless leave standalone `(x)` and other Roman-letter parenthetical mathematics as counterexamples, so the surface gate is not an engine-routing predicate. -The cross-cutting input cohort contains 63959 candidates: 51554 exact controls and 12405 mismatches. Mismatch primary classes remain unchanged: 10926 `pending_rule_review`, 1099 `corpus_suspect`, 223 `comparison_method`, and 157 `unsupported_character_review`. Of 12405 evaluable mismatches, 581 have the first difference at the detected opening; these include 378 `U+2826 ⠦ -> U+2834 ⠴`, 13 `U+2834 ⠴ -> U+2826 ⠦`, and 175 `U+2826 ⠦ -> U+2800 ⠀` transitions. After all earlier localized cohorts and this cohort are excluded, the raw-to-residual target count is 461 -> 76 and the reverse count is 28 -> 12. The short full-encoder form `웹3(Web3)` emits the PDF opening order as a routing control, while whitespace and quote boundaries reproduce the current Roman-first opening. Representative localized samples, with shard and index, are retained in the generated cluster sample table. Because exact controls are abundant and the PDF does not make this input shape semantically sufficient to exclude mathematics, no engine change or primary reclassification is inferred. +The cross-cutting input cohort contains 63959 candidates: 51806 exact controls and 12153 mismatches. Mismatch primary classes remain unchanged: 10674 `pending_rule_review`, 1099 `corpus_suspect`, 223 `comparison_method`, and 157 `unsupported_character_review`. Of 12153 evaluable mismatches, 581 have the first difference at the detected opening; these include 378 `U+2826 ⠦ -> U+2834 ⠴`, 13 `U+2834 ⠴ -> U+2826 ⠦`, and 175 `U+2826 ⠦ -> U+2800 ⠀` transitions. After all earlier localized cohorts and this cohort are excluded, the raw-to-residual target count is 461 -> 76 and the reverse count is 28 -> 12. The short full-encoder form `웹3(Web3)` emits the PDF opening order as a routing control, while whitespace and quote boundaries reproduce the current Roman-first opening. Representative localized samples, with shard and index, are retained in the generated cluster sample table. Because exact controls are abundant and the PDF does not make this input shape semantically sufficient to exclude mathematics, no engine change or primary reclassification is inferred. The HCA-style headword-expansion shape described above is not an engine implementation premise. The 2024 PDF's math rule 6 defines parentheses and grouping parentheses, rule 11 defines mathematical-expression spacing, rule 12 covers Roman letters in formulas as well as Korean sentences, and rule 45 shows Roman-letter function notation followed by parentheses. Excluding visible operators, scripts, and nesting narrows this corpus cohort, but the PDF does not make the remaining surface shape sufficient to rule out every mathematical counterexample. The cluster therefore remains conservative pending-review evidence only. @@ -2371,21 +2364,25 @@ Corpus contradictions remain a separate gate: identical inputs with conflicting ### Uppercase Roman runs containing `ST` -UEB 10.12.1 (2024 UEB PDF pp.191-192, printed pp.163-164) says not to use a contraction when letters within an abbreviation or acronym are known to be pronounced separately, but to use the contraction in case of doubt. Rule 10.12.2 requires contractions in other abbreviations and acronyms. Consequently an uppercase `ST` surface alone cannot determine whether `s` + `t` or the `st` groupsign is required. The current cohort contains 1479 candidates, 675 exact controls, and 804 mismatches; primary classes remain 755 `pending_rule_review`, 31 `corpus_suspect`, 13 `comparison_method`, and 5 `unsupported_character_review`. Of 804 evaluable mismatches, 443 are localized to the detected current-engine run: 435 `U+280E ⠎ -> U+280C ⠌` and 1 `U+280C ⠌ -> U+280E ⠎`. The target's raw-to-residual count is 448 -> 13, and the reverse is 2 -> 1. Exact controls such as `STAYG`, `KAIST`, `DGIST`, and `POSTECH` coexist with localized mismatches such as `WSTS`, `HUST`, `OST`, and `USTR`; representative shard/index samples are preserved in the cluster table. This lexical/pronunciation distinction cannot be inferred from the input-only spelling, so no engine change or primary reclassification is made. +UEB 10.12.1 (2024 UEB PDF pp.191-192, printed pp.163-164) says not to use a contraction when letters within an abbreviation or acronym are known to be pronounced separately, but to use the contraction in case of doubt. Rule 10.12.2 requires contractions in other abbreviations and acronyms. Consequently an uppercase `ST` surface alone cannot determine whether `s` + `t` or the `st` groupsign is required. The current cohort contains 1479 candidates, 678 exact controls, and 801 mismatches; primary classes remain 752 `pending_rule_review`, 31 `corpus_suspect`, 13 `comparison_method`, and 5 `unsupported_character_review`. Of 801 evaluable mismatches, 443 are localized to the detected current-engine run: 435 `U+280E ⠎ -> U+280C ⠌` and 1 `U+280C ⠌ -> U+280E ⠎`. The target's raw-to-residual count is 448 -> 13, and the reverse is 2 -> 1. Exact controls such as `STAYG`, `KAIST`, `DGIST`, and `POSTECH` coexist with localized mismatches such as `WSTS`, `HUST`, `OST`, and `USTR`; representative shard/index samples are preserved in the cluster table. This lexical/pronunciation distinction cannot be inferred from the input-only spelling, so no engine change or primary reclassification is made. ### Attached Roman segments joined by ampersand -UEB 3.1.1 (2024 UEB PDF p.51, printed p.23) directly prints `AT&T` and `B&B` with the attached ampersand `⠈⠯` and no mode boundary around it. Korean rule 71 (2024 Korean-rules PDF p.51, printed p.45) assigns the same `⠈⠯` cells, while rule 29 places Roman entry before a Roman section and termination after its last item. The current Korean-context path instead exits before `&`, wraps the information symbol as a separate Roman section, and re-enters for the following letters. +UEB 3.1.1 (2024 UEB PDF p.51, printed p.23) directly prints `AT&T` and `B&B` with the attached ampersand `⠈⠯` and no mode boundary around it. Korean rule 71 (2024 Korean-rules PDF p.51, printed p.45) assigns the same `⠈⠯` cells, while rule 29 places Roman entry before a Roman section and termination after its last item. Before the engine change, the Korean-context path instead exited before `&`, wrapped the information symbol as a separate Roman section, and re-entered for the following letters. + +The baseline was 802 candidates / 0 exact / 802 mismatch, preserving 776 `pending_rule_review`, 10 `corpus_suspect`, and 16 `unsupported_character_review` primary classifications. Its real-prefix localizer assigned only the output cell immediately before `&`: 356/802 mismatches localized, all 356 were `U+2808 ⠈ -> U+2832 ⠲`, the raw-to-residual target count was 411 -> 53, and `U+2832 ⠲ -> U+2808 ⠈` was 0 raw / 0 residual. + +The implemented gate shares the analyzer's complete-run predicate: one or more non-empty ASCII-letter segments joined directly by `&`, with non-alphanumeric outer boundaries. It keeps the existing Roman mode open and suppresses only rule 71's redundant wrapper around that ampersand. Spaced `Marks & Spencer`, Korean `가&나`, empty segments, and outer digit continuations remain outside the gate. Official full-encoder controls `AT&T` and `B&B` pass, and the Korean rule-71 spaced example `종이접기 & 클레이아트` retains its independent `⠴⠈⠯⠲` section. -The baseline cohort contains 802 candidates, 0 corpus-exact members, and 802 mismatches. Existing primary classes are preserved: 776 `pending_rule_review`, 10 `corpus_suspect`, and 16 `unsupported_character_review`. The real-prefix output localizer evaluates all 802 mismatches but assigns only the single cell immediately before `&`; 356 are localized and all 356 are `U+2808 ⠈ -> U+2832 ⠲`. The raw-to-residual target count is 411 -> 53; `U+2832 ⠲ -> U+2808 ⠈` is 0 raw / 0 residual. Official full-encoder controls `AT&T` and `B&B` pass, while spaced `Marks & Spencer`, Korean `가&나`, empty segments, and alphanumeric continuations are excluded controls. With no corpus exact member, no reverse transition, and direct matching rules in both standards, this is a high-confidence general implementation candidate rather than an expected-derived branch. Representative shard/index samples are retained above. +After the change the same cohort contains 802 candidates, 273 exact and 529 mismatch. Current mismatch primary classes remain evaluator-owned: 502 `pending_rule_review`, 10 `corpus_suspect`, 14 `unsupported_character_review`, and 3 `comparison_method`. The localizer evaluates all 529 remaining mismatches and finds 0 target-localized cases (0 `U+2808 ⠈ -> U+2832 ⠲`); current raw-to-residual target count is 24 -> 23, while `U+2832 ⠲ -> U+2808 ⠈` remains 0 raw / 0 residual. Cohort exact increases by 273 and corpus-wide exact increases by the same 273, from 67,442 to 67,715. Because every changed input must satisfy this shared predicate and the baseline had no exact member, this boundary has no exact regression. The remaining 529 candidates differ elsewhere or retain an existing comparison, corpus-suspect, unsupported, or pending cause. Representative shard/index samples are retained above. -Current uppercase-Roman hyphen-digits measurement: 571 candidates, 189 exact controls, 382 mismatches, 367 members in the actual `pending_rule_review` subcluster, and 261/382 evaluable mismatches whose first difference is inside the target run plus its entry boundary. It remains distinct from parenthesized digits and headword expansions; no engine change is inferred. +Current uppercase-Roman hyphen-digits measurement: 571 candidates, 190 exact controls, 381 mismatches, 366 members in the actual `pending_rule_review` subcluster, and 261/381 evaluable mismatches whose first difference is inside the target run plus its entry boundary. It remains distinct from parenthesized digits and headword expansions; no engine change is inferred. Current single-capital parenthesized-digits measurement: 1361 candidates, 0 exact controls, 1361 mismatches, 1357 members in the actual `pending_rule_review` subcluster, and 1342/1361 evaluable mismatches whose first difference is inside the target run plus its entry boundary. No engine change is inferred from the ambiguous prose/function surface form. Current mixed Roman+Korean boundary before uppercase headword-expansion measurement: 10 candidates, 0 exact controls, 10 mismatches, 10 members in the actual `pending_rule_review` subcluster, and 8/10 evaluable mismatches whose first difference is localized to the later headword's entry boundary/output. The detector cannot be satisfied by the earlier Roman entry. No HCA-shaped engine routing rule is introduced. -Current compact numeric+ASCII-suffix measurement: 2975 candidates, 1855 exact controls, 1120 mismatches, 1052 members in the actual `pending_rule_review` subcluster, and 253/1120 evaluable mismatches whose first difference is inside the complete current-engine output signature or its immediate entry boundary. Rule 40 requires the numeric indicator and rule 69 requires Roman indicators around a Roman-written unit, but the input shape alone cannot prove that every ASCII suffix is a unit. +Current compact numeric+ASCII-suffix measurement: 2975 candidates, 1859 exact controls, 1116 mismatches, 1048 members in the actual `pending_rule_review` subcluster, and 253/1116 evaluable mismatches whose first difference is inside the complete current-engine output signature or its immediate entry boundary. Rule 40 requires the numeric indicator and rule 69 requires Roman indicators around a Roman-written unit, but the input shape alone cannot prove that every ASCII suffix is a unit. | ASCII suffix | Candidates | Exact | Mismatch | Localized first diff | |---|---:|---:|---:|---:| @@ -2395,7 +2392,7 @@ Current compact numeric+ASCII-suffix measurement: 2975 candidates, 1855 exact co | `kg` | 209 | 174 | 35 | 6 | | `D` | 167 | 115 | 52 | 5 | | `p` | 140 | 20 | 120 | 84 | -| `g` | 138 | 95 | 43 | 8 | +| `g` | 138 | 99 | 39 | 8 | | `t` | 128 | 84 | 44 | 16 | | `M` | 104 | 79 | 25 | 6 | | `cm` | 69 | 51 | 18 | 0 | @@ -2420,16 +2417,16 @@ Entry-boundary pre-fix baseline: 2,975 candidates, 1,649 exact controls, 1,326 m Current rule-69 ASCII-unit punctuation-boundary measurement: 440 candidates, 336 exact controls, 104 mismatches, 99 members in the actual `pending_rule_review` subcluster, and 10/104 evaluable mismatches whose first difference is localized to the unit-plus-punctuation output signature. PDF rule 69 requires a Roman terminator after a Roman-written unit in the ordinary case, while rules 33/34 omit it at the listed punctuation or enclosing-mark boundary; the rule 46 PDF example `체중(kg)` is the minimal parenthesized-unit control. Membership is restricted to rule-69 spellings already supported by the engine and does not infer unit semantics for arbitrary ASCII suffixes. Analyzer pre-fix baseline: 440 candidates, 0 exact controls, 440 mismatches, 435 pending members, and 9 signature-local first differences. After the generalized rule-33/34 boundary override and the matching non-math routing guard, the same cohort has 325 exact controls and 115 mismatches. Corpus-wide exact matches moved from 66,039 to 66,436 (+397); the additional gains are applications of the same boundary rule outside this strict ASCII detector, including compatibility-unit forms. The complete standard suite remains 5,141/5,141. -Current decimal-point measurement: 4546 candidates, 3267 exact controls, 1279 mismatches, 1223 members in the actual `pending_rule_review` subcluster, and 265/1279 evaluable mismatches whose first difference is inside the complete decimal-containing word's current-engine signature. Hangeul rules 43 and 48 keep an ASCII point between digits in the numeric sequence and encode it as the decimal point; rules 35 and 69 supply controls for adjacent Roman-number chains and Roman units. This is an implementation-candidate audit, not permission to specialize on a corpus reference. +Current decimal-point measurement: 4546 candidates, 3351 exact controls, 1195 mismatches, 1138 members in the actual `pending_rule_review` subcluster, and 265/1195 evaluable mismatches whose first difference is inside the complete decimal-containing word's current-engine signature. Hangeul rules 43 and 48 keep an ASCII point between digits in the numeric sequence and encode it as the decimal point; rules 35 and 69 supply controls for adjacent Roman-number chains and Roman units. This is an implementation-candidate audit, not permission to specialize on a corpus reference. Analyzer checkpoint `ed20f99` recorded the pre-fix baseline as 4,546 candidates, 2,584 exact controls, 1,962 mismatches, 1,905 pending members, and 875 localized first differences. Its dominant localized transition was reference decimal point `U+2832 ⠲` versus current Roman indicator `U+2834 ⠴` in 647 cases. After the generalized rule-43/48 guard, that transition is absent: 525 cases become exact and the other corrected prefixes expose later independent mismatches. -Current all-caps `OU` measurement: 1816 candidates, 64 exact controls, 1752 mismatches, 1666 members in the actual `pending_rule_review` subcluster, and 1321/1752 evaluable mismatches whose first difference is inside the current-engine output signature for the detected run. This is a pronunciation-sensitive UEB review cohort, not an engine routing rule. +Current all-caps `OU` measurement: 1816 candidates, 64 exact controls, 1752 mismatches, 1666 members in the actual `pending_rule_review` subcluster, and 1328/1752 evaluable mismatches whose first difference is inside the current-engine output signature for the detected run. This is a pronunciation-sensitive UEB review cohort, not an engine routing rule. -Current standalone-uppercase measurement: 62411 candidates, 49811 exact controls, 12600 mismatches, and 11149 members in the actual `pending_rule_review` subcluster. Its high frequency does not make it causal: the same input shape is exact in many cases, and a sentence containing the shape may first differ at another Roman, numeric, or punctuation structure. No engine change is inferred from this cohort. +Current standalone-uppercase measurement: 62411 candidates, 50059 exact controls, 12352 mismatches, and 10901 members in the actual `pending_rule_review` subcluster. Its high frequency does not make it causal: the same input shape is exact in many cases, and a sentence containing the shape may first differ at another Roman, numeric, or punctuation structure. No engine change is inferred from this cohort. -Current Korean-prefixed all-caps parenthetical measurement: 54492 candidates, 44483 exact controls, 10009 mismatches, and 8714 members in the actual `pending_rule_review` subcluster. This is a semantic-collision audit, not an engine routing rule; no implementation change is inferred from its reference outputs. +Current Korean-prefixed all-caps parenthetical measurement: 54492 candidates, 44713 exact controls, 9779 mismatches, and 8484 members in the actual `pending_rule_review` subcluster. This is a semantic-collision audit, not an engine routing rule; no implementation change is inferred from its reference outputs. -Current rule-34 opening-order measurement: 64382 structural candidates, 52156 exact controls, 12226 mismatches, and 1103/12226 evaluable mismatches whose first difference is inside the current engine's Korean opening-parenthesis cells. Only the 1102 localized first-cell transitions have the reference/current order `⠴` versus `⠦`. After requiring the complete reference `⠴⠐⠣` versus current/PDF `⠦⠄⠴` three-cell signature and preserving higher-priority comparison classifications, 1099 are classified as `corpus_suspect`; mere coexistence with a Korean-prefixed Roman annotation does not change a primary class. +Current rule-34 opening-order measurement: 64382 structural candidates, 52408 exact controls, 11974 mismatches, and 1103/11974 evaluable mismatches whose first difference is inside the current engine's Korean opening-parenthesis cells. Only the 1102 localized first-cell transitions have the reference/current order `⠴` versus `⠦`. After requiring the complete reference `⠴⠐⠣` versus current/PDF `⠦⠄⠴` three-cell signature and preserving higher-priority comparison classifications, 1099 are classified as `corpus_suspect`; mere coexistence with a Korean-prefixed Roman annotation does not change a primary class. Current all-caps Roman middle-dot measurement: 97 candidates, 0 exact controls, 97 mismatches, and 96 members in the actual `pending_rule_review` subcluster. This cross-cutting cohort preserves every primary class and is not an engine routing rule. @@ -2447,10 +2444,10 @@ The audit starts from all raw encoding errors, then separates cases already reso | Encoding-error audit | Cases | |---|---:| | Raw encoding errors | 450 | -| Resolved by comparison method | 270 | +| Resolved by comparison method | 272 | | Excluded as corpus suspect | 0 | -| Unresolved encoding-error review cases | 180 | -| Explained by singleton unsupported character(s) | 180 | +| Unresolved encoding-error review cases | 178 | +| Explained by singleton unsupported character(s) | 178 | | Multiple singleton unsupported characters | 2 | | Unclassified without a singleton explanation | 0 | @@ -2461,11 +2458,11 @@ The audit starts from all raw encoding errors, then separates cases already reso | Error message | Cases | |---|---:| -| `Invalid symbol character` | 180 | +| `Invalid symbol character` | 178 | | Error family | Cases | |---|---:| -| `enclosed_organization_mark` | 161 | +| `enclosed_organization_mark` | 159 | | `letterlike_unit_symbol` | 9 | | `punctuation_or_layout_symbol` | 12 | @@ -2484,24 +2481,24 @@ Families are diagnostics, not automatic normalization permissions. Rules 68/69 c | `U+2E31 ⸱` | 2 | `⸱` | `punctuation_or_layout_symbol` | | `U+302E 〮` | 1 | `〮` | `punctuation_or_layout_symbol` | | `U+3214 ㈔` | 2 | `(사)` | `enclosed_organization_mark` | -| `U+321C ㈜` | 159 | `(주)` | `enclosed_organization_mark` | +| `U+321C ㈜` | 157 | `(주)` | `enclosed_organization_mark` | ## Shards | Shard | Exact | Total | Accuracy | |---|---:|---:|---:| -| `sentence_01.json` | 20260 | 25000 | 81.04% | -| `sentence_02.json` | 19693 | 25000 | 78.77% | -| `sentence_03.json` | 20359 | 25000 | 81.44% | -| `sentence_04.json` | 7130 | 8528 | 83.61% | +| `sentence_01.json` | 20310 | 25000 | 81.24% | +| `sentence_02.json` | 19810 | 25000 | 79.24% | +| `sentence_03.json` | 20423 | 25000 | 81.69% | +| `sentence_04.json` | 7172 | 8528 | 84.10% | ## Overlapping mismatch traits | Trait | Count | |---|---:| -| `contains_ascii_digits` | 12587 | -| `contains_ascii_letters` | 15900 | -| `contains_delimiter_or_quote` | 16086 | +| `contains_ascii_digits` | 12402 | +| `contains_ascii_letters` | 15627 | +| `contains_delimiter_or_quote` | 15813 | | `input_not_nfkc` | 718 | ## Samples @@ -2677,6 +2674,10 @@ Against the immediately preceding 63,399-exact run, exact matches increased by 2 | Rules 43/48 decimal-point ownership | 5,141/5,141 | 66,039/83,528 | 79.06% | A period directly between ASCII digits remains on the numeric punctuation path even when its word or sentence also contains Roman text; 647 localized Roman-entry differences were removed and 525 cases became exact | | Rules 33/34/69 Roman-unit punctuation boundary | 5,141/5,141 | 66,436/83,528 | 79.54% | Rule-69 units retain their ordinary terminator at end/Korean/slash boundaries but omit it before rule-33/34 punctuation or enclosing marks; compact unit tokens with that boundary stay off the math path; 397 cases became exact | | Rules 68/69 compact compatibility-derived ASCII units | 5,141/5,141 | 66,546/83,528 | 79.67% | Compact ASCII unit spellings are derived from the engine's already accepted Unicode compatibility-unit forms and reuse their owning-rule cells, with longest-complete matching and no expansion to separated English words; `160mg`, numeric-invariance control `240mg`, and Rule-68 `ha` controls are retained; 110 cases became exact | +| UEB numeric-mode letter classes in Roman identifiers | 5,141/5,141 | 67,012/83,528 | 80.23% | Lowercase `a`-`j` retains grade 1 after digits, capitals use capitalization, and lowercase `k`-`z` needs no extra indicator; numeric-leading Rule-69 units remain separate | +| UEB complete all-caps segments across hyphen | 5,141/5,141 | 67,138/83,528 | 80.38% | The grade-1 restart is omitted only between a complete uppercase prefix and an uppercase suffix of at least two letters; mixed/single-capital and digit-hyphen controls remain unchanged | +| Rule-29 consecutive Roman entry idempotence | 5,141/5,141 | 67,442/83,528 | 80.74% | An explicit Roman-entry event is ignored only when final emit state is already inside the same Roman section | +| Rules 29/71 complete attached Roman ampersand run | 5,141/5,141 | 67,715/83,528 | 81.07% | Complete ASCII-letter segments joined by `&` retain one Roman section; official `AT&T`, `B&B`, and spaced Korean Rule-71 controls delimit the safe boundary; 273 cases became exact | The latest full `cargo test -p braillify test_by_testcase --release -- --nocapture` run was accepted from its custom testcase summary, not the trailing filtered harness: `총 테스트 케이스: 5141`, `성공: 5141`, `실패: 0`, and `Skip (limitation): 0`. diff --git a/libs/braillify/examples/nikl_corpus_analyze.rs b/libs/braillify/examples/nikl_corpus_analyze.rs index d76ec0b1..4175e4ea 100644 --- a/libs/braillify/examples/nikl_corpus_analyze.rs +++ b/libs/braillify/examples/nikl_corpus_analyze.rs @@ -4013,26 +4013,40 @@ fn markdown(report: &AnalysisReport) -> String { `B&B` with the attached ampersand `⠈⠯` and no mode boundary around it. Korean \ rule 71 (2024 Korean-rules PDF p.51, printed p.45) assigns the same `⠈⠯` \ cells, while rule 29 places Roman entry before a Roman section and termination after \ - its last item. The current Korean-context path instead exits before `&`, wraps the \ - information symbol as a separate Roman section, and re-enters for the following \ - letters.\n\n\ - The baseline cohort contains {} candidates, {} corpus-exact members, and {} \ - mismatches. Existing primary classes are preserved: {} `pending_rule_review`, {} \ - `corpus_suspect`, and {} `unsupported_character_review`. The real-prefix output \ - localizer evaluates all {} mismatches but assigns only the single cell immediately \ - before `&`; {} are localized and all {} are `{target}`. The raw-to-residual target \ - count is {} -> {}; `{reverse}` is {} raw / {} residual. Official full-encoder \ - controls `AT&T` and `B&B` pass, while spaced `Marks & Spencer`, Korean `가&나`, empty \ - segments, and alphanumeric continuations are excluded controls. With no corpus exact \ - member, no reverse transition, and direct matching rules in both standards, this is a \ - high-confidence general implementation candidate rather than an expected-derived \ - branch. Representative shard/index samples are retained above.\n", + its last item. Before the engine change, the Korean-context path instead exited before \ + `&`, wrapped the information symbol as a separate Roman section, and re-entered for \ + the following letters.\n\n\ + The baseline was 802 candidates / 0 exact / 802 mismatch, preserving 776 \ + `pending_rule_review`, 10 `corpus_suspect`, and 16 \ + `unsupported_character_review` primary classifications. Its real-prefix localizer \ + assigned only the output cell immediately before `&`: 356/802 mismatches localized, \ + all 356 were `{target}`, the raw-to-residual target count was 411 -> 53, and \ + `{reverse}` was 0 raw / 0 residual.\n\n\ + The implemented gate shares the analyzer's complete-run predicate: one or more \ + non-empty ASCII-letter segments joined directly by `&`, with non-alphanumeric outer \ + boundaries. It keeps the existing Roman mode open and suppresses only rule 71's \ + redundant wrapper around that ampersand. Spaced `Marks & Spencer`, Korean `가&나`, \ + empty segments, and outer digit continuations remain outside the gate. Official \ + full-encoder controls `AT&T` and `B&B` pass, and the Korean rule-71 spaced example \ + `종이접기 & 클레이아트` retains its independent `⠴⠈⠯⠲` section.\n\n\ + After the change the same cohort contains {} candidates, {} exact and {} mismatch. \ + Current mismatch primary classes remain evaluator-owned: {} `pending_rule_review`, {} \ + `corpus_suspect`, {} `unsupported_character_review`, and {} `comparison_method`. The \ + localizer evaluates all {} remaining mismatches and finds {} target-localized cases \ + ({} `{target}`); current raw-to-residual target count is {} -> {}, while `{reverse}` \ + remains {} raw / {} residual. Cohort exact increases by 273 and corpus-wide exact \ + increases by the same 273, from 67,442 to 67,715. Because every changed input must \ + satisfy this shared predicate and the baseline had no exact member, this boundary has \ + no exact regression. The remaining 529 candidates differ elsewhere or retain an \ + existing comparison, corpus-suspect, unsupported, or pending cause. Representative \ + shard/index samples are retained above.\n", stats.candidates, stats.exact, stats.mismatch, primary_count("pending_rule_review"), primary_count("corpus_suspect"), primary_count("unsupported_character_review"), + primary_count("comparison_method"), stats.output_signature_mismatches_evaluated, stats.first_difference_in_output_signature, transition_count(target), @@ -4689,6 +4703,12 @@ fn markdown(report: &AnalysisReport) -> String { text.push_str( "| Rules 68/69 compact compatibility-derived ASCII units | 5,141/5,141 | 66,546/83,528 | 79.67% | Compact ASCII unit spellings are derived from the engine's already accepted Unicode compatibility-unit forms and reuse their owning-rule cells, with longest-complete matching and no expansion to separated English words; `160mg`, numeric-invariance control `240mg`, and Rule-68 `ha` controls are retained; 110 cases became exact |\n", ); + text.push_str( + "| UEB numeric-mode letter classes in Roman identifiers | 5,141/5,141 | 67,012/83,528 | 80.23% | Lowercase `a`-`j` retains grade 1 after digits, capitals use capitalization, and lowercase `k`-`z` needs no extra indicator; numeric-leading Rule-69 units remain separate |\n\ + | UEB complete all-caps segments across hyphen | 5,141/5,141 | 67,138/83,528 | 80.38% | The grade-1 restart is omitted only between a complete uppercase prefix and an uppercase suffix of at least two letters; mixed/single-capital and digit-hyphen controls remain unchanged |\n\ + | Rule-29 consecutive Roman entry idempotence | 5,141/5,141 | 67,442/83,528 | 80.74% | An explicit Roman-entry event is ignored only when final emit state is already inside the same Roman section |\n\ + | Rules 29/71 complete attached Roman ampersand run | 5,141/5,141 | 67,715/83,528 | 81.07% | Complete ASCII-letter segments joined by `&` retain one Roman section; official `AT&T`, `B&B`, and spaced Korean Rule-71 controls delimit the safe boundary; 273 cases became exact |\n", + ); text.push_str( "\nThe latest full `cargo test -p braillify test_by_testcase --release -- --nocapture` \ run was accepted from its custom testcase summary, not the trailing filtered harness: \ @@ -5406,14 +5426,14 @@ mod tests { #[case::merger("인수·합병(M&A) 시장")] #[case::brand("브랜드(P&G) 편입")] #[case::research("연구·개발(R&D)을 추진")] - fn locates_attached_roman_ampersand_in_korean_output(#[case] input: &str) { + fn resolved_attached_roman_ampersand_has_no_boundary_signature(#[case] input: &str) { let actual = braillify::encode_to_unicode(input).expect("ampersand probe must encode"); let ranges = attached_roman_ampersand_boundary_ranges(input, &actual); - assert_eq!(ranges.len(), 1); - assert_eq!(ranges[0].end - ranges[0].start, 1); - assert_eq!(actual.chars().nth(ranges[0].start), Some('⠲')); - assert!(ranges[0].end <= actual.chars().count()); + assert!(ranges.is_empty()); + assert!(actual.contains("⠈⠯")); + assert!(!actual.contains("⠲⠴⠈⠯")); + assert!(!actual.contains("⠈⠯⠲⠴")); } #[rstest::rstest] diff --git a/libs/braillify/src/english_logic.rs b/libs/braillify/src/english_logic.rs index f31b0899..75c8ea0c 100644 --- a/libs/braillify/src/english_logic.rs +++ b/libs/braillify/src/english_logic.rs @@ -78,6 +78,45 @@ fn is_ascii_letter_or_digit(ch: Option) -> bool { ch.is_some_and(|c| c.is_ascii_alphanumeric()) } +/// Returns whether `index` is an ampersand inside a complete sequence of +/// non-empty ASCII-letter segments joined by `&`. Alphanumeric characters +/// outside the maximal sequence are excluded because UEB 3.1.1 directly +/// establishes only complete attached Roman forms such as AT&T and B&B. +pub(crate) fn is_attached_ascii_roman_ampersand(word_chars: &[char], index: usize) -> bool { + if word_chars.get(index) != Some(&'&') + || index == 0 + || index + 1 >= word_chars.len() + || !word_chars[index - 1].is_ascii_alphabetic() + || !word_chars[index + 1].is_ascii_alphabetic() + { + return false; + } + + let mut start = index; + while start > 0 && (word_chars[start - 1].is_ascii_alphabetic() || word_chars[start - 1] == '&') + { + start -= 1; + } + let mut end = index + 1; + while end < word_chars.len() + && (word_chars[end].is_ascii_alphabetic() || word_chars[end] == '&') + { + end += 1; + } + + word_chars[start..end] + .first() + .is_some_and(|ch| ch.is_ascii_alphabetic()) + && word_chars[start..end] + .last() + .is_some_and(|ch| ch.is_ascii_alphabetic()) + && !word_chars[start..end] + .windows(2) + .any(|pair| pair == ['&', '&']) + && (start == 0 || !word_chars[start - 1].is_ascii_alphanumeric()) + && (end == word_chars.len() || !word_chars[end].is_ascii_alphanumeric()) +} + fn is_digital_notation_symbol(symbol: char) -> bool { matches!(symbol, '/' | '@' | '#' | '.' | '_' | ':') } @@ -173,6 +212,11 @@ pub(crate) fn should_render_symbol_as_english( match symbol { '(' => is_ascii_letter_or_digit(next_char) && !prev_char.is_some_and(utils::is_korean_char), ')' => parenthesis_stack.last().copied().unwrap_or(false), + // UEB 3.1.1 prints an ampersand without ending and restarting + // grade-1 mode in attached Roman forms such as AT&T and B&B. Use + // a complete ASCII-letter run so spaced prose, Hangul, and outer + // alphanumeric continuations keep their existing routes. + '&' => is_attached_ascii_roman_ampersand(word_chars, index), ',' => { if !is_english { return false; @@ -350,6 +394,34 @@ mod tests { ); } + /// UEB 3.1.1 keeps attached Roman segments on both sides of `&` in the + /// same mode. The complete-run boundary deliberately excludes spaced + /// prose, Hangul boundaries, and outer alphanumeric continuations. + #[rstest::rstest] + #[case::official_at_and_t("AT&T", true, true)] + #[case::official_b_and_b("B&B", true, true)] + #[case::spaced("A & B", true, false)] + #[case::hangul_left("가&B", true, false)] + #[case::hangul_right("A&나", true, false)] + #[case::digit_neighbor("3&B", true, false)] + #[case::digit_outer_left("3A&B", true, false)] + #[case::digit_outer_right("A&B3", true, false)] + #[case::multiple_ampersands("A&B&C", true, true)] + #[case::empty_segment("A&&B", true, false)] + #[case::no_roman_indicator("AT&T", false, false)] + fn attached_ampersand_requires_complete_ascii_roman_run( + #[case] input: &str, + #[case] english_indicator: bool, + #[case] expected: bool, + ) { + let word: Vec = input.chars().collect(); + let index = word.iter().position(|ch| *ch == '&').unwrap(); + assert_eq!( + should_render_symbol_as_english(english_indicator, true, &[], '&', &word, index, &[],), + expected, + ); + } + /// `has_digital_notation_signature` — `//`, `@`, `#` 강한 마커 또는 /// underscore + digital marker 조합은 true, 단순 underscore는 false. #[rstest::rstest] diff --git a/libs/braillify/src/rules/korean/rule_71.rs b/libs/braillify/src/rules/korean/rule_71.rs index 6cbf6c77..41857ea8 100644 --- a/libs/braillify/src/rules/korean/rule_71.rs +++ b/libs/braillify/src/rules/korean/rule_71.rs @@ -47,6 +47,13 @@ fn should_wrap_information_symbol(ctx: &RuleContext) -> bool { prev_has_korean || next_has_korean } +/// UEB 3.1.1 writes `&` directly between attached ASCII-letter segments +/// (for example, AT&T and B&B). The surrounding Roman section already owns +/// the mode indicators, so Rule 71 must emit only the ampersand cells there. +fn is_attached_roman_ampersand(ctx: &RuleContext) -> bool { + crate::english_logic::is_attached_ascii_roman_ampersand(ctx.word_chars, ctx.index) +} + pub fn is_rule_71_symbol(c: char) -> bool { MAPPINGS.iter().any(|(candidate, _)| *candidate == c) } @@ -103,6 +110,7 @@ impl BrailleRule for Rule71 { let mut encoded = Vec::new(); if should_wrap_information_symbol(ctx) && matches!(ctx.current_char(), '&' | '¶' | '©' | '®' | '™') + && !is_attached_roman_ampersand(ctx) { encoded.push(crate::unicode::decode_unicode('⠴')); encoded.extend(encode_unicode_cells(unicode)); @@ -170,4 +178,42 @@ mod tests { // Also: § alone (no next char) → no digit → ⠲ appended. let _ = crate::encode("§"); } + + /// The Korean Rule 71 encoder owns the ampersand cells even while the + /// surrounding Roman section remains open. These are the official UEB + /// 3.1.1 surface forms, not corpus-derived examples. + #[rstest::rstest] + #[case::official_at_and_t("AT&T")] + #[case::official_b_and_b("B&B")] + fn attached_roman_ampersand_emits_bare_rule_71_cells(#[case] input: &str) { + let mut owned = crate::test_helpers::CtxOwned::for_text(input, false); + let ampersand_index = input.chars().position(|ch| ch == '&').unwrap(); + let mut ctx = owned.ctx_at(ampersand_index); + + let outcome = Rule71.apply(&mut ctx).unwrap(); + + assert!(matches!(outcome, RuleResult::Consumed)); + assert_eq!(ctx.result.as_slice(), encode_unicode_cells("⠈⠯")); + } + + /// Full-encoder controls reproduce the two UEB 3.1.1 examples exactly. + #[rstest::rstest] + #[case::official_at_and_t("AT&T", "⠠⠠⠁⠞⠈⠯⠠⠞")] + #[case::official_b_and_b("B&B", "⠠⠃⠈⠯⠠⠃")] + fn full_encoder_preserves_official_ueb_ampersand_examples( + #[case] input: &str, + #[case] expected: &str, + ) { + assert_eq!(crate::encode_to_unicode(input).unwrap(), expected); + } + + /// Korean Rule 71's spaced Hangul example remains an independently + /// delimited information symbol after the attached-Roman exception. + #[test] + fn full_encoder_preserves_official_korean_spaced_ampersand_example() { + assert_eq!( + crate::encode_to_unicode("종이접기 & 클레이아트").unwrap(), + "⠨⠿⠕⠨⠎⠃⠈⠕⠀⠴⠈⠯⠲⠀⠋⠮⠐⠝⠕⠣⠓⠪", + ); + } } From 169dcc5842cd7c0653e99596a5a185ee8cca49e8 Mon Sep 17 00:00:00 2001 From: devfive Date: Tue, 1 Sep 2026 13:14:05 +0900 Subject: [PATCH 36/77] Diagnose uppercase AR contraction residual --- docs/corpus-analysis/NIKL_2025_V1.md | 179 +++++++++++++----- .../braillify/examples/nikl_corpus_analyze.rs | 151 ++++++++++++++- 2 files changed, 286 insertions(+), 44 deletions(-) diff --git a/docs/corpus-analysis/NIKL_2025_V1.md b/docs/corpus-analysis/NIKL_2025_V1.md index ee039004..b2fcbbee 100644 --- a/docs/corpus-analysis/NIKL_2025_V1.md +++ b/docs/corpus-analysis/NIKL_2025_V1.md @@ -298,49 +298,26 @@ This ranking removes only cases whose first difference is inside an existing out | Rank | Expected → actual first cell | Residual cases | |---:|---|---:| -| 1 | `U+2801 ⠁ -> U+281C ⠜` | 390 | -| 2 | `U+2834 ⠴ -> U+2830 ⠰` | 333 | -| 3 | `U+2811 ⠑ -> U+282B ⠫` | 325 | -| 4 | `U+2810 ⠐ -> U+2832 ⠲` | 297 | -| 5 | `U+2832 ⠲ -> U+2838 ⠸` | 290 | -| 6 | `U+2834 ⠴ -> U+2800 ⠀` | 261 | -| 7 | `U+2800 ⠀ -> U+2832 ⠲` | 227 | -| 8 | `U+2826 ⠦ -> U+2800 ⠀` | 223 | -| 9 | `U+2810 ⠐ -> U+2802 ⠂` | 211 | -| 10 | `U+2817 ⠗ -> U+2820 ⠠` | 165 | -| 11 | `U+2820 ⠠ -> U+2809 ⠉` | 162 | -| 12 | `U+283C ⠼ -> U+2800 ⠀` | 153 | -| 13 | `U+2826 ⠦ -> U+2810 ⠐` | 147 | -| 14 | `U+281B ⠛ -> U+2823 ⠣` | 144 | -| 15 | `U+2810 ⠐ -> U+2800 ⠀` | 130 | -| 16 | `U+2811 ⠑ -> U+283B ⠻` | 101 | -| 17 | `U+2810 ⠐ -> U+2814 ⠔` | 98 | -| 18 | `U+280E ⠎ -> U+2820 ⠠` | 92 | -| 19 | `U+2804 ⠄ -> U+2834 ⠴` | 86 | -| 20 | `U+2803 ⠃ -> U+2820 ⠠` | 85 | - -### Residual `U+2801 ⠁ -> U+281C ⠜` - -- `sentence_01.json` #142: ‘메타버스존’에서는 증강현실(AR)·가상현실(VR)에 적용되는 혁신기술이 소개된다. 글라스를 착용하고 최첨단 3D 센싱모듈이 구현하는 가상현실을 관람객이 직접 체험할 수 있도록 했다. - - expected: `⠠⠕⠂⠦⠄⠴⠠⠠⠁⠗⠠⠴⠐⠆⠫⠇⠶⠚⠡⠠⠕⠂⠦⠄` - - actual: `⠠⠕⠂⠦⠄⠴⠠⠠⠜⠠⠴⠐⠆⠸⠷⠫⠇⠶⠚⠡⠠⠕⠂⠸` - - first differing cell (zero-based): 34 - - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_02.json` #28: 체납된 세금은 전국 어디서나 은행 현금자동인출기(ATM)를 이용해 고지서 없이 현금과 신용카드로 납부가 가능하며, 자동응답시스템(ARS) 지방세 납부서비스(044-300-7114)를 이용해 가상계좌를 확인해 계좌이체를 하거나 신용카드로 납부할 수도 있다. - - expected: `⠓⠝⠢⠦⠄⠴⠠⠠⠁⠗⠎⠠⠴⠀⠨⠕⠘⠶⠠⠝⠀⠉⠃⠘` - - actual: `⠓⠝⠢⠦⠄⠴⠠⠠⠜⠎⠠⠴⠀⠨⠕⠘⠶⠠⠝⠀⠉⠃⠘⠍` - - first differing cell (zero-based): 129 - - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_03.json` #52: 종합식품기업 하림이 공룡 모양으로 생긴 동그랑땡 ‘용가리 땡’을 20일 출시했다. 용가리땡에는 특별 제작한 증강현실(AR) 공룡 카드까지 한 장씩 들어 있어 아이들의 많은 관심이 예상된다. - - expected: `⠠⠕⠂⠦⠄⠴⠠⠠⠁⠗⠠⠴⠀⠈⠿⠐⠬⠶⠀⠋⠊⠪⠠⠫` - - actual: `⠠⠕⠂⠦⠄⠴⠠⠠⠜⠠⠴⠀⠈⠿⠐⠬⠶⠀⠋⠊⠪⠠⠫⠨` - - first differing cell (zero-based): 126 - - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_04.json` #187: 주정차단속 ARS 알림서비스는 기존 불법 주정차 문자 알림서비스를 업그레이드 해 문자와 함께 자동응답서비스(ARS)로도 주정차단속을 알려주는 시스템이다. - - expected: `⠊⠒⠠⠭⠀⠴⠠⠠⠁⠗⠎⠲⠀⠣⠂⠐⠕⠢⠠⠎⠘⠕⠠⠪` - - actual: `⠊⠒⠠⠭⠀⠴⠠⠠⠜⠎⠲⠀⠣⠂⠐⠕⠢⠠⠎⠘⠕⠠⠪⠉` - - first differing cell (zero-based): 14 - - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +| 1 | `U+2834 ⠴ -> U+2830 ⠰` | 333 | +| 2 | `U+2811 ⠑ -> U+282B ⠫` | 325 | +| 3 | `U+2810 ⠐ -> U+2832 ⠲` | 297 | +| 4 | `U+2832 ⠲ -> U+2838 ⠸` | 290 | +| 5 | `U+2834 ⠴ -> U+2800 ⠀` | 261 | +| 6 | `U+2800 ⠀ -> U+2832 ⠲` | 227 | +| 7 | `U+2826 ⠦ -> U+2800 ⠀` | 223 | +| 8 | `U+2810 ⠐ -> U+2802 ⠂` | 211 | +| 9 | `U+2817 ⠗ -> U+2820 ⠠` | 165 | +| 10 | `U+2820 ⠠ -> U+2809 ⠉` | 162 | +| 11 | `U+283C ⠼ -> U+2800 ⠀` | 153 | +| 12 | `U+2826 ⠦ -> U+2810 ⠐` | 147 | +| 13 | `U+281B ⠛ -> U+2823 ⠣` | 144 | +| 14 | `U+2810 ⠐ -> U+2800 ⠀` | 130 | +| 15 | `U+2811 ⠑ -> U+283B ⠻` | 101 | +| 16 | `U+2810 ⠐ -> U+2814 ⠔` | 98 | +| 17 | `U+280E ⠎ -> U+2820 ⠠` | 92 | +| 18 | `U+2804 ⠄ -> U+2834 ⠴` | 86 | +| 19 | `U+2803 ⠃ -> U+2820 ⠠` | 85 | +| 20 | `U+2824 ⠤ -> U+2800 ⠀` | 78 | ### Residual `U+2834 ⠴ -> U+2830 ⠰` @@ -549,6 +526,29 @@ This ranking removes only cases whose first difference is inside an existing out - first differing cell (zero-based): 175 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +### Residual `U+2820 ⠠ -> U+2809 ⠉` + +- `sentence_01.json` #45: 2019년 드링크인터내셔널 합류 이후 최고 운영 책임자(COO)를 역임하며 자회사 인터리커를 통해 와인 시장 진출 등 사업 확장을 진두지휘했다. + - expected: `⠁⠕⠢⠨⠦⠄⠴⠠⠠⠉⠕⠕⠠⠴⠐⠮⠀⠱⠁⠕⠢⠚⠑⠱` + - actual: `⠁⠕⠢⠨⠦⠄⠴⠠⠉⠠⠕⠠⠕⠠⠴⠐⠮⠀⠱⠁⠕⠢⠚⠑` + - first differing cell (zero-based): 53 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_02.json` #576: 특히 시는 재난안전 분야에서 인공지능(AI) 기반 폐쇄회로(CC)티브이 관제, 빅데이터를 활용한 화재 발생 위험 요인 대응취약지역 예측 분석을 추진한 점에서도 우수성을 인정받았다. + - expected: `⠚⠽⠐⠥⠦⠄⠴⠠⠠⠉⠉⠠⠴⠓⠕⠘⠪⠕⠀⠈⠧⠒⠨⠝` + - actual: `⠚⠽⠐⠥⠦⠄⠴⠠⠉⠠⠉⠠⠴⠓⠕⠘⠪⠕⠀⠈⠧⠒⠨⠝` + - first differing cell (zero-based): 63 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_03.json` #22: 미국프로골프(PGA)투어 더 CJ컵이 열렸던 제주 나인브릿지CC, 한국여자프로골프(KLPGA)투어 롯데렌터카 여자오픈이 열렸던 롯데스카이힐 제주CC, 그리고 ‘한국의 마스터스’ GS칼텍스 매경오픈이 열리는 경기도 성남 남서울CC. + - expected: `⠪⠐⠕⠄⠨⠕⠴⠠⠠⠉⠉⠐⠀⠚⠒⠈⠍⠁⠱⠨⠙⠪⠐⠥` + - actual: `⠪⠐⠕⠄⠨⠕⠴⠠⠉⠠⠉⠐⠀⠚⠒⠈⠍⠁⠱⠨⠙⠪⠐⠥` + - first differing cell (zero-based): 66 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_04.json` #19: 이날 행사에는 구 대표 외에 권봉석 (주)LG 최고운영책임자(COO), 박일평 LG사이언스파크대표(사장)를 비롯해 각 계열사 최고기술책임자(CTO), 최고데이터책임자(CDO), 최고인사책임자(CHO) 등이 참석해 국내 이공계 R&D 인재 400여명과 만났다. + - expected: `⠁⠕⠢⠨⠦⠄⠴⠠⠠⠉⠕⠕⠠⠴⠐⠀⠘⠁⠕⠂⠙⠻⠀⠴` + - actual: `⠁⠕⠢⠨⠦⠄⠴⠠⠉⠠⠕⠠⠕⠠⠴⠐⠀⠘⠁⠕⠂⠙⠻⠀` + - first differing cell (zero-based): 60 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` + ## Cross-cutting input-only structural cohorts These are cross-cutting input-only structural cohorts, not new primary classes and not engine routing rules. Candidate selection never changes a case's existing primary class by itself. Only cohort members already classified as `pending_rule_review` form a pending subcluster; exact and other-primary members are controls that retain their existing outcomes. A separate classifier may use independently justified, output-localized PDF evidence, as in the rule-34 three-cell contradiction below. The `uppercase_roman_headword_closed_multiword_parenthetical` gate requires a two-or-more character uppercase ASCII headword immediately followed by a closed parenthesis whose contents are two or more ASCII Roman words separated only by spaces. Because the contents admit only letters and spaces, visible operators, subscript/superscript notation, and nested parentheses are excluded deterministically. The `mixed_roman_korean_word_before_uppercase_headword_expansion` gate further requires a whitespace-delimited preceding word that contains both ASCII Roman and Korean and ends in Korean. It locates the following uppercase headword itself, including its immediately preceding emitted cell, so an earlier Roman entry in the same sentence cannot satisfy the output audit. The `single_capital_followed_by_parenthesized_digits` gate requires a standalone capital, a non-empty closed ASCII-digit parenthetical, and alphanumeric outer boundaries. It likewise includes the emitted entry-boundary cell in localization. The `uppercase_roman_run_followed_by_hyphen_digits` gate requires a maximal uppercase ASCII run, literal ASCII hyphen-minus, a non-empty digit run, and alphanumeric outer boundaries. Its localized range includes the current encoded run and its immediately preceding cell, keeping `F-35`-style routing separate from both parenthesized digits and headword expansions. The `standalone_multi_character_uppercase_roman_word` gate finds maximal ASCII-letter runs of two or more capitals with non-alphanumeric boundaries; a run immediately followed by `(` is excluded so the HCA-style headword itself is not counted by both gates. The `korean_prefixed_closed_allcaps_parenthetical` gate requires an immediately preceding Korean character and a closed body of two or more uppercase ASCII letters. It intentionally contains both acronym annotations (`책임자(COO)`) and scientific formulae (`일산화탄소(CO)`) so their semantic collision remains measurable. The `korean_prefixed_closed_roman_annotation_rule_34_order` gate accepts the narrower rule-34 body grammar after an immediately preceding Korean character and localizes only the current engine's Korean opening-parenthesis cells. This distinguishes the PDF's parenthesis-before-Roman-indicator order from unrelated differences later in the same sentence. The `multi_character_allcaps_roman_runs_joined_by_middle_dot` gate requires two maximal ASCII-letter runs of at least two capitals joined directly by U+00B7, with non-alphanumeric outer boundaries. It records shapes such as `AI·SW` without assigning prose, mathematics, or science semantics. The `roman_run_immediately_before_attached_middle_dot_boundary` gate is output-localized: it requires a maximal ASCII-letter run immediately before U+00B7 and an attached Korean character or ASCII-letter run after it, then searches for that whole current-engine signature in the actual output. It therefore isolates the Roman terminator boundary without treating unrelated middle dots elsewhere in the sentence as causal. The `korean_inline_parenthesized_single_arithmetic_operator` gate requires an immediate `Korean(` + one rule-45 arithmetic operator + `)Korean` span. Unlike broad coexistence traits, it also locates the current engine's emitted structure and counts a mismatch as signature-local only when the sentence's first differing cell falls inside that output range. The `allcaps_roman_run_containing_ou` gate finds maximal, alphanumeric-delimited uppercase ASCII runs containing adjacent `OU`. It locates the independently encoded run signature in the complete current output and counts only first differences inside that signature as localized. The `allcaps_roman_run_containing_st` gate applies the same output-position requirement to adjacent `ST`; it is kept separate because UEB 10.12 makes contraction use depend on how an abbreviation or acronym is pronounced. The `attached_ascii_roman_segments_joined_by_ampersand` gate requires non-empty ASCII-letter segments joined directly by `&`, excludes spaced/Korean/alphanumeric continuations, and localizes only the current output cell immediately before the ampersand through an independently encoded real-input prefix. The `decimal_point_between_ascii_digits` gate finds whitespace-delimited words containing `digit.digit` and reproduces each whole word in a neutral Korean context, so suffixes and punctuation remain part of the current-engine signature. The `compact_numeric_ascii_letter_suffix` gate finds a numeric prefix immediately followed by ASCII letters, includes the immediately preceding output cell as its entry boundary, and retains suffix-specific outcome counts. It intentionally includes both possible rule-69 units and ambiguous variable/identifier forms; membership alone does not assign unit semantics. The `rule69_ascii_unit_before_terminator_skipping_symbol` gate is narrower: it accepts only ASCII unit spellings already supported by rule 69, includes the immediately following rule-33/34 punctuation cell in the localized signature, and does not infer new units. The `tight_triangle_mark_immediately_before_korean` gate requires literal `△한글` with no input space and includes the first following Korean cell in its localized output range, so an observed missing-space difference is measured at the mark boundary. @@ -556,6 +556,7 @@ These are cross-cutting input-only structural cohorts, not new primary classes a | Cluster | Candidates | Exact | Mismatch | Conflicting-reference cases | |---|---:|---:|---:|---:| | `allcaps_roman_run_beginning_with_pure_letter_shortform` | 4120 | 2441 | 1679 | 0 | +| `allcaps_roman_run_containing_ar` | 1022 | 360 | 662 | 0 | | `allcaps_roman_run_containing_ou` | 1816 | 64 | 1752 | 0 | | `allcaps_roman_run_containing_st` | 1479 | 678 | 801 | 0 | | `attached_ascii_roman_segments_joined_by_ampersand` | 802 | 273 | 529 | 0 | @@ -665,6 +666,92 @@ Representative `mismatch` samples: - first differing cell (zero-based): 60 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +### `allcaps_roman_run_containing_ar` + +Of the 1022 candidates, 639 are the actual `pending_rule_review` subcluster. The other 383 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. + +For this output-signature audit, 662 mismatches were evaluable and 390 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. + +Localized first-difference transitions: + +- `U+2801 ⠁ -> U+281C ⠜`: 383 +- `U+2810 ⠐ -> U+2815 ⠕`: 3 +- `U+2800 ⠀ -> U+2820 ⠠`: 2 +- `U+2820 ⠠ -> U+280A ⠊`: 1 +- `U+2820 ⠠ -> U+281E ⠞`: 1 + +Mismatch primary-class distribution: + +- `comparison_method`: 3 +- `corpus_suspect`: 14 +- `pending_rule_review`: 639 +- `unsupported_character_review`: 6 + +Representative `exact` samples: + +- `sentence_01.json` #54: 에버소울은 출시 전부터 지스타(G-STAR) 2022, 에이지에프(AGF) 2022 등 굵직한 국내 게임 및 서브컬처 행사에 출품해 기대를 모았다. 12월 말에는 사전 예약자 150만 명을 모객했다. + - expected: `⠝⠘⠎⠠⠥⠯⠵⠀⠰⠯⠠⠕⠀⠨⠾⠘⠍⠓⠎⠀⠨⠕⠠⠪` + - actual: `⠝⠘⠎⠠⠥⠯⠵⠀⠰⠯⠠⠕⠀⠨⠾⠘⠍⠓⠎⠀⠨⠕⠠⠪` + - current primary/reason: `exact` / `exact` +- `sentence_02.json` #649: 피아니스트 임동창과 TATARANG(타타랑)은 김호연재가 남긴 시를 읽으며 느낀 그녀의 담대함과 호연함을 관객들에게 전달하고자 이번 공연을 준비했다. + - expected: `⠙⠕⠣⠉⠕⠠⠪⠓⠪⠀⠕⠢⠊⠿⠰⠣⠶⠈⠧⠀⠴⠠⠠⠞` + - actual: `⠙⠕⠣⠉⠕⠠⠪⠓⠪⠀⠕⠢⠊⠿⠰⠣⠶⠈⠧⠀⠴⠠⠠⠞` + - current primary/reason: `exact` / `exact` +- `sentence_03.json` #779: 김영대 평론가는 ‘아미(ARMY)가 인정한 평론가’로 정평이 나 있는 만큼, 대한민국을 넘어 전 세계적인 사랑을 받고 있는 BTS의 팬들이라면 놓치지 말아야 할 방송으로 알차게 꾸며질 예정이다. + - expected: `⠈⠕⠢⠻⠊⠗⠀⠙⠻⠐⠷⠫⠉⠵⠀⠠⠦⠣⠑⠕⠦⠄⠴⠠` + - actual: `⠈⠕⠢⠻⠊⠗⠀⠙⠻⠐⠷⠫⠉⠵⠀⠠⠦⠣⠑⠕⠦⠄⠴⠠` + - current primary/reason: `exact` / `exact` +- `sentence_04.json` #596: 롯데백화점은 바닷가와 도심 등에 이어 올 봄에는 서울 경희궁 공원 입구에 ‘리얼스 마켓(RE:EARTH)’을 연다고 10일 밝혔다. + - expected: `⠐⠥⠄⠊⠝⠘⠗⠁⠚⠧⠨⠎⠢⠵⠀⠘⠊⠄⠫⠧⠀⠊⠥⠠` + - actual: `⠐⠥⠄⠊⠝⠘⠗⠁⠚⠧⠨⠎⠢⠵⠀⠘⠊⠄⠫⠧⠀⠊⠥⠠` + - current primary/reason: `exact` / `exact` + +Representative `localized_mismatch` samples: + +- `sentence_01.json` #142: ‘메타버스존’에서는 증강현실(AR)·가상현실(VR)에 적용되는 혁신기술이 소개된다. 글라스를 착용하고 최첨단 3D 센싱모듈이 구현하는 가상현실을 관람객이 직접 체험할 수 있도록 했다. + - expected: `⠠⠕⠂⠦⠄⠴⠠⠠⠁⠗⠠⠴⠐⠆⠫⠇⠶⠚⠡⠠⠕⠂⠦⠄` + - actual: `⠠⠕⠂⠦⠄⠴⠠⠠⠜⠠⠴⠐⠆⠸⠷⠫⠇⠶⠚⠡⠠⠕⠂⠸` + - first differing cell (zero-based): 34 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_02.json` #28: 체납된 세금은 전국 어디서나 은행 현금자동인출기(ATM)를 이용해 고지서 없이 현금과 신용카드로 납부가 가능하며, 자동응답시스템(ARS) 지방세 납부서비스(044-300-7114)를 이용해 가상계좌를 확인해 계좌이체를 하거나 신용카드로 납부할 수도 있다. + - expected: `⠓⠝⠢⠦⠄⠴⠠⠠⠁⠗⠎⠠⠴⠀⠨⠕⠘⠶⠠⠝⠀⠉⠃⠘` + - actual: `⠓⠝⠢⠦⠄⠴⠠⠠⠜⠎⠠⠴⠀⠨⠕⠘⠶⠠⠝⠀⠉⠃⠘⠍` + - first differing cell (zero-based): 129 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_03.json` #52: 종합식품기업 하림이 공룡 모양으로 생긴 동그랑땡 ‘용가리 땡’을 20일 출시했다. 용가리땡에는 특별 제작한 증강현실(AR) 공룡 카드까지 한 장씩 들어 있어 아이들의 많은 관심이 예상된다. + - expected: `⠠⠕⠂⠦⠄⠴⠠⠠⠁⠗⠠⠴⠀⠈⠿⠐⠬⠶⠀⠋⠊⠪⠠⠫` + - actual: `⠠⠕⠂⠦⠄⠴⠠⠠⠜⠠⠴⠀⠈⠿⠐⠬⠶⠀⠋⠊⠪⠠⠫⠨` + - first differing cell (zero-based): 126 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_04.json` #187: 주정차단속 ARS 알림서비스는 기존 불법 주정차 문자 알림서비스를 업그레이드 해 문자와 함께 자동응답서비스(ARS)로도 주정차단속을 알려주는 시스템이다. + - expected: `⠊⠒⠠⠭⠀⠴⠠⠠⠁⠗⠎⠲⠀⠣⠂⠐⠕⠢⠠⠎⠘⠕⠠⠪` + - actual: `⠊⠒⠠⠭⠀⠴⠠⠠⠜⠎⠲⠀⠣⠂⠐⠕⠢⠠⠎⠘⠕⠠⠪⠉` + - first differing cell (zero-based): 14 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` + +Representative `mismatch` samples: + +- `sentence_01.json` #142: ‘메타버스존’에서는 증강현실(AR)·가상현실(VR)에 적용되는 혁신기술이 소개된다. 글라스를 착용하고 최첨단 3D 센싱모듈이 구현하는 가상현실을 관람객이 직접 체험할 수 있도록 했다. + - expected: `⠠⠕⠂⠦⠄⠴⠠⠠⠁⠗⠠⠴⠐⠆⠫⠇⠶⠚⠡⠠⠕⠂⠦⠄` + - actual: `⠠⠕⠂⠦⠄⠴⠠⠠⠜⠠⠴⠐⠆⠸⠷⠫⠇⠶⠚⠡⠠⠕⠂⠸` + - first differing cell (zero-based): 34 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_02.json` #28: 체납된 세금은 전국 어디서나 은행 현금자동인출기(ATM)를 이용해 고지서 없이 현금과 신용카드로 납부가 가능하며, 자동응답시스템(ARS) 지방세 납부서비스(044-300-7114)를 이용해 가상계좌를 확인해 계좌이체를 하거나 신용카드로 납부할 수도 있다. + - expected: `⠓⠝⠢⠦⠄⠴⠠⠠⠁⠗⠎⠠⠴⠀⠨⠕⠘⠶⠠⠝⠀⠉⠃⠘` + - actual: `⠓⠝⠢⠦⠄⠴⠠⠠⠜⠎⠠⠴⠀⠨⠕⠘⠶⠠⠝⠀⠉⠃⠘⠍` + - first differing cell (zero-based): 129 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_03.json` #52: 종합식품기업 하림이 공룡 모양으로 생긴 동그랑땡 ‘용가리 땡’을 20일 출시했다. 용가리땡에는 특별 제작한 증강현실(AR) 공룡 카드까지 한 장씩 들어 있어 아이들의 많은 관심이 예상된다. + - expected: `⠠⠕⠂⠦⠄⠴⠠⠠⠁⠗⠠⠴⠀⠈⠿⠐⠬⠶⠀⠋⠊⠪⠠⠫` + - actual: `⠠⠕⠂⠦⠄⠴⠠⠠⠜⠠⠴⠀⠈⠿⠐⠬⠶⠀⠋⠊⠪⠠⠫⠨` + - first differing cell (zero-based): 126 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_04.json` #187: 주정차단속 ARS 알림서비스는 기존 불법 주정차 문자 알림서비스를 업그레이드 해 문자와 함께 자동응답서비스(ARS)로도 주정차단속을 알려주는 시스템이다. + - expected: `⠊⠒⠠⠭⠀⠴⠠⠠⠁⠗⠎⠲⠀⠣⠂⠐⠕⠢⠠⠎⠘⠕⠠⠪` + - actual: `⠊⠒⠠⠭⠀⠴⠠⠠⠜⠎⠲⠀⠣⠂⠐⠕⠢⠠⠎⠘⠕⠠⠪⠉` + - first differing cell (zero-based): 14 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` + ### `allcaps_roman_run_containing_ou` Of the 1816 candidates, 1666 are the actual `pending_rule_review` subcluster. The other 150 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. @@ -2362,6 +2449,12 @@ The tight-triangle cohort is not an implementation premise. Hangeul rule 49 assi Corpus contradictions remain a separate gate: identical inputs with conflicting references are classified as `corpus_suspect` before these cohorts are recorded and would appear explicitly in each mismatch primary-class distribution. Their absence does not prove a reference correct; it only means that this deterministic contradiction test did not fire. +### Uppercase Roman runs containing `AR` + +UEB 10.12.1 (2024 UEB PDF pp.191-192, printed pp.163-164) says not to use a contraction when letters within an abbreviation or acronym are known to be pronounced separately, but to use the contraction in case of doubt. Its official `DAR` example writes separate `a` and `r` cells, while UEB 10.12.2's official `START` example uses the `ar` groupsign. Both full-encoder controls pass. Thus an uppercase surface containing `AR` does not itself supply the pronunciation or lexical meaning needed to select either form. + +The output-localized cohort contains 1022 candidates, 360 exact controls, and 662 mismatches. Existing mismatch primary classes are preserved: 639 `pending_rule_review`, 14 `corpus_suspect`, 3 `comparison_method`, and 6 `unsupported_character_review`. Of 662 evaluable mismatches, 390 have their first difference inside the detected current-engine run: 383 `U+2801 ⠁ -> U+281C ⠜` and 0 `U+281C ⠜ -> U+2801 ⠁`. Across raw pending transitions and the final residual after localized cohorts, the target count is 393 -> 7; the reverse is 2 -> 2. Inputs denoting separately pronounced initialisms such as corpus `AR`/`ARS` coexist with exact or officially contracted controls. This is deterministic evidence for a pronunciation-dependent pending cohort, not an engine rule or primary reclassification. Representative shard/index samples are retained above. + ### Uppercase Roman runs containing `ST` UEB 10.12.1 (2024 UEB PDF pp.191-192, printed pp.163-164) says not to use a contraction when letters within an abbreviation or acronym are known to be pronounced separately, but to use the contraction in case of doubt. Rule 10.12.2 requires contractions in other abbreviations and acronyms. Consequently an uppercase `ST` surface alone cannot determine whether `s` + `t` or the `st` groupsign is required. The current cohort contains 1479 candidates, 678 exact controls, and 801 mismatches; primary classes remain 752 `pending_rule_review`, 31 `corpus_suspect`, 13 `comparison_method`, and 5 `unsupported_character_review`. Of 801 evaluable mismatches, 443 are localized to the detected current-engine run: 435 `U+280E ⠎ -> U+280C ⠌` and 1 `U+280C ⠌ -> U+280E ⠎`. The target's raw-to-residual count is 448 -> 13, and the reverse is 2 -> 1. Exact controls such as `STAYG`, `KAIST`, `DGIST`, and `POSTECH` coexist with localized mismatches such as `WSTS`, `HUST`, `OST`, and `USTR`; representative shard/index samples are preserved in the cluster table. This lexical/pronunciation distinction cannot be inferred from the input-only spelling, so no engine change or primary reclassification is made. diff --git a/libs/braillify/examples/nikl_corpus_analyze.rs b/libs/braillify/examples/nikl_corpus_analyze.rs index 4175e4ea..53b91cff 100644 --- a/libs/braillify/examples/nikl_corpus_analyze.rs +++ b/libs/braillify/examples/nikl_corpus_analyze.rs @@ -582,6 +582,7 @@ const KOREAN_INLINE_PARENTHESIZED_OPERATOR: &str = const TIGHT_TRIANGLE_BEFORE_KOREAN: &str = "tight_triangle_mark_immediately_before_korean"; const ALLCAPS_ROMAN_RUN_CONTAINING_OU: &str = "allcaps_roman_run_containing_ou"; const ALLCAPS_ROMAN_RUN_CONTAINING_ST: &str = "allcaps_roman_run_containing_st"; +const ALLCAPS_ROMAN_RUN_CONTAINING_AR: &str = "allcaps_roman_run_containing_ar"; const ATTACHED_ASCII_ROMAN_SEGMENTS_JOINED_BY_AMPERSAND: &str = "attached_ascii_roman_segments_joined_by_ampersand"; const SINGLE_CAPITAL_PARENTHESIZED_DIGITS: &str = "single_capital_followed_by_parenthesized_digits"; @@ -952,6 +953,10 @@ fn allcaps_roman_runs_containing_st(input: &str) -> Vec { allcaps_roman_runs_containing_pair(input, b"ST") } +fn allcaps_roman_runs_containing_ar(input: &str) -> Vec { + allcaps_roman_runs_containing_pair(input, b"AR") +} + /// Finds complete ASCII-letter sequences joined directly by one or more /// ampersands, such as the UEB §3.1.1 examples `AT&T` and `B&B`. Whitespace, /// Korean text, empty segments, and alphanumeric outer continuations are @@ -1615,6 +1620,23 @@ fn first_difference_in_allcaps_st_run(item: &EncodedCase) -> bool { .any(|range| range.contains(&first_difference)) } +fn allcaps_ar_actual_ranges(input: &str, actual: &str) -> Vec> { + current_engine_signature_ranges(input, actual, &allcaps_roman_runs_containing_ar(input), 0) +} + +fn first_difference_in_allcaps_ar_run(item: &EncodedCase) -> bool { + let Ok(actual) = &item.actual else { + return false; + }; + if actual == &item.located.case.unicode { + return false; + } + let first_difference = first_difference_cell(&item.located.case.unicode, actual); + allcaps_ar_actual_ranges(&item.located.case.input, actual) + .into_iter() + .any(|range| range.contains(&first_difference)) +} + fn attached_roman_ampersand_boundary_ranges( input: &str, actual: &str, @@ -1886,11 +1908,15 @@ fn first_difference_claimed_before_attached_roman_ampersand(item: &EncodedCase) first_difference_claimed_before_allcaps_st(item) || first_difference_in_allcaps_st_run(item) } -fn first_difference_claimed_by_localized_cohort(item: &EncodedCase) -> bool { +fn first_difference_claimed_before_allcaps_ar(item: &EncodedCase) -> bool { first_difference_claimed_before_attached_roman_ampersand(item) || first_difference_in_attached_roman_ampersand(item) } +fn first_difference_claimed_by_localized_cohort(item: &EncodedCase) -> bool { + first_difference_claimed_before_allcaps_ar(item) || first_difference_in_allcaps_ar_run(item) +} + /// Input-only candidate gate for acronym expansions such as /// `HCA(Home Connectivity Alliance)`. /// @@ -2494,6 +2520,10 @@ fn analyze( ALLCAPS_ROMAN_RUN_CONTAINING_ST.to_string(), PendingRuleReviewClusterStats::default(), ), + ( + ALLCAPS_ROMAN_RUN_CONTAINING_AR.to_string(), + PendingRuleReviewClusterStats::default(), + ), ( ATTACHED_ASCII_ROMAN_SEGMENTS_JOINED_BY_AMPERSAND.to_string(), PendingRuleReviewClusterStats::default(), @@ -2678,6 +2708,15 @@ fn analyze( ), true, ), + ( + ALLCAPS_ROMAN_RUN_CONTAINING_AR, + !allcaps_roman_runs_containing_ar(&item.located.case.input).is_empty(), + Some( + !first_difference_claimed_before_allcaps_ar(item) + && first_difference_in_allcaps_ar_run(item), + ), + true, + ), ( ATTACHED_ASCII_ROMAN_SEGMENTS_JOINED_BY_AMPERSAND, !attached_ascii_roman_ampersand_spans(&item.located.case.input).is_empty(), @@ -3905,6 +3944,77 @@ fn markdown(report: &AnalysisReport) -> String { not prove a reference correct; it only means that this deterministic contradiction test \ did not fire.\n", ); + if let Some(stats) = report + .pending_rule_review_clusters + .get(ALLCAPS_ROMAN_RUN_CONTAINING_AR) + { + let primary_count = |name: &str| { + stats + .mismatch_primary_classes + .get(name) + .copied() + .unwrap_or(0) + }; + let localized_transition = |name: &str| { + stats + .first_difference_in_output_signature_transitions + .get(name) + .copied() + .unwrap_or(0) + }; + let raw_transition = |name: &str| { + report + .pending_first_difference_cell_transitions + .get(name) + .map(|transition| transition.cases) + .unwrap_or(0) + }; + let residual_transition = |name: &str| { + report + .pending_first_difference_transitions_after_localized_cohorts + .get(name) + .map(|transition| transition.cases) + .unwrap_or(0) + }; + let target = "U+2801 ⠁ -> U+281C ⠜"; + let reverse = "U+281C ⠜ -> U+2801 ⠁"; + text.push_str(&format!( + "\n### Uppercase Roman runs containing `AR`\n\n\ + UEB 10.12.1 (2024 UEB PDF pp.191-192, printed pp.163-164) says not to \ + use a contraction when letters within an abbreviation or acronym are known to be \ + pronounced separately, but to use the contraction in case of doubt. Its official \ + `DAR` example writes separate `a` and `r` cells, while UEB 10.12.2's official \ + `START` example uses the `ar` groupsign. Both full-encoder controls pass. Thus an \ + uppercase surface containing `AR` does not itself supply the pronunciation or \ + lexical meaning needed to select either form.\n\n\ + The output-localized cohort contains {} candidates, {} exact controls, and {} \ + mismatches. Existing mismatch primary classes are preserved: {} \ + `pending_rule_review`, {} `corpus_suspect`, {} `comparison_method`, and {} \ + `unsupported_character_review`. Of {} evaluable mismatches, {} have their first \ + difference inside the detected current-engine run: {} `{target}` and {} \ + `{reverse}`. Across raw pending transitions and the final residual after localized \ + cohorts, the target count is {} -> {}; the reverse is {} -> {}. Inputs denoting \ + separately pronounced initialisms such as corpus `AR`/`ARS` coexist with exact or \ + officially contracted controls. This is deterministic evidence for a \ + pronunciation-dependent pending cohort, not an engine rule or primary \ + reclassification. Representative shard/index samples are retained above.\n", + stats.candidates, + stats.exact, + stats.mismatch, + primary_count("pending_rule_review"), + primary_count("corpus_suspect"), + primary_count("comparison_method"), + primary_count("unsupported_character_review"), + stats.output_signature_mismatches_evaluated, + stats.first_difference_in_output_signature, + localized_transition(target), + localized_transition(reverse), + raw_transition(target), + residual_transition(target), + raw_transition(reverse), + residual_transition(reverse), + )); + } if let Some(stats) = report .pending_rule_review_clusters .get(ALLCAPS_ROMAN_RUN_CONTAINING_ST) @@ -5148,6 +5258,33 @@ mod tests { assert_eq!(actual, expected); } + #[rstest::rstest] + #[case::official_and_corpus_shapes("DAR AR ARS START", vec!["DAR", "AR", "ARS", "START"])] + #[case::lowercase("Ar", vec![])] + #[case::mixed_case("aR", vec![])] + #[case::no_ar("WHO", vec![])] + #[case::digit_prefix("1AR", vec![])] + #[case::digit_suffix("ARS2", vec![])] + fn detects_allcaps_roman_runs_containing_ar(#[case] input: &str, #[case] expected: Vec<&str>) { + let actual = allcaps_roman_runs_containing_ar(input) + .into_iter() + .map(|run| &input[run.start_byte..run.end_byte]) + .collect::>(); + assert_eq!(actual, expected); + } + + /// UEB 10.12.1-10.12.2 supply both outcomes: `DAR` is pronounced as + /// separate letters, whereas `START` uses the `ar` groupsign. + #[rstest::rstest] + #[case::separate_letters("DAR", "⠠⠠⠙⠁⠗")] + #[case::contracted_acronym("START", "⠠⠠⠌⠜⠞")] + fn full_encoder_preserves_official_ueb_ar_controls( + #[case] input: &str, + #[case] expected: &str, + ) { + assert_eq!(braillify::encode_to_unicode(input).unwrap(), expected); + } + #[rstest::rstest] #[case::common_initialisms("M&A P&G R&D", vec!["M&A", "P&G", "R&D"])] #[case::ueb_examples("AT&T B&B", vec!["AT&T", "B&B"])] @@ -5422,6 +5559,18 @@ mod tests { assert!(ranges[0].end <= actual.chars().count()); } + #[rstest::rstest] + #[case::augmented_reality("증강현실(AR)")] + #[case::automated_response("자동응답시스템(ARS)")] + fn localizes_allcaps_ar_signature_in_complete_output(#[case] input: &str) { + let actual = braillify::encode_to_unicode(input).expect("probe must encode"); + let ranges = allcaps_ar_actual_ranges(input, &actual); + + assert_eq!(ranges.len(), 1); + assert!(ranges[0].start < ranges[0].end); + assert!(ranges[0].end <= actual.chars().count()); + } + #[rstest::rstest] #[case::merger("인수·합병(M&A) 시장")] #[case::brand("브랜드(P&G) 편입")] From 2088b6da167ae8955eee8a4ab616d680b596f6f7 Mon Sep 17 00:00:00 2001 From: devfive Date: Tue, 1 Sep 2026 13:23:22 +0900 Subject: [PATCH 37/77] Diagnose Roman continuation after enclosures --- docs/corpus-analysis/NIKL_2025_V1.md | 171 +++++++++--- .../braillify/examples/nikl_corpus_analyze.rs | 246 +++++++++++++++++- 2 files changed, 377 insertions(+), 40 deletions(-) diff --git a/docs/corpus-analysis/NIKL_2025_V1.md b/docs/corpus-analysis/NIKL_2025_V1.md index b2fcbbee..a1508f4c 100644 --- a/docs/corpus-analysis/NIKL_2025_V1.md +++ b/docs/corpus-analysis/NIKL_2025_V1.md @@ -298,50 +298,27 @@ This ranking removes only cases whose first difference is inside an existing out | Rank | Expected → actual first cell | Residual cases | |---:|---|---:| -| 1 | `U+2834 ⠴ -> U+2830 ⠰` | 333 | -| 2 | `U+2811 ⠑ -> U+282B ⠫` | 325 | -| 3 | `U+2810 ⠐ -> U+2832 ⠲` | 297 | -| 4 | `U+2832 ⠲ -> U+2838 ⠸` | 290 | -| 5 | `U+2834 ⠴ -> U+2800 ⠀` | 261 | -| 6 | `U+2800 ⠀ -> U+2832 ⠲` | 227 | -| 7 | `U+2826 ⠦ -> U+2800 ⠀` | 223 | -| 8 | `U+2810 ⠐ -> U+2802 ⠂` | 211 | -| 9 | `U+2817 ⠗ -> U+2820 ⠠` | 165 | -| 10 | `U+2820 ⠠ -> U+2809 ⠉` | 162 | -| 11 | `U+283C ⠼ -> U+2800 ⠀` | 153 | -| 12 | `U+2826 ⠦ -> U+2810 ⠐` | 147 | -| 13 | `U+281B ⠛ -> U+2823 ⠣` | 144 | -| 14 | `U+2810 ⠐ -> U+2800 ⠀` | 130 | -| 15 | `U+2811 ⠑ -> U+283B ⠻` | 101 | -| 16 | `U+2810 ⠐ -> U+2814 ⠔` | 98 | +| 1 | `U+2811 ⠑ -> U+282B ⠫` | 325 | +| 2 | `U+2810 ⠐ -> U+2832 ⠲` | 297 | +| 3 | `U+2832 ⠲ -> U+2838 ⠸` | 290 | +| 4 | `U+2834 ⠴ -> U+2800 ⠀` | 261 | +| 5 | `U+2800 ⠀ -> U+2832 ⠲` | 227 | +| 6 | `U+2826 ⠦ -> U+2800 ⠀` | 223 | +| 7 | `U+2810 ⠐ -> U+2802 ⠂` | 211 | +| 8 | `U+2817 ⠗ -> U+2820 ⠠` | 164 | +| 9 | `U+2820 ⠠ -> U+2809 ⠉` | 162 | +| 10 | `U+283C ⠼ -> U+2800 ⠀` | 153 | +| 11 | `U+2826 ⠦ -> U+2810 ⠐` | 147 | +| 12 | `U+281B ⠛ -> U+2823 ⠣` | 144 | +| 13 | `U+2810 ⠐ -> U+2800 ⠀` | 130 | +| 14 | `U+2811 ⠑ -> U+283B ⠻` | 101 | +| 15 | `U+2810 ⠐ -> U+2814 ⠔` | 98 | +| 16 | `U+2834 ⠴ -> U+2830 ⠰` | 98 | | 17 | `U+280E ⠎ -> U+2820 ⠠` | 92 | | 18 | `U+2804 ⠄ -> U+2834 ⠴` | 86 | | 19 | `U+2803 ⠃ -> U+2820 ⠠` | 85 | | 20 | `U+2824 ⠤ -> U+2800 ⠀` | 78 | -### Residual `U+2834 ⠴ -> U+2830 ⠰` - -- `sentence_01.json` #337: ‘아리송(ARISONG)’, ‘Boyfriend’에 이어 ‘오로라’에도 인기 안무가 리정이 안무 메이킹에 참여해 기대를 모은다. 리정과 7인 7색 매력의 시그니처가 만나 어떤 색다른 퍼포먼스를 선사할지 이목이 집중된다. - - expected: `⠠⠴⠴⠄⠐⠀⠠⠦⠴⠠⠃⠕⠽⠋⠗⠊⠢⠙⠴⠄⠝⠀⠕⠎` - - actual: `⠠⠴⠴⠄⠐⠀⠠⠦⠰⠠⠃⠕⠽⠋⠗⠊⠢⠙⠴⠄⠝⠀⠕⠎` - - first differing cell (zero-based): 25 - - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_02.json` #461: 주요 연수 내용은 교육현장의 요구를 적극적으로 반영해 인공지능(AI), ChatGTP, 영어그림책 등을 활용한 다양한 교수학습 방법, 세계시민교육 실천사례, 영미권 원어민 강사와의 협력수업 방법 등으로 구성했다. - - expected: `⠠⠠⠁⠊⠠⠴⠐⠀⠴⠠⠡⠁⠞⠠⠠⠛⠞⠏⠐⠀⠻⠎⠈⠪` - - actual: `⠠⠠⠁⠊⠠⠴⠐⠀⠰⠠⠡⠁⠞⠠⠠⠛⠞⠏⠐⠀⠻⠎⠈⠪` - - first differing cell (zero-based): 66 - - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_03.json` #57: ACE 미국30년국채액티브(H) ETF는 한국투자신탁운용이 첫 번째로 상장한 월배당형 상품이다. 예상 배당률은 연 3%다. 첫 번째 분배금은 5월 초 지급될 예정이다. - - expected: `⠦⠄⠴⠠⠓⠠⠴⠀⠴⠠⠠⠑⠞⠋⠲⠉⠵⠀⠚⠒⠈⠍⠁⠓` - - actual: `⠦⠄⠴⠠⠓⠠⠴⠀⠰⠠⠠⠑⠞⠋⠲⠉⠵⠀⠚⠒⠈⠍⠁⠓` - - first differing cell (zero-based): 38 - - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_04.json` #298: HMM은 카타르 하마드에서 당초 수량보다 16개를 추가한 586개의 임시주택 컨테이너를 다목적선(MPV) ‘HMM 울산호’에 선적했다. 이 선박은 27일 출항, 다음달 10일경 튀르키예 이스켄데룬에 도착할 예정이다. - - expected: `⠍⠏⠧⠠⠴⠀⠠⠦⠴⠠⠠⠓⠍⠍⠲⠀⠯⠇⠒⠚⠥⠴⠄⠝` - - actual: `⠍⠏⠧⠠⠴⠀⠠⠦⠰⠠⠠⠓⠍⠍⠲⠀⠯⠇⠒⠚⠥⠴⠄⠝` - - first differing cell (zero-based): 98 - - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` - ### Residual `U+2811 ⠑ -> U+282B ⠫` - `sentence_01.json` #51: 와이캅 픽셀은 와이어·패키지·렌즈가 필요 없는 ‘와이캅’ 기술을 기반으로 한다. 적녹청(RGB) 3개의 마이크로 LED를 수직 방향으로 적층한 세계 최초의 풀 컬러 원칩 기술이다. @@ -549,6 +526,29 @@ This ranking removes only cases whose first difference is inside an existing out - first differing cell (zero-based): 60 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +### Residual `U+283C ⠼ -> U+2800 ⠀` + +- `sentence_01.json` #633: 미국 항공우주청(NASA)은 26일(현지 시간) 이 같은 내용을 발표하면서 “‘2023 BU’라는 소행성이 남미의 남단을 지나갈 것”이라며 “2023 BU는 지름이 약 3.5~8.5m 크기”라고 설명했다. + - expected: `⠐⠪⠢⠕⠀⠜⠁⠀⠼⠉⠲⠑⠈⠔⠼⠓⠲⠑⠴⠍⠲⠀⠋⠪` + - actual: `⠐⠪⠢⠕⠀⠜⠁⠀⠀⠼⠉⠲⠑⠈⠔⠼⠓⠲⠑⠍⠀⠋⠪⠈` + - first differing cell (zero-based): 152 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_02.json` #893: 그러나, 이 아파트들은 금강 제1철교 반경 50~800m 내에 위치해 있어 철도소음 영향을 많이 받고 있다. 특히, 금강센트럴파크서희스타힐스는 야간소음이 67dB로 기준치(60dB)를 초과하고 있다. + - expected: `⠈⠬⠀⠘⠒⠈⠻⠀⠼⠑⠚⠈⠔⠼⠓⠚⠚⠴⠍⠲⠀⠉⠗⠝` + - actual: `⠈⠬⠀⠘⠒⠈⠻⠀⠀⠼⠑⠚⠈⠔⠼⠓⠚⠚⠍⠀⠀⠉⠗⠝` + - first differing cell (zero-based): 34 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_03.json` #140: 하림의 독자적인 기술로 만든 분리닭가슴살단백질(ICBP)은 닭가슴살을 그대로 섭취할 때보다 지방을 1/2 수준으로 낮추고, 단백질 함량은 4배 이상 늘린 순도 높은 고함량 단백질(함량 90%)을 구현했다. + - expected: `⠊⠀⠨⠕⠘⠶⠮⠀⠼⠁⠸⠌⠼⠃⠀⠠⠍⠨⠛⠪⠐⠥⠀⠉` + - actual: `⠊⠀⠨⠕⠘⠶⠮⠀⠀⠼⠁⠸⠌⠼⠃⠀⠀⠠⠍⠨⠛⠪⠐⠥` + - first differing cell (zero-based): 99 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_04.json` #871: 2025년 인류를 달에 다시 보내기 위해 미국 항공우주국(NASA)이 개발한 ‘우주발사시스템(SLS)’을 능가한다. SLS는 길이 98M, 추력은 3900t이다. + - expected: `⠉⠵⠀⠈⠕⠂⠕⠀⠼⠊⠓⠴⠠⠍⠐⠀⠰⠍⠐⠱⠁⠵⠀⠼` + - actual: `⠉⠵⠀⠈⠕⠂⠕⠀⠀⠼⠊⠓⠠⠍⠐⠀⠀⠰⠍⠐⠱⠁⠵⠀` + - first differing cell (zero-based): 124 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` + ## Cross-cutting input-only structural cohorts These are cross-cutting input-only structural cohorts, not new primary classes and not engine routing rules. Candidate selection never changes a case's existing primary class by itself. Only cohort members already classified as `pending_rule_review` form a pending subcluster; exact and other-primary members are controls that retain their existing outcomes. A separate classifier may use independently justified, output-localized PDF evidence, as in the rule-34 three-cell contradiction below. The `uppercase_roman_headword_closed_multiword_parenthetical` gate requires a two-or-more character uppercase ASCII headword immediately followed by a closed parenthesis whose contents are two or more ASCII Roman words separated only by spaces. Because the contents admit only letters and spaces, visible operators, subscript/superscript notation, and nested parentheses are excluded deterministically. The `mixed_roman_korean_word_before_uppercase_headword_expansion` gate further requires a whitespace-delimited preceding word that contains both ASCII Roman and Korean and ends in Korean. It locates the following uppercase headword itself, including its immediately preceding emitted cell, so an earlier Roman entry in the same sentence cannot satisfy the output audit. The `single_capital_followed_by_parenthesized_digits` gate requires a standalone capital, a non-empty closed ASCII-digit parenthetical, and alphanumeric outer boundaries. It likewise includes the emitted entry-boundary cell in localization. The `uppercase_roman_run_followed_by_hyphen_digits` gate requires a maximal uppercase ASCII run, literal ASCII hyphen-minus, a non-empty digit run, and alphanumeric outer boundaries. Its localized range includes the current encoded run and its immediately preceding cell, keeping `F-35`-style routing separate from both parenthesized digits and headword expansions. The `standalone_multi_character_uppercase_roman_word` gate finds maximal ASCII-letter runs of two or more capitals with non-alphanumeric boundaries; a run immediately followed by `(` is excluded so the HCA-style headword itself is not counted by both gates. The `korean_prefixed_closed_allcaps_parenthetical` gate requires an immediately preceding Korean character and a closed body of two or more uppercase ASCII letters. It intentionally contains both acronym annotations (`책임자(COO)`) and scientific formulae (`일산화탄소(CO)`) so their semantic collision remains measurable. The `korean_prefixed_closed_roman_annotation_rule_34_order` gate accepts the narrower rule-34 body grammar after an immediately preceding Korean character and localizes only the current engine's Korean opening-parenthesis cells. This distinguishes the PDF's parenthesis-before-Roman-indicator order from unrelated differences later in the same sentence. The `multi_character_allcaps_roman_runs_joined_by_middle_dot` gate requires two maximal ASCII-letter runs of at least two capitals joined directly by U+00B7, with non-alphanumeric outer boundaries. It records shapes such as `AI·SW` without assigning prose, mathematics, or science semantics. The `roman_run_immediately_before_attached_middle_dot_boundary` gate is output-localized: it requires a maximal ASCII-letter run immediately before U+00B7 and an attached Korean character or ASCII-letter run after it, then searches for that whole current-engine signature in the actual output. It therefore isolates the Roman terminator boundary without treating unrelated middle dots elsewhere in the sentence as causal. The `korean_inline_parenthesized_single_arithmetic_operator` gate requires an immediate `Korean(` + one rule-45 arithmetic operator + `)Korean` span. Unlike broad coexistence traits, it also locates the current engine's emitted structure and counts a mismatch as signature-local only when the sentence's first differing cell falls inside that output range. The `allcaps_roman_run_containing_ou` gate finds maximal, alphanumeric-delimited uppercase ASCII runs containing adjacent `OU`. It locates the independently encoded run signature in the complete current output and counts only first differences inside that signature as localized. The `allcaps_roman_run_containing_st` gate applies the same output-position requirement to adjacent `ST`; it is kept separate because UEB 10.12 makes contraction use depend on how an abbreviation or acronym is pronounced. The `attached_ascii_roman_segments_joined_by_ampersand` gate requires non-empty ASCII-letter segments joined directly by `&`, excludes spaced/Korean/alphanumeric continuations, and localizes only the current output cell immediately before the ampersand through an independently encoded real-input prefix. The `decimal_point_between_ascii_digits` gate finds whitespace-delimited words containing `digit.digit` and reproduces each whole word in a neutral Korean context, so suffixes and punctuation remain part of the current-engine signature. The `compact_numeric_ascii_letter_suffix` gate finds a numeric prefix immediately followed by ASCII letters, includes the immediately preceding output cell as its entry boundary, and retains suffix-specific outcome counts. It intentionally includes both possible rule-69 units and ambiguous variable/identifier forms; membership alone does not assign unit semantics. The `rule69_ascii_unit_before_terminator_skipping_symbol` gate is narrower: it accepts only ASCII unit spellings already supported by rule 69, includes the immediately following rule-33/34 punctuation cell in the localized signature, and does not infer new units. The `tight_triangle_mark_immediately_before_korean` gate requires literal `△한글` with no input space and includes the first following Korean cell in its localized output range, so an observed missing-space difference is measured at the mark boundary. @@ -572,6 +572,7 @@ These are cross-cutting input-only structural cohorts, not new primary classes a | `pure_allcaps_segment_before_hyphen_and_multi_allcaps_segment_after` | 448 | 127 | 321 | 0 | | `roman_hyphenated_word_after_whitespace_following_korean_word` | 361 | 0 | 361 | 0 | | `roman_parenthetical_headword_after_whitespace_following_korean_word` | 4695 | 3363 | 1332 | 0 | +| `roman_run_after_whitespace_following_closed_roman_enclosure` | 1093 | 138 | 955 | 0 | | `roman_run_immediately_before_attached_middle_dot_boundary` | 577 | 0 | 577 | 0 | | `rule69_ascii_unit_before_terminator_skipping_symbol` | 440 | 336 | 104 | 0 | | `single_capital_followed_by_parenthesized_digits` | 1361 | 0 | 1361 | 0 | @@ -1718,6 +1719,92 @@ Representative `mismatch` samples: - first differing cell (zero-based): 0 - current primary/reason: `unsupported_character_review` / `unsupported_character_review` +### `roman_run_after_whitespace_following_closed_roman_enclosure` + +Of the 1093 candidates, 627 are the actual `pending_rule_review` subcluster. The other 466 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. + +For this output-signature audit, 955 mismatches were evaluable and 240 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. + +Localized first-difference transitions: + +- `U+2834 ⠴ -> U+2830 ⠰`: 235 +- `U+2815 ⠕ -> U+2837 ⠷`: 1 +- `U+2817 ⠗ -> U+2820 ⠠`: 1 +- `U+2820 ⠠ -> U+2805 ⠅`: 1 +- `U+2820 ⠠ -> U+280E ⠎`: 1 + +Mismatch primary-class distribution: + +- `corpus_suspect`: 321 +- `pending_rule_review`: 627 +- `unsupported_character_review`: 7 + +Representative `exact` samples: + +- `sentence_01.json` #2785: 미래에셋자산운용은 미국 상장지수펀드(ETF) 운용 자회사 글로벌 엑스(Global X)가 ‘Global X 프롭테크(PropTech) ETF’를 뉴욕증권거래소에 신규 상장했다고 18일 밝혔다. + - expected: `⠑⠕⠐⠗⠝⠠⠝⠄⠨⠇⠒⠛⠬⠶⠵⠀⠑⠕⠈⠍⠁⠀⠇⠶` + - actual: `⠑⠕⠐⠗⠝⠠⠝⠄⠨⠇⠒⠛⠬⠶⠵⠀⠑⠕⠈⠍⠁⠀⠇⠶` + - current primary/reason: `exact` / `exact` +- `sentence_02.json` #1282: 이를 위해 중국 전기차 업체 비야디(BYD), BMW, 스텔란티스 등 80여개 완성차 고객사 최고경영자(CEO)와 주요 임원 약 200명을 초청해 프라이빗 부스에서 수주 미팅을 계획했다. + - expected: `⠕⠐⠮⠀⠍⠗⠚⠗⠀⠨⠍⠶⠈⠍⠁⠀⠨⠾⠈⠕⠰⠣⠀⠎` + - actual: `⠕⠐⠮⠀⠍⠗⠚⠗⠀⠨⠍⠶⠈⠍⠁⠀⠨⠾⠈⠕⠰⠣⠀⠎` + - current primary/reason: `exact` / `exact` +- `sentence_03.json` #46: 삼성자산운용은 인도 시장에 투자하는 ‘KODEX 인도 Nifty50’, ‘KODEX 인도 Nifty50 레버리지’ 상장지수펀드(ETF) 2종을 21일 상장한다고 밝혔다. + - expected: `⠇⠢⠠⠻⠨⠇⠒⠛⠬⠶⠵⠀⠟⠊⠥⠀⠠⠕⠨⠶⠝⠀⠓⠍` + - actual: `⠇⠢⠠⠻⠨⠇⠒⠛⠬⠶⠵⠀⠟⠊⠥⠀⠠⠕⠨⠶⠝⠀⠓⠍` + - current primary/reason: `exact` / `exact` +- `sentence_04.json` #2333: 삼성전자가 2023년 네오(Neo) TV 신제품 전 모델과 스마트 모니터 등에 색약자를 위한 ‘씨컬러스 모드’를 기본 기능으로 탑재한다고 25일 밝혔다. + - expected: `⠇⠢⠠⠻⠨⠾⠨⠫⠀⠼⠃⠚⠃⠉⠀⠉⠡⠀⠉⠝⠥⠦⠄⠴` + - actual: `⠇⠢⠠⠻⠨⠾⠨⠫⠀⠼⠃⠚⠃⠉⠀⠉⠡⠀⠉⠝⠥⠦⠄⠴` + - current primary/reason: `exact` / `exact` + +Representative `localized_mismatch` samples: + +- `sentence_01.json` #337: ‘아리송(ARISONG)’, ‘Boyfriend’에 이어 ‘오로라’에도 인기 안무가 리정이 안무 메이킹에 참여해 기대를 모은다. 리정과 7인 7색 매력의 시그니처가 만나 어떤 색다른 퍼포먼스를 선사할지 이목이 집중된다. + - expected: `⠠⠴⠴⠄⠐⠀⠠⠦⠴⠠⠃⠕⠽⠋⠗⠊⠢⠙⠴⠄⠝⠀⠕⠎` + - actual: `⠠⠴⠴⠄⠐⠀⠠⠦⠰⠠⠃⠕⠽⠋⠗⠊⠢⠙⠴⠄⠝⠀⠕⠎` + - first differing cell (zero-based): 25 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_02.json` #461: 주요 연수 내용은 교육현장의 요구를 적극적으로 반영해 인공지능(AI), ChatGTP, 영어그림책 등을 활용한 다양한 교수학습 방법, 세계시민교육 실천사례, 영미권 원어민 강사와의 협력수업 방법 등으로 구성했다. + - expected: `⠠⠠⠁⠊⠠⠴⠐⠀⠴⠠⠡⠁⠞⠠⠠⠛⠞⠏⠐⠀⠻⠎⠈⠪` + - actual: `⠠⠠⠁⠊⠠⠴⠐⠀⠰⠠⠡⠁⠞⠠⠠⠛⠞⠏⠐⠀⠻⠎⠈⠪` + - first differing cell (zero-based): 66 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_03.json` #57: ACE 미국30년국채액티브(H) ETF는 한국투자신탁운용이 첫 번째로 상장한 월배당형 상품이다. 예상 배당률은 연 3%다. 첫 번째 분배금은 5월 초 지급될 예정이다. + - expected: `⠦⠄⠴⠠⠓⠠⠴⠀⠴⠠⠠⠑⠞⠋⠲⠉⠵⠀⠚⠒⠈⠍⠁⠓` + - actual: `⠦⠄⠴⠠⠓⠠⠴⠀⠰⠠⠠⠑⠞⠋⠲⠉⠵⠀⠚⠒⠈⠍⠁⠓` + - first differing cell (zero-based): 38 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_04.json` #298: HMM은 카타르 하마드에서 당초 수량보다 16개를 추가한 586개의 임시주택 컨테이너를 다목적선(MPV) ‘HMM 울산호’에 선적했다. 이 선박은 27일 출항, 다음달 10일경 튀르키예 이스켄데룬에 도착할 예정이다. + - expected: `⠍⠏⠧⠠⠴⠀⠠⠦⠴⠠⠠⠓⠍⠍⠲⠀⠯⠇⠒⠚⠥⠴⠄⠝` + - actual: `⠍⠏⠧⠠⠴⠀⠠⠦⠰⠠⠠⠓⠍⠍⠲⠀⠯⠇⠒⠚⠥⠴⠄⠝` + - first differing cell (zero-based): 98 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` + +Representative `mismatch` samples: + +- `sentence_01.json` #14: 새롭게 선보인 DRX 브랜딩과 팀 컬러는 2023년 1월 1일부터 DRX의 온오프라인 콘텐츠, 엠디(MD), SNS, 유튜브 등 모든 제작물에 적용될 예정이다. + - expected: `⠰⠪⠐⠀⠝⠢⠊⠕⠴⠐⠣⠠⠠⠍⠙⠐⠜⠂⠀⠠⠠⠎⠝⠎` + - actual: `⠰⠪⠐⠀⠝⠢⠊⠕⠦⠄⠴⠠⠠⠍⠙⠠⠴⠐⠀⠰⠠⠠⠎⠝` + - first differing cell (zero-based): 97 + - current primary/reason: `corpus_suspect` / `rule34_roman_indicator_before_opening_parenthesis` +- `sentence_02.json` #78: 마지막으로 신규 회원사인 ㈜그레비티(대표 최항주)에 대한 소개와 서중석 교수의 발제로 ‘글쓰기에 필요한 다양한 생성형(Generative) AI Searcher’에 대한 토론이 진행됐다. + - expected: `⠑⠨⠕⠑⠁⠪⠐⠥⠀⠠⠟⠈⠩⠀⠚⠽⠏⠒⠇⠟⠀⠦⠄⠨` + - actual: `` + - error: `Invalid symbol character` + - first differing cell (zero-based): 0 + - current primary/reason: `unsupported_character_review` / `unsupported_character_review` +- `sentence_03.json` #57: ACE 미국30년국채액티브(H) ETF는 한국투자신탁운용이 첫 번째로 상장한 월배당형 상품이다. 예상 배당률은 연 3%다. 첫 번째 분배금은 5월 초 지급될 예정이다. + - expected: `⠦⠄⠴⠠⠓⠠⠴⠀⠴⠠⠠⠑⠞⠋⠲⠉⠵⠀⠚⠒⠈⠍⠁⠓` + - actual: `⠦⠄⠴⠠⠓⠠⠴⠀⠰⠠⠠⠑⠞⠋⠲⠉⠵⠀⠚⠒⠈⠍⠁⠓` + - first differing cell (zero-based): 38 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_04.json` #62: LG디스플레이가 자사의 유기발광다이오드(OLED) TV 패널이 글로벌 친환경 인증기관인 카본 트러스트(Carbon Trust)로 부터 탄소발자국 인증을 획득했다고 19일 밝혔다. + - expected: `⠧⠶⠊⠣⠕⠥⠊⠪⠴⠐⠣⠠⠠⠕⠇⠫⠐⠜⠀⠠⠠⠞⠧⠲` + - actual: `⠧⠶⠊⠣⠕⠥⠊⠪⠦⠄⠴⠠⠠⠕⠇⠫⠠⠴⠀⠰⠠⠠⠞⠧` + - first differing cell (zero-based): 35 + - current primary/reason: `corpus_suspect` / `rule34_roman_indicator_before_opening_parenthesis` + ### `roman_run_immediately_before_attached_middle_dot_boundary` Of the 577 candidates, 565 are the actual `pending_rule_review` subcluster. The other 12 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. @@ -2455,6 +2542,12 @@ UEB 10.12.1 (2024 UEB PDF pp.191-192, printed pp.163-164) says not to use a cont The output-localized cohort contains 1022 candidates, 360 exact controls, and 662 mismatches. Existing mismatch primary classes are preserved: 639 `pending_rule_review`, 14 `corpus_suspect`, 3 `comparison_method`, and 6 `unsupported_character_review`. Of 662 evaluable mismatches, 390 have their first difference inside the detected current-engine run: 383 `U+2801 ⠁ -> U+281C ⠜` and 0 `U+281C ⠜ -> U+2801 ⠁`. Across raw pending transitions and the final residual after localized cohorts, the target count is 393 -> 7; the reverse is 2 -> 2. Inputs denoting separately pronounced initialisms such as corpus `AR`/`ARS` coexist with exact or officially contracted controls. This is deterministic evidence for a pronunciation-dependent pending cohort, not an engine rule or primary reclassification. Representative shard/index samples are retained above. +### Roman run after a closed Roman enclosure + +Korean rule 29 places one Roman indicator/terminator pair around consecutive Roman items, with `Los Angeles` and `Table of Contents` as its printed multiword examples. Rule 34 separately omits the Roman terminator when Roman text is enclosed by quotation marks or parentheses. Neither printed rule states whether a later Roman run after the enclosure, intervening punctuation, and whitespace is a continuation of that section or a fresh section. The input gate therefore detects only the structural boundary; it does not label the later run as semantically new. + +The cohort contains 1093 candidates, 138 exact controls, and 955 mismatches. Existing mismatch primary classes are preserved: 627 `pending_rule_review`, 321 `corpus_suspect`, and 7 `unsupported_character_review`. Of 955 evaluable mismatches, 240 are output-localized to the current later-run signature plus its one leading boundary cell: 235 `U+2834 ⠴ -> U+2830 ⠰` and 0 `U+2830 ⠰ -> U+2834 ⠴`. The target counted 336 raw and 333 residual cases before this cohort; it is now 98 final residual. Across raw and final residual maps, `U+2830 ⠰ -> U+2834 ⠴` is 3 -> 1. Exact controls coexist with both directions, and 321 mismatches are already independently identified corpus contradictions. Without a printed fresh-entry example or semantic enclosure model, changing continuation state would be reference-fitting; this remains a deterministic pending/corpus-review cohort only. Representative shard/index samples are retained above. + ### Uppercase Roman runs containing `ST` UEB 10.12.1 (2024 UEB PDF pp.191-192, printed pp.163-164) says not to use a contraction when letters within an abbreviation or acronym are known to be pronounced separately, but to use the contraction in case of doubt. Rule 10.12.2 requires contractions in other abbreviations and acronyms. Consequently an uppercase `ST` surface alone cannot determine whether `s` + `t` or the `st` groupsign is required. The current cohort contains 1479 candidates, 678 exact controls, and 801 mismatches; primary classes remain 752 `pending_rule_review`, 31 `corpus_suspect`, 13 `comparison_method`, and 5 `unsupported_character_review`. Of 801 evaluable mismatches, 443 are localized to the detected current-engine run: 435 `U+280E ⠎ -> U+280C ⠌` and 1 `U+280C ⠌ -> U+280E ⠎`. The target's raw-to-residual count is 448 -> 13, and the reverse is 2 -> 1. Exact controls such as `STAYG`, `KAIST`, `DGIST`, and `POSTECH` coexist with localized mismatches such as `WSTS`, `HUST`, `OST`, and `USTR`; representative shard/index samples are preserved in the cluster table. This lexical/pronunciation distinction cannot be inferred from the input-only spelling, so no engine change or primary reclassification is made. diff --git a/libs/braillify/examples/nikl_corpus_analyze.rs b/libs/braillify/examples/nikl_corpus_analyze.rs index 53b91cff..091f1ad8 100644 --- a/libs/braillify/examples/nikl_corpus_analyze.rs +++ b/libs/braillify/examples/nikl_corpus_analyze.rs @@ -583,6 +583,8 @@ const TIGHT_TRIANGLE_BEFORE_KOREAN: &str = "tight_triangle_mark_immediately_befo const ALLCAPS_ROMAN_RUN_CONTAINING_OU: &str = "allcaps_roman_run_containing_ou"; const ALLCAPS_ROMAN_RUN_CONTAINING_ST: &str = "allcaps_roman_run_containing_st"; const ALLCAPS_ROMAN_RUN_CONTAINING_AR: &str = "allcaps_roman_run_containing_ar"; +const ROMAN_RUN_AFTER_CLOSED_ROMAN_ENCLOSURE: &str = + "roman_run_after_whitespace_following_closed_roman_enclosure"; const ATTACHED_ASCII_ROMAN_SEGMENTS_JOINED_BY_AMPERSAND: &str = "attached_ascii_roman_segments_joined_by_ampersand"; const SINGLE_CAPITAL_PARENTHESIZED_DIGITS: &str = "single_capital_followed_by_parenthesized_digits"; @@ -957,6 +959,99 @@ fn allcaps_roman_runs_containing_ar(input: &str) -> Vec { allcaps_roman_runs_containing_pair(input, b"AR") } +fn enclosure_contains_ascii_roman(input: &str, closer_byte: usize, closer: char) -> bool { + let (opener, search_end) = match closer { + ')' => ('(', closer_byte), + '’' => ('‘', closer_byte), + '”' => ('“', closer_byte), + _ => return false, + }; + let Some(open_byte) = input[..search_end].rfind(opener) else { + return false; + }; + input[open_byte + opener.len_utf8()..closer_byte] + .chars() + .any(|ch| ch.is_ascii_alphabetic()) +} + +/// Finds an ASCII-letter run after whitespace that follows a closed +/// Roman-containing parenthesis or curly-quoted span. The +/// optional comma/colon/semicolon and opening quote model corpus punctuation; +/// no output or reference cells participate in this input gate. +fn roman_run_after_closed_roman_enclosure_spans(input: &str) -> Vec { + let bytes = input.as_bytes(); + let mut spans = Vec::new(); + let mut cursor = 0usize; + while cursor < bytes.len() { + if !bytes[cursor].is_ascii_alphabetic() { + cursor += input[cursor..] + .chars() + .next() + .expect("cursor must remain on a character boundary") + .len_utf8(); + continue; + } + + let start_byte = cursor; + while cursor < bytes.len() && bytes[cursor].is_ascii_alphabetic() { + cursor += 1; + } + let end_byte = cursor; + if input[start_byte..end_byte].is_empty() + || input[..start_byte] + .chars() + .next_back() + .is_some_and(|ch| ch.is_ascii_alphanumeric()) + || input[end_byte..] + .chars() + .next() + .is_some_and(|ch| ch.is_ascii_alphanumeric()) + { + continue; + } + + let mut boundary = start_byte; + while let Some((offset, ch)) = input[..boundary].char_indices().next_back() { + if matches!(ch, '‘' | '“') { + boundary = offset; + } else { + break; + } + } + + let mut saw_whitespace = false; + while let Some((offset, ch)) = input[..boundary].char_indices().next_back() { + if ch.is_whitespace() { + saw_whitespace = true; + boundary = offset; + } else { + break; + } + } + if !saw_whitespace { + continue; + } + + while let Some((offset, ch)) = input[..boundary].char_indices().next_back() { + if matches!(ch, ',' | ':' | ';') { + boundary = offset; + } else { + break; + } + } + let Some((closer_byte, closer)) = input[..boundary].char_indices().next_back() else { + continue; + }; + if enclosure_contains_ascii_roman(input, closer_byte, closer) { + spans.push(InputSpan { + start_byte, + end_byte, + }); + } + } + spans +} + /// Finds complete ASCII-letter sequences joined directly by one or more /// ampersands, such as the UEB §3.1.1 examples `AT&T` and `B&B`. Whitespace, /// Korean text, empty segments, and alphanumeric outer continuations are @@ -1637,6 +1732,31 @@ fn first_difference_in_allcaps_ar_run(item: &EncodedCase) -> bool { .any(|range| range.contains(&first_difference)) } +fn roman_after_closed_enclosure_actual_ranges( + input: &str, + actual: &str, +) -> Vec> { + current_engine_signature_ranges( + input, + actual, + &roman_run_after_closed_roman_enclosure_spans(input), + 1, + ) +} + +fn first_difference_in_roman_after_closed_enclosure(item: &EncodedCase) -> bool { + let Ok(actual) = &item.actual else { + return false; + }; + if actual == &item.located.case.unicode { + return false; + } + let first_difference = first_difference_cell(&item.located.case.unicode, actual); + roman_after_closed_enclosure_actual_ranges(&item.located.case.input, actual) + .into_iter() + .any(|range| range.contains(&first_difference)) +} + fn attached_roman_ampersand_boundary_ranges( input: &str, actual: &str, @@ -1913,10 +2033,15 @@ fn first_difference_claimed_before_allcaps_ar(item: &EncodedCase) -> bool { || first_difference_in_attached_roman_ampersand(item) } -fn first_difference_claimed_by_localized_cohort(item: &EncodedCase) -> bool { +fn first_difference_claimed_before_roman_after_closed_enclosure(item: &EncodedCase) -> bool { first_difference_claimed_before_allcaps_ar(item) || first_difference_in_allcaps_ar_run(item) } +fn first_difference_claimed_by_localized_cohort(item: &EncodedCase) -> bool { + first_difference_claimed_before_roman_after_closed_enclosure(item) + || first_difference_in_roman_after_closed_enclosure(item) +} + /// Input-only candidate gate for acronym expansions such as /// `HCA(Home Connectivity Alliance)`. /// @@ -2524,6 +2649,10 @@ fn analyze( ALLCAPS_ROMAN_RUN_CONTAINING_AR.to_string(), PendingRuleReviewClusterStats::default(), ), + ( + ROMAN_RUN_AFTER_CLOSED_ROMAN_ENCLOSURE.to_string(), + PendingRuleReviewClusterStats::default(), + ), ( ATTACHED_ASCII_ROMAN_SEGMENTS_JOINED_BY_AMPERSAND.to_string(), PendingRuleReviewClusterStats::default(), @@ -2717,6 +2846,15 @@ fn analyze( ), true, ), + ( + ROMAN_RUN_AFTER_CLOSED_ROMAN_ENCLOSURE, + !roman_run_after_closed_roman_enclosure_spans(&item.located.case.input).is_empty(), + Some( + !first_difference_claimed_before_roman_after_closed_enclosure(item) + && first_difference_in_roman_after_closed_enclosure(item), + ), + true, + ), ( ATTACHED_ASCII_ROMAN_SEGMENTS_JOINED_BY_AMPERSAND, !attached_ascii_roman_ampersand_spans(&item.located.case.input).is_empty(), @@ -4015,6 +4153,77 @@ fn markdown(report: &AnalysisReport) -> String { residual_transition(reverse), )); } + if let Some(stats) = report + .pending_rule_review_clusters + .get(ROMAN_RUN_AFTER_CLOSED_ROMAN_ENCLOSURE) + { + let primary_count = |name: &str| { + stats + .mismatch_primary_classes + .get(name) + .copied() + .unwrap_or(0) + }; + let localized_transition = |name: &str| { + stats + .first_difference_in_output_signature_transitions + .get(name) + .copied() + .unwrap_or(0) + }; + let raw_transition = |name: &str| { + report + .pending_first_difference_cell_transitions + .get(name) + .map(|transition| transition.cases) + .unwrap_or(0) + }; + let residual_transition = |name: &str| { + report + .pending_first_difference_transitions_after_localized_cohorts + .get(name) + .map(|transition| transition.cases) + .unwrap_or(0) + }; + let target = "U+2834 ⠴ -> U+2830 ⠰"; + let reverse = "U+2830 ⠰ -> U+2834 ⠴"; + text.push_str(&format!( + "\n### Roman run after a closed Roman enclosure\n\n\ + Korean rule 29 places one Roman indicator/terminator pair around consecutive Roman \ + items, with `Los Angeles` and `Table of Contents` as its printed multiword examples. \ + Rule 34 separately omits the Roman terminator when Roman text is enclosed by \ + quotation marks or parentheses. Neither printed rule states whether a later Roman \ + run after the enclosure, intervening punctuation, and whitespace is a continuation \ + of that section or a fresh section. The input gate therefore detects only the \ + structural boundary; it does not label the later run as semantically new.\n\n\ + The cohort contains {} candidates, {} exact controls, and {} mismatches. Existing \ + mismatch primary classes are preserved: {} `pending_rule_review`, {} \ + `corpus_suspect`, and {} `unsupported_character_review`. Of {} evaluable \ + mismatches, {} are output-localized to the current later-run signature plus its one \ + leading boundary cell: {} `{target}` and {} `{reverse}`. The target counted {} raw \ + and 333 residual cases before this cohort; it is now {} final residual. Across raw \ + and final residual maps, `{reverse}` is {} -> {}. Exact controls coexist with both \ + directions, and 321 mismatches are already independently identified corpus \ + contradictions. Without a printed fresh-entry example or semantic enclosure model, \ + changing continuation state would be reference-fitting; this remains a deterministic \ + pending/corpus-review cohort only. Representative shard/index samples are retained \ + above.\n", + stats.candidates, + stats.exact, + stats.mismatch, + primary_count("pending_rule_review"), + primary_count("corpus_suspect"), + primary_count("unsupported_character_review"), + stats.output_signature_mismatches_evaluated, + stats.first_difference_in_output_signature, + localized_transition(target), + localized_transition(reverse), + raw_transition(target), + residual_transition(target), + raw_transition(reverse), + residual_transition(reverse), + )); + } if let Some(stats) = report .pending_rule_review_clusters .get(ALLCAPS_ROMAN_RUN_CONTAINING_ST) @@ -5285,6 +5494,27 @@ mod tests { assert_eq!(braillify::encode_to_unicode(input).unwrap(), expected); } + #[rstest::rstest] + #[case::parenthetical_then_comma("인공지능(AI), ChatGTP", vec!["ChatGTP"])] + #[case::parenthetical_then_plain("액티브(H) ETF", vec!["ETF"])] + #[case::parenthetical_then_quoted("다목적선(MPV) ‘HMM 울산호’", vec!["HMM"])] + #[case::quoted_then_quoted("‘아리송(ARISONG)’, ‘Boyfriend’", vec!["Boyfriend"])] + #[case::ordinary_roman_words("Los Angeles", vec![])] + #[case::unclosed_parenthetical("인공지능(AI ChatGTP", vec![])] + #[case::nonroman_enclosure("항목(가) ETF", vec![])] + #[case::no_whitespace("인공지능(AI),ChatGTP", vec![])] + #[case::numeric_next("액티브(H) 3ETF", vec![])] + fn detects_roman_run_after_closed_roman_enclosure( + #[case] input: &str, + #[case] expected: Vec<&str>, + ) { + let actual = roman_run_after_closed_roman_enclosure_spans(input) + .into_iter() + .map(|run| &input[run.start_byte..run.end_byte]) + .collect::>(); + assert_eq!(actual, expected); + } + #[rstest::rstest] #[case::common_initialisms("M&A P&G R&D", vec!["M&A", "P&G", "R&D"])] #[case::ueb_examples("AT&T B&B", vec!["AT&T", "B&B"])] @@ -5571,6 +5801,20 @@ mod tests { assert!(ranges[0].end <= actual.chars().count()); } + #[rstest::rstest] + #[case::parenthetical_then_comma("인공지능(AI), ChatGTP")] + #[case::parenthetical_then_plain("액티브(H) ETF")] + #[case::parenthetical_then_quoted("다목적선(MPV) ‘HMM 울산호’")] + #[case::quoted_then_quoted("‘아리송(ARISONG)’, ‘Boyfriend’")] + fn localizes_roman_after_closed_enclosure_signature(#[case] input: &str) { + let actual = braillify::encode_to_unicode(input).expect("probe must encode"); + let ranges = roman_after_closed_enclosure_actual_ranges(input, &actual); + + assert_eq!(ranges.len(), 1); + assert!(ranges[0].start < ranges[0].end); + assert!(ranges[0].end <= actual.chars().count()); + } + #[rstest::rstest] #[case::merger("인수·합병(M&A) 시장")] #[case::brand("브랜드(P&G) 편입")] From 18c77610cbf9c485a014f4548ab46f8b86099003 Mon Sep 17 00:00:00 2001 From: devfive Date: Tue, 1 Sep 2026 13:27:36 +0900 Subject: [PATCH 38/77] Diagnose uppercase ED contraction residual --- docs/corpus-analysis/NIKL_2025_V1.md | 178 +++++++++++++----- .../braillify/examples/nikl_corpus_analyze.rs | 147 ++++++++++++++- 2 files changed, 281 insertions(+), 44 deletions(-) diff --git a/docs/corpus-analysis/NIKL_2025_V1.md b/docs/corpus-analysis/NIKL_2025_V1.md index a1508f4c..1bdda505 100644 --- a/docs/corpus-analysis/NIKL_2025_V1.md +++ b/docs/corpus-analysis/NIKL_2025_V1.md @@ -298,49 +298,26 @@ This ranking removes only cases whose first difference is inside an existing out | Rank | Expected → actual first cell | Residual cases | |---:|---|---:| -| 1 | `U+2811 ⠑ -> U+282B ⠫` | 325 | -| 2 | `U+2810 ⠐ -> U+2832 ⠲` | 297 | -| 3 | `U+2832 ⠲ -> U+2838 ⠸` | 290 | -| 4 | `U+2834 ⠴ -> U+2800 ⠀` | 261 | -| 5 | `U+2800 ⠀ -> U+2832 ⠲` | 227 | -| 6 | `U+2826 ⠦ -> U+2800 ⠀` | 223 | -| 7 | `U+2810 ⠐ -> U+2802 ⠂` | 211 | -| 8 | `U+2817 ⠗ -> U+2820 ⠠` | 164 | -| 9 | `U+2820 ⠠ -> U+2809 ⠉` | 162 | -| 10 | `U+283C ⠼ -> U+2800 ⠀` | 153 | -| 11 | `U+2826 ⠦ -> U+2810 ⠐` | 147 | -| 12 | `U+281B ⠛ -> U+2823 ⠣` | 144 | -| 13 | `U+2810 ⠐ -> U+2800 ⠀` | 130 | -| 14 | `U+2811 ⠑ -> U+283B ⠻` | 101 | -| 15 | `U+2810 ⠐ -> U+2814 ⠔` | 98 | -| 16 | `U+2834 ⠴ -> U+2830 ⠰` | 98 | -| 17 | `U+280E ⠎ -> U+2820 ⠠` | 92 | -| 18 | `U+2804 ⠄ -> U+2834 ⠴` | 86 | -| 19 | `U+2803 ⠃ -> U+2820 ⠠` | 85 | -| 20 | `U+2824 ⠤ -> U+2800 ⠀` | 78 | - -### Residual `U+2811 ⠑ -> U+282B ⠫` - -- `sentence_01.json` #51: 와이캅 픽셀은 와이어·패키지·렌즈가 필요 없는 ‘와이캅’ 기술을 기반으로 한다. 적녹청(RGB) 3개의 마이크로 LED를 수직 방향으로 적층한 세계 최초의 풀 컬러 원칩 기술이다. - - expected: `⠪⠐⠥⠀⠴⠠⠠⠇⠑⠙⠲⠐⠮⠀⠠⠍⠨⠕⠁⠀⠘⠶⠚⠜` - - actual: `⠪⠐⠥⠀⠴⠠⠠⠇⠫⠲⠐⠮⠀⠠⠍⠨⠕⠁⠀⠘⠶⠚⠜⠶` - - first differing cell (zero-based): 107 - - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_02.json` #33: 지원은 학생이 영재교육종합데이터베이스(GED)를 통해 원서 접수하고 교사의 추천을 받아 진행하고, 자세한 내용은 학교 및 영재교육 기관별 홈페이지에 탑재되어 있다. - - expected: `⠠⠪⠦⠄⠴⠠⠠⠛⠑⠙⠠⠴⠐⠮⠀⠓⠿⠚⠗⠀⠏⠒⠠⠎` - - actual: `⠠⠪⠦⠄⠴⠠⠠⠛⠫⠠⠴⠐⠮⠀⠓⠿⠚⠗⠀⠏⠒⠠⠎⠀` - - first differing cell (zero-based): 40 - - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_03.json` #209: 포드의 글로벌 트럭 디자인 DNA를 토대로 강인하면서도 다양한 사용 목적에 부합하는 실용적인 내·외부를 구현했다. 전면부 시그니처 C클램프 헤드라이트, 매트릭스 LED 헤드라이트, 포드(FORD) 레터링이 특징이다. - - expected: `⠁⠠⠪⠀⠴⠠⠠⠇⠑⠙⠲⠀⠚⠝⠊⠪⠐⠣⠕⠓⠪⠐⠀⠙` - - actual: `⠁⠠⠪⠀⠴⠠⠠⠇⠫⠲⠀⠚⠝⠊⠪⠐⠣⠕⠓⠪⠐⠀⠙⠥` - - first differing cell (zero-based): 159 - - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_04.json` #388: 김 신부는 그동안 제작한 스테인드글라스 작품은 물론 회화·LED(발광다이오드)조명작품·도자기 등 60여점의 작품을 전시한다. 그는 “형상을 떠난 자유로움과 원초적인 아름다움에 대한 깊이를 관람객들에게 전달하고 싶다”고 밝혔다. - - expected: `⠚⠧⠐⠆⠴⠠⠠⠇⠑⠙⠦⠄⠘⠂⠈⠧⠶⠊⠣⠕⠥⠊⠪⠠` - - actual: `⠚⠧⠐⠆⠴⠠⠠⠇⠫⠦⠄⠘⠂⠈⠧⠶⠊⠣⠕⠥⠊⠪⠠⠴` - - first differing cell (zero-based): 61 - - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +| 1 | `U+2810 ⠐ -> U+2832 ⠲` | 297 | +| 2 | `U+2832 ⠲ -> U+2838 ⠸` | 290 | +| 3 | `U+2834 ⠴ -> U+2800 ⠀` | 261 | +| 4 | `U+2800 ⠀ -> U+2832 ⠲` | 227 | +| 5 | `U+2826 ⠦ -> U+2800 ⠀` | 223 | +| 6 | `U+2810 ⠐ -> U+2802 ⠂` | 211 | +| 7 | `U+2817 ⠗ -> U+2820 ⠠` | 164 | +| 8 | `U+2820 ⠠ -> U+2809 ⠉` | 162 | +| 9 | `U+283C ⠼ -> U+2800 ⠀` | 153 | +| 10 | `U+2826 ⠦ -> U+2810 ⠐` | 147 | +| 11 | `U+281B ⠛ -> U+2823 ⠣` | 144 | +| 12 | `U+2810 ⠐ -> U+2800 ⠀` | 130 | +| 13 | `U+2811 ⠑ -> U+283B ⠻` | 101 | +| 14 | `U+2810 ⠐ -> U+2814 ⠔` | 98 | +| 15 | `U+2834 ⠴ -> U+2830 ⠰` | 98 | +| 16 | `U+280E ⠎ -> U+2820 ⠠` | 92 | +| 17 | `U+2804 ⠄ -> U+2834 ⠴` | 86 | +| 18 | `U+2803 ⠃ -> U+2820 ⠠` | 85 | +| 19 | `U+2824 ⠤ -> U+2800 ⠀` | 78 | +| 20 | `U+2826 ⠦ -> U+2834 ⠴` | 76 | ### Residual `U+2810 ⠐ -> U+2832 ⠲` @@ -549,6 +526,29 @@ This ranking removes only cases whose first difference is inside an existing out - first differing cell (zero-based): 124 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +### Residual `U+2826 ⠦ -> U+2810 ⠐` + +- `sentence_01.json` #1318: 비제조업 업황 BSI(73)는 2p 상승했다. 비제조업 업황 BSI가 전월 대비 상승한 것은 지난 2022년 8월 이후 6개월 만에 처음이다. + - expected: `⠶⠀⠴⠠⠠⠃⠎⠊⠦⠄⠼⠛⠉⠠⠴⠉⠵⠀⠼⠃⠴⠏⠲⠀` + - actual: `⠶⠀⠴⠠⠠⠃⠎⠊⠐⠣⠼⠛⠉⠴⠐⠜⠲⠉⠵⠀⠀⠼⠃⠏` + - first differing cell (zero-based): 21 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_02.json` #286: 이희창 학생은 One Show에서 Bronze Pencil(동상), Merit(4위) / One ADC에서 Merit, Shortlist(본상) 31개 / One Portfolio에서 Shortlist, 총 34개의 본상을 수상했다. + - expected: `⠐⠀⠴⠠⠍⠻⠊⠞⠦⠄⠼⠙⠍⠗⠠⠴⠸⠌⠴⠠⠐⠕⠀⠠` + - actual: `⠐⠀⠴⠠⠍⠻⠊⠞⠐⠣⠼⠙⠍⠗⠴⠐⠜⠲⠀⠸⠌⠀⠴⠠` + - first differing cell (zero-based): 58 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_03.json` #551: 영국 일간 가디언은 28일(현지시간) 조너선M(41)이 이날부터 정자를 기증할 경우 10만유로(1억5000만원) 이상의 벌금을 물게 된다고 보도했다. + - expected: `⠥⠉⠎⠠⠾⠴⠠⠍⠦⠄⠼⠙⠁⠠⠴⠕⠀⠕⠉⠂⠘⠍⠓⠎` + - actual: `⠥⠉⠎⠠⠾⠴⠠⠍⠐⠣⠼⠙⠁⠴⠐⠜⠲⠕⠀⠕⠉⠂⠘⠍` + - first differing cell (zero-based): 43 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_04.json` #119: 21일 국제금융센터에 따르면 KB국민은행의 지난 17일 신용부도스와프(CDS) 프리미엄은 43bp(1bp는 0.01%포인트)로 일주일 전 대비 1bp 상승하는 데 그쳤다. + - expected: `⠵⠀⠼⠙⠉⠴⠃⠏⠦⠄⠼⠁⠴⠃⠏⠲⠉⠵⠀⠼⠚⠲⠚⠁` + - actual: `⠵⠀⠼⠙⠉⠴⠃⠏⠐⠣⠼⠁⠰⠃⠏⠲⠉⠵⠀⠼⠚⠲⠚⠁` + - first differing cell (zero-based): 98 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` + ## Cross-cutting input-only structural cohorts These are cross-cutting input-only structural cohorts, not new primary classes and not engine routing rules. Candidate selection never changes a case's existing primary class by itself. Only cohort members already classified as `pending_rule_review` form a pending subcluster; exact and other-primary members are controls that retain their existing outcomes. A separate classifier may use independently justified, output-localized PDF evidence, as in the rule-34 three-cell contradiction below. The `uppercase_roman_headword_closed_multiword_parenthetical` gate requires a two-or-more character uppercase ASCII headword immediately followed by a closed parenthesis whose contents are two or more ASCII Roman words separated only by spaces. Because the contents admit only letters and spaces, visible operators, subscript/superscript notation, and nested parentheses are excluded deterministically. The `mixed_roman_korean_word_before_uppercase_headword_expansion` gate further requires a whitespace-delimited preceding word that contains both ASCII Roman and Korean and ends in Korean. It locates the following uppercase headword itself, including its immediately preceding emitted cell, so an earlier Roman entry in the same sentence cannot satisfy the output audit. The `single_capital_followed_by_parenthesized_digits` gate requires a standalone capital, a non-empty closed ASCII-digit parenthetical, and alphanumeric outer boundaries. It likewise includes the emitted entry-boundary cell in localization. The `uppercase_roman_run_followed_by_hyphen_digits` gate requires a maximal uppercase ASCII run, literal ASCII hyphen-minus, a non-empty digit run, and alphanumeric outer boundaries. Its localized range includes the current encoded run and its immediately preceding cell, keeping `F-35`-style routing separate from both parenthesized digits and headword expansions. The `standalone_multi_character_uppercase_roman_word` gate finds maximal ASCII-letter runs of two or more capitals with non-alphanumeric boundaries; a run immediately followed by `(` is excluded so the HCA-style headword itself is not counted by both gates. The `korean_prefixed_closed_allcaps_parenthetical` gate requires an immediately preceding Korean character and a closed body of two or more uppercase ASCII letters. It intentionally contains both acronym annotations (`책임자(COO)`) and scientific formulae (`일산화탄소(CO)`) so their semantic collision remains measurable. The `korean_prefixed_closed_roman_annotation_rule_34_order` gate accepts the narrower rule-34 body grammar after an immediately preceding Korean character and localizes only the current engine's Korean opening-parenthesis cells. This distinguishes the PDF's parenthesis-before-Roman-indicator order from unrelated differences later in the same sentence. The `multi_character_allcaps_roman_runs_joined_by_middle_dot` gate requires two maximal ASCII-letter runs of at least two capitals joined directly by U+00B7, with non-alphanumeric outer boundaries. It records shapes such as `AI·SW` without assigning prose, mathematics, or science semantics. The `roman_run_immediately_before_attached_middle_dot_boundary` gate is output-localized: it requires a maximal ASCII-letter run immediately before U+00B7 and an attached Korean character or ASCII-letter run after it, then searches for that whole current-engine signature in the actual output. It therefore isolates the Roman terminator boundary without treating unrelated middle dots elsewhere in the sentence as causal. The `korean_inline_parenthesized_single_arithmetic_operator` gate requires an immediate `Korean(` + one rule-45 arithmetic operator + `)Korean` span. Unlike broad coexistence traits, it also locates the current engine's emitted structure and counts a mismatch as signature-local only when the sentence's first differing cell falls inside that output range. The `allcaps_roman_run_containing_ou` gate finds maximal, alphanumeric-delimited uppercase ASCII runs containing adjacent `OU`. It locates the independently encoded run signature in the complete current output and counts only first differences inside that signature as localized. The `allcaps_roman_run_containing_st` gate applies the same output-position requirement to adjacent `ST`; it is kept separate because UEB 10.12 makes contraction use depend on how an abbreviation or acronym is pronounced. The `attached_ascii_roman_segments_joined_by_ampersand` gate requires non-empty ASCII-letter segments joined directly by `&`, excludes spaced/Korean/alphanumeric continuations, and localizes only the current output cell immediately before the ampersand through an independently encoded real-input prefix. The `decimal_point_between_ascii_digits` gate finds whitespace-delimited words containing `digit.digit` and reproduces each whole word in a neutral Korean context, so suffixes and punctuation remain part of the current-engine signature. The `compact_numeric_ascii_letter_suffix` gate finds a numeric prefix immediately followed by ASCII letters, includes the immediately preceding output cell as its entry boundary, and retains suffix-specific outcome counts. It intentionally includes both possible rule-69 units and ambiguous variable/identifier forms; membership alone does not assign unit semantics. The `rule69_ascii_unit_before_terminator_skipping_symbol` gate is narrower: it accepts only ASCII unit spellings already supported by rule 69, includes the immediately following rule-33/34 punctuation cell in the localized signature, and does not infer new units. The `tight_triangle_mark_immediately_before_korean` gate requires literal `△한글` with no input space and includes the first following Korean cell in its localized output range, so an observed missing-space difference is measured at the mark boundary. @@ -557,6 +557,7 @@ These are cross-cutting input-only structural cohorts, not new primary classes a |---|---:|---:|---:|---:| | `allcaps_roman_run_beginning_with_pure_letter_shortform` | 4120 | 2441 | 1679 | 0 | | `allcaps_roman_run_containing_ar` | 1022 | 360 | 662 | 0 | +| `allcaps_roman_run_containing_ed` | 816 | 313 | 503 | 0 | | `allcaps_roman_run_containing_ou` | 1816 | 64 | 1752 | 0 | | `allcaps_roman_run_containing_st` | 1479 | 678 | 801 | 0 | | `attached_ascii_roman_segments_joined_by_ampersand` | 802 | 273 | 529 | 0 | @@ -753,6 +754,91 @@ Representative `mismatch` samples: - first differing cell (zero-based): 14 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +### `allcaps_roman_run_containing_ed` + +Of the 816 candidates, 434 are the actual `pending_rule_review` subcluster. The other 382 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. + +For this output-signature audit, 503 mismatches were evaluable and 326 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. + +Localized first-difference transitions: + +- `U+2811 ⠑ -> U+282B ⠫`: 323 +- `U+2810 ⠐ -> U+280E ⠎`: 1 +- `U+2810 ⠐ -> U+281D ⠝`: 1 +- `U+2815 ⠕ -> U+2820 ⠠`: 1 + +Mismatch primary-class distribution: + +- `comparison_method`: 1 +- `corpus_suspect`: 63 +- `pending_rule_review`: 434 +- `unsupported_character_review`: 5 + +Representative `exact` samples: + +- `sentence_01.json` #26: LG전자 전시관 입구에는 올레드(OLED) 플렉서블 사이니지 260장을 이어 붙인 초대형 조형물 ‘올레드 지평선’이 관람객들의 이목을 집중시킨다. + - expected: `⠴⠠⠠⠇⠛⠲⠨⠾⠨⠀⠨⠾⠠⠕⠈⠧⠒⠀⠕⠃⠈⠍⠝⠉` + - actual: `⠴⠠⠠⠇⠛⠲⠨⠾⠨⠀⠨⠾⠠⠕⠈⠧⠒⠀⠕⠃⠈⠍⠝⠉` + - current primary/reason: `exact` / `exact` +- `sentence_02.json` #44: 아울러 국가응급진료정보망(NEDIS)의 개인정보 수집·연계 법적 근거를 마련해 구급활동일지, 건강보험진료기록과의 연계를 통해 응급환자에 대해 이송부터 응급실 진료, 의료기관 퇴원까지 단절 없는(seamless) 응급의료데이터 체계를 구축한다. + - expected: `⠣⠯⠐⠎⠀⠈⠍⠁⠫⠪⠶⠈⠪⠃⠨⠟⠐⠬⠨⠻⠘⠥⠑⠶` + - actual: `⠣⠯⠐⠎⠀⠈⠍⠁⠫⠪⠶⠈⠪⠃⠨⠟⠐⠬⠨⠻⠘⠥⠑⠶` + - current primary/reason: `exact` / `exact` +- `sentence_03.json` #423: 아이씨에이치는 그동안 삼성전자에 필름형 박막안테나(MFA), 전자파 차폐용 가스켓, IT기기용 테이프 등 스마트폰 부품 소재를 공급해왔으며, 지난해부터는 디스플레이용 복합소재 등 OLED 부품 소재로도 영역을 확대하고 있다. + - expected: `⠣⠕⠠⠠⠕⠝⠕⠰⠕⠉⠵⠀⠈⠪⠊⠿⠣⠒⠀⠇⠢⠠⠻⠨` + - actual: `⠣⠕⠠⠠⠕⠝⠕⠰⠕⠉⠵⠀⠈⠪⠊⠿⠣⠒⠀⠇⠢⠠⠻⠨` + - current primary/reason: `exact` / `exact` +- `sentence_04.json` #484: 윤석열 대통령은 4일 첨단 디스플레이산업과 관련해 “민간이 적기에 투자할 수 있도록 인센티브를 확보하고 OLED(유기발광다이오드) 기술 고도화를 지원하면서 글로벌시장에서 압도적인 1위를 계속 견지하도록 만들겠다”고 말했다. + - expected: `⠩⠒⠠⠹⠳⠀⠊⠗⠓⠿⠐⠻⠵⠀⠼⠙⠕⠂⠀⠰⠎⠢⠊⠒` + - actual: `⠩⠒⠠⠹⠳⠀⠊⠗⠓⠿⠐⠻⠵⠀⠼⠙⠕⠂⠀⠰⠎⠢⠊⠒` + - current primary/reason: `exact` / `exact` + +Representative `localized_mismatch` samples: + +- `sentence_01.json` #51: 와이캅 픽셀은 와이어·패키지·렌즈가 필요 없는 ‘와이캅’ 기술을 기반으로 한다. 적녹청(RGB) 3개의 마이크로 LED를 수직 방향으로 적층한 세계 최초의 풀 컬러 원칩 기술이다. + - expected: `⠪⠐⠥⠀⠴⠠⠠⠇⠑⠙⠲⠐⠮⠀⠠⠍⠨⠕⠁⠀⠘⠶⠚⠜` + - actual: `⠪⠐⠥⠀⠴⠠⠠⠇⠫⠲⠐⠮⠀⠠⠍⠨⠕⠁⠀⠘⠶⠚⠜⠶` + - first differing cell (zero-based): 107 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_02.json` #33: 지원은 학생이 영재교육종합데이터베이스(GED)를 통해 원서 접수하고 교사의 추천을 받아 진행하고, 자세한 내용은 학교 및 영재교육 기관별 홈페이지에 탑재되어 있다. + - expected: `⠠⠪⠦⠄⠴⠠⠠⠛⠑⠙⠠⠴⠐⠮⠀⠓⠿⠚⠗⠀⠏⠒⠠⠎` + - actual: `⠠⠪⠦⠄⠴⠠⠠⠛⠫⠠⠴⠐⠮⠀⠓⠿⠚⠗⠀⠏⠒⠠⠎⠀` + - first differing cell (zero-based): 40 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_03.json` #209: 포드의 글로벌 트럭 디자인 DNA를 토대로 강인하면서도 다양한 사용 목적에 부합하는 실용적인 내·외부를 구현했다. 전면부 시그니처 C클램프 헤드라이트, 매트릭스 LED 헤드라이트, 포드(FORD) 레터링이 특징이다. + - expected: `⠁⠠⠪⠀⠴⠠⠠⠇⠑⠙⠲⠀⠚⠝⠊⠪⠐⠣⠕⠓⠪⠐⠀⠙` + - actual: `⠁⠠⠪⠀⠴⠠⠠⠇⠫⠲⠀⠚⠝⠊⠪⠐⠣⠕⠓⠪⠐⠀⠙⠥` + - first differing cell (zero-based): 159 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_04.json` #388: 김 신부는 그동안 제작한 스테인드글라스 작품은 물론 회화·LED(발광다이오드)조명작품·도자기 등 60여점의 작품을 전시한다. 그는 “형상을 떠난 자유로움과 원초적인 아름다움에 대한 깊이를 관람객들에게 전달하고 싶다”고 밝혔다. + - expected: `⠚⠧⠐⠆⠴⠠⠠⠇⠑⠙⠦⠄⠘⠂⠈⠧⠶⠊⠣⠕⠥⠊⠪⠠` + - actual: `⠚⠧⠐⠆⠴⠠⠠⠇⠫⠦⠄⠘⠂⠈⠧⠶⠊⠣⠕⠥⠊⠪⠠⠴` + - first differing cell (zero-based): 61 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` + +Representative `mismatch` samples: + +- `sentence_01.json` #35: 삼성전자는 올해 CES에서 77인치 OLED TV를 처음으로 공개할 예정이다. 이는 지난 8월 ‘국제정보디스플레이학술대회(IMID) 2022’에서 삼성디스플레이가 공개한 77인치 퀀텀닷(QD)-OLED 패널을 탑재한 제품이다. + - expected: `⠋⠏⠒⠓⠎⠢⠊⠄⠴⠐⠣⠟⠙⠐⠜⠤⠠⠠⠕⠇⠫⠲⠀⠙` + - actual: `⠋⠏⠒⠓⠎⠢⠊⠄⠀⠀⠦⠠⠠⠟⠙⠴⠔⠠⠠⠕⠇⠑⠙⠀` + - first differing cell (zero-based): 172 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_02.json` #33: 지원은 학생이 영재교육종합데이터베이스(GED)를 통해 원서 접수하고 교사의 추천을 받아 진행하고, 자세한 내용은 학교 및 영재교육 기관별 홈페이지에 탑재되어 있다. + - expected: `⠠⠪⠦⠄⠴⠠⠠⠛⠑⠙⠠⠴⠐⠮⠀⠓⠿⠚⠗⠀⠏⠒⠠⠎` + - actual: `⠠⠪⠦⠄⠴⠠⠠⠛⠫⠠⠴⠐⠮⠀⠓⠿⠚⠗⠀⠏⠒⠠⠎⠀` + - first differing cell (zero-based): 40 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_03.json` #209: 포드의 글로벌 트럭 디자인 DNA를 토대로 강인하면서도 다양한 사용 목적에 부합하는 실용적인 내·외부를 구현했다. 전면부 시그니처 C클램프 헤드라이트, 매트릭스 LED 헤드라이트, 포드(FORD) 레터링이 특징이다. + - expected: `⠁⠠⠪⠀⠴⠠⠠⠇⠑⠙⠲⠀⠚⠝⠊⠪⠐⠣⠕⠓⠪⠐⠀⠙` + - actual: `⠁⠠⠪⠀⠴⠠⠠⠇⠫⠲⠀⠚⠝⠊⠪⠐⠣⠕⠓⠪⠐⠀⠙⠥` + - first differing cell (zero-based): 159 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_04.json` #62: LG디스플레이가 자사의 유기발광다이오드(OLED) TV 패널이 글로벌 친환경 인증기관인 카본 트러스트(Carbon Trust)로 부터 탄소발자국 인증을 획득했다고 19일 밝혔다. + - expected: `⠧⠶⠊⠣⠕⠥⠊⠪⠴⠐⠣⠠⠠⠕⠇⠫⠐⠜⠀⠠⠠⠞⠧⠲` + - actual: `⠧⠶⠊⠣⠕⠥⠊⠪⠦⠄⠴⠠⠠⠕⠇⠫⠠⠴⠀⠰⠠⠠⠞⠧` + - first differing cell (zero-based): 35 + - current primary/reason: `corpus_suspect` / `rule34_roman_indicator_before_opening_parenthesis` + ### `allcaps_roman_run_containing_ou` Of the 1816 candidates, 1666 are the actual `pending_rule_review` subcluster. The other 150 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. @@ -2548,6 +2634,12 @@ Korean rule 29 places one Roman indicator/terminator pair around consecutive Rom The cohort contains 1093 candidates, 138 exact controls, and 955 mismatches. Existing mismatch primary classes are preserved: 627 `pending_rule_review`, 321 `corpus_suspect`, and 7 `unsupported_character_review`. Of 955 evaluable mismatches, 240 are output-localized to the current later-run signature plus its one leading boundary cell: 235 `U+2834 ⠴ -> U+2830 ⠰` and 0 `U+2830 ⠰ -> U+2834 ⠴`. The target counted 336 raw and 333 residual cases before this cohort; it is now 98 final residual. Across raw and final residual maps, `U+2830 ⠰ -> U+2834 ⠴` is 3 -> 1. Exact controls coexist with both directions, and 321 mismatches are already independently identified corpus contradictions. Without a printed fresh-entry example or semantic enclosure model, changing continuation state would be reference-fitting; this remains a deterministic pending/corpus-review cohort only. Representative shard/index samples are retained above. +### Uppercase Roman runs containing `ED` + +UEB 10.12.1 (2024 UEB PDF pp.191-192, printed pp.163-164) requires separate letters when an abbreviation's letters are known to be pronounced separately, and its official `OED` example writes `e` and `d` separately. Rule 10.12.2 otherwise uses contractions; its official `BEd` example uses the `ed` groupsign. Both full-encoder controls pass. Consequently an uppercase `ED` surface cannot establish pronunciation or abbreviation semantics by spelling alone. + +The cohort contains 816 candidates, 313 exact controls, and 503 mismatches. Existing mismatch primary classes remain 434 `pending_rule_review`, 63 `corpus_suspect`, 1 `comparison_method`, and 5 `unsupported_character_review`. Of 503 evaluable mismatches, 326 are localized to the detected current-engine run: 323 `U+2811 ⠑ -> U+282B ⠫` and 0 `U+282B ⠫ -> U+2811 ⠑`. Across raw pending transitions and the final residual after localized cohorts, the target is 326 -> 2 and the reverse is 0 -> 0. Corpus initialisms such as `LED` and `GED` require external pronunciation knowledge, while exact and official controls preserve contraction-bearing outcomes. No engine change or primary reclassification is made; representative shard/index samples are retained above. + ### Uppercase Roman runs containing `ST` UEB 10.12.1 (2024 UEB PDF pp.191-192, printed pp.163-164) says not to use a contraction when letters within an abbreviation or acronym are known to be pronounced separately, but to use the contraction in case of doubt. Rule 10.12.2 requires contractions in other abbreviations and acronyms. Consequently an uppercase `ST` surface alone cannot determine whether `s` + `t` or the `st` groupsign is required. The current cohort contains 1479 candidates, 678 exact controls, and 801 mismatches; primary classes remain 752 `pending_rule_review`, 31 `corpus_suspect`, 13 `comparison_method`, and 5 `unsupported_character_review`. Of 801 evaluable mismatches, 443 are localized to the detected current-engine run: 435 `U+280E ⠎ -> U+280C ⠌` and 1 `U+280C ⠌ -> U+280E ⠎`. The target's raw-to-residual count is 448 -> 13, and the reverse is 2 -> 1. Exact controls such as `STAYG`, `KAIST`, `DGIST`, and `POSTECH` coexist with localized mismatches such as `WSTS`, `HUST`, `OST`, and `USTR`; representative shard/index samples are preserved in the cluster table. This lexical/pronunciation distinction cannot be inferred from the input-only spelling, so no engine change or primary reclassification is made. diff --git a/libs/braillify/examples/nikl_corpus_analyze.rs b/libs/braillify/examples/nikl_corpus_analyze.rs index 091f1ad8..549cdab4 100644 --- a/libs/braillify/examples/nikl_corpus_analyze.rs +++ b/libs/braillify/examples/nikl_corpus_analyze.rs @@ -583,6 +583,7 @@ const TIGHT_TRIANGLE_BEFORE_KOREAN: &str = "tight_triangle_mark_immediately_befo const ALLCAPS_ROMAN_RUN_CONTAINING_OU: &str = "allcaps_roman_run_containing_ou"; const ALLCAPS_ROMAN_RUN_CONTAINING_ST: &str = "allcaps_roman_run_containing_st"; const ALLCAPS_ROMAN_RUN_CONTAINING_AR: &str = "allcaps_roman_run_containing_ar"; +const ALLCAPS_ROMAN_RUN_CONTAINING_ED: &str = "allcaps_roman_run_containing_ed"; const ROMAN_RUN_AFTER_CLOSED_ROMAN_ENCLOSURE: &str = "roman_run_after_whitespace_following_closed_roman_enclosure"; const ATTACHED_ASCII_ROMAN_SEGMENTS_JOINED_BY_AMPERSAND: &str = @@ -959,6 +960,10 @@ fn allcaps_roman_runs_containing_ar(input: &str) -> Vec { allcaps_roman_runs_containing_pair(input, b"AR") } +fn allcaps_roman_runs_containing_ed(input: &str) -> Vec { + allcaps_roman_runs_containing_pair(input, b"ED") +} + fn enclosure_contains_ascii_roman(input: &str, closer_byte: usize, closer: char) -> bool { let (opener, search_end) = match closer { ')' => ('(', closer_byte), @@ -1732,6 +1737,23 @@ fn first_difference_in_allcaps_ar_run(item: &EncodedCase) -> bool { .any(|range| range.contains(&first_difference)) } +fn allcaps_ed_actual_ranges(input: &str, actual: &str) -> Vec> { + current_engine_signature_ranges(input, actual, &allcaps_roman_runs_containing_ed(input), 0) +} + +fn first_difference_in_allcaps_ed_run(item: &EncodedCase) -> bool { + let Ok(actual) = &item.actual else { + return false; + }; + if actual == &item.located.case.unicode { + return false; + } + let first_difference = first_difference_cell(&item.located.case.unicode, actual); + allcaps_ed_actual_ranges(&item.located.case.input, actual) + .into_iter() + .any(|range| range.contains(&first_difference)) +} + fn roman_after_closed_enclosure_actual_ranges( input: &str, actual: &str, @@ -2037,11 +2059,15 @@ fn first_difference_claimed_before_roman_after_closed_enclosure(item: &EncodedCa first_difference_claimed_before_allcaps_ar(item) || first_difference_in_allcaps_ar_run(item) } -fn first_difference_claimed_by_localized_cohort(item: &EncodedCase) -> bool { +fn first_difference_claimed_before_allcaps_ed(item: &EncodedCase) -> bool { first_difference_claimed_before_roman_after_closed_enclosure(item) || first_difference_in_roman_after_closed_enclosure(item) } +fn first_difference_claimed_by_localized_cohort(item: &EncodedCase) -> bool { + first_difference_claimed_before_allcaps_ed(item) || first_difference_in_allcaps_ed_run(item) +} + /// Input-only candidate gate for acronym expansions such as /// `HCA(Home Connectivity Alliance)`. /// @@ -2649,6 +2675,10 @@ fn analyze( ALLCAPS_ROMAN_RUN_CONTAINING_AR.to_string(), PendingRuleReviewClusterStats::default(), ), + ( + ALLCAPS_ROMAN_RUN_CONTAINING_ED.to_string(), + PendingRuleReviewClusterStats::default(), + ), ( ROMAN_RUN_AFTER_CLOSED_ROMAN_ENCLOSURE.to_string(), PendingRuleReviewClusterStats::default(), @@ -2855,6 +2885,15 @@ fn analyze( ), true, ), + ( + ALLCAPS_ROMAN_RUN_CONTAINING_ED, + !allcaps_roman_runs_containing_ed(&item.located.case.input).is_empty(), + Some( + !first_difference_claimed_before_allcaps_ed(item) + && first_difference_in_allcaps_ed_run(item), + ), + true, + ), ( ATTACHED_ASCII_ROMAN_SEGMENTS_JOINED_BY_AMPERSAND, !attached_ascii_roman_ampersand_spans(&item.located.case.input).is_empty(), @@ -4224,6 +4263,74 @@ fn markdown(report: &AnalysisReport) -> String { residual_transition(reverse), )); } + if let Some(stats) = report + .pending_rule_review_clusters + .get(ALLCAPS_ROMAN_RUN_CONTAINING_ED) + { + let primary_count = |name: &str| { + stats + .mismatch_primary_classes + .get(name) + .copied() + .unwrap_or(0) + }; + let localized_transition = |name: &str| { + stats + .first_difference_in_output_signature_transitions + .get(name) + .copied() + .unwrap_or(0) + }; + let raw_transition = |name: &str| { + report + .pending_first_difference_cell_transitions + .get(name) + .map(|transition| transition.cases) + .unwrap_or(0) + }; + let residual_transition = |name: &str| { + report + .pending_first_difference_transitions_after_localized_cohorts + .get(name) + .map(|transition| transition.cases) + .unwrap_or(0) + }; + let target = "U+2811 ⠑ -> U+282B ⠫"; + let reverse = "U+282B ⠫ -> U+2811 ⠑"; + text.push_str(&format!( + "\n### Uppercase Roman runs containing `ED`\n\n\ + UEB 10.12.1 (2024 UEB PDF pp.191-192, printed pp.163-164) requires \ + separate letters when an abbreviation's letters are known to be pronounced \ + separately, and its official `OED` example writes `e` and `d` separately. Rule \ + 10.12.2 otherwise uses contractions; its official `BEd` example uses the `ed` \ + groupsign. Both full-encoder controls pass. Consequently an uppercase `ED` surface \ + cannot establish pronunciation or abbreviation semantics by spelling alone.\n\n\ + The cohort contains {} candidates, {} exact controls, and {} mismatches. Existing \ + mismatch primary classes remain {} `pending_rule_review`, {} `corpus_suspect`, {} \ + `comparison_method`, and {} `unsupported_character_review`. Of {} evaluable \ + mismatches, {} are localized to the detected current-engine run: {} `{target}` and \ + {} `{reverse}`. Across raw pending transitions and the final residual after localized \ + cohorts, the target is {} -> {} and the reverse is {} -> {}. Corpus initialisms such \ + as `LED` and `GED` require external pronunciation knowledge, while exact and official \ + controls preserve contraction-bearing outcomes. No engine change or primary \ + reclassification is made; representative shard/index samples are retained above.\n", + stats.candidates, + stats.exact, + stats.mismatch, + primary_count("pending_rule_review"), + primary_count("corpus_suspect"), + primary_count("comparison_method"), + primary_count("unsupported_character_review"), + stats.output_signature_mismatches_evaluated, + stats.first_difference_in_output_signature, + localized_transition(target), + localized_transition(reverse), + raw_transition(target), + residual_transition(target), + raw_transition(reverse), + residual_transition(reverse), + )); + } if let Some(stats) = report .pending_rule_review_clusters .get(ALLCAPS_ROMAN_RUN_CONTAINING_ST) @@ -5494,6 +5601,32 @@ mod tests { assert_eq!(braillify::encode_to_unicode(input).unwrap(), expected); } + #[rstest::rstest] + #[case::initialisms("OED LED GED", vec!["OED", "LED", "GED"])] + #[case::lowercase("Ed", vec![])] + #[case::mixed_case("eD", vec![])] + #[case::no_ed("WHO", vec![])] + #[case::digit_prefix("1LED", vec![])] + #[case::digit_suffix("LED2", vec![])] + fn detects_allcaps_roman_runs_containing_ed(#[case] input: &str, #[case] expected: Vec<&str>) { + let actual = allcaps_roman_runs_containing_ed(input) + .into_iter() + .map(|run| &input[run.start_byte..run.end_byte]) + .collect::>(); + assert_eq!(actual, expected); + } + + /// UEB 10.12.1-10.12.2 supply both `ed` outcomes for abbreviations. + #[rstest::rstest] + #[case::separate_letters("OED", "⠠⠠⠕⠑⠙")] + #[case::contracted_abbreviation("BEd", "⠠⠃⠠⠫")] + fn full_encoder_preserves_official_ueb_ed_controls( + #[case] input: &str, + #[case] expected: &str, + ) { + assert_eq!(braillify::encode_to_unicode(input).unwrap(), expected); + } + #[rstest::rstest] #[case::parenthetical_then_comma("인공지능(AI), ChatGTP", vec!["ChatGTP"])] #[case::parenthetical_then_plain("액티브(H) ETF", vec!["ETF"])] @@ -5801,6 +5934,18 @@ mod tests { assert!(ranges[0].end <= actual.chars().count()); } + #[rstest::rstest] + #[case::light_emitting_diode("마이크로 LED")] + #[case::education_database("데이터베이스(GED)")] + fn localizes_allcaps_ed_signature_in_complete_output(#[case] input: &str) { + let actual = braillify::encode_to_unicode(input).expect("probe must encode"); + let ranges = allcaps_ed_actual_ranges(input, &actual); + + assert_eq!(ranges.len(), 1); + assert!(ranges[0].start < ranges[0].end); + assert!(ranges[0].end <= actual.chars().count()); + } + #[rstest::rstest] #[case::parenthetical_then_comma("인공지능(AI), ChatGTP")] #[case::parenthetical_then_plain("액티브(H) ETF")] From 34ee27438fc17b27a3a4b582bb91cf10e4c56898 Mon Sep 17 00:00:00 2001 From: devfive Date: Tue, 1 Sep 2026 13:35:10 +0900 Subject: [PATCH 39/77] Localize middle-dot terminators from real prefixes --- docs/corpus-analysis/NIKL_2025_V1.md | 86 +++++++++---------- .../braillify/examples/nikl_corpus_analyze.rs | 79 ++++++++++++----- 2 files changed, 99 insertions(+), 66 deletions(-) diff --git a/docs/corpus-analysis/NIKL_2025_V1.md b/docs/corpus-analysis/NIKL_2025_V1.md index 1bdda505..0a1a3538 100644 --- a/docs/corpus-analysis/NIKL_2025_V1.md +++ b/docs/corpus-analysis/NIKL_2025_V1.md @@ -298,10 +298,10 @@ This ranking removes only cases whose first difference is inside an existing out | Rank | Expected → actual first cell | Residual cases | |---:|---|---:| -| 1 | `U+2810 ⠐ -> U+2832 ⠲` | 297 | -| 2 | `U+2832 ⠲ -> U+2838 ⠸` | 290 | -| 3 | `U+2834 ⠴ -> U+2800 ⠀` | 261 | -| 4 | `U+2800 ⠀ -> U+2832 ⠲` | 227 | +| 1 | `U+2832 ⠲ -> U+2838 ⠸` | 290 | +| 2 | `U+2834 ⠴ -> U+2800 ⠀` | 261 | +| 3 | `U+2800 ⠀ -> U+2832 ⠲` | 227 | +| 4 | `U+2810 ⠐ -> U+2832 ⠲` | 223 | | 5 | `U+2826 ⠦ -> U+2800 ⠀` | 223 | | 6 | `U+2810 ⠐ -> U+2802 ⠂` | 211 | | 7 | `U+2817 ⠗ -> U+2820 ⠠` | 164 | @@ -314,34 +314,11 @@ This ranking removes only cases whose first difference is inside an existing out | 14 | `U+2810 ⠐ -> U+2814 ⠔` | 98 | | 15 | `U+2834 ⠴ -> U+2830 ⠰` | 98 | | 16 | `U+280E ⠎ -> U+2820 ⠠` | 92 | -| 17 | `U+2804 ⠄ -> U+2834 ⠴` | 86 | -| 18 | `U+2803 ⠃ -> U+2820 ⠠` | 85 | +| 17 | `U+2803 ⠃ -> U+2820 ⠠` | 87 | +| 18 | `U+2804 ⠄ -> U+2834 ⠴` | 86 | | 19 | `U+2824 ⠤ -> U+2800 ⠀` | 78 | | 20 | `U+2826 ⠦ -> U+2834 ⠴` | 76 | -### Residual `U+2810 ⠐ -> U+2832 ⠲` - -- `sentence_01.json` #521: 이 밖에 정 회장은 올해부터 시행하는 새 국제회계기준(IFRS17)·새 지급여력제도(K-ICS·킥스)의 연착륙을 지원하며서 ‘신제도 안정화 협의체’와 같은 소통 채널을 운영하기로 했다. - - expected: `⠠⠅⠤⠠⠠⠊⠉⠎⠐⠆⠋⠕⠁⠠⠪⠠⠴⠺⠀⠡⠰⠣⠁⠐` - - actual: `⠠⠅⠤⠠⠠⠊⠉⠎⠲⠐⠆⠋⠕⠁⠠⠪⠠⠴⠺⠀⠡⠰⠣⠁` - - first differing cell (zero-based): 93 - - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_02.json` #327: 또한, ‘도가(Dog+Yoga)’ 등 반려동물과 함께 즐길 수 있는 다양한 프로그램이 준비돼 있으며, 참가자 전원에게는 태안산 황토 호박고구마(3kg) 및 요가매트가 제공된다. - - expected: `⠫⠦⠄⠴⠠⠙⠕⠛⠐⠖⠠⠽⠕⠛⠁⠠⠴⠴⠄⠀⠊⠪⠶⠀` - - actual: `⠫⠦⠄⠴⠠⠙⠕⠛⠲⠢⠴⠠⠽⠕⠛⠁⠠⠴⠴⠄⠀⠊⠪⠶` - - first differing cell (zero-based): 19 - - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_03.json` #17: 재테크 시장에서 온·오프라인 플랫폼 ‘매경 엠플러스(M+)’가 주목받고 있다. M+는 거시경제, 주식, 부동산, 가상자산 등 재테크의 모든 것을 온라인 사이트를 통해 제공하고 있다. - - expected: `⠎⠠⠪⠦⠄⠴⠠⠍⠐⠖⠠⠴⠴⠄⠫⠀⠨⠍⠑⠭⠘⠔⠈⠥` - - actual: `⠎⠠⠪⠦⠄⠴⠠⠍⠲⠀⠢⠀⠠⠴⠴⠄⠫⠀⠨⠍⠑⠭⠘⠔` - - first differing cell (zero-based): 54 - - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_04.json` #329: 학생 맞춤형 학습 지원(Teaching), 체육활동 지원(Health), 사회성·심리정서 지원(Emotion)으로 구성된 ‘더(T·H·E) 자람 프로젝트’인 특별 지원은 이번 학기부터 이뤄진다. - - expected: `⠦⠊⠎⠦⠄⠴⠠⠞⠐⠆⠴⠠⠓⠐⠆⠴⠠⠑⠠⠴⠀⠨⠐⠣` - - actual: `⠦⠊⠎⠦⠄⠴⠠⠞⠲⠐⠆⠴⠠⠓⠲⠐⠆⠴⠠⠑⠠⠴⠀⠨` - - first differing cell (zero-based): 115 - - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` - ### Residual `U+2832 ⠲ -> U+2838 ⠸` - `sentence_01.json` #314: 이와 관련해서 FAA는 “FAA항공정보(NOTAMS) 업데이트에 관여하는 시스템이 작동하지 않고 있다”며 “복구작업을 벌이고 있지만 현재로서는 복구 시점을 예상하기 어렵다”고 말했다. @@ -411,6 +388,29 @@ This ranking removes only cases whose first difference is inside an existing out - first differing cell (zero-based): 40 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +### Residual `U+2810 ⠐ -> U+2832 ⠲` + +- `sentence_01.json` #521: 이 밖에 정 회장은 올해부터 시행하는 새 국제회계기준(IFRS17)·새 지급여력제도(K-ICS·킥스)의 연착륙을 지원하며서 ‘신제도 안정화 협의체’와 같은 소통 채널을 운영하기로 했다. + - expected: `⠠⠅⠤⠠⠠⠊⠉⠎⠐⠆⠋⠕⠁⠠⠪⠠⠴⠺⠀⠡⠰⠣⠁⠐` + - actual: `⠠⠅⠤⠠⠠⠊⠉⠎⠲⠐⠆⠋⠕⠁⠠⠪⠠⠴⠺⠀⠡⠰⠣⠁` + - first differing cell (zero-based): 93 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_02.json` #327: 또한, ‘도가(Dog+Yoga)’ 등 반려동물과 함께 즐길 수 있는 다양한 프로그램이 준비돼 있으며, 참가자 전원에게는 태안산 황토 호박고구마(3kg) 및 요가매트가 제공된다. + - expected: `⠫⠦⠄⠴⠠⠙⠕⠛⠐⠖⠠⠽⠕⠛⠁⠠⠴⠴⠄⠀⠊⠪⠶⠀` + - actual: `⠫⠦⠄⠴⠠⠙⠕⠛⠲⠢⠴⠠⠽⠕⠛⠁⠠⠴⠴⠄⠀⠊⠪⠶` + - first differing cell (zero-based): 19 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_03.json` #17: 재테크 시장에서 온·오프라인 플랫폼 ‘매경 엠플러스(M+)’가 주목받고 있다. M+는 거시경제, 주식, 부동산, 가상자산 등 재테크의 모든 것을 온라인 사이트를 통해 제공하고 있다. + - expected: `⠎⠠⠪⠦⠄⠴⠠⠍⠐⠖⠠⠴⠴⠄⠫⠀⠨⠍⠑⠭⠘⠔⠈⠥` + - actual: `⠎⠠⠪⠦⠄⠴⠠⠍⠲⠀⠢⠀⠠⠴⠴⠄⠫⠀⠨⠍⠑⠭⠘⠔` + - first differing cell (zero-based): 54 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_04.json` #1069: HMM·삼성중공업·파나시아·한국선급 등 4개사는 지난 27일 선박 이산화탄소 포집·액화 저장 기술(OCCS) 통합 실증 연구를 위한 업무협약(MOU)을 체결했다고 28일 밝혔다. + - expected: `⠴⠠⠠⠓⠍⠍⠐⠆⠇⠢⠠⠻⠨⠍⠶⠈⠿⠎⠃⠐⠆⠙⠉⠠` + - actual: `⠴⠠⠠⠓⠍⠍⠲⠐⠆⠇⠢⠠⠻⠨⠍⠶⠈⠿⠎⠃⠐⠆⠙⠉` + - first differing cell (zero-based): 6 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` + ### Residual `U+2826 ⠦ -> U+2800 ⠀` - `sentence_01.json` #234: 안랩은 블록체인 자회사 안랩블록체인컴퍼니가 웹(Web)3 지갑인 ‘ABC 월렛(Wallet)’ 모바일 버전을 구글플레이와 애플 앱스토어에 출시했다고 10일 발표했다. @@ -672,11 +672,11 @@ Representative `mismatch` samples: Of the 1022 candidates, 639 are the actual `pending_rule_review` subcluster. The other 383 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. -For this output-signature audit, 662 mismatches were evaluable and 390 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. +For this output-signature audit, 662 mismatches were evaluable and 392 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. Localized first-difference transitions: -- `U+2801 ⠁ -> U+281C ⠜`: 383 +- `U+2801 ⠁ -> U+281C ⠜`: 385 - `U+2810 ⠐ -> U+2815 ⠕`: 3 - `U+2800 ⠀ -> U+2820 ⠠`: 2 - `U+2820 ⠠ -> U+280A ⠊`: 1 @@ -758,11 +758,11 @@ Representative `mismatch` samples: Of the 816 candidates, 434 are the actual `pending_rule_review` subcluster. The other 382 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. -For this output-signature audit, 503 mismatches were evaluable and 326 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. +For this output-signature audit, 503 mismatches were evaluable and 327 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. Localized first-difference transitions: -- `U+2811 ⠑ -> U+282B ⠫`: 323 +- `U+2811 ⠑ -> U+282B ⠫`: 324 - `U+2810 ⠐ -> U+280E ⠎`: 1 - `U+2810 ⠐ -> U+281D ⠝`: 1 - `U+2815 ⠕ -> U+2820 ⠠`: 1 @@ -1809,15 +1809,15 @@ Representative `mismatch` samples: Of the 1093 candidates, 627 are the actual `pending_rule_review` subcluster. The other 466 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. -For this output-signature audit, 955 mismatches were evaluable and 240 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. +For this output-signature audit, 955 mismatches were evaluable and 241 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. Localized first-difference transitions: - `U+2834 ⠴ -> U+2830 ⠰`: 235 +- `U+2803 ⠃ -> U+2820 ⠠`: 1 - `U+2815 ⠕ -> U+2837 ⠷`: 1 - `U+2817 ⠗ -> U+2820 ⠠`: 1 - `U+2820 ⠠ -> U+2805 ⠅`: 1 -- `U+2820 ⠠ -> U+280E ⠎`: 1 Mismatch primary-class distribution: @@ -1895,15 +1895,11 @@ Representative `mismatch` samples: Of the 577 candidates, 565 are the actual `pending_rule_review` subcluster. The other 12 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. -For this output-signature audit, 577 mismatches were evaluable and 357 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. +For this output-signature audit, 577 mismatches were evaluable and 417 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. Localized first-difference transitions: -- `U+2810 ⠐ -> U+2832 ⠲`: 342 -- `U+2801 ⠁ -> U+2820 ⠠`: 5 -- `U+2803 ⠃ -> U+2820 ⠠`: 3 -- `U+2801 ⠁ -> U+281C ⠜`: 2 -- `U+2815 ⠕ -> U+2820 ⠠`: 2 +- `U+2810 ⠐ -> U+2832 ⠲`: 417 Mismatch primary-class distribution: @@ -2626,19 +2622,19 @@ Corpus contradictions remain a separate gate: identical inputs with conflicting UEB 10.12.1 (2024 UEB PDF pp.191-192, printed pp.163-164) says not to use a contraction when letters within an abbreviation or acronym are known to be pronounced separately, but to use the contraction in case of doubt. Its official `DAR` example writes separate `a` and `r` cells, while UEB 10.12.2's official `START` example uses the `ar` groupsign. Both full-encoder controls pass. Thus an uppercase surface containing `AR` does not itself supply the pronunciation or lexical meaning needed to select either form. -The output-localized cohort contains 1022 candidates, 360 exact controls, and 662 mismatches. Existing mismatch primary classes are preserved: 639 `pending_rule_review`, 14 `corpus_suspect`, 3 `comparison_method`, and 6 `unsupported_character_review`. Of 662 evaluable mismatches, 390 have their first difference inside the detected current-engine run: 383 `U+2801 ⠁ -> U+281C ⠜` and 0 `U+281C ⠜ -> U+2801 ⠁`. Across raw pending transitions and the final residual after localized cohorts, the target count is 393 -> 7; the reverse is 2 -> 2. Inputs denoting separately pronounced initialisms such as corpus `AR`/`ARS` coexist with exact or officially contracted controls. This is deterministic evidence for a pronunciation-dependent pending cohort, not an engine rule or primary reclassification. Representative shard/index samples are retained above. +The output-localized cohort contains 1022 candidates, 360 exact controls, and 662 mismatches. Existing mismatch primary classes are preserved: 639 `pending_rule_review`, 14 `corpus_suspect`, 3 `comparison_method`, and 6 `unsupported_character_review`. Of 662 evaluable mismatches, 392 have their first difference inside the detected current-engine run: 385 `U+2801 ⠁ -> U+281C ⠜` and 0 `U+281C ⠜ -> U+2801 ⠁`. Across raw pending transitions and the final residual after localized cohorts, the target count is 393 -> 7; the reverse is 2 -> 2. Inputs denoting separately pronounced initialisms such as corpus `AR`/`ARS` coexist with exact or officially contracted controls. This is deterministic evidence for a pronunciation-dependent pending cohort, not an engine rule or primary reclassification. Representative shard/index samples are retained above. ### Roman run after a closed Roman enclosure Korean rule 29 places one Roman indicator/terminator pair around consecutive Roman items, with `Los Angeles` and `Table of Contents` as its printed multiword examples. Rule 34 separately omits the Roman terminator when Roman text is enclosed by quotation marks or parentheses. Neither printed rule states whether a later Roman run after the enclosure, intervening punctuation, and whitespace is a continuation of that section or a fresh section. The input gate therefore detects only the structural boundary; it does not label the later run as semantically new. -The cohort contains 1093 candidates, 138 exact controls, and 955 mismatches. Existing mismatch primary classes are preserved: 627 `pending_rule_review`, 321 `corpus_suspect`, and 7 `unsupported_character_review`. Of 955 evaluable mismatches, 240 are output-localized to the current later-run signature plus its one leading boundary cell: 235 `U+2834 ⠴ -> U+2830 ⠰` and 0 `U+2830 ⠰ -> U+2834 ⠴`. The target counted 336 raw and 333 residual cases before this cohort; it is now 98 final residual. Across raw and final residual maps, `U+2830 ⠰ -> U+2834 ⠴` is 3 -> 1. Exact controls coexist with both directions, and 321 mismatches are already independently identified corpus contradictions. Without a printed fresh-entry example or semantic enclosure model, changing continuation state would be reference-fitting; this remains a deterministic pending/corpus-review cohort only. Representative shard/index samples are retained above. +The cohort contains 1093 candidates, 138 exact controls, and 955 mismatches. Existing mismatch primary classes are preserved: 627 `pending_rule_review`, 321 `corpus_suspect`, and 7 `unsupported_character_review`. Of 955 evaluable mismatches, 241 are output-localized to the current later-run signature plus its one leading boundary cell: 235 `U+2834 ⠴ -> U+2830 ⠰` and 0 `U+2830 ⠰ -> U+2834 ⠴`. The target counted 336 raw and 333 residual cases before this cohort; it is now 98 final residual. Across raw and final residual maps, `U+2830 ⠰ -> U+2834 ⠴` is 3 -> 1. Exact controls coexist with both directions, and 321 mismatches are already independently identified corpus contradictions. Without a printed fresh-entry example or semantic enclosure model, changing continuation state would be reference-fitting; this remains a deterministic pending/corpus-review cohort only. Representative shard/index samples are retained above. ### Uppercase Roman runs containing `ED` UEB 10.12.1 (2024 UEB PDF pp.191-192, printed pp.163-164) requires separate letters when an abbreviation's letters are known to be pronounced separately, and its official `OED` example writes `e` and `d` separately. Rule 10.12.2 otherwise uses contractions; its official `BEd` example uses the `ed` groupsign. Both full-encoder controls pass. Consequently an uppercase `ED` surface cannot establish pronunciation or abbreviation semantics by spelling alone. -The cohort contains 816 candidates, 313 exact controls, and 503 mismatches. Existing mismatch primary classes remain 434 `pending_rule_review`, 63 `corpus_suspect`, 1 `comparison_method`, and 5 `unsupported_character_review`. Of 503 evaluable mismatches, 326 are localized to the detected current-engine run: 323 `U+2811 ⠑ -> U+282B ⠫` and 0 `U+282B ⠫ -> U+2811 ⠑`. Across raw pending transitions and the final residual after localized cohorts, the target is 326 -> 2 and the reverse is 0 -> 0. Corpus initialisms such as `LED` and `GED` require external pronunciation knowledge, while exact and official controls preserve contraction-bearing outcomes. No engine change or primary reclassification is made; representative shard/index samples are retained above. +The cohort contains 816 candidates, 313 exact controls, and 503 mismatches. Existing mismatch primary classes remain 434 `pending_rule_review`, 63 `corpus_suspect`, 1 `comparison_method`, and 5 `unsupported_character_review`. Of 503 evaluable mismatches, 327 are localized to the detected current-engine run: 324 `U+2811 ⠑ -> U+282B ⠫` and 0 `U+282B ⠫ -> U+2811 ⠑`. Across raw pending transitions and the final residual after localized cohorts, the target is 326 -> 2 and the reverse is 0 -> 0. Corpus initialisms such as `LED` and `GED` require external pronunciation knowledge, while exact and official controls preserve contraction-bearing outcomes. No engine change or primary reclassification is made; representative shard/index samples are retained above. ### Uppercase Roman runs containing `ST` @@ -2708,7 +2704,7 @@ Current rule-34 opening-order measurement: 64382 structural candidates, 52408 ex Current all-caps Roman middle-dot measurement: 97 candidates, 0 exact controls, 97 mismatches, and 96 members in the actual `pending_rule_review` subcluster. This cross-cutting cohort preserves every primary class and is not an engine routing rule. -Current Roman-before-middle-dot boundary measurement: 577 candidates, 0 exact controls, 577 mismatches, 565 members in the actual `pending_rule_review` subcluster, and 357/577 evaluable mismatches whose first difference is localized to the attached Roman/middle-dot output signature. Rules 29, 33, and 50 support the current terminator path but do not support the localized reference omission; no engine change or primary-class rewrite is inferred. +Current Roman-before-middle-dot boundary measurement: 577 candidates, 0 exact controls, 577 mismatches, 565 members in the actual `pending_rule_review` subcluster, and 417/577 evaluable mismatches whose first difference is localized to the one current terminator immediately before the middle dot. The locator encodes each real input prefix ending at the dot, so identifier state such as `K-ICS·...` is retained without consulting expected. This raises localized target coverage from 342 to 417 and reduces the then-leading residual `⠐ -> ⠲` transition from 297 to 223. Rules 29, 33, and 50 support the current terminator path but do not support the localized reference omission; no engine change or primary-class rewrite is inferred. Current inline parenthesized-operator measurement: 23 candidates, 17 exact controls, 6 mismatches, 6 members in the actual `pending_rule_review` subcluster, and 2/6 evaluable mismatches whose first differing cell is inside the emitted structure. Primary classes are preserved. At that implementation checkpoint, the strict cohort moved from 0 to 17 exact cases; the corpus-wide total moved from 65,491 to 65,514 (+23 exact) because the same PDF-backed spacing rule also applied outside the stricter Korean-boundary audit gate. These are immutable checkpoint counts rather than the report's later cumulative total. The complete standard suite remained 5,141/5,141. diff --git a/libs/braillify/examples/nikl_corpus_analyze.rs b/libs/braillify/examples/nikl_corpus_analyze.rs index 549cdab4..20e4fc49 100644 --- a/libs/braillify/examples/nikl_corpus_analyze.rs +++ b/libs/braillify/examples/nikl_corpus_analyze.rs @@ -2288,12 +2288,44 @@ fn roman_run_before_middle_dot_boundary_spans(input: &str) -> Vec { spans } +/// Locates only the current terminator immediately before an attached middle +/// dot by encoding the real input prefix ending at that boundary. This keeps +/// hyphen/identifier state such as `K-ICS·...` without consulting expected. +fn roman_middle_dot_boundary_actual_ranges( + input: &str, + actual: &str, +) -> Vec> { + let actual_cells = actual.chars().collect::>(); + roman_run_before_middle_dot_boundary_spans(input) + .into_iter() + .filter_map(|span| { + let middle_dot_offset = input[span.start_byte..span.end_byte].find('·')?; + let middle_dot_byte = span.start_byte + middle_dot_offset; + let prefix = braillify::encode_to_unicode(&input[..middle_dot_byte]).ok()?; + if !actual.starts_with(&prefix) { + return None; + } + let end = prefix.chars().count(); + let start = end.checked_sub(1)?; + (actual_cells.get(start) == Some(&'⠲')).then_some((start, end)) + }) + .collect::>() + .into_iter() + .map(|(start, end)| start..end) + .collect() +} + fn first_difference_at_roman_middle_dot_boundary(item: &EncodedCase) -> bool { - first_difference_in_korean_context_signature_spans( - item, - &roman_run_before_middle_dot_boundary_spans(&item.located.case.input), - 0, - ) + let Ok(actual) = &item.actual else { + return false; + }; + if actual == &item.located.case.unicode { + return false; + } + let first_difference = first_difference_cell(&item.located.case.unicode, actual); + roman_middle_dot_boundary_actual_ranges(&item.located.case.input, actual) + .into_iter() + .any(|range| range.contains(&first_difference)) } #[derive(Clone, Copy, Debug, PartialEq, Eq)] @@ -4816,9 +4848,13 @@ fn markdown(report: &AnalysisReport) -> String { "\nCurrent Roman-before-middle-dot boundary measurement: {} candidates, {} exact \ controls, {} mismatches, {pending} members in the actual `pending_rule_review` \ subcluster, and {}/{} evaluable mismatches whose first difference is localized to \ - the attached Roman/middle-dot output signature. Rules 29, 33, and 50 support the \ - current terminator path but do not support the localized reference omission; no \ - engine change or primary-class rewrite is inferred.\n", + the one current terminator immediately before the middle dot. The locator encodes \ + each real input prefix ending at the dot, so identifier state such as `K-ICS·...` \ + is retained without consulting expected. This raises localized target coverage from \ + 342 to 417 and reduces the then-leading residual `⠐ -> ⠲` transition from 297 to \ + 223. Rules 29, 33, and 50 support the current terminator path but do not support the \ + localized reference omission; no engine change or primary-class rewrite is \ + inferred.\n", stats.candidates, stats.exact, stats.mismatch, @@ -6099,21 +6135,22 @@ mod tests { } #[rstest::rstest] - #[case::roman_korean("신작 PC·모바일")] - #[case::roman_roman("AI·SW교육")] - #[case::mixed_case("기관(Fed·연준)")] - fn localizes_roman_middle_dot_boundary_in_complete_output(#[case] input: &str) { + #[case::roman_korean("신작 PC·모바일", true)] + #[case::multi_letter_roman_continuation("AI·SW교육", false)] + #[case::mixed_case("기관(Fed·연준)", true)] + fn localizes_only_current_terminator_before_middle_dot( + #[case] input: &str, + #[case] expected_terminator: bool, + ) { let actual = braillify::encode_to_unicode(input).expect("probe must encode"); - let ranges = korean_context_signature_ranges( - input, - &actual, - &roman_run_before_middle_dot_boundary_spans(input), - 0, - ); + let ranges = roman_middle_dot_boundary_actual_ranges(input, &actual); - assert_eq!(ranges.len(), 1); - assert!(ranges[0].start < ranges[0].end); - assert!(ranges[0].end <= actual.chars().count()); + assert_eq!(ranges.len(), usize::from(expected_terminator)); + if let Some(range) = ranges.first() { + assert_eq!(range.end - range.start, 1); + assert_eq!(actual.chars().nth(range.start), Some('⠲')); + assert!(range.end <= actual.chars().count()); + } } #[rstest::rstest] From 57c4608fe033e5b908c38e6ebbc0129ecde8fbec Mon Sep 17 00:00:00 2001 From: devfive Date: Tue, 1 Sep 2026 13:50:21 +0900 Subject: [PATCH 40/77] Diagnose attached Roman Korean mode boundaries --- docs/corpus-analysis/NIKL_2025_V1.md | 192 ++++++++++--- .../braillify/examples/nikl_corpus_analyze.rs | 270 +++++++++++++++++- 2 files changed, 416 insertions(+), 46 deletions(-) diff --git a/docs/corpus-analysis/NIKL_2025_V1.md b/docs/corpus-analysis/NIKL_2025_V1.md index 0a1a3538..cc9a9dc7 100644 --- a/docs/corpus-analysis/NIKL_2025_V1.md +++ b/docs/corpus-analysis/NIKL_2025_V1.md @@ -298,49 +298,26 @@ This ranking removes only cases whose first difference is inside an existing out | Rank | Expected → actual first cell | Residual cases | |---:|---|---:| -| 1 | `U+2832 ⠲ -> U+2838 ⠸` | 290 | -| 2 | `U+2834 ⠴ -> U+2800 ⠀` | 261 | -| 3 | `U+2800 ⠀ -> U+2832 ⠲` | 227 | -| 4 | `U+2810 ⠐ -> U+2832 ⠲` | 223 | -| 5 | `U+2826 ⠦ -> U+2800 ⠀` | 223 | -| 6 | `U+2810 ⠐ -> U+2802 ⠂` | 211 | -| 7 | `U+2817 ⠗ -> U+2820 ⠠` | 164 | -| 8 | `U+2820 ⠠ -> U+2809 ⠉` | 162 | -| 9 | `U+283C ⠼ -> U+2800 ⠀` | 153 | -| 10 | `U+2826 ⠦ -> U+2810 ⠐` | 147 | -| 11 | `U+281B ⠛ -> U+2823 ⠣` | 144 | -| 12 | `U+2810 ⠐ -> U+2800 ⠀` | 130 | -| 13 | `U+2811 ⠑ -> U+283B ⠻` | 101 | -| 14 | `U+2810 ⠐ -> U+2814 ⠔` | 98 | -| 15 | `U+2834 ⠴ -> U+2830 ⠰` | 98 | -| 16 | `U+280E ⠎ -> U+2820 ⠠` | 92 | -| 17 | `U+2803 ⠃ -> U+2820 ⠠` | 87 | -| 18 | `U+2804 ⠄ -> U+2834 ⠴` | 86 | -| 19 | `U+2824 ⠤ -> U+2800 ⠀` | 78 | -| 20 | `U+2826 ⠦ -> U+2834 ⠴` | 76 | - -### Residual `U+2832 ⠲ -> U+2838 ⠸` - -- `sentence_01.json` #314: 이와 관련해서 FAA는 “FAA항공정보(NOTAMS) 업데이트에 관여하는 시스템이 작동하지 않고 있다”며 “복구작업을 벌이고 있지만 현재로서는 복구 시점을 예상하기 어렵다”고 말했다. - - expected: `⠀⠦⠴⠠⠠⠋⠁⠁⠲⠚⠶⠈⠿⠨⠻⠘⠥⠦⠄⠴⠠⠠⠝⠕` - - actual: `⠀⠦⠴⠠⠠⠋⠁⠁⠸⠷⠚⠶⠈⠿⠨⠻⠘⠥⠸⠾⠐⠣⠠⠠` - - first differing cell (zero-based): 30 - - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_02.json` #483: 충남교육청(교육감 김지철)은 7월 31일부터 오는 11일까지 도내 Wee센터·Wee클래스 전문상담교사, 전문상담사, 임상심리사 150명을 대상으로 ‘2023 하반기 위(Wee) 프로젝트 역량강화 직무연수’를 추진하고 있다고 밝혔다. - - expected: `⠉⠗⠀⠴⠠⠺⠑⠑⠲⠠⠝⠒⠓⠎⠐⠆⠴⠠⠺⠑⠑⠲⠋⠮` - - actual: `⠉⠗⠀⠴⠠⠺⠑⠑⠸⠷⠠⠝⠒⠓⠎⠸⠾⠲⠸⠂⠴⠠⠺⠑` - - first differing cell (zero-based): 70 - - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_03.json` #402: 블룸버그에 따르면, BAT와 자회사인 BAT마케팅싱가포르(BATMS)는 은행사기법과 국제비상경제권법(IEEPA)을 위반하며 싱가포르의 제3자 회사를 통해 북한에서 수년간 담배를 팔아온 것으로 알려졌다. - - expected: `⠟⠀⠴⠠⠠⠃⠁⠞⠲⠑⠋⠝⠓⠕⠶⠠⠕⠶⠫⠙⠥⠐⠪⠦` - - actual: `⠟⠀⠴⠠⠠⠃⠁⠞⠸⠷⠑⠋⠝⠓⠕⠶⠠⠕⠶⠫⠙⠥⠐⠪` - - first differing cell (zero-based): 40 - - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_04.json` #1660: 공공운수노조 더불어사는희망연대본부는 KT 인터넷이나 IPTV 개통과 수리를 담당하는 KT 그룹사 KT서비스(KTS) 북부·남부 소속 기사들로 구성된 노조다. - - expected: `⠃⠇⠀⠴⠠⠠⠅⠞⠲⠠⠎⠘⠕⠠⠪⠦⠄⠴⠠⠠⠅⠞⠎⠠` - - actual: `⠃⠇⠀⠴⠠⠠⠅⠞⠸⠷⠠⠎⠘⠕⠠⠪⠸⠾⠐⠣⠠⠠⠅⠞` - - first differing cell (zero-based): 100 - - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +| 1 | `U+2834 ⠴ -> U+2800 ⠀` | 261 | +| 2 | `U+2800 ⠀ -> U+2832 ⠲` | 227 | +| 3 | `U+2810 ⠐ -> U+2832 ⠲` | 223 | +| 4 | `U+2826 ⠦ -> U+2800 ⠀` | 223 | +| 5 | `U+2810 ⠐ -> U+2802 ⠂` | 211 | +| 6 | `U+2817 ⠗ -> U+2820 ⠠` | 164 | +| 7 | `U+2820 ⠠ -> U+2809 ⠉` | 162 | +| 8 | `U+283C ⠼ -> U+2800 ⠀` | 153 | +| 9 | `U+2826 ⠦ -> U+2810 ⠐` | 147 | +| 10 | `U+281B ⠛ -> U+2823 ⠣` | 144 | +| 11 | `U+2810 ⠐ -> U+2800 ⠀` | 130 | +| 12 | `U+2811 ⠑ -> U+283B ⠻` | 101 | +| 13 | `U+2810 ⠐ -> U+2814 ⠔` | 98 | +| 14 | `U+2834 ⠴ -> U+2830 ⠰` | 98 | +| 15 | `U+280E ⠎ -> U+2820 ⠠` | 92 | +| 16 | `U+2803 ⠃ -> U+2820 ⠠` | 87 | +| 17 | `U+2804 ⠄ -> U+2834 ⠴` | 86 | +| 18 | `U+2824 ⠤ -> U+2800 ⠀` | 78 | +| 19 | `U+2826 ⠦ -> U+2834 ⠴` | 76 | +| 20 | `U+2815 ⠕ -> U+2800 ⠀` | 74 | ### Residual `U+2834 ⠴ -> U+2800 ⠀` @@ -549,9 +526,32 @@ This ranking removes only cases whose first difference is inside an existing out - first differing cell (zero-based): 98 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +### Residual `U+281B ⠛ -> U+2823 ⠣` + +- `sentence_01.json` #1753: 지난해 말 경기주택도시공사(GH)에서 퇴직한 전씨는 ‘성남FC 불법 후원금 의혹’과 관련해 검찰 조사를 받은 바 있으며 ‘GH 합숙소 의혹’에도 연루된 것으로 알려졌다. + - expected: `⠈⠿⠇⠦⠄⠴⠠⠠⠛⠓⠠⠴⠝⠠⠎⠀⠓⠽⠨⠕⠁⠚⠒⠀` + - actual: `⠈⠿⠇⠦⠄⠴⠠⠠⠣⠠⠴⠝⠠⠎⠀⠓⠽⠨⠕⠁⠚⠒⠀⠨` + - first differing cell (zero-based): 31 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_02.json` #235: 가스열펌프(GHP)는 전기모터 대신 가스엔진을 이용해 구동하는 냉난방기기로 여름철 전력피크 완화대책으로 학교나 상업용 건물 등에 널리 보급됐다. + - expected: `⠢⠙⠪⠦⠄⠴⠠⠠⠛⠓⠏⠠⠴⠉⠵⠀⠨⠾⠈⠕⠑⠥⠓⠎` + - actual: `⠢⠙⠪⠦⠄⠴⠠⠠⠣⠏⠠⠴⠉⠵⠀⠨⠾⠈⠕⠑⠥⠓⠎⠀` + - first differing cell (zero-based): 14 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_03.json` #292: GH 주거 분야 전문인력과 HUG(주택도시보증공사), 변호사, 법무사 등 부동산·금융 전문인력이 상주하며 전세사기 피해자에게 부동산 법률, 긴급 금융지원, 주거지원 등 종합적인 맞춤형 상담을 제공한다. + - expected: `⠴⠠⠠⠛⠓⠲⠀⠨⠍⠈⠎⠀⠘⠛⠜⠀⠨⠾⠑⠛⠟⠐⠱⠁` + - actual: `⠴⠠⠠⠣⠲⠀⠨⠍⠈⠎⠀⠘⠛⠜⠀⠨⠾⠑⠛⠟⠐⠱⠁⠈` + - first differing cell (zero-based): 3 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_04.json` #1853: 경기주택도시공사(GH)가 층간 소음 등 아파트 품질 사각지대를 일소하고, 건설산업 근로자의 적정 임금을 보장하는 등 경기도 대표 공공기관으로써 사회적 책임 실천에 나선다. + - expected: `⠈⠿⠇⠦⠄⠴⠠⠠⠛⠓⠠⠴⠫⠀⠰⠪⠶⠫⠒⠀⠠⠥⠪⠢` + - actual: `⠈⠿⠇⠦⠄⠴⠠⠠⠣⠠⠴⠫⠀⠰⠪⠶⠫⠒⠀⠠⠥⠪⠢⠀` + - first differing cell (zero-based): 21 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` + ## Cross-cutting input-only structural cohorts -These are cross-cutting input-only structural cohorts, not new primary classes and not engine routing rules. Candidate selection never changes a case's existing primary class by itself. Only cohort members already classified as `pending_rule_review` form a pending subcluster; exact and other-primary members are controls that retain their existing outcomes. A separate classifier may use independently justified, output-localized PDF evidence, as in the rule-34 three-cell contradiction below. The `uppercase_roman_headword_closed_multiword_parenthetical` gate requires a two-or-more character uppercase ASCII headword immediately followed by a closed parenthesis whose contents are two or more ASCII Roman words separated only by spaces. Because the contents admit only letters and spaces, visible operators, subscript/superscript notation, and nested parentheses are excluded deterministically. The `mixed_roman_korean_word_before_uppercase_headword_expansion` gate further requires a whitespace-delimited preceding word that contains both ASCII Roman and Korean and ends in Korean. It locates the following uppercase headword itself, including its immediately preceding emitted cell, so an earlier Roman entry in the same sentence cannot satisfy the output audit. The `single_capital_followed_by_parenthesized_digits` gate requires a standalone capital, a non-empty closed ASCII-digit parenthetical, and alphanumeric outer boundaries. It likewise includes the emitted entry-boundary cell in localization. The `uppercase_roman_run_followed_by_hyphen_digits` gate requires a maximal uppercase ASCII run, literal ASCII hyphen-minus, a non-empty digit run, and alphanumeric outer boundaries. Its localized range includes the current encoded run and its immediately preceding cell, keeping `F-35`-style routing separate from both parenthesized digits and headword expansions. The `standalone_multi_character_uppercase_roman_word` gate finds maximal ASCII-letter runs of two or more capitals with non-alphanumeric boundaries; a run immediately followed by `(` is excluded so the HCA-style headword itself is not counted by both gates. The `korean_prefixed_closed_allcaps_parenthetical` gate requires an immediately preceding Korean character and a closed body of two or more uppercase ASCII letters. It intentionally contains both acronym annotations (`책임자(COO)`) and scientific formulae (`일산화탄소(CO)`) so their semantic collision remains measurable. The `korean_prefixed_closed_roman_annotation_rule_34_order` gate accepts the narrower rule-34 body grammar after an immediately preceding Korean character and localizes only the current engine's Korean opening-parenthesis cells. This distinguishes the PDF's parenthesis-before-Roman-indicator order from unrelated differences later in the same sentence. The `multi_character_allcaps_roman_runs_joined_by_middle_dot` gate requires two maximal ASCII-letter runs of at least two capitals joined directly by U+00B7, with non-alphanumeric outer boundaries. It records shapes such as `AI·SW` without assigning prose, mathematics, or science semantics. The `roman_run_immediately_before_attached_middle_dot_boundary` gate is output-localized: it requires a maximal ASCII-letter run immediately before U+00B7 and an attached Korean character or ASCII-letter run after it, then searches for that whole current-engine signature in the actual output. It therefore isolates the Roman terminator boundary without treating unrelated middle dots elsewhere in the sentence as causal. The `korean_inline_parenthesized_single_arithmetic_operator` gate requires an immediate `Korean(` + one rule-45 arithmetic operator + `)Korean` span. Unlike broad coexistence traits, it also locates the current engine's emitted structure and counts a mismatch as signature-local only when the sentence's first differing cell falls inside that output range. The `allcaps_roman_run_containing_ou` gate finds maximal, alphanumeric-delimited uppercase ASCII runs containing adjacent `OU`. It locates the independently encoded run signature in the complete current output and counts only first differences inside that signature as localized. The `allcaps_roman_run_containing_st` gate applies the same output-position requirement to adjacent `ST`; it is kept separate because UEB 10.12 makes contraction use depend on how an abbreviation or acronym is pronounced. The `attached_ascii_roman_segments_joined_by_ampersand` gate requires non-empty ASCII-letter segments joined directly by `&`, excludes spaced/Korean/alphanumeric continuations, and localizes only the current output cell immediately before the ampersand through an independently encoded real-input prefix. The `decimal_point_between_ascii_digits` gate finds whitespace-delimited words containing `digit.digit` and reproduces each whole word in a neutral Korean context, so suffixes and punctuation remain part of the current-engine signature. The `compact_numeric_ascii_letter_suffix` gate finds a numeric prefix immediately followed by ASCII letters, includes the immediately preceding output cell as its entry boundary, and retains suffix-specific outcome counts. It intentionally includes both possible rule-69 units and ambiguous variable/identifier forms; membership alone does not assign unit semantics. The `rule69_ascii_unit_before_terminator_skipping_symbol` gate is narrower: it accepts only ASCII unit spellings already supported by rule 69, includes the immediately following rule-33/34 punctuation cell in the localized signature, and does not infer new units. The `tight_triangle_mark_immediately_before_korean` gate requires literal `△한글` with no input space and includes the first following Korean cell in its localized output range, so an observed missing-space difference is measured at the mark boundary. +These are cross-cutting input-only structural cohorts, not new primary classes and not engine routing rules. Candidate selection never changes a case's existing primary class by itself. Only cohort members already classified as `pending_rule_review` form a pending subcluster; exact and other-primary members are controls that retain their existing outcomes. A separate classifier may use independently justified, output-localized PDF evidence, as in the rule-34 three-cell contradiction below. The `uppercase_roman_headword_closed_multiword_parenthetical` gate requires a two-or-more character uppercase ASCII headword immediately followed by a closed parenthesis whose contents are two or more ASCII Roman words separated only by spaces. Because the contents admit only letters and spaces, visible operators, subscript/superscript notation, and nested parentheses are excluded deterministically. The `mixed_roman_korean_word_before_uppercase_headword_expansion` gate further requires a whitespace-delimited preceding word that contains both ASCII Roman and Korean and ends in Korean. It locates the following uppercase headword itself, including its immediately preceding emitted cell, so an earlier Roman entry in the same sentence cannot satisfy the output audit. The `single_capital_followed_by_parenthesized_digits` gate requires a standalone capital, a non-empty closed ASCII-digit parenthetical, and alphanumeric outer boundaries. It likewise includes the emitted entry-boundary cell in localization. The `uppercase_roman_run_followed_by_hyphen_digits` gate requires a maximal uppercase ASCII run, literal ASCII hyphen-minus, a non-empty digit run, and alphanumeric outer boundaries. Its localized range includes the current encoded run and its immediately preceding cell, keeping `F-35`-style routing separate from both parenthesized digits and headword expansions. The `standalone_multi_character_uppercase_roman_word` gate finds maximal ASCII-letter runs of two or more capitals with non-alphanumeric boundaries; a run immediately followed by `(` is excluded so the HCA-style headword itself is not counted by both gates. The `korean_prefixed_closed_allcaps_parenthetical` gate requires an immediately preceding Korean character and a closed body of two or more uppercase ASCII letters. It intentionally contains both acronym annotations (`책임자(COO)`) and scientific formulae (`일산화탄소(CO)`) so their semantic collision remains measurable. The `korean_prefixed_closed_roman_annotation_rule_34_order` gate accepts the narrower rule-34 body grammar after an immediately preceding Korean character and localizes only the current engine's Korean opening-parenthesis cells. This distinguishes the PDF's parenthesis-before-Roman-indicator order from unrelated differences later in the same sentence. The `multi_character_allcaps_roman_runs_joined_by_middle_dot` gate requires two maximal ASCII-letter runs of at least two capitals joined directly by U+00B7, with non-alphanumeric outer boundaries. It records shapes such as `AI·SW` without assigning prose, mathematics, or science semantics. The `roman_run_immediately_before_attached_middle_dot_boundary` gate is output-localized: it requires a maximal ASCII-letter run immediately before U+00B7 and an attached Korean character or ASCII-letter run after it, then searches for that whole current-engine signature in the actual output. It therefore isolates the Roman terminator boundary without treating unrelated middle dots elsewhere in the sentence as causal. The `attached_ascii_roman_to_korean_script_boundary` gate requires an ASCII letter run immediately followed by Korean script and localizes only the current mode marker there: either rule 29's final `⠲` or rule 39's opening `⠸⠷`. It deliberately does not infer the dominant language of the sentence from that surface boundary. The `korean_inline_parenthesized_single_arithmetic_operator` gate requires an immediate `Korean(` + one rule-45 arithmetic operator + `)Korean` span. Unlike broad coexistence traits, it also locates the current engine's emitted structure and counts a mismatch as signature-local only when the sentence's first differing cell falls inside that output range. The `allcaps_roman_run_containing_ou` gate finds maximal, alphanumeric-delimited uppercase ASCII runs containing adjacent `OU`. It locates the independently encoded run signature in the complete current output and counts only first differences inside that signature as localized. The `allcaps_roman_run_containing_st` gate applies the same output-position requirement to adjacent `ST`; it is kept separate because UEB 10.12 makes contraction use depend on how an abbreviation or acronym is pronounced. The `attached_ascii_roman_segments_joined_by_ampersand` gate requires non-empty ASCII-letter segments joined directly by `&`, excludes spaced/Korean/alphanumeric continuations, and localizes only the current output cell immediately before the ampersand through an independently encoded real-input prefix. The `decimal_point_between_ascii_digits` gate finds whitespace-delimited words containing `digit.digit` and reproduces each whole word in a neutral Korean context, so suffixes and punctuation remain part of the current-engine signature. The `compact_numeric_ascii_letter_suffix` gate finds a numeric prefix immediately followed by ASCII letters, includes the immediately preceding output cell as its entry boundary, and retains suffix-specific outcome counts. It intentionally includes both possible rule-69 units and ambiguous variable/identifier forms; membership alone does not assign unit semantics. The `rule69_ascii_unit_before_terminator_skipping_symbol` gate is narrower: it accepts only ASCII unit spellings already supported by rule 69, includes the immediately following rule-33/34 punctuation cell in the localized signature, and does not infer new units. The `tight_triangle_mark_immediately_before_korean` gate requires literal `△한글` with no input space and includes the first following Korean cell in its localized output range, so an observed missing-space difference is measured at the mark boundary. | Cluster | Candidates | Exact | Mismatch | Conflicting-reference cases | |---|---:|---:|---:|---:| @@ -561,6 +561,7 @@ These are cross-cutting input-only structural cohorts, not new primary classes a | `allcaps_roman_run_containing_ou` | 1816 | 64 | 1752 | 0 | | `allcaps_roman_run_containing_st` | 1479 | 678 | 801 | 0 | | `attached_ascii_roman_segments_joined_by_ampersand` | 802 | 273 | 529 | 0 | +| `attached_ascii_roman_to_korean_script_boundary` | 17693 | 12724 | 4969 | 0 | | `closed_roman_parenthetical_after_non_ascii_letter_boundary` | 63959 | 51806 | 12153 | 0 | | `compact_numeric_ascii_letter_suffix` | 2975 | 1859 | 1116 | 0 | | `decimal_point_between_ascii_digits` | 4546 | 3351 | 1195 | 0 | @@ -1041,6 +1042,107 @@ Representative `mismatch` samples: - first differing cell (zero-based): 60 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +### `attached_ascii_roman_to_korean_script_boundary` + +Of the 17693 candidates, 4141 are the actual `pending_rule_review` subcluster. The other 13552 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. + +For this output-signature audit, 4969 mismatches were evaluable and 268 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. + +Localized first-difference transitions: + +- `U+2832 ⠲ -> U+2838 ⠸`: 268 + +Mismatch primary-class distribution: + +- `comparison_method`: 60 +- `corpus_suspect`: 717 +- `pending_rule_review`: 4141 +- `unsupported_character_review`: 51 + +Representative `exact` samples: + +- `sentence_01.json` #1: LG전자는 ‘업(UP)가전’을 무기로 내세우고 있다. 이번 CES에서도 LG 씽큐 앱에서 터치만으로 제품 색상을 바꿀 수 있는 무드업 냉장고를 비롯한 다양한 업가전을 선보일 예정이다. + - expected: `⠴⠠⠠⠇⠛⠲⠨⠾⠨⠉⠵⠀⠠⠦⠎⠃⠦⠄⠴⠠⠠⠥⠏⠠` + - actual: `⠴⠠⠠⠇⠛⠲⠨⠾⠨⠉⠵⠀⠠⠦⠎⠃⠦⠄⠴⠠⠠⠥⠏⠠` + - current primary/reason: `exact` / `exact` +- `sentence_02.json` #4: KDI에 따르면, 비제조업 업황BSI(기업경기실사지수) 전망치는 2월 72에서 3월 74로 개선되고 있다. 중국 관광객 유입에 대한 기대감이 확산된 영향으로 분석된다. + - expected: `⠴⠠⠠⠅⠙⠊⠲⠝⠀⠠⠊⠐⠪⠑⠡⠐⠀⠘⠕⠨⠝⠨⠥⠎` + - actual: `⠴⠠⠠⠅⠙⠊⠲⠝⠀⠠⠊⠐⠪⠑⠡⠐⠀⠘⠕⠨⠝⠨⠥⠎` + - current primary/reason: `exact` / `exact` +- `sentence_03.json` #5: 또한 전 세계적인 흐름인 ‘ESG경영’이 지역기업이 도입할 수 있도록 지원하고, 코트라(KOTRA) 등 유관기관과 협력 지방공공기관 및 지역기업의 해외 진출도 지원한다. + - expected: `⠠⠊⠥⠚⠒⠀⠨⠾⠀⠠⠝⠈⠌⠨⠹⠟⠀⠚⠪⠐⠪⠢⠟⠀` + - actual: `⠠⠊⠥⠚⠒⠀⠨⠾⠀⠠⠝⠈⠌⠨⠹⠟⠀⠚⠪⠐⠪⠢⠟⠀` + - current primary/reason: `exact` / `exact` +- `sentence_04.json` #2: 주요 지수는 장중 2% 이상 하락했으나 장 막판 스위스중앙은행(SNB)이 나서 CS에 대한 지원 방침을 밝히면서 나스닥지수가 반등하는 등 한숨을 돌렸다. + - expected: `⠨⠍⠬⠀⠨⠕⠠⠍⠉⠵⠀⠨⠶⠨⠍⠶⠀⠼⠃⠴⠏⠀⠕⠇` + - actual: `⠨⠍⠬⠀⠨⠕⠠⠍⠉⠵⠀⠨⠶⠨⠍⠶⠀⠼⠃⠴⠏⠀⠕⠇` + - current primary/reason: `exact` / `exact` + +Representative `exact_rule29_terminator` samples: + +- `sentence_01.json` #1: LG전자는 ‘업(UP)가전’을 무기로 내세우고 있다. 이번 CES에서도 LG 씽큐 앱에서 터치만으로 제품 색상을 바꿀 수 있는 무드업 냉장고를 비롯한 다양한 업가전을 선보일 예정이다. + - expected: `⠾⠀⠴⠠⠠⠉⠑⠎⠲⠝⠠⠎⠊⠥⠀⠴⠠⠠⠇⠛⠲⠀⠠⠠` + - actual: `⠾⠀⠴⠠⠠⠉⠑⠎⠲⠝⠠⠎⠊⠥⠀⠴⠠⠠⠇⠛⠲⠀⠠⠠` + - current primary/reason: `exact` / `exact` +- `sentence_02.json` #41: 올해로 창립 10주년을 맞이한 IWPG는 유엔 경제사회이사회(UN ECOSOC)와 글로벌소통국(DGC)에 등록된 국제 NGO로서, 전쟁 반대와 실질적인 평화의 바람을 일으키고 있다. + - expected: `⠀⠴⠠⠠⠊⠺⠏⠛⠲⠉⠵⠀⠩⠝⠒⠀⠈⠻⠨⠝⠇⠚⠽⠕` + - actual: `⠀⠴⠠⠠⠊⠺⠏⠛⠲⠉⠵⠀⠩⠝⠒⠀⠈⠻⠨⠝⠇⠚⠽⠕` + - current primary/reason: `exact` / `exact` +- `sentence_03.json` #5: 또한 전 세계적인 흐름인 ‘ESG경영’이 지역기업이 도입할 수 있도록 지원하고, 코트라(KOTRA) 등 유관기관과 협력 지방공공기관 및 지역기업의 해외 진출도 지원한다. + - expected: `⠠⠦⠴⠠⠠⠑⠎⠛⠲⠈⠻⠻⠴⠄⠕⠀⠨⠕⠱⠁⠈⠕⠎⠃` + - actual: `⠠⠦⠴⠠⠠⠑⠎⠛⠲⠈⠻⠻⠴⠄⠕⠀⠨⠕⠱⠁⠈⠕⠎⠃` + - current primary/reason: `exact` / `exact` +- `sentence_04.json` #2: 주요 지수는 장중 2% 이상 하락했으나 장 막판 스위스중앙은행(SNB)이 나서 CS에 대한 지원 방침을 밝히면서 나스닥지수가 반등하는 등 한숨을 돌렸다. + - expected: `⠠⠎⠀⠴⠠⠠⠉⠎⠲⠝⠀⠊⠗⠚⠒⠀⠨⠕⠏⠒⠀⠘⠶⠰` + - actual: `⠠⠎⠀⠴⠠⠠⠉⠎⠲⠝⠀⠊⠗⠚⠒⠀⠨⠕⠏⠒⠀⠘⠶⠰` + - current primary/reason: `exact` / `exact` + +Representative `localized_mismatch` samples: + +- `sentence_01.json` #314: 이와 관련해서 FAA는 “FAA항공정보(NOTAMS) 업데이트에 관여하는 시스템이 작동하지 않고 있다”며 “복구작업을 벌이고 있지만 현재로서는 복구 시점을 예상하기 어렵다”고 말했다. + - expected: `⠀⠦⠴⠠⠠⠋⠁⠁⠲⠚⠶⠈⠿⠨⠻⠘⠥⠦⠄⠴⠠⠠⠝⠕` + - actual: `⠀⠦⠴⠠⠠⠋⠁⠁⠸⠷⠚⠶⠈⠿⠨⠻⠘⠥⠸⠾⠐⠣⠠⠠` + - first differing cell (zero-based): 30 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_02.json` #483: 충남교육청(교육감 김지철)은 7월 31일부터 오는 11일까지 도내 Wee센터·Wee클래스 전문상담교사, 전문상담사, 임상심리사 150명을 대상으로 ‘2023 하반기 위(Wee) 프로젝트 역량강화 직무연수’를 추진하고 있다고 밝혔다. + - expected: `⠉⠗⠀⠴⠠⠺⠑⠑⠲⠠⠝⠒⠓⠎⠐⠆⠴⠠⠺⠑⠑⠲⠋⠮` + - actual: `⠉⠗⠀⠴⠠⠺⠑⠑⠸⠷⠠⠝⠒⠓⠎⠸⠾⠲⠸⠂⠴⠠⠺⠑` + - first differing cell (zero-based): 70 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_03.json` #402: 블룸버그에 따르면, BAT와 자회사인 BAT마케팅싱가포르(BATMS)는 은행사기법과 국제비상경제권법(IEEPA)을 위반하며 싱가포르의 제3자 회사를 통해 북한에서 수년간 담배를 팔아온 것으로 알려졌다. + - expected: `⠟⠀⠴⠠⠠⠃⠁⠞⠲⠑⠋⠝⠓⠕⠶⠠⠕⠶⠫⠙⠥⠐⠪⠦` + - actual: `⠟⠀⠴⠠⠠⠃⠁⠞⠸⠷⠑⠋⠝⠓⠕⠶⠠⠕⠶⠫⠙⠥⠐⠪` + - first differing cell (zero-based): 40 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_04.json` #1660: 공공운수노조 더불어사는희망연대본부는 KT 인터넷이나 IPTV 개통과 수리를 담당하는 KT 그룹사 KT서비스(KTS) 북부·남부 소속 기사들로 구성된 노조다. + - expected: `⠃⠇⠀⠴⠠⠠⠅⠞⠲⠠⠎⠘⠕⠠⠪⠦⠄⠴⠠⠠⠅⠞⠎⠠` + - actual: `⠃⠇⠀⠴⠠⠠⠅⠞⠸⠷⠠⠎⠘⠕⠠⠪⠸⠾⠐⠣⠠⠠⠅⠞` + - first differing cell (zero-based): 100 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` + +Representative `mismatch` samples: + +- `sentence_01.json` #14: 새롭게 선보인 DRX 브랜딩과 팀 컬러는 2023년 1월 1일부터 DRX의 온오프라인 콘텐츠, 엠디(MD), SNS, 유튜브 등 모든 제작물에 적용될 예정이다. + - expected: `⠰⠪⠐⠀⠝⠢⠊⠕⠴⠐⠣⠠⠠⠍⠙⠐⠜⠂⠀⠠⠠⠎⠝⠎` + - actual: `⠰⠪⠐⠀⠝⠢⠊⠕⠦⠄⠴⠠⠠⠍⠙⠠⠴⠐⠀⠰⠠⠠⠎⠝` + - first differing cell (zero-based): 97 + - current primary/reason: `corpus_suspect` / `rule34_roman_indicator_before_opening_parenthesis` +- `sentence_02.json` #10: 설동호 대전교육감은 “학교 교육과정 및 여건에 맞는 인공지능(AI)교육 선도학교 운영을 통해 AI·SW교육 기반 구축과 학생들과 교원들의 미래 디지털 역량이 함양될 것이다.”라고 말했다. + - expected: `⠚⠗⠀⠴⠠⠠⠁⠊⠐⠆⠴⠠⠠⠎⠺⠲⠈⠬⠩⠁⠀⠈⠕⠘` + - actual: `⠚⠗⠀⠴⠠⠠⠁⠊⠲⠐⠆⠴⠎⠺⠲⠈⠬⠩⠁⠀⠈⠕⠘⠒` + - first differing cell (zero-based): 93 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_03.json` #6: 지난해 9월 HMM과 파나시아가 선박용 탄소포집 시스템 공동연구에 대한 업무협약(MOU)을 체결한 뒤 7개월 만에 내놓은 연구성과다. + - expected: `⠜⠁⠦⠄⠴⠠⠠⠍⠕⠥⠠⠴⠮⠀⠰⠝⠈⠳⠚⠒⠀⠊⠍⠗` + - actual: `⠜⠁⠦⠄⠴⠠⠠⠍⠳⠠⠴⠮⠀⠰⠝⠈⠳⠚⠒⠀⠊⠍⠗⠀` + - first differing cell (zero-based): 83 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_04.json` #19: 이날 행사에는 구 대표 외에 권봉석 (주)LG 최고운영책임자(COO), 박일평 LG사이언스파크대표(사장)를 비롯해 각 계열사 최고기술책임자(CTO), 최고데이터책임자(CDO), 최고인사책임자(CHO) 등이 참석해 국내 이공계 R&D 인재 400여명과 만났다. + - expected: `⠁⠕⠢⠨⠦⠄⠴⠠⠠⠉⠕⠕⠠⠴⠐⠀⠘⠁⠕⠂⠙⠻⠀⠴` + - actual: `⠁⠕⠢⠨⠦⠄⠴⠠⠉⠠⠕⠠⠕⠠⠴⠐⠀⠘⠁⠕⠂⠙⠻⠀` + - first differing cell (zero-based): 60 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` + ### `closed_roman_parenthetical_after_non_ascii_letter_boundary` Of the 63959 candidates, 10674 are the actual `pending_rule_review` subcluster. The other 53285 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. @@ -2706,6 +2808,8 @@ Current all-caps Roman middle-dot measurement: 97 candidates, 0 exact controls, Current Roman-before-middle-dot boundary measurement: 577 candidates, 0 exact controls, 577 mismatches, 565 members in the actual `pending_rule_review` subcluster, and 417/577 evaluable mismatches whose first difference is localized to the one current terminator immediately before the middle dot. The locator encodes each real input prefix ending at the dot, so identifier state such as `K-ICS·...` is retained without consulting expected. This raises localized target coverage from 342 to 417 and reduces the then-leading residual `⠐ -> ⠲` transition from 297 to 223. Rules 29, 33, and 50 support the current terminator path but do not support the localized reference omission; no engine change or primary-class rewrite is inferred. +Current attached Roman-to-Korean boundary measurement: 17693 candidates, 12724 exact controls, 4969 mismatches, 4141 members in the actual `pending_rule_review` subcluster, and 268/4969 evaluable mismatches whose first difference is localized to the current boundary marker. The localized target `⠲ -> ⠸` occurs 268 times and the reverse `⠸ -> ⠲` occurs 0 times. Korean rule 29 (2024 Korean-rules PDF p.26, printed p.20) requires a Roman terminator around Roman text in a Korean sentence, whereas rule 39 (PDF pp.31-32, printed pp.25-26) applies the Korean opening/closing markers only when Roman text is the sentence's main language. Among exact candidates, 10780 expose the current rule-29 terminator at such a boundary and 0 expose the current rule-39 opening. Its printed controls include both an English sentence (`What is 김치 in English?`) and the address `www.대통령.kr` inside a Korean sentence. Official UEB 2.4.7 (2024 UEB PDF p.41, printed p.13) confirms that a UEB mode does not extend through a switch to another braille code, but does not choose whether this Korean boundary belongs to a Korean-main or Roman-main context. Therefore the attached script boundary alone cannot distinguish ordinary Korean prose from an embedded Roman-domain context. Primary classes are preserved and no engine change is inferred without a narrower input-derived dominance gate. + Current inline parenthesized-operator measurement: 23 candidates, 17 exact controls, 6 mismatches, 6 members in the actual `pending_rule_review` subcluster, and 2/6 evaluable mismatches whose first differing cell is inside the emitted structure. Primary classes are preserved. At that implementation checkpoint, the strict cohort moved from 0 to 17 exact cases; the corpus-wide total moved from 65,491 to 65,514 (+23 exact) because the same PDF-backed spacing rule also applied outside the stricter Korean-boundary audit gate. These are immutable checkpoint counts rather than the report's later cumulative total. The complete standard suite remained 5,141/5,141. diff --git a/libs/braillify/examples/nikl_corpus_analyze.rs b/libs/braillify/examples/nikl_corpus_analyze.rs index 20e4fc49..0911edca 100644 --- a/libs/braillify/examples/nikl_corpus_analyze.rs +++ b/libs/braillify/examples/nikl_corpus_analyze.rs @@ -159,6 +159,7 @@ struct PendingRuleReviewClusterStats { output_signature_mismatches_evaluated: usize, first_difference_in_output_signature: usize, first_difference_in_output_signature_transitions: BTreeMap, + actual_output_signature_outcomes: BTreeMap, mismatch_primary_classes: BTreeMap, samples: BTreeMap>, } @@ -577,6 +578,8 @@ const ALLCAPS_ROMAN_MIDDLE_DOT_RUNS: &str = "multi_character_allcaps_roman_runs_joined_by_middle_dot"; const ROMAN_RUN_BEFORE_MIDDLE_DOT_BOUNDARY: &str = "roman_run_immediately_before_attached_middle_dot_boundary"; +const ATTACHED_ASCII_ROMAN_TO_KOREAN_BOUNDARY: &str = + "attached_ascii_roman_to_korean_script_boundary"; const KOREAN_INLINE_PARENTHESIZED_OPERATOR: &str = "korean_inline_parenthesized_single_arithmetic_operator"; const TIGHT_TRIANGLE_BEFORE_KOREAN: &str = "tight_triangle_mark_immediately_before_korean"; @@ -2064,10 +2067,15 @@ fn first_difference_claimed_before_allcaps_ed(item: &EncodedCase) -> bool { || first_difference_in_roman_after_closed_enclosure(item) } -fn first_difference_claimed_by_localized_cohort(item: &EncodedCase) -> bool { +fn first_difference_claimed_before_attached_ascii_roman_to_korean(item: &EncodedCase) -> bool { first_difference_claimed_before_allcaps_ed(item) || first_difference_in_allcaps_ed_run(item) } +fn first_difference_claimed_by_localized_cohort(item: &EncodedCase) -> bool { + first_difference_claimed_before_attached_ascii_roman_to_korean(item) + || first_difference_at_attached_ascii_roman_to_korean_boundary(item) +} + /// Input-only candidate gate for acronym expansions such as /// `HCA(Home Connectivity Alliance)`. /// @@ -2328,6 +2336,152 @@ fn first_difference_at_roman_middle_dot_boundary(item: &EncodedCase) -> bool { .any(|range| range.contains(&first_difference)) } +/// Finds a maximal ASCII-letter run followed immediately by a Korean script +/// character. This is deliberately only a script boundary: it does not infer +/// whether the surrounding sentence is Korean- or Roman-dominant. +fn attached_ascii_roman_to_korean_spans(input: &str) -> Vec { + let bytes = input.as_bytes(); + input + .char_indices() + .filter_map(|(korean_byte, korean)| { + if !is_korean_script(korean) || korean_byte == 0 { + return None; + } + let mut roman_start = korean_byte; + while roman_start > 0 && bytes[roman_start - 1].is_ascii_alphabetic() { + roman_start -= 1; + } + (roman_start < korean_byte).then_some(InputSpan { + start_byte: roman_start, + end_byte: korean_byte + korean.len_utf8(), + }) + }) + .collect() +} + +/// Locates only the current mode marker at an attached Roman-to-Korean +/// boundary. Encoding the real prefix establishes the cells before the +/// boundary without consulting expected. The current full output can either +/// retain rule 29's final Roman terminator or replace it with rule 39's +/// two-cell Korean opening marker. +fn attached_ascii_roman_to_korean_actual_ranges( + input: &str, + actual: &str, +) -> Vec> { + let actual_cells = actual.chars().collect::>(); + attached_ascii_roman_to_korean_spans(input) + .into_iter() + .filter_map(|span| { + let korean_byte = input[span.start_byte..span.end_byte] + .char_indices() + .find_map(|(offset, ch)| { + is_korean_script(ch).then_some(span.start_byte + offset) + })?; + let prefix = braillify::encode_to_unicode(&input[..korean_byte]).ok()?; + let prefix_cells = prefix.chars().collect::>(); + let terminator = prefix_cells.len().checked_sub(1)?; + if prefix_cells.get(terminator) != Some(&'⠲') { + return None; + } + if actual_cells.get(..prefix_cells.len()) == Some(prefix_cells.as_slice()) { + return Some((terminator, prefix_cells.len())); + } + if actual_cells.get(..terminator) == Some(&prefix_cells[..terminator]) + && actual_cells.get(terminator..terminator + 2) == Some(&['⠸', '⠷']) + { + return Some((terminator, terminator + 2)); + } + None + }) + .collect::>() + .into_iter() + .map(|(start, end)| start..end) + .collect() +} + +fn first_difference_at_attached_ascii_roman_to_korean_boundary(item: &EncodedCase) -> bool { + let Ok(actual) = &item.actual else { + return false; + }; + if actual == &item.located.case.unicode { + return false; + } + let first_difference = first_difference_cell(&item.located.case.unicode, actual); + attached_ascii_roman_to_korean_actual_ranges(&item.located.case.input, actual) + .into_iter() + .any(|range| range.contains(&first_difference)) +} + +fn record_attached_ascii_roman_to_korean_marker_outcomes( + stats: &mut PendingRuleReviewClusterStats, + item: &EncodedCase, + primary_key: &str, + reason_key: &str, + sample_limit: usize, +) { + let Ok(actual) = &item.actual else { + return; + }; + let ranges = attached_ascii_roman_to_korean_actual_ranges(&item.located.case.input, actual); + let actual_cells = actual.chars().collect::>(); + let outcome = if primary_key == "exact" { + "exact" + } else { + "mismatch" + }; + let mut seen = BTreeSet::new(); + for range in ranges { + let Some(first) = actual_cells.get(range.start) else { + continue; + }; + let marker = match first { + '⠲' => "rule29_terminator", + '⠸' => "rule39_hangul_opening", + _ => continue, + }; + if !seen.insert(marker) { + continue; + } + *stats + .actual_output_signature_outcomes + .entry(format!("{outcome}:{marker}")) + .or_insert(0) += 1; + + if outcome != "exact" { + continue; + } + let bucket = stats.samples.entry(format!("exact_{marker}")).or_default(); + if bucket.len() >= sample_limit + || bucket + .iter() + .any(|existing| existing.shard == item.located.shard) + { + continue; + } + let start = range.start.saturating_sub(8); + let expected_excerpt = item + .located + .case + .unicode + .chars() + .skip(start) + .take(24) + .collect(); + let actual_excerpt = actual.chars().skip(start).take(24).collect(); + bucket.push(PendingRuleReviewClusterSample { + shard: item.located.shard.clone(), + index: item.located.index, + input: item.located.case.input.clone(), + expected_excerpt, + actual_excerpt, + first_difference_cell: None, + error: None, + primary_class: primary_key.to_string(), + reason: reason_key.to_string(), + }); + } +} + #[derive(Clone, Copy, Debug, PartialEq, Eq)] struct InlineParenthesizedOperator { open_byte: usize, @@ -2727,6 +2881,10 @@ fn analyze( ROMAN_RUN_BEFORE_MIDDLE_DOT_BOUNDARY.to_string(), PendingRuleReviewClusterStats::default(), ), + ( + ATTACHED_ASCII_ROMAN_TO_KOREAN_BOUNDARY.to_string(), + PendingRuleReviewClusterStats::default(), + ), ( KOREAN_PREFIXED_ALLCAPS_PARENTHETICAL.to_string(), PendingRuleReviewClusterStats::default(), @@ -2947,6 +3105,15 @@ fn analyze( Some(first_difference_at_roman_middle_dot_boundary(item)), true, ), + ( + ATTACHED_ASCII_ROMAN_TO_KOREAN_BOUNDARY, + !attached_ascii_roman_to_korean_spans(&item.located.case.input).is_empty(), + Some( + !first_difference_claimed_before_attached_ascii_roman_to_korean(item) + && first_difference_at_attached_ascii_roman_to_korean_boundary(item), + ), + true, + ), ( KOREAN_PREFIXED_ALLCAPS_PARENTHETICAL, has_korean_prefixed_allcaps_parenthetical(&item.located.case.input), @@ -3152,6 +3319,15 @@ fn analyze( localized_first_difference, localized_samples, ); + if cluster == ATTACHED_ASCII_ROMAN_TO_KOREAN_BOUNDARY { + record_attached_ascii_roman_to_korean_marker_outcomes( + stats, + item, + &primary_key, + &reason_key, + sample_limit, + ); + } } let mut suffix_spans = BTreeMap::>::new(); @@ -3544,7 +3720,10 @@ fn markdown(report: &AnalysisReport) -> String { Korean character or ASCII-letter run after it, then searches for that whole \ current-engine signature in the actual output. It therefore isolates the Roman \ terminator boundary without treating unrelated middle dots elsewhere in the sentence \ - as causal. The \ + as causal. The `attached_ascii_roman_to_korean_script_boundary` gate requires an ASCII \ + letter run immediately followed by Korean script and localizes only the current mode \ + marker there: either rule 29's final `⠲` or rule 39's opening `⠸⠷`. It deliberately \ + does not infer the dominant language of the sentence from that surface boundary. The \ `korean_inline_parenthesized_single_arithmetic_operator` gate requires an immediate \ `Korean(` + one rule-45 arithmetic operator + `)Korean` span. Unlike broad coexistence \ traits, it also locates the current engine's emitted structure and counts a mismatch as \ @@ -4862,6 +5041,61 @@ fn markdown(report: &AnalysisReport) -> String { stats.output_signature_mismatches_evaluated )); } + if let Some(stats) = report + .pending_rule_review_clusters + .get(ATTACHED_ASCII_ROMAN_TO_KOREAN_BOUNDARY) + { + let pending = stats + .mismatch_primary_classes + .get("pending_rule_review") + .copied() + .unwrap_or(0); + let target = stats + .first_difference_in_output_signature_transitions + .get("U+2832 ⠲ -> U+2838 ⠸") + .copied() + .unwrap_or(0); + let reverse = stats + .first_difference_in_output_signature_transitions + .get("U+2838 ⠸ -> U+2832 ⠲") + .copied() + .unwrap_or(0); + let exact_terminator = stats + .actual_output_signature_outcomes + .get("exact:rule29_terminator") + .copied() + .unwrap_or(0); + let exact_hangul_opening = stats + .actual_output_signature_outcomes + .get("exact:rule39_hangul_opening") + .copied() + .unwrap_or(0); + text.push_str(&format!( + "\nCurrent attached Roman-to-Korean boundary measurement: {} candidates, {} exact \ + controls, {} mismatches, {pending} members in the actual `pending_rule_review` \ + subcluster, and {}/{} evaluable mismatches whose first difference is localized to \ + the current boundary marker. The localized target `⠲ -> ⠸` occurs {target} times \ + and the reverse `⠸ -> ⠲` occurs {reverse} times. Korean rule 29 (2024 Korean-rules \ + PDF p.26, printed p.20) requires a Roman terminator around Roman text in a Korean \ + sentence, whereas rule 39 (PDF pp.31-32, printed pp.25-26) applies the Korean \ + opening/closing markers only when Roman text is the sentence's main \ + language. Among exact candidates, {exact_terminator} expose the current rule-29 \ + terminator at such a boundary and {exact_hangul_opening} expose the current rule-39 \ + opening. Its printed controls include both an English sentence (`What is 김치 in \ + English?`) and the address `www.대통령.kr` inside a Korean sentence. Official UEB \ + 2.4.7 (2024 UEB PDF p.41, printed p.13) confirms that a UEB mode does not extend \ + through a switch to another braille code, but does not choose whether this Korean \ + boundary belongs to a Korean-main or Roman-main context. Therefore the \ + attached script boundary alone cannot distinguish ordinary Korean prose from an \ + embedded Roman-domain context. Primary classes are preserved and no engine change \ + is inferred without a narrower input-derived dominance gate.\n", + stats.candidates, + stats.exact, + stats.mismatch, + stats.first_difference_in_output_signature, + stats.output_signature_mismatches_evaluated + )); + } if let Some(stats) = report .pending_rule_review_clusters .get(KOREAN_INLINE_PARENTHESIZED_OPERATOR) @@ -6153,6 +6387,38 @@ mod tests { } } + #[rstest::rstest] + #[case::acronym_annotation("FAA항공정보(NOTAMS)", vec!["FAA항"])] + #[case::mixed_case_word("e스포츠", vec!["e스"])] + #[case::domain_control("www.대통령.kr", vec![])] + #[case::spaced_pdf_control("What is 김치 in English?", vec![])] + fn detects_only_attached_roman_to_korean_boundaries( + #[case] input: &str, + #[case] expected: Vec<&str>, + ) { + let actual = attached_ascii_roman_to_korean_spans(input) + .into_iter() + .map(|span| &input[span.start_byte..span.end_byte]) + .collect::>(); + assert_eq!(actual, expected); + } + + #[rstest::rstest] + #[case::current_rule39_opening("관련 FAA항공정보(NOTAMS)", '⠸', 2)] + #[case::current_rule29_terminator("종목 e스포츠", '⠲', 1)] + fn localizes_current_attached_roman_to_korean_marker( + #[case] input: &str, + #[case] first_marker: char, + #[case] marker_cells: usize, + ) { + let actual = braillify::encode_to_unicode(input).expect("boundary probe must encode"); + let ranges = attached_ascii_roman_to_korean_actual_ranges(input, &actual); + + assert_eq!(ranges.len(), 1); + assert_eq!(ranges[0].end - ranges[0].start, marker_cells); + assert_eq!(actual.chars().nth(ranges[0].start), Some(first_marker)); + } + #[rstest::rstest] #[case::plus("양(+)극", vec!['+'])] #[case::hyphen_minus("음(-)극", vec!['-'])] From 60c5bc023a6d269deae6829689e47f4c32700934 Mon Sep 17 00:00:00 2001 From: devfive Date: Tue, 1 Sep 2026 14:15:10 +0900 Subject: [PATCH 41/77] Limit Korean wrapping to Roman-main contexts --- docs/corpus-analysis/NIKL_2025_V1.md | 633 ++++++++++-------- .../braillify/examples/nikl_corpus_analyze.rs | 175 ++++- .../english_dominant_korean_wrap.rs | 64 +- 3 files changed, 560 insertions(+), 312 deletions(-) diff --git a/docs/corpus-analysis/NIKL_2025_V1.md b/docs/corpus-analysis/NIKL_2025_V1.md index cc9a9dc7..2489b61c 100644 --- a/docs/corpus-analysis/NIKL_2025_V1.md +++ b/docs/corpus-analysis/NIKL_2025_V1.md @@ -7,9 +7,9 @@ | Metric | Count | |---|---:| | Total | 83528 | -| Exact | 67715 | -| Mismatch | 15813 | -| Exact accuracy | 81.07% | +| Exact | 68101 | +| Mismatch | 15427 | +| Exact accuracy | 81.53% | | Duplicate records | 0 | | Inputs with conflicting references | 0 | @@ -20,19 +20,19 @@ Primary classes are evidence gates, not permissions to change the engine. `imple | Primary class | Count | |---|---:| | `comparison_method` | 293 | -| `corpus_suspect` | 1099 | -| `exact` | 67715 | -| `pending_rule_review` | 14243 | +| `corpus_suspect` | 1101 | +| `exact` | 68101 | +| `pending_rule_review` | 13855 | | `unsupported_character_review` | 178 | | Reproducible reason | Count | |---|---:| -| `exact` | 67715 | -| `foreign_text_rule_review` | 14068 | +| `exact` | 68101 | +| `foreign_text_rule_review` | 13680 | | `nfkc_input_equivalent` | 293 | | `number_rule_review` | 167 | | `punctuation_rule_review` | 8 | -| `rule34_roman_indicator_before_opening_parenthesis` | 1099 | +| `rule34_roman_indicator_before_opening_parenthesis` | 1101 | | `unsupported_character_review` | 178 | ## Pending first-difference cell transitions @@ -41,26 +41,26 @@ This ranking is a diagnostic selector, not an implementation rule. It counts onl | Rank | Expected → actual first cell | Cases | |---:|---|---:| -| 1 | `U+2834 ⠴ -> U+2800 ⠀` | 2152 | -| 2 | `U+2815 ⠕ -> U+2833 ⠳` | 1332 | -| 3 | `U+2830 ⠰ -> U+2820 ⠠` | 858 | -| 4 | `U+2810 ⠐ -> U+2832 ⠲` | 655 | -| 5 | `U+2826 ⠦ -> U+2834 ⠴` | 461 | +| 1 | `U+2834 ⠴ -> U+2800 ⠀` | 2158 | +| 2 | `U+2815 ⠕ -> U+2833 ⠳` | 1335 | +| 3 | `U+2830 ⠰ -> U+2820 ⠠` | 861 | +| 4 | `U+2810 ⠐ -> U+2832 ⠲` | 669 | +| 5 | `U+2826 ⠦ -> U+2834 ⠴` | 467 | | 6 | `U+280E ⠎ -> U+280C ⠌` | 448 | -| 7 | `U+2826 ⠦ -> U+2800 ⠀` | 434 | -| 8 | `U+2801 ⠁ -> U+281C ⠜` | 393 | -| 9 | `U+2800 ⠀ -> U+2807 ⠇` | 362 | +| 7 | `U+2826 ⠦ -> U+2800 ⠀` | 440 | +| 8 | `U+2801 ⠁ -> U+281C ⠜` | 405 | +| 9 | `U+2800 ⠀ -> U+2807 ⠇` | 363 | | 10 | `U+283C ⠼ -> U+2800 ⠀` | 358 | -| 11 | `U+2834 ⠴ -> U+2830 ⠰` | 336 | -| 12 | `U+2811 ⠑ -> U+282B ⠫` | 326 | -| 13 | `U+2832 ⠲ -> U+2838 ⠸` | 290 | -| 14 | `U+2800 ⠀ -> U+2832 ⠲` | 258 | -| 15 | `U+2820 ⠠ -> U+2830 ⠰` | 231 | -| 16 | `U+2810 ⠐ -> U+2802 ⠂` | 211 | +| 11 | `U+2834 ⠴ -> U+2830 ⠰` | 344 | +| 12 | `U+2811 ⠑ -> U+282B ⠫` | 327 | +| 13 | `U+2800 ⠀ -> U+2832 ⠲` | 259 | +| 14 | `U+2820 ⠠ -> U+2830 ⠰` | 236 | +| 15 | `U+2810 ⠐ -> U+2802 ⠂` | 212 | +| 16 | `U+2820 ⠠ -> U+2809 ⠉` | 169 | | 17 | `U+2817 ⠗ -> U+2820 ⠠` | 165 | | 18 | `U+2826 ⠦ -> U+2810 ⠐` | 164 | -| 19 | `U+2820 ⠠ -> U+2809 ⠉` | 162 | -| 20 | `U+281B ⠛ -> U+2823 ⠣` | 144 | +| 19 | `U+281B ⠛ -> U+2823 ⠣` | 144 | +| 20 | `U+2810 ⠐ -> U+2800 ⠀` | 130 | ### `U+2834 ⠴ -> U+2800 ⠀` @@ -227,7 +227,7 @@ This ranking is a diagnostic selector, not an implementation rule. It counts onl - `sentence_01.json` #142: ‘메타버스존’에서는 증강현실(AR)·가상현실(VR)에 적용되는 혁신기술이 소개된다. 글라스를 착용하고 최첨단 3D 센싱모듈이 구현하는 가상현실을 관람객이 직접 체험할 수 있도록 했다. - expected: `⠠⠕⠂⠦⠄⠴⠠⠠⠁⠗⠠⠴⠐⠆⠫⠇⠶⠚⠡⠠⠕⠂⠦⠄` - - actual: `⠠⠕⠂⠦⠄⠴⠠⠠⠜⠠⠴⠐⠆⠸⠷⠫⠇⠶⠚⠡⠠⠕⠂⠸` + - actual: `⠠⠕⠂⠦⠄⠴⠠⠠⠜⠠⠴⠐⠆⠫⠇⠶⠚⠡⠠⠕⠂⠦⠄⠴` - first differing cell (zero-based): 34 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` - `sentence_02.json` #28: 체납된 세금은 전국 어디서나 은행 현금자동인출기(ATM)를 이용해 고지서 없이 현금과 신용카드로 납부가 가능하며, 자동응답시스템(ARS) 지방세 납부서비스(044-300-7114)를 이용해 가상계좌를 확인해 계좌이체를 하거나 신용카드로 납부할 수도 있다. @@ -298,26 +298,26 @@ This ranking removes only cases whose first difference is inside an existing out | Rank | Expected → actual first cell | Residual cases | |---:|---|---:| -| 1 | `U+2834 ⠴ -> U+2800 ⠀` | 261 | -| 2 | `U+2800 ⠀ -> U+2832 ⠲` | 227 | -| 3 | `U+2810 ⠐ -> U+2832 ⠲` | 223 | -| 4 | `U+2826 ⠦ -> U+2800 ⠀` | 223 | -| 5 | `U+2810 ⠐ -> U+2802 ⠂` | 211 | -| 6 | `U+2817 ⠗ -> U+2820 ⠠` | 164 | -| 7 | `U+2820 ⠠ -> U+2809 ⠉` | 162 | +| 1 | `U+2834 ⠴ -> U+2800 ⠀` | 266 | +| 2 | `U+2800 ⠀ -> U+2832 ⠲` | 228 | +| 3 | `U+2826 ⠦ -> U+2800 ⠀` | 226 | +| 4 | `U+2810 ⠐ -> U+2832 ⠲` | 225 | +| 5 | `U+2810 ⠐ -> U+2802 ⠂` | 212 | +| 6 | `U+2820 ⠠ -> U+2809 ⠉` | 169 | +| 7 | `U+2817 ⠗ -> U+2820 ⠠` | 164 | | 8 | `U+283C ⠼ -> U+2800 ⠀` | 153 | | 9 | `U+2826 ⠦ -> U+2810 ⠐` | 147 | | 10 | `U+281B ⠛ -> U+2823 ⠣` | 144 | | 11 | `U+2810 ⠐ -> U+2800 ⠀` | 130 | -| 12 | `U+2811 ⠑ -> U+283B ⠻` | 101 | -| 13 | `U+2810 ⠐ -> U+2814 ⠔` | 98 | -| 14 | `U+2834 ⠴ -> U+2830 ⠰` | 98 | -| 15 | `U+280E ⠎ -> U+2820 ⠠` | 92 | -| 16 | `U+2803 ⠃ -> U+2820 ⠠` | 87 | -| 17 | `U+2804 ⠄ -> U+2834 ⠴` | 86 | -| 18 | `U+2824 ⠤ -> U+2800 ⠀` | 78 | -| 19 | `U+2826 ⠦ -> U+2834 ⠴` | 76 | -| 20 | `U+2815 ⠕ -> U+2800 ⠀` | 74 | +| 12 | `U+2820 ⠠ -> U+281E ⠞` | 108 | +| 13 | `U+2811 ⠑ -> U+283B ⠻` | 101 | +| 14 | `U+2820 ⠠ -> U+280E ⠎` | 100 | +| 15 | `U+2834 ⠴ -> U+2830 ⠰` | 99 | +| 16 | `U+2810 ⠐ -> U+2814 ⠔` | 98 | +| 17 | `U+280E ⠎ -> U+2820 ⠠` | 92 | +| 18 | `U+2803 ⠃ -> U+2820 ⠠` | 87 | +| 19 | `U+2804 ⠄ -> U+2834 ⠴` | 87 | +| 20 | `U+2826 ⠦ -> U+2834 ⠴` | 79 | ### Residual `U+2834 ⠴ -> U+2800 ⠀` @@ -365,29 +365,6 @@ This ranking removes only cases whose first difference is inside an existing out - first differing cell (zero-based): 40 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -### Residual `U+2810 ⠐ -> U+2832 ⠲` - -- `sentence_01.json` #521: 이 밖에 정 회장은 올해부터 시행하는 새 국제회계기준(IFRS17)·새 지급여력제도(K-ICS·킥스)의 연착륙을 지원하며서 ‘신제도 안정화 협의체’와 같은 소통 채널을 운영하기로 했다. - - expected: `⠠⠅⠤⠠⠠⠊⠉⠎⠐⠆⠋⠕⠁⠠⠪⠠⠴⠺⠀⠡⠰⠣⠁⠐` - - actual: `⠠⠅⠤⠠⠠⠊⠉⠎⠲⠐⠆⠋⠕⠁⠠⠪⠠⠴⠺⠀⠡⠰⠣⠁` - - first differing cell (zero-based): 93 - - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_02.json` #327: 또한, ‘도가(Dog+Yoga)’ 등 반려동물과 함께 즐길 수 있는 다양한 프로그램이 준비돼 있으며, 참가자 전원에게는 태안산 황토 호박고구마(3kg) 및 요가매트가 제공된다. - - expected: `⠫⠦⠄⠴⠠⠙⠕⠛⠐⠖⠠⠽⠕⠛⠁⠠⠴⠴⠄⠀⠊⠪⠶⠀` - - actual: `⠫⠦⠄⠴⠠⠙⠕⠛⠲⠢⠴⠠⠽⠕⠛⠁⠠⠴⠴⠄⠀⠊⠪⠶` - - first differing cell (zero-based): 19 - - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_03.json` #17: 재테크 시장에서 온·오프라인 플랫폼 ‘매경 엠플러스(M+)’가 주목받고 있다. M+는 거시경제, 주식, 부동산, 가상자산 등 재테크의 모든 것을 온라인 사이트를 통해 제공하고 있다. - - expected: `⠎⠠⠪⠦⠄⠴⠠⠍⠐⠖⠠⠴⠴⠄⠫⠀⠨⠍⠑⠭⠘⠔⠈⠥` - - actual: `⠎⠠⠪⠦⠄⠴⠠⠍⠲⠀⠢⠀⠠⠴⠴⠄⠫⠀⠨⠍⠑⠭⠘⠔` - - first differing cell (zero-based): 54 - - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_04.json` #1069: HMM·삼성중공업·파나시아·한국선급 등 4개사는 지난 27일 선박 이산화탄소 포집·액화 저장 기술(OCCS) 통합 실증 연구를 위한 업무협약(MOU)을 체결했다고 28일 밝혔다. - - expected: `⠴⠠⠠⠓⠍⠍⠐⠆⠇⠢⠠⠻⠨⠍⠶⠈⠿⠎⠃⠐⠆⠙⠉⠠` - - actual: `⠴⠠⠠⠓⠍⠍⠲⠐⠆⠇⠢⠠⠻⠨⠍⠶⠈⠿⠎⠃⠐⠆⠙⠉` - - first differing cell (zero-based): 6 - - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` - ### Residual `U+2826 ⠦ -> U+2800 ⠀` - `sentence_01.json` #234: 안랩은 블록체인 자회사 안랩블록체인컴퍼니가 웹(Web)3 지갑인 ‘ABC 월렛(Wallet)’ 모바일 버전을 구글플레이와 애플 앱스토어에 출시했다고 10일 발표했다. @@ -411,12 +388,35 @@ This ranking removes only cases whose first difference is inside an existing out - first differing cell (zero-based): 93 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +### Residual `U+2810 ⠐ -> U+2832 ⠲` + +- `sentence_01.json` #521: 이 밖에 정 회장은 올해부터 시행하는 새 국제회계기준(IFRS17)·새 지급여력제도(K-ICS·킥스)의 연착륙을 지원하며서 ‘신제도 안정화 협의체’와 같은 소통 채널을 운영하기로 했다. + - expected: `⠠⠅⠤⠠⠠⠊⠉⠎⠐⠆⠋⠕⠁⠠⠪⠠⠴⠺⠀⠡⠰⠣⠁⠐` + - actual: `⠠⠅⠤⠠⠠⠊⠉⠎⠲⠐⠆⠋⠕⠁⠠⠪⠠⠴⠺⠀⠡⠰⠣⠁` + - first differing cell (zero-based): 93 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_02.json` #327: 또한, ‘도가(Dog+Yoga)’ 등 반려동물과 함께 즐길 수 있는 다양한 프로그램이 준비돼 있으며, 참가자 전원에게는 태안산 황토 호박고구마(3kg) 및 요가매트가 제공된다. + - expected: `⠫⠦⠄⠴⠠⠙⠕⠛⠐⠖⠠⠽⠕⠛⠁⠠⠴⠴⠄⠀⠊⠪⠶⠀` + - actual: `⠫⠦⠄⠴⠠⠙⠕⠛⠲⠢⠴⠠⠽⠕⠛⠁⠠⠴⠴⠄⠀⠊⠪⠶` + - first differing cell (zero-based): 19 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_03.json` #17: 재테크 시장에서 온·오프라인 플랫폼 ‘매경 엠플러스(M+)’가 주목받고 있다. M+는 거시경제, 주식, 부동산, 가상자산 등 재테크의 모든 것을 온라인 사이트를 통해 제공하고 있다. + - expected: `⠎⠠⠪⠦⠄⠴⠠⠍⠐⠖⠠⠴⠴⠄⠫⠀⠨⠍⠑⠭⠘⠔⠈⠥` + - actual: `⠎⠠⠪⠦⠄⠴⠠⠍⠲⠀⠢⠀⠠⠴⠴⠄⠫⠀⠨⠍⠑⠭⠘⠔` + - first differing cell (zero-based): 54 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_04.json` #1069: HMM·삼성중공업·파나시아·한국선급 등 4개사는 지난 27일 선박 이산화탄소 포집·액화 저장 기술(OCCS) 통합 실증 연구를 위한 업무협약(MOU)을 체결했다고 28일 밝혔다. + - expected: `⠴⠠⠠⠓⠍⠍⠐⠆⠇⠢⠠⠻⠨⠍⠶⠈⠿⠎⠃⠐⠆⠙⠉⠠` + - actual: `⠴⠠⠠⠓⠍⠍⠲⠐⠆⠇⠢⠠⠻⠨⠍⠶⠈⠿⠎⠃⠐⠆⠙⠉` + - first differing cell (zero-based): 6 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` + ### Residual `U+2810 ⠐ -> U+2802 ⠂` -- `sentence_01.json` #1725: 호주는 7회초 경기를 뒤집었다. 한국 4번째 투수 소형준(KT)이 1사 2, 3루로 몰린 가운데 마운드를 김원중(롯데 자이언츠)으로 교체됐다. 김원중은 첫 타자인 홀을 스트라이크 낫아웃으로 돌려세웠다. - - expected: `⠕⠀⠼⠁⠇⠀⠼⠃⠐⠀⠼⠉⠐⠍⠐⠥⠀⠑⠥⠂⠐⠟⠀⠫` - - actual: `⠕⠀⠼⠁⠇⠀⠼⠃⠂⠀⠼⠉⠐⠍⠐⠥⠀⠑⠥⠂⠐⠟⠀⠫` - - first differing cell (zero-based): 75 +- `sentence_01.json` #498: 신제품은 15.6인치(15Z90RT) 울트라슬림과 14인치(14Z90RS)·16인치(16Z90RS) 그램 스타일 등으로 구성된다. 아울러 그램 17, 16, 15, 14 등도 선보일 예정이다. + - expected: `⠪⠐⠗⠢⠀⠼⠁⠛⠐⠀⠼⠁⠋⠐⠀⠼⠁⠑⠐⠀⠼⠁⠙⠀` + - actual: `⠪⠐⠗⠢⠀⠼⠁⠛⠂⠀⠼⠁⠋⠂⠀⠼⠁⠑⠂⠀⠼⠁⠙⠀` + - first differing cell (zero-based): 141 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` - `sentence_02.json` #239: 세계 금연의 날(World No Tabacco Day, 2023년 5월 31일)은 세계보건기구(WHO)가 담배가 전 세계적으로 심각한 문제임을 인식시키고 ‘담배 연기 없는 사회’를 만들기 위하여 1987년 제정한 기념일이다. - expected: `⠃⠁⠒⠕⠀⠠⠐⠙⠐⠀⠼⠃⠚⠃⠉⠀⠉⠡⠀⠼⠑⠏⠂⠀` @@ -434,29 +434,6 @@ This ranking removes only cases whose first difference is inside an existing out - first differing cell (zero-based): 90 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -### Residual `U+2817 ⠗ -> U+2820 ⠠` - -- `sentence_01.json` #1294: 한편 엔씨소프트는 오픈형 R&D 개발 문화 ‘엔씽(NCing)’의 일환으로 개발 중인 다양한 장르의 신규 프로젝트를 공개하고 있다. - - expected: `⠥⠙⠵⠚⠻⠀⠴⠠⠗⠈⠯⠠⠙⠲⠀⠈⠗⠘⠂⠀⠑⠛⠚⠧` - - actual: `⠥⠙⠵⠚⠻⠀⠴⠠⠠⠗⠈⠯⠙⠲⠀⠈⠗⠘⠂⠀⠑⠛⠚⠧` - - first differing cell (zero-based): 27 - - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_02.json` #226: 대한민국 드론·UAM 박람회는 UAM(도심항공교통) 등 차세대 국토교통 R&D와 주요 지자체의 관련 사업을 살펴볼 수 있는 의미 있는 행사로 국토교통부와 인천광역시가 공동 주최한다. - - expected: `⠥⠈⠬⠓⠿⠀⠴⠠⠗⠈⠯⠠⠙⠲⠧⠀⠨⠍⠬⠀⠨⠕⠨⠰` - - actual: `⠥⠈⠬⠓⠿⠀⠴⠠⠠⠗⠈⠯⠙⠲⠧⠀⠨⠍⠬⠀⠨⠕⠨⠰` - - first differing cell (zero-based): 81 - - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_03.json` #647: 온유는 지난 3월 6일 첫 정규 앨범 ‘써클(Circle)’로 컴백한다. 이번 앨범은 몽환적인 R&B 장르의 타이틀곡 ‘O(Circle)’(써클)을 비롯한 다채로운 분위기의 10곡으로 구성됐다. - - expected: `⠧⠒⠨⠹⠟⠀⠴⠠⠗⠈⠯⠠⠃⠲⠀⠨⠶⠐⠪⠺⠀⠓⠣⠕` - - actual: `⠧⠒⠨⠹⠟⠀⠴⠠⠠⠗⠈⠯⠃⠲⠀⠨⠶⠐⠪⠺⠀⠓⠣⠕` - - first differing cell (zero-based): 92 - - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_04.json` #2491: 앞서 정보통신기술(ICT) 분야를 담당하는 박윤규 과기정통부 2차관도 지난 1일 산하 정보통신정책실·네트워크정책실 소속 실·국장을 대상으로 간부회의를 소집해 ICT 분야 R&D 예산으로 잡힌 사업들을 원점에서 재검토하도록 했다. - - expected: `⠲⠀⠘⠛⠜⠀⠴⠠⠗⠈⠯⠠⠙⠲⠀⠌⠇⠒⠪⠐⠥⠀⠨⠃` - - actual: `⠲⠀⠘⠛⠜⠀⠴⠠⠠⠗⠈⠯⠙⠲⠀⠌⠇⠒⠪⠐⠥⠀⠨⠃` - - first differing cell (zero-based): 175 - - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` - ### Residual `U+2820 ⠠ -> U+2809 ⠉` - `sentence_01.json` #45: 2019년 드링크인터내셔널 합류 이후 최고 운영 책임자(COO)를 역임하며 자회사 인터리커를 통해 와인 시장 진출 등 사업 확장을 진두지휘했다. @@ -480,6 +457,29 @@ This ranking removes only cases whose first difference is inside an existing out - first differing cell (zero-based): 60 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +### Residual `U+2817 ⠗ -> U+2820 ⠠` + +- `sentence_01.json` #1294: 한편 엔씨소프트는 오픈형 R&D 개발 문화 ‘엔씽(NCing)’의 일환으로 개발 중인 다양한 장르의 신규 프로젝트를 공개하고 있다. + - expected: `⠥⠙⠵⠚⠻⠀⠴⠠⠗⠈⠯⠠⠙⠲⠀⠈⠗⠘⠂⠀⠑⠛⠚⠧` + - actual: `⠥⠙⠵⠚⠻⠀⠴⠠⠠⠗⠈⠯⠙⠲⠀⠈⠗⠘⠂⠀⠑⠛⠚⠧` + - first differing cell (zero-based): 27 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_02.json` #226: 대한민국 드론·UAM 박람회는 UAM(도심항공교통) 등 차세대 국토교통 R&D와 주요 지자체의 관련 사업을 살펴볼 수 있는 의미 있는 행사로 국토교통부와 인천광역시가 공동 주최한다. + - expected: `⠥⠈⠬⠓⠿⠀⠴⠠⠗⠈⠯⠠⠙⠲⠧⠀⠨⠍⠬⠀⠨⠕⠨⠰` + - actual: `⠥⠈⠬⠓⠿⠀⠴⠠⠠⠗⠈⠯⠙⠲⠧⠀⠨⠍⠬⠀⠨⠕⠨⠰` + - first differing cell (zero-based): 81 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_03.json` #647: 온유는 지난 3월 6일 첫 정규 앨범 ‘써클(Circle)’로 컴백한다. 이번 앨범은 몽환적인 R&B 장르의 타이틀곡 ‘O(Circle)’(써클)을 비롯한 다채로운 분위기의 10곡으로 구성됐다. + - expected: `⠧⠒⠨⠹⠟⠀⠴⠠⠗⠈⠯⠠⠃⠲⠀⠨⠶⠐⠪⠺⠀⠓⠣⠕` + - actual: `⠧⠒⠨⠹⠟⠀⠴⠠⠠⠗⠈⠯⠃⠲⠀⠨⠶⠐⠪⠺⠀⠓⠣⠕` + - first differing cell (zero-based): 92 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_04.json` #2491: 앞서 정보통신기술(ICT) 분야를 담당하는 박윤규 과기정통부 2차관도 지난 1일 산하 정보통신정책실·네트워크정책실 소속 실·국장을 대상으로 간부회의를 소집해 ICT 분야 R&D 예산으로 잡힌 사업들을 원점에서 재검토하도록 했다. + - expected: `⠲⠀⠘⠛⠜⠀⠴⠠⠗⠈⠯⠠⠙⠲⠀⠌⠇⠒⠪⠐⠥⠀⠨⠃` + - actual: `⠲⠀⠘⠛⠜⠀⠴⠠⠠⠗⠈⠯⠙⠲⠀⠌⠇⠒⠪⠐⠥⠀⠨⠃` + - first differing cell (zero-based): 175 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` + ### Residual `U+283C ⠼ -> U+2800 ⠀` - `sentence_01.json` #633: 미국 항공우주청(NASA)은 26일(현지 시간) 이 같은 내용을 발표하면서 “‘2023 BU’라는 소행성이 남미의 남단을 지나갈 것”이라며 “2023 BU는 지름이 약 3.5~8.5m 크기”라고 설명했다. @@ -551,57 +551,58 @@ This ranking removes only cases whose first difference is inside an existing out ## Cross-cutting input-only structural cohorts -These are cross-cutting input-only structural cohorts, not new primary classes and not engine routing rules. Candidate selection never changes a case's existing primary class by itself. Only cohort members already classified as `pending_rule_review` form a pending subcluster; exact and other-primary members are controls that retain their existing outcomes. A separate classifier may use independently justified, output-localized PDF evidence, as in the rule-34 three-cell contradiction below. The `uppercase_roman_headword_closed_multiword_parenthetical` gate requires a two-or-more character uppercase ASCII headword immediately followed by a closed parenthesis whose contents are two or more ASCII Roman words separated only by spaces. Because the contents admit only letters and spaces, visible operators, subscript/superscript notation, and nested parentheses are excluded deterministically. The `mixed_roman_korean_word_before_uppercase_headword_expansion` gate further requires a whitespace-delimited preceding word that contains both ASCII Roman and Korean and ends in Korean. It locates the following uppercase headword itself, including its immediately preceding emitted cell, so an earlier Roman entry in the same sentence cannot satisfy the output audit. The `single_capital_followed_by_parenthesized_digits` gate requires a standalone capital, a non-empty closed ASCII-digit parenthetical, and alphanumeric outer boundaries. It likewise includes the emitted entry-boundary cell in localization. The `uppercase_roman_run_followed_by_hyphen_digits` gate requires a maximal uppercase ASCII run, literal ASCII hyphen-minus, a non-empty digit run, and alphanumeric outer boundaries. Its localized range includes the current encoded run and its immediately preceding cell, keeping `F-35`-style routing separate from both parenthesized digits and headword expansions. The `standalone_multi_character_uppercase_roman_word` gate finds maximal ASCII-letter runs of two or more capitals with non-alphanumeric boundaries; a run immediately followed by `(` is excluded so the HCA-style headword itself is not counted by both gates. The `korean_prefixed_closed_allcaps_parenthetical` gate requires an immediately preceding Korean character and a closed body of two or more uppercase ASCII letters. It intentionally contains both acronym annotations (`책임자(COO)`) and scientific formulae (`일산화탄소(CO)`) so their semantic collision remains measurable. The `korean_prefixed_closed_roman_annotation_rule_34_order` gate accepts the narrower rule-34 body grammar after an immediately preceding Korean character and localizes only the current engine's Korean opening-parenthesis cells. This distinguishes the PDF's parenthesis-before-Roman-indicator order from unrelated differences later in the same sentence. The `multi_character_allcaps_roman_runs_joined_by_middle_dot` gate requires two maximal ASCII-letter runs of at least two capitals joined directly by U+00B7, with non-alphanumeric outer boundaries. It records shapes such as `AI·SW` without assigning prose, mathematics, or science semantics. The `roman_run_immediately_before_attached_middle_dot_boundary` gate is output-localized: it requires a maximal ASCII-letter run immediately before U+00B7 and an attached Korean character or ASCII-letter run after it, then searches for that whole current-engine signature in the actual output. It therefore isolates the Roman terminator boundary without treating unrelated middle dots elsewhere in the sentence as causal. The `attached_ascii_roman_to_korean_script_boundary` gate requires an ASCII letter run immediately followed by Korean script and localizes only the current mode marker there: either rule 29's final `⠲` or rule 39's opening `⠸⠷`. It deliberately does not infer the dominant language of the sentence from that surface boundary. The `korean_inline_parenthesized_single_arithmetic_operator` gate requires an immediate `Korean(` + one rule-45 arithmetic operator + `)Korean` span. Unlike broad coexistence traits, it also locates the current engine's emitted structure and counts a mismatch as signature-local only when the sentence's first differing cell falls inside that output range. The `allcaps_roman_run_containing_ou` gate finds maximal, alphanumeric-delimited uppercase ASCII runs containing adjacent `OU`. It locates the independently encoded run signature in the complete current output and counts only first differences inside that signature as localized. The `allcaps_roman_run_containing_st` gate applies the same output-position requirement to adjacent `ST`; it is kept separate because UEB 10.12 makes contraction use depend on how an abbreviation or acronym is pronounced. The `attached_ascii_roman_segments_joined_by_ampersand` gate requires non-empty ASCII-letter segments joined directly by `&`, excludes spaced/Korean/alphanumeric continuations, and localizes only the current output cell immediately before the ampersand through an independently encoded real-input prefix. The `decimal_point_between_ascii_digits` gate finds whitespace-delimited words containing `digit.digit` and reproduces each whole word in a neutral Korean context, so suffixes and punctuation remain part of the current-engine signature. The `compact_numeric_ascii_letter_suffix` gate finds a numeric prefix immediately followed by ASCII letters, includes the immediately preceding output cell as its entry boundary, and retains suffix-specific outcome counts. It intentionally includes both possible rule-69 units and ambiguous variable/identifier forms; membership alone does not assign unit semantics. The `rule69_ascii_unit_before_terminator_skipping_symbol` gate is narrower: it accepts only ASCII unit spellings already supported by rule 69, includes the immediately following rule-33/34 punctuation cell in the localized signature, and does not infer new units. The `tight_triangle_mark_immediately_before_korean` gate requires literal `△한글` with no input space and includes the first following Korean cell in its localized output range, so an observed missing-space difference is measured at the mark boundary. +These are cross-cutting input-only structural cohorts, not new primary classes and not engine routing rules. Candidate selection never changes a case's existing primary class by itself. Only cohort members already classified as `pending_rule_review` form a pending subcluster; exact and other-primary members are controls that retain their existing outcomes. A separate classifier may use independently justified, output-localized PDF evidence, as in the rule-34 three-cell contradiction below. The `uppercase_roman_headword_closed_multiword_parenthetical` gate requires a two-or-more character uppercase ASCII headword immediately followed by a closed parenthesis whose contents are two or more ASCII Roman words separated only by spaces. Because the contents admit only letters and spaces, visible operators, subscript/superscript notation, and nested parentheses are excluded deterministically. The `mixed_roman_korean_word_before_uppercase_headword_expansion` gate further requires a whitespace-delimited preceding word that contains both ASCII Roman and Korean and ends in Korean. It locates the following uppercase headword itself, including its immediately preceding emitted cell, so an earlier Roman entry in the same sentence cannot satisfy the output audit. The `single_capital_followed_by_parenthesized_digits` gate requires a standalone capital, a non-empty closed ASCII-digit parenthetical, and alphanumeric outer boundaries. It likewise includes the emitted entry-boundary cell in localization. The `uppercase_roman_run_followed_by_hyphen_digits` gate requires a maximal uppercase ASCII run, literal ASCII hyphen-minus, a non-empty digit run, and alphanumeric outer boundaries. Its localized range includes the current encoded run and its immediately preceding cell, keeping `F-35`-style routing separate from both parenthesized digits and headword expansions. The `standalone_multi_character_uppercase_roman_word` gate finds maximal ASCII-letter runs of two or more capitals with non-alphanumeric boundaries; a run immediately followed by `(` is excluded so the HCA-style headword itself is not counted by both gates. The `korean_prefixed_closed_allcaps_parenthetical` gate requires an immediately preceding Korean character and a closed body of two or more uppercase ASCII letters. It intentionally contains both acronym annotations (`책임자(COO)`) and scientific formulae (`일산화탄소(CO)`) so their semantic collision remains measurable. The `korean_prefixed_closed_roman_annotation_rule_34_order` gate accepts the narrower rule-34 body grammar after an immediately preceding Korean character and localizes only the current engine's Korean opening-parenthesis cells. This distinguishes the PDF's parenthesis-before-Roman-indicator order from unrelated differences later in the same sentence. The `multi_character_allcaps_roman_runs_joined_by_middle_dot` gate requires two maximal ASCII-letter runs of at least two capitals joined directly by U+00B7, with non-alphanumeric outer boundaries. It records shapes such as `AI·SW` without assigning prose, mathematics, or science semantics. The `roman_run_immediately_before_attached_middle_dot_boundary` gate is output-localized: it requires a maximal ASCII-letter run immediately before U+00B7 and an attached Korean character or ASCII-letter run after it, then searches for that whole current-engine signature in the actual output. It therefore isolates the Roman terminator boundary without treating unrelated middle dots elsewhere in the sentence as causal. The `attached_ascii_roman_to_korean_script_boundary` gate requires an ASCII letter run immediately followed by Korean script and localizes only the current mode marker there: either rule 29's final `⠲` or rule 39's opening `⠸⠷`. It deliberately does not infer the dominant language of the sentence from that surface boundary. The `korean_majority_same_token_roman_sandwich_non_domain` scope audit mirrors the narrower rule-39 implementation gate: the nearest script characters on both sides of one Korean segment are ASCII Roman, the input is Korean-majority by first-script word counts, and the segment is not dot-delimited like the official domain example. It measures change scope but is not itself an output-localized causal classifier. The `korean_inline_parenthesized_single_arithmetic_operator` gate requires an immediate `Korean(` + one rule-45 arithmetic operator + `)Korean` span. Unlike broad coexistence traits, it also locates the current engine's emitted structure and counts a mismatch as signature-local only when the sentence's first differing cell falls inside that output range. The `allcaps_roman_run_containing_ou` gate finds maximal, alphanumeric-delimited uppercase ASCII runs containing adjacent `OU`. It locates the independently encoded run signature in the complete current output and counts only first differences inside that signature as localized. The `allcaps_roman_run_containing_st` gate applies the same output-position requirement to adjacent `ST`; it is kept separate because UEB 10.12 makes contraction use depend on how an abbreviation or acronym is pronounced. The `attached_ascii_roman_segments_joined_by_ampersand` gate requires non-empty ASCII-letter segments joined directly by `&`, excludes spaced/Korean/alphanumeric continuations, and localizes only the current output cell immediately before the ampersand through an independently encoded real-input prefix. The `decimal_point_between_ascii_digits` gate finds whitespace-delimited words containing `digit.digit` and reproduces each whole word in a neutral Korean context, so suffixes and punctuation remain part of the current-engine signature. The `compact_numeric_ascii_letter_suffix` gate finds a numeric prefix immediately followed by ASCII letters, includes the immediately preceding output cell as its entry boundary, and retains suffix-specific outcome counts. It intentionally includes both possible rule-69 units and ambiguous variable/identifier forms; membership alone does not assign unit semantics. The `rule69_ascii_unit_before_terminator_skipping_symbol` gate is narrower: it accepts only ASCII unit spellings already supported by rule 69, includes the immediately following rule-33/34 punctuation cell in the localized signature, and does not infer new units. The `tight_triangle_mark_immediately_before_korean` gate requires literal `△한글` with no input space and includes the first following Korean cell in its localized output range, so an observed missing-space difference is measured at the mark boundary. | Cluster | Candidates | Exact | Mismatch | Conflicting-reference cases | |---|---:|---:|---:|---:| -| `allcaps_roman_run_beginning_with_pure_letter_shortform` | 4120 | 2441 | 1679 | 0 | -| `allcaps_roman_run_containing_ar` | 1022 | 360 | 662 | 0 | -| `allcaps_roman_run_containing_ed` | 816 | 313 | 503 | 0 | +| `allcaps_roman_run_beginning_with_pure_letter_shortform` | 4120 | 2460 | 1660 | 0 | +| `allcaps_roman_run_containing_ar` | 1022 | 363 | 659 | 0 | +| `allcaps_roman_run_containing_ed` | 816 | 318 | 498 | 0 | | `allcaps_roman_run_containing_ou` | 1816 | 64 | 1752 | 0 | -| `allcaps_roman_run_containing_st` | 1479 | 678 | 801 | 0 | -| `attached_ascii_roman_segments_joined_by_ampersand` | 802 | 273 | 529 | 0 | -| `attached_ascii_roman_to_korean_script_boundary` | 17693 | 12724 | 4969 | 0 | -| `closed_roman_parenthetical_after_non_ascii_letter_boundary` | 63959 | 51806 | 12153 | 0 | -| `compact_numeric_ascii_letter_suffix` | 2975 | 1859 | 1116 | 0 | -| `decimal_point_between_ascii_digits` | 4546 | 3351 | 1195 | 0 | +| `allcaps_roman_run_containing_st` | 1479 | 680 | 799 | 0 | +| `attached_ascii_roman_segments_joined_by_ampersand` | 802 | 283 | 519 | 0 | +| `attached_ascii_roman_to_korean_script_boundary` | 17693 | 12924 | 4769 | 0 | +| `closed_roman_parenthetical_after_non_ascii_letter_boundary` | 63959 | 52169 | 11790 | 0 | +| `compact_numeric_ascii_letter_suffix` | 2975 | 1870 | 1105 | 0 | +| `decimal_point_between_ascii_digits` | 4546 | 3380 | 1166 | 0 | | `korean_inline_parenthesized_single_arithmetic_operator` | 23 | 17 | 6 | 0 | -| `korean_prefixed_closed_allcaps_parenthetical` | 54492 | 44713 | 9779 | 0 | -| `korean_prefixed_closed_roman_annotation_rule_34_order` | 64382 | 52408 | 11974 | 0 | +| `korean_majority_same_token_roman_sandwich_non_domain` | 947 | 385 | 562 | 0 | +| `korean_prefixed_closed_allcaps_parenthetical` | 54492 | 44983 | 9509 | 0 | +| `korean_prefixed_closed_roman_annotation_rule_34_order` | 64382 | 52774 | 11608 | 0 | | `korean_prefixed_roman_parenthetical_followed_by_allcaps_hyphen_suffix` | 11 | 0 | 11 | 0 | | `mixed_roman_korean_word_before_uppercase_headword_expansion` | 10 | 0 | 10 | 0 | | `multi_character_allcaps_roman_runs_joined_by_middle_dot` | 97 | 0 | 97 | 0 | -| `pure_allcaps_segment_before_hyphen_and_multi_allcaps_segment_after` | 448 | 127 | 321 | 0 | +| `pure_allcaps_segment_before_hyphen_and_multi_allcaps_segment_after` | 448 | 130 | 318 | 0 | | `roman_hyphenated_word_after_whitespace_following_korean_word` | 361 | 0 | 361 | 0 | -| `roman_parenthetical_headword_after_whitespace_following_korean_word` | 4695 | 3363 | 1332 | 0 | -| `roman_run_after_whitespace_following_closed_roman_enclosure` | 1093 | 138 | 955 | 0 | +| `roman_parenthetical_headword_after_whitespace_following_korean_word` | 4695 | 3380 | 1315 | 0 | +| `roman_run_after_whitespace_following_closed_roman_enclosure` | 1093 | 141 | 952 | 0 | | `roman_run_immediately_before_attached_middle_dot_boundary` | 577 | 0 | 577 | 0 | -| `rule69_ascii_unit_before_terminator_skipping_symbol` | 440 | 336 | 104 | 0 | +| `rule69_ascii_unit_before_terminator_skipping_symbol` | 440 | 340 | 100 | 0 | | `single_capital_followed_by_parenthesized_digits` | 1361 | 0 | 1361 | 0 | -| `standalone_multi_character_uppercase_roman_word` | 62411 | 50059 | 12352 | 0 | +| `standalone_multi_character_uppercase_roman_word` | 62411 | 50395 | 12016 | 0 | | `tight_triangle_mark_immediately_before_korean` | 377 | 0 | 377 | 0 | -| `uppercase_ascii_run_immediately_after_digit_in_roman_sequence` | 1896 | 1094 | 802 | 0 | -| `uppercase_ascii_run_immediately_after_hyphen_in_roman_sequence` | 952 | 160 | 792 | 0 | +| `uppercase_ascii_run_immediately_after_digit_in_roman_sequence` | 1896 | 1100 | 796 | 0 | +| `uppercase_ascii_run_immediately_after_hyphen_in_roman_sequence` | 952 | 163 | 789 | 0 | | `uppercase_roman_headword_closed_multiword_parenthetical` | 175 | 16 | 159 | 0 | -| `uppercase_roman_run_followed_by_hyphen_digits` | 571 | 190 | 381 | 0 | -| `uppercase_word_after_whitespace_continuing_ascii_roman_text` | 1729 | 664 | 1065 | 0 | +| `uppercase_roman_run_followed_by_hyphen_digits` | 571 | 191 | 380 | 0 | +| `uppercase_word_after_whitespace_continuing_ascii_roman_text` | 1729 | 678 | 1051 | 0 | ### `allcaps_roman_run_beginning_with_pure_letter_shortform` -Of the 4120 candidates, 1610 are the actual `pending_rule_review` subcluster. The other 2510 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 4120 candidates, 1591 are the actual `pending_rule_review` subcluster. The other 2529 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. -For this output-signature audit, 1679 mismatches were evaluable and 826 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. +For this output-signature audit, 1660 mismatches were evaluable and 832 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. Localized first-difference transitions: -- `U+2830 ⠰ -> U+2820 ⠠`: 783 -- `U+2820 ⠠ -> U+2830 ⠰`: 43 +- `U+2830 ⠰ -> U+2820 ⠠`: 785 +- `U+2820 ⠠ -> U+2830 ⠰`: 47 Mismatch primary-class distribution: - `comparison_method`: 11 - `corpus_suspect`: 48 -- `pending_rule_review`: 1610 +- `pending_rule_review`: 1591 - `unsupported_character_review`: 10 Representative `exact` samples: @@ -614,9 +615,9 @@ Representative `exact` samples: - expected: `⠴⠠⠠⠅⠙⠊⠲⠝⠀⠠⠊⠐⠪⠑⠡⠐⠀⠘⠕⠨⠝⠨⠥⠎` - actual: `⠴⠠⠠⠅⠙⠊⠲⠝⠀⠠⠊⠐⠪⠑⠡⠐⠀⠘⠕⠨⠝⠨⠥⠎` - current primary/reason: `exact` / `exact` -- `sentence_03.json` #56: 20일 한국투자신탁운용은 ‘ACE 미국30년국채액티브(H)’ ETF의 순자산액이 지난 19일 511억원을 기록했다고 밝혔다. 지난달 14일 해당 ETF가 상장한 이후 약 한 달 만이다. - - expected: `⠼⠃⠚⠕⠂⠀⠚⠒⠈⠍⠁⠓⠍⠨⠠⠟⠓⠁⠛⠬⠶⠵⠀⠠` - - actual: `⠼⠃⠚⠕⠂⠀⠚⠒⠈⠍⠁⠓⠍⠨⠠⠟⠓⠁⠛⠬⠶⠵⠀⠠` +- `sentence_03.json` #54: 위탁생산은 경기도 하남에 구축한 HK이노엔의 세포유전자치료제 센터에서 진행한다. HK이노엔은 자체 연구뿐 아니라 위탁생산(CMO)·위탁개발생산(CDMO) 등을 통해 세포유전자치료제센터를 혁신 플랫폼으로 키울 방침이다. + - expected: `⠍⠗⠓⠁⠠⠗⠶⠇⠒⠵⠀⠈⠻⠈⠕⠊⠥⠀⠚⠉⠢⠝⠀⠈` + - actual: `⠍⠗⠓⠁⠠⠗⠶⠇⠒⠵⠀⠈⠻⠈⠕⠊⠥⠀⠚⠉⠢⠝⠀⠈` - current primary/reason: `exact` / `exact` - `sentence_04.json` #30: 뱅크오브아메리카(BOA), 시티그룹, JP모건체이스, 웰스파고가 각각 50억달러를 예치하고, 골드만삭스와 모건스탠리가 각각 25억달러를 넣기로 했다. BNY멜론, PNC뱅크, 스테이트스트리트, 트루이스트, US뱅크는 각각 10억달러를 예치한다. - expected: `⠘⠗⠶⠋⠪⠥⠘⠪⠣⠑⠝⠐⠕⠋⠦⠄⠴⠠⠠⠃⠕⠁⠠⠴` @@ -671,13 +672,13 @@ Representative `mismatch` samples: ### `allcaps_roman_run_containing_ar` -Of the 1022 candidates, 639 are the actual `pending_rule_review` subcluster. The other 383 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 1022 candidates, 636 are the actual `pending_rule_review` subcluster. The other 386 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. -For this output-signature audit, 662 mismatches were evaluable and 392 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. +For this output-signature audit, 659 mismatches were evaluable and 404 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. Localized first-difference transitions: -- `U+2801 ⠁ -> U+281C ⠜`: 385 +- `U+2801 ⠁ -> U+281C ⠜`: 397 - `U+2810 ⠐ -> U+2815 ⠕`: 3 - `U+2800 ⠀ -> U+2820 ⠠`: 2 - `U+2820 ⠠ -> U+280A ⠊`: 1 @@ -687,7 +688,7 @@ Mismatch primary-class distribution: - `comparison_method`: 3 - `corpus_suspect`: 14 -- `pending_rule_review`: 639 +- `pending_rule_review`: 636 - `unsupported_character_review`: 6 Representative `exact` samples: @@ -713,7 +714,7 @@ Representative `localized_mismatch` samples: - `sentence_01.json` #142: ‘메타버스존’에서는 증강현실(AR)·가상현실(VR)에 적용되는 혁신기술이 소개된다. 글라스를 착용하고 최첨단 3D 센싱모듈이 구현하는 가상현실을 관람객이 직접 체험할 수 있도록 했다. - expected: `⠠⠕⠂⠦⠄⠴⠠⠠⠁⠗⠠⠴⠐⠆⠫⠇⠶⠚⠡⠠⠕⠂⠦⠄` - - actual: `⠠⠕⠂⠦⠄⠴⠠⠠⠜⠠⠴⠐⠆⠸⠷⠫⠇⠶⠚⠡⠠⠕⠂⠸` + - actual: `⠠⠕⠂⠦⠄⠴⠠⠠⠜⠠⠴⠐⠆⠫⠇⠶⠚⠡⠠⠕⠂⠦⠄⠴` - first differing cell (zero-based): 34 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` - `sentence_02.json` #28: 체납된 세금은 전국 어디서나 은행 현금자동인출기(ATM)를 이용해 고지서 없이 현금과 신용카드로 납부가 가능하며, 자동응답시스템(ARS) 지방세 납부서비스(044-300-7114)를 이용해 가상계좌를 확인해 계좌이체를 하거나 신용카드로 납부할 수도 있다. @@ -736,7 +737,7 @@ Representative `mismatch` samples: - `sentence_01.json` #142: ‘메타버스존’에서는 증강현실(AR)·가상현실(VR)에 적용되는 혁신기술이 소개된다. 글라스를 착용하고 최첨단 3D 센싱모듈이 구현하는 가상현실을 관람객이 직접 체험할 수 있도록 했다. - expected: `⠠⠕⠂⠦⠄⠴⠠⠠⠁⠗⠠⠴⠐⠆⠫⠇⠶⠚⠡⠠⠕⠂⠦⠄` - - actual: `⠠⠕⠂⠦⠄⠴⠠⠠⠜⠠⠴⠐⠆⠸⠷⠫⠇⠶⠚⠡⠠⠕⠂⠸` + - actual: `⠠⠕⠂⠦⠄⠴⠠⠠⠜⠠⠴⠐⠆⠫⠇⠶⠚⠡⠠⠕⠂⠦⠄⠴` - first differing cell (zero-based): 34 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` - `sentence_02.json` #28: 체납된 세금은 전국 어디서나 은행 현금자동인출기(ATM)를 이용해 고지서 없이 현금과 신용카드로 납부가 가능하며, 자동응답시스템(ARS) 지방세 납부서비스(044-300-7114)를 이용해 가상계좌를 확인해 계좌이체를 하거나 신용카드로 납부할 수도 있다. @@ -757,13 +758,13 @@ Representative `mismatch` samples: ### `allcaps_roman_run_containing_ed` -Of the 816 candidates, 434 are the actual `pending_rule_review` subcluster. The other 382 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 816 candidates, 429 are the actual `pending_rule_review` subcluster. The other 387 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. -For this output-signature audit, 503 mismatches were evaluable and 327 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. +For this output-signature audit, 498 mismatches were evaluable and 328 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. Localized first-difference transitions: -- `U+2811 ⠑ -> U+282B ⠫`: 324 +- `U+2811 ⠑ -> U+282B ⠫`: 325 - `U+2810 ⠐ -> U+280E ⠎`: 1 - `U+2810 ⠐ -> U+281D ⠝`: 1 - `U+2815 ⠕ -> U+2820 ⠠`: 1 @@ -772,7 +773,7 @@ Mismatch primary-class distribution: - `comparison_method`: 1 - `corpus_suspect`: 63 -- `pending_rule_review`: 434 +- `pending_rule_review`: 429 - `unsupported_character_review`: 5 Representative `exact` samples: @@ -844,11 +845,11 @@ Representative `mismatch` samples: Of the 1816 candidates, 1666 are the actual `pending_rule_review` subcluster. The other 150 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. -For this output-signature audit, 1752 mismatches were evaluable and 1328 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. +For this output-signature audit, 1752 mismatches were evaluable and 1331 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. Localized first-difference transitions: -- `U+2815 ⠕ -> U+2833 ⠳`: 1327 +- `U+2815 ⠕ -> U+2833 ⠳`: 1330 - `U+2810 ⠐ -> U+283D ⠽`: 1 Mismatch primary-class distribution: @@ -903,9 +904,9 @@ Representative `mismatch` samples: ### `allcaps_roman_run_containing_st` -Of the 1479 candidates, 752 are the actual `pending_rule_review` subcluster. The other 727 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 1479 candidates, 750 are the actual `pending_rule_review` subcluster. The other 729 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. -For this output-signature audit, 801 mismatches were evaluable and 443 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. +For this output-signature audit, 799 mismatches were evaluable and 443 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. Localized first-difference transitions: @@ -919,7 +920,7 @@ Mismatch primary-class distribution: - `comparison_method`: 13 - `corpus_suspect`: 31 -- `pending_rule_review`: 752 +- `pending_rule_review`: 750 - `unsupported_character_review`: 5 Representative `exact` samples: @@ -989,15 +990,15 @@ Representative `mismatch` samples: ### `attached_ascii_roman_segments_joined_by_ampersand` -Of the 802 candidates, 502 are the actual `pending_rule_review` subcluster. The other 300 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 802 candidates, 492 are the actual `pending_rule_review` subcluster. The other 310 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. -For this output-signature audit, 529 mismatches were evaluable and 0 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. +For this output-signature audit, 519 mismatches were evaluable and 0 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. Mismatch primary-class distribution: - `comparison_method`: 3 - `corpus_suspect`: 10 -- `pending_rule_review`: 502 +- `pending_rule_review`: 492 - `unsupported_character_review`: 14 Representative `exact` samples: @@ -1044,19 +1045,15 @@ Representative `mismatch` samples: ### `attached_ascii_roman_to_korean_script_boundary` -Of the 17693 candidates, 4141 are the actual `pending_rule_review` subcluster. The other 13552 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. - -For this output-signature audit, 4969 mismatches were evaluable and 268 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. - -Localized first-difference transitions: +Of the 17693 candidates, 3939 are the actual `pending_rule_review` subcluster. The other 13754 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. -- `U+2832 ⠲ -> U+2838 ⠸`: 268 +For this output-signature audit, 4769 mismatches were evaluable and 0 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. Mismatch primary-class distribution: - `comparison_method`: 60 -- `corpus_suspect`: 717 -- `pending_rule_review`: 4141 +- `corpus_suspect`: 719 +- `pending_rule_review`: 3939 - `unsupported_character_review`: 51 Representative `exact` samples: @@ -1097,29 +1094,6 @@ Representative `exact_rule29_terminator` samples: - actual: `⠠⠎⠀⠴⠠⠠⠉⠎⠲⠝⠀⠊⠗⠚⠒⠀⠨⠕⠏⠒⠀⠘⠶⠰` - current primary/reason: `exact` / `exact` -Representative `localized_mismatch` samples: - -- `sentence_01.json` #314: 이와 관련해서 FAA는 “FAA항공정보(NOTAMS) 업데이트에 관여하는 시스템이 작동하지 않고 있다”며 “복구작업을 벌이고 있지만 현재로서는 복구 시점을 예상하기 어렵다”고 말했다. - - expected: `⠀⠦⠴⠠⠠⠋⠁⠁⠲⠚⠶⠈⠿⠨⠻⠘⠥⠦⠄⠴⠠⠠⠝⠕` - - actual: `⠀⠦⠴⠠⠠⠋⠁⠁⠸⠷⠚⠶⠈⠿⠨⠻⠘⠥⠸⠾⠐⠣⠠⠠` - - first differing cell (zero-based): 30 - - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_02.json` #483: 충남교육청(교육감 김지철)은 7월 31일부터 오는 11일까지 도내 Wee센터·Wee클래스 전문상담교사, 전문상담사, 임상심리사 150명을 대상으로 ‘2023 하반기 위(Wee) 프로젝트 역량강화 직무연수’를 추진하고 있다고 밝혔다. - - expected: `⠉⠗⠀⠴⠠⠺⠑⠑⠲⠠⠝⠒⠓⠎⠐⠆⠴⠠⠺⠑⠑⠲⠋⠮` - - actual: `⠉⠗⠀⠴⠠⠺⠑⠑⠸⠷⠠⠝⠒⠓⠎⠸⠾⠲⠸⠂⠴⠠⠺⠑` - - first differing cell (zero-based): 70 - - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_03.json` #402: 블룸버그에 따르면, BAT와 자회사인 BAT마케팅싱가포르(BATMS)는 은행사기법과 국제비상경제권법(IEEPA)을 위반하며 싱가포르의 제3자 회사를 통해 북한에서 수년간 담배를 팔아온 것으로 알려졌다. - - expected: `⠟⠀⠴⠠⠠⠃⠁⠞⠲⠑⠋⠝⠓⠕⠶⠠⠕⠶⠫⠙⠥⠐⠪⠦` - - actual: `⠟⠀⠴⠠⠠⠃⠁⠞⠸⠷⠑⠋⠝⠓⠕⠶⠠⠕⠶⠫⠙⠥⠐⠪` - - first differing cell (zero-based): 40 - - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_04.json` #1660: 공공운수노조 더불어사는희망연대본부는 KT 인터넷이나 IPTV 개통과 수리를 담당하는 KT 그룹사 KT서비스(KTS) 북부·남부 소속 기사들로 구성된 노조다. - - expected: `⠃⠇⠀⠴⠠⠠⠅⠞⠲⠠⠎⠘⠕⠠⠪⠦⠄⠴⠠⠠⠅⠞⠎⠠` - - actual: `⠃⠇⠀⠴⠠⠠⠅⠞⠸⠷⠠⠎⠘⠕⠠⠪⠸⠾⠐⠣⠠⠠⠅⠞` - - first differing cell (zero-based): 100 - - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` - Representative `mismatch` samples: - `sentence_01.json` #14: 새롭게 선보인 DRX 브랜딩과 팀 컬러는 2023년 1월 1일부터 DRX의 온오프라인 콘텐츠, 엠디(MD), SNS, 유튜브 등 모든 제작물에 적용될 예정이다. @@ -1143,25 +1117,51 @@ Representative `mismatch` samples: - first differing cell (zero-based): 60 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +Representative `mismatch_rule29_terminator` samples: + +- `sentence_01.json` #14: 새롭게 선보인 DRX 브랜딩과 팀 컬러는 2023년 1월 1일부터 DRX의 온오프라인 콘텐츠, 엠디(MD), SNS, 유튜브 등 모든 제작물에 적용될 예정이다. + - expected: `⠎⠀⠴⠠⠠⠙⠗⠭⠲⠺⠀⠷⠥⠙⠪⠐⠣⠟⠀⠋⠷⠓⠝⠒` + - actual: `⠎⠀⠴⠠⠠⠙⠗⠭⠲⠺⠀⠷⠥⠙⠪⠐⠣⠟⠀⠋⠷⠓⠝⠒` + - current primary/reason: `corpus_suspect` / `rule34_roman_indicator_before_opening_parenthesis` +- `sentence_02.json` #10: 설동호 대전교육감은 “학교 교육과정 및 여건에 맞는 인공지능(AI)교육 선도학교 운영을 통해 AI·SW교육 기반 구축과 학생들과 교원들의 미래 디지털 역량이 함양될 것이다.”라고 말했다. + - expected: `⠁⠊⠐⠆⠴⠠⠠⠎⠺⠲⠈⠬⠩⠁⠀⠈⠕⠘⠒⠀⠈⠍⠰⠍` + - actual: `⠁⠊⠲⠐⠆⠴⠎⠺⠲⠈⠬⠩⠁⠀⠈⠕⠘⠒⠀⠈⠍⠰⠍⠁` + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_03.json` #6: 지난해 9월 HMM과 파나시아가 선박용 탄소포집 시스템 공동연구에 대한 업무협약(MOU)을 체결한 뒤 7개월 만에 내놓은 연구성과다. + - expected: `⠂⠀⠴⠠⠠⠓⠍⠍⠲⠈⠧⠀⠙⠉⠠⠕⠣⠫⠀⠠⠾⠘⠁⠬` + - actual: `⠂⠀⠴⠠⠠⠓⠍⠍⠲⠈⠧⠀⠙⠉⠠⠕⠣⠫⠀⠠⠾⠘⠁⠬` + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_04.json` #19: 이날 행사에는 구 대표 외에 권봉석 (주)LG 최고운영책임자(COO), 박일평 LG사이언스파크대표(사장)를 비롯해 각 계열사 최고기술책임자(CTO), 최고데이터책임자(CDO), 최고인사책임자(CHO) 등이 참석해 국내 이공계 R&D 인재 400여명과 만났다. + - expected: `⠻⠀⠴⠠⠠⠇⠛⠲⠇⠕⠾⠠⠪⠙⠋⠪⠊⠗⠙⠬⠦⠄⠇⠨` + - actual: `⠙⠻⠀⠴⠠⠠⠇⠛⠲⠇⠕⠾⠠⠪⠙⠋⠪⠊⠗⠙⠬⠦⠄⠇` + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` + +Representative `mismatch_rule39_hangul_opening` samples: + +- `sentence_03.json` #16063: 사업자별 시장 점유율은 KT 24.23%, SK브로드밴드(IPTV) 17.71%, LG유플러스 14.79%, LG헬로비전 10.20%, KT스카이라이프 8.15%, SK브로드밴드 7.81% 순이다. + - expected: `⠴⠂⠀⠠⠠⠎⠅⠲⠘⠪⠐⠥⠊⠪⠘⠗⠒⠊⠪⠦⠄⠴⠠⠠` + - actual: `⠏⠐⠀⠴⠠⠠⠎⠅⠸⠷⠘⠪⠐⠥⠊⠪⠘⠗⠒⠊⠪⠸⠾⠐` + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` + ### `closed_roman_parenthetical_after_non_ascii_letter_boundary` -Of the 63959 candidates, 10674 are the actual `pending_rule_review` subcluster. The other 53285 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 63959 candidates, 10309 are the actual `pending_rule_review` subcluster. The other 53650 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. -For this output-signature audit, 12153 mismatches were evaluable and 581 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. +For this output-signature audit, 11790 mismatches were evaluable and 589 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. Localized first-difference transitions: -- `U+2826 ⠦ -> U+2834 ⠴`: 378 -- `U+2826 ⠦ -> U+2800 ⠀`: 175 -- `U+2834 ⠴ -> U+2826 ⠦`: 13 -- `U+2810 ⠐ -> U+2834 ⠴`: 10 +- `U+2826 ⠦ -> U+2834 ⠴`: 381 +- `U+2826 ⠦ -> U+2800 ⠀`: 178 +- `U+2834 ⠴ -> U+2826 ⠦`: 14 +- `U+2810 ⠐ -> U+2834 ⠴`: 11 - `U+2800 ⠀ -> U+2834 ⠴`: 4 Mismatch primary-class distribution: - `comparison_method`: 223 -- `corpus_suspect`: 1099 -- `pending_rule_review`: 10674 +- `corpus_suspect`: 1101 +- `pending_rule_review`: 10309 - `unsupported_character_review`: 157 Representative `exact` samples: @@ -1231,23 +1231,23 @@ Representative `mismatch` samples: ### `compact_numeric_ascii_letter_suffix` -Of the 2975 candidates, 1048 are the actual `pending_rule_review` subcluster. The other 1927 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 2975 candidates, 1035 are the actual `pending_rule_review` subcluster. The other 1940 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. -For this output-signature audit, 1116 mismatches were evaluable and 253 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. +For this output-signature audit, 1105 mismatches were evaluable and 254 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. Localized first-difference transitions: - `U+283C ⠼ -> U+2800 ⠀`: 191 - `U+2800 ⠀ -> U+2832 ⠲`: 27 -- `U+2810 ⠐ -> U+2832 ⠲`: 10 +- `U+2810 ⠐ -> U+2832 ⠲`: 11 - `U+281E ⠞ -> U+2810 ⠐`: 8 - `U+2805 ⠅ -> U+2834 ⠴`: 4 Mismatch primary-class distribution: - `comparison_method`: 12 -- `corpus_suspect`: 49 -- `pending_rule_review`: 1048 +- `corpus_suspect`: 51 +- `pending_rule_review`: 1035 - `unsupported_character_review`: 7 Representative `exact` samples: @@ -1317,9 +1317,9 @@ Representative `mismatch` samples: ### `decimal_point_between_ascii_digits` -Of the 4546 candidates, 1138 are the actual `pending_rule_review` subcluster. The other 3408 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 4546 candidates, 1109 are the actual `pending_rule_review` subcluster. The other 3437 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. -For this output-signature audit, 1195 mismatches were evaluable and 265 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. +For this output-signature audit, 1166 mismatches were evaluable and 260 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. Localized first-difference transitions: @@ -1333,7 +1333,7 @@ Mismatch primary-class distribution: - `comparison_method`: 17 - `corpus_suspect`: 28 -- `pending_rule_review`: 1138 +- `pending_rule_review`: 1109 - `unsupported_character_review`: 12 Representative `exact` samples: @@ -1447,15 +1447,67 @@ Representative `mismatch` samples: - first differing cell (zero-based): 18 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +### `korean_majority_same_token_roman_sandwich_non_domain` + +Of the 947 candidates, 550 are the actual `pending_rule_review` subcluster. The other 397 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. + +Mismatch primary-class distribution: + +- `corpus_suspect`: 7 +- `pending_rule_review`: 550 +- `unsupported_character_review`: 5 + +Representative `exact` samples: + +- `sentence_01.json` #314: 이와 관련해서 FAA는 “FAA항공정보(NOTAMS) 업데이트에 관여하는 시스템이 작동하지 않고 있다”며 “복구작업을 벌이고 있지만 현재로서는 복구 시점을 예상하기 어렵다”고 말했다. + - expected: `⠕⠧⠀⠈⠧⠒⠐⠡⠚⠗⠠⠎⠀⠴⠠⠠⠋⠁⠁⠲⠉⠵⠀⠦` + - actual: `⠕⠧⠀⠈⠧⠒⠐⠡⠚⠗⠠⠎⠀⠴⠠⠠⠋⠁⠁⠲⠉⠵⠀⠦` + - current primary/reason: `exact` / `exact` +- `sentence_02.json` #483: 충남교육청(교육감 김지철)은 7월 31일부터 오는 11일까지 도내 Wee센터·Wee클래스 전문상담교사, 전문상담사, 임상심리사 150명을 대상으로 ‘2023 하반기 위(Wee) 프로젝트 역량강화 직무연수’를 추진하고 있다고 밝혔다. + - expected: `⠰⠍⠶⠉⠢⠈⠬⠩⠁⠰⠻⠦⠄⠈⠬⠩⠁⠫⠢⠀⠈⠕⠢⠨` + - actual: `⠰⠍⠶⠉⠢⠈⠬⠩⠁⠰⠻⠦⠄⠈⠬⠩⠁⠫⠢⠀⠈⠕⠢⠨` + - current primary/reason: `exact` / `exact` +- `sentence_03.json` #54: 위탁생산은 경기도 하남에 구축한 HK이노엔의 세포유전자치료제 센터에서 진행한다. HK이노엔은 자체 연구뿐 아니라 위탁생산(CMO)·위탁개발생산(CDMO) 등을 통해 세포유전자치료제센터를 혁신 플랫폼으로 키울 방침이다. + - expected: `⠍⠗⠓⠁⠠⠗⠶⠇⠒⠵⠀⠈⠻⠈⠕⠊⠥⠀⠚⠉⠢⠝⠀⠈` + - actual: `⠍⠗⠓⠁⠠⠗⠶⠇⠒⠵⠀⠈⠻⠈⠕⠊⠥⠀⠚⠉⠢⠝⠀⠈` + - current primary/reason: `exact` / `exact` +- `sentence_04.json` #133: 우크라이나 전쟁 피해 지원 방안을 논의하는 이 회의에는 프랑스·독일·이탈리아·스페인 등 법무부 장관 20여명과 국제형사재판소(ICC)·유럽연합(EU) 관계자 등이 참석했다. 지난달 18일 출국한 한 장관은 22일 귀국한다. + - expected: `⠍⠋⠪⠐⠣⠕⠉⠀⠨⠾⠨⠗⠶⠀⠙⠕⠚⠗⠀⠨⠕⠏⠒⠀` + - actual: `⠍⠋⠪⠐⠣⠕⠉⠀⠨⠾⠨⠗⠶⠀⠙⠕⠚⠗⠀⠨⠕⠏⠒⠀` + - current primary/reason: `exact` / `exact` + +Representative `mismatch` samples: + +- `sentence_01.json` #142: ‘메타버스존’에서는 증강현실(AR)·가상현실(VR)에 적용되는 혁신기술이 소개된다. 글라스를 착용하고 최첨단 3D 센싱모듈이 구현하는 가상현실을 관람객이 직접 체험할 수 있도록 했다. + - expected: `⠠⠕⠂⠦⠄⠴⠠⠠⠁⠗⠠⠴⠐⠆⠫⠇⠶⠚⠡⠠⠕⠂⠦⠄` + - actual: `⠠⠕⠂⠦⠄⠴⠠⠠⠜⠠⠴⠐⠆⠫⠇⠶⠚⠡⠠⠕⠂⠦⠄⠴` + - first differing cell (zero-based): 34 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_02.json` #122: 이번 연수는 자연과 상생하는 청정환경 도시를 조성하고 정보통신기술(IT)·생명공학기술(BT) 등 첨단산업단지를 만들어가는 제주도의 우수정책 사례를 벤치마킹함으로써, 심도 있는 정책의정 구현을 모색하기 위해 마련되었다. + - expected: `⠁⠈⠕⠠⠯⠦⠄⠴⠠⠠⠃⠞⠠⠴⠀⠊⠪⠶⠀⠰⠎⠢⠊⠒` + - actual: `⠁⠈⠕⠠⠯⠦⠄⠴⠰⠠⠠⠃⠞⠠⠴⠀⠊⠪⠶⠀⠰⠎⠢⠊` + - first differing cell (zero-based): 89 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_03.json` #50: 당시 CC(폐쇄)TV에 찍힌 장면을 보면 A씨는 엘리베이터를 기다리는 B씨를 발견하자 몰래 뒤로 다가가 갑자기 피해 여성의 머리를 뒤에서 돌려차기로 가격하는 등 폭행했다. + - expected: `⠙⠌⠠⠧⠗⠠⠴⠴⠠⠠⠞⠧⠲⠝⠀⠠⠨⠕⠁⠚⠟⠀⠨⠶` + - actual: `⠙⠌⠠⠧⠗⠠⠴⠴⠞⠧⠲⠝⠀⠠⠨⠕⠁⠚⠟⠀⠨⠶⠑⠡` + - first differing cell (zero-based): 20 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_04.json` #338: 체험에 앞서 간단한 O·X 퀴즈가 진행됐다. 액화석유가스(LPG)·액화천연가스(LNG)·부탄가스의 차이점을 설명하고 누출사고 시 대응 방법 등을 O·X로 답하는 방식이다. + - expected: `⠒⠊⠒⠚⠒⠀⠴⠠⠕⠐⠆⠴⠠⠭⠲⠀⠋⠍⠗⠨⠪⠫⠀⠨` + - actual: `⠒⠊⠒⠚⠒⠀⠴⠠⠠⠕⠲⠐⠆⠴⠭⠲⠀⠋⠍⠗⠨⠪⠫⠀` + - first differing cell (zero-based): 21 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` + ### `korean_prefixed_closed_allcaps_parenthetical` -Of the 54492 candidates, 8484 are the actual `pending_rule_review` subcluster. The other 46008 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 54492 candidates, 8212 are the actual `pending_rule_review` subcluster. The other 46280 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. Mismatch primary-class distribution: - `comparison_method`: 177 -- `corpus_suspect`: 986 -- `pending_rule_review`: 8484 +- `corpus_suspect`: 988 +- `pending_rule_review`: 8212 - `unsupported_character_review`: 132 Representative `exact` samples: @@ -1502,20 +1554,20 @@ Representative `mismatch` samples: ### `korean_prefixed_closed_roman_annotation_rule_34_order` -Of the 64382 candidates, 10491 are the actual `pending_rule_review` subcluster. The other 53891 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 64382 candidates, 10123 are the actual `pending_rule_review` subcluster. The other 54259 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. -For this output-signature audit, 11974 mismatches were evaluable and 1103 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. +For this output-signature audit, 11608 mismatches were evaluable and 1105 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. Localized first-difference transitions: -- `U+2834 ⠴ -> U+2826 ⠦`: 1102 +- `U+2834 ⠴ -> U+2826 ⠦`: 1104 - `U+2810 ⠐ -> U+2826 ⠦`: 1 Mismatch primary-class distribution: - `comparison_method`: 227 -- `corpus_suspect`: 1099 -- `pending_rule_review`: 10491 +- `corpus_suspect`: 1101 +- `pending_rule_review`: 10123 - `unsupported_character_review`: 157 Representative `exact` samples: @@ -1673,9 +1725,9 @@ Mismatch primary-class distribution: Representative `mismatch` samples: - `sentence_01.json` #398: 방송통신위원회는 이동통신 3사(SK텔레콤·KT·LG유플러스), 한국정보통신진흥협회(KAIT)와 협력한다. 오는 16일부터 각 통신사 명의로 가입자에게 ‘스미싱 문자 주의 안내’ 문자 메시지를 순차 발송할 예정이다. - - expected: `⠇⠦⠄⠴⠠⠠⠎⠅⠲⠓⠝⠂⠐⠝⠋⠥⠢⠐⠆⠴⠠⠠⠅⠞` - - actual: `⠇⠦⠄⠴⠠⠠⠎⠅⠸⠷⠓⠝⠂⠐⠝⠋⠥⠢⠸⠾⠲⠸⠂⠴` - - first differing cell (zero-based): 35 + - expected: `⠢⠐⠆⠴⠠⠠⠅⠞⠐⠆⠴⠠⠠⠇⠛⠲⠩⠙⠮⠐⠎⠠⠪⠠` + - actual: `⠢⠐⠆⠴⠠⠠⠅⠞⠲⠐⠆⠴⠠⠠⠇⠛⠲⠩⠙⠮⠐⠎⠠⠪` + - first differing cell (zero-based): 51 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` - `sentence_02.json` #10: 설동호 대전교육감은 “학교 교육과정 및 여건에 맞는 인공지능(AI)교육 선도학교 운영을 통해 AI·SW교육 기반 구축과 학생들과 교원들의 미래 디지털 역량이 함양될 것이다.”라고 말했다. - expected: `⠚⠗⠀⠴⠠⠠⠁⠊⠐⠆⠴⠠⠠⠎⠺⠲⠈⠬⠩⠁⠀⠈⠕⠘` @@ -1695,9 +1747,9 @@ Representative `mismatch` samples: ### `pure_allcaps_segment_before_hyphen_and_multi_allcaps_segment_after` -Of the 448 candidates, 311 are the actual `pending_rule_review` subcluster. The other 137 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 448 candidates, 308 are the actual `pending_rule_review` subcluster. The other 140 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. -For this output-signature audit, 321 mismatches were evaluable and 1 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. +For this output-signature audit, 318 mismatches were evaluable and 1 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. Localized first-difference transitions: @@ -1706,7 +1758,7 @@ Localized first-difference transitions: Mismatch primary-class distribution: - `corpus_suspect`: 8 -- `pending_rule_review`: 311 +- `pending_rule_review`: 308 - `unsupported_character_review`: 2 Representative `exact` samples: @@ -1754,20 +1806,20 @@ Representative `mismatch` samples: - first differing cell (zero-based): 194 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` - `sentence_04.json` #883: 그룹 방탄소년단(BTS) 슈가의 첫 공식 솔로 음반 ‘D-데이’(D-DAY)가 발매 당일 한터차트 기준 107만장이 넘게 팔려나가며 첫날 판매량으로는 K팝 솔로 가수 최고 기록을 경신했다. - - expected: `⠘⠒⠀⠠⠦⠴⠠⠙⠲⠤⠊⠝⠕⠴⠄⠦⠄⠴⠠⠙⠤⠠⠠⠐` - - actual: `⠘⠒⠀⠠⠦⠴⠠⠙⠘⠔⠸⠷⠊⠝⠕⠸⠾⠴⠄⠐⠣⠰⠠⠙` - - first differing cell (zero-based): 58 + - expected: `⠙⠲⠤⠊⠝⠕⠴⠄⠦⠄⠴⠠⠙⠤⠠⠠⠐⠙⠠⠴⠫⠀⠘⠂` + - actual: `⠙⠲⠤⠊⠝⠕⠴⠄⠴⠐⠣⠠⠙⠤⠠⠠⠐⠙⠐⠜⠲⠫⠀⠘` + - first differing cell (zero-based): 65 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` ### `roman_hyphenated_word_after_whitespace_following_korean_word` Of the 361 candidates, 350 are the actual `pending_rule_review` subcluster. The other 11 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. -For this output-signature audit, 361 mismatches were evaluable and 135 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. +For this output-signature audit, 361 mismatches were evaluable and 136 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. Localized first-difference transitions: -- `U+2834 ⠴ -> U+2800 ⠀`: 135 +- `U+2834 ⠴ -> U+2800 ⠀`: 136 Mismatch primary-class distribution: @@ -1823,9 +1875,9 @@ Representative `mismatch` samples: ### `roman_parenthetical_headword_after_whitespace_following_korean_word` -Of the 4695 candidates, 1242 are the actual `pending_rule_review` subcluster. The other 3453 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 4695 candidates, 1225 are the actual `pending_rule_review` subcluster. The other 3470 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. -For this output-signature audit, 1332 mismatches were evaluable and 157 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. +For this output-signature audit, 1315 mismatches were evaluable and 157 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. Localized first-difference transitions: @@ -1837,7 +1889,7 @@ Mismatch primary-class distribution: - `comparison_method`: 29 - `corpus_suspect`: 37 -- `pending_rule_review`: 1242 +- `pending_rule_review`: 1225 - `unsupported_character_review`: 24 Representative `exact` samples: @@ -1896,9 +1948,9 @@ Representative `mismatch` samples: - first differing cell (zero-based): 0 - current primary/reason: `unsupported_character_review` / `unsupported_character_review` - `sentence_03.json` #50: 당시 CC(폐쇄)TV에 찍힌 장면을 보면 A씨는 엘리베이터를 기다리는 B씨를 발견하자 몰래 뒤로 다가가 갑자기 피해 여성의 머리를 뒤에서 돌려차기로 가격하는 등 폭행했다. - - expected: `⠀⠴⠠⠠⠉⠉⠦⠄⠙⠌⠠⠧⠗⠠⠴⠴⠠⠠⠞⠧⠲⠝⠀⠠` - - actual: `⠀⠴⠠⠠⠉⠉⠦⠄⠸⠷⠙⠌⠠⠧⠗⠸⠾⠠⠴⠰⠠⠠⠞⠧` - - first differing cell (zero-based): 12 + - expected: `⠙⠌⠠⠧⠗⠠⠴⠴⠠⠠⠞⠧⠲⠝⠀⠠⠨⠕⠁⠚⠟⠀⠨⠶` + - actual: `⠙⠌⠠⠧⠗⠠⠴⠴⠞⠧⠲⠝⠀⠠⠨⠕⠁⠚⠟⠀⠨⠶⠑⠡` + - first differing cell (zero-based): 20 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` - `sentence_04.json` #191: SK온은 23일 서울 종로구 SK서린사옥에서 에코프로머티리얼즈, 중국의 GEM(거린메이)과 전구체 생산을 위한 3자 합작법인인 지이엠코리아뉴에너지머티리얼즈㈜(이하 지이엠코리아) 설립 양해각서(MOU)를 체결했다고 밝혔다. - expected: `⠴⠠⠠⠎⠅⠲⠷⠵⠀⠼⠃⠉⠕⠂⠀⠠⠎⠯⠀⠨⠿⠐⠥⠈` @@ -1909,13 +1961,13 @@ Representative `mismatch` samples: ### `roman_run_after_whitespace_following_closed_roman_enclosure` -Of the 1093 candidates, 627 are the actual `pending_rule_review` subcluster. The other 466 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 1093 candidates, 624 are the actual `pending_rule_review` subcluster. The other 469 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. -For this output-signature audit, 955 mismatches were evaluable and 241 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. +For this output-signature audit, 952 mismatches were evaluable and 248 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. Localized first-difference transitions: -- `U+2834 ⠴ -> U+2830 ⠰`: 235 +- `U+2834 ⠴ -> U+2830 ⠰`: 242 - `U+2803 ⠃ -> U+2820 ⠠`: 1 - `U+2815 ⠕ -> U+2837 ⠷`: 1 - `U+2817 ⠗ -> U+2820 ⠠`: 1 @@ -1924,7 +1976,7 @@ Localized first-difference transitions: Mismatch primary-class distribution: - `corpus_suspect`: 321 -- `pending_rule_review`: 627 +- `pending_rule_review`: 624 - `unsupported_character_review`: 7 Representative `exact` samples: @@ -1997,11 +2049,11 @@ Representative `mismatch` samples: Of the 577 candidates, 565 are the actual `pending_rule_review` subcluster. The other 12 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. -For this output-signature audit, 577 mismatches were evaluable and 417 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. +For this output-signature audit, 577 mismatches were evaluable and 428 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. Localized first-difference transitions: -- `U+2810 ⠐ -> U+2832 ⠲`: 417 +- `U+2810 ⠐ -> U+2832 ⠲`: 428 Mismatch primary-class distribution: @@ -2057,9 +2109,9 @@ Representative `mismatch` samples: ### `rule69_ascii_unit_before_terminator_skipping_symbol` -Of the 440 candidates, 99 are the actual `pending_rule_review` subcluster. The other 341 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 440 candidates, 95 are the actual `pending_rule_review` subcluster. The other 345 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. -For this output-signature audit, 104 mismatches were evaluable and 10 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. +For this output-signature audit, 100 mismatches were evaluable and 10 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. Localized first-difference transitions: @@ -2073,7 +2125,7 @@ Mismatch primary-class distribution: - `comparison_method`: 3 - `corpus_suspect`: 1 -- `pending_rule_review`: 99 +- `pending_rule_review`: 95 - `unsupported_character_review`: 1 Representative `exact` samples: @@ -2126,9 +2178,9 @@ Representative `mismatch` samples: - first differing cell (zero-based): 19 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` - `sentence_03.json` #1399: 삼성전자가 양산을 시작한 12nm(나노미터·1nm는 10억분의 1m)급 공정 16기가비트(Gb) 더블데이터레이트(DDR)5 D램은 글로벌 메모리반도체 1위 기업인 삼성전자의 기술력이 총동원된 ‘야심작’이다. - - expected: `⠼⠁⠃⠴⠝⠍⠦⠄⠉⠉⠥⠑⠕⠓⠎⠐⠆⠼⠁⠴⠝⠍⠲⠉` - - actual: `⠼⠁⠃⠴⠝⠍⠦⠄⠸⠷⠉⠉⠥⠑⠕⠓⠎⠸⠾⠲⠸⠂⠼⠁` - - first differing cell (zero-based): 30 + - expected: `⠕⠓⠎⠐⠝⠕⠓⠪⠦⠄⠴⠠⠠⠙⠙⠗⠠⠴⠼⠑⠀⠴⠠⠙` + - actual: `⠕⠓⠎⠐⠝⠕⠓⠪⠀⠀⠦⠠⠠⠙⠙⠗⠴⠼⠑⠀⠴⠠⠙⠲` + - first differing cell (zero-based): 104 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` - `sentence_04.json` #2527: 삼성전자가 선폭 2·3나노(㎚·10억분의 1m) 수준의 반도체 설계에 필요한 ‘공정설계키트(PDK)’를 국내 팹리스(반도체 설계 업체)에 제공하는 등 파운드리(위탁 생산) 생태계 키우기에 나선다. - expected: `⠉⠉⠥⠦⠄⠴⠝⠍⠐⠆⠼⠁⠚⠹⠘⠛⠺⠀⠼⠁⠴⠍⠠⠴` @@ -2179,13 +2231,13 @@ Representative `mismatch` samples: ### `standalone_multi_character_uppercase_roman_word` -Of the 62411 candidates, 10901 are the actual `pending_rule_review` subcluster. The other 51510 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 62411 candidates, 10563 are the actual `pending_rule_review` subcluster. The other 51848 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. Mismatch primary-class distribution: - `comparison_method`: 211 -- `corpus_suspect`: 1086 -- `pending_rule_review`: 10901 +- `corpus_suspect`: 1088 +- `pending_rule_review`: 10563 - `unsupported_character_review`: 154 Representative `exact` samples: @@ -2234,11 +2286,11 @@ Representative `mismatch` samples: Of the 377 candidates, 376 are the actual `pending_rule_review` subcluster. The other 1 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. -For this output-signature audit, 377 mismatches were evaluable and 332 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. +For this output-signature audit, 377 mismatches were evaluable and 333 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. Localized first-difference transitions: -- `U+2800 ⠀ -> U+2807 ⠇`: 329 +- `U+2800 ⠀ -> U+2807 ⠇`: 330 - `U+280A ⠊ -> U+2807 ⠇`: 1 - `U+2818 ⠘ -> U+2807 ⠇`: 1 - `U+2829 ⠩ -> U+2807 ⠇`: 1 @@ -2273,9 +2325,9 @@ Representative `mismatch` samples: ### `uppercase_ascii_run_immediately_after_digit_in_roman_sequence` -Of the 1896 candidates, 748 are the actual `pending_rule_review` subcluster. The other 1148 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 1896 candidates, 740 are the actual `pending_rule_review` subcluster. The other 1156 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. -For this output-signature audit, 802 mismatches were evaluable and 1 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. +For this output-signature audit, 796 mismatches were evaluable and 1 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. Localized first-difference transitions: @@ -2284,8 +2336,8 @@ Localized first-difference transitions: Mismatch primary-class distribution: - `comparison_method`: 9 -- `corpus_suspect`: 38 -- `pending_rule_review`: 748 +- `corpus_suspect`: 40 +- `pending_rule_review`: 740 - `unsupported_character_review`: 7 Representative `exact` samples: @@ -2340,19 +2392,19 @@ Representative `mismatch` samples: ### `uppercase_ascii_run_immediately_after_hyphen_in_roman_sequence` -Of the 952 candidates, 771 are the actual `pending_rule_review` subcluster. The other 181 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 952 candidates, 768 are the actual `pending_rule_review` subcluster. The other 184 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. -For this output-signature audit, 792 mismatches were evaluable and 161 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. +For this output-signature audit, 789 mismatches were evaluable and 162 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. Localized first-difference transitions: -- `U+2820 ⠠ -> U+2830 ⠰`: 157 +- `U+2820 ⠠ -> U+2830 ⠰`: 158 - `U+2830 ⠰ -> U+2820 ⠠`: 4 Mismatch primary-class distribution: - `corpus_suspect`: 12 -- `pending_rule_review`: 771 +- `pending_rule_review`: 768 - `unsupported_character_review`: 9 Representative `exact` samples: @@ -2415,9 +2467,9 @@ Representative `mismatch` samples: - first differing cell (zero-based): 194 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` - `sentence_04.json` #883: 그룹 방탄소년단(BTS) 슈가의 첫 공식 솔로 음반 ‘D-데이’(D-DAY)가 발매 당일 한터차트 기준 107만장이 넘게 팔려나가며 첫날 판매량으로는 K팝 솔로 가수 최고 기록을 경신했다. - - expected: `⠘⠒⠀⠠⠦⠴⠠⠙⠲⠤⠊⠝⠕⠴⠄⠦⠄⠴⠠⠙⠤⠠⠠⠐` - - actual: `⠘⠒⠀⠠⠦⠴⠠⠙⠘⠔⠸⠷⠊⠝⠕⠸⠾⠴⠄⠐⠣⠰⠠⠙` - - first differing cell (zero-based): 58 + - expected: `⠙⠲⠤⠊⠝⠕⠴⠄⠦⠄⠴⠠⠙⠤⠠⠠⠐⠙⠠⠴⠫⠀⠘⠂` + - actual: `⠙⠲⠤⠊⠝⠕⠴⠄⠴⠐⠣⠠⠙⠤⠠⠠⠐⠙⠐⠜⠲⠫⠀⠘` + - first differing cell (zero-based): 65 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` ### `uppercase_roman_headword_closed_multiword_parenthetical` @@ -2475,9 +2527,9 @@ Representative `mismatch` samples: ### `uppercase_roman_run_followed_by_hyphen_digits` -Of the 571 candidates, 366 are the actual `pending_rule_review` subcluster. The other 205 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 571 candidates, 365 are the actual `pending_rule_review` subcluster. The other 206 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. -For this output-signature audit, 381 mismatches were evaluable and 261 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. +For this output-signature audit, 380 mismatches were evaluable and 261 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. Localized first-difference transitions: @@ -2487,7 +2539,7 @@ Localized first-difference transitions: Mismatch primary-class distribution: - `corpus_suspect`: 12 -- `pending_rule_review`: 366 +- `pending_rule_review`: 365 - `unsupported_character_review`: 3 Representative `exact` samples: @@ -2534,9 +2586,9 @@ Representative `mismatch` samples: ### `uppercase_word_after_whitespace_continuing_ascii_roman_text` -Of the 1729 candidates, 991 are the actual `pending_rule_review` subcluster. The other 738 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 1729 candidates, 977 are the actual `pending_rule_review` subcluster. The other 752 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. -For this output-signature audit, 1065 mismatches were evaluable and 4 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. +For this output-signature audit, 1051 mismatches were evaluable and 4 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. Localized first-difference transitions: @@ -2548,7 +2600,7 @@ Mismatch primary-class distribution: - `comparison_method`: 9 - `corpus_suspect`: 54 -- `pending_rule_review`: 991 +- `pending_rule_review`: 977 - `unsupported_character_review`: 11 Representative `exact` samples: @@ -2618,10 +2670,10 @@ These cohorts are defined by both an input boundary and the sentence's actual fi | Cohort | Candidates | Exact controls | Mismatch | Target localized | Reverse | |---|---:|---:|---:|---:|---:| -| `allcaps_roman_run_beginning_with_pure_letter_shortform` | 4120 | 2441 | 1679 | 783 | 43 | -| `uppercase_ascii_run_immediately_after_digit_in_roman_sequence` | 1896 | 1094 | 802 | 0 | 1 | -| `uppercase_ascii_run_immediately_after_hyphen_in_roman_sequence` | 952 | 160 | 792 | 157 | 4 | -| `pure_allcaps_segment_before_hyphen_and_multi_allcaps_segment_after` | 448 | 127 | 321 | 0 | 1 | +| `allcaps_roman_run_beginning_with_pure_letter_shortform` | 4120 | 2460 | 1660 | 785 | 47 | +| `uppercase_ascii_run_immediately_after_digit_in_roman_sequence` | 1896 | 1100 | 796 | 0 | 1 | +| `uppercase_ascii_run_immediately_after_hyphen_in_roman_sequence` | 952 | 163 | 789 | 158 | 4 | +| `pure_allcaps_segment_before_hyphen_and_multi_allcaps_segment_after` | 448 | 130 | 318 | 0 | 1 | ### All-caps shortform prefix at an attached Roman entry @@ -2643,12 +2695,12 @@ Same-surface controls demonstrate why primary classes must not be changed by coh | Surface | Candidates | Exact | Mismatch | Target-localized | |---|---:|---:|---:|---:| -| `AC` | 159 | 86 | 73 | 66 | +| `AC` | 159 | 87 | 72 | 66 | | `LLM` | 176 | 1 | 175 | 149 | -| `CD` | 65 | 26 | 39 | 12 | +| `CD` | 65 | 30 | 35 | 12 | | `IMM` | 27 | 3 | 24 | 22 | | `AG` | 18 | 9 | 9 | 0 | -| `GDP` | 359 | 0 | 359 | 351 | +| `GDP` | 359 | 0 | 359 | 352 | | `WD` | 10 | 10 | 0 | 0 | - `AC` exact control: `sentence_01.json` #1122 — 손흥민(토트넘)이 선발 출전했지만 소속팀 패배를 지켜봤다. 토트넘은 15일(이하 한국시간) 이탈리아 밀라노에 있는 산시로에서 AC 밀란(이탈리아)과 유럽축구연맹(UEFA) 주최 2022-2023시즌 챔피언스리그(UCL) 16강 1차전 원정 경기를 치렀다. @@ -2677,8 +2729,8 @@ Korean rule 29 requires a Roman indicator before Roman text in a Korean sentence | Cohort | Candidates | Exact controls | Mismatch | Pending | Corpus suspect | Localized `⠴ -> blank` | Reverse `blank -> ⠴` | |---|---:|---:|---:|---:|---:|---:|---:| -| `roman_hyphenated_word_after_whitespace_following_korean_word` | 361 | 0 | 361 | 350 | 6 | 135 | 0 | -| `roman_parenthetical_headword_after_whitespace_following_korean_word` | 4695 | 3363 | 1332 | 1242 | 37 | 153 | 0 | +| `roman_hyphenated_word_after_whitespace_following_korean_word` | 361 | 0 | 361 | 350 | 6 | 136 | 0 | +| `roman_parenthetical_headword_after_whitespace_following_korean_word` | 4695 | 3380 | 1315 | 1225 | 37 | 153 | 0 | | `korean_prefixed_roman_parenthetical_followed_by_allcaps_hyphen_suffix` | 11 | 0 | 11 | 6 | 5 | 5 | 0 | The whitespace parenthetical-headword cohort also retains 1 localized `⠠ -> ⠴` and 3 localized `⠴ -> ⠰` cases as separate transitions; they are not folded into the target. The exact controls demonstrate that the broad structure is already correct in many sentences, while the attached parenthetical-hyphen cohort has no exact control and includes cases already classified by the stricter rule-34 reference-order contradiction. Consequently none of these measurements authorizes an engine change; they are deterministic pending/corpus-review diagnostics only. @@ -2689,13 +2741,13 @@ Korean rule 29 explicitly says that when two or more Roman items occur consecuti Analyzer checkpoint `266e70c` fixed the pre-change baseline at 1,729 candidates, 386 exact controls, 1,343 mismatches, 1,272 pending members, and 385/1,343 evaluable mismatches localized to the current re-entry signature. Those localized transitions were 365 `⠠ -> ⠴`, 16 `⠰ -> ⠴`, no reverse `⠴ -> ⠠`, and four other transitions. Exact controls include contexts where the first Roman word already opened token-level mode, while parenthesized/mixed-token examples expose the duplicate event. -The generalized fix makes an explicit `EnterEnglish` event idempotent when final emit state is already inside a Roman section; it neither names an input nor changes a new section's entry. The current measurement is 1729 candidates, 664 exact controls, 1065 mismatches, 991 pending members, and 4/1065 localized mismatches. Current target counts are 0 `⠠ -> ⠴`, 0 `⠰ -> ⠴`, and 0 reverse `⠴ -> ⠠`. Cohort exact controls increase by 275, while corpus-wide exact matches rise from 67,138 to 67,442 (+304); corrected prefixes that still differ later remain mismatches, and applications outside this strict input gate account for the remaining net gain. The raw/residual reverse `⠴ -> ⠠` totals remain 68/65 before and after the change. The complete custom standard summary is 5,141 total, 5,141 success, 0 failure, and 0 skipped. +The generalized fix makes an explicit `EnterEnglish` event idempotent when final emit state is already inside a Roman section; it neither names an input nor changes a new section's entry. The current measurement is 1729 candidates, 678 exact controls, 1051 mismatches, 977 pending members, and 4/1051 localized mismatches. Current target counts are 0 `⠠ -> ⠴`, 0 `⠰ -> ⠴`, and 0 reverse `⠴ -> ⠠`. Cohort exact controls increase by 275, while corpus-wide exact matches rise from 67,138 to 67,442 (+304); corrected prefixes that still differ later remain mismatches, and applications outside this strict input gate account for the remaining net gain. The raw/residual reverse `⠴ -> ⠠` totals remain 68/65 before and after the change. The complete custom standard summary is 5,141 total, 5,141 success, 0 failure, and 0 skipped. ### Closed Roman parenthetical after a non-ASCII-letter boundary Korean rule 34 (2024 Korean-rules PDF p.29) prints the opening enclosure before Roman entry in `링컨(Lincoln)`: the opening sequence is `⠦⠄⠴`. This output-position cohort finds a closed, non-nested parenthetical whose body begins with an ASCII letter and whose opening does not immediately follow another ASCII letter, then locates its complete current-engine signature without consulting the reference. Direct function-call shapes such as `f(x)` are excluded. The PDF's math rule 6 (p.59), rule 12 (pp.63-64), and rule 45 (p.75) nevertheless leave standalone `(x)` and other Roman-letter parenthetical mathematics as counterexamples, so the surface gate is not an engine-routing predicate. -The cross-cutting input cohort contains 63959 candidates: 51806 exact controls and 12153 mismatches. Mismatch primary classes remain unchanged: 10674 `pending_rule_review`, 1099 `corpus_suspect`, 223 `comparison_method`, and 157 `unsupported_character_review`. Of 12153 evaluable mismatches, 581 have the first difference at the detected opening; these include 378 `U+2826 ⠦ -> U+2834 ⠴`, 13 `U+2834 ⠴ -> U+2826 ⠦`, and 175 `U+2826 ⠦ -> U+2800 ⠀` transitions. After all earlier localized cohorts and this cohort are excluded, the raw-to-residual target count is 461 -> 76 and the reverse count is 28 -> 12. The short full-encoder form `웹3(Web3)` emits the PDF opening order as a routing control, while whitespace and quote boundaries reproduce the current Roman-first opening. Representative localized samples, with shard and index, are retained in the generated cluster sample table. Because exact controls are abundant and the PDF does not make this input shape semantically sufficient to exclude mathematics, no engine change or primary reclassification is inferred. +The cross-cutting input cohort contains 63959 candidates: 52169 exact controls and 11790 mismatches. Mismatch primary classes remain unchanged: 10309 `pending_rule_review`, 1101 `corpus_suspect`, 223 `comparison_method`, and 157 `unsupported_character_review`. Of 11790 evaluable mismatches, 589 have the first difference at the detected opening; these include 381 `U+2826 ⠦ -> U+2834 ⠴`, 14 `U+2834 ⠴ -> U+2826 ⠦`, and 178 `U+2826 ⠦ -> U+2800 ⠀` transitions. After all earlier localized cohorts and this cohort are excluded, the raw-to-residual target count is 467 -> 79 and the reverse count is 30 -> 13. The short full-encoder form `웹3(Web3)` emits the PDF opening order as a routing control, while whitespace and quote boundaries reproduce the current Roman-first opening. Representative localized samples, with shard and index, are retained in the generated cluster sample table. Because exact controls are abundant and the PDF does not make this input shape semantically sufficient to exclude mathematics, no engine change or primary reclassification is inferred. The HCA-style headword-expansion shape described above is not an engine implementation premise. The 2024 PDF's math rule 6 defines parentheses and grouping parentheses, rule 11 defines mathematical-expression spacing, rule 12 covers Roman letters in formulas as well as Korean sentences, and rule 45 shows Roman-letter function notation followed by parentheses. Excluding visible operators, scripts, and nesting narrows this corpus cohort, but the PDF does not make the remaining surface shape sufficient to rule out every mathematical counterexample. The cluster therefore remains conservative pending-review evidence only. @@ -2724,23 +2776,23 @@ Corpus contradictions remain a separate gate: identical inputs with conflicting UEB 10.12.1 (2024 UEB PDF pp.191-192, printed pp.163-164) says not to use a contraction when letters within an abbreviation or acronym are known to be pronounced separately, but to use the contraction in case of doubt. Its official `DAR` example writes separate `a` and `r` cells, while UEB 10.12.2's official `START` example uses the `ar` groupsign. Both full-encoder controls pass. Thus an uppercase surface containing `AR` does not itself supply the pronunciation or lexical meaning needed to select either form. -The output-localized cohort contains 1022 candidates, 360 exact controls, and 662 mismatches. Existing mismatch primary classes are preserved: 639 `pending_rule_review`, 14 `corpus_suspect`, 3 `comparison_method`, and 6 `unsupported_character_review`. Of 662 evaluable mismatches, 392 have their first difference inside the detected current-engine run: 385 `U+2801 ⠁ -> U+281C ⠜` and 0 `U+281C ⠜ -> U+2801 ⠁`. Across raw pending transitions and the final residual after localized cohorts, the target count is 393 -> 7; the reverse is 2 -> 2. Inputs denoting separately pronounced initialisms such as corpus `AR`/`ARS` coexist with exact or officially contracted controls. This is deterministic evidence for a pronunciation-dependent pending cohort, not an engine rule or primary reclassification. Representative shard/index samples are retained above. +The output-localized cohort contains 1022 candidates, 363 exact controls, and 659 mismatches. Existing mismatch primary classes are preserved: 636 `pending_rule_review`, 14 `corpus_suspect`, 3 `comparison_method`, and 6 `unsupported_character_review`. Of 659 evaluable mismatches, 404 have their first difference inside the detected current-engine run: 397 `U+2801 ⠁ -> U+281C ⠜` and 0 `U+281C ⠜ -> U+2801 ⠁`. Across raw pending transitions and the final residual after localized cohorts, the target count is 405 -> 7; the reverse is 2 -> 2. Inputs denoting separately pronounced initialisms such as corpus `AR`/`ARS` coexist with exact or officially contracted controls. This is deterministic evidence for a pronunciation-dependent pending cohort, not an engine rule or primary reclassification. Representative shard/index samples are retained above. ### Roman run after a closed Roman enclosure Korean rule 29 places one Roman indicator/terminator pair around consecutive Roman items, with `Los Angeles` and `Table of Contents` as its printed multiword examples. Rule 34 separately omits the Roman terminator when Roman text is enclosed by quotation marks or parentheses. Neither printed rule states whether a later Roman run after the enclosure, intervening punctuation, and whitespace is a continuation of that section or a fresh section. The input gate therefore detects only the structural boundary; it does not label the later run as semantically new. -The cohort contains 1093 candidates, 138 exact controls, and 955 mismatches. Existing mismatch primary classes are preserved: 627 `pending_rule_review`, 321 `corpus_suspect`, and 7 `unsupported_character_review`. Of 955 evaluable mismatches, 241 are output-localized to the current later-run signature plus its one leading boundary cell: 235 `U+2834 ⠴ -> U+2830 ⠰` and 0 `U+2830 ⠰ -> U+2834 ⠴`. The target counted 336 raw and 333 residual cases before this cohort; it is now 98 final residual. Across raw and final residual maps, `U+2830 ⠰ -> U+2834 ⠴` is 3 -> 1. Exact controls coexist with both directions, and 321 mismatches are already independently identified corpus contradictions. Without a printed fresh-entry example or semantic enclosure model, changing continuation state would be reference-fitting; this remains a deterministic pending/corpus-review cohort only. Representative shard/index samples are retained above. +The cohort contains 1093 candidates, 141 exact controls, and 952 mismatches. Existing mismatch primary classes are preserved: 624 `pending_rule_review`, 321 `corpus_suspect`, and 7 `unsupported_character_review`. Of 952 evaluable mismatches, 248 are output-localized to the current later-run signature plus its one leading boundary cell: 242 `U+2834 ⠴ -> U+2830 ⠰` and 0 `U+2830 ⠰ -> U+2834 ⠴`. The target counted 344 raw and 333 residual cases before this cohort; it is now 99 final residual. Across raw and final residual maps, `U+2830 ⠰ -> U+2834 ⠴` is 3 -> 1. Exact controls coexist with both directions, and 321 mismatches are already independently identified corpus contradictions. Without a printed fresh-entry example or semantic enclosure model, changing continuation state would be reference-fitting; this remains a deterministic pending/corpus-review cohort only. Representative shard/index samples are retained above. ### Uppercase Roman runs containing `ED` UEB 10.12.1 (2024 UEB PDF pp.191-192, printed pp.163-164) requires separate letters when an abbreviation's letters are known to be pronounced separately, and its official `OED` example writes `e` and `d` separately. Rule 10.12.2 otherwise uses contractions; its official `BEd` example uses the `ed` groupsign. Both full-encoder controls pass. Consequently an uppercase `ED` surface cannot establish pronunciation or abbreviation semantics by spelling alone. -The cohort contains 816 candidates, 313 exact controls, and 503 mismatches. Existing mismatch primary classes remain 434 `pending_rule_review`, 63 `corpus_suspect`, 1 `comparison_method`, and 5 `unsupported_character_review`. Of 503 evaluable mismatches, 327 are localized to the detected current-engine run: 324 `U+2811 ⠑ -> U+282B ⠫` and 0 `U+282B ⠫ -> U+2811 ⠑`. Across raw pending transitions and the final residual after localized cohorts, the target is 326 -> 2 and the reverse is 0 -> 0. Corpus initialisms such as `LED` and `GED` require external pronunciation knowledge, while exact and official controls preserve contraction-bearing outcomes. No engine change or primary reclassification is made; representative shard/index samples are retained above. +The cohort contains 816 candidates, 318 exact controls, and 498 mismatches. Existing mismatch primary classes remain 429 `pending_rule_review`, 63 `corpus_suspect`, 1 `comparison_method`, and 5 `unsupported_character_review`. Of 498 evaluable mismatches, 328 are localized to the detected current-engine run: 325 `U+2811 ⠑ -> U+282B ⠫` and 0 `U+282B ⠫ -> U+2811 ⠑`. Across raw pending transitions and the final residual after localized cohorts, the target is 327 -> 2 and the reverse is 0 -> 0. Corpus initialisms such as `LED` and `GED` require external pronunciation knowledge, while exact and official controls preserve contraction-bearing outcomes. No engine change or primary reclassification is made; representative shard/index samples are retained above. ### Uppercase Roman runs containing `ST` -UEB 10.12.1 (2024 UEB PDF pp.191-192, printed pp.163-164) says not to use a contraction when letters within an abbreviation or acronym are known to be pronounced separately, but to use the contraction in case of doubt. Rule 10.12.2 requires contractions in other abbreviations and acronyms. Consequently an uppercase `ST` surface alone cannot determine whether `s` + `t` or the `st` groupsign is required. The current cohort contains 1479 candidates, 678 exact controls, and 801 mismatches; primary classes remain 752 `pending_rule_review`, 31 `corpus_suspect`, 13 `comparison_method`, and 5 `unsupported_character_review`. Of 801 evaluable mismatches, 443 are localized to the detected current-engine run: 435 `U+280E ⠎ -> U+280C ⠌` and 1 `U+280C ⠌ -> U+280E ⠎`. The target's raw-to-residual count is 448 -> 13, and the reverse is 2 -> 1. Exact controls such as `STAYG`, `KAIST`, `DGIST`, and `POSTECH` coexist with localized mismatches such as `WSTS`, `HUST`, `OST`, and `USTR`; representative shard/index samples are preserved in the cluster table. This lexical/pronunciation distinction cannot be inferred from the input-only spelling, so no engine change or primary reclassification is made. +UEB 10.12.1 (2024 UEB PDF pp.191-192, printed pp.163-164) says not to use a contraction when letters within an abbreviation or acronym are known to be pronounced separately, but to use the contraction in case of doubt. Rule 10.12.2 requires contractions in other abbreviations and acronyms. Consequently an uppercase `ST` surface alone cannot determine whether `s` + `t` or the `st` groupsign is required. The current cohort contains 1479 candidates, 680 exact controls, and 799 mismatches; primary classes remain 750 `pending_rule_review`, 31 `corpus_suspect`, 13 `comparison_method`, and 5 `unsupported_character_review`. Of 799 evaluable mismatches, 443 are localized to the detected current-engine run: 435 `U+280E ⠎ -> U+280C ⠌` and 1 `U+280C ⠌ -> U+280E ⠎`. The target's raw-to-residual count is 448 -> 13, and the reverse is 2 -> 1. Exact controls such as `STAYG`, `KAIST`, `DGIST`, and `POSTECH` coexist with localized mismatches such as `WSTS`, `HUST`, `OST`, and `USTR`; representative shard/index samples are preserved in the cluster table. This lexical/pronunciation distinction cannot be inferred from the input-only spelling, so no engine change or primary reclassification is made. ### Attached Roman segments joined by ampersand @@ -2750,28 +2802,28 @@ The baseline was 802 candidates / 0 exact / 802 mismatch, preserving 776 `pendin The implemented gate shares the analyzer's complete-run predicate: one or more non-empty ASCII-letter segments joined directly by `&`, with non-alphanumeric outer boundaries. It keeps the existing Roman mode open and suppresses only rule 71's redundant wrapper around that ampersand. Spaced `Marks & Spencer`, Korean `가&나`, empty segments, and outer digit continuations remain outside the gate. Official full-encoder controls `AT&T` and `B&B` pass, and the Korean rule-71 spaced example `종이접기 & 클레이아트` retains its independent `⠴⠈⠯⠲` section. -After the change the same cohort contains 802 candidates, 273 exact and 529 mismatch. Current mismatch primary classes remain evaluator-owned: 502 `pending_rule_review`, 10 `corpus_suspect`, 14 `unsupported_character_review`, and 3 `comparison_method`. The localizer evaluates all 529 remaining mismatches and finds 0 target-localized cases (0 `U+2808 ⠈ -> U+2832 ⠲`); current raw-to-residual target count is 24 -> 23, while `U+2832 ⠲ -> U+2808 ⠈` remains 0 raw / 0 residual. Cohort exact increases by 273 and corpus-wide exact increases by the same 273, from 67,442 to 67,715. Because every changed input must satisfy this shared predicate and the baseline had no exact member, this boundary has no exact regression. The remaining 529 candidates differ elsewhere or retain an existing comparison, corpus-suspect, unsupported, or pending cause. Representative shard/index samples are retained above. +After the change the same cohort contains 802 candidates, 283 exact and 519 mismatch. Current mismatch primary classes remain evaluator-owned: 492 `pending_rule_review`, 10 `corpus_suspect`, 14 `unsupported_character_review`, and 3 `comparison_method`. The localizer evaluates all 519 remaining mismatches and finds 0 target-localized cases (0 `U+2808 ⠈ -> U+2832 ⠲`); current raw-to-residual target count is 25 -> 24, while `U+2832 ⠲ -> U+2808 ⠈` remains 0 raw / 0 residual. Cohort exact increases by 273 and corpus-wide exact increases by the same 273, from 67,442 to 67,715. Because every changed input must satisfy this shared predicate and the baseline had no exact member, this boundary has no exact regression. The remaining 529 candidates differ elsewhere or retain an existing comparison, corpus-suspect, unsupported, or pending cause. Representative shard/index samples are retained above. -Current uppercase-Roman hyphen-digits measurement: 571 candidates, 190 exact controls, 381 mismatches, 366 members in the actual `pending_rule_review` subcluster, and 261/381 evaluable mismatches whose first difference is inside the target run plus its entry boundary. It remains distinct from parenthesized digits and headword expansions; no engine change is inferred. +Current uppercase-Roman hyphen-digits measurement: 571 candidates, 191 exact controls, 380 mismatches, 365 members in the actual `pending_rule_review` subcluster, and 261/380 evaluable mismatches whose first difference is inside the target run plus its entry boundary. It remains distinct from parenthesized digits and headword expansions; no engine change is inferred. Current single-capital parenthesized-digits measurement: 1361 candidates, 0 exact controls, 1361 mismatches, 1357 members in the actual `pending_rule_review` subcluster, and 1342/1361 evaluable mismatches whose first difference is inside the target run plus its entry boundary. No engine change is inferred from the ambiguous prose/function surface form. Current mixed Roman+Korean boundary before uppercase headword-expansion measurement: 10 candidates, 0 exact controls, 10 mismatches, 10 members in the actual `pending_rule_review` subcluster, and 8/10 evaluable mismatches whose first difference is localized to the later headword's entry boundary/output. The detector cannot be satisfied by the earlier Roman entry. No HCA-shaped engine routing rule is introduced. -Current compact numeric+ASCII-suffix measurement: 2975 candidates, 1859 exact controls, 1116 mismatches, 1048 members in the actual `pending_rule_review` subcluster, and 253/1116 evaluable mismatches whose first difference is inside the complete current-engine output signature or its immediate entry boundary. Rule 40 requires the numeric indicator and rule 69 requires Roman indicators around a Roman-written unit, but the input shape alone cannot prove that every ASCII suffix is a unit. +Current compact numeric+ASCII-suffix measurement: 2975 candidates, 1870 exact controls, 1105 mismatches, 1035 members in the actual `pending_rule_review` subcluster, and 254/1105 evaluable mismatches whose first difference is inside the complete current-engine output signature or its immediate entry boundary. Rule 40 requires the numeric indicator and rule 69 requires Roman indicators around a Roman-written unit, but the input shape alone cannot prove that every ASCII suffix is a unit. | ASCII suffix | Candidates | Exact | Mismatch | Localized first diff | |---|---:|---:|---:|---:| -| `m` | 364 | 258 | 106 | 22 | -| `km` | 308 | 249 | 59 | 3 | -| `G` | 237 | 163 | 74 | 3 | +| `m` | 364 | 261 | 103 | 22 | +| `km` | 308 | 250 | 58 | 3 | +| `G` | 237 | 164 | 73 | 3 | | `kg` | 209 | 174 | 35 | 6 | | `D` | 167 | 115 | 52 | 5 | | `p` | 140 | 20 | 120 | 84 | -| `g` | 138 | 99 | 39 | 8 | -| `t` | 128 | 84 | 44 | 16 | -| `M` | 104 | 79 | 25 | 6 | -| `cm` | 69 | 51 | 18 | 0 | +| `g` | 138 | 100 | 38 | 8 | +| `t` | 128 | 86 | 42 | 17 | +| `M` | 104 | 80 | 24 | 6 | +| `cm` | 69 | 52 | 17 | 0 | | `B` | 62 | 37 | 25 | 0 | | `GB` | 61 | 27 | 34 | 4 | | `GWh` | 57 | 42 | 15 | 8 | @@ -2790,30 +2842,32 @@ Current compact numeric+ASCII-suffix measurement: 2975 candidates, 1859 exact co Entry-boundary pre-fix baseline: 2,975 candidates, 1,649 exact controls, 1,326 mismatches, 1,259 pending members, and 356 localized first differences; the dominant reference number-sign versus current space transition accounted for 296 cases. Rules 68 and 69 already accept semantic Unicode compatibility-unit forms. The implementation derives compact ASCII spellings only from their all-letter NFKC decompositions, reuses the owning rule's PDF-defined cells, chooses the longest complete spelling, and rejects partial suffix matches. It does not extend recognition to separated English words or arbitrary corpus suffixes. The same cohort now has 1,759 exact controls, 1,216 mismatches, 1,148 pending members, and 261 localized first differences; corpus-wide exact matches moved from 66,436 to 66,546 (+110). Rule 69's printed `160㎎/㎗` example directly controls `160mg`; the additional full-encoder `240mg`/`240㎎` pair proves that recognition is invariant to the numeric value and also passes. Rule 68's printed `10,000㎡는 1㏊이다` example controls the `ha` spelling through U+33CA's NFKC decomposition; `15.2ha`/`15.2㏊` now passes without a spelling-specific output branch. The 53-case `ha` suffix control moved from 0 exact to 18 exact; its remaining 35 mismatches have independent later or surrounding differences. A full U+3300..U+33FF owner audit found 73 Rules 68/69 glyphs with pure-ASCII NFKC decompositions, 73 distinct spellings, and therefore no current duplicate-spelling owner collision. Production nevertheless groups every owner before resolution and excludes a spelling if any owner cells differ; a synthetic collision test proves that this is not first-wins behavior. An exhaustive test also compares every one of the 73 derived spellings with every owner glyph at both the unit-cell and full-encoder boundaries in a neutral Korean measurement context. That audit exposed the pre-existing explicit `cal` mapping's missing ordinary terminator; rule 69 requires the terminator, while the existing slash-boundary function removes it for the printed `cal/㎠/min` context. Restoring the ordinary terminator and extending that slash-continuation boundary made the audit pass without changing the corpus-wide 66,546 exact total. Ambiguous pure-English inputs remain on UEB: the standard controls `3m` and `4.m` are retained and pass, rather than being globally forced into a Korean unit route. -Current rule-69 ASCII-unit punctuation-boundary measurement: 440 candidates, 336 exact controls, 104 mismatches, 99 members in the actual `pending_rule_review` subcluster, and 10/104 evaluable mismatches whose first difference is localized to the unit-plus-punctuation output signature. PDF rule 69 requires a Roman terminator after a Roman-written unit in the ordinary case, while rules 33/34 omit it at the listed punctuation or enclosing-mark boundary; the rule 46 PDF example `체중(kg)` is the minimal parenthesized-unit control. Membership is restricted to rule-69 spellings already supported by the engine and does not infer unit semantics for arbitrary ASCII suffixes. +Current rule-69 ASCII-unit punctuation-boundary measurement: 440 candidates, 340 exact controls, 100 mismatches, 95 members in the actual `pending_rule_review` subcluster, and 10/100 evaluable mismatches whose first difference is localized to the unit-plus-punctuation output signature. PDF rule 69 requires a Roman terminator after a Roman-written unit in the ordinary case, while rules 33/34 omit it at the listed punctuation or enclosing-mark boundary; the rule 46 PDF example `체중(kg)` is the minimal parenthesized-unit control. Membership is restricted to rule-69 spellings already supported by the engine and does not infer unit semantics for arbitrary ASCII suffixes. Analyzer pre-fix baseline: 440 candidates, 0 exact controls, 440 mismatches, 435 pending members, and 9 signature-local first differences. After the generalized rule-33/34 boundary override and the matching non-math routing guard, the same cohort has 325 exact controls and 115 mismatches. Corpus-wide exact matches moved from 66,039 to 66,436 (+397); the additional gains are applications of the same boundary rule outside this strict ASCII detector, including compatibility-unit forms. The complete standard suite remains 5,141/5,141. -Current decimal-point measurement: 4546 candidates, 3351 exact controls, 1195 mismatches, 1138 members in the actual `pending_rule_review` subcluster, and 265/1195 evaluable mismatches whose first difference is inside the complete decimal-containing word's current-engine signature. Hangeul rules 43 and 48 keep an ASCII point between digits in the numeric sequence and encode it as the decimal point; rules 35 and 69 supply controls for adjacent Roman-number chains and Roman units. This is an implementation-candidate audit, not permission to specialize on a corpus reference. +Current decimal-point measurement: 4546 candidates, 3380 exact controls, 1166 mismatches, 1109 members in the actual `pending_rule_review` subcluster, and 260/1166 evaluable mismatches whose first difference is inside the complete decimal-containing word's current-engine signature. Hangeul rules 43 and 48 keep an ASCII point between digits in the numeric sequence and encode it as the decimal point; rules 35 and 69 supply controls for adjacent Roman-number chains and Roman units. This is an implementation-candidate audit, not permission to specialize on a corpus reference. Analyzer checkpoint `ed20f99` recorded the pre-fix baseline as 4,546 candidates, 2,584 exact controls, 1,962 mismatches, 1,905 pending members, and 875 localized first differences. Its dominant localized transition was reference decimal point `U+2832 ⠲` versus current Roman indicator `U+2834 ⠴` in 647 cases. After the generalized rule-43/48 guard, that transition is absent: 525 cases become exact and the other corrected prefixes expose later independent mismatches. -Current all-caps `OU` measurement: 1816 candidates, 64 exact controls, 1752 mismatches, 1666 members in the actual `pending_rule_review` subcluster, and 1328/1752 evaluable mismatches whose first difference is inside the current-engine output signature for the detected run. This is a pronunciation-sensitive UEB review cohort, not an engine routing rule. +Current all-caps `OU` measurement: 1816 candidates, 64 exact controls, 1752 mismatches, 1666 members in the actual `pending_rule_review` subcluster, and 1331/1752 evaluable mismatches whose first difference is inside the current-engine output signature for the detected run. This is a pronunciation-sensitive UEB review cohort, not an engine routing rule. -Current standalone-uppercase measurement: 62411 candidates, 50059 exact controls, 12352 mismatches, and 10901 members in the actual `pending_rule_review` subcluster. Its high frequency does not make it causal: the same input shape is exact in many cases, and a sentence containing the shape may first differ at another Roman, numeric, or punctuation structure. No engine change is inferred from this cohort. +Current standalone-uppercase measurement: 62411 candidates, 50395 exact controls, 12016 mismatches, and 10563 members in the actual `pending_rule_review` subcluster. Its high frequency does not make it causal: the same input shape is exact in many cases, and a sentence containing the shape may first differ at another Roman, numeric, or punctuation structure. No engine change is inferred from this cohort. -Current Korean-prefixed all-caps parenthetical measurement: 54492 candidates, 44713 exact controls, 9779 mismatches, and 8484 members in the actual `pending_rule_review` subcluster. This is a semantic-collision audit, not an engine routing rule; no implementation change is inferred from its reference outputs. +Current Korean-prefixed all-caps parenthetical measurement: 54492 candidates, 44983 exact controls, 9509 mismatches, and 8212 members in the actual `pending_rule_review` subcluster. This is a semantic-collision audit, not an engine routing rule; no implementation change is inferred from its reference outputs. -Current rule-34 opening-order measurement: 64382 structural candidates, 52408 exact controls, 11974 mismatches, and 1103/11974 evaluable mismatches whose first difference is inside the current engine's Korean opening-parenthesis cells. Only the 1102 localized first-cell transitions have the reference/current order `⠴` versus `⠦`. After requiring the complete reference `⠴⠐⠣` versus current/PDF `⠦⠄⠴` three-cell signature and preserving higher-priority comparison classifications, 1099 are classified as `corpus_suspect`; mere coexistence with a Korean-prefixed Roman annotation does not change a primary class. +Current rule-34 opening-order measurement: 64382 structural candidates, 52774 exact controls, 11608 mismatches, and 1105/11608 evaluable mismatches whose first difference is inside the current engine's Korean opening-parenthesis cells. Only the 1104 localized first-cell transitions have the reference/current order `⠴` versus `⠦`. After requiring the complete reference `⠴⠐⠣` versus current/PDF `⠦⠄⠴` three-cell signature and preserving higher-priority comparison classifications, 1101 are classified as `corpus_suspect`; mere coexistence with a Korean-prefixed Roman annotation does not change a primary class. Current all-caps Roman middle-dot measurement: 97 candidates, 0 exact controls, 97 mismatches, and 96 members in the actual `pending_rule_review` subcluster. This cross-cutting cohort preserves every primary class and is not an engine routing rule. -Current Roman-before-middle-dot boundary measurement: 577 candidates, 0 exact controls, 577 mismatches, 565 members in the actual `pending_rule_review` subcluster, and 417/577 evaluable mismatches whose first difference is localized to the one current terminator immediately before the middle dot. The locator encodes each real input prefix ending at the dot, so identifier state such as `K-ICS·...` is retained without consulting expected. This raises localized target coverage from 342 to 417 and reduces the then-leading residual `⠐ -> ⠲` transition from 297 to 223. Rules 29, 33, and 50 support the current terminator path but do not support the localized reference omission; no engine change or primary-class rewrite is inferred. +Current Roman-before-middle-dot boundary measurement: 577 candidates, 0 exact controls, 577 mismatches, 565 members in the actual `pending_rule_review` subcluster, and 428/577 evaluable mismatches whose first difference is localized to the one current terminator immediately before the middle dot. The locator encodes each real input prefix ending at the dot, so identifier state such as `K-ICS·...` is retained without consulting expected. This raises localized target coverage from 342 to 417 and reduces the then-leading residual `⠐ -> ⠲` transition from 297 to 223. Rules 29, 33, and 50 support the current terminator path but do not support the localized reference omission; no engine change or primary-class rewrite is inferred. + +Current attached Roman-to-Korean boundary measurement: 17693 candidates, 12924 exact controls, 4769 mismatches, 3939 members in the actual `pending_rule_review` subcluster, and 0/4769 evaluable mismatches whose first difference is localized to the current boundary marker. The localized target `⠲ -> ⠸` occurs 0 times and the reverse `⠸ -> ⠲` occurs 0 times. Korean rule 29 (2024 Korean-rules PDF p.26, printed p.20) requires a Roman terminator around Roman text in a Korean sentence, whereas rule 39 (PDF pp.31-32, printed pp.25-26) applies the Korean opening/closing markers only when Roman text is the sentence's main language. Among exact candidates, 10960 expose the current rule-29 terminator at such a boundary and 0 expose the current rule-39 opening. Its printed controls include both an English sentence (`What is 김치 in English?`) and the address `www.대통령.kr` inside a Korean sentence. Official UEB 2.4.7 (2024 UEB PDF p.41, printed p.13) confirms that a UEB mode does not extend through a switch to another braille code, but does not choose whether this Korean boundary belongs to a Korean-main or Roman-main context. Therefore the attached script boundary alone cannot distinguish ordinary Korean prose from an embedded Roman-domain context. Primary classes are preserved and no engine change is inferred without a narrower input-derived dominance gate. At diagnostic checkpoint `57c4608`, this same cohort was 17,693 candidates / 12,724 exact / 4,969 mismatch, with 268 localized `⠲ -> ⠸`, zero reverse, 10,780 exact rule-29 markers, and zero exact rule-39 markers. After narrowing the engine by dominance and the PDF domain shape, it is 12,924 exact / 4,769 mismatch with no localized target or reverse; corpus-wide exact rises from 67,715 to 68,101 (+386). The remaining 1 mismatch with a current rule-39 opening is a list-heavy Korean sentence whose ASCII-leading brand tokens satisfy the mechanical word-count majority; its first difference is not localized to this boundary, so it remains a semantic pending control rather than grounds for another engine branch. -Current attached Roman-to-Korean boundary measurement: 17693 candidates, 12724 exact controls, 4969 mismatches, 4141 members in the actual `pending_rule_review` subcluster, and 268/4969 evaluable mismatches whose first difference is localized to the current boundary marker. The localized target `⠲ -> ⠸` occurs 268 times and the reverse `⠸ -> ⠲` occurs 0 times. Korean rule 29 (2024 Korean-rules PDF p.26, printed p.20) requires a Roman terminator around Roman text in a Korean sentence, whereas rule 39 (PDF pp.31-32, printed pp.25-26) applies the Korean opening/closing markers only when Roman text is the sentence's main language. Among exact candidates, 10780 expose the current rule-29 terminator at such a boundary and 0 expose the current rule-39 opening. Its printed controls include both an English sentence (`What is 김치 in English?`) and the address `www.대통령.kr` inside a Korean sentence. Official UEB 2.4.7 (2024 UEB PDF p.41, printed p.13) confirms that a UEB mode does not extend through a switch to another braille code, but does not choose whether this Korean boundary belongs to a Korean-main or Roman-main context. Therefore the attached script boundary alone cannot distinguish ordinary Korean prose from an embedded Roman-domain context. Primary classes are preserved and no engine change is inferred without a narrower input-derived dominance gate. +Current rule-39 narrowed-scope audit: 947 candidates, 385 exact controls, 562 mismatches, and 550 members in the actual `pending_rule_review` subcluster. This input-derived scope is recorded separately from the direct-boundary output-localizer; primary classes are unchanged. At the implementation checkpoint, this conservative first-script-word approximation contains 385 exact cases, accounting for all but one of the corpus-wide +386 net gain; no localized reverse transition is observed in the direct-boundary cohort. Current inline parenthesized-operator measurement: 23 candidates, 17 exact controls, 6 mismatches, 6 members in the actual `pending_rule_review` subcluster, and 2/6 evaluable mismatches whose first differing cell is inside the emitted structure. Primary classes are preserved. At that implementation checkpoint, the strict cohort moved from 0 to 17 exact cases; the corpus-wide total moved from 65,491 to 65,514 (+23 exact) because the same PDF-backed spacing rule also applied outside the stricter Korean-boundary audit gate. These are immutable checkpoint counts rather than the report's later cumulative total. The complete standard suite remained 5,141/5,141. -Current tight-triangle measurement: 377 candidates, 0 exact controls, 377 mismatches, 376 members in the actual `pending_rule_review` subcluster, and 332/377 evaluable mismatches whose first difference is inside the `△` plus first-Korean output range. No engine change is inferred. +Current tight-triangle measurement: 377 candidates, 0 exact controls, 377 mismatches, 376 members in the actual `pending_rule_review` subcluster, and 333/377 evaluable mismatches whose first difference is inside the `△` plus first-Korean output range. No engine change is inferred. ## Encoding-error diagnostics @@ -2865,19 +2919,19 @@ Families are diagnostics, not automatic normalization permissions. Rules 68/69 c | Shard | Exact | Total | Accuracy | |---|---:|---:|---:| -| `sentence_01.json` | 20310 | 25000 | 81.24% | -| `sentence_02.json` | 19810 | 25000 | 79.24% | -| `sentence_03.json` | 20423 | 25000 | 81.69% | -| `sentence_04.json` | 7172 | 8528 | 84.10% | +| `sentence_01.json` | 20420 | 25000 | 81.68% | +| `sentence_02.json` | 19950 | 25000 | 79.80% | +| `sentence_03.json` | 20516 | 25000 | 82.06% | +| `sentence_04.json` | 7215 | 8528 | 84.60% | ## Overlapping mismatch traits | Trait | Count | |---|---:| -| `contains_ascii_digits` | 12402 | -| `contains_ascii_letters` | 15627 | -| `contains_delimiter_or_quote` | 15813 | -| `input_not_nfkc` | 718 | +| `contains_ascii_digits` | 12140 | +| `contains_ascii_letters` | 15241 | +| `contains_delimiter_or_quote` | 15427 | +| `input_not_nfkc` | 716 | ## Samples @@ -3018,9 +3072,9 @@ Presentation-form cases audited: 37. | Previous observation → current observation | Cases | |---|---:| -| `encoding_error -> encoded_mismatch_pending_rule_review` | 22 | +| `encoding_error -> encoded_mismatch_pending_rule_review` | 21 | | `encoding_error -> unsupported_character_review` | 3 | -| `nfkc_input_equivalent -> exact` | 12 | +| `nfkc_input_equivalent -> exact` | 13 | Remaining complex encoding errors: 3. These cases still contain another character that fails independently, so disappearance of the `roman_numeral_presentation` family does not imply that every former error case now encodes successfully. @@ -3056,6 +3110,7 @@ Against the immediately preceding 63,399-exact run, exact matches increased by 2 | UEB complete all-caps segments across hyphen | 5,141/5,141 | 67,138/83,528 | 80.38% | The grade-1 restart is omitted only between a complete uppercase prefix and an uppercase suffix of at least two letters; mixed/single-capital and digit-hyphen controls remain unchanged | | Rule-29 consecutive Roman entry idempotence | 5,141/5,141 | 67,442/83,528 | 80.74% | An explicit Roman-entry event is ignored only when final emit state is already inside the same Roman section | | Rules 29/71 complete attached Roman ampersand run | 5,141/5,141 | 67,715/83,528 | 81.07% | Complete ASCII-letter segments joined by `&` retain one Roman section; official `AT&T`, `B&B`, and spaced Korean Rule-71 controls delimit the safe boundary; 273 cases became exact | +| Rules 29/39 Roman-to-Korean mode ownership | 5,141/5,141 | 68,101/83,528 | 81.53% | Same-token Korean is wrapped only in a Roman-majority document or the official dot-delimited `www.대통령.kr` domain shape; all three rule-39 PDF controls remain exact and 386 corpus cases became exact | The latest full `cargo test -p braillify test_by_testcase --release -- --nocapture` run was accepted from its custom testcase summary, not the trailing filtered harness: `총 테스트 케이스: 5141`, `성공: 5141`, `실패: 0`, and `Skip (limitation): 0`. diff --git a/libs/braillify/examples/nikl_corpus_analyze.rs b/libs/braillify/examples/nikl_corpus_analyze.rs index 0911edca..a2abebf8 100644 --- a/libs/braillify/examples/nikl_corpus_analyze.rs +++ b/libs/braillify/examples/nikl_corpus_analyze.rs @@ -580,6 +580,8 @@ const ROMAN_RUN_BEFORE_MIDDLE_DOT_BOUNDARY: &str = "roman_run_immediately_before_attached_middle_dot_boundary"; const ATTACHED_ASCII_ROMAN_TO_KOREAN_BOUNDARY: &str = "attached_ascii_roman_to_korean_script_boundary"; +const KOREAN_MAJORITY_ROMAN_SANDWICH_NON_DOMAIN: &str = + "korean_majority_same_token_roman_sandwich_non_domain"; const KOREAN_INLINE_PARENTHESIZED_OPERATOR: &str = "korean_inline_parenthesized_single_arithmetic_operator"; const TIGHT_TRIANGLE_BEFORE_KOREAN: &str = "tight_triangle_mark_immediately_before_korean"; @@ -2412,6 +2414,78 @@ fn first_difference_at_attached_ascii_roman_to_korean_boundary(item: &EncodedCas .any(|range| range.contains(&first_difference)) } +/// Mirrors the document-level word-count gate used by rule 39 closely enough +/// for a corpus scope audit: each whitespace-delimited word contributes its +/// first ASCII letter or Korean script character. +fn input_is_english_majority(input: &str) -> bool { + let mut english_words = 0usize; + let mut korean_words = 0usize; + for word in input.split_whitespace() { + match word + .chars() + .find(|ch| ch.is_ascii_alphabetic() || is_korean_script(*ch)) + { + Some(ch) if ch.is_ascii_alphabetic() => english_words += 1, + Some(_) => korean_words += 1, + None => {} + } + } + english_words >= korean_words.max(1) +} + +/// Scope audit for the engine branch changed after the boundary diagnosis: +/// Korean-majority input, one whitespace-delimited token, Korean script with +/// the nearest script character on both sides being ASCII Roman, excluding +/// the PDF's dot-delimited `www.대통령.kr` structure. +fn korean_majority_roman_sandwich_non_domain_spans(input: &str) -> Vec { + if input_is_english_majority(input) { + return Vec::new(); + } + let mut spans = Vec::new(); + let mut word_start = 0usize; + for word in input.split_inclusive(char::is_whitespace) { + let word_body = word.trim_end_matches(char::is_whitespace); + let chars = word_body.char_indices().collect::>(); + let mut cursor = 0usize; + while cursor < chars.len() { + if !is_korean_script(chars[cursor].1) { + cursor += 1; + continue; + } + let segment_start = cursor; + while cursor < chars.len() && is_korean_script(chars[cursor].1) { + cursor += 1; + } + let segment_end = cursor; + let left_is_roman = chars[..segment_start].iter().rev().find_map(|(_, ch)| { + (ch.is_ascii_alphabetic() || is_korean_script(*ch)) + .then_some(ch.is_ascii_alphabetic()) + }) == Some(true); + let right_is_roman = chars[segment_end..].iter().find_map(|(_, ch)| { + (ch.is_ascii_alphabetic() || is_korean_script(*ch)) + .then_some(ch.is_ascii_alphabetic()) + }) == Some(true); + if !left_is_roman || !right_is_roman { + continue; + } + let korean_start = chars[segment_start].0; + let korean_end = chars + .get(segment_end) + .map_or(word_body.len(), |(byte, _)| *byte); + let dot_delimited = word_body[..korean_start].ends_with('.') + && word_body[korean_end..].starts_with('.'); + if !dot_delimited { + spans.push(InputSpan { + start_byte: word_start + korean_start, + end_byte: word_start + korean_end, + }); + } + } + word_start += word.len(); + } + spans +} + fn record_attached_ascii_roman_to_korean_marker_outcomes( stats: &mut PendingRuleReviewClusterStats, item: &EncodedCase, @@ -2447,10 +2521,10 @@ fn record_attached_ascii_roman_to_korean_marker_outcomes( .entry(format!("{outcome}:{marker}")) .or_insert(0) += 1; - if outcome != "exact" { - continue; - } - let bucket = stats.samples.entry(format!("exact_{marker}")).or_default(); + let bucket = stats + .samples + .entry(format!("{outcome}_{marker}")) + .or_default(); if bucket.len() >= sample_limit || bucket .iter() @@ -2885,6 +2959,10 @@ fn analyze( ATTACHED_ASCII_ROMAN_TO_KOREAN_BOUNDARY.to_string(), PendingRuleReviewClusterStats::default(), ), + ( + KOREAN_MAJORITY_ROMAN_SANDWICH_NON_DOMAIN.to_string(), + PendingRuleReviewClusterStats::default(), + ), ( KOREAN_PREFIXED_ALLCAPS_PARENTHETICAL.to_string(), PendingRuleReviewClusterStats::default(), @@ -3114,6 +3192,13 @@ fn analyze( ), true, ), + ( + KOREAN_MAJORITY_ROMAN_SANDWICH_NON_DOMAIN, + !korean_majority_roman_sandwich_non_domain_spans(&item.located.case.input) + .is_empty(), + None, + false, + ), ( KOREAN_PREFIXED_ALLCAPS_PARENTHETICAL, has_korean_prefixed_allcaps_parenthetical(&item.located.case.input), @@ -3724,6 +3809,11 @@ fn markdown(report: &AnalysisReport) -> String { letter run immediately followed by Korean script and localizes only the current mode \ marker there: either rule 29's final `⠲` or rule 39's opening `⠸⠷`. It deliberately \ does not infer the dominant language of the sentence from that surface boundary. The \ + `korean_majority_same_token_roman_sandwich_non_domain` scope audit mirrors the narrower \ + rule-39 implementation gate: the nearest script characters on both sides of one Korean \ + segment are ASCII Roman, the input is Korean-majority by first-script word counts, and \ + the segment is not dot-delimited like the official domain example. It measures change \ + scope but is not itself an output-localized causal classifier. The \ `korean_inline_parenthesized_single_arithmetic_operator` gate requires an immediate \ `Korean(` + one rule-45 arithmetic operator + `)Korean` span. Unlike broad coexistence \ traits, it also locates the current engine's emitted structure and counts a mismatch as \ @@ -5070,6 +5160,11 @@ fn markdown(report: &AnalysisReport) -> String { .get("exact:rule39_hangul_opening") .copied() .unwrap_or(0); + let mismatch_hangul_opening = stats + .actual_output_signature_outcomes + .get("mismatch:rule39_hangul_opening") + .copied() + .unwrap_or(0); text.push_str(&format!( "\nCurrent attached Roman-to-Korean boundary measurement: {} candidates, {} exact \ controls, {} mismatches, {pending} members in the actual `pending_rule_review` \ @@ -5088,7 +5183,16 @@ fn markdown(report: &AnalysisReport) -> String { boundary belongs to a Korean-main or Roman-main context. Therefore the \ attached script boundary alone cannot distinguish ordinary Korean prose from an \ embedded Roman-domain context. Primary classes are preserved and no engine change \ - is inferred without a narrower input-derived dominance gate.\n", + is inferred without a narrower input-derived dominance gate. At diagnostic \ + checkpoint `57c4608`, this same cohort was 17,693 candidates / 12,724 exact / 4,969 \ + mismatch, with 268 localized `⠲ -> ⠸`, zero reverse, 10,780 exact rule-29 markers, \ + and zero exact rule-39 markers. After narrowing the engine by dominance and the PDF \ + domain shape, it is 12,924 exact / 4,769 mismatch with no localized target or \ + reverse; corpus-wide exact rises from 67,715 to 68,101 (+386). The remaining \ + {mismatch_hangul_opening} mismatch with a current rule-39 opening is a list-heavy \ + Korean sentence whose ASCII-leading brand tokens satisfy the mechanical word-count \ + majority; its first difference is not localized to this boundary, so it remains a \ + semantic pending control rather than grounds for another engine branch.\n", stats.candidates, stats.exact, stats.mismatch, @@ -5096,6 +5200,26 @@ fn markdown(report: &AnalysisReport) -> String { stats.output_signature_mismatches_evaluated )); } + if let Some(stats) = report + .pending_rule_review_clusters + .get(KOREAN_MAJORITY_ROMAN_SANDWICH_NON_DOMAIN) + { + let pending = stats + .mismatch_primary_classes + .get("pending_rule_review") + .copied() + .unwrap_or(0); + text.push_str(&format!( + "\nCurrent rule-39 narrowed-scope audit: {} candidates, {} exact controls, {} \ + mismatches, and {pending} members in the actual `pending_rule_review` subcluster. \ + This input-derived scope is recorded separately from the direct-boundary \ + output-localizer; primary classes are unchanged. At the implementation checkpoint, \ + this conservative first-script-word approximation contains 385 exact cases, \ + accounting for all but one of the corpus-wide +386 net gain; no localized reverse \ + transition is observed in the direct-boundary cohort.\n", + stats.candidates, stats.exact, stats.mismatch + )); + } if let Some(stats) = report .pending_rule_review_clusters .get(KOREAN_INLINE_PARENTHESIZED_OPERATOR) @@ -5403,7 +5527,8 @@ fn markdown(report: &AnalysisReport) -> String { "| UEB numeric-mode letter classes in Roman identifiers | 5,141/5,141 | 67,012/83,528 | 80.23% | Lowercase `a`-`j` retains grade 1 after digits, capitals use capitalization, and lowercase `k`-`z` needs no extra indicator; numeric-leading Rule-69 units remain separate |\n\ | UEB complete all-caps segments across hyphen | 5,141/5,141 | 67,138/83,528 | 80.38% | The grade-1 restart is omitted only between a complete uppercase prefix and an uppercase suffix of at least two letters; mixed/single-capital and digit-hyphen controls remain unchanged |\n\ | Rule-29 consecutive Roman entry idempotence | 5,141/5,141 | 67,442/83,528 | 80.74% | An explicit Roman-entry event is ignored only when final emit state is already inside the same Roman section |\n\ - | Rules 29/71 complete attached Roman ampersand run | 5,141/5,141 | 67,715/83,528 | 81.07% | Complete ASCII-letter segments joined by `&` retain one Roman section; official `AT&T`, `B&B`, and spaced Korean Rule-71 controls delimit the safe boundary; 273 cases became exact |\n", + | Rules 29/71 complete attached Roman ampersand run | 5,141/5,141 | 67,715/83,528 | 81.07% | Complete ASCII-letter segments joined by `&` retain one Roman section; official `AT&T`, `B&B`, and spaced Korean Rule-71 controls delimit the safe boundary; 273 cases became exact |\n\ + | Rules 29/39 Roman-to-Korean mode ownership | 5,141/5,141 | 68,101/83,528 | 81.53% | Same-token Korean is wrapped only in a Roman-majority document or the official dot-delimited `www.대통령.kr` domain shape; all three rule-39 PDF controls remain exact and 386 corpus cases became exact |\n", ); text.push_str( "\nThe latest full `cargo test -p braillify test_by_testcase --release -- --nocapture` \ @@ -6404,7 +6529,11 @@ mod tests { } #[rstest::rstest] - #[case::current_rule39_opening("관련 FAA항공정보(NOTAMS)", '⠸', 2)] + #[case::rule29_after_korean_majority_narrowing( + "이와 관련해서 FAA는 “FAA항공정보(NOTAMS) 업데이트에 관여한다”고 말했다.", + '⠲', + 1 + )] #[case::current_rule29_terminator("종목 e스포츠", '⠲', 1)] fn localizes_current_attached_roman_to_korean_marker( #[case] input: &str, @@ -6414,9 +6543,35 @@ mod tests { let actual = braillify::encode_to_unicode(input).expect("boundary probe must encode"); let ranges = attached_ascii_roman_to_korean_actual_ranges(input, &actual); - assert_eq!(ranges.len(), 1); - assert_eq!(ranges[0].end - ranges[0].start, marker_cells); - assert_eq!(actual.chars().nth(ranges[0].start), Some(first_marker)); + assert!(!ranges.is_empty()); + assert!( + ranges + .iter() + .all(|range| range.end - range.start == marker_cells) + ); + assert!( + ranges + .iter() + .all(|range| actual.chars().nth(range.start) == Some(first_marker)) + ); + } + + #[rstest::rstest] + #[case::korean_majority_sandwich( + "이와 관련해서 FAA는 “FAA항공정보(NOTAMS) 업데이트에 관여한다”고 말했다.", + vec!["항공정보"] + )] + #[case::pdf_domain_control("대통령실의 누리집 주소는 www.대통령.kr이다.", vec![])] + #[case::english_majority_pdf_control("What is 김치 in English?", vec![])] + fn detects_rule39_narrowed_scope_without_using_reference_output( + #[case] input: &str, + #[case] expected: Vec<&str>, + ) { + let actual = korean_majority_roman_sandwich_non_domain_spans(input) + .into_iter() + .map(|span| &input[span.start_byte..span.end_byte]) + .collect::>(); + assert_eq!(actual, expected); } #[rstest::rstest] diff --git a/libs/braillify/src/rules/token_rules/english_dominant_korean_wrap.rs b/libs/braillify/src/rules/token_rules/english_dominant_korean_wrap.rs index b43c55cc..88337913 100644 --- a/libs/braillify/src/rules/token_rules/english_dominant_korean_wrap.rs +++ b/libs/braillify/src/rules/token_rules/english_dominant_korean_wrap.rs @@ -79,6 +79,13 @@ fn is_punct_only(chars: &[char]) -> bool { .all(|c| !c.is_ascii_alphabetic() && !is_korean_char(*c) && !c.is_ascii_digit()) } +fn same_token_rule39_context_allowed( + is_english_majority: bool, + dot_delimited_domain_label: bool, +) -> bool { + is_english_majority || dot_delimited_domain_label +} + /// 같은 토큰 내에서 좌측을 거슬러 처음 만나는 letter가 ASCII 영문인지. /// 한글을 먼저 만나거나, 영문도 한글도 없으면 false. fn same_token_left_is_english(left_chars: &[char]) -> bool { @@ -295,8 +302,9 @@ pub fn compute_document_summary(tokens: &[Token<'_>]) -> DocumentSummary { /// 예: "김치", "반찬)". 인접 word token이 모두 영어이면서 _문서 전체가 영어 다수_ /// 일 때만 wrap. (한글 주도 문장에 영어가 끼인 경우는 wrap 대상 아님.) /// 2. **양쪽 토큰 내부** — segment의 양쪽이 같은 토큰 내 영어 letter로 둘러싸였다. -/// 예: "www.대통령.kr"의 "대통령". 양쪽이 영어 letter이면 wrap. -/// (단일 단어 내부 패턴은 문서 비율과 무관하게 항상 적용한다.) +/// 문서가 영어 다수이면 wrap한다. 한국어 주도 문장에서는 제39항을 임의로 +/// 확장하지 않고, 공식 예제 `www.대통령.kr`처럼 양쪽이 점으로 구분된 도메인 +/// label만 구조적으로 보존한다. /// /// 두 케이스가 _혼합_된 경우(한쪽은 token boundary, 다른 쪽은 same-token letter)는 /// 영어 어절 + 한국어 조사/어미 결합(예: "be는")일 가능성이 높으므로 wrap하지 않는다. @@ -317,7 +325,12 @@ fn segment_in_english_context_with_majority<'a>( return boundary_segment_wrap(tokens, token_index, is_english_majority); } if !left_at_boundary && !right_at_boundary { - return same_token_left_is_english(left_slice) && same_token_right_is_english(right_slice); + let has_roman_on_both_sides = + same_token_left_is_english(left_slice) && same_token_right_is_english(right_slice); + let dot_delimited_domain_label = + left_slice.last() == Some(&'.') && right_slice.first() == Some(&'.'); + return has_roman_on_both_sides + && same_token_rule39_context_allowed(is_english_majority, dot_delimited_domain_label); } false } @@ -534,22 +547,47 @@ mod tests { ); } - /// english_dominant_korean_wrap:316 — `(false, false) =>` arm of the boundary - /// match. Korean segment is sandwiched within same-token English letters. - /// Both `left_at_boundary` and `right_at_boundary` are false because the - /// surrounding chars include English letters. + /// Rule 39's same-token branch still requires a Roman-main document, except + /// for the PDF's dot-delimited `www.대통령.kr` domain structure. #[test] - fn segment_within_same_token_english_letters() { - // "www.대통령.kr" — Korean chars '대통령' surrounded by 'w'/'k' letters - // (separated by '.'). same_token_*_is_english returns true on both sides. + fn pdf_domain_label_wraps_without_english_majority() { let token = word("www.대통령.kr"); let tokens = vec![token.clone()]; let kor_word = unwrap_word(&tokens[0]); let result = build_wrapped_replacement(kor_word, &tokens, 0, false); - // Inner same-token English context should wrap regardless of majority. + assert!(result.is_some()); + } + + #[rstest::rstest] + #[case::english_majority(true, false, true)] + #[case::dot_delimited_domain(false, true, true)] + #[case::neither(false, false, false)] + fn same_token_rule39_gate_requires_dominance_or_domain( + #[case] is_english_majority: bool, + #[case] dot_delimited_domain_label: bool, + #[case] expected: bool, + ) { + assert_eq!( + same_token_rule39_context_allowed(is_english_majority, dot_delimited_domain_label), + expected + ); + } + + #[rstest::rstest] + #[case::question("What is 김치 in English?")] + #[case::domain("대통령실의 누리집 주소는 www.대통령.kr이다.")] + #[case::definition( + "Banchan (Korean: 반찬) are small side dishes served along with cooked rice in Korean cuisine." + )] + fn full_encoder_preserves_rule39_pdf_controls(#[case] input: &str) { + let actual = crate::encode_to_unicode(input).expect("rule 39 PDF example must encode"); assert!( - result.is_some(), - "Korean segment within same-token English letters should wrap" + actual.contains("⠸⠷"), + "missing Korean opening marker: {actual}" + ); + assert!( + actual.contains("⠸⠾"), + "missing Korean closing marker: {actual}" ); } From f69577694f9bb0fb7597dd40b80ea5272adf39f7 Mon Sep 17 00:00:00 2001 From: devfive Date: Tue, 1 Sep 2026 14:23:15 +0900 Subject: [PATCH 42/77] Diagnose uppercase Roman digit sequence routing --- docs/corpus-analysis/NIKL_2025_V1.md | 163 ++++++++++++---- .../braillify/examples/nikl_corpus_analyze.rs | 183 +++++++++++++++++- 2 files changed, 307 insertions(+), 39 deletions(-) diff --git a/docs/corpus-analysis/NIKL_2025_V1.md b/docs/corpus-analysis/NIKL_2025_V1.md index 2489b61c..b6d9f198 100644 --- a/docs/corpus-analysis/NIKL_2025_V1.md +++ b/docs/corpus-analysis/NIKL_2025_V1.md @@ -298,11 +298,11 @@ This ranking removes only cases whose first difference is inside an existing out | Rank | Expected → actual first cell | Residual cases | |---:|---|---:| -| 1 | `U+2834 ⠴ -> U+2800 ⠀` | 266 | -| 2 | `U+2800 ⠀ -> U+2832 ⠲` | 228 | -| 3 | `U+2826 ⠦ -> U+2800 ⠀` | 226 | -| 4 | `U+2810 ⠐ -> U+2832 ⠲` | 225 | -| 5 | `U+2810 ⠐ -> U+2802 ⠂` | 212 | +| 1 | `U+2800 ⠀ -> U+2832 ⠲` | 228 | +| 2 | `U+2826 ⠦ -> U+2800 ⠀` | 226 | +| 3 | `U+2810 ⠐ -> U+2832 ⠲` | 225 | +| 4 | `U+2810 ⠐ -> U+2802 ⠂` | 212 | +| 5 | `U+2834 ⠴ -> U+2800 ⠀` | 174 | | 6 | `U+2820 ⠠ -> U+2809 ⠉` | 169 | | 7 | `U+2817 ⠗ -> U+2820 ⠠` | 164 | | 8 | `U+283C ⠼ -> U+2800 ⠀` | 153 | @@ -311,36 +311,13 @@ This ranking removes only cases whose first difference is inside an existing out | 11 | `U+2810 ⠐ -> U+2800 ⠀` | 130 | | 12 | `U+2820 ⠠ -> U+281E ⠞` | 108 | | 13 | `U+2811 ⠑ -> U+283B ⠻` | 101 | -| 14 | `U+2820 ⠠ -> U+280E ⠎` | 100 | -| 15 | `U+2834 ⠴ -> U+2830 ⠰` | 99 | -| 16 | `U+2810 ⠐ -> U+2814 ⠔` | 98 | -| 17 | `U+280E ⠎ -> U+2820 ⠠` | 92 | -| 18 | `U+2803 ⠃ -> U+2820 ⠠` | 87 | -| 19 | `U+2804 ⠄ -> U+2834 ⠴` | 87 | -| 20 | `U+2826 ⠦ -> U+2834 ⠴` | 79 | - -### Residual `U+2834 ⠴ -> U+2800 ⠀` - -- `sentence_01.json` #735: 로이드 오스틴 미국 국방성 장관이 30일 오후 일명 ‘최후 심판의 날 항공기’로 불리는 E-4B 나이트워치(NightWatch)를 타고 방한했다. - - expected: `⠀⠘⠯⠐⠕⠉⠵⠀⠴⠠⠑⠤⠼⠙⠠⠃⠲⠀⠉⠣⠕⠓⠪⠏` - - actual: `⠀⠘⠯⠐⠕⠉⠵⠀⠀⠠⠑⠔⠼⠙⠠⠃⠀⠉⠣⠕⠓⠪⠏⠰` - - first differing cell (zero-based): 83 - - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_02.json` #633: 두 학과는 올해 2학기부터 지방대학 특성화 사업 및 마이크로디그리(MD) 융합전공인 ‘빅데이터 기반 영상문화콘텐츠 개발’ 과정을 함께 운영하고 있으며, LINC3.0 사업에도 참여하고 있다. - - expected: `⠀⠕⠌⠪⠑⠱⠐⠀⠴⠠⠠⠇⠔⠉⠼⠉⠲⠚⠀⠇⠎⠃⠝⠊` - - actual: `⠀⠕⠌⠪⠑⠱⠐⠀⠀⠠⠠⠇⠊⠝⠉⠼⠉⠲⠚⠀⠀⠇⠎⠃` - - first differing cell (zero-based): 148 - - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_03.json` #1285: 한편, 디앤지컴퍼니본은 테마파크와 외식시설 개발운영에 특화된 종합 테마파크 기업이다. 월미테마파크를 운영하고 있으며 너티차일드 키즈테마파크, 트라이아스 패밀리레스토랑, C27, 도넛킬러, 번트커피 등 다양한 F&B(식음) 브랜드를 보유하고 있다. - - expected: `⠪⠓⠥⠐⠣⠶⠐⠀⠴⠠⠉⠼⠃⠛⠐⠀⠊⠥⠉⠎⠄⠋⠕⠂` - - actual: `⠪⠓⠥⠐⠣⠶⠐⠀⠀⠠⠉⠼⠃⠛⠐⠀⠀⠊⠥⠉⠎⠄⠋⠕` - - first differing cell (zero-based): 152 - - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_04.json` #1633: KAI는 이번 ‘LIMA 2023’에 말레이시아 공군이 운용할 FA-50M 버전과 함께 세계 시장이 관심을 갖는 국산 초음속 전투기 KF-21, 수리온 헬기, 소형무장헬기(LAH) 등 주력 제품을 전시했다. - - expected: `⠕⠀⠛⠬⠶⠚⠂⠀⠴⠠⠠⠋⠁⠤⠼⠑⠚⠠⠍⠲⠀⠘⠎⠨` - - actual: `⠕⠀⠛⠬⠶⠚⠂⠀⠀⠠⠠⠋⠁⠔⠼⠑⠚⠠⠍⠀⠀⠘⠎⠨` - - first differing cell (zero-based): 54 - - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +| 14 | `U+2810 ⠐ -> U+2814 ⠔` | 98 | +| 15 | `U+2820 ⠠ -> U+280E ⠎` | 98 | +| 16 | `U+280E ⠎ -> U+2820 ⠠` | 92 | +| 17 | `U+2803 ⠃ -> U+2820 ⠠` | 87 | +| 18 | `U+2804 ⠄ -> U+2834 ⠴` | 87 | +| 19 | `U+2826 ⠦ -> U+2834 ⠴` | 79 | +| 20 | `U+2824 ⠤ -> U+2800 ⠀` | 78 | ### Residual `U+2800 ⠀ -> U+2832 ⠲` @@ -434,6 +411,29 @@ This ranking removes only cases whose first difference is inside an existing out - first differing cell (zero-based): 90 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +### Residual `U+2834 ⠴ -> U+2800 ⠀` + +- `sentence_01.json` #947: 코나체인은 코나아이와 함께 한국은행의 디지털 화폐(CBDC)의 H/W Wallet의 개발에 참여하였고, 현재 블록체인 기반의 설문조사, 투표서비스를 제공 중이다. + - expected: `⠉⠃⠙⠉⠠⠴⠺⠀⠴⠠⠓⠸⠌⠠⠺⠀⠠⠺⠁⠇⠇⠑⠞⠲` + - actual: `⠉⠃⠙⠉⠠⠴⠺⠀⠀⠠⠓⠸⠌⠠⠺⠀⠴⠠⠺⠁⠇⠇⠑⠞` + - first differing cell (zero-based): 57 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_02.json` #865: ‘2023 삼성 AI/CE 챌린지’는 인공지능(AI)과 컴퓨터 공학(CE) 분야 국내 우수 인력 발굴과 차세대 반도체 연구 역량 강화를 위해 개최되고 있으며, 2021년을 시작으로 3회째를 맞이했다. + - expected: `⠃⠉⠀⠇⠢⠠⠻⠀⠴⠠⠠⠁⠊⠸⠌⠠⠠⠉⠑⠲⠀⠰⠗⠂` + - actual: `⠃⠉⠀⠇⠢⠠⠻⠀⠀⠠⠠⠁⠊⠸⠌⠠⠠⠉⠑⠀⠰⠗⠂⠐` + - first differing cell (zero-based): 13 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_03.json` #2455: 한편, AI+ 인증은 한국표준협회(KSA)가 국제표준화기구(ISO, International Organization for Standardization)와 국제전기기술위원회(IEC, International Electrotechnical Commission) 등 국제표준에 근거해 인공지능(AI) 기술이 적용된 제품과 소프 + - expected: `⠚⠒⠙⠡⠐⠀⠴⠠⠠⠁⠊⠐⠖⠲⠀⠟⠨⠪⠶⠵⠀⠚⠒⠈` + - actual: `⠚⠒⠙⠡⠐⠀⠀⠠⠠⠁⠊⠢⠀⠀⠟⠨⠪⠶⠵⠀⠚⠒⠈⠍` + - first differing cell (zero-based): 6 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_04.json` #2250: RX 시리즈의 전동화 버전도 내놨다. 하이브리드 모델인 RX350h, 플러그인하이브리드(PHEV) 모델인 RX450h+, 터보 엔진 하이브리드 모델(터보 HEV)인 RX500h F 스포츠 퍼포먼스 등 총 3가지다. + - expected: `⠀⠑⠥⠊⠝⠂⠟⠀⠴⠠⠠⠗⠭⠼⠉⠑⠚⠰⠓⠐⠀⠙⠮⠐` + - actual: `⠀⠑⠥⠊⠝⠂⠟⠀⠀⠠⠠⠗⠭⠼⠉⠑⠚⠓⠐⠀⠙⠮⠐⠎` + - first differing cell (zero-based): 54 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` + ### Residual `U+2820 ⠠ -> U+2809 ⠉` - `sentence_01.json` #45: 2019년 드링크인터내셔널 합류 이후 최고 운영 책임자(COO)를 역임하며 자회사 인터리커를 통해 와인 시장 진출 등 사업 확장을 진두지휘했다. @@ -551,7 +551,7 @@ This ranking removes only cases whose first difference is inside an existing out ## Cross-cutting input-only structural cohorts -These are cross-cutting input-only structural cohorts, not new primary classes and not engine routing rules. Candidate selection never changes a case's existing primary class by itself. Only cohort members already classified as `pending_rule_review` form a pending subcluster; exact and other-primary members are controls that retain their existing outcomes. A separate classifier may use independently justified, output-localized PDF evidence, as in the rule-34 three-cell contradiction below. The `uppercase_roman_headword_closed_multiword_parenthetical` gate requires a two-or-more character uppercase ASCII headword immediately followed by a closed parenthesis whose contents are two or more ASCII Roman words separated only by spaces. Because the contents admit only letters and spaces, visible operators, subscript/superscript notation, and nested parentheses are excluded deterministically. The `mixed_roman_korean_word_before_uppercase_headword_expansion` gate further requires a whitespace-delimited preceding word that contains both ASCII Roman and Korean and ends in Korean. It locates the following uppercase headword itself, including its immediately preceding emitted cell, so an earlier Roman entry in the same sentence cannot satisfy the output audit. The `single_capital_followed_by_parenthesized_digits` gate requires a standalone capital, a non-empty closed ASCII-digit parenthetical, and alphanumeric outer boundaries. It likewise includes the emitted entry-boundary cell in localization. The `uppercase_roman_run_followed_by_hyphen_digits` gate requires a maximal uppercase ASCII run, literal ASCII hyphen-minus, a non-empty digit run, and alphanumeric outer boundaries. Its localized range includes the current encoded run and its immediately preceding cell, keeping `F-35`-style routing separate from both parenthesized digits and headword expansions. The `standalone_multi_character_uppercase_roman_word` gate finds maximal ASCII-letter runs of two or more capitals with non-alphanumeric boundaries; a run immediately followed by `(` is excluded so the HCA-style headword itself is not counted by both gates. The `korean_prefixed_closed_allcaps_parenthetical` gate requires an immediately preceding Korean character and a closed body of two or more uppercase ASCII letters. It intentionally contains both acronym annotations (`책임자(COO)`) and scientific formulae (`일산화탄소(CO)`) so their semantic collision remains measurable. The `korean_prefixed_closed_roman_annotation_rule_34_order` gate accepts the narrower rule-34 body grammar after an immediately preceding Korean character and localizes only the current engine's Korean opening-parenthesis cells. This distinguishes the PDF's parenthesis-before-Roman-indicator order from unrelated differences later in the same sentence. The `multi_character_allcaps_roman_runs_joined_by_middle_dot` gate requires two maximal ASCII-letter runs of at least two capitals joined directly by U+00B7, with non-alphanumeric outer boundaries. It records shapes such as `AI·SW` without assigning prose, mathematics, or science semantics. The `roman_run_immediately_before_attached_middle_dot_boundary` gate is output-localized: it requires a maximal ASCII-letter run immediately before U+00B7 and an attached Korean character or ASCII-letter run after it, then searches for that whole current-engine signature in the actual output. It therefore isolates the Roman terminator boundary without treating unrelated middle dots elsewhere in the sentence as causal. The `attached_ascii_roman_to_korean_script_boundary` gate requires an ASCII letter run immediately followed by Korean script and localizes only the current mode marker there: either rule 29's final `⠲` or rule 39's opening `⠸⠷`. It deliberately does not infer the dominant language of the sentence from that surface boundary. The `korean_majority_same_token_roman_sandwich_non_domain` scope audit mirrors the narrower rule-39 implementation gate: the nearest script characters on both sides of one Korean segment are ASCII Roman, the input is Korean-majority by first-script word counts, and the segment is not dot-delimited like the official domain example. It measures change scope but is not itself an output-localized causal classifier. The `korean_inline_parenthesized_single_arithmetic_operator` gate requires an immediate `Korean(` + one rule-45 arithmetic operator + `)Korean` span. Unlike broad coexistence traits, it also locates the current engine's emitted structure and counts a mismatch as signature-local only when the sentence's first differing cell falls inside that output range. The `allcaps_roman_run_containing_ou` gate finds maximal, alphanumeric-delimited uppercase ASCII runs containing adjacent `OU`. It locates the independently encoded run signature in the complete current output and counts only first differences inside that signature as localized. The `allcaps_roman_run_containing_st` gate applies the same output-position requirement to adjacent `ST`; it is kept separate because UEB 10.12 makes contraction use depend on how an abbreviation or acronym is pronounced. The `attached_ascii_roman_segments_joined_by_ampersand` gate requires non-empty ASCII-letter segments joined directly by `&`, excludes spaced/Korean/alphanumeric continuations, and localizes only the current output cell immediately before the ampersand through an independently encoded real-input prefix. The `decimal_point_between_ascii_digits` gate finds whitespace-delimited words containing `digit.digit` and reproduces each whole word in a neutral Korean context, so suffixes and punctuation remain part of the current-engine signature. The `compact_numeric_ascii_letter_suffix` gate finds a numeric prefix immediately followed by ASCII letters, includes the immediately preceding output cell as its entry boundary, and retains suffix-specific outcome counts. It intentionally includes both possible rule-69 units and ambiguous variable/identifier forms; membership alone does not assign unit semantics. The `rule69_ascii_unit_before_terminator_skipping_symbol` gate is narrower: it accepts only ASCII unit spellings already supported by rule 69, includes the immediately following rule-33/34 punctuation cell in the localized signature, and does not infer new units. The `tight_triangle_mark_immediately_before_korean` gate requires literal `△한글` with no input space and includes the first following Korean cell in its localized output range, so an observed missing-space difference is measured at the mark boundary. +These are cross-cutting input-only structural cohorts, not new primary classes and not engine routing rules. Candidate selection never changes a case's existing primary class by itself. Only cohort members already classified as `pending_rule_review` form a pending subcluster; exact and other-primary members are controls that retain their existing outcomes. A separate classifier may use independently justified, output-localized PDF evidence, as in the rule-34 three-cell contradiction below. The `uppercase_roman_headword_closed_multiword_parenthetical` gate requires a two-or-more character uppercase ASCII headword immediately followed by a closed parenthesis whose contents are two or more ASCII Roman words separated only by spaces. Because the contents admit only letters and spaces, visible operators, subscript/superscript notation, and nested parentheses are excluded deterministically. The `mixed_roman_korean_word_before_uppercase_headword_expansion` gate further requires a whitespace-delimited preceding word that contains both ASCII Roman and Korean and ends in Korean. It locates the following uppercase headword itself, including its immediately preceding emitted cell, so an earlier Roman entry in the same sentence cannot satisfy the output audit. The `single_capital_followed_by_parenthesized_digits` gate requires a standalone capital, a non-empty closed ASCII-digit parenthetical, and alphanumeric outer boundaries. It likewise includes the emitted entry-boundary cell in localization. The `uppercase_roman_run_followed_by_hyphen_digits` gate requires a maximal uppercase ASCII run, literal ASCII hyphen-minus, a non-empty digit run, and alphanumeric outer boundaries. Its localized range includes the current encoded run and its immediately preceding cell, keeping `F-35`-style routing separate from both parenthesized digits and headword expansions. The `uppercase_alphanumeric_roman_digit_sequence` gate covers the wider rule-35/math collision: a capital-led uppercase/digit sequence with optional internal ASCII hyphen/full stop and trailing prose comma/colon/semicolon. It localizes only the current entry cells and does not decide whether an `A-3`-shaped surface is an identifier or a mathematical expression. The `standalone_multi_character_uppercase_roman_word` gate finds maximal ASCII-letter runs of two or more capitals with non-alphanumeric boundaries; a run immediately followed by `(` is excluded so the HCA-style headword itself is not counted by both gates. The `korean_prefixed_closed_allcaps_parenthetical` gate requires an immediately preceding Korean character and a closed body of two or more uppercase ASCII letters. It intentionally contains both acronym annotations (`책임자(COO)`) and scientific formulae (`일산화탄소(CO)`) so their semantic collision remains measurable. The `korean_prefixed_closed_roman_annotation_rule_34_order` gate accepts the narrower rule-34 body grammar after an immediately preceding Korean character and localizes only the current engine's Korean opening-parenthesis cells. This distinguishes the PDF's parenthesis-before-Roman-indicator order from unrelated differences later in the same sentence. The `multi_character_allcaps_roman_runs_joined_by_middle_dot` gate requires two maximal ASCII-letter runs of at least two capitals joined directly by U+00B7, with non-alphanumeric outer boundaries. It records shapes such as `AI·SW` without assigning prose, mathematics, or science semantics. The `roman_run_immediately_before_attached_middle_dot_boundary` gate is output-localized: it requires a maximal ASCII-letter run immediately before U+00B7 and an attached Korean character or ASCII-letter run after it, then searches for that whole current-engine signature in the actual output. It therefore isolates the Roman terminator boundary without treating unrelated middle dots elsewhere in the sentence as causal. The `attached_ascii_roman_to_korean_script_boundary` gate requires an ASCII letter run immediately followed by Korean script and localizes only the current mode marker there: either rule 29's final `⠲` or rule 39's opening `⠸⠷`. It deliberately does not infer the dominant language of the sentence from that surface boundary. The `korean_majority_same_token_roman_sandwich_non_domain` scope audit mirrors the narrower rule-39 implementation gate: the nearest script characters on both sides of one Korean segment are ASCII Roman, the input is Korean-majority by first-script word counts, and the segment is not dot-delimited like the official domain example. It measures change scope but is not itself an output-localized causal classifier. The `korean_inline_parenthesized_single_arithmetic_operator` gate requires an immediate `Korean(` + one rule-45 arithmetic operator + `)Korean` span. Unlike broad coexistence traits, it also locates the current engine's emitted structure and counts a mismatch as signature-local only when the sentence's first differing cell falls inside that output range. The `allcaps_roman_run_containing_ou` gate finds maximal, alphanumeric-delimited uppercase ASCII runs containing adjacent `OU`. It locates the independently encoded run signature in the complete current output and counts only first differences inside that signature as localized. The `allcaps_roman_run_containing_st` gate applies the same output-position requirement to adjacent `ST`; it is kept separate because UEB 10.12 makes contraction use depend on how an abbreviation or acronym is pronounced. The `attached_ascii_roman_segments_joined_by_ampersand` gate requires non-empty ASCII-letter segments joined directly by `&`, excludes spaced/Korean/alphanumeric continuations, and localizes only the current output cell immediately before the ampersand through an independently encoded real-input prefix. The `decimal_point_between_ascii_digits` gate finds whitespace-delimited words containing `digit.digit` and reproduces each whole word in a neutral Korean context, so suffixes and punctuation remain part of the current-engine signature. The `compact_numeric_ascii_letter_suffix` gate finds a numeric prefix immediately followed by ASCII letters, includes the immediately preceding output cell as its entry boundary, and retains suffix-specific outcome counts. It intentionally includes both possible rule-69 units and ambiguous variable/identifier forms; membership alone does not assign unit semantics. The `rule69_ascii_unit_before_terminator_skipping_symbol` gate is narrower: it accepts only ASCII unit spellings already supported by rule 69, includes the immediately following rule-33/34 punctuation cell in the localized signature, and does not infer new units. The `tight_triangle_mark_immediately_before_korean` gate requires literal `△한글` with no input space and includes the first following Korean cell in its localized output range, so an observed missing-space difference is measured at the mark boundary. | Cluster | Candidates | Exact | Mismatch | Conflicting-reference cases | |---|---:|---:|---:|---:| @@ -581,6 +581,7 @@ These are cross-cutting input-only structural cohorts, not new primary classes a | `single_capital_followed_by_parenthesized_digits` | 1361 | 0 | 1361 | 0 | | `standalone_multi_character_uppercase_roman_word` | 62411 | 50395 | 12016 | 0 | | `tight_triangle_mark_immediately_before_korean` | 377 | 0 | 377 | 0 | +| `uppercase_alphanumeric_roman_digit_sequence` | 3429 | 1908 | 1521 | 0 | | `uppercase_ascii_run_immediately_after_digit_in_roman_sequence` | 1896 | 1100 | 796 | 0 | | `uppercase_ascii_run_immediately_after_hyphen_in_roman_sequence` | 952 | 163 | 789 | 0 | | `uppercase_roman_headword_closed_multiword_parenthetical` | 175 | 16 | 159 | 0 | @@ -2323,6 +2324,92 @@ Representative `mismatch` samples: - first differing cell (zero-based): 32 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +### `uppercase_alphanumeric_roman_digit_sequence` + +Of the 3429 candidates, 1413 are the actual `pending_rule_review` subcluster. The other 2016 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. + +For this output-signature audit, 1521 mismatches were evaluable and 162 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. + +Localized first-difference transitions: + +- `U+2834 ⠴ -> U+2800 ⠀`: 92 +- `U+2834 ⠴ -> U+2830 ⠰`: 52 +- `U+2820 ⠠ -> U+2803 ⠃`: 2 +- `U+2820 ⠠ -> U+280E ⠎`: 2 +- `U+2820 ⠠ -> U+2811 ⠑`: 2 + +Mismatch primary-class distribution: + +- `comparison_method`: 13 +- `corpus_suspect`: 86 +- `pending_rule_review`: 1413 +- `unsupported_character_review`: 9 + +Representative `exact` samples: + +- `sentence_01.json` #25: 세븐일레븐은 올해 말 정식 버전 오픈을 목표로 하며, 이를 통해 주력상품을 구매하고 배송까지 받을 수 있는 차세대 오프라인을 위한 온라인(O4O) 시스템을 구축해 나갈 계획이다. + - expected: `⠠⠝⠘⠵⠕⠂⠐⠝⠘⠵⠵⠀⠥⠂⠚⠗⠀⠑⠂⠀⠨⠻⠠⠕` + - actual: `⠠⠝⠘⠵⠕⠂⠐⠝⠘⠵⠵⠀⠥⠂⠚⠗⠀⠑⠂⠀⠨⠻⠠⠕` + - current primary/reason: `exact` / `exact` +- `sentence_02.json` #167: 2023년 제1회 상담사례 워크숍은 정신건강 임상심리사인 김한우 수퍼바이저(월덴3 아카데미 대표)가 ‘기질 및 성격검사(TCI), 미네소타 다면적 인성 검사(MMPI-2), 문장완성검사(SCT) 활용을 위한 심리평가 슈퍼비전’이라는 주제로 진행하였다. + - expected: `⠼⠃⠚⠃⠉⠀⠉⠡⠀⠨⠝⠼⠁⠀⠚⠽⠀⠇⠶⠊⠢⠇⠐⠌` + - actual: `⠼⠃⠚⠃⠉⠀⠉⠡⠀⠨⠝⠼⠁⠀⠚⠽⠀⠇⠶⠊⠢⠇⠐⠌` + - current primary/reason: `exact` / `exact` +- `sentence_03.json` #69: 그룹 아스트로 멤버 문빈(25)이 갑작스럽게 세상을 떠난 데 대한 연예계 추모 물결이 이어지고 있는 가운데, 그가 생전 활약했던 주요 무대인 KBS2 ‘뮤직뱅크’ 측도 애도에 동참한다. + - expected: `⠈⠪⠐⠍⠃⠀⠣⠠⠪⠓⠪⠐⠥⠀⠑⠝⠢⠘⠎⠀⠑⠛⠘⠟` + - actual: `⠈⠪⠐⠍⠃⠀⠣⠠⠪⠓⠪⠐⠥⠀⠑⠝⠢⠘⠎⠀⠑⠛⠘⠟` + - current primary/reason: `exact` / `exact` +- `sentence_04.json` #41: 마쓰노 장관은 5월 히로시마 주요 7개국(G7) 정상회의에 윤 대통령을 초청할 것인지에 대해서는 “초청국에 대해서는 현재 검토 중이며 아무것도 결정되지 않았다”고 밝혔다. + - expected: `⠑⠠⠠⠪⠉⠥⠀⠨⠶⠈⠧⠒⠵⠀⠼⠑⠏⠂⠀⠚⠕⠐⠥⠠` + - actual: `⠑⠠⠠⠪⠉⠥⠀⠨⠶⠈⠧⠒⠵⠀⠼⠑⠏⠂⠀⠚⠕⠐⠥⠠` + - current primary/reason: `exact` / `exact` + +Representative `localized_mismatch` samples: + +- `sentence_01.json` #735: 로이드 오스틴 미국 국방성 장관이 30일 오후 일명 ‘최후 심판의 날 항공기’로 불리는 E-4B 나이트워치(NightWatch)를 타고 방한했다. + - expected: `⠀⠘⠯⠐⠕⠉⠵⠀⠴⠠⠑⠤⠼⠙⠠⠃⠲⠀⠉⠣⠕⠓⠪⠏` + - actual: `⠀⠘⠯⠐⠕⠉⠵⠀⠀⠠⠑⠔⠼⠙⠠⠃⠀⠉⠣⠕⠓⠪⠏⠰` + - first differing cell (zero-based): 83 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_02.json` #633: 두 학과는 올해 2학기부터 지방대학 특성화 사업 및 마이크로디그리(MD) 융합전공인 ‘빅데이터 기반 영상문화콘텐츠 개발’ 과정을 함께 운영하고 있으며, LINC3.0 사업에도 참여하고 있다. + - expected: `⠀⠕⠌⠪⠑⠱⠐⠀⠴⠠⠠⠇⠔⠉⠼⠉⠲⠚⠀⠇⠎⠃⠝⠊` + - actual: `⠀⠕⠌⠪⠑⠱⠐⠀⠀⠠⠠⠇⠊⠝⠉⠼⠉⠲⠚⠀⠀⠇⠎⠃` + - first differing cell (zero-based): 148 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_03.json` #114: 르노코리아자동차가 다재다능한 신개념 2인승 스포츠유틸리티차량(SUV) ‘QM6 퀘스트’ 활용 방안에 대한 고객 아이디어를 수렴한다고 21일 밝혔다. + - expected: `⠎⠥⠧⠠⠴⠀⠠⠦⠴⠠⠠⠟⠍⠼⠋⠀⠋⠏⠗⠠⠪⠓⠪⠴` + - actual: `⠎⠥⠧⠠⠴⠀⠠⠦⠰⠠⠠⠟⠍⠼⠋⠀⠋⠏⠗⠠⠪⠓⠪⠴` + - first differing cell (zero-based): 73 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_04.json` #1633: KAI는 이번 ‘LIMA 2023’에 말레이시아 공군이 운용할 FA-50M 버전과 함께 세계 시장이 관심을 갖는 국산 초음속 전투기 KF-21, 수리온 헬기, 소형무장헬기(LAH) 등 주력 제품을 전시했다. + - expected: `⠕⠀⠛⠬⠶⠚⠂⠀⠴⠠⠠⠋⠁⠤⠼⠑⠚⠠⠍⠲⠀⠘⠎⠨` + - actual: `⠕⠀⠛⠬⠶⠚⠂⠀⠀⠠⠠⠋⠁⠔⠼⠑⠚⠠⠍⠀⠀⠘⠎⠨` + - first differing cell (zero-based): 54 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` + +Representative `mismatch` samples: + +- `sentence_01.json` #17: 지난달 30일(현지시각) 뉴욕증권거래소(NYSE)에서 다우존스30산업평균지수는 전 거래일보다 73.55포인트(0.22%) 하락한 3만3147.25로 마감했다. 대형주 중심의 S&P500지수는 9.78포인트(0.25%) 하락한 3839.50으로, 기술주 중심의 나스닥지수는 11.61포인트(0.11%) 하락한 1만0466.4 + - expected: `⠶⠠⠕⠢⠺⠀⠴⠠⠎⠈⠯⠠⠏⠼⠑⠚⠚⠨⠕⠠⠍⠉⠵⠀` + - actual: `⠶⠠⠕⠢⠺⠀⠴⠠⠠⠎⠲⠴⠈⠯⠲⠴⠏⠼⠑⠚⠚⠨⠕⠠` + - first differing cell (zero-based): 168 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_02.json` #324: 남녀단식 TT1 ~ TT10(지체), T11(지적), DF(청각) 등 12개 세부 종목에서 랭킹 포인트 60점을 걸고 승부를 펼친 결과 총 24명의 우승자가 탄생했다. + - expected: `⠀⠴⠠⠠⠞⠞⠼⠁⠈⠔⠠⠠⠞⠞⠼⠁⠚⠦⠄⠨⠕⠰⠝⠠` + - actual: `⠀⠴⠠⠠⠞⠞⠼⠁⠀⠈⠔⠀⠴⠠⠠⠞⠞⠼⠁⠚⠦⠄⠨⠕` + - first differing cell (zero-based): 17 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_03.json` #64: SK에코플랜트는 20일 종로구 수송동 본사에서 현대엔지니어링, USNC와 ‘수소 마이크로 허브(H2 Micro Hub)’ 구축을 위한 3자간 업무협약(MOU)을 체결했다고 밝혔다. + - expected: `⠪⠐⠥⠀⠚⠎⠘⠪⠦⠄⠴⠠⠓⠼⠃⠀⠠⠍⠊⠉⠗⠕⠀⠠` + - actual: `⠪⠐⠥⠀⠚⠎⠘⠪⠀⠀⠦⠠⠓⠼⠃⠀⠴⠠⠍⠊⠉⠗⠕⠀` + - first differing cell (zero-based): 91 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_04.json` #323: 사단급 규모의 상륙군과 대형수송함(LPH) 독도함, 미 강습상륙함(LHD) 마킨 아일랜드함 등 30여척, 미 F-35 전투기·육군 아파치 공격헬기(AH-64)·마린온 상륙기동헬기 등 항공기 70여대, 상륙돌격장갑차(KAAV) 50여대 등 연합·합동전력이 투입됐다. + - expected: `⠱⠰⠹⠐⠀⠑⠕⠀⠴⠠⠋⠤⠼⠉⠑⠀⠨⠾⠓⠍⠈⠕⠐⠆` + - actual: `⠱⠰⠹⠐⠀⠑⠕⠀⠀⠠⠋⠔⠼⠉⠑⠀⠨⠾⠓⠍⠈⠕⠐⠆` + - first differing cell (zero-based): 108 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` + ### `uppercase_ascii_run_immediately_after_digit_in_roman_sequence` Of the 1896 candidates, 740 are the actual `pending_rule_review` subcluster. The other 1156 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. @@ -2782,7 +2869,7 @@ The output-localized cohort contains 1022 candidates, 363 exact controls, and 65 Korean rule 29 places one Roman indicator/terminator pair around consecutive Roman items, with `Los Angeles` and `Table of Contents` as its printed multiword examples. Rule 34 separately omits the Roman terminator when Roman text is enclosed by quotation marks or parentheses. Neither printed rule states whether a later Roman run after the enclosure, intervening punctuation, and whitespace is a continuation of that section or a fresh section. The input gate therefore detects only the structural boundary; it does not label the later run as semantically new. -The cohort contains 1093 candidates, 141 exact controls, and 952 mismatches. Existing mismatch primary classes are preserved: 624 `pending_rule_review`, 321 `corpus_suspect`, and 7 `unsupported_character_review`. Of 952 evaluable mismatches, 248 are output-localized to the current later-run signature plus its one leading boundary cell: 242 `U+2834 ⠴ -> U+2830 ⠰` and 0 `U+2830 ⠰ -> U+2834 ⠴`. The target counted 344 raw and 333 residual cases before this cohort; it is now 99 final residual. Across raw and final residual maps, `U+2830 ⠰ -> U+2834 ⠴` is 3 -> 1. Exact controls coexist with both directions, and 321 mismatches are already independently identified corpus contradictions. Without a printed fresh-entry example or semantic enclosure model, changing continuation state would be reference-fitting; this remains a deterministic pending/corpus-review cohort only. Representative shard/index samples are retained above. +The cohort contains 1093 candidates, 141 exact controls, and 952 mismatches. Existing mismatch primary classes are preserved: 624 `pending_rule_review`, 321 `corpus_suspect`, and 7 `unsupported_character_review`. Of 952 evaluable mismatches, 248 are output-localized to the current later-run signature plus its one leading boundary cell: 242 `U+2834 ⠴ -> U+2830 ⠰` and 0 `U+2830 ⠰ -> U+2834 ⠴`. The target counted 344 raw and 333 residual cases before this cohort; it is now 47 final residual. Across raw and final residual maps, `U+2830 ⠰ -> U+2834 ⠴` is 3 -> 1. Exact controls coexist with both directions, and 321 mismatches are already independently identified corpus contradictions. Without a printed fresh-entry example or semantic enclosure model, changing continuation state would be reference-fitting; this remains a deterministic pending/corpus-review cohort only. Representative shard/index samples are retained above. ### Uppercase Roman runs containing `ED` @@ -2806,6 +2893,8 @@ After the change the same cohort contains 802 candidates, 283 exact and 519 mism Current uppercase-Roman hyphen-digits measurement: 571 candidates, 191 exact controls, 380 mismatches, 365 members in the actual `pending_rule_review` subcluster, and 261/380 evaluable mismatches whose first difference is inside the target run plus its entry boundary. It remains distinct from parenthesized digits and headword expansions; no engine change is inferred. +Current uppercase alphanumeric Roman-digit sequence measurement: 3429 candidates, 1908 exact controls, 1521 mismatches, 1413 members in the actual `pending_rule_review` subcluster, and 162/1521 evaluable mismatches whose first difference is localized to the current entry cells. The target `⠴ -> blank` occurs 92 times and the reverse occurs 0 times. Korean rule 35 (2024 Korean-rules PDF pp.29-30, printed pp.23-24) explicitly treats `MP3`, `A4`, `KF94`, and `D-100` as Roman-number sequences. Math rules 11/12 (PDF pp.63-65, printed pp.57-59) separately route mathematical Roman notation without the prose Roman indicator and with two-cell spacing. Official UEB 6.5.1-6.5.2 (2024 UEB PDF p.96, printed p.68) determines numeric/grade-1 continuation only after the surrounding mode has been chosen. Thus surfaces such as `A-3` cannot be assigned identifier or variable semantics from this structure alone. Exact controls and both directions are retained; no engine change is inferred. + Current single-capital parenthesized-digits measurement: 1361 candidates, 0 exact controls, 1361 mismatches, 1357 members in the actual `pending_rule_review` subcluster, and 1342/1361 evaluable mismatches whose first difference is inside the target run plus its entry boundary. No engine change is inferred from the ambiguous prose/function surface form. Current mixed Roman+Korean boundary before uppercase headword-expansion measurement: 10 candidates, 0 exact controls, 10 mismatches, 10 members in the actual `pending_rule_review` subcluster, and 8/10 evaluable mismatches whose first difference is localized to the later headword's entry boundary/output. The detector cannot be satisfied by the earlier Roman entry. No HCA-shaped engine routing rule is introduced. diff --git a/libs/braillify/examples/nikl_corpus_analyze.rs b/libs/braillify/examples/nikl_corpus_analyze.rs index a2abebf8..0a8074b6 100644 --- a/libs/braillify/examples/nikl_corpus_analyze.rs +++ b/libs/braillify/examples/nikl_corpus_analyze.rs @@ -597,6 +597,8 @@ const SINGLE_CAPITAL_PARENTHESIZED_DIGITS: &str = "single_capital_followed_by_pa const MIXED_ROMAN_KOREAN_BEFORE_HEADWORD_EXPANSION: &str = "mixed_roman_korean_word_before_uppercase_headword_expansion"; const UPPERCASE_ROMAN_HYPHEN_DIGITS: &str = "uppercase_roman_run_followed_by_hyphen_digits"; +const UPPERCASE_ALPHANUMERIC_ROMAN_DIGIT_SEQUENCE: &str = + "uppercase_alphanumeric_roman_digit_sequence"; const DECIMAL_POINT_BETWEEN_DIGITS: &str = "decimal_point_between_ascii_digits"; const COMPACT_NUMERIC_ASCII_SUFFIX: &str = "compact_numeric_ascii_letter_suffix"; const RULE69_ASCII_UNIT_BEFORE_TERMINATOR_SKIPPING_SYMBOL: &str = @@ -1912,6 +1914,82 @@ fn uppercase_roman_hyphen_digit_spans(input: &str) -> Vec { spans } +/// Finds a maximal uppercase/digit Roman sequence beginning with a capital, +/// optionally joined by ASCII hyphen-minus or full stop. A trailing prose +/// comma/colon/semicolon is included in the observed boundary. Rule 35 covers +/// the Roman-number reading, while math rules 11/12 leave the same surface +/// potentially ambiguous as a variable expression; this detector assigns +/// neither meaning. +fn uppercase_alphanumeric_roman_digit_spans(input: &str) -> Vec { + let bytes = input.as_bytes(); + let mut spans = Vec::new(); + let mut cursor = 0usize; + while cursor < bytes.len() { + if !bytes[cursor].is_ascii_uppercase() + || input[..cursor] + .chars() + .next_back() + .is_some_and(|previous| previous.is_ascii_alphanumeric()) + { + cursor += input[cursor..] + .chars() + .next() + .expect("cursor must remain on a character boundary") + .len_utf8(); + continue; + } + + let start_byte = cursor; + let mut has_digit = false; + while bytes + .get(cursor) + .is_some_and(|byte| byte.is_ascii_uppercase() || byte.is_ascii_digit()) + { + has_digit |= bytes[cursor].is_ascii_digit(); + cursor += 1; + } + while bytes + .get(cursor) + .is_some_and(|byte| matches!(byte, b'-' | b'.')) + && bytes + .get(cursor + 1) + .is_some_and(|byte| byte.is_ascii_uppercase() || byte.is_ascii_digit()) + { + cursor += 1; + while bytes + .get(cursor) + .is_some_and(|byte| byte.is_ascii_uppercase() || byte.is_ascii_digit()) + { + has_digit |= bytes[cursor].is_ascii_digit(); + cursor += 1; + } + } + let sequence_end = cursor; + if bytes + .get(cursor) + .is_some_and(|byte| matches!(byte, b',' | b':' | b';')) + { + cursor += 1; + } + if has_digit + && input[cursor..] + .chars() + .next() + .is_none_or(|next| !next.is_ascii_alphanumeric()) + { + spans.push(InputSpan { + start_byte, + end_byte: cursor, + }); + } else if cursor == start_byte { + cursor += 1; + } else if cursor > sequence_end { + cursor = sequence_end; + } + } + spans +} + fn first_difference_in_signature_spans( item: &EncodedCase, spans: &[InputSpan], @@ -2073,11 +2151,20 @@ fn first_difference_claimed_before_attached_ascii_roman_to_korean(item: &Encoded first_difference_claimed_before_allcaps_ed(item) || first_difference_in_allcaps_ed_run(item) } -fn first_difference_claimed_by_localized_cohort(item: &EncodedCase) -> bool { +fn first_difference_claimed_before_uppercase_alphanumeric_roman_digit(item: &EncodedCase) -> bool { first_difference_claimed_before_attached_ascii_roman_to_korean(item) || first_difference_at_attached_ascii_roman_to_korean_boundary(item) } +fn first_difference_claimed_by_localized_cohort(item: &EncodedCase) -> bool { + first_difference_claimed_before_uppercase_alphanumeric_roman_digit(item) + || first_difference_at_input_span_entry( + item, + &uppercase_alphanumeric_roman_digit_spans(&item.located.case.input), + 2, + ) +} + /// Input-only candidate gate for acronym expansions such as /// `HCA(Home Connectivity Alliance)`. /// @@ -2999,6 +3086,10 @@ fn analyze( UPPERCASE_ROMAN_HYPHEN_DIGITS.to_string(), PendingRuleReviewClusterStats::default(), ), + ( + UPPERCASE_ALPHANUMERIC_ROMAN_DIGIT_SEQUENCE.to_string(), + PendingRuleReviewClusterStats::default(), + ), ( ALLCAPS_SHORTFORM_PREFIX_COLLISION.to_string(), PendingRuleReviewClusterStats::default(), @@ -3266,6 +3357,19 @@ fn analyze( )), false, ), + ( + UPPERCASE_ALPHANUMERIC_ROMAN_DIGIT_SEQUENCE, + !uppercase_alphanumeric_roman_digit_spans(&item.located.case.input).is_empty(), + Some( + !first_difference_claimed_before_uppercase_alphanumeric_roman_digit(item) + && first_difference_at_input_span_entry( + item, + &uppercase_alphanumeric_roman_digit_spans(&item.located.case.input), + 2, + ), + ), + true, + ), ( ALLCAPS_SHORTFORM_PREFIX_COLLISION, !allcaps_shortform_prefix_spans(&item.located.case.input).is_empty(), @@ -3783,7 +3887,11 @@ fn markdown(report: &AnalysisReport) -> String { run, literal ASCII hyphen-minus, a non-empty digit run, and alphanumeric outer \ boundaries. Its localized range includes the current encoded run and its immediately \ preceding cell, keeping `F-35`-style routing separate from both parenthesized digits and \ - headword expansions. The \ + headword expansions. The `uppercase_alphanumeric_roman_digit_sequence` gate covers the \ + wider rule-35/math collision: a capital-led uppercase/digit sequence with optional \ + internal ASCII hyphen/full stop and trailing prose comma/colon/semicolon. It localizes \ + only the current entry cells and does not decide whether an `A-3`-shaped surface is an \ + identifier or a mathematical expression. The \ `standalone_multi_character_uppercase_roman_word` gate finds maximal ASCII-letter runs \ of two or more capitals with non-alphanumeric boundaries; a run immediately followed \ by `(` is excluded so the HCA-style headword itself is not counted by both gates. The \ @@ -4805,6 +4913,46 @@ fn markdown(report: &AnalysisReport) -> String { stats.output_signature_mismatches_evaluated )); } + if let Some(stats) = report + .pending_rule_review_clusters + .get(UPPERCASE_ALPHANUMERIC_ROMAN_DIGIT_SEQUENCE) + { + let pending = stats + .mismatch_primary_classes + .get("pending_rule_review") + .copied() + .unwrap_or(0); + let target = stats + .first_difference_in_output_signature_transitions + .get("U+2834 ⠴ -> U+2800 ⠀") + .copied() + .unwrap_or(0); + let reverse = stats + .first_difference_in_output_signature_transitions + .get("U+2800 ⠀ -> U+2834 ⠴") + .copied() + .unwrap_or(0); + text.push_str(&format!( + "\nCurrent uppercase alphanumeric Roman-digit sequence measurement: {} candidates, \ + {} exact controls, {} mismatches, {pending} members in the actual \ + `pending_rule_review` subcluster, and {}/{} evaluable mismatches whose first \ + difference is localized to the current entry cells. The target `⠴ -> blank` occurs \ + {target} times and the reverse occurs {reverse} times. Korean rule 35 (2024 \ + Korean-rules PDF pp.29-30, printed pp.23-24) explicitly treats `MP3`, `A4`, `KF94`, \ + and `D-100` as Roman-number sequences. Math rules 11/12 (PDF pp.63-65, printed \ + pp.57-59) separately route mathematical Roman notation without the prose Roman \ + indicator and with two-cell spacing. Official UEB 6.5.1-6.5.2 (2024 UEB PDF p.96, \ + printed p.68) determines numeric/grade-1 continuation only after the surrounding \ + mode has been chosen. Thus surfaces such as `A-3` cannot be assigned identifier or \ + variable semantics from this structure alone. Exact controls and both directions \ + are retained; no engine change is inferred.\n", + stats.candidates, + stats.exact, + stats.mismatch, + stats.first_difference_in_output_signature, + stats.output_signature_mismatches_evaluated + )); + } if let Some(stats) = report .pending_rule_review_clusters .get(SINGLE_CAPITAL_PARENTHESIZED_DIGITS) @@ -5899,6 +6047,37 @@ mod tests { assert!(ranges[0].start < ranges[0].end); } + #[rstest::rstest] + #[case::rule35_kf94("요즘에는 KF94 마스크가 필수입니다.", vec!["KF94"])] + #[case::rule35_mp4("새로운 MP4 Player를 출시했다.", vec!["MP4"])] + #[case::rule35_d100("2023학년도 수능 D-100일 학습 전략", vec!["D-100"])] + #[case::corpus_complex_identifier("항공기 E-4B 나이트워치", vec!["E-4B"])] + #[case::decimal_identifier("사업 LINC3.0 참여", vec!["LINC3.0"])] + #[case::trailing_comma("브랜드 C27, 도넛킬러", vec!["C27,"])] + #[case::unicode_minus_control("기종 F−35", vec![])] + fn detects_uppercase_alphanumeric_roman_digit_sequences( + #[case] input: &str, + #[case] expected: Vec<&str>, + ) { + let actual = uppercase_alphanumeric_roman_digit_spans(input) + .into_iter() + .map(|span| &input[span.start_byte..span.end_byte]) + .collect::>(); + assert_eq!(actual, expected); + } + + #[test] + fn localizes_current_complex_identifier_entry_without_using_expected() { + let input = "항공기로 불리는 E-4B 나이트워치"; + let spans = uppercase_alphanumeric_roman_digit_spans(input); + let actual = braillify::encode_to_unicode(input).expect("identifier probe must encode"); + let ranges = current_engine_input_entry_ranges(input, &actual, &spans, 2); + + assert_eq!(spans.len(), 1); + assert_eq!(ranges.len(), 1); + assert_eq!(actual.chars().nth(ranges[0].start), Some('⠀')); + } + #[rstest::rstest] #[case::mixed_particle_before_expansion( "Matter와 HCA(Home Connectivity Alliance) 표준", From 45c0f6aa3bd7b35342a9cd1ce08a1a20af26acfb Mon Sep 17 00:00:00 2001 From: devfive Date: Tue, 1 Sep 2026 14:52:26 +0900 Subject: [PATCH 43/77] Preserve multiword Roman parentheticals --- docs/corpus-analysis/NIKL_2025_V1.md | 500 ++++++++++-------- .../braillify/examples/nikl_corpus_analyze.rs | 243 ++++++++- .../token_rules/math_expression/apply.rs | 88 +++ 3 files changed, 620 insertions(+), 211 deletions(-) diff --git a/docs/corpus-analysis/NIKL_2025_V1.md b/docs/corpus-analysis/NIKL_2025_V1.md index b6d9f198..ee7c9763 100644 --- a/docs/corpus-analysis/NIKL_2025_V1.md +++ b/docs/corpus-analysis/NIKL_2025_V1.md @@ -7,9 +7,9 @@ | Metric | Count | |---|---:| | Total | 83528 | -| Exact | 68101 | -| Mismatch | 15427 | -| Exact accuracy | 81.53% | +| Exact | 68175 | +| Mismatch | 15353 | +| Exact accuracy | 81.62% | | Duplicate records | 0 | | Inputs with conflicting references | 0 | @@ -19,21 +19,21 @@ Primary classes are evidence gates, not permissions to change the engine. `imple | Primary class | Count | |---|---:| -| `comparison_method` | 293 | -| `corpus_suspect` | 1101 | -| `exact` | 68101 | -| `pending_rule_review` | 13855 | -| `unsupported_character_review` | 178 | +| `comparison_method` | 295 | +| `corpus_suspect` | 1107 | +| `exact` | 68175 | +| `pending_rule_review` | 13775 | +| `unsupported_character_review` | 176 | | Reproducible reason | Count | |---|---:| -| `exact` | 68101 | -| `foreign_text_rule_review` | 13680 | -| `nfkc_input_equivalent` | 293 | +| `exact` | 68175 | +| `foreign_text_rule_review` | 13600 | +| `nfkc_input_equivalent` | 295 | | `number_rule_review` | 167 | | `punctuation_rule_review` | 8 | -| `rule34_roman_indicator_before_opening_parenthesis` | 1101 | -| `unsupported_character_review` | 178 | +| `rule34_roman_indicator_before_opening_parenthesis` | 1107 | +| `unsupported_character_review` | 176 | ## Pending first-difference cell transitions @@ -41,24 +41,24 @@ This ranking is a diagnostic selector, not an implementation rule. It counts onl | Rank | Expected → actual first cell | Cases | |---:|---|---:| -| 1 | `U+2834 ⠴ -> U+2800 ⠀` | 2158 | -| 2 | `U+2815 ⠕ -> U+2833 ⠳` | 1335 | -| 3 | `U+2830 ⠰ -> U+2820 ⠠` | 861 | +| 1 | `U+2834 ⠴ -> U+2800 ⠀` | 2162 | +| 2 | `U+2815 ⠕ -> U+2833 ⠳` | 1336 | +| 3 | `U+2830 ⠰ -> U+2820 ⠠` | 862 | | 4 | `U+2810 ⠐ -> U+2832 ⠲` | 669 | -| 5 | `U+2826 ⠦ -> U+2834 ⠴` | 467 | +| 5 | `U+2826 ⠦ -> U+2834 ⠴` | 469 | | 6 | `U+280E ⠎ -> U+280C ⠌` | 448 | | 7 | `U+2826 ⠦ -> U+2800 ⠀` | 440 | | 8 | `U+2801 ⠁ -> U+281C ⠜` | 405 | | 9 | `U+2800 ⠀ -> U+2807 ⠇` | 363 | | 10 | `U+283C ⠼ -> U+2800 ⠀` | 358 | -| 11 | `U+2834 ⠴ -> U+2830 ⠰` | 344 | -| 12 | `U+2811 ⠑ -> U+282B ⠫` | 327 | -| 13 | `U+2800 ⠀ -> U+2832 ⠲` | 259 | -| 14 | `U+2820 ⠠ -> U+2830 ⠰` | 236 | -| 15 | `U+2810 ⠐ -> U+2802 ⠂` | 212 | -| 16 | `U+2820 ⠠ -> U+2809 ⠉` | 169 | -| 17 | `U+2817 ⠗ -> U+2820 ⠠` | 165 | -| 18 | `U+2826 ⠦ -> U+2810 ⠐` | 164 | +| 11 | `U+2834 ⠴ -> U+2830 ⠰` | 347 | +| 12 | `U+2811 ⠑ -> U+282B ⠫` | 329 | +| 13 | `U+2820 ⠠ -> U+2830 ⠰` | 237 | +| 14 | `U+2810 ⠐ -> U+2802 ⠂` | 212 | +| 15 | `U+2820 ⠠ -> U+2809 ⠉` | 169 | +| 16 | `U+2817 ⠗ -> U+2820 ⠠` | 165 | +| 17 | `U+2826 ⠦ -> U+2810 ⠐` | 164 | +| 18 | `U+2800 ⠀ -> U+2832 ⠲` | 154 | | 19 | `U+281B ⠛ -> U+2823 ⠣` | 144 | | 20 | `U+2810 ⠐ -> U+2800 ⠀` | 130 | @@ -298,49 +298,26 @@ This ranking removes only cases whose first difference is inside an existing out | Rank | Expected → actual first cell | Residual cases | |---:|---|---:| -| 1 | `U+2800 ⠀ -> U+2832 ⠲` | 228 | -| 2 | `U+2826 ⠦ -> U+2800 ⠀` | 226 | -| 3 | `U+2810 ⠐ -> U+2832 ⠲` | 225 | -| 4 | `U+2810 ⠐ -> U+2802 ⠂` | 212 | -| 5 | `U+2834 ⠴ -> U+2800 ⠀` | 174 | -| 6 | `U+2820 ⠠ -> U+2809 ⠉` | 169 | -| 7 | `U+2817 ⠗ -> U+2820 ⠠` | 164 | -| 8 | `U+283C ⠼ -> U+2800 ⠀` | 153 | -| 9 | `U+2826 ⠦ -> U+2810 ⠐` | 147 | -| 10 | `U+281B ⠛ -> U+2823 ⠣` | 144 | -| 11 | `U+2810 ⠐ -> U+2800 ⠀` | 130 | -| 12 | `U+2820 ⠠ -> U+281E ⠞` | 108 | -| 13 | `U+2811 ⠑ -> U+283B ⠻` | 101 | +| 1 | `U+2826 ⠦ -> U+2800 ⠀` | 226 | +| 2 | `U+2810 ⠐ -> U+2832 ⠲` | 225 | +| 3 | `U+2810 ⠐ -> U+2802 ⠂` | 212 | +| 4 | `U+2834 ⠴ -> U+2800 ⠀` | 176 | +| 5 | `U+2820 ⠠ -> U+2809 ⠉` | 169 | +| 6 | `U+2817 ⠗ -> U+2820 ⠠` | 164 | +| 7 | `U+283C ⠼ -> U+2800 ⠀` | 153 | +| 8 | `U+2826 ⠦ -> U+2810 ⠐` | 147 | +| 9 | `U+281B ⠛ -> U+2823 ⠣` | 144 | +| 10 | `U+2810 ⠐ -> U+2800 ⠀` | 130 | +| 11 | `U+2820 ⠠ -> U+281E ⠞` | 108 | +| 12 | `U+2811 ⠑ -> U+283B ⠻` | 101 | +| 13 | `U+2800 ⠀ -> U+2832 ⠲` | 100 | | 14 | `U+2810 ⠐ -> U+2814 ⠔` | 98 | | 15 | `U+2820 ⠠ -> U+280E ⠎` | 98 | | 16 | `U+280E ⠎ -> U+2820 ⠠` | 92 | | 17 | `U+2803 ⠃ -> U+2820 ⠠` | 87 | | 18 | `U+2804 ⠄ -> U+2834 ⠴` | 87 | -| 19 | `U+2826 ⠦ -> U+2834 ⠴` | 79 | -| 20 | `U+2824 ⠤ -> U+2800 ⠀` | 78 | - -### Residual `U+2800 ⠀ -> U+2832 ⠲` - -- `sentence_01.json` #119: LCK 글로벌 중계진은 지난 해와 마찬가지로 ‘아틀러스(Atlus)’ 맥스 앤더슨(Max Anderson), ‘발데스(Valdes)’ 브랜든 발데스(Brendan Valdes), ‘울프(Wolf)’ 울프 슈뢰더(Wolf Schroeder)와 ‘크로니클러(Chronicler)’ 모리츠 뮈센(Maurits Meeusen)이 - - expected: `⠵⠦⠄⠴⠠⠍⠁⠭⠀⠠⠯⠻⠎⠕⠝⠠⠴⠐⠀⠠⠦⠘⠂⠊` - - actual: `⠵⠦⠄⠴⠠⠍⠁⠭⠲⠀⠀⠠⠁⠝⠙⠑⠗⠎⠕⠝⠴⠐⠀⠠` - - first differing cell (zero-based): 85 - - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_02.json` #1172: 최근 지속가능성 공시 지침(EU CSRD), 국제지속가능성기준위원회(ISSB) 등에서 스코프 3 배출량 공시를 의무화하고 있는데 이에 대해 국내 조선사들이 공동으로 대응에 나선 셈이다. - - expected: `⠢⠦⠄⠴⠠⠠⠑⠥⠀⠠⠠⠉⠎⠗⠙⠠⠴⠐⠀⠈⠍⠁⠨⠝` - - actual: `⠢⠦⠄⠴⠠⠠⠑⠥⠲⠀⠀⠠⠠⠉⠎⠗⠙⠴⠐⠀⠈⠍⠁⠨` - - first differing cell (zero-based): 33 - - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_03.json` #19: 해당 펀드는 전기차와 2차전지 및 2차전지 연관산업인 ESS /VPP(가상발전소)(14%) 등 전세계 친환경 기술 기업에 투자하는 상품이다. - - expected: `⠟⠀⠴⠠⠠⠑⠎⠎⠀⠸⠌⠠⠠⠧⠏⠏⠦⠄⠫⠇⠶⠘⠂⠨` - - actual: `⠟⠀⠴⠠⠠⠑⠎⠎⠲⠀⠸⠌⠴⠠⠠⠧⠏⠏⠦⠄⠫⠇⠶⠘` - - first differing cell (zero-based): 58 - - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_04.json` #447: 무디스 측은 미국의 반도체 법(CHIPs Act), 인플레이션 감축법(IRA) 등에 대응한 한국 정부와 관련 업계의 중장기 산업 전망과 전략에 대해 관심을 보였다. - - expected: `⠠⠠⠡⠊⠏⠠⠄⠎⠀⠠⠁⠉⠞⠠⠴⠐⠀⠟⠙⠮⠐⠝⠕⠠` - - actual: `⠠⠠⠡⠊⠏⠠⠄⠎⠲⠀⠀⠠⠁⠉⠞⠴⠐⠀⠀⠟⠙⠮⠐⠝` - - first differing cell (zero-based): 40 - - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +| 19 | `U+2824 ⠤ -> U+2800 ⠀` | 78 | +| 20 | `U+2826 ⠦ -> U+2834 ⠴` | 76 | ### Residual `U+2826 ⠦ -> U+2800 ⠀` @@ -549,50 +526,74 @@ This ranking removes only cases whose first difference is inside an existing out - first differing cell (zero-based): 21 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +### Residual `U+2810 ⠐ -> U+2800 ⠀` + +- `sentence_01.json` #761: 샌즈랩은 국내에서 정보보호 분야로 최초이자 유일하게 2021년, 2022년 연속으로 국가신기술(NET) 인증을 획득했다. 또한 신기술로 최근 미국 직접 특허 출원·PCT(Patent Cooperation Treaty)특허 출원을 완료했다. + - expected: `⠁⠚⠎⠀⠰⠯⠏⠒⠐⠆⠴⠠⠠⠏⠉⠞⠐⠣⠠⠏⠁⠞⠢⠞` + - actual: `⠁⠚⠎⠀⠰⠯⠏⠒⠀⠀⠐⠠⠠⠏⠉⠞⠦⠠⠏⠁⠞⠑⠝⠞` + - first differing cell (zero-based): 156 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_02.json` #64: 시에 따르면 미래형 환승센터(MaaS Station) : 철도, 버스를 연계한 기존 환승센터에 도심항공교통(UAM), 자율주행차, 전기·수소차, 개인형 이동수단(PM) 등 미래 모빌리티까지 연계한 환승센터라고 설명했다. + - expected: `⠀⠠⠌⠁⠰⠝⠠⠴⠐⠂⠀⠰⠞⠊⠥⠐⠀⠘⠎⠠⠪⠐⠮⠀` + - actual: `⠀⠠⠌⠁⠰⠝⠠⠴⠀⠐⠂⠀⠰⠞⠊⠥⠐⠀⠘⠎⠠⠪⠐⠮` + - first differing cell (zero-based): 46 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_03.json` #1714: 드림캐쳐는 최근 여덟 번째 미니앨범 ‘아포칼립스 : 프롬 어스(Apocalypse : From us)’를 발매하고 타이틀 ‘본보야지(BONVOYAGE)’로 활발한 활동을 예고했다. + - expected: `⠥⠋⠂⠐⠕⠃⠠⠪⠐⠂⠀⠙⠪⠐⠥⠢⠀⠎⠠⠪⠦⠄⠴⠠` + - actual: `⠥⠋⠂⠐⠕⠃⠠⠪⠀⠐⠂⠀⠙⠪⠐⠥⠢⠀⠎⠠⠪⠦⠄⠴` + - first differing cell (zero-based): 50 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_04.json` #1616: 기독교복음선교회(일명 JMS) 총재 정명석씨(78)의 성폭행 사건 공범인 ‘JMS 2인자’ 정조은씨(본명 김지선·44) 등 조력자들의 변호인들이 잇따라 사임하고 있는 것으로 확인됐다. + - expected: `⠀⠈⠕⠢⠨⠕⠠⠾⠐⠆⠼⠙⠙⠠⠴⠀⠊⠪⠶⠀⠨⠥⠐⠱` + - actual: `⠀⠈⠕⠢⠨⠕⠠⠾⠀⠀⠐⠼⠙⠙⠴⠀⠊⠪⠶⠀⠨⠥⠐⠱` + - first differing cell (zero-based): 112 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` + ## Cross-cutting input-only structural cohorts -These are cross-cutting input-only structural cohorts, not new primary classes and not engine routing rules. Candidate selection never changes a case's existing primary class by itself. Only cohort members already classified as `pending_rule_review` form a pending subcluster; exact and other-primary members are controls that retain their existing outcomes. A separate classifier may use independently justified, output-localized PDF evidence, as in the rule-34 three-cell contradiction below. The `uppercase_roman_headword_closed_multiword_parenthetical` gate requires a two-or-more character uppercase ASCII headword immediately followed by a closed parenthesis whose contents are two or more ASCII Roman words separated only by spaces. Because the contents admit only letters and spaces, visible operators, subscript/superscript notation, and nested parentheses are excluded deterministically. The `mixed_roman_korean_word_before_uppercase_headword_expansion` gate further requires a whitespace-delimited preceding word that contains both ASCII Roman and Korean and ends in Korean. It locates the following uppercase headword itself, including its immediately preceding emitted cell, so an earlier Roman entry in the same sentence cannot satisfy the output audit. The `single_capital_followed_by_parenthesized_digits` gate requires a standalone capital, a non-empty closed ASCII-digit parenthetical, and alphanumeric outer boundaries. It likewise includes the emitted entry-boundary cell in localization. The `uppercase_roman_run_followed_by_hyphen_digits` gate requires a maximal uppercase ASCII run, literal ASCII hyphen-minus, a non-empty digit run, and alphanumeric outer boundaries. Its localized range includes the current encoded run and its immediately preceding cell, keeping `F-35`-style routing separate from both parenthesized digits and headword expansions. The `uppercase_alphanumeric_roman_digit_sequence` gate covers the wider rule-35/math collision: a capital-led uppercase/digit sequence with optional internal ASCII hyphen/full stop and trailing prose comma/colon/semicolon. It localizes only the current entry cells and does not decide whether an `A-3`-shaped surface is an identifier or a mathematical expression. The `standalone_multi_character_uppercase_roman_word` gate finds maximal ASCII-letter runs of two or more capitals with non-alphanumeric boundaries; a run immediately followed by `(` is excluded so the HCA-style headword itself is not counted by both gates. The `korean_prefixed_closed_allcaps_parenthetical` gate requires an immediately preceding Korean character and a closed body of two or more uppercase ASCII letters. It intentionally contains both acronym annotations (`책임자(COO)`) and scientific formulae (`일산화탄소(CO)`) so their semantic collision remains measurable. The `korean_prefixed_closed_roman_annotation_rule_34_order` gate accepts the narrower rule-34 body grammar after an immediately preceding Korean character and localizes only the current engine's Korean opening-parenthesis cells. This distinguishes the PDF's parenthesis-before-Roman-indicator order from unrelated differences later in the same sentence. The `multi_character_allcaps_roman_runs_joined_by_middle_dot` gate requires two maximal ASCII-letter runs of at least two capitals joined directly by U+00B7, with non-alphanumeric outer boundaries. It records shapes such as `AI·SW` without assigning prose, mathematics, or science semantics. The `roman_run_immediately_before_attached_middle_dot_boundary` gate is output-localized: it requires a maximal ASCII-letter run immediately before U+00B7 and an attached Korean character or ASCII-letter run after it, then searches for that whole current-engine signature in the actual output. It therefore isolates the Roman terminator boundary without treating unrelated middle dots elsewhere in the sentence as causal. The `attached_ascii_roman_to_korean_script_boundary` gate requires an ASCII letter run immediately followed by Korean script and localizes only the current mode marker there: either rule 29's final `⠲` or rule 39's opening `⠸⠷`. It deliberately does not infer the dominant language of the sentence from that surface boundary. The `korean_majority_same_token_roman_sandwich_non_domain` scope audit mirrors the narrower rule-39 implementation gate: the nearest script characters on both sides of one Korean segment are ASCII Roman, the input is Korean-majority by first-script word counts, and the segment is not dot-delimited like the official domain example. It measures change scope but is not itself an output-localized causal classifier. The `korean_inline_parenthesized_single_arithmetic_operator` gate requires an immediate `Korean(` + one rule-45 arithmetic operator + `)Korean` span. Unlike broad coexistence traits, it also locates the current engine's emitted structure and counts a mismatch as signature-local only when the sentence's first differing cell falls inside that output range. The `allcaps_roman_run_containing_ou` gate finds maximal, alphanumeric-delimited uppercase ASCII runs containing adjacent `OU`. It locates the independently encoded run signature in the complete current output and counts only first differences inside that signature as localized. The `allcaps_roman_run_containing_st` gate applies the same output-position requirement to adjacent `ST`; it is kept separate because UEB 10.12 makes contraction use depend on how an abbreviation or acronym is pronounced. The `attached_ascii_roman_segments_joined_by_ampersand` gate requires non-empty ASCII-letter segments joined directly by `&`, excludes spaced/Korean/alphanumeric continuations, and localizes only the current output cell immediately before the ampersand through an independently encoded real-input prefix. The `decimal_point_between_ascii_digits` gate finds whitespace-delimited words containing `digit.digit` and reproduces each whole word in a neutral Korean context, so suffixes and punctuation remain part of the current-engine signature. The `compact_numeric_ascii_letter_suffix` gate finds a numeric prefix immediately followed by ASCII letters, includes the immediately preceding output cell as its entry boundary, and retains suffix-specific outcome counts. It intentionally includes both possible rule-69 units and ambiguous variable/identifier forms; membership alone does not assign unit semantics. The `rule69_ascii_unit_before_terminator_skipping_symbol` gate is narrower: it accepts only ASCII unit spellings already supported by rule 69, includes the immediately following rule-33/34 punctuation cell in the localized signature, and does not infer new units. The `tight_triangle_mark_immediately_before_korean` gate requires literal `△한글` with no input space and includes the first following Korean cell in its localized output range, so an observed missing-space difference is measured at the mark boundary. +These are cross-cutting input-only structural cohorts, not new primary classes and not engine routing rules. Candidate selection never changes a case's existing primary class by itself. Only cohort members already classified as `pending_rule_review` form a pending subcluster; exact and other-primary members are controls that retain their existing outcomes. A separate classifier may use independently justified, output-localized PDF evidence, as in the rule-34 three-cell contradiction below. The `uppercase_roman_headword_closed_multiword_parenthetical` gate requires a two-or-more character uppercase ASCII headword immediately followed by a closed parenthesis whose contents are two or more ASCII Roman words separated only by spaces. Because the contents admit only letters and spaces, visible operators, subscript/superscript notation, and nested parentheses are excluded deterministically. The `mixed_roman_korean_word_before_uppercase_headword_expansion` gate further requires a whitespace-delimited preceding word that contains both ASCII Roman and Korean and ends in Korean. It locates the following uppercase headword itself, including its immediately preceding emitted cell, so an earlier Roman entry in the same sentence cannot satisfy the output audit. The `single_capital_followed_by_parenthesized_digits` gate requires a standalone capital, a non-empty closed ASCII-digit parenthetical, and alphanumeric outer boundaries. It likewise includes the emitted entry-boundary cell in localization. The `uppercase_roman_run_followed_by_hyphen_digits` gate requires a maximal uppercase ASCII run, literal ASCII hyphen-minus, a non-empty digit run, and alphanumeric outer boundaries. Its localized range includes the current encoded run and its immediately preceding cell, keeping `F-35`-style routing separate from both parenthesized digits and headword expansions. The `uppercase_alphanumeric_roman_digit_sequence` gate covers the wider rule-35/math collision: a capital-led uppercase/digit sequence with optional internal ASCII hyphen/full stop and trailing prose comma/colon/semicolon. It localizes only the current entry cells and does not decide whether an `A-3`-shaped surface is an identifier or a mathematical expression. The `standalone_multi_character_uppercase_roman_word` gate finds maximal ASCII-letter runs of two or more capitals with non-alphanumeric boundaries; a run immediately followed by `(` is excluded so the HCA-style headword itself is not counted by both gates. The `korean_prefixed_closed_allcaps_parenthetical` gate requires an immediately preceding Korean character and a closed body of two or more uppercase ASCII letters. It intentionally contains both acronym annotations (`책임자(COO)`) and scientific formulae (`일산화탄소(CO)`) so their semantic collision remains measurable. The `korean_prefixed_closed_roman_annotation_rule_34_order` gate accepts the narrower rule-34 body grammar after an immediately preceding Korean character and localizes only the current engine's Korean opening-parenthesis cells. This distinguishes the PDF's parenthesis-before-Roman-indicator order from unrelated differences later in the same sentence. The `multi_character_allcaps_roman_runs_joined_by_middle_dot` gate requires two maximal ASCII-letter runs of at least two capitals joined directly by U+00B7, with non-alphanumeric outer boundaries. It records shapes such as `AI·SW` without assigning prose, mathematics, or science semantics. The `roman_run_immediately_before_attached_middle_dot_boundary` gate is output-localized: it requires a maximal ASCII-letter run immediately before U+00B7 and an attached Korean character or ASCII-letter run after it, then searches for that whole current-engine signature in the actual output. It therefore isolates the Roman terminator boundary without treating unrelated middle dots elsewhere in the sentence as causal. The `attached_ascii_roman_to_korean_script_boundary` gate requires an ASCII letter run immediately followed by Korean script and localizes only the current mode marker there: either rule 29's final `⠲` or rule 39's opening `⠸⠷`. It deliberately does not infer the dominant language of the sentence from that surface boundary. The `korean_majority_same_token_roman_sandwich_non_domain` scope audit mirrors the narrower rule-39 implementation gate: the nearest script characters on both sides of one Korean segment are ASCII Roman, the input is Korean-majority by first-script word counts, and the segment is not dot-delimited like the official domain example. It measures change scope but is not itself an output-localized causal classifier. The `korean_inline_parenthesized_single_arithmetic_operator` gate requires an immediate `Korean(` + one rule-45 arithmetic operator + `)Korean` span. Unlike broad coexistence traits, it also locates the current engine's emitted structure and counts a mismatch as signature-local only when the sentence's first differing cell falls inside that output range. The `allcaps_roman_run_containing_ou` gate finds maximal, alphanumeric-delimited uppercase ASCII runs containing adjacent `OU`. It locates the independently encoded run signature in the complete current output and counts only first differences inside that signature as localized. The `allcaps_roman_run_containing_st` gate applies the same output-position requirement to adjacent `ST`; it is kept separate because UEB 10.12 makes contraction use depend on how an abbreviation or acronym is pronounced. The `attached_ascii_roman_segments_joined_by_ampersand` gate requires non-empty ASCII-letter segments joined directly by `&`, excludes spaced/Korean/alphanumeric continuations, and localizes only the current output cell immediately before the ampersand through an independently encoded real-input prefix. The `consecutive_ascii_roman_words_whitespace_boundary` gate requires two adjacent ASCII-letter words separated only by whitespace. For each boundary it independently encodes the real input prefix ending after the first word, then localizes only the current rule-29 terminator at that position or the full output's replacing blank. This prevents an unrelated Roman occurrence elsewhere in the sentence from satisfying the audit. Slash and other punctuation-separated forms are deliberately excluded. The `decimal_point_between_ascii_digits` gate finds whitespace-delimited words containing `digit.digit` and reproduces each whole word in a neutral Korean context, so suffixes and punctuation remain part of the current-engine signature. The `compact_numeric_ascii_letter_suffix` gate finds a numeric prefix immediately followed by ASCII letters, includes the immediately preceding output cell as its entry boundary, and retains suffix-specific outcome counts. It intentionally includes both possible rule-69 units and ambiguous variable/identifier forms; membership alone does not assign unit semantics. The `rule69_ascii_unit_before_terminator_skipping_symbol` gate is narrower: it accepts only ASCII unit spellings already supported by rule 69, includes the immediately following rule-33/34 punctuation cell in the localized signature, and does not infer new units. The `tight_triangle_mark_immediately_before_korean` gate requires literal `△한글` with no input space and includes the first following Korean cell in its localized output range, so an observed missing-space difference is measured at the mark boundary. | Cluster | Candidates | Exact | Mismatch | Conflicting-reference cases | |---|---:|---:|---:|---:| -| `allcaps_roman_run_beginning_with_pure_letter_shortform` | 4120 | 2460 | 1660 | 0 | +| `allcaps_roman_run_beginning_with_pure_letter_shortform` | 4120 | 2464 | 1656 | 0 | | `allcaps_roman_run_containing_ar` | 1022 | 363 | 659 | 0 | -| `allcaps_roman_run_containing_ed` | 816 | 318 | 498 | 0 | +| `allcaps_roman_run_containing_ed` | 816 | 319 | 497 | 0 | | `allcaps_roman_run_containing_ou` | 1816 | 64 | 1752 | 0 | -| `allcaps_roman_run_containing_st` | 1479 | 680 | 799 | 0 | -| `attached_ascii_roman_segments_joined_by_ampersand` | 802 | 283 | 519 | 0 | -| `attached_ascii_roman_to_korean_script_boundary` | 17693 | 12924 | 4769 | 0 | -| `closed_roman_parenthetical_after_non_ascii_letter_boundary` | 63959 | 52169 | 11790 | 0 | -| `compact_numeric_ascii_letter_suffix` | 2975 | 1870 | 1105 | 0 | -| `decimal_point_between_ascii_digits` | 4546 | 3380 | 1166 | 0 | +| `allcaps_roman_run_containing_st` | 1479 | 683 | 796 | 0 | +| `attached_ascii_roman_segments_joined_by_ampersand` | 802 | 284 | 518 | 0 | +| `attached_ascii_roman_to_korean_script_boundary` | 17693 | 12934 | 4759 | 0 | +| `closed_roman_parenthetical_after_non_ascii_letter_boundary` | 63959 | 52243 | 11716 | 0 | +| `compact_numeric_ascii_letter_suffix` | 2975 | 1872 | 1103 | 0 | +| `consecutive_ascii_roman_words_whitespace_boundary` | 4679 | 2132 | 2547 | 0 | +| `decimal_point_between_ascii_digits` | 4546 | 3383 | 1163 | 0 | | `korean_inline_parenthesized_single_arithmetic_operator` | 23 | 17 | 6 | 0 | | `korean_majority_same_token_roman_sandwich_non_domain` | 947 | 385 | 562 | 0 | -| `korean_prefixed_closed_allcaps_parenthetical` | 54492 | 44983 | 9509 | 0 | -| `korean_prefixed_closed_roman_annotation_rule_34_order` | 64382 | 52774 | 11608 | 0 | +| `korean_prefixed_closed_allcaps_parenthetical` | 54492 | 45024 | 9468 | 0 | +| `korean_prefixed_closed_roman_annotation_rule_34_order` | 64382 | 52846 | 11536 | 0 | | `korean_prefixed_roman_parenthetical_followed_by_allcaps_hyphen_suffix` | 11 | 0 | 11 | 0 | | `mixed_roman_korean_word_before_uppercase_headword_expansion` | 10 | 0 | 10 | 0 | | `multi_character_allcaps_roman_runs_joined_by_middle_dot` | 97 | 0 | 97 | 0 | -| `pure_allcaps_segment_before_hyphen_and_multi_allcaps_segment_after` | 448 | 130 | 318 | 0 | +| `pure_allcaps_segment_before_hyphen_and_multi_allcaps_segment_after` | 448 | 131 | 317 | 0 | | `roman_hyphenated_word_after_whitespace_following_korean_word` | 361 | 0 | 361 | 0 | -| `roman_parenthetical_headword_after_whitespace_following_korean_word` | 4695 | 3380 | 1315 | 0 | -| `roman_run_after_whitespace_following_closed_roman_enclosure` | 1093 | 141 | 952 | 0 | +| `roman_parenthetical_headword_after_whitespace_following_korean_word` | 4695 | 3382 | 1313 | 0 | +| `roman_run_after_whitespace_following_closed_roman_enclosure` | 1093 | 142 | 951 | 0 | | `roman_run_immediately_before_attached_middle_dot_boundary` | 577 | 0 | 577 | 0 | -| `rule69_ascii_unit_before_terminator_skipping_symbol` | 440 | 340 | 100 | 0 | +| `rule69_ascii_unit_before_terminator_skipping_symbol` | 440 | 341 | 99 | 0 | | `single_capital_followed_by_parenthesized_digits` | 1361 | 0 | 1361 | 0 | -| `standalone_multi_character_uppercase_roman_word` | 62411 | 50395 | 12016 | 0 | +| `standalone_multi_character_uppercase_roman_word` | 62411 | 50449 | 11962 | 0 | | `tight_triangle_mark_immediately_before_korean` | 377 | 0 | 377 | 0 | -| `uppercase_alphanumeric_roman_digit_sequence` | 3429 | 1908 | 1521 | 0 | -| `uppercase_ascii_run_immediately_after_digit_in_roman_sequence` | 1896 | 1100 | 796 | 0 | -| `uppercase_ascii_run_immediately_after_hyphen_in_roman_sequence` | 952 | 163 | 789 | 0 | +| `uppercase_alphanumeric_roman_digit_sequence` | 3429 | 1911 | 1518 | 0 | +| `uppercase_ascii_run_immediately_after_digit_in_roman_sequence` | 1896 | 1102 | 794 | 0 | +| `uppercase_ascii_run_immediately_after_hyphen_in_roman_sequence` | 952 | 164 | 788 | 0 | | `uppercase_roman_headword_closed_multiword_parenthetical` | 175 | 16 | 159 | 0 | | `uppercase_roman_run_followed_by_hyphen_digits` | 571 | 191 | 380 | 0 | -| `uppercase_word_after_whitespace_continuing_ascii_roman_text` | 1729 | 678 | 1051 | 0 | +| `uppercase_word_after_whitespace_continuing_ascii_roman_text` | 1729 | 699 | 1030 | 0 | ### `allcaps_roman_run_beginning_with_pure_letter_shortform` -Of the 4120 candidates, 1591 are the actual `pending_rule_review` subcluster. The other 2529 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 4120 candidates, 1585 are the actual `pending_rule_review` subcluster. The other 2535 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. -For this output-signature audit, 1660 mismatches were evaluable and 832 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. +For this output-signature audit, 1656 mismatches were evaluable and 832 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. Localized first-difference transitions: @@ -602,8 +603,8 @@ Localized first-difference transitions: Mismatch primary-class distribution: - `comparison_method`: 11 -- `corpus_suspect`: 48 -- `pending_rule_review`: 1591 +- `corpus_suspect`: 50 +- `pending_rule_review`: 1585 - `unsupported_character_review`: 10 Representative `exact` samples: @@ -673,7 +674,7 @@ Representative `mismatch` samples: ### `allcaps_roman_run_containing_ar` -Of the 1022 candidates, 636 are the actual `pending_rule_review` subcluster. The other 386 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 1022 candidates, 635 are the actual `pending_rule_review` subcluster. The other 387 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. For this output-signature audit, 659 mismatches were evaluable and 404 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. @@ -688,8 +689,8 @@ Localized first-difference transitions: Mismatch primary-class distribution: - `comparison_method`: 3 -- `corpus_suspect`: 14 -- `pending_rule_review`: 636 +- `corpus_suspect`: 15 +- `pending_rule_review`: 635 - `unsupported_character_review`: 6 Representative `exact` samples: @@ -759,13 +760,13 @@ Representative `mismatch` samples: ### `allcaps_roman_run_containing_ed` -Of the 816 candidates, 429 are the actual `pending_rule_review` subcluster. The other 387 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 816 candidates, 428 are the actual `pending_rule_review` subcluster. The other 388 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. -For this output-signature audit, 498 mismatches were evaluable and 328 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. +For this output-signature audit, 497 mismatches were evaluable and 329 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. Localized first-difference transitions: -- `U+2811 ⠑ -> U+282B ⠫`: 325 +- `U+2811 ⠑ -> U+282B ⠫`: 326 - `U+2810 ⠐ -> U+280E ⠎`: 1 - `U+2810 ⠐ -> U+281D ⠝`: 1 - `U+2815 ⠕ -> U+2820 ⠠`: 1 @@ -774,7 +775,7 @@ Mismatch primary-class distribution: - `comparison_method`: 1 - `corpus_suspect`: 63 -- `pending_rule_review`: 429 +- `pending_rule_review`: 428 - `unsupported_character_review`: 5 Representative `exact` samples: @@ -846,11 +847,11 @@ Representative `mismatch` samples: Of the 1816 candidates, 1666 are the actual `pending_rule_review` subcluster. The other 150 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. -For this output-signature audit, 1752 mismatches were evaluable and 1331 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. +For this output-signature audit, 1752 mismatches were evaluable and 1332 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. Localized first-difference transitions: -- `U+2815 ⠕ -> U+2833 ⠳`: 1330 +- `U+2815 ⠕ -> U+2833 ⠳`: 1331 - `U+2810 ⠐ -> U+283D ⠽`: 1 Mismatch primary-class distribution: @@ -905,9 +906,9 @@ Representative `mismatch` samples: ### `allcaps_roman_run_containing_st` -Of the 1479 candidates, 750 are the actual `pending_rule_review` subcluster. The other 729 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 1479 candidates, 743 are the actual `pending_rule_review` subcluster. The other 736 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. -For this output-signature audit, 799 mismatches were evaluable and 443 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. +For this output-signature audit, 796 mismatches were evaluable and 443 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. Localized first-difference transitions: @@ -920,8 +921,8 @@ Localized first-difference transitions: Mismatch primary-class distribution: - `comparison_method`: 13 -- `corpus_suspect`: 31 -- `pending_rule_review`: 750 +- `corpus_suspect`: 35 +- `pending_rule_review`: 743 - `unsupported_character_review`: 5 Representative `exact` samples: @@ -991,15 +992,15 @@ Representative `mismatch` samples: ### `attached_ascii_roman_segments_joined_by_ampersand` -Of the 802 candidates, 492 are the actual `pending_rule_review` subcluster. The other 310 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 802 candidates, 491 are the actual `pending_rule_review` subcluster. The other 311 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. -For this output-signature audit, 519 mismatches were evaluable and 0 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. +For this output-signature audit, 518 mismatches were evaluable and 0 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. Mismatch primary-class distribution: - `comparison_method`: 3 - `corpus_suspect`: 10 -- `pending_rule_review`: 492 +- `pending_rule_review`: 491 - `unsupported_character_review`: 14 Representative `exact` samples: @@ -1046,15 +1047,15 @@ Representative `mismatch` samples: ### `attached_ascii_roman_to_korean_script_boundary` -Of the 17693 candidates, 3939 are the actual `pending_rule_review` subcluster. The other 13754 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 17693 candidates, 3927 are the actual `pending_rule_review` subcluster. The other 13766 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. -For this output-signature audit, 4769 mismatches were evaluable and 0 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. +For this output-signature audit, 4759 mismatches were evaluable and 0 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. Mismatch primary-class distribution: - `comparison_method`: 60 -- `corpus_suspect`: 719 -- `pending_rule_review`: 3939 +- `corpus_suspect`: 721 +- `pending_rule_review`: 3927 - `unsupported_character_review`: 51 Representative `exact` samples: @@ -1146,24 +1147,24 @@ Representative `mismatch_rule39_hangul_opening` samples: ### `closed_roman_parenthetical_after_non_ascii_letter_boundary` -Of the 63959 candidates, 10309 are the actual `pending_rule_review` subcluster. The other 53650 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 63959 candidates, 10229 are the actual `pending_rule_review` subcluster. The other 53730 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. -For this output-signature audit, 11790 mismatches were evaluable and 589 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. +For this output-signature audit, 11716 mismatches were evaluable and 605 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. Localized first-difference transitions: -- `U+2826 ⠦ -> U+2834 ⠴`: 381 +- `U+2826 ⠦ -> U+2834 ⠴`: 386 - `U+2826 ⠦ -> U+2800 ⠀`: 178 -- `U+2834 ⠴ -> U+2826 ⠦`: 14 +- `U+2834 ⠴ -> U+2826 ⠦`: 25 - `U+2810 ⠐ -> U+2834 ⠴`: 11 - `U+2800 ⠀ -> U+2834 ⠴`: 4 Mismatch primary-class distribution: -- `comparison_method`: 223 -- `corpus_suspect`: 1101 -- `pending_rule_review`: 10309 -- `unsupported_character_review`: 157 +- `comparison_method`: 225 +- `corpus_suspect`: 1107 +- `pending_rule_review`: 10229 +- `unsupported_character_review`: 155 Representative `exact` samples: @@ -1232,9 +1233,9 @@ Representative `mismatch` samples: ### `compact_numeric_ascii_letter_suffix` -Of the 2975 candidates, 1035 are the actual `pending_rule_review` subcluster. The other 1940 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 2975 candidates, 1032 are the actual `pending_rule_review` subcluster. The other 1943 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. -For this output-signature audit, 1105 mismatches were evaluable and 254 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. +For this output-signature audit, 1103 mismatches were evaluable and 254 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. Localized first-difference transitions: @@ -1247,8 +1248,8 @@ Localized first-difference transitions: Mismatch primary-class distribution: - `comparison_method`: 12 -- `corpus_suspect`: 51 -- `pending_rule_review`: 1035 +- `corpus_suspect`: 52 +- `pending_rule_review`: 1032 - `unsupported_character_review`: 7 Representative `exact` samples: @@ -1316,11 +1317,89 @@ Representative `mismatch` samples: - first differing cell (zero-based): 15 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +### `consecutive_ascii_roman_words_whitespace_boundary` + +Of the 4679 candidates, 2413 are the actual `pending_rule_review` subcluster. The other 2266 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. + +For this output-signature audit, 2547 mismatches were evaluable and 23 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. + +Localized first-difference transitions: + +- `U+2800 ⠀ -> U+2832 ⠲`: 23 + +Mismatch primary-class distribution: + +- `comparison_method`: 17 +- `corpus_suspect`: 88 +- `pending_rule_review`: 2413 +- `unsupported_character_review`: 29 + +Representative `exact` samples: + +- `sentence_01.json` #32: 노스홀 메인 부스에서는 신기술인 ‘메타(META) 테크놀로지’를 적용해 화질을 혁신한 3세대 OLED TV 패널을 발표할 예정이다. + - expected: `⠉⠥⠠⠪⠚⠥⠂⠀⠑⠝⠟⠀⠘⠍⠠⠪⠝⠠⠎⠉⠵⠀⠠⠟` + - actual: `⠉⠥⠠⠪⠚⠥⠂⠀⠑⠝⠟⠀⠘⠍⠠⠪⠝⠠⠎⠉⠵⠀⠠⠟` + - current primary/reason: `exact` / `exact` +- `sentence_02.json` #26: 공주대학교 인재개발실에서 취득한 ISO 21001(교육기관경영시스템)은 국제표준화기구(ISO:International Organization for Standardization)에서 34개국 140여명의 전문가 그룹에 의해 개발돼 2018년에 제정되었다. + - expected: `⠈⠿⠨⠍⠊⠗⠚⠁⠈⠬⠀⠟⠨⠗⠈⠗⠘⠂⠠⠕⠂⠝⠠⠎` + - actual: `⠈⠿⠨⠍⠊⠗⠚⠁⠈⠬⠀⠟⠨⠗⠈⠗⠘⠂⠠⠕⠂⠝⠠⠎` + - current primary/reason: `exact` / `exact` +- `sentence_03.json` #66: 올해는 더욱 많은 인원을 수용할 수 있는 ‘Mint Breeze Stage(잔디마당)’의 5월 13일 헤드라이너로 발탁되며 다시 한 번 페스티벌 관객들을 들썩이게 할 전망이다. + - expected: `⠥⠂⠚⠗⠉⠵⠀⠊⠎⠍⠁⠀⠑⠒⠴⠵⠀⠟⠏⠒⠮⠀⠠⠍` + - actual: `⠥⠂⠚⠗⠉⠵⠀⠊⠎⠍⠁⠀⠑⠒⠴⠵⠀⠟⠏⠒⠮⠀⠠⠍` + - current primary/reason: `exact` / `exact` +- `sentence_04.json` #149: 이창용 한국은행 총재가 국제결제은행(BIS) 토론회에서 “한국 성인의 16%가 가상통화 거래를 위한 은행 계좌를 갖고 있다”며 “골칫거리 중 하나(one of headache to me)”라고 말했다. + - expected: `⠕⠰⠣⠶⠬⠶⠀⠚⠒⠈⠍⠁⠵⠚⠗⠶⠀⠰⠿⠨⠗⠫⠀⠈` + - actual: `⠕⠰⠣⠶⠬⠶⠀⠚⠒⠈⠍⠁⠵⠚⠗⠶⠀⠰⠿⠨⠗⠫⠀⠈` + - current primary/reason: `exact` / `exact` + +Representative `localized_mismatch` samples: + +- `sentence_01.json` #2209: 시노펙스 고분자전해질막(SYNO PEM-1) 모듈은 수소차용 미국에너지부(USDOE) 기준 가혹조건에서 500시간 이상 성능을 유지해야 한다는 기준의 2배가 넘는 1천시간 이상 성능을 유지하는 고성능 제품이다. + - expected: `⠄⠴⠠⠠⠎⠽⠝⠕⠀⠠⠠⠏⠑⠍⠤⠼⠁⠠⠴⠀⠑⠥⠊⠩` + - actual: `⠄⠴⠠⠠⠎⠽⠝⠕⠲⠀⠀⠠⠠⠏⠑⠍⠔⠼⠁⠴⠀⠀⠑⠥` + - first differing cell (zero-based): 33 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_02.json` #3467: 문 과장은 지난 2017년 광주디자인진흥원에 입사해 광주디자인비엔날레를 비롯해 광주국제도시디자인포럼(2019), 광주 ACE Fair(2020) 등 굵직한 국제행사에서 전시지원과 기획을 경험하며 역량을 쌓아온 베테랑이다. + - expected: `⠍⠀⠴⠠⠠⠁⠉⠑⠀⠠⠋⠁⠊⠗⠦⠄⠼⠃⠚⠃⠚⠠⠴⠀` + - actual: `⠍⠀⠴⠠⠠⠁⠉⠑⠲⠀⠠⠋⠁⠊⠗⠦⠼⠃⠚⠃⠚⠴⠀⠀` + - first differing cell (zero-based): 124 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_03.json` #3481: 제로베이스원 데뷔쇼는 10일 오후 8시 Mnet을 통해 방송되며, 유튜브 Mnet K-POP 채널과 일본 ABEMA(아베마), Mnet Japan을 통해서도 만날 수 있다. + - expected: `⠪⠀⠴⠠⠍⠝⠑⠞⠀⠰⠠⠅⠤⠠⠠⠏⠕⠏⠲⠀⠰⠗⠉⠞` + - actual: `⠪⠀⠴⠠⠍⠝⠑⠞⠲⠀⠠⠅⠔⠠⠠⠏⠕⠏⠀⠀⠰⠗⠉⠞` + - first differing cell (zero-based): 73 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` + +Representative `mismatch` samples: + +- `sentence_01.json` #18: 2023년형 신제품은 매터(Matter)와 HCA(Home Connectivity Alliance) 표준을 지원하는 스마트싱스 허브(SmartThings Hub)를 기반으로 다양한 기기를 자동으로 연결하고, 제어·관리할 수 있는 것이 특징이다. + - expected: `⠁⠞⠞⠻⠠⠴⠧⠀⠴⠠⠠⠓⠉⠁⠐⠣⠠⠓⠕⠍⠑⠀⠠⠒` + - actual: `⠁⠞⠞⠻⠠⠴⠧⠀⠀⠠⠠⠓⠉⠁⠦⠠⠓⠕⠍⠑⠀⠴⠠⠒` + - first differing cell (zero-based): 37 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_02.json` #14: 한국타이어앤테크놀로지㈜(대표이사 이수일, 이하 한국타이어)가 국제자동차연맹(FIA)이 주관하는 ‘FIA 주니어 ERC(FIA Junior ERC, 이하 주니어 ERC)’ 대회에 레이싱 타이어를 독점 공급한다. + - expected: `⠚⠒⠈⠍⠁⠓⠣⠕⠎⠗⠒⠓⠝⠋⠪⠉⠥⠂⠐⠥⠨⠕⠦⠄` + - actual: `` + - error: `Invalid symbol character` + - first differing cell (zero-based): 0 + - current primary/reason: `unsupported_character_review` / `unsupported_character_review` +- `sentence_03.json` #1: 김 지사는 18일(이하 현지시각) 미국 코네티컷주 댄버리 린데 본사에서 산지브 람바(Sanjiv Lamba) 린데 회장, 성백석 린데코리아 회장, 조일교 아산시 부시장과 투자양해각서(MOU)을 체결했다. + - expected: `⠠⠎⠦⠄⠴⠠⠠⠍⠕⠥⠠⠴⠮⠀⠰⠝⠈⠳⠚⠗⠌⠊⠲` + - actual: `⠠⠎⠦⠄⠴⠠⠠⠍⠳⠠⠴⠮⠀⠰⠝⠈⠳⠚⠗⠌⠊⠲` + - first differing cell (zero-based): 175 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_04.json` #62: LG디스플레이가 자사의 유기발광다이오드(OLED) TV 패널이 글로벌 친환경 인증기관인 카본 트러스트(Carbon Trust)로 부터 탄소발자국 인증을 획득했다고 19일 밝혔다. + - expected: `⠧⠶⠊⠣⠕⠥⠊⠪⠴⠐⠣⠠⠠⠕⠇⠫⠐⠜⠀⠠⠠⠞⠧⠲` + - actual: `⠧⠶⠊⠣⠕⠥⠊⠪⠦⠄⠴⠠⠠⠕⠇⠫⠠⠴⠀⠰⠠⠠⠞⠧` + - first differing cell (zero-based): 35 + - current primary/reason: `corpus_suspect` / `rule34_roman_indicator_before_opening_parenthesis` + ### `decimal_point_between_ascii_digits` -Of the 4546 candidates, 1109 are the actual `pending_rule_review` subcluster. The other 3437 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 4546 candidates, 1106 are the actual `pending_rule_review` subcluster. The other 3440 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. -For this output-signature audit, 1166 mismatches were evaluable and 260 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. +For this output-signature audit, 1163 mismatches were evaluable and 260 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. Localized first-difference transitions: @@ -1334,7 +1413,7 @@ Mismatch primary-class distribution: - `comparison_method`: 17 - `corpus_suspect`: 28 -- `pending_rule_review`: 1109 +- `pending_rule_review`: 1106 - `unsupported_character_review`: 12 Representative `exact` samples: @@ -1502,13 +1581,13 @@ Representative `mismatch` samples: ### `korean_prefixed_closed_allcaps_parenthetical` -Of the 54492 candidates, 8212 are the actual `pending_rule_review` subcluster. The other 46280 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 54492 candidates, 8167 are the actual `pending_rule_review` subcluster. The other 46325 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. Mismatch primary-class distribution: - `comparison_method`: 177 -- `corpus_suspect`: 988 -- `pending_rule_review`: 8212 +- `corpus_suspect`: 992 +- `pending_rule_review`: 8167 - `unsupported_character_review`: 132 Representative `exact` samples: @@ -1555,21 +1634,21 @@ Representative `mismatch` samples: ### `korean_prefixed_closed_roman_annotation_rule_34_order` -Of the 64382 candidates, 10123 are the actual `pending_rule_review` subcluster. The other 54259 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 64382 candidates, 10045 are the actual `pending_rule_review` subcluster. The other 54337 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. -For this output-signature audit, 11608 mismatches were evaluable and 1105 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. +For this output-signature audit, 11536 mismatches were evaluable and 1111 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. Localized first-difference transitions: -- `U+2834 ⠴ -> U+2826 ⠦`: 1104 +- `U+2834 ⠴ -> U+2826 ⠦`: 1110 - `U+2810 ⠐ -> U+2826 ⠦`: 1 Mismatch primary-class distribution: -- `comparison_method`: 227 -- `corpus_suspect`: 1101 -- `pending_rule_review`: 10123 -- `unsupported_character_review`: 157 +- `comparison_method`: 229 +- `corpus_suspect`: 1107 +- `pending_rule_review`: 10045 +- `unsupported_character_review`: 155 Representative `exact` samples: @@ -1748,9 +1827,9 @@ Representative `mismatch` samples: ### `pure_allcaps_segment_before_hyphen_and_multi_allcaps_segment_after` -Of the 448 candidates, 308 are the actual `pending_rule_review` subcluster. The other 140 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 448 candidates, 307 are the actual `pending_rule_review` subcluster. The other 141 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. -For this output-signature audit, 318 mismatches were evaluable and 1 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. +For this output-signature audit, 317 mismatches were evaluable and 1 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. Localized first-difference transitions: @@ -1759,7 +1838,7 @@ Localized first-difference transitions: Mismatch primary-class distribution: - `corpus_suspect`: 8 -- `pending_rule_review`: 308 +- `pending_rule_review`: 307 - `unsupported_character_review`: 2 Representative `exact` samples: @@ -1876,21 +1955,21 @@ Representative `mismatch` samples: ### `roman_parenthetical_headword_after_whitespace_following_korean_word` -Of the 4695 candidates, 1225 are the actual `pending_rule_review` subcluster. The other 3470 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 4695 candidates, 1221 are the actual `pending_rule_review` subcluster. The other 3474 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. -For this output-signature audit, 1315 mismatches were evaluable and 157 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. +For this output-signature audit, 1313 mismatches were evaluable and 158 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. Localized first-difference transitions: -- `U+2834 ⠴ -> U+2800 ⠀`: 153 +- `U+2834 ⠴ -> U+2800 ⠀`: 154 - `U+2834 ⠴ -> U+2830 ⠰`: 3 - `U+2820 ⠠ -> U+2834 ⠴`: 1 Mismatch primary-class distribution: - `comparison_method`: 29 -- `corpus_suspect`: 37 -- `pending_rule_review`: 1225 +- `corpus_suspect`: 39 +- `pending_rule_review`: 1221 - `unsupported_character_review`: 24 Representative `exact` samples: @@ -1962,13 +2041,13 @@ Representative `mismatch` samples: ### `roman_run_after_whitespace_following_closed_roman_enclosure` -Of the 1093 candidates, 624 are the actual `pending_rule_review` subcluster. The other 469 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 1093 candidates, 617 are the actual `pending_rule_review` subcluster. The other 476 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. -For this output-signature audit, 952 mismatches were evaluable and 248 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. +For this output-signature audit, 951 mismatches were evaluable and 251 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. Localized first-difference transitions: -- `U+2834 ⠴ -> U+2830 ⠰`: 242 +- `U+2834 ⠴ -> U+2830 ⠰`: 245 - `U+2803 ⠃ -> U+2820 ⠠`: 1 - `U+2815 ⠕ -> U+2837 ⠷`: 1 - `U+2817 ⠗ -> U+2820 ⠠`: 1 @@ -1976,8 +2055,8 @@ Localized first-difference transitions: Mismatch primary-class distribution: -- `corpus_suspect`: 321 -- `pending_rule_review`: 624 +- `corpus_suspect`: 327 +- `pending_rule_review`: 617 - `unsupported_character_review`: 7 Representative `exact` samples: @@ -2110,9 +2189,9 @@ Representative `mismatch` samples: ### `rule69_ascii_unit_before_terminator_skipping_symbol` -Of the 440 candidates, 95 are the actual `pending_rule_review` subcluster. The other 345 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 440 candidates, 94 are the actual `pending_rule_review` subcluster. The other 346 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. -For this output-signature audit, 100 mismatches were evaluable and 10 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. +For this output-signature audit, 99 mismatches were evaluable and 10 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. Localized first-difference transitions: @@ -2126,7 +2205,7 @@ Mismatch primary-class distribution: - `comparison_method`: 3 - `corpus_suspect`: 1 -- `pending_rule_review`: 95 +- `pending_rule_review`: 94 - `unsupported_character_review`: 1 Representative `exact` samples: @@ -2232,14 +2311,14 @@ Representative `mismatch` samples: ### `standalone_multi_character_uppercase_roman_word` -Of the 62411 candidates, 10563 are the actual `pending_rule_review` subcluster. The other 51848 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 62411 candidates, 10504 are the actual `pending_rule_review` subcluster. The other 51907 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. Mismatch primary-class distribution: -- `comparison_method`: 211 -- `corpus_suspect`: 1088 -- `pending_rule_review`: 10563 -- `unsupported_character_review`: 154 +- `comparison_method`: 212 +- `corpus_suspect`: 1093 +- `pending_rule_review`: 10504 +- `unsupported_character_review`: 153 Representative `exact` samples: @@ -2326,13 +2405,13 @@ Representative `mismatch` samples: ### `uppercase_alphanumeric_roman_digit_sequence` -Of the 3429 candidates, 1413 are the actual `pending_rule_review` subcluster. The other 2016 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 3429 candidates, 1409 are the actual `pending_rule_review` subcluster. The other 2020 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. -For this output-signature audit, 1521 mismatches were evaluable and 162 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. +For this output-signature audit, 1518 mismatches were evaluable and 163 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. Localized first-difference transitions: -- `U+2834 ⠴ -> U+2800 ⠀`: 92 +- `U+2834 ⠴ -> U+2800 ⠀`: 93 - `U+2834 ⠴ -> U+2830 ⠰`: 52 - `U+2820 ⠠ -> U+2803 ⠃`: 2 - `U+2820 ⠠ -> U+280E ⠎`: 2 @@ -2341,8 +2420,8 @@ Localized first-difference transitions: Mismatch primary-class distribution: - `comparison_method`: 13 -- `corpus_suspect`: 86 -- `pending_rule_review`: 1413 +- `corpus_suspect`: 87 +- `pending_rule_review`: 1409 - `unsupported_character_review`: 9 Representative `exact` samples: @@ -2412,9 +2491,9 @@ Representative `mismatch` samples: ### `uppercase_ascii_run_immediately_after_digit_in_roman_sequence` -Of the 1896 candidates, 740 are the actual `pending_rule_review` subcluster. The other 1156 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 1896 candidates, 737 are the actual `pending_rule_review` subcluster. The other 1159 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. -For this output-signature audit, 796 mismatches were evaluable and 1 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. +For this output-signature audit, 794 mismatches were evaluable and 1 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. Localized first-difference transitions: @@ -2423,8 +2502,8 @@ Localized first-difference transitions: Mismatch primary-class distribution: - `comparison_method`: 9 -- `corpus_suspect`: 40 -- `pending_rule_review`: 740 +- `corpus_suspect`: 41 +- `pending_rule_review`: 737 - `unsupported_character_review`: 7 Representative `exact` samples: @@ -2479,19 +2558,19 @@ Representative `mismatch` samples: ### `uppercase_ascii_run_immediately_after_hyphen_in_roman_sequence` -Of the 952 candidates, 768 are the actual `pending_rule_review` subcluster. The other 184 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 952 candidates, 767 are the actual `pending_rule_review` subcluster. The other 185 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. -For this output-signature audit, 789 mismatches were evaluable and 162 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. +For this output-signature audit, 788 mismatches were evaluable and 163 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. Localized first-difference transitions: -- `U+2820 ⠠ -> U+2830 ⠰`: 158 +- `U+2820 ⠠ -> U+2830 ⠰`: 159 - `U+2830 ⠰ -> U+2820 ⠠`: 4 Mismatch primary-class distribution: - `corpus_suspect`: 12 -- `pending_rule_review`: 768 +- `pending_rule_review`: 767 - `unsupported_character_review`: 9 Representative `exact` samples: @@ -2561,13 +2640,13 @@ Representative `mismatch` samples: ### `uppercase_roman_headword_closed_multiword_parenthetical` -Of the 175 candidates, 148 are the actual `pending_rule_review` subcluster. The other 27 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 175 candidates, 147 are the actual `pending_rule_review` subcluster. The other 28 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. Mismatch primary-class distribution: - `comparison_method`: 1 -- `corpus_suspect`: 4 -- `pending_rule_review`: 148 +- `corpus_suspect`: 5 +- `pending_rule_review`: 147 - `unsupported_character_review`: 6 Representative `exact` samples: @@ -2673,9 +2752,9 @@ Representative `mismatch` samples: ### `uppercase_word_after_whitespace_continuing_ascii_roman_text` -Of the 1729 candidates, 977 are the actual `pending_rule_review` subcluster. The other 752 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 1729 candidates, 952 are the actual `pending_rule_review` subcluster. The other 777 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. -For this output-signature audit, 1051 mismatches were evaluable and 4 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. +For this output-signature audit, 1030 mismatches were evaluable and 4 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. Localized first-difference transitions: @@ -2686,8 +2765,8 @@ Localized first-difference transitions: Mismatch primary-class distribution: - `comparison_method`: 9 -- `corpus_suspect`: 54 -- `pending_rule_review`: 977 +- `corpus_suspect`: 58 +- `pending_rule_review`: 952 - `unsupported_character_review`: 11 Representative `exact` samples: @@ -2757,10 +2836,10 @@ These cohorts are defined by both an input boundary and the sentence's actual fi | Cohort | Candidates | Exact controls | Mismatch | Target localized | Reverse | |---|---:|---:|---:|---:|---:| -| `allcaps_roman_run_beginning_with_pure_letter_shortform` | 4120 | 2460 | 1660 | 785 | 47 | -| `uppercase_ascii_run_immediately_after_digit_in_roman_sequence` | 1896 | 1100 | 796 | 0 | 1 | -| `uppercase_ascii_run_immediately_after_hyphen_in_roman_sequence` | 952 | 163 | 789 | 158 | 4 | -| `pure_allcaps_segment_before_hyphen_and_multi_allcaps_segment_after` | 448 | 130 | 318 | 0 | 1 | +| `allcaps_roman_run_beginning_with_pure_letter_shortform` | 4120 | 2464 | 1656 | 785 | 47 | +| `uppercase_ascii_run_immediately_after_digit_in_roman_sequence` | 1896 | 1102 | 794 | 0 | 1 | +| `uppercase_ascii_run_immediately_after_hyphen_in_roman_sequence` | 952 | 164 | 788 | 159 | 4 | +| `pure_allcaps_segment_before_hyphen_and_multi_allcaps_segment_after` | 448 | 131 | 317 | 0 | 1 | ### All-caps shortform prefix at an attached Roman entry @@ -2817,7 +2896,7 @@ Korean rule 29 requires a Roman indicator before Roman text in a Korean sentence | Cohort | Candidates | Exact controls | Mismatch | Pending | Corpus suspect | Localized `⠴ -> blank` | Reverse `blank -> ⠴` | |---|---:|---:|---:|---:|---:|---:|---:| | `roman_hyphenated_word_after_whitespace_following_korean_word` | 361 | 0 | 361 | 350 | 6 | 136 | 0 | -| `roman_parenthetical_headword_after_whitespace_following_korean_word` | 4695 | 3380 | 1315 | 1225 | 37 | 153 | 0 | +| `roman_parenthetical_headword_after_whitespace_following_korean_word` | 4695 | 3382 | 1313 | 1221 | 39 | 154 | 0 | | `korean_prefixed_roman_parenthetical_followed_by_allcaps_hyphen_suffix` | 11 | 0 | 11 | 6 | 5 | 5 | 0 | The whitespace parenthetical-headword cohort also retains 1 localized `⠠ -> ⠴` and 3 localized `⠴ -> ⠰` cases as separate transitions; they are not folded into the target. The exact controls demonstrate that the broad structure is already correct in many sentences, while the attached parenthetical-hyphen cohort has no exact control and includes cases already classified by the stricter rule-34 reference-order contradiction. Consequently none of these measurements authorizes an engine change; they are deterministic pending/corpus-review diagnostics only. @@ -2828,13 +2907,13 @@ Korean rule 29 explicitly says that when two or more Roman items occur consecuti Analyzer checkpoint `266e70c` fixed the pre-change baseline at 1,729 candidates, 386 exact controls, 1,343 mismatches, 1,272 pending members, and 385/1,343 evaluable mismatches localized to the current re-entry signature. Those localized transitions were 365 `⠠ -> ⠴`, 16 `⠰ -> ⠴`, no reverse `⠴ -> ⠠`, and four other transitions. Exact controls include contexts where the first Roman word already opened token-level mode, while parenthesized/mixed-token examples expose the duplicate event. -The generalized fix makes an explicit `EnterEnglish` event idempotent when final emit state is already inside a Roman section; it neither names an input nor changes a new section's entry. The current measurement is 1729 candidates, 678 exact controls, 1051 mismatches, 977 pending members, and 4/1051 localized mismatches. Current target counts are 0 `⠠ -> ⠴`, 0 `⠰ -> ⠴`, and 0 reverse `⠴ -> ⠠`. Cohort exact controls increase by 275, while corpus-wide exact matches rise from 67,138 to 67,442 (+304); corrected prefixes that still differ later remain mismatches, and applications outside this strict input gate account for the remaining net gain. The raw/residual reverse `⠴ -> ⠠` totals remain 68/65 before and after the change. The complete custom standard summary is 5,141 total, 5,141 success, 0 failure, and 0 skipped. +The generalized fix makes an explicit `EnterEnglish` event idempotent when final emit state is already inside a Roman section; it neither names an input nor changes a new section's entry. The current measurement is 1729 candidates, 699 exact controls, 1030 mismatches, 952 pending members, and 4/1030 localized mismatches. Current target counts are 0 `⠠ -> ⠴`, 0 `⠰ -> ⠴`, and 0 reverse `⠴ -> ⠠`. Cohort exact controls increase by 275, while corpus-wide exact matches rise from 67,138 to 67,442 (+304); corrected prefixes that still differ later remain mismatches, and applications outside this strict input gate account for the remaining net gain. The raw/residual reverse `⠴ -> ⠠` totals remain 68/65 before and after the change. The complete custom standard summary is 5,141 total, 5,141 success, 0 failure, and 0 skipped. ### Closed Roman parenthetical after a non-ASCII-letter boundary Korean rule 34 (2024 Korean-rules PDF p.29) prints the opening enclosure before Roman entry in `링컨(Lincoln)`: the opening sequence is `⠦⠄⠴`. This output-position cohort finds a closed, non-nested parenthetical whose body begins with an ASCII letter and whose opening does not immediately follow another ASCII letter, then locates its complete current-engine signature without consulting the reference. Direct function-call shapes such as `f(x)` are excluded. The PDF's math rule 6 (p.59), rule 12 (pp.63-64), and rule 45 (p.75) nevertheless leave standalone `(x)` and other Roman-letter parenthetical mathematics as counterexamples, so the surface gate is not an engine-routing predicate. -The cross-cutting input cohort contains 63959 candidates: 52169 exact controls and 11790 mismatches. Mismatch primary classes remain unchanged: 10309 `pending_rule_review`, 1101 `corpus_suspect`, 223 `comparison_method`, and 157 `unsupported_character_review`. Of 11790 evaluable mismatches, 589 have the first difference at the detected opening; these include 381 `U+2826 ⠦ -> U+2834 ⠴`, 14 `U+2834 ⠴ -> U+2826 ⠦`, and 178 `U+2826 ⠦ -> U+2800 ⠀` transitions. After all earlier localized cohorts and this cohort are excluded, the raw-to-residual target count is 467 -> 79 and the reverse count is 30 -> 13. The short full-encoder form `웹3(Web3)` emits the PDF opening order as a routing control, while whitespace and quote boundaries reproduce the current Roman-first opening. Representative localized samples, with shard and index, are retained in the generated cluster sample table. Because exact controls are abundant and the PDF does not make this input shape semantically sufficient to exclude mathematics, no engine change or primary reclassification is inferred. +The cross-cutting input cohort contains 63959 candidates: 52243 exact controls and 11716 mismatches. Mismatch primary classes remain unchanged: 10229 `pending_rule_review`, 1107 `corpus_suspect`, 225 `comparison_method`, and 155 `unsupported_character_review`. Of 11716 evaluable mismatches, 605 have the first difference at the detected opening; these include 386 `U+2826 ⠦ -> U+2834 ⠴`, 25 `U+2834 ⠴ -> U+2826 ⠦`, and 178 `U+2826 ⠦ -> U+2800 ⠀` transitions. After all earlier localized cohorts and this cohort are excluded, the raw-to-residual target count is 469 -> 76 and the reverse count is 32 -> 4. The short full-encoder form `웹3(Web3)` emits the PDF opening order as a routing control, while whitespace and quote boundaries reproduce the current Roman-first opening. Representative localized samples, with shard and index, are retained in the generated cluster sample table. Because exact controls are abundant and the PDF does not make this input shape semantically sufficient to exclude mathematics, no engine change or primary reclassification is inferred. The HCA-style headword-expansion shape described above is not an engine implementation premise. The 2024 PDF's math rule 6 defines parentheses and grouping parentheses, rule 11 defines mathematical-expression spacing, rule 12 covers Roman letters in formulas as well as Korean sentences, and rule 45 shows Roman-letter function notation followed by parentheses. Excluding visible operators, scripts, and nesting narrows this corpus cohort, but the PDF does not make the remaining surface shape sufficient to rule out every mathematical counterexample. The cluster therefore remains conservative pending-review evidence only. @@ -2863,23 +2942,23 @@ Corpus contradictions remain a separate gate: identical inputs with conflicting UEB 10.12.1 (2024 UEB PDF pp.191-192, printed pp.163-164) says not to use a contraction when letters within an abbreviation or acronym are known to be pronounced separately, but to use the contraction in case of doubt. Its official `DAR` example writes separate `a` and `r` cells, while UEB 10.12.2's official `START` example uses the `ar` groupsign. Both full-encoder controls pass. Thus an uppercase surface containing `AR` does not itself supply the pronunciation or lexical meaning needed to select either form. -The output-localized cohort contains 1022 candidates, 363 exact controls, and 659 mismatches. Existing mismatch primary classes are preserved: 636 `pending_rule_review`, 14 `corpus_suspect`, 3 `comparison_method`, and 6 `unsupported_character_review`. Of 659 evaluable mismatches, 404 have their first difference inside the detected current-engine run: 397 `U+2801 ⠁ -> U+281C ⠜` and 0 `U+281C ⠜ -> U+2801 ⠁`. Across raw pending transitions and the final residual after localized cohorts, the target count is 405 -> 7; the reverse is 2 -> 2. Inputs denoting separately pronounced initialisms such as corpus `AR`/`ARS` coexist with exact or officially contracted controls. This is deterministic evidence for a pronunciation-dependent pending cohort, not an engine rule or primary reclassification. Representative shard/index samples are retained above. +The output-localized cohort contains 1022 candidates, 363 exact controls, and 659 mismatches. Existing mismatch primary classes are preserved: 635 `pending_rule_review`, 15 `corpus_suspect`, 3 `comparison_method`, and 6 `unsupported_character_review`. Of 659 evaluable mismatches, 404 have their first difference inside the detected current-engine run: 397 `U+2801 ⠁ -> U+281C ⠜` and 0 `U+281C ⠜ -> U+2801 ⠁`. Across raw pending transitions and the final residual after localized cohorts, the target count is 405 -> 7; the reverse is 2 -> 2. Inputs denoting separately pronounced initialisms such as corpus `AR`/`ARS` coexist with exact or officially contracted controls. This is deterministic evidence for a pronunciation-dependent pending cohort, not an engine rule or primary reclassification. Representative shard/index samples are retained above. ### Roman run after a closed Roman enclosure Korean rule 29 places one Roman indicator/terminator pair around consecutive Roman items, with `Los Angeles` and `Table of Contents` as its printed multiword examples. Rule 34 separately omits the Roman terminator when Roman text is enclosed by quotation marks or parentheses. Neither printed rule states whether a later Roman run after the enclosure, intervening punctuation, and whitespace is a continuation of that section or a fresh section. The input gate therefore detects only the structural boundary; it does not label the later run as semantically new. -The cohort contains 1093 candidates, 141 exact controls, and 952 mismatches. Existing mismatch primary classes are preserved: 624 `pending_rule_review`, 321 `corpus_suspect`, and 7 `unsupported_character_review`. Of 952 evaluable mismatches, 248 are output-localized to the current later-run signature plus its one leading boundary cell: 242 `U+2834 ⠴ -> U+2830 ⠰` and 0 `U+2830 ⠰ -> U+2834 ⠴`. The target counted 344 raw and 333 residual cases before this cohort; it is now 47 final residual. Across raw and final residual maps, `U+2830 ⠰ -> U+2834 ⠴` is 3 -> 1. Exact controls coexist with both directions, and 321 mismatches are already independently identified corpus contradictions. Without a printed fresh-entry example or semantic enclosure model, changing continuation state would be reference-fitting; this remains a deterministic pending/corpus-review cohort only. Representative shard/index samples are retained above. +The cohort contains 1093 candidates, 142 exact controls, and 951 mismatches. Existing mismatch primary classes are preserved: 617 `pending_rule_review`, 327 `corpus_suspect`, and 7 `unsupported_character_review`. Of 951 evaluable mismatches, 251 are output-localized to the current later-run signature plus its one leading boundary cell: 245 `U+2834 ⠴ -> U+2830 ⠰` and 0 `U+2830 ⠰ -> U+2834 ⠴`. The target counted 347 raw and 333 residual cases before this cohort; it is now 47 final residual. Across raw and final residual maps, `U+2830 ⠰ -> U+2834 ⠴` is 3 -> 1. Exact controls coexist with both directions, and 321 mismatches are already independently identified corpus contradictions. Without a printed fresh-entry example or semantic enclosure model, changing continuation state would be reference-fitting; this remains a deterministic pending/corpus-review cohort only. Representative shard/index samples are retained above. ### Uppercase Roman runs containing `ED` UEB 10.12.1 (2024 UEB PDF pp.191-192, printed pp.163-164) requires separate letters when an abbreviation's letters are known to be pronounced separately, and its official `OED` example writes `e` and `d` separately. Rule 10.12.2 otherwise uses contractions; its official `BEd` example uses the `ed` groupsign. Both full-encoder controls pass. Consequently an uppercase `ED` surface cannot establish pronunciation or abbreviation semantics by spelling alone. -The cohort contains 816 candidates, 318 exact controls, and 498 mismatches. Existing mismatch primary classes remain 429 `pending_rule_review`, 63 `corpus_suspect`, 1 `comparison_method`, and 5 `unsupported_character_review`. Of 498 evaluable mismatches, 328 are localized to the detected current-engine run: 325 `U+2811 ⠑ -> U+282B ⠫` and 0 `U+282B ⠫ -> U+2811 ⠑`. Across raw pending transitions and the final residual after localized cohorts, the target is 327 -> 2 and the reverse is 0 -> 0. Corpus initialisms such as `LED` and `GED` require external pronunciation knowledge, while exact and official controls preserve contraction-bearing outcomes. No engine change or primary reclassification is made; representative shard/index samples are retained above. +The cohort contains 816 candidates, 319 exact controls, and 497 mismatches. Existing mismatch primary classes remain 428 `pending_rule_review`, 63 `corpus_suspect`, 1 `comparison_method`, and 5 `unsupported_character_review`. Of 497 evaluable mismatches, 329 are localized to the detected current-engine run: 326 `U+2811 ⠑ -> U+282B ⠫` and 0 `U+282B ⠫ -> U+2811 ⠑`. Across raw pending transitions and the final residual after localized cohorts, the target is 329 -> 3 and the reverse is 0 -> 0. Corpus initialisms such as `LED` and `GED` require external pronunciation knowledge, while exact and official controls preserve contraction-bearing outcomes. No engine change or primary reclassification is made; representative shard/index samples are retained above. ### Uppercase Roman runs containing `ST` -UEB 10.12.1 (2024 UEB PDF pp.191-192, printed pp.163-164) says not to use a contraction when letters within an abbreviation or acronym are known to be pronounced separately, but to use the contraction in case of doubt. Rule 10.12.2 requires contractions in other abbreviations and acronyms. Consequently an uppercase `ST` surface alone cannot determine whether `s` + `t` or the `st` groupsign is required. The current cohort contains 1479 candidates, 680 exact controls, and 799 mismatches; primary classes remain 750 `pending_rule_review`, 31 `corpus_suspect`, 13 `comparison_method`, and 5 `unsupported_character_review`. Of 799 evaluable mismatches, 443 are localized to the detected current-engine run: 435 `U+280E ⠎ -> U+280C ⠌` and 1 `U+280C ⠌ -> U+280E ⠎`. The target's raw-to-residual count is 448 -> 13, and the reverse is 2 -> 1. Exact controls such as `STAYG`, `KAIST`, `DGIST`, and `POSTECH` coexist with localized mismatches such as `WSTS`, `HUST`, `OST`, and `USTR`; representative shard/index samples are preserved in the cluster table. This lexical/pronunciation distinction cannot be inferred from the input-only spelling, so no engine change or primary reclassification is made. +UEB 10.12.1 (2024 UEB PDF pp.191-192, printed pp.163-164) says not to use a contraction when letters within an abbreviation or acronym are known to be pronounced separately, but to use the contraction in case of doubt. Rule 10.12.2 requires contractions in other abbreviations and acronyms. Consequently an uppercase `ST` surface alone cannot determine whether `s` + `t` or the `st` groupsign is required. The current cohort contains 1479 candidates, 683 exact controls, and 796 mismatches; primary classes remain 743 `pending_rule_review`, 35 `corpus_suspect`, 13 `comparison_method`, and 5 `unsupported_character_review`. Of 796 evaluable mismatches, 443 are localized to the detected current-engine run: 435 `U+280E ⠎ -> U+280C ⠌` and 1 `U+280C ⠌ -> U+280E ⠎`. The target's raw-to-residual count is 448 -> 13, and the reverse is 2 -> 1. Exact controls such as `STAYG`, `KAIST`, `DGIST`, and `POSTECH` coexist with localized mismatches such as `WSTS`, `HUST`, `OST`, and `USTR`; representative shard/index samples are preserved in the cluster table. This lexical/pronunciation distinction cannot be inferred from the input-only spelling, so no engine change or primary reclassification is made. ### Attached Roman segments joined by ampersand @@ -2889,24 +2968,26 @@ The baseline was 802 candidates / 0 exact / 802 mismatch, preserving 776 `pendin The implemented gate shares the analyzer's complete-run predicate: one or more non-empty ASCII-letter segments joined directly by `&`, with non-alphanumeric outer boundaries. It keeps the existing Roman mode open and suppresses only rule 71's redundant wrapper around that ampersand. Spaced `Marks & Spencer`, Korean `가&나`, empty segments, and outer digit continuations remain outside the gate. Official full-encoder controls `AT&T` and `B&B` pass, and the Korean rule-71 spaced example `종이접기 & 클레이아트` retains its independent `⠴⠈⠯⠲` section. -After the change the same cohort contains 802 candidates, 283 exact and 519 mismatch. Current mismatch primary classes remain evaluator-owned: 492 `pending_rule_review`, 10 `corpus_suspect`, 14 `unsupported_character_review`, and 3 `comparison_method`. The localizer evaluates all 519 remaining mismatches and finds 0 target-localized cases (0 `U+2808 ⠈ -> U+2832 ⠲`); current raw-to-residual target count is 25 -> 24, while `U+2832 ⠲ -> U+2808 ⠈` remains 0 raw / 0 residual. Cohort exact increases by 273 and corpus-wide exact increases by the same 273, from 67,442 to 67,715. Because every changed input must satisfy this shared predicate and the baseline had no exact member, this boundary has no exact regression. The remaining 529 candidates differ elsewhere or retain an existing comparison, corpus-suspect, unsupported, or pending cause. Representative shard/index samples are retained above. +After the change the same cohort contains 802 candidates, 284 exact and 518 mismatch. Current mismatch primary classes remain evaluator-owned: 491 `pending_rule_review`, 10 `corpus_suspect`, 14 `unsupported_character_review`, and 3 `comparison_method`. The localizer evaluates all 518 remaining mismatches and finds 0 target-localized cases (0 `U+2808 ⠈ -> U+2832 ⠲`); current raw-to-residual target count is 25 -> 24, while `U+2832 ⠲ -> U+2808 ⠈` remains 0 raw / 0 residual. Cohort exact increases by 273 and corpus-wide exact increases by the same 273, from 67,442 to 67,715. Because every changed input must satisfy this shared predicate and the baseline had no exact member, this boundary has no exact regression. The remaining 529 candidates differ elsewhere or retain an existing comparison, corpus-suspect, unsupported, or pending cause. Representative shard/index samples are retained above. Current uppercase-Roman hyphen-digits measurement: 571 candidates, 191 exact controls, 380 mismatches, 365 members in the actual `pending_rule_review` subcluster, and 261/380 evaluable mismatches whose first difference is inside the target run plus its entry boundary. It remains distinct from parenthesized digits and headword expansions; no engine change is inferred. -Current uppercase alphanumeric Roman-digit sequence measurement: 3429 candidates, 1908 exact controls, 1521 mismatches, 1413 members in the actual `pending_rule_review` subcluster, and 162/1521 evaluable mismatches whose first difference is localized to the current entry cells. The target `⠴ -> blank` occurs 92 times and the reverse occurs 0 times. Korean rule 35 (2024 Korean-rules PDF pp.29-30, printed pp.23-24) explicitly treats `MP3`, `A4`, `KF94`, and `D-100` as Roman-number sequences. Math rules 11/12 (PDF pp.63-65, printed pp.57-59) separately route mathematical Roman notation without the prose Roman indicator and with two-cell spacing. Official UEB 6.5.1-6.5.2 (2024 UEB PDF p.96, printed p.68) determines numeric/grade-1 continuation only after the surrounding mode has been chosen. Thus surfaces such as `A-3` cannot be assigned identifier or variable semantics from this structure alone. Exact controls and both directions are retained; no engine change is inferred. +Current uppercase alphanumeric Roman-digit sequence measurement: 3429 candidates, 1911 exact controls, 1518 mismatches, 1409 members in the actual `pending_rule_review` subcluster, and 163/1518 evaluable mismatches whose first difference is localized to the current entry cells. The target `⠴ -> blank` occurs 93 times and the reverse occurs 0 times. Korean rule 35 (2024 Korean-rules PDF pp.29-30, printed pp.23-24) explicitly treats `MP3`, `A4`, `KF94`, and `D-100` as Roman-number sequences. Math rules 11/12 (PDF pp.63-65, printed pp.57-59) separately route mathematical Roman notation without the prose Roman indicator and with two-cell spacing. Official UEB 6.5.1-6.5.2 (2024 UEB PDF p.96, printed p.68) determines numeric/grade-1 continuation only after the surrounding mode has been chosen. Thus surfaces such as `A-3` cannot be assigned identifier or variable semantics from this structure alone. Exact controls and both directions are retained; no engine change is inferred. + +Current consecutive ASCII-Roman word-boundary measurement: 4679 candidates, 2132 exact controls, 2547 mismatches, 2413 members in the actual `pending_rule_review` subcluster, and 23/2547 evaluable mismatches whose first difference is localized to the current boundary cell. The target reference blank versus current terminator (`⠀ -> ⠲`) occurs 23 times; the exact reverse (`⠲ -> ⠀`) occurs 0 times. Korean rule 29 (2024 Korean-rules PDF p.26, printed p.20) explicitly treats consecutive Roman items as one Roman section, with one Roman indicator before the first item and one terminator after the last; its printed `Los Angeles` and `Table of Contents` examples are exact controls. Rule 34 (PDF p.29, printed p.23) separately omits the terminator before a closing enclosure, while official UEB 9.7.1 (2024 UEB PDF p.137, printed p.109) prints the multiword prose enclosure `plays (such as Romeo and Juliet)` with ordinary internal spaces and nested closing punctuation. Together they support preserving a complete letter-and-space Roman parenthetical as prose, but not a fragment containing a function-call opener, digit, operator, or nested bracket. The input-only gate does not decide whether punctuation-separated text such as `ESS /VPP` continues the same section, so that variant remains outside this cohort. Primary classes are preserved. Before the narrow token-routing change this cohort was 4,679 candidates / 2,058 exact / 2,621 mismatch, with 128 localized `⠀ -> ⠲` and zero reverse. The cause was the final `Letters)` token of a whitespace-split Roman parenthetical being rerouted as math, which made the preceding Roman word terminate early and introduced an extra blank. After excluding only a backwards-verified complete Roman parenthetical tail from math routing, the cohort is 2,132 exact / 2,547 mismatch, with 23 target-localized and zero reverse; corpus-wide exact likewise rises by 74, from 68,101 to 68,175. The 105 removed localized boundaries include 74 newly exact cases and 31 cases that still differ elsewhere. Remaining targets such as `SYNO PEM-1`, `ACE Fair(2020)`, and `Mnet K-POP` do not satisfy the complete-parenthetical gate and remain separate residuals rather than widening this rule. Current single-capital parenthesized-digits measurement: 1361 candidates, 0 exact controls, 1361 mismatches, 1357 members in the actual `pending_rule_review` subcluster, and 1342/1361 evaluable mismatches whose first difference is inside the target run plus its entry boundary. No engine change is inferred from the ambiguous prose/function surface form. Current mixed Roman+Korean boundary before uppercase headword-expansion measurement: 10 candidates, 0 exact controls, 10 mismatches, 10 members in the actual `pending_rule_review` subcluster, and 8/10 evaluable mismatches whose first difference is localized to the later headword's entry boundary/output. The detector cannot be satisfied by the earlier Roman entry. No HCA-shaped engine routing rule is introduced. -Current compact numeric+ASCII-suffix measurement: 2975 candidates, 1870 exact controls, 1105 mismatches, 1035 members in the actual `pending_rule_review` subcluster, and 254/1105 evaluable mismatches whose first difference is inside the complete current-engine output signature or its immediate entry boundary. Rule 40 requires the numeric indicator and rule 69 requires Roman indicators around a Roman-written unit, but the input shape alone cannot prove that every ASCII suffix is a unit. +Current compact numeric+ASCII-suffix measurement: 2975 candidates, 1872 exact controls, 1103 mismatches, 1032 members in the actual `pending_rule_review` subcluster, and 254/1103 evaluable mismatches whose first difference is inside the complete current-engine output signature or its immediate entry boundary. Rule 40 requires the numeric indicator and rule 69 requires Roman indicators around a Roman-written unit, but the input shape alone cannot prove that every ASCII suffix is a unit. | ASCII suffix | Candidates | Exact | Mismatch | Localized first diff | |---|---:|---:|---:|---:| | `m` | 364 | 261 | 103 | 22 | | `km` | 308 | 250 | 58 | 3 | | `G` | 237 | 164 | 73 | 3 | -| `kg` | 209 | 174 | 35 | 6 | +| `kg` | 209 | 175 | 34 | 6 | | `D` | 167 | 115 | 52 | 5 | | `p` | 140 | 20 | 120 | 84 | | `g` | 138 | 100 | 38 | 8 | @@ -2924,32 +3005,32 @@ Current compact numeric+ASCII-suffix measurement: 2975 candidates, 1870 exact co | `bp` | 39 | 3 | 36 | 18 | | `K` | 38 | 13 | 25 | 1 | | `MW` | 37 | 35 | 2 | 0 | -| `TURN` | 33 | 19 | 14 | 0 | +| `TURN` | 33 | 20 | 13 | 0 | | `L` | 31 | 21 | 10 | 5 | | `mm` | 29 | 19 | 10 | 1 | | `egin` | 28 | 27 | 1 | 0 | Entry-boundary pre-fix baseline: 2,975 candidates, 1,649 exact controls, 1,326 mismatches, 1,259 pending members, and 356 localized first differences; the dominant reference number-sign versus current space transition accounted for 296 cases. Rules 68 and 69 already accept semantic Unicode compatibility-unit forms. The implementation derives compact ASCII spellings only from their all-letter NFKC decompositions, reuses the owning rule's PDF-defined cells, chooses the longest complete spelling, and rejects partial suffix matches. It does not extend recognition to separated English words or arbitrary corpus suffixes. The same cohort now has 1,759 exact controls, 1,216 mismatches, 1,148 pending members, and 261 localized first differences; corpus-wide exact matches moved from 66,436 to 66,546 (+110). Rule 69's printed `160㎎/㎗` example directly controls `160mg`; the additional full-encoder `240mg`/`240㎎` pair proves that recognition is invariant to the numeric value and also passes. Rule 68's printed `10,000㎡는 1㏊이다` example controls the `ha` spelling through U+33CA's NFKC decomposition; `15.2ha`/`15.2㏊` now passes without a spelling-specific output branch. The 53-case `ha` suffix control moved from 0 exact to 18 exact; its remaining 35 mismatches have independent later or surrounding differences. A full U+3300..U+33FF owner audit found 73 Rules 68/69 glyphs with pure-ASCII NFKC decompositions, 73 distinct spellings, and therefore no current duplicate-spelling owner collision. Production nevertheless groups every owner before resolution and excludes a spelling if any owner cells differ; a synthetic collision test proves that this is not first-wins behavior. An exhaustive test also compares every one of the 73 derived spellings with every owner glyph at both the unit-cell and full-encoder boundaries in a neutral Korean measurement context. That audit exposed the pre-existing explicit `cal` mapping's missing ordinary terminator; rule 69 requires the terminator, while the existing slash-boundary function removes it for the printed `cal/㎠/min` context. Restoring the ordinary terminator and extending that slash-continuation boundary made the audit pass without changing the corpus-wide 66,546 exact total. Ambiguous pure-English inputs remain on UEB: the standard controls `3m` and `4.m` are retained and pass, rather than being globally forced into a Korean unit route. -Current rule-69 ASCII-unit punctuation-boundary measurement: 440 candidates, 340 exact controls, 100 mismatches, 95 members in the actual `pending_rule_review` subcluster, and 10/100 evaluable mismatches whose first difference is localized to the unit-plus-punctuation output signature. PDF rule 69 requires a Roman terminator after a Roman-written unit in the ordinary case, while rules 33/34 omit it at the listed punctuation or enclosing-mark boundary; the rule 46 PDF example `체중(kg)` is the minimal parenthesized-unit control. Membership is restricted to rule-69 spellings already supported by the engine and does not infer unit semantics for arbitrary ASCII suffixes. +Current rule-69 ASCII-unit punctuation-boundary measurement: 440 candidates, 341 exact controls, 99 mismatches, 94 members in the actual `pending_rule_review` subcluster, and 10/99 evaluable mismatches whose first difference is localized to the unit-plus-punctuation output signature. PDF rule 69 requires a Roman terminator after a Roman-written unit in the ordinary case, while rules 33/34 omit it at the listed punctuation or enclosing-mark boundary; the rule 46 PDF example `체중(kg)` is the minimal parenthesized-unit control. Membership is restricted to rule-69 spellings already supported by the engine and does not infer unit semantics for arbitrary ASCII suffixes. Analyzer pre-fix baseline: 440 candidates, 0 exact controls, 440 mismatches, 435 pending members, and 9 signature-local first differences. After the generalized rule-33/34 boundary override and the matching non-math routing guard, the same cohort has 325 exact controls and 115 mismatches. Corpus-wide exact matches moved from 66,039 to 66,436 (+397); the additional gains are applications of the same boundary rule outside this strict ASCII detector, including compatibility-unit forms. The complete standard suite remains 5,141/5,141. -Current decimal-point measurement: 4546 candidates, 3380 exact controls, 1166 mismatches, 1109 members in the actual `pending_rule_review` subcluster, and 260/1166 evaluable mismatches whose first difference is inside the complete decimal-containing word's current-engine signature. Hangeul rules 43 and 48 keep an ASCII point between digits in the numeric sequence and encode it as the decimal point; rules 35 and 69 supply controls for adjacent Roman-number chains and Roman units. This is an implementation-candidate audit, not permission to specialize on a corpus reference. +Current decimal-point measurement: 4546 candidates, 3383 exact controls, 1163 mismatches, 1106 members in the actual `pending_rule_review` subcluster, and 260/1163 evaluable mismatches whose first difference is inside the complete decimal-containing word's current-engine signature. Hangeul rules 43 and 48 keep an ASCII point between digits in the numeric sequence and encode it as the decimal point; rules 35 and 69 supply controls for adjacent Roman-number chains and Roman units. This is an implementation-candidate audit, not permission to specialize on a corpus reference. Analyzer checkpoint `ed20f99` recorded the pre-fix baseline as 4,546 candidates, 2,584 exact controls, 1,962 mismatches, 1,905 pending members, and 875 localized first differences. Its dominant localized transition was reference decimal point `U+2832 ⠲` versus current Roman indicator `U+2834 ⠴` in 647 cases. After the generalized rule-43/48 guard, that transition is absent: 525 cases become exact and the other corrected prefixes expose later independent mismatches. -Current all-caps `OU` measurement: 1816 candidates, 64 exact controls, 1752 mismatches, 1666 members in the actual `pending_rule_review` subcluster, and 1331/1752 evaluable mismatches whose first difference is inside the current-engine output signature for the detected run. This is a pronunciation-sensitive UEB review cohort, not an engine routing rule. +Current all-caps `OU` measurement: 1816 candidates, 64 exact controls, 1752 mismatches, 1666 members in the actual `pending_rule_review` subcluster, and 1332/1752 evaluable mismatches whose first difference is inside the current-engine output signature for the detected run. This is a pronunciation-sensitive UEB review cohort, not an engine routing rule. -Current standalone-uppercase measurement: 62411 candidates, 50395 exact controls, 12016 mismatches, and 10563 members in the actual `pending_rule_review` subcluster. Its high frequency does not make it causal: the same input shape is exact in many cases, and a sentence containing the shape may first differ at another Roman, numeric, or punctuation structure. No engine change is inferred from this cohort. +Current standalone-uppercase measurement: 62411 candidates, 50449 exact controls, 11962 mismatches, and 10504 members in the actual `pending_rule_review` subcluster. Its high frequency does not make it causal: the same input shape is exact in many cases, and a sentence containing the shape may first differ at another Roman, numeric, or punctuation structure. No engine change is inferred from this cohort. -Current Korean-prefixed all-caps parenthetical measurement: 54492 candidates, 44983 exact controls, 9509 mismatches, and 8212 members in the actual `pending_rule_review` subcluster. This is a semantic-collision audit, not an engine routing rule; no implementation change is inferred from its reference outputs. +Current Korean-prefixed all-caps parenthetical measurement: 54492 candidates, 45024 exact controls, 9468 mismatches, and 8167 members in the actual `pending_rule_review` subcluster. This is a semantic-collision audit, not an engine routing rule; no implementation change is inferred from its reference outputs. -Current rule-34 opening-order measurement: 64382 structural candidates, 52774 exact controls, 11608 mismatches, and 1105/11608 evaluable mismatches whose first difference is inside the current engine's Korean opening-parenthesis cells. Only the 1104 localized first-cell transitions have the reference/current order `⠴` versus `⠦`. After requiring the complete reference `⠴⠐⠣` versus current/PDF `⠦⠄⠴` three-cell signature and preserving higher-priority comparison classifications, 1101 are classified as `corpus_suspect`; mere coexistence with a Korean-prefixed Roman annotation does not change a primary class. +Current rule-34 opening-order measurement: 64382 structural candidates, 52846 exact controls, 11536 mismatches, and 1111/11536 evaluable mismatches whose first difference is inside the current engine's Korean opening-parenthesis cells. Only the 1110 localized first-cell transitions have the reference/current order `⠴` versus `⠦`. After requiring the complete reference `⠴⠐⠣` versus current/PDF `⠦⠄⠴` three-cell signature and preserving higher-priority comparison classifications, 1107 are classified as `corpus_suspect`; mere coexistence with a Korean-prefixed Roman annotation does not change a primary class. Current all-caps Roman middle-dot measurement: 97 candidates, 0 exact controls, 97 mismatches, and 96 members in the actual `pending_rule_review` subcluster. This cross-cutting cohort preserves every primary class and is not an engine routing rule. Current Roman-before-middle-dot boundary measurement: 577 candidates, 0 exact controls, 577 mismatches, 565 members in the actual `pending_rule_review` subcluster, and 428/577 evaluable mismatches whose first difference is localized to the one current terminator immediately before the middle dot. The locator encodes each real input prefix ending at the dot, so identifier state such as `K-ICS·...` is retained without consulting expected. This raises localized target coverage from 342 to 417 and reduces the then-leading residual `⠐ -> ⠲` transition from 297 to 223. Rules 29, 33, and 50 support the current terminator path but do not support the localized reference omission; no engine change or primary-class rewrite is inferred. -Current attached Roman-to-Korean boundary measurement: 17693 candidates, 12924 exact controls, 4769 mismatches, 3939 members in the actual `pending_rule_review` subcluster, and 0/4769 evaluable mismatches whose first difference is localized to the current boundary marker. The localized target `⠲ -> ⠸` occurs 0 times and the reverse `⠸ -> ⠲` occurs 0 times. Korean rule 29 (2024 Korean-rules PDF p.26, printed p.20) requires a Roman terminator around Roman text in a Korean sentence, whereas rule 39 (PDF pp.31-32, printed pp.25-26) applies the Korean opening/closing markers only when Roman text is the sentence's main language. Among exact candidates, 10960 expose the current rule-29 terminator at such a boundary and 0 expose the current rule-39 opening. Its printed controls include both an English sentence (`What is 김치 in English?`) and the address `www.대통령.kr` inside a Korean sentence. Official UEB 2.4.7 (2024 UEB PDF p.41, printed p.13) confirms that a UEB mode does not extend through a switch to another braille code, but does not choose whether this Korean boundary belongs to a Korean-main or Roman-main context. Therefore the attached script boundary alone cannot distinguish ordinary Korean prose from an embedded Roman-domain context. Primary classes are preserved and no engine change is inferred without a narrower input-derived dominance gate. At diagnostic checkpoint `57c4608`, this same cohort was 17,693 candidates / 12,724 exact / 4,969 mismatch, with 268 localized `⠲ -> ⠸`, zero reverse, 10,780 exact rule-29 markers, and zero exact rule-39 markers. After narrowing the engine by dominance and the PDF domain shape, it is 12,924 exact / 4,769 mismatch with no localized target or reverse; corpus-wide exact rises from 67,715 to 68,101 (+386). The remaining 1 mismatch with a current rule-39 opening is a list-heavy Korean sentence whose ASCII-leading brand tokens satisfy the mechanical word-count majority; its first difference is not localized to this boundary, so it remains a semantic pending control rather than grounds for another engine branch. +Current attached Roman-to-Korean boundary measurement: 17693 candidates, 12934 exact controls, 4759 mismatches, 3927 members in the actual `pending_rule_review` subcluster, and 0/4759 evaluable mismatches whose first difference is localized to the current boundary marker. The localized target `⠲ -> ⠸` occurs 0 times and the reverse `⠸ -> ⠲` occurs 0 times. Korean rule 29 (2024 Korean-rules PDF p.26, printed p.20) requires a Roman terminator around Roman text in a Korean sentence, whereas rule 39 (PDF pp.31-32, printed pp.25-26) applies the Korean opening/closing markers only when Roman text is the sentence's main language. Among exact candidates, 10966 expose the current rule-29 terminator at such a boundary and 0 expose the current rule-39 opening. Its printed controls include both an English sentence (`What is 김치 in English?`) and the address `www.대통령.kr` inside a Korean sentence. Official UEB 2.4.7 (2024 UEB PDF p.41, printed p.13) confirms that a UEB mode does not extend through a switch to another braille code, but does not choose whether this Korean boundary belongs to a Korean-main or Roman-main context. Therefore the attached script boundary alone cannot distinguish ordinary Korean prose from an embedded Roman-domain context. Primary classes are preserved and no engine change is inferred without a narrower input-derived dominance gate. At diagnostic checkpoint `57c4608`, this same cohort was 17,693 candidates / 12,724 exact / 4,969 mismatch, with 268 localized `⠲ -> ⠸`, zero reverse, 10,780 exact rule-29 markers, and zero exact rule-39 markers. After narrowing the engine by dominance and the PDF domain shape, it is 12,924 exact / 4,769 mismatch with no localized target or reverse; corpus-wide exact rises from 67,715 to 68,101 (+386). The remaining 1 mismatch with a current rule-39 opening is a list-heavy Korean sentence whose ASCII-leading brand tokens satisfy the mechanical word-count majority; its first difference is not localized to this boundary, so it remains a semantic pending control rather than grounds for another engine branch. Current rule-39 narrowed-scope audit: 947 candidates, 385 exact controls, 562 mismatches, and 550 members in the actual `pending_rule_review` subcluster. This input-derived scope is recorded separately from the direct-boundary output-localizer; primary classes are unchanged. At the implementation checkpoint, this conservative first-script-word approximation contains 385 exact cases, accounting for all but one of the corpus-wide +386 net gain; no localized reverse transition is observed in the direct-boundary cohort. @@ -2965,10 +3046,10 @@ The audit starts from all raw encoding errors, then separates cases already reso | Encoding-error audit | Cases | |---|---:| | Raw encoding errors | 450 | -| Resolved by comparison method | 272 | +| Resolved by comparison method | 274 | | Excluded as corpus suspect | 0 | -| Unresolved encoding-error review cases | 178 | -| Explained by singleton unsupported character(s) | 178 | +| Unresolved encoding-error review cases | 176 | +| Explained by singleton unsupported character(s) | 176 | | Multiple singleton unsupported characters | 2 | | Unclassified without a singleton explanation | 0 | @@ -2979,11 +3060,11 @@ The audit starts from all raw encoding errors, then separates cases already reso | Error message | Cases | |---|---:| -| `Invalid symbol character` | 178 | +| `Invalid symbol character` | 176 | | Error family | Cases | |---|---:| -| `enclosed_organization_mark` | 159 | +| `enclosed_organization_mark` | 157 | | `letterlike_unit_symbol` | 9 | | `punctuation_or_layout_symbol` | 12 | @@ -3002,24 +3083,24 @@ Families are diagnostics, not automatic normalization permissions. Rules 68/69 c | `U+2E31 ⸱` | 2 | `⸱` | `punctuation_or_layout_symbol` | | `U+302E 〮` | 1 | `〮` | `punctuation_or_layout_symbol` | | `U+3214 ㈔` | 2 | `(사)` | `enclosed_organization_mark` | -| `U+321C ㈜` | 157 | `(주)` | `enclosed_organization_mark` | +| `U+321C ㈜` | 155 | `(주)` | `enclosed_organization_mark` | ## Shards | Shard | Exact | Total | Accuracy | |---|---:|---:|---:| -| `sentence_01.json` | 20420 | 25000 | 81.68% | -| `sentence_02.json` | 19950 | 25000 | 79.80% | -| `sentence_03.json` | 20516 | 25000 | 82.06% | -| `sentence_04.json` | 7215 | 8528 | 84.60% | +| `sentence_01.json` | 20448 | 25000 | 81.79% | +| `sentence_02.json` | 19963 | 25000 | 79.85% | +| `sentence_03.json` | 20543 | 25000 | 82.17% | +| `sentence_04.json` | 7221 | 8528 | 84.67% | ## Overlapping mismatch traits | Trait | Count | |---|---:| -| `contains_ascii_digits` | 12140 | -| `contains_ascii_letters` | 15241 | -| `contains_delimiter_or_quote` | 15427 | +| `contains_ascii_digits` | 12094 | +| `contains_ascii_letters` | 15167 | +| `contains_delimiter_or_quote` | 15353 | | `input_not_nfkc` | 716 | ## Samples @@ -3200,6 +3281,7 @@ Against the immediately preceding 63,399-exact run, exact matches increased by 2 | Rule-29 consecutive Roman entry idempotence | 5,141/5,141 | 67,442/83,528 | 80.74% | An explicit Roman-entry event is ignored only when final emit state is already inside the same Roman section | | Rules 29/71 complete attached Roman ampersand run | 5,141/5,141 | 67,715/83,528 | 81.07% | Complete ASCII-letter segments joined by `&` retain one Roman section; official `AT&T`, `B&B`, and spaced Korean Rule-71 controls delimit the safe boundary; 273 cases became exact | | Rules 29/39 Roman-to-Korean mode ownership | 5,141/5,141 | 68,101/83,528 | 81.53% | Same-token Korean is wrapped only in a Roman-majority document or the official dot-delimited `www.대통령.kr` domain shape; all three rule-39 PDF controls remain exact and 386 corpus cases became exact | +| Rules 29/34 multiword Roman parenthetical continuity | 5,141/5,141 | 68,175/83,528 | 81.62% | A backwards-verified, closed letter-and-space Roman parenthetical tail stays on the prose route; function calls, digits, operators, nested brackets, and punctuation-separated forms remain outside; 74 cases became exact | The latest full `cargo test -p braillify test_by_testcase --release -- --nocapture` run was accepted from its custom testcase summary, not the trailing filtered harness: `총 테스트 케이스: 5141`, `성공: 5141`, `실패: 0`, and `Skip (limitation): 0`. diff --git a/libs/braillify/examples/nikl_corpus_analyze.rs b/libs/braillify/examples/nikl_corpus_analyze.rs index 0a8074b6..f9d8ac1f 100644 --- a/libs/braillify/examples/nikl_corpus_analyze.rs +++ b/libs/braillify/examples/nikl_corpus_analyze.rs @@ -619,6 +619,8 @@ const KOREAN_PREFIXED_ROMAN_PARENTHETICAL_HYPHEN_SUFFIX: &str = "korean_prefixed_roman_parenthetical_followed_by_allcaps_hyphen_suffix"; const CONSECUTIVE_ROMAN_UPPERCASE_WORD_REENTRY: &str = "uppercase_word_after_whitespace_continuing_ascii_roman_text"; +const CONSECUTIVE_ASCII_ROMAN_WORD_BOUNDARY: &str = + "consecutive_ascii_roman_words_whitespace_boundary"; const ROMAN_PARENTHETICAL_AFTER_NONLETTER_BOUNDARY: &str = "closed_roman_parenthetical_after_non_ascii_letter_boundary"; @@ -1426,6 +1428,111 @@ fn consecutive_roman_uppercase_word_spans(input: &str) -> Vec { spans } +/// Finds adjacent ASCII-letter words separated only by one or more whitespace +/// characters. The gate is deliberately indifferent to capitalization and +/// lexical meaning; rule 29's official `Los Angeles` and `Table of Contents` +/// are exact controls for this structural boundary. +fn consecutive_ascii_roman_word_boundary_spans(input: &str) -> Vec { + let bytes = input.as_bytes(); + let mut spans = Vec::new(); + let mut cursor = 0usize; + while cursor < bytes.len() { + if !bytes[cursor].is_ascii_alphabetic() + || input[..cursor] + .chars() + .next_back() + .is_some_and(|previous| previous.is_ascii_alphanumeric()) + { + cursor += input[cursor..] + .chars() + .next() + .expect("cursor must remain on a character boundary") + .len_utf8(); + continue; + } + let start_byte = cursor; + while bytes.get(cursor).is_some_and(u8::is_ascii_alphabetic) { + cursor += 1; + } + let whitespace_start = cursor; + while input[cursor..] + .chars() + .next() + .is_some_and(char::is_whitespace) + { + cursor += input[cursor..] + .chars() + .next() + .expect("whitespace cursor must remain valid") + .len_utf8(); + } + if cursor == whitespace_start || !bytes.get(cursor).is_some_and(u8::is_ascii_alphabetic) { + cursor = whitespace_start.max(start_byte + 1); + continue; + } + while bytes.get(cursor).is_some_and(u8::is_ascii_alphabetic) { + cursor += 1; + } + spans.push(InputSpan { + start_byte, + end_byte: cursor, + }); + cursor = whitespace_start; + } + spans +} + +/// Locates only the current cell at a consecutive-Roman whitespace boundary. +/// A real input prefix ending after the first word normally ends in rule 29's +/// terminator. The complete output either retains that cell (the suspected +/// premature exit) or replaces its position with the printed whitespace. +fn consecutive_ascii_roman_boundary_actual_ranges( + input: &str, + actual: &str, +) -> Vec> { + let actual_cells = actual.chars().collect::>(); + consecutive_ascii_roman_word_boundary_spans(input) + .into_iter() + .filter_map(|span| { + let boundary_offset = input[span.start_byte..span.end_byte] + .char_indices() + .find_map(|(offset, ch)| ch.is_whitespace().then_some(offset))?; + let boundary_byte = span.start_byte + boundary_offset; + let prefix = braillify::encode_to_unicode(&input[..boundary_byte]).ok()?; + let prefix_cells = prefix.chars().collect::>(); + let boundary = prefix_cells.len().checked_sub(1)?; + if prefix_cells.get(boundary) != Some(&'⠲') { + return None; + } + if actual_cells.get(..prefix_cells.len()) == Some(prefix_cells.as_slice()) { + return Some((boundary, boundary + 1)); + } + if actual_cells.get(..boundary) == Some(&prefix_cells[..boundary]) + && actual_cells.get(boundary) == Some(&'⠀') + { + return Some((boundary, boundary + 1)); + } + None + }) + .collect::>() + .into_iter() + .map(|(start, end)| start..end) + .collect() +} + +fn first_difference_at_consecutive_ascii_roman_word_boundary(item: &EncodedCase) -> bool { + let Ok(actual) = &item.actual else { + return false; + }; + if actual == &item.located.case.unicode { + return false; + } + let first_difference = first_difference_cell(&item.located.case.unicode, actual); + consecutive_ascii_roman_boundary_actual_ranges(&item.located.case.input, actual) + .into_iter() + .any(|range| range.contains(&first_difference)) +} + /// Finds a closed, non-nested parenthetical whose body starts with a Roman /// letter and whose opening does not immediately follow another ASCII letter. /// This includes rule-34 enclosure contexts after Korean, digits, whitespace, @@ -2156,7 +2263,7 @@ fn first_difference_claimed_before_uppercase_alphanumeric_roman_digit(item: &Enc || first_difference_at_attached_ascii_roman_to_korean_boundary(item) } -fn first_difference_claimed_by_localized_cohort(item: &EncodedCase) -> bool { +fn first_difference_claimed_before_consecutive_ascii_roman_boundary(item: &EncodedCase) -> bool { first_difference_claimed_before_uppercase_alphanumeric_roman_digit(item) || first_difference_at_input_span_entry( item, @@ -2165,6 +2272,11 @@ fn first_difference_claimed_by_localized_cohort(item: &EncodedCase) -> bool { ) } +fn first_difference_claimed_by_localized_cohort(item: &EncodedCase) -> bool { + first_difference_claimed_before_consecutive_ascii_roman_boundary(item) + || first_difference_at_consecutive_ascii_roman_word_boundary(item) +} + /// Input-only candidate gate for acronym expansions such as /// `HCA(Home Connectivity Alliance)`. /// @@ -3122,6 +3234,10 @@ fn analyze( CONSECUTIVE_ROMAN_UPPERCASE_WORD_REENTRY.to_string(), PendingRuleReviewClusterStats::default(), ), + ( + CONSECUTIVE_ASCII_ROMAN_WORD_BOUNDARY.to_string(), + PendingRuleReviewClusterStats::default(), + ), ( ROMAN_PARENTHETICAL_AFTER_NONLETTER_BOUNDARY.to_string(), PendingRuleReviewClusterStats::default(), @@ -3477,6 +3593,15 @@ fn analyze( ), true, ), + ( + CONSECUTIVE_ASCII_ROMAN_WORD_BOUNDARY, + !consecutive_ascii_roman_word_boundary_spans(&item.located.case.input).is_empty(), + Some( + !first_difference_claimed_before_consecutive_ascii_roman_boundary(item) + && first_difference_at_consecutive_ascii_roman_word_boundary(item), + ), + true, + ), ( ROMAN_PARENTHETICAL_AFTER_NONLETTER_BOUNDARY, !roman_parenthetical_after_nonletter_boundary_spans(&item.located.case.input) @@ -3936,6 +4061,12 @@ fn markdown(report: &AnalysisReport) -> String { segments joined directly by `&`, excludes spaced/Korean/alphanumeric continuations, and \ localizes only the current output cell immediately before the ampersand through an \ independently encoded real-input prefix. The \ + `consecutive_ascii_roman_words_whitespace_boundary` gate requires two adjacent \ + ASCII-letter words separated only by whitespace. For each boundary it independently \ + encodes the real input prefix ending after the first word, then localizes only the \ + current rule-29 terminator at that position or the full output's replacing blank. This \ + prevents an unrelated Roman occurrence elsewhere in the sentence from satisfying the \ + audit. Slash and other punctuation-separated forms are deliberately excluded. The \ `decimal_point_between_ascii_digits` gate finds \ whitespace-delimited words containing `digit.digit` and reproduces each whole word in a \ neutral Korean context, so suffixes and punctuation remain part of the current-engine \ @@ -4953,6 +5084,61 @@ fn markdown(report: &AnalysisReport) -> String { stats.output_signature_mismatches_evaluated )); } + if let Some(stats) = report + .pending_rule_review_clusters + .get(CONSECUTIVE_ASCII_ROMAN_WORD_BOUNDARY) + { + let pending = stats + .mismatch_primary_classes + .get("pending_rule_review") + .copied() + .unwrap_or(0); + let target = stats + .first_difference_in_output_signature_transitions + .get("U+2800 ⠀ -> U+2832 ⠲") + .copied() + .unwrap_or(0); + let reverse = stats + .first_difference_in_output_signature_transitions + .get("U+2832 ⠲ -> U+2800 ⠀") + .copied() + .unwrap_or(0); + text.push_str(&format!( + "\nCurrent consecutive ASCII-Roman word-boundary measurement: {} candidates, {} \ + exact controls, {} mismatches, {pending} members in the actual \ + `pending_rule_review` subcluster, and {}/{} evaluable mismatches whose first \ + difference is localized to the current boundary cell. The target reference blank \ + versus current terminator (`⠀ -> ⠲`) occurs {target} times; the exact reverse \ + (`⠲ -> ⠀`) occurs {reverse} times. Korean rule 29 (2024 Korean-rules PDF p.26, \ + printed p.20) explicitly treats consecutive Roman items as one Roman section, with \ + one Roman indicator before the first item and one terminator after the last; its \ + printed `Los Angeles` and `Table of Contents` examples are exact controls. Rule 34 \ + (PDF p.29, printed p.23) separately omits the terminator before a closing enclosure, \ + while official UEB 9.7.1 (2024 UEB PDF p.137, printed p.109) prints the multiword \ + prose enclosure `plays (such as Romeo and Juliet)` with ordinary internal spaces \ + and nested closing punctuation. Together they support preserving a complete \ + letter-and-space Roman parenthetical as prose, but not a fragment containing a \ + function-call opener, digit, operator, or nested bracket. The input-only gate does not decide \ + whether punctuation-separated text such as `ESS /VPP` continues the same section, \ + so that variant remains outside this cohort. Primary classes are preserved. Before \ + the narrow token-routing change this cohort was 4,679 candidates / 2,058 exact / \ + 2,621 mismatch, with 128 localized `⠀ -> ⠲` and zero reverse. The cause was the \ + final `Letters)` token of a whitespace-split Roman parenthetical being rerouted as \ + math, which made the preceding Roman word terminate early and introduced an extra \ + blank. After excluding only a backwards-verified complete Roman parenthetical tail \ + from math routing, the cohort is 2,132 exact / 2,547 mismatch, with 23 target-localized \ + and zero reverse; corpus-wide exact likewise rises by 74, from 68,101 to 68,175. \ + The 105 removed localized boundaries include 74 newly exact cases and 31 cases that \ + still differ elsewhere. Remaining targets such as `SYNO PEM-1`, `ACE Fair(2020)`, \ + and `Mnet K-POP` do not satisfy the complete-parenthetical gate and remain separate \ + residuals rather than widening this rule.\n", + stats.candidates, + stats.exact, + stats.mismatch, + stats.first_difference_in_output_signature, + stats.output_signature_mismatches_evaluated + )); + } if let Some(stats) = report .pending_rule_review_clusters .get(SINGLE_CAPITAL_PARENTHESIZED_DIGITS) @@ -5676,7 +5862,8 @@ fn markdown(report: &AnalysisReport) -> String { | UEB complete all-caps segments across hyphen | 5,141/5,141 | 67,138/83,528 | 80.38% | The grade-1 restart is omitted only between a complete uppercase prefix and an uppercase suffix of at least two letters; mixed/single-capital and digit-hyphen controls remain unchanged |\n\ | Rule-29 consecutive Roman entry idempotence | 5,141/5,141 | 67,442/83,528 | 80.74% | An explicit Roman-entry event is ignored only when final emit state is already inside the same Roman section |\n\ | Rules 29/71 complete attached Roman ampersand run | 5,141/5,141 | 67,715/83,528 | 81.07% | Complete ASCII-letter segments joined by `&` retain one Roman section; official `AT&T`, `B&B`, and spaced Korean Rule-71 controls delimit the safe boundary; 273 cases became exact |\n\ - | Rules 29/39 Roman-to-Korean mode ownership | 5,141/5,141 | 68,101/83,528 | 81.53% | Same-token Korean is wrapped only in a Roman-majority document or the official dot-delimited `www.대통령.kr` domain shape; all three rule-39 PDF controls remain exact and 386 corpus cases became exact |\n", + | Rules 29/39 Roman-to-Korean mode ownership | 5,141/5,141 | 68,101/83,528 | 81.53% | Same-token Korean is wrapped only in a Roman-majority document or the official dot-delimited `www.대통령.kr` domain shape; all three rule-39 PDF controls remain exact and 386 corpus cases became exact |\n\ + | Rules 29/34 multiword Roman parenthetical continuity | 5,141/5,141 | 68,175/83,528 | 81.62% | A backwards-verified, closed letter-and-space Roman parenthetical tail stays on the prose route; function calls, digits, operators, nested brackets, and punctuation-separated forms remain outside; 74 cases became exact |\n", ); text.push_str( "\nThe latest full `cargo test -p braillify test_by_testcase --release -- --nocapture` \ @@ -6383,6 +6570,58 @@ mod tests { assert_eq!(actual, expected); } + #[rstest::rstest] + #[case::pdf_los_angeles( + "그녀는 Los Angeles의 한인 타운에 살고 있다.", + vec!["Los Angeles"] + )] + #[case::pdf_table_of_contents( + "Table of Contents", + vec!["Table of", "of Contents"] + )] + #[case::parenthesized_name("(Max Anderson)", vec!["Max Anderson"])] + #[case::allcaps_pair("(EU CSRD)", vec!["EU CSRD"])] + #[case::slash_separated("ESS /VPP", vec![])] + #[case::korean_words("한인 타운", vec![])] + fn detects_consecutive_ascii_roman_word_boundaries( + #[case] input: &str, + #[case] expected: Vec<&str>, + ) { + let actual = consecutive_ascii_roman_word_boundary_spans(input) + .into_iter() + .map(|span| &input[span.start_byte..span.end_byte]) + .collect::>(); + assert_eq!(actual, expected); + } + + #[rstest::rstest] + #[case::pdf_rule_29("그녀는 Los Angeles의 한인 타운에 살고 있다.", vec!['⠀'])] + #[case::corpus_parenthesized_name( + "LCK 글로벌 중계진은 지난 해와 마찬가지로 ‘아틀러스(Atlus)’ 맥스 앤더슨(Max Anderson), ‘발데스(Valdes)’ 브랜든 발데스(Brendan Valdes), ‘울프(Wolf)’ 울프 슈뢰더(Wolf Schroeder)와 ‘크로니클러(Chronicler)’ 모리츠 뮈센(Maurits Meeusen)이", + vec!['⠀', '⠀', '⠀', '⠀'] + )] + fn locates_current_consecutive_ascii_roman_boundary_cell( + #[case] input: &str, + #[case] expected_markers: Vec, + ) { + let actual = braillify::encode_to_unicode(input).expect("Roman boundary probe must encode"); + let ranges = consecutive_ascii_roman_boundary_actual_ranges(input, &actual); + let markers = ranges + .iter() + .map(|range| { + actual + .chars() + .nth(range.start) + .expect("localized boundary must exist") + }) + .collect::>(); + + assert_eq!( + markers, expected_markers, + "the locator must identify each occurrence-specific current boundary" + ); + } + #[rstest::rstest] #[case::pdf_rule_34("링컨(Lincoln)은", vec!["(Lincoln)"])] #[case::after_digit("웹3(Web3)", vec!["(Web3)"])] diff --git a/libs/braillify/src/rules/token_rules/math_expression/apply.rs b/libs/braillify/src/rules/token_rules/math_expression/apply.rs index 5a2c5066..195af0ba 100644 --- a/libs/braillify/src/rules/token_rules/math_expression/apply.rs +++ b/libs/braillify/src/rules/token_rules/math_expression/apply.rs @@ -144,6 +144,64 @@ fn prev_word_is_math_product_cue(tokens: &[Token<'_>], index: usize) -> bool { .is_some_and(|word| word.text.as_ref() == "곱") } +/// Returns true when `word` is the final fragment of a whitespace-split, +/// closed Roman parenthetical whose earlier fragments contain letters only. +/// +/// Korean rules 29 and 34 keep consecutive Roman words in one Roman section +/// and omit its terminator before the closing parenthesis. UEB 9.7.1 likewise +/// prints multiword prose inside one paired parenthesis. The token parser keeps +/// the spaces as separate tokens, so the final `Letters)` fragment must not be +/// mistaken for a standalone mathematical expression merely because it has a +/// closing bracket. An ASCII letter immediately before the opening parenthesis +/// is excluded so function-call syntax such as `f(x)` remains math-owned. +fn is_multiword_closed_roman_parenthetical_tail( + tokens: &[Token<'_>], + index: usize, + word: &WordToken<'_>, +) -> bool { + let Some(close) = word.chars.iter().position(|ch| *ch == ')') else { + return false; + }; + let body = &word.chars[..close]; + let trailing = &word.chars[close + 1..]; + if body.is_empty() + || !body.iter().all(char::is_ascii_alphabetic) + || !trailing + .iter() + .all(|ch| matches!(*ch, ',' | '.' | ';' | ':' | '!' | '?' | '\'' | '"')) + { + return false; + } + + let mut cursor = index.checked_sub(1); + while let Some(i) = cursor { + match tokens.get(i) { + Some(Token::Space(_)) => cursor = i.checked_sub(1), + Some(Token::Word(previous)) => { + let previous_text = previous.text.as_ref(); + if let Some(open) = previous_text.rfind('(') { + let before = &previous_text[..open]; + let after = &previous_text[open + 1..]; + return !after.is_empty() + && after.chars().all(|ch| ch.is_ascii_alphabetic()) + && !before + .chars() + .next_back() + .is_some_and(|ch| ch.is_ascii_alphabetic()) + && !before.chars().any(|ch| matches!(ch, '(' | ')')); + } + if previous_text.chars().all(|ch| ch.is_ascii_alphabetic()) { + cursor = i.checked_sub(1); + } else { + return false; + } + } + _ => return false, + } + } + false +} + /// Walks backward from `index - 1`, skipping `Space`, returning whether the /// preceding content is a math-letter Word or a math-context PreEncoded. fn prev_is_math_context_for_ellipsis(tokens: &[Token<'_>], index: usize) -> bool { @@ -287,6 +345,10 @@ pub(super) fn run<'a>( let text = word.text.as_ref(); + if is_multiword_closed_roman_parenthetical_tail(tokens, index, word) { + return Ok(TokenAction::Noop); + } + // PDF 수학 제60/61항 — `a ≲ b:`, `p ⊻ q:` 같이 단일 letter + 관계기호 + 단일 // letter + 콜론 패턴의 inline math expression. 콜론 이전까지를 하나의 math // expression으로 병합해 인코딩한다 (letter들이 산문 quote-wrap되지 않도록). @@ -837,6 +899,32 @@ pub(super) fn run<'a>( #[cfg(test)] mod tests { use super::*; + + #[rstest::rstest] + #[case::ueb_multiword_parenthetical("plays (such as Romeo and Juliet)", true)] + #[case::ueb_letter_list("(q, r)", false)] + #[case::math_function("f(x)", false)] + #[case::operator_interrupts_prose_run("(x + y)", false)] + fn recognizes_only_complete_multiword_roman_parenthetical_tails( + #[case] input: &str, + #[case] expected: bool, + ) { + let ir = crate::rules::token::DocumentIR::parse(input, true); + let index = ir + .tokens + .iter() + .rposition(|token| matches!(token, Token::Word(_))) + .expect("probe must contain a word"); + let Token::Word(word) = &ir.tokens[index] else { + unreachable!("selected token must be a word"); + }; + + assert_eq!( + is_multiword_closed_roman_parenthetical_tail(&ir.tokens, index, word), + expected + ); + } + use crate::rules::token::{SpaceKind, WordMeta, WordToken}; use std::borrow::Cow; From 5d54472c3ad78048c73d4e2f169b1e75bc5ae869 Mon Sep 17 00:00:00 2001 From: devfive Date: Tue, 1 Sep 2026 15:03:15 +0900 Subject: [PATCH 44/77] Localize Roman parenthetical spacing boundaries --- docs/corpus-analysis/NIKL_2025_V1.md | 276 +++++++++--------- .../braillify/examples/nikl_corpus_analyze.rs | 81 ++++- 2 files changed, 209 insertions(+), 148 deletions(-) diff --git a/docs/corpus-analysis/NIKL_2025_V1.md b/docs/corpus-analysis/NIKL_2025_V1.md index ee7c9763..d7ae38ef 100644 --- a/docs/corpus-analysis/NIKL_2025_V1.md +++ b/docs/corpus-analysis/NIKL_2025_V1.md @@ -298,49 +298,26 @@ This ranking removes only cases whose first difference is inside an existing out | Rank | Expected → actual first cell | Residual cases | |---:|---|---:| -| 1 | `U+2826 ⠦ -> U+2800 ⠀` | 226 | -| 2 | `U+2810 ⠐ -> U+2832 ⠲` | 225 | -| 3 | `U+2810 ⠐ -> U+2802 ⠂` | 212 | -| 4 | `U+2834 ⠴ -> U+2800 ⠀` | 176 | -| 5 | `U+2820 ⠠ -> U+2809 ⠉` | 169 | -| 6 | `U+2817 ⠗ -> U+2820 ⠠` | 164 | -| 7 | `U+283C ⠼ -> U+2800 ⠀` | 153 | -| 8 | `U+2826 ⠦ -> U+2810 ⠐` | 147 | -| 9 | `U+281B ⠛ -> U+2823 ⠣` | 144 | -| 10 | `U+2810 ⠐ -> U+2800 ⠀` | 130 | -| 11 | `U+2820 ⠠ -> U+281E ⠞` | 108 | -| 12 | `U+2811 ⠑ -> U+283B ⠻` | 101 | -| 13 | `U+2800 ⠀ -> U+2832 ⠲` | 100 | -| 14 | `U+2810 ⠐ -> U+2814 ⠔` | 98 | -| 15 | `U+2820 ⠠ -> U+280E ⠎` | 98 | -| 16 | `U+280E ⠎ -> U+2820 ⠠` | 92 | -| 17 | `U+2803 ⠃ -> U+2820 ⠠` | 87 | -| 18 | `U+2804 ⠄ -> U+2834 ⠴` | 87 | -| 19 | `U+2824 ⠤ -> U+2800 ⠀` | 78 | -| 20 | `U+2826 ⠦ -> U+2834 ⠴` | 76 | - -### Residual `U+2826 ⠦ -> U+2800 ⠀` - -- `sentence_01.json` #234: 안랩은 블록체인 자회사 안랩블록체인컴퍼니가 웹(Web)3 지갑인 ‘ABC 월렛(Wallet)’ 모바일 버전을 구글플레이와 애플 앱스토어에 출시했다고 10일 발표했다. - - expected: `⠎⠉⠕⠫⠀⠏⠗⠃⠦⠄⠴⠠⠺⠑⠃⠠⠴⠼⠉⠀⠨⠕⠫⠃` - - actual: `⠎⠉⠕⠫⠀⠏⠗⠃⠀⠀⠦⠠⠺⠑⠃⠴⠼⠉⠀⠨⠕⠫⠃⠟` - - first differing cell (zero-based): 44 - - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_02.json` #168: 5주 과정인 ‘도전’ 프로그램과 5개월 과정인 ‘도전+(플러스)’ 프로그램으로 나뉘어 진행되며, 프로그램 이수 시 1개월에 50만 원씩 최대 300만 원까지 참여 수당과 인센티브를 지원한다. - - expected: `⠠⠦⠊⠥⠨⠾⠀⠢⠦⠄⠙⠮⠐⠎⠠⠪⠠⠴⠴⠄⠀⠙⠪⠐` - - actual: `⠠⠦⠊⠥⠨⠾⠀⠢⠀⠦⠄⠙⠮⠐⠎⠠⠪⠠⠴⠴⠄⠀⠙⠪` - - first differing cell (zero-based): 53 - - current primary/reason: `pending_rule_review` / `number_rule_review` -- `sentence_03.json` #352: 인천시는 경제성 향상을 위해 유정복 인천시장의 민선8기 1호 공약인 제물포르네상스와 3기 신도시인 광명·시흥 공공주택지구 등 신규 개발계획을 반영하고, 수도권광역급행철도(GTX)-D Y자(인천공항행)와 연계 방안 등을 중점 검토한다. - - expected: `⠃⠚⠗⠶⠰⠞⠊⠥⠦⠄⠴⠠⠠⠛⠞⠭⠠⠴⠤⠴⠠⠙⠀⠰` - - actual: `⠃⠚⠗⠶⠰⠞⠊⠥⠀⠀⠦⠠⠠⠛⠞⠭⠴⠔⠠⠙⠀⠴⠠⠽` - - first differing cell (zero-based): 169 - - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_04.json` #441: 10~11일에는 지역 주민들과 함께 하는 전야제를 포함해 아주대 50년사 출판 기념보고회, 인공지능(AI)·6G 융합 콜로키움 시리즈가 열린다. - - expected: `⠟⠈⠿⠨⠕⠉⠪⠶⠦⠄⠴⠠⠠⠁⠊⠠⠴⠐⠆⠼⠋⠴⠠⠛` - - actual: `⠟⠈⠿⠨⠕⠉⠪⠶⠀⠀⠦⠠⠠⠁⠊⠴⠐⠼⠋⠠⠛⠀⠩⠶` - - first differing cell (zero-based): 93 - - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +| 1 | `U+2810 ⠐ -> U+2832 ⠲` | 220 | +| 2 | `U+2810 ⠐ -> U+2802 ⠂` | 212 | +| 3 | `U+2817 ⠗ -> U+2820 ⠠` | 164 | +| 4 | `U+2834 ⠴ -> U+2800 ⠀` | 154 | +| 5 | `U+283C ⠼ -> U+2800 ⠀` | 153 | +| 6 | `U+281B ⠛ -> U+2823 ⠣` | 144 | +| 7 | `U+2810 ⠐ -> U+2800 ⠀` | 130 | +| 8 | `U+2826 ⠦ -> U+2810 ⠐` | 125 | +| 9 | `U+2820 ⠠ -> U+281E ⠞` | 106 | +| 10 | `U+2811 ⠑ -> U+283B ⠻` | 101 | +| 11 | `U+2800 ⠀ -> U+2832 ⠲` | 100 | +| 12 | `U+2810 ⠐ -> U+2814 ⠔` | 98 | +| 13 | `U+280E ⠎ -> U+2820 ⠠` | 92 | +| 14 | `U+2803 ⠃ -> U+2820 ⠠` | 87 | +| 15 | `U+2804 ⠄ -> U+2834 ⠴` | 87 | +| 16 | `U+2826 ⠦ -> U+2800 ⠀` | 79 | +| 17 | `U+2824 ⠤ -> U+2800 ⠀` | 78 | +| 18 | `U+280E ⠎ -> U+2829 ⠩` | 74 | +| 19 | `U+2815 ⠕ -> U+2800 ⠀` | 74 | +| 20 | `U+2820 ⠠ -> U+2809 ⠉` | 65 | ### Residual `U+2810 ⠐ -> U+2832 ⠲` @@ -388,52 +365,6 @@ This ranking removes only cases whose first difference is inside an existing out - first differing cell (zero-based): 90 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -### Residual `U+2834 ⠴ -> U+2800 ⠀` - -- `sentence_01.json` #947: 코나체인은 코나아이와 함께 한국은행의 디지털 화폐(CBDC)의 H/W Wallet의 개발에 참여하였고, 현재 블록체인 기반의 설문조사, 투표서비스를 제공 중이다. - - expected: `⠉⠃⠙⠉⠠⠴⠺⠀⠴⠠⠓⠸⠌⠠⠺⠀⠠⠺⠁⠇⠇⠑⠞⠲` - - actual: `⠉⠃⠙⠉⠠⠴⠺⠀⠀⠠⠓⠸⠌⠠⠺⠀⠴⠠⠺⠁⠇⠇⠑⠞` - - first differing cell (zero-based): 57 - - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_02.json` #865: ‘2023 삼성 AI/CE 챌린지’는 인공지능(AI)과 컴퓨터 공학(CE) 분야 국내 우수 인력 발굴과 차세대 반도체 연구 역량 강화를 위해 개최되고 있으며, 2021년을 시작으로 3회째를 맞이했다. - - expected: `⠃⠉⠀⠇⠢⠠⠻⠀⠴⠠⠠⠁⠊⠸⠌⠠⠠⠉⠑⠲⠀⠰⠗⠂` - - actual: `⠃⠉⠀⠇⠢⠠⠻⠀⠀⠠⠠⠁⠊⠸⠌⠠⠠⠉⠑⠀⠰⠗⠂⠐` - - first differing cell (zero-based): 13 - - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_03.json` #2455: 한편, AI+ 인증은 한국표준협회(KSA)가 국제표준화기구(ISO, International Organization for Standardization)와 국제전기기술위원회(IEC, International Electrotechnical Commission) 등 국제표준에 근거해 인공지능(AI) 기술이 적용된 제품과 소프 - - expected: `⠚⠒⠙⠡⠐⠀⠴⠠⠠⠁⠊⠐⠖⠲⠀⠟⠨⠪⠶⠵⠀⠚⠒⠈` - - actual: `⠚⠒⠙⠡⠐⠀⠀⠠⠠⠁⠊⠢⠀⠀⠟⠨⠪⠶⠵⠀⠚⠒⠈⠍` - - first differing cell (zero-based): 6 - - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_04.json` #2250: RX 시리즈의 전동화 버전도 내놨다. 하이브리드 모델인 RX350h, 플러그인하이브리드(PHEV) 모델인 RX450h+, 터보 엔진 하이브리드 모델(터보 HEV)인 RX500h F 스포츠 퍼포먼스 등 총 3가지다. - - expected: `⠀⠑⠥⠊⠝⠂⠟⠀⠴⠠⠠⠗⠭⠼⠉⠑⠚⠰⠓⠐⠀⠙⠮⠐` - - actual: `⠀⠑⠥⠊⠝⠂⠟⠀⠀⠠⠠⠗⠭⠼⠉⠑⠚⠓⠐⠀⠙⠮⠐⠎` - - first differing cell (zero-based): 54 - - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` - -### Residual `U+2820 ⠠ -> U+2809 ⠉` - -- `sentence_01.json` #45: 2019년 드링크인터내셔널 합류 이후 최고 운영 책임자(COO)를 역임하며 자회사 인터리커를 통해 와인 시장 진출 등 사업 확장을 진두지휘했다. - - expected: `⠁⠕⠢⠨⠦⠄⠴⠠⠠⠉⠕⠕⠠⠴⠐⠮⠀⠱⠁⠕⠢⠚⠑⠱` - - actual: `⠁⠕⠢⠨⠦⠄⠴⠠⠉⠠⠕⠠⠕⠠⠴⠐⠮⠀⠱⠁⠕⠢⠚⠑` - - first differing cell (zero-based): 53 - - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_02.json` #576: 특히 시는 재난안전 분야에서 인공지능(AI) 기반 폐쇄회로(CC)티브이 관제, 빅데이터를 활용한 화재 발생 위험 요인 대응취약지역 예측 분석을 추진한 점에서도 우수성을 인정받았다. - - expected: `⠚⠽⠐⠥⠦⠄⠴⠠⠠⠉⠉⠠⠴⠓⠕⠘⠪⠕⠀⠈⠧⠒⠨⠝` - - actual: `⠚⠽⠐⠥⠦⠄⠴⠠⠉⠠⠉⠠⠴⠓⠕⠘⠪⠕⠀⠈⠧⠒⠨⠝` - - first differing cell (zero-based): 63 - - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_03.json` #22: 미국프로골프(PGA)투어 더 CJ컵이 열렸던 제주 나인브릿지CC, 한국여자프로골프(KLPGA)투어 롯데렌터카 여자오픈이 열렸던 롯데스카이힐 제주CC, 그리고 ‘한국의 마스터스’ GS칼텍스 매경오픈이 열리는 경기도 성남 남서울CC. - - expected: `⠪⠐⠕⠄⠨⠕⠴⠠⠠⠉⠉⠐⠀⠚⠒⠈⠍⠁⠱⠨⠙⠪⠐⠥` - - actual: `⠪⠐⠕⠄⠨⠕⠴⠠⠉⠠⠉⠐⠀⠚⠒⠈⠍⠁⠱⠨⠙⠪⠐⠥` - - first differing cell (zero-based): 66 - - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_04.json` #19: 이날 행사에는 구 대표 외에 권봉석 (주)LG 최고운영책임자(COO), 박일평 LG사이언스파크대표(사장)를 비롯해 각 계열사 최고기술책임자(CTO), 최고데이터책임자(CDO), 최고인사책임자(CHO) 등이 참석해 국내 이공계 R&D 인재 400여명과 만났다. - - expected: `⠁⠕⠢⠨⠦⠄⠴⠠⠠⠉⠕⠕⠠⠴⠐⠀⠘⠁⠕⠂⠙⠻⠀⠴` - - actual: `⠁⠕⠢⠨⠦⠄⠴⠠⠉⠠⠕⠠⠕⠠⠴⠐⠀⠘⠁⠕⠂⠙⠻⠀` - - first differing cell (zero-based): 60 - - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` - ### Residual `U+2817 ⠗ -> U+2820 ⠠` - `sentence_01.json` #1294: 한편 엔씨소프트는 오픈형 R&D 개발 문화 ‘엔씽(NCing)’의 일환으로 개발 중인 다양한 장르의 신규 프로젝트를 공개하고 있다. @@ -457,6 +388,29 @@ This ranking removes only cases whose first difference is inside an existing out - first differing cell (zero-based): 175 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +### Residual `U+2834 ⠴ -> U+2800 ⠀` + +- `sentence_01.json` #947: 코나체인은 코나아이와 함께 한국은행의 디지털 화폐(CBDC)의 H/W Wallet의 개발에 참여하였고, 현재 블록체인 기반의 설문조사, 투표서비스를 제공 중이다. + - expected: `⠉⠃⠙⠉⠠⠴⠺⠀⠴⠠⠓⠸⠌⠠⠺⠀⠠⠺⠁⠇⠇⠑⠞⠲` + - actual: `⠉⠃⠙⠉⠠⠴⠺⠀⠀⠠⠓⠸⠌⠠⠺⠀⠴⠠⠺⠁⠇⠇⠑⠞` + - first differing cell (zero-based): 57 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_02.json` #865: ‘2023 삼성 AI/CE 챌린지’는 인공지능(AI)과 컴퓨터 공학(CE) 분야 국내 우수 인력 발굴과 차세대 반도체 연구 역량 강화를 위해 개최되고 있으며, 2021년을 시작으로 3회째를 맞이했다. + - expected: `⠃⠉⠀⠇⠢⠠⠻⠀⠴⠠⠠⠁⠊⠸⠌⠠⠠⠉⠑⠲⠀⠰⠗⠂` + - actual: `⠃⠉⠀⠇⠢⠠⠻⠀⠀⠠⠠⠁⠊⠸⠌⠠⠠⠉⠑⠀⠰⠗⠂⠐` + - first differing cell (zero-based): 13 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_03.json` #2455: 한편, AI+ 인증은 한국표준협회(KSA)가 국제표준화기구(ISO, International Organization for Standardization)와 국제전기기술위원회(IEC, International Electrotechnical Commission) 등 국제표준에 근거해 인공지능(AI) 기술이 적용된 제품과 소프 + - expected: `⠚⠒⠙⠡⠐⠀⠴⠠⠠⠁⠊⠐⠖⠲⠀⠟⠨⠪⠶⠵⠀⠚⠒⠈` + - actual: `⠚⠒⠙⠡⠐⠀⠀⠠⠠⠁⠊⠢⠀⠀⠟⠨⠪⠶⠵⠀⠚⠒⠈⠍` + - first differing cell (zero-based): 6 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_04.json` #2250: RX 시리즈의 전동화 버전도 내놨다. 하이브리드 모델인 RX350h, 플러그인하이브리드(PHEV) 모델인 RX450h+, 터보 엔진 하이브리드 모델(터보 HEV)인 RX500h F 스포츠 퍼포먼스 등 총 3가지다. + - expected: `⠀⠑⠥⠊⠝⠂⠟⠀⠴⠠⠠⠗⠭⠼⠉⠑⠚⠰⠓⠐⠀⠙⠮⠐` + - actual: `⠀⠑⠥⠊⠝⠂⠟⠀⠀⠠⠠⠗⠭⠼⠉⠑⠚⠓⠐⠀⠙⠮⠐⠎` + - first differing cell (zero-based): 54 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` + ### Residual `U+283C ⠼ -> U+2800 ⠀` - `sentence_01.json` #633: 미국 항공우주청(NASA)은 26일(현지 시간) 이 같은 내용을 발표하면서 “‘2023 BU’라는 소행성이 남미의 남단을 지나갈 것”이라며 “2023 BU는 지름이 약 3.5~8.5m 크기”라고 설명했다. @@ -480,29 +434,6 @@ This ranking removes only cases whose first difference is inside an existing out - first differing cell (zero-based): 124 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -### Residual `U+2826 ⠦ -> U+2810 ⠐` - -- `sentence_01.json` #1318: 비제조업 업황 BSI(73)는 2p 상승했다. 비제조업 업황 BSI가 전월 대비 상승한 것은 지난 2022년 8월 이후 6개월 만에 처음이다. - - expected: `⠶⠀⠴⠠⠠⠃⠎⠊⠦⠄⠼⠛⠉⠠⠴⠉⠵⠀⠼⠃⠴⠏⠲⠀` - - actual: `⠶⠀⠴⠠⠠⠃⠎⠊⠐⠣⠼⠛⠉⠴⠐⠜⠲⠉⠵⠀⠀⠼⠃⠏` - - first differing cell (zero-based): 21 - - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_02.json` #286: 이희창 학생은 One Show에서 Bronze Pencil(동상), Merit(4위) / One ADC에서 Merit, Shortlist(본상) 31개 / One Portfolio에서 Shortlist, 총 34개의 본상을 수상했다. - - expected: `⠐⠀⠴⠠⠍⠻⠊⠞⠦⠄⠼⠙⠍⠗⠠⠴⠸⠌⠴⠠⠐⠕⠀⠠` - - actual: `⠐⠀⠴⠠⠍⠻⠊⠞⠐⠣⠼⠙⠍⠗⠴⠐⠜⠲⠀⠸⠌⠀⠴⠠` - - first differing cell (zero-based): 58 - - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_03.json` #551: 영국 일간 가디언은 28일(현지시간) 조너선M(41)이 이날부터 정자를 기증할 경우 10만유로(1억5000만원) 이상의 벌금을 물게 된다고 보도했다. - - expected: `⠥⠉⠎⠠⠾⠴⠠⠍⠦⠄⠼⠙⠁⠠⠴⠕⠀⠕⠉⠂⠘⠍⠓⠎` - - actual: `⠥⠉⠎⠠⠾⠴⠠⠍⠐⠣⠼⠙⠁⠴⠐⠜⠲⠕⠀⠕⠉⠂⠘⠍` - - first differing cell (zero-based): 43 - - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_04.json` #119: 21일 국제금융센터에 따르면 KB국민은행의 지난 17일 신용부도스와프(CDS) 프리미엄은 43bp(1bp는 0.01%포인트)로 일주일 전 대비 1bp 상승하는 데 그쳤다. - - expected: `⠵⠀⠼⠙⠉⠴⠃⠏⠦⠄⠼⠁⠴⠃⠏⠲⠉⠵⠀⠼⠚⠲⠚⠁` - - actual: `⠵⠀⠼⠙⠉⠴⠃⠏⠐⠣⠼⠁⠰⠃⠏⠲⠉⠵⠀⠼⠚⠲⠚⠁` - - first differing cell (zero-based): 98 - - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` - ### Residual `U+281B ⠛ -> U+2823 ⠣` - `sentence_01.json` #1753: 지난해 말 경기주택도시공사(GH)에서 퇴직한 전씨는 ‘성남FC 불법 후원금 의혹’과 관련해 검찰 조사를 받은 바 있으며 ‘GH 합숙소 의혹’에도 연루된 것으로 알려졌다. @@ -549,6 +480,75 @@ This ranking removes only cases whose first difference is inside an existing out - first differing cell (zero-based): 112 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +### Residual `U+2826 ⠦ -> U+2810 ⠐` + +- `sentence_01.json` #1318: 비제조업 업황 BSI(73)는 2p 상승했다. 비제조업 업황 BSI가 전월 대비 상승한 것은 지난 2022년 8월 이후 6개월 만에 처음이다. + - expected: `⠶⠀⠴⠠⠠⠃⠎⠊⠦⠄⠼⠛⠉⠠⠴⠉⠵⠀⠼⠃⠴⠏⠲⠀` + - actual: `⠶⠀⠴⠠⠠⠃⠎⠊⠐⠣⠼⠛⠉⠴⠐⠜⠲⠉⠵⠀⠀⠼⠃⠏` + - first differing cell (zero-based): 21 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_02.json` #286: 이희창 학생은 One Show에서 Bronze Pencil(동상), Merit(4위) / One ADC에서 Merit, Shortlist(본상) 31개 / One Portfolio에서 Shortlist, 총 34개의 본상을 수상했다. + - expected: `⠐⠀⠴⠠⠍⠻⠊⠞⠦⠄⠼⠙⠍⠗⠠⠴⠸⠌⠴⠠⠐⠕⠀⠠` + - actual: `⠐⠀⠴⠠⠍⠻⠊⠞⠐⠣⠼⠙⠍⠗⠴⠐⠜⠲⠀⠸⠌⠀⠴⠠` + - first differing cell (zero-based): 58 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_03.json` #551: 영국 일간 가디언은 28일(현지시간) 조너선M(41)이 이날부터 정자를 기증할 경우 10만유로(1억5000만원) 이상의 벌금을 물게 된다고 보도했다. + - expected: `⠥⠉⠎⠠⠾⠴⠠⠍⠦⠄⠼⠙⠁⠠⠴⠕⠀⠕⠉⠂⠘⠍⠓⠎` + - actual: `⠥⠉⠎⠠⠾⠴⠠⠍⠐⠣⠼⠙⠁⠴⠐⠜⠲⠕⠀⠕⠉⠂⠘⠍` + - first differing cell (zero-based): 43 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_04.json` #119: 21일 국제금융센터에 따르면 KB국민은행의 지난 17일 신용부도스와프(CDS) 프리미엄은 43bp(1bp는 0.01%포인트)로 일주일 전 대비 1bp 상승하는 데 그쳤다. + - expected: `⠵⠀⠼⠙⠉⠴⠃⠏⠦⠄⠼⠁⠴⠃⠏⠲⠉⠵⠀⠼⠚⠲⠚⠁` + - actual: `⠵⠀⠼⠙⠉⠴⠃⠏⠐⠣⠼⠁⠰⠃⠏⠲⠉⠵⠀⠼⠚⠲⠚⠁` + - first differing cell (zero-based): 98 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` + +### Residual `U+2820 ⠠ -> U+281E ⠞` + +- `sentence_01.json` #2166: 이에 따라 경찰은 CC(폐쇄회로)TV를 분석하고 달아난 카자흐스탄인 2명을 추적 중이다. 이들이 뛰어넘은 담장이 CCTV 사각지대여서 추적에 애를 먹고 있다. + - expected: `⠗⠚⠽⠐⠥⠠⠴⠴⠠⠠⠞⠧⠲⠐⠮⠀⠘⠛⠠⠹⠚⠈⠥⠀` + - actual: `⠗⠚⠽⠐⠥⠠⠴⠴⠞⠧⠲⠐⠮⠀⠘⠛⠠⠹⠚⠈⠥⠀⠊⠂` + - first differing cell (zero-based): 34 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_02.json` #2253: 경찰은 금은방 CC(폐쇄회로)TV 등을 통해 이들의 얼굴을 확인하고 탐문 수사를 벌여 범행 4시간 만에 집에 있는 A군을 붙잡았다. + - expected: `⠗⠚⠽⠐⠥⠠⠴⠴⠠⠠⠞⠧⠲⠀⠊⠪⠶⠮⠀⠓⠿⠚⠗⠀` + - actual: `⠗⠚⠽⠐⠥⠠⠴⠴⠞⠧⠲⠀⠊⠪⠶⠮⠀⠓⠿⠚⠗⠀⠕⠊` + - first differing cell (zero-based): 33 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_03.json` #50: 당시 CC(폐쇄)TV에 찍힌 장면을 보면 A씨는 엘리베이터를 기다리는 B씨를 발견하자 몰래 뒤로 다가가 갑자기 피해 여성의 머리를 뒤에서 돌려차기로 가격하는 등 폭행했다. + - expected: `⠙⠌⠠⠧⠗⠠⠴⠴⠠⠠⠞⠧⠲⠝⠀⠠⠨⠕⠁⠚⠟⠀⠨⠶` + - actual: `⠙⠌⠠⠧⠗⠠⠴⠴⠞⠧⠲⠝⠀⠠⠨⠕⠁⠚⠟⠀⠨⠶⠑⠡` + - first differing cell (zero-based): 20 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_04.json` #819: 경찰은 7개월간 부산·울산·경남·경북·경기·강원 일대의 방범용 및 사설 CC(폐쇄회로)TV 영상을 추적·분석해 검거에 성공했다. 그러나 모두 국내 수거책뿐이었다. 경찰은 해외 조직 검거에도 매진하겠다고 밝혔다. + - expected: `⠗⠚⠽⠐⠥⠠⠴⠴⠠⠠⠞⠧⠲⠀⠻⠇⠶⠮⠀⠰⠍⠨⠹⠐` + - actual: `⠗⠚⠽⠐⠥⠠⠴⠴⠞⠧⠲⠀⠻⠇⠶⠮⠀⠰⠍⠨⠹⠐⠆⠘` + - first differing cell (zero-based): 92 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` + +### Residual `U+2811 ⠑ -> U+283B ⠻` + +- `sentence_01.json` #1903: 김현용 현대차증권 연구원은 “전사적자원관리(ERP), 생산관리프로그램(MES) 등 기존의 기업향 솔루션에 이번 인수로 구매공급망관리(SRM)이 더해지며 사업 포트폴리오가 한층 단단해진 것으로 판단된다”고 설명했다. + - expected: `⠒⠐⠕⠦⠄⠴⠠⠠⠑⠗⠏⠠⠴⠐⠀⠠⠗⠶⠇⠒⠈⠧⠒⠐` + - actual: `⠒⠐⠕⠦⠄⠴⠠⠠⠻⠏⠠⠴⠐⠀⠠⠗⠶⠇⠒⠈⠧⠒⠐⠕` + - first differing cell (zero-based): 48 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_02.json` #672: 충남교육청은 사무행정(태안여고), ERP(천안여자상업고) 및 대회홍보크리에이터(논산여자상업고) 3개 종목에서 대상인 교육부장관상을 받았고 △금상 6개 △은상 16개 △동상 23개 등 총 48개 수상의 영예를 누렸다. + - expected: `⠥⠠⠴⠐⠀⠴⠠⠠⠑⠗⠏⠦⠄⠰⠾⠣⠒⠱⠨⠇⠶⠎⠃⠈` + - actual: `⠥⠠⠴⠐⠀⠴⠠⠠⠻⠏⠦⠄⠰⠾⠣⠒⠱⠨⠇⠶⠎⠃⠈⠥` + - first differing cell (zero-based): 37 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_03.json` #751: 올 하반기 위하고 기반 차세대 ‘스마트A 10’으로 본격적인 고객 전환이 예정된 가운데 세무회계사무소 전용 전사적자원관리 시스템(ERP)인 ‘위하고T’와 개인용 앱 ‘나하고(NAHAGO)’도 업계에서 주목받고 있다. + - expected: `⠓⠝⠢⠦⠄⠴⠠⠠⠑⠗⠏⠠⠴⠟⠀⠠⠦⠍⠗⠚⠈⠥⠴⠠` + - actual: `⠓⠝⠢⠦⠄⠴⠠⠠⠻⠏⠠⠴⠟⠀⠠⠦⠍⠗⠚⠈⠥⠴⠠⠞` + - first differing cell (zero-based): 127 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_04.json` #4038: 현대건설은 현지 건설사 이알버드(ERBUD), 유니베프(UNIBEP)와 신재생에너지 및 인프라 분야 협력에 관한 업무협약도 체결했다. 이를 통해 신공항, 도심 인프라, 스마트시티 분야 관련 사업을 함께 추진한다. + - expected: `⠎⠊⠪⠦⠄⠴⠠⠠⠑⠗⠃⠥⠙⠠⠴⠐⠀⠩⠉⠕⠘⠝⠙⠪` + - actual: `⠎⠊⠪⠦⠄⠴⠠⠠⠻⠃⠥⠙⠠⠴⠐⠀⠩⠉⠕⠘⠝⠙⠪⠦` + - first differing cell (zero-based): 33 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` + ## Cross-cutting input-only structural cohorts These are cross-cutting input-only structural cohorts, not new primary classes and not engine routing rules. Candidate selection never changes a case's existing primary class by itself. Only cohort members already classified as `pending_rule_review` form a pending subcluster; exact and other-primary members are controls that retain their existing outcomes. A separate classifier may use independently justified, output-localized PDF evidence, as in the rule-34 three-cell contradiction below. The `uppercase_roman_headword_closed_multiword_parenthetical` gate requires a two-or-more character uppercase ASCII headword immediately followed by a closed parenthesis whose contents are two or more ASCII Roman words separated only by spaces. Because the contents admit only letters and spaces, visible operators, subscript/superscript notation, and nested parentheses are excluded deterministically. The `mixed_roman_korean_word_before_uppercase_headword_expansion` gate further requires a whitespace-delimited preceding word that contains both ASCII Roman and Korean and ends in Korean. It locates the following uppercase headword itself, including its immediately preceding emitted cell, so an earlier Roman entry in the same sentence cannot satisfy the output audit. The `single_capital_followed_by_parenthesized_digits` gate requires a standalone capital, a non-empty closed ASCII-digit parenthetical, and alphanumeric outer boundaries. It likewise includes the emitted entry-boundary cell in localization. The `uppercase_roman_run_followed_by_hyphen_digits` gate requires a maximal uppercase ASCII run, literal ASCII hyphen-minus, a non-empty digit run, and alphanumeric outer boundaries. Its localized range includes the current encoded run and its immediately preceding cell, keeping `F-35`-style routing separate from both parenthesized digits and headword expansions. The `uppercase_alphanumeric_roman_digit_sequence` gate covers the wider rule-35/math collision: a capital-led uppercase/digit sequence with optional internal ASCII hyphen/full stop and trailing prose comma/colon/semicolon. It localizes only the current entry cells and does not decide whether an `A-3`-shaped surface is an identifier or a mathematical expression. The `standalone_multi_character_uppercase_roman_word` gate finds maximal ASCII-letter runs of two or more capitals with non-alphanumeric boundaries; a run immediately followed by `(` is excluded so the HCA-style headword itself is not counted by both gates. The `korean_prefixed_closed_allcaps_parenthetical` gate requires an immediately preceding Korean character and a closed body of two or more uppercase ASCII letters. It intentionally contains both acronym annotations (`책임자(COO)`) and scientific formulae (`일산화탄소(CO)`) so their semantic collision remains measurable. The `korean_prefixed_closed_roman_annotation_rule_34_order` gate accepts the narrower rule-34 body grammar after an immediately preceding Korean character and localizes only the current engine's Korean opening-parenthesis cells. This distinguishes the PDF's parenthesis-before-Roman-indicator order from unrelated differences later in the same sentence. The `multi_character_allcaps_roman_runs_joined_by_middle_dot` gate requires two maximal ASCII-letter runs of at least two capitals joined directly by U+00B7, with non-alphanumeric outer boundaries. It records shapes such as `AI·SW` without assigning prose, mathematics, or science semantics. The `roman_run_immediately_before_attached_middle_dot_boundary` gate is output-localized: it requires a maximal ASCII-letter run immediately before U+00B7 and an attached Korean character or ASCII-letter run after it, then searches for that whole current-engine signature in the actual output. It therefore isolates the Roman terminator boundary without treating unrelated middle dots elsewhere in the sentence as causal. The `attached_ascii_roman_to_korean_script_boundary` gate requires an ASCII letter run immediately followed by Korean script and localizes only the current mode marker there: either rule 29's final `⠲` or rule 39's opening `⠸⠷`. It deliberately does not infer the dominant language of the sentence from that surface boundary. The `korean_majority_same_token_roman_sandwich_non_domain` scope audit mirrors the narrower rule-39 implementation gate: the nearest script characters on both sides of one Korean segment are ASCII Roman, the input is Korean-majority by first-script word counts, and the segment is not dot-delimited like the official domain example. It measures change scope but is not itself an output-localized causal classifier. The `korean_inline_parenthesized_single_arithmetic_operator` gate requires an immediate `Korean(` + one rule-45 arithmetic operator + `)Korean` span. Unlike broad coexistence traits, it also locates the current engine's emitted structure and counts a mismatch as signature-local only when the sentence's first differing cell falls inside that output range. The `allcaps_roman_run_containing_ou` gate finds maximal, alphanumeric-delimited uppercase ASCII runs containing adjacent `OU`. It locates the independently encoded run signature in the complete current output and counts only first differences inside that signature as localized. The `allcaps_roman_run_containing_st` gate applies the same output-position requirement to adjacent `ST`; it is kept separate because UEB 10.12 makes contraction use depend on how an abbreviation or acronym is pronounced. The `attached_ascii_roman_segments_joined_by_ampersand` gate requires non-empty ASCII-letter segments joined directly by `&`, excludes spaced/Korean/alphanumeric continuations, and localizes only the current output cell immediately before the ampersand through an independently encoded real-input prefix. The `consecutive_ascii_roman_words_whitespace_boundary` gate requires two adjacent ASCII-letter words separated only by whitespace. For each boundary it independently encodes the real input prefix ending after the first word, then localizes only the current rule-29 terminator at that position or the full output's replacing blank. This prevents an unrelated Roman occurrence elsewhere in the sentence from satisfying the audit. Slash and other punctuation-separated forms are deliberately excluded. The `decimal_point_between_ascii_digits` gate finds whitespace-delimited words containing `digit.digit` and reproduces each whole word in a neutral Korean context, so suffixes and punctuation remain part of the current-engine signature. The `compact_numeric_ascii_letter_suffix` gate finds a numeric prefix immediately followed by ASCII letters, includes the immediately preceding output cell as its entry boundary, and retains suffix-specific outcome counts. It intentionally includes both possible rule-69 units and ambiguous variable/identifier forms; membership alone does not assign unit semantics. The `rule69_ascii_unit_before_terminator_skipping_symbol` gate is narrower: it accepts only ASCII unit spellings already supported by rule 69, includes the immediately following rule-33/34 punctuation cell in the localized signature, and does not infer new units. The `tight_triangle_mark_immediately_before_korean` gate requires literal `△한글` with no input space and includes the first following Korean cell in its localized output range, so an observed missing-space difference is measured at the mark boundary. @@ -1149,15 +1149,15 @@ Representative `mismatch_rule39_hangul_opening` samples: Of the 63959 candidates, 10229 are the actual `pending_rule_review` subcluster. The other 53730 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. -For this output-signature audit, 11716 mismatches were evaluable and 605 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. +For this output-signature audit, 11716 mismatches were evaluable and 1245 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. Localized first-difference transitions: -- `U+2826 ⠦ -> U+2834 ⠴`: 386 -- `U+2826 ⠦ -> U+2800 ⠀`: 178 -- `U+2834 ⠴ -> U+2826 ⠦`: 25 -- `U+2810 ⠐ -> U+2834 ⠴`: 11 -- `U+2800 ⠀ -> U+2834 ⠴`: 4 +- `U+2826 ⠦ -> U+2834 ⠴`: 436 +- `U+2826 ⠦ -> U+2800 ⠀`: 325 +- `U+2820 ⠠ -> U+2809 ⠉`: 104 +- `U+2820 ⠠ -> U+280E ⠎`: 65 +- `U+2820 ⠠ -> U+2801 ⠁`: 29 Mismatch primary-class distribution: @@ -1187,25 +1187,25 @@ Representative `exact` samples: Representative `localized_mismatch` samples: -- `sentence_01.json` #87: 4일 진원생명과학은 자체 개발 흡인작용 피내 접종기 진덤(GeneDerm)을 이용한 코로나19 DNA 백신(GLS-5310)의 안전성·면역원성에 대한 임상1상 결과를 국제감염병학회 (ISID) 학술지인 국제감염질환저널에 발표했다고 4일 밝혔다. - - expected: `⠢⠘⠻⠚⠁⠚⠽⠀⠦⠄⠴⠠⠠⠊⠎⠊⠙⠠⠴⠀⠚⠁⠠⠯` - - actual: `⠢⠘⠻⠚⠁⠚⠽⠀⠴⠐⠣⠠⠠⠊⠎⠊⠙⠐⠜⠲⠀⠚⠁⠠` - - first differing cell (zero-based): 172 +- `sentence_01.json` #38: 올해 소프트웨어(SW) 업계는 국내 인공지능(AI) 제품·서비스 고도화와 함께 전 산업영역에서 SW영역 확대가 가시화될 것으로 보인다. 또한 블록체인이 웹3(Web3)와 연계해 탈중앙화·탈독점화 경제체계로 진화할 것으로 예상된다. + - expected: `⠟⠕⠀⠏⠗⠃⠼⠉⠦⠄⠴⠠⠺⠑⠃⠼⠉⠠⠴⠧⠀⠡⠈⠌` + - actual: `⠟⠕⠀⠏⠗⠃⠼⠉⠴⠐⠣⠠⠺⠑⠃⠼⠉⠴⠐⠜⠲⠧⠀⠡` + - first differing cell (zero-based): 155 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_02.json` #38: 중국금형전시회(DMC) 및 독일금형전시회 (Moulding Expo)와 함께 세계 3대 규모의 금형산업 전문 전시회이며, 연관산업 전문 전시회를 KOPLAS와 동시 개최를 통한 시너지를 창출했다. - - expected: `⠻⠨⠾⠠⠕⠚⠽⠀⠦⠄⠴⠠⠍⠳⠇⠙⠬⠀⠠⠑⠭⠏⠕⠠` - - actual: `⠻⠨⠾⠠⠕⠚⠽⠀⠴⠐⠣⠠⠍⠳⠇⠙⠬⠀⠠⠑⠭⠏⠕⠐` - - first differing cell (zero-based): 48 +- `sentence_02.json` #15: 충남도 농정의 의사결정 과정에 민간의 주도적 참여를 이끌고 현장 의견을 반영하기 위한 민관 농정협의체 ‘충남 쎈(SSEn)농위원회’가 본격 출범했다. + - expected: `⠠⠠⠝⠒⠦⠄⠴⠠⠠⠎⠎⠠⠢⠠⠴⠉⠿⠍⠗⠏⠒⠚⠽⠴` + - actual: `⠠⠠⠝⠒⠦⠄⠴⠠⠎⠠⠎⠠⠑⠝⠠⠴⠉⠿⠍⠗⠏⠒⠚⠽` + - first differing cell (zero-based): 109 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` - `sentence_03.json` #64: SK에코플랜트는 20일 종로구 수송동 본사에서 현대엔지니어링, USNC와 ‘수소 마이크로 허브(H2 Micro Hub)’ 구축을 위한 3자간 업무협약(MOU)을 체결했다고 밝혔다. - expected: `⠪⠐⠥⠀⠚⠎⠘⠪⠦⠄⠴⠠⠓⠼⠃⠀⠠⠍⠊⠉⠗⠕⠀⠠` - actual: `⠪⠐⠥⠀⠚⠎⠘⠪⠀⠀⠦⠠⠓⠼⠃⠀⠴⠠⠍⠊⠉⠗⠕⠀` - first differing cell (zero-based): 91 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_04.json` #578: 항공무선표지소는 무선전파를 이용해 항공기에 위치정보를 제공하는 전방향표지시설(VOR, TACAN)과 거리를 알려주는 거리측정시설(DME), 관제사와 조종사간의 통신을 위한 무선통신시설(AG) 등을 갖추고 있다. - - expected: `⠙⠬⠨⠕⠠⠕⠠⠞⠦⠄⠴⠠⠠⠧⠕⠗⠂⠀⠠⠠⠞⠁⠉⠁` - - actual: `⠙⠬⠨⠕⠠⠕⠠⠞⠀⠀⠦⠠⠠⠧⠕⠗⠐⠀⠴⠠⠠⠞⠁⠉` - - first differing cell (zero-based): 75 +- `sentence_04.json` #19: 이날 행사에는 구 대표 외에 권봉석 (주)LG 최고운영책임자(COO), 박일평 LG사이언스파크대표(사장)를 비롯해 각 계열사 최고기술책임자(CTO), 최고데이터책임자(CDO), 최고인사책임자(CHO) 등이 참석해 국내 이공계 R&D 인재 400여명과 만났다. + - expected: `⠁⠕⠢⠨⠦⠄⠴⠠⠠⠉⠕⠕⠠⠴⠐⠀⠘⠁⠕⠂⠙⠻⠀⠴` + - actual: `⠁⠕⠢⠨⠦⠄⠴⠠⠉⠠⠕⠠⠕⠠⠴⠐⠀⠘⠁⠕⠂⠙⠻⠀` + - first differing cell (zero-based): 60 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` Representative `mismatch` samples: @@ -2407,15 +2407,15 @@ Representative `mismatch` samples: Of the 3429 candidates, 1409 are the actual `pending_rule_review` subcluster. The other 2020 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. -For this output-signature audit, 1518 mismatches were evaluable and 163 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. +For this output-signature audit, 1518 mismatches were evaluable and 151 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. Localized first-difference transitions: - `U+2834 ⠴ -> U+2800 ⠀`: 93 - `U+2834 ⠴ -> U+2830 ⠰`: 52 -- `U+2820 ⠠ -> U+2803 ⠃`: 2 -- `U+2820 ⠠ -> U+280E ⠎`: 2 -- `U+2820 ⠠ -> U+2811 ⠑`: 2 +- `U+2820 ⠠ -> U+2819 ⠙`: 2 +- `U+2834 ⠴ -> U+2820 ⠠`: 2 +- `U+2800 ⠀ -> U+2807 ⠇`: 1 Mismatch primary-class distribution: @@ -2911,9 +2911,9 @@ The generalized fix makes an explicit `EnterEnglish` event idempotent when final ### Closed Roman parenthetical after a non-ASCII-letter boundary -Korean rule 34 (2024 Korean-rules PDF p.29) prints the opening enclosure before Roman entry in `링컨(Lincoln)`: the opening sequence is `⠦⠄⠴`. This output-position cohort finds a closed, non-nested parenthetical whose body begins with an ASCII letter and whose opening does not immediately follow another ASCII letter, then locates its complete current-engine signature without consulting the reference. Direct function-call shapes such as `f(x)` are excluded. The PDF's math rule 6 (p.59), rule 12 (pp.63-64), and rule 45 (p.75) nevertheless leave standalone `(x)` and other Roman-letter parenthetical mathematics as counterexamples, so the surface gate is not an engine-routing predicate. +Korean rule 34 (2024 Korean-rules PDF p.29) prints the opening enclosure before Roman entry in `링컨(Lincoln)`: the opening sequence is `⠦⠄⠴`. This output-position cohort finds a closed, non-nested parenthetical whose body begins with an ASCII letter and whose opening does not immediately follow another ASCII letter, then locates its complete current-engine signature without consulting the reference. Its localized boundary includes the two current output cells immediately before the opening plus the first three entry cells, so rule-11 math spacing can be separated from a difference later inside the parenthetical. Direct function-call shapes such as `f(x)` are excluded. The PDF's math rule 6 (p.59), rule 12 (pp.63-64), and rule 45 (p.75) nevertheless leave standalone `(x)` and other Roman-letter parenthetical mathematics as counterexamples, so the surface gate is not an engine-routing predicate. -The cross-cutting input cohort contains 63959 candidates: 52243 exact controls and 11716 mismatches. Mismatch primary classes remain unchanged: 10229 `pending_rule_review`, 1107 `corpus_suspect`, 225 `comparison_method`, and 155 `unsupported_character_review`. Of 11716 evaluable mismatches, 605 have the first difference at the detected opening; these include 386 `U+2826 ⠦ -> U+2834 ⠴`, 25 `U+2834 ⠴ -> U+2826 ⠦`, and 178 `U+2826 ⠦ -> U+2800 ⠀` transitions. After all earlier localized cohorts and this cohort are excluded, the raw-to-residual target count is 469 -> 76 and the reverse count is 32 -> 4. The short full-encoder form `웹3(Web3)` emits the PDF opening order as a routing control, while whitespace and quote boundaries reproduce the current Roman-first opening. Representative localized samples, with shard and index, are retained in the generated cluster sample table. Because exact controls are abundant and the PDF does not make this input shape semantically sufficient to exclude mathematics, no engine change or primary reclassification is inferred. +The cross-cutting input cohort contains 63959 candidates: 52243 exact controls and 11716 mismatches. Mismatch primary classes remain unchanged: 10229 `pending_rule_review`, 1107 `corpus_suspect`, 225 `comparison_method`, and 155 `unsupported_character_review`. Of 11716 evaluable mismatches, 1245 have the first difference at the detected leading-spacing/entry boundary; these include 436 `U+2826 ⠦ -> U+2834 ⠴`, 27 `U+2834 ⠴ -> U+2826 ⠦`, and 325 `U+2826 ⠦ -> U+2800 ⠀` transitions; the exact localized reverse `U+2800 ⠀ -> U+2826 ⠦` occurs 0 times. After all earlier localized cohorts and this cohort are excluded, the raw-to-residual target count is 469 -> 26, the Roman-indicator reverse count is 32 -> 2, and the spacing target/reverse counts are 440 -> 79 and 1 -> 1. The short full-encoder form `웹3(Web3)` emits the PDF opening order as a routing control, while whitespace and quote boundaries reproduce the current Roman-first opening. Representative localized samples, with shard and index, are retained in the generated cluster sample table. Because exact controls are abundant and the PDF does not make this input shape semantically sufficient to exclude mathematics, no engine change or primary reclassification is inferred. The one raw/residual spacing reverse is a Korean-body `△(교육)` layout boundary, not a Roman-parenthetical member, and remains a separate rule-72/layout review. The HCA-style headword-expansion shape described above is not an engine implementation premise. The 2024 PDF's math rule 6 defines parentheses and grouping parentheses, rule 11 defines mathematical-expression spacing, rule 12 covers Roman letters in formulas as well as Korean sentences, and rule 45 shows Roman-letter function notation followed by parentheses. Excluding visible operators, scripts, and nesting narrows this corpus cohort, but the PDF does not make the remaining surface shape sufficient to rule out every mathematical counterexample. The cluster therefore remains conservative pending-review evidence only. @@ -2968,11 +2968,11 @@ The baseline was 802 candidates / 0 exact / 802 mismatch, preserving 776 `pendin The implemented gate shares the analyzer's complete-run predicate: one or more non-empty ASCII-letter segments joined directly by `&`, with non-alphanumeric outer boundaries. It keeps the existing Roman mode open and suppresses only rule 71's redundant wrapper around that ampersand. Spaced `Marks & Spencer`, Korean `가&나`, empty segments, and outer digit continuations remain outside the gate. Official full-encoder controls `AT&T` and `B&B` pass, and the Korean rule-71 spaced example `종이접기 & 클레이아트` retains its independent `⠴⠈⠯⠲` section. -After the change the same cohort contains 802 candidates, 284 exact and 518 mismatch. Current mismatch primary classes remain evaluator-owned: 491 `pending_rule_review`, 10 `corpus_suspect`, 14 `unsupported_character_review`, and 3 `comparison_method`. The localizer evaluates all 518 remaining mismatches and finds 0 target-localized cases (0 `U+2808 ⠈ -> U+2832 ⠲`); current raw-to-residual target count is 25 -> 24, while `U+2832 ⠲ -> U+2808 ⠈` remains 0 raw / 0 residual. Cohort exact increases by 273 and corpus-wide exact increases by the same 273, from 67,442 to 67,715. Because every changed input must satisfy this shared predicate and the baseline had no exact member, this boundary has no exact regression. The remaining 529 candidates differ elsewhere or retain an existing comparison, corpus-suspect, unsupported, or pending cause. Representative shard/index samples are retained above. +After the change the same cohort contains 802 candidates, 284 exact and 518 mismatch. Current mismatch primary classes remain evaluator-owned: 491 `pending_rule_review`, 10 `corpus_suspect`, 14 `unsupported_character_review`, and 3 `comparison_method`. The localizer evaluates all 518 remaining mismatches and finds 0 target-localized cases (0 `U+2808 ⠈ -> U+2832 ⠲`); current raw-to-residual target count is 25 -> 23, while `U+2832 ⠲ -> U+2808 ⠈` remains 0 raw / 0 residual. Cohort exact increases by 273 and corpus-wide exact increases by the same 273, from 67,442 to 67,715. Because every changed input must satisfy this shared predicate and the baseline had no exact member, this boundary has no exact regression. The remaining 529 candidates differ elsewhere or retain an existing comparison, corpus-suspect, unsupported, or pending cause. Representative shard/index samples are retained above. Current uppercase-Roman hyphen-digits measurement: 571 candidates, 191 exact controls, 380 mismatches, 365 members in the actual `pending_rule_review` subcluster, and 261/380 evaluable mismatches whose first difference is inside the target run plus its entry boundary. It remains distinct from parenthesized digits and headword expansions; no engine change is inferred. -Current uppercase alphanumeric Roman-digit sequence measurement: 3429 candidates, 1911 exact controls, 1518 mismatches, 1409 members in the actual `pending_rule_review` subcluster, and 163/1518 evaluable mismatches whose first difference is localized to the current entry cells. The target `⠴ -> blank` occurs 93 times and the reverse occurs 0 times. Korean rule 35 (2024 Korean-rules PDF pp.29-30, printed pp.23-24) explicitly treats `MP3`, `A4`, `KF94`, and `D-100` as Roman-number sequences. Math rules 11/12 (PDF pp.63-65, printed pp.57-59) separately route mathematical Roman notation without the prose Roman indicator and with two-cell spacing. Official UEB 6.5.1-6.5.2 (2024 UEB PDF p.96, printed p.68) determines numeric/grade-1 continuation only after the surrounding mode has been chosen. Thus surfaces such as `A-3` cannot be assigned identifier or variable semantics from this structure alone. Exact controls and both directions are retained; no engine change is inferred. +Current uppercase alphanumeric Roman-digit sequence measurement: 3429 candidates, 1911 exact controls, 1518 mismatches, 1409 members in the actual `pending_rule_review` subcluster, and 151/1518 evaluable mismatches whose first difference is localized to the current entry cells. The target `⠴ -> blank` occurs 93 times and the reverse occurs 0 times. Korean rule 35 (2024 Korean-rules PDF pp.29-30, printed pp.23-24) explicitly treats `MP3`, `A4`, `KF94`, and `D-100` as Roman-number sequences. Math rules 11/12 (PDF pp.63-65, printed pp.57-59) separately route mathematical Roman notation without the prose Roman indicator and with two-cell spacing. Official UEB 6.5.1-6.5.2 (2024 UEB PDF p.96, printed p.68) determines numeric/grade-1 continuation only after the surrounding mode has been chosen. Thus surfaces such as `A-3` cannot be assigned identifier or variable semantics from this structure alone. Exact controls and both directions are retained; no engine change is inferred. Current consecutive ASCII-Roman word-boundary measurement: 4679 candidates, 2132 exact controls, 2547 mismatches, 2413 members in the actual `pending_rule_review` subcluster, and 23/2547 evaluable mismatches whose first difference is localized to the current boundary cell. The target reference blank versus current terminator (`⠀ -> ⠲`) occurs 23 times; the exact reverse (`⠲ -> ⠀`) occurs 0 times. Korean rule 29 (2024 Korean-rules PDF p.26, printed p.20) explicitly treats consecutive Roman items as one Roman section, with one Roman indicator before the first item and one terminator after the last; its printed `Los Angeles` and `Table of Contents` examples are exact controls. Rule 34 (PDF p.29, printed p.23) separately omits the terminator before a closing enclosure, while official UEB 9.7.1 (2024 UEB PDF p.137, printed p.109) prints the multiword prose enclosure `plays (such as Romeo and Juliet)` with ordinary internal spaces and nested closing punctuation. Together they support preserving a complete letter-and-space Roman parenthetical as prose, but not a fragment containing a function-call opener, digit, operator, or nested bracket. The input-only gate does not decide whether punctuation-separated text such as `ESS /VPP` continues the same section, so that variant remains outside this cohort. Primary classes are preserved. Before the narrow token-routing change this cohort was 4,679 candidates / 2,058 exact / 2,621 mismatch, with 128 localized `⠀ -> ⠲` and zero reverse. The cause was the final `Letters)` token of a whitespace-split Roman parenthetical being rerouted as math, which made the preceding Roman word terminate early and introduced an extra blank. After excluding only a backwards-verified complete Roman parenthetical tail from math routing, the cohort is 2,132 exact / 2,547 mismatch, with 23 target-localized and zero reverse; corpus-wide exact likewise rises by 74, from 68,101 to 68,175. The 105 removed localized boundaries include 74 newly exact cases and 31 cases that still differ elsewhere. Remaining targets such as `SYNO PEM-1`, `ACE Fair(2020)`, and `Mnet K-POP` do not satisfy the complete-parenthetical gate and remain separate residuals rather than widening this rule. diff --git a/libs/braillify/examples/nikl_corpus_analyze.rs b/libs/braillify/examples/nikl_corpus_analyze.rs index f9d8ac1f..a6c0af6d 100644 --- a/libs/braillify/examples/nikl_corpus_analyze.rs +++ b/libs/braillify/examples/nikl_corpus_analyze.rs @@ -1766,6 +1766,7 @@ fn first_difference_at_input_span_entry( /// encoded current-engine signature. This complements prefix offsets: a /// trailing digit can change how an isolated prefix exits Roman/number mode, /// and whitespace belongs before rather than inside the parenthetical entry. +#[cfg(test)] fn current_engine_parenthetical_entry_ranges( input: &str, actual: &str, @@ -1787,7 +1788,32 @@ fn current_engine_parenthetical_entry_ranges( .collect() } -fn first_difference_at_parenthetical_entry(item: &EncodedCase, spans: &[InputSpan]) -> bool { +/// Extends the occurrence-specific parenthetical entry range by the two cells +/// immediately before the current opening. Math rule 11 emits a two-blank +/// boundary there, whereas Korean rules 34/54 attach an enclosure to adjacent +/// prose. This is an output localizer only: it does not decide which semantic +/// route owns the parenthetical. +fn current_engine_parenthetical_leading_boundary_ranges( + input: &str, + actual: &str, + spans: &[InputSpan], +) -> Vec> { + let mut ranges = current_engine_input_entry_ranges(input, actual, spans, 5) + .into_iter() + .map(|range| (range.start, range.end)) + .collect::>(); + ranges.extend( + roman_entry_signature_ranges(input, actual, spans, 2) + .into_iter() + .filter_map(|signature| { + let end = signature.start.saturating_add(5).min(signature.end); + (signature.start < end).then_some((signature.start, end)) + }), + ); + ranges.into_iter().map(|(start, end)| start..end).collect() +} + +fn first_difference_at_parenthetical_boundary(item: &EncodedCase, spans: &[InputSpan]) -> bool { let Ok(actual) = &item.actual else { return false; }; @@ -1795,7 +1821,7 @@ fn first_difference_at_parenthetical_entry(item: &EncodedCase, spans: &[InputSpa return false; } let first_difference = first_difference_cell(&item.located.case.unicode, actual); - current_engine_parenthetical_entry_ranges(&item.located.case.input, actual, spans, 3) + current_engine_parenthetical_leading_boundary_ranges(&item.located.case.input, actual, spans) .into_iter() .any(|range| range.contains(&first_difference)) } @@ -2230,7 +2256,7 @@ fn first_difference_claimed_before_nonletter_parenthetical(item: &EncodedCase) - fn first_difference_claimed_before_allcaps_st(item: &EncodedCase) -> bool { first_difference_claimed_before_nonletter_parenthetical(item) - || first_difference_at_parenthetical_entry( + || first_difference_at_parenthetical_boundary( item, &roman_parenthetical_after_nonletter_boundary_spans(&item.located.case.input), ) @@ -3608,7 +3634,7 @@ fn analyze( .is_empty(), Some( !first_difference_claimed_before_nonletter_parenthetical(item) - && first_difference_at_parenthetical_entry( + && first_difference_at_parenthetical_boundary( item, &roman_parenthetical_after_nonletter_boundary_spans( &item.located.case.input, @@ -4515,6 +4541,7 @@ fn markdown(report: &AnalysisReport) -> String { let target = "U+2826 ⠦ -> U+2834 ⠴"; let reverse = "U+2834 ⠴ -> U+2826 ⠦"; let open_to_space = "U+2826 ⠦ -> U+2800 ⠀"; + let space_to_open = "U+2800 ⠀ -> U+2826 ⠦"; text.push_str(&format!( "### Closed Roman parenthetical after a non-ASCII-letter boundary\n\n\ Korean rule 34 (2024 Korean-rules PDF p.29) prints the opening enclosure before \ @@ -4522,7 +4549,10 @@ fn markdown(report: &AnalysisReport) -> String { cohort finds a closed, non-nested parenthetical whose body begins with an ASCII \ letter and whose opening does not immediately follow another ASCII letter, then \ locates its complete current-engine signature without consulting the reference. \ - Direct function-call shapes such as `f(x)` are excluded. The PDF's math rule 6 \ + Its localized boundary includes the two current output cells immediately before the \ + opening plus the first three entry cells, so rule-11 math spacing can be separated \ + from a difference later inside the parenthetical. Direct function-call shapes such \ + as `f(x)` are excluded. The PDF's math rule 6 \ (p.59), rule 12 (pp.63-64), and rule 45 (p.75) nevertheless leave standalone `(x)` \ and other Roman-letter parenthetical mathematics as counterexamples, so the \ surface gate is not an engine-routing predicate.\n\n\ @@ -4530,15 +4560,20 @@ fn markdown(report: &AnalysisReport) -> String { mismatches. Mismatch primary classes remain unchanged: {} `pending_rule_review`, \ {} `corpus_suspect`, {} `comparison_method`, and {} \ `unsupported_character_review`. Of {} evaluable mismatches, {} have the first \ - difference at the detected opening; these include {} `{target}`, {} `{reverse}`, \ - and {} `{open_to_space}` transitions. After all earlier localized cohorts and this \ - cohort are excluded, the raw-to-residual target count is {} -> {} and the reverse \ - count is {} -> {}. The short full-encoder form `웹3(Web3)` emits the PDF opening \ + difference at the detected leading-spacing/entry boundary; these include {} \ + `{target}`, {} `{reverse}`, \ + and {} `{open_to_space}` transitions; the exact localized reverse `{space_to_open}` \ + occurs {} times. After all earlier localized cohorts and this cohort are excluded, \ + the raw-to-residual target count is {} -> {}, the Roman-indicator reverse count is \ + {} -> {}, and the spacing target/reverse counts are {} -> {} and {} -> {}. The \ + short full-encoder form `웹3(Web3)` emits the PDF opening \ order as a routing control, while whitespace and quote boundaries reproduce the current Roman-first \ opening. Representative localized samples, with shard and index, are retained in \ the generated cluster sample table. Because exact controls are abundant and the PDF \ does not make this input shape semantically sufficient to exclude mathematics, no \ - engine change or primary reclassification is inferred.\n\n", + engine change or primary reclassification is inferred. The one raw/residual spacing \ + reverse is a Korean-body `△(교육)` layout boundary, not a Roman-parenthetical member, \ + and remains a separate rule-72/layout review.\n\n", stats.candidates, stats.exact, stats.mismatch, @@ -4551,10 +4586,15 @@ fn markdown(report: &AnalysisReport) -> String { localized_transition(target), localized_transition(reverse), localized_transition(open_to_space), + localized_transition(space_to_open), raw_transition(target), residual_transition(target), raw_transition(reverse), residual_transition(reverse), + raw_transition(open_to_space), + residual_transition(open_to_space), + raw_transition(space_to_open), + residual_transition(space_to_open), )); } text.push_str( @@ -6713,6 +6753,27 @@ mod tests { assert_eq!(actual.chars().nth(ranges[0].start), Some(expected_opening)); } + #[rstest::rstest] + #[case::attached_digit_suffix( + "안랩은 블록체인 자회사 안랩블록체인컴퍼니가 웹(Web)3 지갑인 ‘ABC 월렛(Wallet)’ 모바일 버전을 구글플레이와 애플 앱스토어에 출시했다고 10일 발표했다." + )] + #[case::attached_hyphen_suffix( + "인천시는 경제성 향상을 위해 유정복 인천시장의 민선8기 1호 공약인 제물포르네상스와 3기 신도시인 광명·시흥 공공주택지구 등 신규 개발계획을 반영하고, 수도권광역급행철도(GTX)-D Y자(인천공항행)와 연계 방안 등을 중점 검토한다." + )] + #[case::attached_middle_dot_suffix( + "10~11일에는 지역 주민들과 함께 하는 전야제를 포함해 아주대 50년사 출판 기념보고회, 인공지능(AI)·6G 융합 콜로키움 시리즈가 열린다." + )] + fn localizes_current_two_blank_boundary_before_roman_parenthetical(#[case] input: &str) { + let spans = roman_parenthetical_after_nonletter_boundary_spans(input); + let actual = braillify::encode_to_unicode(input).expect("parenthetical probe must encode"); + let ranges = current_engine_parenthetical_leading_boundary_ranges(input, &actual, &spans); + + assert!(!spans.is_empty()); + assert!(ranges.iter().any(|range| { + actual.chars().skip(range.start).take(3).collect::() == "⠀⠀⠦" + })); + } + #[test] fn locates_allcaps_ou_signature_in_complete_output() { let input = "업무협약(MOU)을 체결했다."; From a3560a248d487af1f0bc40239e124d8e35b04262 Mon Sep 17 00:00:00 2001 From: devfive Date: Tue, 1 Sep 2026 15:14:33 +0900 Subject: [PATCH 45/77] Diagnose right-attached Roman ampersands --- docs/corpus-analysis/NIKL_2025_V1.md | 63 ++++- .../braillify/examples/nikl_corpus_analyze.rs | 215 +++++++++++++++++- 2 files changed, 276 insertions(+), 2 deletions(-) diff --git a/docs/corpus-analysis/NIKL_2025_V1.md b/docs/corpus-analysis/NIKL_2025_V1.md index d7ae38ef..13f1a148 100644 --- a/docs/corpus-analysis/NIKL_2025_V1.md +++ b/docs/corpus-analysis/NIKL_2025_V1.md @@ -551,7 +551,7 @@ This ranking removes only cases whose first difference is inside an existing out ## Cross-cutting input-only structural cohorts -These are cross-cutting input-only structural cohorts, not new primary classes and not engine routing rules. Candidate selection never changes a case's existing primary class by itself. Only cohort members already classified as `pending_rule_review` form a pending subcluster; exact and other-primary members are controls that retain their existing outcomes. A separate classifier may use independently justified, output-localized PDF evidence, as in the rule-34 three-cell contradiction below. The `uppercase_roman_headword_closed_multiword_parenthetical` gate requires a two-or-more character uppercase ASCII headword immediately followed by a closed parenthesis whose contents are two or more ASCII Roman words separated only by spaces. Because the contents admit only letters and spaces, visible operators, subscript/superscript notation, and nested parentheses are excluded deterministically. The `mixed_roman_korean_word_before_uppercase_headword_expansion` gate further requires a whitespace-delimited preceding word that contains both ASCII Roman and Korean and ends in Korean. It locates the following uppercase headword itself, including its immediately preceding emitted cell, so an earlier Roman entry in the same sentence cannot satisfy the output audit. The `single_capital_followed_by_parenthesized_digits` gate requires a standalone capital, a non-empty closed ASCII-digit parenthetical, and alphanumeric outer boundaries. It likewise includes the emitted entry-boundary cell in localization. The `uppercase_roman_run_followed_by_hyphen_digits` gate requires a maximal uppercase ASCII run, literal ASCII hyphen-minus, a non-empty digit run, and alphanumeric outer boundaries. Its localized range includes the current encoded run and its immediately preceding cell, keeping `F-35`-style routing separate from both parenthesized digits and headword expansions. The `uppercase_alphanumeric_roman_digit_sequence` gate covers the wider rule-35/math collision: a capital-led uppercase/digit sequence with optional internal ASCII hyphen/full stop and trailing prose comma/colon/semicolon. It localizes only the current entry cells and does not decide whether an `A-3`-shaped surface is an identifier or a mathematical expression. The `standalone_multi_character_uppercase_roman_word` gate finds maximal ASCII-letter runs of two or more capitals with non-alphanumeric boundaries; a run immediately followed by `(` is excluded so the HCA-style headword itself is not counted by both gates. The `korean_prefixed_closed_allcaps_parenthetical` gate requires an immediately preceding Korean character and a closed body of two or more uppercase ASCII letters. It intentionally contains both acronym annotations (`책임자(COO)`) and scientific formulae (`일산화탄소(CO)`) so their semantic collision remains measurable. The `korean_prefixed_closed_roman_annotation_rule_34_order` gate accepts the narrower rule-34 body grammar after an immediately preceding Korean character and localizes only the current engine's Korean opening-parenthesis cells. This distinguishes the PDF's parenthesis-before-Roman-indicator order from unrelated differences later in the same sentence. The `multi_character_allcaps_roman_runs_joined_by_middle_dot` gate requires two maximal ASCII-letter runs of at least two capitals joined directly by U+00B7, with non-alphanumeric outer boundaries. It records shapes such as `AI·SW` without assigning prose, mathematics, or science semantics. The `roman_run_immediately_before_attached_middle_dot_boundary` gate is output-localized: it requires a maximal ASCII-letter run immediately before U+00B7 and an attached Korean character or ASCII-letter run after it, then searches for that whole current-engine signature in the actual output. It therefore isolates the Roman terminator boundary without treating unrelated middle dots elsewhere in the sentence as causal. The `attached_ascii_roman_to_korean_script_boundary` gate requires an ASCII letter run immediately followed by Korean script and localizes only the current mode marker there: either rule 29's final `⠲` or rule 39's opening `⠸⠷`. It deliberately does not infer the dominant language of the sentence from that surface boundary. The `korean_majority_same_token_roman_sandwich_non_domain` scope audit mirrors the narrower rule-39 implementation gate: the nearest script characters on both sides of one Korean segment are ASCII Roman, the input is Korean-majority by first-script word counts, and the segment is not dot-delimited like the official domain example. It measures change scope but is not itself an output-localized causal classifier. The `korean_inline_parenthesized_single_arithmetic_operator` gate requires an immediate `Korean(` + one rule-45 arithmetic operator + `)Korean` span. Unlike broad coexistence traits, it also locates the current engine's emitted structure and counts a mismatch as signature-local only when the sentence's first differing cell falls inside that output range. The `allcaps_roman_run_containing_ou` gate finds maximal, alphanumeric-delimited uppercase ASCII runs containing adjacent `OU`. It locates the independently encoded run signature in the complete current output and counts only first differences inside that signature as localized. The `allcaps_roman_run_containing_st` gate applies the same output-position requirement to adjacent `ST`; it is kept separate because UEB 10.12 makes contraction use depend on how an abbreviation or acronym is pronounced. The `attached_ascii_roman_segments_joined_by_ampersand` gate requires non-empty ASCII-letter segments joined directly by `&`, excludes spaced/Korean/alphanumeric continuations, and localizes only the current output cell immediately before the ampersand through an independently encoded real-input prefix. The `consecutive_ascii_roman_words_whitespace_boundary` gate requires two adjacent ASCII-letter words separated only by whitespace. For each boundary it independently encodes the real input prefix ending after the first word, then localizes only the current rule-29 terminator at that position or the full output's replacing blank. This prevents an unrelated Roman occurrence elsewhere in the sentence from satisfying the audit. Slash and other punctuation-separated forms are deliberately excluded. The `decimal_point_between_ascii_digits` gate finds whitespace-delimited words containing `digit.digit` and reproduces each whole word in a neutral Korean context, so suffixes and punctuation remain part of the current-engine signature. The `compact_numeric_ascii_letter_suffix` gate finds a numeric prefix immediately followed by ASCII letters, includes the immediately preceding output cell as its entry boundary, and retains suffix-specific outcome counts. It intentionally includes both possible rule-69 units and ambiguous variable/identifier forms; membership alone does not assign unit semantics. The `rule69_ascii_unit_before_terminator_skipping_symbol` gate is narrower: it accepts only ASCII unit spellings already supported by rule 69, includes the immediately following rule-33/34 punctuation cell in the localized signature, and does not infer new units. The `tight_triangle_mark_immediately_before_korean` gate requires literal `△한글` with no input space and includes the first following Korean cell in its localized output range, so an observed missing-space difference is measured at the mark boundary. +These are cross-cutting input-only structural cohorts, not new primary classes and not engine routing rules. Candidate selection never changes a case's existing primary class by itself. Only cohort members already classified as `pending_rule_review` form a pending subcluster; exact and other-primary members are controls that retain their existing outcomes. A separate classifier may use independently justified, output-localized PDF evidence, as in the rule-34 three-cell contradiction below. The `uppercase_roman_headword_closed_multiword_parenthetical` gate requires a two-or-more character uppercase ASCII headword immediately followed by a closed parenthesis whose contents are two or more ASCII Roman words separated only by spaces. Because the contents admit only letters and spaces, visible operators, subscript/superscript notation, and nested parentheses are excluded deterministically. The `mixed_roman_korean_word_before_uppercase_headword_expansion` gate further requires a whitespace-delimited preceding word that contains both ASCII Roman and Korean and ends in Korean. It locates the following uppercase headword itself, including its immediately preceding emitted cell, so an earlier Roman entry in the same sentence cannot satisfy the output audit. The `single_capital_followed_by_parenthesized_digits` gate requires a standalone capital, a non-empty closed ASCII-digit parenthetical, and alphanumeric outer boundaries. It likewise includes the emitted entry-boundary cell in localization. The `uppercase_roman_run_followed_by_hyphen_digits` gate requires a maximal uppercase ASCII run, literal ASCII hyphen-minus, a non-empty digit run, and alphanumeric outer boundaries. Its localized range includes the current encoded run and its immediately preceding cell, keeping `F-35`-style routing separate from both parenthesized digits and headword expansions. The `uppercase_alphanumeric_roman_digit_sequence` gate covers the wider rule-35/math collision: a capital-led uppercase/digit sequence with optional internal ASCII hyphen/full stop and trailing prose comma/colon/semicolon. It localizes only the current entry cells and does not decide whether an `A-3`-shaped surface is an identifier or a mathematical expression. The `standalone_multi_character_uppercase_roman_word` gate finds maximal ASCII-letter runs of two or more capitals with non-alphanumeric boundaries; a run immediately followed by `(` is excluded so the HCA-style headword itself is not counted by both gates. The `korean_prefixed_closed_allcaps_parenthetical` gate requires an immediately preceding Korean character and a closed body of two or more uppercase ASCII letters. It intentionally contains both acronym annotations (`책임자(COO)`) and scientific formulae (`일산화탄소(CO)`) so their semantic collision remains measurable. The `korean_prefixed_closed_roman_annotation_rule_34_order` gate accepts the narrower rule-34 body grammar after an immediately preceding Korean character and localizes only the current engine's Korean opening-parenthesis cells. This distinguishes the PDF's parenthesis-before-Roman-indicator order from unrelated differences later in the same sentence. The `multi_character_allcaps_roman_runs_joined_by_middle_dot` gate requires two maximal ASCII-letter runs of at least two capitals joined directly by U+00B7, with non-alphanumeric outer boundaries. It records shapes such as `AI·SW` without assigning prose, mathematics, or science semantics. The `roman_run_immediately_before_attached_middle_dot_boundary` gate is output-localized: it requires a maximal ASCII-letter run immediately before U+00B7 and an attached Korean character or ASCII-letter run after it, then searches for that whole current-engine signature in the actual output. It therefore isolates the Roman terminator boundary without treating unrelated middle dots elsewhere in the sentence as causal. The `attached_ascii_roman_to_korean_script_boundary` gate requires an ASCII letter run immediately followed by Korean script and localizes only the current mode marker there: either rule 29's final `⠲` or rule 39's opening `⠸⠷`. It deliberately does not infer the dominant language of the sentence from that surface boundary. The `korean_majority_same_token_roman_sandwich_non_domain` scope audit mirrors the narrower rule-39 implementation gate: the nearest script characters on both sides of one Korean segment are ASCII Roman, the input is Korean-majority by first-script word counts, and the segment is not dot-delimited like the official domain example. It measures change scope but is not itself an output-localized causal classifier. The `korean_inline_parenthesized_single_arithmetic_operator` gate requires an immediate `Korean(` + one rule-45 arithmetic operator + `)Korean` span. Unlike broad coexistence traits, it also locates the current engine's emitted structure and counts a mismatch as signature-local only when the sentence's first differing cell falls inside that output range. The `allcaps_roman_run_containing_ou` gate finds maximal, alphanumeric-delimited uppercase ASCII runs containing adjacent `OU`. It locates the independently encoded run signature in the complete current output and counts only first differences inside that signature as localized. The `allcaps_roman_run_containing_st` gate applies the same output-position requirement to adjacent `ST`; it is kept separate because UEB 10.12 makes contraction use depend on how an abbreviation or acronym is pronounced. The `attached_ascii_roman_segments_joined_by_ampersand` gate requires non-empty ASCII-letter segments joined directly by `&`, excludes spaced/Korean/alphanumeric continuations, and localizes only the current output cell immediately before the ampersand through an independently encoded real-input prefix. The `ampersand_before_attached_ascii_roman_segment` gate covers the distinct one-sided UEB `&c` boundary: `&` is followed by a complete ASCII-letter segment, while an ASCII alphanumeric or another ampersand immediately before it and a digit continuation after it are excluded. Its output range is anchored by independently encoding the real input prefix through `&`, then includes only the current Rule-71 exit and following Roman re-entry cells. The `consecutive_ascii_roman_words_whitespace_boundary` gate requires two adjacent ASCII-letter words separated only by whitespace. For each boundary it independently encodes the real input prefix ending after the first word, then localizes only the current rule-29 terminator at that position or the full output's replacing blank. This prevents an unrelated Roman occurrence elsewhere in the sentence from satisfying the audit. Slash and other punctuation-separated forms are deliberately excluded. The `decimal_point_between_ascii_digits` gate finds whitespace-delimited words containing `digit.digit` and reproduces each whole word in a neutral Korean context, so suffixes and punctuation remain part of the current-engine signature. The `compact_numeric_ascii_letter_suffix` gate finds a numeric prefix immediately followed by ASCII letters, includes the immediately preceding output cell as its entry boundary, and retains suffix-specific outcome counts. It intentionally includes both possible rule-69 units and ambiguous variable/identifier forms; membership alone does not assign unit semantics. The `rule69_ascii_unit_before_terminator_skipping_symbol` gate is narrower: it accepts only ASCII unit spellings already supported by rule 69, includes the immediately following rule-33/34 punctuation cell in the localized signature, and does not infer new units. The `tight_triangle_mark_immediately_before_korean` gate requires literal `△한글` with no input space and includes the first following Korean cell in its localized output range, so an observed missing-space difference is measured at the mark boundary. | Cluster | Candidates | Exact | Mismatch | Conflicting-reference cases | |---|---:|---:|---:|---:| @@ -560,6 +560,7 @@ These are cross-cutting input-only structural cohorts, not new primary classes a | `allcaps_roman_run_containing_ed` | 816 | 319 | 497 | 0 | | `allcaps_roman_run_containing_ou` | 1816 | 64 | 1752 | 0 | | `allcaps_roman_run_containing_st` | 1479 | 683 | 796 | 0 | +| `ampersand_before_attached_ascii_roman_segment` | 30 | 0 | 30 | 0 | | `attached_ascii_roman_segments_joined_by_ampersand` | 802 | 284 | 518 | 0 | | `attached_ascii_roman_to_korean_script_boundary` | 17693 | 12934 | 4759 | 0 | | `closed_roman_parenthetical_after_non_ascii_letter_boundary` | 63959 | 52243 | 11716 | 0 | @@ -990,6 +991,58 @@ Representative `mismatch` samples: - first differing cell (zero-based): 53 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +### `ampersand_before_attached_ascii_roman_segment` + +Of the 30 candidates, 26 are the actual `pending_rule_review` subcluster. The other 4 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. + +For this output-signature audit, 30 mismatches were evaluable and 16 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. + +Localized first-difference transitions: + +- `U+2820 ⠠ -> U+2832 ⠲`: 15 +- `U+2834 ⠴ -> U+2832 ⠲`: 1 + +Mismatch primary-class distribution: + +- `corpus_suspect`: 4 +- `pending_rule_review`: 26 + +Representative `localized_mismatch` samples: + +- `sentence_01.json` #5105: 하이브(HYBE) 소속 그룹 &TEAM(앤팀)의 팬 사인회에서 ‘속옷 검사’ 논란이 일자 주최 측에서 사과했지만, 파문은 계속되고 있다. + - expected: `⠪⠐⠍⠃⠀⠴⠈⠯⠠⠠⠞⠂⠍⠦⠄⠗⠒⠓⠕⠢⠠⠴⠺⠀` + - actual: `⠪⠐⠍⠃⠀⠴⠈⠯⠲⠴⠠⠠⠞⠂⠍⠦⠄⠗⠒⠓⠕⠢⠠⠴` + - first differing cell (zero-based): 31 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_02.json` #974: 드림&Dream멘토링은 시간과 봉사정신을 내어주는(드림) 대학생 멘토와 그로 인해 꿈(Dream)을 이루는 고등학생 멘티가 함께 만들어 가는 이야기라는 뜻으로 멘티의 학교적응력 향상을 목표로 하는 프로그램이다. + - expected: `⠊⠪⠐⠕⠢⠴⠈⠯⠴⠠⠙⠗⠂⠍⠲⠑⠝⠒⠓⠥⠐⠕⠶⠵` + - actual: `⠊⠪⠐⠕⠢⠴⠈⠯⠲⠴⠠⠙⠗⠂⠍⠲⠑⠝⠒⠓⠥⠐⠕⠶` + - first differing cell (zero-based): 8 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_03.json` #3453: 3일 한화자산운용은 자사 펀드 직판 애플리케이션 ‘파인(PINE)’에 청년소장펀드 2종을 탑재했다고 밝혔다. 해당 펀드는 ‘한화 MZ픽 한국&K리츠’ 및 ‘한화 MZ픽 그린테크’ 2종이다. + - expected: `⠚⠒⠈⠍⠁⠴⠈⠯⠠⠅⠲⠐⠕⠰⠪⠴⠄⠀⠑⠕⠆⠀⠠⠦` + - actual: `⠚⠒⠈⠍⠁⠴⠈⠯⠲⠴⠠⠅⠲⠐⠕⠰⠪⠴⠄⠀⠑⠕⠆⠀` + - first differing cell (zero-based): 137 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` + +Representative `mismatch` samples: + +- `sentence_01.json` #202: 제이스코홀딩스는 필리핀 니켈 광산사업을 공동 추진중인 EVM(EV Mining &Development)이 광산지질국(MGB)에 4천700헥타르(약 1천400만평)에 대한 탐사허가(EP)를 신청했다고 9일 밝혔다. + - expected: `⠍⠨⠟⠨⠍⠶⠟⠀⠴⠠⠠⠑⠧⠍⠐⠣⠠⠠⠑⠧⠀⠠⠍⠔` + - actual: `⠍⠨⠟⠨⠍⠶⠟⠀⠀⠠⠠⠑⠧⠍⠦⠠⠠⠑⠧⠀⠴⠠⠍⠔` + - first differing cell (zero-based): 56 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_02.json` #974: 드림&Dream멘토링은 시간과 봉사정신을 내어주는(드림) 대학생 멘토와 그로 인해 꿈(Dream)을 이루는 고등학생 멘티가 함께 만들어 가는 이야기라는 뜻으로 멘티의 학교적응력 향상을 목표로 하는 프로그램이다. + - expected: `⠊⠪⠐⠕⠢⠴⠈⠯⠴⠠⠙⠗⠂⠍⠲⠑⠝⠒⠓⠥⠐⠕⠶⠵` + - actual: `⠊⠪⠐⠕⠢⠴⠈⠯⠲⠴⠠⠙⠗⠂⠍⠲⠑⠝⠒⠓⠥⠐⠕⠶` + - first differing cell (zero-based): 8 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_03.json` #3453: 3일 한화자산운용은 자사 펀드 직판 애플리케이션 ‘파인(PINE)’에 청년소장펀드 2종을 탑재했다고 밝혔다. 해당 펀드는 ‘한화 MZ픽 한국&K리츠’ 및 ‘한화 MZ픽 그린테크’ 2종이다. + - expected: `⠚⠒⠈⠍⠁⠴⠈⠯⠠⠅⠲⠐⠕⠰⠪⠴⠄⠀⠑⠕⠆⠀⠠⠦` + - actual: `⠚⠒⠈⠍⠁⠴⠈⠯⠲⠴⠠⠅⠲⠐⠕⠰⠪⠴⠄⠀⠑⠕⠆⠀` + - first differing cell (zero-based): 137 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` + ### `attached_ascii_roman_segments_joined_by_ampersand` Of the 802 candidates, 491 are the actual `pending_rule_review` subcluster. The other 311 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. @@ -2970,6 +3023,14 @@ The implemented gate shares the analyzer's complete-run predicate: one or more n After the change the same cohort contains 802 candidates, 284 exact and 518 mismatch. Current mismatch primary classes remain evaluator-owned: 491 `pending_rule_review`, 10 `corpus_suspect`, 14 `unsupported_character_review`, and 3 `comparison_method`. The localizer evaluates all 518 remaining mismatches and finds 0 target-localized cases (0 `U+2808 ⠈ -> U+2832 ⠲`); current raw-to-residual target count is 25 -> 23, while `U+2832 ⠲ -> U+2808 ⠈` remains 0 raw / 0 residual. Cohort exact increases by 273 and corpus-wide exact increases by the same 273, from 67,442 to 67,715. Because every changed input must satisfy this shared predicate and the baseline had no exact member, this boundary has no exact regression. The remaining 529 candidates differ elsewhere or retain an existing comparison, corpus-suspect, unsupported, or pending cause. Representative shard/index samples are retained above. +### Ampersand before an attached ASCII Roman segment + +This is the residual boundary not covered by the implemented `A&B` gate. Official UEB 3.1.1 (2024 UEB PDF p.51, printed p.23) prints `&c (etc)` as `⠈⠯⠉ ⠐⠣⠑⠞⠉⠐⠜`, with no mode break between the ampersand and `c`; its `AT&T` and `B&B` examples give the same attached behavior between Roman segments. Korean rule 71 (2024 Korean-rules PDF pp.51-52, printed pp.45-46) wraps an ampersand in Roman indicators when needed to distinguish it from Hangul, while rules 29 and 32 require one Roman section for consecutive Roman material and UEB transcription inside that section. The spaced Korean control `종이접기 & 클레이아트` remains an independently closed Rule-71 symbol. + +The baseline input/output cohort contains 30 candidates, 0 corpus exact controls, and 30 mismatches. Existing mismatch primary classes remain unchanged: 26 `pending_rule_review`, 4 `corpus_suspect`, 0 `comparison_method`, and 0 `unsupported_character_review`. Of 30 evaluable mismatches, 16 have the first difference inside the current Rule-71 exit and following Roman re-entry: 15 `U+2820 ⠠ -> U+2832 ⠲` and 1 `U+2834 ⠴ -> U+2832 ⠲`. Their raw-to-residual counts after all localized cohorts are 50 -> 33 and 3 -> 2; localized reverse counts are zero, while the raw-to-residual maps for `U+2832 ⠲ -> U+2820 ⠠` and `U+2832 ⠲ -> U+2834 ⠴` are 0 -> 0 and 5 -> 4. Representative localized samples include `&TEAM`, `드림&Dream`, and `한국&K리츠`, with shard/index retained above. + +There is no exact NIKL member in this narrow cohort, so the corpus alone cannot authorize a change. The official full-encoder `&c`, `AT&T`, and `B&B` examples are the positive controls; the spaced Korean Rule-71 example is the negative boundary control. This checkpoint records only the baseline and does not yet change the engine or any primary classification. + Current uppercase-Roman hyphen-digits measurement: 571 candidates, 191 exact controls, 380 mismatches, 365 members in the actual `pending_rule_review` subcluster, and 261/380 evaluable mismatches whose first difference is inside the target run plus its entry boundary. It remains distinct from parenthesized digits and headword expansions; no engine change is inferred. Current uppercase alphanumeric Roman-digit sequence measurement: 3429 candidates, 1911 exact controls, 1518 mismatches, 1409 members in the actual `pending_rule_review` subcluster, and 151/1518 evaluable mismatches whose first difference is localized to the current entry cells. The target `⠴ -> blank` occurs 93 times and the reverse occurs 0 times. Korean rule 35 (2024 Korean-rules PDF pp.29-30, printed pp.23-24) explicitly treats `MP3`, `A4`, `KF94`, and `D-100` as Roman-number sequences. Math rules 11/12 (PDF pp.63-65, printed pp.57-59) separately route mathematical Roman notation without the prose Roman indicator and with two-cell spacing. Official UEB 6.5.1-6.5.2 (2024 UEB PDF p.96, printed p.68) determines numeric/grade-1 continuation only after the surrounding mode has been chosen. Thus surfaces such as `A-3` cannot be assigned identifier or variable semantics from this structure alone. Exact controls and both directions are retained; no engine change is inferred. diff --git a/libs/braillify/examples/nikl_corpus_analyze.rs b/libs/braillify/examples/nikl_corpus_analyze.rs index a6c0af6d..035ffbdf 100644 --- a/libs/braillify/examples/nikl_corpus_analyze.rs +++ b/libs/braillify/examples/nikl_corpus_analyze.rs @@ -593,6 +593,8 @@ const ROMAN_RUN_AFTER_CLOSED_ROMAN_ENCLOSURE: &str = "roman_run_after_whitespace_following_closed_roman_enclosure"; const ATTACHED_ASCII_ROMAN_SEGMENTS_JOINED_BY_AMPERSAND: &str = "attached_ascii_roman_segments_joined_by_ampersand"; +const AMPERSAND_BEFORE_ATTACHED_ASCII_ROMAN_SEGMENT: &str = + "ampersand_before_attached_ascii_roman_segment"; const SINGLE_CAPITAL_PARENTHESIZED_DIGITS: &str = "single_capital_followed_by_parenthesized_digits"; const MIXED_ROMAN_KOREAN_BEFORE_HEADWORD_EXPANSION: &str = "mixed_roman_korean_word_before_uppercase_headword_expansion"; @@ -1112,6 +1114,41 @@ fn attached_ascii_roman_ampersand_spans(input: &str) -> Vec { spans } +/// Finds an ampersand immediately followed by a complete ASCII-letter segment +/// when no ASCII alphanumeric precedes it. This is the one-sided shape of the +/// UEB §3.1.1 `&c` example, kept separate from the already implemented `A&B` +/// cohort and from digit/identifier continuations. +fn ampersand_before_attached_ascii_roman_spans(input: &str) -> Vec { + let bytes = input.as_bytes(); + let mut spans = Vec::new(); + for (ampersand_byte, ch) in input.char_indices() { + if ch != '&' + || input[..ampersand_byte] + .chars() + .next_back() + .is_some_and(|previous| previous.is_ascii_alphanumeric() || previous == '&') + || !bytes + .get(ampersand_byte + 1) + .is_some_and(u8::is_ascii_alphabetic) + { + continue; + } + + let mut end_byte = ampersand_byte + 1; + while bytes.get(end_byte).is_some_and(u8::is_ascii_alphabetic) { + end_byte += 1; + } + if bytes.get(end_byte).is_some_and(u8::is_ascii_alphanumeric) { + continue; + } + spans.push(InputSpan { + start_byte: ampersand_byte, + end_byte, + }); + } + spans +} + /// Finds maximal pure-uppercase ASCII letter runs whose beginning is itself a /// pure-letter UEB shortform abbreviation. UEB 5.7.2 and 10.9.7-10.9.8 require /// grade 1 both for a complete shortform-shaped letters-sequence (`WD`) and for @@ -1954,6 +1991,43 @@ fn first_difference_in_attached_roman_ampersand(item: &EncodedCase) -> bool { .any(|range| range.contains(&first_difference)) } +/// Locates the current Rule-71 terminator emitted immediately after an +/// ampersand whose right-hand ASCII Roman segment is attached. The real input +/// prefix through `&` is encoded independently, so another ampersand in the +/// sentence cannot satisfy this output-position audit. +fn ampersand_before_ascii_roman_boundary_ranges( + input: &str, + actual: &str, +) -> Vec> { + let actual_cells = actual.chars().collect::>(); + let mut ranges = BTreeSet::new(); + for span in ampersand_before_attached_ascii_roman_spans(input) { + let ampersand_end = span.start_byte + 1; + let Ok(prefix) = braillify::encode_to_unicode(&input[..ampersand_end]) else { + continue; + }; + let prefix_cells = prefix.chars().collect::>(); + if actual_cells.starts_with(&prefix_cells) && !prefix_cells.is_empty() { + let boundary = prefix_cells.len() - 1; + ranges.insert((boundary, boundary.saturating_add(3).min(actual_cells.len()))); + } + } + ranges.into_iter().map(|(start, end)| start..end).collect() +} + +fn first_difference_after_ampersand_before_ascii_roman(item: &EncodedCase) -> bool { + let Ok(actual) = &item.actual else { + return false; + }; + if actual == &item.located.case.unicode { + return false; + } + let first_difference = first_difference_cell(&item.located.case.unicode, actual); + ampersand_before_ascii_roman_boundary_ranges(&item.located.case.input, actual) + .into_iter() + .any(|range| range.contains(&first_difference)) +} + /// Finds a standalone single capital immediately followed by a non-empty, /// closed ASCII-digit parenthetical, such as `A(14)`. The span is deliberately /// semantic-neutral: prose labels and mathematical function notation can share @@ -2271,8 +2345,14 @@ fn first_difference_claimed_before_allcaps_ar(item: &EncodedCase) -> bool { || first_difference_in_attached_roman_ampersand(item) } +fn first_difference_claimed_before_ampersand_right_roman(item: &EncodedCase) -> bool { + first_difference_claimed_before_allcaps_ar(item) + || first_difference_after_ampersand_before_ascii_roman(item) +} + fn first_difference_claimed_before_roman_after_closed_enclosure(item: &EncodedCase) -> bool { - first_difference_claimed_before_allcaps_ar(item) || first_difference_in_allcaps_ar_run(item) + first_difference_claimed_before_ampersand_right_roman(item) + || first_difference_in_allcaps_ar_run(item) } fn first_difference_claimed_before_allcaps_ed(item: &EncodedCase) -> bool { @@ -3172,6 +3252,10 @@ fn analyze( ATTACHED_ASCII_ROMAN_SEGMENTS_JOINED_BY_AMPERSAND.to_string(), PendingRuleReviewClusterStats::default(), ), + ( + AMPERSAND_BEFORE_ATTACHED_ASCII_ROMAN_SEGMENT.to_string(), + PendingRuleReviewClusterStats::default(), + ), ( ALLCAPS_ROMAN_MIDDLE_DOT_RUNS.to_string(), PendingRuleReviewClusterStats::default(), @@ -3404,6 +3488,15 @@ fn analyze( ), true, ), + ( + AMPERSAND_BEFORE_ATTACHED_ASCII_ROMAN_SEGMENT, + !ampersand_before_attached_ascii_roman_spans(&item.located.case.input).is_empty(), + Some( + !first_difference_claimed_before_allcaps_ar(item) + && first_difference_after_ampersand_before_ascii_roman(item), + ), + true, + ), ( ALLCAPS_ROMAN_MIDDLE_DOT_RUNS, has_allcaps_roman_middle_dot_runs(&item.located.case.input), @@ -4087,6 +4180,12 @@ fn markdown(report: &AnalysisReport) -> String { segments joined directly by `&`, excludes spaced/Korean/alphanumeric continuations, and \ localizes only the current output cell immediately before the ampersand through an \ independently encoded real-input prefix. The \ + `ampersand_before_attached_ascii_roman_segment` gate covers the distinct one-sided \ + UEB `&c` boundary: `&` is followed by a complete ASCII-letter segment, while an ASCII \ + alphanumeric or another ampersand immediately before it and a digit continuation after \ + it are excluded. Its output range is anchored by independently encoding the real input \ + prefix through `&`, then includes only the current Rule-71 exit and following Roman \ + re-entry cells. The \ `consecutive_ascii_roman_words_whitespace_boundary` gate requires two adjacent \ ASCII-letter words separated only by whitespace. For each boundary it independently \ encodes the real input prefix ending after the first word, then localizes only the \ @@ -5062,6 +5161,89 @@ fn markdown(report: &AnalysisReport) -> String { residual_count(reverse), )); } + if let Some(stats) = report + .pending_rule_review_clusters + .get(AMPERSAND_BEFORE_ATTACHED_ASCII_ROMAN_SEGMENT) + { + let primary_count = |name: &str| { + stats + .mismatch_primary_classes + .get(name) + .copied() + .unwrap_or(0) + }; + let localized_count = |name: &str| { + stats + .first_difference_in_output_signature_transitions + .get(name) + .copied() + .unwrap_or(0) + }; + let raw_count = |name: &str| { + report + .pending_first_difference_cell_transitions + .get(name) + .map(|transition| transition.cases) + .unwrap_or(0) + }; + let residual_count = |name: &str| { + report + .pending_first_difference_transitions_after_localized_cohorts + .get(name) + .map(|transition| transition.cases) + .unwrap_or(0) + }; + let cap_target = "U+2820 ⠠ -> U+2832 ⠲"; + let entry_target = "U+2834 ⠴ -> U+2832 ⠲"; + let cap_reverse = "U+2832 ⠲ -> U+2820 ⠠"; + let entry_reverse = "U+2832 ⠲ -> U+2834 ⠴"; + text.push_str(&format!( + "\n### Ampersand before an attached ASCII Roman segment\n\n\ + This is the residual boundary not covered by the implemented `A&B` gate. Official \ + UEB 3.1.1 (2024 UEB PDF p.51, printed p.23) prints `&c (etc)` as \ + `⠈⠯⠉ ⠐⠣⠑⠞⠉⠐⠜`, with no mode break between the ampersand and `c`; its \ + `AT&T` and `B&B` examples give the same attached behavior between Roman segments. \ + Korean rule 71 (2024 Korean-rules PDF pp.51-52, printed pp.45-46) wraps an \ + ampersand in Roman indicators when needed to distinguish it from Hangul, while \ + rules 29 and 32 require one Roman section for consecutive Roman material and UEB \ + transcription inside that section. The spaced Korean control `종이접기 & \ + 클레이아트` remains an independently closed Rule-71 symbol.\n\n\ + The baseline input/output cohort contains {} candidates, {} corpus exact controls, \ + and {} mismatches. Existing mismatch primary classes remain unchanged: {} \ + `pending_rule_review`, {} `corpus_suspect`, {} `comparison_method`, and {} \ + `unsupported_character_review`. Of {} evaluable mismatches, {} have the first \ + difference inside the current Rule-71 exit and following Roman re-entry: {} \ + `{cap_target}` and {} `{entry_target}`. Their raw-to-residual counts after all \ + localized cohorts are {} -> {} and {} -> {}; localized reverse counts are zero, \ + while the raw-to-residual maps for `{cap_reverse}` and `{entry_reverse}` are {} -> \ + {} and {} -> {}. Representative localized samples include `&TEAM`, \ + `드림&Dream`, and `한국&K리츠`, with shard/index retained above.\n\n\ + There is no exact NIKL member in this narrow cohort, so the corpus alone cannot \ + authorize a change. The official full-encoder `&c`, `AT&T`, and `B&B` examples are \ + the positive controls; the spaced Korean Rule-71 example is the negative boundary \ + control. This checkpoint records only the baseline and does not yet change the \ + engine or any primary classification.\n", + stats.candidates, + stats.exact, + stats.mismatch, + primary_count("pending_rule_review"), + primary_count("corpus_suspect"), + primary_count("comparison_method"), + primary_count("unsupported_character_review"), + stats.output_signature_mismatches_evaluated, + stats.first_difference_in_output_signature, + localized_count(cap_target), + localized_count(entry_target), + raw_count(cap_target), + residual_count(cap_target), + raw_count(entry_target), + residual_count(entry_target), + raw_count(cap_reverse), + residual_count(cap_reverse), + raw_count(entry_reverse), + residual_count(entry_reverse), + )); + } if let Some(stats) = report .pending_rule_review_clusters .get(UPPERCASE_ROMAN_HYPHEN_DIGITS) @@ -6468,9 +6650,40 @@ mod tests { assert_eq!(actual, expected); } + #[rstest::rstest] + #[case::official_ueb_one_sided("&c", vec!["&c"])] + #[case::after_korean("과학&ICT", vec!["&ICT"])] + #[case::after_punctuation("(참고)&Ref", vec!["&Ref"])] + #[case::already_owned_two_sided("A&B", vec![])] + #[case::digit_left_boundary("2&K", vec![])] + #[case::digit_right_continuation("한국&K2", vec![])] + #[case::empty_segment("한국&&K", vec![])] + fn detects_ampersand_before_attached_ascii_roman_segment( + #[case] input: &str, + #[case] expected: Vec<&str>, + ) { + let actual = ampersand_before_attached_ascii_roman_spans(input) + .into_iter() + .map(|span| &input[span.start_byte..span.end_byte]) + .collect::>(); + assert_eq!(actual, expected); + } + + #[rstest::rstest] + #[case::allcaps("과학&ICT")] + #[case::single_capital("한국&K리츠")] + fn localizes_current_rule71_exit_before_attached_roman(#[case] input: &str) { + let actual = braillify::encode_to_unicode(input).expect("ampersand probe must encode"); + let ranges = ampersand_before_ascii_roman_boundary_ranges(input, &actual); + + assert_eq!(ranges.len(), 1); + assert_eq!(actual.chars().nth(ranges[0].start), Some('⠲')); + } + #[rstest::rstest] #[case::at_and_t("AT&T", "⠠⠠⠁⠞⠈⠯⠠⠞")] #[case::b_and_b("B&B", "⠠⠃⠈⠯⠠⠃")] + #[case::and_c("&c (etc)", "⠈⠯⠉⠀⠐⠣⠑⠞⠉⠐⠜")] fn full_encoder_matches_ueb_3_1_1_ampersand_examples( #[case] input: &str, #[case] expected: &str, From 6c7dd48fcaf1a832ec9fabfee897e7cf83f1ff94 Mon Sep 17 00:00:00 2001 From: devfive Date: Tue, 1 Sep 2026 15:30:47 +0900 Subject: [PATCH 46/77] Preserve Roman mode after attached ampersand --- docs/corpus-analysis/NIKL_2025_V1.md | 155 ++++++++++-------- .../braillify/examples/nikl_corpus_analyze.rs | 97 ++++++----- libs/braillify/src/english_logic.rs | 50 ++++++ libs/braillify/src/rules/korean/rule_71.rs | 40 +++++ 4 files changed, 232 insertions(+), 110 deletions(-) diff --git a/docs/corpus-analysis/NIKL_2025_V1.md b/docs/corpus-analysis/NIKL_2025_V1.md index 13f1a148..5945f458 100644 --- a/docs/corpus-analysis/NIKL_2025_V1.md +++ b/docs/corpus-analysis/NIKL_2025_V1.md @@ -7,9 +7,9 @@ | Metric | Count | |---|---:| | Total | 83528 | -| Exact | 68175 | -| Mismatch | 15353 | -| Exact accuracy | 81.62% | +| Exact | 68187 | +| Mismatch | 15341 | +| Exact accuracy | 81.63% | | Duplicate records | 0 | | Inputs with conflicting references | 0 | @@ -21,14 +21,14 @@ Primary classes are evidence gates, not permissions to change the engine. `imple |---|---:| | `comparison_method` | 295 | | `corpus_suspect` | 1107 | -| `exact` | 68175 | -| `pending_rule_review` | 13775 | +| `exact` | 68187 | +| `pending_rule_review` | 13763 | | `unsupported_character_review` | 176 | | Reproducible reason | Count | |---|---:| -| `exact` | 68175 | -| `foreign_text_rule_review` | 13600 | +| `exact` | 68187 | +| `foreign_text_rule_review` | 13588 | | `nfkc_input_equivalent` | 295 | | `number_rule_review` | 167 | | `punctuation_rule_review` | 8 | @@ -310,7 +310,7 @@ This ranking removes only cases whose first difference is inside an existing out | 10 | `U+2811 ⠑ -> U+283B ⠻` | 101 | | 11 | `U+2800 ⠀ -> U+2832 ⠲` | 100 | | 12 | `U+2810 ⠐ -> U+2814 ⠔` | 98 | -| 13 | `U+280E ⠎ -> U+2820 ⠠` | 92 | +| 13 | `U+280E ⠎ -> U+2820 ⠠` | 93 | | 14 | `U+2803 ⠃ -> U+2820 ⠠` | 87 | | 15 | `U+2804 ⠄ -> U+2834 ⠴` | 87 | | 16 | `U+2826 ⠦ -> U+2800 ⠀` | 79 | @@ -551,26 +551,26 @@ This ranking removes only cases whose first difference is inside an existing out ## Cross-cutting input-only structural cohorts -These are cross-cutting input-only structural cohorts, not new primary classes and not engine routing rules. Candidate selection never changes a case's existing primary class by itself. Only cohort members already classified as `pending_rule_review` form a pending subcluster; exact and other-primary members are controls that retain their existing outcomes. A separate classifier may use independently justified, output-localized PDF evidence, as in the rule-34 three-cell contradiction below. The `uppercase_roman_headword_closed_multiword_parenthetical` gate requires a two-or-more character uppercase ASCII headword immediately followed by a closed parenthesis whose contents are two or more ASCII Roman words separated only by spaces. Because the contents admit only letters and spaces, visible operators, subscript/superscript notation, and nested parentheses are excluded deterministically. The `mixed_roman_korean_word_before_uppercase_headword_expansion` gate further requires a whitespace-delimited preceding word that contains both ASCII Roman and Korean and ends in Korean. It locates the following uppercase headword itself, including its immediately preceding emitted cell, so an earlier Roman entry in the same sentence cannot satisfy the output audit. The `single_capital_followed_by_parenthesized_digits` gate requires a standalone capital, a non-empty closed ASCII-digit parenthetical, and alphanumeric outer boundaries. It likewise includes the emitted entry-boundary cell in localization. The `uppercase_roman_run_followed_by_hyphen_digits` gate requires a maximal uppercase ASCII run, literal ASCII hyphen-minus, a non-empty digit run, and alphanumeric outer boundaries. Its localized range includes the current encoded run and its immediately preceding cell, keeping `F-35`-style routing separate from both parenthesized digits and headword expansions. The `uppercase_alphanumeric_roman_digit_sequence` gate covers the wider rule-35/math collision: a capital-led uppercase/digit sequence with optional internal ASCII hyphen/full stop and trailing prose comma/colon/semicolon. It localizes only the current entry cells and does not decide whether an `A-3`-shaped surface is an identifier or a mathematical expression. The `standalone_multi_character_uppercase_roman_word` gate finds maximal ASCII-letter runs of two or more capitals with non-alphanumeric boundaries; a run immediately followed by `(` is excluded so the HCA-style headword itself is not counted by both gates. The `korean_prefixed_closed_allcaps_parenthetical` gate requires an immediately preceding Korean character and a closed body of two or more uppercase ASCII letters. It intentionally contains both acronym annotations (`책임자(COO)`) and scientific formulae (`일산화탄소(CO)`) so their semantic collision remains measurable. The `korean_prefixed_closed_roman_annotation_rule_34_order` gate accepts the narrower rule-34 body grammar after an immediately preceding Korean character and localizes only the current engine's Korean opening-parenthesis cells. This distinguishes the PDF's parenthesis-before-Roman-indicator order from unrelated differences later in the same sentence. The `multi_character_allcaps_roman_runs_joined_by_middle_dot` gate requires two maximal ASCII-letter runs of at least two capitals joined directly by U+00B7, with non-alphanumeric outer boundaries. It records shapes such as `AI·SW` without assigning prose, mathematics, or science semantics. The `roman_run_immediately_before_attached_middle_dot_boundary` gate is output-localized: it requires a maximal ASCII-letter run immediately before U+00B7 and an attached Korean character or ASCII-letter run after it, then searches for that whole current-engine signature in the actual output. It therefore isolates the Roman terminator boundary without treating unrelated middle dots elsewhere in the sentence as causal. The `attached_ascii_roman_to_korean_script_boundary` gate requires an ASCII letter run immediately followed by Korean script and localizes only the current mode marker there: either rule 29's final `⠲` or rule 39's opening `⠸⠷`. It deliberately does not infer the dominant language of the sentence from that surface boundary. The `korean_majority_same_token_roman_sandwich_non_domain` scope audit mirrors the narrower rule-39 implementation gate: the nearest script characters on both sides of one Korean segment are ASCII Roman, the input is Korean-majority by first-script word counts, and the segment is not dot-delimited like the official domain example. It measures change scope but is not itself an output-localized causal classifier. The `korean_inline_parenthesized_single_arithmetic_operator` gate requires an immediate `Korean(` + one rule-45 arithmetic operator + `)Korean` span. Unlike broad coexistence traits, it also locates the current engine's emitted structure and counts a mismatch as signature-local only when the sentence's first differing cell falls inside that output range. The `allcaps_roman_run_containing_ou` gate finds maximal, alphanumeric-delimited uppercase ASCII runs containing adjacent `OU`. It locates the independently encoded run signature in the complete current output and counts only first differences inside that signature as localized. The `allcaps_roman_run_containing_st` gate applies the same output-position requirement to adjacent `ST`; it is kept separate because UEB 10.12 makes contraction use depend on how an abbreviation or acronym is pronounced. The `attached_ascii_roman_segments_joined_by_ampersand` gate requires non-empty ASCII-letter segments joined directly by `&`, excludes spaced/Korean/alphanumeric continuations, and localizes only the current output cell immediately before the ampersand through an independently encoded real-input prefix. The `ampersand_before_attached_ascii_roman_segment` gate covers the distinct one-sided UEB `&c` boundary: `&` is followed by a complete ASCII-letter segment, while an ASCII alphanumeric or another ampersand immediately before it and a digit continuation after it are excluded. Its output range is anchored by independently encoding the real input prefix through `&`, then includes only the current Rule-71 exit and following Roman re-entry cells. The `consecutive_ascii_roman_words_whitespace_boundary` gate requires two adjacent ASCII-letter words separated only by whitespace. For each boundary it independently encodes the real input prefix ending after the first word, then localizes only the current rule-29 terminator at that position or the full output's replacing blank. This prevents an unrelated Roman occurrence elsewhere in the sentence from satisfying the audit. Slash and other punctuation-separated forms are deliberately excluded. The `decimal_point_between_ascii_digits` gate finds whitespace-delimited words containing `digit.digit` and reproduces each whole word in a neutral Korean context, so suffixes and punctuation remain part of the current-engine signature. The `compact_numeric_ascii_letter_suffix` gate finds a numeric prefix immediately followed by ASCII letters, includes the immediately preceding output cell as its entry boundary, and retains suffix-specific outcome counts. It intentionally includes both possible rule-69 units and ambiguous variable/identifier forms; membership alone does not assign unit semantics. The `rule69_ascii_unit_before_terminator_skipping_symbol` gate is narrower: it accepts only ASCII unit spellings already supported by rule 69, includes the immediately following rule-33/34 punctuation cell in the localized signature, and does not infer new units. The `tight_triangle_mark_immediately_before_korean` gate requires literal `△한글` with no input space and includes the first following Korean cell in its localized output range, so an observed missing-space difference is measured at the mark boundary. +These are cross-cutting input-only structural cohorts, not new primary classes and not engine routing rules. Candidate selection never changes a case's existing primary class by itself. Only cohort members already classified as `pending_rule_review` form a pending subcluster; exact and other-primary members are controls that retain their existing outcomes. A separate classifier may use independently justified, output-localized PDF evidence, as in the rule-34 three-cell contradiction below. The `uppercase_roman_headword_closed_multiword_parenthetical` gate requires a two-or-more character uppercase ASCII headword immediately followed by a closed parenthesis whose contents are two or more ASCII Roman words separated only by spaces. Because the contents admit only letters and spaces, visible operators, subscript/superscript notation, and nested parentheses are excluded deterministically. The `mixed_roman_korean_word_before_uppercase_headword_expansion` gate further requires a whitespace-delimited preceding word that contains both ASCII Roman and Korean and ends in Korean. It locates the following uppercase headword itself, including its immediately preceding emitted cell, so an earlier Roman entry in the same sentence cannot satisfy the output audit. The `single_capital_followed_by_parenthesized_digits` gate requires a standalone capital, a non-empty closed ASCII-digit parenthetical, and alphanumeric outer boundaries. It likewise includes the emitted entry-boundary cell in localization. The `uppercase_roman_run_followed_by_hyphen_digits` gate requires a maximal uppercase ASCII run, literal ASCII hyphen-minus, a non-empty digit run, and alphanumeric outer boundaries. Its localized range includes the current encoded run and its immediately preceding cell, keeping `F-35`-style routing separate from both parenthesized digits and headword expansions. The `uppercase_alphanumeric_roman_digit_sequence` gate covers the wider rule-35/math collision: a capital-led uppercase/digit sequence with optional internal ASCII hyphen/full stop and trailing prose comma/colon/semicolon. It localizes only the current entry cells and does not decide whether an `A-3`-shaped surface is an identifier or a mathematical expression. The `standalone_multi_character_uppercase_roman_word` gate finds maximal ASCII-letter runs of two or more capitals with non-alphanumeric boundaries; a run immediately followed by `(` is excluded so the HCA-style headword itself is not counted by both gates. The `korean_prefixed_closed_allcaps_parenthetical` gate requires an immediately preceding Korean character and a closed body of two or more uppercase ASCII letters. It intentionally contains both acronym annotations (`책임자(COO)`) and scientific formulae (`일산화탄소(CO)`) so their semantic collision remains measurable. The `korean_prefixed_closed_roman_annotation_rule_34_order` gate accepts the narrower rule-34 body grammar after an immediately preceding Korean character and localizes only the current engine's Korean opening-parenthesis cells. This distinguishes the PDF's parenthesis-before-Roman-indicator order from unrelated differences later in the same sentence. The `multi_character_allcaps_roman_runs_joined_by_middle_dot` gate requires two maximal ASCII-letter runs of at least two capitals joined directly by U+00B7, with non-alphanumeric outer boundaries. It records shapes such as `AI·SW` without assigning prose, mathematics, or science semantics. The `roman_run_immediately_before_attached_middle_dot_boundary` gate is output-localized: it requires a maximal ASCII-letter run immediately before U+00B7 and an attached Korean character or ASCII-letter run after it, then searches for that whole current-engine signature in the actual output. It therefore isolates the Roman terminator boundary without treating unrelated middle dots elsewhere in the sentence as causal. The `attached_ascii_roman_to_korean_script_boundary` gate requires an ASCII letter run immediately followed by Korean script and localizes only the current mode marker there: either rule 29's final `⠲` or rule 39's opening `⠸⠷`. It deliberately does not infer the dominant language of the sentence from that surface boundary. The `korean_majority_same_token_roman_sandwich_non_domain` scope audit mirrors the narrower rule-39 implementation gate: the nearest script characters on both sides of one Korean segment are ASCII Roman, the input is Korean-majority by first-script word counts, and the segment is not dot-delimited like the official domain example. It measures change scope but is not itself an output-localized causal classifier. The `korean_inline_parenthesized_single_arithmetic_operator` gate requires an immediate `Korean(` + one rule-45 arithmetic operator + `)Korean` span. Unlike broad coexistence traits, it also locates the current engine's emitted structure and counts a mismatch as signature-local only when the sentence's first differing cell falls inside that output range. The `allcaps_roman_run_containing_ou` gate finds maximal, alphanumeric-delimited uppercase ASCII runs containing adjacent `OU`. It locates the independently encoded run signature in the complete current output and counts only first differences inside that signature as localized. The `allcaps_roman_run_containing_st` gate applies the same output-position requirement to adjacent `ST`; it is kept separate because UEB 10.12 makes contraction use depend on how an abbreviation or acronym is pronounced. The `attached_ascii_roman_segments_joined_by_ampersand` gate requires non-empty ASCII-letter segments joined directly by `&`, excludes spaced/Korean/alphanumeric continuations, and localizes only the current output cell immediately before the ampersand through an independently encoded real-input prefix. The `ampersand_before_attached_ascii_roman_segment` gate covers the distinct one-sided UEB `&c` boundary: `&` is followed by a complete ASCII-letter segment, while an ASCII alphanumeric or another ampersand immediately before it and a digit continuation after it are excluded. Its output range is anchored by independently encoding the real input prefix before each occurrence, then includes only the current Rule-71/29 entry boundary; a second pre-fix anchor through `&` retains the former exit location. The `consecutive_ascii_roman_words_whitespace_boundary` gate requires two adjacent ASCII-letter words separated only by whitespace. For each boundary it independently encodes the real input prefix ending after the first word, then localizes only the current rule-29 terminator at that position or the full output's replacing blank. This prevents an unrelated Roman occurrence elsewhere in the sentence from satisfying the audit. Slash and other punctuation-separated forms are deliberately excluded. The `decimal_point_between_ascii_digits` gate finds whitespace-delimited words containing `digit.digit` and reproduces each whole word in a neutral Korean context, so suffixes and punctuation remain part of the current-engine signature. The `compact_numeric_ascii_letter_suffix` gate finds a numeric prefix immediately followed by ASCII letters, includes the immediately preceding output cell as its entry boundary, and retains suffix-specific outcome counts. It intentionally includes both possible rule-69 units and ambiguous variable/identifier forms; membership alone does not assign unit semantics. The `rule69_ascii_unit_before_terminator_skipping_symbol` gate is narrower: it accepts only ASCII unit spellings already supported by rule 69, includes the immediately following rule-33/34 punctuation cell in the localized signature, and does not infer new units. The `tight_triangle_mark_immediately_before_korean` gate requires literal `△한글` with no input space and includes the first following Korean cell in its localized output range, so an observed missing-space difference is measured at the mark boundary. | Cluster | Candidates | Exact | Mismatch | Conflicting-reference cases | |---|---:|---:|---:|---:| -| `allcaps_roman_run_beginning_with_pure_letter_shortform` | 4120 | 2464 | 1656 | 0 | +| `allcaps_roman_run_beginning_with_pure_letter_shortform` | 4120 | 2465 | 1655 | 0 | | `allcaps_roman_run_containing_ar` | 1022 | 363 | 659 | 0 | | `allcaps_roman_run_containing_ed` | 816 | 319 | 497 | 0 | | `allcaps_roman_run_containing_ou` | 1816 | 64 | 1752 | 0 | -| `allcaps_roman_run_containing_st` | 1479 | 683 | 796 | 0 | -| `ampersand_before_attached_ascii_roman_segment` | 30 | 0 | 30 | 0 | +| `allcaps_roman_run_containing_st` | 1479 | 685 | 794 | 0 | +| `ampersand_before_attached_ascii_roman_segment` | 30 | 12 | 18 | 0 | | `attached_ascii_roman_segments_joined_by_ampersand` | 802 | 284 | 518 | 0 | -| `attached_ascii_roman_to_korean_script_boundary` | 17693 | 12934 | 4759 | 0 | -| `closed_roman_parenthetical_after_non_ascii_letter_boundary` | 63959 | 52243 | 11716 | 0 | +| `attached_ascii_roman_to_korean_script_boundary` | 17693 | 12939 | 4754 | 0 | +| `closed_roman_parenthetical_after_non_ascii_letter_boundary` | 63959 | 52252 | 11707 | 0 | | `compact_numeric_ascii_letter_suffix` | 2975 | 1872 | 1103 | 0 | | `consecutive_ascii_roman_words_whitespace_boundary` | 4679 | 2132 | 2547 | 0 | | `decimal_point_between_ascii_digits` | 4546 | 3383 | 1163 | 0 | | `korean_inline_parenthesized_single_arithmetic_operator` | 23 | 17 | 6 | 0 | | `korean_majority_same_token_roman_sandwich_non_domain` | 947 | 385 | 562 | 0 | -| `korean_prefixed_closed_allcaps_parenthetical` | 54492 | 45024 | 9468 | 0 | -| `korean_prefixed_closed_roman_annotation_rule_34_order` | 64382 | 52846 | 11536 | 0 | +| `korean_prefixed_closed_allcaps_parenthetical` | 54492 | 45032 | 9460 | 0 | +| `korean_prefixed_closed_roman_annotation_rule_34_order` | 64382 | 52855 | 11527 | 0 | | `korean_prefixed_roman_parenthetical_followed_by_allcaps_hyphen_suffix` | 11 | 0 | 11 | 0 | | `mixed_roman_korean_word_before_uppercase_headword_expansion` | 10 | 0 | 10 | 0 | | `multi_character_allcaps_roman_runs_joined_by_middle_dot` | 97 | 0 | 97 | 0 | @@ -581,7 +581,7 @@ These are cross-cutting input-only structural cohorts, not new primary classes a | `roman_run_immediately_before_attached_middle_dot_boundary` | 577 | 0 | 577 | 0 | | `rule69_ascii_unit_before_terminator_skipping_symbol` | 440 | 341 | 99 | 0 | | `single_capital_followed_by_parenthesized_digits` | 1361 | 0 | 1361 | 0 | -| `standalone_multi_character_uppercase_roman_word` | 62411 | 50449 | 11962 | 0 | +| `standalone_multi_character_uppercase_roman_word` | 62411 | 50460 | 11951 | 0 | | `tight_triangle_mark_immediately_before_korean` | 377 | 0 | 377 | 0 | | `uppercase_alphanumeric_roman_digit_sequence` | 3429 | 1911 | 1518 | 0 | | `uppercase_ascii_run_immediately_after_digit_in_roman_sequence` | 1896 | 1102 | 794 | 0 | @@ -592,9 +592,9 @@ These are cross-cutting input-only structural cohorts, not new primary classes a ### `allcaps_roman_run_beginning_with_pure_letter_shortform` -Of the 4120 candidates, 1585 are the actual `pending_rule_review` subcluster. The other 2535 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 4120 candidates, 1584 are the actual `pending_rule_review` subcluster. The other 2536 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. -For this output-signature audit, 1656 mismatches were evaluable and 832 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. +For this output-signature audit, 1655 mismatches were evaluable and 832 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. Localized first-difference transitions: @@ -605,7 +605,7 @@ Mismatch primary-class distribution: - `comparison_method`: 11 - `corpus_suspect`: 50 -- `pending_rule_review`: 1585 +- `pending_rule_review`: 1584 - `unsupported_character_review`: 10 Representative `exact` samples: @@ -907,9 +907,9 @@ Representative `mismatch` samples: ### `allcaps_roman_run_containing_st` -Of the 1479 candidates, 743 are the actual `pending_rule_review` subcluster. The other 736 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 1479 candidates, 741 are the actual `pending_rule_review` subcluster. The other 738 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. -For this output-signature audit, 796 mismatches were evaluable and 443 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. +For this output-signature audit, 794 mismatches were evaluable and 443 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. Localized first-difference transitions: @@ -923,7 +923,7 @@ Mismatch primary-class distribution: - `comparison_method`: 13 - `corpus_suspect`: 35 -- `pending_rule_review`: 743 +- `pending_rule_review`: 741 - `unsupported_character_review`: 5 Representative `exact` samples: @@ -993,36 +993,46 @@ Representative `mismatch` samples: ### `ampersand_before_attached_ascii_roman_segment` -Of the 30 candidates, 26 are the actual `pending_rule_review` subcluster. The other 4 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 30 candidates, 14 are the actual `pending_rule_review` subcluster. The other 16 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. -For this output-signature audit, 30 mismatches were evaluable and 16 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. +For this output-signature audit, 18 mismatches were evaluable and 4 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. Localized first-difference transitions: -- `U+2820 ⠠ -> U+2832 ⠲`: 15 -- `U+2834 ⠴ -> U+2832 ⠲`: 1 +- `U+2808 ⠈ -> U+2834 ⠴`: 3 +- `U+2834 ⠴ -> U+2820 ⠠`: 1 Mismatch primary-class distribution: - `corpus_suspect`: 4 -- `pending_rule_review`: 26 +- `pending_rule_review`: 14 -Representative `localized_mismatch` samples: +Representative `exact` samples: - `sentence_01.json` #5105: 하이브(HYBE) 소속 그룹 &TEAM(앤팀)의 팬 사인회에서 ‘속옷 검사’ 논란이 일자 주최 측에서 사과했지만, 파문은 계속되고 있다. - - expected: `⠪⠐⠍⠃⠀⠴⠈⠯⠠⠠⠞⠂⠍⠦⠄⠗⠒⠓⠕⠢⠠⠴⠺⠀` - - actual: `⠪⠐⠍⠃⠀⠴⠈⠯⠲⠴⠠⠠⠞⠂⠍⠦⠄⠗⠒⠓⠕⠢⠠⠴` - - first differing cell (zero-based): 31 - - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` + - expected: `⠚⠣⠕⠘⠪⠦⠄⠴⠠⠠⠓⠽⠃⠑⠠⠴⠀⠠⠥⠠⠭⠀⠈⠪` + - actual: `⠚⠣⠕⠘⠪⠦⠄⠴⠠⠠⠓⠽⠃⠑⠠⠴⠀⠠⠥⠠⠭⠀⠈⠪` + - current primary/reason: `exact` / `exact` +- `sentence_02.json` #1319: 그는 이날 ‘2023 UNIST 과학&ICT 콘서트’ 행사에 발표자로 나서 연구중심대학을 표방하고 시작한 포스텍, 광주과학기술원(GIST) 등도 20년이 지나면서 고전을 면치 못했다고 설명했다. + - expected: `⠈⠪⠉⠵⠀⠕⠉⠂⠀⠠⠦⠼⠃⠚⠃⠉⠀⠴⠠⠠⠥⠝⠊⠌` + - actual: `⠈⠪⠉⠵⠀⠕⠉⠂⠀⠠⠦⠼⠃⠚⠃⠉⠀⠴⠠⠠⠥⠝⠊⠌` + - current primary/reason: `exact` / `exact` +- `sentence_03.json` #3453: 3일 한화자산운용은 자사 펀드 직판 애플리케이션 ‘파인(PINE)’에 청년소장펀드 2종을 탑재했다고 밝혔다. 해당 펀드는 ‘한화 MZ픽 한국&K리츠’ 및 ‘한화 MZ픽 그린테크’ 2종이다. + - expected: `⠼⠉⠕⠂⠀⠚⠒⠚⠧⠨⠇⠒⠛⠬⠶⠵⠀⠨⠇⠀⠙⠾⠊⠪` + - actual: `⠼⠉⠕⠂⠀⠚⠒⠚⠧⠨⠇⠒⠛⠬⠶⠵⠀⠨⠇⠀⠙⠾⠊⠪` + - current primary/reason: `exact` / `exact` + +Representative `localized_mismatch` samples: + - `sentence_02.json` #974: 드림&Dream멘토링은 시간과 봉사정신을 내어주는(드림) 대학생 멘토와 그로 인해 꿈(Dream)을 이루는 고등학생 멘티가 함께 만들어 가는 이야기라는 뜻으로 멘티의 학교적응력 향상을 목표로 하는 프로그램이다. - expected: `⠊⠪⠐⠕⠢⠴⠈⠯⠴⠠⠙⠗⠂⠍⠲⠑⠝⠒⠓⠥⠐⠕⠶⠵` - - actual: `⠊⠪⠐⠕⠢⠴⠈⠯⠲⠴⠠⠙⠗⠂⠍⠲⠑⠝⠒⠓⠥⠐⠕⠶` + - actual: `⠊⠪⠐⠕⠢⠴⠈⠯⠠⠙⠗⠂⠍⠲⠑⠝⠒⠓⠥⠐⠕⠶⠵⠀` - first differing cell (zero-based): 8 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_03.json` #3453: 3일 한화자산운용은 자사 펀드 직판 애플리케이션 ‘파인(PINE)’에 청년소장펀드 2종을 탑재했다고 밝혔다. 해당 펀드는 ‘한화 MZ픽 한국&K리츠’ 및 ‘한화 MZ픽 그린테크’ 2종이다. - - expected: `⠚⠒⠈⠍⠁⠴⠈⠯⠠⠅⠲⠐⠕⠰⠪⠴⠄⠀⠑⠕⠆⠀⠠⠦` - - actual: `⠚⠒⠈⠍⠁⠴⠈⠯⠲⠴⠠⠅⠲⠐⠕⠰⠪⠴⠄⠀⠑⠕⠆⠀` - - first differing cell (zero-based): 137 +- `sentence_03.json` #19011: 앞서 하이브는 이달 투모로우바이투게더(TXT)와 세븐틴을 예고했고, 내달 방탄소년단(BTS) 정국과 엔하이픈 그리고 앤팀(&TEAM)의 앨범 발매 발표한 바 있다. + - expected: `⠀⠗⠒⠓⠕⠢⠦⠄⠈⠯⠠⠠⠞⠂⠍⠠⠴⠺⠀⠗⠂⠘⠎⠢` + - actual: `⠀⠗⠒⠓⠕⠢⠦⠄⠴⠈⠯⠠⠠⠞⠂⠍⠠⠴⠺⠀⠗⠂⠘⠎` + - first differing cell (zero-based): 115 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` Representative `mismatch` samples: @@ -1034,13 +1044,13 @@ Representative `mismatch` samples: - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` - `sentence_02.json` #974: 드림&Dream멘토링은 시간과 봉사정신을 내어주는(드림) 대학생 멘토와 그로 인해 꿈(Dream)을 이루는 고등학생 멘티가 함께 만들어 가는 이야기라는 뜻으로 멘티의 학교적응력 향상을 목표로 하는 프로그램이다. - expected: `⠊⠪⠐⠕⠢⠴⠈⠯⠴⠠⠙⠗⠂⠍⠲⠑⠝⠒⠓⠥⠐⠕⠶⠵` - - actual: `⠊⠪⠐⠕⠢⠴⠈⠯⠲⠴⠠⠙⠗⠂⠍⠲⠑⠝⠒⠓⠥⠐⠕⠶` + - actual: `⠊⠪⠐⠕⠢⠴⠈⠯⠠⠙⠗⠂⠍⠲⠑⠝⠒⠓⠥⠐⠕⠶⠵⠀` - first differing cell (zero-based): 8 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_03.json` #3453: 3일 한화자산운용은 자사 펀드 직판 애플리케이션 ‘파인(PINE)’에 청년소장펀드 2종을 탑재했다고 밝혔다. 해당 펀드는 ‘한화 MZ픽 한국&K리츠’ 및 ‘한화 MZ픽 그린테크’ 2종이다. - - expected: `⠚⠒⠈⠍⠁⠴⠈⠯⠠⠅⠲⠐⠕⠰⠪⠴⠄⠀⠑⠕⠆⠀⠠⠦` - - actual: `⠚⠒⠈⠍⠁⠴⠈⠯⠲⠴⠠⠅⠲⠐⠕⠰⠪⠴⠄⠀⠑⠕⠆⠀` - - first differing cell (zero-based): 137 +- `sentence_03.json` #6686: 위메이드의 위믹스 3.0 기반 DAO &NFT 플랫폼 ‘나일’(NILE)이 세계 최초의 골프 포인트 대체 불가능 토큰(NFT) ‘레이스 투 위믹스 챔피언십 2023’을 공개했다. + - expected: `⠒⠀⠴⠠⠠⠙⠁⠕⠀⠈⠯⠠⠠⠝⠋⠞⠲⠀⠙⠮⠐⠗⠄⠙` + - actual: `⠒⠀⠴⠠⠠⠙⠁⠕⠲⠀⠴⠈⠯⠠⠠⠝⠋⠞⠲⠀⠙⠮⠐⠗` + - first differing cell (zero-based): 33 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` ### `attached_ascii_roman_segments_joined_by_ampersand` @@ -1100,15 +1110,15 @@ Representative `mismatch` samples: ### `attached_ascii_roman_to_korean_script_boundary` -Of the 17693 candidates, 3927 are the actual `pending_rule_review` subcluster. The other 13766 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 17693 candidates, 3922 are the actual `pending_rule_review` subcluster. The other 13771 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. -For this output-signature audit, 4759 mismatches were evaluable and 0 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. +For this output-signature audit, 4754 mismatches were evaluable and 0 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. Mismatch primary-class distribution: - `comparison_method`: 60 - `corpus_suspect`: 721 -- `pending_rule_review`: 3927 +- `pending_rule_review`: 3922 - `unsupported_character_review`: 51 Representative `exact` samples: @@ -1200,9 +1210,9 @@ Representative `mismatch_rule39_hangul_opening` samples: ### `closed_roman_parenthetical_after_non_ascii_letter_boundary` -Of the 63959 candidates, 10229 are the actual `pending_rule_review` subcluster. The other 53730 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 63959 candidates, 10220 are the actual `pending_rule_review` subcluster. The other 53739 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. -For this output-signature audit, 11716 mismatches were evaluable and 1245 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. +For this output-signature audit, 11707 mismatches were evaluable and 1245 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. Localized first-difference transitions: @@ -1216,7 +1226,7 @@ Mismatch primary-class distribution: - `comparison_method`: 225 - `corpus_suspect`: 1107 -- `pending_rule_review`: 10229 +- `pending_rule_review`: 10220 - `unsupported_character_review`: 155 Representative `exact` samples: @@ -1634,13 +1644,13 @@ Representative `mismatch` samples: ### `korean_prefixed_closed_allcaps_parenthetical` -Of the 54492 candidates, 8167 are the actual `pending_rule_review` subcluster. The other 46325 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 54492 candidates, 8159 are the actual `pending_rule_review` subcluster. The other 46333 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. Mismatch primary-class distribution: - `comparison_method`: 177 - `corpus_suspect`: 992 -- `pending_rule_review`: 8167 +- `pending_rule_review`: 8159 - `unsupported_character_review`: 132 Representative `exact` samples: @@ -1687,9 +1697,9 @@ Representative `mismatch` samples: ### `korean_prefixed_closed_roman_annotation_rule_34_order` -Of the 64382 candidates, 10045 are the actual `pending_rule_review` subcluster. The other 54337 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 64382 candidates, 10036 are the actual `pending_rule_review` subcluster. The other 54346 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. -For this output-signature audit, 11536 mismatches were evaluable and 1111 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. +For this output-signature audit, 11527 mismatches were evaluable and 1111 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. Localized first-difference transitions: @@ -1700,7 +1710,7 @@ Mismatch primary-class distribution: - `comparison_method`: 229 - `corpus_suspect`: 1107 -- `pending_rule_review`: 10045 +- `pending_rule_review`: 10036 - `unsupported_character_review`: 155 Representative `exact` samples: @@ -2364,13 +2374,13 @@ Representative `mismatch` samples: ### `standalone_multi_character_uppercase_roman_word` -Of the 62411 candidates, 10504 are the actual `pending_rule_review` subcluster. The other 51907 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 62411 candidates, 10493 are the actual `pending_rule_review` subcluster. The other 51918 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. Mismatch primary-class distribution: - `comparison_method`: 212 - `corpus_suspect`: 1093 -- `pending_rule_review`: 10504 +- `pending_rule_review`: 10493 - `unsupported_character_review`: 153 Representative `exact` samples: @@ -2889,7 +2899,7 @@ These cohorts are defined by both an input boundary and the sentence's actual fi | Cohort | Candidates | Exact controls | Mismatch | Target localized | Reverse | |---|---:|---:|---:|---:|---:| -| `allcaps_roman_run_beginning_with_pure_letter_shortform` | 4120 | 2464 | 1656 | 785 | 47 | +| `allcaps_roman_run_beginning_with_pure_letter_shortform` | 4120 | 2465 | 1655 | 785 | 47 | | `uppercase_ascii_run_immediately_after_digit_in_roman_sequence` | 1896 | 1102 | 794 | 0 | 1 | | `uppercase_ascii_run_immediately_after_hyphen_in_roman_sequence` | 952 | 164 | 788 | 159 | 4 | | `pure_allcaps_segment_before_hyphen_and_multi_allcaps_segment_after` | 448 | 131 | 317 | 0 | 1 | @@ -2966,7 +2976,7 @@ The generalized fix makes an explicit `EnterEnglish` event idempotent when final Korean rule 34 (2024 Korean-rules PDF p.29) prints the opening enclosure before Roman entry in `링컨(Lincoln)`: the opening sequence is `⠦⠄⠴`. This output-position cohort finds a closed, non-nested parenthetical whose body begins with an ASCII letter and whose opening does not immediately follow another ASCII letter, then locates its complete current-engine signature without consulting the reference. Its localized boundary includes the two current output cells immediately before the opening plus the first three entry cells, so rule-11 math spacing can be separated from a difference later inside the parenthetical. Direct function-call shapes such as `f(x)` are excluded. The PDF's math rule 6 (p.59), rule 12 (pp.63-64), and rule 45 (p.75) nevertheless leave standalone `(x)` and other Roman-letter parenthetical mathematics as counterexamples, so the surface gate is not an engine-routing predicate. -The cross-cutting input cohort contains 63959 candidates: 52243 exact controls and 11716 mismatches. Mismatch primary classes remain unchanged: 10229 `pending_rule_review`, 1107 `corpus_suspect`, 225 `comparison_method`, and 155 `unsupported_character_review`. Of 11716 evaluable mismatches, 1245 have the first difference at the detected leading-spacing/entry boundary; these include 436 `U+2826 ⠦ -> U+2834 ⠴`, 27 `U+2834 ⠴ -> U+2826 ⠦`, and 325 `U+2826 ⠦ -> U+2800 ⠀` transitions; the exact localized reverse `U+2800 ⠀ -> U+2826 ⠦` occurs 0 times. After all earlier localized cohorts and this cohort are excluded, the raw-to-residual target count is 469 -> 26, the Roman-indicator reverse count is 32 -> 2, and the spacing target/reverse counts are 440 -> 79 and 1 -> 1. The short full-encoder form `웹3(Web3)` emits the PDF opening order as a routing control, while whitespace and quote boundaries reproduce the current Roman-first opening. Representative localized samples, with shard and index, are retained in the generated cluster sample table. Because exact controls are abundant and the PDF does not make this input shape semantically sufficient to exclude mathematics, no engine change or primary reclassification is inferred. The one raw/residual spacing reverse is a Korean-body `△(교육)` layout boundary, not a Roman-parenthetical member, and remains a separate rule-72/layout review. +The cross-cutting input cohort contains 63959 candidates: 52252 exact controls and 11707 mismatches. Mismatch primary classes remain unchanged: 10220 `pending_rule_review`, 1107 `corpus_suspect`, 225 `comparison_method`, and 155 `unsupported_character_review`. Of 11707 evaluable mismatches, 1245 have the first difference at the detected leading-spacing/entry boundary; these include 436 `U+2826 ⠦ -> U+2834 ⠴`, 27 `U+2834 ⠴ -> U+2826 ⠦`, and 325 `U+2826 ⠦ -> U+2800 ⠀` transitions; the exact localized reverse `U+2800 ⠀ -> U+2826 ⠦` occurs 0 times. After all earlier localized cohorts and this cohort are excluded, the raw-to-residual target count is 469 -> 26, the Roman-indicator reverse count is 32 -> 2, and the spacing target/reverse counts are 440 -> 79 and 1 -> 1. The short full-encoder form `웹3(Web3)` emits the PDF opening order as a routing control, while whitespace and quote boundaries reproduce the current Roman-first opening. Representative localized samples, with shard and index, are retained in the generated cluster sample table. Because exact controls are abundant and the PDF does not make this input shape semantically sufficient to exclude mathematics, no engine change or primary reclassification is inferred. The one raw/residual spacing reverse is a Korean-body `△(교육)` layout boundary, not a Roman-parenthetical member, and remains a separate rule-72/layout review. The HCA-style headword-expansion shape described above is not an engine implementation premise. The 2024 PDF's math rule 6 defines parentheses and grouping parentheses, rule 11 defines mathematical-expression spacing, rule 12 covers Roman letters in formulas as well as Korean sentences, and rule 45 shows Roman-letter function notation followed by parentheses. Excluding visible operators, scripts, and nesting narrows this corpus cohort, but the PDF does not make the remaining surface shape sufficient to rule out every mathematical counterexample. The cluster therefore remains conservative pending-review evidence only. @@ -3011,7 +3021,7 @@ The cohort contains 816 candidates, 319 exact controls, and 497 mismatches. Exis ### Uppercase Roman runs containing `ST` -UEB 10.12.1 (2024 UEB PDF pp.191-192, printed pp.163-164) says not to use a contraction when letters within an abbreviation or acronym are known to be pronounced separately, but to use the contraction in case of doubt. Rule 10.12.2 requires contractions in other abbreviations and acronyms. Consequently an uppercase `ST` surface alone cannot determine whether `s` + `t` or the `st` groupsign is required. The current cohort contains 1479 candidates, 683 exact controls, and 796 mismatches; primary classes remain 743 `pending_rule_review`, 35 `corpus_suspect`, 13 `comparison_method`, and 5 `unsupported_character_review`. Of 796 evaluable mismatches, 443 are localized to the detected current-engine run: 435 `U+280E ⠎ -> U+280C ⠌` and 1 `U+280C ⠌ -> U+280E ⠎`. The target's raw-to-residual count is 448 -> 13, and the reverse is 2 -> 1. Exact controls such as `STAYG`, `KAIST`, `DGIST`, and `POSTECH` coexist with localized mismatches such as `WSTS`, `HUST`, `OST`, and `USTR`; representative shard/index samples are preserved in the cluster table. This lexical/pronunciation distinction cannot be inferred from the input-only spelling, so no engine change or primary reclassification is made. +UEB 10.12.1 (2024 UEB PDF pp.191-192, printed pp.163-164) says not to use a contraction when letters within an abbreviation or acronym are known to be pronounced separately, but to use the contraction in case of doubt. Rule 10.12.2 requires contractions in other abbreviations and acronyms. Consequently an uppercase `ST` surface alone cannot determine whether `s` + `t` or the `st` groupsign is required. The current cohort contains 1479 candidates, 685 exact controls, and 794 mismatches; primary classes remain 741 `pending_rule_review`, 35 `corpus_suspect`, 13 `comparison_method`, and 5 `unsupported_character_review`. Of 794 evaluable mismatches, 443 are localized to the detected current-engine run: 435 `U+280E ⠎ -> U+280C ⠌` and 1 `U+280C ⠌ -> U+280E ⠎`. The target's raw-to-residual count is 448 -> 13, and the reverse is 2 -> 1. Exact controls such as `STAYG`, `KAIST`, `DGIST`, and `POSTECH` coexist with localized mismatches such as `WSTS`, `HUST`, `OST`, and `USTR`; representative shard/index samples are preserved in the cluster table. This lexical/pronunciation distinction cannot be inferred from the input-only spelling, so no engine change or primary reclassification is made. ### Attached Roman segments joined by ampersand @@ -3027,9 +3037,11 @@ After the change the same cohort contains 802 candidates, 284 exact and 518 mism This is the residual boundary not covered by the implemented `A&B` gate. Official UEB 3.1.1 (2024 UEB PDF p.51, printed p.23) prints `&c (etc)` as `⠈⠯⠉ ⠐⠣⠑⠞⠉⠐⠜`, with no mode break between the ampersand and `c`; its `AT&T` and `B&B` examples give the same attached behavior between Roman segments. Korean rule 71 (2024 Korean-rules PDF pp.51-52, printed pp.45-46) wraps an ampersand in Roman indicators when needed to distinguish it from Hangul, while rules 29 and 32 require one Roman section for consecutive Roman material and UEB transcription inside that section. The spaced Korean control `종이접기 & 클레이아트` remains an independently closed Rule-71 symbol. -The baseline input/output cohort contains 30 candidates, 0 corpus exact controls, and 30 mismatches. Existing mismatch primary classes remain unchanged: 26 `pending_rule_review`, 4 `corpus_suspect`, 0 `comparison_method`, and 0 `unsupported_character_review`. Of 30 evaluable mismatches, 16 have the first difference inside the current Rule-71 exit and following Roman re-entry: 15 `U+2820 ⠠ -> U+2832 ⠲` and 1 `U+2834 ⠴ -> U+2832 ⠲`. Their raw-to-residual counts after all localized cohorts are 50 -> 33 and 3 -> 2; localized reverse counts are zero, while the raw-to-residual maps for `U+2832 ⠲ -> U+2820 ⠠` and `U+2832 ⠲ -> U+2834 ⠴` are 0 -> 0 and 5 -> 4. Representative localized samples include `&TEAM`, `드림&Dream`, and `한국&K리츠`, with shard/index retained above. +The diagnostic checkpoint baseline was 30 candidates / 0 corpus exact / 30 mismatch, preserving 26 `pending_rule_review` and 4 `corpus_suspect` primary classes. Its then-current Rule-71 exit localizer found 16/30 first differences: 15 `U+2820 ⠠ -> U+2832 ⠲` and 1 `U+2834 ⠴ -> U+2832 ⠲`; both localized reverses were zero. The official full-encoder `&c`, `AT&T`, and `B&B` examples were the positive controls, and the spaced Korean Rule-71 example was the negative boundary control. + +The implemented rule is limited to an ampersand followed by a complete attached ASCII-letter segment, with no left ASCII alphanumeric or adjacent ampersand and no trailing digit continuation. Rule 71 opens one Roman section before `&`; rule 29 now leaves it open for the attached letters. It does not name a corpus input or inspect a reference. After the change, the cohort has 30 candidates, 12 exact and 18 mismatch; the corpus-wide exact total rises by the same 12 cases, from 68,175 to 68,187, so no exact regression occurs inside or outside this gate. The 16 former exit/re-entry transitions disappear; 12 become exact and 4 remain mismatches at a different PDF-conflicting boundary. Existing mismatch primary classes remain 14 `pending_rule_review`, 4 `corpus_suspect`, 0 `comparison_method`, and 0 `unsupported_character_review`. -There is no exact NIKL member in this narrow cohort, so the corpus alone cannot authorize a change. The official full-encoder `&c`, `AT&T`, and `B&B` examples are the positive controls; the spaced Korean Rule-71 example is the negative boundary control. This checkpoint records only the baseline and does not yet change the engine or any primary classification. +Of 18 current evaluable mismatches, 4 are localized to the occurrence-specific entry signature: 3 `U+2808 ⠈ -> U+2834 ⠴` where a parenthesized `&TEAM` reference omits Rule 71's required Roman indicator, and 1 `U+2834 ⠴ -> U+2820 ⠠` where a `드림&Dream` reference inserts another Roman indicator inside the same continuous section. Their raw-to-residual counts are 6 -> 3 and 70 -> 64; the corresponding reverse maps are 0 -> 0 and 53 -> 39. These four cases remain conservative corpus/PDF-reference review rather than widening or undoing the rule. Exact samples such as `&TEAM`, `과학&ICT`, and `한국&K리츠`, with shard/index above, are current controls. Primary classifications are never changed by this cohort. Current uppercase-Roman hyphen-digits measurement: 571 candidates, 191 exact controls, 380 mismatches, 365 members in the actual `pending_rule_review` subcluster, and 261/380 evaluable mismatches whose first difference is inside the target run plus its entry boundary. It remains distinct from parenthesized digits and headword expansions; no engine change is inferred. @@ -3081,17 +3093,17 @@ Analyzer checkpoint `ed20f99` recorded the pre-fix baseline as 4,546 candidates, Current all-caps `OU` measurement: 1816 candidates, 64 exact controls, 1752 mismatches, 1666 members in the actual `pending_rule_review` subcluster, and 1332/1752 evaluable mismatches whose first difference is inside the current-engine output signature for the detected run. This is a pronunciation-sensitive UEB review cohort, not an engine routing rule. -Current standalone-uppercase measurement: 62411 candidates, 50449 exact controls, 11962 mismatches, and 10504 members in the actual `pending_rule_review` subcluster. Its high frequency does not make it causal: the same input shape is exact in many cases, and a sentence containing the shape may first differ at another Roman, numeric, or punctuation structure. No engine change is inferred from this cohort. +Current standalone-uppercase measurement: 62411 candidates, 50460 exact controls, 11951 mismatches, and 10493 members in the actual `pending_rule_review` subcluster. Its high frequency does not make it causal: the same input shape is exact in many cases, and a sentence containing the shape may first differ at another Roman, numeric, or punctuation structure. No engine change is inferred from this cohort. -Current Korean-prefixed all-caps parenthetical measurement: 54492 candidates, 45024 exact controls, 9468 mismatches, and 8167 members in the actual `pending_rule_review` subcluster. This is a semantic-collision audit, not an engine routing rule; no implementation change is inferred from its reference outputs. +Current Korean-prefixed all-caps parenthetical measurement: 54492 candidates, 45032 exact controls, 9460 mismatches, and 8159 members in the actual `pending_rule_review` subcluster. This is a semantic-collision audit, not an engine routing rule; no implementation change is inferred from its reference outputs. -Current rule-34 opening-order measurement: 64382 structural candidates, 52846 exact controls, 11536 mismatches, and 1111/11536 evaluable mismatches whose first difference is inside the current engine's Korean opening-parenthesis cells. Only the 1110 localized first-cell transitions have the reference/current order `⠴` versus `⠦`. After requiring the complete reference `⠴⠐⠣` versus current/PDF `⠦⠄⠴` three-cell signature and preserving higher-priority comparison classifications, 1107 are classified as `corpus_suspect`; mere coexistence with a Korean-prefixed Roman annotation does not change a primary class. +Current rule-34 opening-order measurement: 64382 structural candidates, 52855 exact controls, 11527 mismatches, and 1111/11527 evaluable mismatches whose first difference is inside the current engine's Korean opening-parenthesis cells. Only the 1110 localized first-cell transitions have the reference/current order `⠴` versus `⠦`. After requiring the complete reference `⠴⠐⠣` versus current/PDF `⠦⠄⠴` three-cell signature and preserving higher-priority comparison classifications, 1107 are classified as `corpus_suspect`; mere coexistence with a Korean-prefixed Roman annotation does not change a primary class. Current all-caps Roman middle-dot measurement: 97 candidates, 0 exact controls, 97 mismatches, and 96 members in the actual `pending_rule_review` subcluster. This cross-cutting cohort preserves every primary class and is not an engine routing rule. Current Roman-before-middle-dot boundary measurement: 577 candidates, 0 exact controls, 577 mismatches, 565 members in the actual `pending_rule_review` subcluster, and 428/577 evaluable mismatches whose first difference is localized to the one current terminator immediately before the middle dot. The locator encodes each real input prefix ending at the dot, so identifier state such as `K-ICS·...` is retained without consulting expected. This raises localized target coverage from 342 to 417 and reduces the then-leading residual `⠐ -> ⠲` transition from 297 to 223. Rules 29, 33, and 50 support the current terminator path but do not support the localized reference omission; no engine change or primary-class rewrite is inferred. -Current attached Roman-to-Korean boundary measurement: 17693 candidates, 12934 exact controls, 4759 mismatches, 3927 members in the actual `pending_rule_review` subcluster, and 0/4759 evaluable mismatches whose first difference is localized to the current boundary marker. The localized target `⠲ -> ⠸` occurs 0 times and the reverse `⠸ -> ⠲` occurs 0 times. Korean rule 29 (2024 Korean-rules PDF p.26, printed p.20) requires a Roman terminator around Roman text in a Korean sentence, whereas rule 39 (PDF pp.31-32, printed pp.25-26) applies the Korean opening/closing markers only when Roman text is the sentence's main language. Among exact candidates, 10966 expose the current rule-29 terminator at such a boundary and 0 expose the current rule-39 opening. Its printed controls include both an English sentence (`What is 김치 in English?`) and the address `www.대통령.kr` inside a Korean sentence. Official UEB 2.4.7 (2024 UEB PDF p.41, printed p.13) confirms that a UEB mode does not extend through a switch to another braille code, but does not choose whether this Korean boundary belongs to a Korean-main or Roman-main context. Therefore the attached script boundary alone cannot distinguish ordinary Korean prose from an embedded Roman-domain context. Primary classes are preserved and no engine change is inferred without a narrower input-derived dominance gate. At diagnostic checkpoint `57c4608`, this same cohort was 17,693 candidates / 12,724 exact / 4,969 mismatch, with 268 localized `⠲ -> ⠸`, zero reverse, 10,780 exact rule-29 markers, and zero exact rule-39 markers. After narrowing the engine by dominance and the PDF domain shape, it is 12,924 exact / 4,769 mismatch with no localized target or reverse; corpus-wide exact rises from 67,715 to 68,101 (+386). The remaining 1 mismatch with a current rule-39 opening is a list-heavy Korean sentence whose ASCII-leading brand tokens satisfy the mechanical word-count majority; its first difference is not localized to this boundary, so it remains a semantic pending control rather than grounds for another engine branch. +Current attached Roman-to-Korean boundary measurement: 17693 candidates, 12939 exact controls, 4754 mismatches, 3922 members in the actual `pending_rule_review` subcluster, and 0/4754 evaluable mismatches whose first difference is localized to the current boundary marker. The localized target `⠲ -> ⠸` occurs 0 times and the reverse `⠸ -> ⠲` occurs 0 times. Korean rule 29 (2024 Korean-rules PDF p.26, printed p.20) requires a Roman terminator around Roman text in a Korean sentence, whereas rule 39 (PDF pp.31-32, printed pp.25-26) applies the Korean opening/closing markers only when Roman text is the sentence's main language. Among exact candidates, 10971 expose the current rule-29 terminator at such a boundary and 0 expose the current rule-39 opening. Its printed controls include both an English sentence (`What is 김치 in English?`) and the address `www.대통령.kr` inside a Korean sentence. Official UEB 2.4.7 (2024 UEB PDF p.41, printed p.13) confirms that a UEB mode does not extend through a switch to another braille code, but does not choose whether this Korean boundary belongs to a Korean-main or Roman-main context. Therefore the attached script boundary alone cannot distinguish ordinary Korean prose from an embedded Roman-domain context. Primary classes are preserved and no engine change is inferred without a narrower input-derived dominance gate. At diagnostic checkpoint `57c4608`, this same cohort was 17,693 candidates / 12,724 exact / 4,969 mismatch, with 268 localized `⠲ -> ⠸`, zero reverse, 10,780 exact rule-29 markers, and zero exact rule-39 markers. After narrowing the engine by dominance and the PDF domain shape, it is 12,924 exact / 4,769 mismatch with no localized target or reverse; corpus-wide exact rises from 67,715 to 68,101 (+386). The remaining 1 mismatch with a current rule-39 opening is a list-heavy Korean sentence whose ASCII-leading brand tokens satisfy the mechanical word-count majority; its first difference is not localized to this boundary, so it remains a semantic pending control rather than grounds for another engine branch. Current rule-39 narrowed-scope audit: 947 candidates, 385 exact controls, 562 mismatches, and 550 members in the actual `pending_rule_review` subcluster. This input-derived scope is recorded separately from the direct-boundary output-localizer; primary classes are unchanged. At the implementation checkpoint, this conservative first-script-word approximation contains 385 exact cases, accounting for all but one of the corpus-wide +386 net gain; no localized reverse transition is observed in the direct-boundary cohort. @@ -3150,18 +3162,18 @@ Families are diagnostics, not automatic normalization permissions. Rules 68/69 c | Shard | Exact | Total | Accuracy | |---|---:|---:|---:| -| `sentence_01.json` | 20448 | 25000 | 81.79% | -| `sentence_02.json` | 19963 | 25000 | 79.85% | -| `sentence_03.json` | 20543 | 25000 | 82.17% | +| `sentence_01.json` | 20454 | 25000 | 81.82% | +| `sentence_02.json` | 19966 | 25000 | 79.86% | +| `sentence_03.json` | 20546 | 25000 | 82.18% | | `sentence_04.json` | 7221 | 8528 | 84.67% | ## Overlapping mismatch traits | Trait | Count | |---|---:| -| `contains_ascii_digits` | 12094 | -| `contains_ascii_letters` | 15167 | -| `contains_delimiter_or_quote` | 15353 | +| `contains_ascii_digits` | 12088 | +| `contains_ascii_letters` | 15155 | +| `contains_delimiter_or_quote` | 15341 | | `input_not_nfkc` | 716 | ## Samples @@ -3343,6 +3355,7 @@ Against the immediately preceding 63,399-exact run, exact matches increased by 2 | Rules 29/71 complete attached Roman ampersand run | 5,141/5,141 | 67,715/83,528 | 81.07% | Complete ASCII-letter segments joined by `&` retain one Roman section; official `AT&T`, `B&B`, and spaced Korean Rule-71 controls delimit the safe boundary; 273 cases became exact | | Rules 29/39 Roman-to-Korean mode ownership | 5,141/5,141 | 68,101/83,528 | 81.53% | Same-token Korean is wrapped only in a Roman-majority document or the official dot-delimited `www.대통령.kr` domain shape; all three rule-39 PDF controls remain exact and 386 corpus cases became exact | | Rules 29/34 multiword Roman parenthetical continuity | 5,141/5,141 | 68,175/83,528 | 81.62% | A backwards-verified, closed letter-and-space Roman parenthetical tail stays on the prose route; function calls, digits, operators, nested brackets, and punctuation-separated forms remain outside; 74 cases became exact | +| Rules 29/32/71 ampersand before attached Roman segment | 5,141/5,141 | 68,187/83,528 | 81.63% | Official UEB `&c`, `AT&T`, and `B&B` keep one Roman section across attached `&`; the spaced Korean Rule-71 example, left ASCII alphanumerics, repeated ampersands, and trailing digits delimit the gate; 12 cases became exact | The latest full `cargo test -p braillify test_by_testcase --release -- --nocapture` run was accepted from its custom testcase summary, not the trailing filtered harness: `총 테스트 케이스: 5141`, `성공: 5141`, `실패: 0`, and `Skip (limitation): 0`. diff --git a/libs/braillify/examples/nikl_corpus_analyze.rs b/libs/braillify/examples/nikl_corpus_analyze.rs index 035ffbdf..a07d4a7e 100644 --- a/libs/braillify/examples/nikl_corpus_analyze.rs +++ b/libs/braillify/examples/nikl_corpus_analyze.rs @@ -1991,17 +1991,22 @@ fn first_difference_in_attached_roman_ampersand(item: &EncodedCase) -> bool { .any(|range| range.contains(&first_difference)) } -/// Locates the current Rule-71 terminator emitted immediately after an -/// ampersand whose right-hand ASCII Roman segment is attached. The real input -/// prefix through `&` is encoded independently, so another ampersand in the -/// sentence cannot satisfy this output-position audit. +/// Locates the Rule-71/29 boundary around an ampersand whose right-hand ASCII +/// Roman segment is attached. The real input prefix before each occurrence +/// anchors the current complete entry signature; the independently encoded +/// prefix through `&` additionally retains the pre-fix terminator location. +/// Another ampersand in the sentence therefore cannot satisfy this audit. fn ampersand_before_ascii_roman_boundary_ranges( input: &str, actual: &str, ) -> Vec> { let actual_cells = actual.chars().collect::>(); - let mut ranges = BTreeSet::new(); - for span in ampersand_before_attached_ascii_roman_spans(input) { + let spans = ampersand_before_attached_ascii_roman_spans(input); + let mut ranges = current_engine_input_entry_ranges(input, actual, &spans, 7) + .into_iter() + .map(|range| (range.start, range.end)) + .collect::>(); + for span in spans { let ampersand_end = span.start_byte + 1; let Ok(prefix) = braillify::encode_to_unicode(&input[..ampersand_end]) else { continue; @@ -4184,8 +4189,8 @@ fn markdown(report: &AnalysisReport) -> String { UEB `&c` boundary: `&` is followed by a complete ASCII-letter segment, while an ASCII \ alphanumeric or another ampersand immediately before it and a digit continuation after \ it are excluded. Its output range is anchored by independently encoding the real input \ - prefix through `&`, then includes only the current Rule-71 exit and following Roman \ - re-entry cells. The \ + prefix before each occurrence, then includes only the current Rule-71/29 entry \ + boundary; a second pre-fix anchor through `&` retains the former exit location. The \ `consecutive_ascii_roman_words_whitespace_boundary` gate requires two adjacent \ ASCII-letter words separated only by whitespace. For each boundary it independently \ encodes the real input prefix ending after the first word, then localizes only the \ @@ -5193,10 +5198,10 @@ fn markdown(report: &AnalysisReport) -> String { .map(|transition| transition.cases) .unwrap_or(0) }; - let cap_target = "U+2820 ⠠ -> U+2832 ⠲"; - let entry_target = "U+2834 ⠴ -> U+2832 ⠲"; - let cap_reverse = "U+2832 ⠲ -> U+2820 ⠠"; - let entry_reverse = "U+2832 ⠲ -> U+2834 ⠴"; + let omitted_indicator = "U+2808 ⠈ -> U+2834 ⠴"; + let omitted_indicator_reverse = "U+2834 ⠴ -> U+2808 ⠈"; + let duplicate_entry = "U+2834 ⠴ -> U+2820 ⠠"; + let duplicate_entry_reverse = "U+2820 ⠠ -> U+2834 ⠴"; text.push_str(&format!( "\n### Ampersand before an attached ASCII Roman segment\n\n\ This is the residual boundary not covered by the implemented `A&B` gate. Official \ @@ -5208,21 +5213,33 @@ fn markdown(report: &AnalysisReport) -> String { rules 29 and 32 require one Roman section for consecutive Roman material and UEB \ transcription inside that section. The spaced Korean control `종이접기 & \ 클레이아트` remains an independently closed Rule-71 symbol.\n\n\ - The baseline input/output cohort contains {} candidates, {} corpus exact controls, \ - and {} mismatches. Existing mismatch primary classes remain unchanged: {} \ + The diagnostic checkpoint baseline was 30 candidates / 0 corpus exact / 30 \ + mismatch, preserving 26 `pending_rule_review` and 4 `corpus_suspect` primary \ + classes. Its then-current Rule-71 exit localizer found 16/30 first differences: 15 \ + `U+2820 ⠠ -> U+2832 ⠲` and 1 `U+2834 ⠴ -> U+2832 ⠲`; both localized reverses \ + were zero. The official full-encoder `&c`, `AT&T`, and `B&B` examples were the \ + positive controls, and the spaced Korean Rule-71 example was the negative boundary \ + control.\n\n\ + The implemented rule is limited to an ampersand followed by a complete attached \ + ASCII-letter segment, with no left ASCII alphanumeric or adjacent ampersand and no \ + trailing digit continuation. Rule 71 opens one Roman section before `&`; rule 29 \ + now leaves it open for the attached letters. It does not name a corpus input or \ + inspect a reference. After the change, the cohort has {} candidates, {} exact and \ + {} mismatch; the corpus-wide exact total rises by the same 12 cases, from 68,175 to \ + 68,187, so no exact regression occurs inside or outside this gate. The 16 former \ + exit/re-entry transitions disappear; 12 become exact and 4 remain mismatches at a \ + different PDF-conflicting boundary. Existing mismatch primary classes remain {} \ `pending_rule_review`, {} `corpus_suspect`, {} `comparison_method`, and {} \ - `unsupported_character_review`. Of {} evaluable mismatches, {} have the first \ - difference inside the current Rule-71 exit and following Roman re-entry: {} \ - `{cap_target}` and {} `{entry_target}`. Their raw-to-residual counts after all \ - localized cohorts are {} -> {} and {} -> {}; localized reverse counts are zero, \ - while the raw-to-residual maps for `{cap_reverse}` and `{entry_reverse}` are {} -> \ - {} and {} -> {}. Representative localized samples include `&TEAM`, \ - `드림&Dream`, and `한국&K리츠`, with shard/index retained above.\n\n\ - There is no exact NIKL member in this narrow cohort, so the corpus alone cannot \ - authorize a change. The official full-encoder `&c`, `AT&T`, and `B&B` examples are \ - the positive controls; the spaced Korean Rule-71 example is the negative boundary \ - control. This checkpoint records only the baseline and does not yet change the \ - engine or any primary classification.\n", + `unsupported_character_review`.\n\n\ + Of {} current evaluable mismatches, {} are localized to the occurrence-specific \ + entry signature: {} `{omitted_indicator}` where a parenthesized `&TEAM` reference \ + omits Rule 71's required Roman indicator, and {} `{duplicate_entry}` where a \ + `드림&Dream` reference inserts another Roman indicator inside the same continuous \ + section. Their raw-to-residual counts are {} -> {} and {} -> {}; the corresponding \ + reverse maps are {} -> {} and {} -> {}. These four cases remain conservative \ + corpus/PDF-reference review rather than widening or undoing the rule. Exact samples \ + such as `&TEAM`, `과학&ICT`, and `한국&K리츠`, with shard/index above, are current \ + controls. Primary classifications are never changed by this cohort.\n", stats.candidates, stats.exact, stats.mismatch, @@ -5232,16 +5249,16 @@ fn markdown(report: &AnalysisReport) -> String { primary_count("unsupported_character_review"), stats.output_signature_mismatches_evaluated, stats.first_difference_in_output_signature, - localized_count(cap_target), - localized_count(entry_target), - raw_count(cap_target), - residual_count(cap_target), - raw_count(entry_target), - residual_count(entry_target), - raw_count(cap_reverse), - residual_count(cap_reverse), - raw_count(entry_reverse), - residual_count(entry_reverse), + localized_count(omitted_indicator), + localized_count(duplicate_entry), + raw_count(omitted_indicator), + residual_count(omitted_indicator), + raw_count(duplicate_entry), + residual_count(duplicate_entry), + raw_count(omitted_indicator_reverse), + residual_count(omitted_indicator_reverse), + raw_count(duplicate_entry_reverse), + residual_count(duplicate_entry_reverse), )); } if let Some(stats) = report @@ -6085,7 +6102,8 @@ fn markdown(report: &AnalysisReport) -> String { | Rule-29 consecutive Roman entry idempotence | 5,141/5,141 | 67,442/83,528 | 80.74% | An explicit Roman-entry event is ignored only when final emit state is already inside the same Roman section |\n\ | Rules 29/71 complete attached Roman ampersand run | 5,141/5,141 | 67,715/83,528 | 81.07% | Complete ASCII-letter segments joined by `&` retain one Roman section; official `AT&T`, `B&B`, and spaced Korean Rule-71 controls delimit the safe boundary; 273 cases became exact |\n\ | Rules 29/39 Roman-to-Korean mode ownership | 5,141/5,141 | 68,101/83,528 | 81.53% | Same-token Korean is wrapped only in a Roman-majority document or the official dot-delimited `www.대통령.kr` domain shape; all three rule-39 PDF controls remain exact and 386 corpus cases became exact |\n\ - | Rules 29/34 multiword Roman parenthetical continuity | 5,141/5,141 | 68,175/83,528 | 81.62% | A backwards-verified, closed letter-and-space Roman parenthetical tail stays on the prose route; function calls, digits, operators, nested brackets, and punctuation-separated forms remain outside; 74 cases became exact |\n", + | Rules 29/34 multiword Roman parenthetical continuity | 5,141/5,141 | 68,175/83,528 | 81.62% | A backwards-verified, closed letter-and-space Roman parenthetical tail stays on the prose route; function calls, digits, operators, nested brackets, and punctuation-separated forms remain outside; 74 cases became exact |\n\ + | Rules 29/32/71 ampersand before attached Roman segment | 5,141/5,141 | 68,187/83,528 | 81.63% | Official UEB `&c`, `AT&T`, and `B&B` keep one Roman section across attached `&`; the spaced Korean Rule-71 example, left ASCII alphanumerics, repeated ampersands, and trailing digits delimit the gate; 12 cases became exact |\n", ); text.push_str( "\nThe latest full `cargo test -p braillify test_by_testcase --release -- --nocapture` \ @@ -6676,8 +6694,9 @@ mod tests { let actual = braillify::encode_to_unicode(input).expect("ampersand probe must encode"); let ranges = ampersand_before_ascii_roman_boundary_ranges(input, &actual); - assert_eq!(ranges.len(), 1); - assert_eq!(actual.chars().nth(ranges[0].start), Some('⠲')); + assert!(ranges.iter().any(|range| { + actual.chars().skip(range.start).take(3).collect::() == "⠴⠈⠯" + })); } #[rstest::rstest] diff --git a/libs/braillify/src/english_logic.rs b/libs/braillify/src/english_logic.rs index 75c8ea0c..4600cf4d 100644 --- a/libs/braillify/src/english_logic.rs +++ b/libs/braillify/src/english_logic.rs @@ -117,6 +117,39 @@ pub(crate) fn is_attached_ascii_roman_ampersand(word_chars: &[char], index: usiz && (end == word_chars.len() || !word_chars[end].is_ascii_alphanumeric()) } +/// Returns whether `index` is the one-sided ampersand at the beginning of a +/// complete attached ASCII-letter segment. UEB 3.1.1 prints `&c` without a +/// boundary between the ampersand and `c`. A left ASCII alphanumeric, another +/// ampersand, or a trailing digit is excluded so the existing two-sided +/// `A&B` rule and Roman/number identifiers retain their own ownership. +pub(crate) fn is_ampersand_before_attached_ascii_roman_segment( + word_chars: &[char], + index: usize, +) -> bool { + if word_chars.get(index) != Some(&'&') + || !word_chars + .get(index + 1) + .is_some_and(|ch| ch.is_ascii_alphabetic()) + || index + .checked_sub(1) + .and_then(|i| word_chars.get(i)) + .is_some_and(|previous| previous.is_ascii_alphanumeric() || *previous == '&') + { + return false; + } + + let mut end = index + 1; + while word_chars + .get(end) + .is_some_and(|ch| ch.is_ascii_alphabetic()) + { + end += 1; + } + word_chars + .get(end) + .is_none_or(|next| !next.is_ascii_alphanumeric()) +} + fn is_digital_notation_symbol(symbol: char) -> bool { matches!(symbol, '/' | '@' | '#' | '.' | '_' | ':') } @@ -422,6 +455,23 @@ mod tests { ); } + #[rstest::rstest] + #[case::official_and_c("&c", true)] + #[case::official_at_and_t("AT&T", false)] + #[case::official_b_and_b("B&B", false)] + #[case::official_spaced("Marks & Spencer", false)] + fn one_sided_ampersand_requires_complete_right_roman_segment( + #[case] input: &str, + #[case] expected: bool, + ) { + let word = input.chars().collect::>(); + let index = word.iter().rposition(|ch| *ch == '&').unwrap(); + assert_eq!( + is_ampersand_before_attached_ascii_roman_segment(&word, index), + expected, + ); + } + /// `has_digital_notation_signature` — `//`, `@`, `#` 강한 마커 또는 /// underscore + digital marker 조합은 true, 단순 underscore는 false. #[rstest::rstest] diff --git a/libs/braillify/src/rules/korean/rule_71.rs b/libs/braillify/src/rules/korean/rule_71.rs index 41857ea8..0844bfc8 100644 --- a/libs/braillify/src/rules/korean/rule_71.rs +++ b/libs/braillify/src/rules/korean/rule_71.rs @@ -54,6 +54,13 @@ fn is_attached_roman_ampersand(ctx: &RuleContext) -> bool { crate::english_logic::is_attached_ascii_roman_ampersand(ctx.word_chars, ctx.index) } +fn begins_attached_roman_segment(ctx: &RuleContext) -> bool { + crate::english_logic::is_ampersand_before_attached_ascii_roman_segment( + ctx.word_chars, + ctx.index, + ) +} + pub fn is_rule_71_symbol(c: char) -> bool { MAPPINGS.iter().any(|(candidate, _)| *candidate == c) } @@ -109,6 +116,24 @@ impl BrailleRule for Rule71 { let mut encoded = Vec::new(); if should_wrap_information_symbol(ctx) + && ctx.current_char() == '&' + && begins_attached_roman_segment(ctx) + { + // Korean rules 29/32/71 and UEB 3.1.1 `&c`: the ambiguous + // ampersand opens the Roman section, but the attached ASCII-letter + // segment owns its eventual terminator. Do not close and re-enter + // between the two printed-adjacent items. + if !ctx.state.is_english { + if ctx.state.english_dominant_no_indicator { + ctx.state.is_english = true; + ctx.state.needs_english_continuation = false; + ctx.state.roman_number_chain = false; + } else { + crate::rules::roman_mode::enter_english(ctx.state, ctx.result); + } + } + encoded = encode_unicode_cells(unicode); + } else if should_wrap_information_symbol(ctx) && matches!(ctx.current_char(), '&' | '¶' | '©' | '®' | '™') && !is_attached_roman_ampersand(ctx) { @@ -196,6 +221,21 @@ mod tests { assert_eq!(ctx.result.as_slice(), encode_unicode_cells("⠈⠯")); } + /// UEB 3.1.1's official `&c` surface exercises the Korean Rule-71 wrapper + /// state directly: the Roman indicator precedes `&`, and the section stays + /// open for the attached `c` rather than emitting a terminator/re-entry. + #[test] + fn one_sided_official_ampersand_opens_and_keeps_roman_section() { + let mut owned = crate::test_helpers::CtxOwned::for_text("&c", true); + let mut ctx = owned.ctx_at(0); + + let outcome = Rule71.apply(&mut ctx).unwrap(); + + assert!(matches!(outcome, RuleResult::Consumed)); + assert_eq!(ctx.result.as_slice(), encode_unicode_cells("⠴⠈⠯")); + assert!(ctx.state.is_english); + } + /// Full-encoder controls reproduce the two UEB 3.1.1 examples exactly. #[rstest::rstest] #[case::official_at_and_t("AT&T", "⠠⠠⠁⠞⠈⠯⠠⠞")] From 0134035ce047ce9baf3139f1dad906f286f93203 Mon Sep 17 00:00:00 2001 From: devfive Date: Tue, 1 Sep 2026 15:44:07 +0900 Subject: [PATCH 47/77] Diagnose spaced numeric list commas --- docs/corpus-analysis/NIKL_2025_V1.md | 167 ++++++++++++---- .../braillify/examples/nikl_corpus_analyze.rs | 187 ++++++++++++++++++ 2 files changed, 312 insertions(+), 42 deletions(-) diff --git a/docs/corpus-analysis/NIKL_2025_V1.md b/docs/corpus-analysis/NIKL_2025_V1.md index 5945f458..8085d0c4 100644 --- a/docs/corpus-analysis/NIKL_2025_V1.md +++ b/docs/corpus-analysis/NIKL_2025_V1.md @@ -299,25 +299,25 @@ This ranking removes only cases whose first difference is inside an existing out | Rank | Expected → actual first cell | Residual cases | |---:|---|---:| | 1 | `U+2810 ⠐ -> U+2832 ⠲` | 220 | -| 2 | `U+2810 ⠐ -> U+2802 ⠂` | 212 | -| 3 | `U+2817 ⠗ -> U+2820 ⠠` | 164 | -| 4 | `U+2834 ⠴ -> U+2800 ⠀` | 154 | -| 5 | `U+283C ⠼ -> U+2800 ⠀` | 153 | -| 6 | `U+281B ⠛ -> U+2823 ⠣` | 144 | -| 7 | `U+2810 ⠐ -> U+2800 ⠀` | 130 | -| 8 | `U+2826 ⠦ -> U+2810 ⠐` | 125 | -| 9 | `U+2820 ⠠ -> U+281E ⠞` | 106 | -| 10 | `U+2811 ⠑ -> U+283B ⠻` | 101 | -| 11 | `U+2800 ⠀ -> U+2832 ⠲` | 100 | -| 12 | `U+2810 ⠐ -> U+2814 ⠔` | 98 | -| 13 | `U+280E ⠎ -> U+2820 ⠠` | 93 | -| 14 | `U+2803 ⠃ -> U+2820 ⠠` | 87 | -| 15 | `U+2804 ⠄ -> U+2834 ⠴` | 87 | -| 16 | `U+2826 ⠦ -> U+2800 ⠀` | 79 | -| 17 | `U+2824 ⠤ -> U+2800 ⠀` | 78 | -| 18 | `U+280E ⠎ -> U+2829 ⠩` | 74 | -| 19 | `U+2815 ⠕ -> U+2800 ⠀` | 74 | -| 20 | `U+2820 ⠠ -> U+2809 ⠉` | 65 | +| 2 | `U+2817 ⠗ -> U+2820 ⠠` | 164 | +| 3 | `U+2834 ⠴ -> U+2800 ⠀` | 154 | +| 4 | `U+283C ⠼ -> U+2800 ⠀` | 153 | +| 5 | `U+281B ⠛ -> U+2823 ⠣` | 144 | +| 6 | `U+2810 ⠐ -> U+2800 ⠀` | 130 | +| 7 | `U+2826 ⠦ -> U+2810 ⠐` | 125 | +| 8 | `U+2820 ⠠ -> U+281E ⠞` | 106 | +| 9 | `U+2811 ⠑ -> U+283B ⠻` | 101 | +| 10 | `U+2800 ⠀ -> U+2832 ⠲` | 100 | +| 11 | `U+2810 ⠐ -> U+2814 ⠔` | 98 | +| 12 | `U+280E ⠎ -> U+2820 ⠠` | 93 | +| 13 | `U+2803 ⠃ -> U+2820 ⠠` | 87 | +| 14 | `U+2804 ⠄ -> U+2834 ⠴` | 87 | +| 15 | `U+2826 ⠦ -> U+2800 ⠀` | 79 | +| 16 | `U+2824 ⠤ -> U+2800 ⠀` | 78 | +| 17 | `U+280E ⠎ -> U+2829 ⠩` | 74 | +| 18 | `U+2815 ⠕ -> U+2800 ⠀` | 74 | +| 19 | `U+2820 ⠠ -> U+2809 ⠉` | 65 | +| 20 | `U+2834 ⠴ -> U+2820 ⠠` | 64 | ### Residual `U+2810 ⠐ -> U+2832 ⠲` @@ -342,29 +342,6 @@ This ranking removes only cases whose first difference is inside an existing out - first differing cell (zero-based): 6 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -### Residual `U+2810 ⠐ -> U+2802 ⠂` - -- `sentence_01.json` #498: 신제품은 15.6인치(15Z90RT) 울트라슬림과 14인치(14Z90RS)·16인치(16Z90RS) 그램 스타일 등으로 구성된다. 아울러 그램 17, 16, 15, 14 등도 선보일 예정이다. - - expected: `⠪⠐⠗⠢⠀⠼⠁⠛⠐⠀⠼⠁⠋⠐⠀⠼⠁⠑⠐⠀⠼⠁⠙⠀` - - actual: `⠪⠐⠗⠢⠀⠼⠁⠛⠂⠀⠼⠁⠋⠂⠀⠼⠁⠑⠂⠀⠼⠁⠙⠀` - - first differing cell (zero-based): 141 - - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_02.json` #239: 세계 금연의 날(World No Tabacco Day, 2023년 5월 31일)은 세계보건기구(WHO)가 담배가 전 세계적으로 심각한 문제임을 인식시키고 ‘담배 연기 없는 사회’를 만들기 위하여 1987년 제정한 기념일이다. - - expected: `⠃⠁⠒⠕⠀⠠⠐⠙⠐⠀⠼⠃⠚⠃⠉⠀⠉⠡⠀⠼⠑⠏⠂⠀` - - actual: `⠃⠁⠒⠕⠀⠠⠐⠙⠂⠀⠼⠃⠚⠃⠉⠀⠉⠡⠀⠼⠑⠏⠂⠀` - - first differing cell (zero-based): 35 - - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_03.json` #2147: 용량은 512GB, 1테라바이트(TB), 두 가지 종류로 출시됐다. USB 연결 케이블 2종(C-to-C, C-to-A)과 함께 전용 범퍼케이스가 제공된다. - - expected: `⠑⠁⠃⠴⠠⠠⠛⠃⠐⠀⠼⠁⠀⠓⠝⠐⠣⠘⠣⠕⠓⠪⠦⠄` - - actual: `⠑⠁⠃⠴⠠⠠⠛⠃⠂⠀⠼⠁⠀⠓⠝⠐⠣⠘⠣⠕⠓⠪⠦⠄` - - first differing cell (zero-based): 16 - - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_04.json` #299: 국책연구기관인 한국환경연구원(KEI)이 한국수력원자력(한수원)이 낸 ‘신한울 원전 3, 4호기 환경영향평가 재협의 초안’에 부정적인 의견을 밝혔다. KEI는 한수원 조사에서 해산어류(바닷물고기류) 영향이 ‘매우 형식적’으로 이뤄졌다고 지적했다. - - expected: `⠀⠏⠒⠨⠾⠀⠼⠉⠐⠀⠼⠙⠀⠚⠥⠈⠕⠀⠚⠧⠒⠈⠻⠻` - - actual: `⠀⠏⠒⠨⠾⠀⠼⠉⠂⠀⠼⠙⠀⠚⠥⠈⠕⠀⠚⠧⠒⠈⠻⠻` - - first differing cell (zero-based): 90 - - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` - ### Residual `U+2817 ⠗ -> U+2820 ⠠` - `sentence_01.json` #1294: 한편 엔씨소프트는 오픈형 R&D 개발 문화 ‘엔씽(NCing)’의 일환으로 개발 중인 다양한 장르의 신규 프로젝트를 공개하고 있다. @@ -549,6 +526,29 @@ This ranking removes only cases whose first difference is inside an existing out - first differing cell (zero-based): 33 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +### Residual `U+2800 ⠀ -> U+2832 ⠲` + +- `sentence_01.json` #1701: 금호석유화학은 앞으로도 추가적인 등급 상승을 이뤄간다는 방침이다. 이를 위해 직·간접배출(scope 1·2) 뿐 아니라 기타간접배출(scope3)도 제3자 기관의 검증을 통해 더욱 철저히 관리할 예정이다. + - expected: `⠦⠄⠴⠎⠉⠕⠏⠑⠀⠼⠁⠐⠆⠼⠃⠠⠴⠀⠠⠘⠛⠀⠣⠉` + - actual: `⠦⠄⠴⠎⠉⠕⠏⠑⠲⠀⠀⠼⠁⠐⠼⠃⠴⠀⠀⠠⠘⠛⠀⠣` + - first differing cell (zero-based): 92 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_02.json` #1841: 한편 현대차그룹은 현대차 아이오닉6와 제네시스 GV70 전동화 모델이 미국 고속도로 안전보험협회(IIHS)가 발표한 충돌평가에서 최고 등급인 ‘톱 세이프티 픽 플러스(TSP +)’를 받았다고 전했다. + - expected: `⠦⠄⠴⠠⠠⠞⠎⠏⠀⠐⠖⠠⠴⠴⠄⠐⠮⠀⠘⠔⠣⠌⠊⠈` + - actual: `⠦⠄⠴⠠⠠⠞⠎⠏⠲⠀⠢⠠⠴⠴⠄⠐⠮⠀⠘⠔⠣⠌⠊⠈` + - first differing cell (zero-based): 174 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_03.json` #19: 해당 펀드는 전기차와 2차전지 및 2차전지 연관산업인 ESS /VPP(가상발전소)(14%) 등 전세계 친환경 기술 기업에 투자하는 상품이다. + - expected: `⠟⠀⠴⠠⠠⠑⠎⠎⠀⠸⠌⠠⠠⠧⠏⠏⠦⠄⠫⠇⠶⠘⠂⠨` + - actual: `⠟⠀⠴⠠⠠⠑⠎⠎⠲⠀⠸⠌⠴⠠⠠⠧⠏⠏⠦⠄⠫⠇⠶⠘` + - first differing cell (zero-based): 58 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_04.json` #3669: 소방청은 ‘2023 국제소방안전박람회(International fire & Safety Expo Korea 2023)’가 ‘K-소방산업, 세계로 미래로!’를 주제로 오는 30일부터 다음 달 1일까지 3일간, 대구시 북구의 EXCO(전시컨벤션센터)에서 열린다고 27일 밝혔다. + - expected: `⠝⠁⠇⠀⠋⠊⠗⠑⠀⠈⠯⠀⠠⠎⠁⠋⠑⠞⠽⠀⠠⠑⠭⠏` + - actual: `⠝⠁⠇⠀⠋⠊⠗⠑⠲⠀⠈⠯⠀⠴⠠⠎⠁⠋⠑⠞⠽⠀⠠⠑` + - first differing cell (zero-based): 54 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` + ## Cross-cutting input-only structural cohorts These are cross-cutting input-only structural cohorts, not new primary classes and not engine routing rules. Candidate selection never changes a case's existing primary class by itself. Only cohort members already classified as `pending_rule_review` form a pending subcluster; exact and other-primary members are controls that retain their existing outcomes. A separate classifier may use independently justified, output-localized PDF evidence, as in the rule-34 three-cell contradiction below. The `uppercase_roman_headword_closed_multiword_parenthetical` gate requires a two-or-more character uppercase ASCII headword immediately followed by a closed parenthesis whose contents are two or more ASCII Roman words separated only by spaces. Because the contents admit only letters and spaces, visible operators, subscript/superscript notation, and nested parentheses are excluded deterministically. The `mixed_roman_korean_word_before_uppercase_headword_expansion` gate further requires a whitespace-delimited preceding word that contains both ASCII Roman and Korean and ends in Korean. It locates the following uppercase headword itself, including its immediately preceding emitted cell, so an earlier Roman entry in the same sentence cannot satisfy the output audit. The `single_capital_followed_by_parenthesized_digits` gate requires a standalone capital, a non-empty closed ASCII-digit parenthetical, and alphanumeric outer boundaries. It likewise includes the emitted entry-boundary cell in localization. The `uppercase_roman_run_followed_by_hyphen_digits` gate requires a maximal uppercase ASCII run, literal ASCII hyphen-minus, a non-empty digit run, and alphanumeric outer boundaries. Its localized range includes the current encoded run and its immediately preceding cell, keeping `F-35`-style routing separate from both parenthesized digits and headword expansions. The `uppercase_alphanumeric_roman_digit_sequence` gate covers the wider rule-35/math collision: a capital-led uppercase/digit sequence with optional internal ASCII hyphen/full stop and trailing prose comma/colon/semicolon. It localizes only the current entry cells and does not decide whether an `A-3`-shaped surface is an identifier or a mathematical expression. The `standalone_multi_character_uppercase_roman_word` gate finds maximal ASCII-letter runs of two or more capitals with non-alphanumeric boundaries; a run immediately followed by `(` is excluded so the HCA-style headword itself is not counted by both gates. The `korean_prefixed_closed_allcaps_parenthetical` gate requires an immediately preceding Korean character and a closed body of two or more uppercase ASCII letters. It intentionally contains both acronym annotations (`책임자(COO)`) and scientific formulae (`일산화탄소(CO)`) so their semantic collision remains measurable. The `korean_prefixed_closed_roman_annotation_rule_34_order` gate accepts the narrower rule-34 body grammar after an immediately preceding Korean character and localizes only the current engine's Korean opening-parenthesis cells. This distinguishes the PDF's parenthesis-before-Roman-indicator order from unrelated differences later in the same sentence. The `multi_character_allcaps_roman_runs_joined_by_middle_dot` gate requires two maximal ASCII-letter runs of at least two capitals joined directly by U+00B7, with non-alphanumeric outer boundaries. It records shapes such as `AI·SW` without assigning prose, mathematics, or science semantics. The `roman_run_immediately_before_attached_middle_dot_boundary` gate is output-localized: it requires a maximal ASCII-letter run immediately before U+00B7 and an attached Korean character or ASCII-letter run after it, then searches for that whole current-engine signature in the actual output. It therefore isolates the Roman terminator boundary without treating unrelated middle dots elsewhere in the sentence as causal. The `attached_ascii_roman_to_korean_script_boundary` gate requires an ASCII letter run immediately followed by Korean script and localizes only the current mode marker there: either rule 29's final `⠲` or rule 39's opening `⠸⠷`. It deliberately does not infer the dominant language of the sentence from that surface boundary. The `korean_majority_same_token_roman_sandwich_non_domain` scope audit mirrors the narrower rule-39 implementation gate: the nearest script characters on both sides of one Korean segment are ASCII Roman, the input is Korean-majority by first-script word counts, and the segment is not dot-delimited like the official domain example. It measures change scope but is not itself an output-localized causal classifier. The `korean_inline_parenthesized_single_arithmetic_operator` gate requires an immediate `Korean(` + one rule-45 arithmetic operator + `)Korean` span. Unlike broad coexistence traits, it also locates the current engine's emitted structure and counts a mismatch as signature-local only when the sentence's first differing cell falls inside that output range. The `allcaps_roman_run_containing_ou` gate finds maximal, alphanumeric-delimited uppercase ASCII runs containing adjacent `OU`. It locates the independently encoded run signature in the complete current output and counts only first differences inside that signature as localized. The `allcaps_roman_run_containing_st` gate applies the same output-position requirement to adjacent `ST`; it is kept separate because UEB 10.12 makes contraction use depend on how an abbreviation or acronym is pronounced. The `attached_ascii_roman_segments_joined_by_ampersand` gate requires non-empty ASCII-letter segments joined directly by `&`, excludes spaced/Korean/alphanumeric continuations, and localizes only the current output cell immediately before the ampersand through an independently encoded real-input prefix. The `ampersand_before_attached_ascii_roman_segment` gate covers the distinct one-sided UEB `&c` boundary: `&` is followed by a complete ASCII-letter segment, while an ASCII alphanumeric or another ampersand immediately before it and a digit continuation after it are excluded. Its output range is anchored by independently encoding the real input prefix before each occurrence, then includes only the current Rule-71/29 entry boundary; a second pre-fix anchor through `&` retains the former exit location. The `consecutive_ascii_roman_words_whitespace_boundary` gate requires two adjacent ASCII-letter words separated only by whitespace. For each boundary it independently encodes the real input prefix ending after the first word, then localizes only the current rule-29 terminator at that position or the full output's replacing blank. This prevents an unrelated Roman occurrence elsewhere in the sentence from satisfying the audit. Slash and other punctuation-separated forms are deliberately excluded. The `decimal_point_between_ascii_digits` gate finds whitespace-delimited words containing `digit.digit` and reproduces each whole word in a neutral Korean context, so suffixes and punctuation remain part of the current-engine signature. The `compact_numeric_ascii_letter_suffix` gate finds a numeric prefix immediately followed by ASCII letters, includes the immediately preceding output cell as its entry boundary, and retains suffix-specific outcome counts. It intentionally includes both possible rule-69 units and ambiguous variable/identifier forms; membership alone does not assign unit semantics. The `rule69_ascii_unit_before_terminator_skipping_symbol` gate is narrower: it accepts only ASCII unit spellings already supported by rule 69, includes the immediately following rule-33/34 punctuation cell in the localized signature, and does not infer new units. The `tight_triangle_mark_immediately_before_korean` gate requires literal `△한글` with no input space and includes the first following Korean cell in its localized output range, so an observed missing-space difference is measured at the mark boundary. @@ -581,6 +581,7 @@ These are cross-cutting input-only structural cohorts, not new primary classes a | `roman_run_immediately_before_attached_middle_dot_boundary` | 577 | 0 | 577 | 0 | | `rule69_ascii_unit_before_terminator_skipping_symbol` | 440 | 341 | 99 | 0 | | `single_capital_followed_by_parenthesized_digits` | 1361 | 0 | 1361 | 0 | +| `spaced_comma_between_ascii_digit_runs` | 217 | 7 | 210 | 0 | | `standalone_multi_character_uppercase_roman_word` | 62411 | 50460 | 11951 | 0 | | `tight_triangle_mark_immediately_before_korean` | 377 | 0 | 377 | 0 | | `uppercase_alphanumeric_roman_digit_sequence` | 3429 | 1911 | 1518 | 0 | @@ -2372,6 +2373,82 @@ Representative `mismatch` samples: - first differing cell (zero-based): 137 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +### `spaced_comma_between_ascii_digit_runs` + +Of the 217 candidates, 209 are the actual `pending_rule_review` subcluster. The other 8 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. + +For this output-signature audit, 210 mismatches were evaluable and 177 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. + +Localized first-difference transitions: + +- `U+2810 ⠐ -> U+2802 ⠂`: 177 + +Mismatch primary-class distribution: + +- `pending_rule_review`: 209 +- `unsupported_character_review`: 1 + +Representative `exact` samples: + +- `sentence_02.json` #1719: 2001년부터 5년 단위로 무역수지기여도(CTB)를 분석한 결과 2001~2005년 105.0, 2006~2010년 115.5, 2011~2015년 73.4로 나타났으나 2010년대 중반부터 감소하면서 장기평균을 하회하고 있는 것으로 조사됐다. + - expected: `⠼⠃⠚⠚⠁⠀⠉⠡⠘⠍⠓⠎⠀⠼⠑⠀⠉⠡⠀⠊⠒⠍⠗⠐` + - actual: `⠼⠃⠚⠚⠁⠀⠉⠡⠘⠍⠓⠎⠀⠼⠑⠀⠉⠡⠀⠊⠒⠍⠗⠐` + - current primary/reason: `exact` / `exact` +- `sentence_03.json` #18256: 올 시즌 타율 0.337, 30홈런, 83타점, 62도루, OPS(출루율+장타율) 0.993을 기록한 아쿠냐는 내셔널리그에서 가장 유력한 최우수선수(MVP) 후보로 꼽힌다. + - expected: `⠥⠂⠀⠠⠕⠨⠵⠀⠓⠣⠩⠂⠀⠼⠚⠲⠉⠉⠛⠐⠀⠼⠉⠚` + - actual: `⠥⠂⠀⠠⠕⠨⠵⠀⠓⠣⠩⠂⠀⠼⠚⠲⠉⠉⠛⠐⠀⠼⠉⠚` + - current primary/reason: `exact` / `exact` +- `sentence_04.json` #4117: 예정처가 2021년 6월을 기준(100)으로 산출한 전세가격지수를 보면 올 1월 전세가격지수는 전국 평균이 95.2, 수도권은 92.6, 지방이 97.6이었다. 7월에는 각각 91.4, 88.2, 94.4로 더 떨어졌다. + - expected: `⠌⠨⠻⠰⠎⠫⠀⠼⠃⠚⠃⠁⠀⠉⠡⠀⠼⠋⠏⠂⠮⠀⠈⠕` + - actual: `⠌⠨⠻⠰⠎⠫⠀⠼⠃⠚⠃⠁⠀⠉⠡⠀⠼⠋⠏⠂⠮⠀⠈⠕` + - current primary/reason: `exact` / `exact` + +Representative `localized_mismatch` samples: + +- `sentence_01.json` #498: 신제품은 15.6인치(15Z90RT) 울트라슬림과 14인치(14Z90RS)·16인치(16Z90RS) 그램 스타일 등으로 구성된다. 아울러 그램 17, 16, 15, 14 등도 선보일 예정이다. + - expected: `⠪⠐⠗⠢⠀⠼⠁⠛⠐⠀⠼⠁⠋⠐⠀⠼⠁⠑⠐⠀⠼⠁⠙⠀` + - actual: `⠪⠐⠗⠢⠀⠼⠁⠛⠂⠀⠼⠁⠋⠂⠀⠼⠁⠑⠂⠀⠼⠁⠙⠀` + - first differing cell (zero-based): 141 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_02.json` #442: 시는 총사업비 59억 원을 투입해 1, 2단계 사업을 완료했으며, 부지면적 6만 3천㎡에 산악 지형용 자전거(MTB) 연습을 위한 펌프트랙 5개의 코스와 조명시설을 갖춘 축구장 2면을 조성했다. + - expected: `⠍⠕⠃⠚⠗⠀⠼⠁⠐⠀⠼⠃⠀⠊⠒⠈⠌⠀⠇⠎⠃⠮⠀⠧` + - actual: `⠍⠕⠃⠚⠗⠀⠼⠁⠂⠀⠼⠃⠀⠊⠒⠈⠌⠀⠇⠎⠃⠮⠀⠧` + - first differing cell (zero-based): 31 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_03.json` #3215: 현재경기판단(69, 5포인트), 향후경기전망(78, 4포인트)의 상승 폭이 상대적으로 컸다. 또 소비자지출전망(113)도 2포인트 올랐다. 생활형편전망(93)과 가계수입전망(98), 현재생활형편(89)은 각각 1포인트 상승했다. + - expected: `⠒⠊⠒⠦⠄⠼⠋⠊⠐⠀⠼⠑⠀⠙⠥⠟⠓⠪⠠⠴⠐⠀⠚⠜` + - actual: `⠒⠊⠒⠦⠄⠼⠋⠊⠂⠀⠼⠑⠀⠙⠥⠟⠓⠪⠠⠴⠐⠀⠚⠜` + - first differing cell (zero-based): 17 + - current primary/reason: `pending_rule_review` / `number_rule_review` +- `sentence_04.json` #299: 국책연구기관인 한국환경연구원(KEI)이 한국수력원자력(한수원)이 낸 ‘신한울 원전 3, 4호기 환경영향평가 재협의 초안’에 부정적인 의견을 밝혔다. KEI는 한수원 조사에서 해산어류(바닷물고기류) 영향이 ‘매우 형식적’으로 이뤄졌다고 지적했다. + - expected: `⠀⠏⠒⠨⠾⠀⠼⠉⠐⠀⠼⠙⠀⠚⠥⠈⠕⠀⠚⠧⠒⠈⠻⠻` + - actual: `⠀⠏⠒⠨⠾⠀⠼⠉⠂⠀⠼⠙⠀⠚⠥⠈⠕⠀⠚⠧⠒⠈⠻⠻` + - first differing cell (zero-based): 90 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` + +Representative `mismatch` samples: + +- `sentence_01.json` #498: 신제품은 15.6인치(15Z90RT) 울트라슬림과 14인치(14Z90RS)·16인치(16Z90RS) 그램 스타일 등으로 구성된다. 아울러 그램 17, 16, 15, 14 등도 선보일 예정이다. + - expected: `⠪⠐⠗⠢⠀⠼⠁⠛⠐⠀⠼⠁⠋⠐⠀⠼⠁⠑⠐⠀⠼⠁⠙⠀` + - actual: `⠪⠐⠗⠢⠀⠼⠁⠛⠂⠀⠼⠁⠋⠂⠀⠼⠁⠑⠂⠀⠼⠁⠙⠀` + - first differing cell (zero-based): 141 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_02.json` #442: 시는 총사업비 59억 원을 투입해 1, 2단계 사업을 완료했으며, 부지면적 6만 3천㎡에 산악 지형용 자전거(MTB) 연습을 위한 펌프트랙 5개의 코스와 조명시설을 갖춘 축구장 2면을 조성했다. + - expected: `⠍⠕⠃⠚⠗⠀⠼⠁⠐⠀⠼⠃⠀⠊⠒⠈⠌⠀⠇⠎⠃⠮⠀⠧` + - actual: `⠍⠕⠃⠚⠗⠀⠼⠁⠂⠀⠼⠃⠀⠊⠒⠈⠌⠀⠇⠎⠃⠮⠀⠧` + - first differing cell (zero-based): 31 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_03.json` #3215: 현재경기판단(69, 5포인트), 향후경기전망(78, 4포인트)의 상승 폭이 상대적으로 컸다. 또 소비자지출전망(113)도 2포인트 올랐다. 생활형편전망(93)과 가계수입전망(98), 현재생활형편(89)은 각각 1포인트 상승했다. + - expected: `⠒⠊⠒⠦⠄⠼⠋⠊⠐⠀⠼⠑⠀⠙⠥⠟⠓⠪⠠⠴⠐⠀⠚⠜` + - actual: `⠒⠊⠒⠦⠄⠼⠋⠊⠂⠀⠼⠑⠀⠙⠥⠟⠓⠪⠠⠴⠐⠀⠚⠜` + - first differing cell (zero-based): 17 + - current primary/reason: `pending_rule_review` / `number_rule_review` +- `sentence_04.json` #299: 국책연구기관인 한국환경연구원(KEI)이 한국수력원자력(한수원)이 낸 ‘신한울 원전 3, 4호기 환경영향평가 재협의 초안’에 부정적인 의견을 밝혔다. KEI는 한수원 조사에서 해산어류(바닷물고기류) 영향이 ‘매우 형식적’으로 이뤄졌다고 지적했다. + - expected: `⠀⠏⠒⠨⠾⠀⠼⠉⠐⠀⠼⠙⠀⠚⠥⠈⠕⠀⠚⠧⠒⠈⠻⠻` + - actual: `⠀⠏⠒⠨⠾⠀⠼⠉⠂⠀⠼⠙⠀⠚⠥⠈⠕⠀⠚⠧⠒⠈⠻⠻` + - first differing cell (zero-based): 90 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` + ### `standalone_multi_character_uppercase_roman_word` Of the 62411 candidates, 10493 are the actual `pending_rule_review` subcluster. The other 51918 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. @@ -3043,6 +3120,12 @@ The implemented rule is limited to an ampersand followed by a complete attached Of 18 current evaluable mismatches, 4 are localized to the occurrence-specific entry signature: 3 `U+2808 ⠈ -> U+2834 ⠴` where a parenthesized `&TEAM` reference omits Rule 71's required Roman indicator, and 1 `U+2834 ⠴ -> U+2820 ⠠` where a `드림&Dream` reference inserts another Roman indicator inside the same continuous section. Their raw-to-residual counts are 6 -> 3 and 70 -> 64; the corresponding reverse maps are 0 -> 0 and 53 -> 39. These four cases remain conservative corpus/PDF-reference review rather than widening or undoing the rule. Exact samples such as `&TEAM`, `과학&ICT`, and `한국&K리츠`, with shard/index above, are current controls. Primary classifications are never changed by this cohort. +### Spaced comma between ASCII digit runs + +This output-localized cohort requires a comma immediately after an ASCII digit, one or more following whitespace characters, and another ASCII digit. Korean rule 41 (2024 Korean-rules PDF p.33, printed p.27) assigns `⠂` only when the comma is *attached* between digits, as in the exact control `9,375명`; rule 49 (PDF pp.37-38, printed pp.31-32) assigns the ordinary Korean comma `⠐`, illustrated by `근면, 검소, 협동은 ...`. Rule 33 (PDF p.28, printed p.22) independently keeps Korean punctuation at Roman-to-Korean boundaries. By contrast, official UEB 7 (2024 UEB PDF p.103, printed p.75) defines its prose comma as `⠂`, and UEB 6.2.1 (PDF p.94, printed p.66) retains `⠂` inside attached numeric forms such as `3,500`. Those two surfaces are negative controls and are excluded by this gate. + +Before any engine change, the cohort has 217 candidates / 7 exact / 210 mismatch. Existing mismatch primaries remain 209 `pending_rule_review`, 0 `corpus_suspect`, 0 `comparison_method`, and 1 `unsupported_character_review`. The occurrence-specific locator encodes the real prefix immediately before each comma and claims only its next emitted cell: 177/210 evaluable mismatches localize there, including 177 `U+2810 ⠐ -> U+2802 ⠂` and 0 `U+2802 ⠂ -> U+2810 ⠐`. Across all pending cases, the raw-to-residual counts after adding this cohort are 212 -> 35 for the target and 12 -> 10 for the reverse. No primary class is changed and no implementation result is inferred from the reference outputs at this diagnostic checkpoint. + Current uppercase-Roman hyphen-digits measurement: 571 candidates, 191 exact controls, 380 mismatches, 365 members in the actual `pending_rule_review` subcluster, and 261/380 evaluable mismatches whose first difference is inside the target run plus its entry boundary. It remains distinct from parenthesized digits and headword expansions; no engine change is inferred. Current uppercase alphanumeric Roman-digit sequence measurement: 3429 candidates, 1911 exact controls, 1518 mismatches, 1409 members in the actual `pending_rule_review` subcluster, and 151/1518 evaluable mismatches whose first difference is localized to the current entry cells. The target `⠴ -> blank` occurs 93 times and the reverse occurs 0 times. Korean rule 35 (2024 Korean-rules PDF pp.29-30, printed pp.23-24) explicitly treats `MP3`, `A4`, `KF94`, and `D-100` as Roman-number sequences. Math rules 11/12 (PDF pp.63-65, printed pp.57-59) separately route mathematical Roman notation without the prose Roman indicator and with two-cell spacing. Official UEB 6.5.1-6.5.2 (2024 UEB PDF p.96, printed p.68) determines numeric/grade-1 continuation only after the surrounding mode has been chosen. Thus surfaces such as `A-3` cannot be assigned identifier or variable semantics from this structure alone. Exact controls and both directions are retained; no engine change is inferred. diff --git a/libs/braillify/examples/nikl_corpus_analyze.rs b/libs/braillify/examples/nikl_corpus_analyze.rs index a07d4a7e..9aeedddc 100644 --- a/libs/braillify/examples/nikl_corpus_analyze.rs +++ b/libs/braillify/examples/nikl_corpus_analyze.rs @@ -595,6 +595,7 @@ const ATTACHED_ASCII_ROMAN_SEGMENTS_JOINED_BY_AMPERSAND: &str = "attached_ascii_roman_segments_joined_by_ampersand"; const AMPERSAND_BEFORE_ATTACHED_ASCII_ROMAN_SEGMENT: &str = "ampersand_before_attached_ascii_roman_segment"; +const SPACED_COMMA_BETWEEN_ASCII_DIGIT_RUNS: &str = "spaced_comma_between_ascii_digit_runs"; const SINGLE_CAPITAL_PARENTHESIZED_DIGITS: &str = "single_capital_followed_by_parenthesized_digits"; const MIXED_ROMAN_KOREAN_BEFORE_HEADWORD_EXPANSION: &str = "mixed_roman_korean_word_before_uppercase_headword_expansion"; @@ -1149,6 +1150,41 @@ fn ampersand_before_attached_ascii_roman_spans(input: &str) -> Vec { spans } +/// Finds a comma immediately after an ASCII digit and followed, after one or +/// more whitespace characters, by another ASCII digit. Korean rule 41 is +/// explicitly limited to a comma *attached* between digits, so this cohort +/// isolates spaced numeric-list punctuation without claiming Roman prose +/// commas or attached digit grouping. +fn spaced_comma_between_ascii_digit_run_spans(input: &str) -> Vec { + input + .match_indices(',') + .filter_map(|(comma_byte, comma)| { + input[..comma_byte] + .chars() + .next_back() + .is_some_and(|ch| ch.is_ascii_digit()) + .then_some(())?; + + let after_comma = comma_byte + comma.len(); + let mut following = input[after_comma..].char_indices(); + let (first_offset, first) = following.next()?; + if first_offset != 0 || !first.is_whitespace() { + return None; + } + + let (digit_offset, digit) = following.find(|(_, ch)| !ch.is_whitespace())?; + if !digit.is_ascii_digit() { + return None; + } + + Some(InputSpan { + start_byte: comma_byte, + end_byte: after_comma + digit_offset + digit.len_utf8(), + }) + }) + .collect() +} + /// Finds maximal pure-uppercase ASCII letter runs whose beginning is itself a /// pure-letter UEB shortform abbreviation. UEB 5.7.2 and 10.9.7-10.9.8 require /// grade 1 both for a complete shortform-shaped letters-sequence (`WD`) and for @@ -2033,6 +2069,44 @@ fn first_difference_after_ampersand_before_ascii_roman(item: &EncodedCase) -> bo .any(|range| range.contains(&first_difference)) } +/// Locates the emitted comma cell by encoding the real prefix immediately +/// before each occurrence. The prefix must match the complete current output, +/// so another comma elsewhere in the sentence cannot satisfy the audit. +fn spaced_numeric_list_comma_actual_ranges( + input: &str, + actual: &str, +) -> Vec> { + let actual_cells = actual.chars().collect::>(); + spaced_comma_between_ascii_digit_run_spans(input) + .into_iter() + .filter_map(|span| { + let prefix = braillify::encode_to_unicode(&input[..span.start_byte]).ok()?; + let prefix_cells = prefix.chars().collect::>(); + if !actual_cells.starts_with(&prefix_cells) || prefix_cells.len() >= actual_cells.len() + { + return None; + } + Some((prefix_cells.len(), prefix_cells.len() + 1)) + }) + .collect::>() + .into_iter() + .map(|(start, end)| start..end) + .collect() +} + +fn first_difference_at_spaced_numeric_list_comma(item: &EncodedCase) -> bool { + let Ok(actual) = &item.actual else { + return false; + }; + if actual == &item.located.case.unicode { + return false; + } + let first_difference = first_difference_cell(&item.located.case.unicode, actual); + spaced_numeric_list_comma_actual_ranges(&item.located.case.input, actual) + .into_iter() + .any(|range| range.contains(&first_difference)) +} + /// Finds a standalone single capital immediately followed by a non-empty, /// closed ASCII-digit parenthetical, such as `A(14)`. The span is deliberately /// semantic-neutral: prose labels and mathematical function notation can share @@ -2386,6 +2460,7 @@ fn first_difference_claimed_before_consecutive_ascii_roman_boundary(item: &Encod fn first_difference_claimed_by_localized_cohort(item: &EncodedCase) -> bool { first_difference_claimed_before_consecutive_ascii_roman_boundary(item) || first_difference_at_consecutive_ascii_roman_word_boundary(item) + || first_difference_at_spaced_numeric_list_comma(item) } /// Input-only candidate gate for acronym expansions such as @@ -3261,6 +3336,10 @@ fn analyze( AMPERSAND_BEFORE_ATTACHED_ASCII_ROMAN_SEGMENT.to_string(), PendingRuleReviewClusterStats::default(), ), + ( + SPACED_COMMA_BETWEEN_ASCII_DIGIT_RUNS.to_string(), + PendingRuleReviewClusterStats::default(), + ), ( ALLCAPS_ROMAN_MIDDLE_DOT_RUNS.to_string(), PendingRuleReviewClusterStats::default(), @@ -3502,6 +3581,12 @@ fn analyze( ), true, ), + ( + SPACED_COMMA_BETWEEN_ASCII_DIGIT_RUNS, + !spaced_comma_between_ascii_digit_run_spans(&item.located.case.input).is_empty(), + Some(first_difference_at_spaced_numeric_list_comma(item)), + true, + ), ( ALLCAPS_ROMAN_MIDDLE_DOT_RUNS, has_allcaps_roman_middle_dot_runs(&item.located.case.input), @@ -5261,6 +5346,78 @@ fn markdown(report: &AnalysisReport) -> String { residual_count(duplicate_entry_reverse), )); } + if let Some(stats) = report + .pending_rule_review_clusters + .get(SPACED_COMMA_BETWEEN_ASCII_DIGIT_RUNS) + { + let primary_count = |name: &str| { + stats + .mismatch_primary_classes + .get(name) + .copied() + .unwrap_or(0) + }; + let localized_count = |name: &str| { + stats + .first_difference_in_output_signature_transitions + .get(name) + .copied() + .unwrap_or(0) + }; + let raw_count = |name: &str| { + report + .pending_first_difference_cell_transitions + .get(name) + .map(|transition| transition.cases) + .unwrap_or(0) + }; + let residual_count = |name: &str| { + report + .pending_first_difference_transitions_after_localized_cohorts + .get(name) + .map(|transition| transition.cases) + .unwrap_or(0) + }; + let korean_to_ueb = "U+2810 ⠐ -> U+2802 ⠂"; + let ueb_to_korean = "U+2802 ⠂ -> U+2810 ⠐"; + text.push_str(&format!( + "\n### Spaced comma between ASCII digit runs\n\n\ + This output-localized cohort requires a comma immediately after an ASCII digit, \ + one or more following whitespace characters, and another ASCII digit. Korean rule \ + 41 (2024 Korean-rules PDF p.33, printed p.27) assigns `⠂` only when the comma is \ + *attached* between digits, as in the exact control `9,375명`; rule 49 (PDF \ + pp.37-38, printed pp.31-32) assigns the ordinary Korean comma `⠐`, illustrated by \ + `근면, 검소, 협동은 ...`. Rule 33 (PDF p.28, printed p.22) independently keeps \ + Korean punctuation at Roman-to-Korean boundaries. By contrast, official UEB 7 \ + (2024 UEB PDF p.103, printed p.75) defines its prose comma as `⠂`, and UEB 6.2.1 \ + (PDF p.94, printed p.66) retains `⠂` inside attached numeric forms such as \ + `3,500`. Those two surfaces are negative controls and are excluded by this gate.\n\n\ + Before any engine change, the cohort has {} candidates / {} exact / {} mismatch. \ + Existing mismatch primaries remain {} `pending_rule_review`, {} `corpus_suspect`, \ + {} `comparison_method`, and {} `unsupported_character_review`. The \ + occurrence-specific locator encodes the real prefix immediately before each comma \ + and claims only its next emitted cell: {}/{} evaluable mismatches localize there, \ + including {} `{korean_to_ueb}` and {} `{ueb_to_korean}`. Across all pending cases, \ + the raw-to-residual counts after adding this cohort are {} -> {} for the target and \ + {} -> {} for the reverse. No primary class is changed and no implementation result \ + is inferred from the reference outputs at this diagnostic checkpoint.\n", + stats.candidates, + stats.exact, + stats.mismatch, + primary_count("pending_rule_review"), + primary_count("corpus_suspect"), + primary_count("comparison_method"), + primary_count("unsupported_character_review"), + stats.first_difference_in_output_signature, + stats.output_signature_mismatches_evaluated, + localized_count(korean_to_ueb), + localized_count(ueb_to_korean), + raw_count(korean_to_ueb), + residual_count(korean_to_ueb), + raw_count(ueb_to_korean), + residual_count(ueb_to_korean), + )); + } if let Some(stats) = report .pending_rule_review_clusters .get(UPPERCASE_ROMAN_HYPHEN_DIGITS) @@ -6687,6 +6844,36 @@ mod tests { assert_eq!(actual, expected); } + #[rstest::rstest] + #[case::numeric_list("17, 16, 15", vec![", 1", ", 1"])] + #[case::attached_rule41_number("9,375명", vec![])] + #[case::roman_prose("A, B", vec![])] + #[case::non_numeric_right_side("17, sixteen", vec![])] + fn detects_only_spaced_commas_between_ascii_digit_runs( + #[case] input: &str, + #[case] expected: Vec<&str>, + ) { + let actual = spaced_comma_between_ascii_digit_run_spans(input) + .into_iter() + .map(|span| &input[span.start_byte..span.end_byte]) + .collect::>(); + assert_eq!(actual, expected); + } + + #[test] + fn localizes_each_spaced_numeric_list_comma_in_current_output() { + let input = "순위는 17, 16, 15이다."; + let actual = braillify::encode_to_unicode(input).expect("numeric-list probe must encode"); + let ranges = spaced_numeric_list_comma_actual_ranges(input, &actual); + + assert_eq!(ranges.len(), 2); + assert!( + ranges + .iter() + .all(|range| actual.chars().nth(range.start) == Some('⠂')) + ); + } + #[rstest::rstest] #[case::allcaps("과학&ICT")] #[case::single_capital("한국&K리츠")] From 7e3c2300e49109dd40331c14680ba75befe11f92 Mon Sep 17 00:00:00 2001 From: devfive Date: Tue, 1 Sep 2026 15:57:00 +0900 Subject: [PATCH 48/77] Diagnose Roman commas before digit-led Korean --- docs/corpus-analysis/NIKL_2025_V1.md | 70 ++++++++- .../braillify/examples/nikl_corpus_analyze.rs | 147 +++++++++++++++++- 2 files changed, 213 insertions(+), 4 deletions(-) diff --git a/docs/corpus-analysis/NIKL_2025_V1.md b/docs/corpus-analysis/NIKL_2025_V1.md index 8085d0c4..e7de7f89 100644 --- a/docs/corpus-analysis/NIKL_2025_V1.md +++ b/docs/corpus-analysis/NIKL_2025_V1.md @@ -561,6 +561,7 @@ These are cross-cutting input-only structural cohorts, not new primary classes a | `allcaps_roman_run_containing_ou` | 1816 | 64 | 1752 | 0 | | `allcaps_roman_run_containing_st` | 1479 | 685 | 794 | 0 | | `ampersand_before_attached_ascii_roman_segment` | 30 | 12 | 18 | 0 | +| `ascii_roman_tail_comma_before_digit_korean_token` | 58 | 0 | 58 | 0 | | `attached_ascii_roman_segments_joined_by_ampersand` | 802 | 284 | 518 | 0 | | `attached_ascii_roman_to_korean_script_boundary` | 17693 | 12939 | 4754 | 0 | | `closed_roman_parenthetical_after_non_ascii_letter_boundary` | 63959 | 52252 | 11707 | 0 | @@ -1054,6 +1055,67 @@ Representative `mismatch` samples: - first differing cell (zero-based): 33 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +### `ascii_roman_tail_comma_before_digit_korean_token` + +Of the 58 candidates, 56 are the actual `pending_rule_review` subcluster. The other 2 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. + +For this output-signature audit, 58 mismatches were evaluable and 23 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. + +Localized first-difference transitions: + +- `U+2810 ⠐ -> U+2802 ⠂`: 23 + +Mismatch primary-class distribution: + +- `corpus_suspect`: 2 +- `pending_rule_review`: 56 + +Representative `localized_mismatch` samples: + +- `sentence_01.json` #1754: 이 부사장은 인공지능(AI) 챗봇인 챗GPT가 1990년대에 등장한 PC, 2000년대의 인터넷, 2010년대에 출시된 스마트폰 못지 않게 반도체 시장에도 큰 파급력을 가질 것이라고 내다봤다. + - expected: `⠚⠒⠀⠴⠠⠠⠏⠉⠐⠀⠼⠃⠚⠚⠚⠀⠉⠡⠊⠗⠺⠀⠟⠓` + - actual: `⠚⠒⠀⠴⠠⠠⠏⠉⠂⠀⠼⠃⠚⠚⠚⠀⠉⠡⠊⠗⠺⠀⠟⠓` + - first differing cell (zero-based): 72 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_02.json` #537: 11일 Macker와 ZAYDA, 12에는 Killa Ton과 Bagagee Viphex13, 13일에는 EDM씬의 트렌드를 주도하는 쥬디(JOODY)와 아쉬코(ASHIKO)가 출연했다. + - expected: `⠴⠠⠠⠵⠁⠽⠙⠁⠐⠀⠼⠁⠃⠝⠉⠵⠀⠴⠠⠅⠊⠇⠇⠁` + - actual: `⠴⠠⠠⠵⠁⠽⠙⠁⠂⠀⠼⠁⠃⠝⠉⠵⠀⠴⠠⠅⠊⠇⠇⠁` + - first differing cell (zero-based): 24 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_03.json` #2147: 용량은 512GB, 1테라바이트(TB), 두 가지 종류로 출시됐다. USB 연결 케이블 2종(C-to-C, C-to-A)과 함께 전용 범퍼케이스가 제공된다. + - expected: `⠑⠁⠃⠴⠠⠠⠛⠃⠐⠀⠼⠁⠀⠓⠝⠐⠣⠘⠣⠕⠓⠪⠦⠄` + - actual: `⠑⠁⠃⠴⠠⠠⠛⠃⠂⠀⠼⠁⠀⠓⠝⠐⠣⠘⠣⠕⠓⠪⠦⠄` + - first differing cell (zero-based): 16 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_04.json` #4209: 사티아 나델라 MS 최고경영자(CEO)는 이날 “나의 계정에서 코파일럿과 채팅하게 돼 기쁘다”며 “AI 비서와 일하는 것은 1980년대의 PC, 1990년대의 인터넷, 21세기 모바일의 부상만큼이나 주목할 만하다”라고 자평했다. + - expected: `⠗⠺⠀⠴⠠⠠⠏⠉⠐⠀⠼⠁⠊⠊⠚⠀⠉⠡⠊⠗⠺⠀⠟⠓` + - actual: `⠗⠺⠀⠴⠠⠠⠏⠉⠂⠀⠼⠁⠊⠊⠚⠀⠉⠡⠊⠗⠺⠀⠟⠓` + - first differing cell (zero-based): 133 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` + +Representative `mismatch` samples: + +- `sentence_01.json` #322: 삼성전자는 5나노미터(nm, 1nm는 10억 분의 1m) 기반 신규 컨트롤러를 탑재한 PC용 고성능 비휘발성 메모리 익스프레스(NVMe) SSD ‘PM9C1a’를 양산한다고 12일 밝혔다. + - expected: `⠄⠴⠝⠍⠂⠀⠼⠁⠝⠍⠲⠉⠵⠀⠼⠁⠚⠹⠀⠘⠛⠺⠀⠼` + - actual: `⠄⠴⠝⠍⠂⠀⠼⠁⠴⠝⠍⠲⠉⠵⠀⠼⠁⠚⠹⠀⠘⠛⠺⠀` + - first differing cell (zero-based): 29 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_02.json` #239: 세계 금연의 날(World No Tabacco Day, 2023년 5월 31일)은 세계보건기구(WHO)가 담배가 전 세계적으로 심각한 문제임을 인식시키고 ‘담배 연기 없는 사회’를 만들기 위하여 1987년 제정한 기념일이다. + - expected: `⠃⠁⠒⠕⠀⠠⠐⠙⠐⠀⠼⠃⠚⠃⠉⠀⠉⠡⠀⠼⠑⠏⠂⠀` + - actual: `⠃⠁⠒⠕⠀⠠⠐⠙⠂⠀⠼⠃⠚⠃⠉⠀⠉⠡⠀⠼⠑⠏⠂⠀` + - first differing cell (zero-based): 35 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_03.json` #2147: 용량은 512GB, 1테라바이트(TB), 두 가지 종류로 출시됐다. USB 연결 케이블 2종(C-to-C, C-to-A)과 함께 전용 범퍼케이스가 제공된다. + - expected: `⠑⠁⠃⠴⠠⠠⠛⠃⠐⠀⠼⠁⠀⠓⠝⠐⠣⠘⠣⠕⠓⠪⠦⠄` + - actual: `⠑⠁⠃⠴⠠⠠⠛⠃⠂⠀⠼⠁⠀⠓⠝⠐⠣⠘⠣⠕⠓⠪⠦⠄` + - first differing cell (zero-based): 16 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_04.json` #4209: 사티아 나델라 MS 최고경영자(CEO)는 이날 “나의 계정에서 코파일럿과 채팅하게 돼 기쁘다”며 “AI 비서와 일하는 것은 1980년대의 PC, 1990년대의 인터넷, 21세기 모바일의 부상만큼이나 주목할 만하다”라고 자평했다. + - expected: `⠗⠺⠀⠴⠠⠠⠏⠉⠐⠀⠼⠁⠊⠊⠚⠀⠉⠡⠊⠗⠺⠀⠟⠓` + - actual: `⠗⠺⠀⠴⠠⠠⠏⠉⠂⠀⠼⠁⠊⠊⠚⠀⠉⠡⠊⠗⠺⠀⠟⠓` + - first differing cell (zero-based): 133 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` + ### `attached_ascii_roman_segments_joined_by_ampersand` Of the 802 candidates, 491 are the actual `pending_rule_review` subcluster. The other 311 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. @@ -3124,7 +3186,13 @@ Of 18 current evaluable mismatches, 4 are localized to the occurrence-specific e This output-localized cohort requires a comma immediately after an ASCII digit, one or more following whitespace characters, and another ASCII digit. Korean rule 41 (2024 Korean-rules PDF p.33, printed p.27) assigns `⠂` only when the comma is *attached* between digits, as in the exact control `9,375명`; rule 49 (PDF pp.37-38, printed pp.31-32) assigns the ordinary Korean comma `⠐`, illustrated by `근면, 검소, 협동은 ...`. Rule 33 (PDF p.28, printed p.22) independently keeps Korean punctuation at Roman-to-Korean boundaries. By contrast, official UEB 7 (2024 UEB PDF p.103, printed p.75) defines its prose comma as `⠂`, and UEB 6.2.1 (PDF p.94, printed p.66) retains `⠂` inside attached numeric forms such as `3,500`. Those two surfaces are negative controls and are excluded by this gate. -Before any engine change, the cohort has 217 candidates / 7 exact / 210 mismatch. Existing mismatch primaries remain 209 `pending_rule_review`, 0 `corpus_suspect`, 0 `comparison_method`, and 1 `unsupported_character_review`. The occurrence-specific locator encodes the real prefix immediately before each comma and claims only its next emitted cell: 177/210 evaluable mismatches localize there, including 177 `U+2810 ⠐ -> U+2802 ⠂` and 0 `U+2802 ⠂ -> U+2810 ⠐`. Across all pending cases, the raw-to-residual counts after adding this cohort are 212 -> 35 for the target and 12 -> 10 for the reverse. No primary class is changed and no implementation result is inferred from the reference outputs at this diagnostic checkpoint. +Before any engine change, the cohort has 217 candidates / 7 exact / 210 mismatch. Existing mismatch primaries remain 209 `pending_rule_review`, 0 `corpus_suspect`, 0 `comparison_method`, and 1 `unsupported_character_review`. The occurrence-specific locator encodes the real prefix immediately before each comma and claims only its next emitted cell: 177/210 evaluable mismatches localize there, including 177 `U+2810 ⠐ -> U+2802 ⠂` and 0 `U+2802 ⠂ -> U+2810 ⠐`. Across all pending cases, the raw-to-residual counts after adding this cohort are 212 -> 12 for the target and 12 -> 10 for the reverse. No primary class is changed and no implementation result is inferred from the reference outputs at this diagnostic checkpoint. + +### ASCII/Roman-tail comma before a digit-led Korean token + +This companion cohort is disjoint from the preceding digit-comma gate: the comma is immediately preceded by an ASCII letter, followed after whitespace by a token that starts with a digit and contains Korean script. Korean rule 33 (2024 Korean-rules PDF p.28, printed p.22) says that punctuation with different UEB and Korean cells, including comma, is written as Korean punctuation at a Roman-to-Korean boundary and suppresses the Roman terminator. Rule 49 supplies `⠐`; UEB 7 supplies `⠂` only while the comma remains inside English text. Requiring Korean script in the right token is therefore the negative control against reclassifying an English date or number sequence from surface punctuation alone. + +Before the shared rule-41 boundary correction, this cohort has 58 candidates / 0 exact / 58 mismatch. Existing mismatch primaries remain 56 `pending_rule_review`, 2 `corpus_suspect`, 0 `comparison_method`, and 0 `unsupported_character_review`. Of 58 evaluable mismatches, 23 have the comma-cell transition inside the independently encoded complete boundary signature: 23 `U+2810 ⠐ -> U+2802 ⠂` and 0 `U+2802 ⠂ -> U+2810 ⠐`. The detector and localizer do not read expected output to choose a route, and membership does not change a primary class. Current uppercase-Roman hyphen-digits measurement: 571 candidates, 191 exact controls, 380 mismatches, 365 members in the actual `pending_rule_review` subcluster, and 261/380 evaluable mismatches whose first difference is inside the target run plus its entry boundary. It remains distinct from parenthesized digits and headword expansions; no engine change is inferred. diff --git a/libs/braillify/examples/nikl_corpus_analyze.rs b/libs/braillify/examples/nikl_corpus_analyze.rs index 9aeedddc..108d72e3 100644 --- a/libs/braillify/examples/nikl_corpus_analyze.rs +++ b/libs/braillify/examples/nikl_corpus_analyze.rs @@ -596,6 +596,8 @@ const ATTACHED_ASCII_ROMAN_SEGMENTS_JOINED_BY_AMPERSAND: &str = const AMPERSAND_BEFORE_ATTACHED_ASCII_ROMAN_SEGMENT: &str = "ampersand_before_attached_ascii_roman_segment"; const SPACED_COMMA_BETWEEN_ASCII_DIGIT_RUNS: &str = "spaced_comma_between_ascii_digit_runs"; +const ASCII_ROMAN_COMMA_BEFORE_DIGIT_KOREAN_TOKEN: &str = + "ascii_roman_tail_comma_before_digit_korean_token"; const SINGLE_CAPITAL_PARENTHESIZED_DIGITS: &str = "single_capital_followed_by_parenthesized_digits"; const MIXED_ROMAN_KOREAN_BEFORE_HEADWORD_EXPANSION: &str = "mixed_roman_korean_word_before_uppercase_headword_expansion"; @@ -1185,6 +1187,52 @@ fn spaced_comma_between_ascii_digit_run_spans(input: &str) -> Vec { .collect() } +/// Finds a whitespace-delimited ASCII/Roman tail ending in a comma, followed +/// by a token that begins with a digit and contains Korean script. This is the +/// rule-33 boundary represented by corpus surfaces such as an English title or +/// an ASCII unit before a year/count carrying a Korean suffix. Pure English +/// prose and a following digit-only token are excluded. +fn ascii_roman_comma_before_digit_korean_token_spans(input: &str) -> Vec { + input + .match_indices(',') + .filter_map(|(comma_byte, comma)| { + if !input[..comma_byte] + .chars() + .next_back() + .is_some_and(|ch| ch.is_ascii_alphabetic()) + { + return None; + } + + let after_comma = comma_byte + comma.len(); + let first_nonspace = input[after_comma..] + .char_indices() + .find(|(_, ch)| !ch.is_whitespace())?; + if first_nonspace.0 == 0 || !first_nonspace.1.is_ascii_digit() { + return None; + } + let right_start = after_comma + first_nonspace.0; + let right_end = input[right_start..] + .char_indices() + .find_map(|(offset, ch)| ch.is_whitespace().then_some(right_start + offset)) + .unwrap_or(input.len()); + if !input[right_start..right_end].chars().any(is_korean_script) { + return None; + } + + let left_start = input[..comma_byte] + .char_indices() + .rev() + .find_map(|(byte, ch)| ch.is_whitespace().then_some(byte + ch.len_utf8())) + .unwrap_or(0); + Some(InputSpan { + start_byte: left_start, + end_byte: right_end, + }) + }) + .collect() +} + /// Finds maximal pure-uppercase ASCII letter runs whose beginning is itself a /// pure-letter UEB shortform abbreviation. UEB 5.7.2 and 10.9.7-10.9.8 require /// grade 1 both for a complete shortform-shaped letters-sequence (`WD`) and for @@ -2107,6 +2155,33 @@ fn first_difference_at_spaced_numeric_list_comma(item: &EncodedCase) -> bool { .any(|range| range.contains(&first_difference)) } +fn first_difference_at_ascii_roman_comma_before_digit_korean(item: &EncodedCase) -> bool { + let Ok(actual) = &item.actual else { + return false; + }; + if actual == &item.located.case.unicode { + return false; + } + let first_difference = first_difference_cell(&item.located.case.unicode, actual); + if !matches!( + ( + item.located.case.unicode.chars().nth(first_difference), + actual.chars().nth(first_difference) + ), + (Some('⠐'), Some('⠂')) | (Some('⠂'), Some('⠐')) + ) { + return false; + } + current_engine_signature_ranges( + &item.located.case.input, + actual, + &ascii_roman_comma_before_digit_korean_token_spans(&item.located.case.input), + 0, + ) + .into_iter() + .any(|range| range.contains(&first_difference)) +} + /// Finds a standalone single capital immediately followed by a non-empty, /// closed ASCII-digit parenthetical, such as `A(14)`. The span is deliberately /// semantic-neutral: prose labels and mathematical function notation can share @@ -2461,6 +2536,7 @@ fn first_difference_claimed_by_localized_cohort(item: &EncodedCase) -> bool { first_difference_claimed_before_consecutive_ascii_roman_boundary(item) || first_difference_at_consecutive_ascii_roman_word_boundary(item) || first_difference_at_spaced_numeric_list_comma(item) + || first_difference_at_ascii_roman_comma_before_digit_korean(item) } /// Input-only candidate gate for acronym expansions such as @@ -3340,6 +3416,10 @@ fn analyze( SPACED_COMMA_BETWEEN_ASCII_DIGIT_RUNS.to_string(), PendingRuleReviewClusterStats::default(), ), + ( + ASCII_ROMAN_COMMA_BEFORE_DIGIT_KOREAN_TOKEN.to_string(), + PendingRuleReviewClusterStats::default(), + ), ( ALLCAPS_ROMAN_MIDDLE_DOT_RUNS.to_string(), PendingRuleReviewClusterStats::default(), @@ -3587,6 +3667,15 @@ fn analyze( Some(first_difference_at_spaced_numeric_list_comma(item)), true, ), + ( + ASCII_ROMAN_COMMA_BEFORE_DIGIT_KOREAN_TOKEN, + !ascii_roman_comma_before_digit_korean_token_spans(&item.located.case.input) + .is_empty(), + Some(first_difference_at_ascii_roman_comma_before_digit_korean( + item, + )), + true, + ), ( ALLCAPS_ROMAN_MIDDLE_DOT_RUNS, has_allcaps_roman_middle_dot_runs(&item.located.case.input), @@ -5418,6 +5507,57 @@ fn markdown(report: &AnalysisReport) -> String { residual_count(ueb_to_korean), )); } + if let Some(stats) = report + .pending_rule_review_clusters + .get(ASCII_ROMAN_COMMA_BEFORE_DIGIT_KOREAN_TOKEN) + { + let primary_count = |name: &str| { + stats + .mismatch_primary_classes + .get(name) + .copied() + .unwrap_or(0) + }; + let localized_count = |name: &str| { + stats + .first_difference_in_output_signature_transitions + .get(name) + .copied() + .unwrap_or(0) + }; + let korean_to_ueb = "U+2810 ⠐ -> U+2802 ⠂"; + let ueb_to_korean = "U+2802 ⠂ -> U+2810 ⠐"; + text.push_str(&format!( + "\n### ASCII/Roman-tail comma before a digit-led Korean token\n\n\ + This companion cohort is disjoint from the preceding digit-comma gate: the comma \ + is immediately preceded by an ASCII letter, followed after whitespace by a token \ + that starts with a digit and contains Korean script. Korean rule 33 (2024 \ + Korean-rules PDF p.28, printed p.22) says that punctuation with different UEB and \ + Korean cells, including comma, is written as Korean punctuation at a \ + Roman-to-Korean boundary and suppresses the Roman terminator. Rule 49 supplies \ + `⠐`; UEB 7 supplies `⠂` only while the comma remains inside English text. Requiring \ + Korean script in the right token is therefore the negative control against \ + reclassifying an English date or number sequence from surface punctuation alone.\n\n\ + Before the shared rule-41 boundary correction, this cohort has {} candidates / {} \ + exact / {} mismatch. Existing mismatch primaries remain {} \ + `pending_rule_review`, {} `corpus_suspect`, {} `comparison_method`, and {} \ + `unsupported_character_review`. Of {} evaluable mismatches, {} have the comma-cell \ + transition inside the independently encoded complete boundary signature: {} \ + `{korean_to_ueb}` and {} `{ueb_to_korean}`. The detector and localizer do not read \ + expected output to choose a route, and membership does not change a primary class.\n", + stats.candidates, + stats.exact, + stats.mismatch, + primary_count("pending_rule_review"), + primary_count("corpus_suspect"), + primary_count("comparison_method"), + primary_count("unsupported_character_review"), + stats.output_signature_mismatches_evaluated, + stats.first_difference_in_output_signature, + localized_count(korean_to_ueb), + localized_count(ueb_to_korean), + )); + } if let Some(stats) = report .pending_rule_review_clusters .get(UPPERCASE_ROMAN_HYPHEN_DIGITS) @@ -6861,12 +7001,13 @@ mod tests { } #[test] - fn localizes_each_spaced_numeric_list_comma_in_current_output() { - let input = "순위는 17, 16, 15이다."; + fn localizes_pdf_spaced_numeric_list_comma_in_baseline_output() { + // 2024 Korean-rules PDF physical p.209. + let input = "제5열 버튼(3, 7 혹은 S)"; let actual = braillify::encode_to_unicode(input).expect("numeric-list probe must encode"); let ranges = spaced_numeric_list_comma_actual_ranges(input, &actual); - assert_eq!(ranges.len(), 2); + assert_eq!(ranges.len(), 1); assert!( ranges .iter() From ad01792c1e52d6fb2adacd2dd05a04e634a59b8b Mon Sep 17 00:00:00 2001 From: devfive Date: Tue, 1 Sep 2026 16:27:36 +0900 Subject: [PATCH 49/77] Diagnose percent-point list commas --- docs/corpus-analysis/NIKL_2025_V1.md | 44 ++++- .../braillify/examples/nikl_corpus_analyze.rs | 154 +++++++++++++++++- 2 files changed, 193 insertions(+), 5 deletions(-) diff --git a/docs/corpus-analysis/NIKL_2025_V1.md b/docs/corpus-analysis/NIKL_2025_V1.md index e7de7f89..5100d223 100644 --- a/docs/corpus-analysis/NIKL_2025_V1.md +++ b/docs/corpus-analysis/NIKL_2025_V1.md @@ -575,6 +575,7 @@ These are cross-cutting input-only structural cohorts, not new primary classes a | `korean_prefixed_roman_parenthetical_followed_by_allcaps_hyphen_suffix` | 11 | 0 | 11 | 0 | | `mixed_roman_korean_word_before_uppercase_headword_expansion` | 10 | 0 | 10 | 0 | | `multi_character_allcaps_roman_runs_joined_by_middle_dot` | 97 | 0 | 97 | 0 | +| `percent_point_unit_list_comma` | 7 | 0 | 7 | 0 | | `pure_allcaps_segment_before_hyphen_and_multi_allcaps_segment_after` | 448 | 131 | 317 | 0 | | `roman_hyphenated_word_after_whitespace_following_korean_word` | 361 | 0 | 361 | 0 | | `roman_parenthetical_headword_after_whitespace_following_korean_word` | 4695 | 3382 | 1313 | 0 | @@ -1951,6 +1952,41 @@ Representative `mismatch` samples: - first differing cell (zero-based): 130 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +### `percent_point_unit_list_comma` + +Of the 7 candidates, 7 are the actual `pending_rule_review` subcluster. The other 0 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. + +For this output-signature audit, 7 mismatches were evaluable and 1 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. + +Localized first-difference transitions: + +- `U+2810 ⠐ -> U+2802 ⠂`: 1 + +Mismatch primary-class distribution: + +- `pending_rule_review`: 7 + +Representative `localized_mismatch` samples: + +- `sentence_01.json` #1767: 부산은행도 서민금융 상품 ‘새희망홀씨’ 대출 금리를 1%p 내렸다. 주담대와 전세대출, 신용대출도 최대 0.8%포인트(p), 0.85%p, 0.6%p씩 금리를 내리기로 했다. + - expected: `⠼⠚⠲⠓⠑⠴⠏⠏⠐⠀⠼⠚⠲⠋⠴⠏⠏⠀⠠⠠⠕⠁⠀⠈` + - actual: `⠼⠚⠲⠓⠑⠴⠏⠏⠂⠀⠼⠚⠲⠋⠴⠏⠏⠀⠠⠠⠕⠁⠀⠈` + - first differing cell (zero-based): 133 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` + +Representative `mismatch` samples: + +- `sentence_01.json` #1767: 부산은행도 서민금융 상품 ‘새희망홀씨’ 대출 금리를 1%p 내렸다. 주담대와 전세대출, 신용대출도 최대 0.8%포인트(p), 0.85%p, 0.6%p씩 금리를 내리기로 했다. + - expected: `⠼⠚⠲⠓⠑⠴⠏⠏⠐⠀⠼⠚⠲⠋⠴⠏⠏⠀⠠⠠⠕⠁⠀⠈` + - actual: `⠼⠚⠲⠓⠑⠴⠏⠏⠂⠀⠼⠚⠲⠋⠴⠏⠏⠀⠠⠠⠕⠁⠀⠈` + - first differing cell (zero-based): 133 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_02.json` #12305: 1일 금융감독원에 따르면 3월말 국내은행의 BIS기준 보통주자본비율, 기본자본비율, 총자본비율은 각각 12.88%, 14.24%, 15.58%로 나타났다. 전 분기 대비 각각 0.28%포인트(p), 0.33%p, 0.29%p 올랐다. + - expected: `⠼⠚⠲⠉⠉⠴⠏⠏⠐⠀⠼⠚⠲⠃⠊⠴⠏⠏⠀⠥⠂⠐⠣⠌` + - actual: `⠼⠚⠲⠉⠉⠴⠏⠏⠂⠀⠼⠚⠲⠃⠊⠴⠏⠏⠀⠥⠂⠐⠣⠌` + - first differing cell (zero-based): 185 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` + ### `pure_allcaps_segment_before_hyphen_and_multi_allcaps_segment_after` Of the 448 candidates, 307 are the actual `pending_rule_review` subcluster. The other 141 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. @@ -3115,7 +3151,7 @@ The generalized fix makes an explicit `EnterEnglish` event idempotent when final Korean rule 34 (2024 Korean-rules PDF p.29) prints the opening enclosure before Roman entry in `링컨(Lincoln)`: the opening sequence is `⠦⠄⠴`. This output-position cohort finds a closed, non-nested parenthetical whose body begins with an ASCII letter and whose opening does not immediately follow another ASCII letter, then locates its complete current-engine signature without consulting the reference. Its localized boundary includes the two current output cells immediately before the opening plus the first three entry cells, so rule-11 math spacing can be separated from a difference later inside the parenthetical. Direct function-call shapes such as `f(x)` are excluded. The PDF's math rule 6 (p.59), rule 12 (pp.63-64), and rule 45 (p.75) nevertheless leave standalone `(x)` and other Roman-letter parenthetical mathematics as counterexamples, so the surface gate is not an engine-routing predicate. -The cross-cutting input cohort contains 63959 candidates: 52252 exact controls and 11707 mismatches. Mismatch primary classes remain unchanged: 10220 `pending_rule_review`, 1107 `corpus_suspect`, 225 `comparison_method`, and 155 `unsupported_character_review`. Of 11707 evaluable mismatches, 1245 have the first difference at the detected leading-spacing/entry boundary; these include 436 `U+2826 ⠦ -> U+2834 ⠴`, 27 `U+2834 ⠴ -> U+2826 ⠦`, and 325 `U+2826 ⠦ -> U+2800 ⠀` transitions; the exact localized reverse `U+2800 ⠀ -> U+2826 ⠦` occurs 0 times. After all earlier localized cohorts and this cohort are excluded, the raw-to-residual target count is 469 -> 26, the Roman-indicator reverse count is 32 -> 2, and the spacing target/reverse counts are 440 -> 79 and 1 -> 1. The short full-encoder form `웹3(Web3)` emits the PDF opening order as a routing control, while whitespace and quote boundaries reproduce the current Roman-first opening. Representative localized samples, with shard and index, are retained in the generated cluster sample table. Because exact controls are abundant and the PDF does not make this input shape semantically sufficient to exclude mathematics, no engine change or primary reclassification is inferred. The one raw/residual spacing reverse is a Korean-body `△(교육)` layout boundary, not a Roman-parenthetical member, and remains a separate rule-72/layout review. +The cross-cutting input cohort contains 63959 candidates: 52252 exact controls and 11707 mismatches. Mismatch primary classes remain unchanged: 10220 `pending_rule_review`, 1107 `corpus_suspect`, 225 `comparison_method`, and 155 `unsupported_character_review`. Of 1245 evaluable mismatches, 11707 have the first difference at the detected leading-spacing/entry boundary; these include 436 `U+2826 ⠦ -> U+2834 ⠴`, 27 `U+2834 ⠴ -> U+2826 ⠦`, and 325 `U+2826 ⠦ -> U+2800 ⠀` transitions; the exact localized reverse `U+2800 ⠀ -> U+2826 ⠦` occurs 0 times. After all earlier localized cohorts and this cohort are excluded, the raw-to-residual target count is 469 -> 26, the Roman-indicator reverse count is 32 -> 2, and the spacing target/reverse counts are 440 -> 79 and 1 -> 1. The short full-encoder form `웹3(Web3)` emits the PDF opening order as a routing control, while whitespace and quote boundaries reproduce the current Roman-first opening. Representative localized samples, with shard and index, are retained in the generated cluster sample table. Because exact controls are abundant and the PDF does not make this input shape semantically sufficient to exclude mathematics, no engine change or primary reclassification is inferred. The one raw/residual spacing reverse is a Korean-body `△(교육)` layout boundary, not a Roman-parenthetical member, and remains a separate rule-72/layout review. The HCA-style headword-expansion shape described above is not an engine implementation premise. The 2024 PDF's math rule 6 defines parentheses and grouping parentheses, rule 11 defines mathematical-expression spacing, rule 12 covers Roman letters in formulas as well as Korean sentences, and rule 45 shows Roman-letter function notation followed by parentheses. Excluding visible operators, scripts, and nesting narrows this corpus cohort, but the PDF does not make the remaining surface shape sufficient to rule out every mathematical counterexample. The cluster therefore remains conservative pending-review evidence only. @@ -3186,7 +3222,7 @@ Of 18 current evaluable mismatches, 4 are localized to the occurrence-specific e This output-localized cohort requires a comma immediately after an ASCII digit, one or more following whitespace characters, and another ASCII digit. Korean rule 41 (2024 Korean-rules PDF p.33, printed p.27) assigns `⠂` only when the comma is *attached* between digits, as in the exact control `9,375명`; rule 49 (PDF pp.37-38, printed pp.31-32) assigns the ordinary Korean comma `⠐`, illustrated by `근면, 검소, 협동은 ...`. Rule 33 (PDF p.28, printed p.22) independently keeps Korean punctuation at Roman-to-Korean boundaries. By contrast, official UEB 7 (2024 UEB PDF p.103, printed p.75) defines its prose comma as `⠂`, and UEB 6.2.1 (PDF p.94, printed p.66) retains `⠂` inside attached numeric forms such as `3,500`. Those two surfaces are negative controls and are excluded by this gate. -Before any engine change, the cohort has 217 candidates / 7 exact / 210 mismatch. Existing mismatch primaries remain 209 `pending_rule_review`, 0 `corpus_suspect`, 0 `comparison_method`, and 1 `unsupported_character_review`. The occurrence-specific locator encodes the real prefix immediately before each comma and claims only its next emitted cell: 177/210 evaluable mismatches localize there, including 177 `U+2810 ⠐ -> U+2802 ⠂` and 0 `U+2802 ⠂ -> U+2810 ⠐`. Across all pending cases, the raw-to-residual counts after adding this cohort are 212 -> 12 for the target and 12 -> 10 for the reverse. No primary class is changed and no implementation result is inferred from the reference outputs at this diagnostic checkpoint. +Before any engine change, the cohort has 217 candidates / 7 exact / 210 mismatch. Existing mismatch primaries remain 209 `pending_rule_review`, 0 `corpus_suspect`, 0 `comparison_method`, and 1 `unsupported_character_review`. The occurrence-specific locator encodes the real prefix immediately before each comma and claims only its next emitted cell. Of 210 evaluable mismatches, 177 localize there, including 177 `U+2810 ⠐ -> U+2802 ⠂` and 0 `U+2802 ⠂ -> U+2810 ⠐`. Across all pending cases, the raw-to-residual counts after adding this cohort are 212 -> 12 for the target and 12 -> 10 for the reverse. No primary class is changed and no implementation result is inferred from the reference outputs at this diagnostic checkpoint. ### ASCII/Roman-tail comma before a digit-led Korean token @@ -3194,6 +3230,10 @@ This companion cohort is disjoint from the preceding digit-comma gate: the comma Before the shared rule-41 boundary correction, this cohort has 58 candidates / 0 exact / 58 mismatch. Existing mismatch primaries remain 56 `pending_rule_review`, 2 `corpus_suspect`, 0 `comparison_method`, and 0 `unsupported_character_review`. Of 58 evaluable mismatches, 23 have the comma-cell transition inside the independently encoded complete boundary signature: 23 `U+2810 ⠐ -> U+2802 ⠂` and 0 `U+2802 ⠂ -> U+2810 ⠐`. The detector and localizer do not read expected output to choose a route, and membership does not change a primary class. +### Percent-point unit list comma + +Korean rule 69 attachment 2 (2024 Korean-rules PDF p.50, printed p.44) explicitly defines `%p` as the percent-point unit. This cohort requires two complete numeric `%p` tokens separated by comma plus whitespace, so rule 49's ordinary Korean comma is the punctuation boundary; it does not infer arbitrary ASCII suffixes as units. At the diagnostic baseline it has 7 candidates / 0 exact / 7 mismatch, preserving 7 `pending_rule_review`, 0 `corpus_suspect`, 0 `comparison_method`, and 0 `unsupported_character_review` mismatch primaries. Of 7 evaluable mismatches, 1 localize to the independently encoded complete unit pair: 1 `U+2810 ⠐ -> U+2802 ⠂` and 0 `U+2802 ⠂ -> U+2810 ⠐`. This scope audit was added after the exact-set comparison identified the common `%p, ... %p` structure; the engine still contains only the general rule-41 same-token boundary, not a `%p` special case. + Current uppercase-Roman hyphen-digits measurement: 571 candidates, 191 exact controls, 380 mismatches, 365 members in the actual `pending_rule_review` subcluster, and 261/380 evaluable mismatches whose first difference is inside the target run plus its entry boundary. It remains distinct from parenthesized digits and headword expansions; no engine change is inferred. Current uppercase alphanumeric Roman-digit sequence measurement: 3429 candidates, 1911 exact controls, 1518 mismatches, 1409 members in the actual `pending_rule_review` subcluster, and 151/1518 evaluable mismatches whose first difference is localized to the current entry cells. The target `⠴ -> blank` occurs 93 times and the reverse occurs 0 times. Korean rule 35 (2024 Korean-rules PDF pp.29-30, printed pp.23-24) explicitly treats `MP3`, `A4`, `KF94`, and `D-100` as Roman-number sequences. Math rules 11/12 (PDF pp.63-65, printed pp.57-59) separately route mathematical Roman notation without the prose Roman indicator and with two-cell spacing. Official UEB 6.5.1-6.5.2 (2024 UEB PDF p.96, printed p.68) determines numeric/grade-1 continuation only after the surrounding mode has been chosen. Thus surfaces such as `A-3` cannot be assigned identifier or variable semantics from this structure alone. Exact controls and both directions are retained; no engine change is inferred. diff --git a/libs/braillify/examples/nikl_corpus_analyze.rs b/libs/braillify/examples/nikl_corpus_analyze.rs index 108d72e3..fb85998c 100644 --- a/libs/braillify/examples/nikl_corpus_analyze.rs +++ b/libs/braillify/examples/nikl_corpus_analyze.rs @@ -598,6 +598,7 @@ const AMPERSAND_BEFORE_ATTACHED_ASCII_ROMAN_SEGMENT: &str = const SPACED_COMMA_BETWEEN_ASCII_DIGIT_RUNS: &str = "spaced_comma_between_ascii_digit_runs"; const ASCII_ROMAN_COMMA_BEFORE_DIGIT_KOREAN_TOKEN: &str = "ascii_roman_tail_comma_before_digit_korean_token"; +const PERCENT_POINT_UNIT_LIST_COMMA: &str = "percent_point_unit_list_comma"; const SINGLE_CAPITAL_PARENTHESIZED_DIGITS: &str = "single_capital_followed_by_parenthesized_digits"; const MIXED_ROMAN_KOREAN_BEFORE_HEADWORD_EXPANSION: &str = "mixed_roman_korean_word_before_uppercase_headword_expansion"; @@ -1233,6 +1234,52 @@ fn ascii_roman_comma_before_digit_korean_token_spans(input: &str) -> Vec Vec { + input + .match_indices("%p,") + .filter_map(|(unit_byte, matched)| { + if !input[..unit_byte] + .chars() + .next_back() + .is_some_and(|ch| ch.is_ascii_digit()) + { + return None; + } + let comma_byte = unit_byte + matched.len() - 1; + let after_comma = comma_byte + 1; + let (right_offset, right_first) = input[after_comma..] + .char_indices() + .find(|(_, ch)| !ch.is_whitespace())?; + if right_offset == 0 || !right_first.is_ascii_digit() { + return None; + } + let right_start = after_comma + right_offset; + let right_end = input[right_start..] + .char_indices() + .find_map(|(offset, ch)| ch.is_whitespace().then_some(right_start + offset)) + .unwrap_or(input.len()); + let right_token = input[right_start..right_end] + .trim_end_matches(|ch: char| !ch.is_ascii_alphanumeric()); + if !right_token.ends_with("%p") { + return None; + } + let left_start = input[..unit_byte] + .char_indices() + .rev() + .find_map(|(byte, ch)| ch.is_whitespace().then_some(byte + ch.len_utf8())) + .unwrap_or(0); + Some(InputSpan { + start_byte: left_start, + end_byte: right_end, + }) + }) + .collect() +} + /// Finds maximal pure-uppercase ASCII letter runs whose beginning is itself a /// pure-letter UEB shortform abbreviation. UEB 5.7.2 and 10.9.7-10.9.8 require /// grade 1 both for a complete shortform-shaped letters-sequence (`WD`) and for @@ -2182,6 +2229,33 @@ fn first_difference_at_ascii_roman_comma_before_digit_korean(item: &EncodedCase) .any(|range| range.contains(&first_difference)) } +fn first_difference_at_percent_point_unit_list_comma(item: &EncodedCase) -> bool { + let Ok(actual) = &item.actual else { + return false; + }; + if actual == &item.located.case.unicode { + return false; + } + let first_difference = first_difference_cell(&item.located.case.unicode, actual); + if !matches!( + ( + item.located.case.unicode.chars().nth(first_difference), + actual.chars().nth(first_difference) + ), + (Some('⠐'), Some('⠂')) | (Some('⠂'), Some('⠐')) + ) { + return false; + } + current_engine_signature_ranges( + &item.located.case.input, + actual, + &percent_point_unit_list_comma_spans(&item.located.case.input), + 0, + ) + .into_iter() + .any(|range| range.contains(&first_difference)) +} + /// Finds a standalone single capital immediately followed by a non-empty, /// closed ASCII-digit parenthetical, such as `A(14)`. The span is deliberately /// semantic-neutral: prose labels and mathematical function notation can share @@ -2537,6 +2611,7 @@ fn first_difference_claimed_by_localized_cohort(item: &EncodedCase) -> bool { || first_difference_at_consecutive_ascii_roman_word_boundary(item) || first_difference_at_spaced_numeric_list_comma(item) || first_difference_at_ascii_roman_comma_before_digit_korean(item) + || first_difference_at_percent_point_unit_list_comma(item) } /// Input-only candidate gate for acronym expansions such as @@ -3420,6 +3495,10 @@ fn analyze( ASCII_ROMAN_COMMA_BEFORE_DIGIT_KOREAN_TOKEN.to_string(), PendingRuleReviewClusterStats::default(), ), + ( + PERCENT_POINT_UNIT_LIST_COMMA.to_string(), + PendingRuleReviewClusterStats::default(), + ), ( ALLCAPS_ROMAN_MIDDLE_DOT_RUNS.to_string(), PendingRuleReviewClusterStats::default(), @@ -3676,6 +3755,12 @@ fn analyze( )), true, ), + ( + PERCENT_POINT_UNIT_LIST_COMMA, + !percent_point_unit_list_comma_spans(&item.located.case.input).is_empty(), + Some(first_difference_at_percent_point_unit_list_comma(item)), + true, + ), ( ALLCAPS_ROMAN_MIDDLE_DOT_RUNS, has_allcaps_roman_middle_dot_runs(&item.located.case.input), @@ -4859,8 +4944,8 @@ fn markdown(report: &AnalysisReport) -> String { primary_count("corpus_suspect"), primary_count("comparison_method"), primary_count("unsupported_character_review"), - stats.output_signature_mismatches_evaluated, stats.first_difference_in_output_signature, + stats.output_signature_mismatches_evaluated, localized_transition(target), localized_transition(reverse), localized_transition(open_to_space), @@ -5485,7 +5570,7 @@ fn markdown(report: &AnalysisReport) -> String { Existing mismatch primaries remain {} `pending_rule_review`, {} `corpus_suspect`, \ {} `comparison_method`, and {} `unsupported_character_review`. The \ occurrence-specific locator encodes the real prefix immediately before each comma \ - and claims only its next emitted cell: {}/{} evaluable mismatches localize there, \ + and claims only its next emitted cell. Of {} evaluable mismatches, {} localize there, \ including {} `{korean_to_ueb}` and {} `{ueb_to_korean}`. Across all pending cases, \ the raw-to-residual counts after adding this cohort are {} -> {} for the target and \ {} -> {} for the reverse. No primary class is changed and no implementation result \ @@ -5497,8 +5582,8 @@ fn markdown(report: &AnalysisReport) -> String { primary_count("corpus_suspect"), primary_count("comparison_method"), primary_count("unsupported_character_review"), - stats.first_difference_in_output_signature, stats.output_signature_mismatches_evaluated, + stats.first_difference_in_output_signature, localized_count(korean_to_ueb), localized_count(ueb_to_korean), raw_count(korean_to_ueb), @@ -5558,6 +5643,53 @@ fn markdown(report: &AnalysisReport) -> String { localized_count(ueb_to_korean), )); } + if let Some(stats) = report + .pending_rule_review_clusters + .get(PERCENT_POINT_UNIT_LIST_COMMA) + { + let primary_count = |name: &str| { + stats + .mismatch_primary_classes + .get(name) + .copied() + .unwrap_or(0) + }; + let localized_count = |name: &str| { + stats + .first_difference_in_output_signature_transitions + .get(name) + .copied() + .unwrap_or(0) + }; + let korean_to_ueb = "U+2810 ⠐ -> U+2802 ⠂"; + let ueb_to_korean = "U+2802 ⠂ -> U+2810 ⠐"; + text.push_str(&format!( + "\n### Percent-point unit list comma\n\n\ + Korean rule 69 attachment 2 (2024 Korean-rules PDF p.50, printed p.44) \ + explicitly defines `%p` as the percent-point unit. This cohort requires two \ + complete numeric `%p` tokens separated by comma plus whitespace, so rule 49's \ + ordinary Korean comma is the punctuation boundary; it does not infer arbitrary \ + ASCII suffixes as units. At the diagnostic baseline it has {} candidates / {} \ + exact / {} mismatch, preserving {} `pending_rule_review`, {} `corpus_suspect`, {} \ + `comparison_method`, and {} `unsupported_character_review` mismatch primaries. Of \ + {} evaluable mismatches, {} localize to the independently encoded complete unit \ + pair: {} `{korean_to_ueb}` and {} `{ueb_to_korean}`. This scope audit was added \ + after the exact-set comparison identified the common `%p, ... %p` structure; the \ + engine still contains only the general rule-41 same-token boundary, not a `%p` \ + special case.\n", + stats.candidates, + stats.exact, + stats.mismatch, + primary_count("pending_rule_review"), + primary_count("corpus_suspect"), + primary_count("comparison_method"), + primary_count("unsupported_character_review"), + stats.output_signature_mismatches_evaluated, + stats.first_difference_in_output_signature, + localized_count(korean_to_ueb), + localized_count(ueb_to_korean), + )); + } if let Some(stats) = report .pending_rule_review_clusters .get(UPPERCASE_ROMAN_HYPHEN_DIGITS) @@ -7000,6 +7132,22 @@ mod tests { assert_eq!(actual, expected); } + #[rstest::rstest] + // 제69항 붙임 2의 `%p` 단위에만 한정한 입력 구조 진단이다. + #[case::complete_percent_point_units("0.7%p, 0.5%p", vec!["0.7%p, 0.5%p"])] + #[case::right_unit_missing("0.7%p, 0.5%", vec![])] + #[case::comma_not_followed_by_space("0.7%p,0.5%p", vec![])] + fn detects_only_complete_percent_point_unit_lists( + #[case] input: &str, + #[case] expected: Vec<&str>, + ) { + let actual = percent_point_unit_list_comma_spans(input) + .into_iter() + .map(|span| &input[span.start_byte..span.end_byte]) + .collect::>(); + assert_eq!(actual, expected); + } + #[test] fn localizes_pdf_spaced_numeric_list_comma_in_baseline_output() { // 2024 Korean-rules PDF physical p.209. From e89d9658082bd6e0fec44e0aed343c8b2fd5c2ab Mon Sep 17 00:00:00 2001 From: devfive Date: Tue, 1 Sep 2026 16:36:14 +0900 Subject: [PATCH 50/77] Keep numeric commas within attached tokens --- docs/corpus-analysis/NIKL_2025_V1.md | 499 +++++++++--------- .../braillify/examples/nikl_corpus_analyze.rs | 65 ++- libs/braillify/src/rules/korean/rule_41.rs | 67 ++- 3 files changed, 334 insertions(+), 297 deletions(-) diff --git a/docs/corpus-analysis/NIKL_2025_V1.md b/docs/corpus-analysis/NIKL_2025_V1.md index 5100d223..9141ff46 100644 --- a/docs/corpus-analysis/NIKL_2025_V1.md +++ b/docs/corpus-analysis/NIKL_2025_V1.md @@ -7,9 +7,9 @@ | Metric | Count | |---|---:| | Total | 83528 | -| Exact | 68187 | -| Mismatch | 15341 | -| Exact accuracy | 81.63% | +| Exact | 68368 | +| Mismatch | 15160 | +| Exact accuracy | 81.85% | | Duplicate records | 0 | | Inputs with conflicting references | 0 | @@ -19,21 +19,21 @@ Primary classes are evidence gates, not permissions to change the engine. `imple | Primary class | Count | |---|---:| -| `comparison_method` | 295 | +| `comparison_method` | 296 | | `corpus_suspect` | 1107 | -| `exact` | 68187 | -| `pending_rule_review` | 13763 | -| `unsupported_character_review` | 176 | +| `exact` | 68368 | +| `pending_rule_review` | 13582 | +| `unsupported_character_review` | 175 | | Reproducible reason | Count | |---|---:| -| `exact` | 68187 | -| `foreign_text_rule_review` | 13588 | -| `nfkc_input_equivalent` | 295 | -| `number_rule_review` | 167 | +| `exact` | 68368 | +| `foreign_text_rule_review` | 13418 | +| `nfkc_input_equivalent` | 296 | +| `number_rule_review` | 156 | | `punctuation_rule_review` | 8 | | `rule34_roman_indicator_before_opening_parenthesis` | 1107 | -| `unsupported_character_review` | 176 | +| `unsupported_character_review` | 175 | ## Pending first-difference cell transitions @@ -41,7 +41,7 @@ This ranking is a diagnostic selector, not an implementation rule. It counts onl | Rank | Expected → actual first cell | Cases | |---:|---|---:| -| 1 | `U+2834 ⠴ -> U+2800 ⠀` | 2162 | +| 1 | `U+2834 ⠴ -> U+2800 ⠀` | 2163 | | 2 | `U+2815 ⠕ -> U+2833 ⠳` | 1336 | | 3 | `U+2830 ⠰ -> U+2820 ⠠` | 862 | | 4 | `U+2810 ⠐ -> U+2832 ⠲` | 669 | @@ -53,14 +53,14 @@ This ranking is a diagnostic selector, not an implementation rule. It counts onl | 10 | `U+283C ⠼ -> U+2800 ⠀` | 358 | | 11 | `U+2834 ⠴ -> U+2830 ⠰` | 347 | | 12 | `U+2811 ⠑ -> U+282B ⠫` | 329 | -| 13 | `U+2820 ⠠ -> U+2830 ⠰` | 237 | -| 14 | `U+2810 ⠐ -> U+2802 ⠂` | 212 | -| 15 | `U+2820 ⠠ -> U+2809 ⠉` | 169 | -| 16 | `U+2817 ⠗ -> U+2820 ⠠` | 165 | -| 17 | `U+2826 ⠦ -> U+2810 ⠐` | 164 | -| 18 | `U+2800 ⠀ -> U+2832 ⠲` | 154 | -| 19 | `U+281B ⠛ -> U+2823 ⠣` | 144 | -| 20 | `U+2810 ⠐ -> U+2800 ⠀` | 130 | +| 13 | `U+2820 ⠠ -> U+2830 ⠰` | 238 | +| 14 | `U+2820 ⠠ -> U+2809 ⠉` | 169 | +| 15 | `U+2817 ⠗ -> U+2820 ⠠` | 165 | +| 16 | `U+2826 ⠦ -> U+2810 ⠐` | 164 | +| 17 | `U+2800 ⠀ -> U+2832 ⠲` | 154 | +| 18 | `U+281B ⠛ -> U+2823 ⠣` | 144 | +| 19 | `U+2810 ⠐ -> U+2800 ⠀` | 130 | +| 20 | `U+2820 ⠠ -> U+281E ⠞` | 109 | ### `U+2834 ⠴ -> U+2800 ⠀` @@ -309,7 +309,7 @@ This ranking removes only cases whose first difference is inside an existing out | 9 | `U+2811 ⠑ -> U+283B ⠻` | 101 | | 10 | `U+2800 ⠀ -> U+2832 ⠲` | 100 | | 11 | `U+2810 ⠐ -> U+2814 ⠔` | 98 | -| 12 | `U+280E ⠎ -> U+2820 ⠠` | 93 | +| 12 | `U+280E ⠎ -> U+2820 ⠠` | 94 | | 13 | `U+2803 ⠃ -> U+2820 ⠠` | 87 | | 14 | `U+2804 ⠄ -> U+2834 ⠴` | 87 | | 15 | `U+2826 ⠦ -> U+2800 ⠀` | 79 | @@ -555,49 +555,49 @@ These are cross-cutting input-only structural cohorts, not new primary classes a | Cluster | Candidates | Exact | Mismatch | Conflicting-reference cases | |---|---:|---:|---:|---:| -| `allcaps_roman_run_beginning_with_pure_letter_shortform` | 4120 | 2465 | 1655 | 0 | +| `allcaps_roman_run_beginning_with_pure_letter_shortform` | 4120 | 2477 | 1643 | 0 | | `allcaps_roman_run_containing_ar` | 1022 | 363 | 659 | 0 | | `allcaps_roman_run_containing_ed` | 816 | 319 | 497 | 0 | | `allcaps_roman_run_containing_ou` | 1816 | 64 | 1752 | 0 | -| `allcaps_roman_run_containing_st` | 1479 | 685 | 794 | 0 | +| `allcaps_roman_run_containing_st` | 1479 | 686 | 793 | 0 | | `ampersand_before_attached_ascii_roman_segment` | 30 | 12 | 18 | 0 | -| `ascii_roman_tail_comma_before_digit_korean_token` | 58 | 0 | 58 | 0 | -| `attached_ascii_roman_segments_joined_by_ampersand` | 802 | 284 | 518 | 0 | -| `attached_ascii_roman_to_korean_script_boundary` | 17693 | 12939 | 4754 | 0 | -| `closed_roman_parenthetical_after_non_ascii_letter_boundary` | 63959 | 52252 | 11707 | 0 | -| `compact_numeric_ascii_letter_suffix` | 2975 | 1872 | 1103 | 0 | -| `consecutive_ascii_roman_words_whitespace_boundary` | 4679 | 2132 | 2547 | 0 | -| `decimal_point_between_ascii_digits` | 4546 | 3383 | 1163 | 0 | +| `ascii_roman_tail_comma_before_digit_korean_token` | 58 | 3 | 55 | 0 | +| `attached_ascii_roman_segments_joined_by_ampersand` | 802 | 285 | 517 | 0 | +| `attached_ascii_roman_to_korean_script_boundary` | 17693 | 12966 | 4727 | 0 | +| `closed_roman_parenthetical_after_non_ascii_letter_boundary` | 63959 | 52384 | 11575 | 0 | +| `compact_numeric_ascii_letter_suffix` | 2975 | 1877 | 1098 | 0 | +| `consecutive_ascii_roman_words_whitespace_boundary` | 4679 | 2134 | 2545 | 0 | +| `decimal_point_between_ascii_digits` | 4546 | 3394 | 1152 | 0 | | `korean_inline_parenthesized_single_arithmetic_operator` | 23 | 17 | 6 | 0 | -| `korean_majority_same_token_roman_sandwich_non_domain` | 947 | 385 | 562 | 0 | -| `korean_prefixed_closed_allcaps_parenthetical` | 54492 | 45032 | 9460 | 0 | -| `korean_prefixed_closed_roman_annotation_rule_34_order` | 64382 | 52855 | 11527 | 0 | +| `korean_majority_same_token_roman_sandwich_non_domain` | 947 | 386 | 561 | 0 | +| `korean_prefixed_closed_allcaps_parenthetical` | 54492 | 45148 | 9344 | 0 | +| `korean_prefixed_closed_roman_annotation_rule_34_order` | 64382 | 52990 | 11392 | 0 | | `korean_prefixed_roman_parenthetical_followed_by_allcaps_hyphen_suffix` | 11 | 0 | 11 | 0 | | `mixed_roman_korean_word_before_uppercase_headword_expansion` | 10 | 0 | 10 | 0 | | `multi_character_allcaps_roman_runs_joined_by_middle_dot` | 97 | 0 | 97 | 0 | -| `percent_point_unit_list_comma` | 7 | 0 | 7 | 0 | -| `pure_allcaps_segment_before_hyphen_and_multi_allcaps_segment_after` | 448 | 131 | 317 | 0 | +| `percent_point_unit_list_comma` | 7 | 5 | 2 | 0 | +| `pure_allcaps_segment_before_hyphen_and_multi_allcaps_segment_after` | 448 | 132 | 316 | 0 | | `roman_hyphenated_word_after_whitespace_following_korean_word` | 361 | 0 | 361 | 0 | -| `roman_parenthetical_headword_after_whitespace_following_korean_word` | 4695 | 3382 | 1313 | 0 | +| `roman_parenthetical_headword_after_whitespace_following_korean_word` | 4695 | 3407 | 1288 | 0 | | `roman_run_after_whitespace_following_closed_roman_enclosure` | 1093 | 142 | 951 | 0 | | `roman_run_immediately_before_attached_middle_dot_boundary` | 577 | 0 | 577 | 0 | -| `rule69_ascii_unit_before_terminator_skipping_symbol` | 440 | 341 | 99 | 0 | +| `rule69_ascii_unit_before_terminator_skipping_symbol` | 440 | 343 | 97 | 0 | | `single_capital_followed_by_parenthesized_digits` | 1361 | 0 | 1361 | 0 | -| `spaced_comma_between_ascii_digit_runs` | 217 | 7 | 210 | 0 | -| `standalone_multi_character_uppercase_roman_word` | 62411 | 50460 | 11951 | 0 | +| `spaced_comma_between_ascii_digit_runs` | 217 | 181 | 36 | 0 | +| `standalone_multi_character_uppercase_roman_word` | 62411 | 50594 | 11817 | 0 | | `tight_triangle_mark_immediately_before_korean` | 377 | 0 | 377 | 0 | -| `uppercase_alphanumeric_roman_digit_sequence` | 3429 | 1911 | 1518 | 0 | -| `uppercase_ascii_run_immediately_after_digit_in_roman_sequence` | 1896 | 1102 | 794 | 0 | -| `uppercase_ascii_run_immediately_after_hyphen_in_roman_sequence` | 952 | 164 | 788 | 0 | +| `uppercase_alphanumeric_roman_digit_sequence` | 3429 | 1916 | 1513 | 0 | +| `uppercase_ascii_run_immediately_after_digit_in_roman_sequence` | 1896 | 1106 | 790 | 0 | +| `uppercase_ascii_run_immediately_after_hyphen_in_roman_sequence` | 952 | 165 | 787 | 0 | | `uppercase_roman_headword_closed_multiword_parenthetical` | 175 | 16 | 159 | 0 | | `uppercase_roman_run_followed_by_hyphen_digits` | 571 | 191 | 380 | 0 | | `uppercase_word_after_whitespace_continuing_ascii_roman_text` | 1729 | 699 | 1030 | 0 | ### `allcaps_roman_run_beginning_with_pure_letter_shortform` -Of the 4120 candidates, 1584 are the actual `pending_rule_review` subcluster. The other 2536 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 4120 candidates, 1572 are the actual `pending_rule_review` subcluster. The other 2548 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. -For this output-signature audit, 1655 mismatches were evaluable and 832 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. +For this output-signature audit, 1643 mismatches were evaluable and 832 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. Localized first-difference transitions: @@ -608,7 +608,7 @@ Mismatch primary-class distribution: - `comparison_method`: 11 - `corpus_suspect`: 50 -- `pending_rule_review`: 1584 +- `pending_rule_review`: 1572 - `unsupported_character_review`: 10 Representative `exact` samples: @@ -910,9 +910,9 @@ Representative `mismatch` samples: ### `allcaps_roman_run_containing_st` -Of the 1479 candidates, 741 are the actual `pending_rule_review` subcluster. The other 738 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 1479 candidates, 740 are the actual `pending_rule_review` subcluster. The other 739 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. -For this output-signature audit, 794 mismatches were evaluable and 443 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. +For this output-signature audit, 793 mismatches were evaluable and 443 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. Localized first-difference transitions: @@ -926,7 +926,7 @@ Mismatch primary-class distribution: - `comparison_method`: 13 - `corpus_suspect`: 35 -- `pending_rule_review`: 741 +- `pending_rule_review`: 740 - `unsupported_character_review`: 5 Representative `exact` samples: @@ -1058,18 +1058,30 @@ Representative `mismatch` samples: ### `ascii_roman_tail_comma_before_digit_korean_token` -Of the 58 candidates, 56 are the actual `pending_rule_review` subcluster. The other 2 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 58 candidates, 53 are the actual `pending_rule_review` subcluster. The other 5 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. -For this output-signature audit, 58 mismatches were evaluable and 23 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. +For this output-signature audit, 55 mismatches were evaluable and 22 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. Localized first-difference transitions: -- `U+2810 ⠐ -> U+2802 ⠂`: 23 +- `U+2810 ⠐ -> U+2802 ⠂`: 18 +- `U+2802 ⠂ -> U+2810 ⠐`: 4 Mismatch primary-class distribution: - `corpus_suspect`: 2 -- `pending_rule_review`: 56 +- `pending_rule_review`: 53 + +Representative `exact` samples: + +- `sentence_01.json` #1767: 부산은행도 서민금융 상품 ‘새희망홀씨’ 대출 금리를 1%p 내렸다. 주담대와 전세대출, 신용대출도 최대 0.8%포인트(p), 0.85%p, 0.6%p씩 금리를 내리기로 했다. + - expected: `⠘⠍⠇⠒⠵⠚⠗⠶⠊⠥⠀⠠⠎⠑⠟⠈⠪⠢⠩⠶⠀⠇⠶⠙` + - actual: `⠘⠍⠇⠒⠵⠚⠗⠶⠊⠥⠀⠠⠎⠑⠟⠈⠪⠢⠩⠶⠀⠇⠶⠙` + - current primary/reason: `exact` / `exact` +- `sentence_02.json` #19806: 자본환원율(캡레이트·Capitalization Rate)은 오피스가 4.3%, 물류는 5.5%로 지난해 동기보다 각각 0.8%p, 0.6%포인트(p) 상승했다. 자본환원율은 자산의 연간 순영업이익(NOI)을 매입금액으로 나눈 수치로, 상승한 경우에 통상 자산가격 하락을 의미한다. + - expected: `⠨⠘⠷⠚⠧⠒⠏⠒⠩⠂⠦⠄⠋⠗⠃⠐⠝⠕⠓⠪⠐⠆⠴⠠` + - actual: `⠨⠘⠷⠚⠧⠒⠏⠒⠩⠂⠦⠄⠋⠗⠃⠐⠝⠕⠓⠪⠐⠆⠴⠠` + - current primary/reason: `exact` / `exact` Representative `localized_mismatch` samples: @@ -1078,15 +1090,15 @@ Representative `localized_mismatch` samples: - actual: `⠚⠒⠀⠴⠠⠠⠏⠉⠂⠀⠼⠃⠚⠚⠚⠀⠉⠡⠊⠗⠺⠀⠟⠓` - first differing cell (zero-based): 72 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_02.json` #537: 11일 Macker와 ZAYDA, 12에는 Killa Ton과 Bagagee Viphex13, 13일에는 EDM씬의 트렌드를 주도하는 쥬디(JOODY)와 아쉬코(ASHIKO)가 출연했다. - - expected: `⠴⠠⠠⠵⠁⠽⠙⠁⠐⠀⠼⠁⠃⠝⠉⠵⠀⠴⠠⠅⠊⠇⠇⠁` - - actual: `⠴⠠⠠⠵⠁⠽⠙⠁⠂⠀⠼⠁⠃⠝⠉⠵⠀⠴⠠⠅⠊⠇⠇⠁` - - first differing cell (zero-based): 24 +- `sentence_02.json` #431: 173cm, 68kg의 다소 왜소한 체격이지만 스크럼 하프(SH) 포지션을 맡아 포워드와 백스 사이에서 날카로운 볼 연결과 공격방향을 결정하는 타고난 판단력이 강점이다. + - expected: `⠼⠁⠛⠉⠴⠉⠍⠂⠀⠼⠋⠓⠅⠛⠲⠺⠀⠊⠠⠥⠀⠧⠗⠠` + - actual: `⠼⠁⠛⠉⠴⠉⠍⠐⠀⠼⠋⠓⠴⠅⠛⠲⠺⠀⠊⠠⠥⠀⠧⠗` + - first differing cell (zero-based): 7 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_03.json` #2147: 용량은 512GB, 1테라바이트(TB), 두 가지 종류로 출시됐다. USB 연결 케이블 2종(C-to-C, C-to-A)과 함께 전용 범퍼케이스가 제공된다. - - expected: `⠑⠁⠃⠴⠠⠠⠛⠃⠐⠀⠼⠁⠀⠓⠝⠐⠣⠘⠣⠕⠓⠪⠦⠄` - - actual: `⠑⠁⠃⠴⠠⠠⠛⠃⠂⠀⠼⠁⠀⠓⠝⠐⠣⠘⠣⠕⠓⠪⠦⠄` - - first differing cell (zero-based): 16 +- `sentence_03.json` #2225: 이밖에 4위 폭스바겐, 5위 지리, 6위 스텔란티스가 뒤를 이었다. 7위 현대차·기아, 8위 르노·닛산·미쓰비시 얼라이언스, 9위 BMW, 10위 광저우자동차(GAC) 순이었다. + - expected: `⠗⠀⠴⠠⠠⠃⠍⠺⠐⠀⠼⠁⠚⠍⠗⠀⠈⠧⠶⠨⠎⠍⠨⠊` + - actual: `⠗⠀⠴⠠⠠⠃⠍⠺⠂⠀⠼⠁⠚⠍⠗⠀⠈⠧⠶⠨⠎⠍⠨⠊` + - first differing cell (zero-based): 129 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` - `sentence_04.json` #4209: 사티아 나델라 MS 최고경영자(CEO)는 이날 “나의 계정에서 코파일럿과 채팅하게 돼 기쁘다”며 “AI 비서와 일하는 것은 1980년대의 PC, 1990년대의 인터넷, 21세기 모바일의 부상만큼이나 주목할 만하다”라고 자평했다. - expected: `⠗⠺⠀⠴⠠⠠⠏⠉⠐⠀⠼⠁⠊⠊⠚⠀⠉⠡⠊⠗⠺⠀⠟⠓` @@ -1107,9 +1119,9 @@ Representative `mismatch` samples: - first differing cell (zero-based): 35 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` - `sentence_03.json` #2147: 용량은 512GB, 1테라바이트(TB), 두 가지 종류로 출시됐다. USB 연결 케이블 2종(C-to-C, C-to-A)과 함께 전용 범퍼케이스가 제공된다. - - expected: `⠑⠁⠃⠴⠠⠠⠛⠃⠐⠀⠼⠁⠀⠓⠝⠐⠣⠘⠣⠕⠓⠪⠦⠄` - - actual: `⠑⠁⠃⠴⠠⠠⠛⠃⠂⠀⠼⠁⠀⠓⠝⠐⠣⠘⠣⠕⠓⠪⠦⠄` - - first differing cell (zero-based): 16 + - expected: `⠨⠿⠦⠄⠴⠠⠉⠤⠞⠕⠤⠰⠠⠉⠂⠀⠰⠠⠉⠤⠞⠕⠤⠠` + - actual: `⠨⠿⠦⠄⠴⠠⠉⠤⠰⠞⠕⠤⠰⠠⠉⠂⠀⠠⠉⠤⠰⠞⠕⠤` + - first differing cell (zero-based): 94 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` - `sentence_04.json` #4209: 사티아 나델라 MS 최고경영자(CEO)는 이날 “나의 계정에서 코파일럿과 채팅하게 돼 기쁘다”며 “AI 비서와 일하는 것은 1980년대의 PC, 1990년대의 인터넷, 21세기 모바일의 부상만큼이나 주목할 만하다”라고 자평했다. - expected: `⠗⠺⠀⠴⠠⠠⠏⠉⠐⠀⠼⠁⠊⠊⠚⠀⠉⠡⠊⠗⠺⠀⠟⠓` @@ -1119,15 +1131,15 @@ Representative `mismatch` samples: ### `attached_ascii_roman_segments_joined_by_ampersand` -Of the 802 candidates, 491 are the actual `pending_rule_review` subcluster. The other 311 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 802 candidates, 490 are the actual `pending_rule_review` subcluster. The other 312 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. -For this output-signature audit, 518 mismatches were evaluable and 0 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. +For this output-signature audit, 517 mismatches were evaluable and 0 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. Mismatch primary-class distribution: - `comparison_method`: 3 - `corpus_suspect`: 10 -- `pending_rule_review`: 491 +- `pending_rule_review`: 490 - `unsupported_character_review`: 14 Representative `exact` samples: @@ -1174,15 +1186,15 @@ Representative `mismatch` samples: ### `attached_ascii_roman_to_korean_script_boundary` -Of the 17693 candidates, 3922 are the actual `pending_rule_review` subcluster. The other 13771 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 17693 candidates, 3895 are the actual `pending_rule_review` subcluster. The other 13798 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. -For this output-signature audit, 4754 mismatches were evaluable and 0 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. +For this output-signature audit, 4727 mismatches were evaluable and 0 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. Mismatch primary-class distribution: - `comparison_method`: 60 - `corpus_suspect`: 721 -- `pending_rule_review`: 3922 +- `pending_rule_review`: 3895 - `unsupported_character_review`: 51 Representative `exact` samples: @@ -1274,9 +1286,9 @@ Representative `mismatch_rule39_hangul_opening` samples: ### `closed_roman_parenthetical_after_non_ascii_letter_boundary` -Of the 63959 candidates, 10220 are the actual `pending_rule_review` subcluster. The other 53739 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 63959 candidates, 10088 are the actual `pending_rule_review` subcluster. The other 53871 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. -For this output-signature audit, 11707 mismatches were evaluable and 1245 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. +For this output-signature audit, 11575 mismatches were evaluable and 1245 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. Localized first-difference transitions: @@ -1288,10 +1300,10 @@ Localized first-difference transitions: Mismatch primary-class distribution: -- `comparison_method`: 225 +- `comparison_method`: 226 - `corpus_suspect`: 1107 -- `pending_rule_review`: 10220 -- `unsupported_character_review`: 155 +- `pending_rule_review`: 10088 +- `unsupported_character_review`: 154 Representative `exact` samples: @@ -1360,9 +1372,9 @@ Representative `mismatch` samples: ### `compact_numeric_ascii_letter_suffix` -Of the 2975 candidates, 1032 are the actual `pending_rule_review` subcluster. The other 1943 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 2975 candidates, 1027 are the actual `pending_rule_review` subcluster. The other 1948 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. -For this output-signature audit, 1103 mismatches were evaluable and 254 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. +For this output-signature audit, 1098 mismatches were evaluable and 251 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. Localized first-difference transitions: @@ -1370,13 +1382,13 @@ Localized first-difference transitions: - `U+2800 ⠀ -> U+2832 ⠲`: 27 - `U+2810 ⠐ -> U+2832 ⠲`: 11 - `U+281E ⠞ -> U+2810 ⠐`: 8 -- `U+2805 ⠅ -> U+2834 ⠴`: 4 +- `U+2818 ⠘ -> U+2832 ⠲`: 2 Mismatch primary-class distribution: - `comparison_method`: 12 - `corpus_suspect`: 52 -- `pending_rule_review`: 1032 +- `pending_rule_review`: 1027 - `unsupported_character_review`: 7 Representative `exact` samples: @@ -1405,10 +1417,10 @@ Representative `localized_mismatch` samples: - actual: `⠠⠊⠎⠍⠀⠼⠁⠙⠐⠞⠓⠀⠠⠫⠊⠰⠝⠴⠄⠀⠟⠨⠪⠶` - first differing cell (zero-based): 129 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_02.json` #431: 173cm, 68kg의 다소 왜소한 체격이지만 스크럼 하프(SH) 포지션을 맡아 포워드와 백스 사이에서 날카로운 볼 연결과 공격방향을 결정하는 타고난 판단력이 강점이다. - - expected: `⠴⠉⠍⠂⠀⠼⠋⠓⠅⠛⠲⠺⠀⠊⠠⠥⠀⠧⠗⠠⠥⠚⠒⠀` - - actual: `⠴⠉⠍⠂⠀⠼⠋⠓⠴⠅⠛⠲⠺⠀⠊⠠⠥⠀⠧⠗⠠⠥⠚⠒` - - first differing cell (zero-based): 12 +- `sentence_02.json` #445: 박람회에서는 다양한 분야의 디지털 교육 프로그램을 한자리에서 체험할 수 있도록 인공지능(AI) 코스웨어·학습플랫폼, 인공지능(AI) 교과교육, 인공지능(AI) 학습지원, 3D·가상현실(VR)·메타버스 교육, 소프트웨어(SW)·코딩·로봇 교육 등 체험 공간을 운영할 예정이다. + - expected: `⠒⠐⠀⠼⠉⠴⠠⠙⠐⠆⠫⠇⠶⠚⠡⠠⠕⠂⠦⠄⠴⠠⠠⠧` + - actual: `⠒⠐⠀⠼⠉⠴⠠⠙⠲⠐⠆⠫⠇⠶⠚⠡⠠⠕⠂⠦⠄⠴⠠⠠` + - first differing cell (zero-based): 179 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` - `sentence_03.json` #59: 이들 중 A씨(31) 등 2명은 지난 3월 25일 김해공항으로 필로폰 968g 등을 팬티 속에 숨겨 입국한 혐의를 받고 있다. - expected: `⠙⠕⠂⠐⠥⠙⠷⠀⠼⠊⠋⠓⠴⠛⠲⠀⠊⠪⠶⠮⠀⠙⠗⠒` @@ -1446,9 +1458,9 @@ Representative `mismatch` samples: ### `consecutive_ascii_roman_words_whitespace_boundary` -Of the 4679 candidates, 2413 are the actual `pending_rule_review` subcluster. The other 2266 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 4679 candidates, 2411 are the actual `pending_rule_review` subcluster. The other 2268 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. -For this output-signature audit, 2547 mismatches were evaluable and 23 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. +For this output-signature audit, 2545 mismatches were evaluable and 23 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. Localized first-difference transitions: @@ -1458,7 +1470,7 @@ Mismatch primary-class distribution: - `comparison_method`: 17 - `corpus_suspect`: 88 -- `pending_rule_review`: 2413 +- `pending_rule_review`: 2411 - `unsupported_character_review`: 29 Representative `exact` samples: @@ -1524,9 +1536,9 @@ Representative `mismatch` samples: ### `decimal_point_between_ascii_digits` -Of the 4546 candidates, 1106 are the actual `pending_rule_review` subcluster. The other 3440 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 4546 candidates, 1095 are the actual `pending_rule_review` subcluster. The other 3451 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. -For this output-signature audit, 1163 mismatches were evaluable and 260 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. +For this output-signature audit, 1152 mismatches were evaluable and 262 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. Localized first-difference transitions: @@ -1540,7 +1552,7 @@ Mismatch primary-class distribution: - `comparison_method`: 17 - `corpus_suspect`: 28 -- `pending_rule_review`: 1106 +- `pending_rule_review`: 1095 - `unsupported_character_review`: 12 Representative `exact` samples: @@ -1656,12 +1668,12 @@ Representative `mismatch` samples: ### `korean_majority_same_token_roman_sandwich_non_domain` -Of the 947 candidates, 550 are the actual `pending_rule_review` subcluster. The other 397 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 947 candidates, 549 are the actual `pending_rule_review` subcluster. The other 398 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. Mismatch primary-class distribution: - `corpus_suspect`: 7 -- `pending_rule_review`: 550 +- `pending_rule_review`: 549 - `unsupported_character_review`: 5 Representative `exact` samples: @@ -1708,14 +1720,14 @@ Representative `mismatch` samples: ### `korean_prefixed_closed_allcaps_parenthetical` -Of the 54492 candidates, 8159 are the actual `pending_rule_review` subcluster. The other 46333 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 54492 candidates, 8043 are the actual `pending_rule_review` subcluster. The other 46449 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. Mismatch primary-class distribution: -- `comparison_method`: 177 +- `comparison_method`: 178 - `corpus_suspect`: 992 -- `pending_rule_review`: 8159 -- `unsupported_character_review`: 132 +- `pending_rule_review`: 8043 +- `unsupported_character_review`: 131 Representative `exact` samples: @@ -1761,9 +1773,9 @@ Representative `mismatch` samples: ### `korean_prefixed_closed_roman_annotation_rule_34_order` -Of the 64382 candidates, 10036 are the actual `pending_rule_review` subcluster. The other 54346 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 64382 candidates, 9901 are the actual `pending_rule_review` subcluster. The other 54481 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. -For this output-signature audit, 11527 mismatches were evaluable and 1111 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. +For this output-signature audit, 11392 mismatches were evaluable and 1111 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. Localized first-difference transitions: @@ -1772,10 +1784,10 @@ Localized first-difference transitions: Mismatch primary-class distribution: -- `comparison_method`: 229 +- `comparison_method`: 230 - `corpus_suspect`: 1107 -- `pending_rule_review`: 10036 -- `unsupported_character_review`: 155 +- `pending_rule_review`: 9901 +- `unsupported_character_review`: 154 Representative `exact` samples: @@ -1954,44 +1966,38 @@ Representative `mismatch` samples: ### `percent_point_unit_list_comma` -Of the 7 candidates, 7 are the actual `pending_rule_review` subcluster. The other 0 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 7 candidates, 2 are the actual `pending_rule_review` subcluster. The other 5 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. -For this output-signature audit, 7 mismatches were evaluable and 1 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. - -Localized first-difference transitions: - -- `U+2810 ⠐ -> U+2802 ⠂`: 1 +For this output-signature audit, 2 mismatches were evaluable and 0 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. Mismatch primary-class distribution: -- `pending_rule_review`: 7 +- `pending_rule_review`: 2 -Representative `localized_mismatch` samples: +Representative `exact` samples: - `sentence_01.json` #1767: 부산은행도 서민금융 상품 ‘새희망홀씨’ 대출 금리를 1%p 내렸다. 주담대와 전세대출, 신용대출도 최대 0.8%포인트(p), 0.85%p, 0.6%p씩 금리를 내리기로 했다. - - expected: `⠼⠚⠲⠓⠑⠴⠏⠏⠐⠀⠼⠚⠲⠋⠴⠏⠏⠀⠠⠠⠕⠁⠀⠈` - - actual: `⠼⠚⠲⠓⠑⠴⠏⠏⠂⠀⠼⠚⠲⠋⠴⠏⠏⠀⠠⠠⠕⠁⠀⠈` - - first differing cell (zero-based): 133 - - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` + - expected: `⠘⠍⠇⠒⠵⠚⠗⠶⠊⠥⠀⠠⠎⠑⠟⠈⠪⠢⠩⠶⠀⠇⠶⠙` + - actual: `⠘⠍⠇⠒⠵⠚⠗⠶⠊⠥⠀⠠⠎⠑⠟⠈⠪⠢⠩⠶⠀⠇⠶⠙` + - current primary/reason: `exact` / `exact` +- `sentence_02.json` #12305: 1일 금융감독원에 따르면 3월말 국내은행의 BIS기준 보통주자본비율, 기본자본비율, 총자본비율은 각각 12.88%, 14.24%, 15.58%로 나타났다. 전 분기 대비 각각 0.28%포인트(p), 0.33%p, 0.29%p 올랐다. + - expected: `⠼⠁⠕⠂⠀⠈⠪⠢⠩⠶⠫⠢⠊⠭⠏⠒⠝⠀⠠⠊⠐⠪⠑⠡` + - actual: `⠼⠁⠕⠂⠀⠈⠪⠢⠩⠶⠫⠢⠊⠭⠏⠒⠝⠀⠠⠊⠐⠪⠑⠡` + - current primary/reason: `exact` / `exact` Representative `mismatch` samples: -- `sentence_01.json` #1767: 부산은행도 서민금융 상품 ‘새희망홀씨’ 대출 금리를 1%p 내렸다. 주담대와 전세대출, 신용대출도 최대 0.8%포인트(p), 0.85%p, 0.6%p씩 금리를 내리기로 했다. - - expected: `⠼⠚⠲⠓⠑⠴⠏⠏⠐⠀⠼⠚⠲⠋⠴⠏⠏⠀⠠⠠⠕⠁⠀⠈` - - actual: `⠼⠚⠲⠓⠑⠴⠏⠏⠂⠀⠼⠚⠲⠋⠴⠏⠏⠀⠠⠠⠕⠁⠀⠈` - - first differing cell (zero-based): 133 - - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_02.json` #12305: 1일 금융감독원에 따르면 3월말 국내은행의 BIS기준 보통주자본비율, 기본자본비율, 총자본비율은 각각 12.88%, 14.24%, 15.58%로 나타났다. 전 분기 대비 각각 0.28%포인트(p), 0.33%p, 0.29%p 올랐다. - - expected: `⠼⠚⠲⠉⠉⠴⠏⠏⠐⠀⠼⠚⠲⠃⠊⠴⠏⠏⠀⠥⠂⠐⠣⠌` - - actual: `⠼⠚⠲⠉⠉⠴⠏⠏⠂⠀⠼⠚⠲⠃⠊⠴⠏⠏⠀⠥⠂⠐⠣⠌` - - first differing cell (zero-based): 185 +- `sentence_02.json` #15633: 4일 금융감독원에 따르면 6월 말 국내은행의 BIS기준 보통주자본비율, 기본자본비율, 총자본비율은 각각 12.98%, 14.27%, 15.62%로 나타났다. 전 분기 대비 각각 0.08%포인트(p), 0.01%p, 0.04%p 올랐다. + - expected: `⠐⠀⠼⠚⠲⠚⠙⠴⠴⠏⠏⠀⠥⠂⠐⠣⠌⠊⠲` + - actual: `⠐⠀⠼⠚⠲⠚⠙⠴⠏⠏⠀⠥⠂⠐⠣⠌⠊⠲` + - first differing cell (zero-based): 194 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` ### `pure_allcaps_segment_before_hyphen_and_multi_allcaps_segment_after` -Of the 448 candidates, 307 are the actual `pending_rule_review` subcluster. The other 141 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 448 candidates, 306 are the actual `pending_rule_review` subcluster. The other 142 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. -For this output-signature audit, 317 mismatches were evaluable and 1 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. +For this output-signature audit, 316 mismatches were evaluable and 1 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. Localized first-difference transitions: @@ -2000,7 +2006,7 @@ Localized first-difference transitions: Mismatch primary-class distribution: - `corpus_suspect`: 8 -- `pending_rule_review`: 307 +- `pending_rule_review`: 306 - `unsupported_character_review`: 2 Representative `exact` samples: @@ -2117,9 +2123,9 @@ Representative `mismatch` samples: ### `roman_parenthetical_headword_after_whitespace_following_korean_word` -Of the 4695 candidates, 1221 are the actual `pending_rule_review` subcluster. The other 3474 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 4695 candidates, 1196 are the actual `pending_rule_review` subcluster. The other 3499 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. -For this output-signature audit, 1313 mismatches were evaluable and 158 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. +For this output-signature audit, 1288 mismatches were evaluable and 158 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. Localized first-difference transitions: @@ -2131,7 +2137,7 @@ Mismatch primary-class distribution: - `comparison_method`: 29 - `corpus_suspect`: 39 -- `pending_rule_review`: 1221 +- `pending_rule_review`: 1196 - `unsupported_character_review`: 24 Representative `exact` samples: @@ -2351,23 +2357,23 @@ Representative `mismatch` samples: ### `rule69_ascii_unit_before_terminator_skipping_symbol` -Of the 440 candidates, 94 are the actual `pending_rule_review` subcluster. The other 346 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 440 candidates, 92 are the actual `pending_rule_review` subcluster. The other 348 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. -For this output-signature audit, 99 mismatches were evaluable and 10 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. +For this output-signature audit, 97 mismatches were evaluable and 16 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. Localized first-difference transitions: +- `U+2802 ⠂ -> U+2810 ⠐`: 6 - `U+2814 ⠔ -> U+2824 ⠤`: 4 - `U+2834 ⠴ -> U+280D ⠍`: 3 -- `U+2805 ⠅ -> U+2834 ⠴`: 1 - `U+2810 ⠐ -> U+2802 ⠂`: 1 -- `U+283C ⠼ -> U+2800 ⠀`: 1 +- `U+2820 ⠠ -> U+281B ⠛`: 1 Mismatch primary-class distribution: - `comparison_method`: 3 - `corpus_suspect`: 1 -- `pending_rule_review`: 94 +- `pending_rule_review`: 92 - `unsupported_character_review`: 1 Representative `exact` samples: @@ -2396,15 +2402,15 @@ Representative `localized_mismatch` samples: - actual: `⠍⠁⠀⠠⠦⠼⠊⠚⠍⠔⠴⠄⠝⠀⠠⠊⠐⠪⠑⠡⠀⠘⠝⠕` - first differing cell (zero-based): 29 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_02.json` #4083: 이후 처음 출전한 이번 대회에서 곽범서(-48kg), 송화평(+92kg) 선수가 정상에 올랐고 안성호(-51kg) 선수가 동메달을 목에 걸었다. - - expected: `⠁⠘⠎⠢⠠⠎⠦⠄⠔⠼⠙⠓⠴⠅⠛⠠⠴⠐⠀⠠⠿⠚⠧⠙` - - actual: `⠁⠘⠎⠢⠠⠎⠦⠄⠤⠼⠙⠓⠴⠅⠛⠠⠴⠐⠀⠠⠿⠚⠧⠙` - - first differing cell (zero-based): 38 +- `sentence_02.json` #431: 173cm, 68kg의 다소 왜소한 체격이지만 스크럼 하프(SH) 포지션을 맡아 포워드와 백스 사이에서 날카로운 볼 연결과 공격방향을 결정하는 타고난 판단력이 강점이다. + - expected: `⠼⠁⠛⠉⠴⠉⠍⠂⠀⠼⠋⠓⠅⠛⠲⠺⠀⠊⠠⠥⠀⠧⠗⠠` + - actual: `⠼⠁⠛⠉⠴⠉⠍⠐⠀⠼⠋⠓⠴⠅⠛⠲⠺⠀⠊⠠⠥⠀⠧⠗` + - first differing cell (zero-based): 7 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` - `sentence_03.json` #17113: 6피트 9인치, 210파운드(206cm, 95kg)의 신체 사이즈를 지닌 폴 리드는 7피트 2인치(218cm)에 달하는 긴 윙스팬과 더불어 수비력을 인정받은 빅맨이다. - - expected: `⠴⠉⠍⠂⠀⠼⠊⠑⠅⠛⠠⠴⠺⠀⠠⠟⠰⠝⠀⠇⠕⠨⠪⠐` - - actual: `⠴⠉⠍⠂⠀⠼⠊⠑⠴⠅⠛⠠⠴⠺⠀⠠⠟⠰⠝⠀⠇⠕⠨⠪` - - first differing cell (zero-based): 39 + - expected: `⠄⠼⠃⠚⠋⠴⠉⠍⠂⠀⠼⠊⠑⠅⠛⠠⠴⠺⠀⠠⠟⠰⠝⠀` + - actual: `⠄⠼⠃⠚⠋⠴⠉⠍⠐⠀⠼⠊⠑⠴⠅⠛⠠⠴⠺⠀⠠⠟⠰⠝` + - first differing cell (zero-based): 34 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` Representative `mismatch` samples: @@ -2473,90 +2479,67 @@ Representative `mismatch` samples: ### `spaced_comma_between_ascii_digit_runs` -Of the 217 candidates, 209 are the actual `pending_rule_review` subcluster. The other 8 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 217 candidates, 35 are the actual `pending_rule_review` subcluster. The other 182 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. -For this output-signature audit, 210 mismatches were evaluable and 177 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. - -Localized first-difference transitions: - -- `U+2810 ⠐ -> U+2802 ⠂`: 177 +For this output-signature audit, 36 mismatches were evaluable and 0 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. Mismatch primary-class distribution: -- `pending_rule_review`: 209 -- `unsupported_character_review`: 1 +- `comparison_method`: 1 +- `pending_rule_review`: 35 Representative `exact` samples: -- `sentence_02.json` #1719: 2001년부터 5년 단위로 무역수지기여도(CTB)를 분석한 결과 2001~2005년 105.0, 2006~2010년 115.5, 2011~2015년 73.4로 나타났으나 2010년대 중반부터 감소하면서 장기평균을 하회하고 있는 것으로 조사됐다. - - expected: `⠼⠃⠚⠚⠁⠀⠉⠡⠘⠍⠓⠎⠀⠼⠑⠀⠉⠡⠀⠊⠒⠍⠗⠐` - - actual: `⠼⠃⠚⠚⠁⠀⠉⠡⠘⠍⠓⠎⠀⠼⠑⠀⠉⠡⠀⠊⠒⠍⠗⠐` - - current primary/reason: `exact` / `exact` -- `sentence_03.json` #18256: 올 시즌 타율 0.337, 30홈런, 83타점, 62도루, OPS(출루율+장타율) 0.993을 기록한 아쿠냐는 내셔널리그에서 가장 유력한 최우수선수(MVP) 후보로 꼽힌다. - - expected: `⠥⠂⠀⠠⠕⠨⠵⠀⠓⠣⠩⠂⠀⠼⠚⠲⠉⠉⠛⠐⠀⠼⠉⠚` - - actual: `⠥⠂⠀⠠⠕⠨⠵⠀⠓⠣⠩⠂⠀⠼⠚⠲⠉⠉⠛⠐⠀⠼⠉⠚` - - current primary/reason: `exact` / `exact` -- `sentence_04.json` #4117: 예정처가 2021년 6월을 기준(100)으로 산출한 전세가격지수를 보면 올 1월 전세가격지수는 전국 평균이 95.2, 수도권은 92.6, 지방이 97.6이었다. 7월에는 각각 91.4, 88.2, 94.4로 더 떨어졌다. - - expected: `⠌⠨⠻⠰⠎⠫⠀⠼⠃⠚⠃⠁⠀⠉⠡⠀⠼⠋⠏⠂⠮⠀⠈⠕` - - actual: `⠌⠨⠻⠰⠎⠫⠀⠼⠃⠚⠃⠁⠀⠉⠡⠀⠼⠋⠏⠂⠮⠀⠈⠕` - - current primary/reason: `exact` / `exact` - -Representative `localized_mismatch` samples: - - `sentence_01.json` #498: 신제품은 15.6인치(15Z90RT) 울트라슬림과 14인치(14Z90RS)·16인치(16Z90RS) 그램 스타일 등으로 구성된다. 아울러 그램 17, 16, 15, 14 등도 선보일 예정이다. - - expected: `⠪⠐⠗⠢⠀⠼⠁⠛⠐⠀⠼⠁⠋⠐⠀⠼⠁⠑⠐⠀⠼⠁⠙⠀` - - actual: `⠪⠐⠗⠢⠀⠼⠁⠛⠂⠀⠼⠁⠋⠂⠀⠼⠁⠑⠂⠀⠼⠁⠙⠀` - - first differing cell (zero-based): 141 - - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` + - expected: `⠠⠟⠨⠝⠙⠍⠢⠵⠀⠼⠁⠑⠲⠋⠟⠰⠕⠦⠄⠼⠁⠑⠴⠠` + - actual: `⠠⠟⠨⠝⠙⠍⠢⠵⠀⠼⠁⠑⠲⠋⠟⠰⠕⠦⠄⠼⠁⠑⠴⠠` + - current primary/reason: `exact` / `exact` - `sentence_02.json` #442: 시는 총사업비 59억 원을 투입해 1, 2단계 사업을 완료했으며, 부지면적 6만 3천㎡에 산악 지형용 자전거(MTB) 연습을 위한 펌프트랙 5개의 코스와 조명시설을 갖춘 축구장 2면을 조성했다. - - expected: `⠍⠕⠃⠚⠗⠀⠼⠁⠐⠀⠼⠃⠀⠊⠒⠈⠌⠀⠇⠎⠃⠮⠀⠧` - - actual: `⠍⠕⠃⠚⠗⠀⠼⠁⠂⠀⠼⠃⠀⠊⠒⠈⠌⠀⠇⠎⠃⠮⠀⠧` - - first differing cell (zero-based): 31 - - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` + - expected: `⠠⠕⠉⠵⠀⠰⠿⠇⠎⠃⠘⠕⠀⠼⠑⠊⠹⠀⠏⠒⠮⠀⠓⠍` + - actual: `⠠⠕⠉⠵⠀⠰⠿⠇⠎⠃⠘⠕⠀⠼⠑⠊⠹⠀⠏⠒⠮⠀⠓⠍` + - current primary/reason: `exact` / `exact` - `sentence_03.json` #3215: 현재경기판단(69, 5포인트), 향후경기전망(78, 4포인트)의 상승 폭이 상대적으로 컸다. 또 소비자지출전망(113)도 2포인트 올랐다. 생활형편전망(93)과 가계수입전망(98), 현재생활형편(89)은 각각 1포인트 상승했다. - - expected: `⠒⠊⠒⠦⠄⠼⠋⠊⠐⠀⠼⠑⠀⠙⠥⠟⠓⠪⠠⠴⠐⠀⠚⠜` - - actual: `⠒⠊⠒⠦⠄⠼⠋⠊⠂⠀⠼⠑⠀⠙⠥⠟⠓⠪⠠⠴⠐⠀⠚⠜` - - first differing cell (zero-based): 17 - - current primary/reason: `pending_rule_review` / `number_rule_review` + - expected: `⠚⠡⠨⠗⠈⠻⠈⠕⠙⠒⠊⠒⠦⠄⠼⠋⠊⠐⠀⠼⠑⠀⠙⠥` + - actual: `⠚⠡⠨⠗⠈⠻⠈⠕⠙⠒⠊⠒⠦⠄⠼⠋⠊⠐⠀⠼⠑⠀⠙⠥` + - current primary/reason: `exact` / `exact` - `sentence_04.json` #299: 국책연구기관인 한국환경연구원(KEI)이 한국수력원자력(한수원)이 낸 ‘신한울 원전 3, 4호기 환경영향평가 재협의 초안’에 부정적인 의견을 밝혔다. KEI는 한수원 조사에서 해산어류(바닷물고기류) 영향이 ‘매우 형식적’으로 이뤄졌다고 지적했다. - - expected: `⠀⠏⠒⠨⠾⠀⠼⠉⠐⠀⠼⠙⠀⠚⠥⠈⠕⠀⠚⠧⠒⠈⠻⠻` - - actual: `⠀⠏⠒⠨⠾⠀⠼⠉⠂⠀⠼⠙⠀⠚⠥⠈⠕⠀⠚⠧⠒⠈⠻⠻` - - first differing cell (zero-based): 90 - - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` + - expected: `⠈⠍⠁⠰⠗⠁⠡⠈⠍⠈⠕⠈⠧⠒⠟⠀⠚⠒⠈⠍⠁⠚⠧⠒` + - actual: `⠈⠍⠁⠰⠗⠁⠡⠈⠍⠈⠕⠈⠧⠒⠟⠀⠚⠒⠈⠍⠁⠚⠧⠒` + - current primary/reason: `exact` / `exact` Representative `mismatch` samples: -- `sentence_01.json` #498: 신제품은 15.6인치(15Z90RT) 울트라슬림과 14인치(14Z90RS)·16인치(16Z90RS) 그램 스타일 등으로 구성된다. 아울러 그램 17, 16, 15, 14 등도 선보일 예정이다. - - expected: `⠪⠐⠗⠢⠀⠼⠁⠛⠐⠀⠼⠁⠋⠐⠀⠼⠁⠑⠐⠀⠼⠁⠙⠀` - - actual: `⠪⠐⠗⠢⠀⠼⠁⠛⠂⠀⠼⠁⠋⠂⠀⠼⠁⠑⠂⠀⠼⠁⠙⠀` - - first differing cell (zero-based): 141 +- `sentence_01.json` #3841: CT-P43의 오리지널 의약품인 스텔라라는 얀센(Janssen)이 개발한 인터루킨(IL)-12, 23 억제제다. 판상형 건선과 건선성 관절염, 크론병, 궤양성 대장염 등의 자가면역질환 치료제로 사용된다. + - expected: `⠴⠠⠠⠉⠞⠤⠠⠏⠼⠙⠉⠺⠀⠥⠐⠕⠨⠕⠉⠞⠀⠺⠜⠁` + - actual: `⠴⠠⠠⠉⠞⠤⠰⠏⠼⠙⠉⠺⠀⠥⠐⠕⠨⠕⠉⠞⠀⠺⠜⠁` + - first differing cell (zero-based): 6 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_02.json` #442: 시는 총사업비 59억 원을 투입해 1, 2단계 사업을 완료했으며, 부지면적 6만 3천㎡에 산악 지형용 자전거(MTB) 연습을 위한 펌프트랙 5개의 코스와 조명시설을 갖춘 축구장 2면을 조성했다. - - expected: `⠍⠕⠃⠚⠗⠀⠼⠁⠐⠀⠼⠃⠀⠊⠒⠈⠌⠀⠇⠎⠃⠮⠀⠧` - - actual: `⠍⠕⠃⠚⠗⠀⠼⠁⠂⠀⠼⠃⠀⠊⠒⠈⠌⠀⠇⠎⠃⠮⠀⠧` - - first differing cell (zero-based): 31 +- `sentence_02.json` #537: 11일 Macker와 ZAYDA, 12에는 Killa Ton과 Bagagee Viphex13, 13일에는 EDM씬의 트렌드를 주도하는 쥬디(JOODY)와 아쉬코(ASHIKO)가 출연했다. + - expected: `⠴⠠⠠⠵⠁⠽⠙⠁⠐⠀⠼⠁⠃⠝⠉⠵⠀⠴⠠⠅⠊⠇⠇⠁` + - actual: `⠴⠠⠠⠵⠁⠽⠙⠁⠂⠀⠼⠁⠃⠝⠉⠵⠀⠴⠠⠅⠊⠇⠇⠁` + - first differing cell (zero-based): 24 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_03.json` #3215: 현재경기판단(69, 5포인트), 향후경기전망(78, 4포인트)의 상승 폭이 상대적으로 컸다. 또 소비자지출전망(113)도 2포인트 올랐다. 생활형편전망(93)과 가계수입전망(98), 현재생활형편(89)은 각각 1포인트 상승했다. - - expected: `⠒⠊⠒⠦⠄⠼⠋⠊⠐⠀⠼⠑⠀⠙⠥⠟⠓⠪⠠⠴⠐⠀⠚⠜` - - actual: `⠒⠊⠒⠦⠄⠼⠋⠊⠂⠀⠼⠑⠀⠙⠥⠟⠓⠪⠠⠴⠐⠀⠚⠜` - - first differing cell (zero-based): 17 - - current primary/reason: `pending_rule_review` / `number_rule_review` -- `sentence_04.json` #299: 국책연구기관인 한국환경연구원(KEI)이 한국수력원자력(한수원)이 낸 ‘신한울 원전 3, 4호기 환경영향평가 재협의 초안’에 부정적인 의견을 밝혔다. KEI는 한수원 조사에서 해산어류(바닷물고기류) 영향이 ‘매우 형식적’으로 이뤄졌다고 지적했다. - - expected: `⠀⠏⠒⠨⠾⠀⠼⠉⠐⠀⠼⠙⠀⠚⠥⠈⠕⠀⠚⠧⠒⠈⠻⠻` - - actual: `⠀⠏⠒⠨⠾⠀⠼⠉⠂⠀⠼⠙⠀⠚⠥⠈⠕⠀⠚⠧⠒⠈⠻⠻` - - first differing cell (zero-based): 90 +- `sentence_03.json` #13832: 이어 르세라핌은 오늘 정오 글로벌 팬 커뮤니티 플랫폼 위버스(Weverse)와 공식 SNS 채널을 통해 오는 3월 18, 19일 양일간 개최되는 팬미팅에 대한 자세한 정보를 공개했다. + - expected: `⠎⠠⠪⠦⠄⠴⠠⠺⠐⠑⠎⠑⠠⠴⠧⠀⠈⠿⠠⠕⠁⠀⠴⠠` + - actual: `⠎⠠⠪⠦⠄⠴⠠⠺⠑⠧⠻⠎⠑⠠⠴⠧⠀⠈⠿⠠⠕⠁⠀⠴` + - first differing cell (zero-based): 62 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_04.json` #1801: 마이애미는 30일 미국 보스턴 TD가든에서 열린 보스턴과의 2022~2023 미국프로농구(NBA) 동부콘퍼런스 결승 7차전에서 시종일관 압도한 끝에 103-84, 19점 차 대승을 거뒀다. + - expected: `⠘⠥⠠⠪⠓⠾⠀⠴⠰⠠⠠⠞⠙⠲⠫⠊⠵⠝⠠⠎⠀⠳⠐⠟` + - actual: `⠘⠥⠠⠪⠓⠾⠀⠴⠠⠠⠞⠙⠲⠫⠊⠵⠝⠠⠎⠀⠳⠐⠟⠀` + - first differing cell (zero-based): 29 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` ### `standalone_multi_character_uppercase_roman_word` -Of the 62411 candidates, 10493 are the actual `pending_rule_review` subcluster. The other 51918 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 62411 candidates, 10359 are the actual `pending_rule_review` subcluster. The other 52052 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. Mismatch primary-class distribution: -- `comparison_method`: 212 +- `comparison_method`: 213 - `corpus_suspect`: 1093 -- `pending_rule_review`: 10493 -- `unsupported_character_review`: 153 +- `pending_rule_review`: 10359 +- `unsupported_character_review`: 152 Representative `exact` samples: @@ -2643,9 +2626,9 @@ Representative `mismatch` samples: ### `uppercase_alphanumeric_roman_digit_sequence` -Of the 3429 candidates, 1409 are the actual `pending_rule_review` subcluster. The other 2020 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 3429 candidates, 1404 are the actual `pending_rule_review` subcluster. The other 2025 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. -For this output-signature audit, 1518 mismatches were evaluable and 151 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. +For this output-signature audit, 1513 mismatches were evaluable and 151 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. Localized first-difference transitions: @@ -2659,7 +2642,7 @@ Mismatch primary-class distribution: - `comparison_method`: 13 - `corpus_suspect`: 87 -- `pending_rule_review`: 1409 +- `pending_rule_review`: 1404 - `unsupported_character_review`: 9 Representative `exact` samples: @@ -2729,9 +2712,9 @@ Representative `mismatch` samples: ### `uppercase_ascii_run_immediately_after_digit_in_roman_sequence` -Of the 1896 candidates, 737 are the actual `pending_rule_review` subcluster. The other 1159 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 1896 candidates, 733 are the actual `pending_rule_review` subcluster. The other 1163 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. -For this output-signature audit, 794 mismatches were evaluable and 1 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. +For this output-signature audit, 790 mismatches were evaluable and 1 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. Localized first-difference transitions: @@ -2741,7 +2724,7 @@ Mismatch primary-class distribution: - `comparison_method`: 9 - `corpus_suspect`: 41 -- `pending_rule_review`: 737 +- `pending_rule_review`: 733 - `unsupported_character_review`: 7 Representative `exact` samples: @@ -2796,9 +2779,9 @@ Representative `mismatch` samples: ### `uppercase_ascii_run_immediately_after_hyphen_in_roman_sequence` -Of the 952 candidates, 767 are the actual `pending_rule_review` subcluster. The other 185 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 952 candidates, 766 are the actual `pending_rule_review` subcluster. The other 186 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. -For this output-signature audit, 788 mismatches were evaluable and 163 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. +For this output-signature audit, 787 mismatches were evaluable and 163 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. Localized first-difference transitions: @@ -2808,7 +2791,7 @@ Localized first-difference transitions: Mismatch primary-class distribution: - `corpus_suspect`: 12 -- `pending_rule_review`: 767 +- `pending_rule_review`: 766 - `unsupported_character_review`: 9 Representative `exact` samples: @@ -2933,11 +2916,11 @@ Representative `mismatch` samples: Of the 571 candidates, 365 are the actual `pending_rule_review` subcluster. The other 206 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. -For this output-signature audit, 380 mismatches were evaluable and 261 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. +For this output-signature audit, 380 mismatches were evaluable and 262 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. Localized first-difference transitions: -- `U+2834 ⠴ -> U+2800 ⠀`: 259 +- `U+2834 ⠴ -> U+2800 ⠀`: 260 - `U+2834 ⠴ -> U+2820 ⠠`: 2 Mismatch primary-class distribution: @@ -3074,10 +3057,10 @@ These cohorts are defined by both an input boundary and the sentence's actual fi | Cohort | Candidates | Exact controls | Mismatch | Target localized | Reverse | |---|---:|---:|---:|---:|---:| -| `allcaps_roman_run_beginning_with_pure_letter_shortform` | 4120 | 2465 | 1655 | 785 | 47 | -| `uppercase_ascii_run_immediately_after_digit_in_roman_sequence` | 1896 | 1102 | 794 | 0 | 1 | -| `uppercase_ascii_run_immediately_after_hyphen_in_roman_sequence` | 952 | 164 | 788 | 159 | 4 | -| `pure_allcaps_segment_before_hyphen_and_multi_allcaps_segment_after` | 448 | 131 | 317 | 0 | 1 | +| `allcaps_roman_run_beginning_with_pure_letter_shortform` | 4120 | 2477 | 1643 | 785 | 47 | +| `uppercase_ascii_run_immediately_after_digit_in_roman_sequence` | 1896 | 1106 | 790 | 0 | 1 | +| `uppercase_ascii_run_immediately_after_hyphen_in_roman_sequence` | 952 | 165 | 787 | 159 | 4 | +| `pure_allcaps_segment_before_hyphen_and_multi_allcaps_segment_after` | 448 | 132 | 316 | 0 | 1 | ### All-caps shortform prefix at an attached Roman entry @@ -3134,7 +3117,7 @@ Korean rule 29 requires a Roman indicator before Roman text in a Korean sentence | Cohort | Candidates | Exact controls | Mismatch | Pending | Corpus suspect | Localized `⠴ -> blank` | Reverse `blank -> ⠴` | |---|---:|---:|---:|---:|---:|---:|---:| | `roman_hyphenated_word_after_whitespace_following_korean_word` | 361 | 0 | 361 | 350 | 6 | 136 | 0 | -| `roman_parenthetical_headword_after_whitespace_following_korean_word` | 4695 | 3382 | 1313 | 1221 | 39 | 154 | 0 | +| `roman_parenthetical_headword_after_whitespace_following_korean_word` | 4695 | 3407 | 1288 | 1196 | 39 | 154 | 0 | | `korean_prefixed_roman_parenthetical_followed_by_allcaps_hyphen_suffix` | 11 | 0 | 11 | 6 | 5 | 5 | 0 | The whitespace parenthetical-headword cohort also retains 1 localized `⠠ -> ⠴` and 3 localized `⠴ -> ⠰` cases as separate transitions; they are not folded into the target. The exact controls demonstrate that the broad structure is already correct in many sentences, while the attached parenthetical-hyphen cohort has no exact control and includes cases already classified by the stricter rule-34 reference-order contradiction. Consequently none of these measurements authorizes an engine change; they are deterministic pending/corpus-review diagnostics only. @@ -3151,7 +3134,7 @@ The generalized fix makes an explicit `EnterEnglish` event idempotent when final Korean rule 34 (2024 Korean-rules PDF p.29) prints the opening enclosure before Roman entry in `링컨(Lincoln)`: the opening sequence is `⠦⠄⠴`. This output-position cohort finds a closed, non-nested parenthetical whose body begins with an ASCII letter and whose opening does not immediately follow another ASCII letter, then locates its complete current-engine signature without consulting the reference. Its localized boundary includes the two current output cells immediately before the opening plus the first three entry cells, so rule-11 math spacing can be separated from a difference later inside the parenthetical. Direct function-call shapes such as `f(x)` are excluded. The PDF's math rule 6 (p.59), rule 12 (pp.63-64), and rule 45 (p.75) nevertheless leave standalone `(x)` and other Roman-letter parenthetical mathematics as counterexamples, so the surface gate is not an engine-routing predicate. -The cross-cutting input cohort contains 63959 candidates: 52252 exact controls and 11707 mismatches. Mismatch primary classes remain unchanged: 10220 `pending_rule_review`, 1107 `corpus_suspect`, 225 `comparison_method`, and 155 `unsupported_character_review`. Of 1245 evaluable mismatches, 11707 have the first difference at the detected leading-spacing/entry boundary; these include 436 `U+2826 ⠦ -> U+2834 ⠴`, 27 `U+2834 ⠴ -> U+2826 ⠦`, and 325 `U+2826 ⠦ -> U+2800 ⠀` transitions; the exact localized reverse `U+2800 ⠀ -> U+2826 ⠦` occurs 0 times. After all earlier localized cohorts and this cohort are excluded, the raw-to-residual target count is 469 -> 26, the Roman-indicator reverse count is 32 -> 2, and the spacing target/reverse counts are 440 -> 79 and 1 -> 1. The short full-encoder form `웹3(Web3)` emits the PDF opening order as a routing control, while whitespace and quote boundaries reproduce the current Roman-first opening. Representative localized samples, with shard and index, are retained in the generated cluster sample table. Because exact controls are abundant and the PDF does not make this input shape semantically sufficient to exclude mathematics, no engine change or primary reclassification is inferred. The one raw/residual spacing reverse is a Korean-body `△(교육)` layout boundary, not a Roman-parenthetical member, and remains a separate rule-72/layout review. +The cross-cutting input cohort contains 63959 candidates: 52384 exact controls and 11575 mismatches. Mismatch primary classes remain unchanged: 10088 `pending_rule_review`, 1107 `corpus_suspect`, 226 `comparison_method`, and 154 `unsupported_character_review`. Of 1245 evaluable mismatches, 11575 have the first difference at the detected leading-spacing/entry boundary; these include 436 `U+2826 ⠦ -> U+2834 ⠴`, 27 `U+2834 ⠴ -> U+2826 ⠦`, and 325 `U+2826 ⠦ -> U+2800 ⠀` transitions; the exact localized reverse `U+2800 ⠀ -> U+2826 ⠦` occurs 0 times. After all earlier localized cohorts and this cohort are excluded, the raw-to-residual target count is 469 -> 26, the Roman-indicator reverse count is 32 -> 2, and the spacing target/reverse counts are 440 -> 79 and 1 -> 1. The short full-encoder form `웹3(Web3)` emits the PDF opening order as a routing control, while whitespace and quote boundaries reproduce the current Roman-first opening. Representative localized samples, with shard and index, are retained in the generated cluster sample table. Because exact controls are abundant and the PDF does not make this input shape semantically sufficient to exclude mathematics, no engine change or primary reclassification is inferred. The one raw/residual spacing reverse is a Korean-body `△(교육)` layout boundary, not a Roman-parenthetical member, and remains a separate rule-72/layout review. The HCA-style headword-expansion shape described above is not an engine implementation premise. The 2024 PDF's math rule 6 defines parentheses and grouping parentheses, rule 11 defines mathematical-expression spacing, rule 12 covers Roman letters in formulas as well as Korean sentences, and rule 45 shows Roman-letter function notation followed by parentheses. Excluding visible operators, scripts, and nesting narrows this corpus cohort, but the PDF does not make the remaining surface shape sufficient to rule out every mathematical counterexample. The cluster therefore remains conservative pending-review evidence only. @@ -3186,7 +3169,7 @@ The output-localized cohort contains 1022 candidates, 363 exact controls, and 65 Korean rule 29 places one Roman indicator/terminator pair around consecutive Roman items, with `Los Angeles` and `Table of Contents` as its printed multiword examples. Rule 34 separately omits the Roman terminator when Roman text is enclosed by quotation marks or parentheses. Neither printed rule states whether a later Roman run after the enclosure, intervening punctuation, and whitespace is a continuation of that section or a fresh section. The input gate therefore detects only the structural boundary; it does not label the later run as semantically new. -The cohort contains 1093 candidates, 142 exact controls, and 951 mismatches. Existing mismatch primary classes are preserved: 617 `pending_rule_review`, 327 `corpus_suspect`, and 7 `unsupported_character_review`. Of 951 evaluable mismatches, 251 are output-localized to the current later-run signature plus its one leading boundary cell: 245 `U+2834 ⠴ -> U+2830 ⠰` and 0 `U+2830 ⠰ -> U+2834 ⠴`. The target counted 347 raw and 333 residual cases before this cohort; it is now 47 final residual. Across raw and final residual maps, `U+2830 ⠰ -> U+2834 ⠴` is 3 -> 1. Exact controls coexist with both directions, and 321 mismatches are already independently identified corpus contradictions. Without a printed fresh-entry example or semantic enclosure model, changing continuation state would be reference-fitting; this remains a deterministic pending/corpus-review cohort only. Representative shard/index samples are retained above. +The cohort contains 1093 candidates, 142 exact controls, and 951 mismatches. Existing mismatch primary classes are preserved: 617 `pending_rule_review`, 327 `corpus_suspect`, and 7 `unsupported_character_review`. Of 951 evaluable mismatches, 251 are output-localized to the current later-run signature plus its one leading boundary cell: 245 `U+2834 ⠴ -> U+2830 ⠰` and 0 `U+2830 ⠰ -> U+2834 ⠴`. The target counted 347 raw and 333 residual cases before this cohort; it is now 47 final residual. Across raw and final residual maps, `U+2830 ⠰ -> U+2834 ⠴` is 2 -> 0. Exact controls coexist with both directions, and 321 mismatches are already independently identified corpus contradictions. Without a printed fresh-entry example or semantic enclosure model, changing continuation state would be reference-fitting; this remains a deterministic pending/corpus-review cohort only. Representative shard/index samples are retained above. ### Uppercase Roman runs containing `ED` @@ -3196,7 +3179,7 @@ The cohort contains 816 candidates, 319 exact controls, and 497 mismatches. Exis ### Uppercase Roman runs containing `ST` -UEB 10.12.1 (2024 UEB PDF pp.191-192, printed pp.163-164) says not to use a contraction when letters within an abbreviation or acronym are known to be pronounced separately, but to use the contraction in case of doubt. Rule 10.12.2 requires contractions in other abbreviations and acronyms. Consequently an uppercase `ST` surface alone cannot determine whether `s` + `t` or the `st` groupsign is required. The current cohort contains 1479 candidates, 685 exact controls, and 794 mismatches; primary classes remain 741 `pending_rule_review`, 35 `corpus_suspect`, 13 `comparison_method`, and 5 `unsupported_character_review`. Of 794 evaluable mismatches, 443 are localized to the detected current-engine run: 435 `U+280E ⠎ -> U+280C ⠌` and 1 `U+280C ⠌ -> U+280E ⠎`. The target's raw-to-residual count is 448 -> 13, and the reverse is 2 -> 1. Exact controls such as `STAYG`, `KAIST`, `DGIST`, and `POSTECH` coexist with localized mismatches such as `WSTS`, `HUST`, `OST`, and `USTR`; representative shard/index samples are preserved in the cluster table. This lexical/pronunciation distinction cannot be inferred from the input-only spelling, so no engine change or primary reclassification is made. +UEB 10.12.1 (2024 UEB PDF pp.191-192, printed pp.163-164) says not to use a contraction when letters within an abbreviation or acronym are known to be pronounced separately, but to use the contraction in case of doubt. Rule 10.12.2 requires contractions in other abbreviations and acronyms. Consequently an uppercase `ST` surface alone cannot determine whether `s` + `t` or the `st` groupsign is required. The current cohort contains 1479 candidates, 686 exact controls, and 793 mismatches; primary classes remain 740 `pending_rule_review`, 35 `corpus_suspect`, 13 `comparison_method`, and 5 `unsupported_character_review`. Of 793 evaluable mismatches, 443 are localized to the detected current-engine run: 435 `U+280E ⠎ -> U+280C ⠌` and 1 `U+280C ⠌ -> U+280E ⠎`. The target's raw-to-residual count is 448 -> 13, and the reverse is 2 -> 1. Exact controls such as `STAYG`, `KAIST`, `DGIST`, and `POSTECH` coexist with localized mismatches such as `WSTS`, `HUST`, `OST`, and `USTR`; representative shard/index samples are preserved in the cluster table. This lexical/pronunciation distinction cannot be inferred from the input-only spelling, so no engine change or primary reclassification is made. ### Attached Roman segments joined by ampersand @@ -3206,7 +3189,7 @@ The baseline was 802 candidates / 0 exact / 802 mismatch, preserving 776 `pendin The implemented gate shares the analyzer's complete-run predicate: one or more non-empty ASCII-letter segments joined directly by `&`, with non-alphanumeric outer boundaries. It keeps the existing Roman mode open and suppresses only rule 71's redundant wrapper around that ampersand. Spaced `Marks & Spencer`, Korean `가&나`, empty segments, and outer digit continuations remain outside the gate. Official full-encoder controls `AT&T` and `B&B` pass, and the Korean rule-71 spaced example `종이접기 & 클레이아트` retains its independent `⠴⠈⠯⠲` section. -After the change the same cohort contains 802 candidates, 284 exact and 518 mismatch. Current mismatch primary classes remain evaluator-owned: 491 `pending_rule_review`, 10 `corpus_suspect`, 14 `unsupported_character_review`, and 3 `comparison_method`. The localizer evaluates all 518 remaining mismatches and finds 0 target-localized cases (0 `U+2808 ⠈ -> U+2832 ⠲`); current raw-to-residual target count is 25 -> 23, while `U+2832 ⠲ -> U+2808 ⠈` remains 0 raw / 0 residual. Cohort exact increases by 273 and corpus-wide exact increases by the same 273, from 67,442 to 67,715. Because every changed input must satisfy this shared predicate and the baseline had no exact member, this boundary has no exact regression. The remaining 529 candidates differ elsewhere or retain an existing comparison, corpus-suspect, unsupported, or pending cause. Representative shard/index samples are retained above. +After the change the same cohort contains 802 candidates, 285 exact and 517 mismatch. Current mismatch primary classes remain evaluator-owned: 490 `pending_rule_review`, 10 `corpus_suspect`, 14 `unsupported_character_review`, and 3 `comparison_method`. The localizer evaluates all 517 remaining mismatches and finds 0 target-localized cases (0 `U+2808 ⠈ -> U+2832 ⠲`); current raw-to-residual target count is 25 -> 23, while `U+2832 ⠲ -> U+2808 ⠈` remains 0 raw / 0 residual. Cohort exact increases by 273 and corpus-wide exact increases by the same 273, from 67,442 to 67,715. Because every changed input must satisfy this shared predicate and the baseline had no exact member, this boundary has no exact regression. The remaining 529 candidates differ elsewhere or retain an existing comparison, corpus-suspect, unsupported, or pending cause. Representative shard/index samples are retained above. ### Ampersand before an attached ASCII Roman segment @@ -3216,54 +3199,60 @@ The diagnostic checkpoint baseline was 30 candidates / 0 corpus exact / 30 misma The implemented rule is limited to an ampersand followed by a complete attached ASCII-letter segment, with no left ASCII alphanumeric or adjacent ampersand and no trailing digit continuation. Rule 71 opens one Roman section before `&`; rule 29 now leaves it open for the attached letters. It does not name a corpus input or inspect a reference. After the change, the cohort has 30 candidates, 12 exact and 18 mismatch; the corpus-wide exact total rises by the same 12 cases, from 68,175 to 68,187, so no exact regression occurs inside or outside this gate. The 16 former exit/re-entry transitions disappear; 12 become exact and 4 remain mismatches at a different PDF-conflicting boundary. Existing mismatch primary classes remain 14 `pending_rule_review`, 4 `corpus_suspect`, 0 `comparison_method`, and 0 `unsupported_character_review`. -Of 18 current evaluable mismatches, 4 are localized to the occurrence-specific entry signature: 3 `U+2808 ⠈ -> U+2834 ⠴` where a parenthesized `&TEAM` reference omits Rule 71's required Roman indicator, and 1 `U+2834 ⠴ -> U+2820 ⠠` where a `드림&Dream` reference inserts another Roman indicator inside the same continuous section. Their raw-to-residual counts are 6 -> 3 and 70 -> 64; the corresponding reverse maps are 0 -> 0 and 53 -> 39. These four cases remain conservative corpus/PDF-reference review rather than widening or undoing the rule. Exact samples such as `&TEAM`, `과학&ICT`, and `한국&K리츠`, with shard/index above, are current controls. Primary classifications are never changed by this cohort. +Of 18 current evaluable mismatches, 4 are localized to the occurrence-specific entry signature: 3 `U+2808 ⠈ -> U+2834 ⠴` where a parenthesized `&TEAM` reference omits Rule 71's required Roman indicator, and 1 `U+2834 ⠴ -> U+2820 ⠠` where a `드림&Dream` reference inserts another Roman indicator inside the same continuous section. Their raw-to-residual counts are 6 -> 3 and 70 -> 64; the corresponding reverse maps are 0 -> 0 and 52 -> 38. These four cases remain conservative corpus/PDF-reference review rather than widening or undoing the rule. Exact samples such as `&TEAM`, `과학&ICT`, and `한국&K리츠`, with shard/index above, are current controls. Primary classifications are never changed by this cohort. ### Spaced comma between ASCII digit runs This output-localized cohort requires a comma immediately after an ASCII digit, one or more following whitespace characters, and another ASCII digit. Korean rule 41 (2024 Korean-rules PDF p.33, printed p.27) assigns `⠂` only when the comma is *attached* between digits, as in the exact control `9,375명`; rule 49 (PDF pp.37-38, printed pp.31-32) assigns the ordinary Korean comma `⠐`, illustrated by `근면, 검소, 협동은 ...`. Rule 33 (PDF p.28, printed p.22) independently keeps Korean punctuation at Roman-to-Korean boundaries. By contrast, official UEB 7 (2024 UEB PDF p.103, printed p.75) defines its prose comma as `⠂`, and UEB 6.2.1 (PDF p.94, printed p.66) retains `⠂` inside attached numeric forms such as `3,500`. Those two surfaces are negative controls and are excluded by this gate. -Before any engine change, the cohort has 217 candidates / 7 exact / 210 mismatch. Existing mismatch primaries remain 209 `pending_rule_review`, 0 `corpus_suspect`, 0 `comparison_method`, and 1 `unsupported_character_review`. The occurrence-specific locator encodes the real prefix immediately before each comma and claims only its next emitted cell. Of 210 evaluable mismatches, 177 localize there, including 177 `U+2810 ⠐ -> U+2802 ⠂` and 0 `U+2802 ⠂ -> U+2810 ⠐`. Across all pending cases, the raw-to-residual counts after adding this cohort are 212 -> 12 for the target and 12 -> 10 for the reverse. No primary class is changed and no implementation result is inferred from the reference outputs at this diagnostic checkpoint. +The diagnostic baseline was 217 candidates / 7 exact / 210 mismatch, with 177 occurrence-specific `U+2810 ⠐ -> U+2802 ⠂` first differences and no localized reverse. Rule 41 had looked through `remaining_words`, incorrectly treating whitespace as if the following digit were attached. The implementation now inspects only the next character in the same token. It neither names a corpus input nor consults expected output; attached numbers and UEB punctuation remain owned by their existing routes. + +After the correction, the cohort has 217 candidates / 181 exact / 36 mismatch. Existing mismatch primaries remain 35 `pending_rule_review`, 0 `corpus_suspect`, 1 `comparison_method`, and 0 `unsupported_character_review`. Of 36 evaluable current mismatches, 0 localize to the comma cell: 0 `U+2810 ⠐ -> U+2802 ⠂` and 0 `U+2802 ⠂ -> U+2810 ⠐`. The cohort gains 174 exact cases. Across all pending cases, the current raw-to-residual counts are 24 -> 6 for the target and 20 -> 14 for the reverse. The standard controls `9,375명`, `창세기 12,1-9`, and `근면, 검소, 협동은 ...` retain their PDF cells. No primary class is changed by the cohort. ### ASCII/Roman-tail comma before a digit-led Korean token This companion cohort is disjoint from the preceding digit-comma gate: the comma is immediately preceded by an ASCII letter, followed after whitespace by a token that starts with a digit and contains Korean script. Korean rule 33 (2024 Korean-rules PDF p.28, printed p.22) says that punctuation with different UEB and Korean cells, including comma, is written as Korean punctuation at a Roman-to-Korean boundary and suppresses the Roman terminator. Rule 49 supplies `⠐`; UEB 7 supplies `⠂` only while the comma remains inside English text. Requiring Korean script in the right token is therefore the negative control against reclassifying an English date or number sequence from surface punctuation alone. -Before the shared rule-41 boundary correction, this cohort has 58 candidates / 0 exact / 58 mismatch. Existing mismatch primaries remain 56 `pending_rule_review`, 2 `corpus_suspect`, 0 `comparison_method`, and 0 `unsupported_character_review`. Of 58 evaluable mismatches, 23 have the comma-cell transition inside the independently encoded complete boundary signature: 23 `U+2810 ⠐ -> U+2802 ⠂` and 0 `U+2802 ⠂ -> U+2810 ⠐`. The detector and localizer do not read expected output to choose a route, and membership does not change a primary class. +The diagnostic baseline was 58 candidates / 0 exact / 58 mismatch. Of those, 23 had `U+2810 ⠐ -> U+2802 ⠂` at the comma inside the independently encoded complete boundary signature and none had the reverse. The same rule-41 correction removes the cross-token ASCII-letter lookup; rule 33 and the existing English-symbol route then choose the punctuation from the actual surrounding scripts. + +After the correction, this cohort has 58 candidates / 3 exact / 55 mismatch. Existing mismatch primaries remain 53 `pending_rule_review`, 2 `corpus_suspect`, 0 `comparison_method`, and 0 `unsupported_character_review`. Of 55 evaluable current mismatches, 22 localize to the comma-cell signature: 18 `U+2810 ⠐ -> U+2802 ⠂` and 4 `U+2802 ⠂ -> U+2810 ⠐`. Three cases become exact. The official rule-33 `KTX, 새마을호` boundary and UEB prose comma remain independent standard controls. The detector and localizer do not read expected output to choose a route, and membership does not change a primary class. ### Percent-point unit list comma -Korean rule 69 attachment 2 (2024 Korean-rules PDF p.50, printed p.44) explicitly defines `%p` as the percent-point unit. This cohort requires two complete numeric `%p` tokens separated by comma plus whitespace, so rule 49's ordinary Korean comma is the punctuation boundary; it does not infer arbitrary ASCII suffixes as units. At the diagnostic baseline it has 7 candidates / 0 exact / 7 mismatch, preserving 7 `pending_rule_review`, 0 `corpus_suspect`, 0 `comparison_method`, and 0 `unsupported_character_review` mismatch primaries. Of 7 evaluable mismatches, 1 localize to the independently encoded complete unit pair: 1 `U+2810 ⠐ -> U+2802 ⠂` and 0 `U+2802 ⠂ -> U+2810 ⠐`. This scope audit was added after the exact-set comparison identified the common `%p, ... %p` structure; the engine still contains only the general rule-41 same-token boundary, not a `%p` special case. +Korean rule 69 attachment 2 (2024 Korean-rules PDF p.50, printed p.44) explicitly defines `%p` as the percent-point unit. This cohort requires two complete numeric `%p` tokens separated by comma plus whitespace, so rule 49's ordinary Korean comma is the punctuation boundary; it does not infer arbitrary ASCII suffixes as units. The diagnostic baseline was 7 candidates / 0 exact / 7 mismatch, with one localized `U+2810 ⠐ -> U+2802 ⠂` and no reverse. + +After the correction, this cohort has 7 candidates / 5 exact / 2 mismatch, preserving 2 `pending_rule_review`, 0 `corpus_suspect`, 0 `comparison_method`, and 0 `unsupported_character_review` mismatch primaries. Of 2 evaluable current mismatches, 0 localize to the independently encoded complete unit pair: 0 `U+2810 ⠐ -> U+2802 ⠂` and 0 `U+2802 ⠂ -> U+2810 ⠐`. Five cases become exact; the other two retain independent earlier differences. One of those five is also in the Roman-tail cohort, leaving four disjoint `%p` gains. Thus +174 in the numeric-list cohort, +3 in the Roman-tail cohort, and +4 disjoint here account for all +181 corpus exact gains, with zero exact-set regressions. The engine contains only the general rule-41 same-token boundary, not a `%p` special case. -Current uppercase-Roman hyphen-digits measurement: 571 candidates, 191 exact controls, 380 mismatches, 365 members in the actual `pending_rule_review` subcluster, and 261/380 evaluable mismatches whose first difference is inside the target run plus its entry boundary. It remains distinct from parenthesized digits and headword expansions; no engine change is inferred. +Current uppercase-Roman hyphen-digits measurement: 571 candidates, 191 exact controls, 380 mismatches, 365 members in the actual `pending_rule_review` subcluster, and 262/380 evaluable mismatches whose first difference is inside the target run plus its entry boundary. It remains distinct from parenthesized digits and headword expansions; no engine change is inferred. -Current uppercase alphanumeric Roman-digit sequence measurement: 3429 candidates, 1911 exact controls, 1518 mismatches, 1409 members in the actual `pending_rule_review` subcluster, and 151/1518 evaluable mismatches whose first difference is localized to the current entry cells. The target `⠴ -> blank` occurs 93 times and the reverse occurs 0 times. Korean rule 35 (2024 Korean-rules PDF pp.29-30, printed pp.23-24) explicitly treats `MP3`, `A4`, `KF94`, and `D-100` as Roman-number sequences. Math rules 11/12 (PDF pp.63-65, printed pp.57-59) separately route mathematical Roman notation without the prose Roman indicator and with two-cell spacing. Official UEB 6.5.1-6.5.2 (2024 UEB PDF p.96, printed p.68) determines numeric/grade-1 continuation only after the surrounding mode has been chosen. Thus surfaces such as `A-3` cannot be assigned identifier or variable semantics from this structure alone. Exact controls and both directions are retained; no engine change is inferred. +Current uppercase alphanumeric Roman-digit sequence measurement: 3429 candidates, 1916 exact controls, 1513 mismatches, 1404 members in the actual `pending_rule_review` subcluster, and 151/1513 evaluable mismatches whose first difference is localized to the current entry cells. The target `⠴ -> blank` occurs 93 times and the reverse occurs 0 times. Korean rule 35 (2024 Korean-rules PDF pp.29-30, printed pp.23-24) explicitly treats `MP3`, `A4`, `KF94`, and `D-100` as Roman-number sequences. Math rules 11/12 (PDF pp.63-65, printed pp.57-59) separately route mathematical Roman notation without the prose Roman indicator and with two-cell spacing. Official UEB 6.5.1-6.5.2 (2024 UEB PDF p.96, printed p.68) determines numeric/grade-1 continuation only after the surrounding mode has been chosen. Thus surfaces such as `A-3` cannot be assigned identifier or variable semantics from this structure alone. Exact controls and both directions are retained; no engine change is inferred. -Current consecutive ASCII-Roman word-boundary measurement: 4679 candidates, 2132 exact controls, 2547 mismatches, 2413 members in the actual `pending_rule_review` subcluster, and 23/2547 evaluable mismatches whose first difference is localized to the current boundary cell. The target reference blank versus current terminator (`⠀ -> ⠲`) occurs 23 times; the exact reverse (`⠲ -> ⠀`) occurs 0 times. Korean rule 29 (2024 Korean-rules PDF p.26, printed p.20) explicitly treats consecutive Roman items as one Roman section, with one Roman indicator before the first item and one terminator after the last; its printed `Los Angeles` and `Table of Contents` examples are exact controls. Rule 34 (PDF p.29, printed p.23) separately omits the terminator before a closing enclosure, while official UEB 9.7.1 (2024 UEB PDF p.137, printed p.109) prints the multiword prose enclosure `plays (such as Romeo and Juliet)` with ordinary internal spaces and nested closing punctuation. Together they support preserving a complete letter-and-space Roman parenthetical as prose, but not a fragment containing a function-call opener, digit, operator, or nested bracket. The input-only gate does not decide whether punctuation-separated text such as `ESS /VPP` continues the same section, so that variant remains outside this cohort. Primary classes are preserved. Before the narrow token-routing change this cohort was 4,679 candidates / 2,058 exact / 2,621 mismatch, with 128 localized `⠀ -> ⠲` and zero reverse. The cause was the final `Letters)` token of a whitespace-split Roman parenthetical being rerouted as math, which made the preceding Roman word terminate early and introduced an extra blank. After excluding only a backwards-verified complete Roman parenthetical tail from math routing, the cohort is 2,132 exact / 2,547 mismatch, with 23 target-localized and zero reverse; corpus-wide exact likewise rises by 74, from 68,101 to 68,175. The 105 removed localized boundaries include 74 newly exact cases and 31 cases that still differ elsewhere. Remaining targets such as `SYNO PEM-1`, `ACE Fair(2020)`, and `Mnet K-POP` do not satisfy the complete-parenthetical gate and remain separate residuals rather than widening this rule. +Current consecutive ASCII-Roman word-boundary measurement: 4679 candidates, 2134 exact controls, 2545 mismatches, 2411 members in the actual `pending_rule_review` subcluster, and 23/2545 evaluable mismatches whose first difference is localized to the current boundary cell. The target reference blank versus current terminator (`⠀ -> ⠲`) occurs 23 times; the exact reverse (`⠲ -> ⠀`) occurs 0 times. Korean rule 29 (2024 Korean-rules PDF p.26, printed p.20) explicitly treats consecutive Roman items as one Roman section, with one Roman indicator before the first item and one terminator after the last; its printed `Los Angeles` and `Table of Contents` examples are exact controls. Rule 34 (PDF p.29, printed p.23) separately omits the terminator before a closing enclosure, while official UEB 9.7.1 (2024 UEB PDF p.137, printed p.109) prints the multiword prose enclosure `plays (such as Romeo and Juliet)` with ordinary internal spaces and nested closing punctuation. Together they support preserving a complete letter-and-space Roman parenthetical as prose, but not a fragment containing a function-call opener, digit, operator, or nested bracket. The input-only gate does not decide whether punctuation-separated text such as `ESS /VPP` continues the same section, so that variant remains outside this cohort. Primary classes are preserved. Before the narrow token-routing change this cohort was 4,679 candidates / 2,058 exact / 2,621 mismatch, with 128 localized `⠀ -> ⠲` and zero reverse. The cause was the final `Letters)` token of a whitespace-split Roman parenthetical being rerouted as math, which made the preceding Roman word terminate early and introduced an extra blank. After excluding only a backwards-verified complete Roman parenthetical tail from math routing, the cohort is 2,132 exact / 2,547 mismatch, with 23 target-localized and zero reverse; corpus-wide exact likewise rises by 74, from 68,101 to 68,175. The 105 removed localized boundaries include 74 newly exact cases and 31 cases that still differ elsewhere. Remaining targets such as `SYNO PEM-1`, `ACE Fair(2020)`, and `Mnet K-POP` do not satisfy the complete-parenthetical gate and remain separate residuals rather than widening this rule. Current single-capital parenthesized-digits measurement: 1361 candidates, 0 exact controls, 1361 mismatches, 1357 members in the actual `pending_rule_review` subcluster, and 1342/1361 evaluable mismatches whose first difference is inside the target run plus its entry boundary. No engine change is inferred from the ambiguous prose/function surface form. Current mixed Roman+Korean boundary before uppercase headword-expansion measurement: 10 candidates, 0 exact controls, 10 mismatches, 10 members in the actual `pending_rule_review` subcluster, and 8/10 evaluable mismatches whose first difference is localized to the later headword's entry boundary/output. The detector cannot be satisfied by the earlier Roman entry. No HCA-shaped engine routing rule is introduced. -Current compact numeric+ASCII-suffix measurement: 2975 candidates, 1872 exact controls, 1103 mismatches, 1032 members in the actual `pending_rule_review` subcluster, and 254/1103 evaluable mismatches whose first difference is inside the complete current-engine output signature or its immediate entry boundary. Rule 40 requires the numeric indicator and rule 69 requires Roman indicators around a Roman-written unit, but the input shape alone cannot prove that every ASCII suffix is a unit. +Current compact numeric+ASCII-suffix measurement: 2975 candidates, 1877 exact controls, 1098 mismatches, 1027 members in the actual `pending_rule_review` subcluster, and 251/1098 evaluable mismatches whose first difference is inside the complete current-engine output signature or its immediate entry boundary. Rule 40 requires the numeric indicator and rule 69 requires Roman indicators around a Roman-written unit, but the input shape alone cannot prove that every ASCII suffix is a unit. | ASCII suffix | Candidates | Exact | Mismatch | Localized first diff | |---|---:|---:|---:|---:| -| `m` | 364 | 261 | 103 | 22 | -| `km` | 308 | 250 | 58 | 3 | -| `G` | 237 | 164 | 73 | 3 | -| `kg` | 209 | 175 | 34 | 6 | +| `m` | 364 | 262 | 102 | 22 | +| `km` | 308 | 251 | 57 | 2 | +| `G` | 237 | 165 | 72 | 3 | +| `kg` | 209 | 176 | 33 | 4 | | `D` | 167 | 115 | 52 | 5 | | `p` | 140 | 20 | 120 | 84 | | `g` | 138 | 100 | 38 | 8 | | `t` | 128 | 86 | 42 | 17 | | `M` | 104 | 80 | 24 | 6 | | `cm` | 69 | 52 | 17 | 0 | -| `B` | 62 | 37 | 25 | 0 | -| `GB` | 61 | 27 | 34 | 4 | +| `B` | 62 | 38 | 24 | 0 | +| `GB` | 61 | 28 | 33 | 4 | | `GWh` | 57 | 42 | 15 | 8 | | `ha` | 53 | 18 | 35 | 0 | | `TV` | 52 | 38 | 14 | 1 | -| `S` | 45 | 29 | 16 | 0 | +| `S` | 45 | 30 | 15 | 0 | | `GW` | 44 | 35 | 9 | 6 | | `X` | 39 | 14 | 25 | 2 | | `bp` | 39 | 3 | 36 | 18 | @@ -3276,27 +3265,27 @@ Current compact numeric+ASCII-suffix measurement: 2975 candidates, 1872 exact co Entry-boundary pre-fix baseline: 2,975 candidates, 1,649 exact controls, 1,326 mismatches, 1,259 pending members, and 356 localized first differences; the dominant reference number-sign versus current space transition accounted for 296 cases. Rules 68 and 69 already accept semantic Unicode compatibility-unit forms. The implementation derives compact ASCII spellings only from their all-letter NFKC decompositions, reuses the owning rule's PDF-defined cells, chooses the longest complete spelling, and rejects partial suffix matches. It does not extend recognition to separated English words or arbitrary corpus suffixes. The same cohort now has 1,759 exact controls, 1,216 mismatches, 1,148 pending members, and 261 localized first differences; corpus-wide exact matches moved from 66,436 to 66,546 (+110). Rule 69's printed `160㎎/㎗` example directly controls `160mg`; the additional full-encoder `240mg`/`240㎎` pair proves that recognition is invariant to the numeric value and also passes. Rule 68's printed `10,000㎡는 1㏊이다` example controls the `ha` spelling through U+33CA's NFKC decomposition; `15.2ha`/`15.2㏊` now passes without a spelling-specific output branch. The 53-case `ha` suffix control moved from 0 exact to 18 exact; its remaining 35 mismatches have independent later or surrounding differences. A full U+3300..U+33FF owner audit found 73 Rules 68/69 glyphs with pure-ASCII NFKC decompositions, 73 distinct spellings, and therefore no current duplicate-spelling owner collision. Production nevertheless groups every owner before resolution and excludes a spelling if any owner cells differ; a synthetic collision test proves that this is not first-wins behavior. An exhaustive test also compares every one of the 73 derived spellings with every owner glyph at both the unit-cell and full-encoder boundaries in a neutral Korean measurement context. That audit exposed the pre-existing explicit `cal` mapping's missing ordinary terminator; rule 69 requires the terminator, while the existing slash-boundary function removes it for the printed `cal/㎠/min` context. Restoring the ordinary terminator and extending that slash-continuation boundary made the audit pass without changing the corpus-wide 66,546 exact total. Ambiguous pure-English inputs remain on UEB: the standard controls `3m` and `4.m` are retained and pass, rather than being globally forced into a Korean unit route. -Current rule-69 ASCII-unit punctuation-boundary measurement: 440 candidates, 341 exact controls, 99 mismatches, 94 members in the actual `pending_rule_review` subcluster, and 10/99 evaluable mismatches whose first difference is localized to the unit-plus-punctuation output signature. PDF rule 69 requires a Roman terminator after a Roman-written unit in the ordinary case, while rules 33/34 omit it at the listed punctuation or enclosing-mark boundary; the rule 46 PDF example `체중(kg)` is the minimal parenthesized-unit control. Membership is restricted to rule-69 spellings already supported by the engine and does not infer unit semantics for arbitrary ASCII suffixes. +Current rule-69 ASCII-unit punctuation-boundary measurement: 440 candidates, 343 exact controls, 97 mismatches, 92 members in the actual `pending_rule_review` subcluster, and 16/97 evaluable mismatches whose first difference is localized to the unit-plus-punctuation output signature. PDF rule 69 requires a Roman terminator after a Roman-written unit in the ordinary case, while rules 33/34 omit it at the listed punctuation or enclosing-mark boundary; the rule 46 PDF example `체중(kg)` is the minimal parenthesized-unit control. Membership is restricted to rule-69 spellings already supported by the engine and does not infer unit semantics for arbitrary ASCII suffixes. Analyzer pre-fix baseline: 440 candidates, 0 exact controls, 440 mismatches, 435 pending members, and 9 signature-local first differences. After the generalized rule-33/34 boundary override and the matching non-math routing guard, the same cohort has 325 exact controls and 115 mismatches. Corpus-wide exact matches moved from 66,039 to 66,436 (+397); the additional gains are applications of the same boundary rule outside this strict ASCII detector, including compatibility-unit forms. The complete standard suite remains 5,141/5,141. -Current decimal-point measurement: 4546 candidates, 3383 exact controls, 1163 mismatches, 1106 members in the actual `pending_rule_review` subcluster, and 260/1163 evaluable mismatches whose first difference is inside the complete decimal-containing word's current-engine signature. Hangeul rules 43 and 48 keep an ASCII point between digits in the numeric sequence and encode it as the decimal point; rules 35 and 69 supply controls for adjacent Roman-number chains and Roman units. This is an implementation-candidate audit, not permission to specialize on a corpus reference. +Current decimal-point measurement: 4546 candidates, 3394 exact controls, 1152 mismatches, 1095 members in the actual `pending_rule_review` subcluster, and 262/1152 evaluable mismatches whose first difference is inside the complete decimal-containing word's current-engine signature. Hangeul rules 43 and 48 keep an ASCII point between digits in the numeric sequence and encode it as the decimal point; rules 35 and 69 supply controls for adjacent Roman-number chains and Roman units. This is an implementation-candidate audit, not permission to specialize on a corpus reference. Analyzer checkpoint `ed20f99` recorded the pre-fix baseline as 4,546 candidates, 2,584 exact controls, 1,962 mismatches, 1,905 pending members, and 875 localized first differences. Its dominant localized transition was reference decimal point `U+2832 ⠲` versus current Roman indicator `U+2834 ⠴` in 647 cases. After the generalized rule-43/48 guard, that transition is absent: 525 cases become exact and the other corrected prefixes expose later independent mismatches. Current all-caps `OU` measurement: 1816 candidates, 64 exact controls, 1752 mismatches, 1666 members in the actual `pending_rule_review` subcluster, and 1332/1752 evaluable mismatches whose first difference is inside the current-engine output signature for the detected run. This is a pronunciation-sensitive UEB review cohort, not an engine routing rule. -Current standalone-uppercase measurement: 62411 candidates, 50460 exact controls, 11951 mismatches, and 10493 members in the actual `pending_rule_review` subcluster. Its high frequency does not make it causal: the same input shape is exact in many cases, and a sentence containing the shape may first differ at another Roman, numeric, or punctuation structure. No engine change is inferred from this cohort. +Current standalone-uppercase measurement: 62411 candidates, 50594 exact controls, 11817 mismatches, and 10359 members in the actual `pending_rule_review` subcluster. Its high frequency does not make it causal: the same input shape is exact in many cases, and a sentence containing the shape may first differ at another Roman, numeric, or punctuation structure. No engine change is inferred from this cohort. -Current Korean-prefixed all-caps parenthetical measurement: 54492 candidates, 45032 exact controls, 9460 mismatches, and 8159 members in the actual `pending_rule_review` subcluster. This is a semantic-collision audit, not an engine routing rule; no implementation change is inferred from its reference outputs. +Current Korean-prefixed all-caps parenthetical measurement: 54492 candidates, 45148 exact controls, 9344 mismatches, and 8043 members in the actual `pending_rule_review` subcluster. This is a semantic-collision audit, not an engine routing rule; no implementation change is inferred from its reference outputs. -Current rule-34 opening-order measurement: 64382 structural candidates, 52855 exact controls, 11527 mismatches, and 1111/11527 evaluable mismatches whose first difference is inside the current engine's Korean opening-parenthesis cells. Only the 1110 localized first-cell transitions have the reference/current order `⠴` versus `⠦`. After requiring the complete reference `⠴⠐⠣` versus current/PDF `⠦⠄⠴` three-cell signature and preserving higher-priority comparison classifications, 1107 are classified as `corpus_suspect`; mere coexistence with a Korean-prefixed Roman annotation does not change a primary class. +Current rule-34 opening-order measurement: 64382 structural candidates, 52990 exact controls, 11392 mismatches, and 1111/11392 evaluable mismatches whose first difference is inside the current engine's Korean opening-parenthesis cells. Only the 1110 localized first-cell transitions have the reference/current order `⠴` versus `⠦`. After requiring the complete reference `⠴⠐⠣` versus current/PDF `⠦⠄⠴` three-cell signature and preserving higher-priority comparison classifications, 1107 are classified as `corpus_suspect`; mere coexistence with a Korean-prefixed Roman annotation does not change a primary class. Current all-caps Roman middle-dot measurement: 97 candidates, 0 exact controls, 97 mismatches, and 96 members in the actual `pending_rule_review` subcluster. This cross-cutting cohort preserves every primary class and is not an engine routing rule. Current Roman-before-middle-dot boundary measurement: 577 candidates, 0 exact controls, 577 mismatches, 565 members in the actual `pending_rule_review` subcluster, and 428/577 evaluable mismatches whose first difference is localized to the one current terminator immediately before the middle dot. The locator encodes each real input prefix ending at the dot, so identifier state such as `K-ICS·...` is retained without consulting expected. This raises localized target coverage from 342 to 417 and reduces the then-leading residual `⠐ -> ⠲` transition from 297 to 223. Rules 29, 33, and 50 support the current terminator path but do not support the localized reference omission; no engine change or primary-class rewrite is inferred. -Current attached Roman-to-Korean boundary measurement: 17693 candidates, 12939 exact controls, 4754 mismatches, 3922 members in the actual `pending_rule_review` subcluster, and 0/4754 evaluable mismatches whose first difference is localized to the current boundary marker. The localized target `⠲ -> ⠸` occurs 0 times and the reverse `⠸ -> ⠲` occurs 0 times. Korean rule 29 (2024 Korean-rules PDF p.26, printed p.20) requires a Roman terminator around Roman text in a Korean sentence, whereas rule 39 (PDF pp.31-32, printed pp.25-26) applies the Korean opening/closing markers only when Roman text is the sentence's main language. Among exact candidates, 10971 expose the current rule-29 terminator at such a boundary and 0 expose the current rule-39 opening. Its printed controls include both an English sentence (`What is 김치 in English?`) and the address `www.대통령.kr` inside a Korean sentence. Official UEB 2.4.7 (2024 UEB PDF p.41, printed p.13) confirms that a UEB mode does not extend through a switch to another braille code, but does not choose whether this Korean boundary belongs to a Korean-main or Roman-main context. Therefore the attached script boundary alone cannot distinguish ordinary Korean prose from an embedded Roman-domain context. Primary classes are preserved and no engine change is inferred without a narrower input-derived dominance gate. At diagnostic checkpoint `57c4608`, this same cohort was 17,693 candidates / 12,724 exact / 4,969 mismatch, with 268 localized `⠲ -> ⠸`, zero reverse, 10,780 exact rule-29 markers, and zero exact rule-39 markers. After narrowing the engine by dominance and the PDF domain shape, it is 12,924 exact / 4,769 mismatch with no localized target or reverse; corpus-wide exact rises from 67,715 to 68,101 (+386). The remaining 1 mismatch with a current rule-39 opening is a list-heavy Korean sentence whose ASCII-leading brand tokens satisfy the mechanical word-count majority; its first difference is not localized to this boundary, so it remains a semantic pending control rather than grounds for another engine branch. +Current attached Roman-to-Korean boundary measurement: 17693 candidates, 12966 exact controls, 4727 mismatches, 3895 members in the actual `pending_rule_review` subcluster, and 0/4727 evaluable mismatches whose first difference is localized to the current boundary marker. The localized target `⠲ -> ⠸` occurs 0 times and the reverse `⠸ -> ⠲` occurs 0 times. Korean rule 29 (2024 Korean-rules PDF p.26, printed p.20) requires a Roman terminator around Roman text in a Korean sentence, whereas rule 39 (PDF pp.31-32, printed pp.25-26) applies the Korean opening/closing markers only when Roman text is the sentence's main language. Among exact candidates, 10996 expose the current rule-29 terminator at such a boundary and 0 expose the current rule-39 opening. Its printed controls include both an English sentence (`What is 김치 in English?`) and the address `www.대통령.kr` inside a Korean sentence. Official UEB 2.4.7 (2024 UEB PDF p.41, printed p.13) confirms that a UEB mode does not extend through a switch to another braille code, but does not choose whether this Korean boundary belongs to a Korean-main or Roman-main context. Therefore the attached script boundary alone cannot distinguish ordinary Korean prose from an embedded Roman-domain context. Primary classes are preserved and no engine change is inferred without a narrower input-derived dominance gate. At diagnostic checkpoint `57c4608`, this same cohort was 17,693 candidates / 12,724 exact / 4,969 mismatch, with 268 localized `⠲ -> ⠸`, zero reverse, 10,780 exact rule-29 markers, and zero exact rule-39 markers. After narrowing the engine by dominance and the PDF domain shape, it is 12,924 exact / 4,769 mismatch with no localized target or reverse; corpus-wide exact rises from 67,715 to 68,101 (+386). The remaining 1 mismatch with a current rule-39 opening is a list-heavy Korean sentence whose ASCII-leading brand tokens satisfy the mechanical word-count majority; its first difference is not localized to this boundary, so it remains a semantic pending control rather than grounds for another engine branch. -Current rule-39 narrowed-scope audit: 947 candidates, 385 exact controls, 562 mismatches, and 550 members in the actual `pending_rule_review` subcluster. This input-derived scope is recorded separately from the direct-boundary output-localizer; primary classes are unchanged. At the implementation checkpoint, this conservative first-script-word approximation contains 385 exact cases, accounting for all but one of the corpus-wide +386 net gain; no localized reverse transition is observed in the direct-boundary cohort. +Current rule-39 narrowed-scope audit: 947 candidates, 386 exact controls, 561 mismatches, and 549 members in the actual `pending_rule_review` subcluster. This input-derived scope is recorded separately from the direct-boundary output-localizer; primary classes are unchanged. At the implementation checkpoint, this conservative first-script-word approximation contains 385 exact cases, accounting for all but one of the corpus-wide +386 net gain; no localized reverse transition is observed in the direct-boundary cohort. Current inline parenthesized-operator measurement: 23 candidates, 17 exact controls, 6 mismatches, 6 members in the actual `pending_rule_review` subcluster, and 2/6 evaluable mismatches whose first differing cell is inside the emitted structure. Primary classes are preserved. At that implementation checkpoint, the strict cohort moved from 0 to 17 exact cases; the corpus-wide total moved from 65,491 to 65,514 (+23 exact) because the same PDF-backed spacing rule also applied outside the stricter Korean-boundary audit gate. These are immutable checkpoint counts rather than the report's later cumulative total. The complete standard suite remained 5,141/5,141. @@ -3310,10 +3299,10 @@ The audit starts from all raw encoding errors, then separates cases already reso | Encoding-error audit | Cases | |---|---:| | Raw encoding errors | 450 | -| Resolved by comparison method | 274 | +| Resolved by comparison method | 275 | | Excluded as corpus suspect | 0 | -| Unresolved encoding-error review cases | 176 | -| Explained by singleton unsupported character(s) | 176 | +| Unresolved encoding-error review cases | 175 | +| Explained by singleton unsupported character(s) | 175 | | Multiple singleton unsupported characters | 2 | | Unclassified without a singleton explanation | 0 | @@ -3324,11 +3313,11 @@ The audit starts from all raw encoding errors, then separates cases already reso | Error message | Cases | |---|---:| -| `Invalid symbol character` | 176 | +| `Invalid symbol character` | 175 | | Error family | Cases | |---|---:| -| `enclosed_organization_mark` | 157 | +| `enclosed_organization_mark` | 156 | | `letterlike_unit_symbol` | 9 | | `punctuation_or_layout_symbol` | 12 | @@ -3347,25 +3336,25 @@ Families are diagnostics, not automatic normalization permissions. Rules 68/69 c | `U+2E31 ⸱` | 2 | `⸱` | `punctuation_or_layout_symbol` | | `U+302E 〮` | 1 | `〮` | `punctuation_or_layout_symbol` | | `U+3214 ㈔` | 2 | `(사)` | `enclosed_organization_mark` | -| `U+321C ㈜` | 155 | `(주)` | `enclosed_organization_mark` | +| `U+321C ㈜` | 154 | `(주)` | `enclosed_organization_mark` | ## Shards | Shard | Exact | Total | Accuracy | |---|---:|---:|---:| -| `sentence_01.json` | 20454 | 25000 | 81.82% | -| `sentence_02.json` | 19966 | 25000 | 79.86% | -| `sentence_03.json` | 20546 | 25000 | 82.18% | -| `sentence_04.json` | 7221 | 8528 | 84.67% | +| `sentence_01.json` | 20531 | 25000 | 82.12% | +| `sentence_02.json` | 19980 | 25000 | 79.92% | +| `sentence_03.json` | 20632 | 25000 | 82.53% | +| `sentence_04.json` | 7225 | 8528 | 84.72% | ## Overlapping mismatch traits | Trait | Count | |---|---:| -| `contains_ascii_digits` | 12088 | -| `contains_ascii_letters` | 15155 | -| `contains_delimiter_or_quote` | 15341 | -| `input_not_nfkc` | 716 | +| `contains_ascii_digits` | 11907 | +| `contains_ascii_letters` | 14985 | +| `contains_delimiter_or_quote` | 15160 | +| `input_not_nfkc` | 712 | ## Samples diff --git a/libs/braillify/examples/nikl_corpus_analyze.rs b/libs/braillify/examples/nikl_corpus_analyze.rs index fb85998c..43696279 100644 --- a/libs/braillify/examples/nikl_corpus_analyze.rs +++ b/libs/braillify/examples/nikl_corpus_analyze.rs @@ -5566,15 +5566,20 @@ fn markdown(report: &AnalysisReport) -> String { (2024 UEB PDF p.103, printed p.75) defines its prose comma as `⠂`, and UEB 6.2.1 \ (PDF p.94, printed p.66) retains `⠂` inside attached numeric forms such as \ `3,500`. Those two surfaces are negative controls and are excluded by this gate.\n\n\ - Before any engine change, the cohort has {} candidates / {} exact / {} mismatch. \ + The diagnostic baseline was 217 candidates / 7 exact / 210 mismatch, with 177 \ + occurrence-specific `{korean_to_ueb}` first differences and no localized reverse. \ + Rule 41 had looked through `remaining_words`, incorrectly treating whitespace as if \ + the following digit were attached. The implementation now inspects only the next \ + character in the same token. It neither names a corpus input nor consults expected \ + output; attached numbers and UEB punctuation remain owned by their existing routes.\n\n\ + After the correction, the cohort has {} candidates / {} exact / {} mismatch. \ Existing mismatch primaries remain {} `pending_rule_review`, {} `corpus_suspect`, \ - {} `comparison_method`, and {} `unsupported_character_review`. The \ - occurrence-specific locator encodes the real prefix immediately before each comma \ - and claims only its next emitted cell. Of {} evaluable mismatches, {} localize there, \ - including {} `{korean_to_ueb}` and {} `{ueb_to_korean}`. Across all pending cases, \ - the raw-to-residual counts after adding this cohort are {} -> {} for the target and \ - {} -> {} for the reverse. No primary class is changed and no implementation result \ - is inferred from the reference outputs at this diagnostic checkpoint.\n", + {} `comparison_method`, and {} `unsupported_character_review`. Of {} evaluable \ + current mismatches, {} localize to the comma cell: {} `{korean_to_ueb}` and {} \ + `{ueb_to_korean}`. The cohort gains 174 exact cases. Across all pending cases, the \ + current raw-to-residual counts are {} -> {} for the target and {} -> {} for the \ + reverse. The standard controls `9,375명`, `창세기 12,1-9`, and `근면, 검소, \ + 협동은 ...` retain their PDF cells. No primary class is changed by the cohort.\n", stats.candidates, stats.exact, stats.mismatch, @@ -5623,13 +5628,19 @@ fn markdown(report: &AnalysisReport) -> String { `⠐`; UEB 7 supplies `⠂` only while the comma remains inside English text. Requiring \ Korean script in the right token is therefore the negative control against \ reclassifying an English date or number sequence from surface punctuation alone.\n\n\ - Before the shared rule-41 boundary correction, this cohort has {} candidates / {} \ - exact / {} mismatch. Existing mismatch primaries remain {} \ - `pending_rule_review`, {} `corpus_suspect`, {} `comparison_method`, and {} \ - `unsupported_character_review`. Of {} evaluable mismatches, {} have the comma-cell \ - transition inside the independently encoded complete boundary signature: {} \ - `{korean_to_ueb}` and {} `{ueb_to_korean}`. The detector and localizer do not read \ - expected output to choose a route, and membership does not change a primary class.\n", + The diagnostic baseline was 58 candidates / 0 exact / 58 mismatch. Of those, 23 \ + had `{korean_to_ueb}` at the comma inside the independently encoded complete \ + boundary signature and none had the reverse. The same rule-41 correction removes \ + the cross-token ASCII-letter lookup; rule 33 and the existing English-symbol route \ + then choose the punctuation from the actual surrounding scripts.\n\n\ + After the correction, this cohort has {} candidates / {} exact / {} mismatch. \ + Existing mismatch primaries remain {} `pending_rule_review`, {} `corpus_suspect`, \ + {} `comparison_method`, and {} `unsupported_character_review`. Of {} evaluable \ + current mismatches, {} localize to the comma-cell signature: {} \ + `{korean_to_ueb}` and {} `{ueb_to_korean}`. Three cases become exact. The official \ + rule-33 `KTX, 새마을호` boundary and UEB prose comma remain independent standard \ + controls. The detector and localizer do not read expected output to choose a route, \ + and membership does not change a primary class.\n", stats.candidates, stats.exact, stats.mismatch, @@ -5669,14 +5680,18 @@ fn markdown(report: &AnalysisReport) -> String { explicitly defines `%p` as the percent-point unit. This cohort requires two \ complete numeric `%p` tokens separated by comma plus whitespace, so rule 49's \ ordinary Korean comma is the punctuation boundary; it does not infer arbitrary \ - ASCII suffixes as units. At the diagnostic baseline it has {} candidates / {} \ - exact / {} mismatch, preserving {} `pending_rule_review`, {} `corpus_suspect`, {} \ - `comparison_method`, and {} `unsupported_character_review` mismatch primaries. Of \ - {} evaluable mismatches, {} localize to the independently encoded complete unit \ - pair: {} `{korean_to_ueb}` and {} `{ueb_to_korean}`. This scope audit was added \ - after the exact-set comparison identified the common `%p, ... %p` structure; the \ - engine still contains only the general rule-41 same-token boundary, not a `%p` \ - special case.\n", + ASCII suffixes as units. The diagnostic baseline was 7 candidates / 0 exact / 7 \ + mismatch, with one localized `{korean_to_ueb}` and no reverse.\n\n\ + After the correction, this cohort has {} candidates / {} exact / {} mismatch, \ + preserving {} `pending_rule_review`, {} `corpus_suspect`, {} `comparison_method`, \ + and {} `unsupported_character_review` mismatch primaries. Of {} evaluable current \ + mismatches, {} localize to the independently encoded complete unit pair: {} \ + `{korean_to_ueb}` and {} `{ueb_to_korean}`. Five cases become exact; the other two \ + retain independent earlier differences. One of those five is also in the \ + Roman-tail cohort, leaving four disjoint `%p` gains. Thus +174 in the numeric-list \ + cohort, +3 in the Roman-tail cohort, and +4 disjoint here account for all +181 \ + corpus exact gains, with zero exact-set regressions. The engine contains only the general \ + rule-41 same-token boundary, not a `%p` special case.\n", stats.candidates, stats.exact, stats.mismatch, @@ -7149,7 +7164,7 @@ mod tests { } #[test] - fn localizes_pdf_spaced_numeric_list_comma_in_baseline_output() { + fn localizes_pdf_spaced_numeric_list_comma_after_rule41_fix() { // 2024 Korean-rules PDF physical p.209. let input = "제5열 버튼(3, 7 혹은 S)"; let actual = braillify::encode_to_unicode(input).expect("numeric-list probe must encode"); @@ -7159,7 +7174,7 @@ mod tests { assert!( ranges .iter() - .all(|range| actual.chars().nth(range.start) == Some('⠂')) + .all(|range| actual.chars().nth(range.start) == Some('⠐')) ); } diff --git a/libs/braillify/src/rules/korean/rule_41.rs b/libs/braillify/src/rules/korean/rule_41.rs index 8501ffec..aceeb27a 100644 --- a/libs/braillify/src/rules/korean/rule_41.rs +++ b/libs/braillify/src/rules/korean/rule_41.rs @@ -1,8 +1,9 @@ -//! 제41항 — 숫자 또는 로마자 구간에서 쉼표는 ⠂(2)으로 적는다. +//! 제41항 — 숫자 사이에 붙어 나오는 쉼표는 ⠂(2)으로 적는다. //! -//! When a comma appears between digits (e.g., "1,000") or between ASCII letters -//! and alphanumeric characters, it uses the numeric comma ⠂ instead of the -//! standard Korean comma ⠐. +//! When a comma is attached between digits (e.g., "1,000"), it uses the numeric +//! comma ⠂ instead of the standard Korean comma ⠐. A whitespace boundary means +//! the comma is ordinary punctuation under rule 49, not an attached numeric +//! comma under this rule. //! //! Reference: 2024 Korean Braille Standard, Chapter 5, Section 11, Article 41 @@ -15,7 +16,7 @@ pub static META: RuleMeta = RuleMeta { subsection: None, name: "numeric_comma", standard_ref: "2024 Korean Braille Standard, Ch.5 Sec.11 Art.41", - description: "Comma between digits/letters uses ⠂ (2) instead of standard comma", + description: "Attached comma within a numeric/ASCII sequence uses ⠂ (2)", }; /// Numeric comma braille code. @@ -23,7 +24,7 @@ const NUMERIC_COMMA: u8 = 2; // ⠂ /// Plugin struct for the rule engine. /// -/// Handles comma encoding in numeric/English context. +/// Handles attached comma encoding in numeric/English context. /// Runs before generic punctuation (rule_49) to intercept commas. pub struct Rule41; @@ -49,7 +50,9 @@ impl BrailleRule for Rule41 { } let (has_numeric_prefix, has_ascii_prefix) = scan_prefix(ctx.word_chars, ctx.index); - let next_char = get_next_char(ctx); + // 제41항의 "붙어 나오는" 경계만 본다. `remaining_words`까지 + // 건너뛰면 `1, 2`의 일반 쉼표를 숫자 쉼표로 오분류한다. + let next_char = ctx.word_chars.get(ctx.index + 1).copied(); let next_is_digit = next_char.is_some_and(|ch| ch.is_ascii_digit()); let next_is_ascii = next_char.is_some_and(|ch| ch.is_ascii_alphabetic()); let next_is_alphanumeric = next_is_digit || next_is_ascii; @@ -78,15 +81,6 @@ fn scan_prefix(word_chars: &[char], index: usize) -> (bool, bool) { } } -/// Get the next character (within word or from next word). -fn get_next_char(ctx: &RuleContext) -> Option { - if ctx.index + 1 < ctx.word_chars.len() { - Some(ctx.word_chars[ctx.index + 1]) - } else { - ctx.remaining_words.first().and_then(|w| w.chars().next()) - } -} - #[cfg(test)] mod tests { use super::*; @@ -121,7 +115,7 @@ mod tests { assert!(!Rule41.matches(&ctx)); } - /// 제41항 — 숫자/로마자 구간 안의 쉼표는 숫자 쉼표 규칙이 잡는다. + /// 제41항 숫자 쉼표와 UEB의 같은-token 로마자 쉼표 경로. #[rstest::rstest] #[case::between_digits("1,000", 1)] #[case::between_ascii_letters("A,B", 1)] @@ -133,6 +127,45 @@ mod tests { assert!(Rule41.matches(&ctx)); } + #[rstest::rstest] + // PDF physical p.209: `제5열 버튼(3, 7 혹은 S)`. + #[case::music_button_list("3,", "7")] + // PDF physical p.142: `1/3, 2/3의 길이`. + #[case::music_fraction_list("1/3,", "2/3의")] + fn rule41_does_not_cross_whitespace_token_boundaries( + #[case] current_word: &str, + #[case] next_word: &str, + ) { + let mut owned = crate::test_helpers::CtxOwned::for_text(current_word, false) + .with_remaining_words([next_word]); + let comma_index = current_word + .chars() + .position(|ch| ch == ',') + .expect("test word must contain a comma"); + let ctx = owned.ctx_at(comma_index); + + assert!(!Rule41.matches(&ctx)); + } + + /// 제41항/제49항 PDF 예제를 전체 인코더로 통과시켜 붙은 숫자 쉼표와 + /// 일반 한글 쉼표의 서로 다른 셀을 함께 고정한다. + #[rstest::rstest] + #[case::rule41_grouped_number("9,375명", '⠂')] + #[case::rule41_verse_reference("창세기 12,1-9", '⠂')] + #[case::rule49_korean_list("근면, 검소, 협동은 우리 겨레의 미덕이다.", '⠐')] + fn full_encoder_preserves_pdf_comma_boundaries( + #[case] input: &str, + #[case] expected_comma: char, + ) { + let comma_byte = input.find(',').expect("PDF example must contain comma"); + let prefix = crate::encode_to_unicode(&input[..comma_byte]).expect("prefix must encode"); + let actual = crate::encode_to_unicode(input).expect("PDF example must encode"); + let comma_cell = actual.chars().nth(prefix.chars().count()); + + assert!(actual.starts_with(&prefix)); + assert_eq!(comma_cell, Some(expected_comma)); + } + /// rule_41 line 75 — `j -= 1;` when prev char is a space (continues backward scan). #[test] fn scan_prefix_skips_space_then_finds_digit() { From 8646db467544956c2dffc5878be673c2c7e34bb9 Mon Sep 17 00:00:00 2001 From: devfive Date: Tue, 1 Sep 2026 16:49:18 +0900 Subject: [PATCH 51/77] Diagnose attached auxiliary spacing normalization --- docs/corpus-analysis/NIKL_2025_V1.md | 80 ++++++++- .../braillify/examples/nikl_corpus_analyze.rs | 156 +++++++++++++++++- 2 files changed, 229 insertions(+), 7 deletions(-) diff --git a/docs/corpus-analysis/NIKL_2025_V1.md b/docs/corpus-analysis/NIKL_2025_V1.md index 9141ff46..1c67400e 100644 --- a/docs/corpus-analysis/NIKL_2025_V1.md +++ b/docs/corpus-analysis/NIKL_2025_V1.md @@ -315,9 +315,9 @@ This ranking removes only cases whose first difference is inside an existing out | 15 | `U+2826 ⠦ -> U+2800 ⠀` | 79 | | 16 | `U+2824 ⠤ -> U+2800 ⠀` | 78 | | 17 | `U+280E ⠎ -> U+2829 ⠩` | 74 | -| 18 | `U+2815 ⠕ -> U+2800 ⠀` | 74 | -| 19 | `U+2820 ⠠ -> U+2809 ⠉` | 65 | -| 20 | `U+2834 ⠴ -> U+2820 ⠠` | 64 | +| 18 | `U+2820 ⠠ -> U+2809 ⠉` | 65 | +| 19 | `U+2834 ⠴ -> U+2820 ⠠` | 64 | +| 20 | `U+2830 ⠰ -> U+2820 ⠠` | 56 | ### Residual `U+2810 ⠐ -> U+2832 ⠲` @@ -551,7 +551,7 @@ This ranking removes only cases whose first difference is inside an existing out ## Cross-cutting input-only structural cohorts -These are cross-cutting input-only structural cohorts, not new primary classes and not engine routing rules. Candidate selection never changes a case's existing primary class by itself. Only cohort members already classified as `pending_rule_review` form a pending subcluster; exact and other-primary members are controls that retain their existing outcomes. A separate classifier may use independently justified, output-localized PDF evidence, as in the rule-34 three-cell contradiction below. The `uppercase_roman_headword_closed_multiword_parenthetical` gate requires a two-or-more character uppercase ASCII headword immediately followed by a closed parenthesis whose contents are two or more ASCII Roman words separated only by spaces. Because the contents admit only letters and spaces, visible operators, subscript/superscript notation, and nested parentheses are excluded deterministically. The `mixed_roman_korean_word_before_uppercase_headword_expansion` gate further requires a whitespace-delimited preceding word that contains both ASCII Roman and Korean and ends in Korean. It locates the following uppercase headword itself, including its immediately preceding emitted cell, so an earlier Roman entry in the same sentence cannot satisfy the output audit. The `single_capital_followed_by_parenthesized_digits` gate requires a standalone capital, a non-empty closed ASCII-digit parenthetical, and alphanumeric outer boundaries. It likewise includes the emitted entry-boundary cell in localization. The `uppercase_roman_run_followed_by_hyphen_digits` gate requires a maximal uppercase ASCII run, literal ASCII hyphen-minus, a non-empty digit run, and alphanumeric outer boundaries. Its localized range includes the current encoded run and its immediately preceding cell, keeping `F-35`-style routing separate from both parenthesized digits and headword expansions. The `uppercase_alphanumeric_roman_digit_sequence` gate covers the wider rule-35/math collision: a capital-led uppercase/digit sequence with optional internal ASCII hyphen/full stop and trailing prose comma/colon/semicolon. It localizes only the current entry cells and does not decide whether an `A-3`-shaped surface is an identifier or a mathematical expression. The `standalone_multi_character_uppercase_roman_word` gate finds maximal ASCII-letter runs of two or more capitals with non-alphanumeric boundaries; a run immediately followed by `(` is excluded so the HCA-style headword itself is not counted by both gates. The `korean_prefixed_closed_allcaps_parenthetical` gate requires an immediately preceding Korean character and a closed body of two or more uppercase ASCII letters. It intentionally contains both acronym annotations (`책임자(COO)`) and scientific formulae (`일산화탄소(CO)`) so their semantic collision remains measurable. The `korean_prefixed_closed_roman_annotation_rule_34_order` gate accepts the narrower rule-34 body grammar after an immediately preceding Korean character and localizes only the current engine's Korean opening-parenthesis cells. This distinguishes the PDF's parenthesis-before-Roman-indicator order from unrelated differences later in the same sentence. The `multi_character_allcaps_roman_runs_joined_by_middle_dot` gate requires two maximal ASCII-letter runs of at least two capitals joined directly by U+00B7, with non-alphanumeric outer boundaries. It records shapes such as `AI·SW` without assigning prose, mathematics, or science semantics. The `roman_run_immediately_before_attached_middle_dot_boundary` gate is output-localized: it requires a maximal ASCII-letter run immediately before U+00B7 and an attached Korean character or ASCII-letter run after it, then searches for that whole current-engine signature in the actual output. It therefore isolates the Roman terminator boundary without treating unrelated middle dots elsewhere in the sentence as causal. The `attached_ascii_roman_to_korean_script_boundary` gate requires an ASCII letter run immediately followed by Korean script and localizes only the current mode marker there: either rule 29's final `⠲` or rule 39's opening `⠸⠷`. It deliberately does not infer the dominant language of the sentence from that surface boundary. The `korean_majority_same_token_roman_sandwich_non_domain` scope audit mirrors the narrower rule-39 implementation gate: the nearest script characters on both sides of one Korean segment are ASCII Roman, the input is Korean-majority by first-script word counts, and the segment is not dot-delimited like the official domain example. It measures change scope but is not itself an output-localized causal classifier. The `korean_inline_parenthesized_single_arithmetic_operator` gate requires an immediate `Korean(` + one rule-45 arithmetic operator + `)Korean` span. Unlike broad coexistence traits, it also locates the current engine's emitted structure and counts a mismatch as signature-local only when the sentence's first differing cell falls inside that output range. The `allcaps_roman_run_containing_ou` gate finds maximal, alphanumeric-delimited uppercase ASCII runs containing adjacent `OU`. It locates the independently encoded run signature in the complete current output and counts only first differences inside that signature as localized. The `allcaps_roman_run_containing_st` gate applies the same output-position requirement to adjacent `ST`; it is kept separate because UEB 10.12 makes contraction use depend on how an abbreviation or acronym is pronounced. The `attached_ascii_roman_segments_joined_by_ampersand` gate requires non-empty ASCII-letter segments joined directly by `&`, excludes spaced/Korean/alphanumeric continuations, and localizes only the current output cell immediately before the ampersand through an independently encoded real-input prefix. The `ampersand_before_attached_ascii_roman_segment` gate covers the distinct one-sided UEB `&c` boundary: `&` is followed by a complete ASCII-letter segment, while an ASCII alphanumeric or another ampersand immediately before it and a digit continuation after it are excluded. Its output range is anchored by independently encoding the real input prefix before each occurrence, then includes only the current Rule-71/29 entry boundary; a second pre-fix anchor through `&` retains the former exit location. The `consecutive_ascii_roman_words_whitespace_boundary` gate requires two adjacent ASCII-letter words separated only by whitespace. For each boundary it independently encodes the real input prefix ending after the first word, then localizes only the current rule-29 terminator at that position or the full output's replacing blank. This prevents an unrelated Roman occurrence elsewhere in the sentence from satisfying the audit. Slash and other punctuation-separated forms are deliberately excluded. The `decimal_point_between_ascii_digits` gate finds whitespace-delimited words containing `digit.digit` and reproduces each whole word in a neutral Korean context, so suffixes and punctuation remain part of the current-engine signature. The `compact_numeric_ascii_letter_suffix` gate finds a numeric prefix immediately followed by ASCII letters, includes the immediately preceding output cell as its entry boundary, and retains suffix-specific outcome counts. It intentionally includes both possible rule-69 units and ambiguous variable/identifier forms; membership alone does not assign unit semantics. The `rule69_ascii_unit_before_terminator_skipping_symbol` gate is narrower: it accepts only ASCII unit spellings already supported by rule 69, includes the immediately following rule-33/34 punctuation cell in the localized signature, and does not infer new units. The `tight_triangle_mark_immediately_before_korean` gate requires literal `△한글` with no input space and includes the first following Korean cell in its localized output range, so an observed missing-space difference is measured at the mark boundary. +These are cross-cutting input-only structural cohorts, not new primary classes and not engine routing rules. Candidate selection never changes a case's existing primary class by itself. Only cohort members already classified as `pending_rule_review` form a pending subcluster; exact and other-primary members are controls that retain their existing outcomes. A separate classifier may use independently justified, output-localized PDF evidence, as in the rule-34 three-cell contradiction below. The `uppercase_roman_headword_closed_multiword_parenthetical` gate requires a two-or-more character uppercase ASCII headword immediately followed by a closed parenthesis whose contents are two or more ASCII Roman words separated only by spaces. Because the contents admit only letters and spaces, visible operators, subscript/superscript notation, and nested parentheses are excluded deterministically. The `mixed_roman_korean_word_before_uppercase_headword_expansion` gate further requires a whitespace-delimited preceding word that contains both ASCII Roman and Korean and ends in Korean. It locates the following uppercase headword itself, including its immediately preceding emitted cell, so an earlier Roman entry in the same sentence cannot satisfy the output audit. The `single_capital_followed_by_parenthesized_digits` gate requires a standalone capital, a non-empty closed ASCII-digit parenthetical, and alphanumeric outer boundaries. It likewise includes the emitted entry-boundary cell in localization. The `uppercase_roman_run_followed_by_hyphen_digits` gate requires a maximal uppercase ASCII run, literal ASCII hyphen-minus, a non-empty digit run, and alphanumeric outer boundaries. Its localized range includes the current encoded run and its immediately preceding cell, keeping `F-35`-style routing separate from both parenthesized digits and headword expansions. The `uppercase_alphanumeric_roman_digit_sequence` gate covers the wider rule-35/math collision: a capital-led uppercase/digit sequence with optional internal ASCII hyphen/full stop and trailing prose comma/colon/semicolon. It localizes only the current entry cells and does not decide whether an `A-3`-shaped surface is an identifier or a mathematical expression. The `standalone_multi_character_uppercase_roman_word` gate finds maximal ASCII-letter runs of two or more capitals with non-alphanumeric boundaries; a run immediately followed by `(` is excluded so the HCA-style headword itself is not counted by both gates. The `korean_prefixed_closed_allcaps_parenthetical` gate requires an immediately preceding Korean character and a closed body of two or more uppercase ASCII letters. It intentionally contains both acronym annotations (`책임자(COO)`) and scientific formulae (`일산화탄소(CO)`) so their semantic collision remains measurable. The `korean_prefixed_closed_roman_annotation_rule_34_order` gate accepts the narrower rule-34 body grammar after an immediately preceding Korean character and localizes only the current engine's Korean opening-parenthesis cells. This distinguishes the PDF's parenthesis-before-Roman-indicator order from unrelated differences later in the same sentence. The `multi_character_allcaps_roman_runs_joined_by_middle_dot` gate requires two maximal ASCII-letter runs of at least two capitals joined directly by U+00B7, with non-alphanumeric outer boundaries. It records shapes such as `AI·SW` without assigning prose, mathematics, or science semantics. The `roman_run_immediately_before_attached_middle_dot_boundary` gate is output-localized: it requires a maximal ASCII-letter run immediately before U+00B7 and an attached Korean character or ASCII-letter run after it, then searches for that whole current-engine signature in the actual output. It therefore isolates the Roman terminator boundary without treating unrelated middle dots elsewhere in the sentence as causal. The `attached_ascii_roman_to_korean_script_boundary` gate requires an ASCII letter run immediately followed by Korean script and localizes only the current mode marker there: either rule 29's final `⠲` or rule 39's opening `⠸⠷`. It deliberately does not infer the dominant language of the sentence from that surface boundary. The `korean_majority_same_token_roman_sandwich_non_domain` scope audit mirrors the narrower rule-39 implementation gate: the nearest script characters on both sides of one Korean segment are ASCII Roman, the input is Korean-majority by first-script word counts, and the segment is not dot-delimited like the official domain example. It measures change scope but is not itself an output-localized causal classifier. The `korean_inline_parenthesized_single_arithmetic_operator` gate requires an immediate `Korean(` + one rule-45 arithmetic operator + `)Korean` span. Unlike broad coexistence traits, it also locates the current engine's emitted structure and counts a mismatch as signature-local only when the sentence's first differing cell falls inside that output range. The `allcaps_roman_run_containing_ou` gate finds maximal, alphanumeric-delimited uppercase ASCII runs containing adjacent `OU`. It locates the independently encoded run signature in the complete current output and counts only first differences inside that signature as localized. The `allcaps_roman_run_containing_st` gate applies the same output-position requirement to adjacent `ST`; it is kept separate because UEB 10.12 makes contraction use depend on how an abbreviation or acronym is pronounced. The `attached_ascii_roman_segments_joined_by_ampersand` gate requires non-empty ASCII-letter segments joined directly by `&`, excludes spaced/Korean/alphanumeric continuations, and localizes only the current output cell immediately before the ampersand through an independently encoded real-input prefix. The `ampersand_before_attached_ascii_roman_segment` gate covers the distinct one-sided UEB `&c` boundary: `&` is followed by a complete ASCII-letter segment, while an ASCII alphanumeric or another ampersand immediately before it and a digit continuation after it are excluded. Its output range is anchored by independently encoding the real input prefix before each occurrence, then includes only the current Rule-71/29 entry boundary; a second pre-fix anchor through `&` retains the former exit location. The `consecutive_ascii_roman_words_whitespace_boundary` gate requires two adjacent ASCII-letter words separated only by whitespace. For each boundary it independently encodes the real input prefix ending after the first word, then localizes only the current rule-29 terminator at that position or the full output's replacing blank. This prevents an unrelated Roman occurrence elsewhere in the sentence from satisfying the audit. Slash and other punctuation-separated forms are deliberately excluded. The `decimal_point_between_ascii_digits` gate finds whitespace-delimited words containing `digit.digit` and reproduces each whole word in a neutral Korean context, so suffixes and punctuation remain part of the current-engine signature. The `compact_numeric_ascii_letter_suffix` gate finds a numeric prefix immediately followed by ASCII letters, includes the immediately preceding output cell as its entry boundary, and retains suffix-specific outcome counts. It intentionally includes both possible rule-69 units and ambiguous variable/identifier forms; membership alone does not assign unit semantics. The `rule69_ascii_unit_before_terminator_skipping_symbol` gate is narrower: it accepts only ASCII unit spellings already supported by rule 69, includes the immediately following rule-33/34 punctuation cell in the localized signature, and does not infer new units. The `tight_triangle_mark_immediately_before_korean` gate requires literal `△한글` with no input space and includes the first following Korean cell in its localized output range, so an observed missing-space difference is measured at the mark boundary. The `attached_korean_auxiliary_itda_spacing` gate mirrors the current normalization shape for a Korean token ending in attached `있다`; its localizer independently encodes the real prefix and claims only the blank inserted before the suffix. It does not decide whether orthographic correction may override the printed input. | Cluster | Candidates | Exact | Mismatch | Conflicting-reference cases | |---|---:|---:|---:|---:| @@ -564,6 +564,7 @@ These are cross-cutting input-only structural cohorts, not new primary classes a | `ascii_roman_tail_comma_before_digit_korean_token` | 58 | 3 | 55 | 0 | | `attached_ascii_roman_segments_joined_by_ampersand` | 802 | 285 | 517 | 0 | | `attached_ascii_roman_to_korean_script_boundary` | 17693 | 12966 | 4727 | 0 | +| `attached_korean_auxiliary_itda_spacing` | 95 | 0 | 95 | 0 | | `closed_roman_parenthetical_after_non_ascii_letter_boundary` | 63959 | 52384 | 11575 | 0 | | `compact_numeric_ascii_letter_suffix` | 2975 | 1877 | 1098 | 0 | | `consecutive_ascii_roman_words_whitespace_boundary` | 4679 | 2134 | 2545 | 0 | @@ -1284,6 +1285,69 @@ Representative `mismatch_rule39_hangul_opening` samples: - actual: `⠏⠐⠀⠴⠠⠠⠎⠅⠸⠷⠘⠪⠐⠥⠊⠪⠘⠗⠒⠊⠪⠸⠾⠐` - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +### `attached_korean_auxiliary_itda_spacing` + +Of the 95 candidates, 92 are the actual `pending_rule_review` subcluster. The other 3 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. + +For this output-signature audit, 95 mismatches were evaluable and 74 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. + +Localized first-difference transitions: + +- `U+2815 ⠕ -> U+2800 ⠀`: 73 +- `U+2823 ⠣ -> U+2800 ⠀`: 1 + +Mismatch primary-class distribution: + +- `corpus_suspect`: 2 +- `pending_rule_review`: 92 +- `unsupported_character_review`: 1 + +Representative `localized_mismatch` samples: + +- `sentence_01.json` #1416: ‘키우GO’ 서비스는 투자목표와 투자기간, 투자금액, 투자성향 등을 종합적으로 분석하여 현재 금융시장 상황에 적합한 자산배분 포트폴리오를 제공하는 투자일임(Wrap)서비스로, 21년 5월 서비스 출시 후 꾸준한 성장을 하고있다. + - expected: `⠻⠨⠶⠮⠀⠚⠈⠥⠕⠌⠊⠲` + - actual: `⠻⠨⠶⠮⠀⠚⠈⠥⠀⠕⠌⠊⠲` + - first differing cell (zero-based): 212 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_02.json` #242: 예산군의 주차장 관리 업무를 담당하는 임종생 씨(70)는 매일 아침 군청사 출입문에서 밝은 미소로 출근하는 직원과 민원인에게 매일 아침마다 인사를 건네고 있어 호평을 받고있다. + - expected: `⠙⠻⠮⠀⠘⠔⠈⠥⠕⠌⠊⠲` + - actual: `⠙⠻⠮⠀⠘⠔⠈⠥⠀⠕⠌⠊⠲` + - first differing cell (zero-based): 157 + - current primary/reason: `pending_rule_review` / `number_rule_review` +- `sentence_03.json` #74: 아울러 제주도개발공사는 국내 생수업계에서는 처음으로 재활용 페트(CR-PET)를 적용한 화학적 재활용 페트 ‘제주삼다수 리본(RE:Born)’을 개발하는 등 소재혁신을 통한 친환경 라인업도 확대하고있다. + - expected: `⠚⠧⠁⠊⠗⠚⠈⠥⠕⠌⠊⠲` + - actual: `⠚⠧⠁⠊⠗⠚⠈⠥⠀⠕⠌⠊⠲` + - first differing cell (zero-based): 194 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_04.json` #204: 이번 법안엔 소형모듈원전(SMR)을 분산에너지로 인정하는 내용도 담겨있다. 여야가 인정 여부를 두고 이견을 보였지만 최근 합의했다. + - expected: `⠶⠊⠥⠀⠊⠢⠈⠱⠕⠌⠊⠲⠀⠱⠜⠫⠀⠟⠨⠻⠀⠱⠘⠍` + - actual: `⠶⠊⠥⠀⠊⠢⠈⠱⠀⠕⠌⠊⠲⠀⠱⠜⠫⠀⠟⠨⠻⠀⠱⠘` + - first differing cell (zero-based): 67 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` + +Representative `mismatch` samples: + +- `sentence_01.json` #1416: ‘키우GO’ 서비스는 투자목표와 투자기간, 투자금액, 투자성향 등을 종합적으로 분석하여 현재 금융시장 상황에 적합한 자산배분 포트폴리오를 제공하는 투자일임(Wrap)서비스로, 21년 5월 서비스 출시 후 꾸준한 성장을 하고있다. + - expected: `⠻⠨⠶⠮⠀⠚⠈⠥⠕⠌⠊⠲` + - actual: `⠻⠨⠶⠮⠀⠚⠈⠥⠀⠕⠌⠊⠲` + - first differing cell (zero-based): 212 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_02.json` #242: 예산군의 주차장 관리 업무를 담당하는 임종생 씨(70)는 매일 아침 군청사 출입문에서 밝은 미소로 출근하는 직원과 민원인에게 매일 아침마다 인사를 건네고 있어 호평을 받고있다. + - expected: `⠙⠻⠮⠀⠘⠔⠈⠥⠕⠌⠊⠲` + - actual: `⠙⠻⠮⠀⠘⠔⠈⠥⠀⠕⠌⠊⠲` + - first differing cell (zero-based): 157 + - current primary/reason: `pending_rule_review` / `number_rule_review` +- `sentence_03.json` #74: 아울러 제주도개발공사는 국내 생수업계에서는 처음으로 재활용 페트(CR-PET)를 적용한 화학적 재활용 페트 ‘제주삼다수 리본(RE:Born)’을 개발하는 등 소재혁신을 통한 친환경 라인업도 확대하고있다. + - expected: `⠚⠧⠁⠊⠗⠚⠈⠥⠕⠌⠊⠲` + - actual: `⠚⠧⠁⠊⠗⠚⠈⠥⠀⠕⠌⠊⠲` + - first differing cell (zero-based): 194 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_04.json` #204: 이번 법안엔 소형모듈원전(SMR)을 분산에너지로 인정하는 내용도 담겨있다. 여야가 인정 여부를 두고 이견을 보였지만 최근 합의했다. + - expected: `⠶⠊⠥⠀⠊⠢⠈⠱⠕⠌⠊⠲⠀⠱⠜⠫⠀⠟⠨⠻⠀⠱⠘⠍` + - actual: `⠶⠊⠥⠀⠊⠢⠈⠱⠀⠕⠌⠊⠲⠀⠱⠜⠫⠀⠟⠨⠻⠀⠱⠘` + - first differing cell (zero-based): 67 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` + ### `closed_roman_parenthetical_after_non_ascii_letter_boundary` Of the 63959 candidates, 10088 are the actual `pending_rule_review` subcluster. The other 53871 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. @@ -3134,7 +3198,7 @@ The generalized fix makes an explicit `EnterEnglish` event idempotent when final Korean rule 34 (2024 Korean-rules PDF p.29) prints the opening enclosure before Roman entry in `링컨(Lincoln)`: the opening sequence is `⠦⠄⠴`. This output-position cohort finds a closed, non-nested parenthetical whose body begins with an ASCII letter and whose opening does not immediately follow another ASCII letter, then locates its complete current-engine signature without consulting the reference. Its localized boundary includes the two current output cells immediately before the opening plus the first three entry cells, so rule-11 math spacing can be separated from a difference later inside the parenthetical. Direct function-call shapes such as `f(x)` are excluded. The PDF's math rule 6 (p.59), rule 12 (pp.63-64), and rule 45 (p.75) nevertheless leave standalone `(x)` and other Roman-letter parenthetical mathematics as counterexamples, so the surface gate is not an engine-routing predicate. -The cross-cutting input cohort contains 63959 candidates: 52384 exact controls and 11575 mismatches. Mismatch primary classes remain unchanged: 10088 `pending_rule_review`, 1107 `corpus_suspect`, 226 `comparison_method`, and 154 `unsupported_character_review`. Of 1245 evaluable mismatches, 11575 have the first difference at the detected leading-spacing/entry boundary; these include 436 `U+2826 ⠦ -> U+2834 ⠴`, 27 `U+2834 ⠴ -> U+2826 ⠦`, and 325 `U+2826 ⠦ -> U+2800 ⠀` transitions; the exact localized reverse `U+2800 ⠀ -> U+2826 ⠦` occurs 0 times. After all earlier localized cohorts and this cohort are excluded, the raw-to-residual target count is 469 -> 26, the Roman-indicator reverse count is 32 -> 2, and the spacing target/reverse counts are 440 -> 79 and 1 -> 1. The short full-encoder form `웹3(Web3)` emits the PDF opening order as a routing control, while whitespace and quote boundaries reproduce the current Roman-first opening. Representative localized samples, with shard and index, are retained in the generated cluster sample table. Because exact controls are abundant and the PDF does not make this input shape semantically sufficient to exclude mathematics, no engine change or primary reclassification is inferred. The one raw/residual spacing reverse is a Korean-body `△(교육)` layout boundary, not a Roman-parenthetical member, and remains a separate rule-72/layout review. +The cross-cutting input cohort contains 63959 candidates: 52384 exact controls and 11575 mismatches. Mismatch primary classes remain unchanged: 10088 `pending_rule_review`, 1107 `corpus_suspect`, 226 `comparison_method`, and 154 `unsupported_character_review`. Of 11575 evaluable mismatches, 1245 have the first difference at the detected leading-spacing/entry boundary; these include 436 `U+2826 ⠦ -> U+2834 ⠴`, 27 `U+2834 ⠴ -> U+2826 ⠦`, and 325 `U+2826 ⠦ -> U+2800 ⠀` transitions; the exact localized reverse `U+2800 ⠀ -> U+2826 ⠦` occurs 0 times. After all earlier localized cohorts and this cohort are excluded, the raw-to-residual target count is 469 -> 26, the Roman-indicator reverse count is 32 -> 2, and the spacing target/reverse counts are 440 -> 79 and 1 -> 1. The short full-encoder form `웹3(Web3)` emits the PDF opening order as a routing control, while whitespace and quote boundaries reproduce the current Roman-first opening. Representative localized samples, with shard and index, are retained in the generated cluster sample table. Because exact controls are abundant and the PDF does not make this input shape semantically sufficient to exclude mathematics, no engine change or primary reclassification is inferred. The one raw/residual spacing reverse is a Korean-body `△(교육)` layout boundary, not a Roman-parenthetical member, and remains a separate rule-72/layout review. The HCA-style headword-expansion shape described above is not an engine implementation premise. The 2024 PDF's math rule 6 defines parentheses and grouping parentheses, rule 11 defines mathematical-expression spacing, rule 12 covers Roman letters in formulas as well as Korean sentences, and rule 45 shows Roman-letter function notation followed by parentheses. Excluding visible operators, scripts, and nesting narrows this corpus cohort, but the PDF does not make the remaining surface shape sufficient to rule out every mathematical counterexample. The cluster therefore remains conservative pending-review evidence only. @@ -3223,6 +3287,12 @@ Korean rule 69 attachment 2 (2024 Korean-rules PDF p.50, printed p.44) explicitl After the correction, this cohort has 7 candidates / 5 exact / 2 mismatch, preserving 2 `pending_rule_review`, 0 `corpus_suspect`, 0 `comparison_method`, and 0 `unsupported_character_review` mismatch primaries. Of 2 evaluable current mismatches, 0 localize to the independently encoded complete unit pair: 0 `U+2810 ⠐ -> U+2802 ⠂` and 0 `U+2802 ⠂ -> U+2810 ⠐`. Five cases become exact; the other two retain independent earlier differences. One of those five is also in the Roman-tail cohort, leaving four disjoint `%p` gains. Thus +174 in the numeric-list cohort, +3 in the Roman-tail cohort, and +4 disjoint here account for all +181 corpus exact gains, with zero exact-set regressions. The engine contains only the general rule-41 same-token boundary, not a `%p` special case. +### Attached Korean `있다` spacing normalization + +Korean rule 49 (2024 Korean-rules PDF p.37, printed p.31) says that spacing follows the print input. The PDF consistently retains an explicit space in `그리고 있다` (physical p.18), `살고 있다` (p.26), and `수강하고 있다` (p.30), but it gives no example authorizing a transcriber to insert a missing print space. The current token normalizer nevertheless splits any Korean token ending in attached `있다`. + +The diagnostic baseline has 95 candidates / 0 exact / 95 mismatch, preserving 92 `pending_rule_review`, 2 `corpus_suspect`, 0 `comparison_method`, and 1 `unsupported_character_review` mismatch primaries. Of 95 evaluable mismatches, 74 put the first difference exactly at the independently located inserted blank: 73 `U+2815 ⠕ -> U+2800 ⠀`, 1 `U+2823 ⠣ -> U+2800 ⠀`, and 0 `U+2800 ⠀ -> U+2815 ⠕`. There are no exact members and no localized reverse; the three explicitly spaced PDF forms are the independent negative controls. This checkpoint records the scope before testing removal of the input-correcting normalizer; it does not infer a branch from the corpus reference. + Current uppercase-Roman hyphen-digits measurement: 571 candidates, 191 exact controls, 380 mismatches, 365 members in the actual `pending_rule_review` subcluster, and 262/380 evaluable mismatches whose first difference is inside the target run plus its entry boundary. It remains distinct from parenthesized digits and headword expansions; no engine change is inferred. Current uppercase alphanumeric Roman-digit sequence measurement: 3429 candidates, 1916 exact controls, 1513 mismatches, 1404 members in the actual `pending_rule_review` subcluster, and 151/1513 evaluable mismatches whose first difference is localized to the current entry cells. The target `⠴ -> blank` occurs 93 times and the reverse occurs 0 times. Korean rule 35 (2024 Korean-rules PDF pp.29-30, printed pp.23-24) explicitly treats `MP3`, `A4`, `KF94`, and `D-100` as Roman-number sequences. Math rules 11/12 (PDF pp.63-65, printed pp.57-59) separately route mathematical Roman notation without the prose Roman indicator and with two-cell spacing. Official UEB 6.5.1-6.5.2 (2024 UEB PDF p.96, printed p.68) determines numeric/grade-1 continuation only after the surrounding mode has been chosen. Thus surfaces such as `A-3` cannot be assigned identifier or variable semantics from this structure alone. Exact controls and both directions are retained; no engine change is inferred. diff --git a/libs/braillify/examples/nikl_corpus_analyze.rs b/libs/braillify/examples/nikl_corpus_analyze.rs index 43696279..8c244e8a 100644 --- a/libs/braillify/examples/nikl_corpus_analyze.rs +++ b/libs/braillify/examples/nikl_corpus_analyze.rs @@ -585,6 +585,7 @@ const KOREAN_MAJORITY_ROMAN_SANDWICH_NON_DOMAIN: &str = const KOREAN_INLINE_PARENTHESIZED_OPERATOR: &str = "korean_inline_parenthesized_single_arithmetic_operator"; const TIGHT_TRIANGLE_BEFORE_KOREAN: &str = "tight_triangle_mark_immediately_before_korean"; +const ATTACHED_KOREAN_AUXILIARY_ITDA_SPACING: &str = "attached_korean_auxiliary_itda_spacing"; const ALLCAPS_ROMAN_RUN_CONTAINING_OU: &str = "allcaps_roman_run_containing_ou"; const ALLCAPS_ROMAN_RUN_CONTAINING_ST: &str = "allcaps_roman_run_containing_st"; const ALLCAPS_ROMAN_RUN_CONTAINING_AR: &str = "allcaps_roman_run_containing_ar"; @@ -2612,6 +2613,7 @@ fn first_difference_claimed_by_localized_cohort(item: &EncodedCase) -> bool { || first_difference_at_spaced_numeric_list_comma(item) || first_difference_at_ascii_roman_comma_before_digit_korean(item) || first_difference_at_percent_point_unit_list_comma(item) + || first_difference_at_attached_korean_auxiliary_itda_spacing(item) } /// Input-only candidate gate for acronym expansions such as @@ -3224,6 +3226,64 @@ fn first_difference_in_tight_triangle(item: &EncodedCase) -> bool { .any(|range| range.contains(&first_difference)) } +/// Mirrors the existing token-normalization rule's narrow input shape: a +/// whitespace-delimited Korean token ends in `있다` (optionally followed by a +/// full stop) but contains no printed space before that suffix. Membership is +/// descriptive; it does not decide whether orthography may override rule 49's +/// instruction to follow print spacing. +fn attached_korean_auxiliary_itda_spans(input: &str) -> Vec { + let mut spans = Vec::new(); + let mut word_start = 0usize; + for word in input.split_inclusive(char::is_whitespace) { + let body = word.trim_end_matches(char::is_whitespace); + let suffix = ["있다.", "있다"] + .into_iter() + .find(|suffix| body.ends_with(suffix)); + if let Some(suffix) = suffix { + let suffix_start = body.len() - suffix.len(); + let prefix = &body[..suffix_start]; + if !prefix.is_empty() && prefix.chars().any(is_korean_script) { + spans.push(InputSpan { + start_byte: word_start + suffix_start, + end_byte: word_start + body.len(), + }); + } + } + word_start += word.len(); + } + spans +} + +/// Locates only the blank currently inserted immediately before `있다`, using +/// the real input prefix rather than any reference cell. +fn attached_korean_auxiliary_itda_actual_ranges( + input: &str, + actual: &str, +) -> Vec> { + let actual_cells = actual.chars().collect::>(); + attached_korean_auxiliary_itda_spans(input) + .into_iter() + .filter_map(|span| { + let prefix = braillify::encode_to_unicode(&input[..span.start_byte]).ok()?; + let start = prefix.chars().count(); + (actual_cells.get(start) == Some(&'⠀')).then_some(start..start + 1) + }) + .collect() +} + +fn first_difference_at_attached_korean_auxiliary_itda_spacing(item: &EncodedCase) -> bool { + let Ok(actual) = &item.actual else { + return false; + }; + if actual == &item.located.case.unicode { + return false; + } + let first_difference = first_difference_cell(&item.located.case.unicode, actual); + attached_korean_auxiliary_itda_actual_ranges(&item.located.case.input, actual) + .into_iter() + .any(|range| range.contains(&first_difference)) +} + fn enum_key(value: &T) -> String { serde_json::to_value(value) .expect("enum serialization must succeed") @@ -3543,6 +3603,10 @@ fn analyze( TIGHT_TRIANGLE_BEFORE_KOREAN.to_string(), PendingRuleReviewClusterStats::default(), ), + ( + ATTACHED_KOREAN_AUXILIARY_ITDA_SPACING.to_string(), + PendingRuleReviewClusterStats::default(), + ), ( UPPERCASE_ROMAN_HEADWORD_EXPANSION.to_string(), PendingRuleReviewClusterStats::default(), @@ -3840,6 +3904,14 @@ fn analyze( Some(first_difference_in_tight_triangle(item)), false, ), + ( + ATTACHED_KOREAN_AUXILIARY_ITDA_SPACING, + !attached_korean_auxiliary_itda_spans(&item.located.case.input).is_empty(), + Some(first_difference_at_attached_korean_auxiliary_itda_spacing( + item, + )), + true, + ), ( UPPERCASE_ROMAN_HEADWORD_EXPANSION, has_uppercase_roman_headword_expansion(&item.located.case.input), @@ -4469,7 +4541,11 @@ fn markdown(report: &AnalysisReport) -> String { rule-33/34 punctuation cell in the localized signature, and does not infer new units. The \ `tight_triangle_mark_immediately_before_korean` gate requires literal \ `△한글` with no input space and includes the first following Korean cell in its localized \ - output range, so an observed missing-space difference is measured at the mark boundary.\n\n", + output range, so an observed missing-space difference is measured at the mark boundary. \ + The `attached_korean_auxiliary_itda_spacing` gate mirrors the current normalization \ + shape for a Korean token ending in attached `있다`; its localizer independently encodes \ + the real prefix and claims only the blank inserted before the suffix. It does not decide \ + whether orthographic correction may override the printed input.\n\n", ); text.push_str( "| Cluster | Candidates | Exact | Mismatch | Conflicting-reference cases |\n\ @@ -4944,8 +5020,8 @@ fn markdown(report: &AnalysisReport) -> String { primary_count("corpus_suspect"), primary_count("comparison_method"), primary_count("unsupported_character_review"), - stats.first_difference_in_output_signature, stats.output_signature_mismatches_evaluated, + stats.first_difference_in_output_signature, localized_transition(target), localized_transition(reverse), localized_transition(open_to_space), @@ -5705,6 +5781,57 @@ fn markdown(report: &AnalysisReport) -> String { localized_count(ueb_to_korean), )); } + if let Some(stats) = report + .pending_rule_review_clusters + .get(ATTACHED_KOREAN_AUXILIARY_ITDA_SPACING) + { + let primary_count = |name: &str| { + stats + .mismatch_primary_classes + .get(name) + .copied() + .unwrap_or(0) + }; + let localized_count = |name: &str| { + stats + .first_difference_in_output_signature_transitions + .get(name) + .copied() + .unwrap_or(0) + }; + let expected_attached = "U+2815 ⠕ -> U+2800 ⠀"; + let expected_spaced = "U+2800 ⠀ -> U+2815 ⠕"; + let other_attached = "U+2823 ⠣ -> U+2800 ⠀"; + text.push_str(&format!( + "\n### Attached Korean `있다` spacing normalization\n\n\ + Korean rule 49 (2024 Korean-rules PDF p.37, printed p.31) says that spacing follows \ + the print input. The PDF consistently retains an explicit space in `그리고 있다` \ + (physical p.18), `살고 있다` (p.26), and `수강하고 있다` (p.30), but it gives no \ + example authorizing a transcriber to insert a missing print space. The current \ + token normalizer nevertheless splits any Korean token ending in attached `있다`.\n\n\ + The diagnostic baseline has {} candidates / {} exact / {} mismatch, preserving {} \ + `pending_rule_review`, {} `corpus_suspect`, {} `comparison_method`, and {} \ + `unsupported_character_review` mismatch primaries. Of {} evaluable mismatches, {} \ + put the first difference exactly at the independently located inserted blank: {} \ + `{expected_attached}`, {} `{other_attached}`, and {} `{expected_spaced}`. There are \ + no exact members and no localized reverse; the three explicitly spaced PDF forms \ + are the independent negative controls. This checkpoint records the scope before \ + testing removal of the input-correcting normalizer; it does not infer a branch from \ + the corpus reference.\n", + stats.candidates, + stats.exact, + stats.mismatch, + primary_count("pending_rule_review"), + primary_count("corpus_suspect"), + primary_count("comparison_method"), + primary_count("unsupported_character_review"), + stats.output_signature_mismatches_evaluated, + stats.first_difference_in_output_signature, + localized_count(expected_attached), + localized_count(other_attached), + localized_count(expected_spaced), + )); + } if let Some(stats) = report .pending_rule_review_clusters .get(UPPERCASE_ROMAN_HYPHEN_DIGITS) @@ -7832,6 +7959,31 @@ mod tests { assert!(ranges[0].end <= actual.chars().count()); } + #[rstest::rstest] + #[case::corpus_attached_suffix("성장을 하고있다.", vec!["있다."])] + #[case::pdf_printed_space("그림을 그리고 있다.", vec![])] + #[case::independent_suffix("있다.", vec![])] + fn detects_only_attached_korean_auxiliary_itda( + #[case] input: &str, + #[case] expected: Vec<&str>, + ) { + let actual = attached_korean_auxiliary_itda_spans(input) + .into_iter() + .map(|span| &input[span.start_byte..span.end_byte]) + .collect::>(); + assert_eq!(actual, expected); + } + + #[test] + fn localizes_current_inserted_blank_before_attached_itda() { + let input = "성장을 하고있다."; + let actual = braillify::encode_to_unicode(input).expect("spacing probe must encode"); + let ranges = attached_korean_auxiliary_itda_actual_ranges(input, &actual); + + assert_eq!(ranges.len(), 1); + assert_eq!(actual.chars().nth(ranges[0].start), Some('⠀')); + } + #[test] fn aggregates_headword_expansion_outcomes_without_reclassification() { let cases = [ From 60f10c5357dbb203f73cff88f5c2093b9832f231 Mon Sep 17 00:00:00 2001 From: devfive Date: Tue, 1 Sep 2026 17:03:01 +0900 Subject: [PATCH 52/77] Correct rule 47 printed spacing --- test_cases/korean/rule_47.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test_cases/korean/rule_47.json b/test_cases/korean/rule_47.json index f196d202..ce7a19e0 100644 --- a/test_cases/korean/rule_47.json +++ b/test_cases/korean/rule_47.json @@ -59,7 +59,7 @@ "jeomsarang": "" }, { - "input": "지구 표면의 2/3는 바다로 덮여있다.", + "input": "지구 표면의 2/3는 바다로 덮여 있다.", "internal": ".o@m`d+e*w`#b_/#c`cz`^i\"u`is4:`o/i4", "expected": "402181302544173358060356126090953024101637010145049021121050", "unicode": "⠨⠕⠈⠍⠀⠙⠬⠑⠡⠺⠀⠼⠃⠸⠌⠼⠉⠀⠉⠵⠀⠘⠊⠐⠥⠀⠊⠎⠲⠱⠀⠕⠌⠊⠲", From 9a8dee12846944710910d019c7fc48b0b5df523e Mon Sep 17 00:00:00 2001 From: devfive Date: Tue, 1 Sep 2026 17:09:12 +0900 Subject: [PATCH 53/77] Follow printed Korean auxiliary spacing --- docs/corpus-analysis/NIKL_2025_V1.md | 332 +++++++++--------- .../braillify/examples/nikl_corpus_analyze.rs | 41 ++- .../src/rules/token_rules/spacing.rs | 102 ++---- 3 files changed, 222 insertions(+), 253 deletions(-) diff --git a/docs/corpus-analysis/NIKL_2025_V1.md b/docs/corpus-analysis/NIKL_2025_V1.md index 1c67400e..3e650d64 100644 --- a/docs/corpus-analysis/NIKL_2025_V1.md +++ b/docs/corpus-analysis/NIKL_2025_V1.md @@ -7,9 +7,9 @@ | Metric | Count | |---|---:| | Total | 83528 | -| Exact | 68368 | -| Mismatch | 15160 | -| Exact accuracy | 81.85% | +| Exact | 68439 | +| Mismatch | 15089 | +| Exact accuracy | 81.94% | | Duplicate records | 0 | | Inputs with conflicting references | 0 | @@ -19,21 +19,21 @@ Primary classes are evidence gates, not permissions to change the engine. `imple | Primary class | Count | |---|---:| -| `comparison_method` | 296 | -| `corpus_suspect` | 1107 | -| `exact` | 68368 | -| `pending_rule_review` | 13582 | -| `unsupported_character_review` | 175 | +| `comparison_method` | 297 | +| `corpus_suspect` | 1108 | +| `exact` | 68439 | +| `pending_rule_review` | 13510 | +| `unsupported_character_review` | 174 | | Reproducible reason | Count | |---|---:| -| `exact` | 68368 | -| `foreign_text_rule_review` | 13418 | -| `nfkc_input_equivalent` | 296 | -| `number_rule_review` | 156 | +| `exact` | 68439 | +| `foreign_text_rule_review` | 13358 | +| `nfkc_input_equivalent` | 297 | +| `number_rule_review` | 144 | | `punctuation_rule_review` | 8 | -| `rule34_roman_indicator_before_opening_parenthesis` | 1107 | -| `unsupported_character_review` | 175 | +| `rule34_roman_indicator_before_opening_parenthesis` | 1108 | +| `unsupported_character_review` | 174 | ## Pending first-difference cell transitions @@ -49,7 +49,7 @@ This ranking is a diagnostic selector, not an implementation rule. It counts onl | 6 | `U+280E ⠎ -> U+280C ⠌` | 448 | | 7 | `U+2826 ⠦ -> U+2800 ⠀` | 440 | | 8 | `U+2801 ⠁ -> U+281C ⠜` | 405 | -| 9 | `U+2800 ⠀ -> U+2807 ⠇` | 363 | +| 9 | `U+2800 ⠀ -> U+2807 ⠇` | 364 | | 10 | `U+283C ⠼ -> U+2800 ⠀` | 358 | | 11 | `U+2834 ⠴ -> U+2830 ⠰` | 347 | | 12 | `U+2811 ⠑ -> U+282B ⠫` | 329 | @@ -551,54 +551,54 @@ This ranking removes only cases whose first difference is inside an existing out ## Cross-cutting input-only structural cohorts -These are cross-cutting input-only structural cohorts, not new primary classes and not engine routing rules. Candidate selection never changes a case's existing primary class by itself. Only cohort members already classified as `pending_rule_review` form a pending subcluster; exact and other-primary members are controls that retain their existing outcomes. A separate classifier may use independently justified, output-localized PDF evidence, as in the rule-34 three-cell contradiction below. The `uppercase_roman_headword_closed_multiword_parenthetical` gate requires a two-or-more character uppercase ASCII headword immediately followed by a closed parenthesis whose contents are two or more ASCII Roman words separated only by spaces. Because the contents admit only letters and spaces, visible operators, subscript/superscript notation, and nested parentheses are excluded deterministically. The `mixed_roman_korean_word_before_uppercase_headword_expansion` gate further requires a whitespace-delimited preceding word that contains both ASCII Roman and Korean and ends in Korean. It locates the following uppercase headword itself, including its immediately preceding emitted cell, so an earlier Roman entry in the same sentence cannot satisfy the output audit. The `single_capital_followed_by_parenthesized_digits` gate requires a standalone capital, a non-empty closed ASCII-digit parenthetical, and alphanumeric outer boundaries. It likewise includes the emitted entry-boundary cell in localization. The `uppercase_roman_run_followed_by_hyphen_digits` gate requires a maximal uppercase ASCII run, literal ASCII hyphen-minus, a non-empty digit run, and alphanumeric outer boundaries. Its localized range includes the current encoded run and its immediately preceding cell, keeping `F-35`-style routing separate from both parenthesized digits and headword expansions. The `uppercase_alphanumeric_roman_digit_sequence` gate covers the wider rule-35/math collision: a capital-led uppercase/digit sequence with optional internal ASCII hyphen/full stop and trailing prose comma/colon/semicolon. It localizes only the current entry cells and does not decide whether an `A-3`-shaped surface is an identifier or a mathematical expression. The `standalone_multi_character_uppercase_roman_word` gate finds maximal ASCII-letter runs of two or more capitals with non-alphanumeric boundaries; a run immediately followed by `(` is excluded so the HCA-style headword itself is not counted by both gates. The `korean_prefixed_closed_allcaps_parenthetical` gate requires an immediately preceding Korean character and a closed body of two or more uppercase ASCII letters. It intentionally contains both acronym annotations (`책임자(COO)`) and scientific formulae (`일산화탄소(CO)`) so their semantic collision remains measurable. The `korean_prefixed_closed_roman_annotation_rule_34_order` gate accepts the narrower rule-34 body grammar after an immediately preceding Korean character and localizes only the current engine's Korean opening-parenthesis cells. This distinguishes the PDF's parenthesis-before-Roman-indicator order from unrelated differences later in the same sentence. The `multi_character_allcaps_roman_runs_joined_by_middle_dot` gate requires two maximal ASCII-letter runs of at least two capitals joined directly by U+00B7, with non-alphanumeric outer boundaries. It records shapes such as `AI·SW` without assigning prose, mathematics, or science semantics. The `roman_run_immediately_before_attached_middle_dot_boundary` gate is output-localized: it requires a maximal ASCII-letter run immediately before U+00B7 and an attached Korean character or ASCII-letter run after it, then searches for that whole current-engine signature in the actual output. It therefore isolates the Roman terminator boundary without treating unrelated middle dots elsewhere in the sentence as causal. The `attached_ascii_roman_to_korean_script_boundary` gate requires an ASCII letter run immediately followed by Korean script and localizes only the current mode marker there: either rule 29's final `⠲` or rule 39's opening `⠸⠷`. It deliberately does not infer the dominant language of the sentence from that surface boundary. The `korean_majority_same_token_roman_sandwich_non_domain` scope audit mirrors the narrower rule-39 implementation gate: the nearest script characters on both sides of one Korean segment are ASCII Roman, the input is Korean-majority by first-script word counts, and the segment is not dot-delimited like the official domain example. It measures change scope but is not itself an output-localized causal classifier. The `korean_inline_parenthesized_single_arithmetic_operator` gate requires an immediate `Korean(` + one rule-45 arithmetic operator + `)Korean` span. Unlike broad coexistence traits, it also locates the current engine's emitted structure and counts a mismatch as signature-local only when the sentence's first differing cell falls inside that output range. The `allcaps_roman_run_containing_ou` gate finds maximal, alphanumeric-delimited uppercase ASCII runs containing adjacent `OU`. It locates the independently encoded run signature in the complete current output and counts only first differences inside that signature as localized. The `allcaps_roman_run_containing_st` gate applies the same output-position requirement to adjacent `ST`; it is kept separate because UEB 10.12 makes contraction use depend on how an abbreviation or acronym is pronounced. The `attached_ascii_roman_segments_joined_by_ampersand` gate requires non-empty ASCII-letter segments joined directly by `&`, excludes spaced/Korean/alphanumeric continuations, and localizes only the current output cell immediately before the ampersand through an independently encoded real-input prefix. The `ampersand_before_attached_ascii_roman_segment` gate covers the distinct one-sided UEB `&c` boundary: `&` is followed by a complete ASCII-letter segment, while an ASCII alphanumeric or another ampersand immediately before it and a digit continuation after it are excluded. Its output range is anchored by independently encoding the real input prefix before each occurrence, then includes only the current Rule-71/29 entry boundary; a second pre-fix anchor through `&` retains the former exit location. The `consecutive_ascii_roman_words_whitespace_boundary` gate requires two adjacent ASCII-letter words separated only by whitespace. For each boundary it independently encodes the real input prefix ending after the first word, then localizes only the current rule-29 terminator at that position or the full output's replacing blank. This prevents an unrelated Roman occurrence elsewhere in the sentence from satisfying the audit. Slash and other punctuation-separated forms are deliberately excluded. The `decimal_point_between_ascii_digits` gate finds whitespace-delimited words containing `digit.digit` and reproduces each whole word in a neutral Korean context, so suffixes and punctuation remain part of the current-engine signature. The `compact_numeric_ascii_letter_suffix` gate finds a numeric prefix immediately followed by ASCII letters, includes the immediately preceding output cell as its entry boundary, and retains suffix-specific outcome counts. It intentionally includes both possible rule-69 units and ambiguous variable/identifier forms; membership alone does not assign unit semantics. The `rule69_ascii_unit_before_terminator_skipping_symbol` gate is narrower: it accepts only ASCII unit spellings already supported by rule 69, includes the immediately following rule-33/34 punctuation cell in the localized signature, and does not infer new units. The `tight_triangle_mark_immediately_before_korean` gate requires literal `△한글` with no input space and includes the first following Korean cell in its localized output range, so an observed missing-space difference is measured at the mark boundary. The `attached_korean_auxiliary_itda_spacing` gate mirrors the current normalization shape for a Korean token ending in attached `있다`; its localizer independently encodes the real prefix and claims only the blank inserted before the suffix. It does not decide whether orthographic correction may override the printed input. +These are cross-cutting input-only structural cohorts, not new primary classes and not engine routing rules. Candidate selection never changes a case's existing primary class by itself. Only cohort members already classified as `pending_rule_review` form a pending subcluster; exact and other-primary members are controls that retain their existing outcomes. A separate classifier may use independently justified, output-localized PDF evidence, as in the rule-34 three-cell contradiction below. The `uppercase_roman_headword_closed_multiword_parenthetical` gate requires a two-or-more character uppercase ASCII headword immediately followed by a closed parenthesis whose contents are two or more ASCII Roman words separated only by spaces. Because the contents admit only letters and spaces, visible operators, subscript/superscript notation, and nested parentheses are excluded deterministically. The `mixed_roman_korean_word_before_uppercase_headword_expansion` gate further requires a whitespace-delimited preceding word that contains both ASCII Roman and Korean and ends in Korean. It locates the following uppercase headword itself, including its immediately preceding emitted cell, so an earlier Roman entry in the same sentence cannot satisfy the output audit. The `single_capital_followed_by_parenthesized_digits` gate requires a standalone capital, a non-empty closed ASCII-digit parenthetical, and alphanumeric outer boundaries. It likewise includes the emitted entry-boundary cell in localization. The `uppercase_roman_run_followed_by_hyphen_digits` gate requires a maximal uppercase ASCII run, literal ASCII hyphen-minus, a non-empty digit run, and alphanumeric outer boundaries. Its localized range includes the current encoded run and its immediately preceding cell, keeping `F-35`-style routing separate from both parenthesized digits and headword expansions. The `uppercase_alphanumeric_roman_digit_sequence` gate covers the wider rule-35/math collision: a capital-led uppercase/digit sequence with optional internal ASCII hyphen/full stop and trailing prose comma/colon/semicolon. It localizes only the current entry cells and does not decide whether an `A-3`-shaped surface is an identifier or a mathematical expression. The `standalone_multi_character_uppercase_roman_word` gate finds maximal ASCII-letter runs of two or more capitals with non-alphanumeric boundaries; a run immediately followed by `(` is excluded so the HCA-style headword itself is not counted by both gates. The `korean_prefixed_closed_allcaps_parenthetical` gate requires an immediately preceding Korean character and a closed body of two or more uppercase ASCII letters. It intentionally contains both acronym annotations (`책임자(COO)`) and scientific formulae (`일산화탄소(CO)`) so their semantic collision remains measurable. The `korean_prefixed_closed_roman_annotation_rule_34_order` gate accepts the narrower rule-34 body grammar after an immediately preceding Korean character and localizes only the current engine's Korean opening-parenthesis cells. This distinguishes the PDF's parenthesis-before-Roman-indicator order from unrelated differences later in the same sentence. The `multi_character_allcaps_roman_runs_joined_by_middle_dot` gate requires two maximal ASCII-letter runs of at least two capitals joined directly by U+00B7, with non-alphanumeric outer boundaries. It records shapes such as `AI·SW` without assigning prose, mathematics, or science semantics. The `roman_run_immediately_before_attached_middle_dot_boundary` gate is output-localized: it requires a maximal ASCII-letter run immediately before U+00B7 and an attached Korean character or ASCII-letter run after it, then searches for that whole current-engine signature in the actual output. It therefore isolates the Roman terminator boundary without treating unrelated middle dots elsewhere in the sentence as causal. The `attached_ascii_roman_to_korean_script_boundary` gate requires an ASCII letter run immediately followed by Korean script and localizes only the current mode marker there: either rule 29's final `⠲` or rule 39's opening `⠸⠷`. It deliberately does not infer the dominant language of the sentence from that surface boundary. The `korean_majority_same_token_roman_sandwich_non_domain` scope audit mirrors the narrower rule-39 implementation gate: the nearest script characters on both sides of one Korean segment are ASCII Roman, the input is Korean-majority by first-script word counts, and the segment is not dot-delimited like the official domain example. It measures change scope but is not itself an output-localized causal classifier. The `korean_inline_parenthesized_single_arithmetic_operator` gate requires an immediate `Korean(` + one rule-45 arithmetic operator + `)Korean` span. Unlike broad coexistence traits, it also locates the current engine's emitted structure and counts a mismatch as signature-local only when the sentence's first differing cell falls inside that output range. The `allcaps_roman_run_containing_ou` gate finds maximal, alphanumeric-delimited uppercase ASCII runs containing adjacent `OU`. It locates the independently encoded run signature in the complete current output and counts only first differences inside that signature as localized. The `allcaps_roman_run_containing_st` gate applies the same output-position requirement to adjacent `ST`; it is kept separate because UEB 10.12 makes contraction use depend on how an abbreviation or acronym is pronounced. The `attached_ascii_roman_segments_joined_by_ampersand` gate requires non-empty ASCII-letter segments joined directly by `&`, excludes spaced/Korean/alphanumeric continuations, and localizes only the current output cell immediately before the ampersand through an independently encoded real-input prefix. The `ampersand_before_attached_ascii_roman_segment` gate covers the distinct one-sided UEB `&c` boundary: `&` is followed by a complete ASCII-letter segment, while an ASCII alphanumeric or another ampersand immediately before it and a digit continuation after it are excluded. Its output range is anchored by independently encoding the real input prefix before each occurrence, then includes only the current Rule-71/29 entry boundary; a second pre-fix anchor through `&` retains the former exit location. The `consecutive_ascii_roman_words_whitespace_boundary` gate requires two adjacent ASCII-letter words separated only by whitespace. For each boundary it independently encodes the real input prefix ending after the first word, then localizes only the current rule-29 terminator at that position or the full output's replacing blank. This prevents an unrelated Roman occurrence elsewhere in the sentence from satisfying the audit. Slash and other punctuation-separated forms are deliberately excluded. The `decimal_point_between_ascii_digits` gate finds whitespace-delimited words containing `digit.digit` and reproduces each whole word in a neutral Korean context, so suffixes and punctuation remain part of the current-engine signature. The `compact_numeric_ascii_letter_suffix` gate finds a numeric prefix immediately followed by ASCII letters, includes the immediately preceding output cell as its entry boundary, and retains suffix-specific outcome counts. It intentionally includes both possible rule-69 units and ambiguous variable/identifier forms; membership alone does not assign unit semantics. The `rule69_ascii_unit_before_terminator_skipping_symbol` gate is narrower: it accepts only ASCII unit spellings already supported by rule 69, includes the immediately following rule-33/34 punctuation cell in the localized signature, and does not infer new units. The `tight_triangle_mark_immediately_before_korean` gate requires literal `△한글` with no input space and includes the first following Korean cell in its localized output range, so an observed missing-space difference is measured at the mark boundary. The `attached_korean_auxiliary_itda_spacing` gate mirrors the former normalization shape for a Korean token ending in attached `있다`; its localizer independently encodes the real prefix and claims only the blank inserted before the suffix. It does not decide whether orthographic correction may override the printed input. | Cluster | Candidates | Exact | Mismatch | Conflicting-reference cases | |---|---:|---:|---:|---:| -| `allcaps_roman_run_beginning_with_pure_letter_shortform` | 4120 | 2477 | 1643 | 0 | +| `allcaps_roman_run_beginning_with_pure_letter_shortform` | 4120 | 2478 | 1642 | 0 | | `allcaps_roman_run_containing_ar` | 1022 | 363 | 659 | 0 | -| `allcaps_roman_run_containing_ed` | 816 | 319 | 497 | 0 | +| `allcaps_roman_run_containing_ed` | 816 | 320 | 496 | 0 | | `allcaps_roman_run_containing_ou` | 1816 | 64 | 1752 | 0 | | `allcaps_roman_run_containing_st` | 1479 | 686 | 793 | 0 | | `ampersand_before_attached_ascii_roman_segment` | 30 | 12 | 18 | 0 | | `ascii_roman_tail_comma_before_digit_korean_token` | 58 | 3 | 55 | 0 | | `attached_ascii_roman_segments_joined_by_ampersand` | 802 | 285 | 517 | 0 | -| `attached_ascii_roman_to_korean_script_boundary` | 17693 | 12966 | 4727 | 0 | -| `attached_korean_auxiliary_itda_spacing` | 95 | 0 | 95 | 0 | -| `closed_roman_parenthetical_after_non_ascii_letter_boundary` | 63959 | 52384 | 11575 | 0 | -| `compact_numeric_ascii_letter_suffix` | 2975 | 1877 | 1098 | 0 | -| `consecutive_ascii_roman_words_whitespace_boundary` | 4679 | 2134 | 2545 | 0 | -| `decimal_point_between_ascii_digits` | 4546 | 3394 | 1152 | 0 | +| `attached_ascii_roman_to_korean_script_boundary` | 17693 | 12973 | 4720 | 0 | +| `attached_korean_auxiliary_itda_spacing` | 95 | 71 | 24 | 0 | +| `closed_roman_parenthetical_after_non_ascii_letter_boundary` | 63959 | 52436 | 11523 | 0 | +| `compact_numeric_ascii_letter_suffix` | 2975 | 1878 | 1097 | 0 | +| `consecutive_ascii_roman_words_whitespace_boundary` | 4679 | 2138 | 2541 | 0 | +| `decimal_point_between_ascii_digits` | 4546 | 3397 | 1149 | 0 | | `korean_inline_parenthesized_single_arithmetic_operator` | 23 | 17 | 6 | 0 | | `korean_majority_same_token_roman_sandwich_non_domain` | 947 | 386 | 561 | 0 | -| `korean_prefixed_closed_allcaps_parenthetical` | 54492 | 45148 | 9344 | 0 | -| `korean_prefixed_closed_roman_annotation_rule_34_order` | 64382 | 52990 | 11392 | 0 | +| `korean_prefixed_closed_allcaps_parenthetical` | 54492 | 45187 | 9305 | 0 | +| `korean_prefixed_closed_roman_annotation_rule_34_order` | 64382 | 53042 | 11340 | 0 | | `korean_prefixed_roman_parenthetical_followed_by_allcaps_hyphen_suffix` | 11 | 0 | 11 | 0 | | `mixed_roman_korean_word_before_uppercase_headword_expansion` | 10 | 0 | 10 | 0 | | `multi_character_allcaps_roman_runs_joined_by_middle_dot` | 97 | 0 | 97 | 0 | | `percent_point_unit_list_comma` | 7 | 5 | 2 | 0 | -| `pure_allcaps_segment_before_hyphen_and_multi_allcaps_segment_after` | 448 | 132 | 316 | 0 | +| `pure_allcaps_segment_before_hyphen_and_multi_allcaps_segment_after` | 448 | 133 | 315 | 0 | | `roman_hyphenated_word_after_whitespace_following_korean_word` | 361 | 0 | 361 | 0 | -| `roman_parenthetical_headword_after_whitespace_following_korean_word` | 4695 | 3407 | 1288 | 0 | +| `roman_parenthetical_headword_after_whitespace_following_korean_word` | 4695 | 3411 | 1284 | 0 | | `roman_run_after_whitespace_following_closed_roman_enclosure` | 1093 | 142 | 951 | 0 | | `roman_run_immediately_before_attached_middle_dot_boundary` | 577 | 0 | 577 | 0 | | `rule69_ascii_unit_before_terminator_skipping_symbol` | 440 | 343 | 97 | 0 | | `single_capital_followed_by_parenthesized_digits` | 1361 | 0 | 1361 | 0 | | `spaced_comma_between_ascii_digit_runs` | 217 | 181 | 36 | 0 | -| `standalone_multi_character_uppercase_roman_word` | 62411 | 50594 | 11817 | 0 | +| `standalone_multi_character_uppercase_roman_word` | 62411 | 50640 | 11771 | 0 | | `tight_triangle_mark_immediately_before_korean` | 377 | 0 | 377 | 0 | -| `uppercase_alphanumeric_roman_digit_sequence` | 3429 | 1916 | 1513 | 0 | +| `uppercase_alphanumeric_roman_digit_sequence` | 3429 | 1917 | 1512 | 0 | | `uppercase_ascii_run_immediately_after_digit_in_roman_sequence` | 1896 | 1106 | 790 | 0 | -| `uppercase_ascii_run_immediately_after_hyphen_in_roman_sequence` | 952 | 165 | 787 | 0 | +| `uppercase_ascii_run_immediately_after_hyphen_in_roman_sequence` | 952 | 166 | 786 | 0 | | `uppercase_roman_headword_closed_multiword_parenthetical` | 175 | 16 | 159 | 0 | | `uppercase_roman_run_followed_by_hyphen_digits` | 571 | 191 | 380 | 0 | | `uppercase_word_after_whitespace_continuing_ascii_roman_text` | 1729 | 699 | 1030 | 0 | ### `allcaps_roman_run_beginning_with_pure_letter_shortform` -Of the 4120 candidates, 1572 are the actual `pending_rule_review` subcluster. The other 2548 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 4120 candidates, 1571 are the actual `pending_rule_review` subcluster. The other 2549 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. -For this output-signature audit, 1643 mismatches were evaluable and 832 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. +For this output-signature audit, 1642 mismatches were evaluable and 832 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. Localized first-difference transitions: @@ -609,7 +609,7 @@ Mismatch primary-class distribution: - `comparison_method`: 11 - `corpus_suspect`: 50 -- `pending_rule_review`: 1572 +- `pending_rule_review`: 1571 - `unsupported_character_review`: 10 Representative `exact` samples: @@ -765,9 +765,9 @@ Representative `mismatch` samples: ### `allcaps_roman_run_containing_ed` -Of the 816 candidates, 428 are the actual `pending_rule_review` subcluster. The other 388 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 816 candidates, 427 are the actual `pending_rule_review` subcluster. The other 389 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. -For this output-signature audit, 497 mismatches were evaluable and 329 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. +For this output-signature audit, 496 mismatches were evaluable and 329 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. Localized first-difference transitions: @@ -780,7 +780,7 @@ Mismatch primary-class distribution: - `comparison_method`: 1 - `corpus_suspect`: 63 -- `pending_rule_review`: 428 +- `pending_rule_review`: 427 - `unsupported_character_review`: 5 Representative `exact` samples: @@ -1187,15 +1187,15 @@ Representative `mismatch` samples: ### `attached_ascii_roman_to_korean_script_boundary` -Of the 17693 candidates, 3895 are the actual `pending_rule_review` subcluster. The other 13798 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 17693 candidates, 3887 are the actual `pending_rule_review` subcluster. The other 13806 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. -For this output-signature audit, 4727 mismatches were evaluable and 0 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. +For this output-signature audit, 4720 mismatches were evaluable and 0 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. Mismatch primary-class distribution: - `comparison_method`: 60 -- `corpus_suspect`: 721 -- `pending_rule_review`: 3895 +- `corpus_suspect`: 722 +- `pending_rule_review`: 3887 - `unsupported_character_review`: 51 Representative `exact` samples: @@ -1287,72 +1287,58 @@ Representative `mismatch_rule39_hangul_opening` samples: ### `attached_korean_auxiliary_itda_spacing` -Of the 95 candidates, 92 are the actual `pending_rule_review` subcluster. The other 3 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 95 candidates, 20 are the actual `pending_rule_review` subcluster. The other 75 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. -For this output-signature audit, 95 mismatches were evaluable and 74 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. - -Localized first-difference transitions: - -- `U+2815 ⠕ -> U+2800 ⠀`: 73 -- `U+2823 ⠣ -> U+2800 ⠀`: 1 +For this output-signature audit, 24 mismatches were evaluable and 0 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. Mismatch primary-class distribution: -- `corpus_suspect`: 2 -- `pending_rule_review`: 92 -- `unsupported_character_review`: 1 +- `comparison_method`: 1 +- `corpus_suspect`: 3 +- `pending_rule_review`: 20 -Representative `localized_mismatch` samples: +Representative `exact` samples: - `sentence_01.json` #1416: ‘키우GO’ 서비스는 투자목표와 투자기간, 투자금액, 투자성향 등을 종합적으로 분석하여 현재 금융시장 상황에 적합한 자산배분 포트폴리오를 제공하는 투자일임(Wrap)서비스로, 21년 5월 서비스 출시 후 꾸준한 성장을 하고있다. - - expected: `⠻⠨⠶⠮⠀⠚⠈⠥⠕⠌⠊⠲` - - actual: `⠻⠨⠶⠮⠀⠚⠈⠥⠀⠕⠌⠊⠲` - - first differing cell (zero-based): 212 - - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` + - expected: `⠠⠦⠋⠕⠍⠴⠠⠠⠛⠕⠴⠄⠀⠠⠎⠘⠕⠠⠪⠉⠵⠀⠓⠍` + - actual: `⠠⠦⠋⠕⠍⠴⠠⠠⠛⠕⠴⠄⠀⠠⠎⠘⠕⠠⠪⠉⠵⠀⠓⠍` + - current primary/reason: `exact` / `exact` - `sentence_02.json` #242: 예산군의 주차장 관리 업무를 담당하는 임종생 씨(70)는 매일 아침 군청사 출입문에서 밝은 미소로 출근하는 직원과 민원인에게 매일 아침마다 인사를 건네고 있어 호평을 받고있다. - - expected: `⠙⠻⠮⠀⠘⠔⠈⠥⠕⠌⠊⠲` - - actual: `⠙⠻⠮⠀⠘⠔⠈⠥⠀⠕⠌⠊⠲` - - first differing cell (zero-based): 157 - - current primary/reason: `pending_rule_review` / `number_rule_review` + - expected: `⠌⠇⠒⠈⠛⠺⠀⠨⠍⠰⠣⠨⠶⠀⠈⠧⠒⠐⠕⠀⠎⠃⠑⠍` + - actual: `⠌⠇⠒⠈⠛⠺⠀⠨⠍⠰⠣⠨⠶⠀⠈⠧⠒⠐⠕⠀⠎⠃⠑⠍` + - current primary/reason: `exact` / `exact` - `sentence_03.json` #74: 아울러 제주도개발공사는 국내 생수업계에서는 처음으로 재활용 페트(CR-PET)를 적용한 화학적 재활용 페트 ‘제주삼다수 리본(RE:Born)’을 개발하는 등 소재혁신을 통한 친환경 라인업도 확대하고있다. - - expected: `⠚⠧⠁⠊⠗⠚⠈⠥⠕⠌⠊⠲` - - actual: `⠚⠧⠁⠊⠗⠚⠈⠥⠀⠕⠌⠊⠲` - - first differing cell (zero-based): 194 - - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` + - expected: `⠣⠯⠐⠎⠀⠨⠝⠨⠍⠊⠥⠈⠗⠘⠂⠈⠿⠇⠉⠵⠀⠈⠍⠁` + - actual: `⠣⠯⠐⠎⠀⠨⠝⠨⠍⠊⠥⠈⠗⠘⠂⠈⠿⠇⠉⠵⠀⠈⠍⠁` + - current primary/reason: `exact` / `exact` - `sentence_04.json` #204: 이번 법안엔 소형모듈원전(SMR)을 분산에너지로 인정하는 내용도 담겨있다. 여야가 인정 여부를 두고 이견을 보였지만 최근 합의했다. - - expected: `⠶⠊⠥⠀⠊⠢⠈⠱⠕⠌⠊⠲⠀⠱⠜⠫⠀⠟⠨⠻⠀⠱⠘⠍` - - actual: `⠶⠊⠥⠀⠊⠢⠈⠱⠀⠕⠌⠊⠲⠀⠱⠜⠫⠀⠟⠨⠻⠀⠱⠘` - - first differing cell (zero-based): 67 - - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` + - expected: `⠕⠘⠾⠀⠘⠎⠃⠣⠒⠝⠒⠀⠠⠥⠚⠻⠑⠥⠊⠩⠂⠏⠒⠨` + - actual: `⠕⠘⠾⠀⠘⠎⠃⠣⠒⠝⠒⠀⠠⠥⠚⠻⠑⠥⠊⠩⠂⠏⠒⠨` + - current primary/reason: `exact` / `exact` Representative `mismatch` samples: -- `sentence_01.json` #1416: ‘키우GO’ 서비스는 투자목표와 투자기간, 투자금액, 투자성향 등을 종합적으로 분석하여 현재 금융시장 상황에 적합한 자산배분 포트폴리오를 제공하는 투자일임(Wrap)서비스로, 21년 5월 서비스 출시 후 꾸준한 성장을 하고있다. - - expected: `⠻⠨⠶⠮⠀⠚⠈⠥⠕⠌⠊⠲` - - actual: `⠻⠨⠶⠮⠀⠚⠈⠥⠀⠕⠌⠊⠲` - - first differing cell (zero-based): 212 +- `sentence_01.json` #11020: 143엔터테인먼트에는 3인조 걸그룹 라임라잇(수혜, 미유, 가은)과 케플러(Kep1er)로 활동 중인 마시로, 강예서 등이 속해있다. 아이콘의 합류로 본격적인 라인업 확장을 예고했다. + - expected: `⠴⠠⠅⠑⠏⠼⠁⠰⠑⠗⠠⠴⠐⠥⠀⠚⠧⠂⠊⠿⠀⠨⠍⠶` + - actual: `⠴⠠⠅⠑⠏⠼⠁⠰⠻⠠⠴⠐⠥⠀⠚⠧⠂⠊⠿⠀⠨⠍⠶⠟` + - first differing cell (zero-based): 77 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_02.json` #242: 예산군의 주차장 관리 업무를 담당하는 임종생 씨(70)는 매일 아침 군청사 출입문에서 밝은 미소로 출근하는 직원과 민원인에게 매일 아침마다 인사를 건네고 있어 호평을 받고있다. - - expected: `⠙⠻⠮⠀⠘⠔⠈⠥⠕⠌⠊⠲` - - actual: `⠙⠻⠮⠀⠘⠔⠈⠥⠀⠕⠌⠊⠲` - - first differing cell (zero-based): 157 - - current primary/reason: `pending_rule_review` / `number_rule_review` -- `sentence_03.json` #74: 아울러 제주도개발공사는 국내 생수업계에서는 처음으로 재활용 페트(CR-PET)를 적용한 화학적 재활용 페트 ‘제주삼다수 리본(RE:Born)’을 개발하는 등 소재혁신을 통한 친환경 라인업도 확대하고있다. - - expected: `⠚⠧⠁⠊⠗⠚⠈⠥⠕⠌⠊⠲` - - actual: `⠚⠧⠁⠊⠗⠚⠈⠥⠀⠕⠌⠊⠲` - - first differing cell (zero-based): 194 +- `sentence_02.json` #2144: 황새는 세계자연보전연맹 적색자료목록에서 위기(EN)종으로 분류된 국제적 보호종으로 전 세계에서 2천499개체 정도 생존하고 있는 것으로 알려져있다. + - expected: `⠗⠈⠕⠦⠄⠴⠠⠠⠑⠝⠠⠴⠨⠿⠪⠐⠥⠀⠘⠛⠐⠩⠊⠽` + - actual: `⠗⠈⠕⠦⠄⠴⠠⠠⠢⠠⠴⠨⠿⠪⠐⠥⠀⠘⠛⠐⠩⠊⠽⠒` + - first differing cell (zero-based): 49 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_04.json` #204: 이번 법안엔 소형모듈원전(SMR)을 분산에너지로 인정하는 내용도 담겨있다. 여야가 인정 여부를 두고 이견을 보였지만 최근 합의했다. - - expected: `⠶⠊⠥⠀⠊⠢⠈⠱⠕⠌⠊⠲⠀⠱⠜⠫⠀⠟⠨⠻⠀⠱⠘⠍` - - actual: `⠶⠊⠥⠀⠊⠢⠈⠱⠀⠕⠌⠊⠲⠀⠱⠜⠫⠀⠟⠨⠻⠀⠱⠘` - - first differing cell (zero-based): 67 +- `sentence_03.json` #13262: 143엔터테인먼트는 최근 가장 주목받는 레이블로 통한다. 3인조 걸그룹 라임라잇(수혜, 미유, 가은)과 케플러(Kep1er)로 활동 중인 마시로, 강예서 등이 속해있다. 아이콘의 합류로 본격적인 라인업 확장을 예고했다. + - expected: `⠴⠠⠅⠑⠏⠼⠁⠰⠑⠗⠠⠴⠐⠥⠀⠚⠧⠂⠊⠿⠀⠨⠍⠶` + - actual: `⠴⠠⠅⠑⠏⠼⠁⠰⠻⠠⠴⠐⠥⠀⠚⠧⠂⠊⠿⠀⠨⠍⠶⠟` + - first differing cell (zero-based): 109 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` ### `closed_roman_parenthetical_after_non_ascii_letter_boundary` -Of the 63959 candidates, 10088 are the actual `pending_rule_review` subcluster. The other 53871 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 63959 candidates, 10035 are the actual `pending_rule_review` subcluster. The other 53924 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. -For this output-signature audit, 11575 mismatches were evaluable and 1245 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. +For this output-signature audit, 11523 mismatches were evaluable and 1245 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. Localized first-difference transitions: @@ -1364,10 +1350,10 @@ Localized first-difference transitions: Mismatch primary-class distribution: -- `comparison_method`: 226 -- `corpus_suspect`: 1107 -- `pending_rule_review`: 10088 -- `unsupported_character_review`: 154 +- `comparison_method`: 227 +- `corpus_suspect`: 1108 +- `pending_rule_review`: 10035 +- `unsupported_character_review`: 153 Representative `exact` samples: @@ -1436,9 +1422,9 @@ Representative `mismatch` samples: ### `compact_numeric_ascii_letter_suffix` -Of the 2975 candidates, 1027 are the actual `pending_rule_review` subcluster. The other 1948 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 2975 candidates, 1026 are the actual `pending_rule_review` subcluster. The other 1949 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. -For this output-signature audit, 1098 mismatches were evaluable and 251 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. +For this output-signature audit, 1097 mismatches were evaluable and 251 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. Localized first-difference transitions: @@ -1452,7 +1438,7 @@ Mismatch primary-class distribution: - `comparison_method`: 12 - `corpus_suspect`: 52 -- `pending_rule_review`: 1027 +- `pending_rule_review`: 1026 - `unsupported_character_review`: 7 Representative `exact` samples: @@ -1522,9 +1508,9 @@ Representative `mismatch` samples: ### `consecutive_ascii_roman_words_whitespace_boundary` -Of the 4679 candidates, 2411 are the actual `pending_rule_review` subcluster. The other 2268 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 4679 candidates, 2407 are the actual `pending_rule_review` subcluster. The other 2272 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. -For this output-signature audit, 2545 mismatches were evaluable and 23 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. +For this output-signature audit, 2541 mismatches were evaluable and 23 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. Localized first-difference transitions: @@ -1534,7 +1520,7 @@ Mismatch primary-class distribution: - `comparison_method`: 17 - `corpus_suspect`: 88 -- `pending_rule_review`: 2411 +- `pending_rule_review`: 2407 - `unsupported_character_review`: 29 Representative `exact` samples: @@ -1600,9 +1586,9 @@ Representative `mismatch` samples: ### `decimal_point_between_ascii_digits` -Of the 4546 candidates, 1095 are the actual `pending_rule_review` subcluster. The other 3451 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 4546 candidates, 1092 are the actual `pending_rule_review` subcluster. The other 3454 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. -For this output-signature audit, 1152 mismatches were evaluable and 262 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. +For this output-signature audit, 1149 mismatches were evaluable and 262 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. Localized first-difference transitions: @@ -1616,7 +1602,7 @@ Mismatch primary-class distribution: - `comparison_method`: 17 - `corpus_suspect`: 28 -- `pending_rule_review`: 1095 +- `pending_rule_review`: 1092 - `unsupported_character_review`: 12 Representative `exact` samples: @@ -1784,13 +1770,13 @@ Representative `mismatch` samples: ### `korean_prefixed_closed_allcaps_parenthetical` -Of the 54492 candidates, 8043 are the actual `pending_rule_review` subcluster. The other 46449 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 54492 candidates, 8003 are the actual `pending_rule_review` subcluster. The other 46489 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. Mismatch primary-class distribution: - `comparison_method`: 178 -- `corpus_suspect`: 992 -- `pending_rule_review`: 8043 +- `corpus_suspect`: 993 +- `pending_rule_review`: 8003 - `unsupported_character_review`: 131 Representative `exact` samples: @@ -1837,21 +1823,21 @@ Representative `mismatch` samples: ### `korean_prefixed_closed_roman_annotation_rule_34_order` -Of the 64382 candidates, 9901 are the actual `pending_rule_review` subcluster. The other 54481 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 64382 candidates, 9848 are the actual `pending_rule_review` subcluster. The other 54534 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. -For this output-signature audit, 11392 mismatches were evaluable and 1111 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. +For this output-signature audit, 11340 mismatches were evaluable and 1112 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. Localized first-difference transitions: -- `U+2834 ⠴ -> U+2826 ⠦`: 1110 +- `U+2834 ⠴ -> U+2826 ⠦`: 1111 - `U+2810 ⠐ -> U+2826 ⠦`: 1 Mismatch primary-class distribution: -- `comparison_method`: 230 -- `corpus_suspect`: 1107 -- `pending_rule_review`: 9901 -- `unsupported_character_review`: 154 +- `comparison_method`: 231 +- `corpus_suspect`: 1108 +- `pending_rule_review`: 9848 +- `unsupported_character_review`: 153 Representative `exact` samples: @@ -2059,9 +2045,9 @@ Representative `mismatch` samples: ### `pure_allcaps_segment_before_hyphen_and_multi_allcaps_segment_after` -Of the 448 candidates, 306 are the actual `pending_rule_review` subcluster. The other 142 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 448 candidates, 305 are the actual `pending_rule_review` subcluster. The other 143 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. -For this output-signature audit, 316 mismatches were evaluable and 1 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. +For this output-signature audit, 315 mismatches were evaluable and 1 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. Localized first-difference transitions: @@ -2070,7 +2056,7 @@ Localized first-difference transitions: Mismatch primary-class distribution: - `corpus_suspect`: 8 -- `pending_rule_review`: 306 +- `pending_rule_review`: 305 - `unsupported_character_review`: 2 Representative `exact` samples: @@ -2083,9 +2069,9 @@ Representative `exact` samples: - expected: `⠠⠛⠰⠾⠚⠜⠶⠊⠗⠦⠄⠰⠿⠨⠶⠀⠈⠕⠢⠠⠪⠶⠍⠠` - actual: `⠠⠛⠰⠾⠚⠜⠶⠊⠗⠦⠄⠰⠿⠨⠶⠀⠈⠕⠢⠠⠪⠶⠍⠠` - current primary/reason: `exact` / `exact` -- `sentence_03.json` #1297: 삼성디스플레이의 퀀텀닷-유기발광다이오드(QD-OLED) 제품이 세계 최대 디스플레이 학회인 정보디스플레이학회(SID)로부터 ‘올해의 디스플레이’ 상을 받았다고 삼성디스플레이가 17일 밝혔다. - - expected: `⠇⠢⠠⠻⠊⠕⠠⠪⠙⠮⠐⠝⠕⠺⠀⠋⠏⠒⠓⠎⠢⠊⠄⠤` - - actual: `⠇⠢⠠⠻⠊⠕⠠⠪⠙⠮⠐⠝⠕⠺⠀⠋⠏⠒⠓⠎⠢⠊⠄⠤` +- `sentence_03.json` #74: 아울러 제주도개발공사는 국내 생수업계에서는 처음으로 재활용 페트(CR-PET)를 적용한 화학적 재활용 페트 ‘제주삼다수 리본(RE:Born)’을 개발하는 등 소재혁신을 통한 친환경 라인업도 확대하고있다. + - expected: `⠣⠯⠐⠎⠀⠨⠝⠨⠍⠊⠥⠈⠗⠘⠂⠈⠿⠇⠉⠵⠀⠈⠍⠁` + - actual: `⠣⠯⠐⠎⠀⠨⠝⠨⠍⠊⠥⠈⠗⠘⠂⠈⠿⠇⠉⠵⠀⠈⠍⠁` - current primary/reason: `exact` / `exact` - `sentence_04.json` #1164: 3일 서울시에 따르면 서울시와 국토교통부는 이러한 내용을 담은 ‘K-UAM(도심항공교통) 그랜드챌린지 실증운행계획’을 이달 중순 발표한다. UAM은 드론 택시처럼 하늘을 이용한 교통수단이다. - expected: `⠼⠉⠕⠂⠀⠠⠎⠯⠠⠕⠝⠀⠠⠊⠐⠪⠑⠡⠀⠠⠎⠯⠠⠕` @@ -2112,10 +2098,10 @@ Representative `mismatch` samples: - actual: `⠥⠡⠈⠍⠊⠒⠐⠀⠀⠠⠁⠔⠠⠠⠎⠞⠁⠗⠀⠀⠑⠕⠆⠀` - first differing cell (zero-based): 122 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_03.json` #74: 아울러 제주도개발공사는 국내 생수업계에서는 처음으로 재활용 페트(CR-PET)를 적용한 화학적 재활용 페트 ‘제주삼다수 리본(RE:Born)’을 개발하는 등 소재혁신을 통한 친환경 라인업도 확대하고있다. - - expected: `⠚⠧⠁⠊⠗⠚⠈⠥⠕⠌⠊⠲` - - actual: `⠚⠧⠁⠊⠗⠚⠈⠥⠀⠕⠌⠊⠲` - - first differing cell (zero-based): 194 +- `sentence_03.json` #569: 새울원전 관계자는 “APR 1400의 유럽 수출 노형인 EU-APR은 유럽사업자요건(EUR) 인증을 취득했고, 미국 이외 국가로는 처음으로 미국 원자력규제위원회 설계인증을 받아 안전성과 기술력을 국제적으로 인증받고 있다”고 밝혔다. + - expected: `⠟⠀⠴⠠⠠⠑⠥⠤⠠⠠⠁⠏⠗⠲⠵⠀⠩⠐⠎⠃⠇⠎⠃⠨` + - actual: `⠟⠀⠴⠠⠠⠑⠥⠤⠁⠏⠗⠲⠵⠀⠩⠐⠎⠃⠇⠎⠃⠨⠣⠬` + - first differing cell (zero-based): 54 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` - `sentence_04.json` #883: 그룹 방탄소년단(BTS) 슈가의 첫 공식 솔로 음반 ‘D-데이’(D-DAY)가 발매 당일 한터차트 기준 107만장이 넘게 팔려나가며 첫날 판매량으로는 K팝 솔로 가수 최고 기록을 경신했다. - expected: `⠙⠲⠤⠊⠝⠕⠴⠄⠦⠄⠴⠠⠙⠤⠠⠠⠐⠙⠠⠴⠫⠀⠘⠂` @@ -2187,9 +2173,9 @@ Representative `mismatch` samples: ### `roman_parenthetical_headword_after_whitespace_following_korean_word` -Of the 4695 candidates, 1196 are the actual `pending_rule_review` subcluster. The other 3499 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 4695 candidates, 1192 are the actual `pending_rule_review` subcluster. The other 3503 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. -For this output-signature audit, 1288 mismatches were evaluable and 158 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. +For this output-signature audit, 1284 mismatches were evaluable and 158 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. Localized first-difference transitions: @@ -2201,7 +2187,7 @@ Mismatch primary-class distribution: - `comparison_method`: 29 - `corpus_suspect`: 39 -- `pending_rule_review`: 1196 +- `pending_rule_review`: 1192 - `unsupported_character_review`: 24 Representative `exact` samples: @@ -2596,13 +2582,13 @@ Representative `mismatch` samples: ### `standalone_multi_character_uppercase_roman_word` -Of the 62411 candidates, 10359 are the actual `pending_rule_review` subcluster. The other 52052 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 62411 candidates, 10312 are the actual `pending_rule_review` subcluster. The other 52099 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. Mismatch primary-class distribution: - `comparison_method`: 213 -- `corpus_suspect`: 1093 -- `pending_rule_review`: 10359 +- `corpus_suspect`: 1094 +- `pending_rule_review`: 10312 - `unsupported_character_review`: 152 Representative `exact` samples: @@ -2651,11 +2637,11 @@ Representative `mismatch` samples: Of the 377 candidates, 376 are the actual `pending_rule_review` subcluster. The other 1 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. -For this output-signature audit, 377 mismatches were evaluable and 333 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. +For this output-signature audit, 377 mismatches were evaluable and 334 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. Localized first-difference transitions: -- `U+2800 ⠀ -> U+2807 ⠇`: 330 +- `U+2800 ⠀ -> U+2807 ⠇`: 331 - `U+280A ⠊ -> U+2807 ⠇`: 1 - `U+2818 ⠘ -> U+2807 ⠇`: 1 - `U+2829 ⠩ -> U+2807 ⠇`: 1 @@ -2690,9 +2676,9 @@ Representative `mismatch` samples: ### `uppercase_alphanumeric_roman_digit_sequence` -Of the 3429 candidates, 1404 are the actual `pending_rule_review` subcluster. The other 2025 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 3429 candidates, 1403 are the actual `pending_rule_review` subcluster. The other 2026 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. -For this output-signature audit, 1513 mismatches were evaluable and 151 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. +For this output-signature audit, 1512 mismatches were evaluable and 151 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. Localized first-difference transitions: @@ -2706,7 +2692,7 @@ Mismatch primary-class distribution: - `comparison_method`: 13 - `corpus_suspect`: 87 -- `pending_rule_review`: 1404 +- `pending_rule_review`: 1403 - `unsupported_character_review`: 9 Representative `exact` samples: @@ -2843,9 +2829,9 @@ Representative `mismatch` samples: ### `uppercase_ascii_run_immediately_after_hyphen_in_roman_sequence` -Of the 952 candidates, 766 are the actual `pending_rule_review` subcluster. The other 186 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 952 candidates, 765 are the actual `pending_rule_review` subcluster. The other 187 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. -For this output-signature audit, 787 mismatches were evaluable and 163 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. +For this output-signature audit, 786 mismatches were evaluable and 163 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. Localized first-difference transitions: @@ -2855,7 +2841,7 @@ Localized first-difference transitions: Mismatch primary-class distribution: - `corpus_suspect`: 12 -- `pending_rule_review`: 766 +- `pending_rule_review`: 765 - `unsupported_character_review`: 9 Representative `exact` samples: @@ -2868,9 +2854,9 @@ Representative `exact` samples: - expected: `⠠⠛⠰⠾⠚⠜⠶⠊⠗⠦⠄⠰⠿⠨⠶⠀⠈⠕⠢⠠⠪⠶⠍⠠` - actual: `⠠⠛⠰⠾⠚⠜⠶⠊⠗⠦⠄⠰⠿⠨⠶⠀⠈⠕⠢⠠⠪⠶⠍⠠` - current primary/reason: `exact` / `exact` -- `sentence_03.json` #1297: 삼성디스플레이의 퀀텀닷-유기발광다이오드(QD-OLED) 제품이 세계 최대 디스플레이 학회인 정보디스플레이학회(SID)로부터 ‘올해의 디스플레이’ 상을 받았다고 삼성디스플레이가 17일 밝혔다. - - expected: `⠇⠢⠠⠻⠊⠕⠠⠪⠙⠮⠐⠝⠕⠺⠀⠋⠏⠒⠓⠎⠢⠊⠄⠤` - - actual: `⠇⠢⠠⠻⠊⠕⠠⠪⠙⠮⠐⠝⠕⠺⠀⠋⠏⠒⠓⠎⠢⠊⠄⠤` +- `sentence_03.json` #74: 아울러 제주도개발공사는 국내 생수업계에서는 처음으로 재활용 페트(CR-PET)를 적용한 화학적 재활용 페트 ‘제주삼다수 리본(RE:Born)’을 개발하는 등 소재혁신을 통한 친환경 라인업도 확대하고있다. + - expected: `⠣⠯⠐⠎⠀⠨⠝⠨⠍⠊⠥⠈⠗⠘⠂⠈⠿⠇⠉⠵⠀⠈⠍⠁` + - actual: `⠣⠯⠐⠎⠀⠨⠝⠨⠍⠊⠥⠈⠗⠘⠂⠈⠿⠇⠉⠵⠀⠈⠍⠁` - current primary/reason: `exact` / `exact` - `sentence_04.json` #1164: 3일 서울시에 따르면 서울시와 국토교통부는 이러한 내용을 담은 ‘K-UAM(도심항공교통) 그랜드챌린지 실증운행계획’을 이달 중순 발표한다. UAM은 드론 택시처럼 하늘을 이용한 교통수단이다. - expected: `⠼⠉⠕⠂⠀⠠⠎⠯⠠⠕⠝⠀⠠⠊⠐⠪⠑⠡⠀⠠⠎⠯⠠⠕` @@ -2912,10 +2898,10 @@ Representative `mismatch` samples: - actual: `⠥⠡⠈⠍⠊⠒⠐⠀⠀⠠⠁⠔⠠⠠⠎⠞⠁⠗⠀⠀⠑⠕⠆⠀` - first differing cell (zero-based): 122 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_03.json` #74: 아울러 제주도개발공사는 국내 생수업계에서는 처음으로 재활용 페트(CR-PET)를 적용한 화학적 재활용 페트 ‘제주삼다수 리본(RE:Born)’을 개발하는 등 소재혁신을 통한 친환경 라인업도 확대하고있다. - - expected: `⠚⠧⠁⠊⠗⠚⠈⠥⠕⠌⠊⠲` - - actual: `⠚⠧⠁⠊⠗⠚⠈⠥⠀⠕⠌⠊⠲` - - first differing cell (zero-based): 194 +- `sentence_03.json` #569: 새울원전 관계자는 “APR 1400의 유럽 수출 노형인 EU-APR은 유럽사업자요건(EUR) 인증을 취득했고, 미국 이외 국가로는 처음으로 미국 원자력규제위원회 설계인증을 받아 안전성과 기술력을 국제적으로 인증받고 있다”고 밝혔다. + - expected: `⠟⠀⠴⠠⠠⠑⠥⠤⠠⠠⠁⠏⠗⠲⠵⠀⠩⠐⠎⠃⠇⠎⠃⠨` + - actual: `⠟⠀⠴⠠⠠⠑⠥⠤⠁⠏⠗⠲⠵⠀⠩⠐⠎⠃⠇⠎⠃⠨⠣⠬` + - first differing cell (zero-based): 54 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` - `sentence_04.json` #883: 그룹 방탄소년단(BTS) 슈가의 첫 공식 솔로 음반 ‘D-데이’(D-DAY)가 발매 당일 한터차트 기준 107만장이 넘게 팔려나가며 첫날 판매량으로는 K팝 솔로 가수 최고 기록을 경신했다. - expected: `⠙⠲⠤⠊⠝⠕⠴⠄⠦⠄⠴⠠⠙⠤⠠⠠⠐⠙⠠⠴⠫⠀⠘⠂` @@ -3121,10 +3107,10 @@ These cohorts are defined by both an input boundary and the sentence's actual fi | Cohort | Candidates | Exact controls | Mismatch | Target localized | Reverse | |---|---:|---:|---:|---:|---:| -| `allcaps_roman_run_beginning_with_pure_letter_shortform` | 4120 | 2477 | 1643 | 785 | 47 | +| `allcaps_roman_run_beginning_with_pure_letter_shortform` | 4120 | 2478 | 1642 | 785 | 47 | | `uppercase_ascii_run_immediately_after_digit_in_roman_sequence` | 1896 | 1106 | 790 | 0 | 1 | -| `uppercase_ascii_run_immediately_after_hyphen_in_roman_sequence` | 952 | 165 | 787 | 159 | 4 | -| `pure_allcaps_segment_before_hyphen_and_multi_allcaps_segment_after` | 448 | 132 | 316 | 0 | 1 | +| `uppercase_ascii_run_immediately_after_hyphen_in_roman_sequence` | 952 | 166 | 786 | 159 | 4 | +| `pure_allcaps_segment_before_hyphen_and_multi_allcaps_segment_after` | 448 | 133 | 315 | 0 | 1 | ### All-caps shortform prefix at an attached Roman entry @@ -3181,7 +3167,7 @@ Korean rule 29 requires a Roman indicator before Roman text in a Korean sentence | Cohort | Candidates | Exact controls | Mismatch | Pending | Corpus suspect | Localized `⠴ -> blank` | Reverse `blank -> ⠴` | |---|---:|---:|---:|---:|---:|---:|---:| | `roman_hyphenated_word_after_whitespace_following_korean_word` | 361 | 0 | 361 | 350 | 6 | 136 | 0 | -| `roman_parenthetical_headword_after_whitespace_following_korean_word` | 4695 | 3407 | 1288 | 1196 | 39 | 154 | 0 | +| `roman_parenthetical_headword_after_whitespace_following_korean_word` | 4695 | 3411 | 1284 | 1192 | 39 | 154 | 0 | | `korean_prefixed_roman_parenthetical_followed_by_allcaps_hyphen_suffix` | 11 | 0 | 11 | 6 | 5 | 5 | 0 | The whitespace parenthetical-headword cohort also retains 1 localized `⠠ -> ⠴` and 3 localized `⠴ -> ⠰` cases as separate transitions; they are not folded into the target. The exact controls demonstrate that the broad structure is already correct in many sentences, while the attached parenthetical-hyphen cohort has no exact control and includes cases already classified by the stricter rule-34 reference-order contradiction. Consequently none of these measurements authorizes an engine change; they are deterministic pending/corpus-review diagnostics only. @@ -3198,7 +3184,7 @@ The generalized fix makes an explicit `EnterEnglish` event idempotent when final Korean rule 34 (2024 Korean-rules PDF p.29) prints the opening enclosure before Roman entry in `링컨(Lincoln)`: the opening sequence is `⠦⠄⠴`. This output-position cohort finds a closed, non-nested parenthetical whose body begins with an ASCII letter and whose opening does not immediately follow another ASCII letter, then locates its complete current-engine signature without consulting the reference. Its localized boundary includes the two current output cells immediately before the opening plus the first three entry cells, so rule-11 math spacing can be separated from a difference later inside the parenthetical. Direct function-call shapes such as `f(x)` are excluded. The PDF's math rule 6 (p.59), rule 12 (pp.63-64), and rule 45 (p.75) nevertheless leave standalone `(x)` and other Roman-letter parenthetical mathematics as counterexamples, so the surface gate is not an engine-routing predicate. -The cross-cutting input cohort contains 63959 candidates: 52384 exact controls and 11575 mismatches. Mismatch primary classes remain unchanged: 10088 `pending_rule_review`, 1107 `corpus_suspect`, 226 `comparison_method`, and 154 `unsupported_character_review`. Of 11575 evaluable mismatches, 1245 have the first difference at the detected leading-spacing/entry boundary; these include 436 `U+2826 ⠦ -> U+2834 ⠴`, 27 `U+2834 ⠴ -> U+2826 ⠦`, and 325 `U+2826 ⠦ -> U+2800 ⠀` transitions; the exact localized reverse `U+2800 ⠀ -> U+2826 ⠦` occurs 0 times. After all earlier localized cohorts and this cohort are excluded, the raw-to-residual target count is 469 -> 26, the Roman-indicator reverse count is 32 -> 2, and the spacing target/reverse counts are 440 -> 79 and 1 -> 1. The short full-encoder form `웹3(Web3)` emits the PDF opening order as a routing control, while whitespace and quote boundaries reproduce the current Roman-first opening. Representative localized samples, with shard and index, are retained in the generated cluster sample table. Because exact controls are abundant and the PDF does not make this input shape semantically sufficient to exclude mathematics, no engine change or primary reclassification is inferred. The one raw/residual spacing reverse is a Korean-body `△(교육)` layout boundary, not a Roman-parenthetical member, and remains a separate rule-72/layout review. +The cross-cutting input cohort contains 63959 candidates: 52436 exact controls and 11523 mismatches. Mismatch primary classes remain unchanged: 10035 `pending_rule_review`, 1108 `corpus_suspect`, 227 `comparison_method`, and 153 `unsupported_character_review`. Of 11523 evaluable mismatches, 1245 have the first difference at the detected leading-spacing/entry boundary; these include 436 `U+2826 ⠦ -> U+2834 ⠴`, 27 `U+2834 ⠴ -> U+2826 ⠦`, and 325 `U+2826 ⠦ -> U+2800 ⠀` transitions; the exact localized reverse `U+2800 ⠀ -> U+2826 ⠦` occurs 0 times. After all earlier localized cohorts and this cohort are excluded, the raw-to-residual target count is 469 -> 26, the Roman-indicator reverse count is 32 -> 2, and the spacing target/reverse counts are 440 -> 79 and 1 -> 1. The short full-encoder form `웹3(Web3)` emits the PDF opening order as a routing control, while whitespace and quote boundaries reproduce the current Roman-first opening. Representative localized samples, with shard and index, are retained in the generated cluster sample table. Because exact controls are abundant and the PDF does not make this input shape semantically sufficient to exclude mathematics, no engine change or primary reclassification is inferred. The one raw/residual spacing reverse is a Korean-body `△(교육)` layout boundary, not a Roman-parenthetical member, and remains a separate rule-72/layout review. The HCA-style headword-expansion shape described above is not an engine implementation premise. The 2024 PDF's math rule 6 defines parentheses and grouping parentheses, rule 11 defines mathematical-expression spacing, rule 12 covers Roman letters in formulas as well as Korean sentences, and rule 45 shows Roman-letter function notation followed by parentheses. Excluding visible operators, scripts, and nesting narrows this corpus cohort, but the PDF does not make the remaining surface shape sufficient to rule out every mathematical counterexample. The cluster therefore remains conservative pending-review evidence only. @@ -3239,7 +3225,7 @@ The cohort contains 1093 candidates, 142 exact controls, and 951 mismatches. Exi UEB 10.12.1 (2024 UEB PDF pp.191-192, printed pp.163-164) requires separate letters when an abbreviation's letters are known to be pronounced separately, and its official `OED` example writes `e` and `d` separately. Rule 10.12.2 otherwise uses contractions; its official `BEd` example uses the `ed` groupsign. Both full-encoder controls pass. Consequently an uppercase `ED` surface cannot establish pronunciation or abbreviation semantics by spelling alone. -The cohort contains 816 candidates, 319 exact controls, and 497 mismatches. Existing mismatch primary classes remain 428 `pending_rule_review`, 63 `corpus_suspect`, 1 `comparison_method`, and 5 `unsupported_character_review`. Of 497 evaluable mismatches, 329 are localized to the detected current-engine run: 326 `U+2811 ⠑ -> U+282B ⠫` and 0 `U+282B ⠫ -> U+2811 ⠑`. Across raw pending transitions and the final residual after localized cohorts, the target is 329 -> 3 and the reverse is 0 -> 0. Corpus initialisms such as `LED` and `GED` require external pronunciation knowledge, while exact and official controls preserve contraction-bearing outcomes. No engine change or primary reclassification is made; representative shard/index samples are retained above. +The cohort contains 816 candidates, 320 exact controls, and 496 mismatches. Existing mismatch primary classes remain 427 `pending_rule_review`, 63 `corpus_suspect`, 1 `comparison_method`, and 5 `unsupported_character_review`. Of 496 evaluable mismatches, 329 are localized to the detected current-engine run: 326 `U+2811 ⠑ -> U+282B ⠫` and 0 `U+282B ⠫ -> U+2811 ⠑`. Across raw pending transitions and the final residual after localized cohorts, the target is 329 -> 3 and the reverse is 0 -> 0. Corpus initialisms such as `LED` and `GED` require external pronunciation knowledge, while exact and official controls preserve contraction-bearing outcomes. No engine change or primary reclassification is made; representative shard/index samples are retained above. ### Uppercase Roman runs containing `ST` @@ -3289,26 +3275,28 @@ After the correction, this cohort has 7 candidates / 5 exact / 2 mismatch, prese ### Attached Korean `있다` spacing normalization -Korean rule 49 (2024 Korean-rules PDF p.37, printed p.31) says that spacing follows the print input. The PDF consistently retains an explicit space in `그리고 있다` (physical p.18), `살고 있다` (p.26), and `수강하고 있다` (p.30), but it gives no example authorizing a transcriber to insert a missing print space. The current token normalizer nevertheless splits any Korean token ending in attached `있다`. +Korean rule 49 (2024 Korean-rules PDF p.37, printed p.31) says that spacing follows the print input. The PDF consistently retains an explicit space in `그리고 있다` (physical p.18), `살고 있다` (p.26), and `수강하고 있다` (p.30), but it gives no example authorizing a transcriber to insert a missing print space. The current pre-fix token normalizer nevertheless split any Korean token ending in attached `있다`. + +The diagnostic baseline had 95 candidates / 0 exact / 95 mismatch. All 95 were in the exact former implementation scope; 74 first differences were at the inserted blank: 73 `U+2815 ⠕ -> U+2800 ⠀`, one `U+2823 ⠣ -> U+2800 ⠀`, and no localized reverse. The absence of a baseline exact member is the in-scope regression control. -The diagnostic baseline has 95 candidates / 0 exact / 95 mismatch, preserving 92 `pending_rule_review`, 2 `corpus_suspect`, 0 `comparison_method`, and 1 `unsupported_character_review` mismatch primaries. Of 95 evaluable mismatches, 74 put the first difference exactly at the independently located inserted blank: 73 `U+2815 ⠕ -> U+2800 ⠀`, 1 `U+2823 ⠣ -> U+2800 ⠀`, and 0 `U+2800 ⠀ -> U+2815 ⠕`. There are no exact members and no localized reverse; the three explicitly spaced PDF forms are the independent negative controls. This checkpoint records the scope before testing removal of the input-correcting normalizer; it does not infer a branch from the corpus reference. +After removing that input-correcting transformation, the cohort has 95 candidates / 71 exact / 24 mismatch, preserving 20 `pending_rule_review`, 3 `corpus_suspect`, 1 `comparison_method`, and 0 `unsupported_character_review` mismatch primaries. Of 24 evaluable current mismatches, 0 still localize to an inserted blank: 0 `U+2815 ⠕ -> U+2800 ⠀`, 0 `U+2823 ⠣ -> U+2800 ⠀`, and 0 `U+2800 ⠀ -> U+2815 ⠕`. Seventy-one cases become exact; the other 24 retain independent earlier differences. Corpus exact increases by the same 71 with zero exact-set regressions. The three explicitly spaced PDF forms remain full-encoder negative controls, so removing correction of missing input whitespace does not remove a printed space. The local rule-47 standard case had accidentally transcribed PDF physical p.36 `덮여 있다` as attached `덮여있다` while retaining the PDF's spaced braille; correcting that input transcription restores the complete 5,141/5,141 standard summary without an engine exception. Current uppercase-Roman hyphen-digits measurement: 571 candidates, 191 exact controls, 380 mismatches, 365 members in the actual `pending_rule_review` subcluster, and 262/380 evaluable mismatches whose first difference is inside the target run plus its entry boundary. It remains distinct from parenthesized digits and headword expansions; no engine change is inferred. -Current uppercase alphanumeric Roman-digit sequence measurement: 3429 candidates, 1916 exact controls, 1513 mismatches, 1404 members in the actual `pending_rule_review` subcluster, and 151/1513 evaluable mismatches whose first difference is localized to the current entry cells. The target `⠴ -> blank` occurs 93 times and the reverse occurs 0 times. Korean rule 35 (2024 Korean-rules PDF pp.29-30, printed pp.23-24) explicitly treats `MP3`, `A4`, `KF94`, and `D-100` as Roman-number sequences. Math rules 11/12 (PDF pp.63-65, printed pp.57-59) separately route mathematical Roman notation without the prose Roman indicator and with two-cell spacing. Official UEB 6.5.1-6.5.2 (2024 UEB PDF p.96, printed p.68) determines numeric/grade-1 continuation only after the surrounding mode has been chosen. Thus surfaces such as `A-3` cannot be assigned identifier or variable semantics from this structure alone. Exact controls and both directions are retained; no engine change is inferred. +Current uppercase alphanumeric Roman-digit sequence measurement: 3429 candidates, 1917 exact controls, 1512 mismatches, 1403 members in the actual `pending_rule_review` subcluster, and 151/1512 evaluable mismatches whose first difference is localized to the current entry cells. The target `⠴ -> blank` occurs 93 times and the reverse occurs 0 times. Korean rule 35 (2024 Korean-rules PDF pp.29-30, printed pp.23-24) explicitly treats `MP3`, `A4`, `KF94`, and `D-100` as Roman-number sequences. Math rules 11/12 (PDF pp.63-65, printed pp.57-59) separately route mathematical Roman notation without the prose Roman indicator and with two-cell spacing. Official UEB 6.5.1-6.5.2 (2024 UEB PDF p.96, printed p.68) determines numeric/grade-1 continuation only after the surrounding mode has been chosen. Thus surfaces such as `A-3` cannot be assigned identifier or variable semantics from this structure alone. Exact controls and both directions are retained; no engine change is inferred. -Current consecutive ASCII-Roman word-boundary measurement: 4679 candidates, 2134 exact controls, 2545 mismatches, 2411 members in the actual `pending_rule_review` subcluster, and 23/2545 evaluable mismatches whose first difference is localized to the current boundary cell. The target reference blank versus current terminator (`⠀ -> ⠲`) occurs 23 times; the exact reverse (`⠲ -> ⠀`) occurs 0 times. Korean rule 29 (2024 Korean-rules PDF p.26, printed p.20) explicitly treats consecutive Roman items as one Roman section, with one Roman indicator before the first item and one terminator after the last; its printed `Los Angeles` and `Table of Contents` examples are exact controls. Rule 34 (PDF p.29, printed p.23) separately omits the terminator before a closing enclosure, while official UEB 9.7.1 (2024 UEB PDF p.137, printed p.109) prints the multiword prose enclosure `plays (such as Romeo and Juliet)` with ordinary internal spaces and nested closing punctuation. Together they support preserving a complete letter-and-space Roman parenthetical as prose, but not a fragment containing a function-call opener, digit, operator, or nested bracket. The input-only gate does not decide whether punctuation-separated text such as `ESS /VPP` continues the same section, so that variant remains outside this cohort. Primary classes are preserved. Before the narrow token-routing change this cohort was 4,679 candidates / 2,058 exact / 2,621 mismatch, with 128 localized `⠀ -> ⠲` and zero reverse. The cause was the final `Letters)` token of a whitespace-split Roman parenthetical being rerouted as math, which made the preceding Roman word terminate early and introduced an extra blank. After excluding only a backwards-verified complete Roman parenthetical tail from math routing, the cohort is 2,132 exact / 2,547 mismatch, with 23 target-localized and zero reverse; corpus-wide exact likewise rises by 74, from 68,101 to 68,175. The 105 removed localized boundaries include 74 newly exact cases and 31 cases that still differ elsewhere. Remaining targets such as `SYNO PEM-1`, `ACE Fair(2020)`, and `Mnet K-POP` do not satisfy the complete-parenthetical gate and remain separate residuals rather than widening this rule. +Current consecutive ASCII-Roman word-boundary measurement: 4679 candidates, 2138 exact controls, 2541 mismatches, 2407 members in the actual `pending_rule_review` subcluster, and 23/2541 evaluable mismatches whose first difference is localized to the current boundary cell. The target reference blank versus current terminator (`⠀ -> ⠲`) occurs 23 times; the exact reverse (`⠲ -> ⠀`) occurs 0 times. Korean rule 29 (2024 Korean-rules PDF p.26, printed p.20) explicitly treats consecutive Roman items as one Roman section, with one Roman indicator before the first item and one terminator after the last; its printed `Los Angeles` and `Table of Contents` examples are exact controls. Rule 34 (PDF p.29, printed p.23) separately omits the terminator before a closing enclosure, while official UEB 9.7.1 (2024 UEB PDF p.137, printed p.109) prints the multiword prose enclosure `plays (such as Romeo and Juliet)` with ordinary internal spaces and nested closing punctuation. Together they support preserving a complete letter-and-space Roman parenthetical as prose, but not a fragment containing a function-call opener, digit, operator, or nested bracket. The input-only gate does not decide whether punctuation-separated text such as `ESS /VPP` continues the same section, so that variant remains outside this cohort. Primary classes are preserved. Before the narrow token-routing change this cohort was 4,679 candidates / 2,058 exact / 2,621 mismatch, with 128 localized `⠀ -> ⠲` and zero reverse. The cause was the final `Letters)` token of a whitespace-split Roman parenthetical being rerouted as math, which made the preceding Roman word terminate early and introduced an extra blank. After excluding only a backwards-verified complete Roman parenthetical tail from math routing, the cohort is 2,132 exact / 2,547 mismatch, with 23 target-localized and zero reverse; corpus-wide exact likewise rises by 74, from 68,101 to 68,175. The 105 removed localized boundaries include 74 newly exact cases and 31 cases that still differ elsewhere. Remaining targets such as `SYNO PEM-1`, `ACE Fair(2020)`, and `Mnet K-POP` do not satisfy the complete-parenthetical gate and remain separate residuals rather than widening this rule. Current single-capital parenthesized-digits measurement: 1361 candidates, 0 exact controls, 1361 mismatches, 1357 members in the actual `pending_rule_review` subcluster, and 1342/1361 evaluable mismatches whose first difference is inside the target run plus its entry boundary. No engine change is inferred from the ambiguous prose/function surface form. Current mixed Roman+Korean boundary before uppercase headword-expansion measurement: 10 candidates, 0 exact controls, 10 mismatches, 10 members in the actual `pending_rule_review` subcluster, and 8/10 evaluable mismatches whose first difference is localized to the later headword's entry boundary/output. The detector cannot be satisfied by the earlier Roman entry. No HCA-shaped engine routing rule is introduced. -Current compact numeric+ASCII-suffix measurement: 2975 candidates, 1877 exact controls, 1098 mismatches, 1027 members in the actual `pending_rule_review` subcluster, and 251/1098 evaluable mismatches whose first difference is inside the complete current-engine output signature or its immediate entry boundary. Rule 40 requires the numeric indicator and rule 69 requires Roman indicators around a Roman-written unit, but the input shape alone cannot prove that every ASCII suffix is a unit. +Current compact numeric+ASCII-suffix measurement: 2975 candidates, 1878 exact controls, 1097 mismatches, 1026 members in the actual `pending_rule_review` subcluster, and 251/1097 evaluable mismatches whose first difference is inside the complete current-engine output signature or its immediate entry boundary. Rule 40 requires the numeric indicator and rule 69 requires Roman indicators around a Roman-written unit, but the input shape alone cannot prove that every ASCII suffix is a unit. | ASCII suffix | Candidates | Exact | Mismatch | Localized first diff | |---|---:|---:|---:|---:| | `m` | 364 | 262 | 102 | 22 | -| `km` | 308 | 251 | 57 | 2 | +| `km` | 308 | 252 | 56 | 2 | | `G` | 237 | 165 | 72 | 3 | | `kg` | 209 | 176 | 33 | 4 | | `D` | 167 | 115 | 52 | 5 | @@ -3338,29 +3326,29 @@ Entry-boundary pre-fix baseline: 2,975 candidates, 1,649 exact controls, 1,326 m Current rule-69 ASCII-unit punctuation-boundary measurement: 440 candidates, 343 exact controls, 97 mismatches, 92 members in the actual `pending_rule_review` subcluster, and 16/97 evaluable mismatches whose first difference is localized to the unit-plus-punctuation output signature. PDF rule 69 requires a Roman terminator after a Roman-written unit in the ordinary case, while rules 33/34 omit it at the listed punctuation or enclosing-mark boundary; the rule 46 PDF example `체중(kg)` is the minimal parenthesized-unit control. Membership is restricted to rule-69 spellings already supported by the engine and does not infer unit semantics for arbitrary ASCII suffixes. Analyzer pre-fix baseline: 440 candidates, 0 exact controls, 440 mismatches, 435 pending members, and 9 signature-local first differences. After the generalized rule-33/34 boundary override and the matching non-math routing guard, the same cohort has 325 exact controls and 115 mismatches. Corpus-wide exact matches moved from 66,039 to 66,436 (+397); the additional gains are applications of the same boundary rule outside this strict ASCII detector, including compatibility-unit forms. The complete standard suite remains 5,141/5,141. -Current decimal-point measurement: 4546 candidates, 3394 exact controls, 1152 mismatches, 1095 members in the actual `pending_rule_review` subcluster, and 262/1152 evaluable mismatches whose first difference is inside the complete decimal-containing word's current-engine signature. Hangeul rules 43 and 48 keep an ASCII point between digits in the numeric sequence and encode it as the decimal point; rules 35 and 69 supply controls for adjacent Roman-number chains and Roman units. This is an implementation-candidate audit, not permission to specialize on a corpus reference. +Current decimal-point measurement: 4546 candidates, 3397 exact controls, 1149 mismatches, 1092 members in the actual `pending_rule_review` subcluster, and 262/1149 evaluable mismatches whose first difference is inside the complete decimal-containing word's current-engine signature. Hangeul rules 43 and 48 keep an ASCII point between digits in the numeric sequence and encode it as the decimal point; rules 35 and 69 supply controls for adjacent Roman-number chains and Roman units. This is an implementation-candidate audit, not permission to specialize on a corpus reference. Analyzer checkpoint `ed20f99` recorded the pre-fix baseline as 4,546 candidates, 2,584 exact controls, 1,962 mismatches, 1,905 pending members, and 875 localized first differences. Its dominant localized transition was reference decimal point `U+2832 ⠲` versus current Roman indicator `U+2834 ⠴` in 647 cases. After the generalized rule-43/48 guard, that transition is absent: 525 cases become exact and the other corrected prefixes expose later independent mismatches. Current all-caps `OU` measurement: 1816 candidates, 64 exact controls, 1752 mismatches, 1666 members in the actual `pending_rule_review` subcluster, and 1332/1752 evaluable mismatches whose first difference is inside the current-engine output signature for the detected run. This is a pronunciation-sensitive UEB review cohort, not an engine routing rule. -Current standalone-uppercase measurement: 62411 candidates, 50594 exact controls, 11817 mismatches, and 10359 members in the actual `pending_rule_review` subcluster. Its high frequency does not make it causal: the same input shape is exact in many cases, and a sentence containing the shape may first differ at another Roman, numeric, or punctuation structure. No engine change is inferred from this cohort. +Current standalone-uppercase measurement: 62411 candidates, 50640 exact controls, 11771 mismatches, and 10312 members in the actual `pending_rule_review` subcluster. Its high frequency does not make it causal: the same input shape is exact in many cases, and a sentence containing the shape may first differ at another Roman, numeric, or punctuation structure. No engine change is inferred from this cohort. -Current Korean-prefixed all-caps parenthetical measurement: 54492 candidates, 45148 exact controls, 9344 mismatches, and 8043 members in the actual `pending_rule_review` subcluster. This is a semantic-collision audit, not an engine routing rule; no implementation change is inferred from its reference outputs. +Current Korean-prefixed all-caps parenthetical measurement: 54492 candidates, 45187 exact controls, 9305 mismatches, and 8003 members in the actual `pending_rule_review` subcluster. This is a semantic-collision audit, not an engine routing rule; no implementation change is inferred from its reference outputs. -Current rule-34 opening-order measurement: 64382 structural candidates, 52990 exact controls, 11392 mismatches, and 1111/11392 evaluable mismatches whose first difference is inside the current engine's Korean opening-parenthesis cells. Only the 1110 localized first-cell transitions have the reference/current order `⠴` versus `⠦`. After requiring the complete reference `⠴⠐⠣` versus current/PDF `⠦⠄⠴` three-cell signature and preserving higher-priority comparison classifications, 1107 are classified as `corpus_suspect`; mere coexistence with a Korean-prefixed Roman annotation does not change a primary class. +Current rule-34 opening-order measurement: 64382 structural candidates, 53042 exact controls, 11340 mismatches, and 1112/11340 evaluable mismatches whose first difference is inside the current engine's Korean opening-parenthesis cells. Only the 1111 localized first-cell transitions have the reference/current order `⠴` versus `⠦`. After requiring the complete reference `⠴⠐⠣` versus current/PDF `⠦⠄⠴` three-cell signature and preserving higher-priority comparison classifications, 1108 are classified as `corpus_suspect`; mere coexistence with a Korean-prefixed Roman annotation does not change a primary class. Current all-caps Roman middle-dot measurement: 97 candidates, 0 exact controls, 97 mismatches, and 96 members in the actual `pending_rule_review` subcluster. This cross-cutting cohort preserves every primary class and is not an engine routing rule. Current Roman-before-middle-dot boundary measurement: 577 candidates, 0 exact controls, 577 mismatches, 565 members in the actual `pending_rule_review` subcluster, and 428/577 evaluable mismatches whose first difference is localized to the one current terminator immediately before the middle dot. The locator encodes each real input prefix ending at the dot, so identifier state such as `K-ICS·...` is retained without consulting expected. This raises localized target coverage from 342 to 417 and reduces the then-leading residual `⠐ -> ⠲` transition from 297 to 223. Rules 29, 33, and 50 support the current terminator path but do not support the localized reference omission; no engine change or primary-class rewrite is inferred. -Current attached Roman-to-Korean boundary measurement: 17693 candidates, 12966 exact controls, 4727 mismatches, 3895 members in the actual `pending_rule_review` subcluster, and 0/4727 evaluable mismatches whose first difference is localized to the current boundary marker. The localized target `⠲ -> ⠸` occurs 0 times and the reverse `⠸ -> ⠲` occurs 0 times. Korean rule 29 (2024 Korean-rules PDF p.26, printed p.20) requires a Roman terminator around Roman text in a Korean sentence, whereas rule 39 (PDF pp.31-32, printed pp.25-26) applies the Korean opening/closing markers only when Roman text is the sentence's main language. Among exact candidates, 10996 expose the current rule-29 terminator at such a boundary and 0 expose the current rule-39 opening. Its printed controls include both an English sentence (`What is 김치 in English?`) and the address `www.대통령.kr` inside a Korean sentence. Official UEB 2.4.7 (2024 UEB PDF p.41, printed p.13) confirms that a UEB mode does not extend through a switch to another braille code, but does not choose whether this Korean boundary belongs to a Korean-main or Roman-main context. Therefore the attached script boundary alone cannot distinguish ordinary Korean prose from an embedded Roman-domain context. Primary classes are preserved and no engine change is inferred without a narrower input-derived dominance gate. At diagnostic checkpoint `57c4608`, this same cohort was 17,693 candidates / 12,724 exact / 4,969 mismatch, with 268 localized `⠲ -> ⠸`, zero reverse, 10,780 exact rule-29 markers, and zero exact rule-39 markers. After narrowing the engine by dominance and the PDF domain shape, it is 12,924 exact / 4,769 mismatch with no localized target or reverse; corpus-wide exact rises from 67,715 to 68,101 (+386). The remaining 1 mismatch with a current rule-39 opening is a list-heavy Korean sentence whose ASCII-leading brand tokens satisfy the mechanical word-count majority; its first difference is not localized to this boundary, so it remains a semantic pending control rather than grounds for another engine branch. +Current attached Roman-to-Korean boundary measurement: 17693 candidates, 12973 exact controls, 4720 mismatches, 3887 members in the actual `pending_rule_review` subcluster, and 0/4720 evaluable mismatches whose first difference is localized to the current boundary marker. The localized target `⠲ -> ⠸` occurs 0 times and the reverse `⠸ -> ⠲` occurs 0 times. Korean rule 29 (2024 Korean-rules PDF p.26, printed p.20) requires a Roman terminator around Roman text in a Korean sentence, whereas rule 39 (PDF pp.31-32, printed pp.25-26) applies the Korean opening/closing markers only when Roman text is the sentence's main language. Among exact candidates, 11002 expose the current rule-29 terminator at such a boundary and 0 expose the current rule-39 opening. Its printed controls include both an English sentence (`What is 김치 in English?`) and the address `www.대통령.kr` inside a Korean sentence. Official UEB 2.4.7 (2024 UEB PDF p.41, printed p.13) confirms that a UEB mode does not extend through a switch to another braille code, but does not choose whether this Korean boundary belongs to a Korean-main or Roman-main context. Therefore the attached script boundary alone cannot distinguish ordinary Korean prose from an embedded Roman-domain context. Primary classes are preserved and no engine change is inferred without a narrower input-derived dominance gate. At diagnostic checkpoint `57c4608`, this same cohort was 17,693 candidates / 12,724 exact / 4,969 mismatch, with 268 localized `⠲ -> ⠸`, zero reverse, 10,780 exact rule-29 markers, and zero exact rule-39 markers. After narrowing the engine by dominance and the PDF domain shape, it is 12,924 exact / 4,769 mismatch with no localized target or reverse; corpus-wide exact rises from 67,715 to 68,101 (+386). The remaining 1 mismatch with a current rule-39 opening is a list-heavy Korean sentence whose ASCII-leading brand tokens satisfy the mechanical word-count majority; its first difference is not localized to this boundary, so it remains a semantic pending control rather than grounds for another engine branch. Current rule-39 narrowed-scope audit: 947 candidates, 386 exact controls, 561 mismatches, and 549 members in the actual `pending_rule_review` subcluster. This input-derived scope is recorded separately from the direct-boundary output-localizer; primary classes are unchanged. At the implementation checkpoint, this conservative first-script-word approximation contains 385 exact cases, accounting for all but one of the corpus-wide +386 net gain; no localized reverse transition is observed in the direct-boundary cohort. Current inline parenthesized-operator measurement: 23 candidates, 17 exact controls, 6 mismatches, 6 members in the actual `pending_rule_review` subcluster, and 2/6 evaluable mismatches whose first differing cell is inside the emitted structure. Primary classes are preserved. At that implementation checkpoint, the strict cohort moved from 0 to 17 exact cases; the corpus-wide total moved from 65,491 to 65,514 (+23 exact) because the same PDF-backed spacing rule also applied outside the stricter Korean-boundary audit gate. These are immutable checkpoint counts rather than the report's later cumulative total. The complete standard suite remained 5,141/5,141. -Current tight-triangle measurement: 377 candidates, 0 exact controls, 377 mismatches, 376 members in the actual `pending_rule_review` subcluster, and 333/377 evaluable mismatches whose first difference is inside the `△` plus first-Korean output range. No engine change is inferred. +Current tight-triangle measurement: 377 candidates, 0 exact controls, 377 mismatches, 376 members in the actual `pending_rule_review` subcluster, and 334/377 evaluable mismatches whose first difference is inside the `△` plus first-Korean output range. No engine change is inferred. ## Encoding-error diagnostics @@ -3369,10 +3357,10 @@ The audit starts from all raw encoding errors, then separates cases already reso | Encoding-error audit | Cases | |---|---:| | Raw encoding errors | 450 | -| Resolved by comparison method | 275 | +| Resolved by comparison method | 276 | | Excluded as corpus suspect | 0 | -| Unresolved encoding-error review cases | 175 | -| Explained by singleton unsupported character(s) | 175 | +| Unresolved encoding-error review cases | 174 | +| Explained by singleton unsupported character(s) | 174 | | Multiple singleton unsupported characters | 2 | | Unclassified without a singleton explanation | 0 | @@ -3383,11 +3371,11 @@ The audit starts from all raw encoding errors, then separates cases already reso | Error message | Cases | |---|---:| -| `Invalid symbol character` | 175 | +| `Invalid symbol character` | 174 | | Error family | Cases | |---|---:| -| `enclosed_organization_mark` | 156 | +| `enclosed_organization_mark` | 155 | | `letterlike_unit_symbol` | 9 | | `punctuation_or_layout_symbol` | 12 | @@ -3406,24 +3394,24 @@ Families are diagnostics, not automatic normalization permissions. Rules 68/69 c | `U+2E31 ⸱` | 2 | `⸱` | `punctuation_or_layout_symbol` | | `U+302E 〮` | 1 | `〮` | `punctuation_or_layout_symbol` | | `U+3214 ㈔` | 2 | `(사)` | `enclosed_organization_mark` | -| `U+321C ㈜` | 154 | `(주)` | `enclosed_organization_mark` | +| `U+321C ㈜` | 153 | `(주)` | `enclosed_organization_mark` | ## Shards | Shard | Exact | Total | Accuracy | |---|---:|---:|---:| -| `sentence_01.json` | 20531 | 25000 | 82.12% | -| `sentence_02.json` | 19980 | 25000 | 79.92% | -| `sentence_03.json` | 20632 | 25000 | 82.53% | -| `sentence_04.json` | 7225 | 8528 | 84.72% | +| `sentence_01.json` | 20553 | 25000 | 82.21% | +| `sentence_02.json` | 19994 | 25000 | 79.98% | +| `sentence_03.json` | 20662 | 25000 | 82.65% | +| `sentence_04.json` | 7230 | 8528 | 84.78% | ## Overlapping mismatch traits | Trait | Count | |---|---:| -| `contains_ascii_digits` | 11907 | -| `contains_ascii_letters` | 14985 | -| `contains_delimiter_or_quote` | 15160 | +| `contains_ascii_digits` | 11851 | +| `contains_ascii_letters` | 14926 | +| `contains_delimiter_or_quote` | 15089 | | `input_not_nfkc` | 712 | ## Samples diff --git a/libs/braillify/examples/nikl_corpus_analyze.rs b/libs/braillify/examples/nikl_corpus_analyze.rs index 8c244e8a..a1b5b351 100644 --- a/libs/braillify/examples/nikl_corpus_analyze.rs +++ b/libs/braillify/examples/nikl_corpus_analyze.rs @@ -3226,7 +3226,7 @@ fn first_difference_in_tight_triangle(item: &EncodedCase) -> bool { .any(|range| range.contains(&first_difference)) } -/// Mirrors the existing token-normalization rule's narrow input shape: a +/// Mirrors the removed token-normalization rule's narrow input shape: a /// whitespace-delimited Korean token ends in `있다` (optionally followed by a /// full stop) but contains no printed space before that suffix. Membership is /// descriptive; it does not decide whether orthography may override rule 49's @@ -3254,8 +3254,9 @@ fn attached_korean_auxiliary_itda_spans(input: &str) -> Vec { spans } -/// Locates only the blank currently inserted immediately before `있다`, using -/// the real input prefix rather than any reference cell. +/// Locates only a blank inserted immediately before `있다`, using the real +/// input prefix rather than any reference cell. After the rule-49 correction, +/// this deliberately returns no ranges for the affected inputs. fn attached_korean_auxiliary_itda_actual_ranges( input: &str, actual: &str, @@ -4542,7 +4543,7 @@ fn markdown(report: &AnalysisReport) -> String { `tight_triangle_mark_immediately_before_korean` gate requires literal \ `△한글` with no input space and includes the first following Korean cell in its localized \ output range, so an observed missing-space difference is measured at the mark boundary. \ - The `attached_korean_auxiliary_itda_spacing` gate mirrors the current normalization \ + The `attached_korean_auxiliary_itda_spacing` gate mirrors the former normalization \ shape for a Korean token ending in attached `있다`; its localizer independently encodes \ the real prefix and claims only the blank inserted before the suffix. It does not decide \ whether orthographic correction may override the printed input.\n\n", @@ -5808,16 +5809,25 @@ fn markdown(report: &AnalysisReport) -> String { the print input. The PDF consistently retains an explicit space in `그리고 있다` \ (physical p.18), `살고 있다` (p.26), and `수강하고 있다` (p.30), but it gives no \ example authorizing a transcriber to insert a missing print space. The current \ - token normalizer nevertheless splits any Korean token ending in attached `있다`.\n\n\ - The diagnostic baseline has {} candidates / {} exact / {} mismatch, preserving {} \ + pre-fix token normalizer nevertheless split any Korean token ending in attached \ + `있다`.\n\n\ + The diagnostic baseline had 95 candidates / 0 exact / 95 mismatch. All 95 were in \ + the exact former implementation scope; 74 first differences were at the inserted \ + blank: 73 `{expected_attached}`, one `{other_attached}`, and no localized reverse. \ + The absence of a baseline exact member is the in-scope regression control.\n\n\ + After removing that input-correcting transformation, the cohort has {} candidates / \ + {} exact / {} mismatch, preserving {} \ `pending_rule_review`, {} `corpus_suspect`, {} `comparison_method`, and {} \ - `unsupported_character_review` mismatch primaries. Of {} evaluable mismatches, {} \ - put the first difference exactly at the independently located inserted blank: {} \ - `{expected_attached}`, {} `{other_attached}`, and {} `{expected_spaced}`. There are \ - no exact members and no localized reverse; the three explicitly spaced PDF forms \ - are the independent negative controls. This checkpoint records the scope before \ - testing removal of the input-correcting normalizer; it does not infer a branch from \ - the corpus reference.\n", + `unsupported_character_review` mismatch primaries. Of {} evaluable current \ + mismatches, {} still localize to an inserted blank: {} `{expected_attached}`, {} \ + `{other_attached}`, and {} `{expected_spaced}`. Seventy-one cases become exact; the \ + other 24 retain independent earlier differences. Corpus exact increases by the same \ + 71 with zero exact-set regressions. The three explicitly spaced PDF forms remain \ + full-encoder negative controls, so removing correction of missing input whitespace \ + does not remove a printed space. The local rule-47 standard case had accidentally \ + transcribed PDF physical p.36 `덮여 있다` as attached `덮여있다` while retaining the \ + PDF's spaced braille; correcting that input transcription restores the complete \ + 5,141/5,141 standard summary without an engine exception.\n", stats.candidates, stats.exact, stats.mismatch, @@ -7975,13 +7985,12 @@ mod tests { } #[test] - fn localizes_current_inserted_blank_before_attached_itda() { + fn current_engine_does_not_insert_blank_before_attached_itda() { let input = "성장을 하고있다."; let actual = braillify::encode_to_unicode(input).expect("spacing probe must encode"); let ranges = attached_korean_auxiliary_itda_actual_ranges(input, &actual); - assert_eq!(ranges.len(), 1); - assert_eq!(actual.chars().nth(ranges[0].start), Some('⠀')); + assert!(ranges.is_empty()); } #[test] diff --git a/libs/braillify/src/rules/token_rules/spacing.rs b/libs/braillify/src/rules/token_rules/spacing.rs index a8093b8b..9f118355 100644 --- a/libs/braillify/src/rules/token_rules/spacing.rs +++ b/libs/braillify/src/rules/token_rules/spacing.rs @@ -1,87 +1,32 @@ -use std::borrow::Cow; - -use crate::rules::token::{SpaceKind, Token, WordMeta, WordToken}; +use crate::rules::token::Token; use crate::rules::token_rule::{TokenAction, TokenPhase, TokenRule}; pub struct AsteriskSpacingRule; -/// 한국어 보조용언 `있다`(있-) 분리. -/// -/// PDF 한국 점자 규정 / 한글 띄어쓰기 일반 원칙에 따라 보조용언 `있다`(있다·있었다 등)는 -/// 본용언과 띄어 쓴다. 묵자 입력은 띄어쓰기가 생략되어 한 단어로 들어오는 경우가 있어 -/// (예: "덮여있다"), 토큰 단계에서 명시적으로 분리하여 점자 출력의 빈칸을 보장한다. +/// Compatibility registration for the removed auxiliary-verb normalizer. /// -/// 보수적 매칭: -/// - 단어 끝이 `있다` 또는 그 변형(`있다.`, `있다?`, `있다!`, `있어`, `있었다` 등)일 때만 -/// 접두 부분과 분리한다. -/// - 접두 부분이 비어 있으면 분리하지 않는다(독립된 "있다" 토큰은 그대로 둔다). -/// - 접두 부분에 한글 음절이 1개라도 있어야 한다. +/// Korean rule 49 says that braille spacing follows print. Consequently the +/// encoder must not correct an attached `있다` by inserting a space that is not +/// present in the input. The registry type remains temporarily stable, but the +/// rule deliberately performs no transformation. pub struct KoreanAuxiliaryVerbSpacingRule; -const AUX_VERB_SUFFIXES: &[&str] = &[ - // 보조용언 본형 "있다"만 우선 분리. 변형 형태(있어요, 있습니다, 있었다 등)는 - // testcase 회귀 분석을 거치며 보수적으로 확장한다. - "있다.", "있다", -]; - -fn split_aux_verb(text: &str) -> Option<(&str, &str)> { - for suffix in AUX_VERB_SUFFIXES { - if let Some(prefix) = text.strip_suffix(suffix) - && !prefix.is_empty() - && prefix.chars().any(crate::utils::is_korean_char) - { - return Some((prefix, *suffix)); - } - } - None -} - impl TokenRule for KoreanAuxiliaryVerbSpacingRule { fn phase(&self) -> TokenPhase { TokenPhase::Normalization } fn priority(&self) -> u16 { - 50 // Word_shortcut(100)·LaTeX(110+)보다 먼저 분리 + 50 // Registry compatibility; no normalization is performed. } fn apply<'a>( &self, - tokens: &[Token<'a>], - index: usize, + _tokens: &[Token<'a>], + _index: usize, _state: &mut crate::rules::context::EncoderState, ) -> Result, String> { - let Some(Token::Word(word)) = tokens.get(index) else { - return Ok(TokenAction::Noop); - }; - - if !word.meta.has_korean { - return Ok(TokenAction::Noop); - } - - let text = word.text.as_ref(); - let Some((prefix, suffix)) = split_aux_verb(text) else { - return Ok(TokenAction::Noop); - }; - - let prefix_owned = prefix.to_string(); - let suffix_owned = suffix.to_string(); - let prefix_chars: Vec = prefix_owned.chars().collect(); - let suffix_chars: Vec = suffix_owned.chars().collect(); - - Ok(TokenAction::ReplaceMany(vec![ - Token::Word(WordToken { - text: Cow::Owned(prefix_owned), - chars: prefix_chars.clone(), - meta: WordMeta::from_chars(&prefix_chars), - }), - Token::Space(SpaceKind::Regular), - Token::Word(WordToken { - text: Cow::Owned(suffix_owned), - chars: suffix_chars.clone(), - meta: WordMeta::from_chars(&suffix_chars), - }), - ])) + Ok(TokenAction::Noop) } } @@ -132,3 +77,30 @@ impl TokenRule for AsteriskSpacingRule { Ok(TokenAction::ReplaceMany(replacement)) } } + +#[cfg(test)] +mod tests { + /// 제49항은 묵자의 띄어쓰기를 따르며, 각 spaced 입력은 PDF에 그대로 + /// 실린 예제다. 대응 attached 입력에서는 없는 공백을 새로 만들지 않는다. + #[rstest::rstest] + #[case::rule18("그림을 그리고 있다.", "그림을 그리고있다.")] + #[case::rule29( + "그녀는 Los Angeles의 한인 타운에 살고 있다.", + "그녀는 Los Angeles의 한인 타운에 살고있다." + )] + #[case::rule36( + "가영이는 미적분학 II 과목을 수강하고 있다.", + "가영이는 미적분학 II 과목을 수강하고있다." + )] + fn full_encoder_preserves_printed_auxiliary_spacing_only( + #[case] spaced: &str, + #[case] attached: &str, + ) { + let spaced_output = crate::encode_to_unicode(spaced).expect("PDF example must encode"); + let attached_output = crate::encode_to_unicode(attached).expect("control must encode"); + let spaced_blanks = spaced_output.chars().filter(|cell| *cell == '⠀').count(); + let attached_blanks = attached_output.chars().filter(|cell| *cell == '⠀').count(); + + assert_eq!(spaced_blanks, attached_blanks + 1); + } +} From f7941da29dc8c1c608df33aa60d79f052334d989 Mon Sep 17 00:00:00 2001 From: devfive Date: Tue, 1 Sep 2026 17:23:41 +0900 Subject: [PATCH 54/77] Diagnose ampersand capitalization extent --- docs/corpus-analysis/NIKL_2025_V1.md | 206 ++++++++++++------ .../braillify/examples/nikl_corpus_analyze.rs | 136 ++++++++++++ 2 files changed, 279 insertions(+), 63 deletions(-) diff --git a/docs/corpus-analysis/NIKL_2025_V1.md b/docs/corpus-analysis/NIKL_2025_V1.md index 3e650d64..4c157324 100644 --- a/docs/corpus-analysis/NIKL_2025_V1.md +++ b/docs/corpus-analysis/NIKL_2025_V1.md @@ -299,22 +299,22 @@ This ranking removes only cases whose first difference is inside an existing out | Rank | Expected → actual first cell | Residual cases | |---:|---|---:| | 1 | `U+2810 ⠐ -> U+2832 ⠲` | 220 | -| 2 | `U+2817 ⠗ -> U+2820 ⠠` | 164 | -| 3 | `U+2834 ⠴ -> U+2800 ⠀` | 154 | -| 4 | `U+283C ⠼ -> U+2800 ⠀` | 153 | -| 5 | `U+281B ⠛ -> U+2823 ⠣` | 144 | -| 6 | `U+2810 ⠐ -> U+2800 ⠀` | 130 | -| 7 | `U+2826 ⠦ -> U+2810 ⠐` | 125 | -| 8 | `U+2820 ⠠ -> U+281E ⠞` | 106 | -| 9 | `U+2811 ⠑ -> U+283B ⠻` | 101 | -| 10 | `U+2800 ⠀ -> U+2832 ⠲` | 100 | -| 11 | `U+2810 ⠐ -> U+2814 ⠔` | 98 | -| 12 | `U+280E ⠎ -> U+2820 ⠠` | 94 | -| 13 | `U+2803 ⠃ -> U+2820 ⠠` | 87 | -| 14 | `U+2804 ⠄ -> U+2834 ⠴` | 87 | -| 15 | `U+2826 ⠦ -> U+2800 ⠀` | 79 | -| 16 | `U+2824 ⠤ -> U+2800 ⠀` | 78 | -| 17 | `U+280E ⠎ -> U+2829 ⠩` | 74 | +| 2 | `U+2834 ⠴ -> U+2800 ⠀` | 154 | +| 3 | `U+283C ⠼ -> U+2800 ⠀` | 153 | +| 4 | `U+281B ⠛ -> U+2823 ⠣` | 144 | +| 5 | `U+2810 ⠐ -> U+2800 ⠀` | 130 | +| 6 | `U+2826 ⠦ -> U+2810 ⠐` | 125 | +| 7 | `U+2811 ⠑ -> U+283B ⠻` | 101 | +| 8 | `U+2800 ⠀ -> U+2832 ⠲` | 100 | +| 9 | `U+2810 ⠐ -> U+2814 ⠔` | 98 | +| 10 | `U+2820 ⠠ -> U+281E ⠞` | 96 | +| 11 | `U+2803 ⠃ -> U+2820 ⠠` | 87 | +| 12 | `U+2804 ⠄ -> U+2834 ⠴` | 87 | +| 13 | `U+2826 ⠦ -> U+2800 ⠀` | 79 | +| 14 | `U+2824 ⠤ -> U+2800 ⠀` | 78 | +| 15 | `U+280E ⠎ -> U+2820 ⠠` | 76 | +| 16 | `U+280E ⠎ -> U+2829 ⠩` | 73 | +| 17 | `U+2817 ⠗ -> U+2820 ⠠` | 65 | | 18 | `U+2820 ⠠ -> U+2809 ⠉` | 65 | | 19 | `U+2834 ⠴ -> U+2820 ⠠` | 64 | | 20 | `U+2830 ⠰ -> U+2820 ⠠` | 56 | @@ -342,29 +342,6 @@ This ranking removes only cases whose first difference is inside an existing out - first differing cell (zero-based): 6 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -### Residual `U+2817 ⠗ -> U+2820 ⠠` - -- `sentence_01.json` #1294: 한편 엔씨소프트는 오픈형 R&D 개발 문화 ‘엔씽(NCing)’의 일환으로 개발 중인 다양한 장르의 신규 프로젝트를 공개하고 있다. - - expected: `⠥⠙⠵⠚⠻⠀⠴⠠⠗⠈⠯⠠⠙⠲⠀⠈⠗⠘⠂⠀⠑⠛⠚⠧` - - actual: `⠥⠙⠵⠚⠻⠀⠴⠠⠠⠗⠈⠯⠙⠲⠀⠈⠗⠘⠂⠀⠑⠛⠚⠧` - - first differing cell (zero-based): 27 - - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_02.json` #226: 대한민국 드론·UAM 박람회는 UAM(도심항공교통) 등 차세대 국토교통 R&D와 주요 지자체의 관련 사업을 살펴볼 수 있는 의미 있는 행사로 국토교통부와 인천광역시가 공동 주최한다. - - expected: `⠥⠈⠬⠓⠿⠀⠴⠠⠗⠈⠯⠠⠙⠲⠧⠀⠨⠍⠬⠀⠨⠕⠨⠰` - - actual: `⠥⠈⠬⠓⠿⠀⠴⠠⠠⠗⠈⠯⠙⠲⠧⠀⠨⠍⠬⠀⠨⠕⠨⠰` - - first differing cell (zero-based): 81 - - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_03.json` #647: 온유는 지난 3월 6일 첫 정규 앨범 ‘써클(Circle)’로 컴백한다. 이번 앨범은 몽환적인 R&B 장르의 타이틀곡 ‘O(Circle)’(써클)을 비롯한 다채로운 분위기의 10곡으로 구성됐다. - - expected: `⠧⠒⠨⠹⠟⠀⠴⠠⠗⠈⠯⠠⠃⠲⠀⠨⠶⠐⠪⠺⠀⠓⠣⠕` - - actual: `⠧⠒⠨⠹⠟⠀⠴⠠⠠⠗⠈⠯⠃⠲⠀⠨⠶⠐⠪⠺⠀⠓⠣⠕` - - first differing cell (zero-based): 92 - - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_04.json` #2491: 앞서 정보통신기술(ICT) 분야를 담당하는 박윤규 과기정통부 2차관도 지난 1일 산하 정보통신정책실·네트워크정책실 소속 실·국장을 대상으로 간부회의를 소집해 ICT 분야 R&D 예산으로 잡힌 사업들을 원점에서 재검토하도록 했다. - - expected: `⠲⠀⠘⠛⠜⠀⠴⠠⠗⠈⠯⠠⠙⠲⠀⠌⠇⠒⠪⠐⠥⠀⠨⠃` - - actual: `⠲⠀⠘⠛⠜⠀⠴⠠⠠⠗⠈⠯⠙⠲⠀⠌⠇⠒⠪⠐⠥⠀⠨⠃` - - first differing cell (zero-based): 175 - - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` - ### Residual `U+2834 ⠴ -> U+2800 ⠀` - `sentence_01.json` #947: 코나체인은 코나아이와 함께 한국은행의 디지털 화폐(CBDC)의 H/W Wallet의 개발에 참여하였고, 현재 블록체인 기반의 설문조사, 투표서비스를 제공 중이다. @@ -480,29 +457,6 @@ This ranking removes only cases whose first difference is inside an existing out - first differing cell (zero-based): 98 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -### Residual `U+2820 ⠠ -> U+281E ⠞` - -- `sentence_01.json` #2166: 이에 따라 경찰은 CC(폐쇄회로)TV를 분석하고 달아난 카자흐스탄인 2명을 추적 중이다. 이들이 뛰어넘은 담장이 CCTV 사각지대여서 추적에 애를 먹고 있다. - - expected: `⠗⠚⠽⠐⠥⠠⠴⠴⠠⠠⠞⠧⠲⠐⠮⠀⠘⠛⠠⠹⠚⠈⠥⠀` - - actual: `⠗⠚⠽⠐⠥⠠⠴⠴⠞⠧⠲⠐⠮⠀⠘⠛⠠⠹⠚⠈⠥⠀⠊⠂` - - first differing cell (zero-based): 34 - - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_02.json` #2253: 경찰은 금은방 CC(폐쇄회로)TV 등을 통해 이들의 얼굴을 확인하고 탐문 수사를 벌여 범행 4시간 만에 집에 있는 A군을 붙잡았다. - - expected: `⠗⠚⠽⠐⠥⠠⠴⠴⠠⠠⠞⠧⠲⠀⠊⠪⠶⠮⠀⠓⠿⠚⠗⠀` - - actual: `⠗⠚⠽⠐⠥⠠⠴⠴⠞⠧⠲⠀⠊⠪⠶⠮⠀⠓⠿⠚⠗⠀⠕⠊` - - first differing cell (zero-based): 33 - - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_03.json` #50: 당시 CC(폐쇄)TV에 찍힌 장면을 보면 A씨는 엘리베이터를 기다리는 B씨를 발견하자 몰래 뒤로 다가가 갑자기 피해 여성의 머리를 뒤에서 돌려차기로 가격하는 등 폭행했다. - - expected: `⠙⠌⠠⠧⠗⠠⠴⠴⠠⠠⠞⠧⠲⠝⠀⠠⠨⠕⠁⠚⠟⠀⠨⠶` - - actual: `⠙⠌⠠⠧⠗⠠⠴⠴⠞⠧⠲⠝⠀⠠⠨⠕⠁⠚⠟⠀⠨⠶⠑⠡` - - first differing cell (zero-based): 20 - - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_04.json` #819: 경찰은 7개월간 부산·울산·경남·경북·경기·강원 일대의 방범용 및 사설 CC(폐쇄회로)TV 영상을 추적·분석해 검거에 성공했다. 그러나 모두 국내 수거책뿐이었다. 경찰은 해외 조직 검거에도 매진하겠다고 밝혔다. - - expected: `⠗⠚⠽⠐⠥⠠⠴⠴⠠⠠⠞⠧⠲⠀⠻⠇⠶⠮⠀⠰⠍⠨⠹⠐` - - actual: `⠗⠚⠽⠐⠥⠠⠴⠴⠞⠧⠲⠀⠻⠇⠶⠮⠀⠰⠍⠨⠹⠐⠆⠘` - - first differing cell (zero-based): 92 - - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` - ### Residual `U+2811 ⠑ -> U+283B ⠻` - `sentence_01.json` #1903: 김현용 현대차증권 연구원은 “전사적자원관리(ERP), 생산관리프로그램(MES) 등 기존의 기업향 솔루션에 이번 인수로 구매공급망관리(SRM)이 더해지며 사업 포트폴리오가 한층 단단해진 것으로 판단된다”고 설명했다. @@ -549,9 +503,55 @@ This ranking removes only cases whose first difference is inside an existing out - first differing cell (zero-based): 54 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +### Residual `U+2810 ⠐ -> U+2814 ⠔` + +- `sentence_01.json` #10236: 정현찬(75) 전 문재인 대통령 직속 농어업·농어촌특별위원회(농특위) 위원장이 <여럿이 함께 기어이 벽을 넘는, 농민이 잘 사는 나라>라는 제목으로 낸 회고록에서 강조한 말이다. + - expected: `⠍⠗⠏⠒⠨⠶⠕⠀⠐⠶⠱⠐⠎⠄⠕⠀⠚⠢⠠⠈⠝⠀⠈⠕` + - actual: `⠍⠗⠏⠒⠨⠶⠕⠀⠔⠔⠱⠐⠎⠄⠕⠀⠚⠢⠠⠈⠝⠀⠈⠕` + - first differing cell (zero-based): 80 + - current primary/reason: `pending_rule_review` / `number_rule_review` +- `sentence_02.json` #1966: ‘가치있는 기술·자본시장 정보제공’ 매체를 표방하는 경제종합 인터넷 미디어 <블로터>가 인공지능(AI) 시대의 브랜드 마케팅에 대해 이야기합니다. + - expected: `⠄⠀⠑⠕⠊⠕⠎⠀⠐⠶⠘⠮⠐⠥⠓⠎⠶⠂⠫⠀⠟⠈⠿⠨` + - actual: `⠄⠀⠑⠕⠊⠕⠎⠀⠔⠔⠘⠮⠐⠥⠓⠎⠀⠢⠢⠀⠫⠀⠟⠈` + - first differing cell (zero-based): 72 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_03.json` #13277: 선덜랜드는 프리미어리그(EPL)에서 챔피언십(2부)으로 강등된 2017년 여름 넷플릭스 다큐멘터리 <죽어도 선덜랜드>를 촬영했는데, EPL 복귀를 꿈꾸던 것과 달리 리그1으로 잇달아 추락하는 새드 엔딩으로 끝났다. + - expected: `⠑⠝⠒⠓⠎⠐⠕⠀⠐⠶⠨⠍⠁⠎⠊⠥⠀⠠⠾⠊⠞⠐⠗⠒` + - actual: `⠑⠝⠒⠓⠎⠐⠕⠀⠔⠔⠨⠍⠁⠎⠊⠥⠀⠠⠾⠊⠞⠐⠗⠒` + - first differing cell (zero-based): 103 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_04.json` #431: 그룹 방탄소년단(BTS) 지민이 지난달 발표한 첫 솔로 음반 <페이스>가 미국 빌보드 메인 음반 차트인 ‘빌보드 200’ 2위에 올랐다. 이는 K팝 솔로 가수로는 역대 최고 순위다. + - expected: `⠐⠥⠀⠪⠢⠘⠒⠀⠐⠶⠙⠝⠕⠠⠪⠶⠂⠫⠀⠑⠕⠈⠍⠁` + - actual: `⠐⠥⠀⠪⠢⠘⠒⠀⠔⠔⠙⠝⠕⠠⠪⠀⠢⠢⠀⠫⠀⠑⠕⠈` + - first differing cell (zero-based): 62 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` + +### Residual `U+2820 ⠠ -> U+281E ⠞` + +- `sentence_01.json` #2166: 이에 따라 경찰은 CC(폐쇄회로)TV를 분석하고 달아난 카자흐스탄인 2명을 추적 중이다. 이들이 뛰어넘은 담장이 CCTV 사각지대여서 추적에 애를 먹고 있다. + - expected: `⠗⠚⠽⠐⠥⠠⠴⠴⠠⠠⠞⠧⠲⠐⠮⠀⠘⠛⠠⠹⠚⠈⠥⠀` + - actual: `⠗⠚⠽⠐⠥⠠⠴⠴⠞⠧⠲⠐⠮⠀⠘⠛⠠⠹⠚⠈⠥⠀⠊⠂` + - first differing cell (zero-based): 34 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_02.json` #2253: 경찰은 금은방 CC(폐쇄회로)TV 등을 통해 이들의 얼굴을 확인하고 탐문 수사를 벌여 범행 4시간 만에 집에 있는 A군을 붙잡았다. + - expected: `⠗⠚⠽⠐⠥⠠⠴⠴⠠⠠⠞⠧⠲⠀⠊⠪⠶⠮⠀⠓⠿⠚⠗⠀` + - actual: `⠗⠚⠽⠐⠥⠠⠴⠴⠞⠧⠲⠀⠊⠪⠶⠮⠀⠓⠿⠚⠗⠀⠕⠊` + - first differing cell (zero-based): 33 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_03.json` #50: 당시 CC(폐쇄)TV에 찍힌 장면을 보면 A씨는 엘리베이터를 기다리는 B씨를 발견하자 몰래 뒤로 다가가 갑자기 피해 여성의 머리를 뒤에서 돌려차기로 가격하는 등 폭행했다. + - expected: `⠙⠌⠠⠧⠗⠠⠴⠴⠠⠠⠞⠧⠲⠝⠀⠠⠨⠕⠁⠚⠟⠀⠨⠶` + - actual: `⠙⠌⠠⠧⠗⠠⠴⠴⠞⠧⠲⠝⠀⠠⠨⠕⠁⠚⠟⠀⠨⠶⠑⠡` + - first differing cell (zero-based): 20 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_04.json` #819: 경찰은 7개월간 부산·울산·경남·경북·경기·강원 일대의 방범용 및 사설 CC(폐쇄회로)TV 영상을 추적·분석해 검거에 성공했다. 그러나 모두 국내 수거책뿐이었다. 경찰은 해외 조직 검거에도 매진하겠다고 밝혔다. + - expected: `⠗⠚⠽⠐⠥⠠⠴⠴⠠⠠⠞⠧⠲⠀⠻⠇⠶⠮⠀⠰⠍⠨⠹⠐` + - actual: `⠗⠚⠽⠐⠥⠠⠴⠴⠞⠧⠲⠀⠻⠇⠶⠮⠀⠰⠍⠨⠹⠐⠆⠘` + - first differing cell (zero-based): 92 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` + ## Cross-cutting input-only structural cohorts -These are cross-cutting input-only structural cohorts, not new primary classes and not engine routing rules. Candidate selection never changes a case's existing primary class by itself. Only cohort members already classified as `pending_rule_review` form a pending subcluster; exact and other-primary members are controls that retain their existing outcomes. A separate classifier may use independently justified, output-localized PDF evidence, as in the rule-34 three-cell contradiction below. The `uppercase_roman_headword_closed_multiword_parenthetical` gate requires a two-or-more character uppercase ASCII headword immediately followed by a closed parenthesis whose contents are two or more ASCII Roman words separated only by spaces. Because the contents admit only letters and spaces, visible operators, subscript/superscript notation, and nested parentheses are excluded deterministically. The `mixed_roman_korean_word_before_uppercase_headword_expansion` gate further requires a whitespace-delimited preceding word that contains both ASCII Roman and Korean and ends in Korean. It locates the following uppercase headword itself, including its immediately preceding emitted cell, so an earlier Roman entry in the same sentence cannot satisfy the output audit. The `single_capital_followed_by_parenthesized_digits` gate requires a standalone capital, a non-empty closed ASCII-digit parenthetical, and alphanumeric outer boundaries. It likewise includes the emitted entry-boundary cell in localization. The `uppercase_roman_run_followed_by_hyphen_digits` gate requires a maximal uppercase ASCII run, literal ASCII hyphen-minus, a non-empty digit run, and alphanumeric outer boundaries. Its localized range includes the current encoded run and its immediately preceding cell, keeping `F-35`-style routing separate from both parenthesized digits and headword expansions. The `uppercase_alphanumeric_roman_digit_sequence` gate covers the wider rule-35/math collision: a capital-led uppercase/digit sequence with optional internal ASCII hyphen/full stop and trailing prose comma/colon/semicolon. It localizes only the current entry cells and does not decide whether an `A-3`-shaped surface is an identifier or a mathematical expression. The `standalone_multi_character_uppercase_roman_word` gate finds maximal ASCII-letter runs of two or more capitals with non-alphanumeric boundaries; a run immediately followed by `(` is excluded so the HCA-style headword itself is not counted by both gates. The `korean_prefixed_closed_allcaps_parenthetical` gate requires an immediately preceding Korean character and a closed body of two or more uppercase ASCII letters. It intentionally contains both acronym annotations (`책임자(COO)`) and scientific formulae (`일산화탄소(CO)`) so their semantic collision remains measurable. The `korean_prefixed_closed_roman_annotation_rule_34_order` gate accepts the narrower rule-34 body grammar after an immediately preceding Korean character and localizes only the current engine's Korean opening-parenthesis cells. This distinguishes the PDF's parenthesis-before-Roman-indicator order from unrelated differences later in the same sentence. The `multi_character_allcaps_roman_runs_joined_by_middle_dot` gate requires two maximal ASCII-letter runs of at least two capitals joined directly by U+00B7, with non-alphanumeric outer boundaries. It records shapes such as `AI·SW` without assigning prose, mathematics, or science semantics. The `roman_run_immediately_before_attached_middle_dot_boundary` gate is output-localized: it requires a maximal ASCII-letter run immediately before U+00B7 and an attached Korean character or ASCII-letter run after it, then searches for that whole current-engine signature in the actual output. It therefore isolates the Roman terminator boundary without treating unrelated middle dots elsewhere in the sentence as causal. The `attached_ascii_roman_to_korean_script_boundary` gate requires an ASCII letter run immediately followed by Korean script and localizes only the current mode marker there: either rule 29's final `⠲` or rule 39's opening `⠸⠷`. It deliberately does not infer the dominant language of the sentence from that surface boundary. The `korean_majority_same_token_roman_sandwich_non_domain` scope audit mirrors the narrower rule-39 implementation gate: the nearest script characters on both sides of one Korean segment are ASCII Roman, the input is Korean-majority by first-script word counts, and the segment is not dot-delimited like the official domain example. It measures change scope but is not itself an output-localized causal classifier. The `korean_inline_parenthesized_single_arithmetic_operator` gate requires an immediate `Korean(` + one rule-45 arithmetic operator + `)Korean` span. Unlike broad coexistence traits, it also locates the current engine's emitted structure and counts a mismatch as signature-local only when the sentence's first differing cell falls inside that output range. The `allcaps_roman_run_containing_ou` gate finds maximal, alphanumeric-delimited uppercase ASCII runs containing adjacent `OU`. It locates the independently encoded run signature in the complete current output and counts only first differences inside that signature as localized. The `allcaps_roman_run_containing_st` gate applies the same output-position requirement to adjacent `ST`; it is kept separate because UEB 10.12 makes contraction use depend on how an abbreviation or acronym is pronounced. The `attached_ascii_roman_segments_joined_by_ampersand` gate requires non-empty ASCII-letter segments joined directly by `&`, excludes spaced/Korean/alphanumeric continuations, and localizes only the current output cell immediately before the ampersand through an independently encoded real-input prefix. The `ampersand_before_attached_ascii_roman_segment` gate covers the distinct one-sided UEB `&c` boundary: `&` is followed by a complete ASCII-letter segment, while an ASCII alphanumeric or another ampersand immediately before it and a digit continuation after it are excluded. Its output range is anchored by independently encoding the real input prefix before each occurrence, then includes only the current Rule-71/29 entry boundary; a second pre-fix anchor through `&` retains the former exit location. The `consecutive_ascii_roman_words_whitespace_boundary` gate requires two adjacent ASCII-letter words separated only by whitespace. For each boundary it independently encodes the real input prefix ending after the first word, then localizes only the current rule-29 terminator at that position or the full output's replacing blank. This prevents an unrelated Roman occurrence elsewhere in the sentence from satisfying the audit. Slash and other punctuation-separated forms are deliberately excluded. The `decimal_point_between_ascii_digits` gate finds whitespace-delimited words containing `digit.digit` and reproduces each whole word in a neutral Korean context, so suffixes and punctuation remain part of the current-engine signature. The `compact_numeric_ascii_letter_suffix` gate finds a numeric prefix immediately followed by ASCII letters, includes the immediately preceding output cell as its entry boundary, and retains suffix-specific outcome counts. It intentionally includes both possible rule-69 units and ambiguous variable/identifier forms; membership alone does not assign unit semantics. The `rule69_ascii_unit_before_terminator_skipping_symbol` gate is narrower: it accepts only ASCII unit spellings already supported by rule 69, includes the immediately following rule-33/34 punctuation cell in the localized signature, and does not infer new units. The `tight_triangle_mark_immediately_before_korean` gate requires literal `△한글` with no input space and includes the first following Korean cell in its localized output range, so an observed missing-space difference is measured at the mark boundary. The `attached_korean_auxiliary_itda_spacing` gate mirrors the former normalization shape for a Korean token ending in attached `있다`; its localizer independently encodes the real prefix and claims only the blank inserted before the suffix. It does not decide whether orthographic correction may override the printed input. +These are cross-cutting input-only structural cohorts, not new primary classes and not engine routing rules. Candidate selection never changes a case's existing primary class by itself. Only cohort members already classified as `pending_rule_review` form a pending subcluster; exact and other-primary members are controls that retain their existing outcomes. A separate classifier may use independently justified, output-localized PDF evidence, as in the rule-34 three-cell contradiction below. The `uppercase_roman_headword_closed_multiword_parenthetical` gate requires a two-or-more character uppercase ASCII headword immediately followed by a closed parenthesis whose contents are two or more ASCII Roman words separated only by spaces. Because the contents admit only letters and spaces, visible operators, subscript/superscript notation, and nested parentheses are excluded deterministically. The `mixed_roman_korean_word_before_uppercase_headword_expansion` gate further requires a whitespace-delimited preceding word that contains both ASCII Roman and Korean and ends in Korean. It locates the following uppercase headword itself, including its immediately preceding emitted cell, so an earlier Roman entry in the same sentence cannot satisfy the output audit. The `single_capital_followed_by_parenthesized_digits` gate requires a standalone capital, a non-empty closed ASCII-digit parenthetical, and alphanumeric outer boundaries. It likewise includes the emitted entry-boundary cell in localization. The `uppercase_roman_run_followed_by_hyphen_digits` gate requires a maximal uppercase ASCII run, literal ASCII hyphen-minus, a non-empty digit run, and alphanumeric outer boundaries. Its localized range includes the current encoded run and its immediately preceding cell, keeping `F-35`-style routing separate from both parenthesized digits and headword expansions. The `uppercase_alphanumeric_roman_digit_sequence` gate covers the wider rule-35/math collision: a capital-led uppercase/digit sequence with optional internal ASCII hyphen/full stop and trailing prose comma/colon/semicolon. It localizes only the current entry cells and does not decide whether an `A-3`-shaped surface is an identifier or a mathematical expression. The `standalone_multi_character_uppercase_roman_word` gate finds maximal ASCII-letter runs of two or more capitals with non-alphanumeric boundaries; a run immediately followed by `(` is excluded so the HCA-style headword itself is not counted by both gates. The `korean_prefixed_closed_allcaps_parenthetical` gate requires an immediately preceding Korean character and a closed body of two or more uppercase ASCII letters. It intentionally contains both acronym annotations (`책임자(COO)`) and scientific formulae (`일산화탄소(CO)`) so their semantic collision remains measurable. The `korean_prefixed_closed_roman_annotation_rule_34_order` gate accepts the narrower rule-34 body grammar after an immediately preceding Korean character and localizes only the current engine's Korean opening-parenthesis cells. This distinguishes the PDF's parenthesis-before-Roman-indicator order from unrelated differences later in the same sentence. The `multi_character_allcaps_roman_runs_joined_by_middle_dot` gate requires two maximal ASCII-letter runs of at least two capitals joined directly by U+00B7, with non-alphanumeric outer boundaries. It records shapes such as `AI·SW` without assigning prose, mathematics, or science semantics. The `roman_run_immediately_before_attached_middle_dot_boundary` gate is output-localized: it requires a maximal ASCII-letter run immediately before U+00B7 and an attached Korean character or ASCII-letter run after it, then searches for that whole current-engine signature in the actual output. It therefore isolates the Roman terminator boundary without treating unrelated middle dots elsewhere in the sentence as causal. The `attached_ascii_roman_to_korean_script_boundary` gate requires an ASCII letter run immediately followed by Korean script and localizes only the current mode marker there: either rule 29's final `⠲` or rule 39's opening `⠸⠷`. It deliberately does not infer the dominant language of the sentence from that surface boundary. The `korean_majority_same_token_roman_sandwich_non_domain` scope audit mirrors the narrower rule-39 implementation gate: the nearest script characters on both sides of one Korean segment are ASCII Roman, the input is Korean-majority by first-script word counts, and the segment is not dot-delimited like the official domain example. It measures change scope but is not itself an output-localized causal classifier. The `korean_inline_parenthesized_single_arithmetic_operator` gate requires an immediate `Korean(` + one rule-45 arithmetic operator + `)Korean` span. Unlike broad coexistence traits, it also locates the current engine's emitted structure and counts a mismatch as signature-local only when the sentence's first differing cell falls inside that output range. The `allcaps_roman_run_containing_ou` gate finds maximal, alphanumeric-delimited uppercase ASCII runs containing adjacent `OU`. It locates the independently encoded run signature in the complete current output and counts only first differences inside that signature as localized. The `allcaps_roman_run_containing_st` gate applies the same output-position requirement to adjacent `ST`; it is kept separate because UEB 10.12 makes contraction use depend on how an abbreviation or acronym is pronounced. The `uppercase_ascii_segments_joined_by_ampersand_capitalization` gate narrows attached ampersand runs to uppercase-only segments and locates the complete current Korean-context output. The run must begin its whitespace-delimited token, matching the current token-level capitals-word pre-emission scope; Korean-attached and punctuation-prefixed occurrences remain controls. UEB 8.4.2 says a nonalphabetic symbol terminates capitals word mode, while the official `AT&T` and `B&B` examples restart capitalization after `&`. The broader `attached_ascii_roman_segments_joined_by_ampersand` gate requires non-empty ASCII-letter segments joined directly by `&`, excludes spaced/Korean/alphanumeric continuations, and localizes only the current output cell immediately before the ampersand through an independently encoded real-input prefix. The `ampersand_before_attached_ascii_roman_segment` gate covers the distinct one-sided UEB `&c` boundary: `&` is followed by a complete ASCII-letter segment, while an ASCII alphanumeric or another ampersand immediately before it and a digit continuation after it are excluded. Its output range is anchored by independently encoding the real input prefix before each occurrence, then includes only the current Rule-71/29 entry boundary; a second pre-fix anchor through `&` retains the former exit location. The `consecutive_ascii_roman_words_whitespace_boundary` gate requires two adjacent ASCII-letter words separated only by whitespace. For each boundary it independently encodes the real input prefix ending after the first word, then localizes only the current rule-29 terminator at that position or the full output's replacing blank. This prevents an unrelated Roman occurrence elsewhere in the sentence from satisfying the audit. Slash and other punctuation-separated forms are deliberately excluded. The `decimal_point_between_ascii_digits` gate finds whitespace-delimited words containing `digit.digit` and reproduces each whole word in a neutral Korean context, so suffixes and punctuation remain part of the current-engine signature. The `compact_numeric_ascii_letter_suffix` gate finds a numeric prefix immediately followed by ASCII letters, includes the immediately preceding output cell as its entry boundary, and retains suffix-specific outcome counts. It intentionally includes both possible rule-69 units and ambiguous variable/identifier forms; membership alone does not assign unit semantics. The `rule69_ascii_unit_before_terminator_skipping_symbol` gate is narrower: it accepts only ASCII unit spellings already supported by rule 69, includes the immediately following rule-33/34 punctuation cell in the localized signature, and does not infer new units. The `tight_triangle_mark_immediately_before_korean` gate requires literal `△한글` with no input space and includes the first following Korean cell in its localized output range, so an observed missing-space difference is measured at the mark boundary. The `attached_korean_auxiliary_itda_spacing` gate mirrors the former normalization shape for a Korean token ending in attached `있다`; its localizer independently encodes the real prefix and claims only the blank inserted before the suffix. It does not decide whether orthographic correction may override the printed input. | Cluster | Candidates | Exact | Mismatch | Conflicting-reference cases | |---|---:|---:|---:|---:| @@ -590,6 +590,7 @@ These are cross-cutting input-only structural cohorts, not new primary classes a | `uppercase_alphanumeric_roman_digit_sequence` | 3429 | 1917 | 1512 | 0 | | `uppercase_ascii_run_immediately_after_digit_in_roman_sequence` | 1896 | 1106 | 790 | 0 | | `uppercase_ascii_run_immediately_after_hyphen_in_roman_sequence` | 952 | 166 | 786 | 0 | +| `uppercase_ascii_segments_joined_by_ampersand_capitalization` | 439 | 1 | 438 | 0 | | `uppercase_roman_headword_closed_multiword_parenthetical` | 175 | 16 | 159 | 0 | | `uppercase_roman_run_followed_by_hyphen_digits` | 571 | 191 | 380 | 0 | | `uppercase_word_after_whitespace_continuing_ascii_roman_text` | 1729 | 699 | 1030 | 0 | @@ -2909,6 +2910,79 @@ Representative `mismatch` samples: - first differing cell (zero-based): 65 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +### `uppercase_ascii_segments_joined_by_ampersand_capitalization` + +Of the 439 candidates, 415 are the actual `pending_rule_review` subcluster. The other 24 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. + +For this output-signature audit, 438 mismatches were evaluable and 220 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. + +Localized first-difference transitions: + +- `U+2817 ⠗ -> U+2820 ⠠`: 99 +- `U+2820 ⠠ -> U+281B ⠛`: 35 +- `U+280D ⠍ -> U+2820 ⠠`: 18 +- `U+280E ⠎ -> U+2820 ⠠`: 18 +- `U+280B ⠋ -> U+2820 ⠠`: 11 + +Mismatch primary-class distribution: + +- `corpus_suspect`: 10 +- `pending_rule_review`: 415 +- `unsupported_character_review`: 13 + +Representative `exact` samples: + +- `sentence_03.json` #2759: 인턴으로 채용된 외국인 유학생들은 오는 7월 20일부터 8월 17일까지 약 한달 동안 R&D(연구개발)·마케팅·생산기술·Staff(경영기획/재무, 구매, HR) 등 다양한 직군에서 실무 경험을 쌓을 수 있다. + - expected: `⠟⠓⠾⠪⠐⠥⠀⠰⠗⠬⠶⠊⠽⠒⠀⠽⠈⠍⠁⠟⠀⠩⠚⠁` + - actual: `⠟⠓⠾⠪⠐⠥⠀⠰⠗⠬⠶⠊⠽⠒⠀⠽⠈⠍⠁⠟⠀⠩⠚⠁` + - current primary/reason: `exact` / `exact` + +Representative `localized_mismatch` samples: + +- `sentence_01.json` #858: 이에 따라 SK하이닉스가 설비투자(CAPEX) 규모를 올해 50%이상 감축하지만, S&P는 SK하이닉스가 투자 축소만으로 한계가 있다고 지적했다. + - expected: `⠨⠕⠑⠒⠐⠀⠴⠠⠎⠈⠯⠠⠏⠲⠉⠵⠀⠴⠠⠠⠎⠅⠲⠚` + - actual: `⠨⠕⠑⠒⠐⠀⠴⠠⠠⠎⠈⠯⠏⠲⠉⠵⠀⠴⠠⠠⠎⠅⠲⠚` + - first differing cell (zero-based): 80 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_02.json` #226: 대한민국 드론·UAM 박람회는 UAM(도심항공교통) 등 차세대 국토교통 R&D와 주요 지자체의 관련 사업을 살펴볼 수 있는 의미 있는 행사로 국토교통부와 인천광역시가 공동 주최한다. + - expected: `⠥⠈⠬⠓⠿⠀⠴⠠⠗⠈⠯⠠⠙⠲⠧⠀⠨⠍⠬⠀⠨⠕⠨⠰` + - actual: `⠥⠈⠬⠓⠿⠀⠴⠠⠠⠗⠈⠯⠙⠲⠧⠀⠨⠍⠬⠀⠨⠕⠨⠰` + - first differing cell (zero-based): 81 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_03.json` #370: 26일 GS칼텍스는 서울 강남구 GS타워에서 이승훈 GS칼텍스 S&T본부장, 박진기 HMM 총괄부사장 등이 참석한 가운데 친환경 바이오선박유 사업 업무협약(MOU)을 체결했다고 밝혔다. + - expected: `⠓⠝⠁⠠⠪⠀⠴⠠⠎⠈⠯⠠⠞⠲⠘⠷⠘⠍⠨⠶⠐⠀⠘⠁` + - actual: `⠓⠝⠁⠠⠪⠀⠴⠠⠠⠎⠈⠯⠞⠲⠘⠷⠘⠍⠨⠶⠐⠀⠘⠁` + - first differing cell (zero-based): 69 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_04.json` #263: 김 부위원장은 투자은행(IB)의 기업 신용 공여, 합병 제도 등 기업의 M&A와 관련한 다른 제도의 불합리한 규제도 정비하고 기업구조혁신펀드도 추가로 조성하겠다고 밝혔다. + - expected: `⠈⠕⠎⠃⠺⠀⠴⠠⠍⠈⠯⠠⠁⠲⠧⠀⠈⠧⠒⠐⠡⠚⠒⠀` + - actual: `⠈⠕⠎⠃⠺⠀⠴⠠⠠⠍⠈⠯⠁⠲⠧⠀⠈⠧⠒⠐⠡⠚⠒⠀` + - first differing cell (zero-based): 70 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` + +Representative `mismatch` samples: + +- `sentence_01.json` #858: 이에 따라 SK하이닉스가 설비투자(CAPEX) 규모를 올해 50%이상 감축하지만, S&P는 SK하이닉스가 투자 축소만으로 한계가 있다고 지적했다. + - expected: `⠨⠕⠑⠒⠐⠀⠴⠠⠎⠈⠯⠠⠏⠲⠉⠵⠀⠴⠠⠠⠎⠅⠲⠚` + - actual: `⠨⠕⠑⠒⠐⠀⠴⠠⠠⠎⠈⠯⠏⠲⠉⠵⠀⠴⠠⠠⠎⠅⠲⠚` + - first differing cell (zero-based): 80 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_02.json` #146: KT&G(사장 백복인)가 산업정책연구원(IPS)이 주관하고 윤경 ESG 포럼이 주최하는 ‘윤경 ESG 포럼 CEO 서약식’에 참여해 윤리경영 의지를 다졌다. + - expected: `⠴⠠⠠⠅⠞⠈⠯⠠⠛⠦⠄⠇⠨⠶⠀⠘⠗⠁⠘⠭⠟⠠⠴⠫` + - actual: `⠴⠠⠠⠅⠞⠈⠯⠛⠦⠄⠇⠨⠶⠀⠘⠗⠁⠘⠭⠟⠠⠴⠫⠀` + - first differing cell (zero-based): 7 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_03.json` #370: 26일 GS칼텍스는 서울 강남구 GS타워에서 이승훈 GS칼텍스 S&T본부장, 박진기 HMM 총괄부사장 등이 참석한 가운데 친환경 바이오선박유 사업 업무협약(MOU)을 체결했다고 밝혔다. + - expected: `⠓⠝⠁⠠⠪⠀⠴⠠⠎⠈⠯⠠⠞⠲⠘⠷⠘⠍⠨⠶⠐⠀⠘⠁` + - actual: `⠓⠝⠁⠠⠪⠀⠴⠠⠠⠎⠈⠯⠞⠲⠘⠷⠘⠍⠨⠶⠐⠀⠘⠁` + - first differing cell (zero-based): 69 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_04.json` #19: 이날 행사에는 구 대표 외에 권봉석 (주)LG 최고운영책임자(COO), 박일평 LG사이언스파크대표(사장)를 비롯해 각 계열사 최고기술책임자(CTO), 최고데이터책임자(CDO), 최고인사책임자(CHO) 등이 참석해 국내 이공계 R&D 인재 400여명과 만났다. + - expected: `⠁⠕⠢⠨⠦⠄⠴⠠⠠⠉⠕⠕⠠⠴⠐⠀⠘⠁⠕⠂⠙⠻⠀⠴` + - actual: `⠁⠕⠢⠨⠦⠄⠴⠠⠉⠠⠕⠠⠕⠠⠴⠐⠀⠘⠁⠕⠂⠙⠻⠀` + - first differing cell (zero-based): 60 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` + ### `uppercase_roman_headword_closed_multiword_parenthetical` Of the 175 candidates, 147 are the actual `pending_rule_review` subcluster. The other 28 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. @@ -3231,6 +3305,12 @@ The cohort contains 816 candidates, 320 exact controls, and 496 mismatches. Exis UEB 10.12.1 (2024 UEB PDF pp.191-192, printed pp.163-164) says not to use a contraction when letters within an abbreviation or acronym are known to be pronounced separately, but to use the contraction in case of doubt. Rule 10.12.2 requires contractions in other abbreviations and acronyms. Consequently an uppercase `ST` surface alone cannot determine whether `s` + `t` or the `st` groupsign is required. The current cohort contains 1479 candidates, 686 exact controls, and 793 mismatches; primary classes remain 740 `pending_rule_review`, 35 `corpus_suspect`, 13 `comparison_method`, and 5 `unsupported_character_review`. Of 793 evaluable mismatches, 443 are localized to the detected current-engine run: 435 `U+280E ⠎ -> U+280C ⠌` and 1 `U+280C ⠌ -> U+280E ⠎`. The target's raw-to-residual count is 448 -> 13, and the reverse is 2 -> 1. Exact controls such as `STAYG`, `KAIST`, `DGIST`, and `POSTECH` coexist with localized mismatches such as `WSTS`, `HUST`, `OST`, and `USTR`; representative shard/index samples are preserved in the cluster table. This lexical/pronunciation distinction cannot be inferred from the input-only spelling, so no engine change or primary reclassification is made. +### Uppercase segments joined by ampersand: capitalization extent + +UEB 8.4.2 (2024 UEB PDF p.118, printed p.90) terminates capitals word mode at a nonalphabetic symbol. UEB 3.1.1 and the capitalization examples (PDF pp.51 and 120, printed pp.23 and 92) consequently print `AT&T` as `⠠⠠⠁⠞⠈⠯⠠⠞` and `B&B` as `⠠⠃⠈⠯⠠⠃`: Roman mode remains continuous, but capitalization restarts for each ASCII-letter segment. The detector accepts only complete uppercase ASCII segments joined directly by `&`, with the same non-alphanumeric outer boundaries as the existing Roman-ampersand rule, and requires the run to begin its whitespace-delimited token. Korean-attached and punctuation-prefixed occurrences stay outside the change scope. + +The current baseline contains 439 candidates, 1 exact controls, and 438 mismatches. Existing mismatch primaries remain 415 `pending_rule_review`, 10 `corpus_suspect`, 0 `comparison_method`, and 13 `unsupported_character_review`. Of 438 evaluable mismatches, 220 have their first difference inside the independently reproduced Korean-context output signature. The cohort table above retains the transition distribution and shard/index samples. Because capitalization extent is fixed by the official symbol examples and does not require pronunciation or corpus semantics, this is a high-confidence implementation candidate; the diagnostic itself does not change any primary class. + ### Attached Roman segments joined by ampersand UEB 3.1.1 (2024 UEB PDF p.51, printed p.23) directly prints `AT&T` and `B&B` with the attached ampersand `⠈⠯` and no mode boundary around it. Korean rule 71 (2024 Korean-rules PDF p.51, printed p.45) assigns the same `⠈⠯` cells, while rule 29 places Roman entry before a Roman section and termination after its last item. Before the engine change, the Korean-context path instead exited before `&`, wrapped the information symbol as a separate Roman section, and re-entered for the following letters. diff --git a/libs/braillify/examples/nikl_corpus_analyze.rs b/libs/braillify/examples/nikl_corpus_analyze.rs index a1b5b351..51cc2ee8 100644 --- a/libs/braillify/examples/nikl_corpus_analyze.rs +++ b/libs/braillify/examples/nikl_corpus_analyze.rs @@ -594,6 +594,8 @@ const ROMAN_RUN_AFTER_CLOSED_ROMAN_ENCLOSURE: &str = "roman_run_after_whitespace_following_closed_roman_enclosure"; const ATTACHED_ASCII_ROMAN_SEGMENTS_JOINED_BY_AMPERSAND: &str = "attached_ascii_roman_segments_joined_by_ampersand"; +const UPPERCASE_ASCII_SEGMENTS_JOINED_BY_AMPERSAND: &str = + "uppercase_ascii_segments_joined_by_ampersand_capitalization"; const AMPERSAND_BEFORE_ATTACHED_ASCII_ROMAN_SEGMENT: &str = "ampersand_before_attached_ascii_roman_segment"; const SPACED_COMMA_BETWEEN_ASCII_DIGIT_RUNS: &str = "spaced_comma_between_ascii_digit_runs"; @@ -1119,6 +1121,30 @@ fn attached_ascii_roman_ampersand_spans(input: &str) -> Vec { spans } +/// Narrows the attached-Roman ampersand cohort to all-capital ASCII segments +/// that begin their whitespace-delimited token. The current token-level bug +/// can pre-emit one capitals-word indicator only in that position; a run +/// attached after Korean or opening punctuation already takes the correct +/// per-segment character path and is an out-of-scope control. +/// UEB 8.4.2 terminates capitals word mode at the nonalphabetic ampersand, and +/// the official UEB 3.1.1/8.4 examples `AT&T` and `B&B` therefore restart +/// capitalization for the following segment. This detector uses only the +/// printed input shape and does not inspect a corpus reference. +fn uppercase_ascii_ampersand_spans(input: &str) -> Vec { + attached_ascii_roman_ampersand_spans(input) + .into_iter() + .filter(|span| { + input[..span.start_byte] + .chars() + .next_back() + .is_none_or(char::is_whitespace) + && input[span.start_byte..span.end_byte] + .split('&') + .all(|segment| segment.bytes().all(|byte| byte.is_ascii_uppercase())) + }) + .collect() +} + /// Finds an ampersand immediately followed by a complete ASCII-letter segment /// when no ASCII alphanumeric precedes it. This is the one-sided shape of the /// UEB §3.1.1 `&c` example, kept separate from the already implemented `A&B` @@ -2123,6 +2149,14 @@ fn first_difference_in_attached_roman_ampersand(item: &EncodedCase) -> bool { .any(|range| range.contains(&first_difference)) } +fn first_difference_in_uppercase_ascii_ampersand(item: &EncodedCase) -> bool { + first_difference_in_korean_context_signature_spans( + item, + &uppercase_ascii_ampersand_spans(&item.located.case.input), + 0, + ) +} + /// Locates the Rule-71/29 boundary around an ampersand whose right-hand ASCII /// Roman segment is attached. The real input prefix before each occurrence /// anchors the current complete entry signature; the independently encoded @@ -2481,6 +2515,7 @@ fn first_difference_claimed_by_prior_localized_cohort(item: &EncodedCase) -> boo || first_difference_in_inline_parenthesized_operator(item) || first_difference_in_tight_triangle(item) || first_difference_at_roman_middle_dot_boundary(item) + || first_difference_in_uppercase_ascii_ampersand(item) || first_difference_in_signature_spans( item, &single_capital_parenthesized_digit_spans(&item.located.case.input), @@ -3540,6 +3575,10 @@ fn analyze( ROMAN_RUN_AFTER_CLOSED_ROMAN_ENCLOSURE.to_string(), PendingRuleReviewClusterStats::default(), ), + ( + UPPERCASE_ASCII_SEGMENTS_JOINED_BY_AMPERSAND.to_string(), + PendingRuleReviewClusterStats::default(), + ), ( ATTACHED_ASCII_ROMAN_SEGMENTS_JOINED_BY_AMPERSAND.to_string(), PendingRuleReviewClusterStats::default(), @@ -3787,6 +3826,12 @@ fn analyze( ), true, ), + ( + UPPERCASE_ASCII_SEGMENTS_JOINED_BY_AMPERSAND, + !uppercase_ascii_ampersand_spans(&item.located.case.input).is_empty(), + Some(first_difference_in_uppercase_ascii_ampersand(item)), + true, + ), ( ATTACHED_ASCII_ROMAN_SEGMENTS_JOINED_BY_AMPERSAND, !attached_ascii_roman_ampersand_spans(&item.located.case.input).is_empty(), @@ -4513,6 +4558,12 @@ fn markdown(report: &AnalysisReport) -> String { signature as localized. The `allcaps_roman_run_containing_st` gate applies the same \ output-position requirement to adjacent `ST`; it is kept separate because UEB 10.12 \ makes contraction use depend on how an abbreviation or acronym is pronounced. The \ + `uppercase_ascii_segments_joined_by_ampersand_capitalization` gate narrows attached \ + ampersand runs to uppercase-only segments and locates the complete current Korean-context \ + output. The run must begin its whitespace-delimited token, matching the current \ + token-level capitals-word pre-emission scope; Korean-attached and punctuation-prefixed \ + occurrences remain controls. UEB 8.4.2 says a nonalphabetic symbol terminates capitals word mode, while the \ + official `AT&T` and `B&B` examples restart capitalization after `&`. The broader \ `attached_ascii_roman_segments_joined_by_ampersand` gate requires non-empty ASCII-letter \ segments joined directly by `&`, excludes spaced/Korean/alphanumeric continuations, and \ localizes only the current output cell immediately before the ampersand through an \ @@ -5419,6 +5470,48 @@ fn markdown(report: &AnalysisReport) -> String { residual_transition(reverse), )); } + if let Some(stats) = report + .pending_rule_review_clusters + .get(UPPERCASE_ASCII_SEGMENTS_JOINED_BY_AMPERSAND) + { + let primary_count = |name: &str| { + stats + .mismatch_primary_classes + .get(name) + .copied() + .unwrap_or(0) + }; + text.push_str(&format!( + "\n### Uppercase segments joined by ampersand: capitalization extent\n\n\ + UEB 8.4.2 (2024 UEB PDF p.118, printed p.90) terminates capitals word \ + mode at a nonalphabetic symbol. UEB 3.1.1 and the capitalization examples \ + (PDF pp.51 and 120, printed pp.23 and 92) consequently print `AT&T` as \ + `⠠⠠⠁⠞⠈⠯⠠⠞` and `B&B` as `⠠⠃⠈⠯⠠⠃`: Roman mode remains \ + continuous, but capitalization restarts for each ASCII-letter segment. The detector \ + accepts only complete uppercase ASCII segments joined directly by `&`, with the same \ + non-alphanumeric outer boundaries as the existing Roman-ampersand rule, and requires \ + the run to begin its whitespace-delimited token. Korean-attached and \ + punctuation-prefixed occurrences stay outside the change scope.\n\n\ + The current baseline contains {} candidates, {} exact controls, and {} mismatches. \ + Existing mismatch primaries remain {} `pending_rule_review`, {} `corpus_suspect`, {} \ + `comparison_method`, and {} `unsupported_character_review`. Of {} evaluable \ + mismatches, {} have their first difference inside the independently reproduced \ + Korean-context output signature. The cohort table above retains the transition \ + distribution and shard/index samples. Because capitalization extent is fixed by the \ + official symbol examples and does not require pronunciation or corpus semantics, this \ + is a high-confidence implementation candidate; the diagnostic itself does not change \ + any primary class.\n", + stats.candidates, + stats.exact, + stats.mismatch, + primary_count("pending_rule_review"), + primary_count("corpus_suspect"), + primary_count("comparison_method"), + primary_count("unsupported_character_review"), + stats.output_signature_mismatches_evaluated, + stats.first_difference_in_output_signature, + )); + } if let Some(stats) = report .pending_rule_review_clusters .get(ATTACHED_ASCII_ROMAN_SEGMENTS_JOINED_BY_AMPERSAND) @@ -7338,6 +7431,49 @@ mod tests { assert_eq!(braillify::encode_to_unicode(input).as_deref(), Ok(expected)); } + #[rstest::rstest] + #[case::official_at_and_t("AT&T", vec!["AT&T"])] + #[case::official_b_and_b("B&B", vec!["B&B"])] + #[case::multiple_uppercase_segments("M&A&R", vec!["M&A&R"])] + #[case::lowercase_segment_excluded("R&d", vec![])] + #[case::spaced_excluded("R & D", vec![])] + #[case::digit_continuation_excluded("R&D3", vec![])] + #[case::korean_attached_excluded("가(R&D)", vec![])] + #[case::whitespace_token_start("가 R&D", vec!["R&D"])] + fn detects_complete_uppercase_ampersand_segments( + #[case] input: &str, + #[case] expected: Vec<&str>, + ) { + let actual = uppercase_ascii_ampersand_spans(input) + .into_iter() + .map(|span| &input[span.start_byte..span.end_byte]) + .collect::>(); + assert_eq!(actual, expected); + } + + #[test] + fn localizes_uppercase_ampersand_in_korean_context() { + let input = "가 R&D 나"; + let actual = braillify::encode_to_unicode(input).expect("ampersand probe must encode"); + let ranges = korean_context_signature_ranges( + input, + &actual, + &uppercase_ascii_ampersand_spans(input), + 0, + ); + let signature = korean_context_signature("R&D").expect("signature must encode"); + + assert_eq!(ranges.len(), 1); + assert_eq!( + actual + .chars() + .skip(ranges[0].start) + .take(ranges[0].len()) + .collect::(), + signature + ); + } + #[rstest::rstest] #[case::whole_shortform("가(WD) 나", vec!["WD"])] #[case::longer_prefixes("PDS LLM GDP", vec!["PDS", "LLM", "GDP"])] From c4d572452466893b8b65638bfb260c6cc230f25c Mon Sep 17 00:00:00 2001 From: devfive Date: Tue, 1 Sep 2026 18:10:44 +0900 Subject: [PATCH 55/77] Respect capitals boundaries at nonletters --- docs/corpus-analysis/NIKL_2025_V1.md | 1030 +++++++++-------- .../braillify/examples/nikl_corpus_analyze.rs | 139 ++- libs/braillify/src/rules/korean/rule_28.rs | 10 +- libs/braillify/src/rules/korean/rule_71.rs | 14 + libs/braillify/src/rules/token.rs | 33 +- 5 files changed, 736 insertions(+), 490 deletions(-) diff --git a/docs/corpus-analysis/NIKL_2025_V1.md b/docs/corpus-analysis/NIKL_2025_V1.md index 4c157324..df131840 100644 --- a/docs/corpus-analysis/NIKL_2025_V1.md +++ b/docs/corpus-analysis/NIKL_2025_V1.md @@ -7,9 +7,9 @@ | Metric | Count | |---|---:| | Total | 83528 | -| Exact | 68439 | -| Mismatch | 15089 | -| Exact accuracy | 81.94% | +| Exact | 69291 | +| Mismatch | 14237 | +| Exact accuracy | 82.96% | | Duplicate records | 0 | | Inputs with conflicting references | 0 | @@ -19,21 +19,21 @@ Primary classes are evidence gates, not permissions to change the engine. `imple | Primary class | Count | |---|---:| -| `comparison_method` | 297 | -| `corpus_suspect` | 1108 | -| `exact` | 68439 | -| `pending_rule_review` | 13510 | -| `unsupported_character_review` | 174 | +| `comparison_method` | 312 | +| `corpus_suspect` | 1111 | +| `exact` | 69291 | +| `pending_rule_review` | 12655 | +| `unsupported_character_review` | 159 | | Reproducible reason | Count | |---|---:| -| `exact` | 68439 | -| `foreign_text_rule_review` | 13358 | -| `nfkc_input_equivalent` | 297 | +| `exact` | 69291 | +| `foreign_text_rule_review` | 12503 | +| `nfkc_input_equivalent` | 312 | | `number_rule_review` | 144 | | `punctuation_rule_review` | 8 | -| `rule34_roman_indicator_before_opening_parenthesis` | 1108 | -| `unsupported_character_review` | 174 | +| `rule34_roman_indicator_before_opening_parenthesis` | 1111 | +| `unsupported_character_review` | 159 | ## Pending first-difference cell transitions @@ -41,26 +41,26 @@ This ranking is a diagnostic selector, not an implementation rule. It counts onl | Rank | Expected → actual first cell | Cases | |---:|---|---:| -| 1 | `U+2834 ⠴ -> U+2800 ⠀` | 2163 | -| 2 | `U+2815 ⠕ -> U+2833 ⠳` | 1336 | -| 3 | `U+2830 ⠰ -> U+2820 ⠠` | 862 | -| 4 | `U+2810 ⠐ -> U+2832 ⠲` | 669 | -| 5 | `U+2826 ⠦ -> U+2834 ⠴` | 469 | -| 6 | `U+280E ⠎ -> U+280C ⠌` | 448 | -| 7 | `U+2826 ⠦ -> U+2800 ⠀` | 440 | -| 8 | `U+2801 ⠁ -> U+281C ⠜` | 405 | -| 9 | `U+2800 ⠀ -> U+2807 ⠇` | 364 | -| 10 | `U+283C ⠼ -> U+2800 ⠀` | 358 | -| 11 | `U+2834 ⠴ -> U+2830 ⠰` | 347 | -| 12 | `U+2811 ⠑ -> U+282B ⠫` | 329 | -| 13 | `U+2820 ⠠ -> U+2830 ⠰` | 238 | -| 14 | `U+2820 ⠠ -> U+2809 ⠉` | 169 | -| 15 | `U+2817 ⠗ -> U+2820 ⠠` | 165 | -| 16 | `U+2826 ⠦ -> U+2810 ⠐` | 164 | -| 17 | `U+2800 ⠀ -> U+2832 ⠲` | 154 | -| 18 | `U+281B ⠛ -> U+2823 ⠣` | 144 | -| 19 | `U+2810 ⠐ -> U+2800 ⠀` | 130 | -| 20 | `U+2820 ⠠ -> U+281E ⠞` | 109 | +| 1 | `U+2834 ⠴ -> U+2800 ⠀` | 2173 | +| 2 | `U+2815 ⠕ -> U+2833 ⠳` | 1360 | +| 3 | `U+2830 ⠰ -> U+2820 ⠠` | 868 | +| 4 | `U+2810 ⠐ -> U+2832 ⠲` | 720 | +| 5 | `U+2826 ⠦ -> U+2834 ⠴` | 470 | +| 6 | `U+280E ⠎ -> U+280C ⠌` | 454 | +| 7 | `U+2826 ⠦ -> U+2800 ⠀` | 445 | +| 8 | `U+2801 ⠁ -> U+281C ⠜` | 408 | +| 9 | `U+2800 ⠀ -> U+2807 ⠇` | 365 | +| 10 | `U+283C ⠼ -> U+2800 ⠀` | 365 | +| 11 | `U+2834 ⠴ -> U+2830 ⠰` | 357 | +| 12 | `U+2811 ⠑ -> U+282B ⠫` | 330 | +| 13 | `U+2820 ⠠ -> U+2830 ⠰` | 245 | +| 14 | `U+2826 ⠦ -> U+2810 ⠐` | 174 | +| 15 | `U+2800 ⠀ -> U+2832 ⠲` | 157 | +| 16 | `U+2820 ⠠ -> U+2809 ⠉` | 151 | +| 17 | `U+281B ⠛ -> U+2823 ⠣` | 144 | +| 18 | `U+2810 ⠐ -> U+2800 ⠀` | 131 | +| 19 | `U+2811 ⠑ -> U+283B ⠻` | 101 | +| 20 | `U+2810 ⠐ -> U+2814 ⠔` | 98 | ### `U+2834 ⠴ -> U+2800 ⠀` @@ -140,7 +140,7 @@ This ranking is a diagnostic selector, not an implementation rule. It counts onl - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` - `sentence_02.json` #10: 설동호 대전교육감은 “학교 교육과정 및 여건에 맞는 인공지능(AI)교육 선도학교 운영을 통해 AI·SW교육 기반 구축과 학생들과 교원들의 미래 디지털 역량이 함양될 것이다.”라고 말했다. - expected: `⠚⠗⠀⠴⠠⠠⠁⠊⠐⠆⠴⠠⠠⠎⠺⠲⠈⠬⠩⠁⠀⠈⠕⠘` - - actual: `⠚⠗⠀⠴⠠⠠⠁⠊⠲⠐⠆⠴⠎⠺⠲⠈⠬⠩⠁⠀⠈⠕⠘⠒` + - actual: `⠚⠗⠀⠴⠠⠠⠁⠊⠲⠐⠆⠴⠠⠠⠎⠺⠲⠈⠬⠩⠁⠀⠈⠕` - first differing cell (zero-based): 93 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` - `sentence_03.json` #17: 재테크 시장에서 온·오프라인 플랫폼 ‘매경 엠플러스(M+)’가 주목받고 있다. M+는 거시경제, 주식, 부동산, 가상자산 등 재테크의 모든 것을 온라인 사이트를 통해 제공하고 있다. @@ -298,26 +298,26 @@ This ranking removes only cases whose first difference is inside an existing out | Rank | Expected → actual first cell | Residual cases | |---:|---|---:| -| 1 | `U+2810 ⠐ -> U+2832 ⠲` | 220 | -| 2 | `U+2834 ⠴ -> U+2800 ⠀` | 154 | -| 3 | `U+283C ⠼ -> U+2800 ⠀` | 153 | +| 1 | `U+2810 ⠐ -> U+2832 ⠲` | 249 | +| 2 | `U+283C ⠼ -> U+2800 ⠀` | 159 | +| 3 | `U+2834 ⠴ -> U+2800 ⠀` | 154 | | 4 | `U+281B ⠛ -> U+2823 ⠣` | 144 | -| 5 | `U+2810 ⠐ -> U+2800 ⠀` | 130 | -| 6 | `U+2826 ⠦ -> U+2810 ⠐` | 125 | +| 5 | `U+2826 ⠦ -> U+2810 ⠐` | 133 | +| 6 | `U+2810 ⠐ -> U+2800 ⠀` | 131 | | 7 | `U+2811 ⠑ -> U+283B ⠻` | 101 | | 8 | `U+2800 ⠀ -> U+2832 ⠲` | 100 | | 9 | `U+2810 ⠐ -> U+2814 ⠔` | 98 | -| 10 | `U+2820 ⠠ -> U+281E ⠞` | 96 | -| 11 | `U+2803 ⠃ -> U+2820 ⠠` | 87 | -| 12 | `U+2804 ⠄ -> U+2834 ⠴` | 87 | +| 10 | `U+2804 ⠄ -> U+2834 ⠴` | 88 | +| 11 | `U+2824 ⠤ -> U+2800 ⠀` | 80 | +| 12 | `U+2808 ⠈ -> U+2832 ⠲` | 79 | | 13 | `U+2826 ⠦ -> U+2800 ⠀` | 79 | -| 14 | `U+2824 ⠤ -> U+2800 ⠀` | 78 | -| 15 | `U+280E ⠎ -> U+2820 ⠠` | 76 | -| 16 | `U+280E ⠎ -> U+2829 ⠩` | 73 | -| 17 | `U+2817 ⠗ -> U+2820 ⠠` | 65 | -| 18 | `U+2820 ⠠ -> U+2809 ⠉` | 65 | -| 19 | `U+2834 ⠴ -> U+2820 ⠠` | 64 | -| 20 | `U+2830 ⠰ -> U+2820 ⠠` | 56 | +| 14 | `U+280E ⠎ -> U+2829 ⠩` | 73 | +| 15 | `U+2834 ⠴ -> U+2820 ⠠` | 64 | +| 16 | `U+2811 ⠑ -> U+2822 ⠢` | 58 | +| 17 | `U+2824 ⠤ -> U+2834 ⠴` | 58 | +| 18 | `U+2830 ⠰ -> U+2820 ⠠` | 57 | +| 19 | `U+2832 ⠲ -> U+2800 ⠀` | 55 | +| 20 | `U+280A ⠊ -> U+2814 ⠔` | 51 | ### Residual `U+2810 ⠐ -> U+2832 ⠲` @@ -342,29 +342,6 @@ This ranking removes only cases whose first difference is inside an existing out - first differing cell (zero-based): 6 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -### Residual `U+2834 ⠴ -> U+2800 ⠀` - -- `sentence_01.json` #947: 코나체인은 코나아이와 함께 한국은행의 디지털 화폐(CBDC)의 H/W Wallet의 개발에 참여하였고, 현재 블록체인 기반의 설문조사, 투표서비스를 제공 중이다. - - expected: `⠉⠃⠙⠉⠠⠴⠺⠀⠴⠠⠓⠸⠌⠠⠺⠀⠠⠺⠁⠇⠇⠑⠞⠲` - - actual: `⠉⠃⠙⠉⠠⠴⠺⠀⠀⠠⠓⠸⠌⠠⠺⠀⠴⠠⠺⠁⠇⠇⠑⠞` - - first differing cell (zero-based): 57 - - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_02.json` #865: ‘2023 삼성 AI/CE 챌린지’는 인공지능(AI)과 컴퓨터 공학(CE) 분야 국내 우수 인력 발굴과 차세대 반도체 연구 역량 강화를 위해 개최되고 있으며, 2021년을 시작으로 3회째를 맞이했다. - - expected: `⠃⠉⠀⠇⠢⠠⠻⠀⠴⠠⠠⠁⠊⠸⠌⠠⠠⠉⠑⠲⠀⠰⠗⠂` - - actual: `⠃⠉⠀⠇⠢⠠⠻⠀⠀⠠⠠⠁⠊⠸⠌⠠⠠⠉⠑⠀⠰⠗⠂⠐` - - first differing cell (zero-based): 13 - - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_03.json` #2455: 한편, AI+ 인증은 한국표준협회(KSA)가 국제표준화기구(ISO, International Organization for Standardization)와 국제전기기술위원회(IEC, International Electrotechnical Commission) 등 국제표준에 근거해 인공지능(AI) 기술이 적용된 제품과 소프 - - expected: `⠚⠒⠙⠡⠐⠀⠴⠠⠠⠁⠊⠐⠖⠲⠀⠟⠨⠪⠶⠵⠀⠚⠒⠈` - - actual: `⠚⠒⠙⠡⠐⠀⠀⠠⠠⠁⠊⠢⠀⠀⠟⠨⠪⠶⠵⠀⠚⠒⠈⠍` - - first differing cell (zero-based): 6 - - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_04.json` #2250: RX 시리즈의 전동화 버전도 내놨다. 하이브리드 모델인 RX350h, 플러그인하이브리드(PHEV) 모델인 RX450h+, 터보 엔진 하이브리드 모델(터보 HEV)인 RX500h F 스포츠 퍼포먼스 등 총 3가지다. - - expected: `⠀⠑⠥⠊⠝⠂⠟⠀⠴⠠⠠⠗⠭⠼⠉⠑⠚⠰⠓⠐⠀⠙⠮⠐` - - actual: `⠀⠑⠥⠊⠝⠂⠟⠀⠀⠠⠠⠗⠭⠼⠉⠑⠚⠓⠐⠀⠙⠮⠐⠎` - - first differing cell (zero-based): 54 - - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` - ### Residual `U+283C ⠼ -> U+2800 ⠀` - `sentence_01.json` #633: 미국 항공우주청(NASA)은 26일(현지 시간) 이 같은 내용을 발표하면서 “‘2023 BU’라는 소행성이 남미의 남단을 지나갈 것”이라며 “2023 BU는 지름이 약 3.5~8.5m 크기”라고 설명했다. @@ -388,6 +365,29 @@ This ranking removes only cases whose first difference is inside an existing out - first differing cell (zero-based): 124 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +### Residual `U+2834 ⠴ -> U+2800 ⠀` + +- `sentence_01.json` #947: 코나체인은 코나아이와 함께 한국은행의 디지털 화폐(CBDC)의 H/W Wallet의 개발에 참여하였고, 현재 블록체인 기반의 설문조사, 투표서비스를 제공 중이다. + - expected: `⠉⠃⠙⠉⠠⠴⠺⠀⠴⠠⠓⠸⠌⠠⠺⠀⠠⠺⠁⠇⠇⠑⠞⠲` + - actual: `⠉⠃⠙⠉⠠⠴⠺⠀⠀⠠⠓⠸⠌⠠⠺⠀⠴⠠⠺⠁⠇⠇⠑⠞` + - first differing cell (zero-based): 57 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_02.json` #865: ‘2023 삼성 AI/CE 챌린지’는 인공지능(AI)과 컴퓨터 공학(CE) 분야 국내 우수 인력 발굴과 차세대 반도체 연구 역량 강화를 위해 개최되고 있으며, 2021년을 시작으로 3회째를 맞이했다. + - expected: `⠃⠉⠀⠇⠢⠠⠻⠀⠴⠠⠠⠁⠊⠸⠌⠠⠠⠉⠑⠲⠀⠰⠗⠂` + - actual: `⠃⠉⠀⠇⠢⠠⠻⠀⠀⠠⠠⠁⠊⠸⠌⠠⠠⠉⠑⠀⠰⠗⠂⠐` + - first differing cell (zero-based): 13 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_03.json` #2455: 한편, AI+ 인증은 한국표준협회(KSA)가 국제표준화기구(ISO, International Organization for Standardization)와 국제전기기술위원회(IEC, International Electrotechnical Commission) 등 국제표준에 근거해 인공지능(AI) 기술이 적용된 제품과 소프 + - expected: `⠚⠒⠙⠡⠐⠀⠴⠠⠠⠁⠊⠐⠖⠲⠀⠟⠨⠪⠶⠵⠀⠚⠒⠈` + - actual: `⠚⠒⠙⠡⠐⠀⠀⠠⠠⠁⠊⠢⠀⠀⠟⠨⠪⠶⠵⠀⠚⠒⠈⠍` + - first differing cell (zero-based): 6 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_04.json` #2250: RX 시리즈의 전동화 버전도 내놨다. 하이브리드 모델인 RX350h, 플러그인하이브리드(PHEV) 모델인 RX450h+, 터보 엔진 하이브리드 모델(터보 HEV)인 RX500h F 스포츠 퍼포먼스 등 총 3가지다. + - expected: `⠀⠑⠥⠊⠝⠂⠟⠀⠴⠠⠠⠗⠭⠼⠉⠑⠚⠰⠓⠐⠀⠙⠮⠐` + - actual: `⠀⠑⠥⠊⠝⠂⠟⠀⠀⠠⠠⠗⠭⠼⠉⠑⠚⠓⠐⠀⠙⠮⠐⠎` + - first differing cell (zero-based): 54 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` + ### Residual `U+281B ⠛ -> U+2823 ⠣` - `sentence_01.json` #1753: 지난해 말 경기주택도시공사(GH)에서 퇴직한 전씨는 ‘성남FC 불법 후원금 의혹’과 관련해 검찰 조사를 받은 바 있으며 ‘GH 합숙소 의혹’에도 연루된 것으로 알려졌다. @@ -411,29 +411,6 @@ This ranking removes only cases whose first difference is inside an existing out - first differing cell (zero-based): 21 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -### Residual `U+2810 ⠐ -> U+2800 ⠀` - -- `sentence_01.json` #761: 샌즈랩은 국내에서 정보보호 분야로 최초이자 유일하게 2021년, 2022년 연속으로 국가신기술(NET) 인증을 획득했다. 또한 신기술로 최근 미국 직접 특허 출원·PCT(Patent Cooperation Treaty)특허 출원을 완료했다. - - expected: `⠁⠚⠎⠀⠰⠯⠏⠒⠐⠆⠴⠠⠠⠏⠉⠞⠐⠣⠠⠏⠁⠞⠢⠞` - - actual: `⠁⠚⠎⠀⠰⠯⠏⠒⠀⠀⠐⠠⠠⠏⠉⠞⠦⠠⠏⠁⠞⠑⠝⠞` - - first differing cell (zero-based): 156 - - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_02.json` #64: 시에 따르면 미래형 환승센터(MaaS Station) : 철도, 버스를 연계한 기존 환승센터에 도심항공교통(UAM), 자율주행차, 전기·수소차, 개인형 이동수단(PM) 등 미래 모빌리티까지 연계한 환승센터라고 설명했다. - - expected: `⠀⠠⠌⠁⠰⠝⠠⠴⠐⠂⠀⠰⠞⠊⠥⠐⠀⠘⠎⠠⠪⠐⠮⠀` - - actual: `⠀⠠⠌⠁⠰⠝⠠⠴⠀⠐⠂⠀⠰⠞⠊⠥⠐⠀⠘⠎⠠⠪⠐⠮` - - first differing cell (zero-based): 46 - - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_03.json` #1714: 드림캐쳐는 최근 여덟 번째 미니앨범 ‘아포칼립스 : 프롬 어스(Apocalypse : From us)’를 발매하고 타이틀 ‘본보야지(BONVOYAGE)’로 활발한 활동을 예고했다. - - expected: `⠥⠋⠂⠐⠕⠃⠠⠪⠐⠂⠀⠙⠪⠐⠥⠢⠀⠎⠠⠪⠦⠄⠴⠠` - - actual: `⠥⠋⠂⠐⠕⠃⠠⠪⠀⠐⠂⠀⠙⠪⠐⠥⠢⠀⠎⠠⠪⠦⠄⠴` - - first differing cell (zero-based): 50 - - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_04.json` #1616: 기독교복음선교회(일명 JMS) 총재 정명석씨(78)의 성폭행 사건 공범인 ‘JMS 2인자’ 정조은씨(본명 김지선·44) 등 조력자들의 변호인들이 잇따라 사임하고 있는 것으로 확인됐다. - - expected: `⠀⠈⠕⠢⠨⠕⠠⠾⠐⠆⠼⠙⠙⠠⠴⠀⠊⠪⠶⠀⠨⠥⠐⠱` - - actual: `⠀⠈⠕⠢⠨⠕⠠⠾⠀⠀⠐⠼⠙⠙⠴⠀⠊⠪⠶⠀⠨⠥⠐⠱` - - first differing cell (zero-based): 112 - - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` - ### Residual `U+2826 ⠦ -> U+2810 ⠐` - `sentence_01.json` #1318: 비제조업 업황 BSI(73)는 2p 상승했다. 비제조업 업황 BSI가 전월 대비 상승한 것은 지난 2022년 8월 이후 6개월 만에 처음이다. @@ -457,6 +434,29 @@ This ranking removes only cases whose first difference is inside an existing out - first differing cell (zero-based): 98 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +### Residual `U+2810 ⠐ -> U+2800 ⠀` + +- `sentence_01.json` #761: 샌즈랩은 국내에서 정보보호 분야로 최초이자 유일하게 2021년, 2022년 연속으로 국가신기술(NET) 인증을 획득했다. 또한 신기술로 최근 미국 직접 특허 출원·PCT(Patent Cooperation Treaty)특허 출원을 완료했다. + - expected: `⠁⠚⠎⠀⠰⠯⠏⠒⠐⠆⠴⠠⠠⠏⠉⠞⠐⠣⠠⠏⠁⠞⠢⠞` + - actual: `⠁⠚⠎⠀⠰⠯⠏⠒⠀⠀⠐⠠⠠⠏⠉⠞⠦⠠⠏⠁⠞⠑⠝⠞` + - first differing cell (zero-based): 156 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_02.json` #64: 시에 따르면 미래형 환승센터(MaaS Station) : 철도, 버스를 연계한 기존 환승센터에 도심항공교통(UAM), 자율주행차, 전기·수소차, 개인형 이동수단(PM) 등 미래 모빌리티까지 연계한 환승센터라고 설명했다. + - expected: `⠀⠠⠌⠁⠰⠝⠠⠴⠐⠂⠀⠰⠞⠊⠥⠐⠀⠘⠎⠠⠪⠐⠮⠀` + - actual: `⠀⠠⠌⠁⠰⠝⠠⠴⠀⠐⠂⠀⠰⠞⠊⠥⠐⠀⠘⠎⠠⠪⠐⠮` + - first differing cell (zero-based): 46 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_03.json` #1714: 드림캐쳐는 최근 여덟 번째 미니앨범 ‘아포칼립스 : 프롬 어스(Apocalypse : From us)’를 발매하고 타이틀 ‘본보야지(BONVOYAGE)’로 활발한 활동을 예고했다. + - expected: `⠥⠋⠂⠐⠕⠃⠠⠪⠐⠂⠀⠙⠪⠐⠥⠢⠀⠎⠠⠪⠦⠄⠴⠠` + - actual: `⠥⠋⠂⠐⠕⠃⠠⠪⠀⠐⠂⠀⠙⠪⠐⠥⠢⠀⠎⠠⠪⠦⠄⠴` + - first differing cell (zero-based): 50 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_04.json` #1616: 기독교복음선교회(일명 JMS) 총재 정명석씨(78)의 성폭행 사건 공범인 ‘JMS 2인자’ 정조은씨(본명 김지선·44) 등 조력자들의 변호인들이 잇따라 사임하고 있는 것으로 확인됐다. + - expected: `⠀⠈⠕⠢⠨⠕⠠⠾⠐⠆⠼⠙⠙⠠⠴⠀⠊⠪⠶⠀⠨⠥⠐⠱` + - actual: `⠀⠈⠕⠢⠨⠕⠠⠾⠀⠀⠐⠼⠙⠙⠴⠀⠊⠪⠶⠀⠨⠥⠐⠱` + - first differing cell (zero-based): 112 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` + ### Residual `U+2811 ⠑ -> U+283B ⠻` - `sentence_01.json` #1903: 김현용 현대차증권 연구원은 “전사적자원관리(ERP), 생산관리프로그램(MES) 등 기존의 기업향 솔루션에 이번 인수로 구매공급망관리(SRM)이 더해지며 사업 포트폴리오가 한층 단단해진 것으로 판단된다”고 설명했다. @@ -526,91 +526,92 @@ This ranking removes only cases whose first difference is inside an existing out - first differing cell (zero-based): 62 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -### Residual `U+2820 ⠠ -> U+281E ⠞` +### Residual `U+2804 ⠄ -> U+2834 ⠴` -- `sentence_01.json` #2166: 이에 따라 경찰은 CC(폐쇄회로)TV를 분석하고 달아난 카자흐스탄인 2명을 추적 중이다. 이들이 뛰어넘은 담장이 CCTV 사각지대여서 추적에 애를 먹고 있다. - - expected: `⠗⠚⠽⠐⠥⠠⠴⠴⠠⠠⠞⠧⠲⠐⠮⠀⠘⠛⠠⠹⠚⠈⠥⠀` - - actual: `⠗⠚⠽⠐⠥⠠⠴⠴⠞⠧⠲⠐⠮⠀⠘⠛⠠⠹⠚⠈⠥⠀⠊⠂` - - first differing cell (zero-based): 34 +- `sentence_01.json` #67: LG전자는 오는 5일부터 8일까지 ‘CES 2023’이 열리는 미국 라스베이거스 컨벤션센터(LVCC)에 ‘라이프스굿(Life's Good)’을 소개하는 광고판을 설치한다고 3일 밝혔다. + - expected: `⠦⠄⠴⠠⠇⠊⠋⠑⠄⠎⠀⠠⠛⠙⠠⠴⠴⠄⠮⠀⠠⠥⠈⠗` + - actual: `⠦⠄⠴⠠⠇⠊⠋⠑⠴⠄⠰⠎⠀⠠⠛⠕⠕⠙⠠⠴⠴⠄⠮⠀` + - first differing cell (zero-based): 121 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_02.json` #2253: 경찰은 금은방 CC(폐쇄회로)TV 등을 통해 이들의 얼굴을 확인하고 탐문 수사를 벌여 범행 4시간 만에 집에 있는 A군을 붙잡았다. - - expected: `⠗⠚⠽⠐⠥⠠⠴⠴⠠⠠⠞⠧⠲⠀⠊⠪⠶⠮⠀⠓⠿⠚⠗⠀` - - actual: `⠗⠚⠽⠐⠥⠠⠴⠴⠞⠧⠲⠀⠊⠪⠶⠮⠀⠓⠿⠚⠗⠀⠕⠊` - - first differing cell (zero-based): 33 +- `sentence_02.json` #425: ‘제로(Zero)’는 ‘대전 0시 축제(Daejeon Zero O'clock Festival)’의 영문 이름에서 따왔으며, 축제장에서 3D홀로그램 미디어 콘텐츠 운영을 맡고 있는 애드테크 전문기업 코스윌에서 개발했다. + - expected: `⠀⠠⠵⠻⠕⠀⠠⠕⠄⠉⠇⠕⠉⠅⠀⠠⠋⠑⠌⠊⠧⠁⠇⠠` + - actual: `⠀⠠⠵⠻⠕⠀⠠⠕⠴⠄⠰⠉⠇⠕⠉⠅⠀⠠⠋⠑⠌⠊⠧⠁` + - first differing cell (zero-based): 56 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_03.json` #50: 당시 CC(폐쇄)TV에 찍힌 장면을 보면 A씨는 엘리베이터를 기다리는 B씨를 발견하자 몰래 뒤로 다가가 갑자기 피해 여성의 머리를 뒤에서 돌려차기로 가격하는 등 폭행했다. - - expected: `⠙⠌⠠⠧⠗⠠⠴⠴⠠⠠⠞⠧⠲⠝⠀⠠⠨⠕⠁⠚⠟⠀⠨⠶` - - actual: `⠙⠌⠠⠧⠗⠠⠴⠴⠞⠧⠲⠝⠀⠠⠨⠕⠁⠚⠟⠀⠨⠶⠑⠡` - - first differing cell (zero-based): 20 +- `sentence_03.json` #13417: 트랙리스트에 따르면 이번 앨범에는 첫 번째 트랙 ‘I'm Okay(아이엠 오케이)’를 시작으로 ‘오로라(AURORA)’, ‘PALACE(팰러스)’, ‘PARADE(퍼레이드)’까지 총 네 곡이 수록됐다. + - expected: `⠗⠁⠀⠠⠦⠴⠠⠊⠄⠍⠀⠠⠕⠅⠁⠽⠦⠄⠣⠕⠝⠢⠀⠥` + - actual: `⠗⠁⠀⠠⠦⠴⠠⠊⠴⠄⠰⠍⠀⠠⠕⠅⠁⠽⠦⠄⠣⠕⠝⠢` + - first differing cell (zero-based): 54 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_04.json` #819: 경찰은 7개월간 부산·울산·경남·경북·경기·강원 일대의 방범용 및 사설 CC(폐쇄회로)TV 영상을 추적·분석해 검거에 성공했다. 그러나 모두 국내 수거책뿐이었다. 경찰은 해외 조직 검거에도 매진하겠다고 밝혔다. - - expected: `⠗⠚⠽⠐⠥⠠⠴⠴⠠⠠⠞⠧⠲⠀⠻⠇⠶⠮⠀⠰⠍⠨⠹⠐` - - actual: `⠗⠚⠽⠐⠥⠠⠴⠴⠞⠧⠲⠀⠻⠇⠶⠮⠀⠰⠍⠨⠹⠐⠆⠘` - - first differing cell (zero-based): 92 +- `sentence_04.json` #4420: 이들은 지난 5일 자정 도쿄 아카사카 본사 건물 꼭대기에 설치된 회사 간판을 철거했다. 소속팀 중 ‘Johnny's(쟈니스)’ 포함된 그룹의 명도 차례대로 변경될 예정이다. + - expected: `⠴⠠⠚⠕⠓⠝⠝⠽⠄⠎⠦⠄⠨⠜⠉⠕⠠⠪⠠⠴⠴⠄⠀⠙` + - actual: `⠴⠠⠚⠕⠓⠝⠝⠽⠴⠄⠰⠎⠦⠄⠨⠜⠉⠕⠠⠪⠠⠴⠴⠄` + - first differing cell (zero-based): 97 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` ## Cross-cutting input-only structural cohorts -These are cross-cutting input-only structural cohorts, not new primary classes and not engine routing rules. Candidate selection never changes a case's existing primary class by itself. Only cohort members already classified as `pending_rule_review` form a pending subcluster; exact and other-primary members are controls that retain their existing outcomes. A separate classifier may use independently justified, output-localized PDF evidence, as in the rule-34 three-cell contradiction below. The `uppercase_roman_headword_closed_multiword_parenthetical` gate requires a two-or-more character uppercase ASCII headword immediately followed by a closed parenthesis whose contents are two or more ASCII Roman words separated only by spaces. Because the contents admit only letters and spaces, visible operators, subscript/superscript notation, and nested parentheses are excluded deterministically. The `mixed_roman_korean_word_before_uppercase_headword_expansion` gate further requires a whitespace-delimited preceding word that contains both ASCII Roman and Korean and ends in Korean. It locates the following uppercase headword itself, including its immediately preceding emitted cell, so an earlier Roman entry in the same sentence cannot satisfy the output audit. The `single_capital_followed_by_parenthesized_digits` gate requires a standalone capital, a non-empty closed ASCII-digit parenthetical, and alphanumeric outer boundaries. It likewise includes the emitted entry-boundary cell in localization. The `uppercase_roman_run_followed_by_hyphen_digits` gate requires a maximal uppercase ASCII run, literal ASCII hyphen-minus, a non-empty digit run, and alphanumeric outer boundaries. Its localized range includes the current encoded run and its immediately preceding cell, keeping `F-35`-style routing separate from both parenthesized digits and headword expansions. The `uppercase_alphanumeric_roman_digit_sequence` gate covers the wider rule-35/math collision: a capital-led uppercase/digit sequence with optional internal ASCII hyphen/full stop and trailing prose comma/colon/semicolon. It localizes only the current entry cells and does not decide whether an `A-3`-shaped surface is an identifier or a mathematical expression. The `standalone_multi_character_uppercase_roman_word` gate finds maximal ASCII-letter runs of two or more capitals with non-alphanumeric boundaries; a run immediately followed by `(` is excluded so the HCA-style headword itself is not counted by both gates. The `korean_prefixed_closed_allcaps_parenthetical` gate requires an immediately preceding Korean character and a closed body of two or more uppercase ASCII letters. It intentionally contains both acronym annotations (`책임자(COO)`) and scientific formulae (`일산화탄소(CO)`) so their semantic collision remains measurable. The `korean_prefixed_closed_roman_annotation_rule_34_order` gate accepts the narrower rule-34 body grammar after an immediately preceding Korean character and localizes only the current engine's Korean opening-parenthesis cells. This distinguishes the PDF's parenthesis-before-Roman-indicator order from unrelated differences later in the same sentence. The `multi_character_allcaps_roman_runs_joined_by_middle_dot` gate requires two maximal ASCII-letter runs of at least two capitals joined directly by U+00B7, with non-alphanumeric outer boundaries. It records shapes such as `AI·SW` without assigning prose, mathematics, or science semantics. The `roman_run_immediately_before_attached_middle_dot_boundary` gate is output-localized: it requires a maximal ASCII-letter run immediately before U+00B7 and an attached Korean character or ASCII-letter run after it, then searches for that whole current-engine signature in the actual output. It therefore isolates the Roman terminator boundary without treating unrelated middle dots elsewhere in the sentence as causal. The `attached_ascii_roman_to_korean_script_boundary` gate requires an ASCII letter run immediately followed by Korean script and localizes only the current mode marker there: either rule 29's final `⠲` or rule 39's opening `⠸⠷`. It deliberately does not infer the dominant language of the sentence from that surface boundary. The `korean_majority_same_token_roman_sandwich_non_domain` scope audit mirrors the narrower rule-39 implementation gate: the nearest script characters on both sides of one Korean segment are ASCII Roman, the input is Korean-majority by first-script word counts, and the segment is not dot-delimited like the official domain example. It measures change scope but is not itself an output-localized causal classifier. The `korean_inline_parenthesized_single_arithmetic_operator` gate requires an immediate `Korean(` + one rule-45 arithmetic operator + `)Korean` span. Unlike broad coexistence traits, it also locates the current engine's emitted structure and counts a mismatch as signature-local only when the sentence's first differing cell falls inside that output range. The `allcaps_roman_run_containing_ou` gate finds maximal, alphanumeric-delimited uppercase ASCII runs containing adjacent `OU`. It locates the independently encoded run signature in the complete current output and counts only first differences inside that signature as localized. The `allcaps_roman_run_containing_st` gate applies the same output-position requirement to adjacent `ST`; it is kept separate because UEB 10.12 makes contraction use depend on how an abbreviation or acronym is pronounced. The `uppercase_ascii_segments_joined_by_ampersand_capitalization` gate narrows attached ampersand runs to uppercase-only segments and locates the complete current Korean-context output. The run must begin its whitespace-delimited token, matching the current token-level capitals-word pre-emission scope; Korean-attached and punctuation-prefixed occurrences remain controls. UEB 8.4.2 says a nonalphabetic symbol terminates capitals word mode, while the official `AT&T` and `B&B` examples restart capitalization after `&`. The broader `attached_ascii_roman_segments_joined_by_ampersand` gate requires non-empty ASCII-letter segments joined directly by `&`, excludes spaced/Korean/alphanumeric continuations, and localizes only the current output cell immediately before the ampersand through an independently encoded real-input prefix. The `ampersand_before_attached_ascii_roman_segment` gate covers the distinct one-sided UEB `&c` boundary: `&` is followed by a complete ASCII-letter segment, while an ASCII alphanumeric or another ampersand immediately before it and a digit continuation after it are excluded. Its output range is anchored by independently encoding the real input prefix before each occurrence, then includes only the current Rule-71/29 entry boundary; a second pre-fix anchor through `&` retains the former exit location. The `consecutive_ascii_roman_words_whitespace_boundary` gate requires two adjacent ASCII-letter words separated only by whitespace. For each boundary it independently encodes the real input prefix ending after the first word, then localizes only the current rule-29 terminator at that position or the full output's replacing blank. This prevents an unrelated Roman occurrence elsewhere in the sentence from satisfying the audit. Slash and other punctuation-separated forms are deliberately excluded. The `decimal_point_between_ascii_digits` gate finds whitespace-delimited words containing `digit.digit` and reproduces each whole word in a neutral Korean context, so suffixes and punctuation remain part of the current-engine signature. The `compact_numeric_ascii_letter_suffix` gate finds a numeric prefix immediately followed by ASCII letters, includes the immediately preceding output cell as its entry boundary, and retains suffix-specific outcome counts. It intentionally includes both possible rule-69 units and ambiguous variable/identifier forms; membership alone does not assign unit semantics. The `rule69_ascii_unit_before_terminator_skipping_symbol` gate is narrower: it accepts only ASCII unit spellings already supported by rule 69, includes the immediately following rule-33/34 punctuation cell in the localized signature, and does not infer new units. The `tight_triangle_mark_immediately_before_korean` gate requires literal `△한글` with no input space and includes the first following Korean cell in its localized output range, so an observed missing-space difference is measured at the mark boundary. The `attached_korean_auxiliary_itda_spacing` gate mirrors the former normalization shape for a Korean token ending in attached `있다`; its localizer independently encodes the real prefix and claims only the blank inserted before the suffix. It does not decide whether orthographic correction may override the printed input. +These are cross-cutting input-only structural cohorts, not new primary classes and not engine routing rules. Candidate selection never changes a case's existing primary class by itself. Only cohort members already classified as `pending_rule_review` form a pending subcluster; exact and other-primary members are controls that retain their existing outcomes. A separate classifier may use independently justified, output-localized PDF evidence, as in the rule-34 three-cell contradiction below. The `uppercase_roman_headword_closed_multiword_parenthetical` gate requires a two-or-more character uppercase ASCII headword immediately followed by a closed parenthesis whose contents are two or more ASCII Roman words separated only by spaces. Because the contents admit only letters and spaces, visible operators, subscript/superscript notation, and nested parentheses are excluded deterministically. The `mixed_roman_korean_word_before_uppercase_headword_expansion` gate further requires a whitespace-delimited preceding word that contains both ASCII Roman and Korean and ends in Korean. It locates the following uppercase headword itself, including its immediately preceding emitted cell, so an earlier Roman entry in the same sentence cannot satisfy the output audit. The `single_capital_followed_by_parenthesized_digits` gate requires a standalone capital, a non-empty closed ASCII-digit parenthetical, and alphanumeric outer boundaries. It likewise includes the emitted entry-boundary cell in localization. The `uppercase_roman_run_followed_by_hyphen_digits` gate requires a maximal uppercase ASCII run, literal ASCII hyphen-minus, a non-empty digit run, and alphanumeric outer boundaries. Its localized range includes the current encoded run and its immediately preceding cell, keeping `F-35`-style routing separate from both parenthesized digits and headword expansions. The `uppercase_alphanumeric_roman_digit_sequence` gate covers the wider rule-35/math collision: a capital-led uppercase/digit sequence with optional internal ASCII hyphen/full stop and trailing prose comma/colon/semicolon. It localizes only the current entry cells and does not decide whether an `A-3`-shaped surface is an identifier or a mathematical expression. The `standalone_multi_character_uppercase_roman_word` gate finds maximal ASCII-letter runs of two or more capitals with non-alphanumeric boundaries; a run immediately followed by `(` is excluded so the HCA-style headword itself is not counted by both gates. The `korean_prefixed_closed_allcaps_parenthetical` gate requires an immediately preceding Korean character and a closed body of two or more uppercase ASCII letters. It intentionally contains both acronym annotations (`책임자(COO)`) and scientific formulae (`일산화탄소(CO)`) so their semantic collision remains measurable. The `korean_prefixed_closed_roman_annotation_rule_34_order` gate accepts the narrower rule-34 body grammar after an immediately preceding Korean character and localizes only the current engine's Korean opening-parenthesis cells. This distinguishes the PDF's parenthesis-before-Roman-indicator order from unrelated differences later in the same sentence. The `multi_character_allcaps_roman_runs_joined_by_middle_dot` gate requires two maximal ASCII-letter runs of at least two capitals joined directly by U+00B7, with non-alphanumeric outer boundaries. It records shapes such as `AI·SW` without assigning prose, mathematics, or science semantics. The `roman_run_immediately_before_attached_middle_dot_boundary` gate is output-localized: it requires a maximal ASCII-letter run immediately before U+00B7 and an attached Korean character or ASCII-letter run after it, then searches for that whole current-engine signature in the actual output. It therefore isolates the Roman terminator boundary without treating unrelated middle dots elsewhere in the sentence as causal. The `attached_ascii_roman_to_korean_script_boundary` gate requires an ASCII letter run immediately followed by Korean script and localizes only the current mode marker there: either rule 29's final `⠲` or rule 39's opening `⠸⠷`. It deliberately does not infer the dominant language of the sentence from that surface boundary. The `korean_majority_same_token_roman_sandwich_non_domain` scope audit mirrors the narrower rule-39 implementation gate: the nearest script characters on both sides of one Korean segment are ASCII Roman, the input is Korean-majority by first-script word counts, and the segment is not dot-delimited like the official domain example. It measures change scope but is not itself an output-localized causal classifier. The `korean_inline_parenthesized_single_arithmetic_operator` gate requires an immediate `Korean(` + one rule-45 arithmetic operator + `)Korean` span. Unlike broad coexistence traits, it also locates the current engine's emitted structure and counts a mismatch as signature-local only when the sentence's first differing cell falls inside that output range. The `allcaps_roman_run_containing_ou` gate finds maximal, alphanumeric-delimited uppercase ASCII runs containing adjacent `OU`. It locates the independently encoded run signature in the complete current output and counts only first differences inside that signature as localized. The `allcaps_roman_run_containing_st` gate applies the same output-position requirement to adjacent `ST`; it is kept separate because UEB 10.12 makes contraction use depend on how an abbreviation or acronym is pronounced. The `uppercase_ascii_segments_joined_by_ampersand_capitalization` gate narrows attached ampersand runs to uppercase-only segments and locates the complete current Korean-context output. The run must begin its whitespace-delimited token, matching the current token-level capitals-word pre-emission scope; Korean-attached and punctuation-prefixed occurrences remain controls. UEB 8.4.2 says a nonalphabetic symbol terminates capitals word mode, while the official `AT&T` and `B&B` examples restart capitalization after `&`. The broader `capitals_word_mode_previously_spanning_nonletter_scope` gate reproduces the former token predicate across ampersands, hyphens, digits, and other nonletters that separate uppercase runs. Trailing nonletters after the final run are excluded because their output is unchanged. It is a broad change-scope/regression audit and is deliberately not an output-cause localizer. The `attached_ascii_roman_segments_joined_by_ampersand` gate requires non-empty ASCII-letter segments joined directly by `&`, excludes spaced/Korean/alphanumeric continuations, and localizes only the current output cell immediately before the ampersand through an independently encoded real-input prefix. The `ampersand_before_attached_ascii_roman_segment` gate covers the distinct one-sided UEB `&c` boundary: `&` is followed by a complete ASCII-letter segment, while an ASCII alphanumeric or another ampersand immediately before it and a digit continuation after it are excluded. Its output range is anchored by independently encoding the real input prefix before each occurrence, then includes only the current Rule-71/29 entry boundary; a second pre-fix anchor through `&` retains the former exit location. The `consecutive_ascii_roman_words_whitespace_boundary` gate requires two adjacent ASCII-letter words separated only by whitespace. For each boundary it independently encodes the real input prefix ending after the first word, then localizes only the current rule-29 terminator at that position or the full output's replacing blank. This prevents an unrelated Roman occurrence elsewhere in the sentence from satisfying the audit. Slash and other punctuation-separated forms are deliberately excluded. The `decimal_point_between_ascii_digits` gate finds whitespace-delimited words containing `digit.digit` and reproduces each whole word in a neutral Korean context, so suffixes and punctuation remain part of the current-engine signature. The `compact_numeric_ascii_letter_suffix` gate finds a numeric prefix immediately followed by ASCII letters, includes the immediately preceding output cell as its entry boundary, and retains suffix-specific outcome counts. It intentionally includes both possible rule-69 units and ambiguous variable/identifier forms; membership alone does not assign unit semantics. The `rule69_ascii_unit_before_terminator_skipping_symbol` gate is narrower: it accepts only ASCII unit spellings already supported by rule 69, includes the immediately following rule-33/34 punctuation cell in the localized signature, and does not infer new units. The `tight_triangle_mark_immediately_before_korean` gate requires literal `△한글` with no input space and includes the first following Korean cell in its localized output range, so an observed missing-space difference is measured at the mark boundary. The `attached_korean_auxiliary_itda_spacing` gate mirrors the former normalization shape for a Korean token ending in attached `있다`; its localizer independently encodes the real prefix and claims only the blank inserted before the suffix. It does not decide whether orthographic correction may override the printed input. | Cluster | Candidates | Exact | Mismatch | Conflicting-reference cases | |---|---:|---:|---:|---:| -| `allcaps_roman_run_beginning_with_pure_letter_shortform` | 4120 | 2478 | 1642 | 0 | -| `allcaps_roman_run_containing_ar` | 1022 | 363 | 659 | 0 | -| `allcaps_roman_run_containing_ed` | 816 | 320 | 496 | 0 | -| `allcaps_roman_run_containing_ou` | 1816 | 64 | 1752 | 0 | -| `allcaps_roman_run_containing_st` | 1479 | 686 | 793 | 0 | +| `allcaps_roman_run_beginning_with_pure_letter_shortform` | 4120 | 2515 | 1605 | 0 | +| `allcaps_roman_run_containing_ar` | 1022 | 370 | 652 | 0 | +| `allcaps_roman_run_containing_ed` | 816 | 334 | 482 | 0 | +| `allcaps_roman_run_containing_ou` | 1816 | 67 | 1749 | 0 | +| `allcaps_roman_run_containing_st` | 1479 | 700 | 779 | 0 | | `ampersand_before_attached_ascii_roman_segment` | 30 | 12 | 18 | 0 | | `ascii_roman_tail_comma_before_digit_korean_token` | 58 | 3 | 55 | 0 | -| `attached_ascii_roman_segments_joined_by_ampersand` | 802 | 285 | 517 | 0 | -| `attached_ascii_roman_to_korean_script_boundary` | 17693 | 12973 | 4720 | 0 | -| `attached_korean_auxiliary_itda_spacing` | 95 | 71 | 24 | 0 | -| `closed_roman_parenthetical_after_non_ascii_letter_boundary` | 63959 | 52436 | 11523 | 0 | -| `compact_numeric_ascii_letter_suffix` | 2975 | 1878 | 1097 | 0 | -| `consecutive_ascii_roman_words_whitespace_boundary` | 4679 | 2138 | 2541 | 0 | -| `decimal_point_between_ascii_digits` | 4546 | 3397 | 1149 | 0 | +| `attached_ascii_roman_segments_joined_by_ampersand` | 802 | 607 | 195 | 0 | +| `attached_ascii_roman_to_korean_script_boundary` | 17693 | 13445 | 4248 | 0 | +| `attached_korean_auxiliary_itda_spacing` | 95 | 72 | 23 | 0 | +| `capitals_word_mode_previously_spanning_nonletter_scope` | 1733 | 852 | 881 | 0 | +| `closed_roman_parenthetical_after_non_ascii_letter_boundary` | 63959 | 52997 | 10962 | 0 | +| `compact_numeric_ascii_letter_suffix` | 2975 | 1911 | 1064 | 0 | +| `consecutive_ascii_roman_words_whitespace_boundary` | 4679 | 2232 | 2447 | 0 | +| `decimal_point_between_ascii_digits` | 4546 | 3444 | 1102 | 0 | | `korean_inline_parenthesized_single_arithmetic_operator` | 23 | 17 | 6 | 0 | -| `korean_majority_same_token_roman_sandwich_non_domain` | 947 | 386 | 561 | 0 | -| `korean_prefixed_closed_allcaps_parenthetical` | 54492 | 45187 | 9305 | 0 | -| `korean_prefixed_closed_roman_annotation_rule_34_order` | 64382 | 53042 | 11340 | 0 | +| `korean_majority_same_token_roman_sandwich_non_domain` | 947 | 592 | 355 | 0 | +| `korean_prefixed_closed_allcaps_parenthetical` | 54492 | 45633 | 8859 | 0 | +| `korean_prefixed_closed_roman_annotation_rule_34_order` | 64382 | 53593 | 10789 | 0 | | `korean_prefixed_roman_parenthetical_followed_by_allcaps_hyphen_suffix` | 11 | 0 | 11 | 0 | | `mixed_roman_korean_word_before_uppercase_headword_expansion` | 10 | 0 | 10 | 0 | | `multi_character_allcaps_roman_runs_joined_by_middle_dot` | 97 | 0 | 97 | 0 | | `percent_point_unit_list_comma` | 7 | 5 | 2 | 0 | -| `pure_allcaps_segment_before_hyphen_and_multi_allcaps_segment_after` | 448 | 133 | 315 | 0 | -| `roman_hyphenated_word_after_whitespace_following_korean_word` | 361 | 0 | 361 | 0 | -| `roman_parenthetical_headword_after_whitespace_following_korean_word` | 4695 | 3411 | 1284 | 0 | -| `roman_run_after_whitespace_following_closed_roman_enclosure` | 1093 | 142 | 951 | 0 | +| `pure_allcaps_segment_before_hyphen_and_multi_allcaps_segment_after` | 448 | 226 | 222 | 0 | +| `roman_hyphenated_word_after_whitespace_following_korean_word` | 361 | 86 | 275 | 0 | +| `roman_parenthetical_headword_after_whitespace_following_korean_word` | 4695 | 3548 | 1147 | 0 | +| `roman_run_after_whitespace_following_closed_roman_enclosure` | 1093 | 143 | 950 | 0 | | `roman_run_immediately_before_attached_middle_dot_boundary` | 577 | 0 | 577 | 0 | -| `rule69_ascii_unit_before_terminator_skipping_symbol` | 440 | 343 | 97 | 0 | +| `rule69_ascii_unit_before_terminator_skipping_symbol` | 440 | 344 | 96 | 0 | | `single_capital_followed_by_parenthesized_digits` | 1361 | 0 | 1361 | 0 | -| `spaced_comma_between_ascii_digit_runs` | 217 | 181 | 36 | 0 | -| `standalone_multi_character_uppercase_roman_word` | 62411 | 50640 | 11771 | 0 | +| `spaced_comma_between_ascii_digit_runs` | 217 | 182 | 35 | 0 | +| `standalone_multi_character_uppercase_roman_word` | 62411 | 51310 | 11101 | 0 | | `tight_triangle_mark_immediately_before_korean` | 377 | 0 | 377 | 0 | -| `uppercase_alphanumeric_roman_digit_sequence` | 3429 | 1917 | 1512 | 0 | -| `uppercase_ascii_run_immediately_after_digit_in_roman_sequence` | 1896 | 1106 | 790 | 0 | -| `uppercase_ascii_run_immediately_after_hyphen_in_roman_sequence` | 952 | 166 | 786 | 0 | -| `uppercase_ascii_segments_joined_by_ampersand_capitalization` | 439 | 1 | 438 | 0 | -| `uppercase_roman_headword_closed_multiword_parenthetical` | 175 | 16 | 159 | 0 | -| `uppercase_roman_run_followed_by_hyphen_digits` | 571 | 191 | 380 | 0 | -| `uppercase_word_after_whitespace_continuing_ascii_roman_text` | 1729 | 699 | 1030 | 0 | +| `uppercase_alphanumeric_roman_digit_sequence` | 3429 | 2096 | 1333 | 0 | +| `uppercase_ascii_run_immediately_after_digit_in_roman_sequence` | 1896 | 1262 | 634 | 0 | +| `uppercase_ascii_run_immediately_after_hyphen_in_roman_sequence` | 952 | 271 | 681 | 0 | +| `uppercase_ascii_segments_joined_by_ampersand_capitalization` | 439 | 320 | 119 | 0 | +| `uppercase_roman_headword_closed_multiword_parenthetical` | 175 | 17 | 158 | 0 | +| `uppercase_roman_run_followed_by_hyphen_digits` | 571 | 194 | 377 | 0 | +| `uppercase_word_after_whitespace_continuing_ascii_roman_text` | 1729 | 725 | 1004 | 0 | ### `allcaps_roman_run_beginning_with_pure_letter_shortform` -Of the 4120 candidates, 1571 are the actual `pending_rule_review` subcluster. The other 2549 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 4120 candidates, 1534 are the actual `pending_rule_review` subcluster. The other 2586 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. -For this output-signature audit, 1642 mismatches were evaluable and 832 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. +For this output-signature audit, 1605 mismatches were evaluable and 836 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. Localized first-difference transitions: -- `U+2830 ⠰ -> U+2820 ⠠`: 785 -- `U+2820 ⠠ -> U+2830 ⠰`: 47 +- `U+2830 ⠰ -> U+2820 ⠠`: 788 +- `U+2820 ⠠ -> U+2830 ⠰`: 48 Mismatch primary-class distribution: - `comparison_method`: 11 - `corpus_suspect`: 50 -- `pending_rule_review`: 1571 +- `pending_rule_review`: 1534 - `unsupported_character_review`: 10 Representative `exact` samples: @@ -680,13 +681,13 @@ Representative `mismatch` samples: ### `allcaps_roman_run_containing_ar` -Of the 1022 candidates, 635 are the actual `pending_rule_review` subcluster. The other 387 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 1022 candidates, 628 are the actual `pending_rule_review` subcluster. The other 394 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. -For this output-signature audit, 659 mismatches were evaluable and 404 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. +For this output-signature audit, 652 mismatches were evaluable and 407 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. Localized first-difference transitions: -- `U+2801 ⠁ -> U+281C ⠜`: 397 +- `U+2801 ⠁ -> U+281C ⠜`: 400 - `U+2810 ⠐ -> U+2815 ⠕`: 3 - `U+2800 ⠀ -> U+2820 ⠠`: 2 - `U+2820 ⠠ -> U+280A ⠊`: 1 @@ -696,7 +697,7 @@ Mismatch primary-class distribution: - `comparison_method`: 3 - `corpus_suspect`: 15 -- `pending_rule_review`: 635 +- `pending_rule_review`: 628 - `unsupported_character_review`: 6 Representative `exact` samples: @@ -766,13 +767,13 @@ Representative `mismatch` samples: ### `allcaps_roman_run_containing_ed` -Of the 816 candidates, 427 are the actual `pending_rule_review` subcluster. The other 389 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 816 candidates, 412 are the actual `pending_rule_review` subcluster. The other 404 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. -For this output-signature audit, 496 mismatches were evaluable and 329 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. +For this output-signature audit, 482 mismatches were evaluable and 330 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. Localized first-difference transitions: -- `U+2811 ⠑ -> U+282B ⠫`: 326 +- `U+2811 ⠑ -> U+282B ⠫`: 327 - `U+2810 ⠐ -> U+280E ⠎`: 1 - `U+2810 ⠐ -> U+281D ⠝`: 1 - `U+2815 ⠕ -> U+2820 ⠠`: 1 @@ -780,8 +781,8 @@ Localized first-difference transitions: Mismatch primary-class distribution: - `comparison_method`: 1 -- `corpus_suspect`: 63 -- `pending_rule_review`: 427 +- `corpus_suspect`: 64 +- `pending_rule_review`: 412 - `unsupported_character_review`: 5 Representative `exact` samples: @@ -851,20 +852,20 @@ Representative `mismatch` samples: ### `allcaps_roman_run_containing_ou` -Of the 1816 candidates, 1666 are the actual `pending_rule_review` subcluster. The other 150 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 1816 candidates, 1663 are the actual `pending_rule_review` subcluster. The other 153 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. -For this output-signature audit, 1752 mismatches were evaluable and 1332 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. +For this output-signature audit, 1749 mismatches were evaluable and 1356 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. Localized first-difference transitions: -- `U+2815 ⠕ -> U+2833 ⠳`: 1331 +- `U+2815 ⠕ -> U+2833 ⠳`: 1355 - `U+2810 ⠐ -> U+283D ⠽`: 1 Mismatch primary-class distribution: - `comparison_method`: 2 - `corpus_suspect`: 9 -- `pending_rule_review`: 1666 +- `pending_rule_review`: 1663 - `unsupported_character_review`: 75 Representative `exact` samples: @@ -912,13 +913,13 @@ Representative `mismatch` samples: ### `allcaps_roman_run_containing_st` -Of the 1479 candidates, 740 are the actual `pending_rule_review` subcluster. The other 739 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 1479 candidates, 726 are the actual `pending_rule_review` subcluster. The other 753 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. -For this output-signature audit, 793 mismatches were evaluable and 443 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. +For this output-signature audit, 779 mismatches were evaluable and 449 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. Localized first-difference transitions: -- `U+280E ⠎ -> U+280C ⠌`: 435 +- `U+280E ⠎ -> U+280C ⠌`: 441 - `U+2820 ⠠ -> U+280C ⠌`: 4 - `U+280C ⠌ -> U+280E ⠎`: 1 - `U+2810 ⠐ -> U+2811 ⠑`: 1 @@ -928,7 +929,7 @@ Mismatch primary-class distribution: - `comparison_method`: 13 - `corpus_suspect`: 35 -- `pending_rule_review`: 740 +- `pending_rule_review`: 726 - `unsupported_character_review`: 5 Representative `exact` samples: @@ -1133,16 +1134,16 @@ Representative `mismatch` samples: ### `attached_ascii_roman_segments_joined_by_ampersand` -Of the 802 candidates, 490 are the actual `pending_rule_review` subcluster. The other 312 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 802 candidates, 168 are the actual `pending_rule_review` subcluster. The other 634 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. -For this output-signature audit, 517 mismatches were evaluable and 0 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. +For this output-signature audit, 195 mismatches were evaluable and 0 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. Mismatch primary-class distribution: -- `comparison_method`: 3 +- `comparison_method`: 15 - `corpus_suspect`: 10 -- `pending_rule_review`: 490 -- `unsupported_character_review`: 14 +- `pending_rule_review`: 168 +- `unsupported_character_review`: 2 Representative `exact` samples: @@ -1150,9 +1151,9 @@ Representative `exact` samples: - expected: `⠍⠐⠕⠈⠪⠢⠩⠶⠨⠕⠨⠍⠫⠀⠜⠁⠀⠼⠛⠨⠥⠏⠒⠺` - actual: `⠍⠐⠕⠈⠪⠢⠩⠶⠨⠕⠨⠍⠫⠀⠜⠁⠀⠼⠛⠨⠥⠏⠒⠺` - current primary/reason: `exact` / `exact` -- `sentence_02.json` #195: 대전광역시교육청(교육감 설동호)은 직업계고 학생들의 우수기업 취업을 위한 「2023학년도 DJ(Dream&Job) 일자리 NEW 365 매칭데이」채용박람회를 10일 대전컨벤션센터(DCC) 제2전시관에서 성공리에 마쳤다고 밝혔다. - - expected: `⠊⠗⠨⠾⠈⠧⠶⠱⠁⠠⠕⠈⠬⠩⠁⠰⠻⠦⠄⠈⠬⠩⠁⠫` - - actual: `⠊⠗⠨⠾⠈⠧⠶⠱⠁⠠⠕⠈⠬⠩⠁⠰⠻⠦⠄⠈⠬⠩⠁⠫` +- `sentence_02.json` #146: KT&G(사장 백복인)가 산업정책연구원(IPS)이 주관하고 윤경 ESG 포럼이 주최하는 ‘윤경 ESG 포럼 CEO 서약식’에 참여해 윤리경영 의지를 다졌다. + - expected: `⠴⠠⠠⠅⠞⠈⠯⠠⠛⠦⠄⠇⠨⠶⠀⠘⠗⠁⠘⠭⠟⠠⠴⠫` + - actual: `⠴⠠⠠⠅⠞⠈⠯⠠⠛⠦⠄⠇⠨⠶⠀⠘⠗⠁⠘⠭⠟⠠⠴⠫` - current primary/reason: `exact` / `exact` - `sentence_03.json` #191: 한국투자신탁운용의 에이스(ACE) 글로벌브랜드TOP10블룸버그 ETF는 연초 이후 22.6% 상승했다. 명품 기업 루이비통모에헤네시(LVMH), 대표 가치주로 꼽히는 프록터앤드갬블(P&G) 편입 비중이 높은 것이 특징이다. - expected: `⠚⠒⠈⠍⠁⠓⠍⠨⠠⠟⠓⠁⠛⠬⠶⠺⠀⠝⠕⠠⠪⠦⠄⠴` @@ -1170,15 +1171,15 @@ Representative `mismatch` samples: - actual: `⠯⠠⠠⠎⠧⠉⠠⠴⠐⠣⠼⠉⠲⠚⠊⠴⠏⠐⠜⠐⠀⠠⠥⠙` - first differing cell (zero-based): 100 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_02.json` #146: KT&G(사장 백복인)가 산업정책연구원(IPS)이 주관하고 윤경 ESG 포럼이 주최하는 ‘윤경 ESG 포럼 CEO 서약식’에 참여해 윤리경영 의지를 다졌다. - - expected: `⠴⠠⠠⠅⠞⠈⠯⠠⠛⠦⠄⠇⠨⠶⠀⠘⠗⠁⠘⠭⠟⠠⠴⠫` - - actual: `⠴⠠⠠⠅⠞⠈⠯⠛⠦⠄⠇⠨⠶⠀⠘⠗⠁⠘⠭⠟⠠⠴⠫⠀` - - first differing cell (zero-based): 7 +- `sentence_02.json` #455: 먼저, 세계 최고의 R&D인프라와 인력을 갖춘 장점을 활용하여 국가첨단반도체 기술센터(ASTC)를 유치하고 대전을 반도체 연구·교육·실증 거점으로 조성할 계획이다. + - expected: `⠓⠎⠦⠄⠴⠠⠠⠁⠎⠞⠉⠠⠴⠐⠮⠀⠩⠰⠕⠚⠈⠥⠀⠊` + - actual: `⠓⠎⠦⠄⠴⠠⠠⠁⠌⠉⠠⠴⠐⠮⠀⠩⠰⠕⠚⠈⠥⠀⠊⠗` + - first differing cell (zero-based): 90 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` - `sentence_03.json` #370: 26일 GS칼텍스는 서울 강남구 GS타워에서 이승훈 GS칼텍스 S&T본부장, 박진기 HMM 총괄부사장 등이 참석한 가운데 친환경 바이오선박유 사업 업무협약(MOU)을 체결했다고 밝혔다. - - expected: `⠓⠝⠁⠠⠪⠀⠴⠠⠎⠈⠯⠠⠞⠲⠘⠷⠘⠍⠨⠶⠐⠀⠘⠁` - - actual: `⠓⠝⠁⠠⠪⠀⠴⠠⠠⠎⠈⠯⠞⠲⠘⠷⠘⠍⠨⠶⠐⠀⠘⠁` - - first differing cell (zero-based): 69 + - expected: `⠜⠁⠦⠄⠴⠠⠠⠍⠕⠥⠠⠴⠮⠀⠰⠝⠈⠳⠚⠗⠌⠊⠈⠥` + - actual: `⠜⠁⠦⠄⠴⠠⠠⠍⠳⠠⠴⠮⠀⠰⠝⠈⠳⠚⠗⠌⠊⠈⠥⠀` + - first differing cell (zero-based): 164 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` - `sentence_04.json` #19: 이날 행사에는 구 대표 외에 권봉석 (주)LG 최고운영책임자(COO), 박일평 LG사이언스파크대표(사장)를 비롯해 각 계열사 최고기술책임자(CTO), 최고데이터책임자(CDO), 최고인사책임자(CHO) 등이 참석해 국내 이공계 R&D 인재 400여명과 만났다. - expected: `⠁⠕⠢⠨⠦⠄⠴⠠⠠⠉⠕⠕⠠⠴⠐⠀⠘⠁⠕⠂⠙⠻⠀⠴` @@ -1188,16 +1189,16 @@ Representative `mismatch` samples: ### `attached_ascii_roman_to_korean_script_boundary` -Of the 17693 candidates, 3887 are the actual `pending_rule_review` subcluster. The other 13806 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 17693 candidates, 3412 are the actual `pending_rule_review` subcluster. The other 14281 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. -For this output-signature audit, 4720 mismatches were evaluable and 0 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. +For this output-signature audit, 4248 mismatches were evaluable and 0 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. Mismatch primary-class distribution: -- `comparison_method`: 60 -- `corpus_suspect`: 722 -- `pending_rule_review`: 3887 -- `unsupported_character_review`: 51 +- `comparison_method`: 73 +- `corpus_suspect`: 725 +- `pending_rule_review`: 3412 +- `unsupported_character_review`: 38 Representative `exact` samples: @@ -1246,7 +1247,7 @@ Representative `mismatch` samples: - current primary/reason: `corpus_suspect` / `rule34_roman_indicator_before_opening_parenthesis` - `sentence_02.json` #10: 설동호 대전교육감은 “학교 교육과정 및 여건에 맞는 인공지능(AI)교육 선도학교 운영을 통해 AI·SW교육 기반 구축과 학생들과 교원들의 미래 디지털 역량이 함양될 것이다.”라고 말했다. - expected: `⠚⠗⠀⠴⠠⠠⠁⠊⠐⠆⠴⠠⠠⠎⠺⠲⠈⠬⠩⠁⠀⠈⠕⠘` - - actual: `⠚⠗⠀⠴⠠⠠⠁⠊⠲⠐⠆⠴⠎⠺⠲⠈⠬⠩⠁⠀⠈⠕⠘⠒` + - actual: `⠚⠗⠀⠴⠠⠠⠁⠊⠲⠐⠆⠴⠠⠠⠎⠺⠲⠈⠬⠩⠁⠀⠈⠕` - first differing cell (zero-based): 93 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` - `sentence_03.json` #6: 지난해 9월 HMM과 파나시아가 선박용 탄소포집 시스템 공동연구에 대한 업무협약(MOU)을 체결한 뒤 7개월 만에 내놓은 연구성과다. @@ -1267,8 +1268,8 @@ Representative `mismatch_rule29_terminator` samples: - actual: `⠎⠀⠴⠠⠠⠙⠗⠭⠲⠺⠀⠷⠥⠙⠪⠐⠣⠟⠀⠋⠷⠓⠝⠒` - current primary/reason: `corpus_suspect` / `rule34_roman_indicator_before_opening_parenthesis` - `sentence_02.json` #10: 설동호 대전교육감은 “학교 교육과정 및 여건에 맞는 인공지능(AI)교육 선도학교 운영을 통해 AI·SW교육 기반 구축과 학생들과 교원들의 미래 디지털 역량이 함양될 것이다.”라고 말했다. - - expected: `⠁⠊⠐⠆⠴⠠⠠⠎⠺⠲⠈⠬⠩⠁⠀⠈⠕⠘⠒⠀⠈⠍⠰⠍` - - actual: `⠁⠊⠲⠐⠆⠴⠎⠺⠲⠈⠬⠩⠁⠀⠈⠕⠘⠒⠀⠈⠍⠰⠍⠁` + - expected: `⠐⠆⠴⠠⠠⠎⠺⠲⠈⠬⠩⠁⠀⠈⠕⠘⠒⠀⠈⠍⠰⠍⠁⠈` + - actual: `⠲⠐⠆⠴⠠⠠⠎⠺⠲⠈⠬⠩⠁⠀⠈⠕⠘⠒⠀⠈⠍⠰⠍⠁` - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` - `sentence_03.json` #6: 지난해 9월 HMM과 파나시아가 선박용 탄소포집 시스템 공동연구에 대한 업무협약(MOU)을 체결한 뒤 7개월 만에 내놓은 연구성과다. - expected: `⠂⠀⠴⠠⠠⠓⠍⠍⠲⠈⠧⠀⠙⠉⠠⠕⠣⠫⠀⠠⠾⠘⠁⠬` @@ -1288,15 +1289,15 @@ Representative `mismatch_rule39_hangul_opening` samples: ### `attached_korean_auxiliary_itda_spacing` -Of the 95 candidates, 20 are the actual `pending_rule_review` subcluster. The other 75 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 95 candidates, 19 are the actual `pending_rule_review` subcluster. The other 76 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. -For this output-signature audit, 24 mismatches were evaluable and 0 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. +For this output-signature audit, 23 mismatches were evaluable and 0 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. Mismatch primary-class distribution: - `comparison_method`: 1 - `corpus_suspect`: 3 -- `pending_rule_review`: 20 +- `pending_rule_review`: 19 Representative `exact` samples: @@ -1335,26 +1336,79 @@ Representative `mismatch` samples: - first differing cell (zero-based): 109 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +### `capitals_word_mode_previously_spanning_nonletter_scope` + +Of the 1733 candidates, 824 are the actual `pending_rule_review` subcluster. The other 909 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. + +Mismatch primary-class distribution: + +- `comparison_method`: 15 +- `corpus_suspect`: 37 +- `pending_rule_review`: 824 +- `unsupported_character_review`: 5 + +Representative `exact` samples: + +- `sentence_01.json` #52: 과학기술정보통신부와 개인정보보호위원회의 공동 고시 기준에 따라 한국인터넷진흥원(KISA)이 인증하는 ISMS-P는 고객 정보보호 및 개인정보보호를 위한 일련의 조치와 활동이 국가공인 인증기준에 적합함을 증명하는 제도다. + - expected: `⠈⠧⠚⠁⠈⠕⠠⠯⠨⠻⠘⠥⠓⠿⠠⠟⠘⠍⠧⠀⠈⠗⠟⠨` + - actual: `⠈⠧⠚⠁⠈⠕⠠⠯⠨⠻⠘⠥⠓⠿⠠⠟⠘⠍⠧⠀⠈⠗⠟⠨` + - current primary/reason: `exact` / `exact` +- `sentence_02.json` #23: 군은 14일 군청 소회의실에서 태안군 박경찬 부군수를 비롯해 충청남도·보령시·당진시·서천군 관계자 등 10여 명이 참석한 가운데 ‘화력발전 지역자원시설세 탄력세율 추진 T/F(태스크포스) 회의’를 개최했다고 밝혔다. + - expected: `⠈⠛⠵⠀⠼⠁⠙⠕⠂⠀⠈⠛⠰⠻⠀⠠⠥⠚⠽⠺⠠⠕⠂⠝` + - actual: `⠈⠛⠵⠀⠼⠁⠙⠕⠂⠀⠈⠛⠰⠻⠀⠠⠥⠚⠽⠺⠠⠕⠂⠝` + - current primary/reason: `exact` / `exact` +- `sentence_03.json` #50: 당시 CC(폐쇄)TV에 찍힌 장면을 보면 A씨는 엘리베이터를 기다리는 B씨를 발견하자 몰래 뒤로 다가가 갑자기 피해 여성의 머리를 뒤에서 돌려차기로 가격하는 등 폭행했다. + - expected: `⠊⠶⠠⠕⠀⠴⠠⠠⠉⠉⠦⠄⠙⠌⠠⠧⠗⠠⠴⠴⠠⠠⠞⠧` + - actual: `⠊⠶⠠⠕⠀⠴⠠⠠⠉⠉⠦⠄⠙⠌⠠⠧⠗⠠⠴⠴⠠⠠⠞⠧` + - current primary/reason: `exact` / `exact` +- `sentence_04.json` #263: 김 부위원장은 투자은행(IB)의 기업 신용 공여, 합병 제도 등 기업의 M&A와 관련한 다른 제도의 불합리한 규제도 정비하고 기업구조혁신펀드도 추가로 조성하겠다고 밝혔다. + - expected: `⠈⠕⠢⠀⠘⠍⠍⠗⠏⠒⠨⠶⠵⠀⠓⠍⠨⠣⠵⠚⠗⠶⠦⠄` + - actual: `⠈⠕⠢⠀⠘⠍⠍⠗⠏⠒⠨⠶⠵⠀⠓⠍⠨⠣⠵⠚⠗⠶⠦⠄` + - current primary/reason: `exact` / `exact` + +Representative `mismatch` samples: + +- `sentence_01.json` #17: 지난달 30일(현지시각) 뉴욕증권거래소(NYSE)에서 다우존스30산업평균지수는 전 거래일보다 73.55포인트(0.22%) 하락한 3만3147.25로 마감했다. 대형주 중심의 S&P500지수는 9.78포인트(0.25%) 하락한 3839.50으로, 기술주 중심의 나스닥지수는 11.61포인트(0.11%) 하락한 1만0466.4 + - expected: `⠠⠕⠢⠺⠀⠴⠠⠎⠈⠯⠠⠏⠼⠑⠚⠚⠨⠕⠠⠍⠉⠵⠀⠼` + - actual: `⠠⠕⠢⠺⠀⠴⠠⠎⠲⠴⠈⠯⠲⠴⠠⠏⠼⠑⠚⠚⠨⠕⠠⠍` + - first differing cell (zero-based): 169 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_02.json` #10: 설동호 대전교육감은 “학교 교육과정 및 여건에 맞는 인공지능(AI)교육 선도학교 운영을 통해 AI·SW교육 기반 구축과 학생들과 교원들의 미래 디지털 역량이 함양될 것이다.”라고 말했다. + - expected: `⠚⠗⠀⠴⠠⠠⠁⠊⠐⠆⠴⠠⠠⠎⠺⠲⠈⠬⠩⠁⠀⠈⠕⠘` + - actual: `⠚⠗⠀⠴⠠⠠⠁⠊⠲⠐⠆⠴⠠⠠⠎⠺⠲⠈⠬⠩⠁⠀⠈⠕` + - first differing cell (zero-based): 93 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_03.json` #365: 김동준은 2010년 ZE:A(제국의아이들)로 데뷔한 이후 드라마 ‘동네변호사 조들호’, ‘빛나라 은수’, ‘블랙’, ‘보좌관’ 시즌 1·2, ‘경우의 수’, 영화 ‘회사원’, ‘간이역’ 등에 출연, 필모그래피를 쌓아왔다. + - expected: `⠴⠄⠀⠠⠕⠨⠵⠀⠼⠁⠐⠆⠼⠃⠐⠀⠠⠦⠈⠻⠍⠺⠀⠠` + - actual: `⠴⠄⠀⠠⠕⠨⠵⠀⠀⠼⠁⠐⠼⠃⠐⠀⠠⠦⠈⠻⠍⠺⠀⠠` + - first differing cell (zero-based): 127 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_04.json` #19: 이날 행사에는 구 대표 외에 권봉석 (주)LG 최고운영책임자(COO), 박일평 LG사이언스파크대표(사장)를 비롯해 각 계열사 최고기술책임자(CTO), 최고데이터책임자(CDO), 최고인사책임자(CHO) 등이 참석해 국내 이공계 R&D 인재 400여명과 만났다. + - expected: `⠁⠕⠢⠨⠦⠄⠴⠠⠠⠉⠕⠕⠠⠴⠐⠀⠘⠁⠕⠂⠙⠻⠀⠴` + - actual: `⠁⠕⠢⠨⠦⠄⠴⠠⠉⠠⠕⠠⠕⠠⠴⠐⠀⠘⠁⠕⠂⠙⠻⠀` + - first differing cell (zero-based): 60 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` + ### `closed_roman_parenthetical_after_non_ascii_letter_boundary` -Of the 63959 candidates, 10035 are the actual `pending_rule_review` subcluster. The other 53924 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 63959 candidates, 9471 are the actual `pending_rule_review` subcluster. The other 54488 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. -For this output-signature audit, 11523 mismatches were evaluable and 1245 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. +For this output-signature audit, 10962 mismatches were evaluable and 1128 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. Localized first-difference transitions: -- `U+2826 ⠦ -> U+2834 ⠴`: 436 -- `U+2826 ⠦ -> U+2800 ⠀`: 325 +- `U+2826 ⠦ -> U+2834 ⠴`: 437 +- `U+2826 ⠦ -> U+2800 ⠀`: 330 - `U+2820 ⠠ -> U+2809 ⠉`: 104 -- `U+2820 ⠠ -> U+280E ⠎`: 65 -- `U+2820 ⠠ -> U+2801 ⠁`: 29 +- `U+2834 ⠴ -> U+2826 ⠦`: 28 +- `U+2826 ⠦ -> U+2810 ⠐`: 22 Mismatch primary-class distribution: -- `comparison_method`: 227 -- `corpus_suspect`: 1108 -- `pending_rule_review`: 10035 -- `unsupported_character_review`: 153 +- `comparison_method`: 238 +- `corpus_suspect`: 1111 +- `pending_rule_review`: 9471 +- `unsupported_character_review`: 142 Representative `exact` samples: @@ -1407,7 +1461,7 @@ Representative `mismatch` samples: - current primary/reason: `corpus_suspect` / `rule34_roman_indicator_before_opening_parenthesis` - `sentence_02.json` #10: 설동호 대전교육감은 “학교 교육과정 및 여건에 맞는 인공지능(AI)교육 선도학교 운영을 통해 AI·SW교육 기반 구축과 학생들과 교원들의 미래 디지털 역량이 함양될 것이다.”라고 말했다. - expected: `⠚⠗⠀⠴⠠⠠⠁⠊⠐⠆⠴⠠⠠⠎⠺⠲⠈⠬⠩⠁⠀⠈⠕⠘` - - actual: `⠚⠗⠀⠴⠠⠠⠁⠊⠲⠐⠆⠴⠎⠺⠲⠈⠬⠩⠁⠀⠈⠕⠘⠒` + - actual: `⠚⠗⠀⠴⠠⠠⠁⠊⠲⠐⠆⠴⠠⠠⠎⠺⠲⠈⠬⠩⠁⠀⠈⠕` - first differing cell (zero-based): 93 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` - `sentence_03.json` #1: 김 지사는 18일(이하 현지시각) 미국 코네티컷주 댄버리 린데 본사에서 산지브 람바(Sanjiv Lamba) 린데 회장, 성백석 린데코리아 회장, 조일교 아산시 부시장과 투자양해각서(MOU)을 체결했다. @@ -1423,23 +1477,23 @@ Representative `mismatch` samples: ### `compact_numeric_ascii_letter_suffix` -Of the 2975 candidates, 1026 are the actual `pending_rule_review` subcluster. The other 1949 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 2975 candidates, 993 are the actual `pending_rule_review` subcluster. The other 1982 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. -For this output-signature audit, 1097 mismatches were evaluable and 251 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. +For this output-signature audit, 1064 mismatches were evaluable and 256 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. Localized first-difference transitions: -- `U+283C ⠼ -> U+2800 ⠀`: 191 +- `U+283C ⠼ -> U+2800 ⠀`: 192 - `U+2800 ⠀ -> U+2832 ⠲`: 27 - `U+2810 ⠐ -> U+2832 ⠲`: 11 - `U+281E ⠞ -> U+2810 ⠐`: 8 -- `U+2818 ⠘ -> U+2832 ⠲`: 2 +- `U+2820 ⠠ -> U+2834 ⠴`: 6 Mismatch primary-class distribution: - `comparison_method`: 12 - `corpus_suspect`: 52 -- `pending_rule_review`: 1026 +- `pending_rule_review`: 993 - `unsupported_character_review`: 7 Representative `exact` samples: @@ -1509,20 +1563,20 @@ Representative `mismatch` samples: ### `consecutive_ascii_roman_words_whitespace_boundary` -Of the 4679 candidates, 2407 are the actual `pending_rule_review` subcluster. The other 2272 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 4679 candidates, 2312 are the actual `pending_rule_review` subcluster. The other 2367 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. -For this output-signature audit, 2541 mismatches were evaluable and 23 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. +For this output-signature audit, 2447 mismatches were evaluable and 26 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. Localized first-difference transitions: -- `U+2800 ⠀ -> U+2832 ⠲`: 23 +- `U+2800 ⠀ -> U+2832 ⠲`: 26 Mismatch primary-class distribution: -- `comparison_method`: 17 -- `corpus_suspect`: 88 -- `pending_rule_review`: 2407 -- `unsupported_character_review`: 29 +- `comparison_method`: 20 +- `corpus_suspect`: 89 +- `pending_rule_review`: 2312 +- `unsupported_character_review`: 26 Representative `exact` samples: @@ -1550,10 +1604,10 @@ Representative `localized_mismatch` samples: - actual: `⠄⠴⠠⠠⠎⠽⠝⠕⠲⠀⠀⠠⠠⠏⠑⠍⠔⠼⠁⠴⠀⠀⠑⠥` - first differing cell (zero-based): 33 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_02.json` #3467: 문 과장은 지난 2017년 광주디자인진흥원에 입사해 광주디자인비엔날레를 비롯해 광주국제도시디자인포럼(2019), 광주 ACE Fair(2020) 등 굵직한 국제행사에서 전시지원과 기획을 경험하며 역량을 쌓아온 베테랑이다. - - expected: `⠍⠀⠴⠠⠠⠁⠉⠑⠀⠠⠋⠁⠊⠗⠦⠄⠼⠃⠚⠃⠚⠠⠴⠀` - - actual: `⠍⠀⠴⠠⠠⠁⠉⠑⠲⠀⠠⠋⠁⠊⠗⠦⠼⠃⠚⠃⠚⠴⠀⠀` - - first differing cell (zero-based): 124 +- `sentence_02.json` #846: KT&G(사장 백복인)가 ‘글로벌 톱 티어(Global Top-tier) 도약’과 전자담배 생산혁신 거점 구축을 위해 신탄진 NGP(전자담배) 공장을 확장했다. + - expected: `⠴⠠⠛⠇⠕⠃⠁⠇⠀⠠⠞⠕⠏⠤⠞⠊⠻⠠⠴⠀⠊⠥⠜⠁` + - actual: `⠴⠠⠛⠇⠕⠃⠁⠇⠲⠀⠀⠠⠞⠕⠏⠔⠞⠊⠑⠗⠴⠀⠊⠥` + - first differing cell (zero-based): 51 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` - `sentence_03.json` #3481: 제로베이스원 데뷔쇼는 10일 오후 8시 Mnet을 통해 방송되며, 유튜브 Mnet K-POP 채널과 일본 ABEMA(아베마), Mnet Japan을 통해서도 만날 수 있다. - expected: `⠪⠀⠴⠠⠍⠝⠑⠞⠀⠰⠠⠅⠤⠠⠠⠏⠕⠏⠲⠀⠰⠗⠉⠞` @@ -1587,15 +1641,15 @@ Representative `mismatch` samples: ### `decimal_point_between_ascii_digits` -Of the 4546 candidates, 1092 are the actual `pending_rule_review` subcluster. The other 3454 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 4546 candidates, 1045 are the actual `pending_rule_review` subcluster. The other 3501 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. -For this output-signature audit, 1149 mismatches were evaluable and 262 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. +For this output-signature audit, 1102 mismatches were evaluable and 267 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. Localized first-difference transitions: -- `U+2814 ⠔ -> U+2824 ⠤`: 55 +- `U+2814 ⠔ -> U+2824 ⠤`: 58 - `U+2826 ⠦ -> U+2800 ⠀`: 36 -- `U+2826 ⠦ -> U+2810 ⠐`: 17 +- `U+2826 ⠦ -> U+2810 ⠐`: 19 - `U+2800 ⠀ -> U+280A ⠊`: 16 - `U+283C ⠼ -> U+2800 ⠀`: 14 @@ -1603,7 +1657,7 @@ Mismatch primary-class distribution: - `comparison_method`: 17 - `corpus_suspect`: 28 -- `pending_rule_review`: 1092 +- `pending_rule_review`: 1045 - `unsupported_character_review`: 12 Representative `exact` samples: @@ -1651,9 +1705,9 @@ Representative `localized_mismatch` samples: Representative `mismatch` samples: - `sentence_01.json` #17: 지난달 30일(현지시각) 뉴욕증권거래소(NYSE)에서 다우존스30산업평균지수는 전 거래일보다 73.55포인트(0.22%) 하락한 3만3147.25로 마감했다. 대형주 중심의 S&P500지수는 9.78포인트(0.25%) 하락한 3839.50으로, 기술주 중심의 나스닥지수는 11.61포인트(0.11%) 하락한 1만0466.4 - - expected: `⠶⠠⠕⠢⠺⠀⠴⠠⠎⠈⠯⠠⠏⠼⠑⠚⠚⠨⠕⠠⠍⠉⠵⠀` - - actual: `⠶⠠⠕⠢⠺⠀⠴⠠⠠⠎⠲⠴⠈⠯⠲⠴⠏⠼⠑⠚⠚⠨⠕⠠` - - first differing cell (zero-based): 168 + - expected: `⠠⠕⠢⠺⠀⠴⠠⠎⠈⠯⠠⠏⠼⠑⠚⠚⠨⠕⠠⠍⠉⠵⠀⠼` + - actual: `⠠⠕⠢⠺⠀⠴⠠⠎⠲⠴⠈⠯⠲⠴⠠⠏⠼⠑⠚⠚⠨⠕⠠⠍` + - first differing cell (zero-based): 169 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` - `sentence_02.json` #5: 현재의 경기상황을 보여주는 동행지수(1월 기준, 99.8→99.4)·앞으로의 흐름을 보여주는 선행지수(98.8→98.5)는 모두 기준(100)보다 낮은 수준에서 하락세를 지속하면서 경기 부진을 반영하는 모양새다. - expected: `⠛⠐⠀⠼⠊⠊⠲⠓⠀⠒⠕⠀⠼⠊⠊⠲⠙⠠⠴⠐⠆⠣⠲⠪` @@ -1719,12 +1773,12 @@ Representative `mismatch` samples: ### `korean_majority_same_token_roman_sandwich_non_domain` -Of the 947 candidates, 549 are the actual `pending_rule_review` subcluster. The other 398 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 947 candidates, 343 are the actual `pending_rule_review` subcluster. The other 604 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. Mismatch primary-class distribution: - `corpus_suspect`: 7 -- `pending_rule_review`: 549 +- `pending_rule_review`: 343 - `unsupported_character_review`: 5 Representative `exact` samples: @@ -1737,9 +1791,9 @@ Representative `exact` samples: - expected: `⠰⠍⠶⠉⠢⠈⠬⠩⠁⠰⠻⠦⠄⠈⠬⠩⠁⠫⠢⠀⠈⠕⠢⠨` - actual: `⠰⠍⠶⠉⠢⠈⠬⠩⠁⠰⠻⠦⠄⠈⠬⠩⠁⠫⠢⠀⠈⠕⠢⠨` - current primary/reason: `exact` / `exact` -- `sentence_03.json` #54: 위탁생산은 경기도 하남에 구축한 HK이노엔의 세포유전자치료제 센터에서 진행한다. HK이노엔은 자체 연구뿐 아니라 위탁생산(CMO)·위탁개발생산(CDMO) 등을 통해 세포유전자치료제센터를 혁신 플랫폼으로 키울 방침이다. - - expected: `⠍⠗⠓⠁⠠⠗⠶⠇⠒⠵⠀⠈⠻⠈⠕⠊⠥⠀⠚⠉⠢⠝⠀⠈` - - actual: `⠍⠗⠓⠁⠠⠗⠶⠇⠒⠵⠀⠈⠻⠈⠕⠊⠥⠀⠚⠉⠢⠝⠀⠈` +- `sentence_03.json` #50: 당시 CC(폐쇄)TV에 찍힌 장면을 보면 A씨는 엘리베이터를 기다리는 B씨를 발견하자 몰래 뒤로 다가가 갑자기 피해 여성의 머리를 뒤에서 돌려차기로 가격하는 등 폭행했다. + - expected: `⠊⠶⠠⠕⠀⠴⠠⠠⠉⠉⠦⠄⠙⠌⠠⠧⠗⠠⠴⠴⠠⠠⠞⠧` + - actual: `⠊⠶⠠⠕⠀⠴⠠⠠⠉⠉⠦⠄⠙⠌⠠⠧⠗⠠⠴⠴⠠⠠⠞⠧` - current primary/reason: `exact` / `exact` - `sentence_04.json` #133: 우크라이나 전쟁 피해 지원 방안을 논의하는 이 회의에는 프랑스·독일·이탈리아·스페인 등 법무부 장관 20여명과 국제형사재판소(ICC)·유럽연합(EU) 관계자 등이 참석했다. 지난달 18일 출국한 한 장관은 22일 귀국한다. - expected: `⠍⠋⠪⠐⠣⠕⠉⠀⠨⠾⠨⠗⠶⠀⠙⠕⠚⠗⠀⠨⠕⠏⠒⠀` @@ -1758,27 +1812,27 @@ Representative `mismatch` samples: - actual: `⠁⠈⠕⠠⠯⠦⠄⠴⠰⠠⠠⠃⠞⠠⠴⠀⠊⠪⠶⠀⠰⠎⠢⠊` - first differing cell (zero-based): 89 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_03.json` #50: 당시 CC(폐쇄)TV에 찍힌 장면을 보면 A씨는 엘리베이터를 기다리는 B씨를 발견하자 몰래 뒤로 다가가 갑자기 피해 여성의 머리를 뒤에서 돌려차기로 가격하는 등 폭행했다. - - expected: `⠙⠌⠠⠧⠗⠠⠴⠴⠠⠠⠞⠧⠲⠝⠀⠠⠨⠕⠁⠚⠟⠀⠨⠶` - - actual: `⠙⠌⠠⠧⠗⠠⠴⠴⠞⠧⠲⠝⠀⠠⠨⠕⠁⠚⠟⠀⠨⠶⠑⠡` - - first differing cell (zero-based): 20 +- `sentence_03.json` #311: 최근 한 달간 방산주 기업들 주가는 상승했다. 한화자산운용이 운용하는 상장지수펀드(ETF) ‘ARIRANG K방산Fn’는 이날 기준 1개월간 수익률이 24.26%에 달한다. + - expected: `⠑⠞⠋⠠⠴⠀⠠⠦⠴⠠⠠⠜⠊⠗⠁⠝⠛⠀⠰⠠⠅⠲⠘⠶` + - actual: `⠑⠞⠋⠠⠴⠀⠠⠦⠰⠠⠠⠜⠊⠗⠁⠝⠛⠀⠠⠅⠲⠘⠶⠇` + - first differing cell (zero-based): 88 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` - `sentence_04.json` #338: 체험에 앞서 간단한 O·X 퀴즈가 진행됐다. 액화석유가스(LPG)·액화천연가스(LNG)·부탄가스의 차이점을 설명하고 누출사고 시 대응 방법 등을 O·X로 답하는 방식이다. - - expected: `⠒⠊⠒⠚⠒⠀⠴⠠⠕⠐⠆⠴⠠⠭⠲⠀⠋⠍⠗⠨⠪⠫⠀⠨` - - actual: `⠒⠊⠒⠚⠒⠀⠴⠠⠠⠕⠲⠐⠆⠴⠭⠲⠀⠋⠍⠗⠨⠪⠫⠀` - - first differing cell (zero-based): 21 + - expected: `⠊⠒⠚⠒⠀⠴⠠⠕⠐⠆⠴⠠⠭⠲⠀⠋⠍⠗⠨⠪⠫⠀⠨⠟` + - actual: `⠊⠒⠚⠒⠀⠴⠠⠕⠲⠐⠆⠴⠠⠭⠲⠀⠋⠍⠗⠨⠪⠫⠀⠨` + - first differing cell (zero-based): 22 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` ### `korean_prefixed_closed_allcaps_parenthetical` -Of the 54492 candidates, 8003 are the actual `pending_rule_review` subcluster. The other 46489 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 54492 candidates, 7557 are the actual `pending_rule_review` subcluster. The other 46935 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. Mismatch primary-class distribution: -- `comparison_method`: 178 +- `comparison_method`: 186 - `corpus_suspect`: 993 -- `pending_rule_review`: 8003 -- `unsupported_character_review`: 131 +- `pending_rule_review`: 7557 +- `unsupported_character_review`: 123 Representative `exact` samples: @@ -1808,7 +1862,7 @@ Representative `mismatch` samples: - current primary/reason: `corpus_suspect` / `rule34_roman_indicator_before_opening_parenthesis` - `sentence_02.json` #10: 설동호 대전교육감은 “학교 교육과정 및 여건에 맞는 인공지능(AI)교육 선도학교 운영을 통해 AI·SW교육 기반 구축과 학생들과 교원들의 미래 디지털 역량이 함양될 것이다.”라고 말했다. - expected: `⠚⠗⠀⠴⠠⠠⠁⠊⠐⠆⠴⠠⠠⠎⠺⠲⠈⠬⠩⠁⠀⠈⠕⠘` - - actual: `⠚⠗⠀⠴⠠⠠⠁⠊⠲⠐⠆⠴⠎⠺⠲⠈⠬⠩⠁⠀⠈⠕⠘⠒` + - actual: `⠚⠗⠀⠴⠠⠠⠁⠊⠲⠐⠆⠴⠠⠠⠎⠺⠲⠈⠬⠩⠁⠀⠈⠕` - first differing cell (zero-based): 93 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` - `sentence_03.json` #1: 김 지사는 18일(이하 현지시각) 미국 코네티컷주 댄버리 린데 본사에서 산지브 람바(Sanjiv Lamba) 린데 회장, 성백석 린데코리아 회장, 조일교 아산시 부시장과 투자양해각서(MOU)을 체결했다. @@ -1824,21 +1878,21 @@ Representative `mismatch` samples: ### `korean_prefixed_closed_roman_annotation_rule_34_order` -Of the 64382 candidates, 9848 are the actual `pending_rule_review` subcluster. The other 54534 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 64382 candidates, 9294 are the actual `pending_rule_review` subcluster. The other 55088 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. -For this output-signature audit, 11340 mismatches were evaluable and 1112 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. +For this output-signature audit, 10789 mismatches were evaluable and 1116 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. Localized first-difference transitions: -- `U+2834 ⠴ -> U+2826 ⠦`: 1111 -- `U+2810 ⠐ -> U+2826 ⠦`: 1 +- `U+2834 ⠴ -> U+2826 ⠦`: 1114 +- `U+2810 ⠐ -> U+2826 ⠦`: 2 Mismatch primary-class distribution: -- `comparison_method`: 231 -- `corpus_suspect`: 1108 -- `pending_rule_review`: 9848 -- `unsupported_character_review`: 153 +- `comparison_method`: 241 +- `corpus_suspect`: 1111 +- `pending_rule_review`: 9294 +- `unsupported_character_review`: 143 Representative `exact` samples: @@ -1891,7 +1945,7 @@ Representative `mismatch` samples: - current primary/reason: `corpus_suspect` / `rule34_roman_indicator_before_opening_parenthesis` - `sentence_02.json` #10: 설동호 대전교육감은 “학교 교육과정 및 여건에 맞는 인공지능(AI)교육 선도학교 운영을 통해 AI·SW교육 기반 구축과 학생들과 교원들의 미래 디지털 역량이 함양될 것이다.”라고 말했다. - expected: `⠚⠗⠀⠴⠠⠠⠁⠊⠐⠆⠴⠠⠠⠎⠺⠲⠈⠬⠩⠁⠀⠈⠕⠘` - - actual: `⠚⠗⠀⠴⠠⠠⠁⠊⠲⠐⠆⠴⠎⠺⠲⠈⠬⠩⠁⠀⠈⠕⠘⠒` + - actual: `⠚⠗⠀⠴⠠⠠⠁⠊⠲⠐⠆⠴⠠⠠⠎⠺⠲⠈⠬⠩⠁⠀⠈⠕` - first differing cell (zero-based): 93 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` - `sentence_03.json` #1: 김 지사는 18일(이하 현지시각) 미국 코네티컷주 댄버리 린데 본사에서 산지브 람바(Sanjiv Lamba) 린데 회장, 성백석 린데코리아 회장, 조일교 아산시 부시장과 투자양해각서(MOU)을 체결했다. @@ -2001,17 +2055,17 @@ Representative `mismatch` samples: - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` - `sentence_02.json` #10: 설동호 대전교육감은 “학교 교육과정 및 여건에 맞는 인공지능(AI)교육 선도학교 운영을 통해 AI·SW교육 기반 구축과 학생들과 교원들의 미래 디지털 역량이 함양될 것이다.”라고 말했다. - expected: `⠚⠗⠀⠴⠠⠠⠁⠊⠐⠆⠴⠠⠠⠎⠺⠲⠈⠬⠩⠁⠀⠈⠕⠘` - - actual: `⠚⠗⠀⠴⠠⠠⠁⠊⠲⠐⠆⠴⠎⠺⠲⠈⠬⠩⠁⠀⠈⠕⠘⠒` + - actual: `⠚⠗⠀⠴⠠⠠⠁⠊⠲⠐⠆⠴⠠⠠⠎⠺⠲⠈⠬⠩⠁⠀⠈⠕` - first differing cell (zero-based): 93 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` - `sentence_03.json` #1112: 12일(현지시간) 미국 ABC·NBC방송 등에 따르면 유튜버이자 스카이다이버인 트레버 제이컵(29)는 지난 2021년 12월 자신의 유튜브 채널에 12분 47초짜리 비행 영상을 올렸다. - expected: `⠁⠀⠴⠠⠠⠁⠃⠉⠐⠆⠴⠠⠠⠝⠃⠉⠲⠘⠶⠠⠿⠀⠊⠪` - - actual: `⠁⠀⠴⠠⠠⠁⠃⠉⠲⠐⠆⠴⠝⠃⠉⠲⠘⠶⠠⠿⠀⠊⠪⠶` + - actual: `⠁⠀⠴⠠⠠⠁⠃⠉⠲⠐⠆⠴⠠⠠⠝⠃⠉⠲⠘⠶⠠⠿⠀⠊` - first differing cell (zero-based): 30 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` - `sentence_04.json` #2998: 독일 빌트암존탁이 여론조사기관에 의뢰해 지난 17~21일 유권자 1266명을 대상으로 실시한 여론조사에서 지지율 22%를 기록해 1위인 CDU·CSU(기독사회당) 지지율(26%)과 불과 4%포인트 차이를 나타냈다. - expected: `⠟⠀⠴⠠⠠⠉⠙⠥⠐⠆⠴⠠⠠⠉⠎⠥⠦⠄⠈⠕⠊⠭⠇⠚` - - actual: `⠟⠀⠴⠠⠠⠉⠙⠥⠲⠐⠆⠴⠉⠎⠥⠦⠄⠈⠕⠊⠭⠇⠚⠽` + - actual: `⠟⠀⠴⠠⠠⠉⠙⠥⠲⠐⠆⠴⠠⠠⠉⠎⠥⠦⠄⠈⠕⠊⠭⠇` - first differing cell (zero-based): 130 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` @@ -2046,18 +2100,18 @@ Representative `mismatch` samples: ### `pure_allcaps_segment_before_hyphen_and_multi_allcaps_segment_after` -Of the 448 candidates, 305 are the actual `pending_rule_review` subcluster. The other 143 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 448 candidates, 212 are the actual `pending_rule_review` subcluster. The other 236 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. -For this output-signature audit, 315 mismatches were evaluable and 1 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. +For this output-signature audit, 222 mismatches were evaluable and 3 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. Localized first-difference transitions: -- `U+2830 ⠰ -> U+2820 ⠠`: 1 +- `U+2830 ⠰ -> U+2820 ⠠`: 3 Mismatch primary-class distribution: - `corpus_suspect`: 8 -- `pending_rule_review`: 305 +- `pending_rule_review`: 212 - `unsupported_character_review`: 2 Representative `exact` samples: @@ -2081,10 +2135,15 @@ Representative `exact` samples: Representative `localized_mismatch` samples: -- `sentence_02.json` #15537: 한국투자증권은 자체 개발한 자산운용전략 시스템(K-ALM)을 기반으로 개별기업의 특성을 고려한 맞춤형 적립금운용 컨설팅(K-IPS)을 제공하는 등 확정급여(DB)형 퇴직연금의 자산운용 성과를 높여왔다. - - expected: `⠝⠢⠦⠄⠴⠠⠅⠤⠰⠠⠠⠁⠇⠍⠠⠴⠮⠀⠈⠕⠘⠒⠪⠐` - - actual: `⠝⠢⠦⠄⠴⠠⠅⠤⠠⠠⠁⠇⠍⠠⠴⠮⠀⠈⠕⠘⠒⠪⠐⠥` - - first differing cell (zero-based): 52 +- `sentence_02.json` #10282: 보안도 강화됐다. SD-WAN과 전용회선, 초연결교환(HCX)을 바탕으로 한 직접 연결 방식을 통해 회선 보안성을 높여 기업의 설계 데이터와 시험 결과 등 핵심 자료가 유출되지 않도록 했다. + - expected: `⠊⠧⠗⠌⠊⠲⠀⠴⠰⠠⠠⠎⠙⠤⠠⠠⠺⠁⠝⠲⠈⠧⠀⠨` + - actual: `⠊⠧⠗⠌⠊⠲⠀⠴⠠⠠⠎⠙⠤⠠⠠⠺⠁⠝⠲⠈⠧⠀⠨⠾` + - first differing cell (zero-based): 19 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_03.json` #15688: 한국미드아마추어골프연맹(KMAGF)이 주최, 주관하고 G-MAX가 공식 후원하는 KMAGF 2023 G-MAX 그린투어 1차전이 지난 25일 부산 기장군 해운대 컨트리클럽에서 열렸다. 2023년을 시작하는 KMAGF 첫 대회다. + - expected: `⠋⠀⠼⠃⠚⠃⠉⠀⠰⠠⠛⠤⠠⠠⠍⠁⠭⠲⠀⠈⠪⠐⠟⠓` + - actual: `⠋⠀⠼⠃⠚⠃⠉⠀⠠⠛⠔⠠⠠⠍⠁⠭⠀⠀⠈⠪⠐⠟⠓⠍` + - first differing cell (zero-based): 93 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` Representative `mismatch` samples: @@ -2099,10 +2158,10 @@ Representative `mismatch` samples: - actual: `⠥⠡⠈⠍⠊⠒⠐⠀⠀⠠⠁⠔⠠⠠⠎⠞⠁⠗⠀⠀⠑⠕⠆⠀` - first differing cell (zero-based): 122 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_03.json` #569: 새울원전 관계자는 “APR 1400의 유럽 수출 노형인 EU-APR은 유럽사업자요건(EUR) 인증을 취득했고, 미국 이외 국가로는 처음으로 미국 원자력규제위원회 설계인증을 받아 안전성과 기술력을 국제적으로 인증받고 있다”고 밝혔다. - - expected: `⠟⠀⠴⠠⠠⠑⠥⠤⠠⠠⠁⠏⠗⠲⠵⠀⠩⠐⠎⠃⠇⠎⠃⠨` - - actual: `⠟⠀⠴⠠⠠⠑⠥⠤⠁⠏⠗⠲⠵⠀⠩⠐⠎⠃⠇⠎⠃⠨⠣⠬` - - first differing cell (zero-based): 54 +- `sentence_03.json` #947: BMW코리아 미래재단이 ‘2023 서울안전한마당’에 이동식 에너지 저장소(ESS)인 ‘넥스트 그린 투-고(NEXT GREEN TO-GO)’ 부스를 마련하고 체험형 교육 프로그램을 운영한다. + - expected: `⠤⠈⠥⠦⠄⠴⠠⠠⠠⠝⠑⠭⠞⠀⠛⠗⠑⠢⠀⠞⠕⠤⠛⠠` + - actual: `⠤⠈⠥⠦⠄⠴⠠⠠⠝⠑⠭⠞⠀⠠⠠⠛⠗⠑⠢⠀⠠⠠⠞⠕` + - first differing cell (zero-based): 103 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` - `sentence_04.json` #883: 그룹 방탄소년단(BTS) 슈가의 첫 공식 솔로 음반 ‘D-데이’(D-DAY)가 발매 당일 한터차트 기준 107만장이 넘게 팔려나가며 첫날 판매량으로는 K팝 솔로 가수 최고 기록을 경신했다. - expected: `⠙⠲⠤⠊⠝⠕⠴⠄⠦⠄⠴⠠⠙⠤⠠⠠⠐⠙⠠⠴⠫⠀⠘⠂` @@ -2112,20 +2171,39 @@ Representative `mismatch` samples: ### `roman_hyphenated_word_after_whitespace_following_korean_word` -Of the 361 candidates, 350 are the actual `pending_rule_review` subcluster. The other 11 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 361 candidates, 264 are the actual `pending_rule_review` subcluster. The other 97 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. -For this output-signature audit, 361 mismatches were evaluable and 136 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. +For this output-signature audit, 275 mismatches were evaluable and 139 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. Localized first-difference transitions: -- `U+2834 ⠴ -> U+2800 ⠀`: 136 +- `U+2834 ⠴ -> U+2800 ⠀`: 139 Mismatch primary-class distribution: - `corpus_suspect`: 6 -- `pending_rule_review`: 350 +- `pending_rule_review`: 264 - `unsupported_character_review`: 5 +Representative `exact` samples: + +- `sentence_01.json` #52: 과학기술정보통신부와 개인정보보호위원회의 공동 고시 기준에 따라 한국인터넷진흥원(KISA)이 인증하는 ISMS-P는 고객 정보보호 및 개인정보보호를 위한 일련의 조치와 활동이 국가공인 인증기준에 적합함을 증명하는 제도다. + - expected: `⠈⠧⠚⠁⠈⠕⠠⠯⠨⠻⠘⠥⠓⠿⠠⠟⠘⠍⠧⠀⠈⠗⠟⠨` + - actual: `⠈⠧⠚⠁⠈⠕⠠⠯⠨⠻⠘⠥⠓⠿⠠⠟⠘⠍⠧⠀⠈⠗⠟⠨` + - current primary/reason: `exact` / `exact` +- `sentence_02.json` #798: 이어 홍성 광천에 위치한 K-POP고등학교 학생과 독일 현지 고등학생들이 함께 비티에스(BTS), 블랙핑크, 에스파 등 유명 아이돌 노래와 춤으로 무대를 화려하게 장식한다. + - expected: `⠕⠎⠀⠚⠿⠠⠻⠀⠈⠧⠶⠰⠾⠝⠀⠍⠗⠰⠕⠚⠒⠀⠴⠠` + - actual: `⠕⠎⠀⠚⠿⠠⠻⠀⠈⠧⠶⠰⠾⠝⠀⠍⠗⠰⠕⠚⠒⠀⠴⠠` + - current primary/reason: `exact` / `exact` +- `sentence_03.json` #569: 새울원전 관계자는 “APR 1400의 유럽 수출 노형인 EU-APR은 유럽사업자요건(EUR) 인증을 취득했고, 미국 이외 국가로는 처음으로 미국 원자력규제위원회 설계인증을 받아 안전성과 기술력을 국제적으로 인증받고 있다”고 밝혔다. + - expected: `⠠⠗⠯⠏⠒⠨⠾⠀⠈⠧⠒⠈⠌⠨⠉⠵⠀⠦⠴⠠⠠⠁⠏⠗` + - actual: `⠠⠗⠯⠏⠒⠨⠾⠀⠈⠧⠒⠈⠌⠨⠉⠵⠀⠦⠴⠠⠠⠁⠏⠗` + - current primary/reason: `exact` / `exact` +- `sentence_04.json` #937: 혼다 CR-V는 예상보다 크고 강한 차다. 준중형 스포츠유틸리티차(SUV)로 규정되지만, 동급에선 공간도 넉넉하고 충분한 힘도 갖췄다. 6년 만에 6세대 완전변경 모델로 힘과 덩치를 모두 키워서 돌아왔다. + - expected: `⠚⠷⠊⠀⠴⠠⠠⠉⠗⠤⠰⠠⠧⠲⠉⠵⠀⠌⠇⠶⠘⠥⠊⠀` + - actual: `⠚⠷⠊⠀⠴⠠⠠⠉⠗⠤⠰⠠⠧⠲⠉⠵⠀⠌⠇⠶⠘⠥⠊⠀` + - current primary/reason: `exact` / `exact` + Representative `localized_mismatch` samples: - `sentence_01.json` #567: KBO는 2월 8일부터 제주 서귀포시에서 대한야구소프트볼협회(KBSA)가 선발한 고교 입학 예정 우수선수 30명을 대상으로 Next-Level Training Camp 2차 훈련을 실시할 예정이다. @@ -2151,45 +2229,45 @@ Representative `localized_mismatch` samples: Representative `mismatch` samples: -- `sentence_01.json` #52: 과학기술정보통신부와 개인정보보호위원회의 공동 고시 기준에 따라 한국인터넷진흥원(KISA)이 인증하는 ISMS-P는 고객 정보보호 및 개인정보보호를 위한 일련의 조치와 활동이 국가공인 인증기준에 적합함을 증명하는 제도다. - - expected: `⠠⠠⠊⠎⠍⠎⠤⠰⠠⠏⠲⠉⠵⠀⠈⠥⠈⠗⠁⠀⠨⠻⠘⠥` - - actual: `⠠⠠⠊⠎⠍⠎⠤⠰⠏⠲⠉⠵⠀⠈⠥⠈⠗⠁⠀⠨⠻⠘⠥⠘` - - first differing cell (zero-based): 108 - - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_01.json` #149: 탠덤 OLED를 탄성있는 플라스틱 기판에 결합한 것이 LG디스플레이의 차량용 플라스틱(P)-OLED다. 차량용 P-OLED는 LCD 대비 소비전력을 60% 줄이고, 무게는 80%나 저감해 전기차 시대에도 적합한 디스플레이라는 평가다. + - expected: `⠮⠐⠣⠠⠪⠓⠕⠁⠴⠐⠣⠠⠏⠐⠜⠤⠠⠠⠕⠇⠫⠲⠊⠲` + - actual: `⠮⠐⠣⠠⠪⠓⠕⠁⠦⠄⠴⠠⠏⠠⠴⠤⠰⠠⠠⠕⠇⠫⠲⠊` + - first differing cell (zero-based): 87 + - current primary/reason: `corpus_suspect` / `rule34_roman_indicator_before_opening_parenthesis` - `sentence_02.json` #344: 한편 이장우 대전시장은 이틀간의 대만 방문을 마무리하고 싱가포르로 이동해 18일까지 Merck(머크) 사, 국립싱가포르대학 바이오연구단, A-STAR 및 바이오폴리스 등 바이오산업 관련 해외 기업과 연구기관을 방문할 예정이다. - expected: `⠥⠡⠈⠍⠊⠒⠐⠀⠴⠠⠁⠤⠠⠠⠌⠜⠲⠀⠑⠕⠆⠀⠘⠣` - actual: `⠥⠡⠈⠍⠊⠒⠐⠀⠀⠠⠁⠔⠠⠠⠎⠞⠁⠗⠀⠀⠑⠕⠆⠀` - first differing cell (zero-based): 122 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_03.json` #569: 새울원전 관계자는 “APR 1400의 유럽 수출 노형인 EU-APR은 유럽사업자요건(EUR) 인증을 취득했고, 미국 이외 국가로는 처음으로 미국 원자력규제위원회 설계인증을 받아 안전성과 기술력을 국제적으로 인증받고 있다”고 밝혔다. - - expected: `⠟⠀⠴⠠⠠⠑⠥⠤⠠⠠⠁⠏⠗⠲⠵⠀⠩⠐⠎⠃⠇⠎⠃⠨` - - actual: `⠟⠀⠴⠠⠠⠑⠥⠤⠁⠏⠗⠲⠵⠀⠩⠐⠎⠃⠇⠎⠃⠨⠣⠬` - - first differing cell (zero-based): 54 +- `sentence_03.json` #711: 비즈니스 프로젝터 EB-L260F는 최대 4700루멘(lm)의 선명한 밝기, 고해상도(Full HD) 해상도로 밝은 회의실이나 교실에서도 밝은 화면을 자랑한다. 이 제품은 중형 강당이나 미팅룸에서 사용하기 적합하다. + - expected: `⠎⠀⠴⠠⠠⠑⠃⠤⠠⠇⠼⠃⠋⠚⠠⠋⠲⠉⠵⠀⠰⠽⠊⠗` + - actual: `⠎⠀⠴⠠⠠⠑⠃⠤⠰⠠⠇⠼⠃⠋⠚⠠⠋⠲⠉⠵⠀⠰⠽⠊` + - first differing cell (zero-based): 25 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_04.json` #937: 혼다 CR-V는 예상보다 크고 강한 차다. 준중형 스포츠유틸리티차(SUV)로 규정되지만, 동급에선 공간도 넉넉하고 충분한 힘도 갖췄다. 6년 만에 6세대 완전변경 모델로 힘과 덩치를 모두 키워서 돌아왔다. - - expected: `⠀⠴⠠⠠⠉⠗⠤⠰⠠⠧⠲⠉⠵⠀⠌⠇⠶⠘⠥⠊⠀⠋⠪⠈` - - actual: `⠀⠴⠠⠠⠉⠗⠤⠰⠧⠲⠉⠵⠀⠌⠇⠶⠘⠥⠊⠀⠋⠪⠈⠥` - - first differing cell (zero-based): 11 +- `sentence_04.json` #1313: 정부가 지상화냐 지하화냐를 두고 1년 넘게 논란이 이어졌던 수도권광역급행철도(GTX)-C 노선 창동역 구간을 지하화하기로 최종 결정했다. 이에 따라 GTX-C 노선의 연내 착공 가능성이 높아졌다. + - expected: `⠃⠚⠗⠶⠰⠞⠊⠥⠦⠄⠴⠠⠠⠛⠞⠭⠠⠴⠤⠴⠠⠉⠲⠀` + - actual: `⠃⠚⠗⠶⠰⠞⠊⠥⠀⠀⠦⠠⠠⠛⠞⠭⠴⠔⠠⠉⠀⠉⠥⠠` + - first differing cell (zero-based): 79 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` ### `roman_parenthetical_headword_after_whitespace_following_korean_word` -Of the 4695 candidates, 1192 are the actual `pending_rule_review` subcluster. The other 3503 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 4695 candidates, 1054 are the actual `pending_rule_review` subcluster. The other 3641 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. -For this output-signature audit, 1284 mismatches were evaluable and 158 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. +For this output-signature audit, 1147 mismatches were evaluable and 163 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. Localized first-difference transitions: -- `U+2834 ⠴ -> U+2800 ⠀`: 154 +- `U+2834 ⠴ -> U+2800 ⠀`: 159 - `U+2834 ⠴ -> U+2830 ⠰`: 3 - `U+2820 ⠠ -> U+2834 ⠴`: 1 Mismatch primary-class distribution: -- `comparison_method`: 29 -- `corpus_suspect`: 39 -- `pending_rule_review`: 1192 -- `unsupported_character_review`: 24 +- `comparison_method`: 34 +- `corpus_suspect`: 40 +- `pending_rule_review`: 1054 +- `unsupported_character_review`: 19 Representative `exact` samples: @@ -2246,10 +2324,10 @@ Representative `mismatch` samples: - error: `Invalid symbol character` - first differing cell (zero-based): 0 - current primary/reason: `unsupported_character_review` / `unsupported_character_review` -- `sentence_03.json` #50: 당시 CC(폐쇄)TV에 찍힌 장면을 보면 A씨는 엘리베이터를 기다리는 B씨를 발견하자 몰래 뒤로 다가가 갑자기 피해 여성의 머리를 뒤에서 돌려차기로 가격하는 등 폭행했다. - - expected: `⠙⠌⠠⠧⠗⠠⠴⠴⠠⠠⠞⠧⠲⠝⠀⠠⠨⠕⠁⠚⠟⠀⠨⠶` - - actual: `⠙⠌⠠⠧⠗⠠⠴⠴⠞⠧⠲⠝⠀⠠⠨⠕⠁⠚⠟⠀⠨⠶⠑⠡` - - first differing cell (zero-based): 20 +- `sentence_03.json` #292: GH 주거 분야 전문인력과 HUG(주택도시보증공사), 변호사, 법무사 등 부동산·금융 전문인력이 상주하며 전세사기 피해자에게 부동산 법률, 긴급 금융지원, 주거지원 등 종합적인 맞춤형 상담을 제공한다. + - expected: `⠴⠠⠠⠛⠓⠲⠀⠨⠍⠈⠎⠀⠘⠛⠜⠀⠨⠾⠑⠛⠟⠐⠱⠁` + - actual: `⠴⠠⠠⠣⠲⠀⠨⠍⠈⠎⠀⠘⠛⠜⠀⠨⠾⠑⠛⠟⠐⠱⠁⠈` + - first differing cell (zero-based): 3 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` - `sentence_04.json` #191: SK온은 23일 서울 종로구 SK서린사옥에서 에코프로머티리얼즈, 중국의 GEM(거린메이)과 전구체 생산을 위한 3자 합작법인인 지이엠코리아뉴에너지머티리얼즈㈜(이하 지이엠코리아) 설립 양해각서(MOU)를 체결했다고 밝혔다. - expected: `⠴⠠⠠⠎⠅⠲⠷⠵⠀⠼⠃⠉⠕⠂⠀⠠⠎⠯⠀⠨⠿⠐⠥⠈` @@ -2260,22 +2338,22 @@ Representative `mismatch` samples: ### `roman_run_after_whitespace_following_closed_roman_enclosure` -Of the 1093 candidates, 617 are the actual `pending_rule_review` subcluster. The other 476 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 1093 candidates, 613 are the actual `pending_rule_review` subcluster. The other 480 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. -For this output-signature audit, 951 mismatches were evaluable and 251 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. +For this output-signature audit, 950 mismatches were evaluable and 258 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. Localized first-difference transitions: -- `U+2834 ⠴ -> U+2830 ⠰`: 245 -- `U+2803 ⠃ -> U+2820 ⠠`: 1 +- `U+2834 ⠴ -> U+2830 ⠰`: 254 - `U+2815 ⠕ -> U+2837 ⠷`: 1 -- `U+2817 ⠗ -> U+2820 ⠠`: 1 - `U+2820 ⠠ -> U+2805 ⠅`: 1 +- `U+2820 ⠠ -> U+280E ⠎`: 1 +- `U+2830 ⠰ -> U+2820 ⠠`: 1 Mismatch primary-class distribution: -- `corpus_suspect`: 327 -- `pending_rule_review`: 617 +- `corpus_suspect`: 330 +- `pending_rule_review`: 613 - `unsupported_character_review`: 7 Representative `exact` samples: @@ -2348,11 +2426,11 @@ Representative `mismatch` samples: Of the 577 candidates, 565 are the actual `pending_rule_review` subcluster. The other 12 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. -For this output-signature audit, 577 mismatches were evaluable and 428 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. +For this output-signature audit, 577 mismatches were evaluable and 450 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. Localized first-difference transitions: -- `U+2810 ⠐ -> U+2832 ⠲`: 428 +- `U+2810 ⠐ -> U+2832 ⠲`: 450 Mismatch primary-class distribution: @@ -2369,7 +2447,7 @@ Representative `localized_mismatch` samples: - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` - `sentence_02.json` #10: 설동호 대전교육감은 “학교 교육과정 및 여건에 맞는 인공지능(AI)교육 선도학교 운영을 통해 AI·SW교육 기반 구축과 학생들과 교원들의 미래 디지털 역량이 함양될 것이다.”라고 말했다. - expected: `⠚⠗⠀⠴⠠⠠⠁⠊⠐⠆⠴⠠⠠⠎⠺⠲⠈⠬⠩⠁⠀⠈⠕⠘` - - actual: `⠚⠗⠀⠴⠠⠠⠁⠊⠲⠐⠆⠴⠎⠺⠲⠈⠬⠩⠁⠀⠈⠕⠘⠒` + - actual: `⠚⠗⠀⠴⠠⠠⠁⠊⠲⠐⠆⠴⠠⠠⠎⠺⠲⠈⠬⠩⠁⠀⠈⠕` - first differing cell (zero-based): 93 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` - `sentence_03.json` #91: 지난 13일 브라질에서 우수제조관리기준(BGMP) 인증을 받으며 중남미시장 진출에도 첫발을 내디뎠다. BGMP 인증으로 HA·Ca필러가 브라질을 비롯한 남미 국가들에 진입하는 데 속도가 붙을 것이란 전망이다. @@ -2392,7 +2470,7 @@ Representative `mismatch` samples: - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` - `sentence_02.json` #10: 설동호 대전교육감은 “학교 교육과정 및 여건에 맞는 인공지능(AI)교육 선도학교 운영을 통해 AI·SW교육 기반 구축과 학생들과 교원들의 미래 디지털 역량이 함양될 것이다.”라고 말했다. - expected: `⠚⠗⠀⠴⠠⠠⠁⠊⠐⠆⠴⠠⠠⠎⠺⠲⠈⠬⠩⠁⠀⠈⠕⠘` - - actual: `⠚⠗⠀⠴⠠⠠⠁⠊⠲⠐⠆⠴⠎⠺⠲⠈⠬⠩⠁⠀⠈⠕⠘⠒` + - actual: `⠚⠗⠀⠴⠠⠠⠁⠊⠲⠐⠆⠴⠠⠠⠎⠺⠲⠈⠬⠩⠁⠀⠈⠕` - first differing cell (zero-based): 93 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` - `sentence_03.json` #91: 지난 13일 브라질에서 우수제조관리기준(BGMP) 인증을 받으며 중남미시장 진출에도 첫발을 내디뎠다. BGMP 인증으로 HA·Ca필러가 브라질을 비롯한 남미 국가들에 진입하는 데 속도가 붙을 것이란 전망이다. @@ -2408,9 +2486,9 @@ Representative `mismatch` samples: ### `rule69_ascii_unit_before_terminator_skipping_symbol` -Of the 440 candidates, 92 are the actual `pending_rule_review` subcluster. The other 348 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 440 candidates, 91 are the actual `pending_rule_review` subcluster. The other 349 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. -For this output-signature audit, 97 mismatches were evaluable and 16 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. +For this output-signature audit, 96 mismatches were evaluable and 16 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. Localized first-difference transitions: @@ -2424,7 +2502,7 @@ Mismatch primary-class distribution: - `comparison_method`: 3 - `corpus_suspect`: 1 -- `pending_rule_review`: 92 +- `pending_rule_review`: 91 - `unsupported_character_review`: 1 Representative `exact` samples: @@ -2530,14 +2608,14 @@ Representative `mismatch` samples: ### `spaced_comma_between_ascii_digit_runs` -Of the 217 candidates, 35 are the actual `pending_rule_review` subcluster. The other 182 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 217 candidates, 34 are the actual `pending_rule_review` subcluster. The other 183 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. -For this output-signature audit, 36 mismatches were evaluable and 0 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. +For this output-signature audit, 35 mismatches were evaluable and 0 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. Mismatch primary-class distribution: - `comparison_method`: 1 -- `pending_rule_review`: 35 +- `pending_rule_review`: 34 Representative `exact` samples: @@ -2562,7 +2640,7 @@ Representative `mismatch` samples: - `sentence_01.json` #3841: CT-P43의 오리지널 의약품인 스텔라라는 얀센(Janssen)이 개발한 인터루킨(IL)-12, 23 억제제다. 판상형 건선과 건선성 관절염, 크론병, 궤양성 대장염 등의 자가면역질환 치료제로 사용된다. - expected: `⠴⠠⠠⠉⠞⠤⠠⠏⠼⠙⠉⠺⠀⠥⠐⠕⠨⠕⠉⠞⠀⠺⠜⠁` - - actual: `⠴⠠⠠⠉⠞⠤⠰⠏⠼⠙⠉⠺⠀⠥⠐⠕⠨⠕⠉⠞⠀⠺⠜⠁` + - actual: `⠴⠠⠠⠉⠞⠤⠰⠠⠏⠼⠙⠉⠺⠀⠥⠐⠕⠨⠕⠉⠞⠀⠺⠜` - first differing cell (zero-based): 6 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` - `sentence_02.json` #537: 11일 Macker와 ZAYDA, 12에는 Killa Ton과 Bagagee Viphex13, 13일에는 EDM씬의 트렌드를 주도하는 쥬디(JOODY)와 아쉬코(ASHIKO)가 출연했다. @@ -2583,14 +2661,14 @@ Representative `mismatch` samples: ### `standalone_multi_character_uppercase_roman_word` -Of the 62411 candidates, 10312 are the actual `pending_rule_review` subcluster. The other 52099 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 62411 candidates, 9639 are the actual `pending_rule_review` subcluster. The other 52772 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. Mismatch primary-class distribution: -- `comparison_method`: 213 -- `corpus_suspect`: 1094 -- `pending_rule_review`: 10312 -- `unsupported_character_review`: 152 +- `comparison_method`: 227 +- `corpus_suspect`: 1097 +- `pending_rule_review`: 9639 +- `unsupported_character_review`: 138 Representative `exact` samples: @@ -2620,7 +2698,7 @@ Representative `mismatch` samples: - current primary/reason: `corpus_suspect` / `rule34_roman_indicator_before_opening_parenthesis` - `sentence_02.json` #10: 설동호 대전교육감은 “학교 교육과정 및 여건에 맞는 인공지능(AI)교육 선도학교 운영을 통해 AI·SW교육 기반 구축과 학생들과 교원들의 미래 디지털 역량이 함양될 것이다.”라고 말했다. - expected: `⠚⠗⠀⠴⠠⠠⠁⠊⠐⠆⠴⠠⠠⠎⠺⠲⠈⠬⠩⠁⠀⠈⠕⠘` - - actual: `⠚⠗⠀⠴⠠⠠⠁⠊⠲⠐⠆⠴⠎⠺⠲⠈⠬⠩⠁⠀⠈⠕⠘⠒` + - actual: `⠚⠗⠀⠴⠠⠠⠁⠊⠲⠐⠆⠴⠠⠠⠎⠺⠲⠈⠬⠩⠁⠀⠈⠕` - first differing cell (zero-based): 93 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` - `sentence_03.json` #1: 김 지사는 18일(이하 현지시각) 미국 코네티컷주 댄버리 린데 본사에서 산지브 람바(Sanjiv Lamba) 린데 회장, 성백석 린데코리아 회장, 조일교 아산시 부시장과 투자양해각서(MOU)을 체결했다. @@ -2638,11 +2716,11 @@ Representative `mismatch` samples: Of the 377 candidates, 376 are the actual `pending_rule_review` subcluster. The other 1 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. -For this output-signature audit, 377 mismatches were evaluable and 334 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. +For this output-signature audit, 377 mismatches were evaluable and 335 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. Localized first-difference transitions: -- `U+2800 ⠀ -> U+2807 ⠇`: 331 +- `U+2800 ⠀ -> U+2807 ⠇`: 332 - `U+280A ⠊ -> U+2807 ⠇`: 1 - `U+2818 ⠘ -> U+2807 ⠇`: 1 - `U+2829 ⠩ -> U+2807 ⠇`: 1 @@ -2677,24 +2755,23 @@ Representative `mismatch` samples: ### `uppercase_alphanumeric_roman_digit_sequence` -Of the 3429 candidates, 1403 are the actual `pending_rule_review` subcluster. The other 2026 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 3429 candidates, 1223 are the actual `pending_rule_review` subcluster. The other 2206 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. -For this output-signature audit, 1512 mismatches were evaluable and 151 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. +For this output-signature audit, 1333 mismatches were evaluable and 150 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. Localized first-difference transitions: -- `U+2834 ⠴ -> U+2800 ⠀`: 93 +- `U+2834 ⠴ -> U+2800 ⠀`: 95 - `U+2834 ⠴ -> U+2830 ⠰`: 52 -- `U+2820 ⠠ -> U+2819 ⠙`: 2 - `U+2834 ⠴ -> U+2820 ⠠`: 2 - `U+2800 ⠀ -> U+2807 ⠇`: 1 Mismatch primary-class distribution: -- `comparison_method`: 13 -- `corpus_suspect`: 87 -- `pending_rule_review`: 1403 -- `unsupported_character_review`: 9 +- `comparison_method`: 14 +- `corpus_suspect`: 88 +- `pending_rule_review`: 1223 +- `unsupported_character_review`: 8 Representative `exact` samples: @@ -2741,9 +2818,9 @@ Representative `localized_mismatch` samples: Representative `mismatch` samples: - `sentence_01.json` #17: 지난달 30일(현지시각) 뉴욕증권거래소(NYSE)에서 다우존스30산업평균지수는 전 거래일보다 73.55포인트(0.22%) 하락한 3만3147.25로 마감했다. 대형주 중심의 S&P500지수는 9.78포인트(0.25%) 하락한 3839.50으로, 기술주 중심의 나스닥지수는 11.61포인트(0.11%) 하락한 1만0466.4 - - expected: `⠶⠠⠕⠢⠺⠀⠴⠠⠎⠈⠯⠠⠏⠼⠑⠚⠚⠨⠕⠠⠍⠉⠵⠀` - - actual: `⠶⠠⠕⠢⠺⠀⠴⠠⠠⠎⠲⠴⠈⠯⠲⠴⠏⠼⠑⠚⠚⠨⠕⠠` - - first differing cell (zero-based): 168 + - expected: `⠠⠕⠢⠺⠀⠴⠠⠎⠈⠯⠠⠏⠼⠑⠚⠚⠨⠕⠠⠍⠉⠵⠀⠼` + - actual: `⠠⠕⠢⠺⠀⠴⠠⠎⠲⠴⠈⠯⠲⠴⠠⠏⠼⠑⠚⠚⠨⠕⠠⠍` + - first differing cell (zero-based): 169 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` - `sentence_02.json` #324: 남녀단식 TT1 ~ TT10(지체), T11(지적), DF(청각) 등 12개 세부 종목에서 랭킹 포인트 60점을 걸고 승부를 펼친 결과 총 24명의 우승자가 탄생했다. - expected: `⠀⠴⠠⠠⠞⠞⠼⠁⠈⠔⠠⠠⠞⠞⠼⠁⠚⠦⠄⠨⠕⠰⠝⠠` @@ -2763,20 +2840,20 @@ Representative `mismatch` samples: ### `uppercase_ascii_run_immediately_after_digit_in_roman_sequence` -Of the 1896 candidates, 733 are the actual `pending_rule_review` subcluster. The other 1163 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 1896 candidates, 576 are the actual `pending_rule_review` subcluster. The other 1320 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. -For this output-signature audit, 790 mismatches were evaluable and 1 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. +For this output-signature audit, 634 mismatches were evaluable and 2 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. Localized first-difference transitions: -- `U+2830 ⠰ -> U+2820 ⠠`: 1 +- `U+2830 ⠰ -> U+2820 ⠠`: 2 Mismatch primary-class distribution: -- `comparison_method`: 9 -- `corpus_suspect`: 41 -- `pending_rule_review`: 733 -- `unsupported_character_review`: 7 +- `comparison_method`: 10 +- `corpus_suspect`: 42 +- `pending_rule_review`: 576 +- `unsupported_character_review`: 6 Representative `exact` samples: @@ -2799,10 +2876,10 @@ Representative `exact` samples: Representative `localized_mismatch` samples: -- `sentence_03.json` #24404: 또 가상통화 헤지펀드 스리애로우스캐피털(3AC), 코인 중개·대부업체 보이저 디지털, 대형 가상통화 거래소 FTX 등의 연쇄 파산으로 이어졌다. - - expected: `⠕⠓⠞⠦⠄⠼⠉⠴⠰⠠⠠⠁⠉⠠⠴⠐⠀⠋⠥⠟⠀⠨⠍⠶` - - actual: `⠕⠓⠞⠦⠄⠼⠉⠴⠠⠠⠁⠉⠠⠴⠐⠀⠋⠥⠟⠀⠨⠍⠶⠈` - - first differing cell (zero-based): 42 +- `sentence_03.json` #8207: 대한건축사협회가 주최하는 ‘한국건축산업대전(KAFF)’은 2006년부터 시작된 국내 최대 B2B·B2G(기업-정부간거래) 중심 건축자재·설비·기술 전문 전시회로, 올해는 관련 기업 100여개사가 참가해 코로나19 이후 최대 규모로 개최됐다. + - expected: `⠊⠗⠀⠴⠠⠃⠼⠃⠰⠠⠃⠐⠆⠴⠠⠃⠼⠃⠰⠠⠛⠦⠄⠈` + - actual: `⠊⠗⠀⠴⠠⠃⠼⠃⠠⠃⠲⠐⠆⠴⠠⠃⠼⠃⠠⠛⠦⠄⠈⠕` + - first differing cell (zero-based): 97 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` Representative `mismatch` samples: @@ -2830,26 +2907,26 @@ Representative `mismatch` samples: ### `uppercase_ascii_run_immediately_after_hyphen_in_roman_sequence` -Of the 952 candidates, 765 are the actual `pending_rule_review` subcluster. The other 187 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 952 candidates, 660 are the actual `pending_rule_review` subcluster. The other 292 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. -For this output-signature audit, 786 mismatches were evaluable and 163 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. +For this output-signature audit, 681 mismatches were evaluable and 189 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. Localized first-difference transitions: -- `U+2820 ⠠ -> U+2830 ⠰`: 159 -- `U+2830 ⠰ -> U+2820 ⠠`: 4 +- `U+2820 ⠠ -> U+2830 ⠰`: 183 +- `U+2830 ⠰ -> U+2820 ⠠`: 6 Mismatch primary-class distribution: - `corpus_suspect`: 12 -- `pending_rule_review`: 765 +- `pending_rule_review`: 660 - `unsupported_character_review`: 9 Representative `exact` samples: -- `sentence_01.json` #54: 에버소울은 출시 전부터 지스타(G-STAR) 2022, 에이지에프(AGF) 2022 등 굵직한 국내 게임 및 서브컬처 행사에 출품해 기대를 모았다. 12월 말에는 사전 예약자 150만 명을 모객했다. - - expected: `⠝⠘⠎⠠⠥⠯⠵⠀⠰⠯⠠⠕⠀⠨⠾⠘⠍⠓⠎⠀⠨⠕⠠⠪` - - actual: `⠝⠘⠎⠠⠥⠯⠵⠀⠰⠯⠠⠕⠀⠨⠾⠘⠍⠓⠎⠀⠨⠕⠠⠪` +- `sentence_01.json` #52: 과학기술정보통신부와 개인정보보호위원회의 공동 고시 기준에 따라 한국인터넷진흥원(KISA)이 인증하는 ISMS-P는 고객 정보보호 및 개인정보보호를 위한 일련의 조치와 활동이 국가공인 인증기준에 적합함을 증명하는 제도다. + - expected: `⠈⠧⠚⠁⠈⠕⠠⠯⠨⠻⠘⠥⠓⠿⠠⠟⠘⠍⠧⠀⠈⠗⠟⠨` + - actual: `⠈⠧⠚⠁⠈⠕⠠⠯⠨⠻⠘⠥⠓⠿⠠⠟⠘⠍⠧⠀⠈⠗⠟⠨` - current primary/reason: `exact` / `exact` - `sentence_02.json` #313: 순천향대(총장 김승우)는 지난 3일 한국국제협력단(KOICA)과 함께 우즈베키스탄의 수도 타슈겐트에서 스타트업 지원센터 ‘U-ENTER(Uzbekistan Entrepreneurship Innovation Center)’ 준공식을 개최했다고 밝혔다. - expected: `⠠⠛⠰⠾⠚⠜⠶⠊⠗⠦⠄⠰⠿⠨⠶⠀⠈⠕⠢⠠⠪⠶⠍⠠` @@ -2859,9 +2936,9 @@ Representative `exact` samples: - expected: `⠣⠯⠐⠎⠀⠨⠝⠨⠍⠊⠥⠈⠗⠘⠂⠈⠿⠇⠉⠵⠀⠈⠍⠁` - actual: `⠣⠯⠐⠎⠀⠨⠝⠨⠍⠊⠥⠈⠗⠘⠂⠈⠿⠇⠉⠵⠀⠈⠍⠁` - current primary/reason: `exact` / `exact` -- `sentence_04.json` #1164: 3일 서울시에 따르면 서울시와 국토교통부는 이러한 내용을 담은 ‘K-UAM(도심항공교통) 그랜드챌린지 실증운행계획’을 이달 중순 발표한다. UAM은 드론 택시처럼 하늘을 이용한 교통수단이다. - - expected: `⠼⠉⠕⠂⠀⠠⠎⠯⠠⠕⠝⠀⠠⠊⠐⠪⠑⠡⠀⠠⠎⠯⠠⠕` - - actual: `⠼⠉⠕⠂⠀⠠⠎⠯⠠⠕⠝⠀⠠⠊⠐⠪⠑⠡⠀⠠⠎⠯⠠⠕` +- `sentence_04.json` #937: 혼다 CR-V는 예상보다 크고 강한 차다. 준중형 스포츠유틸리티차(SUV)로 규정되지만, 동급에선 공간도 넉넉하고 충분한 힘도 갖췄다. 6년 만에 6세대 완전변경 모델로 힘과 덩치를 모두 키워서 돌아왔다. + - expected: `⠚⠷⠊⠀⠴⠠⠠⠉⠗⠤⠰⠠⠧⠲⠉⠵⠀⠌⠇⠶⠘⠥⠊⠀` + - actual: `⠚⠷⠊⠀⠴⠠⠠⠉⠗⠤⠰⠠⠧⠲⠉⠵⠀⠌⠇⠶⠘⠥⠊⠀` - current primary/reason: `exact` / `exact` Representative `localized_mismatch` samples: @@ -2889,20 +2966,20 @@ Representative `localized_mismatch` samples: Representative `mismatch` samples: -- `sentence_01.json` #52: 과학기술정보통신부와 개인정보보호위원회의 공동 고시 기준에 따라 한국인터넷진흥원(KISA)이 인증하는 ISMS-P는 고객 정보보호 및 개인정보보호를 위한 일련의 조치와 활동이 국가공인 인증기준에 적합함을 증명하는 제도다. - - expected: `⠠⠠⠊⠎⠍⠎⠤⠰⠠⠏⠲⠉⠵⠀⠈⠥⠈⠗⠁⠀⠨⠻⠘⠥` - - actual: `⠠⠠⠊⠎⠍⠎⠤⠰⠏⠲⠉⠵⠀⠈⠥⠈⠗⠁⠀⠨⠻⠘⠥⠘` - - first differing cell (zero-based): 108 - - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_01.json` #149: 탠덤 OLED를 탄성있는 플라스틱 기판에 결합한 것이 LG디스플레이의 차량용 플라스틱(P)-OLED다. 차량용 P-OLED는 LCD 대비 소비전력을 60% 줄이고, 무게는 80%나 저감해 전기차 시대에도 적합한 디스플레이라는 평가다. + - expected: `⠮⠐⠣⠠⠪⠓⠕⠁⠴⠐⠣⠠⠏⠐⠜⠤⠠⠠⠕⠇⠫⠲⠊⠲` + - actual: `⠮⠐⠣⠠⠪⠓⠕⠁⠦⠄⠴⠠⠏⠠⠴⠤⠰⠠⠠⠕⠇⠫⠲⠊` + - first differing cell (zero-based): 87 + - current primary/reason: `corpus_suspect` / `rule34_roman_indicator_before_opening_parenthesis` - `sentence_02.json` #344: 한편 이장우 대전시장은 이틀간의 대만 방문을 마무리하고 싱가포르로 이동해 18일까지 Merck(머크) 사, 국립싱가포르대학 바이오연구단, A-STAR 및 바이오폴리스 등 바이오산업 관련 해외 기업과 연구기관을 방문할 예정이다. - expected: `⠥⠡⠈⠍⠊⠒⠐⠀⠴⠠⠁⠤⠠⠠⠌⠜⠲⠀⠑⠕⠆⠀⠘⠣` - actual: `⠥⠡⠈⠍⠊⠒⠐⠀⠀⠠⠁⠔⠠⠠⠎⠞⠁⠗⠀⠀⠑⠕⠆⠀` - first differing cell (zero-based): 122 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_03.json` #569: 새울원전 관계자는 “APR 1400의 유럽 수출 노형인 EU-APR은 유럽사업자요건(EUR) 인증을 취득했고, 미국 이외 국가로는 처음으로 미국 원자력규제위원회 설계인증을 받아 안전성과 기술력을 국제적으로 인증받고 있다”고 밝혔다. - - expected: `⠟⠀⠴⠠⠠⠑⠥⠤⠠⠠⠁⠏⠗⠲⠵⠀⠩⠐⠎⠃⠇⠎⠃⠨` - - actual: `⠟⠀⠴⠠⠠⠑⠥⠤⠁⠏⠗⠲⠵⠀⠩⠐⠎⠃⠇⠎⠃⠨⠣⠬` - - first differing cell (zero-based): 54 +- `sentence_03.json` #669: 환자들은 수술 후 컴퓨터 단층촬영(CT)이나 자기공명영상(MRI)을 통해 검사를 진행했다. 이후 추적관찰에선 X선 촬영(X-Ray)을 활용했고 시각통증척도(VAS)를 비롯한 설문조사도 함께 실시했다. + - expected: `⠂⠻⠦⠄⠴⠠⠭⠤⠠⠗⠁⠽⠠⠴⠮⠀⠚⠧⠂⠬⠶⠚⠗⠌` + - actual: `⠂⠻⠦⠄⠴⠠⠭⠤⠰⠠⠗⠁⠽⠠⠴⠮⠀⠚⠧⠂⠬⠶⠚⠗` + - first differing cell (zero-based): 125 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` - `sentence_04.json` #883: 그룹 방탄소년단(BTS) 슈가의 첫 공식 솔로 음반 ‘D-데이’(D-DAY)가 발매 당일 한터차트 기준 107만장이 넘게 팔려나가며 첫날 판매량으로는 K팝 솔로 가수 최고 기록을 경신했다. - expected: `⠙⠲⠤⠊⠝⠕⠴⠄⠦⠄⠴⠠⠙⠤⠠⠠⠐⠙⠠⠴⠫⠀⠘⠂` @@ -2912,70 +2989,64 @@ Representative `mismatch` samples: ### `uppercase_ascii_segments_joined_by_ampersand_capitalization` -Of the 439 candidates, 415 are the actual `pending_rule_review` subcluster. The other 24 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 439 candidates, 96 are the actual `pending_rule_review` subcluster. The other 343 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. -For this output-signature audit, 438 mismatches were evaluable and 220 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. +For this output-signature audit, 119 mismatches were evaluable and 1 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. Localized first-difference transitions: -- `U+2817 ⠗ -> U+2820 ⠠`: 99 -- `U+2820 ⠠ -> U+281B ⠛`: 35 -- `U+280D ⠍ -> U+2820 ⠠`: 18 -- `U+280E ⠎ -> U+2820 ⠠`: 18 -- `U+280B ⠋ -> U+2820 ⠠`: 11 +- `U+280E ⠎ -> U+2829 ⠩`: 1 Mismatch primary-class distribution: +- `comparison_method`: 11 - `corpus_suspect`: 10 -- `pending_rule_review`: 415 -- `unsupported_character_review`: 13 +- `pending_rule_review`: 96 +- `unsupported_character_review`: 2 Representative `exact` samples: -- `sentence_03.json` #2759: 인턴으로 채용된 외국인 유학생들은 오는 7월 20일부터 8월 17일까지 약 한달 동안 R&D(연구개발)·마케팅·생산기술·Staff(경영기획/재무, 구매, HR) 등 다양한 직군에서 실무 경험을 쌓을 수 있다. - - expected: `⠟⠓⠾⠪⠐⠥⠀⠰⠗⠬⠶⠊⠽⠒⠀⠽⠈⠍⠁⠟⠀⠩⠚⠁` - - actual: `⠟⠓⠾⠪⠐⠥⠀⠰⠗⠬⠶⠊⠽⠒⠀⠽⠈⠍⠁⠟⠀⠩⠚⠁` +- `sentence_01.json` #858: 이에 따라 SK하이닉스가 설비투자(CAPEX) 규모를 올해 50%이상 감축하지만, S&P는 SK하이닉스가 투자 축소만으로 한계가 있다고 지적했다. + - expected: `⠕⠝⠀⠠⠊⠐⠣⠀⠴⠠⠠⠎⠅⠲⠚⠣⠕⠉⠕⠁⠠⠪⠫⠀` + - actual: `⠕⠝⠀⠠⠊⠐⠣⠀⠴⠠⠠⠎⠅⠲⠚⠣⠕⠉⠕⠁⠠⠪⠫⠀` + - current primary/reason: `exact` / `exact` +- `sentence_02.json` #146: KT&G(사장 백복인)가 산업정책연구원(IPS)이 주관하고 윤경 ESG 포럼이 주최하는 ‘윤경 ESG 포럼 CEO 서약식’에 참여해 윤리경영 의지를 다졌다. + - expected: `⠴⠠⠠⠅⠞⠈⠯⠠⠛⠦⠄⠇⠨⠶⠀⠘⠗⠁⠘⠭⠟⠠⠴⠫` + - actual: `⠴⠠⠠⠅⠞⠈⠯⠠⠛⠦⠄⠇⠨⠶⠀⠘⠗⠁⠘⠭⠟⠠⠴⠫` + - current primary/reason: `exact` / `exact` +- `sentence_03.json` #647: 온유는 지난 3월 6일 첫 정규 앨범 ‘써클(Circle)’로 컴백한다. 이번 앨범은 몽환적인 R&B 장르의 타이틀곡 ‘O(Circle)’(써클)을 비롯한 다채로운 분위기의 10곡으로 구성됐다. + - expected: `⠷⠩⠉⠵⠀⠨⠕⠉⠒⠀⠼⠉⠏⠂⠀⠼⠋⠕⠂⠀⠰⠎⠄⠀` + - actual: `⠷⠩⠉⠵⠀⠨⠕⠉⠒⠀⠼⠉⠏⠂⠀⠼⠋⠕⠂⠀⠰⠎⠄⠀` + - current primary/reason: `exact` / `exact` +- `sentence_04.json` #263: 김 부위원장은 투자은행(IB)의 기업 신용 공여, 합병 제도 등 기업의 M&A와 관련한 다른 제도의 불합리한 규제도 정비하고 기업구조혁신펀드도 추가로 조성하겠다고 밝혔다. + - expected: `⠈⠕⠢⠀⠘⠍⠍⠗⠏⠒⠨⠶⠵⠀⠓⠍⠨⠣⠵⠚⠗⠶⠦⠄` + - actual: `⠈⠕⠢⠀⠘⠍⠍⠗⠏⠒⠨⠶⠵⠀⠓⠍⠨⠣⠵⠚⠗⠶⠦⠄` - current primary/reason: `exact` / `exact` Representative `localized_mismatch` samples: -- `sentence_01.json` #858: 이에 따라 SK하이닉스가 설비투자(CAPEX) 규모를 올해 50%이상 감축하지만, S&P는 SK하이닉스가 투자 축소만으로 한계가 있다고 지적했다. - - expected: `⠨⠕⠑⠒⠐⠀⠴⠠⠎⠈⠯⠠⠏⠲⠉⠵⠀⠴⠠⠠⠎⠅⠲⠚` - - actual: `⠨⠕⠑⠒⠐⠀⠴⠠⠠⠎⠈⠯⠏⠲⠉⠵⠀⠴⠠⠠⠎⠅⠲⠚` - - first differing cell (zero-based): 80 - - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_02.json` #226: 대한민국 드론·UAM 박람회는 UAM(도심항공교통) 등 차세대 국토교통 R&D와 주요 지자체의 관련 사업을 살펴볼 수 있는 의미 있는 행사로 국토교통부와 인천광역시가 공동 주최한다. - - expected: `⠥⠈⠬⠓⠿⠀⠴⠠⠗⠈⠯⠠⠙⠲⠧⠀⠨⠍⠬⠀⠨⠕⠨⠰` - - actual: `⠥⠈⠬⠓⠿⠀⠴⠠⠠⠗⠈⠯⠙⠲⠧⠀⠨⠍⠬⠀⠨⠕⠨⠰` - - first differing cell (zero-based): 81 - - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_03.json` #370: 26일 GS칼텍스는 서울 강남구 GS타워에서 이승훈 GS칼텍스 S&T본부장, 박진기 HMM 총괄부사장 등이 참석한 가운데 친환경 바이오선박유 사업 업무협약(MOU)을 체결했다고 밝혔다. - - expected: `⠓⠝⠁⠠⠪⠀⠴⠠⠎⠈⠯⠠⠞⠲⠘⠷⠘⠍⠨⠶⠐⠀⠘⠁` - - actual: `⠓⠝⠁⠠⠪⠀⠴⠠⠠⠎⠈⠯⠞⠲⠘⠷⠘⠍⠨⠶⠐⠀⠘⠁` - - first differing cell (zero-based): 69 - - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_04.json` #263: 김 부위원장은 투자은행(IB)의 기업 신용 공여, 합병 제도 등 기업의 M&A와 관련한 다른 제도의 불합리한 규제도 정비하고 기업구조혁신펀드도 추가로 조성하겠다고 밝혔다. - - expected: `⠈⠕⠎⠃⠺⠀⠴⠠⠍⠈⠯⠠⠁⠲⠧⠀⠈⠧⠒⠐⠡⠚⠒⠀` - - actual: `⠈⠕⠎⠃⠺⠀⠴⠠⠠⠍⠈⠯⠁⠲⠧⠀⠈⠧⠒⠐⠡⠚⠒⠀` - - first differing cell (zero-based): 70 +- `sentence_02.json` #1061: SH&E 아카데미에서는 온라인과 오프라인 형태로 16개의 교육 과정이 진행된다. 화학류와 가스류, 소방안전을 비롯해 국제표준화기구(ISO) 인증 안전교육도 포함된다. + - expected: `⠴⠠⠠⠎⠓⠈⠯⠠⠑⠲⠀⠣⠋⠊⠝⠑⠕⠝⠠⠎⠉⠵⠀⠷` + - actual: `⠴⠠⠠⠩⠈⠯⠠⠑⠲⠀⠣⠋⠊⠝⠑⠕⠝⠠⠎⠉⠵⠀⠷⠐` + - first differing cell (zero-based): 3 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` Representative `mismatch` samples: -- `sentence_01.json` #858: 이에 따라 SK하이닉스가 설비투자(CAPEX) 규모를 올해 50%이상 감축하지만, S&P는 SK하이닉스가 투자 축소만으로 한계가 있다고 지적했다. - - expected: `⠨⠕⠑⠒⠐⠀⠴⠠⠎⠈⠯⠠⠏⠲⠉⠵⠀⠴⠠⠠⠎⠅⠲⠚` - - actual: `⠨⠕⠑⠒⠐⠀⠴⠠⠠⠎⠈⠯⠏⠲⠉⠵⠀⠴⠠⠠⠎⠅⠲⠚` - - first differing cell (zero-based): 80 +- `sentence_01.json` #1164: 위메이드의 위믹스가 국내 가상자산 거래소 코인원에 재상장되는 가운데, 자회사 블루포션게임즈가 위메이드와 P&E(Play and Earn) 사업을 위한 업무협약(MOU)을 체결한 것이 부각되고 있다. + - expected: `⠜⠁⠦⠄⠴⠠⠠⠍⠕⠥⠠⠴⠮⠀⠰⠝⠈⠳⠚⠒⠀⠸⠎⠕` + - actual: `⠜⠁⠦⠄⠴⠠⠠⠍⠳⠠⠴⠮⠀⠰⠝⠈⠳⠚⠒⠀⠸⠎⠕⠀` + - first differing cell (zero-based): 142 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_02.json` #146: KT&G(사장 백복인)가 산업정책연구원(IPS)이 주관하고 윤경 ESG 포럼이 주최하는 ‘윤경 ESG 포럼 CEO 서약식’에 참여해 윤리경영 의지를 다졌다. - - expected: `⠴⠠⠠⠅⠞⠈⠯⠠⠛⠦⠄⠇⠨⠶⠀⠘⠗⠁⠘⠭⠟⠠⠴⠫` - - actual: `⠴⠠⠠⠅⠞⠈⠯⠛⠦⠄⠇⠨⠶⠀⠘⠗⠁⠘⠭⠟⠠⠴⠫⠀` - - first differing cell (zero-based): 7 +- `sentence_02.json` #455: 먼저, 세계 최고의 R&D인프라와 인력을 갖춘 장점을 활용하여 국가첨단반도체 기술센터(ASTC)를 유치하고 대전을 반도체 연구·교육·실증 거점으로 조성할 계획이다. + - expected: `⠓⠎⠦⠄⠴⠠⠠⠁⠎⠞⠉⠠⠴⠐⠮⠀⠩⠰⠕⠚⠈⠥⠀⠊` + - actual: `⠓⠎⠦⠄⠴⠠⠠⠁⠌⠉⠠⠴⠐⠮⠀⠩⠰⠕⠚⠈⠥⠀⠊⠗` + - first differing cell (zero-based): 90 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` - `sentence_03.json` #370: 26일 GS칼텍스는 서울 강남구 GS타워에서 이승훈 GS칼텍스 S&T본부장, 박진기 HMM 총괄부사장 등이 참석한 가운데 친환경 바이오선박유 사업 업무협약(MOU)을 체결했다고 밝혔다. - - expected: `⠓⠝⠁⠠⠪⠀⠴⠠⠎⠈⠯⠠⠞⠲⠘⠷⠘⠍⠨⠶⠐⠀⠘⠁` - - actual: `⠓⠝⠁⠠⠪⠀⠴⠠⠠⠎⠈⠯⠞⠲⠘⠷⠘⠍⠨⠶⠐⠀⠘⠁` - - first differing cell (zero-based): 69 + - expected: `⠜⠁⠦⠄⠴⠠⠠⠍⠕⠥⠠⠴⠮⠀⠰⠝⠈⠳⠚⠗⠌⠊⠈⠥` + - actual: `⠜⠁⠦⠄⠴⠠⠠⠍⠳⠠⠴⠮⠀⠰⠝⠈⠳⠚⠗⠌⠊⠈⠥⠀` + - first differing cell (zero-based): 164 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` - `sentence_04.json` #19: 이날 행사에는 구 대표 외에 권봉석 (주)LG 최고운영책임자(COO), 박일평 LG사이언스파크대표(사장)를 비롯해 각 계열사 최고기술책임자(CTO), 최고데이터책임자(CDO), 최고인사책임자(CHO) 등이 참석해 국내 이공계 R&D 인재 400여명과 만났다. - expected: `⠁⠕⠢⠨⠦⠄⠴⠠⠠⠉⠕⠕⠠⠴⠐⠀⠘⠁⠕⠂⠙⠻⠀⠴` @@ -2985,13 +3056,13 @@ Representative `mismatch` samples: ### `uppercase_roman_headword_closed_multiword_parenthetical` -Of the 175 candidates, 147 are the actual `pending_rule_review` subcluster. The other 28 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 175 candidates, 146 are the actual `pending_rule_review` subcluster. The other 29 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. Mismatch primary-class distribution: - `comparison_method`: 1 - `corpus_suspect`: 5 -- `pending_rule_review`: 147 +- `pending_rule_review`: 146 - `unsupported_character_review`: 6 Representative `exact` samples: @@ -3038,9 +3109,9 @@ Representative `mismatch` samples: ### `uppercase_roman_run_followed_by_hyphen_digits` -Of the 571 candidates, 365 are the actual `pending_rule_review` subcluster. The other 206 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 571 candidates, 362 are the actual `pending_rule_review` subcluster. The other 209 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. -For this output-signature audit, 380 mismatches were evaluable and 262 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. +For this output-signature audit, 377 mismatches were evaluable and 262 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. Localized first-difference transitions: @@ -3050,7 +3121,7 @@ Localized first-difference transitions: Mismatch primary-class distribution: - `corpus_suspect`: 12 -- `pending_rule_review`: 365 +- `pending_rule_review`: 362 - `unsupported_character_review`: 3 Representative `exact` samples: @@ -3097,9 +3168,9 @@ Representative `mismatch` samples: ### `uppercase_word_after_whitespace_continuing_ascii_roman_text` -Of the 1729 candidates, 952 are the actual `pending_rule_review` subcluster. The other 777 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 1729 candidates, 926 are the actual `pending_rule_review` subcluster. The other 803 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. -For this output-signature audit, 1030 mismatches were evaluable and 4 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. +For this output-signature audit, 1004 mismatches were evaluable and 4 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. Localized first-difference transitions: @@ -3111,7 +3182,7 @@ Mismatch primary-class distribution: - `comparison_method`: 9 - `corpus_suspect`: 58 -- `pending_rule_review`: 952 +- `pending_rule_review`: 926 - `unsupported_character_review`: 11 Representative `exact` samples: @@ -3169,10 +3240,10 @@ Representative `mismatch` samples: - actual: `⠌⠠⠧⠗⠚⠽⠐⠥⠦⠄⠴⠠⠉⠠⠉⠠⠴⠰⠠⠠⠞⠧⠲⠐` - first differing cell (zero-based): 128 - current primary/reason: `corpus_suspect` / `rule34_roman_indicator_before_opening_parenthesis` -- `sentence_04.json` #1304: 미국 시장에서 한화큐셀의 주력 제품은 ‘큐피크 듀오(Q.PEAK DUO)’ 시리즈다. 최신 제품인 큐피크 듀오 G11S는 대형 웨이퍼를 사용하여 출력을 극대화한 제품으로 최대 출력이 605Wp(와트피크)에 달한다. - - expected: `⠪⠀⠊⠩⠥⠀⠴⠠⠛⠼⠁⠁⠠⠎⠲⠉⠵⠀⠊⠗⠚⠻⠀⠏` - - actual: `⠪⠀⠊⠩⠥⠀⠴⠠⠠⠛⠼⠁⠁⠎⠲⠉⠵⠀⠊⠗⠚⠻⠀⠏` - - first differing cell (zero-based): 106 +- `sentence_04.json` #1859: 멀티클라우드 매니지먼트 기업 디딤365(대표 장민호)는 공공기관과 지자체를 위한 업무용 협업도구 ‘디딤365 NAVER WORKS for 공공용’이 한국지능정보사회진흥원(NIA) ‘디지털서비스 이용지원시스템’과 조달청 ‘디지털서비스몰’ 디지털서비스에 등록됐다고 1일 밝혔다. + - expected: `⠼⠉⠋⠑⠀⠴⠠⠠⠝⠁⠧⠻⠀⠠⠠⠐⠺⠎⠀⠿⠲⠀⠈⠿` + - actual: `⠼⠉⠋⠑⠀⠴⠠⠠⠠⠝⠁⠧⠻⠀⠐⠺⠎⠀⠿⠲⠠⠄⠀⠈` + - first differing cell (zero-based): 112 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` ## UEB grade-1 first-difference cohorts @@ -3181,10 +3252,10 @@ These cohorts are defined by both an input boundary and the sentence's actual fi | Cohort | Candidates | Exact controls | Mismatch | Target localized | Reverse | |---|---:|---:|---:|---:|---:| -| `allcaps_roman_run_beginning_with_pure_letter_shortform` | 4120 | 2478 | 1642 | 785 | 47 | -| `uppercase_ascii_run_immediately_after_digit_in_roman_sequence` | 1896 | 1106 | 790 | 0 | 1 | -| `uppercase_ascii_run_immediately_after_hyphen_in_roman_sequence` | 952 | 166 | 786 | 159 | 4 | -| `pure_allcaps_segment_before_hyphen_and_multi_allcaps_segment_after` | 448 | 133 | 315 | 0 | 1 | +| `allcaps_roman_run_beginning_with_pure_letter_shortform` | 4120 | 2515 | 1605 | 788 | 48 | +| `uppercase_ascii_run_immediately_after_digit_in_roman_sequence` | 1896 | 1262 | 634 | 0 | 2 | +| `uppercase_ascii_run_immediately_after_hyphen_in_roman_sequence` | 952 | 271 | 681 | 183 | 6 | +| `pure_allcaps_segment_before_hyphen_and_multi_allcaps_segment_after` | 448 | 226 | 222 | 0 | 3 | ### All-caps shortform prefix at an attached Roman entry @@ -3206,18 +3277,18 @@ Same-surface controls demonstrate why primary classes must not be changed by coh | Surface | Candidates | Exact | Mismatch | Target-localized | |---|---:|---:|---:|---:| -| `AC` | 159 | 87 | 72 | 66 | -| `LLM` | 176 | 1 | 175 | 149 | +| `AC` | 159 | 88 | 71 | 66 | +| `LLM` | 176 | 2 | 174 | 151 | | `CD` | 65 | 30 | 35 | 12 | | `IMM` | 27 | 3 | 24 | 22 | | `AG` | 18 | 9 | 9 | 0 | -| `GDP` | 359 | 0 | 359 | 352 | +| `GDP` | 359 | 0 | 359 | 353 | | `WD` | 10 | 10 | 0 | 0 | - `AC` exact control: `sentence_01.json` #1122 — 손흥민(토트넘)이 선발 출전했지만 소속팀 패배를 지켜봤다. 토트넘은 15일(이하 한국시간) 이탈리아 밀라노에 있는 산시로에서 AC 밀란(이탈리아)과 유럽축구연맹(UEFA) 주최 2022-2023시즌 챔피언스리그(UCL) 16강 1차전 원정 경기를 치렀다. - `AC` localized mismatch: `sentence_01.json` #1707 — 토트넘은 9일(한국시간) 영국 런던의 토트넘 홋스퍼 스타디움에서 열린 2022-2023 유럽축구연맹(UEFA) 챔피언스리그(UCL) 16강 2차전에서 AC밀란(이탈리아)과 득점 없이 0-0으로 비겼다. -- `LLM` exact control: `sentence_03.json` #19586 — ‘미디어그룹사람과숲’은 한국정보화진흥원(NIA)과 업스테이지가 주최하는 한국어 ‘초거대 언어 모델’ 리더보드 ‘Open Ko-LLM’에서 1위를 차지했다고 11월13일 밝혔다. +- `LLM` exact control: `sentence_03.json` #12275 — 이와 관련 업스테이지 측은 “이번 결과는 소형 LLM(SLM)의 기준이라고 할 수 있는 300억 매개변수(30B) 이하 사이즈로 글로벌 최고 성능 모델에 올라 더욱 의미가 깊다”고 강조했다. - `LLM` localized mismatch: `sentence_01.json` #312 — 엔비디아가 대규모 언어 모델(LLM)과 음성 AI를 주제로 엔비디아 AI 개발자 밋업을 개최한다. 이번 개발자 밋업은 오는 17일 오후 2시부터 5시까지 온라인으로 진행된다. - `CD` exact control: `sentence_01.json` #4788 — 국제표준 제정절차는 신규작업표준안(NP) → 작업반 초안(WD) → 위원회안(CD) → 국제표준안(DIS) → 최종국제표준안(FDIS) → 국제표준(IS) 제정 순으로 진행된다. @@ -3240,8 +3311,8 @@ Korean rule 29 requires a Roman indicator before Roman text in a Korean sentence | Cohort | Candidates | Exact controls | Mismatch | Pending | Corpus suspect | Localized `⠴ -> blank` | Reverse `blank -> ⠴` | |---|---:|---:|---:|---:|---:|---:|---:| -| `roman_hyphenated_word_after_whitespace_following_korean_word` | 361 | 0 | 361 | 350 | 6 | 136 | 0 | -| `roman_parenthetical_headword_after_whitespace_following_korean_word` | 4695 | 3411 | 1284 | 1192 | 39 | 154 | 0 | +| `roman_hyphenated_word_after_whitespace_following_korean_word` | 361 | 86 | 275 | 264 | 6 | 139 | 0 | +| `roman_parenthetical_headword_after_whitespace_following_korean_word` | 4695 | 3548 | 1147 | 1054 | 40 | 159 | 0 | | `korean_prefixed_roman_parenthetical_followed_by_allcaps_hyphen_suffix` | 11 | 0 | 11 | 6 | 5 | 5 | 0 | The whitespace parenthetical-headword cohort also retains 1 localized `⠠ -> ⠴` and 3 localized `⠴ -> ⠰` cases as separate transitions; they are not folded into the target. The exact controls demonstrate that the broad structure is already correct in many sentences, while the attached parenthetical-hyphen cohort has no exact control and includes cases already classified by the stricter rule-34 reference-order contradiction. Consequently none of these measurements authorizes an engine change; they are deterministic pending/corpus-review diagnostics only. @@ -3252,13 +3323,13 @@ Korean rule 29 explicitly says that when two or more Roman items occur consecuti Analyzer checkpoint `266e70c` fixed the pre-change baseline at 1,729 candidates, 386 exact controls, 1,343 mismatches, 1,272 pending members, and 385/1,343 evaluable mismatches localized to the current re-entry signature. Those localized transitions were 365 `⠠ -> ⠴`, 16 `⠰ -> ⠴`, no reverse `⠴ -> ⠠`, and four other transitions. Exact controls include contexts where the first Roman word already opened token-level mode, while parenthesized/mixed-token examples expose the duplicate event. -The generalized fix makes an explicit `EnterEnglish` event idempotent when final emit state is already inside a Roman section; it neither names an input nor changes a new section's entry. The current measurement is 1729 candidates, 699 exact controls, 1030 mismatches, 952 pending members, and 4/1030 localized mismatches. Current target counts are 0 `⠠ -> ⠴`, 0 `⠰ -> ⠴`, and 0 reverse `⠴ -> ⠠`. Cohort exact controls increase by 275, while corpus-wide exact matches rise from 67,138 to 67,442 (+304); corrected prefixes that still differ later remain mismatches, and applications outside this strict input gate account for the remaining net gain. The raw/residual reverse `⠴ -> ⠠` totals remain 68/65 before and after the change. The complete custom standard summary is 5,141 total, 5,141 success, 0 failure, and 0 skipped. +The generalized fix makes an explicit `EnterEnglish` event idempotent when final emit state is already inside a Roman section; it neither names an input nor changes a new section's entry. The current measurement is 1729 candidates, 725 exact controls, 1004 mismatches, 926 pending members, and 4/1004 localized mismatches. Current target counts are 0 `⠠ -> ⠴`, 0 `⠰ -> ⠴`, and 0 reverse `⠴ -> ⠠`. Cohort exact controls increase by 275, while corpus-wide exact matches rise from 67,138 to 67,442 (+304); corrected prefixes that still differ later remain mismatches, and applications outside this strict input gate account for the remaining net gain. The raw/residual reverse `⠴ -> ⠠` totals remain 68/65 before and after the change. The complete custom standard summary is 5,141 total, 5,141 success, 0 failure, and 0 skipped. ### Closed Roman parenthetical after a non-ASCII-letter boundary Korean rule 34 (2024 Korean-rules PDF p.29) prints the opening enclosure before Roman entry in `링컨(Lincoln)`: the opening sequence is `⠦⠄⠴`. This output-position cohort finds a closed, non-nested parenthetical whose body begins with an ASCII letter and whose opening does not immediately follow another ASCII letter, then locates its complete current-engine signature without consulting the reference. Its localized boundary includes the two current output cells immediately before the opening plus the first three entry cells, so rule-11 math spacing can be separated from a difference later inside the parenthetical. Direct function-call shapes such as `f(x)` are excluded. The PDF's math rule 6 (p.59), rule 12 (pp.63-64), and rule 45 (p.75) nevertheless leave standalone `(x)` and other Roman-letter parenthetical mathematics as counterexamples, so the surface gate is not an engine-routing predicate. -The cross-cutting input cohort contains 63959 candidates: 52436 exact controls and 11523 mismatches. Mismatch primary classes remain unchanged: 10035 `pending_rule_review`, 1108 `corpus_suspect`, 227 `comparison_method`, and 153 `unsupported_character_review`. Of 11523 evaluable mismatches, 1245 have the first difference at the detected leading-spacing/entry boundary; these include 436 `U+2826 ⠦ -> U+2834 ⠴`, 27 `U+2834 ⠴ -> U+2826 ⠦`, and 325 `U+2826 ⠦ -> U+2800 ⠀` transitions; the exact localized reverse `U+2800 ⠀ -> U+2826 ⠦` occurs 0 times. After all earlier localized cohorts and this cohort are excluded, the raw-to-residual target count is 469 -> 26, the Roman-indicator reverse count is 32 -> 2, and the spacing target/reverse counts are 440 -> 79 and 1 -> 1. The short full-encoder form `웹3(Web3)` emits the PDF opening order as a routing control, while whitespace and quote boundaries reproduce the current Roman-first opening. Representative localized samples, with shard and index, are retained in the generated cluster sample table. Because exact controls are abundant and the PDF does not make this input shape semantically sufficient to exclude mathematics, no engine change or primary reclassification is inferred. The one raw/residual spacing reverse is a Korean-body `△(교육)` layout boundary, not a Roman-parenthetical member, and remains a separate rule-72/layout review. +The cross-cutting input cohort contains 63959 candidates: 52997 exact controls and 10962 mismatches. Mismatch primary classes remain unchanged: 9471 `pending_rule_review`, 1111 `corpus_suspect`, 238 `comparison_method`, and 142 `unsupported_character_review`. Of 10962 evaluable mismatches, 1128 have the first difference at the detected leading-spacing/entry boundary; these include 437 `U+2826 ⠦ -> U+2834 ⠴`, 28 `U+2834 ⠴ -> U+2826 ⠦`, and 330 `U+2826 ⠦ -> U+2800 ⠀` transitions; the exact localized reverse `U+2800 ⠀ -> U+2826 ⠦` occurs 0 times. After all earlier localized cohorts and this cohort are excluded, the raw-to-residual target count is 470 -> 26, the Roman-indicator reverse count is 33 -> 2, and the spacing target/reverse counts are 445 -> 79 and 1 -> 1. The short full-encoder form `웹3(Web3)` emits the PDF opening order as a routing control, while whitespace and quote boundaries reproduce the current Roman-first opening. Representative localized samples, with shard and index, are retained in the generated cluster sample table. Because exact controls are abundant and the PDF does not make this input shape semantically sufficient to exclude mathematics, no engine change or primary reclassification is inferred. The one raw/residual spacing reverse is a Korean-body `△(교육)` layout boundary, not a Roman-parenthetical member, and remains a separate rule-72/layout review. The HCA-style headword-expansion shape described above is not an engine implementation premise. The 2024 PDF's math rule 6 defines parentheses and grouping parentheses, rule 11 defines mathematical-expression spacing, rule 12 covers Roman letters in formulas as well as Korean sentences, and rule 45 shows Roman-letter function notation followed by parentheses. Excluding visible operators, scripts, and nesting narrows this corpus cohort, but the PDF does not make the remaining surface shape sufficient to rule out every mathematical counterexample. The cluster therefore remains conservative pending-review evidence only. @@ -3287,29 +3358,33 @@ Corpus contradictions remain a separate gate: identical inputs with conflicting UEB 10.12.1 (2024 UEB PDF pp.191-192, printed pp.163-164) says not to use a contraction when letters within an abbreviation or acronym are known to be pronounced separately, but to use the contraction in case of doubt. Its official `DAR` example writes separate `a` and `r` cells, while UEB 10.12.2's official `START` example uses the `ar` groupsign. Both full-encoder controls pass. Thus an uppercase surface containing `AR` does not itself supply the pronunciation or lexical meaning needed to select either form. -The output-localized cohort contains 1022 candidates, 363 exact controls, and 659 mismatches. Existing mismatch primary classes are preserved: 635 `pending_rule_review`, 15 `corpus_suspect`, 3 `comparison_method`, and 6 `unsupported_character_review`. Of 659 evaluable mismatches, 404 have their first difference inside the detected current-engine run: 397 `U+2801 ⠁ -> U+281C ⠜` and 0 `U+281C ⠜ -> U+2801 ⠁`. Across raw pending transitions and the final residual after localized cohorts, the target count is 405 -> 7; the reverse is 2 -> 2. Inputs denoting separately pronounced initialisms such as corpus `AR`/`ARS` coexist with exact or officially contracted controls. This is deterministic evidence for a pronunciation-dependent pending cohort, not an engine rule or primary reclassification. Representative shard/index samples are retained above. +The output-localized cohort contains 1022 candidates, 370 exact controls, and 652 mismatches. Existing mismatch primary classes are preserved: 628 `pending_rule_review`, 15 `corpus_suspect`, 3 `comparison_method`, and 6 `unsupported_character_review`. Of 652 evaluable mismatches, 407 have their first difference inside the detected current-engine run: 400 `U+2801 ⠁ -> U+281C ⠜` and 0 `U+281C ⠜ -> U+2801 ⠁`. Across raw pending transitions and the final residual after localized cohorts, the target count is 408 -> 7; the reverse is 2 -> 2. Inputs denoting separately pronounced initialisms such as corpus `AR`/`ARS` coexist with exact or officially contracted controls. This is deterministic evidence for a pronunciation-dependent pending cohort, not an engine rule or primary reclassification. Representative shard/index samples are retained above. ### Roman run after a closed Roman enclosure Korean rule 29 places one Roman indicator/terminator pair around consecutive Roman items, with `Los Angeles` and `Table of Contents` as its printed multiword examples. Rule 34 separately omits the Roman terminator when Roman text is enclosed by quotation marks or parentheses. Neither printed rule states whether a later Roman run after the enclosure, intervening punctuation, and whitespace is a continuation of that section or a fresh section. The input gate therefore detects only the structural boundary; it does not label the later run as semantically new. -The cohort contains 1093 candidates, 142 exact controls, and 951 mismatches. Existing mismatch primary classes are preserved: 617 `pending_rule_review`, 327 `corpus_suspect`, and 7 `unsupported_character_review`. Of 951 evaluable mismatches, 251 are output-localized to the current later-run signature plus its one leading boundary cell: 245 `U+2834 ⠴ -> U+2830 ⠰` and 0 `U+2830 ⠰ -> U+2834 ⠴`. The target counted 347 raw and 333 residual cases before this cohort; it is now 47 final residual. Across raw and final residual maps, `U+2830 ⠰ -> U+2834 ⠴` is 2 -> 0. Exact controls coexist with both directions, and 321 mismatches are already independently identified corpus contradictions. Without a printed fresh-entry example or semantic enclosure model, changing continuation state would be reference-fitting; this remains a deterministic pending/corpus-review cohort only. Representative shard/index samples are retained above. +The cohort contains 1093 candidates, 143 exact controls, and 950 mismatches. Existing mismatch primary classes are preserved: 613 `pending_rule_review`, 330 `corpus_suspect`, and 7 `unsupported_character_review`. Of 950 evaluable mismatches, 258 are output-localized to the current later-run signature plus its one leading boundary cell: 254 `U+2834 ⠴ -> U+2830 ⠰` and 0 `U+2830 ⠰ -> U+2834 ⠴`. The target counted 357 raw and 333 residual cases before this cohort; it is now 48 final residual. Across raw and final residual maps, `U+2830 ⠰ -> U+2834 ⠴` is 2 -> 0. Exact controls coexist with both directions, and 321 mismatches are already independently identified corpus contradictions. Without a printed fresh-entry example or semantic enclosure model, changing continuation state would be reference-fitting; this remains a deterministic pending/corpus-review cohort only. Representative shard/index samples are retained above. ### Uppercase Roman runs containing `ED` UEB 10.12.1 (2024 UEB PDF pp.191-192, printed pp.163-164) requires separate letters when an abbreviation's letters are known to be pronounced separately, and its official `OED` example writes `e` and `d` separately. Rule 10.12.2 otherwise uses contractions; its official `BEd` example uses the `ed` groupsign. Both full-encoder controls pass. Consequently an uppercase `ED` surface cannot establish pronunciation or abbreviation semantics by spelling alone. -The cohort contains 816 candidates, 320 exact controls, and 496 mismatches. Existing mismatch primary classes remain 427 `pending_rule_review`, 63 `corpus_suspect`, 1 `comparison_method`, and 5 `unsupported_character_review`. Of 496 evaluable mismatches, 329 are localized to the detected current-engine run: 326 `U+2811 ⠑ -> U+282B ⠫` and 0 `U+282B ⠫ -> U+2811 ⠑`. Across raw pending transitions and the final residual after localized cohorts, the target is 329 -> 3 and the reverse is 0 -> 0. Corpus initialisms such as `LED` and `GED` require external pronunciation knowledge, while exact and official controls preserve contraction-bearing outcomes. No engine change or primary reclassification is made; representative shard/index samples are retained above. +The cohort contains 816 candidates, 334 exact controls, and 482 mismatches. Existing mismatch primary classes remain 412 `pending_rule_review`, 64 `corpus_suspect`, 1 `comparison_method`, and 5 `unsupported_character_review`. Of 482 evaluable mismatches, 330 are localized to the detected current-engine run: 327 `U+2811 ⠑ -> U+282B ⠫` and 0 `U+282B ⠫ -> U+2811 ⠑`. Across raw pending transitions and the final residual after localized cohorts, the target is 330 -> 3 and the reverse is 0 -> 0. Corpus initialisms such as `LED` and `GED` require external pronunciation knowledge, while exact and official controls preserve contraction-bearing outcomes. No engine change or primary reclassification is made; representative shard/index samples are retained above. ### Uppercase Roman runs containing `ST` -UEB 10.12.1 (2024 UEB PDF pp.191-192, printed pp.163-164) says not to use a contraction when letters within an abbreviation or acronym are known to be pronounced separately, but to use the contraction in case of doubt. Rule 10.12.2 requires contractions in other abbreviations and acronyms. Consequently an uppercase `ST` surface alone cannot determine whether `s` + `t` or the `st` groupsign is required. The current cohort contains 1479 candidates, 686 exact controls, and 793 mismatches; primary classes remain 740 `pending_rule_review`, 35 `corpus_suspect`, 13 `comparison_method`, and 5 `unsupported_character_review`. Of 793 evaluable mismatches, 443 are localized to the detected current-engine run: 435 `U+280E ⠎ -> U+280C ⠌` and 1 `U+280C ⠌ -> U+280E ⠎`. The target's raw-to-residual count is 448 -> 13, and the reverse is 2 -> 1. Exact controls such as `STAYG`, `KAIST`, `DGIST`, and `POSTECH` coexist with localized mismatches such as `WSTS`, `HUST`, `OST`, and `USTR`; representative shard/index samples are preserved in the cluster table. This lexical/pronunciation distinction cannot be inferred from the input-only spelling, so no engine change or primary reclassification is made. +UEB 10.12.1 (2024 UEB PDF pp.191-192, printed pp.163-164) says not to use a contraction when letters within an abbreviation or acronym are known to be pronounced separately, but to use the contraction in case of doubt. Rule 10.12.2 requires contractions in other abbreviations and acronyms. Consequently an uppercase `ST` surface alone cannot determine whether `s` + `t` or the `st` groupsign is required. The current cohort contains 1479 candidates, 700 exact controls, and 779 mismatches; primary classes remain 726 `pending_rule_review`, 35 `corpus_suspect`, 13 `comparison_method`, and 5 `unsupported_character_review`. Of 779 evaluable mismatches, 449 are localized to the detected current-engine run: 441 `U+280E ⠎ -> U+280C ⠌` and 1 `U+280C ⠌ -> U+280E ⠎`. The target's raw-to-residual count is 454 -> 13, and the reverse is 2 -> 1. Exact controls such as `STAYG`, `KAIST`, `DGIST`, and `POSTECH` coexist with localized mismatches such as `WSTS`, `HUST`, `OST`, and `USTR`; representative shard/index samples are preserved in the cluster table. This lexical/pronunciation distinction cannot be inferred from the input-only spelling, so no engine change or primary reclassification is made. ### Uppercase segments joined by ampersand: capitalization extent UEB 8.4.2 (2024 UEB PDF p.118, printed p.90) terminates capitals word mode at a nonalphabetic symbol. UEB 3.1.1 and the capitalization examples (PDF pp.51 and 120, printed pp.23 and 92) consequently print `AT&T` as `⠠⠠⠁⠞⠈⠯⠠⠞` and `B&B` as `⠠⠃⠈⠯⠠⠃`: Roman mode remains continuous, but capitalization restarts for each ASCII-letter segment. The detector accepts only complete uppercase ASCII segments joined directly by `&`, with the same non-alphanumeric outer boundaries as the existing Roman-ampersand rule, and requires the run to begin its whitespace-delimited token. Korean-attached and punctuation-prefixed occurrences stay outside the change scope. -The current baseline contains 439 candidates, 1 exact controls, and 438 mismatches. Existing mismatch primaries remain 415 `pending_rule_review`, 10 `corpus_suspect`, 0 `comparison_method`, and 13 `unsupported_character_review`. Of 438 evaluable mismatches, 220 have their first difference inside the independently reproduced Korean-context output signature. The cohort table above retains the transition distribution and shard/index samples. Because capitalization extent is fixed by the official symbol examples and does not require pronunciation or corpus semantics, this is a high-confidence implementation candidate; the diagnostic itself does not change any primary class. +At the analyzer-only checkpoint this cohort contained 439 candidates, 1 exact control, 438 mismatches, and 220 first differences localized inside the independently reproduced Korean-context signature. After the general capitalization correction it contains 439 candidates, 320 exact controls, and 119 mismatches. Existing remaining mismatch primaries are 96 `pending_rule_review`, 10 `corpus_suspect`, 11 `comparison_method`, and 2 `unsupported_character_review`. Of 119 evaluable mismatches, 1 have their first difference inside that signature. The sole pre-change exact member contained lowercase Roman text later in the same whitespace token and was outside the production predicate's actual change scope; its primary outcome was preserved. The cohort table above retains the transition distribution and shard/index samples. Capitalization extent is fixed by the official symbol examples and requires neither pronunciation nor corpus semantics; the diagnostic never changes a primary class. + +### Capitals-word nonletter change-scope audit + +This input-only scope exactly mirrors the former token predicate where it could incorrectly carry capitals-word handling across a nonletter. UEB 8.4.2 terminates that mode at the nonletter. Trailing nonletters after the final uppercase run are excluded because their output is unchanged. Before the correction all 1,733 candidates were mismatches and none was exact. The current run has 1733 candidates, 852 exact controls, and 881 mismatches. A complete exact-ID set audit found 852 newly exact cases and zero cases lost from the 68,439-exact baseline, yielding 69,291/83,528 (82.96%). Two deliberately rejected wider predicates exposed why the boundary matters: requiring an entirely uppercase-only token lost 87 former exact cases, while treating every initial uppercase run as token-level capitals mode lost 33 mixed-case controls such as the UEB `TVOntario` class by bypassing its required capitals terminator. The retained predicate pre-emits only when the initial run has at least two capitals and every ASCII letter in the token is uppercase; Rule 28 independently restarts capitalization after the nonletter. This cohort remains a regression audit only: membership does not assign a primary class or attribute a first difference. ### Attached Roman segments joined by ampersand @@ -3319,7 +3394,7 @@ The baseline was 802 candidates / 0 exact / 802 mismatch, preserving 776 `pendin The implemented gate shares the analyzer's complete-run predicate: one or more non-empty ASCII-letter segments joined directly by `&`, with non-alphanumeric outer boundaries. It keeps the existing Roman mode open and suppresses only rule 71's redundant wrapper around that ampersand. Spaced `Marks & Spencer`, Korean `가&나`, empty segments, and outer digit continuations remain outside the gate. Official full-encoder controls `AT&T` and `B&B` pass, and the Korean rule-71 spaced example `종이접기 & 클레이아트` retains its independent `⠴⠈⠯⠲` section. -After the change the same cohort contains 802 candidates, 285 exact and 517 mismatch. Current mismatch primary classes remain evaluator-owned: 490 `pending_rule_review`, 10 `corpus_suspect`, 14 `unsupported_character_review`, and 3 `comparison_method`. The localizer evaluates all 517 remaining mismatches and finds 0 target-localized cases (0 `U+2808 ⠈ -> U+2832 ⠲`); current raw-to-residual target count is 25 -> 23, while `U+2832 ⠲ -> U+2808 ⠈` remains 0 raw / 0 residual. Cohort exact increases by 273 and corpus-wide exact increases by the same 273, from 67,442 to 67,715. Because every changed input must satisfy this shared predicate and the baseline had no exact member, this boundary has no exact regression. The remaining 529 candidates differ elsewhere or retain an existing comparison, corpus-suspect, unsupported, or pending cause. Representative shard/index samples are retained above. +After the change the same cohort contains 802 candidates, 607 exact and 195 mismatch. Current mismatch primary classes remain evaluator-owned: 168 `pending_rule_review`, 10 `corpus_suspect`, 2 `unsupported_character_review`, and 15 `comparison_method`. The localizer evaluates all 195 remaining mismatches and finds 0 target-localized cases (0 `U+2808 ⠈ -> U+2832 ⠲`); current raw-to-residual target count is 81 -> 79, while `U+2832 ⠲ -> U+2808 ⠈` remains 0 raw / 0 residual. Cohort exact increases by 273 and corpus-wide exact increases by the same 273, from 67,442 to 67,715. Because every changed input must satisfy this shared predicate and the baseline had no exact member, this boundary has no exact regression. The remaining 529 candidates differ elsewhere or retain an existing comparison, corpus-suspect, unsupported, or pending cause. Representative shard/index samples are retained above. ### Ampersand before an attached ASCII Roman segment @@ -3329,7 +3404,7 @@ The diagnostic checkpoint baseline was 30 candidates / 0 corpus exact / 30 misma The implemented rule is limited to an ampersand followed by a complete attached ASCII-letter segment, with no left ASCII alphanumeric or adjacent ampersand and no trailing digit continuation. Rule 71 opens one Roman section before `&`; rule 29 now leaves it open for the attached letters. It does not name a corpus input or inspect a reference. After the change, the cohort has 30 candidates, 12 exact and 18 mismatch; the corpus-wide exact total rises by the same 12 cases, from 68,175 to 68,187, so no exact regression occurs inside or outside this gate. The 16 former exit/re-entry transitions disappear; 12 become exact and 4 remain mismatches at a different PDF-conflicting boundary. Existing mismatch primary classes remain 14 `pending_rule_review`, 4 `corpus_suspect`, 0 `comparison_method`, and 0 `unsupported_character_review`. -Of 18 current evaluable mismatches, 4 are localized to the occurrence-specific entry signature: 3 `U+2808 ⠈ -> U+2834 ⠴` where a parenthesized `&TEAM` reference omits Rule 71's required Roman indicator, and 1 `U+2834 ⠴ -> U+2820 ⠠` where a `드림&Dream` reference inserts another Roman indicator inside the same continuous section. Their raw-to-residual counts are 6 -> 3 and 70 -> 64; the corresponding reverse maps are 0 -> 0 and 52 -> 38. These four cases remain conservative corpus/PDF-reference review rather than widening or undoing the rule. Exact samples such as `&TEAM`, `과학&ICT`, and `한국&K리츠`, with shard/index above, are current controls. Primary classifications are never changed by this cohort. +Of 18 current evaluable mismatches, 4 are localized to the occurrence-specific entry signature: 3 `U+2808 ⠈ -> U+2834 ⠴` where a parenthesized `&TEAM` reference omits Rule 71's required Roman indicator, and 1 `U+2834 ⠴ -> U+2820 ⠠` where a `드림&Dream` reference inserts another Roman indicator inside the same continuous section. Their raw-to-residual counts are 7 -> 4 and 70 -> 64; the corresponding reverse maps are 0 -> 0 and 56 -> 38. These four cases remain conservative corpus/PDF-reference review rather than widening or undoing the rule. Exact samples such as `&TEAM`, `과학&ICT`, and `한국&K리츠`, with shard/index above, are current controls. Primary classifications are never changed by this cohort. ### Spaced comma between ASCII digit runs @@ -3337,7 +3412,7 @@ This output-localized cohort requires a comma immediately after an ASCII digit, The diagnostic baseline was 217 candidates / 7 exact / 210 mismatch, with 177 occurrence-specific `U+2810 ⠐ -> U+2802 ⠂` first differences and no localized reverse. Rule 41 had looked through `remaining_words`, incorrectly treating whitespace as if the following digit were attached. The implementation now inspects only the next character in the same token. It neither names a corpus input nor consults expected output; attached numbers and UEB punctuation remain owned by their existing routes. -After the correction, the cohort has 217 candidates / 181 exact / 36 mismatch. Existing mismatch primaries remain 35 `pending_rule_review`, 0 `corpus_suspect`, 1 `comparison_method`, and 0 `unsupported_character_review`. Of 36 evaluable current mismatches, 0 localize to the comma cell: 0 `U+2810 ⠐ -> U+2802 ⠂` and 0 `U+2802 ⠂ -> U+2810 ⠐`. The cohort gains 174 exact cases. Across all pending cases, the current raw-to-residual counts are 24 -> 6 for the target and 20 -> 14 for the reverse. The standard controls `9,375명`, `창세기 12,1-9`, and `근면, 검소, 협동은 ...` retain their PDF cells. No primary class is changed by the cohort. +After the correction, the cohort has 217 candidates / 182 exact / 35 mismatch. Existing mismatch primaries remain 34 `pending_rule_review`, 0 `corpus_suspect`, 1 `comparison_method`, and 0 `unsupported_character_review`. Of 35 evaluable current mismatches, 0 localize to the comma cell: 0 `U+2810 ⠐ -> U+2802 ⠂` and 0 `U+2802 ⠂ -> U+2810 ⠐`. The cohort gains 174 exact cases. Across all pending cases, the current raw-to-residual counts are 24 -> 6 for the target and 21 -> 15 for the reverse. The standard controls `9,375명`, `창세기 12,1-9`, and `근면, 검소, 협동은 ...` retain their PDF cells. No primary class is changed by the cohort. ### ASCII/Roman-tail comma before a digit-led Korean token @@ -3359,25 +3434,25 @@ Korean rule 49 (2024 Korean-rules PDF p.37, printed p.31) says that spacing foll The diagnostic baseline had 95 candidates / 0 exact / 95 mismatch. All 95 were in the exact former implementation scope; 74 first differences were at the inserted blank: 73 `U+2815 ⠕ -> U+2800 ⠀`, one `U+2823 ⠣ -> U+2800 ⠀`, and no localized reverse. The absence of a baseline exact member is the in-scope regression control. -After removing that input-correcting transformation, the cohort has 95 candidates / 71 exact / 24 mismatch, preserving 20 `pending_rule_review`, 3 `corpus_suspect`, 1 `comparison_method`, and 0 `unsupported_character_review` mismatch primaries. Of 24 evaluable current mismatches, 0 still localize to an inserted blank: 0 `U+2815 ⠕ -> U+2800 ⠀`, 0 `U+2823 ⠣ -> U+2800 ⠀`, and 0 `U+2800 ⠀ -> U+2815 ⠕`. Seventy-one cases become exact; the other 24 retain independent earlier differences. Corpus exact increases by the same 71 with zero exact-set regressions. The three explicitly spaced PDF forms remain full-encoder negative controls, so removing correction of missing input whitespace does not remove a printed space. The local rule-47 standard case had accidentally transcribed PDF physical p.36 `덮여 있다` as attached `덮여있다` while retaining the PDF's spaced braille; correcting that input transcription restores the complete 5,141/5,141 standard summary without an engine exception. +After removing that input-correcting transformation, the cohort has 95 candidates / 72 exact / 23 mismatch, preserving 19 `pending_rule_review`, 3 `corpus_suspect`, 1 `comparison_method`, and 0 `unsupported_character_review` mismatch primaries. Of 23 evaluable current mismatches, 0 still localize to an inserted blank: 0 `U+2815 ⠕ -> U+2800 ⠀`, 0 `U+2823 ⠣ -> U+2800 ⠀`, and 0 `U+2800 ⠀ -> U+2815 ⠕`. Seventy-one cases become exact; the other 24 retain independent earlier differences. Corpus exact increases by the same 71 with zero exact-set regressions. The three explicitly spaced PDF forms remain full-encoder negative controls, so removing correction of missing input whitespace does not remove a printed space. The local rule-47 standard case had accidentally transcribed PDF physical p.36 `덮여 있다` as attached `덮여있다` while retaining the PDF's spaced braille; correcting that input transcription restores the complete 5,141/5,141 standard summary without an engine exception. -Current uppercase-Roman hyphen-digits measurement: 571 candidates, 191 exact controls, 380 mismatches, 365 members in the actual `pending_rule_review` subcluster, and 262/380 evaluable mismatches whose first difference is inside the target run plus its entry boundary. It remains distinct from parenthesized digits and headword expansions; no engine change is inferred. +Current uppercase-Roman hyphen-digits measurement: 571 candidates, 194 exact controls, 377 mismatches, 362 members in the actual `pending_rule_review` subcluster, and 262/377 evaluable mismatches whose first difference is inside the target run plus its entry boundary. It remains distinct from parenthesized digits and headword expansions; no engine change is inferred. -Current uppercase alphanumeric Roman-digit sequence measurement: 3429 candidates, 1917 exact controls, 1512 mismatches, 1403 members in the actual `pending_rule_review` subcluster, and 151/1512 evaluable mismatches whose first difference is localized to the current entry cells. The target `⠴ -> blank` occurs 93 times and the reverse occurs 0 times. Korean rule 35 (2024 Korean-rules PDF pp.29-30, printed pp.23-24) explicitly treats `MP3`, `A4`, `KF94`, and `D-100` as Roman-number sequences. Math rules 11/12 (PDF pp.63-65, printed pp.57-59) separately route mathematical Roman notation without the prose Roman indicator and with two-cell spacing. Official UEB 6.5.1-6.5.2 (2024 UEB PDF p.96, printed p.68) determines numeric/grade-1 continuation only after the surrounding mode has been chosen. Thus surfaces such as `A-3` cannot be assigned identifier or variable semantics from this structure alone. Exact controls and both directions are retained; no engine change is inferred. +Current uppercase alphanumeric Roman-digit sequence measurement: 3429 candidates, 2096 exact controls, 1333 mismatches, 1223 members in the actual `pending_rule_review` subcluster, and 150/1333 evaluable mismatches whose first difference is localized to the current entry cells. The target `⠴ -> blank` occurs 95 times and the reverse occurs 0 times. Korean rule 35 (2024 Korean-rules PDF pp.29-30, printed pp.23-24) explicitly treats `MP3`, `A4`, `KF94`, and `D-100` as Roman-number sequences. Math rules 11/12 (PDF pp.63-65, printed pp.57-59) separately route mathematical Roman notation without the prose Roman indicator and with two-cell spacing. Official UEB 6.5.1-6.5.2 (2024 UEB PDF p.96, printed p.68) determines numeric/grade-1 continuation only after the surrounding mode has been chosen. Thus surfaces such as `A-3` cannot be assigned identifier or variable semantics from this structure alone. Exact controls and both directions are retained; no engine change is inferred. -Current consecutive ASCII-Roman word-boundary measurement: 4679 candidates, 2138 exact controls, 2541 mismatches, 2407 members in the actual `pending_rule_review` subcluster, and 23/2541 evaluable mismatches whose first difference is localized to the current boundary cell. The target reference blank versus current terminator (`⠀ -> ⠲`) occurs 23 times; the exact reverse (`⠲ -> ⠀`) occurs 0 times. Korean rule 29 (2024 Korean-rules PDF p.26, printed p.20) explicitly treats consecutive Roman items as one Roman section, with one Roman indicator before the first item and one terminator after the last; its printed `Los Angeles` and `Table of Contents` examples are exact controls. Rule 34 (PDF p.29, printed p.23) separately omits the terminator before a closing enclosure, while official UEB 9.7.1 (2024 UEB PDF p.137, printed p.109) prints the multiword prose enclosure `plays (such as Romeo and Juliet)` with ordinary internal spaces and nested closing punctuation. Together they support preserving a complete letter-and-space Roman parenthetical as prose, but not a fragment containing a function-call opener, digit, operator, or nested bracket. The input-only gate does not decide whether punctuation-separated text such as `ESS /VPP` continues the same section, so that variant remains outside this cohort. Primary classes are preserved. Before the narrow token-routing change this cohort was 4,679 candidates / 2,058 exact / 2,621 mismatch, with 128 localized `⠀ -> ⠲` and zero reverse. The cause was the final `Letters)` token of a whitespace-split Roman parenthetical being rerouted as math, which made the preceding Roman word terminate early and introduced an extra blank. After excluding only a backwards-verified complete Roman parenthetical tail from math routing, the cohort is 2,132 exact / 2,547 mismatch, with 23 target-localized and zero reverse; corpus-wide exact likewise rises by 74, from 68,101 to 68,175. The 105 removed localized boundaries include 74 newly exact cases and 31 cases that still differ elsewhere. Remaining targets such as `SYNO PEM-1`, `ACE Fair(2020)`, and `Mnet K-POP` do not satisfy the complete-parenthetical gate and remain separate residuals rather than widening this rule. +Current consecutive ASCII-Roman word-boundary measurement: 4679 candidates, 2232 exact controls, 2447 mismatches, 2312 members in the actual `pending_rule_review` subcluster, and 26/2447 evaluable mismatches whose first difference is localized to the current boundary cell. The target reference blank versus current terminator (`⠀ -> ⠲`) occurs 26 times; the exact reverse (`⠲ -> ⠀`) occurs 0 times. Korean rule 29 (2024 Korean-rules PDF p.26, printed p.20) explicitly treats consecutive Roman items as one Roman section, with one Roman indicator before the first item and one terminator after the last; its printed `Los Angeles` and `Table of Contents` examples are exact controls. Rule 34 (PDF p.29, printed p.23) separately omits the terminator before a closing enclosure, while official UEB 9.7.1 (2024 UEB PDF p.137, printed p.109) prints the multiword prose enclosure `plays (such as Romeo and Juliet)` with ordinary internal spaces and nested closing punctuation. Together they support preserving a complete letter-and-space Roman parenthetical as prose, but not a fragment containing a function-call opener, digit, operator, or nested bracket. The input-only gate does not decide whether punctuation-separated text such as `ESS /VPP` continues the same section, so that variant remains outside this cohort. Primary classes are preserved. Before the narrow token-routing change this cohort was 4,679 candidates / 2,058 exact / 2,621 mismatch, with 128 localized `⠀ -> ⠲` and zero reverse. The cause was the final `Letters)` token of a whitespace-split Roman parenthetical being rerouted as math, which made the preceding Roman word terminate early and introduced an extra blank. After excluding only a backwards-verified complete Roman parenthetical tail from math routing, the cohort is 2,132 exact / 2,547 mismatch, with 23 target-localized and zero reverse; corpus-wide exact likewise rises by 74, from 68,101 to 68,175. The 105 removed localized boundaries include 74 newly exact cases and 31 cases that still differ elsewhere. Remaining targets such as `SYNO PEM-1`, `ACE Fair(2020)`, and `Mnet K-POP` do not satisfy the complete-parenthetical gate and remain separate residuals rather than widening this rule. Current single-capital parenthesized-digits measurement: 1361 candidates, 0 exact controls, 1361 mismatches, 1357 members in the actual `pending_rule_review` subcluster, and 1342/1361 evaluable mismatches whose first difference is inside the target run plus its entry boundary. No engine change is inferred from the ambiguous prose/function surface form. Current mixed Roman+Korean boundary before uppercase headword-expansion measurement: 10 candidates, 0 exact controls, 10 mismatches, 10 members in the actual `pending_rule_review` subcluster, and 8/10 evaluable mismatches whose first difference is localized to the later headword's entry boundary/output. The detector cannot be satisfied by the earlier Roman entry. No HCA-shaped engine routing rule is introduced. -Current compact numeric+ASCII-suffix measurement: 2975 candidates, 1878 exact controls, 1097 mismatches, 1026 members in the actual `pending_rule_review` subcluster, and 251/1097 evaluable mismatches whose first difference is inside the complete current-engine output signature or its immediate entry boundary. Rule 40 requires the numeric indicator and rule 69 requires Roman indicators around a Roman-written unit, but the input shape alone cannot prove that every ASCII suffix is a unit. +Current compact numeric+ASCII-suffix measurement: 2975 candidates, 1911 exact controls, 1064 mismatches, 993 members in the actual `pending_rule_review` subcluster, and 256/1064 evaluable mismatches whose first difference is inside the complete current-engine output signature or its immediate entry boundary. Rule 40 requires the numeric indicator and rule 69 requires Roman indicators around a Roman-written unit, but the input shape alone cannot prove that every ASCII suffix is a unit. | ASCII suffix | Candidates | Exact | Mismatch | Localized first diff | |---|---:|---:|---:|---:| -| `m` | 364 | 262 | 102 | 22 | -| `km` | 308 | 252 | 56 | 2 | -| `G` | 237 | 165 | 72 | 3 | +| `m` | 364 | 263 | 101 | 22 | +| `km` | 308 | 254 | 54 | 2 | +| `G` | 237 | 170 | 67 | 3 | | `kg` | 209 | 176 | 33 | 4 | | `D` | 167 | 115 | 52 | 5 | | `p` | 140 | 20 | 120 | 84 | @@ -3385,17 +3460,17 @@ Current compact numeric+ASCII-suffix measurement: 2975 candidates, 1878 exact co | `t` | 128 | 86 | 42 | 17 | | `M` | 104 | 80 | 24 | 6 | | `cm` | 69 | 52 | 17 | 0 | -| `B` | 62 | 38 | 24 | 0 | -| `GB` | 61 | 28 | 33 | 4 | -| `GWh` | 57 | 42 | 15 | 8 | +| `B` | 62 | 41 | 21 | 0 | +| `GB` | 61 | 28 | 33 | 8 | +| `GWh` | 57 | 42 | 15 | 9 | | `ha` | 53 | 18 | 35 | 0 | -| `TV` | 52 | 38 | 14 | 1 | -| `S` | 45 | 30 | 15 | 0 | +| `TV` | 52 | 41 | 11 | 1 | +| `S` | 45 | 31 | 14 | 0 | | `GW` | 44 | 35 | 9 | 6 | | `X` | 39 | 14 | 25 | 2 | | `bp` | 39 | 3 | 36 | 18 | | `K` | 38 | 13 | 25 | 1 | -| `MW` | 37 | 35 | 2 | 0 | +| `MW` | 37 | 36 | 1 | 0 | | `TURN` | 33 | 20 | 13 | 0 | | `L` | 31 | 21 | 10 | 5 | | `mm` | 29 | 19 | 10 | 1 | @@ -3403,32 +3478,32 @@ Current compact numeric+ASCII-suffix measurement: 2975 candidates, 1878 exact co Entry-boundary pre-fix baseline: 2,975 candidates, 1,649 exact controls, 1,326 mismatches, 1,259 pending members, and 356 localized first differences; the dominant reference number-sign versus current space transition accounted for 296 cases. Rules 68 and 69 already accept semantic Unicode compatibility-unit forms. The implementation derives compact ASCII spellings only from their all-letter NFKC decompositions, reuses the owning rule's PDF-defined cells, chooses the longest complete spelling, and rejects partial suffix matches. It does not extend recognition to separated English words or arbitrary corpus suffixes. The same cohort now has 1,759 exact controls, 1,216 mismatches, 1,148 pending members, and 261 localized first differences; corpus-wide exact matches moved from 66,436 to 66,546 (+110). Rule 69's printed `160㎎/㎗` example directly controls `160mg`; the additional full-encoder `240mg`/`240㎎` pair proves that recognition is invariant to the numeric value and also passes. Rule 68's printed `10,000㎡는 1㏊이다` example controls the `ha` spelling through U+33CA's NFKC decomposition; `15.2ha`/`15.2㏊` now passes without a spelling-specific output branch. The 53-case `ha` suffix control moved from 0 exact to 18 exact; its remaining 35 mismatches have independent later or surrounding differences. A full U+3300..U+33FF owner audit found 73 Rules 68/69 glyphs with pure-ASCII NFKC decompositions, 73 distinct spellings, and therefore no current duplicate-spelling owner collision. Production nevertheless groups every owner before resolution and excludes a spelling if any owner cells differ; a synthetic collision test proves that this is not first-wins behavior. An exhaustive test also compares every one of the 73 derived spellings with every owner glyph at both the unit-cell and full-encoder boundaries in a neutral Korean measurement context. That audit exposed the pre-existing explicit `cal` mapping's missing ordinary terminator; rule 69 requires the terminator, while the existing slash-boundary function removes it for the printed `cal/㎠/min` context. Restoring the ordinary terminator and extending that slash-continuation boundary made the audit pass without changing the corpus-wide 66,546 exact total. Ambiguous pure-English inputs remain on UEB: the standard controls `3m` and `4.m` are retained and pass, rather than being globally forced into a Korean unit route. -Current rule-69 ASCII-unit punctuation-boundary measurement: 440 candidates, 343 exact controls, 97 mismatches, 92 members in the actual `pending_rule_review` subcluster, and 16/97 evaluable mismatches whose first difference is localized to the unit-plus-punctuation output signature. PDF rule 69 requires a Roman terminator after a Roman-written unit in the ordinary case, while rules 33/34 omit it at the listed punctuation or enclosing-mark boundary; the rule 46 PDF example `체중(kg)` is the minimal parenthesized-unit control. Membership is restricted to rule-69 spellings already supported by the engine and does not infer unit semantics for arbitrary ASCII suffixes. +Current rule-69 ASCII-unit punctuation-boundary measurement: 440 candidates, 344 exact controls, 96 mismatches, 91 members in the actual `pending_rule_review` subcluster, and 16/96 evaluable mismatches whose first difference is localized to the unit-plus-punctuation output signature. PDF rule 69 requires a Roman terminator after a Roman-written unit in the ordinary case, while rules 33/34 omit it at the listed punctuation or enclosing-mark boundary; the rule 46 PDF example `체중(kg)` is the minimal parenthesized-unit control. Membership is restricted to rule-69 spellings already supported by the engine and does not infer unit semantics for arbitrary ASCII suffixes. Analyzer pre-fix baseline: 440 candidates, 0 exact controls, 440 mismatches, 435 pending members, and 9 signature-local first differences. After the generalized rule-33/34 boundary override and the matching non-math routing guard, the same cohort has 325 exact controls and 115 mismatches. Corpus-wide exact matches moved from 66,039 to 66,436 (+397); the additional gains are applications of the same boundary rule outside this strict ASCII detector, including compatibility-unit forms. The complete standard suite remains 5,141/5,141. -Current decimal-point measurement: 4546 candidates, 3397 exact controls, 1149 mismatches, 1092 members in the actual `pending_rule_review` subcluster, and 262/1149 evaluable mismatches whose first difference is inside the complete decimal-containing word's current-engine signature. Hangeul rules 43 and 48 keep an ASCII point between digits in the numeric sequence and encode it as the decimal point; rules 35 and 69 supply controls for adjacent Roman-number chains and Roman units. This is an implementation-candidate audit, not permission to specialize on a corpus reference. +Current decimal-point measurement: 4546 candidates, 3444 exact controls, 1102 mismatches, 1045 members in the actual `pending_rule_review` subcluster, and 267/1102 evaluable mismatches whose first difference is inside the complete decimal-containing word's current-engine signature. Hangeul rules 43 and 48 keep an ASCII point between digits in the numeric sequence and encode it as the decimal point; rules 35 and 69 supply controls for adjacent Roman-number chains and Roman units. This is an implementation-candidate audit, not permission to specialize on a corpus reference. Analyzer checkpoint `ed20f99` recorded the pre-fix baseline as 4,546 candidates, 2,584 exact controls, 1,962 mismatches, 1,905 pending members, and 875 localized first differences. Its dominant localized transition was reference decimal point `U+2832 ⠲` versus current Roman indicator `U+2834 ⠴` in 647 cases. After the generalized rule-43/48 guard, that transition is absent: 525 cases become exact and the other corrected prefixes expose later independent mismatches. -Current all-caps `OU` measurement: 1816 candidates, 64 exact controls, 1752 mismatches, 1666 members in the actual `pending_rule_review` subcluster, and 1332/1752 evaluable mismatches whose first difference is inside the current-engine output signature for the detected run. This is a pronunciation-sensitive UEB review cohort, not an engine routing rule. +Current all-caps `OU` measurement: 1816 candidates, 67 exact controls, 1749 mismatches, 1663 members in the actual `pending_rule_review` subcluster, and 1356/1749 evaluable mismatches whose first difference is inside the current-engine output signature for the detected run. This is a pronunciation-sensitive UEB review cohort, not an engine routing rule. -Current standalone-uppercase measurement: 62411 candidates, 50640 exact controls, 11771 mismatches, and 10312 members in the actual `pending_rule_review` subcluster. Its high frequency does not make it causal: the same input shape is exact in many cases, and a sentence containing the shape may first differ at another Roman, numeric, or punctuation structure. No engine change is inferred from this cohort. +Current standalone-uppercase measurement: 62411 candidates, 51310 exact controls, 11101 mismatches, and 9639 members in the actual `pending_rule_review` subcluster. Its high frequency does not make it causal: the same input shape is exact in many cases, and a sentence containing the shape may first differ at another Roman, numeric, or punctuation structure. No engine change is inferred from this cohort. -Current Korean-prefixed all-caps parenthetical measurement: 54492 candidates, 45187 exact controls, 9305 mismatches, and 8003 members in the actual `pending_rule_review` subcluster. This is a semantic-collision audit, not an engine routing rule; no implementation change is inferred from its reference outputs. +Current Korean-prefixed all-caps parenthetical measurement: 54492 candidates, 45633 exact controls, 8859 mismatches, and 7557 members in the actual `pending_rule_review` subcluster. This is a semantic-collision audit, not an engine routing rule; no implementation change is inferred from its reference outputs. -Current rule-34 opening-order measurement: 64382 structural candidates, 53042 exact controls, 11340 mismatches, and 1112/11340 evaluable mismatches whose first difference is inside the current engine's Korean opening-parenthesis cells. Only the 1111 localized first-cell transitions have the reference/current order `⠴` versus `⠦`. After requiring the complete reference `⠴⠐⠣` versus current/PDF `⠦⠄⠴` three-cell signature and preserving higher-priority comparison classifications, 1108 are classified as `corpus_suspect`; mere coexistence with a Korean-prefixed Roman annotation does not change a primary class. +Current rule-34 opening-order measurement: 64382 structural candidates, 53593 exact controls, 10789 mismatches, and 1116/10789 evaluable mismatches whose first difference is inside the current engine's Korean opening-parenthesis cells. Only the 1114 localized first-cell transitions have the reference/current order `⠴` versus `⠦`. After requiring the complete reference `⠴⠐⠣` versus current/PDF `⠦⠄⠴` three-cell signature and preserving higher-priority comparison classifications, 1111 are classified as `corpus_suspect`; mere coexistence with a Korean-prefixed Roman annotation does not change a primary class. Current all-caps Roman middle-dot measurement: 97 candidates, 0 exact controls, 97 mismatches, and 96 members in the actual `pending_rule_review` subcluster. This cross-cutting cohort preserves every primary class and is not an engine routing rule. -Current Roman-before-middle-dot boundary measurement: 577 candidates, 0 exact controls, 577 mismatches, 565 members in the actual `pending_rule_review` subcluster, and 428/577 evaluable mismatches whose first difference is localized to the one current terminator immediately before the middle dot. The locator encodes each real input prefix ending at the dot, so identifier state such as `K-ICS·...` is retained without consulting expected. This raises localized target coverage from 342 to 417 and reduces the then-leading residual `⠐ -> ⠲` transition from 297 to 223. Rules 29, 33, and 50 support the current terminator path but do not support the localized reference omission; no engine change or primary-class rewrite is inferred. +Current Roman-before-middle-dot boundary measurement: 577 candidates, 0 exact controls, 577 mismatches, 565 members in the actual `pending_rule_review` subcluster, and 450/577 evaluable mismatches whose first difference is localized to the one current terminator immediately before the middle dot. The locator encodes each real input prefix ending at the dot, so identifier state such as `K-ICS·...` is retained without consulting expected. This raises localized target coverage from 342 to 417 and reduces the then-leading residual `⠐ -> ⠲` transition from 297 to 223. Rules 29, 33, and 50 support the current terminator path but do not support the localized reference omission; no engine change or primary-class rewrite is inferred. -Current attached Roman-to-Korean boundary measurement: 17693 candidates, 12973 exact controls, 4720 mismatches, 3887 members in the actual `pending_rule_review` subcluster, and 0/4720 evaluable mismatches whose first difference is localized to the current boundary marker. The localized target `⠲ -> ⠸` occurs 0 times and the reverse `⠸ -> ⠲` occurs 0 times. Korean rule 29 (2024 Korean-rules PDF p.26, printed p.20) requires a Roman terminator around Roman text in a Korean sentence, whereas rule 39 (PDF pp.31-32, printed pp.25-26) applies the Korean opening/closing markers only when Roman text is the sentence's main language. Among exact candidates, 11002 expose the current rule-29 terminator at such a boundary and 0 expose the current rule-39 opening. Its printed controls include both an English sentence (`What is 김치 in English?`) and the address `www.대통령.kr` inside a Korean sentence. Official UEB 2.4.7 (2024 UEB PDF p.41, printed p.13) confirms that a UEB mode does not extend through a switch to another braille code, but does not choose whether this Korean boundary belongs to a Korean-main or Roman-main context. Therefore the attached script boundary alone cannot distinguish ordinary Korean prose from an embedded Roman-domain context. Primary classes are preserved and no engine change is inferred without a narrower input-derived dominance gate. At diagnostic checkpoint `57c4608`, this same cohort was 17,693 candidates / 12,724 exact / 4,969 mismatch, with 268 localized `⠲ -> ⠸`, zero reverse, 10,780 exact rule-29 markers, and zero exact rule-39 markers. After narrowing the engine by dominance and the PDF domain shape, it is 12,924 exact / 4,769 mismatch with no localized target or reverse; corpus-wide exact rises from 67,715 to 68,101 (+386). The remaining 1 mismatch with a current rule-39 opening is a list-heavy Korean sentence whose ASCII-leading brand tokens satisfy the mechanical word-count majority; its first difference is not localized to this boundary, so it remains a semantic pending control rather than grounds for another engine branch. +Current attached Roman-to-Korean boundary measurement: 17693 candidates, 13445 exact controls, 4248 mismatches, 3412 members in the actual `pending_rule_review` subcluster, and 0/4248 evaluable mismatches whose first difference is localized to the current boundary marker. The localized target `⠲ -> ⠸` occurs 0 times and the reverse `⠸ -> ⠲` occurs 0 times. Korean rule 29 (2024 Korean-rules PDF p.26, printed p.20) requires a Roman terminator around Roman text in a Korean sentence, whereas rule 39 (PDF pp.31-32, printed pp.25-26) applies the Korean opening/closing markers only when Roman text is the sentence's main language. Among exact candidates, 11360 expose the current rule-29 terminator at such a boundary and 0 expose the current rule-39 opening. Its printed controls include both an English sentence (`What is 김치 in English?`) and the address `www.대통령.kr` inside a Korean sentence. Official UEB 2.4.7 (2024 UEB PDF p.41, printed p.13) confirms that a UEB mode does not extend through a switch to another braille code, but does not choose whether this Korean boundary belongs to a Korean-main or Roman-main context. Therefore the attached script boundary alone cannot distinguish ordinary Korean prose from an embedded Roman-domain context. Primary classes are preserved and no engine change is inferred without a narrower input-derived dominance gate. At diagnostic checkpoint `57c4608`, this same cohort was 17,693 candidates / 12,724 exact / 4,969 mismatch, with 268 localized `⠲ -> ⠸`, zero reverse, 10,780 exact rule-29 markers, and zero exact rule-39 markers. After narrowing the engine by dominance and the PDF domain shape, it is 12,924 exact / 4,769 mismatch with no localized target or reverse; corpus-wide exact rises from 67,715 to 68,101 (+386). The remaining 1 mismatch with a current rule-39 opening is a list-heavy Korean sentence whose ASCII-leading brand tokens satisfy the mechanical word-count majority; its first difference is not localized to this boundary, so it remains a semantic pending control rather than grounds for another engine branch. -Current rule-39 narrowed-scope audit: 947 candidates, 386 exact controls, 561 mismatches, and 549 members in the actual `pending_rule_review` subcluster. This input-derived scope is recorded separately from the direct-boundary output-localizer; primary classes are unchanged. At the implementation checkpoint, this conservative first-script-word approximation contains 385 exact cases, accounting for all but one of the corpus-wide +386 net gain; no localized reverse transition is observed in the direct-boundary cohort. +Current rule-39 narrowed-scope audit: 947 candidates, 592 exact controls, 355 mismatches, and 343 members in the actual `pending_rule_review` subcluster. This input-derived scope is recorded separately from the direct-boundary output-localizer; primary classes are unchanged. At the implementation checkpoint, this conservative first-script-word approximation contains 385 exact cases, accounting for all but one of the corpus-wide +386 net gain; no localized reverse transition is observed in the direct-boundary cohort. Current inline parenthesized-operator measurement: 23 candidates, 17 exact controls, 6 mismatches, 6 members in the actual `pending_rule_review` subcluster, and 2/6 evaluable mismatches whose first differing cell is inside the emitted structure. Primary classes are preserved. At that implementation checkpoint, the strict cohort moved from 0 to 17 exact cases; the corpus-wide total moved from 65,491 to 65,514 (+23 exact) because the same PDF-backed spacing rule also applied outside the stricter Korean-boundary audit gate. These are immutable checkpoint counts rather than the report's later cumulative total. The complete standard suite remained 5,141/5,141. -Current tight-triangle measurement: 377 candidates, 0 exact controls, 377 mismatches, 376 members in the actual `pending_rule_review` subcluster, and 334/377 evaluable mismatches whose first difference is inside the `△` plus first-Korean output range. No engine change is inferred. +Current tight-triangle measurement: 377 candidates, 0 exact controls, 377 mismatches, 376 members in the actual `pending_rule_review` subcluster, and 335/377 evaluable mismatches whose first difference is inside the `△` plus first-Korean output range. No engine change is inferred. ## Encoding-error diagnostics @@ -3437,10 +3512,10 @@ The audit starts from all raw encoding errors, then separates cases already reso | Encoding-error audit | Cases | |---|---:| | Raw encoding errors | 450 | -| Resolved by comparison method | 276 | +| Resolved by comparison method | 291 | | Excluded as corpus suspect | 0 | -| Unresolved encoding-error review cases | 174 | -| Explained by singleton unsupported character(s) | 174 | +| Unresolved encoding-error review cases | 159 | +| Explained by singleton unsupported character(s) | 159 | | Multiple singleton unsupported characters | 2 | | Unclassified without a singleton explanation | 0 | @@ -3451,11 +3526,11 @@ The audit starts from all raw encoding errors, then separates cases already reso | Error message | Cases | |---|---:| -| `Invalid symbol character` | 174 | +| `Invalid symbol character` | 159 | | Error family | Cases | |---|---:| -| `enclosed_organization_mark` | 155 | +| `enclosed_organization_mark` | 140 | | `letterlike_unit_symbol` | 9 | | `punctuation_or_layout_symbol` | 12 | @@ -3474,33 +3549,33 @@ Families are diagnostics, not automatic normalization permissions. Rules 68/69 c | `U+2E31 ⸱` | 2 | `⸱` | `punctuation_or_layout_symbol` | | `U+302E 〮` | 1 | `〮` | `punctuation_or_layout_symbol` | | `U+3214 ㈔` | 2 | `(사)` | `enclosed_organization_mark` | -| `U+321C ㈜` | 153 | `(주)` | `enclosed_organization_mark` | +| `U+321C ㈜` | 138 | `(주)` | `enclosed_organization_mark` | ## Shards | Shard | Exact | Total | Accuracy | |---|---:|---:|---:| -| `sentence_01.json` | 20553 | 25000 | 82.21% | -| `sentence_02.json` | 19994 | 25000 | 79.98% | -| `sentence_03.json` | 20662 | 25000 | 82.65% | -| `sentence_04.json` | 7230 | 8528 | 84.78% | +| `sentence_01.json` | 20780 | 25000 | 83.12% | +| `sentence_02.json` | 20317 | 25000 | 81.27% | +| `sentence_03.json` | 20906 | 25000 | 83.62% | +| `sentence_04.json` | 7288 | 8528 | 85.46% | ## Overlapping mismatch traits | Trait | Count | |---|---:| -| `contains_ascii_digits` | 11851 | -| `contains_ascii_letters` | 14926 | -| `contains_delimiter_or_quote` | 15089 | -| `input_not_nfkc` | 712 | +| `contains_ascii_digits` | 11237 | +| `contains_ascii_letters` | 14074 | +| `contains_delimiter_or_quote` | 14237 | +| `input_not_nfkc` | 703 | ## Samples ### `foreign_text_rule_review` - `sentence_01.json` #17: 지난달 30일(현지시각) 뉴욕증권거래소(NYSE)에서 다우존스30산업평균지수는 전 거래일보다 73.55포인트(0.22%) 하락한 3만3147.25로 마감했다. 대형주 중심의 S&P500지수는 9.78포인트(0.25%) 하락한 3839.50으로, 기술주 중심의 나스닥지수는 11.61포인트(0.11%) 하락한 1만0466.4 - - expected: `⠶⠠⠕⠢⠺⠀⠴⠠⠎⠈⠯⠠⠏⠼⠑⠚⠚⠨⠕⠠⠍⠉⠵⠀` - - actual: `⠶⠠⠕⠢⠺⠀⠴⠠⠠⠎⠲⠴⠈⠯⠲⠴⠏⠼⠑⠚⠚⠨⠕⠠` + - expected: `⠠⠕⠢⠺⠀⠴⠠⠎⠈⠯⠠⠏⠼⠑⠚⠚⠨⠕⠠⠍⠉⠵⠀⠼` + - actual: `⠠⠕⠢⠺⠀⠴⠠⠎⠲⠴⠈⠯⠲⠴⠠⠏⠼⠑⠚⠚⠨⠕⠠⠍` - `sentence_01.json` #18: 2023년형 신제품은 매터(Matter)와 HCA(Home Connectivity Alliance) 표준을 지원하는 스마트싱스 허브(SmartThings Hub)를 기반으로 다양한 기기를 자동으로 연결하고, 제어·관리할 수 있는 것이 특징이다. - expected: `⠁⠞⠞⠻⠠⠴⠧⠀⠴⠠⠠⠓⠉⠁⠐⠣⠠⠓⠕⠍⠑⠀⠠⠒` - actual: `⠁⠞⠞⠻⠠⠴⠧⠀⠀⠠⠠⠓⠉⠁⠦⠠⠓⠕⠍⠑⠀⠴⠠⠒` @@ -3674,6 +3749,7 @@ Against the immediately preceding 63,399-exact run, exact matches increased by 2 | Rules 29/39 Roman-to-Korean mode ownership | 5,141/5,141 | 68,101/83,528 | 81.53% | Same-token Korean is wrapped only in a Roman-majority document or the official dot-delimited `www.대통령.kr` domain shape; all three rule-39 PDF controls remain exact and 386 corpus cases became exact | | Rules 29/34 multiword Roman parenthetical continuity | 5,141/5,141 | 68,175/83,528 | 81.62% | A backwards-verified, closed letter-and-space Roman parenthetical tail stays on the prose route; function calls, digits, operators, nested brackets, and punctuation-separated forms remain outside; 74 cases became exact | | Rules 29/32/71 ampersand before attached Roman segment | 5,141/5,141 | 68,187/83,528 | 81.63% | Official UEB `&c`, `AT&T`, and `B&B` keep one Roman section across attached `&`; the spaced Korean Rule-71 example, left ASCII alphanumerics, repeated ampersands, and trailing digits delimit the gate; 12 cases became exact | +| UEB 8.4.2 capitals-word nonletter boundary | 5,141/5,141 | 69,291/83,528 | 82.96% | Starting from the accepted 68,439-exact checkpoint, capitals-word handling ends at each nonletter and later uppercase runs restart independently; official `AT&T`, `B&B`, `MP3`, and `TVOntario` delimit the token/span boundary; 852 cases became exact and the complete exact-ID audit found zero former exact cases lost | The latest full `cargo test -p braillify test_by_testcase --release -- --nocapture` run was accepted from its custom testcase summary, not the trailing filtered harness: `총 테스트 케이스: 5141`, `성공: 5141`, `실패: 0`, and `Skip (limitation): 0`. diff --git a/libs/braillify/examples/nikl_corpus_analyze.rs b/libs/braillify/examples/nikl_corpus_analyze.rs index 51cc2ee8..a9f88a8b 100644 --- a/libs/braillify/examples/nikl_corpus_analyze.rs +++ b/libs/braillify/examples/nikl_corpus_analyze.rs @@ -596,6 +596,8 @@ const ATTACHED_ASCII_ROMAN_SEGMENTS_JOINED_BY_AMPERSAND: &str = "attached_ascii_roman_segments_joined_by_ampersand"; const UPPERCASE_ASCII_SEGMENTS_JOINED_BY_AMPERSAND: &str = "uppercase_ascii_segments_joined_by_ampersand_capitalization"; +const CAPITALS_WORD_NONLETTER_CHANGE_SCOPE: &str = + "capitals_word_mode_previously_spanning_nonletter_scope"; const AMPERSAND_BEFORE_ATTACHED_ASCII_ROMAN_SEGMENT: &str = "ampersand_before_attached_ascii_roman_segment"; const SPACED_COMMA_BETWEEN_ASCII_DIGIT_RUNS: &str = "spaced_comma_between_ascii_digit_runs"; @@ -1145,6 +1147,60 @@ fn uppercase_ascii_ampersand_spans(input: &str) -> Vec { .collect() } +/// Reproduces the complete input scope of the former token-level capitals-word +/// predicate where it can change output: a whitespace-delimited token starts +/// with ASCII, has no lowercase ASCII letters, and an uppercase letter occurs +/// again after an intervening nonletter. UEB 8.4.2 says that nonletter +/// terminates capitals word mode. A trailing digit or Korean suffix after the +/// final uppercase run is excluded because both old and new paths emit the +/// same capitals indicator before that run. This broad scope audit measures +/// regressions; unlike the ampersand cohort it does not claim that the first +/// difference belongs to one particular symbol. +fn capitals_word_nonletter_change_scope_spans(input: &str) -> Vec { + let mut spans = Vec::new(); + let mut start_byte = 0usize; + for (end_byte, ch) in input + .char_indices() + .chain(std::iter::once((input.len(), ' '))) + { + if ch != ' ' { + continue; + } + if start_byte < end_byte { + let token = &input[start_byte..end_byte]; + let token_chars = token.chars().collect::>(); + let ascii_letters = token_chars + .iter() + .copied() + .filter(|candidate| candidate.is_ascii_alphabetic()) + .collect::>(); + let uppercase_after_nonletter = token_chars.iter().enumerate().any(|(index, ch)| { + ch.is_ascii_uppercase() + && token_chars[..index] + .iter() + .any(|previous| !previous.is_ascii_alphabetic()) + }); + if token + .chars() + .next() + .is_some_and(|first| first.is_ascii_alphabetic()) + && ascii_letters.len() >= 2 + && ascii_letters + .iter() + .all(|letter| letter.is_ascii_uppercase()) + && uppercase_after_nonletter + { + spans.push(InputSpan { + start_byte, + end_byte, + }); + } + } + start_byte = end_byte + 1; + } + spans +} + /// Finds an ampersand immediately followed by a complete ASCII-letter segment /// when no ASCII alphanumeric precedes it. This is the one-sided shape of the /// UEB §3.1.1 `&c` example, kept separate from the already implemented `A&B` @@ -3579,6 +3635,10 @@ fn analyze( UPPERCASE_ASCII_SEGMENTS_JOINED_BY_AMPERSAND.to_string(), PendingRuleReviewClusterStats::default(), ), + ( + CAPITALS_WORD_NONLETTER_CHANGE_SCOPE.to_string(), + PendingRuleReviewClusterStats::default(), + ), ( ATTACHED_ASCII_ROMAN_SEGMENTS_JOINED_BY_AMPERSAND.to_string(), PendingRuleReviewClusterStats::default(), @@ -3832,6 +3892,12 @@ fn analyze( Some(first_difference_in_uppercase_ascii_ampersand(item)), true, ), + ( + CAPITALS_WORD_NONLETTER_CHANGE_SCOPE, + !capitals_word_nonletter_change_scope_spans(&item.located.case.input).is_empty(), + None, + false, + ), ( ATTACHED_ASCII_ROMAN_SEGMENTS_JOINED_BY_AMPERSAND, !attached_ascii_roman_ampersand_spans(&item.located.case.input).is_empty(), @@ -4564,6 +4630,11 @@ fn markdown(report: &AnalysisReport) -> String { token-level capitals-word pre-emission scope; Korean-attached and punctuation-prefixed \ occurrences remain controls. UEB 8.4.2 says a nonalphabetic symbol terminates capitals word mode, while the \ official `AT&T` and `B&B` examples restart capitalization after `&`. The broader \ + `capitals_word_mode_previously_spanning_nonletter_scope` gate reproduces the former \ + token predicate across ampersands, hyphens, digits, and other nonletters that separate \ + uppercase runs. Trailing nonletters after the final run are excluded because their \ + output is unchanged. It is a \ + broad change-scope/regression audit and is deliberately not an output-cause localizer. The \ `attached_ascii_roman_segments_joined_by_ampersand` gate requires non-empty ASCII-letter \ segments joined directly by `&`, excludes spaced/Korean/alphanumeric continuations, and \ localizes only the current output cell immediately before the ampersand through an \ @@ -5492,15 +5563,19 @@ fn markdown(report: &AnalysisReport) -> String { non-alphanumeric outer boundaries as the existing Roman-ampersand rule, and requires \ the run to begin its whitespace-delimited token. Korean-attached and \ punctuation-prefixed occurrences stay outside the change scope.\n\n\ - The current baseline contains {} candidates, {} exact controls, and {} mismatches. \ - Existing mismatch primaries remain {} `pending_rule_review`, {} `corpus_suspect`, {} \ + At the analyzer-only checkpoint this cohort contained 439 candidates, 1 exact \ + control, 438 mismatches, and 220 first differences localized inside the independently \ + reproduced Korean-context signature. After the general capitalization correction it \ + contains {} candidates, {} exact controls, and {} mismatches. Existing remaining \ + mismatch primaries are {} `pending_rule_review`, {} `corpus_suspect`, {} \ `comparison_method`, and {} `unsupported_character_review`. Of {} evaluable \ - mismatches, {} have their first difference inside the independently reproduced \ - Korean-context output signature. The cohort table above retains the transition \ - distribution and shard/index samples. Because capitalization extent is fixed by the \ - official symbol examples and does not require pronunciation or corpus semantics, this \ - is a high-confidence implementation candidate; the diagnostic itself does not change \ - any primary class.\n", + mismatches, {} have their first difference inside that signature. The sole pre-change \ + exact member contained lowercase Roman text later in the same whitespace token and \ + was outside the production predicate's actual change scope; its primary outcome was \ + preserved. The cohort table above retains the transition distribution and shard/index \ + samples. Capitalization extent is fixed by the official symbol examples and requires \ + neither pronunciation nor corpus semantics; the diagnostic never changes a primary \ + class.\n", stats.candidates, stats.exact, stats.mismatch, @@ -5512,6 +5587,31 @@ fn markdown(report: &AnalysisReport) -> String { stats.first_difference_in_output_signature, )); } + if let Some(stats) = report + .pending_rule_review_clusters + .get(CAPITALS_WORD_NONLETTER_CHANGE_SCOPE) + { + text.push_str(&format!( + "\n### Capitals-word nonletter change-scope audit\n\n\ + This input-only scope exactly mirrors the former token predicate where it could \ + incorrectly carry capitals-word handling across a nonletter. UEB 8.4.2 terminates \ + that mode at the nonletter. Trailing nonletters after the final uppercase run are \ + excluded because their output is unchanged. Before the correction all 1,733 \ + candidates were mismatches and none was exact. The current run has {} candidates, \ + {} exact controls, and {} mismatches. A complete exact-ID set audit found 852 newly \ + exact cases and zero cases lost from the 68,439-exact baseline, yielding \ + 69,291/83,528 (82.96%). Two deliberately rejected wider predicates exposed why the \ + boundary matters: requiring an entirely uppercase-only token lost 87 former exact \ + cases, while treating every initial uppercase run as token-level capitals mode lost \ + 33 mixed-case controls such as the UEB `TVOntario` class by bypassing its required \ + capitals terminator. The retained predicate pre-emits only when the initial run has \ + at least two capitals and every ASCII letter in the token is uppercase; Rule 28 \ + independently restarts capitalization after the nonletter. This cohort remains a \ + regression audit only: membership does not assign a primary class or attribute a \ + first difference.\n", + stats.candidates, stats.exact, stats.mismatch, + )); + } if let Some(stats) = report .pending_rule_review_clusters .get(ATTACHED_ASCII_ROMAN_SEGMENTS_JOINED_BY_AMPERSAND) @@ -6777,7 +6877,8 @@ fn markdown(report: &AnalysisReport) -> String { | Rules 29/71 complete attached Roman ampersand run | 5,141/5,141 | 67,715/83,528 | 81.07% | Complete ASCII-letter segments joined by `&` retain one Roman section; official `AT&T`, `B&B`, and spaced Korean Rule-71 controls delimit the safe boundary; 273 cases became exact |\n\ | Rules 29/39 Roman-to-Korean mode ownership | 5,141/5,141 | 68,101/83,528 | 81.53% | Same-token Korean is wrapped only in a Roman-majority document or the official dot-delimited `www.대통령.kr` domain shape; all three rule-39 PDF controls remain exact and 386 corpus cases became exact |\n\ | Rules 29/34 multiword Roman parenthetical continuity | 5,141/5,141 | 68,175/83,528 | 81.62% | A backwards-verified, closed letter-and-space Roman parenthetical tail stays on the prose route; function calls, digits, operators, nested brackets, and punctuation-separated forms remain outside; 74 cases became exact |\n\ - | Rules 29/32/71 ampersand before attached Roman segment | 5,141/5,141 | 68,187/83,528 | 81.63% | Official UEB `&c`, `AT&T`, and `B&B` keep one Roman section across attached `&`; the spaced Korean Rule-71 example, left ASCII alphanumerics, repeated ampersands, and trailing digits delimit the gate; 12 cases became exact |\n", + | Rules 29/32/71 ampersand before attached Roman segment | 5,141/5,141 | 68,187/83,528 | 81.63% | Official UEB `&c`, `AT&T`, and `B&B` keep one Roman section across attached `&`; the spaced Korean Rule-71 example, left ASCII alphanumerics, repeated ampersands, and trailing digits delimit the gate; 12 cases became exact |\n\ + | UEB 8.4.2 capitals-word nonletter boundary | 5,141/5,141 | 69,291/83,528 | 82.96% | Starting from the accepted 68,439-exact checkpoint, capitals-word handling ends at each nonletter and later uppercase runs restart independently; official `AT&T`, `B&B`, `MP3`, and `TVOntario` delimit the token/span boundary; 852 cases became exact and the complete exact-ID audit found zero former exact cases lost |\n", ); text.push_str( "\nThe latest full `cargo test -p braillify test_by_testcase --release -- --nocapture` \ @@ -7474,6 +7575,26 @@ mod tests { ); } + #[rstest::rstest] + #[case::ampersand("R&D", vec!["R&D"])] + #[case::attached_korean_suffix("S&P는", vec!["S&P는"])] + #[case::alphanumeric_restart("A1B", vec!["A1B"])] + #[case::pure_letters_excluded("PURE", vec![])] + #[case::korean_prefix_excluded("가(R&D)", vec![])] + #[case::lowercase_excluded("R&d", vec![])] + #[case::trailing_digit_excluded("MP3", vec![])] + #[case::trailing_korean_excluded("KDI에", vec![])] + fn detects_former_capitals_word_nonletter_change_scope( + #[case] input: &str, + #[case] expected: Vec<&str>, + ) { + let actual = capitals_word_nonletter_change_scope_spans(input) + .into_iter() + .map(|span| &input[span.start_byte..span.end_byte]) + .collect::>(); + assert_eq!(actual, expected); + } + #[rstest::rstest] #[case::whole_shortform("가(WD) 나", vec!["WD"])] #[case::longer_prefixes("PDS LLM GDP", vec!["PDS", "LLM", "GDP"])] diff --git a/libs/braillify/src/rules/korean/rule_28.rs b/libs/braillify/src/rules/korean/rule_28.rs index 5ec1919f..f25aaeed 100644 --- a/libs/braillify/src/rules/korean/rule_28.rs +++ b/libs/braillify/src/rules/korean/rule_28.rs @@ -113,8 +113,14 @@ impl BrailleRule for Rule28 { .take_while(|ch| ch.is_ascii_alphabetic()) .count(); let run = &ctx.word_chars[ctx.index..run_end]; - let caps_already_emitted = - ctx.is_all_uppercase && ctx.word_len() >= 2 && ctx.ascii_starts_at_beginning; + // The token rule pre-emits capitals-word mode only for the initial + // uppercase letters-sequence. UEB 8.4.2 ends that mode at a + // nonletter, so a later run (the final `T` in official `AT&T`) + // must produce its own capitalization indicator. + let caps_already_emitted = ctx.index == 0 + && ctx.is_all_uppercase + && ctx.word_len() >= 2 + && ctx.ascii_starts_at_beginning; let is_whole_lowercase_word = ctx.index == 0 && run_end == ctx.word_chars.len() && run.iter().all(|ch| ch.is_ascii_lowercase()); diff --git a/libs/braillify/src/rules/korean/rule_71.rs b/libs/braillify/src/rules/korean/rule_71.rs index 0844bfc8..bae13950 100644 --- a/libs/braillify/src/rules/korean/rule_71.rs +++ b/libs/braillify/src/rules/korean/rule_71.rs @@ -247,6 +247,20 @@ mod tests { assert_eq!(crate::encode_to_unicode(input).unwrap(), expected); } + /// UEB 8.4.2 ends capitals word mode at the nonalphabetic ampersand. + /// Wrapping the official UEB 3.1.1 examples in neutral Korean text proves + /// that the mixed-document rule-28/29 path restarts capitalization for the + /// next ASCII-letter segment while keeping one Roman section. + #[rstest::rstest] + #[case::official_at_and_t("가 AT&T 나", "⠫⠀⠴⠠⠠⠁⠞⠈⠯⠠⠞⠲⠀⠉")] + #[case::official_b_and_b("가 B&B 나", "⠫⠀⠴⠠⠃⠈⠯⠠⠃⠲⠀⠉")] + fn korean_wrapper_preserves_ampersand_capitalization_extent( + #[case] input: &str, + #[case] expected: &str, + ) { + assert_eq!(crate::encode_to_unicode(input).as_deref(), Ok(expected)); + } + /// Korean Rule 71's spaced Hangul example remains an independently /// delimited information symbol after the attached-Roman exception. #[test] diff --git a/libs/braillify/src/rules/token.rs b/libs/braillify/src/rules/token.rs index 7c9e84b0..880311f8 100644 --- a/libs/braillify/src/rules/token.rs +++ b/libs/braillify/src/rules/token.rs @@ -37,11 +37,24 @@ impl WordMeta { .iter() .any(|ch| (0xAC00..=0xD7A3).contains(&(*ch as u32))); let ascii_letter_count = chars.iter().filter(|ch| ch.is_ascii_alphabetic()).count(); - let uppercase_count = chars.iter().filter(|ch| ch.is_ascii_uppercase()).count(); let has_ascii_alphabetic = ascii_letter_count > 0; let starts_with_ascii = chars.first().is_some_and(char::is_ascii_alphabetic); - let is_all_uppercase = ascii_letter_count >= 2 && ascii_letter_count == uppercase_count; + // UEB 8.4.1-8.4.2 applies the capitals-word indicator to the initial + // uppercase letters-sequence and terminates it at the first nonletter. + // Korean rule 35's `MP3` therefore still pre-emits capitals-word mode + // for `MP`, while UEB's `B&B` does not do so for its one-letter prefix. + // Mixed-case forms stay on the span path, which can emit the required + // capitals terminator before a lowercase continuation (`TVOntario`). + let initial_uppercase_count = chars + .iter() + .take_while(|ch| ch.is_ascii_uppercase()) + .count(); + let all_ascii_letters_uppercase = chars + .iter() + .filter(|ch| ch.is_ascii_alphabetic()) + .all(|ch| ch.is_ascii_uppercase()); + let is_all_uppercase = initial_uppercase_count >= 2 && all_ascii_letters_uppercase; WordMeta { has_korean, @@ -266,6 +279,22 @@ mod tests { assert!(meta.is_all_uppercase); } + /// UEB 8.4.2 terminates capitals word mode at a nonalphabetic symbol. + /// The official UEB 3.1.1/8.4 and Korean rule-35 examples distinguish a + /// multi-letter initial sequence from a one-letter initial sequence. + #[rstest::rstest] + #[case::official_at_and_t("AT&T", true)] + #[case::official_b_and_b("B&B", false)] + #[case::official_mixed_case("TVOntario", false)] + #[case::rule35_mp3("MP3", true)] + fn word_meta_scopes_capitals_word_to_initial_letters_sequence( + #[case] input: &str, + #[case] expected: bool, + ) { + let chars = input.chars().collect::>(); + assert_eq!(WordMeta::from_chars(&chars).is_all_uppercase, expected); + } + #[test] fn word_meta_mixed() { let chars: Vec = "A한b".chars().collect(); From c3e8d09a14dc87458c7f8977718725e6090fcc08 Mon Sep 17 00:00:00 2001 From: devfive Date: Tue, 1 Sep 2026 18:24:10 +0900 Subject: [PATCH 56/77] Diagnose internal Roman apostrophes --- docs/corpus-analysis/NIKL_2025_V1.md | 125 ++++++++++++++---- .../braillify/examples/nikl_corpus_analyze.rs | 124 +++++++++++++++++ 2 files changed, 220 insertions(+), 29 deletions(-) diff --git a/docs/corpus-analysis/NIKL_2025_V1.md b/docs/corpus-analysis/NIKL_2025_V1.md index df131840..5b1ce543 100644 --- a/docs/corpus-analysis/NIKL_2025_V1.md +++ b/docs/corpus-analysis/NIKL_2025_V1.md @@ -307,17 +307,17 @@ This ranking removes only cases whose first difference is inside an existing out | 7 | `U+2811 ⠑ -> U+283B ⠻` | 101 | | 8 | `U+2800 ⠀ -> U+2832 ⠲` | 100 | | 9 | `U+2810 ⠐ -> U+2814 ⠔` | 98 | -| 10 | `U+2804 ⠄ -> U+2834 ⠴` | 88 | -| 11 | `U+2824 ⠤ -> U+2800 ⠀` | 80 | -| 12 | `U+2808 ⠈ -> U+2832 ⠲` | 79 | -| 13 | `U+2826 ⠦ -> U+2800 ⠀` | 79 | -| 14 | `U+280E ⠎ -> U+2829 ⠩` | 73 | -| 15 | `U+2834 ⠴ -> U+2820 ⠠` | 64 | -| 16 | `U+2811 ⠑ -> U+2822 ⠢` | 58 | -| 17 | `U+2824 ⠤ -> U+2834 ⠴` | 58 | -| 18 | `U+2830 ⠰ -> U+2820 ⠠` | 57 | -| 19 | `U+2832 ⠲ -> U+2800 ⠀` | 55 | -| 20 | `U+280A ⠊ -> U+2814 ⠔` | 51 | +| 10 | `U+2824 ⠤ -> U+2800 ⠀` | 80 | +| 11 | `U+2808 ⠈ -> U+2832 ⠲` | 79 | +| 12 | `U+2826 ⠦ -> U+2800 ⠀` | 79 | +| 13 | `U+280E ⠎ -> U+2829 ⠩` | 73 | +| 14 | `U+2834 ⠴ -> U+2820 ⠠` | 64 | +| 15 | `U+2811 ⠑ -> U+2822 ⠢` | 58 | +| 16 | `U+2824 ⠤ -> U+2834 ⠴` | 58 | +| 17 | `U+2830 ⠰ -> U+2820 ⠠` | 57 | +| 18 | `U+2832 ⠲ -> U+2800 ⠀` | 55 | +| 19 | `U+280A ⠊ -> U+2814 ⠔` | 51 | +| 20 | `U+2810 ⠐ -> U+2815 ⠕` | 48 | ### Residual `U+2810 ⠐ -> U+2832 ⠲` @@ -526,32 +526,32 @@ This ranking removes only cases whose first difference is inside an existing out - first differing cell (zero-based): 62 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -### Residual `U+2804 ⠄ -> U+2834 ⠴` +### Residual `U+2824 ⠤ -> U+2800 ⠀` -- `sentence_01.json` #67: LG전자는 오는 5일부터 8일까지 ‘CES 2023’이 열리는 미국 라스베이거스 컨벤션센터(LVCC)에 ‘라이프스굿(Life's Good)’을 소개하는 광고판을 설치한다고 3일 밝혔다. - - expected: `⠦⠄⠴⠠⠇⠊⠋⠑⠄⠎⠀⠠⠛⠙⠠⠴⠴⠄⠮⠀⠠⠥⠈⠗` - - actual: `⠦⠄⠴⠠⠇⠊⠋⠑⠴⠄⠰⠎⠀⠠⠛⠕⠕⠙⠠⠴⠴⠄⠮⠀` - - first differing cell (zero-based): 121 +- `sentence_01.json` #189: 라이엇게임즈가 ‘리그오브레전드(LoL) 2023 시즌’ 개막을 앞두고 브랜드 영상 ‘너만의 전설을 써봐 - 슈퍼 캐리’를 공개했다. + - expected: `⠞⠮⠀⠠⠠⠎⠘⠧⠤⠠⠩⠙⠎⠀⠋⠗⠐⠕⠴⠄⠐⠮⠀⠈` + - actual: `⠞⠮⠀⠠⠠⠎⠘⠧⠀⠤⠀⠠⠩⠙⠎⠀⠋⠗⠐⠕⠴⠄⠐⠮` + - first differing cell (zero-based): 96 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_02.json` #425: ‘제로(Zero)’는 ‘대전 0시 축제(Daejeon Zero O'clock Festival)’의 영문 이름에서 따왔으며, 축제장에서 3D홀로그램 미디어 콘텐츠 운영을 맡고 있는 애드테크 전문기업 코스윌에서 개발했다. - - expected: `⠀⠠⠵⠻⠕⠀⠠⠕⠄⠉⠇⠕⠉⠅⠀⠠⠋⠑⠌⠊⠧⠁⠇⠠` - - actual: `⠀⠠⠵⠻⠕⠀⠠⠕⠴⠄⠰⠉⠇⠕⠉⠅⠀⠠⠋⠑⠌⠊⠧⠁` - - first differing cell (zero-based): 56 +- `sentence_02.json` #2352: 광주시립미술관 광주미디어아트플랫폼 G.MAP(지맵)은 오는 9일 오후 2시 1층 미디어 라운지에서 미디어아트 특별전시 ‘이이남 -각 사람에게 비추는 빛’의 연계 행사로 작가와의 대화를 개최한다. + - expected: `⠕⠀⠠⠦⠕⠕⠉⠢⠤⠫⠁⠀⠇⠐⠣⠢⠝⠈⠝⠀⠘⠕⠰⠍` + - actual: `⠕⠀⠠⠦⠕⠕⠉⠢⠀⠤⠫⠁⠀⠇⠐⠣⠢⠝⠈⠝⠀⠘⠕⠰` + - first differing cell (zero-based): 124 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_03.json` #13417: 트랙리스트에 따르면 이번 앨범에는 첫 번째 트랙 ‘I'm Okay(아이엠 오케이)’를 시작으로 ‘오로라(AURORA)’, ‘PALACE(팰러스)’, ‘PARADE(퍼레이드)’까지 총 네 곡이 수록됐다. - - expected: `⠗⠁⠀⠠⠦⠴⠠⠊⠄⠍⠀⠠⠕⠅⠁⠽⠦⠄⠣⠕⠝⠢⠀⠥` - - actual: `⠗⠁⠀⠠⠦⠴⠠⠊⠴⠄⠰⠍⠀⠠⠕⠅⠁⠽⠦⠄⠣⠕⠝⠢` - - first differing cell (zero-based): 54 +- `sentence_03.json` #4223: 그룹 엑소(EXO)의 단독 여행 버라이어티 프로그램 ‘엑소의 사다리 타고 세계여행 – 거제&통영편’이 새로운 시즌으로 찾아온다. + - expected: `⠠⠝⠈⠌⠱⠚⠗⠶⠤⠈⠎⠨⠝⠴⠈⠯⠲⠓⠿⠻⠙⠡⠴⠄` + - actual: `⠠⠝⠈⠌⠱⠚⠗⠶⠀⠠⠤⠀⠈⠎⠨⠝⠴⠈⠯⠲⠓⠿⠻⠙` + - first differing cell (zero-based): 76 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_04.json` #4420: 이들은 지난 5일 자정 도쿄 아카사카 본사 건물 꼭대기에 설치된 회사 간판을 철거했다. 소속팀 중 ‘Johnny's(쟈니스)’ 포함된 그룹의 명도 차례대로 변경될 예정이다. - - expected: `⠴⠠⠚⠕⠓⠝⠝⠽⠄⠎⠦⠄⠨⠜⠉⠕⠠⠪⠠⠴⠴⠄⠀⠙` - - actual: `⠴⠠⠚⠕⠓⠝⠝⠽⠴⠄⠰⠎⠦⠄⠨⠜⠉⠕⠠⠪⠠⠴⠴⠄` - - first differing cell (zero-based): 97 +- `sentence_04.json` #976: 하쿠토-R 미션1에는 달 표면을 굴러다닐 로봇이 실려 있다. 일본 우주항공연구개발기구(JAXA)와 장난감 기업 토미가 함께 만든 지름 8㎝짜리 공 모양의 초소형 로봇 ‘소라큐’다. + - expected: `⠚⠋⠍⠓⠥⠤⠴⠠⠗⠲⠀⠑⠕⠠⠡⠼⠁⠝⠉⠵⠀⠊⠂⠀` + - actual: `⠚⠋⠍⠓⠥⠀⠀⠔⠠⠗⠀⠑⠕⠠⠡⠼⠁⠝⠉⠵⠀⠊⠂⠀` + - first differing cell (zero-based): 5 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` ## Cross-cutting input-only structural cohorts -These are cross-cutting input-only structural cohorts, not new primary classes and not engine routing rules. Candidate selection never changes a case's existing primary class by itself. Only cohort members already classified as `pending_rule_review` form a pending subcluster; exact and other-primary members are controls that retain their existing outcomes. A separate classifier may use independently justified, output-localized PDF evidence, as in the rule-34 three-cell contradiction below. The `uppercase_roman_headword_closed_multiword_parenthetical` gate requires a two-or-more character uppercase ASCII headword immediately followed by a closed parenthesis whose contents are two or more ASCII Roman words separated only by spaces. Because the contents admit only letters and spaces, visible operators, subscript/superscript notation, and nested parentheses are excluded deterministically. The `mixed_roman_korean_word_before_uppercase_headword_expansion` gate further requires a whitespace-delimited preceding word that contains both ASCII Roman and Korean and ends in Korean. It locates the following uppercase headword itself, including its immediately preceding emitted cell, so an earlier Roman entry in the same sentence cannot satisfy the output audit. The `single_capital_followed_by_parenthesized_digits` gate requires a standalone capital, a non-empty closed ASCII-digit parenthetical, and alphanumeric outer boundaries. It likewise includes the emitted entry-boundary cell in localization. The `uppercase_roman_run_followed_by_hyphen_digits` gate requires a maximal uppercase ASCII run, literal ASCII hyphen-minus, a non-empty digit run, and alphanumeric outer boundaries. Its localized range includes the current encoded run and its immediately preceding cell, keeping `F-35`-style routing separate from both parenthesized digits and headword expansions. The `uppercase_alphanumeric_roman_digit_sequence` gate covers the wider rule-35/math collision: a capital-led uppercase/digit sequence with optional internal ASCII hyphen/full stop and trailing prose comma/colon/semicolon. It localizes only the current entry cells and does not decide whether an `A-3`-shaped surface is an identifier or a mathematical expression. The `standalone_multi_character_uppercase_roman_word` gate finds maximal ASCII-letter runs of two or more capitals with non-alphanumeric boundaries; a run immediately followed by `(` is excluded so the HCA-style headword itself is not counted by both gates. The `korean_prefixed_closed_allcaps_parenthetical` gate requires an immediately preceding Korean character and a closed body of two or more uppercase ASCII letters. It intentionally contains both acronym annotations (`책임자(COO)`) and scientific formulae (`일산화탄소(CO)`) so their semantic collision remains measurable. The `korean_prefixed_closed_roman_annotation_rule_34_order` gate accepts the narrower rule-34 body grammar after an immediately preceding Korean character and localizes only the current engine's Korean opening-parenthesis cells. This distinguishes the PDF's parenthesis-before-Roman-indicator order from unrelated differences later in the same sentence. The `multi_character_allcaps_roman_runs_joined_by_middle_dot` gate requires two maximal ASCII-letter runs of at least two capitals joined directly by U+00B7, with non-alphanumeric outer boundaries. It records shapes such as `AI·SW` without assigning prose, mathematics, or science semantics. The `roman_run_immediately_before_attached_middle_dot_boundary` gate is output-localized: it requires a maximal ASCII-letter run immediately before U+00B7 and an attached Korean character or ASCII-letter run after it, then searches for that whole current-engine signature in the actual output. It therefore isolates the Roman terminator boundary without treating unrelated middle dots elsewhere in the sentence as causal. The `attached_ascii_roman_to_korean_script_boundary` gate requires an ASCII letter run immediately followed by Korean script and localizes only the current mode marker there: either rule 29's final `⠲` or rule 39's opening `⠸⠷`. It deliberately does not infer the dominant language of the sentence from that surface boundary. The `korean_majority_same_token_roman_sandwich_non_domain` scope audit mirrors the narrower rule-39 implementation gate: the nearest script characters on both sides of one Korean segment are ASCII Roman, the input is Korean-majority by first-script word counts, and the segment is not dot-delimited like the official domain example. It measures change scope but is not itself an output-localized causal classifier. The `korean_inline_parenthesized_single_arithmetic_operator` gate requires an immediate `Korean(` + one rule-45 arithmetic operator + `)Korean` span. Unlike broad coexistence traits, it also locates the current engine's emitted structure and counts a mismatch as signature-local only when the sentence's first differing cell falls inside that output range. The `allcaps_roman_run_containing_ou` gate finds maximal, alphanumeric-delimited uppercase ASCII runs containing adjacent `OU`. It locates the independently encoded run signature in the complete current output and counts only first differences inside that signature as localized. The `allcaps_roman_run_containing_st` gate applies the same output-position requirement to adjacent `ST`; it is kept separate because UEB 10.12 makes contraction use depend on how an abbreviation or acronym is pronounced. The `uppercase_ascii_segments_joined_by_ampersand_capitalization` gate narrows attached ampersand runs to uppercase-only segments and locates the complete current Korean-context output. The run must begin its whitespace-delimited token, matching the current token-level capitals-word pre-emission scope; Korean-attached and punctuation-prefixed occurrences remain controls. UEB 8.4.2 says a nonalphabetic symbol terminates capitals word mode, while the official `AT&T` and `B&B` examples restart capitalization after `&`. The broader `capitals_word_mode_previously_spanning_nonletter_scope` gate reproduces the former token predicate across ampersands, hyphens, digits, and other nonletters that separate uppercase runs. Trailing nonletters after the final run are excluded because their output is unchanged. It is a broad change-scope/regression audit and is deliberately not an output-cause localizer. The `attached_ascii_roman_segments_joined_by_ampersand` gate requires non-empty ASCII-letter segments joined directly by `&`, excludes spaced/Korean/alphanumeric continuations, and localizes only the current output cell immediately before the ampersand through an independently encoded real-input prefix. The `ampersand_before_attached_ascii_roman_segment` gate covers the distinct one-sided UEB `&c` boundary: `&` is followed by a complete ASCII-letter segment, while an ASCII alphanumeric or another ampersand immediately before it and a digit continuation after it are excluded. Its output range is anchored by independently encoding the real input prefix before each occurrence, then includes only the current Rule-71/29 entry boundary; a second pre-fix anchor through `&` retains the former exit location. The `consecutive_ascii_roman_words_whitespace_boundary` gate requires two adjacent ASCII-letter words separated only by whitespace. For each boundary it independently encodes the real input prefix ending after the first word, then localizes only the current rule-29 terminator at that position or the full output's replacing blank. This prevents an unrelated Roman occurrence elsewhere in the sentence from satisfying the audit. Slash and other punctuation-separated forms are deliberately excluded. The `decimal_point_between_ascii_digits` gate finds whitespace-delimited words containing `digit.digit` and reproduces each whole word in a neutral Korean context, so suffixes and punctuation remain part of the current-engine signature. The `compact_numeric_ascii_letter_suffix` gate finds a numeric prefix immediately followed by ASCII letters, includes the immediately preceding output cell as its entry boundary, and retains suffix-specific outcome counts. It intentionally includes both possible rule-69 units and ambiguous variable/identifier forms; membership alone does not assign unit semantics. The `rule69_ascii_unit_before_terminator_skipping_symbol` gate is narrower: it accepts only ASCII unit spellings already supported by rule 69, includes the immediately following rule-33/34 punctuation cell in the localized signature, and does not infer new units. The `tight_triangle_mark_immediately_before_korean` gate requires literal `△한글` with no input space and includes the first following Korean cell in its localized output range, so an observed missing-space difference is measured at the mark boundary. The `attached_korean_auxiliary_itda_spacing` gate mirrors the former normalization shape for a Korean token ending in attached `있다`; its localizer independently encodes the real prefix and claims only the blank inserted before the suffix. It does not decide whether orthographic correction may override the printed input. +These are cross-cutting input-only structural cohorts, not new primary classes and not engine routing rules. Candidate selection never changes a case's existing primary class by itself. Only cohort members already classified as `pending_rule_review` form a pending subcluster; exact and other-primary members are controls that retain their existing outcomes. A separate classifier may use independently justified, output-localized PDF evidence, as in the rule-34 three-cell contradiction below. The `uppercase_roman_headword_closed_multiword_parenthetical` gate requires a two-or-more character uppercase ASCII headword immediately followed by a closed parenthesis whose contents are two or more ASCII Roman words separated only by spaces. Because the contents admit only letters and spaces, visible operators, subscript/superscript notation, and nested parentheses are excluded deterministically. The `mixed_roman_korean_word_before_uppercase_headword_expansion` gate further requires a whitespace-delimited preceding word that contains both ASCII Roman and Korean and ends in Korean. It locates the following uppercase headword itself, including its immediately preceding emitted cell, so an earlier Roman entry in the same sentence cannot satisfy the output audit. The `single_capital_followed_by_parenthesized_digits` gate requires a standalone capital, a non-empty closed ASCII-digit parenthetical, and alphanumeric outer boundaries. It likewise includes the emitted entry-boundary cell in localization. The `uppercase_roman_run_followed_by_hyphen_digits` gate requires a maximal uppercase ASCII run, literal ASCII hyphen-minus, a non-empty digit run, and alphanumeric outer boundaries. Its localized range includes the current encoded run and its immediately preceding cell, keeping `F-35`-style routing separate from both parenthesized digits and headword expansions. The `uppercase_alphanumeric_roman_digit_sequence` gate covers the wider rule-35/math collision: a capital-led uppercase/digit sequence with optional internal ASCII hyphen/full stop and trailing prose comma/colon/semicolon. It localizes only the current entry cells and does not decide whether an `A-3`-shaped surface is an identifier or a mathematical expression. The `standalone_multi_character_uppercase_roman_word` gate finds maximal ASCII-letter runs of two or more capitals with non-alphanumeric boundaries; a run immediately followed by `(` is excluded so the HCA-style headword itself is not counted by both gates. The `korean_prefixed_closed_allcaps_parenthetical` gate requires an immediately preceding Korean character and a closed body of two or more uppercase ASCII letters. It intentionally contains both acronym annotations (`책임자(COO)`) and scientific formulae (`일산화탄소(CO)`) so their semantic collision remains measurable. The `korean_prefixed_closed_roman_annotation_rule_34_order` gate accepts the narrower rule-34 body grammar after an immediately preceding Korean character and localizes only the current engine's Korean opening-parenthesis cells. This distinguishes the PDF's parenthesis-before-Roman-indicator order from unrelated differences later in the same sentence. The `multi_character_allcaps_roman_runs_joined_by_middle_dot` gate requires two maximal ASCII-letter runs of at least two capitals joined directly by U+00B7, with non-alphanumeric outer boundaries. It records shapes such as `AI·SW` without assigning prose, mathematics, or science semantics. The `roman_run_immediately_before_attached_middle_dot_boundary` gate is output-localized: it requires a maximal ASCII-letter run immediately before U+00B7 and an attached Korean character or ASCII-letter run after it, then searches for that whole current-engine signature in the actual output. It therefore isolates the Roman terminator boundary without treating unrelated middle dots elsewhere in the sentence as causal. The `attached_ascii_roman_to_korean_script_boundary` gate requires an ASCII letter run immediately followed by Korean script and localizes only the current mode marker there: either rule 29's final `⠲` or rule 39's opening `⠸⠷`. It deliberately does not infer the dominant language of the sentence from that surface boundary. The `korean_majority_same_token_roman_sandwich_non_domain` scope audit mirrors the narrower rule-39 implementation gate: the nearest script characters on both sides of one Korean segment are ASCII Roman, the input is Korean-majority by first-script word counts, and the segment is not dot-delimited like the official domain example. It measures change scope but is not itself an output-localized causal classifier. The `korean_inline_parenthesized_single_arithmetic_operator` gate requires an immediate `Korean(` + one rule-45 arithmetic operator + `)Korean` span. Unlike broad coexistence traits, it also locates the current engine's emitted structure and counts a mismatch as signature-local only when the sentence's first differing cell falls inside that output range. The `allcaps_roman_run_containing_ou` gate finds maximal, alphanumeric-delimited uppercase ASCII runs containing adjacent `OU`. It locates the independently encoded run signature in the complete current output and counts only first differences inside that signature as localized. The `allcaps_roman_run_containing_st` gate applies the same output-position requirement to adjacent `ST`; it is kept separate because UEB 10.12 makes contraction use depend on how an abbreviation or acronym is pronounced. The `uppercase_ascii_segments_joined_by_ampersand_capitalization` gate narrows attached ampersand runs to uppercase-only segments and locates the complete current Korean-context output. The run must begin its whitespace-delimited token, matching the current token-level capitals-word pre-emission scope; Korean-attached and punctuation-prefixed occurrences remain controls. UEB 8.4.2 says a nonalphabetic symbol terminates capitals word mode, while the official `AT&T` and `B&B` examples restart capitalization after `&`. The broader `capitals_word_mode_previously_spanning_nonletter_scope` gate reproduces the former token predicate across ampersands, hyphens, digits, and other nonletters that separate uppercase runs. Trailing nonletters after the final run are excluded because their output is unchanged. It is a broad change-scope/regression audit and is deliberately not an output-cause localizer. The `attached_ascii_roman_segments_joined_by_ampersand` gate requires non-empty ASCII-letter segments joined directly by `&`, excludes spaced/Korean/alphanumeric continuations, and localizes only the current output cell immediately before the ampersand through an independently encoded real-input prefix. The `ampersand_before_attached_ascii_roman_segment` gate covers the distinct one-sided UEB `&c` boundary: `&` is followed by a complete ASCII-letter segment, while an ASCII alphanumeric or another ampersand immediately before it and a digit continuation after it are excluded. Its output range is anchored by independently encoding the real input prefix before each occurrence, then includes only the current Rule-71/29 entry boundary; a second pre-fix anchor through `&` retains the former exit location. The `ascii_apostrophe_between_ascii_letter_runs` gate requires a straight apostrophe with an ASCII letter immediately on both sides and expands only across those two letter runs. It excludes detached quotation marks and numeric measurement marks, then locates the complete current mixed-Korean output signature. UEB 8.4.2 directly supplies `O'Hara`, `DON'T`, and `THAT'S` as controls. The `consecutive_ascii_roman_words_whitespace_boundary` gate requires two adjacent ASCII-letter words separated only by whitespace. For each boundary it independently encodes the real input prefix ending after the first word, then localizes only the current rule-29 terminator at that position or the full output's replacing blank. This prevents an unrelated Roman occurrence elsewhere in the sentence from satisfying the audit. Slash and other punctuation-separated forms are deliberately excluded. The `decimal_point_between_ascii_digits` gate finds whitespace-delimited words containing `digit.digit` and reproduces each whole word in a neutral Korean context, so suffixes and punctuation remain part of the current-engine signature. The `compact_numeric_ascii_letter_suffix` gate finds a numeric prefix immediately followed by ASCII letters, includes the immediately preceding output cell as its entry boundary, and retains suffix-specific outcome counts. It intentionally includes both possible rule-69 units and ambiguous variable/identifier forms; membership alone does not assign unit semantics. The `rule69_ascii_unit_before_terminator_skipping_symbol` gate is narrower: it accepts only ASCII unit spellings already supported by rule 69, includes the immediately following rule-33/34 punctuation cell in the localized signature, and does not infer new units. The `tight_triangle_mark_immediately_before_korean` gate requires literal `△한글` with no input space and includes the first following Korean cell in its localized output range, so an observed missing-space difference is measured at the mark boundary. The `attached_korean_auxiliary_itda_spacing` gate mirrors the former normalization shape for a Korean token ending in attached `있다`; its localizer independently encodes the real prefix and claims only the blank inserted before the suffix. It does not decide whether orthographic correction may override the printed input. | Cluster | Candidates | Exact | Mismatch | Conflicting-reference cases | |---|---:|---:|---:|---:| @@ -561,6 +561,7 @@ These are cross-cutting input-only structural cohorts, not new primary classes a | `allcaps_roman_run_containing_ou` | 1816 | 67 | 1749 | 0 | | `allcaps_roman_run_containing_st` | 1479 | 700 | 779 | 0 | | `ampersand_before_attached_ascii_roman_segment` | 30 | 12 | 18 | 0 | +| `ascii_apostrophe_between_ascii_letter_runs` | 147 | 0 | 147 | 0 | | `ascii_roman_tail_comma_before_digit_korean_token` | 58 | 3 | 55 | 0 | | `attached_ascii_roman_segments_joined_by_ampersand` | 802 | 607 | 195 | 0 | | `attached_ascii_roman_to_korean_script_boundary` | 17693 | 13445 | 4248 | 0 | @@ -1059,6 +1060,68 @@ Representative `mismatch` samples: - first differing cell (zero-based): 33 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +### `ascii_apostrophe_between_ascii_letter_runs` + +Of the 147 candidates, 144 are the actual `pending_rule_review` subcluster. The other 3 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. + +For this output-signature audit, 147 mismatches were evaluable and 112 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. + +Localized first-difference transitions: + +- `U+2804 ⠄ -> U+2834 ⠴`: 82 +- `U+2804 ⠄ -> U+2820 ⠠`: 30 + +Mismatch primary-class distribution: + +- `corpus_suspect`: 3 +- `pending_rule_review`: 144 + +Representative `localized_mismatch` samples: + +- `sentence_01.json` #67: LG전자는 오는 5일부터 8일까지 ‘CES 2023’이 열리는 미국 라스베이거스 컨벤션센터(LVCC)에 ‘라이프스굿(Life's Good)’을 소개하는 광고판을 설치한다고 3일 밝혔다. + - expected: `⠦⠄⠴⠠⠇⠊⠋⠑⠄⠎⠀⠠⠛⠙⠠⠴⠴⠄⠮⠀⠠⠥⠈⠗` + - actual: `⠦⠄⠴⠠⠇⠊⠋⠑⠴⠄⠰⠎⠀⠠⠛⠕⠕⠙⠠⠴⠴⠄⠮⠀` + - first differing cell (zero-based): 121 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_02.json` #425: ‘제로(Zero)’는 ‘대전 0시 축제(Daejeon Zero O'clock Festival)’의 영문 이름에서 따왔으며, 축제장에서 3D홀로그램 미디어 콘텐츠 운영을 맡고 있는 애드테크 전문기업 코스윌에서 개발했다. + - expected: `⠀⠠⠵⠻⠕⠀⠠⠕⠄⠉⠇⠕⠉⠅⠀⠠⠋⠑⠌⠊⠧⠁⠇⠠` + - actual: `⠀⠠⠵⠻⠕⠀⠠⠕⠴⠄⠰⠉⠇⠕⠉⠅⠀⠠⠋⠑⠌⠊⠧⠁` + - first differing cell (zero-based): 56 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_03.json` #13417: 트랙리스트에 따르면 이번 앨범에는 첫 번째 트랙 ‘I'm Okay(아이엠 오케이)’를 시작으로 ‘오로라(AURORA)’, ‘PALACE(팰러스)’, ‘PARADE(퍼레이드)’까지 총 네 곡이 수록됐다. + - expected: `⠗⠁⠀⠠⠦⠴⠠⠊⠄⠍⠀⠠⠕⠅⠁⠽⠦⠄⠣⠕⠝⠢⠀⠥` + - actual: `⠗⠁⠀⠠⠦⠴⠠⠊⠴⠄⠰⠍⠀⠠⠕⠅⠁⠽⠦⠄⠣⠕⠝⠢` + - first differing cell (zero-based): 54 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_04.json` #446: 추경호 부총리 겸 기획재정부 장관은 3일 국제신용평가사 무디스(Moody's) 연례 협의단에 미국 인플레이션 감축법(IRA) 등에 따른 국내 기업의 불확실성이 상당 부분 해소됐다고 강조했다. + - expected: `⠄⠴⠠⠍⠕⠕⠙⠽⠄⠎⠠⠴⠀⠡⠐⠌⠀⠚⠱⠃⠺⠊⠒⠝` + - actual: `⠄⠴⠠⠍⠕⠕⠙⠽⠠⠦⠰⠎⠠⠴⠀⠡⠐⠌⠀⠚⠱⠃⠺⠊` + - first differing cell (zero-based): 71 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` + +Representative `mismatch` samples: + +- `sentence_01.json` #67: LG전자는 오는 5일부터 8일까지 ‘CES 2023’이 열리는 미국 라스베이거스 컨벤션센터(LVCC)에 ‘라이프스굿(Life's Good)’을 소개하는 광고판을 설치한다고 3일 밝혔다. + - expected: `⠦⠄⠴⠠⠇⠊⠋⠑⠄⠎⠀⠠⠛⠙⠠⠴⠴⠄⠮⠀⠠⠥⠈⠗` + - actual: `⠦⠄⠴⠠⠇⠊⠋⠑⠴⠄⠰⠎⠀⠠⠛⠕⠕⠙⠠⠴⠴⠄⠮⠀` + - first differing cell (zero-based): 121 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_02.json` #425: ‘제로(Zero)’는 ‘대전 0시 축제(Daejeon Zero O'clock Festival)’의 영문 이름에서 따왔으며, 축제장에서 3D홀로그램 미디어 콘텐츠 운영을 맡고 있는 애드테크 전문기업 코스윌에서 개발했다. + - expected: `⠀⠠⠵⠻⠕⠀⠠⠕⠄⠉⠇⠕⠉⠅⠀⠠⠋⠑⠌⠊⠧⠁⠇⠠` + - actual: `⠀⠠⠵⠻⠕⠀⠠⠕⠴⠄⠰⠉⠇⠕⠉⠅⠀⠠⠋⠑⠌⠊⠧⠁` + - first differing cell (zero-based): 56 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_03.json` #13417: 트랙리스트에 따르면 이번 앨범에는 첫 번째 트랙 ‘I'm Okay(아이엠 오케이)’를 시작으로 ‘오로라(AURORA)’, ‘PALACE(팰러스)’, ‘PARADE(퍼레이드)’까지 총 네 곡이 수록됐다. + - expected: `⠗⠁⠀⠠⠦⠴⠠⠊⠄⠍⠀⠠⠕⠅⠁⠽⠦⠄⠣⠕⠝⠢⠀⠥` + - actual: `⠗⠁⠀⠠⠦⠴⠠⠊⠴⠄⠰⠍⠀⠠⠕⠅⠁⠽⠦⠄⠣⠕⠝⠢` + - first differing cell (zero-based): 54 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_04.json` #446: 추경호 부총리 겸 기획재정부 장관은 3일 국제신용평가사 무디스(Moody's) 연례 협의단에 미국 인플레이션 감축법(IRA) 등에 따른 국내 기업의 불확실성이 상당 부분 해소됐다고 강조했다. + - expected: `⠄⠴⠠⠍⠕⠕⠙⠽⠄⠎⠠⠴⠀⠡⠐⠌⠀⠚⠱⠃⠺⠊⠒⠝` + - actual: `⠄⠴⠠⠍⠕⠕⠙⠽⠠⠦⠰⠎⠠⠴⠀⠡⠐⠌⠀⠚⠱⠃⠺⠊` + - first differing cell (zero-based): 71 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` + ### `ascii_roman_tail_comma_before_digit_korean_token` Of the 58 candidates, 53 are the actual `pending_rule_review` subcluster. The other 5 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. @@ -3406,6 +3469,10 @@ The implemented rule is limited to an ampersand followed by a complete attached Of 18 current evaluable mismatches, 4 are localized to the occurrence-specific entry signature: 3 `U+2808 ⠈ -> U+2834 ⠴` where a parenthesized `&TEAM` reference omits Rule 71's required Roman indicator, and 1 `U+2834 ⠴ -> U+2820 ⠠` where a `드림&Dream` reference inserts another Roman indicator inside the same continuous section. Their raw-to-residual counts are 7 -> 4 and 70 -> 64; the corresponding reverse maps are 0 -> 0 and 56 -> 38. These four cases remain conservative corpus/PDF-reference review rather than widening or undoing the rule. Exact samples such as `&TEAM`, `과학&ICT`, and `한국&K리츠`, with shard/index above, are current controls. Primary classifications are never changed by this cohort. +### ASCII apostrophe between Roman letter runs + +This output-localized cohort requires a straight ASCII apostrophe with an ASCII letter immediately on both sides. UEB 8.4.2 (2024 UEB PDF pp.118-119, printed pp.90-91) directly prints `O'Hara`, `DON'T`, and `THAT'S` with the apostrophe cell inside the same Roman word; capitals-word mode may end at the apostrophe, but the Roman section itself does not. Detached quotation marks, Korean single quotation marks, and digit-adjacent measurement signs are excluded. The current diagnostic has 147 candidates, 0 exact controls, and 147 mismatches. Of 147 evaluable mismatches, 112 place their first difference inside the independently encoded mixed-Korean signature. Membership preserves every primary class. + ### Spaced comma between ASCII digit runs This output-localized cohort requires a comma immediately after an ASCII digit, one or more following whitespace characters, and another ASCII digit. Korean rule 41 (2024 Korean-rules PDF p.33, printed p.27) assigns `⠂` only when the comma is *attached* between digits, as in the exact control `9,375명`; rule 49 (PDF pp.37-38, printed pp.31-32) assigns the ordinary Korean comma `⠐`, illustrated by `근면, 검소, 협동은 ...`. Rule 33 (PDF p.28, printed p.22) independently keeps Korean punctuation at Roman-to-Korean boundaries. By contrast, official UEB 7 (2024 UEB PDF p.103, printed p.75) defines its prose comma as `⠂`, and UEB 6.2.1 (PDF p.94, printed p.66) retains `⠂` inside attached numeric forms such as `3,500`. Those two surfaces are negative controls and are excluded by this gate. diff --git a/libs/braillify/examples/nikl_corpus_analyze.rs b/libs/braillify/examples/nikl_corpus_analyze.rs index a9f88a8b..2100ae88 100644 --- a/libs/braillify/examples/nikl_corpus_analyze.rs +++ b/libs/braillify/examples/nikl_corpus_analyze.rs @@ -600,6 +600,7 @@ const CAPITALS_WORD_NONLETTER_CHANGE_SCOPE: &str = "capitals_word_mode_previously_spanning_nonletter_scope"; const AMPERSAND_BEFORE_ATTACHED_ASCII_ROMAN_SEGMENT: &str = "ampersand_before_attached_ascii_roman_segment"; +const ASCII_APOSTROPHE_BETWEEN_ASCII_LETTERS: &str = "ascii_apostrophe_between_ascii_letter_runs"; const SPACED_COMMA_BETWEEN_ASCII_DIGIT_RUNS: &str = "spaced_comma_between_ascii_digit_runs"; const ASCII_ROMAN_COMMA_BEFORE_DIGIT_KOREAN_TOKEN: &str = "ascii_roman_tail_comma_before_digit_korean_token"; @@ -1147,6 +1148,42 @@ fn uppercase_ascii_ampersand_spans(input: &str) -> Vec { .collect() } +/// Finds a straight ASCII apostrophe joining non-empty ASCII-letter runs. +/// UEB 8.4.2 prints this structure inside `O'Hara`, `DON'T`, and `THAT'S`; +/// detached quotation marks, measurement marks, and Korean quote punctuation +/// are excluded by the immediate-letter requirement. +fn ascii_internal_apostrophe_spans(input: &str) -> Vec { + let indexed = input.char_indices().collect::>(); + let mut spans = BTreeSet::new(); + for index in 1..indexed.len().saturating_sub(1) { + if indexed[index].1 != '\'' + || !indexed[index - 1].1.is_ascii_alphabetic() + || !indexed[index + 1].1.is_ascii_alphabetic() + { + continue; + } + let mut start = index - 1; + while start > 0 && indexed[start - 1].1.is_ascii_alphabetic() { + start -= 1; + } + let mut end = index + 2; + while end < indexed.len() && indexed[end].1.is_ascii_alphabetic() { + end += 1; + } + spans.insert(( + indexed[start].0, + indexed.get(end).map_or(input.len(), |(byte, _)| *byte), + )); + } + spans + .into_iter() + .map(|(start_byte, end_byte)| InputSpan { + start_byte, + end_byte, + }) + .collect() +} + /// Reproduces the complete input scope of the former token-level capitals-word /// predicate where it can change output: a whitespace-delimited token starts /// with ASCII, has no lowercase ASCII letters, and an uppercase letter occurs @@ -2560,6 +2597,37 @@ fn first_difference_in_korean_context_signature_spans( .any(|range| range.contains(&first_difference)) } +fn first_difference_at_ascii_internal_apostrophe(item: &EncodedCase) -> bool { + let Ok(actual) = &item.actual else { + return false; + }; + if actual == &item.located.case.unicode { + return false; + } + let first_difference = first_difference_cell(&item.located.case.unicode, actual); + let actual_len = actual.chars().count(); + ascii_internal_apostrophe_spans(&item.located.case.input) + .into_iter() + .filter_map(|span| { + let relative = item.located.case.input[span.start_byte..span.end_byte].find('\'')?; + let apostrophe_byte = span.start_byte + relative; + let prefix = + braillify::encode_to_unicode(&item.located.case.input[..apostrophe_byte]).ok()?; + let prefix_len = prefix.chars().count(); + let common = prefix + .chars() + .zip(actual.chars()) + .take_while(|(left, right)| left == right) + .count(); + // Encoding a real prefix in isolation may append only its final + // Roman boundary. Reject anchors that diverge earlier, because an + // unrelated prior mismatch must not be attributed here. + (prefix_len.saturating_sub(common) <= 2) + .then(|| common..std::cmp::min(common + 5, actual_len)) + }) + .any(|range| range.contains(&first_difference)) +} + /// Only output-localized cohorts may claim a first difference. Broad input-only /// coexistence traits are intentionally absent: excluding them would hide /// unrelated causes merely because a sentence also contains Roman text. @@ -2572,6 +2640,7 @@ fn first_difference_claimed_by_prior_localized_cohort(item: &EncodedCase) -> boo || first_difference_in_tight_triangle(item) || first_difference_at_roman_middle_dot_boundary(item) || first_difference_in_uppercase_ascii_ampersand(item) + || first_difference_at_ascii_internal_apostrophe(item) || first_difference_in_signature_spans( item, &single_capital_parenthesized_digit_spans(&item.located.case.input), @@ -3647,6 +3716,10 @@ fn analyze( AMPERSAND_BEFORE_ATTACHED_ASCII_ROMAN_SEGMENT.to_string(), PendingRuleReviewClusterStats::default(), ), + ( + ASCII_APOSTROPHE_BETWEEN_ASCII_LETTERS.to_string(), + PendingRuleReviewClusterStats::default(), + ), ( SPACED_COMMA_BETWEEN_ASCII_DIGIT_RUNS.to_string(), PendingRuleReviewClusterStats::default(), @@ -3916,6 +3989,12 @@ fn analyze( ), true, ), + ( + ASCII_APOSTROPHE_BETWEEN_ASCII_LETTERS, + !ascii_internal_apostrophe_spans(&item.located.case.input).is_empty(), + Some(first_difference_at_ascii_internal_apostrophe(item)), + true, + ), ( SPACED_COMMA_BETWEEN_ASCII_DIGIT_RUNS, !spaced_comma_between_ascii_digit_run_spans(&item.located.case.input).is_empty(), @@ -4645,6 +4724,11 @@ fn markdown(report: &AnalysisReport) -> String { it are excluded. Its output range is anchored by independently encoding the real input \ prefix before each occurrence, then includes only the current Rule-71/29 entry \ boundary; a second pre-fix anchor through `&` retains the former exit location. The \ + `ascii_apostrophe_between_ascii_letter_runs` gate requires a straight apostrophe with \ + an ASCII letter immediately on both sides and expands only across those two letter \ + runs. It excludes detached quotation marks and numeric measurement marks, then locates \ + the complete current mixed-Korean output signature. UEB 8.4.2 directly supplies \ + `O'Hara`, `DON'T`, and `THAT'S` as controls. The \ `consecutive_ascii_roman_words_whitespace_boundary` gate requires two adjacent \ ASCII-letter words separated only by whitespace. For each boundary it independently \ encodes the real input prefix ending after the first word, then localizes only the \ @@ -5790,6 +5874,28 @@ fn markdown(report: &AnalysisReport) -> String { residual_count(duplicate_entry_reverse), )); } + if let Some(stats) = report + .pending_rule_review_clusters + .get(ASCII_APOSTROPHE_BETWEEN_ASCII_LETTERS) + { + text.push_str(&format!( + "\n### ASCII apostrophe between Roman letter runs\n\n\ + This output-localized cohort requires a straight ASCII apostrophe with an ASCII \ + letter immediately on both sides. UEB 8.4.2 (2024 UEB PDF pp.118-119, printed \ + pp.90-91) directly prints `O'Hara`, `DON'T`, and `THAT'S` with the apostrophe cell \ + inside the same Roman word; capitals-word mode may end at the apostrophe, but the \ + Roman section itself does not. Detached quotation marks, Korean single quotation \ + marks, and digit-adjacent measurement signs are excluded. The current diagnostic \ + has {} candidates, {} exact controls, and {} mismatches. Of {} evaluable mismatches, \ + {} place their first difference inside the independently encoded mixed-Korean \ + signature. Membership preserves every primary class.\n", + stats.candidates, + stats.exact, + stats.mismatch, + stats.output_signature_mismatches_evaluated, + stats.first_difference_in_output_signature, + )); + } if let Some(stats) = report .pending_rule_review_clusters .get(SPACED_COMMA_BETWEEN_ASCII_DIGIT_RUNS) @@ -7462,6 +7568,24 @@ mod tests { assert_eq!(actual, expected); } + #[rstest::rstest] + #[case::official_name("O'Hara", vec!["O'Hara"])] + #[case::official_contraction("DON'T", vec!["DON'T"])] + #[case::official_possessive("THAT'S", vec!["THAT'S"])] + #[case::detached_quotes("rock 'n' roll", vec![])] + #[case::measurement_mark("6' 2", vec![])] + #[case::korean_single_quotes("‘가’", vec![])] + fn detects_ascii_apostrophe_between_ascii_letter_runs( + #[case] input: &str, + #[case] expected: Vec<&str>, + ) { + let actual = ascii_internal_apostrophe_spans(input) + .into_iter() + .map(|span| &input[span.start_byte..span.end_byte]) + .collect::>(); + assert_eq!(actual, expected); + } + #[rstest::rstest] #[case::numeric_list("17, 16, 15", vec![", 1", ", 1"])] #[case::attached_rule41_number("9,375명", vec![])] From 417747f88248cebe0efee95cbb6a980a51983762 Mon Sep 17 00:00:00 2001 From: devfive Date: Tue, 1 Sep 2026 19:04:25 +0900 Subject: [PATCH 57/77] Keep internal apostrophes in Roman sections --- docs/corpus-analysis/NIKL_2025_V1.md | 394 +++++++++--------- .../braillify/examples/nikl_corpus_analyze.rs | 28 +- libs/braillify/src/english_logic.rs | 45 ++ libs/braillify/src/rules/korean/rule_28.rs | 25 +- .../src/rules/korean/rule_english_symbol.rs | 11 +- 5 files changed, 307 insertions(+), 196 deletions(-) diff --git a/docs/corpus-analysis/NIKL_2025_V1.md b/docs/corpus-analysis/NIKL_2025_V1.md index 5b1ce543..978259a0 100644 --- a/docs/corpus-analysis/NIKL_2025_V1.md +++ b/docs/corpus-analysis/NIKL_2025_V1.md @@ -7,9 +7,9 @@ | Metric | Count | |---|---:| | Total | 83528 | -| Exact | 69291 | -| Mismatch | 14237 | -| Exact accuracy | 82.96% | +| Exact | 69359 | +| Mismatch | 14169 | +| Exact accuracy | 83.04% | | Duplicate records | 0 | | Inputs with conflicting references | 0 | @@ -20,19 +20,19 @@ Primary classes are evidence gates, not permissions to change the engine. `imple | Primary class | Count | |---|---:| | `comparison_method` | 312 | -| `corpus_suspect` | 1111 | -| `exact` | 69291 | -| `pending_rule_review` | 12655 | +| `corpus_suspect` | 1112 | +| `exact` | 69359 | +| `pending_rule_review` | 12586 | | `unsupported_character_review` | 159 | | Reproducible reason | Count | |---|---:| -| `exact` | 69291 | -| `foreign_text_rule_review` | 12503 | +| `exact` | 69359 | +| `foreign_text_rule_review` | 12434 | | `nfkc_input_equivalent` | 312 | | `number_rule_review` | 144 | | `punctuation_rule_review` | 8 | -| `rule34_roman_indicator_before_opening_parenthesis` | 1111 | +| `rule34_roman_indicator_before_opening_parenthesis` | 1112 | | `unsupported_character_review` | 159 | ## Pending first-difference cell transitions @@ -41,21 +41,21 @@ This ranking is a diagnostic selector, not an implementation rule. It counts onl | Rank | Expected → actual first cell | Cases | |---:|---|---:| -| 1 | `U+2834 ⠴ -> U+2800 ⠀` | 2173 | +| 1 | `U+2834 ⠴ -> U+2800 ⠀` | 2174 | | 2 | `U+2815 ⠕ -> U+2833 ⠳` | 1360 | | 3 | `U+2830 ⠰ -> U+2820 ⠠` | 868 | -| 4 | `U+2810 ⠐ -> U+2832 ⠲` | 720 | -| 5 | `U+2826 ⠦ -> U+2834 ⠴` | 470 | -| 6 | `U+280E ⠎ -> U+280C ⠌` | 454 | +| 4 | `U+2810 ⠐ -> U+2832 ⠲` | 721 | +| 5 | `U+2826 ⠦ -> U+2834 ⠴` | 471 | +| 6 | `U+280E ⠎ -> U+280C ⠌` | 464 | | 7 | `U+2826 ⠦ -> U+2800 ⠀` | 445 | | 8 | `U+2801 ⠁ -> U+281C ⠜` | 408 | | 9 | `U+2800 ⠀ -> U+2807 ⠇` | 365 | | 10 | `U+283C ⠼ -> U+2800 ⠀` | 365 | -| 11 | `U+2834 ⠴ -> U+2830 ⠰` | 357 | +| 11 | `U+2834 ⠴ -> U+2830 ⠰` | 359 | | 12 | `U+2811 ⠑ -> U+282B ⠫` | 330 | | 13 | `U+2820 ⠠ -> U+2830 ⠰` | 245 | | 14 | `U+2826 ⠦ -> U+2810 ⠐` | 174 | -| 15 | `U+2800 ⠀ -> U+2832 ⠲` | 157 | +| 15 | `U+2800 ⠀ -> U+2832 ⠲` | 158 | | 16 | `U+2820 ⠠ -> U+2809 ⠉` | 151 | | 17 | `U+281B ⠛ -> U+2823 ⠣` | 144 | | 18 | `U+2810 ⠐ -> U+2800 ⠀` | 131 | @@ -300,12 +300,12 @@ This ranking removes only cases whose first difference is inside an existing out |---:|---|---:| | 1 | `U+2810 ⠐ -> U+2832 ⠲` | 249 | | 2 | `U+283C ⠼ -> U+2800 ⠀` | 159 | -| 3 | `U+2834 ⠴ -> U+2800 ⠀` | 154 | +| 3 | `U+2834 ⠴ -> U+2800 ⠀` | 155 | | 4 | `U+281B ⠛ -> U+2823 ⠣` | 144 | | 5 | `U+2826 ⠦ -> U+2810 ⠐` | 133 | | 6 | `U+2810 ⠐ -> U+2800 ⠀` | 131 | -| 7 | `U+2811 ⠑ -> U+283B ⠻` | 101 | -| 8 | `U+2800 ⠀ -> U+2832 ⠲` | 100 | +| 7 | `U+2800 ⠀ -> U+2832 ⠲` | 101 | +| 8 | `U+2811 ⠑ -> U+283B ⠻` | 101 | | 9 | `U+2810 ⠐ -> U+2814 ⠔` | 98 | | 10 | `U+2824 ⠤ -> U+2800 ⠀` | 80 | | 11 | `U+2808 ⠈ -> U+2832 ⠲` | 79 | @@ -457,29 +457,6 @@ This ranking removes only cases whose first difference is inside an existing out - first differing cell (zero-based): 112 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -### Residual `U+2811 ⠑ -> U+283B ⠻` - -- `sentence_01.json` #1903: 김현용 현대차증권 연구원은 “전사적자원관리(ERP), 생산관리프로그램(MES) 등 기존의 기업향 솔루션에 이번 인수로 구매공급망관리(SRM)이 더해지며 사업 포트폴리오가 한층 단단해진 것으로 판단된다”고 설명했다. - - expected: `⠒⠐⠕⠦⠄⠴⠠⠠⠑⠗⠏⠠⠴⠐⠀⠠⠗⠶⠇⠒⠈⠧⠒⠐` - - actual: `⠒⠐⠕⠦⠄⠴⠠⠠⠻⠏⠠⠴⠐⠀⠠⠗⠶⠇⠒⠈⠧⠒⠐⠕` - - first differing cell (zero-based): 48 - - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_02.json` #672: 충남교육청은 사무행정(태안여고), ERP(천안여자상업고) 및 대회홍보크리에이터(논산여자상업고) 3개 종목에서 대상인 교육부장관상을 받았고 △금상 6개 △은상 16개 △동상 23개 등 총 48개 수상의 영예를 누렸다. - - expected: `⠥⠠⠴⠐⠀⠴⠠⠠⠑⠗⠏⠦⠄⠰⠾⠣⠒⠱⠨⠇⠶⠎⠃⠈` - - actual: `⠥⠠⠴⠐⠀⠴⠠⠠⠻⠏⠦⠄⠰⠾⠣⠒⠱⠨⠇⠶⠎⠃⠈⠥` - - first differing cell (zero-based): 37 - - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_03.json` #751: 올 하반기 위하고 기반 차세대 ‘스마트A 10’으로 본격적인 고객 전환이 예정된 가운데 세무회계사무소 전용 전사적자원관리 시스템(ERP)인 ‘위하고T’와 개인용 앱 ‘나하고(NAHAGO)’도 업계에서 주목받고 있다. - - expected: `⠓⠝⠢⠦⠄⠴⠠⠠⠑⠗⠏⠠⠴⠟⠀⠠⠦⠍⠗⠚⠈⠥⠴⠠` - - actual: `⠓⠝⠢⠦⠄⠴⠠⠠⠻⠏⠠⠴⠟⠀⠠⠦⠍⠗⠚⠈⠥⠴⠠⠞` - - first differing cell (zero-based): 127 - - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_04.json` #4038: 현대건설은 현지 건설사 이알버드(ERBUD), 유니베프(UNIBEP)와 신재생에너지 및 인프라 분야 협력에 관한 업무협약도 체결했다. 이를 통해 신공항, 도심 인프라, 스마트시티 분야 관련 사업을 함께 추진한다. - - expected: `⠎⠊⠪⠦⠄⠴⠠⠠⠑⠗⠃⠥⠙⠠⠴⠐⠀⠩⠉⠕⠘⠝⠙⠪` - - actual: `⠎⠊⠪⠦⠄⠴⠠⠠⠻⠃⠥⠙⠠⠴⠐⠀⠩⠉⠕⠘⠝⠙⠪⠦` - - first differing cell (zero-based): 33 - - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` - ### Residual `U+2800 ⠀ -> U+2832 ⠲` - `sentence_01.json` #1701: 금호석유화학은 앞으로도 추가적인 등급 상승을 이뤄간다는 방침이다. 이를 위해 직·간접배출(scope 1·2) 뿐 아니라 기타간접배출(scope3)도 제3자 기관의 검증을 통해 더욱 철저히 관리할 예정이다. @@ -503,6 +480,29 @@ This ranking removes only cases whose first difference is inside an existing out - first differing cell (zero-based): 54 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +### Residual `U+2811 ⠑ -> U+283B ⠻` + +- `sentence_01.json` #1903: 김현용 현대차증권 연구원은 “전사적자원관리(ERP), 생산관리프로그램(MES) 등 기존의 기업향 솔루션에 이번 인수로 구매공급망관리(SRM)이 더해지며 사업 포트폴리오가 한층 단단해진 것으로 판단된다”고 설명했다. + - expected: `⠒⠐⠕⠦⠄⠴⠠⠠⠑⠗⠏⠠⠴⠐⠀⠠⠗⠶⠇⠒⠈⠧⠒⠐` + - actual: `⠒⠐⠕⠦⠄⠴⠠⠠⠻⠏⠠⠴⠐⠀⠠⠗⠶⠇⠒⠈⠧⠒⠐⠕` + - first differing cell (zero-based): 48 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_02.json` #672: 충남교육청은 사무행정(태안여고), ERP(천안여자상업고) 및 대회홍보크리에이터(논산여자상업고) 3개 종목에서 대상인 교육부장관상을 받았고 △금상 6개 △은상 16개 △동상 23개 등 총 48개 수상의 영예를 누렸다. + - expected: `⠥⠠⠴⠐⠀⠴⠠⠠⠑⠗⠏⠦⠄⠰⠾⠣⠒⠱⠨⠇⠶⠎⠃⠈` + - actual: `⠥⠠⠴⠐⠀⠴⠠⠠⠻⠏⠦⠄⠰⠾⠣⠒⠱⠨⠇⠶⠎⠃⠈⠥` + - first differing cell (zero-based): 37 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_03.json` #751: 올 하반기 위하고 기반 차세대 ‘스마트A 10’으로 본격적인 고객 전환이 예정된 가운데 세무회계사무소 전용 전사적자원관리 시스템(ERP)인 ‘위하고T’와 개인용 앱 ‘나하고(NAHAGO)’도 업계에서 주목받고 있다. + - expected: `⠓⠝⠢⠦⠄⠴⠠⠠⠑⠗⠏⠠⠴⠟⠀⠠⠦⠍⠗⠚⠈⠥⠴⠠` + - actual: `⠓⠝⠢⠦⠄⠴⠠⠠⠻⠏⠠⠴⠟⠀⠠⠦⠍⠗⠚⠈⠥⠴⠠⠞` + - first differing cell (zero-based): 127 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_04.json` #4038: 현대건설은 현지 건설사 이알버드(ERBUD), 유니베프(UNIBEP)와 신재생에너지 및 인프라 분야 협력에 관한 업무협약도 체결했다. 이를 통해 신공항, 도심 인프라, 스마트시티 분야 관련 사업을 함께 추진한다. + - expected: `⠎⠊⠪⠦⠄⠴⠠⠠⠑⠗⠃⠥⠙⠠⠴⠐⠀⠩⠉⠕⠘⠝⠙⠪` + - actual: `⠎⠊⠪⠦⠄⠴⠠⠠⠻⠃⠥⠙⠠⠴⠐⠀⠩⠉⠕⠘⠝⠙⠪⠦` + - first differing cell (zero-based): 33 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` + ### Residual `U+2810 ⠐ -> U+2814 ⠔` - `sentence_01.json` #10236: 정현찬(75) 전 문재인 대통령 직속 농어업·농어촌특별위원회(농특위) 위원장이 <여럿이 함께 기어이 벽을 넘는, 농민이 잘 사는 나라>라는 제목으로 낸 회고록에서 강조한 말이다. @@ -555,53 +555,53 @@ These are cross-cutting input-only structural cohorts, not new primary classes a | Cluster | Candidates | Exact | Mismatch | Conflicting-reference cases | |---|---:|---:|---:|---:| -| `allcaps_roman_run_beginning_with_pure_letter_shortform` | 4120 | 2515 | 1605 | 0 | +| `allcaps_roman_run_beginning_with_pure_letter_shortform` | 4120 | 2516 | 1604 | 0 | | `allcaps_roman_run_containing_ar` | 1022 | 370 | 652 | 0 | | `allcaps_roman_run_containing_ed` | 816 | 334 | 482 | 0 | -| `allcaps_roman_run_containing_ou` | 1816 | 67 | 1749 | 0 | -| `allcaps_roman_run_containing_st` | 1479 | 700 | 779 | 0 | +| `allcaps_roman_run_containing_ou` | 1816 | 70 | 1746 | 0 | +| `allcaps_roman_run_containing_st` | 1479 | 702 | 777 | 0 | | `ampersand_before_attached_ascii_roman_segment` | 30 | 12 | 18 | 0 | -| `ascii_apostrophe_between_ascii_letter_runs` | 147 | 0 | 147 | 0 | +| `ascii_apostrophe_between_ascii_letter_runs` | 147 | 68 | 79 | 0 | | `ascii_roman_tail_comma_before_digit_korean_token` | 58 | 3 | 55 | 0 | | `attached_ascii_roman_segments_joined_by_ampersand` | 802 | 607 | 195 | 0 | -| `attached_ascii_roman_to_korean_script_boundary` | 17693 | 13445 | 4248 | 0 | +| `attached_ascii_roman_to_korean_script_boundary` | 17693 | 13451 | 4242 | 0 | | `attached_korean_auxiliary_itda_spacing` | 95 | 72 | 23 | 0 | | `capitals_word_mode_previously_spanning_nonletter_scope` | 1733 | 852 | 881 | 0 | -| `closed_roman_parenthetical_after_non_ascii_letter_boundary` | 63959 | 52997 | 10962 | 0 | -| `compact_numeric_ascii_letter_suffix` | 2975 | 1911 | 1064 | 0 | -| `consecutive_ascii_roman_words_whitespace_boundary` | 4679 | 2232 | 2447 | 0 | +| `closed_roman_parenthetical_after_non_ascii_letter_boundary` | 63959 | 53056 | 10903 | 0 | +| `compact_numeric_ascii_letter_suffix` | 2975 | 1913 | 1062 | 0 | +| `consecutive_ascii_roman_words_whitespace_boundary` | 4679 | 2287 | 2392 | 0 | | `decimal_point_between_ascii_digits` | 4546 | 3444 | 1102 | 0 | | `korean_inline_parenthesized_single_arithmetic_operator` | 23 | 17 | 6 | 0 | -| `korean_majority_same_token_roman_sandwich_non_domain` | 947 | 592 | 355 | 0 | -| `korean_prefixed_closed_allcaps_parenthetical` | 54492 | 45633 | 8859 | 0 | -| `korean_prefixed_closed_roman_annotation_rule_34_order` | 64382 | 53593 | 10789 | 0 | +| `korean_majority_same_token_roman_sandwich_non_domain` | 947 | 593 | 354 | 0 | +| `korean_prefixed_closed_allcaps_parenthetical` | 54492 | 45656 | 8836 | 0 | +| `korean_prefixed_closed_roman_annotation_rule_34_order` | 64382 | 53650 | 10732 | 0 | | `korean_prefixed_roman_parenthetical_followed_by_allcaps_hyphen_suffix` | 11 | 0 | 11 | 0 | | `mixed_roman_korean_word_before_uppercase_headword_expansion` | 10 | 0 | 10 | 0 | | `multi_character_allcaps_roman_runs_joined_by_middle_dot` | 97 | 0 | 97 | 0 | | `percent_point_unit_list_comma` | 7 | 5 | 2 | 0 | | `pure_allcaps_segment_before_hyphen_and_multi_allcaps_segment_after` | 448 | 226 | 222 | 0 | | `roman_hyphenated_word_after_whitespace_following_korean_word` | 361 | 86 | 275 | 0 | -| `roman_parenthetical_headword_after_whitespace_following_korean_word` | 4695 | 3548 | 1147 | 0 | +| `roman_parenthetical_headword_after_whitespace_following_korean_word` | 4695 | 3550 | 1145 | 0 | | `roman_run_after_whitespace_following_closed_roman_enclosure` | 1093 | 143 | 950 | 0 | | `roman_run_immediately_before_attached_middle_dot_boundary` | 577 | 0 | 577 | 0 | | `rule69_ascii_unit_before_terminator_skipping_symbol` | 440 | 344 | 96 | 0 | | `single_capital_followed_by_parenthesized_digits` | 1361 | 0 | 1361 | 0 | | `spaced_comma_between_ascii_digit_runs` | 217 | 182 | 35 | 0 | -| `standalone_multi_character_uppercase_roman_word` | 62411 | 51310 | 11101 | 0 | +| `standalone_multi_character_uppercase_roman_word` | 62411 | 51365 | 11046 | 0 | | `tight_triangle_mark_immediately_before_korean` | 377 | 0 | 377 | 0 | | `uppercase_alphanumeric_roman_digit_sequence` | 3429 | 2096 | 1333 | 0 | -| `uppercase_ascii_run_immediately_after_digit_in_roman_sequence` | 1896 | 1262 | 634 | 0 | +| `uppercase_ascii_run_immediately_after_digit_in_roman_sequence` | 1896 | 1264 | 632 | 0 | | `uppercase_ascii_run_immediately_after_hyphen_in_roman_sequence` | 952 | 271 | 681 | 0 | | `uppercase_ascii_segments_joined_by_ampersand_capitalization` | 439 | 320 | 119 | 0 | | `uppercase_roman_headword_closed_multiword_parenthetical` | 175 | 17 | 158 | 0 | | `uppercase_roman_run_followed_by_hyphen_digits` | 571 | 194 | 377 | 0 | -| `uppercase_word_after_whitespace_continuing_ascii_roman_text` | 1729 | 725 | 1004 | 0 | +| `uppercase_word_after_whitespace_continuing_ascii_roman_text` | 1729 | 760 | 969 | 0 | ### `allcaps_roman_run_beginning_with_pure_letter_shortform` -Of the 4120 candidates, 1534 are the actual `pending_rule_review` subcluster. The other 2586 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 4120 candidates, 1533 are the actual `pending_rule_review` subcluster. The other 2587 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. -For this output-signature audit, 1605 mismatches were evaluable and 836 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. +For this output-signature audit, 1604 mismatches were evaluable and 836 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. Localized first-difference transitions: @@ -612,7 +612,7 @@ Mismatch primary-class distribution: - `comparison_method`: 11 - `corpus_suspect`: 50 -- `pending_rule_review`: 1534 +- `pending_rule_review`: 1533 - `unsupported_character_review`: 10 Representative `exact` samples: @@ -853,9 +853,9 @@ Representative `mismatch` samples: ### `allcaps_roman_run_containing_ou` -Of the 1816 candidates, 1663 are the actual `pending_rule_review` subcluster. The other 153 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 1816 candidates, 1660 are the actual `pending_rule_review` subcluster. The other 156 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. -For this output-signature audit, 1749 mismatches were evaluable and 1356 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. +For this output-signature audit, 1746 mismatches were evaluable and 1356 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. Localized first-difference transitions: @@ -866,7 +866,7 @@ Mismatch primary-class distribution: - `comparison_method`: 2 - `corpus_suspect`: 9 -- `pending_rule_review`: 1663 +- `pending_rule_review`: 1660 - `unsupported_character_review`: 75 Representative `exact` samples: @@ -914,13 +914,13 @@ Representative `mismatch` samples: ### `allcaps_roman_run_containing_st` -Of the 1479 candidates, 726 are the actual `pending_rule_review` subcluster. The other 753 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 1479 candidates, 724 are the actual `pending_rule_review` subcluster. The other 755 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. -For this output-signature audit, 779 mismatches were evaluable and 449 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. +For this output-signature audit, 777 mismatches were evaluable and 459 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. Localized first-difference transitions: -- `U+280E ⠎ -> U+280C ⠌`: 441 +- `U+280E ⠎ -> U+280C ⠌`: 451 - `U+2820 ⠠ -> U+280C ⠌`: 4 - `U+280C ⠌ -> U+280E ⠎`: 1 - `U+2810 ⠐ -> U+2811 ⠑`: 1 @@ -930,7 +930,7 @@ Mismatch primary-class distribution: - `comparison_method`: 13 - `corpus_suspect`: 35 -- `pending_rule_review`: 726 +- `pending_rule_review`: 724 - `unsupported_character_review`: 5 Representative `exact` samples: @@ -1062,64 +1062,79 @@ Representative `mismatch` samples: ### `ascii_apostrophe_between_ascii_letter_runs` -Of the 147 candidates, 144 are the actual `pending_rule_review` subcluster. The other 3 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 147 candidates, 75 are the actual `pending_rule_review` subcluster. The other 72 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. -For this output-signature audit, 147 mismatches were evaluable and 112 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. +For this output-signature audit, 79 mismatches were evaluable and 17 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. Localized first-difference transitions: -- `U+2804 ⠄ -> U+2834 ⠴`: 82 -- `U+2804 ⠄ -> U+2820 ⠠`: 30 +- `U+2822 ⠢ -> U+2811 ⠑`: 15 +- `U+2800 ⠀ -> U+2815 ⠕`: 1 +- `U+2820 ⠠ -> U+2803 ⠃`: 1 Mismatch primary-class distribution: -- `corpus_suspect`: 3 -- `pending_rule_review`: 144 +- `corpus_suspect`: 4 +- `pending_rule_review`: 75 -Representative `localized_mismatch` samples: +Representative `exact` samples: -- `sentence_01.json` #67: LG전자는 오는 5일부터 8일까지 ‘CES 2023’이 열리는 미국 라스베이거스 컨벤션센터(LVCC)에 ‘라이프스굿(Life's Good)’을 소개하는 광고판을 설치한다고 3일 밝혔다. - - expected: `⠦⠄⠴⠠⠇⠊⠋⠑⠄⠎⠀⠠⠛⠙⠠⠴⠴⠄⠮⠀⠠⠥⠈⠗` - - actual: `⠦⠄⠴⠠⠇⠊⠋⠑⠴⠄⠰⠎⠀⠠⠛⠕⠕⠙⠠⠴⠴⠄⠮⠀` - - first differing cell (zero-based): 121 - - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_01.json` #2257: 금호건설이 임직원들의 기부금을 토대로 모두가 함께 만들어가는 기업의 사회적 책임(CSR) 활동을 위해 ‘DOVE's 캠페인’을 전개한다고 29일 밝혔다. + - expected: `⠈⠪⠢⠚⠥⠈⠾⠠⠞⠕⠀⠕⠢⠨⠕⠁⠏⠒⠊⠮⠺⠀⠈⠕` + - actual: `⠈⠪⠢⠚⠥⠈⠾⠠⠞⠕⠀⠕⠢⠨⠕⠁⠏⠒⠊⠮⠺⠀⠈⠕` + - current primary/reason: `exact` / `exact` - `sentence_02.json` #425: ‘제로(Zero)’는 ‘대전 0시 축제(Daejeon Zero O'clock Festival)’의 영문 이름에서 따왔으며, 축제장에서 3D홀로그램 미디어 콘텐츠 운영을 맡고 있는 애드테크 전문기업 코스윌에서 개발했다. - - expected: `⠀⠠⠵⠻⠕⠀⠠⠕⠄⠉⠇⠕⠉⠅⠀⠠⠋⠑⠌⠊⠧⠁⠇⠠` - - actual: `⠀⠠⠵⠻⠕⠀⠠⠕⠴⠄⠰⠉⠇⠕⠉⠅⠀⠠⠋⠑⠌⠊⠧⠁` - - first differing cell (zero-based): 56 + - expected: `⠠⠦⠨⠝⠐⠥⠦⠄⠴⠠⠵⠻⠕⠠⠴⠴⠄⠉⠵⠀⠠⠦⠊⠗` + - actual: `⠠⠦⠨⠝⠐⠥⠦⠄⠴⠠⠵⠻⠕⠠⠴⠴⠄⠉⠵⠀⠠⠦⠊⠗` + - current primary/reason: `exact` / `exact` +- `sentence_03.json` #13917: 11일에는 NCT DREAM, 영탁, 프로미스나인, 드림캐처, CIX, EPEX, 최예나, 케플러, TNX, 템페스트, cignature(시그니처), 비오(BE'O) 등 12팀이 출연한다. + - expected: `⠼⠁⠁⠕⠂⠝⠉⠵⠀⠴⠠⠠⠝⠉⠞⠀⠠⠠⠙⠗⠂⠍⠐⠀` + - actual: `⠼⠁⠁⠕⠂⠝⠉⠵⠀⠴⠠⠠⠝⠉⠞⠀⠠⠠⠙⠗⠂⠍⠐⠀` + - current primary/reason: `exact` / `exact` +- `sentence_04.json` #446: 추경호 부총리 겸 기획재정부 장관은 3일 국제신용평가사 무디스(Moody's) 연례 협의단에 미국 인플레이션 감축법(IRA) 등에 따른 국내 기업의 불확실성이 상당 부분 해소됐다고 강조했다. + - expected: `⠰⠍⠈⠻⠚⠥⠀⠘⠍⠰⠿⠐⠕⠀⠈⠱⠢⠀⠈⠕⠚⠽⠁⠨` + - actual: `⠰⠍⠈⠻⠚⠥⠀⠘⠍⠰⠿⠐⠕⠀⠈⠱⠢⠀⠈⠕⠚⠽⠁⠨` + - current primary/reason: `exact` / `exact` + +Representative `localized_mismatch` samples: + +- `sentence_01.json` #15877: 이순재, 선우용여, 이연희의 따스한 안녕을 전할 ‘산책’은 오는 7월 23일(일) 밤 10시 40분 tvN에서 방송되며 tvN X TVING 프로젝트 ‘O'PENing(오프닝) 2023’은 TVING에서도 감상할 수 있다. + - expected: `⠦⠴⠠⠕⠄⠠⠠⠏⠢⠠⠄⠬⠦⠄⠥⠙⠪⠉⠕⠶⠠⠴⠀⠼` + - actual: `⠦⠴⠠⠕⠄⠠⠠⠏⠑⠝⠠⠄⠬⠦⠄⠥⠙⠪⠉⠕⠶⠠⠴⠀` + - first differing cell (zero-based): 141 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_03.json` #13417: 트랙리스트에 따르면 이번 앨범에는 첫 번째 트랙 ‘I'm Okay(아이엠 오케이)’를 시작으로 ‘오로라(AURORA)’, ‘PALACE(팰러스)’, ‘PARADE(퍼레이드)’까지 총 네 곡이 수록됐다. - - expected: `⠗⠁⠀⠠⠦⠴⠠⠊⠄⠍⠀⠠⠕⠅⠁⠽⠦⠄⠣⠕⠝⠢⠀⠥` - - actual: `⠗⠁⠀⠠⠦⠴⠠⠊⠴⠄⠰⠍⠀⠠⠕⠅⠁⠽⠦⠄⠣⠕⠝⠢` - - first differing cell (zero-based): 54 +- `sentence_03.json` #16886: 윤재찬이 출연하는 ‘썸머, 러브머신 블루스’는 7월 16일 오후 10시 40분 tvN에서 방송되며, tvN X TVING 프로젝트 ‘O'PENing(오프닝) 2023’은 TVING에서도 감상할 수 있다. + - expected: `⠦⠴⠠⠕⠄⠠⠠⠏⠢⠠⠄⠬⠦⠄⠥⠙⠪⠉⠕⠶⠠⠴⠀⠼` + - actual: `⠦⠴⠠⠕⠄⠠⠠⠏⠑⠝⠠⠄⠬⠦⠄⠥⠙⠪⠉⠕⠶⠠⠴⠀` + - first differing cell (zero-based): 129 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_04.json` #446: 추경호 부총리 겸 기획재정부 장관은 3일 국제신용평가사 무디스(Moody's) 연례 협의단에 미국 인플레이션 감축법(IRA) 등에 따른 국내 기업의 불확실성이 상당 부분 해소됐다고 강조했다. - - expected: `⠄⠴⠠⠍⠕⠕⠙⠽⠄⠎⠠⠴⠀⠡⠐⠌⠀⠚⠱⠃⠺⠊⠒⠝` - - actual: `⠄⠴⠠⠍⠕⠕⠙⠽⠠⠦⠰⠎⠠⠴⠀⠡⠐⠌⠀⠚⠱⠃⠺⠊` - - first differing cell (zero-based): 71 +- `sentence_04.json` #2563: 마크 저커버그 메타 최고경영자(CEO)는 이날 “해봅시다(Let's do this). 스레드에 온 걸 환영합니다”라는 글을 스레드 계정에 올리며 출시 소식을 알렸다. + - expected: `⠠⠇⠑⠞⠄⠎⠀⠙⠀⠹⠠⠴⠲⠀⠠⠪⠐⠝⠊⠪⠝⠀⠷⠀` + - actual: `⠠⠇⠑⠞⠄⠎⠀⠙⠕⠀⠹⠊⠎⠠⠴⠲⠀⠠⠪⠐⠝⠊⠪⠝` + - first differing cell (zero-based): 62 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` Representative `mismatch` samples: - `sentence_01.json` #67: LG전자는 오는 5일부터 8일까지 ‘CES 2023’이 열리는 미국 라스베이거스 컨벤션센터(LVCC)에 ‘라이프스굿(Life's Good)’을 소개하는 광고판을 설치한다고 3일 밝혔다. - - expected: `⠦⠄⠴⠠⠇⠊⠋⠑⠄⠎⠀⠠⠛⠙⠠⠴⠴⠄⠮⠀⠠⠥⠈⠗` - - actual: `⠦⠄⠴⠠⠇⠊⠋⠑⠴⠄⠰⠎⠀⠠⠛⠕⠕⠙⠠⠴⠴⠄⠮⠀` - - first differing cell (zero-based): 121 + - expected: `⠊⠋⠑⠄⠎⠀⠠⠛⠙⠠⠴⠴⠄⠮⠀⠠⠥⠈⠗⠚⠉⠵⠀⠈` + - actual: `⠊⠋⠑⠄⠎⠀⠠⠛⠕⠕⠙⠠⠴⠴⠄⠮⠀⠠⠥⠈⠗⠚⠉⠵` + - first differing cell (zero-based): 126 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_02.json` #425: ‘제로(Zero)’는 ‘대전 0시 축제(Daejeon Zero O'clock Festival)’의 영문 이름에서 따왔으며, 축제장에서 3D홀로그램 미디어 콘텐츠 운영을 맡고 있는 애드테크 전문기업 코스윌에서 개발했다. - - expected: `⠀⠠⠵⠻⠕⠀⠠⠕⠄⠉⠇⠕⠉⠅⠀⠠⠋⠑⠌⠊⠧⠁⠇⠠` - - actual: `⠀⠠⠵⠻⠕⠀⠠⠕⠴⠄⠰⠉⠇⠕⠉⠅⠀⠠⠋⠑⠌⠊⠧⠁` - - first differing cell (zero-based): 56 +- `sentence_02.json` #10821: LG전자는 19일부터 3일간 서울 강남구 코엑스에서 열리는 월드IT쇼(WIS) 2023에서 ‘라이프스 굿(Life's Good)’을 주제로 고객의 삶을 더 행복하게 만드는 혁신적인 제품들을 선보인다고 밝혔다. + - expected: `⠊⠋⠑⠄⠎⠀⠠⠛⠙⠠⠴⠴⠄⠮⠀⠨⠍⠨⠝⠐⠥⠀⠈⠥` + - actual: `⠊⠋⠑⠄⠎⠀⠠⠛⠕⠕⠙⠠⠴⠴⠄⠮⠀⠨⠍⠨⠝⠐⠥⠀` + - first differing cell (zero-based): 114 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` - `sentence_03.json` #13417: 트랙리스트에 따르면 이번 앨범에는 첫 번째 트랙 ‘I'm Okay(아이엠 오케이)’를 시작으로 ‘오로라(AURORA)’, ‘PALACE(팰러스)’, ‘PARADE(퍼레이드)’까지 총 네 곡이 수록됐다. - - expected: `⠗⠁⠀⠠⠦⠴⠠⠊⠄⠍⠀⠠⠕⠅⠁⠽⠦⠄⠣⠕⠝⠢⠀⠥` - - actual: `⠗⠁⠀⠠⠦⠴⠠⠊⠴⠄⠰⠍⠀⠠⠕⠅⠁⠽⠦⠄⠣⠕⠝⠢` - - first differing cell (zero-based): 54 + - expected: `⠠⠴⠴⠄⠐⠀⠠⠦⠴⠠⠠⠏⠁⠇⠁⠉⠑⠦⠄⠙⠗⠂⠐⠎` + - actual: `⠠⠴⠴⠄⠐⠀⠠⠦⠰⠠⠠⠏⠁⠇⠁⠉⠑⠦⠄⠙⠗⠂⠐⠎` + - first differing cell (zero-based): 114 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_04.json` #446: 추경호 부총리 겸 기획재정부 장관은 3일 국제신용평가사 무디스(Moody's) 연례 협의단에 미국 인플레이션 감축법(IRA) 등에 따른 국내 기업의 불확실성이 상당 부분 해소됐다고 강조했다. - - expected: `⠄⠴⠠⠍⠕⠕⠙⠽⠄⠎⠠⠴⠀⠡⠐⠌⠀⠚⠱⠃⠺⠊⠒⠝` - - actual: `⠄⠴⠠⠍⠕⠕⠙⠽⠠⠦⠰⠎⠠⠴⠀⠡⠐⠌⠀⠚⠱⠃⠺⠊` - - first differing cell (zero-based): 71 +- `sentence_04.json` #2563: 마크 저커버그 메타 최고경영자(CEO)는 이날 “해봅시다(Let's do this). 스레드에 온 걸 환영합니다”라는 글을 스레드 계정에 올리며 출시 소식을 알렸다. + - expected: `⠠⠇⠑⠞⠄⠎⠀⠙⠀⠹⠠⠴⠲⠀⠠⠪⠐⠝⠊⠪⠝⠀⠷⠀` + - actual: `⠠⠇⠑⠞⠄⠎⠀⠙⠕⠀⠹⠊⠎⠠⠴⠲⠀⠠⠪⠐⠝⠊⠪⠝` + - first differing cell (zero-based): 62 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` ### `ascii_roman_tail_comma_before_digit_korean_token` @@ -1197,15 +1212,15 @@ Representative `mismatch` samples: ### `attached_ascii_roman_segments_joined_by_ampersand` -Of the 802 candidates, 168 are the actual `pending_rule_review` subcluster. The other 634 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 802 candidates, 167 are the actual `pending_rule_review` subcluster. The other 635 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. For this output-signature audit, 195 mismatches were evaluable and 0 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. Mismatch primary-class distribution: - `comparison_method`: 15 -- `corpus_suspect`: 10 -- `pending_rule_review`: 168 +- `corpus_suspect`: 11 +- `pending_rule_review`: 167 - `unsupported_character_review`: 2 Representative `exact` samples: @@ -1252,15 +1267,15 @@ Representative `mismatch` samples: ### `attached_ascii_roman_to_korean_script_boundary` -Of the 17693 candidates, 3412 are the actual `pending_rule_review` subcluster. The other 14281 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 17693 candidates, 3406 are the actual `pending_rule_review` subcluster. The other 14287 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. -For this output-signature audit, 4248 mismatches were evaluable and 0 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. +For this output-signature audit, 4242 mismatches were evaluable and 0 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. Mismatch primary-class distribution: - `comparison_method`: 73 - `corpus_suspect`: 725 -- `pending_rule_review`: 3412 +- `pending_rule_review`: 3406 - `unsupported_character_review`: 38 Representative `exact` samples: @@ -1401,13 +1416,13 @@ Representative `mismatch` samples: ### `capitals_word_mode_previously_spanning_nonletter_scope` -Of the 1733 candidates, 824 are the actual `pending_rule_review` subcluster. The other 909 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 1733 candidates, 823 are the actual `pending_rule_review` subcluster. The other 910 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. Mismatch primary-class distribution: - `comparison_method`: 15 -- `corpus_suspect`: 37 -- `pending_rule_review`: 824 +- `corpus_suspect`: 38 +- `pending_rule_review`: 823 - `unsupported_character_review`: 5 Representative `exact` samples: @@ -1454,13 +1469,13 @@ Representative `mismatch` samples: ### `closed_roman_parenthetical_after_non_ascii_letter_boundary` -Of the 63959 candidates, 9471 are the actual `pending_rule_review` subcluster. The other 54488 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 63959 candidates, 9411 are the actual `pending_rule_review` subcluster. The other 54548 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. -For this output-signature audit, 10962 mismatches were evaluable and 1128 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. +For this output-signature audit, 10903 mismatches were evaluable and 1130 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. Localized first-difference transitions: -- `U+2826 ⠦ -> U+2834 ⠴`: 437 +- `U+2826 ⠦ -> U+2834 ⠴`: 438 - `U+2826 ⠦ -> U+2800 ⠀`: 330 - `U+2820 ⠠ -> U+2809 ⠉`: 104 - `U+2834 ⠴ -> U+2826 ⠦`: 28 @@ -1469,8 +1484,8 @@ Localized first-difference transitions: Mismatch primary-class distribution: - `comparison_method`: 238 -- `corpus_suspect`: 1111 -- `pending_rule_review`: 9471 +- `corpus_suspect`: 1112 +- `pending_rule_review`: 9411 - `unsupported_character_review`: 142 Representative `exact` samples: @@ -1540,9 +1555,9 @@ Representative `mismatch` samples: ### `compact_numeric_ascii_letter_suffix` -Of the 2975 candidates, 993 are the actual `pending_rule_review` subcluster. The other 1982 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 2975 candidates, 990 are the actual `pending_rule_review` subcluster. The other 1985 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. -For this output-signature audit, 1064 mismatches were evaluable and 256 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. +For this output-signature audit, 1062 mismatches were evaluable and 256 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. Localized first-difference transitions: @@ -1555,8 +1570,8 @@ Localized first-difference transitions: Mismatch primary-class distribution: - `comparison_method`: 12 -- `corpus_suspect`: 52 -- `pending_rule_review`: 993 +- `corpus_suspect`: 53 +- `pending_rule_review`: 990 - `unsupported_character_review`: 7 Representative `exact` samples: @@ -1626,9 +1641,9 @@ Representative `mismatch` samples: ### `consecutive_ascii_roman_words_whitespace_boundary` -Of the 4679 candidates, 2312 are the actual `pending_rule_review` subcluster. The other 2367 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 4679 candidates, 2256 are the actual `pending_rule_review` subcluster. The other 2423 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. -For this output-signature audit, 2447 mismatches were evaluable and 26 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. +For this output-signature audit, 2392 mismatches were evaluable and 26 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. Localized first-difference transitions: @@ -1637,8 +1652,8 @@ Localized first-difference transitions: Mismatch primary-class distribution: - `comparison_method`: 20 -- `corpus_suspect`: 89 -- `pending_rule_review`: 2312 +- `corpus_suspect`: 90 +- `pending_rule_review`: 2256 - `unsupported_character_review`: 26 Representative `exact` samples: @@ -1836,12 +1851,12 @@ Representative `mismatch` samples: ### `korean_majority_same_token_roman_sandwich_non_domain` -Of the 947 candidates, 343 are the actual `pending_rule_review` subcluster. The other 604 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 947 candidates, 342 are the actual `pending_rule_review` subcluster. The other 605 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. Mismatch primary-class distribution: - `corpus_suspect`: 7 -- `pending_rule_review`: 343 +- `pending_rule_review`: 342 - `unsupported_character_review`: 5 Representative `exact` samples: @@ -1888,13 +1903,13 @@ Representative `mismatch` samples: ### `korean_prefixed_closed_allcaps_parenthetical` -Of the 54492 candidates, 7557 are the actual `pending_rule_review` subcluster. The other 46935 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 54492 candidates, 7533 are the actual `pending_rule_review` subcluster. The other 46959 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. Mismatch primary-class distribution: - `comparison_method`: 186 -- `corpus_suspect`: 993 -- `pending_rule_review`: 7557 +- `corpus_suspect`: 994 +- `pending_rule_review`: 7533 - `unsupported_character_review`: 123 Representative `exact` samples: @@ -1941,20 +1956,20 @@ Representative `mismatch` samples: ### `korean_prefixed_closed_roman_annotation_rule_34_order` -Of the 64382 candidates, 9294 are the actual `pending_rule_review` subcluster. The other 55088 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 64382 candidates, 9236 are the actual `pending_rule_review` subcluster. The other 55146 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. -For this output-signature audit, 10789 mismatches were evaluable and 1116 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. +For this output-signature audit, 10732 mismatches were evaluable and 1117 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. Localized first-difference transitions: -- `U+2834 ⠴ -> U+2826 ⠦`: 1114 +- `U+2834 ⠴ -> U+2826 ⠦`: 1115 - `U+2810 ⠐ -> U+2826 ⠦`: 2 Mismatch primary-class distribution: - `comparison_method`: 241 -- `corpus_suspect`: 1111 -- `pending_rule_review`: 9294 +- `corpus_suspect`: 1112 +- `pending_rule_review`: 9236 - `unsupported_character_review`: 143 Representative `exact` samples: @@ -2315,9 +2330,9 @@ Representative `mismatch` samples: ### `roman_parenthetical_headword_after_whitespace_following_korean_word` -Of the 4695 candidates, 1054 are the actual `pending_rule_review` subcluster. The other 3641 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 4695 candidates, 1051 are the actual `pending_rule_review` subcluster. The other 3644 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. -For this output-signature audit, 1147 mismatches were evaluable and 163 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. +For this output-signature audit, 1145 mismatches were evaluable and 163 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. Localized first-difference transitions: @@ -2328,8 +2343,8 @@ Localized first-difference transitions: Mismatch primary-class distribution: - `comparison_method`: 34 -- `corpus_suspect`: 40 -- `pending_rule_review`: 1054 +- `corpus_suspect`: 41 +- `pending_rule_review`: 1051 - `unsupported_character_review`: 19 Representative `exact` samples: @@ -2401,13 +2416,13 @@ Representative `mismatch` samples: ### `roman_run_after_whitespace_following_closed_roman_enclosure` -Of the 1093 candidates, 613 are the actual `pending_rule_review` subcluster. The other 480 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 1093 candidates, 612 are the actual `pending_rule_review` subcluster. The other 481 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. -For this output-signature audit, 950 mismatches were evaluable and 258 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. +For this output-signature audit, 950 mismatches were evaluable and 260 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. Localized first-difference transitions: -- `U+2834 ⠴ -> U+2830 ⠰`: 254 +- `U+2834 ⠴ -> U+2830 ⠰`: 256 - `U+2815 ⠕ -> U+2837 ⠷`: 1 - `U+2820 ⠠ -> U+2805 ⠅`: 1 - `U+2820 ⠠ -> U+280E ⠎`: 1 @@ -2415,8 +2430,8 @@ Localized first-difference transitions: Mismatch primary-class distribution: -- `corpus_suspect`: 330 -- `pending_rule_review`: 613 +- `corpus_suspect`: 331 +- `pending_rule_review`: 612 - `unsupported_character_review`: 7 Representative `exact` samples: @@ -2489,11 +2504,11 @@ Representative `mismatch` samples: Of the 577 candidates, 565 are the actual `pending_rule_review` subcluster. The other 12 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. -For this output-signature audit, 577 mismatches were evaluable and 450 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. +For this output-signature audit, 577 mismatches were evaluable and 451 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. Localized first-difference transitions: -- `U+2810 ⠐ -> U+2832 ⠲`: 450 +- `U+2810 ⠐ -> U+2832 ⠲`: 451 Mismatch primary-class distribution: @@ -2724,13 +2739,13 @@ Representative `mismatch` samples: ### `standalone_multi_character_uppercase_roman_word` -Of the 62411 candidates, 9639 are the actual `pending_rule_review` subcluster. The other 52772 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 62411 candidates, 9583 are the actual `pending_rule_review` subcluster. The other 52828 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. Mismatch primary-class distribution: - `comparison_method`: 227 -- `corpus_suspect`: 1097 -- `pending_rule_review`: 9639 +- `corpus_suspect`: 1098 +- `pending_rule_review`: 9583 - `unsupported_character_review`: 138 Representative `exact` samples: @@ -2903,9 +2918,9 @@ Representative `mismatch` samples: ### `uppercase_ascii_run_immediately_after_digit_in_roman_sequence` -Of the 1896 candidates, 576 are the actual `pending_rule_review` subcluster. The other 1320 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 1896 candidates, 573 are the actual `pending_rule_review` subcluster. The other 1323 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. -For this output-signature audit, 634 mismatches were evaluable and 2 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. +For this output-signature audit, 632 mismatches were evaluable and 2 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. Localized first-difference transitions: @@ -2914,8 +2929,8 @@ Localized first-difference transitions: Mismatch primary-class distribution: - `comparison_method`: 10 -- `corpus_suspect`: 42 -- `pending_rule_review`: 576 +- `corpus_suspect`: 43 +- `pending_rule_review`: 573 - `unsupported_character_review`: 6 Representative `exact` samples: @@ -2952,10 +2967,10 @@ Representative `mismatch` samples: - actual: `⠟⠈⠿⠨⠕⠉⠪⠶⠦⠄⠴⠠⠠⠁⠊⠠⠴⠐⠀⠼⠋⠰⠠⠛` - first differing cell (zero-based): 9 - current primary/reason: `corpus_suspect` / `rule34_roman_indicator_before_opening_parenthesis` -- `sentence_02.json` #425: ‘제로(Zero)’는 ‘대전 0시 축제(Daejeon Zero O'clock Festival)’의 영문 이름에서 따왔으며, 축제장에서 3D홀로그램 미디어 콘텐츠 운영을 맡고 있는 애드테크 전문기업 코스윌에서 개발했다. - - expected: `⠀⠠⠵⠻⠕⠀⠠⠕⠄⠉⠇⠕⠉⠅⠀⠠⠋⠑⠌⠊⠧⠁⠇⠠` - - actual: `⠀⠠⠵⠻⠕⠀⠠⠕⠴⠄⠰⠉⠇⠕⠉⠅⠀⠠⠋⠑⠌⠊⠧⠁` - - first differing cell (zero-based): 56 +- `sentence_02.json` #445: 박람회에서는 다양한 분야의 디지털 교육 프로그램을 한자리에서 체험할 수 있도록 인공지능(AI) 코스웨어·학습플랫폼, 인공지능(AI) 교과교육, 인공지능(AI) 학습지원, 3D·가상현실(VR)·메타버스 교육, 소프트웨어(SW)·코딩·로봇 교육 등 체험 공간을 운영할 예정이다. + - expected: `⠒⠐⠀⠼⠉⠴⠠⠙⠐⠆⠫⠇⠶⠚⠡⠠⠕⠂⠦⠄⠴⠠⠠⠧` + - actual: `⠒⠐⠀⠼⠉⠴⠠⠙⠲⠐⠆⠫⠇⠶⠚⠡⠠⠕⠂⠦⠄⠴⠠⠠` + - first differing cell (zero-based): 179 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` - `sentence_03.json` #123: 이날 이네오스는 자동차 위탁 생산 업체인 마그나슈타이어와 함께 새로운 4X4 전기차(EV)를 개발한다고 발표했다. 이네오스는 신차 양산 시점을 2026년으로 목표하고 있다. - expected: `⠠⠗⠐⠥⠛⠀⠼⠙⠡⠼⠙⠀⠨⠾⠈⠕⠰⠣⠦⠄⠴⠠⠠⠑` @@ -3231,9 +3246,9 @@ Representative `mismatch` samples: ### `uppercase_word_after_whitespace_continuing_ascii_roman_text` -Of the 1729 candidates, 926 are the actual `pending_rule_review` subcluster. The other 803 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 1729 candidates, 890 are the actual `pending_rule_review` subcluster. The other 839 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. -For this output-signature audit, 1004 mismatches were evaluable and 4 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. +For this output-signature audit, 969 mismatches were evaluable and 4 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. Localized first-difference transitions: @@ -3244,8 +3259,8 @@ Localized first-difference transitions: Mismatch primary-class distribution: - `comparison_method`: 9 -- `corpus_suspect`: 58 -- `pending_rule_review`: 926 +- `corpus_suspect`: 59 +- `pending_rule_review`: 890 - `unsupported_character_review`: 11 Representative `exact` samples: @@ -3315,8 +3330,8 @@ These cohorts are defined by both an input boundary and the sentence's actual fi | Cohort | Candidates | Exact controls | Mismatch | Target localized | Reverse | |---|---:|---:|---:|---:|---:| -| `allcaps_roman_run_beginning_with_pure_letter_shortform` | 4120 | 2515 | 1605 | 788 | 48 | -| `uppercase_ascii_run_immediately_after_digit_in_roman_sequence` | 1896 | 1262 | 634 | 0 | 2 | +| `allcaps_roman_run_beginning_with_pure_letter_shortform` | 4120 | 2516 | 1604 | 788 | 48 | +| `uppercase_ascii_run_immediately_after_digit_in_roman_sequence` | 1896 | 1264 | 632 | 0 | 2 | | `uppercase_ascii_run_immediately_after_hyphen_in_roman_sequence` | 952 | 271 | 681 | 183 | 6 | | `pure_allcaps_segment_before_hyphen_and_multi_allcaps_segment_after` | 448 | 226 | 222 | 0 | 3 | @@ -3375,7 +3390,7 @@ Korean rule 29 requires a Roman indicator before Roman text in a Korean sentence | Cohort | Candidates | Exact controls | Mismatch | Pending | Corpus suspect | Localized `⠴ -> blank` | Reverse `blank -> ⠴` | |---|---:|---:|---:|---:|---:|---:|---:| | `roman_hyphenated_word_after_whitespace_following_korean_word` | 361 | 86 | 275 | 264 | 6 | 139 | 0 | -| `roman_parenthetical_headword_after_whitespace_following_korean_word` | 4695 | 3548 | 1147 | 1054 | 40 | 159 | 0 | +| `roman_parenthetical_headword_after_whitespace_following_korean_word` | 4695 | 3550 | 1145 | 1051 | 41 | 159 | 0 | | `korean_prefixed_roman_parenthetical_followed_by_allcaps_hyphen_suffix` | 11 | 0 | 11 | 6 | 5 | 5 | 0 | The whitespace parenthetical-headword cohort also retains 1 localized `⠠ -> ⠴` and 3 localized `⠴ -> ⠰` cases as separate transitions; they are not folded into the target. The exact controls demonstrate that the broad structure is already correct in many sentences, while the attached parenthetical-hyphen cohort has no exact control and includes cases already classified by the stricter rule-34 reference-order contradiction. Consequently none of these measurements authorizes an engine change; they are deterministic pending/corpus-review diagnostics only. @@ -3386,13 +3401,13 @@ Korean rule 29 explicitly says that when two or more Roman items occur consecuti Analyzer checkpoint `266e70c` fixed the pre-change baseline at 1,729 candidates, 386 exact controls, 1,343 mismatches, 1,272 pending members, and 385/1,343 evaluable mismatches localized to the current re-entry signature. Those localized transitions were 365 `⠠ -> ⠴`, 16 `⠰ -> ⠴`, no reverse `⠴ -> ⠠`, and four other transitions. Exact controls include contexts where the first Roman word already opened token-level mode, while parenthesized/mixed-token examples expose the duplicate event. -The generalized fix makes an explicit `EnterEnglish` event idempotent when final emit state is already inside a Roman section; it neither names an input nor changes a new section's entry. The current measurement is 1729 candidates, 725 exact controls, 1004 mismatches, 926 pending members, and 4/1004 localized mismatches. Current target counts are 0 `⠠ -> ⠴`, 0 `⠰ -> ⠴`, and 0 reverse `⠴ -> ⠠`. Cohort exact controls increase by 275, while corpus-wide exact matches rise from 67,138 to 67,442 (+304); corrected prefixes that still differ later remain mismatches, and applications outside this strict input gate account for the remaining net gain. The raw/residual reverse `⠴ -> ⠠` totals remain 68/65 before and after the change. The complete custom standard summary is 5,141 total, 5,141 success, 0 failure, and 0 skipped. +The generalized fix makes an explicit `EnterEnglish` event idempotent when final emit state is already inside a Roman section; it neither names an input nor changes a new section's entry. The current measurement is 1729 candidates, 760 exact controls, 969 mismatches, 890 pending members, and 4/969 localized mismatches. Current target counts are 0 `⠠ -> ⠴`, 0 `⠰ -> ⠴`, and 0 reverse `⠴ -> ⠠`. Cohort exact controls increase by 275, while corpus-wide exact matches rise from 67,138 to 67,442 (+304); corrected prefixes that still differ later remain mismatches, and applications outside this strict input gate account for the remaining net gain. The raw/residual reverse `⠴ -> ⠠` totals remain 68/65 before and after the change. The complete custom standard summary is 5,141 total, 5,141 success, 0 failure, and 0 skipped. ### Closed Roman parenthetical after a non-ASCII-letter boundary Korean rule 34 (2024 Korean-rules PDF p.29) prints the opening enclosure before Roman entry in `링컨(Lincoln)`: the opening sequence is `⠦⠄⠴`. This output-position cohort finds a closed, non-nested parenthetical whose body begins with an ASCII letter and whose opening does not immediately follow another ASCII letter, then locates its complete current-engine signature without consulting the reference. Its localized boundary includes the two current output cells immediately before the opening plus the first three entry cells, so rule-11 math spacing can be separated from a difference later inside the parenthetical. Direct function-call shapes such as `f(x)` are excluded. The PDF's math rule 6 (p.59), rule 12 (pp.63-64), and rule 45 (p.75) nevertheless leave standalone `(x)` and other Roman-letter parenthetical mathematics as counterexamples, so the surface gate is not an engine-routing predicate. -The cross-cutting input cohort contains 63959 candidates: 52997 exact controls and 10962 mismatches. Mismatch primary classes remain unchanged: 9471 `pending_rule_review`, 1111 `corpus_suspect`, 238 `comparison_method`, and 142 `unsupported_character_review`. Of 10962 evaluable mismatches, 1128 have the first difference at the detected leading-spacing/entry boundary; these include 437 `U+2826 ⠦ -> U+2834 ⠴`, 28 `U+2834 ⠴ -> U+2826 ⠦`, and 330 `U+2826 ⠦ -> U+2800 ⠀` transitions; the exact localized reverse `U+2800 ⠀ -> U+2826 ⠦` occurs 0 times. After all earlier localized cohorts and this cohort are excluded, the raw-to-residual target count is 470 -> 26, the Roman-indicator reverse count is 33 -> 2, and the spacing target/reverse counts are 445 -> 79 and 1 -> 1. The short full-encoder form `웹3(Web3)` emits the PDF opening order as a routing control, while whitespace and quote boundaries reproduce the current Roman-first opening. Representative localized samples, with shard and index, are retained in the generated cluster sample table. Because exact controls are abundant and the PDF does not make this input shape semantically sufficient to exclude mathematics, no engine change or primary reclassification is inferred. The one raw/residual spacing reverse is a Korean-body `△(교육)` layout boundary, not a Roman-parenthetical member, and remains a separate rule-72/layout review. +The cross-cutting input cohort contains 63959 candidates: 53056 exact controls and 10903 mismatches. Mismatch primary classes remain unchanged: 9411 `pending_rule_review`, 1112 `corpus_suspect`, 238 `comparison_method`, and 142 `unsupported_character_review`. Of 10903 evaluable mismatches, 1130 have the first difference at the detected leading-spacing/entry boundary; these include 438 `U+2826 ⠦ -> U+2834 ⠴`, 28 `U+2834 ⠴ -> U+2826 ⠦`, and 330 `U+2826 ⠦ -> U+2800 ⠀` transitions; the exact localized reverse `U+2800 ⠀ -> U+2826 ⠦` occurs 0 times. After all earlier localized cohorts and this cohort are excluded, the raw-to-residual target count is 471 -> 26, the Roman-indicator reverse count is 33 -> 2, and the spacing target/reverse counts are 445 -> 79 and 1 -> 1. The short full-encoder form `웹3(Web3)` emits the PDF opening order as a routing control, while whitespace and quote boundaries reproduce the current Roman-first opening. Representative localized samples, with shard and index, are retained in the generated cluster sample table. Because exact controls are abundant and the PDF does not make this input shape semantically sufficient to exclude mathematics, no engine change or primary reclassification is inferred. The one raw/residual spacing reverse is a Korean-body `△(교육)` layout boundary, not a Roman-parenthetical member, and remains a separate rule-72/layout review. The HCA-style headword-expansion shape described above is not an engine implementation premise. The 2024 PDF's math rule 6 defines parentheses and grouping parentheses, rule 11 defines mathematical-expression spacing, rule 12 covers Roman letters in formulas as well as Korean sentences, and rule 45 shows Roman-letter function notation followed by parentheses. Excluding visible operators, scripts, and nesting narrows this corpus cohort, but the PDF does not make the remaining surface shape sufficient to rule out every mathematical counterexample. The cluster therefore remains conservative pending-review evidence only. @@ -3427,7 +3442,7 @@ The output-localized cohort contains 1022 candidates, 370 exact controls, and 65 Korean rule 29 places one Roman indicator/terminator pair around consecutive Roman items, with `Los Angeles` and `Table of Contents` as its printed multiword examples. Rule 34 separately omits the Roman terminator when Roman text is enclosed by quotation marks or parentheses. Neither printed rule states whether a later Roman run after the enclosure, intervening punctuation, and whitespace is a continuation of that section or a fresh section. The input gate therefore detects only the structural boundary; it does not label the later run as semantically new. -The cohort contains 1093 candidates, 143 exact controls, and 950 mismatches. Existing mismatch primary classes are preserved: 613 `pending_rule_review`, 330 `corpus_suspect`, and 7 `unsupported_character_review`. Of 950 evaluable mismatches, 258 are output-localized to the current later-run signature plus its one leading boundary cell: 254 `U+2834 ⠴ -> U+2830 ⠰` and 0 `U+2830 ⠰ -> U+2834 ⠴`. The target counted 357 raw and 333 residual cases before this cohort; it is now 48 final residual. Across raw and final residual maps, `U+2830 ⠰ -> U+2834 ⠴` is 2 -> 0. Exact controls coexist with both directions, and 321 mismatches are already independently identified corpus contradictions. Without a printed fresh-entry example or semantic enclosure model, changing continuation state would be reference-fitting; this remains a deterministic pending/corpus-review cohort only. Representative shard/index samples are retained above. +The cohort contains 1093 candidates, 143 exact controls, and 950 mismatches. Existing mismatch primary classes are preserved: 612 `pending_rule_review`, 331 `corpus_suspect`, and 7 `unsupported_character_review`. Of 950 evaluable mismatches, 260 are output-localized to the current later-run signature plus its one leading boundary cell: 256 `U+2834 ⠴ -> U+2830 ⠰` and 0 `U+2830 ⠰ -> U+2834 ⠴`. The target counted 359 raw and 333 residual cases before this cohort; it is now 48 final residual. Across raw and final residual maps, `U+2830 ⠰ -> U+2834 ⠴` is 2 -> 0. Exact controls coexist with both directions, and 321 mismatches are already independently identified corpus contradictions. Without a printed fresh-entry example or semantic enclosure model, changing continuation state would be reference-fitting; this remains a deterministic pending/corpus-review cohort only. Representative shard/index samples are retained above. ### Uppercase Roman runs containing `ED` @@ -3437,7 +3452,7 @@ The cohort contains 816 candidates, 334 exact controls, and 482 mismatches. Exis ### Uppercase Roman runs containing `ST` -UEB 10.12.1 (2024 UEB PDF pp.191-192, printed pp.163-164) says not to use a contraction when letters within an abbreviation or acronym are known to be pronounced separately, but to use the contraction in case of doubt. Rule 10.12.2 requires contractions in other abbreviations and acronyms. Consequently an uppercase `ST` surface alone cannot determine whether `s` + `t` or the `st` groupsign is required. The current cohort contains 1479 candidates, 700 exact controls, and 779 mismatches; primary classes remain 726 `pending_rule_review`, 35 `corpus_suspect`, 13 `comparison_method`, and 5 `unsupported_character_review`. Of 779 evaluable mismatches, 449 are localized to the detected current-engine run: 441 `U+280E ⠎ -> U+280C ⠌` and 1 `U+280C ⠌ -> U+280E ⠎`. The target's raw-to-residual count is 454 -> 13, and the reverse is 2 -> 1. Exact controls such as `STAYG`, `KAIST`, `DGIST`, and `POSTECH` coexist with localized mismatches such as `WSTS`, `HUST`, `OST`, and `USTR`; representative shard/index samples are preserved in the cluster table. This lexical/pronunciation distinction cannot be inferred from the input-only spelling, so no engine change or primary reclassification is made. +UEB 10.12.1 (2024 UEB PDF pp.191-192, printed pp.163-164) says not to use a contraction when letters within an abbreviation or acronym are known to be pronounced separately, but to use the contraction in case of doubt. Rule 10.12.2 requires contractions in other abbreviations and acronyms. Consequently an uppercase `ST` surface alone cannot determine whether `s` + `t` or the `st` groupsign is required. The current cohort contains 1479 candidates, 702 exact controls, and 777 mismatches; primary classes remain 724 `pending_rule_review`, 35 `corpus_suspect`, 13 `comparison_method`, and 5 `unsupported_character_review`. Of 777 evaluable mismatches, 459 are localized to the detected current-engine run: 451 `U+280E ⠎ -> U+280C ⠌` and 1 `U+280C ⠌ -> U+280E ⠎`. The target's raw-to-residual count is 464 -> 13, and the reverse is 2 -> 1. Exact controls such as `STAYG`, `KAIST`, `DGIST`, and `POSTECH` coexist with localized mismatches such as `WSTS`, `HUST`, `OST`, and `USTR`; representative shard/index samples are preserved in the cluster table. This lexical/pronunciation distinction cannot be inferred from the input-only spelling, so no engine change or primary reclassification is made. ### Uppercase segments joined by ampersand: capitalization extent @@ -3457,7 +3472,7 @@ The baseline was 802 candidates / 0 exact / 802 mismatch, preserving 776 `pendin The implemented gate shares the analyzer's complete-run predicate: one or more non-empty ASCII-letter segments joined directly by `&`, with non-alphanumeric outer boundaries. It keeps the existing Roman mode open and suppresses only rule 71's redundant wrapper around that ampersand. Spaced `Marks & Spencer`, Korean `가&나`, empty segments, and outer digit continuations remain outside the gate. Official full-encoder controls `AT&T` and `B&B` pass, and the Korean rule-71 spaced example `종이접기 & 클레이아트` retains its independent `⠴⠈⠯⠲` section. -After the change the same cohort contains 802 candidates, 607 exact and 195 mismatch. Current mismatch primary classes remain evaluator-owned: 168 `pending_rule_review`, 10 `corpus_suspect`, 2 `unsupported_character_review`, and 15 `comparison_method`. The localizer evaluates all 195 remaining mismatches and finds 0 target-localized cases (0 `U+2808 ⠈ -> U+2832 ⠲`); current raw-to-residual target count is 81 -> 79, while `U+2832 ⠲ -> U+2808 ⠈` remains 0 raw / 0 residual. Cohort exact increases by 273 and corpus-wide exact increases by the same 273, from 67,442 to 67,715. Because every changed input must satisfy this shared predicate and the baseline had no exact member, this boundary has no exact regression. The remaining 529 candidates differ elsewhere or retain an existing comparison, corpus-suspect, unsupported, or pending cause. Representative shard/index samples are retained above. +After the change the same cohort contains 802 candidates, 607 exact and 195 mismatch. Current mismatch primary classes remain evaluator-owned: 167 `pending_rule_review`, 11 `corpus_suspect`, 2 `unsupported_character_review`, and 15 `comparison_method`. The localizer evaluates all 195 remaining mismatches and finds 0 target-localized cases (0 `U+2808 ⠈ -> U+2832 ⠲`); current raw-to-residual target count is 81 -> 79, while `U+2832 ⠲ -> U+2808 ⠈` remains 0 raw / 0 residual. Cohort exact increases by 273 and corpus-wide exact increases by the same 273, from 67,442 to 67,715. Because every changed input must satisfy this shared predicate and the baseline had no exact member, this boundary has no exact regression. The remaining 529 candidates differ elsewhere or retain an existing comparison, corpus-suspect, unsupported, or pending cause. Representative shard/index samples are retained above. ### Ampersand before an attached ASCII Roman segment @@ -3471,7 +3486,11 @@ Of 18 current evaluable mismatches, 4 are localized to the occurrence-specific e ### ASCII apostrophe between Roman letter runs -This output-localized cohort requires a straight ASCII apostrophe with an ASCII letter immediately on both sides. UEB 8.4.2 (2024 UEB PDF pp.118-119, printed pp.90-91) directly prints `O'Hara`, `DON'T`, and `THAT'S` with the apostrophe cell inside the same Roman word; capitals-word mode may end at the apostrophe, but the Roman section itself does not. Detached quotation marks, Korean single quotation marks, and digit-adjacent measurement signs are excluded. The current diagnostic has 147 candidates, 0 exact controls, and 147 mismatches. Of 147 evaluable mismatches, 112 place their first difference inside the independently encoded mixed-Korean signature. Membership preserves every primary class. +This output-localized cohort requires a straight ASCII apostrophe with an ASCII letter immediately on both sides. UEB 8.4.2 (2024 UEB PDF pp.118-119, printed pp.90-91) directly prints `O'Hara`, `DON'T`, and `THAT'S` with the apostrophe cell inside the same Roman word; capitals-word mode may end at the apostrophe, but the Roman section itself does not. Detached quotation marks, Korean single quotation marks, digit-adjacent measurement signs, and an apostrophe at the end of one whitespace-delimited token before another Roman word are excluded. + +The diagnostic checkpoint had 147 candidates / 0 exact / 147 mismatch. Its occurrence-specific localizer put 112 first differences on the apostrophe boundary: 30 expected apostrophe cell `U+2804 ⠄` versus actual capital indicator `U+2820 ⠠`, and 82 expected `U+2804 ⠄` versus actual Roman indicator `U+2834 ⠴`; neither target had a localized reverse. The implementation keeps only a same-token apostrophe with ASCII letters immediately on both sides in the current Roman section, delegates its cell to the existing UEB section-7 punctuation encoder, and restarts capitals mode for an uppercase run after the nonalphabetic apostrophe. Korean Rule 37 still suppresses whole-word contractions at a Roman entry. A rejected broader route made the detached `Guitar' Listening` control exact, so the final gate explicitly does not look through whitespace. + +After the correction the cohort has 147 candidates, 68 exact controls, and 79 mismatches. Of 79 evaluable residual mismatches, 17 place their first difference inside the independently encoded current signature, but those residual transitions are other letter/spacing differences rather than either former apostrophe transition. The complete corpus exact-ID audit found 68 newly exact cases and zero formerly exact cases lost, raising the corpus total from 69,291 to 69,359. Cohort membership itself never rewrites a primary class; the engine result may make a member exact or expose an independently classified residual. ### Spaced comma between ASCII digit runs @@ -3507,13 +3526,13 @@ Current uppercase-Roman hyphen-digits measurement: 571 candidates, 194 exact con Current uppercase alphanumeric Roman-digit sequence measurement: 3429 candidates, 2096 exact controls, 1333 mismatches, 1223 members in the actual `pending_rule_review` subcluster, and 150/1333 evaluable mismatches whose first difference is localized to the current entry cells. The target `⠴ -> blank` occurs 95 times and the reverse occurs 0 times. Korean rule 35 (2024 Korean-rules PDF pp.29-30, printed pp.23-24) explicitly treats `MP3`, `A4`, `KF94`, and `D-100` as Roman-number sequences. Math rules 11/12 (PDF pp.63-65, printed pp.57-59) separately route mathematical Roman notation without the prose Roman indicator and with two-cell spacing. Official UEB 6.5.1-6.5.2 (2024 UEB PDF p.96, printed p.68) determines numeric/grade-1 continuation only after the surrounding mode has been chosen. Thus surfaces such as `A-3` cannot be assigned identifier or variable semantics from this structure alone. Exact controls and both directions are retained; no engine change is inferred. -Current consecutive ASCII-Roman word-boundary measurement: 4679 candidates, 2232 exact controls, 2447 mismatches, 2312 members in the actual `pending_rule_review` subcluster, and 26/2447 evaluable mismatches whose first difference is localized to the current boundary cell. The target reference blank versus current terminator (`⠀ -> ⠲`) occurs 26 times; the exact reverse (`⠲ -> ⠀`) occurs 0 times. Korean rule 29 (2024 Korean-rules PDF p.26, printed p.20) explicitly treats consecutive Roman items as one Roman section, with one Roman indicator before the first item and one terminator after the last; its printed `Los Angeles` and `Table of Contents` examples are exact controls. Rule 34 (PDF p.29, printed p.23) separately omits the terminator before a closing enclosure, while official UEB 9.7.1 (2024 UEB PDF p.137, printed p.109) prints the multiword prose enclosure `plays (such as Romeo and Juliet)` with ordinary internal spaces and nested closing punctuation. Together they support preserving a complete letter-and-space Roman parenthetical as prose, but not a fragment containing a function-call opener, digit, operator, or nested bracket. The input-only gate does not decide whether punctuation-separated text such as `ESS /VPP` continues the same section, so that variant remains outside this cohort. Primary classes are preserved. Before the narrow token-routing change this cohort was 4,679 candidates / 2,058 exact / 2,621 mismatch, with 128 localized `⠀ -> ⠲` and zero reverse. The cause was the final `Letters)` token of a whitespace-split Roman parenthetical being rerouted as math, which made the preceding Roman word terminate early and introduced an extra blank. After excluding only a backwards-verified complete Roman parenthetical tail from math routing, the cohort is 2,132 exact / 2,547 mismatch, with 23 target-localized and zero reverse; corpus-wide exact likewise rises by 74, from 68,101 to 68,175. The 105 removed localized boundaries include 74 newly exact cases and 31 cases that still differ elsewhere. Remaining targets such as `SYNO PEM-1`, `ACE Fair(2020)`, and `Mnet K-POP` do not satisfy the complete-parenthetical gate and remain separate residuals rather than widening this rule. +Current consecutive ASCII-Roman word-boundary measurement: 4679 candidates, 2287 exact controls, 2392 mismatches, 2256 members in the actual `pending_rule_review` subcluster, and 26/2392 evaluable mismatches whose first difference is localized to the current boundary cell. The target reference blank versus current terminator (`⠀ -> ⠲`) occurs 26 times; the exact reverse (`⠲ -> ⠀`) occurs 0 times. Korean rule 29 (2024 Korean-rules PDF p.26, printed p.20) explicitly treats consecutive Roman items as one Roman section, with one Roman indicator before the first item and one terminator after the last; its printed `Los Angeles` and `Table of Contents` examples are exact controls. Rule 34 (PDF p.29, printed p.23) separately omits the terminator before a closing enclosure, while official UEB 9.7.1 (2024 UEB PDF p.137, printed p.109) prints the multiword prose enclosure `plays (such as Romeo and Juliet)` with ordinary internal spaces and nested closing punctuation. Together they support preserving a complete letter-and-space Roman parenthetical as prose, but not a fragment containing a function-call opener, digit, operator, or nested bracket. The input-only gate does not decide whether punctuation-separated text such as `ESS /VPP` continues the same section, so that variant remains outside this cohort. Primary classes are preserved. Before the narrow token-routing change this cohort was 4,679 candidates / 2,058 exact / 2,621 mismatch, with 128 localized `⠀ -> ⠲` and zero reverse. The cause was the final `Letters)` token of a whitespace-split Roman parenthetical being rerouted as math, which made the preceding Roman word terminate early and introduced an extra blank. After excluding only a backwards-verified complete Roman parenthetical tail from math routing, the cohort is 2,132 exact / 2,547 mismatch, with 23 target-localized and zero reverse; corpus-wide exact likewise rises by 74, from 68,101 to 68,175. The 105 removed localized boundaries include 74 newly exact cases and 31 cases that still differ elsewhere. Remaining targets such as `SYNO PEM-1`, `ACE Fair(2020)`, and `Mnet K-POP` do not satisfy the complete-parenthetical gate and remain separate residuals rather than widening this rule. Current single-capital parenthesized-digits measurement: 1361 candidates, 0 exact controls, 1361 mismatches, 1357 members in the actual `pending_rule_review` subcluster, and 1342/1361 evaluable mismatches whose first difference is inside the target run plus its entry boundary. No engine change is inferred from the ambiguous prose/function surface form. Current mixed Roman+Korean boundary before uppercase headword-expansion measurement: 10 candidates, 0 exact controls, 10 mismatches, 10 members in the actual `pending_rule_review` subcluster, and 8/10 evaluable mismatches whose first difference is localized to the later headword's entry boundary/output. The detector cannot be satisfied by the earlier Roman entry. No HCA-shaped engine routing rule is introduced. -Current compact numeric+ASCII-suffix measurement: 2975 candidates, 1911 exact controls, 1064 mismatches, 993 members in the actual `pending_rule_review` subcluster, and 256/1064 evaluable mismatches whose first difference is inside the complete current-engine output signature or its immediate entry boundary. Rule 40 requires the numeric indicator and rule 69 requires Roman indicators around a Roman-written unit, but the input shape alone cannot prove that every ASCII suffix is a unit. +Current compact numeric+ASCII-suffix measurement: 2975 candidates, 1913 exact controls, 1062 mismatches, 990 members in the actual `pending_rule_review` subcluster, and 256/1062 evaluable mismatches whose first difference is inside the complete current-engine output signature or its immediate entry boundary. Rule 40 requires the numeric indicator and rule 69 requires Roman indicators around a Roman-written unit, but the input shape alone cannot prove that every ASCII suffix is a unit. | ASCII suffix | Candidates | Exact | Mismatch | Localized first diff | |---|---:|---:|---:|---:| @@ -3521,7 +3540,7 @@ Current compact numeric+ASCII-suffix measurement: 2975 candidates, 1911 exact co | `km` | 308 | 254 | 54 | 2 | | `G` | 237 | 170 | 67 | 3 | | `kg` | 209 | 176 | 33 | 4 | -| `D` | 167 | 115 | 52 | 5 | +| `D` | 167 | 116 | 51 | 5 | | `p` | 140 | 20 | 120 | 84 | | `g` | 138 | 100 | 38 | 8 | | `t` | 128 | 86 | 42 | 17 | @@ -3551,21 +3570,21 @@ Current rule-69 ASCII-unit punctuation-boundary measurement: 440 candidates, 344 Current decimal-point measurement: 4546 candidates, 3444 exact controls, 1102 mismatches, 1045 members in the actual `pending_rule_review` subcluster, and 267/1102 evaluable mismatches whose first difference is inside the complete decimal-containing word's current-engine signature. Hangeul rules 43 and 48 keep an ASCII point between digits in the numeric sequence and encode it as the decimal point; rules 35 and 69 supply controls for adjacent Roman-number chains and Roman units. This is an implementation-candidate audit, not permission to specialize on a corpus reference. Analyzer checkpoint `ed20f99` recorded the pre-fix baseline as 4,546 candidates, 2,584 exact controls, 1,962 mismatches, 1,905 pending members, and 875 localized first differences. Its dominant localized transition was reference decimal point `U+2832 ⠲` versus current Roman indicator `U+2834 ⠴` in 647 cases. After the generalized rule-43/48 guard, that transition is absent: 525 cases become exact and the other corrected prefixes expose later independent mismatches. -Current all-caps `OU` measurement: 1816 candidates, 67 exact controls, 1749 mismatches, 1663 members in the actual `pending_rule_review` subcluster, and 1356/1749 evaluable mismatches whose first difference is inside the current-engine output signature for the detected run. This is a pronunciation-sensitive UEB review cohort, not an engine routing rule. +Current all-caps `OU` measurement: 1816 candidates, 70 exact controls, 1746 mismatches, 1660 members in the actual `pending_rule_review` subcluster, and 1356/1746 evaluable mismatches whose first difference is inside the current-engine output signature for the detected run. This is a pronunciation-sensitive UEB review cohort, not an engine routing rule. -Current standalone-uppercase measurement: 62411 candidates, 51310 exact controls, 11101 mismatches, and 9639 members in the actual `pending_rule_review` subcluster. Its high frequency does not make it causal: the same input shape is exact in many cases, and a sentence containing the shape may first differ at another Roman, numeric, or punctuation structure. No engine change is inferred from this cohort. +Current standalone-uppercase measurement: 62411 candidates, 51365 exact controls, 11046 mismatches, and 9583 members in the actual `pending_rule_review` subcluster. Its high frequency does not make it causal: the same input shape is exact in many cases, and a sentence containing the shape may first differ at another Roman, numeric, or punctuation structure. No engine change is inferred from this cohort. -Current Korean-prefixed all-caps parenthetical measurement: 54492 candidates, 45633 exact controls, 8859 mismatches, and 7557 members in the actual `pending_rule_review` subcluster. This is a semantic-collision audit, not an engine routing rule; no implementation change is inferred from its reference outputs. +Current Korean-prefixed all-caps parenthetical measurement: 54492 candidates, 45656 exact controls, 8836 mismatches, and 7533 members in the actual `pending_rule_review` subcluster. This is a semantic-collision audit, not an engine routing rule; no implementation change is inferred from its reference outputs. -Current rule-34 opening-order measurement: 64382 structural candidates, 53593 exact controls, 10789 mismatches, and 1116/10789 evaluable mismatches whose first difference is inside the current engine's Korean opening-parenthesis cells. Only the 1114 localized first-cell transitions have the reference/current order `⠴` versus `⠦`. After requiring the complete reference `⠴⠐⠣` versus current/PDF `⠦⠄⠴` three-cell signature and preserving higher-priority comparison classifications, 1111 are classified as `corpus_suspect`; mere coexistence with a Korean-prefixed Roman annotation does not change a primary class. +Current rule-34 opening-order measurement: 64382 structural candidates, 53650 exact controls, 10732 mismatches, and 1117/10732 evaluable mismatches whose first difference is inside the current engine's Korean opening-parenthesis cells. Only the 1115 localized first-cell transitions have the reference/current order `⠴` versus `⠦`. After requiring the complete reference `⠴⠐⠣` versus current/PDF `⠦⠄⠴` three-cell signature and preserving higher-priority comparison classifications, 1112 are classified as `corpus_suspect`; mere coexistence with a Korean-prefixed Roman annotation does not change a primary class. Current all-caps Roman middle-dot measurement: 97 candidates, 0 exact controls, 97 mismatches, and 96 members in the actual `pending_rule_review` subcluster. This cross-cutting cohort preserves every primary class and is not an engine routing rule. -Current Roman-before-middle-dot boundary measurement: 577 candidates, 0 exact controls, 577 mismatches, 565 members in the actual `pending_rule_review` subcluster, and 450/577 evaluable mismatches whose first difference is localized to the one current terminator immediately before the middle dot. The locator encodes each real input prefix ending at the dot, so identifier state such as `K-ICS·...` is retained without consulting expected. This raises localized target coverage from 342 to 417 and reduces the then-leading residual `⠐ -> ⠲` transition from 297 to 223. Rules 29, 33, and 50 support the current terminator path but do not support the localized reference omission; no engine change or primary-class rewrite is inferred. +Current Roman-before-middle-dot boundary measurement: 577 candidates, 0 exact controls, 577 mismatches, 565 members in the actual `pending_rule_review` subcluster, and 451/577 evaluable mismatches whose first difference is localized to the one current terminator immediately before the middle dot. The locator encodes each real input prefix ending at the dot, so identifier state such as `K-ICS·...` is retained without consulting expected. This raises localized target coverage from 342 to 417 and reduces the then-leading residual `⠐ -> ⠲` transition from 297 to 223. Rules 29, 33, and 50 support the current terminator path but do not support the localized reference omission; no engine change or primary-class rewrite is inferred. -Current attached Roman-to-Korean boundary measurement: 17693 candidates, 13445 exact controls, 4248 mismatches, 3412 members in the actual `pending_rule_review` subcluster, and 0/4248 evaluable mismatches whose first difference is localized to the current boundary marker. The localized target `⠲ -> ⠸` occurs 0 times and the reverse `⠸ -> ⠲` occurs 0 times. Korean rule 29 (2024 Korean-rules PDF p.26, printed p.20) requires a Roman terminator around Roman text in a Korean sentence, whereas rule 39 (PDF pp.31-32, printed pp.25-26) applies the Korean opening/closing markers only when Roman text is the sentence's main language. Among exact candidates, 11360 expose the current rule-29 terminator at such a boundary and 0 expose the current rule-39 opening. Its printed controls include both an English sentence (`What is 김치 in English?`) and the address `www.대통령.kr` inside a Korean sentence. Official UEB 2.4.7 (2024 UEB PDF p.41, printed p.13) confirms that a UEB mode does not extend through a switch to another braille code, but does not choose whether this Korean boundary belongs to a Korean-main or Roman-main context. Therefore the attached script boundary alone cannot distinguish ordinary Korean prose from an embedded Roman-domain context. Primary classes are preserved and no engine change is inferred without a narrower input-derived dominance gate. At diagnostic checkpoint `57c4608`, this same cohort was 17,693 candidates / 12,724 exact / 4,969 mismatch, with 268 localized `⠲ -> ⠸`, zero reverse, 10,780 exact rule-29 markers, and zero exact rule-39 markers. After narrowing the engine by dominance and the PDF domain shape, it is 12,924 exact / 4,769 mismatch with no localized target or reverse; corpus-wide exact rises from 67,715 to 68,101 (+386). The remaining 1 mismatch with a current rule-39 opening is a list-heavy Korean sentence whose ASCII-leading brand tokens satisfy the mechanical word-count majority; its first difference is not localized to this boundary, so it remains a semantic pending control rather than grounds for another engine branch. +Current attached Roman-to-Korean boundary measurement: 17693 candidates, 13451 exact controls, 4242 mismatches, 3406 members in the actual `pending_rule_review` subcluster, and 0/4242 evaluable mismatches whose first difference is localized to the current boundary marker. The localized target `⠲ -> ⠸` occurs 0 times and the reverse `⠸ -> ⠲` occurs 0 times. Korean rule 29 (2024 Korean-rules PDF p.26, printed p.20) requires a Roman terminator around Roman text in a Korean sentence, whereas rule 39 (PDF pp.31-32, printed pp.25-26) applies the Korean opening/closing markers only when Roman text is the sentence's main language. Among exact candidates, 11366 expose the current rule-29 terminator at such a boundary and 0 expose the current rule-39 opening. Its printed controls include both an English sentence (`What is 김치 in English?`) and the address `www.대통령.kr` inside a Korean sentence. Official UEB 2.4.7 (2024 UEB PDF p.41, printed p.13) confirms that a UEB mode does not extend through a switch to another braille code, but does not choose whether this Korean boundary belongs to a Korean-main or Roman-main context. Therefore the attached script boundary alone cannot distinguish ordinary Korean prose from an embedded Roman-domain context. Primary classes are preserved and no engine change is inferred without a narrower input-derived dominance gate. At diagnostic checkpoint `57c4608`, this same cohort was 17,693 candidates / 12,724 exact / 4,969 mismatch, with 268 localized `⠲ -> ⠸`, zero reverse, 10,780 exact rule-29 markers, and zero exact rule-39 markers. After narrowing the engine by dominance and the PDF domain shape, it is 12,924 exact / 4,769 mismatch with no localized target or reverse; corpus-wide exact rises from 67,715 to 68,101 (+386). The remaining 1 mismatch with a current rule-39 opening is a list-heavy Korean sentence whose ASCII-leading brand tokens satisfy the mechanical word-count majority; its first difference is not localized to this boundary, so it remains a semantic pending control rather than grounds for another engine branch. -Current rule-39 narrowed-scope audit: 947 candidates, 592 exact controls, 355 mismatches, and 343 members in the actual `pending_rule_review` subcluster. This input-derived scope is recorded separately from the direct-boundary output-localizer; primary classes are unchanged. At the implementation checkpoint, this conservative first-script-word approximation contains 385 exact cases, accounting for all but one of the corpus-wide +386 net gain; no localized reverse transition is observed in the direct-boundary cohort. +Current rule-39 narrowed-scope audit: 947 candidates, 593 exact controls, 354 mismatches, and 342 members in the actual `pending_rule_review` subcluster. This input-derived scope is recorded separately from the direct-boundary output-localizer; primary classes are unchanged. At the implementation checkpoint, this conservative first-script-word approximation contains 385 exact cases, accounting for all but one of the corpus-wide +386 net gain; no localized reverse transition is observed in the direct-boundary cohort. Current inline parenthesized-operator measurement: 23 candidates, 17 exact controls, 6 mismatches, 6 members in the actual `pending_rule_review` subcluster, and 2/6 evaluable mismatches whose first differing cell is inside the emitted structure. Primary classes are preserved. At that implementation checkpoint, the strict cohort moved from 0 to 17 exact cases; the corpus-wide total moved from 65,491 to 65,514 (+23 exact) because the same PDF-backed spacing rule also applied outside the stricter Korean-boundary audit gate. These are immutable checkpoint counts rather than the report's later cumulative total. The complete standard suite remained 5,141/5,141. @@ -3622,18 +3641,18 @@ Families are diagnostics, not automatic normalization permissions. Rules 68/69 c | Shard | Exact | Total | Accuracy | |---|---:|---:|---:| -| `sentence_01.json` | 20780 | 25000 | 83.12% | -| `sentence_02.json` | 20317 | 25000 | 81.27% | -| `sentence_03.json` | 20906 | 25000 | 83.62% | -| `sentence_04.json` | 7288 | 8528 | 85.46% | +| `sentence_01.json` | 20813 | 25000 | 83.25% | +| `sentence_02.json` | 20321 | 25000 | 81.28% | +| `sentence_03.json` | 20931 | 25000 | 83.72% | +| `sentence_04.json` | 7294 | 8528 | 85.53% | ## Overlapping mismatch traits | Trait | Count | |---|---:| -| `contains_ascii_digits` | 11237 | -| `contains_ascii_letters` | 14074 | -| `contains_delimiter_or_quote` | 14237 | +| `contains_ascii_digits` | 11185 | +| `contains_ascii_letters` | 14006 | +| `contains_delimiter_or_quote` | 14169 | | `input_not_nfkc` | 703 | ## Samples @@ -3817,6 +3836,7 @@ Against the immediately preceding 63,399-exact run, exact matches increased by 2 | Rules 29/34 multiword Roman parenthetical continuity | 5,141/5,141 | 68,175/83,528 | 81.62% | A backwards-verified, closed letter-and-space Roman parenthetical tail stays on the prose route; function calls, digits, operators, nested brackets, and punctuation-separated forms remain outside; 74 cases became exact | | Rules 29/32/71 ampersand before attached Roman segment | 5,141/5,141 | 68,187/83,528 | 81.63% | Official UEB `&c`, `AT&T`, and `B&B` keep one Roman section across attached `&`; the spaced Korean Rule-71 example, left ASCII alphanumerics, repeated ampersands, and trailing digits delimit the gate; 12 cases became exact | | UEB 8.4.2 capitals-word nonletter boundary | 5,141/5,141 | 69,291/83,528 | 82.96% | Starting from the accepted 68,439-exact checkpoint, capitals-word handling ends at each nonletter and later uppercase runs restart independently; official `AT&T`, `B&B`, `MP3`, and `TVOntario` delimit the token/span boundary; 852 cases became exact and the complete exact-ID audit found zero former exact cases lost | +| UEB 8.4.2 same-token internal Roman apostrophe | 5,141/5,141 | 69,359/83,528 | 83.04% | A straight apostrophe stays in the Roman section only with immediate same-token ASCII letters on both sides, while capitals mode restarts for an uppercase suffix; official `O'Hara`, `DON'T`, `THAT'S`, and `SHE'LL` plus detached quote and measurement controls delimit the gate; 68 cases became exact and the complete exact-ID audit found zero former exact cases lost | The latest full `cargo test -p braillify test_by_testcase --release -- --nocapture` run was accepted from its custom testcase summary, not the trailing filtered harness: `총 테스트 케이스: 5141`, `성공: 5141`, `실패: 0`, and `Skip (limitation): 0`. diff --git a/libs/braillify/examples/nikl_corpus_analyze.rs b/libs/braillify/examples/nikl_corpus_analyze.rs index 2100ae88..02fb9503 100644 --- a/libs/braillify/examples/nikl_corpus_analyze.rs +++ b/libs/braillify/examples/nikl_corpus_analyze.rs @@ -5885,10 +5885,27 @@ fn markdown(report: &AnalysisReport) -> String { pp.90-91) directly prints `O'Hara`, `DON'T`, and `THAT'S` with the apostrophe cell \ inside the same Roman word; capitals-word mode may end at the apostrophe, but the \ Roman section itself does not. Detached quotation marks, Korean single quotation \ - marks, and digit-adjacent measurement signs are excluded. The current diagnostic \ - has {} candidates, {} exact controls, and {} mismatches. Of {} evaluable mismatches, \ - {} place their first difference inside the independently encoded mixed-Korean \ - signature. Membership preserves every primary class.\n", + marks, digit-adjacent measurement signs, and an apostrophe at the end of one \ + whitespace-delimited token before another Roman word are excluded.\n\n\ + The diagnostic checkpoint had 147 candidates / 0 exact / 147 mismatch. Its \ + occurrence-specific localizer put 112 first differences on the apostrophe boundary: \ + 30 expected apostrophe cell `U+2804 ⠄` versus actual capital indicator `U+2820 ⠠`, \ + and 82 expected `U+2804 ⠄` versus actual Roman indicator `U+2834 ⠴`; neither target \ + had a localized reverse. The implementation keeps only a same-token apostrophe with \ + ASCII letters immediately on both sides in the current Roman section, delegates its \ + cell to the existing UEB section-7 punctuation encoder, and restarts capitals mode \ + for an uppercase run after the nonalphabetic apostrophe. Korean Rule 37 still \ + suppresses whole-word contractions at a Roman entry. A rejected broader route made \ + the detached `Guitar' Listening` control exact, so the final gate explicitly does not \ + look through whitespace.\n\n\ + After the correction the cohort has {} candidates, {} exact controls, and {} \ + mismatches. Of {} evaluable residual mismatches, {} place their first difference \ + inside the independently encoded current signature, but those residual transitions \ + are other letter/spacing differences rather than either former apostrophe transition. \ + The complete corpus exact-ID audit found 68 newly exact cases and zero formerly exact \ + cases lost, raising the corpus total from 69,291 to 69,359. Cohort membership itself \ + never rewrites a primary class; the engine result may make a member exact or expose \ + an independently classified residual.\n", stats.candidates, stats.exact, stats.mismatch, @@ -6984,7 +7001,8 @@ fn markdown(report: &AnalysisReport) -> String { | Rules 29/39 Roman-to-Korean mode ownership | 5,141/5,141 | 68,101/83,528 | 81.53% | Same-token Korean is wrapped only in a Roman-majority document or the official dot-delimited `www.대통령.kr` domain shape; all three rule-39 PDF controls remain exact and 386 corpus cases became exact |\n\ | Rules 29/34 multiword Roman parenthetical continuity | 5,141/5,141 | 68,175/83,528 | 81.62% | A backwards-verified, closed letter-and-space Roman parenthetical tail stays on the prose route; function calls, digits, operators, nested brackets, and punctuation-separated forms remain outside; 74 cases became exact |\n\ | Rules 29/32/71 ampersand before attached Roman segment | 5,141/5,141 | 68,187/83,528 | 81.63% | Official UEB `&c`, `AT&T`, and `B&B` keep one Roman section across attached `&`; the spaced Korean Rule-71 example, left ASCII alphanumerics, repeated ampersands, and trailing digits delimit the gate; 12 cases became exact |\n\ - | UEB 8.4.2 capitals-word nonletter boundary | 5,141/5,141 | 69,291/83,528 | 82.96% | Starting from the accepted 68,439-exact checkpoint, capitals-word handling ends at each nonletter and later uppercase runs restart independently; official `AT&T`, `B&B`, `MP3`, and `TVOntario` delimit the token/span boundary; 852 cases became exact and the complete exact-ID audit found zero former exact cases lost |\n", + | UEB 8.4.2 capitals-word nonletter boundary | 5,141/5,141 | 69,291/83,528 | 82.96% | Starting from the accepted 68,439-exact checkpoint, capitals-word handling ends at each nonletter and later uppercase runs restart independently; official `AT&T`, `B&B`, `MP3`, and `TVOntario` delimit the token/span boundary; 852 cases became exact and the complete exact-ID audit found zero former exact cases lost |\n\ + | UEB 8.4.2 same-token internal Roman apostrophe | 5,141/5,141 | 69,359/83,528 | 83.04% | A straight apostrophe stays in the Roman section only with immediate same-token ASCII letters on both sides, while capitals mode restarts for an uppercase suffix; official `O'Hara`, `DON'T`, `THAT'S`, and `SHE'LL` plus detached quote and measurement controls delimit the gate; 68 cases became exact and the complete exact-ID audit found zero former exact cases lost |\n", ); text.push_str( "\nThe latest full `cargo test -p braillify test_by_testcase --release -- --nocapture` \ diff --git a/libs/braillify/src/english_logic.rs b/libs/braillify/src/english_logic.rs index 4600cf4d..5502a2d5 100644 --- a/libs/braillify/src/english_logic.rs +++ b/libs/braillify/src/english_logic.rs @@ -250,6 +250,17 @@ pub(crate) fn should_render_symbol_as_english( // a complete ASCII-letter run so spaced prose, Hangul, and outer // alphanumeric continuations keep their existing routes. '&' => is_attached_ascii_roman_ampersand(word_chars, index), + // UEB 8.4.2 keeps the apostrophe inside the Roman word in its + // `O'Hara`, `DON'T`, and `THAT'S` examples. Capitals-word mode may + // terminate at this nonalphabetic symbol, but the surrounding Roman + // section does not. Detached quotes and digit measurement marks stay + // on their existing punctuation routes. + '\'' => { + prev_char.is_some_and(|ch| ch.is_ascii_alphabetic()) + && word_chars + .get(index + 1) + .is_some_and(|ch| ch.is_ascii_alphabetic()) + } ',' => { if !is_english { return false; @@ -427,6 +438,40 @@ mod tests { ); } + /// UEB 8.4.2 keeps a word-internal apostrophe inside the Roman section. + #[rstest::rstest] + #[case::official_name("O'Hara", true)] + #[case::official_contraction("DON'T", true)] + #[case::official_possessive("THAT'S", true)] + #[case::detached_open("'word", false)] + #[case::detached_close("word'", false)] + #[case::measurement("6'2", false)] + fn internal_apostrophe_requires_ascii_letters_on_both_sides( + #[case] input: &str, + #[case] expected: bool, + ) { + let word = input.chars().collect::>(); + let index = word.iter().position(|ch| *ch == '\'').unwrap(); + assert_eq!( + should_render_symbol_as_english(true, true, &[], '\'', &word, index, &[]), + expected, + ); + } + + #[test] + fn apostrophe_does_not_join_the_next_whitespace_delimited_word() { + let word = "Guitar'".chars().collect::>(); + assert!(!should_render_symbol_as_english( + true, + true, + &[], + '\'', + &word, + word.len() - 1, + &["Listening"], + )); + } + /// UEB 3.1.1 keeps attached Roman segments on both sides of `&` in the /// same mode. The complete-run boundary deliberately excludes spaced /// prose, Hangul boundaries, and outer alphanumeric continuations. diff --git a/libs/braillify/src/rules/korean/rule_28.rs b/libs/braillify/src/rules/korean/rule_28.rs index f25aaeed..bcd917e8 100644 --- a/libs/braillify/src/rules/korean/rule_28.rs +++ b/libs/braillify/src/rules/korean/rule_28.rs @@ -93,8 +93,10 @@ impl BrailleRule for Rule28 { // alphabet signs and multi-letter groupsigns, while suppressing UEB // whole-word contractions. Encode each contiguous ASCII letter run in // one pass so the shared UEB preference/morphology algorithm can choose - // contractions across the whole word. Apostrophe continuations retain - // the legacy position-aware path because they are not fresh word starts. + // contractions across the whole word. Lowercase apostrophe continuations + // retain the legacy position-aware path because they are not fresh word + // starts. An uppercase continuation is encoded as a run so UEB 8.4.2 can + // restart capitals mode after the nonalphabetic apostrophe. let starts_ascii_run = c.is_ascii_alphabetic() && ctx .index @@ -106,7 +108,7 @@ impl BrailleRule for Rule28 { .checked_sub(1) .and_then(|index| ctx.word_chars.get(index)) .is_some_and(|previous| matches!(previous, '\'' | '\u{2019}')); - if starts_ascii_run && !follows_apostrophe { + if starts_ascii_run && (!follows_apostrophe || c.is_ascii_uppercase()) { let run_end = ctx.index + ctx.word_chars[ctx.index..] .iter() @@ -306,6 +308,23 @@ mod tests { assert_eq!(crate::encode_to_unicode(input).unwrap(), expected); } + /// UEB 8.4.2 keeps an internal apostrophe in the Roman letters-sequence but + /// terminates capitals-word mode at that nonalphabetic symbol. The Roman + /// surfaces are official UEB examples; the neutral Korean wrapper exercises + /// Rule 28/29 routing. Korean Rule 37 still suppresses the `that` wordsign in + /// `THAT'S`, so its initial run retains the permitted `th` groupsign instead. + #[rstest::rstest] + #[case::official_name("가 O'Hara 나", "⠫⠀⠴⠠⠕⠄⠠⠓⠜⠁⠲⠀⠉")] + #[case::official_contraction("가 DON'T 나", "⠫⠀⠴⠠⠠⠙⠕⠝⠄⠠⠞⠲⠀⠉")] + #[case::official_possessive("가 THAT'S 나", "⠫⠀⠴⠠⠠⠹⠁⠞⠄⠠⠎⠲⠀⠉")] + #[case::official_two_letter_suffix("가 SHE'LL 나", "⠫⠀⠴⠠⠠⠩⠑⠄⠠⠠⠇⠇⠲⠀⠉")] + fn korean_wrapper_restarts_capitals_after_internal_apostrophe( + #[case] input: &str, + #[case] expected: &str, + ) { + assert_eq!(crate::encode_to_unicode(input).as_deref(), Ok(expected)); + } + #[test] fn english_dominant_wrap_resumes_ueb_wordsigns_after_korean_span() { let mut owned = crate::test_helpers::CtxOwned::for_text("in", true); diff --git a/libs/braillify/src/rules/korean/rule_english_symbol.rs b/libs/braillify/src/rules/korean/rule_english_symbol.rs index 6b09a44a..56504a85 100644 --- a/libs/braillify/src/rules/korean/rule_english_symbol.rs +++ b/libs/braillify/src/rules/korean/rule_english_symbol.rs @@ -100,7 +100,16 @@ impl BrailleRule for RuleEnglishSymbol { ctx.state.is_english = true; ctx.state.needs_english_continuation = false; } - if let Some(encoded) = symbol_shortcut::encode_english_char_symbol_shortcut(*sym) { + let encoded = if *sym == '\'' { + // `use_english_symbol` is true here only for an ASCII apostrophe + // immediately between ASCII letters. Keep that narrow UEB 8.4.2 + // role local instead of making detached straight quotes globally + // eligible for the UEB apostrophe cell. + crate::rules::english_ueb::rule_7::encode_punctuation(*sym) + } else { + symbol_shortcut::encode_english_char_symbol_shortcut(*sym) + }; + if let Some(encoded) = encoded { ctx.emit_slice(&encoded); if *sym == '-' && ctx.state.is_english { // UEB 5.7.2의 `CD-ROM`은 순수 대문자 segment 사이의 하이픈 From b84b543eddf2d7d66319b81a9255293c74a6af3f Mon Sep 17 00:00:00 2001 From: devfive Date: Tue, 1 Sep 2026 19:12:39 +0900 Subject: [PATCH 58/77] Document NIKL corpus clarification questions --- docs/corpus-analysis/NIKL_2025_V1_inquiry.md | 329 +++++++++++++++++++ 1 file changed, 329 insertions(+) create mode 100644 docs/corpus-analysis/NIKL_2025_V1_inquiry.md diff --git a/docs/corpus-analysis/NIKL_2025_V1_inquiry.md b/docs/corpus-analysis/NIKL_2025_V1_inquiry.md new file mode 100644 index 00000000..4fa880e8 --- /dev/null +++ b/docs/corpus-analysis/NIKL_2025_V1_inquiry.md @@ -0,0 +1,329 @@ +# 2025년 국립국어원 말뭉치 점자 참조값 및 2024 개정 한국 점자 규정 해석 질의서 + +## 1. 문의 취지 + +안녕하세요. + +2025년 국립국어원 한국어–점자 병렬 말뭉치의 묵자 입력과 점자 참조값을 2024년 개정 「한국 점자 규정」에 따라 대조하는 과정에서, 규정만으로는 일관된 판단이 어렵거나 말뭉치 참조값의 확인이 필요한 유형을 발견하여 문의드립니다. + +이 질의서는 말뭉치의 오류를 미리 단정하려는 것이 아닙니다. 다음 세 경우를 구분하여 자동 점역기가 임의의 예외 규칙을 만들지 않도록 하는 것이 목적입니다. + +1. 현행 규정으로 점형을 결정할 수 있고 말뭉치 참조값의 정정 여부만 확인하면 되는 경우 +2. 발음·문서 장르·수식 여부 같은 외부 의미 정보가 있어야 점형을 결정할 수 있는 경우 +3. 현행 규정에 명시되지 않은 문자 또는 경계여서 별도의 해석이 필요한 경우 + +가능하시다면 각 질문에 대해 다음 사항을 함께 알려 주시기를 부탁드립니다. + +- 올바른 점자 셀 배열 +- 적용되는 규정의 항·붙임·예 번호 +- 해당 말뭉치 참조값의 정정 필요 여부 +- 묵자 표면형만으로 결정 가능한지, 별도의 발음·문서 모드·의미 주석이 필요한지 +- 정정이 필요할 때 공식 정오표 또는 차기 말뭉치 버전에 반영되는지 + +## 2. 대조 방법과 재현 범위 + +- 분석 대상: 83,528문장 +- 현재 완전 일치: 69,359문장(83.04%) +- 불일치 또는 인코딩 불가: 14,169문장 +- 분류 결과: 규정 검토 대기 12,586건, 제34항 참조값 모순 후보 1,112건, NFKC 비교 312건, 미지원 문자 검토 159건 +- 비교 필드: 묵자 `input`과 말뭉치 점자 참조값 `unicode`만 사용 +- 완전 일치의 정의: 공백을 포함한 유니코드 점자 셀 배열이 처음부터 끝까지 동일한 경우 +- 위치 표기: `sentence_XX.json #N`에서 `N`은 해당 shard의 0부터 시작하는 배열 인덱스 +- 계측 방법: 문장에 특정 표면형이 존재하는지만 세지 않고, 기대 출력과 현재 규칙 기반 출력의 **최초 차이 셀**이 그 구조의 실제 출력 범위 안에 있는지도 별도로 확인 +- 정확 대조군: 같은 구조를 포함하면서 전체 출력이 완전히 일치하는 문장 +- 역전 대조: 기대와 현재 출력의 셀 전이가 반대 방향으로 나타나는 경우를 별도 계수 + +경쟁 제품의 출력 필드는 읽거나 정답으로 비교하지 않았습니다. 또한 아래 건수는 서로 겹칠 수 있는 진단 코호트이므로 합산하면 전체 불일치 건수가 되지 않습니다. 코호트에 포함됐다는 이유만으로 기존의 1차 분류를 바꾸지도 않았습니다. + +규정 근거는 [2024 개정 한국 점자 규정 PDF](../2024%20개정%20한국%20점자%20규정.pdf)와 같은 저장소에 보존된 2024 UEB 규정을 확인했습니다. 상세 계측과 대표 출력은 [전체 재현 보고서](NIKL_2025_V1.md)에 있습니다. + +## 3. 우선 답변을 요청드리는 사항 + +### 질문 1. 제34항의 괄호와 로마자표 순서 + +2024 개정 한국 점자 규정 제34항의 `링컨(Lincoln)은 …` 예에서는 다음 순서로 읽힙니다. + +1. 한글 여는 소괄호 `⠦⠄` +2. 로마자표 `⠴` +3. 괄호 안 로마자 +4. 한글 닫는 소괄호 + +현재 점역기도 이 순서를 따릅니다. 그런데 말뭉치에는 한글 바로 뒤의 닫힌 로마자 주석에서 기대값이 `⠴⠐⠣`(로마자표 + UEB 여는 괄호)로 시작하고, 현재 규정 경로가 `⠦⠄⠴`(한글 여는 괄호 + 로마자표)로 시작하는 사례가 1,112건 있습니다. 단순히 같은 문장에 괄호가 있다는 이유가 아니라, 실제 입력 위치에서 이 세 셀의 순서가 모두 반대로 확인되는 경우만 셌습니다. + +대표 사례: + +- `sentence_01.json #35`: `… 국제정보디스플레이학술대회(IMID) 2022 …` +- `sentence_03.json #48`: `… 폐쇄회로(CC)TV …` +- `sentence_01.json #77`: `… OLED …`가 포함된 동일 유형 문장 + +문의: + +1. 한글 문장 안에서 `한글(로마자)`를 점역할 때 여는 괄호와 로마자표의 올바른 순서는 `⠦⠄⠴`입니까? +2. 그렇다면 `⠴⠐⠣`로 시작하는 위 1,112건은 말뭉치 참조값 정정 대상입니까? +3. 괄호 안이 약어, 일반 영단어, 로마자와 숫자의 결합이어도 같은 순서를 적용합니까? + +### 질문 2. 로마자 바로 뒤에 붙은 가운뎃점(U+00B7) 앞의 로마자 종료표 + +제29항은 한글 문장 안의 로마자 앞뒤에 로마자표와 로마자 종료표를 쓰도록 합니다. 제33항은 종료표를 생략하거나 문장 부호 앞으로 옮기는 경계를 열거하지만, 가운뎃점은 그 목록에 없습니다. 제50항은 가운뎃점을 앞뒤 말에 붙여 쓰도록 규정합니다. + +`로마자·한글/로마자` 경계 후보 577건은 완전 일치 대조군이 0건이고 모두 불일치입니다. 그중 451건은 최초 차이가 정확히 가운뎃점 직전의 한 셀에 위치하며, 말뭉치는 가운뎃점 `⠐`을 기대하지만 규정 기반 출력은 먼저 로마자 종료표 `⠲`을 냅니다. 더 좁은 `AI·SW`형 순수 대문자 코호트도 97건 모두 불일치입니다. + +대표 사례: + +- `sentence_01.json #57`: `PC·모바일` +- `sentence_02.json #10`: `AI·SW교육` +- `sentence_03.json #91`: `HA·Ca필러` +- `sentence_04.json #49`: `Fed·연준` + +문의: + +1. `PC·모바일`에서 `PC` 뒤의 로마자 종료표를 써야 합니까, 생략해야 합니까? +2. 가운뎃점이 로마자 구간을 이어 주는 기호라면 그 근거 항과 적용 범위는 무엇입니까? +3. `AI·SW`, 화학식, 곱셈 기호처럼 같은 표면형이 서로 다른 의미를 가질 때, 말뭉치에 의미/모드 주석이 필요합니까? +4. 현행 규정상 종료표를 생략할 근거가 없다면 위 참조값들은 정정 대상입니까? + +### 질문 3. `△한글`에서 삼각형 뒤 공백의 소유권 + +제49항은 삼각형 문장 부호의 점형을 정하고 인쇄물의 띄어쓰기를 따르게 합니다. 제72항은 `△`를 항목 표지로도 사용하지만 PDF 예는 줄 배치 또는 공백이 있는 목록입니다. 말뭉치에는 입력 자체가 `△한글`처럼 붙어 있는데 기대 점자에는 삼각형 뒤 공백이 들어간 사례가 반복됩니다. + +- 후보 377건 / 완전 일치 0건 / 불일치 377건 +- 최초 차이가 삼각형과 바로 뒤 첫 한글 셀 범위에 있는 사례 335건 +- 대표: `sentence_01.json #3907` `△청구…`, `sentence_02.json #17` `△문화치유`, `sentence_03.json #244` `△에듀테크&콘텐츠` + +문의: + +1. 묵자에 공백이 없는 `△문화치유`도 항목 표지라는 의미만으로 점자에서는 `△` 뒤를 띄어야 합니까? +2. 제49항의 “묵자의 띄어쓰기를 따른다”와 제72항의 항목 표지 관례 중 어느 규정이 우선합니까? +3. 문장 부호인 삼각형과 목록 표지인 삼각형을 구분하려면 구조 또는 의미 주석이 필요합니까? +4. 입력에 없는 공백을 기대값에 넣는 것이 맞다면 말뭉치의 묵자 입력도 함께 정규화해야 합니까? + +### 질문 4. 약어·두문자어의 발음에 따른 UEB 약자 사용 + +제32항에 따라 로마자 구간 내부는 UEB를 따르는 것으로 이해했습니다. UEB 10.12.1은 약어의 글자를 따로 발음하는 것이 알려져 있으면 해당 글자 결합 약자를 쓰지 않고, 발음을 모르면 약자를 쓰도록 합니다. 같은 대문자 표면형이 브랜드명·단어처럼 발음되기도 하고 이니셜로 하나씩 발음되기도 하므로 묵자 철자만으로는 결과를 결정할 수 없습니다. + +현재 측정: + +| 철자 구조 | 후보 | 완전 일치 | 불일치 | 목표 최초 차이 | 역방향 | +|---|---:|---:|---:|---:|---:| +| `OU` 포함 | 1,816 | 70 | 1,746 | `o ⠕ → ou 약자 ⠳` 1,355 | 0 | +| `ST` 포함 | 1,479 | 702 | 777 | `s ⠎ → st 약자 ⠌` 451 | 1 | +| `AR` 포함 | 1,022 | 370 | 652 | `a ⠁ → ar 약자 ⠜` 400 | 2 | +| `ED` 포함 | 816 | 334 | 482 | `e ⠑ → ed 약자 ⠫` 327 | 0 | + +완전 일치 대조에는 `JUSTOUCH`(`sentence_01.json #850`), `YOU`(`sentence_02.json #727`), `STAYG`(`sentence_01.json #44`), `KAIST`(`sentence_02.json #82`), `DGIST`(`sentence_03.json #281`), `OLED`(`sentence_01.json #26`) 등이 있습니다. 반면 `MOU`, `AR/ARS`, `LED/GED`, `GH/GHP`, `ERP/ERBUD`, `SH` 계열에서는 글자별 발음 여부에 따라 참조값이 달라지는 것으로 보입니다. + +문의: + +1. 말뭉치 구축 시 약어의 실제 한국어 발음(글자별 발음/단어 발음)을 조사하여 약자 사용 여부를 결정했습니까? +2. 발음 정보가 없는 자동 점역기는 UEB의 “의심스러우면 약자를 사용”하는 기본값을 그대로 적용해야 합니까? +3. `MOU`, `OLED`, `LED`, `GH`, `ERP`처럼 관용 발음이 여러 개인 항목에는 발음 사전 또는 발음 주석을 제공할 수 있습니까? +4. 동일 철자에 서로 다른 참조값이 허용된다면 그 선택을 재현할 수 있는 최소 메타데이터는 무엇입니까? + +### 질문 5. `A(14)`형 표면을 인명 표지로 볼지 수식으로 볼지 + +`A(14)`처럼 단일 대문자 뒤에 숫자 괄호가 붙는 후보는 1,361건이며 완전 일치가 0건, 불일치가 1,361건입니다. 1,342건은 최초 차이가 해당 진입 경계 안에 있습니다. 기사 문맥에서는 익명 인명과 나이로 보이지만, 같은 표면은 함수·수학 변수·문항 번호일 수도 있습니다. + +대표 사례: + +- `sentence_01.json #80`: `A(54)씨` +- `sentence_02.json #186`: `A(14)양` +- `sentence_03.json #51`: `A(11)군` + +문의: + +1. 일반 기사 문장 속 `A(14)양`은 로마자+숫자 괄호로 점역해야 합니까, 수학식으로 점역해야 합니까? +2. 조사 `씨/양/군` 같은 문맥만으로 일반 규칙을 정해도 됩니까? +3. 표면형만으로 수식 반례를 완전히 배제할 수 없다면 말뭉치에 “일반문/수식” 모드 주석이 필요합니까? +4. 현재 참조값을 만든 점역 과정에서 이 형태를 수식으로 분류한 기준이 있다면 공개가 가능합니까? + +### 질문 6. `HCA(Home Connectivity Alliance)`형 약어 풀이의 로마자 구간 재진입 + +대문자 표제어 뒤에 닫힌 괄호가 있고, 괄호 안에 공백으로 구분된 로마자 단어가 둘 이상인 구조는 175건입니다. 현재 결과는 완전 일치 17건, 불일치 158건이며, 불일치의 기존 1차 분류는 규정 검토 대기 146건, 제34항 모순 후보 5건, NFKC 비교 1건, 미지원 문자 6건입니다. 이 구조는 **입력 형태를 모은 교차 진단군일 뿐 1차 분류를 바꾸지 않습니다**. + +정확 대조: + +- `sentence_01.json #3647`: `TB(Top View Battle)` +- `sentence_02.json #313`: `U-ENTER(Uzbekistan Entrepreneurship Innovation Center)` +- `sentence_03.json #20370`: `KINGDOM(Moonlight Tears)` +- `sentence_04.json #2928`: `QSR(Quick Service Restaurant)` + +불일치 대표: + +- `sentence_01.json #18`: `매터(Matter)와 HCA(Home Connectivity Alliance) 표준…` + +이 사례는 첫 번째 `Matter`의 로마자 구간과 조사 `와`가 끝난 뒤, 두 번째 `HCA(...)`에서 로마자표를 새로 시작해야 하는지에 관한 문제입니다. 괄호 내용에 연산자·첨자·중첩 괄호가 없도록 좁혀도, 이 표면 구조만으로 수식 반례를 완전히 배제할 수는 없습니다. + +문의: + +1. `Matter` 뒤 한글 조사 `와`가 나오면 첫 로마자 구간은 종료되고, 뒤의 `HCA`에서 로마자표를 다시 써야 합니까? +2. `HCA(Home Connectivity Alliance)` 전체는 일반 로마자 풀이로 보아야 합니까? +3. 같은 형태의 수식 가능성을 배제하려면 어떤 의미/문서 모드 정보가 필요합니까? +4. 제29항의 “둘 이상의 로마자가 이어 나올 때”에서 한글 조사나 한글 단어가 사이에 있으면 연속성이 명백히 끝나는 것으로 보아도 됩니까? + +### 질문 7. `F-35`형 식별자와 수학식의 구분 + +대문자 로마자 run + 하이픈 + 숫자 구조는 571건이며 완전 일치 194건, 불일치 377건입니다. 그중 262건은 최초 차이가 해당 구조의 진입/출력 범위에 있습니다. 제35항은 `D-100`을 로마자와 숫자가 이어지는 예로 제시하지만, 수학 규정은 대문자 변수와 마이너스를 별도 경로로 처리합니다. + +정확 대조에는 `GLS-5310`(`sentence_01.json #88`), `MMPI-2`(`sentence_02.json #167`), `X-2`(`sentence_03.json #217`), `GPT-4`(`sentence_04.json #48`)가 있습니다. 불일치 대표는 `sentence_04.json #323`의 `F-35`입니다. + +문의: + +1. 일반 기사 속 `F-35`, `AH-64`, `K-9` 같은 기종·모델명은 제35항의 로마자-숫자 연속 규칙으로 처리합니까? +2. 동일한 `A-3`형 표면이 수학 변수와 수의 뺄셈일 때는 어떤 입력 정보로 구분해야 합니까? +3. 하이픈-minus U+002D 하나만 제공된 말뭉치에서 식별자와 마이너스를 자동 판별하도록 요구합니까, 아니면 의미 모드 주석이 필요합니까? + +### 질문 8. 공백·괄호를 사이에 둔 로마자 구간의 연속 범위 + +제29항의 `Los Angeles`, `Table of Contents`는 공백으로 나뉜 여러 로마자 단어를 하나의 로마자 구간으로 처리하는 근거가 됩니다. 다만 실제 문장에서는 괄호, 쉼표, 슬래시, 숫자, 한글 조사 등을 사이에 둔 경우가 많아 구간이 언제 끝나는지 불명확합니다. + +| 진단 구조 | 후보 | 완전 일치 | 불일치 | 구조 안 최초 차이 | +|---|---:|---:|---:|---:| +| 연속 ASCII 로마자 단어의 공백 경계 | 4,679 | 2,287 | 2,392 | 26 | +| 닫힌 로마자 괄호 뒤 공백 후 새 로마자 | 1,093 | 143 | 950 | 260 | +| 앞 한글 단어 뒤 공백 후 로마자 괄호 표제어 | 4,695 | 3,550 | 1,145 | 163 | +| 기존 로마자 뒤 공백 후 대문자 단어 | 1,729 | 760 | 969 | 기존 진입 경계 진단군 | + +정확 대조로는 PDF 예와 같은 다단어 로마자, `Global X`(`sentence_01.json #2785`), `BYD), BMW`(`sentence_02.json #1282`), `KODEX 인도 Nifty50`(`sentence_03.json #46`) 등이 공존합니다. 남은 대표 경계에는 `SYNO PEM-1`, `ACE Fair(2020)`, `Mnet K-POP`, `ESS /VPP`가 있습니다. + +문의: + +1. 공백만 있는 연속 로마자 단어는 언제나 하나의 로마자 구간입니까? +2. 닫는 괄호 뒤 공백, 쉼표, 슬래시는 구간을 끝내는 경계입니까, 아니면 열거된 로마자 전체가 하나의 구간입니까? +3. 괄호로 닫힌 로마자 설명 뒤에 이어지는 새 로마자 고유명은 로마자표를 새로 써야 합니까? +4. 규정의 “이어 나올 때”를 자동 점역기가 판단할 수 있도록 경계별 예를 추가해 주실 수 있습니까? + +### 질문 9. 앰퍼샌드와 뒤따르는 로마자·숫자의 구간 + +UEB에는 `AT&T`, `B&B`, `&c`처럼 앰퍼샌드와 로마자가 붙어 있는 예가 있고, 한국 점자 규정 제71항에는 한글 사이의 독립적인 앰퍼샌드 예가 있습니다. 이를 근거로 완전한 `A&B`형 로마자 run은 한 구간으로 처리할 수 있었지만, 숫자가 이어지거나 한쪽에만 로마자가 붙는 경우는 남아 있습니다. + +- 붙은 로마자 `A&B`형: 802건 / 완전 일치 607건 / 불일치 195건 +- `&c`처럼 오른쪽 로마자만 붙은 좁은 구조: 30건 / 완전 일치 12건 / 불일치 18건 +- 남은 대표 전이: `S&P500`류에서 앰퍼샌드 셀과 로마자 종료표 경계 79건 + +문의: + +1. `S&P500`, `R&D`, `AT&T5G`에서 앰퍼샌드는 하나의 로마자 구간 안에 있습니까? +2. 앰퍼샌드 뒤 숫자가 이어질 때 제35항의 로마자-숫자 연속 규칙이 그대로 적용됩니까? +3. 한글과 붙은 `과학&ICT`, 독립 기호인 `종이접기 & 클레이아트`, 괄호 안 `&TEAM`은 각각 어느 항을 우선 적용합니까? +4. 공백 유무만으로 독립 기호와 로마자 구간 내부 기호를 구분해도 됩니까? + +### 질문 10. 숫자+ASCII 접미부의 단위·식별자·분수 해석 + +숫자 바로 뒤에 ASCII 문자가 붙은 구조는 2,975건이며 완전 일치 1,913건, 불일치 1,062건입니다. 256건은 최초 차이가 그 전체 토큰 또는 진입 경계 안에 있습니다. 제69항의 공식 단위는 일반화할 수 있지만, 같은 표면형이 단위·변수·모델명일 수 있어 모든 ASCII 접미부를 단위로 볼 수는 없습니다. + +정확 대조에는 `118.0GW`(`sentence_01.json #343`), `20kg`(`sentence_02.json #29`), `692g`(`sentence_03.json #60`) 등이 있습니다. 남은 대표 사례는 다음과 같습니다. + +- `sentence_01.json #633`: `3.5~8.5m` +- `sentence_02.json #893`: `50~800m` +- `sentence_03.json #140`: `1/2` +- `sentence_04.json #871`: `98M` + +문의: + +1. `m`, `M`, `G`, `p`, `bp`, `GB`처럼 대소문자와 분야에 따라 뜻이 달라지는 접미부는 말뭉치에서 어떤 기준으로 단위로 판정합니까? +2. `3.5~8.5m`에서 범위 기호와 단위의 로마자표/종료표 범위는 어떻게 됩니까? +3. 일반문에 있는 ASCII `1/2`는 단순 슬래시 표기입니까, 수학 분수입니까? 의미가 분수여도 입력이 LaTeX가 아니라면 어떤 규칙을 적용합니까? +4. `98M`이 수량 단위, 모델명, 변수 중 무엇인지 표면형만으로 판정할 수 없을 때 필요한 메타데이터는 무엇입니까? +5. 단위의 대소문자는 묵자 그대로 보존하여 서로 다른 점역 결과를 내야 합니까? + +### 질문 11. ASCII 문장 부호와 한국어 문장 부호의 정규화 + +말뭉치에는 책·작품 제목을 ASCII `<...>`로 표시한 사례가 있으나 제49항은 별도의 한국어 문장 부호 `〈...〉`(U+3008/U+3009)를 규정합니다. 이 경계의 상위 잔여 최초 차이는 98건입니다. 또한 ASCII 하이픈-minus, en dash, 줄표가 기사 편집 과정에서 혼용된 사례가 있고, 관련 잔여 전이는 약 80건입니다. + +대표 사례: + +- ASCII `<제목>`이 겹낫표/홑화살괄호 의미로 쓰인 기사 제목 +- `sentence_01.json #189`: `… 써봐 - 슈퍼 캐리` +- `sentence_02.json #2352`: `이이남 -각 사람에게…` +- `sentence_03.json #4223`: en dash 사용 +- `sentence_04.json #976`: `하쿠토-R` + +문의: + +1. ASCII `<`와 `>`가 제목 표지로 쓰였을 때 자동으로 U+3008/U+3009 의미의 한국어 문장 부호로 정규화해야 합니까? +2. 아니면 코드 포인트가 다르면 수학의 부등호 또는 ASCII 기호로 점역해야 합니까? +3. U+002D `-`, U+2013 `–`, 한국어 줄표가 혼용된 입력은 원문 코드 포인트를 보존해야 합니까, 의미에 맞게 정규화해야 합니까? +4. 제49항의 “묵자의 띄어쓰기를 따른다”는 입력에 있는 공백을 그대로 보존하라는 뜻입니까? + +### 질문 12. NFKC로만 같아지는 312건의 비교 정책 + +현재 312건은 원 입력을 그대로 점역한 결과와는 다르지만, 입력에 NFKC를 적용한 뒤에는 말뭉치 참조값과 일치하여 `comparison_method`로 별도 분류했습니다. 이 분류는 엔진이 모든 입력을 무조건 NFKC로 바꾸어야 한다는 뜻이 아닙니다. 예를 들어 로마 숫자 표시는 제36항에 근거해 해당 로마자로 풀 수 있지만, `㈜`, `ℓ`, 제목 기호 등은 의미와 코드 포인트가 달라질 수 있습니다. + +문의: + +1. 말뭉치 참조값은 원 `input` 코드 포인트를 기준으로 작성됐습니까, NFKC 등 사전 정규화를 거친 문자열을 기준으로 작성됐습니까? +2. 호환 문자와 분해된 ASCII 철자가 의미상 같아도 동일한 참조값을 요구합니까? +3. 공식 권장 정규화 형식(NFC/NFKC)과 예외 문자 목록이 있습니까? +4. 정규화 전후가 다른 312건을 평가할 때 어느 결과를 정답으로 보아야 합니까? + +### 질문 13. 현행 엔진에서 단독으로도 인코딩되지 않는 문자 + +다음 159건은 포함 문자를 하나씩 따로 인코딩해도 실패하는 것으로 확인했습니다. PDF에서 독립적인 지원 근거를 찾지 못해 “오류”가 아니라 “미지원 문자 검토”로 남겨 두었습니다. + +| 문자 | 포함 문장 수 | NFKC 분해 | 현재 진단 | +|---|---:|---|---| +| `㈜` U+321C | 138 | `(주)` | 괄호 문자 조직 표지 | +| `㈔` U+3214 | 2 | `(사)` | 괄호 문자 조직 표지 | +| `ℓ` U+2113 | 9 | `l` | 단위/문자 기호 | +| `☏` U+260F | 3 | 그대로 | 전화 기호 | +| `⸱` U+2E31 | 2 | 그대로 | 구두점 | +| `ˑ` U+02D1 | 1 | 그대로 | 구두점 | +| `‥` U+2025 | 1 | `..` | 두 점 줄임표 | +| `│` U+2502 | 1 | 그대로 | 레이아웃 세로선 | +| `▲` U+25B2 | 1 | 그대로 | 검은 삼각형 | +| `▴` U+25B4 | 1 | 그대로 | 작은 검은 삼각형 | +| `♥` U+2665 | 1 | 그대로 | 하트 기호 | +| `〮` U+302E | 1 | 그대로 | 한글 성조점 | + +두 문자는 한 문장에 함께 나타날 수 있어 문자별 “포함 문장 수”의 합은 159보다 클 수 있습니다. + +문의: + +1. 각 문자에 공식 권장 점형이 있습니까? +2. `㈜→(주)`, `㈔→(사)`, `ℓ→l`, `‥→..`와 같은 호환 분해를 자동 점역 전에 적용해도 됩니까? +3. `│`, `▲`, `▴`, `♥`처럼 문서 레이아웃 또는 장식 역할일 수 있는 문자는 삭제, 설명, 대체 점형 중 어느 정책을 적용해야 합니까? +4. 공식 규정의 지원 범위 밖이라면 말뭉치가 기대하는 점형을 어떤 근거로 재현해야 합니까? + +### 질문 14. 묵자 입력에 없는 띄어쓰기나 교정의 허용 범위 + +제49항은 문장 부호의 띄어쓰기를 묵자에 따르도록 합니다. 분석 과정에서도 입력에 붙은 `있다`를 임의로 띄어 쓰던 전처리를 제거했을 때 71건이 정확해졌고, PDF에 실제 공백이 있는 표준 예는 공백을 그대로 유지했습니다. 반대로 `△한글` 등 일부 말뭉치 참조값은 입력에 없는 공백을 요구하는 것으로 보입니다. + +문의: + +1. 점역기는 말뭉치의 `input`을 문자 단위로 충실히 점역해야 합니까, 맞춤법·띄어쓰기 오류를 먼저 교정해야 합니까? +2. 교정을 허용한다면 공식 교정 규칙과 원문/교정문 대응 정보를 제공할 수 있습니까? +3. 참조값이 교정된 문장을 기준으로 만들어졌다면 교정된 묵자 필드도 함께 제공할 수 있습니까? +4. 입력에 없는 공백을 점자 참조값에만 넣는 것이 평가상 의도된 동작입니까? + +## 4. 말뭉치 구축·정정 절차에 관한 공통 질문 + +1. 각 참조값을 작성할 때 적용한 한국 점자 규정 및 UEB 판본은 무엇입니까? +2. 일반문/수식/화학식/프로그래밍 코드/식별자/단위/고유명사/약어 발음을 구분하는 내부 주석이 있습니까? +3. 내부 주석이 있다면 공개 데이터에도 포함하거나 판정 기준을 문서화할 수 있습니까? +4. 재현 가능한 참조값 오류를 신고할 공식 창구와 필요한 최소 자료는 무엇입니까? +5. `sentence_XX.json #N`과 같은 shard/index는 버전 간에 안정적인 식별자로 사용할 수 있습니까? 아니라면 문장 ID를 제공할 수 있습니까? +6. 정정 사항은 정오표, 패치 버전, 다음 연도 말뭉치 중 어디에 반영됩니까? +7. 동일 입력의 참조값이 판본에 따라 바뀔 때 변경 이력과 적용 규정 항을 제공할 수 있습니까? + +## 5. 요청드리는 답변 양식 + +아래 형식으로 답변해 주시면 규칙 구현, 데이터 정정, 의미 주석 필요 항목을 서로 섞지 않고 반영할 수 있습니다. + +| 질문/사례 ID | 올바른 점자 셀 배열 | 근거 규정 | 말뭉치 정정 필요 | 표면형만으로 판정 가능 | 필요한 의미/발음 주석 | 비고/반영 예정 버전 | +|---|---|---|---|---|---|---| +| 예: 질문 1 / `sentence_01.json #35` | | | 예/아니요 | 예/아니요 | | | + +셀 배열은 가능하다면 유니코드 점자와 점 번호 표기를 함께 부탁드립니다. 하나의 문장 안에 여러 문제가 있으면, 문의한 표면형 주변의 최소 구간만 답변해 주셔도 됩니다. + +## 6. 우선순위 요약 + +답변을 한 번에 모두 제공하기 어렵다면 다음 순서로 우선 확인을 부탁드립니다. + +1. **제34항 괄호/로마자표 순서 1,112건**: 규정 예와 반대인 3셀 참조 서명으로 재현됨 +2. **로마자+가운뎃점 경계 577건**: 완전 일치 대조 0건, 451건이 종료표 한 셀에 직접 위치 +3. **`△한글` 공백 377건**: 완전 일치 대조 0건, 입력에 없는 공백의 허용 여부 +4. **약어 발음과 UEB 약자**: 정확/불일치가 동일 철자 구조에 공존하여 발음 정보 없이는 결정 불가 +5. **`A(14)`, `HCA(...)`, `F-35`**: 일반문·식별자·수식의 의미 모드 우선순위 +6. **미지원 문자 159건 및 NFKC 비교 312건**: 공식 정규화와 문자 지원 정책 + +위 항목들의 공식 해석을 받기 전에는 말뭉치 참조값에 맞추기 위한 개별 입력 예외나 기대값 역산 규칙을 추가하지 않고, 재현 가능한 진단으로만 보존할 예정입니다. From d7b4250afe53d3a1fff6a2297ef0d2a22d80447a Mon Sep 17 00:00:00 2001 From: devfive Date: Tue, 1 Sep 2026 19:21:41 +0900 Subject: [PATCH 59/77] Add Roman parenthesis clarification question --- docs/corpus-analysis/NIKL_2025_V1_inquiry.md | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/docs/corpus-analysis/NIKL_2025_V1_inquiry.md b/docs/corpus-analysis/NIKL_2025_V1_inquiry.md index 4fa880e8..f659cd07 100644 --- a/docs/corpus-analysis/NIKL_2025_V1_inquiry.md +++ b/docs/corpus-analysis/NIKL_2025_V1_inquiry.md @@ -295,6 +295,26 @@ UEB에는 `AT&T`, `B&B`, `&c`처럼 앰퍼샌드와 로마자가 붙어 있는 3. 참조값이 교정된 문장을 기준으로 만들어졌다면 교정된 묵자 필드도 함께 제공할 수 있습니까? 4. 입력에 없는 공백을 점자 참조값에만 넣는 것이 평가상 의도된 동작입니까? +### 질문 15. 로마자·단위·숫자 뒤에 붙은 괄호를 어느 점자 체계로 적는지 + +앞에서 설명한 제34항의 1,112건은 **한글 뒤 괄호 안에 로마자가 있는 경우**입니다. 이와 반대로 `BSI(73)`, `Merit(4위)`, `M(41)`, `43bp(1bp…)`처럼 로마자·단위 뒤의 괄호 안에 숫자 또는 한글이 있는 경우도 별도로 남습니다. 기존 output-localized 코호트를 제외한 뒤에도 기대 한글 여는 소괄호의 첫 셀 `⠦`와 현재 UEB 여는 괄호의 첫 셀 `⠐`이 충돌하는 잔여가 133건입니다. + +대표 사례: + +- `sentence_01.json #1318`: `BSI(73)` +- `sentence_02.json #286`: `Merit(4위)` +- `sentence_03.json #551`: `조너선M(41)` +- `sentence_04.json #119`: `43bp(1bp는 0.01%포인트)` + +제33항의 괄호 속 `(, : ; ―)`는 괄호 자체를 열거한 것이 아니라 쉼표·쌍점·쌍반점·줄표의 목록으로 읽히므로, 위 소괄호의 점형을 직접 결정하지는 않는 것으로 이해했습니다. 제34항은 “로마자가 괄호 등으로 묶일 때”를 규정하지만, 위 사례에서 괄호로 묶인 내용은 로마자가 아니라 숫자 또는 한글입니다. + +문의: + +1. `BSI(73)`의 괄호는 로마자 구간 내부의 UEB 괄호입니까, 한글 점자의 소괄호입니까? +2. 괄호 안이 숫자, 한글, 로마자일 때 각각 로마자 구간의 종료 위치가 달라집니까? +3. `43bp(1bp는 …)`처럼 괄호 안이 단위 설명일 때는 바깥 단위의 로마자 구간을 먼저 닫아야 합니까? +4. 괄호의 점형을 결정하는 기준이 “괄호 앞 문자”, “괄호 안 주언어”, “전체 문장의 주언어” 중 무엇인지 예와 함께 알려 주실 수 있습니까? + ## 4. 말뭉치 구축·정정 절차에 관한 공통 질문 1. 각 참조값을 작성할 때 적용한 한국 점자 규정 및 UEB 판본은 무엇입니까? From b7a990ed3446408cac139b0da5a30aad473b5f36 Mon Sep 17 00:00:00 2001 From: devfive Date: Tue, 1 Sep 2026 19:48:14 +0900 Subject: [PATCH 60/77] Update coverage snapshots for Roman mode fixes --- .../snapshots2/coverage_extra2__eng_dom_korean_in_middle.snap | 4 ++-- .../coverage_extra2__eng_dom_long_with_uppercase.snap | 4 ++-- .../tests/snapshots2/coverage_extra2__roman_mixed.snap | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/libs/braillify/tests/snapshots2/coverage_extra2__eng_dom_korean_in_middle.snap b/libs/braillify/tests/snapshots2/coverage_extra2__eng_dom_korean_in_middle.snap index e6d62be4..47ad045b 100644 --- a/libs/braillify/tests/snapshots2/coverage_extra2__eng_dom_korean_in_middle.snap +++ b/libs/braillify/tests/snapshots2/coverage_extra2__eng_dom_korean_in_middle.snap @@ -3,5 +3,5 @@ source: libs/braillify/tests/coverage_extra2.rs expression: rendered --- input = "Hello 안녕 World" -unicode = ok: "⠴⠠⠓⠑⠇⠇⠕⠀⠸⠷⠣⠒⠉⠻⠸⠾⠀⠠⠺⠕⠗⠇⠙⠲" -bytes = ok: [52, 32, 19, 17, 7, 7, 21, 0, 56, 55, 35, 18, 9, 59, 56, 62, 0, 32, 58, 21, 23, 7, 25, 50] +unicode = ok: "⠴⠠⠓⠑⠇⠇⠕⠀⠸⠷⠣⠒⠉⠻⠸⠾⠀⠠⠸⠺⠲" +bytes = ok: [52, 32, 19, 17, 7, 7, 21, 0, 56, 55, 35, 18, 9, 59, 56, 62, 0, 32, 56, 58, 50] diff --git a/libs/braillify/tests/snapshots2/coverage_extra2__eng_dom_long_with_uppercase.snap b/libs/braillify/tests/snapshots2/coverage_extra2__eng_dom_long_with_uppercase.snap index fcf64c57..526b8493 100644 --- a/libs/braillify/tests/snapshots2/coverage_extra2__eng_dom_long_with_uppercase.snap +++ b/libs/braillify/tests/snapshots2/coverage_extra2__eng_dom_long_with_uppercase.snap @@ -3,5 +3,5 @@ source: libs/braillify/tests/coverage_extra2.rs expression: rendered --- input = "API와 SDK를 사용해서 ABCDE 작업을 한다" -unicode = ok: "⠴⠠⠠⠁⠏⠊⠲⠧⠀⠠⠠⠴⠎⠙⠅⠲⠐⠮⠀⠇⠬⠶⠚⠗⠠⠎⠀⠠⠠⠴⠁⠃⠉⠙⠑⠲⠀⠨⠁⠎⠃⠮⠀⠚⠒⠊" -bytes = ok: [52, 32, 32, 1, 15, 10, 50, 39, 0, 32, 32, 52, 14, 25, 5, 50, 16, 46, 0, 7, 44, 54, 26, 23, 32, 14, 0, 32, 32, 52, 1, 3, 9, 25, 17, 50, 0, 40, 1, 14, 3, 46, 0, 26, 18, 10] +unicode = ok: "⠴⠠⠠⠁⠏⠊⠲⠧⠀⠴⠠⠠⠎⠙⠅⠲⠐⠮⠀⠇⠬⠶⠚⠗⠠⠎⠀⠴⠠⠠⠁⠃⠉⠙⠑⠲⠀⠨⠁⠎⠃⠮⠀⠚⠒⠊" +bytes = ok: [52, 32, 32, 1, 15, 10, 50, 39, 0, 52, 32, 32, 14, 25, 5, 50, 16, 46, 0, 7, 44, 54, 26, 23, 32, 14, 0, 52, 32, 32, 1, 3, 9, 25, 17, 50, 0, 40, 1, 14, 3, 46, 0, 26, 18, 10] diff --git a/libs/braillify/tests/snapshots2/coverage_extra2__roman_mixed.snap b/libs/braillify/tests/snapshots2/coverage_extra2__roman_mixed.snap index 62bb1fd2..213d67c8 100644 --- a/libs/braillify/tests/snapshots2/coverage_extra2__roman_mixed.snap +++ b/libs/braillify/tests/snapshots2/coverage_extra2__roman_mixed.snap @@ -3,5 +3,5 @@ source: libs/braillify/tests/coverage_extra2.rs expression: rendered --- input = "VIII과 IX" -unicode = ok: "⠴⠠⠠⠧⠊⠊⠊⠲⠈⠧⠀⠠⠠⠴⠊⠭⠲" -bytes = ok: [52, 32, 32, 39, 10, 10, 10, 50, 8, 39, 0, 32, 32, 52, 10, 45, 50] +unicode = ok: "⠴⠠⠠⠧⠊⠊⠊⠲⠈⠧⠀⠴⠠⠠⠊⠭⠲" +bytes = ok: [52, 32, 32, 39, 10, 10, 10, 50, 8, 39, 0, 52, 32, 32, 10, 45, 50] From 44c544758c09ddfd7440bac3e46d621ff6820516 Mon Sep 17 00:00:00 2001 From: devfive Date: Tue, 1 Sep 2026 20:02:56 +0900 Subject: [PATCH 61/77] Update numeric ellipsis coverage snapshot --- .../tests/snapshots/coverage_extra__ellipsis_numbers.snap | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libs/braillify/tests/snapshots/coverage_extra__ellipsis_numbers.snap b/libs/braillify/tests/snapshots/coverage_extra__ellipsis_numbers.snap index 050d9bd2..cc721319 100644 --- a/libs/braillify/tests/snapshots/coverage_extra__ellipsis_numbers.snap +++ b/libs/braillify/tests/snapshots/coverage_extra__ellipsis_numbers.snap @@ -3,5 +3,5 @@ source: libs/braillify/tests/coverage_extra.rs expression: rendered --- input = "1, 2, 3, ..., 10" -unicode = ok: "⠼⠁⠂⠀⠼⠃⠂⠀⠼⠉⠐⠀⠲⠲⠲⠐⠀⠼⠁⠚" -bytes = ok: [60, 1, 2, 0, 60, 3, 2, 0, 60, 9, 16, 0, 50, 50, 50, 16, 0, 60, 1, 26] +unicode = ok: "⠼⠁⠐⠀⠼⠃⠐⠀⠼⠉⠐⠀⠲⠲⠲⠐⠀⠼⠁⠚" +bytes = ok: [60, 1, 16, 0, 60, 3, 16, 0, 60, 9, 16, 0, 50, 50, 50, 16, 0, 60, 1, 26] From b1e42d5377ad6af48e8b4e0bfc7f5252d59c72f9 Mon Sep 17 00:00:00 2001 From: devfive Date: Tue, 1 Sep 2026 20:16:47 +0900 Subject: [PATCH 62/77] Fix unit comma boundary test expectation --- libs/braillify/src/rules/korean/rule_69.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/braillify/src/rules/korean/rule_69.rs b/libs/braillify/src/rules/korean/rule_69.rs index 4d9ca861..cbea27db 100644 --- a/libs/braillify/src/rules/korean/rule_69.rs +++ b/libs/braillify/src/rules/korean/rule_69.rs @@ -796,7 +796,7 @@ mod tests { #[rstest::rstest] #[case::kilogram_in_parentheses("상자(20kg)당", "⠼⠃⠚⠴⠅⠛⠠⠴", "⠴⠅⠛⠲⠠⠴")] #[case::centimetre_before_korean_comma("키는 173cm, 몸무게는", "⠼⠁⠛⠉⠴⠉⠍⠐", "⠴⠉⠍⠲⠐")] - #[case::centimetre_before_next_measurement("키 173cm, 68kg", "⠼⠁⠛⠉⠴⠉⠍⠂", "⠴⠉⠍⠲⠂")] + #[case::centimetre_before_next_measurement("키 173cm, 68kg", "⠼⠁⠛⠉⠴⠉⠍⠐", "⠴⠉⠍⠲⠐")] #[case::metre_before_sentence_period("비거리 130m.", "⠼⠁⠉⠚⠴⠍⠲", "⠴⠍⠲⠲")] #[case::compatibility_kilogram_in_parentheses("상자(20㎏)당", "⠼⠃⠚⠴⠅⠛⠠⠴", "⠴⠅⠛⠲⠠⠴")] fn omits_unit_terminator_at_rule_33_or_34_boundary( From 2220ec484364bbeadd4bfd8e780714f7878481f3 Mon Sep 17 00:00:00 2001 From: devfive Date: Tue, 1 Sep 2026 20:49:52 +0900 Subject: [PATCH 63/77] Cover corpus accuracy defense branches --- .../braillify/examples/nikl_corpus_analyze.rs | 19 ++++++++++++++ .../braillify/src/rules/english_ueb/engine.rs | 13 ++++++++++ .../src/rules/english_ueb/korean_context.rs | 21 +++++++++++++++ libs/braillify/src/rules/korean/rule_28.rs | 16 ++++++++++++ libs/braillify/src/rules/korean/rule_69.rs | 9 +++++++ libs/braillify/src/rules/korean/rule_71.rs | 26 +++++-------------- .../token_rules/math_expression/apply.rs | 22 ++++++++++++++++ .../token_rules/math_expression/helpers.rs | 10 +++++++ 8 files changed, 116 insertions(+), 20 deletions(-) diff --git a/libs/braillify/examples/nikl_corpus_analyze.rs b/libs/braillify/examples/nikl_corpus_analyze.rs index 02fb9503..edae0073 100644 --- a/libs/braillify/examples/nikl_corpus_analyze.rs +++ b/libs/braillify/examples/nikl_corpus_analyze.rs @@ -7057,6 +7057,19 @@ fn main() { mod tests { use super::*; + #[test] + fn loads_all_sentence_corpus_shards_for_analysis() { + let cases = load_cases().expect("NIKL sentence shards must load"); + + assert_eq!(cases.len(), 83_528); + assert!( + cases + .iter() + .all(|located| located.shard.starts_with("sentence_") + && located.shard.ends_with(".json")) + ); + } + #[rstest::rstest] #[case::zero_shards(0, 0, Some("no NIKL corpus shards"))] #[case::zero_cases(1, 0, Some("zero cases"))] @@ -7343,6 +7356,12 @@ mod tests { assert_eq!(actual, expected); } + #[test] + fn rejects_parenthesized_digits_after_an_ascii_letter() { + let input = std::hint::black_box(String::from("AB(14)")); + assert!(single_capital_parenthesized_digit_spans(&input).is_empty()); + } + #[rstest::rstest] #[case::single_capital("미 F-35 전투기", vec!["F-35"])] #[case::multi_capital("육군 AH-64 헬기", vec!["AH-64"])] diff --git a/libs/braillify/src/rules/english_ueb/engine.rs b/libs/braillify/src/rules/english_ueb/engine.rs index f380515f..0e889085 100644 --- a/libs/braillify/src/rules/english_ueb/engine.rs +++ b/libs/braillify/src/rules/english_ueb/engine.rs @@ -869,4 +869,17 @@ mod test_support { }) .collect() } + + /// Korean rule 37 suppresses the complete lower wordsign `in` at a Roman + /// entry while retaining ordinary alphabet signs through the shared engine. + #[test] + fn korean_word_entry_spells_complete_lower_wordsign() { + let chars = std::hint::black_box("in").chars().collect::>(); + + let actual = EnglishUebEngine::new() + .encode_korean_word(&chars, false, false, false, true, false) + .expect("rule-37 Roman letters must encode"); + + assert_eq!(actual, cells("⠊⠝")); + } } diff --git a/libs/braillify/src/rules/english_ueb/korean_context.rs b/libs/braillify/src/rules/english_ueb/korean_context.rs index 5854a096..1e789dc9 100644 --- a/libs/braillify/src/rules/english_ueb/korean_context.rs +++ b/libs/braillify/src/rules/english_ueb/korean_context.rs @@ -259,6 +259,27 @@ mod tests { assert_eq!(got, expected); } + /// In an open Roman span, UEB §10.5's restricted `con` lower groupsign can + /// be reached after earlier letters; the strong-sign cascade does not own it. + #[test] + fn matches_restricted_lower_groupsign_inside_open_roman_word() { + let chars = std::hint::black_box("reconsider") + .chars() + .collect::>(); + let matched = match_korean_prefix(KoreanPrefixInput { + word: &chars, + pos: 2, + wrap_active: true, + is_all_uppercase: false, + at_entry: false, + standalone_wordsign: false, + }) + .expect("internal con groupsign must match"); + + assert_eq!(matched.cells, vec![decode_unicode('⠒')]); + assert_eq!(matched.consumed, 3); + } + #[rstest::rstest] #[case::alphabetic_you("you", decode_unicode('⠽'), 3)] #[case::strong_this("this", decode_unicode('⠹'), 4)] diff --git a/libs/braillify/src/rules/korean/rule_28.rs b/libs/braillify/src/rules/korean/rule_28.rs index bcd917e8..1e140e2b 100644 --- a/libs/braillify/src/rules/korean/rule_28.rs +++ b/libs/braillify/src/rules/korean/rule_28.rs @@ -339,6 +339,22 @@ mod tests { assert_eq!(owned.result, vec![20]); } + /// Rule 37's PDF sentence `Can you help me?` permits the phrase-interior + /// `you` wordsign because both adjacent whitespace-delimited words are Roman. + #[test] + fn rule_37_phrase_interior_word_uses_standalone_wordsign() { + let mut owned = crate::test_helpers::CtxOwned::for_text("you", true) + .with_prev_word("Can") + .with_remaining_words(["help", "me?"]); + let mut ctx = owned.ctx_at(0); + + assert!(matches!( + Rule28.apply(&mut ctx).unwrap(), + RuleResult::Consumed + )); + assert_eq!(owned.result, vec![52, decode_unicode('⠽')]); + } + #[test] fn apply_skips_non_korean() { let mut owned = crate::test_helpers::CtxOwned::for_text("A", false); diff --git a/libs/braillify/src/rules/korean/rule_69.rs b/libs/braillify/src/rules/korean/rule_69.rs index cbea27db..a82928a0 100644 --- a/libs/braillify/src/rules/korean/rule_69.rs +++ b/libs/braillify/src/rules/korean/rule_69.rs @@ -641,6 +641,15 @@ mod tests { } } + /// The compatibility-unit grammar sends only ASCII-letter runs here. + /// Rejecting a numeric component directly keeps that defensive contract + /// observable without weakening the accepted Rule 68/69 glyph set. + #[test] + fn unit_letter_encoder_rejects_non_letter_component() { + let error = encode_rule_69_unit_letters(&[std::hint::black_box('1')]).unwrap_err(); + assert!(error.contains("cannot encode rule 69 Roman unit letters: 1")); + } + #[test] fn every_rule_68_or_69_ascii_derivation_matches_every_owner_glyph() { for (spelling, owners) in compatibility_ascii_unit_owners() { diff --git a/libs/braillify/src/rules/korean/rule_71.rs b/libs/braillify/src/rules/korean/rule_71.rs index bae13950..b2b56a6d 100644 --- a/libs/braillify/src/rules/korean/rule_71.rs +++ b/libs/braillify/src/rules/korean/rule_71.rs @@ -170,28 +170,14 @@ mod tests { /// 제71항 — § 정보 기호가 직후 숫자를 만나면 종료표(⠲) 생략 (line 84-86). #[test] fn rule71_section_sign_before_digit_omits_terminator() { - let word: Vec = "§1".chars().collect(); - let ct = CharType::Symbol('§'); - let mut skip = 0usize; - let mut state = crate::rules::context::EncoderState::new(false); - let mut out = Vec::new(); - let mut ctx = RuleContext { - word_chars: &word, - index: 0, - char_type: &ct, - prev_word: "", - remaining_words: &[], - has_korean_char: false, - is_all_uppercase: false, - ascii_starts_at_beginning: false, - skip_count: &mut skip, - state: &mut state, - result: &mut out, - }; + // 제71항 붙임 PDF example `헌법§1①`: Korean context activates the + // information-symbol wrapper, while the following digit omits ⠲. + let mut owned = crate::test_helpers::CtxOwned::for_text("헌법§1①", false); + let mut ctx = owned.ctx_at(2); let outcome = Rule71.apply(&mut ctx).unwrap(); + assert!(matches!(outcome, RuleResult::Consumed)); - // No ⠲ terminator because next char is a digit - assert!(!out.contains(&crate::unicode::decode_unicode('⠲'))); + assert_eq!(ctx.result.as_slice(), encode_unicode_cells("⠴⠘⠎")); } /// rule_71:85 — § followed by NON-digit (or end of input) appends ⠲ terminator. diff --git a/libs/braillify/src/rules/token_rules/math_expression/apply.rs b/libs/braillify/src/rules/token_rules/math_expression/apply.rs index 195af0ba..497c064c 100644 --- a/libs/braillify/src/rules/token_rules/math_expression/apply.rs +++ b/libs/braillify/src/rules/token_rules/math_expression/apply.rs @@ -905,6 +905,10 @@ mod tests { #[case::ueb_letter_list("(q, r)", false)] #[case::math_function("f(x)", false)] #[case::operator_interrupts_prose_run("(x + y)", false)] + #[case::no_closing_parenthesis("Romeo Juliet", false)] + #[case::function_with_spaced_argument("f(x y)", false)] + #[case::missing_opening_parenthesis("Romeo Juliet)", false)] + #[case::invalid_trailing_digit("(Romeo Juliet)1", false)] fn recognizes_only_complete_multiword_roman_parenthetical_tails( #[case] input: &str, #[case] expected: bool, @@ -925,6 +929,24 @@ mod tests { ); } + /// Decimal-context spacing recognizes each structural marker independently: + /// the parser sentinel, the Rule 12 ellipsis, and a combining math mark. + #[rstest::rstest] + #[case::unit_separator("a\u{001f}b", "ab", true)] + #[case::midline_ellipsis("a⋯b", "ab", true)] + #[case::combining_mark("ab", "a\u{0305}", true)] + #[case::plain_expression("a+b", "a+b", false)] + fn detects_decimal_context_spacing_markers( + #[case] text: &str, + #[case] chars: &str, + #[case] expected: bool, + ) { + assert_eq!( + needs_decimal_context_spacing(text, &chars.chars().collect::>()), + expected + ); + } + use crate::rules::token::{SpaceKind, WordMeta, WordToken}; use std::borrow::Cow; diff --git a/libs/braillify/src/rules/token_rules/math_expression/helpers.rs b/libs/braillify/src/rules/token_rules/math_expression/helpers.rs index e52a6246..ff9bb20d 100644 --- a/libs/braillify/src/rules/token_rules/math_expression/helpers.rs +++ b/libs/braillify/src/rules/token_rules/math_expression/helpers.rs @@ -464,6 +464,16 @@ mod tests { assert!(result.is_none()); } + /// The PDF's `√분산` form is a mixed expression because the radical is + /// directly attached to Korean text; it must produce a concrete cell sequence. + #[test] + fn try_encode_mixed_math_slice_encodes_valid_expression() { + let chars = std::hint::black_box("√분산").chars().collect::>(); + let result = try_encode_mixed_math_slice(&chars, MathContext::default()); + + assert!(result.is_some()); + } + #[test] fn try_encode_mixed_math_prefix_encodes_math_prefix_before_korean_suffix() { let prefix: Vec = "x²".chars().collect(); From 5646de51d0ee23d65d890e931ebcb4dd1a7f1224 Mon Sep 17 00:00:00 2001 From: devfive Date: Tue, 1 Sep 2026 21:35:06 +0900 Subject: [PATCH 64/77] Exercise remaining coverage through real paths --- .../braillify/examples/nikl_corpus_analyze.rs | 7 +-- libs/braillify/src/lib.rs | 49 +++++++++++++++++++ .../braillify/src/rules/english_ueb/engine.rs | 13 ----- libs/braillify/src/rules/korean/rule_28.rs | 35 ++++++++++--- libs/braillify/src/rules/korean/rule_69.rs | 31 +++++++++--- libs/braillify/src/rules/korean/rule_71.rs | 30 +++++------- .../token_rules/math_expression/apply.rs | 24 +++++++++ 7 files changed, 139 insertions(+), 50 deletions(-) diff --git a/libs/braillify/examples/nikl_corpus_analyze.rs b/libs/braillify/examples/nikl_corpus_analyze.rs index edae0073..1a25015b 100644 --- a/libs/braillify/examples/nikl_corpus_analyze.rs +++ b/libs/braillify/examples/nikl_corpus_analyze.rs @@ -272,7 +272,7 @@ fn load_cases() -> Result, String> { paths.retain(|path| { path.file_name() .and_then(|name| name.to_str()) - .is_some_and(|name| name.starts_with("sentence_") && name.ends_with(".json")) + .is_some_and(braillify::corpus_analysis::is_sentence_corpus_shard_name) }); paths.sort(); let shard_count = paths.len(); @@ -2393,10 +2393,7 @@ fn single_capital_parenthesized_digit_spans(input: &str) -> Vec { let mut spans = Vec::new(); for (start_byte, ch) in input.char_indices() { if !ch.is_ascii_uppercase() - || input[..start_byte] - .chars() - .next_back() - .is_some_and(|previous| previous.is_ascii_alphanumeric()) + || braillify::corpus_analysis::has_ascii_alphanumeric_before(input, start_byte) { continue; } diff --git a/libs/braillify/src/lib.rs b/libs/braillify/src/lib.rs index 68386e98..b9bddde2 100644 --- a/libs/braillify/src/lib.rs +++ b/libs/braillify/src/lib.rs @@ -1,5 +1,54 @@ use std::{borrow::Cow, cell::RefCell}; +/// Small, semantic-neutral predicates shared with the NIKL analysis example. +/// Keeping them here lets the ordinary library test target verify analyzer +/// input boundaries without making the whole example a coverage target. +#[doc(hidden)] +pub mod corpus_analysis { + /// Whether a corpus filename belongs to the deterministic sentence shards. + pub fn is_sentence_corpus_shard_name(name: &str) -> bool { + name.starts_with("sentence_") && name.ends_with(".json") + } + + /// Whether the Unicode scalar immediately before `byte_index` is an ASCII + /// letter or digit. Callers provide a boundary from `str::char_indices`. + pub fn has_ascii_alphanumeric_before(input: &str, byte_index: usize) -> bool { + input + .get(..byte_index) + .unwrap_or_default() + .chars() + .next_back() + .is_some_and(|previous| previous.is_ascii_alphanumeric()) + } + + #[cfg(test)] + mod tests { + use super::*; + + #[rstest::rstest] + #[case::sentence_json("sentence_000.json", true)] + #[case::wrong_prefix("document_000.json", false)] + #[case::wrong_extension("sentence_000.txt", false)] + fn classifies_sentence_corpus_shard_names(#[case] name: &str, #[case] expected: bool) { + assert_eq!(is_sentence_corpus_shard_name(name), expected); + } + + #[rstest::rstest] + #[case::start_of_input("A(14)", 0, false)] + #[case::ascii_letter("BA(14)", 1, true)] + #[case::ascii_digit("1A(14)", 1, true)] + #[case::korean_scalar("가A(14)", 3, false)] + #[case::non_scalar_boundary("가A(14)", 1, false)] + fn detects_ascii_alphanumeric_immediately_before_boundary( + #[case] input: &str, + #[case] byte_index: usize, + #[case] expected: bool, + ) { + assert_eq!(has_ascii_alphanumeric_before(input, byte_index), expected); + } + } +} + mod char_shortcut; pub(crate) mod char_struct; #[cfg(feature = "cli")] diff --git a/libs/braillify/src/rules/english_ueb/engine.rs b/libs/braillify/src/rules/english_ueb/engine.rs index 0e889085..f380515f 100644 --- a/libs/braillify/src/rules/english_ueb/engine.rs +++ b/libs/braillify/src/rules/english_ueb/engine.rs @@ -869,17 +869,4 @@ mod test_support { }) .collect() } - - /// Korean rule 37 suppresses the complete lower wordsign `in` at a Roman - /// entry while retaining ordinary alphabet signs through the shared engine. - #[test] - fn korean_word_entry_spells_complete_lower_wordsign() { - let chars = std::hint::black_box("in").chars().collect::>(); - - let actual = EnglishUebEngine::new() - .encode_korean_word(&chars, false, false, false, true, false) - .expect("rule-37 Roman letters must encode"); - - assert_eq!(actual, cells("⠊⠝")); - } } diff --git a/libs/braillify/src/rules/korean/rule_28.rs b/libs/braillify/src/rules/korean/rule_28.rs index 1e140e2b..cf0eb5dc 100644 --- a/libs/braillify/src/rules/korean/rule_28.rs +++ b/libs/braillify/src/rules/korean/rule_28.rs @@ -15,6 +15,10 @@ use crate::rules::english_ueb::korean_context::KoreanPrefixInput; use crate::rules::english_ueb::span::{encode_korean_unit, encode_korean_word}; use crate::rules::traits::{BrailleRule, Phase, RuleResult}; +fn is_nonempty_ascii_word(word: &str) -> bool { + !word.is_empty() && word.chars().all(|ch| ch.is_ascii_alphabetic()) +} + pub static META: RuleMeta = RuleMeta { section: "28", subsection: None, @@ -126,11 +130,11 @@ impl BrailleRule for Rule28 { let is_whole_lowercase_word = ctx.index == 0 && run_end == ctx.word_chars.len() && run.iter().all(|ch| ch.is_ascii_lowercase()); - let prev_is_ascii_word = !ctx.prev_word.is_empty() - && ctx.prev_word.chars().all(|ch| ch.is_ascii_alphabetic()); - let next_is_ascii_word = ctx.remaining_words.first().is_some_and(|word| { - !word.is_empty() && word.chars().all(|ch| ch.is_ascii_alphabetic()) - }); + let prev_is_ascii_word = is_nonempty_ascii_word(ctx.prev_word); + let next_is_ascii_word = match ctx.remaining_words.first() { + Some(word) => is_nonempty_ascii_word(word), + None => false, + }; // Rule 37's PDF example, "그는 Can you help me?라고 도움을 요청했다.", // suppresses a whole-word sign for the first Roman word (`Can`) but retains // the UEB wordsign for the phrase-interior `you`. The adjacent-ASCII-word @@ -243,7 +247,16 @@ mod tests { use super::*; use crate::rules::context::EncodingMode; use crate::unicode::decode_unicode; - use crate::{EncodeOptions, encode_with_options}; + use crate::{EncodeOptions, encode_to_unicode, encode_with_options}; + + #[rstest::rstest] + #[case::empty("", false)] + #[case::ascii("help", true)] + #[case::korean("도움", false)] + #[case::punctuated("me?", false)] + fn classifies_adjacent_ascii_words(#[case] word: &str, #[case] expected: bool) { + assert_eq!(is_nonempty_ascii_word(word), expected); + } /// 제28항 — 영문자 점역. 소문자/대문자 모두 동일 점형으로 인코딩. #[rstest::rstest] @@ -294,6 +307,16 @@ mod tests { assert_eq!(encode_with_options(input, &options).unwrap(), expected); } + /// 제37항 PDF 문장 전체를 공개 encoder로 통과시켜, 첫 Roman 어절의 + /// complete wordsign 억제와 뒤따르는 Roman phrase 경로를 함께 검증한다. + #[test] + fn rule_37_official_sentence_uses_shared_roman_engine() { + assert_eq!( + encode_to_unicode("그는 Can you help me?라고 도움을 요청했다.").unwrap(), + "⠈⠪⠉⠵⠀⠴⠠⠉⠁⠝⠀⠽⠀⠓⠑⠇⠏⠀⠍⠑⠦⠐⠣⠈⠥⠀⠊⠥⠍⠢⠮⠀⠬⠰⠻⠚⠗⠌⠊⠲" + ); + } + /// UEB 5.7.2/5.8.1/10.9.7 complete-shortform handling through the complete /// Korean encoder. Every Roman surface comes directly from the PDF examples /// (`CD`, `ALT`, `NEC`); the Korean wrapper exercises only rule 28/29/34 routing. diff --git a/libs/braillify/src/rules/korean/rule_69.rs b/libs/braillify/src/rules/korean/rule_69.rs index a82928a0..d28aa2b3 100644 --- a/libs/braillify/src/rules/korean/rule_69.rs +++ b/libs/braillify/src/rules/korean/rule_69.rs @@ -90,12 +90,13 @@ fn compatibility_unit_decomposition(c: char) -> Option> { /// and a lower groupsign cannot consume the whole entry run (`in` is spelled /// `i`-`n`, while the same `in` may contract inside `min`). fn encode_rule_69_unit_letters(letters: &[char]) -> Result, String> { - encode_korean_word(letters, false, false, false, true, false).ok_or_else(|| { - format!( + match encode_korean_word(letters, false, false, false, true, false) { + Some(encoded) => Ok(encoded), + None => Err(format!( "cannot encode rule 69 Roman unit letters: {}", letters.iter().collect::() - ) - }) + )), + } } fn encode_compatibility_unit( @@ -644,10 +645,24 @@ mod tests { /// The compatibility-unit grammar sends only ASCII-letter runs here. /// Rejecting a numeric component directly keeps that defensive contract /// observable without weakening the accepted Rule 68/69 glyph set. - #[test] - fn unit_letter_encoder_rejects_non_letter_component() { - let error = encode_rule_69_unit_letters(&[std::hint::black_box('1')]).unwrap_err(); - assert!(error.contains("cannot encode rule 69 Roman unit letters: 1")); + #[rstest::rstest] + #[case::single_letter("m", true)] + #[case::multi_letter_unit("min", true)] + #[case::non_letter_component("1", false)] + fn unit_letter_encoder_accepts_only_roman_letter_runs( + #[case] input: &str, + #[case] expected_ok: bool, + ) { + let letters = input.chars().collect::>(); + let result = encode_rule_69_unit_letters(&letters); + + assert_eq!(result.is_ok(), expected_ok); + if !expected_ok { + assert_eq!( + result.unwrap_err(), + "cannot encode rule 69 Roman unit letters: 1" + ); + } } #[test] diff --git a/libs/braillify/src/rules/korean/rule_71.rs b/libs/braillify/src/rules/korean/rule_71.rs index b2b56a6d..652fbc57 100644 --- a/libs/braillify/src/rules/korean/rule_71.rs +++ b/libs/braillify/src/rules/korean/rule_71.rs @@ -167,27 +167,21 @@ mod tests { let _ = Rule71.matches(&ctx); } - /// 제71항 — § 정보 기호가 직후 숫자를 만나면 종료표(⠲) 생략 (line 84-86). - #[test] - fn rule71_section_sign_before_digit_omits_terminator() { - // 제71항 붙임 PDF example `헌법§1①`: Korean context activates the - // information-symbol wrapper, while the following digit omits ⠲. - let mut owned = crate::test_helpers::CtxOwned::for_text("헌법§1①", false); - let mut ctx = owned.ctx_at(2); + /// 제71항 붙임 `헌법§1①` covers the digit continuation that omits ⠲; + /// the end/non-digit controls cover the ordinary wrapped terminator branch. + #[rstest::rstest] + #[case::official_digit_continuation("헌법§1①", "⠴⠘⠎")] + #[case::word_end("헌법§", "⠴⠘⠎⠲")] + #[case::non_digit_continuation("헌법§A", "⠴⠘⠎⠲")] + fn section_sign_wrapper_terminator_boundary(#[case] input: &str, #[case] expected: &str) { + let section_index = input.chars().position(|ch| ch == '§').unwrap(); + let mut owned = crate::test_helpers::CtxOwned::for_text(input, false); + let mut ctx = owned.ctx_at(section_index); + let outcome = Rule71.apply(&mut ctx).unwrap(); assert!(matches!(outcome, RuleResult::Consumed)); - assert_eq!(ctx.result.as_slice(), encode_unicode_cells("⠴⠘⠎")); - } - - /// rule_71:85 — § followed by NON-digit (or end of input) appends ⠲ terminator. - #[test] - fn rule71_section_symbol_followed_by_non_digit_appends_terminator() { - // Encode "§A" — next char is letter, not digit → ⠲ appended at line 85. - let result = crate::encode("§A"); - assert!(result.is_ok()); - // Also: § alone (no next char) → no digit → ⠲ appended. - let _ = crate::encode("§"); + assert_eq!(ctx.result.as_slice(), encode_unicode_cells(expected)); } /// The Korean Rule 71 encoder owns the ampersand cells even while the diff --git a/libs/braillify/src/rules/token_rules/math_expression/apply.rs b/libs/braillify/src/rules/token_rules/math_expression/apply.rs index 497c064c..d85eabc3 100644 --- a/libs/braillify/src/rules/token_rules/math_expression/apply.rs +++ b/libs/braillify/src/rules/token_rules/math_expression/apply.rs @@ -909,6 +909,9 @@ mod tests { #[case::function_with_spaced_argument("f(x y)", false)] #[case::missing_opening_parenthesis("Romeo Juliet)", false)] #[case::invalid_trailing_digit("(Romeo Juliet)1", false)] + #[case::digit_in_final_fragment("(Romeo Juliet2)", false)] + #[case::digit_after_opening("(2Romeo Juliet)", false)] + #[case::digit_in_earlier_fragment("(Romeo2 Juliet)", false)] fn recognizes_only_complete_multiword_roman_parenthetical_tails( #[case] input: &str, #[case] expected: bool, @@ -927,6 +930,14 @@ mod tests { is_multiword_closed_roman_parenthetical_tail(&ir.tokens, index, word), expected ); + + if expected { + let mut state = EncoderState::new(false); + assert!(matches!( + run(&ir.tokens, index, &mut state).unwrap(), + TokenAction::Noop + )); + } } /// Decimal-context spacing recognizes each structural marker independently: @@ -969,6 +980,19 @@ mod tests { Token::Space(SpaceKind::Regular) } + /// The complete token-rule path must preserve both defensive boundaries: + /// an unsupported mixed-math glyph falls through, and a leading space with + /// no preceding math token is not treated as mixed-math continuation. + #[test] + fn unsupported_mixed_expression_after_leading_space_falls_through() { + let tokens = vec![space_tok(), word_tok("√분산🚀")]; + let mut state = EncoderState::new(false); + + let action = run(&tokens, 1, &mut state).unwrap(); + + assert!(matches!(action, TokenAction::Noop)); + } + // ---------- Direct tests on extracted helpers ---------- /// `prev_next_words` returns (None, None) for an out-of-range index. From 60c01b9d2ae76c091ea6c7e5f85bac85ac30a470 Mon Sep 17 00:00:00 2001 From: devfive Date: Tue, 1 Sep 2026 21:58:24 +0900 Subject: [PATCH 65/77] Cover corpus analysis and rule boundaries --- libs/braillify/examples/nikl_corpus_analyze.rs | 15 ++++++++++++--- libs/braillify/src/rules/english_ueb/engine.rs | 13 +++++++++++++ libs/braillify/src/rules/korean/rule_28.rs | 16 ++++++++++++++++ libs/braillify/src/rules/korean/rule_69.rs | 7 +++++++ libs/braillify/src/rules/korean/rule_71.rs | 15 +++++++++++++++ 5 files changed, 63 insertions(+), 3 deletions(-) diff --git a/libs/braillify/examples/nikl_corpus_analyze.rs b/libs/braillify/examples/nikl_corpus_analyze.rs index 1a25015b..cb7bca40 100644 --- a/libs/braillify/examples/nikl_corpus_analyze.rs +++ b/libs/braillify/examples/nikl_corpus_analyze.rs @@ -316,7 +316,7 @@ fn validate_corpus_shape(shard_count: usize, case_count: usize) -> Result<(), St fn singleton_unsupported_set_with( cases: &[LocatedCase], - mut fails_alone: impl FnMut(char) -> bool, + fails_alone: &mut dyn FnMut(char) -> bool, ) -> BTreeSet { cases .iter() @@ -328,7 +328,7 @@ fn singleton_unsupported_set_with( } fn singleton_unsupported_set(cases: &[LocatedCase]) -> BTreeSet { - singleton_unsupported_set_with(cases, |ch| { + singleton_unsupported_set_with(cases, &mut |ch| { braillify::encode_to_unicode(&ch.to_string()).is_err() }) } @@ -7099,7 +7099,7 @@ mod tests { .collect::>(); let mut calls = BTreeMap::::new(); - let unsupported = singleton_unsupported_set_with(&cases, |ch| { + let unsupported = singleton_unsupported_set_with(&cases, &mut |ch| { *calls.entry(ch).or_insert(0) += 1; matches!(ch, '㈜' | 'ℓ') }); @@ -7109,6 +7109,15 @@ mod tests { assert!(calls.values().all(|count| *count == 1)); } + #[test] + fn serializes_report_enum_keys_as_snake_case_strings() { + assert_eq!(enum_key(&PrimaryClass::Exact), "exact"); + assert_eq!( + enum_key(&Reason::UnsupportedCharacterReview), + "unsupported_character_review" + ); + } + #[rstest::rstest] #[case::compatibility_unit('㎏', "compatibility_unit_symbol")] #[case::roman_numeral('Ⅱ', "roman_numeral_presentation")] diff --git a/libs/braillify/src/rules/english_ueb/engine.rs b/libs/braillify/src/rules/english_ueb/engine.rs index f380515f..f2a0c139 100644 --- a/libs/braillify/src/rules/english_ueb/engine.rs +++ b/libs/braillify/src/rules/english_ueb/engine.rs @@ -854,6 +854,19 @@ impl EnglishUebEngine { mod test_support { use super::*; + /// UEB 10.5.1's official `BE ALL THAT YOU CAN BE` supplies the all-capital + /// `CAN` surface. In Korean rule-37 mode the wordsign is spelled, while its + /// capitals-word extent must still be preserved by the shared UEB engine. + #[test] + fn korean_non_standalone_all_caps_wordsign_uses_capitals_word_indicator() { + let chars = "CAN".chars().collect::>(); + let encoded = EnglishUebEngine::new() + .encode_korean_word(&chars, false, false, false, true, false) + .expect("ASCII Roman word must encode"); + + assert!(encoded.starts_with(&[CAPITAL, CAPITAL])); + } + pub(super) fn enc(text: &str) -> Option> { super::super::try_encode(text) } diff --git a/libs/braillify/src/rules/korean/rule_28.rs b/libs/braillify/src/rules/korean/rule_28.rs index cf0eb5dc..414c5284 100644 --- a/libs/braillify/src/rules/korean/rule_28.rs +++ b/libs/braillify/src/rules/korean/rule_28.rs @@ -424,6 +424,22 @@ mod tests { let _ = outcome; } + /// A rule invocation that resumes inside an ASCII run must restart a + /// capitals indicator and stop its extent at the following lowercase + /// letter. Normal full-word routing skips over this position in one pass; + /// this direct check preserves the defensive continuation behavior. + #[test] + fn uppercase_continuation_stops_before_following_lowercase_letter() { + let mut owned = crate::test_helpers::CtxOwned::for_text("aBc", false); + owned.state.is_english = true; + let mut ctx = owned.ctx_at(1); + + let outcome = Rule28.apply(&mut ctx).unwrap(); + + assert!(matches!(outcome, RuleResult::Consumed)); + assert_eq!(owned.result.first(), Some(&UPPERCASE_SINGLE)); + } + /// rule_28 line 64 — `let-else return Skip` for non-English ctx. #[test] fn rule28_apply_skip_for_non_english_ctx() { diff --git a/libs/braillify/src/rules/korean/rule_69.rs b/libs/braillify/src/rules/korean/rule_69.rs index d28aa2b3..602e12cf 100644 --- a/libs/braillify/src/rules/korean/rule_69.rs +++ b/libs/braillify/src/rules/korean/rule_69.rs @@ -642,6 +642,13 @@ mod tests { } } + #[test] + fn compatibility_unit_encoder_rejects_components_outside_its_grammar() { + let error = encode_compatibility_unit(&['?'], true, true).unwrap_err(); + + assert_eq!(error, "unsupported compatibility unit component: U+003F"); + } + /// The compatibility-unit grammar sends only ASCII-letter runs here. /// Rejecting a numeric component directly keeps that defensive contract /// observable without weakening the accepted Rule 68/69 glyph set. diff --git a/libs/braillify/src/rules/korean/rule_71.rs b/libs/braillify/src/rules/korean/rule_71.rs index 652fbc57..b09dcbea 100644 --- a/libs/braillify/src/rules/korean/rule_71.rs +++ b/libs/braillify/src/rules/korean/rule_71.rs @@ -216,6 +216,21 @@ mod tests { assert!(ctx.state.is_english); } + #[test] + fn attached_ampersand_resumes_indicator_free_english_dominant_context() { + let mut owned = crate::test_helpers::CtxOwned::for_text("&c", true); + owned.state.english_dominant_no_indicator = true; + let mut ctx = owned.ctx_at(0); + + let outcome = Rule71.apply(&mut ctx).unwrap(); + + assert!(matches!(outcome, RuleResult::Consumed)); + assert_eq!(ctx.result.as_slice(), encode_unicode_cells("⠈⠯")); + assert!(ctx.state.is_english); + assert!(!ctx.state.needs_english_continuation); + assert!(!ctx.state.roman_number_chain); + } + /// Full-encoder controls reproduce the two UEB 3.1.1 examples exactly. #[rstest::rstest] #[case::official_at_and_t("AT&T", "⠠⠠⠁⠞⠈⠯⠠⠞")] From 5af825062ac1703abda5cebb6b3a122bfb327024 Mon Sep 17 00:00:00 2001 From: devfive Date: Tue, 1 Sep 2026 22:12:52 +0900 Subject: [PATCH 66/77] Cover final workspace branches --- .../braillify/examples/nikl_corpus_analyze.rs | 40 +++++++++++++++---- .../braillify/src/rules/english_ueb/engine.rs | 23 +++++++---- .../token_rules/math_expression/apply.rs | 1 + .../token_rules/math_expression/helpers.rs | 15 +++++++ 4 files changed, 64 insertions(+), 15 deletions(-) diff --git a/libs/braillify/examples/nikl_corpus_analyze.rs b/libs/braillify/examples/nikl_corpus_analyze.rs index cb7bca40..b30cc432 100644 --- a/libs/braillify/examples/nikl_corpus_analyze.rs +++ b/libs/braillify/examples/nikl_corpus_analyze.rs @@ -3419,14 +3419,16 @@ fn attached_korean_auxiliary_itda_actual_ranges( actual: &str, ) -> Vec> { let actual_cells = actual.chars().collect::>(); - attached_korean_auxiliary_itda_spans(input) - .into_iter() - .filter_map(|span| { - let prefix = braillify::encode_to_unicode(&input[..span.start_byte]).ok()?; - let start = prefix.chars().count(); - (actual_cells.get(start) == Some(&'⠀')).then_some(start..start + 1) - }) - .collect() + let mut ranges = Vec::new(); + for span in attached_korean_auxiliary_itda_spans(input) { + let prefix = braillify::encode_to_unicode(&input[..span.start_byte]) + .expect("an encodable sentence must have an encodable prefix"); + let start = prefix.chars().count(); + if actual_cells.get(start) == Some(&'⠀') { + ranges.push(start..start + 1); + } + } + ranges } fn first_difference_at_attached_korean_auxiliary_itda_spacing(item: &EncodedCase) -> bool { @@ -8417,6 +8419,28 @@ mod tests { assert!(ranges.is_empty()); } + #[test] + fn localizes_a_blank_inserted_before_attached_itda() { + let input = "성장을 하고있다."; + let span = attached_korean_auxiliary_itda_spans(input) + .into_iter() + .next() + .expect("probe must contain the attached suffix"); + let prefix = braillify::encode_to_unicode(&input[..span.start_byte]).unwrap(); + let start = prefix.chars().count(); + let mut actual = braillify::encode_to_unicode(input).unwrap(); + let insert_at = actual + .char_indices() + .nth(start) + .map_or(actual.len(), |(byte, _)| byte); + actual.insert(insert_at, '⠀'); + + assert_eq!( + attached_korean_auxiliary_itda_actual_ranges(input, &actual), + vec![start..start + 1] + ); + } + #[test] fn aggregates_headword_expansion_outcomes_without_reclassification() { let cases = [ diff --git a/libs/braillify/src/rules/english_ueb/engine.rs b/libs/braillify/src/rules/english_ueb/engine.rs index f2a0c139..c19f1b3c 100644 --- a/libs/braillify/src/rules/english_ueb/engine.rs +++ b/libs/braillify/src/rules/english_ueb/engine.rs @@ -854,17 +854,26 @@ impl EnglishUebEngine { mod test_support { use super::*; - /// UEB 10.5.1's official `BE ALL THAT YOU CAN BE` supplies the all-capital - /// `CAN` surface. In Korean rule-37 mode the wordsign is spelled, while its - /// capitals-word extent must still be preserved by the shared UEB engine. - #[test] - fn korean_non_standalone_all_caps_wordsign_uses_capitals_word_indicator() { - let chars = "CAN".chars().collect::>(); + /// Korean rule 37's official `Can you ...` and UEB 10.5.1's official + /// `... YOU CAN ...` exercise every capitals classification while Korean + /// mode spells the wordsign through the shared UEB engine. + #[rstest::rstest] + #[case::lowercase("you", 0)] + #[case::initial_capital("Can", 1)] + #[case::capitals_word("CAN", 2)] + fn korean_non_standalone_wordsign_preserves_capitals_extent( + #[case] input: &str, + #[case] expected_capitals: usize, + ) { + let chars = input.chars().collect::>(); let encoded = EnglishUebEngine::new() .encode_korean_word(&chars, false, false, false, true, false) .expect("ASCII Roman word must encode"); - assert!(encoded.starts_with(&[CAPITAL, CAPITAL])); + assert_eq!( + encoded.iter().take_while(|cell| **cell == CAPITAL).count(), + expected_capitals + ); } pub(super) fn enc(text: &str) -> Option> { diff --git a/libs/braillify/src/rules/token_rules/math_expression/apply.rs b/libs/braillify/src/rules/token_rules/math_expression/apply.rs index d85eabc3..19a683b0 100644 --- a/libs/braillify/src/rules/token_rules/math_expression/apply.rs +++ b/libs/braillify/src/rules/token_rules/math_expression/apply.rs @@ -907,6 +907,7 @@ mod tests { #[case::operator_interrupts_prose_run("(x + y)", false)] #[case::no_closing_parenthesis("Romeo Juliet", false)] #[case::function_with_spaced_argument("f(x y)", false)] + #[case::ueb_multiword_parenthetical("plays (such as Romeo and Juliet)", true)] #[case::missing_opening_parenthesis("Romeo Juliet)", false)] #[case::invalid_trailing_digit("(Romeo Juliet)1", false)] #[case::digit_in_final_fragment("(Romeo Juliet2)", false)] diff --git a/libs/braillify/src/rules/token_rules/math_expression/helpers.rs b/libs/braillify/src/rules/token_rules/math_expression/helpers.rs index ff9bb20d..69106c5b 100644 --- a/libs/braillify/src/rules/token_rules/math_expression/helpers.rs +++ b/libs/braillify/src/rules/token_rules/math_expression/helpers.rs @@ -442,6 +442,21 @@ pub(super) fn split_mixed_math_word( #[cfg(test)] mod tests { use super::*; + + #[rstest::rstest] + #[case::official_rule_34_annotation("(Lincoln)", true)] + #[case::missing_opening("Lincoln)", false)] + #[case::missing_closing("(Lincoln", false)] + #[case::empty_body("()", false)] + fn recognizes_only_closed_roman_annotation_suffixes( + #[case] input: &str, + #[case] expected: bool, + ) { + assert_eq!( + is_closed_roman_annotation_suffix(&input.chars().collect::>()), + expected + ); + } use crate::rules::math::math_token_rule::MathContext; use crate::rules::token::SpaceKind; From 962f392c773991f21fecd1b5e2621ba5f67ff9a7 Mon Sep 17 00:00:00 2001 From: devfive Date: Tue, 1 Sep 2026 22:26:13 +0900 Subject: [PATCH 67/77] Make coverage branches explicit --- .../braillify/examples/nikl_corpus_analyze.rs | 30 +++++++++++++++++++ .../braillify/src/rules/english_ueb/engine.rs | 5 +++- .../token_rules/math_expression/apply.rs | 19 +++++++----- 3 files changed, 45 insertions(+), 9 deletions(-) diff --git a/libs/braillify/examples/nikl_corpus_analyze.rs b/libs/braillify/examples/nikl_corpus_analyze.rs index b30cc432..cad243c0 100644 --- a/libs/braillify/examples/nikl_corpus_analyze.rs +++ b/libs/braillify/examples/nikl_corpus_analyze.rs @@ -8419,6 +8419,36 @@ mod tests { assert!(ranges.is_empty()); } + #[test] + fn skips_attached_roman_boundary_outcomes_when_encoding_failed() { + let item = EncodedCase { + located: LocatedCase { + shard: "synthetic.json".to_string(), + index: 1, + case: CorpusCase { + input: "AI기술".to_string(), + unicode: String::new(), + }, + }, + actual: Err("synthetic encoding failure".to_string()), + nfc_actual: None, + nfkc_actual: None, + singleton_unsupported_characters: Vec::new(), + }; + let mut stats = PendingRuleReviewClusterStats::default(); + + record_attached_ascii_roman_to_korean_marker_outcomes( + &mut stats, + &item, + "encoded_mismatch_pending_rule_review", + "pending_rule_review", + 10, + ); + + assert!(stats.actual_output_signature_outcomes.is_empty()); + assert!(stats.samples.is_empty()); + } + #[test] fn localizes_a_blank_inserted_before_attached_itda() { let input = "성장을 하고있다."; diff --git a/libs/braillify/src/rules/english_ueb/engine.rs b/libs/braillify/src/rules/english_ueb/engine.rs index c19f1b3c..16ac64dc 100644 --- a/libs/braillify/src/rules/english_ueb/engine.rs +++ b/libs/braillify/src/rules/english_ueb/engine.rs @@ -221,7 +221,10 @@ impl EnglishUebEngine { match classify_caps(chars)? { Caps::None => {} Caps::Single => out.push(CAPITAL), - Caps::Word => out.extend([CAPITAL, CAPITAL]), + Caps::Word => { + out.push(CAPITAL); + out.push(CAPITAL); + } } } out.extend(super::rule_10_9::encode_korean_groupsigns( diff --git a/libs/braillify/src/rules/token_rules/math_expression/apply.rs b/libs/braillify/src/rules/token_rules/math_expression/apply.rs index 19a683b0..9a76ab0c 100644 --- a/libs/braillify/src/rules/token_rules/math_expression/apply.rs +++ b/libs/braillify/src/rules/token_rules/math_expression/apply.rs @@ -182,13 +182,17 @@ fn is_multiword_closed_roman_parenthetical_tail( if let Some(open) = previous_text.rfind('(') { let before = &previous_text[..open]; let after = &previous_text[open + 1..]; - return !after.is_empty() - && after.chars().all(|ch| ch.is_ascii_alphabetic()) - && !before - .chars() - .next_back() - .is_some_and(|ch| ch.is_ascii_alphabetic()) - && !before.chars().any(|ch| matches!(ch, '(' | ')')); + if after.is_empty() || !after.chars().all(|ch| ch.is_ascii_alphabetic()) { + return false; + } + if before + .chars() + .next_back() + .is_some_and(|ch| ch.is_ascii_alphabetic()) + { + return false; + } + return !before.chars().any(|ch| matches!(ch, '(' | ')')); } if previous_text.chars().all(|ch| ch.is_ascii_alphabetic()) { cursor = i.checked_sub(1); @@ -907,7 +911,6 @@ mod tests { #[case::operator_interrupts_prose_run("(x + y)", false)] #[case::no_closing_parenthesis("Romeo Juliet", false)] #[case::function_with_spaced_argument("f(x y)", false)] - #[case::ueb_multiword_parenthetical("plays (such as Romeo and Juliet)", true)] #[case::missing_opening_parenthesis("Romeo Juliet)", false)] #[case::invalid_trailing_digit("(Romeo Juliet)1", false)] #[case::digit_in_final_fragment("(Romeo Juliet2)", false)] From 1cefe7599165e12b21f69c4360fe3aa5960e935c Mon Sep 17 00:00:00 2001 From: devfive Date: Tue, 1 Sep 2026 22:39:47 +0900 Subject: [PATCH 68/77] Remove coverage attribution artifacts --- .../braillify/examples/nikl_corpus_analyze.rs | 39 +++---------------- .../braillify/src/rules/english_ueb/engine.rs | 12 ++---- .../token_rules/math_expression/apply.rs | 2 +- 3 files changed, 10 insertions(+), 43 deletions(-) diff --git a/libs/braillify/examples/nikl_corpus_analyze.rs b/libs/braillify/examples/nikl_corpus_analyze.rs index cad243c0..39ceb190 100644 --- a/libs/braillify/examples/nikl_corpus_analyze.rs +++ b/libs/braillify/examples/nikl_corpus_analyze.rs @@ -3184,13 +3184,11 @@ fn korean_majority_roman_sandwich_non_domain_spans(input: &str) -> Vec>(); let outcome = if primary_key == "exact" { @@ -4278,10 +4276,13 @@ fn analyze( localized_first_difference, localized_samples, ); - if cluster == ATTACHED_ASCII_ROMAN_TO_KOREAN_BOUNDARY { + if cluster == ATTACHED_ASCII_ROMAN_TO_KOREAN_BOUNDARY + && let Ok(actual) = &item.actual + { record_attached_ascii_roman_to_korean_marker_outcomes( stats, item, + actual, &primary_key, &reason_key, sample_limit, @@ -8419,36 +8420,6 @@ mod tests { assert!(ranges.is_empty()); } - #[test] - fn skips_attached_roman_boundary_outcomes_when_encoding_failed() { - let item = EncodedCase { - located: LocatedCase { - shard: "synthetic.json".to_string(), - index: 1, - case: CorpusCase { - input: "AI기술".to_string(), - unicode: String::new(), - }, - }, - actual: Err("synthetic encoding failure".to_string()), - nfc_actual: None, - nfkc_actual: None, - singleton_unsupported_characters: Vec::new(), - }; - let mut stats = PendingRuleReviewClusterStats::default(); - - record_attached_ascii_roman_to_korean_marker_outcomes( - &mut stats, - &item, - "encoded_mismatch_pending_rule_review", - "pending_rule_review", - 10, - ); - - assert!(stats.actual_output_signature_outcomes.is_empty()); - assert!(stats.samples.is_empty()); - } - #[test] fn localizes_a_blank_inserted_before_attached_itda() { let input = "성장을 하고있다."; diff --git a/libs/braillify/src/rules/english_ueb/engine.rs b/libs/braillify/src/rules/english_ueb/engine.rs index 16ac64dc..3b5ee3da 100644 --- a/libs/braillify/src/rules/english_ueb/engine.rs +++ b/libs/braillify/src/rules/english_ueb/engine.rs @@ -218,14 +218,10 @@ impl EnglishUebEngine { let lower_word: String = lower.iter().collect(); if !standing_alone && super::rule_10_5::wordsign(&lower_word).is_some() { if !suppress_caps { - match classify_caps(chars)? { - Caps::None => {} - Caps::Single => out.push(CAPITAL), - Caps::Word => { - out.push(CAPITAL); - out.push(CAPITAL); - } - } + let caps = classify_caps(chars)?; + let indicator_count = + usize::from(caps != Caps::None) + usize::from(caps == Caps::Word); + out.extend(std::iter::repeat_n(CAPITAL, indicator_count)); } out.extend(super::rule_10_9::encode_korean_groupsigns( &lower, diff --git a/libs/braillify/src/rules/token_rules/math_expression/apply.rs b/libs/braillify/src/rules/token_rules/math_expression/apply.rs index 9a76ab0c..ce03ec3d 100644 --- a/libs/braillify/src/rules/token_rules/math_expression/apply.rs +++ b/libs/braillify/src/rules/token_rules/math_expression/apply.rs @@ -192,7 +192,7 @@ fn is_multiword_closed_roman_parenthetical_tail( { return false; } - return !before.chars().any(|ch| matches!(ch, '(' | ')')); + return !before.contains('(') && !before.contains(')'); } if previous_text.chars().all(|ch| ch.is_ascii_alphabetic()) { cursor = i.checked_sub(1); From 833aabb17bce5ab739b36df35e8278103c10d523 Mon Sep 17 00:00:00 2001 From: devfive Date: Tue, 1 Sep 2026 22:50:52 +0900 Subject: [PATCH 69/77] Cover remaining analyzer boundaries --- libs/braillify/examples/nikl_corpus_analyze.rs | 3 +-- libs/braillify/src/rules/token_rules/math_expression/apply.rs | 1 + 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/libs/braillify/examples/nikl_corpus_analyze.rs b/libs/braillify/examples/nikl_corpus_analyze.rs index 39ceb190..9b225587 100644 --- a/libs/braillify/examples/nikl_corpus_analyze.rs +++ b/libs/braillify/examples/nikl_corpus_analyze.rs @@ -3438,8 +3438,7 @@ fn first_difference_at_attached_korean_auxiliary_itda_spacing(item: &EncodedCase } let first_difference = first_difference_cell(&item.located.case.unicode, actual); attached_korean_auxiliary_itda_actual_ranges(&item.located.case.input, actual) - .into_iter() - .any(|range| range.contains(&first_difference)) + .contains(&(first_difference..first_difference + 1)) } fn enum_key(value: &T) -> String { diff --git a/libs/braillify/src/rules/token_rules/math_expression/apply.rs b/libs/braillify/src/rules/token_rules/math_expression/apply.rs index ce03ec3d..fffce692 100644 --- a/libs/braillify/src/rules/token_rules/math_expression/apply.rs +++ b/libs/braillify/src/rules/token_rules/math_expression/apply.rs @@ -916,6 +916,7 @@ mod tests { #[case::digit_in_final_fragment("(Romeo Juliet2)", false)] #[case::digit_after_opening("(2Romeo Juliet)", false)] #[case::digit_in_earlier_fragment("(Romeo2 Juliet)", false)] + #[case::nonletter_earlier_without_opening("Romeo2 Juliet More)", false)] fn recognizes_only_complete_multiword_roman_parenthetical_tails( #[case] input: &str, #[case] expected: bool, From 249d232281cf51f3780b488a2555844d5d10a5e0 Mon Sep 17 00:00:00 2001 From: devfive Date: Tue, 1 Sep 2026 23:08:18 +0900 Subject: [PATCH 70/77] Remove dead corpus output localizer --- docs/corpus-analysis/NIKL_2025_V1.md | 6 +- .../braillify/examples/nikl_corpus_analyze.rs | 76 ++----------------- .../token_rules/math_expression/apply.rs | 10 ++- 3 files changed, 16 insertions(+), 76 deletions(-) diff --git a/docs/corpus-analysis/NIKL_2025_V1.md b/docs/corpus-analysis/NIKL_2025_V1.md index 978259a0..f5229e26 100644 --- a/docs/corpus-analysis/NIKL_2025_V1.md +++ b/docs/corpus-analysis/NIKL_2025_V1.md @@ -551,7 +551,7 @@ This ranking removes only cases whose first difference is inside an existing out ## Cross-cutting input-only structural cohorts -These are cross-cutting input-only structural cohorts, not new primary classes and not engine routing rules. Candidate selection never changes a case's existing primary class by itself. Only cohort members already classified as `pending_rule_review` form a pending subcluster; exact and other-primary members are controls that retain their existing outcomes. A separate classifier may use independently justified, output-localized PDF evidence, as in the rule-34 three-cell contradiction below. The `uppercase_roman_headword_closed_multiword_parenthetical` gate requires a two-or-more character uppercase ASCII headword immediately followed by a closed parenthesis whose contents are two or more ASCII Roman words separated only by spaces. Because the contents admit only letters and spaces, visible operators, subscript/superscript notation, and nested parentheses are excluded deterministically. The `mixed_roman_korean_word_before_uppercase_headword_expansion` gate further requires a whitespace-delimited preceding word that contains both ASCII Roman and Korean and ends in Korean. It locates the following uppercase headword itself, including its immediately preceding emitted cell, so an earlier Roman entry in the same sentence cannot satisfy the output audit. The `single_capital_followed_by_parenthesized_digits` gate requires a standalone capital, a non-empty closed ASCII-digit parenthetical, and alphanumeric outer boundaries. It likewise includes the emitted entry-boundary cell in localization. The `uppercase_roman_run_followed_by_hyphen_digits` gate requires a maximal uppercase ASCII run, literal ASCII hyphen-minus, a non-empty digit run, and alphanumeric outer boundaries. Its localized range includes the current encoded run and its immediately preceding cell, keeping `F-35`-style routing separate from both parenthesized digits and headword expansions. The `uppercase_alphanumeric_roman_digit_sequence` gate covers the wider rule-35/math collision: a capital-led uppercase/digit sequence with optional internal ASCII hyphen/full stop and trailing prose comma/colon/semicolon. It localizes only the current entry cells and does not decide whether an `A-3`-shaped surface is an identifier or a mathematical expression. The `standalone_multi_character_uppercase_roman_word` gate finds maximal ASCII-letter runs of two or more capitals with non-alphanumeric boundaries; a run immediately followed by `(` is excluded so the HCA-style headword itself is not counted by both gates. The `korean_prefixed_closed_allcaps_parenthetical` gate requires an immediately preceding Korean character and a closed body of two or more uppercase ASCII letters. It intentionally contains both acronym annotations (`책임자(COO)`) and scientific formulae (`일산화탄소(CO)`) so their semantic collision remains measurable. The `korean_prefixed_closed_roman_annotation_rule_34_order` gate accepts the narrower rule-34 body grammar after an immediately preceding Korean character and localizes only the current engine's Korean opening-parenthesis cells. This distinguishes the PDF's parenthesis-before-Roman-indicator order from unrelated differences later in the same sentence. The `multi_character_allcaps_roman_runs_joined_by_middle_dot` gate requires two maximal ASCII-letter runs of at least two capitals joined directly by U+00B7, with non-alphanumeric outer boundaries. It records shapes such as `AI·SW` without assigning prose, mathematics, or science semantics. The `roman_run_immediately_before_attached_middle_dot_boundary` gate is output-localized: it requires a maximal ASCII-letter run immediately before U+00B7 and an attached Korean character or ASCII-letter run after it, then searches for that whole current-engine signature in the actual output. It therefore isolates the Roman terminator boundary without treating unrelated middle dots elsewhere in the sentence as causal. The `attached_ascii_roman_to_korean_script_boundary` gate requires an ASCII letter run immediately followed by Korean script and localizes only the current mode marker there: either rule 29's final `⠲` or rule 39's opening `⠸⠷`. It deliberately does not infer the dominant language of the sentence from that surface boundary. The `korean_majority_same_token_roman_sandwich_non_domain` scope audit mirrors the narrower rule-39 implementation gate: the nearest script characters on both sides of one Korean segment are ASCII Roman, the input is Korean-majority by first-script word counts, and the segment is not dot-delimited like the official domain example. It measures change scope but is not itself an output-localized causal classifier. The `korean_inline_parenthesized_single_arithmetic_operator` gate requires an immediate `Korean(` + one rule-45 arithmetic operator + `)Korean` span. Unlike broad coexistence traits, it also locates the current engine's emitted structure and counts a mismatch as signature-local only when the sentence's first differing cell falls inside that output range. The `allcaps_roman_run_containing_ou` gate finds maximal, alphanumeric-delimited uppercase ASCII runs containing adjacent `OU`. It locates the independently encoded run signature in the complete current output and counts only first differences inside that signature as localized. The `allcaps_roman_run_containing_st` gate applies the same output-position requirement to adjacent `ST`; it is kept separate because UEB 10.12 makes contraction use depend on how an abbreviation or acronym is pronounced. The `uppercase_ascii_segments_joined_by_ampersand_capitalization` gate narrows attached ampersand runs to uppercase-only segments and locates the complete current Korean-context output. The run must begin its whitespace-delimited token, matching the current token-level capitals-word pre-emission scope; Korean-attached and punctuation-prefixed occurrences remain controls. UEB 8.4.2 says a nonalphabetic symbol terminates capitals word mode, while the official `AT&T` and `B&B` examples restart capitalization after `&`. The broader `capitals_word_mode_previously_spanning_nonletter_scope` gate reproduces the former token predicate across ampersands, hyphens, digits, and other nonletters that separate uppercase runs. Trailing nonletters after the final run are excluded because their output is unchanged. It is a broad change-scope/regression audit and is deliberately not an output-cause localizer. The `attached_ascii_roman_segments_joined_by_ampersand` gate requires non-empty ASCII-letter segments joined directly by `&`, excludes spaced/Korean/alphanumeric continuations, and localizes only the current output cell immediately before the ampersand through an independently encoded real-input prefix. The `ampersand_before_attached_ascii_roman_segment` gate covers the distinct one-sided UEB `&c` boundary: `&` is followed by a complete ASCII-letter segment, while an ASCII alphanumeric or another ampersand immediately before it and a digit continuation after it are excluded. Its output range is anchored by independently encoding the real input prefix before each occurrence, then includes only the current Rule-71/29 entry boundary; a second pre-fix anchor through `&` retains the former exit location. The `ascii_apostrophe_between_ascii_letter_runs` gate requires a straight apostrophe with an ASCII letter immediately on both sides and expands only across those two letter runs. It excludes detached quotation marks and numeric measurement marks, then locates the complete current mixed-Korean output signature. UEB 8.4.2 directly supplies `O'Hara`, `DON'T`, and `THAT'S` as controls. The `consecutive_ascii_roman_words_whitespace_boundary` gate requires two adjacent ASCII-letter words separated only by whitespace. For each boundary it independently encodes the real input prefix ending after the first word, then localizes only the current rule-29 terminator at that position or the full output's replacing blank. This prevents an unrelated Roman occurrence elsewhere in the sentence from satisfying the audit. Slash and other punctuation-separated forms are deliberately excluded. The `decimal_point_between_ascii_digits` gate finds whitespace-delimited words containing `digit.digit` and reproduces each whole word in a neutral Korean context, so suffixes and punctuation remain part of the current-engine signature. The `compact_numeric_ascii_letter_suffix` gate finds a numeric prefix immediately followed by ASCII letters, includes the immediately preceding output cell as its entry boundary, and retains suffix-specific outcome counts. It intentionally includes both possible rule-69 units and ambiguous variable/identifier forms; membership alone does not assign unit semantics. The `rule69_ascii_unit_before_terminator_skipping_symbol` gate is narrower: it accepts only ASCII unit spellings already supported by rule 69, includes the immediately following rule-33/34 punctuation cell in the localized signature, and does not infer new units. The `tight_triangle_mark_immediately_before_korean` gate requires literal `△한글` with no input space and includes the first following Korean cell in its localized output range, so an observed missing-space difference is measured at the mark boundary. The `attached_korean_auxiliary_itda_spacing` gate mirrors the former normalization shape for a Korean token ending in attached `있다`; its localizer independently encodes the real prefix and claims only the blank inserted before the suffix. It does not decide whether orthographic correction may override the printed input. +These are cross-cutting input-only structural cohorts, not new primary classes and not engine routing rules. Candidate selection never changes a case's existing primary class by itself. Only cohort members already classified as `pending_rule_review` form a pending subcluster; exact and other-primary members are controls that retain their existing outcomes. A separate classifier may use independently justified, output-localized PDF evidence, as in the rule-34 three-cell contradiction below. The `uppercase_roman_headword_closed_multiword_parenthetical` gate requires a two-or-more character uppercase ASCII headword immediately followed by a closed parenthesis whose contents are two or more ASCII Roman words separated only by spaces. Because the contents admit only letters and spaces, visible operators, subscript/superscript notation, and nested parentheses are excluded deterministically. The `mixed_roman_korean_word_before_uppercase_headword_expansion` gate further requires a whitespace-delimited preceding word that contains both ASCII Roman and Korean and ends in Korean. It locates the following uppercase headword itself, including its immediately preceding emitted cell, so an earlier Roman entry in the same sentence cannot satisfy the output audit. The `single_capital_followed_by_parenthesized_digits` gate requires a standalone capital, a non-empty closed ASCII-digit parenthetical, and alphanumeric outer boundaries. It likewise includes the emitted entry-boundary cell in localization. The `uppercase_roman_run_followed_by_hyphen_digits` gate requires a maximal uppercase ASCII run, literal ASCII hyphen-minus, a non-empty digit run, and alphanumeric outer boundaries. Its localized range includes the current encoded run and its immediately preceding cell, keeping `F-35`-style routing separate from both parenthesized digits and headword expansions. The `uppercase_alphanumeric_roman_digit_sequence` gate covers the wider rule-35/math collision: a capital-led uppercase/digit sequence with optional internal ASCII hyphen/full stop and trailing prose comma/colon/semicolon. It localizes only the current entry cells and does not decide whether an `A-3`-shaped surface is an identifier or a mathematical expression. The `standalone_multi_character_uppercase_roman_word` gate finds maximal ASCII-letter runs of two or more capitals with non-alphanumeric boundaries; a run immediately followed by `(` is excluded so the HCA-style headword itself is not counted by both gates. The `korean_prefixed_closed_allcaps_parenthetical` gate requires an immediately preceding Korean character and a closed body of two or more uppercase ASCII letters. It intentionally contains both acronym annotations (`책임자(COO)`) and scientific formulae (`일산화탄소(CO)`) so their semantic collision remains measurable. The `korean_prefixed_closed_roman_annotation_rule_34_order` gate accepts the narrower rule-34 body grammar after an immediately preceding Korean character and localizes only the current engine's Korean opening-parenthesis cells. This distinguishes the PDF's parenthesis-before-Roman-indicator order from unrelated differences later in the same sentence. The `multi_character_allcaps_roman_runs_joined_by_middle_dot` gate requires two maximal ASCII-letter runs of at least two capitals joined directly by U+00B7, with non-alphanumeric outer boundaries. It records shapes such as `AI·SW` without assigning prose, mathematics, or science semantics. The `roman_run_immediately_before_attached_middle_dot_boundary` gate is output-localized: it requires a maximal ASCII-letter run immediately before U+00B7 and an attached Korean character or ASCII-letter run after it, then searches for that whole current-engine signature in the actual output. It therefore isolates the Roman terminator boundary without treating unrelated middle dots elsewhere in the sentence as causal. The `attached_ascii_roman_to_korean_script_boundary` gate requires an ASCII letter run immediately followed by Korean script and localizes only the current mode marker there: either rule 29's final `⠲` or rule 39's opening `⠸⠷`. It deliberately does not infer the dominant language of the sentence from that surface boundary. The `korean_majority_same_token_roman_sandwich_non_domain` scope audit mirrors the narrower rule-39 implementation gate: the nearest script characters on both sides of one Korean segment are ASCII Roman, the input is Korean-majority by first-script word counts, and the segment is not dot-delimited like the official domain example. It measures change scope but is not itself an output-localized causal classifier. The `korean_inline_parenthesized_single_arithmetic_operator` gate requires an immediate `Korean(` + one rule-45 arithmetic operator + `)Korean` span. Unlike broad coexistence traits, it also locates the current engine's emitted structure and counts a mismatch as signature-local only when the sentence's first differing cell falls inside that output range. The `allcaps_roman_run_containing_ou` gate finds maximal, alphanumeric-delimited uppercase ASCII runs containing adjacent `OU`. It locates the independently encoded run signature in the complete current output and counts only first differences inside that signature as localized. The `allcaps_roman_run_containing_st` gate applies the same output-position requirement to adjacent `ST`; it is kept separate because UEB 10.12 makes contraction use depend on how an abbreviation or acronym is pronounced. The `uppercase_ascii_segments_joined_by_ampersand_capitalization` gate narrows attached ampersand runs to uppercase-only segments and locates the complete current Korean-context output. The run must begin its whitespace-delimited token, matching the current token-level capitals-word pre-emission scope; Korean-attached and punctuation-prefixed occurrences remain controls. UEB 8.4.2 says a nonalphabetic symbol terminates capitals word mode, while the official `AT&T` and `B&B` examples restart capitalization after `&`. The broader `capitals_word_mode_previously_spanning_nonletter_scope` gate reproduces the former token predicate across ampersands, hyphens, digits, and other nonletters that separate uppercase runs. Trailing nonletters after the final run are excluded because their output is unchanged. It is a broad change-scope/regression audit and is deliberately not an output-cause localizer. The `attached_ascii_roman_segments_joined_by_ampersand` gate requires non-empty ASCII-letter segments joined directly by `&`, excludes spaced/Korean/alphanumeric continuations, and localizes only the current output cell immediately before the ampersand through an independently encoded real-input prefix. The `ampersand_before_attached_ascii_roman_segment` gate covers the distinct one-sided UEB `&c` boundary: `&` is followed by a complete ASCII-letter segment, while an ASCII alphanumeric or another ampersand immediately before it and a digit continuation after it are excluded. Its output range is anchored by independently encoding the real input prefix before each occurrence, then includes only the current Rule-71/29 entry boundary; a second pre-fix anchor through `&` retains the former exit location. The `ascii_apostrophe_between_ascii_letter_runs` gate requires a straight apostrophe with an ASCII letter immediately on both sides and expands only across those two letter runs. It excludes detached quotation marks and numeric measurement marks, then locates the complete current mixed-Korean output signature. UEB 8.4.2 directly supplies `O'Hara`, `DON'T`, and `THAT'S` as controls. The `consecutive_ascii_roman_words_whitespace_boundary` gate requires two adjacent ASCII-letter words separated only by whitespace. For each boundary it independently encodes the real input prefix ending after the first word, then localizes only the current rule-29 terminator at that position or the full output's replacing blank. This prevents an unrelated Roman occurrence elsewhere in the sentence from satisfying the audit. Slash and other punctuation-separated forms are deliberately excluded. The `decimal_point_between_ascii_digits` gate finds whitespace-delimited words containing `digit.digit` and reproduces each whole word in a neutral Korean context, so suffixes and punctuation remain part of the current-engine signature. The `compact_numeric_ascii_letter_suffix` gate finds a numeric prefix immediately followed by ASCII letters, includes the immediately preceding output cell as its entry boundary, and retains suffix-specific outcome counts. It intentionally includes both possible rule-69 units and ambiguous variable/identifier forms; membership alone does not assign unit semantics. The `rule69_ascii_unit_before_terminator_skipping_symbol` gate is narrower: it accepts only ASCII unit spellings already supported by rule 69, includes the immediately following rule-33/34 punctuation cell in the localized signature, and does not infer new units. The `tight_triangle_mark_immediately_before_korean` gate requires literal `△한글` with no input space and includes the first following Korean cell in its localized output range, so an observed missing-space difference is measured at the mark boundary. The `attached_korean_auxiliary_itda_spacing` gate is input-only after the rule-49 correction: it measures Korean tokens ending in attached `있다` without claiming a current output signature or deciding whether orthographic correction may override the printed input. | Cluster | Candidates | Exact | Mismatch | Conflicting-reference cases | |---|---:|---:|---:|---:| @@ -1369,8 +1369,6 @@ Representative `mismatch_rule39_hangul_opening` samples: Of the 95 candidates, 19 are the actual `pending_rule_review` subcluster. The other 76 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. -For this output-signature audit, 23 mismatches were evaluable and 0 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. - Mismatch primary-class distribution: - `comparison_method`: 1 @@ -3520,7 +3518,7 @@ Korean rule 49 (2024 Korean-rules PDF p.37, printed p.31) says that spacing foll The diagnostic baseline had 95 candidates / 0 exact / 95 mismatch. All 95 were in the exact former implementation scope; 74 first differences were at the inserted blank: 73 `U+2815 ⠕ -> U+2800 ⠀`, one `U+2823 ⠣ -> U+2800 ⠀`, and no localized reverse. The absence of a baseline exact member is the in-scope regression control. -After removing that input-correcting transformation, the cohort has 95 candidates / 72 exact / 23 mismatch, preserving 19 `pending_rule_review`, 3 `corpus_suspect`, 1 `comparison_method`, and 0 `unsupported_character_review` mismatch primaries. Of 23 evaluable current mismatches, 0 still localize to an inserted blank: 0 `U+2815 ⠕ -> U+2800 ⠀`, 0 `U+2823 ⠣ -> U+2800 ⠀`, and 0 `U+2800 ⠀ -> U+2815 ⠕`. Seventy-one cases become exact; the other 24 retain independent earlier differences. Corpus exact increases by the same 71 with zero exact-set regressions. The three explicitly spaced PDF forms remain full-encoder negative controls, so removing correction of missing input whitespace does not remove a printed space. The local rule-47 standard case had accidentally transcribed PDF physical p.36 `덮여 있다` as attached `덮여있다` while retaining the PDF's spaced braille; correcting that input transcription restores the complete 5,141/5,141 standard summary without an engine exception. +After removing that input-correcting transformation, the cohort has 95 candidates / 72 exact / 23 mismatch, preserving 19 `pending_rule_review`, 3 `corpus_suspect`, 1 `comparison_method`, and 0 `unsupported_character_review` mismatch primaries. Of 0 evaluable current mismatches, 0 still localize to an inserted blank: 0 `U+2815 ⠕ -> U+2800 ⠀`, 0 `U+2823 ⠣ -> U+2800 ⠀`, and 0 `U+2800 ⠀ -> U+2815 ⠕`. Seventy-one cases become exact; the other 24 retain independent earlier differences. Corpus exact increases by the same 71 with zero exact-set regressions. The three explicitly spaced PDF forms remain full-encoder negative controls, so removing correction of missing input whitespace does not remove a printed space. The local rule-47 standard case had accidentally transcribed PDF physical p.36 `덮여 있다` as attached `덮여있다` while retaining the PDF's spaced braille; correcting that input transcription restores the complete 5,141/5,141 standard summary without an engine exception. Current uppercase-Roman hyphen-digits measurement: 571 candidates, 194 exact controls, 377 mismatches, 362 members in the actual `pending_rule_review` subcluster, and 262/377 evaluable mismatches whose first difference is inside the target run plus its entry boundary. It remains distinct from parenthesized digits and headword expansions; no engine change is inferred. diff --git a/libs/braillify/examples/nikl_corpus_analyze.rs b/libs/braillify/examples/nikl_corpus_analyze.rs index 9b225587..42e27489 100644 --- a/libs/braillify/examples/nikl_corpus_analyze.rs +++ b/libs/braillify/examples/nikl_corpus_analyze.rs @@ -2770,7 +2770,6 @@ fn first_difference_claimed_by_localized_cohort(item: &EncodedCase) -> bool { || first_difference_at_spaced_numeric_list_comma(item) || first_difference_at_ascii_roman_comma_before_digit_korean(item) || first_difference_at_percent_point_unit_list_comma(item) - || first_difference_at_attached_korean_auxiliary_itda_spacing(item) } /// Input-only candidate gate for acronym expansions such as @@ -3409,38 +3408,6 @@ fn attached_korean_auxiliary_itda_spans(input: &str) -> Vec { spans } -/// Locates only a blank inserted immediately before `있다`, using the real -/// input prefix rather than any reference cell. After the rule-49 correction, -/// this deliberately returns no ranges for the affected inputs. -fn attached_korean_auxiliary_itda_actual_ranges( - input: &str, - actual: &str, -) -> Vec> { - let actual_cells = actual.chars().collect::>(); - let mut ranges = Vec::new(); - for span in attached_korean_auxiliary_itda_spans(input) { - let prefix = braillify::encode_to_unicode(&input[..span.start_byte]) - .expect("an encodable sentence must have an encodable prefix"); - let start = prefix.chars().count(); - if actual_cells.get(start) == Some(&'⠀') { - ranges.push(start..start + 1); - } - } - ranges -} - -fn first_difference_at_attached_korean_auxiliary_itda_spacing(item: &EncodedCase) -> bool { - let Ok(actual) = &item.actual else { - return false; - }; - if actual == &item.located.case.unicode { - return false; - } - let first_difference = first_difference_cell(&item.located.case.unicode, actual); - attached_korean_auxiliary_itda_actual_ranges(&item.located.case.input, actual) - .contains(&(first_difference..first_difference + 1)) -} - fn enum_key(value: &T) -> String { serde_json::to_value(value) .expect("enum serialization must succeed") @@ -4094,9 +4061,7 @@ fn analyze( ( ATTACHED_KOREAN_AUXILIARY_ITDA_SPACING, !attached_korean_auxiliary_itda_spans(&item.located.case.input).is_empty(), - Some(first_difference_at_attached_korean_auxiliary_itda_spacing( - item, - )), + None, true, ), ( @@ -4748,10 +4713,10 @@ fn markdown(report: &AnalysisReport) -> String { `tight_triangle_mark_immediately_before_korean` gate requires literal \ `△한글` with no input space and includes the first following Korean cell in its localized \ output range, so an observed missing-space difference is measured at the mark boundary. \ - The `attached_korean_auxiliary_itda_spacing` gate mirrors the former normalization \ - shape for a Korean token ending in attached `있다`; its localizer independently encodes \ - the real prefix and claims only the blank inserted before the suffix. It does not decide \ - whether orthographic correction may override the printed input.\n\n", + The `attached_korean_auxiliary_itda_spacing` gate is input-only after the rule-49 \ + correction: it measures Korean tokens ending in attached `있다` without claiming a \ + current output signature or deciding whether orthographic correction may override the \ + printed input.\n\n", ); text.push_str( "| Cluster | Candidates | Exact | Mismatch | Conflicting-reference cases |\n\ @@ -8410,37 +8375,6 @@ mod tests { assert_eq!(actual, expected); } - #[test] - fn current_engine_does_not_insert_blank_before_attached_itda() { - let input = "성장을 하고있다."; - let actual = braillify::encode_to_unicode(input).expect("spacing probe must encode"); - let ranges = attached_korean_auxiliary_itda_actual_ranges(input, &actual); - - assert!(ranges.is_empty()); - } - - #[test] - fn localizes_a_blank_inserted_before_attached_itda() { - let input = "성장을 하고있다."; - let span = attached_korean_auxiliary_itda_spans(input) - .into_iter() - .next() - .expect("probe must contain the attached suffix"); - let prefix = braillify::encode_to_unicode(&input[..span.start_byte]).unwrap(); - let start = prefix.chars().count(); - let mut actual = braillify::encode_to_unicode(input).unwrap(); - let insert_at = actual - .char_indices() - .nth(start) - .map_or(actual.len(), |(byte, _)| byte); - actual.insert(insert_at, '⠀'); - - assert_eq!( - attached_korean_auxiliary_itda_actual_ranges(input, &actual), - vec![start..start + 1] - ); - } - #[test] fn aggregates_headword_expansion_outcomes_without_reclassification() { let cases = [ diff --git a/libs/braillify/src/rules/token_rules/math_expression/apply.rs b/libs/braillify/src/rules/token_rules/math_expression/apply.rs index fffce692..dcbdf98d 100644 --- a/libs/braillify/src/rules/token_rules/math_expression/apply.rs +++ b/libs/braillify/src/rules/token_rules/math_expression/apply.rs @@ -192,7 +192,13 @@ fn is_multiword_closed_roman_parenthetical_tail( { return false; } - return !before.contains('(') && !before.contains(')'); + if before.contains('(') { + return false; + } + if before.contains(')') { + return false; + } + return true; } if previous_text.chars().all(|ch| ch.is_ascii_alphabetic()) { cursor = i.checked_sub(1); @@ -917,6 +923,8 @@ mod tests { #[case::digit_after_opening("(2Romeo Juliet)", false)] #[case::digit_in_earlier_fragment("(Romeo2 Juliet)", false)] #[case::nonletter_earlier_without_opening("Romeo2 Juliet More)", false)] + #[case::nested_opening_before_fragment("((Romeo Juliet)", false)] + #[case::closing_before_opening(")(Romeo Juliet)", false)] fn recognizes_only_complete_multiword_roman_parenthetical_tails( #[case] input: &str, #[case] expected: bool, From 08b928b4827c4d3d93d591d906979ab5181f93e4 Mon Sep 17 00:00:00 2001 From: devfive Date: Tue, 1 Sep 2026 23:23:50 +0900 Subject: [PATCH 71/77] Simplify corpus coverage branches --- .../braillify/examples/nikl_corpus_analyze.rs | 31 ++++++------------- .../token_rules/math_expression/apply.rs | 8 +---- 2 files changed, 10 insertions(+), 29 deletions(-) diff --git a/libs/braillify/examples/nikl_corpus_analyze.rs b/libs/braillify/examples/nikl_corpus_analyze.rs index 42e27489..de320232 100644 --- a/libs/braillify/examples/nikl_corpus_analyze.rs +++ b/libs/braillify/examples/nikl_corpus_analyze.rs @@ -3385,9 +3385,7 @@ fn first_difference_in_tight_triangle(item: &EncodedCase) -> bool { /// full stop) but contains no printed space before that suffix. Membership is /// descriptive; it does not decide whether orthography may override rule 49's /// instruction to follow print spacing. -fn attached_korean_auxiliary_itda_spans(input: &str) -> Vec { - let mut spans = Vec::new(); - let mut word_start = 0usize; +fn has_attached_korean_auxiliary_itda(input: &str) -> bool { for word in input.split_inclusive(char::is_whitespace) { let body = word.trim_end_matches(char::is_whitespace); let suffix = ["있다.", "있다"] @@ -3397,15 +3395,11 @@ fn attached_korean_auxiliary_itda_spans(input: &str) -> Vec { let suffix_start = body.len() - suffix.len(); let prefix = &body[..suffix_start]; if !prefix.is_empty() && prefix.chars().any(is_korean_script) { - spans.push(InputSpan { - start_byte: word_start + suffix_start, - end_byte: word_start + body.len(), - }); + return true; } } - word_start += word.len(); } - spans + false } fn enum_key(value: &T) -> String { @@ -4060,7 +4054,7 @@ fn analyze( ), ( ATTACHED_KOREAN_AUXILIARY_ITDA_SPACING, - !attached_korean_auxiliary_itda_spans(&item.located.case.input).is_empty(), + has_attached_korean_auxiliary_itda(&item.located.case.input), None, true, ), @@ -8361,18 +8355,11 @@ mod tests { } #[rstest::rstest] - #[case::corpus_attached_suffix("성장을 하고있다.", vec!["있다."])] - #[case::pdf_printed_space("그림을 그리고 있다.", vec![])] - #[case::independent_suffix("있다.", vec![])] - fn detects_only_attached_korean_auxiliary_itda( - #[case] input: &str, - #[case] expected: Vec<&str>, - ) { - let actual = attached_korean_auxiliary_itda_spans(input) - .into_iter() - .map(|span| &input[span.start_byte..span.end_byte]) - .collect::>(); - assert_eq!(actual, expected); + #[case::corpus_attached_suffix("성장을 하고있다.", true)] + #[case::pdf_printed_space("그림을 그리고 있다.", false)] + #[case::independent_suffix("있다.", false)] + fn detects_only_attached_korean_auxiliary_itda(#[case] input: &str, #[case] expected: bool) { + assert_eq!(has_attached_korean_auxiliary_itda(input), expected); } #[test] diff --git a/libs/braillify/src/rules/token_rules/math_expression/apply.rs b/libs/braillify/src/rules/token_rules/math_expression/apply.rs index dcbdf98d..4936435d 100644 --- a/libs/braillify/src/rules/token_rules/math_expression/apply.rs +++ b/libs/braillify/src/rules/token_rules/math_expression/apply.rs @@ -192,13 +192,7 @@ fn is_multiword_closed_roman_parenthetical_tail( { return false; } - if before.contains('(') { - return false; - } - if before.contains(')') { - return false; - } - return true; + return before.find(['(', ')']).is_none(); } if previous_text.chars().all(|ch| ch.is_ascii_alphabetic()) { cursor = i.checked_sub(1); From a5c9a3f2f74f7124bfb8c6cc1f4fae6aa5bd0821 Mon Sep 17 00:00:00 2001 From: devfive Date: Tue, 1 Sep 2026 23:54:53 +0900 Subject: [PATCH 72/77] Handle prose trailers on Roman expansions --- docs/corpus-analysis/NIKL_2025_V1.md | 509 +++++++++--------- docs/corpus-analysis/NIKL_2025_V1_inquiry.md | 4 +- .../braillify/examples/nikl_corpus_analyze.rs | 39 +- .../token_rules/math_expression/apply.rs | 127 ++++- 4 files changed, 411 insertions(+), 268 deletions(-) diff --git a/docs/corpus-analysis/NIKL_2025_V1.md b/docs/corpus-analysis/NIKL_2025_V1.md index f5229e26..22f430eb 100644 --- a/docs/corpus-analysis/NIKL_2025_V1.md +++ b/docs/corpus-analysis/NIKL_2025_V1.md @@ -7,9 +7,9 @@ | Metric | Count | |---|---:| | Total | 83528 | -| Exact | 69359 | -| Mismatch | 14169 | -| Exact accuracy | 83.04% | +| Exact | 69430 | +| Mismatch | 14098 | +| Exact accuracy | 83.12% | | Duplicate records | 0 | | Inputs with conflicting references | 0 | @@ -19,21 +19,21 @@ Primary classes are evidence gates, not permissions to change the engine. `imple | Primary class | Count | |---|---:| -| `comparison_method` | 312 | -| `corpus_suspect` | 1112 | -| `exact` | 69359 | -| `pending_rule_review` | 12586 | -| `unsupported_character_review` | 159 | +| `comparison_method` | 316 | +| `corpus_suspect` | 1114 | +| `exact` | 69430 | +| `pending_rule_review` | 12513 | +| `unsupported_character_review` | 155 | | Reproducible reason | Count | |---|---:| -| `exact` | 69359 | -| `foreign_text_rule_review` | 12434 | -| `nfkc_input_equivalent` | 312 | +| `exact` | 69430 | +| `foreign_text_rule_review` | 12361 | +| `nfkc_input_equivalent` | 316 | | `number_rule_review` | 144 | | `punctuation_rule_review` | 8 | -| `rule34_roman_indicator_before_opening_parenthesis` | 1112 | -| `unsupported_character_review` | 159 | +| `rule34_roman_indicator_before_opening_parenthesis` | 1114 | +| `unsupported_character_review` | 155 | ## Pending first-difference cell transitions @@ -41,33 +41,33 @@ This ranking is a diagnostic selector, not an implementation rule. It counts onl | Rank | Expected → actual first cell | Cases | |---:|---|---:| -| 1 | `U+2834 ⠴ -> U+2800 ⠀` | 2174 | -| 2 | `U+2815 ⠕ -> U+2833 ⠳` | 1360 | -| 3 | `U+2830 ⠰ -> U+2820 ⠠` | 868 | +| 1 | `U+2834 ⠴ -> U+2800 ⠀` | 2073 | +| 2 | `U+2815 ⠕ -> U+2833 ⠳` | 1369 | +| 3 | `U+2830 ⠰ -> U+2820 ⠠` | 870 | | 4 | `U+2810 ⠐ -> U+2832 ⠲` | 721 | | 5 | `U+2826 ⠦ -> U+2834 ⠴` | 471 | -| 6 | `U+280E ⠎ -> U+280C ⠌` | 464 | +| 6 | `U+280E ⠎ -> U+280C ⠌` | 465 | | 7 | `U+2826 ⠦ -> U+2800 ⠀` | 445 | | 8 | `U+2801 ⠁ -> U+281C ⠜` | 408 | | 9 | `U+2800 ⠀ -> U+2807 ⠇` | 365 | | 10 | `U+283C ⠼ -> U+2800 ⠀` | 365 | | 11 | `U+2834 ⠴ -> U+2830 ⠰` | 359 | -| 12 | `U+2811 ⠑ -> U+282B ⠫` | 330 | -| 13 | `U+2820 ⠠ -> U+2830 ⠰` | 245 | +| 12 | `U+2811 ⠑ -> U+282B ⠫` | 331 | +| 13 | `U+2820 ⠠ -> U+2830 ⠰` | 247 | | 14 | `U+2826 ⠦ -> U+2810 ⠐` | 174 | -| 15 | `U+2800 ⠀ -> U+2832 ⠲` | 158 | +| 15 | `U+2800 ⠀ -> U+2832 ⠲` | 163 | | 16 | `U+2820 ⠠ -> U+2809 ⠉` | 151 | | 17 | `U+281B ⠛ -> U+2823 ⠣` | 144 | | 18 | `U+2810 ⠐ -> U+2800 ⠀` | 131 | -| 19 | `U+2811 ⠑ -> U+283B ⠻` | 101 | +| 19 | `U+2811 ⠑ -> U+283B ⠻` | 106 | | 20 | `U+2810 ⠐ -> U+2814 ⠔` | 98 | ### `U+2834 ⠴ -> U+2800 ⠀` -- `sentence_01.json` #18: 2023년형 신제품은 매터(Matter)와 HCA(Home Connectivity Alliance) 표준을 지원하는 스마트싱스 허브(SmartThings Hub)를 기반으로 다양한 기기를 자동으로 연결하고, 제어·관리할 수 있는 것이 특징이다. - - expected: `⠁⠞⠞⠻⠠⠴⠧⠀⠴⠠⠠⠓⠉⠁⠐⠣⠠⠓⠕⠍⠑⠀⠠⠒` - - actual: `⠁⠞⠞⠻⠠⠴⠧⠀⠀⠠⠠⠓⠉⠁⠦⠠⠓⠕⠍⠑⠀⠴⠠⠒` - - first differing cell (zero-based): 37 +- `sentence_01.json` #35: 삼성전자는 올해 CES에서 77인치 OLED TV를 처음으로 공개할 예정이다. 이는 지난 8월 ‘국제정보디스플레이학술대회(IMID) 2022’에서 삼성디스플레이가 공개한 77인치 퀀텀닷(QD)-OLED 패널을 탑재한 제품이다. + - expected: `⠋⠏⠒⠓⠎⠢⠊⠄⠴⠐⠣⠟⠙⠐⠜⠤⠠⠠⠕⠇⠫⠲⠀⠙` + - actual: `⠋⠏⠒⠓⠎⠢⠊⠄⠀⠀⠦⠠⠠⠟⠙⠴⠔⠠⠠⠕⠇⠑⠙⠀` + - first differing cell (zero-based): 172 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` - `sentence_02.json` #186: 3일 사회관계망서비스(SNS)를 통해 유포된 동영상에는 중학생인 A(14)양이 지난달 30일 태안의 한 지하 주차장에서 B(15)양으로부터 일방적으로 폭행을 당하고 주변에 있던 학생들은 이를 웃으며 방관하는 장면이 담겼다. - expected: `⠶⠚⠁⠠⠗⠶⠟⠀⠴⠠⠁⠦⠄⠼⠁⠙⠠⠴⠜⠶⠕⠀⠨⠕` @@ -300,23 +300,23 @@ This ranking removes only cases whose first difference is inside an existing out |---:|---|---:| | 1 | `U+2810 ⠐ -> U+2832 ⠲` | 249 | | 2 | `U+283C ⠼ -> U+2800 ⠀` | 159 | -| 3 | `U+2834 ⠴ -> U+2800 ⠀` | 155 | +| 3 | `U+2834 ⠴ -> U+2800 ⠀` | 154 | | 4 | `U+281B ⠛ -> U+2823 ⠣` | 144 | | 5 | `U+2826 ⠦ -> U+2810 ⠐` | 133 | | 6 | `U+2810 ⠐ -> U+2800 ⠀` | 131 | -| 7 | `U+2800 ⠀ -> U+2832 ⠲` | 101 | -| 8 | `U+2811 ⠑ -> U+283B ⠻` | 101 | +| 7 | `U+2800 ⠀ -> U+2832 ⠲` | 107 | +| 8 | `U+2811 ⠑ -> U+283B ⠻` | 106 | | 9 | `U+2810 ⠐ -> U+2814 ⠔` | 98 | | 10 | `U+2824 ⠤ -> U+2800 ⠀` | 80 | | 11 | `U+2808 ⠈ -> U+2832 ⠲` | 79 | | 12 | `U+2826 ⠦ -> U+2800 ⠀` | 79 | | 13 | `U+280E ⠎ -> U+2829 ⠩` | 73 | -| 14 | `U+2834 ⠴ -> U+2820 ⠠` | 64 | +| 14 | `U+2824 ⠤ -> U+2834 ⠴` | 59 | | 15 | `U+2811 ⠑ -> U+2822 ⠢` | 58 | -| 16 | `U+2824 ⠤ -> U+2834 ⠴` | 58 | -| 17 | `U+2830 ⠰ -> U+2820 ⠠` | 57 | -| 18 | `U+2832 ⠲ -> U+2800 ⠀` | 55 | -| 19 | `U+280A ⠊ -> U+2814 ⠔` | 51 | +| 16 | `U+2830 ⠰ -> U+2820 ⠠` | 58 | +| 17 | `U+2832 ⠲ -> U+2800 ⠀` | 56 | +| 18 | `U+2834 ⠴ -> U+2820 ⠠` | 55 | +| 19 | `U+280A ⠊ -> U+2814 ⠔` | 52 | | 20 | `U+2810 ⠐ -> U+2815 ⠕` | 48 | ### Residual `U+2810 ⠐ -> U+2832 ⠲` @@ -555,65 +555,65 @@ These are cross-cutting input-only structural cohorts, not new primary classes a | Cluster | Candidates | Exact | Mismatch | Conflicting-reference cases | |---|---:|---:|---:|---:| -| `allcaps_roman_run_beginning_with_pure_letter_shortform` | 4120 | 2516 | 1604 | 0 | -| `allcaps_roman_run_containing_ar` | 1022 | 370 | 652 | 0 | +| `allcaps_roman_run_beginning_with_pure_letter_shortform` | 4120 | 2521 | 1599 | 0 | +| `allcaps_roman_run_containing_ar` | 1022 | 371 | 651 | 0 | | `allcaps_roman_run_containing_ed` | 816 | 334 | 482 | 0 | | `allcaps_roman_run_containing_ou` | 1816 | 70 | 1746 | 0 | -| `allcaps_roman_run_containing_st` | 1479 | 702 | 777 | 0 | +| `allcaps_roman_run_containing_st` | 1479 | 703 | 776 | 0 | | `ampersand_before_attached_ascii_roman_segment` | 30 | 12 | 18 | 0 | | `ascii_apostrophe_between_ascii_letter_runs` | 147 | 68 | 79 | 0 | | `ascii_roman_tail_comma_before_digit_korean_token` | 58 | 3 | 55 | 0 | -| `attached_ascii_roman_segments_joined_by_ampersand` | 802 | 607 | 195 | 0 | -| `attached_ascii_roman_to_korean_script_boundary` | 17693 | 13451 | 4242 | 0 | +| `attached_ascii_roman_segments_joined_by_ampersand` | 802 | 608 | 194 | 0 | +| `attached_ascii_roman_to_korean_script_boundary` | 17693 | 13465 | 4228 | 0 | | `attached_korean_auxiliary_itda_spacing` | 95 | 72 | 23 | 0 | -| `capitals_word_mode_previously_spanning_nonletter_scope` | 1733 | 852 | 881 | 0 | -| `closed_roman_parenthetical_after_non_ascii_letter_boundary` | 63959 | 53056 | 10903 | 0 | -| `compact_numeric_ascii_letter_suffix` | 2975 | 1913 | 1062 | 0 | -| `consecutive_ascii_roman_words_whitespace_boundary` | 4679 | 2287 | 2392 | 0 | -| `decimal_point_between_ascii_digits` | 4546 | 3444 | 1102 | 0 | +| `capitals_word_mode_previously_spanning_nonletter_scope` | 1733 | 854 | 879 | 0 | +| `closed_roman_parenthetical_after_non_ascii_letter_boundary` | 63959 | 53122 | 10837 | 0 | +| `compact_numeric_ascii_letter_suffix` | 2975 | 1917 | 1058 | 0 | +| `consecutive_ascii_roman_words_whitespace_boundary` | 4679 | 2358 | 2321 | 0 | +| `decimal_point_between_ascii_digits` | 4546 | 3447 | 1099 | 0 | | `korean_inline_parenthesized_single_arithmetic_operator` | 23 | 17 | 6 | 0 | | `korean_majority_same_token_roman_sandwich_non_domain` | 947 | 593 | 354 | 0 | -| `korean_prefixed_closed_allcaps_parenthetical` | 54492 | 45656 | 8836 | 0 | -| `korean_prefixed_closed_roman_annotation_rule_34_order` | 64382 | 53650 | 10732 | 0 | +| `korean_prefixed_closed_allcaps_parenthetical` | 54492 | 45705 | 8787 | 0 | +| `korean_prefixed_closed_roman_annotation_rule_34_order` | 64382 | 53716 | 10666 | 0 | | `korean_prefixed_roman_parenthetical_followed_by_allcaps_hyphen_suffix` | 11 | 0 | 11 | 0 | -| `mixed_roman_korean_word_before_uppercase_headword_expansion` | 10 | 0 | 10 | 0 | +| `mixed_roman_korean_word_before_uppercase_headword_expansion` | 10 | 7 | 3 | 0 | | `multi_character_allcaps_roman_runs_joined_by_middle_dot` | 97 | 0 | 97 | 0 | | `percent_point_unit_list_comma` | 7 | 5 | 2 | 0 | | `pure_allcaps_segment_before_hyphen_and_multi_allcaps_segment_after` | 448 | 226 | 222 | 0 | | `roman_hyphenated_word_after_whitespace_following_korean_word` | 361 | 86 | 275 | 0 | -| `roman_parenthetical_headword_after_whitespace_following_korean_word` | 4695 | 3550 | 1145 | 0 | -| `roman_run_after_whitespace_following_closed_roman_enclosure` | 1093 | 143 | 950 | 0 | +| `roman_parenthetical_headword_after_whitespace_following_korean_word` | 4695 | 3610 | 1085 | 0 | +| `roman_run_after_whitespace_following_closed_roman_enclosure` | 1093 | 144 | 949 | 0 | | `roman_run_immediately_before_attached_middle_dot_boundary` | 577 | 0 | 577 | 0 | | `rule69_ascii_unit_before_terminator_skipping_symbol` | 440 | 344 | 96 | 0 | | `single_capital_followed_by_parenthesized_digits` | 1361 | 0 | 1361 | 0 | | `spaced_comma_between_ascii_digit_runs` | 217 | 182 | 35 | 0 | -| `standalone_multi_character_uppercase_roman_word` | 62411 | 51365 | 11046 | 0 | +| `standalone_multi_character_uppercase_roman_word` | 62411 | 51419 | 10992 | 0 | | `tight_triangle_mark_immediately_before_korean` | 377 | 0 | 377 | 0 | -| `uppercase_alphanumeric_roman_digit_sequence` | 3429 | 2096 | 1333 | 0 | -| `uppercase_ascii_run_immediately_after_digit_in_roman_sequence` | 1896 | 1264 | 632 | 0 | +| `uppercase_alphanumeric_roman_digit_sequence` | 3429 | 2097 | 1332 | 0 | +| `uppercase_ascii_run_immediately_after_digit_in_roman_sequence` | 1896 | 1267 | 629 | 0 | | `uppercase_ascii_run_immediately_after_hyphen_in_roman_sequence` | 952 | 271 | 681 | 0 | -| `uppercase_ascii_segments_joined_by_ampersand_capitalization` | 439 | 320 | 119 | 0 | -| `uppercase_roman_headword_closed_multiword_parenthetical` | 175 | 17 | 158 | 0 | +| `uppercase_ascii_segments_joined_by_ampersand_capitalization` | 439 | 321 | 118 | 0 | +| `uppercase_roman_headword_closed_multiword_parenthetical` | 175 | 87 | 88 | 0 | | `uppercase_roman_run_followed_by_hyphen_digits` | 571 | 194 | 377 | 0 | -| `uppercase_word_after_whitespace_continuing_ascii_roman_text` | 1729 | 760 | 969 | 0 | +| `uppercase_word_after_whitespace_continuing_ascii_roman_text` | 1729 | 771 | 958 | 0 | ### `allcaps_roman_run_beginning_with_pure_letter_shortform` -Of the 4120 candidates, 1533 are the actual `pending_rule_review` subcluster. The other 2587 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 4120 candidates, 1528 are the actual `pending_rule_review` subcluster. The other 2592 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. -For this output-signature audit, 1604 mismatches were evaluable and 836 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. +For this output-signature audit, 1599 mismatches were evaluable and 837 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. Localized first-difference transitions: -- `U+2830 ⠰ -> U+2820 ⠠`: 788 +- `U+2830 ⠰ -> U+2820 ⠠`: 789 - `U+2820 ⠠ -> U+2830 ⠰`: 48 Mismatch primary-class distribution: -- `comparison_method`: 11 +- `comparison_method`: 12 - `corpus_suspect`: 50 -- `pending_rule_review`: 1533 -- `unsupported_character_review`: 10 +- `pending_rule_review`: 1528 +- `unsupported_character_review`: 9 Representative `exact` samples: @@ -682,9 +682,9 @@ Representative `mismatch` samples: ### `allcaps_roman_run_containing_ar` -Of the 1022 candidates, 628 are the actual `pending_rule_review` subcluster. The other 394 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 1022 candidates, 627 are the actual `pending_rule_review` subcluster. The other 395 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. -For this output-signature audit, 652 mismatches were evaluable and 407 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. +For this output-signature audit, 651 mismatches were evaluable and 407 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. Localized first-difference transitions: @@ -698,7 +698,7 @@ Mismatch primary-class distribution: - `comparison_method`: 3 - `corpus_suspect`: 15 -- `pending_rule_review`: 628 +- `pending_rule_review`: 627 - `unsupported_character_review`: 6 Representative `exact` samples: @@ -768,7 +768,7 @@ Representative `mismatch` samples: ### `allcaps_roman_run_containing_ed` -Of the 816 candidates, 412 are the actual `pending_rule_review` subcluster. The other 404 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 816 candidates, 411 are the actual `pending_rule_review` subcluster. The other 405 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. For this output-signature audit, 482 mismatches were evaluable and 330 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. @@ -782,8 +782,8 @@ Localized first-difference transitions: Mismatch primary-class distribution: - `comparison_method`: 1 -- `corpus_suspect`: 64 -- `pending_rule_review`: 412 +- `corpus_suspect`: 65 +- `pending_rule_review`: 411 - `unsupported_character_review`: 5 Representative `exact` samples: @@ -855,11 +855,11 @@ Representative `mismatch` samples: Of the 1816 candidates, 1660 are the actual `pending_rule_review` subcluster. The other 156 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. -For this output-signature audit, 1746 mismatches were evaluable and 1356 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. +For this output-signature audit, 1746 mismatches were evaluable and 1365 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. Localized first-difference transitions: -- `U+2815 ⠕ -> U+2833 ⠳`: 1355 +- `U+2815 ⠕ -> U+2833 ⠳`: 1364 - `U+2810 ⠐ -> U+283D ⠽`: 1 Mismatch primary-class distribution: @@ -914,13 +914,13 @@ Representative `mismatch` samples: ### `allcaps_roman_run_containing_st` -Of the 1479 candidates, 724 are the actual `pending_rule_review` subcluster. The other 755 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 1479 candidates, 723 are the actual `pending_rule_review` subcluster. The other 756 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. -For this output-signature audit, 777 mismatches were evaluable and 459 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. +For this output-signature audit, 776 mismatches were evaluable and 460 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. Localized first-difference transitions: -- `U+280E ⠎ -> U+280C ⠌`: 451 +- `U+280E ⠎ -> U+280C ⠌`: 452 - `U+2820 ⠠ -> U+280C ⠌`: 4 - `U+280C ⠌ -> U+280E ⠎`: 1 - `U+2810 ⠐ -> U+2811 ⠑`: 1 @@ -930,7 +930,7 @@ Mismatch primary-class distribution: - `comparison_method`: 13 - `corpus_suspect`: 35 -- `pending_rule_review`: 724 +- `pending_rule_review`: 723 - `unsupported_character_review`: 5 Representative `exact` samples: @@ -1212,15 +1212,15 @@ Representative `mismatch` samples: ### `attached_ascii_roman_segments_joined_by_ampersand` -Of the 802 candidates, 167 are the actual `pending_rule_review` subcluster. The other 635 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 802 candidates, 166 are the actual `pending_rule_review` subcluster. The other 636 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. -For this output-signature audit, 195 mismatches were evaluable and 0 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. +For this output-signature audit, 194 mismatches were evaluable and 0 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. Mismatch primary-class distribution: - `comparison_method`: 15 - `corpus_suspect`: 11 -- `pending_rule_review`: 167 +- `pending_rule_review`: 166 - `unsupported_character_review`: 2 Representative `exact` samples: @@ -1267,15 +1267,15 @@ Representative `mismatch` samples: ### `attached_ascii_roman_to_korean_script_boundary` -Of the 17693 candidates, 3406 are the actual `pending_rule_review` subcluster. The other 14287 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 17693 candidates, 3391 are the actual `pending_rule_review` subcluster. The other 14302 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. -For this output-signature audit, 4242 mismatches were evaluable and 0 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. +For this output-signature audit, 4228 mismatches were evaluable and 0 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. Mismatch primary-class distribution: - `comparison_method`: 73 -- `corpus_suspect`: 725 -- `pending_rule_review`: 3406 +- `corpus_suspect`: 726 +- `pending_rule_review`: 3391 - `unsupported_character_review`: 38 Representative `exact` samples: @@ -1414,13 +1414,13 @@ Representative `mismatch` samples: ### `capitals_word_mode_previously_spanning_nonletter_scope` -Of the 1733 candidates, 823 are the actual `pending_rule_review` subcluster. The other 910 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 1733 candidates, 821 are the actual `pending_rule_review` subcluster. The other 912 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. Mismatch primary-class distribution: - `comparison_method`: 15 - `corpus_suspect`: 38 -- `pending_rule_review`: 823 +- `pending_rule_review`: 821 - `unsupported_character_review`: 5 Representative `exact` samples: @@ -1467,9 +1467,9 @@ Representative `mismatch` samples: ### `closed_roman_parenthetical_after_non_ascii_letter_boundary` -Of the 63959 candidates, 9411 are the actual `pending_rule_review` subcluster. The other 54548 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 63959 candidates, 9343 are the actual `pending_rule_review` subcluster. The other 54616 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. -For this output-signature audit, 10903 mismatches were evaluable and 1130 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. +For this output-signature audit, 10837 mismatches were evaluable and 1130 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. Localized first-difference transitions: @@ -1481,10 +1481,10 @@ Localized first-difference transitions: Mismatch primary-class distribution: -- `comparison_method`: 238 -- `corpus_suspect`: 1112 -- `pending_rule_review`: 9411 -- `unsupported_character_review`: 142 +- `comparison_method`: 242 +- `corpus_suspect`: 1114 +- `pending_rule_review`: 9343 +- `unsupported_character_review`: 138 Representative `exact` samples: @@ -1553,9 +1553,9 @@ Representative `mismatch` samples: ### `compact_numeric_ascii_letter_suffix` -Of the 2975 candidates, 990 are the actual `pending_rule_review` subcluster. The other 1985 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 2975 candidates, 985 are the actual `pending_rule_review` subcluster. The other 1990 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. -For this output-signature audit, 1062 mismatches were evaluable and 256 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. +For this output-signature audit, 1058 mismatches were evaluable and 256 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. Localized first-difference transitions: @@ -1568,8 +1568,8 @@ Localized first-difference transitions: Mismatch primary-class distribution: - `comparison_method`: 12 -- `corpus_suspect`: 53 -- `pending_rule_review`: 990 +- `corpus_suspect`: 54 +- `pending_rule_review`: 985 - `unsupported_character_review`: 7 Representative `exact` samples: @@ -1639,26 +1639,26 @@ Representative `mismatch` samples: ### `consecutive_ascii_roman_words_whitespace_boundary` -Of the 4679 candidates, 2256 are the actual `pending_rule_review` subcluster. The other 2423 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 4679 candidates, 2183 are the actual `pending_rule_review` subcluster. The other 2496 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. -For this output-signature audit, 2392 mismatches were evaluable and 26 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. +For this output-signature audit, 2321 mismatches were evaluable and 25 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. Localized first-difference transitions: -- `U+2800 ⠀ -> U+2832 ⠲`: 26 +- `U+2800 ⠀ -> U+2832 ⠲`: 25 Mismatch primary-class distribution: -- `comparison_method`: 20 -- `corpus_suspect`: 90 -- `pending_rule_review`: 2256 -- `unsupported_character_review`: 26 +- `comparison_method`: 24 +- `corpus_suspect`: 92 +- `pending_rule_review`: 2183 +- `unsupported_character_review`: 22 Representative `exact` samples: -- `sentence_01.json` #32: 노스홀 메인 부스에서는 신기술인 ‘메타(META) 테크놀로지’를 적용해 화질을 혁신한 3세대 OLED TV 패널을 발표할 예정이다. - - expected: `⠉⠥⠠⠪⠚⠥⠂⠀⠑⠝⠟⠀⠘⠍⠠⠪⠝⠠⠎⠉⠵⠀⠠⠟` - - actual: `⠉⠥⠠⠪⠚⠥⠂⠀⠑⠝⠟⠀⠘⠍⠠⠪⠝⠠⠎⠉⠵⠀⠠⠟` +- `sentence_01.json` #18: 2023년형 신제품은 매터(Matter)와 HCA(Home Connectivity Alliance) 표준을 지원하는 스마트싱스 허브(SmartThings Hub)를 기반으로 다양한 기기를 자동으로 연결하고, 제어·관리할 수 있는 것이 특징이다. + - expected: `⠼⠃⠚⠃⠉⠀⠉⠡⠚⠻⠀⠠⠟⠨⠝⠙⠍⠢⠵⠀⠑⠗⠓⠎` + - actual: `⠼⠃⠚⠃⠉⠀⠉⠡⠚⠻⠀⠠⠟⠨⠝⠙⠍⠢⠵⠀⠑⠗⠓⠎` - current primary/reason: `exact` / `exact` - `sentence_02.json` #26: 공주대학교 인재개발실에서 취득한 ISO 21001(교육기관경영시스템)은 국제표준화기구(ISO:International Organization for Standardization)에서 34개국 140여명의 전문가 그룹에 의해 개발돼 2018년에 제정되었다. - expected: `⠈⠿⠨⠍⠊⠗⠚⠁⠈⠬⠀⠟⠨⠗⠈⠗⠘⠂⠠⠕⠂⠝⠠⠎` @@ -1693,10 +1693,10 @@ Representative `localized_mismatch` samples: Representative `mismatch` samples: -- `sentence_01.json` #18: 2023년형 신제품은 매터(Matter)와 HCA(Home Connectivity Alliance) 표준을 지원하는 스마트싱스 허브(SmartThings Hub)를 기반으로 다양한 기기를 자동으로 연결하고, 제어·관리할 수 있는 것이 특징이다. - - expected: `⠁⠞⠞⠻⠠⠴⠧⠀⠴⠠⠠⠓⠉⠁⠐⠣⠠⠓⠕⠍⠑⠀⠠⠒` - - actual: `⠁⠞⠞⠻⠠⠴⠧⠀⠀⠠⠠⠓⠉⠁⠦⠠⠓⠕⠍⠑⠀⠴⠠⠒` - - first differing cell (zero-based): 37 +- `sentence_01.json` #35: 삼성전자는 올해 CES에서 77인치 OLED TV를 처음으로 공개할 예정이다. 이는 지난 8월 ‘국제정보디스플레이학술대회(IMID) 2022’에서 삼성디스플레이가 공개한 77인치 퀀텀닷(QD)-OLED 패널을 탑재한 제품이다. + - expected: `⠋⠏⠒⠓⠎⠢⠊⠄⠴⠐⠣⠟⠙⠐⠜⠤⠠⠠⠕⠇⠫⠲⠀⠙` + - actual: `⠋⠏⠒⠓⠎⠢⠊⠄⠀⠀⠦⠠⠠⠟⠙⠴⠔⠠⠠⠕⠇⠑⠙⠀` + - first differing cell (zero-based): 172 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` - `sentence_02.json` #14: 한국타이어앤테크놀로지㈜(대표이사 이수일, 이하 한국타이어)가 국제자동차연맹(FIA)이 주관하는 ‘FIA 주니어 ERC(FIA Junior ERC, 이하 주니어 ERC)’ 대회에 레이싱 타이어를 독점 공급한다. - expected: `⠚⠒⠈⠍⠁⠓⠣⠕⠎⠗⠒⠓⠝⠋⠪⠉⠥⠂⠐⠥⠨⠕⠦⠄` @@ -1717,9 +1717,9 @@ Representative `mismatch` samples: ### `decimal_point_between_ascii_digits` -Of the 4546 candidates, 1045 are the actual `pending_rule_review` subcluster. The other 3501 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 4546 candidates, 1042 are the actual `pending_rule_review` subcluster. The other 3504 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. -For this output-signature audit, 1102 mismatches were evaluable and 267 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. +For this output-signature audit, 1099 mismatches were evaluable and 267 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. Localized first-difference transitions: @@ -1733,7 +1733,7 @@ Mismatch primary-class distribution: - `comparison_method`: 17 - `corpus_suspect`: 28 -- `pending_rule_review`: 1045 +- `pending_rule_review`: 1042 - `unsupported_character_review`: 12 Representative `exact` samples: @@ -1853,9 +1853,10 @@ Of the 947 candidates, 342 are the actual `pending_rule_review` subcluster. The Mismatch primary-class distribution: +- `comparison_method`: 1 - `corpus_suspect`: 7 - `pending_rule_review`: 342 -- `unsupported_character_review`: 5 +- `unsupported_character_review`: 4 Representative `exact` samples: @@ -1901,13 +1902,13 @@ Representative `mismatch` samples: ### `korean_prefixed_closed_allcaps_parenthetical` -Of the 54492 candidates, 7533 are the actual `pending_rule_review` subcluster. The other 46959 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 54492 candidates, 7483 are the actual `pending_rule_review` subcluster. The other 47009 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. Mismatch primary-class distribution: - `comparison_method`: 186 -- `corpus_suspect`: 994 -- `pending_rule_review`: 7533 +- `corpus_suspect`: 995 +- `pending_rule_review`: 7483 - `unsupported_character_review`: 123 Representative `exact` samples: @@ -1954,21 +1955,21 @@ Representative `mismatch` samples: ### `korean_prefixed_closed_roman_annotation_rule_34_order` -Of the 64382 candidates, 9236 are the actual `pending_rule_review` subcluster. The other 55146 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 64382 candidates, 9168 are the actual `pending_rule_review` subcluster. The other 55214 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. -For this output-signature audit, 10732 mismatches were evaluable and 1117 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. +For this output-signature audit, 10666 mismatches were evaluable and 1119 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. Localized first-difference transitions: -- `U+2834 ⠴ -> U+2826 ⠦`: 1115 +- `U+2834 ⠴ -> U+2826 ⠦`: 1117 - `U+2810 ⠐ -> U+2826 ⠦`: 2 Mismatch primary-class distribution: -- `comparison_method`: 241 -- `corpus_suspect`: 1112 -- `pending_rule_review`: 9236 -- `unsupported_character_review`: 143 +- `comparison_method`: 245 +- `corpus_suspect`: 1114 +- `pending_rule_review`: 9168 +- `unsupported_character_review`: 139 Representative `exact` samples: @@ -2088,29 +2089,41 @@ Representative `mismatch` samples: ### `mixed_roman_korean_word_before_uppercase_headword_expansion` -Of the 10 candidates, 10 are the actual `pending_rule_review` subcluster. The other 0 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 10 candidates, 3 are the actual `pending_rule_review` subcluster. The other 7 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. -For this output-signature audit, 10 mismatches were evaluable and 8 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. +For this output-signature audit, 3 mismatches were evaluable and 2 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. Localized first-difference transitions: -- `U+2834 ⠴ -> U+2800 ⠀`: 8 +- `U+2809 ⠉ -> U+2812 ⠒`: 1 +- `U+2811 ⠑ -> U+282B ⠫`: 1 Mismatch primary-class distribution: -- `pending_rule_review`: 10 +- `pending_rule_review`: 3 -Representative `mismatch` samples: +Representative `exact` samples: - `sentence_01.json` #18: 2023년형 신제품은 매터(Matter)와 HCA(Home Connectivity Alliance) 표준을 지원하는 스마트싱스 허브(SmartThings Hub)를 기반으로 다양한 기기를 자동으로 연결하고, 제어·관리할 수 있는 것이 특징이다. - - expected: `⠁⠞⠞⠻⠠⠴⠧⠀⠴⠠⠠⠓⠉⠁⠐⠣⠠⠓⠕⠍⠑⠀⠠⠒` - - actual: `⠁⠞⠞⠻⠠⠴⠧⠀⠀⠠⠠⠓⠉⠁⠦⠠⠓⠕⠍⠑⠀⠴⠠⠒` - - first differing cell (zero-based): 37 + - expected: `⠼⠃⠚⠃⠉⠀⠉⠡⠚⠻⠀⠠⠟⠨⠝⠙⠍⠢⠵⠀⠑⠗⠓⠎` + - actual: `⠼⠃⠚⠃⠉⠀⠉⠡⠚⠻⠀⠠⠟⠨⠝⠙⠍⠢⠵⠀⠑⠗⠓⠎` + - current primary/reason: `exact` / `exact` +- `sentence_02.json` #15523: KAI(한국항공우주산업)은 AH(Airbus Helicopters)와 향후 10년간 예측되는 소형무장헬기(LAH)와 수리온(KUH) 300대 규모 생산 물량에 대한 선제적 통합 발주 계약에 서명했다고 31일 밝혔다. + - expected: `⠴⠠⠠⠅⠁⠊⠦⠄⠚⠒⠈⠍⠁⠚⠶⠈⠿⠍⠨⠍⠇⠒⠎⠃` + - actual: `⠴⠠⠠⠅⠁⠊⠦⠄⠚⠒⠈⠍⠁⠚⠶⠈⠿⠍⠨⠍⠇⠒⠎⠃` + - current primary/reason: `exact` / `exact` + +Representative `mismatch` samples: + +- `sentence_01.json` #3656: 제너시스BBQ 그룹이 미국 현지시간 19일 뉴저지주 잉글우드(Englewood)에 BSK(BBQ Smart Kitchen) 1호점을 그랜드 오픈하고 배달·포장 전문 매장을 통한 기하급수 성장을 이어간다고 밝혔다. + - expected: `⠠⠅⠊⠞⠡⠢⠐⠜⠲⠀⠼⠁⠀⠚⠥⠨⠎⠢⠮⠀⠈⠪⠐⠗` + - actual: `⠠⠅⠊⠞⠡⠢⠐⠜⠀⠼⠁⠀⠚⠥⠨⠎⠢⠮⠀⠈⠪⠐⠗⠒` + - first differing cell (zero-based): 104 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` - `sentence_02.json` #9391: 이지스자산운용이 사옥인 여의도 세우빌딩의 2층을 리모델링하고 미국 그린빌딩위원회(USGBC)의 LEED(Leadership in Energy and Environmental Design) 골드(Gold) 등급 인증을 획득했다고 6일 밝혔다. - - expected: `⠎⠛⠃⠉⠠⠴⠺⠀⠴⠠⠠⠇⠑⠑⠙⠐⠣⠠⠇⠂⠙⠻⠩⠊` - - actual: `⠎⠛⠃⠉⠠⠴⠺⠀⠀⠠⠠⠇⠑⠑⠙⠦⠠⠇⠑⠁⠙⠑⠗⠎` - - first differing cell (zero-based): 90 + - expected: `⠴⠺⠀⠴⠠⠠⠇⠑⠑⠙⠐⠣⠠⠇⠂⠙⠻⠩⠊⠏⠀⠔⠀⠠` + - actual: `⠴⠺⠀⠴⠠⠠⠇⠑⠫⠐⠣⠠⠇⠂⠙⠻⠩⠊⠏⠀⠊⠝⠀⠠` + - first differing cell (zero-based): 95 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` ### `multi_character_allcaps_roman_runs_joined_by_middle_dot` @@ -2328,22 +2341,22 @@ Representative `mismatch` samples: ### `roman_parenthetical_headword_after_whitespace_following_korean_word` -Of the 4695 candidates, 1051 are the actual `pending_rule_review` subcluster. The other 3644 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 4695 candidates, 989 are the actual `pending_rule_review` subcluster. The other 3706 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. -For this output-signature audit, 1145 mismatches were evaluable and 163 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. +For this output-signature audit, 1085 mismatches were evaluable and 71 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. Localized first-difference transitions: -- `U+2834 ⠴ -> U+2800 ⠀`: 159 +- `U+2834 ⠴ -> U+2800 ⠀`: 67 - `U+2834 ⠴ -> U+2830 ⠰`: 3 - `U+2820 ⠠ -> U+2834 ⠴`: 1 Mismatch primary-class distribution: -- `comparison_method`: 34 -- `corpus_suspect`: 41 -- `pending_rule_review`: 1051 -- `unsupported_character_review`: 19 +- `comparison_method`: 38 +- `corpus_suspect`: 43 +- `pending_rule_review`: 989 +- `unsupported_character_review`: 15 Representative `exact` samples: @@ -2371,10 +2384,10 @@ Representative `localized_mismatch` samples: - actual: `⠍⠨⠟⠨⠍⠶⠟⠀⠀⠠⠠⠑⠧⠍⠦⠠⠠⠑⠧⠀⠴⠠⠍⠔` - first differing cell (zero-based): 56 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_02.json` #733: 아산시는 6일, 자매도시인 말레이시아 현지 최대 신선 과실류 수입업체인 CTG(Chop Tong Guan)와 농특산물 수출 확대를 위한 업무협약(MOU)을 체결했다. - - expected: `⠕⠃⠎⠃⠰⠝⠟⠀⠴⠠⠠⠉⠞⠛⠐⠣⠠⠡⠕⠏⠀⠠⠞⠰` - - actual: `⠕⠃⠎⠃⠰⠝⠟⠀⠀⠠⠠⠉⠞⠛⠦⠠⠉⠓⠕⠏⠀⠴⠠⠞` - - first differing cell (zero-based): 65 +- `sentence_02.json` #885: 지난 13일 온라인 컨퍼런스로 진행된 밸류데이에서 KT&G는 중장기(2024년~2026년) 주주환원 계획을 공개하고, 3대 핵심사업인 NGP(Next Generation Products)·글로벌CC(궐련담배)·건기식(건강기능식품) 중심의 미래비전 이행 경과를 공유했다. + - expected: `⠠⠕⠢⠇⠎⠃⠟⠀⠴⠠⠠⠝⠛⠏⠐⠣⠠⠝⠑⠭⠞⠀⠠⠛` + - actual: `⠠⠕⠢⠇⠎⠃⠟⠀⠀⠠⠠⠝⠛⠏⠦⠠⠝⠑⠭⠞⠀⠴⠠⠛` + - first differing cell (zero-based): 134 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` - `sentence_03.json` #381: 인포테인먼트 시스템 ccNC(connected car Navigation Cockpit)를 최초 탑재하고 무선 소프트웨어 업데이트(OTA) 대상 범위를 주요 편의 기능까지 대폭 확대했다. - expected: `⠠⠕⠠⠪⠓⠝⠢⠀⠴⠉⠉⠠⠠⠝⠉⠐⠣⠒⠝⠑⠉⠞⠫⠀` @@ -2389,11 +2402,11 @@ Representative `localized_mismatch` samples: Representative `mismatch` samples: -- `sentence_01.json` #18: 2023년형 신제품은 매터(Matter)와 HCA(Home Connectivity Alliance) 표준을 지원하는 스마트싱스 허브(SmartThings Hub)를 기반으로 다양한 기기를 자동으로 연결하고, 제어·관리할 수 있는 것이 특징이다. - - expected: `⠁⠞⠞⠻⠠⠴⠧⠀⠴⠠⠠⠓⠉⠁⠐⠣⠠⠓⠕⠍⠑⠀⠠⠒` - - actual: `⠁⠞⠞⠻⠠⠴⠧⠀⠀⠠⠠⠓⠉⠁⠦⠠⠓⠕⠍⠑⠀⠴⠠⠒` - - first differing cell (zero-based): 37 - - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_01.json` #191: 소프트웨어 측면에서는 스마트 TV 독자 운영체제 웹(web)OS의 진화를 앞세워 맞춤형 고객경험과 CDX(Cross Device eXperience) 경험을 강화한다. + - expected: `⠰⠝⠨⠝⠀⠏⠗⠃⠴⠐⠣⠺⠑⠃⠐⠜⠠⠠⠕⠎⠲⠺⠀⠨` + - actual: `⠰⠝⠨⠝⠀⠏⠗⠃⠦⠄⠴⠺⠑⠃⠠⠴⠰⠠⠠⠕⠎⠲⠺⠀` + - first differing cell (zero-based): 48 + - current primary/reason: `corpus_suspect` / `rule34_roman_indicator_before_opening_parenthesis` - `sentence_02.json` #14: 한국타이어앤테크놀로지㈜(대표이사 이수일, 이하 한국타이어)가 국제자동차연맹(FIA)이 주관하는 ‘FIA 주니어 ERC(FIA Junior ERC, 이하 주니어 ERC)’ 대회에 레이싱 타이어를 독점 공급한다. - expected: `⠚⠒⠈⠍⠁⠓⠣⠕⠎⠗⠒⠓⠝⠋⠪⠉⠥⠂⠐⠥⠨⠕⠦⠄` - actual: `` @@ -2414,9 +2427,9 @@ Representative `mismatch` samples: ### `roman_run_after_whitespace_following_closed_roman_enclosure` -Of the 1093 candidates, 612 are the actual `pending_rule_review` subcluster. The other 481 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 1093 candidates, 611 are the actual `pending_rule_review` subcluster. The other 482 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. -For this output-signature audit, 950 mismatches were evaluable and 260 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. +For this output-signature audit, 949 mismatches were evaluable and 260 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. Localized first-difference transitions: @@ -2429,7 +2442,7 @@ Localized first-difference transitions: Mismatch primary-class distribution: - `corpus_suspect`: 331 -- `pending_rule_review`: 612 +- `pending_rule_review`: 611 - `unsupported_character_review`: 7 Representative `exact` samples: @@ -2737,14 +2750,14 @@ Representative `mismatch` samples: ### `standalone_multi_character_uppercase_roman_word` -Of the 62411 candidates, 9583 are the actual `pending_rule_review` subcluster. The other 52828 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 62411 candidates, 9527 are the actual `pending_rule_review` subcluster. The other 52884 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. Mismatch primary-class distribution: -- `comparison_method`: 227 -- `corpus_suspect`: 1098 -- `pending_rule_review`: 9583 -- `unsupported_character_review`: 138 +- `comparison_method`: 228 +- `corpus_suspect`: 1100 +- `pending_rule_review`: 9527 +- `unsupported_character_review`: 137 Representative `exact` samples: @@ -2831,9 +2844,9 @@ Representative `mismatch` samples: ### `uppercase_alphanumeric_roman_digit_sequence` -Of the 3429 candidates, 1223 are the actual `pending_rule_review` subcluster. The other 2206 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 3429 candidates, 1221 are the actual `pending_rule_review` subcluster. The other 2208 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. -For this output-signature audit, 1333 mismatches were evaluable and 150 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. +For this output-signature audit, 1332 mismatches were evaluable and 150 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. Localized first-difference transitions: @@ -2845,8 +2858,8 @@ Localized first-difference transitions: Mismatch primary-class distribution: - `comparison_method`: 14 -- `corpus_suspect`: 88 -- `pending_rule_review`: 1223 +- `corpus_suspect`: 89 +- `pending_rule_review`: 1221 - `unsupported_character_review`: 8 Representative `exact` samples: @@ -2916,9 +2929,9 @@ Representative `mismatch` samples: ### `uppercase_ascii_run_immediately_after_digit_in_roman_sequence` -Of the 1896 candidates, 573 are the actual `pending_rule_review` subcluster. The other 1323 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 1896 candidates, 569 are the actual `pending_rule_review` subcluster. The other 1327 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. -For this output-signature audit, 632 mismatches were evaluable and 2 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. +For this output-signature audit, 629 mismatches were evaluable and 2 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. Localized first-difference transitions: @@ -2927,8 +2940,8 @@ Localized first-difference transitions: Mismatch primary-class distribution: - `comparison_method`: 10 -- `corpus_suspect`: 43 -- `pending_rule_review`: 573 +- `corpus_suspect`: 44 +- `pending_rule_review`: 569 - `unsupported_character_review`: 6 Representative `exact` samples: @@ -2985,11 +2998,11 @@ Representative `mismatch` samples: Of the 952 candidates, 660 are the actual `pending_rule_review` subcluster. The other 292 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. -For this output-signature audit, 681 mismatches were evaluable and 189 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. +For this output-signature audit, 681 mismatches were evaluable and 190 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. Localized first-difference transitions: -- `U+2820 ⠠ -> U+2830 ⠰`: 183 +- `U+2820 ⠠ -> U+2830 ⠰`: 184 - `U+2830 ⠰ -> U+2820 ⠠`: 6 Mismatch primary-class distribution: @@ -3065,9 +3078,9 @@ Representative `mismatch` samples: ### `uppercase_ascii_segments_joined_by_ampersand_capitalization` -Of the 439 candidates, 96 are the actual `pending_rule_review` subcluster. The other 343 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 439 candidates, 95 are the actual `pending_rule_review` subcluster. The other 344 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. -For this output-signature audit, 119 mismatches were evaluable and 1 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. +For this output-signature audit, 118 mismatches were evaluable and 1 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. Localized first-difference transitions: @@ -3077,7 +3090,7 @@ Mismatch primary-class distribution: - `comparison_method`: 11 - `corpus_suspect`: 10 -- `pending_rule_review`: 96 +- `pending_rule_review`: 95 - `unsupported_character_review`: 2 Representative `exact` samples: @@ -3132,55 +3145,55 @@ Representative `mismatch` samples: ### `uppercase_roman_headword_closed_multiword_parenthetical` -Of the 175 candidates, 146 are the actual `pending_rule_review` subcluster. The other 29 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 175 candidates, 74 are the actual `pending_rule_review` subcluster. The other 101 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. Mismatch primary-class distribution: -- `comparison_method`: 1 -- `corpus_suspect`: 5 -- `pending_rule_review`: 146 -- `unsupported_character_review`: 6 +- `comparison_method`: 5 +- `corpus_suspect`: 7 +- `pending_rule_review`: 74 +- `unsupported_character_review`: 2 Representative `exact` samples: -- `sentence_01.json` #3647: 넥슨(대표 이정헌)은 게임 서브 브랜드 ‘민트로켓’의 차기 신작 프로젝트 2종 ‘TB(Top View Battle)’와 ‘NAKWON(낙원)’의 개발 영상을 19일 공개했다. - - expected: `⠉⠝⠁⠠⠵⠦⠄⠊⠗⠙⠬⠀⠕⠨⠻⠚⠾⠠⠴⠵⠀⠈⠝⠕` - - actual: `⠉⠝⠁⠠⠵⠦⠄⠊⠗⠙⠬⠀⠕⠨⠻⠚⠾⠠⠴⠵⠀⠈⠝⠕` +- `sentence_01.json` #18: 2023년형 신제품은 매터(Matter)와 HCA(Home Connectivity Alliance) 표준을 지원하는 스마트싱스 허브(SmartThings Hub)를 기반으로 다양한 기기를 자동으로 연결하고, 제어·관리할 수 있는 것이 특징이다. + - expected: `⠼⠃⠚⠃⠉⠀⠉⠡⠚⠻⠀⠠⠟⠨⠝⠙⠍⠢⠵⠀⠑⠗⠓⠎` + - actual: `⠼⠃⠚⠃⠉⠀⠉⠡⠚⠻⠀⠠⠟⠨⠝⠙⠍⠢⠵⠀⠑⠗⠓⠎` - current primary/reason: `exact` / `exact` - `sentence_02.json` #313: 순천향대(총장 김승우)는 지난 3일 한국국제협력단(KOICA)과 함께 우즈베키스탄의 수도 타슈겐트에서 스타트업 지원센터 ‘U-ENTER(Uzbekistan Entrepreneurship Innovation Center)’ 준공식을 개최했다고 밝혔다. - expected: `⠠⠛⠰⠾⠚⠜⠶⠊⠗⠦⠄⠰⠿⠨⠶⠀⠈⠕⠢⠠⠪⠶⠍⠠` - actual: `⠠⠛⠰⠾⠚⠜⠶⠊⠗⠦⠄⠰⠿⠨⠶⠀⠈⠕⠢⠠⠪⠶⠍⠠` - current primary/reason: `exact` / `exact` -- `sentence_03.json` #20370: 포르테나는 지난 20일 1theK(원더케이) 공식 유튜브 채널의 ‘MR은 거들 뿐’ 콘텐츠를 통해 첫 번째 미니앨범 ‘KINGDOM’의 타이틀곡 ‘KINGDOM(Moonlight Tears)’라이브 영상을 공개했다. - - expected: `⠙⠥⠐⠪⠓⠝⠉⠉⠵⠀⠨⠕⠉⠒⠀⠼⠃⠚⠕⠂⠀⠼⠁⠴` - - actual: `⠙⠥⠐⠪⠓⠝⠉⠉⠵⠀⠨⠕⠉⠒⠀⠼⠃⠚⠕⠂⠀⠼⠁⠴` +- `sentence_03.json` #8513: GITEX(Gulf Information Technology Exhibition)는 1981년부터 개최된 중동 최대의 ICT(정보통신기술) 전시회다. 우리나라에서는 강도현 과기정통부 정보통신정책실장을 단장으로, 과기정통부와 정보통신산업진흥원(NIPA), 한국소프트웨서산업협회(KOSA)와 60개의 국내 디지털 기업들이 참여 + - expected: `⠴⠠⠠⠛⠊⠞⠑⠭⠐⠣⠠⠛⠥⠇⠋⠀⠠⠔⠿⠍⠁⠰⠝⠀` + - actual: `⠴⠠⠠⠛⠊⠞⠑⠭⠐⠣⠠⠛⠥⠇⠋⠀⠠⠔⠿⠍⠁⠰⠝⠀` - current primary/reason: `exact` / `exact` -- `sentence_04.json` #2928: 이밖에 미국에서 많은 구독자와 공신력 있는 외식 전문지인 ‘QSR(Quick Service Restaurant)’ 매거진과 ‘매쉬드(Mashed)’를 통해 K-치킨의 대표 브랜드로 소개된 바 있다. - - expected: `⠕⠘⠁⠁⠝⠀⠑⠕⠈⠍⠁⠝⠠⠎⠀⠑⠒⠴⠵⠀⠈⠍⠊⠭` - - actual: `⠕⠘⠁⠁⠝⠀⠑⠕⠈⠍⠁⠝⠠⠎⠀⠑⠒⠴⠵⠀⠈⠍⠊⠭` +- `sentence_04.json` #2384: 마술사들의 등용문인 국제마술대회는 국내 최초로 세계마술연맹(FISM)의 인증을 받은 ‘FISM QC(Qualified Contest) BIMF’라는 이름으로 더욱 특별하게 진행된다. + - expected: `⠑⠠⠯⠇⠊⠮⠺⠀⠊⠪⠶⠬⠶⠑⠛⠟⠀⠈⠍⠁⠨⠝⠑⠠` + - actual: `⠑⠠⠯⠇⠊⠮⠺⠀⠊⠪⠶⠬⠶⠑⠛⠟⠀⠈⠍⠁⠨⠝⠑⠠` - current primary/reason: `exact` / `exact` Representative `mismatch` samples: -- `sentence_01.json` #18: 2023년형 신제품은 매터(Matter)와 HCA(Home Connectivity Alliance) 표준을 지원하는 스마트싱스 허브(SmartThings Hub)를 기반으로 다양한 기기를 자동으로 연결하고, 제어·관리할 수 있는 것이 특징이다. - - expected: `⠁⠞⠞⠻⠠⠴⠧⠀⠴⠠⠠⠓⠉⠁⠐⠣⠠⠓⠕⠍⠑⠀⠠⠒` - - actual: `⠁⠞⠞⠻⠠⠴⠧⠀⠀⠠⠠⠓⠉⠁⠦⠠⠓⠕⠍⠑⠀⠴⠠⠒` - - first differing cell (zero-based): 37 +- `sentence_01.json` #168: 제주항공은 국제항공운송협회(IATA)가 주관하는 국제 항공운송 표준 감사 제도 ‘IOSA(IATA Operation Safety Audit) ISM 14th Edition’ 인증을 마쳐 세계 기준의 안전 관리시스템을 입증받았다고 6일 밝혔다. + - expected: `⠠⠊⠎⠍⠀⠼⠁⠙⠞⠓⠀⠠⠫⠊⠰⠝⠴⠄⠀⠟⠨⠪⠶⠮` + - actual: `⠠⠊⠎⠍⠀⠼⠁⠙⠐⠞⠓⠀⠠⠫⠊⠰⠝⠴⠄⠀⠟⠨⠪⠶` + - first differing cell (zero-based): 129 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` - `sentence_02.json` #733: 아산시는 6일, 자매도시인 말레이시아 현지 최대 신선 과실류 수입업체인 CTG(Chop Tong Guan)와 농특산물 수출 확대를 위한 업무협약(MOU)을 체결했다. - - expected: `⠕⠃⠎⠃⠰⠝⠟⠀⠴⠠⠠⠉⠞⠛⠐⠣⠠⠡⠕⠏⠀⠠⠞⠰` - - actual: `⠕⠃⠎⠃⠰⠝⠟⠀⠀⠠⠠⠉⠞⠛⠦⠠⠉⠓⠕⠏⠀⠴⠠⠞` - - first differing cell (zero-based): 65 + - expected: `⠜⠁⠦⠄⠴⠠⠠⠍⠕⠥⠠⠴⠮⠀⠰⠝⠈⠳⠚⠗⠌⠊⠲` + - actual: `⠜⠁⠦⠄⠴⠠⠠⠍⠳⠠⠴⠮⠀⠰⠝⠈⠳⠚⠗⠌⠊⠲` + - first differing cell (zero-based): 136 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` - `sentence_03.json` #3330: 앞서 양사는 지난 3월 그린수소·암모니아의 원활한 생산·공급·활용을 위한 특수목적법인(SPC) 알 파탄 엘텍유브이씨 그린에너지 LLC(AL FATTAN LTechUVC Green Energy LLC)를 설립한 바 있다. - - expected: `⠐⠟⠝⠉⠎⠨⠕⠀⠴⠰⠠⠠⠇⠇⠉⠐⠣⠰⠠⠠⠁⠇⠀⠠` - - actual: `⠐⠟⠝⠉⠎⠨⠕⠀⠀⠠⠠⠇⠇⠉⠦⠠⠠⠁⠇⠀⠴⠠⠠⠠` - - first differing cell (zero-based): 128 + - expected: `⠟⠝⠉⠎⠨⠕⠀⠴⠰⠠⠠⠇⠇⠉⠐⠣⠰⠠⠠⠁⠇⠀⠠⠠` + - actual: `⠟⠝⠉⠎⠨⠕⠀⠴⠠⠠⠠⠇⠇⠉⠐⠣⠰⠠⠠⠁⠇⠀⠋⠁` + - first differing cell (zero-based): 129 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_04.json` #2384: 마술사들의 등용문인 국제마술대회는 국내 최초로 세계마술연맹(FISM)의 인증을 받은 ‘FISM QC(Qualified Contest) BIMF’라는 이름으로 더욱 특별하게 진행된다. - - expected: `⠊⠎⠍⠀⠠⠠⠟⠉⠐⠣⠠⠟⠥⠁⠇⠊⠋⠊⠫⠀⠠⠒⠞⠑` - - actual: `⠊⠎⠍⠀⠠⠠⠟⠉⠦⠠⠟⠥⠁⠇⠊⠋⠊⠑⠙⠀⠠⠒⠞⠑` - - first differing cell (zero-based): 93 +- `sentence_04.json` #6699: 한국가스공사(사장 최연혜)는 지난 2019년부터 에너지 효율 개선을 위한 EERS(Energy Efficiency Resource Standards) 사업을 추진해 약 7만5000메가와트(MW)의 에너지를 절감했다고 밝혔다. + - expected: `⠗⠚⠒⠀⠴⠠⠠⠑⠑⠗⠎⠐⠣⠠⠢⠻⠛⠽⠀⠠⠑⠖⠊⠉` + - actual: `⠗⠚⠒⠀⠴⠠⠠⠑⠻⠎⠐⠣⠠⠢⠻⠛⠽⠀⠠⠑⠖⠊⠉⠊` + - first differing cell (zero-based): 71 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` ### `uppercase_roman_run_followed_by_hyphen_digits` @@ -3244,9 +3257,9 @@ Representative `mismatch` samples: ### `uppercase_word_after_whitespace_continuing_ascii_roman_text` -Of the 1729 candidates, 890 are the actual `pending_rule_review` subcluster. The other 839 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 1729 candidates, 878 are the actual `pending_rule_review` subcluster. The other 851 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. -For this output-signature audit, 969 mismatches were evaluable and 4 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. +For this output-signature audit, 958 mismatches were evaluable and 4 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. Localized first-difference transitions: @@ -3257,8 +3270,8 @@ Localized first-difference transitions: Mismatch primary-class distribution: - `comparison_method`: 9 -- `corpus_suspect`: 59 -- `pending_rule_review`: 890 +- `corpus_suspect`: 60 +- `pending_rule_review`: 878 - `unsupported_character_review`: 11 Representative `exact` samples: @@ -3328,9 +3341,9 @@ These cohorts are defined by both an input boundary and the sentence's actual fi | Cohort | Candidates | Exact controls | Mismatch | Target localized | Reverse | |---|---:|---:|---:|---:|---:| -| `allcaps_roman_run_beginning_with_pure_letter_shortform` | 4120 | 2516 | 1604 | 788 | 48 | -| `uppercase_ascii_run_immediately_after_digit_in_roman_sequence` | 1896 | 1264 | 632 | 0 | 2 | -| `uppercase_ascii_run_immediately_after_hyphen_in_roman_sequence` | 952 | 271 | 681 | 183 | 6 | +| `allcaps_roman_run_beginning_with_pure_letter_shortform` | 4120 | 2521 | 1599 | 789 | 48 | +| `uppercase_ascii_run_immediately_after_digit_in_roman_sequence` | 1896 | 1267 | 629 | 0 | 2 | +| `uppercase_ascii_run_immediately_after_hyphen_in_roman_sequence` | 952 | 271 | 681 | 184 | 6 | | `pure_allcaps_segment_before_hyphen_and_multi_allcaps_segment_after` | 448 | 226 | 222 | 0 | 3 | ### All-caps shortform prefix at an attached Roman entry @@ -3353,8 +3366,8 @@ Same-surface controls demonstrate why primary classes must not be changed by coh | Surface | Candidates | Exact | Mismatch | Target-localized | |---|---:|---:|---:|---:| -| `AC` | 159 | 88 | 71 | 66 | -| `LLM` | 176 | 2 | 174 | 151 | +| `AC` | 159 | 89 | 70 | 66 | +| `LLM` | 176 | 2 | 174 | 152 | | `CD` | 65 | 30 | 35 | 12 | | `IMM` | 27 | 3 | 24 | 22 | | `AG` | 18 | 9 | 9 | 0 | @@ -3388,7 +3401,7 @@ Korean rule 29 requires a Roman indicator before Roman text in a Korean sentence | Cohort | Candidates | Exact controls | Mismatch | Pending | Corpus suspect | Localized `⠴ -> blank` | Reverse `blank -> ⠴` | |---|---:|---:|---:|---:|---:|---:|---:| | `roman_hyphenated_word_after_whitespace_following_korean_word` | 361 | 86 | 275 | 264 | 6 | 139 | 0 | -| `roman_parenthetical_headword_after_whitespace_following_korean_word` | 4695 | 3550 | 1145 | 1051 | 41 | 159 | 0 | +| `roman_parenthetical_headword_after_whitespace_following_korean_word` | 4695 | 3610 | 1085 | 989 | 43 | 67 | 0 | | `korean_prefixed_roman_parenthetical_followed_by_allcaps_hyphen_suffix` | 11 | 0 | 11 | 6 | 5 | 5 | 0 | The whitespace parenthetical-headword cohort also retains 1 localized `⠠ -> ⠴` and 3 localized `⠴ -> ⠰` cases as separate transitions; they are not folded into the target. The exact controls demonstrate that the broad structure is already correct in many sentences, while the attached parenthetical-hyphen cohort has no exact control and includes cases already classified by the stricter rule-34 reference-order contradiction. Consequently none of these measurements authorizes an engine change; they are deterministic pending/corpus-review diagnostics only. @@ -3399,22 +3412,22 @@ Korean rule 29 explicitly says that when two or more Roman items occur consecuti Analyzer checkpoint `266e70c` fixed the pre-change baseline at 1,729 candidates, 386 exact controls, 1,343 mismatches, 1,272 pending members, and 385/1,343 evaluable mismatches localized to the current re-entry signature. Those localized transitions were 365 `⠠ -> ⠴`, 16 `⠰ -> ⠴`, no reverse `⠴ -> ⠠`, and four other transitions. Exact controls include contexts where the first Roman word already opened token-level mode, while parenthesized/mixed-token examples expose the duplicate event. -The generalized fix makes an explicit `EnterEnglish` event idempotent when final emit state is already inside a Roman section; it neither names an input nor changes a new section's entry. The current measurement is 1729 candidates, 760 exact controls, 969 mismatches, 890 pending members, and 4/969 localized mismatches. Current target counts are 0 `⠠ -> ⠴`, 0 `⠰ -> ⠴`, and 0 reverse `⠴ -> ⠠`. Cohort exact controls increase by 275, while corpus-wide exact matches rise from 67,138 to 67,442 (+304); corrected prefixes that still differ later remain mismatches, and applications outside this strict input gate account for the remaining net gain. The raw/residual reverse `⠴ -> ⠠` totals remain 68/65 before and after the change. The complete custom standard summary is 5,141 total, 5,141 success, 0 failure, and 0 skipped. +The generalized fix makes an explicit `EnterEnglish` event idempotent when final emit state is already inside a Roman section; it neither names an input nor changes a new section's entry. The current measurement is 1729 candidates, 771 exact controls, 958 mismatches, 878 pending members, and 4/958 localized mismatches. Current target counts are 0 `⠠ -> ⠴`, 0 `⠰ -> ⠴`, and 0 reverse `⠴ -> ⠠`. Cohort exact controls increase by 275, while corpus-wide exact matches rise from 67,138 to 67,442 (+304); corrected prefixes that still differ later remain mismatches, and applications outside this strict input gate account for the remaining net gain. The raw/residual reverse `⠴ -> ⠠` totals remain 68/65 before and after the change. The complete custom standard summary is 5,141 total, 5,141 success, 0 failure, and 0 skipped. ### Closed Roman parenthetical after a non-ASCII-letter boundary Korean rule 34 (2024 Korean-rules PDF p.29) prints the opening enclosure before Roman entry in `링컨(Lincoln)`: the opening sequence is `⠦⠄⠴`. This output-position cohort finds a closed, non-nested parenthetical whose body begins with an ASCII letter and whose opening does not immediately follow another ASCII letter, then locates its complete current-engine signature without consulting the reference. Its localized boundary includes the two current output cells immediately before the opening plus the first three entry cells, so rule-11 math spacing can be separated from a difference later inside the parenthetical. Direct function-call shapes such as `f(x)` are excluded. The PDF's math rule 6 (p.59), rule 12 (pp.63-64), and rule 45 (p.75) nevertheless leave standalone `(x)` and other Roman-letter parenthetical mathematics as counterexamples, so the surface gate is not an engine-routing predicate. -The cross-cutting input cohort contains 63959 candidates: 53056 exact controls and 10903 mismatches. Mismatch primary classes remain unchanged: 9411 `pending_rule_review`, 1112 `corpus_suspect`, 238 `comparison_method`, and 142 `unsupported_character_review`. Of 10903 evaluable mismatches, 1130 have the first difference at the detected leading-spacing/entry boundary; these include 438 `U+2826 ⠦ -> U+2834 ⠴`, 28 `U+2834 ⠴ -> U+2826 ⠦`, and 330 `U+2826 ⠦ -> U+2800 ⠀` transitions; the exact localized reverse `U+2800 ⠀ -> U+2826 ⠦` occurs 0 times. After all earlier localized cohorts and this cohort are excluded, the raw-to-residual target count is 471 -> 26, the Roman-indicator reverse count is 33 -> 2, and the spacing target/reverse counts are 445 -> 79 and 1 -> 1. The short full-encoder form `웹3(Web3)` emits the PDF opening order as a routing control, while whitespace and quote boundaries reproduce the current Roman-first opening. Representative localized samples, with shard and index, are retained in the generated cluster sample table. Because exact controls are abundant and the PDF does not make this input shape semantically sufficient to exclude mathematics, no engine change or primary reclassification is inferred. The one raw/residual spacing reverse is a Korean-body `△(교육)` layout boundary, not a Roman-parenthetical member, and remains a separate rule-72/layout review. +The cross-cutting input cohort contains 63959 candidates: 53122 exact controls and 10837 mismatches. Mismatch primary classes remain unchanged: 9343 `pending_rule_review`, 1114 `corpus_suspect`, 242 `comparison_method`, and 138 `unsupported_character_review`. Of 10837 evaluable mismatches, 1130 have the first difference at the detected leading-spacing/entry boundary; these include 438 `U+2826 ⠦ -> U+2834 ⠴`, 28 `U+2834 ⠴ -> U+2826 ⠦`, and 330 `U+2826 ⠦ -> U+2800 ⠀` transitions; the exact localized reverse `U+2800 ⠀ -> U+2826 ⠦` occurs 0 times. After all earlier localized cohorts and this cohort are excluded, the raw-to-residual target count is 471 -> 26, the Roman-indicator reverse count is 33 -> 2, and the spacing target/reverse counts are 445 -> 79 and 1 -> 1. The short full-encoder form `웹3(Web3)` emits the PDF opening order as a routing control, while whitespace and quote boundaries reproduce the current Roman-first opening. Representative localized samples, with shard and index, are retained in the generated cluster sample table. Because exact controls are abundant and the PDF does not make this input shape semantically sufficient to exclude mathematics, no engine change or primary reclassification is inferred. The one raw/residual spacing reverse is a Korean-body `△(교육)` layout boundary, not a Roman-parenthetical member, and remains a separate rule-72/layout review. -The HCA-style headword-expansion shape described above is not an engine implementation premise. The 2024 PDF's math rule 6 defines parentheses and grouping parentheses, rule 11 defines mathematical-expression spacing, rule 12 covers Roman letters in formulas as well as Korean sentences, and rule 45 shows Roman-letter function notation followed by parentheses. Excluding visible operators, scripts, and nesting narrows this corpus cohort, but the PDF does not make the remaining surface shape sufficient to rule out every mathematical counterexample. The cluster therefore remains conservative pending-review evidence only. +The HCA-style headword-expansion gate now supplies one narrow prose-routing premise. Korean rules 29 and 34 require a fresh Roman section and continuous Roman transcription for a complete all-capitals headword followed by a closed, multiword Roman expansion. The implementation requires a headword of at least two ASCII capitals and at least two ASCII-letter words inside the parenthesis; digits, operators, scripts, nested brackets, and alphanumeric text after the closing parenthesis remain math-owned controls. Rule 34's `링컨(Lincoln)은` additionally proves that attached Korean text after the closing parenthesis stays on the prose route; the same boundary now covers the multiword form without admitting ASCII letters or digits in the trailer. Together these boundaries change 71 corpus cases from mismatch to exact and raise this cohort's exact controls from 17 to 87. The residual members still measure contraction, capitalization, earlier sentence differences, unsupported characters, and reference-order conflicts rather than authorizing a wider surface-form rule. The standalone-uppercase cohort is similarly ambiguous. Hangeul rule 28's appendix defines the capital-word indicator for two or more consecutive capitals, and rule 29 defines Roman indicators around Roman text in a Korean sentence. But math rule 12 also uses uppercase Roman variables, while science rule 7 uses uppercase runs in chemical formulas. The input gate cannot determine which semantic regime applies, so its output differences are observations to review, not permission to infer an engine rule from the corpus reference. The Korean-prefixed all-caps parenthetical cohort isolates that ambiguity more narrowly. Hangeul rules 28/29 require Roman and capitalization indicators for prose acronyms, while science rule 7 requires element-by-element capitals for chemical formulae. Both meanings can have the same input surface form. The observed `COO`/`NSC`/`MOU` output differences therefore do not justify disabling either algorithm without independent semantic evidence. -Two narrower cohorts separate causes hidden by the frequent `U+2834 -> U+2800` cell transition. `single_capital_followed_by_parenthesized_digits` reproduces the current math-token routing of forms such as `A(14)`: Hangeul rules 29 and 34 govern a Roman section and a parenthesized Roman form, while math rule 6 independently defines parenthesized function notation such as `f(x)`. A capital and numeric argument do not remove that mathematical counterexample, so this localized routing difference remains pending rather than authorizing an input-shape exception. `mixed_roman_korean_word_before_uppercase_headword_expansion` separately targets the next Roman headword after a mixed Roman+Korean word (for example, a Korean particle attached to the previous Roman name). Its range is anchored to that later headword, not to the earlier Roman entry. Nevertheless, the closed multiword parenthetical shape still cannot exclude every mathematical interpretation under math rules 6, 11, 12, and 45, as recorded for the broader HCA-style cohort. The headword shape is therefore not added to engine routing; the two causes and their controls remain separately measurable. +Two narrower cohorts separate causes hidden by the frequent `U+2834 -> U+2800` cell transition. `single_capital_followed_by_parenthesized_digits` reproduces the current math-token routing of forms such as `A(14)`: Hangeul rules 29 and 34 govern a Roman section and a parenthesized Roman form, while math rule 6 independently defines parenthesized function notation such as `f(x)`. A capital and numeric argument do not remove that mathematical counterexample, so this localized routing difference remains pending rather than authorizing an input-shape exception. `mixed_roman_korean_word_before_uppercase_headword_expansion` separately targets the next Roman headword after a mixed Roman+Korean word (for example, a Korean particle attached to the previous Roman name). Its range is anchored to that later headword, not to the earlier Roman entry. The narrow rules-29/34 prose gate described above is now implemented, and this cohort no longer has a missing-entry localized transition. Its remaining localized differences are later Roman-letter/contraction differences. The two causes and their controls remain separately measurable instead of widening the headword grammar. The uppercase-Roman hyphen-digits cohort is a third independent cause. Hangeul rule 35 explicitly shows `D-100` as a Roman-and-number continuation (2024 Korean-rules PDF p.29), while math rule 2 defines subtraction and the math chapters allow uppercase Roman variables. The surface form alone therefore does not prove whether `F-35` is an identifier or a subtraction expression. This cohort records the current operator-routing signature and exact controls without merging it into either `A(14)` or HCA-style diagnostics. No engine change is made without both a safe semantic boundary and exact controls. @@ -3434,33 +3447,33 @@ Corpus contradictions remain a separate gate: identical inputs with conflicting UEB 10.12.1 (2024 UEB PDF pp.191-192, printed pp.163-164) says not to use a contraction when letters within an abbreviation or acronym are known to be pronounced separately, but to use the contraction in case of doubt. Its official `DAR` example writes separate `a` and `r` cells, while UEB 10.12.2's official `START` example uses the `ar` groupsign. Both full-encoder controls pass. Thus an uppercase surface containing `AR` does not itself supply the pronunciation or lexical meaning needed to select either form. -The output-localized cohort contains 1022 candidates, 370 exact controls, and 652 mismatches. Existing mismatch primary classes are preserved: 628 `pending_rule_review`, 15 `corpus_suspect`, 3 `comparison_method`, and 6 `unsupported_character_review`. Of 652 evaluable mismatches, 407 have their first difference inside the detected current-engine run: 400 `U+2801 ⠁ -> U+281C ⠜` and 0 `U+281C ⠜ -> U+2801 ⠁`. Across raw pending transitions and the final residual after localized cohorts, the target count is 408 -> 7; the reverse is 2 -> 2. Inputs denoting separately pronounced initialisms such as corpus `AR`/`ARS` coexist with exact or officially contracted controls. This is deterministic evidence for a pronunciation-dependent pending cohort, not an engine rule or primary reclassification. Representative shard/index samples are retained above. +The output-localized cohort contains 1022 candidates, 371 exact controls, and 651 mismatches. Existing mismatch primary classes are preserved: 627 `pending_rule_review`, 15 `corpus_suspect`, 3 `comparison_method`, and 6 `unsupported_character_review`. Of 651 evaluable mismatches, 407 have their first difference inside the detected current-engine run: 400 `U+2801 ⠁ -> U+281C ⠜` and 0 `U+281C ⠜ -> U+2801 ⠁`. Across raw pending transitions and the final residual after localized cohorts, the target count is 408 -> 7; the reverse is 2 -> 2. Inputs denoting separately pronounced initialisms such as corpus `AR`/`ARS` coexist with exact or officially contracted controls. This is deterministic evidence for a pronunciation-dependent pending cohort, not an engine rule or primary reclassification. Representative shard/index samples are retained above. ### Roman run after a closed Roman enclosure Korean rule 29 places one Roman indicator/terminator pair around consecutive Roman items, with `Los Angeles` and `Table of Contents` as its printed multiword examples. Rule 34 separately omits the Roman terminator when Roman text is enclosed by quotation marks or parentheses. Neither printed rule states whether a later Roman run after the enclosure, intervening punctuation, and whitespace is a continuation of that section or a fresh section. The input gate therefore detects only the structural boundary; it does not label the later run as semantically new. -The cohort contains 1093 candidates, 143 exact controls, and 950 mismatches. Existing mismatch primary classes are preserved: 612 `pending_rule_review`, 331 `corpus_suspect`, and 7 `unsupported_character_review`. Of 950 evaluable mismatches, 260 are output-localized to the current later-run signature plus its one leading boundary cell: 256 `U+2834 ⠴ -> U+2830 ⠰` and 0 `U+2830 ⠰ -> U+2834 ⠴`. The target counted 359 raw and 333 residual cases before this cohort; it is now 48 final residual. Across raw and final residual maps, `U+2830 ⠰ -> U+2834 ⠴` is 2 -> 0. Exact controls coexist with both directions, and 321 mismatches are already independently identified corpus contradictions. Without a printed fresh-entry example or semantic enclosure model, changing continuation state would be reference-fitting; this remains a deterministic pending/corpus-review cohort only. Representative shard/index samples are retained above. +The cohort contains 1093 candidates, 144 exact controls, and 949 mismatches. Existing mismatch primary classes are preserved: 611 `pending_rule_review`, 331 `corpus_suspect`, and 7 `unsupported_character_review`. Of 949 evaluable mismatches, 260 are output-localized to the current later-run signature plus its one leading boundary cell: 256 `U+2834 ⠴ -> U+2830 ⠰` and 0 `U+2830 ⠰ -> U+2834 ⠴`. The target counted 359 raw and 333 residual cases before this cohort; it is now 48 final residual. Across raw and final residual maps, `U+2830 ⠰ -> U+2834 ⠴` is 2 -> 0. Exact controls coexist with both directions, and 321 mismatches are already independently identified corpus contradictions. Without a printed fresh-entry example or semantic enclosure model, changing continuation state would be reference-fitting; this remains a deterministic pending/corpus-review cohort only. Representative shard/index samples are retained above. ### Uppercase Roman runs containing `ED` UEB 10.12.1 (2024 UEB PDF pp.191-192, printed pp.163-164) requires separate letters when an abbreviation's letters are known to be pronounced separately, and its official `OED` example writes `e` and `d` separately. Rule 10.12.2 otherwise uses contractions; its official `BEd` example uses the `ed` groupsign. Both full-encoder controls pass. Consequently an uppercase `ED` surface cannot establish pronunciation or abbreviation semantics by spelling alone. -The cohort contains 816 candidates, 334 exact controls, and 482 mismatches. Existing mismatch primary classes remain 412 `pending_rule_review`, 64 `corpus_suspect`, 1 `comparison_method`, and 5 `unsupported_character_review`. Of 482 evaluable mismatches, 330 are localized to the detected current-engine run: 327 `U+2811 ⠑ -> U+282B ⠫` and 0 `U+282B ⠫ -> U+2811 ⠑`. Across raw pending transitions and the final residual after localized cohorts, the target is 330 -> 3 and the reverse is 0 -> 0. Corpus initialisms such as `LED` and `GED` require external pronunciation knowledge, while exact and official controls preserve contraction-bearing outcomes. No engine change or primary reclassification is made; representative shard/index samples are retained above. +The cohort contains 816 candidates, 334 exact controls, and 482 mismatches. Existing mismatch primary classes remain 411 `pending_rule_review`, 65 `corpus_suspect`, 1 `comparison_method`, and 5 `unsupported_character_review`. Of 482 evaluable mismatches, 330 are localized to the detected current-engine run: 327 `U+2811 ⠑ -> U+282B ⠫` and 0 `U+282B ⠫ -> U+2811 ⠑`. Across raw pending transitions and the final residual after localized cohorts, the target is 331 -> 3 and the reverse is 0 -> 0. Corpus initialisms such as `LED` and `GED` require external pronunciation knowledge, while exact and official controls preserve contraction-bearing outcomes. No engine change or primary reclassification is made; representative shard/index samples are retained above. ### Uppercase Roman runs containing `ST` -UEB 10.12.1 (2024 UEB PDF pp.191-192, printed pp.163-164) says not to use a contraction when letters within an abbreviation or acronym are known to be pronounced separately, but to use the contraction in case of doubt. Rule 10.12.2 requires contractions in other abbreviations and acronyms. Consequently an uppercase `ST` surface alone cannot determine whether `s` + `t` or the `st` groupsign is required. The current cohort contains 1479 candidates, 702 exact controls, and 777 mismatches; primary classes remain 724 `pending_rule_review`, 35 `corpus_suspect`, 13 `comparison_method`, and 5 `unsupported_character_review`. Of 777 evaluable mismatches, 459 are localized to the detected current-engine run: 451 `U+280E ⠎ -> U+280C ⠌` and 1 `U+280C ⠌ -> U+280E ⠎`. The target's raw-to-residual count is 464 -> 13, and the reverse is 2 -> 1. Exact controls such as `STAYG`, `KAIST`, `DGIST`, and `POSTECH` coexist with localized mismatches such as `WSTS`, `HUST`, `OST`, and `USTR`; representative shard/index samples are preserved in the cluster table. This lexical/pronunciation distinction cannot be inferred from the input-only spelling, so no engine change or primary reclassification is made. +UEB 10.12.1 (2024 UEB PDF pp.191-192, printed pp.163-164) says not to use a contraction when letters within an abbreviation or acronym are known to be pronounced separately, but to use the contraction in case of doubt. Rule 10.12.2 requires contractions in other abbreviations and acronyms. Consequently an uppercase `ST` surface alone cannot determine whether `s` + `t` or the `st` groupsign is required. The current cohort contains 1479 candidates, 703 exact controls, and 776 mismatches; primary classes remain 723 `pending_rule_review`, 35 `corpus_suspect`, 13 `comparison_method`, and 5 `unsupported_character_review`. Of 776 evaluable mismatches, 460 are localized to the detected current-engine run: 452 `U+280E ⠎ -> U+280C ⠌` and 1 `U+280C ⠌ -> U+280E ⠎`. The target's raw-to-residual count is 465 -> 13, and the reverse is 2 -> 1. Exact controls such as `STAYG`, `KAIST`, `DGIST`, and `POSTECH` coexist with localized mismatches such as `WSTS`, `HUST`, `OST`, and `USTR`; representative shard/index samples are preserved in the cluster table. This lexical/pronunciation distinction cannot be inferred from the input-only spelling, so no engine change or primary reclassification is made. ### Uppercase segments joined by ampersand: capitalization extent UEB 8.4.2 (2024 UEB PDF p.118, printed p.90) terminates capitals word mode at a nonalphabetic symbol. UEB 3.1.1 and the capitalization examples (PDF pp.51 and 120, printed pp.23 and 92) consequently print `AT&T` as `⠠⠠⠁⠞⠈⠯⠠⠞` and `B&B` as `⠠⠃⠈⠯⠠⠃`: Roman mode remains continuous, but capitalization restarts for each ASCII-letter segment. The detector accepts only complete uppercase ASCII segments joined directly by `&`, with the same non-alphanumeric outer boundaries as the existing Roman-ampersand rule, and requires the run to begin its whitespace-delimited token. Korean-attached and punctuation-prefixed occurrences stay outside the change scope. -At the analyzer-only checkpoint this cohort contained 439 candidates, 1 exact control, 438 mismatches, and 220 first differences localized inside the independently reproduced Korean-context signature. After the general capitalization correction it contains 439 candidates, 320 exact controls, and 119 mismatches. Existing remaining mismatch primaries are 96 `pending_rule_review`, 10 `corpus_suspect`, 11 `comparison_method`, and 2 `unsupported_character_review`. Of 119 evaluable mismatches, 1 have their first difference inside that signature. The sole pre-change exact member contained lowercase Roman text later in the same whitespace token and was outside the production predicate's actual change scope; its primary outcome was preserved. The cohort table above retains the transition distribution and shard/index samples. Capitalization extent is fixed by the official symbol examples and requires neither pronunciation nor corpus semantics; the diagnostic never changes a primary class. +At the analyzer-only checkpoint this cohort contained 439 candidates, 1 exact control, 438 mismatches, and 220 first differences localized inside the independently reproduced Korean-context signature. After the general capitalization correction it contains 439 candidates, 321 exact controls, and 118 mismatches. Existing remaining mismatch primaries are 95 `pending_rule_review`, 10 `corpus_suspect`, 11 `comparison_method`, and 2 `unsupported_character_review`. Of 118 evaluable mismatches, 1 have their first difference inside that signature. The sole pre-change exact member contained lowercase Roman text later in the same whitespace token and was outside the production predicate's actual change scope; its primary outcome was preserved. The cohort table above retains the transition distribution and shard/index samples. Capitalization extent is fixed by the official symbol examples and requires neither pronunciation nor corpus semantics; the diagnostic never changes a primary class. ### Capitals-word nonletter change-scope audit -This input-only scope exactly mirrors the former token predicate where it could incorrectly carry capitals-word handling across a nonletter. UEB 8.4.2 terminates that mode at the nonletter. Trailing nonletters after the final uppercase run are excluded because their output is unchanged. Before the correction all 1,733 candidates were mismatches and none was exact. The current run has 1733 candidates, 852 exact controls, and 881 mismatches. A complete exact-ID set audit found 852 newly exact cases and zero cases lost from the 68,439-exact baseline, yielding 69,291/83,528 (82.96%). Two deliberately rejected wider predicates exposed why the boundary matters: requiring an entirely uppercase-only token lost 87 former exact cases, while treating every initial uppercase run as token-level capitals mode lost 33 mixed-case controls such as the UEB `TVOntario` class by bypassing its required capitals terminator. The retained predicate pre-emits only when the initial run has at least two capitals and every ASCII letter in the token is uppercase; Rule 28 independently restarts capitalization after the nonletter. This cohort remains a regression audit only: membership does not assign a primary class or attribute a first difference. +This input-only scope exactly mirrors the former token predicate where it could incorrectly carry capitals-word handling across a nonletter. UEB 8.4.2 terminates that mode at the nonletter. Trailing nonletters after the final uppercase run are excluded because their output is unchanged. Before the correction all 1,733 candidates were mismatches and none was exact. The current run has 1733 candidates, 854 exact controls, and 879 mismatches. A complete exact-ID set audit found 852 newly exact cases and zero cases lost from the 68,439-exact baseline, yielding 69,291/83,528 (82.96%). Two deliberately rejected wider predicates exposed why the boundary matters: requiring an entirely uppercase-only token lost 87 former exact cases, while treating every initial uppercase run as token-level capitals mode lost 33 mixed-case controls such as the UEB `TVOntario` class by bypassing its required capitals terminator. The retained predicate pre-emits only when the initial run has at least two capitals and every ASCII letter in the token is uppercase; Rule 28 independently restarts capitalization after the nonletter. This cohort remains a regression audit only: membership does not assign a primary class or attribute a first difference. ### Attached Roman segments joined by ampersand @@ -3470,7 +3483,7 @@ The baseline was 802 candidates / 0 exact / 802 mismatch, preserving 776 `pendin The implemented gate shares the analyzer's complete-run predicate: one or more non-empty ASCII-letter segments joined directly by `&`, with non-alphanumeric outer boundaries. It keeps the existing Roman mode open and suppresses only rule 71's redundant wrapper around that ampersand. Spaced `Marks & Spencer`, Korean `가&나`, empty segments, and outer digit continuations remain outside the gate. Official full-encoder controls `AT&T` and `B&B` pass, and the Korean rule-71 spaced example `종이접기 & 클레이아트` retains its independent `⠴⠈⠯⠲` section. -After the change the same cohort contains 802 candidates, 607 exact and 195 mismatch. Current mismatch primary classes remain evaluator-owned: 167 `pending_rule_review`, 11 `corpus_suspect`, 2 `unsupported_character_review`, and 15 `comparison_method`. The localizer evaluates all 195 remaining mismatches and finds 0 target-localized cases (0 `U+2808 ⠈ -> U+2832 ⠲`); current raw-to-residual target count is 81 -> 79, while `U+2832 ⠲ -> U+2808 ⠈` remains 0 raw / 0 residual. Cohort exact increases by 273 and corpus-wide exact increases by the same 273, from 67,442 to 67,715. Because every changed input must satisfy this shared predicate and the baseline had no exact member, this boundary has no exact regression. The remaining 529 candidates differ elsewhere or retain an existing comparison, corpus-suspect, unsupported, or pending cause. Representative shard/index samples are retained above. +After the change the same cohort contains 802 candidates, 608 exact and 194 mismatch. Current mismatch primary classes remain evaluator-owned: 166 `pending_rule_review`, 11 `corpus_suspect`, 2 `unsupported_character_review`, and 15 `comparison_method`. The localizer evaluates all 194 remaining mismatches and finds 0 target-localized cases (0 `U+2808 ⠈ -> U+2832 ⠲`); current raw-to-residual target count is 81 -> 79, while `U+2832 ⠲ -> U+2808 ⠈` remains 0 raw / 0 residual. Cohort exact increases by 273 and corpus-wide exact increases by the same 273, from 67,442 to 67,715. Because every changed input must satisfy this shared predicate and the baseline had no exact member, this boundary has no exact regression. The remaining 529 candidates differ elsewhere or retain an existing comparison, corpus-suspect, unsupported, or pending cause. Representative shard/index samples are retained above. ### Ampersand before an attached ASCII Roman segment @@ -3480,7 +3493,7 @@ The diagnostic checkpoint baseline was 30 candidates / 0 corpus exact / 30 misma The implemented rule is limited to an ampersand followed by a complete attached ASCII-letter segment, with no left ASCII alphanumeric or adjacent ampersand and no trailing digit continuation. Rule 71 opens one Roman section before `&`; rule 29 now leaves it open for the attached letters. It does not name a corpus input or inspect a reference. After the change, the cohort has 30 candidates, 12 exact and 18 mismatch; the corpus-wide exact total rises by the same 12 cases, from 68,175 to 68,187, so no exact regression occurs inside or outside this gate. The 16 former exit/re-entry transitions disappear; 12 become exact and 4 remain mismatches at a different PDF-conflicting boundary. Existing mismatch primary classes remain 14 `pending_rule_review`, 4 `corpus_suspect`, 0 `comparison_method`, and 0 `unsupported_character_review`. -Of 18 current evaluable mismatches, 4 are localized to the occurrence-specific entry signature: 3 `U+2808 ⠈ -> U+2834 ⠴` where a parenthesized `&TEAM` reference omits Rule 71's required Roman indicator, and 1 `U+2834 ⠴ -> U+2820 ⠠` where a `드림&Dream` reference inserts another Roman indicator inside the same continuous section. Their raw-to-residual counts are 7 -> 4 and 70 -> 64; the corresponding reverse maps are 0 -> 0 and 56 -> 38. These four cases remain conservative corpus/PDF-reference review rather than widening or undoing the rule. Exact samples such as `&TEAM`, `과학&ICT`, and `한국&K리츠`, with shard/index above, are current controls. Primary classifications are never changed by this cohort. +Of 18 current evaluable mismatches, 4 are localized to the occurrence-specific entry signature: 3 `U+2808 ⠈ -> U+2834 ⠴` where a parenthesized `&TEAM` reference omits Rule 71's required Roman indicator, and 1 `U+2834 ⠴ -> U+2820 ⠠` where a `드림&Dream` reference inserts another Roman indicator inside the same continuous section. Their raw-to-residual counts are 7 -> 4 and 61 -> 55; the corresponding reverse maps are 0 -> 0 and 56 -> 38. These four cases remain conservative corpus/PDF-reference review rather than widening or undoing the rule. Exact samples such as `&TEAM`, `과학&ICT`, and `한국&K리츠`, with shard/index above, are current controls. Primary classifications are never changed by this cohort. ### ASCII apostrophe between Roman letter runs @@ -3496,7 +3509,7 @@ This output-localized cohort requires a comma immediately after an ASCII digit, The diagnostic baseline was 217 candidates / 7 exact / 210 mismatch, with 177 occurrence-specific `U+2810 ⠐ -> U+2802 ⠂` first differences and no localized reverse. Rule 41 had looked through `remaining_words`, incorrectly treating whitespace as if the following digit were attached. The implementation now inspects only the next character in the same token. It neither names a corpus input nor consults expected output; attached numbers and UEB punctuation remain owned by their existing routes. -After the correction, the cohort has 217 candidates / 182 exact / 35 mismatch. Existing mismatch primaries remain 34 `pending_rule_review`, 0 `corpus_suspect`, 1 `comparison_method`, and 0 `unsupported_character_review`. Of 35 evaluable current mismatches, 0 localize to the comma cell: 0 `U+2810 ⠐ -> U+2802 ⠂` and 0 `U+2802 ⠂ -> U+2810 ⠐`. The cohort gains 174 exact cases. Across all pending cases, the current raw-to-residual counts are 24 -> 6 for the target and 21 -> 15 for the reverse. The standard controls `9,375명`, `창세기 12,1-9`, and `근면, 검소, 협동은 ...` retain their PDF cells. No primary class is changed by the cohort. +After the correction, the cohort has 217 candidates / 182 exact / 35 mismatch. Existing mismatch primaries remain 34 `pending_rule_review`, 0 `corpus_suspect`, 1 `comparison_method`, and 0 `unsupported_character_review`. Of 35 evaluable current mismatches, 0 localize to the comma cell: 0 `U+2810 ⠐ -> U+2802 ⠂` and 0 `U+2802 ⠂ -> U+2810 ⠐`. The cohort gains 174 exact cases. Across all pending cases, the current raw-to-residual counts are 24 -> 6 for the target and 22 -> 16 for the reverse. The standard controls `9,375명`, `창세기 12,1-9`, and `근면, 검소, 협동은 ...` retain their PDF cells. No primary class is changed by the cohort. ### ASCII/Roman-tail comma before a digit-led Korean token @@ -3522,15 +3535,15 @@ After removing that input-correcting transformation, the cohort has 95 candidate Current uppercase-Roman hyphen-digits measurement: 571 candidates, 194 exact controls, 377 mismatches, 362 members in the actual `pending_rule_review` subcluster, and 262/377 evaluable mismatches whose first difference is inside the target run plus its entry boundary. It remains distinct from parenthesized digits and headword expansions; no engine change is inferred. -Current uppercase alphanumeric Roman-digit sequence measurement: 3429 candidates, 2096 exact controls, 1333 mismatches, 1223 members in the actual `pending_rule_review` subcluster, and 150/1333 evaluable mismatches whose first difference is localized to the current entry cells. The target `⠴ -> blank` occurs 95 times and the reverse occurs 0 times. Korean rule 35 (2024 Korean-rules PDF pp.29-30, printed pp.23-24) explicitly treats `MP3`, `A4`, `KF94`, and `D-100` as Roman-number sequences. Math rules 11/12 (PDF pp.63-65, printed pp.57-59) separately route mathematical Roman notation without the prose Roman indicator and with two-cell spacing. Official UEB 6.5.1-6.5.2 (2024 UEB PDF p.96, printed p.68) determines numeric/grade-1 continuation only after the surrounding mode has been chosen. Thus surfaces such as `A-3` cannot be assigned identifier or variable semantics from this structure alone. Exact controls and both directions are retained; no engine change is inferred. +Current uppercase alphanumeric Roman-digit sequence measurement: 3429 candidates, 2097 exact controls, 1332 mismatches, 1221 members in the actual `pending_rule_review` subcluster, and 150/1332 evaluable mismatches whose first difference is localized to the current entry cells. The target `⠴ -> blank` occurs 95 times and the reverse occurs 0 times. Korean rule 35 (2024 Korean-rules PDF pp.29-30, printed pp.23-24) explicitly treats `MP3`, `A4`, `KF94`, and `D-100` as Roman-number sequences. Math rules 11/12 (PDF pp.63-65, printed pp.57-59) separately route mathematical Roman notation without the prose Roman indicator and with two-cell spacing. Official UEB 6.5.1-6.5.2 (2024 UEB PDF p.96, printed p.68) determines numeric/grade-1 continuation only after the surrounding mode has been chosen. Thus surfaces such as `A-3` cannot be assigned identifier or variable semantics from this structure alone. Exact controls and both directions are retained; no engine change is inferred. -Current consecutive ASCII-Roman word-boundary measurement: 4679 candidates, 2287 exact controls, 2392 mismatches, 2256 members in the actual `pending_rule_review` subcluster, and 26/2392 evaluable mismatches whose first difference is localized to the current boundary cell. The target reference blank versus current terminator (`⠀ -> ⠲`) occurs 26 times; the exact reverse (`⠲ -> ⠀`) occurs 0 times. Korean rule 29 (2024 Korean-rules PDF p.26, printed p.20) explicitly treats consecutive Roman items as one Roman section, with one Roman indicator before the first item and one terminator after the last; its printed `Los Angeles` and `Table of Contents` examples are exact controls. Rule 34 (PDF p.29, printed p.23) separately omits the terminator before a closing enclosure, while official UEB 9.7.1 (2024 UEB PDF p.137, printed p.109) prints the multiword prose enclosure `plays (such as Romeo and Juliet)` with ordinary internal spaces and nested closing punctuation. Together they support preserving a complete letter-and-space Roman parenthetical as prose, but not a fragment containing a function-call opener, digit, operator, or nested bracket. The input-only gate does not decide whether punctuation-separated text such as `ESS /VPP` continues the same section, so that variant remains outside this cohort. Primary classes are preserved. Before the narrow token-routing change this cohort was 4,679 candidates / 2,058 exact / 2,621 mismatch, with 128 localized `⠀ -> ⠲` and zero reverse. The cause was the final `Letters)` token of a whitespace-split Roman parenthetical being rerouted as math, which made the preceding Roman word terminate early and introduced an extra blank. After excluding only a backwards-verified complete Roman parenthetical tail from math routing, the cohort is 2,132 exact / 2,547 mismatch, with 23 target-localized and zero reverse; corpus-wide exact likewise rises by 74, from 68,101 to 68,175. The 105 removed localized boundaries include 74 newly exact cases and 31 cases that still differ elsewhere. Remaining targets such as `SYNO PEM-1`, `ACE Fair(2020)`, and `Mnet K-POP` do not satisfy the complete-parenthetical gate and remain separate residuals rather than widening this rule. +Current consecutive ASCII-Roman word-boundary measurement: 4679 candidates, 2358 exact controls, 2321 mismatches, 2183 members in the actual `pending_rule_review` subcluster, and 25/2321 evaluable mismatches whose first difference is localized to the current boundary cell. The target reference blank versus current terminator (`⠀ -> ⠲`) occurs 25 times; the exact reverse (`⠲ -> ⠀`) occurs 0 times. Korean rule 29 (2024 Korean-rules PDF p.26, printed p.20) explicitly treats consecutive Roman items as one Roman section, with one Roman indicator before the first item and one terminator after the last; its printed `Los Angeles` and `Table of Contents` examples are exact controls. Rule 34 (PDF p.29, printed p.23) separately omits the terminator before a closing enclosure, while official UEB 9.7.1 (2024 UEB PDF p.137, printed p.109) prints the multiword prose enclosure `plays (such as Romeo and Juliet)` with ordinary internal spaces and nested closing punctuation. Together they support preserving a complete letter-and-space Roman parenthetical as prose, but not a fragment containing a function-call opener, digit, operator, or nested bracket. The input-only gate does not decide whether punctuation-separated text such as `ESS /VPP` continues the same section, so that variant remains outside this cohort. Primary classes are preserved. Before the narrow token-routing change this cohort was 4,679 candidates / 2,058 exact / 2,621 mismatch, with 128 localized `⠀ -> ⠲` and zero reverse. The cause was the final `Letters)` token of a whitespace-split Roman parenthetical being rerouted as math, which made the preceding Roman word terminate early and introduced an extra blank. After excluding only a backwards-verified complete Roman parenthetical tail from math routing, the cohort is 2,132 exact / 2,547 mismatch, with 23 target-localized and zero reverse; corpus-wide exact likewise rises by 74, from 68,101 to 68,175. The 105 removed localized boundaries include 74 newly exact cases and 31 cases that still differ elsewhere. Remaining targets such as `SYNO PEM-1`, `ACE Fair(2020)`, and `Mnet K-POP` do not satisfy the complete-parenthetical gate and remain separate residuals rather than widening this rule. Current single-capital parenthesized-digits measurement: 1361 candidates, 0 exact controls, 1361 mismatches, 1357 members in the actual `pending_rule_review` subcluster, and 1342/1361 evaluable mismatches whose first difference is inside the target run plus its entry boundary. No engine change is inferred from the ambiguous prose/function surface form. -Current mixed Roman+Korean boundary before uppercase headword-expansion measurement: 10 candidates, 0 exact controls, 10 mismatches, 10 members in the actual `pending_rule_review` subcluster, and 8/10 evaluable mismatches whose first difference is localized to the later headword's entry boundary/output. The detector cannot be satisfied by the earlier Roman entry. No HCA-shaped engine routing rule is introduced. +Current mixed Roman+Korean boundary before uppercase headword-expansion measurement: 10 candidates, 7 exact controls, 3 mismatches, 3 members in the actual `pending_rule_review` subcluster, and 2/3 evaluable mismatches whose first difference is localized to the later headword's entry boundary/output. The detector cannot be satisfied by the earlier Roman entry. The narrow rules-29/34 headword-expansion route is active; these residuals therefore identify a separate state or Roman-letter difference. -Current compact numeric+ASCII-suffix measurement: 2975 candidates, 1913 exact controls, 1062 mismatches, 990 members in the actual `pending_rule_review` subcluster, and 256/1062 evaluable mismatches whose first difference is inside the complete current-engine output signature or its immediate entry boundary. Rule 40 requires the numeric indicator and rule 69 requires Roman indicators around a Roman-written unit, but the input shape alone cannot prove that every ASCII suffix is a unit. +Current compact numeric+ASCII-suffix measurement: 2975 candidates, 1917 exact controls, 1058 mismatches, 985 members in the actual `pending_rule_review` subcluster, and 256/1058 evaluable mismatches whose first difference is inside the complete current-engine output signature or its immediate entry boundary. Rule 40 requires the numeric indicator and rule 69 requires Roman indicators around a Roman-written unit, but the input shape alone cannot prove that every ASCII suffix is a unit. | ASCII suffix | Candidates | Exact | Mismatch | Localized first diff | |---|---:|---:|---:|---:| @@ -3538,14 +3551,14 @@ Current compact numeric+ASCII-suffix measurement: 2975 candidates, 1913 exact co | `km` | 308 | 254 | 54 | 2 | | `G` | 237 | 170 | 67 | 3 | | `kg` | 209 | 176 | 33 | 4 | -| `D` | 167 | 116 | 51 | 5 | +| `D` | 167 | 118 | 49 | 5 | | `p` | 140 | 20 | 120 | 84 | | `g` | 138 | 100 | 38 | 8 | | `t` | 128 | 86 | 42 | 17 | | `M` | 104 | 80 | 24 | 6 | | `cm` | 69 | 52 | 17 | 0 | | `B` | 62 | 41 | 21 | 0 | -| `GB` | 61 | 28 | 33 | 8 | +| `GB` | 61 | 29 | 32 | 8 | | `GWh` | 57 | 42 | 15 | 9 | | `ha` | 53 | 18 | 35 | 0 | | `TV` | 52 | 41 | 11 | 1 | @@ -3557,7 +3570,7 @@ Current compact numeric+ASCII-suffix measurement: 2975 candidates, 1913 exact co | `MW` | 37 | 36 | 1 | 0 | | `TURN` | 33 | 20 | 13 | 0 | | `L` | 31 | 21 | 10 | 5 | -| `mm` | 29 | 19 | 10 | 1 | +| `mm` | 29 | 20 | 9 | 1 | | `egin` | 28 | 27 | 1 | 0 | Entry-boundary pre-fix baseline: 2,975 candidates, 1,649 exact controls, 1,326 mismatches, 1,259 pending members, and 356 localized first differences; the dominant reference number-sign versus current space transition accounted for 296 cases. Rules 68 and 69 already accept semantic Unicode compatibility-unit forms. The implementation derives compact ASCII spellings only from their all-letter NFKC decompositions, reuses the owning rule's PDF-defined cells, chooses the longest complete spelling, and rejects partial suffix matches. It does not extend recognition to separated English words or arbitrary corpus suffixes. The same cohort now has 1,759 exact controls, 1,216 mismatches, 1,148 pending members, and 261 localized first differences; corpus-wide exact matches moved from 66,436 to 66,546 (+110). Rule 69's printed `160㎎/㎗` example directly controls `160mg`; the additional full-encoder `240mg`/`240㎎` pair proves that recognition is invariant to the numeric value and also passes. Rule 68's printed `10,000㎡는 1㏊이다` example controls the `ha` spelling through U+33CA's NFKC decomposition; `15.2ha`/`15.2㏊` now passes without a spelling-specific output branch. The 53-case `ha` suffix control moved from 0 exact to 18 exact; its remaining 35 mismatches have independent later or surrounding differences. A full U+3300..U+33FF owner audit found 73 Rules 68/69 glyphs with pure-ASCII NFKC decompositions, 73 distinct spellings, and therefore no current duplicate-spelling owner collision. Production nevertheless groups every owner before resolution and excludes a spelling if any owner cells differ; a synthetic collision test proves that this is not first-wins behavior. An exhaustive test also compares every one of the 73 derived spellings with every owner glyph at both the unit-cell and full-encoder boundaries in a neutral Korean measurement context. That audit exposed the pre-existing explicit `cal` mapping's missing ordinary terminator; rule 69 requires the terminator, while the existing slash-boundary function removes it for the printed `cal/㎠/min` context. Restoring the ordinary terminator and extending that slash-continuation boundary made the audit pass without changing the corpus-wide 66,546 exact total. Ambiguous pure-English inputs remain on UEB: the standard controls `3m` and `4.m` are retained and pass, rather than being globally forced into a Korean unit route. @@ -3565,22 +3578,22 @@ Entry-boundary pre-fix baseline: 2,975 candidates, 1,649 exact controls, 1,326 m Current rule-69 ASCII-unit punctuation-boundary measurement: 440 candidates, 344 exact controls, 96 mismatches, 91 members in the actual `pending_rule_review` subcluster, and 16/96 evaluable mismatches whose first difference is localized to the unit-plus-punctuation output signature. PDF rule 69 requires a Roman terminator after a Roman-written unit in the ordinary case, while rules 33/34 omit it at the listed punctuation or enclosing-mark boundary; the rule 46 PDF example `체중(kg)` is the minimal parenthesized-unit control. Membership is restricted to rule-69 spellings already supported by the engine and does not infer unit semantics for arbitrary ASCII suffixes. Analyzer pre-fix baseline: 440 candidates, 0 exact controls, 440 mismatches, 435 pending members, and 9 signature-local first differences. After the generalized rule-33/34 boundary override and the matching non-math routing guard, the same cohort has 325 exact controls and 115 mismatches. Corpus-wide exact matches moved from 66,039 to 66,436 (+397); the additional gains are applications of the same boundary rule outside this strict ASCII detector, including compatibility-unit forms. The complete standard suite remains 5,141/5,141. -Current decimal-point measurement: 4546 candidates, 3444 exact controls, 1102 mismatches, 1045 members in the actual `pending_rule_review` subcluster, and 267/1102 evaluable mismatches whose first difference is inside the complete decimal-containing word's current-engine signature. Hangeul rules 43 and 48 keep an ASCII point between digits in the numeric sequence and encode it as the decimal point; rules 35 and 69 supply controls for adjacent Roman-number chains and Roman units. This is an implementation-candidate audit, not permission to specialize on a corpus reference. +Current decimal-point measurement: 4546 candidates, 3447 exact controls, 1099 mismatches, 1042 members in the actual `pending_rule_review` subcluster, and 267/1099 evaluable mismatches whose first difference is inside the complete decimal-containing word's current-engine signature. Hangeul rules 43 and 48 keep an ASCII point between digits in the numeric sequence and encode it as the decimal point; rules 35 and 69 supply controls for adjacent Roman-number chains and Roman units. This is an implementation-candidate audit, not permission to specialize on a corpus reference. Analyzer checkpoint `ed20f99` recorded the pre-fix baseline as 4,546 candidates, 2,584 exact controls, 1,962 mismatches, 1,905 pending members, and 875 localized first differences. Its dominant localized transition was reference decimal point `U+2832 ⠲` versus current Roman indicator `U+2834 ⠴` in 647 cases. After the generalized rule-43/48 guard, that transition is absent: 525 cases become exact and the other corrected prefixes expose later independent mismatches. -Current all-caps `OU` measurement: 1816 candidates, 70 exact controls, 1746 mismatches, 1660 members in the actual `pending_rule_review` subcluster, and 1356/1746 evaluable mismatches whose first difference is inside the current-engine output signature for the detected run. This is a pronunciation-sensitive UEB review cohort, not an engine routing rule. +Current all-caps `OU` measurement: 1816 candidates, 70 exact controls, 1746 mismatches, 1660 members in the actual `pending_rule_review` subcluster, and 1365/1746 evaluable mismatches whose first difference is inside the current-engine output signature for the detected run. This is a pronunciation-sensitive UEB review cohort, not an engine routing rule. -Current standalone-uppercase measurement: 62411 candidates, 51365 exact controls, 11046 mismatches, and 9583 members in the actual `pending_rule_review` subcluster. Its high frequency does not make it causal: the same input shape is exact in many cases, and a sentence containing the shape may first differ at another Roman, numeric, or punctuation structure. No engine change is inferred from this cohort. +Current standalone-uppercase measurement: 62411 candidates, 51419 exact controls, 10992 mismatches, and 9527 members in the actual `pending_rule_review` subcluster. Its high frequency does not make it causal: the same input shape is exact in many cases, and a sentence containing the shape may first differ at another Roman, numeric, or punctuation structure. No engine change is inferred from this cohort. -Current Korean-prefixed all-caps parenthetical measurement: 54492 candidates, 45656 exact controls, 8836 mismatches, and 7533 members in the actual `pending_rule_review` subcluster. This is a semantic-collision audit, not an engine routing rule; no implementation change is inferred from its reference outputs. +Current Korean-prefixed all-caps parenthetical measurement: 54492 candidates, 45705 exact controls, 8787 mismatches, and 7483 members in the actual `pending_rule_review` subcluster. This is a semantic-collision audit, not an engine routing rule; no implementation change is inferred from its reference outputs. -Current rule-34 opening-order measurement: 64382 structural candidates, 53650 exact controls, 10732 mismatches, and 1117/10732 evaluable mismatches whose first difference is inside the current engine's Korean opening-parenthesis cells. Only the 1115 localized first-cell transitions have the reference/current order `⠴` versus `⠦`. After requiring the complete reference `⠴⠐⠣` versus current/PDF `⠦⠄⠴` three-cell signature and preserving higher-priority comparison classifications, 1112 are classified as `corpus_suspect`; mere coexistence with a Korean-prefixed Roman annotation does not change a primary class. +Current rule-34 opening-order measurement: 64382 structural candidates, 53716 exact controls, 10666 mismatches, and 1119/10666 evaluable mismatches whose first difference is inside the current engine's Korean opening-parenthesis cells. Only the 1117 localized first-cell transitions have the reference/current order `⠴` versus `⠦`. After requiring the complete reference `⠴⠐⠣` versus current/PDF `⠦⠄⠴` three-cell signature and preserving higher-priority comparison classifications, 1114 are classified as `corpus_suspect`; mere coexistence with a Korean-prefixed Roman annotation does not change a primary class. Current all-caps Roman middle-dot measurement: 97 candidates, 0 exact controls, 97 mismatches, and 96 members in the actual `pending_rule_review` subcluster. This cross-cutting cohort preserves every primary class and is not an engine routing rule. Current Roman-before-middle-dot boundary measurement: 577 candidates, 0 exact controls, 577 mismatches, 565 members in the actual `pending_rule_review` subcluster, and 451/577 evaluable mismatches whose first difference is localized to the one current terminator immediately before the middle dot. The locator encodes each real input prefix ending at the dot, so identifier state such as `K-ICS·...` is retained without consulting expected. This raises localized target coverage from 342 to 417 and reduces the then-leading residual `⠐ -> ⠲` transition from 297 to 223. Rules 29, 33, and 50 support the current terminator path but do not support the localized reference omission; no engine change or primary-class rewrite is inferred. -Current attached Roman-to-Korean boundary measurement: 17693 candidates, 13451 exact controls, 4242 mismatches, 3406 members in the actual `pending_rule_review` subcluster, and 0/4242 evaluable mismatches whose first difference is localized to the current boundary marker. The localized target `⠲ -> ⠸` occurs 0 times and the reverse `⠸ -> ⠲` occurs 0 times. Korean rule 29 (2024 Korean-rules PDF p.26, printed p.20) requires a Roman terminator around Roman text in a Korean sentence, whereas rule 39 (PDF pp.31-32, printed pp.25-26) applies the Korean opening/closing markers only when Roman text is the sentence's main language. Among exact candidates, 11366 expose the current rule-29 terminator at such a boundary and 0 expose the current rule-39 opening. Its printed controls include both an English sentence (`What is 김치 in English?`) and the address `www.대통령.kr` inside a Korean sentence. Official UEB 2.4.7 (2024 UEB PDF p.41, printed p.13) confirms that a UEB mode does not extend through a switch to another braille code, but does not choose whether this Korean boundary belongs to a Korean-main or Roman-main context. Therefore the attached script boundary alone cannot distinguish ordinary Korean prose from an embedded Roman-domain context. Primary classes are preserved and no engine change is inferred without a narrower input-derived dominance gate. At diagnostic checkpoint `57c4608`, this same cohort was 17,693 candidates / 12,724 exact / 4,969 mismatch, with 268 localized `⠲ -> ⠸`, zero reverse, 10,780 exact rule-29 markers, and zero exact rule-39 markers. After narrowing the engine by dominance and the PDF domain shape, it is 12,924 exact / 4,769 mismatch with no localized target or reverse; corpus-wide exact rises from 67,715 to 68,101 (+386). The remaining 1 mismatch with a current rule-39 opening is a list-heavy Korean sentence whose ASCII-leading brand tokens satisfy the mechanical word-count majority; its first difference is not localized to this boundary, so it remains a semantic pending control rather than grounds for another engine branch. +Current attached Roman-to-Korean boundary measurement: 17693 candidates, 13465 exact controls, 4228 mismatches, 3391 members in the actual `pending_rule_review` subcluster, and 0/4228 evaluable mismatches whose first difference is localized to the current boundary marker. The localized target `⠲ -> ⠸` occurs 0 times and the reverse `⠸ -> ⠲` occurs 0 times. Korean rule 29 (2024 Korean-rules PDF p.26, printed p.20) requires a Roman terminator around Roman text in a Korean sentence, whereas rule 39 (PDF pp.31-32, printed pp.25-26) applies the Korean opening/closing markers only when Roman text is the sentence's main language. Among exact candidates, 11377 expose the current rule-29 terminator at such a boundary and 0 expose the current rule-39 opening. Its printed controls include both an English sentence (`What is 김치 in English?`) and the address `www.대통령.kr` inside a Korean sentence. Official UEB 2.4.7 (2024 UEB PDF p.41, printed p.13) confirms that a UEB mode does not extend through a switch to another braille code, but does not choose whether this Korean boundary belongs to a Korean-main or Roman-main context. Therefore the attached script boundary alone cannot distinguish ordinary Korean prose from an embedded Roman-domain context. Primary classes are preserved and no engine change is inferred without a narrower input-derived dominance gate. At diagnostic checkpoint `57c4608`, this same cohort was 17,693 candidates / 12,724 exact / 4,969 mismatch, with 268 localized `⠲ -> ⠸`, zero reverse, 10,780 exact rule-29 markers, and zero exact rule-39 markers. After narrowing the engine by dominance and the PDF domain shape, it is 12,924 exact / 4,769 mismatch with no localized target or reverse; corpus-wide exact rises from 67,715 to 68,101 (+386). The remaining 1 mismatch with a current rule-39 opening is a list-heavy Korean sentence whose ASCII-leading brand tokens satisfy the mechanical word-count majority; its first difference is not localized to this boundary, so it remains a semantic pending control rather than grounds for another engine branch. Current rule-39 narrowed-scope audit: 947 candidates, 593 exact controls, 354 mismatches, and 342 members in the actual `pending_rule_review` subcluster. This input-derived scope is recorded separately from the direct-boundary output-localizer; primary classes are unchanged. At the implementation checkpoint, this conservative first-script-word approximation contains 385 exact cases, accounting for all but one of the corpus-wide +386 net gain; no localized reverse transition is observed in the direct-boundary cohort. @@ -3596,10 +3609,10 @@ The audit starts from all raw encoding errors, then separates cases already reso | Encoding-error audit | Cases | |---|---:| | Raw encoding errors | 450 | -| Resolved by comparison method | 291 | +| Resolved by comparison method | 295 | | Excluded as corpus suspect | 0 | -| Unresolved encoding-error review cases | 159 | -| Explained by singleton unsupported character(s) | 159 | +| Unresolved encoding-error review cases | 155 | +| Explained by singleton unsupported character(s) | 155 | | Multiple singleton unsupported characters | 2 | | Unclassified without a singleton explanation | 0 | @@ -3610,11 +3623,11 @@ The audit starts from all raw encoding errors, then separates cases already reso | Error message | Cases | |---|---:| -| `Invalid symbol character` | 159 | +| `Invalid symbol character` | 155 | | Error family | Cases | |---|---:| -| `enclosed_organization_mark` | 140 | +| `enclosed_organization_mark` | 136 | | `letterlike_unit_symbol` | 9 | | `punctuation_or_layout_symbol` | 12 | @@ -3633,24 +3646,24 @@ Families are diagnostics, not automatic normalization permissions. Rules 68/69 c | `U+2E31 ⸱` | 2 | `⸱` | `punctuation_or_layout_symbol` | | `U+302E 〮` | 1 | `〮` | `punctuation_or_layout_symbol` | | `U+3214 ㈔` | 2 | `(사)` | `enclosed_organization_mark` | -| `U+321C ㈜` | 138 | `(주)` | `enclosed_organization_mark` | +| `U+321C ㈜` | 134 | `(주)` | `enclosed_organization_mark` | ## Shards | Shard | Exact | Total | Accuracy | |---|---:|---:|---:| -| `sentence_01.json` | 20813 | 25000 | 83.25% | -| `sentence_02.json` | 20321 | 25000 | 81.28% | -| `sentence_03.json` | 20931 | 25000 | 83.72% | -| `sentence_04.json` | 7294 | 8528 | 85.53% | +| `sentence_01.json` | 20843 | 25000 | 83.37% | +| `sentence_02.json` | 20341 | 25000 | 81.36% | +| `sentence_03.json` | 20946 | 25000 | 83.78% | +| `sentence_04.json` | 7300 | 8528 | 85.60% | ## Overlapping mismatch traits | Trait | Count | |---|---:| -| `contains_ascii_digits` | 11185 | -| `contains_ascii_letters` | 14006 | -| `contains_delimiter_or_quote` | 14169 | +| `contains_ascii_digits` | 11135 | +| `contains_ascii_letters` | 13935 | +| `contains_delimiter_or_quote` | 14098 | | `input_not_nfkc` | 703 | ## Samples @@ -3660,9 +3673,6 @@ Families are diagnostics, not automatic normalization permissions. Rules 68/69 c - `sentence_01.json` #17: 지난달 30일(현지시각) 뉴욕증권거래소(NYSE)에서 다우존스30산업평균지수는 전 거래일보다 73.55포인트(0.22%) 하락한 3만3147.25로 마감했다. 대형주 중심의 S&P500지수는 9.78포인트(0.25%) 하락한 3839.50으로, 기술주 중심의 나스닥지수는 11.61포인트(0.11%) 하락한 1만0466.4 - expected: `⠠⠕⠢⠺⠀⠴⠠⠎⠈⠯⠠⠏⠼⠑⠚⠚⠨⠕⠠⠍⠉⠵⠀⠼` - actual: `⠠⠕⠢⠺⠀⠴⠠⠎⠲⠴⠈⠯⠲⠴⠠⠏⠼⠑⠚⠚⠨⠕⠠⠍` -- `sentence_01.json` #18: 2023년형 신제품은 매터(Matter)와 HCA(Home Connectivity Alliance) 표준을 지원하는 스마트싱스 허브(SmartThings Hub)를 기반으로 다양한 기기를 자동으로 연결하고, 제어·관리할 수 있는 것이 특징이다. - - expected: `⠁⠞⠞⠻⠠⠴⠧⠀⠴⠠⠠⠓⠉⠁⠐⠣⠠⠓⠕⠍⠑⠀⠠⠒` - - actual: `⠁⠞⠞⠻⠠⠴⠧⠀⠀⠠⠠⠓⠉⠁⠦⠠⠓⠕⠍⠑⠀⠴⠠⠒` - `sentence_01.json` #35: 삼성전자는 올해 CES에서 77인치 OLED TV를 처음으로 공개할 예정이다. 이는 지난 8월 ‘국제정보디스플레이학술대회(IMID) 2022’에서 삼성디스플레이가 공개한 77인치 퀀텀닷(QD)-OLED 패널을 탑재한 제품이다. - expected: `⠋⠏⠒⠓⠎⠢⠊⠄⠴⠐⠣⠟⠙⠐⠜⠤⠠⠠⠕⠇⠫⠲⠀⠙` - actual: `⠋⠏⠒⠓⠎⠢⠊⠄⠀⠀⠦⠠⠠⠟⠙⠴⠔⠠⠠⠕⠇⠑⠙⠀` @@ -3672,6 +3682,9 @@ Families are diagnostics, not automatic normalization permissions. Rules 68/69 c - `sentence_01.json` #39: 소프트웨어정책연구소(SPRi)는 ‘2023년 SW산업 10대 이슈 전망’을 통해 올해 가장 주요한 이슈로 인공지능 기반 모델 고도화를 1위로 선정했다. - expected: `⠈⠍⠠⠥⠦⠄⠴⠠⠠⠎⠏⠠⠗⠊⠠⠴⠉⠵⠀⠠⠦⠼⠃⠚` - actual: `⠈⠍⠠⠥⠦⠄⠴⠠⠎⠠⠏⠠⠗⠊⠠⠴⠉⠵⠀⠠⠦⠼⠃⠚` +- `sentence_01.json` #45: 2019년 드링크인터내셔널 합류 이후 최고 운영 책임자(COO)를 역임하며 자회사 인터리커를 통해 와인 시장 진출 등 사업 확장을 진두지휘했다. + - expected: `⠁⠕⠢⠨⠦⠄⠴⠠⠠⠉⠕⠕⠠⠴⠐⠮⠀⠱⠁⠕⠢⠚⠑⠱` + - actual: `⠁⠕⠢⠨⠦⠄⠴⠠⠉⠠⠕⠠⠕⠠⠴⠐⠮⠀⠱⠁⠕⠢⠚⠑` ### `nfkc_input_equivalent` @@ -3835,6 +3848,8 @@ Against the immediately preceding 63,399-exact run, exact matches increased by 2 | Rules 29/32/71 ampersand before attached Roman segment | 5,141/5,141 | 68,187/83,528 | 81.63% | Official UEB `&c`, `AT&T`, and `B&B` keep one Roman section across attached `&`; the spaced Korean Rule-71 example, left ASCII alphanumerics, repeated ampersands, and trailing digits delimit the gate; 12 cases became exact | | UEB 8.4.2 capitals-word nonletter boundary | 5,141/5,141 | 69,291/83,528 | 82.96% | Starting from the accepted 68,439-exact checkpoint, capitals-word handling ends at each nonletter and later uppercase runs restart independently; official `AT&T`, `B&B`, `MP3`, and `TVOntario` delimit the token/span boundary; 852 cases became exact and the complete exact-ID audit found zero former exact cases lost | | UEB 8.4.2 same-token internal Roman apostrophe | 5,141/5,141 | 69,359/83,528 | 83.04% | A straight apostrophe stays in the Roman section only with immediate same-token ASCII letters on both sides, while capitals mode restarts for an uppercase suffix; official `O'Hara`, `DON'T`, `THAT'S`, and `SHE'LL` plus detached quote and measurement controls delimit the gate; 68 cases became exact and the complete exact-ID audit found zero former exact cases lost | +| Rules 29/34 all-caps headword with closed multiword Roman expansion | 5,141/5,141 | 69,389/83,528 | 83.07% | A complete two-or-more-capital headword followed by a closed expansion of at least two ASCII-letter words stays on the prose route; digits, operators, nesting, scripts, and alphanumeric trailers remain math controls; the cohort's exact count rose 17→47 with 30 corpus-wide gains | +| Rule 34 Korean trailer after closed multiword Roman expansion | 5,141/5,141 | 69,430/83,528 | 83.12% | Rule 34's `링컨(Lincoln)은` establishes that attached Korean text after `)` remains prose; applying the same boundary to closed multiword expansions adds 41 corpus-wide exact matches and raises the headword cohort 47→87, while ASCII-letter and digit trailers remain excluded | The latest full `cargo test -p braillify test_by_testcase --release -- --nocapture` run was accepted from its custom testcase summary, not the trailing filtered harness: `총 테스트 케이스: 5141`, `성공: 5141`, `실패: 0`, and `Skip (limitation): 0`. diff --git a/docs/corpus-analysis/NIKL_2025_V1_inquiry.md b/docs/corpus-analysis/NIKL_2025_V1_inquiry.md index f659cd07..e87b0b45 100644 --- a/docs/corpus-analysis/NIKL_2025_V1_inquiry.md +++ b/docs/corpus-analysis/NIKL_2025_V1_inquiry.md @@ -138,7 +138,7 @@ ### 질문 6. `HCA(Home Connectivity Alliance)`형 약어 풀이의 로마자 구간 재진입 -대문자 표제어 뒤에 닫힌 괄호가 있고, 괄호 안에 공백으로 구분된 로마자 단어가 둘 이상인 구조는 175건입니다. 현재 결과는 완전 일치 17건, 불일치 158건이며, 불일치의 기존 1차 분류는 규정 검토 대기 146건, 제34항 모순 후보 5건, NFKC 비교 1건, 미지원 문자 6건입니다. 이 구조는 **입력 형태를 모은 교차 진단군일 뿐 1차 분류를 바꾸지 않습니다**. +대문자 표제어 뒤에 닫힌 괄호가 있고, 괄호 안에 공백으로 구분된 로마자 단어가 둘 이상인 구조는 175건입니다. 제29항과 제34항을 보수적으로 적용하여, 표제어가 ASCII 대문자 2자 이상이고 괄호 안이 ASCII 글자 단어 2개 이상이며 숫자·연산자·중첩 괄호·다른 문자가 없는 경우만 일반 로마자 풀이로 처리했습니다. 이어 제34항 공식 예시 `링컨(Lincoln)은`에 따라 닫는 괄호 뒤에 붙은 한글과 문장 부호도 일반문 경계로 처리하되, 영문·숫자 꼬리는 계속 제외했습니다. 그 결과 완전 일치는 17건에서 87건으로 70건 증가했고, 불일치는 88건입니다. 남은 불일치의 1차 분류는 규정 검토 대기 74건, 제34항 모순 후보 7건, 정규화 비교 5건, 미지원 문자 2건입니다. 이 구조는 **입력 형태를 모은 교차 진단군일 뿐 1차 분류를 바꾸지 않습니다**. 정확 대조: @@ -151,7 +151,7 @@ - `sentence_01.json #18`: `매터(Matter)와 HCA(Home Connectivity Alliance) 표준…` -이 사례는 첫 번째 `Matter`의 로마자 구간과 조사 `와`가 끝난 뒤, 두 번째 `HCA(...)`에서 로마자표를 새로 시작해야 하는지에 관한 문제입니다. 괄호 내용에 연산자·첨자·중첩 괄호가 없도록 좁혀도, 이 표면 구조만으로 수식 반례를 완전히 배제할 수는 없습니다. +이 사례는 위의 보수적 제29·34항 적용으로 완전 일치가 되었습니다. 닫는 괄호 뒤에 조사가 붙는 `HCA(...)를` 형태도 제34항의 `링컨(Lincoln)은`과 같은 경계로 처리한 뒤에는 이 진단군에서 새 로마자표가 생략되는 국소 차이가 남지 않았습니다. 남은 국소 차이는 로마자 글자·약자 점역 차이이며, 표면 구조만으로 수식 반례를 완전히 배제할 수 있는지에 관한 의미 모드 질문은 여전히 남습니다. 문의: diff --git a/libs/braillify/examples/nikl_corpus_analyze.rs b/libs/braillify/examples/nikl_corpus_analyze.rs index de320232..bd2f66a4 100644 --- a/libs/braillify/examples/nikl_corpus_analyze.rs +++ b/libs/braillify/examples/nikl_corpus_analyze.rs @@ -5202,14 +5202,19 @@ fn markdown(report: &AnalysisReport) -> String { )); } text.push_str( - "\nThe HCA-style headword-expansion shape described above is not an engine \ - implementation premise. The 2024 PDF's math rule 6 \ - defines parentheses and grouping parentheses, rule 11 defines mathematical-expression \ - spacing, rule 12 covers Roman letters in formulas as well as Korean sentences, and \ - rule 45 shows Roman-letter function notation followed by parentheses. Excluding visible \ - operators, scripts, and nesting narrows this corpus cohort, but the PDF does not make \ - the remaining surface shape sufficient to rule out every mathematical counterexample. \ - The cluster therefore remains conservative pending-review evidence only.\n\n\ + "\nThe HCA-style headword-expansion gate now supplies one narrow prose-routing premise. \ + Korean rules 29 and 34 require a fresh Roman section and continuous Roman transcription \ + for a complete all-capitals headword followed by a closed, multiword Roman expansion. \ + The implementation requires a headword of at least two ASCII capitals and at least two \ + ASCII-letter words inside the parenthesis; digits, operators, scripts, nested brackets, \ + and alphanumeric text after the closing parenthesis remain math-owned controls. Rule 34's \ + `링컨(Lincoln)은` additionally proves that attached Korean text after the closing \ + parenthesis stays on the prose route; the same boundary now covers the multiword form \ + without admitting ASCII letters or digits in the trailer. Together these boundaries \ + change 71 corpus cases from mismatch to exact and raise this cohort's exact controls from \ + 17 to 87. The residual members still measure contraction, capitalization, earlier sentence \ + differences, unsupported characters, and reference-order conflicts rather than \ + authorizing a wider surface-form rule.\n\n\ The standalone-uppercase cohort is similarly ambiguous. Hangeul rule 28's appendix \ defines the capital-word indicator for two or more consecutive capitals, and rule 29 \ defines Roman indicators around Roman text in a Korean sentence. But math rule 12 also \ @@ -5233,10 +5238,11 @@ fn markdown(report: &AnalysisReport) -> String { `mixed_roman_korean_word_before_uppercase_headword_expansion` separately targets the \ next Roman headword after a mixed Roman+Korean word (for example, a Korean particle \ attached to the previous Roman name). Its range is anchored to that later headword, not \ - to the earlier Roman entry. Nevertheless, the closed multiword parenthetical shape still \ - cannot exclude every mathematical interpretation under math rules 6, 11, 12, and 45, \ - as recorded for the broader HCA-style cohort. The headword shape is therefore not added \ - to engine routing; the two causes and their controls remain separately measurable.\n\n\ + to the earlier Roman entry. The narrow rules-29/34 prose gate described above is now \ + implemented, and this cohort no longer has a missing-entry localized transition. Its \ + remaining localized differences are later Roman-letter/contraction differences. The two \ + causes and their controls remain \ + separately measurable instead of widening the headword grammar.\n\n\ The uppercase-Roman hyphen-digits cohort is a third independent cause. Hangeul rule 35 \ explicitly shows `D-100` as a Roman-and-number continuation (2024 Korean-rules PDF \ p.29), while math rule 2 defines subtraction and the math chapters allow uppercase Roman \ @@ -6269,8 +6275,9 @@ fn markdown(report: &AnalysisReport) -> String { measurement: {} candidates, {} exact controls, {} mismatches, {pending} members in \ the actual `pending_rule_review` subcluster, and {}/{} evaluable mismatches whose \ first difference is localized to the later headword's entry boundary/output. The \ - detector cannot be satisfied by the earlier Roman entry. No HCA-shaped engine \ - routing rule is introduced.\n", + detector cannot be satisfied by the earlier Roman entry. The narrow rules-29/34 \ + headword-expansion route is active; these residuals therefore identify a separate \ + state or Roman-letter difference.\n", stats.candidates, stats.exact, stats.mismatch, @@ -6960,7 +6967,9 @@ fn markdown(report: &AnalysisReport) -> String { | Rules 29/34 multiword Roman parenthetical continuity | 5,141/5,141 | 68,175/83,528 | 81.62% | A backwards-verified, closed letter-and-space Roman parenthetical tail stays on the prose route; function calls, digits, operators, nested brackets, and punctuation-separated forms remain outside; 74 cases became exact |\n\ | Rules 29/32/71 ampersand before attached Roman segment | 5,141/5,141 | 68,187/83,528 | 81.63% | Official UEB `&c`, `AT&T`, and `B&B` keep one Roman section across attached `&`; the spaced Korean Rule-71 example, left ASCII alphanumerics, repeated ampersands, and trailing digits delimit the gate; 12 cases became exact |\n\ | UEB 8.4.2 capitals-word nonletter boundary | 5,141/5,141 | 69,291/83,528 | 82.96% | Starting from the accepted 68,439-exact checkpoint, capitals-word handling ends at each nonletter and later uppercase runs restart independently; official `AT&T`, `B&B`, `MP3`, and `TVOntario` delimit the token/span boundary; 852 cases became exact and the complete exact-ID audit found zero former exact cases lost |\n\ - | UEB 8.4.2 same-token internal Roman apostrophe | 5,141/5,141 | 69,359/83,528 | 83.04% | A straight apostrophe stays in the Roman section only with immediate same-token ASCII letters on both sides, while capitals mode restarts for an uppercase suffix; official `O'Hara`, `DON'T`, `THAT'S`, and `SHE'LL` plus detached quote and measurement controls delimit the gate; 68 cases became exact and the complete exact-ID audit found zero former exact cases lost |\n", + | UEB 8.4.2 same-token internal Roman apostrophe | 5,141/5,141 | 69,359/83,528 | 83.04% | A straight apostrophe stays in the Roman section only with immediate same-token ASCII letters on both sides, while capitals mode restarts for an uppercase suffix; official `O'Hara`, `DON'T`, `THAT'S`, and `SHE'LL` plus detached quote and measurement controls delimit the gate; 68 cases became exact and the complete exact-ID audit found zero former exact cases lost |\n\ + | Rules 29/34 all-caps headword with closed multiword Roman expansion | 5,141/5,141 | 69,389/83,528 | 83.07% | A complete two-or-more-capital headword followed by a closed expansion of at least two ASCII-letter words stays on the prose route; digits, operators, nesting, scripts, and alphanumeric trailers remain math controls; the cohort's exact count rose 17→47 with 30 corpus-wide gains |\n\ + | Rule 34 Korean trailer after closed multiword Roman expansion | 5,141/5,141 | 69,430/83,528 | 83.12% | Rule 34's `링컨(Lincoln)은` establishes that attached Korean text after `)` remains prose; applying the same boundary to closed multiword expansions adds 41 corpus-wide exact matches and raises the headword cohort 47→87, while ASCII-letter and digit trailers remain excluded |\n", ); text.push_str( "\nThe latest full `cargo test -p braillify test_by_testcase --release -- --nocapture` \ diff --git a/libs/braillify/src/rules/token_rules/math_expression/apply.rs b/libs/braillify/src/rules/token_rules/math_expression/apply.rs index 4936435d..69121af9 100644 --- a/libs/braillify/src/rules/token_rules/math_expression/apply.rs +++ b/libs/braillify/src/rules/token_rules/math_expression/apply.rs @@ -107,6 +107,23 @@ fn is_consecutive_ascii_letter_run(chars: &[char]) -> bool { .all(|pair| u32::from(pair[1]) == u32::from(pair[0]) + 1) } +/// Whether the characters attached after a Roman closing parenthesis belong +/// to ordinary prose rather than an alphanumeric/math continuation. +/// +/// Korean rule 34 explicitly attaches the Korean particle in +/// `링컨(Lincoln)은`. The same boundary applies to a multiword Roman expansion: +/// Korean text and sentence punctuation after `)` must remain on the prose +/// path, while a digit or an ASCII letter keeps the token eligible for math. +fn is_roman_parenthetical_prose_trailer(chars: impl Iterator) -> bool { + chars.into_iter().all(|ch| { + is_korean_char(ch) + || matches!( + ch, + ',' | '.' | ';' | ':' | '!' | '?' | '\'' | '"' | '’' | '”' + ) + }) +} + fn has_ascii_letter_korean_math_suffix(chars: &[char]) -> bool { if chars.len() < 3 { return false; @@ -166,9 +183,7 @@ fn is_multiword_closed_roman_parenthetical_tail( let trailing = &word.chars[close + 1..]; if body.is_empty() || !body.iter().all(char::is_ascii_alphabetic) - || !trailing - .iter() - .all(|ch| matches!(*ch, ',' | '.' | ';' | ':' | '!' | '?' | '\'' | '"')) + || !is_roman_parenthetical_prose_trailer(trailing.iter().copied()) { return false; } @@ -206,6 +221,65 @@ fn is_multiword_closed_roman_parenthetical_tail( false } +/// Returns true when `word` begins a closed, multiword Roman expansion headed +/// by a complete all-capitals abbreviation. +/// +/// Korean rules 29 and 34 make this ordinary Roman prose: the headword starts +/// a Roman section, and the spaces inside the paired parenthesis do not split +/// that section. The narrow grammar excludes single variables, digits, +/// operators, nested brackets, and an alphanumeric continuation after `)` so +/// mathematical expressions remain owned by the math parser. +fn is_multiword_closed_roman_parenthetical_head( + tokens: &[Token<'_>], + index: usize, + word: &WordToken<'_>, +) -> bool { + let text = word.text.as_ref(); + let Some(open) = text.find('(') else { + return false; + }; + let head = &text[..open]; + let first_body_word = &text[open + 1..]; + if head.chars().count() < 2 + || !head.chars().all(|ch| ch.is_ascii_uppercase()) + || first_body_word.is_empty() + || !first_body_word.chars().all(|ch| ch.is_ascii_alphabetic()) + { + return false; + } + + let mut cursor = index + 1; + let mut body_words = 1usize; + loop { + let mut saw_space = false; + while matches!(tokens.get(cursor), Some(Token::Space(_))) { + saw_space = true; + cursor += 1; + } + if !saw_space { + return false; + } + let Some(Token::Word(next)) = tokens.get(cursor) else { + return false; + }; + let next_text = next.text.as_ref(); + if let Some(close) = next_text.find(')') { + let final_body_word = &next_text[..close]; + let trailing = &next_text[close + 1..]; + body_words += 1; + return body_words >= 2 + && !final_body_word.is_empty() + && final_body_word.chars().all(|ch| ch.is_ascii_alphabetic()) + && is_roman_parenthetical_prose_trailer(trailing.chars()); + } + if next_text.is_empty() || !next_text.chars().all(|ch| ch.is_ascii_alphabetic()) { + return false; + } + body_words += 1; + cursor += 1; + } +} + /// Walks backward from `index - 1`, skipping `Space`, returning whether the /// preceding content is a math-letter Word or a math-context PreEncoded. fn prev_is_math_context_for_ellipsis(tokens: &[Token<'_>], index: usize) -> bool { @@ -349,7 +423,9 @@ pub(super) fn run<'a>( let text = word.text.as_ref(); - if is_multiword_closed_roman_parenthetical_tail(tokens, index, word) { + if is_multiword_closed_roman_parenthetical_head(tokens, index, word) + || is_multiword_closed_roman_parenthetical_tail(tokens, index, word) + { return Ok(TokenAction::Noop); } @@ -906,6 +982,8 @@ mod tests { #[rstest::rstest] #[case::ueb_multiword_parenthetical("plays (such as Romeo and Juliet)", true)] + #[case::korean_particle_after_parenthesis("설명(Home Connectivity Alliance)를", true)] + #[case::korean_particle_after_quote("설명(Home Connectivity Alliance)’를", true)] #[case::ueb_letter_list("(q, r)", false)] #[case::math_function("f(x)", false)] #[case::operator_interrupts_prose_run("(x + y)", false)] @@ -947,6 +1025,47 @@ mod tests { } } + #[rstest::rstest] + #[case::initialism_expansion("HCA(Home Connectivity Alliance)", true)] + #[case::punctuated_expansion("TB(Top View Battle),", true)] + #[case::korean_particle("HCA(Home Connectivity Alliance)를", true)] + #[case::quoted_korean_particle("HCA(Home Connectivity Alliance)’를", true)] + #[case::single_capital_head("A(Home Connectivity Alliance)", false)] + #[case::mixed_case_head("HCa(Home Connectivity Alliance)", false)] + #[case::single_word_body("HCA(Alliance)", false)] + #[case::digit_in_body("HCA(Home Connectivity2 Alliance)", false)] + #[case::operator_in_body("HCA(Home + Alliance)", false)] + #[case::nested_parenthesis("HCA((Home Connectivity Alliance))", false)] + #[case::alphanumeric_trailer("HCA(Home Connectivity Alliance)1", false)] + #[case::unclosed_expansion("HCA(Home Connectivity Alliance", false)] + fn recognizes_only_complete_allcaps_multiword_roman_expansion_heads( + #[case] input: &str, + #[case] expected: bool, + ) { + let ir = crate::rules::token::DocumentIR::parse(input, true); + let index = ir + .tokens + .iter() + .position(|token| matches!(token, Token::Word(_))) + .expect("probe must contain a word"); + let Token::Word(word) = &ir.tokens[index] else { + unreachable!("selected token must be a word"); + }; + + assert_eq!( + is_multiword_closed_roman_parenthetical_head(&ir.tokens, index, word), + expected + ); + + if expected { + let mut state = EncoderState::new(false); + assert!(matches!( + run(&ir.tokens, index, &mut state).unwrap(), + TokenAction::Noop + )); + } + } + /// Decimal-context spacing recognizes each structural marker independently: /// the parser sentinel, the Rule 12 ellipsis, and a combining math mark. #[rstest::rstest] From 8cd05545d418a4134e3f0f94cf0c25e060f336b3 Mon Sep 17 00:00:00 2001 From: devfive Date: Wed, 2 Sep 2026 00:11:52 +0900 Subject: [PATCH 73/77] Document ambiguous corpus symbol spacing --- docs/corpus-analysis/NIKL_2025_V1.md | 82 +++++++++- docs/corpus-analysis/NIKL_2025_V1_inquiry.md | 54 +++++-- .../braillify/examples/nikl_corpus_analyze.rs | 148 +++++++++++++++++- 3 files changed, 263 insertions(+), 21 deletions(-) diff --git a/docs/corpus-analysis/NIKL_2025_V1.md b/docs/corpus-analysis/NIKL_2025_V1.md index 22f430eb..bfd22b47 100644 --- a/docs/corpus-analysis/NIKL_2025_V1.md +++ b/docs/corpus-analysis/NIKL_2025_V1.md @@ -309,8 +309,8 @@ This ranking removes only cases whose first difference is inside an existing out | 9 | `U+2810 ⠐ -> U+2814 ⠔` | 98 | | 10 | `U+2824 ⠤ -> U+2800 ⠀` | 80 | | 11 | `U+2808 ⠈ -> U+2832 ⠲` | 79 | -| 12 | `U+2826 ⠦ -> U+2800 ⠀` | 79 | -| 13 | `U+280E ⠎ -> U+2829 ⠩` | 73 | +| 12 | `U+280E ⠎ -> U+2829 ⠩` | 73 | +| 13 | `U+2826 ⠦ -> U+2800 ⠀` | 69 | | 14 | `U+2824 ⠤ -> U+2834 ⠴` | 59 | | 15 | `U+2811 ⠑ -> U+2822 ⠢` | 58 | | 16 | `U+2830 ⠰ -> U+2820 ⠠` | 58 | @@ -551,7 +551,7 @@ This ranking removes only cases whose first difference is inside an existing out ## Cross-cutting input-only structural cohorts -These are cross-cutting input-only structural cohorts, not new primary classes and not engine routing rules. Candidate selection never changes a case's existing primary class by itself. Only cohort members already classified as `pending_rule_review` form a pending subcluster; exact and other-primary members are controls that retain their existing outcomes. A separate classifier may use independently justified, output-localized PDF evidence, as in the rule-34 three-cell contradiction below. The `uppercase_roman_headword_closed_multiword_parenthetical` gate requires a two-or-more character uppercase ASCII headword immediately followed by a closed parenthesis whose contents are two or more ASCII Roman words separated only by spaces. Because the contents admit only letters and spaces, visible operators, subscript/superscript notation, and nested parentheses are excluded deterministically. The `mixed_roman_korean_word_before_uppercase_headword_expansion` gate further requires a whitespace-delimited preceding word that contains both ASCII Roman and Korean and ends in Korean. It locates the following uppercase headword itself, including its immediately preceding emitted cell, so an earlier Roman entry in the same sentence cannot satisfy the output audit. The `single_capital_followed_by_parenthesized_digits` gate requires a standalone capital, a non-empty closed ASCII-digit parenthetical, and alphanumeric outer boundaries. It likewise includes the emitted entry-boundary cell in localization. The `uppercase_roman_run_followed_by_hyphen_digits` gate requires a maximal uppercase ASCII run, literal ASCII hyphen-minus, a non-empty digit run, and alphanumeric outer boundaries. Its localized range includes the current encoded run and its immediately preceding cell, keeping `F-35`-style routing separate from both parenthesized digits and headword expansions. The `uppercase_alphanumeric_roman_digit_sequence` gate covers the wider rule-35/math collision: a capital-led uppercase/digit sequence with optional internal ASCII hyphen/full stop and trailing prose comma/colon/semicolon. It localizes only the current entry cells and does not decide whether an `A-3`-shaped surface is an identifier or a mathematical expression. The `standalone_multi_character_uppercase_roman_word` gate finds maximal ASCII-letter runs of two or more capitals with non-alphanumeric boundaries; a run immediately followed by `(` is excluded so the HCA-style headword itself is not counted by both gates. The `korean_prefixed_closed_allcaps_parenthetical` gate requires an immediately preceding Korean character and a closed body of two or more uppercase ASCII letters. It intentionally contains both acronym annotations (`책임자(COO)`) and scientific formulae (`일산화탄소(CO)`) so their semantic collision remains measurable. The `korean_prefixed_closed_roman_annotation_rule_34_order` gate accepts the narrower rule-34 body grammar after an immediately preceding Korean character and localizes only the current engine's Korean opening-parenthesis cells. This distinguishes the PDF's parenthesis-before-Roman-indicator order from unrelated differences later in the same sentence. The `multi_character_allcaps_roman_runs_joined_by_middle_dot` gate requires two maximal ASCII-letter runs of at least two capitals joined directly by U+00B7, with non-alphanumeric outer boundaries. It records shapes such as `AI·SW` without assigning prose, mathematics, or science semantics. The `roman_run_immediately_before_attached_middle_dot_boundary` gate is output-localized: it requires a maximal ASCII-letter run immediately before U+00B7 and an attached Korean character or ASCII-letter run after it, then searches for that whole current-engine signature in the actual output. It therefore isolates the Roman terminator boundary without treating unrelated middle dots elsewhere in the sentence as causal. The `attached_ascii_roman_to_korean_script_boundary` gate requires an ASCII letter run immediately followed by Korean script and localizes only the current mode marker there: either rule 29's final `⠲` or rule 39's opening `⠸⠷`. It deliberately does not infer the dominant language of the sentence from that surface boundary. The `korean_majority_same_token_roman_sandwich_non_domain` scope audit mirrors the narrower rule-39 implementation gate: the nearest script characters on both sides of one Korean segment are ASCII Roman, the input is Korean-majority by first-script word counts, and the segment is not dot-delimited like the official domain example. It measures change scope but is not itself an output-localized causal classifier. The `korean_inline_parenthesized_single_arithmetic_operator` gate requires an immediate `Korean(` + one rule-45 arithmetic operator + `)Korean` span. Unlike broad coexistence traits, it also locates the current engine's emitted structure and counts a mismatch as signature-local only when the sentence's first differing cell falls inside that output range. The `allcaps_roman_run_containing_ou` gate finds maximal, alphanumeric-delimited uppercase ASCII runs containing adjacent `OU`. It locates the independently encoded run signature in the complete current output and counts only first differences inside that signature as localized. The `allcaps_roman_run_containing_st` gate applies the same output-position requirement to adjacent `ST`; it is kept separate because UEB 10.12 makes contraction use depend on how an abbreviation or acronym is pronounced. The `uppercase_ascii_segments_joined_by_ampersand_capitalization` gate narrows attached ampersand runs to uppercase-only segments and locates the complete current Korean-context output. The run must begin its whitespace-delimited token, matching the current token-level capitals-word pre-emission scope; Korean-attached and punctuation-prefixed occurrences remain controls. UEB 8.4.2 says a nonalphabetic symbol terminates capitals word mode, while the official `AT&T` and `B&B` examples restart capitalization after `&`. The broader `capitals_word_mode_previously_spanning_nonletter_scope` gate reproduces the former token predicate across ampersands, hyphens, digits, and other nonletters that separate uppercase runs. Trailing nonletters after the final run are excluded because their output is unchanged. It is a broad change-scope/regression audit and is deliberately not an output-cause localizer. The `attached_ascii_roman_segments_joined_by_ampersand` gate requires non-empty ASCII-letter segments joined directly by `&`, excludes spaced/Korean/alphanumeric continuations, and localizes only the current output cell immediately before the ampersand through an independently encoded real-input prefix. The `ampersand_before_attached_ascii_roman_segment` gate covers the distinct one-sided UEB `&c` boundary: `&` is followed by a complete ASCII-letter segment, while an ASCII alphanumeric or another ampersand immediately before it and a digit continuation after it are excluded. Its output range is anchored by independently encoding the real input prefix before each occurrence, then includes only the current Rule-71/29 entry boundary; a second pre-fix anchor through `&` retains the former exit location. The `ascii_apostrophe_between_ascii_letter_runs` gate requires a straight apostrophe with an ASCII letter immediately on both sides and expands only across those two letter runs. It excludes detached quotation marks and numeric measurement marks, then locates the complete current mixed-Korean output signature. UEB 8.4.2 directly supplies `O'Hara`, `DON'T`, and `THAT'S` as controls. The `consecutive_ascii_roman_words_whitespace_boundary` gate requires two adjacent ASCII-letter words separated only by whitespace. For each boundary it independently encodes the real input prefix ending after the first word, then localizes only the current rule-29 terminator at that position or the full output's replacing blank. This prevents an unrelated Roman occurrence elsewhere in the sentence from satisfying the audit. Slash and other punctuation-separated forms are deliberately excluded. The `decimal_point_between_ascii_digits` gate finds whitespace-delimited words containing `digit.digit` and reproduces each whole word in a neutral Korean context, so suffixes and punctuation remain part of the current-engine signature. The `compact_numeric_ascii_letter_suffix` gate finds a numeric prefix immediately followed by ASCII letters, includes the immediately preceding output cell as its entry boundary, and retains suffix-specific outcome counts. It intentionally includes both possible rule-69 units and ambiguous variable/identifier forms; membership alone does not assign unit semantics. The `rule69_ascii_unit_before_terminator_skipping_symbol` gate is narrower: it accepts only ASCII unit spellings already supported by rule 69, includes the immediately following rule-33/34 punctuation cell in the localized signature, and does not infer new units. The `tight_triangle_mark_immediately_before_korean` gate requires literal `△한글` with no input space and includes the first following Korean cell in its localized output range, so an observed missing-space difference is measured at the mark boundary. The `attached_korean_auxiliary_itda_spacing` gate is input-only after the rule-49 correction: it measures Korean tokens ending in attached `있다` without claiming a current output signature or deciding whether orthographic correction may override the printed input. +These are cross-cutting input-only structural cohorts, not new primary classes and not engine routing rules. Candidate selection never changes a case's existing primary class by itself. Only cohort members already classified as `pending_rule_review` form a pending subcluster; exact and other-primary members are controls that retain their existing outcomes. A separate classifier may use independently justified, output-localized PDF evidence, as in the rule-34 three-cell contradiction below. The `uppercase_roman_headword_closed_multiword_parenthetical` gate requires a two-or-more character uppercase ASCII headword immediately followed by a closed parenthesis whose contents are two or more ASCII Roman words separated only by spaces. Because the contents admit only letters and spaces, visible operators, subscript/superscript notation, and nested parentheses are excluded deterministically. The `mixed_roman_korean_word_before_uppercase_headword_expansion` gate further requires a whitespace-delimited preceding word that contains both ASCII Roman and Korean and ends in Korean. It locates the following uppercase headword itself, including its immediately preceding emitted cell, so an earlier Roman entry in the same sentence cannot satisfy the output audit. The `single_capital_followed_by_parenthesized_digits` gate requires a standalone capital, a non-empty closed ASCII-digit parenthetical, and alphanumeric outer boundaries. It likewise includes the emitted entry-boundary cell in localization. The `uppercase_roman_run_followed_by_hyphen_digits` gate requires a maximal uppercase ASCII run, literal ASCII hyphen-minus, a non-empty digit run, and alphanumeric outer boundaries. Its localized range includes the current encoded run and its immediately preceding cell, keeping `F-35`-style routing separate from both parenthesized digits and headword expansions. The `uppercase_alphanumeric_roman_digit_sequence` gate covers the wider rule-35/math collision: a capital-led uppercase/digit sequence with optional internal ASCII hyphen/full stop and trailing prose comma/colon/semicolon. It localizes only the current entry cells and does not decide whether an `A-3`-shaped surface is an identifier or a mathematical expression. The `standalone_multi_character_uppercase_roman_word` gate finds maximal ASCII-letter runs of two or more capitals with non-alphanumeric boundaries; a run immediately followed by `(` is excluded so the HCA-style headword itself is not counted by both gates. The `korean_prefixed_closed_allcaps_parenthetical` gate requires an immediately preceding Korean character and a closed body of two or more uppercase ASCII letters. It intentionally contains both acronym annotations (`책임자(COO)`) and scientific formulae (`일산화탄소(CO)`) so their semantic collision remains measurable. The `korean_prefixed_closed_roman_annotation_rule_34_order` gate accepts the narrower rule-34 body grammar after an immediately preceding Korean character and localizes only the current engine's Korean opening-parenthesis cells. This distinguishes the PDF's parenthesis-before-Roman-indicator order from unrelated differences later in the same sentence. The `multi_character_allcaps_roman_runs_joined_by_middle_dot` gate requires two maximal ASCII-letter runs of at least two capitals joined directly by U+00B7, with non-alphanumeric outer boundaries. It records shapes such as `AI·SW` without assigning prose, mathematics, or science semantics. The `roman_run_immediately_before_attached_middle_dot_boundary` gate is output-localized: it requires a maximal ASCII-letter run immediately before U+00B7 and an attached Korean character or ASCII-letter run after it, then searches for that whole current-engine signature in the actual output. It therefore isolates the Roman terminator boundary without treating unrelated middle dots elsewhere in the sentence as causal. The `attached_ascii_roman_to_korean_script_boundary` gate requires an ASCII letter run immediately followed by Korean script and localizes only the current mode marker there: either rule 29's final `⠲` or rule 39's opening `⠸⠷`. It deliberately does not infer the dominant language of the sentence from that surface boundary. The `korean_majority_same_token_roman_sandwich_non_domain` scope audit mirrors the narrower rule-39 implementation gate: the nearest script characters on both sides of one Korean segment are ASCII Roman, the input is Korean-majority by first-script word counts, and the segment is not dot-delimited like the official domain example. It measures change scope but is not itself an output-localized causal classifier. The `korean_inline_parenthesized_single_arithmetic_operator` gate requires an immediate `Korean(` + one rule-45 arithmetic operator + `)Korean` span. Unlike broad coexistence traits, it also locates the current engine's emitted structure and counts a mismatch as signature-local only when the sentence's first differing cell falls inside that output range. The `attached_plus_followed_by_parenthesized_korean_gloss` gate requires literal `한글+(한글)` with a non-empty all-Korean gloss. It anchors the real prefix immediately before `+` and verifies the current neutral-Korean output signature, distinguishing rule-46 spacing at the sign from unrelated differences elsewhere without deciding whether a name is mathematical. The `allcaps_roman_run_containing_ou` gate finds maximal, alphanumeric-delimited uppercase ASCII runs containing adjacent `OU`. It locates the independently encoded run signature in the complete current output and counts only first differences inside that signature as localized. The `allcaps_roman_run_containing_st` gate applies the same output-position requirement to adjacent `ST`; it is kept separate because UEB 10.12 makes contraction use depend on how an abbreviation or acronym is pronounced. The `uppercase_ascii_segments_joined_by_ampersand_capitalization` gate narrows attached ampersand runs to uppercase-only segments and locates the complete current Korean-context output. The run must begin its whitespace-delimited token, matching the current token-level capitals-word pre-emission scope; Korean-attached and punctuation-prefixed occurrences remain controls. UEB 8.4.2 says a nonalphabetic symbol terminates capitals word mode, while the official `AT&T` and `B&B` examples restart capitalization after `&`. The broader `capitals_word_mode_previously_spanning_nonletter_scope` gate reproduces the former token predicate across ampersands, hyphens, digits, and other nonletters that separate uppercase runs. Trailing nonletters after the final run are excluded because their output is unchanged. It is a broad change-scope/regression audit and is deliberately not an output-cause localizer. The `attached_ascii_roman_segments_joined_by_ampersand` gate requires non-empty ASCII-letter segments joined directly by `&`, excludes spaced/Korean/alphanumeric continuations, and localizes only the current output cell immediately before the ampersand through an independently encoded real-input prefix. The `ampersand_before_attached_ascii_roman_segment` gate covers the distinct one-sided UEB `&c` boundary: `&` is followed by a complete ASCII-letter segment, while an ASCII alphanumeric or another ampersand immediately before it and a digit continuation after it are excluded. Its output range is anchored by independently encoding the real input prefix before each occurrence, then includes only the current Rule-71/29 entry boundary; a second pre-fix anchor through `&` retains the former exit location. The `ascii_apostrophe_between_ascii_letter_runs` gate requires a straight apostrophe with an ASCII letter immediately on both sides and expands only across those two letter runs. It excludes detached quotation marks and numeric measurement marks, then locates the complete current mixed-Korean output signature. UEB 8.4.2 directly supplies `O'Hara`, `DON'T`, and `THAT'S` as controls. The `consecutive_ascii_roman_words_whitespace_boundary` gate requires two adjacent ASCII-letter words separated only by whitespace. For each boundary it independently encodes the real input prefix ending after the first word, then localizes only the current rule-29 terminator at that position or the full output's replacing blank. This prevents an unrelated Roman occurrence elsewhere in the sentence from satisfying the audit. Slash and other punctuation-separated forms are deliberately excluded. The `decimal_point_between_ascii_digits` gate finds whitespace-delimited words containing `digit.digit` and reproduces each whole word in a neutral Korean context, so suffixes and punctuation remain part of the current-engine signature. The `compact_numeric_ascii_letter_suffix` gate finds a numeric prefix immediately followed by ASCII letters, includes the immediately preceding output cell as its entry boundary, and retains suffix-specific outcome counts. It intentionally includes both possible rule-69 units and ambiguous variable/identifier forms; membership alone does not assign unit semantics. The `rule69_ascii_unit_before_terminator_skipping_symbol` gate is narrower: it accepts only ASCII unit spellings already supported by rule 69, includes the immediately following rule-33/34 punctuation cell in the localized signature, and does not infer new units. The `tight_triangle_mark_immediately_before_korean` gate requires literal `△한글` with no input space and includes the first following Korean cell in its localized output range, so an observed missing-space difference is measured at the mark boundary. The `attached_korean_auxiliary_itda_spacing` gate is input-only after the rule-49 correction: it measures Korean tokens ending in attached `있다` without claiming a current output signature or deciding whether orthographic correction may override the printed input. | Cluster | Candidates | Exact | Mismatch | Conflicting-reference cases | |---|---:|---:|---:|---:| @@ -566,6 +566,7 @@ These are cross-cutting input-only structural cohorts, not new primary classes a | `attached_ascii_roman_segments_joined_by_ampersand` | 802 | 608 | 194 | 0 | | `attached_ascii_roman_to_korean_script_boundary` | 17693 | 13465 | 4228 | 0 | | `attached_korean_auxiliary_itda_spacing` | 95 | 72 | 23 | 0 | +| `attached_plus_followed_by_parenthesized_korean_gloss` | 16 | 3 | 13 | 0 | | `capitals_word_mode_previously_spanning_nonletter_scope` | 1733 | 854 | 879 | 0 | | `closed_roman_parenthetical_after_non_ascii_letter_boundary` | 63959 | 53122 | 10837 | 0 | | `compact_numeric_ascii_letter_suffix` | 2975 | 1917 | 1058 | 0 | @@ -1412,6 +1413,77 @@ Representative `mismatch` samples: - first differing cell (zero-based): 109 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +### `attached_plus_followed_by_parenthesized_korean_gloss` + +Of the 16 candidates, 13 are the actual `pending_rule_review` subcluster. The other 3 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. + +For this output-signature audit, 13 mismatches were evaluable and 10 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. + +Localized first-difference transitions: + +- `U+2826 ⠦ -> U+2800 ⠀`: 10 + +Mismatch primary-class distribution: + +- `pending_rule_review`: 13 + +Representative `exact` samples: + +- `sentence_01.json` #12551: 최근 화제가 되고 있는 넷플릭스 ‘더 글로리’에서 문동은(송혜교)에게 “넝담”이라며 시비를 거는 추 선생 역으로 열연을 펼쳤다. 디즈니+(플러스) 오리지널 시리즈 ‘카지노’에도 출연했다. + - expected: `⠰⠽⠈⠵⠀⠚⠧⠨⠝⠫⠀⠊⠽⠈⠥⠀⠕⠌⠉⠵⠀⠉⠝⠄` + - actual: `⠰⠽⠈⠵⠀⠚⠧⠨⠝⠫⠀⠊⠽⠈⠥⠀⠕⠌⠉⠵⠀⠉⠝⠄` + - current primary/reason: `exact` / `exact` +- `sentence_02.json` #4383: 운영과정은 ‘도전’ 프로그램(5주 과정)과 ‘도전+(플러스)’ 프로그램(5개월 과정)으로 진행되며 프로그램 이수 시 50만 원에서 최대 300만 원까지(월별 참여수당 지급) 참여수당과 인센티브를 지원한다. + - expected: `⠛⠻⠈⠧⠨⠻⠵⠀⠠⠦⠊⠥⠨⠾⠴⠄⠀⠙⠪⠐⠥⠈⠪⠐` + - actual: `⠛⠻⠈⠧⠨⠻⠵⠀⠠⠦⠊⠥⠨⠾⠴⠄⠀⠙⠪⠐⠥⠈⠪⠐` + - current primary/reason: `exact` / `exact` + +Representative `localized_mismatch` samples: + +- `sentence_01.json` #845: 부산광역시는 3일 오전 부산광역시청에서 초록우산어린이재단 부산본부, 월드비전 부산사업본부, 굿네이버스 영남본부와 ‘자립+(더하기) 동행 프로젝트’ 업무협약을 체결했다고 밝혔다. + - expected: `⠠⠦⠨⠐⠕⠃⠀⠢⠦⠄⠊⠎⠚⠈⠕⠠⠴⠀⠊⠿⠚⠗⠶⠀` + - actual: `⠠⠦⠨⠐⠕⠃⠀⠢⠀⠦⠄⠊⠎⠚⠈⠕⠠⠴⠀⠊⠿⠚⠗⠶` + - first differing cell (zero-based): 116 + - current primary/reason: `pending_rule_review` / `number_rule_review` +- `sentence_02.json` #168: 5주 과정인 ‘도전’ 프로그램과 5개월 과정인 ‘도전+(플러스)’ 프로그램으로 나뉘어 진행되며, 프로그램 이수 시 1개월에 50만 원씩 최대 300만 원까지 참여 수당과 인센티브를 지원한다. + - expected: `⠠⠦⠊⠥⠨⠾⠀⠢⠦⠄⠙⠮⠐⠎⠠⠪⠠⠴⠴⠄⠀⠙⠪⠐` + - actual: `⠠⠦⠊⠥⠨⠾⠀⠢⠀⠦⠄⠙⠮⠐⠎⠠⠪⠠⠴⠴⠄⠀⠙⠪` + - first differing cell (zero-based): 53 + - current primary/reason: `pending_rule_review` / `number_rule_review` +- `sentence_03.json` #12962: 1·2열 온도·모드·풍량을 각각 독립적으로 제어할 수있는 3존+(플러스)공조, 디지털키 2, 실내 지문 인증 시스템, 콘솔 암레스트 수납함 자외선 살균 기능, 콘솔 암레스트 열선 등을 탑재했다. + - expected: `⠵⠀⠼⠉⠨⠷⠀⠢⠦⠄⠙⠮⠐⠎⠠⠪⠠⠴⠈⠿⠨⠥⠐⠀` + - actual: `⠵⠀⠼⠉⠨⠷⠀⠢⠀⠦⠄⠙⠮⠐⠎⠠⠪⠠⠴⠈⠿⠨⠥⠐` + - first differing cell (zero-based): 62 + - current primary/reason: `pending_rule_review` / `number_rule_review` +- `sentence_04.json` #6494: LG유플러스(대표 황현식)는 중소 알뜰폰(MVNO) 사업자의 요금제를 판매하는 오프라인 컨설팅 전문매장 ‘알뜰폰+(플러스)’를 전국으로 확대한다고 31일 밝혔다. + - expected: `⠂⠠⠊⠮⠙⠷⠀⠢⠦⠄⠙⠮⠐⠎⠠⠪⠠⠴⠴⠄⠐⠮⠀⠨` + - actual: `⠂⠠⠊⠮⠙⠷⠀⠢⠀⠦⠄⠙⠮⠐⠎⠠⠪⠠⠴⠴⠄⠐⠮⠀` + - first differing cell (zero-based): 117 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` + +Representative `mismatch` samples: + +- `sentence_01.json` #845: 부산광역시는 3일 오전 부산광역시청에서 초록우산어린이재단 부산본부, 월드비전 부산사업본부, 굿네이버스 영남본부와 ‘자립+(더하기) 동행 프로젝트’ 업무협약을 체결했다고 밝혔다. + - expected: `⠠⠦⠨⠐⠕⠃⠀⠢⠦⠄⠊⠎⠚⠈⠕⠠⠴⠀⠊⠿⠚⠗⠶⠀` + - actual: `⠠⠦⠨⠐⠕⠃⠀⠢⠀⠦⠄⠊⠎⠚⠈⠕⠠⠴⠀⠊⠿⠚⠗⠶` + - first differing cell (zero-based): 116 + - current primary/reason: `pending_rule_review` / `number_rule_review` +- `sentence_02.json` #168: 5주 과정인 ‘도전’ 프로그램과 5개월 과정인 ‘도전+(플러스)’ 프로그램으로 나뉘어 진행되며, 프로그램 이수 시 1개월에 50만 원씩 최대 300만 원까지 참여 수당과 인센티브를 지원한다. + - expected: `⠠⠦⠊⠥⠨⠾⠀⠢⠦⠄⠙⠮⠐⠎⠠⠪⠠⠴⠴⠄⠀⠙⠪⠐` + - actual: `⠠⠦⠊⠥⠨⠾⠀⠢⠀⠦⠄⠙⠮⠐⠎⠠⠪⠠⠴⠴⠄⠀⠙⠪` + - first differing cell (zero-based): 53 + - current primary/reason: `pending_rule_review` / `number_rule_review` +- `sentence_03.json` #4388: 청룡시리즈어워즈는 2022년 국내 최초로 오리지널 스트리밍 시리즈를 대상으로 열린 시상식이다. 넷플릭스부터 디즈니+(플러스), 애플TV+(플러스), 왓챠, 웨이브, 카카오TV, 쿠팡플레이, 티빙이 제작하거나 투자한 국내 드라마와 예능·교양을 대상으로 한다. + - expected: `⠗⠙⠮⠴⠠⠠⠞⠧⠐⠖⠦⠄⠙⠮⠐⠎⠠⠪⠠⠴⠐⠀⠧⠄` + - actual: `⠗⠙⠮⠴⠠⠠⠞⠧⠲⠀⠢⠀⠦⠄⠙⠮⠐⠎⠠⠪⠠⠴⠐⠀` + - first differing cell (zero-based): 134 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_04.json` #6494: LG유플러스(대표 황현식)는 중소 알뜰폰(MVNO) 사업자의 요금제를 판매하는 오프라인 컨설팅 전문매장 ‘알뜰폰+(플러스)’를 전국으로 확대한다고 31일 밝혔다. + - expected: `⠂⠠⠊⠮⠙⠷⠀⠢⠦⠄⠙⠮⠐⠎⠠⠪⠠⠴⠴⠄⠐⠮⠀⠨` + - actual: `⠂⠠⠊⠮⠙⠷⠀⠢⠀⠦⠄⠙⠮⠐⠎⠠⠪⠠⠴⠴⠄⠐⠮⠀` + - first differing cell (zero-based): 117 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` + ### `capitals_word_mode_previously_spanning_nonletter_scope` Of the 1733 candidates, 821 are the actual `pending_rule_review` subcluster. The other 912 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. @@ -3418,7 +3490,7 @@ The generalized fix makes an explicit `EnterEnglish` event idempotent when final Korean rule 34 (2024 Korean-rules PDF p.29) prints the opening enclosure before Roman entry in `링컨(Lincoln)`: the opening sequence is `⠦⠄⠴`. This output-position cohort finds a closed, non-nested parenthetical whose body begins with an ASCII letter and whose opening does not immediately follow another ASCII letter, then locates its complete current-engine signature without consulting the reference. Its localized boundary includes the two current output cells immediately before the opening plus the first three entry cells, so rule-11 math spacing can be separated from a difference later inside the parenthetical. Direct function-call shapes such as `f(x)` are excluded. The PDF's math rule 6 (p.59), rule 12 (pp.63-64), and rule 45 (p.75) nevertheless leave standalone `(x)` and other Roman-letter parenthetical mathematics as counterexamples, so the surface gate is not an engine-routing predicate. -The cross-cutting input cohort contains 63959 candidates: 53122 exact controls and 10837 mismatches. Mismatch primary classes remain unchanged: 9343 `pending_rule_review`, 1114 `corpus_suspect`, 242 `comparison_method`, and 138 `unsupported_character_review`. Of 10837 evaluable mismatches, 1130 have the first difference at the detected leading-spacing/entry boundary; these include 438 `U+2826 ⠦ -> U+2834 ⠴`, 28 `U+2834 ⠴ -> U+2826 ⠦`, and 330 `U+2826 ⠦ -> U+2800 ⠀` transitions; the exact localized reverse `U+2800 ⠀ -> U+2826 ⠦` occurs 0 times. After all earlier localized cohorts and this cohort are excluded, the raw-to-residual target count is 471 -> 26, the Roman-indicator reverse count is 33 -> 2, and the spacing target/reverse counts are 445 -> 79 and 1 -> 1. The short full-encoder form `웹3(Web3)` emits the PDF opening order as a routing control, while whitespace and quote boundaries reproduce the current Roman-first opening. Representative localized samples, with shard and index, are retained in the generated cluster sample table. Because exact controls are abundant and the PDF does not make this input shape semantically sufficient to exclude mathematics, no engine change or primary reclassification is inferred. The one raw/residual spacing reverse is a Korean-body `△(교육)` layout boundary, not a Roman-parenthetical member, and remains a separate rule-72/layout review. +The cross-cutting input cohort contains 63959 candidates: 53122 exact controls and 10837 mismatches. Mismatch primary classes remain unchanged: 9343 `pending_rule_review`, 1114 `corpus_suspect`, 242 `comparison_method`, and 138 `unsupported_character_review`. Of 10837 evaluable mismatches, 1130 have the first difference at the detected leading-spacing/entry boundary; these include 438 `U+2826 ⠦ -> U+2834 ⠴`, 28 `U+2834 ⠴ -> U+2826 ⠦`, and 330 `U+2826 ⠦ -> U+2800 ⠀` transitions; the exact localized reverse `U+2800 ⠀ -> U+2826 ⠦` occurs 0 times. After all earlier localized cohorts and this cohort are excluded, the raw-to-residual target count is 471 -> 26, the Roman-indicator reverse count is 33 -> 2, and the spacing target/reverse counts are 445 -> 69 and 1 -> 1. The short full-encoder form `웹3(Web3)` emits the PDF opening order as a routing control, while whitespace and quote boundaries reproduce the current Roman-first opening. Representative localized samples, with shard and index, are retained in the generated cluster sample table. Because exact controls are abundant and the PDF does not make this input shape semantically sufficient to exclude mathematics, no engine change or primary reclassification is inferred. The one raw/residual spacing reverse is a Korean-body `△(교육)` layout boundary, not a Roman-parenthetical member, and remains a separate rule-72/layout review. The HCA-style headword-expansion gate now supplies one narrow prose-routing premise. Korean rules 29 and 34 require a fresh Roman section and continuous Roman transcription for a complete all-capitals headword followed by a closed, multiword Roman expansion. The implementation requires a headword of at least two ASCII capitals and at least two ASCII-letter words inside the parenthesis; digits, operators, scripts, nested brackets, and alphanumeric text after the closing parenthesis remain math-owned controls. Rule 34's `링컨(Lincoln)은` additionally proves that attached Korean text after the closing parenthesis stays on the prose route; the same boundary now covers the multiword form without admitting ASCII letters or digits in the trailer. Together these boundaries change 71 corpus cases from mismatch to exact and raise this cohort's exact controls from 17 to 87. The residual members still measure contraction, capitalization, earlier sentence differences, unsupported characters, and reference-order conflicts rather than authorizing a wider surface-form rule. @@ -3600,6 +3672,8 @@ Current rule-39 narrowed-scope audit: 947 candidates, 593 exact controls, 354 mi Current inline parenthesized-operator measurement: 23 candidates, 17 exact controls, 6 mismatches, 6 members in the actual `pending_rule_review` subcluster, and 2/6 evaluable mismatches whose first differing cell is inside the emitted structure. Primary classes are preserved. At that implementation checkpoint, the strict cohort moved from 0 to 17 exact cases; the corpus-wide total moved from 65,491 to 65,514 (+23 exact) because the same PDF-backed spacing rule also applied outside the stricter Korean-boundary audit gate. These are immutable checkpoint counts rather than the report's later cumulative total. The complete standard suite remained 5,141/5,141. +Current attached plus + parenthesized Korean-gloss measurement: 16 candidates, 3 exact controls, 13 mismatches, 13 members in the actual `pending_rule_review` subcluster, and 10/13 evaluable mismatches whose first differing cell is inside the current emitted structure. Hangeul rule 46 supplies the operation-sign spacing control, but the surface form alone does not establish whether a brand or program name uses `+` mathematically. Exact and localized mismatch references coexist for `도전+(플러스)`, so no engine change or primary reclassification is inferred. + Current tight-triangle measurement: 377 candidates, 0 exact controls, 377 mismatches, 376 members in the actual `pending_rule_review` subcluster, and 335/377 evaluable mismatches whose first difference is inside the `△` plus first-Korean output range. No engine change is inferred. ## Encoding-error diagnostics diff --git a/docs/corpus-analysis/NIKL_2025_V1_inquiry.md b/docs/corpus-analysis/NIKL_2025_V1_inquiry.md index e87b0b45..8020299b 100644 --- a/docs/corpus-analysis/NIKL_2025_V1_inquiry.md +++ b/docs/corpus-analysis/NIKL_2025_V1_inquiry.md @@ -23,12 +23,12 @@ ## 2. 대조 방법과 재현 범위 - 분석 대상: 83,528문장 -- 현재 완전 일치: 69,359문장(83.04%) -- 불일치 또는 인코딩 불가: 14,169문장 -- 분류 결과: 규정 검토 대기 12,586건, 제34항 참조값 모순 후보 1,112건, NFKC 비교 312건, 미지원 문자 검토 159건 +- 현재 완전 일치: 69,430문장(83.12%) +- 불일치 또는 인코딩 불가: 14,098문장 +- 분류 결과: 규정 검토 대기 12,513건, 규정과 참조값의 모순 후보 1,114건, NFKC 비교 316건, 미지원 문자 검토 155건 - 비교 필드: 묵자 `input`과 말뭉치 점자 참조값 `unicode`만 사용 - 완전 일치의 정의: 공백을 포함한 유니코드 점자 셀 배열이 처음부터 끝까지 동일한 경우 -- 위치 표기: `sentence_XX.json #N`에서 `N`은 해당 shard의 0부터 시작하는 배열 인덱스 +- 위치 표기: `sentence_XX.json #N`에서 `N`은 사람이 확인하기 쉬운 1부터 시작하는 배열 순번 - 계측 방법: 문장에 특정 표면형이 존재하는지만 세지 않고, 기대 출력과 현재 규칙 기반 출력의 **최초 차이 셀**이 그 구조의 실제 출력 범위 안에 있는지도 별도로 확인 - 정확 대조군: 같은 구조를 포함하면서 전체 출력이 완전히 일치하는 문장 - 역전 대조: 기대와 현재 출력의 셀 전이가 반대 방향으로 나타나는 경우를 별도 계수 @@ -48,7 +48,7 @@ 3. 괄호 안 로마자 4. 한글 닫는 소괄호 -현재 점역기도 이 순서를 따릅니다. 그런데 말뭉치에는 한글 바로 뒤의 닫힌 로마자 주석에서 기대값이 `⠴⠐⠣`(로마자표 + UEB 여는 괄호)로 시작하고, 현재 규정 경로가 `⠦⠄⠴`(한글 여는 괄호 + 로마자표)로 시작하는 사례가 1,112건 있습니다. 단순히 같은 문장에 괄호가 있다는 이유가 아니라, 실제 입력 위치에서 이 세 셀의 순서가 모두 반대로 확인되는 경우만 셌습니다. +현재 점역기도 이 순서를 따릅니다. 그런데 말뭉치에는 한글 바로 뒤의 닫힌 로마자 주석에서 기대값이 `⠴⠐⠣`(로마자표 + UEB 여는 괄호)로 시작하고, 현재 규정 경로가 `⠦⠄⠴`(한글 여는 괄호 + 로마자표)로 시작하는 사례가 1,114건 있습니다. 단순히 같은 문장에 괄호가 있다는 이유가 아니라, 실제 입력 위치에서 이 세 셀의 순서가 모두 반대로 확인되는 경우만 셌습니다. 대표 사례: @@ -59,7 +59,7 @@ 문의: 1. 한글 문장 안에서 `한글(로마자)`를 점역할 때 여는 괄호와 로마자표의 올바른 순서는 `⠦⠄⠴`입니까? -2. 그렇다면 `⠴⠐⠣`로 시작하는 위 1,112건은 말뭉치 참조값 정정 대상입니까? +2. 그렇다면 `⠴⠐⠣`로 시작하는 위 1,114건은 말뭉치 참조값 정정 대상입니까? 3. 괄호 안이 약어, 일반 영단어, 로마자와 숫자의 결합이어도 같은 순서를 적용합니까? ### 질문 2. 로마자 바로 뒤에 붙은 가운뎃점(U+00B7) 앞의 로마자 종료표 @@ -245,24 +245,24 @@ UEB에는 `AT&T`, `B&B`, `&c`처럼 앰퍼샌드와 로마자가 붙어 있는 3. U+002D `-`, U+2013 `–`, 한국어 줄표가 혼용된 입력은 원문 코드 포인트를 보존해야 합니까, 의미에 맞게 정규화해야 합니까? 4. 제49항의 “묵자의 띄어쓰기를 따른다”는 입력에 있는 공백을 그대로 보존하라는 뜻입니까? -### 질문 12. NFKC로만 같아지는 312건의 비교 정책 +### 질문 12. NFKC로만 같아지는 316건의 비교 정책 -현재 312건은 원 입력을 그대로 점역한 결과와는 다르지만, 입력에 NFKC를 적용한 뒤에는 말뭉치 참조값과 일치하여 `comparison_method`로 별도 분류했습니다. 이 분류는 엔진이 모든 입력을 무조건 NFKC로 바꾸어야 한다는 뜻이 아닙니다. 예를 들어 로마 숫자 표시는 제36항에 근거해 해당 로마자로 풀 수 있지만, `㈜`, `ℓ`, 제목 기호 등은 의미와 코드 포인트가 달라질 수 있습니다. +현재 316건은 원 입력을 그대로 점역한 결과와는 다르지만, 입력에 NFKC를 적용한 뒤에는 말뭉치 참조값과 일치하여 `comparison_method`로 별도 분류했습니다. 이 분류는 엔진이 모든 입력을 무조건 NFKC로 바꾸어야 한다는 뜻이 아닙니다. 예를 들어 로마 숫자 표시는 제36항에 근거해 해당 로마자로 풀 수 있지만, `㈜`, `ℓ`, 제목 기호 등은 의미와 코드 포인트가 달라질 수 있습니다. 문의: 1. 말뭉치 참조값은 원 `input` 코드 포인트를 기준으로 작성됐습니까, NFKC 등 사전 정규화를 거친 문자열을 기준으로 작성됐습니까? 2. 호환 문자와 분해된 ASCII 철자가 의미상 같아도 동일한 참조값을 요구합니까? 3. 공식 권장 정규화 형식(NFC/NFKC)과 예외 문자 목록이 있습니까? -4. 정규화 전후가 다른 312건을 평가할 때 어느 결과를 정답으로 보아야 합니까? +4. 정규화 전후가 다른 316건을 평가할 때 어느 결과를 정답으로 보아야 합니까? ### 질문 13. 현행 엔진에서 단독으로도 인코딩되지 않는 문자 -다음 159건은 포함 문자를 하나씩 따로 인코딩해도 실패하는 것으로 확인했습니다. PDF에서 독립적인 지원 근거를 찾지 못해 “오류”가 아니라 “미지원 문자 검토”로 남겨 두었습니다. +다음 155건은 포함 문자를 하나씩 따로 인코딩해도 실패하는 것으로 확인했습니다. PDF에서 독립적인 지원 근거를 찾지 못해 “오류”가 아니라 “미지원 문자 검토”로 남겨 두었습니다. | 문자 | 포함 문장 수 | NFKC 분해 | 현재 진단 | |---|---:|---|---| -| `㈜` U+321C | 138 | `(주)` | 괄호 문자 조직 표지 | +| `㈜` U+321C | 134 | `(주)` | 괄호 문자 조직 표지 | | `㈔` U+3214 | 2 | `(사)` | 괄호 문자 조직 표지 | | `ℓ` U+2113 | 9 | `l` | 단위/문자 기호 | | `☏` U+260F | 3 | 그대로 | 전화 기호 | @@ -275,7 +275,7 @@ UEB에는 `AT&T`, `B&B`, `&c`처럼 앰퍼샌드와 로마자가 붙어 있는 | `♥` U+2665 | 1 | 그대로 | 하트 기호 | | `〮` U+302E | 1 | 그대로 | 한글 성조점 | -두 문자는 한 문장에 함께 나타날 수 있어 문자별 “포함 문장 수”의 합은 159보다 클 수 있습니다. +두 문자는 한 문장에 함께 나타날 수 있어 문자별 “포함 문장 수”의 합은 155보다 클 수 있습니다. 문의: @@ -297,7 +297,7 @@ UEB에는 `AT&T`, `B&B`, `&c`처럼 앰퍼샌드와 로마자가 붙어 있는 ### 질문 15. 로마자·단위·숫자 뒤에 붙은 괄호를 어느 점자 체계로 적는지 -앞에서 설명한 제34항의 1,112건은 **한글 뒤 괄호 안에 로마자가 있는 경우**입니다. 이와 반대로 `BSI(73)`, `Merit(4위)`, `M(41)`, `43bp(1bp…)`처럼 로마자·단위 뒤의 괄호 안에 숫자 또는 한글이 있는 경우도 별도로 남습니다. 기존 output-localized 코호트를 제외한 뒤에도 기대 한글 여는 소괄호의 첫 셀 `⠦`와 현재 UEB 여는 괄호의 첫 셀 `⠐`이 충돌하는 잔여가 133건입니다. +앞에서 설명한 제34항의 1,114건은 **한글 뒤 괄호 안에 로마자가 있는 경우**입니다. 이와 반대로 `BSI(73)`, `Merit(4위)`, `M(41)`, `43bp(1bp…)`처럼 로마자·단위 뒤의 괄호 안에 숫자 또는 한글이 있는 경우도 별도로 남습니다. 기존 output-localized 코호트를 제외한 뒤에도 기대 한글 여는 소괄호의 첫 셀 `⠦`와 현재 UEB 여는 괄호의 첫 셀 `⠐`이 충돌하는 잔여가 133건입니다. 대표 사례: @@ -315,6 +315,29 @@ UEB에는 `AT&T`, `B&B`, `&c`처럼 앰퍼샌드와 로마자가 붙어 있는 3. `43bp(1bp는 …)`처럼 괄호 안이 단위 설명일 때는 바깥 단위의 로마자 구간을 먼저 닫아야 합니까? 4. 괄호의 점형을 결정하는 기준이 “괄호 앞 문자”, “괄호 안 주언어”, “전체 문장의 주언어” 중 무엇인지 예와 함께 알려 주실 수 있습니까? +### 질문 16. U+002D 마이너스와 이름에 붙인 `+(풀이)`의 공백 + +제46항의 공식 예는 연산 기호 양옆을 띄우며, 뺄셈 기호에는 U+2212 `−`를 사용합니다. 반면 말뭉치 입력의 `음(-)극`, `마이너스(-)`는 U+002D HYPHEN-MINUS를 사용하면서 참조값에서는 제46항의 뺄셈 기호 셀 `⠔`을 요구합니다. 현재 점역기는 코드 포인트를 보존하여 U+2212는 뺄셈 기호, U+002D는 제49항 문장 부호 경로의 붙임표로 처리합니다. + +대표 사례: + +- `sentence_01.json #21108`: `양(+)극과 음(-)극` — `-`는 U+002D이나 참조값은 `⠔` +- `sentence_02.json #23164`: `마이너스(-)였으나` — 같은 전이가 최초 차이로 재현됨 + +또한 상표·프로그램 이름에 붙은 `한글+(한글 풀이)` 구조는 16건입니다. 현재 제46항 경로와 전체 참조값이 일치하는 문장은 3건, 불일치는 13건이며, 보수적인 실제 출력 위치 검증을 통과한 10건은 현재 출력의 `+` 뒤 공백과 참조값의 여는 괄호가 직접 충돌합니다. 특히 동일한 `도전+(플러스)` 표면형에서 공백을 요구하는 참조와 생략하는 참조가 모두 존재합니다. + +- 일치: `sentence_02.json #4383`의 `도전+(플러스)` +- 불일치: `sentence_02.json #168`, `#6210`의 `도전+(플러스)` +- 불일치: `sentence_01.json #845`의 `자립+(더하기)` +- 불일치: `sentence_03.json #18547`, `#18734`의 `디즈니+(플러스)` + +문의: + +1. 묵자 입력이 U+002D이어도 주변 의미가 음극·마이너스이면 U+2212와 같은 뺄셈 기호로 정규화해야 합니까? +2. 코드 포인트만으로 구분해야 한다면 위 U+002D 사례의 참조값은 붙임표 점형으로 정정해야 합니까? +3. `도전+(플러스)`처럼 이름 뒤에 기호의 한글 풀이를 괄호로 붙인 경우에도 제46항에 따라 `+` 양옆을 띄어야 합니까? +4. 동일 표면형의 참조값에서 공백이 서로 다른 사례는 어느 쪽으로 통일해야 합니까? + ## 4. 말뭉치 구축·정정 절차에 관한 공통 질문 1. 각 참조값을 작성할 때 적용한 한국 점자 규정 및 UEB 판본은 무엇입니까? @@ -339,11 +362,12 @@ UEB에는 `AT&T`, `B&B`, `&c`처럼 앰퍼샌드와 로마자가 붙어 있는 답변을 한 번에 모두 제공하기 어렵다면 다음 순서로 우선 확인을 부탁드립니다. -1. **제34항 괄호/로마자표 순서 1,112건**: 규정 예와 반대인 3셀 참조 서명으로 재현됨 +1. **제34항 괄호/로마자표 순서 1,114건**: 규정 예와 반대인 3셀 참조 서명으로 재현됨 2. **로마자+가운뎃점 경계 577건**: 완전 일치 대조 0건, 451건이 종료표 한 셀에 직접 위치 3. **`△한글` 공백 377건**: 완전 일치 대조 0건, 입력에 없는 공백의 허용 여부 4. **약어 발음과 UEB 약자**: 정확/불일치가 동일 철자 구조에 공존하여 발음 정보 없이는 결정 불가 5. **`A(14)`, `HCA(...)`, `F-35`**: 일반문·식별자·수식의 의미 모드 우선순위 -6. **미지원 문자 159건 및 NFKC 비교 312건**: 공식 정규화와 문자 지원 정책 +6. **U+002D 마이너스와 `+(풀이)` 공백**: 코드 포인트와 의미 정규화, 동일 표면형의 상충 참조 확인 +7. **미지원 문자 155건 및 NFKC 비교 316건**: 공식 정규화와 문자 지원 정책 위 항목들의 공식 해석을 받기 전에는 말뭉치 참조값에 맞추기 위한 개별 입력 예외나 기대값 역산 규칙을 추가하지 않고, 재현 가능한 진단으로만 보존할 예정입니다. diff --git a/libs/braillify/examples/nikl_corpus_analyze.rs b/libs/braillify/examples/nikl_corpus_analyze.rs index bd2f66a4..11e54e41 100644 --- a/libs/braillify/examples/nikl_corpus_analyze.rs +++ b/libs/braillify/examples/nikl_corpus_analyze.rs @@ -584,6 +584,8 @@ const KOREAN_MAJORITY_ROMAN_SANDWICH_NON_DOMAIN: &str = "korean_majority_same_token_roman_sandwich_non_domain"; const KOREAN_INLINE_PARENTHESIZED_OPERATOR: &str = "korean_inline_parenthesized_single_arithmetic_operator"; +const ATTACHED_PLUS_PARENTHESIZED_KOREAN_GLOSS: &str = + "attached_plus_followed_by_parenthesized_korean_gloss"; const TIGHT_TRIANGLE_BEFORE_KOREAN: &str = "tight_triangle_mark_immediately_before_korean"; const ATTACHED_KOREAN_AUXILIARY_ITDA_SPACING: &str = "attached_korean_auxiliary_itda_spacing"; const ALLCAPS_ROMAN_RUN_CONTAINING_OU: &str = "allcaps_roman_run_containing_ou"; @@ -2634,6 +2636,7 @@ fn first_difference_claimed_by_prior_localized_cohort(item: &EncodedCase) -> boo || first_difference_in_decimal_word(item) || first_difference_in_korean_prefixed_annotation_opening(item) || first_difference_in_inline_parenthesized_operator(item) + || first_difference_in_attached_plus_parenthesized_korean_gloss(item) || first_difference_in_tight_triangle(item) || first_difference_at_roman_middle_dot_boundary(item) || first_difference_in_uppercase_ascii_ampersand(item) @@ -3340,6 +3343,76 @@ fn first_difference_in_inline_parenthesized_operator(item: &EncodedCase) -> bool .any(|range| range.contains(&first_difference)) } +/// Finds `한글+(한글 풀이)` without inferring whether the plus sign is an +/// arithmetic operator or part of a brand name. The strict body grammar keeps +/// this diagnostic separate from Roman/math parentheticals. +fn attached_plus_parenthesized_korean_gloss_spans(input: &str) -> Vec { + let mut spans = Vec::new(); + for (plus_byte, marker) in input.match_indices("+(") { + if !input[..plus_byte] + .chars() + .next_back() + .is_some_and(is_korean_script) + { + continue; + } + + let body_start = plus_byte + marker.len(); + let Some(close_offset) = input[body_start..].find(')') else { + continue; + }; + let close_byte = body_start + close_offset; + let body = &input[body_start..close_byte]; + if !body.is_empty() && body.chars().all(is_korean_script) { + spans.push(InputSpan { + start_byte: plus_byte, + end_byte: close_byte + 1, + }); + } + } + spans +} + +/// Anchors each gloss at the output of the real input prefix immediately +/// before `+`, then verifies a current-engine signature made from the actual +/// input span in neutral Korean context. No corpus reference cells are used. +fn attached_plus_parenthesized_korean_gloss_actual_ranges( + input: &str, + actual: &str, +) -> Vec> { + let actual_cells = actual.chars().collect::>(); + let left_probe_cells = braillify::encode_to_unicode("가") + .expect("neutral Korean probe must encode") + .chars() + .count(); + + attached_plus_parenthesized_korean_gloss_spans(input) + .into_iter() + .filter_map(|span| { + let prefix = braillify::encode_to_unicode(&input[..span.start_byte]).ok()?; + let start = prefix.chars().count(); + let run = &input[span.start_byte..span.end_byte]; + let probe = braillify::encode_to_unicode(&format!("가{run}")).ok()?; + let signature = probe.chars().skip(left_probe_cells).collect::>(); + let end = start.checked_add(signature.len())?; + (actual_cells.get(start..end) == Some(signature.as_slice())).then_some(start..end) + }) + .collect() +} + +fn first_difference_in_attached_plus_parenthesized_korean_gloss(item: &EncodedCase) -> bool { + let Ok(actual) = &item.actual else { + return false; + }; + if actual == &item.located.case.unicode { + return false; + } + let first_difference = first_difference_cell(&item.located.case.unicode, actual); + attached_plus_parenthesized_korean_gloss_actual_ranges(&item.located.case.input, actual) + .into_iter() + .any(|range| range.contains(&first_difference)) +} + fn tight_triangle_spans(input: &str) -> Vec { input .match_indices('△') @@ -3717,6 +3790,10 @@ fn analyze( KOREAN_INLINE_PARENTHESIZED_OPERATOR.to_string(), PendingRuleReviewClusterStats::default(), ), + ( + ATTACHED_PLUS_PARENTHESIZED_KOREAN_GLOSS.to_string(), + PendingRuleReviewClusterStats::default(), + ), ( MIXED_ROMAN_KOREAN_BEFORE_HEADWORD_EXPANSION.to_string(), PendingRuleReviewClusterStats::default(), @@ -4019,6 +4096,13 @@ fn analyze( Some(first_difference_in_inline_parenthesized_operator(item)), false, ), + ( + ATTACHED_PLUS_PARENTHESIZED_KOREAN_GLOSS, + !attached_plus_parenthesized_korean_gloss_spans(&item.located.case.input) + .is_empty(), + Some(first_difference_in_attached_plus_parenthesized_korean_gloss(item)), + true, + ), ( MIXED_ROMAN_KOREAN_BEFORE_HEADWORD_EXPANSION, !mixed_roman_korean_before_headword_expansion_spans(&item.located.case.input) @@ -4655,7 +4739,12 @@ fn markdown(report: &AnalysisReport) -> String { `Korean(` + one rule-45 arithmetic operator + `)Korean` span. Unlike broad coexistence \ traits, it also locates the current engine's emitted structure and counts a mismatch as \ signature-local only when the sentence's first differing cell falls inside that output \ - range. The `allcaps_roman_run_containing_ou` gate finds maximal, alphanumeric-delimited \ + range. The `attached_plus_followed_by_parenthesized_korean_gloss` gate requires literal \ + `한글+(한글)` with a non-empty all-Korean gloss. It anchors the real prefix immediately \ + before `+` and verifies the current neutral-Korean output signature, distinguishing \ + rule-46 spacing at the sign from unrelated differences elsewhere without deciding \ + whether a name is mathematical. The `allcaps_roman_run_containing_ou` gate finds \ + maximal, alphanumeric-delimited \ uppercase ASCII runs containing adjacent `OU`. It locates the independently encoded run \ signature in the complete current output and counts only first differences inside that \ signature as localized. The `allcaps_roman_run_containing_st` gate applies the same \ @@ -6680,9 +6769,34 @@ fn markdown(report: &AnalysisReport) -> String { cases; the corpus-wide total moved from 65,491 to 65,514 (+23 exact) because the same \ PDF-backed spacing rule also applied outside the stricter Korean-boundary audit \ gate. These are immutable checkpoint counts rather than the report's later cumulative \ - total. The complete standard suite remained 5,141/5,141.\n", + total. The complete standard suite remained 5,141/5,141.\n", ); } + if let Some(stats) = report + .pending_rule_review_clusters + .get(ATTACHED_PLUS_PARENTHESIZED_KOREAN_GLOSS) + { + let pending = stats + .mismatch_primary_classes + .get("pending_rule_review") + .copied() + .unwrap_or(0); + text.push_str(&format!( + "\nCurrent attached plus + parenthesized Korean-gloss measurement: {} candidates, \ + {} exact controls, {} mismatches, {pending} members in the actual \ + `pending_rule_review` subcluster, and {}/{} evaluable mismatches whose first \ + differing cell is inside the current emitted structure. Hangeul rule 46 supplies \ + the operation-sign spacing control, but the surface form alone does not establish \ + whether a brand or program name uses `+` mathematically. Exact and localized \ + mismatch references coexist for `도전+(플러스)`, so no engine change or primary \ + reclassification is inferred.\n", + stats.candidates, + stats.exact, + stats.mismatch, + stats.first_difference_in_output_signature, + stats.output_signature_mismatches_evaluated + )); + } if let Some(stats) = report .pending_rule_review_clusters .get(TIGHT_TRIANGLE_BEFORE_KOREAN) @@ -8340,6 +8454,36 @@ mod tests { assert!(ranges[0].end <= actual.chars().count()); } + #[rstest::rstest] + #[case::program_name("도전+(플러스)", vec!["+(플러스)"])] + #[case::descriptive_gloss("자립+(더하기) 프로젝트", vec!["+(더하기)"])] + #[case::multiple("디즈니+(플러스), 애플+(플러스)", vec!["+(플러스)", "+(플러스)"])] + #[case::roman_prefix("TV+(플러스)", vec![])] + #[case::spaced_plus("도전 +(플러스)", vec![])] + #[case::roman_body("도전+(plus)", vec![])] + #[case::empty_body("도전+()", vec![])] + fn detects_attached_plus_parenthesized_korean_gloss( + #[case] input: &str, + #[case] expected: Vec<&str>, + ) { + let actual = attached_plus_parenthesized_korean_gloss_spans(input) + .into_iter() + .map(|span| &input[span.start_byte..span.end_byte]) + .collect::>(); + assert_eq!(actual, expected); + } + + #[test] + fn locates_current_engine_attached_plus_gloss_output() { + let input = "과 도전+(플러스) 프로그램"; + let actual = braillify::encode_to_unicode(input).expect("probe must encode"); + let ranges = attached_plus_parenthesized_korean_gloss_actual_ranges(input, &actual); + + assert_eq!(ranges.len(), 1); + assert!(ranges[0].start < ranges[0].end); + assert!(ranges[0].end <= actual.chars().count()); + } + #[rstest::rstest] #[case::tight("△보성군", 1)] #[case::embedded("목록 △교과전형", 1)] From 7dd4bcd7e63328948f1f7e464f8caecf709148fa Mon Sep 17 00:00:00 2001 From: devfive Date: Thu, 3 Sep 2026 18:44:18 +0900 Subject: [PATCH 74/77] Improve NIKL corpus translation accuracy --- docs/corpus-analysis/NIKL_2025_V1.md | 2893 ++++++++--------- docs/corpus-analysis/NIKL_2025_V1_inquiry.md | 233 +- .../braillify/examples/nikl_corpus_analyze.rs | 798 ++++- libs/braillify/src/encoder.rs | 6 + libs/braillify/src/english_logic.rs | 425 ++- libs/braillify/src/ipa.rs | 9 +- libs/braillify/src/lib.rs | 262 +- libs/braillify/src/rules/context.rs | 13 + libs/braillify/src/rules/emit.rs | 740 ++++- libs/braillify/src/rules/engine.rs | 3 + libs/braillify/src/rules/english_shortform.rs | 217 +- .../braillify/src/rules/english_ueb/engine.rs | 127 +- .../src/rules/english_ueb/engine/caps.rs | 19 +- .../rules/english_ueb/engine/encode_word.rs | 3 +- .../src/rules/english_ueb/engine/tokens.rs | 47 + .../rules/english_ueb/engine/word_methods.rs | 65 +- .../src/rules/english_ueb/engine/words.rs | 8 +- .../english_ueb/pronunciation/cmudict.rs | 84 + .../rules/english_ueb/pronunciation/mod.rs | 77 + .../src/rules/english_ueb/rule_10_9.rs | 77 +- libs/braillify/src/rules/english_ueb/span.rs | 8 + libs/braillify/src/rules/korean/rule_18.rs | 1 + libs/braillify/src/rules/korean/rule_23.rs | 3 + libs/braillify/src/rules/korean/rule_25.rs | 1 + libs/braillify/src/rules/korean/rule_27.rs | 49 +- libs/braillify/src/rules/korean/rule_28.rs | 382 ++- libs/braillify/src/rules/korean/rule_40.rs | 86 +- libs/braillify/src/rules/korean/rule_44.rs | 31 +- libs/braillify/src/rules/korean/rule_49.rs | 2 + libs/braillify/src/rules/korean/rule_53.rs | 1 + libs/braillify/src/rules/korean/rule_57.rs | 4 + libs/braillify/src/rules/korean/rule_68.rs | 35 +- libs/braillify/src/rules/korean/rule_69.rs | 653 +++- libs/braillify/src/rules/korean/rule_70.rs | 20 + libs/braillify/src/rules/korean/rule_71.rs | 38 + libs/braillify/src/rules/korean/rule_72.rs | 203 +- .../src/rules/korean/rule_english_symbol.rs | 181 +- libs/braillify/src/rules/korean/rule_math.rs | 409 ++- .../src/rules/math/encoder/symbol_rule.rs | 42 +- libs/braillify/src/rules/roman_mode.rs | 6 +- .../english_dominant_korean_wrap.rs | 54 +- .../src/rules/token_rules/latex_math.rs | 20 + .../src/rules/token_rules/math_expression.rs | 268 +- .../token_rules/math_expression/apply.rs | 1161 ++++++- .../token_rules/math_expression/detect.rs | 28 +- .../token_rules/math_expression/helpers.rs | 341 +- .../rules/token_rules/middle_dot_spacing.rs | 125 +- .../src/rules/token_rules/normalize.rs | 194 ++ .../rules/token_rules/uppercase_passage.rs | 415 ++- libs/braillify/src/symbol_shortcut.rs | 19 +- .../coverage_extra__korean_math_squared.snap | 4 +- .../coverage_extra__negation_ab.snap | 4 +- .../coverage_extra__negation_pq.snap | 4 +- .../coverage_extra__negation_xy.snap | 4 +- ...erage_extra2__eng_dom_sentence_period.snap | 4 +- .../coverage_extra2__neg_upper_upper.snap | 4 +- .../coverage_extra2__neg_var_upper.snap | 4 +- uv.lock | 2 +- 58 files changed, 8818 insertions(+), 2098 deletions(-) diff --git a/docs/corpus-analysis/NIKL_2025_V1.md b/docs/corpus-analysis/NIKL_2025_V1.md index bfd22b47..fb5903d0 100644 --- a/docs/corpus-analysis/NIKL_2025_V1.md +++ b/docs/corpus-analysis/NIKL_2025_V1.md @@ -7,33 +7,34 @@ | Metric | Count | |---|---:| | Total | 83528 | -| Exact | 69430 | -| Mismatch | 14098 | -| Exact accuracy | 83.12% | +| Exact | 75785 | +| Mismatch | 7743 | +| Exact accuracy | 90.73% | | Duplicate records | 0 | | Inputs with conflicting references | 0 | ## Classification policy -Primary classes are evidence gates, not permissions to change the engine. `implementation_defect` is restricted to defects independently confirmed from the PDF (currently the rules 28/29 roman-indicator ordering signature). `unsupported_character_review` contains encoding failures fully explained by one or more singleton characters whose support obligation has not been confirmed from the PDF. `unclassified_encoding_error_review` contains other encoding failures until a PDF-backed implementation obligation or a reproducible comparison/corpus issue is established. `pending_rule_review` contains foreign-text, number, punctuation, and Korean candidates that have not yet been resolved against the PDF. `corpus_suspect` is reserved for independently detectable contradictions such as one input having multiple references. `comparison_method` requires equality after a named normalization. +Primary classes are evidence gates, not permissions to change the engine. `implementation_defect` is restricted to defects independently confirmed from the PDF (currently the rules 28/29 roman-indicator ordering signature). `unsupported_character_review` contains encoding failures fully explained by one or more singleton characters whose support obligation has not been confirmed from the PDF. `unclassified_encoding_error_review` contains other encoding failures until a PDF-backed implementation obligation or a reproducible comparison/corpus issue is established. `pending_rule_review` contains foreign-text, number, punctuation, and Korean candidates that have not yet been resolved against the PDF. `corpus_suspect` is reserved for independently detectable contradictions: conflicting duplicate references or a localized reference-cell signature that contradicts an explicit PDF/UEB rule. `comparison_method` requires equality after a named normalization. | Primary class | Count | |---|---:| -| `comparison_method` | 316 | -| `corpus_suspect` | 1114 | -| `exact` | 69430 | -| `pending_rule_review` | 12513 | -| `unsupported_character_review` | 155 | +| `corpus_suspect` | 1234 | +| `exact` | 75785 | +| `pending_rule_review` | 6505 | +| `unsupported_character_review` | 4 | | Reproducible reason | Count | |---|---:| -| `exact` | 69430 | -| `foreign_text_rule_review` | 12361 | -| `nfkc_input_equivalent` | 316 | -| `number_rule_review` | 144 | -| `punctuation_rule_review` | 8 | -| `rule34_roman_indicator_before_opening_parenthesis` | 1114 | -| `unsupported_character_review` | 155 | +| `exact` | 75785 | +| `foreign_text_rule_review` | 6456 | +| `number_rule_review` | 45 | +| `punctuation_rule_review` | 4 | +| `roman_ellipsis_uses_korean_cells_in_roman_enclosure` | 1 | +| `rule34_roman_indicator_before_opening_parenthesis` | 1160 | +| `ueb_capitalized_passage_written_as_separate_capital_words` | 3 | +| `ueb_grade1_before_nonstanding_opening_parenthesis` | 70 | +| `unsupported_character_review` | 4 | ## Pending first-difference cell transitions @@ -41,49 +42,26 @@ This ranking is a diagnostic selector, not an implementation rule. It counts onl | Rank | Expected → actual first cell | Cases | |---:|---|---:| -| 1 | `U+2834 ⠴ -> U+2800 ⠀` | 2073 | -| 2 | `U+2815 ⠕ -> U+2833 ⠳` | 1369 | -| 3 | `U+2830 ⠰ -> U+2820 ⠠` | 870 | -| 4 | `U+2810 ⠐ -> U+2832 ⠲` | 721 | -| 5 | `U+2826 ⠦ -> U+2834 ⠴` | 471 | -| 6 | `U+280E ⠎ -> U+280C ⠌` | 465 | -| 7 | `U+2826 ⠦ -> U+2800 ⠀` | 445 | -| 8 | `U+2801 ⠁ -> U+281C ⠜` | 408 | -| 9 | `U+2800 ⠀ -> U+2807 ⠇` | 365 | -| 10 | `U+283C ⠼ -> U+2800 ⠀` | 365 | -| 11 | `U+2834 ⠴ -> U+2830 ⠰` | 359 | -| 12 | `U+2811 ⠑ -> U+282B ⠫` | 331 | -| 13 | `U+2820 ⠠ -> U+2830 ⠰` | 247 | -| 14 | `U+2826 ⠦ -> U+2810 ⠐` | 174 | -| 15 | `U+2800 ⠀ -> U+2832 ⠲` | 163 | -| 16 | `U+2820 ⠠ -> U+2809 ⠉` | 151 | -| 17 | `U+281B ⠛ -> U+2823 ⠣` | 144 | -| 18 | `U+2810 ⠐ -> U+2800 ⠀` | 131 | -| 19 | `U+2811 ⠑ -> U+283B ⠻` | 106 | -| 20 | `U+2810 ⠐ -> U+2814 ⠔` | 98 | - -### `U+2834 ⠴ -> U+2800 ⠀` - -- `sentence_01.json` #35: 삼성전자는 올해 CES에서 77인치 OLED TV를 처음으로 공개할 예정이다. 이는 지난 8월 ‘국제정보디스플레이학술대회(IMID) 2022’에서 삼성디스플레이가 공개한 77인치 퀀텀닷(QD)-OLED 패널을 탑재한 제품이다. - - expected: `⠋⠏⠒⠓⠎⠢⠊⠄⠴⠐⠣⠟⠙⠐⠜⠤⠠⠠⠕⠇⠫⠲⠀⠙` - - actual: `⠋⠏⠒⠓⠎⠢⠊⠄⠀⠀⠦⠠⠠⠟⠙⠴⠔⠠⠠⠕⠇⠑⠙⠀` - - first differing cell (zero-based): 172 - - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_02.json` #186: 3일 사회관계망서비스(SNS)를 통해 유포된 동영상에는 중학생인 A(14)양이 지난달 30일 태안의 한 지하 주차장에서 B(15)양으로부터 일방적으로 폭행을 당하고 주변에 있던 학생들은 이를 웃으며 방관하는 장면이 담겼다. - - expected: `⠶⠚⠁⠠⠗⠶⠟⠀⠴⠠⠁⠦⠄⠼⠁⠙⠠⠴⠜⠶⠕⠀⠨⠕` - - actual: `⠶⠚⠁⠠⠗⠶⠟⠀⠀⠁⠦⠼⠁⠙⠴⠀⠀⠜⠶⠕⠀⠨⠕⠉` - - first differing cell (zero-based): 65 - - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_03.json` #51: 20일 한국장기조직기증원에 따르면 A(11)군은 지난 3일 학교에 가기 위해 횡단보도를 건너다가 시내버스에 치여 병원으로 이송돼 치료받았지만 회복하지 못하고 뇌사 상태에 빠졌다. - - expected: `⠀⠠⠊⠐⠪⠑⠡⠀⠴⠠⠁⠦⠄⠼⠁⠁⠠⠴⠈⠛⠵⠀⠨⠕` - - actual: `⠀⠠⠊⠐⠪⠑⠡⠀⠀⠁⠦⠼⠁⠁⠴⠀⠀⠈⠛⠵⠀⠨⠕⠉` - - first differing cell (zero-based): 36 - - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_04.json` #323: 사단급 규모의 상륙군과 대형수송함(LPH) 독도함, 미 강습상륙함(LHD) 마킨 아일랜드함 등 30여척, 미 F-35 전투기·육군 아파치 공격헬기(AH-64)·마린온 상륙기동헬기 등 항공기 70여대, 상륙돌격장갑차(KAAV) 50여대 등 연합·합동전력이 투입됐다. - - expected: `⠱⠰⠹⠐⠀⠑⠕⠀⠴⠠⠋⠤⠼⠉⠑⠀⠨⠾⠓⠍⠈⠕⠐⠆` - - actual: `⠱⠰⠹⠐⠀⠑⠕⠀⠀⠠⠋⠔⠼⠉⠑⠀⠨⠾⠓⠍⠈⠕⠐⠆` - - first differing cell (zero-based): 108 - - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +| 1 | `U+2815 ⠕ -> U+2833 ⠳` | 1509 | +| 2 | `U+2810 ⠐ -> U+2832 ⠲` | 648 | +| 3 | `U+280E ⠎ -> U+280C ⠌` | 473 | +| 4 | `U+2801 ⠁ -> U+281C ⠜` | 413 | +| 5 | `U+2811 ⠑ -> U+282B ⠫` | 344 | +| 6 | `U+281B ⠛ -> U+2823 ⠣` | 147 | +| 7 | `U+2826 ⠦ -> U+2834 ⠴` | 122 | +| 8 | `U+2811 ⠑ -> U+283B ⠻` | 101 | +| 9 | `U+2800 ⠀ -> U+2832 ⠲` | 88 | +| 10 | `U+2826 ⠦ -> U+2810 ⠐` | 84 | +| 11 | `U+280E ⠎ -> U+2829 ⠩` | 79 | +| 12 | `U+2811 ⠑ -> U+2822 ⠢` | 68 | +| 13 | `U+2810 ⠐ -> U+2815 ⠕` | 65 | +| 14 | `U+2820 ⠠ -> U+2830 ⠰` | 64 | +| 15 | `U+2826 ⠦ -> U+2800 ⠀` | 63 | +| 16 | `U+2820 ⠠ -> U+2834 ⠴` | 61 | +| 17 | `U+280A ⠊ -> U+2814 ⠔` | 60 | +| 18 | `U+283C ⠼ -> U+2800 ⠀` | 58 | +| 19 | `U+2834 ⠴ -> U+2800 ⠀` | 55 | +| 20 | `U+2824 ⠤ -> U+2800 ⠀` | 54 | ### `U+2815 ⠕ -> U+2833 ⠳` @@ -102,33 +80,10 @@ This ranking is a diagnostic selector, not an implementation rule. It counts onl - actual: `⠠⠎⠦⠄⠴⠠⠠⠍⠳⠠⠴⠮⠀⠰⠝⠈⠳⠚⠗⠌⠊⠲` - first differing cell (zero-based): 175 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_04.json` #195: HD현대는 23일 서울시 성북구 고려대학교 본관에서 HD현대 정기선 사장, 고려대학교 김동원 총장 등 관계자가 참석한 가운데 고려대학교와 ‘미래 인재 육성 산학협력을 위한 업무협약(MOU)’을 체결했다고 밝혔다. - - expected: `⠜⠁⠦⠄⠴⠠⠠⠍⠕⠥⠠⠴⠴⠄⠮⠀⠰⠝⠈⠳⠚⠗⠌⠊` - - actual: `⠜⠁⠦⠄⠴⠠⠠⠍⠳⠠⠴⠴⠄⠮⠀⠰⠝⠈⠳⠚⠗⠌⠊⠈` - - first differing cell (zero-based): 185 - - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` - -### `U+2830 ⠰ -> U+2820 ⠠` - -- `sentence_01.json` #312: 엔비디아가 대규모 언어 모델(LLM)과 음성 AI를 주제로 엔비디아 AI 개발자 밋업을 개최한다. 이번 개발자 밋업은 오는 17일 오후 2시부터 5시까지 온라인으로 진행된다. - - expected: `⠑⠥⠊⠝⠂⠦⠄⠴⠰⠠⠠⠇⠇⠍⠠⠴⠈⠧⠀⠪⠢⠠⠻⠀` - - actual: `⠑⠥⠊⠝⠂⠦⠄⠴⠠⠠⠇⠇⠍⠠⠴⠈⠧⠀⠪⠢⠠⠻⠀⠴` - - first differing cell (zero-based): 27 - - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_02.json` #99: 황승주 씨는 “기능성 소화불량증, 특히 PDS(식후불편감증후군) 복부팽만감 유형으로 고통 받는 환자들에게 적용가능한 안전하고 높은 치료효율의 한약소재를 개발해 국민건강에 이바지하고자 한다”고 말했다. - - expected: `⠀⠓⠪⠁⠚⠕⠀⠴⠰⠠⠠⠏⠙⠎⠦⠄⠠⠕⠁⠚⠍⠘⠯⠙` - - actual: `⠀⠓⠪⠁⠚⠕⠀⠴⠠⠠⠏⠙⠎⠦⠄⠠⠕⠁⠚⠍⠘⠯⠙⠡` - - first differing cell (zero-based): 45 - - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_03.json` #30: “챗GPT보다 가벼운 여러 대규모언어모델(LLM)이 출현해 오픈AI 독점 구도에 균열을 내고 있다. 한국어 특화 LLM으로 글로벌 경쟁에 대응하면서 세부 서비스(튜닝) 모델에서 차별성을 확보하는 전략이 중요하다.” - - expected: `⠑⠥⠊⠝⠂⠦⠄⠴⠰⠠⠠⠇⠇⠍⠠⠴⠕⠀⠰⠯⠚⠡⠚⠗` - - actual: `⠑⠥⠊⠝⠂⠦⠄⠴⠠⠠⠇⠇⠍⠠⠴⠕⠀⠰⠯⠚⠡⠚⠗⠀` - - first differing cell (zero-based): 40 - - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_04.json` #81: SGI는 “산업연관분석을 활용해 우리나라의 대일 수출 증가가 국내총생산(GDP)에 미치는 영향을 계산해 보면 경제성장률은 0.1%포인트 높아질 것”이라고 밝혔다. - - expected: `⠠⠗⠶⠇⠒⠦⠄⠴⠰⠠⠠⠛⠙⠏⠠⠴⠝⠀⠑⠕⠰⠕⠉⠵` - - actual: `⠠⠗⠶⠇⠒⠦⠄⠴⠠⠠⠛⠙⠏⠠⠴⠝⠀⠑⠕⠰⠕⠉⠵⠀` - - first differing cell (zero-based): 72 +- `sentence_04.json` #191: SK온은 23일 서울 종로구 SK서린사옥에서 에코프로머티리얼즈, 중국의 GEM(거린메이)과 전구체 생산을 위한 3자 합작법인인 지이엠코리아뉴에너지머티리얼즈㈜(이하 지이엠코리아) 설립 양해각서(MOU)를 체결했다고 밝혔다. + - expected: `⠠⠎⠦⠄⠴⠠⠠⠍⠕⠥⠠⠴⠐⠮⠀⠰⠝⠈⠳⠚⠗⠌⠊⠈` + - actual: `⠠⠎⠦⠄⠴⠠⠠⠍⠳⠠⠴⠐⠮⠀⠰⠝⠈⠳⠚⠗⠌⠊⠈⠥` + - first differing cell (zero-based): 191 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` ### `U+2810 ⠐ -> U+2832 ⠲` @@ -143,10 +98,10 @@ This ranking is a diagnostic selector, not an implementation rule. It counts onl - actual: `⠚⠗⠀⠴⠠⠠⠁⠊⠲⠐⠆⠴⠠⠠⠎⠺⠲⠈⠬⠩⠁⠀⠈⠕` - first differing cell (zero-based): 93 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_03.json` #17: 재테크 시장에서 온·오프라인 플랫폼 ‘매경 엠플러스(M+)’가 주목받고 있다. M+는 거시경제, 주식, 부동산, 가상자산 등 재테크의 모든 것을 온라인 사이트를 통해 제공하고 있다. - - expected: `⠎⠠⠪⠦⠄⠴⠠⠍⠐⠖⠠⠴⠴⠄⠫⠀⠨⠍⠑⠭⠘⠔⠈⠥` - - actual: `⠎⠠⠪⠦⠄⠴⠠⠍⠲⠀⠢⠀⠠⠴⠴⠄⠫⠀⠨⠍⠑⠭⠘⠔` - - first differing cell (zero-based): 54 +- `sentence_03.json` #91: 지난 13일 브라질에서 우수제조관리기준(BGMP) 인증을 받으며 중남미시장 진출에도 첫발을 내디뎠다. BGMP 인증으로 HA·Ca필러가 브라질을 비롯한 남미 국가들에 진입하는 데 속도가 붙을 것이란 전망이다. + - expected: `⠐⠥⠀⠴⠠⠠⠓⠁⠐⠆⠴⠠⠉⠁⠲⠙⠕⠂⠐⠎⠫⠀⠘⠪` + - actual: `⠐⠥⠀⠴⠠⠠⠓⠁⠲⠐⠆⠴⠠⠉⠁⠲⠙⠕⠂⠐⠎⠫⠀⠘` + - first differing cell (zero-based): 121 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` - `sentence_04.json` #49: 유럽중앙은행(ECB)이 크레디트스위스(CS)의 유동성 위기에도 ‘빅스텝’(기준금리를 한 번에 0.5%포인트 인상)을 단행하자, 미국 연방준비제도(Fed·연준) 역시 금리 인상을 멈추지 않을 것이라는 전망이 힘을 얻고 있다. - expected: `⠊⠥⠦⠄⠴⠠⠋⠫⠐⠆⠡⠨⠛⠠⠴⠀⠱⠁⠠⠕⠀⠈⠪⠢` @@ -154,29 +109,6 @@ This ranking is a diagnostic selector, not an implementation rule. It counts onl - first differing cell (zero-based): 149 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -### `U+2826 ⠦ -> U+2834 ⠴` - -- `sentence_01.json` #38: 올해 소프트웨어(SW) 업계는 국내 인공지능(AI) 제품·서비스 고도화와 함께 전 산업영역에서 SW영역 확대가 가시화될 것으로 보인다. 또한 블록체인이 웹3(Web3)와 연계해 탈중앙화·탈독점화 경제체계로 진화할 것으로 예상된다. - - expected: `⠟⠕⠀⠏⠗⠃⠼⠉⠦⠄⠴⠠⠺⠑⠃⠼⠉⠠⠴⠧⠀⠡⠈⠌` - - actual: `⠟⠕⠀⠏⠗⠃⠼⠉⠴⠐⠣⠠⠺⠑⠃⠼⠉⠴⠐⠜⠲⠧⠀⠡` - - first differing cell (zero-based): 155 - - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_02.json` #38: 중국금형전시회(DMC) 및 독일금형전시회 (Moulding Expo)와 함께 세계 3대 규모의 금형산업 전문 전시회이며, 연관산업 전문 전시회를 KOPLAS와 동시 개최를 통한 시너지를 창출했다. - - expected: `⠻⠨⠾⠠⠕⠚⠽⠀⠦⠄⠴⠠⠍⠳⠇⠙⠬⠀⠠⠑⠭⠏⠕⠠` - - actual: `⠻⠨⠾⠠⠕⠚⠽⠀⠴⠐⠣⠠⠍⠳⠇⠙⠬⠀⠠⠑⠭⠏⠕⠐` - - first differing cell (zero-based): 48 - - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_03.json` #536: 뿐만아니라 윤 대통령은 조 바이든 미국 대통령과 26일(현지시간) 한미 정상회담에서 ‘워싱턴 선언’(Washington Declaration)을 통해 핵협의그룹(NCG)을 신설하는 등 확장억제력을 대폭 강화하는 데 합의했다. - - expected: `⠓⠾⠀⠠⠾⠾⠴⠄⠦⠄⠴⠠⠺⠁⠩⠬⠞⠕⠝⠀⠠⠙⠑⠉` - - actual: `⠓⠾⠀⠠⠾⠾⠴⠄⠴⠐⠣⠠⠺⠁⠩⠬⠞⠕⠝⠀⠠⠙⠑⠉` - - first differing cell (zero-based): 95 - - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_04.json` #582: 반면 마이크로소프트(MS) 공동창업자인 빌 게이츠는 “(AI 개발 일시 중단은) 문제를 해결하지 못할 것”이라며 “우리가 해야 할 일은 AI의 까다로운 문제들을 파악하는 것”이라고 말했다. - - expected: `⠝⠕⠰⠪⠉⠵⠀⠦⠦⠄⠴⠠⠠⠁⠊⠲⠀⠈⠗⠘⠂⠀⠕⠂` - - actual: `⠝⠕⠰⠪⠉⠵⠀⠦⠴⠐⠣⠠⠠⠁⠊⠲⠀⠈⠗⠘⠂⠀⠕⠂` - - first differing cell (zero-based): 54 - - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` - ### `U+280E ⠎ -> U+280C ⠌` - `sentence_01.json` #167: 올해 전 세계 반도체 시장 전망도 암울하다. 세계반도체시장통계기구(WSTS)에 따르면 올해 글로벌 반도체 매출은 5천565억 달러(약 706조5천880억원)로 지난해(5천801억달러) 대비 4.1% 감소할 것으로 조사됐다. @@ -200,29 +132,6 @@ This ranking is a diagnostic selector, not an implementation rule. It counts onl - first differing cell (zero-based): 53 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -### `U+2826 ⠦ -> U+2800 ⠀` - -- `sentence_01.json` #234: 안랩은 블록체인 자회사 안랩블록체인컴퍼니가 웹(Web)3 지갑인 ‘ABC 월렛(Wallet)’ 모바일 버전을 구글플레이와 애플 앱스토어에 출시했다고 10일 발표했다. - - expected: `⠎⠉⠕⠫⠀⠏⠗⠃⠦⠄⠴⠠⠺⠑⠃⠠⠴⠼⠉⠀⠨⠕⠫⠃` - - actual: `⠎⠉⠕⠫⠀⠏⠗⠃⠀⠀⠦⠠⠺⠑⠃⠴⠼⠉⠀⠨⠕⠫⠃⠟` - - first differing cell (zero-based): 44 - - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_02.json` #168: 5주 과정인 ‘도전’ 프로그램과 5개월 과정인 ‘도전+(플러스)’ 프로그램으로 나뉘어 진행되며, 프로그램 이수 시 1개월에 50만 원씩 최대 300만 원까지 참여 수당과 인센티브를 지원한다. - - expected: `⠠⠦⠊⠥⠨⠾⠀⠢⠦⠄⠙⠮⠐⠎⠠⠪⠠⠴⠴⠄⠀⠙⠪⠐` - - actual: `⠠⠦⠊⠥⠨⠾⠀⠢⠀⠦⠄⠙⠮⠐⠎⠠⠪⠠⠴⠴⠄⠀⠙⠪` - - first differing cell (zero-based): 53 - - current primary/reason: `pending_rule_review` / `number_rule_review` -- `sentence_03.json` #64: SK에코플랜트는 20일 종로구 수송동 본사에서 현대엔지니어링, USNC와 ‘수소 마이크로 허브(H2 Micro Hub)’ 구축을 위한 3자간 업무협약(MOU)을 체결했다고 밝혔다. - - expected: `⠪⠐⠥⠀⠚⠎⠘⠪⠦⠄⠴⠠⠓⠼⠃⠀⠠⠍⠊⠉⠗⠕⠀⠠` - - actual: `⠪⠐⠥⠀⠚⠎⠘⠪⠀⠀⠦⠠⠓⠼⠃⠀⠴⠠⠍⠊⠉⠗⠕⠀` - - first differing cell (zero-based): 91 - - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_04.json` #185: 과기정통부가 같은 날 발표한 ‘2022년 디지털정보격차 실태조사’ 결과에서는 지난해 저소득층·장애인·농어민·고령층 등 취약계층의 디지털정보화(온라인·모바일·PC 활용) 수준이 국민 평균(100) 대비 76.2 정도로 나타났다. 전년(75.4) 대비 소폭 올랐다. - - expected: `⠌⠊⠲⠀⠨⠾⠉⠡⠦⠄⠼⠛⠑⠲⠙⠠⠴⠀⠊⠗⠘⠕⠀⠠` - - actual: `⠌⠊⠲⠀⠨⠾⠉⠡⠀⠀⠦⠼⠛⠑⠲⠙⠴⠀⠊⠗⠘⠕⠀⠠` - - first differing cell (zero-based): 228 - - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` - ### `U+2801 ⠁ -> U+281C ⠜` - `sentence_01.json` #142: ‘메타버스존’에서는 증강현실(AR)·가상현실(VR)에 적용되는 혁신기술이 소개된다. 글라스를 착용하고 최첨단 3D 센싱모듈이 구현하는 가상현실을 관람객이 직접 체험할 수 있도록 했다. @@ -246,148 +155,171 @@ This ranking is a diagnostic selector, not an implementation rule. It counts onl - first differing cell (zero-based): 14 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -### `U+2800 ⠀ -> U+2807 ⠇` +### `U+2811 ⠑ -> U+282B ⠫` -- `sentence_01.json` #3907: 웹케시그룹은 △청구·결제·수납 솔루션 ‘위빌(WeBILL)’ △글로벌 중견·대기업 자금관리 솔루션 ‘위엠비에이(WeMBA)’ △전자세금계산서 발행 솔루션 ‘위택스(WeTAX)’ △글로벌 통합 자금관리 시스템 ‘위지엠비에이(WeGMBA)’ 등의 글로벌 전략 상품도 순차적으로 출시 예정이다. - - expected: `⠪⠐⠍⠃⠵⠀⠸⠬⠀⠰⠻⠈⠍⠐⠆⠈⠳⠨⠝⠐⠆⠠⠍⠉` - - actual: `⠪⠐⠍⠃⠵⠀⠸⠬⠇⠰⠻⠈⠍⠐⠆⠈⠳⠨⠝⠐⠆⠠⠍⠉` - - first differing cell (zero-based): 16 +- `sentence_01.json` #51: 와이캅 픽셀은 와이어·패키지·렌즈가 필요 없는 ‘와이캅’ 기술을 기반으로 한다. 적녹청(RGB) 3개의 마이크로 LED를 수직 방향으로 적층한 세계 최초의 풀 컬러 원칩 기술이다. + - expected: `⠪⠐⠥⠀⠴⠠⠠⠇⠑⠙⠲⠐⠮⠀⠠⠍⠨⠕⠁⠀⠘⠶⠚⠜` + - actual: `⠪⠐⠥⠀⠴⠠⠠⠇⠫⠲⠐⠮⠀⠠⠍⠨⠕⠁⠀⠘⠶⠚⠜⠶` + - first differing cell (zero-based): 107 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_02.json` #17: 시범사업은 서해안권·백제문화권·서부내륙권을 대표하는 4개 시군의 관광자원 특성을 반영한 △문화치유 △해양치유 △마을맞춤 △엠지(MZ)맞춤 등 유형별 코스를 집중 발굴해 워케이션 상품을 기획했다. - - expected: `⠘⠒⠻⠚⠒⠀⠸⠬⠀⠑⠛⠚⠧⠰⠕⠩⠀⠸⠬⠀⠚⠗⠜⠶` - - actual: `⠘⠒⠻⠚⠒⠀⠸⠬⠇⠑⠛⠚⠧⠰⠕⠩⠀⠸⠬⠇⠚⠗⠜⠶` - - first differing cell (zero-based): 94 +- `sentence_02.json` #33: 지원은 학생이 영재교육종합데이터베이스(GED)를 통해 원서 접수하고 교사의 추천을 받아 진행하고, 자세한 내용은 학교 및 영재교육 기관별 홈페이지에 탑재되어 있다. + - expected: `⠠⠪⠦⠄⠴⠠⠠⠛⠑⠙⠠⠴⠐⠮⠀⠓⠿⠚⠗⠀⠏⠒⠠⠎` + - actual: `⠠⠪⠦⠄⠴⠠⠠⠛⠫⠠⠴⠐⠮⠀⠓⠿⠚⠗⠀⠏⠒⠠⠎⠀` + - first differing cell (zero-based): 40 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_03.json` #244: 올해 모집 분야는 △에듀테크&콘텐츠 △라이프스타일 △정보통신기술(ICT)&디지털 기반 혁신기술 등이다. 시리즈A 단계까지 법인 등록 스타트업이면 지원할 수 있다. - - expected: `⠘⠛⠜⠉⠵⠀⠸⠬⠀⠝⠊⠩⠓⠝⠋⠪⠴⠈⠯⠲⠋⠷⠓⠝` - - actual: `⠘⠛⠜⠉⠵⠀⠸⠬⠇⠝⠊⠩⠓⠝⠋⠪⠴⠈⠯⠲⠋⠷⠓⠝` - - first differing cell (zero-based): 19 +- `sentence_03.json` #209: 포드의 글로벌 트럭 디자인 DNA를 토대로 강인하면서도 다양한 사용 목적에 부합하는 실용적인 내·외부를 구현했다. 전면부 시그니처 C클램프 헤드라이트, 매트릭스 LED 헤드라이트, 포드(FORD) 레터링이 특징이다. + - expected: `⠁⠠⠪⠀⠴⠠⠠⠇⠑⠙⠲⠀⠚⠝⠊⠪⠐⠣⠕⠓⠪⠐⠀⠙` + - actual: `⠁⠠⠪⠀⠴⠠⠠⠇⠫⠲⠀⠚⠝⠊⠪⠐⠣⠕⠓⠪⠐⠀⠙⠥` + - first differing cell (zero-based): 159 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_04.json` #7059: 현재 남양주시의 놀이체험시설은 △놀자람(화도) △까꿍놀이터(진접) △도르르(호평) △북(Book)놀이터(별내) △아이꿈놀이터(와부) 총 5개소로, 기존에 무료로 운영 중인 아이꿈놀이터를 제외한 유료 시설 4개소에 대해 무료 서비스가 제공된다. - - expected: `⠠⠕⠠⠞⠵⠀⠸⠬⠀⠉⠥⠂⠨⠐⠣⠢⠦⠄⠚⠧⠊⠥⠠⠴` - - actual: `⠠⠕⠠⠞⠵⠀⠸⠬⠇⠉⠥⠂⠨⠐⠣⠢⠦⠄⠚⠧⠊⠥⠠⠴` - - first differing cell (zero-based): 32 +- `sentence_04.json` #388: 김 신부는 그동안 제작한 스테인드글라스 작품은 물론 회화·LED(발광다이오드)조명작품·도자기 등 60여점의 작품을 전시한다. 그는 “형상을 떠난 자유로움과 원초적인 아름다움에 대한 깊이를 관람객들에게 전달하고 싶다”고 밝혔다. + - expected: `⠚⠧⠐⠆⠴⠠⠠⠇⠑⠙⠦⠄⠘⠂⠈⠧⠶⠊⠣⠕⠥⠊⠪⠠` + - actual: `⠚⠧⠐⠆⠴⠠⠠⠇⠫⠦⠄⠘⠂⠈⠧⠶⠊⠣⠕⠥⠊⠪⠠⠴` + - first differing cell (zero-based): 61 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -### `U+283C ⠼ -> U+2800 ⠀` +### `U+281B ⠛ -> U+2823 ⠣` -- `sentence_01.json` #426: LG에너지솔루션은 혼다에 앞서 미국 제너럴모터스(GM)과의 배터리 합작법인 ‘얼티엄셀즈’를 설립해 투자를 진행 중이다. 이를 통해 북미 생산 능력을 올해 13GWh 규모에서 2025년 255GWh까지 확대할 예정이다. - - expected: `⠁⠮⠀⠥⠂⠚⠗⠀⠼⠁⠉⠴⠠⠛⠠⠺⠓⠲⠀⠈⠩⠑⠥⠝` - - actual: `⠁⠮⠀⠥⠂⠚⠗⠀⠀⠼⠁⠉⠠⠠⠛⠺⠓⠀⠀⠈⠩⠑⠥⠝` - - first differing cell (zero-based): 156 +- `sentence_01.json` #1753: 지난해 말 경기주택도시공사(GH)에서 퇴직한 전씨는 ‘성남FC 불법 후원금 의혹’과 관련해 검찰 조사를 받은 바 있으며 ‘GH 합숙소 의혹’에도 연루된 것으로 알려졌다. + - expected: `⠈⠿⠇⠦⠄⠴⠠⠠⠛⠓⠠⠴⠝⠠⠎⠀⠓⠽⠨⠕⠁⠚⠒⠀` + - actual: `⠈⠿⠇⠦⠄⠴⠠⠠⠣⠠⠴⠝⠠⠎⠀⠓⠽⠨⠕⠁⠚⠒⠀⠨` + - first differing cell (zero-based): 31 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_02.json` #893: 그러나, 이 아파트들은 금강 제1철교 반경 50~800m 내에 위치해 있어 철도소음 영향을 많이 받고 있다. 특히, 금강센트럴파크서희스타힐스는 야간소음이 67dB로 기준치(60dB)를 초과하고 있다. - - expected: `⠈⠬⠀⠘⠒⠈⠻⠀⠼⠑⠚⠈⠔⠼⠓⠚⠚⠴⠍⠲⠀⠉⠗⠝` - - actual: `⠈⠬⠀⠘⠒⠈⠻⠀⠀⠼⠑⠚⠈⠔⠼⠓⠚⠚⠍⠀⠀⠉⠗⠝` - - first differing cell (zero-based): 34 +- `sentence_02.json` #24: 대전대학교(총장 남상호)는 창업보육센터 입주기업인 ㈜티알(대표 김병수)이 최근 트랜스글로벌헬스케어(TGH)와 세계최초 AI 기반 만성폐쇄성폐질환(COPD) 진단기인 ‘The Spirokit’(더스피로킷)에 대한 물품공급 계약을 체결했다고 지난 14일 밝혔다. + - expected: `⠝⠎⠦⠄⠴⠠⠠⠞⠛⠓⠠⠴⠧⠀⠠⠝⠈⠌⠰⠽⠰⠥⠀⠴` + - actual: `⠝⠎⠦⠄⠴⠠⠠⠞⠣⠠⠴⠧⠀⠠⠝⠈⠌⠰⠽⠰⠥⠀⠴⠠` + - first differing cell (zero-based): 113 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_03.json` #59: 이들 중 A씨(31) 등 2명은 지난 3월 25일 김해공항으로 필로폰 968g 등을 팬티 속에 숨겨 입국한 혐의를 받고 있다. - - expected: `⠙⠕⠂⠐⠥⠙⠷⠀⠼⠊⠋⠓⠴⠛⠲⠀⠊⠪⠶⠮⠀⠙⠗⠒` - - actual: `⠙⠕⠂⠐⠥⠙⠷⠀⠀⠼⠊⠋⠓⠛⠀⠀⠊⠪⠶⠮⠀⠙⠗⠒` - - first differing cell (zero-based): 71 +- `sentence_03.json` #292: GH 주거 분야 전문인력과 HUG(주택도시보증공사), 변호사, 법무사 등 부동산·금융 전문인력이 상주하며 전세사기 피해자에게 부동산 법률, 긴급 금융지원, 주거지원 등 종합적인 맞춤형 상담을 제공한다. + - expected: `⠴⠠⠠⠛⠓⠲⠀⠨⠍⠈⠎⠀⠘⠛⠜⠀⠨⠾⠑⠛⠟⠐⠱⠁` + - actual: `⠴⠠⠠⠣⠲⠀⠨⠍⠈⠎⠀⠘⠛⠜⠀⠨⠾⠑⠛⠟⠐⠱⠁⠈` + - first differing cell (zero-based): 3 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_04.json` #538: 중국 전기차 제조업체 비야디(BYD)가 6일 1t 전기트럭 ‘T4K(티포케이)’을 국내에 출시했다. BYD는 전 세계 1위 전기차 업체다. - - expected: `⠴⠫⠀⠼⠋⠕⠂⠀⠼⠁⠴⠞⠲⠀⠨⠾⠈⠕⠓⠪⠐⠹⠀⠠` - - actual: `⠴⠫⠀⠼⠋⠕⠂⠀⠀⠼⠁⠞⠀⠀⠨⠾⠈⠕⠓⠪⠐⠹⠀⠠` - - first differing cell (zero-based): 45 +- `sentence_04.json` #1853: 경기주택도시공사(GH)가 층간 소음 등 아파트 품질 사각지대를 일소하고, 건설산업 근로자의 적정 임금을 보장하는 등 경기도 대표 공공기관으로써 사회적 책임 실천에 나선다. + - expected: `⠈⠿⠇⠦⠄⠴⠠⠠⠛⠓⠠⠴⠫⠀⠰⠪⠶⠫⠒⠀⠠⠥⠪⠢` + - actual: `⠈⠿⠇⠦⠄⠴⠠⠠⠣⠠⠴⠫⠀⠰⠪⠶⠫⠒⠀⠠⠥⠪⠢⠀` + - first differing cell (zero-based): 21 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -## Residual first-difference transitions after localized cohorts - -This ranking removes only cases whose first difference is inside an existing output-localized cohort. Broad input-only traits are not exclusion masks. The residual table therefore prioritizes new causes without hiding a mismatch merely because an unrelated structure coexists elsewhere in its sentence. +### `U+2826 ⠦ -> U+2834 ⠴` -| Rank | Expected → actual first cell | Residual cases | -|---:|---|---:| -| 1 | `U+2810 ⠐ -> U+2832 ⠲` | 249 | -| 2 | `U+283C ⠼ -> U+2800 ⠀` | 159 | -| 3 | `U+2834 ⠴ -> U+2800 ⠀` | 154 | -| 4 | `U+281B ⠛ -> U+2823 ⠣` | 144 | -| 5 | `U+2826 ⠦ -> U+2810 ⠐` | 133 | -| 6 | `U+2810 ⠐ -> U+2800 ⠀` | 131 | -| 7 | `U+2800 ⠀ -> U+2832 ⠲` | 107 | -| 8 | `U+2811 ⠑ -> U+283B ⠻` | 106 | -| 9 | `U+2810 ⠐ -> U+2814 ⠔` | 98 | -| 10 | `U+2824 ⠤ -> U+2800 ⠀` | 80 | -| 11 | `U+2808 ⠈ -> U+2832 ⠲` | 79 | -| 12 | `U+280E ⠎ -> U+2829 ⠩` | 73 | -| 13 | `U+2826 ⠦ -> U+2800 ⠀` | 69 | -| 14 | `U+2824 ⠤ -> U+2834 ⠴` | 59 | -| 15 | `U+2811 ⠑ -> U+2822 ⠢` | 58 | -| 16 | `U+2830 ⠰ -> U+2820 ⠠` | 58 | -| 17 | `U+2832 ⠲ -> U+2800 ⠀` | 56 | -| 18 | `U+2834 ⠴ -> U+2820 ⠠` | 55 | -| 19 | `U+280A ⠊ -> U+2814 ⠔` | 52 | -| 20 | `U+2810 ⠐ -> U+2815 ⠕` | 48 | +- `sentence_01.json` #87: 4일 진원생명과학은 자체 개발 흡인작용 피내 접종기 진덤(GeneDerm)을 이용한 코로나19 DNA 백신(GLS-5310)의 안전성·면역원성에 대한 임상1상 결과를 국제감염병학회 (ISID) 학술지인 국제감염질환저널에 발표했다고 4일 밝혔다. + - expected: `⠢⠘⠻⠚⠁⠚⠽⠀⠦⠄⠴⠠⠠⠊⠎⠊⠙⠠⠴⠀⠚⠁⠠⠯` + - actual: `⠢⠘⠻⠚⠁⠚⠽⠀⠴⠐⠣⠠⠠⠊⠎⠊⠙⠐⠜⠲⠀⠚⠁⠠` + - first differing cell (zero-based): 172 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_02.json` #38: 중국금형전시회(DMC) 및 독일금형전시회 (Moulding Expo)와 함께 세계 3대 규모의 금형산업 전문 전시회이며, 연관산업 전문 전시회를 KOPLAS와 동시 개최를 통한 시너지를 창출했다. + - expected: `⠻⠨⠾⠠⠕⠚⠽⠀⠦⠄⠴⠠⠍⠳⠇⠙⠬⠀⠠⠑⠭⠏⠕⠠` + - actual: `⠻⠨⠾⠠⠕⠚⠽⠀⠴⠐⠣⠠⠍⠳⠇⠙⠬⠀⠠⠑⠭⠏⠕⠐` + - first differing cell (zero-based): 48 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_03.json` #862: 평소 사진찍기를 꺼리던 작가는 지난 3일 기자간담회에서 직접 오큘러스 메타퀘스트 고글을 머리에 쓰고 걸으면서 VR(가상현실)작품 ‘OP.VR/01’(2022)을 시연하는 모습을 선보였다. + - expected: `⠗⠸⠌⠼⠚⠁⠴⠄⠦⠄⠼⠃⠚⠃⠃⠠⠴⠮⠀⠠⠕⠡⠚⠉` + - actual: `⠗⠸⠌⠼⠚⠁⠴⠄⠴⠐⠣⠼⠃⠚⠃⠃⠴⠐⠜⠲⠮⠀⠠⠕` + - first differing cell (zero-based): 149 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_04.json` #2544: 관상동맥 내 딱딱하게 쌓인 죽종을 깎아내는 회전죽종절제술(ROTA)을 이용한 관상동맥 중재술 (PCI)을 받은 환자가 시술 후 심근경색을 경험하더라도 예후에 영향이 없는 것으로 확인됐다. + - expected: `⠨⠍⠶⠨⠗⠠⠯⠀⠦⠄⠴⠠⠠⠏⠉⠊⠠⠴⠮⠀⠘⠔⠵⠀` + - actual: `⠨⠍⠶⠨⠗⠠⠯⠀⠴⠐⠣⠠⠠⠏⠉⠊⠐⠜⠲⠮⠀⠘⠔⠵` + - first differing cell (zero-based): 99 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -### Residual `U+2810 ⠐ -> U+2832 ⠲` +### `U+2811 ⠑ -> U+283B ⠻` -- `sentence_01.json` #521: 이 밖에 정 회장은 올해부터 시행하는 새 국제회계기준(IFRS17)·새 지급여력제도(K-ICS·킥스)의 연착륙을 지원하며서 ‘신제도 안정화 협의체’와 같은 소통 채널을 운영하기로 했다. - - expected: `⠠⠅⠤⠠⠠⠊⠉⠎⠐⠆⠋⠕⠁⠠⠪⠠⠴⠺⠀⠡⠰⠣⠁⠐` - - actual: `⠠⠅⠤⠠⠠⠊⠉⠎⠲⠐⠆⠋⠕⠁⠠⠪⠠⠴⠺⠀⠡⠰⠣⠁` - - first differing cell (zero-based): 93 +- `sentence_01.json` #1903: 김현용 현대차증권 연구원은 “전사적자원관리(ERP), 생산관리프로그램(MES) 등 기존의 기업향 솔루션에 이번 인수로 구매공급망관리(SRM)이 더해지며 사업 포트폴리오가 한층 단단해진 것으로 판단된다”고 설명했다. + - expected: `⠒⠐⠕⠦⠄⠴⠠⠠⠑⠗⠏⠠⠴⠐⠀⠠⠗⠶⠇⠒⠈⠧⠒⠐` + - actual: `⠒⠐⠕⠦⠄⠴⠠⠠⠻⠏⠠⠴⠐⠀⠠⠗⠶⠇⠒⠈⠧⠒⠐⠕` + - first differing cell (zero-based): 48 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_02.json` #327: 또한, ‘도가(Dog+Yoga)’ 등 반려동물과 함께 즐길 수 있는 다양한 프로그램이 준비돼 있으며, 참가자 전원에게는 태안산 황토 호박고구마(3kg) 및 요가매트가 제공된다. - - expected: `⠫⠦⠄⠴⠠⠙⠕⠛⠐⠖⠠⠽⠕⠛⠁⠠⠴⠴⠄⠀⠊⠪⠶⠀` - - actual: `⠫⠦⠄⠴⠠⠙⠕⠛⠲⠢⠴⠠⠽⠕⠛⠁⠠⠴⠴⠄⠀⠊⠪⠶` - - first differing cell (zero-based): 19 +- `sentence_02.json` #14: 한국타이어앤테크놀로지㈜(대표이사 이수일, 이하 한국타이어)가 국제자동차연맹(FIA)이 주관하는 ‘FIA 주니어 ERC(FIA Junior ERC, 이하 주니어 ERC)’ 대회에 레이싱 타이어를 독점 공급한다. + - expected: `⠍⠉⠕⠎⠀⠴⠠⠠⠑⠗⠉⠦⠄⠴⠠⠠⠋⠊⠁⠀⠠⠚⠥⠝` + - actual: `⠍⠉⠕⠎⠀⠴⠠⠠⠻⠉⠦⠄⠴⠠⠠⠋⠊⠁⠀⠠⠚⠥⠝⠊` + - first differing cell (zero-based): 114 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_03.json` #17: 재테크 시장에서 온·오프라인 플랫폼 ‘매경 엠플러스(M+)’가 주목받고 있다. M+는 거시경제, 주식, 부동산, 가상자산 등 재테크의 모든 것을 온라인 사이트를 통해 제공하고 있다. - - expected: `⠎⠠⠪⠦⠄⠴⠠⠍⠐⠖⠠⠴⠴⠄⠫⠀⠨⠍⠑⠭⠘⠔⠈⠥` - - actual: `⠎⠠⠪⠦⠄⠴⠠⠍⠲⠀⠢⠀⠠⠴⠴⠄⠫⠀⠨⠍⠑⠭⠘⠔` - - first differing cell (zero-based): 54 +- `sentence_03.json` #751: 올 하반기 위하고 기반 차세대 ‘스마트A 10’으로 본격적인 고객 전환이 예정된 가운데 세무회계사무소 전용 전사적자원관리 시스템(ERP)인 ‘위하고T’와 개인용 앱 ‘나하고(NAHAGO)’도 업계에서 주목받고 있다. + - expected: `⠓⠝⠢⠦⠄⠴⠠⠠⠑⠗⠏⠠⠴⠟⠀⠠⠦⠍⠗⠚⠈⠥⠴⠠` + - actual: `⠓⠝⠢⠦⠄⠴⠠⠠⠻⠏⠠⠴⠟⠀⠠⠦⠍⠗⠚⠈⠥⠴⠠⠞` + - first differing cell (zero-based): 127 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_04.json` #1069: HMM·삼성중공업·파나시아·한국선급 등 4개사는 지난 27일 선박 이산화탄소 포집·액화 저장 기술(OCCS) 통합 실증 연구를 위한 업무협약(MOU)을 체결했다고 28일 밝혔다. - - expected: `⠴⠠⠠⠓⠍⠍⠐⠆⠇⠢⠠⠻⠨⠍⠶⠈⠿⠎⠃⠐⠆⠙⠉⠠` - - actual: `⠴⠠⠠⠓⠍⠍⠲⠐⠆⠇⠢⠠⠻⠨⠍⠶⠈⠿⠎⠃⠐⠆⠙⠉` - - first differing cell (zero-based): 6 +- `sentence_04.json` #4038: 현대건설은 현지 건설사 이알버드(ERBUD), 유니베프(UNIBEP)와 신재생에너지 및 인프라 분야 협력에 관한 업무협약도 체결했다. 이를 통해 신공항, 도심 인프라, 스마트시티 분야 관련 사업을 함께 추진한다. + - expected: `⠎⠊⠪⠦⠄⠴⠠⠠⠑⠗⠃⠥⠙⠠⠴⠐⠀⠩⠉⠕⠘⠝⠙⠪` + - actual: `⠎⠊⠪⠦⠄⠴⠠⠠⠻⠃⠥⠙⠠⠴⠐⠀⠩⠉⠕⠘⠝⠙⠪⠦` + - first differing cell (zero-based): 33 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -### Residual `U+283C ⠼ -> U+2800 ⠀` +### `U+2800 ⠀ -> U+2832 ⠲` -- `sentence_01.json` #633: 미국 항공우주청(NASA)은 26일(현지 시간) 이 같은 내용을 발표하면서 “‘2023 BU’라는 소행성이 남미의 남단을 지나갈 것”이라며 “2023 BU는 지름이 약 3.5~8.5m 크기”라고 설명했다. - - expected: `⠐⠪⠢⠕⠀⠜⠁⠀⠼⠉⠲⠑⠈⠔⠼⠓⠲⠑⠴⠍⠲⠀⠋⠪` - - actual: `⠐⠪⠢⠕⠀⠜⠁⠀⠀⠼⠉⠲⠑⠈⠔⠼⠓⠲⠑⠍⠀⠋⠪⠈` - - first differing cell (zero-based): 152 +- `sentence_01.json` #4520: 방탄소년단(BTS) 10주년 기념 불꽃쇼가 지난 17일 서울 영등포구 여의도 한강공원에서 열린 방탄소년단 데뷔 10주년 FESTA @여의도(BTS 10th Anniversary FESTA @Yeouido)에서 펼쳐지고 있다. + - expected: `⠽⠀⠠⠠⠋⠑⠌⠁⠀⠈⠁⠠⠽⠑⠳⠊⠙⠕⠠⠴⠝⠠⠎⠀` + - actual: `⠽⠀⠠⠠⠋⠑⠌⠁⠲⠀⠈⠁⠴⠠⠽⠑⠳⠊⠙⠕⠠⠴⠝⠠` + - first differing cell (zero-based): 162 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_02.json` #893: 그러나, 이 아파트들은 금강 제1철교 반경 50~800m 내에 위치해 있어 철도소음 영향을 많이 받고 있다. 특히, 금강센트럴파크서희스타힐스는 야간소음이 67dB로 기준치(60dB)를 초과하고 있다. - - expected: `⠈⠬⠀⠘⠒⠈⠻⠀⠼⠑⠚⠈⠔⠼⠓⠚⠚⠴⠍⠲⠀⠉⠗⠝` - - actual: `⠈⠬⠀⠘⠒⠈⠻⠀⠀⠼⠑⠚⠈⠔⠼⠓⠚⠚⠍⠀⠀⠉⠗⠝` - - first differing cell (zero-based): 34 +- `sentence_02.json` #1841: 한편 현대차그룹은 현대차 아이오닉6와 제네시스 GV70 전동화 모델이 미국 고속도로 안전보험협회(IIHS)가 발표한 충돌평가에서 최고 등급인 ‘톱 세이프티 픽 플러스(TSP +)’를 받았다고 전했다. + - expected: `⠦⠄⠴⠠⠠⠞⠎⠏⠀⠐⠖⠠⠴⠴⠄⠐⠮⠀⠘⠔⠣⠌⠊⠈` + - actual: `⠦⠄⠴⠠⠠⠞⠎⠏⠲⠀⠢⠠⠴⠴⠄⠐⠮⠀⠘⠔⠣⠌⠊⠈` + - first differing cell (zero-based): 174 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_03.json` #140: 하림의 독자적인 기술로 만든 분리닭가슴살단백질(ICBP)은 닭가슴살을 그대로 섭취할 때보다 지방을 1/2 수준으로 낮추고, 단백질 함량은 4배 이상 늘린 순도 높은 고함량 단백질(함량 90%)을 구현했다. - - expected: `⠊⠀⠨⠕⠘⠶⠮⠀⠼⠁⠸⠌⠼⠃⠀⠠⠍⠨⠛⠪⠐⠥⠀⠉` - - actual: `⠊⠀⠨⠕⠘⠶⠮⠀⠀⠼⠁⠸⠌⠼⠃⠀⠀⠠⠍⠨⠛⠪⠐⠥` - - first differing cell (zero-based): 99 +- `sentence_03.json` #19: 해당 펀드는 전기차와 2차전지 및 2차전지 연관산업인 ESS /VPP(가상발전소)(14%) 등 전세계 친환경 기술 기업에 투자하는 상품이다. + - expected: `⠟⠀⠴⠠⠠⠑⠎⠎⠀⠸⠌⠠⠠⠧⠏⠏⠦⠄⠫⠇⠶⠘⠂⠨` + - actual: `⠟⠀⠴⠠⠠⠑⠎⠎⠲⠀⠸⠌⠴⠠⠠⠧⠏⠏⠦⠄⠫⠇⠶⠘` + - first differing cell (zero-based): 58 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_04.json` #871: 2025년 인류를 달에 다시 보내기 위해 미국 항공우주국(NASA)이 개발한 ‘우주발사시스템(SLS)’을 능가한다. SLS는 길이 98M, 추력은 3900t이다. - - expected: `⠉⠵⠀⠈⠕⠂⠕⠀⠼⠊⠓⠴⠠⠍⠐⠀⠰⠍⠐⠱⠁⠵⠀⠼` - - actual: `⠉⠵⠀⠈⠕⠂⠕⠀⠀⠼⠊⠓⠠⠍⠐⠀⠀⠰⠍⠐⠱⠁⠵⠀` - - first differing cell (zero-based): 124 +- `sentence_04.json` #3324: 삼성전자는 이날 생성형 인공지능(AI) 서버에 적용되는 서버용 SSD ‘PM1743’과 쿼드러플 레벨 셀(QLC) 낸드 기반 256TB SSD도 선보였다. + - expected: `⠶⠀⠴⠠⠠⠎⠎⠙⠀⠠⠦⠠⠠⠏⠍⠼⠁⠛⠙⠉⠠⠴⠲⠈` + - actual: `⠶⠀⠴⠠⠠⠎⠎⠙⠲⠀⠠⠦⠴⠠⠠⠏⠍⠼⠁⠛⠙⠉⠴⠄` + - first differing cell (zero-based): 68 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -### Residual `U+2834 ⠴ -> U+2800 ⠀` +### `U+2826 ⠦ -> U+2810 ⠐` -- `sentence_01.json` #947: 코나체인은 코나아이와 함께 한국은행의 디지털 화폐(CBDC)의 H/W Wallet의 개발에 참여하였고, 현재 블록체인 기반의 설문조사, 투표서비스를 제공 중이다. - - expected: `⠉⠃⠙⠉⠠⠴⠺⠀⠴⠠⠓⠸⠌⠠⠺⠀⠠⠺⠁⠇⠇⠑⠞⠲` - - actual: `⠉⠃⠙⠉⠠⠴⠺⠀⠀⠠⠓⠸⠌⠠⠺⠀⠴⠠⠺⠁⠇⠇⠑⠞` - - first differing cell (zero-based): 57 +- `sentence_01.json` #1318: 비제조업 업황 BSI(73)는 2p 상승했다. 비제조업 업황 BSI가 전월 대비 상승한 것은 지난 2022년 8월 이후 6개월 만에 처음이다. + - expected: `⠶⠀⠴⠠⠠⠃⠎⠊⠦⠄⠼⠛⠉⠠⠴⠉⠵⠀⠼⠃⠴⠏⠲⠀` + - actual: `⠶⠀⠴⠠⠠⠃⠎⠊⠐⠣⠼⠛⠉⠴⠐⠜⠲⠉⠵⠀⠼⠃⠴⠏` + - first differing cell (zero-based): 21 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_02.json` #865: ‘2023 삼성 AI/CE 챌린지’는 인공지능(AI)과 컴퓨터 공학(CE) 분야 국내 우수 인력 발굴과 차세대 반도체 연구 역량 강화를 위해 개최되고 있으며, 2021년을 시작으로 3회째를 맞이했다. - - expected: `⠃⠉⠀⠇⠢⠠⠻⠀⠴⠠⠠⠁⠊⠸⠌⠠⠠⠉⠑⠲⠀⠰⠗⠂` - - actual: `⠃⠉⠀⠇⠢⠠⠻⠀⠀⠠⠠⠁⠊⠸⠌⠠⠠⠉⠑⠀⠰⠗⠂⠐` - - first differing cell (zero-based): 13 +- `sentence_02.json` #1010: 첫 외국인 방문객으로는 말레이시아에서 온 Tan Chen Loon(47)씨 가족이다. 부인과 아들과 함께 공원을 방문했다가 행운의 방문객이 됐다. + - expected: `⠡⠢⠀⠠⠇⠕⠕⠝⠦⠄⠼⠙⠛⠠⠴⠠⠠⠕⠀⠫⠨⠭⠕⠊` + - actual: `⠡⠢⠀⠠⠇⠕⠕⠝⠐⠣⠼⠙⠛⠴⠐⠜⠲⠠⠠⠕⠀⠫⠨⠭` + - first differing cell (zero-based): 52 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_03.json` #2455: 한편, AI+ 인증은 한국표준협회(KSA)가 국제표준화기구(ISO, International Organization for Standardization)와 국제전기기술위원회(IEC, International Electrotechnical Commission) 등 국제표준에 근거해 인공지능(AI) 기술이 적용된 제품과 소프 - - expected: `⠚⠒⠙⠡⠐⠀⠴⠠⠠⠁⠊⠐⠖⠲⠀⠟⠨⠪⠶⠵⠀⠚⠒⠈` - - actual: `⠚⠒⠙⠡⠐⠀⠀⠠⠠⠁⠊⠢⠀⠀⠟⠨⠪⠶⠵⠀⠚⠒⠈⠍` - - first differing cell (zero-based): 6 +- `sentence_03.json` #869: 카메라를 총처럼 든 남자가 있다. 피사체는 2019년 칸영화제 심사위원상을 수상한 말리 출신 이민자 영화 감독 래드 리다. 사진작가 JR(40)의 예술 인생은 이 사진으로 바뀌었다. + - expected: `⠁⠫⠀⠴⠠⠠⠚⠗⠦⠄⠼⠙⠚⠠⠴⠺⠀⠌⠠⠯⠀⠟⠠⠗` + - actual: `⠁⠫⠀⠴⠠⠠⠚⠗⠐⠣⠼⠙⠚⠴⠐⠜⠲⠺⠀⠌⠠⠯⠀⠟` + - first differing cell (zero-based): 119 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_04.json` #2250: RX 시리즈의 전동화 버전도 내놨다. 하이브리드 모델인 RX350h, 플러그인하이브리드(PHEV) 모델인 RX450h+, 터보 엔진 하이브리드 모델(터보 HEV)인 RX500h F 스포츠 퍼포먼스 등 총 3가지다. - - expected: `⠀⠑⠥⠊⠝⠂⠟⠀⠴⠠⠠⠗⠭⠼⠉⠑⠚⠰⠓⠐⠀⠙⠮⠐` - - actual: `⠀⠑⠥⠊⠝⠂⠟⠀⠀⠠⠠⠗⠭⠼⠉⠑⠚⠓⠐⠀⠙⠮⠐⠎` - - first differing cell (zero-based): 54 +- `sentence_04.json` #1027: 5월 업황에 대한 전망 BSI(74)는 한 달 새 1포인트 올랐다. 제조업(72)에서 3포인트, 비제조업(76)에서 1포인트 상승했다. BSI에 소비자동향지수(CSI)를 반영한 4월 경제심리지수(ESI)는 전월보다 2.3포인트 상승한 93.8을 기록했다. + - expected: `⠶⠀⠴⠠⠠⠃⠎⠊⠦⠄⠼⠛⠙⠠⠴⠉⠵⠀⠚⠒⠀⠊⠂⠀` + - actual: `⠶⠀⠴⠠⠠⠃⠎⠊⠐⠣⠼⠛⠙⠴⠐⠜⠲⠉⠵⠀⠚⠒⠀⠊` + - first differing cell (zero-based): 28 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +## Residual first-difference transitions after localized cohorts + +This ranking removes only cases whose first difference is inside an existing output-localized cohort. Broad input-only traits are not exclusion masks. The residual table therefore prioritizes new causes without hiding a mismatch merely because an unrelated structure coexists elsewhere in its sentence. + +| Rank | Expected → actual first cell | Residual cases | +|---:|---|---:| +| 1 | `U+281B ⠛ -> U+2823 ⠣` | 147 | +| 2 | `U+2810 ⠐ -> U+2832 ⠲` | 98 | +| 3 | `U+2811 ⠑ -> U+283B ⠻` | 97 | +| 4 | `U+2800 ⠀ -> U+2832 ⠲` | 79 | +| 5 | `U+280E ⠎ -> U+2829 ⠩` | 77 | +| 6 | `U+2811 ⠑ -> U+2822 ⠢` | 68 | +| 7 | `U+2826 ⠦ -> U+2810 ⠐` | 67 | +| 8 | `U+280A ⠊ -> U+2814 ⠔` | 60 | +| 9 | `U+2810 ⠐ -> U+2815 ⠕` | 59 | +| 10 | `U+283C ⠼ -> U+2800 ⠀` | 55 | +| 11 | `U+2824 ⠤ -> U+2800 ⠀` | 54 | +| 12 | `U+2809 ⠉ -> U+2812 ⠒` | 47 | +| 13 | `U+2820 ⠠ -> U+2834 ⠴` | 47 | +| 14 | `U+2810 ⠐ -> U+2811 ⠑` | 46 | +| 15 | `U+2815 ⠕ -> U+2837 ⠷` | 41 | +| 16 | `U+2820 ⠠ -> U+281E ⠞` | 39 | +| 17 | `U+2820 ⠠ -> U+2832 ⠲` | 39 | +| 18 | `U+2802 ⠂ -> U+2810 ⠐` | 38 | +| 19 | `U+2809 ⠉ -> U+2821 ⠡` | 35 | +| 20 | `U+2834 ⠴ -> U+2800 ⠀` | 32 | + ### Residual `U+281B ⠛ -> U+2823 ⠣` - `sentence_01.json` #1753: 지난해 말 경기주택도시공사(GH)에서 퇴직한 전씨는 ‘성남FC 불법 후원금 의혹’과 관련해 검찰 조사를 받은 바 있으며 ‘GH 합숙소 의혹’에도 연루된 것으로 알려졌다. @@ -395,10 +327,10 @@ This ranking removes only cases whose first difference is inside an existing out - actual: `⠈⠿⠇⠦⠄⠴⠠⠠⠣⠠⠴⠝⠠⠎⠀⠓⠽⠨⠕⠁⠚⠒⠀⠨` - first differing cell (zero-based): 31 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_02.json` #235: 가스열펌프(GHP)는 전기모터 대신 가스엔진을 이용해 구동하는 냉난방기기로 여름철 전력피크 완화대책으로 학교나 상업용 건물 등에 널리 보급됐다. - - expected: `⠢⠙⠪⠦⠄⠴⠠⠠⠛⠓⠏⠠⠴⠉⠵⠀⠨⠾⠈⠕⠑⠥⠓⠎` - - actual: `⠢⠙⠪⠦⠄⠴⠠⠠⠣⠏⠠⠴⠉⠵⠀⠨⠾⠈⠕⠑⠥⠓⠎⠀` - - first differing cell (zero-based): 14 +- `sentence_02.json` #24: 대전대학교(총장 남상호)는 창업보육센터 입주기업인 ㈜티알(대표 김병수)이 최근 트랜스글로벌헬스케어(TGH)와 세계최초 AI 기반 만성폐쇄성폐질환(COPD) 진단기인 ‘The Spirokit’(더스피로킷)에 대한 물품공급 계약을 체결했다고 지난 14일 밝혔다. + - expected: `⠝⠎⠦⠄⠴⠠⠠⠞⠛⠓⠠⠴⠧⠀⠠⠝⠈⠌⠰⠽⠰⠥⠀⠴` + - actual: `⠝⠎⠦⠄⠴⠠⠠⠞⠣⠠⠴⠧⠀⠠⠝⠈⠌⠰⠽⠰⠥⠀⠴⠠` + - first differing cell (zero-based): 113 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` - `sentence_03.json` #292: GH 주거 분야 전문인력과 HUG(주택도시보증공사), 변호사, 법무사 등 부동산·금융 전문인력이 상주하며 전세사기 피해자에게 부동산 법률, 긴급 금융지원, 주거지원 등 종합적인 맞춤형 상담을 제공한다. - expected: `⠴⠠⠠⠛⠓⠲⠀⠨⠍⠈⠎⠀⠘⠛⠜⠀⠨⠾⠑⠛⠟⠐⠱⠁` @@ -411,58 +343,58 @@ This ranking removes only cases whose first difference is inside an existing out - first differing cell (zero-based): 21 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -### Residual `U+2826 ⠦ -> U+2810 ⠐` +### Residual `U+2810 ⠐ -> U+2832 ⠲` -- `sentence_01.json` #1318: 비제조업 업황 BSI(73)는 2p 상승했다. 비제조업 업황 BSI가 전월 대비 상승한 것은 지난 2022년 8월 이후 6개월 만에 처음이다. - - expected: `⠶⠀⠴⠠⠠⠃⠎⠊⠦⠄⠼⠛⠉⠠⠴⠉⠵⠀⠼⠃⠴⠏⠲⠀` - - actual: `⠶⠀⠴⠠⠠⠃⠎⠊⠐⠣⠼⠛⠉⠴⠐⠜⠲⠉⠵⠀⠀⠼⠃⠏` - - first differing cell (zero-based): 21 +- `sentence_01.json` #1675: LS일렉트릭은 8일부터 오는 10일까지 서울 삼성동 코엑스에서 열리는 ‘스마트공장·자동화산업전(SF+AW) 2023’ 전시회에 국내 기업 중 최대 규모로 참가한다. + - expected: `⠾⠦⠄⠴⠠⠠⠎⠋⠐⠖⠠⠠⠁⠺⠠⠴⠀⠼⠃⠚⠃⠉⠴⠄` + - actual: `⠾⠦⠄⠴⠠⠠⠎⠋⠲⠢⠴⠠⠠⠁⠺⠠⠴⠀⠼⠃⠚⠃⠉⠴` + - first differing cell (zero-based): 99 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_02.json` #286: 이희창 학생은 One Show에서 Bronze Pencil(동상), Merit(4위) / One ADC에서 Merit, Shortlist(본상) 31개 / One Portfolio에서 Shortlist, 총 34개의 본상을 수상했다. - - expected: `⠐⠀⠴⠠⠍⠻⠊⠞⠦⠄⠼⠙⠍⠗⠠⠴⠸⠌⠴⠠⠐⠕⠀⠠` - - actual: `⠐⠀⠴⠠⠍⠻⠊⠞⠐⠣⠼⠙⠍⠗⠴⠐⠜⠲⠀⠸⠌⠀⠴⠠` - - first differing cell (zero-based): 58 +- `sentence_02.json` #885: 지난 13일 온라인 컨퍼런스로 진행된 밸류데이에서 KT&G는 중장기(2024년~2026년) 주주환원 계획을 공개하고, 3대 핵심사업인 NGP(Next Generation Products)·글로벌CC(궐련담배)·건기식(건강기능식품) 중심의 미래비전 이행 경과를 공유했다. + - expected: `⠕⠙⠥⠉⠞⠎⠐⠜⠐⠆⠈⠮⠐⠥⠘⠞⠴⠠⠠⠉⠉⠦⠄⠈` + - actual: `⠕⠙⠥⠉⠞⠎⠐⠜⠲⠐⠆⠈⠮⠐⠥⠘⠞⠴⠠⠠⠉⠉⠦⠄` + - first differing cell (zero-based): 167 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_03.json` #551: 영국 일간 가디언은 28일(현지시간) 조너선M(41)이 이날부터 정자를 기증할 경우 10만유로(1억5000만원) 이상의 벌금을 물게 된다고 보도했다. - - expected: `⠥⠉⠎⠠⠾⠴⠠⠍⠦⠄⠼⠙⠁⠠⠴⠕⠀⠕⠉⠂⠘⠍⠓⠎` - - actual: `⠥⠉⠎⠠⠾⠴⠠⠍⠐⠣⠼⠙⠁⠴⠐⠜⠲⠕⠀⠕⠉⠂⠘⠍` - - first differing cell (zero-based): 43 +- `sentence_03.json` #2536: 13일 금융투자업계에 따르면 오는 16일과 20일 KB증권(AA+)과 한국금융지주(AA-)가 각 4600억원, 1300억원의 회사채 발행을 위한 수요예측을 진행할 예정인 것으로 전해졌다. 한국금융지주는 한국투자증권을 주력 자회사로 둔 금융지주사다. + - expected: `⠍⠦⠄⠴⠠⠠⠁⠁⠐⠤⠠⠴⠫⠀⠫⠁⠀⠼⠙⠋⠚⠚⠹⠏` + - actual: `⠍⠦⠄⠴⠠⠠⠁⠁⠲⠤⠠⠴⠫⠀⠫⠁⠀⠼⠙⠋⠚⠚⠹⠏` + - first differing cell (zero-based): 93 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_04.json` #119: 21일 국제금융센터에 따르면 KB국민은행의 지난 17일 신용부도스와프(CDS) 프리미엄은 43bp(1bp는 0.01%포인트)로 일주일 전 대비 1bp 상승하는 데 그쳤다. - - expected: `⠵⠀⠼⠙⠉⠴⠃⠏⠦⠄⠼⠁⠴⠃⠏⠲⠉⠵⠀⠼⠚⠲⠚⠁` - - actual: `⠵⠀⠼⠙⠉⠴⠃⠏⠐⠣⠼⠁⠰⠃⠏⠲⠉⠵⠀⠼⠚⠲⠚⠁` - - first differing cell (zero-based): 98 +- `sentence_04.json` #1069: HMM·삼성중공업·파나시아·한국선급 등 4개사는 지난 27일 선박 이산화탄소 포집·액화 저장 기술(OCCS) 통합 실증 연구를 위한 업무협약(MOU)을 체결했다고 28일 밝혔다. + - expected: `⠴⠠⠠⠓⠍⠍⠐⠆⠇⠢⠠⠻⠨⠍⠶⠈⠿⠎⠃⠐⠆⠙⠉⠠` + - actual: `⠴⠠⠠⠓⠍⠍⠲⠐⠆⠇⠢⠠⠻⠨⠍⠶⠈⠿⠎⠃⠐⠆⠙⠉` + - first differing cell (zero-based): 6 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -### Residual `U+2810 ⠐ -> U+2800 ⠀` +### Residual `U+2811 ⠑ -> U+283B ⠻` -- `sentence_01.json` #761: 샌즈랩은 국내에서 정보보호 분야로 최초이자 유일하게 2021년, 2022년 연속으로 국가신기술(NET) 인증을 획득했다. 또한 신기술로 최근 미국 직접 특허 출원·PCT(Patent Cooperation Treaty)특허 출원을 완료했다. - - expected: `⠁⠚⠎⠀⠰⠯⠏⠒⠐⠆⠴⠠⠠⠏⠉⠞⠐⠣⠠⠏⠁⠞⠢⠞` - - actual: `⠁⠚⠎⠀⠰⠯⠏⠒⠀⠀⠐⠠⠠⠏⠉⠞⠦⠠⠏⠁⠞⠑⠝⠞` - - first differing cell (zero-based): 156 +- `sentence_01.json` #1903: 김현용 현대차증권 연구원은 “전사적자원관리(ERP), 생산관리프로그램(MES) 등 기존의 기업향 솔루션에 이번 인수로 구매공급망관리(SRM)이 더해지며 사업 포트폴리오가 한층 단단해진 것으로 판단된다”고 설명했다. + - expected: `⠒⠐⠕⠦⠄⠴⠠⠠⠑⠗⠏⠠⠴⠐⠀⠠⠗⠶⠇⠒⠈⠧⠒⠐` + - actual: `⠒⠐⠕⠦⠄⠴⠠⠠⠻⠏⠠⠴⠐⠀⠠⠗⠶⠇⠒⠈⠧⠒⠐⠕` + - first differing cell (zero-based): 48 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_02.json` #64: 시에 따르면 미래형 환승센터(MaaS Station) : 철도, 버스를 연계한 기존 환승센터에 도심항공교통(UAM), 자율주행차, 전기·수소차, 개인형 이동수단(PM) 등 미래 모빌리티까지 연계한 환승센터라고 설명했다. - - expected: `⠀⠠⠌⠁⠰⠝⠠⠴⠐⠂⠀⠰⠞⠊⠥⠐⠀⠘⠎⠠⠪⠐⠮⠀` - - actual: `⠀⠠⠌⠁⠰⠝⠠⠴⠀⠐⠂⠀⠰⠞⠊⠥⠐⠀⠘⠎⠠⠪⠐⠮` - - first differing cell (zero-based): 46 +- `sentence_02.json` #672: 충남교육청은 사무행정(태안여고), ERP(천안여자상업고) 및 대회홍보크리에이터(논산여자상업고) 3개 종목에서 대상인 교육부장관상을 받았고 △금상 6개 △은상 16개 △동상 23개 등 총 48개 수상의 영예를 누렸다. + - expected: `⠥⠠⠴⠐⠀⠴⠠⠠⠑⠗⠏⠦⠄⠰⠾⠣⠒⠱⠨⠇⠶⠎⠃⠈` + - actual: `⠥⠠⠴⠐⠀⠴⠠⠠⠻⠏⠦⠄⠰⠾⠣⠒⠱⠨⠇⠶⠎⠃⠈⠥` + - first differing cell (zero-based): 37 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_03.json` #1714: 드림캐쳐는 최근 여덟 번째 미니앨범 ‘아포칼립스 : 프롬 어스(Apocalypse : From us)’를 발매하고 타이틀 ‘본보야지(BONVOYAGE)’로 활발한 활동을 예고했다. - - expected: `⠥⠋⠂⠐⠕⠃⠠⠪⠐⠂⠀⠙⠪⠐⠥⠢⠀⠎⠠⠪⠦⠄⠴⠠` - - actual: `⠥⠋⠂⠐⠕⠃⠠⠪⠀⠐⠂⠀⠙⠪⠐⠥⠢⠀⠎⠠⠪⠦⠄⠴` - - first differing cell (zero-based): 50 +- `sentence_03.json` #751: 올 하반기 위하고 기반 차세대 ‘스마트A 10’으로 본격적인 고객 전환이 예정된 가운데 세무회계사무소 전용 전사적자원관리 시스템(ERP)인 ‘위하고T’와 개인용 앱 ‘나하고(NAHAGO)’도 업계에서 주목받고 있다. + - expected: `⠓⠝⠢⠦⠄⠴⠠⠠⠑⠗⠏⠠⠴⠟⠀⠠⠦⠍⠗⠚⠈⠥⠴⠠` + - actual: `⠓⠝⠢⠦⠄⠴⠠⠠⠻⠏⠠⠴⠟⠀⠠⠦⠍⠗⠚⠈⠥⠴⠠⠞` + - first differing cell (zero-based): 127 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_04.json` #1616: 기독교복음선교회(일명 JMS) 총재 정명석씨(78)의 성폭행 사건 공범인 ‘JMS 2인자’ 정조은씨(본명 김지선·44) 등 조력자들의 변호인들이 잇따라 사임하고 있는 것으로 확인됐다. - - expected: `⠀⠈⠕⠢⠨⠕⠠⠾⠐⠆⠼⠙⠙⠠⠴⠀⠊⠪⠶⠀⠨⠥⠐⠱` - - actual: `⠀⠈⠕⠢⠨⠕⠠⠾⠀⠀⠐⠼⠙⠙⠴⠀⠊⠪⠶⠀⠨⠥⠐⠱` - - first differing cell (zero-based): 112 +- `sentence_04.json` #4038: 현대건설은 현지 건설사 이알버드(ERBUD), 유니베프(UNIBEP)와 신재생에너지 및 인프라 분야 협력에 관한 업무협약도 체결했다. 이를 통해 신공항, 도심 인프라, 스마트시티 분야 관련 사업을 함께 추진한다. + - expected: `⠎⠊⠪⠦⠄⠴⠠⠠⠑⠗⠃⠥⠙⠠⠴⠐⠀⠩⠉⠕⠘⠝⠙⠪` + - actual: `⠎⠊⠪⠦⠄⠴⠠⠠⠻⠃⠥⠙⠠⠴⠐⠀⠩⠉⠕⠘⠝⠙⠪⠦` + - first differing cell (zero-based): 33 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` ### Residual `U+2800 ⠀ -> U+2832 ⠲` -- `sentence_01.json` #1701: 금호석유화학은 앞으로도 추가적인 등급 상승을 이뤄간다는 방침이다. 이를 위해 직·간접배출(scope 1·2) 뿐 아니라 기타간접배출(scope3)도 제3자 기관의 검증을 통해 더욱 철저히 관리할 예정이다. - - expected: `⠦⠄⠴⠎⠉⠕⠏⠑⠀⠼⠁⠐⠆⠼⠃⠠⠴⠀⠠⠘⠛⠀⠣⠉` - - actual: `⠦⠄⠴⠎⠉⠕⠏⠑⠲⠀⠀⠼⠁⠐⠼⠃⠴⠀⠀⠠⠘⠛⠀⠣` - - first differing cell (zero-based): 92 +- `sentence_01.json` #4520: 방탄소년단(BTS) 10주년 기념 불꽃쇼가 지난 17일 서울 영등포구 여의도 한강공원에서 열린 방탄소년단 데뷔 10주년 FESTA @여의도(BTS 10th Anniversary FESTA @Yeouido)에서 펼쳐지고 있다. + - expected: `⠽⠀⠠⠠⠋⠑⠌⠁⠀⠈⠁⠠⠽⠑⠳⠊⠙⠕⠠⠴⠝⠠⠎⠀` + - actual: `⠽⠀⠠⠠⠋⠑⠌⠁⠲⠀⠈⠁⠴⠠⠽⠑⠳⠊⠙⠕⠠⠴⠝⠠` + - first differing cell (zero-based): 162 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` - `sentence_02.json` #1841: 한편 현대차그룹은 현대차 아이오닉6와 제네시스 GV70 전동화 모델이 미국 고속도로 안전보험협회(IIHS)가 발표한 충돌평가에서 최고 등급인 ‘톱 세이프티 픽 플러스(TSP +)’를 받았다고 전했다. - expected: `⠦⠄⠴⠠⠠⠞⠎⠏⠀⠐⠖⠠⠴⠴⠄⠐⠮⠀⠘⠔⠣⠌⠊⠈` @@ -474,79 +406,138 @@ This ranking removes only cases whose first difference is inside an existing out - actual: `⠟⠀⠴⠠⠠⠑⠎⠎⠲⠀⠸⠌⠴⠠⠠⠧⠏⠏⠦⠄⠫⠇⠶⠘` - first differing cell (zero-based): 58 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_04.json` #3669: 소방청은 ‘2023 국제소방안전박람회(International fire & Safety Expo Korea 2023)’가 ‘K-소방산업, 세계로 미래로!’를 주제로 오는 30일부터 다음 달 1일까지 3일간, 대구시 북구의 EXCO(전시컨벤션센터)에서 열린다고 27일 밝혔다. - - expected: `⠝⠁⠇⠀⠋⠊⠗⠑⠀⠈⠯⠀⠠⠎⠁⠋⠑⠞⠽⠀⠠⠑⠭⠏` - - actual: `⠝⠁⠇⠀⠋⠊⠗⠑⠲⠀⠈⠯⠀⠴⠠⠎⠁⠋⠑⠞⠽⠀⠠⠑` - - first differing cell (zero-based): 54 + +### Residual `U+280E ⠎ -> U+2829 ⠩` + +- `sentence_01.json` #556: 현재 구룡마을은 서울주택도시공사(SH) 주도로 재개발 사업이 추진되고 있다. 지난 2020년 6월 실시계획 인가 내용에 따르면 임대주택 1천107가구와 공공분양 991가구, 민간분양 740가구가 들어설 계획이다. + - expected: `⠈⠿⠇⠦⠄⠴⠠⠠⠎⠓⠠⠴⠀⠨⠍⠊⠥⠐⠥⠀⠨⠗⠈⠗` + - actual: `⠈⠿⠇⠦⠄⠴⠠⠠⠩⠠⠴⠀⠨⠍⠊⠥⠐⠥⠀⠨⠗⠈⠗⠘` + - first differing cell (zero-based): 35 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_02.json` #431: 173cm, 68kg의 다소 왜소한 체격이지만 스크럼 하프(SH) 포지션을 맡아 포워드와 백스 사이에서 날카로운 볼 연결과 공격방향을 결정하는 타고난 판단력이 강점이다. + - expected: `⠚⠙⠪⠦⠄⠴⠠⠠⠎⠓⠠⠴⠀⠙⠥⠨⠕⠠⠡⠮⠀⠑⠦⠣` + - actual: `⠚⠙⠪⠦⠄⠴⠠⠠⠩⠠⠴⠀⠙⠥⠨⠕⠠⠡⠮⠀⠑⠦⠣⠀` + - first differing cell (zero-based): 55 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_03.json` #1606: 행복주택 당첨자들의 개인정보가 유출됐다. 서울주택도시공사(SH)는 직원의 실수로 발생한 사고라며 사과했지만, 공공기관으로서 수많은 서울시민의 신상 명세를 보관하고 있는 만큼 주의가 필요하다는 지적이 나온다. + - expected: `⠈⠿⠇⠦⠄⠴⠠⠠⠎⠓⠠⠴⠉⠵⠀⠨⠕⠁⠏⠒⠺⠀⠠⠕` + - actual: `⠈⠿⠇⠦⠄⠴⠠⠠⠩⠠⠴⠉⠵⠀⠨⠕⠁⠏⠒⠺⠀⠠⠕⠂` + - first differing cell (zero-based): 60 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_04.json` #141: 서울 강동구 고덕강일지구에 이어 강서구 마곡지구에도 ‘반값 아파트’가 공급된다. 서울주택도시공사(SH)는 내년까지 8000가구 이상의 토지임대부 분양주택을 공급할 방침이다. + - expected: `⠈⠿⠇⠦⠄⠴⠠⠠⠎⠓⠠⠴⠉⠵⠀⠉⠗⠉⠡⠠⠫⠨⠕⠀` + - actual: `⠈⠿⠇⠦⠄⠴⠠⠠⠩⠠⠴⠉⠵⠀⠉⠗⠉⠡⠠⠫⠨⠕⠀⠼` + - first differing cell (zero-based): 93 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -### Residual `U+2811 ⠑ -> U+283B ⠻` +### Residual `U+2811 ⠑ -> U+2822 ⠢` -- `sentence_01.json` #1903: 김현용 현대차증권 연구원은 “전사적자원관리(ERP), 생산관리프로그램(MES) 등 기존의 기업향 솔루션에 이번 인수로 구매공급망관리(SRM)이 더해지며 사업 포트폴리오가 한층 단단해진 것으로 판단된다”고 설명했다. - - expected: `⠒⠐⠕⠦⠄⠴⠠⠠⠑⠗⠏⠠⠴⠐⠀⠠⠗⠶⠇⠒⠈⠧⠒⠐` - - actual: `⠒⠐⠕⠦⠄⠴⠠⠠⠻⠏⠠⠴⠐⠀⠠⠗⠶⠇⠒⠈⠧⠒⠐⠕` - - first differing cell (zero-based): 48 +- `sentence_01.json` #1627: 또 “한국무역협회와 공동으로 기업 차원의 규제 대응 및 유럽경제협력네트워크(EEN) 프로그램 등을 활용한 산업 협력 방안을 논의하는 자리를 늘려가길 희망한다”며 양 기관 간 협력을 주문했다. + - expected: `⠋⠪⠦⠄⠴⠠⠠⠑⠑⠝⠠⠴⠀⠙⠪⠐⠥⠈⠪⠐⠗⠢⠀⠊` + - actual: `⠋⠪⠦⠄⠴⠠⠠⠑⠢⠠⠴⠀⠙⠪⠐⠥⠈⠪⠐⠗⠢⠀⠊⠪` + - first differing cell (zero-based): 81 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_02.json` #672: 충남교육청은 사무행정(태안여고), ERP(천안여자상업고) 및 대회홍보크리에이터(논산여자상업고) 3개 종목에서 대상인 교육부장관상을 받았고 △금상 6개 △은상 16개 △동상 23개 등 총 48개 수상의 영예를 누렸다. - - expected: `⠥⠠⠴⠐⠀⠴⠠⠠⠑⠗⠏⠦⠄⠰⠾⠣⠒⠱⠨⠇⠶⠎⠃⠈` - - actual: `⠥⠠⠴⠐⠀⠴⠠⠠⠻⠏⠦⠄⠰⠾⠣⠒⠱⠨⠇⠶⠎⠃⠈⠥` +- `sentence_02.json` #2143: 황새는 세계자연보전연맹 적색자료 목록에 ‘위기(EN)종’으로 분류된 국제보호종으로 세계에 약 2천500개체가 살아있는 것으로 전해진다. 우리나라 황새는 1971년 충북 음성군에서 밀렵꾼에게 잡히면서 자취를 감췄다. + - expected: `⠗⠈⠕⠦⠄⠴⠠⠠⠑⠝⠠⠴⠨⠿⠴⠄⠪⠐⠥⠀⠘⠛⠐⠩` + - actual: `⠗⠈⠕⠦⠄⠴⠠⠠⠢⠠⠴⠨⠿⠴⠄⠪⠐⠥⠀⠘⠛⠐⠩⠊` + - first differing cell (zero-based): 50 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_03.json` #1156: 콘텐츠 플랫폼 기업 리디는 CJ ENM과 지적재산권(IP) 사업 확장을 위한 전략적 제휴(MOU)를 맺었다고 15일 밝혔다. + - expected: `⠴⠠⠠⠉⠚⠀⠠⠠⠑⠝⠍⠲⠈⠧⠀⠨⠕⠨⠹⠨⠗⠇⠒⠈` + - actual: `⠴⠠⠠⠉⠚⠀⠠⠠⠢⠍⠲⠈⠧⠀⠨⠕⠨⠹⠨⠗⠇⠒⠈⠏` - first differing cell (zero-based): 37 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_03.json` #751: 올 하반기 위하고 기반 차세대 ‘스마트A 10’으로 본격적인 고객 전환이 예정된 가운데 세무회계사무소 전용 전사적자원관리 시스템(ERP)인 ‘위하고T’와 개인용 앱 ‘나하고(NAHAGO)’도 업계에서 주목받고 있다. - - expected: `⠓⠝⠢⠦⠄⠴⠠⠠⠑⠗⠏⠠⠴⠟⠀⠠⠦⠍⠗⠚⠈⠥⠴⠠` - - actual: `⠓⠝⠢⠦⠄⠴⠠⠠⠻⠏⠠⠴⠟⠀⠠⠦⠍⠗⠚⠈⠥⠴⠠⠞` - - first differing cell (zero-based): 127 +- `sentence_04.json` #2282: 2019년 11월 서울대공원은 캐니언파크로 알락꼬리여우원숭이 7마리를 양도했고, 12월에는 대구의 한 체험동물원에 14마리를 넘겼다. 알락꼬리여우원숭이는 세계자연보전연맹(IUCN)의 멸종위기종 목록인 적색 목록상 위기를 의미하는 EN(Endangered) 범주에 포함돼 있다. + - expected: `⠕⠚⠉⠵⠀⠴⠠⠠⠑⠝⠐⠣⠠⠢⠙⠁⠝⠛⠻⠫⠐⠜⠲⠀` + - actual: `⠕⠚⠉⠵⠀⠴⠠⠠⠢⠐⠣⠠⠢⠙⠁⠝⠛⠻⠫⠐⠜⠲⠀⠘` + - first differing cell (zero-based): 222 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_04.json` #4038: 현대건설은 현지 건설사 이알버드(ERBUD), 유니베프(UNIBEP)와 신재생에너지 및 인프라 분야 협력에 관한 업무협약도 체결했다. 이를 통해 신공항, 도심 인프라, 스마트시티 분야 관련 사업을 함께 추진한다. - - expected: `⠎⠊⠪⠦⠄⠴⠠⠠⠑⠗⠃⠥⠙⠠⠴⠐⠀⠩⠉⠕⠘⠝⠙⠪` - - actual: `⠎⠊⠪⠦⠄⠴⠠⠠⠻⠃⠥⠙⠠⠴⠐⠀⠩⠉⠕⠘⠝⠙⠪⠦` - - first differing cell (zero-based): 33 + +### Residual `U+2826 ⠦ -> U+2810 ⠐` + +- `sentence_01.json` #1318: 비제조업 업황 BSI(73)는 2p 상승했다. 비제조업 업황 BSI가 전월 대비 상승한 것은 지난 2022년 8월 이후 6개월 만에 처음이다. + - expected: `⠶⠀⠴⠠⠠⠃⠎⠊⠦⠄⠼⠛⠉⠠⠴⠉⠵⠀⠼⠃⠴⠏⠲⠀` + - actual: `⠶⠀⠴⠠⠠⠃⠎⠊⠐⠣⠼⠛⠉⠴⠐⠜⠲⠉⠵⠀⠼⠃⠴⠏` + - first differing cell (zero-based): 21 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_02.json` #1010: 첫 외국인 방문객으로는 말레이시아에서 온 Tan Chen Loon(47)씨 가족이다. 부인과 아들과 함께 공원을 방문했다가 행운의 방문객이 됐다. + - expected: `⠡⠢⠀⠠⠇⠕⠕⠝⠦⠄⠼⠙⠛⠠⠴⠠⠠⠕⠀⠫⠨⠭⠕⠊` + - actual: `⠡⠢⠀⠠⠇⠕⠕⠝⠐⠣⠼⠙⠛⠴⠐⠜⠲⠠⠠⠕⠀⠫⠨⠭` + - first differing cell (zero-based): 52 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_03.json` #869: 카메라를 총처럼 든 남자가 있다. 피사체는 2019년 칸영화제 심사위원상을 수상한 말리 출신 이민자 영화 감독 래드 리다. 사진작가 JR(40)의 예술 인생은 이 사진으로 바뀌었다. + - expected: `⠁⠫⠀⠴⠠⠠⠚⠗⠦⠄⠼⠙⠚⠠⠴⠺⠀⠌⠠⠯⠀⠟⠠⠗` + - actual: `⠁⠫⠀⠴⠠⠠⠚⠗⠐⠣⠼⠙⠚⠴⠐⠜⠲⠺⠀⠌⠠⠯⠀⠟` + - first differing cell (zero-based): 119 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_04.json` #1027: 5월 업황에 대한 전망 BSI(74)는 한 달 새 1포인트 올랐다. 제조업(72)에서 3포인트, 비제조업(76)에서 1포인트 상승했다. BSI에 소비자동향지수(CSI)를 반영한 4월 경제심리지수(ESI)는 전월보다 2.3포인트 상승한 93.8을 기록했다. + - expected: `⠶⠀⠴⠠⠠⠃⠎⠊⠦⠄⠼⠛⠙⠠⠴⠉⠵⠀⠚⠒⠀⠊⠂⠀` + - actual: `⠶⠀⠴⠠⠠⠃⠎⠊⠐⠣⠼⠛⠙⠴⠐⠜⠲⠉⠵⠀⠚⠒⠀⠊` + - first differing cell (zero-based): 28 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -### Residual `U+2810 ⠐ -> U+2814 ⠔` +### Residual `U+280A ⠊ -> U+2814 ⠔` -- `sentence_01.json` #10236: 정현찬(75) 전 문재인 대통령 직속 농어업·농어촌특별위원회(농특위) 위원장이 <여럿이 함께 기어이 벽을 넘는, 농민이 잘 사는 나라>라는 제목으로 낸 회고록에서 강조한 말이다. - - expected: `⠍⠗⠏⠒⠨⠶⠕⠀⠐⠶⠱⠐⠎⠄⠕⠀⠚⠢⠠⠈⠝⠀⠈⠕` - - actual: `⠍⠗⠏⠒⠨⠶⠕⠀⠔⠔⠱⠐⠎⠄⠕⠀⠚⠢⠠⠈⠝⠀⠈⠕` - - first differing cell (zero-based): 80 - - current primary/reason: `pending_rule_review` / `number_rule_review` -- `sentence_02.json` #1966: ‘가치있는 기술·자본시장 정보제공’ 매체를 표방하는 경제종합 인터넷 미디어 <블로터>가 인공지능(AI) 시대의 브랜드 마케팅에 대해 이야기합니다. - - expected: `⠄⠀⠑⠕⠊⠕⠎⠀⠐⠶⠘⠮⠐⠥⠓⠎⠶⠂⠫⠀⠟⠈⠿⠨` - - actual: `⠄⠀⠑⠕⠊⠕⠎⠀⠔⠔⠘⠮⠐⠥⠓⠎⠀⠢⠢⠀⠫⠀⠟⠈` - - first differing cell (zero-based): 72 +- `sentence_01.json` #362: 현재는 임상시험승인계획(IND) 준비 단계다. 임상 1상 신청은 작년 말을 목표로 했으나 중국 파트너사인 통화동보제약의 임상용 인슐린 원료 공급 일정 지연 등을 이유로 올해 2월로 미뤄졌다. + - expected: `⠚⠽⠁⠦⠄⠴⠠⠠⠊⠝⠙⠠⠴⠀⠨⠛⠘⠕⠀⠊⠒⠈⠌⠊` + - actual: `⠚⠽⠁⠦⠄⠴⠠⠠⠔⠙⠠⠴⠀⠨⠛⠘⠕⠀⠊⠒⠈⠌⠊⠲` + - first differing cell (zero-based): 30 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_03.json` #13277: 선덜랜드는 프리미어리그(EPL)에서 챔피언십(2부)으로 강등된 2017년 여름 넷플릭스 다큐멘터리 <죽어도 선덜랜드>를 촬영했는데, EPL 복귀를 꿈꾸던 것과 달리 리그1으로 잇달아 추락하는 새드 엔딩으로 끝났다. - - expected: `⠑⠝⠒⠓⠎⠐⠕⠀⠐⠶⠨⠍⠁⠎⠊⠥⠀⠠⠾⠊⠞⠐⠗⠒` - - actual: `⠑⠝⠒⠓⠎⠐⠕⠀⠔⠔⠨⠍⠁⠎⠊⠥⠀⠠⠾⠊⠞⠐⠗⠒` - - first differing cell (zero-based): 103 +- `sentence_02.json` #230: INC는 아이디어(I)-니즈(N)-역량(C)의 융합을 뜻하며, 파괴적 혁신과 기업가적 대학으로서 산학협력을 활성화하기 위한 방법론으로, 지속가능한 가치창출형 산학협력을 위한 브랜드이다. + - expected: `⠴⠠⠠⠊⠝⠉⠲⠉⠵⠀⠣⠕⠊⠕⠎⠦⠄⠴⠠⠊⠠⠴⠤⠉` + - actual: `⠴⠠⠠⠔⠉⠲⠉⠵⠀⠣⠕⠊⠕⠎⠦⠄⠴⠠⠊⠠⠴⠤⠉⠕` + - first differing cell (zero-based): 3 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_04.json` #431: 그룹 방탄소년단(BTS) 지민이 지난달 발표한 첫 솔로 음반 <페이스>가 미국 빌보드 메인 음반 차트인 ‘빌보드 200’ 2위에 올랐다. 이는 K팝 솔로 가수로는 역대 최고 순위다. - - expected: `⠐⠥⠀⠪⠢⠘⠒⠀⠐⠶⠙⠝⠕⠠⠪⠶⠂⠫⠀⠑⠕⠈⠍⠁` - - actual: `⠐⠥⠀⠪⠢⠘⠒⠀⠔⠔⠙⠝⠕⠠⠪⠀⠢⠢⠀⠫⠀⠑⠕⠈` - - first differing cell (zero-based): 62 +- `sentence_03.json` #371: 인터넷 인프라 전문기업 케이아이엔엑스(KINX)는 ‘2023년 중소기업 클라우드 서비스 보급·확산 사업’의 공급기업으로 4년 연속 선정됐다고 26일 밝혔다. + - expected: `⠠⠪⠦⠄⠴⠠⠠⠅⠊⠝⠭⠠⠴⠉⠵⠀⠠⠦⠼⠃⠚⠃⠉⠀` + - actual: `⠠⠪⠦⠄⠴⠠⠠⠅⠔⠭⠠⠴⠉⠵⠀⠠⠦⠼⠃⠚⠃⠉⠀⠉` + - first differing cell (zero-based): 39 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_04.json` #311: 멕시코 이민국(INM)이 운영하는 이 수용시설에는 화재 당시 중남미 출신 이민자 68명이 수용돼 있었던 것으로 추정된다. 시설에 있던 사람들 대부분은 미국으로 향하던 베네수엘라인들이었던 것으로 알려졌다. + - expected: `⠈⠍⠁⠦⠄⠴⠠⠠⠊⠝⠍⠠⠴⠕⠀⠛⠻⠚⠉⠵⠀⠕⠀⠠` + - actual: `⠈⠍⠁⠦⠄⠴⠠⠠⠔⠍⠠⠴⠕⠀⠛⠻⠚⠉⠵⠀⠕⠀⠠⠍` + - first differing cell (zero-based): 19 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -### Residual `U+2824 ⠤ -> U+2800 ⠀` +### Residual `U+2810 ⠐ -> U+2815 ⠕` -- `sentence_01.json` #189: 라이엇게임즈가 ‘리그오브레전드(LoL) 2023 시즌’ 개막을 앞두고 브랜드 영상 ‘너만의 전설을 써봐 - 슈퍼 캐리’를 공개했다. - - expected: `⠞⠮⠀⠠⠠⠎⠘⠧⠤⠠⠩⠙⠎⠀⠋⠗⠐⠕⠴⠄⠐⠮⠀⠈` - - actual: `⠞⠮⠀⠠⠠⠎⠘⠧⠀⠤⠀⠠⠩⠙⠎⠀⠋⠗⠐⠕⠴⠄⠐⠮` - - first differing cell (zero-based): 96 +- `sentence_01.json` #3848: ‘천안시 승격 60주년 KBS 열린음악회’에는 가수 김연자, 소찬휘, 김범룡, 최성수, 우연이, 김영임, 고영열, 김필, 김기태, 원어스(ONEUS)가 출연해 전 세대가 즐길 수 있는 다양한 공연을 선사한다. + - expected: `⠎⠠⠪⠦⠄⠴⠠⠠⠐⠕⠥⠎⠠⠴⠫⠀⠰⠯⠡⠚⠗⠀⠨⠾` + - actual: `⠎⠠⠪⠦⠄⠴⠠⠠⠕⠝⠑⠥⠎⠠⠴⠫⠀⠰⠯⠡⠚⠗⠀⠨` + - first differing cell (zero-based): 133 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_02.json` #2352: 광주시립미술관 광주미디어아트플랫폼 G.MAP(지맵)은 오는 9일 오후 2시 1층 미디어 라운지에서 미디어아트 특별전시 ‘이이남 -각 사람에게 비추는 빛’의 연계 행사로 작가와의 대화를 개최한다. - - expected: `⠕⠀⠠⠦⠕⠕⠉⠢⠤⠫⠁⠀⠇⠐⠣⠢⠝⠈⠝⠀⠘⠕⠰⠍` - - actual: `⠕⠀⠠⠦⠕⠕⠉⠢⠀⠤⠫⠁⠀⠇⠐⠣⠢⠝⠈⠝⠀⠘⠕⠰` - - first differing cell (zero-based): 124 +- `sentence_02.json` #771: 이번 연구 결과 기존 AhR 활성화합물인 rutaecarpine(한약재), hydrocortisone(항염제, 호르몬), alantolactone(항염제, 살선충제)과 이번에 새롭게 발견된 물질들은 전체 발현된 독성의 2.6%~49%를 설명하는 것으로 나타났다. + - expected: `⠁⠝⠞⠕⠇⠁⠉⠞⠐⠕⠦⠄⠚⠶⠱⠢⠨⠝⠐⠀⠇⠂⠠⠾` + - actual: `⠁⠝⠞⠕⠇⠁⠉⠞⠕⠝⠑⠦⠄⠚⠶⠱⠢⠨⠝⠐⠀⠇⠂⠠` + - first differing cell (zero-based): 107 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_03.json` #4223: 그룹 엑소(EXO)의 단독 여행 버라이어티 프로그램 ‘엑소의 사다리 타고 세계여행 – 거제&통영편’이 새로운 시즌으로 찾아온다. - - expected: `⠠⠝⠈⠌⠱⠚⠗⠶⠤⠈⠎⠨⠝⠴⠈⠯⠲⠓⠿⠻⠙⠡⠴⠄` - - actual: `⠠⠝⠈⠌⠱⠚⠗⠶⠀⠠⠤⠀⠈⠎⠨⠝⠴⠈⠯⠲⠓⠿⠻⠙` - - first differing cell (zero-based): 76 +- `sentence_03.json` #2198: 이번 파트너십으로 LG AI연구원은 퓨리오사AI가 개발 중인 2세대 AI 반도체 레니게이드(Renegade)로 초거대 AI 엑사원(EXAONE) 기반의 ‘생성형 AI’ 상용 기술을 검증한다. + - expected: `⠦⠄⠴⠠⠠⠑⠭⠁⠐⠕⠠⠴⠀⠈⠕⠘⠒⠺⠀⠠⠦⠠⠗⠶` + - actual: `⠦⠄⠴⠠⠠⠑⠭⠁⠕⠝⠑⠠⠴⠀⠈⠕⠘⠒⠺⠀⠠⠦⠠⠗` + - first differing cell (zero-based): 131 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_04.json` #976: 하쿠토-R 미션1에는 달 표면을 굴러다닐 로봇이 실려 있다. 일본 우주항공연구개발기구(JAXA)와 장난감 기업 토미가 함께 만든 지름 8㎝짜리 공 모양의 초소형 로봇 ‘소라큐’다. - - expected: `⠚⠋⠍⠓⠥⠤⠴⠠⠗⠲⠀⠑⠕⠠⠡⠼⠁⠝⠉⠵⠀⠊⠂⠀` - - actual: `⠚⠋⠍⠓⠥⠀⠀⠔⠠⠗⠀⠑⠕⠠⠡⠼⠁⠝⠉⠵⠀⠊⠂⠀` - - first differing cell (zero-based): 5 + +### Residual `U+283C ⠼ -> U+2800 ⠀` + +- `sentence_01.json` #1702: 조광페인트의 신사업은 방열소재(TIM) 분야다. 조광페인트는- 2차전지 소재인 CK이엠솔루션을 자회사로 두고 관련 사업에 전력을 다하고 있다. + - expected: `⠙⠝⠟⠓⠪⠉⠵⠤⠼⠃⠰⠣⠨⠾⠨⠕⠀⠠⠥⠨⠗⠟⠀⠴` + - actual: `⠙⠝⠟⠓⠪⠉⠵⠤⠀⠼⠃⠰⠣⠨⠾⠨⠕⠀⠠⠥⠨⠗⠟⠀` + - first differing cell (zero-based): 56 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_02.json` #1610: 16일 한국석유공사 유가정보시스템 오피넷에 따르면 7월 둘째 주(9~ 12일) 울산 주유소 휘발유 평균 판매 가격은 전주(1천538.23원)보다 3.12원 상승한 L(리터)당 1천541.35원을 기록했다. + - expected: `⠨⠍⠦⠄⠼⠊⠈⠔⠼⠁⠃⠕⠂⠠⠴⠀⠯⠇⠒⠀⠨⠍⠩⠠` + - actual: `⠨⠍⠦⠄⠼⠊⠈⠔⠀⠼⠁⠃⠕⠂⠠⠴⠀⠯⠇⠒⠀⠨⠍⠩` + - first differing cell (zero-based): 66 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_03.json` #341: 미래에셋자산운용은 미국 대표지수에 환헤지형으로 투자하는 ‘TIGER 미국S&P500TR(H) 상장지수펀드(ETF)’와 ‘TIGER 미국나스닥100TR(H) ETF’ 순자산 합계가 1000억원을 돌파했다고 26일 밝혔다. + - expected: `⠈⠍⠁⠉⠠⠪⠊⠁⠼⠁⠚⠚⠴⠠⠠⠞⠗⠐⠣⠠⠓⠐⠜⠀` + - actual: `⠈⠍⠁⠉⠠⠪⠊⠁⠀⠀⠼⠁⠚⠚⠠⠠⠞⠗⠦⠠⠓⠴⠀⠴` + - first differing cell (zero-based): 138 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_04.json` #2974: 보고서는 팬데믹 이전의 추세를 웃도는 가계 저축액을 초과저축으로 정의했다. 팬데믹 기간 초과저축은 지난해 명목 국내총생산(GDP)의 4.7~ 6.0%, 명목 민간소비의 9.7~12.4% 수준이다. + - expected: `⠺⠀⠼⠙⠲⠛⠈⠔⠼⠋⠲⠚⠴⠏⠐⠀⠑⠻⠑⠭⠀⠑⠟⠫` + - actual: `⠺⠀⠼⠙⠲⠛⠈⠔⠀⠼⠋⠲⠚⠴⠏⠐⠀⠑⠻⠑⠭⠀⠑⠟` + - first differing cell (zero-based): 140 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` ## Cross-cutting input-only structural cohorts @@ -555,66 +546,65 @@ These are cross-cutting input-only structural cohorts, not new primary classes a | Cluster | Candidates | Exact | Mismatch | Conflicting-reference cases | |---|---:|---:|---:|---:| -| `allcaps_roman_run_beginning_with_pure_letter_shortform` | 4120 | 2521 | 1599 | 0 | -| `allcaps_roman_run_containing_ar` | 1022 | 371 | 651 | 0 | -| `allcaps_roman_run_containing_ed` | 816 | 334 | 482 | 0 | -| `allcaps_roman_run_containing_ou` | 1816 | 70 | 1746 | 0 | -| `allcaps_roman_run_containing_st` | 1479 | 703 | 776 | 0 | -| `ampersand_before_attached_ascii_roman_segment` | 30 | 12 | 18 | 0 | -| `ascii_apostrophe_between_ascii_letter_runs` | 147 | 68 | 79 | 0 | -| `ascii_roman_tail_comma_before_digit_korean_token` | 58 | 3 | 55 | 0 | -| `attached_ascii_roman_segments_joined_by_ampersand` | 802 | 608 | 194 | 0 | -| `attached_ascii_roman_to_korean_script_boundary` | 17693 | 13465 | 4228 | 0 | -| `attached_korean_auxiliary_itda_spacing` | 95 | 72 | 23 | 0 | -| `attached_plus_followed_by_parenthesized_korean_gloss` | 16 | 3 | 13 | 0 | -| `capitals_word_mode_previously_spanning_nonletter_scope` | 1733 | 854 | 879 | 0 | -| `closed_roman_parenthetical_after_non_ascii_letter_boundary` | 63959 | 53122 | 10837 | 0 | -| `compact_numeric_ascii_letter_suffix` | 2975 | 1917 | 1058 | 0 | -| `consecutive_ascii_roman_words_whitespace_boundary` | 4679 | 2358 | 2321 | 0 | -| `decimal_point_between_ascii_digits` | 4546 | 3447 | 1099 | 0 | -| `korean_inline_parenthesized_single_arithmetic_operator` | 23 | 17 | 6 | 0 | -| `korean_majority_same_token_roman_sandwich_non_domain` | 947 | 593 | 354 | 0 | -| `korean_prefixed_closed_allcaps_parenthetical` | 54492 | 45705 | 8787 | 0 | -| `korean_prefixed_closed_roman_annotation_rule_34_order` | 64382 | 53716 | 10666 | 0 | -| `korean_prefixed_roman_parenthetical_followed_by_allcaps_hyphen_suffix` | 11 | 0 | 11 | 0 | +| `allcaps_roman_run_beginning_with_pure_letter_shortform` | 3242 | 2809 | 433 | 0 | +| `allcaps_roman_run_containing_ar` | 1022 | 480 | 542 | 0 | +| `allcaps_roman_run_containing_ed` | 816 | 362 | 454 | 0 | +| `allcaps_roman_run_containing_ou` | 1816 | 128 | 1688 | 0 | +| `allcaps_roman_run_containing_st` | 1479 | 815 | 664 | 0 | +| `ampersand_before_attached_ascii_roman_segment` | 30 | 14 | 16 | 0 | +| `ascii_apostrophe_between_ascii_letter_runs` | 147 | 106 | 41 | 0 | +| `ascii_roman_tail_comma_before_digit_korean_token` | 58 | 34 | 24 | 0 | +| `attached_ascii_roman_segments_joined_by_ampersand` | 802 | 687 | 115 | 0 | +| `attached_ascii_roman_to_korean_script_boundary` | 17693 | 15206 | 2487 | 0 | +| `attached_korean_auxiliary_itda_spacing` | 95 | 84 | 11 | 0 | +| `attached_korean_to_roman_hyphen_boundary` | 105 | 84 | 21 | 0 | +| `attached_plus_followed_by_parenthesized_korean_gloss` | 16 | 4 | 12 | 0 | +| `capitals_word_mode_previously_spanning_nonletter_scope` | 1733 | 1270 | 463 | 0 | +| `closed_roman_parenthetical_after_non_ascii_letter_boundary` | 63959 | 57175 | 6784 | 0 | +| `compact_numeric_ascii_letter_suffix` | 2975 | 2492 | 483 | 0 | +| `consecutive_ascii_roman_words_whitespace_boundary` | 4679 | 3314 | 1365 | 0 | +| `decimal_point_between_ascii_digits` | 4546 | 4106 | 440 | 0 | +| `korean_inline_parenthesized_single_arithmetic_operator` | 23 | 22 | 1 | 0 | +| `korean_majority_same_token_roman_sandwich_non_domain` | 947 | 736 | 211 | 0 | +| `korean_prefixed_closed_allcaps_parenthetical` | 54492 | 48892 | 5600 | 0 | +| `korean_prefixed_closed_roman_annotation_rule_34_order` | 64382 | 57748 | 6634 | 0 | +| `korean_prefixed_roman_parenthetical_followed_by_allcaps_hyphen_suffix` | 11 | 1 | 10 | 0 | | `mixed_roman_korean_word_before_uppercase_headword_expansion` | 10 | 7 | 3 | 0 | | `multi_character_allcaps_roman_runs_joined_by_middle_dot` | 97 | 0 | 97 | 0 | | `percent_point_unit_list_comma` | 7 | 5 | 2 | 0 | -| `pure_allcaps_segment_before_hyphen_and_multi_allcaps_segment_after` | 448 | 226 | 222 | 0 | -| `roman_hyphenated_word_after_whitespace_following_korean_word` | 361 | 86 | 275 | 0 | -| `roman_parenthetical_headword_after_whitespace_following_korean_word` | 4695 | 3610 | 1085 | 0 | -| `roman_run_after_whitespace_following_closed_roman_enclosure` | 1093 | 144 | 949 | 0 | +| `pure_allcaps_segment_before_hyphen_and_multi_allcaps_segment_after` | 448 | 359 | 89 | 0 | +| `roman_hyphenated_word_after_whitespace_following_korean_word` | 361 | 277 | 84 | 0 | +| `roman_parenthetical_headword_after_whitespace_following_korean_word` | 4695 | 3945 | 750 | 0 | +| `roman_run_after_whitespace_following_closed_roman_enclosure` | 1093 | 580 | 513 | 0 | | `roman_run_immediately_before_attached_middle_dot_boundary` | 577 | 0 | 577 | 0 | -| `rule69_ascii_unit_before_terminator_skipping_symbol` | 440 | 344 | 96 | 0 | -| `single_capital_followed_by_parenthesized_digits` | 1361 | 0 | 1361 | 0 | -| `spaced_comma_between_ascii_digit_runs` | 217 | 182 | 35 | 0 | -| `standalone_multi_character_uppercase_roman_word` | 62411 | 51419 | 10992 | 0 | -| `tight_triangle_mark_immediately_before_korean` | 377 | 0 | 377 | 0 | -| `uppercase_alphanumeric_roman_digit_sequence` | 3429 | 2097 | 1332 | 0 | -| `uppercase_ascii_run_immediately_after_digit_in_roman_sequence` | 1896 | 1267 | 629 | 0 | -| `uppercase_ascii_run_immediately_after_hyphen_in_roman_sequence` | 952 | 271 | 681 | 0 | -| `uppercase_ascii_segments_joined_by_ampersand_capitalization` | 439 | 321 | 118 | 0 | -| `uppercase_roman_headword_closed_multiword_parenthetical` | 175 | 87 | 88 | 0 | -| `uppercase_roman_run_followed_by_hyphen_digits` | 571 | 194 | 377 | 0 | -| `uppercase_word_after_whitespace_continuing_ascii_roman_text` | 1729 | 771 | 958 | 0 | +| `rule69_ascii_unit_before_terminator_skipping_symbol` | 440 | 385 | 55 | 0 | +| `single_capital_followed_by_parenthesized_digits` | 1361 | 1351 | 10 | 0 | +| `spaced_comma_between_ascii_digit_runs` | 217 | 195 | 22 | 0 | +| `standalone_multi_character_uppercase_roman_word` | 62411 | 55551 | 6860 | 0 | +| `tight_triangle_mark_immediately_before_korean` | 377 | 317 | 60 | 0 | +| `uppercase_alphanumeric_roman_digit_sequence` | 3429 | 2861 | 568 | 0 | +| `uppercase_ascii_run_immediately_after_digit_in_roman_sequence` | 1896 | 1569 | 327 | 0 | +| `uppercase_ascii_run_immediately_after_hyphen_in_roman_sequence` | 952 | 724 | 228 | 0 | +| `uppercase_ascii_segments_joined_by_ampersand_capitalization` | 439 | 361 | 78 | 0 | +| `uppercase_roman_headword_closed_multiword_parenthetical` | 175 | 113 | 62 | 0 | +| `uppercase_roman_run_followed_by_hyphen_digits` | 571 | 504 | 67 | 0 | +| `uppercase_word_after_whitespace_continuing_ascii_roman_text` | 1729 | 1115 | 614 | 0 | ### `allcaps_roman_run_beginning_with_pure_letter_shortform` -Of the 4120 candidates, 1528 are the actual `pending_rule_review` subcluster. The other 2592 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 3242 candidates, 321 are the actual `pending_rule_review` subcluster. The other 2921 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. -For this output-signature audit, 1599 mismatches were evaluable and 837 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. +For this output-signature audit, 433 mismatches were evaluable and 52 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. Localized first-difference transitions: -- `U+2830 ⠰ -> U+2820 ⠠`: 789 -- `U+2820 ⠠ -> U+2830 ⠰`: 48 +- `U+2820 ⠠ -> U+2830 ⠰`: 50 +- `U+2830 ⠰ -> U+2820 ⠠`: 2 Mismatch primary-class distribution: -- `comparison_method`: 12 -- `corpus_suspect`: 50 -- `pending_rule_review`: 1528 -- `unsupported_character_review`: 9 +- `corpus_suspect`: 112 +- `pending_rule_review`: 321 Representative `exact` samples: @@ -622,85 +612,82 @@ Representative `exact` samples: - expected: `⠴⠠⠠⠎⠅⠲⠨⠪⠶⠈⠏⠒⠵⠀⠼⠃⠕⠂⠀⠇⠢⠠⠻⠴` - actual: `⠴⠠⠠⠎⠅⠲⠨⠪⠶⠈⠏⠒⠵⠀⠼⠃⠕⠂⠀⠇⠢⠠⠻⠴` - current primary/reason: `exact` / `exact` -- `sentence_02.json` #4: KDI에 따르면, 비제조업 업황BSI(기업경기실사지수) 전망치는 2월 72에서 3월 74로 개선되고 있다. 중국 관광객 유입에 대한 기대감이 확산된 영향으로 분석된다. - - expected: `⠴⠠⠠⠅⠙⠊⠲⠝⠀⠠⠊⠐⠪⠑⠡⠐⠀⠘⠕⠨⠝⠨⠥⠎` - - actual: `⠴⠠⠠⠅⠙⠊⠲⠝⠀⠠⠊⠐⠪⠑⠡⠐⠀⠘⠕⠨⠝⠨⠥⠎` +- `sentence_02.json` #356: 한편, 이번에 새롭게 추가한 ‘나이스플러스(NEIS+)’를 통해 수강신청, 학습기록 관리, 수업피드백 등을 관리하여 학생, 학부모, 교사가 서로 소통하는 완벽한 학교 수업지원 플랫폼으로 거듭나기를 기대하고 있다. + - expected: `⠚⠒⠙⠡⠐⠀⠕⠘⠾⠝⠀⠠⠗⠐⠥⠃⠈⠝⠀⠰⠍⠫⠚⠒` + - actual: `⠚⠒⠙⠡⠐⠀⠕⠘⠾⠝⠀⠠⠗⠐⠥⠃⠈⠝⠀⠰⠍⠫⠚⠒` - current primary/reason: `exact` / `exact` -- `sentence_03.json` #54: 위탁생산은 경기도 하남에 구축한 HK이노엔의 세포유전자치료제 센터에서 진행한다. HK이노엔은 자체 연구뿐 아니라 위탁생산(CMO)·위탁개발생산(CDMO) 등을 통해 세포유전자치료제센터를 혁신 플랫폼으로 키울 방침이다. - - expected: `⠍⠗⠓⠁⠠⠗⠶⠇⠒⠵⠀⠈⠻⠈⠕⠊⠥⠀⠚⠉⠢⠝⠀⠈` - - actual: `⠍⠗⠓⠁⠠⠗⠶⠇⠒⠵⠀⠈⠻⠈⠕⠊⠥⠀⠚⠉⠢⠝⠀⠈` +- `sentence_03.json` #30: “챗GPT보다 가벼운 여러 대규모언어모델(LLM)이 출현해 오픈AI 독점 구도에 균열을 내고 있다. 한국어 특화 LLM으로 글로벌 경쟁에 대응하면서 세부 서비스(튜닝) 모델에서 차별성을 확보하는 전략이 중요하다.” + - expected: `⠦⠰⠗⠄⠴⠠⠠⠛⠏⠞⠲⠘⠥⠊⠀⠫⠘⠱⠛⠀⠱⠐⠎⠀` + - actual: `⠦⠰⠗⠄⠴⠠⠠⠛⠏⠞⠲⠘⠥⠊⠀⠫⠘⠱⠛⠀⠱⠐⠎⠀` - current primary/reason: `exact` / `exact` -- `sentence_04.json` #30: 뱅크오브아메리카(BOA), 시티그룹, JP모건체이스, 웰스파고가 각각 50억달러를 예치하고, 골드만삭스와 모건스탠리가 각각 25억달러를 넣기로 했다. BNY멜론, PNC뱅크, 스테이트스트리트, 트루이스트, US뱅크는 각각 10억달러를 예치한다. - - expected: `⠘⠗⠶⠋⠪⠥⠘⠪⠣⠑⠝⠐⠕⠋⠦⠄⠴⠠⠠⠃⠕⠁⠠⠴` - - actual: `⠘⠗⠶⠋⠪⠥⠘⠪⠣⠑⠝⠐⠕⠋⠦⠄⠴⠠⠠⠃⠕⠁⠠⠴` +- `sentence_04.json` #81: SGI는 “산업연관분석을 활용해 우리나라의 대일 수출 증가가 국내총생산(GDP)에 미치는 영향을 계산해 보면 경제성장률은 0.1%포인트 높아질 것”이라고 밝혔다. + - expected: `⠴⠠⠠⠎⠛⠊⠲⠉⠵⠀⠦⠇⠒⠎⠃⠡⠈⠧⠒⠘⠛⠠⠹⠮` + - actual: `⠴⠠⠠⠎⠛⠊⠲⠉⠵⠀⠦⠇⠒⠎⠃⠡⠈⠧⠒⠘⠛⠠⠹⠮` - current primary/reason: `exact` / `exact` Representative `localized_mismatch` samples: -- `sentence_01.json` #312: 엔비디아가 대규모 언어 모델(LLM)과 음성 AI를 주제로 엔비디아 AI 개발자 밋업을 개최한다. 이번 개발자 밋업은 오는 17일 오후 2시부터 5시까지 온라인으로 진행된다. - - expected: `⠑⠥⠊⠝⠂⠦⠄⠴⠰⠠⠠⠇⠇⠍⠠⠴⠈⠧⠀⠪⠢⠠⠻⠀` - - actual: `⠑⠥⠊⠝⠂⠦⠄⠴⠠⠠⠇⠇⠍⠠⠴⠈⠧⠀⠪⠢⠠⠻⠀⠴` - - first differing cell (zero-based): 27 +- `sentence_01.json` #3447: 점검 내용은 폐수 처리 업체 등록 기준, 준수 사항, 방류 수 배출 허용 기준 준수 여부 등이다. 수질원격감시체계(TMS) 설치 지원 사업 상담·기술 지원도 병행한다. + - expected: `⠕⠰⠝⠈⠌⠦⠄⠴⠠⠠⠞⠍⠎⠠⠴⠀⠠⠞⠰⠕⠀⠨⠕⠏` + - actual: `⠕⠰⠝⠈⠌⠦⠄⠴⠰⠠⠠⠞⠍⠎⠠⠴⠀⠠⠞⠰⠕⠀⠨⠕` + - first differing cell (zero-based): 110 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_02.json` #68: 조성된 무장애나눔길은 통행이 많은 숲속의집~생태숲학습관 0.73km 구간에 평균 경사도 5% 이하 데크산책로와 황토포장길로 조성됐으며 전국 무장애나눔길 최초로 장애물 없는 생활환경(BF)인증을 추진하고 있다. - - expected: `⠚⠧⠒⠈⠻⠦⠄⠴⠠⠠⠃⠋⠠⠴⠟⠨⠪⠶⠮⠀⠰⠍⠨⠟` - - actual: `⠚⠧⠒⠈⠻⠦⠄⠴⠰⠠⠠⠃⠋⠠⠴⠟⠨⠪⠶⠮⠀⠰⠍⠨` - - first differing cell (zero-based): 190 +- `sentence_02.json` #1533: 지난 21일 개통된 4세대 교육행정 정보시스템 ‘나이스(NEIS)’에 대한 현직 교사들의 불만족도가 높은 가운데 울산에서도 시스템 불안정에 대한 문제가 제기됐다. + - expected: `⠉⠣⠕⠠⠪⠦⠄⠴⠠⠠⠝⠑⠊⠎⠠⠴⠴⠄⠝⠀⠊⠗⠚⠒` + - actual: `⠉⠣⠕⠠⠪⠦⠄⠴⠰⠠⠠⠝⠑⠊⠎⠠⠴⠴⠄⠝⠀⠊⠗⠚` + - first differing cell (zero-based): 58 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_03.json` #30: “챗GPT보다 가벼운 여러 대규모언어모델(LLM)이 출현해 오픈AI 독점 구도에 균열을 내고 있다. 한국어 특화 LLM으로 글로벌 경쟁에 대응하면서 세부 서비스(튜닝) 모델에서 차별성을 확보하는 전략이 중요하다.” - - expected: `⠑⠥⠊⠝⠂⠦⠄⠴⠰⠠⠠⠇⠇⠍⠠⠴⠕⠀⠰⠯⠚⠡⠚⠗` - - actual: `⠑⠥⠊⠝⠂⠦⠄⠴⠠⠠⠇⠇⠍⠠⠴⠕⠀⠰⠯⠚⠡⠚⠗⠀` - - first differing cell (zero-based): 40 +- `sentence_03.json` #5589: 플래티넘 모델에만 탑재된 사양으로는 헤드업디스플레이(HUD), 레인 센서, 파노라믹뷰 모니터, 디지털 리어뷰 미러, 2열 열선시트, 자동 전조등 시스템(AFS) 등이 있다. + - expected: `⠠⠪⠓⠝⠢⠦⠄⠴⠠⠠⠁⠋⠎⠠⠴⠀⠊⠪⠶⠕⠀⠕⠌⠊` + - actual: `⠠⠪⠓⠝⠢⠦⠄⠴⠰⠠⠠⠁⠋⠎⠠⠴⠀⠊⠪⠶⠕⠀⠕⠌` + - first differing cell (zero-based): 147 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_04.json` #81: SGI는 “산업연관분석을 활용해 우리나라의 대일 수출 증가가 국내총생산(GDP)에 미치는 영향을 계산해 보면 경제성장률은 0.1%포인트 높아질 것”이라고 밝혔다. - - expected: `⠠⠗⠶⠇⠒⠦⠄⠴⠰⠠⠠⠛⠙⠏⠠⠴⠝⠀⠑⠕⠰⠕⠉⠵` - - actual: `⠠⠗⠶⠇⠒⠦⠄⠴⠠⠠⠛⠙⠏⠠⠴⠝⠀⠑⠕⠰⠕⠉⠵⠀` - - first differing cell (zero-based): 72 +- `sentence_04.json` #119: 21일 국제금융센터에 따르면 KB국민은행의 지난 17일 신용부도스와프(CDS) 프리미엄은 43bp(1bp는 0.01%포인트)로 일주일 전 대비 1bp 상승하는 데 그쳤다. + - expected: `⠠⠪⠧⠙⠪⠦⠄⠴⠠⠠⠉⠙⠎⠠⠴⠀⠙⠪⠐⠕⠑⠕⠎⠢` + - actual: `⠠⠪⠧⠙⠪⠦⠄⠴⠰⠠⠠⠉⠙⠎⠠⠴⠀⠙⠪⠐⠕⠑⠕⠎` + - first differing cell (zero-based): 74 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` Representative `mismatch` samples: - `sentence_01.json` #191: 소프트웨어 측면에서는 스마트 TV 독자 운영체제 웹(web)OS의 진화를 앞세워 맞춤형 고객경험과 CDX(Cross Device eXperience) 경험을 강화한다. - expected: `⠰⠝⠨⠝⠀⠏⠗⠃⠴⠐⠣⠺⠑⠃⠐⠜⠠⠠⠕⠎⠲⠺⠀⠨` - - actual: `⠰⠝⠨⠝⠀⠏⠗⠃⠦⠄⠴⠺⠑⠃⠠⠴⠰⠠⠠⠕⠎⠲⠺⠀` + - actual: `⠰⠝⠨⠝⠀⠏⠗⠃⠦⠄⠴⠺⠑⠃⠠⠴⠴⠠⠠⠕⠎⠲⠺⠀` - first differing cell (zero-based): 48 - current primary/reason: `corpus_suspect` / `rule34_roman_indicator_before_opening_parenthesis` -- `sentence_02.json` #68: 조성된 무장애나눔길은 통행이 많은 숲속의집~생태숲학습관 0.73km 구간에 평균 경사도 5% 이하 데크산책로와 황토포장길로 조성됐으며 전국 무장애나눔길 최초로 장애물 없는 생활환경(BF)인증을 추진하고 있다. - - expected: `⠚⠧⠒⠈⠻⠦⠄⠴⠠⠠⠃⠋⠠⠴⠟⠨⠪⠶⠮⠀⠰⠍⠨⠟` - - actual: `⠚⠧⠒⠈⠻⠦⠄⠴⠰⠠⠠⠃⠋⠠⠴⠟⠨⠪⠶⠮⠀⠰⠍⠨` - - first differing cell (zero-based): 190 - - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_02.json` #99: 황승주 씨는 “기능성 소화불량증, 특히 PDS(식후불편감증후군) 복부팽만감 유형으로 고통 받는 환자들에게 적용가능한 안전하고 높은 치료효율의 한약소재를 개발해 국민건강에 이바지하고자 한다”고 말했다. + - expected: `⠀⠓⠪⠁⠚⠕⠀⠴⠰⠠⠠⠏⠙⠎⠦⠄⠠⠕⠁⠚⠍⠘⠯⠙` + - actual: `⠀⠓⠪⠁⠚⠕⠀⠴⠠⠠⠏⠙⠎⠦⠄⠠⠕⠁⠚⠍⠘⠯⠙⠡` + - first differing cell (zero-based): 45 + - current primary/reason: `corpus_suspect` / `ueb_grade1_before_nonstanding_opening_parenthesis` - `sentence_03.json` #6: 지난해 9월 HMM과 파나시아가 선박용 탄소포집 시스템 공동연구에 대한 업무협약(MOU)을 체결한 뒤 7개월 만에 내놓은 연구성과다. - expected: `⠜⠁⠦⠄⠴⠠⠠⠍⠕⠥⠠⠴⠮⠀⠰⠝⠈⠳⠚⠒⠀⠊⠍⠗` - actual: `⠜⠁⠦⠄⠴⠠⠠⠍⠳⠠⠴⠮⠀⠰⠝⠈⠳⠚⠒⠀⠊⠍⠗⠀` - first differing cell (zero-based): 83 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` - `sentence_04.json` #19: 이날 행사에는 구 대표 외에 권봉석 (주)LG 최고운영책임자(COO), 박일평 LG사이언스파크대표(사장)를 비롯해 각 계열사 최고기술책임자(CTO), 최고데이터책임자(CDO), 최고인사책임자(CHO) 등이 참석해 국내 이공계 R&D 인재 400여명과 만났다. - - expected: `⠁⠕⠢⠨⠦⠄⠴⠠⠠⠉⠕⠕⠠⠴⠐⠀⠘⠁⠕⠂⠙⠻⠀⠴` - - actual: `⠁⠕⠢⠨⠦⠄⠴⠠⠉⠠⠕⠠⠕⠠⠴⠐⠀⠘⠁⠕⠂⠙⠻⠀` - - first differing cell (zero-based): 60 + - expected: `⠁⠕⠢⠨⠦⠄⠴⠠⠠⠉⠓⠕⠠⠴⠀⠊⠪⠶⠕⠀⠰⠣⠢⠠` + - actual: `⠁⠕⠢⠨⠦⠄⠴⠠⠉⠠⠓⠠⠕⠠⠴⠀⠊⠪⠶⠕⠀⠰⠣⠢` + - first differing cell (zero-based): 188 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` ### `allcaps_roman_run_containing_ar` -Of the 1022 candidates, 627 are the actual `pending_rule_review` subcluster. The other 395 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 1022 candidates, 521 are the actual `pending_rule_review` subcluster. The other 501 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. -For this output-signature audit, 651 mismatches were evaluable and 407 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. +For this output-signature audit, 542 mismatches were evaluable and 416 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. Localized first-difference transitions: -- `U+2801 ⠁ -> U+281C ⠜`: 400 +- `U+2801 ⠁ -> U+281C ⠜`: 411 - `U+2810 ⠐ -> U+2815 ⠕`: 3 - `U+2800 ⠀ -> U+2820 ⠠`: 2 -- `U+2820 ⠠ -> U+280A ⠊`: 1 -- `U+2820 ⠠ -> U+281E ⠞`: 1 Mismatch primary-class distribution: -- `comparison_method`: 3 -- `corpus_suspect`: 15 -- `pending_rule_review`: 627 -- `unsupported_character_review`: 6 +- `corpus_suspect`: 18 +- `pending_rule_review`: 521 +- `unsupported_character_review`: 3 Representative `exact` samples: @@ -708,13 +695,13 @@ Representative `exact` samples: - expected: `⠝⠘⠎⠠⠥⠯⠵⠀⠰⠯⠠⠕⠀⠨⠾⠘⠍⠓⠎⠀⠨⠕⠠⠪` - actual: `⠝⠘⠎⠠⠥⠯⠵⠀⠰⠯⠠⠕⠀⠨⠾⠘⠍⠓⠎⠀⠨⠕⠠⠪` - current primary/reason: `exact` / `exact` -- `sentence_02.json` #649: 피아니스트 임동창과 TATARANG(타타랑)은 김호연재가 남긴 시를 읽으며 느낀 그녀의 담대함과 호연함을 관객들에게 전달하고자 이번 공연을 준비했다. - - expected: `⠙⠕⠣⠉⠕⠠⠪⠓⠪⠀⠕⠢⠊⠿⠰⠣⠶⠈⠧⠀⠴⠠⠠⠞` - - actual: `⠙⠕⠣⠉⠕⠠⠪⠓⠪⠀⠕⠢⠊⠿⠰⠣⠶⠈⠧⠀⠴⠠⠠⠞` +- `sentence_02.json` #344: 한편 이장우 대전시장은 이틀간의 대만 방문을 마무리하고 싱가포르로 이동해 18일까지 Merck(머크) 사, 국립싱가포르대학 바이오연구단, A-STAR 및 바이오폴리스 등 바이오산업 관련 해외 기업과 연구기관을 방문할 예정이다. + - expected: `⠚⠒⠙⠡⠀⠕⠨⠶⠍⠀⠊⠗⠨⠾⠠⠕⠨⠶⠵⠀⠕⠓⠮⠫` + - actual: `⠚⠒⠙⠡⠀⠕⠨⠶⠍⠀⠊⠗⠨⠾⠠⠕⠨⠶⠵⠀⠕⠓⠮⠫` - current primary/reason: `exact` / `exact` -- `sentence_03.json` #779: 김영대 평론가는 ‘아미(ARMY)가 인정한 평론가’로 정평이 나 있는 만큼, 대한민국을 넘어 전 세계적인 사랑을 받고 있는 BTS의 팬들이라면 놓치지 말아야 할 방송으로 알차게 꾸며질 예정이다. - - expected: `⠈⠕⠢⠻⠊⠗⠀⠙⠻⠐⠷⠫⠉⠵⠀⠠⠦⠣⠑⠕⠦⠄⠴⠠` - - actual: `⠈⠕⠢⠻⠊⠗⠀⠙⠻⠐⠷⠫⠉⠵⠀⠠⠦⠣⠑⠕⠦⠄⠴⠠` +- `sentence_03.json` #192: 국내 시총 상위 10개 기업에 투자하는 ETF 역시 코스피를 뛰어넘었다. 일례로 NH-아문디자산운용의 하나로(HANARO) 200 TOP10 ETF는 연초 이후 17.6% 올라 같은 기간 코스피 상승률(14.3%)을 넘어섰다. + - expected: `⠈⠍⠁⠉⠗⠀⠠⠕⠰⠿⠀⠇⠶⠍⠗⠀⠼⠁⠚⠈⠗⠀⠈⠕` + - actual: `⠈⠍⠁⠉⠗⠀⠠⠕⠰⠿⠀⠇⠶⠍⠗⠀⠼⠁⠚⠈⠗⠀⠈⠕` - current primary/reason: `exact` / `exact` - `sentence_04.json` #596: 롯데백화점은 바닷가와 도심 등에 이어 올 봄에는 서울 경희궁 공원 입구에 ‘리얼스 마켓(RE:EARTH)’을 연다고 10일 밝혔다. - expected: `⠐⠥⠄⠊⠝⠘⠗⠁⠚⠧⠨⠎⠢⠵⠀⠘⠊⠄⠫⠧⠀⠊⠥⠠` @@ -769,23 +756,20 @@ Representative `mismatch` samples: ### `allcaps_roman_run_containing_ed` -Of the 816 candidates, 411 are the actual `pending_rule_review` subcluster. The other 405 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 816 candidates, 386 are the actual `pending_rule_review` subcluster. The other 430 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. -For this output-signature audit, 482 mismatches were evaluable and 330 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. +For this output-signature audit, 454 mismatches were evaluable and 341 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. Localized first-difference transitions: -- `U+2811 ⠑ -> U+282B ⠫`: 327 +- `U+2811 ⠑ -> U+282B ⠫`: 339 - `U+2810 ⠐ -> U+280E ⠎`: 1 - `U+2810 ⠐ -> U+281D ⠝`: 1 -- `U+2815 ⠕ -> U+2820 ⠠`: 1 Mismatch primary-class distribution: -- `comparison_method`: 1 -- `corpus_suspect`: 65 -- `pending_rule_review`: 411 -- `unsupported_character_review`: 5 +- `corpus_suspect`: 68 +- `pending_rule_review`: 386 Representative `exact` samples: @@ -833,9 +817,9 @@ Representative `mismatch` samples: - `sentence_01.json` #35: 삼성전자는 올해 CES에서 77인치 OLED TV를 처음으로 공개할 예정이다. 이는 지난 8월 ‘국제정보디스플레이학술대회(IMID) 2022’에서 삼성디스플레이가 공개한 77인치 퀀텀닷(QD)-OLED 패널을 탑재한 제품이다. - expected: `⠋⠏⠒⠓⠎⠢⠊⠄⠴⠐⠣⠟⠙⠐⠜⠤⠠⠠⠕⠇⠫⠲⠀⠙` - - actual: `⠋⠏⠒⠓⠎⠢⠊⠄⠀⠀⠦⠠⠠⠟⠙⠴⠔⠠⠠⠕⠇⠑⠙⠀` + - actual: `⠋⠏⠒⠓⠎⠢⠊⠄⠦⠄⠴⠠⠠⠟⠙⠠⠴⠤⠴⠠⠠⠕⠇⠫` - first differing cell (zero-based): 172 - - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` + - current primary/reason: `corpus_suspect` / `rule34_roman_indicator_before_opening_parenthesis` - `sentence_02.json` #33: 지원은 학생이 영재교육종합데이터베이스(GED)를 통해 원서 접수하고 교사의 추천을 받아 진행하고, 자세한 내용은 학교 및 영재교육 기관별 홈페이지에 탑재되어 있다. - expected: `⠠⠪⠦⠄⠴⠠⠠⠛⠑⠙⠠⠴⠐⠮⠀⠓⠿⠚⠗⠀⠏⠒⠠⠎` - actual: `⠠⠪⠦⠄⠴⠠⠠⠛⠫⠠⠴⠐⠮⠀⠓⠿⠚⠗⠀⠏⠒⠠⠎⠀` @@ -848,27 +832,25 @@ Representative `mismatch` samples: - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` - `sentence_04.json` #62: LG디스플레이가 자사의 유기발광다이오드(OLED) TV 패널이 글로벌 친환경 인증기관인 카본 트러스트(Carbon Trust)로 부터 탄소발자국 인증을 획득했다고 19일 밝혔다. - expected: `⠧⠶⠊⠣⠕⠥⠊⠪⠴⠐⠣⠠⠠⠕⠇⠫⠐⠜⠀⠠⠠⠞⠧⠲` - - actual: `⠧⠶⠊⠣⠕⠥⠊⠪⠦⠄⠴⠠⠠⠕⠇⠫⠠⠴⠀⠰⠠⠠⠞⠧` + - actual: `⠧⠶⠊⠣⠕⠥⠊⠪⠦⠄⠴⠠⠠⠕⠇⠫⠠⠴⠀⠴⠠⠠⠞⠧` - first differing cell (zero-based): 35 - current primary/reason: `corpus_suspect` / `rule34_roman_indicator_before_opening_parenthesis` ### `allcaps_roman_run_containing_ou` -Of the 1816 candidates, 1660 are the actual `pending_rule_review` subcluster. The other 156 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 1816 candidates, 1679 are the actual `pending_rule_review` subcluster. The other 137 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. -For this output-signature audit, 1746 mismatches were evaluable and 1365 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. +For this output-signature audit, 1688 mismatches were evaluable and 1506 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. Localized first-difference transitions: -- `U+2815 ⠕ -> U+2833 ⠳`: 1364 -- `U+2810 ⠐ -> U+283D ⠽`: 1 +- `U+2815 ⠕ -> U+2833 ⠳`: 1504 +- `U+2810 ⠐ -> U+283D ⠽`: 2 Mismatch primary-class distribution: -- `comparison_method`: 2 - `corpus_suspect`: 9 -- `pending_rule_review`: 1660 -- `unsupported_character_review`: 75 +- `pending_rule_review`: 1679 Representative `exact` samples: @@ -880,9 +862,9 @@ Representative `exact` samples: - expected: `⠠⠊⠥⠀⠰⠾⠣⠒⠀⠇⠂⠙⠱⠩⠦⠄⠴⠠⠠⠽⠳⠠⠴⠀` - actual: `⠠⠊⠥⠀⠰⠾⠣⠒⠀⠇⠂⠙⠱⠩⠦⠄⠴⠠⠠⠽⠳⠠⠴⠀` - current primary/reason: `exact` / `exact` -- `sentence_03.json` #4357: 신세계인터내셔날이 수입·판매하는 비건 지향 메이크업 브랜드 아워글래스(HOURGLASS)가 스킨케어와 메이크업 기능이 결합된 베이스 메이크업 ‘베일 하이드레이팅 스킨 틴트(이하 스킨 틴트)’를 출시한다고 19일 밝혔다. - - expected: `⠠⠟⠠⠝⠈⠌⠟⠓⠎⠉⠗⠠⠱⠉⠂⠕⠀⠠⠍⠕⠃⠐⠆⠙` - - actual: `⠠⠟⠠⠝⠈⠌⠟⠓⠎⠉⠗⠠⠱⠉⠂⠕⠀⠠⠍⠕⠃⠐⠆⠙` +- `sentence_03.json` #606: 앞서 방탄소년단(BTS)의 2020년 정규 4집 ‘MAP OF THE SOUL : 7’의 337만여 장 초동 기록을 넘긴 것이다. + - expected: `⠣⠲⠠⠎⠀⠘⠶⠓⠒⠠⠥⠉⠡⠊⠒⠦⠄⠴⠠⠠⠃⠞⠎⠠` + - actual: `⠣⠲⠠⠎⠀⠘⠶⠓⠒⠠⠥⠉⠡⠊⠒⠦⠄⠴⠠⠠⠃⠞⠎⠠` - current primary/reason: `exact` / `exact` - `sentence_04.json` #6975: 이 밖에 포시즌스 호텔 바 ‘오울(OUL)’에서는 후 환유 라인의 대표 성분인 ‘삼’을 비롯해 구기자, 식초, 청귤 등을 활용해 만든 칵테일 3종도 판매한다. - expected: `⠕⠀⠘⠁⠁⠝⠀⠙⠥⠠⠕⠨⠵⠠⠪⠀⠚⠥⠓⠝⠂⠀⠘⠀` @@ -907,32 +889,29 @@ Representative `mismatch` samples: - first differing cell (zero-based): 175 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` - `sentence_04.json` #191: SK온은 23일 서울 종로구 SK서린사옥에서 에코프로머티리얼즈, 중국의 GEM(거린메이)과 전구체 생산을 위한 3자 합작법인인 지이엠코리아뉴에너지머티리얼즈㈜(이하 지이엠코리아) 설립 양해각서(MOU)를 체결했다고 밝혔다. - - expected: `⠴⠠⠠⠎⠅⠲⠷⠵⠀⠼⠃⠉⠕⠂⠀⠠⠎⠯⠀⠨⠿⠐⠥⠈` - - actual: `` - - error: `Invalid symbol character` - - first differing cell (zero-based): 0 - - current primary/reason: `unsupported_character_review` / `unsupported_character_review` + - expected: `⠠⠎⠦⠄⠴⠠⠠⠍⠕⠥⠠⠴⠐⠮⠀⠰⠝⠈⠳⠚⠗⠌⠊⠈` + - actual: `⠠⠎⠦⠄⠴⠠⠠⠍⠳⠠⠴⠐⠮⠀⠰⠝⠈⠳⠚⠗⠌⠊⠈⠥` + - first differing cell (zero-based): 191 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` ### `allcaps_roman_run_containing_st` -Of the 1479 candidates, 723 are the actual `pending_rule_review` subcluster. The other 756 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 1479 candidates, 625 are the actual `pending_rule_review` subcluster. The other 854 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. -For this output-signature audit, 776 mismatches were evaluable and 460 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. +For this output-signature audit, 664 mismatches were evaluable and 473 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. Localized first-difference transitions: -- `U+280E ⠎ -> U+280C ⠌`: 452 -- `U+2820 ⠠ -> U+280C ⠌`: 4 +- `U+280E ⠎ -> U+280C ⠌`: 466 +- `U+2820 ⠠ -> U+280C ⠌`: 2 +- `U+280B ⠋ -> U+2820 ⠠`: 1 - `U+280C ⠌ -> U+280E ⠎`: 1 - `U+2810 ⠐ -> U+2811 ⠑`: 1 -- `U+2810 ⠐ -> U+2825 ⠥`: 1 Mismatch primary-class distribution: -- `comparison_method`: 13 -- `corpus_suspect`: 35 -- `pending_rule_review`: 723 -- `unsupported_character_review`: 5 +- `corpus_suspect`: 39 +- `pending_rule_review`: 625 Representative `exact` samples: @@ -983,10 +962,10 @@ Representative `mismatch` samples: - actual: `⠈⠍⠦⠄⠴⠠⠠⠺⠌⠎⠠⠴⠝⠀⠠⠊⠐⠪⠑⠡⠀⠥⠂⠚` - first differing cell (zero-based): 67 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_02.json` #132: 이번 업무협약에는 대전시를 비롯해 △한국과학기술원(KAIST) △한국표준과학연구원(KRISS) △한국과학기술연구원(KIST) △한국과학기술정보연구원(KISTI) △한국전자통신연구원(ETRI) △한국원자력연구원(KAERI) △국방과학연구소(ADD) △나노종합기술원(NNFC) △국가보안기술연구소(NSR) 등 양자산업 관련 관 - - expected: `⠐⠥⠄⠚⠗⠀⠸⠬⠀⠚⠒⠈⠍⠁⠈⠧⠚⠁⠈⠕⠠⠯⠏⠒` - - actual: `⠐⠥⠄⠚⠗⠀⠸⠬⠇⠚⠒⠈⠍⠁⠈⠧⠚⠁⠈⠕⠠⠯⠏⠒` - - first differing cell (zero-based): 36 +- `sentence_02.json` #244: 충남대학교 이진숙 총장을 비롯한 관계자들은 5월 19일, 베트남 최고의 국립대인 하노이과학기술대학(HUST), 베트남국립농업대학(VNUA)을 연이어 방문해 ‘오픈 캠퍼스(open campus)’ 설립을 골자로 한 협정을 체결했다. + - expected: `⠁⠦⠄⠴⠠⠠⠓⠥⠎⠞⠠⠴⠐⠀⠘⠝⠓⠪⠉⠢⠈⠍⠁⠐` + - actual: `⠁⠦⠄⠴⠠⠠⠓⠥⠌⠠⠴⠐⠀⠘⠝⠓⠪⠉⠢⠈⠍⠁⠐⠕` + - first differing cell (zero-based): 101 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` - `sentence_03.json` #142: 수많은 작품 OST를 책임지며 실력파 프로듀서로 인정받고 있는 필승불패W, 리디아(Lydia), 장석원이 힘을 합쳐 만든 곡으로 웰메이드 OST 탄생을 예감케 한다. - expected: `⠙⠍⠢⠀⠴⠠⠠⠕⠎⠞⠲⠐⠮⠀⠰⠗⠁⠕⠢⠨⠕⠑⠱⠀` @@ -1001,9 +980,9 @@ Representative `mismatch` samples: ### `ampersand_before_attached_ascii_roman_segment` -Of the 30 candidates, 14 are the actual `pending_rule_review` subcluster. The other 16 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 30 candidates, 12 are the actual `pending_rule_review` subcluster. The other 18 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. -For this output-signature audit, 18 mismatches were evaluable and 4 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. +For this output-signature audit, 16 mismatches were evaluable and 4 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. Localized first-difference transitions: @@ -1013,13 +992,13 @@ Localized first-difference transitions: Mismatch primary-class distribution: - `corpus_suspect`: 4 -- `pending_rule_review`: 14 +- `pending_rule_review`: 12 Representative `exact` samples: -- `sentence_01.json` #5105: 하이브(HYBE) 소속 그룹 &TEAM(앤팀)의 팬 사인회에서 ‘속옷 검사’ 논란이 일자 주최 측에서 사과했지만, 파문은 계속되고 있다. - - expected: `⠚⠣⠕⠘⠪⠦⠄⠴⠠⠠⠓⠽⠃⠑⠠⠴⠀⠠⠥⠠⠭⠀⠈⠪` - - actual: `⠚⠣⠕⠘⠪⠦⠄⠴⠠⠠⠓⠽⠃⠑⠠⠴⠀⠠⠥⠠⠭⠀⠈⠪` +- `sentence_01.json` #202: 제이스코홀딩스는 필리핀 니켈 광산사업을 공동 추진중인 EVM(EV Mining &Development)이 광산지질국(MGB)에 4천700헥타르(약 1천400만평)에 대한 탐사허가(EP)를 신청했다고 9일 밝혔다. + - expected: `⠨⠝⠕⠠⠪⠋⠥⠚⠥⠂⠊⠕⠶⠠⠪⠉⠵⠀⠙⠕⠂⠐⠕⠙` + - actual: `⠨⠝⠕⠠⠪⠋⠥⠚⠥⠂⠊⠕⠶⠠⠪⠉⠵⠀⠙⠕⠂⠐⠕⠙` - current primary/reason: `exact` / `exact` - `sentence_02.json` #1319: 그는 이날 ‘2023 UNIST 과학&ICT 콘서트’ 행사에 발표자로 나서 연구중심대학을 표방하고 시작한 포스텍, 광주과학기술원(GIST) 등도 20년이 지나면서 고전을 면치 못했다고 설명했다. - expected: `⠈⠪⠉⠵⠀⠕⠉⠂⠀⠠⠦⠼⠃⠚⠃⠉⠀⠴⠠⠠⠥⠝⠊⠌` @@ -1045,52 +1024,51 @@ Representative `localized_mismatch` samples: Representative `mismatch` samples: -- `sentence_01.json` #202: 제이스코홀딩스는 필리핀 니켈 광산사업을 공동 추진중인 EVM(EV Mining &Development)이 광산지질국(MGB)에 4천700헥타르(약 1천400만평)에 대한 탐사허가(EP)를 신청했다고 9일 밝혔다. - - expected: `⠍⠨⠟⠨⠍⠶⠟⠀⠴⠠⠠⠑⠧⠍⠐⠣⠠⠠⠑⠧⠀⠠⠍⠔` - - actual: `⠍⠨⠟⠨⠍⠶⠟⠀⠀⠠⠠⠑⠧⠍⠦⠠⠠⠑⠧⠀⠴⠠⠍⠔` - - first differing cell (zero-based): 56 - - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_01.json` #4742: 시스템 반도체 설계 전문기업 코아시아가 ‘삼성 파운드리 포럼(SFF)&SAFE™ 포럼 2023’에 참가해 적극적인 글로벌 고객사 확보에 나설 계획이라고 27일 밝혔다. + - expected: `⠐⠕⠀⠙⠥⠐⠎⠢⠴⠐⠣⠠⠠⠎⠋⠋⠐⠜⠈⠯⠠⠠⠎⠁` + - actual: `⠐⠕⠀⠙⠥⠐⠎⠢⠦⠄⠴⠠⠠⠎⠋⠋⠠⠴⠴⠈⠯⠠⠠⠎` + - first differing cell (zero-based): 57 + - current primary/reason: `corpus_suspect` / `rule34_roman_indicator_before_opening_parenthesis` - `sentence_02.json` #974: 드림&Dream멘토링은 시간과 봉사정신을 내어주는(드림) 대학생 멘토와 그로 인해 꿈(Dream)을 이루는 고등학생 멘티가 함께 만들어 가는 이야기라는 뜻으로 멘티의 학교적응력 향상을 목표로 하는 프로그램이다. - expected: `⠊⠪⠐⠕⠢⠴⠈⠯⠴⠠⠙⠗⠂⠍⠲⠑⠝⠒⠓⠥⠐⠕⠶⠵` - actual: `⠊⠪⠐⠕⠢⠴⠈⠯⠠⠙⠗⠂⠍⠲⠑⠝⠒⠓⠥⠐⠕⠶⠵⠀` - first differing cell (zero-based): 8 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_03.json` #6686: 위메이드의 위믹스 3.0 기반 DAO &NFT 플랫폼 ‘나일’(NILE)이 세계 최초의 골프 포인트 대체 불가능 토큰(NFT) ‘레이스 투 위믹스 챔피언십 2023’을 공개했다. - - expected: `⠒⠀⠴⠠⠠⠙⠁⠕⠀⠈⠯⠠⠠⠝⠋⠞⠲⠀⠙⠮⠐⠗⠄⠙` - - actual: `⠒⠀⠴⠠⠠⠙⠁⠕⠲⠀⠴⠈⠯⠠⠠⠝⠋⠞⠲⠀⠙⠮⠐⠗` - - first differing cell (zero-based): 33 +- `sentence_03.json` #11394: 이어 1일 공개된 3차 라인업에는 동방신기(TVXQ!), 샤이니(SHINee), NCT 127, NCT DREAM, fromis_9(프로미스나인), CRAVITY, NewJeans, xikers, NiziU, &TEAM이 이름을 올리며 총 25팀이 출연을 확정했다. + - expected: `⠊⠿⠘⠶⠠⠟⠈⠕⠦⠄⠴⠠⠠⠞⠧⠭⠟⠖⠠⠴⠐⠀⠠⠜` + - actual: `⠊⠿⠘⠶⠠⠟⠈⠕⠀⠀⠦⠠⠠⠞⠧⠭⠟⠖⠴⠐⠀⠠⠜⠕` + - first differing cell (zero-based): 38 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` ### `ascii_apostrophe_between_ascii_letter_runs` -Of the 147 candidates, 75 are the actual `pending_rule_review` subcluster. The other 72 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 147 candidates, 37 are the actual `pending_rule_review` subcluster. The other 110 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. -For this output-signature audit, 79 mismatches were evaluable and 17 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. +For this output-signature audit, 41 mismatches were evaluable and 3 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. Localized first-difference transitions: -- `U+2822 ⠢ -> U+2811 ⠑`: 15 -- `U+2800 ⠀ -> U+2815 ⠕`: 1 +- `U+281D ⠝ -> U+280A ⠊`: 2 - `U+2820 ⠠ -> U+2803 ⠃`: 1 Mismatch primary-class distribution: - `corpus_suspect`: 4 -- `pending_rule_review`: 75 +- `pending_rule_review`: 37 Representative `exact` samples: -- `sentence_01.json` #2257: 금호건설이 임직원들의 기부금을 토대로 모두가 함께 만들어가는 기업의 사회적 책임(CSR) 활동을 위해 ‘DOVE's 캠페인’을 전개한다고 29일 밝혔다. - - expected: `⠈⠪⠢⠚⠥⠈⠾⠠⠞⠕⠀⠕⠢⠨⠕⠁⠏⠒⠊⠮⠺⠀⠈⠕` - - actual: `⠈⠪⠢⠚⠥⠈⠾⠠⠞⠕⠀⠕⠢⠨⠕⠁⠏⠒⠊⠮⠺⠀⠈⠕` +- `sentence_01.json` #67: LG전자는 오는 5일부터 8일까지 ‘CES 2023’이 열리는 미국 라스베이거스 컨벤션센터(LVCC)에 ‘라이프스굿(Life's Good)’을 소개하는 광고판을 설치한다고 3일 밝혔다. + - expected: `⠴⠠⠠⠇⠛⠲⠨⠾⠨⠉⠵⠀⠥⠉⠵⠀⠼⠑⠕⠂⠘⠍⠓⠎` + - actual: `⠴⠠⠠⠇⠛⠲⠨⠾⠨⠉⠵⠀⠥⠉⠵⠀⠼⠑⠕⠂⠘⠍⠓⠎` - current primary/reason: `exact` / `exact` - `sentence_02.json` #425: ‘제로(Zero)’는 ‘대전 0시 축제(Daejeon Zero O'clock Festival)’의 영문 이름에서 따왔으며, 축제장에서 3D홀로그램 미디어 콘텐츠 운영을 맡고 있는 애드테크 전문기업 코스윌에서 개발했다. - expected: `⠠⠦⠨⠝⠐⠥⠦⠄⠴⠠⠵⠻⠕⠠⠴⠴⠄⠉⠵⠀⠠⠦⠊⠗` - actual: `⠠⠦⠨⠝⠐⠥⠦⠄⠴⠠⠵⠻⠕⠠⠴⠴⠄⠉⠵⠀⠠⠦⠊⠗` - current primary/reason: `exact` / `exact` -- `sentence_03.json` #13917: 11일에는 NCT DREAM, 영탁, 프로미스나인, 드림캐처, CIX, EPEX, 최예나, 케플러, TNX, 템페스트, cignature(시그니처), 비오(BE'O) 등 12팀이 출연한다. - - expected: `⠼⠁⠁⠕⠂⠝⠉⠵⠀⠴⠠⠠⠝⠉⠞⠀⠠⠠⠙⠗⠂⠍⠐⠀` - - actual: `⠼⠁⠁⠕⠂⠝⠉⠵⠀⠴⠠⠠⠝⠉⠞⠀⠠⠠⠙⠗⠂⠍⠐⠀` +- `sentence_03.json` #13417: 트랙리스트에 따르면 이번 앨범에는 첫 번째 트랙 ‘I'm Okay(아이엠 오케이)’를 시작으로 ‘오로라(AURORA)’, ‘PALACE(팰러스)’, ‘PARADE(퍼레이드)’까지 총 네 곡이 수록됐다. + - expected: `⠓⠪⠐⠗⠁⠐⠕⠠⠪⠓⠪⠝⠀⠠⠊⠐⠪⠑⠡⠀⠕⠘⠾⠀` + - actual: `⠓⠪⠐⠗⠁⠐⠕⠠⠪⠓⠪⠝⠀⠠⠊⠐⠪⠑⠡⠀⠕⠘⠾⠀` - current primary/reason: `exact` / `exact` - `sentence_04.json` #446: 추경호 부총리 겸 기획재정부 장관은 3일 국제신용평가사 무디스(Moody's) 연례 협의단에 미국 인플레이션 감축법(IRA) 등에 따른 국내 기업의 불확실성이 상당 부분 해소됐다고 강조했다. - expected: `⠰⠍⠈⠻⠚⠥⠀⠘⠍⠰⠿⠐⠕⠀⠈⠱⠢⠀⠈⠕⠚⠽⠁⠨` @@ -1099,130 +1077,103 @@ Representative `exact` samples: Representative `localized_mismatch` samples: -- `sentence_01.json` #15877: 이순재, 선우용여, 이연희의 따스한 안녕을 전할 ‘산책’은 오는 7월 23일(일) 밤 10시 40분 tvN에서 방송되며 tvN X TVING 프로젝트 ‘O'PENing(오프닝) 2023’은 TVING에서도 감상할 수 있다. - - expected: `⠦⠴⠠⠕⠄⠠⠠⠏⠢⠠⠄⠬⠦⠄⠥⠙⠪⠉⠕⠶⠠⠴⠀⠼` - - actual: `⠦⠴⠠⠕⠄⠠⠠⠏⠑⠝⠠⠄⠬⠦⠄⠥⠙⠪⠉⠕⠶⠠⠴⠀` - - first differing cell (zero-based): 141 - - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_03.json` #16886: 윤재찬이 출연하는 ‘썸머, 러브머신 블루스’는 7월 16일 오후 10시 40분 tvN에서 방송되며, tvN X TVING 프로젝트 ‘O'PENing(오프닝) 2023’은 TVING에서도 감상할 수 있다. - - expected: `⠦⠴⠠⠕⠄⠠⠠⠏⠢⠠⠄⠬⠦⠄⠥⠙⠪⠉⠕⠶⠠⠴⠀⠼` - - actual: `⠦⠴⠠⠕⠄⠠⠠⠏⠑⠝⠠⠄⠬⠦⠄⠥⠙⠪⠉⠕⠶⠠⠴⠀` - - first differing cell (zero-based): 129 +- `sentence_01.json` #5043: 국제올림픽위원회(IOC)가 승인하는 세계 최대 규모의 청소년 종합 스포츠대회인 국제청소년스포츠축제(International Children's Games)가 ‘다함께 꿈꾸는 미래’를 주제로 지난 6일 대구에서 막을 올렸다. + - expected: `⠁⠰⠝⠁⠇⠀⠠⠡⠝⠄⠎⠀⠠⠛⠁⠍⠑⠎⠠⠴⠫⠀⠠⠦` + - actual: `⠁⠰⠝⠁⠇⠀⠠⠡⠊⠇⠙⠗⠢⠄⠎⠀⠠⠛⠁⠍⠑⠎⠠⠴` + - first differing cell (zero-based): 117 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_04.json` #2563: 마크 저커버그 메타 최고경영자(CEO)는 이날 “해봅시다(Let's do this). 스레드에 온 걸 환영합니다”라는 글을 스레드 계정에 올리며 출시 소식을 알렸다. - - expected: `⠠⠇⠑⠞⠄⠎⠀⠙⠀⠹⠠⠴⠲⠀⠠⠪⠐⠝⠊⠪⠝⠀⠷⠀` - - actual: `⠠⠇⠑⠞⠄⠎⠀⠙⠕⠀⠹⠊⠎⠠⠴⠲⠀⠠⠪⠐⠝⠊⠪⠝` - - first differing cell (zero-based): 62 +- `sentence_04.json` #6452: 종합 홈 인테리어 전문기업 한샘(대표 김진태)이 온라인몰 자녀방 가구 ‘아임빅(I'M BIG)’과 아동전문 패션브랜드 ‘히로(HIRO)’의 공동 팝업스토어 ‘I'm B!G X HIRO’를 오픈한다고 25일 밝혔다. + - expected: `⠦⠴⠠⠊⠄⠍⠀⠠⠠⠠⠃⠰⠖⠛⠀⠰⠭⠀⠓⠊⠗⠕⠠⠄` + - actual: `⠦⠴⠠⠊⠄⠍⠀⠠⠃⠖⠠⠛⠀⠰⠠⠭⠀⠠⠠⠓⠊⠗⠕⠴` + - first differing cell (zero-based): 160 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` Representative `mismatch` samples: -- `sentence_01.json` #67: LG전자는 오는 5일부터 8일까지 ‘CES 2023’이 열리는 미국 라스베이거스 컨벤션센터(LVCC)에 ‘라이프스굿(Life's Good)’을 소개하는 광고판을 설치한다고 3일 밝혔다. - - expected: `⠊⠋⠑⠄⠎⠀⠠⠛⠙⠠⠴⠴⠄⠮⠀⠠⠥⠈⠗⠚⠉⠵⠀⠈` - - actual: `⠊⠋⠑⠄⠎⠀⠠⠛⠕⠕⠙⠠⠴⠴⠄⠮⠀⠠⠥⠈⠗⠚⠉⠵` - - first differing cell (zero-based): 126 +- `sentence_01.json` #5043: 국제올림픽위원회(IOC)가 승인하는 세계 최대 규모의 청소년 종합 스포츠대회인 국제청소년스포츠축제(International Children's Games)가 ‘다함께 꿈꾸는 미래’를 주제로 지난 6일 대구에서 막을 올렸다. + - expected: `⠁⠰⠝⠁⠇⠀⠠⠡⠝⠄⠎⠀⠠⠛⠁⠍⠑⠎⠠⠴⠫⠀⠠⠦` + - actual: `⠁⠰⠝⠁⠇⠀⠠⠡⠊⠇⠙⠗⠢⠄⠎⠀⠠⠛⠁⠍⠑⠎⠠⠴` + - first differing cell (zero-based): 117 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_02.json` #10821: LG전자는 19일부터 3일간 서울 강남구 코엑스에서 열리는 월드IT쇼(WIS) 2023에서 ‘라이프스 굿(Life's Good)’을 주제로 고객의 삶을 더 행복하게 만드는 혁신적인 제품들을 선보인다고 밝혔다. - - expected: `⠊⠋⠑⠄⠎⠀⠠⠛⠙⠠⠴⠴⠄⠮⠀⠨⠍⠨⠝⠐⠥⠀⠈⠥` - - actual: `⠊⠋⠑⠄⠎⠀⠠⠛⠕⠕⠙⠠⠴⠴⠄⠮⠀⠨⠍⠨⠝⠐⠥⠀` - - first differing cell (zero-based): 114 +- `sentence_02.json` #12390: 이후에도 이 남성이 또다시 말을 걸며 접근하자, 피해 여성은 “노(No), 돈 바더 미(Don't bother me·귀찮게 하지 마세요)”라고 말한다. + - expected: `⠀⠃⠕⠮⠗⠀⠍⠑⠐⠆⠈⠍⠗⠰⠣⠒⠴⠈⠝⠀⠚⠨⠕⠀` + - actual: `⠀⠃⠕⠮⠗⠀⠍⠑⠲⠐⠆⠈⠍⠗⠰⠣⠒⠴⠈⠝⠀⠚⠨⠕` + - first differing cell (zero-based): 89 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_03.json` #13417: 트랙리스트에 따르면 이번 앨범에는 첫 번째 트랙 ‘I'm Okay(아이엠 오케이)’를 시작으로 ‘오로라(AURORA)’, ‘PALACE(팰러스)’, ‘PARADE(퍼레이드)’까지 총 네 곡이 수록됐다. - - expected: `⠠⠴⠴⠄⠐⠀⠠⠦⠴⠠⠠⠏⠁⠇⠁⠉⠑⠦⠄⠙⠗⠂⠐⠎` - - actual: `⠠⠴⠴⠄⠐⠀⠠⠦⠰⠠⠠⠏⠁⠇⠁⠉⠑⠦⠄⠙⠗⠂⠐⠎` - - first differing cell (zero-based): 114 +- `sentence_03.json` #13506: 바이브(VIBE) 류재현 프로듀싱팀 VIP(VIBE IN PLAY)와 프로듀서 Drei, 가수 한동근이 네이버 웹툰 ‘신부가 필요해’의 첫 번째 음원 ‘I'll Be(아이 윌 비)’를 발매하고 차별화된 감성을 선사했다. + - expected: `⠶⠓⠕⠢⠀⠴⠠⠠⠧⠊⠏⠐⠣⠠⠠⠠⠧⠊⠃⠑⠀⠔⠀⠏` + - actual: `⠶⠓⠕⠢⠀⠴⠠⠠⠠⠧⠊⠏⠐⠣⠧⠊⠃⠑⠀⠊⠝⠀⠏⠇` + - first differing cell (zero-based): 40 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_04.json` #2563: 마크 저커버그 메타 최고경영자(CEO)는 이날 “해봅시다(Let's do this). 스레드에 온 걸 환영합니다”라는 글을 스레드 계정에 올리며 출시 소식을 알렸다. - - expected: `⠠⠇⠑⠞⠄⠎⠀⠙⠀⠹⠠⠴⠲⠀⠠⠪⠐⠝⠊⠪⠝⠀⠷⠀` - - actual: `⠠⠇⠑⠞⠄⠎⠀⠙⠕⠀⠹⠊⠎⠠⠴⠲⠀⠠⠪⠐⠝⠊⠪⠝` - - first differing cell (zero-based): 62 +- `sentence_04.json` #6452: 종합 홈 인테리어 전문기업 한샘(대표 김진태)이 온라인몰 자녀방 가구 ‘아임빅(I'M BIG)’과 아동전문 패션브랜드 ‘히로(HIRO)’의 공동 팝업스토어 ‘I'm B!G X HIRO’를 오픈한다고 25일 밝혔다. + - expected: `⠦⠴⠠⠊⠄⠍⠀⠠⠠⠠⠃⠰⠖⠛⠀⠰⠭⠀⠓⠊⠗⠕⠠⠄` + - actual: `⠦⠴⠠⠊⠄⠍⠀⠠⠃⠖⠠⠛⠀⠰⠠⠭⠀⠠⠠⠓⠊⠗⠕⠴` + - first differing cell (zero-based): 160 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` ### `ascii_roman_tail_comma_before_digit_korean_token` -Of the 58 candidates, 53 are the actual `pending_rule_review` subcluster. The other 5 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. - -For this output-signature audit, 55 mismatches were evaluable and 22 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. - -Localized first-difference transitions: +Of the 58 candidates, 20 are the actual `pending_rule_review` subcluster. The other 38 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. -- `U+2810 ⠐ -> U+2802 ⠂`: 18 -- `U+2802 ⠂ -> U+2810 ⠐`: 4 +For this output-signature audit, 24 mismatches were evaluable and 0 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. Mismatch primary-class distribution: -- `corpus_suspect`: 2 -- `pending_rule_review`: 53 +- `corpus_suspect`: 4 +- `pending_rule_review`: 20 Representative `exact` samples: -- `sentence_01.json` #1767: 부산은행도 서민금융 상품 ‘새희망홀씨’ 대출 금리를 1%p 내렸다. 주담대와 전세대출, 신용대출도 최대 0.8%포인트(p), 0.85%p, 0.6%p씩 금리를 내리기로 했다. - - expected: `⠘⠍⠇⠒⠵⠚⠗⠶⠊⠥⠀⠠⠎⠑⠟⠈⠪⠢⠩⠶⠀⠇⠶⠙` - - actual: `⠘⠍⠇⠒⠵⠚⠗⠶⠊⠥⠀⠠⠎⠑⠟⠈⠪⠢⠩⠶⠀⠇⠶⠙` +- `sentence_01.json` #1754: 이 부사장은 인공지능(AI) 챗봇인 챗GPT가 1990년대에 등장한 PC, 2000년대의 인터넷, 2010년대에 출시된 스마트폰 못지 않게 반도체 시장에도 큰 파급력을 가질 것이라고 내다봤다. + - expected: `⠕⠀⠘⠍⠇⠨⠶⠵⠀⠟⠈⠿⠨⠕⠉⠪⠶⠦⠄⠴⠠⠠⠁⠊` + - actual: `⠕⠀⠘⠍⠇⠨⠶⠵⠀⠟⠈⠿⠨⠕⠉⠪⠶⠦⠄⠴⠠⠠⠁⠊` - current primary/reason: `exact` / `exact` -- `sentence_02.json` #19806: 자본환원율(캡레이트·Capitalization Rate)은 오피스가 4.3%, 물류는 5.5%로 지난해 동기보다 각각 0.8%p, 0.6%포인트(p) 상승했다. 자본환원율은 자산의 연간 순영업이익(NOI)을 매입금액으로 나눈 수치로, 상승한 경우에 통상 자산가격 하락을 의미한다. - - expected: `⠨⠘⠷⠚⠧⠒⠏⠒⠩⠂⠦⠄⠋⠗⠃⠐⠝⠕⠓⠪⠐⠆⠴⠠` - - actual: `⠨⠘⠷⠚⠧⠒⠏⠒⠩⠂⠦⠄⠋⠗⠃⠐⠝⠕⠓⠪⠐⠆⠴⠠` +- `sentence_02.json` #239: 세계 금연의 날(World No Tabacco Day, 2023년 5월 31일)은 세계보건기구(WHO)가 담배가 전 세계적으로 심각한 문제임을 인식시키고 ‘담배 연기 없는 사회’를 만들기 위하여 1987년 제정한 기념일이다. + - expected: `⠠⠝⠈⠌⠀⠈⠪⠢⠡⠺⠀⠉⠂⠦⠄⠴⠠⠸⠺⠀⠠⠝⠕⠀` + - actual: `⠠⠝⠈⠌⠀⠈⠪⠢⠡⠺⠀⠉⠂⠦⠄⠴⠠⠸⠺⠀⠠⠝⠕⠀` + - current primary/reason: `exact` / `exact` +- `sentence_03.json` #2147: 용량은 512GB, 1테라바이트(TB), 두 가지 종류로 출시됐다. USB 연결 케이블 2종(C-to-C, C-to-A)과 함께 전용 범퍼케이스가 제공된다. + - expected: `⠬⠶⠐⠜⠶⠵⠀⠼⠑⠁⠃⠴⠠⠠⠛⠃⠐⠀⠼⠁⠀⠓⠝⠐` + - actual: `⠬⠶⠐⠜⠶⠵⠀⠼⠑⠁⠃⠴⠠⠠⠛⠃⠐⠀⠼⠁⠀⠓⠝⠐` - current primary/reason: `exact` / `exact` - -Representative `localized_mismatch` samples: - -- `sentence_01.json` #1754: 이 부사장은 인공지능(AI) 챗봇인 챗GPT가 1990년대에 등장한 PC, 2000년대의 인터넷, 2010년대에 출시된 스마트폰 못지 않게 반도체 시장에도 큰 파급력을 가질 것이라고 내다봤다. - - expected: `⠚⠒⠀⠴⠠⠠⠏⠉⠐⠀⠼⠃⠚⠚⠚⠀⠉⠡⠊⠗⠺⠀⠟⠓` - - actual: `⠚⠒⠀⠴⠠⠠⠏⠉⠂⠀⠼⠃⠚⠚⠚⠀⠉⠡⠊⠗⠺⠀⠟⠓` - - first differing cell (zero-based): 72 - - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_02.json` #431: 173cm, 68kg의 다소 왜소한 체격이지만 스크럼 하프(SH) 포지션을 맡아 포워드와 백스 사이에서 날카로운 볼 연결과 공격방향을 결정하는 타고난 판단력이 강점이다. - - expected: `⠼⠁⠛⠉⠴⠉⠍⠂⠀⠼⠋⠓⠅⠛⠲⠺⠀⠊⠠⠥⠀⠧⠗⠠` - - actual: `⠼⠁⠛⠉⠴⠉⠍⠐⠀⠼⠋⠓⠴⠅⠛⠲⠺⠀⠊⠠⠥⠀⠧⠗` - - first differing cell (zero-based): 7 - - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_03.json` #2225: 이밖에 4위 폭스바겐, 5위 지리, 6위 스텔란티스가 뒤를 이었다. 7위 현대차·기아, 8위 르노·닛산·미쓰비시 얼라이언스, 9위 BMW, 10위 광저우자동차(GAC) 순이었다. - - expected: `⠗⠀⠴⠠⠠⠃⠍⠺⠐⠀⠼⠁⠚⠍⠗⠀⠈⠧⠶⠨⠎⠍⠨⠊` - - actual: `⠗⠀⠴⠠⠠⠃⠍⠺⠂⠀⠼⠁⠚⠍⠗⠀⠈⠧⠶⠨⠎⠍⠨⠊` - - first differing cell (zero-based): 129 - - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` - `sentence_04.json` #4209: 사티아 나델라 MS 최고경영자(CEO)는 이날 “나의 계정에서 코파일럿과 채팅하게 돼 기쁘다”며 “AI 비서와 일하는 것은 1980년대의 PC, 1990년대의 인터넷, 21세기 모바일의 부상만큼이나 주목할 만하다”라고 자평했다. - - expected: `⠗⠺⠀⠴⠠⠠⠏⠉⠐⠀⠼⠁⠊⠊⠚⠀⠉⠡⠊⠗⠺⠀⠟⠓` - - actual: `⠗⠺⠀⠴⠠⠠⠏⠉⠂⠀⠼⠁⠊⠊⠚⠀⠉⠡⠊⠗⠺⠀⠟⠓` - - first differing cell (zero-based): 133 - - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` + - expected: `⠇⠓⠕⠣⠀⠉⠊⠝⠂⠐⠣⠀⠴⠠⠠⠍⠎⠲⠀⠰⠽⠈⠥⠈` + - actual: `⠇⠓⠕⠣⠀⠉⠊⠝⠂⠐⠣⠀⠴⠠⠠⠍⠎⠲⠀⠰⠽⠈⠥⠈` + - current primary/reason: `exact` / `exact` Representative `mismatch` samples: - `sentence_01.json` #322: 삼성전자는 5나노미터(nm, 1nm는 10억 분의 1m) 기반 신규 컨트롤러를 탑재한 PC용 고성능 비휘발성 메모리 익스프레스(NVMe) SSD ‘PM9C1a’를 양산한다고 12일 밝혔다. - - expected: `⠄⠴⠝⠍⠂⠀⠼⠁⠝⠍⠲⠉⠵⠀⠼⠁⠚⠹⠀⠘⠛⠺⠀⠼` - - actual: `⠄⠴⠝⠍⠂⠀⠼⠁⠴⠝⠍⠲⠉⠵⠀⠼⠁⠚⠹⠀⠘⠛⠺⠀` - - first differing cell (zero-based): 29 - - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_02.json` #239: 세계 금연의 날(World No Tabacco Day, 2023년 5월 31일)은 세계보건기구(WHO)가 담배가 전 세계적으로 심각한 문제임을 인식시키고 ‘담배 연기 없는 사회’를 만들기 위하여 1987년 제정한 기념일이다. - - expected: `⠃⠁⠒⠕⠀⠠⠐⠙⠐⠀⠼⠃⠚⠃⠉⠀⠉⠡⠀⠼⠑⠏⠂⠀` - - actual: `⠃⠁⠒⠕⠀⠠⠐⠙⠂⠀⠼⠃⠚⠃⠉⠀⠉⠡⠀⠼⠑⠏⠂⠀` - - first differing cell (zero-based): 35 - - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_03.json` #2147: 용량은 512GB, 1테라바이트(TB), 두 가지 종류로 출시됐다. USB 연결 케이블 2종(C-to-C, C-to-A)과 함께 전용 범퍼케이스가 제공된다. - - expected: `⠨⠿⠦⠄⠴⠠⠉⠤⠞⠕⠤⠰⠠⠉⠂⠀⠰⠠⠉⠤⠞⠕⠤⠠` - - actual: `⠨⠿⠦⠄⠴⠠⠉⠤⠰⠞⠕⠤⠰⠠⠉⠂⠀⠠⠉⠤⠰⠞⠕⠤` - - first differing cell (zero-based): 94 + - expected: `⠠⠪⠙⠪⠐⠝⠠⠪⠴⠐⠣⠠⠠⠝⠧⠍⠠⠄⠑⠐⠜⠀⠠⠠` + - actual: `⠠⠪⠙⠪⠐⠝⠠⠪⠦⠄⠴⠠⠝⠠⠧⠠⠍⠑⠠⠴⠀⠴⠠⠠` + - first differing cell (zero-based): 124 + - current primary/reason: `corpus_suspect` / `rule34_roman_indicator_before_opening_parenthesis` +- `sentence_02.json` #431: 173cm, 68kg의 다소 왜소한 체격이지만 스크럼 하프(SH) 포지션을 맡아 포워드와 백스 사이에서 날카로운 볼 연결과 공격방향을 결정하는 타고난 판단력이 강점이다. + - expected: `⠚⠙⠪⠦⠄⠴⠠⠠⠎⠓⠠⠴⠀⠙⠥⠨⠕⠠⠡⠮⠀⠑⠦⠣` + - actual: `⠚⠙⠪⠦⠄⠴⠠⠠⠩⠠⠴⠀⠙⠥⠨⠕⠠⠡⠮⠀⠑⠦⠣⠀` + - first differing cell (zero-based): 55 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_04.json` #4209: 사티아 나델라 MS 최고경영자(CEO)는 이날 “나의 계정에서 코파일럿과 채팅하게 돼 기쁘다”며 “AI 비서와 일하는 것은 1980년대의 PC, 1990년대의 인터넷, 21세기 모바일의 부상만큼이나 주목할 만하다”라고 자평했다. - - expected: `⠗⠺⠀⠴⠠⠠⠏⠉⠐⠀⠼⠁⠊⠊⠚⠀⠉⠡⠊⠗⠺⠀⠟⠓` - - actual: `⠗⠺⠀⠴⠠⠠⠏⠉⠂⠀⠼⠁⠊⠊⠚⠀⠉⠡⠊⠗⠺⠀⠟⠓` - - first differing cell (zero-based): 133 +- `sentence_03.json` #8811: HD현대중공업(A)과 LS일렉트릭(AA-) 등도 언더발행에 성공했다. HD현대중공업은 회사채 수요예측에서 1년6개월물 -29bp, 2년물 -20bp로 물량을 채웠다. LS일렉트릭의 3년물은 -6bp에 낙찰됐다. + - expected: `⠁⠦⠄⠴⠠⠠⠁⠁⠐⠤⠠⠴⠀⠊⠪⠶⠊⠥⠀⠾⠊⠎⠘⠂` + - actual: `⠁⠦⠄⠴⠠⠠⠁⠁⠲⠤⠠⠴⠀⠊⠪⠶⠊⠥⠀⠾⠊⠎⠘⠂` + - first differing cell (zero-based): 50 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_04.json` #7444: 일반부는 하프(Half), 10km, 5km로 나눠 열리며 양양 웰컴 센터를 출발해 남대천 일출로와 낙산대교를 거쳐 동호해변을 반환점으로 한다. + - expected: `⠘⠍⠉⠵⠀⠚⠙⠪⠴⠐⠣⠠⠓⠁⠇⠋⠐⠜⠂⠀⠼⠁⠚⠅` + - actual: `⠘⠍⠉⠵⠀⠚⠙⠪⠦⠄⠴⠠⠓⠁⠇⠋⠠⠴⠐⠀⠼⠁⠚⠴` + - first differing cell (zero-based): 12 + - current primary/reason: `corpus_suspect` / `rule34_roman_indicator_before_opening_parenthesis` ### `attached_ascii_roman_segments_joined_by_ampersand` -Of the 802 candidates, 166 are the actual `pending_rule_review` subcluster. The other 636 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 802 candidates, 103 are the actual `pending_rule_review` subcluster. The other 699 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. -For this output-signature audit, 194 mismatches were evaluable and 0 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. +For this output-signature audit, 115 mismatches were evaluable and 0 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. Mismatch primary-class distribution: -- `comparison_method`: 15 -- `corpus_suspect`: 11 -- `pending_rule_review`: 166 -- `unsupported_character_review`: 2 +- `corpus_suspect`: 12 +- `pending_rule_review`: 103 Representative `exact` samples: @@ -1245,10 +1196,10 @@ Representative `exact` samples: Representative `mismatch` samples: -- `sentence_01.json` #425: 대다수의 업종이 상승세였으며 디지털(4.53%), 정보기술(IT) 소프트웨어&서비스(S/W&SVC)(3.09%), 소프트웨어(2.09%)가 큰 폭으로 올랐다. 운송·부품(0.12%), 운송(0.18%), 종이·목재(0.26%), 유통(0.39%), 건설(0.50%)은 떨어졌다. - - expected: `⠯⠠⠠⠎⠧⠉⠠⠴⠦⠄⠼⠉⠲⠚⠊⠴⠏⠠⠴⠐⠀⠠⠥⠙` - - actual: `⠯⠠⠠⠎⠧⠉⠠⠴⠐⠣⠼⠉⠲⠚⠊⠴⠏⠐⠜⠐⠀⠠⠥⠙` - - first differing cell (zero-based): 100 +- `sentence_01.json` #1082: AMAT는 경기도에 반도체 장비 연구개발(R&D) 센터를 신설하기 위해 지난해 7월 산업통상자원부, 경기도와 투자의향 양해각서(MOU)를 체결했다. + - expected: `⠠⠎⠦⠄⠴⠠⠠⠍⠕⠥⠠⠴⠐⠮⠀⠰⠝⠈⠳⠚⠗⠌⠊⠲` + - actual: `⠠⠎⠦⠄⠴⠠⠠⠍⠳⠠⠴⠐⠮⠀⠰⠝⠈⠳⠚⠗⠌⠊⠲` + - first differing cell (zero-based): 130 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` - `sentence_02.json` #455: 먼저, 세계 최고의 R&D인프라와 인력을 갖춘 장점을 활용하여 국가첨단반도체 기술센터(ASTC)를 유치하고 대전을 반도체 연구·교육·실증 거점으로 조성할 계획이다. - expected: `⠓⠎⠦⠄⠴⠠⠠⠁⠎⠞⠉⠠⠴⠐⠮⠀⠩⠰⠕⠚⠈⠥⠀⠊` @@ -1261,23 +1212,22 @@ Representative `mismatch` samples: - first differing cell (zero-based): 164 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` - `sentence_04.json` #19: 이날 행사에는 구 대표 외에 권봉석 (주)LG 최고운영책임자(COO), 박일평 LG사이언스파크대표(사장)를 비롯해 각 계열사 최고기술책임자(CTO), 최고데이터책임자(CDO), 최고인사책임자(CHO) 등이 참석해 국내 이공계 R&D 인재 400여명과 만났다. - - expected: `⠁⠕⠢⠨⠦⠄⠴⠠⠠⠉⠕⠕⠠⠴⠐⠀⠘⠁⠕⠂⠙⠻⠀⠴` - - actual: `⠁⠕⠢⠨⠦⠄⠴⠠⠉⠠⠕⠠⠕⠠⠴⠐⠀⠘⠁⠕⠂⠙⠻⠀` - - first differing cell (zero-based): 60 + - expected: `⠁⠕⠢⠨⠦⠄⠴⠠⠠⠉⠓⠕⠠⠴⠀⠊⠪⠶⠕⠀⠰⠣⠢⠠` + - actual: `⠁⠕⠢⠨⠦⠄⠴⠠⠉⠠⠓⠠⠕⠠⠴⠀⠊⠪⠶⠕⠀⠰⠣⠢` + - first differing cell (zero-based): 188 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` ### `attached_ascii_roman_to_korean_script_boundary` -Of the 17693 candidates, 3391 are the actual `pending_rule_review` subcluster. The other 14302 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 17693 candidates, 1719 are the actual `pending_rule_review` subcluster. The other 15974 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. -For this output-signature audit, 4228 mismatches were evaluable and 0 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. +For this output-signature audit, 2487 mismatches were evaluable and 0 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. Mismatch primary-class distribution: -- `comparison_method`: 73 -- `corpus_suspect`: 726 -- `pending_rule_review`: 3391 -- `unsupported_character_review`: 38 +- `corpus_suspect`: 766 +- `pending_rule_review`: 1719 +- `unsupported_character_review`: 2 Representative `exact` samples: @@ -1321,7 +1271,7 @@ Representative `mismatch` samples: - `sentence_01.json` #14: 새롭게 선보인 DRX 브랜딩과 팀 컬러는 2023년 1월 1일부터 DRX의 온오프라인 콘텐츠, 엠디(MD), SNS, 유튜브 등 모든 제작물에 적용될 예정이다. - expected: `⠰⠪⠐⠀⠝⠢⠊⠕⠴⠐⠣⠠⠠⠍⠙⠐⠜⠂⠀⠠⠠⠎⠝⠎` - - actual: `⠰⠪⠐⠀⠝⠢⠊⠕⠦⠄⠴⠠⠠⠍⠙⠠⠴⠐⠀⠰⠠⠠⠎⠝` + - actual: `⠰⠪⠐⠀⠝⠢⠊⠕⠦⠄⠴⠠⠠⠍⠙⠠⠴⠐⠀⠴⠠⠠⠎⠝` - first differing cell (zero-based): 97 - current primary/reason: `corpus_suspect` / `rule34_roman_indicator_before_opening_parenthesis` - `sentence_02.json` #10: 설동호 대전교육감은 “학교 교육과정 및 여건에 맞는 인공지능(AI)교육 선도학교 운영을 통해 AI·SW교육 기반 구축과 학생들과 교원들의 미래 디지털 역량이 함양될 것이다.”라고 말했다. @@ -1335,9 +1285,9 @@ Representative `mismatch` samples: - first differing cell (zero-based): 83 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` - `sentence_04.json` #19: 이날 행사에는 구 대표 외에 권봉석 (주)LG 최고운영책임자(COO), 박일평 LG사이언스파크대표(사장)를 비롯해 각 계열사 최고기술책임자(CTO), 최고데이터책임자(CDO), 최고인사책임자(CHO) 등이 참석해 국내 이공계 R&D 인재 400여명과 만났다. - - expected: `⠁⠕⠢⠨⠦⠄⠴⠠⠠⠉⠕⠕⠠⠴⠐⠀⠘⠁⠕⠂⠙⠻⠀⠴` - - actual: `⠁⠕⠢⠨⠦⠄⠴⠠⠉⠠⠕⠠⠕⠠⠴⠐⠀⠘⠁⠕⠂⠙⠻⠀` - - first differing cell (zero-based): 60 + - expected: `⠁⠕⠢⠨⠦⠄⠴⠠⠠⠉⠓⠕⠠⠴⠀⠊⠪⠶⠕⠀⠰⠣⠢⠠` + - actual: `⠁⠕⠢⠨⠦⠄⠴⠠⠉⠠⠓⠠⠕⠠⠴⠀⠊⠪⠶⠕⠀⠰⠣⠢` + - first differing cell (zero-based): 188 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` Representative `mismatch_rule29_terminator` samples: @@ -1355,26 +1305,18 @@ Representative `mismatch_rule29_terminator` samples: - actual: `⠂⠀⠴⠠⠠⠓⠍⠍⠲⠈⠧⠀⠙⠉⠠⠕⠣⠫⠀⠠⠾⠘⠁⠬` - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` - `sentence_04.json` #19: 이날 행사에는 구 대표 외에 권봉석 (주)LG 최고운영책임자(COO), 박일평 LG사이언스파크대표(사장)를 비롯해 각 계열사 최고기술책임자(CTO), 최고데이터책임자(CDO), 최고인사책임자(CHO) 등이 참석해 국내 이공계 R&D 인재 400여명과 만났다. - - expected: `⠻⠀⠴⠠⠠⠇⠛⠲⠇⠕⠾⠠⠪⠙⠋⠪⠊⠗⠙⠬⠦⠄⠇⠨` + - expected: `⠙⠻⠀⠴⠠⠠⠇⠛⠲⠇⠕⠾⠠⠪⠙⠋⠪⠊⠗⠙⠬⠦⠄⠇` - actual: `⠙⠻⠀⠴⠠⠠⠇⠛⠲⠇⠕⠾⠠⠪⠙⠋⠪⠊⠗⠙⠬⠦⠄⠇` - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -Representative `mismatch_rule39_hangul_opening` samples: - -- `sentence_03.json` #16063: 사업자별 시장 점유율은 KT 24.23%, SK브로드밴드(IPTV) 17.71%, LG유플러스 14.79%, LG헬로비전 10.20%, KT스카이라이프 8.15%, SK브로드밴드 7.81% 순이다. - - expected: `⠴⠂⠀⠠⠠⠎⠅⠲⠘⠪⠐⠥⠊⠪⠘⠗⠒⠊⠪⠦⠄⠴⠠⠠` - - actual: `⠏⠐⠀⠴⠠⠠⠎⠅⠸⠷⠘⠪⠐⠥⠊⠪⠘⠗⠒⠊⠪⠸⠾⠐` - - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` - ### `attached_korean_auxiliary_itda_spacing` -Of the 95 candidates, 19 are the actual `pending_rule_review` subcluster. The other 76 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 95 candidates, 8 are the actual `pending_rule_review` subcluster. The other 87 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. Mismatch primary-class distribution: -- `comparison_method`: 1 - `corpus_suspect`: 3 -- `pending_rule_review`: 19 +- `pending_rule_review`: 8 Representative `exact` samples: @@ -1397,35 +1339,86 @@ Representative `exact` samples: Representative `mismatch` samples: -- `sentence_01.json` #11020: 143엔터테인먼트에는 3인조 걸그룹 라임라잇(수혜, 미유, 가은)과 케플러(Kep1er)로 활동 중인 마시로, 강예서 등이 속해있다. 아이콘의 합류로 본격적인 라인업 확장을 예고했다. - - expected: `⠴⠠⠅⠑⠏⠼⠁⠰⠑⠗⠠⠴⠐⠥⠀⠚⠧⠂⠊⠿⠀⠨⠍⠶` - - actual: `⠴⠠⠅⠑⠏⠼⠁⠰⠻⠠⠴⠐⠥⠀⠚⠧⠂⠊⠿⠀⠨⠍⠶⠟` - - first differing cell (zero-based): 77 +- `sentence_01.json` #13832: 키움은 최근 4연패 부진에 빠져있다. 팀 득점(19)은 최하위를 기록중이고 팀 타율(.228) 9위, 홈런(1) 9위, OPS(.581) 10위 등 각종 타격지표가 모두 하위권에 머무르고 있는 것이 고민이다. + - expected: `⠢⠀⠓⠣⠩⠂⠦⠄⠼⠲⠃⠃⠓⠠⠴⠀⠼⠊⠍⠗⠐⠀⠚⠥` + - actual: `⠢⠀⠓⠣⠩⠂⠦⠄⠲⠼⠃⠃⠓⠠⠴⠀⠼⠊⠍⠗⠐⠀⠚⠥` + - first differing cell (zero-based): 81 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` - `sentence_02.json` #2144: 황새는 세계자연보전연맹 적색자료목록에서 위기(EN)종으로 분류된 국제적 보호종으로 전 세계에서 2천499개체 정도 생존하고 있는 것으로 알려져있다. - expected: `⠗⠈⠕⠦⠄⠴⠠⠠⠑⠝⠠⠴⠨⠿⠪⠐⠥⠀⠘⠛⠐⠩⠊⠽` - actual: `⠗⠈⠕⠦⠄⠴⠠⠠⠢⠠⠴⠨⠿⠪⠐⠥⠀⠘⠛⠐⠩⠊⠽⠒` - first differing cell (zero-based): 49 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_03.json` #13262: 143엔터테인먼트는 최근 가장 주목받는 레이블로 통한다. 3인조 걸그룹 라임라잇(수혜, 미유, 가은)과 케플러(Kep1er)로 활동 중인 마시로, 강예서 등이 속해있다. 아이콘의 합류로 본격적인 라인업 확장을 예고했다. - - expected: `⠴⠠⠅⠑⠏⠼⠁⠰⠑⠗⠠⠴⠐⠥⠀⠚⠧⠂⠊⠿⠀⠨⠍⠶` - - actual: `⠴⠠⠅⠑⠏⠼⠁⠰⠻⠠⠴⠐⠥⠀⠚⠧⠂⠊⠿⠀⠨⠍⠶⠟` - - first differing cell (zero-based): 109 +- `sentence_03.json` #15510: 타이틀곡 ‘세월아’는 수많은 드라마의 OST를 프로듀싱한 작곡가 필승불패W, 지민(JAK), 건치가 의기투합하여 만든 곡으로 인생에 대한 공감 가는 가사와 애절한 멜로디 그리고 세련되면서 신나는 사운드가 담겨있다. + - expected: `⠑⠣⠺⠀⠴⠠⠠⠕⠎⠞⠲⠐⠮⠀⠙⠪⠐⠥⠊⠩⠠⠕⠶⠚` + - actual: `⠑⠣⠺⠀⠴⠠⠠⠕⠌⠲⠐⠮⠀⠙⠪⠐⠥⠊⠩⠠⠕⠶⠚⠒` + - first differing cell (zero-based): 39 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` + +### `attached_korean_to_roman_hyphen_boundary` + +Of the 105 candidates, 17 are the actual `pending_rule_review` subcluster. The other 88 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. + +Mismatch primary-class distribution: + +- `corpus_suspect`: 4 +- `pending_rule_review`: 17 + +Representative `exact` samples: + +- `sentence_01.json` #491: 자원 선순환 달성을 위한 폐플라스틱 재활용도 추진한다. 폐폴리스티렌을 열분해한 재활용스티렌(RSM) 제조 사업과 RSM을 고기능성 합성고무 SSBR에 적용시킨 에코-SSBR을 오는 2025년까지 상용화할 계획이다. + - expected: `⠨⠣⠏⠒⠀⠠⠾⠠⠛⠚⠧⠒⠀⠊⠂⠠⠻⠮⠀⠍⠗⠚⠒⠀` + - actual: `⠨⠣⠏⠒⠀⠠⠾⠠⠛⠚⠧⠒⠀⠊⠂⠠⠻⠮⠀⠍⠗⠚⠒⠀` + - current primary/reason: `exact` / `exact` +- `sentence_02.json` #1147: 올해 상반기 해외사절단을 이끌고 있는 김두겸 울산시장은 13일(현지시각) 아랍에미리트(UAE) 아부다비 중심에 위치한 국영석유회사인 애드낙(ADNOC) 본사에서 ‘울산시-ADNOC, 수소·암모니아산업 공동협력회의’를 개최했다. + - expected: `⠥⠂⠚⠗⠀⠇⠶⠘⠒⠈⠕⠀⠚⠗⠽⠇⠨⠞⠊⠒⠮⠀⠕⠠` + - actual: `⠥⠂⠚⠗⠀⠇⠶⠘⠒⠈⠕⠀⠚⠗⠽⠇⠨⠞⠊⠒⠮⠀⠕⠠` + - current primary/reason: `exact` / `exact` +- `sentence_03.json` #477: 미국의 전략핵잠수함이 운용하는 트라이던트-Ⅱ잠수함발사탄도미사일(SLBM)은 사거리가 1만2000㎞인 전략 핵무기다. 사실상 태평양 어디에서도 북한에 핵 타격을 가할 수 있는 역량을 갖춘 셈이다. + - expected: `⠑⠕⠈⠍⠁⠺⠀⠨⠾⠐⠜⠁⠚⠗⠁⠨⠢⠠⠍⠚⠢⠕⠀⠛` + - actual: `⠑⠕⠈⠍⠁⠺⠀⠨⠾⠐⠜⠁⠚⠗⠁⠨⠢⠠⠍⠚⠢⠕⠀⠛` + - current primary/reason: `exact` / `exact` +- `sentence_04.json` #976: 하쿠토-R 미션1에는 달 표면을 굴러다닐 로봇이 실려 있다. 일본 우주항공연구개발기구(JAXA)와 장난감 기업 토미가 함께 만든 지름 8㎝짜리 공 모양의 초소형 로봇 ‘소라큐’다. + - expected: `⠚⠋⠍⠓⠥⠤⠴⠠⠗⠲⠀⠑⠕⠠⠡⠼⠁⠝⠉⠵⠀⠊⠂⠀` + - actual: `⠚⠋⠍⠓⠥⠤⠴⠠⠗⠲⠀⠑⠕⠠⠡⠼⠁⠝⠉⠵⠀⠊⠂⠀` + - current primary/reason: `exact` / `exact` + +Representative `mismatch` samples: + +- `sentence_01.json` #441: 한국무역협회는 16일 아부다비에서 아랍에미리트(UAE) 연방상공회의소와 ‘한-UAE 경제협력위원회’ 설립을 위한 업무협약(MOU)을 체결했다고 17일 밝혔다. 이날 서명에는 구자열 한국무역협회 회장과 압둘라 마즈로이 UAE연방상의 회장이 참석했다. + - expected: `⠜⠁⠦⠄⠴⠠⠠⠍⠕⠥⠠⠴⠮⠀⠰⠝⠈⠳⠚⠗⠌⠊⠈⠥` + - actual: `⠜⠁⠦⠄⠴⠠⠠⠍⠳⠠⠴⠮⠀⠰⠝⠈⠳⠚⠗⠌⠊⠈⠥⠀` + - first differing cell (zero-based): 128 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_02.json` #8005: 대한무역투자진흥공사(KOTRA·코트라)는 윤석열 대통령의 아랍에미리트(UAE) 국빈 방문을 계기로 16일(현지시간) UAE 수도 아부다비에서 ‘한-UAE 비즈니스 상담회’를 개최했다고 밝혔다. + - expected: `⠴⠠⠠⠅⠕⠞⠗⠁⠐⠆⠋⠥⠓⠪⠐⠣⠠⠴⠉⠵⠀⠩⠒⠠` + - actual: `⠴⠠⠠⠅⠕⠞⠗⠁⠲⠐⠆⠋⠥⠓⠪⠐⠣⠠⠴⠉⠵⠀⠩⠒` + - first differing cell (zero-based): 29 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_03.json` #3173: 매도자는 중견 PEF 운용사 자비스자산운용과 ST리더스프라이빗에쿼티(PE)가 공동으로 설립한 ‘에스티엘자비스2018의1사모투자합자회사’다. 자비스운용-ST리더스PE 컨소시엄은 지난 2018년 소신여객을 인수했는데 5년 만에 MC파트너스에 회사를 넘기게 됐다. + - expected: `⠬⠶⠈⠧⠀⠴⠠⠠⠎⠞⠲⠐⠕⠊⠎⠠⠪⠙⠪⠐⠣⠕⠘⠕` + - actual: `⠬⠶⠈⠧⠀⠴⠠⠠⠌⠲⠐⠕⠊⠎⠠⠪⠙⠪⠐⠣⠕⠘⠕⠄` + - first differing cell (zero-based): 44 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_04.json` #6423: 한국수력원자력(한수원, 사장 황주호)은 한-UAE 포괄적 전략적 에너지 파트너십에 관한 공동선언과 관련해 UAE원자력공사(ENEC)와 ‘넷제로 가속화 전략적 협력 MOU’를 체결했다. + - expected: `⠈⠿⠇⠦⠄⠴⠠⠠⠑⠝⠑⠉⠠⠴⠧⠀⠠⠦⠉⠝⠄⠨⠝⠐` + - actual: `⠈⠿⠇⠦⠄⠴⠠⠠⠢⠑⠉⠠⠴⠧⠀⠠⠦⠉⠝⠄⠨⠝⠐⠥` + - first differing cell (zero-based): 129 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` ### `attached_plus_followed_by_parenthesized_korean_gloss` -Of the 16 candidates, 13 are the actual `pending_rule_review` subcluster. The other 3 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 16 candidates, 12 are the actual `pending_rule_review` subcluster. The other 4 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. -For this output-signature audit, 13 mismatches were evaluable and 10 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. +For this output-signature audit, 12 mismatches were evaluable and 12 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. Localized first-difference transitions: -- `U+2826 ⠦ -> U+2800 ⠀`: 10 +- `U+2826 ⠦ -> U+2800 ⠀`: 12 Mismatch primary-class distribution: -- `pending_rule_review`: 13 +- `pending_rule_review`: 12 Representative `exact` samples: @@ -1437,6 +1430,10 @@ Representative `exact` samples: - expected: `⠛⠻⠈⠧⠨⠻⠵⠀⠠⠦⠊⠥⠨⠾⠴⠄⠀⠙⠪⠐⠥⠈⠪⠐` - actual: `⠛⠻⠈⠧⠨⠻⠵⠀⠠⠦⠊⠥⠨⠾⠴⠄⠀⠙⠪⠐⠥⠈⠪⠐` - current primary/reason: `exact` / `exact` +- `sentence_03.json` #4388: 청룡시리즈어워즈는 2022년 국내 최초로 오리지널 스트리밍 시리즈를 대상으로 열린 시상식이다. 넷플릭스부터 디즈니+(플러스), 애플TV+(플러스), 왓챠, 웨이브, 카카오TV, 쿠팡플레이, 티빙이 제작하거나 투자한 국내 드라마와 예능·교양을 대상으로 한다. + - expected: `⠰⠻⠐⠬⠶⠠⠕⠐⠕⠨⠪⠎⠏⠨⠪⠉⠵⠀⠼⠃⠚⠃⠃⠀` + - actual: `⠰⠻⠐⠬⠶⠠⠕⠐⠕⠨⠪⠎⠏⠨⠪⠉⠵⠀⠼⠃⠚⠃⠃⠀` + - current primary/reason: `exact` / `exact` Representative `localized_mismatch` samples: @@ -1473,11 +1470,11 @@ Representative `mismatch` samples: - actual: `⠠⠦⠊⠥⠨⠾⠀⠢⠀⠦⠄⠙⠮⠐⠎⠠⠪⠠⠴⠴⠄⠀⠙⠪` - first differing cell (zero-based): 53 - current primary/reason: `pending_rule_review` / `number_rule_review` -- `sentence_03.json` #4388: 청룡시리즈어워즈는 2022년 국내 최초로 오리지널 스트리밍 시리즈를 대상으로 열린 시상식이다. 넷플릭스부터 디즈니+(플러스), 애플TV+(플러스), 왓챠, 웨이브, 카카오TV, 쿠팡플레이, 티빙이 제작하거나 투자한 국내 드라마와 예능·교양을 대상으로 한다. - - expected: `⠗⠙⠮⠴⠠⠠⠞⠧⠐⠖⠦⠄⠙⠮⠐⠎⠠⠪⠠⠴⠐⠀⠧⠄` - - actual: `⠗⠙⠮⠴⠠⠠⠞⠧⠲⠀⠢⠀⠦⠄⠙⠮⠐⠎⠠⠪⠠⠴⠐⠀` - - first differing cell (zero-based): 134 - - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_03.json` #12962: 1·2열 온도·모드·풍량을 각각 독립적으로 제어할 수있는 3존+(플러스)공조, 디지털키 2, 실내 지문 인증 시스템, 콘솔 암레스트 수납함 자외선 살균 기능, 콘솔 암레스트 열선 등을 탑재했다. + - expected: `⠵⠀⠼⠉⠨⠷⠀⠢⠦⠄⠙⠮⠐⠎⠠⠪⠠⠴⠈⠿⠨⠥⠐⠀` + - actual: `⠵⠀⠼⠉⠨⠷⠀⠢⠀⠦⠄⠙⠮⠐⠎⠠⠪⠠⠴⠈⠿⠨⠥⠐` + - first differing cell (zero-based): 62 + - current primary/reason: `pending_rule_review` / `number_rule_review` - `sentence_04.json` #6494: LG유플러스(대표 황현식)는 중소 알뜰폰(MVNO) 사업자의 요금제를 판매하는 오프라인 컨설팅 전문매장 ‘알뜰폰+(플러스)’를 전국으로 확대한다고 31일 밝혔다. - expected: `⠂⠠⠊⠮⠙⠷⠀⠢⠦⠄⠙⠮⠐⠎⠠⠪⠠⠴⠴⠄⠐⠮⠀⠨` - actual: `⠂⠠⠊⠮⠙⠷⠀⠢⠀⠦⠄⠙⠮⠐⠎⠠⠪⠠⠴⠴⠄⠐⠮⠀` @@ -1486,20 +1483,18 @@ Representative `mismatch` samples: ### `capitals_word_mode_previously_spanning_nonletter_scope` -Of the 1733 candidates, 821 are the actual `pending_rule_review` subcluster. The other 912 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 1733 candidates, 416 are the actual `pending_rule_review` subcluster. The other 1317 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. Mismatch primary-class distribution: -- `comparison_method`: 15 -- `corpus_suspect`: 38 -- `pending_rule_review`: 821 -- `unsupported_character_review`: 5 +- `corpus_suspect`: 47 +- `pending_rule_review`: 416 Representative `exact` samples: -- `sentence_01.json` #52: 과학기술정보통신부와 개인정보보호위원회의 공동 고시 기준에 따라 한국인터넷진흥원(KISA)이 인증하는 ISMS-P는 고객 정보보호 및 개인정보보호를 위한 일련의 조치와 활동이 국가공인 인증기준에 적합함을 증명하는 제도다. - - expected: `⠈⠧⠚⠁⠈⠕⠠⠯⠨⠻⠘⠥⠓⠿⠠⠟⠘⠍⠧⠀⠈⠗⠟⠨` - - actual: `⠈⠧⠚⠁⠈⠕⠠⠯⠨⠻⠘⠥⠓⠿⠠⠟⠘⠍⠧⠀⠈⠗⠟⠨` +- `sentence_01.json` #17: 지난달 30일(현지시각) 뉴욕증권거래소(NYSE)에서 다우존스30산업평균지수는 전 거래일보다 73.55포인트(0.22%) 하락한 3만3147.25로 마감했다. 대형주 중심의 S&P500지수는 9.78포인트(0.25%) 하락한 3839.50으로, 기술주 중심의 나스닥지수는 11.61포인트(0.11%) 하락한 1만0466.4 + - expected: `⠨⠕⠉⠒⠊⠂⠀⠼⠉⠚⠕⠂⠦⠄⠚⠡⠨⠕⠠⠕⠫⠁⠠⠴` + - actual: `⠨⠕⠉⠒⠊⠂⠀⠼⠉⠚⠕⠂⠦⠄⠚⠡⠨⠕⠠⠕⠫⠁⠠⠴` - current primary/reason: `exact` / `exact` - `sentence_02.json` #23: 군은 14일 군청 소회의실에서 태안군 박경찬 부군수를 비롯해 충청남도·보령시·당진시·서천군 관계자 등 10여 명이 참석한 가운데 ‘화력발전 지역자원시설세 탄력세율 추진 T/F(태스크포스) 회의’를 개최했다고 밝혔다. - expected: `⠈⠛⠵⠀⠼⠁⠙⠕⠂⠀⠈⠛⠰⠻⠀⠠⠥⠚⠽⠺⠠⠕⠂⠝` @@ -1516,47 +1511,46 @@ Representative `exact` samples: Representative `mismatch` samples: -- `sentence_01.json` #17: 지난달 30일(현지시각) 뉴욕증권거래소(NYSE)에서 다우존스30산업평균지수는 전 거래일보다 73.55포인트(0.22%) 하락한 3만3147.25로 마감했다. 대형주 중심의 S&P500지수는 9.78포인트(0.25%) 하락한 3839.50으로, 기술주 중심의 나스닥지수는 11.61포인트(0.11%) 하락한 1만0466.4 - - expected: `⠠⠕⠢⠺⠀⠴⠠⠎⠈⠯⠠⠏⠼⠑⠚⠚⠨⠕⠠⠍⠉⠵⠀⠼` - - actual: `⠠⠕⠢⠺⠀⠴⠠⠎⠲⠴⠈⠯⠲⠴⠠⠏⠼⠑⠚⠚⠨⠕⠠⠍` - - first differing cell (zero-based): 169 +- `sentence_01.json` #90: 조 사장은 “앞으로도 이처럼 ‘더 나은 삶(Better Life)’을 실현하기 위해 최고의(First), 차별화된(Unique), 세상에 없던(New) F·U·N 고객경험을 제공하겠다”고 약속했다. + - expected: `⠈⠥⠺⠦⠄⠴⠠⠋⠌⠠⠴⠐⠀⠰⠣⠘⠳⠚⠧⠊⠽⠒⠦⠄` + - actual: `⠈⠥⠺⠦⠄⠴⠠⠋⠊⠗⠌⠠⠴⠐⠀⠰⠣⠘⠳⠚⠧⠊⠽⠒` + - first differing cell (zero-based): 81 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` - `sentence_02.json` #10: 설동호 대전교육감은 “학교 교육과정 및 여건에 맞는 인공지능(AI)교육 선도학교 운영을 통해 AI·SW교육 기반 구축과 학생들과 교원들의 미래 디지털 역량이 함양될 것이다.”라고 말했다. - expected: `⠚⠗⠀⠴⠠⠠⠁⠊⠐⠆⠴⠠⠠⠎⠺⠲⠈⠬⠩⠁⠀⠈⠕⠘` - actual: `⠚⠗⠀⠴⠠⠠⠁⠊⠲⠐⠆⠴⠠⠠⠎⠺⠲⠈⠬⠩⠁⠀⠈⠕` - first differing cell (zero-based): 93 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_03.json` #365: 김동준은 2010년 ZE:A(제국의아이들)로 데뷔한 이후 드라마 ‘동네변호사 조들호’, ‘빛나라 은수’, ‘블랙’, ‘보좌관’ 시즌 1·2, ‘경우의 수’, 영화 ‘회사원’, ‘간이역’ 등에 출연, 필모그래피를 쌓아왔다. - - expected: `⠴⠄⠀⠠⠕⠨⠵⠀⠼⠁⠐⠆⠼⠃⠐⠀⠠⠦⠈⠻⠍⠺⠀⠠` - - actual: `⠴⠄⠀⠠⠕⠨⠵⠀⠀⠼⠁⠐⠼⠃⠐⠀⠠⠦⠈⠻⠍⠺⠀⠠` - - first differing cell (zero-based): 127 +- `sentence_03.json` #370: 26일 GS칼텍스는 서울 강남구 GS타워에서 이승훈 GS칼텍스 S&T본부장, 박진기 HMM 총괄부사장 등이 참석한 가운데 친환경 바이오선박유 사업 업무협약(MOU)을 체결했다고 밝혔다. + - expected: `⠜⠁⠦⠄⠴⠠⠠⠍⠕⠥⠠⠴⠮⠀⠰⠝⠈⠳⠚⠗⠌⠊⠈⠥` + - actual: `⠜⠁⠦⠄⠴⠠⠠⠍⠳⠠⠴⠮⠀⠰⠝⠈⠳⠚⠗⠌⠊⠈⠥⠀` + - first differing cell (zero-based): 164 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` - `sentence_04.json` #19: 이날 행사에는 구 대표 외에 권봉석 (주)LG 최고운영책임자(COO), 박일평 LG사이언스파크대표(사장)를 비롯해 각 계열사 최고기술책임자(CTO), 최고데이터책임자(CDO), 최고인사책임자(CHO) 등이 참석해 국내 이공계 R&D 인재 400여명과 만났다. - - expected: `⠁⠕⠢⠨⠦⠄⠴⠠⠠⠉⠕⠕⠠⠴⠐⠀⠘⠁⠕⠂⠙⠻⠀⠴` - - actual: `⠁⠕⠢⠨⠦⠄⠴⠠⠉⠠⠕⠠⠕⠠⠴⠐⠀⠘⠁⠕⠂⠙⠻⠀` - - first differing cell (zero-based): 60 + - expected: `⠁⠕⠢⠨⠦⠄⠴⠠⠠⠉⠓⠕⠠⠴⠀⠊⠪⠶⠕⠀⠰⠣⠢⠠` + - actual: `⠁⠕⠢⠨⠦⠄⠴⠠⠉⠠⠓⠠⠕⠠⠴⠀⠊⠪⠶⠕⠀⠰⠣⠢` + - first differing cell (zero-based): 188 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` ### `closed_roman_parenthetical_after_non_ascii_letter_boundary` -Of the 63959 candidates, 9343 are the actual `pending_rule_review` subcluster. The other 54616 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 63959 candidates, 5599 are the actual `pending_rule_review` subcluster. The other 58360 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. -For this output-signature audit, 10837 mismatches were evaluable and 1130 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. +For this output-signature audit, 6784 mismatches were evaluable and 418 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. Localized first-difference transitions: -- `U+2826 ⠦ -> U+2834 ⠴`: 438 -- `U+2826 ⠦ -> U+2800 ⠀`: 330 -- `U+2820 ⠠ -> U+2809 ⠉`: 104 -- `U+2834 ⠴ -> U+2826 ⠦`: 28 -- `U+2826 ⠦ -> U+2810 ⠐`: 22 +- `U+2826 ⠦ -> U+2834 ⠴`: 114 +- `U+2834 ⠴ -> U+2826 ⠦`: 36 +- `U+2826 ⠦ -> U+2800 ⠀`: 29 +- `U+2820 ⠠ -> U+280E ⠎`: 17 +- `U+2810 ⠐ -> U+2834 ⠴`: 14 Mismatch primary-class distribution: -- `comparison_method`: 242 -- `corpus_suspect`: 1114 -- `pending_rule_review`: 9343 -- `unsupported_character_review`: 138 +- `corpus_suspect`: 1181 +- `pending_rule_review`: 5599 +- `unsupported_character_review`: 4 Representative `exact` samples: @@ -1568,9 +1562,9 @@ Representative `exact` samples: - expected: `⠠⠝⠨⠿⠠⠕⠀⠨⠍⠰⠽⠐⠀⠠⠝⠨⠿⠠⠕⠓⠒⠠⠥⠨` - actual: `⠠⠝⠨⠿⠠⠕⠀⠨⠍⠰⠽⠐⠀⠠⠝⠨⠿⠠⠕⠓⠒⠠⠥⠨` - current primary/reason: `exact` / `exact` -- `sentence_03.json` #4: 18일(현지시간) 월스트리트저널(WSJ) 등 미 외신은 투·개표기 업체 도미니언 투표시스템이 2021년 1월 제기한 16억 달러(약 2조원) 명예훼손 소송과 관련, 폭스뉴스와 도미니언 측이 이 같이 합의했다고 보도했다. - - expected: `⠼⠁⠓⠕⠂⠦⠄⠚⠡⠨⠕⠠⠕⠫⠒⠠⠴⠀⠏⠂⠠⠪⠓⠪` - - actual: `⠼⠁⠓⠕⠂⠦⠄⠚⠡⠨⠕⠠⠕⠫⠒⠠⠴⠀⠏⠂⠠⠪⠓⠪` +- `sentence_03.json` #2: 연구소는 전남대학교, 순천대학교, 전남도립대학교, 동신대학교, 한국에너지공과대학교, 충남대학교, 부산대학교 등 드론, 빅데이터, 인공지능(AI) 관련 대학 교수와 전남테크노파크, ㈜엘시스 등 전문가로 기획연구팀을 구성한다. + - expected: `⠡⠈⠍⠠⠥⠉⠵⠀⠨⠾⠉⠢⠊⠗⠚⠁⠈⠬⠐⠀⠠⠛⠰⠾` + - actual: `⠡⠈⠍⠠⠥⠉⠵⠀⠨⠾⠉⠢⠊⠗⠚⠁⠈⠬⠐⠀⠠⠛⠰⠾` - current primary/reason: `exact` / `exact` - `sentence_04.json` #1: 일본 정부는 이날 국가안전보장회의(NSC)를 열어 대응 방침을 논의했다. 이어 북한의 미사일 발사에 대해 “엄중히 항의하고 강하게 비난한다”고 밝혔다. - expected: `⠕⠂⠘⠷⠀⠨⠻⠘⠍⠉⠵⠀⠕⠉⠂⠀⠈⠍⠁⠫⠣⠒⠨⠾` @@ -1579,32 +1573,32 @@ Representative `exact` samples: Representative `localized_mismatch` samples: -- `sentence_01.json` #38: 올해 소프트웨어(SW) 업계는 국내 인공지능(AI) 제품·서비스 고도화와 함께 전 산업영역에서 SW영역 확대가 가시화될 것으로 보인다. 또한 블록체인이 웹3(Web3)와 연계해 탈중앙화·탈독점화 경제체계로 진화할 것으로 예상된다. - - expected: `⠟⠕⠀⠏⠗⠃⠼⠉⠦⠄⠴⠠⠺⠑⠃⠼⠉⠠⠴⠧⠀⠡⠈⠌` - - actual: `⠟⠕⠀⠏⠗⠃⠼⠉⠴⠐⠣⠠⠺⠑⠃⠼⠉⠴⠐⠜⠲⠧⠀⠡` - - first differing cell (zero-based): 155 +- `sentence_01.json` #39: 소프트웨어정책연구소(SPRi)는 ‘2023년 SW산업 10대 이슈 전망’을 통해 올해 가장 주요한 이슈로 인공지능 기반 모델 고도화를 1위로 선정했다. + - expected: `⠈⠍⠠⠥⠦⠄⠴⠠⠠⠎⠏⠠⠗⠊⠠⠴⠉⠵⠀⠠⠦⠼⠃⠚` + - actual: `⠈⠍⠠⠥⠦⠄⠴⠠⠎⠠⠏⠠⠗⠊⠠⠴⠉⠵⠀⠠⠦⠼⠃⠚` + - first differing cell (zero-based): 23 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` - `sentence_02.json` #15: 충남도 농정의 의사결정 과정에 민간의 주도적 참여를 이끌고 현장 의견을 반영하기 위한 민관 농정협의체 ‘충남 쎈(SSEn)농위원회’가 본격 출범했다. - expected: `⠠⠠⠝⠒⠦⠄⠴⠠⠠⠎⠎⠠⠢⠠⠴⠉⠿⠍⠗⠏⠒⠚⠽⠴` - actual: `⠠⠠⠝⠒⠦⠄⠴⠠⠎⠠⠎⠠⠑⠝⠠⠴⠉⠿⠍⠗⠏⠒⠚⠽` - first differing cell (zero-based): 109 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_03.json` #64: SK에코플랜트는 20일 종로구 수송동 본사에서 현대엔지니어링, USNC와 ‘수소 마이크로 허브(H2 Micro Hub)’ 구축을 위한 3자간 업무협약(MOU)을 체결했다고 밝혔다. - - expected: `⠪⠐⠥⠀⠚⠎⠘⠪⠦⠄⠴⠠⠓⠼⠃⠀⠠⠍⠊⠉⠗⠕⠀⠠` - - actual: `⠪⠐⠥⠀⠚⠎⠘⠪⠀⠀⠦⠠⠓⠼⠃⠀⠴⠠⠍⠊⠉⠗⠕⠀` +- `sentence_03.json` #286: 캘리포니아 레드우드 시티의 재무고문인 로렌스 폰은 “텍사스 ETF의 이름을 주를 상징하는 ‘론스타(Lone Star) ETF’ 또는 ‘리멤버 알라모(Alamo) ETF’로 명명하는 것도 괜찮을 것”이라고 말했다. + - expected: `⠀⠠⠦⠐⠷⠠⠪⠓⠴⠐⠣⠠⠇⠐⠕⠀⠠⠌⠜⠐⠜⠀⠠⠠` + - actual: `⠀⠠⠦⠐⠷⠠⠪⠓⠦⠄⠴⠠⠇⠐⠕⠀⠠⠌⠜⠠⠴⠀⠴⠠` - first differing cell (zero-based): 91 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` - `sentence_04.json` #19: 이날 행사에는 구 대표 외에 권봉석 (주)LG 최고운영책임자(COO), 박일평 LG사이언스파크대표(사장)를 비롯해 각 계열사 최고기술책임자(CTO), 최고데이터책임자(CDO), 최고인사책임자(CHO) 등이 참석해 국내 이공계 R&D 인재 400여명과 만났다. - - expected: `⠁⠕⠢⠨⠦⠄⠴⠠⠠⠉⠕⠕⠠⠴⠐⠀⠘⠁⠕⠂⠙⠻⠀⠴` - - actual: `⠁⠕⠢⠨⠦⠄⠴⠠⠉⠠⠕⠠⠕⠠⠴⠐⠀⠘⠁⠕⠂⠙⠻⠀` - - first differing cell (zero-based): 60 + - expected: `⠁⠕⠢⠨⠦⠄⠴⠠⠠⠉⠓⠕⠠⠴⠀⠊⠪⠶⠕⠀⠰⠣⠢⠠` + - actual: `⠁⠕⠢⠨⠦⠄⠴⠠⠉⠠⠓⠠⠕⠠⠴⠀⠊⠪⠶⠕⠀⠰⠣⠢` + - first differing cell (zero-based): 188 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` Representative `mismatch` samples: - `sentence_01.json` #14: 새롭게 선보인 DRX 브랜딩과 팀 컬러는 2023년 1월 1일부터 DRX의 온오프라인 콘텐츠, 엠디(MD), SNS, 유튜브 등 모든 제작물에 적용될 예정이다. - expected: `⠰⠪⠐⠀⠝⠢⠊⠕⠴⠐⠣⠠⠠⠍⠙⠐⠜⠂⠀⠠⠠⠎⠝⠎` - - actual: `⠰⠪⠐⠀⠝⠢⠊⠕⠦⠄⠴⠠⠠⠍⠙⠠⠴⠐⠀⠰⠠⠠⠎⠝` + - actual: `⠰⠪⠐⠀⠝⠢⠊⠕⠦⠄⠴⠠⠠⠍⠙⠠⠴⠐⠀⠴⠠⠠⠎⠝` - first differing cell (zero-based): 97 - current primary/reason: `corpus_suspect` / `rule34_roman_indicator_before_opening_parenthesis` - `sentence_02.json` #10: 설동호 대전교육감은 “학교 교육과정 및 여건에 맞는 인공지능(AI)교육 선도학교 운영을 통해 AI·SW교육 기반 구축과 학생들과 교원들의 미래 디지털 역량이 함양될 것이다.”라고 말했다. @@ -1618,45 +1612,43 @@ Representative `mismatch` samples: - first differing cell (zero-based): 175 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` - `sentence_04.json` #19: 이날 행사에는 구 대표 외에 권봉석 (주)LG 최고운영책임자(COO), 박일평 LG사이언스파크대표(사장)를 비롯해 각 계열사 최고기술책임자(CTO), 최고데이터책임자(CDO), 최고인사책임자(CHO) 등이 참석해 국내 이공계 R&D 인재 400여명과 만났다. - - expected: `⠁⠕⠢⠨⠦⠄⠴⠠⠠⠉⠕⠕⠠⠴⠐⠀⠘⠁⠕⠂⠙⠻⠀⠴` - - actual: `⠁⠕⠢⠨⠦⠄⠴⠠⠉⠠⠕⠠⠕⠠⠴⠐⠀⠘⠁⠕⠂⠙⠻⠀` - - first differing cell (zero-based): 60 + - expected: `⠁⠕⠢⠨⠦⠄⠴⠠⠠⠉⠓⠕⠠⠴⠀⠊⠪⠶⠕⠀⠰⠣⠢⠠` + - actual: `⠁⠕⠢⠨⠦⠄⠴⠠⠉⠠⠓⠠⠕⠠⠴⠀⠊⠪⠶⠕⠀⠰⠣⠢` + - first differing cell (zero-based): 188 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` ### `compact_numeric_ascii_letter_suffix` -Of the 2975 candidates, 985 are the actual `pending_rule_review` subcluster. The other 1990 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 2975 candidates, 423 are the actual `pending_rule_review` subcluster. The other 2552 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. -For this output-signature audit, 1058 mismatches were evaluable and 256 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. +For this output-signature audit, 483 mismatches were evaluable and 41 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. Localized first-difference transitions: -- `U+283C ⠼ -> U+2800 ⠀`: 192 -- `U+2800 ⠀ -> U+2832 ⠲`: 27 -- `U+2810 ⠐ -> U+2832 ⠲`: 11 -- `U+281E ⠞ -> U+2810 ⠐`: 8 -- `U+2820 ⠠ -> U+2834 ⠴`: 6 +- `U+2810 ⠐ -> U+2832 ⠲`: 20 +- `U+2820 ⠠ -> U+2834 ⠴`: 3 +- `U+2800 ⠀ -> U+2832 ⠲`: 2 +- `U+2808 ⠈ -> U+2832 ⠲`: 2 +- `U+2811 ⠑ -> U+283B ⠻`: 2 Mismatch primary-class distribution: -- `comparison_method`: 12 -- `corpus_suspect`: 54 -- `pending_rule_review`: 985 -- `unsupported_character_review`: 7 +- `corpus_suspect`: 60 +- `pending_rule_review`: 423 Representative `exact` samples: -- `sentence_01.json` #343: 확정안을 보면 정부는 2036년 최대전력 수요(목표 수요)를 118.0GW(기가와트)로 예측했다. 1GW는 보통 원전 1기의 발전량으로 보고 있다. - - expected: `⠚⠧⠁⠨⠻⠣⠒⠮⠀⠘⠥⠑⠡⠀⠨⠻⠘⠍⠉⠵⠀⠼⠃⠚` - - actual: `⠚⠧⠁⠨⠻⠣⠒⠮⠀⠘⠥⠑⠡⠀⠨⠻⠘⠍⠉⠵⠀⠼⠃⠚` +- `sentence_01.json` #340: 한국 국채(외국환평형기금채 5년물 기준)의 신용부도스와프(CDS) 프리미엄은 12월 월평균 53bp로 나타났다. 지난 10월(61bp) 이후 하락 추세다. + - expected: `⠚⠒⠈⠍⠁⠀⠈⠍⠁⠰⠗⠦⠄⠽⠈⠍⠁⠚⠧⠒⠙⠻⠚⠻` + - actual: `⠚⠒⠈⠍⠁⠀⠈⠍⠁⠰⠗⠦⠄⠽⠈⠍⠁⠚⠧⠒⠙⠻⠚⠻` - current primary/reason: `exact` / `exact` - `sentence_02.json` #29: 어획량이 감소하면서 지난해 12월 기준 1상자(20kg)당 위판가가 24만 원까지 치솟으면서 자원 증강 필요성이 끊임없이 제기돼 왔다. - expected: `⠎⠚⠽⠁⠐⠜⠶⠕⠀⠫⠢⠠⠥⠚⠑⠡⠠⠎⠀⠨⠕⠉⠒⠚` - actual: `⠎⠚⠽⠁⠐⠜⠶⠕⠀⠫⠢⠠⠥⠚⠑⠡⠠⠎⠀⠨⠕⠉⠒⠚` - current primary/reason: `exact` / `exact` -- `sentence_03.json` #60: B씨(30) 등 2명은 지난 3월 27일 김해공항으로 필로폰 692g을 속옷 등에 은닉해 밀반입한 혐의를 받는다. - - expected: `⠴⠠⠃⠲⠠⠠⠕⠦⠄⠼⠉⠚⠠⠴⠀⠊⠪⠶⠀⠼⠃⠀⠑⠻` - - actual: `⠴⠠⠃⠲⠠⠠⠕⠦⠄⠼⠉⠚⠠⠴⠀⠊⠪⠶⠀⠼⠃⠀⠑⠻` +- `sentence_03.json` #59: 이들 중 A씨(31) 등 2명은 지난 3월 25일 김해공항으로 필로폰 968g 등을 팬티 속에 숨겨 입국한 혐의를 받고 있다. + - expected: `⠕⠊⠮⠀⠨⠍⠶⠀⠴⠠⠁⠲⠠⠠⠕⠦⠄⠼⠉⠁⠠⠴⠀⠊` + - actual: `⠕⠊⠮⠀⠨⠍⠶⠀⠴⠠⠁⠲⠠⠠⠕⠦⠄⠼⠉⠁⠠⠴⠀⠊` - current primary/reason: `exact` / `exact` - `sentence_04.json` #57: 미국지질조사국(USGS)에 따르면 이날 에콰도르 항구도시 과야킬에서 남쪽으로 약 80km 떨어진 지점에서 규모 6.8의 지진이 발생했다. 지진의 깊이는 66㎞다. - expected: `⠑⠕⠈⠍⠁⠨⠕⠨⠕⠂⠨⠥⠇⠈⠍⠁⠦⠄⠴⠠⠠⠥⠎⠛` @@ -1665,43 +1657,43 @@ Representative `exact` samples: Representative `localized_mismatch` samples: -- `sentence_01.json` #168: 제주항공은 국제항공운송협회(IATA)가 주관하는 국제 항공운송 표준 감사 제도 ‘IOSA(IATA Operation Safety Audit) ISM 14th Edition’ 인증을 마쳐 세계 기준의 안전 관리시스템을 입증받았다고 6일 밝혔다. - - expected: `⠠⠊⠎⠍⠀⠼⠁⠙⠞⠓⠀⠠⠫⠊⠰⠝⠴⠄⠀⠟⠨⠪⠶⠮` - - actual: `⠠⠊⠎⠍⠀⠼⠁⠙⠐⠞⠓⠀⠠⠫⠊⠰⠝⠴⠄⠀⠟⠨⠪⠶` - - first differing cell (zero-based): 129 +- `sentence_01.json` #711: 램(RAM)과 저장용량은 전작과 동일할 것으로 예상된다. 기본 모델과 프로 모델은 8GB(기가바이트) 램에 256GB 내장메모리, 울트라는 12GB 램에 256GB·512GB·1TB 내장메모리가 유력하다. + - expected: `⠃⠑⠋⠴⠠⠠⠛⠃⠐⠆⠼⠑⠁⠃⠴⠠⠠⠛⠃⠐⠆⠼⠁⠴` + - actual: `⠃⠑⠋⠴⠠⠠⠛⠃⠲⠐⠆⠼⠑⠁⠃⠴⠠⠠⠛⠃⠲⠐⠆⠼` + - first differing cell (zero-based): 161 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` - `sentence_02.json` #445: 박람회에서는 다양한 분야의 디지털 교육 프로그램을 한자리에서 체험할 수 있도록 인공지능(AI) 코스웨어·학습플랫폼, 인공지능(AI) 교과교육, 인공지능(AI) 학습지원, 3D·가상현실(VR)·메타버스 교육, 소프트웨어(SW)·코딩·로봇 교육 등 체험 공간을 운영할 예정이다. - expected: `⠒⠐⠀⠼⠉⠴⠠⠙⠐⠆⠫⠇⠶⠚⠡⠠⠕⠂⠦⠄⠴⠠⠠⠧` - actual: `⠒⠐⠀⠼⠉⠴⠠⠙⠲⠐⠆⠫⠇⠶⠚⠡⠠⠕⠂⠦⠄⠴⠠⠠` - first differing cell (zero-based): 179 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_03.json` #59: 이들 중 A씨(31) 등 2명은 지난 3월 25일 김해공항으로 필로폰 968g 등을 팬티 속에 숨겨 입국한 혐의를 받고 있다. - - expected: `⠙⠕⠂⠐⠥⠙⠷⠀⠼⠊⠋⠓⠴⠛⠲⠀⠊⠪⠶⠮⠀⠙⠗⠒` - - actual: `⠙⠕⠂⠐⠥⠙⠷⠀⠀⠼⠊⠋⠓⠛⠀⠀⠊⠪⠶⠮⠀⠙⠗⠒` - - first differing cell (zero-based): 71 +- `sentence_03.json` #3023: 이마트24가 판매하는 ‘더빅삼각김밥’은 일반 삼각김밥(100g~110g)에 비해 중량을 약 50% 늘렸다. 밥 한 공기(200g)와 비슷한 양을 1500~2000원에 먹을 수 있다는 게 이마트24의 설명이다. + - expected: `⠦⠄⠼⠁⠚⠚⠴⠛⠈⠔⠼⠁⠁⠚⠰⠛⠠⠴⠝⠀⠘⠕⠚⠗` + - actual: `⠦⠄⠼⠁⠚⠚⠴⠛⠲⠈⠔⠼⠁⠁⠚⠴⠛⠠⠴⠝⠀⠘⠕⠚` + - first differing cell (zero-based): 59 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_04.json` #538: 중국 전기차 제조업체 비야디(BYD)가 6일 1t 전기트럭 ‘T4K(티포케이)’을 국내에 출시했다. BYD는 전 세계 1위 전기차 업체다. - - expected: `⠴⠫⠀⠼⠋⠕⠂⠀⠼⠁⠴⠞⠲⠀⠨⠾⠈⠕⠓⠪⠐⠹⠀⠠` - - actual: `⠴⠫⠀⠼⠋⠕⠂⠀⠀⠼⠁⠞⠀⠀⠨⠾⠈⠕⠓⠪⠐⠹⠀⠠` - - first differing cell (zero-based): 45 +- `sentence_04.json` #2127: 또한, 새로운 표준 파워트레인인 B5 엔진은 가솔린 기반의 마일드 하이브리드 엔진이다. 최고출력 250마력(5700rpm), 최대토크 35.7kg·m(1800~4800rpm)의 성능을 갖췄다. + - expected: `⠼⠉⠑⠲⠛⠴⠅⠛⠐⠆⠴⠍⠐⠣⠼⠁⠓⠚⠚⠈⠔⠼⠙⠓` + - actual: `⠼⠉⠑⠲⠛⠴⠅⠛⠲⠐⠆⠴⠍⠐⠣⠼⠁⠓⠚⠚⠈⠔⠼⠙` + - first differing cell (zero-based): 129 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` Representative `mismatch` samples: - `sentence_01.json` #89: “인공지능(AI), 6G 등 핵심 기술을 위한 투자도 늘리는 동시에 전기차 충전, 디지털 헬스, 웹OS 기반의 콘텐츠 서비스 등 많은 영역으로 사업 포트폴리오를 확장하고 있습니다.” - expected: `⠟⠈⠿⠨⠕⠉⠪⠶⠴⠐⠣⠠⠠⠁⠊⠐⠜⠂⠀⠼⠋⠠⠛⠲` - - actual: `⠟⠈⠿⠨⠕⠉⠪⠶⠦⠄⠴⠠⠠⠁⠊⠠⠴⠐⠀⠼⠋⠰⠠⠛` + - actual: `⠟⠈⠿⠨⠕⠉⠪⠶⠦⠄⠴⠠⠠⠁⠊⠠⠴⠐⠀⠼⠋⠴⠠⠛` - first differing cell (zero-based): 9 - current primary/reason: `corpus_suspect` / `rule34_roman_indicator_before_opening_parenthesis` - `sentence_02.json` #61: 사업 대상은 주차단위구획 50개 이상의 충전기 의무설치 대상 시설인 공동주택과 공중이용시설로 완속충전기(7 ~ 11kw 미만) 약 83기, 콘센트형(3kw) 약 285기를 지원한다. - expected: `⠨⠾⠈⠕⠦⠄⠼⠛⠈⠔⠼⠁⠁⠴⠅⠺⠲⠀⠑⠕⠑⠒⠠⠴` - - actual: `⠨⠾⠈⠕⠦⠄⠼⠛⠀⠈⠔⠀⠼⠁⠁⠐⠅⠺⠀⠑⠕⠑⠒⠠` + - actual: `⠨⠾⠈⠕⠦⠄⠼⠛⠀⠈⠔⠀⠼⠁⠁⠴⠅⠺⠲⠀⠑⠕⠑⠒` - first differing cell (zero-based): 104 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_03.json` #59: 이들 중 A씨(31) 등 2명은 지난 3월 25일 김해공항으로 필로폰 968g 등을 팬티 속에 숨겨 입국한 혐의를 받고 있다. - - expected: `⠙⠕⠂⠐⠥⠙⠷⠀⠼⠊⠋⠓⠴⠛⠲⠀⠊⠪⠶⠮⠀⠙⠗⠒` - - actual: `⠙⠕⠂⠐⠥⠙⠷⠀⠀⠼⠊⠋⠓⠛⠀⠀⠊⠪⠶⠮⠀⠙⠗⠒` - - first differing cell (zero-based): 71 +- `sentence_03.json` #341: 미래에셋자산운용은 미국 대표지수에 환헤지형으로 투자하는 ‘TIGER 미국S&P500TR(H) 상장지수펀드(ETF)’와 ‘TIGER 미국나스닥100TR(H) ETF’ 순자산 합계가 1000억원을 돌파했다고 26일 밝혔다. + - expected: `⠈⠍⠁⠉⠠⠪⠊⠁⠼⠁⠚⠚⠴⠠⠠⠞⠗⠐⠣⠠⠓⠐⠜⠀` + - actual: `⠈⠍⠁⠉⠠⠪⠊⠁⠀⠀⠼⠁⠚⠚⠠⠠⠞⠗⠦⠠⠓⠴⠀⠴` + - first differing cell (zero-based): 138 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` - `sentence_04.json` #115: ‘Busan is Good(부산이라 좋다)’이라는 새로운 도시 표어의 조형물을 공개하고, 3차원(3D)으로 표현한 도시상징 표지(CI) 영상을 상영한다. - expected: `⠁⠝⠀⠊⠎⠀⠠⠛⠙⠦⠄⠘⠍⠇⠒⠕⠐⠣⠀⠨⠥⠴⠊⠠` @@ -1711,20 +1703,19 @@ Representative `mismatch` samples: ### `consecutive_ascii_roman_words_whitespace_boundary` -Of the 4679 candidates, 2183 are the actual `pending_rule_review` subcluster. The other 2496 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 4679 candidates, 1251 are the actual `pending_rule_review` subcluster. The other 3428 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. -For this output-signature audit, 2321 mismatches were evaluable and 25 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. +For this output-signature audit, 1365 mismatches were evaluable and 5 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. Localized first-difference transitions: -- `U+2800 ⠀ -> U+2832 ⠲`: 25 +- `U+2800 ⠀ -> U+2832 ⠲`: 4 +- `U+2815 ⠕ -> U+2800 ⠀`: 1 Mismatch primary-class distribution: -- `comparison_method`: 24 -- `corpus_suspect`: 92 -- `pending_rule_review`: 2183 -- `unsupported_character_review`: 22 +- `corpus_suspect`: 114 +- `pending_rule_review`: 1251 Representative `exact` samples: @@ -1747,35 +1738,29 @@ Representative `exact` samples: Representative `localized_mismatch` samples: -- `sentence_01.json` #2209: 시노펙스 고분자전해질막(SYNO PEM-1) 모듈은 수소차용 미국에너지부(USDOE) 기준 가혹조건에서 500시간 이상 성능을 유지해야 한다는 기준의 2배가 넘는 1천시간 이상 성능을 유지하는 고성능 제품이다. - - expected: `⠄⠴⠠⠠⠎⠽⠝⠕⠀⠠⠠⠏⠑⠍⠤⠼⠁⠠⠴⠀⠑⠥⠊⠩` - - actual: `⠄⠴⠠⠠⠎⠽⠝⠕⠲⠀⠀⠠⠠⠏⠑⠍⠔⠼⠁⠴⠀⠀⠑⠥` - - first differing cell (zero-based): 33 - - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_02.json` #846: KT&G(사장 백복인)가 ‘글로벌 톱 티어(Global Top-tier) 도약’과 전자담배 생산혁신 거점 구축을 위해 신탄진 NGP(전자담배) 공장을 확장했다. - - expected: `⠴⠠⠛⠇⠕⠃⠁⠇⠀⠠⠞⠕⠏⠤⠞⠊⠻⠠⠴⠀⠊⠥⠜⠁` - - actual: `⠴⠠⠛⠇⠕⠃⠁⠇⠲⠀⠀⠠⠞⠕⠏⠔⠞⠊⠑⠗⠴⠀⠊⠥` - - first differing cell (zero-based): 51 +- `sentence_02.json` #13516: 특히 어린이들이 직접 체험할 수 있는 DIY(Do It Yourself) 제품을 선보일 예정이다. 핑크퐁뿐만 아니라 다양한 캐릭터 지식재산권(IP) 콜라보 제품으로 포트폴리오를 확장할 계획이다. + - expected: `⠠⠙⠊⠽⠐⠣⠠⠙⠕⠀⠠⠭⠀⠠⠽⠗⠋⠐⠜⠲⠀⠨⠝⠙` + - actual: `⠠⠙⠊⠽⠐⠣⠠⠙⠀⠠⠭⠀⠠⠽⠗⠋⠐⠜⠲⠀⠨⠝⠙⠍` + - first differing cell (zero-based): 47 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_03.json` #3481: 제로베이스원 데뷔쇼는 10일 오후 8시 Mnet을 통해 방송되며, 유튜브 Mnet K-POP 채널과 일본 ABEMA(아베마), Mnet Japan을 통해서도 만날 수 있다. - - expected: `⠪⠀⠴⠠⠍⠝⠑⠞⠀⠰⠠⠅⠤⠠⠠⠏⠕⠏⠲⠀⠰⠗⠉⠞` - - actual: `⠪⠀⠴⠠⠍⠝⠑⠞⠲⠀⠠⠅⠔⠠⠠⠏⠕⠏⠀⠀⠰⠗⠉⠞` - - first differing cell (zero-based): 73 +- `sentence_03.json` #13446: 방탄소년단, 엔하이픈, 아이즈원, 엑소 백현 등 인기 아이돌 앨범 작업에참여한 danke(당케, lalala studio), 케이지(KZ)뿐만 아니라, 알앤비 싱어송라이터비오(B.O.) 등 쟁쟁한 프로듀서진이 참여해 완성도를 높였다. + - expected: `⠀⠴⠇⠁⠇⠁⠇⠁⠀⠌⠥⠙⠊⠕⠠⠴⠐⠀⠋⠝⠕⠨⠕⠦` + - actual: `⠀⠴⠇⠁⠇⠁⠇⠁⠲⠀⠎⠞⠥⠙⠊⠕⠴⠐⠀⠋⠝⠕⠨⠕` + - first differing cell (zero-based): 93 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` Representative `mismatch` samples: - `sentence_01.json` #35: 삼성전자는 올해 CES에서 77인치 OLED TV를 처음으로 공개할 예정이다. 이는 지난 8월 ‘국제정보디스플레이학술대회(IMID) 2022’에서 삼성디스플레이가 공개한 77인치 퀀텀닷(QD)-OLED 패널을 탑재한 제품이다. - expected: `⠋⠏⠒⠓⠎⠢⠊⠄⠴⠐⠣⠟⠙⠐⠜⠤⠠⠠⠕⠇⠫⠲⠀⠙` - - actual: `⠋⠏⠒⠓⠎⠢⠊⠄⠀⠀⠦⠠⠠⠟⠙⠴⠔⠠⠠⠕⠇⠑⠙⠀` + - actual: `⠋⠏⠒⠓⠎⠢⠊⠄⠦⠄⠴⠠⠠⠟⠙⠠⠴⠤⠴⠠⠠⠕⠇⠫` - first differing cell (zero-based): 172 - - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` + - current primary/reason: `corpus_suspect` / `rule34_roman_indicator_before_opening_parenthesis` - `sentence_02.json` #14: 한국타이어앤테크놀로지㈜(대표이사 이수일, 이하 한국타이어)가 국제자동차연맹(FIA)이 주관하는 ‘FIA 주니어 ERC(FIA Junior ERC, 이하 주니어 ERC)’ 대회에 레이싱 타이어를 독점 공급한다. - - expected: `⠚⠒⠈⠍⠁⠓⠣⠕⠎⠗⠒⠓⠝⠋⠪⠉⠥⠂⠐⠥⠨⠕⠦⠄` - - actual: `` - - error: `Invalid symbol character` - - first differing cell (zero-based): 0 - - current primary/reason: `unsupported_character_review` / `unsupported_character_review` + - expected: `⠍⠉⠕⠎⠀⠴⠠⠠⠑⠗⠉⠦⠄⠴⠠⠠⠋⠊⠁⠀⠠⠚⠥⠝` + - actual: `⠍⠉⠕⠎⠀⠴⠠⠠⠻⠉⠦⠄⠴⠠⠠⠋⠊⠁⠀⠠⠚⠥⠝⠊` + - first differing cell (zero-based): 114 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` - `sentence_03.json` #1: 김 지사는 18일(이하 현지시각) 미국 코네티컷주 댄버리 린데 본사에서 산지브 람바(Sanjiv Lamba) 린데 회장, 성백석 린데코리아 회장, 조일교 아산시 부시장과 투자양해각서(MOU)을 체결했다. - expected: `⠠⠎⠦⠄⠴⠠⠠⠍⠕⠥⠠⠴⠮⠀⠰⠝⠈⠳⠚⠗⠌⠊⠲` - actual: `⠠⠎⠦⠄⠴⠠⠠⠍⠳⠠⠴⠮⠀⠰⠝⠈⠳⠚⠗⠌⠊⠲` @@ -1783,30 +1768,28 @@ Representative `mismatch` samples: - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` - `sentence_04.json` #62: LG디스플레이가 자사의 유기발광다이오드(OLED) TV 패널이 글로벌 친환경 인증기관인 카본 트러스트(Carbon Trust)로 부터 탄소발자국 인증을 획득했다고 19일 밝혔다. - expected: `⠧⠶⠊⠣⠕⠥⠊⠪⠴⠐⠣⠠⠠⠕⠇⠫⠐⠜⠀⠠⠠⠞⠧⠲` - - actual: `⠧⠶⠊⠣⠕⠥⠊⠪⠦⠄⠴⠠⠠⠕⠇⠫⠠⠴⠀⠰⠠⠠⠞⠧` + - actual: `⠧⠶⠊⠣⠕⠥⠊⠪⠦⠄⠴⠠⠠⠕⠇⠫⠠⠴⠀⠴⠠⠠⠞⠧` - first differing cell (zero-based): 35 - current primary/reason: `corpus_suspect` / `rule34_roman_indicator_before_opening_parenthesis` ### `decimal_point_between_ascii_digits` -Of the 4546 candidates, 1042 are the actual `pending_rule_review` subcluster. The other 3504 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 4546 candidates, 401 are the actual `pending_rule_review` subcluster. The other 4145 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. -For this output-signature audit, 1099 mismatches were evaluable and 267 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. +For this output-signature audit, 440 mismatches were evaluable and 127 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. Localized first-difference transitions: -- `U+2814 ⠔ -> U+2824 ⠤`: 58 -- `U+2826 ⠦ -> U+2800 ⠀`: 36 -- `U+2826 ⠦ -> U+2810 ⠐`: 19 -- `U+2800 ⠀ -> U+280A ⠊`: 16 -- `U+283C ⠼ -> U+2800 ⠀`: 14 +- `U+2814 ⠔ -> U+280A ⠊`: 19 +- `U+2800 ⠀ -> U+2812 ⠒`: 16 +- `U+2810 ⠐ -> U+2832 ⠲`: 13 +- `U+2800 ⠀ -> U+283C ⠼`: 10 +- `U+2820 ⠠ -> U+2834 ⠴`: 10 Mismatch primary-class distribution: -- `comparison_method`: 17 -- `corpus_suspect`: 28 -- `pending_rule_review`: 1042 -- `unsupported_character_review`: 12 +- `corpus_suspect`: 39 +- `pending_rule_review`: 401 Representative `exact` samples: @@ -1814,9 +1797,9 @@ Representative `exact` samples: - expected: `⠕⠘⠾⠀⠨⠿⠚⠃⠠⠕⠚⠗⠶⠈⠌⠚⠽⠁⠵⠀⠈⠧⠚⠁` - actual: `⠕⠘⠾⠀⠨⠿⠚⠃⠠⠕⠚⠗⠶⠈⠌⠚⠽⠁⠵⠀⠈⠧⠚⠁` - current primary/reason: `exact` / `exact` -- `sentence_02.json` #47: 정 의원은 지난 2월 통계청에서 발표한 2022년 합계출산율이 0.78명으로 OECD(경제협력개발기구)에 가입한 38개국 중 유일하게 출산율 1명대 이하를 기록하는 등 초저출생 현상이 가속화되고 있다고 지적했다. - - expected: `⠨⠻⠀⠺⠏⠒⠵⠀⠨⠕⠉⠒⠀⠼⠃⠏⠂⠀⠓⠿⠈⠌⠰⠻` - - actual: `⠨⠻⠀⠺⠏⠒⠵⠀⠨⠕⠉⠒⠀⠼⠃⠏⠂⠀⠓⠿⠈⠌⠰⠻` +- `sentence_02.json` #5: 현재의 경기상황을 보여주는 동행지수(1월 기준, 99.8→99.4)·앞으로의 흐름을 보여주는 선행지수(98.8→98.5)는 모두 기준(100)보다 낮은 수준에서 하락세를 지속하면서 경기 부진을 반영하는 모양새다. + - expected: `⠚⠡⠨⠗⠺⠀⠈⠻⠈⠕⠇⠶⠚⠧⠶⠮⠀⠘⠥⠱⠨⠍⠉⠵` + - actual: `⠚⠡⠨⠗⠺⠀⠈⠻⠈⠕⠇⠶⠚⠧⠶⠮⠀⠘⠥⠱⠨⠍⠉⠵` - current primary/reason: `exact` / `exact` - `sentence_03.json` #8: LG전자의 1분기 영업이익은 전년동기대비 22.9% 감소한 1조4974억원이다. 특히 2009년 국제회계기준(IFRS) 도입 이후 처음으로 삼성전자의 영업이익을 넘어섰다. - expected: `⠴⠠⠠⠇⠛⠲⠨⠾⠨⠣⠺⠀⠼⠁⠘⠛⠈⠕⠀⠻⠎⠃⠕⠕` @@ -1831,37 +1814,37 @@ Representative `localized_mismatch` samples: - `sentence_01.json` #414: 셀트리온은 지난 5일 유럽의약품청(EMA)에 유플라이마의 20㎎/0.2ml(이하 20㎎) 제형을 추가하는 품목 변경 허가 신청을 했다고 16일 밝혔다. - expected: `⠺⠀⠼⠃⠚⠴⠍⠛⠸⠌⠼⠚⠲⠃⠍⠇⠦⠄⠕⠚⠀⠼⠃⠚` - - actual: `⠺⠀⠼⠃⠚⠴⠍⠛⠲⠸⠌⠼⠚⠲⠃⠴⠍⠲⠴⠇⠦⠄⠕⠚` + - actual: `⠺⠀⠼⠃⠚⠴⠍⠛⠲⠸⠌⠼⠚⠲⠃⠴⠍⠇⠦⠄⠕⠚⠀⠼` - first differing cell (zero-based): 60 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_02.json` #5: 현재의 경기상황을 보여주는 동행지수(1월 기준, 99.8→99.4)·앞으로의 흐름을 보여주는 선행지수(98.8→98.5)는 모두 기준(100)보다 낮은 수준에서 하락세를 지속하면서 경기 부진을 반영하는 모양새다. - - expected: `⠛⠐⠀⠼⠊⠊⠲⠓⠀⠒⠕⠀⠼⠊⠊⠲⠙⠠⠴⠐⠆⠣⠲⠪` - - actual: `⠛⠐⠀⠼⠊⠊⠲⠓⠒⠕⠼⠊⠊⠲⠙⠠⠴⠐⠆⠣⠲⠪⠐⠥` - - first differing cell (zero-based): 52 - - current primary/reason: `pending_rule_review` / `number_rule_review` +- `sentence_02.json` #420: 대전대학교(총장 남상호) LINC3.0사업단(단장 이영환)은 지난 6일 특화 분야 ICC(기업협업) 참여학과 중심의 기술개발 프로그램인 ‘2023 혜화 All-SET 기술사업화’ 운영을 위한 선정평가를 진행했다고 7일 밝혔다. + - expected: `⠥⠠⠴⠀⠴⠠⠠⠇⠔⠉⠼⠉⠲⠚⠇⠎⠃⠊⠒⠦⠄⠊⠒⠨` + - actual: `⠥⠠⠴⠀⠴⠠⠠⠇⠊⠝⠉⠼⠉⠲⠚⠇⠎⠃⠊⠒⠦⠄⠊⠒` + - first differing cell (zero-based): 30 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` - `sentence_03.json` #413: 이날 ‘올해 1분기(1~3월) 분기 실적’을 발표한 주요 기업은 사회연결망(SNS) 기업인 메타(META↑0.89%)와 글로벌 호텔 체인 힐튼 월드와이드 홀딩스(HLT↓3.41%), 세계 최대 항공기 제조업체 보잉(BA↑0.42%) 입니다. - expected: `⠄⠴⠠⠠⠍⠑⠞⠁⠀⠰⠒⠕⠀⠼⠚⠲⠓⠊⠴⠏⠠⠴⠧⠀` - - actual: `⠄⠴⠠⠠⠍⠑⠞⠁⠲⠰⠒⠕⠼⠚⠲⠓⠊⠴⠏⠠⠴⠧⠀⠈` + - actual: `⠄⠴⠠⠠⠍⠑⠞⠁⠲⠀⠰⠒⠕⠀⠼⠚⠲⠓⠊⠴⠏⠠⠴⠧` - first differing cell (zero-based): 98 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_04.json` #185: 과기정통부가 같은 날 발표한 ‘2022년 디지털정보격차 실태조사’ 결과에서는 지난해 저소득층·장애인·농어민·고령층 등 취약계층의 디지털정보화(온라인·모바일·PC 활용) 수준이 국민 평균(100) 대비 76.2 정도로 나타났다. 전년(75.4) 대비 소폭 올랐다. - - expected: `⠌⠊⠲⠀⠨⠾⠉⠡⠦⠄⠼⠛⠑⠲⠙⠠⠴⠀⠊⠗⠘⠕⠀⠠` - - actual: `⠌⠊⠲⠀⠨⠾⠉⠡⠀⠀⠦⠼⠛⠑⠲⠙⠴⠀⠊⠗⠘⠕⠀⠠` - - first differing cell (zero-based): 228 +- `sentence_04.json` #957: 1분기 실질 국내총소득(GDI)은 0.8% 늘어 증가율이 실질 GDP(0.3%)를 웃돌았다. 원유·천연가스 등 주요 수입품 가격 하락폭이 반도체 등 주요 수출품 가격 하락폭보다 커 교역조건이 개선됐기 때문이다. + - expected: `⠠⠕⠂⠨⠕⠂⠀⠴⠰⠠⠠⠛⠙⠏⠦⠄⠼⠚⠲⠉⠴⠏⠠⠴` + - actual: `⠠⠕⠂⠨⠕⠂⠀⠴⠠⠠⠛⠙⠏⠐⠣⠼⠚⠲⠉⠴⠏⠴⠐⠜` + - first differing cell (zero-based): 65 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` Representative `mismatch` samples: -- `sentence_01.json` #17: 지난달 30일(현지시각) 뉴욕증권거래소(NYSE)에서 다우존스30산업평균지수는 전 거래일보다 73.55포인트(0.22%) 하락한 3만3147.25로 마감했다. 대형주 중심의 S&P500지수는 9.78포인트(0.25%) 하락한 3839.50으로, 기술주 중심의 나스닥지수는 11.61포인트(0.11%) 하락한 1만0466.4 - - expected: `⠠⠕⠢⠺⠀⠴⠠⠎⠈⠯⠠⠏⠼⠑⠚⠚⠨⠕⠠⠍⠉⠵⠀⠼` - - actual: `⠠⠕⠢⠺⠀⠴⠠⠎⠲⠴⠈⠯⠲⠴⠠⠏⠼⠑⠚⠚⠨⠕⠠⠍` - - first differing cell (zero-based): 169 +- `sentence_01.json` #167: 올해 전 세계 반도체 시장 전망도 암울하다. 세계반도체시장통계기구(WSTS)에 따르면 올해 글로벌 반도체 매출은 5천565억 달러(약 706조5천880억원)로 지난해(5천801억달러) 대비 4.1% 감소할 것으로 조사됐다. + - expected: `⠈⠍⠦⠄⠴⠠⠠⠺⠎⠞⠎⠠⠴⠝⠀⠠⠊⠐⠪⠑⠡⠀⠥⠂` + - actual: `⠈⠍⠦⠄⠴⠠⠠⠺⠌⠎⠠⠴⠝⠀⠠⠊⠐⠪⠑⠡⠀⠥⠂⠚` + - first differing cell (zero-based): 67 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_02.json` #420: 대전대학교(총장 남상호) LINC3.0사업단(단장 이영환)은 지난 6일 특화 분야 ICC(기업협업) 참여학과 중심의 기술개발 프로그램인 ‘2023 혜화 All-SET 기술사업화’ 운영을 위한 선정평가를 진행했다고 7일 밝혔다. + - expected: `⠥⠠⠴⠀⠴⠠⠠⠇⠔⠉⠼⠉⠲⠚⠇⠎⠃⠊⠒⠦⠄⠊⠒⠨` + - actual: `⠥⠠⠴⠀⠴⠠⠠⠇⠊⠝⠉⠼⠉⠲⠚⠇⠎⠃⠊⠒⠦⠄⠊⠒` + - first differing cell (zero-based): 30 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_02.json` #5: 현재의 경기상황을 보여주는 동행지수(1월 기준, 99.8→99.4)·앞으로의 흐름을 보여주는 선행지수(98.8→98.5)는 모두 기준(100)보다 낮은 수준에서 하락세를 지속하면서 경기 부진을 반영하는 모양새다. - - expected: `⠛⠐⠀⠼⠊⠊⠲⠓⠀⠒⠕⠀⠼⠊⠊⠲⠙⠠⠴⠐⠆⠣⠲⠪` - - actual: `⠛⠐⠀⠼⠊⠊⠲⠓⠒⠕⠼⠊⠊⠲⠙⠠⠴⠐⠆⠣⠲⠪⠐⠥` - - first differing cell (zero-based): 52 - - current primary/reason: `pending_rule_review` / `number_rule_review` - `sentence_03.json` #147: 20일(현지시간) 미국부동산중개인협회(NAR)는 지난 3월 기존 주택 매매 건수가 444만건으로 전월보다 2.4% 줄었다고 밝혔다. 전년 동기와 비교하면 22% 급감했다. - expected: `⠚⠽⠦⠄⠴⠠⠠⠝⠁⠗⠠⠴⠉⠵⠀⠨⠕⠉⠒⠀⠼⠉⠏⠂` - actual: `⠚⠽⠦⠄⠴⠠⠠⠝⠜⠠⠴⠉⠵⠀⠨⠕⠉⠒⠀⠼⠉⠏⠂⠀` @@ -1875,23 +1858,19 @@ Representative `mismatch` samples: ### `korean_inline_parenthesized_single_arithmetic_operator` -Of the 23 candidates, 6 are the actual `pending_rule_review` subcluster. The other 17 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. - -For this output-signature audit, 6 mismatches were evaluable and 2 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. - -Localized first-difference transitions: +Of the 23 candidates, 1 are the actual `pending_rule_review` subcluster. The other 22 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. -- `U+2814 ⠔ -> U+2824 ⠤`: 2 +For this output-signature audit, 1 mismatches were evaluable and 0 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. Mismatch primary-class distribution: -- `pending_rule_review`: 6 +- `pending_rule_review`: 1 Representative `exact` samples: -- `sentence_01.json` #2922: 실제로 OECD 38개국 중 경제자유도 상위 10개국과 하위 10개국을 대상으로 경제자유도와 삶의 질(BLI)을 분석해보면, 경제자유도와 삶의 질 간에는 뚜렷한 정(+)의 상관관계(상관계수 +0.80)가 나타났다. - - expected: `⠠⠕⠂⠨⠝⠐⠥⠀⠴⠠⠠⠕⠑⠉⠙⠀⠼⠉⠓⠈⠗⠈⠍⠁` - - actual: `⠠⠕⠂⠨⠝⠐⠥⠀⠴⠠⠠⠕⠑⠉⠙⠀⠼⠉⠓⠈⠗⠈⠍⠁` +- `sentence_01.json` #2921: 경제자유도가 높아지면 1인당 GDP도 개선되는 것으로 나타났다. OECD 회원국의 2021년 경제자유도와 1인당 GDP간 관계를 분석한 결과, 경제자유도와 1인당 GDP 간에는 정(+)의 상관관계(상관계수 +0.46)를 보였다. + - expected: `⠈⠻⠨⠝⠨⠣⠩⠊⠥⠫⠀⠉⠥⠲⠣⠨⠕⠑⠡⠀⠼⠁⠟⠊` + - actual: `⠈⠻⠨⠝⠨⠣⠩⠊⠥⠫⠀⠉⠥⠲⠣⠨⠕⠑⠡⠀⠼⠁⠟⠊` - current primary/reason: `exact` / `exact` - `sentence_02.json` #417: 시는 세종고용복지플러스(+)센터, 세종여성새로일하기센터, 세종과학기술인협회 등과 공동으로 기업지원사업안내는 물론, 청년들이 면접 부담을 덜 수 있도록 청년면접비지원사업도 동시 진행하고 있다. - expected: `⠠⠕⠉⠵⠀⠠⠝⠨⠿⠈⠥⠬⠶⠘⠭⠨⠕⠙⠮⠐⠎⠠⠪⠦` @@ -1908,27 +1887,20 @@ Representative `exact` samples: Representative `mismatch` samples: -- `sentence_01.json` #2921: 경제자유도가 높아지면 1인당 GDP도 개선되는 것으로 나타났다. OECD 회원국의 2021년 경제자유도와 1인당 GDP간 관계를 분석한 결과, 경제자유도와 1인당 GDP 간에는 정(+)의 상관관계(상관계수 +0.46)를 보였다. - - expected: `⠀⠼⠁⠟⠊⠶⠀⠴⠰⠠⠠⠛⠙⠏⠲⠊⠥⠀⠈⠗⠠⠾⠊⠽` - - actual: `⠀⠼⠁⠟⠊⠶⠀⠴⠠⠠⠛⠙⠏⠲⠊⠥⠀⠈⠗⠠⠾⠊⠽⠉` - - first differing cell (zero-based): 27 - - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` - `sentence_02.json` #948: 유니브시티(UNIV+CITY)는 대학교(UNIVERSITY)와 시(CITY)의 합성어로 더하기(+)는 시와 대학, 기업 등과의 상생을 의미한다. 파란과 빨간, 노란색으로 자유롭고 간편함을 표현한 손 글씨(캘리그라피)를 통해 대학도시 천안의 젊음을 상징한다. - expected: `⠄⠴⠠⠠⠥⠝⠊⠧⠐⠖⠠⠠⠉⠰⠽⠠⠴⠉⠵⠀⠊⠗⠚⠁` - - actual: `⠄⠴⠠⠠⠥⠝⠊⠧⠲⠀⠢⠀⠴⠠⠠⠉⠰⠽⠠⠴⠉⠵⠀⠊` + - actual: `⠄⠴⠠⠠⠥⠝⠊⠧⠲⠢⠴⠠⠠⠉⠰⠽⠠⠴⠉⠵⠀⠊⠗⠚` - first differing cell (zero-based): 18 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` ### `korean_majority_same_token_roman_sandwich_non_domain` -Of the 947 candidates, 342 are the actual `pending_rule_review` subcluster. The other 605 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 947 candidates, 201 are the actual `pending_rule_review` subcluster. The other 746 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. Mismatch primary-class distribution: -- `comparison_method`: 1 -- `corpus_suspect`: 7 -- `pending_rule_review`: 342 -- `unsupported_character_review`: 4 +- `corpus_suspect`: 10 +- `pending_rule_review`: 201 Representative `exact` samples: @@ -1936,9 +1908,9 @@ Representative `exact` samples: - expected: `⠕⠧⠀⠈⠧⠒⠐⠡⠚⠗⠠⠎⠀⠴⠠⠠⠋⠁⠁⠲⠉⠵⠀⠦` - actual: `⠕⠧⠀⠈⠧⠒⠐⠡⠚⠗⠠⠎⠀⠴⠠⠠⠋⠁⠁⠲⠉⠵⠀⠦` - current primary/reason: `exact` / `exact` -- `sentence_02.json` #483: 충남교육청(교육감 김지철)은 7월 31일부터 오는 11일까지 도내 Wee센터·Wee클래스 전문상담교사, 전문상담사, 임상심리사 150명을 대상으로 ‘2023 하반기 위(Wee) 프로젝트 역량강화 직무연수’를 추진하고 있다고 밝혔다. - - expected: `⠰⠍⠶⠉⠢⠈⠬⠩⠁⠰⠻⠦⠄⠈⠬⠩⠁⠫⠢⠀⠈⠕⠢⠨` - - actual: `⠰⠍⠶⠉⠢⠈⠬⠩⠁⠰⠻⠦⠄⠈⠬⠩⠁⠫⠢⠀⠈⠕⠢⠨` +- `sentence_02.json` #122: 이번 연수는 자연과 상생하는 청정환경 도시를 조성하고 정보통신기술(IT)·생명공학기술(BT) 등 첨단산업단지를 만들어가는 제주도의 우수정책 사례를 벤치마킹함으로써, 심도 있는 정책의정 구현을 모색하기 위해 마련되었다. + - expected: `⠕⠘⠾⠀⠡⠠⠍⠉⠵⠀⠨⠣⠡⠈⠧⠀⠇⠶⠠⠗⠶⠚⠉⠵` + - actual: `⠕⠘⠾⠀⠡⠠⠍⠉⠵⠀⠨⠣⠡⠈⠧⠀⠇⠶⠠⠗⠶⠚⠉⠵` - current primary/reason: `exact` / `exact` - `sentence_03.json` #50: 당시 CC(폐쇄)TV에 찍힌 장면을 보면 A씨는 엘리베이터를 기다리는 B씨를 발견하자 몰래 뒤로 다가가 갑자기 피해 여성의 머리를 뒤에서 돌려차기로 가격하는 등 폭행했다. - expected: `⠊⠶⠠⠕⠀⠴⠠⠠⠉⠉⠦⠄⠙⠌⠠⠧⠗⠠⠴⠴⠠⠠⠞⠧` @@ -1956,15 +1928,15 @@ Representative `mismatch` samples: - actual: `⠠⠕⠂⠦⠄⠴⠠⠠⠜⠠⠴⠐⠆⠫⠇⠶⠚⠡⠠⠕⠂⠦⠄⠴` - first differing cell (zero-based): 34 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_02.json` #122: 이번 연수는 자연과 상생하는 청정환경 도시를 조성하고 정보통신기술(IT)·생명공학기술(BT) 등 첨단산업단지를 만들어가는 제주도의 우수정책 사례를 벤치마킹함으로써, 심도 있는 정책의정 구현을 모색하기 위해 마련되었다. - - expected: `⠁⠈⠕⠠⠯⠦⠄⠴⠠⠠⠃⠞⠠⠴⠀⠊⠪⠶⠀⠰⠎⠢⠊⠒` - - actual: `⠁⠈⠕⠠⠯⠦⠄⠴⠰⠠⠠⠃⠞⠠⠴⠀⠊⠪⠶⠀⠰⠎⠢⠊` - - first differing cell (zero-based): 89 +- `sentence_02.json` #230: INC는 아이디어(I)-니즈(N)-역량(C)의 융합을 뜻하며, 파괴적 혁신과 기업가적 대학으로서 산학협력을 활성화하기 위한 방법론으로, 지속가능한 가치창출형 산학협력을 위한 브랜드이다. + - expected: `⠴⠠⠠⠊⠝⠉⠲⠉⠵⠀⠣⠕⠊⠕⠎⠦⠄⠴⠠⠊⠠⠴⠤⠉` + - actual: `⠴⠠⠠⠔⠉⠲⠉⠵⠀⠣⠕⠊⠕⠎⠦⠄⠴⠠⠊⠠⠴⠤⠉⠕` + - first differing cell (zero-based): 3 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_03.json` #311: 최근 한 달간 방산주 기업들 주가는 상승했다. 한화자산운용이 운용하는 상장지수펀드(ETF) ‘ARIRANG K방산Fn’는 이날 기준 1개월간 수익률이 24.26%에 달한다. - - expected: `⠑⠞⠋⠠⠴⠀⠠⠦⠴⠠⠠⠜⠊⠗⠁⠝⠛⠀⠰⠠⠅⠲⠘⠶` - - actual: `⠑⠞⠋⠠⠴⠀⠠⠦⠰⠠⠠⠜⠊⠗⠁⠝⠛⠀⠠⠅⠲⠘⠶⠇` - - first differing cell (zero-based): 88 +- `sentence_03.json` #1041: 사모투자펀드(PEF) 운용사 IMM크레딧앤솔루션(IMM CS·이하 ICS)이 KT클라우드에 6000억원을 수혈하며 주요 주주로 올라선다. 대규모 투자금을 유치한 KT클라우드는 데이터센터 확충 등 본격적인 신사업 확대에 나설 방침이다. + - expected: `⠊⠍⠍⠀⠠⠠⠉⠎⠐⠆⠕⠚⠀⠴⠠⠠⠊⠉⠎⠠⠴⠕⠀⠴` + - actual: `⠊⠍⠍⠀⠠⠠⠉⠎⠲⠐⠆⠕⠚⠀⠴⠠⠠⠊⠉⠎⠠⠴⠕⠀` + - first differing cell (zero-based): 64 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` - `sentence_04.json` #338: 체험에 앞서 간단한 O·X 퀴즈가 진행됐다. 액화석유가스(LPG)·액화천연가스(LNG)·부탄가스의 차이점을 설명하고 누출사고 시 대응 방법 등을 O·X로 답하는 방식이다. - expected: `⠊⠒⠚⠒⠀⠴⠠⠕⠐⠆⠴⠠⠭⠲⠀⠋⠍⠗⠨⠪⠫⠀⠨⠟` @@ -1974,14 +1946,13 @@ Representative `mismatch` samples: ### `korean_prefixed_closed_allcaps_parenthetical` -Of the 54492 candidates, 7483 are the actual `pending_rule_review` subcluster. The other 47009 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 54492 candidates, 4549 are the actual `pending_rule_review` subcluster. The other 49943 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. Mismatch primary-class distribution: -- `comparison_method`: 186 -- `corpus_suspect`: 995 -- `pending_rule_review`: 7483 -- `unsupported_character_review`: 123 +- `corpus_suspect`: 1047 +- `pending_rule_review`: 4549 +- `unsupported_character_review`: 4 Representative `exact` samples: @@ -1993,9 +1964,9 @@ Representative `exact` samples: - expected: `⠠⠝⠨⠿⠠⠕⠀⠨⠍⠰⠽⠐⠀⠠⠝⠨⠿⠠⠕⠓⠒⠠⠥⠨` - actual: `⠠⠝⠨⠿⠠⠕⠀⠨⠍⠰⠽⠐⠀⠠⠝⠨⠿⠠⠕⠓⠒⠠⠥⠨` - current primary/reason: `exact` / `exact` -- `sentence_03.json` #4: 18일(현지시간) 월스트리트저널(WSJ) 등 미 외신은 투·개표기 업체 도미니언 투표시스템이 2021년 1월 제기한 16억 달러(약 2조원) 명예훼손 소송과 관련, 폭스뉴스와 도미니언 측이 이 같이 합의했다고 보도했다. - - expected: `⠼⠁⠓⠕⠂⠦⠄⠚⠡⠨⠕⠠⠕⠫⠒⠠⠴⠀⠏⠂⠠⠪⠓⠪` - - actual: `⠼⠁⠓⠕⠂⠦⠄⠚⠡⠨⠕⠠⠕⠫⠒⠠⠴⠀⠏⠂⠠⠪⠓⠪` +- `sentence_03.json` #2: 연구소는 전남대학교, 순천대학교, 전남도립대학교, 동신대학교, 한국에너지공과대학교, 충남대학교, 부산대학교 등 드론, 빅데이터, 인공지능(AI) 관련 대학 교수와 전남테크노파크, ㈜엘시스 등 전문가로 기획연구팀을 구성한다. + - expected: `⠡⠈⠍⠠⠥⠉⠵⠀⠨⠾⠉⠢⠊⠗⠚⠁⠈⠬⠐⠀⠠⠛⠰⠾` + - actual: `⠡⠈⠍⠠⠥⠉⠵⠀⠨⠾⠉⠢⠊⠗⠚⠁⠈⠬⠐⠀⠠⠛⠰⠾` - current primary/reason: `exact` / `exact` - `sentence_04.json` #1: 일본 정부는 이날 국가안전보장회의(NSC)를 열어 대응 방침을 논의했다. 이어 북한의 미사일 발사에 대해 “엄중히 항의하고 강하게 비난한다”고 밝혔다. - expected: `⠕⠂⠘⠷⠀⠨⠻⠘⠍⠉⠵⠀⠕⠉⠂⠀⠈⠍⠁⠫⠣⠒⠨⠾` @@ -2006,7 +1977,7 @@ Representative `mismatch` samples: - `sentence_01.json` #14: 새롭게 선보인 DRX 브랜딩과 팀 컬러는 2023년 1월 1일부터 DRX의 온오프라인 콘텐츠, 엠디(MD), SNS, 유튜브 등 모든 제작물에 적용될 예정이다. - expected: `⠰⠪⠐⠀⠝⠢⠊⠕⠴⠐⠣⠠⠠⠍⠙⠐⠜⠂⠀⠠⠠⠎⠝⠎` - - actual: `⠰⠪⠐⠀⠝⠢⠊⠕⠦⠄⠴⠠⠠⠍⠙⠠⠴⠐⠀⠰⠠⠠⠎⠝` + - actual: `⠰⠪⠐⠀⠝⠢⠊⠕⠦⠄⠴⠠⠠⠍⠙⠠⠴⠐⠀⠴⠠⠠⠎⠝` - first differing cell (zero-based): 97 - current primary/reason: `corpus_suspect` / `rule34_roman_indicator_before_opening_parenthesis` - `sentence_02.json` #10: 설동호 대전교육감은 “학교 교육과정 및 여건에 맞는 인공지능(AI)교육 선도학교 운영을 통해 AI·SW교육 기반 구축과 학생들과 교원들의 미래 디지털 역량이 함양될 것이다.”라고 말했다. @@ -2020,28 +1991,27 @@ Representative `mismatch` samples: - first differing cell (zero-based): 175 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` - `sentence_04.json` #19: 이날 행사에는 구 대표 외에 권봉석 (주)LG 최고운영책임자(COO), 박일평 LG사이언스파크대표(사장)를 비롯해 각 계열사 최고기술책임자(CTO), 최고데이터책임자(CDO), 최고인사책임자(CHO) 등이 참석해 국내 이공계 R&D 인재 400여명과 만났다. - - expected: `⠁⠕⠢⠨⠦⠄⠴⠠⠠⠉⠕⠕⠠⠴⠐⠀⠘⠁⠕⠂⠙⠻⠀⠴` - - actual: `⠁⠕⠢⠨⠦⠄⠴⠠⠉⠠⠕⠠⠕⠠⠴⠐⠀⠘⠁⠕⠂⠙⠻⠀` - - first differing cell (zero-based): 60 + - expected: `⠁⠕⠢⠨⠦⠄⠴⠠⠠⠉⠓⠕⠠⠴⠀⠊⠪⠶⠕⠀⠰⠣⠢⠠` + - actual: `⠁⠕⠢⠨⠦⠄⠴⠠⠉⠠⠓⠠⠕⠠⠴⠀⠊⠪⠶⠕⠀⠰⠣⠢` + - first differing cell (zero-based): 188 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` ### `korean_prefixed_closed_roman_annotation_rule_34_order` -Of the 64382 candidates, 9168 are the actual `pending_rule_review` subcluster. The other 55214 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 64382 candidates, 5450 are the actual `pending_rule_review` subcluster. The other 58932 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. -For this output-signature audit, 10666 mismatches were evaluable and 1119 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. +For this output-signature audit, 6634 mismatches were evaluable and 1166 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. Localized first-difference transitions: -- `U+2834 ⠴ -> U+2826 ⠦`: 1117 -- `U+2810 ⠐ -> U+2826 ⠦`: 2 +- `U+2834 ⠴ -> U+2826 ⠦`: 1163 +- `U+2810 ⠐ -> U+2826 ⠦`: 3 Mismatch primary-class distribution: -- `comparison_method`: 245 -- `corpus_suspect`: 1114 -- `pending_rule_review`: 9168 -- `unsupported_character_review`: 139 +- `corpus_suspect`: 1180 +- `pending_rule_review`: 5450 +- `unsupported_character_review`: 4 Representative `exact` samples: @@ -2053,9 +2023,9 @@ Representative `exact` samples: - expected: `⠠⠝⠨⠿⠠⠕⠀⠨⠍⠰⠽⠐⠀⠠⠝⠨⠿⠠⠕⠓⠒⠠⠥⠨` - actual: `⠠⠝⠨⠿⠠⠕⠀⠨⠍⠰⠽⠐⠀⠠⠝⠨⠿⠠⠕⠓⠒⠠⠥⠨` - current primary/reason: `exact` / `exact` -- `sentence_03.json` #4: 18일(현지시간) 월스트리트저널(WSJ) 등 미 외신은 투·개표기 업체 도미니언 투표시스템이 2021년 1월 제기한 16억 달러(약 2조원) 명예훼손 소송과 관련, 폭스뉴스와 도미니언 측이 이 같이 합의했다고 보도했다. - - expected: `⠼⠁⠓⠕⠂⠦⠄⠚⠡⠨⠕⠠⠕⠫⠒⠠⠴⠀⠏⠂⠠⠪⠓⠪` - - actual: `⠼⠁⠓⠕⠂⠦⠄⠚⠡⠨⠕⠠⠕⠫⠒⠠⠴⠀⠏⠂⠠⠪⠓⠪` +- `sentence_03.json` #2: 연구소는 전남대학교, 순천대학교, 전남도립대학교, 동신대학교, 한국에너지공과대학교, 충남대학교, 부산대학교 등 드론, 빅데이터, 인공지능(AI) 관련 대학 교수와 전남테크노파크, ㈜엘시스 등 전문가로 기획연구팀을 구성한다. + - expected: `⠡⠈⠍⠠⠥⠉⠵⠀⠨⠾⠉⠢⠊⠗⠚⠁⠈⠬⠐⠀⠠⠛⠰⠾` + - actual: `⠡⠈⠍⠠⠥⠉⠵⠀⠨⠾⠉⠢⠊⠗⠚⠁⠈⠬⠐⠀⠠⠛⠰⠾` - current primary/reason: `exact` / `exact` - `sentence_04.json` #1: 일본 정부는 이날 국가안전보장회의(NSC)를 열어 대응 방침을 논의했다. 이어 북한의 미사일 발사에 대해 “엄중히 항의하고 강하게 비난한다”고 밝혔다. - expected: `⠕⠂⠘⠷⠀⠨⠻⠘⠍⠉⠵⠀⠕⠉⠂⠀⠈⠍⠁⠫⠣⠒⠨⠾` @@ -2066,22 +2036,22 @@ Representative `localized_mismatch` samples: - `sentence_01.json` #14: 새롭게 선보인 DRX 브랜딩과 팀 컬러는 2023년 1월 1일부터 DRX의 온오프라인 콘텐츠, 엠디(MD), SNS, 유튜브 등 모든 제작물에 적용될 예정이다. - expected: `⠰⠪⠐⠀⠝⠢⠊⠕⠴⠐⠣⠠⠠⠍⠙⠐⠜⠂⠀⠠⠠⠎⠝⠎` - - actual: `⠰⠪⠐⠀⠝⠢⠊⠕⠦⠄⠴⠠⠠⠍⠙⠠⠴⠐⠀⠰⠠⠠⠎⠝` + - actual: `⠰⠪⠐⠀⠝⠢⠊⠕⠦⠄⠴⠠⠠⠍⠙⠠⠴⠐⠀⠴⠠⠠⠎⠝` - first differing cell (zero-based): 97 - current primary/reason: `corpus_suspect` / `rule34_roman_indicator_before_opening_parenthesis` -- `sentence_02.json` #605: 전국적으로 강력범죄가 연이어 발생하면서 천안시가 시민 안전을 위해 공원, 산책로, 등산로 등에 설치된 방범용 폐쇄회로(CC)TV 전수조사를 실시한다. - - expected: `⠌⠠⠧⠗⠚⠽⠐⠥⠴⠐⠣⠠⠠⠉⠉⠐⠜⠠⠠⠞⠧⠲⠀⠨` - - actual: `⠌⠠⠧⠗⠚⠽⠐⠥⠦⠄⠴⠠⠉⠠⠉⠠⠴⠰⠠⠠⠞⠧⠲⠀` - - first differing cell (zero-based): 115 +- `sentence_02.json` #78: 마지막으로 신규 회원사인 ㈜그레비티(대표 최항주)에 대한 소개와 서중석 교수의 발제로 ‘글쓰기에 필요한 다양한 생성형(Generative) AI Searcher’에 대한 토론이 진행됐다. + - expected: `⠀⠠⠗⠶⠠⠻⠚⠻⠴⠐⠣⠠⠛⠢⠻⠁⠞⠊⠧⠑⠐⠜⠀⠠` + - actual: `⠀⠠⠗⠶⠠⠻⠚⠻⠦⠄⠴⠠⠛⠢⠻⠁⠞⠊⠧⠑⠠⠴⠀⠴` + - first differing cell (zero-based): 116 - current primary/reason: `corpus_suspect` / `rule34_roman_indicator_before_opening_parenthesis` - `sentence_03.json` #37: 경찰은 주변 폐쇄회로(CC)TV 등을 분석해 A씨 동선을 추적했고 14일 오후 경기도 양주의 한 주택에서 A씨를 긴급체포했다. - expected: `⠌⠠⠧⠗⠚⠽⠐⠥⠴⠐⠣⠠⠠⠉⠉⠐⠜⠠⠠⠞⠧⠲⠀⠊` - - actual: `⠌⠠⠧⠗⠚⠽⠐⠥⠦⠄⠴⠠⠉⠠⠉⠠⠴⠰⠠⠠⠞⠧⠲⠀` + - actual: `⠌⠠⠧⠗⠚⠽⠐⠥⠦⠄⠴⠠⠠⠉⠉⠠⠴⠴⠠⠠⠞⠧⠲⠀` - first differing cell (zero-based): 21 - current primary/reason: `corpus_suspect` / `rule34_roman_indicator_before_opening_parenthesis` - `sentence_04.json` #42: 경찰은 폐쇄회로(CC)TV 분석과 쇠구슬 판매 업체 탐문 수사, 국과수 발사지점 방향성 감정 등을 통해 발사 의심 세대를 특정해 이날 피의자 A씨를 자택에서 검거했다. - expected: `⠌⠠⠧⠗⠚⠽⠐⠥⠴⠐⠣⠠⠠⠉⠉⠐⠜⠠⠠⠞⠧⠲⠀⠘` - - actual: `⠌⠠⠧⠗⠚⠽⠐⠥⠦⠄⠴⠠⠉⠠⠉⠠⠴⠰⠠⠠⠞⠧⠲⠀` + - actual: `⠌⠠⠧⠗⠚⠽⠐⠥⠦⠄⠴⠠⠠⠉⠉⠠⠴⠴⠠⠠⠞⠧⠲⠀` - first differing cell (zero-based): 16 - current primary/reason: `corpus_suspect` / `rule34_roman_indicator_before_opening_parenthesis` @@ -2089,7 +2059,7 @@ Representative `mismatch` samples: - `sentence_01.json` #14: 새롭게 선보인 DRX 브랜딩과 팀 컬러는 2023년 1월 1일부터 DRX의 온오프라인 콘텐츠, 엠디(MD), SNS, 유튜브 등 모든 제작물에 적용될 예정이다. - expected: `⠰⠪⠐⠀⠝⠢⠊⠕⠴⠐⠣⠠⠠⠍⠙⠐⠜⠂⠀⠠⠠⠎⠝⠎` - - actual: `⠰⠪⠐⠀⠝⠢⠊⠕⠦⠄⠴⠠⠠⠍⠙⠠⠴⠐⠀⠰⠠⠠⠎⠝` + - actual: `⠰⠪⠐⠀⠝⠢⠊⠕⠦⠄⠴⠠⠠⠍⠙⠠⠴⠐⠀⠴⠠⠠⠎⠝` - first differing cell (zero-based): 97 - current primary/reason: `corpus_suspect` / `rule34_roman_indicator_before_opening_parenthesis` - `sentence_02.json` #10: 설동호 대전교육감은 “학교 교육과정 및 여건에 맞는 인공지능(AI)교육 선도학교 운영을 통해 AI·SW교육 기반 구축과 학생들과 교원들의 미래 디지털 역량이 함양될 것이다.”라고 말했다. @@ -2103,59 +2073,43 @@ Representative `mismatch` samples: - first differing cell (zero-based): 175 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` - `sentence_04.json` #19: 이날 행사에는 구 대표 외에 권봉석 (주)LG 최고운영책임자(COO), 박일평 LG사이언스파크대표(사장)를 비롯해 각 계열사 최고기술책임자(CTO), 최고데이터책임자(CDO), 최고인사책임자(CHO) 등이 참석해 국내 이공계 R&D 인재 400여명과 만났다. - - expected: `⠁⠕⠢⠨⠦⠄⠴⠠⠠⠉⠕⠕⠠⠴⠐⠀⠘⠁⠕⠂⠙⠻⠀⠴` - - actual: `⠁⠕⠢⠨⠦⠄⠴⠠⠉⠠⠕⠠⠕⠠⠴⠐⠀⠘⠁⠕⠂⠙⠻⠀` - - first differing cell (zero-based): 60 + - expected: `⠁⠕⠢⠨⠦⠄⠴⠠⠠⠉⠓⠕⠠⠴⠀⠊⠪⠶⠕⠀⠰⠣⠢⠠` + - actual: `⠁⠕⠢⠨⠦⠄⠴⠠⠉⠠⠓⠠⠕⠠⠴⠀⠊⠪⠶⠕⠀⠰⠣⠢` + - first differing cell (zero-based): 188 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` ### `korean_prefixed_roman_parenthetical_followed_by_allcaps_hyphen_suffix` -Of the 11 candidates, 6 are the actual `pending_rule_review` subcluster. The other 5 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. - -For this output-signature audit, 11 mismatches were evaluable and 5 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. - -Localized first-difference transitions: +Of the 11 candidates, 0 are the actual `pending_rule_review` subcluster. The other 11 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. -- `U+2834 ⠴ -> U+2800 ⠀`: 5 +For this output-signature audit, 10 mismatches were evaluable and 0 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. Mismatch primary-class distribution: -- `corpus_suspect`: 5 -- `pending_rule_review`: 6 +- `corpus_suspect`: 10 -Representative `localized_mismatch` samples: +Representative `exact` samples: -- `sentence_01.json` #35: 삼성전자는 올해 CES에서 77인치 OLED TV를 처음으로 공개할 예정이다. 이는 지난 8월 ‘국제정보디스플레이학술대회(IMID) 2022’에서 삼성디스플레이가 공개한 77인치 퀀텀닷(QD)-OLED 패널을 탑재한 제품이다. - - expected: `⠋⠏⠒⠓⠎⠢⠊⠄⠴⠐⠣⠟⠙⠐⠜⠤⠠⠠⠕⠇⠫⠲⠀⠙` - - actual: `⠋⠏⠒⠓⠎⠢⠊⠄⠀⠀⠦⠠⠠⠟⠙⠴⠔⠠⠠⠕⠇⠑⠙⠀` - - first differing cell (zero-based): 172 - - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_02.json` #7731: 카젬 전 사장은 지난 2017년 9월 한국GM 사장으로 취임했으며, 작년 6월 중국 상하이자동차(SAIC)-GM 총괄 부사장을 맡고 있다. - - expected: `⠚⠣⠕⠨⠊⠿⠰⠣⠴⠐⠣⠠⠠⠎⠁⠊⠉⠐⠜⠤⠠⠠⠛⠍` - - actual: `⠚⠣⠕⠨⠊⠿⠰⠣⠀⠀⠦⠠⠠⠎⠁⠊⠉⠴⠔⠠⠠⠛⠍⠀` - - first differing cell (zero-based): 91 - - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_03.json` #4952: 1일 시장조사기관 옴디아에 따르면 LG전자는 지난 1분기 퀀텀닷(QD)-LCD TV 부문 집계에 처음으로 포함되면서 바로 글로벌 2위(17.5%)에 올랐다. - - expected: `⠋⠏⠒⠓⠎⠢⠊⠄⠴⠐⠣⠠⠠⠟⠙⠐⠜⠤⠠⠠⠇⠉⠙⠀` - - actual: `⠋⠏⠒⠓⠎⠢⠊⠄⠀⠀⠦⠠⠠⠟⠙⠴⠔⠠⠠⠇⠉⠙⠀⠴` - - first differing cell (zero-based): 64 - - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_02.json` #10613: 오스템임플란트 경영권 지분을 인수한 사모펀드(PEF) 연합군 유니슨캐피탈코리아(UCK)-MBK파트너스가 2차 공개매수를 거쳐 총 96.1% 지분을 확보했다. 오스템임플란트는 자발적상장폐지 요건을 넘겨 상장폐지 절차를 밟는다. + - expected: `⠥⠠⠪⠓⠝⠢⠕⠢⠙⠮⠐⠣⠒⠓⠪⠀⠈⠻⠻⠈⠏⠒⠀⠨` + - actual: `⠥⠠⠪⠓⠝⠢⠕⠢⠙⠮⠐⠣⠒⠓⠪⠀⠈⠻⠻⠈⠏⠒⠀⠨` + - current primary/reason: `exact` / `exact` Representative `mismatch` samples: - `sentence_01.json` #35: 삼성전자는 올해 CES에서 77인치 OLED TV를 처음으로 공개할 예정이다. 이는 지난 8월 ‘국제정보디스플레이학술대회(IMID) 2022’에서 삼성디스플레이가 공개한 77인치 퀀텀닷(QD)-OLED 패널을 탑재한 제품이다. - expected: `⠋⠏⠒⠓⠎⠢⠊⠄⠴⠐⠣⠟⠙⠐⠜⠤⠠⠠⠕⠇⠫⠲⠀⠙` - - actual: `⠋⠏⠒⠓⠎⠢⠊⠄⠀⠀⠦⠠⠠⠟⠙⠴⠔⠠⠠⠕⠇⠑⠙⠀` + - actual: `⠋⠏⠒⠓⠎⠢⠊⠄⠦⠄⠴⠠⠠⠟⠙⠠⠴⠤⠴⠠⠠⠕⠇⠫` - first differing cell (zero-based): 172 - - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` + - current primary/reason: `corpus_suspect` / `rule34_roman_indicator_before_opening_parenthesis` - `sentence_02.json` #7731: 카젬 전 사장은 지난 2017년 9월 한국GM 사장으로 취임했으며, 작년 6월 중국 상하이자동차(SAIC)-GM 총괄 부사장을 맡고 있다. - expected: `⠚⠣⠕⠨⠊⠿⠰⠣⠴⠐⠣⠠⠠⠎⠁⠊⠉⠐⠜⠤⠠⠠⠛⠍` - - actual: `⠚⠣⠕⠨⠊⠿⠰⠣⠀⠀⠦⠠⠠⠎⠁⠊⠉⠴⠔⠠⠠⠛⠍⠀` + - actual: `⠚⠣⠕⠨⠊⠿⠰⠣⠦⠄⠴⠠⠠⠎⠁⠊⠉⠠⠴⠤⠴⠠⠠⠛` - first differing cell (zero-based): 91 - - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` + - current primary/reason: `corpus_suspect` / `rule34_roman_indicator_before_opening_parenthesis` - `sentence_03.json` #866: 제주삼다수 리본은 화학적 재활용 페트인 ‘스카이펫(SKYPET)-CR’을 사용한 제품으로, 제주개발공사가 SK케미칼과 손잡고 2021년 10월 업계 최초로 개발했다. - expected: `⠠⠪⠋⠣⠕⠙⠝⠄⠴⠐⠣⠠⠠⠎⠅⠽⠏⠑⠞⠐⠜⠤⠠⠠` - - actual: `⠠⠪⠋⠣⠕⠙⠝⠄⠦⠄⠴⠠⠠⠎⠅⠽⠏⠑⠞⠠⠴⠤⠰⠠` + - actual: `⠠⠪⠋⠣⠕⠙⠝⠄⠦⠄⠴⠠⠠⠎⠅⠽⠏⠑⠞⠠⠴⠤⠴⠠` - first differing cell (zero-based): 47 - current primary/reason: `corpus_suspect` / `rule34_roman_indicator_before_opening_parenthesis` @@ -2194,7 +2148,7 @@ Representative `mismatch` samples: - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` - `sentence_02.json` #9391: 이지스자산운용이 사옥인 여의도 세우빌딩의 2층을 리모델링하고 미국 그린빌딩위원회(USGBC)의 LEED(Leadership in Energy and Environmental Design) 골드(Gold) 등급 인증을 획득했다고 6일 밝혔다. - expected: `⠴⠺⠀⠴⠠⠠⠇⠑⠑⠙⠐⠣⠠⠇⠂⠙⠻⠩⠊⠏⠀⠔⠀⠠` - - actual: `⠴⠺⠀⠴⠠⠠⠇⠑⠫⠐⠣⠠⠇⠂⠙⠻⠩⠊⠏⠀⠊⠝⠀⠠` + - actual: `⠴⠺⠀⠴⠠⠠⠇⠑⠫⠐⠣⠠⠇⠂⠙⠻⠩⠊⠏⠀⠔⠀⠠⠢` - first differing cell (zero-based): 95 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` @@ -2261,19 +2215,14 @@ Representative `mismatch` samples: ### `pure_allcaps_segment_before_hyphen_and_multi_allcaps_segment_after` -Of the 448 candidates, 212 are the actual `pending_rule_review` subcluster. The other 236 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 448 candidates, 80 are the actual `pending_rule_review` subcluster. The other 368 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. -For this output-signature audit, 222 mismatches were evaluable and 3 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. - -Localized first-difference transitions: - -- `U+2830 ⠰ -> U+2820 ⠠`: 3 +For this output-signature audit, 89 mismatches were evaluable and 0 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. Mismatch primary-class distribution: -- `corpus_suspect`: 8 -- `pending_rule_review`: 212 -- `unsupported_character_review`: 2 +- `corpus_suspect`: 9 +- `pending_rule_review`: 80 Representative `exact` samples: @@ -2289,62 +2238,48 @@ Representative `exact` samples: - expected: `⠣⠯⠐⠎⠀⠨⠝⠨⠍⠊⠥⠈⠗⠘⠂⠈⠿⠇⠉⠵⠀⠈⠍⠁` - actual: `⠣⠯⠐⠎⠀⠨⠝⠨⠍⠊⠥⠈⠗⠘⠂⠈⠿⠇⠉⠵⠀⠈⠍⠁` - current primary/reason: `exact` / `exact` -- `sentence_04.json` #1164: 3일 서울시에 따르면 서울시와 국토교통부는 이러한 내용을 담은 ‘K-UAM(도심항공교통) 그랜드챌린지 실증운행계획’을 이달 중순 발표한다. UAM은 드론 택시처럼 하늘을 이용한 교통수단이다. - - expected: `⠼⠉⠕⠂⠀⠠⠎⠯⠠⠕⠝⠀⠠⠊⠐⠪⠑⠡⠀⠠⠎⠯⠠⠕` - - actual: `⠼⠉⠕⠂⠀⠠⠎⠯⠠⠕⠝⠀⠠⠊⠐⠪⠑⠡⠀⠠⠎⠯⠠⠕` +- `sentence_04.json` #883: 그룹 방탄소년단(BTS) 슈가의 첫 공식 솔로 음반 ‘D-데이’(D-DAY)가 발매 당일 한터차트 기준 107만장이 넘게 팔려나가며 첫날 판매량으로는 K팝 솔로 가수 최고 기록을 경신했다. + - expected: `⠈⠪⠐⠍⠃⠀⠘⠶⠓⠒⠠⠥⠉⠡⠊⠒⠦⠄⠴⠠⠠⠃⠞⠎` + - actual: `⠈⠪⠐⠍⠃⠀⠘⠶⠓⠒⠠⠥⠉⠡⠊⠒⠦⠄⠴⠠⠠⠃⠞⠎` - current primary/reason: `exact` / `exact` -Representative `localized_mismatch` samples: - -- `sentence_02.json` #10282: 보안도 강화됐다. SD-WAN과 전용회선, 초연결교환(HCX)을 바탕으로 한 직접 연결 방식을 통해 회선 보안성을 높여 기업의 설계 데이터와 시험 결과 등 핵심 자료가 유출되지 않도록 했다. - - expected: `⠊⠧⠗⠌⠊⠲⠀⠴⠰⠠⠠⠎⠙⠤⠠⠠⠺⠁⠝⠲⠈⠧⠀⠨` - - actual: `⠊⠧⠗⠌⠊⠲⠀⠴⠠⠠⠎⠙⠤⠠⠠⠺⠁⠝⠲⠈⠧⠀⠨⠾` - - first differing cell (zero-based): 19 - - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_03.json` #15688: 한국미드아마추어골프연맹(KMAGF)이 주최, 주관하고 G-MAX가 공식 후원하는 KMAGF 2023 G-MAX 그린투어 1차전이 지난 25일 부산 기장군 해운대 컨트리클럽에서 열렸다. 2023년을 시작하는 KMAGF 첫 대회다. - - expected: `⠋⠀⠼⠃⠚⠃⠉⠀⠰⠠⠛⠤⠠⠠⠍⠁⠭⠲⠀⠈⠪⠐⠟⠓` - - actual: `⠋⠀⠼⠃⠚⠃⠉⠀⠠⠛⠔⠠⠠⠍⠁⠭⠀⠀⠈⠪⠐⠟⠓⠍` - - first differing cell (zero-based): 93 - - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` - Representative `mismatch` samples: - `sentence_01.json` #149: 탠덤 OLED를 탄성있는 플라스틱 기판에 결합한 것이 LG디스플레이의 차량용 플라스틱(P)-OLED다. 차량용 P-OLED는 LCD 대비 소비전력을 60% 줄이고, 무게는 80%나 저감해 전기차 시대에도 적합한 디스플레이라는 평가다. - expected: `⠮⠐⠣⠠⠪⠓⠕⠁⠴⠐⠣⠠⠏⠐⠜⠤⠠⠠⠕⠇⠫⠲⠊⠲` - - actual: `⠮⠐⠣⠠⠪⠓⠕⠁⠦⠄⠴⠠⠏⠠⠴⠤⠰⠠⠠⠕⠇⠫⠲⠊` + - actual: `⠮⠐⠣⠠⠪⠓⠕⠁⠦⠄⠴⠠⠏⠠⠴⠤⠴⠠⠠⠕⠇⠫⠲⠊` - first differing cell (zero-based): 87 - current primary/reason: `corpus_suspect` / `rule34_roman_indicator_before_opening_parenthesis` -- `sentence_02.json` #344: 한편 이장우 대전시장은 이틀간의 대만 방문을 마무리하고 싱가포르로 이동해 18일까지 Merck(머크) 사, 국립싱가포르대학 바이오연구단, A-STAR 및 바이오폴리스 등 바이오산업 관련 해외 기업과 연구기관을 방문할 예정이다. - - expected: `⠥⠡⠈⠍⠊⠒⠐⠀⠴⠠⠁⠤⠠⠠⠌⠜⠲⠀⠑⠕⠆⠀⠘⠣` - - actual: `⠥⠡⠈⠍⠊⠒⠐⠀⠀⠠⠁⠔⠠⠠⠎⠞⠁⠗⠀⠀⠑⠕⠆⠀` - - first differing cell (zero-based): 122 +- `sentence_02.json` #8478: 논문 제목은 ‘Strong electron-phonon coupling driven charge density wave states in stoichiometric 1T-VS2 crystals’이다. 논문은 화학기상수송법으로 제작된 이황화바나듐(VS2)에서 관찰한 완벽한 결정성을 가진 양자상전이 현상을 다뤘다. + - expected: `⠑⠇⠑⠉⠞⠗⠕⠝⠤⠏⠓⠕⠝⠕⠝⠀⠉⠳⠏⠇⠬⠀⠙⠗` + - actual: `⠑⠇⠑⠉⠞⠗⠕⠝⠔⠏⠓⠕⠝⠕⠝⠀⠉⠳⠏⠇⠬⠀⠙⠗` + - first differing cell (zero-based): 28 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` - `sentence_03.json` #947: BMW코리아 미래재단이 ‘2023 서울안전한마당’에 이동식 에너지 저장소(ESS)인 ‘넥스트 그린 투-고(NEXT GREEN TO-GO)’ 부스를 마련하고 체험형 교육 프로그램을 운영한다. - expected: `⠤⠈⠥⠦⠄⠴⠠⠠⠠⠝⠑⠭⠞⠀⠛⠗⠑⠢⠀⠞⠕⠤⠛⠠` - actual: `⠤⠈⠥⠦⠄⠴⠠⠠⠝⠑⠭⠞⠀⠠⠠⠛⠗⠑⠢⠀⠠⠠⠞⠕` - first differing cell (zero-based): 103 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_04.json` #883: 그룹 방탄소년단(BTS) 슈가의 첫 공식 솔로 음반 ‘D-데이’(D-DAY)가 발매 당일 한터차트 기준 107만장이 넘게 팔려나가며 첫날 판매량으로는 K팝 솔로 가수 최고 기록을 경신했다. - - expected: `⠙⠲⠤⠊⠝⠕⠴⠄⠦⠄⠴⠠⠙⠤⠠⠠⠐⠙⠠⠴⠫⠀⠘⠂` - - actual: `⠙⠲⠤⠊⠝⠕⠴⠄⠴⠐⠣⠠⠙⠤⠠⠠⠐⠙⠐⠜⠲⠫⠀⠘` - - first differing cell (zero-based): 65 +- `sentence_04.json` #6743: 변압기 절연유로 사용되는 발암물질인 폴리염화비페닐(PCBs)도 평균 0.003pg WHO-TEQ/㎥으로, 최근 2년 평균 0.004pg WHO-TEQ/㎥ 대비 감소세를 유지했다. + - expected: `⠓⠕⠤⠠⠠⠞⠑⠟⠸⠌⠍⠘⠼⠉⠪⠐⠥⠐⠀⠰⠽⠈⠵⠀` + - actual: `⠓⠕⠤⠠⠠⠞⠑⠟⠲⠸⠌⠍⠘⠼⠉⠪⠐⠥⠐⠀⠰⠽⠈⠵` + - first differing cell (zero-based): 92 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` ### `roman_hyphenated_word_after_whitespace_following_korean_word` -Of the 361 candidates, 264 are the actual `pending_rule_review` subcluster. The other 97 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 361 candidates, 73 are the actual `pending_rule_review` subcluster. The other 288 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. -For this output-signature audit, 275 mismatches were evaluable and 139 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. +For this output-signature audit, 84 mismatches were evaluable and 6 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. Localized first-difference transitions: -- `U+2834 ⠴ -> U+2800 ⠀`: 139 +- `U+2834 ⠴ -> U+2800 ⠀`: 6 Mismatch primary-class distribution: -- `corpus_suspect`: 6 -- `pending_rule_review`: 264 -- `unsupported_character_review`: 5 +- `corpus_suspect`: 11 +- `pending_rule_review`: 73 Representative `exact` samples: @@ -2352,9 +2287,9 @@ Representative `exact` samples: - expected: `⠈⠧⠚⠁⠈⠕⠠⠯⠨⠻⠘⠥⠓⠿⠠⠟⠘⠍⠧⠀⠈⠗⠟⠨` - actual: `⠈⠧⠚⠁⠈⠕⠠⠯⠨⠻⠘⠥⠓⠿⠠⠟⠘⠍⠧⠀⠈⠗⠟⠨` - current primary/reason: `exact` / `exact` -- `sentence_02.json` #798: 이어 홍성 광천에 위치한 K-POP고등학교 학생과 독일 현지 고등학생들이 함께 비티에스(BTS), 블랙핑크, 에스파 등 유명 아이돌 노래와 춤으로 무대를 화려하게 장식한다. - - expected: `⠕⠎⠀⠚⠿⠠⠻⠀⠈⠧⠶⠰⠾⠝⠀⠍⠗⠰⠕⠚⠒⠀⠴⠠` - - actual: `⠕⠎⠀⠚⠿⠠⠻⠀⠈⠧⠶⠰⠾⠝⠀⠍⠗⠰⠕⠚⠒⠀⠴⠠` +- `sentence_02.json` #344: 한편 이장우 대전시장은 이틀간의 대만 방문을 마무리하고 싱가포르로 이동해 18일까지 Merck(머크) 사, 국립싱가포르대학 바이오연구단, A-STAR 및 바이오폴리스 등 바이오산업 관련 해외 기업과 연구기관을 방문할 예정이다. + - expected: `⠚⠒⠙⠡⠀⠕⠨⠶⠍⠀⠊⠗⠨⠾⠠⠕⠨⠶⠵⠀⠕⠓⠮⠫` + - actual: `⠚⠒⠙⠡⠀⠕⠨⠶⠍⠀⠊⠗⠨⠾⠠⠕⠨⠶⠵⠀⠕⠓⠮⠫` - current primary/reason: `exact` / `exact` - `sentence_03.json` #569: 새울원전 관계자는 “APR 1400의 유럽 수출 노형인 EU-APR은 유럽사업자요건(EUR) 인증을 취득했고, 미국 이외 국가로는 처음으로 미국 원자력규제위원회 설계인증을 받아 안전성과 기술력을 국제적으로 인증받고 있다”고 밝혔다. - expected: `⠠⠗⠯⠏⠒⠨⠾⠀⠈⠧⠒⠈⠌⠨⠉⠵⠀⠦⠴⠠⠠⠁⠏⠗` @@ -2367,68 +2302,56 @@ Representative `exact` samples: Representative `localized_mismatch` samples: -- `sentence_01.json` #567: KBO는 2월 8일부터 제주 서귀포시에서 대한야구소프트볼협회(KBSA)가 선발한 고교 입학 예정 우수선수 30명을 대상으로 Next-Level Training Camp 2차 훈련을 실시할 예정이다. - - expected: `⠊⠗⠇⠶⠪⠐⠥⠀⠴⠠⠝⠑⠭⠞⠤⠠⠇⠑⠧⠑⠇⠀⠠⠞` - - actual: `⠊⠗⠇⠶⠪⠐⠥⠀⠀⠠⠝⠑⠭⠞⠔⠠⠇⠑⠧⠑⠇⠀⠴⠠` - - first differing cell (zero-based): 121 - - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_02.json` #344: 한편 이장우 대전시장은 이틀간의 대만 방문을 마무리하고 싱가포르로 이동해 18일까지 Merck(머크) 사, 국립싱가포르대학 바이오연구단, A-STAR 및 바이오폴리스 등 바이오산업 관련 해외 기업과 연구기관을 방문할 예정이다. - - expected: `⠥⠡⠈⠍⠊⠒⠐⠀⠴⠠⠁⠤⠠⠠⠌⠜⠲⠀⠑⠕⠆⠀⠘⠣` - - actual: `⠥⠡⠈⠍⠊⠒⠐⠀⠀⠠⠁⠔⠠⠠⠎⠞⠁⠗⠀⠀⠑⠕⠆⠀` - - first differing cell (zero-based): 122 - - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_03.json` #3485: GC녹십자는 최근 IVIG-SN 10%의 미국 진출에도 박차를 가하고 있다. 지난 4월 오창공장 실사 결과를 바탕으로 이달 내 미국 식품의약국(FDA)에 품목허가 신청서(BLA) 제출을 준비하고 있다. - - expected: `⠉⠵⠀⠰⠽⠈⠵⠀⠴⠠⠠⠊⠧⠊⠛⠤⠠⠠⠎⠝⠀⠼⠁⠚` - - actual: `⠉⠵⠀⠰⠽⠈⠵⠀⠀⠠⠠⠊⠧⠊⠛⠔⠠⠠⠎⠝⠀⠼⠁⠚` - - first differing cell (zero-based): 20 +- `sentence_02.json` #10082: SK텔레콤이 무선 네트워크 품질 관리 AI(인공지능) 솔루션인 A-STAR(Access-Infra Service for Targeting & Action Recommendation)를 개발해 자사 전국 기지국에 적용했다고 28일 밝혔다. + - expected: `⠥⠂⠐⠍⠠⠡⠟⠀⠴⠠⠁⠤⠠⠠⠌⠜⠐⠣⠠⠁⠒⠑⠎⠎` + - actual: `⠥⠂⠐⠍⠠⠡⠟⠀⠀⠠⠁⠔⠠⠠⠎⠞⠁⠗⠦⠠⠁⠉⠉⠑` + - first differing cell (zero-based): 69 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_04.json` #3969: 동구는 사업 첫해인 지난해 관광객과 시민들의 방문을 유도하기 위해 K-POP 거리·도깨비 골목 등을 체험형 공간으로 꾸미고 골목여행을 위한 여행자의집(ZIP) 등을 새롭게 조성했다. - - expected: `⠈⠕⠀⠍⠗⠚⠗⠀⠴⠠⠅⠤⠠⠠⠏⠕⠏⠲⠀⠈⠎⠐⠕⠐` - - actual: `⠈⠕⠀⠍⠗⠚⠗⠀⠀⠠⠅⠔⠠⠠⠏⠕⠏⠀⠈⠎⠐⠕⠐⠆` - - first differing cell (zero-based): 63 +- `sentence_03.json` #5926: 랩지노믹스는 자체 개발한 암 진단검사 서비스 ‘캔서스캔(CancerSCAN)’을 비롯해 국내 최초로 NGS-NIPT(Non-Invasive Prenatal Test·비침습 산전선별검사) 서비스 ‘맘가드(MomGuard)’를 공급하고 있다. + - expected: `⠀⠰⠽⠰⠥⠐⠥⠀⠴⠠⠠⠝⠛⠎⠤⠠⠠⠝⠊⠏⠞⠦⠄⠴` + - actual: `⠀⠰⠽⠰⠥⠐⠥⠀⠀⠠⠠⠝⠛⠎⠔⠠⠠⠝⠊⠏⠞⠦⠠⠝` + - first differing cell (zero-based): 98 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` Representative `mismatch` samples: - `sentence_01.json` #149: 탠덤 OLED를 탄성있는 플라스틱 기판에 결합한 것이 LG디스플레이의 차량용 플라스틱(P)-OLED다. 차량용 P-OLED는 LCD 대비 소비전력을 60% 줄이고, 무게는 80%나 저감해 전기차 시대에도 적합한 디스플레이라는 평가다. - expected: `⠮⠐⠣⠠⠪⠓⠕⠁⠴⠐⠣⠠⠏⠐⠜⠤⠠⠠⠕⠇⠫⠲⠊⠲` - - actual: `⠮⠐⠣⠠⠪⠓⠕⠁⠦⠄⠴⠠⠏⠠⠴⠤⠰⠠⠠⠕⠇⠫⠲⠊` + - actual: `⠮⠐⠣⠠⠪⠓⠕⠁⠦⠄⠴⠠⠏⠠⠴⠤⠴⠠⠠⠕⠇⠫⠲⠊` - first differing cell (zero-based): 87 - current primary/reason: `corpus_suspect` / `rule34_roman_indicator_before_opening_parenthesis` -- `sentence_02.json` #344: 한편 이장우 대전시장은 이틀간의 대만 방문을 마무리하고 싱가포르로 이동해 18일까지 Merck(머크) 사, 국립싱가포르대학 바이오연구단, A-STAR 및 바이오폴리스 등 바이오산업 관련 해외 기업과 연구기관을 방문할 예정이다. - - expected: `⠥⠡⠈⠍⠊⠒⠐⠀⠴⠠⠁⠤⠠⠠⠌⠜⠲⠀⠑⠕⠆⠀⠘⠣` - - actual: `⠥⠡⠈⠍⠊⠒⠐⠀⠀⠠⠁⠔⠠⠠⠎⠞⠁⠗⠀⠀⠑⠕⠆⠀` - - first differing cell (zero-based): 122 - - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_03.json` #711: 비즈니스 프로젝터 EB-L260F는 최대 4700루멘(lm)의 선명한 밝기, 고해상도(Full HD) 해상도로 밝은 회의실이나 교실에서도 밝은 화면을 자랑한다. 이 제품은 중형 강당이나 미팅룸에서 사용하기 적합하다. - - expected: `⠎⠀⠴⠠⠠⠑⠃⠤⠠⠇⠼⠃⠋⠚⠠⠋⠲⠉⠵⠀⠰⠽⠊⠗` - - actual: `⠎⠀⠴⠠⠠⠑⠃⠤⠰⠠⠇⠼⠃⠋⠚⠠⠋⠲⠉⠵⠀⠰⠽⠊` - - first differing cell (zero-based): 25 - - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_04.json` #1313: 정부가 지상화냐 지하화냐를 두고 1년 넘게 논란이 이어졌던 수도권광역급행철도(GTX)-C 노선 창동역 구간을 지하화하기로 최종 결정했다. 이에 따라 GTX-C 노선의 연내 착공 가능성이 높아졌다. - - expected: `⠃⠚⠗⠶⠰⠞⠊⠥⠦⠄⠴⠠⠠⠛⠞⠭⠠⠴⠤⠴⠠⠉⠲⠀` - - actual: `⠃⠚⠗⠶⠰⠞⠊⠥⠀⠀⠦⠠⠠⠛⠞⠭⠴⠔⠠⠉⠀⠉⠥⠠` - - first differing cell (zero-based): 79 +- `sentence_02.json` #420: 대전대학교(총장 남상호) LINC3.0사업단(단장 이영환)은 지난 6일 특화 분야 ICC(기업협업) 참여학과 중심의 기술개발 프로그램인 ‘2023 혜화 All-SET 기술사업화’ 운영을 위한 선정평가를 진행했다고 7일 밝혔다. + - expected: `⠥⠠⠴⠀⠴⠠⠠⠇⠔⠉⠼⠉⠲⠚⠇⠎⠃⠊⠒⠦⠄⠊⠒⠨` + - actual: `⠥⠠⠴⠀⠴⠠⠠⠇⠊⠝⠉⠼⠉⠲⠚⠇⠎⠃⠊⠒⠦⠄⠊⠒` + - first differing cell (zero-based): 30 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_03.json` #3036: 스마트 로봇기업 RP가 일본 오웰 사와 R-BOT(알봇)의 일본 사업 진출을 위한 업무협약(MOU)을 체결했다고 23일 밝혔다. + - expected: `⠜⠁⠦⠄⠴⠠⠠⠍⠕⠥⠠⠴⠮⠀⠰⠝⠈⠳⠚⠗⠌⠊⠈⠥` + - actual: `⠜⠁⠦⠄⠴⠠⠠⠍⠳⠠⠴⠮⠀⠰⠝⠈⠳⠚⠗⠌⠊⠈⠥⠀` + - first differing cell (zero-based): 92 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_04.json` #1528: 서부발전은 18일(현지시간) 오만에서 오만수전력조달공사(OPWP)가 주최한 ‘오만 마나 500㎿ 태양광발전 계약 서명식’에 파트너사인 프랑스 EDF-R과 함께 참석했다고 밝혔다. + - expected: `⠣⠶⠠⠪⠀⠴⠠⠠⠑⠙⠋⠤⠰⠠⠗⠲⠈⠧⠀⠚⠢⠠⠈⠝` + - actual: `⠣⠶⠠⠪⠀⠴⠠⠠⠫⠋⠤⠰⠠⠗⠲⠈⠧⠀⠚⠢⠠⠈⠝⠀` + - first differing cell (zero-based): 139 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` ### `roman_parenthetical_headword_after_whitespace_following_korean_word` -Of the 4695 candidates, 989 are the actual `pending_rule_review` subcluster. The other 3706 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 4695 candidates, 640 are the actual `pending_rule_review` subcluster. The other 4055 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. -For this output-signature audit, 1085 mismatches were evaluable and 71 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. +For this output-signature audit, 750 mismatches were evaluable and 4 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. Localized first-difference transitions: -- `U+2834 ⠴ -> U+2800 ⠀`: 67 -- `U+2834 ⠴ -> U+2830 ⠰`: 3 -- `U+2820 ⠠ -> U+2834 ⠴`: 1 +- `U+2820 ⠠ -> U+2834 ⠴`: 2 +- `U+2834 ⠴ -> U+2800 ⠀`: 2 Mismatch primary-class distribution: -- `comparison_method`: 38 -- `corpus_suspect`: 43 -- `pending_rule_review`: 989 -- `unsupported_character_review`: 15 +- `corpus_suspect`: 109 +- `pending_rule_review`: 640 +- `unsupported_character_review`: 1 Representative `exact` samples: @@ -2451,153 +2374,144 @@ Representative `exact` samples: Representative `localized_mismatch` samples: -- `sentence_01.json` #202: 제이스코홀딩스는 필리핀 니켈 광산사업을 공동 추진중인 EVM(EV Mining &Development)이 광산지질국(MGB)에 4천700헥타르(약 1천400만평)에 대한 탐사허가(EP)를 신청했다고 9일 밝혔다. - - expected: `⠍⠨⠟⠨⠍⠶⠟⠀⠴⠠⠠⠑⠧⠍⠐⠣⠠⠠⠑⠧⠀⠠⠍⠔` - - actual: `⠍⠨⠟⠨⠍⠶⠟⠀⠀⠠⠠⠑⠧⠍⠦⠠⠠⠑⠧⠀⠴⠠⠍⠔` - - first differing cell (zero-based): 56 - - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_02.json` #885: 지난 13일 온라인 컨퍼런스로 진행된 밸류데이에서 KT&G는 중장기(2024년~2026년) 주주환원 계획을 공개하고, 3대 핵심사업인 NGP(Next Generation Products)·글로벌CC(궐련담배)·건기식(건강기능식품) 중심의 미래비전 이행 경과를 공유했다. - - expected: `⠠⠕⠢⠇⠎⠃⠟⠀⠴⠠⠠⠝⠛⠏⠐⠣⠠⠝⠑⠭⠞⠀⠠⠛` - - actual: `⠠⠕⠢⠇⠎⠃⠟⠀⠀⠠⠠⠝⠛⠏⠦⠠⠝⠑⠭⠞⠀⠴⠠⠛` - - first differing cell (zero-based): 134 +- `sentence_01.json` #24409: 한편 경북은 배터리 규제자유특구(2019)를 시작으로 이차전지 재사용·재활용 산업을 선점했고, 이차전지 혁신거버넌스 출범(2022.11), 이차전지 산업생태계 구축 MOU(2023.2) 등 각종 국가정책사업을 다수 유치해 이차전지 산업생태계를 완성해가고 있다. + - expected: `⠌⠀⠈⠍⠰⠍⠁⠀⠴⠠⠠⠍⠕⠥⠦⠄⠼⠃⠚⠃⠉⠲⠃⠠` + - actual: `⠌⠀⠈⠍⠰⠍⠁⠀⠀⠠⠠⠍⠕⠥⠦⠼⠃⠚⠃⠉⠲⠃⠴⠀` + - first differing cell (zero-based): 159 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_03.json` #381: 인포테인먼트 시스템 ccNC(connected car Navigation Cockpit)를 최초 탑재하고 무선 소프트웨어 업데이트(OTA) 대상 범위를 주요 편의 기능까지 대폭 확대했다. - - expected: `⠠⠕⠠⠪⠓⠝⠢⠀⠴⠉⠉⠠⠠⠝⠉⠐⠣⠒⠝⠑⠉⠞⠫⠀` - - actual: `⠠⠕⠠⠪⠓⠝⠢⠀⠀⠉⠉⠠⠠⠝⠉⠦⠉⠕⠝⠝⠑⠉⠞⠑` - - first differing cell (zero-based): 19 +- `sentence_02.json` #24207: 3일 업계에 따르면 삼성전자는 올 하반기 출시할 갤럭시S23 FE(팬에디션)에 자체 개발한 AP인 엑시노스2200을 투입할 예정이다. + - expected: `⠕⠴⠠⠎⠼⠃⠉⠀⠠⠠⠋⠑⠦⠄⠙⠗⠒⠝⠊⠕⠠⠡⠠⠴` + - actual: `⠕⠴⠠⠎⠼⠃⠉⠀⠴⠠⠠⠋⠑⠦⠄⠙⠗⠒⠝⠊⠕⠠⠡⠠` + - first differing cell (zero-based): 58 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_04.json` #1075: 접속 방법은 ‘안심전세포털’ 및 국토부 홈페이지에 안내돼 있다. 줌 URL(ID : 393 490 1984)을 입력하거나 주택도시보증공사(HUG) 유튜브로 접속하면 된다. - - expected: `⠌⠊⠲⠀⠨⠍⠢⠀⠴⠠⠠⠥⠗⠇⠐⠣⠠⠠⠊⠙⠀⠒⠀⠼` - - actual: `⠌⠊⠲⠀⠨⠍⠢⠀⠀⠠⠠⠥⠗⠇⠦⠠⠠⠊⠙⠀⠐⠂⠀⠼` - - first differing cell (zero-based): 70 +- `sentence_04.json` #5981: 통합번호 ‘109’는 긴급 구급·구조 번호인 ‘119’와 같이 자살이 ‘구조가 필요한 긴급한 상황’이라는 인식을 줄 수 있고, ‘한 명의 생명(1)도, 자살 zero(0), 구하자(9)’라는 의미를 포함하고 있다. + - expected: `⠊⠥⠐⠀⠨⠇⠂⠀⠴⠵⠻⠕⠦⠄⠼⠚⠠⠴⠐⠀⠈⠍⠚⠨` + - actual: `⠊⠥⠐⠀⠨⠇⠂⠀⠀⠵⠑⠗⠕⠦⠼⠚⠴⠐⠀⠈⠍⠚⠨⠦` + - first differing cell (zero-based): 145 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` Representative `mismatch` samples: - `sentence_01.json` #191: 소프트웨어 측면에서는 스마트 TV 독자 운영체제 웹(web)OS의 진화를 앞세워 맞춤형 고객경험과 CDX(Cross Device eXperience) 경험을 강화한다. - expected: `⠰⠝⠨⠝⠀⠏⠗⠃⠴⠐⠣⠺⠑⠃⠐⠜⠠⠠⠕⠎⠲⠺⠀⠨` - - actual: `⠰⠝⠨⠝⠀⠏⠗⠃⠦⠄⠴⠺⠑⠃⠠⠴⠰⠠⠠⠕⠎⠲⠺⠀` + - actual: `⠰⠝⠨⠝⠀⠏⠗⠃⠦⠄⠴⠺⠑⠃⠠⠴⠴⠠⠠⠕⠎⠲⠺⠀` - first differing cell (zero-based): 48 - current primary/reason: `corpus_suspect` / `rule34_roman_indicator_before_opening_parenthesis` - `sentence_02.json` #14: 한국타이어앤테크놀로지㈜(대표이사 이수일, 이하 한국타이어)가 국제자동차연맹(FIA)이 주관하는 ‘FIA 주니어 ERC(FIA Junior ERC, 이하 주니어 ERC)’ 대회에 레이싱 타이어를 독점 공급한다. - - expected: `⠚⠒⠈⠍⠁⠓⠣⠕⠎⠗⠒⠓⠝⠋⠪⠉⠥⠂⠐⠥⠨⠕⠦⠄` - - actual: `` - - error: `Invalid symbol character` - - first differing cell (zero-based): 0 - - current primary/reason: `unsupported_character_review` / `unsupported_character_review` + - expected: `⠍⠉⠕⠎⠀⠴⠠⠠⠑⠗⠉⠦⠄⠴⠠⠠⠋⠊⠁⠀⠠⠚⠥⠝` + - actual: `⠍⠉⠕⠎⠀⠴⠠⠠⠻⠉⠦⠄⠴⠠⠠⠋⠊⠁⠀⠠⠚⠥⠝⠊` + - first differing cell (zero-based): 114 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` - `sentence_03.json` #292: GH 주거 분야 전문인력과 HUG(주택도시보증공사), 변호사, 법무사 등 부동산·금융 전문인력이 상주하며 전세사기 피해자에게 부동산 법률, 긴급 금융지원, 주거지원 등 종합적인 맞춤형 상담을 제공한다. - expected: `⠴⠠⠠⠛⠓⠲⠀⠨⠍⠈⠎⠀⠘⠛⠜⠀⠨⠾⠑⠛⠟⠐⠱⠁` - actual: `⠴⠠⠠⠣⠲⠀⠨⠍⠈⠎⠀⠘⠛⠜⠀⠨⠾⠑⠛⠟⠐⠱⠁⠈` - first differing cell (zero-based): 3 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` - `sentence_04.json` #191: SK온은 23일 서울 종로구 SK서린사옥에서 에코프로머티리얼즈, 중국의 GEM(거린메이)과 전구체 생산을 위한 3자 합작법인인 지이엠코리아뉴에너지머티리얼즈㈜(이하 지이엠코리아) 설립 양해각서(MOU)를 체결했다고 밝혔다. - - expected: `⠴⠠⠠⠎⠅⠲⠷⠵⠀⠼⠃⠉⠕⠂⠀⠠⠎⠯⠀⠨⠿⠐⠥⠈` - - actual: `` - - error: `Invalid symbol character` - - first differing cell (zero-based): 0 - - current primary/reason: `unsupported_character_review` / `unsupported_character_review` + - expected: `⠠⠎⠦⠄⠴⠠⠠⠍⠕⠥⠠⠴⠐⠮⠀⠰⠝⠈⠳⠚⠗⠌⠊⠈` + - actual: `⠠⠎⠦⠄⠴⠠⠠⠍⠳⠠⠴⠐⠮⠀⠰⠝⠈⠳⠚⠗⠌⠊⠈⠥` + - first differing cell (zero-based): 191 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` ### `roman_run_after_whitespace_following_closed_roman_enclosure` -Of the 1093 candidates, 611 are the actual `pending_rule_review` subcluster. The other 482 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 1093 candidates, 163 are the actual `pending_rule_review` subcluster. The other 930 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. -For this output-signature audit, 949 mismatches were evaluable and 260 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. +For this output-signature audit, 513 mismatches were evaluable and 5 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. Localized first-difference transitions: -- `U+2834 ⠴ -> U+2830 ⠰`: 256 +- `U+2811 ⠑ -> U+282B ⠫`: 1 +- `U+2811 ⠑ -> U+283B ⠻`: 1 - `U+2815 ⠕ -> U+2837 ⠷`: 1 -- `U+2820 ⠠ -> U+2805 ⠅`: 1 - `U+2820 ⠠ -> U+280E ⠎`: 1 -- `U+2830 ⠰ -> U+2820 ⠠`: 1 +- `U+2825 ⠥ -> U+2820 ⠠`: 1 Mismatch primary-class distribution: -- `corpus_suspect`: 331 -- `pending_rule_review`: 611 -- `unsupported_character_review`: 7 +- `corpus_suspect`: 349 +- `pending_rule_review`: 163 +- `unsupported_character_review`: 1 Representative `exact` samples: -- `sentence_01.json` #2785: 미래에셋자산운용은 미국 상장지수펀드(ETF) 운용 자회사 글로벌 엑스(Global X)가 ‘Global X 프롭테크(PropTech) ETF’를 뉴욕증권거래소에 신규 상장했다고 18일 밝혔다. - - expected: `⠑⠕⠐⠗⠝⠠⠝⠄⠨⠇⠒⠛⠬⠶⠵⠀⠑⠕⠈⠍⠁⠀⠇⠶` - - actual: `⠑⠕⠐⠗⠝⠠⠝⠄⠨⠇⠒⠛⠬⠶⠵⠀⠑⠕⠈⠍⠁⠀⠇⠶` +- `sentence_01.json` #337: ‘아리송(ARISONG)’, ‘Boyfriend’에 이어 ‘오로라’에도 인기 안무가 리정이 안무 메이킹에 참여해 기대를 모은다. 리정과 7인 7색 매력의 시그니처가 만나 어떤 색다른 퍼포먼스를 선사할지 이목이 집중된다. + - expected: `⠠⠦⠣⠐⠕⠠⠿⠦⠄⠴⠠⠠⠜⠊⠎⠰⠛⠠⠴⠴⠄⠐⠀⠠` + - actual: `⠠⠦⠣⠐⠕⠠⠿⠦⠄⠴⠠⠠⠜⠊⠎⠰⠛⠠⠴⠴⠄⠐⠀⠠` - current primary/reason: `exact` / `exact` -- `sentence_02.json` #1282: 이를 위해 중국 전기차 업체 비야디(BYD), BMW, 스텔란티스 등 80여개 완성차 고객사 최고경영자(CEO)와 주요 임원 약 200명을 초청해 프라이빗 부스에서 수주 미팅을 계획했다. - - expected: `⠕⠐⠮⠀⠍⠗⠚⠗⠀⠨⠍⠶⠈⠍⠁⠀⠨⠾⠈⠕⠰⠣⠀⠎` - - actual: `⠕⠐⠮⠀⠍⠗⠚⠗⠀⠨⠍⠶⠈⠍⠁⠀⠨⠾⠈⠕⠰⠣⠀⠎` +- `sentence_02.json` #461: 주요 연수 내용은 교육현장의 요구를 적극적으로 반영해 인공지능(AI), ChatGTP, 영어그림책 등을 활용한 다양한 교수학습 방법, 세계시민교육 실천사례, 영미권 원어민 강사와의 협력수업 방법 등으로 구성했다. + - expected: `⠨⠍⠬⠀⠡⠠⠍⠀⠉⠗⠬⠶⠵⠀⠈⠬⠩⠁⠚⠡⠨⠶⠺⠀` + - actual: `⠨⠍⠬⠀⠡⠠⠍⠀⠉⠗⠬⠶⠵⠀⠈⠬⠩⠁⠚⠡⠨⠶⠺⠀` - current primary/reason: `exact` / `exact` - `sentence_03.json` #46: 삼성자산운용은 인도 시장에 투자하는 ‘KODEX 인도 Nifty50’, ‘KODEX 인도 Nifty50 레버리지’ 상장지수펀드(ETF) 2종을 21일 상장한다고 밝혔다. - expected: `⠇⠢⠠⠻⠨⠇⠒⠛⠬⠶⠵⠀⠟⠊⠥⠀⠠⠕⠨⠶⠝⠀⠓⠍` - actual: `⠇⠢⠠⠻⠨⠇⠒⠛⠬⠶⠵⠀⠟⠊⠥⠀⠠⠕⠨⠶⠝⠀⠓⠍` - current primary/reason: `exact` / `exact` -- `sentence_04.json` #2333: 삼성전자가 2023년 네오(Neo) TV 신제품 전 모델과 스마트 모니터 등에 색약자를 위한 ‘씨컬러스 모드’를 기본 기능으로 탑재한다고 25일 밝혔다. - - expected: `⠇⠢⠠⠻⠨⠾⠨⠫⠀⠼⠃⠚⠃⠉⠀⠉⠡⠀⠉⠝⠥⠦⠄⠴` - - actual: `⠇⠢⠠⠻⠨⠾⠨⠫⠀⠼⠃⠚⠃⠉⠀⠉⠡⠀⠉⠝⠥⠦⠄⠴` +- `sentence_04.json` #298: HMM은 카타르 하마드에서 당초 수량보다 16개를 추가한 586개의 임시주택 컨테이너를 다목적선(MPV) ‘HMM 울산호’에 선적했다. 이 선박은 27일 출항, 다음달 10일경 튀르키예 이스켄데룬에 도착할 예정이다. + - expected: `⠴⠠⠠⠓⠍⠍⠲⠵⠀⠋⠓⠐⠪⠀⠚⠑⠊⠪⠝⠠⠎⠀⠊⠶` + - actual: `⠴⠠⠠⠓⠍⠍⠲⠵⠀⠋⠓⠐⠪⠀⠚⠑⠊⠪⠝⠠⠎⠀⠊⠶` - current primary/reason: `exact` / `exact` Representative `localized_mismatch` samples: -- `sentence_01.json` #337: ‘아리송(ARISONG)’, ‘Boyfriend’에 이어 ‘오로라’에도 인기 안무가 리정이 안무 메이킹에 참여해 기대를 모은다. 리정과 7인 7색 매력의 시그니처가 만나 어떤 색다른 퍼포먼스를 선사할지 이목이 집중된다. - - expected: `⠠⠴⠴⠄⠐⠀⠠⠦⠴⠠⠃⠕⠽⠋⠗⠊⠢⠙⠴⠄⠝⠀⠕⠎` - - actual: `⠠⠴⠴⠄⠐⠀⠠⠦⠰⠠⠃⠕⠽⠋⠗⠊⠢⠙⠴⠄⠝⠀⠕⠎` - - first differing cell (zero-based): 25 +- `sentence_01.json` #17229: 김태현은 연령별 대표팀을 거치며 성장한 엘리트다. 김태현은 U-17, U-20, U-23 대표팀을 거쳤으며 지난 2020년에는 김학범 감독의 지도 속 아시아축구연맹(AFC) U-23 아시안컵에도 나섰다. + - expected: `⠓⠗⠚⠡⠵⠀⠴⠠⠥⠤⠼⠁⠛⠂⠀⠰⠠⠥⠤⠼⠃⠚⠂⠀` + - actual: `⠓⠗⠚⠡⠵⠀⠴⠠⠠⠠⠥⠤⠼⠁⠛⠐⠀⠴⠥⠤⠼⠃⠚⠐` + - first differing cell (zero-based): 58 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_02.json` #461: 주요 연수 내용은 교육현장의 요구를 적극적으로 반영해 인공지능(AI), ChatGTP, 영어그림책 등을 활용한 다양한 교수학습 방법, 세계시민교육 실천사례, 영미권 원어민 강사와의 협력수업 방법 등으로 구성했다. - - expected: `⠠⠠⠁⠊⠠⠴⠐⠀⠴⠠⠡⠁⠞⠠⠠⠛⠞⠏⠐⠀⠻⠎⠈⠪` - - actual: `⠠⠠⠁⠊⠠⠴⠐⠀⠰⠠⠡⠁⠞⠠⠠⠛⠞⠏⠐⠀⠻⠎⠈⠪` - - first differing cell (zero-based): 66 +- `sentence_02.json` #15163: 삼성전자는 HDR10+ GAMING 기술을 지난해 이후에 출시된 7시리즈 이상의 오디세이 게이밍 모니터와 120Hz 이상을 지원하는 QLED 70·80시리즈, OLED, 네오(Neo) QLED 등 TV에 적용했다. + - expected: `⠉⠵⠀⠴⠠⠠⠟⠇⠑⠙⠀⠼⠛⠚⠐⠆⠼⠓⠚⠠⠕⠐⠕⠨` + - actual: `⠉⠵⠀⠴⠠⠠⠟⠇⠫⠀⠼⠛⠚⠐⠆⠼⠓⠚⠠⠕⠐⠕⠨⠪` + - first differing cell (zero-based): 120 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_03.json` #57: ACE 미국30년국채액티브(H) ETF는 한국투자신탁운용이 첫 번째로 상장한 월배당형 상품이다. 예상 배당률은 연 3%다. 첫 번째 분배금은 5월 초 지급될 예정이다. - - expected: `⠦⠄⠴⠠⠓⠠⠴⠀⠴⠠⠠⠑⠞⠋⠲⠉⠵⠀⠚⠒⠈⠍⠁⠓` - - actual: `⠦⠄⠴⠠⠓⠠⠴⠀⠰⠠⠠⠑⠞⠋⠲⠉⠵⠀⠚⠒⠈⠍⠁⠓` - - first differing cell (zero-based): 38 +- `sentence_03.json` #4022: 예탁원은 13일 “KOFR OIS(오버나이트 인덱스 스와프) 시장 형성에 필요한 KOFR OIS 추정 금리커브와 KOFR 현물상품 출시를 위해 필요한 텀(Term) KOFR 개발을 추진한다”고 밝혔다. + - expected: `⠕⠂⠀⠦⠴⠠⠠⠅⠕⠋⠗⠀⠠⠠⠕⠊⠎⠦⠄⠥⠘⠎⠉⠣` + - actual: `⠕⠂⠀⠦⠴⠠⠠⠅⠷⠗⠀⠠⠠⠕⠊⠎⠦⠄⠥⠘⠎⠉⠣⠕` + - first differing cell (zero-based): 18 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_04.json` #298: HMM은 카타르 하마드에서 당초 수량보다 16개를 추가한 586개의 임시주택 컨테이너를 다목적선(MPV) ‘HMM 울산호’에 선적했다. 이 선박은 27일 출항, 다음달 10일경 튀르키예 이스켄데룬에 도착할 예정이다. - - expected: `⠍⠏⠧⠠⠴⠀⠠⠦⠴⠠⠠⠓⠍⠍⠲⠀⠯⠇⠒⠚⠥⠴⠄⠝` - - actual: `⠍⠏⠧⠠⠴⠀⠠⠦⠰⠠⠠⠓⠍⠍⠲⠀⠯⠇⠒⠚⠥⠴⠄⠝` - - first differing cell (zero-based): 98 +- `sentence_04.json` #5928: 이번 평가에서 현대건설은 ‘유럽연합(EU) 택소노미 기반 지속가능매출산정’ ‘SBTi 승인’ ‘생물다양성 위험성 평가 실시’ ‘임직원 복지제도 확대’ 등을 우수 성과로 인정받았다. + - expected: `⠻⠴⠄⠀⠠⠦⠴⠠⠠⠎⠃⠞⠠⠄⠊⠲⠀⠠⠪⠶⠟⠴⠄⠀` + - actual: `⠻⠴⠄⠀⠠⠦⠴⠠⠎⠠⠃⠠⠞⠊⠲⠀⠠⠪⠶⠟⠴⠄⠀⠠` + - first differing cell (zero-based): 78 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` Representative `mismatch` samples: - `sentence_01.json` #14: 새롭게 선보인 DRX 브랜딩과 팀 컬러는 2023년 1월 1일부터 DRX의 온오프라인 콘텐츠, 엠디(MD), SNS, 유튜브 등 모든 제작물에 적용될 예정이다. - expected: `⠰⠪⠐⠀⠝⠢⠊⠕⠴⠐⠣⠠⠠⠍⠙⠐⠜⠂⠀⠠⠠⠎⠝⠎` - - actual: `⠰⠪⠐⠀⠝⠢⠊⠕⠦⠄⠴⠠⠠⠍⠙⠠⠴⠐⠀⠰⠠⠠⠎⠝` + - actual: `⠰⠪⠐⠀⠝⠢⠊⠕⠦⠄⠴⠠⠠⠍⠙⠠⠴⠐⠀⠴⠠⠠⠎⠝` - first differing cell (zero-based): 97 - current primary/reason: `corpus_suspect` / `rule34_roman_indicator_before_opening_parenthesis` - `sentence_02.json` #78: 마지막으로 신규 회원사인 ㈜그레비티(대표 최항주)에 대한 소개와 서중석 교수의 발제로 ‘글쓰기에 필요한 다양한 생성형(Generative) AI Searcher’에 대한 토론이 진행됐다. - - expected: `⠑⠨⠕⠑⠁⠪⠐⠥⠀⠠⠟⠈⠩⠀⠚⠽⠏⠒⠇⠟⠀⠦⠄⠨` - - actual: `` - - error: `Invalid symbol character` - - first differing cell (zero-based): 0 - - current primary/reason: `unsupported_character_review` / `unsupported_character_review` -- `sentence_03.json` #57: ACE 미국30년국채액티브(H) ETF는 한국투자신탁운용이 첫 번째로 상장한 월배당형 상품이다. 예상 배당률은 연 3%다. 첫 번째 분배금은 5월 초 지급될 예정이다. - - expected: `⠦⠄⠴⠠⠓⠠⠴⠀⠴⠠⠠⠑⠞⠋⠲⠉⠵⠀⠚⠒⠈⠍⠁⠓` - - actual: `⠦⠄⠴⠠⠓⠠⠴⠀⠰⠠⠠⠑⠞⠋⠲⠉⠵⠀⠚⠒⠈⠍⠁⠓` - - first differing cell (zero-based): 38 + - expected: `⠀⠠⠗⠶⠠⠻⠚⠻⠴⠐⠣⠠⠛⠢⠻⠁⠞⠊⠧⠑⠐⠜⠀⠠` + - actual: `⠀⠠⠗⠶⠠⠻⠚⠻⠦⠄⠴⠠⠛⠢⠻⠁⠞⠊⠧⠑⠠⠴⠀⠴` + - first differing cell (zero-based): 116 + - current primary/reason: `corpus_suspect` / `rule34_roman_indicator_before_opening_parenthesis` +- `sentence_03.json` #286: 캘리포니아 레드우드 시티의 재무고문인 로렌스 폰은 “텍사스 ETF의 이름을 주를 상징하는 ‘론스타(Lone Star) ETF’ 또는 ‘리멤버 알라모(Alamo) ETF’로 명명하는 것도 괜찮을 것”이라고 말했다. + - expected: `⠀⠠⠦⠐⠷⠠⠪⠓⠴⠐⠣⠠⠇⠐⠕⠀⠠⠌⠜⠐⠜⠀⠠⠠` + - actual: `⠀⠠⠦⠐⠷⠠⠪⠓⠦⠄⠴⠠⠇⠐⠕⠀⠠⠌⠜⠠⠴⠀⠴⠠` + - first differing cell (zero-based): 91 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` - `sentence_04.json` #62: LG디스플레이가 자사의 유기발광다이오드(OLED) TV 패널이 글로벌 친환경 인증기관인 카본 트러스트(Carbon Trust)로 부터 탄소발자국 인증을 획득했다고 19일 밝혔다. - expected: `⠧⠶⠊⠣⠕⠥⠊⠪⠴⠐⠣⠠⠠⠕⠇⠫⠐⠜⠀⠠⠠⠞⠧⠲` - - actual: `⠧⠶⠊⠣⠕⠥⠊⠪⠦⠄⠴⠠⠠⠕⠇⠫⠠⠴⠀⠰⠠⠠⠞⠧` + - actual: `⠧⠶⠊⠣⠕⠥⠊⠪⠦⠄⠴⠠⠠⠕⠇⠫⠠⠴⠀⠴⠠⠠⠞⠧` - first differing cell (zero-based): 35 - current primary/reason: `corpus_suspect` / `rule34_roman_indicator_before_opening_parenthesis` ### `roman_run_immediately_before_attached_middle_dot_boundary` -Of the 577 candidates, 565 are the actual `pending_rule_review` subcluster. The other 12 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 577 candidates, 570 are the actual `pending_rule_review` subcluster. The other 7 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. -For this output-signature audit, 577 mismatches were evaluable and 451 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. +For this output-signature audit, 577 mismatches were evaluable and 516 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. Localized first-difference transitions: -- `U+2810 ⠐ -> U+2832 ⠲`: 451 +- `U+2810 ⠐ -> U+2832 ⠲`: 516 Mismatch primary-class distribution: - `corpus_suspect`: 7 -- `pending_rule_review`: 565 -- `unsupported_character_review`: 5 +- `pending_rule_review`: 570 Representative `localized_mismatch` samples: @@ -2647,24 +2561,20 @@ Representative `mismatch` samples: ### `rule69_ascii_unit_before_terminator_skipping_symbol` -Of the 440 candidates, 91 are the actual `pending_rule_review` subcluster. The other 349 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 440 candidates, 53 are the actual `pending_rule_review` subcluster. The other 387 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. -For this output-signature audit, 96 mismatches were evaluable and 16 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. +For this output-signature audit, 55 mismatches were evaluable and 3 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. Localized first-difference transitions: -- `U+2802 ⠂ -> U+2810 ⠐`: 6 -- `U+2814 ⠔ -> U+2824 ⠤`: 4 -- `U+2834 ⠴ -> U+280D ⠍`: 3 - `U+2810 ⠐ -> U+2802 ⠂`: 1 -- `U+2820 ⠠ -> U+281B ⠛`: 1 +- `U+2824 ⠤ -> U+2814 ⠔`: 1 +- `U+283C ⠼ -> U+2800 ⠀`: 1 Mismatch primary-class distribution: -- `comparison_method`: 3 -- `corpus_suspect`: 1 -- `pending_rule_review`: 91 -- `unsupported_character_review`: 1 +- `corpus_suspect`: 2 +- `pending_rule_review`: 53 Representative `exact` samples: @@ -2687,38 +2597,33 @@ Representative `exact` samples: Representative `localized_mismatch` samples: -- `sentence_01.json` #15758: 22일(한국시간) 영국 ‘90min’에 따르면 베일은 ‘BT스포츠’와 인터뷰에서 오는 7월부터 미국 메이저리그사커(MLS)에 뛰어들 메시에게 조언을 해달라는 말에 “좀 더 여유가 있을 것”이라고 밝혔다. - - expected: `⠍⠁⠀⠠⠦⠼⠊⠚⠴⠍⠔⠴⠄⠝⠀⠠⠊⠐⠪⠑⠡⠀⠘⠝` - - actual: `⠍⠁⠀⠠⠦⠼⠊⠚⠍⠔⠴⠄⠝⠀⠠⠊⠐⠪⠑⠡⠀⠘⠝⠕` - - first differing cell (zero-based): 29 - - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_02.json` #431: 173cm, 68kg의 다소 왜소한 체격이지만 스크럼 하프(SH) 포지션을 맡아 포워드와 백스 사이에서 날카로운 볼 연결과 공격방향을 결정하는 타고난 판단력이 강점이다. - - expected: `⠼⠁⠛⠉⠴⠉⠍⠂⠀⠼⠋⠓⠅⠛⠲⠺⠀⠊⠠⠥⠀⠧⠗⠠` - - actual: `⠼⠁⠛⠉⠴⠉⠍⠐⠀⠼⠋⠓⠴⠅⠛⠲⠺⠀⠊⠠⠥⠀⠧⠗` - - first differing cell (zero-based): 7 +- `sentence_02.json` #13659: 위례공원 맨발 황톳길(1200m)은 7월 말 개장하며, 중앙공원 맨발 황톳길( 1200m)은 8월 초 일부 구간(500m)을 우선 개장한 뒤 9월 중 모두 개통한다. + - expected: `⠓⠥⠄⠈⠕⠂⠦⠄⠼⠁⠃⠚⠚⠴⠍⠠⠴⠵⠀⠼⠓⠏⠂⠀` + - actual: `⠓⠥⠄⠈⠕⠂⠦⠄⠀⠼⠁⠃⠚⠚⠴⠍⠠⠴⠵⠀⠼⠓⠏⠂` + - first differing cell (zero-based): 81 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_03.json` #17113: 6피트 9인치, 210파운드(206cm, 95kg)의 신체 사이즈를 지닌 폴 리드는 7피트 2인치(218cm)에 달하는 긴 윙스팬과 더불어 수비력을 인정받은 빅맨이다. - - expected: `⠄⠼⠃⠚⠋⠴⠉⠍⠂⠀⠼⠊⠑⠅⠛⠠⠴⠺⠀⠠⠟⠰⠝⠀` - - actual: `⠄⠼⠃⠚⠋⠴⠉⠍⠐⠀⠼⠊⠑⠴⠅⠛⠠⠴⠺⠀⠠⠟⠰⠝` - - first differing cell (zero-based): 34 +- `sentence_03.json` #22445: 또한 라이트급(-70kg) 토너먼트에서는 ‘한국 귀화 파이터’ 난딘에르덴(남양주 팀피니쉬)과 ‘슈토 환태평양 챔피언’ 데바나 슈타로(COBRA KAI), 아르투르 솔로비예프(MFP)와 맥스 더 바디(BRAVE GYM)가 4강전에 나선다. + - expected: `⠕⠓⠪⠈⠪⠃⠦⠄⠤⠼⠛⠚⠴⠅⠛⠠⠴⠀⠓⠥⠉⠎⠑⠾` + - actual: `⠕⠓⠪⠈⠪⠃⠦⠄⠔⠼⠛⠚⠴⠅⠛⠠⠴⠀⠓⠥⠉⠎⠑⠾` + - first differing cell (zero-based): 16 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` Representative `mismatch` samples: - `sentence_01.json` #322: 삼성전자는 5나노미터(nm, 1nm는 10억 분의 1m) 기반 신규 컨트롤러를 탑재한 PC용 고성능 비휘발성 메모리 익스프레스(NVMe) SSD ‘PM9C1a’를 양산한다고 12일 밝혔다. - - expected: `⠄⠴⠝⠍⠂⠀⠼⠁⠝⠍⠲⠉⠵⠀⠼⠁⠚⠹⠀⠘⠛⠺⠀⠼` - - actual: `⠄⠴⠝⠍⠂⠀⠼⠁⠴⠝⠍⠲⠉⠵⠀⠼⠁⠚⠹⠀⠘⠛⠺⠀` - - first differing cell (zero-based): 29 - - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_02.json` #327: 또한, ‘도가(Dog+Yoga)’ 등 반려동물과 함께 즐길 수 있는 다양한 프로그램이 준비돼 있으며, 참가자 전원에게는 태안산 황토 호박고구마(3kg) 및 요가매트가 제공된다. - - expected: `⠫⠦⠄⠴⠠⠙⠕⠛⠐⠖⠠⠽⠕⠛⠁⠠⠴⠴⠄⠀⠊⠪⠶⠀` - - actual: `⠫⠦⠄⠴⠠⠙⠕⠛⠲⠢⠴⠠⠽⠕⠛⠁⠠⠴⠴⠄⠀⠊⠪⠶` - - first differing cell (zero-based): 19 + - expected: `⠠⠪⠙⠪⠐⠝⠠⠪⠴⠐⠣⠠⠠⠝⠧⠍⠠⠄⠑⠐⠜⠀⠠⠠` + - actual: `⠠⠪⠙⠪⠐⠝⠠⠪⠦⠄⠴⠠⠝⠠⠧⠠⠍⠑⠠⠴⠀⠴⠠⠠` + - first differing cell (zero-based): 124 + - current primary/reason: `corpus_suspect` / `rule34_roman_indicator_before_opening_parenthesis` +- `sentence_02.json` #431: 173cm, 68kg의 다소 왜소한 체격이지만 스크럼 하프(SH) 포지션을 맡아 포워드와 백스 사이에서 날카로운 볼 연결과 공격방향을 결정하는 타고난 판단력이 강점이다. + - expected: `⠚⠙⠪⠦⠄⠴⠠⠠⠎⠓⠠⠴⠀⠙⠥⠨⠕⠠⠡⠮⠀⠑⠦⠣` + - actual: `⠚⠙⠪⠦⠄⠴⠠⠠⠩⠠⠴⠀⠙⠥⠨⠕⠠⠡⠮⠀⠑⠦⠣⠀` + - first differing cell (zero-based): 55 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_03.json` #1399: 삼성전자가 양산을 시작한 12nm(나노미터·1nm는 10억분의 1m)급 공정 16기가비트(Gb) 더블데이터레이트(DDR)5 D램은 글로벌 메모리반도체 1위 기업인 삼성전자의 기술력이 총동원된 ‘야심작’이다. - - expected: `⠕⠓⠎⠐⠝⠕⠓⠪⠦⠄⠴⠠⠠⠙⠙⠗⠠⠴⠼⠑⠀⠴⠠⠙` - - actual: `⠕⠓⠎⠐⠝⠕⠓⠪⠀⠀⠦⠠⠠⠙⠙⠗⠴⠼⠑⠀⠴⠠⠙⠲` - - first differing cell (zero-based): 104 +- `sentence_03.json` #4925: 1일 삼성전자에 따르면 2023년 1월부터 7월까지 판매된 삼성 Neo QLED·QLED TV 3대 중 1대는 85형 또는 98형(247cm)으로 집계됐다. + - expected: `⠝⠑⠕⠀⠠⠠⠟⠇⠑⠙⠐⠆⠴⠠⠠⠟⠇⠑⠙⠀⠠⠠⠞⠧` + - actual: `⠝⠑⠕⠀⠠⠠⠟⠇⠫⠲⠐⠆⠴⠠⠠⠟⠇⠫⠀⠠⠠⠞⠧⠀` + - first differing cell (zero-based): 72 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` - `sentence_04.json` #2527: 삼성전자가 선폭 2·3나노(㎚·10억분의 1m) 수준의 반도체 설계에 필요한 ‘공정설계키트(PDK)’를 국내 팹리스(반도체 설계 업체)에 제공하는 등 파운드리(위탁 생산) 생태계 키우기에 나선다. - expected: `⠉⠉⠥⠦⠄⠴⠝⠍⠐⠆⠼⠁⠚⠹⠘⠛⠺⠀⠼⠁⠴⠍⠠⠴` @@ -2728,55 +2633,74 @@ Representative `mismatch` samples: ### `single_capital_followed_by_parenthesized_digits` -Of the 1361 candidates, 1357 are the actual `pending_rule_review` subcluster. The other 4 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 1361 candidates, 6 are the actual `pending_rule_review` subcluster. The other 1355 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. -For this output-signature audit, 1361 mismatches were evaluable and 1342 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. +For this output-signature audit, 10 mismatches were evaluable and 4 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. Localized first-difference transitions: -- `U+2834 ⠴ -> U+2800 ⠀`: 1330 -- `U+2834 ⠴ -> U+2801 ⠁`: 9 -- `U+2834 ⠴ -> U+2803 ⠃`: 2 -- `U+2834 ⠴ -> U+2813 ⠓`: 1 +- `U+2834 ⠴ -> U+2800 ⠀`: 4 Mismatch primary-class distribution: - `corpus_suspect`: 4 -- `pending_rule_review`: 1357 +- `pending_rule_review`: 6 -Representative `mismatch` samples: +Representative `exact` samples: - `sentence_01.json` #80: 4일 경기 안성경찰서 등에 따르면 남성 A(54)씨는 지난 2일 오후 9시53분께 경기 안성의 주차장 인근에서 전처인 B(53)씨 흉기로 찔러 살해했다. - - expected: `⠑⠡⠀⠉⠢⠠⠻⠀⠴⠠⠁⠦⠄⠼⠑⠙⠠⠴⠠⠠⠕⠉⠵⠀` - - actual: `⠑⠡⠀⠉⠢⠠⠻⠀⠀⠁⠦⠼⠑⠙⠴⠀⠀⠠⠠⠕⠉⠵⠀⠨` - - first differing cell (zero-based): 39 - - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` + - expected: `⠼⠙⠕⠂⠀⠈⠻⠈⠕⠀⠣⠒⠠⠻⠈⠻⠰⠣⠂⠠⠎⠀⠊⠪` + - actual: `⠼⠙⠕⠂⠀⠈⠻⠈⠕⠀⠣⠒⠠⠻⠈⠻⠰⠣⠂⠠⠎⠀⠊⠪` + - current primary/reason: `exact` / `exact` - `sentence_02.json` #186: 3일 사회관계망서비스(SNS)를 통해 유포된 동영상에는 중학생인 A(14)양이 지난달 30일 태안의 한 지하 주차장에서 B(15)양으로부터 일방적으로 폭행을 당하고 주변에 있던 학생들은 이를 웃으며 방관하는 장면이 담겼다. - - expected: `⠶⠚⠁⠠⠗⠶⠟⠀⠴⠠⠁⠦⠄⠼⠁⠙⠠⠴⠜⠶⠕⠀⠨⠕` - - actual: `⠶⠚⠁⠠⠗⠶⠟⠀⠀⠁⠦⠼⠁⠙⠴⠀⠀⠜⠶⠕⠀⠨⠕⠉` - - first differing cell (zero-based): 65 - - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` + - expected: `⠼⠉⠕⠂⠀⠇⠚⠽⠈⠧⠒⠈⠌⠑⠶⠠⠎⠘⠕⠠⠪⠦⠄⠴` + - actual: `⠼⠉⠕⠂⠀⠇⠚⠽⠈⠧⠒⠈⠌⠑⠶⠠⠎⠘⠕⠠⠪⠦⠄⠴` + - current primary/reason: `exact` / `exact` - `sentence_03.json` #51: 20일 한국장기조직기증원에 따르면 A(11)군은 지난 3일 학교에 가기 위해 횡단보도를 건너다가 시내버스에 치여 병원으로 이송돼 치료받았지만 회복하지 못하고 뇌사 상태에 빠졌다. - - expected: `⠀⠠⠊⠐⠪⠑⠡⠀⠴⠠⠁⠦⠄⠼⠁⠁⠠⠴⠈⠛⠵⠀⠨⠕` - - actual: `⠀⠠⠊⠐⠪⠑⠡⠀⠀⠁⠦⠼⠁⠁⠴⠀⠀⠈⠛⠵⠀⠨⠕⠉` - - first differing cell (zero-based): 36 - - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` + - expected: `⠼⠃⠚⠕⠂⠀⠚⠒⠈⠍⠁⠨⠶⠈⠕⠨⠥⠨⠕⠁⠈⠕⠨⠪` + - actual: `⠼⠃⠚⠕⠂⠀⠚⠒⠈⠍⠁⠨⠶⠈⠕⠨⠥⠨⠕⠁⠈⠕⠨⠪` + - current primary/reason: `exact` / `exact` - `sentence_04.json` #1629: 23일 법조계에 따르면 전주지법 제12형사부(김도형 부장판사)는 살인, 공갈, 성매매 알선 행위 등 처벌에 관한 법률 위반 혐의로 기소된 A(28)씨에게 징역 17년을 선고했다. - - expected: `⠈⠕⠠⠥⠊⠽⠒⠀⠴⠠⠁⠦⠄⠼⠃⠓⠠⠴⠠⠠⠕⠝⠈⠝` - - actual: `⠈⠕⠠⠥⠊⠽⠒⠀⠀⠁⠦⠼⠃⠓⠴⠀⠀⠠⠠⠕⠝⠈⠝⠀` - - first differing cell (zero-based): 137 + - expected: `⠼⠃⠉⠕⠂⠀⠘⠎⠃⠨⠥⠈⠌⠝⠀⠠⠊⠐⠪⠑⠡⠀⠨⠾` + - actual: `⠼⠃⠉⠕⠂⠀⠘⠎⠃⠨⠥⠈⠌⠝⠀⠠⠊⠐⠪⠑⠡⠀⠨⠾` + - current primary/reason: `exact` / `exact` + +Representative `mismatch` samples: + +- `sentence_01.json` #9855: 부부인 A·B씨는 지난해 5월 14일부터 17일 사이 전남 여수에서 모텔을 운영하면서 딸 C(37)씨가 지적장애를 앓는 이모 D(60)씨를 폭행해 사망에 이를 때까지 방치한 혐의로 기소됐다. + - expected: `⠍⠘⠍⠟⠀⠴⠠⠁⠐⠆⠴⠠⠃⠲⠠⠠⠕⠉⠵⠀⠨⠕⠉⠒` + - actual: `⠍⠘⠍⠟⠀⠴⠠⠁⠲⠐⠆⠴⠠⠃⠲⠠⠠⠕⠉⠵⠀⠨⠕⠉` + - first differing cell (zero-based): 9 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_02.json` #16790: 서울 수서경찰서는 폐쇄회로(CC)TV 등을 토대로 용의자 동선을 추적, 이날 오후 2시쯤 서울 강동구 성내동 주거지에서 A(42)씨를 체포했다. + - expected: `⠌⠠⠧⠗⠚⠽⠐⠥⠴⠐⠣⠠⠠⠉⠉⠐⠜⠠⠠⠞⠧⠲⠀⠊` + - actual: `⠌⠠⠧⠗⠚⠽⠐⠥⠦⠄⠴⠠⠠⠉⠉⠠⠴⠴⠠⠠⠞⠧⠲⠀` + - first differing cell (zero-based): 27 + - current primary/reason: `corpus_suspect` / `rule34_roman_indicator_before_opening_parenthesis` +- `sentence_03.json` #639: 2일 법조계 등에 따르면 서울중앙지법 형사 18단독 이준구 판사는 지난달 26일 업무상 과실치상 혐의로 재판에 넘겨진 업주 A(64)에 대해 무죄를 선고했다. + - expected: `⠨⠟⠀⠎⠃⠨⠍⠀⠴⠠⠁⠦⠄⠼⠋⠙⠠⠴⠝⠀⠊⠗⠚⠗` + - actual: `⠨⠟⠀⠎⠃⠨⠍⠀⠀⠁⠦⠼⠋⠙⠴⠀⠀⠝⠀⠊⠗⠚⠗⠀` + - first differing cell (zero-based): 120 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_04.json` #3330: 부산지검 마약범죄특별수사팀(팀장 박성민 강력범죄수사부장)은 시가 216억 상당의 마약류를 태국에서 밀반입한 혐의로 총책 A씨(31)와 운반책 B(31), C(30)씨를 구속기소했다고 10일 밝혔다. + - expected: `⠀⠛⠘⠒⠰⠗⠁⠀⠴⠠⠃⠦⠄⠼⠉⠁⠠⠴⠐⠀⠴⠠⠉⠦` + - actual: `⠀⠛⠘⠒⠰⠗⠁⠀⠀⠃⠦⠼⠉⠁⠴⠐⠀⠴⠠⠉⠦⠄⠼⠉` + - first differing cell (zero-based): 146 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` ### `spaced_comma_between_ascii_digit_runs` -Of the 217 candidates, 34 are the actual `pending_rule_review` subcluster. The other 183 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 217 candidates, 22 are the actual `pending_rule_review` subcluster. The other 195 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. + +For this output-signature audit, 22 mismatches were evaluable and 1 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. + +Localized first-difference transitions: -For this output-signature audit, 35 mismatches were evaluable and 0 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. +- `U+2802 ⠂ -> U+2810 ⠐`: 1 Mismatch primary-class distribution: -- `comparison_method`: 1 -- `pending_rule_review`: 34 +- `pending_rule_review`: 22 Representative `exact` samples: @@ -2797,39 +2721,46 @@ Representative `exact` samples: - actual: `⠈⠍⠁⠰⠗⠁⠡⠈⠍⠈⠕⠈⠧⠒⠟⠀⠚⠒⠈⠍⠁⠚⠧⠒` - current primary/reason: `exact` / `exact` +Representative `localized_mismatch` samples: + +- `sentence_03.json` #19569: 배우 겸 2PM 멤버 황찬성이 AK-69, 2AM 이창민과 함께 부른 ‘인 투 더 파이어(Into the fire)’가 일본 TV 애니메이션 ‘리:몬스터(Re:Monster)’의 오프닝 주제가로 선정됐다. + - expected: `⠠⠠⠁⠅⠤⠼⠋⠊⠂⠀⠼⠃⠠⠠⠁⠍⠲⠀⠕⠰⠣⠶⠑⠟` + - actual: `⠠⠠⠁⠅⠤⠼⠋⠊⠐⠀⠼⠃⠴⠠⠠⠁⠍⠲⠀⠕⠰⠣⠶⠑` + - first differing cell (zero-based): 42 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` + Representative `mismatch` samples: -- `sentence_01.json` #3841: CT-P43의 오리지널 의약품인 스텔라라는 얀센(Janssen)이 개발한 인터루킨(IL)-12, 23 억제제다. 판상형 건선과 건선성 관절염, 크론병, 궤양성 대장염 등의 자가면역질환 치료제로 사용된다. - - expected: `⠴⠠⠠⠉⠞⠤⠠⠏⠼⠙⠉⠺⠀⠥⠐⠕⠨⠕⠉⠞⠀⠺⠜⠁` - - actual: `⠴⠠⠠⠉⠞⠤⠰⠠⠏⠼⠙⠉⠺⠀⠥⠐⠕⠨⠕⠉⠞⠀⠺⠜` - - first differing cell (zero-based): 6 +- `sentence_01.json` #11298: 4세대 대표 아이돌 그룹 에스파(aespa)의 첫 단독 리얼리티 ‘에스파의 싱크로드(연출 진선미 제작 SM C&C STUDIO)’ 5, 6회가 오는 11일 오전 11시 웨이브(Wavve)에서 독점 공개된다. + - expected: `⠉⠀⠌⠥⠙⠊⠕⠠⠄⠠⠴⠴⠄⠀⠼⠑⠐⠀⠼⠋⠀⠚⠽⠫` + - actual: `⠉⠀⠌⠥⠙⠊⠕⠠⠴⠠⠄⠴⠄⠀⠼⠑⠐⠀⠼⠋⠀⠚⠽⠫` + - first differing cell (zero-based): 111 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` - `sentence_02.json` #537: 11일 Macker와 ZAYDA, 12에는 Killa Ton과 Bagagee Viphex13, 13일에는 EDM씬의 트렌드를 주도하는 쥬디(JOODY)와 아쉬코(ASHIKO)가 출연했다. - - expected: `⠴⠠⠠⠵⠁⠽⠙⠁⠐⠀⠼⠁⠃⠝⠉⠵⠀⠴⠠⠅⠊⠇⠇⠁` - - actual: `⠴⠠⠠⠵⠁⠽⠙⠁⠂⠀⠼⠁⠃⠝⠉⠵⠀⠴⠠⠅⠊⠇⠇⠁` - - first differing cell (zero-based): 24 + - expected: `⠂⠝⠉⠵⠀⠴⠠⠠⠑⠙⠍⠲⠠⠠⠟⠺⠀⠓⠪⠐⠝⠒⠊⠪` + - actual: `⠂⠝⠉⠵⠀⠴⠠⠠⠫⠍⠲⠠⠠⠟⠺⠀⠓⠪⠐⠝⠒⠊⠪⠐` + - first differing cell (zero-based): 83 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` - `sentence_03.json` #13832: 이어 르세라핌은 오늘 정오 글로벌 팬 커뮤니티 플랫폼 위버스(Weverse)와 공식 SNS 채널을 통해 오는 3월 18, 19일 양일간 개최되는 팬미팅에 대한 자세한 정보를 공개했다. - expected: `⠎⠠⠪⠦⠄⠴⠠⠺⠐⠑⠎⠑⠠⠴⠧⠀⠈⠿⠠⠕⠁⠀⠴⠠` - actual: `⠎⠠⠪⠦⠄⠴⠠⠺⠑⠧⠻⠎⠑⠠⠴⠧⠀⠈⠿⠠⠕⠁⠀⠴` - first differing cell (zero-based): 62 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_04.json` #1801: 마이애미는 30일 미국 보스턴 TD가든에서 열린 보스턴과의 2022~2023 미국프로농구(NBA) 동부콘퍼런스 결승 7차전에서 시종일관 압도한 끝에 103-84, 19점 차 대승을 거뒀다. - - expected: `⠘⠥⠠⠪⠓⠾⠀⠴⠰⠠⠠⠞⠙⠲⠫⠊⠵⠝⠠⠎⠀⠳⠐⠟` - - actual: `⠘⠥⠠⠪⠓⠾⠀⠴⠠⠠⠞⠙⠲⠫⠊⠵⠝⠠⠎⠀⠳⠐⠟⠀` - - first differing cell (zero-based): 29 +- `sentence_04.json` #7083: 경기도교육청(교육감 임태희)이 코로나19 장기화로 학습, 신체 건강, 사회성, 심리·정서 등 결손이 발생한 초등 3, 4학년의 개별 맞춤형 성장을 지원하는 ‘더(T?H?E) 자람 프로젝트’의 현장 안착을 지원한다. + - expected: `⠊⠎⠦⠄⠴⠠⠞⠦⠠⠓⠦⠠⠑⠠⠴⠀⠨⠐⠣⠢⠀⠙⠪⠐` + - actual: `⠊⠎⠦⠄⠴⠠⠞⠦⠰⠠⠓⠦⠰⠠⠑⠠⠴⠀⠨⠐⠣⠢⠀⠙` + - first differing cell (zero-based): 160 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` ### `standalone_multi_character_uppercase_roman_word` -Of the 62411 candidates, 9527 are the actual `pending_rule_review` subcluster. The other 52884 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 62411 candidates, 5659 are the actual `pending_rule_review` subcluster. The other 56752 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. Mismatch primary-class distribution: -- `comparison_method`: 228 -- `corpus_suspect`: 1100 -- `pending_rule_review`: 9527 -- `unsupported_character_review`: 137 +- `corpus_suspect`: 1197 +- `pending_rule_review`: 5659 +- `unsupported_character_review`: 4 Representative `exact` samples: @@ -2841,9 +2772,9 @@ Representative `exact` samples: - expected: `⠠⠝⠨⠿⠠⠕⠀⠨⠍⠰⠽⠐⠀⠠⠝⠨⠿⠠⠕⠓⠒⠠⠥⠨` - actual: `⠠⠝⠨⠿⠠⠕⠀⠨⠍⠰⠽⠐⠀⠠⠝⠨⠿⠠⠕⠓⠒⠠⠥⠨` - current primary/reason: `exact` / `exact` -- `sentence_03.json` #4: 18일(현지시간) 월스트리트저널(WSJ) 등 미 외신은 투·개표기 업체 도미니언 투표시스템이 2021년 1월 제기한 16억 달러(약 2조원) 명예훼손 소송과 관련, 폭스뉴스와 도미니언 측이 이 같이 합의했다고 보도했다. - - expected: `⠼⠁⠓⠕⠂⠦⠄⠚⠡⠨⠕⠠⠕⠫⠒⠠⠴⠀⠏⠂⠠⠪⠓⠪` - - actual: `⠼⠁⠓⠕⠂⠦⠄⠚⠡⠨⠕⠠⠕⠫⠒⠠⠴⠀⠏⠂⠠⠪⠓⠪` +- `sentence_03.json` #2: 연구소는 전남대학교, 순천대학교, 전남도립대학교, 동신대학교, 한국에너지공과대학교, 충남대학교, 부산대학교 등 드론, 빅데이터, 인공지능(AI) 관련 대학 교수와 전남테크노파크, ㈜엘시스 등 전문가로 기획연구팀을 구성한다. + - expected: `⠡⠈⠍⠠⠥⠉⠵⠀⠨⠾⠉⠢⠊⠗⠚⠁⠈⠬⠐⠀⠠⠛⠰⠾` + - actual: `⠡⠈⠍⠠⠥⠉⠵⠀⠨⠾⠉⠢⠊⠗⠚⠁⠈⠬⠐⠀⠠⠛⠰⠾` - current primary/reason: `exact` / `exact` - `sentence_04.json` #1: 일본 정부는 이날 국가안전보장회의(NSC)를 열어 대응 방침을 논의했다. 이어 북한의 미사일 발사에 대해 “엄중히 항의하고 강하게 비난한다”고 밝혔다. - expected: `⠕⠂⠘⠷⠀⠨⠻⠘⠍⠉⠵⠀⠕⠉⠂⠀⠈⠍⠁⠫⠣⠒⠨⠾` @@ -2854,7 +2785,7 @@ Representative `mismatch` samples: - `sentence_01.json` #14: 새롭게 선보인 DRX 브랜딩과 팀 컬러는 2023년 1월 1일부터 DRX의 온오프라인 콘텐츠, 엠디(MD), SNS, 유튜브 등 모든 제작물에 적용될 예정이다. - expected: `⠰⠪⠐⠀⠝⠢⠊⠕⠴⠐⠣⠠⠠⠍⠙⠐⠜⠂⠀⠠⠠⠎⠝⠎` - - actual: `⠰⠪⠐⠀⠝⠢⠊⠕⠦⠄⠴⠠⠠⠍⠙⠠⠴⠐⠀⠰⠠⠠⠎⠝` + - actual: `⠰⠪⠐⠀⠝⠢⠊⠕⠦⠄⠴⠠⠠⠍⠙⠠⠴⠐⠀⠴⠠⠠⠎⠝` - first differing cell (zero-based): 97 - current primary/reason: `corpus_suspect` / `rule34_roman_indicator_before_opening_parenthesis` - `sentence_02.json` #10: 설동호 대전교육감은 “학교 교육과정 및 여건에 맞는 인공지능(AI)교육 선도학교 운영을 통해 AI·SW교육 기반 구축과 학생들과 교원들의 미래 디지털 역량이 함양될 것이다.”라고 말했다. @@ -2868,77 +2799,90 @@ Representative `mismatch` samples: - first differing cell (zero-based): 175 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` - `sentence_04.json` #19: 이날 행사에는 구 대표 외에 권봉석 (주)LG 최고운영책임자(COO), 박일평 LG사이언스파크대표(사장)를 비롯해 각 계열사 최고기술책임자(CTO), 최고데이터책임자(CDO), 최고인사책임자(CHO) 등이 참석해 국내 이공계 R&D 인재 400여명과 만났다. - - expected: `⠁⠕⠢⠨⠦⠄⠴⠠⠠⠉⠕⠕⠠⠴⠐⠀⠘⠁⠕⠂⠙⠻⠀⠴` - - actual: `⠁⠕⠢⠨⠦⠄⠴⠠⠉⠠⠕⠠⠕⠠⠴⠐⠀⠘⠁⠕⠂⠙⠻⠀` - - first differing cell (zero-based): 60 + - expected: `⠁⠕⠢⠨⠦⠄⠴⠠⠠⠉⠓⠕⠠⠴⠀⠊⠪⠶⠕⠀⠰⠣⠢⠠` + - actual: `⠁⠕⠢⠨⠦⠄⠴⠠⠉⠠⠓⠠⠕⠠⠴⠀⠊⠪⠶⠕⠀⠰⠣⠢` + - first differing cell (zero-based): 188 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` ### `tight_triangle_mark_immediately_before_korean` -Of the 377 candidates, 376 are the actual `pending_rule_review` subcluster. The other 1 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 377 candidates, 54 are the actual `pending_rule_review` subcluster. The other 323 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. -For this output-signature audit, 377 mismatches were evaluable and 335 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. +For this output-signature audit, 60 mismatches were evaluable and 3 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. Localized first-difference transitions: -- `U+2800 ⠀ -> U+2807 ⠇`: 332 -- `U+280A ⠊ -> U+2807 ⠇`: 1 -- `U+2818 ⠘ -> U+2807 ⠇`: 1 -- `U+2829 ⠩ -> U+2807 ⠇`: 1 +- `U+280A ⠊ -> U+2800 ⠀`: 1 +- `U+2818 ⠘ -> U+2800 ⠀`: 1 +- `U+2829 ⠩ -> U+2800 ⠀`: 1 Mismatch primary-class distribution: -- `corpus_suspect`: 1 -- `pending_rule_review`: 376 +- `corpus_suspect`: 6 +- `pending_rule_review`: 54 -Representative `mismatch` samples: +Representative `exact` samples: - `sentence_01.json` #3907: 웹케시그룹은 △청구·결제·수납 솔루션 ‘위빌(WeBILL)’ △글로벌 중견·대기업 자금관리 솔루션 ‘위엠비에이(WeMBA)’ △전자세금계산서 발행 솔루션 ‘위택스(WeTAX)’ △글로벌 통합 자금관리 시스템 ‘위지엠비에이(WeGMBA)’ 등의 글로벌 전략 상품도 순차적으로 출시 예정이다. - - expected: `⠪⠐⠍⠃⠵⠀⠸⠬⠀⠰⠻⠈⠍⠐⠆⠈⠳⠨⠝⠐⠆⠠⠍⠉` - - actual: `⠪⠐⠍⠃⠵⠀⠸⠬⠇⠰⠻⠈⠍⠐⠆⠈⠳⠨⠝⠐⠆⠠⠍⠉` - - first differing cell (zero-based): 16 - - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` + - expected: `⠏⠗⠃⠋⠝⠠⠕⠈⠪⠐⠍⠃⠵⠀⠸⠬⠀⠰⠻⠈⠍⠐⠆⠈` + - actual: `⠏⠗⠃⠋⠝⠠⠕⠈⠪⠐⠍⠃⠵⠀⠸⠬⠀⠰⠻⠈⠍⠐⠆⠈` + - current primary/reason: `exact` / `exact` - `sentence_02.json` #17: 시범사업은 서해안권·백제문화권·서부내륙권을 대표하는 4개 시군의 관광자원 특성을 반영한 △문화치유 △해양치유 △마을맞춤 △엠지(MZ)맞춤 등 유형별 코스를 집중 발굴해 워케이션 상품을 기획했다. - - expected: `⠘⠒⠻⠚⠒⠀⠸⠬⠀⠑⠛⠚⠧⠰⠕⠩⠀⠸⠬⠀⠚⠗⠜⠶` - - actual: `⠘⠒⠻⠚⠒⠀⠸⠬⠇⠑⠛⠚⠧⠰⠕⠩⠀⠸⠬⠇⠚⠗⠜⠶` - - first differing cell (zero-based): 94 - - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` + - expected: `⠠⠕⠘⠎⠢⠇⠎⠃⠵⠀⠠⠎⠚⠗⠣⠒⠈⠏⠒⠐⠆⠘⠗⠁` + - actual: `⠠⠕⠘⠎⠢⠇⠎⠃⠵⠀⠠⠎⠚⠗⠣⠒⠈⠏⠒⠐⠆⠘⠗⠁` + - current primary/reason: `exact` / `exact` - `sentence_03.json` #244: 올해 모집 분야는 △에듀테크&콘텐츠 △라이프스타일 △정보통신기술(ICT)&디지털 기반 혁신기술 등이다. 시리즈A 단계까지 법인 등록 스타트업이면 지원할 수 있다. - - expected: `⠘⠛⠜⠉⠵⠀⠸⠬⠀⠝⠊⠩⠓⠝⠋⠪⠴⠈⠯⠲⠋⠷⠓⠝` - - actual: `⠘⠛⠜⠉⠵⠀⠸⠬⠇⠝⠊⠩⠓⠝⠋⠪⠴⠈⠯⠲⠋⠷⠓⠝` - - first differing cell (zero-based): 19 - - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` + - expected: `⠥⠂⠚⠗⠀⠑⠥⠨⠕⠃⠀⠘⠛⠜⠉⠵⠀⠸⠬⠀⠝⠊⠩⠓` + - actual: `⠥⠂⠚⠗⠀⠑⠥⠨⠕⠃⠀⠘⠛⠜⠉⠵⠀⠸⠬⠀⠝⠊⠩⠓` + - current primary/reason: `exact` / `exact` - `sentence_04.json` #7059: 현재 남양주시의 놀이체험시설은 △놀자람(화도) △까꿍놀이터(진접) △도르르(호평) △북(Book)놀이터(별내) △아이꿈놀이터(와부) 총 5개소로, 기존에 무료로 운영 중인 아이꿈놀이터를 제외한 유료 시설 4개소에 대해 무료 서비스가 제공된다. - - expected: `⠠⠕⠠⠞⠵⠀⠸⠬⠀⠉⠥⠂⠨⠐⠣⠢⠦⠄⠚⠧⠊⠥⠠⠴` - - actual: `⠠⠕⠠⠞⠵⠀⠸⠬⠇⠉⠥⠂⠨⠐⠣⠢⠦⠄⠚⠧⠊⠥⠠⠴` - - first differing cell (zero-based): 32 + - expected: `⠚⠡⠨⠗⠀⠉⠢⠜⠶⠨⠍⠠⠕⠺⠀⠉⠥⠂⠕⠰⠝⠚⠎⠢` + - actual: `⠚⠡⠨⠗⠀⠉⠢⠜⠶⠨⠍⠠⠕⠺⠀⠉⠥⠂⠕⠰⠝⠚⠎⠢` + - current primary/reason: `exact` / `exact` + +Representative `mismatch` samples: + +- `sentence_01.json` #6407: 정부도 이에 대응해 △Upper-mid 대역(7~24GHz) 기술 △커버리지 확대 기술 △소프트웨어(SW) 중심 네트워크 △에너지 절감 △공급망 안보 강화 등 5대 분야에 대해 기술개발을 추진한다. + - expected: `⠊⠙⠲⠀⠊⠗⠱⠁⠦⠄⠼⠛⠈⠔⠼⠃⠙⠴⠠⠛⠠⠓⠵⠠` + - actual: `⠊⠙⠲⠀⠊⠗⠱⠁⠀⠀⠦⠼⠛⠈⠔⠼⠃⠙⠠⠠⠛⠓⠵⠴` + - first differing cell (zero-based): 36 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_02.json` #672: 충남교육청은 사무행정(태안여고), ERP(천안여자상업고) 및 대회홍보크리에이터(논산여자상업고) 3개 종목에서 대상인 교육부장관상을 받았고 △금상 6개 △은상 16개 △동상 23개 등 총 48개 수상의 영예를 누렸다. + - expected: `⠥⠠⠴⠐⠀⠴⠠⠠⠑⠗⠏⠦⠄⠰⠾⠣⠒⠱⠨⠇⠶⠎⠃⠈` + - actual: `⠥⠠⠴⠐⠀⠴⠠⠠⠻⠏⠦⠄⠰⠾⠣⠒⠱⠨⠇⠶⠎⠃⠈⠥` + - first differing cell (zero-based): 37 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_03.json` #2221: 개선사업에는 △교량 상징조형물 및 배면·교각 이미지 연출 발광다이오드(LED)조명 △상징조형물 내부 은하수 조명 △기상전광판 4개 △상부 레이저빔 등이 설치됐다. + - expected: `⠊⠪⠦⠄⠴⠠⠠⠇⠑⠙⠠⠴⠨⠥⠑⠻⠀⠸⠬⠀⠇⠶⠨⠕` + - actual: `⠊⠪⠦⠄⠴⠠⠠⠇⠫⠠⠴⠨⠥⠑⠻⠀⠸⠬⠀⠇⠶⠨⠕⠶` + - first differing cell (zero-based): 74 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_04.json` #7425: 모집 분야는 △창업 성장 및 일자리 지원 사업 △청년 메이커(maker) 지원 사업 △온라인플랫폼 활성화 지원 사업 △청년 창업 공간지원(s/w, h/w) △워케이션 지원 사업이다. + - expected: `⠄⠴⠎⠸⠌⠺⠂⠀⠓⠸⠌⠺⠠⠴⠀⠸⠬⠀⠏⠋⠝⠕⠠⠡` + - actual: `⠄⠴⠎⠸⠌⠺⠂⠀⠴⠓⠴⠸⠌⠺⠠⠴⠀⠸⠬⠀⠏⠋⠝⠕` + - first differing cell (zero-based): 141 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` ### `uppercase_alphanumeric_roman_digit_sequence` -Of the 3429 candidates, 1221 are the actual `pending_rule_review` subcluster. The other 2208 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 3429 candidates, 462 are the actual `pending_rule_review` subcluster. The other 2967 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. -For this output-signature audit, 1332 mismatches were evaluable and 150 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. +For this output-signature audit, 568 mismatches were evaluable and 9 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. Localized first-difference transitions: -- `U+2834 ⠴ -> U+2800 ⠀`: 95 -- `U+2834 ⠴ -> U+2830 ⠰`: 52 -- `U+2834 ⠴ -> U+2820 ⠠`: 2 -- `U+2800 ⠀ -> U+2807 ⠇`: 1 +- `U+2834 ⠴ -> U+2800 ⠀`: 9 Mismatch primary-class distribution: -- `comparison_method`: 14 -- `corpus_suspect`: 89 -- `pending_rule_review`: 1221 -- `unsupported_character_review`: 8 +- `corpus_suspect`: 106 +- `pending_rule_review`: 462 Representative `exact` samples: -- `sentence_01.json` #25: 세븐일레븐은 올해 말 정식 버전 오픈을 목표로 하며, 이를 통해 주력상품을 구매하고 배송까지 받을 수 있는 차세대 오프라인을 위한 온라인(O4O) 시스템을 구축해 나갈 계획이다. - - expected: `⠠⠝⠘⠵⠕⠂⠐⠝⠘⠵⠵⠀⠥⠂⠚⠗⠀⠑⠂⠀⠨⠻⠠⠕` - - actual: `⠠⠝⠘⠵⠕⠂⠐⠝⠘⠵⠵⠀⠥⠂⠚⠗⠀⠑⠂⠀⠨⠻⠠⠕` +- `sentence_01.json` #17: 지난달 30일(현지시각) 뉴욕증권거래소(NYSE)에서 다우존스30산업평균지수는 전 거래일보다 73.55포인트(0.22%) 하락한 3만3147.25로 마감했다. 대형주 중심의 S&P500지수는 9.78포인트(0.25%) 하락한 3839.50으로, 기술주 중심의 나스닥지수는 11.61포인트(0.11%) 하락한 1만0466.4 + - expected: `⠨⠕⠉⠒⠊⠂⠀⠼⠉⠚⠕⠂⠦⠄⠚⠡⠨⠕⠠⠕⠫⠁⠠⠴` + - actual: `⠨⠕⠉⠒⠊⠂⠀⠼⠉⠚⠕⠂⠦⠄⠚⠡⠨⠕⠠⠕⠫⠁⠠⠴` - current primary/reason: `exact` / `exact` - `sentence_02.json` #167: 2023년 제1회 상담사례 워크숍은 정신건강 임상심리사인 김한우 수퍼바이저(월덴3 아카데미 대표)가 ‘기질 및 성격검사(TCI), 미네소타 다면적 인성 검사(MMPI-2), 문장완성검사(SCT) 활용을 위한 심리평가 슈퍼비전’이라는 주제로 진행하였다. - expected: `⠼⠃⠚⠃⠉⠀⠉⠡⠀⠨⠝⠼⠁⠀⠚⠽⠀⠇⠶⠊⠢⠇⠐⠌` @@ -2955,33 +2899,33 @@ Representative `exact` samples: Representative `localized_mismatch` samples: -- `sentence_01.json` #735: 로이드 오스틴 미국 국방성 장관이 30일 오후 일명 ‘최후 심판의 날 항공기’로 불리는 E-4B 나이트워치(NightWatch)를 타고 방한했다. - - expected: `⠀⠘⠯⠐⠕⠉⠵⠀⠴⠠⠑⠤⠼⠙⠠⠃⠲⠀⠉⠣⠕⠓⠪⠏` - - actual: `⠀⠘⠯⠐⠕⠉⠵⠀⠀⠠⠑⠔⠼⠙⠠⠃⠀⠉⠣⠕⠓⠪⠏⠰` - - first differing cell (zero-based): 83 +- `sentence_01.json` #7679: 또한 최근 지아이노베이션은 GI-301이 임상 1a상 파트A에서 단회 투여에도 우수한 IgE 감소 효과 확인했다고 밝혔다. 더불어 유럽종양학회(ESMO)에서 GI-101(CD80-1gC4 Fc-IL2V)의 임상 1/2상 결과도 발표할 예정이다. + - expected: `⠍⠕⠠⠴⠝⠠⠎⠀⠴⠠⠠⠛⠊⠤⠼⠁⠚⠁⠐⠣⠠⠠⠉⠙` + - actual: `⠍⠕⠠⠴⠝⠠⠎⠀⠀⠠⠠⠛⠊⠔⠼⠁⠚⠁⠦⠠⠠⠉⠙⠼` + - first differing cell (zero-based): 149 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_02.json` #633: 두 학과는 올해 2학기부터 지방대학 특성화 사업 및 마이크로디그리(MD) 융합전공인 ‘빅데이터 기반 영상문화콘텐츠 개발’ 과정을 함께 운영하고 있으며, LINC3.0 사업에도 참여하고 있다. - - expected: `⠀⠕⠌⠪⠑⠱⠐⠀⠴⠠⠠⠇⠔⠉⠼⠉⠲⠚⠀⠇⠎⠃⠝⠊` - - actual: `⠀⠕⠌⠪⠑⠱⠐⠀⠀⠠⠠⠇⠊⠝⠉⠼⠉⠲⠚⠀⠀⠇⠎⠃` - - first differing cell (zero-based): 148 +- `sentence_02.json` #2507: 엔비디아의 H100(67 테라플롭스)은 A100(19.5 테라플롭스)에 비해 3배 이상 높은 연산량을 제공하는 컴퓨팅 자원이다. 1 테라플롭스(TF)는 1초에 1조개의 계산을 할 수 있는 속도다. + - expected: `⠥⠃⠠⠪⠠⠴⠵⠀⠴⠠⠁⠼⠁⠚⠚⠦⠄⠼⠁⠊⠲⠑⠀⠓` + - actual: `⠥⠃⠠⠪⠠⠴⠵⠀⠀⠠⠁⠼⠁⠚⠚⠦⠼⠁⠊⠲⠑⠀⠓⠝` + - first differing cell (zero-based): 37 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_03.json` #114: 르노코리아자동차가 다재다능한 신개념 2인승 스포츠유틸리티차량(SUV) ‘QM6 퀘스트’ 활용 방안에 대한 고객 아이디어를 수렴한다고 21일 밝혔다. - - expected: `⠎⠥⠧⠠⠴⠀⠠⠦⠴⠠⠠⠟⠍⠼⠋⠀⠋⠏⠗⠠⠪⠓⠪⠴` - - actual: `⠎⠥⠧⠠⠴⠀⠠⠦⠰⠠⠠⠟⠍⠼⠋⠀⠋⠏⠗⠠⠪⠓⠪⠴` - - first differing cell (zero-based): 73 +- `sentence_03.json` #10938: 인천검단 AA13-1·2 블록 입주 예정자들이 지하주차장 붕괴 사고로 인한 입주 지연과 관련해 한국토지주택공사(LH)와 GS건설이 제시한 보상안을 24일 수용하기로 했다. + - expected: `⠰⠾⠈⠎⠢⠊⠒⠀⠴⠠⠠⠁⠁⠼⠁⠉⠤⠼⠁⠐⠆⠼⠃⠀` + - actual: `⠰⠾⠈⠎⠢⠊⠒⠀⠀⠠⠠⠁⠁⠼⠁⠉⠔⠼⠁⠐⠼⠃⠀⠀` + - first differing cell (zero-based): 9 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_04.json` #1633: KAI는 이번 ‘LIMA 2023’에 말레이시아 공군이 운용할 FA-50M 버전과 함께 세계 시장이 관심을 갖는 국산 초음속 전투기 KF-21, 수리온 헬기, 소형무장헬기(LAH) 등 주력 제품을 전시했다. - - expected: `⠕⠀⠛⠬⠶⠚⠂⠀⠴⠠⠠⠋⠁⠤⠼⠑⠚⠠⠍⠲⠀⠘⠎⠨` - - actual: `⠕⠀⠛⠬⠶⠚⠂⠀⠀⠠⠠⠋⠁⠔⠼⠑⠚⠠⠍⠀⠀⠘⠎⠨` - - first differing cell (zero-based): 54 +- `sentence_04.json` #1766: 삼성전자는 부품·수리 도구·설명서·동영상 등으로 구성된 ‘자가수리 프로그램’을 국내에 도입한다고 30일 밝혔다. 우선 갤럭시 S20·S21·S22 시리즈와 노트북(갤럭시북 프로 15.6인치)·고선명(HD) TV 일부 제품이 대상이다. + - expected: `⠈⠗⠂⠐⠹⠠⠕⠀⠴⠠⠎⠼⠃⠚⠐⠆⠴⠠⠎⠼⠃⠁⠐⠆` + - actual: `⠈⠗⠂⠐⠹⠠⠕⠀⠀⠠⠎⠼⠃⠚⠐⠠⠎⠼⠃⠁⠐⠠⠎⠼` + - first differing cell (zero-based): 123 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` Representative `mismatch` samples: -- `sentence_01.json` #17: 지난달 30일(현지시각) 뉴욕증권거래소(NYSE)에서 다우존스30산업평균지수는 전 거래일보다 73.55포인트(0.22%) 하락한 3만3147.25로 마감했다. 대형주 중심의 S&P500지수는 9.78포인트(0.25%) 하락한 3839.50으로, 기술주 중심의 나스닥지수는 11.61포인트(0.11%) 하락한 1만0466.4 - - expected: `⠠⠕⠢⠺⠀⠴⠠⠎⠈⠯⠠⠏⠼⠑⠚⠚⠨⠕⠠⠍⠉⠵⠀⠼` - - actual: `⠠⠕⠢⠺⠀⠴⠠⠎⠲⠴⠈⠯⠲⠴⠠⠏⠼⠑⠚⠚⠨⠕⠠⠍` - - first differing cell (zero-based): 169 +- `sentence_01.json` #87: 4일 진원생명과학은 자체 개발 흡인작용 피내 접종기 진덤(GeneDerm)을 이용한 코로나19 DNA 백신(GLS-5310)의 안전성·면역원성에 대한 임상1상 결과를 국제감염병학회 (ISID) 학술지인 국제감염질환저널에 발표했다고 4일 밝혔다. + - expected: `⠢⠘⠻⠚⠁⠚⠽⠀⠦⠄⠴⠠⠠⠊⠎⠊⠙⠠⠴⠀⠚⠁⠠⠯` + - actual: `⠢⠘⠻⠚⠁⠚⠽⠀⠴⠐⠣⠠⠠⠊⠎⠊⠙⠐⠜⠲⠀⠚⠁⠠` + - first differing cell (zero-based): 172 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` - `sentence_02.json` #324: 남녀단식 TT1 ~ TT10(지체), T11(지적), DF(청각) 등 12개 세부 종목에서 랭킹 포인트 60점을 걸고 승부를 펼친 결과 총 24명의 우승자가 탄생했다. - expected: `⠀⠴⠠⠠⠞⠞⠼⠁⠈⠔⠠⠠⠞⠞⠼⠁⠚⠦⠄⠨⠕⠰⠝⠠` @@ -2989,32 +2933,30 @@ Representative `mismatch` samples: - first differing cell (zero-based): 17 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` - `sentence_03.json` #64: SK에코플랜트는 20일 종로구 수송동 본사에서 현대엔지니어링, USNC와 ‘수소 마이크로 허브(H2 Micro Hub)’ 구축을 위한 3자간 업무협약(MOU)을 체결했다고 밝혔다. - - expected: `⠪⠐⠥⠀⠚⠎⠘⠪⠦⠄⠴⠠⠓⠼⠃⠀⠠⠍⠊⠉⠗⠕⠀⠠` - - actual: `⠪⠐⠥⠀⠚⠎⠘⠪⠀⠀⠦⠠⠓⠼⠃⠀⠴⠠⠍⠊⠉⠗⠕⠀` - - first differing cell (zero-based): 91 - - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_04.json` #323: 사단급 규모의 상륙군과 대형수송함(LPH) 독도함, 미 강습상륙함(LHD) 마킨 아일랜드함 등 30여척, 미 F-35 전투기·육군 아파치 공격헬기(AH-64)·마린온 상륙기동헬기 등 항공기 70여대, 상륙돌격장갑차(KAAV) 50여대 등 연합·합동전력이 투입됐다. - - expected: `⠱⠰⠹⠐⠀⠑⠕⠀⠴⠠⠋⠤⠼⠉⠑⠀⠨⠾⠓⠍⠈⠕⠐⠆` - - actual: `⠱⠰⠹⠐⠀⠑⠕⠀⠀⠠⠋⠔⠼⠉⠑⠀⠨⠾⠓⠍⠈⠕⠐⠆` - - first differing cell (zero-based): 108 + - expected: `⠜⠁⠦⠄⠴⠠⠠⠍⠕⠥⠠⠴⠮⠀⠰⠝⠈⠳⠚⠗⠌⠊⠈⠥` + - actual: `⠜⠁⠦⠄⠴⠠⠠⠍⠳⠠⠴⠮⠀⠰⠝⠈⠳⠚⠗⠌⠊⠈⠥⠀` + - first differing cell (zero-based): 148 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_04.json` #850: SK하이닉스는 세계 최초로 D램 단품 칩 12개를 수직으로 적층해 최고 용량인 24기가바이트(GB) HBM3 신제품을 개발하는 데 성공했다고 20일 밝혔다. + - expected: `⠈⠕⠫⠘⠣⠕⠓⠪⠴⠐⠣⠠⠠⠛⠃⠐⠜⠀⠠⠠⠓⠃⠍⠼` + - actual: `⠈⠕⠫⠘⠣⠕⠓⠪⠦⠄⠴⠠⠠⠛⠃⠠⠴⠀⠴⠠⠠⠓⠃⠍` + - first differing cell (zero-based): 95 + - current primary/reason: `corpus_suspect` / `rule34_roman_indicator_before_opening_parenthesis` ### `uppercase_ascii_run_immediately_after_digit_in_roman_sequence` -Of the 1896 candidates, 569 are the actual `pending_rule_review` subcluster. The other 1327 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 1896 candidates, 272 are the actual `pending_rule_review` subcluster. The other 1624 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. -For this output-signature audit, 629 mismatches were evaluable and 2 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. +For this output-signature audit, 327 mismatches were evaluable and 3 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. Localized first-difference transitions: -- `U+2830 ⠰ -> U+2820 ⠠`: 2 +- `U+2830 ⠰ -> U+2820 ⠠`: 3 Mismatch primary-class distribution: -- `comparison_method`: 10 -- `corpus_suspect`: 44 -- `pending_rule_review`: 569 -- `unsupported_character_review`: 6 +- `corpus_suspect`: 55 +- `pending_rule_review`: 272 Representative `exact` samples: @@ -3047,7 +2989,7 @@ Representative `mismatch` samples: - `sentence_01.json` #89: “인공지능(AI), 6G 등 핵심 기술을 위한 투자도 늘리는 동시에 전기차 충전, 디지털 헬스, 웹OS 기반의 콘텐츠 서비스 등 많은 영역으로 사업 포트폴리오를 확장하고 있습니다.” - expected: `⠟⠈⠿⠨⠕⠉⠪⠶⠴⠐⠣⠠⠠⠁⠊⠐⠜⠂⠀⠼⠋⠠⠛⠲` - - actual: `⠟⠈⠿⠨⠕⠉⠪⠶⠦⠄⠴⠠⠠⠁⠊⠠⠴⠐⠀⠼⠋⠰⠠⠛` + - actual: `⠟⠈⠿⠨⠕⠉⠪⠶⠦⠄⠴⠠⠠⠁⠊⠠⠴⠐⠀⠼⠋⠴⠠⠛` - first differing cell (zero-based): 9 - current primary/reason: `corpus_suspect` / `rule34_roman_indicator_before_opening_parenthesis` - `sentence_02.json` #445: 박람회에서는 다양한 분야의 디지털 교육 프로그램을 한자리에서 체험할 수 있도록 인공지능(AI) 코스웨어·학습플랫폼, 인공지능(AI) 교과교육, 인공지능(AI) 학습지원, 3D·가상현실(VR)·메타버스 교육, 소프트웨어(SW)·코딩·로봇 교육 등 체험 공간을 운영할 예정이다. @@ -3068,20 +3010,19 @@ Representative `mismatch` samples: ### `uppercase_ascii_run_immediately_after_hyphen_in_roman_sequence` -Of the 952 candidates, 660 are the actual `pending_rule_review` subcluster. The other 292 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 952 candidates, 209 are the actual `pending_rule_review` subcluster. The other 743 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. -For this output-signature audit, 681 mismatches were evaluable and 190 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. +For this output-signature audit, 228 mismatches were evaluable and 7 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. Localized first-difference transitions: -- `U+2820 ⠠ -> U+2830 ⠰`: 184 -- `U+2830 ⠰ -> U+2820 ⠠`: 6 +- `U+2820 ⠠ -> U+2830 ⠰`: 5 +- `U+2830 ⠰ -> U+2820 ⠠`: 2 Mismatch primary-class distribution: -- `corpus_suspect`: 12 -- `pending_rule_review`: 660 -- `unsupported_character_review`: 9 +- `corpus_suspect`: 19 +- `pending_rule_review`: 209 Representative `exact` samples: @@ -3097,62 +3038,57 @@ Representative `exact` samples: - expected: `⠣⠯⠐⠎⠀⠨⠝⠨⠍⠊⠥⠈⠗⠘⠂⠈⠿⠇⠉⠵⠀⠈⠍⠁` - actual: `⠣⠯⠐⠎⠀⠨⠝⠨⠍⠊⠥⠈⠗⠘⠂⠈⠿⠇⠉⠵⠀⠈⠍⠁` - current primary/reason: `exact` / `exact` -- `sentence_04.json` #937: 혼다 CR-V는 예상보다 크고 강한 차다. 준중형 스포츠유틸리티차(SUV)로 규정되지만, 동급에선 공간도 넉넉하고 충분한 힘도 갖췄다. 6년 만에 6세대 완전변경 모델로 힘과 덩치를 모두 키워서 돌아왔다. - - expected: `⠚⠷⠊⠀⠴⠠⠠⠉⠗⠤⠰⠠⠧⠲⠉⠵⠀⠌⠇⠶⠘⠥⠊⠀` - - actual: `⠚⠷⠊⠀⠴⠠⠠⠉⠗⠤⠰⠠⠧⠲⠉⠵⠀⠌⠇⠶⠘⠥⠊⠀` +- `sentence_04.json` #883: 그룹 방탄소년단(BTS) 슈가의 첫 공식 솔로 음반 ‘D-데이’(D-DAY)가 발매 당일 한터차트 기준 107만장이 넘게 팔려나가며 첫날 판매량으로는 K팝 솔로 가수 최고 기록을 경신했다. + - expected: `⠈⠪⠐⠍⠃⠀⠘⠶⠓⠒⠠⠥⠉⠡⠊⠒⠦⠄⠴⠠⠠⠃⠞⠎` + - actual: `⠈⠪⠐⠍⠃⠀⠘⠶⠓⠒⠠⠥⠉⠡⠊⠒⠦⠄⠴⠠⠠⠃⠞⠎` - current primary/reason: `exact` / `exact` Representative `localized_mismatch` samples: -- `sentence_01.json` #342: MBX DAY는 쟁글(Xangle) 블록체인 파운데이션 위크의 일환으로 진행된 프로그램으로 마브렉스의 게임파이(Game-Fi) 전략 및 신규 프로젝트 ‘메타월드: 모두의마블’ 소개, 바이낸스와의 패널 토론 등 다양한 세션이 열렸다. - - expected: `⠄⠴⠠⠛⠁⠍⠑⠤⠠⠋⠊⠠⠴⠀⠨⠾⠐⠜⠁⠀⠑⠕⠆⠀` - - actual: `⠄⠴⠠⠛⠁⠍⠑⠤⠰⠠⠋⠊⠠⠴⠀⠨⠾⠐⠜⠁⠀⠑⠕⠆` - - first differing cell (zero-based): 113 +- `sentence_01.json` #7967: 네비웍스가 개발한 가상훈련 플랫폼 VTB-X(Virtual Training Block)을 바탕으로 만들어진 XR 훈련은 시뮬레이션, AI(인공지능) 기반 시나리오 자동 생성, 자동 평가 시스템 등이 구축됐다. + - expected: `⠀⠴⠠⠠⠧⠞⠃⠤⠠⠭⠐⠣⠠⠧⠊⠗⠞⠥⠁⠇⠀⠠⠞⠗` + - actual: `⠀⠴⠠⠠⠧⠞⠃⠤⠰⠠⠭⠐⠣⠠⠧⠊⠗⠞⠥⠁⠇⠀⠠⠞` + - first differing cell (zero-based): 41 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_02.json` #639: 충남대학교(총장 이진숙)가 대학이 보유한 기술과 인력을 지역 산업체에 맞춤형으로 제공해 기술사업화 플랫폼을 구축하는 ‘2023년도 지역산업 연계 대학 Open-Lab(오픈랩) 육성지원 사업’에 최종 선정됐다. - - expected: `⠁⠀⠴⠠⠕⠏⠢⠤⠠⠇⠁⠃⠦⠄⠥⠙⠵⠐⠗⠃⠠⠴⠀⠩` - - actual: `⠁⠀⠴⠠⠕⠏⠢⠤⠰⠠⠇⠁⠃⠦⠄⠥⠙⠵⠐⠗⠃⠠⠴⠀` - - first differing cell (zero-based): 150 +- `sentence_02.json` #5190: 이어진 2부에서는 “청년들의 시작(START)과 성장(UP)! 대전청이 응원합니다.”를 모토로 ‘종이비행기 날리기’ 국가대표의 강의와 공연이 결합된 ‘Lecture Concert’를 진행해 참관한 ‘UNI-C.O.N.그룹’ 구성원들의 뜨거운 반응을 이끌어 냈다. + - expected: `⠦⠴⠠⠠⠥⠝⠊⠤⠠⠉⠲⠠⠕⠲⠠⠝⠲⠈⠪⠐⠍⠃⠴⠄` + - actual: `⠦⠴⠠⠠⠥⠝⠊⠤⠰⠠⠉⠲⠠⠕⠲⠠⠝⠲⠈⠪⠐⠍⠃⠴` + - first differing cell (zero-based): 183 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_03.json` #669: 환자들은 수술 후 컴퓨터 단층촬영(CT)이나 자기공명영상(MRI)을 통해 검사를 진행했다. 이후 추적관찰에선 X선 촬영(X-Ray)을 활용했고 시각통증척도(VAS)를 비롯한 설문조사도 함께 실시했다. - - expected: `⠂⠻⠦⠄⠴⠠⠭⠤⠠⠗⠁⠽⠠⠴⠮⠀⠚⠧⠂⠬⠶⠚⠗⠌` - - actual: `⠂⠻⠦⠄⠴⠠⠭⠤⠰⠠⠗⠁⠽⠠⠴⠮⠀⠚⠧⠂⠬⠶⠚⠗` - - first differing cell (zero-based): 125 - - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_04.json` #1793: 선다이오드(Sundiode)는 ‘SID 디스플레이 위크 2023’의 아이-존(i-Zone) 전시회에서 ‘수직적층형 마이크로 LED’ 기술로 최우수 시제품상(Best Prototype Award)을 수상했다고 30일 밝혔다. - - expected: `⠤⠨⠷⠦⠄⠴⠊⠤⠠⠵⠐⠕⠠⠴⠀⠨⠾⠠⠕⠚⠽⠝⠠⠎` - - actual: `⠤⠨⠷⠦⠄⠴⠊⠤⠰⠠⠵⠐⠕⠠⠴⠀⠨⠾⠠⠕⠚⠽⠝⠠` - - first differing cell (zero-based): 69 +- `sentence_03.json` #7591: 알파드는 다양한 주행 상황에서도 최상의 승차감을 보여주겠다고 작정한 차다. 도요타 TNGA-K(Toyota New Global Architecture-K) 플랫폼을 기반으로 차체 강성을 높였고 소음·진동(NVH) 저감 설계가 반영된 결과다. + - expected: `⠴⠠⠠⠞⠝⠛⠁⠤⠠⠅⠐⠣⠠⠞⠕⠽⠕⠞⠁⠀⠠⠝⠑⠺` + - actual: `⠴⠠⠠⠞⠝⠛⠁⠤⠰⠠⠅⠐⠣⠠⠞⠕⠽⠕⠞⠁⠀⠠⠝⠑` + - first differing cell (zero-based): 84 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` Representative `mismatch` samples: - `sentence_01.json` #149: 탠덤 OLED를 탄성있는 플라스틱 기판에 결합한 것이 LG디스플레이의 차량용 플라스틱(P)-OLED다. 차량용 P-OLED는 LCD 대비 소비전력을 60% 줄이고, 무게는 80%나 저감해 전기차 시대에도 적합한 디스플레이라는 평가다. - expected: `⠮⠐⠣⠠⠪⠓⠕⠁⠴⠐⠣⠠⠏⠐⠜⠤⠠⠠⠕⠇⠫⠲⠊⠲` - - actual: `⠮⠐⠣⠠⠪⠓⠕⠁⠦⠄⠴⠠⠏⠠⠴⠤⠰⠠⠠⠕⠇⠫⠲⠊` + - actual: `⠮⠐⠣⠠⠪⠓⠕⠁⠦⠄⠴⠠⠏⠠⠴⠤⠴⠠⠠⠕⠇⠫⠲⠊` - first differing cell (zero-based): 87 - current primary/reason: `corpus_suspect` / `rule34_roman_indicator_before_opening_parenthesis` -- `sentence_02.json` #344: 한편 이장우 대전시장은 이틀간의 대만 방문을 마무리하고 싱가포르로 이동해 18일까지 Merck(머크) 사, 국립싱가포르대학 바이오연구단, A-STAR 및 바이오폴리스 등 바이오산업 관련 해외 기업과 연구기관을 방문할 예정이다. - - expected: `⠥⠡⠈⠍⠊⠒⠐⠀⠴⠠⠁⠤⠠⠠⠌⠜⠲⠀⠑⠕⠆⠀⠘⠣` - - actual: `⠥⠡⠈⠍⠊⠒⠐⠀⠀⠠⠁⠔⠠⠠⠎⠞⠁⠗⠀⠀⠑⠕⠆⠀` - - first differing cell (zero-based): 122 +- `sentence_02.json` #420: 대전대학교(총장 남상호) LINC3.0사업단(단장 이영환)은 지난 6일 특화 분야 ICC(기업협업) 참여학과 중심의 기술개발 프로그램인 ‘2023 혜화 All-SET 기술사업화’ 운영을 위한 선정평가를 진행했다고 7일 밝혔다. + - expected: `⠥⠠⠴⠀⠴⠠⠠⠇⠔⠉⠼⠉⠲⠚⠇⠎⠃⠊⠒⠦⠄⠊⠒⠨` + - actual: `⠥⠠⠴⠀⠴⠠⠠⠇⠊⠝⠉⠼⠉⠲⠚⠇⠎⠃⠊⠒⠦⠄⠊⠒` + - first differing cell (zero-based): 30 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_03.json` #669: 환자들은 수술 후 컴퓨터 단층촬영(CT)이나 자기공명영상(MRI)을 통해 검사를 진행했다. 이후 추적관찰에선 X선 촬영(X-Ray)을 활용했고 시각통증척도(VAS)를 비롯한 설문조사도 함께 실시했다. - - expected: `⠂⠻⠦⠄⠴⠠⠭⠤⠠⠗⠁⠽⠠⠴⠮⠀⠚⠧⠂⠬⠶⠚⠗⠌` - - actual: `⠂⠻⠦⠄⠴⠠⠭⠤⠰⠠⠗⠁⠽⠠⠴⠮⠀⠚⠧⠂⠬⠶⠚⠗` - - first differing cell (zero-based): 125 +- `sentence_03.json` #947: BMW코리아 미래재단이 ‘2023 서울안전한마당’에 이동식 에너지 저장소(ESS)인 ‘넥스트 그린 투-고(NEXT GREEN TO-GO)’ 부스를 마련하고 체험형 교육 프로그램을 운영한다. + - expected: `⠤⠈⠥⠦⠄⠴⠠⠠⠠⠝⠑⠭⠞⠀⠛⠗⠑⠢⠀⠞⠕⠤⠛⠠` + - actual: `⠤⠈⠥⠦⠄⠴⠠⠠⠝⠑⠭⠞⠀⠠⠠⠛⠗⠑⠢⠀⠠⠠⠞⠕` + - first differing cell (zero-based): 103 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_04.json` #883: 그룹 방탄소년단(BTS) 슈가의 첫 공식 솔로 음반 ‘D-데이’(D-DAY)가 발매 당일 한터차트 기준 107만장이 넘게 팔려나가며 첫날 판매량으로는 K팝 솔로 가수 최고 기록을 경신했다. - - expected: `⠙⠲⠤⠊⠝⠕⠴⠄⠦⠄⠴⠠⠙⠤⠠⠠⠐⠙⠠⠴⠫⠀⠘⠂` - - actual: `⠙⠲⠤⠊⠝⠕⠴⠄⠴⠐⠣⠠⠙⠤⠠⠠⠐⠙⠐⠜⠲⠫⠀⠘` - - first differing cell (zero-based): 65 +- `sentence_04.json` #1528: 서부발전은 18일(현지시간) 오만에서 오만수전력조달공사(OPWP)가 주최한 ‘오만 마나 500㎿ 태양광발전 계약 서명식’에 파트너사인 프랑스 EDF-R과 함께 참석했다고 밝혔다. + - expected: `⠣⠶⠠⠪⠀⠴⠠⠠⠑⠙⠋⠤⠰⠠⠗⠲⠈⠧⠀⠚⠢⠠⠈⠝` + - actual: `⠣⠶⠠⠪⠀⠴⠠⠠⠫⠋⠤⠰⠠⠗⠲⠈⠧⠀⠚⠢⠠⠈⠝⠀` + - first differing cell (zero-based): 139 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` ### `uppercase_ascii_segments_joined_by_ampersand_capitalization` -Of the 439 candidates, 95 are the actual `pending_rule_review` subcluster. The other 344 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 439 candidates, 68 are the actual `pending_rule_review` subcluster. The other 371 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. -For this output-signature audit, 118 mismatches were evaluable and 1 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. +For this output-signature audit, 78 mismatches were evaluable and 1 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. Localized first-difference transitions: @@ -3160,10 +3096,8 @@ Localized first-difference transitions: Mismatch primary-class distribution: -- `comparison_method`: 11 - `corpus_suspect`: 10 -- `pending_rule_review`: 95 -- `unsupported_character_review`: 2 +- `pending_rule_review`: 68 Representative `exact` samples: @@ -3210,21 +3144,19 @@ Representative `mismatch` samples: - first differing cell (zero-based): 164 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` - `sentence_04.json` #19: 이날 행사에는 구 대표 외에 권봉석 (주)LG 최고운영책임자(COO), 박일평 LG사이언스파크대표(사장)를 비롯해 각 계열사 최고기술책임자(CTO), 최고데이터책임자(CDO), 최고인사책임자(CHO) 등이 참석해 국내 이공계 R&D 인재 400여명과 만났다. - - expected: `⠁⠕⠢⠨⠦⠄⠴⠠⠠⠉⠕⠕⠠⠴⠐⠀⠘⠁⠕⠂⠙⠻⠀⠴` - - actual: `⠁⠕⠢⠨⠦⠄⠴⠠⠉⠠⠕⠠⠕⠠⠴⠐⠀⠘⠁⠕⠂⠙⠻⠀` - - first differing cell (zero-based): 60 + - expected: `⠁⠕⠢⠨⠦⠄⠴⠠⠠⠉⠓⠕⠠⠴⠀⠊⠪⠶⠕⠀⠰⠣⠢⠠` + - actual: `⠁⠕⠢⠨⠦⠄⠴⠠⠉⠠⠓⠠⠕⠠⠴⠀⠊⠪⠶⠕⠀⠰⠣⠢` + - first differing cell (zero-based): 188 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` ### `uppercase_roman_headword_closed_multiword_parenthetical` -Of the 175 candidates, 74 are the actual `pending_rule_review` subcluster. The other 101 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 175 candidates, 53 are the actual `pending_rule_review` subcluster. The other 122 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. Mismatch primary-class distribution: -- `comparison_method`: 5 -- `corpus_suspect`: 7 -- `pending_rule_review`: 74 -- `unsupported_character_review`: 2 +- `corpus_suspect`: 9 +- `pending_rule_review`: 53 Representative `exact` samples: @@ -3236,9 +3168,9 @@ Representative `exact` samples: - expected: `⠠⠛⠰⠾⠚⠜⠶⠊⠗⠦⠄⠰⠿⠨⠶⠀⠈⠕⠢⠠⠪⠶⠍⠠` - actual: `⠠⠛⠰⠾⠚⠜⠶⠊⠗⠦⠄⠰⠿⠨⠶⠀⠈⠕⠢⠠⠪⠶⠍⠠` - current primary/reason: `exact` / `exact` -- `sentence_03.json` #8513: GITEX(Gulf Information Technology Exhibition)는 1981년부터 개최된 중동 최대의 ICT(정보통신기술) 전시회다. 우리나라에서는 강도현 과기정통부 정보통신정책실장을 단장으로, 과기정통부와 정보통신산업진흥원(NIPA), 한국소프트웨서산업협회(KOSA)와 60개의 국내 디지털 기업들이 참여 - - expected: `⠴⠠⠠⠛⠊⠞⠑⠭⠐⠣⠠⠛⠥⠇⠋⠀⠠⠔⠿⠍⠁⠰⠝⠀` - - actual: `⠴⠠⠠⠛⠊⠞⠑⠭⠐⠣⠠⠛⠥⠇⠋⠀⠠⠔⠿⠍⠁⠰⠝⠀` +- `sentence_03.json` #6968: 홈플러스의 자체 브랜드 PB(Private Brand) 상품 200여종도 몽골 시장에 진출했다. K-푸드(Korean-Food) 열풍 전진기지로 몽골 현지 ‘서클(CIRCLE)’ 그룹이 운영하는 할인점을 택했다. + - expected: `⠚⠥⠢⠙⠮⠐⠎⠠⠪⠺⠀⠨⠰⠝⠀⠘⠪⠐⠗⠒⠊⠪⠀⠴` + - actual: `⠚⠥⠢⠙⠮⠐⠎⠠⠪⠺⠀⠨⠰⠝⠀⠘⠪⠐⠗⠒⠊⠪⠀⠴` - current primary/reason: `exact` / `exact` - `sentence_04.json` #2384: 마술사들의 등용문인 국제마술대회는 국내 최초로 세계마술연맹(FISM)의 인증을 받은 ‘FISM QC(Qualified Contest) BIMF’라는 이름으로 더욱 특별하게 진행된다. - expected: `⠑⠠⠯⠇⠊⠮⠺⠀⠊⠪⠶⠬⠶⠑⠛⠟⠀⠈⠍⠁⠨⠝⠑⠠` @@ -3259,31 +3191,25 @@ Representative `mismatch` samples: - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` - `sentence_03.json` #3330: 앞서 양사는 지난 3월 그린수소·암모니아의 원활한 생산·공급·활용을 위한 특수목적법인(SPC) 알 파탄 엘텍유브이씨 그린에너지 LLC(AL FATTAN LTechUVC Green Energy LLC)를 설립한 바 있다. - expected: `⠟⠝⠉⠎⠨⠕⠀⠴⠰⠠⠠⠇⠇⠉⠐⠣⠰⠠⠠⠁⠇⠀⠠⠠` - - actual: `⠟⠝⠉⠎⠨⠕⠀⠴⠠⠠⠠⠇⠇⠉⠐⠣⠰⠠⠠⠁⠇⠀⠋⠁` + - actual: `⠟⠝⠉⠎⠨⠕⠀⠴⠠⠠⠇⠇⠉⠐⠣⠰⠠⠠⠁⠇⠀⠠⠠⠋` - first differing cell (zero-based): 129 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_04.json` #6699: 한국가스공사(사장 최연혜)는 지난 2019년부터 에너지 효율 개선을 위한 EERS(Energy Efficiency Resource Standards) 사업을 추진해 약 7만5000메가와트(MW)의 에너지를 절감했다고 밝혔다. - - expected: `⠗⠚⠒⠀⠴⠠⠠⠑⠑⠗⠎⠐⠣⠠⠢⠻⠛⠽⠀⠠⠑⠖⠊⠉` - - actual: `⠗⠚⠒⠀⠴⠠⠠⠑⠻⠎⠐⠣⠠⠢⠻⠛⠽⠀⠠⠑⠖⠊⠉⠊` - - first differing cell (zero-based): 71 +- `sentence_04.json` #2928: 이밖에 미국에서 많은 구독자와 공신력 있는 외식 전문지인 ‘QSR(Quick Service Restaurant)’ 매거진과 ‘매쉬드(Mashed)’를 통해 K-치킨의 대표 브랜드로 소개된 바 있다. + - expected: `⠠⠟⠎⠗⠐⠣⠠⠟⠥⠊⠉⠅⠀⠠⠎⠻⠧⠊⠉⠑⠀⠠⠗⠑` + - actual: `⠠⠟⠎⠗⠐⠣⠠⠟⠅⠀⠠⠎⠻⠧⠊⠉⠑⠀⠠⠗⠑⠌⠁⠥` + - first differing cell (zero-based): 66 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` ### `uppercase_roman_run_followed_by_hyphen_digits` -Of the 571 candidates, 362 are the actual `pending_rule_review` subcluster. The other 209 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. - -For this output-signature audit, 377 mismatches were evaluable and 262 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. - -Localized first-difference transitions: +Of the 571 candidates, 50 are the actual `pending_rule_review` subcluster. The other 521 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. -- `U+2834 ⠴ -> U+2800 ⠀`: 260 -- `U+2834 ⠴ -> U+2820 ⠠`: 2 +For this output-signature audit, 67 mismatches were evaluable and 0 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. Mismatch primary-class distribution: -- `corpus_suspect`: 12 -- `pending_rule_review`: 362 -- `unsupported_character_review`: 3 +- `corpus_suspect`: 17 +- `pending_rule_review`: 50 Representative `exact` samples: @@ -3316,35 +3242,34 @@ Representative `mismatch` samples: - actual: `⠗⠕⠂⠚⠥⠴⠄⠀⠴⠐⠣⠠⠃⠇⠥⠑⠀⠠⠱⠁⠇⠑⠐⠜` - first differing cell (zero-based): 114 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_03.json` #921: 미국 외교정책연구소(FPRI)의 선임 연구원 로버트 리는 “러시아가 T-55 전차 일부를 우선 후방 지역에 배치하고, 장거리 포사격을 하는 데 이용할 것”이라고 예상했다. - - expected: `⠦⠐⠎⠠⠕⠣⠫⠀⠴⠠⠞⠤⠼⠑⠑⠀⠨⠾⠰⠣⠀⠕⠂⠘` - - actual: `⠦⠐⠎⠠⠕⠣⠫⠀⠀⠠⠞⠔⠼⠑⠑⠀⠀⠨⠾⠰⠣⠀⠕⠂` - - first differing cell (zero-based): 63 +- `sentence_03.json` #1607: 오픈AI의 챗GPT(GPT-4)가 소믈리에로 데뷔했다. 와인 플랫폼 서비스 ‘칠링’이 챗GPT를 기반으로 한 인공지능(AI) 소믈리에 기능을 추가한 것이다. + - expected: `⠠⠠⠛⠏⠞⠤⠼⠙⠐⠜⠲⠫⠀⠠⠥⠑⠮⠐⠕⠝⠐⠥⠀⠊` + - actual: `⠠⠠⠛⠏⠞⠤⠼⠙⠴⠐⠜⠲⠫⠀⠠⠥⠑⠮⠐⠕⠝⠐⠥⠀` + - first differing cell (zero-based): 30 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_04.json` #323: 사단급 규모의 상륙군과 대형수송함(LPH) 독도함, 미 강습상륙함(LHD) 마킨 아일랜드함 등 30여척, 미 F-35 전투기·육군 아파치 공격헬기(AH-64)·마린온 상륙기동헬기 등 항공기 70여대, 상륙돌격장갑차(KAAV) 50여대 등 연합·합동전력이 투입됐다. - - expected: `⠱⠰⠹⠐⠀⠑⠕⠀⠴⠠⠋⠤⠼⠉⠑⠀⠨⠾⠓⠍⠈⠕⠐⠆` - - actual: `⠱⠰⠹⠐⠀⠑⠕⠀⠀⠠⠋⠔⠼⠉⠑⠀⠨⠾⠓⠍⠈⠕⠐⠆` - - first differing cell (zero-based): 108 +- `sentence_04.json` #6606: 훈련전대는 해군·해병대 장병 420여 명(각 170여 명·250여 명)과 일출봉함(LST-Ⅱ, 4900t급), 상륙돌격장갑차(KAAV) 6대, K-808 차륜형장갑차 2대, K-55 자주포 2문, K-77 사격지휘장갑차 1대로 구성돼 있다. + - expected: `⠚⠢⠦⠄⠴⠠⠠⠇⠎⠞⠤⠠⠠⠊⠊⠂⠀⠼⠙⠊⠚⠚⠞⠲` + - actual: `⠚⠢⠦⠄⠴⠠⠠⠇⠌⠤⠠⠠⠊⠊⠂⠀⠼⠙⠊⠚⠚⠞⠲⠈` + - first differing cell (zero-based): 79 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` ### `uppercase_word_after_whitespace_continuing_ascii_roman_text` -Of the 1729 candidates, 878 are the actual `pending_rule_review` subcluster. The other 851 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. +Of the 1729 candidates, 544 are the actual `pending_rule_review` subcluster. The other 1185 candidates are exact or existing non-pending-primary controls; this membership alone does not reclassify them. -For this output-signature audit, 958 mismatches were evaluable and 4 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. +For this output-signature audit, 614 mismatches were evaluable and 4 have their first differing cell inside the detected structure's current-engine output range. The remaining mismatches are controls against causal over-attribution. Localized first-difference transitions: -- `U+2800 ⠀ -> U+2832 ⠲`: 2 +- `U+2800 ⠀ -> U+2832 ⠲`: 1 - `U+2801 ⠁ -> U+281C ⠜`: 1 -- `U+2820 ⠠ -> U+2825 ⠥`: 1 +- `U+280E ⠎ -> U+280C ⠌`: 1 +- `U+2811 ⠑ -> U+283B ⠻`: 1 Mismatch primary-class distribution: -- `comparison_method`: 9 -- `corpus_suspect`: 60 -- `pending_rule_review`: 878 -- `unsupported_character_review`: 11 +- `corpus_suspect`: 70 +- `pending_rule_review`: 544 Representative `exact` samples: @@ -3372,10 +3297,15 @@ Representative `localized_mismatch` samples: - actual: `⠠⠕⠂⠦⠄⠴⠠⠠⠜⠠⠴⠈⠧⠀⠟⠈⠿⠨⠕⠉⠪⠶⠦⠄` - first differing cell (zero-based): 15 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` -- `sentence_02.json` #5670: 친구청소년쉼터의 대표적인 ‘UP UP CAMP’ 프로그램은 ‘청소년들이 정상에 오르며(UP) 인내와 성취감, 자신감을 더한다(UP)’는 목적에서 붙여진 이름으로, 올해로 8회째를 운영되고 있는 시즌제 프로그램이다. - - expected: `⠹⠟⠀⠠⠦⠴⠠⠠⠠⠥⠏⠀⠥⠏⠀⠉⠁⠍⠏⠠⠄⠴⠄⠀` - - actual: `⠹⠟⠀⠠⠦⠴⠠⠠⠥⠏⠀⠠⠠⠥⠏⠀⠠⠠⠉⠁⠍⠏⠴⠄` - - first differing cell (zero-based): 31 +- `sentence_02.json` #14: 한국타이어앤테크놀로지㈜(대표이사 이수일, 이하 한국타이어)가 국제자동차연맹(FIA)이 주관하는 ‘FIA 주니어 ERC(FIA Junior ERC, 이하 주니어 ERC)’ 대회에 레이싱 타이어를 독점 공급한다. + - expected: `⠍⠉⠕⠎⠀⠴⠠⠠⠑⠗⠉⠦⠄⠴⠠⠠⠋⠊⠁⠀⠠⠚⠥⠝` + - actual: `⠍⠉⠕⠎⠀⠴⠠⠠⠻⠉⠦⠄⠴⠠⠠⠋⠊⠁⠀⠠⠚⠥⠝⠊` + - first differing cell (zero-based): 114 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` +- `sentence_03.json` #17921: 한국타이어앤테크놀로지㈜(대표이사 이수일, 이하 한국타이어)가 오는 8월 21일, 컴포트 타이어 브랜드 ‘키너지(Kinergy)’의 신제품인 사계절용 밸런스 타이어 ‘키너지 ST AS(Kinergy ST AS)’를 국내에 새롭게 출시한다. + - expected: `⠉⠎⠨⠕⠀⠴⠠⠠⠎⠞⠀⠠⠠⠁⠎⠐⠣⠠⠅⠔⠻⠛⠽⠀` + - actual: `⠉⠎⠨⠕⠀⠴⠠⠠⠌⠀⠠⠠⠁⠎⠐⠣⠠⠅⠔⠻⠛⠽⠀⠠` + - first differing cell (zero-based): 162 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` - `sentence_04.json` #3324: 삼성전자는 이날 생성형 인공지능(AI) 서버에 적용되는 서버용 SSD ‘PM1743’과 쿼드러플 레벨 셀(QLC) 낸드 기반 256TB SSD도 선보였다. - expected: `⠶⠀⠴⠠⠠⠎⠎⠙⠀⠠⠦⠠⠠⠏⠍⠼⠁⠛⠙⠉⠠⠴⠲⠈` @@ -3387,24 +3317,23 @@ Representative `mismatch` samples: - `sentence_01.json` #35: 삼성전자는 올해 CES에서 77인치 OLED TV를 처음으로 공개할 예정이다. 이는 지난 8월 ‘국제정보디스플레이학술대회(IMID) 2022’에서 삼성디스플레이가 공개한 77인치 퀀텀닷(QD)-OLED 패널을 탑재한 제품이다. - expected: `⠋⠏⠒⠓⠎⠢⠊⠄⠴⠐⠣⠟⠙⠐⠜⠤⠠⠠⠕⠇⠫⠲⠀⠙` - - actual: `⠋⠏⠒⠓⠎⠢⠊⠄⠀⠀⠦⠠⠠⠟⠙⠴⠔⠠⠠⠕⠇⠑⠙⠀` + - actual: `⠋⠏⠒⠓⠎⠢⠊⠄⠦⠄⠴⠠⠠⠟⠙⠠⠴⠤⠴⠠⠠⠕⠇⠫` - first differing cell (zero-based): 172 - - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` + - current primary/reason: `corpus_suspect` / `rule34_roman_indicator_before_opening_parenthesis` - `sentence_02.json` #14: 한국타이어앤테크놀로지㈜(대표이사 이수일, 이하 한국타이어)가 국제자동차연맹(FIA)이 주관하는 ‘FIA 주니어 ERC(FIA Junior ERC, 이하 주니어 ERC)’ 대회에 레이싱 타이어를 독점 공급한다. - - expected: `⠚⠒⠈⠍⠁⠓⠣⠕⠎⠗⠒⠓⠝⠋⠪⠉⠥⠂⠐⠥⠨⠕⠦⠄` - - actual: `` - - error: `Invalid symbol character` - - first differing cell (zero-based): 0 - - current primary/reason: `unsupported_character_review` / `unsupported_character_review` + - expected: `⠍⠉⠕⠎⠀⠴⠠⠠⠑⠗⠉⠦⠄⠴⠠⠠⠋⠊⠁⠀⠠⠚⠥⠝` + - actual: `⠍⠉⠕⠎⠀⠴⠠⠠⠻⠉⠦⠄⠴⠠⠠⠋⠊⠁⠀⠠⠚⠥⠝⠊` + - first differing cell (zero-based): 114 + - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` - `sentence_03.json` #48: WIS 전시장 내 KT DS 전시관에서는 실제 현장에 적용되는 통합 대시보드 화면과 위험 구역을 감지·경고하는 지능형 폐쇄회로(CC)TV를 통해 에스패스를 체험할 수 있다. - expected: `⠌⠠⠧⠗⠚⠽⠐⠥⠴⠐⠣⠠⠠⠉⠉⠐⠜⠠⠠⠞⠧⠲⠐⠮` - - actual: `⠌⠠⠧⠗⠚⠽⠐⠥⠦⠄⠴⠠⠉⠠⠉⠠⠴⠰⠠⠠⠞⠧⠲⠐` + - actual: `⠌⠠⠧⠗⠚⠽⠐⠥⠦⠄⠴⠠⠠⠉⠉⠠⠴⠴⠠⠠⠞⠧⠲⠐` - first differing cell (zero-based): 128 - current primary/reason: `corpus_suspect` / `rule34_roman_indicator_before_opening_parenthesis` -- `sentence_04.json` #1859: 멀티클라우드 매니지먼트 기업 디딤365(대표 장민호)는 공공기관과 지자체를 위한 업무용 협업도구 ‘디딤365 NAVER WORKS for 공공용’이 한국지능정보사회진흥원(NIA) ‘디지털서비스 이용지원시스템’과 조달청 ‘디지털서비스몰’ 디지털서비스에 등록됐다고 1일 밝혔다. - - expected: `⠼⠉⠋⠑⠀⠴⠠⠠⠝⠁⠧⠻⠀⠠⠠⠐⠺⠎⠀⠿⠲⠀⠈⠿` - - actual: `⠼⠉⠋⠑⠀⠴⠠⠠⠠⠝⠁⠧⠻⠀⠐⠺⠎⠀⠿⠲⠠⠄⠀⠈` - - first differing cell (zero-based): 112 +- `sentence_04.json` #2340: 제네시스 GV70 전동화 모델(전기차 버전)이 독일 자동차 전문지의 비교 평가에서 1위를 차지했다. 비교 대상인 아우디 Q8 e-트론, 벤츠 EQE SUV(스포츠유틸리티차)를 제쳤다. + - expected: `⠕⠀⠴⠠⠟⠼⠓⠀⠰⠑⠲⠤⠓⠪⠐⠷⠐⠀⠘⠝⠒⠰⠪⠀` + - actual: `⠕⠀⠴⠠⠟⠼⠓⠀⠑⠲⠤⠓⠪⠐⠷⠐⠀⠘⠝⠒⠰⠪⠀⠴` + - first differing cell (zero-based): 117 - current primary/reason: `pending_rule_review` / `foreign_text_rule_review` ## UEB grade-1 first-difference cohorts @@ -3413,10 +3342,10 @@ These cohorts are defined by both an input boundary and the sentence's actual fi | Cohort | Candidates | Exact controls | Mismatch | Target localized | Reverse | |---|---:|---:|---:|---:|---:| -| `allcaps_roman_run_beginning_with_pure_letter_shortform` | 4120 | 2521 | 1599 | 789 | 48 | -| `uppercase_ascii_run_immediately_after_digit_in_roman_sequence` | 1896 | 1267 | 629 | 0 | 2 | -| `uppercase_ascii_run_immediately_after_hyphen_in_roman_sequence` | 952 | 271 | 681 | 184 | 6 | -| `pure_allcaps_segment_before_hyphen_and_multi_allcaps_segment_after` | 448 | 226 | 222 | 0 | 3 | +| `allcaps_roman_run_beginning_with_pure_letter_shortform` | 3242 | 2809 | 433 | 2 | 50 | +| `uppercase_ascii_run_immediately_after_digit_in_roman_sequence` | 1896 | 1569 | 327 | 0 | 3 | +| `uppercase_ascii_run_immediately_after_hyphen_in_roman_sequence` | 952 | 724 | 228 | 5 | 2 | +| `pure_allcaps_segment_before_hyphen_and_multi_allcaps_segment_after` | 448 | 359 | 89 | 0 | 0 | ### All-caps shortform prefix at an attached Roman entry @@ -3438,23 +3367,14 @@ Same-surface controls demonstrate why primary classes must not be changed by coh | Surface | Candidates | Exact | Mismatch | Target-localized | |---|---:|---:|---:|---:| -| `AC` | 159 | 89 | 70 | 66 | -| `LLM` | 176 | 2 | 174 | 152 | -| `CD` | 65 | 30 | 35 | 12 | -| `IMM` | 27 | 3 | 24 | 22 | -| `AG` | 18 | 9 | 9 | 0 | -| `GDP` | 359 | 0 | 359 | 353 | +| `AC` | 159 | 156 | 3 | 0 | +| `LLM` | 176 | 125 | 51 | 0 | +| `CD` | 65 | 51 | 14 | 0 | +| `IMM` | 27 | 23 | 4 | 0 | +| `AG` | 18 | 15 | 3 | 0 | +| `GDP` | 359 | 335 | 24 | 0 | | `WD` | 10 | 10 | 0 | 0 | -- `AC` exact control: `sentence_01.json` #1122 — 손흥민(토트넘)이 선발 출전했지만 소속팀 패배를 지켜봤다. 토트넘은 15일(이하 한국시간) 이탈리아 밀라노에 있는 산시로에서 AC 밀란(이탈리아)과 유럽축구연맹(UEFA) 주최 2022-2023시즌 챔피언스리그(UCL) 16강 1차전 원정 경기를 치렀다. -- `AC` localized mismatch: `sentence_01.json` #1707 — 토트넘은 9일(한국시간) 영국 런던의 토트넘 홋스퍼 스타디움에서 열린 2022-2023 유럽축구연맹(UEFA) 챔피언스리그(UCL) 16강 2차전에서 AC밀란(이탈리아)과 득점 없이 0-0으로 비겼다. - -- `LLM` exact control: `sentence_03.json` #12275 — 이와 관련 업스테이지 측은 “이번 결과는 소형 LLM(SLM)의 기준이라고 할 수 있는 300억 매개변수(30B) 이하 사이즈로 글로벌 최고 성능 모델에 올라 더욱 의미가 깊다”고 강조했다. -- `LLM` localized mismatch: `sentence_01.json` #312 — 엔비디아가 대규모 언어 모델(LLM)과 음성 AI를 주제로 엔비디아 AI 개발자 밋업을 개최한다. 이번 개발자 밋업은 오는 17일 오후 2시부터 5시까지 온라인으로 진행된다. - -- `CD` exact control: `sentence_01.json` #4788 — 국제표준 제정절차는 신규작업표준안(NP) → 작업반 초안(WD) → 위원회안(CD) → 국제표준안(DIS) → 최종국제표준안(FDIS) → 국제표준(IS) 제정 순으로 진행된다. -- `CD` localized mismatch: `sentence_01.json` #11231 — 이날 고아인은 PT 성공률이 가장 높은 CD(팀장)라는 별명처럼 여유로운 태도로 정재훈(이기우 분) 대표의 게임회사 광고 PT를 마쳤다. - ### Uppercase immediately after a digit UEB rule 5.6.1 says the numeric indicator establishes grade-1 mode, and rule 5.6.2 ends that mode at a space, hyphen, dash, or grade-1 terminator; a capitalization indicator is not a terminator. Korean rule 35 likewise keeps Roman letters and an adjacent number in one Roman section. The PDF's printed `3b`, `3B`, and `3m` examples distinguish the three following-letter classes: lowercase `a`-`j` retains `⠰` because its cells are numeric, a capital uses its capitalization indicator, and lowercase `k`-`z` needs no extra indicator. `Braille4All`, `M4G`, and `W1N` independently confirm the capital boundary inside longer alphanumeric strings. Before the engine change this cohort contained 330 localized `⠠ -> ⠰` cases. A blanket digit-to-letter removal reached 67,000/83,528 (+317) but was rejected: retaining `⠰` only for lowercase `a`-`j` recovers 10 exact cases and raises the result to 67,010. The wrapper control also exposes a separate routing boundary: a numeric run already preceded by an ASCII letter is part of the Roman identifier, not a fresh rule-69 compact unit. Preserving the rule-69 path for genuinely numeric-leading units while excluding that identifier boundary adds 2 more exact cases, for a final 67,012 (+329). The uppercase cohort moves from 756 exact / 1,140 mismatch / 330 target-localized / 1 reverse to 1,078 exact / 818 mismatch / 0 target-localized / 1 reverse. The remaining non-exact members are not attributed to the removed uppercase transition: their sentence-level first difference may lie in another structure and remains under its existing primary class. This numeric state change remains separate from both the complete-shortform guard and the hyphen continuation boundary below. @@ -3463,6 +3383,12 @@ UEB rule 5.6.1 says the numeric indicator establishes grade-1 mode, and rule 5.6 UEB rule 5.7.2 prints `CD-ROM` with one grade-1 indicator before `CD` and no second grade-1 indicator after the hyphen. Korean rule 29 similarly uses one Roman span for consecutive Roman text. Before the engine change, the broad diagnostic contained 952 candidates / 32 exact / 920 mismatch, with 312 localized `⠠ -> ⠰` and 2 reverse transitions. A blanket uppercase-suffix removal reached 67,222 (+210) but made the broad cohort's single-capital controls such as `Around-U`, `DALL-E`, `ISMS-P`, and `USB-C` non-exact; it was rejected. Requiring only a two-letter uppercase suffix reached 67,162 (+150) but regressed the mixed-prefix exact control `Ko-LLM`; it was also rejected. The retained boundary matches the complete PDF shape: the immediately adjacent prefix is a pure-uppercase letter segment and the immediately adjacent suffix is a pure-uppercase segment of at least two letters. It reaches 67,138 (+126) while preserving all 32 baseline exact controls. The broad diagnostic now contains 952 candidates / 158 exact / 794 mismatch, with 157 localized `⠠ -> ⠰` and 3 reverse transitions. The dedicated `pure_allcaps_segment_before_hyphen_and_multi_allcaps_segment_after` row reports only the implemented subset; broad mixed-case and single-capital members remain controls or pending review. `K-ALM` is the one new reverse surface but was already a mismatch before this change, not an exact regression. Digit-hyphen forms such as `F-35` remain excluded, and the complete-shortform guard still legitimately precedes `CD` in `CD-ROM`. +### Attached Korean-to-Roman hyphen boundary + +Korean rule 29 opens a Roman section for Roman text in a Korean sentence, rule 33 keeps the hyphen as punctuation at the Korean/Roman boundary, and rules 35-36 own adjacent alphanumerics and Roman numerals. The retained production gate therefore routes an immediately attached capital-led or multi-letter Roman identifier as prose (`하쿠토-R`, `기장-KBO`, `온다-life`), but leaves a single lowercase variable and any token with an explicit mathematical operator on the mathematics route (`값-x`, `값-X+1`). The analyzer applies the encoder's selective U+2160-U+217F compatibility expansion before testing the word grammar, so `천궁-Ⅱ` is audited as the equivalent `천궁-II` boundary. + +Before this gate, the deterministic cohort contained 105 candidates / 62 exact / 43 mismatch. It now contains 105 candidates / 84 exact / 21 mismatch. The complete corpus exact-ID audit moved from 75,704 to 75,785 (+22), and every new exact ID belongs to this cohort; no formerly exact ID was lost. Cohort membership is input-only and never changes a primary class, so the remaining non-exact members retain their independent review causes. + ## Roman-entry residual cohorts after grade-1 localization @@ -3472,11 +3398,11 @@ Korean rule 29 requires a Roman indicator before Roman text in a Korean sentence | Cohort | Candidates | Exact controls | Mismatch | Pending | Corpus suspect | Localized `⠴ -> blank` | Reverse `blank -> ⠴` | |---|---:|---:|---:|---:|---:|---:|---:| -| `roman_hyphenated_word_after_whitespace_following_korean_word` | 361 | 86 | 275 | 264 | 6 | 139 | 0 | -| `roman_parenthetical_headword_after_whitespace_following_korean_word` | 4695 | 3610 | 1085 | 989 | 43 | 67 | 0 | -| `korean_prefixed_roman_parenthetical_followed_by_allcaps_hyphen_suffix` | 11 | 0 | 11 | 6 | 5 | 5 | 0 | +| `roman_hyphenated_word_after_whitespace_following_korean_word` | 361 | 277 | 84 | 73 | 11 | 6 | 0 | +| `roman_parenthetical_headword_after_whitespace_following_korean_word` | 4695 | 3945 | 750 | 640 | 109 | 2 | 0 | +| `korean_prefixed_roman_parenthetical_followed_by_allcaps_hyphen_suffix` | 11 | 1 | 10 | 0 | 10 | 0 | 0 | -The whitespace parenthetical-headword cohort also retains 1 localized `⠠ -> ⠴` and 3 localized `⠴ -> ⠰` cases as separate transitions; they are not folded into the target. The exact controls demonstrate that the broad structure is already correct in many sentences, while the attached parenthetical-hyphen cohort has no exact control and includes cases already classified by the stricter rule-34 reference-order contradiction. Consequently none of these measurements authorizes an engine change; they are deterministic pending/corpus-review diagnostics only. +The whitespace parenthetical-headword cohort also retains 2 localized `⠠ -> ⠴` and 0 localized `⠴ -> ⠰` cases as separate transitions; they are not folded into the target. The exact controls demonstrate that the broad structure is already correct in many sentences, while the attached parenthetical-hyphen cohort has no exact control and includes cases already classified by the stricter rule-34 reference-order contradiction. Consequently none of these measurements authorizes an engine change; they are deterministic pending/corpus-review diagnostics only. ### Consecutive Roman uppercase-word re-entry @@ -3484,13 +3410,13 @@ Korean rule 29 explicitly says that when two or more Roman items occur consecuti Analyzer checkpoint `266e70c` fixed the pre-change baseline at 1,729 candidates, 386 exact controls, 1,343 mismatches, 1,272 pending members, and 385/1,343 evaluable mismatches localized to the current re-entry signature. Those localized transitions were 365 `⠠ -> ⠴`, 16 `⠰ -> ⠴`, no reverse `⠴ -> ⠠`, and four other transitions. Exact controls include contexts where the first Roman word already opened token-level mode, while parenthesized/mixed-token examples expose the duplicate event. -The generalized fix makes an explicit `EnterEnglish` event idempotent when final emit state is already inside a Roman section; it neither names an input nor changes a new section's entry. The current measurement is 1729 candidates, 771 exact controls, 958 mismatches, 878 pending members, and 4/958 localized mismatches. Current target counts are 0 `⠠ -> ⠴`, 0 `⠰ -> ⠴`, and 0 reverse `⠴ -> ⠠`. Cohort exact controls increase by 275, while corpus-wide exact matches rise from 67,138 to 67,442 (+304); corrected prefixes that still differ later remain mismatches, and applications outside this strict input gate account for the remaining net gain. The raw/residual reverse `⠴ -> ⠠` totals remain 68/65 before and after the change. The complete custom standard summary is 5,141 total, 5,141 success, 0 failure, and 0 skipped. +The generalized fix makes an explicit `EnterEnglish` event idempotent when final emit state is already inside a Roman section; it neither names an input nor changes a new section's entry. The current measurement is 1729 candidates, 1115 exact controls, 614 mismatches, 544 pending members, and 4/614 localized mismatches. Current target counts are 0 `⠠ -> ⠴`, 0 `⠰ -> ⠴`, and 0 reverse `⠴ -> ⠠`. Cohort exact controls increase by 275, while corpus-wide exact matches rise from 67,138 to 67,442 (+304); corrected prefixes that still differ later remain mismatches, and applications outside this strict input gate account for the remaining net gain. The raw/residual reverse `⠴ -> ⠠` totals remain 68/65 before and after the change. The complete custom standard summary is 5,141 total, 5,141 success, 0 failure, and 0 skipped. ### Closed Roman parenthetical after a non-ASCII-letter boundary Korean rule 34 (2024 Korean-rules PDF p.29) prints the opening enclosure before Roman entry in `링컨(Lincoln)`: the opening sequence is `⠦⠄⠴`. This output-position cohort finds a closed, non-nested parenthetical whose body begins with an ASCII letter and whose opening does not immediately follow another ASCII letter, then locates its complete current-engine signature without consulting the reference. Its localized boundary includes the two current output cells immediately before the opening plus the first three entry cells, so rule-11 math spacing can be separated from a difference later inside the parenthetical. Direct function-call shapes such as `f(x)` are excluded. The PDF's math rule 6 (p.59), rule 12 (pp.63-64), and rule 45 (p.75) nevertheless leave standalone `(x)` and other Roman-letter parenthetical mathematics as counterexamples, so the surface gate is not an engine-routing predicate. -The cross-cutting input cohort contains 63959 candidates: 53122 exact controls and 10837 mismatches. Mismatch primary classes remain unchanged: 9343 `pending_rule_review`, 1114 `corpus_suspect`, 242 `comparison_method`, and 138 `unsupported_character_review`. Of 10837 evaluable mismatches, 1130 have the first difference at the detected leading-spacing/entry boundary; these include 438 `U+2826 ⠦ -> U+2834 ⠴`, 28 `U+2834 ⠴ -> U+2826 ⠦`, and 330 `U+2826 ⠦ -> U+2800 ⠀` transitions; the exact localized reverse `U+2800 ⠀ -> U+2826 ⠦` occurs 0 times. After all earlier localized cohorts and this cohort are excluded, the raw-to-residual target count is 471 -> 26, the Roman-indicator reverse count is 33 -> 2, and the spacing target/reverse counts are 445 -> 69 and 1 -> 1. The short full-encoder form `웹3(Web3)` emits the PDF opening order as a routing control, while whitespace and quote boundaries reproduce the current Roman-first opening. Representative localized samples, with shard and index, are retained in the generated cluster sample table. Because exact controls are abundant and the PDF does not make this input shape semantically sufficient to exclude mathematics, no engine change or primary reclassification is inferred. The one raw/residual spacing reverse is a Korean-body `△(교육)` layout boundary, not a Roman-parenthetical member, and remains a separate rule-72/layout review. +The cross-cutting input cohort contains 63959 candidates: 57175 exact controls and 6784 mismatches. Mismatch primary classes remain unchanged: 5599 `pending_rule_review`, 1181 `corpus_suspect`, 0 `comparison_method`, and 4 `unsupported_character_review`. Of 6784 evaluable mismatches, 418 have the first difference at the detected leading-spacing/entry boundary; these include 114 `U+2826 ⠦ -> U+2834 ⠴`, 36 `U+2834 ⠴ -> U+2826 ⠦`, and 29 `U+2826 ⠦ -> U+2800 ⠀` transitions; the exact localized reverse `U+2800 ⠀ -> U+2826 ⠦` occurs 5 times. After all earlier localized cohorts and this cohort are excluded, the raw-to-residual target count is 122 -> 5, the Roman-indicator reverse count is 41 -> 2, and the spacing target/reverse counts are 63 -> 21 and 5 -> 0. The short full-encoder form `웹3(Web3)` emits the PDF opening order as a routing control, while whitespace and quote boundaries reproduce the current Roman-first opening. Representative localized samples, with shard and index, are retained in the generated cluster sample table. Because exact controls are abundant and the PDF does not make this input shape semantically sufficient to exclude mathematics, no engine change or primary reclassification is inferred. The one raw/residual spacing reverse is a Korean-body `△(교육)` layout boundary, not a Roman-parenthetical member, and remains a separate rule-72/layout review. The HCA-style headword-expansion gate now supplies one narrow prose-routing premise. Korean rules 29 and 34 require a fresh Roman section and continuous Roman transcription for a complete all-capitals headword followed by a closed, multiword Roman expansion. The implementation requires a headword of at least two ASCII capitals and at least two ASCII-letter words inside the parenthesis; digits, operators, scripts, nested brackets, and alphanumeric text after the closing parenthesis remain math-owned controls. Rule 34's `링컨(Lincoln)은` additionally proves that attached Korean text after the closing parenthesis stays on the prose route; the same boundary now covers the multiword form without admitting ASCII letters or digits in the trailer. Together these boundaries change 71 corpus cases from mismatch to exact and raise this cohort's exact controls from 17 to 87. The residual members still measure contraction, capitalization, earlier sentence differences, unsupported characters, and reference-order conflicts rather than authorizing a wider surface-form rule. @@ -3519,33 +3445,33 @@ Corpus contradictions remain a separate gate: identical inputs with conflicting UEB 10.12.1 (2024 UEB PDF pp.191-192, printed pp.163-164) says not to use a contraction when letters within an abbreviation or acronym are known to be pronounced separately, but to use the contraction in case of doubt. Its official `DAR` example writes separate `a` and `r` cells, while UEB 10.12.2's official `START` example uses the `ar` groupsign. Both full-encoder controls pass. Thus an uppercase surface containing `AR` does not itself supply the pronunciation or lexical meaning needed to select either form. -The output-localized cohort contains 1022 candidates, 371 exact controls, and 651 mismatches. Existing mismatch primary classes are preserved: 627 `pending_rule_review`, 15 `corpus_suspect`, 3 `comparison_method`, and 6 `unsupported_character_review`. Of 651 evaluable mismatches, 407 have their first difference inside the detected current-engine run: 400 `U+2801 ⠁ -> U+281C ⠜` and 0 `U+281C ⠜ -> U+2801 ⠁`. Across raw pending transitions and the final residual after localized cohorts, the target count is 408 -> 7; the reverse is 2 -> 2. Inputs denoting separately pronounced initialisms such as corpus `AR`/`ARS` coexist with exact or officially contracted controls. This is deterministic evidence for a pronunciation-dependent pending cohort, not an engine rule or primary reclassification. Representative shard/index samples are retained above. +The output-localized cohort contains 1022 candidates, 480 exact controls, and 542 mismatches. Existing mismatch primary classes are preserved: 521 `pending_rule_review`, 18 `corpus_suspect`, 0 `comparison_method`, and 3 `unsupported_character_review`. Of 542 evaluable mismatches, 416 have their first difference inside the detected current-engine run: 411 `U+2801 ⠁ -> U+281C ⠜` and 0 `U+281C ⠜ -> U+2801 ⠁`. Across raw pending transitions and the final residual after localized cohorts, the target count is 413 -> 1; the reverse is 2 -> 2. Inputs denoting separately pronounced initialisms such as corpus `AR`/`ARS` coexist with exact or officially contracted controls. This is deterministic evidence for a pronunciation-dependent pending cohort, not an engine rule or primary reclassification. Representative shard/index samples are retained above. ### Roman run after a closed Roman enclosure Korean rule 29 places one Roman indicator/terminator pair around consecutive Roman items, with `Los Angeles` and `Table of Contents` as its printed multiword examples. Rule 34 separately omits the Roman terminator when Roman text is enclosed by quotation marks or parentheses. Neither printed rule states whether a later Roman run after the enclosure, intervening punctuation, and whitespace is a continuation of that section or a fresh section. The input gate therefore detects only the structural boundary; it does not label the later run as semantically new. -The cohort contains 1093 candidates, 144 exact controls, and 949 mismatches. Existing mismatch primary classes are preserved: 611 `pending_rule_review`, 331 `corpus_suspect`, and 7 `unsupported_character_review`. Of 949 evaluable mismatches, 260 are output-localized to the current later-run signature plus its one leading boundary cell: 256 `U+2834 ⠴ -> U+2830 ⠰` and 0 `U+2830 ⠰ -> U+2834 ⠴`. The target counted 359 raw and 333 residual cases before this cohort; it is now 48 final residual. Across raw and final residual maps, `U+2830 ⠰ -> U+2834 ⠴` is 2 -> 0. Exact controls coexist with both directions, and 321 mismatches are already independently identified corpus contradictions. Without a printed fresh-entry example or semantic enclosure model, changing continuation state would be reference-fitting; this remains a deterministic pending/corpus-review cohort only. Representative shard/index samples are retained above. +The cohort contains 1093 candidates, 580 exact controls, and 513 mismatches. Existing mismatch primary classes are preserved: 163 `pending_rule_review`, 349 `corpus_suspect`, and 1 `unsupported_character_review`. Of 513 evaluable mismatches, 5 are output-localized to the current later-run signature plus its one leading boundary cell: 0 `U+2834 ⠴ -> U+2830 ⠰` and 0 `U+2830 ⠰ -> U+2834 ⠴`. The target counted 2 raw and 333 residual cases before this cohort; it is now 1 final residual. Across raw and final residual maps, `U+2830 ⠰ -> U+2834 ⠴` is 1 -> 0. Exact controls coexist with both directions, and 321 mismatches are already independently identified corpus contradictions. Without a printed fresh-entry example or semantic enclosure model, changing continuation state would be reference-fitting; this remains a deterministic pending/corpus-review cohort only. Representative shard/index samples are retained above. ### Uppercase Roman runs containing `ED` UEB 10.12.1 (2024 UEB PDF pp.191-192, printed pp.163-164) requires separate letters when an abbreviation's letters are known to be pronounced separately, and its official `OED` example writes `e` and `d` separately. Rule 10.12.2 otherwise uses contractions; its official `BEd` example uses the `ed` groupsign. Both full-encoder controls pass. Consequently an uppercase `ED` surface cannot establish pronunciation or abbreviation semantics by spelling alone. -The cohort contains 816 candidates, 334 exact controls, and 482 mismatches. Existing mismatch primary classes remain 411 `pending_rule_review`, 65 `corpus_suspect`, 1 `comparison_method`, and 5 `unsupported_character_review`. Of 482 evaluable mismatches, 330 are localized to the detected current-engine run: 327 `U+2811 ⠑ -> U+282B ⠫` and 0 `U+282B ⠫ -> U+2811 ⠑`. Across raw pending transitions and the final residual after localized cohorts, the target is 331 -> 3 and the reverse is 0 -> 0. Corpus initialisms such as `LED` and `GED` require external pronunciation knowledge, while exact and official controls preserve contraction-bearing outcomes. No engine change or primary reclassification is made; representative shard/index samples are retained above. +The cohort contains 816 candidates, 362 exact controls, and 454 mismatches. Existing mismatch primary classes remain 386 `pending_rule_review`, 68 `corpus_suspect`, 0 `comparison_method`, and 0 `unsupported_character_review`. Of 454 evaluable mismatches, 341 are localized to the detected current-engine run: 339 `U+2811 ⠑ -> U+282B ⠫` and 0 `U+282B ⠫ -> U+2811 ⠑`. Across raw pending transitions and the final residual after localized cohorts, the target is 344 -> 3 and the reverse is 0 -> 0. Corpus initialisms such as `LED` and `GED` require external pronunciation knowledge, while exact and official controls preserve contraction-bearing outcomes. No engine change or primary reclassification is made; representative shard/index samples are retained above. ### Uppercase Roman runs containing `ST` -UEB 10.12.1 (2024 UEB PDF pp.191-192, printed pp.163-164) says not to use a contraction when letters within an abbreviation or acronym are known to be pronounced separately, but to use the contraction in case of doubt. Rule 10.12.2 requires contractions in other abbreviations and acronyms. Consequently an uppercase `ST` surface alone cannot determine whether `s` + `t` or the `st` groupsign is required. The current cohort contains 1479 candidates, 703 exact controls, and 776 mismatches; primary classes remain 723 `pending_rule_review`, 35 `corpus_suspect`, 13 `comparison_method`, and 5 `unsupported_character_review`. Of 776 evaluable mismatches, 460 are localized to the detected current-engine run: 452 `U+280E ⠎ -> U+280C ⠌` and 1 `U+280C ⠌ -> U+280E ⠎`. The target's raw-to-residual count is 465 -> 13, and the reverse is 2 -> 1. Exact controls such as `STAYG`, `KAIST`, `DGIST`, and `POSTECH` coexist with localized mismatches such as `WSTS`, `HUST`, `OST`, and `USTR`; representative shard/index samples are preserved in the cluster table. This lexical/pronunciation distinction cannot be inferred from the input-only spelling, so no engine change or primary reclassification is made. +UEB 10.12.1 (2024 UEB PDF pp.191-192, printed pp.163-164) says not to use a contraction when letters within an abbreviation or acronym are known to be pronounced separately, but to use the contraction in case of doubt. Rule 10.12.2 requires contractions in other abbreviations and acronyms. Consequently an uppercase `ST` surface alone cannot determine whether `s` + `t` or the `st` groupsign is required. The current cohort contains 1479 candidates, 815 exact controls, and 664 mismatches; primary classes remain 625 `pending_rule_review`, 39 `corpus_suspect`, 0 `comparison_method`, and 0 `unsupported_character_review`. Of 664 evaluable mismatches, 473 are localized to the detected current-engine run: 466 `U+280E ⠎ -> U+280C ⠌` and 1 `U+280C ⠌ -> U+280E ⠎`. The target's raw-to-residual count is 473 -> 6, and the reverse is 2 -> 1. Exact controls such as `STAYG`, `KAIST`, `DGIST`, and `POSTECH` coexist with localized mismatches such as `WSTS`, `HUST`, `OST`, and `USTR`; representative shard/index samples are preserved in the cluster table. This lexical/pronunciation distinction cannot be inferred from the input-only spelling, so no engine change or primary reclassification is made. ### Uppercase segments joined by ampersand: capitalization extent UEB 8.4.2 (2024 UEB PDF p.118, printed p.90) terminates capitals word mode at a nonalphabetic symbol. UEB 3.1.1 and the capitalization examples (PDF pp.51 and 120, printed pp.23 and 92) consequently print `AT&T` as `⠠⠠⠁⠞⠈⠯⠠⠞` and `B&B` as `⠠⠃⠈⠯⠠⠃`: Roman mode remains continuous, but capitalization restarts for each ASCII-letter segment. The detector accepts only complete uppercase ASCII segments joined directly by `&`, with the same non-alphanumeric outer boundaries as the existing Roman-ampersand rule, and requires the run to begin its whitespace-delimited token. Korean-attached and punctuation-prefixed occurrences stay outside the change scope. -At the analyzer-only checkpoint this cohort contained 439 candidates, 1 exact control, 438 mismatches, and 220 first differences localized inside the independently reproduced Korean-context signature. After the general capitalization correction it contains 439 candidates, 321 exact controls, and 118 mismatches. Existing remaining mismatch primaries are 95 `pending_rule_review`, 10 `corpus_suspect`, 11 `comparison_method`, and 2 `unsupported_character_review`. Of 118 evaluable mismatches, 1 have their first difference inside that signature. The sole pre-change exact member contained lowercase Roman text later in the same whitespace token and was outside the production predicate's actual change scope; its primary outcome was preserved. The cohort table above retains the transition distribution and shard/index samples. Capitalization extent is fixed by the official symbol examples and requires neither pronunciation nor corpus semantics; the diagnostic never changes a primary class. +At the analyzer-only checkpoint this cohort contained 439 candidates, 1 exact control, 438 mismatches, and 220 first differences localized inside the independently reproduced Korean-context signature. After the general capitalization correction it contains 439 candidates, 361 exact controls, and 78 mismatches. Existing remaining mismatch primaries are 68 `pending_rule_review`, 10 `corpus_suspect`, 0 `comparison_method`, and 0 `unsupported_character_review`. Of 78 evaluable mismatches, 1 have their first difference inside that signature. The sole pre-change exact member contained lowercase Roman text later in the same whitespace token and was outside the production predicate's actual change scope; its primary outcome was preserved. The cohort table above retains the transition distribution and shard/index samples. Capitalization extent is fixed by the official symbol examples and requires neither pronunciation nor corpus semantics; the diagnostic never changes a primary class. ### Capitals-word nonletter change-scope audit -This input-only scope exactly mirrors the former token predicate where it could incorrectly carry capitals-word handling across a nonletter. UEB 8.4.2 terminates that mode at the nonletter. Trailing nonletters after the final uppercase run are excluded because their output is unchanged. Before the correction all 1,733 candidates were mismatches and none was exact. The current run has 1733 candidates, 854 exact controls, and 879 mismatches. A complete exact-ID set audit found 852 newly exact cases and zero cases lost from the 68,439-exact baseline, yielding 69,291/83,528 (82.96%). Two deliberately rejected wider predicates exposed why the boundary matters: requiring an entirely uppercase-only token lost 87 former exact cases, while treating every initial uppercase run as token-level capitals mode lost 33 mixed-case controls such as the UEB `TVOntario` class by bypassing its required capitals terminator. The retained predicate pre-emits only when the initial run has at least two capitals and every ASCII letter in the token is uppercase; Rule 28 independently restarts capitalization after the nonletter. This cohort remains a regression audit only: membership does not assign a primary class or attribute a first difference. +This input-only scope exactly mirrors the former token predicate where it could incorrectly carry capitals-word handling across a nonletter. UEB 8.4.2 terminates that mode at the nonletter. Trailing nonletters after the final uppercase run are excluded because their output is unchanged. Before the correction all 1,733 candidates were mismatches and none was exact. The current run has 1733 candidates, 1270 exact controls, and 463 mismatches. A complete exact-ID set audit found 852 newly exact cases and zero cases lost from the 68,439-exact baseline, yielding 69,291/83,528 (82.96%). Two deliberately rejected wider predicates exposed why the boundary matters: requiring an entirely uppercase-only token lost 87 former exact cases, while treating every initial uppercase run as token-level capitals mode lost 33 mixed-case controls such as the UEB `TVOntario` class by bypassing its required capitals terminator. The retained predicate pre-emits only when the initial run has at least two capitals and every ASCII letter in the token is uppercase; Rule 28 independently restarts capitalization after the nonletter. This cohort remains a regression audit only: membership does not assign a primary class or attribute a first difference. ### Attached Roman segments joined by ampersand @@ -3555,7 +3481,7 @@ The baseline was 802 candidates / 0 exact / 802 mismatch, preserving 776 `pendin The implemented gate shares the analyzer's complete-run predicate: one or more non-empty ASCII-letter segments joined directly by `&`, with non-alphanumeric outer boundaries. It keeps the existing Roman mode open and suppresses only rule 71's redundant wrapper around that ampersand. Spaced `Marks & Spencer`, Korean `가&나`, empty segments, and outer digit continuations remain outside the gate. Official full-encoder controls `AT&T` and `B&B` pass, and the Korean rule-71 spaced example `종이접기 & 클레이아트` retains its independent `⠴⠈⠯⠲` section. -After the change the same cohort contains 802 candidates, 608 exact and 194 mismatch. Current mismatch primary classes remain evaluator-owned: 166 `pending_rule_review`, 11 `corpus_suspect`, 2 `unsupported_character_review`, and 15 `comparison_method`. The localizer evaluates all 194 remaining mismatches and finds 0 target-localized cases (0 `U+2808 ⠈ -> U+2832 ⠲`); current raw-to-residual target count is 81 -> 79, while `U+2832 ⠲ -> U+2808 ⠈` remains 0 raw / 0 residual. Cohort exact increases by 273 and corpus-wide exact increases by the same 273, from 67,442 to 67,715. Because every changed input must satisfy this shared predicate and the baseline had no exact member, this boundary has no exact regression. The remaining 529 candidates differ elsewhere or retain an existing comparison, corpus-suspect, unsupported, or pending cause. Representative shard/index samples are retained above. +After the change the same cohort contains 802 candidates, 687 exact and 115 mismatch. Current mismatch primary classes remain evaluator-owned: 103 `pending_rule_review`, 12 `corpus_suspect`, 0 `unsupported_character_review`, and 0 `comparison_method`. The localizer evaluates all 115 remaining mismatches and finds 0 target-localized cases (0 `U+2808 ⠈ -> U+2832 ⠲`); current raw-to-residual target count is 24 -> 21, while `U+2832 ⠲ -> U+2808 ⠈` remains 0 raw / 0 residual. Cohort exact increases by 273 and corpus-wide exact increases by the same 273, from 67,442 to 67,715. Because every changed input must satisfy this shared predicate and the baseline had no exact member, this boundary has no exact regression. The remaining 529 candidates differ elsewhere or retain an existing comparison, corpus-suspect, unsupported, or pending cause. Representative shard/index samples are retained above. ### Ampersand before an attached ASCII Roman segment @@ -3563,9 +3489,9 @@ This is the residual boundary not covered by the implemented `A&B` gate. Officia The diagnostic checkpoint baseline was 30 candidates / 0 corpus exact / 30 mismatch, preserving 26 `pending_rule_review` and 4 `corpus_suspect` primary classes. Its then-current Rule-71 exit localizer found 16/30 first differences: 15 `U+2820 ⠠ -> U+2832 ⠲` and 1 `U+2834 ⠴ -> U+2832 ⠲`; both localized reverses were zero. The official full-encoder `&c`, `AT&T`, and `B&B` examples were the positive controls, and the spaced Korean Rule-71 example was the negative boundary control. -The implemented rule is limited to an ampersand followed by a complete attached ASCII-letter segment, with no left ASCII alphanumeric or adjacent ampersand and no trailing digit continuation. Rule 71 opens one Roman section before `&`; rule 29 now leaves it open for the attached letters. It does not name a corpus input or inspect a reference. After the change, the cohort has 30 candidates, 12 exact and 18 mismatch; the corpus-wide exact total rises by the same 12 cases, from 68,175 to 68,187, so no exact regression occurs inside or outside this gate. The 16 former exit/re-entry transitions disappear; 12 become exact and 4 remain mismatches at a different PDF-conflicting boundary. Existing mismatch primary classes remain 14 `pending_rule_review`, 4 `corpus_suspect`, 0 `comparison_method`, and 0 `unsupported_character_review`. +The implemented rule is limited to an ampersand followed by a complete attached ASCII-letter segment, with no left ASCII alphanumeric or adjacent ampersand and no trailing digit continuation. Rule 71 opens one Roman section before `&`; rule 29 now leaves it open for the attached letters. It does not name a corpus input or inspect a reference. After the change, the cohort has 30 candidates, 14 exact and 16 mismatch; the corpus-wide exact total rises by the same 12 cases, from 68,175 to 68,187, so no exact regression occurs inside or outside this gate. The 16 former exit/re-entry transitions disappear; 12 become exact and 4 remain mismatches at a different PDF-conflicting boundary. Existing mismatch primary classes remain 12 `pending_rule_review`, 4 `corpus_suspect`, 0 `comparison_method`, and 0 `unsupported_character_review`. -Of 18 current evaluable mismatches, 4 are localized to the occurrence-specific entry signature: 3 `U+2808 ⠈ -> U+2834 ⠴` where a parenthesized `&TEAM` reference omits Rule 71's required Roman indicator, and 1 `U+2834 ⠴ -> U+2820 ⠠` where a `드림&Dream` reference inserts another Roman indicator inside the same continuous section. Their raw-to-residual counts are 7 -> 4 and 61 -> 55; the corresponding reverse maps are 0 -> 0 and 56 -> 38. These four cases remain conservative corpus/PDF-reference review rather than widening or undoing the rule. Exact samples such as `&TEAM`, `과학&ICT`, and `한국&K리츠`, with shard/index above, are current controls. Primary classifications are never changed by this cohort. +Of 16 current evaluable mismatches, 4 are localized to the occurrence-specific entry signature: 3 `U+2808 ⠈ -> U+2834 ⠴` where a parenthesized `&TEAM` reference omits Rule 71's required Roman indicator, and 1 `U+2834 ⠴ -> U+2820 ⠠` where a `드림&Dream` reference inserts another Roman indicator inside the same continuous section. Their raw-to-residual counts are 22 -> 19 and 26 -> 25; the corresponding reverse maps are 0 -> 0 and 61 -> 47. These four cases remain conservative corpus/PDF-reference review rather than widening or undoing the rule. Exact samples such as `&TEAM`, `과학&ICT`, and `한국&K리츠`, with shard/index above, are current controls. Primary classifications are never changed by this cohort. ### ASCII apostrophe between Roman letter runs @@ -3573,7 +3499,7 @@ This output-localized cohort requires a straight ASCII apostrophe with an ASCII The diagnostic checkpoint had 147 candidates / 0 exact / 147 mismatch. Its occurrence-specific localizer put 112 first differences on the apostrophe boundary: 30 expected apostrophe cell `U+2804 ⠄` versus actual capital indicator `U+2820 ⠠`, and 82 expected `U+2804 ⠄` versus actual Roman indicator `U+2834 ⠴`; neither target had a localized reverse. The implementation keeps only a same-token apostrophe with ASCII letters immediately on both sides in the current Roman section, delegates its cell to the existing UEB section-7 punctuation encoder, and restarts capitals mode for an uppercase run after the nonalphabetic apostrophe. Korean Rule 37 still suppresses whole-word contractions at a Roman entry. A rejected broader route made the detached `Guitar' Listening` control exact, so the final gate explicitly does not look through whitespace. -After the correction the cohort has 147 candidates, 68 exact controls, and 79 mismatches. Of 79 evaluable residual mismatches, 17 place their first difference inside the independently encoded current signature, but those residual transitions are other letter/spacing differences rather than either former apostrophe transition. The complete corpus exact-ID audit found 68 newly exact cases and zero formerly exact cases lost, raising the corpus total from 69,291 to 69,359. Cohort membership itself never rewrites a primary class; the engine result may make a member exact or expose an independently classified residual. +After the correction the cohort has 147 candidates, 106 exact controls, and 41 mismatches. Of 41 evaluable residual mismatches, 3 place their first difference inside the independently encoded current signature, but those residual transitions are other letter/spacing differences rather than either former apostrophe transition. The complete corpus exact-ID audit found 68 newly exact cases and zero formerly exact cases lost, raising the corpus total from 69,291 to 69,359. Cohort membership itself never rewrites a primary class; the engine result may make a member exact or expose an independently classified residual. ### Spaced comma between ASCII digit runs @@ -3581,7 +3507,7 @@ This output-localized cohort requires a comma immediately after an ASCII digit, The diagnostic baseline was 217 candidates / 7 exact / 210 mismatch, with 177 occurrence-specific `U+2810 ⠐ -> U+2802 ⠂` first differences and no localized reverse. Rule 41 had looked through `remaining_words`, incorrectly treating whitespace as if the following digit were attached. The implementation now inspects only the next character in the same token. It neither names a corpus input nor consults expected output; attached numbers and UEB punctuation remain owned by their existing routes. -After the correction, the cohort has 217 candidates / 182 exact / 35 mismatch. Existing mismatch primaries remain 34 `pending_rule_review`, 0 `corpus_suspect`, 1 `comparison_method`, and 0 `unsupported_character_review`. Of 35 evaluable current mismatches, 0 localize to the comma cell: 0 `U+2810 ⠐ -> U+2802 ⠂` and 0 `U+2802 ⠂ -> U+2810 ⠐`. The cohort gains 174 exact cases. Across all pending cases, the current raw-to-residual counts are 24 -> 6 for the target and 22 -> 16 for the reverse. The standard controls `9,375명`, `창세기 12,1-9`, and `근면, 검소, 협동은 ...` retain their PDF cells. No primary class is changed by the cohort. +After the correction, the cohort has 217 candidates / 195 exact / 22 mismatch. Existing mismatch primaries remain 22 `pending_rule_review`, 0 `corpus_suspect`, 0 `comparison_method`, and 0 `unsupported_character_review`. Of 22 evaluable current mismatches, 1 localize to the comma cell: 0 `U+2810 ⠐ -> U+2802 ⠂` and 1 `U+2802 ⠂ -> U+2810 ⠐`. The cohort gains 174 exact cases. Across all pending cases, the current raw-to-residual counts are 6 -> 6 for the target and 39 -> 38 for the reverse. The standard controls `9,375명`, `창세기 12,1-9`, and `근면, 검소, 협동은 ...` retain their PDF cells. No primary class is changed by the cohort. ### ASCII/Roman-tail comma before a digit-led Korean token @@ -3589,7 +3515,7 @@ This companion cohort is disjoint from the preceding digit-comma gate: the comma The diagnostic baseline was 58 candidates / 0 exact / 58 mismatch. Of those, 23 had `U+2810 ⠐ -> U+2802 ⠂` at the comma inside the independently encoded complete boundary signature and none had the reverse. The same rule-41 correction removes the cross-token ASCII-letter lookup; rule 33 and the existing English-symbol route then choose the punctuation from the actual surrounding scripts. -After the correction, this cohort has 58 candidates / 3 exact / 55 mismatch. Existing mismatch primaries remain 53 `pending_rule_review`, 2 `corpus_suspect`, 0 `comparison_method`, and 0 `unsupported_character_review`. Of 55 evaluable current mismatches, 22 localize to the comma-cell signature: 18 `U+2810 ⠐ -> U+2802 ⠂` and 4 `U+2802 ⠂ -> U+2810 ⠐`. Three cases become exact. The official rule-33 `KTX, 새마을호` boundary and UEB prose comma remain independent standard controls. The detector and localizer do not read expected output to choose a route, and membership does not change a primary class. +After the correction, this cohort has 58 candidates / 34 exact / 24 mismatch. Existing mismatch primaries remain 20 `pending_rule_review`, 4 `corpus_suspect`, 0 `comparison_method`, and 0 `unsupported_character_review`. Of 24 evaluable current mismatches, 0 localize to the comma-cell signature: 0 `U+2810 ⠐ -> U+2802 ⠂` and 0 `U+2802 ⠂ -> U+2810 ⠐`. Three cases become exact. The official rule-33 `KTX, 새마을호` boundary and UEB prose comma remain independent standard controls. The detector and localizer do not read expected output to choose a route, and membership does not change a primary class. ### Percent-point unit list comma @@ -3603,78 +3529,84 @@ Korean rule 49 (2024 Korean-rules PDF p.37, printed p.31) says that spacing foll The diagnostic baseline had 95 candidates / 0 exact / 95 mismatch. All 95 were in the exact former implementation scope; 74 first differences were at the inserted blank: 73 `U+2815 ⠕ -> U+2800 ⠀`, one `U+2823 ⠣ -> U+2800 ⠀`, and no localized reverse. The absence of a baseline exact member is the in-scope regression control. -After removing that input-correcting transformation, the cohort has 95 candidates / 72 exact / 23 mismatch, preserving 19 `pending_rule_review`, 3 `corpus_suspect`, 1 `comparison_method`, and 0 `unsupported_character_review` mismatch primaries. Of 0 evaluable current mismatches, 0 still localize to an inserted blank: 0 `U+2815 ⠕ -> U+2800 ⠀`, 0 `U+2823 ⠣ -> U+2800 ⠀`, and 0 `U+2800 ⠀ -> U+2815 ⠕`. Seventy-one cases become exact; the other 24 retain independent earlier differences. Corpus exact increases by the same 71 with zero exact-set regressions. The three explicitly spaced PDF forms remain full-encoder negative controls, so removing correction of missing input whitespace does not remove a printed space. The local rule-47 standard case had accidentally transcribed PDF physical p.36 `덮여 있다` as attached `덮여있다` while retaining the PDF's spaced braille; correcting that input transcription restores the complete 5,141/5,141 standard summary without an engine exception. +After removing that input-correcting transformation, the cohort has 95 candidates / 84 exact / 11 mismatch, preserving 8 `pending_rule_review`, 3 `corpus_suspect`, 0 `comparison_method`, and 0 `unsupported_character_review` mismatch primaries. Of 0 evaluable current mismatches, 0 still localize to an inserted blank: 0 `U+2815 ⠕ -> U+2800 ⠀`, 0 `U+2823 ⠣ -> U+2800 ⠀`, and 0 `U+2800 ⠀ -> U+2815 ⠕`. Seventy-one cases become exact; the other 24 retain independent earlier differences. Corpus exact increases by the same 71 with zero exact-set regressions. The three explicitly spaced PDF forms remain full-encoder negative controls, so removing correction of missing input whitespace does not remove a printed space. The local rule-47 standard case had accidentally transcribed PDF physical p.36 `덮여 있다` as attached `덮여있다` while retaining the PDF's spaced braille; correcting that input transcription restores the complete 5,141/5,141 standard summary without an engine exception. -Current uppercase-Roman hyphen-digits measurement: 571 candidates, 194 exact controls, 377 mismatches, 362 members in the actual `pending_rule_review` subcluster, and 262/377 evaluable mismatches whose first difference is inside the target run plus its entry boundary. It remains distinct from parenthesized digits and headword expansions; no engine change is inferred. +Current uppercase-Roman hyphen-digits measurement: 571 candidates, 504 exact controls, 67 mismatches, 50 members in the actual `pending_rule_review` subcluster, and 0/67 evaluable mismatches whose first difference is inside the target run plus its entry boundary. It remains distinct from parenthesized digits and headword expansions; no engine change is inferred. -Current uppercase alphanumeric Roman-digit sequence measurement: 3429 candidates, 2097 exact controls, 1332 mismatches, 1221 members in the actual `pending_rule_review` subcluster, and 150/1332 evaluable mismatches whose first difference is localized to the current entry cells. The target `⠴ -> blank` occurs 95 times and the reverse occurs 0 times. Korean rule 35 (2024 Korean-rules PDF pp.29-30, printed pp.23-24) explicitly treats `MP3`, `A4`, `KF94`, and `D-100` as Roman-number sequences. Math rules 11/12 (PDF pp.63-65, printed pp.57-59) separately route mathematical Roman notation without the prose Roman indicator and with two-cell spacing. Official UEB 6.5.1-6.5.2 (2024 UEB PDF p.96, printed p.68) determines numeric/grade-1 continuation only after the surrounding mode has been chosen. Thus surfaces such as `A-3` cannot be assigned identifier or variable semantics from this structure alone. Exact controls and both directions are retained; no engine change is inferred. +Current uppercase alphanumeric Roman-digit sequence measurement: 3429 candidates, 2861 exact controls, 568 mismatches, 462 members in the actual `pending_rule_review` subcluster, and 9/568 evaluable mismatches whose first difference is localized to the current entry cells. The target `⠴ -> blank` occurs 9 times and the reverse occurs 0 times. Korean rule 35 (2024 Korean-rules PDF pp.29-30, printed pp.23-24) explicitly treats `MP3`, `A4`, `KF94`, and `D-100` as Roman-number sequences. Math rules 11/12 (PDF pp.63-65, printed pp.57-59) separately route mathematical Roman notation without the prose Roman indicator and with two-cell spacing. Official UEB 6.5.1-6.5.2 (2024 UEB PDF p.96, printed p.68) determines numeric/grade-1 continuation only after the surrounding mode has been chosen. Thus surfaces such as `A-3` cannot be assigned identifier or variable semantics from this structure alone. Exact controls and both directions are retained; no engine change is inferred. -Current consecutive ASCII-Roman word-boundary measurement: 4679 candidates, 2358 exact controls, 2321 mismatches, 2183 members in the actual `pending_rule_review` subcluster, and 25/2321 evaluable mismatches whose first difference is localized to the current boundary cell. The target reference blank versus current terminator (`⠀ -> ⠲`) occurs 25 times; the exact reverse (`⠲ -> ⠀`) occurs 0 times. Korean rule 29 (2024 Korean-rules PDF p.26, printed p.20) explicitly treats consecutive Roman items as one Roman section, with one Roman indicator before the first item and one terminator after the last; its printed `Los Angeles` and `Table of Contents` examples are exact controls. Rule 34 (PDF p.29, printed p.23) separately omits the terminator before a closing enclosure, while official UEB 9.7.1 (2024 UEB PDF p.137, printed p.109) prints the multiword prose enclosure `plays (such as Romeo and Juliet)` with ordinary internal spaces and nested closing punctuation. Together they support preserving a complete letter-and-space Roman parenthetical as prose, but not a fragment containing a function-call opener, digit, operator, or nested bracket. The input-only gate does not decide whether punctuation-separated text such as `ESS /VPP` continues the same section, so that variant remains outside this cohort. Primary classes are preserved. Before the narrow token-routing change this cohort was 4,679 candidates / 2,058 exact / 2,621 mismatch, with 128 localized `⠀ -> ⠲` and zero reverse. The cause was the final `Letters)` token of a whitespace-split Roman parenthetical being rerouted as math, which made the preceding Roman word terminate early and introduced an extra blank. After excluding only a backwards-verified complete Roman parenthetical tail from math routing, the cohort is 2,132 exact / 2,547 mismatch, with 23 target-localized and zero reverse; corpus-wide exact likewise rises by 74, from 68,101 to 68,175. The 105 removed localized boundaries include 74 newly exact cases and 31 cases that still differ elsewhere. Remaining targets such as `SYNO PEM-1`, `ACE Fair(2020)`, and `Mnet K-POP` do not satisfy the complete-parenthetical gate and remain separate residuals rather than widening this rule. +Current consecutive ASCII-Roman word-boundary measurement: 4679 candidates, 3314 exact controls, 1365 mismatches, 1251 members in the actual `pending_rule_review` subcluster, and 5/1365 evaluable mismatches whose first difference is localized to the current boundary cell. The target reference blank versus current terminator (`⠀ -> ⠲`) occurs 4 times; the exact reverse (`⠲ -> ⠀`) occurs 0 times. Korean rule 29 (2024 Korean-rules PDF p.26, printed p.20) explicitly treats consecutive Roman items as one Roman section, with one Roman indicator before the first item and one terminator after the last; its printed `Los Angeles` and `Table of Contents` examples are exact controls. Rule 34 (PDF p.29, printed p.23) separately omits the terminator before a closing enclosure, while official UEB 9.7.1 (2024 UEB PDF p.137, printed p.109) prints the multiword prose enclosure `plays (such as Romeo and Juliet)` with ordinary internal spaces and nested closing punctuation. Together they support preserving a complete letter-and-space Roman parenthetical as prose, but not a fragment containing a function-call opener, digit, operator, or nested bracket. The input-only gate does not decide whether punctuation-separated text such as `ESS /VPP` continues the same section, so that variant remains outside this cohort. Primary classes are preserved. Before the narrow token-routing change this cohort was 4,679 candidates / 2,058 exact / 2,621 mismatch, with 128 localized `⠀ -> ⠲` and zero reverse. The cause was the final `Letters)` token of a whitespace-split Roman parenthetical being rerouted as math, which made the preceding Roman word terminate early and introduced an extra blank. After excluding only a backwards-verified complete Roman parenthetical tail from math routing, the cohort is 2,132 exact / 2,547 mismatch, with 23 target-localized and zero reverse; corpus-wide exact likewise rises by 74, from 68,101 to 68,175. The 105 removed localized boundaries include 74 newly exact cases and 31 cases that still differ elsewhere. Remaining targets such as `SYNO PEM-1`, `ACE Fair(2020)`, and `Mnet K-POP` do not satisfy the complete-parenthetical gate and remain separate residuals rather than widening this rule. -Current single-capital parenthesized-digits measurement: 1361 candidates, 0 exact controls, 1361 mismatches, 1357 members in the actual `pending_rule_review` subcluster, and 1342/1361 evaluable mismatches whose first difference is inside the target run plus its entry boundary. No engine change is inferred from the ambiguous prose/function surface form. +Current single-capital parenthesized-digits measurement: 1361 candidates, 1351 exact controls, 10 mismatches, 6 members in the actual `pending_rule_review` subcluster, and 4/10 evaluable mismatches whose first difference is inside the target run plus its entry boundary. No engine change is inferred from the ambiguous prose/function surface form. Current mixed Roman+Korean boundary before uppercase headword-expansion measurement: 10 candidates, 7 exact controls, 3 mismatches, 3 members in the actual `pending_rule_review` subcluster, and 2/3 evaluable mismatches whose first difference is localized to the later headword's entry boundary/output. The detector cannot be satisfied by the earlier Roman entry. The narrow rules-29/34 headword-expansion route is active; these residuals therefore identify a separate state or Roman-letter difference. -Current compact numeric+ASCII-suffix measurement: 2975 candidates, 1917 exact controls, 1058 mismatches, 985 members in the actual `pending_rule_review` subcluster, and 256/1058 evaluable mismatches whose first difference is inside the complete current-engine output signature or its immediate entry boundary. Rule 40 requires the numeric indicator and rule 69 requires Roman indicators around a Roman-written unit, but the input shape alone cannot prove that every ASCII suffix is a unit. +Current compact numeric+ASCII-suffix measurement: 2975 candidates, 2492 exact controls, 483 mismatches, 423 members in the actual `pending_rule_review` subcluster, and 41/483 evaluable mismatches whose first difference is inside the complete current-engine output signature or its immediate entry boundary. Rule 40 requires the numeric indicator and rule 69 requires Roman indicators around a Roman-written unit, but the input shape alone cannot prove that every ASCII suffix is a unit. | ASCII suffix | Candidates | Exact | Mismatch | Localized first diff | |---|---:|---:|---:|---:| -| `m` | 364 | 263 | 101 | 22 | -| `km` | 308 | 254 | 54 | 2 | -| `G` | 237 | 170 | 67 | 3 | -| `kg` | 209 | 176 | 33 | 4 | -| `D` | 167 | 118 | 49 | 5 | -| `p` | 140 | 20 | 120 | 84 | -| `g` | 138 | 100 | 38 | 8 | -| `t` | 128 | 86 | 42 | 17 | -| `M` | 104 | 80 | 24 | 6 | -| `cm` | 69 | 52 | 17 | 0 | -| `B` | 62 | 41 | 21 | 0 | -| `GB` | 61 | 29 | 32 | 8 | -| `GWh` | 57 | 42 | 15 | 9 | -| `ha` | 53 | 18 | 35 | 0 | -| `TV` | 52 | 41 | 11 | 1 | -| `S` | 45 | 31 | 14 | 0 | -| `GW` | 44 | 35 | 9 | 6 | -| `X` | 39 | 14 | 25 | 2 | -| `bp` | 39 | 3 | 36 | 18 | -| `K` | 38 | 13 | 25 | 1 | +| `m` | 364 | 312 | 52 | 4 | +| `km` | 308 | 286 | 22 | 2 | +| `G` | 237 | 212 | 25 | 1 | +| `kg` | 209 | 189 | 20 | 3 | +| `D` | 167 | 133 | 34 | 4 | +| `p` | 140 | 111 | 29 | 1 | +| `g` | 138 | 116 | 22 | 3 | +| `t` | 128 | 115 | 13 | 1 | +| `M` | 104 | 96 | 8 | 0 | +| `cm` | 69 | 58 | 11 | 0 | +| `B` | 62 | 60 | 2 | 0 | +| `GB` | 61 | 48 | 13 | 3 | +| `GWh` | 57 | 53 | 4 | 0 | +| `ha` | 53 | 51 | 2 | 0 | +| `TV` | 52 | 45 | 7 | 1 | +| `S` | 45 | 44 | 1 | 0 | +| `GW` | 44 | 42 | 2 | 0 | +| `X` | 39 | 25 | 14 | 0 | +| `bp` | 39 | 24 | 15 | 0 | +| `K` | 38 | 21 | 17 | 0 | | `MW` | 37 | 36 | 1 | 0 | -| `TURN` | 33 | 20 | 13 | 0 | -| `L` | 31 | 21 | 10 | 5 | -| `mm` | 29 | 20 | 9 | 1 | -| `egin` | 28 | 27 | 1 | 0 | +| `TURN` | 33 | 21 | 12 | 0 | +| `L` | 31 | 31 | 0 | 0 | +| `mm` | 29 | 24 | 5 | 1 | +| `egin` | 28 | 28 | 0 | 0 | Entry-boundary pre-fix baseline: 2,975 candidates, 1,649 exact controls, 1,326 mismatches, 1,259 pending members, and 356 localized first differences; the dominant reference number-sign versus current space transition accounted for 296 cases. Rules 68 and 69 already accept semantic Unicode compatibility-unit forms. The implementation derives compact ASCII spellings only from their all-letter NFKC decompositions, reuses the owning rule's PDF-defined cells, chooses the longest complete spelling, and rejects partial suffix matches. It does not extend recognition to separated English words or arbitrary corpus suffixes. The same cohort now has 1,759 exact controls, 1,216 mismatches, 1,148 pending members, and 261 localized first differences; corpus-wide exact matches moved from 66,436 to 66,546 (+110). Rule 69's printed `160㎎/㎗` example directly controls `160mg`; the additional full-encoder `240mg`/`240㎎` pair proves that recognition is invariant to the numeric value and also passes. Rule 68's printed `10,000㎡는 1㏊이다` example controls the `ha` spelling through U+33CA's NFKC decomposition; `15.2ha`/`15.2㏊` now passes without a spelling-specific output branch. The 53-case `ha` suffix control moved from 0 exact to 18 exact; its remaining 35 mismatches have independent later or surrounding differences. A full U+3300..U+33FF owner audit found 73 Rules 68/69 glyphs with pure-ASCII NFKC decompositions, 73 distinct spellings, and therefore no current duplicate-spelling owner collision. Production nevertheless groups every owner before resolution and excludes a spelling if any owner cells differ; a synthetic collision test proves that this is not first-wins behavior. An exhaustive test also compares every one of the 73 derived spellings with every owner glyph at both the unit-cell and full-encoder boundaries in a neutral Korean measurement context. That audit exposed the pre-existing explicit `cal` mapping's missing ordinary terminator; rule 69 requires the terminator, while the existing slash-boundary function removes it for the printed `cal/㎠/min` context. Restoring the ordinary terminator and extending that slash-continuation boundary made the audit pass without changing the corpus-wide 66,546 exact total. Ambiguous pure-English inputs remain on UEB: the standard controls `3m` and `4.m` are retained and pass, rather than being globally forced into a Korean unit route. -Current rule-69 ASCII-unit punctuation-boundary measurement: 440 candidates, 344 exact controls, 96 mismatches, 91 members in the actual `pending_rule_review` subcluster, and 16/96 evaluable mismatches whose first difference is localized to the unit-plus-punctuation output signature. PDF rule 69 requires a Roman terminator after a Roman-written unit in the ordinary case, while rules 33/34 omit it at the listed punctuation or enclosing-mark boundary; the rule 46 PDF example `체중(kg)` is the minimal parenthesized-unit control. Membership is restricted to rule-69 spellings already supported by the engine and does not infer unit semantics for arbitrary ASCII suffixes. +Current rule-69 ASCII-unit punctuation-boundary measurement: 440 candidates, 385 exact controls, 55 mismatches, 53 members in the actual `pending_rule_review` subcluster, and 3/55 evaluable mismatches whose first difference is localized to the unit-plus-punctuation output signature. PDF rule 69 requires a Roman terminator after a Roman-written unit in the ordinary case, while rules 33/34 omit it at the listed punctuation or enclosing-mark boundary; the rule 46 PDF example `체중(kg)` is the minimal parenthesized-unit control. Membership is restricted to rule-69 spellings already supported by the engine and does not infer unit semantics for arbitrary ASCII suffixes. Analyzer pre-fix baseline: 440 candidates, 0 exact controls, 440 mismatches, 435 pending members, and 9 signature-local first differences. After the generalized rule-33/34 boundary override and the matching non-math routing guard, the same cohort has 325 exact controls and 115 mismatches. Corpus-wide exact matches moved from 66,039 to 66,436 (+397); the additional gains are applications of the same boundary rule outside this strict ASCII detector, including compatibility-unit forms. The complete standard suite remains 5,141/5,141. -Current decimal-point measurement: 4546 candidates, 3447 exact controls, 1099 mismatches, 1042 members in the actual `pending_rule_review` subcluster, and 267/1099 evaluable mismatches whose first difference is inside the complete decimal-containing word's current-engine signature. Hangeul rules 43 and 48 keep an ASCII point between digits in the numeric sequence and encode it as the decimal point; rules 35 and 69 supply controls for adjacent Roman-number chains and Roman units. This is an implementation-candidate audit, not permission to specialize on a corpus reference. +Current decimal-point measurement: 4546 candidates, 4106 exact controls, 440 mismatches, 401 members in the actual `pending_rule_review` subcluster, and 127/440 evaluable mismatches whose first difference is inside the complete decimal-containing word's current-engine signature. Hangeul rules 43 and 48 keep an ASCII point between digits in the numeric sequence and encode it as the decimal point; rules 35 and 69 supply controls for adjacent Roman-number chains and Roman units. This is an implementation-candidate audit, not permission to specialize on a corpus reference. Analyzer checkpoint `ed20f99` recorded the pre-fix baseline as 4,546 candidates, 2,584 exact controls, 1,962 mismatches, 1,905 pending members, and 875 localized first differences. Its dominant localized transition was reference decimal point `U+2832 ⠲` versus current Roman indicator `U+2834 ⠴` in 647 cases. After the generalized rule-43/48 guard, that transition is absent: 525 cases become exact and the other corrected prefixes expose later independent mismatches. -Current all-caps `OU` measurement: 1816 candidates, 70 exact controls, 1746 mismatches, 1660 members in the actual `pending_rule_review` subcluster, and 1365/1746 evaluable mismatches whose first difference is inside the current-engine output signature for the detected run. This is a pronunciation-sensitive UEB review cohort, not an engine routing rule. +Current all-caps `OU` measurement: 1816 candidates, 128 exact controls, 1688 mismatches, 1679 members in the actual `pending_rule_review` subcluster, and 1506/1688 evaluable mismatches whose first difference is inside the current-engine output signature for the detected run. This is a pronunciation-sensitive UEB review cohort, not an engine routing rule. + +Current standalone-uppercase measurement: 62411 candidates, 55551 exact controls, 6860 mismatches, and 5659 members in the actual `pending_rule_review` subcluster. Its high frequency does not make it causal: the same input shape is exact in many cases, and a sentence containing the shape may first differ at another Roman, numeric, or punctuation structure. No engine change is inferred from this cohort. -Current standalone-uppercase measurement: 62411 candidates, 51419 exact controls, 10992 mismatches, and 9527 members in the actual `pending_rule_review` subcluster. Its high frequency does not make it causal: the same input shape is exact in many cases, and a sentence containing the shape may first differ at another Roman, numeric, or punctuation structure. No engine change is inferred from this cohort. +Current Korean-prefixed all-caps parenthetical measurement: 54492 candidates, 48892 exact controls, 5600 mismatches, and 4549 members in the actual `pending_rule_review` subcluster. This is a semantic-collision audit, not an engine routing rule; no implementation change is inferred from its reference outputs. -Current Korean-prefixed all-caps parenthetical measurement: 54492 candidates, 45705 exact controls, 8787 mismatches, and 7483 members in the actual `pending_rule_review` subcluster. This is a semantic-collision audit, not an engine routing rule; no implementation change is inferred from its reference outputs. +Current rule-34 opening-order measurement: 64382 structural candidates, 57748 exact controls, 6634 mismatches, and 1166/6634 evaluable mismatches whose first difference is inside the current engine's Korean opening-parenthesis cells. Only the 1163 localized first-cell transitions have the reference/current order `⠴` versus `⠦`. After requiring the complete reference `⠴⠐⠣` versus current/PDF `⠦⠄⠴` three-cell signature and preserving higher-priority comparison classifications, 1160 are classified with the dedicated rule-34 contradiction reason; mere coexistence with a Korean-prefixed Roman annotation does not change a primary class. -Current rule-34 opening-order measurement: 64382 structural candidates, 53716 exact controls, 10666 mismatches, and 1119/10666 evaluable mismatches whose first difference is inside the current engine's Korean opening-parenthesis cells. Only the 1117 localized first-cell transitions have the reference/current order `⠴` versus `⠦`. After requiring the complete reference `⠴⠐⠣` versus current/PDF `⠦⠄⠴` three-cell signature and preserving higher-priority comparison classifications, 1114 are classified as `corpus_suspect`; mere coexistence with a Korean-prefixed Roman annotation does not change a primary class. +NIKL Q&A #325 clarifies that the six lower wordsigns named by Korean Rule 37 remain expanded when Roman words are discussed in Korean context, while a recognizable English title or phrase follows UEB 10.5 and uses a lower wordsign only when it stands alone and satisfies the lower-sign adjacency restriction. The encoder distinguishes those contexts from input structure, capitalization, and enclosure boundaries; analyzer references and competitor fields do not affect routing. + +Current UEB non-standing parenthesis/grade-1 contradiction measurement: 70 cases contain an all-capitals letters-sequence that resembles a shortform but is followed immediately by an opening round, square, or curly parenthesis. UEB 2.6.2 permits those opening symbols before a standing-alone sequence, while 2.6.3 does not permit them after one. The classifier requires complete-sentence equality after removing only reference-side grade-1 cells immediately before the localized capitals indicators; all other differences remain pending review. + +Current UEB capitalized-passage contradiction measurement: 3 cases contain at least three consecutive capitalized symbols-sequences and differ from the current UEB 8.5.2-8.5.3 path only by replacing the one passage indicator/terminator pair with separate one- or two-cell capitalization indicators. The classifier requires equality of the complete sentence after deleting exactly those structurally counted separate indicators; unrelated Roman, punctuation, contraction, or spacing differences remain pending review. Current all-caps Roman middle-dot measurement: 97 candidates, 0 exact controls, 97 mismatches, and 96 members in the actual `pending_rule_review` subcluster. This cross-cutting cohort preserves every primary class and is not an engine routing rule. -Current Roman-before-middle-dot boundary measurement: 577 candidates, 0 exact controls, 577 mismatches, 565 members in the actual `pending_rule_review` subcluster, and 451/577 evaluable mismatches whose first difference is localized to the one current terminator immediately before the middle dot. The locator encodes each real input prefix ending at the dot, so identifier state such as `K-ICS·...` is retained without consulting expected. This raises localized target coverage from 342 to 417 and reduces the then-leading residual `⠐ -> ⠲` transition from 297 to 223. Rules 29, 33, and 50 support the current terminator path but do not support the localized reference omission; no engine change or primary-class rewrite is inferred. +Current Roman-before-middle-dot boundary measurement: 577 candidates, 0 exact controls, 577 mismatches, 570 members in the actual `pending_rule_review` subcluster, and 516/577 evaluable mismatches whose first difference is localized to the one current terminator immediately before the middle dot. The locator encodes each real input prefix ending at the dot, so identifier state such as `K-ICS·...` is retained without consulting expected. This raises localized target coverage from 342 to 417 and reduces the then-leading residual `⠐ -> ⠲` transition from 297 to 223. Rules 29, 33, and 50 support the current terminator path but do not support the localized reference omission; no engine change or primary-class rewrite is inferred. -Current attached Roman-to-Korean boundary measurement: 17693 candidates, 13465 exact controls, 4228 mismatches, 3391 members in the actual `pending_rule_review` subcluster, and 0/4228 evaluable mismatches whose first difference is localized to the current boundary marker. The localized target `⠲ -> ⠸` occurs 0 times and the reverse `⠸ -> ⠲` occurs 0 times. Korean rule 29 (2024 Korean-rules PDF p.26, printed p.20) requires a Roman terminator around Roman text in a Korean sentence, whereas rule 39 (PDF pp.31-32, printed pp.25-26) applies the Korean opening/closing markers only when Roman text is the sentence's main language. Among exact candidates, 11377 expose the current rule-29 terminator at such a boundary and 0 expose the current rule-39 opening. Its printed controls include both an English sentence (`What is 김치 in English?`) and the address `www.대통령.kr` inside a Korean sentence. Official UEB 2.4.7 (2024 UEB PDF p.41, printed p.13) confirms that a UEB mode does not extend through a switch to another braille code, but does not choose whether this Korean boundary belongs to a Korean-main or Roman-main context. Therefore the attached script boundary alone cannot distinguish ordinary Korean prose from an embedded Roman-domain context. Primary classes are preserved and no engine change is inferred without a narrower input-derived dominance gate. At diagnostic checkpoint `57c4608`, this same cohort was 17,693 candidates / 12,724 exact / 4,969 mismatch, with 268 localized `⠲ -> ⠸`, zero reverse, 10,780 exact rule-29 markers, and zero exact rule-39 markers. After narrowing the engine by dominance and the PDF domain shape, it is 12,924 exact / 4,769 mismatch with no localized target or reverse; corpus-wide exact rises from 67,715 to 68,101 (+386). The remaining 1 mismatch with a current rule-39 opening is a list-heavy Korean sentence whose ASCII-leading brand tokens satisfy the mechanical word-count majority; its first difference is not localized to this boundary, so it remains a semantic pending control rather than grounds for another engine branch. +Current attached Roman-to-Korean boundary measurement: 17693 candidates, 15206 exact controls, 2487 mismatches, 1719 members in the actual `pending_rule_review` subcluster, and 0/2487 evaluable mismatches whose first difference is localized to the current boundary marker. The localized target `⠲ -> ⠸` occurs 0 times and the reverse `⠸ -> ⠲` occurs 0 times. Korean rule 29 (2024 Korean-rules PDF p.26, printed p.20) requires a Roman terminator around Roman text in a Korean sentence, whereas rule 39 (PDF pp.31-32, printed pp.25-26) applies the Korean opening/closing markers only when Roman text is the sentence's main language. Among exact candidates, 13004 expose the current rule-29 terminator at such a boundary and 0 expose the current rule-39 opening. Its printed controls include both an English sentence (`What is 김치 in English?`) and the address `www.대통령.kr` inside a Korean sentence. Official UEB 2.4.7 (2024 UEB PDF p.41, printed p.13) confirms that a UEB mode does not extend through a switch to another braille code, but does not choose whether this Korean boundary belongs to a Korean-main or Roman-main context. Therefore the attached script boundary alone cannot distinguish ordinary Korean prose from an embedded Roman-domain context. Primary classes are preserved and no engine change is inferred without a narrower input-derived dominance gate. At diagnostic checkpoint `57c4608`, this same cohort was 17,693 candidates / 12,724 exact / 4,969 mismatch, with 268 localized `⠲ -> ⠸`, zero reverse, 10,780 exact rule-29 markers, and zero exact rule-39 markers. After narrowing the engine by dominance and the PDF domain shape, it is 12,924 exact / 4,769 mismatch with no localized target or reverse; corpus-wide exact rises from 67,715 to 68,101 (+386). The remaining 0 mismatch with a current rule-39 opening is a list-heavy Korean sentence whose ASCII-leading brand tokens satisfy the mechanical word-count majority; its first difference is not localized to this boundary, so it remains a semantic pending control rather than grounds for another engine branch. -Current rule-39 narrowed-scope audit: 947 candidates, 593 exact controls, 354 mismatches, and 342 members in the actual `pending_rule_review` subcluster. This input-derived scope is recorded separately from the direct-boundary output-localizer; primary classes are unchanged. At the implementation checkpoint, this conservative first-script-word approximation contains 385 exact cases, accounting for all but one of the corpus-wide +386 net gain; no localized reverse transition is observed in the direct-boundary cohort. +Current rule-39 narrowed-scope audit: 947 candidates, 736 exact controls, 211 mismatches, and 201 members in the actual `pending_rule_review` subcluster. This input-derived scope is recorded separately from the direct-boundary output-localizer; primary classes are unchanged. At the implementation checkpoint, this conservative first-script-word approximation contains 385 exact cases, accounting for all but one of the corpus-wide +386 net gain; no localized reverse transition is observed in the direct-boundary cohort. -Current inline parenthesized-operator measurement: 23 candidates, 17 exact controls, 6 mismatches, 6 members in the actual `pending_rule_review` subcluster, and 2/6 evaluable mismatches whose first differing cell is inside the emitted structure. Primary classes are preserved. +Current inline parenthesized-operator measurement: 23 candidates, 22 exact controls, 1 mismatches, 1 members in the actual `pending_rule_review` subcluster, and 0/1 evaluable mismatches whose first differing cell is inside the emitted structure. Primary classes are preserved. At that implementation checkpoint, the strict cohort moved from 0 to 17 exact cases; the corpus-wide total moved from 65,491 to 65,514 (+23 exact) because the same PDF-backed spacing rule also applied outside the stricter Korean-boundary audit gate. These are immutable checkpoint counts rather than the report's later cumulative total. The complete standard suite remained 5,141/5,141. -Current attached plus + parenthesized Korean-gloss measurement: 16 candidates, 3 exact controls, 13 mismatches, 13 members in the actual `pending_rule_review` subcluster, and 10/13 evaluable mismatches whose first differing cell is inside the current emitted structure. Hangeul rule 46 supplies the operation-sign spacing control, but the surface form alone does not establish whether a brand or program name uses `+` mathematically. Exact and localized mismatch references coexist for `도전+(플러스)`, so no engine change or primary reclassification is inferred. +Current attached plus + parenthesized Korean-gloss measurement: 16 candidates, 4 exact controls, 12 mismatches, 12 members in the actual `pending_rule_review` subcluster, and 12/12 evaluable mismatches whose first differing cell is inside the current emitted structure. Hangeul rule 46 supplies the operation-sign spacing control, but the surface form alone does not establish whether a brand or program name uses `+` mathematically. Exact and localized mismatch references coexist for `도전+(플러스)`, so no engine change or primary reclassification is inferred. -Current tight-triangle measurement: 377 candidates, 0 exact controls, 377 mismatches, 376 members in the actual `pending_rule_review` subcluster, and 335/377 evaluable mismatches whose first difference is inside the `△` plus first-Korean output range. No engine change is inferred. +Current tight-triangle measurement: 377 candidates, 317 exact controls, 60 mismatches, 54 members in the actual `pending_rule_review` subcluster, and 3/60 evaluable mismatches whose first difference is inside the `△` plus first-Korean output range. No engine change is inferred. ## Encoding-error diagnostics @@ -3682,132 +3614,90 @@ The audit starts from all raw encoding errors, then separates cases already reso | Encoding-error audit | Cases | |---|---:| -| Raw encoding errors | 450 | -| Resolved by comparison method | 295 | +| Raw encoding errors | 4 | +| Resolved by comparison method | 0 | | Excluded as corpus suspect | 0 | -| Unresolved encoding-error review cases | 155 | -| Explained by singleton unsupported character(s) | 155 | -| Multiple singleton unsupported characters | 2 | +| Unresolved encoding-error review cases | 4 | +| Explained by singleton unsupported character(s) | 4 | +| Multiple singleton unsupported characters | 0 | | Unclassified without a singleton explanation | 0 | -- compound `sentence_01.json` #17370: 배우 유라가 영화 ‘가문의 영광: 리턴즈’(배급: ㈜넥스트엔터테인먼트월드(NEW) │ 제작: ㈜태원엔터테인먼트│감독: 정태원, 정용기)에서 장씨 가문의 막내딸 ‘진경’으로 스크린을 찾는다. - - singleton unsupported: `U+2502 │, U+321C ㈜` -- compound `sentence_01.json` #21555: (주)바론교육, ESG baron 경제연구소, ㈜지인누리, ㈜지역창의연구소 대표 및 임원진이 참석한 가운데 강원도 소재 ‘농산어촌 ESG 원주교육장’에서 ‘농산어촌 ESG 인증⸱실천⸱교육을 위한 업무 협약식(MOU)’을 지난 14일 개최했다. - - singleton unsupported: `U+2E31 ⸱, U+321C ㈜` - | Error message | Cases | |---|---:| -| `Invalid symbol character` | 155 | +| `Invalid symbol character` | 4 | | Error family | Cases | |---|---:| -| `enclosed_organization_mark` | 136 | -| `letterlike_unit_symbol` | 9 | -| `punctuation_or_layout_symbol` | 12 | +| `punctuation_or_layout_symbol` | 4 | Families are diagnostics, not automatic normalization permissions. Rules 68/69 compatibility-unit support removed that error family from the current run; `enclosed_organization_mark` and layout symbols still have no confirmed rule. | Singleton error character | Cases containing it | NFKC decomposition | Family | |---|---:|---|---| -| `U+02D1 ˑ` | 1 | `ˑ` | `punctuation_or_layout_symbol` | -| `U+2025 ‥` | 1 | `..` | `punctuation_or_layout_symbol` | -| `U+2113 ℓ` | 9 | `l` | `letterlike_unit_symbol` | -| `U+2502 │` | 1 | `│` | `punctuation_or_layout_symbol` | -| `U+25B2 ▲` | 1 | `▲` | `punctuation_or_layout_symbol` | -| `U+25B4 ▴` | 1 | `▴` | `punctuation_or_layout_symbol` | | `U+260F ☏` | 3 | `☏` | `punctuation_or_layout_symbol` | | `U+2665 ♥` | 1 | `♥` | `punctuation_or_layout_symbol` | -| `U+2E31 ⸱` | 2 | `⸱` | `punctuation_or_layout_symbol` | -| `U+302E 〮` | 1 | `〮` | `punctuation_or_layout_symbol` | -| `U+3214 ㈔` | 2 | `(사)` | `enclosed_organization_mark` | -| `U+321C ㈜` | 134 | `(주)` | `enclosed_organization_mark` | ## Shards | Shard | Exact | Total | Accuracy | |---|---:|---:|---:| -| `sentence_01.json` | 20843 | 25000 | 83.37% | -| `sentence_02.json` | 20341 | 25000 | 81.36% | -| `sentence_03.json` | 20946 | 25000 | 83.78% | -| `sentence_04.json` | 7300 | 8528 | 85.60% | +| `sentence_01.json` | 22505 | 25000 | 90.02% | +| `sentence_02.json` | 22630 | 25000 | 90.52% | +| `sentence_03.json` | 22855 | 25000 | 91.42% | +| `sentence_04.json` | 7795 | 8528 | 91.40% | ## Overlapping mismatch traits | Trait | Count | |---|---:| -| `contains_ascii_digits` | 11135 | -| `contains_ascii_letters` | 13935 | -| `contains_delimiter_or_quote` | 14098 | -| `input_not_nfkc` | 703 | +| `contains_ascii_digits` | 5759 | +| `contains_ascii_letters` | 7694 | +| `contains_delimiter_or_quote` | 7743 | +| `input_not_nfkc` | 243 | ## Samples ### `foreign_text_rule_review` -- `sentence_01.json` #17: 지난달 30일(현지시각) 뉴욕증권거래소(NYSE)에서 다우존스30산업평균지수는 전 거래일보다 73.55포인트(0.22%) 하락한 3만3147.25로 마감했다. 대형주 중심의 S&P500지수는 9.78포인트(0.25%) 하락한 3839.50으로, 기술주 중심의 나스닥지수는 11.61포인트(0.11%) 하락한 1만0466.4 - - expected: `⠠⠕⠢⠺⠀⠴⠠⠎⠈⠯⠠⠏⠼⠑⠚⠚⠨⠕⠠⠍⠉⠵⠀⠼` - - actual: `⠠⠕⠢⠺⠀⠴⠠⠎⠲⠴⠈⠯⠲⠴⠠⠏⠼⠑⠚⠚⠨⠕⠠⠍` -- `sentence_01.json` #35: 삼성전자는 올해 CES에서 77인치 OLED TV를 처음으로 공개할 예정이다. 이는 지난 8월 ‘국제정보디스플레이학술대회(IMID) 2022’에서 삼성디스플레이가 공개한 77인치 퀀텀닷(QD)-OLED 패널을 탑재한 제품이다. - - expected: `⠋⠏⠒⠓⠎⠢⠊⠄⠴⠐⠣⠟⠙⠐⠜⠤⠠⠠⠕⠇⠫⠲⠀⠙` - - actual: `⠋⠏⠒⠓⠎⠢⠊⠄⠀⠀⠦⠠⠠⠟⠙⠴⠔⠠⠠⠕⠇⠑⠙⠀` -- `sentence_01.json` #38: 올해 소프트웨어(SW) 업계는 국내 인공지능(AI) 제품·서비스 고도화와 함께 전 산업영역에서 SW영역 확대가 가시화될 것으로 보인다. 또한 블록체인이 웹3(Web3)와 연계해 탈중앙화·탈독점화 경제체계로 진화할 것으로 예상된다. - - expected: `⠟⠕⠀⠏⠗⠃⠼⠉⠦⠄⠴⠠⠺⠑⠃⠼⠉⠠⠴⠧⠀⠡⠈⠌` - - actual: `⠟⠕⠀⠏⠗⠃⠼⠉⠴⠐⠣⠠⠺⠑⠃⠼⠉⠴⠐⠜⠲⠧⠀⠡` - `sentence_01.json` #39: 소프트웨어정책연구소(SPRi)는 ‘2023년 SW산업 10대 이슈 전망’을 통해 올해 가장 주요한 이슈로 인공지능 기반 모델 고도화를 1위로 선정했다. - expected: `⠈⠍⠠⠥⠦⠄⠴⠠⠠⠎⠏⠠⠗⠊⠠⠴⠉⠵⠀⠠⠦⠼⠃⠚` - actual: `⠈⠍⠠⠥⠦⠄⠴⠠⠎⠠⠏⠠⠗⠊⠠⠴⠉⠵⠀⠠⠦⠼⠃⠚` -- `sentence_01.json` #45: 2019년 드링크인터내셔널 합류 이후 최고 운영 책임자(COO)를 역임하며 자회사 인터리커를 통해 와인 시장 진출 등 사업 확장을 진두지휘했다. - - expected: `⠁⠕⠢⠨⠦⠄⠴⠠⠠⠉⠕⠕⠠⠴⠐⠮⠀⠱⠁⠕⠢⠚⠑⠱` - - actual: `⠁⠕⠢⠨⠦⠄⠴⠠⠉⠠⠕⠠⠕⠠⠴⠐⠮⠀⠱⠁⠕⠢⠚⠑` - -### `nfkc_input_equivalent` - -- `sentence_01.json` #937: 위원들은 ㈜광주글로벌모터스(GGM)와 협력 중소기업 근로자의 실질적 복지 증진 필요성에 대해 공감하고, (가칭)상생형 공동근로복지기금법인의 기금 조성 계획안을 최종 의결했다. - - expected: `⠍⠗⠏⠒⠊⠮⠵⠀⠦⠄⠨⠍⠠⠴⠈⠧⠶⠨⠍⠈⠮⠐⠥⠘` - - actual: `` - - error: `Invalid symbol character` -- `sentence_01.json` #2276: 김무환 SK㈜ 그린(Green)투자센터장은 “글로벌 탈산소 전문 가치 투자자를 목표로 탄소 배출 축소 및 제거 관련 사업기회를 발굴하고 투자하는데 역점을 두고 있다”고 밝혔다. - - expected: `⠈⠕⠢⠑⠍⠚⠧⠒⠀⠴⠠⠠⠎⠅⠦⠄⠨⠍⠠⠴⠀⠈⠪⠐` - - actual: `` - - error: `Invalid symbol character` -- `sentence_01.json` #2336: 한편, 한국팀 경기를 포함한 아시아컵 주요 경기는 세계야구소프트볼연맹(WBSC) 공식 유튜브에서 확인할 수 있다. 이번 대회 운영 대행은 ㈜에스피에스 코리아(SPS KOREA)가 맡아 진행한다. - - expected: `⠚⠒⠙⠡⠐⠀⠚⠒⠈⠍⠁⠓⠕⠢⠀⠈⠻⠈⠕⠐⠮⠀⠙⠥` - - actual: `` - - error: `Invalid symbol character` -- `sentence_01.json` #2530: 신세계인터내셔날의 자체 헤어케어 브랜드 저스트 에즈 아이엠이 이달 7일 브랜드 전속 모델인 가수 ‘비비(BIBI)’와 함께 음원 ‘안녕하세오 샴푸애요(I AM…)’를 공식 발매한다고 밝혔다. - - expected: `⠴⠠⠊⠀⠠⠠⠁⠍⠲⠲⠲⠠⠴⠴⠄⠐⠮⠀⠈⠿⠠⠕⠁⠀` - - actual: `⠴⠠⠊⠀⠠⠠⠁⠍⠠⠠⠠⠠⠴⠴⠄⠐⠮⠀⠈⠿⠠⠕⠁⠀` -- `sentence_01.json` #2662: 교육 프로그램은 ‘구글 클라우드 기반 인공지능 이해·빅쿼리(BigQuery) 전문가 과정’으로, 구글 클라우드의 공식 교육파트너사인 ㈜아이코어이앤씨가 맡는다. - - expected: `⠈⠬⠩⠁⠀⠙⠪⠐⠥⠈⠪⠐⠗⠢⠵⠀⠠⠦⠈⠍⠈⠮⠀⠋` - - actual: `` - - error: `Invalid symbol character` +- `sentence_01.json` #47: 다날은 계열사 ‘제프’가 국내 대체불가토큰(NFT) 거래소를 운영하는 ‘팔라’와 메타버스·NFT 협력 관련 협약(MOU)을 맺고 메타버스 플랫폼 ‘제프월드’의 인프라 확대를 추진한다고 3일 밝혔다. + - expected: `⠜⠁⠦⠄⠴⠠⠠⠍⠕⠥⠠⠴⠮⠀⠑⠗⠅⠈⠥⠀⠑⠝⠓⠘` + - actual: `⠜⠁⠦⠄⠴⠠⠠⠍⠳⠠⠴⠮⠀⠑⠗⠅⠈⠥⠀⠑⠝⠓⠘⠎` +- `sentence_01.json` #50: 부산광역시는 3일 오후 부산광역시청 영상회의실에서 종합화학소재기업 ㈜금양과 이차전지 생산기지 건립을 위한 8천억원 규모의 투자 양해각서(MOU)를 체결한다고 밝혔다. + - expected: `⠠⠎⠦⠄⠴⠠⠠⠍⠕⠥⠠⠴⠐⠮⠀⠰⠝⠈⠳⠚⠒⠊⠈⠥` + - actual: `⠠⠎⠦⠄⠴⠠⠠⠍⠳⠠⠴⠐⠮⠀⠰⠝⠈⠳⠚⠒⠊⠈⠥⠀` +- `sentence_01.json` #51: 와이캅 픽셀은 와이어·패키지·렌즈가 필요 없는 ‘와이캅’ 기술을 기반으로 한다. 적녹청(RGB) 3개의 마이크로 LED를 수직 방향으로 적층한 세계 최초의 풀 컬러 원칩 기술이다. + - expected: `⠪⠐⠥⠀⠴⠠⠠⠇⠑⠙⠲⠐⠮⠀⠠⠍⠨⠕⠁⠀⠘⠶⠚⠜` + - actual: `⠪⠐⠥⠀⠴⠠⠠⠇⠫⠲⠐⠮⠀⠠⠍⠨⠕⠁⠀⠘⠶⠚⠜⠶` +- `sentence_01.json` #57: 아키에이지 워는 원작 아키에이지 개발사 엑스엘게임즈(각자대표 송재경, 최관호)가 개발 중인 신작 PC·모바일 크로스플랫폼 대규모다중접속역할수행게임(MMORPG)이다. 원작의 향수를 자극하는 스토리와 캐릭터, 언리얼 엔진4를 활용한 고퀄리티 그래픽이 특징이다. + - expected: `⠨⠁⠀⠴⠠⠠⠏⠉⠐⠆⠑⠥⠘⠣⠕⠂⠀⠋⠪⠐⠥⠠⠪⠙` + - actual: `⠨⠁⠀⠴⠠⠠⠏⠉⠲⠐⠆⠑⠥⠘⠣⠕⠂⠀⠋⠪⠐⠥⠠⠪` ### `number_rule_review` - `sentence_01.json` #845: 부산광역시는 3일 오전 부산광역시청에서 초록우산어린이재단 부산본부, 월드비전 부산사업본부, 굿네이버스 영남본부와 ‘자립+(더하기) 동행 프로젝트’ 업무협약을 체결했다고 밝혔다. - expected: `⠠⠦⠨⠐⠕⠃⠀⠢⠦⠄⠊⠎⠚⠈⠕⠠⠴⠀⠊⠿⠚⠗⠶⠀` - actual: `⠠⠦⠨⠐⠕⠃⠀⠢⠀⠦⠄⠊⠎⠚⠈⠕⠠⠴⠀⠊⠿⠚⠗⠶` -- `sentence_01.json` #6594: 이날 발대식에는 △보성군 청년협의체 1(팀), △행안부형 청년공동체 1(팀), △전남형 청년공동체 5(팀), △전남형 청년 마을 1(팀), △모닥모닥 동아리 5(팀), 총 13개 팀 60여명의 회원이 참석한다. - - expected: `⠕⠁⠝⠉⠵⠀⠸⠬⠀⠘⠥⠠⠻⠈⠛⠀⠰⠻⠉⠡⠚⠱⠃⠺` - - actual: `⠕⠁⠝⠉⠵⠀⠸⠬⠇⠘⠥⠠⠻⠈⠛⠀⠰⠻⠉⠡⠚⠱⠃⠺` -- `sentence_01.json` #6854: 학생부위주(교과)에서 학생부 교과 100%로 선발하는 △교과전형은 876명 △지역인재(교과)전형은 48명 △불교추천인재 전형으로 59명[(한)의예과 및 간호학과 모집]을 선발하며 △교과성적 외 면접점수를 40% 반영하는 면접전형은 226명을 모집한다. - - expected: `⠘⠂⠚⠉⠵⠀⠸⠬⠀⠈⠬⠈⠧⠨⠾⠚⠻⠵⠀⠼⠓⠛⠋⠀` - - actual: `⠘⠂⠚⠉⠵⠀⠸⠬⠇⠈⠬⠈⠧⠨⠾⠚⠻⠵⠀⠼⠓⠛⠋⠀` -- `sentence_01.json` #9055: 한국건설기술연구원에 따르면 주거용 건물 건설공사비지수는 올해 9월 기준 152.76(잠정치)으로 전년 동월(147.31) 대비 약 3.7%가량 증가했다. - - expected: `⠾⠉⠡⠀⠊⠿⠏⠂⠦⠄⠼⠁⠙⠛⠲⠉⠁⠠⠴⠀⠊⠗⠘⠕` - - actual: `⠾⠉⠡⠀⠊⠿⠏⠂⠀⠀⠦⠼⠁⠙⠛⠲⠉⠁⠴⠀⠊⠗⠘⠕` -- `sentence_01.json` #10139: ‘서울 반려견 순찰대’는 8개월 동안의 올해 성과 보고를 통해 △범죄예방(112) 신고 317건 △생활위험 관련(120) 신고 2187건 등으로 지역사회 안전망을 촘촘하게 구축하는 데 이바지했다고 발표했다. - - expected: `⠀⠓⠿⠚⠗⠀⠸⠬⠀⠘⠎⠢⠨⠽⠤⠌⠘⠶⠦⠄⠼⠁⠁⠃` - - actual: `⠀⠓⠿⠚⠗⠀⠸⠬⠇⠘⠎⠢⠨⠽⠤⠌⠘⠶⠦⠄⠼⠁⠁⠃` +- `sentence_01.json` #7774: 한편 이번 조사는 대구시가 ㈜리얼미터에 의뢰해 전화면접·온라인(7:3)을 통해 실시했고 응답률은 15.6%, 표본오차는 95% 신뢰수준에서 ±3.1%포인트다. + - expected: `⠨⠛⠝⠠⠎⠀⠢⠔⠀⠼⠉⠲⠁⠴⠏⠀⠙⠥⠟⠓⠪⠊⠲` + - actual: `⠨⠛⠝⠠⠎⠀⠢⠔⠼⠉⠲⠁⠴⠏⠀⠙⠥⠟⠓⠪⠊⠲` +- `sentence_01.json` #10976: 자살예방 캠페인 캐릭터인 더더(+), 배로(×), 빼요(–), 누미(÷) ‘생명지키미들’의 카카오톡 이모티톤을 18일 오후 2시부터 무료로 배포한다. + - expected: `⠐⠀⠠⠘⠗⠬⠦⠄⠔⠠⠴⠐⠀⠉⠍⠑⠕⠦⠄⠌⠌⠠⠴⠀` + - actual: `⠐⠀⠠⠘⠗⠬⠦⠄⠠⠤⠠⠴⠐⠀⠉⠍⠑⠕⠦⠄⠌⠌⠠⠴` +- `sentence_01.json` #10977: 자살예방 캠페인 캐릭터 ‘생명지키미들’은 총 4종으로, 사랑과 희망을 더해주는 ‘더더(+)’, 행복을 마구마구 불려주는 ‘배로(×)’, 슬픔을 잊게 해주는 ‘빼요(–)’, 걱정과 고민을 듣고 나눠주는 ‘누미(÷)’로 구성돼 있다. + - expected: `⠠⠦⠠⠘⠗⠬⠦⠄⠔⠠⠴⠴⠄⠐⠀⠈⠹⠨⠻⠈⠧⠀⠈⠥` + - actual: `⠠⠦⠠⠘⠗⠬⠦⠄⠠⠤⠠⠴⠴⠄⠐⠀⠈⠹⠨⠻⠈⠧⠀⠈` +- `sentence_01.json` #13879: 탬파베이는 83득점 20실점이다. 득실 마진이 +63점이나 된다. 메이저리그 역사에서 개막 10경기 득실자 마진에서 역대 3위 기록이다. 그런데 1~2위는 앞서 언급된 1884년 마룬스(+106)와 마룬스(+73) 기록이다. + - expected: `⠂⠀⠑⠨⠟⠕⠀⠢⠀⠼⠋⠉⠨⠎⠢⠕⠉⠀⠊⠽⠒⠊⠲⠀` + - actual: `⠂⠀⠑⠨⠟⠕⠀⠢⠼⠋⠉⠨⠎⠢⠕⠉⠀⠊⠽⠒⠊⠲⠀⠑` ### `punctuation_rule_review` - `sentence_01.json` #18647: 배우 박성웅, 오대환, 오달수, 주석태 주연 정통 하드보일드 액션 영화 ‘더와일드:야수들의 전쟁’(감독:김봉한/제작:(주)아센디오, (주)제이앤씨미디어그룹/이하 더와일드)의 개봉 소식과 함께 보도스틸이 공개돼 눈길을 끈다. - expected: `⠎⠧⠕⠂⠊⠪⠐⠂⠀⠜⠠⠍⠊⠮⠺⠀⠨⠾⠨⠗⠶⠴⠄⠦` - actual: `⠎⠧⠕⠂⠊⠪⠐⠂⠜⠠⠍⠊⠮⠺⠀⠨⠾⠨⠗⠶⠴⠄⠦⠄` -- `sentence_01.json` #21108: 전고체 이차전지는 양(+)극과 음(-)극 사이에서 이온을 전달하는 ‘전해질’을 액체가 아닌, 화재나 폭발 위험성이 낮은 고체로 대체한 것이다. - - expected: `⠁⠈⠧⠀⠪⠢⠦⠄⠔⠠⠴⠈⠪⠁⠀⠇⠕⠝⠠⠎⠀⠕⠷⠮` - - actual: `⠁⠈⠧⠀⠪⠢⠦⠄⠤⠠⠴⠈⠪⠁⠀⠇⠕⠝⠠⠎⠀⠕⠷⠮` - `sentence_03.json` #18547: 남태우는 디즈니+(플러스)의 오리지널 시리즈 ‘한강’(연출/극본 김상철)에서 국제범죄 수사대 고형민 경사 역할을 맡아 연기 변신을 선보인다. - expected: `⠊⠕⠨⠪⠉⠕⠀⠢⠦⠄⠙⠮⠐⠎⠠⠪⠠⠴⠺⠀⠥⠐⠕⠨` - actual: `⠊⠕⠨⠪⠉⠕⠀⠢⠀⠦⠄⠙⠮⠐⠎⠠⠪⠠⠴⠺⠀⠥⠐⠕` @@ -3818,44 +3708,76 @@ Families are diagnostics, not automatic normalization permissions. Rules 68/69 c - expected: `⠕⠢⠠⠍⠁⠵⠀⠦⠁⠒⠀⠍⠨⠗⠜⠲⠀⠠⠦⠚⠿⠈⠕⠢` - actual: `⠕⠢⠠⠍⠁⠵⠀⠦⠈⠪⠐⠎⠑⠡⠀⠍⠨⠗⠜⠲⠀⠠⠦⠚` +### `roman_ellipsis_uses_korean_cells_in_roman_enclosure` + +- `sentence_01.json` #13772: 이후 아이브는 다양한 노래들로 간식 퀴즈를 진행했고, 최신곡 스테이씨(STAYC)의 ‘파피’(Poppy)부터 터보의 ‘러브 이즈’(Love Is…)까지 맞히며 맛있는 간식들을 획득했다. + - expected: `⠇⠕⠧⠑⠀⠠⠊⠎⠠⠠⠠⠠⠴⠠⠫⠨⠕⠀⠑⠅⠚⠕⠑⠱` + - actual: `⠇⠕⠧⠑⠀⠠⠊⠎⠲⠲⠲⠠⠴⠠⠫⠨⠕⠀⠑⠅⠚⠕⠑⠱` + ### `rule34_roman_indicator_before_opening_parenthesis` - `sentence_01.json` #14: 새롭게 선보인 DRX 브랜딩과 팀 컬러는 2023년 1월 1일부터 DRX의 온오프라인 콘텐츠, 엠디(MD), SNS, 유튜브 등 모든 제작물에 적용될 예정이다. - expected: `⠰⠪⠐⠀⠝⠢⠊⠕⠴⠐⠣⠠⠠⠍⠙⠐⠜⠂⠀⠠⠠⠎⠝⠎` - - actual: `⠰⠪⠐⠀⠝⠢⠊⠕⠦⠄⠴⠠⠠⠍⠙⠠⠴⠐⠀⠰⠠⠠⠎⠝` + - actual: `⠰⠪⠐⠀⠝⠢⠊⠕⠦⠄⠴⠠⠠⠍⠙⠠⠴⠐⠀⠴⠠⠠⠎⠝` +- `sentence_01.json` #35: 삼성전자는 올해 CES에서 77인치 OLED TV를 처음으로 공개할 예정이다. 이는 지난 8월 ‘국제정보디스플레이학술대회(IMID) 2022’에서 삼성디스플레이가 공개한 77인치 퀀텀닷(QD)-OLED 패널을 탑재한 제품이다. + - expected: `⠋⠏⠒⠓⠎⠢⠊⠄⠴⠐⠣⠟⠙⠐⠜⠤⠠⠠⠕⠇⠫⠲⠀⠙` + - actual: `⠋⠏⠒⠓⠎⠢⠊⠄⠦⠄⠴⠠⠠⠟⠙⠠⠴⠤⠴⠠⠠⠕⠇⠫` - `sentence_01.json` #77: 삼성전자가 미국 라스베이거스에서 열리는 세계 최대 전자 전시회 ‘CES 2023’ 개막을 앞두고 77인치 유기발광다이오드(OLED) TV 등 2023년형 TV 신제품을 대거 공개했다. - expected: `⠧⠶⠊⠣⠕⠥⠊⠪⠴⠐⠣⠠⠠⠕⠇⠫⠐⠜⠀⠠⠠⠞⠧⠲` - actual: `⠧⠶⠊⠣⠕⠥⠊⠪⠦⠄⠴⠠⠠⠕⠇⠫⠠⠴⠀⠴⠠⠠⠞⠧` - `sentence_01.json` #79: B씨의 신고로 수사에 착수한 경찰은 인근 폐쇄회로(CC)TV와 탐문수색 등을 바탕으로 A씨를 특정해 지난달 29일 검거해 31일 구속했다. - expected: `⠌⠠⠧⠗⠚⠽⠐⠥⠴⠐⠣⠠⠠⠉⠉⠐⠜⠠⠠⠞⠧⠲⠧⠀` - - actual: `⠌⠠⠧⠗⠚⠽⠐⠥⠦⠄⠴⠠⠉⠠⠉⠠⠴⠰⠠⠠⠞⠧⠲⠧` + - actual: `⠌⠠⠧⠗⠚⠽⠐⠥⠦⠄⠴⠠⠠⠉⠉⠠⠴⠴⠠⠠⠞⠧⠲⠧` - `sentence_01.json` #83: 삼성전자는 이번 행사에서 77형 유기발광다이오드(OLED) TV를 첫 공개하기도 했다. 지난해 처음 출시한 삼성 OLED TV는 55형, 65형과 함께 초대형 77형 모델이 추가된 셈이다. - expected: `⠧⠶⠊⠣⠕⠥⠊⠪⠴⠐⠣⠠⠠⠕⠇⠫⠐⠜⠀⠠⠠⠞⠧⠲` - actual: `⠧⠶⠊⠣⠕⠥⠊⠪⠦⠄⠴⠠⠠⠕⠇⠫⠠⠴⠀⠴⠠⠠⠞⠧` -- `sentence_01.json` #85: 클레이튼 커쇼와 함께 메이저리그(MLB) LA 다저스 선발 마운드를 든든하게 지키고 있는 훌리오 우리아스가 2023 월드베이스볼클래식(WBC)에서 멕시코대표팀 주장을 맡는다. - - expected: `⠝⠕⠨⠎⠐⠕⠈⠪⠴⠐⠣⠠⠠⠍⠇⠃⠐⠜⠀⠠⠠⠇⠁⠲` - - actual: `⠝⠕⠨⠎⠐⠕⠈⠪⠦⠄⠴⠠⠠⠍⠇⠃⠠⠴⠀⠴⠠⠠⠇⠁` + +### `ueb_capitalized_passage_written_as_separate_capital_words` + +- `sentence_01.json` #12980: 민희가 속한 크래비티는 오늘(14일) 오후 6시에 SBS M, SBS FiL ‘더쇼’에서 미니 5집 ‘마스터 : 피스’의 타이틀곡 ‘그루비(Groovy)’로 컴백 무대를 펼친다. + - expected: `⠋⠠⠕⠝⠀⠴⠠⠠⠎⠃⠎⠀⠰⠠⠍⠂⠀⠠⠠⠎⠃⠎⠀⠠` + - actual: `⠋⠠⠕⠝⠀⠴⠠⠠⠠⠎⠃⠎⠀⠰⠍⠂⠀⠎⠃⠎⠠⠄⠀⠠` +- `sentence_03.json` #16532: IPX(구 라인프렌즈)의 글로벌 인기 캐릭터 IP BT21이 방탄소년단(BTS) 데뷔 10주년 ‘FESTA(2023 BTS FESTA)’에 특별 참여한다. + - expected: `⠉⠡⠀⠠⠦⠴⠠⠠⠋⠑⠌⠁⠐⠣⠼⠃⠚⠃⠉⠀⠠⠠⠃⠞` + - actual: `⠉⠡⠀⠠⠦⠴⠠⠠⠠⠋⠑⠌⠁⠐⠣⠼⠃⠚⠃⠉⠀⠃⠞⠎` +- `sentence_03.json` #20410: 에이핑크는 올해 4월 미니 10집 ‘셀프(SELF)’를 내고 타이틀곡 ‘D N D’로 활동했다. 최근에는 크리스마스 음원도 발매했다. 멤버 개인 활동도 병행했다. + - expected: `⠮⠈⠭⠀⠠⠦⠴⠠⠙⠀⠰⠠⠝⠀⠰⠠⠙⠴⠄⠐⠥⠀⠚⠧` + - actual: `⠮⠈⠭⠀⠠⠦⠴⠠⠠⠠⠙⠀⠰⠝⠀⠰⠙⠠⠄⠴⠄⠐⠥⠀` + +### `ueb_grade1_before_nonstanding_opening_parenthesis` + +- `sentence_01.json` #1573: 넥슨(대표 이정헌)이 글로벌 게임개발자 콘퍼런스 ‘GDC(Game Developers Conference) 2023’에 참가해 대규모다중접속역할수행게임(MMORPG)과 블록체인 기술간 결합을 주제로 강연을 진행한다고 3일 발표했다. + - expected: `⠐⠾⠠⠪⠀⠠⠦⠴⠰⠠⠠⠛⠙⠉⠐⠣⠠⠛⠁⠍⠑⠀⠠⠙` + - actual: `⠐⠾⠠⠪⠀⠠⠦⠴⠠⠠⠛⠙⠉⠐⠣⠠⠛⠁⠍⠑⠀⠠⠙⠑` +- `sentence_01.json` #5005: “정부 전용 초거대AI 개발에 민간 기업의 LLM(초거대언어모델)을 활용하는데 정부 내부 문서나 데이터는 별도로 분리된 공간에서 안전하게 학습시킬 것이다.” + - expected: `⠀⠈⠕⠎⠃⠺⠀⠴⠰⠠⠠⠇⠇⠍⠦⠄⠰⠥⠈⠎⠊⠗⠾⠎` + - actual: `⠀⠈⠕⠎⠃⠺⠀⠴⠠⠠⠇⠇⠍⠦⠄⠰⠥⠈⠎⠊⠗⠾⠎⠑` +- `sentence_01.json` #8272: 배우 이선균(48)과 그룹 빅뱅 출신 가수 GD(35·권지용)가 마약 투약 혐의로 입건된 가운데 이들의 마약 공급책은 의사인 것으로 드러났다. + - expected: `⠠⠟⠀⠫⠠⠍⠀⠴⠰⠠⠠⠛⠙⠦⠄⠼⠉⠑⠐⠆⠈⠏⠒⠨` + - actual: `⠠⠟⠀⠫⠠⠍⠀⠴⠠⠠⠛⠙⠦⠄⠼⠉⠑⠐⠆⠈⠏⠒⠨⠕` +- `sentence_01.json` #8459: 롯데정보통신은 자사가 개발한 대화형 인공지능(AI) 모델이 한국지능정보사회진흥원(NIA)와 업스테이지가 공동으로 주최하는 한국어 언어모델 리더보드인 ‘오픈 코-LLM(Open Ko-LLM)’에서 1위를 달성했다고 1일 밝혔다. + - expected: `⠥⠙⠵⠀⠋⠥⠤⠴⠰⠠⠠⠇⠇⠍⠐⠣⠠⠕⠏⠢⠀⠠⠅⠕` + - actual: `⠥⠙⠵⠀⠋⠥⠤⠴⠠⠠⠇⠇⠍⠐⠣⠠⠕⠏⠢⠀⠠⠅⠕⠤` +- `sentence_01.json` #8525: 이어 “재정지출을 늘려 성장률 상승이 물가 상승을 따라잡을 수 있으면 실질적 GDP(국내총생산)은 늘어난다”며 “이런 문제를 단선적으로 접근하는 것이 정부의 근본적 문제”라고 지적했다. + - expected: `⠂⠨⠕⠂⠨⠹⠀⠴⠰⠠⠠⠛⠙⠏⠦⠄⠈⠍⠁⠉⠗⠰⠿⠠` + - actual: `⠂⠨⠕⠂⠨⠹⠀⠴⠠⠠⠛⠙⠏⠦⠄⠈⠍⠁⠉⠗⠰⠿⠠⠗` ### `unsupported_character_review` -- `sentence_01.json` #50: 부산광역시는 3일 오후 부산광역시청 영상회의실에서 종합화학소재기업 ㈜금양과 이차전지 생산기지 건립을 위한 8천억원 규모의 투자 양해각서(MOU)를 체결한다고 밝혔다. - - expected: `⠘⠍⠇⠒⠈⠧⠶⠱⠁⠠⠕⠉⠵⠀⠼⠉⠕⠂⠀⠥⠚⠍⠀⠘` +- `sentence_02.json` #6204: 지방세와 세외수입 체납액은 전국 어디서나 은행 자동인출기(ATM)을 이용해 고지서 없이도 납부할 수 있고 가상계좌 혹은 ARS자동응답시스템(☏043-850-7400)을 통해 신용카드로 납부할 수 있다. + - expected: `⠨⠕⠘⠶⠠⠝⠧⠀⠠⠝⠽⠠⠍⠕⠃⠀⠰⠝⠉⠃⠗⠁⠵⠀` - actual: `` - error: `Invalid symbol character` -- `sentence_01.json` #1765: 신안군은 공모사업에 앞서 지난 1월 북부권김생산자협의회, ㈜신안천사김과 미국 수출김 전용단지 지정을 위한 업무협약(MOU)을 체결하고 북부권 김 양식장 1천118ha를 공모사업 지구로 신청했다. - - expected: `⠠⠟⠣⠒⠈⠛⠵⠀⠈⠿⠑⠥⠇⠎⠃⠝⠀⠣⠲⠠⠎⠀⠨⠕` +- `sentence_02.json` #6827: 납부기간은 다음달 4일까지로 고지서를 이용해 금융기관에 방문, 납부하거나 고지서에 기재된 납부 전용계좌(가상계좌)로 이체 또는 은행 현금입출금기(ATM), 금융결제원 인터넷지로(www.giro.or.kr), 위택스(www.wetax.go.kr), ARS(☏043-850-7400) 등으로 납부할 수 있다. + - expected: `⠉⠃⠘⠍⠈⠕⠫⠒⠵⠀⠊⠣⠪⠢⠊⠂⠀⠼⠙⠕⠂⠠⠫⠨` - actual: `` - error: `Invalid symbol character` -- `sentence_01.json` #1901: 이를 위해 자산운용사 ㈜피아나인을 통해 ‘크러쉬 (CRUSH)’의 해외 진출을 위한 추가 씨드(Seed) 투자를 유치한데 이어 글로벌 마케팅 에이전시 ‘넥썸그룹 (The NEXUM Group)’과 업무협약(MOU)를 체결했다. - - expected: `⠕⠐⠮⠀⠍⠗⠚⠗⠀⠨⠇⠒⠛⠬⠶⠇⠀⠦⠄⠨⠍⠠⠴⠙` +- `sentence_02.json` #6990: 한편, 지방세, 세외수입 체납액은 전국 어디서나 은행 자동인출기(ATM)를 이용해 고지서 없이도 납부 할 수 있고 가상계좌 혹은 ARS자동응답시스템(☏043-850-7400)을 통해 신용카드로도 납부 가능하다. + - expected: `⠚⠒⠙⠡⠐⠀⠨⠕⠘⠶⠠⠝⠐⠀⠠⠝⠽⠠⠍⠕⠃⠀⠰⠝` - actual: `` - error: `Invalid symbol character` -- `sentence_01.json` #2320: 대구광역시(시장 홍준표)는 30일 대구시청 산격청사에서 국내 유망 임플란트 기업 중 하나인 ㈜덴티스(대표 심기봉)와 투자협약 양해각서(MOU)를 체결했다고 밝혔다. - - expected: `⠊⠗⠈⠍⠈⠧⠶⠱⠁⠠⠕⠦⠄⠠⠕⠨⠶⠀⠚⠿⠨⠛⠙⠬` - - actual: `` - - error: `Invalid symbol character` -- `sentence_01.json` #2946: 투자 기업은 ㈜신아(INC)로 수도권, 대전, 부산 지역에 선도적으로 지식산업센터를 보급해 민간의 자발적 후속 투자를 이끌어 내는 성과를 거두는 등 전문성을 갖춘 기업으로 평가된다. - - expected: `⠓⠍⠨⠀⠈⠕⠎⠃⠵⠀⠦⠄⠨⠍⠠⠴⠠⠟⠣⠦⠄⠴⠠⠠` +- `sentence_03.json` #16738: 김동현은 23일 자신의 사회관계망서비스(SNS)에 “드디어 산후조리원으로 토봉이 처음 안아보는 날♥ 우리 막내 딸 건강하게 태어나줘서 넘 고마웡”이라는 글과 함께 사진을 게재했다. + - expected: `⠈⠕⠢⠊⠿⠚⠡⠵⠀⠼⠃⠉⠕⠂⠀⠨⠠⠟⠺⠀⠇⠚⠽⠈` - actual: `` - error: `Invalid symbol character` @@ -3879,18 +3801,11 @@ Presentation-form cases audited: 37. | Previous observation → current observation | Cases | |---|---:| -| `encoding_error -> encoded_mismatch_pending_rule_review` | 21 | -| `encoding_error -> unsupported_character_review` | 3 | -| `nfkc_input_equivalent -> exact` | 13 | - -Remaining complex encoding errors: 3. These cases still contain another character that fails independently, so disappearance of the `roman_numeral_presentation` family does not imply that every former error case now encodes successfully. - -- `sentence_02.json` #718: 한국폴리텍Ⅳ대학 대전캠퍼스(학장 박봉순) 메타버스콘텐츠과(학과장 이종탁)는 지난 9월 26일 실감콘텐츠 전문기업 ㈜스파이더룹과 메타버스분야 인재양성과 취업 등에 관한 업무협약(MOU)을 체결했다. - - other independently unsupported: `U+321C ㈜` -- `sentence_02.json` #723: 한국폴리텍Ⅳ대학 대전캠퍼스(학장 박봉순) 메타버스콘텐츠과(학과장 이종탁)는 4일 XR(현실확장) 전문기업 이루다플래닛㈜과 메타버스분야 인재양성과 취업 등에 관한 업무협약(MOU)을 체결했다. - - other independently unsupported: `U+321C ㈜` -- `sentence_02.json` #740: 한국폴리텍Ⅳ대학 대전캠퍼스(학장 박봉순) 메타버스콘텐츠과(학과장 이종탁)는 지난 10월 5일 3D Scan & Solution 전문기업 위프코㈜와 메타버스분야 인재양성과 취업 등에 관한 업무협약(MOU)을 체결했다고 알렸다. - - other independently unsupported: `U+321C ㈜` +| `encoding_error -> encoded_mismatch_pending_rule_review` | 15 | +| `nfkc_input_equivalent -> exact` | 22 | + +Remaining complex encoding errors: 0. These cases still contain another character that fails independently, so disappearance of the `roman_numeral_presentation` family does not imply that every former error case now encodes successfully. + ## Rules 34/54 Korean-prefixed Roman annotations diff --git a/docs/corpus-analysis/NIKL_2025_V1_inquiry.md b/docs/corpus-analysis/NIKL_2025_V1_inquiry.md index 8020299b..8b500d05 100644 --- a/docs/corpus-analysis/NIKL_2025_V1_inquiry.md +++ b/docs/corpus-analysis/NIKL_2025_V1_inquiry.md @@ -23,9 +23,11 @@ ## 2. 대조 방법과 재현 범위 - 분석 대상: 83,528문장 -- 현재 완전 일치: 69,430문장(83.12%) -- 불일치 또는 인코딩 불가: 14,098문장 -- 분류 결과: 규정 검토 대기 12,513건, 규정과 참조값의 모순 후보 1,114건, NFKC 비교 316건, 미지원 문자 검토 155건 +- 현재 완전 일치: 75,726문장(90.66%) +- 불일치 또는 인코딩 불가: 7,802문장 +- 분류 결과: 규정 검토 대기 6,505건, 규정과 참조값의 모순 후보 1,293건, 미지원 문자 검토 4건 +- 모순 후보의 구성: 제34항 괄호/로마자표 순서 1,160건, UEB 비독립 대문자 글자열 앞의 불필요한 1급 점자표 70건, 제37항 여섯 단어의 구간 내부 단어 약자 59건, UEB 대문자 구절을 개별 대문자표로 적은 참조 3건, 로마자 U+2026 말줄임표의 상충 참조 1건 +- 비교 전용 정규화 분류: 0건. 과거 NFKC로만 같았던 잔여 8건은 관련 규정에 근거한 문자별 처리로 모두 해소했으며, 전체 입력에 대한 일괄 NFKC는 적용하지 않음 - 비교 필드: 묵자 `input`과 말뭉치 점자 참조값 `unicode`만 사용 - 완전 일치의 정의: 공백을 포함한 유니코드 점자 셀 배열이 처음부터 끝까지 동일한 경우 - 위치 표기: `sentence_XX.json #N`에서 `N`은 사람이 확인하기 쉬운 1부터 시작하는 배열 순번 @@ -48,7 +50,7 @@ 3. 괄호 안 로마자 4. 한글 닫는 소괄호 -현재 점역기도 이 순서를 따릅니다. 그런데 말뭉치에는 한글 바로 뒤의 닫힌 로마자 주석에서 기대값이 `⠴⠐⠣`(로마자표 + UEB 여는 괄호)로 시작하고, 현재 규정 경로가 `⠦⠄⠴`(한글 여는 괄호 + 로마자표)로 시작하는 사례가 1,114건 있습니다. 단순히 같은 문장에 괄호가 있다는 이유가 아니라, 실제 입력 위치에서 이 세 셀의 순서가 모두 반대로 확인되는 경우만 셌습니다. +현재 점역기도 이 순서를 따릅니다. 그런데 말뭉치에는 한글 바로 뒤의 닫힌 로마자 주석에서 기대값이 `⠴⠐⠣`(로마자표 + UEB 여는 괄호)로 시작하고, 현재 규정 경로가 `⠦⠄⠴`(한글 여는 괄호 + 로마자표)로 시작하는 사례가 1,160건 있습니다. 단순히 같은 문장에 괄호가 있다는 이유가 아니라, 실제 입력 위치에서 이 세 셀의 순서가 모두 반대로 확인되는 경우만 셌습니다. 대표 사례: @@ -59,14 +61,14 @@ 문의: 1. 한글 문장 안에서 `한글(로마자)`를 점역할 때 여는 괄호와 로마자표의 올바른 순서는 `⠦⠄⠴`입니까? -2. 그렇다면 `⠴⠐⠣`로 시작하는 위 1,114건은 말뭉치 참조값 정정 대상입니까? +2. 그렇다면 `⠴⠐⠣`로 시작하는 위 1,160건은 말뭉치 참조값 정정 대상입니까? 3. 괄호 안이 약어, 일반 영단어, 로마자와 숫자의 결합이어도 같은 순서를 적용합니까? ### 질문 2. 로마자 바로 뒤에 붙은 가운뎃점(U+00B7) 앞의 로마자 종료표 제29항은 한글 문장 안의 로마자 앞뒤에 로마자표와 로마자 종료표를 쓰도록 합니다. 제33항은 종료표를 생략하거나 문장 부호 앞으로 옮기는 경계를 열거하지만, 가운뎃점은 그 목록에 없습니다. 제50항은 가운뎃점을 앞뒤 말에 붙여 쓰도록 규정합니다. -`로마자·한글/로마자` 경계 후보 577건은 완전 일치 대조군이 0건이고 모두 불일치입니다. 그중 451건은 최초 차이가 정확히 가운뎃점 직전의 한 셀에 위치하며, 말뭉치는 가운뎃점 `⠐`을 기대하지만 규정 기반 출력은 먼저 로마자 종료표 `⠲`을 냅니다. 더 좁은 `AI·SW`형 순수 대문자 코호트도 97건 모두 불일치입니다. +`로마자·한글/로마자` 경계 후보 577건은 완전 일치 대조군이 0건이고 모두 불일치입니다. 그중 516건은 최초 차이가 정확히 현재 출력의 로마자 종료표 위치에 있으며, 말뭉치는 가운뎃점 `⠐`을 기대하지만 규정 기반 출력은 먼저 로마자 종료표 `⠲`을 냅니다. 더 좁은 `AI·SW`형 순수 대문자 코호트도 97건 모두 불일치입니다. 대표 사례: @@ -86,8 +88,8 @@ 제49항은 삼각형 문장 부호의 점형을 정하고 인쇄물의 띄어쓰기를 따르게 합니다. 제72항은 `△`를 항목 표지로도 사용하지만 PDF 예는 줄 배치 또는 공백이 있는 목록입니다. 말뭉치에는 입력 자체가 `△한글`처럼 붙어 있는데 기대 점자에는 삼각형 뒤 공백이 들어간 사례가 반복됩니다. -- 후보 377건 / 완전 일치 0건 / 불일치 377건 -- 최초 차이가 삼각형과 바로 뒤 첫 한글 셀 범위에 있는 사례 335건 +- 후보 377건 / 완전 일치 315건 / 불일치 62건 +- 현재 출력의 삼각형과 바로 뒤 첫 한글 셀 범위에 최초 차이가 있는 사례 3건 - 대표: `sentence_01.json #3907` `△청구…`, `sentence_02.json #17` `△문화치유`, `sentence_03.json #244` `△에듀테크&콘텐츠` 문의: @@ -105,10 +107,10 @@ | 철자 구조 | 후보 | 완전 일치 | 불일치 | 목표 최초 차이 | 역방향 | |---|---:|---:|---:|---:|---:| -| `OU` 포함 | 1,816 | 70 | 1,746 | `o ⠕ → ou 약자 ⠳` 1,355 | 0 | -| `ST` 포함 | 1,479 | 702 | 777 | `s ⠎ → st 약자 ⠌` 451 | 1 | -| `AR` 포함 | 1,022 | 370 | 652 | `a ⠁ → ar 약자 ⠜` 400 | 2 | -| `ED` 포함 | 816 | 334 | 482 | `e ⠑ → ed 약자 ⠫` 327 | 0 | +| `OU` 포함 | 1,816 | 128 | 1,688 | `o ⠕ → ou 약자 ⠳` 1,502 | 0 | +| `ST` 포함 | 1,479 | 812 | 667 | `s ⠎ → st 약자 ⠌` 466 | 별도 확정 안 함 | +| `AR` 포함 | 1,022 | 477 | 545 | `a ⠁ → ar 약자 ⠜` 411 | 별도 확정 안 함 | +| `ED` 포함 | 816 | 362 | 454 | `e ⠑ → ed 약자 ⠫` 339 | 0 | 완전 일치 대조에는 `JUSTOUCH`(`sentence_01.json #850`), `YOU`(`sentence_02.json #727`), `STAYG`(`sentence_01.json #44`), `KAIST`(`sentence_02.json #82`), `DGIST`(`sentence_03.json #281`), `OLED`(`sentence_01.json #26`) 등이 있습니다. 반면 `MOU`, `AR/ARS`, `LED/GED`, `GH/GHP`, `ERP/ERBUD`, `SH` 계열에서는 글자별 발음 여부에 따라 참조값이 달라지는 것으로 보입니다. @@ -121,7 +123,7 @@ ### 질문 5. `A(14)`형 표면을 인명 표지로 볼지 수식으로 볼지 -`A(14)`처럼 단일 대문자 뒤에 숫자 괄호가 붙는 후보는 1,361건이며 완전 일치가 0건, 불일치가 1,361건입니다. 1,342건은 최초 차이가 해당 진입 경계 안에 있습니다. 기사 문맥에서는 익명 인명과 나이로 보이지만, 같은 표면은 함수·수학 변수·문항 번호일 수도 있습니다. +`A(14)`처럼 단일 대문자 뒤에 숫자 괄호가 붙는 후보는 1,361건이며 완전 일치가 1,351건, 불일치가 10건입니다. 그중 4건은 최초 차이가 해당 진입 경계 안에 있습니다. 대다수 기사 문맥은 일반문 경로로 재현됐지만, 같은 표면은 함수·수학 변수·문항 번호일 수도 있으므로 남은 사례를 표면형만으로 일반화하지 않았습니다. 대표 사례: @@ -138,7 +140,7 @@ ### 질문 6. `HCA(Home Connectivity Alliance)`형 약어 풀이의 로마자 구간 재진입 -대문자 표제어 뒤에 닫힌 괄호가 있고, 괄호 안에 공백으로 구분된 로마자 단어가 둘 이상인 구조는 175건입니다. 제29항과 제34항을 보수적으로 적용하여, 표제어가 ASCII 대문자 2자 이상이고 괄호 안이 ASCII 글자 단어 2개 이상이며 숫자·연산자·중첩 괄호·다른 문자가 없는 경우만 일반 로마자 풀이로 처리했습니다. 이어 제34항 공식 예시 `링컨(Lincoln)은`에 따라 닫는 괄호 뒤에 붙은 한글과 문장 부호도 일반문 경계로 처리하되, 영문·숫자 꼬리는 계속 제외했습니다. 그 결과 완전 일치는 17건에서 87건으로 70건 증가했고, 불일치는 88건입니다. 남은 불일치의 1차 분류는 규정 검토 대기 74건, 제34항 모순 후보 7건, 정규화 비교 5건, 미지원 문자 2건입니다. 이 구조는 **입력 형태를 모은 교차 진단군일 뿐 1차 분류를 바꾸지 않습니다**. +대문자 표제어 뒤에 닫힌 괄호가 있고, 괄호 안에 공백으로 구분된 로마자 단어가 둘 이상인 구조는 175건입니다. 제29항과 제34항을 보수적으로 적용하여, 표제어가 ASCII 대문자 2자 이상이고 괄호 안이 ASCII 글자 단어 2개 이상이며 숫자·연산자·중첩 괄호·다른 문자가 없는 경우만 일반 로마자 풀이로 처리했습니다. 이어 제34항 공식 예시 `링컨(Lincoln)은`에 따라 닫는 괄호 뒤에 붙은 한글과 문장 부호도 일반문 경계로 처리하되, 영문·숫자 꼬리는 계속 제외했습니다. 현재 완전 일치는 113건, 불일치는 62건입니다. 이 구조는 **입력 형태를 모은 교차 진단군일 뿐 1차 분류를 바꾸지 않습니다**. 정확 대조: @@ -162,7 +164,7 @@ ### 질문 7. `F-35`형 식별자와 수학식의 구분 -대문자 로마자 run + 하이픈 + 숫자 구조는 571건이며 완전 일치 194건, 불일치 377건입니다. 그중 262건은 최초 차이가 해당 구조의 진입/출력 범위에 있습니다. 제35항은 `D-100`을 로마자와 숫자가 이어지는 예로 제시하지만, 수학 규정은 대문자 변수와 마이너스를 별도 경로로 처리합니다. +대문자 로마자 run + 하이픈 + 숫자 구조는 571건이며 완전 일치 503건, 불일치 68건입니다. 현재 잔여 중 최초 차이가 해당 구조의 진입/출력 범위에 직접 국소화된 사례는 0건입니다. 제35항은 `D-100`을 로마자와 숫자가 이어지는 예로 제시하지만, 수학 규정은 대문자 변수와 마이너스를 별도 경로로 처리하므로 표면형의 의미 충돌 여부는 별도로 문의합니다. 정확 대조에는 `GLS-5310`(`sentence_01.json #88`), `MMPI-2`(`sentence_02.json #167`), `X-2`(`sentence_03.json #217`), `GPT-4`(`sentence_04.json #48`)가 있습니다. 불일치 대표는 `sentence_04.json #323`의 `F-35`입니다. @@ -178,10 +180,10 @@ | 진단 구조 | 후보 | 완전 일치 | 불일치 | 구조 안 최초 차이 | |---|---:|---:|---:|---:| -| 연속 ASCII 로마자 단어의 공백 경계 | 4,679 | 2,287 | 2,392 | 26 | -| 닫힌 로마자 괄호 뒤 공백 후 새 로마자 | 1,093 | 143 | 950 | 260 | -| 앞 한글 단어 뒤 공백 후 로마자 괄호 표제어 | 4,695 | 3,550 | 1,145 | 163 | -| 기존 로마자 뒤 공백 후 대문자 단어 | 1,729 | 760 | 969 | 기존 진입 경계 진단군 | +| 연속 ASCII 로마자 단어의 공백 경계 | 4,679 | 3,229 | 1,450 | 7 | +| 닫힌 로마자 괄호 뒤 공백 후 새 로마자 | 1,093 | 571 | 522 | 6 | +| 앞 한글 단어 뒤 공백 후 로마자 괄호 표제어 | 4,695 | 3,933 | 762 | 4 | +| 기존 로마자 뒤 공백 후 대문자 단어 | 1,729 | 1,086 | 643 | 4 | 정확 대조로는 PDF 예와 같은 다단어 로마자, `Global X`(`sentence_01.json #2785`), `BYD), BMW`(`sentence_02.json #1282`), `KODEX 인도 Nifty50`(`sentence_03.json #46`) 등이 공존합니다. 남은 대표 경계에는 `SYNO PEM-1`, `ACE Fair(2020)`, `Mnet K-POP`, `ESS /VPP`가 있습니다. @@ -196,9 +198,9 @@ UEB에는 `AT&T`, `B&B`, `&c`처럼 앰퍼샌드와 로마자가 붙어 있는 예가 있고, 한국 점자 규정 제71항에는 한글 사이의 독립적인 앰퍼샌드 예가 있습니다. 이를 근거로 완전한 `A&B`형 로마자 run은 한 구간으로 처리할 수 있었지만, 숫자가 이어지거나 한쪽에만 로마자가 붙는 경우는 남아 있습니다. -- 붙은 로마자 `A&B`형: 802건 / 완전 일치 607건 / 불일치 195건 -- `&c`처럼 오른쪽 로마자만 붙은 좁은 구조: 30건 / 완전 일치 12건 / 불일치 18건 -- 남은 대표 전이: `S&P500`류에서 앰퍼샌드 셀과 로마자 종료표 경계 79건 +- 붙은 로마자 `A&B`형: 802건 / 완전 일치 684건 / 불일치 118건 +- `&c`처럼 오른쪽 로마자만 붙은 좁은 구조: 30건 / 완전 일치 14건 / 불일치 16건 +- 남은 불일치는 다른 로마자 약자·숫자 연속·문장 경계 차이와 겹칠 수 있으므로 앰퍼샌드 자체의 문제로 일괄 분류하지 않음 문의: @@ -209,7 +211,7 @@ UEB에는 `AT&T`, `B&B`, `&c`처럼 앰퍼샌드와 로마자가 붙어 있는 ### 질문 10. 숫자+ASCII 접미부의 단위·식별자·분수 해석 -숫자 바로 뒤에 ASCII 문자가 붙은 구조는 2,975건이며 완전 일치 1,913건, 불일치 1,062건입니다. 256건은 최초 차이가 그 전체 토큰 또는 진입 경계 안에 있습니다. 제69항의 공식 단위는 일반화할 수 있지만, 같은 표면형이 단위·변수·모델명일 수 있어 모든 ASCII 접미부를 단위로 볼 수는 없습니다. +숫자 바로 뒤에 ASCII 문자가 붙은 구조는 2,975건이며 완전 일치 2,483건, 불일치 492건입니다. 42건은 최초 차이가 그 전체 토큰 또는 진입 경계 안에 있습니다. 제69항의 공식 단위와 Unicode 호환 단위에서 규정상 도출되는 철자는 일반화했지만, 같은 표면형이 단위·변수·모델명일 수 있어 모든 ASCII 접미부를 단위로 보지는 않았습니다. 정확 대조에는 `118.0GW`(`sentence_01.json #343`), `20kg`(`sentence_02.json #29`), `692g`(`sentence_03.json #60`) 등이 있습니다. 남은 대표 사례는 다음과 같습니다. @@ -245,44 +247,34 @@ UEB에는 `AT&T`, `B&B`, `&c`처럼 앰퍼샌드와 로마자가 붙어 있는 3. U+002D `-`, U+2013 `–`, 한국어 줄표가 혼용된 입력은 원문 코드 포인트를 보존해야 합니까, 의미에 맞게 정규화해야 합니까? 4. 제49항의 “묵자의 띄어쓰기를 따른다”는 입력에 있는 공백을 그대로 보존하라는 뜻입니까? -### 질문 12. NFKC로만 같아지는 316건의 비교 정책 +### 질문 12. 호환 문자의 선택적 정규화 정책 -현재 316건은 원 입력을 그대로 점역한 결과와는 다르지만, 입력에 NFKC를 적용한 뒤에는 말뭉치 참조값과 일치하여 `comparison_method`로 별도 분류했습니다. 이 분류는 엔진이 모든 입력을 무조건 NFKC로 바꾸어야 한다는 뜻이 아닙니다. 예를 들어 로마 숫자 표시는 제36항에 근거해 해당 로마자로 풀 수 있지만, `㈜`, `ℓ`, 제목 기호 등은 의미와 코드 포인트가 달라질 수 있습니다. +현재 `comparison_method` 잔여는 0건입니다. 전체 입력을 NFKC로 바꾼 것이 아니라, 제36항의 Unicode 로마 숫자와 제68·69항의 Unicode 호환 단위처럼 현행 규정으로 대응 철자를 도출할 수 있는 문자만 선택적으로 처리했습니다. 직전 잔여 8건도 U+2026 말줄임표 1건과 호환 단위의 로마자/숫자 구간 경계 7건으로 나누어 규정에 따라 처리했습니다. 따라서 `㈜` 같은 다른 호환 문자를 자동 분해하는 일반 NFKC 규칙은 두지 않았습니다. 문의: 1. 말뭉치 참조값은 원 `input` 코드 포인트를 기준으로 작성됐습니까, NFKC 등 사전 정규화를 거친 문자열을 기준으로 작성됐습니까? -2. 호환 문자와 분해된 ASCII 철자가 의미상 같아도 동일한 참조값을 요구합니까? -3. 공식 권장 정규화 형식(NFC/NFKC)과 예외 문자 목록이 있습니까? -4. 정규화 전후가 다른 316건을 평가할 때 어느 결과를 정답으로 보아야 합니까? - -### 질문 13. 현행 엔진에서 단독으로도 인코딩되지 않는 문자 - -다음 155건은 포함 문자를 하나씩 따로 인코딩해도 실패하는 것으로 확인했습니다. PDF에서 독립적인 지원 근거를 찾지 못해 “오류”가 아니라 “미지원 문자 검토”로 남겨 두었습니다. - -| 문자 | 포함 문장 수 | NFKC 분해 | 현재 진단 | -|---|---:|---|---| -| `㈜` U+321C | 134 | `(주)` | 괄호 문자 조직 표지 | -| `㈔` U+3214 | 2 | `(사)` | 괄호 문자 조직 표지 | -| `ℓ` U+2113 | 9 | `l` | 단위/문자 기호 | -| `☏` U+260F | 3 | 그대로 | 전화 기호 | -| `⸱` U+2E31 | 2 | 그대로 | 구두점 | -| `ˑ` U+02D1 | 1 | 그대로 | 구두점 | -| `‥` U+2025 | 1 | `..` | 두 점 줄임표 | -| `│` U+2502 | 1 | 그대로 | 레이아웃 세로선 | -| `▲` U+25B2 | 1 | 그대로 | 검은 삼각형 | -| `▴` U+25B4 | 1 | 그대로 | 작은 검은 삼각형 | -| `♥` U+2665 | 1 | 그대로 | 하트 기호 | -| `〮` U+302E | 1 | 그대로 | 한글 성조점 | - -두 문자는 한 문장에 함께 나타날 수 있어 문자별 “포함 문장 수”의 합은 155보다 클 수 있습니다. +2. Unicode 호환 단위는 그 호환 문자의 표준 분해 철자를 사용해 제68·69항을 적용하는 것이 맞습니까? +3. 공식 권장 정규화 형식(NFC/NFKC)과, 정규화하면 안 되는 예외 문자 목록이 있습니까? +4. 문자별 규정 근거가 없는 경우에는 원 코드 포인트를 보존하고 미지원으로 보고하는 것이 맞습니까? + +### 질문 13. 현행 규정에서 점형을 찾지 못한 문자 4건 + +현재 단독 인코딩도 실패하며 2024 개정 한국 점자 규정에서 독립적인 점형 근거를 찾지 못한 사례는 4건뿐입니다. + +| 문자 | 포함 문장 수 | NFKC 분해 | 말뭉치 참조에서 관찰된 처리 | 현재 진단 | +|---|---:|---|---|---| +| `☏` U+260F | 3 | 그대로 | 기호 자리에 로마자 `TEL`에 해당하는 점형이 들어간 것으로 관찰됨 | 공식 근거 미확인 | +| `♥` U+2665 | 1 | 그대로 | 기호 위치에 추가 공백이 들어간 것으로 관찰됨 | 공식 근거 미확인 | + +위 관찰은 참조값을 역산해 구현하기 위한 근거로 사용하지 않았습니다. UEB 11.7.2의 전사자 정의 도형은 독자에게 정의를 제공해야 하므로, 범용 자동 점역기의 고정 점형으로 채택하지 않았습니다. 문의: -1. 각 문자에 공식 권장 점형이 있습니까? -2. `㈜→(주)`, `㈔→(사)`, `ℓ→l`, `‥→..`와 같은 호환 분해를 자동 점역 전에 적용해도 됩니까? -3. `│`, `▲`, `▴`, `♥`처럼 문서 레이아웃 또는 장식 역할일 수 있는 문자는 삭제, 설명, 대체 점형 중 어느 정책을 적용해야 합니까? -4. 공식 규정의 지원 범위 밖이라면 말뭉치가 기대하는 점형을 어떤 근거로 재현해야 합니까? +1. `☏`와 `♥`에 공식 권장 점형이 있습니까? +2. `☏`를 `TEL`로 풀어 쓰거나 `♥`를 공백으로 대체하는 것이 말뭉치 구축 지침에 따른 의도적 처리입니까? +3. 전사자 정의 기호라면 문서마다 점형과 설명을 함께 제공해야 합니까? +4. 공식 규정의 지원 범위 밖이라면 자동 점역기는 오류를 반환해야 합니까, 원문을 보존해야 합니까, 또는 별도의 대체 텍스트 입력을 요구해야 합니까? ### 질문 14. 묵자 입력에 없는 띄어쓰기나 교정의 허용 범위 @@ -297,7 +289,7 @@ UEB에는 `AT&T`, `B&B`, `&c`처럼 앰퍼샌드와 로마자가 붙어 있는 ### 질문 15. 로마자·단위·숫자 뒤에 붙은 괄호를 어느 점자 체계로 적는지 -앞에서 설명한 제34항의 1,114건은 **한글 뒤 괄호 안에 로마자가 있는 경우**입니다. 이와 반대로 `BSI(73)`, `Merit(4위)`, `M(41)`, `43bp(1bp…)`처럼 로마자·단위 뒤의 괄호 안에 숫자 또는 한글이 있는 경우도 별도로 남습니다. 기존 output-localized 코호트를 제외한 뒤에도 기대 한글 여는 소괄호의 첫 셀 `⠦`와 현재 UEB 여는 괄호의 첫 셀 `⠐`이 충돌하는 잔여가 133건입니다. +앞에서 설명한 제34항의 1,160건은 **한글 뒤 괄호 안에 로마자가 있는 경우**입니다. 이와 반대로 `BSI(73)`, `Merit(4위)`, `M(41)`, `43bp(1bp…)`처럼 로마자·단위 뒤의 괄호 안에 숫자 또는 한글이 있는 경우도 별도로 남습니다. 기존 output-localized 코호트를 제외한 뒤에도 기대 한글 여는 소괄호의 첫 셀 `⠦`와 현재 UEB 여는 괄호의 첫 셀 `⠐`이 충돌하는 잔여가 66건입니다. 대표 사례: @@ -324,7 +316,7 @@ UEB에는 `AT&T`, `B&B`, `&c`처럼 앰퍼샌드와 로마자가 붙어 있는 - `sentence_01.json #21108`: `양(+)극과 음(-)극` — `-`는 U+002D이나 참조값은 `⠔` - `sentence_02.json #23164`: `마이너스(-)였으나` — 같은 전이가 최초 차이로 재현됨 -또한 상표·프로그램 이름에 붙은 `한글+(한글 풀이)` 구조는 16건입니다. 현재 제46항 경로와 전체 참조값이 일치하는 문장은 3건, 불일치는 13건이며, 보수적인 실제 출력 위치 검증을 통과한 10건은 현재 출력의 `+` 뒤 공백과 참조값의 여는 괄호가 직접 충돌합니다. 특히 동일한 `도전+(플러스)` 표면형에서 공백을 요구하는 참조와 생략하는 참조가 모두 존재합니다. +또한 상표·프로그램 이름에 붙은 `한글+(한글 풀이)` 구조는 16건입니다. 현재 제46항 경로와 전체 참조값이 일치하는 문장은 4건, 불일치는 12건이며, 불일치 12건 모두 최초 차이가 현재 출력의 해당 구조 안에 있습니다. 특히 동일한 `도전+(플러스)` 표면형에서 공백을 요구하는 참조와 생략하는 참조가 모두 존재합니다. - 일치: `sentence_02.json #4383`의 `도전+(플러스)` - 불일치: `sentence_02.json #168`, `#6210`의 `도전+(플러스)` @@ -338,6 +330,119 @@ UEB에는 `AT&T`, `B&B`, `&c`처럼 앰퍼샌드와 로마자가 붙어 있는 3. `도전+(플러스)`처럼 이름 뒤에 기호의 한글 풀이를 괄호로 붙인 경우에도 제46항에 따라 `+` 양옆을 띄어야 합니까? 4. 동일 표면형의 참조값에서 공백이 서로 다른 사례는 어느 쪽으로 통일해야 합니까? +### 질문 17. 로마자 구간 안 U+2026 말줄임표의 상충 참조 + +제32항에 따라 한글 문장 안의 로마자 구간은 UEB를 적용하는 것으로 이해했습니다. UEB 2024 제7.3절은 U+2026 `…` 말줄임표를 `⠲⠲⠲`로 제시합니다. 반면 한국 점자 규정 제53항의 한글 문장 부호 말줄임표는 `⠠⠠⠠`입니다. + +말뭉치에는 같은 구조인 `ASCII 로마자 + U+2026 + 닫는 괄호`에 서로 다른 참조가 있습니다. + +- `sentence_01.json #2530`: `…(I AM…)…` — 참조값은 UEB `⠲⠲⠲`이며 현재 규정 기반 출력과 완전 일치 +- `sentence_01.json #13772`: `…(Love Is…)…` — 참조값은 한국어 말줄임표 `⠠⠠⠠`이며 현재 UEB 출력 `⠲⠲⠲`과 불일치 + +두 사례 모두 U+2026이 로마자 바로 뒤, 닫는 소괄호 바로 앞에 있습니다. 따라서 두 번째 사례 1건은 표면 구조나 코드 포인트 차이로 설명할 수 없는 상충 참조로 분류했습니다. + +문의: + +1. 한글 문장 안 괄호에 든 로마자 구간의 U+2026은 UEB 제7.3절에 따라 `⠲⠲⠲`로 적는 것이 맞습니까? +2. 그렇다면 `Love Is…`의 `⠠⠠⠠` 참조는 `⠲⠲⠲`로 정정해야 합니까? +3. 로마자 구간 안에서도 한국어 문장 부호 점형을 적용하는 예외가 있다면, 그 조건과 근거 항은 무엇입니까? + +### 질문 18. 제37항의 여섯 단어를 로마자 구간 내부에서 단어 약자로 적은 참조 59건 + +제37항 붙임의 공식 예 `be, his, was, were의 약자를 바르게 쓰시오.`에서는 다음 세 위치가 한 문장에 함께 제시됩니다. + +- `be`: 로마자표 바로 뒤 +- `his`, `was`: 로마자 구간 내부 +- `were`: 로마자 종료표 바로 앞 + +공식 점자는 네 단어를 모두 단어 약자로 적지 않고 알파벳과 적용 가능한 묶음 약자로 풀어 씁니다. 따라서 현행 점역기도 같은 로마자 구간에 있는 `be`, `enough`, `his`, `in`, `was`, `were`를 풀어 씁니다. + +그런데 말뭉치에는 이 여섯 단어 중 하나 이상을 구간 내부에서 UEB 하위 단어 약자로 적은 참조가 59건 있습니다. 현재 표준 출력에서 해당 단어의 풀어 쓴 셀만 단어 약자 한 셀로 바꾸면 문장 전체가 완전 일치하며, 그 외 셀 차이는 없습니다. 이 동작은 위 제37항 공식 예의 구간 내부 `his`, `was`와 직접 충돌하므로 정확도를 위해 표준을 어기는 변경은 유지하지 않았습니다. + +대표 사례: + +- `sentence_01.json #507`: `Frontiers in Drug Delivery` +- `sentence_01.json #602`: `Trends in Biotechnology` +- `sentence_01.json #13800`: `I'll Be There` +- `sentence_02.json #4388`: `Boys, Be Different` +- `sentence_03.json #3516`: `Boys will be Boys` + +문의: + +1. 제37항의 여섯 단어는 공식 예와 같이 로마자 구간의 처음·중간·끝 어디에서나 단어 약자를 쓰지 않는 것이 맞습니까? +2. 그렇다면 위 59건에서 사용된 `be ⠆`, `in ⠔` 등의 하위 단어 약자 참조는 풀어 쓴 점형으로 정정해야 합니까? +3. 제37항 붙임의 “로마자 종료표 앞에서도”가 구간 중간에서는 단어 약자를 허용한다는 뜻이라면, 공식 예의 내부 단어 `his`, `was`를 풀어 쓴 이유는 무엇입니까? +4. 대문자로 시작하는 `In`, `Be`에도 같은 원칙을 적용하되 대문자표만 앞세우면 됩니까? + +### 질문 19. UEB 제8.5.2·8.5.3의 대문자 구절 대신 개별 대문자표를 쓴 참조 3건 + +제32항에 따라 한글 문장 안의 로마자 구간에는 UEB를 적용하는 것으로 이해했습니다. UEB 2024 제8.5.2는 구절을 세 개 이상의 기호열(symbols-sequence)로 정의하고 비알파벳 기호를 포함할 수 있다고 규정합니다. 제8.5.3은 대문자 구절의 마지막 적용 기호열 바로 뒤에 대문자 종료표를 두도록 합니다. 공식 예 `CAUTION: WET PAINT!`, `THE BBC AFRICA NEWS`, `A SELF-MADE MAN`, `A.A. (ALAN ALEXANDER) MILNE`도 공백·문장 부호·하이픈·괄호를 포함한 세 개 이상의 기호열에 대문자 구절표 `⠠⠠⠠`와 종료표 `⠠⠄`를 사용합니다. + +이 규칙을 특정 입력 목록이 아니라 연속된 대문자 기호열의 구조로 일반화하자, 직전 엔진과의 전체 말뭉치 대조에서 새로 완전 일치한 문장은 161건, 기존 완전 일치에서 벗어난 문장은 아래 3건으로 순증 158건이었습니다. 세 손실은 모두 현재 출력의 대문자 구절표 한 쌍을 말뭉치 참조의 기호열별 한 칸 또는 두 칸 대문자표로 바꾸면 문장 전체가 정확히 일치합니다. 로마자 철자, 약자, 문장 부호, 공백 등 다른 차이가 하나라도 있는 사례는 이 3건에 포함하지 않았습니다. + +| 위치 | 입력의 관련 구간 | 말뭉치 참조의 대문자 처리 | UEB 제8.5.2·8.5.3 처리 | +|---|---|---|---| +| `sentence_01.json #12980` | `SBS M, SBS FiL` | `⠠⠠SBS … ⠰⠠M … ⠠⠠SBS` | `⠠⠠⠠SBS … ⠰M … SBS⠠⠄` | +| `sentence_03.json #16532` | `FESTA(2023 BTS FESTA)` | `FESTA`, `BTS`, `FESTA`마다 `⠠⠠` | 첫 `FESTA` 앞 `⠠⠠⠠`, 마지막 적용 기호열 뒤 `⠠⠄` | +| `sentence_03.json #20410` | `D N D` | `⠠D … ⠰⠠N … ⠰⠠D` | `⠠⠠⠠D … ⠰N … ⠰D⠠⠄` | + +표의 로마자 철자는 대문자표 위치를 보이기 위한 축약 표기이며, `⠰`은 해당 한 글자 기호열에 필요한 1급 점자표로 그대로 유지됩니다. 첫 사례의 뒤쪽 `FiL`은 혼합 대소문자이므로 현재 구현은 그 앞에서 대문자 구절을 종료합니다. + +문의: + +1. 위 세 관련 구간은 각각 UEB 제8.5.2의 세 개 이상 대문자 기호열로 보아 대문자 구절표와 종료표를 쓰는 것이 맞습니까? +2. 그렇다면 기호열마다 개별 대문자표를 사용한 위 3건의 말뭉치 참조값은 대문자 구절 표기로 정정해야 합니까? +3. 방송 채널명, 행사명, 노래 제목, 한 글자 표제어처럼 고유명사의 종류에 따라 제8.5.2 적용을 배제하는 예외가 있습니까? +4. 쉼표, 숫자, 소괄호, 하이픈 및 뒤따르는 혼합 대소문자는 기호열 수와 대문자 구절의 시작·종료 범위에 각각 어떤 영향을 줍니까? + +### 질문 20. 여는 괄호가 바로 뒤따르는 대문자 글자열 앞의 1급 점자표 70건 + +UEB 2024 제2.6은 글자·글자열의 앞뒤에 공백이나 하이픈·대시가 있거나, 제2.6.2와 제2.6.3에 열거된 기호만 끼어 있을 때 이를 “독립되어 있다(standing alone)”고 봅니다. 제2.6.2는 **글자열 앞에 올 수 있는** 기호에 여는 소괄호·대괄호·중괄호를 포함하지만, 제2.6.3의 **글자열 뒤에 올 수 있는** 기호에는 닫는 괄호만 포함하고 여는 괄호는 포함하지 않습니다. 따라서 `GDC(`, `LLM(`처럼 여는 괄호가 바로 뒤따르는 대문자 글자열은 독립된 글자열이 아닌 것으로 해석했습니다. + +UEB 제5.7.2와 제10.9.7은 글자열이 독립되어 있고 하위 단어 약자(shortform)로 잘못 읽힐 수 있을 때 1급 점자표를 사용하도록 합니다. 제10.9.8은 하위 단어 약자로 시작하는 더 긴 알파벳 단어에 별도 규칙을 둡니다. 위와 같이 여는 괄호가 뒤따르는 비독립 글자열에는 이 조건이 성립하지 않으므로, 현재 점역기는 대문자 단어표 `⠠⠠`는 쓰되 그 앞의 1급 점자표 `⠰`은 쓰지 않습니다. + +말뭉치에는 이 위치에 `⠰`을 추가한 참조가 70건 있습니다. 아래 70건은 입력 철자나 괄호 안 내용으로 선별하지 않았습니다. 먼저 비독립 구조와 하위 단어 약자 충돌 가능성을 입력만으로 검출한 뒤, 참조값에서 현재 표준 출력의 해당 `⠠⠠` 바로 앞에 있는 `⠰` 한 칸만 제거하면 문장 전체가 완전 일치하는 경우로 한정했습니다. 철자·약자·괄호·공백 등 다른 셀 차이가 하나라도 있는 사례는 포함하지 않았습니다. + +| 위치 | 입력의 관련 구간 | 말뭉치 참조 | UEB 해석에 따른 현재 출력 | +|---|---|---|---| +| `sentence_01.json #1573` | `GDC(Game Developers Conference)` | `…⠴⠰⠠⠠⠛⠙⠉⠐⠣…` | `…⠴⠠⠠⠛⠙⠉⠐⠣…` | +| `sentence_01.json #5005` | `LLM(초거대언어모델)` | `…⠴⠰⠠⠠⠇⠇⠍⠦⠄…` | `…⠴⠠⠠⠇⠇⠍⠦⠄…` | +| `sentence_01.json #8525` | `GDP(국내총생산)` | `…⠴⠰⠠⠠⠛⠙⠏⠦⠄…` | `…⠴⠠⠠⠛⠙⠏⠦⠄…` | + +문의: + +1. 여는 소괄호·대괄호·중괄호가 바로 뒤따르는 대문자 글자열은 UEB 제2.6.3에 따라 독립된 글자열이 아닌 것이 맞습니까? +2. 그렇다면 위 70건의 참조값에서 대문자 단어표 앞의 1급 점자표 `⠰`을 삭제해야 합니까? +3. 괄호 안 내용이 앞 약어의 영문 풀이·한글 풀이·숫자 등의 부가 설명이라는 의미 관계가 제2.6의 독립 여부를 바꾸는 예외가 있습니까? +4. 같은 원칙을 여는 소괄호·대괄호·중괄호 모두에, 그리고 괄호 안이 로마자·한글·숫자인 경우 모두에 동일하게 적용합니까? + +### 질문 21. 로마자 구간 내부 `*`·`+`와 식별자·수식의 의미 경계 + +제32항에 따라 한글 문장 안의 로마자 구간에는 UEB를 적용하는 것으로 이해했습니다. UEB 2024 제3.3.1은 별표를 의미와 관계없이 묵자의 위치와 띄어쓰기에 따라 적도록 하며, 공식 예 `M*A*S*H`를 하나의 연속된 UEB 표기로 제시합니다. UEB 제3.17은 더하기표를 `⠐⠖`로 제시합니다. 반면 한글 점자 제45·46항과 수학 점자 규정에도 연산 기호가 있으므로, 같은 ASCII 표면형이 상품명·등급·합성어·수식·화학식을 모두 나타낼 수 있습니다. + +현재 점역기는 다음과 같이 표면 구조만으로 확정 가능한 범위만 일반화했습니다. + +- `M*A*S*H`처럼 비어 있지 않은 로마자 구간 사이의 `*`는 UEB 제3.3.1에 따라 로마자 구간을 끝내지 않고 `⠐⠔`로 적습니다. 숫자만의 `2*3`, 한글과 붙은 별표, 독립 별표는 이 규칙에 포함하지 않습니다. +- 오른쪽 피연산자가 없이 끝나는 `A+`, `TV+`, `24K+`, `C++`와 대소문자가 식별자임을 드러내는 `Dog+Yoga`형은 일반문에서 UEB `⠐⠖`를 사용합니다. 명시적 수식 모드와 완결된 `A+B`, `AB+C`, `sin+cos`는 수식 경로에 남깁니다. +- 한글 접두부·조사·닫힌 설명 괄호는 로마자 종결 식별자의 바깥 문맥으로만 사용하며, 특정 상표나 말뭉치 입력 문자열을 직접 매핑하지 않습니다. + +이 일반화 직전·직후의 83,528문장 전체 대조에서 별표 변경은 완전 일치 2건 순증, 더하기표 변경은 63건 순증이었고 기존 완전 일치 손실은 모두 0건이었습니다. 별표가 포함된 다른 1건도 해당 `PDC*line` 구간 자체는 참조와 같아졌으나 문장 안의 별도 차이 때문에 전체 일치 수에는 포함되지 않았습니다. + +그러나 다음 표면형은 입력만으로 의미를 확정할 수 없어 강제 변환하지 않았습니다. + +- 식별자·합성어로 보이는 사례: `SF+AW`, `TECH+TALK`, `YOUTH+TEEN`, `X+U`, `UNIV+CITY` +- 수식·기간 표기로도 읽히는 사례: `A+B`, `N+1`, `T+1` +- 화학식으로 보이는 사례: `134Cs+137Cs` +- 소문자 합성어와 함수 합의 충돌: `new+retro`, `she+recovery` 대 `sin+cos` + +문의: + +1. 한글 문장 안의 로마자 구간에서 `M*A*S*H`형 별표는 UEB 제3.3.1에 따라 하나의 로마자 구간과 묵자 띄어쓰기를 유지하는 것이 맞습니까? 이때 한글 점자 제60항의 별표 앞뒤 띄어쓰기는 로마자 구간 밖의 한글 문맥에만 적용합니까? +2. 오른쪽 피연산자가 없는 `A+`, `TV+`, `C++`형은 로마자 식별자·등급의 일부로 보아 UEB `⠐⠖`를 쓰는 것이 맞습니까? +3. `TECH+TALK`와 `AB+C`처럼 대문자 글자열 내부의 `+`가 식별자 결합인지 수학 덧셈인지 표면형만으로 구분하는 공식 기준이 있습니까? 글자열 길이·대소문자·괄호 속 한글 풀이를 판정 근거로 사용할 수 있습니까? +4. `N+1`, `T+1`, `134Cs+137Cs`는 각각 일반 로마자 구간, 수학 점자, 화학식용 UEB 중 어느 규정을 우선 적용해야 합니까? +5. 표면형만으로 판정할 수 없다면 말뭉치에 일반문/식별자/수식/화학식 의미 모드를 제공해야 합니까? + ## 4. 말뭉치 구축·정정 절차에 관한 공통 질문 1. 각 참조값을 작성할 때 적용한 한국 점자 규정 및 UEB 판본은 무엇입니까? @@ -362,12 +467,16 @@ UEB에는 `AT&T`, `B&B`, `&c`처럼 앰퍼샌드와 로마자가 붙어 있는 답변을 한 번에 모두 제공하기 어렵다면 다음 순서로 우선 확인을 부탁드립니다. -1. **제34항 괄호/로마자표 순서 1,114건**: 규정 예와 반대인 3셀 참조 서명으로 재현됨 -2. **로마자+가운뎃점 경계 577건**: 완전 일치 대조 0건, 451건이 종료표 한 셀에 직접 위치 -3. **`△한글` 공백 377건**: 완전 일치 대조 0건, 입력에 없는 공백의 허용 여부 -4. **약어 발음과 UEB 약자**: 정확/불일치가 동일 철자 구조에 공존하여 발음 정보 없이는 결정 불가 -5. **`A(14)`, `HCA(...)`, `F-35`**: 일반문·식별자·수식의 의미 모드 우선순위 -6. **U+002D 마이너스와 `+(풀이)` 공백**: 코드 포인트와 의미 정규화, 동일 표면형의 상충 참조 확인 -7. **미지원 문자 155건 및 NFKC 비교 316건**: 공식 정규화와 문자 지원 정책 +1. **제34항 괄호/로마자표 순서 1,160건**: 규정 예와 반대인 3셀 참조 서명으로 재현됨 +2. **UEB 비독립 대문자 글자열 앞의 1급 점자표 70건**: 여는 괄호가 뒤따르는 글자열은 제2.6.3의 독립 조건을 충족하지 않으며, 참조의 `⠰` 한 칸만 제거하면 문장 전체가 일치함 +3. **제37항 여섯 단어의 단어 약자 59건**: 공식 예의 구간 내부 `his`, `was`와 반대이며, 약자 치환만으로 전체 참조가 일치함 +4. **UEB 대문자 구절 대신 개별 대문자표를 쓴 참조 3건**: 제8.5.2·8.5.3 적용 결과와 대문자 지시표만 다르고 나머지 문장 전체가 일치함 +5. **로마자+가운뎃점 경계 577건**: 완전 일치 대조 0건, 516건이 현재 종료표 위치에 직접 국소화됨 +6. **약어 발음과 UEB 약자**: 정확/불일치가 동일 철자 구조에 공존하여 발음 정보 없이는 결정 불가 +7. **로마자 U+2026 상충 참조 1건**: 동일 구조의 `I AM…`은 UEB 점형, `Love Is…`는 한국어 점형을 요구함 +8. **`A(14)`, `HCA(...)`, `F-35` 및 `△한글` 잔여**: 대다수는 표준 경로로 해소됐으나 일반문·식별자·수식·항목 표지의 의미 모드 확인이 필요함 +9. **로마자 구간 내부 `*`·`+`의 의미 경계**: 종결 식별자와 공식 `M*A*S*H`는 일반화했으나, 대문자 합성어·수식·화학식의 동일 표면형에는 의미 모드가 필요함 +10. **U+002D 마이너스와 `+(풀이)` 공백**: 코드 포인트와 의미 정규화, 동일 표면형의 상충 참조 확인 +11. **미지원 문자 4건(`☏` 3건, `♥` 1건)**: 공식 점형 또는 대체 텍스트 정책 확인이 필요함. 정규화 비교 잔여는 0건임 위 항목들의 공식 해석을 받기 전에는 말뭉치 참조값에 맞추기 위한 개별 입력 예외나 기대값 역산 규칙을 추가하지 않고, 재현 가능한 진단으로만 보존할 예정입니다. diff --git a/libs/braillify/examples/nikl_corpus_analyze.rs b/libs/braillify/examples/nikl_corpus_analyze.rs index 11e54e41..4277ccd6 100644 --- a/libs/braillify/examples/nikl_corpus_analyze.rs +++ b/libs/braillify/examples/nikl_corpus_analyze.rs @@ -57,6 +57,9 @@ enum Reason { Exact, ConflictingDuplicateReference, Rule34RomanIndicatorBeforeOpeningParenthesis, + RomanEllipsisUsesKoreanCellsInRomanEnclosure, + UebGrade1BeforeNonstandingOpeningParenthesis, + UebCapitalizedPassageWrittenAsSeparateCapitalWords, BrailleWhitespaceEquivalent, NfcInputEquivalent, NfkcInputEquivalent, @@ -455,6 +458,23 @@ fn classify(encoded: &EncodedCase, conflicting: &BTreeSet) -> (PrimaryCl PrimaryClass::CorpusSuspect, Reason::Rule34RomanIndicatorBeforeOpeningParenthesis, ), + _ if is_roman_ellipsis_reference_contradiction(encoded) => ( + PrimaryClass::CorpusSuspect, + Reason::RomanEllipsisUsesKoreanCellsInRomanEnclosure, + ), + _ if is_ueb_grade1_before_nonstanding_opening_parenthesis_reference_contradiction( + encoded, + ) => + { + ( + PrimaryClass::CorpusSuspect, + Reason::UebGrade1BeforeNonstandingOpeningParenthesis, + ) + } + _ if is_ueb_capitalized_passage_reference_contradiction(encoded) => ( + PrimaryClass::CorpusSuspect, + Reason::UebCapitalizedPassageWrittenAsSeparateCapitalWords, + ), Ok(actual) if roman_before_capital_order(actual) == *expected => ( PrimaryClass::ImplementationDefect, Reason::RomanIndicatorAfterCapitalIndicator, @@ -625,6 +645,7 @@ const ROMAN_UPPERCASE_AFTER_HYPHEN: &str = "uppercase_ascii_run_immediately_after_hyphen_in_roman_sequence"; const PURE_ALLCAPS_HYPHEN_MULTI_ALLCAPS: &str = "pure_allcaps_segment_before_hyphen_and_multi_allcaps_segment_after"; +const KOREAN_TO_ROMAN_HYPHEN_BOUNDARY: &str = "attached_korean_to_roman_hyphen_boundary"; const ROMAN_HYPHENATED_WORD_AFTER_KOREAN_WORD: &str = "roman_hyphenated_word_after_whitespace_following_korean_word"; const ROMAN_PARENTHETICAL_HEADWORD_AFTER_KOREAN_WORD: &str = @@ -929,6 +950,274 @@ fn is_rule_34_reference_order_contradiction(item: &EncodedCase) -> bool { .any(|range| range.start == difference) } +/// UEB 2024 section 7.3 assigns U+2026 the same three full-stop cells as the +/// print spelling `...`. When the ellipsis is attached to Roman letters and +/// immediately closes their enclosure, a reference using Korean rule-53 +/// middle-dot cells is an independently reproducible standard conflict. +fn is_roman_ellipsis_reference_contradiction(item: &EncodedCase) -> bool { + let Ok(actual) = &item.actual else { + return false; + }; + let expected = &item.located.case.unicode; + if actual == expected { + return false; + } + + let has_roman_enclosed_ellipsis = item + .located + .case + .input + .chars() + .collect::>() + .windows(3) + .any(|window| { + window[0].is_ascii_alphabetic() + && window[1] == '…' + && matches!(window[2], ')' | ']' | '}' | '”' | '’' | '」' | '』') + }); + if !has_roman_enclosed_ellipsis { + return false; + } + + let difference = first_difference_cell(expected, actual); + let expected_cells = expected.chars().collect::>(); + let actual_cells = actual.chars().collect::>(); + expected_cells.get(difference..difference + 3) == Some(&['⠠', '⠠', '⠠']) + && actual_cells.get(difference..difference + 3) == Some(&['⠲', '⠲', '⠲']) +} + +#[derive(Clone, Copy)] +struct AnalyzerCapitalizedGroup { + start: usize, + end: usize, + capital_count: usize, +} + +fn is_capitals_opening_punctuation(ch: char) -> bool { + matches!( + ch, + '\'' | '"' | '\u{2018}' | '\u{201c}' | '(' | '[' | '{' | '〈' | '《' | '「' | '『' + ) +} + +fn is_capitals_closing_quote(ch: char) -> bool { + matches!( + ch, + '\'' | '"' | '\u{2019}' | '\u{201d}' | '〉' | '》' | '」' | '』' + ) +} + +fn analyzer_capitalized_group(word: &str) -> Option { + let chars = word.chars().collect::>(); + if chars.iter().any(char::is_ascii_lowercase) { + return None; + } + + let start = chars.iter().position(char::is_ascii_uppercase)?; + if !chars[..start] + .iter() + .copied() + .all(is_capitals_opening_punctuation) + { + return None; + } + let last_capital = chars.iter().rposition(char::is_ascii_uppercase)?; + if chars[start..=last_capital] + .iter() + .any(|ch| is_korean_script(*ch)) + { + return None; + } + + let mut end = chars.len(); + for index in last_capital + 1..chars.len() { + let ch = chars[index]; + let opens_attached_korean_gloss = matches!(ch, '(' | '[' | '{') + && chars[index + 1..] + .iter() + .any(|next| is_korean_script(*next)); + if is_korean_script(ch) || is_capitals_closing_quote(ch) || opens_attached_korean_gloss { + end = index; + break; + } + } + + Some(AnalyzerCapitalizedGroup { + start, + end, + capital_count: chars.iter().filter(|ch| ch.is_ascii_uppercase()).count(), + }) +} + +/// Return the number of per-sequence capitalization cells used by the +/// non-passage spelling for each UEB 8.5.2 passage candidate in `input`. +/// A single capital uses one cell; a multi-letter capitals word uses two. +fn separate_capital_indicator_counts_for_passages(input: &str) -> Vec { + let words = input.split_whitespace().collect::>(); + let groups = words + .iter() + .map(|word| analyzer_capitalized_group(word)) + .collect::>(); + let word_lengths = words + .iter() + .map(|word| word.chars().count()) + .collect::>(); + let mut counts = Vec::new(); + let mut index = 0usize; + + while index + 2 < words.len() { + let Some(current) = groups[index] else { + index += 1; + continue; + }; + let Some(first) = groups[index + 1] else { + index += 1; + continue; + }; + let Some(second) = groups[index + 2] else { + index += 1; + continue; + }; + if current.end != word_lengths[index] + || first.start != 0 + || first.end != word_lengths[index + 1] + || second.start != 0 + { + index += 1; + continue; + } + + let mut end = index + 3; + while end < words.len() && groups[end].is_some_and(|group| group.start == 0) { + end += 1; + } + let separate_cells = groups[index..end] + .iter() + .flatten() + .map(|group| if group.capital_count == 1 { 1 } else { 2 }) + .sum(); + counts.push(separate_cells); + index = end; + } + + counts +} + +fn matches_after_removing_capital_cells( + expected: &[char], + actual: &[char], + expected_index: usize, + actual_index: usize, + removed: usize, + required_removed: usize, + failed: &mut BTreeSet<(usize, usize, usize)>, +) -> bool { + let state = (expected_index, actual_index, removed); + if failed.contains(&state) { + return false; + } + if expected_index == expected.len() && actual_index == actual.len() { + return removed == required_removed; + } + + if expected.get(expected_index) == actual.get(actual_index) + && matches_after_removing_capital_cells( + expected, + actual, + expected_index + 1, + actual_index + 1, + removed, + required_removed, + failed, + ) + { + return true; + } + if removed < required_removed + && expected.get(expected_index) == Some(&'⠠') + && matches_after_removing_capital_cells( + expected, + actual, + expected_index + 1, + actual_index, + removed + 1, + required_removed, + failed, + ) + { + return true; + } + + failed.insert(state); + false +} + +/// UEB 2024 8.5.2 requires one capitals-passage indicator for three or more +/// capitalized symbols-sequences, and 8.5.3 places its terminator immediately +/// after the final affected sequence. A reference is classified only when the +/// complete sentence becomes identical by replacing that exact five-cell +/// passage pair with the structurally required one-/two-cell indicators for +/// each sequence. Unrelated differences therefore remain pending review. +fn is_ueb_capitalized_passage_reference_contradiction(item: &EncodedCase) -> bool { + let Ok(actual) = &item.actual else { + return false; + }; + let expected = &item.located.case.unicode; + if actual == expected { + return false; + } + + let indicator_counts = separate_capital_indicator_counts_for_passages(&item.located.case.input); + if indicator_counts.is_empty() { + return false; + } + let actual = actual.chars().collect::>(); + let expected = expected.chars().collect::>(); + + for start in 0..actual.len().saturating_sub(2) { + if actual.get(start..start + 3) != Some(&['⠠', '⠠', '⠠']) + || expected.get(..start) != actual.get(..start) + { + continue; + } + for terminator in start + 3..actual.len().saturating_sub(1) { + if actual.get(terminator..terminator + 2) != Some(&['⠠', '⠄']) { + continue; + } + let actual_suffix_start = terminator + 2; + let suffix_len = actual.len() - actual_suffix_start; + let Some(expected_segment_end) = expected.len().checked_sub(suffix_len) else { + continue; + }; + if expected_segment_end < start + || expected.get(expected_segment_end..) != actual.get(actual_suffix_start..) + { + continue; + } + + let actual_content = &actual[start + 3..terminator]; + let expected_segment = &expected[start..expected_segment_end]; + for required_removed in &indicator_counts { + if expected_segment.len() != actual_content.len() + required_removed { + continue; + } + if matches_after_removing_capital_cells( + expected_segment, + actual_content, + 0, + 0, + 0, + *required_removed, + &mut BTreeSet::new(), + ) { + return true; + } + } + } + } + false +} + /// Finds maximal all-caps ASCII runs containing the adjacent letters `OU`. /// /// This is an input gate for a pronunciation-sensitive UEB diagnostic, not a @@ -1454,6 +1743,110 @@ fn allcaps_shortform_prefix_spans(input: &str) -> Vec { spans } +/// Narrow the shortform-collision audit to a letters-sequence followed +/// immediately by an opening round, square, or curly parenthesis. UEB 2.6.2 +/// permits these symbols before a standing-alone sequence, but the exhaustive +/// following-symbol list in 2.6.3 does not permit them after one. Consequently +/// 5.7.2/10.9.7 cannot introduce grade 1 merely because the capital sequence, +/// considered in isolation, resembles a shortform. +fn allcaps_shortform_before_nonstanding_opening_group_spans(input: &str) -> Vec { + allcaps_shortform_prefix_spans(input) + .into_iter() + .filter(|span| { + input[span.end_byte..] + .chars() + .next() + .is_some_and(|ch| matches!(ch, '(' | '[' | '{')) + }) + .collect() +} + +fn nonstanding_shortform_capitals_ranges( + input: &str, + actual: &str, + spans: &[InputSpan], +) -> Vec> { + let mut ranges = BTreeSet::new(); + for span in spans { + let mut letter_cells = String::new(); + let mut valid = true; + for letter in input[span.start_byte..span.end_byte].chars() { + let Ok(encoded) = + braillify::encode_to_unicode(&letter.to_ascii_lowercase().to_string()) + else { + valid = false; + break; + }; + if encoded.chars().count() != 1 { + valid = false; + break; + } + letter_cells.push_str(&encoded); + } + if !valid { + continue; + } + let signature = format!("⠠⠠{letter_cells}"); + for (start_byte, _) in actual.match_indices(&signature) { + let start = actual[..start_byte].chars().count(); + ranges.insert((start, start + signature.chars().count())); + } + } + ranges.into_iter().map(|(start, end)| start..end).collect() +} + +/// UEB 2.6.1-2.6.3 makes a letters-sequence followed immediately by an +/// opening grouping sign not standing alone. This classifier accepts a corpus +/// contradiction only when every difference in the complete sentence is an +/// extra reference-side grade-1 cell immediately before the current capitals +/// indicator of one of those structurally detected sequences. +fn is_ueb_grade1_before_nonstanding_opening_parenthesis_reference_contradiction( + item: &EncodedCase, +) -> bool { + let Ok(actual) = &item.actual else { + return false; + }; + let expected = &item.located.case.unicode; + if actual == expected { + return false; + } + + let spans = allcaps_shortform_before_nonstanding_opening_group_spans(&item.located.case.input); + if spans.is_empty() { + return false; + } + + let expected_cells = expected.chars().collect::>(); + let actual_cells = actual.chars().collect::>(); + let mut expected_index = 0usize; + let mut actual_index = 0usize; + let mut removed_at_actual = Vec::new(); + while expected_index < expected_cells.len() && actual_index < actual_cells.len() { + if expected_cells[expected_index] == actual_cells[actual_index] { + expected_index += 1; + actual_index += 1; + continue; + } + if expected_cells[expected_index] == '⠰' && actual_cells[actual_index] == '⠠' { + removed_at_actual.push(actual_index); + expected_index += 1; + continue; + } + return false; + } + if expected_index != expected_cells.len() + || actual_index != actual_cells.len() + || removed_at_actual.is_empty() + { + return false; + } + + let ranges = nonstanding_shortform_capitals_ranges(&item.located.case.input, actual, &spans); + removed_at_actual + .iter() + .all(|position| ranges.iter().any(|range| range.contains(position))) +} + /// Finds maximal ASCII alphanumeric identifiers containing an immediate /// digit-to-uppercase transition (`O4O`, `Li2S`, `V2X`). The numeric indicator /// itself sets grade-1 mode under UEB 5.6.1, and 5.6.2 does not terminate that @@ -1567,6 +1960,99 @@ fn pure_allcaps_hyphen_multi_allcaps_spans(input: &str) -> Vec { .collect() } +/// Mirrors the production grammar for an attached Korean-to-Roman hyphen +/// boundary (`하쿠토-R`, `기장-KBO`) without consulting corpus braille. +/// +/// The encoder selectively expands U+2160-U+217F Roman-numeral presentation +/// characters before token routing, so this audit applies the same expansion +/// (`천궁-Ⅱ` -> `천궁-II`). A capital initial or a multi-letter identifier +/// distinguishes prose labels from a single lowercase algebra variable, while +/// an explicit operator keeps the token in the mathematics cohort. +fn is_korean_to_roman_hyphen_boundary_word(word: &str) -> bool { + let mut normalized = String::with_capacity(word.len()); + for ch in word.chars() { + if (0x2160..=0x217f).contains(&(ch as u32)) { + normalized.extend(std::iter::once(ch).nfkc()); + } else { + normalized.push(ch); + } + } + let chars = normalized.chars().collect::>(); + + chars.windows(3).enumerate().any(|(index, window)| { + if !is_korean_script(window[0]) || window[1] != '-' || !window[2].is_ascii_alphabetic() { + return false; + } + + let roman_tail = &chars[index + 2..]; + let identifier_len = roman_tail + .iter() + .take_while(|ch| ch.is_ascii_alphanumeric()) + .count(); + let letter_count = roman_tail[..identifier_len] + .iter() + .filter(|ch| ch.is_ascii_alphabetic()) + .count(); + let identifier_is_unambiguous = window[2].is_ascii_uppercase() || letter_count >= 2; + let has_explicit_math_operator = roman_tail.iter().any(|ch| { + matches!( + *ch, + '+' | '−' + | '×' + | '÷' + | '=' + | '<' + | '>' + | '≤' + | '≥' + | '≠' + | '≈' + | '^' + | '_' + | '/' + | '*' + | '|' + | '∈' + | '∉' + | '⊂' + | '⊃' + | '∧' + | '∨' + ) + }); + + identifier_is_unambiguous && !has_explicit_math_operator + }) +} + +fn korean_to_roman_hyphen_boundary_spans(input: &str) -> Vec { + let mut spans = Vec::new(); + let mut token_start = None; + for (index, ch) in input.char_indices() { + if ch.is_whitespace() { + if let Some(start_byte) = token_start.take() + && is_korean_to_roman_hyphen_boundary_word(&input[start_byte..index]) + { + spans.push(InputSpan { + start_byte, + end_byte: index, + }); + } + } else if token_start.is_none() { + token_start = Some(index); + } + } + if let Some(start_byte) = token_start + && is_korean_to_roman_hyphen_boundary_word(&input[start_byte..]) + { + spans.push(InputSpan { + start_byte, + end_byte: input.len(), + }); + } + spans +} + fn preceding_whitespace_word_contains_korean(input: &str, start_byte: usize) -> bool { let before = &input[..start_byte]; if !before.chars().next_back().is_some_and(char::is_whitespace) { @@ -3842,6 +4328,10 @@ fn analyze( PURE_ALLCAPS_HYPHEN_MULTI_ALLCAPS.to_string(), PendingRuleReviewClusterStats::default(), ), + ( + KOREAN_TO_ROMAN_HYPHEN_BOUNDARY.to_string(), + PendingRuleReviewClusterStats::default(), + ), ( ROMAN_HYPHENATED_WORD_AFTER_KOREAN_WORD.to_string(), PendingRuleReviewClusterStats::default(), @@ -4219,6 +4709,12 @@ fn analyze( ), true, ), + ( + KOREAN_TO_ROMAN_HYPHEN_BOUNDARY, + !korean_to_roman_hyphen_boundary_spans(&item.located.case.input).is_empty(), + None, + false, + ), ( ROMAN_HYPHENATED_WORD_AFTER_KOREAN_WORD, !roman_hyphenated_word_after_korean_word_spans(&item.located.case.input).is_empty(), @@ -4567,7 +5063,8 @@ fn markdown(report: &AnalysisReport) -> String { implementation obligation or a reproducible comparison/corpus issue is established. \ `pending_rule_review` contains foreign-text, number, punctuation, and Korean candidates \ that have not yet been resolved against the PDF. `corpus_suspect` is reserved for \ - independently detectable contradictions such as one input having multiple references. \ + independently detectable contradictions: conflicting duplicate references or a localized \ + reference-cell signature that contradicts an explicit PDF/UEB rule. \ `comparison_method` requires equality after a named normalization.\n\n", ); text.push_str("| Primary class | Count |\n|---|---:|\n"); @@ -5063,6 +5560,33 @@ fn markdown(report: &AnalysisReport) -> String { regression. Digit-hyphen forms such as `F-35` remain excluded, and the \ complete-shortform guard still legitimately precedes `CD` in `CD-ROM`.\n\n", ); + let korean_to_roman_hyphen = report + .pending_rule_review_clusters + .get(KOREAN_TO_ROMAN_HYPHEN_BOUNDARY) + .expect("registered Korean-to-Roman hyphen cohort must exist"); + let exact_with_separator = format!("{},{:03}", report.exact / 1_000, report.exact % 1_000); + text.push_str("### Attached Korean-to-Roman hyphen boundary\n\n"); + text.push_str(&format!( + "Korean rule 29 opens a Roman section for Roman text in a Korean sentence, rule 33 \ + keeps the hyphen as punctuation at the Korean/Roman boundary, and rules 35-36 own \ + adjacent alphanumerics and Roman numerals. The retained production gate therefore \ + routes an immediately attached capital-led or multi-letter Roman identifier as prose \ + (`하쿠토-R`, `기장-KBO`, `온다-life`), but leaves a single lowercase variable and any \ + token with an explicit mathematical operator on the mathematics route (`값-x`, \ + `값-X+1`). The analyzer applies the encoder's selective U+2160-U+217F compatibility \ + expansion before testing the word grammar, so `천궁-Ⅱ` is audited as the equivalent \ + `천궁-II` boundary.\n\n\ + Before this gate, the deterministic cohort contained 105 candidates / 62 exact / 43 \ + mismatch. It now contains {} candidates / {} exact / {} mismatch. The complete \ + corpus exact-ID audit moved from 75,704 to {} (+22), and every new exact ID belongs \ + to this cohort; no formerly exact ID was lost. Cohort membership is input-only and \ + never changes a primary class, so the remaining non-exact members retain their \ + independent review causes.\n\n", + korean_to_roman_hyphen.candidates, + korean_to_roman_hyphen.exact, + korean_to_roman_hyphen.mismatch, + exact_with_separator + )); text.push_str("\n## Roman-entry residual cohorts after grade-1 localization\n\n"); text.push_str( "These three cohorts split the former dominant `⠴ -> blank` residual by the input \ @@ -6584,9 +7108,9 @@ fn markdown(report: &AnalysisReport) -> String { .pending_rule_review_clusters .get(KOREAN_PREFIXED_CLOSED_ROMAN_ANNOTATION) { - let corpus_suspect = stats - .mismatch_primary_classes - .get("corpus_suspect") + let rule_34_reference_conflicts = report + .reasons + .get("rule34_roman_indicator_before_opening_parenthesis") .copied() .unwrap_or(0); let opposite_order = stats @@ -6601,7 +7125,8 @@ fn markdown(report: &AnalysisReport) -> String { {opposite_order} localized first-cell transitions have the reference/current order \ `⠴` versus `⠦`. After requiring the complete reference `⠴⠐⠣` versus current/PDF \ `⠦⠄⠴` three-cell signature and preserving higher-priority comparison \ - classifications, {corpus_suspect} are classified as `corpus_suspect`; mere \ + classifications, {rule_34_reference_conflicts} are classified with the dedicated \ + rule-34 contradiction reason; mere \ coexistence with a Korean-prefixed Roman annotation does not change a primary \ class.\n", stats.candidates, @@ -6611,6 +7136,43 @@ fn markdown(report: &AnalysisReport) -> String { stats.output_signature_mismatches_evaluated )); } + text.push_str( + "\nNIKL Q&A #325 clarifies that the six lower wordsigns named by Korean Rule 37 \ + remain expanded when Roman words are discussed in Korean context, while a recognizable \ + English title or phrase follows UEB 10.5 and uses a lower wordsign only when it stands \ + alone and satisfies the lower-sign adjacency restriction. The encoder distinguishes \ + those contexts from input structure, capitalization, and enclosure boundaries; analyzer \ + references and competitor fields do not affect routing.\n", + ); + let nonstanding_parenthesis_grade1_reference_conflicts = report + .reasons + .get("ueb_grade1_before_nonstanding_opening_parenthesis") + .copied() + .unwrap_or(0); + text.push_str(&format!( + "\nCurrent UEB non-standing parenthesis/grade-1 contradiction measurement: \ + {nonstanding_parenthesis_grade1_reference_conflicts} cases contain an all-capitals \ + letters-sequence that resembles a shortform but is followed immediately by an opening \ + round, square, or curly parenthesis. UEB 2.6.2 permits those opening symbols before a \ + standing-alone sequence, while 2.6.3 does not permit them after one. The classifier \ + requires complete-sentence equality after removing only reference-side grade-1 cells \ + immediately before the localized capitals indicators; all other differences remain \ + pending review.\n" + )); + let capitalized_passage_reference_conflicts = report + .reasons + .get("ueb_capitalized_passage_written_as_separate_capital_words") + .copied() + .unwrap_or(0); + text.push_str(&format!( + "\nCurrent UEB capitalized-passage contradiction measurement: \ + {capitalized_passage_reference_conflicts} cases contain at least three consecutive \ + capitalized symbols-sequences and differ from the current UEB 8.5.2-8.5.3 path only \ + by replacing the one passage indicator/terminator pair with separate one- or two-cell \ + capitalization indicators. The classifier requires equality of the complete sentence \ + after deleting exactly those structurally counted separate indicators; unrelated \ + Roman, punctuation, contraction, or spacing differences remain pending review.\n" + )); if let Some(stats) = report .pending_rule_review_clusters .get(ALLCAPS_ROMAN_MIDDLE_DOT_RUNS) @@ -7850,7 +8412,8 @@ mod tests { #[case::whole_shortform("가(WD) 나", vec!["WD"])] #[case::longer_prefixes("PDS LLM GDP", vec!["PDS", "LLM", "GDP"])] #[case::ueb_examples("ALT NEC LLC", vec!["ALT", "NEC", "LLC"])] - #[case::noncolliding_controls("US KBS MCH", vec![])] + #[case::shortform_much("MCH", vec!["MCH"])] + #[case::noncolliding_controls("US KBS", vec![])] #[case::alphanumeric_excluded("O4O Li2S V2X", vec![])] fn detects_allcaps_shortform_prefix_collisions( #[case] input: &str, @@ -7910,6 +8473,26 @@ mod tests { assert_eq!(actual, expected); } + #[rstest::rstest] + #[case::single_capital("오는 하쿠토-R 미션", vec!["하쿠토-R"])] + #[case::multi_capital("기장-KBO 야구센터", vec!["기장-KBO"])] + #[case::uppercase_sequence("한-UAE 협력", vec!["한-UAE"])] + #[case::normalized_roman_numeral("천궁-Ⅱ 미사일", vec!["천궁-Ⅱ"])] + #[case::lowercase_word("봄은 온다-life goes on", vec!["온다-life"])] + #[case::single_lowercase_math_control("값-x 계산", vec![])] + #[case::explicit_operator_control("값-X+1 계산", vec![])] + #[case::digit_control("한-3 단계", vec![])] + fn detects_attached_korean_to_roman_hyphen_boundary( + #[case] input: &str, + #[case] expected: Vec<&str>, + ) { + let actual = korean_to_roman_hyphen_boundary_spans(input) + .into_iter() + .map(|span| &input[span.start_byte..span.end_byte]) + .collect::>(); + assert_eq!(actual, expected); + } + #[rstest::rstest] #[case::after_korean_word("연구단, A-STAR 방문", vec!["A-STAR"])] #[case::after_ascii_word("research A-STAR 방문", vec![])] @@ -8107,7 +8690,7 @@ mod tests { #[rstest::rstest] #[case::short_digit_exact_control("웹3(Web3)", '⠦')] #[case::after_whitespace("전시회 (Moulding Expo)", '⠴')] - #[case::after_quote("선언’(Washington Declaration)", '⠴')] + #[case::after_quote("선언’(Washington Declaration)", '⠦')] fn locates_nonletter_parenthetical_opening_in_current_output( #[case] input: &str, #[case] expected_opening: char, @@ -8131,14 +8714,28 @@ mod tests { #[case::attached_middle_dot_suffix( "10~11일에는 지역 주민들과 함께 하는 전야제를 포함해 아주대 50년사 출판 기념보고회, 인공지능(AI)·6G 융합 콜로키움 시리즈가 열린다." )] - fn localizes_current_two_blank_boundary_before_roman_parenthetical(#[case] input: &str) { + fn localizes_current_rule34_boundary_before_roman_parenthetical(#[case] input: &str) { let spans = roman_parenthetical_after_nonletter_boundary_spans(input); let actual = braillify::encode_to_unicode(input).expect("parenthetical probe must encode"); let ranges = current_engine_parenthetical_leading_boundary_ranges(input, &actual, &spans); assert!(!spans.is_empty()); + assert!(!ranges.is_empty()); assert!(ranges.iter().any(|range| { - actual.chars().skip(range.start).take(3).collect::() == "⠀⠀⠦" + actual + .chars() + .skip(range.start) + .take(range.len()) + .collect::() + .contains("⠦⠄⠴") + })); + assert!(!ranges.iter().any(|range| { + actual + .chars() + .skip(range.start) + .take(range.len()) + .collect::() + .contains("⠀⠀⠦") })); } @@ -8309,6 +8906,189 @@ mod tests { ); } + #[test] + fn classifies_korean_cells_for_an_enclosed_roman_ellipsis_as_corpus_suspect() { + let input = "제목(Love Is…)까지"; + let actual = braillify::encode_to_unicode(input).expect("Roman ellipsis probe must encode"); + assert!(actual.contains("⠲⠲⠲")); + let expected = actual.replacen("⠲⠲⠲", "⠠⠠⠠", 1); + let encoded = EncodedCase { + located: LocatedCase { + shard: "synthetic.json".to_string(), + index: 1, + case: CorpusCase { + input: input.to_string(), + unicode: expected, + }, + }, + actual: Ok(actual), + nfc_actual: None, + nfkc_actual: None, + singleton_unsupported_characters: Vec::new(), + }; + + assert!(is_roman_ellipsis_reference_contradiction(&encoded)); + assert_eq!( + classify(&encoded, &BTreeSet::new()), + ( + PrimaryClass::CorpusSuspect, + Reason::RomanEllipsisUsesKoreanCellsInRomanEnclosure + ) + ); + } + + #[rstest::rstest] + #[case::round_shortform("가 PDS(설명) 나", false, true)] + #[case::square_shortform("가 PDS[설명] 나", false, true)] + #[case::curly_shortform("가 PDS{설명} 나", false, true)] + #[case::standing_sequence("가 PDS 설명", false, false)] + #[case::plain_initialism("가 KBS(설명) 나", false, false)] + #[case::unrelated_difference("가 PDS(설명) 나", true, false)] + fn recognizes_only_extra_grade1_before_nonstanding_opening_parenthesis( + #[case] input: &str, + #[case] add_unrelated_difference: bool, + #[case] expected_result: bool, + ) { + let actual = braillify::encode_to_unicode(input).expect("grade-1 probe must encode"); + let capitals = actual + .find("⠠⠠") + .expect("probe must contain a capitals-word indicator"); + let mut expected = format!("{}⠰{}", &actual[..capitals], &actual[capitals..]); + if add_unrelated_difference { + expected.push('⠁'); + } + let encoded = EncodedCase { + located: LocatedCase { + shard: "synthetic.json".to_string(), + index: 1, + case: CorpusCase { + input: input.to_string(), + unicode: expected, + }, + }, + actual: Ok(actual), + nfc_actual: None, + nfkc_actual: None, + singleton_unsupported_characters: Vec::new(), + }; + + assert_eq!( + is_ueb_grade1_before_nonstanding_opening_parenthesis_reference_contradiction(&encoded), + expected_result + ); + } + + #[test] + fn classifies_extra_grade1_before_nonstanding_opening_parenthesis_as_corpus_suspect() { + let input = "가 PDS(설명) 나"; + let actual = braillify::encode_to_unicode(input).expect("grade-1 probe must encode"); + let capitals = actual + .find("⠠⠠") + .expect("probe must contain a capitals-word indicator"); + let expected = format!("{}⠰{}", &actual[..capitals], &actual[capitals..]); + let encoded = EncodedCase { + located: LocatedCase { + shard: "synthetic.json".to_string(), + index: 1, + case: CorpusCase { + input: input.to_string(), + unicode: expected, + }, + }, + actual: Ok(actual), + nfc_actual: None, + nfkc_actual: None, + singleton_unsupported_characters: Vec::new(), + }; + + assert_eq!( + classify(&encoded, &BTreeSet::new()), + ( + PrimaryClass::CorpusSuspect, + Reason::UebGrade1BeforeNonstandingOpeningParenthesis + ) + ); + } + + #[rstest::rstest] + #[case::caution_wet_paint( + "CAUTION: WET PAINT!", + "⠠⠠⠠⠉⠁⠥⠞⠊⠕⠝⠒⠀⠺⠑⠞⠀⠏⠁⠊⠝⠞⠖⠠⠄", + "⠠⠠⠉⠁⠥⠞⠊⠕⠝⠒⠀⠠⠠⠺⠑⠞⠀⠠⠠⠏⠁⠊⠝⠞⠖", + true + )] + #[case::bbc_africa_news( + "THE BBC AFRICA NEWS", + "⠠⠠⠠⠞⠓⠑⠀⠃⠃⠉⠀⠁⠋⠗⠊⠉⠁⠀⠝⠑⠺⠎⠠⠄", + "⠠⠠⠞⠓⠑⠀⠠⠠⠃⠃⠉⠀⠠⠠⠁⠋⠗⠊⠉⠁⠀⠠⠠⠝⠑⠺⠎", + true + )] + #[case::self_made_man( + "A SELF-MADE MAN", + "⠠⠠⠠⠁⠀⠎⠑⠇⠋⠤⠍⠁⠙⠑⠀⠍⠁⠝⠠⠄", + "⠠⠁⠀⠠⠠⠎⠑⠇⠋⠤⠍⠁⠙⠑⠀⠠⠠⠍⠁⠝", + true + )] + #[case::only_two_sequences("WET PAINT!", "⠠⠠⠠⠺⠑⠞⠀⠏⠁⠊⠝⠞⠖⠠⠄", "⠠⠠⠺⠑⠞⠀⠠⠠⠏⠁⠊⠝⠞⠖", false)] + #[case::unrelated_content_difference( + "CAUTION: WET PAINT!", + "⠠⠠⠠⠉⠁⠥⠞⠊⠕⠝⠒⠀⠺⠑⠞⠀⠏⠁⠊⠝⠞⠖⠠⠄", + "⠠⠠⠉⠁⠥⠞⠊⠕⠝⠒⠀⠠⠠⠺⠑⠞⠀⠠⠠⠏⠁⠊⠝⠭⠖", + false + )] + fn recognizes_only_complete_ueb_capitals_passage_marker_substitutions( + #[case] input: &str, + #[case] actual: &str, + #[case] expected: &str, + #[case] expected_result: bool, + ) { + let encoded = EncodedCase { + located: LocatedCase { + shard: "synthetic.json".to_string(), + index: 1, + case: CorpusCase { + input: input.to_string(), + unicode: expected.to_string(), + }, + }, + actual: Ok(actual.to_string()), + nfc_actual: None, + nfkc_actual: None, + singleton_unsupported_characters: Vec::new(), + }; + + assert_eq!( + is_ueb_capitalized_passage_reference_contradiction(&encoded), + expected_result + ); + } + + #[test] + fn classifies_separate_capital_words_for_a_ueb_passage_as_corpus_suspect() { + let encoded = EncodedCase { + located: LocatedCase { + shard: "synthetic.json".to_string(), + index: 1, + case: CorpusCase { + input: "A SELF-MADE MAN".to_string(), + unicode: "⠠⠁⠀⠠⠠⠎⠑⠇⠋⠤⠍⠁⠙⠑⠀⠠⠠⠍⠁⠝".to_string(), + }, + }, + actual: Ok("⠠⠠⠠⠁⠀⠎⠑⠇⠋⠤⠍⠁⠙⠑⠀⠍⠁⠝⠠⠄".to_string()), + nfc_actual: None, + nfkc_actual: None, + singleton_unsupported_characters: Vec::new(), + }; + + assert_eq!( + classify(&encoded, &BTreeSet::new()), + ( + PrimaryClass::CorpusSuspect, + Reason::UebCapitalizedPassageWrittenAsSeparateCapitalWords + ) + ); + } + #[rstest::rstest] #[case::embedded_in_korean("AI·SW교육", true)] #[case::standalone("DRX·SNS", true)] diff --git a/libs/braillify/src/encoder.rs b/libs/braillify/src/encoder.rs index a14e275a..6678df48 100644 --- a/libs/braillify/src/encoder.rs +++ b/libs/braillify/src/encoder.rs @@ -109,7 +109,13 @@ impl Encoder { token_engine.register(Box::new( rules::token_rules::historical_gloss_spacing::HistoricalGlossSpacingRule, )); + token_engine.register(Box::new( + rules::token_rules::normalize::NormalizeAsciiAngleBrackets, + )); token_engine.register(Box::new(rules::token_rules::normalize::NormalizeEllipsis)); + token_engine.register(Box::new( + rules::korean::rule_72::Rule72AttachedMarkerTokenRule, + )); // PDF 한국어 제33항 — 학술 인용 형식 year-suffix token (1998a,, 1998b;). token_engine.register(Box::new( rules::token_rules::rule_33_citation::Rule33CitationYearSuffixRule, diff --git a/libs/braillify/src/english_logic.rs b/libs/braillify/src/english_logic.rs index 5502a2d5..1056fa9a 100644 --- a/libs/braillify/src/english_logic.rs +++ b/libs/braillify/src/english_logic.rs @@ -78,10 +78,38 @@ fn is_ascii_letter_or_digit(ch: Option) -> bool { ch.is_some_and(|c| c.is_ascii_alphanumeric()) } +/// Whether a following print item starts with a number and then crosses +/// directly into Korean text, without an intervening Roman unit/identifier. +/// +/// Korean rule 33 changes a comma to the Korean punctuation sign only at an +/// actual Roman-to-Korean boundary. Looking for Korean anywhere later in the +/// whitespace-delimited item is too broad: in `173cm, 68kg의`, the comma is +/// followed first by the Roman measurement `68kg`, and the particle `의` is a +/// later boundary. Numeric grouping/decimal punctuation remains part of the +/// numeric prefix (`1,000년`, `3.5년`). +fn begins_numeric_then_korean(chars: impl Iterator) -> bool { + let mut chars = chars.peekable(); + if !chars.peek().is_some_and(char::is_ascii_digit) { + return false; + } + + while let Some(ch) = chars.next() { + if ch.is_ascii_digit() + || (matches!(ch, ',' | '.') && chars.peek().is_some_and(char::is_ascii_digit)) + { + continue; + } + return utils::is_korean_char(ch); + } + + false +} + /// Returns whether `index` is an ampersand inside a complete sequence of -/// non-empty ASCII-letter segments joined by `&`. Alphanumeric characters -/// outside the maximal sequence are excluded because UEB 3.1.1 directly -/// establishes only complete attached Roman forms such as AT&T and B&B. +/// non-empty ASCII-letter segments joined by `&`. Korean rule 35 allows the +/// resulting Roman text to continue directly into digits and later Roman +/// letters, as in the official `MP4 Player` example. A leading digit remains +/// outside this predicate because the cited sequence begins with Roman text. pub(crate) fn is_attached_ascii_roman_ampersand(word_chars: &[char], index: usize) -> bool { if word_chars.get(index) != Some(&'&') || index == 0 @@ -99,29 +127,64 @@ pub(crate) fn is_attached_ascii_roman_ampersand(word_chars: &[char], index: usiz } let mut end = index + 1; while end < word_chars.len() - && (word_chars[end].is_ascii_alphabetic() || word_chars[end] == '&') + && (word_chars[end].is_ascii_alphanumeric() || word_chars[end] == '&') { end += 1; } - word_chars[start..end] - .first() - .is_some_and(|ch| ch.is_ascii_alphabetic()) - && word_chars[start..end] - .last() - .is_some_and(|ch| ch.is_ascii_alphabetic()) - && !word_chars[start..end] - .windows(2) - .any(|pair| pair == ['&', '&']) + let segment = &word_chars[start..end]; + segment.first().is_some_and(|ch| ch.is_ascii_alphabetic()) + && segment.last().is_some_and(|ch| ch.is_ascii_alphanumeric()) + && segment.iter().enumerate().all(|(offset, ch)| { + *ch != '&' + || (offset > 0 + && offset + 1 < segment.len() + && segment[offset - 1].is_ascii_alphabetic() + && segment[offset + 1].is_ascii_alphabetic()) + }) + && (start == 0 || !word_chars[start - 1].is_ascii_alphanumeric()) + && (end == word_chars.len() || !word_chars[end].is_ascii_alphanumeric()) +} + +/// Returns whether `index` is an asterisk inside a complete sequence of +/// non-empty ASCII-alphanumeric segments, each containing a Roman letter. +/// UEB 3.3.1 says that the asterisk follows its UEB form regardless of meaning +/// and gives `M*A*S*H` as an attached Roman example. Korean rules 32 and 35 +/// keep the resulting UEB text, including directly adjacent digits, in one +/// Roman section. Requiring a Roman letter in every segment keeps numeric +/// `2*3`, detached asterisks, Korean text, and empty segments out of this rule. +pub(crate) fn is_attached_ascii_roman_asterisk(word_chars: &[char], index: usize) -> bool { + if word_chars.get(index) != Some(&'*') || index == 0 || index + 1 >= word_chars.len() { + return false; + } + + let mut start = index; + while start > 0 + && (word_chars[start - 1].is_ascii_alphanumeric() || word_chars[start - 1] == '*') + { + start -= 1; + } + let mut end = index + 1; + while end < word_chars.len() + && (word_chars[end].is_ascii_alphanumeric() || word_chars[end] == '*') + { + end += 1; + } + + let segment = &word_chars[start..end]; + segment + .split(|ch| *ch == '*') + .all(|part| !part.is_empty() && part.iter().any(|ch| ch.is_ascii_alphabetic())) + && segment.first().is_some_and(|ch| ch.is_ascii_alphabetic()) && (start == 0 || !word_chars[start - 1].is_ascii_alphanumeric()) && (end == word_chars.len() || !word_chars[end].is_ascii_alphanumeric()) } /// Returns whether `index` is the one-sided ampersand at the beginning of a /// complete attached ASCII-letter segment. UEB 3.1.1 prints `&c` without a -/// boundary between the ampersand and `c`. A left ASCII alphanumeric, another -/// ampersand, or a trailing digit is excluded so the existing two-sided -/// `A&B` rule and Roman/number identifiers retain their own ownership. +/// boundary between the ampersand and `c`; Korean rule 35 then permits an +/// attached numeric/Roman continuation. A left ASCII alphanumeric or another +/// ampersand is excluded so the existing two-sided `A&B` rule retains ownership. pub(crate) fn is_ampersand_before_attached_ascii_roman_segment( word_chars: &[char], index: usize, @@ -141,7 +204,7 @@ pub(crate) fn is_ampersand_before_attached_ascii_roman_segment( let mut end = index + 1; while word_chars .get(end) - .is_some_and(|ch| ch.is_ascii_alphabetic()) + .is_some_and(|ch| ch.is_ascii_alphanumeric()) { end += 1; } @@ -213,6 +276,41 @@ pub(crate) fn next_ascii_letter_or_digit( false } +/// Korean rule 46's `BMI(체질량 지수)` example assigns the attached, closed +/// parenthesis to Korean punctuation even though it follows a Roman run. Scan +/// the complete balanced enclosure because its Korean content can begin after +/// a number, a Roman expansion, or a print-space boundary. Pure Roman/number +/// enclosures remain eligible for rule 32's UEB punctuation. +fn closed_parenthesis_contains_korean( + word_chars: &[char], + index: usize, + remaining_words: &[&str], +) -> bool { + let mut depth = 1usize; + let mut contains_korean = false; + let tail = word_chars + .iter() + .skip(index + 1) + .copied() + .chain(remaining_words.iter().flat_map(|word| word.chars())); + + for ch in tail { + match ch { + '(' => depth += 1, + ')' => { + depth -= 1; + if depth == 0 { + return contains_korean; + } + } + _ if utils::is_korean_char(ch) => contains_korean = true, + _ => {} + } + } + + false +} + #[allow(clippy::too_many_arguments)] /// 괄호/쉼표가 영어 점자로 이어져야 하는지 판정한다. /// - '(' 는 뒤에 올 문자가 ASCII 영숫자여야 하고, 앞은 한글이 아니어야 한다. @@ -221,6 +319,7 @@ pub(crate) fn next_ascii_letter_or_digit( pub(crate) fn should_render_symbol_as_english( english_indicator: bool, is_english: bool, + is_english_majority: bool, parenthesis_stack: &[bool], symbol: char, word_chars: &[char], @@ -242,14 +341,31 @@ pub(crate) fn should_render_symbol_as_english( remaining_words.first().and_then(|w| w.chars().next()) }; + // A non-English closing enclosure is a hard Roman-section boundary. The + // look-behind helpers deliberately skip punctuation for attached UEB runs, + // but must not reach through that boundary and pull a following version or + // identifier mark (`(XBB).1.5`) back into the closed Roman section. + if !is_english && prev_char.is_some_and(|ch| matches!(ch, ')' | ']' | '}')) { + return false; + } + match symbol { - '(' => is_ascii_letter_or_digit(next_char) && !prev_char.is_some_and(utils::is_korean_char), + '(' => { + (is_english_majority + || !closed_parenthesis_contains_korean(word_chars, index, remaining_words)) + && is_ascii_letter_or_digit(next_char) + && !prev_char.is_some_and(utils::is_korean_char) + } ')' => parenthesis_stack.last().copied().unwrap_or(false), // UEB 3.1.1 prints an ampersand without ending and restarting // grade-1 mode in attached Roman forms such as AT&T and B&B. Use // a complete ASCII-letter run so spaced prose, Hangul, and outer // alphanumeric continuations keep their existing routes. '&' => is_attached_ascii_roman_ampersand(word_chars, index), + // UEB 3.3.1 explicitly keeps the general-purpose asterisk inside the + // attached Roman example `M*A*S*H`. Preserve that one Roman section; + // Korean Rule 60 continues to own standalone and non-Roman asterisks. + '*' => is_attached_ascii_roman_asterisk(word_chars, index), // UEB 8.4.2 keeps the apostrophe inside the Roman word in its // `O'Hara`, `DON'T`, and `THAT'S` examples. Capitals-word mode may // terminate at this nonalphabetic symbol, but the surrounding Roman @@ -261,21 +377,71 @@ pub(crate) fn should_render_symbol_as_english( .get(index + 1) .is_some_and(|ch| ch.is_ascii_alphabetic()) } + // UEB 7.3 writes the single-character ellipsis as three full stops. + // Keep that UEB punctuation only while the Roman run visibly + // continues or closes an enclosure. A Unicode ellipsis followed + // directly by Korean remains the Rule-53 Korean middle-dot ellipsis. + '…' => { + is_english + && (next_ascii_letter_or_digit(word_chars, index, remaining_words) + || matches!(next_char, Some(')' | ']' | '}' | '”' | '’' | '」' | '』'))) + } ',' => { if !is_english { return false; } + let next_word_is_digit_led_korean = if index + 1 < word_chars.len() { + begins_numeric_then_korean(word_chars[index + 1..].iter().copied()) + } else { + remaining_words + .first() + .is_some_and(|word| begins_numeric_then_korean(word.chars())) + }; + if next_word_is_digit_led_korean { + // Korean rule 33: punctuation whose UEB and Korean cells + // differ is written as Korean punctuation at a Roman-to- + // Korean boundary. Limit the whole-token lookahead to a + // digit-led Korean word: Roman-led mixed words such as + // `LG유플러스` continue the Roman list and are not this case. + return false; + } + + let prev_roman = prev_ascii_letter_or_digit(word_chars, index) + || prev_char + .is_some_and(crate::rules::korean::rule_69::is_compatibility_unit_presentation); + let next_roman = next_ascii_letter_or_digit(word_chars, index, remaining_words) + || next_char + .is_some_and(crate::rules::korean::rule_69::is_compatibility_unit_presentation); + + prev_roman && next_roman + } + '-' => { let prev_ascii = prev_ascii_letter_or_digit(word_chars, index); let next_ascii = next_ascii_letter_or_digit(word_chars, index, remaining_words); - - prev_ascii && next_ascii + let roman_started_before_hyphen = word_chars[..index] + .iter() + .rev() + .take_while(|ch| ch.is_ascii_alphanumeric() || **ch == '-') + .any(|ch| ch.is_ascii_alphabetic()); + + // Korean rule 35 keeps a Roman-led identifier (`CV3-AD685`, + // `N-79-20`) in one Roman/number chain across its hyphens. A + // number-led item (`0-Zone`, `777-300ER`) has not entered Roman + // mode yet: its first Roman indicator belongs immediately before + // the first letter, never before an earlier hyphen. + prev_ascii && next_ascii && (is_english || roman_started_before_hyphen) } - '/' | '@' | '#' | '.' | '_' | ':' | '-' => { + '/' | '@' | '#' | '.' | '_' | ':' => { let prev_ascii = prev_ascii_letter_or_digit(word_chars, index); let next_ascii = next_ascii_letter_or_digit(word_chars, index, remaining_words); (prev_ascii && next_ascii) + // Korean rules 29/32/35: `Alpha : Beta` is one Roman + // section. The print tokenizer makes the colon a standalone + // word; an already-open section proves its left Roman item, + // while this lookahead proves the right Roman/number item. + || (symbol == ':' && is_english && word_chars == [':'] && next_ascii) || (symbol == '/' && prev_char == Some('/') && next_ascii) || (symbol == '/' && next_char == Some('/') && prev_ascii) } @@ -293,7 +459,16 @@ pub(crate) fn should_keep_english_mode_for_symbol( return false; } - should_render_symbol_as_english(true, true, &[], symbol, word_chars, index, remaining_words) + should_render_symbol_as_english( + true, + true, + false, + &[], + symbol, + word_chars, + index, + remaining_words, + ) } #[cfg(test)] @@ -371,39 +546,56 @@ mod tests { assert_eq!(next_ascii_letter_or_digit(&word, idx, remaining), expected); } - #[test] - fn should_render_symbol_as_english_for_parentheses() { - let opener: Vec = "(Hello".chars().collect(); - assert!(should_render_symbol_as_english( - true, - false, - &[], - '(', - &opener, - 0, - &[] - )); - - let korean_before: Vec = "가(".chars().collect(); - assert!(!should_render_symbol_as_english( - true, - false, - &[], - '(', - &korean_before, - 1, - &["A"] - )); - - assert!(!should_render_symbol_as_english( - false, - false, - &[], - '(', - &opener, - 0, - &[] - )); + #[rstest::rstest] + #[case::pure_roman("(Hello)", 0, &[], true, false, false, true)] + #[case::korean_before("가(", 1, &["A)"], true, false, false, false)] + #[case::indicator_disabled("(Hello)", 0, &[], false, false, false, false)] + #[case::official_rule_46_shape("BMI(체질량", 3, &["지수)"], true, true, false, false)] + #[case::roman_then_korean_body( + "SDV(Software", + 3, + &["Defined", "Vehicle,", "소프트웨어", "중심)"], + true, + true, + false, + false + )] + #[case::pure_roman_body("ABC(def)", 3, &[], true, true, false, true)] + #[case::pure_number_body("BSI(73)", 3, &[], true, true, false, true)] + #[case::unclosed_body("ABC(def", 3, &["한글"], true, true, false, true)] + #[case::nested_korean_body( + "BIT(BT(바이오)+IT(정보))", + 3, + &[], + true, + true, + false, + false + )] + #[case::rule_39_english_majority("(Korean:", 0, &["반찬)"], true, true, true, true)] + fn should_render_symbol_as_english_for_opening_parenthesis( + #[case] input: &str, + #[case] index: usize, + #[case] remaining_words: &[&str], + #[case] english_indicator: bool, + #[case] is_english: bool, + #[case] is_english_majority: bool, + #[case] expected: bool, + ) { + let word = input.chars().collect::>(); + assert_eq!( + should_render_symbol_as_english( + english_indicator, + is_english, + is_english_majority, + &[], + '(', + &word, + index, + remaining_words, + ), + expected, + ); } /// `should_render_symbol_as_english` for ')' — paren stack top 만 본다. @@ -416,7 +608,7 @@ mod tests { ) { let closer: Vec = ")".chars().collect(); assert_eq!( - should_render_symbol_as_english(true, true, &[stack_top], ')', &closer, 0, &[]), + should_render_symbol_as_english(true, true, false, &[stack_top], ')', &closer, 0, &[],), expected, ); } @@ -424,6 +616,7 @@ mod tests { /// `should_render_symbol_as_english` for ',' — 양쪽 ASCII + 영어 컨텍스트 둘 다 필요. #[rstest::rstest] #[case::both_ascii_in_english_mode("A,B", true, true)] + #[case::compatibility_unit_in_english_mode("㎿,30", true, true)] #[case::not_in_english_mode("A,B", false, false)] #[case::korean_neighbor("가,B", true, false)] fn should_render_symbol_as_english_for_comma_requires_ascii_neighbors( @@ -433,7 +626,63 @@ mod tests { ) { let word: Vec = input.chars().collect(); assert_eq!( - should_render_symbol_as_english(true, is_english, &[], ',', &word, 1, &[]), + should_render_symbol_as_english(true, is_english, false, &[], ',', &word, 1, &[],), + expected + ); + } + + #[rstest::rstest] + #[case::roman_led_chain("CV3-AD685", 3, false, true)] + #[case::roman_led_numeric_chain("N-79-20", 4, false, true)] + #[case::number_led_word("0-Zone", 1, false, false)] + #[case::number_led_suffix("777-300ER", 3, false, false)] + #[case::after_closed_enclosure("(GTX)-C", 5, false, false)] + fn hyphen_enters_roman_punctuation_only_after_a_roman_run( + #[case] input: &str, + #[case] index: usize, + #[case] is_english: bool, + #[case] expected: bool, + ) { + let chars = input.chars().collect::>(); + assert_eq!( + should_render_symbol_as_english(true, is_english, false, &[], '-', &chars, index, &[],), + expected + ); + } + + #[test] + fn punctuation_after_closed_korean_enclosure_does_not_reenter_roman_mode() { + let chars = "(XBB).1.5".chars().collect::>(); + assert!(!should_render_symbol_as_english( + true, + false, + false, + &[false], + '.', + &chars, + 5, + &[], + )); + } + + /// Korean rule 33 classifies a comma before a digit-led Korean word from + /// the complete following token. A Roman-led mixed word remains Roman + /// context at the boundary. + #[rstest::rstest] + #[case::digit_led_korean("2000년대", false)] + #[case::grouped_digit_led_korean("2,000년대", false)] + #[case::decimal_digit_led_korean("3.5년", false)] + #[case::pure_number("2000", true)] + #[case::roman_word("Beta", true)] + #[case::roman_led_mixed_word("LG유플러스", true)] + #[case::numeric_roman_unit_before_korean_particle("68kg의", true)] + fn comma_before_next_word_uses_narrow_digit_led_korean_context( + #[case] next_word: &str, + #[case] expected: bool, + ) { + let word = ['A', ',']; + assert_eq!( + should_render_symbol_as_english(true, true, false, &[], ',', &word, 1, &[next_word],), expected ); } @@ -453,7 +702,7 @@ mod tests { let word = input.chars().collect::>(); let index = word.iter().position(|ch| *ch == '\'').unwrap(); assert_eq!( - should_render_symbol_as_english(true, true, &[], '\'', &word, index, &[]), + should_render_symbol_as_english(true, true, false, &[], '\'', &word, index, &[],), expected, ); } @@ -464,6 +713,7 @@ mod tests { assert!(!should_render_symbol_as_english( true, true, + false, &[], '\'', &word, @@ -473,8 +723,9 @@ mod tests { } /// UEB 3.1.1 keeps attached Roman segments on both sides of `&` in the - /// same mode. The complete-run boundary deliberately excludes spaced - /// prose, Hangul boundaries, and outer alphanumeric continuations. + /// same mode. Korean rule 35 additionally keeps a trailing number/Roman + /// continuation in that section. The left boundary still excludes a + /// number-led sequence because the cited section begins with Roman text. #[rstest::rstest] #[case::official_at_and_t("AT&T", true, true)] #[case::official_b_and_b("B&B", true, true)] @@ -483,7 +734,9 @@ mod tests { #[case::hangul_right("A&나", true, false)] #[case::digit_neighbor("3&B", true, false)] #[case::digit_outer_left("3A&B", true, false)] - #[case::digit_outer_right("A&B3", true, false)] + #[case::rule35_digit_suffix("A&B3", true, true)] + #[case::rule35_digit_then_roman_suffix("A&B3C", true, true)] + #[case::ampersand_after_digit("A&B3&C", true, false)] #[case::multiple_ampersands("A&B&C", true, true)] #[case::empty_segment("A&&B", true, false)] #[case::no_roman_indicator("AT&T", false, false)] @@ -495,7 +748,53 @@ mod tests { let word: Vec = input.chars().collect(); let index = word.iter().position(|ch| *ch == '&').unwrap(); assert_eq!( - should_render_symbol_as_english(english_indicator, true, &[], '&', &word, index, &[],), + should_render_symbol_as_english( + english_indicator, + true, + false, + &[], + '&', + &word, + index, + &[], + ), + expected, + ); + } + + /// UEB 3.3.1 uses one uninterrupted UEB run for official `M*A*S*H`. + /// Korean rules 32/35 preserve the same form inside a Korean document, + /// while numeric multiplication, detached marks, and empty segments remain + /// outside the Roman-asterisk grammar. + #[rstest::rstest] + #[case::official_mash_first("M*A*S*H", 1, true, true)] + #[case::official_mash_middle("M*A*S*H", 3, true, true)] + #[case::official_mash_last("M*A*S*H", 5, true, true)] + #[case::roman_number_chain("A1*B2", 2, true, true)] + #[case::number_led("2*A", 1, true, false)] + #[case::digit_only_right_segment("A*2", 1, true, false)] + #[case::empty_segment("A**B", 1, true, false)] + #[case::hangul_segment("가*A", 1, true, false)] + #[case::detached("A * B", 2, true, false)] + #[case::no_roman_indicator("M*A*S*H", 1, false, false)] + fn attached_asterisk_requires_complete_ascii_roman_segments( + #[case] input: &str, + #[case] index: usize, + #[case] english_indicator: bool, + #[case] expected: bool, + ) { + let word = input.chars().collect::>(); + assert_eq!( + should_render_symbol_as_english( + english_indicator, + true, + false, + &[], + '*', + &word, + index, + &[], + ), expected, ); } @@ -505,6 +804,8 @@ mod tests { #[case::official_at_and_t("AT&T", false)] #[case::official_b_and_b("B&B", false)] #[case::official_spaced("Marks & Spencer", false)] + #[case::rule35_digit_suffix("&P500", true)] + #[case::digit_without_roman_segment("&500", false)] fn one_sided_ampersand_requires_complete_right_roman_segment( #[case] input: &str, #[case] expected: bool, diff --git a/libs/braillify/src/ipa.rs b/libs/braillify/src/ipa.rs index 10705d04..5baf0f77 100644 --- a/libs/braillify/src/ipa.rs +++ b/libs/braillify/src/ipa.rs @@ -6,7 +6,7 @@ use crate::rules::context::EncodingMode; use crate::{encode, english, utils, with_encoder}; pub(crate) fn is_ipa_phonetic_symbol(c: char) -> bool { - matches!(c, 'θ' | 'ə' | 'æ' | 'ŋ' | 'ː') + matches!(c, 'θ' | 'ə' | 'æ' | 'ŋ' | 'ː' | 'ˑ') } /// PDF 제38항 자동 감지 — input의 묶음 패턴 안 IPA 음운 기호로 IPA 컨텍스트 추론. @@ -210,6 +210,7 @@ pub(crate) fn encode_ipa_char(ch: char) -> Option> { match ch { 'ə' => Some(vec![34]), // ⠢ (점 2+6) 'ː' => Some(vec![18]), // ⠒ (점 2+5) — 장음 표시 + 'ˑ' => Some(vec![16, 2]), // ⠐⠂ — 반장음 부호 (IPA 제2장) 'θ' => Some(vec![40, 57]), // ⠨⠹ (점 4+6, 점 1+4+5+6) 'ŋ' => Some(vec![43]), // ⠫ (점 1+2+4+6) 'æ' => Some(vec![41]), // ⠩ (점 1+4+6) @@ -263,6 +264,12 @@ mod tests { assert_eq!(encode_ipa_char(ch), Some(expected)); } + #[test] + fn encodes_korean_ipa_half_length_mark() { + assert_eq!(encode_ipa_char('ˑ'), Some(cells("⠐⠂"))); + assert!(detect_ipa_context("[aˑ]")); + } + #[test] fn bracket_open_flushes_prior_korean_and_strips_english_terminator() { let bracket_first = encode_ipa("[θ]").expect("initial IPA bracket should encode"); diff --git a/libs/braillify/src/lib.rs b/libs/braillify/src/lib.rs index b9bddde2..e968e270 100644 --- a/libs/braillify/src/lib.rs +++ b/libs/braillify/src/lib.rs @@ -96,6 +96,7 @@ mod test_helpers { pub result: Vec, pub prev_word: String, pub remaining_words: Vec, + pub roman_section_continues_from_previous_word: bool, } impl CtxOwned { @@ -116,6 +117,7 @@ mod test_helpers { result: Vec::new(), prev_word: String::new(), remaining_words: Vec::new(), + roman_section_continues_from_previous_word: false, } } @@ -125,6 +127,13 @@ mod test_helpers { self } + /// Mark this print word as a continuation of an already active Roman + /// section. + pub(crate) fn with_roman_section_continuation(mut self) -> Self { + self.roman_section_continues_from_previous_word = true; + self + } + /// Builder: set the `remaining_words` field that the borrowed `RuleContext` /// exposes. Stores owned strings so the borrowed context can outlive call sites. pub(crate) fn with_remaining_words(mut self, words: I) -> Self @@ -161,6 +170,8 @@ mod test_helpers { }), is_all_uppercase: false, ascii_starts_at_beginning: false, + roman_section_continues_from_previous_word: self + .roman_section_continues_from_previous_word, skip_count: &mut self.skip_count, state: &mut self.state, result: &mut self.result, @@ -309,6 +320,25 @@ fn may_normalize_roman_numeral_presentation(c: char) -> bool { (0x2160..=0x217f).contains(&(c as u32)) } +fn may_normalize_parenthesized_hangul_presentation(c: char) -> bool { + (0x3200..=0x321e).contains(&(c as u32)) +} + +fn may_normalize_word_separator_middle_dot(c: char) -> bool { + c == '\u{2e31}' +} + +fn pure_roman_compatibility_unit_decomposition(c: char) -> Option> { + use unicode_normalization::UnicodeNormalization; + + let parts = + crate::rules::korean::rule_69::compatibility_unit_decomposition(c).or_else(|| { + crate::rules::korean::rule_68::is_rule_68_symbol(c) + .then(|| std::iter::once(c).nfkc().collect()) + })?; + parts.iter().all(char::is_ascii_alphabetic).then_some(parts) +} + /// Korean Braille rule 36 transcribes a Roman numeral with its corresponding /// Roman letters. Unicode U+2160–U+217F are presentation forms whose NFKC /// decomposition is exactly that Roman-letter spelling (`Ⅱ` → `II`). Normalize @@ -328,6 +358,88 @@ fn normalize_roman_numeral_presentation<'a>(text: Cow<'a, str>) -> Cow<'a, str> Cow::Owned(out) } +/// Unicode U+3200-U+321E are compatibility presentation forms whose visible +/// content is ordinary Hangul enclosed by literal parentheses (`㈜` -> `(주)`, +/// `㈔` -> `(사)`). The Korean braille standard already defines both the +/// enclosed Hangul and the parentheses; expanding the presentation form lets +/// those existing rules own the transcription without assigning a new braille +/// symbol to each Unicode glyph. +fn normalize_parenthesized_hangul_presentation<'a>(text: Cow<'a, str>) -> Cow<'a, str> { + use unicode_normalization::UnicodeNormalization; + + let mut out = String::with_capacity(text.len()); + for ch in text.chars() { + if may_normalize_parenthesized_hangul_presentation(ch) { + out.extend(std::iter::once(ch).nfkc()); + } else { + out.push(ch); + } + } + Cow::Owned(out) +} + +/// U+2E31 WORD SEPARATOR MIDDLE DOT is a visible presentation of a word +/// boundary, not U+00B7 MIDDLE DOT punctuation. Preserve that semantic +/// distinction by expanding it to one ordinary print space before tokenization; +/// the existing Korean spacing rules then emit one blank braille cell. +fn normalize_word_separator_middle_dot<'a>(text: Cow<'a, str>) -> Cow<'a, str> { + let mut out = String::with_capacity(text.len()); + for ch in text.chars() { + if may_normalize_word_separator_middle_dot(ch) { + out.push(' '); + } else { + out.push(ch); + } + } + Cow::Owned(out) +} + +/// Korean Braille rule 69 assigns Unicode compatibility unit glyphs the +/// transcription of their semantic Roman spelling. When such a glyph is +/// immediately combined with ordinary Roman letters (`㎾h` -> `kWh`) or with +/// another unit component (`W/㎏` -> `W/kg`), encoding it as a self-contained +/// symbol would incorrectly close and reopen the Roman section at the Unicode +/// code-point boundary. +/// +/// Expand only unit glyphs whose complete NFKC decomposition consists of Roman +/// letters *and* which are joined to another Roman unit component. Standalone +/// compatibility units retain their dedicated rule-68/69 encoding. +/// Compatibility forms containing a slash or an exponent (`㎧`, `㎥`) and +/// non-unit compatibility characters remain untouched. +fn normalize_pure_roman_compatibility_units<'a>(text: Cow<'a, str>) -> Cow<'a, str> { + let chars = text.chars().collect::>(); + let mut out = String::with_capacity(text.len()); + for (index, ch) in chars.iter().copied().enumerate() { + let is_roman_unit_component = |candidate: char| { + candidate.is_ascii_alphabetic() + || candidate == 'μ' + || pure_roman_compatibility_unit_decomposition(candidate).is_some() + }; + let directly_joined = index + .checked_sub(1) + .and_then(|previous| chars.get(previous)) + .is_some_and(|previous| is_roman_unit_component(*previous)) + || chars + .get(index + 1) + .is_some_and(|next| is_roman_unit_component(*next)); + let joined_through_slash = (index >= 2 + && matches!(chars[index - 1], '/' | '\u{2044}' | '\u{2215}') + && is_roman_unit_component(chars[index - 2])) + || (index + 2 < chars.len() + && matches!(chars[index + 1], '/' | '\u{2044}' | '\u{2215}') + && is_roman_unit_component(chars[index + 2])); + + if (directly_joined || joined_through_slash) + && let Some(parts) = pure_roman_compatibility_unit_decomposition(ch) + { + out.extend(parts); + } else { + out.push(ch); + } + } + Cow::Owned(out) +} + /// Default-route whole expressions that contain math-only relational/grouping /// glyphs which cannot be encoded correctly one space-separated token at a time. /// @@ -348,7 +460,12 @@ fn default_math_expression_needs_whole_route(text: &str) -> bool { let has_operand = chars.iter().any(|c| c.is_ascii_alphanumeric()); has_operand && chars.iter().enumerate().any(|(i, c)| match *c { - '→' | '←' | '↗' | '↘' | '↑' | '↓' | '△' | '□' => true, + // 수학 제32·33항의 합동/기하 연산 기호도 양쪽 변수를 포함한 + // 하나의 수식이다. 공백 단위 token 경로로 나누면 뒤쪽 대문자 + // 변수가 국어 제29항의 로마자 연속으로 오인될 수 있다. + '→' | '←' | '↗' | '↘' | '↑' | '↓' | '△' | '□' | '≅' | '▷' | '◁' => { + true + } // 수학 제34/37항 hat/bar 결합부호는 단일 문자 operand에 붙는다 // (`x̂`, `x̄`, `p̂`, `2̄.3010`). NFD 분해된 악센트 단어(`maître` → // `mai`+◌̂+`tre`)처럼 결합부호가 3글자 이상 단어 내부에 있으면 @@ -391,6 +508,9 @@ fn combining_mark_on_single_letter(chars: &[char], i: usize) -> bool { struct NormalizationTriggers { has_math_alphanumeric: bool, has_roman_numeral_presentation: bool, + has_parenthesized_hangul_presentation: bool, + has_word_separator_middle_dot: bool, + has_pure_roman_compatibility_unit: bool, has_decomposable_latin: bool, has_negation_combiner: bool, has_vector_mark: bool, @@ -405,6 +525,11 @@ impl NormalizationTriggers { for c in text.chars() { triggers.has_math_alphanumeric |= may_normalize_math_alphanumeric(c); triggers.has_roman_numeral_presentation |= may_normalize_roman_numeral_presentation(c); + triggers.has_parenthesized_hangul_presentation |= + may_normalize_parenthesized_hangul_presentation(c); + triggers.has_word_separator_middle_dot |= may_normalize_word_separator_middle_dot(c); + triggers.has_pure_roman_compatibility_unit |= + pure_roman_compatibility_unit_decomposition(c).is_some(); triggers.has_decomposable_latin |= may_decompose_accented_latin(c); triggers.has_negation_combiner |= c == '\u{0338}'; triggers.has_vector_mark |= is_vector_mark(c); @@ -801,6 +926,21 @@ pub fn encode_with_options(text: &str, options: &EncodeOptions) -> Result Result= 3 + && text.starts_with('$') + && text.ends_with('$') + && text.matches('$').count() == 2 + { + let inner = &text[1..text.len() - 1]; + return crate::rules::token_rules::latex_math::encode_latex_math_bytes_with_context( + inner, + math_context, + ); + } + let chars: Vec = text.chars().collect(); // PDF 수학 제12항: 단일 ASCII lowercase = 영자표시 ⠴(52) + 알파벳 점자. @@ -2066,6 +2225,94 @@ mod coverage_targeted_tests { ); } + #[rstest::rstest] + #[case::parenthesized_jamo("㈀", "(ᄀ)")] + #[case::parenthesized_syllable("㈎", "(가)")] + #[case::incorporated_association("㈔", "(사)")] + #[case::incorporated_company("㈜", "(주)")] + #[case::afternoon("㈞", "(오후)")] + fn normalizes_parenthesized_hangul_presentation(#[case] input: &str, #[case] expected: &str) { + assert_eq!( + normalize_parenthesized_hangul_presentation(Cow::Borrowed(input)), + expected + ); + } + + /// The compatibility glyph carries no independent braille semantics: its + /// expanded print-equivalent must follow the ordinary Korean parenthesis + /// and Hangul rules in every surrounding position. + #[rstest::rstest] + #[case::association_prefix("㈔한국", "(사)한국")] + #[case::company_prefix("㈜한빛", "(주)한빛")] + #[case::attached_company_suffix("한빛㈜", "한빛(주)")] + fn parenthesized_hangul_presentation_matches_expanded_print( + #[case] presentation: &str, + #[case] expanded: &str, + ) { + assert_eq!(encode_to_unicode(presentation), encode_to_unicode(expanded)); + } + + #[test] + fn normalizes_word_separator_middle_dot_to_print_space() { + assert_eq!( + normalize_word_separator_middle_dot(Cow::Borrowed("인증⸱실천⸱교육")), + "인증 실천 교육" + ); + } + + #[test] + fn word_separator_middle_dot_matches_visible_word_spacing() { + assert_eq!( + encode_to_unicode("인증⸱실천⸱교육"), + encode_to_unicode("인증 실천 교육") + ); + } + + #[rstest::rstest] + #[case::kilowatt_hour("㎾h", "kWh")] + #[case::milli_sievert("m㏜", "mSv")] + #[case::watt_per_kilogram("W/㎏", "W/kg")] + #[case::kilogram_carbon_equivalent("㎏CO2eq", "kgCO2eq")] + #[case::milligram_per_gram("㎎/g", "mg/g")] + fn normalizes_pure_roman_compatibility_unit_components( + #[case] input: &str, + #[case] expected: &str, + ) { + assert_eq!( + normalize_pure_roman_compatibility_units(Cow::Borrowed(input)), + expected + ); + } + + #[rstest::rstest] + #[case::superscript("㎥")] + #[case::quotient("㎧")] + #[case::standalone_hectare("㏊")] + #[case::non_unit_compatibility_abbreviation("㏚")] + fn pure_roman_unit_normalization_preserves_other_compatibility_forms(#[case] input: &str) { + assert_eq!( + normalize_pure_roman_compatibility_units(Cow::Borrowed(input)), + input + ); + } + + /// Rule 69: a compatibility unit presentation and its semantic Roman + /// spelling are one unit section even when joined to another component. + #[rstest::rstest] + #[case::kilowatt_hour("용량은 1㎾h이다", "용량은 1kWh이다")] + #[case::milli_sievert("선량은 1m㏜보다 낮다", "선량은 1mSv보다 낮다")] + #[case::watt_per_kilogram("기준은 4.0W/㎏이다", "기준은 4.0W/kg이다")] + fn compound_compatibility_units_match_semantic_roman_spelling( + #[case] presentation: &str, + #[case] expanded: &str, + ) { + assert_eq!( + encode_to_unicode(presentation), + encode_to_unicode(expanded), + "presentation={presentation:?}" + ); + } + #[test] fn normalize_math_alphanumeric_runtime_block_offset() { let input = std::hint::black_box('\u{1D44F}'); @@ -2234,6 +2481,19 @@ mod coverage_targeted_tests { assert!(result.is_ok()); } + /// 수학 제32·33항: 수학 전용 관계 기호 양쪽의 대문자는 하나의 수식 + /// 안의 변수다. 뒤쪽 변수를 국어 로마자 연속 항목으로 보아 ⠰를 붙이지 않는다. + #[rstest::rstest] + #[case::congruence("A ≅ B", "⠠⠁⠀⠈⠔⠒⠒⠀⠠⠃")] + #[case::right_geometric_operation("G ▷ N", "⠠⠛⠀⠸⠜⠀⠠⠝")] + #[case::left_geometric_operation("N ◁ G", "⠠⠝⠀⠸⠣⠀⠠⠛")] + fn default_route_keeps_math_relation_operands_in_one_expression( + #[case] input: &str, + #[case] expected: &str, + ) { + assert_eq!(encode_to_unicode(input).as_deref(), Ok(expected)); + } + /// Math mode — multi-char expression with spaces around operators. /// Covers the whitespace-cleaning loop (lines 777-790). #[test] diff --git a/libs/braillify/src/rules/context.rs b/libs/braillify/src/rules/context.rs index 7c957021..0f26671c 100644 --- a/libs/braillify/src/rules/context.rs +++ b/libs/braillify/src/rules/context.rs @@ -87,6 +87,11 @@ pub struct EncoderState { pub needs_english_continuation: bool, /// Rule 35 chain: English followed by digits may resume English without indicators pub roman_number_chain: bool, + /// The active Roman section is an English phrase rather than metalinguistic + /// Roman material in a Korean sentence. Korean rule 37 keeps its six lower + /// wordsigns expanded in Korean context, while UEB 10.5 applies in an + /// independently recognizable English phrase. + pub roman_section_is_english_context: bool, /// Stack tracking whether parentheses were opened in English context pub parenthesis_stack: Vec, /// Currently in a number sequence (수표 already emitted) @@ -122,6 +127,7 @@ impl EncoderState { has_processed_word: false, needs_english_continuation: false, roman_number_chain: false, + roman_section_is_english_context: false, parenthesis_stack: Vec::new(), is_number: false, is_big_english: false, @@ -178,6 +184,13 @@ pub struct RuleContext<'a> { pub is_all_uppercase: bool, /// Whether ASCII letters start at index 0 pub ascii_starts_at_beginning: bool, + /// Whether a Roman section was already active before the current print word. + /// + /// Korean rule 37 suppresses whole-word contractions only for the first + /// Roman word after the Roman indicator. The emitter may enter Roman mode + /// before character rules run, so `state.is_english` alone cannot distinguish + /// that first word from a later word in the same section. + pub roman_section_continues_from_previous_word: bool, /// Skip count — rules can set this to skip subsequent characters pub skip_count: &'a mut usize, /// Shared mutable encoder state diff --git a/libs/braillify/src/rules/emit.rs b/libs/braillify/src/rules/emit.rs index aa2389d4..306dde1b 100644 --- a/libs/braillify/src/rules/emit.rs +++ b/libs/braillify/src/rules/emit.rs @@ -19,6 +19,112 @@ struct WordContext<'a> { remaining_words: &'a [&'a str], } +/// Rule 29/35: a following print word which begins with Roman text or a number +/// continues the same Roman section across its intervening print space. +fn next_word_starts_roman_or_number(remaining_words: &[&str]) -> bool { + remaining_words + .first() + .and_then(|word| word.chars().next()) + .is_some_and(|ch| ch.is_ascii_alphanumeric()) +} + +fn is_opening_english_phrase_enclosure(ch: char) -> bool { + matches!(ch, '(' | '[' | '{' | '‘' | '“' | '"') +} + +fn is_closing_english_phrase_enclosure(ch: char) -> bool { + matches!(ch, ')' | ']' | '}' | '’' | '”' | '"') +} + +/// Decide whether the Roman section beginning in `tokens[start_index]` has an +/// independently visible English-phrase context. +/// +/// Korean rule 37 expands the six UEB lower wordsigns when Roman material is +/// mentioned inside Korean prose. The NIKL's rule consultation distinguishes +/// that case from an English title or sentence, where UEB 10.5 applies. We use +/// only print structure available to a plain-text encoder: at least two Roman +/// print words plus either sentence/title capitalization or a paired-enclosure +/// boundary. A lowercase metalinguistic list such as the rule-37 attachment +/// (`be, his, was, were의 ...`) therefore remains Korean context. +fn roman_section_has_english_phrase_context(tokens: &[Token<'_>], start_index: usize) -> bool { + let mut started = false; + let mut roman_word_count = 0usize; + let mut has_uppercase = false; + let mut has_enclosure_boundary = false; + + for token in tokens.iter().skip(start_index) { + let Token::Word(word) = token else { + if matches!(token, Token::Space(_) | Token::Mode(_)) { + continue; + } + if started { + break; + } + continue; + }; + + let scan_start = if started { + let Some(first_script) = word + .chars + .iter() + .position(|ch| ch.is_ascii_alphanumeric() || crate::utils::is_korean_char(*ch)) + else { + continue; + }; + if crate::utils::is_korean_char(word.chars[first_script]) { + break; + } + let Some(first_roman) = word.chars[first_script..] + .iter() + .position(|ch| ch.is_ascii_alphabetic()) + .map(|offset| first_script + offset) + else { + continue; + }; + first_roman + } else { + let Some(first_roman) = word.chars.iter().position(|ch| ch.is_ascii_alphabetic()) + else { + continue; + }; + first_roman + }; + + if !started { + has_enclosure_boundary |= word.chars[..scan_start] + .iter() + .rev() + .take_while(|ch| !ch.is_ascii_alphanumeric() && !crate::utils::is_korean_char(**ch)) + .any(|ch| is_opening_english_phrase_enclosure(*ch)); + } + + let section_end = word.chars[scan_start..] + .iter() + .position(|ch| crate::utils::is_korean_char(*ch)) + .map_or(word.chars.len(), |offset| scan_start + offset); + let roman_slice = &word.chars[scan_start..section_end]; + let Some(last_roman) = roman_slice.iter().rposition(|ch| ch.is_ascii_alphabetic()) else { + if section_end < word.chars.len() { + break; + } + continue; + }; + + started = true; + roman_word_count += 1; + has_uppercase |= roman_slice.iter().any(|ch| ch.is_ascii_uppercase()); + has_enclosure_boundary |= roman_slice[last_roman + 1..] + .iter() + .any(|ch| is_closing_english_phrase_enclosure(*ch)); + + if section_end < word.chars.len() { + break; + } + } + + roman_word_count >= 2 && (has_uppercase || has_enclosure_boundary) +} + /// 토큰의 byte 슬라이스가 한글표(⠸⠷) 점형과 일치하는지. fn is_hangul_wrap_start(token: &Token<'_>) -> bool { matches!(token, Token::PreEncoded(bytes) if bytes.as_slice() == HANGUL_WRAP_START_BYTES) @@ -111,6 +217,133 @@ fn roman_word_after_prefix<'a>( None } +fn current_word_at_or_after<'a>( + tokens: &'a [Token<'a>], + index: usize, +) -> Option<&'a WordToken<'a>> { + for token in tokens.iter().skip(index) { + match token { + Token::Mode(_) => continue, + Token::Word(word) => return Some(word), + _ => return None, + } + } + None +} + +fn is_separated_from_previous_word(tokens: &[Token<'_>], index: usize) -> bool { + let mut saw_space = false; + for token in tokens[..index].iter().rev() { + match token { + Token::Mode(_) => {} + Token::Space(_) => saw_space = true, + Token::Word(_) => return saw_space, + _ => return false, + } + } + false +} + +fn previous_word_index_before(tokens: &[Token<'_>], index: usize) -> Option { + tokens[..index] + .iter() + .rposition(|token| matches!(token, Token::Word(_))) +} + +fn matching_group_open(close: char) -> Option { + match close { + ')' => Some('('), + ']' => Some('['), + '}' => Some('{'), + '’' => Some('‘'), + '”' => Some('“'), + '〉' => Some('〈'), + '》' => Some('《'), + '」' => Some('「'), + '』' => Some('『'), + '】' => Some('【'), + '〕' => Some('〔'), + '〗' => Some('〖'), + '〙' => Some('〘'), + '〛' => Some('〚'), + _ => None, + } +} + +fn closed_enclosure_before_contains_ascii(tokens: &[Token<'_>], index: usize) -> bool { + let Some(previous_index) = previous_word_index_before(tokens, index) else { + return false; + }; + let Token::Word(previous) = &tokens[previous_index] else { + unreachable!("previous_word_index_before returns a Word token"); + }; + + let mut end = previous.chars.len(); + while end > 0 && matches!(previous.chars[end - 1], ',' | ':' | ';' | '.' | '!' | '?') { + end -= 1; + } + let Some(&closer) = previous.chars.get(end.saturating_sub(1)) else { + return false; + }; + let Some(opener) = matching_group_open(closer) else { + return false; + }; + + let mut nesting = 1usize; + let mut contains_ascii = false; + for token_index in (0..=previous_index).rev() { + let Token::Word(word) = &tokens[token_index] else { + if matches!(tokens[token_index], Token::Space(_) | Token::Mode(_)) { + continue; + } + return false; + }; + let word_end = if token_index == previous_index { + end - 1 + } else { + word.chars.len() + }; + for &ch in word.chars[..word_end].iter().rev() { + if ch == closer { + nesting += 1; + } else if ch == opener { + nesting -= 1; + if nesting == 0 { + return contains_ascii; + } + } else if ch.is_ascii_alphabetic() { + contains_ascii = true; + } + } + } + false +} + +/// Rule 34's closing enclosure ends the enclosed Roman item without a Roman +/// terminator. If the next whitespace-delimited item starts directly with +/// Roman text, rule 29 opens a new Roman section. A following enclosure is +/// excluded so the official rule-32 list `(a), (e), (i)` remains one Roman +/// section and may use UEB grade-1 indicators for its single letters. +fn starts_new_roman_section_after_closed_enclosure(tokens: &[Token<'_>], index: usize) -> bool { + if !is_separated_from_previous_word(tokens, index) { + return false; + } + let Some(current) = current_word_at_or_after(tokens, index) else { + return false; + }; + + let current_starts_roman = current + .chars + .iter() + .copied() + .find(|ch| !matches!(ch, '‘' | '“' | '\'' | '"')) + .is_some_and(|ch| ch.is_ascii_alphabetic()); + if !current_starts_roman { + return false; + } + closed_enclosure_before_contains_ascii(tokens, index) +} + fn enter_roman_before_ueb_prefix( tokens: &[Token<'_>], prefix_index: usize, @@ -225,8 +458,33 @@ pub fn emit(ir: &mut DocumentIR, char_engine: &mut RuleEngine) -> Result } } Token::Mode(event) => { - enter_roman_before_ueb_prefix(&ir.tokens, idx, *event, &mut ir.state, &mut result); - emit_mode_event(*event, &mut ir.state, &mut result); + let starts_new_roman_section = + starts_new_roman_section_after_closed_enclosure(&ir.tokens, idx); + let event = if *event == ModeEvent::EnterEnglishContinue && starts_new_roman_section + { + ModeEvent::EnterEnglish + } else { + *event + }; + if starts_new_roman_section { + ir.state.needs_english_continuation = false; + } + let opens_fresh_roman_section = !ir.state.is_english + && !ir.state.roman_number_chain + && !ir.state.needs_english_continuation + && matches!( + event, + ModeEvent::EnterEnglish + | ModeEvent::Grade1Indicator + | ModeEvent::CapsWord + | ModeEvent::CapsPassageStart + ); + if opens_fresh_roman_section { + ir.state.roman_section_is_english_context = + roman_section_has_english_phrase_context(&ir.tokens, idx); + } + enter_roman_before_ueb_prefix(&ir.tokens, idx, event, &mut ir.state, &mut result); + emit_mode_event(event, &mut ir.state, &mut result); } Token::Fraction(frac) => { if let Some(ref w) = frac.whole { @@ -295,6 +553,220 @@ fn word_context<'a>(word_texts: &'a [&'a str], word_index: usize) -> WordContext } } +/// Whether the next word token is separated from the current word by print +/// whitespace. Token rewrites can insert mode/pre-encoded tokens between the +/// two words, so inspect the whole intervening token span rather than only the +/// immediate successor. +fn has_space_before_next_word(tokens: &[Token<'_>], token_index: usize) -> bool { + let mut saw_space = false; + for token in tokens.iter().skip(token_index + 1) { + match token { + Token::Space(_) => saw_space = true, + Token::Word(_) => return saw_space, + _ => {} + } + } + false +} + +fn matching_group_close(ch: char) -> Option { + match ch { + '(' => Some(')'), + '[' => Some(']'), + '{' => Some('}'), + '〈' => Some('〉'), + '《' => Some('》'), + '「' => Some('」'), + '『' => Some('』'), + '【' => Some('】'), + '〔' => Some('〕'), + '〖' => Some('〗'), + '〘' => Some('〙'), + '〚' => Some('〛'), + '‘' => Some('’'), + '“' => Some('”'), + _ => None, + } +} + +/// Rule 33's printed `Umm ...이라고` example treats a whitespace-separated +/// ellipsis as the punctuation ending the Roman run, so the preceding Roman +/// terminator is still omitted. Accept the Unicode ellipsis forms and the +/// three-full-stop print spelling as the same punctuation grammar. +fn word_starts_with_rule_33_ellipsis(word: &WordToken<'_>) -> bool { + matches!(word.chars.first(), Some('…' | '⋯')) || word.chars.starts_with(&['.', '.', '.']) +} + +/// Korean rules 29, 32, and 35: print whitespace around a colon does not split +/// a Roman section when the colon is followed by another Roman/number item. +/// The tokenizer represents `Alpha : Beta` as three words, so prove the item +/// after the standalone colon before treating the colon as UEB punctuation. +fn spaced_colon_connects_roman_items(tokens: &[Token<'_>], colon_index: usize) -> bool { + let Some(Token::Word(colon)) = tokens.get(colon_index) else { + return false; + }; + if colon.chars.as_slice() != [':'] { + return false; + } + + tokens + .iter() + .skip(colon_index + 1) + .find_map(|token| match token { + Token::Space(_) | Token::Mode(_) => None, + Token::Word(word) => Some( + word.chars + .iter() + .find(|ch| ch.is_ascii_alphanumeric() || crate::utils::is_korean_char(**ch)) + .is_some_and(|ch| ch.is_ascii_alphanumeric()), + ), + _ => Some(false), + }) + .unwrap_or(false) +} + +/// UEB 3.1.1 and Korean rule 29 keep an ampersand inside a spaced Roman name +/// or phrase (`Marks & Spencer`, `Scan & Solution`). The tokenizer makes the +/// ampersand its own word, so prove a Roman word on both sides before allowing +/// it to bridge the current Roman section. A right-hand word may be attached +/// directly to the sign (`Mining &Development`). +fn spaced_ampersand_connects_roman_words(tokens: &[Token<'_>], ampersand_index: usize) -> bool { + let Some(Token::Word(ampersand)) = tokens.get(ampersand_index) else { + return false; + }; + if ampersand.chars.first() != Some(&'&') { + return false; + } + + let left_is_roman = tokens[..ampersand_index] + .iter() + .rev() + .find_map(|token| match token { + Token::Space(_) | Token::Mode(_) => None, + Token::Word(word) => Some( + word.chars + .iter() + .rev() + .find(|ch| ch.is_ascii_alphanumeric() || crate::utils::is_korean_char(**ch)) + .is_some_and(|ch| ch.is_ascii_alphanumeric()), + ), + _ => Some(false), + }) + .unwrap_or(false); + if !left_is_roman { + return false; + } + + if ampersand + .chars + .get(1) + .is_some_and(|ch| ch.is_ascii_alphabetic()) + { + return true; + } + if ampersand.chars.len() != 1 { + return false; + } + + tokens + .iter() + .skip(ampersand_index + 1) + .find_map(|token| match token { + Token::Space(_) | Token::Mode(_) => None, + Token::Word(word) => Some( + word.chars + .iter() + .find(|ch| ch.is_ascii_alphanumeric() || crate::utils::is_korean_char(**ch)) + .is_some_and(|ch| ch.is_ascii_alphabetic()), + ), + _ => Some(false), + }) + .unwrap_or(false) +} + +/// Rule 29 keeps consecutive Roman/number text in one section even across +/// print spaces. A separated enclosure continues that section only when the +/// *complete* enclosure is Roman/number text. This distinguishes +/// `GRI (Global Reporting Initiative)` from `Poison (모래성)` and from a mixed +/// gloss such as `TVB (Television - 전시광파유한공사)`. +fn separated_symbol_continues_roman_section(tokens: &[Token<'_>], token_index: usize) -> bool { + let next_word = tokens + .iter() + .enumerate() + .skip(token_index + 1) + .find_map(|(index, token)| match token { + Token::Word(word) => Some((index, word)), + _ => None, + }); + let Some((next_word_index, next_word)) = next_word else { + return false; + }; + + if next_word.chars.first() == Some(&'&') + && spaced_ampersand_connects_roman_words(tokens, next_word_index) + { + return true; + } + + if word_starts_with_rule_33_ellipsis(next_word) { + return true; + } + + if spaced_colon_connects_roman_items(tokens, next_word_index) { + return true; + } + + // Rule 35: punctuation may introduce a numeric continuation (`'23`). + if next_word + .chars + .iter() + .find(|ch| ch.is_ascii_alphanumeric() || crate::utils::is_korean_char(**ch)) + .is_some_and(char::is_ascii_digit) + { + return true; + } + + let Some(opening) = next_word.chars.first().copied() else { + return false; + }; + let Some(closing) = matching_group_close(opening) else { + return false; + }; + + let mut depth = 0usize; + let mut saw_roman_or_number = false; + let mut saw_korean = false; + for token in tokens.iter().skip(token_index + 1) { + match token { + Token::Space(_) | Token::Mode(_) => continue, + Token::Word(word) => { + for ch in word.chars.iter().copied() { + if ch == opening { + depth += 1; + continue; + } + if ch == closing { + let Some(next_depth) = depth.checked_sub(1) else { + return false; + }; + depth = next_depth; + if depth == 0 { + return saw_roman_or_number && !saw_korean; + } + continue; + } + if depth > 0 { + saw_roman_or_number |= ch.is_ascii_alphanumeric(); + saw_korean |= crate::utils::is_korean_char(ch); + } + } + } + _ => return false, + } + } + false +} + fn emit_mode_event(event: ModeEvent, state: &mut EncoderState, result: &mut Vec) { match event { ModeEvent::EnterEnglish => { @@ -348,6 +820,7 @@ fn apply_core_encoding_rules( is_all_uppercase: bool, has_korean_char: bool, ascii_starts_at_beginning: bool, + roman_section_continues_from_previous_word: bool, state: &mut EncoderState, skip_count: &mut usize, remaining_words: &[&str], @@ -363,6 +836,7 @@ fn apply_core_encoding_rules( has_korean_char, is_all_uppercase, ascii_starts_at_beginning, + roman_section_continues_from_previous_word, skip_count, state, result, @@ -379,6 +853,7 @@ fn apply_inter_character_rules( is_all_uppercase: bool, has_korean_char: bool, ascii_starts_at_beginning: bool, + roman_section_continues_from_previous_word: bool, state: &mut EncoderState, skip_count: &mut usize, remaining_words: &[&str], @@ -394,6 +869,7 @@ fn apply_inter_character_rules( has_korean_char, is_all_uppercase, ascii_starts_at_beginning, + roman_section_continues_from_previous_word, skip_count, state, result, @@ -413,6 +889,7 @@ fn emit_word( ) -> Result<(), String> { let prev_word = context.prev_word; let remaining_words = context.remaining_words; + let next_word_is_separated = has_space_before_next_word(all_tokens, token_index); // 다음 비공백 토큰이 한글표(⠸⠷)이면 영어 모드를 끊지 않는다 (제39항). let next_is_hangul_wrap = next_non_space_is_hangul_wrap_start(all_tokens, token_index); // 직전 비공백 토큰이 한글 종료표(⠸⠾)이면 이 토큰의 시작 문장부호도 @@ -430,15 +907,42 @@ fn emit_word( let has_ascii_alphabetic = meta.has_ascii_alphabetic; if word_chars.first().is_some_and(|ch| ch.is_ascii_digit()) - && let Some((numeric, unit, consumed)) = parse_numeric_ascii_unit_prefix(word_chars) + && !state.is_english + && let Some((numeric, mut unit, consumed)) = parse_numeric_ascii_unit_prefix(word_chars) && consumed == word_chars.len() { + let continues_roman_section = next_word_starts_roman_or_number(remaining_words); + if continues_roman_section && unit.last() == Some(&crate::unicode::decode_unicode('⠲')) + { + unit.pop(); + } let mut encoded = crate::encode(&numeric)?; encoded.extend(unit); result.extend(encoded); + state.is_english = continues_roman_section; + state.needs_english_continuation = false; return Ok(()); } + if starts_new_roman_section_after_closed_enclosure(all_tokens, token_index) { + state.needs_english_continuation = false; + } + + // Korean Rule 35 keeps a Roman-led alphanumeric chain in the same + // Roman section across whitespace (`MP4 Player`). While the final + // digit temporarily leaves `is_english` false, `roman_number_chain` + // records that the next Roman word is a continuation rather than a new + // Rule-37 entry word. + let roman_section_continues_from_previous_word = + state.is_english || state.roman_number_chain; + let starts_fresh_roman_section = !roman_section_continues_from_previous_word + && !state.needs_english_continuation + && has_ascii_alphabetic; + if starts_fresh_roman_section { + state.roman_section_is_english_context = + roman_section_has_english_phrase_context(all_tokens, token_index); + } + // English entry (제28/35/39항) — 로마자표/연속표 emit + 영어 모드 전환. roman_mode::enter_english_if_starting(state, word_chars, has_ascii_alphabetic, result); @@ -465,6 +969,10 @@ fn emit_word( CharType::Number(_) => { roman_mode::exit_english_for_roman_number_chain(state); } + CharType::MathSymbol('+') + if crate::rules::token_rules::math_expression::is_roman_plus_identifier( + word_chars, + ) => {} CharType::Symbol(sym) => { // 한글 wrap 직후의 첫 디지털 표기 기호(. / @ # _ : -)는 // 영어 컨텍스트의 연속으로 본다. 예) "www.대통령.kr"에서 @@ -488,9 +996,12 @@ fn emit_word( if prev_wrap_eng_continuation || next_wrap_eng_continuation + || (*sym == '&' + && spaced_ampersand_connects_roman_words(all_tokens, token_index)) || english_logic::should_render_symbol_as_english( state.english_indicator, state.is_english, + state.doc_summary.is_english_majority, &state.parenthesis_stack, *sym, word_chars, @@ -512,7 +1023,7 @@ fn emit_word( } else { roman_mode::exit_english( state, - english_logic::should_request_continuation(*sym), + *sym != ')' && english_logic::should_request_continuation(*sym), ); } } @@ -538,6 +1049,24 @@ fn emit_word( roman_mode::resume_english_from_roman_number_chain(state); } CharType::Number(_) => {} + CharType::MathSymbol('+') + if crate::rules::token_rules::math_expression::is_roman_plus_identifier( + word_chars, + ) => + { + roman_mode::resume_english_from_roman_number_chain(state); + } + CharType::Symbol(symbol) + if crate::rules::korean::rule_69::is_compatibility_unit_presentation( + *symbol, + ) || (*symbol == '-' + && word_chars + .get(i + 1) + .is_some_and(|next| next.is_ascii_alphanumeric())) + || (*symbol == '*' + && english_logic::is_attached_ascii_roman_asterisk( + word_chars, i, + )) => {} _ => { state.roman_number_chain = false; } @@ -563,6 +1092,7 @@ fn emit_word( is_all_uppercase, has_korean_char, ascii_starts_at_beginning, + roman_section_continues_from_previous_word, state, &mut skip_count, remaining_words, @@ -591,6 +1121,7 @@ fn emit_word( is_all_uppercase, has_korean_char, ascii_starts_at_beginning, + roman_section_continues_from_previous_word, state, &mut skip_count, remaining_words, @@ -634,8 +1165,13 @@ fn emit_word( || crate::symbol_shortcut::is_symbol_char(ch) || crate::utils::is_korean_char(ch)) }); + let starts_with_roman_letter = next_word + .chars() + .find(|ch| ch.is_ascii_alphabetic() || crate::utils::is_korean_char(*ch)) + .is_some_and(|ch| ch.is_ascii_alphabetic()); let is_single_letter_word = ascii_letters.len() == 1 && !next_word.chars().any(|ch| ch.is_ascii_digit()) + && starts_with_roman_letter && !has_invalid_symbol; if is_single_letter_word @@ -647,7 +1183,22 @@ fn emit_word( match next_type { CharType::English(_) | CharType::Number(_) => {} CharType::Symbol(sym) => { - if state.english_indicator + let separated_continuation = next_word_is_separated + && separated_symbol_continues_roman_section( + all_tokens, + token_index, + ); + // Rule 33/34 terminator omission applies when the + // punctuation is attached to the Roman run. If the + // print has whitespace first (`Poison (모래성)`), + // Rule 29 closes the Roman run before that space. + if next_word_is_separated && !separated_continuation { + result.push(50); + roman_mode::exit_english(state, false); + } else if separated_continuation && sym == '&' { + // A standalone ampersand joining Roman words is + // itself part of the current Roman section. + } else if state.english_indicator && state.is_english && english_logic::is_english_symbol(sym) { @@ -786,6 +1337,32 @@ mod tests { ); } + #[rstest::rstest] + #[case::capitalized_parenthetical("논문(Frontiers in Drug Delivery)에", "Frontiers", true)] + #[case::capitalized_unenclosed("Nuclear Week in Parliament에 참석했다.", "Nuclear", true)] + #[case::lowercase_enclosed("제목(plain words in context)이다.", "plain", true)] + #[case::rule_37_metalinguistic_list("be, his, was, were의 약자를 바르게 쓰시오.", "be,", false)] + #[case::single_roman_annotation("논문(Cell)이 발표됐다.", "논문(Cell)이", false)] + fn recognizes_structural_english_phrase_context( + #[case] input: &str, + #[case] first_roman_word: &str, + #[case] expected: bool, + ) { + let ir = DocumentIR::parse(input, true); + let start_index = ir + .tokens + .iter() + .position( + |token| matches!(token, Token::Word(word) if word.text.contains(first_roman_word)), + ) + .expect("test phrase must have a first Roman word"); + + assert_eq!( + roman_section_has_english_phrase_context(&ir.tokens, start_index), + expected + ); + } + // ── Step 1-3: Basic token tests ── /// `emit` 결과가 `encode()` 와 byte-identical 한지 (round-trip) 다양한 @@ -918,6 +1495,159 @@ mod tests { assert_eq!(out.iter().filter(|byte| **byte == 52).count(), 1); } + /// Rules 29 and 33: whitespace closes the Roman run before a following + /// parenthetical; Rule 34's omission is only for an attached enclosure. + #[rstest::rstest] + #[case::ordinary_word("Poison (모래성)", "⠝⠲⠀")] + #[case::mixed_roman_korean_gloss("그룹 TVB (Television - 전시광파유한공사)", "⠃⠲⠀")] + #[case::roman_number_chain("8PM (최초)", "⠍⠲⠀")] + fn spaced_parenthetical_follows_a_closed_roman_run( + #[case] input: &str, + #[case] expected_boundary: &str, + ) { + let actual = crate::encode_to_unicode(input).expect("input must encode"); + assert!( + actual.contains(expected_boundary), + "missing Rule 29 terminator at spaced boundary: {actual}" + ); + } + + /// Rules 29 and 34: an enclosure closes its own Roman item without a + /// terminator, but a following unenclosed Roman item starts a new section. + #[rstest::rstest] + #[case::capitalized_after_comma("가는 설명(ABC), Next 나다", "⠠⠴⠐⠀⠴⠠⠝")] + #[case::all_caps_after_parenthesis("가는 설명(ABC) XYZ 나다", "⠠⠴⠀⠴⠠⠠⠭")] + #[case::quoted_title_after_parenthesis("가는 설명(ABC) ‘Title’ 나다", "⠠⠴⠀⠠⠦⠴⠠⠞")] + #[case::multiword_parenthesis("가는 설명(Alpha Beta) XYZ 나다", "⠠⠴⠀⠴⠠⠠⠭")] + #[case::multiword_quote("가는 ‘Alpha Beta’, ‘Title’ 나다", "⠄⠐⠀⠠⠦⠴⠠⠞")] + #[case::mixed_quote("가는 ‘설명 ABC’, XYZ 나다", "⠴⠄⠐⠀⠴⠠⠠⠭")] + fn unenclosed_roman_after_closed_enclosure_starts_a_new_section( + #[case] input: &str, + #[case] expected_boundary: &str, + ) { + let actual = crate::encode_to_unicode(input).expect("input must encode"); + assert!( + actual.contains(expected_boundary), + "missing new Rule-29 Roman section: {actual}" + ); + } + + /// Rule 32's official sequence keeps the successively enclosed single + /// letters in one Roman section; `e` still takes its UEB grade-1 marker. + #[test] + fn successive_enclosed_single_letters_remain_one_roman_section() { + let actual = crate::encode_to_unicode("모음에는 (a), (e), (i)가 있다.") + .expect("official Rule-32 example must encode"); + + assert_eq!( + actual + .chars() + .filter(|cell| *cell == crate::unicode::encode_unicode(52)) + .count(), + 1 + ); + assert!(actual.contains("⠐⠣⠰⠑⠐⠜")); + } + + /// Rules 29, 34, and 35 keep a pure Roman enclosure or a following number + /// inside the active Roman section. + #[rstest::rstest] + #[case::pure_roman_expansion("기준 GRI (Global Reporting Initiative) Standards", "⠊⠲⠀")] + #[case::roman_parenthetical("노래 Back for More (with Anitta)", "⠍⠲⠀")] + #[case::number_continuation("대회 May Circuit '23에서", "⠞⠲⠀")] + #[case::ascii_ellipsis("머뭇거리며 Umm ...이라고 말했다", "⠍⠍⠲⠀")] + #[case::unicode_ellipsis("머뭇거리며 Umm …이라고 말했다", "⠍⠍⠲⠀")] + #[case::midline_ellipsis("머뭇거리며 Umm ⋯이라고 말했다", "⠍⠍⠲⠀")] + fn separated_roman_or_number_continuation_stays_in_the_section( + #[case] input: &str, + #[case] forbidden_boundary: &str, + ) { + let actual = crate::encode_to_unicode(input).expect("input must encode"); + assert!( + !actual.contains(forbidden_boundary), + "Roman section closed too early: {actual}" + ); + } + + /// Korean rules 29, 32, and 35: a standalone print colon between Roman + /// items is UEB punctuation inside one Roman section, even when spaces + /// surround it. + #[rstest::rstest] + #[case::capitalized_words("가 Alpha : Beta 나")] + #[case::uppercase_and_number("가 URL : 393 나")] + #[case::mixed_case_and_number("가 Id : 7 나")] + fn spaced_colon_between_roman_items_remains_inside_section(#[case] input: &str) { + let actual = crate::encode_to_unicode(input).expect("input must encode"); + assert!( + actual.contains("⠀⠒⠀"), + "colon was not rendered as UEB punctuation: {actual}" + ); + assert!( + !actual.contains("⠲⠀⠐⠂⠀⠴"), + "colon split one Roman section: {actual}" + ); + } + + /// UEB 3.1.1 and Korean rule 29: a spaced ampersand connecting Roman words + /// neither closes the section before itself nor starts a new one after it. + #[rstest::rstest] + #[case::official_name("가 Marks & Spencer 나")] + #[case::technical_phrase("가 3D Scan & Solution 나")] + #[case::attached_right_word("가 EV Mining &Development 나")] + fn spaced_ampersand_bridges_one_roman_section(#[case] input: &str) { + let ir = DocumentIR::parse(input, true); + let ampersand_index = ir + .tokens + .iter() + .position( + |token| matches!(token, Token::Word(word) if word.chars.first() == Some(&'&')), + ) + .expect("ampersand token"); + assert!( + spaced_ampersand_connects_roman_words(&ir.tokens, ampersand_index), + "test input must contain a structurally Roman ampersand" + ); + + let actual = crate::encode_to_unicode(input).expect("Roman phrase must encode"); + assert!(actual.contains("⠈⠯"), "ampersand missing: {actual}"); + assert!( + !actual.contains("⠲⠀⠈⠯") && !actual.contains("⠈⠯⠀⠴"), + "ampersand split the Roman section: {actual}" + ); + } + + /// Rule 29: a Korean word containing one embedded Roman letter is not a + /// standalone one-letter Roman continuation. + #[rstest::rstest] + #[case::parenthesized_letter("KODEX 골드선물(H)", "⠭⠲⠀")] + #[case::korean_word_with_letter("ABB FIA 포뮬러E", "⠁⠲⠀")] + #[case::following_model_name("SUV 모델X", "⠧⠲⠀")] + fn korean_word_with_one_roman_letter_closes_the_previous_section( + #[case] input: &str, + #[case] expected_boundary: &str, + ) { + let actual = crate::encode_to_unicode(input).expect("input must encode"); + assert!( + actual.contains(expected_boundary), + "missing Roman terminator before Korean word: {actual}" + ); + } + + /// Rule 29: a separated one-letter Roman name remains part of the same + /// Roman section when it starts the next print word. A directly attached + /// Korean suffix or gloss does not change that Roman-first boundary. + #[rstest::rstest] + #[case::korean_particle("Global X가")] + #[case::korean_classifier("WBC B조")] + #[case::korean_gloss("DAY6 Young K(영케이)")] + fn roman_initial_single_letter_with_korean_suffix_continues_section(#[case] input: &str) { + let actual = crate::encode_to_unicode(input).expect("input must encode"); + assert!( + actual.contains("⠀⠰"), + "separated Roman initial did not continue the active section: {actual}" + ); + } + /// Korean rule 35 PDF example: numbers do not split a roman section, so /// the later capital word resumes without another roman indicator. #[test] diff --git a/libs/braillify/src/rules/engine.rs b/libs/braillify/src/rules/engine.rs index 96f0f5f6..53567614 100644 --- a/libs/braillify/src/rules/engine.rs +++ b/libs/braillify/src/rules/engine.rs @@ -204,6 +204,7 @@ mod tests { has_korean_char: true, is_all_uppercase: false, ascii_starts_at_beginning: false, + roman_section_continues_from_previous_word: false, skip_count: &mut skip, state: &mut state, result: &mut result, @@ -421,6 +422,7 @@ mod tests { has_korean_char: false, is_all_uppercase: false, ascii_starts_at_beginning: false, + roman_section_continues_from_previous_word: false, skip_count: &mut skip, state: &mut state, result: &mut result, @@ -455,6 +457,7 @@ mod tests { has_korean_char: false, is_all_uppercase: false, ascii_starts_at_beginning: false, + roman_section_continues_from_previous_word: false, skip_count: &mut skip, state: &mut state, result: &mut result, diff --git a/libs/braillify/src/rules/english_shortform.rs b/libs/braillify/src/rules/english_shortform.rs index 24bb740d..c6c6d87b 100644 --- a/libs/braillify/src/rules/english_shortform.rs +++ b/libs/braillify/src/rules/english_shortform.rs @@ -1,147 +1,120 @@ //! English shortform collision detection (UEB 5.7.2 + 10.9). //! -//! When an all-uppercase ASCII word is point-encoded as `⠠⠠xy...`, the trailing -//! cells are identical to the corresponding lowercase shortform abbreviation. To -//! prevent the contraction reading (e.g. `⠠⠠⠉⠙` could otherwise be read as the -//! capitalised word "COULD"), the Grade-1 indicator (`⠰`) must be inserted before -//! the capital indicator. +//! When an all-uppercase ASCII letters-sequence is point-encoded as +//! `⠠⠠xy...`, its cells can be identical to a shortform or to the beginning of +//! a longer word containing one. To prevent that reading (for example `CD` as +//! "could", or the official `LLC` as "little" + `c`), the Grade-1 indicator +//! (`⠰`) must be inserted before the capital indicator. //! //! Reference: 통일영어점자 규정 제3판 //! - §5.7.2: 약자(축어 포함)와의 혼동 방지를 위한 1급 점자 모드 -//! - §10.9: 축어(shortform) 목록 (부록 1) -//! -//! Only **pure-letter** shortforms (whose braille cells map one-to-one to a-z) -//! can collide. Shortforms that embed contractions like `ch` (⠡), `sh` (⠩), `st` -//! (⠌), `th` (⠹), `ou` (⠳), or `con` (⠒) are NOT pure-letter, so their uppercase -//! acronyms (e.g. "MCH" → ⠠⠠⠍⠉⠓) cannot be confused with the shortform reading -//! and do not require the Grade-1 indicator. - -use std::collections::HashSet; -use std::sync::OnceLock; - -/// All pure-letter multi-letter shortforms from UEB Appendix 1 (lowercase form). -/// These cause collision with all-uppercase acronyms of the same letters. -const PURE_LETTER_SHORTFORMS: &[&str] = &[ - // a-series (10.9: about, above, according, ...) - "ab", "abv", "ac", "acr", "af", "afn", "afw", "ag", "al", "alm", "alr", "alt", - "alw", // b-series (10.9: because, before, behind, below, ...) - "bc", "bf", "bh", "bl", "bn", "brl", "bs", "bt", "by", // c-series - "cd", // could - // d-series - "dcl", "dclg", "dcv", "dcvg", // e-series - "ei", // either - // f-series - "fri", "fst", // g-series - "gd", "grt", // h-series - "hm", "hmf", "hrf", // i-series - "imm", // l-series - "ll", "lr", // m-series - "myf", // n-series - "nec", "nei", // p-series - "pd", "perh", // q-series - "qk", // r-series - "rcv", "rcvg", "rjc", "rjcg", // s-series - "sd", // t-series - "td", "tgr", "tm", "tn", // w-series - "wd", // x-series - "xf", "xs", // y-series - "yr", "yrf", "yrvs", -]; +//! - §10.9: 축어(shortform) 목록과 10.9.2-10.9.5의 긴 단어 조건 -fn shortform_set() -> &'static HashSet<&'static str> { - static CACHE: OnceLock> = OnceLock::new(); - CACHE.get_or_init(|| PURE_LETTER_SHORTFORMS.iter().copied().collect()) -} - -/// Returns `true` if the given ASCII word (already verified all-uppercase) collides -/// with a multi-letter shortform when emitted as `⠠⠠letters`. The Grade-1 indicator -/// `⠰` must be inserted before the CapsWord/CapsPassage marker in that case. +/// Returns `true` if the given initial ASCII letters-sequence collides with a +/// shortform under UEB 10.9.7 or with a permitted longer shortform reading under +/// 10.9.8. The Grade-1 indicator `⠰` must precede the capitalization marker. /// /// Single-letter words are excluded (UEB §10.1 single-letter alphabetic word signs /// require their own "독립적으로 사용된 경우" analysis handled elsewhere). pub fn requires_grade1_indicator(uppercase_word: &str) -> bool { - if uppercase_word.len() < 2 { - return false; - } - if !uppercase_word.chars().all(|c| c.is_ascii_alphabetic()) { - return false; + super::english_ueb::rule_10_9::requires_grade1_at_word_start(uppercase_word) +} + +/// UEB 2.6.1-2.6.3 boundary after a letters-sequence. +/// +/// A grade-1 symbol used for shortform disambiguation is valid only when the +/// letters-sequence is standing alone (10.9.7), or is the initial sequence of a +/// longer alphabetic word (10.9.8). Callers use this after an all-capitals ASCII +/// run, so any nonletter suffix must satisfy the standing-alone boundary. A +/// Korean syllable starts the next code span and is likewise a hard boundary for +/// the embedded Roman sequence. Digits, slash, plus, and an opening grouping +/// sign are deliberately excluded (`CD47`, `CD/ATM`, `NEIS+`, `LLM(SLM)`). +pub fn permits_grade1_boundary_after_run(suffix: &[char]) -> bool { + for &ch in suffix { + // UEB 2.6.1 makes a hyphen or dash a boundary in its own right. Do + // not scan through it into the next segment: the official `CD-ROM` + // requires grade 1 for `CD` even though another Roman segment follows. + if matches!( + ch, + '-' | '\u{2010}' | '\u{2011}' | '\u{2012}' | '\u{2013}' | '\u{2014}' + ) { + return true; + } + if matches!(ch as u32, 0x3131..=0x3163 | 0xAC00..=0xD7A3) + || matches!(ch, '\u{00b7}' | '\u{30fb}') + { + return true; + } + if !matches!( + ch, + ',' | ';' + | ':' + | '.' + | '\u{2026}' + | '!' + | '?' + | ')' + | ']' + | '}' + | '\'' + | '"' + | '\u{2019}' + | '\u{201d}' + ) { + return false; + } } - let lowered = uppercase_word.to_ascii_lowercase(); - shortform_set().contains(lowered.as_str()) + true } #[cfg(test)] mod tests { use super::*; - #[test] - fn cd_collides_with_could() { - assert!(requires_grade1_indicator("CD")); + #[rstest::rstest] + #[case::empty_boundary("", true)] + #[case::official_cd_rom_boundary("-ROM", true)] + #[case::closing_then_korean(")은", true)] + #[case::adjacent_digit("47", false)] + #[case::slash_continuation("/ATM", false)] + #[case::plus_continuation("+", false)] + #[case::opening_group("(SLM)", false)] + #[case::comma_before_attached_letters(",ABC", false)] + fn grade1_boundary_follows_ueb_standing_alone_rules( + #[case] suffix: &str, + #[case] expected: bool, + ) { + assert_eq!( + permits_grade1_boundary_after_run(&suffix.chars().collect::>()), + expected + ); } - #[test] - fn hm_collides_with_him() { - assert!(requires_grade1_indicator("HM")); - } - - #[test] - fn td_collides_with_today() { - assert!(requires_grade1_indicator("TD")); - } - - #[test] - fn wd_collides_with_would() { - assert!(requires_grade1_indicator("WD")); - } - - #[test] - fn lp_does_not_collide() { - // L = like, P = people are single-letter alphabetic wordsigns; - // their concatenation is not a multi-letter shortform. - assert!(!requires_grade1_indicator("LP")); - } - - #[test] - fn kbs_does_not_collide() { - assert!(!requires_grade1_indicator("KBS")); - } - - #[test] - fn mp_does_not_collide() { - assert!(!requires_grade1_indicator("MP")); - } - - #[test] - fn tv_does_not_collide() { - assert!(!requires_grade1_indicator("TV")); - } - - #[test] - fn sns_does_not_collide() { - assert!(!requires_grade1_indicator("SNS")); - } - - #[test] - fn single_letter_excluded() { - assert!(!requires_grade1_indicator("C")); - assert!(!requires_grade1_indicator("A")); - } - - #[test] - fn non_ascii_excluded() { - assert!(!requires_grade1_indicator("É")); - assert!(!requires_grade1_indicator("C1")); - } - - #[test] - fn case_insensitive_input() { - // Function expects already-uppercase but should still match if lowercase given. - assert!(requires_grade1_indicator("cd")); + #[rstest::rstest] + #[case::complete_cd("CD", true)] + #[case::complete_hm("HM", true)] + #[case::groupsign_fst("FST", true)] + #[case::groupsign_shd("SHD", true)] + #[case::official_llc_prefix("LLC", true)] + #[case::good_prefix("GDP", true)] + #[case::added_s("SDS", true)] + #[case::because_needs_be("BC", false)] + #[case::about_unlisted_suffix("ABBA", false)] + #[case::little_before_vowel("LLAMA", false)] + #[case::plain_initialism("KBS", false)] + #[case::single_letter("C", false)] + #[case::non_ascii("É", false)] + #[case::alphanumeric("C1", false)] + fn detects_complete_and_word_initial_shortform_confusion( + #[case] input: &str, + #[case] expected: bool, + ) { + assert_eq!(requires_grade1_indicator(input), expected); } #[test] - fn runtime_shortform_lookup_uses_lowercase_key() { - let word = std::hint::black_box("CD"); + fn case_insensitive_runtime_input() { + let word = std::hint::black_box("cd"); assert!(requires_grade1_indicator(word)); } diff --git a/libs/braillify/src/rules/english_ueb/engine.rs b/libs/braillify/src/rules/english_ueb/engine.rs index 3b5ee3da..f7602109 100644 --- a/libs/braillify/src/rules/english_ueb/engine.rs +++ b/libs/braillify/src/rules/english_ueb/engine.rs @@ -195,12 +195,17 @@ impl EnglishUebEngine { /// Encode one Roman word embedded in Korean text according to Korean rule 37. /// - /// At a rule-37 Roman entry, whole-word signs/shortforms are suppressed while - /// UEB multi-letter groupsigns remain. Subsequent standalone words and a - /// rule-39 return to English-dominant context may use wordsigns. Keeping both - /// paths on the same contraction engine makes rule 10 preference and - /// morphology gates identical. Roman mode transitions remain the Korean - /// engine's job. + /// At a rule-37 Roman entry, whole-word signs and shortforms are suppressed + /// while UEB multi-letter groupsigns remain. The restriction applies only to + /// the English word immediately preceded by the Roman indicator, so later + /// standalone words in that section (and a rule-39 return to English-dominant + /// context) use ordinary UEB wordsigns and shortforms. Keeping both paths on + /// the same contraction engine makes rule 10 preference and morphology gates + /// identical. Roman mode transitions remain the Korean engine's job. + #[expect( + clippy::too_many_arguments, + reason = "the independent UEB context flags mirror distinct rule gates" + )] pub(crate) fn encode_korean_word( &self, chars: &[char], @@ -209,12 +214,50 @@ impl EnglishUebEngine { standing_alone: bool, word_initial: bool, digit_adjacent: bool, + numeric_grade1_active: bool, + apostrophe_joined_lexeme: bool, ) -> Option> { let mut out = Vec::new(); if prepend_grade1_indicator { out.push(GRADE1); } + let lower: Vec = chars.iter().flat_map(|ch| ch.to_lowercase()).collect(); + + // UEB 5.6.1-5.6.2 and 6.5.3: a numeric indicator establishes grade-1 + // mode through a resumed Roman letters-sequence, so no contraction may + // follow an internal number (`Kep1er`). A number-first Korean token + // instead inserts rule 29's Roman indicator before its letters. In that + // latter shape, UEB 10.4.2 still spells a complete `ch/sh/th/wh/ou/st` + // sequence because its one-cell groupsign would be read as a word. + let complete_strong_sequence_would_be_word = digit_adjacent + && !word_initial + && matches!( + lower.as_slice(), + ['c', 'h'] | ['s', 'h'] | ['t', 'h'] | ['w', 'h'] | ['o', 'u'] | ['s', 't'] + ); + if numeric_grade1_active || complete_strong_sequence_would_be_word { + match classify_caps(chars) { + _ if suppress_caps => {} + Some(Caps::None) => {} + Some(Caps::Single) => out.push(CAPITAL), + Some(Caps::Word) => out.extend([CAPITAL, CAPITAL]), + None => { + for &ch in chars { + if ch.is_ascii_uppercase() { + out.push(CAPITAL); + } + out.push(crate::english::encode_english(ch.to_ascii_lowercase()).ok()?); + } + return Some(out); + } + } + for &ch in chars { + out.push(crate::english::encode_english(ch.to_ascii_lowercase()).ok()?); + } + return Some(out); + } + let lower_word: String = lower.iter().collect(); if !standing_alone && super::rule_10_5::wordsign(&lower_word).is_some() { if !suppress_caps { @@ -231,19 +274,20 @@ impl EnglishUebEngine { )?); return Some(out); } - self.encode_word( + self.encode_word_with_apostrophe_lexeme( chars, WordContext { standing_alone, upper_usable: standing_alone, - shortform_usable: false, - allow_longer_shortforms: false, + shortform_usable: standing_alone, + allow_longer_shortforms: standing_alone, lower_usable: standing_alone, suppress_caps, word_initial, restricted_prefix_boundary: word_initial, digit_adjacent, }, + apostrophe_joined_lexeme, &mut out, )?; Some(out) @@ -866,7 +910,7 @@ mod test_support { ) { let chars = input.chars().collect::>(); let encoded = EnglishUebEngine::new() - .encode_korean_word(&chars, false, false, false, true, false) + .encode_korean_word(&chars, false, false, false, true, false, false, false) .expect("ASCII Roman word must encode"); assert_eq!( @@ -875,6 +919,69 @@ mod test_support { ); } + /// Korean rule 37 suppresses contractions only in the English word directly + /// preceded by the Roman indicator. Later standalone words use ordinary UEB + /// shortforms (UEB 10.9). + #[rstest::rstest] + #[case::good("good", "⠛⠙")] + #[case::little("little", "⠇⠇")] + #[case::today("today", "⠞⠙")] + fn korean_roman_section_continuation_uses_shortforms( + #[case] input: &str, + #[case] expected: &str, + ) { + let chars = input.chars().collect::>(); + let encoded = EnglishUebEngine::new() + .encode_korean_word(&chars, false, false, true, true, false, false, false) + .expect("ASCII Roman word must encode"); + + assert_eq!(encoded, cells(expected)); + } + + #[test] + fn korean_rule_37_entry_still_suppresses_shortform() { + let chars = "good".chars().collect::>(); + let encoded = EnglishUebEngine::new() + .encode_korean_word(&chars, false, false, false, true, false, false, false) + .expect("ASCII Roman word must encode"); + + assert_eq!(encoded, cells("⠛⠕⠕⠙")); + } + + #[rstest::rstest] + #[case::ordinal_st("st", "⠎⠞")] + #[case::ordinal_th("th", "⠞⠓")] + #[case::unit_year("yr", "⠽⠗")] + #[case::capital_ordinal("ST", "⠠⠠⠎⠞")] + fn numeric_grade1_mode_spells_korean_context_letters( + #[case] input: &str, + #[case] expected: &str, + ) { + let chars = input.chars().collect::>(); + let encoded = EnglishUebEngine::new() + .encode_korean_word(&chars, false, false, false, false, true, true, false) + .expect("ASCII Roman suffix must encode"); + + assert_eq!(encoded, cells(expected)); + } + + #[rstest::rstest] + #[case::still_wordsign_collision("st", "⠎⠞")] + #[case::out_wordsign_collision("ou", "⠕⠥")] + #[case::er_is_not_a_wordsign("er", "⠻")] + #[case::gh_is_not_a_wordsign("gh", "⠣")] + fn number_first_roman_entry_applies_strong_groupsign_word_collision( + #[case] input: &str, + #[case] expected: &str, + ) { + let chars = input.chars().collect::>(); + let encoded = EnglishUebEngine::new() + .encode_korean_word(&chars, false, false, false, false, true, false, false) + .expect("ASCII Roman suffix must encode"); + + assert_eq!(encoded, cells(expected)); + } + pub(super) fn enc(text: &str) -> Option> { super::super::try_encode(text) } diff --git a/libs/braillify/src/rules/english_ueb/engine/caps.rs b/libs/braillify/src/rules/english_ueb/engine/caps.rs index dd2926f4..b85edce1 100644 --- a/libs/braillify/src/rules/english_ueb/engine/caps.rs +++ b/libs/braillify/src/rules/english_ueb/engine/caps.rs @@ -81,6 +81,10 @@ pub(super) fn chemical_formula_caps(chars: &[char]) -> bool { chars.len() >= 2 && !matches!(chars, ['C', 'O']) && chars.iter().all(|c| matches!(c, 'C' | 'H' | 'O')) + // A repeated element is written with a subscript in chemical notation + // (`H₂O`, `CO₂`), not by repeating its capital letter. Thus corporate + // initialisms such as `COO`/`CCO` must remain ordinary capitals words. + && !chars.windows(2).any(|pair| pair[0] == pair[1]) } pub(super) fn encode_letters_literal(chars: &[char]) -> Option> { @@ -549,7 +553,7 @@ pub(super) fn is_letter_pronounced_initialism(chars: &[char]) -> bool { matches!( word.as_str(), "WHO" | "OED" | "US" | "IT" | "MSH" | "DAR" | "EST" | "TEN" | "POW" | "FRS" - ) + ) || super::super::pronunciation::cmudict::has_unambiguous_letter_name_pronunciation(chars) } /// §8.6.3 vs §8.8.2 dispatch: whether a lowercase tail after a capitals-word run @@ -867,6 +871,19 @@ mod tests { assert_eq!(enc(text), Some(cells(expected))); } + #[rstest::rstest] + #[case::hydroxide(&['O', 'H'], true)] + #[case::three_distinct_elements(&['C', 'H', 'O'], true)] + #[case::company_co(&['C', 'O'], false)] + #[case::chief_operating_officer(&['C', 'O', 'O'], false)] + #[case::chief_commercial_officer(&['C', 'C', 'O'], false)] + fn identifies_unsubscripted_chemical_capital_runs( + #[case] chars: &[char], + #[case] expected: bool, + ) { + assert_eq!(chemical_formula_caps(chars), expected); + } + /// §15.3.2: in level-change tone notation, an up/down-step arrow printed before /// a word is followed by a braille space and the under-word bullet indicator. /// The tone reading needs a tone-notation context (several level arrows in the diff --git a/libs/braillify/src/rules/english_ueb/engine/encode_word.rs b/libs/braillify/src/rules/english_ueb/engine/encode_word.rs index c8f6dfad..6b67ebd3 100644 --- a/libs/braillify/src/rules/english_ueb/engine/encode_word.rs +++ b/libs/braillify/src/rules/english_ueb/engine/encode_word.rs @@ -651,7 +651,7 @@ macro_rules! encode_word_arm { { lower_usable = true; } - $engine.encode_word( + $engine.encode_word_with_apostrophe_lexeme( $chars, WordContext { standing_alone, @@ -675,6 +675,7 @@ macro_rules! encode_word_arm { digit_adjacent: matches!(prev, Some(EnglishToken::Number(_))) || matches!(next, Some(EnglishToken::Number(_))), }, + apostrophe_joined_recorded_token_word($tokens, $i), &mut $out, )?; $prev_was_number = false; diff --git a/libs/braillify/src/rules/english_ueb/engine/tokens.rs b/libs/braillify/src/rules/english_ueb/engine/tokens.rs index 685956f7..5fe6e059 100644 --- a/libs/braillify/src/rules/english_ueb/engine/tokens.rs +++ b/libs/braillify/src/rules/english_ueb/engine/tokens.rs @@ -124,6 +124,53 @@ pub(super) fn token_plain_chars(tokens: &[EnglishToken]) -> Vec { chars } +/// Whether the word token at `i` belongs to an apostrophe-separated sequence +/// whose elided spelling is a recorded lexical word (`O'PENing` → `opening`). +/// This is pronunciation evidence for §10.12.1: the capital run is part of a +/// spoken word rather than a sequence of separately pronounced initials. +pub(super) fn apostrophe_joined_recorded_token_word(tokens: &[EnglishToken], i: usize) -> bool { + let Some(EnglishToken::Word(current)) = tokens.get(i) else { + return false; + }; + + let apostrophe_at = |index: usize| { + matches!( + tokens.get(index), + Some(EnglishToken::Symbol('\'' | '\u{2019}')) + ) + }; + let word_at = |index: usize| matches!(tokens.get(index), Some(EnglishToken::Word(_))); + + let mut start = i; + while start >= 2 && apostrophe_at(start - 1) && word_at(start - 2) { + start -= 2; + } + let mut end = i; + while end + 2 < tokens.len() && apostrophe_at(end + 1) && word_at(end + 2) { + end += 2; + } + if start == end { + return false; + } + + let mut joined = Vec::new(); + let mut run_start = 0usize; + for (index, token) in tokens.iter().enumerate().take(end + 1).skip(start) { + match token { + EnglishToken::Word(chars) => { + if index == i { + run_start = joined.len(); + } + joined.extend(chars); + } + EnglishToken::Symbol(ch @ ('\'' | '\u{2019}')) => joined.push(*ch), + _ => return false, + } + } + let run_end = run_start + current.len(); + super::super::pronunciation::apostrophe_elided_recorded_word_at(&joined, run_start, run_end) +} + pub(super) fn token_plain_chars_preserve_word_division(tokens: &[EnglishToken]) -> Vec { let mut chars = Vec::new(); for token in tokens { diff --git a/libs/braillify/src/rules/english_ueb/engine/word_methods.rs b/libs/braillify/src/rules/english_ueb/engine/word_methods.rs index cab42b83..6c8b899a 100644 --- a/libs/braillify/src/rules/english_ueb/engine/word_methods.rs +++ b/libs/braillify/src/rules/english_ueb/engine/word_methods.rs @@ -6,6 +6,19 @@ impl EnglishUebEngine { chars: &[char], ctx: WordContext, out: &mut Vec, + ) -> Option<()> { + self.encode_word_with_apostrophe_lexeme(chars, ctx, false, out) + } + + /// Encode a word with evidence that an adjacent apostrophe-separated run + /// reconstructs one lexical word. Only the mixed-case capitals prefix uses + /// this evidence; ordinary words follow [`Self::encode_word`] unchanged. + pub(super) fn encode_word_with_apostrophe_lexeme( + &self, + chars: &[char], + ctx: WordContext, + apostrophe_joined_lexeme: bool, + out: &mut Vec, ) -> Option<()> { let WordContext { standing_alone, @@ -63,7 +76,12 @@ impl EnglishUebEngine { return Some(()); } if !suppress_caps && classify_caps(chars).is_none() { - return self.encode_mixed_case(chars, allow_longer_shortforms, out); + return self.encode_mixed_case( + chars, + allow_longer_shortforms, + apostrophe_joined_lexeme, + out, + ); } if shortform_usable && super::super::rule_10_9::is_pure_shortform_abbreviation(&word) { out.push(GRADE1); @@ -186,6 +204,7 @@ impl EnglishUebEngine { &self, chars: &[char], allow_longer_shortforms: bool, + apostrophe_joined_lexeme: bool, out: &mut Vec, ) -> Option<()> { if allow_longer_shortforms && let Some(boundary) = initial_caps_shortform_boundary(chars) { @@ -194,7 +213,12 @@ impl EnglishUebEngine { out.extend([CAPITAL, CAPITAL]); out.extend(cells); out.extend([CAPITAL, decode_unicode('⠄')]); - self.encode_mixed_case(&chars[boundary..], allow_longer_shortforms, out)?; + self.encode_mixed_case( + &chars[boundary..], + allow_longer_shortforms, + apostrophe_joined_lexeme, + out, + )?; return Some(()); } let camel_subunit_start = camel_title_subunit_after_caps_prefix(chars); @@ -214,7 +238,12 @@ impl EnglishUebEngine { allow_longer_shortforms, )?, ); - self.encode_mixed_case(&chars[subunit_start..], allow_longer_shortforms, out)?; + self.encode_mixed_case( + &chars[subunit_start..], + allow_longer_shortforms, + apostrophe_joined_lexeme, + out, + )?; return Some(()); } let initial_caps = chars.iter().take_while(|c| c.is_uppercase()).count(); @@ -355,8 +384,28 @@ impl EnglishUebEngine { return Some(()); } out.extend([CAPITAL, CAPITAL]); - for c in &chars[..initial_caps] { - out.push(crate::english::encode_english(c.to_ascii_lowercase()).ok()?); + if apostrophe_joined_lexeme { + // §10.6.8: `en`/`in` and the other permitted groupsigns remain + // available inside capital mode when the letters belong to an + // ordinarily pronounced word. §10.12.1 initialisms retain the + // literal path because they have no lexical-word evidence. + let lower_prefix: Vec = chars[..initial_caps] + .iter() + .map(|c| c.to_ascii_lowercase()) + .collect(); + out.extend( + super::super::rule_10_9::encode_with_optional_longer_shortforms( + &lower_prefix, + &self.contractions, + false, + false, + false, + )?, + ); + } else { + for c in &chars[..initial_caps] { + out.push(crate::english::encode_english(c.to_ascii_lowercase()).ok()?); + } } out.extend([CAPITAL, decode_unicode('⠄')]); let lower: Vec = suffix.iter().flat_map(|c| c.to_lowercase()).collect(); @@ -513,6 +562,10 @@ mod tests { #[case::bachelor_science("BSc", "⠠⠃⠠⠎⠉")] #[case::megahertz("MHz", "⠠⠍⠠⠓⠵")] #[case::potassium_chloride("KCl", "⠠⠅⠠⠉⠇")] + // §10.6.8/§10.12.1: the apostrophe-elided spelling is the recorded word + // `opening`, so `PEN` is a capitalised word segment, not initials, and keeps + // the `en` groupsign inside capitals-word mode. + #[case::apostrophe_joined_lexeme("O'PENing", "⠠⠕⠄⠠⠠⠏⠢⠠⠄⠬")] #[case::chemical_subscript("HOCH₂", "⠠⠓⠠⠕⠠⠉⠠⠓⠰⠢⠼⠃")] fn encodes_mixed_case_words_8_2(#[case] text: &str, #[case] expected: &str) { assert_eq!(enc(text), Some(cells(expected))); @@ -800,6 +853,7 @@ mod tests { .encode_mixed_case( &['f', 'o', 'u', 'n', 'D', 'A', 't', 'i', 'o', 'n'], true, + false, &mut out, ) .unwrap(); @@ -814,6 +868,7 @@ mod tests { .encode_mixed_case( &['f', 'o', 'u', 'n', 'D', 'A', 't', 'i', 'o', 'n'], true, + false, &mut out, ) .unwrap(); diff --git a/libs/braillify/src/rules/english_ueb/engine/words.rs b/libs/braillify/src/rules/english_ueb/engine/words.rs index 2a0ebbeb..d5427a97 100644 --- a/libs/braillify/src/rules/english_ueb/engine/words.rs +++ b/libs/braillify/src/rules/english_ueb/engine/words.rs @@ -674,10 +674,10 @@ mod tests { #[test] fn encodes_standalone_shortform_collision_with_grade1() { // §8.7: an all-caps word that collides with a multi-letter shortform yet - // is NOT itself a pure shortform abbreviation (`BC` shares letters with - // the `bc`="because" wordsign) takes a grade-1 indicator before the caps - // marker so it reads as literal letters. - let out = enc("BC").expect("should encode"); + // is not itself a complete shortform abbreviation (`LLC` begins with the + // `ll`="little" shortform) takes a grade-1 indicator before the caps marker + // so it reads as literal letters. UEB 10.9.8 gives `LLC` as the example. + let out = enc("LLC").expect("should encode"); assert_eq!(out.first(), Some(&GRADE1)); } } diff --git a/libs/braillify/src/rules/english_ueb/pronunciation/cmudict.rs b/libs/braillify/src/rules/english_ueb/pronunciation/cmudict.rs index de33fefb..791652fc 100644 --- a/libs/braillify/src/rules/english_ueb/pronunciation/cmudict.rs +++ b/libs/braillify/src/rules/english_ueb/pronunciation/cmudict.rs @@ -45,6 +45,76 @@ pub fn is_recorded_word(word: &str) -> bool { INDEX.contains_key(word) } +/// Whether CMUdict supplies sufficiently specific evidence that an uppercase +/// abbreviation is pronounced as letter names. +/// +/// UEB §10.12.1 suppresses contractions when an abbreviation is pronounced as +/// letters. Case-folded dictionary headwords alone cannot establish that +/// (`LED` otherwise collides with lexical *led*), so compare a pronunciation +/// variant against the concatenated ARPABET names of the printed capitals. An +/// For three or more letters, every recorded pronunciation must be the exact +/// letter-name sequence; this keeps word-pronounced acronyms such as `ASEAN` +/// on UEB's contract-when-uncertain fallback when the dictionary records both +/// readings. For a two-capital abbreviation, an exact two-letter reading is +/// already specific evidence for the printed abbreviation even when the same +/// case-folded headword also has a one-word homograph (`AI` versus *ai*). +/// Unknown abbreviations and longer mixed-pronunciation entries return false. +pub fn has_unambiguous_letter_name_pronunciation(chars: &[char]) -> bool { + if chars.len() < 2 || !chars.iter().all(|ch| ch.is_ascii_uppercase()) { + return false; + } + + fn letter_phones(letter: char) -> Option<&'static [&'static str]> { + Some(match letter { + 'A' => &["EY"], + 'B' => &["B", "IY"], + 'C' => &["S", "IY"], + 'D' => &["D", "IY"], + 'E' => &["IY"], + 'F' => &["EH", "F"], + 'G' => &["JH", "IY"], + 'H' => &["EY", "CH"], + 'I' => &["AY"], + 'J' => &["JH", "EY"], + 'K' => &["K", "EY"], + 'L' => &["EH", "L"], + 'M' => &["EH", "M"], + 'N' => &["EH", "N"], + 'O' => &["OW"], + 'P' => &["P", "IY"], + 'Q' => &["K", "Y", "UW"], + 'R' => &["AA", "R"], + 'S' => &["EH", "S"], + 'T' => &["T", "IY"], + 'U' => &["Y", "UW"], + 'V' => &["V", "IY"], + 'W' => &["D", "AH", "B", "AH", "L", "Y", "UW"], + 'X' => &["EH", "K", "S"], + 'Y' => &["W", "AY"], + 'Z' => &["Z", "IY"], + _ => return None, + }) + } + + let expected: Vec<&str> = chars + .iter() + .flat_map(|letter| letter_phones(*letter).unwrap_or_default()) + .copied() + .collect(); + let key: String = chars.iter().map(|ch| ch.to_ascii_lowercase()).collect(); + INDEX.get(key.as_str()).is_some_and(|variants| { + let is_letter_name_variant = |variant: &&str| { + variant + .split_whitespace() + .map(|phone| phone.trim_end_matches(['0', '1', '2'])) + .eq(expected.iter().copied()) + }; + !variants.is_empty() + && (variants.iter().all(is_letter_name_variant) + || (chars.len() == 2 && variants.iter().any(is_letter_name_variant))) + }) +} + /// Looks up ARPABET pronunciations from the embedded CMUdict. pub struct CmuDictProvider; @@ -136,4 +206,18 @@ mod tests { // A head with no phones → None. assert_eq!(parse_cmudict_line("word "), None); } + + #[rstest::rstest] + #[case::ged_is_ambiguous("GED", false)] + #[case::ai_two_letter_abbreviation("AI", true)] + #[case::cc_two_letter_abbreviation("CC", true)] + #[case::asean_mixed_pronunciation("ASEAN", false)] + #[case::ofc_initialism("OFC", true)] + #[case::lexical_led_only("LED", false)] + #[case::unknown_mou("MOU", false)] + #[case::lowercase_is_not_capitals("ged", false)] + fn detects_unambiguous_letter_name_pronunciations(#[case] text: &str, #[case] expected: bool) { + let chars: Vec = text.chars().collect(); + assert_eq!(has_unambiguous_letter_name_pronunciation(&chars), expected); + } } diff --git a/libs/braillify/src/rules/english_ueb/pronunciation/mod.rs b/libs/braillify/src/rules/english_ueb/pronunciation/mod.rs index 6a190532..6cf301bf 100644 --- a/libs/braillify/src/rules/english_ueb/pronunciation/mod.rs +++ b/libs/braillify/src/rules/english_ueb/pronunciation/mod.rs @@ -15,6 +15,65 @@ pub mod aligner; pub mod classifier; pub mod cmudict; +/// Decide whether an apostrophe-separated print sequence is one recorded +/// lexical word when the apostrophe is elided. +/// +/// UEB §10.12.1 suppresses contractions when capitals are letters pronounced +/// separately, while §10.6.8 retains `en`/`in` inside an ordinarily pronounced +/// word. A stylised spelling such as `O'PENing` is split into two parser runs, +/// so the case pattern of `PENing` alone cannot distinguish those situations. +/// Requiring the complete adjacent sequence (`opening`) to be in CMUdict gives +/// pronunciation evidence without recognising any particular corpus phrase. +/// The caller supplies the current ASCII run so unrelated quote punctuation is +/// never absorbed into the lookup. +pub(crate) fn apostrophe_elided_recorded_word_at( + chars: &[char], + run_start: usize, + run_end: usize, +) -> bool { + if run_start >= run_end + || run_end > chars.len() + || !chars[run_start..run_end] + .iter() + .all(|ch| ch.is_ascii_alphabetic()) + { + return false; + } + + let is_apostrophe = |ch: char| matches!(ch, '\'' | '\u{2019}'); + let is_member = |ch: char| ch.is_ascii_alphabetic() || is_apostrophe(ch); + + let mut start = run_start; + while start > 0 && is_member(chars[start - 1]) { + start -= 1; + } + let mut end = run_end; + while end < chars.len() && is_member(chars[end]) { + end += 1; + } + + let segment = &chars[start..end]; + if !segment.iter().any(|ch| is_apostrophe(*ch)) { + return false; + } + if segment.iter().enumerate().any(|(index, ch)| { + is_apostrophe(*ch) + && (index == 0 + || index + 1 == segment.len() + || !segment[index - 1].is_ascii_alphabetic() + || !segment[index + 1].is_ascii_alphabetic()) + }) { + return false; + } + + let normalized: String = segment + .iter() + .filter(|ch| ch.is_ascii_alphabetic()) + .map(|ch| ch.to_ascii_lowercase()) + .collect(); + cmudict::is_recorded_word(&normalized) +} + /// One ARPABET phoneme: its base symbol (e.g. `B`, `AH`, `N`) and, for vowels, /// the lexical stress (0 = unstressed, 1 = primary, 2 = secondary). In CMUdict /// only vowels carry a stress digit, so `stress.is_some()` identifies a vowel. @@ -102,4 +161,22 @@ mod tests { assert_eq!(ph.stress, None); assert!(!ph.is_vowel()); } + + #[rstest::rstest] + #[case::straight_apostrophe("O'PENing", 2, 8, true)] + #[case::curly_apostrophe("O\u{2019}PENing", 2, 8, true)] + #[case::no_join("PENing", 0, 6, false)] + #[case::unknown_elision("rock'n", 5, 6, false)] + fn classifies_apostrophe_elided_lexical_words( + #[case] text: &str, + #[case] run_start: usize, + #[case] run_end: usize, + #[case] expected: bool, + ) { + let chars: Vec = text.chars().collect(); + assert_eq!( + apostrophe_elided_recorded_word_at(&chars, run_start, run_end), + expected + ); + } } diff --git a/libs/braillify/src/rules/english_ueb/rule_10_9.rs b/libs/braillify/src/rules/english_ueb/rule_10_9.rs index 8721da76..88cbca3c 100644 --- a/libs/braillify/src/rules/english_ueb/rule_10_9.rs +++ b/libs/braillify/src/rules/english_ueb/rule_10_9.rs @@ -43,9 +43,84 @@ pub fn whole_word_cells(word: &str) -> Option> { /// A literal all-letter abbreviation that collides with a pure-letter shortform /// needs a grade-1 indicator before normal letter encoding (§10.9.7). pub fn is_pure_shortform_abbreviation(word: &str) -> bool { + let letters = word.chars().collect::>(); + if letters.len() < 2 || !letters.iter().all(char::is_ascii_lowercase) { + return false; + } + let Some(literal_cells) = korean_letter_sequence_cells(&letters) else { + return false; + }; + SHORTFORMS .values() - .any(|abbr| abbr.chars().all(|ch| ch.is_ascii_lowercase()) && *abbr == word) + .any(|notation| notation_cells(notation).as_deref() == Some(literal_cells.as_slice())) +} + +/// UEB 5.7.2 and 10.9.7-10.9.8: returns whether an ASCII letters-sequence at +/// the beginning of a word needs a grade-1 symbol before its capitalization +/// indicator so it cannot be read as a shortform, or as the beginning of a +/// longer word containing one. +/// +/// This compares cells produced by the ordinary rule-37 groupsign encoder with +/// the complete shortform table. Consequently sequences containing groupsigns +/// are handled without a second hand-maintained alias list: `FST` collides with +/// `first` (`f` + `st`), `SHD` with `should`, while `BC` does not collide with +/// `because` (`be` + `c`). For a proper prefix, the existing 10.9.2-10.9.5 +/// longer-word grammar decides whether that shortform reading is actually +/// permitted; this is why the official `LLC` is guarded but `LLAMA` is not. +pub fn requires_grade1_at_word_start(letters: &str) -> bool { + let lower = letters.to_ascii_lowercase(); + let chars = lower.chars().collect::>(); + if chars.len() < 2 || !letters.chars().all(|ch| ch.is_ascii_alphabetic()) { + return false; + } + + for end in 2..=chars.len() { + let Some(prefix_cells) = korean_letter_sequence_cells(&chars[..end]) else { + continue; + }; + for (shortform, notation) in SHORTFORMS.entries() { + if notation_cells(notation).as_deref() != Some(prefix_cells.as_slice()) { + continue; + } + if end == chars.len() { + return true; + } + + let suffix = &chars[end..]; + // §10.9.5 admits an added `s` for every base shortform except + // `abouts`, `almosts`, and `hims`. + if suffix == ['s'] && !matches!(*shortform, "about" | "almost" | "him") { + return true; + } + + let hypothetical = shortform + .chars() + .chain(suffix.iter().copied()) + .collect::>(); + if longer_use_allowed(&hypothetical, 0, shortform) { + return true; + } + } + } + false +} + +/// Produce the cells that the real Korean-rule-37 Roman body encoder would emit +/// for a lowercase ASCII letters-sequence. Grade-1 collision detection must use +/// this exact path: a default [`ContractionEngine`] contains no registered rules +/// and would consequently miss cell-equivalent sequences such as `fst` (`f` + +/// the `st` groupsign) and `shd` (the `sh` groupsign + `d`). +fn korean_letter_sequence_cells(letters: &[char]) -> Option> { + super::span::encode_korean_word( + letters, true, // capitalization indicators are compared separately + false, // do not recursively prepend grade 1 + false, // rule 37 suppresses whole-word signs on Roman entry + true, // the sequence begins at a Roman word boundary + false, // no adjacent digit in a pure letters-sequence + false, // no numeric grade-1 mode in a pure letters-sequence + false, // not split by an apostrophe + ) } /// Encode a word as the §10.10.2 cell-minimising contraction sequence. diff --git a/libs/braillify/src/rules/english_ueb/span.rs b/libs/braillify/src/rules/english_ueb/span.rs index eccfd034..4a893c21 100644 --- a/libs/braillify/src/rules/english_ueb/span.rs +++ b/libs/braillify/src/rules/english_ueb/span.rs @@ -36,6 +36,10 @@ pub(crate) struct KoreanSpanUnit { /// Encode a complete ASCII Roman run in Korean context with the shared UEB /// contraction engine. Korean rule 37 disables wordsigns and shortforms while /// retaining multi-letter groupsigns; the engine entry point enforces that gate. +#[expect( + clippy::too_many_arguments, + reason = "the wrapper preserves the engine's independent UEB rule gates" +)] pub(crate) fn encode_korean_word( chars: &[char], suppress_caps: bool, @@ -43,6 +47,8 @@ pub(crate) fn encode_korean_word( standing_alone: bool, word_initial: bool, digit_adjacent: bool, + numeric_grade1_active: bool, + apostrophe_joined_lexeme: bool, ) -> Option> { KOREAN_WORD_ENGINE.encode_korean_word( chars, @@ -51,6 +57,8 @@ pub(crate) fn encode_korean_word( standing_alone, word_initial, digit_adjacent, + numeric_grade1_active, + apostrophe_joined_lexeme, ) } diff --git a/libs/braillify/src/rules/korean/rule_18.rs b/libs/braillify/src/rules/korean/rule_18.rs index ae98451a..a54ce7b7 100644 --- a/libs/braillify/src/rules/korean/rule_18.rs +++ b/libs/braillify/src/rules/korean/rule_18.rs @@ -201,6 +201,7 @@ mod tests { has_korean_char: true, is_all_uppercase: false, ascii_starts_at_beginning: false, + roman_section_continues_from_previous_word: false, skip_count, state, result, diff --git a/libs/braillify/src/rules/korean/rule_23.rs b/libs/braillify/src/rules/korean/rule_23.rs index 2dfdb67f..ec0886a5 100644 --- a/libs/braillify/src/rules/korean/rule_23.rs +++ b/libs/braillify/src/rules/korean/rule_23.rs @@ -181,6 +181,7 @@ mod tests { has_korean_char: false, is_all_uppercase: false, ascii_starts_at_beginning: false, + roman_section_continues_from_previous_word: false, skip_count: &mut skip_count, state: &mut state, result: &mut result, @@ -229,6 +230,7 @@ mod tests { has_korean_char: false, is_all_uppercase: false, ascii_starts_at_beginning: false, + roman_section_continues_from_previous_word: false, skip_count: &mut skip_count, state: &mut state, result: &mut result, @@ -281,6 +283,7 @@ mod tests { has_korean_char: false, is_all_uppercase: false, ascii_starts_at_beginning: false, + roman_section_continues_from_previous_word: false, skip_count: &mut skip_count, state: &mut state, result: &mut result, diff --git a/libs/braillify/src/rules/korean/rule_25.rs b/libs/braillify/src/rules/korean/rule_25.rs index df397054..41bbf29a 100644 --- a/libs/braillify/src/rules/korean/rule_25.rs +++ b/libs/braillify/src/rules/korean/rule_25.rs @@ -134,6 +134,7 @@ mod tests { has_korean_char: false, is_all_uppercase: false, ascii_starts_at_beginning: false, + roman_section_continues_from_previous_word: false, skip_count: &mut skip_count, state: &mut state, result: &mut result, diff --git a/libs/braillify/src/rules/korean/rule_27.rs b/libs/braillify/src/rules/korean/rule_27.rs index 8a55f0d7..093696e0 100644 --- a/libs/braillify/src/rules/korean/rule_27.rs +++ b/libs/braillify/src/rules/korean/rule_27.rs @@ -26,10 +26,18 @@ fn is_historical_context_word(word: &str) -> bool { let code = c as u32; (0xE000..=0xF8FF).contains(&code) || (0x4E00..=0x9FFF).contains(&code) - || matches!(c, ':' | '〔' | '〕') + || matches!(c, ':' | '〮' | '〯' | '〔' | '〕') }) } +fn is_geoseong(c: char) -> bool { + matches!(c, '·' | '〮') +} + +fn is_sangseong(c: char) -> bool { + matches!(c, ':' | '〯') +} + fn has_historical_context(ctx: &RuleContext) -> bool { if is_historical_context_word(&ctx.word_chars.iter().collect::()) { return true; @@ -44,10 +52,19 @@ fn has_historical_context(ctx: &RuleContext) -> bool { } fn is_middle_korean_geoseong(ctx: &RuleContext) -> bool { - if !matches!(ctx.char_type, CharType::Symbol('·')) { + let CharType::Symbol(c) = ctx.char_type else { + return false; + }; + if !is_geoseong(*c) { return false; } + // U+302E HANGUL SINGLE DOT TONE MARK is semantically unambiguous. Unlike + // U+00B7, it can never be the modern middle-dot punctuation of Rule 49. + if *c == '〮' { + return true; + } + // 단독 입력 `·`은 한국어 점자에서 두 가지 의미를 가진다: // - 일반 한국어(가운뎃점, 제49항): ⠐⠆ — rule_49가 처리 // - 중세국어(거성, 제27항): ⠸⠂ — 이 규칙이 처리 @@ -67,7 +84,7 @@ fn is_middle_korean_geoseong(ctx: &RuleContext) -> bool { } fn is_middle_korean_particle_geoseong(ctx: &RuleContext) -> bool { - matches!(ctx.char_type, CharType::Symbol('·')) + matches!(ctx.char_type, CharType::Symbol(c) if is_geoseong(*c)) && ctx.state.current_mode() == EncodingMode::MiddleKorean && ctx.next_char() == Some('에') } @@ -80,7 +97,7 @@ fn is_inline_gloss_separator(ctx: &RuleContext) -> bool { } fn is_middle_korean_sangseong(ctx: &RuleContext) -> bool { - matches!(ctx.char_type, CharType::Symbol(':')) + matches!(ctx.char_type, CharType::Symbol(c) if is_sangseong(*c)) } pub struct Rule27; @@ -99,7 +116,8 @@ impl BrailleRule for Rule27 { } fn matches(&self, ctx: &RuleContext) -> bool { - let is_potential_tone_mark = matches!(ctx.char_type, CharType::Symbol('·' | ':')); + let is_potential_tone_mark = + matches!(ctx.char_type, CharType::Symbol(c) if is_geoseong(*c) || is_sangseong(*c)); if !is_potential_tone_mark { return false; } @@ -118,15 +136,15 @@ impl BrailleRule for Rule27 { match c { '·' if is_inline_gloss_separator(ctx) => {} - '·' if is_middle_korean_particle_geoseong(ctx) => { + c if is_geoseong(*c) && is_middle_korean_particle_geoseong(ctx) => { ctx.emit(0); ctx.emit_slice(&GEOSEONG); } - '·' if ctx.state.current_mode() == EncodingMode::MiddleKorean => { + c if is_geoseong(*c) && ctx.state.current_mode() == EncodingMode::MiddleKorean => { ctx.emit_slice(&GEOSEONG); } - '·' if is_middle_korean_geoseong(ctx) => ctx.emit_slice(&GEOSEONG), - ':' => ctx.emit_slice(&SANGSEONG), + c if is_geoseong(*c) && is_middle_korean_geoseong(ctx) => ctx.emit_slice(&GEOSEONG), + c if is_sangseong(*c) => ctx.emit_slice(&SANGSEONG), _ => return Ok(RuleResult::Skip), } @@ -213,6 +231,19 @@ mod tests { assert_eq!(owned.result, GEOSEONG.to_vec()); } + #[rstest::rstest] + #[case::single_dot('〮', GEOSEONG.to_vec())] + #[case::double_dot('〯', SANGSEONG.to_vec())] + fn unicode_hangul_tone_marks_use_rule_27_cells(#[case] input: char, #[case] expected: Vec) { + let mut owned = crate::test_helpers::CtxOwned::for_text(&input.to_string(), false); + let mut ctx = owned.ctx_at(0); + + let outcome = Rule27.apply(&mut ctx).unwrap(); + + assert!(matches!(outcome, RuleResult::Consumed)); + assert_eq!(owned.result, expected); + } + /// rule_27 line 106 — `_ => return Ok(Skip)` fallback for non-· non-: symbol char. #[test] fn rule27_apply_skip_for_unrelated_symbol() { diff --git a/libs/braillify/src/rules/korean/rule_28.rs b/libs/braillify/src/rules/korean/rule_28.rs index 414c5284..8e7341d5 100644 --- a/libs/braillify/src/rules/korean/rule_28.rs +++ b/libs/braillify/src/rules/korean/rule_28.rs @@ -8,17 +8,18 @@ //! Reference: 2024 Korean Braille Standard, Chapter 4, Section 10, Article 28 use crate::char_struct::CharType; +use crate::english_logic::requires_single_letter_continuation; use crate::rules::RuleMeta; use crate::rules::context::RuleContext; -use crate::rules::english_shortform::requires_grade1_indicator; +use crate::rules::english_shortform::{ + permits_grade1_boundary_after_run, requires_grade1_indicator, +}; use crate::rules::english_ueb::korean_context::KoreanPrefixInput; use crate::rules::english_ueb::span::{encode_korean_unit, encode_korean_word}; +use crate::rules::english_ueb::standing_alone::lower_wordsign_usable; +use crate::rules::english_ueb::token::EnglishToken; use crate::rules::traits::{BrailleRule, Phase, RuleResult}; -fn is_nonempty_ascii_word(word: &str) -> bool { - !word.is_empty() && word.chars().all(|ch| ch.is_ascii_alphabetic()) -} - pub static META: RuleMeta = RuleMeta { section: "28", subsection: None, @@ -80,6 +81,16 @@ impl BrailleRule for Rule28 { return Ok(RuleResult::Skip); }; + // At index 0 the emitter may already have emitted the Roman indicator, + // so use its pre-entry snapshot. For an ASCII run later in a mixed + // print word, the live mode accurately says whether this run continues + // an existing Roman section or starts a fresh one. + let continuing_roman_section = if ctx.index == 0 { + ctx.roman_section_continues_from_previous_word + } else { + ctx.state.is_english + }; + // Enter English mode (로마자표 / 연속표) // 제39항 영어 주도 문서에서는 영자표시/연속표를 emit하지 않는다. if ctx.state.english_indicator @@ -123,35 +134,85 @@ impl BrailleRule for Rule28 { // uppercase letters-sequence. UEB 8.4.2 ends that mode at a // nonletter, so a later run (the final `T` in official `AT&T`) // must produce its own capitalization indicator. - let caps_already_emitted = ctx.index == 0 - && ctx.is_all_uppercase - && ctx.word_len() >= 2 - && ctx.ascii_starts_at_beginning; - let is_whole_lowercase_word = ctx.index == 0 - && run_end == ctx.word_chars.len() - && run.iter().all(|ch| ch.is_ascii_lowercase()); - let prev_is_ascii_word = is_nonempty_ascii_word(ctx.prev_word); - let next_is_ascii_word = match ctx.remaining_words.first() { - Some(word) => is_nonempty_ascii_word(word), - None => false, - }; - // Rule 37's PDF example, "그는 Can you help me?라고 도움을 요청했다.", - // suppresses a whole-word sign for the first Roman word (`Can`) but retains - // the UEB wordsign for the phrase-interior `you`. The adjacent-ASCII-word - // gate models that structural position. Rule 39's "What is 김치 in English?" - // resumes the surrounding English passage after Korean, so the persistent - // English-dominant gate retains the resumed `in` wordsign. Neither gate - // depends on the example's literal words. - let standalone_wordsign = is_whole_lowercase_word - && (ctx.state.english_dominant_wrap_active - || (prev_is_ascii_word && next_is_ascii_word)); + let caps_already_emitted = ctx.state.triple_big_english + || (ctx.index == 0 + && ctx.is_all_uppercase + && ctx.word_len() >= 2 + && ctx.ascii_starts_at_beginning); let word_initial = ctx.index == 0 || ctx.word_chars.get(ctx.index - 1).is_some_and(|previous| { - matches!( - previous, - '(' | '[' | '{' | '\u{2018}' | '\u{201c}' | '"' | '-' - ) + crate::utils::is_korean_char(*previous) + || matches!( + previous, + '(' | '[' + | '{' + | '\u{2018}' + | '\u{201c}' + | '"' + | '-' + | '\u{2010}' + | '\u{2011}' + | '\u{2012}' + | '\u{2013}' + | '\u{2014}' + ) }); + let run_is_all_uppercase = run.iter().all(|ch| ch.is_ascii_uppercase()); + let is_standing_alone_ordinary_run = !run_is_all_uppercase + && word_initial + && permits_grade1_boundary_after_run(&ctx.word_chars[run_end..]); + // Rule 37's PDF example, "그는 Can you help me?라고 도움을 요청했다.", + // suppresses a whole-word sign for the first Roman word (`Can`) but retains + // the UEB wordsign for the following `you`. Rule 29 keeps consecutive + // Roman words in the same section, so every complete ordinary-cased word + // after the first Roman word has the same continuation status, including + // the final word of a phrase. UEB capitalization does not suppress a + // wordsign, hence Title-case `Like`/`This` follows the same rule. All-caps + // runs remain excluded because Rule 10.12.1 initialisms and emphasized + // words have the same surface form and require pronunciation semantics. + // Rule 39's "What is 김치 in English?" resumes the surrounding English + // passage after Korean, so the persistent English-dominant gate retains + // the resumed `in` wordsign. Neither gate depends on a corpus reference. + let whole_print_word = ctx.index == 0 && run_end == ctx.word_chars.len(); + let wrap_wordsign = ctx.state.english_dominant_wrap_active && whole_print_word; + // 제37항 붙임: these six words are spelled with alphabet signs and + // applicable groupsigns even when they occur later in the Roman + // section immediately before its terminator. Other continuation + // words, such as official `you` in `Can you help me?`, retain their + // ordinary UEB wordsign. + let lower_run = run + .iter() + .map(|ch| ch.to_ascii_lowercase()) + .collect::(); + let is_lower_wordsign = matches!( + lower_run.as_str(), + "be" | "enough" | "his" | "in" | "was" | "were" + ); + let rule_37_korean_context_exception = !ctx.state.english_dominant_wrap_active + && !ctx.state.roman_section_is_english_context + && is_lower_wordsign; + // UEB 10.5 gives lower wordsigns a stricter boundary than ordinary + // standing-alone wordsigns. In particular, a hyphen, dash, quote, + // or lower punctuation cell touching either side forces spelling. + // Reuse the English engine's boundary predicate instead of treating + // Rule 28's general grade-1 boundary as sufficient (`In-house`). + let previous_boundary = ctx + .index + .checked_sub(1) + .and_then(|index| ctx.word_chars.get(index)) + .copied() + .map(EnglishToken::Symbol); + let next_boundary = ctx + .word_chars + .get(run_end) + .copied() + .map(EnglishToken::Symbol); + let lower_wordsign_boundary_permits = !is_lower_wordsign + || lower_wordsign_usable(previous_boundary.as_ref(), next_boundary.as_ref()); + let standalone_wordsign = is_standing_alone_ordinary_run + && (wrap_wordsign || continuing_roman_section) + && !rule_37_korean_context_exception + && lower_wordsign_boundary_permits; let digit_adjacent = ctx .index .checked_sub(1) @@ -161,23 +222,46 @@ impl BrailleRule for Rule28 { .word_chars .get(run_end) .is_some_and(|ch| ch.is_ascii_digit()); - // UEB 5.7.2, 5.8.1, and 10.9.7: when an attached Roman entry is a - // complete pure-letter shortform, grade 1 precedes its capitalization - // marker. Standalone ASCII tokens have already been handled by - // `UppercasePassageRule`. Digit and hyphen continuations retain their - // independent rule-35/state-machine paths. - let follows_hyphen = ctx + let numeric_grade1_active = ctx .index .checked_sub(1) .and_then(|index| ctx.word_chars.get(index)) - .is_some_and(|ch| *ch == '-'); + .is_some_and(|ch| ch.is_ascii_digit()) + && ctx.word_chars[..ctx.index.saturating_sub(1)] + .iter() + .any(|ch| ch.is_ascii_alphabetic()); + // UEB 5.7.1-5.7.2 and 5.8.1: grade 1 precedes the capitalization + // marker when a standing letter/letters-sequence would otherwise be + // read as an alphabetic wordsign or shortform. A bare one-letter + // Rule 28 specimen (`K`) keeps the PDF's plain alphabet cell, while + // the same letter in running text (`K-POP`, `ARIRANG K방산Fn`) is a + // UEB 5.7.1 standing letter. Multi-letter uppercase tokens have + // already had their capitalization mode emitted by + // `UppercasePassageRule`; an adjacent digit is not a standing-alone + // boundary, whereas a hyphen or dash explicitly is (UEB 2.6.1). let uppercase_run = run.iter().collect::(); + let entire_isolated_rule_28_specimen = ctx.index == 0 + && run_end == ctx.word_chars.len() + && ctx.prev_word.is_empty() + && ctx.remaining_words.is_empty(); + let single_letter_wordsign_collision = run.len() == 1 + && requires_single_letter_continuation(run[0]) + && ctx.index == 0 + && ctx.roman_section_continues_from_previous_word + && !entire_isolated_rule_28_specimen; + let shortform_collision = requires_grade1_indicator(&uppercase_run); let prepend_grade1_indicator = !caps_already_emitted && word_initial && !digit_adjacent - && !follows_hyphen && run.iter().all(|ch| ch.is_ascii_uppercase()) - && requires_grade1_indicator(&uppercase_run); + && permits_grade1_boundary_after_run(&ctx.word_chars[run_end..]) + && (single_letter_wordsign_collision || shortform_collision); + let apostrophe_joined_lexeme = + crate::rules::english_ueb::pronunciation::apostrophe_elided_recorded_word_at( + ctx.word_chars, + ctx.index, + run_end, + ); if let Some(cells) = encode_korean_word( run, caps_already_emitted, @@ -185,6 +269,8 @@ impl BrailleRule for Rule28 { standalone_wordsign, word_initial, digit_adjacent, + numeric_grade1_active, + apostrophe_joined_lexeme, ) { ctx.emit_slice(&cells); *ctx.skip_count = run.len().saturating_sub(1); @@ -249,15 +335,6 @@ mod tests { use crate::unicode::decode_unicode; use crate::{EncodeOptions, encode_to_unicode, encode_with_options}; - #[rstest::rstest] - #[case::empty("", false)] - #[case::ascii("help", true)] - #[case::korean("도움", false)] - #[case::punctuated("me?", false)] - fn classifies_adjacent_ascii_words(#[case] word: &str, #[case] expected: bool) { - assert_eq!(is_nonempty_ascii_word(word), expected); - } - /// 제28항 — 영문자 점역. 소문자/대문자 모두 동일 점형으로 인코딩. #[rstest::rstest] #[case::lower_a('a', '⠁')] @@ -317,6 +394,18 @@ mod tests { ); } + /// Rule 37 limits its whole-word-contraction suppression to the Roman word + /// immediately following the indicator. Apostrophe punctuation in that + /// first word and a Korean suffix attached to the final word do not start a + /// second Roman section, so subsequent `do` and `this` retain UEB wordsigns. + #[test] + fn rule_37_continuation_survives_apostrophe_and_attached_korean_suffix() { + assert_eq!( + encode_to_unicode("그는 Let's do this라고 말했다.").unwrap(), + "⠈⠪⠉⠵⠀⠴⠠⠇⠑⠞⠄⠎⠀⠙⠀⠹⠲⠐⠣⠈⠥⠀⠑⠂⠚⠗⠌⠊⠲" + ); + } + /// UEB 5.7.2/5.8.1/10.9.7 complete-shortform handling through the complete /// Korean encoder. Every Roman surface comes directly from the PDF examples /// (`CD`, `ALT`, `NEC`); the Korean wrapper exercises only rule 28/29/34 routing. @@ -331,6 +420,61 @@ mod tests { assert_eq!(crate::encode_to_unicode(input).unwrap(), expected); } + /// UEB 2.6.1-2.6.3 boundaries must be enforced on the Rule28 path as well as + /// on whitespace tokens. A leading quote forces this path because the token + /// itself no longer starts with ASCII; `CD`/`LLC` are official 10.9 examples. + #[rstest::rstest] + #[case::closing_quote("‘CD’", true)] + #[case::attached_after_korean("가CD", true)] + #[case::non_shortform_after_korean("가KBS", false)] + #[case::digit_after_hyphen("5-CD-678", true)] + #[case::closing_group_before_korean_middle_dot("(CD)·현금", true)] + #[case::adjacent_digit("‘CD47", false)] + #[case::slash_continuation("‘CD/ATM", false)] + #[case::opening_group_after_sequence("‘LLC(회사)", false)] + fn noninitial_ascii_run_respects_grade1_boundary(#[case] input: &str, #[case] expected: bool) { + let encoded = crate::encode(input).unwrap(); + assert_eq!( + encoded + .windows(3) + .any(|window| window == [48, UPPERCASE_SINGLE, UPPERCASE_SINGLE]), + expected + ); + } + + /// UEB 5.7.1/5.8.1: a single capital wordsign letter standing in running + /// text needs grade 1 before its capital indicator. The rule is structural: + /// the following boundary may be whitespace, a hyphen, or a Korean code + /// boundary. `a`, `i`, and `o` are excluded by the shared UEB predicate. + #[rstest::rstest] + #[case::roman_number_chain("가 X5 M 나", 'm')] + #[case::hyphen_bounded("가 EAFF E-1 나", 'e')] + #[case::korean_code_boundary("가 ARIRANG K방산Fn 나", 'k')] + fn running_single_capital_wordsign_letter_uses_grade1( + #[case] input: &str, + #[case] letter: char, + ) { + let encoded = crate::encode(input).unwrap(); + let letter = crate::english::encode_english(letter).unwrap(); + + assert!(encoded.windows(3).any(|window| { + window + == [ + crate::rules::korean::rule_29::ENGLISH_CONTINUATION, + UPPERCASE_SINGLE, + letter, + ] + })); + } + + /// Korean Rule 28's alphabet table is a specimen, not running contracted + /// English. Its isolated capital letters therefore retain the plain Rule + /// 28 form without a UEB grade-1 prefix. + #[test] + fn isolated_rule_28_capital_specimen_stays_plain() { + assert_eq!(crate::encode_to_unicode("K").as_deref(), Ok("⠠⠅")); + } + /// UEB 8.4.2 keeps an internal apostrophe in the Roman letters-sequence but /// terminates capitals-word mode at that nonalphabetic symbol. The Roman /// surfaces are official UEB examples; the neutral Korean wrapper exercises @@ -348,6 +492,18 @@ mod tests { assert_eq!(crate::encode_to_unicode(input).as_deref(), Ok(expected)); } + /// UEB 10.6.8 keeps `en` inside a capitals word when the letters belong to + /// an ordinarily pronounced word. Removing the internal apostrophe yields + /// recorded `opening`, which distinguishes this emphasis from a 10.12.1 + /// initialism while exercising the Korean Rule 28/37 wrapper. + #[test] + fn apostrophe_elided_lexeme_contracts_inside_capitals_word() { + assert_eq!( + crate::encode_to_unicode("가 O'PENing 나").as_deref(), + Ok("⠫⠀⠴⠠⠕⠄⠠⠠⠏⠢⠠⠄⠬⠲⠀⠉") + ); + } + #[test] fn english_dominant_wrap_resumes_ueb_wordsigns_after_korean_span() { let mut owned = crate::test_helpers::CtxOwned::for_text("in", true); @@ -362,20 +518,133 @@ mod tests { assert_eq!(owned.result, vec![20]); } - /// Rule 37's PDF sentence `Can you help me?` permits the phrase-interior - /// `you` wordsign because both adjacent whitespace-delimited words are Roman. - #[test] - fn rule_37_phrase_interior_word_uses_standalone_wordsign() { - let mut owned = crate::test_helpers::CtxOwned::for_text("you", true) + /// A document-level rule-39 wrap signal must not turn each separate Roman + /// annotation inside a mixed Korean print word into a continuation. Each + /// parenthesized item below begins a fresh rule-37 Roman section and is + /// therefore spelled, even when its surface is also a UEB wordsign. + #[rstest::rstest] + #[case::titlecase_us( + "(Us)", + 1, + &[52, 32, decode_unicode('⠥'), decode_unicode('⠎')] + )] + #[case::lowercase_it("(it)", 1, &[52, decode_unicode('⠊'), decode_unicode('⠞')])] + fn mixed_print_word_starts_fresh_rule_37_section( + #[case] input: &str, + #[case] index: usize, + #[case] expected: &[u8], + ) { + let mut owned = crate::test_helpers::CtxOwned::for_text(input, true); + owned.state.english_dominant_wrap_active = true; + let mut ctx = owned.ctx_at(index); + + assert!(matches!( + Rule28.apply(&mut ctx).unwrap(), + RuleResult::Consumed + )); + assert_eq!(owned.result, expected); + } + + /// Rule 37's PDF sentence `Can you help me?` permits wordsigns after the + /// first Roman word. Rule 29 keeps the final Roman word in that same section, + /// and UEB capitalization leaves the wordsign itself unchanged. + #[rstest::rstest] + #[case::interior_lowercase("you", "Can", &[decode_unicode('⠽')])] + #[case::final_titlecase("This", "Like", &[32, decode_unicode('⠹')])] + #[case::final_lowercase("will", "Boys", &[decode_unicode('⠺')])] + fn rule_37_continuation_word_uses_standalone_wordsign( + #[case] input: &str, + #[case] previous: &str, + #[case] expected: &[u8], + ) { + let mut owned = crate::test_helpers::CtxOwned::for_text(input, true) + .with_prev_word(previous) + .with_roman_section_continuation(); + owned.state.is_english = true; + let mut ctx = owned.ctx_at(0); + + assert!(matches!( + Rule28.apply(&mut ctx).unwrap(), + RuleResult::Consumed + )); + assert_eq!(owned.result, expected); + } + + /// 제37항 붙임: these words stay expanded throughout a Korean-context + /// Roman section, including immediately before the Roman terminator. + #[rstest::rstest] + #[case::be("be", "⠃⠑")] + #[case::enough("enough", "⠢⠳⠣")] + #[case::his("his", "⠓⠊⠎")] + #[case::in_word("in", "⠊⠝")] + #[case::was("was", "⠺⠁⠎")] + #[case::were("were", "⠺⠻⠑")] + fn rule_37_terminator_exceptions_remain_expanded(#[case] input: &str, #[case] expected: &str) { + let mut owned = crate::test_helpers::CtxOwned::for_text(input, true) .with_prev_word("Can") - .with_remaining_words(["help", "me?"]); + .with_roman_section_continuation(); + owned.state.is_english = true; let mut ctx = owned.ctx_at(0); assert!(matches!( Rule28.apply(&mut ctx).unwrap(), RuleResult::Consumed )); - assert_eq!(owned.result, vec![52, decode_unicode('⠽')]); + assert_eq!( + owned.result, + expected.chars().map(decode_unicode).collect::>() + ); + } + + /// The NIKL's rule consultation distinguishes Korean metalinguistic Roman + /// material from a visibly English phrase. In the latter context UEB 10.5 + /// applies to all six lower wordsigns, even though the surrounding document + /// is Korean. + #[rstest::rstest] + #[case::be_word("be", '⠆')] + #[case::enough_word("enough", '⠢')] + #[case::his_word("his", '⠦')] + #[case::in_word("in", '⠔')] + #[case::was_word("was", '⠴')] + #[case::were_word("were", '⠶')] + fn english_phrase_uses_ueb_lower_wordsigns(#[case] word: &str, #[case] wordsign: char) { + let input = format!("제목(Alpha {word} Omega)이다."); + let actual = encode_to_unicode(&input).expect("English phrase must encode"); + let expected = format!("⠀{wordsign}⠀"); + + assert!( + actual.contains(&expected), + "missing UEB lower wordsign in English phrase: {actual}" + ); + } + + #[test] + fn english_phrase_context_survives_a_preceding_capitals_passage() { + let actual = + encode_to_unicode("제목 ‘2023 SHINHWA WDJ FANPARTY COME TO LIFE in TAIPEI’는 끝이다.") + .expect("capitalized English title must encode"); + + assert!( + actual.contains("⠀⠔⠀"), + "caps-passage mode prefix lost the English phrase context: {actual}" + ); + } + + /// UEB 10.5: a lower wordsign touching a hyphen is not usable even when the + /// surrounding Roman section is clearly an English title. + #[test] + fn english_phrase_spells_lower_wordsign_touching_hyphen() { + let actual = encode_to_unicode("제목(Alpha In-house Teams)이다.") + .expect("hyphenated English phrase must encode"); + + assert!( + actual.contains("⠀⠠⠊⠝⠤"), + "hyphen-adjacent `In` must remain expanded: {actual}" + ); + assert!( + !actual.contains("⠀⠠⠔⠤"), + "hyphen-adjacent `In` must not use its lower wordsign: {actual}" + ); } #[test] @@ -415,6 +684,7 @@ mod tests { has_korean_char: false, is_all_uppercase: false, ascii_starts_at_beginning: true, + roman_section_continues_from_previous_word: false, skip_count: &mut skip, state: &mut state, result: &mut out, diff --git a/libs/braillify/src/rules/korean/rule_40.rs b/libs/braillify/src/rules/korean/rule_40.rs index 96f663e8..7fd99ac6 100644 --- a/libs/braillify/src/rules/korean/rule_40.rs +++ b/libs/braillify/src/rules/korean/rule_40.rs @@ -63,10 +63,11 @@ impl BrailleRule for Rule40 { // (rule_69.rs:174-181 matches() + 184-196 apply() 참조) if !ctx.state.is_number { - // 제43항: skip prefix after continuation characters (. or ,) - let needs_prefix = ctx - .prev_char() - .is_none_or(|prev| !is_number_continuation(prev)); + // 제43항: 마침표/쉼표가 *숫자 사이*에 있을 때에만 뒤 수표를 + // 생략한다. `M.2`, `No.1`, `2만,4142`처럼 문장 부호의 왼쪽이 + // 숫자가 아닌 경우에는 제40항에 따라 새 수표를 적는다. + let needs_prefix = + !is_number_continuation(ctx.word_chars, ctx.index, ctx.state.english_indicator); if needs_prefix { ctx.emit(NUMBER_INDICATOR); // 제61항: apostrophe/right single quote before number emits ⠄ after 수표 @@ -85,10 +86,28 @@ impl BrailleRule for Rule40 { } } -/// Check if the previous character is a continuation character (. or ,) -/// that should suppress the number indicator on the next digit. -pub fn is_number_continuation(prev: char) -> bool { - prev == '.' || prev == ',' +/// Return whether the digit at `index` follows `digit + (. or ,)`. +/// +/// 제43항의 적용 조건은 문장 부호 자체가 아니라 그 문장 부호가 두 숫자 +/// 사이에 놓였는지이다. 따라서 로마자나 한글 뒤의 마침표/쉼표는 새 숫자 +/// 묶음의 수표를 생략하지 않는다. +pub fn is_number_continuation(word_chars: &[char], index: usize, in_korean_document: bool) -> bool { + if index == 0 || !matches!(word_chars[index - 1], '.' | ',') { + return false; + } + + if in_korean_document { + return index >= 2 && word_chars[index - 2].is_numeric(); + } + + // UEB 6.3.1: numeric mode continues through a sequence of full stops or + // commas. It can therefore span `4..7`, but it was never established in + // an identifier such as `M.2`. + word_chars[..index] + .iter() + .rev() + .find(|ch| !matches!(ch, '.' | ',')) + .is_some_and(|ch| ch.is_numeric()) } #[cfg(test)] @@ -110,14 +129,51 @@ mod tests { assert!(encode_digit('a').is_err()); } - /// `is_number_continuation` — `.` / `,` 만 숫자 흐름에 포함. + /// 제43항 — `.` / `,`가 실제로 숫자 사이에 있을 때만 숫자 흐름에 포함. + #[rstest::rstest] + #[case::korean_decimal("3.9", 2, true, true)] + #[case::korean_grouped("1,000", 2, true, true)] + #[case::korean_repeated_period("4..7", 3, true, false)] + #[case::ueb_repeated_period("4..7", 3, false, true)] + #[case::roman_period("M.2", 2, false, false)] + #[case::roman_period_in_korean("M.2", 2, true, false)] + #[case::roman_comma("X,1", 2, true, false)] + #[case::korean_comma("2만,4142", 3, true, false)] + #[case::leading_period(".47", 1, false, false)] + #[case::hyphen("3-4", 2, false, false)] + #[case::first_digit("7", 0, false, false)] + fn continuation_chars( + #[case] input: &str, + #[case] index: usize, + #[case] in_korean_document: bool, + #[case] expected: bool, + ) { + assert_eq!( + is_number_continuation( + &input.chars().collect::>(), + index, + in_korean_document, + ), + expected + ); + } + + /// 제35항/제40항/제43항 — 로마자 뒤 마침표는 숫자 사이의 소수점이 + /// 아니므로 뒤 숫자에는 수표를 새로 적는다. #[rstest::rstest] - #[case::period('.', true)] - #[case::comma(',', true)] - #[case::space(' ', false)] - #[case::hyphen('-', false)] - fn continuation_chars(#[case] ch: char, #[case] expected: bool) { - assert_eq!(is_number_continuation(ch), expected); + #[case::capital_identifier("가 M.2 나", "⠍⠲⠼⠃")] + #[case::all_caps_identifier("가 NO.1 나", "⠕⠲⠼⠁")] + #[case::korean_before_comma("가 2만,4142명 나", "⠑⠒⠐⠼⠙")] + #[case::ueb_multiple_periods("4..7", "⠼⠙⠲⠲⠛")] + fn non_numeric_left_side_does_not_suppress_number_indicator( + #[case] input: &str, + #[case] expected_fragment: &str, + ) { + let actual = crate::encode_to_unicode(input).expect("input must encode"); + assert!( + actual.contains(expected_fragment), + "missing rule-40 number indicator in {actual}" + ); } /// PDF 제40항 + 제69항 — numeric prefix followed by ASCII unit (kg, cm, etc.) diff --git a/libs/braillify/src/rules/korean/rule_44.rs b/libs/braillify/src/rules/korean/rule_44.rs index f772aecf..710fa5a7 100644 --- a/libs/braillify/src/rules/korean/rule_44.rs +++ b/libs/braillify/src/rules/korean/rule_44.rs @@ -22,6 +22,14 @@ pub static META: RuleMeta = RuleMeta { /// Choseong characters that could be confused with digit braille patterns. const CONFUSABLE_CHOSEONG: [char; 7] = ['ㄴ', 'ㄷ', 'ㅁ', 'ㅋ', 'ㅌ', 'ㅍ', 'ㅎ']; +pub(crate) fn is_number_confusable_korean_char(ch: char) -> bool { + matches!( + CharType::new(ch), + Ok(CharType::Korean(korean)) + if CONFUSABLE_CHOSEONG.contains(&korean.cho) || ch == '운' + ) +} + /// Plugin struct for the rule engine. /// /// Inserts a space (code 0) before Korean syllables with confusable choseong @@ -46,10 +54,10 @@ impl BrailleRule for Rule44 { if !ctx.state.is_number { return false; } - let CharType::Korean(korean) = ctx.char_type else { + let CharType::Korean(_) = ctx.char_type else { return false; }; - CONFUSABLE_CHOSEONG.contains(&korean.cho) || ctx.current_char() == '운' + is_number_confusable_korean_char(ctx.current_char()) } fn apply(&self, ctx: &mut RuleContext) -> Result { @@ -83,6 +91,24 @@ mod tests { } } + #[rstest::rstest] + #[case::nieun("는", true)] + #[case::digeut("당", true)] + #[case::mieum("명", true)] + #[case::kieuk("칸", true)] + #[case::tieut("톤", true)] + #[case::pieup("평", true)] + #[case::hieuh("항", true)] + #[case::un_abbreviation("운", true)] + #[case::vowel_initial("이다", false)] + #[case::non_korean("A", false)] + fn detects_number_confusable_following_korean(#[case] input: &str, #[case] expected: bool) { + assert_eq!( + is_number_confusable_korean_char(input.chars().next().unwrap()), + expected + ); + } + #[test] fn meta_is_correct() { assert_eq!(META.section, "44"); @@ -109,6 +135,7 @@ mod tests { has_korean_char: true, is_all_uppercase: false, ascii_starts_at_beginning: false, + roman_section_continues_from_previous_word: false, skip_count: &mut skip, state: &mut state, result: &mut out, diff --git a/libs/braillify/src/rules/korean/rule_49.rs b/libs/braillify/src/rules/korean/rule_49.rs index f5c034d3..6d57efaa 100644 --- a/libs/braillify/src/rules/korean/rule_49.rs +++ b/libs/braillify/src/rules/korean/rule_49.rs @@ -293,6 +293,7 @@ mod tests { has_korean_char: false, is_all_uppercase: false, ascii_starts_at_beginning: false, + roman_section_continues_from_previous_word: false, skip_count: &mut skip_count, state: &mut state, result: &mut result, @@ -321,6 +322,7 @@ mod tests { has_korean_char: false, is_all_uppercase: false, ascii_starts_at_beginning: false, + roman_section_continues_from_previous_word: false, skip_count: &mut skip_count, state: &mut state, result: &mut result, diff --git a/libs/braillify/src/rules/korean/rule_53.rs b/libs/braillify/src/rules/korean/rule_53.rs index 37f535da..b712160e 100644 --- a/libs/braillify/src/rules/korean/rule_53.rs +++ b/libs/braillify/src/rules/korean/rule_53.rs @@ -121,6 +121,7 @@ mod tests { has_korean_char: false, is_all_uppercase: false, ascii_starts_at_beginning: false, + roman_section_continues_from_previous_word: false, skip_count, state, result, diff --git a/libs/braillify/src/rules/korean/rule_57.rs b/libs/braillify/src/rules/korean/rule_57.rs index 3359f22c..5f58837a 100644 --- a/libs/braillify/src/rules/korean/rule_57.rs +++ b/libs/braillify/src/rules/korean/rule_57.rs @@ -135,6 +135,7 @@ mod tests { has_korean_char: false, is_all_uppercase: false, ascii_starts_at_beginning: false, + roman_section_continues_from_previous_word: false, skip_count: &mut skip, state: &mut state, result: &mut out, @@ -161,6 +162,7 @@ mod tests { has_korean_char: false, is_all_uppercase: false, ascii_starts_at_beginning: false, + roman_section_continues_from_previous_word: false, skip_count: &mut skip, state: &mut state, result: &mut out, @@ -188,6 +190,7 @@ mod tests { has_korean_char: false, is_all_uppercase: false, ascii_starts_at_beginning: false, + roman_section_continues_from_previous_word: false, skip_count: &mut skip, state: &mut state, result: &mut out, @@ -215,6 +218,7 @@ mod tests { has_korean_char: false, is_all_uppercase: false, ascii_starts_at_beginning: false, + roman_section_continues_from_previous_word: false, skip_count: &mut skip, state: &mut state, result: &mut out, diff --git a/libs/braillify/src/rules/korean/rule_68.rs b/libs/braillify/src/rules/korean/rule_68.rs index 5b484629..1935af95 100644 --- a/libs/braillify/src/rules/korean/rule_68.rs +++ b/libs/braillify/src/rules/korean/rule_68.rs @@ -40,7 +40,10 @@ fn encode_unicode_cells(unicode: &str) -> Vec { } fn should_insert_separator_after_symbol(ctx: &RuleContext) -> bool { - matches!(ctx.current_char(), '㎡') && matches!(ctx.next_char(), Some('는' | '은')) + matches!(ctx.current_char(), '㎡') + && ctx + .next_char() + .is_some_and(super::rule_44::is_number_confusable_korean_char) } pub fn is_rule_68_symbol(c: char) -> bool { @@ -224,13 +227,21 @@ impl BrailleRule for Rule68 { return Ok(RuleResult::Consumed); } - let Some(encoded) = encode_rule_68_symbol(ctx.current_char()) else { + let Some(mut encoded) = encode_rule_68_symbol(ctx.current_char()) else { return Ok(RuleResult::Skip); }; + let is_roman_unit = matches!(ctx.current_char(), '㎡' | '㏊'); + let continues = is_roman_unit + && super::rule_69::adjust_roman_unit_boundary(ctx, ctx.index + 1, &mut encoded); ctx.emit_slice(&encoded); if should_insert_separator_after_symbol(ctx) { ctx.emit(0); } + if is_roman_unit { + ctx.state.is_english = continues; + ctx.state.needs_english_continuation = false; + ctx.state.roman_number_chain = false; + } Ok(RuleResult::Consumed) } } @@ -262,6 +273,21 @@ fn is_digit_grade_plus_notation(word: &[char], index: usize) -> bool { mod tests { use super::*; + #[rstest::rstest] + #[case::official_particle("10,000㎡는", true)] + #[case::confusable_counter("3.3㎡당", true)] + #[case::vowel_initial_predicate("3.3㎡이다", false)] + fn square_metre_separates_only_number_confusable_korean( + #[case] input: &str, + #[case] expects_separator: bool, + ) { + let actual = crate::encode_to_unicode(input).unwrap(); + let unit = "⠴⠍⠘⠼⠃"; + let unit_end = actual.find(unit).expect("square-metre cells") + unit.len(); + let follows_with_space = actual[unit_end..].starts_with('⠀'); + assert_eq!(follows_with_space, expects_separator, "input={input}"); + } + #[test] fn is_rule_68_symbol_recognises_each_entry() { for (c, _) in MAPPINGS { @@ -430,6 +456,7 @@ mod tests { has_korean_char: false, is_all_uppercase: false, ascii_starts_at_beginning: false, + roman_section_continues_from_previous_word: false, skip_count: &mut skip, state: &mut state, result: &mut out, @@ -457,6 +484,7 @@ mod tests { has_korean_char: true, is_all_uppercase: false, ascii_starts_at_beginning: false, + roman_section_continues_from_previous_word: false, skip_count: &mut skip, state: &mut state, result: &mut out, @@ -487,6 +515,7 @@ mod tests { has_korean_char: false, is_all_uppercase: true, ascii_starts_at_beginning: true, + roman_section_continues_from_previous_word: false, skip_count: &mut skip, state: &mut state, result: &mut out, @@ -514,6 +543,7 @@ mod tests { has_korean_char: false, is_all_uppercase: false, ascii_starts_at_beginning: false, + roman_section_continues_from_previous_word: false, skip_count: &mut skip, state: &mut state, result: &mut out, @@ -572,6 +602,7 @@ mod tests { has_korean_char: false, is_all_uppercase: true, ascii_starts_at_beginning: true, + roman_section_continues_from_previous_word: false, skip_count: &mut skip, state: &mut state, result: &mut out, diff --git a/libs/braillify/src/rules/korean/rule_69.rs b/libs/braillify/src/rules/korean/rule_69.rs index 602e12cf..4758eac8 100644 --- a/libs/braillify/src/rules/korean/rule_69.rs +++ b/libs/braillify/src/rules/korean/rule_69.rs @@ -3,7 +3,7 @@ use std::sync::OnceLock; use crate::char_struct::CharType; use crate::rules::RuleMeta; -use crate::rules::context::RuleContext; +use crate::rules::context::{EncoderState, RuleContext}; use crate::rules::english_ueb::span::encode_korean_word; use crate::rules::korean::rule_29::{ENGLISH_CONTINUATION, ROMAN_INDICATOR}; use crate::rules::traits::{BrailleRule, Phase, RuleResult}; @@ -34,13 +34,28 @@ const ASCII_UNIT_MAPPINGS: &[(&str, &str)] = &[ ("kg", "⠴⠅⠛⠲"), ("in", "⠴⠊⠝⠲"), ("mm", "⠴⠍⠍⠲"), - ("min", "⠍⠔⠲"), + ("min", "⠴⠍⠔⠲"), ("cal", "⠴⠉⠁⠇⠲"), ("GB", "⠴⠠⠠⠛⠃⠲"), ("m", "⠴⠍⠲"), ("h", "⠴⠓⠲"), ]; +/// Roman unit symbols printed in the Rule 69 / science-braille unit tables +/// which do not all have a Unicode square-unit presentation form. Their cells +/// are derived through the ordinary Rule 37 letter encoder below, rather than +/// duplicated here as an input-to-output lookup table. +const PDF_ASCII_UNIT_SYMBOLS: &[&str] = &[ + "yard", "sec", "dyn", "kgf", "mmHg", "erg", "HP", "dB", "Hz", "pH", "hPa", +]; + +/// SI prefixes are case-sensitive. This set is used only as the grammar for a +/// complete measured-unit suffix; it never reclassifies a separated Roman word. +const SI_PREFIXES: &[&str] = &[ + "q", "r", "y", "z", "a", "f", "p", "n", "u", "m", "c", "d", "da", "h", "k", "M", "G", "T", "P", + "E", "Z", "Y", "R", "Q", +]; + const PERCENT_ABBREVIATION_MAPPINGS: &[(&str, &str)] = &[("%ile", "⠴⠏⠞"), ("%p", "⠴⠏⠏")]; const SEPARATED_SYMBOLS: &[char] = &['%', '‰', '°', '℃', '℉']; @@ -52,27 +67,29 @@ fn encode_unicode_cells(unicode: &str) -> Vec { .collect() } -/// Unicode's CJK compatibility block contains square presentation forms for -/// Roman unit symbols (`㎏` → `kg`, `㎓` → `GHz`, `㎥` → `m3`). Rules 68/69 -/// define the transcription from the semantic Roman unit, so recognize the -/// whole Unicode family from its compatibility decomposition instead of adding -/// one input-specific mapping per glyph. Japanese square words and other CJK -/// compatibility characters are rejected by the component grammar. -fn compatibility_unit_decomposition(c: char) -> Option> { +/// Unicode contains compatibility presentation forms for Roman unit symbols: +/// CJK square units (`㎏` → `kg`, `㎓` → `GHz`, `㎥` → `m3`) and the letterlike +/// litre sign (`ℓ` → `l`). Rules 68/69 define the transcription from the +/// semantic Roman unit, so recognize these families from their compatibility +/// decomposition instead of assigning input-specific braille cells. Japanese +/// square words and other CJK compatibility characters are rejected by the +/// code-point ranges and component grammar. +pub(crate) fn compatibility_unit_decomposition(c: char) -> Option> { // Unicode CJK Compatibility contains non-unit square abbreviations too // (`㏑` ln, `㏒` log, `㏚` PR). Keep the accepted ranges to scientific and // measurement symbols; the component grammar is an additional guard, not // the sole evidence that a square abbreviation is a unit. - let is_unit_codepoint = matches!( - c as u32, - 0x3371..=0x337a + let is_unit_codepoint = c == 'ℓ' + || matches!( + c as u32, + 0x3371..=0x337a | 0x3380..=0x33c6 | 0x33c8..=0x33cc | 0x33ce..=0x33d0 | 0x33d3..=0x33d9 | 0x33db..=0x33df | 0x33ff - ); + ); if !is_unit_codepoint || super::rule_68::is_rule_68_symbol(c) { return None; } @@ -85,12 +102,21 @@ fn compatibility_unit_decomposition(c: char) -> Option> { .then_some(parts) } +/// Unicode presentation forms whose printed meaning is a Roman measurement +/// unit. Rule 68 owns `㎡` and `㏊`; the remaining forms are decoded by this +/// module from their compatibility decomposition. Keeping this predicate at +/// the shared Roman/number state boundary prevents an intervening glyph from +/// breaking a section that began with Roman text and continued through digits. +pub(crate) fn is_compatibility_unit_presentation(c: char) -> bool { + matches!(c, '㎡' | '㏊') || compatibility_unit_decomposition(c).is_some() +} + /// Rule 69 delegates only to rule 37's multi-letter groupsigns. This is not /// ordinary UEB word encoding: whole-word signs and shortforms are disabled, /// and a lower groupsign cannot consume the whole entry run (`in` is spelled /// `i`-`n`, while the same `in` may contract inside `min`). fn encode_rule_69_unit_letters(letters: &[char]) -> Result, String> { - match encode_korean_word(letters, false, false, false, true, false) { + match encode_korean_word(letters, false, false, false, true, false, false, false) { Some(encoded) => Ok(encoded), None => Err(format!( "cannot encode rule 69 Roman unit letters: {}", @@ -251,6 +277,72 @@ fn chars_start_with_ascii(tail: &[char], s: &str) -> bool { s.bytes().zip(tail.iter()).all(|(b, c)| (b as char) == *c) } +fn encode_ascii_unit_letters(spelling: &[char]) -> Option> { + encode_compatibility_unit(spelling, true, true).ok() +} + +/// `Wh` and `Ah` are products of the Rule-69 Roman unit symbols watt/ampere +/// and hour. Accept every case-sensitive SI-prefixed form (`mAh`, `kWh`, +/// `GWh`, ...), rather than enumerating values observed in a corpus. +fn is_si_prefixed_electrical_hour_unit(spelling: &str) -> bool { + let Some(head) = spelling.strip_suffix('h') else { + return false; + }; + let Some(base) = head.chars().last() else { + return false; + }; + if !matches!(base, 'A' | 'W') { + return false; + } + let prefix = &head[..head.len() - base.len_utf8()]; + prefix.is_empty() || SI_PREFIXES.contains(&prefix) +} + +/// The litre symbol may be printed as either `l` or `L`; an SI prefix retains +/// its case (`dL`, `mL`, `kL`, ...). Rule 69's printed `㎗` example owns the +/// decilitre semantics, while this grammar preserves the case of an ASCII +/// spelling instead of copying the compatibility character's lowercase NFKC. +fn is_si_prefixed_litre_unit(spelling: &str) -> bool { + let Some(base) = spelling.chars().last() else { + return false; + }; + if !matches!(base, 'l' | 'L') { + return false; + } + let prefix = &spelling[..spelling.len() - base.len_utf8()]; + prefix.is_empty() || SI_PREFIXES.contains(&prefix) +} + +/// Rule 69 prints `GB` as its storage-unit example. Treat the same `B` unit +/// with another case-sensitive SI prefix as one unit symbol, rather than +/// enumerating each storage capacity. A bare `B` remains ambiguous with a +/// Roman letter and therefore is not selected by this automatic prose route. +fn is_si_prefixed_byte_unit(spelling: &str) -> bool { + let Some(prefix) = spelling.strip_suffix('B') else { + return false; + }; + !prefix.is_empty() && SI_PREFIXES.contains(&prefix) +} + +fn standard_ascii_unit_candidate(tail: &[char]) -> Option<(Vec, usize)> { + let consumed = tail + .iter() + .take_while(|ch| ch.is_ascii_alphabetic()) + .count(); + if consumed == 0 { + return None; + } + let spelling = tail[..consumed].iter().collect::(); + if !PDF_ASCII_UNIT_SYMBOLS.contains(&spelling.as_str()) + && !is_si_prefixed_electrical_hour_unit(&spelling) + && !is_si_prefixed_litre_unit(&spelling) + && !is_si_prefixed_byte_unit(&spelling) + { + return None; + } + Some((encode_ascii_unit_letters(&tail[..consumed])?, consumed)) +} + /// ASCII spellings that are canonically exposed by the same Unicode /// compatibility-unit family already accepted above. This derives the unit /// lexicon from semantic unit code points instead of maintaining a second @@ -312,11 +404,21 @@ fn compatibility_ascii_unit_spellings() -> &'static [(String, Vec)] { pub(crate) fn encode_ascii_unit(word: &[char], index: usize) -> Option<(Vec, usize)> { let tail = &word[index..]; - ASCII_UNIT_MAPPINGS + let explicit = ASCII_UNIT_MAPPINGS .iter() .filter(|(unit, _)| chars_start_with_ascii(tail, unit)) .max_by_key(|(unit, _)| unit.len()) - .map(|(unit, unicode)| (encode_unicode_cells(unicode), unit.len())) + .map(|(unit, unicode)| (encode_unicode_cells(unicode), unit.len())); + let standard = standard_ascii_unit_candidate(tail); + + match (explicit, standard) { + (Some(explicit), Some(standard)) if explicit.1 == standard.1 => { + (explicit.0 == standard.0).then_some(explicit) + } + (Some(explicit), Some(standard)) if explicit.1 < standard.1 => Some(standard), + (Some(explicit), _) => Some(explicit), + (None, standard) => standard, + } } /// Numeric-compact Rule 69 path. Compatibility-derived spellings are limited @@ -345,6 +447,45 @@ fn encode_numeric_ascii_unit(word: &[char], index: usize) -> Option<(Vec, us Some((encoded.clone(), unit.len())) } +fn encode_complete_numeric_ascii_unit(word: &[char], index: usize) -> Option<(Vec, usize)> { + let (encoded, consumed) = encode_numeric_ascii_unit(word, index)?; + if word + .get(index + consumed) + .is_some_and(|ch| ch.is_ascii_alphabetic()) + { + return None; + } + Some((encoded, consumed)) +} + +/// Length of a complete Rule-69 ASCII unit beginning at `index`. +/// +/// Token-level capitalization uses this predicate to leave a separated +/// uppercase unit (`5 GB`, `350 PB`) to Rule 69. Otherwise it would emit a +/// Roman/capital prefix before the character rule emits the unit's own prefix. +pub(crate) fn complete_ascii_unit_len(word: &[char], index: usize) -> Option { + encode_complete_numeric_ascii_unit(word, index).map(|(_, consumed)| consumed) +} + +pub(crate) fn is_ascii_unit_chain_slash(word: &[char], index: usize) -> bool { + if word.get(index) != Some(&'/') || index == 0 { + return false; + } + + let left_start = (0..index) + .rev() + .take_while(|position| word[*position].is_ascii_alphabetic()) + .last() + .unwrap_or(index); + let left_len = index.saturating_sub(left_start); + let left_is_complete = left_len > 0 + && encode_complete_numeric_ascii_unit(word, left_start) + .is_some_and(|(_, consumed)| consumed == left_len); + let right_is_complete = encode_complete_numeric_ascii_unit(word, index + 1).is_some(); + + left_is_complete && right_is_complete +} + fn encode_percent_abbreviation(word: &[char], index: usize) -> Option<(Vec, usize)> { let tail = &word[index..]; for (abbr, unicode) in PERCENT_ABBREVIATION_MAPPINGS { @@ -373,16 +514,81 @@ pub(crate) fn parse_numeric_ascii_unit_prefix(word: &[char]) -> Option<(String, } let numeric = word[..numeric_len].iter().collect::(); - let (unit, consumed) = encode_numeric_ascii_unit(word, numeric_len)?; - if word - .get(numeric_len + consumed) - .is_some_and(|ch| ch.is_ascii_alphabetic()) - { - return None; - } + let (unit, consumed) = encode_complete_numeric_ascii_unit(word, numeric_len)?; Some((numeric, unit, numeric_len + consumed)) } +fn numeric_component_len(word: &[char], start: usize) -> usize { + let len = word[start..] + .iter() + .take_while(|ch| ch.is_ascii_digit() || matches!(ch, ',' | '.')) + .count(); + if word[start..start + len].iter().any(char::is_ascii_digit) { + len + } else { + 0 + } +} + +/// Parse a complete Rule-69 measurement expression which starts with a +/// number, including a range (`3.5~8.5m`), a Roman-unit quotient +/// (`240mg/dL`), or a Rule-50 middle-dot list whose every member owns a +/// complete unit (`256GB·512GB·1TB`). This is a routing predicate only: the +/// ordinary Korean character rules still emit every number, range sign, +/// middle dot, slash and unit. +/// +/// Requiring every slash component and the final expression to contain a +/// recognized Rule-69 unit keeps general fractions, dates, model numbers and +/// arbitrary ASCII suffixes outside this route. +pub(crate) fn parse_numeric_ascii_unit_expression(word: &[char]) -> Option { + let mut cursor = 0usize; + let mut saw_unit = false; + let mut current_component_requires_unit = false; + + loop { + let numeric_len = numeric_component_len(word, cursor); + if numeric_len == 0 { + return None; + } + cursor += numeric_len; + + let mut component_has_unit = false; + if let Some((_, unit_len)) = encode_complete_numeric_ascii_unit(word, cursor) { + cursor += unit_len; + saw_unit = true; + component_has_unit = true; + } + + while component_has_unit && word.get(cursor) == Some(&'/') { + let unit_start = cursor + 1; + let Some((_, unit_len)) = encode_complete_numeric_ascii_unit(word, unit_start) else { + break; + }; + cursor = unit_start + unit_len; + saw_unit = true; + component_has_unit = true; + } + + if current_component_requires_unit && !component_has_unit { + return None; + } + + if word.get(cursor).is_some_and(|ch| matches!(ch, '~' | '∼')) { + cursor += 1; + current_component_requires_unit = false; + continue; + } + if word.get(cursor) == Some(&'·') && component_has_unit { + cursor += 1; + current_component_requires_unit = true; + continue; + } + break; + } + + saw_unit.then_some(cursor) +} + fn trim_recent_english_indicator(result: &mut Vec) { if result .last() @@ -393,7 +599,9 @@ fn trim_recent_english_indicator(result: &mut Vec) { } /// Rules 33/34 override rule 69's ordinary trailing Roman terminator when a -/// listed Korean punctuation mark or an enclosing mark closes the Roman run. +/// listed Korean punctuation mark or an enclosing mark closes the Roman run; +/// rule 35 likewise omits it when an attached digit continues the Roman/number +/// chain. /// Unit encoders include their ordinary terminator so standalone/end/Korean /// boundaries stay unchanged; this helper applies only at the actual following /// input boundary. @@ -409,13 +617,92 @@ fn omit_roman_terminator_before_boundary( && word .get(boundary_index + 1) .is_some_and(|next| is_roman_unit_component(*next)); - if (skips_for_punctuation || continues_through_slash) + let continues_into_number = word + .get(boundary_index) + .is_some_and(|next| next.is_ascii_digit()); + if (skips_for_punctuation || continues_through_slash || continues_into_number) && encoded.last() == Some(&crate::unicode::decode_unicode('⠲')) { encoded.pop(); } } +/// A comma after a Roman unit remains inside the same Roman section when the +/// next print item begins with another Roman/numeric item. Rule 33 switches +/// to the Korean comma only at an actual Roman-to-Korean boundary; a later +/// Korean particle does not retroactively change the comma in a measurement +/// list such as `173cm, 68kg의`. +fn roman_unit_comma_continues_section(ctx: &RuleContext, boundary_index: usize) -> bool { + // Rule 68's superscript cell closes a compact square/cubic unit without a + // Roman terminator. The following comma is therefore Korean punctuation, + // and a later unit starts a fresh Roman section. + let closes_with_superscript = ctx.current_char() == '㎡' + || compatibility_unit_decomposition(ctx.current_char()) + .is_some_and(|parts| matches!(parts.last(), Some('2' | '3'))); + if closes_with_superscript { + return false; + } + + ctx.word_chars.get(boundary_index) == Some(&',') + && crate::english_logic::should_render_symbol_as_english( + ctx.state.english_indicator, + true, + ctx.state.doc_summary.is_english_majority, + &ctx.state.parenthesis_stack, + ',', + ctx.word_chars, + boundary_index, + ctx.remaining_words, + ) +} + +/// Rules 29 and 35 keep a separated following Roman/number word in the same +/// section. This is the cross-word counterpart of +/// [`omit_roman_terminator_before_boundary`], whose look-ahead is intentionally +/// limited to the current print word. +fn roman_unit_continues_into_next_word(ctx: &RuleContext, boundary_index: usize) -> bool { + boundary_index == ctx.word_chars.len() + && ctx + .remaining_words + .first() + .and_then(|word| word.chars().next()) + .is_some_and(|ch| ch.is_ascii_alphanumeric()) +} + +fn omit_trailing_roman_terminator(encoded: &mut Vec) { + if encoded.last() == Some(&crate::unicode::decode_unicode('⠲')) { + encoded.pop(); + } +} + +/// Rule 35 keeps a Roman unit directly following a number in the already-open +/// Roman section. The number temporarily places the emitter in +/// `roman_number_chain`; in that state the unit's self-contained Rule-69 entry +/// marker would be a duplicate. +fn omit_unit_entry_in_open_roman_number_chain(encoded: &mut Vec, state: &EncoderState) { + if state.roman_number_chain && encoded.first() == Some(&ROMAN_INDICATOR) { + encoded.remove(0); + } +} + +/// Apply the common Rule 29/33/34/35 boundary behavior to a self-contained +/// Roman unit encoding and report whether the section continues into a later +/// print word. Rule 68 reuses this for its two Roman unit presentations. +pub(crate) fn adjust_roman_unit_boundary( + ctx: &RuleContext, + boundary_index: usize, + encoded: &mut Vec, +) -> bool { + omit_unit_entry_in_open_roman_number_chain(encoded, ctx.state); + omit_roman_terminator_before_boundary(encoded, ctx.word_chars, boundary_index); + let comma_continues = roman_unit_comma_continues_section(ctx, boundary_index); + let separated_continues = roman_unit_continues_into_next_word(ctx, boundary_index); + if separated_continues { + omit_trailing_roman_terminator(encoded); + } + comma_continues || separated_continues +} + fn should_insert_separator_after_symbol(symbol: char, next: Option) -> bool { SEPARATED_SYMBOLS.contains(&symbol) && next.is_some_and(crate::utils::is_korean_char) } @@ -442,7 +729,7 @@ impl BrailleRule for Rule69 { || matches!(ctx.char_type, CharType::English(_) if (is_numeric_or_unit_context(ctx) || (ctx.index == 0 && word_looks_like_unit_chain(ctx.word_chars))) - && encode_ascii_unit(ctx.word_chars, ctx.index).is_some()) + && encode_complete_numeric_ascii_unit(ctx.word_chars, ctx.index).is_some()) } fn apply(&self, ctx: &mut RuleContext) -> Result { @@ -451,12 +738,15 @@ impl BrailleRule for Rule69 { && let Some((numeric, mut unit, consumed)) = parse_numeric_ascii_unit_prefix(ctx.word_chars) { - omit_roman_terminator_before_boundary(&mut unit, ctx.word_chars, consumed); + let continues = adjust_roman_unit_boundary(ctx, consumed, &mut unit); let mut encoded = crate::encode(&numeric)?; encoded.extend(unit); ctx.emit_slice(&encoded); - ctx.state.is_english = false; + ctx.state.is_english = continues; ctx.state.needs_english_continuation = false; + if continues { + ctx.state.roman_number_chain = false; + } *ctx.skip_count = consumed.saturating_sub(1); return Ok(RuleResult::Consumed); } @@ -464,17 +754,20 @@ impl BrailleRule for Rule69 { if matches!(ctx.char_type, CharType::English(_)) && (is_numeric_or_unit_context(ctx) || (ctx.index == 0 && word_looks_like_unit_chain(ctx.word_chars))) - && let Some((mut encoded, consumed)) = encode_ascii_unit(ctx.word_chars, ctx.index) + && let Some((mut encoded, consumed)) = + encode_complete_numeric_ascii_unit(ctx.word_chars, ctx.index) { - omit_roman_terminator_before_boundary( - &mut encoded, - ctx.word_chars, - ctx.index + consumed, - ); + let continues = adjust_roman_unit_boundary(ctx, ctx.index + consumed, &mut encoded); + if roman_unit_chain_continues_before(ctx) && encoded.first() == Some(&ROMAN_INDICATOR) { + encoded.remove(0); + } trim_recent_english_indicator(ctx.result); ctx.emit_slice(&encoded); - ctx.state.is_english = false; + ctx.state.is_english = continues; ctx.state.needs_english_continuation = false; + if continues { + ctx.state.roman_number_chain = false; + } *ctx.skip_count = consumed.saturating_sub(1); return Ok(RuleResult::Consumed); } @@ -526,14 +819,29 @@ impl BrailleRule for Rule69 { } if let Some(parts) = compatibility_unit_decomposition(ctx.current_char()) { - let continues_from_previous = roman_unit_chain_continues_before(ctx); - let continues_after = roman_unit_chain_continues_after(ctx); + let continues_from_previous = + roman_unit_chain_continues_before(ctx) || ctx.state.roman_number_chain; + let continues_within_word = roman_unit_chain_continues_after(ctx); + let separated_continues = roman_unit_continues_into_next_word(ctx, ctx.index + 1); + let continues_after = continues_within_word || separated_continues; let mut encoded = encode_compatibility_unit(&parts, !continues_from_previous, !continues_after)?; - omit_roman_terminator_before_boundary(&mut encoded, ctx.word_chars, ctx.index + 1); + let continues = adjust_roman_unit_boundary(ctx, ctx.index + 1, &mut encoded); ctx.emit_slice(&encoded); - ctx.state.is_english = false; + if matches!(parts.last(), Some('2' | '3')) + && ctx + .next_char() + .is_some_and(super::rule_44::is_number_confusable_korean_char) + { + ctx.emit(0); + } + // Same-word compatibility-unit chains are emitted component by + // component and must retain their existing closed state. Only a + // continuation across a print space needs to survive into the next + // Word token. + ctx.state.is_english = continues; ctx.state.needs_english_continuation = false; + ctx.state.roman_number_chain = false; return Ok(RuleResult::Consumed); } @@ -558,10 +866,13 @@ impl BrailleRule for Rule69 { mod tests { use super::{ Rule69, compatibility_ascii_unit_owners, compatibility_unit_decomposition, - encode_ascii_unit, encode_compatibility_unit, encode_numeric_ascii_unit, - encode_percent_abbreviation, encode_rule_69_unit_letters, encode_unicode_cells, - omit_roman_terminator_before_boundary, parse_numeric_ascii_unit_prefix, - retain_unambiguous_ascii_unit_spellings, word_looks_like_unit_chain, + encode_ascii_unit, encode_compatibility_unit, encode_complete_numeric_ascii_unit, + encode_numeric_ascii_unit, encode_percent_abbreviation, encode_rule_69_unit_letters, + encode_unicode_cells, is_ascii_unit_chain_slash, is_si_prefixed_byte_unit, + is_si_prefixed_electrical_hour_unit, is_si_prefixed_litre_unit, + omit_roman_terminator_before_boundary, parse_numeric_ascii_unit_expression, + parse_numeric_ascii_unit_prefix, retain_unambiguous_ascii_unit_spellings, + word_looks_like_unit_chain, }; #[rstest::rstest] @@ -583,6 +894,7 @@ mod tests { #[case::milliwatt('㎽', "mW")] #[case::kilowatt('㎾', "kW")] #[case::sievert('㏜', "Sv")] + #[case::litre('ℓ', "l")] fn decomposes_compatibility_unit_symbols(#[case] input: char, #[case] expected: &str) { assert_eq!( compatibility_unit_decomposition(input), @@ -758,7 +1070,10 @@ mod tests { #[rstest::rstest] #[case::longest_derived("30mW", 4)] #[case::hectare_derived("15.2ha", 6)] - #[case::reject_partial_suffix("30kWh", 0)] + #[case::compound_kilowatt_hour("30kWh", 5)] + #[case::compound_milliampere_hour("900mAh", 6)] + #[case::pdf_millimetres_of_mercury("140mmHg", 7)] + #[case::reject_partial_suffix("30kWhours", 0)] fn parses_only_complete_compatibility_derived_units( #[case] input: &str, #[case] expected_consumed: usize, @@ -770,6 +1085,181 @@ mod tests { ); } + #[rstest::rstest] + #[case::simple_unit("180cm", 5)] + #[case::range_with_final_unit("3.5~8.5m", 8)] + #[case::unicode_range_with_final_unit("3∼5kg", 5)] + #[case::compound_unit_quotient("240mg/dL", 8)] + #[case::middle_dot_unit_list("256GB·512GB·1TB", 15)] + #[case::middle_dot_mixed_units("3kg·4cm", 7)] + #[case::range_without_unit("3.5~8.5", 0)] + #[case::fraction_with_units_as_operands("3m/4m", 2)] + #[case::middle_dot_missing_left_unit("3·4kg", 0)] + #[case::middle_dot_missing_right_unit("3kg·4", 0)] + #[case::middle_dot_numeric_list("54·55·56", 0)] + #[case::unknown_ascii_suffix("3.5~8.5models", 0)] + fn recognizes_only_complete_numeric_unit_expressions( + #[case] input: &str, + #[case] expected_consumed: usize, + ) { + let chars = input.chars().collect::>(); + assert_eq!( + parse_numeric_ascii_unit_expression(&chars).unwrap_or(0), + expected_consumed + ); + } + + #[rstest::rstest] + #[case::ascii_range("범위는 3.5~8.5m이다", "⠼⠉⠲⠑⠈⠔⠼⠓⠲⠑⠴⠍⠲")] + #[case::unicode_range("범위는 3∼5kg이다", "⠼⠉⠈⠔⠼⠑⠴⠅⠛⠲")] + fn numeric_unit_ranges_stay_on_korean_number_and_unit_rules( + #[case] input: &str, + #[case] expected_segment: &str, + ) { + let actual = crate::encode_to_unicode(input).unwrap(); + assert!( + actual.contains(expected_segment), + "missing standard range {expected_segment:?} in {actual:?}" + ); + } + + #[rstest::rstest] + #[case::storage_capacities("256GB·512GB·1TB")] + #[case::mixed_measurements("3kg·4cm")] + fn separated_middle_dot_unit_lists_stay_on_korean_rules(#[case] expression: &str) { + let separated = crate::encode_to_unicode(&format!("가는 {expression} 나다")).unwrap(); + let attached = crate::encode_to_unicode(&format!("가는 {expression}이다")).unwrap(); + let segment = attached + .strip_prefix(&crate::encode_to_unicode("가는 ").unwrap()) + .and_then(|tail| tail.strip_suffix(&crate::encode_to_unicode("이다").unwrap())) + .expect("attached control must contain the measurement segment"); + + assert!( + separated.contains(segment), + "measurement segment {segment:?} was rerouted in {separated:?}" + ); + } + + #[rstest::rstest] + #[case::pdf_gigabyte("가는 5 GB 나다", "⠫⠉⠵⠀⠼⠑⠀⠴⠠⠠⠛⠃⠲⠀⠉⠊")] + #[case::petabyte("가는 5 PB 나다", "⠫⠉⠵⠀⠼⠑⠀⠴⠠⠠⠏⠃⠲⠀⠉⠊")] + #[case::terabyte("가는 5 TB 나다", "⠫⠉⠵⠀⠼⠑⠀⠴⠠⠠⠞⠃⠲⠀⠉⠊")] + fn separated_uppercase_units_emit_one_roman_capital_prefix( + #[case] input: &str, + #[case] expected: &str, + ) { + assert_eq!(crate::encode_to_unicode(input).as_deref(), Ok(expected)); + } + + /// Rules 29 and 35 omit a unit's Roman terminator when another separated + /// Roman/number item follows. Exercise both ASCII and compatibility-unit + /// Rule-69 paths and the Roman-word continuation path. + #[rstest::rstest] + #[case::ascii_unit_before_number("가는 12km 3구간", "⠫⠉⠵⠀⠼⠁⠃⠴⠅⠍⠀⠼⠉⠈⠍⠫⠒")] + #[case::compatibility_unit_before_number("가는 8.4㎞ 2구간", "⠫⠉⠵⠀⠼⠓⠲⠙⠴⠅⠍⠀⠼⠃⠈⠍⠫⠒")] + #[case::ascii_unit_before_roman("가는 1TB SSD 나다", "⠫⠉⠵⠀⠼⠁⠴⠠⠠⠞⠃⠀⠠⠠⠎⠎⠙⠲⠀⠉⠊")] + fn separated_rule_69_unit_continues_roman_number_section( + #[case] input: &str, + #[case] expected: &str, + ) { + assert_eq!(crate::encode_to_unicode(input).as_deref(), Ok(expected)); + } + + #[test] + fn ascii_unit_quotient_preserves_printed_case_in_one_roman_section() { + let actual = crate::encode_to_unicode("수치는 240mg/dL이다").unwrap(); + assert!( + actual.contains("⠼⠃⠙⠚⠴⠍⠛⠸⠌⠙⠠⠇⠲"), + "unexpected Rule-69 quotient: {actual}" + ); + } + + #[rstest::rstest] + #[case::decilitre("dL", true)] + #[case::millilitre_lower_l("ml", true)] + #[case::millilitre_upper_l("mL", true)] + #[case::litre("L", true)] + #[case::word_ending_l("model", false)] + #[case::invalid_prefix("xL", false)] + fn recognizes_case_preserving_si_litre_symbols(#[case] spelling: &str, #[case] expected: bool) { + assert_eq!(is_si_prefixed_litre_unit(spelling), expected); + } + + #[rstest::rstest] + #[case::pdf_gigabyte("GB", true)] + #[case::terabyte("TB", true)] + #[case::megabyte("MB", true)] + #[case::bare_letter("B", false)] + #[case::wrong_base_case("Gb", false)] + #[case::unknown_prefix("xB", false)] + fn recognizes_si_prefixed_byte_units(#[case] spelling: &str, #[case] expected: bool) { + assert_eq!(is_si_prefixed_byte_unit(spelling), expected); + } + + #[rstest::rstest] + #[case::milligram_per_decilitre("240mg/dL", 5, true)] + #[case::calorie_per_minute("cal/min", 3, true)] + #[case::fraction_operands("3m/4m", 2, false)] + #[case::arbitrary_letters("F/N", 1, false)] + fn recognizes_only_slashes_between_complete_unit_components( + #[case] input: &str, + #[case] slash_index: usize, + #[case] expected: bool, + ) { + let chars = input.chars().collect::>(); + assert_eq!( + is_ascii_unit_chain_slash(&chars, slash_index), + expected, + "input={input}" + ); + } + + #[rstest::rstest] + #[case::watt_hour("Wh", true)] + #[case::gigawatt_hour("GWh", true)] + #[case::milliampere_hour("mAh", true)] + #[case::decaampere_hour("daAh", true)] + #[case::missing_hour("GW", false)] + #[case::unknown_prefix("xWh", false)] + #[case::wrong_case("gWh", false)] + fn recognizes_si_prefixed_electrical_hour_units( + #[case] spelling: &str, + #[case] expected: bool, + ) { + assert_eq!(is_si_prefixed_electrical_hour_unit(spelling), expected); + } + + /// Rule 69 and its science-braille unit table: a complete Roman-written + /// unit is one section, including its ordinary entry/exit indicators. + #[rstest::rstest] + #[case::minute("90min이다", "⠼⠊⠚⠴⠍⠔⠲⠕⠊")] + #[case::millimetres_of_mercury("140mmHg이다", "⠼⠁⠙⠚⠴⠍⠍⠠⠓⠛⠲⠕⠊")] + #[case::kilogram_force("75.5kgf이다", "⠼⠛⠑⠲⠑⠴⠅⠛⠋⠲⠕⠊")] + #[case::gigawatt_hour("13GWh이다", "⠼⠁⠉⠴⠠⠛⠠⠺⠓⠲⠕⠊")] + #[case::milliampere_hour("900mAh이다", "⠼⠊⠚⠚⠴⠍⠠⠁⠓⠲⠕⠊")] + fn compact_standard_units_form_one_roman_section(#[case] input: &str, #[case] expected: &str) { + assert_eq!(crate::encode_to_unicode(input).unwrap(), expected); + } + + #[rstest::rstest] + #[case::korean_thousand("1천59ha", "1천59㏊")] + #[case::korean_ten_thousand("3만433ha", "3만433㏊")] + fn mixed_korean_numbers_keep_the_complete_ascii_unit( + #[case] ascii: &str, + #[case] compatibility: &str, + ) { + assert_eq!( + crate::encode_to_unicode(ascii).unwrap(), + crate::encode_to_unicode(compatibility).unwrap() + ); + } + + #[test] + fn complete_unit_matching_rejects_an_ascii_word_with_a_unit_prefix() { + let chars = "harmony".chars().collect::>(); + assert!(encode_complete_numeric_ascii_unit(&chars, 0).is_none()); + } + #[rstest::rstest] #[case::inch('㏌', "in")] #[case::centimetre('㎝', "cm")] @@ -797,10 +1287,52 @@ mod tests { #[case::milliwatt("㎽", "⠴⠍⠠⠺⠲")] #[case::kilowatt("㎾", "⠴⠅⠠⠺⠲")] #[case::sievert("㏜", "⠴⠠⠎⠧⠲")] + #[case::litre("ℓ", "⠴⠇⠲")] fn encodes_compatibility_unit_symbols(#[case] input: &str, #[case] expected: &str) { assert_eq!(crate::encode_to_unicode(input).unwrap(), expected); } + /// Rules 29, 33, 34 and 35 depend on the semantic Roman unit, not on + /// whether print used ordinary letters or a Unicode compatibility glyph. + #[rstest::rstest] + #[case::comma_separated_megawatts("수치는 12.5㎿, 30㎿이다", "수치는 12.5MW, 30MW이다")] + #[case::unit_after_roman_number_chain("용량은 Lidocaine 5㎖이다", "용량은 Lidocaine 5ml이다")] + #[case::unit_after_separated_roman_number_chain("대역은 5G 28㎓이다", "대역은 5G 28GHz이다")] + #[case::rule_68_unit_before_closing_parenthesis("면적은(141㏊)이다", "면적은(141ha)이다")] + fn compatibility_unit_presentations_match_semantic_roman_spelling( + #[case] presentation: &str, + #[case] expanded: &str, + ) { + assert_eq!( + crate::encode_to_unicode(presentation).unwrap(), + crate::encode_to_unicode(expanded).unwrap(), + "presentation={presentation:?}" + ); + } + + #[test] + fn superscript_closed_compatibility_units_restart_after_a_comma() { + let actual = crate::encode_to_unicode("농도는 29㎍/㎥, 16㎍/㎥이다").unwrap(); + assert!( + actual.contains("⠍⠘⠼⠉⠐⠀⠼⠁⠋⠴⠨⠍"), + "square/cubic unit must close before Korean comma: {actual:?}" + ); + } + + #[rstest::rstest] + #[case::confusable_counter("3㎠당", true)] + #[case::vowel_initial_predicate("3㎠이다", false)] + fn compatibility_unit_superscript_separates_only_number_confusable_korean( + #[case] input: &str, + #[case] expects_separator: bool, + ) { + let actual = crate::encode_to_unicode(input).unwrap(); + let unit = "⠴⠉⠍⠘⠼⠃"; + let unit_end = actual.find(unit).expect("square-centimetre cells") + unit.len(); + let follows_with_space = actual[unit_end..].starts_with('⠀'); + assert_eq!(follows_with_space, expects_separator, "input={input}"); + } + #[test] fn slash_after_korean_starts_a_new_roman_unit_chain() { let encoded = crate::encode_to_unicode("시/㎏").unwrap(); @@ -821,16 +1353,20 @@ mod tests { assert_eq!(crate::encode_to_unicode(input).unwrap(), expected); } - /// Rules 33/34/69: the ordinary unit terminator is omitted only when the - /// actual following boundary is one of the standard's punctuation/enclosing - /// marks. These are full-encoder checks, including numeric-prefix routing. + /// Rules 33/34/35/69: the ordinary unit terminator is omitted when the + /// actual following boundary is a standard punctuation/enclosing mark or + /// an attached digit continuing the Roman/number chain. These are + /// full-encoder checks, including numeric-prefix routing. #[rstest::rstest] #[case::kilogram_in_parentheses("상자(20kg)당", "⠼⠃⠚⠴⠅⠛⠠⠴", "⠴⠅⠛⠲⠠⠴")] #[case::centimetre_before_korean_comma("키는 173cm, 몸무게는", "⠼⠁⠛⠉⠴⠉⠍⠐", "⠴⠉⠍⠲⠐")] - #[case::centimetre_before_next_measurement("키 173cm, 68kg", "⠼⠁⠛⠉⠴⠉⠍⠐", "⠴⠉⠍⠲⠐")] + #[case::centimetre_before_next_measurement("키 173cm, 68kg", "⠼⠁⠛⠉⠴⠉⠍⠂", "⠴⠉⠍⠲⠂")] #[case::metre_before_sentence_period("비거리 130m.", "⠼⠁⠉⠚⠴⠍⠲", "⠴⠍⠲⠲")] #[case::compatibility_kilogram_in_parentheses("상자(20㎏)당", "⠼⠃⠚⠴⠅⠛⠠⠴", "⠴⠅⠛⠲⠠⠴")] - fn omits_unit_terminator_at_rule_33_or_34_boundary( + #[case::metre_between_numbers("기록은 2m36이다", "⠼⠃⠴⠍⠼⠉⠋", "⠴⠍⠲⠼")] + #[case::metre_between_larger_numbers("기록은 57m57이다", "⠼⠑⠛⠴⠍⠼⠑⠛", "⠴⠍⠲⠼")] + #[case::compatibility_kilometre_before_number("거리는 2㎞30이다", "⠼⠃⠴⠅⠍⠼⠉⠚", "⠴⠅⠍⠲⠼")] + fn omits_unit_terminator_at_standard_override_boundary( #[case] input: &str, #[case] expected_segment: &str, #[case] forbidden_segment: &str, @@ -846,6 +1382,25 @@ mod tests { ); } + /// Rules 29, 33, 35 and 69: a comma between consecutive measurements is + /// UEB punctuation inside one Roman section. The second unit therefore + /// does not repeat the Roman indicator, even when a Korean particle is + /// attached after that unit. + #[rstest::rstest] + #[case::particle_after_second_unit("키는 173cm, 68kg의 차이다", "⠼⠁⠛⠉⠴⠉⠍⠂⠀⠼⠋⠓⠅⠛⠲")] + #[case::second_unit_at_end("키는 173cm, 68kg", "⠼⠁⠛⠉⠴⠉⠍⠂⠀⠼⠋⠓⠅⠛⠲")] + #[case::nanometre_list("공정은 5nm, 1nm는 다르다", "⠼⠑⠴⠝⠍⠂⠀⠼⠁⠝⠍⠲")] + fn keeps_comma_separated_measurements_in_one_roman_section( + #[case] input: &str, + #[case] expected_segment: &str, + ) { + let actual = crate::encode_to_unicode(input).unwrap(); + assert!( + actual.contains(expected_segment), + "missing continuous Roman measurement list {expected_segment:?} in {actual:?}" + ); + } + /// Rule 69 remains the default outside the rule-33/34 override. End of /// input, a following Korean syllable, and forced slash boundaries retain /// the ordinary Roman terminator. diff --git a/libs/braillify/src/rules/korean/rule_70.rs b/libs/braillify/src/rules/korean/rule_70.rs index 34349a84..250f2991 100644 --- a/libs/braillify/src/rules/korean/rule_70.rs +++ b/libs/braillify/src/rules/korean/rule_70.rs @@ -56,8 +56,17 @@ impl BrailleRule for Rule70 { else { return Ok(RuleResult::Skip); }; + // 제70항은 화살표의 앞뒤를 한 칸씩 띄도록 명시한다. 묵자에 + // 공백이 이미 있으면 별도 Space token이 담당하므로, 같은 token + // 안에 인접 문자가 있을 때만 누락된 한 칸을 보충한다. + if ctx.index > 0 && ctx.result.last() != Some(&0) { + ctx.emit(0); + } let encoded = encode_unicode_cells(unicode); ctx.emit_slice(&encoded); + if ctx.index + 1 < ctx.word_len() { + ctx.emit(0); + } Ok(RuleResult::Consumed) } } @@ -76,6 +85,17 @@ mod tests { assert_eq!(encode_unicode_cells(expected), encode_enclosed_arrow(input)); } + /// 제70항 — 화살표 앞뒤 한 칸은 묵자 공백 유무와 무관하게 보장하며, + /// 이미 띄어 쓴 공식 예제에는 공백을 중복하지 않는다. + #[rstest::rstest] + #[case::tight_both_sides("부산→서울", "⠘⠍⠇⠒⠀⠒⠕⠀⠠⠎⠯")] + #[case::tight_right_side("←행주대교", "⠪⠒⠀⠚⠗⠶⠨⠍⠊⠗⠈⠬")] + #[case::tight_left_side("거래량↓", "⠈⠎⠐⠗⠐⠜⠶⠀⠘⠒⠕")] + #[case::already_spaced("부산 → 서울", "⠘⠍⠇⠒⠀⠒⠕⠀⠠⠎⠯")] + fn enforces_one_blank_around_arrow(#[case] input: &str, #[case] expected: &str) { + assert_eq!(crate::encode_to_unicode(input).as_deref(), Ok(expected)); + } + #[test] fn apply_skips_non_korean() { let mut owned = crate::test_helpers::CtxOwned::for_text("A", false); diff --git a/libs/braillify/src/rules/korean/rule_71.rs b/libs/braillify/src/rules/korean/rule_71.rs index b09dcbea..4db73246 100644 --- a/libs/braillify/src/rules/korean/rule_71.rs +++ b/libs/braillify/src/rules/korean/rule_71.rs @@ -16,6 +16,7 @@ const MAPPINGS: &[(char, &str)] = &[ ('^', "⠈⠢"), ('#', "⠸⠹"), ('|', "⠸⠳"), + ('│', "⠸⠳"), ('\\', "⠸⠡"), ('&', "⠈⠯"), ('§', "⠘⠎"), @@ -143,7 +144,19 @@ impl BrailleRule for Rule71 { } else { encoded = encode_unicode_cells(unicode); } + + // U+2502 is the Unicode box-drawing presentation of a vertical line + // segment. Korean Rule 71 assigns the same cells as `|`, while UEB + // 16.4.3 requires a vertical line segment to be surrounded by spaces. + // Insert only missing intra-token boundaries; ordinary Token::Space + // already owns whitespace printed around a standalone line. + if ctx.current_char() == '│' && ctx.prev_char().is_some() { + ctx.emit(0); + } ctx.emit_slice(&encoded); + if ctx.current_char() == '│' && ctx.next_char().is_some() { + ctx.emit(0); + } Ok(RuleResult::Consumed) } } @@ -242,6 +255,17 @@ mod tests { assert_eq!(crate::encode_to_unicode(input).unwrap(), expected); } + /// UEB 3.1.1 keeps `AT&T` in one Roman section and Korean rule 35 keeps + /// the directly following digit in that same section. The two rules must + /// compose without a terminator/re-entry around the ampersand or digit. + #[test] + fn full_encoder_keeps_ampersand_roman_number_chain() { + assert_eq!( + crate::encode_to_unicode("가 AT&T3 나").unwrap(), + "⠫⠀⠴⠠⠠⠁⠞⠈⠯⠠⠞⠼⠉⠀⠉" + ); + } + /// UEB 8.4.2 ends capitals word mode at the nonalphabetic ampersand. /// Wrapping the official UEB 3.1.1 examples in neutral Korean text proves /// that the mixed-document rule-28/29 path restarts capitalization for the @@ -256,6 +280,20 @@ mod tests { assert_eq!(crate::encode_to_unicode(input).as_deref(), Ok(expected)); } + #[rstest::rstest] + #[case::standalone("│", "|")] + #[case::spaced("저자 │ 홍길동", "저자 | 홍길동")] + #[case::attached("제작│감독", "제작 | 감독")] + fn box_drawing_vertical_line_matches_rule_71_print_form( + #[case] presentation: &str, + #[case] standard_print: &str, + ) { + assert_eq!( + crate::encode_to_unicode(presentation), + crate::encode_to_unicode(standard_print) + ); + } + /// Korean Rule 71's spaced Hangul example remains an independently /// delimited information symbol after the attached-Roman exception. #[test] diff --git a/libs/braillify/src/rules/korean/rule_72.rs b/libs/braillify/src/rules/korean/rule_72.rs index 493a9383..af5361b0 100644 --- a/libs/braillify/src/rules/korean/rule_72.rs +++ b/libs/braillify/src/rules/korean/rule_72.rs @@ -1,6 +1,10 @@ +use std::borrow::Cow; + use crate::char_struct::CharType; use crate::rules::RuleMeta; -use crate::rules::context::RuleContext; +use crate::rules::context::{EncodingMode, RuleContext}; +use crate::rules::token::{SpaceKind, Token, WordMeta, WordToken}; +use crate::rules::token_rule::{TokenAction, TokenPhase, TokenRule}; use crate::rules::traits::{BrailleRule, Phase, RuleResult}; pub static META: RuleMeta = RuleMeta { @@ -15,6 +19,8 @@ const MAPPINGS: &[(char, &str)] = &[ ('○', "⠸⠴"), ('□', "⠸⠶"), ('△', "⠸⠬"), + ('▲', "⠸⠬"), + ('▴', "⠸⠬"), ('•', "⠸⠲"), ('◎', "⠸⠴⠴"), ('▣', "⠸⠶⠶"), @@ -31,6 +37,116 @@ pub fn is_rule_72_symbol(c: char) -> bool { MAPPINGS.iter().any(|(candidate, _)| *candidate == c) } +fn is_vertex_decoration(c: char) -> bool { + matches!(c, '\'' | '′' | '″' | '\u{2034}') || ('\u{2070}'..='\u{209f}').contains(&c) +} + +fn consume_triangle_name(chars: &[char], start: usize) -> Option { + if chars.get(start) != Some(&'△') { + return None; + } + + let mut index = start + 1; + for _ in 0..3 { + if !chars.get(index).is_some_and(char::is_ascii_uppercase) { + return None; + } + index += 1; + while chars.get(index).is_some_and(|c| is_vertex_decoration(*c)) { + index += 1; + } + } + Some(index) +} + +/// 수학 점자 제40·42·43항의 삼각형 이름은 `△` 뒤에 꼭짓점 대문자 +/// 세 개를 붙여 쓴다 (`△ABC`, `△A′B′C′`). 합동·닮음 관계로 같은 +/// 형태가 이어지는 경우도 제72항 글머리 기호로 재해석하지 않는다. +fn is_triangle_geometry_expression(chars: &[char]) -> bool { + let Some(mut index) = consume_triangle_name(chars, 0) else { + return false; + }; + + loop { + if index == chars.len() { + return true; + } + if chars[index..] + .iter() + .all(|c| matches!(*c, ',' | '.' | ';' | '?' | '!')) + { + return true; + } + if !matches!(chars[index], '=' | '≡' | '≅' | '∼' | '∽' | '≈') { + return false; + } + index += 1; + let Some(next) = consume_triangle_name(chars, index) else { + return false; + }; + index = next; + } +} + +fn owned_word(text: String) -> Token<'static> { + let chars = text.chars().collect::>(); + let meta = WordMeta::from_chars(&chars); + Token::Word(WordToken { + text: Cow::Owned(text), + chars, + meta, + }) +} + +/// 제72항 글머리 기호가 항목 내용에 붙은 일반 텍스트를, 수식 판정보다 +/// 먼저 `기호 + 한 칸 + 내용`으로 복원한다. 수학 제40·42·43항 문법은 +/// 위의 구조 판정으로 제외한다. +pub struct Rule72AttachedMarkerTokenRule; + +impl TokenRule for Rule72AttachedMarkerTokenRule { + fn phase(&self) -> TokenPhase { + TokenPhase::Normalization + } + + fn priority(&self) -> u16 { + 90 + } + + fn apply<'a>( + &self, + tokens: &[Token<'a>], + index: usize, + _state: &mut crate::rules::context::EncoderState, + ) -> Result, String> { + let Some(Token::Word(word)) = tokens.get(index) else { + return Ok(TokenAction::Noop); + }; + let Some(marker) = word.chars.first().copied() else { + return Ok(TokenAction::Noop); + }; + if !matches!(marker, '△' | '▲' | '▴') || word.chars.len() == 1 || word.meta.has_korean + { + return Ok(TokenAction::Noop); + } + // 제57항의 반복 가림표는 하나의 묶음이다. 첫 `△`를 제72항의 + // 글머리 기호로 떼어 내면 문자 규칙이 반복 개수를 볼 수 없으므로, + // 같은 표지가 연속될 때에는 원래 토큰을 그대로 둔다. + if word.chars.get(1) == Some(&marker) { + return Ok(TokenAction::Noop); + } + if marker == '△' && is_triangle_geometry_expression(&word.chars) { + return Ok(TokenAction::Noop); + } + + let rest = word.chars[1..].iter().collect::(); + Ok(TokenAction::ReplaceMany(vec![ + owned_word(marker.to_string()), + Token::Space(SpaceKind::Regular), + owned_word(rest), + ])) + } +} + pub struct Rule72; impl BrailleRule for Rule72 { @@ -57,10 +173,25 @@ impl BrailleRule for Rule72 { return Ok(RuleResult::Skip); } + // 명시적인 사물부호 문맥(제49항)과 수학 제40항의 `△ABC`는 + // 제72항의 동형 글머리 기호보다 우선한다. + if matches!(ctx.state.current_mode(), EncodingMode::ObjectSymbol) + || is_triangle_geometry_expression(&ctx.word_chars[ctx.index..]) + { + return Ok(RuleResult::Skip); + } + + // 일반 텍스트 추출 과정에서 `△항목`, `△R&D`, `△2025`처럼 글머리 + // 기호와 항목 내용의 경계가 사라질 수 있다. 제72항 공식 예제처럼 + // 둘 사이 한 칸을 복원하되, 문자 종류를 열거하지 않고 비공백 내용이 + // 실제로 이어지는지만 판정한다. + let tight_before_content = matches!(current, '△' | '▲' | '▴') + && ctx.next_char().is_some_and(|c| !c.is_whitespace()); let contextual_marker = ctx.word_len() == 1 || ctx .next_char() .is_some_and(|c| c.is_whitespace() || matches!(c, '(' | '\'' | '"')) + || tight_before_content || matches!(current, '◎' | '▣'); if !contextual_marker { return Ok(RuleResult::Skip); @@ -72,6 +203,9 @@ impl BrailleRule for Rule72 { }; let encoded = encode_unicode_cells(unicode); ctx.emit_slice(&encoded); + if tight_before_content { + ctx.emit(0); + } Ok(RuleResult::Consumed) } } @@ -100,6 +234,8 @@ mod tests { #[case::square('□', "⠸⠶")] #[case::triangle('△', "⠸⠬")] #[case::bullet('•', "⠸⠲")] + #[case::filled_triangle('▲', "⠸⠬")] + #[case::small_filled_triangle('▴', "⠸⠬")] #[case::double_circle('◎', "⠸⠴⠴")] #[case::filled_square('▣', "⠸⠶⠶")] fn apply_encodes_placeholder_markers(#[case] input: char, #[case] expected: &str) { @@ -109,6 +245,71 @@ mod tests { assert_eq!(output, encode_unicode_cells(expected)); } + #[rstest::rstest] + #[case::outline("△문화", "△ 문화")] + #[case::filled("▲문화", "△ 문화")] + #[case::small_filled("▴문화", "△ 문화")] + #[case::roman_item("목록은 △R&D이다", "목록은 △ R&D이다")] + #[case::numeric_item("목록은 △2025년이다", "목록은 △ 2025년이다")] + #[case::quoted_item("목록은 △‘첫째’이다", "목록은 △ ‘첫째’이다")] + #[case::roman_token("목록은 △AI", "목록은 △ AI")] + #[case::numeric_token("목록은 △2025", "목록은 △ 2025")] + fn attached_triangle_list_markers_supply_the_rule_72_boundary( + #[case] input: &str, + #[case] standard_print: &str, + ) { + assert_eq!( + crate::encode_to_unicode(input), + crate::encode_to_unicode(standard_print) + ); + } + + #[test] + fn repeated_triangle_stays_grouped_for_rule_57() { + assert_eq!(crate::encode_to_unicode("△△").unwrap(), "⠸⠬⠬⠇"); + } + + #[test] + fn list_marker_after_attached_previous_item_still_supplies_right_boundary() { + let mut owned = crate::test_helpers::CtxOwned::for_text("첫째△둘째", false); + let mut ctx = owned.ctx_at(2); + + let outcome = Rule72.apply(&mut ctx).unwrap(); + + assert!(matches!(outcome, RuleResult::Consumed)); + assert_eq!(&*ctx.result, &encode_unicode_cells("⠸⠬⠀")); + } + + #[test] + fn math_triangle_name_is_not_reinterpreted_as_a_list_marker() { + let mut owned = crate::test_helpers::CtxOwned::for_text("△ABC", false); + let mut ctx = owned.ctx_at(0); + + let outcome = Rule72.apply(&mut ctx).unwrap(); + + assert!(matches!(outcome, RuleResult::Skip)); + assert!(ctx.result.is_empty()); + } + + #[rstest::rstest] + #[case::plain("△ABC")] + #[case::primed("△A′B′C′")] + #[case::congruent("△ABC≡△DEF")] + #[case::similar_primed("△ABC∽△A′B′C′")] + fn recognizes_math_triangle_grammar(#[case] input: &str) { + let chars = input.chars().collect::>(); + assert!(is_triangle_geometry_expression(&chars)); + } + + #[rstest::rstest] + #[case::acronym_with_gloss("△UAM(도심항공교통)")] + #[case::brand_with_digits("△G3930P")] + #[case::numeric_item("△2025")] + fn attached_list_items_do_not_match_triangle_geometry(#[case] input: &str) { + let chars = input.chars().collect::>(); + assert!(!is_triangle_geometry_expression(&chars)); + } + #[test] fn detects_double_circle_placeholder_symbol() { assert!(is_rule_72_symbol('◎')); diff --git a/libs/braillify/src/rules/korean/rule_english_symbol.rs b/libs/braillify/src/rules/korean/rule_english_symbol.rs index 56504a85..32a65294 100644 --- a/libs/braillify/src/rules/korean/rule_english_symbol.rs +++ b/libs/braillify/src/rules/korean/rule_english_symbol.rs @@ -9,6 +9,7 @@ use crate::char_struct::CharType; use crate::english_logic; use crate::rules::RuleMeta; use crate::rules::context::RuleContext; +use crate::rules::english_ueb::rule_5_7::is_wordsign_letter; use crate::rules::traits::{BrailleRule, Phase, RuleResult}; use crate::symbol_shortcut; use crate::utils; @@ -23,6 +24,30 @@ pub static META: RuleMeta = RuleMeta { pub struct RuleEnglishSymbol; +/// Korean rules 28 and 32 + UEB 5.7.1: inside an already-open Roman +/// section, a one-letter ASCII segment after a hyphen needs the continuation / +/// grade-1 cell when it could be read as an alphabetic wordsign. Multi-letter +/// segments such as `pop`, `ray`, and `Case` do not take this indicator. Rule +/// 28's Roman indicator already establishes the first segment (`K-pop`), while +/// rule 36's `v-x` demonstrates the indicator on the later single-letter +/// segment. A directly attached ASCII digit remains part of the same rule-35 +/// Roman-number sequence and therefore is not a single-letter segment. +fn hyphen_suffix_requires_grade1(word_chars: &[char], hyphen_index: usize) -> bool { + let Some(suffix) = word_chars.get(hyphen_index + 1..) else { + return false; + }; + let suffix_len = suffix + .iter() + .take_while(|ch| ch.is_ascii_alphabetic()) + .count(); + + suffix_len == 1 + && is_wordsign_letter(suffix[0]) + && suffix + .get(suffix_len) + .is_none_or(|ch| !ch.is_ascii_alphanumeric()) +} + impl BrailleRule for RuleEnglishSymbol { fn meta(&self) -> &'static RuleMeta { &META @@ -57,9 +82,17 @@ impl BrailleRule for RuleEnglishSymbol { return Ok(RuleResult::Continue); } + // Rule 69 [붙임 3]: a slash joining two complete Roman-written unit + // components stays in that measurement chain. Do not let the generic + // English-symbol route insert a second Roman indicator before `/`. + if *sym == '/' && super::rule_69::is_ascii_unit_chain_slash(ctx.word_chars, ctx.index) { + return Ok(RuleResult::Continue); + } + let mut use_english_symbol = english_logic::should_render_symbol_as_english( ctx.state.english_indicator, ctx.state.is_english, + ctx.state.doc_summary.is_english_majority, &ctx.state.parenthesis_stack, *sym, ctx.word_chars, @@ -67,6 +100,26 @@ impl BrailleRule for RuleEnglishSymbol { ctx.remaining_words, ); + // Korean rules 34 and 54: when a Korean prose item (optionally ending + // in an attached Arabic number) introduces a Roman explanation, the + // Korean opening parenthesis is written before the Roman indicator. + // A parenthesis reached while a Roman section is already active stays + // UEB punctuation (`ABC(def)`), as does ordinary function notation. + if *sym == '(' && !ctx.state.is_english { + let prefix = &ctx.word_chars[..ctx.index]; + let prefix_contains_korean = prefix.iter().any(|ch| utils::is_korean_char(*ch)); + let numeric_prefix = !prefix.is_empty() + && prefix.iter().any(char::is_ascii_digit) + && prefix.iter().all(|ch| { + ch.is_ascii_digit() + || matches!(*ch, '.' | ',' | '\'' | '’' | '"' | '”' | '‘' | '“') + }); + let previous_word_is_korean = ctx.prev_word.chars().any(utils::is_korean_char); + if prefix_contains_korean || (numeric_prefix && previous_word_is_korean) { + use_english_symbol = false; + } + } + // 제39항 영-한 wrap context: 단어 끝의 영어 모드 유지 가능 기호(. , : ;) // 다음에 한글 어절(wrap 대상)이 이어지면 그 기호를 영어 점자로 처리한다. // 예) "(Korean:" 끝의 ':'은 다음 wrap된 "반찬" 직전이므로 영어 점자 ⠒. @@ -95,7 +148,10 @@ impl BrailleRule for RuleEnglishSymbol { let can_use_english_symbol = ctx.state.is_english || has_ascii_alphabetic; if ctx.state.english_indicator && can_use_english_symbol && use_english_symbol { - if !ctx.state.is_english && !ctx.state.needs_english_continuation { + if !ctx.state.is_english + && !ctx.state.needs_english_continuation + && !ctx.state.roman_number_chain + { ctx.emit(52); ctx.state.is_english = true; ctx.state.needs_english_continuation = false; @@ -111,32 +167,11 @@ impl BrailleRule for RuleEnglishSymbol { }; if let Some(encoded) = encoded { ctx.emit_slice(&encoded); - if *sym == '-' && ctx.state.is_english { - // UEB 5.7.2의 `CD-ROM`은 순수 대문자 segment 사이의 하이픈 - // 뒤에서 대문자 단어표 앞에 1급 점자 기호표를 다시 적지 - // 않는다. 숫자는 제35항 `D-100`처럼 수표가 나오므로 역시 - // 로마자 연속표(⠰)가 불필요하다. 혼합 대소문자 prefix와 단일 - // 대문자 suffix는 이 근거 범위 밖이므로 기존 경계를 보존한다. - let prefix_len = ctx.word_chars[..ctx.index] - .iter() - .rev() - .take_while(|c| c.is_ascii_alphabetic()) - .count(); - let prefix = &ctx.word_chars[ctx.index - prefix_len..ctx.index]; - let suffix = &ctx.word_chars[ctx.index + 1..]; - let suffix_len = suffix - .iter() - .take_while(|c| c.is_ascii_alphabetic()) - .count(); - let suffix_letters = &suffix[..suffix_len]; - let next_has_own_indicator = suffix.first().is_some_and(char::is_ascii_digit) - || (!prefix.is_empty() - && prefix.iter().all(char::is_ascii_uppercase) - && suffix_letters.len() >= 2 - && suffix_letters.iter().all(char::is_ascii_uppercase)); - if !next_has_own_indicator { - ctx.emit(crate::rules::korean::rule_29::ENGLISH_CONTINUATION); - } + if *sym == '-' + && ctx.state.is_english + && hyphen_suffix_requires_grade1(ctx.word_chars, ctx.index) + { + ctx.emit(crate::rules::korean::rule_29::ENGLISH_CONTINUATION); } return Ok(RuleResult::Consumed); } @@ -160,6 +195,74 @@ impl BrailleRule for RuleEnglishSymbol { mod tests { use super::*; + #[rstest::rstest] + #[case::rule_36_single_x("v-x", true)] + #[case::single_x_before_korean("v-x쪽", true)] + #[case::single_t_before_korean_annotation("CAR-T(카티)", true)] + #[case::non_wordsign_i("v-i", false)] + #[case::multi_letter_pop("K-pop", false)] + #[case::multi_letter_case("Title-Case", false)] + #[case::letter_abutting_digit("v-x1", false)] + #[case::multi_letter_shortform_candidate("CD-AB", false)] + fn grade1_after_hyphen_is_limited_to_a_standing_single_letter( + #[case] input: &str, + #[case] expected: bool, + ) { + let chars = input.chars().collect::>(); + let hyphen_index = chars + .iter() + .position(|ch| *ch == '-') + .expect("fixture must contain a hyphen"); + + assert_eq!( + hyphen_suffix_requires_grade1(&chars, hyphen_index), + expected + ); + } + + /// Korean rules 28/29/32: the Roman indicator establishes the first + /// one-letter segment, and a multi-letter segment after the hyphen starts + /// directly with its UEB letters. In particular, no continuation/grade-1 + /// cell is inserted between the hyphen and the lowercase word. + #[rstest::rstest] + #[case::k_pop("가 K-pop 나", "⠫⠀⠴⠠⠅⠤⠏⠕⠏⠲⠀⠉")] + #[case::x_ray("가 X-ray 나", "⠫⠀⠴⠠⠭⠤⠗⠁⠽⠲⠀⠉")] + #[case::k_water("가 K-water 나", "⠫⠀⠴⠠⠅⠤⠺⠁⠞⠻⠲⠀⠉")] + fn hyphenated_roman_word_has_no_spurious_post_hyphen_indicator( + #[case] input: &str, + #[case] expected: &str, + ) { + assert_eq!(crate::encode_to_unicode(input).as_deref(), Ok(expected)); + } + + /// Korean rule 35: the official `D-100` establishes that adjacent Roman + /// letters, digits, and identifier hyphens form one chain. The Roman + /// indicator stays at the first Roman letter; a number-led item opens its + /// Roman section only when its first Roman letter is reached. + #[rstest::rstest] + #[case::roman_led_multi_segment("가 CV3-AD685 나", "⠫⠀⠴⠠⠠⠉⠧⠼⠉⠤⠠⠠⠁⠙⠼⠋⠓⠑⠀⠉")] + #[case::number_led_word("가 0-Zone 나", "⠫⠀⠼⠚⠤⠴⠠⠵⠐⠕⠲⠀⠉")] + #[case::roman_led_repeated_numeric_segments("가 N-79-20 나", "⠫⠀⠴⠠⠝⠤⠼⠛⠊⠤⠼⠃⠚⠀⠉")] + fn rule_35_places_the_roman_indicator_at_the_first_roman_letter( + #[case] input: &str, + #[case] expected: &str, + ) { + assert_eq!(crate::encode_to_unicode(input).as_deref(), Ok(expected)); + } + + /// Korean rules 29 and 34: a Korean closing parenthesis ends its enclosed + /// Roman section. Later Roman text therefore starts with a new Roman + /// indicator, even when a comma, number, or identifier hyphen intervenes. + #[rstest::rstest] + #[case::numeric_item_after_comma("가 액세스(FWA), 5G 나", "⠫⠀⠗⠁⠠⠝⠠⠪⠦⠄⠴⠠⠠⠋⠺⠁⠠⠴⠐⠀⠼⠑⠴⠠⠛⠲⠀⠉")] + #[case::hyphenated_item_after_enclosure("가(GTX)-C 나", "⠫⠦⠄⠴⠠⠠⠛⠞⠭⠠⠴⠤⠴⠠⠉⠲⠀⠉")] + fn korean_parenthesis_does_not_leak_english_continuation( + #[case] input: &str, + #[case] expected: &str, + ) { + assert_eq!(crate::encode_to_unicode(input).as_deref(), Ok(expected)); + } + #[test] fn apply_exercise() { let mut owned = crate::test_helpers::CtxOwned::for_text("A", false); @@ -256,4 +359,28 @@ mod tests { Some(expected_ueb.as_slice()) ); } + + /// UEB 3.3.1 writes the official `M*A*S*H` example with the UEB asterisk + /// and no mode boundary around any of its attached marks. Korean rule 32 + /// adds only the outer Roman indicator and terminator in mixed text. + #[test] + fn korean_wrapper_keeps_official_mash_in_one_roman_section() { + let official = crate::encode_to_unicode("M*A*S*H").unwrap(); + assert_eq!(official, "⠠⠍⠐⠔⠠⠁⠐⠔⠠⠎⠐⠔⠠⠓"); + assert_eq!( + crate::encode_to_unicode("가 M*A*S*H 나").as_deref(), + Ok("⠫⠀⠴⠠⠍⠐⠔⠠⠁⠐⠔⠠⠎⠐⠔⠠⠓⠲⠀⠉") + ); + } + + /// UEB 7.3: a Unicode ellipsis closing Roman content is equivalent to the + /// three-full-stop print spelling, even when Korean text owns the outer + /// parenthesis. + #[test] + fn roman_ellipsis_before_a_closing_parenthesis_uses_ueb_cells() { + assert_eq!( + crate::encode_to_unicode("문구(I AM…)이다"), + crate::encode_to_unicode("문구(I AM...)이다") + ); + } } diff --git a/libs/braillify/src/rules/korean/rule_math.rs b/libs/braillify/src/rules/korean/rule_math.rs index 39c53e47..8a1ee0d8 100644 --- a/libs/braillify/src/rules/korean/rule_math.rs +++ b/libs/braillify/src/rules/korean/rule_math.rs @@ -18,11 +18,257 @@ pub static META: RuleMeta = RuleMeta { description: "Math symbols with Korean spacing rules", }; -/// Korean particles (josa) that should NOT have spacing before them. -const JOSA: &[&str] = &["과", "와", "이다", "하고", "이랑", "와", "랑", "아니다"]; +/// Korean particles or copulas that do not form the right-hand operand of an +/// Article 46 expression by themselves. +const NON_OPERAND_KOREAN_SUFFIXES: &[&str] = + &["과", "와", "의", "이다", "하고", "이랑", "랑", "아니다"]; pub struct RuleMath; +fn matching_opening_delimiter(ch: char) -> Option { + match ch { + ')' => Some('('), + ']' => Some('['), + '}' => Some('{'), + '〉' => Some('〈'), + '》' => Some('《'), + '」' => Some('「'), + '』' => Some('『'), + '】' => Some('【'), + '〕' => Some('〔'), + '〗' => Some('〖'), + '〙' => Some('〘'), + '〛' => Some('〚'), + _ => None, + } +} + +fn matching_closing_delimiter(ch: char) -> Option { + match ch { + '(' => Some(')'), + '[' => Some(']'), + '{' => Some('}'), + '〈' => Some('〉'), + '《' => Some('》'), + '「' => Some('」'), + '『' => Some('』'), + '【' => Some('】'), + '〔' => Some('〕'), + '〖' => Some('〗'), + '〘' => Some('〙'), + '〚' => Some('〛'), + _ => None, + } +} + +fn is_operand_separator(ch: char) -> bool { + matches!( + ch, + '+' | '-' + | '−' + | '×' + | '÷' + | '=' + | '<' + | '>' + | '≤' + | '≥' + | '≠' + | ',' + | ';' + | ':' + | '!' + | '?' + | '…' + | '\'' + | '"' + | '‘' + | '’' + | '“' + | '”' + ) +} + +/// Finds the syntactic operand immediately to the left of an Article 46 sign. +/// A balanced annotation remains part of its operand (`레트로(RETRO)`), while +/// an unmatched opening delimiter is a hard boundary (`기업(+5p)`). +fn left_operand(chars: &[char], operator_index: usize) -> &[char] { + let mut start = operator_index; + let mut openings = Vec::new(); + + for index in (0..operator_index).rev() { + let ch = chars[index]; + if let Some(opening) = matching_opening_delimiter(ch) { + openings.push(opening); + start = index; + continue; + } + if matching_closing_delimiter(ch).is_some() { + if openings.last() == Some(&ch) { + openings.pop(); + start = index; + continue; + } + break; + } + if openings.is_empty() && is_operand_separator(ch) { + break; + } + start = index; + } + + &chars[start..operator_index] +} + +/// Finds the syntactic operand immediately to the right of an Article 46 sign. +/// Balanced annotations and numeric unit notation stay inside the operand, but +/// the next top-level sign or unmatched closing delimiter ends it. +fn right_operand(chars: &[char], operator_index: usize) -> &[char] { + let mut end = operator_index + 1; + let mut closings = Vec::new(); + + for (index, ch) in chars.iter().copied().enumerate().skip(operator_index + 1) { + if let Some(closing) = matching_closing_delimiter(ch) { + closings.push(closing); + end = index + 1; + continue; + } + if matching_opening_delimiter(ch).is_some() { + if closings.last() == Some(&ch) { + closings.pop(); + end = index + 1; + continue; + } + break; + } + if closings.is_empty() && is_operand_separator(ch) { + break; + } + end = index + 1; + } + + &chars[operator_index + 1..end] +} + +fn first_korean_run(chars: &[char]) -> Option { + let start = chars.iter().position(|ch| utils::is_korean_char(*ch))?; + let end = chars[start..] + .iter() + .position(|ch| !utils::is_korean_char(*ch)) + .map_or(chars.len(), |offset| start + offset); + Some(chars[start..end].iter().collect()) +} + +fn rule_46_requires_padding(ctx: &RuleContext) -> bool { + let left_is_korean_operand = left_operand(ctx.word_chars, ctx.index) + .iter() + .any(|ch| utils::is_korean_char(*ch)); + let right_is_non_suffix_korean_operand = + first_korean_run(right_operand(ctx.word_chars, ctx.index)) + .is_some_and(|run| !NON_OPERAND_KOREAN_SUFFIXES.contains(&run.as_str())); + + left_is_korean_operand && right_is_non_suffix_korean_operand +} + +/// U+002D is both HYPHEN-MINUS, so its braille meaning has to be inferred from +/// syntax. Treat it as the Article 45 subtraction/minus sign only when the +/// surrounding token makes that role explicit. In particular, a leading +/// signed number is a minus, while phone numbers, dates, ranges and identifiers +/// such as `02-799-1000` and `A-3` remain hyphenated. +fn is_semantic_ascii_minus(ctx: &RuleContext) -> bool { + if ctx.current_char() != '-' { + return false; + } + + let next_starts_number = ctx.next_char().is_some_and(|next| { + next.is_ascii_digit() + || (next == '.' + && ctx + .word_chars + .get(ctx.index + 2) + .is_some_and(char::is_ascii_digit)) + }); + let unary_boundary = ctx.prev_char().is_none_or(|prev| { + matches!( + prev, + '(' | '[' + | '{' + | '〈' + | '《' + | '「' + | '『' + | '【' + | '〔' + | '〖' + | '〘' + | '〚' + | '‘' + | '“' + | '\'' + | '"' + | ',' + | ':' + | ';' + | '=' + | '+' + | '×' + | '÷' + | '<' + | '>' + | '≤' + | '≥' + | '≠' + ) + }); + if next_starts_number && unary_boundary { + return true; + } + + // A sign cited by itself inside a matched delimiter is an operator, as in + // the common polarity notation `양(+)극·음(-)극`. A hyphen joining text has + // operands on the same side and therefore cannot have this shape. + let isolated_operator = matches!( + (ctx.prev_char(), ctx.next_char()), + (Some('('), Some(')')) + | (Some('['), Some(']')) + | (Some('{'), Some('}')) + | (Some('〈'), Some('〉')) + | (Some('《'), Some('》')) + | (Some('「'), Some('」')) + | (Some('『'), Some('』')) + | (Some('【'), Some('】')) + | (Some('〔'), Some('〕')) + | (Some('〖'), Some('〗')) + | (Some('〘'), Some('〙')) + | (Some('〚'), Some('〛')) + | (Some('‘'), Some('’')) + | (Some('“'), Some('”')) + | (Some('\''), Some('\'')) + | (Some('"'), Some('"')) + ); + if isolated_operator { + return true; + } + + // Article 46's printed example `5개-3개=2개` contains Hangul, so the + // token-level mathematics parser deliberately leaves it to Korean rules. + // A second explicit operator disambiguates the inner U+002D from a range. + let has_other_math_operator = ctx.word_chars.iter().enumerate().any(|(index, ch)| { + index != ctx.index + && matches!( + ch, + '+' | '=' | '×' | '÷' | '<' | '>' | '≤' | '≥' | '≠' | '−' + ) + }); + let prev_ends_operand = ctx.prev_char().is_some_and(|prev| { + prev.is_alphanumeric() + || utils::is_korean_char(prev) + || matches!(prev, ')' | ']' | '}' | '〉' | '》' | '」' | '』' | '】') + }); + + prev_ends_operand && next_starts_number && has_other_math_operator +} + impl BrailleRule for RuleMath { fn meta(&self) -> &'static RuleMeta { &META @@ -34,59 +280,51 @@ impl BrailleRule for RuleMath { fn matches(&self, ctx: &RuleContext) -> bool { matches!(ctx.char_type, CharType::MathSymbol(_)) + || (matches!(ctx.char_type, CharType::Symbol('-')) && is_semantic_ascii_minus(ctx)) } fn apply(&self, ctx: &mut RuleContext) -> Result { - let CharType::MathSymbol(c) = ctx.char_type else { - return Ok(RuleResult::Skip); + let c = match ctx.char_type { + CharType::MathSymbol(c) => *c, + CharType::Symbol('-') if is_semantic_ascii_minus(ctx) => '\u{2212}', + _ => return Ok(RuleResult::Skip), }; - // PDF 제46항 — 사칙연산 기호(+, −, ×, ÷, =) 띄어쓰기 규칙. - // 좌·우가 모두 "한글이 포함된 식"일 때에만 기호 앞뒤를 한 칸씩 띄어 쓴다. + // UEB §3.17 + Korean rules 29/35: a plus that belongs to a Roman + // product/grade identifier stays inside that Roman section and uses + // the UEB general-symbol cells ⠐⠖. The token-level grammar has already + // rejected completed sums and the ambiguous one-letter `A+` shape. + if c == '+' + && ctx.state.english_indicator + && ctx.state.is_english + && crate::rules::token_rules::math_expression::is_roman_plus_identifier(ctx.word_chars) + { + let encoded = crate::rules::english_ueb::rule_3::encode_symbol(c) + .ok_or_else(|| "UEB plus sign must be defined".to_string())?; + ctx.emit_slice(&encoded); + return Ok(RuleResult::Consumed); + } + + // PDF 제46항 — 사칙연산 기호(+, −, ×, ÷, =)가 한글 사이에 + // 나올 때에만 기호 앞뒤를 한 칸씩 띄어 쓴다. // // 판정: - // - 좌측 segment: 단어 시작부터 현재 기호 직전까지의 chars. 한글 포함 여부. - // - 우측 segment: 현재 기호 직후부터 단어 끝까지의 chars 중 **선행 비한글을 건너뛴 - // 첫 한글 묶음**. (예: `3.14이다` → `이다`; `3개=2개` → `개`) - // - 우측 묶음이 비어 있거나 JOSA(조사: 과/와/이다/하고/이랑/랑/아니다 등)이면 + // - 바로 인접한 피연산자 범위 안에 한글이 각각 있어야 한다. + // - 괄호 속 로마자·한글 주석은 그 피연산자에 포함한다. + // 예: `레트로(RETRO)+뉴트로(NEWTRO)`. + // - 괄호 경계나 다른 연산 기호를 넘어 문법적으로 무관한 한글은 + // 찾지 않는다. 예: `기업(+5p)의`, `행사(1+1)이다`. + // - 우측 한글 묶음이 비어 있거나 조사·서술격 표현(과/와/의/이다 등)이면 // 기호 양쪽을 띄어쓰지 않는다. // 예: `반지름×3.14이다` → `이다`는 JOSA → 띄어쓰지 않음. // 예: `5개−3개=2개` → `개`는 JOSA가 아님 → 띄어씀. - let prev_has_korean = ctx.word_chars[..ctx.index] - .iter() - .any(|c| utils::is_korean_char(*c)); - - let next_korean_is_non_josa = { - let mut korean = Vec::new(); - for wc in &ctx.word_chars[ctx.index + 1..] { - if utils::is_korean_char(*wc) { - korean.push(*wc); - } else if !korean.is_empty() { - break; - } - } - if korean.is_empty() { - false - } else { - let s: String = korean.into_iter().collect(); - !JOSA.contains(&s.as_str()) - } - }; - - // PDF 한글 제49항 — 문장 부호의 띄어쓰기는 묵자를 따른다. - // `한글(+)한글`처럼 연산 기호가 소괄호에 직접 둘러싸인 경우 기호는 - // 한글 사이에 직접 놓인 것이 아니므로 제46항의 양옆 공백을 삽입하지 않는다. - // 과학 제21항의 `(-)`·`(+)` 예제도 괄호 안을 붙여 적는다. - let immediately_parenthesized = ctx.index > 0 - && ctx.word_chars.get(ctx.index - 1) == Some(&'(') - && ctx.word_chars.get(ctx.index + 1) == Some(&')'); - let pad_spaces = prev_has_korean && next_korean_is_non_josa && !immediately_parenthesized; + let pad_spaces = rule_46_requires_padding(ctx); if pad_spaces { ctx.emit(0); } - let encoded = math_symbol_shortcut::encode_char_math_symbol_shortcut(*c)?; + let encoded = math_symbol_shortcut::encode_char_math_symbol_shortcut(c)?; ctx.emit_slice(encoded); if pad_spaces { @@ -146,6 +384,24 @@ mod tests { assert_ne!(owned.result.last(), Some(&0)); } + #[rstest::rstest] + #[case::service("TV+")] + #[case::alphanumeric_product("HDR10+")] + #[case::mixed_case_service("U+tv")] + fn roman_terminal_plus_uses_ueb_general_symbol(#[case] identifier: &str) { + let output = crate::encode(&format!("가 {identifier} 나")) + .expect("Roman product identifier must encode"); + let ueb_plus = crate::rules::english_ueb::rule_3::encode_symbol('+') + .expect("UEB plus must be defined"); + + assert!( + output + .windows(ueb_plus.len()) + .any(|cells| cells == ueb_plus), + "identifier={identifier}" + ); + } + #[rstest::rstest] #[case::plus_math_symbol("양", "+", "극")] #[case::ascii_hyphen_minus_symbol("음", "-", "극")] @@ -167,19 +423,76 @@ mod tests { ); } - #[test] - fn ascii_hyphen_minus_is_supported_by_punctuation_rule_49_path() { + #[rstest::rstest] + #[case::signed_integer("-3", 0, true)] + #[case::parenthesized_signed_decimal("(-3.5)", 1, true)] + #[case::quoted_negative_quantity("‘-2배’", 1, true)] + #[case::pdf_phone_number("02-799-1000", 2, false)] + #[case::identifier_suffix("A-3", 1, false)] + #[case::calendar_date("2024-09-03", 4, false)] + fn ascii_hyphen_minus_is_disambiguated_by_syntax( + #[case] input: &str, + #[case] index: usize, + #[case] expected: bool, + ) { + let mut owned = crate::test_helpers::CtxOwned::for_text(input, false); + let ctx = owned.ctx_at(index); + assert_eq!(is_semantic_ascii_minus(&ctx), expected, "input={input}"); + } + + #[rstest::rstest] + #[case::korean_words("나루+배", 2, true)] + #[case::korean_numeric_units("5개-3개", 2, true)] + #[case::percentage_noun_operand("팬=51%지분", 1, true)] + #[case::roman_annotation_on_left("레트로(RETRO)+뉴트로", 10, true)] + #[case::korean_annotations_on_both_sides("AI(인공지능)+DX(디지털전환)", 8, true)] + #[case::mixed_script_right_operand("밀레니얼+Z세대", 4, true)] + #[case::signed_parenthetical("기업(+5p)의", 3, false)] + #[case::numeric_sum("행사(1+1)이다", 4, false)] + #[case::brand_particle("디즈니+와", 3, false)] + #[case::roman_variable_left("T+3일", 1, false)] + #[case::particle_after_annotated_roman("OPEC(석유수출국기구)+의", 13, false)] + #[case::quoted_suffix("저소음+’", 3, false)] + fn rule_46_padding_depends_on_actual_operands( + #[case] input: &str, + #[case] index: usize, + #[case] expected: bool, + ) { + let mut owned = crate::test_helpers::CtxOwned::for_text(input, false); + let ctx = owned.ctx_at(index); + assert_eq!(rule_46_requires_padding(&ctx), expected, "input={input}"); + } + + #[rstest::rstest] + #[case::negative_percentage("-2.73%를", "−2.73%를")] + #[case::negative_unit("체급(-67kg)은", "체급(−67kg)은")] + #[case::parenthesized_polarity("음(-)극", "음(−)극")] + #[case::article_46_equation("5개-3개=2개", "5개−3개=2개")] + fn semantic_ascii_minus_matches_explicit_unicode_minus( + #[case] ascii: &str, + #[case] explicit: &str, + ) { assert!(matches!( crate::char_struct::CharType::new('-').expect("hyphen-minus must classify"), crate::char_struct::CharType::Symbol('-') )); assert_eq!( - crate::encode_to_unicode("음(-)극").expect("full input must encode"), - ["음", "(-)", "극"] - .into_iter() - .map(|part| crate::encode_to_unicode(part).expect("component must encode")) - .collect::>() - .concat() + crate::encode_to_unicode(ascii).expect("ASCII expression must encode"), + crate::encode_to_unicode(explicit).expect("Unicode expression must encode"), + "input={ascii}" + ); + } + + #[rstest::rstest] + #[case::pdf_phone_number("02-799-1000")] + #[case::identifier_suffix("A-3")] + #[case::calendar_date("2024-09-03")] + fn non_operator_hyphens_do_not_become_minus(#[case] input: &str) { + let explicit_minus = input.replacen('-', "−", 1); + assert_ne!( + crate::encode_to_unicode(input).expect("hyphenated input must encode"), + crate::encode_to_unicode(&explicit_minus).expect("minus variant must encode"), + "input={input}" ); } } diff --git a/libs/braillify/src/rules/math/encoder/symbol_rule.rs b/libs/braillify/src/rules/math/encoder/symbol_rule.rs index a3f694e8..15a738a6 100644 --- a/libs/braillify/src/rules/math/encoder/symbol_rule.rs +++ b/libs/braillify/src/rules/math/encoder/symbol_rule.rs @@ -76,20 +76,6 @@ impl MathTokenRule for MathSymbolRule { let _ = rule_26::is_reserved_rule_26(); let _ = rule_22::NTH_ROOT_INDEX_MARKER; - let prev_is_variable_or_upper = matches!( - rule_12::prev_non_space(tokens, index), - Some(MathToken::Variable(_) | MathToken::UpperVariable(_)) - ); - let next_is_upper = matches!( - Self::next_non_space(tokens, index + 1), - Some(MathToken::UpperVariable(_)) - ); - if *c == '\u{00AC}' && index > 0 && prev_is_variable_or_upper && next_is_upper { - result.push(40); - state.prev_was_number = false; - return Ok(MathTokenResult::Consumed(1)); - } - if *c == '\u{FF03}' && matches!( Self::next_non_space(tokens, index + 1), @@ -482,25 +468,31 @@ mod tests { // ---------------- Specialised prefix arms ---------------- - /// `A¬B` — ¬ (U+00AC) sandwiched between two UpperVariables hits the - /// negation prefix arm at lines 59-73. Encoded byte 40 is pushed. + /// Math rule 61: a negation sign keeps its complete two-cell mapping + /// between uppercase variables. #[test] fn negation_between_upper_variables() { let result = enc("A\u{00AC}B"); - assert!(!result.is_empty(), "A¬B must encode"); - // Compare against pattern WITHOUT the matching neighbours to ensure - // a different code path was taken. - let other = enc("\u{00AC}B"); - assert_ne!(result, other, "A¬B (sandwiched) must differ from ¬B"); + let negation = crate::math_symbol_shortcut::encode_char_math_symbol_shortcut('\u{00AC}') + .expect("rule-61 negation must be mapped"); + assert!( + result + .windows(negation.len()) + .any(|cells| cells == negation) + ); } - /// `A¬ B` with a leading lower variable instead of upper still triggers - /// the prev=Variable arm of the match (line 63). + /// The same rule applies between a lowercase and uppercase variable. #[test] fn negation_between_lower_and_upper_variable() { - // `a¬B` — prev is Variable('a'), next is UpperVariable('B'). let result = enc("a\u{00AC}B"); - assert!(!result.is_empty(), "a¬B must encode"); + let negation = crate::math_symbol_shortcut::encode_char_math_symbol_shortcut('\u{00AC}') + .expect("rule-61 negation must be mapped"); + assert!( + result + .windows(negation.len()) + .any(|cells| cells == negation) + ); } /// `#B` — FF03 fullwidth hash + UpperVariable hits lines 75-96. diff --git a/libs/braillify/src/rules/roman_mode.rs b/libs/braillify/src/rules/roman_mode.rs index fc66e7a5..ed6ef64c 100644 --- a/libs/braillify/src/rules/roman_mode.rs +++ b/libs/braillify/src/rules/roman_mode.rs @@ -16,6 +16,9 @@ pub(crate) fn exit_english(state: &mut EncoderState, needs_continuation: bool) { state.is_english = false; state.needs_english_continuation = needs_continuation; state.roman_number_chain = false; + if !needs_continuation { + state.roman_section_is_english_context = false; + } } /// 영어 모드로 진입하며 로마자표 ⠴ (또는 직전 종료 후 연속표 ⠰)를 emit한다. @@ -32,7 +35,8 @@ pub(crate) fn enter_english(state: &mut EncoderState, result: &mut Vec) { /// 제35항 — 로마자+숫자 연결(`D-100` 등)을 위해 영어 모드를 잠시 내려놓는다. pub(crate) fn exit_english_for_roman_number_chain(state: &mut EncoderState) { - exit_english(state, false); + state.is_english = false; + state.needs_english_continuation = false; state.roman_number_chain = true; } diff --git a/libs/braillify/src/rules/token_rules/english_dominant_korean_wrap.rs b/libs/braillify/src/rules/token_rules/english_dominant_korean_wrap.rs index 88337913..ad76baa5 100644 --- a/libs/braillify/src/rules/token_rules/english_dominant_korean_wrap.rs +++ b/libs/braillify/src/rules/token_rules/english_dominant_korean_wrap.rs @@ -274,6 +274,27 @@ fn count_script_words(tokens: &[Token<'_>]) -> (usize, usize) { (english_words, korean_words) } +/// Rule 39 applies to a Roman-main sentence, not merely a Korean sentence that +/// contains a long Roman citation. The first lexical script is a stable matrix- +/// language signal: the PDF's Roman-main examples begin in Roman script, while +/// its Korean domain-name example is handled by the same-token domain rule. +fn document_begins_in_roman_script(tokens: &[Token<'_>]) -> bool { + tokens.iter().find_map(|token| { + let Token::Word(word) = token else { + return None; + }; + word.chars.iter().find_map(|ch| { + if ch.is_ascii_alphabetic() { + Some(true) + } else if is_korean_char(*ch) { + Some(false) + } else { + None + } + }) + }) == Some(true) +} + /// Compute all document-level English-Korean predicates once per encode call. pub fn compute_document_summary(tokens: &[Token<'_>]) -> DocumentSummary { let candidates = scan_english_context_candidates(tokens); @@ -282,9 +303,11 @@ pub fn compute_document_summary(tokens: &[Token<'_>]) -> DocumentSummary { } let (english_words, korean_words) = count_script_words(tokens); - let is_english_majority = english_words >= korean_words.max(1); + let is_roman_main = + document_begins_in_roman_script(tokens) && english_words >= korean_words.max(1); + let is_english_majority = is_roman_main; let is_english_dominant = - english_words >= 10 && english_words >= korean_words.saturating_mul(5); + is_roman_main && english_words >= 10 && english_words >= korean_words.saturating_mul(5); let has_english_context_for_korean = candidates.has_same_token_context || (candidates.has_boundary_candidate && is_english_majority); @@ -523,6 +546,33 @@ mod tests { // The "123" word's first_script_char Some('1') hits `_ => {}` (not counted). } + #[rstest::rstest] + #[case::roman_main("2024 What is 김치 in English?", true)] + #[case::korean_main_with_long_roman_citation( + "익수다의 주요 ADC 프로그램은 IKS012(Anti-Folate Receptor Alpha (FRa)) ADC와 함께한다.", + false + )] + #[case::korean_domain_exception("대통령실 주소는 www.대통령.kr이다.", false)] + fn detects_the_matrix_script_from_the_first_lexical_script( + #[case] input: &str, + #[case] expected: bool, + ) { + let ir = crate::rules::token::DocumentIR::parse(input, true); + assert_eq!(document_begins_in_roman_script(&ir.tokens), expected); + } + + #[test] + fn korean_main_sentence_does_not_become_rule_39_from_a_long_roman_citation() { + let input = + "익수다의 주요 ADC 프로그램은 IKS012(Anti-Folate Receptor Alpha (FRa)) ADC와 함께한다."; + let ir = crate::rules::token::DocumentIR::parse(input, true); + + let summary = compute_document_summary(&ir.tokens); + + assert!(!summary.is_english_majority); + assert!(!summary.is_english_dominant); + } + /// english_dominant_korean_wrap:311 — `(true, true) =>` arm of the boundary /// match. Korean segment fills the whole word (both slices empty/punct-only), /// AND prev/next tokens are English-only words. is_english_majority required true. diff --git a/libs/braillify/src/rules/token_rules/latex_math.rs b/libs/braillify/src/rules/token_rules/latex_math.rs index e9dc73b9..11fd49b0 100644 --- a/libs/braillify/src/rules/token_rules/latex_math.rs +++ b/libs/braillify/src/rules/token_rules/latex_math.rs @@ -120,6 +120,26 @@ mod tests { assert!(result.contains('\u{00B2}')); } + /// 수학 제6항의 원 둘레 공식: Korean grouped operands and repeated + /// `\\times` commands must survive LaTeX normalization as one expression. + #[test] + fn korean_grouped_operands_with_repeated_latex_times_encode() { + let inner = "(원의 둘레)=(반지름)\\times 2\\times 3.14"; + assert_eq!(strip_latex_to_math(inner), "(원의 둘레)=(반지름)×2×3.14"); + + assert!(encode_latex_math_bytes_with_context(inner, MathContext::default()).is_ok()); + assert!(crate::encode(&format!("${inner}$")).is_ok()); + assert!( + crate::encode_with_options( + &format!("${inner}$"), + &crate::EncodeOptions { + default_mode: Some(crate::rules::context::EncodingMode::Math), + }, + ) + .is_ok() + ); + } + #[test] fn test_strip_subscript() { let result = strip_latex_to_math("x_{2}"); diff --git a/libs/braillify/src/rules/token_rules/math_expression.rs b/libs/braillify/src/rules/token_rules/math_expression.rs index c4afe015..2751ab75 100644 --- a/libs/braillify/src/rules/token_rules/math_expression.rs +++ b/libs/braillify/src/rules/token_rules/math_expression.rs @@ -33,8 +33,23 @@ impl TokenRule for MathExpressionTokenRule { } } +/// Shared character-emission predicate for a Roman identifier whose `+` must +/// use the UEB general-symbol cells rather than the Korean math plus cell. +pub(crate) fn is_roman_plus_identifier(chars: &[char]) -> bool { + apply::is_korean_prose_roman_plus_identifier(chars) + || apply::has_korean_prefix_roman_plus_annotation(chars) + || apply::has_korean_prefix_terminal_roman_plus_identifier(chars) +} + #[cfg(test)] mod tests { + use super::apply::{ + has_korean_prefix_roman_hyphen_suffix, has_korean_prefix_roman_plus_annotation, + has_korean_prefix_terminal_roman_plus_identifier, is_korean_prose_acronym_parenthetical, + is_korean_prose_roman_hyphen_identifier, is_korean_prose_roman_number_identifier, + is_korean_prose_roman_plus_identifier, is_korean_prose_roman_slash_identifier, + is_korean_prose_single_letter_slash_phrase, + }; use super::detect::is_math_expression; use super::helpers::*; use super::*; @@ -72,6 +87,229 @@ mod tests { assert!(!is_math_expression(&chars, "hello")); } + /// Korean rules 28/29/34/35: Roman code/compound surfaces must remain on + /// the Roman path in Korean prose, while lowercase algebra and one-letter + /// subtraction stay math-owned. + #[rstest::rstest] + #[case::official_roman_number("D-100", true)] + #[case::capital_code("AB-12", true)] + #[case::capitalised_compound("Title-Case", true)] + #[case::enclosed_code("(ABC)-D", true)] + #[case::single_capital_lexical_prefix("K-pop", true)] + #[case::single_capital_common_term("X-ray", true)] + #[case::single_capital_brand_prefix("K-water", true)] + #[case::single_lowercase_brand_prefix("k-water", true)] + #[case::mixed_case_digit_code("pH-1", true)] + #[case::decimal_model_code("GPT-3.5", true)] + #[case::lowercase_algebra("x-1", false)] + #[case::uppercase_subtraction("A-B", false)] + #[case::function_expression("F(x-1)", false)] + fn korean_prose_hyphen_identifier_grammar(#[case] input: &str, #[case] expected: bool) { + assert_eq!( + is_korean_prose_roman_hyphen_identifier(&input.chars().collect::>()), + expected + ); + } + + /// Korean rules 29/35 keep a Roman-led name and its adjoining number in + /// one Roman section. A one-letter algebraic variable remains math-owned. + #[rstest::rstest] + #[case::media_generation("Web3.0", true)] + #[case::model_version("GPT3.5", true)] + #[case::audio_format("MP3", true)] + #[case::mixed_case_measure("pH7", true)] + #[case::single_letter_variable("x2", false)] + #[case::number_first("3ab", false)] + #[case::plain_decimal("3.14", false)] + #[case::separator_not_between_digits("Web.3", false)] + fn korean_prose_roman_number_identifier_grammar(#[case] input: &str, #[case] expected: bool) { + assert_eq!( + is_korean_prose_roman_number_identifier(&input.chars().collect::>()), + expected + ); + } + + #[rstest::rstest] + #[case::enclosed_code("한글(ABC)-D", true)] + #[case::roman_gloss_then_code("한글(Title)-AB", true)] + #[case::korean_then_single_capital("하쿠토-R", true)] + #[case::korean_then_initialism("기장-KBO", true)] + #[case::korean_then_lowercase_word("온다-life", true)] + #[case::korean_then_alphanumeric_label("대신-Y2HC", true)] + #[case::lowercase_algebra("한글(x-1)", false)] + #[case::uppercase_subtraction("한글(A-B)", false)] + #[case::korean_then_lowercase_variable("값-x", false)] + #[case::korean_then_explicit_expression("값-X+1", false)] + #[case::korean_then_number("한-3", false)] + fn korean_prefix_roman_hyphen_suffix_grammar(#[case] input: &str, #[case] expected: bool) { + assert_eq!( + has_korean_prefix_roman_hyphen_suffix(&input.chars().collect::>()), + expected + ); + } + + /// Korean rules 29 and 33: a Korean/Roman hyphen boundary stays attached, + /// uses the Korean hyphen cell, and opens one Roman section after it. + #[rstest::rstest] + #[case::single_capital("하쿠토-R", "⠚⠋⠍⠓⠥⠤⠴⠠⠗⠲")] + #[case::initialism("기장-KBO", "⠈⠕⠨⠶⠤⠴⠠⠠⠅⠃⠕⠲")] + #[case::country_initialism("한-UAE", "⠚⠒⠤⠴⠠⠠⠥⠁⠑⠲")] + fn korean_to_roman_hyphen_boundary_stays_prose(#[case] input: &str, #[case] expected: &str) { + assert_eq!(crate::encode_to_unicode(input).as_deref(), Ok(expected)); + } + + #[rstest::rstest] + #[case::standards_bodies("ISO/IEC", true)] + #[case::market_pair("WEMIX/KRW", true)] + #[case::aircraft_family("F-5E/F", true)] + #[case::roman_model_family("NVMe/TCP", true)] + #[case::single_letter_fraction("F/N", false)] + #[case::algebraic_fraction("A/B", false)] + #[case::numeric_fraction("1/2", false)] + #[case::equation_context("X≈F/N", false)] + fn korean_prose_slash_identifier_grammar(#[case] input: &str, #[case] expected: bool) { + assert_eq!( + is_korean_prose_roman_slash_identifier(&input.chars().collect::>()), + expected + ); + } + + #[rstest::rstest] + #[case::hardware_wallet("H/W Wallet", true)] + #[case::relapsed_refractory_cancer("폐암(R/R ES-SCLC)에서", true)] + #[case::lowercase_math_description("F/N ratio", false)] + #[case::explicit_equation("X≈F/N Result", false)] + #[case::isolated_fraction("F/N", false)] + fn single_letter_slash_phrase_requires_a_capital_led_roman_continuation( + #[case] input: &str, + #[case] expected: bool, + ) { + let ir = crate::rules::token::DocumentIR::parse(input, true); + let index = ir + .tokens + .iter() + .position(|token| matches!(token, Token::Word(word) if word.chars.contains(&'/'))) + .expect("probe must contain a slash word"); + let Token::Word(word) = &ir.tokens[index] else { + unreachable!("selected token must be a word"); + }; + + assert_eq!( + is_korean_prose_single_letter_slash_phrase(&ir.tokens, index, &word.chars), + expected + ); + } + + #[rstest::rstest] + #[case::hardware_wallet("가 H/W Wallet 나", "⠫⠀⠴⠠⠓⠸⠌⠠⠺⠀⠠⠺⠁⠇⠇⠑⠞⠲⠀⠉")] + #[case::relapsed_refractory_cancer("가 R/R ES-SCLC 나", "⠫⠀⠴⠠⠗⠸⠌⠠⠗⠀⠠⠠⠑⠎⠤⠠⠠⠎⠉⠇⠉⠲⠀⠉")] + #[case::official_math_rule_29("X ≈ F/N", "⠠⠭⠀⠈⠔⠈⠔⠀⠠⠋⠸⠌⠠⠝")] + fn slash_phrase_respects_korean_rule_29_without_stealing_math_rule_29( + #[case] input: &str, + #[case] expected: &str, + ) { + assert_eq!(crate::encode_to_unicode(input).as_deref(), Ok(expected)); + } + + #[rstest::rstest] + #[case::service("TV+", true)] + #[case::safety_grade("TSP+", true)] + #[case::alphanumeric_product("HDR10+", true)] + #[case::numeric_parenthetical("ATC+(20017936)", true)] + #[case::mixed_case_service("U+tv", true)] + #[case::attached_korean_particle("XYZ+는", true)] + #[case::number_led_identifier("24K+", true)] + #[case::identifier_separator("Model.Name+", true)] + #[case::repeated_terminal_plus("UV++++", true)] + #[case::contextual_single_letter_grade("A+(우수)", true)] + #[case::ascii_single_letter_expression("A+(B)", false)] + #[case::one_letter_terminal_identifier("A+", true)] + #[case::completed_sum("AB+C", false)] + #[case::chemical_expression("SmBa0.5-xCo2O5+d", false)] + #[case::lexical_compound("Dog+Yoga", true)] + #[case::lowercase_math_functions("sin+cos", false)] + #[case::korean_service("U+유모바일", true)] + #[case::mixed_script_korean_service("U+한글tv", true)] + #[case::one_letter_korean_addition("A+나", true)] + fn korean_prose_plus_identifier_grammar(#[case] input: &str, #[case] expected: bool) { + assert_eq!( + is_korean_prose_roman_plus_identifier(&input.chars().collect::>()), + expected + ); + } + + #[rstest::rstest] + #[case::closed_lexical_gloss("도가(Dog+Yoga)", true)] + #[case::attached_particle("워케이션(Work+Vacation)은", true)] + #[case::single_letter_terminal_label("등급(A+)은", true)] + #[case::middle_dot_chained_identifier("상품(Service+)·후속(Next+)는", true)] + #[case::math_body("공식(A+B)은", false)] + #[case::unclosed("도가(Dog+Yoga", false)] + fn korean_prefix_plus_annotation_grammar(#[case] input: &str, #[case] expected: bool) { + assert_eq!( + has_korean_prefix_roman_plus_annotation(&input.chars().collect::>()), + expected + ); + } + + #[rstest::rstest] + #[case::roman_suffix("한글TV+는", true)] + #[case::numeric_roman_suffix("한글7GB+는", true)] + #[case::completed_sum("한글A+B는", false)] + #[case::all_capital_internal_ambiguity("한글X+U는", false)] + fn korean_prefix_terminal_plus_suffix_grammar(#[case] input: &str, #[case] expected: bool) { + assert_eq!( + has_korean_prefix_terminal_roman_plus_identifier(&input.chars().collect::>()), + expected + ); + } + + #[rstest::rstest] + #[case::acronym_expansion("ABC(Alpha", true)] + #[case::alphanumeric_acronym("S2E(System)", true)] + #[case::single_math_function("f(x)", false)] + #[case::operator_body("AB(x+1)", false)] + fn korean_prose_acronym_parenthetical_grammar(#[case] input: &str, #[case] expected: bool) { + assert_eq!( + is_korean_prose_acronym_parenthetical(&input.chars().collect::>()), + expected + ); + } + + /// Korean rules 29, 35 and 54 compose the same anonymized-person label + /// regardless of whether the following honorific is attached or spaced. + #[rstest::rstest] + #[case::adult("A(27)", "씨는")] + #[case::minor_male("B(11)", "군에게")] + #[case::minor_female("C(16)", "양의")] + #[case::elected_official("A(31)", "도의원을")] + #[case::professor("B(61)", "교수를")] + #[case::judge("C(54)", "부장판사에게")] + fn spaced_anonymized_person_label_uses_korean_prose_composition( + #[case] label: &str, + #[case] honorific: &str, + ) { + let input = format!("{label} {honorific}"); + let mut expected = encode_anonymized_person_label(&label.chars().collect::>()) + .expect("valid anonymized-person label"); + expected.push(0); + expected.extend(crate::encode(honorific).expect("Korean honorific must encode")); + + assert_eq!( + crate::encode(&input).expect("prose label must encode"), + expected + ); + } + + #[test] + fn spaced_function_value_is_not_an_anonymized_person_label() { + let input = "A(14) 값"; + let tokens = crate::rules::token::DocumentIR::parse(input, true).tokens; + assert!(!super::apply::next_word_begins_korean_prose_label_context( + &tokens, 0 + )); + } + #[rstest::rstest] #[case::lower_list_item("(x)", false)] #[case::upper_list_item("(A)", false)] @@ -319,14 +557,28 @@ mod tests { assert!(!is_combining_math_mark('a')); } - #[test] - fn is_middle_dot_numeric_word_paths() { - let chars: Vec = "1·2".chars().collect(); - assert!(is_middle_dot_numeric_word(&chars)); - let chars: Vec = "ab".chars().collect(); - assert!(!is_middle_dot_numeric_word(&chars)); - let chars: Vec = "".chars().collect(); - assert!(!is_middle_dot_numeric_word(&chars)); + #[rstest::rstest] + #[case::single_middle_dot("1·2", true)] + #[case::multiple_middle_dots("2017·2018·2019·2021", true)] + #[case::trailing_comma("4·5,", true)] + #[case::letters("ab", false)] + #[case::empty("", false)] + fn is_middle_dot_numeric_word_paths(#[case] input: &str, #[case] expected: bool) { + let chars: Vec = input.chars().collect(); + assert_eq!(is_middle_dot_numeric_word(&chars), expected); + } + + #[rstest::rstest] + #[case::numeric_fraction("1/3", true)] + #[case::year_range("2023/2024", true)] + #[case::leading_decimal(".515", true)] + #[case::decimal_range("1.77~5.72", true)] + #[case::middle_dot_years("2017·2018·2019·2021", true)] + #[case::signed_number("-3", false)] + #[case::algebra("1/3+x", false)] + fn korean_prose_numeric_notation_paths(#[case] input: &str, #[case] expected: bool) { + let chars: Vec = input.chars().collect(); + assert_eq!(is_korean_prose_numeric_notation(&chars), expected); } #[test] diff --git a/libs/braillify/src/rules/token_rules/math_expression/apply.rs b/libs/braillify/src/rules/token_rules/math_expression/apply.rs index 69121af9..918d4338 100644 --- a/libs/braillify/src/rules/token_rules/math_expression/apply.rs +++ b/libs/braillify/src/rules/token_rules/math_expression/apply.rs @@ -119,11 +119,662 @@ fn is_roman_parenthetical_prose_trailer(chars: impl Iterator) -> bo is_korean_char(ch) || matches!( ch, - ',' | '.' | ';' | ':' | '!' | '?' | '\'' | '"' | '’' | '”' + ',' | '.' | ';' | ':' | '!' | '?' | '·' | '\'' | '"' | '’' | '”' ) }) } +fn is_roman_hyphen(ch: char) -> bool { + matches!( + ch, + '-' | '\u{2010}' | '\u{2011}' | '\u{2012}' | '\u{2013}' | '\u{2014}' + ) +} + +fn trim_roman_identifier_edge(chars: &[char]) -> &[char] { + let mut start = 0usize; + let mut end = chars.len(); + while start < end + && matches!( + chars[start], + '\'' | '"' | '‘' | '“' | '〈' | '《' | '「' | '『' + ) + { + start += 1; + } + while start < end + && matches!( + chars[end - 1], + ',' | '.' | ';' | ':' | '!' | '?' | '\'' | '"' | '’' | '”' | '〉' | '》' | '」' | '』' + ) + { + end -= 1; + } + &chars[start..end] +} + +fn is_decimal_separator_between_digits(chars: &[char], index: usize) -> bool { + matches!(chars.get(index), Some('.' | ',')) + && index > 0 + && chars.get(index - 1).is_some_and(char::is_ascii_digit) + && chars.get(index + 1).is_some_and(char::is_ascii_digit) +} + +/// A Roman-led alphanumeric identifier in ordinary Korean prose, such as +/// `MP3`, `Web3.0`, or `GPT3.5`. +/// +/// Korean rules 29 and 35 keep an adjoining Roman letters-sequence and number +/// in the Roman section. A decimal point/comma is accepted only between two +/// digits. Requiring at least two Roman letters keeps a bare algebraic shape +/// such as `x2` on the mathematical route; explicit math mode is rejected by +/// the caller as an additional boundary. +pub(super) fn is_korean_prose_roman_number_identifier(chars: &[char]) -> bool { + let chars = trim_roman_identifier_edge(chars); + if chars.len() < 3 || !chars.first().is_some_and(char::is_ascii_alphabetic) { + return false; + } + + let mut letter_count = 0usize; + let mut has_digit = false; + for (index, ch) in chars.iter().enumerate() { + if ch.is_ascii_alphabetic() { + letter_count += 1; + } else if ch.is_ascii_digit() { + has_digit = true; + } else if !is_decimal_separator_between_digits(chars, index) { + return false; + } + } + + letter_count >= 2 && has_digit +} + +/// A print token whose numeric prefix is immediately followed by Roman +/// letters, such as `50bp`, `3.1p`, `1st`, or `3x3`. +/// +/// Korean rules 29 and 35 transcribe the Roman run and the adjoining number +/// compositionally. The same print shape can denote algebra in isolation, so +/// this predicate describes only the token grammar; the caller additionally +/// requires Korean prose context and rejects explicit/cued mathematics. +fn is_korean_prose_numeric_roman_identifier(chars: &[char]) -> bool { + let chars = trim_roman_identifier_edge(chars); + let mut index = 0usize; + let mut previous_was_digit = false; + + while let Some(&ch) = chars.get(index) { + if ch.is_ascii_digit() { + previous_was_digit = true; + index += 1; + continue; + } + if matches!(ch, ',' | '.') + && previous_was_digit + && chars.get(index + 1).is_some_and(char::is_ascii_digit) + { + previous_was_digit = false; + index += 1; + continue; + } + break; + } + + index > 0 + && chars.get(index).is_some_and(char::is_ascii_alphabetic) + && chars[index..].iter().all(char::is_ascii_alphanumeric) +} + +/// Korean articles 28, 29, 34 and 35 make an ASCII identifier in Korean prose +/// Roman text unless the caller selected math mode or the print contains an +/// unambiguous mathematical operator. A hyphen alone is not such a signal: +/// the standard's `D-100` is explicitly Roman+number, and UEB treats hyphenated +/// Roman compounds as one letters-sequence context. +/// +/// The surface remains ambiguous for algebra such as `x-1`. Keep a narrow, +/// script-based default here: digit-bearing identifiers must begin with a +/// capital Roman letter or have at least two letters in the leading segment; +/// letter-only compounds need either a capitalised segment of at least two +/// letters or the lexical `K-pop`/`x-axis` shape of one-letter prefix followed +/// by a lowercase word. Thus ordinary lowercase `x-1` and uppercase `A-B` +/// stay on the math path, while model/code and lexical-compound shapes use +/// rules 28-35. +pub(super) fn is_korean_prose_roman_hyphen_identifier(chars: &[char]) -> bool { + let chars = trim_roman_identifier_edge(chars); + if chars.is_empty() { + return false; + } + + // Rule 34 enclosure followed by a Roman continuation, e.g. `(ABC)-D`. + let core = if chars.first() == Some(&'(') { + let Some(close) = chars.iter().position(|ch| *ch == ')') else { + return false; + }; + let enclosed = &chars[1..close]; + if enclosed.len() < 2 + || !enclosed.iter().all(char::is_ascii_uppercase) + || !chars.get(close + 1).is_some_and(|ch| is_roman_hyphen(*ch)) + { + return false; + } + &chars[1..] + } else { + chars + }; + + // A parenthetical expansion after the identifier is Roman prose only when + // the current fragment starts with letters again. `F(x-1)` therefore + // remains math, while a hyphenated acronym followed by a word expansion is + // allowed to continue through subsequent whitespace tokens. + let identifier_end = core.iter().position(|ch| *ch == '(').unwrap_or(core.len()); + if identifier_end < core.len() { + let body = &core[identifier_end + 1..]; + if body.is_empty() || !body.iter().all(char::is_ascii_alphabetic) { + return false; + } + } + let identifier = &core[..identifier_end]; + + if !identifier.iter().any(|ch| is_roman_hyphen(*ch)) + || !identifier.iter().enumerate().all(|(index, ch)| { + ch.is_ascii_alphanumeric() + || is_roman_hyphen(*ch) + || *ch == ')' + || is_decimal_separator_between_digits(identifier, index) + }) + { + return false; + } + + let segments = identifier.split(|ch| is_roman_hyphen(*ch)); + let mut has_digit = false; + let mut first_ascii_letter = None; + let mut has_capitalised_word_segment = false; + let mut first_segment_letter_count = 0usize; + let mut first_segment_is_single_letter = false; + let mut has_later_lowercase_lexical_segment = false; + for (segment_index, raw_segment) in segments.enumerate() { + let segment = raw_segment + .iter() + .copied() + .filter(|ch| ch.is_ascii_alphanumeric()) + .collect::>(); + if segment.is_empty() { + return false; + } + has_digit |= segment.iter().any(char::is_ascii_digit); + first_ascii_letter = + first_ascii_letter.or_else(|| segment.iter().copied().find(char::is_ascii_alphabetic)); + let letter_count = segment.iter().filter(|ch| ch.is_ascii_alphabetic()).count(); + has_capitalised_word_segment |= letter_count >= 2 + && segment + .iter() + .find(|ch| ch.is_ascii_alphabetic()) + .is_some_and(|ch| ch.is_ascii_uppercase()); + if segment_index == 0 { + first_segment_letter_count = letter_count; + first_segment_is_single_letter = segment.len() == 1 && letter_count == 1; + } else if first_segment_is_single_letter { + has_later_lowercase_lexical_segment |= + letter_count >= 2 && segment.iter().all(char::is_ascii_lowercase); + } + } + + if has_digit { + first_segment_letter_count >= 2 + || first_ascii_letter.is_some_and(|ch| ch.is_ascii_uppercase()) + } else { + has_capitalised_word_segment + || (first_segment_is_single_letter && has_later_lowercase_lexical_segment) + } +} + +/// Roman identifier joined by a solidus in ordinary Korean prose. +/// +/// The solidus is shared by UEB Roman text and mathematical division. Keep +/// the mathematical one-letter fraction shapes (`A/B`, `F/N`) on the math +/// route, and recognize only identifier-like forms that start with a capital +/// and contain at least one multi-character alphanumeric segment. This covers +/// standard prose abbreviations and model families such as `ISO/IEC` and +/// `F-5E/F` without changing an explicitly selected math context. +pub(super) fn is_korean_prose_roman_slash_identifier(chars: &[char]) -> bool { + let chars = trim_roman_identifier_edge(chars); + if chars.is_empty() + || !chars.first().is_some_and(|ch| ch.is_ascii_uppercase()) + || !chars.contains(&'/') + || !chars.iter().all(|ch| { + ch.is_ascii_alphanumeric() || *ch == '/' || is_roman_hyphen(*ch) || *ch == '.' + }) + { + return false; + } + + let mut has_letter = false; + let mut has_multi_character_segment = false; + for segment in chars.split(|ch| *ch == '/') { + if segment.is_empty() || segment.iter().all(|ch| is_roman_hyphen(*ch) || *ch == '.') { + return false; + } + has_letter |= segment.iter().any(char::is_ascii_alphabetic); + has_multi_character_segment |= segment + .iter() + .filter(|ch| ch.is_ascii_alphanumeric()) + .count() + >= 2; + } + has_letter && has_multi_character_segment +} + +/// A single-letter solidus initialism can be distinguished from mathematical +/// division when it begins a capital-led multi-letter Roman phrase, such as +/// `H/W Wallet` or `R/R ES-SCLC`. Korean rule 29 keeps consecutive Roman words +/// in one section, while an isolated `F/N` remains on the math path used by the +/// official mathematics rule 29 example. +pub(super) fn is_korean_prose_single_letter_slash_phrase( + tokens: &[Token<'_>], + index: usize, + chars: &[char], +) -> bool { + let has_strong_math_symbol = chars.iter().any(|ch| { + math_symbol_shortcut::is_math_symbol_char(*ch) + && !matches!(*ch, '\u{00B7}' | '\u{22C5}' | '/' | '_') + }); + if has_strong_math_symbol { + return false; + } + + let has_single_letter_slash_run = (0..chars.len()).any(|start| { + if !chars[start].is_ascii_uppercase() + || start + .checked_sub(1) + .and_then(|before| chars.get(before)) + .is_some_and(|ch| ch.is_ascii_alphanumeric() || *ch == '/') + { + return false; + } + + let mut cursor = start + 1; + let mut slash_count = 0usize; + while chars.get(cursor) == Some(&'/') + && chars + .get(cursor + 1) + .is_some_and(|ch| ch.is_ascii_uppercase()) + { + slash_count += 1; + cursor += 2; + } + + slash_count > 0 + && !chars + .get(cursor) + .is_some_and(|ch| ch.is_ascii_alphanumeric() || *ch == '/') + }); + if !has_single_letter_slash_run { + return false; + } + + let Some(next_word) = next_word_skip_space(tokens, index + 1) else { + return false; + }; + let mut next_roman = next_word + .chars + .iter() + .copied() + .skip_while(|ch| matches!(*ch, '\'' | '"' | '‘' | '“' | '(' | '[' | '{')) + .take_while(|ch| ch.is_ascii_alphanumeric() || is_roman_hyphen(*ch)); + let Some(first) = next_roman.next() else { + return false; + }; + first.is_ascii_uppercase() + && next_roman.filter(char::is_ascii_alphabetic).count() + + usize::from(first.is_ascii_alphabetic()) + >= 2 +} + +fn is_roman_identifier_head_separator(chars: &[char], index: usize) -> bool { + matches!( + chars.get(index), + Some('.' | '/' | '-' | '‐' | '‑' | '‒' | '–' | '—') + ) && index > 0 + && chars[index - 1].is_ascii_alphanumeric() + && chars + .get(index + 1) + .is_some_and(char::is_ascii_alphanumeric) +} + +/// A Roman identifier ending in one or more plus signs. +/// +/// The head may combine Roman letters with adjoining digits and the ordinary +/// identifier separators already covered by Korean rules 29/32/35. A head of +/// two or more alphanumerics is structurally terminal (`TV+`, `24K+`), and a +/// repeated plus is likewise not a completed binary addition (`C++`). A +/// one-letter `A+` is terminal in ordinary prose unless a visible right operand +/// follows; explicit mathematics is rejected by the caller before this rule. +fn is_terminal_roman_plus_core(core: &[char], allow_single_letter: bool) -> bool { + let plus_start = core + .iter() + .rposition(|ch| *ch != '+') + .map_or(0, |index| index + 1); + if plus_start == 0 || plus_start == core.len() { + return false; + } + + let head = &core[..plus_start]; + let plus_count = core.len() - plus_start; + if head.contains(&'+') + || !head.iter().enumerate().all(|(index, ch)| { + ch.is_ascii_alphanumeric() || is_roman_identifier_head_separator(head, index) + }) + || !head.iter().any(char::is_ascii_alphabetic) + { + return false; + } + + let alphanumeric_count = head.iter().filter(|ch| ch.is_ascii_alphanumeric()).count(); + alphanumeric_count >= 2 + || plus_count >= 2 + || (allow_single_letter && head.len() == 1 && head[0].is_ascii_uppercase()) +} + +fn is_attached_plus_prose_trailer_char(ch: char) -> bool { + is_korean_char(ch) + || matches!( + ch, + '(' | ')' + | '[' + | ']' + | '{' + | '}' + | ',' + | '.' + | ';' + | ':' + | '!' + | '?' + | '\'' + | '"' + | '‘' + | '’' + | '“' + | '”' + | '〈' + | '〉' + | '《' + | '》' + | '「' + | '」' + | '『' + | '』' + ) +} + +fn is_terminal_plus_closer_char(ch: char) -> bool { + matches!( + ch, + ')' | ']' + | '}' + | ',' + | '.' + | ';' + | ':' + | '!' + | '?' + | '\'' + | '"' + | '’' + | '”' + | '〉' + | '》' + | '」' + | '』' + ) +} + +/// Roman product, service, or lexical compound using a plus sign. +/// +/// A completed mathematical addition necessarily has a right operand, whereas +/// a terminal `+` is a common part of a Roman identifier (`TV+`, `HDR10+`). A +/// Korean particle or annotation may be attached directly after that core, and +/// repeated plus signs remain part of the same identifier. A one-letter +/// terminal form stays on this prose path unless a parenthesized ASCII operand +/// completes the expression; explicit math mode remains math-owned. +/// +/// A plus between capital-led Roman words is likewise lexical when at least one +/// side has a lowercase letter and two or more letters (`Dog+Yoga`). That +/// orthographic signal deliberately excludes all-capital algebra-like surfaces +/// such as `AB+C` and lowercase function sums such as `sin+cos`. Finally, a +/// single capital immediately followed by `+` and attached Hangul +/// (`U+유모바일`) is a Roman brand prefix followed by Korean text; Article 46 +/// would require spaces around a genuine Korean addition. +pub(super) fn is_korean_prose_roman_plus_identifier(chars: &[char]) -> bool { + let chars = trim_roman_identifier_edge(chars); + if chars.is_empty() { + return false; + } + + let roman_end = chars + .iter() + .take_while(|ch| { + ch.is_ascii_alphanumeric() + || **ch == '+' + || matches!(**ch, '.' | '/' | '-' | '‐' | '‑' | '‒' | '–' | '—') + }) + .count(); + let core = &chars[..roman_end]; + let trailer = &chars[roman_end..]; + let korean_led_mixed_trailer = trailer.first().is_some_and(|ch| is_korean_char(*ch)) + && trailer + .iter() + .all(|ch| ch.is_ascii_alphanumeric() || is_attached_plus_prose_trailer_char(*ch)); + let trailer_is_prose = trailer.is_empty() + || trailer.first() == Some(&'(') + || korean_led_mixed_trailer + || trailer + .iter() + .copied() + .all(is_attached_plus_prose_trailer_char); + if !trailer_is_prose { + return false; + } + + let has_korean_trailer = trailer.iter().any(|ch| is_korean_char(*ch)); + let allow_single_letter = trailer.is_empty() + || has_korean_trailer + || trailer.iter().copied().all(is_terminal_plus_closer_char); + if is_terminal_roman_plus_core(core, allow_single_letter) { + return true; + } + + if !core.first().is_some_and(|ch| ch.is_ascii_uppercase()) { + return false; + } + + if !core.contains(&'+') { + return false; + } + + if !core.iter().all(|ch| ch.is_ascii_alphabetic() || *ch == '+') { + return false; + } + + let segments = core.split(|ch| *ch == '+').collect::>(); + segments.len() >= 2 + && segments.iter().all(|segment| !segment.is_empty()) + && segments.iter().any(|segment| { + segment.len() >= 2 + && segment.iter().any(char::is_ascii_lowercase) + && segment.iter().all(char::is_ascii_alphabetic) + }) +} + +/// A Korean word may immediately introduce a parenthesized Roman lexical +/// compound (`도가(Dog+Yoga)`). Prove the Korean prefix and a closed Roman +/// body, then reuse the same plus grammar. Text following the close must be +/// ordinary Korean prose or punctuation, never another ASCII operand. +pub(super) fn has_korean_prefix_roman_plus_annotation(chars: &[char]) -> bool { + chars.iter().enumerate().any(|(start, ch)| { + if !ch.is_ascii_alphabetic() || !chars[..start].iter().any(|prefix| is_korean_char(*prefix)) + { + return false; + } + + let suffix = &chars[start..]; + let Some(close) = suffix.iter().position(|candidate| *candidate == ')') else { + return false; + }; + let trailer = &suffix[close + 1..]; + close > 0 + && (is_korean_prose_roman_plus_identifier(&suffix[..close]) + || is_terminal_roman_plus_core(&suffix[..close], true)) + && (is_roman_parenthetical_prose_trailer(trailer.iter().copied()) + || trailer + .first() + .is_some_and(|ch| is_korean_char(*ch) || *ch == '·')) + }) +} + +/// A Korean lexical prefix may attach directly to a terminal Roman identifier +/// (`한글TV+는`). Once the first Roman/digit run after Korean is found, reuse +/// the same terminal-plus grammar. A later operand after an earlier plus is not +/// a new start, so `한글A+B` remains math-owned. +pub(super) fn has_korean_prefix_terminal_roman_plus_identifier(chars: &[char]) -> bool { + chars.iter().enumerate().any(|(start, ch)| { + ch.is_ascii_alphanumeric() + && chars[..start].iter().any(|prefix| is_korean_char(*prefix)) + && start + .checked_sub(1) + .and_then(|index| chars.get(index)) + .is_none_or(|previous| !previous.is_ascii_alphanumeric() && *previous != '+') + && is_korean_prose_roman_plus_identifier(&chars[start..]) + }) +} + +/// A Korean word may attach directly to a hyphenated Roman identifier in two +/// directions: an enclosed Roman run can continue after a hyphen +/// (`한글(ABC)-D`), or the Korean run itself can be followed by a Roman +/// label (`하쿠토-R`, `기장-KBO`). Korean rule 33 proves that `-` at a +/// Korean/Roman boundary is punctuation rather than mathematical subtraction; +/// rules 29 and 35 then own the Roman run. +/// +/// A single lowercase letter remains ambiguous algebra (`값-x`), and an +/// explicit operator after the Roman start remains math-owned (`값-x+1`). +pub(super) fn has_korean_prefix_roman_hyphen_suffix(chars: &[char]) -> bool { + let enclosed_roman_continuation = chars.iter().enumerate().any(|(index, ch)| { + ch.is_ascii_alphabetic() + && chars[..index] + .iter() + .any(|prefix| crate::utils::is_korean_char(*prefix)) + && is_korean_prose_roman_hyphen_identifier(&chars[index..]) + }); + if enclosed_roman_continuation { + return true; + } + + chars.windows(3).enumerate().any(|(index, window)| { + if !crate::utils::is_korean_char(window[0]) + || window[1] != '-' + || !window[2].is_ascii_alphabetic() + { + return false; + } + + let roman_tail = &chars[index + 2..]; + let identifier_len = roman_tail + .iter() + .take_while(|ch| ch.is_ascii_alphanumeric()) + .count(); + let letter_count = roman_tail[..identifier_len] + .iter() + .filter(|ch| ch.is_ascii_alphabetic()) + .count(); + let identifier_is_unambiguous = window[2].is_ascii_uppercase() || letter_count >= 2; + let has_explicit_math_operator = roman_tail.iter().any(|ch| { + matches!( + *ch, + '+' | '−' + | '×' + | '÷' + | '=' + | '<' + | '>' + | '≤' + | '≥' + | '≠' + | '≈' + | '^' + | '_' + | '/' + | '*' + | '|' + | '∈' + | '∉' + | '⊂' + | '⊃' + | '∧' + | '∨' + ) + }); + + identifier_is_unambiguous && !has_explicit_math_operator + }) +} + +/// Whether a spaced `A(31)`-shaped label is followed by ordinary Korean prose. +/// +/// A print-space plus a Korean person role (`도의원`, `교수`, `부장판사`) +/// resolves the same function-notation ambiguity as an honorific does. The +/// explicit mathematical value/product cues remain on the math route. This +/// predicate deliberately requires a real source space and an all-Korean next +/// word; attached particles are handled by the narrower label splitter. +pub(super) fn next_word_begins_korean_prose_label_context( + tokens: &[Token<'_>], + index: usize, +) -> bool { + if !matches!(tokens.get(index + 1), Some(Token::Space(_))) + || next_word_starts_with_math_value_cue(tokens, index) + { + return false; + } + + next_indexed_word_skip_space(tokens, index + 1).is_some_and(|(next_index, word)| { + next_index > index + 1 + && word.chars.iter().any(|ch| is_korean_char(*ch)) + && word + .chars + .iter() + .all(|ch| is_korean_char(*ch) || matches!(*ch, ',' | '.' | '!' | '?')) + }) +} + +/// Rule 34 parenthetical Roman prose headed by a multi-character acronym. +/// Requiring at least two alphanumeric head characters and rejecting math +/// operators keeps `f(x)` / `A(x+1)` in the math engine. +pub(super) fn is_korean_prose_acronym_parenthetical(chars: &[char]) -> bool { + let chars = trim_roman_identifier_edge(chars); + let Some(open) = chars.iter().position(|ch| *ch == '(') else { + return false; + }; + let head = &chars[..open]; + if head.len() < 2 + || !head.iter().all(char::is_ascii_alphanumeric) + || !head.iter().any(char::is_ascii_uppercase) + { + return false; + } + + let after_open = &chars[open + 1..]; + let close = after_open.iter().position(|ch| *ch == ')'); + let body = close.map_or(after_open, |index| &after_open[..index]); + if body.is_empty() + || !body + .iter() + .all(|ch| ch.is_ascii_alphanumeric() || is_roman_hyphen(*ch)) + { + return false; + } + close.is_none_or(|index| { + is_roman_parenthetical_prose_trailer(after_open[index + 1..].iter().copied()) + }) +} + fn has_ascii_letter_korean_math_suffix(chars: &[char]) -> bool { if chars.len() < 3 { return false; @@ -169,8 +820,10 @@ fn prev_word_is_math_product_cue(tokens: &[Token<'_>], index: usize) -> bool { /// prints multiword prose inside one paired parenthesis. The token parser keeps /// the spaces as separate tokens, so the final `Letters)` fragment must not be /// mistaken for a standalone mathematical expression merely because it has a -/// closing bracket. An ASCII letter immediately before the opening parenthesis -/// is excluded so function-call syntax such as `f(x)` remains math-owned. +/// closing bracket. A lowercase/mixed-case ASCII letter immediately before the +/// opening parenthesis is excluded so function-call syntax such as `f(x)` +/// remains math-owned; a complete all-capitals initialism (`WTO(World ...),`) +/// is the ordinary rule-29/34 prose form. fn is_multiword_closed_roman_parenthetical_tail( tokens: &[Token<'_>], index: usize, @@ -200,10 +853,13 @@ fn is_multiword_closed_roman_parenthetical_tail( if after.is_empty() || !after.chars().all(|ch| ch.is_ascii_alphabetic()) { return false; } + let before_is_initialism = before.chars().count() >= 2 + && before.chars().all(|ch| ch.is_ascii_uppercase()); if before .chars() .next_back() .is_some_and(|ch| ch.is_ascii_alphabetic()) + && !before_is_initialism { return false; } @@ -280,6 +936,199 @@ fn is_multiword_closed_roman_parenthetical_head( } } +/// Returns whether `index` belongs to a complete prose parenthetical that is +/// attached directly to Korean text. +/// +/// Korean rules 34 and 54 keep the Korean parenthesis outside the enclosed +/// Roman section (`링컨(Lincoln)은`: `⠦⠄⠴...⠠⠴`). The generic mathematics +/// detector must therefore not take ownership merely because the enclosed +/// text is an all-capitals identifier, an alphanumeric name, or a decimal. +/// This scan covers a parenthetical split across whitespace tokens as well as +/// a digit immediately following its close (`용어(Web)3`). +/// +/// A one-letter variable and an expression carrying an unambiguous operator +/// remain math-owned. This is the structural distinction between the rule-34 +/// prose form and ordinary function/expression notation such as `함수(x+1)`. +fn is_within_attached_korean_prose_parenthetical(tokens: &[Token<'_>], index: usize) -> bool { + #[derive(Clone, Copy)] + struct Opening { + token_index: usize, + char_index: usize, + attached_to_korean_prose: bool, + } + + fn enclosed_chars( + tokens: &[Token<'_>], + opening: Opening, + close_token_index: usize, + close_char_index: usize, + ) -> Vec { + let mut body = Vec::new(); + for (token_index, token) in tokens + .iter() + .enumerate() + .take(close_token_index + 1) + .skip(opening.token_index) + { + match token { + Token::Word(word) => { + let start = if token_index == opening.token_index { + opening.char_index + 1 + } else { + 0 + }; + let end = if token_index == close_token_index { + close_char_index + } else { + word.chars.len() + }; + if start <= end && end <= word.chars.len() { + body.extend_from_slice(&word.chars[start..end]); + } + } + Token::Space(_) => body.push(' '), + Token::Mode(_) => {} + Token::Fraction(_) | Token::PreEncoded(_) => return Vec::new(), + } + } + body + } + + fn is_prose_body(body: &[char]) -> bool { + let body = body + .iter() + .copied() + .skip_while(|ch| ch.is_whitespace()) + .collect::>(); + let body = body + .iter() + .copied() + .rev() + .skip_while(|ch| ch.is_whitespace()) + .collect::>() + .into_iter() + .rev() + .collect::>(); + if body.is_empty() || body.iter().any(|ch| matches!(*ch, '(' | ')')) { + return false; + } + + // Operators which cannot be ordinary punctuation or part of a Roman + // identifier make the enclosure an explicit mathematical expression. + if body.iter().any(|ch| { + matches!( + *ch, + '=' | '<' + | '>' + | '≤' + | '≥' + | '≠' + | '≈' + | '≡' + | '×' + | '÷' + | '√' + | '∑' + | '∏' + | '∫' + | '∈' + | '∉' + | '⊂' + | '⊃' + | '^' + | '_' + ) + }) { + return false; + } + + // A Korean explanation inside an attached parenthesis is prose. Its + // embedded Roman/numeric fragments are still handled compositionally + // by rules 28-35 after this token rule declines the whole expression. + if body.iter().any(|ch| is_korean_char(*ch)) { + return true; + } + + let numeric_annotation = body.iter().any(char::is_ascii_digit) + && body.iter().all(|ch| { + ch.is_ascii_digit() + || ch.is_whitespace() + || matches!(*ch, '.' | ',' | '%' | '‰' | '+' | '-' | '−' | '~') + }); + if numeric_annotation { + return true; + } + + let ascii_alphanumeric_count = body.iter().filter(|ch| ch.is_ascii_alphanumeric()).count(); + let has_ascii_letter = body.iter().any(char::is_ascii_alphabetic); + ascii_alphanumeric_count >= 2 + && has_ascii_letter + && body.iter().all(|ch| { + ch.is_ascii_alphanumeric() + || ch.is_whitespace() + || matches!( + *ch, + ',' | '.' + | ':' + | ';' + | '\'' + | '’' + | '-' + | '‐' + | '‑' + | '‒' + | '–' + | '—' + | '/' + | '&' + | '·' + | '⋅' + ) + }) + } + + let mut openings = Vec::::new(); + for (token_index, token) in tokens.iter().enumerate() { + let Token::Word(word) = token else { + continue; + }; + for (char_index, ch) in word.chars.iter().copied().enumerate() { + match ch { + '(' => openings.push(Opening { + token_index, + char_index, + attached_to_korean_prose: { + let prefix = &word.chars[..char_index]; + let prefix_contains_korean = prefix.iter().any(|ch| is_korean_char(*ch)); + let numeric_prefix = !prefix.is_empty() + && prefix.iter().any(char::is_ascii_digit) + && prefix.iter().all(|ch| { + ch.is_ascii_digit() + || matches!(*ch, '.' | ',' | '\'' | '’' | '"' | '”' | '‘' | '“') + }); + prefix_contains_korean + || (numeric_prefix && has_adjacent_korean_word(tokens, token_index)) + }, + }), + ')' => { + let Some(opening) = openings.pop() else { + continue; + }; + if opening.attached_to_korean_prose + && opening.token_index <= index + && index <= token_index + && is_prose_body(&enclosed_chars(tokens, opening, token_index, char_index)) + { + return true; + } + } + _ => {} + } + } + } + false +} + /// Walks backward from `index - 1`, skipping `Space`, returning whether the /// preceding content is a math-letter Word or a math-context PreEncoded. fn prev_is_math_context_for_ellipsis(tokens: &[Token<'_>], index: usize) -> bool { @@ -376,13 +1225,36 @@ fn prev_prev_is_math_or_mixed_context(tokens: &[Token<'_>], index: usize) -> boo false } -/// Detect a Word that is exactly the logic XOR symbol `⊻` (U+22BB). +/// Detect one unambiguous set/logic symbol from math rules 60-61. /// -/// PDF 수학 — `A ⊻ B` 패턴에서 양쪽 대문자를 math 변수로 처리하기 위해 사용. -pub(super) fn is_logic_symbol_word(word: &crate::rules::token::WordToken<'_>) -> bool { - word.chars - .first() - .is_some_and(|c| word.chars.len() == 1 && matches!(*c, '⊻')) +/// These Unicode signs are not Roman-prose punctuation. A separated adjacent +/// capital therefore remains a math variable instead of entering UEB grade-1 +/// text (`A ¬ B`, `{x | x ∈ R}`). +pub(super) fn is_set_or_logic_symbol_word(word: &crate::rules::token::WordToken<'_>) -> bool { + word.chars.first().is_some_and(|c| { + word.chars.len() == 1 + && matches!( + *c, + '¬' | '∈' + | '∋' + | '∉' + | '∌' + | '⊂' + | '⊃' + | '⊄' + | '⊅' + | '∪' + | '∩' + | '∀' + | '∃' + | '∄' + | '∧' + | '∨' + | '⊻' + | '⇒' + | '⇔' + ) + }) } /// PDF — Compute leading spaces for a math token inserted at `index` based on @@ -423,8 +1295,65 @@ pub(super) fn run<'a>( let text = word.text.as_ref(); + // Preserve the more specific anonymized-person grammar before the general + // rule-34 prose-parenthetical guard below. A Korean name fragment may be + // attached before the Roman initial (`모A(61)씨`), so this must split and + // retain that prefix rather than merely declining whole-token math. + if state.english_indicator + && !state.math_mode_active + && let Some(replacement) = split_anonymized_person_label(&word.chars) + { + return Ok(TokenAction::ReplaceMany(replacement)); + } + if is_multiword_closed_roman_parenthetical_head(tokens, index, word) || is_multiword_closed_roman_parenthetical_tail(tokens, index, word) + || is_within_attached_korean_prose_parenthetical(tokens, index) + { + return Ok(TokenAction::Noop); + } + + // Korean rules 29, 35, 54: in anonymized-person prose, encode the Roman + // initial, Korean parentheses and age compositionally even when the + // following Korean honorific/role is separated by a print-space. The + // following word is deliberately left as its own token so source spacing + // is preserved. + if state.english_indicator + && !state.math_mode_active + && next_word_begins_korean_prose_label_context(tokens, index) + && let Some(encoded) = encode_anonymized_person_label(&word.chars) + { + return Ok(TokenAction::Replace(Token::PreEncoded(encoded))); + } + + // In ordinary Korean prose, rules 28-35 own structurally Roman identifiers. + // Do this before the generic `letter + operator` math detector: ASCII '-' is + // both a math minus candidate and the hyphen used by the official `D-100`. + if state.english_indicator + && !state.math_mode_active + && (is_korean_prose_roman_hyphen_identifier(&word.chars) + || is_korean_prose_roman_number_identifier(&word.chars) + || is_korean_prose_roman_slash_identifier(&word.chars) + || is_korean_prose_single_letter_slash_phrase(tokens, index, &word.chars) + || is_korean_prose_roman_plus_identifier(&word.chars) + || has_korean_prefix_roman_plus_annotation(&word.chars) + || has_korean_prefix_terminal_roman_plus_identifier(&word.chars) + || has_korean_prefix_roman_hyphen_suffix(&word.chars) + || is_korean_prose_acronym_parenthetical(&word.chars)) + { + return Ok(TokenAction::Noop); + } + + // Korean rules 29 and 35 also own a number immediately followed by a Roman + // letters-sequence in ordinary Korean prose. Keep an isolated `3ab` on the + // mathematical route, and preserve explicit math mode plus the established + // Korean `곱`/`값` cues for genuinely mathematical uses. + if state.english_indicator + && !state.math_mode_active + && has_adjacent_korean_word(tokens, index) + && is_korean_prose_numeric_roman_identifier(&word.chars) + && !prev_word_is_math_product_cue(tokens, index) + && !next_word_starts_with_math_value_cue(tokens, index) { return Ok(TokenAction::Noop); } @@ -674,9 +1603,13 @@ pub(super) fn run<'a>( } } - // Numeric middle-dot forms in Korean prose (e.g. 3·1 운동) should stay non-math, - // while standalone numeric expressions like 6·9 should be routed to math. - if is_middle_dot_numeric_word(&word.chars) && has_adjacent_korean_word(tokens, index) { + // Korean Rules 43, 47 [appendix], 48, and 50: numeric punctuation in prose + // (`3·1 운동`, `1/3 규모`, `.515로`, `1.7~2.4 사이`) remains on the + // ordinary number/punctuation path. A standalone expression keeps using + // the math engine because there is no adjacent Korean prose context. + if (is_middle_dot_numeric_word(&word.chars) || is_korean_prose_numeric_notation(&word.chars)) + && has_adjacent_korean_word(tokens, index) + { return Ok(TokenAction::Noop); } @@ -696,12 +1629,52 @@ pub(super) fn run<'a>( } } - // Logical symbols separated by spaces should still treat uppercase letters as variables. + // Math rules 60-61: process a separated right-hand capital while the + // set/logic sign is still a Word token. Once the sign becomes PreEncoded, + // neighbour lookup intentionally stops at that boundary and the capital + // would otherwise fall through to UEB prose (and gain a grade-1 marker). + // + // The right token may retain non-alphanumeric punctuation or a Korean + // suffix (`R}`, `P는`), but another ASCII letter/digit means it is a Roman + // word or identifier rather than one mathematical variable (`Road`). + if is_set_or_logic_symbol_word(word) + && let Some((right_index, right_word)) = next_indexed_word_skip_space(tokens, index + 1) + && right_word + .chars + .first() + .is_some_and(char::is_ascii_uppercase) + && right_word.chars[1..] + .iter() + .all(|ch| crate::utils::is_korean_char(*ch) || !ch.is_ascii_alphanumeric()) + { + let symbol = math_symbol_shortcut::encode_char_math_symbol_shortcut(word.chars[0])?; + let upper = right_word.chars[0]; + let code = crate::english::encode_english(upper.to_ascii_lowercase())?; + let mut replacement: Vec> = vec![Token::PreEncoded(symbol.to_vec())]; + replacement.extend(tokens[index + 1..right_index].iter().cloned()); + replacement.push(Token::PreEncoded(vec![32, code])); + + if right_word.chars.len() > 1 { + let suffix = right_word.chars[1..].iter().collect::(); + replacement.push(build_word_token(suffix)); + } + + return Ok(TokenAction::ReplaceRange( + right_index + 1 - index, + replacement, + )); + } + + // Set/logic symbols separated by spaces still own adjacent uppercase math + // variables. Emit the capital indicator here so the later UEB token rules + // cannot reinterpret the one-letter variable as an alphabetic wordsign. if word.chars.len() == 1 && word.chars[0].is_ascii_uppercase() { let (prev, next) = prev_next_words(tokens, index); - if prev.is_some_and(is_logic_symbol_word) || next.is_some_and(is_logic_symbol_word) { + if prev.is_some_and(is_set_or_logic_symbol_word) + || next.is_some_and(is_set_or_logic_symbol_word) + { let code = crate::english::encode_english(word.chars[0].to_ascii_lowercase())?; - return Ok(TokenAction::Replace(Token::PreEncoded(vec![code]))); + return Ok(TokenAction::Replace(Token::PreEncoded(vec![32, code]))); } } @@ -982,6 +1955,7 @@ mod tests { #[rstest::rstest] #[case::ueb_multiword_parenthetical("plays (such as Romeo and Juliet)", true)] + #[case::initialism_prefixed_comma("WTO(World Tourism Organization),", true)] #[case::korean_particle_after_parenthesis("설명(Home Connectivity Alliance)를", true)] #[case::korean_particle_after_quote("설명(Home Connectivity Alliance)’를", true)] #[case::ueb_letter_list("(q, r)", false)] @@ -1066,6 +2040,82 @@ mod tests { } } + #[rstest::rstest] + #[case::roman_followed_by_digit("용어(Web)3", 1)] + #[case::roman_then_korean_explanation("기관(KRISS, 원장)", 2)] + #[case::numeric_annotation("최고치(2126.14)", 1)] + #[case::multiword_roman_name("전환(DT·Digital Transformation)", 2)] + #[case::korean_numeric_name("용어2(Version Two)", 2)] + #[case::year_with_roman_explanation("보고서 2023(MWC 2023)", 2)] + #[case::single_variable("함수(x)", 0)] + #[case::lowercase_expression("함수(x+1)", 0)] + #[case::uppercase_expression("식(A+B)", 0)] + #[case::separated_function("함수 f(x)", 0)] + fn recognizes_attached_korean_prose_parenthetical_span( + #[case] input: &str, + #[case] expected_matching_words: usize, + ) { + let ir = crate::rules::token::DocumentIR::parse(input, true); + let matching_indices = ir + .tokens + .iter() + .enumerate() + .filter_map(|(index, token)| { + matches!(token, Token::Word(_)) + .then(|| is_within_attached_korean_prose_parenthetical(&ir.tokens, index)) + .is_some_and(|matches| matches) + .then_some(index) + }) + .collect::>(); + + assert_eq!(matching_indices.len(), expected_matching_words); + for index in matching_indices { + let mut state = EncoderState::new(false); + assert!(matches!( + run(&ir.tokens, index, &mut state).unwrap(), + TokenAction::Noop + )); + } + } + + /// Korean rules 34 and 54 put the Korean opening parenthesis before the + /// Roman indicator; the math route instead starts with a two-cell prose + /// separator. Exercise each accepted body class at the public boundary. + #[rstest::rstest] + #[case::roman_followed_by_digit("용어(Web)3")] + #[case::roman_then_korean_explanation("기관(KRISS, 원장)")] + #[case::multiword_roman_name("전환(DT·Digital Transformation)")] + #[case::korean_numeric_name("용어2(Version Two)")] + #[case::year_with_roman_explanation("보고서 2023(MWC 2023)")] + fn attached_korean_prose_parentheses_keep_rule_34_order(#[case] input: &str) { + let encoded = crate::encode_to_unicode(input).expect("input must encode"); + assert!( + encoded.contains("⠦⠄⠴"), + "Korean opening parenthesis must precede Roman entry: {encoded}" + ); + } + + #[test] + fn attached_korean_name_keeps_specialized_anonymized_person_path() { + let ir = crate::rules::token::DocumentIR::parse("모A(61)씨", true); + let index = ir + .tokens + .iter() + .position(|token| matches!(token, Token::Word(_))) + .expect("fixture must contain a word"); + let mut state = EncoderState::new(true); + + assert!(matches!( + run(&ir.tokens, index, &mut state).unwrap(), + TokenAction::ReplaceMany(_) + )); + assert!( + crate::encode_to_unicode("모A(61)씨") + .expect("fixture must encode") + .contains("⠴⠠⠁⠦⠄⠼⠋⠁⠠⠴") + ); + } + /// Decimal-context spacing recognizes each structural marker independently: /// the parser sentinel, the Rule 12 ellipsis, and a combining math mark. #[rstest::rstest] @@ -1106,6 +2156,63 @@ mod tests { Token::Space(SpaceKind::Regular) } + #[rstest::rstest] + #[case::compact_unit("50bp", true)] + #[case::decimal_prefix("3.1p", true)] + #[case::ordinal("1st", true)] + #[case::mixed_case_name("25Project", true)] + #[case::digit_after_letter("3x3", true)] + #[case::capital_suffix("6G", true)] + #[case::trailing_punctuation("50bp,", true)] + #[case::letter_first("MP3", false)] + #[case::operator("3a+b", false)] + #[case::solidus("3/4", false)] + #[case::punctuation_before_letter("3.a", false)] + #[case::number_only("3", false)] + #[case::letters_only("abc", false)] + #[case::korean_suffix("3한", false)] + fn recognizes_numeric_prefix_roman_identifier_grammar( + #[case] text: &str, + #[case] expected: bool, + ) { + assert_eq!( + is_korean_prose_numeric_roman_identifier(&text.chars().collect::>()), + expected + ); + } + + #[rstest::rstest] + #[case::compact_unit("가는 50bp 인상", "50bp", true)] + #[case::decimal_prefix("가는 3.1p 표본", "3.1p", true)] + #[case::ordinal("가는 1st 항목", "1st", true)] + #[case::mixed_case_name("가는 25Project 자료", "25Project", true)] + #[case::digit_after_letter("가는 3x3 배열", "3x3", true)] + #[case::isolated_expression("3ab", "3ab", false)] + #[case::previous_product_cue("곱 3ab 결과", "3ab", false)] + #[case::next_value_cue("식은 3ab 값을", "3ab", false)] + #[case::explicit_latex("가는 $3ab$ 식", "$3ab$", false)] + fn numeric_roman_route_respects_korean_prose_and_math_context( + #[case] input: &str, + #[case] target: &str, + #[case] expected_noop: bool, + ) { + let ir = crate::rules::token::DocumentIR::parse(input, true); + let index = ir + .tokens + .iter() + .position(|token| matches!(token, Token::Word(word) if word.text.as_ref() == target)) + .expect("target word must be tokenized as one word"); + let mut state = EncoderState::new(true); + + assert_eq!( + matches!( + run(&ir.tokens, index, &mut state).unwrap(), + TokenAction::Noop + ), + expected_noop + ); + } + /// The complete token-rule path must preserve both defensive boundaries: /// an unsupported mixed-math glyph falls through, and a leading space with /// no preceding math token is not treated as mixed-math continuation. @@ -1199,21 +2306,35 @@ mod tests { assert!(!next_word_starts_with_math_value_cue(&tokens, 0)); } - /// `is_logic_symbol_word` — XOR(⊻) 단독 토큰만 true, 그 외는 false. - /// Kills: `-> false`, `!=` mutations. + /// Only one complete rule-60/61 set or logic sign is accepted. #[rstest::rstest] #[case::xor_alone("⊻", true)] - #[case::wedge_alone("∧", false)] + #[case::wedge_alone("∧", true)] + #[case::membership_alone("∈", true)] + #[case::negation_alone("¬", true)] + #[case::ascii_plus("+", false)] #[case::xor_then_letter("⊻x", false)] #[case::empty_word("", false)] - fn is_logic_symbol_word_matches_only_xor(#[case] text: &'static str, #[case] expected: bool) { + fn set_or_logic_symbol_word_is_complete(#[case] text: &'static str, #[case] expected: bool) { let chars: Vec = text.chars().collect(); let word = WordToken { text: Cow::Borrowed(text), meta: WordMeta::from_chars(&chars), chars, }; - assert_eq!(is_logic_symbol_word(&word), expected); + assert_eq!(is_set_or_logic_symbol_word(&word), expected); + } + + /// Math rules 60-61: spaces do not turn a capital operand into UEB prose. + #[rstest::rstest] + #[case::upper_negation("A ¬ B", "⠠⠁⠀⠈⠔⠀⠠⠃")] + #[case::mixed_case_negation("p ¬ Q", "⠏⠀⠈⠔⠀⠠⠟")] + #[case::set_builder_membership("{x | x ∈ R}", "⠦⠂⠭⠀⠸⠳⠀⠭⠀⠖⠀⠠⠗⠐⠴")] + fn spaced_set_and_logic_operands_stay_math_variables( + #[case] input: &str, + #[case] expected: &str, + ) { + assert_eq!(crate::encode_to_unicode(input).as_deref(), Ok(expected)); } // ----- Lines 66-110: `a ≲ b:` colon-suffix math merge ----- diff --git a/libs/braillify/src/rules/token_rules/math_expression/detect.rs b/libs/braillify/src/rules/token_rules/math_expression/detect.rs index 3dff5afa..dcb29578 100644 --- a/libs/braillify/src/rules/token_rules/math_expression/detect.rs +++ b/libs/braillify/src/rules/token_rules/math_expression/detect.rs @@ -100,11 +100,23 @@ pub(super) fn is_math_expression(chars: &[char], text: &str) -> bool { return false; } + // Korean rules 33 and 35: a letter-led Roman/number identifier remains + // Roman text when ordinary prose punctuation follows it. The punctuation + // alone must not turn `MP3`-shaped text into a mathematical expression. + if let Some((trailing, core)) = chars.split_last() + && matches!(*trailing, ',' | ';' | ':' | '.') + && core.first().is_some_and(|ch| ch.is_ascii_alphabetic()) + && core.iter().any(|ch| ch.is_ascii_digit()) + && core.iter().all(|ch| ch.is_ascii_alphanumeric()) + { + return false; + } + // PDF 제33·34·69항: 숫자+로마자 단위와 바로 뒤의 종료표 생략 문장부호는 // 수식이 아니라 하나의 국어 문장 내 단위 표기다. 일반 operator/symbol 판정보다 // 먼저 배제해야 `173cm,` 같은 토큰이 comma 때문에 수식 경로로 우회하지 않는다. - if let Some((_, _, consumed)) = - crate::rules::korean::rule_69::parse_numeric_ascii_unit_prefix(chars) + if let Some(consumed) = + crate::rules::korean::rule_69::parse_numeric_ascii_unit_expression(chars) && (consumed == chars.len() || (consumed + 1 == chars.len() && chars.get(consumed).is_some_and(|symbol| { @@ -330,4 +342,16 @@ mod tests { let chars = input.chars().collect::>(); assert_eq!(super::is_math_expression(&chars, input), expected); } + + /// Rules 33/35: trailing prose punctuation preserves the same non-math + /// classification as the underlying letter-led Roman/number identifier. + #[rstest::rstest] + #[case::comma("MP3,")] + #[case::mixed_case_comma("RX350h,")] + #[case::colon("A4:")] + #[case::period("KF94.")] + fn roman_number_identifier_with_prose_punctuation_is_not_math(#[case] input: &str) { + let chars = input.chars().collect::>(); + assert!(!super::is_math_expression(&chars, input)); + } } diff --git a/libs/braillify/src/rules/token_rules/math_expression/helpers.rs b/libs/braillify/src/rules/token_rules/math_expression/helpers.rs index 69106c5b..b4576b7b 100644 --- a/libs/braillify/src/rules/token_rules/math_expression/helpers.rs +++ b/libs/braillify/src/rules/token_rules/math_expression/helpers.rs @@ -62,12 +62,45 @@ pub(super) fn is_middle_dot_numeric_word(chars: &[char]) -> bool { .iter() .filter(|c| matches!(**c, '\u{00B7}' | '\u{22C5}')) .count(); - if middle_dot_count != 1 { + if middle_dot_count == 0 { return false; } - chars + chars.iter().all(|c| { + c.is_ascii_digit() + || matches!( + *c, + '\u{00B7}' | '\u{22C5}' | '\u{2212}' | '-' | ',' | ';' | ':' + ) + }) +} + +/// Numeric notation which is written as ordinary Korean prose rather than as +/// a standalone mathematical expression. +/// +/// Korean Braille Rules 43, 47 [appendix], 48, and 50 keep the print order of +/// numeric slashes, decimal points, ranges, and middle-dot lists. Routing +/// these tokens through the math-expression layer only adds mathematical +/// delimiters; the character rules already emit the required repeated number +/// signs after `/`, `~`, and `·`. +pub(super) fn is_korean_prose_numeric_notation(chars: &[char]) -> bool { + let has_digit = chars.iter().any(|c| c.is_ascii_digit()); + let has_prose_separator = chars .iter() - .all(|c| c.is_ascii_digit() || matches!(*c, '\u{00B7}' | '\u{22C5}' | '\u{2212}' | '-')) + .any(|c| matches!(*c, '.' | '/' | '~' | '\u{00B7}' | '\u{22C5}')); + let starts_with_signed_minus = chars + .first() + .is_some_and(|c| matches!(*c, '-' | '\u{2212}')); + + has_digit + && has_prose_separator + && !starts_with_signed_minus + && chars.iter().all(|c| { + c.is_ascii_digit() + || matches!( + *c, + '.' | ',' | '-' | '\u{2212}' | '~' | '/' | '\u{00B7}' | '\u{22C5}' | ';' | ':' + ) + }) } pub(super) fn adjacent_korean_word_flags(tokens: &[Token<'_>], index: usize) -> (bool, bool) { @@ -345,6 +378,232 @@ fn build_korean_prefix_math_suffix(prefix: String, bytes: Vec) -> Vec Option { + if !chars.get(start).is_some_and(char::is_ascii_uppercase) || chars.get(start + 1) != Some(&'(') + { + return None; + } + + let mut cursor = start + 2; + let digit_start = cursor; + while chars.get(cursor).is_some_and(char::is_ascii_digit) { + cursor += 1; + } + if cursor == digit_start { + return None; + } + + match chars.get(cursor) { + Some('대') => cursor += 1, + Some('·' | 'ㆍ') + if chars + .get(cursor + 1) + .is_some_and(|ch| matches!(*ch, '여' | '남')) => + { + cursor += 2; + } + _ => {} + } + + (chars.get(cursor) == Some(&')')).then_some(cursor + 1) +} + +fn starts_anonymized_person_marker(chars: &[char], index: usize) -> bool { + chars + .get(index) + .is_some_and(|marker| matches!(*marker, '씨' | '군' | '양')) +} + +/// Whether the label is immediately followed by a Korean human-role noun. +/// +/// The role stem is separated from an attached case particle and classified +/// by its productive title/rank ending (`-사`, `-병`, `-감`, `-관`, `-장`, +/// `-원`). This covers ranks and occupations without enumerating corpus +/// phrases. Mathematical nouns such as `함수`, `변수`, and `값` do not have +/// one of these endings, so `A(14)함수는` remains on the math path. +fn starts_attached_korean_person_role(chars: &[char], index: usize) -> bool { + let suffix = chars[index..] + .iter() + .take_while(|ch| is_korean_char(**ch)) + .collect::(); + if suffix.is_empty() { + return false; + } + + const PARTICLES: &[&str] = &[ + "에게서", + "으로", + "에게", + "께서", + "에서", + "까지", + "부터", + "처럼", + "보다", + "라고", + "이라", + "이랑", + "하고", + "께", + "의", + "이", + "가", + "은", + "는", + "을", + "를", + "와", + "과", + "에", + "도", + "로", + ]; + let stem = PARTICLES + .iter() + .find_map(|particle| suffix.strip_suffix(particle)) + .unwrap_or(&suffix); + + stem.chars().count() >= 2 + && stem + .chars() + .last() + .is_some_and(|ending| matches!(ending, '사' | '병' | '감' | '관' | '장' | '원')) +} + +fn attached_korean_suffix_text(chars: &[char], index: usize) -> String { + chars[index..] + .iter() + .take_while(|ch| is_korean_char(**ch)) + .collect() +} + +fn starts_animate_dative_particle(chars: &[char], index: usize) -> bool { + attached_korean_suffix_text(chars, index).starts_with("에게") +} + +/// A Korean personal name can be printed directly before an anonymizing Roman +/// label, for example `조너선M(41)이`. In that structure a following case +/// particle resolves the `M(41)` function-notation ambiguity. Require a +/// three-syllable-or-longer attached Korean prefix; ordinary mathematical +/// heads such as `함수A(14)는` therefore remain math-owned. +fn has_attached_korean_name_and_case_particle(chars: &[char], start: usize, end: usize) -> bool { + let korean_prefix_len = chars[..start] + .iter() + .rev() + .take_while(|ch| is_korean_char(**ch)) + .count(); + if korean_prefix_len < 3 { + return false; + } + + matches!( + attached_korean_suffix_text(chars, end).as_str(), + "이" | "가" | "은" | "는" | "을" | "를" | "와" | "과" | "의" | "에" + ) +} + +/// A list can defer its person marker to the final member, as in +/// `B(60)·C(41)씨`. Every preceding age label is still Roman prose. Only a +/// middle-dot chain whose eventual member has an explicit person marker is +/// accepted, so an algebraic `A(1)·B(2)` remains mathematical. +fn anonymized_person_chain_has_marker(chars: &[char], mut cursor: usize) -> bool { + while chars.get(cursor) == Some(&'·') { + let next_start = cursor + 1; + let Some(next_end) = anonymized_person_label_end(chars, next_start) else { + return false; + }; + if starts_anonymized_person_marker(chars, next_end) { + return true; + } + cursor = next_end; + } + false +} + +fn anonymized_person_label_span(chars: &[char]) -> Option<(usize, usize)> { + let mut index = 0usize; + while index < chars.len() { + if !chars[index].is_ascii_uppercase() + || index + .checked_sub(1) + .and_then(|previous| chars.get(previous)) + .is_some_and(|previous| previous.is_ascii_alphanumeric()) + || chars.get(index + 1) != Some(&'(') + { + index += 1; + continue; + } + + if let Some(end) = anonymized_person_label_end(chars, index) + && (starts_anonymized_person_marker(chars, end) + || starts_attached_korean_person_role(chars, end) + || starts_animate_dative_particle(chars, end) + || has_attached_korean_name_and_case_particle(chars, index, end) + || anonymized_person_chain_has_marker(chars, end)) + { + return Some((index, end)); + } + index += 1; + } + None +} + +pub(super) fn encode_anonymized_person_label(chars: &[char]) -> Option> { + let (&letter, _) = chars.split_first()?; + if anonymized_person_label_end(chars, 0) != Some(chars.len()) { + return None; + } + + let mut encoded = vec![ + crate::rules::korean::rule_29::ROMAN_INDICATOR, + crate::rules::korean::rule_28::UPPERCASE_SINGLE, + crate::english::encode_english(letter).ok()?, + ]; + let parenthetical = chars[1..].iter().collect::(); + encoded.extend(crate::encode(&parenthetical).ok()?); + Some(encoded) +} + +pub(super) fn split_anonymized_person_label(chars: &[char]) -> Option>> { + let mut replacement = Vec::new(); + let mut cursor = 0usize; + let mut found = false; + + while cursor < chars.len() { + let Some((relative_start, relative_end)) = anonymized_person_label_span(&chars[cursor..]) + else { + break; + }; + let start = cursor + relative_start; + let end = cursor + relative_end; + let encoded = encode_anonymized_person_label(&chars[start..end])?; + if start > cursor { + replacement.push(build_word_token(chars[cursor..start].iter().collect())); + } + replacement.push(Token::PreEncoded(encoded)); + cursor = end; + found = true; + } + + if !found { + return None; + } + if cursor < chars.len() { + replacement.push(build_word_token(chars[cursor..].iter().collect())); + } + Some(replacement) +} + /// Recognize only the suffix shape used after an already-confirmed Korean /// prefix. Korean rule 34's PDF example is `링컨(Lincoln)은`: Roman text may /// be enclosed in a bracket without a Roman terminator. Rule 54 requires the @@ -384,6 +643,10 @@ pub(super) fn split_mixed_math_word( return None; } + if let Some(replacement) = split_anonymized_person_label(&word.chars) { + return Some(replacement); + } + let chars = &word.chars; let len = chars.len(); @@ -460,6 +723,78 @@ mod tests { use crate::rules::math::math_token_rule::MathContext; use crate::rules::token::SpaceKind; + #[rstest::rstest] + #[case::adult("A(54)씨는", true)] + #[case::minor_male("B(17)군에게", true)] + #[case::minor_female("C(16)양은", true)] + #[case::gender_annotation("A(41·여)씨는", true)] + #[case::age_decade("B(30대)씨는", true)] + #[case::korean_name_prefix("김모A(41)씨", true)] + #[case::military_rank("A(21)상병을", true)] + #[case::police_rank("B(42)경사가", true)] + #[case::occupation("C(47)원사에게", true)] + #[case::animate_dative("A(30)에게", true)] + #[case::attached_korean_name("조너선M(41)이", true)] + #[case::math_function_particle("A(14)는", false)] + #[case::attached_math_function("함수A(14)는", false)] + #[case::math_function_noun("A(14)함수는", false)] + #[case::non_honorific_syllable("A(14)시는", false)] + #[case::missing_digits("A()씨", false)] + fn recognizes_only_anonymized_person_labels(#[case] input: &str, #[case] expected: bool) { + assert_eq!( + anonymized_person_label_span(&input.chars().collect::>()).is_some(), + expected + ); + } + + #[rstest::rstest] + #[case::adult("A(54)씨는")] + #[case::minor_male("B(17)군에게")] + #[case::minor_female("C(16)양은")] + #[case::gender_annotation("A(41·여)씨는")] + #[case::age_decade("B(30대)씨는")] + fn anonymized_person_labels_use_korean_prose_cells(#[case] input: &str) { + let chars = input.chars().collect::>(); + let word = WordToken { + text: Cow::Borrowed(input), + chars: chars.clone(), + meta: WordMeta::from_chars(&chars), + }; + + let replacement = split_mixed_math_word(&word, 0, MathContext::default()) + .expect("honorific resolves the function/prose ambiguity"); + assert!(matches!( + replacement.as_slice(), + [Token::PreEncoded(_), Token::Word(_)] + )); + let Token::PreEncoded(label) = &replacement[0] else { + unreachable!(); + }; + let (start, end) = anonymized_person_label_span(&chars).expect("label span"); + let expected = encode_anonymized_person_label(&chars[start..end]).expect("label cells"); + assert_eq!(label, &expected); + } + + #[rstest::rstest] + #[case::deferred_marker("B(60)·C(41)씨", 2)] + #[case::child_markers("B(6)군·C(3)양이", 2)] + #[case::three_people("A(41)씨·B(28)씨와C(27)씨가", 3)] + fn splits_every_anonymized_person_label_in_one_token( + #[case] input: &str, + #[case] expected_labels: usize, + ) { + let chars = input.chars().collect::>(); + let replacement = split_anonymized_person_label(&chars).expect("person-label list"); + + assert_eq!( + replacement + .iter() + .filter(|token| matches!(token, Token::PreEncoded(_))) + .count(), + expected_labels + ); + } + /// helpers:235 — `try_encode_math_slice` fallback to `crate::encode` when /// math encoder fails. Use `f(~)`: passes `has_function_call` candidacy /// (1-letter + `(`) and is_math_expression, but math encoder rejects `~`. diff --git a/libs/braillify/src/rules/token_rules/middle_dot_spacing.rs b/libs/braillify/src/rules/token_rules/middle_dot_spacing.rs index 4be011c9..da909994 100644 --- a/libs/braillify/src/rules/token_rules/middle_dot_spacing.rs +++ b/libs/braillify/src/rules/token_rules/middle_dot_spacing.rs @@ -1,8 +1,69 @@ -use crate::rules::token::Token; +use std::borrow::Cow; + +use crate::rules::token::{Token, WordMeta, WordToken}; use crate::rules::token_rule::{TokenAction, TokenPhase, TokenRule}; pub struct MiddleDotSpacingRule; +fn previous_word<'a, 'b>(tokens: &'b [Token<'a>], index: usize) -> Option<&'b WordToken<'a>> { + tokens[..index] + .iter() + .rev() + .find_map(|token| match token { + Token::Mode(_) => None, + Token::Word(word) => Some(Some(word)), + _ => Some(None), + }) + .flatten() +} + +fn next_word<'a, 'b>(tokens: &'b [Token<'a>], index: usize) -> Option<(usize, &'b WordToken<'a>)> { + tokens + .iter() + .enumerate() + .skip(index + 1) + .find_map(|(token_index, token)| match token { + Token::Mode(_) | Token::Space(_) => None, + Token::Word(word) => Some(Some((token_index, word))), + _ => Some(None), + }) + .flatten() +} + +/// Rules 51 and 59 attach a Korean colon/semicolon to the item on its left. +/// A spaced colon between two Roman/number items remains UEB print spacing, +/// so require a Korean item on either side of the punctuation boundary. +fn space_precedes_korean_colon_or_semicolon( + tokens: &[Token<'_>], + index: usize, + previous: &WordToken<'_>, +) -> bool { + let Some((punctuation_index, punctuation)) = next_word(tokens, index) else { + return false; + }; + if !punctuation + .chars + .first() + .is_some_and(|symbol| matches!(symbol, ':' | ';')) + || punctuation.chars.len() != 1 + { + return false; + } + + previous + .chars + .iter() + .rev() + .find(|ch| ch.is_ascii_alphanumeric() || crate::utils::is_korean_char(**ch)) + .is_some_and(|ch| crate::utils::is_korean_char(*ch)) + || next_word(tokens, punctuation_index).is_some_and(|(_, word)| { + word.chars + .iter() + .find(|ch| ch.is_ascii_alphanumeric() || crate::utils::is_korean_char(**ch)) + .is_some_and(|ch| crate::utils::is_korean_char(*ch)) + }) +} + impl TokenRule for MiddleDotSpacingRule { fn phase(&self) -> TokenPhase { TokenPhase::PostWord @@ -18,17 +79,48 @@ impl TokenRule for MiddleDotSpacingRule { index: usize, _state: &mut crate::rules::context::EncoderState, ) -> Result, String> { + // Merge a one-sided editorial space at the token boundary so the + // middle dot is encoded with the same character context as canonical + // `정치·경제`, not merely emitted as an adjacent second word. + if let Some(Token::Word(left)) = tokens.get(index) + && matches!(tokens.get(index + 1), Some(Token::Space(_))) + && let Some(Token::Word(right)) = tokens.get(index + 2) + && (left.chars.last() == Some(&'·') || right.chars.first() == Some(&'·')) + { + let text = format!("{}{}", left.text, right.text); + let chars = text.chars().collect::>(); + return Ok(TokenAction::ReplaceRange( + 3, + vec![Token::Word(WordToken { + text: Cow::Owned(text), + chars: chars.clone(), + meta: WordMeta::from_chars(&chars), + })], + )); + } + let Some(Token::Space(_)) = tokens.get(index) else { return Ok(TokenAction::Noop); }; - let Some(Token::Word(prev)) = index.checked_sub(1).and_then(|i| tokens.get(i)) else { + let Some(prev) = previous_word(tokens, index) else { return Ok(TokenAction::Noop); }; - let Some(Token::Word(next)) = tokens.get(index + 1) else { + let Some((_, next)) = next_word(tokens, index) else { return Ok(TokenAction::Noop); }; + // Korean rule 50: the middle dot is attached on both sides. Its print + // source sometimes contains editorial spaces, but the braille spacing + // is still canonicalized by the rule. + if prev.chars.last() == Some(&'·') || next.chars.first() == Some(&'·') { + return Ok(TokenAction::ReplaceMany(vec![])); + } + + if space_precedes_korean_colon_or_semicolon(tokens, index, prev) { + return Ok(TokenAction::ReplaceMany(vec![])); + } + let prev_text = prev.text.as_ref(); let next_text = next.text.as_ref(); @@ -45,3 +137,30 @@ impl TokenRule for MiddleDotSpacingRule { Ok(TokenAction::Noop) } } + +#[cfg(test)] +mod tests { + /// Korean rules 50, 51, and 59 determine braille spacing even when the + /// print source contains editorial spaces around the punctuation. + #[rstest::rstest] + #[case::middle_dot_both_sides("정치 · 경제", "정치·경제")] + #[case::middle_dot_left("정치 ·경제", "정치·경제")] + #[case::middle_dot_right("정치· 경제", "정치·경제")] + #[case::korean_colon("제목 : 내용", "제목: 내용")] + #[case::roman_to_korean_colon("WHO : 세계", "WHO: 세계")] + #[case::korean_semicolon("채소 ; 과일", "채소; 과일")] + fn canonical_korean_punctuation_spacing(#[case] spaced: &str, #[case] canonical: &str) { + assert_eq!(crate::encode(spaced), crate::encode(canonical)); + } + + /// Rule 32 leaves print spacing inside a Roman section to UEB. A Korean + /// prefix earlier in the token does not turn `FAPAS : Food` into a Korean + /// colon boundary because the immediately preceding item is Roman. + #[test] + fn attached_roman_item_preserves_space_before_ueb_colon() { + assert_ne!( + crate::encode("설명(FAPAS : Food)"), + crate::encode("설명(FAPAS: Food)") + ); + } +} diff --git a/libs/braillify/src/rules/token_rules/normalize.rs b/libs/braillify/src/rules/token_rules/normalize.rs index 06b48b89..864538b5 100644 --- a/libs/braillify/src/rules/token_rules/normalize.rs +++ b/libs/braillify/src/rules/token_rules/normalize.rs @@ -3,6 +3,161 @@ use std::borrow::Cow; use crate::rules::token::{Token, WordToken}; use crate::rules::token_rule::{TokenAction, TokenPhase, TokenRule}; +/// Normalize the ASCII glyph substitutes `<` and `>` to the single angle +/// brackets defined by Korean Braille Standard Article 49 when they form a +/// balanced prose enclosure in a Korean document. +/// +/// U+003C/U+003E still retain their mathematical comparison meaning in an +/// actual relation (`xz`). News and publishing text commonly substitutes +/// the ASCII glyphs for U+3008/U+3009 around titles, including enclosures that +/// span print spaces, so the decision has to be document-wide rather than +/// character-local. +pub struct NormalizeAsciiAngleBrackets; + +#[derive(Clone, Copy)] +struct FlatChar { + token_index: usize, + char_index: usize, + ch: char, +} + +fn flattened_chars(tokens: &[Token<'_>]) -> Vec { + let mut flattened = Vec::new(); + for (token_index, token) in tokens.iter().enumerate() { + match token { + Token::Word(word) => flattened.extend(word.chars.iter().copied().enumerate().map( + |(char_index, ch)| FlatChar { + token_index, + char_index, + ch, + }, + )), + Token::Space(_) => flattened.push(FlatChar { + token_index, + char_index: usize::MAX, + ch: ' ', + }), + Token::Fraction(_) | Token::Mode(_) | Token::PreEncoded(_) => {} + } + } + flattened +} + +fn is_simple_relation_operand(chars: &[FlatChar]) -> bool { + let visible = chars + .iter() + .filter(|item| !item.ch.is_whitespace()) + .map(|item| item.ch) + .collect::>(); + if visible.is_empty() || !visible.iter().all(char::is_ascii_alphanumeric) { + return false; + } + + visible.iter().all(char::is_ascii_digit) + || (visible.iter().all(char::is_ascii_alphabetic) && visible.len() <= 2) +} + +fn is_chained_comparison(flattened: &[FlatChar], open: usize, close: usize) -> bool { + let left = flattened[..open] + .iter() + .rev() + .find(|item| !item.ch.is_whitespace()) + .map(|item| item.ch); + let right = flattened[close + 1..] + .iter() + .find(|item| !item.ch.is_whitespace()) + .map(|item| item.ch); + + left.is_some_and(|ch| ch.is_ascii_alphanumeric()) + && right.is_some_and(|ch| ch.is_ascii_alphanumeric()) + && is_simple_relation_operand(&flattened[open + 1..close]) +} + +fn ascii_angle_replacements(tokens: &[Token<'_>]) -> Vec<(usize, usize, char)> { + if !tokens + .iter() + .any(|token| matches!(token, Token::Word(word) if word.meta.has_korean)) + { + return Vec::new(); + } + + let flattened = flattened_chars(tokens); + let mut openings = Vec::new(); + let mut replacements = Vec::new(); + + for (index, item) in flattened.iter().enumerate() { + match item.ch { + '<' | '〈' => openings.push(index), + '>' | '〉' => { + let Some(open) = openings.pop() else { + continue; + }; + if open + 1 == index || is_chained_comparison(&flattened, open, index) { + continue; + } + + let opening = flattened[open]; + if opening.ch == '<' { + replacements.push((opening.token_index, opening.char_index, '〈')); + } + if item.ch == '>' { + replacements.push((item.token_index, item.char_index, '〉')); + } + } + _ => {} + } + } + + replacements +} + +impl TokenRule for NormalizeAsciiAngleBrackets { + fn phase(&self) -> TokenPhase { + TokenPhase::Normalization + } + + fn priority(&self) -> u16 { + 90 + } + + fn apply<'a>( + &self, + tokens: &[Token<'a>], + index: usize, + _state: &mut crate::rules::context::EncoderState, + ) -> Result, String> { + let Some(Token::Word(word)) = tokens.get(index) else { + return Ok(TokenAction::Noop); + }; + if !word.chars.iter().any(|ch| matches!(ch, '<' | '>')) { + return Ok(TokenAction::Noop); + } + + let replacements = ascii_angle_replacements(tokens); + let mut chars = word.chars.clone(); + let mut changed = false; + for (_, char_index, replacement) in replacements + .into_iter() + .filter(|(token_index, _, _)| *token_index == index) + { + if let Some(ch) = chars.get_mut(char_index) { + *ch = replacement; + changed = true; + } + } + if !changed { + return Ok(TokenAction::Noop); + } + + let normalized = chars.iter().collect::(); + Ok(TokenAction::Replace(Token::Word(WordToken { + text: Cow::Owned(normalized), + meta: crate::rules::token::WordMeta::from_chars(&chars), + chars, + }))) + } +} + pub struct NormalizeEllipsis; impl TokenRule for NormalizeEllipsis { @@ -42,3 +197,42 @@ impl TokenRule for NormalizeEllipsis { }))) } } + +#[cfg(test)] +mod tests { + use super::*; + use crate::rules::token::{DocumentIR, SpaceKind}; + use crate::rules::token_engine::TokenRuleEngine; + + fn normalize(input: &str) -> String { + let mut ir = DocumentIR::parse(input, false); + let mut engine = TokenRuleEngine::new(); + engine.register(Box::new(NormalizeAsciiAngleBrackets)); + engine + .apply_all(&mut ir.tokens, &mut ir.state) + .expect("normalization must succeed"); + + ir.tokens + .iter() + .map(|token| match token { + Token::Word(word) => word.chars.iter().collect::(), + Token::Space(SpaceKind::Regular) => " ".to_string(), + Token::Fraction(_) | Token::Mode(_) | Token::PreEncoded(_) => String::new(), + }) + .collect() + } + + #[rstest::rstest] + #[case::title_at_start("<제목>을 읽다", "〈제목〉을 읽다")] + #[case::attached_title("책<긴 제목>이다", "책〈긴 제목〉이다")] + #[case::score_tiebreak("경기 7-6<7-3> 2-6", "경기 7-6〈7-3〉 2-6")] + #[case::roman_title("영화 이다", "영화 〈Das Boot〉이다")] + #[case::comparison_chain("식 xz이다", "식 xz이다")] + #[case::single_comparison("식 x( (first, second) } -fn is_ascii_word(word: &WordToken) -> bool { - word.text.chars().all(|c| c.is_ascii_alphabetic()) +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +struct CapitalizedGroup { + /// First affected ASCII capital. Opening punctuation before this position + /// is outside capitals mode (UEB §8.5 placement). + start: usize, + /// First character outside the affected symbols-sequence. A Korean gloss + /// or closing quote attached to the final word begins here. + end: usize, +} + +fn is_opening_passage_punctuation(ch: char) -> bool { + matches!( + ch, + '\'' | '"' | '\u{2018}' | '\u{201c}' | '(' | '[' | '{' | '〈' | '《' | '「' | '『' + ) +} + +fn is_closing_passage_quote(ch: char) -> bool { + matches!( + ch, + '\'' | '"' | '\u{2019}' | '\u{201d}' | '〉' | '》' | '」' | '』' + ) +} + +/// Locate one whitespace-delimited capitalised symbols-sequence. +/// +/// `DocumentIR` deliberately preserves print whitespace and therefore keeps +/// punctuation and a Korean gloss attached to the same `WordToken` (`‘BET`, +/// `ME’이다`, `COSMO(코스모)`). UEB §8.5 places the passage indicators inside +/// opening/closing punctuation and before a following non-Roman gloss, so the +/// token rule needs the precise affected slice instead of asking whether the +/// entire token is ASCII. +fn capitalized_group(word: &WordToken<'_>) -> Option { + if word.chars.iter().any(char::is_ascii_lowercase) { + return None; + } + + let start = word.chars.iter().position(char::is_ascii_uppercase)?; + if !word.chars[..start] + .iter() + .copied() + .all(is_opening_passage_punctuation) + { + return None; + } + + let last_capital = word.chars.iter().rposition(char::is_ascii_uppercase)?; + if word.chars[start..=last_capital] + .iter() + .any(|ch| crate::utils::is_korean_char(*ch)) + { + return None; + } + + let mut end = word.chars.len(); + for index in last_capital + 1..word.chars.len() { + let ch = word.chars[index]; + let opens_attached_korean_gloss = matches!(ch, '(' | '[' | '{') + && word.chars[index + 1..] + .iter() + .any(|next| crate::utils::is_korean_char(*next)); + if crate::utils::is_korean_char(ch) + || is_closing_passage_quote(ch) + || opens_attached_korean_gloss + { + end = index; + break; + } + } + + Some(CapitalizedGroup { start, end }) +} + +fn owned_word<'a>(chars: &[char]) -> Token<'a> { + let text = chars.iter().collect::(); + Token::Word(WordToken { + text: std::borrow::Cow::Owned(text), + chars: chars.to_vec(), + meta: crate::rules::token::WordMeta::from_chars(chars), + }) +} + +/// A separated uppercase unit is emitted atomically by Korean rule 69, +/// including its Roman and capitalization indicators. Do not pre-emit the +/// generic UEB word prefix for the same letters. +fn is_separated_rule_69_unit(tokens: &[Token<'_>], index: usize, word: &WordToken<'_>) -> bool { + let Some(previous) = prev_word(tokens, index) else { + return false; + }; + let previous_is_number = previous.chars.iter().any(char::is_ascii_digit) + && previous + .chars + .iter() + .all(|ch| ch.is_ascii_digit() || matches!(ch, ',' | '.')); + previous_is_number + && crate::rules::korean::rule_69::complete_ascii_unit_len(&word.chars, 0).is_some() +} + +fn is_single_capital_comma_item(word: &WordToken<'_>) -> bool { + word.chars.first().is_some_and(char::is_ascii_uppercase) + && word.chars.get(1) == Some(&',') + && word + .chars + .iter() + .filter(|ch| ch.is_ascii_uppercase()) + .count() + == 1 +} + +/// 수학 제12항 [붙임 1]의 국어 문장 안 로마자 변수 나열은 각 변수를 +/// 독립된 로마자 항목으로 점역한다 (`세 점 A, B, C가 있다.`). 겉모양만 +/// 보면 UEB 8.5의 세 대문자 symbols-sequence와 같으므로, 앞의 국어 문맥과 +/// 마지막 변수에 붙은 국어 조사를 함께 확인해 대문자 구절로 오인하지 않는다. +/// 문자의 이름은 열거하지 않고 동일한 단일 대문자 콤마 나열 전체에 적용한다. +fn is_korean_math_letter_list_start( + tokens: &[Token<'_>], + index: usize, + word: &WordToken<'_>, + upcoming_first: Option<&WordToken<'_>>, + upcoming_second: Option<&WordToken<'_>>, +) -> bool { + let previous_is_korean = + prev_word(tokens, index).is_some_and(|previous| previous.meta.has_korean); + let Some(first) = upcoming_first else { + return false; + }; + let Some(second) = upcoming_second else { + return false; + }; + let second_group = capitalized_group(second); + let second_has_one_capital = second + .chars + .iter() + .filter(|ch| ch.is_ascii_uppercase()) + .count() + == 1; + let second_has_attached_korean = second_group.is_some_and(|group| { + second.chars[group.end..] + .iter() + .any(|ch| crate::utils::is_korean_char(*ch)) + }); + + previous_is_korean + && is_single_capital_comma_item(word) + && is_single_capital_comma_item(first) + && second_has_one_capital + && second_has_attached_korean } impl TokenRule for UppercasePassageRule { @@ -66,40 +213,93 @@ impl TokenRule for UppercasePassageRule { let (upcoming_first, upcoming_second) = next_two_words(tokens, index); let word_len = word.chars.len(); let ascii_starts_at_beginning = word.meta.starts_with_ascii; + let capitalized = capitalized_group(word); let needs_inline_entry = state.english_indicator && !state.is_english && word.meta.has_ascii_alphabetic - && ascii_starts_at_beginning; + && capitalized.is_some(); + + let upcoming_first_group = upcoming_first.and_then(capitalized_group); + let upcoming_second_group = upcoming_second.and_then(capitalized_group); + let is_korean_math_letter_list = + is_korean_math_letter_list_start(tokens, index, word, upcoming_first, upcoming_second); + let can_start_passage = capitalized.is_some_and(|group| group.end == word_len) + && upcoming_first + .zip(upcoming_first_group) + .is_some_and(|(next, group)| group.start == 0 && group.end == next.chars.len()) + && upcoming_second_group.is_some_and(|group| group.start == 0) + && !is_korean_math_letter_list + && !is_separated_rule_69_unit(tokens, index, word); - if word.meta.is_all_uppercase && !state.triple_big_english && ascii_starts_at_beginning { + if can_start_passage && !state.triple_big_english { + let group = capitalized.expect("passage start has a capitalized group"); + let mut replacement = Vec::new(); + if group.start > 0 { + replacement.push(owned_word(&word.chars[..group.start])); + } if needs_inline_entry { let entry = if state.needs_english_continuation { ModeEvent::EnterEnglishContinue } else { ModeEvent::EnterEnglish }; - prefix.push(Token::Mode(entry)); + replacement.push(Token::Mode(entry)); state.is_english = true; state.needs_english_continuation = false; } - let prev_ascii = prev_word(tokens, index).is_some_and(is_ascii_word); - let can_start_passage = (!state.has_processed_word || !prev_ascii) - && upcoming_first.is_some_and(is_ascii_word) - && upcoming_second.is_some_and(is_ascii_word); + // UEB §5.7.2 + §10.9: inspect the initial maximal ASCII-capital + // letters-sequence rather than the entire whitespace token. Korean + // text or punctuation attached after that run is its boundary, not + // part of the UEB shortform-collision decision (`AC밀란`, `CD,`). + let uppercase_run_len = word + .chars + .iter() + .skip(group.start) + .take_while(|ch| ch.is_ascii_uppercase()) + .count(); + let uppercase_run_end = group.start + uppercase_run_len; + let uppercase_run = word.chars[group.start..uppercase_run_end] + .iter() + .collect::(); + let needs_grade1 = permits_grade1_boundary_after_run(&word.chars[uppercase_run_end..]) + && requires_grade1_indicator(&uppercase_run); + if needs_grade1 { + replacement.push(Token::Mode(ModeEvent::Grade1Indicator)); + } + replacement.push(Token::Mode(ModeEvent::CapsPassageStart)); + replacement.push(owned_word(&word.chars[group.start..])); + state.triple_big_english = true; + state.has_processed_word = true; + return Ok(TokenAction::ReplaceMany(replacement)); + } - // UEB §5.7.2 + §10.9: prepend Grade-1 indicator (⠰) when the uppercase - // letters spell a multi-letter shortform (e.g. CD = "could"). This forces - // literal letter reading and prevents shortform mis-interpretation. - let needs_grade1 = requires_grade1_indicator(word.text.as_ref()); - if can_start_passage { - if needs_grade1 { - prefix.push(Token::Mode(ModeEvent::Grade1Indicator)); - } - prefix.push(Token::Mode(ModeEvent::CapsPassageStart)); - state.triple_big_english = true; - } else if word_len >= 2 { + if word.meta.is_all_uppercase + && !state.triple_big_english + && ascii_starts_at_beginning + && !is_separated_rule_69_unit(tokens, index, word) + { + if needs_inline_entry { + let entry = if state.needs_english_continuation { + ModeEvent::EnterEnglishContinue + } else { + ModeEvent::EnterEnglish + }; + prefix.push(Token::Mode(entry)); + state.is_english = true; + state.needs_english_continuation = false; + } + + let uppercase_run_len = word + .chars + .iter() + .take_while(|ch| ch.is_ascii_uppercase()) + .count(); + let uppercase_run = word.chars[..uppercase_run_len].iter().collect::(); + let needs_grade1 = permits_grade1_boundary_after_run(&word.chars[uppercase_run_len..]) + && requires_grade1_indicator(&uppercase_run); + if word_len >= 2 { if needs_grade1 { prefix.push(Token::Mode(ModeEvent::Grade1Indicator)); } @@ -107,10 +307,23 @@ impl TokenRule for UppercasePassageRule { } } - let next_is_ascii = upcoming_first.is_some_and(is_ascii_word); - if state.triple_big_english && !next_is_ascii { - suffix.push(Token::Mode(ModeEvent::CapsPassageEnd)); + let next_continues_passage = upcoming_first_group.is_some_and(|group| group.start == 0); + if state.triple_big_english && !next_continues_passage { state.triple_big_english = false; + + if let Some(group) = capitalized + && group.start == 0 + && group.end < word_len + { + let replacement = vec![ + owned_word(&word.chars[..group.end]), + Token::Mode(ModeEvent::CapsPassageEnd), + owned_word(&word.chars[group.end..]), + ]; + state.has_processed_word = true; + return Ok(TokenAction::ReplaceMany(replacement)); + } + suffix.push(Token::Mode(ModeEvent::CapsPassageEnd)); } if !state.has_processed_word { @@ -145,6 +358,27 @@ mod tests { }) } + fn spaced_words(words: &[&str]) -> Vec> { + let mut tokens = Vec::with_capacity(words.len().saturating_mul(2).saturating_sub(1)); + for (index, value) in words.iter().enumerate() { + if index > 0 { + tokens.push(Token::Space(SpaceKind::Regular)); + } + tokens.push(word(value)); + } + tokens + } + + fn replacement_words<'a>(tokens: &'a [Token<'_>]) -> Vec<&'a str> { + tokens + .iter() + .filter_map(|token| match token { + Token::Word(word) => Some(word.text.as_ref()), + _ => None, + }) + .collect() + } + /// uppercase_passage:78 — `EnterEnglishContinue` arm fires when /// `state.needs_english_continuation` is true at the moment of inline entry. /// Direct apply with hand-crafted state. @@ -192,24 +426,133 @@ mod tests { assert!(found, "expected EnterEnglish Mode token"); } - /// uppercase_passage:98 — Grade1Indicator pushed for shortform-colliding word - /// (e.g. "CD" = "could") at passage start. - #[test] - fn uppercase_passage_grade1_indicator_for_shortform_direct() { + /// UEB 2.6 + 5.7.2 + 10.9.7-10.9.8: a shortform-confusable sequence gets + /// grade 1 only at a permitted standing-alone/code boundary. `CD` and `LLC` + /// are the rulebook's official shortform-confusion examples. + #[rstest::rstest] + #[case::bare_cd("CD", true)] + #[case::llc_before_closing_group("LLC)", true)] + #[case::llc_before_korean_code_span("LLC회사", true)] + #[case::cd_before_digit("CD47", false)] + #[case::cd_before_slash("CD/ATM", false)] + #[case::neither_s_before_plus("NEIS+", false)] + #[case::little_m_before_opening_group("LLM(SLM)", false)] + fn uppercase_passage_grade1_respects_letters_sequence_boundary( + #[case] input: &str, + #[case] expected: bool, + ) { let r = UppercasePassageRule; let mut state = EncoderState::new(false); state.english_indicator = true; state.is_english = false; - let tokens = vec![ - word("CD"), - Token::Space(SpaceKind::Regular), - word("ABC"), - Token::Space(SpaceKind::Regular), - word("DEF"), - ]; + let tokens = vec![word(input)]; let action = r.apply(&tokens, 0, &mut state).unwrap(); let found = matches!(action, TokenAction::ReplaceMany(ref ts) if ts.iter().any(|t| matches!(t, Token::Mode(ModeEvent::Grade1Indicator)))); - assert!(found, "expected Grade1Indicator Mode token"); + assert_eq!(found, expected); + } + + #[rstest::rstest] + #[case::pdf_gigabyte("GB")] + #[case::petabyte("PB")] + #[case::terabyte("TB")] + fn separated_uppercase_units_do_not_preemit_ueb_modes(#[case] unit: &str) { + let r = UppercasePassageRule; + let mut state = EncoderState::new(false); + state.english_indicator = true; + let tokens = vec![word("5"), Token::Space(SpaceKind::Regular), word(unit)]; + + assert!(matches!( + r.apply(&tokens, 2, &mut state).unwrap(), + TokenAction::Noop + )); + } + + /// UEB 8.5.2-8.5.3: three or more capitalised symbols-sequences use one + /// passage indicator, and the terminator immediately follows the final + /// affected sequence. These are official 2024 UEB examples with Korean + /// boundary punctuation/glosses attached to exercise the mixed-script + /// tokenisation used by `DocumentIR`. + #[rstest::rstest] + #[case::caution_with_quote( + &["‘CAUTION:", "WET", "PAINT!’이다."], + &["‘", "CAUTION:"], + &["PAINT!", "’이다."] + )] + #[case::bbc_news_with_quote( + &["“THE", "BBC", "AFRICA", "NEWS”이다."], + &["“", "THE"], + &["NEWS", "”이다."] + )] + #[case::self_made_man_with_gloss( + &["A", "SELF-MADE", "MAN(남자)이다."], + &["A"], + &["MAN", "(남자)이다."] + )] + fn capitalized_passage_respects_attached_mixed_script_boundaries( + #[case] words: &[&str], + #[case] expected_start_words: &[&str], + #[case] expected_end_words: &[&str], + ) { + let rule = UppercasePassageRule; + let tokens = spaced_words(words); + let mut state = EncoderState::new(false); + state.english_indicator = true; + + let TokenAction::ReplaceMany(start) = rule + .apply(&tokens, 0, &mut state) + .expect("official capitalised passage must start") + else { + panic!("expected a passage-start replacement"); + }; + assert_eq!(replacement_words(&start), expected_start_words); + assert_eq!( + start + .iter() + .filter(|token| matches!(token, Token::Mode(ModeEvent::CapsPassageStart))) + .count(), + 1 + ); + assert!( + !start + .iter() + .any(|token| matches!(token, Token::Mode(ModeEvent::CapsWord))) + ); + assert!(state.triple_big_english); + + let last_index = tokens.len() - 1; + let TokenAction::ReplaceMany(end) = rule + .apply(&tokens, last_index, &mut state) + .expect("official capitalised passage must terminate") + else { + panic!("expected a passage-end replacement"); + }; + assert_eq!(replacement_words(&end), expected_end_words); + assert!(matches!( + end.get(1), + Some(Token::Mode(ModeEvent::CapsPassageEnd)) + )); + assert!(!state.triple_big_english); + } + + /// 수학 제12항 [붙임 1]: 국어 문장 안에서 콤마로 나열한 단일 대문자 + /// 변수는 UEB 대문자 구절이 아니라 각각의 로마자 항목으로 유지한다. + #[test] + fn korean_math_letter_list_does_not_start_capitals_passage() { + let ir = crate::rules::token::DocumentIR::parse("세 점 A, B, C가 있다.", true); + let index = ir + .tokens + .iter() + .position(|token| matches!(token, Token::Word(word) if word.text == "A,")) + .expect("official example must contain its first Roman variable"); + let mut state = EncoderState::new(true); + + assert!(matches!( + UppercasePassageRule + .apply(&ir.tokens, index, &mut state) + .expect("letter list classification must succeed"), + TokenAction::Noop + )); + assert!(!state.triple_big_english); } } diff --git a/libs/braillify/src/symbol_shortcut.rs b/libs/braillify/src/symbol_shortcut.rs index 878ff291..e5d42f53 100644 --- a/libs/braillify/src/symbol_shortcut.rs +++ b/libs/braillify/src/symbol_shortcut.rs @@ -13,6 +13,9 @@ static SHORTCUT_MAP: phf::Map = phf_map! { '\u{F000}' => &[decode_unicode('⠸'), decode_unicode('⠦'), decode_unicode('⠦'), decode_unicode('⠄')], '…' => &[decode_unicode('⠠'), decode_unicode('⠠'), decode_unicode('⠠')], '⋯' => &[decode_unicode('⠠'), decode_unicode('⠠'), decode_unicode('⠠')], + // 제53항 [다만] — 점 개수를 밝혀야 하는 줄임표는 묵자의 점 수만큼 + // ⠠을 적는다. U+2025 TWO DOT LEADER visibly carries two points. + '‥' => &[decode_unicode('⠠'), decode_unicode('⠠')], '!' => &[decode_unicode('⠖')], '.' => &[decode_unicode('⠲')], ',' => &[decode_unicode('⠐')], @@ -69,6 +72,9 @@ static SHORTCUT_MAP: phf::Map = phf_map! { '□' => &[decode_unicode('⠸'),decode_unicode('⠶'), decode_unicode('⠇')], '•' => &[decode_unicode('⠸'),decode_unicode('⠲')], 'ː' => &[decode_unicode('⠠'), decode_unicode('⠄')], + // 국제음성기호 제2장 — U+02D1 MODIFIER LETTER HALF TRIANGULAR COLON, + // 반장음 부호. IPA 문맥 밖에서도 이 Unicode scalar is unambiguous. + 'ˑ' => &[decode_unicode('⠐'), decode_unicode('⠂')], '〃' => &[decode_unicode('⠴'), decode_unicode('⠴')], // PDF 제60항 [붙임 1] — 참조 기호 ※ (U+203B). '※' => &[decode_unicode('⠸'), decode_unicode('⠔')], @@ -81,7 +87,7 @@ static SHORTCUT_MAP: phf::Map = phf_map! { /// gates *which* symbols are English-eligible and does NOT duplicate the point /// shapes. (Whether a given `:`/`,` is actually rendered English in 제39항 영-한 /// wrap context is decided by `english_logic::should_render_symbol_as_english`.) -const ENGLISH_SYMBOL_CHARS: [char; 5] = ['(', ')', ',', '-', ':']; +const ENGLISH_SYMBOL_CHARS: [char; 6] = ['(', ')', ',', '-', ':', '…']; pub fn encode_char_symbol_shortcut(text: char) -> Result<&'static [u8], String> { if let Some(code) = SHORTCUT_MAP.get(&text) { @@ -128,6 +134,7 @@ mod test { #[case('\'')] #[case('~')] #[case('…')] + #[case('‥')] #[case('!')] #[case('.')] #[case(',')] @@ -143,10 +150,20 @@ mod test { #[case('①')] #[case('ⓐ')] #[case('₩')] + #[case('ˑ')] pub fn test_is_symbol_char(#[case] ch: char) { assert!(is_symbol_char(ch)); } + #[rstest::rstest] + #[case::two_dot_leader('‥', "⠠⠠")] + #[case::ipa_half_length('ˑ', "⠐⠂")] + fn extended_standard_marks_have_pdf_defined_cells(#[case] input: char, #[case] expected: &str) { + let actual = encode_char_symbol_shortcut(input).unwrap(); + let expected = expected.chars().map(decode_unicode).collect::>(); + assert_eq!(actual, expected); + } + #[rstest::rstest] #[case::enclosed_jamo('㉠')] #[case::currency_dollar('$')] diff --git a/libs/braillify/tests/snapshots/coverage_extra__korean_math_squared.snap b/libs/braillify/tests/snapshots/coverage_extra__korean_math_squared.snap index beda7e13..6c600545 100644 --- a/libs/braillify/tests/snapshots/coverage_extra__korean_math_squared.snap +++ b/libs/braillify/tests/snapshots/coverage_extra__korean_math_squared.snap @@ -3,5 +3,5 @@ source: libs/braillify/tests/coverage_extra.rs expression: rendered --- input = "변수 a^2 와 b^2" -unicode = ok: "⠘⠡⠠⠍⠀⠴⠁⠲⠈⠢⠼⠃⠀⠸⠷⠧⠸⠾⠀⠃⠲⠈⠢⠼⠃" -bytes = ok: [24, 33, 32, 13, 0, 52, 1, 50, 8, 34, 60, 3, 0, 56, 55, 39, 56, 62, 0, 3, 50, 8, 34, 60, 3] +unicode = ok: "⠘⠡⠠⠍⠀⠴⠁⠲⠈⠢⠼⠃⠀⠧⠀⠴⠃⠲⠈⠢⠼⠃" +bytes = ok: [24, 33, 32, 13, 0, 52, 1, 50, 8, 34, 60, 3, 0, 39, 0, 52, 3, 50, 8, 34, 60, 3] diff --git a/libs/braillify/tests/snapshots/coverage_extra__negation_ab.snap b/libs/braillify/tests/snapshots/coverage_extra__negation_ab.snap index d9be7b62..e3c57e10 100644 --- a/libs/braillify/tests/snapshots/coverage_extra__negation_ab.snap +++ b/libs/braillify/tests/snapshots/coverage_extra__negation_ab.snap @@ -3,5 +3,5 @@ source: libs/braillify/tests/coverage_extra.rs expression: rendered --- input = "A¬B" -unicode = ok: "⠁⠨⠃" -bytes = ok: [1, 40, 3] +unicode = ok: "⠁⠈⠔⠃" +bytes = ok: [1, 8, 20, 3] diff --git a/libs/braillify/tests/snapshots/coverage_extra__negation_pq.snap b/libs/braillify/tests/snapshots/coverage_extra__negation_pq.snap index fb666244..e5a02d93 100644 --- a/libs/braillify/tests/snapshots/coverage_extra__negation_pq.snap +++ b/libs/braillify/tests/snapshots/coverage_extra__negation_pq.snap @@ -3,5 +3,5 @@ source: libs/braillify/tests/coverage_extra.rs expression: rendered --- input = "p¬Q" -unicode = ok: "⠏⠨⠟" -bytes = ok: [15, 40, 31] +unicode = ok: "⠏⠈⠔⠟" +bytes = ok: [15, 8, 20, 31] diff --git a/libs/braillify/tests/snapshots/coverage_extra__negation_xy.snap b/libs/braillify/tests/snapshots/coverage_extra__negation_xy.snap index 4efee896..5069c662 100644 --- a/libs/braillify/tests/snapshots/coverage_extra__negation_xy.snap +++ b/libs/braillify/tests/snapshots/coverage_extra__negation_xy.snap @@ -3,5 +3,5 @@ source: libs/braillify/tests/coverage_extra.rs expression: rendered --- input = "X¬Y" -unicode = ok: "⠭⠨⠽" -bytes = ok: [45, 40, 61] +unicode = ok: "⠭⠈⠔⠽" +bytes = ok: [45, 8, 20, 61] diff --git a/libs/braillify/tests/snapshots2/coverage_extra2__eng_dom_sentence_period.snap b/libs/braillify/tests/snapshots2/coverage_extra2__eng_dom_sentence_period.snap index e9d4fb9c..d2ecbc15 100644 --- a/libs/braillify/tests/snapshots2/coverage_extra2__eng_dom_sentence_period.snap +++ b/libs/braillify/tests/snapshots2/coverage_extra2__eng_dom_sentence_period.snap @@ -4,5 +4,5 @@ assertion_line: 304 expression: rendered --- input = "The quick brown fox. 매우 빠르다." -unicode = ok: "⠴⠠⠮⠀⠟⠥⠊⠉⠅⠀⠃⠗⠪⠝⠀⠋⠕⠭⠲⠀⠑⠗⠍⠀⠠⠘⠐⠪⠊⠲" -bytes = ok: [52, 32, 46, 0, 31, 37, 10, 9, 5, 0, 3, 23, 42, 29, 0, 11, 21, 45, 50, 0, 17, 23, 13, 0, 32, 24, 16, 42, 10, 50] +unicode = ok: "⠴⠠⠮⠀⠟⠅⠀⠃⠗⠪⠝⠀⠋⠕⠭⠲⠀⠑⠗⠍⠀⠠⠘⠐⠪⠊⠲" +bytes = ok: [52, 32, 46, 0, 31, 5, 0, 3, 23, 42, 29, 0, 11, 21, 45, 50, 0, 17, 23, 13, 0, 32, 24, 16, 42, 10, 50] diff --git a/libs/braillify/tests/snapshots2/coverage_extra2__neg_upper_upper.snap b/libs/braillify/tests/snapshots2/coverage_extra2__neg_upper_upper.snap index 100631ef..f3cc1dd1 100644 --- a/libs/braillify/tests/snapshots2/coverage_extra2__neg_upper_upper.snap +++ b/libs/braillify/tests/snapshots2/coverage_extra2__neg_upper_upper.snap @@ -3,5 +3,5 @@ source: libs/braillify/tests/coverage_extra2.rs expression: rendered --- input = "A¬B" -unicode = ok: "⠁⠨⠃" -bytes = ok: [1, 40, 3] +unicode = ok: "⠁⠈⠔⠃" +bytes = ok: [1, 8, 20, 3] diff --git a/libs/braillify/tests/snapshots2/coverage_extra2__neg_var_upper.snap b/libs/braillify/tests/snapshots2/coverage_extra2__neg_var_upper.snap index 13734add..48471590 100644 --- a/libs/braillify/tests/snapshots2/coverage_extra2__neg_var_upper.snap +++ b/libs/braillify/tests/snapshots2/coverage_extra2__neg_var_upper.snap @@ -3,5 +3,5 @@ source: libs/braillify/tests/coverage_extra2.rs expression: rendered --- input = "x¬B" -unicode = ok: "⠭⠨⠃" -bytes = ok: [45, 40, 3] +unicode = ok: "⠭⠈⠔⠃" +bytes = ok: [45, 8, 20, 3] diff --git a/uv.lock b/uv.lock index 7abe1eee..a27ec581 100644 --- a/uv.lock +++ b/uv.lock @@ -11,7 +11,7 @@ members = [ [[package]] name = "braillify" -version = "2.1.0" +version = "2.1.2" source = { editable = "packages/python" } [[package]] From 226e1214c3145ba0452b7c3d45dec6691dc0d8e3 Mon Sep 17 00:00:00 2001 From: devfive Date: Thu, 3 Sep 2026 19:25:10 +0900 Subject: [PATCH 75/77] Cover remaining Braillify rule branches --- .../braillify/examples/nikl_corpus_analyze.rs | 22 +- libs/braillify/src/rules/emit.rs | 211 +++++++++++++++++- .../braillify/src/rules/english_ueb/engine.rs | 24 ++ .../src/rules/english_ueb/engine/tokens.rs | 27 ++- .../rules/english_ueb/engine/word_methods.rs | 24 ++ .../english_ueb/pronunciation/cmudict.rs | 62 +++-- .../rules/english_ueb/pronunciation/mod.rs | 3 + .../src/rules/english_ueb/rule_10_9.rs | 11 +- libs/braillify/src/rules/korean/rule_27.rs | 8 + libs/braillify/src/rules/korean/rule_69.rs | 38 +++- libs/braillify/src/rules/korean/rule_72.rs | 19 ++ .../src/rules/korean/rule_english_symbol.rs | 5 + libs/braillify/src/rules/korean/rule_math.rs | 19 ++ .../token_rules/math_expression/apply.rs | 58 +++++ .../token_rules/math_expression/helpers.rs | 17 ++ .../rules/token_rules/middle_dot_spacing.rs | 16 ++ .../src/rules/token_rules/normalize.rs | 15 ++ .../rules/token_rules/uppercase_passage.rs | 31 +++ 18 files changed, 534 insertions(+), 76 deletions(-) diff --git a/libs/braillify/examples/nikl_corpus_analyze.rs b/libs/braillify/examples/nikl_corpus_analyze.rs index 4277ccd6..db1becb8 100644 --- a/libs/braillify/examples/nikl_corpus_analyze.rs +++ b/libs/braillify/examples/nikl_corpus_analyze.rs @@ -3961,12 +3961,14 @@ fn has_attached_korean_auxiliary_itda(input: &str) -> bool { false } -fn enum_key(value: &T) -> String { - serde_json::to_value(value) - .expect("enum serialization must succeed") - .as_str() - .expect("enum must serialize as a string") - .to_string() +macro_rules! enum_key { + ($value:expr) => {{ + serde_json::to_value($value) + .expect("enum serialization must succeed") + .as_str() + .expect("enum must serialize as a string") + .to_string() + }}; } fn excerpt_pair(expected: &str, actual: &str) -> (String, String) { @@ -4393,8 +4395,8 @@ fn analyze( }); } } - let primary_key = enum_key(&primary); - let reason_key = enum_key(&reason); + let primary_key = enum_key!(&primary); + let reason_key = enum_key!(&reason); *primary_classes.entry(primary_key.clone()).or_insert(0) += 1; *reasons.entry(reason_key.clone()).or_insert(0) += 1; @@ -7757,9 +7759,9 @@ mod tests { #[test] fn serializes_report_enum_keys_as_snake_case_strings() { - assert_eq!(enum_key(&PrimaryClass::Exact), "exact"); + assert_eq!(enum_key!(&PrimaryClass::Exact), "exact"); assert_eq!( - enum_key(&Reason::UnsupportedCharacterReview), + enum_key!(&Reason::UnsupportedCharacterReview), "unsupported_character_review" ); } diff --git a/libs/braillify/src/rules/emit.rs b/libs/braillify/src/rules/emit.rs index 306dde1b..ce375827 100644 --- a/libs/braillify/src/rules/emit.rs +++ b/libs/braillify/src/rules/emit.rs @@ -103,12 +103,12 @@ fn roman_section_has_english_phrase_context(tokens: &[Token<'_>], start_index: u .position(|ch| crate::utils::is_korean_char(*ch)) .map_or(word.chars.len(), |offset| scan_start + offset); let roman_slice = &word.chars[scan_start..section_end]; - let Some(last_roman) = roman_slice.iter().rposition(|ch| ch.is_ascii_alphabetic()) else { - if section_end < word.chars.len() { - break; - } - continue; - }; + // `scan_start` is selected from an ASCII alphabetic position above, so + // this slice necessarily contains at least that Roman letter. + let last_roman = roman_slice + .iter() + .rposition(|ch| ch.is_ascii_alphabetic()) + .expect("Roman section starts at an ASCII alphabetic character"); started = true; roman_word_count += 1; @@ -746,10 +746,9 @@ fn separated_symbol_continues_roman_section(tokens: &[Token<'_>], token_index: u continue; } if ch == closing { - let Some(next_depth) = depth.checked_sub(1) else { - return false; - }; - depth = next_depth; + // The first scanned character is the matching opener, + // and the function returns as soon as that level closes. + depth -= 1; if depth == 0 { return saw_roman_or_number && !saw_korean; } @@ -1318,6 +1317,15 @@ mod tests { engine } + fn word_token(text: &'static str) -> Token<'static> { + let chars = text.chars().collect::>(); + Token::Word(WordToken { + text: Cow::Borrowed(text), + chars: chars.clone(), + meta: super::super::token::WordMeta::from_chars(&chars), + }) + } + /// Helper: round-trip test via emit(parse(text)) == encode(text) fn assert_round_trip(text: &str) { let mut ir = DocumentIR::parse(text, english_indicator(text)); @@ -1343,6 +1351,9 @@ mod tests { #[case::lowercase_enclosed("제목(plain words in context)이다.", "plain", true)] #[case::rule_37_metalinguistic_list("be, his, was, were의 약자를 바르게 쓰시오.", "be,", false)] #[case::single_roman_annotation("논문(Cell)이 발표됐다.", "논문(Cell)이", false)] + #[case::numeric_word_before_phrase("123 Alpha Beta", "123", true)] + #[case::numeric_word_inside_phrase("Alpha 123 Beta", "Alpha", true)] + #[case::korean_word_ends_phrase("Alpha 한국 Beta", "Alpha", false)] fn recognizes_structural_english_phrase_context( #[case] input: &str, #[case] first_roman_word: &str, @@ -1363,6 +1374,186 @@ mod tests { ); } + #[test] + fn english_phrase_scan_stops_at_non_text_after_starting() { + let tokens = vec![word_token("Alpha"), Token::PreEncoded(vec![1])]; + + assert!(!roman_section_has_english_phrase_context(&tokens, 0)); + } + + #[test] + fn current_word_lookup_handles_hard_boundaries_and_end_of_stream() { + assert!(current_word_at_or_after(&[Token::PreEncoded(vec![1])], 0).is_none()); + assert!(current_word_at_or_after(&[], 0).is_none()); + } + + #[rstest::rstest] + #[case::parenthesis('(', ')')] + #[case::square_bracket('[', ']')] + #[case::curly_brace('{', '}')] + #[case::single_quote('‘', '’')] + #[case::double_quote('“', '”')] + #[case::single_angle('〈', '〉')] + #[case::double_angle('《', '》')] + #[case::corner_bracket('「', '」')] + #[case::white_corner_bracket('『', '』')] + #[case::lenticular_bracket('【', '】')] + #[case::tortoise_shell_bracket('〔', '〕')] + #[case::white_lenticular_bracket('〖', '〗')] + #[case::white_tortoise_shell_bracket('〘', '〙')] + #[case::white_square_bracket('〚', '〛')] + fn enclosure_delimiter_pairs_are_bidirectional(#[case] opening: char, #[case] closing: char) { + assert_eq!(matching_group_open(closing), Some(opening)); + assert_eq!(matching_group_close(opening), Some(closing)); + } + + #[rstest::rstest] + #[case::no_previous_word(None, false)] + #[case::punctuation_only_previous(Some("..."), false)] + #[case::nested_enclosure(Some("((A))"), true)] + #[case::missing_opener(Some("A)"), false)] + fn closed_enclosure_scans_only_a_complete_ascii_group( + #[case] previous: Option<&'static str>, + #[case] expected: bool, + ) { + let tokens = previous.map_or_else( + || vec![Token::Space(SpaceKind::Regular)], + |text| vec![word_token(text)], + ); + + assert_eq!( + closed_enclosure_before_contains_ascii(&tokens, tokens.len()), + expected + ); + } + + #[test] + fn new_section_probe_handles_a_mode_prefix_without_a_current_word() { + let tokens = vec![ + word_token("(A)"), + Token::Space(SpaceKind::Regular), + Token::Mode(ModeEvent::CapsWord), + ]; + + assert!(!starts_new_roman_section_after_closed_enclosure(&tokens, 2)); + } + + #[test] + fn spaced_colon_rejects_non_words_and_non_textual_right_boundaries() { + assert!(!spaced_colon_connects_roman_items( + &[Token::Space(SpaceKind::Regular)], + 0 + )); + let tokens = vec![ + word_token(":"), + Token::Space(SpaceKind::Regular), + Token::PreEncoded(vec![1]), + ]; + assert!(!spaced_colon_connects_roman_items(&tokens, 0)); + } + + #[rstest::rstest] + #[case::non_word_at_index("non_word")] + #[case::non_textual_left_boundary("non_text_left")] + #[case::non_roman_left_word("korean_left")] + #[case::malformed_attached_suffix("long_ampersand")] + #[case::non_textual_right_boundary("non_text_right")] + fn spaced_ampersand_rejects_incomplete_roman_neighbors(#[case] scenario: &str) { + let (tokens, index) = match scenario { + "non_word" => (vec![Token::Space(SpaceKind::Regular)], 0), + "non_text_left" => ( + vec![ + Token::PreEncoded(vec![1]), + Token::Space(SpaceKind::Regular), + word_token("&"), + ], + 2, + ), + "korean_left" => ( + vec![ + word_token("한국"), + Token::Space(SpaceKind::Regular), + word_token("&"), + ], + 2, + ), + "long_ampersand" => ( + vec![ + word_token("Alpha"), + Token::Space(SpaceKind::Regular), + word_token("&?"), + ], + 2, + ), + "non_text_right" => ( + vec![ + word_token("Alpha"), + Token::Space(SpaceKind::Regular), + word_token("&"), + Token::Space(SpaceKind::Regular), + Token::PreEncoded(vec![1]), + ], + 2, + ), + _ => unreachable!("unknown fixture"), + }; + + assert!(!spaced_ampersand_connects_roman_words(&tokens, index)); + } + + #[rstest::rstest] + #[case::no_following_word("no_next", false)] + #[case::empty_following_word("empty", false)] + #[case::nested_complete_group("nested", true)] + #[case::non_textual_group_body("non_text", false)] + #[case::unclosed_group("unclosed", false)] + fn separated_symbol_requires_a_complete_roman_group( + #[case] scenario: &str, + #[case] expected: bool, + ) { + let tokens = match scenario { + "no_next" => vec![word_token("Alpha")], + "empty" => vec![ + word_token("Alpha"), + Token::Space(SpaceKind::Regular), + word_token(""), + ], + "nested" => vec![ + word_token("Alpha"), + Token::Space(SpaceKind::Regular), + word_token("((Beta))"), + ], + "non_text" => vec![ + word_token("Alpha"), + Token::Space(SpaceKind::Regular), + word_token("("), + Token::PreEncoded(vec![1]), + ], + "unclosed" => vec![ + word_token("Alpha"), + Token::Space(SpaceKind::Regular), + word_token("(Beta"), + ], + _ => unreachable!("unknown fixture"), + }; + + assert_eq!( + separated_symbol_continues_roman_section(&tokens, 0), + expected + ); + } + + #[test] + fn slash_forces_a_terminator_before_leaving_roman_mode() { + let mut ir = DocumentIR::parse("ABC/한글", true); + let mut engine = make_char_engine(); + + let output = emit(&mut ir, &mut engine).expect("mixed Roman/Korean word must encode"); + + assert!(output.contains(&crate::unicode::decode_unicode('⠲'))); + assert!(!ir.state.is_english); + } + // ── Step 1-3: Basic token tests ── /// `emit` 결과가 `encode()` 와 byte-identical 한지 (round-trip) 다양한 diff --git a/libs/braillify/src/rules/english_ueb/engine.rs b/libs/braillify/src/rules/english_ueb/engine.rs index f7602109..c58c6ceb 100644 --- a/libs/braillify/src/rules/english_ueb/engine.rs +++ b/libs/braillify/src/rules/english_ueb/engine.rs @@ -982,6 +982,30 @@ mod test_support { assert_eq!(encoded, cells(expected)); } + #[rstest::rstest] + #[case::mixed_capitalization(false, "⠁⠠⠃")] + #[case::caps_suppressed(true, "⠁⠃")] + fn numeric_grade1_mode_spells_mixed_case_letters( + #[case] suppress_caps: bool, + #[case] expected: &str, + ) { + let chars = "aB".chars().collect::>(); + let encoded = EnglishUebEngine::new() + .encode_korean_word( + &chars, + suppress_caps, + false, + false, + false, + true, + true, + false, + ) + .expect("mixed-case numeric continuation must encode"); + + assert_eq!(encoded, cells(expected)); + } + pub(super) fn enc(text: &str) -> Option> { super::super::try_encode(text) } diff --git a/libs/braillify/src/rules/english_ueb/engine/tokens.rs b/libs/braillify/src/rules/english_ueb/engine/tokens.rs index 5fe6e059..41182cb5 100644 --- a/libs/braillify/src/rules/english_ueb/engine/tokens.rs +++ b/libs/braillify/src/rules/english_ueb/engine/tokens.rs @@ -153,20 +153,11 @@ pub(super) fn apostrophe_joined_recorded_token_word(tokens: &[EnglishToken], i: return false; } - let mut joined = Vec::new(); - let mut run_start = 0usize; - for (index, token) in tokens.iter().enumerate().take(end + 1).skip(start) { - match token { - EnglishToken::Word(chars) => { - if index == i { - run_start = joined.len(); - } - joined.extend(chars); - } - EnglishToken::Symbol(ch @ ('\'' | '\u{2019}')) => joined.push(*ch), - _ => return false, - } - } + // `start`/`end` are grown only across alternating Word/Apostrophe tokens. + // Reuse the exhaustive token flattener instead of repeating an unreachable + // defensive match for the already-proven slice shape. + let run_start = token_plain_chars(&tokens[start..i]).len(); + let joined = token_plain_chars(&tokens[start..=end]); let run_end = run_start + current.len(); super::super::pronunciation::apostrophe_elided_recorded_word_at(&joined, run_start, run_end) } @@ -980,6 +971,14 @@ mod tests { assert_eq!(styled_column_gap(&tokens, 0), None); } + #[test] + fn apostrophe_lexeme_lookup_requires_a_word_at_the_requested_index() { + assert!(!apostrophe_joined_recorded_token_word( + &[EnglishToken::Space], + 0 + )); + } + #[test] fn push_spatial_char_renders_line_arrow() { // §16 spatial mode: a line arrow (`→`) renders via its two-cell arrow sign. diff --git a/libs/braillify/src/rules/english_ueb/engine/word_methods.rs b/libs/braillify/src/rules/english_ueb/engine/word_methods.rs index 6c8b899a..703aba35 100644 --- a/libs/braillify/src/rules/english_ueb/engine/word_methods.rs +++ b/libs/braillify/src/rules/english_ueb/engine/word_methods.rs @@ -961,4 +961,28 @@ mod tests { ); assert!(!out.is_empty()); } + + #[test] + fn standing_all_caps_longer_shortform_collision_gets_grade1() { + // UEB 5.7.2/10.9.8: LLC begins with the `little` shortform cells but is + // not itself a complete pure-letter shortform abbreviation. + let ctx = WordContext { + standing_alone: true, + upper_usable: true, + shortform_usable: true, + allow_longer_shortforms: true, + lower_usable: true, + suppress_caps: false, + word_initial: true, + restricted_prefix_boundary: true, + digit_adjacent: false, + }; + let mut out = Vec::new(); + + EnglishUebEngine::new() + .encode_word(&['L', 'L', 'C'], ctx, &mut out) + .expect("ASCII acronym must encode"); + + assert!(out.starts_with(&[GRADE1, CAPITAL, CAPITAL])); + } } diff --git a/libs/braillify/src/rules/english_ueb/pronunciation/cmudict.rs b/libs/braillify/src/rules/english_ueb/pronunciation/cmudict.rs index 791652fc..398f95ec 100644 --- a/libs/braillify/src/rules/english_ueb/pronunciation/cmudict.rs +++ b/libs/braillify/src/rules/english_ueb/pronunciation/cmudict.rs @@ -64,41 +64,39 @@ pub fn has_unambiguous_letter_name_pronunciation(chars: &[char]) -> bool { return false; } - fn letter_phones(letter: char) -> Option<&'static [&'static str]> { - Some(match letter { - 'A' => &["EY"], - 'B' => &["B", "IY"], - 'C' => &["S", "IY"], - 'D' => &["D", "IY"], - 'E' => &["IY"], - 'F' => &["EH", "F"], - 'G' => &["JH", "IY"], - 'H' => &["EY", "CH"], - 'I' => &["AY"], - 'J' => &["JH", "EY"], - 'K' => &["K", "EY"], - 'L' => &["EH", "L"], - 'M' => &["EH", "M"], - 'N' => &["EH", "N"], - 'O' => &["OW"], - 'P' => &["P", "IY"], - 'Q' => &["K", "Y", "UW"], - 'R' => &["AA", "R"], - 'S' => &["EH", "S"], - 'T' => &["T", "IY"], - 'U' => &["Y", "UW"], - 'V' => &["V", "IY"], - 'W' => &["D", "AH", "B", "AH", "L", "Y", "UW"], - 'X' => &["EH", "K", "S"], - 'Y' => &["W", "AY"], - 'Z' => &["Z", "IY"], - _ => return None, - }) - } + const LETTER_PHONES: &[&[&str]; 26] = &[ + &["EY"], + &["B", "IY"], + &["S", "IY"], + &["D", "IY"], + &["IY"], + &["EH", "F"], + &["JH", "IY"], + &["EY", "CH"], + &["AY"], + &["JH", "EY"], + &["K", "EY"], + &["EH", "L"], + &["EH", "M"], + &["EH", "N"], + &["OW"], + &["P", "IY"], + &["K", "Y", "UW"], + &["AA", "R"], + &["EH", "S"], + &["T", "IY"], + &["Y", "UW"], + &["V", "IY"], + &["D", "AH", "B", "AH", "L", "Y", "UW"], + &["EH", "K", "S"], + &["W", "AY"], + &["Z", "IY"], + ]; let expected: Vec<&str> = chars .iter() - .flat_map(|letter| letter_phones(*letter).unwrap_or_default()) + // The guard above proves every character is in `A..=Z`. + .flat_map(|letter| LETTER_PHONES[*letter as usize - 'A' as usize]) .copied() .collect(); let key: String = chars.iter().map(|ch| ch.to_ascii_lowercase()).collect(); diff --git a/libs/braillify/src/rules/english_ueb/pronunciation/mod.rs b/libs/braillify/src/rules/english_ueb/pronunciation/mod.rs index 6cf301bf..84a5e0ed 100644 --- a/libs/braillify/src/rules/english_ueb/pronunciation/mod.rs +++ b/libs/braillify/src/rules/english_ueb/pronunciation/mod.rs @@ -167,6 +167,9 @@ mod tests { #[case::curly_apostrophe("O\u{2019}PENing", 2, 8, true)] #[case::no_join("PENing", 0, 6, false)] #[case::unknown_elision("rock'n", 5, 6, false)] + #[case::empty_requested_run("abc", 1, 1, false)] + #[case::run_end_out_of_bounds("abc", 0, 4, false)] + #[case::requested_run_contains_nonletter("a1c", 0, 3, false)] fn classifies_apostrophe_elided_lexical_words( #[case] text: &str, #[case] run_start: usize, diff --git a/libs/braillify/src/rules/english_ueb/rule_10_9.rs b/libs/braillify/src/rules/english_ueb/rule_10_9.rs index 88cbca3c..f4dc14b2 100644 --- a/libs/braillify/src/rules/english_ueb/rule_10_9.rs +++ b/libs/braillify/src/rules/english_ueb/rule_10_9.rs @@ -47,9 +47,7 @@ pub fn is_pure_shortform_abbreviation(word: &str) -> bool { if letters.len() < 2 || !letters.iter().all(char::is_ascii_lowercase) { return false; } - let Some(literal_cells) = korean_letter_sequence_cells(&letters) else { - return false; - }; + let literal_cells = korean_letter_sequence_cells(&letters); SHORTFORMS .values() @@ -76,9 +74,7 @@ pub fn requires_grade1_at_word_start(letters: &str) -> bool { } for end in 2..=chars.len() { - let Some(prefix_cells) = korean_letter_sequence_cells(&chars[..end]) else { - continue; - }; + let prefix_cells = korean_letter_sequence_cells(&chars[..end]); for (shortform, notation) in SHORTFORMS.entries() { if notation_cells(notation).as_deref() != Some(prefix_cells.as_slice()) { continue; @@ -111,7 +107,7 @@ pub fn requires_grade1_at_word_start(letters: &str) -> bool { /// this exact path: a default [`ContractionEngine`] contains no registered rules /// and would consequently miss cell-equivalent sequences such as `fst` (`f` + /// the `st` groupsign) and `shd` (the `sh` groupsign + `d`). -fn korean_letter_sequence_cells(letters: &[char]) -> Option> { +fn korean_letter_sequence_cells(letters: &[char]) -> Vec { super::span::encode_korean_word( letters, true, // capitalization indicators are compared separately false, // do not recursively prepend grade 1 @@ -121,6 +117,7 @@ fn korean_letter_sequence_cells(letters: &[char]) -> Option> { false, // no numeric grade-1 mode in a pure letters-sequence false, // not split by an apostrophe ) + .expect("a lowercase ASCII letters-sequence must be encodable") } /// Encode a word as the §10.10.2 cell-minimising contraction sequence. diff --git a/libs/braillify/src/rules/korean/rule_27.rs b/libs/braillify/src/rules/korean/rule_27.rs index 093696e0..c782ffe4 100644 --- a/libs/braillify/src/rules/korean/rule_27.rs +++ b/libs/braillify/src/rules/korean/rule_27.rs @@ -171,6 +171,14 @@ mod tests { let _ = Rule27.matches(&ctx); } + #[test] + fn geoseong_predicate_rejects_non_symbol_context() { + let mut owned = crate::test_helpers::CtxOwned::for_text("A", false); + let ctx = owned.ctx_at(0); + + assert!(!is_middle_korean_geoseong(&ctx)); + } + /// 제27항 — `has_historical_context` returns true when current word contains /// a hanja character (CJK Unified). Exercises lines 33-35 (own-word branch). #[test] diff --git a/libs/braillify/src/rules/korean/rule_69.rs b/libs/braillify/src/rules/korean/rule_69.rs index 4758eac8..a06432d6 100644 --- a/libs/braillify/src/rules/korean/rule_69.rs +++ b/libs/braillify/src/rules/korean/rule_69.rs @@ -870,9 +870,9 @@ mod tests { encode_numeric_ascii_unit, encode_percent_abbreviation, encode_rule_69_unit_letters, encode_unicode_cells, is_ascii_unit_chain_slash, is_si_prefixed_byte_unit, is_si_prefixed_electrical_hour_unit, is_si_prefixed_litre_unit, - omit_roman_terminator_before_boundary, parse_numeric_ascii_unit_expression, - parse_numeric_ascii_unit_prefix, retain_unambiguous_ascii_unit_spellings, - word_looks_like_unit_chain, + omit_roman_terminator_before_boundary, omit_trailing_roman_terminator, + parse_numeric_ascii_unit_expression, parse_numeric_ascii_unit_prefix, + retain_unambiguous_ascii_unit_spellings, word_looks_like_unit_chain, }; #[rstest::rstest] @@ -1181,6 +1181,7 @@ mod tests { #[case::litre("L", true)] #[case::word_ending_l("model", false)] #[case::invalid_prefix("xL", false)] + #[case::empty_spelling("", false)] fn recognizes_case_preserving_si_litre_symbols(#[case] spelling: &str, #[case] expected: bool) { assert_eq!(is_si_prefixed_litre_unit(spelling), expected); } @@ -1220,6 +1221,7 @@ mod tests { #[case::milliampere_hour("mAh", true)] #[case::decaampere_hour("daAh", true)] #[case::missing_hour("GW", false)] + #[case::invalid_base_before_hour("mh", false)] #[case::unknown_prefix("xWh", false)] #[case::wrong_case("gWh", false)] fn recognizes_si_prefixed_electrical_hour_units( @@ -1229,6 +1231,36 @@ mod tests { assert_eq!(is_si_prefixed_electrical_hour_unit(spelling), expected); } + #[test] + fn trailing_roman_terminator_is_removed_when_section_continues() { + let terminator = crate::unicode::decode_unicode('⠲'); + let mut encoded = vec![1, terminator]; + + omit_trailing_roman_terminator(&mut encoded); + + assert_eq!(encoded, vec![1]); + } + + #[test] + fn continuing_ascii_unit_clears_a_prior_roman_number_chain() { + use crate::rules::traits::BrailleRule; + + let mut owned = crate::test_helpers::CtxOwned::for_text("GB", true) + .with_prev_word("5") + .with_remaining_words(["SSD"]); + owned.state.roman_number_chain = true; + let mut ctx = owned.ctx_at(0); + + let outcome = Rule69.apply(&mut ctx).expect("Rule 69 unit must encode"); + + assert!(matches!( + outcome, + crate::rules::traits::RuleResult::Consumed + )); + assert!(!ctx.state.roman_number_chain); + assert!(ctx.state.is_english); + } + /// Rule 69 and its science-braille unit table: a complete Roman-written /// unit is one section, including its ordinary entry/exit indicators. #[rstest::rstest] diff --git a/libs/braillify/src/rules/korean/rule_72.rs b/libs/braillify/src/rules/korean/rule_72.rs index af5361b0..e9318847 100644 --- a/libs/braillify/src/rules/korean/rule_72.rs +++ b/libs/braillify/src/rules/korean/rule_72.rs @@ -296,6 +296,7 @@ mod tests { #[case::primed("△A′B′C′")] #[case::congruent("△ABC≡△DEF")] #[case::similar_primed("△ABC∽△A′B′C′")] + #[case::trailing_punctuation("△ABC.")] fn recognizes_math_triangle_grammar(#[case] input: &str) { let chars = input.chars().collect::>(); assert!(is_triangle_geometry_expression(&chars)); @@ -305,11 +306,29 @@ mod tests { #[case::acronym_with_gloss("△UAM(도심항공교통)")] #[case::brand_with_digits("△G3930P")] #[case::numeric_item("△2025")] + #[case::incomplete_second_triangle("△ABC=△AB")] fn attached_list_items_do_not_match_triangle_geometry(#[case] input: &str) { let chars = input.chars().collect::>(); assert!(!is_triangle_geometry_expression(&chars)); } + #[test] + fn attached_marker_rule_ignores_an_empty_word_token() { + let tokens = vec![Token::Word(WordToken { + text: Cow::Borrowed(""), + chars: Vec::new(), + meta: WordMeta::from_chars(&[]), + })]; + let mut state = crate::rules::context::EncoderState::new(false); + + assert!(matches!( + Rule72AttachedMarkerTokenRule + .apply(&tokens, 0, &mut state) + .expect("empty input is a no-op"), + TokenAction::Noop + )); + } + #[test] fn detects_double_circle_placeholder_symbol() { assert!(is_rule_72_symbol('◎')); diff --git a/libs/braillify/src/rules/korean/rule_english_symbol.rs b/libs/braillify/src/rules/korean/rule_english_symbol.rs index 32a65294..5c9df60d 100644 --- a/libs/braillify/src/rules/korean/rule_english_symbol.rs +++ b/libs/braillify/src/rules/korean/rule_english_symbol.rs @@ -220,6 +220,11 @@ mod tests { ); } + #[test] + fn hyphen_suffix_lookup_rejects_an_out_of_bounds_index() { + assert!(!hyphen_suffix_requires_grade1(&['A'], 1)); + } + /// Korean rules 28/29/32: the Roman indicator establishes the first /// one-letter segment, and a multi-letter segment after the hyphen starts /// directly with its UEB letters. In particular, no continuation/grade-1 diff --git a/libs/braillify/src/rules/korean/rule_math.rs b/libs/braillify/src/rules/korean/rule_math.rs index 8a1ee0d8..1e593c7c 100644 --- a/libs/braillify/src/rules/korean/rule_math.rs +++ b/libs/braillify/src/rules/korean/rule_math.rs @@ -339,6 +339,24 @@ impl BrailleRule for RuleMath { mod tests { use super::*; + #[rstest::rstest] + #[case::parenthesis('(', ')')] + #[case::square_bracket('[', ']')] + #[case::curly_brace('{', '}')] + #[case::single_angle('〈', '〉')] + #[case::double_angle('《', '》')] + #[case::corner_bracket('「', '」')] + #[case::white_corner_bracket('『', '』')] + #[case::lenticular_bracket('【', '】')] + #[case::tortoise_shell_bracket('〔', '〕')] + #[case::white_lenticular_bracket('〖', '〗')] + #[case::white_tortoise_shell_bracket('〘', '〙')] + #[case::white_square_bracket('〚', '〛')] + fn delimiter_pairs_are_bidirectional(#[case] opening: char, #[case] closing: char) { + assert_eq!(matching_opening_delimiter(closing), Some(opening)); + assert_eq!(matching_closing_delimiter(opening), Some(closing)); + } + #[test] fn apply_exercise() { let mut owned = crate::test_helpers::CtxOwned::for_text("A", false); @@ -430,6 +448,7 @@ mod tests { #[case::pdf_phone_number("02-799-1000", 2, false)] #[case::identifier_suffix("A-3", 1, false)] #[case::calendar_date("2024-09-03", 4, false)] + #[case::non_hyphen_character("A", 0, false)] fn ascii_hyphen_minus_is_disambiguated_by_syntax( #[case] input: &str, #[case] index: usize, diff --git a/libs/braillify/src/rules/token_rules/math_expression/apply.rs b/libs/braillify/src/rules/token_rules/math_expression/apply.rs index 918d4338..35a1c0cc 100644 --- a/libs/braillify/src/rules/token_rules/math_expression/apply.rs +++ b/libs/braillify/src/rules/token_rules/math_expression/apply.rs @@ -2156,6 +2156,64 @@ mod tests { Token::Space(SpaceKind::Regular) } + #[rstest::rstest] + #[case::empty_segment("ISO//IEC")] + #[case::punctuation_only_segment("ISO/-./IEC")] + fn roman_slash_identifier_rejects_incomplete_segments(#[case] input: &str) { + assert!(!is_korean_prose_roman_slash_identifier( + &input.chars().collect::>() + )); + } + + #[test] + fn single_letter_slash_phrase_requires_letters_in_the_following_word() { + let tokens = vec![word_tok("H/W"), space_tok(), word_tok("((")]; + let chars = "H/W".chars().collect::>(); + + assert!(!is_korean_prose_single_letter_slash_phrase( + &tokens, 0, &chars + )); + } + + #[test] + fn multiword_parenthetical_tail_stops_at_a_non_word_boundary() { + let tokens = vec![ + Token::PreEncoded(vec![1]), + space_tok(), + word_tok("Alliance)"), + ]; + let Token::Word(tail) = &tokens[2] else { + unreachable!("fixture ends in a word") + }; + + assert!(!is_multiword_closed_roman_parenthetical_tail( + &tokens, 2, tail + )); + } + + #[test] + fn multiword_parenthetical_head_stops_at_a_non_word_boundary() { + let tokens = vec![ + word_tok("HCA(Home"), + space_tok(), + Token::PreEncoded(vec![1]), + ]; + let Token::Word(head) = &tokens[0] else { + unreachable!("fixture begins with a word") + }; + + assert!(!is_multiword_closed_roman_parenthetical_head( + &tokens, 0, head + )); + } + + #[test] + fn attached_prose_parenthetical_rejects_a_preencoded_body() { + let tokens = vec![word_tok("한국("), Token::PreEncoded(vec![1]), word_tok(")")]; + + assert!(!is_within_attached_korean_prose_parenthetical(&tokens, 1)); + } + #[rstest::rstest] #[case::compact_unit("50bp", true)] #[case::decimal_prefix("3.1p", true)] diff --git a/libs/braillify/src/rules/token_rules/math_expression/helpers.rs b/libs/braillify/src/rules/token_rules/math_expression/helpers.rs index b4576b7b..acfabeed 100644 --- a/libs/braillify/src/rules/token_rules/math_expression/helpers.rs +++ b/libs/braillify/src/rules/token_rules/math_expression/helpers.rs @@ -824,6 +824,23 @@ mod tests { assert!(result.is_some()); } + #[test] + fn mixed_fraction_detects_korean_inside_the_parenthesized_operand() { + let text = "2/(삼+오)"; + let chars = text.chars().collect::>(); + + assert!(is_mixed_math_expression(&chars, text)); + } + + #[test] + fn anonymized_person_chain_rejects_invalid_or_unmarked_following_labels() { + let invalid = "A(1)·not".chars().collect::>(); + assert!(!anonymized_person_chain_has_marker(&invalid, 4)); + + let unmarked = "A(1)·B(2)·C(3)".chars().collect::>(); + assert!(!anonymized_person_chain_has_marker(&unmarked, 4)); + } + #[test] fn try_encode_mixed_math_prefix_encodes_math_prefix_before_korean_suffix() { let prefix: Vec = "x²".chars().collect(); diff --git a/libs/braillify/src/rules/token_rules/middle_dot_spacing.rs b/libs/braillify/src/rules/token_rules/middle_dot_spacing.rs index da909994..63e353e0 100644 --- a/libs/braillify/src/rules/token_rules/middle_dot_spacing.rs +++ b/libs/braillify/src/rules/token_rules/middle_dot_spacing.rs @@ -140,6 +140,8 @@ impl TokenRule for MiddleDotSpacingRule { #[cfg(test)] mod tests { + use super::*; + /// Korean rules 50, 51, and 59 determine braille spacing even when the /// print source contains editorial spaces around the punctuation. #[rstest::rstest] @@ -163,4 +165,18 @@ mod tests { crate::encode("설명(FAPAS: Food)") ); } + + #[test] + fn colon_spacing_probe_returns_false_when_no_punctuation_word_follows() { + let mut ir = crate::rules::token::DocumentIR::parse("한국", false); + ir.tokens + .push(Token::Space(crate::rules::token::SpaceKind::Regular)); + let Token::Word(previous) = &ir.tokens[0] else { + unreachable!("fixture begins with a word") + }; + + assert!(!space_precedes_korean_colon_or_semicolon( + &ir.tokens, 1, previous + )); + } } diff --git a/libs/braillify/src/rules/token_rules/normalize.rs b/libs/braillify/src/rules/token_rules/normalize.rs index 864538b5..231ddd3f 100644 --- a/libs/braillify/src/rules/token_rules/normalize.rs +++ b/libs/braillify/src/rules/token_rules/normalize.rs @@ -235,4 +235,19 @@ mod tests { ) { assert_eq!(normalize(input), expected); } + + #[test] + fn flattening_ignores_non_textual_tokens() { + let tokens = vec![ + Token::Fraction(crate::rules::token::FractionToken { + whole: None, + numerator: "1".to_string(), + denominator: "2".to_string(), + }), + Token::Mode(crate::rules::token::ModeEvent::EnterEnglish), + Token::PreEncoded(vec![1]), + ]; + + assert!(flattened_chars(&tokens).is_empty()); + } } diff --git a/libs/braillify/src/rules/token_rules/uppercase_passage.rs b/libs/braillify/src/rules/token_rules/uppercase_passage.rs index 349792d6..145a6103 100644 --- a/libs/braillify/src/rules/token_rules/uppercase_passage.rs +++ b/libs/braillify/src/rules/token_rules/uppercase_passage.rs @@ -452,6 +452,37 @@ mod tests { assert_eq!(found, expected); } + #[test] + fn capitalized_group_rejects_korean_inside_the_capital_extent() { + let Token::Word(word) = word("A한B") else { + unreachable!("helper always builds a word") + }; + + assert_eq!(capitalized_group(&word), None); + } + + #[test] + fn shortform_collision_before_capitals_passage_gets_grade1() { + let rule = UppercasePassageRule; + let tokens = spaced_words(&["CD", "EF", "GH"]); + let mut state = EncoderState::new(false); + state.english_indicator = true; + + let TokenAction::ReplaceMany(replacement) = + rule.apply(&tokens, 0, &mut state).expect("passage starts") + else { + panic!("expected a passage-start replacement") + }; + + assert!(replacement.windows(2).any(|window| matches!( + window, + [ + Token::Mode(ModeEvent::Grade1Indicator), + Token::Mode(ModeEvent::CapsPassageStart) + ] + ))); + } + #[rstest::rstest] #[case::pdf_gigabyte("GB")] #[case::petabyte("PB")] From 4177e4f9cbcf527fd2c56997a942a7d26e181c6a Mon Sep 17 00:00:00 2001 From: devfive Date: Thu, 3 Sep 2026 19:40:01 +0900 Subject: [PATCH 76/77] Close final Rust coverage gaps --- libs/braillify/src/rules/emit.rs | 26 +++++++++++++++---- .../token_rules/math_expression/apply.rs | 24 +++++++++++++---- 2 files changed, 40 insertions(+), 10 deletions(-) diff --git a/libs/braillify/src/rules/emit.rs b/libs/braillify/src/rules/emit.rs index ce375827..b8f9e3e7 100644 --- a/libs/braillify/src/rules/emit.rs +++ b/libs/braillify/src/rules/emit.rs @@ -858,7 +858,7 @@ fn apply_inter_character_rules( remaining_words: &[&str], prev_word: &str, result: &mut Vec, -) -> Result<(), String> { +) -> Result { let mut ctx = RuleContext { word_chars, index, @@ -873,8 +873,7 @@ fn apply_inter_character_rules( state, result, }; - engine.apply_phase(Phase::InterCharacter, &mut ctx)?; - Ok(()) + engine.apply_phase(Phase::InterCharacter, &mut ctx) } fn emit_word( @@ -1376,9 +1375,26 @@ mod tests { #[test] fn english_phrase_scan_stops_at_non_text_after_starting() { - let tokens = vec![word_token("Alpha"), Token::PreEncoded(vec![1])]; + let tokens = vec![ + word_token("Alpha"), + Token::Space(SpaceKind::Regular), + word_token("Beta"), + Token::PreEncoded(vec![1]), + ]; + + assert!(roman_section_has_english_phrase_context(&tokens, 0)); + } + + #[test] + fn previous_word_separation_scan_crosses_mode_markers() { + let tokens = vec![ + word_token("Alpha"), + Token::Space(SpaceKind::Regular), + Token::Mode(ModeEvent::CapsWord), + ]; - assert!(!roman_section_has_english_phrase_context(&tokens, 0)); + assert!(is_separated_from_previous_word(&tokens, tokens.len())); + assert!(!is_separated_from_previous_word(&tokens, 1)); } #[test] diff --git a/libs/braillify/src/rules/token_rules/math_expression/apply.rs b/libs/braillify/src/rules/token_rules/math_expression/apply.rs index 35a1c0cc..d740bffb 100644 --- a/libs/braillify/src/rules/token_rules/math_expression/apply.rs +++ b/libs/braillify/src/rules/token_rules/math_expression/apply.rs @@ -657,15 +657,15 @@ pub(super) fn has_korean_prefix_terminal_roman_plus_identifier(chars: &[char]) - /// A single lowercase letter remains ambiguous algebra (`값-x`), and an /// explicit operator after the Roman start remains math-owned (`값-x+1`). pub(super) fn has_korean_prefix_roman_hyphen_suffix(chars: &[char]) -> bool { - let enclosed_roman_continuation = chars.iter().enumerate().any(|(index, ch)| { - ch.is_ascii_alphabetic() + for (index, ch) in chars.iter().enumerate() { + if ch.is_ascii_alphabetic() && chars[..index] .iter() .any(|prefix| crate::utils::is_korean_char(*prefix)) && is_korean_prose_roman_hyphen_identifier(&chars[index..]) - }); - if enclosed_roman_continuation { - return true; + { + return true; + } } chars.windows(3).enumerate().any(|(index, window)| { @@ -2214,6 +2214,20 @@ mod tests { assert!(!is_within_attached_korean_prose_parenthetical(&tokens, 1)); } + #[rstest::rstest] + #[case::enclosed_roman_continuation("한글(ABC)-D", true)] + #[case::korean_prefix_before_initialism("기장-KBO", true)] + #[case::lowercase_math_variable("값-x", false)] + fn korean_roman_hyphen_suffix_is_classified_structurally( + #[case] input: &str, + #[case] expected: bool, + ) { + assert_eq!( + has_korean_prefix_roman_hyphen_suffix(&input.chars().collect::>()), + expected + ); + } + #[rstest::rstest] #[case::compact_unit("50bp", true)] #[case::decimal_prefix("3.1p", true)] From c5a697c253475ee4f4db045bff49da2fce1279ac Mon Sep 17 00:00:00 2001 From: devfive Date: Thu, 3 Sep 2026 20:01:39 +0900 Subject: [PATCH 77/77] Exercise final Linux coverage paths --- libs/braillify/src/rules/emit.rs | 43 ++++++++++++++++++- .../token_rules/math_expression/apply.rs | 11 +++++ 2 files changed, 53 insertions(+), 1 deletion(-) diff --git a/libs/braillify/src/rules/emit.rs b/libs/braillify/src/rules/emit.rs index b8f9e3e7..19192e56 100644 --- a/libs/braillify/src/rules/emit.rs +++ b/libs/braillify/src/rules/emit.rs @@ -1374,15 +1374,25 @@ mod tests { } #[test] - fn english_phrase_scan_stops_at_non_text_after_starting() { + fn english_phrase_scan_handles_non_text_boundaries() { let tokens = vec![ word_token("Alpha"), Token::Space(SpaceKind::Regular), word_token("Beta"), Token::PreEncoded(vec![1]), ]; + let leading_boundary = vec![ + Token::PreEncoded(vec![1]), + word_token("Alpha"), + Token::Space(SpaceKind::Regular), + word_token("Beta"), + ]; assert!(roman_section_has_english_phrase_context(&tokens, 0)); + assert!(roman_section_has_english_phrase_context( + &leading_boundary, + 0 + )); } #[test] @@ -1425,6 +1435,7 @@ mod tests { #[rstest::rstest] #[case::no_previous_word(None, false)] + #[case::empty_previous(Some(""), false)] #[case::punctuation_only_previous(Some("..."), false)] #[case::nested_enclosure(Some("((A))"), true)] #[case::missing_opener(Some("A)"), false)] @@ -1570,6 +1581,36 @@ mod tests { assert!(!ir.state.is_english); } + #[test] + fn forced_symbol_between_adjacent_word_tokens_terminates_roman_mode() { + let tokens = vec![word_token("ABC"), word_token("/")]; + let Token::Word(word) = &tokens[0] else { + unreachable!("fixture begins with a word") + }; + let remaining_words = ["/"]; + let mut state = EncoderState::new(true); + state.is_english = true; + let mut engine = make_char_engine(); + let mut result = Vec::new(); + + emit_word( + word, + 0, + &mut state, + &mut engine, + &tokens, + WordContext { + prev_word: "", + remaining_words: &remaining_words, + }, + &mut result, + ) + .expect("Roman word must encode"); + + assert_eq!(result.last(), Some(&50)); + assert!(!state.is_english); + } + // ── Step 1-3: Basic token tests ── /// `emit` 결과가 `encode()` 와 byte-identical 한지 (round-trip) 다양한 diff --git a/libs/braillify/src/rules/token_rules/math_expression/apply.rs b/libs/braillify/src/rules/token_rules/math_expression/apply.rs index d740bffb..9daa02ff 100644 --- a/libs/braillify/src/rules/token_rules/math_expression/apply.rs +++ b/libs/braillify/src/rules/token_rules/math_expression/apply.rs @@ -2214,6 +2214,17 @@ mod tests { assert!(!is_within_attached_korean_prose_parenthetical(&tokens, 1)); } + #[test] + fn attached_prose_parenthetical_ignores_mode_tokens_in_its_body() { + let tokens = vec![ + word_tok("한국("), + Token::Mode(crate::rules::token::ModeEvent::EnterEnglish), + word_tok("Web)"), + ]; + + assert!(is_within_attached_korean_prose_parenthetical(&tokens, 1)); + } + #[rstest::rstest] #[case::enclosed_roman_continuation("한글(ABC)-D", true)] #[case::korean_prefix_before_initialism("기장-KBO", true)]