Skip to content

Commit c7c8df2

Browse files
airmangclaude
andcommitted
feat: publish the height-budget fit engine (S-085) as 3.5.0
Vertical row-height budget makes form-fill shrink and typed refusal real instead of a silent page-shifting row growth. Wild-form re-measurement (fit-on 50.8% vs fit-off 49.2%, expected-pass layer unchanged) is published honestly with the three named structural defect classes; contract identity is untouched, so this is a core-only release. Co-Authored-By: Claude Fable 5 <[email protected]>
1 parent e044f1d commit c7c8df2

6 files changed

Lines changed: 142 additions & 17 deletions

File tree

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,3 +55,4 @@ Thumbs.db
5555

5656
# local scratch / measurement artifacts (oracle PDFs, PNGs, calibration)
5757
work/
58+
differential-fiton-*.json

CHANGELOG.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,25 @@
44

55
## [Unreleased]
66

7+
## [3.5.0] - 2026-07-20
8+
9+
### Added
10+
- 양식 채움 fit 판정에 **수직(행 높이) 예산**을 도입합니다. 이전에는 셀의 가로
11+
폭만 측정해 세로로 100줄까지 "맞음"으로 판정, 축소도 거부도 발동하지 않고 행이
12+
조용히 자라 페이지를 밀었습니다. 이제 `available_height`(셀 높이 − 상하 마진)와
13+
글꼴별 라인하이트로 세로 예산을 재서, `wrap_then_shrink`는 값을 예산 안으로
14+
축소하고, 최소 글꼴에서도 안 들어가는 과대 입력은 `overflow="fail"`에서 **typed
15+
거부**(FIELD_OVERFLOW)로 돌려줍니다. `allow_row_expand`/`expand_row`는 명시적
16+
opt-out입니다. `FitPolicy.keep()`과 폭-only 경로는 바이트 불변입니다.
17+
18+
### Measured (정직 발견)
19+
- wild 공개 양식 전수 재측정(실한컴): 양식 채움 차등이 fit-on 32/63 = 50.8%
20+
(fit-off 31/63 = 49.2%). 기대-통과 층(short+medium)은 61.4%로 **이동 없음**
21+
fit은 wild 실패의 지배 레버가 아니며, 지배 원인은 구조적 3부류(채움-겹침·다중
22+
페이지 경계 밀림·표 구조 민감도)임이 측정됐습니다. 상세는 `docs/corpus-metrics.md`
23+
및 leap 데모 `demo/S-085-wild-formfill/`.
24+
25+
726
## [3.4.1] - 2026-07-19
827

928
### Note

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
`hwpx-mcp-server``hwpx-plugin`은 같은 프로젝트가 직접 유지보수하는 first-party 연동 구성요소입니다.
2626
“first-party”는 프로젝트 유지보수 관계를 뜻하며, 한컴 또는 제3자의 공식 인증을 뜻하지 않습니다.
2727

28-
현재 PyPI 공개 릴리스는 `python-hwpx 3.4.1`입니다. 일반
28+
현재 PyPI 공개 릴리스는 `python-hwpx 3.5.0`입니다. 일반
2929
`pip install python-hwpx`로 이 릴리스를 설치할 수 있습니다.
3030
현재 패키지 분류는 `Development Status :: 3 - Alpha`입니다. 이 분류는 API와 제품의
3131
성숙도를 나타내며, 공개 버전이나 플러그인의 최소 호환 버전을 대신하지 않습니다.

docs/corpus-metrics-history.json

Lines changed: 95 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -6,27 +6,109 @@
66
{
77
"measuredAt": "2026-07-01",
88
"stack": "python-hwpx 2.29.x (corpus v1 FLOOR)",
9-
"corpus": {"version": "v1", "producedN": 100, "negativeControls": 7},
9+
"corpus": {
10+
"version": "v1",
11+
"producedN": 100,
12+
"negativeControls": 7
13+
},
1014
"oracle": "real Hancom COM (Windows box), unattended",
11-
"open": {"pass": 100, "n": 100, "rate": 1.0},
12-
"parsed": {"pass": 95, "n": 100, "rate": 0.95},
15+
"open": {
16+
"pass": 100,
17+
"n": 100,
18+
"rate": 1.0
19+
},
20+
"parsed": {
21+
"pass": 95,
22+
"n": 100,
23+
"rate": 0.95
24+
},
1325
"render": null,
1426
"byteIdentity": null,
1527
"pii": null
1628
},
1729
{
1830
"measuredAt": "2026-07-19",
1931
"stack": "python-hwpx 3.4.1 candidate (corpus v2)",
20-
"corpus": {"version": "v2 (additive over frozen v1)", "producedN": 497, "negativeControls": 9, "internalFixtureTagged": 21},
32+
"corpus": {
33+
"version": "v2 (additive over frozen v1)",
34+
"producedN": 497,
35+
"negativeControls": 9,
36+
"internalFixtureTagged": 21
37+
},
2138
"oracle": "real Hancom COM 12.0.0.3288 (Windows box, unattended) + Mac Hancom GUI sample",
22-
"open": {"pass": 476, "n": 476, "rate": 1.0, "lowerBound": 0.9937, "denominator": "product-provenance"},
23-
"parsed": {"pass": 458, "n": 476, "rate": 0.9622, "note": "redline docs judged via InitScan/GetText (42/42); textless docs count as non-parsed by definition"},
24-
"render": {"checked": 416, "unavailableRedline": 43, "unverified": 17, "failed": 0, "n": 476, "note": "Hancom refuses PDF export of tracked-change documents (measured limitation)"},
25-
"byteIdentity": {"pass": 497, "n": 497, "rate": 1.0, "scope": "untouched parts, patch path only"},
26-
"pii": {"leaks": 0, "documents": 35, "syntheticValues": 140, "surfaces": "engine + 3 MCP extraction surfaces"},
27-
"formFillDifferential": {"pass": 31, "n": 63, "rate": 0.4921, "note": "wild public form bases, intent-stratified: short 15/22, medium 12/22, overflow-stress 7/22; honest robustness finding"},
28-
"authoringGates": {"pass": 58, "n": 65, "note": "the 7 fails are intentional 결문-absent negative variants; real authored docs 58/58"},
29-
"nativeToc": {"structural": "15/15", "renumberSample": "5/5 (Mac Hancom refresh->render, session-separated)"}
39+
"open": {
40+
"pass": 476,
41+
"n": 476,
42+
"rate": 1.0,
43+
"lowerBound": 0.9937,
44+
"denominator": "product-provenance"
45+
},
46+
"parsed": {
47+
"pass": 458,
48+
"n": 476,
49+
"rate": 0.9622,
50+
"note": "redline docs judged via InitScan/GetText (42/42); textless docs count as non-parsed by definition"
51+
},
52+
"render": {
53+
"checked": 416,
54+
"unavailableRedline": 43,
55+
"unverified": 17,
56+
"failed": 0,
57+
"n": 476,
58+
"note": "Hancom refuses PDF export of tracked-change documents (measured limitation)"
59+
},
60+
"byteIdentity": {
61+
"pass": 497,
62+
"n": 497,
63+
"rate": 1.0,
64+
"scope": "untouched parts, patch path only"
65+
},
66+
"pii": {
67+
"leaks": 0,
68+
"documents": 35,
69+
"syntheticValues": 140,
70+
"surfaces": "engine + 3 MCP extraction surfaces"
71+
},
72+
"formFillDifferential": {
73+
"pass": 31,
74+
"n": 63,
75+
"rate": 0.4921,
76+
"note": "wild public form bases, intent-stratified: short 15/22, medium 12/22, overflow-stress 7/22; honest robustness finding"
77+
},
78+
"authoringGates": {
79+
"pass": 58,
80+
"n": 65,
81+
"note": "the 7 fails are intentional 결문-absent negative variants; real authored docs 58/58"
82+
},
83+
"nativeToc": {
84+
"structural": "15/15",
85+
"renumberSample": "5/5 (Mac Hancom refresh->render, session-separated)"
86+
}
87+
},
88+
{
89+
"measuredAt": "2026-07-20",
90+
"stack": "python-hwpx 3.5.0 (corpus v2, S-085 fit measurement)",
91+
"note": "height-budget fit engine shipped; wild form-fill differential fit-on 32/63=50.8% vs fit-off 31/63=49.2%; expected-pass layer 61.4% unchanged — fit is not the dominant lever, three structural defect classes named (fill-overlap, multi-page boundary, table-shape). Corpus open/parse/byte/pii/toc unchanged from 3.4.1.",
92+
"formFillFit": {
93+
"fitOff": {
94+
"pass": 31,
95+
"n": 63
96+
},
97+
"fitOn": {
98+
"pass": 32,
99+
"n": 63
100+
},
101+
"expectedPassLayer": {
102+
"pass": 27,
103+
"n": 44,
104+
"note": "short+medium, 0 movement"
105+
},
106+
"overflowLayer": {
107+
"fitOff": 7,
108+
"fitOn": 8,
109+
"n": 22
110+
}
111+
}
30112
}
31113
]
32-
}
114+
}

docs/corpus-metrics.md

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
| 파싱(내용 적재) | 458/476 = 96.2% | COM 텍스트 프로브 (redline 문서는 `InitScan/GetText` 42/42) | 비텍스트 문서(그림 전용 등)는 정의상 비파싱 집계 |
2323
| 렌더 검증 | render_checked 416/476 | COM `SaveAs("PDF")` → 오프라인 fitz | +43 `render_unavailable` (한컴이 변경추적 문서의 PDF export 자체를 거부 — 실측 한계) · +17 unverified |
2424
| 바이트 보존 | **497/497 = 100%** (미수정 part) | zip-part diff (오라클 불요) | patch 경로 한정 — 풀세이브는 명시적 out-of-claim |
25-
| 양식 채움 차등 | pass 31/63 = 49.2% (wild 공개 양식 25종) | blank↔filled 실렌더 기하 비교 | **정직 실측**: short 15/22 · medium 12/22 · overflow-스트레스 7/22 (overflow 변종은 의도적 과적재). 검증된 특정 양식 대비 wild-base 강건성이 현재 잔여 과제 |
25+
| 양식 채움 차등 | fit-off 31/63 = 49.2% · **fit-on 32/63 = 50.8%** (wild 공개 양식 25종) | blank↔filled 실렌더 기하 비교 | **정직 실측**: 아래 "양식 채움 fit 실측" 절 참조 — fit은 wild 실패의 지배 레버가 아님이 측정됨 |
2626
| 저작 품질 게이트 | 58/65 = 89.2% | 공문 구조 hard-gate + 품질 검사 (오라클 불요) | 실패 7건 전원이 **의도적 결문-생략 네거티브 변종** (게이트 판별력 증거) — 실저작 58/58 |
2727
| PII 0-leak | **0 누출** (35문서 / 합성 개인정보 140값) | raw-grep 전 산출물 + MCP 추출 3표면 | 마스킹 default-on 경로 |
2828
| 네이티브 목차 | 구조 15/15 · 실한컴 재계산 후 페이지 정합 5/5 | 구조 검사 + Mac 한컴 refresh→render 샘플 | refresh와 export는 세션 분리(한컴 크래시 회피 계약) |
@@ -32,14 +32,37 @@
3232
픽스처 21건(재생 프로브·스크립트 제작 마스터)은 정체 기준으로 태깅해 분리 발행한다
3333
(10 open / 11 refuse — 제품 산출물 아님).
3434

35+
## 양식 채움 fit 실측 (S-085, 2026-07-20)
36+
37+
같은 66쌍을 fit 비활성(baseline)과 fit 활성(height-budget 엔진)으로 실한컴 렌더해
38+
차등 판정한 결과. 층은 채움 값 길이로 나눈다(short/medium=기대-통과, overflow=
39+
의도적 과적재 스트레스).
40+
41+
|| fit-off | fit-on | 비고 |
42+
|---|---|---|---|
43+
| short (한두 글자) | 15/22 = 68% | 15/22 = 68% | **0 이동** — 축소할 값이 없으므로 fit 무관 |
44+
| medium | 12/22 = 55% | 12/22 = 55% | 0 이동 |
45+
| overflow-stress | 7/22 = 32% | 8/22 = 36% | +4pp — fit이 실제로 작동하는 유일한 층 |
46+
| **기대-통과(short+medium)** | **27/44 = 61.4%** | **27/44 = 61.4%** | 0 이동 |
47+
48+
**결론(정직 발견)**: 양식 채움 차등 실패의 **지배 원인은 fit이 아니라 구조적
49+
요인**이다. short 값(축소 불가)조차 68%에 정체하는 것이 그 증거다. 측정된 3부류
50+
지배 결함: ① 채움 시 글자 겹침(3글자 채움에 겹침 1621개 관측) ② 대형 다중
51+
페이지 양식의 경계 밀림(page N→N+1) ③ 표 구조 민감도. **fit 엔진 개선(가로·세로
52+
예산 측정으로 축소·typed 거부 실동작)은 correctness로 출하**하되(단위 증명 +
53+
leap 데모 `demo/S-085-wild-formfill/`), wild 통과율 자체는 이 3부류가 해결돼야
54+
오르는 잔여 과제로 명명한다. 무음 서식 파괴(조용한 페이지 밀림)는 이제 typed
55+
거부로 대체된다.
56+
3557
## 릴리스별 히스토리
3658

3759
기계 판독본: {download}`corpus-metrics-history.json <corpus-metrics-history.json>`.
3860
오라클 열은 **실제 박스 런이 있었던 릴리스만** 채운다(추정 금지).
3961

4062
| 릴리스(측정 스택) | 일자 | N | 오픈 | 파싱 | 렌더 | 바이트 | PII |
4163
|---|---|---|---|---|---|---|---|
42-
| 3.4.1 후보 (corpus v2) | 2026-07-19 | 497 | **100%** (476/476) | 96.2% | 416 checked / 43 unavailable / 17 unverified | 100% | 0-leak |
64+
| 3.5.0 (corpus v2) | 2026-07-19~20 | 497 | **100%** (476/476) | 96.2% | 416 checked / 43 unavailable / 17 unverified | 100% | 0-leak |
65+
| 3.4.1 (corpus v2) | 2026-07-19 | 497 | **100%** (476/476) | 96.2% | 416 checked / 43 unavailable / 17 unverified | 100% | 0-leak |
4366
| 2.29.x (corpus v1, FLOOR) | 2026-07-01 | 100 | 100% (100/100) | 95% | — (미공급) |||
4467

4568
## 방법론 요약

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
44

55
[project]
66
name = "python-hwpx"
7-
version = "3.4.1"
7+
version = "3.5.0"
88
description = "한글 없이 HWPX 문서를 열고, 편집하고, 생성하고, 검증하는 Python 자동화 라이브러리"
99
readme = { file = "README.md", content-type = "text/markdown" }
1010
license = "Apache-2.0"

0 commit comments

Comments
 (0)