knowledge(품셈md): 도구 「글자층 붙은 수」 주석으로 PDF 쪽 붙은 수 가름 · 시험 하나

- md 주석 <!-- 글자층 붙은 수: 「0.3350.335」→「0.335 0.335」 --> 에 적힌 글자열만 PDF 쪽에서 가름(자간 벌린 수의 거꾸로)
- PDF 에 있고 왼쪽이 오른쪽에서 공백만 뺀 것일 때만 · 기계검사 칸 「글자층 붙은 수 N곳」 · 못 쓰면 불통
- 전후 대조: 기계검사 칸 있는 md 518개 판정 바뀜 0

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01V1MKKZKpUHTPKb513FneU8
This commit is contained in:
2026-09-19 09:18:43 +09:00
co-authored by Claude Opus 5
parent 23453c36b6
commit f4e142b38a
2 changed files with 59 additions and 11 deletions
@@ -494,20 +494,35 @@ def compare(pdf: list[tuple[int, str]], md: list[tuple[int, str]]) -> dict:
#: 짐작으로 붙이지 않고 **적힌 글자열만** PDF 쪽에서 붙여 셈 · 오른쪽은 왼쪽에서 공백만 뺀 것이어야 함. #: 짐작으로 붙이지 않고 **적힌 글자열만** PDF 쪽에서 붙여 셈 · 오른쪽은 왼쪽에서 공백만 뺀 것이어야 함.
_SPACED = re.compile(r"<!--\s*자간 벌린 수:(.*?)-->", re.S) _SPACED = re.compile(r"<!--\s*자간 벌린 수:(.*?)-->", re.S)
_SPACED_PAIR = re.compile(r"「([^」]+)」\s*→\s*「([^」]+)」") _SPACED_PAIR = re.compile(r"「([^」]+)」\s*→\s*「([^」]+)」")
#: 글자층 붙은 수 — 자간 벌린 수의 거꾸로. 글자층이 여러 칸 값을 공백 없이 한 덩이로 담은 것
#: md 주석 `<!-- 글자층 붙은 수: 「0.3350.335」→「0.335 0.335」 -->` · 왼쪽은 오른쪽에서 공백만 뺀 것이어야 함.
_GLUED = re.compile(r"<!--\s*글자층 붙은 수:(.*?)-->", re.S)
def _rewrite(pdf: list[tuple[int, str]], body: str, mark: re.Pattern, ok):
"""(PDF 줄, 고친 곳 수, 못 쓴 글자열) — md 주석에 적힌 글자열만 PDF 줄에서 바꿈(PDF 에 있고 ok 일 때만)."""
done, bad = 0, []
for block in mark.findall(body):
for old, new in _SPACED_PAIR.findall(block):
hits = sum(line.count(old) for _, line in pdf)
if not hits or not ok(old, new):
bad.append(old)
continue
pdf = [(at, line.replace(old, new)) for at, line in pdf]
done += hits
return pdf, done, bad
def join_spaced_numbers(pdf: list[tuple[int, str]], body: str): def join_spaced_numbers(pdf: list[tuple[int, str]], body: str):
"""(PDF 줄, 붙인 곳 수, 못 쓴 글자열) — md 주석에 적힌 자간 벌린 수만 PDF 줄에서 붙임.""" """(PDF 줄, 붙인 곳 수, 못 쓴 글자열) — md 주석에 적힌 자간 벌린 수만 PDF 줄에서 붙임."""
joined, bad = 0, [] return _rewrite(pdf, body, _SPACED, lambda spaced, tight: re.sub(r"\s", "", spaced) == tight)
for block in _SPACED.findall(body):
for spaced, tight in _SPACED_PAIR.findall(block):
hits = sum(line.count(spaced) for _, line in pdf) def split_glued_numbers(pdf: list[tuple[int, str]], body: str):
if not hits or re.sub(r"\s", "", spaced) != tight: """(PDF 줄, 가른 곳 수, 못 쓴 글자열) — md 주석에 적힌 글자층 붙은 수만 PDF 줄에서 가름."""
bad.append(spaced) return _rewrite(
continue pdf, body, _GLUED, lambda glued, split: glued != split and re.sub(r"\s", "", split) == glued
pdf = [(at, line.replace(spaced, tight)) for at, line in pdf] )
joined += hits
return pdf, joined, bad
def verdict(result: dict) -> str: def verdict(result: dict) -> str:
@@ -518,7 +533,8 @@ def verdict(result: dict) -> str:
sum(result["char_extra"].values()), sum(result["char_extra"].values()),
) )
joined, bad = result.get("spaced", (0, [])) joined, bad = result.get("spaced", (0, []))
word = "통과" if not any(counts) and not bad else "불통" split, bad_glued = result.get("glued", (0, []))
word = "통과" if not any(counts) and not bad and not bad_glued else "불통"
line = ( line = (
f"{word} · ①수 결손 {counts[0]} 허구 {counts[1]} · ②글자 빠짐 {counts[2]} 더함 {counts[3]}" f"{word} · ①수 결손 {counts[0]} 허구 {counts[1]} · ②글자 빠짐 {counts[2]} 더함 {counts[3]}"
) )
@@ -530,6 +546,10 @@ def verdict(result: dict) -> str:
line += f" · 자간 벌린 수 {joined}" line += f" · 자간 벌린 수 {joined}"
if bad: if bad:
line += " · 자간 벌린 수 못 씀 " + " ".join(f"{s}" for s in bad) line += " · 자간 벌린 수 못 씀 " + " ".join(f"{s}" for s in bad)
if split: # ③ 이 그림으로 칸마다 값을 꼭 보게
line += f" · 글자층 붙은 수 {split}"
if bad_glued:
line += " · 글자층 붙은 수 못 씀 " + " ".join(f"{s}" for s in bad_glued)
return line return line
@@ -555,8 +575,10 @@ def check_file(path: Path) -> tuple[dict, str]:
with pymupdf.open(book["pdf"]) as doc: with pymupdf.open(book["pdf"]) as doc:
pdf = pdf_lines(doc, read_head(path), book["footer"]) pdf = pdf_lines(doc, read_head(path), book["footer"])
pdf, joined, bad = join_spaced_numbers(pdf, body) pdf, joined, bad = join_spaced_numbers(pdf, body)
pdf, split, bad_glued = split_glued_numbers(pdf, body)
result = compare(pdf, md_lines(body)) result = compare(pdf, md_lines(body))
result["spaced"] = (joined, bad) result["spaced"] = (joined, bad)
result["glued"] = (split, bad_glued)
return result, verdict(result) return result, verdict(result)
+26
View File
@@ -250,3 +250,29 @@ def test_spaced_numbers_marker_joins_only_listed_strings():
assert bad == ["9 . 9", "1 . 3"] assert bad == ["9 . 9", "1 . 3"]
result["spaced"] = (0, bad) result["spaced"] = (0, bad)
assert tool.verdict(result).startswith("불통") assert tool.verdict(result).startswith("불통")
def test_glued_numbers_marker_splits_only_listed_strings():
"""md 주석 「글자층 붙은 수」 에 적힌 글자열만 PDF 쪽에서 갈라 셈 · 없거나 공백만 뺀 것이 아니면 불통."""
pdf = [(834, "Foundation Chipping 특별인부 인/㎡ 0.3350.3350.335"), (834, "0.3350 따로")]
body = (
"Foundation Chipping 특별인부 인/㎡ 0.335 0.335 0.335\n0.3350 따로\n"
"<!-- 글자층 붙은 수: 「0.3350.3350.335」→「0.335 0.335 0.335」 -->"
)
before = tool.compare(pdf, tool.md_lines(body))
assert before["num_missing"] and before["num_extra"] # 주석 없으면 ① 결손·허구
split, count, bad = tool.split_glued_numbers(pdf, body)
assert count == 1 and not bad
assert split[1] == (834, "0.3350 따로") # 적히지 않은 곳은 그대로
result = tool.compare(split, tool.md_lines(body))
result["glued"] = (count, bad)
line = tool.verdict(result)
assert line.startswith("통과") and "글자층 붙은 수 1곳" in line, line
# PDF 에 없는 글자열 · 공백만 뺀 것이 아닌 짝 · 가르지 않은 짝은 못 씀 → 불통
_, _, bad = tool.split_glued_numbers(
pdf,
"<!-- 글자층 붙은 수: 「9.99.9」→「9.9 9.9」 · 「0.3350.335」→「0.335 0.336」 · 「0.3350」→「0.3350」 -->",
)
assert bad == ["9.99.9", "0.3350.335", "0.3350"]
result["glued"] = (0, bad)
assert tool.verdict(result).startswith("불통")