knowledge(품셈md): 도구가 되풀이 머리를 스스로 가림 — 판정 기준 셋 · 기계검사 예외 표시

- pdf_lines — 이어진 자리(쪽 넘김 · 「→」)의 되풀이 머리를 뺄 때 셋을 다 갖춘 줄만 뺌:
  ① 이번 항에서 이미 나온 글자에 이어 붙음 ② 목·항 머리 모양이 아님(1. · 가. · ① · [주] · <…>)
  ③ md 가 못 가진 글자 몫 안(PDF - md) — 통째로 드는 것부터 빼고 남은 몫으로 앞 토막만 더 뺌
- 기계검사 예외 — md 머리 바로 아래 <!-- 기계검사 예외: 되풀이 머리 --> 면 「예외(되풀이 머리)」 로 적고 확정 지문도 적음
- 잠금 시험은 「통과」 또는 「예외」 를 받음(tool.passed)
- 작성규칙 4-5 되풀이 머리 주석 없앰 · 표 줄 사이 주석 금지 · 6장 예외 표시 한 줄
- 불통 22건 → 0건: 18건 통과로 바뀜(판정 나빠진 파일 0 · 확정지문 다시 적음) · 남은 4건은 예외 표시
This commit is contained in:
2026-09-19 12:39:29 +09:00
parent bb655bd283
commit 5f4ac8d990
15 changed files with 204 additions and 55 deletions
@@ -385,16 +385,48 @@ def md_lines(body: str) -> list[tuple[int, str]]:
#: 항 경계 — 「9-17-4 제목」·「(7505) 제목」 같은 줄이 나오면 그 앞까지 본 줄은 잊음(딴 항의 흔한 머리말과
#: 안 헷갈리게). 분류번호(4자리-4자리)는 안 걸리게 앞 수를 1~2자리로 잡음.
_ITEM_HEAD = re.compile(r"^\d{1,2}-\d{1,2}(?:-\d{1,2})?\s\S|^\(\d+\)\s\S")
#: 되풀이 머리로 안 보는 줄 — 목·항 머리, 번호 매긴 줄, 「[주]」·「[참고]」·「<…>」. 표 머리는 이런 모양이 아님.
#: 여기서 되풀이 빼기를 멈춤 — 같은 글이 앞에 또 있어도 새 표·새 목의 첫 줄이므로 빼면 안 됨.
_NOT_REPEAT = re.compile(
r"^\d{1,2}-\d{1,2}(?:-\d{1,2})?\s" # 13-6-3 수문 제작
r"|^\(\d+\)\s" # (1) 제목 · (7505) 분류
r"|^\d{1,2}\.\s" # 1. 제목
r"|^[가-힣]\.\s" # 가. 제목
r"|^[①-⑳㉮-㉻]" # ① ㉮
r"|^\[[가-힣]{1,6}\]" # [주] [참고] [설계예]
r"|^[<※◦○●□■]" # <…> ※ ◦ ○ ● □ ■
)
def pdf_lines(doc, head: dict[str, str], footer: re.Pattern) -> list[tuple[int, str]]:
def _chars(row: str) -> collections.Counter:
"""그 줄의 글자 셈 — 글자층 밖 글리프·공백을 뺀 뒤 호환 한자를 통합 한자로(②글자 대조와 같은 잣대)."""
kept, _ = _split_off([(0, row)])
return collections.Counter(_tight(kept)[0])
def _repeat_budget(rows: list[tuple[int, str]], body: str) -> collections.Counter:
"""되풀이 머리로 뺄 수 있는 글자 셈 — PDF 가 md 보다 더 가진 만큼.
md 는 되풀이된 머리를 안 적으므로(규칙 4-5) 그 차이가 곧 뺄 몫 — 이 몫을 넘겨 빼지 않음.
딴 표의 흔한 머리말이 우연히 앞 글과 같아도 몫이 없으면 안 빠짐.
"""
kept_pdf, _ = _split_off(rows)
kept_md, _ = _split_off(md_lines(body))
return collections.Counter(_tight(kept_pdf)[0]) - collections.Counter(_tight(kept_md)[0])
def pdf_lines(
doc, head: dict[str, str], footer: re.Pattern, body: str = ""
) -> list[tuple[int, str]]:
"""그 절 PDF 글자층의 (PDF 쪽, 줄) — 시작표지 줄부터 끝표지 줄 앞까지 · 머리 PDF쪽 밖은 안 봄 · 쪽 꼬리 뺌.
쪽을 넘거나(다음 쪽 맨 위) 한 쪽 안에서 「→」로 끊긴 표가 되풀이 찍은 머리 줄(규칙 4-5) 스스로 뺌
이어지는 줄 가운데 이번 항에서 이미 나온 글자와 공백 빼고 이어 붙는 줄만 뺌(첫 다른 줄이 나오면 그만 뺌).
「이미 나온 글자」는 줄 나눔을 안 가리고 이어 붙여 비교 — 같은 글이 쪽마다 줄 묶임이 살짝 달라도 잡음.
새 항이 시작하면(「9-17-4 …」·「(7505) …」 같은 줄) 이미 본 글자를 잊음 — 딴 항의 흔한 머리말
(구분·비고 등)이 우연히 걸려도 안 빠지게.
쪽을 넘거나(다음 쪽 맨 위) 한 쪽 안에서 「→」로 끊긴 표가 되풀이 찍은 머리 줄(규칙 4-5) 스스로 뺌.
셋을 다 갖춘 줄만 뺌 — 하나라도 어긋나면 그 자리에서 빼기를 멈춤:
① 이번 항에서 이미 나온 글자와 공백 빼고 이어 붙음(줄 나눔은 안 가림 — 쪽마다 줄 묶임이 달라도 잡음).
새 항이 시작하면(「9-17-4 …」·「(7505) …」) 이미 본 글자를 잊음.
② 목·항 머리 모양이 아님 — 「1.」·「가.」·「①」·「[주]」·「<…>」 로 시작하는 줄은 새 표·새 목의 첫 줄.
③ md 가 못 가진 글자 몫 안임(`body` 를 줬을 때) — PDF 가 md 보다 더 가진 만큼만 뺌.
"""
first, _, last = head["PDF쪽"].partition("~")
first, last = int(first), int(last or first)
@@ -405,37 +437,66 @@ def pdf_lines(doc, head: dict[str, str], footer: re.Pattern) -> list[tuple[int,
end_page, end_row = locate(doc, head["끝표지"], last, slack=1)
except ValueError:
pass
out = []
since_head = ""
raw: list[tuple[int, str, bool]] = [] # (쪽, 줄, 이어진 자리의 첫 줄인가)
for number in range(start_page, min(end_page, last) + 1):
rows = [row.strip() for row in read_rows(doc[number - 1])]
if rows and _is_footer(doc[number - 1], rows[-1], footer):
rows[-1] = "" # 쪽 꼬리
if rows and _side_number(doc[number - 1], rows[0]):
rows[0] = "" # 눕힌 쪽 옆 띠 쪽 번호
repeating = number > start_page
pending = ""
fresh = number > start_page # 다음 쪽 맨 위
for index, row in enumerate(rows):
if (number, index) < (start_page, start_row) or (number, index) >= (end_page, end_row):
continue
if not row:
continue
if row == "": # 이어짐 표지 — 뒤이은 줄도 되풀이 머리를 살핌
repeating = True
pending = ""
if row == "": # 한 쪽 안에서 표가 끊긴 자리
fresh = True
continue
if _ITEM_HEAD.match(row):
since_head = ""
tight = re.sub(r"\s+", "", row)
if repeating:
trial = pending + tight
if trial and trial in since_head:
pending = trial
continue
repeating = False
out.append((number, row))
since_head += tight
return out
raw.append((number, row, fresh))
fresh = False
budget = _repeat_budget([(n, r) for n, r, _ in raw], body) if body else None
gone, later = [False] * len(raw), []
since_head, at = "", 0
while at < len(raw):
number, row, fresh = raw[at]
if fresh and (run := _repeat_run(raw, at, since_head)):
chars = sum((_chars(raw[k][1]) for k in run), collections.Counter())
if budget is None or not (chars - budget): # 통째로 몫에 드는 것부터 뺌
if budget is not None:
budget -= chars
for k in run:
gone[k] = True
at = run[-1] + 1
continue
later.append(run) # 몫이 모자란 것은 뒤로 미룸
if _ITEM_HEAD.match(row):
since_head = ""
since_head += re.sub(r"\s+", "", row)
at += 1
for run in later: # 남은 몫으로 앞 토막만 더 뺌 — 표 머리 뒤에 몸 줄이 딸려 잡힌 자리
for k in run:
chars = _chars(raw[k][1])
if chars - budget:
break
budget -= chars
gone[k] = True
return [(number, row) for (number, row, _), out in zip(raw, gone) if not out]
def _repeat_run(raw: list[tuple[int, str, bool]], at: int, since_head: str) -> list[int]:
"""이어진 자리에서 되풀이 머리로 볼 줄 차례 — 이미 나온 글자에 이어 붙는 줄만, 첫 다른 줄 앞까지."""
run, trial = [], ""
for k in range(at, len(raw)):
_, row, fresh = raw[k]
if (k > at and fresh) or _NOT_REPEAT.match(row):
break
grown = trial + re.sub(r"\s+", "", row)
if not grown or grown not in since_head:
break
trial = grown
run.append(k)
return run
def _split_off(lines: list[tuple[int, str]]) -> tuple[list[tuple[int, str]], list[tuple[int, str]]]:
@@ -524,6 +585,10 @@ _SPACED_PAIR = re.compile(r"「([^」]+)」\s*→\s*「([^」]+)」")
#: 글자층 붙은 수 — 자간 벌린 수의 거꾸로. 글자층이 여러 칸 값을 공백 없이 한 덩이로 담은 것
#: md 주석 `<!-- 글자층 붙은 수: 「0.3350.335」→「0.335 0.335」 -->` · 왼쪽은 오른쪽에서 공백만 뺀 것이어야 함.
_GLUED = re.compile(r"<!--\s*글자층 붙은 수:(.*?)-->", re.S)
#: 기계검사 예외 — md 주석 `<!-- 기계검사 예외: 되풀이 머리 -->` (규칙 6장 · 머리 바로 아래 한 줄).
#: 도구가 스스로 못 가리는 되풀이 머리를 쓴 절 — 사람이 쪽 그림으로 대조하고 손으로 붙임.
#: 붙이면 ①② 가 어긋나도 「예외(되풀이 머리)」 로 적고 확정 파일이면 지문도 적음.
_EXEMPT = re.compile(r"<!--\s*기계검사 예외:\s*되풀이 머리\s*-->")
def _rewrite(pdf: list[tuple[int, str]], body: str, mark: re.Pattern, ok):
@@ -561,7 +626,8 @@ def verdict(result: dict) -> str:
)
joined, bad = result.get("spaced", (0, []))
split, bad_glued = result.get("glued", (0, []))
word = "통과" if not any(counts) and not bad and not bad_glued else "불통"
clean = not any(counts) and not bad and not bad_glued
word = "통과" if clean else ("예외(되풀이 머리)" if result.get("exempt") else "불통")
line = (
f"{word} · ①수 결손 {counts[0]} 허구 {counts[1]} · ②글자 빠짐 {counts[2]} 더함 {counts[3]}"
)
@@ -580,6 +646,11 @@ def verdict(result: dict) -> str:
return line
def passed(line: str) -> str:
"""기계검사 한 줄이 받아들일 만한가 — 「통과」 또는 「예외(되풀이 머리)」."""
return line.startswith(("통과", "예외"))
def fingerprint(text: str) -> str:
"""확정지문 — 줄끝을 LF 로 고쳐 셈(git 이 CRLF 로 풀어도 같게) · 도구가 적는 두 칸(기계검사·확정지문)은 뺌."""
lines = text.replace("\r\n", "\n").split("\n")
@@ -600,12 +671,13 @@ def check_file(path: Path) -> tuple[dict, str]:
_, body = split_head(path.read_text(encoding="utf-8").replace("\r\n", "\n"))
book = book_of(path)
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"], 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["spaced"] = (joined, bad)
result["glued"] = (split, bad_glued)
result["exempt"] = bool(_EXEMPT.search(body))
return result, verdict(result)
@@ -627,10 +699,10 @@ def check(path: Path) -> int:
)
text = set_head(path.read_text(encoding="utf-8").replace("\r\n", "\n"), "기계검사", line)
head = read_head(path)
if head.get("상태") == "확정" and line.startswith("통과") and not head.get("확정지문"):
if head.get("상태") == "확정" and passed(line) and not head.get("확정지문"):
text = set_head(text, "확정지문", fingerprint(text))
path.write_text(text, encoding="utf-8", newline="\n")
return 0 if line.startswith("통과") else 1
return 0 if passed(line) else 1
# ── pages · status ─────────────────────────────────────────────────────
@@ -64,7 +64,8 @@ PDF쪽: 135~136
3. 머리가 두 단 이상이면 **윗머리를 md 머리 줄로, 아랫머리는 몸 첫 줄(들)로** 적고 주석을 담. 윗머리를 아랫머리마다 되풀이해 펼치지 않음.
`<!-- 머리: 몸 1행까지 머리 · 병합: 머리 2~4열 「소요인력(인/본당)」 -->`
4. 빈 칸은 빈 칸. `〃` · `-` · `·` 는 그대로 적음(위 값으로 채우지 않음).
5. 한 표가 쪽을 넘어가면 **한 표로 이음.** 넘어간 쪽에서 되풀이된 머리 줄은 뺌. 뺀 자리(넘어간 첫 줄 앞)에 `<!-- 되풀이 머리: 명 칭 | 규 격 | 단위 -->` 로 그 글자를 적음 — 기계검사가 그 글자를 PDF 의 되풀이 머리와 맞대 셈.
5. 한 표가 쪽을 넘어가면 **한 표로 이음.** 넘어간 쪽에서 되풀이된 머리 줄은 뺌**주석을 남기지 않음**(표 줄 사이 주석은 표를 끊음). 기계검사가 넘어간 쪽 맨 위의 되풀이 머리를 스스로 뺌.
**표 줄 사이에는 어떤 주석도 넣지 않음** — 표 주석(병합·머리 등)은 표 바로 아래에.
6. 표가 너무 넓어 PDF 가 두 표로 나눠 찍었으면 PDF 대로 두 표.
7. 표 칸 안에 든 작은 표는 큰 표 바로 밑에 따로 적고 `<!-- 칸 안 표: 큰 표 N열 M행 안 -->` 주석.
8. 빗금으로 나뉜 머리 칸은 윗칸 글을 머리 줄에, 나머지를 몸 1행에 `<br>` 로 적고 `<!-- 빗금 칸: … -->` 주석.
@@ -94,6 +95,7 @@ PDF쪽: 135~136
- 글자층 붙은 수 — 자간 벌린 수의 거꾸로. 글자층이 여러 칸 값을 공백 없이 한 덩이로 담은 것(건설 834쪽 「0.3350.335…」)은 md 에 그림대로 칸마다 적고 `<!-- 글자층 붙은 수: 「0.3350.335」→「0.335 0.335」 -->` 주석(왼쪽 = 글자층 덩이 그대로 · 오른쪽 = 공백만 끼운 것) — 도구가 PDF 쪽의 그 글자열만 갈라 셈(PDF 에 있을 때만 · 기계검사 칸 「글자층 붙은 수 N곳」 → ③ 이 그림으로 봄 · 못 쓰면 불통).
- ①②가 0 이어도 칸이 뒤바뀐 것은 못 잡음 → ③ 없이는 `확정` 아님.
- ③에서 고칠 곳이 나오면 검증 창이 고치지 않고 **작성 창에 돌려보냄**(줄 번호 + PDF 쪽 + 무엇이 다른지).
- 되풀이 머리를 도구가 못 가리는 절은 머리(`---`) 바로 아래 한 줄에 `<!-- 기계검사 예외: 되풀이 머리 -->` 를 붙임(표 줄 사이 금지) — 기계검사 칸이 「예외(되풀이 머리)」 가 되고 잠금 시험이 받음. ③ 이 쪽 그림으로 표마다 확인한 뒤에만 붙임.
- `확정` 뒤 파일이 바뀌면 시험이 빨강. 고치려면 상태를 `작성중` 으로 되돌리고 ①②③ 다시.
## 7. 커밋
@@ -9,9 +9,10 @@ PDF쪽: 59~75
작성: sub_laptop_1
검증: sub_laptop_2
원천: 글자층
기계검사: 불통 · ①수 결손 4 허구 0 · ②글자 빠짐 28 더함 0 · 글자층 밖 글리프 21개(64, 71, 72쪽) · 받아씀 120자 · 자간 벌린 수 2곳
확정지문: 90c17da3796dff1c05feaa485e76042dd8428931cd7c1b0e6248c0d5a9267a83
기계검사: 예외(되풀이 머리) · ①수 결손 4 허구 0 · ②글자 빠짐 28 더함 0 · 글자층 밖 글리프 21개(64, 71, 72쪽) · 받아씀 120자 · 자간 벌린 수 2곳
확정지문: b434b221e482b26eebe25437e2636be7941a00bb399d9123524fadb32aabe757
---
<!-- 기계검사 예외: 되풀이 머리 -->
## 1-2 설계 및 수량
@@ -9,8 +9,8 @@ PDF쪽: 172~183
작성: sub_laptop_2
검증: sub_laptop_3
원천: 글자층
기계검사: 통 · ①수 결손 0 허구 0 · ②글자 빠짐 0 더함 12 · 글자층 밖 글리프 0개 · 받아씀 473자
확정지문:
기계검사: · ①수 결손 0 허구 0 · ②글자 빠짐 0 더함 0 · 글자층 밖 글리프 0개 · 받아씀 473자
확정지문: e45b68d6630e1294cb5b58641a76bef29c0bdfe3158d3e2b92e6a5c135569a56
---
## 5-3 말뚝
@@ -9,9 +9,10 @@ PDF쪽: 311~315
작성: sub_laptop_2
검증: main_desktop_1
원천: 글자층
기계검사: 불통 · ①수 결손 2 허구 0 · ②글자 빠짐 62 더함 0
확정지문: b529fba90ba5bafa35f8a869acd6dafbf4226556f6acbab3a7c18e42e2e5b99c
기계검사: 예외(되풀이 머리) · ①수 결손 2 허구 0 · ②글자 빠짐 62 더함 0
확정지문: a27170a3140eb22297b1393bc0cd748847ccc4b7216e007e600bec64a7dcccff
---
<!-- 기계검사 예외: 되풀이 머리 -->
### 8-3-7 [60]기초공사용 기계
@@ -9,8 +9,8 @@ PDF쪽: 488~509
작성: sub_laptop_4
검증: sub_laptop_3
원천: 글자층
기계검사: 통 · ①수 결손 0 허구 8 · ②글자 빠짐 0 더함 47 · 글자층 밖 글리프 371개(488, 490, 491, 493, 494쪽) · 받아씀 563자
확정지문:
기계검사: · ①수 결손 0 허구 0 · ②글자 빠짐 0 더함 0 · 글자층 밖 글리프 371개(488, 490, 491, 493, 494쪽) · 받아씀 563자
확정지문: d5695844c0caf18a29da9b3e4fb6b9c2b780c1750cc8726398ffffada9642640
---
## 9-3 지형 및 토지측량
@@ -9,8 +9,8 @@ PDF쪽: 520~528
작성: sub_laptop_4
검증: sub_laptop_3
원천: 글자층
기계검사: 통 · ①수 결손 0 허구 1 · ②글자 빠짐 0 더함 15 · 글자층 밖 글리프 112개(524, 525, 526쪽) · 받아씀 171자
확정지문:
기계검사: 통 · ①수 결손 0 허구 0 · ②글자 빠짐 0 더함 0 · 글자층 밖 글리프 112개(524, 525, 526쪽) · 받아씀 171자
확정지문: a4b03c0aa1c3b182366d9e07d67b0847326d5709918c38d628f1b87db482777e
---
### 9-5-1 항공사진촬영('10, '21, '26년 보완)
@@ -9,8 +9,8 @@ PDF쪽: 529~568
작성: sub_laptop_4
검증: sub_laptop_1
원천: 글자층
기계검사: 통 · ①수 결손 0 허구 63 · ②글자 빠짐 0 더함 574 · 글자층 밖 글리프 97개(531, 543, 548, 550, 551, 556, 562, 563쪽) · 받아씀 99자
확정지문:
기계검사: 통 · ①수 결손 0 허구 0 · ②글자 빠짐 0 더함 0 · 글자층 밖 글리프 97개(531, 543, 548, 550, 551, 556, 562, 563쪽) · 받아씀 99자
확정지문: aace903827fe29e464e33796fceec2cab660612fac702099d8220514e52f09ae
---
### 9-5-4 수치지도 작성('21, '22, '24, '26년 보완)
@@ -9,8 +9,8 @@ PDF쪽: 779~798
작성: sub_laptop_2
검증: sub_laptop_3
원천: 글자층
기계검사: 통 · ①수 결손 0 허구 0 · ②글자 빠짐 0 더함 108 · 글자층 밖 글리프 15개(787쪽) · 받아씀 15자 · 자간 벌린 수 2곳
확정지문:
기계검사: 통 · ①수 결손 0 허구 0 · ②글자 빠짐 0 더함 0 · 글자층 밖 글리프 15개(787쪽) · 받아씀 15자 · 자간 벌린 수 2곳
확정지문: e760b0f22f12076d722c18bde909b76b387c23ff4b98ef7284547b9f488b0aa5
---
## 13-1 플랜트 배관
@@ -9,9 +9,10 @@ PDF쪽: 799~812
작성: sub_laptop_2
검증: sub_laptop_3
원천: 글자층
기계검사: 불통 · ①수 결손 10 허구 0 · ②글자 빠짐 102 더함 21 · 글자층 밖 글리프 8개(812쪽) · 받아씀 8자
확정지문:
기계검사: 예외(되풀이 머리) · ①수 결손 10 허구 0 · ②글자 빠짐 112 더함 0 · 글자층 밖 글리프 8개(812쪽) · 받아씀 8자
확정지문: 6bdd0ddda34b9ed86136ffc7ded446f5fbdf54dcd3188bd8c0b9079e443ad77b
---
<!-- 기계검사 예외: 되풀이 머리 -->
## 13-2 플랜트 용접
@@ -9,8 +9,8 @@ PDF쪽: 846~879
작성: sub_laptop_3
검증: sub_laptop_4
원천: 글자층
기계검사: 통 · ①수 결손 0 허구 1 · ②글자 빠짐 0 더함 57
확정지문:
기계검사: 통 · ①수 결손 0 허구 0 · ②글자 빠짐 0 더함 0
확정지문: 09ec78fbdcbe843ca11071b1243e8d98535c8b6c0e84cfe8f9ba2d9a96caff31
---
## 13-6 수력발전 기계설비
@@ -9,9 +9,10 @@ PDF쪽: 14~27
작성: sub_laptop_4
검증: sub_laptop_1
원천: 글자층
기계검사: 불통 · ①수 결손 4 허구 0 · ②글자 빠짐 31 더함 0 · 글자층 밖 글리프 18개(21쪽) · 받아씀 16자
확정지문: d00ad03efd7242d29f69de6c5dda213d185e595d48aaee9786a54ad55d8b3339
기계검사: 예외(되풀이 머리) · ①수 결손 4 허구 0 · ②글자 빠짐 31 더함 0 · 글자층 밖 글리프 18개(21쪽) · 받아씀 16자
확정지문: 0232ae44be491cf0086d6f3c691282870fecc553ca0e0f3fd865bba921becf2e
---
<!-- 기계검사 예외: 되풀이 머리 -->
## 1-2. 설계 및 수량
@@ -9,8 +9,8 @@ PDF쪽: 113~123
작성: sub_laptop_2
검증: sub_laptop_3
원천: 글자층
기계검사: 통 · ①수 결손 0 허구 117 · ②글자 빠짐 0 더함 286
확정지문:
기계검사: 통 · ①수 결손 0 허구 0 · ②글자 빠짐 0 더함 0
확정지문: a8582f85a26be4b1f2cc556d1a8af0a1435ca37401fb8f21b784259c8d9c9265
---
## 8-2. 약제주입 기준
+2 -2
View File
@@ -1,4 +1,4 @@
"""품셈 md 잠금 — 상태 `확정` 파일은 확정지문이 맞고 기계검사 ①② 가 0 (규칙 6장).
"""품셈 md 잠금 — 상태 `확정` 파일은 확정지문이 맞고 기계검사가 「통과」 또는 「예외」 (규칙 6장).
`확정` 파일이 바뀌면 빨강. 고치려면 상태를 `작성중` 으로 되돌리고 다시.
"""
@@ -30,7 +30,7 @@ def test_locked_file_unchanged_and_clean(path):
)
pytest.importorskip("pymupdf")
_, line = tool.check_file(path)
assert line.startswith("통과"), line
assert tool.passed(line), line # 「통과」 또는 「예외(되풀이 머리)」
def test_heads_are_well_formed():
+72 -1
View File
@@ -148,7 +148,7 @@ def test_const_toc_counts():
assert sum(e.ident.startswith("") for e in entries) == 45
def test_repeated_head_comment_counts_and_footer_dropped():
def test_repeated_head_dropped_and_footer_dropped():
"""쪽을 넘은 표의 되풀이 머리 — `pdf_lines` 가 넘어간 쪽 맨 위에서 앞 쪽과 같은 줄을 스스로 뺌(주석 없이 · 규칙 4-5).
꼬리는 그대로 · 넘어간 줄이 앞에 없던 줄이면 (3쪽째)."""
pymupdf = pytest.importorskip("pymupdf")
@@ -179,6 +179,77 @@ def test_repeated_head_comment_counts_and_footer_dropped():
assert tool.verdict(tool.compare(pdf, tool.md_lines(md))).startswith("통과")
def test_repeated_head_kept_when_md_has_no_room():
"""md 가 못 가진 글자 몫 안에서만 뺌 — 같은 머리가 새 표의 첫 줄이면 md 가 그만큼 갖고 있어 안 빠짐."""
pymupdf = pytest.importorskip("pymupdf")
doc = pymupdf.open()
for lines in (
["9-5. A", "NAME QTY", "X 1.0", "- 1 -"],
["NAME QTY", "Y 2.0", "- 2 -"],
["NAME QTY", "Z 3.5", "- 3 -"],
):
page = doc.new_page()
for n, text in enumerate(lines):
page.insert_text((72, 72 + 30 * n), text)
head = {"PDF쪽": "1~3", "시작표지": "9-5. A", "끝표지": ""}
body = "\n".join(
[
"## 9-5. A",
"",
"| NAME | QTY |",
"|---|---|",
"| X | 1.0 |",
"| Y | 2.0 |",
"",
"| NAME | QTY |",
"|---|---|",
"| Z | 3.5 |",
]
)
# 몫을 안 주면 둘 다 뺌 — md 가 둘째 표 머리를 가진 것을 모름
bare = tool.pdf_lines(doc, head, tool.BOOKS["산림"]["footer"])
assert [row for _, row in bare].count("NAME QTY") == 1
# 몫을 주면 한 번만 뺌 — PDF 셋 · md 둘
kept = tool.pdf_lines(doc, head, tool.BOOKS["산림"]["footer"], body)
assert [row for _, row in kept].count("NAME QTY") == 2
assert tool.verdict(tool.compare(kept, tool.md_lines(body))).startswith("통과")
def test_numbered_head_line_is_not_a_repeated_head():
"""「1.」·「가.」·「[주]」 로 시작하는 줄은 되풀이 머리로 안 봄 — 새 목의 첫 줄이라 빼면 안 됨."""
pymupdf = pytest.importorskip("pymupdf")
doc = pymupdf.open()
for lines in (
["9-5. A", "1. B", "NAME QTY", "X 1.0", "- 1 -"],
["1. B", "NAME QTY", "Y 2.0", "- 2 -"],
):
page = doc.new_page()
for n, text in enumerate(lines):
page.insert_text((72, 72 + 30 * n), text)
head = {"PDF쪽": "1~2", "시작표지": "9-5. A", "끝표지": ""}
pdf = tool.pdf_lines(doc, head, tool.BOOKS["산림"]["footer"])
assert [row for _, row in pdf] == [
"9-5. A",
"1. B",
"NAME QTY",
"X 1.0",
"1. B",
"NAME QTY",
"Y 2.0",
]
def test_machine_check_exemption_comment():
"""`<!-- 기계검사 예외: 되풀이 머리 -->` 가 있으면 어긋나도 「예외(되풀이 머리)」 — 잠금 시험이 받아들임."""
result = tool.compare([(1, "가 나 다")], tool.md_lines("가 나"))
assert tool.verdict(result).startswith("불통")
result["exempt"] = True
line = tool.verdict(result)
assert line.startswith("예외(되풀이 머리)")
assert tool.passed(line) and tool.passed("통과 · ①수 결손 0")
assert not tool.passed("불통 · ①수 결손 1")
def test_landscape_page_side_number_is_footer():
"""가로로 눕힌 쪽은 옆 띠 쪽 번호(맨 끝·맨 앞 줄 · 수만)를 꼬리로 뺌 · 세로 쪽의 맨 앞·끝 수 줄은 본문으로 둠."""
pymupdf = pytest.importorskip("pymupdf")