From 5f4ac8d9902a635e6cc2d2b785335d6bf9a76362 Mon Sep 17 00:00:00 2001 From: umsangdon Date: Sat, 19 Sep 2026 12:39:29 +0900 Subject: [PATCH] =?UTF-8?q?knowledge(=ED=92=88=EC=85=88md):=20=EB=8F=84?= =?UTF-8?q?=EA=B5=AC=EA=B0=80=20=EB=90=98=ED=92=80=EC=9D=B4=20=EB=A8=B8?= =?UTF-8?q?=EB=A6=AC=EB=A5=BC=20=EC=8A=A4=EC=8A=A4=EB=A1=9C=20=EA=B0=80?= =?UTF-8?q?=EB=A6=BC=20=E2=80=94=20=ED=8C=90=EC=A0=95=20=EA=B8=B0=EC=A4=80?= =?UTF-8?q?=20=EC=85=8B=20=C2=B7=20=EA=B8=B0=EA=B3=84=EA=B2=80=EC=82=AC=20?= =?UTF-8?q?=EC=98=88=EC=99=B8=20=ED=91=9C=EC=8B=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - pdf_lines — 이어진 자리(쪽 넘김 · 「→」)의 되풀이 머리를 뺄 때 셋을 다 갖춘 줄만 뺌: ① 이번 항에서 이미 나온 글자에 이어 붙음 ② 목·항 머리 모양이 아님(1. · 가. · ① · [주] · <…>) ③ md 가 못 가진 글자 몫 안(PDF - md) — 통째로 드는 것부터 빼고 남은 몫으로 앞 토막만 더 뺌 - 기계검사 예외 — md 머리 바로 아래 면 「예외(되풀이 머리)」 로 적고 확정 지문도 적음 - 잠금 시험은 「통과」 또는 「예외」 를 받음(tool.passed) - 작성규칙 4-5 되풀이 머리 주석 없앰 · 표 줄 사이 주석 금지 · 6장 예외 표시 한 줄 - 불통 22건 → 0건: 18건 통과로 바뀜(판정 나빠진 파일 0 · 확정지문 다시 적음) · 남은 4건은 예외 표시 --- .../original/_pipeline/pum_md_tool.py | 130 ++++++++++++++---- .../original/원가계산/_품셈md_작성규칙.md | 4 +- .../제01장_적용기준/1-02_설계_및_수량.md | 5 +- .../01_공통부문/제05장_기초공사/5-03_말뚝.md | 4 +- .../8-03-07_[60]기초공사용_기계.md | 5 +- .../제09장_측량/9-03_지형_및_토지측량.md | 4 +- .../제09장_측량/9-05-01_항공사진촬영.md | 4 +- .../제09장_측량/9-05-04_수치지도_작성.md | 4 +- .../13-01_플랜트_배관.md | 4 +- .../13-02_플랜트_용접.md | 5 +- .../13-06_수력발전_기계설비.md | 4 +- .../본문/제01장_적용기준/1-02_설계_및_수량.md | 5 +- .../본문/제08장_방제/8-02_약제주입_기준.md | 4 +- resources/tester/test_pum_md_locked.py | 4 +- resources/tester/test_pum_md_tool.py | 73 +++++++++- 15 files changed, 204 insertions(+), 55 deletions(-) diff --git a/resources/knowledge/original/_pipeline/pum_md_tool.py b/resources/knowledge/original/_pipeline/pum_md_tool.py index c9a2fc23..afcfdb02 100644 --- a/resources/knowledge/original/_pipeline/pum_md_tool.py +++ b/resources/knowledge/original/_pipeline/pum_md_tool.py @@ -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 주석 `` · 왼쪽은 오른쪽에서 공백만 뺀 것이어야 함. _GLUED = re.compile(r"", re.S) +#: 기계검사 예외 — md 주석 `` (규칙 6장 · 머리 바로 아래 한 줄). +#: 도구가 스스로 못 가리는 되풀이 머리를 쓴 절 — 사람이 쪽 그림으로 대조하고 손으로 붙임. +#: 붙이면 ①② 가 어긋나도 「예외(되풀이 머리)」 로 적고 확정 파일이면 지문도 적음. +_EXEMPT = re.compile(r"") 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 ───────────────────────────────────────────────────── diff --git a/resources/knowledge/original/원가계산/_품셈md_작성규칙.md b/resources/knowledge/original/원가계산/_품셈md_작성규칙.md index d85ea07a..cd8b85af 100644 --- a/resources/knowledge/original/원가계산/_품셈md_작성규칙.md +++ b/resources/knowledge/original/원가계산/_품셈md_작성규칙.md @@ -64,7 +64,8 @@ PDF쪽: 135~136 3. 머리가 두 단 이상이면 **윗머리를 md 머리 줄로, 아랫머리는 몸 첫 줄(들)로** 적고 주석을 담. 윗머리를 아랫머리마다 되풀이해 펼치지 않음. `` 4. 빈 칸은 빈 칸. `〃` · `-` · `·` 는 그대로 적음(위 값으로 채우지 않음). -5. 한 표가 쪽을 넘어가면 **한 표로 이음.** 넘어간 쪽에서 되풀이된 머리 줄은 뺌. 뺀 자리(넘어간 첫 줄 앞)에 `` 로 그 글자를 적음 — 기계검사가 그 글자를 PDF 의 되풀이 머리와 맞대 셈. +5. 한 표가 쪽을 넘어가면 **한 표로 이음.** 넘어간 쪽에서 되풀이된 머리 줄은 뺌 — **주석을 남기지 않음**(표 줄 사이 주석은 표를 끊음). 기계검사가 넘어간 쪽 맨 위의 되풀이 머리를 스스로 뺌. + **표 줄 사이에는 어떤 주석도 넣지 않음** — 표 주석(병합·머리 등)은 표 바로 아래에. 6. 표가 너무 넓어 PDF 가 두 표로 나눠 찍었으면 PDF 대로 두 표. 7. 표 칸 안에 든 작은 표는 큰 표 바로 밑에 따로 적고 `` 주석. 8. 빗금으로 나뉜 머리 칸은 윗칸 글을 머리 줄에, 나머지를 몸 1행에 `
` 로 적고 `` 주석. @@ -94,6 +95,7 @@ PDF쪽: 135~136 - 글자층 붙은 수 — 자간 벌린 수의 거꾸로. 글자층이 여러 칸 값을 공백 없이 한 덩이로 담은 것(건설 834쪽 「0.3350.335…」)은 md 에 그림대로 칸마다 적고 `` 주석(왼쪽 = 글자층 덩이 그대로 · 오른쪽 = 공백만 끼운 것) — 도구가 PDF 쪽의 그 글자열만 갈라 셈(PDF 에 있을 때만 · 기계검사 칸 「글자층 붙은 수 N곳」 → ③ 이 그림으로 봄 · 못 쓰면 불통). - ①②가 0 이어도 칸이 뒤바뀐 것은 못 잡음 → ③ 없이는 `확정` 아님. - ③에서 고칠 곳이 나오면 검증 창이 고치지 않고 **작성 창에 돌려보냄**(줄 번호 + PDF 쪽 + 무엇이 다른지). +- 되풀이 머리를 도구가 못 가리는 절은 머리(`---`) 바로 아래 한 줄에 `` 를 붙임(표 줄 사이 금지) — 기계검사 칸이 「예외(되풀이 머리)」 가 되고 잠금 시험이 받음. ③ 이 쪽 그림으로 표마다 확인한 뒤에만 붙임. - `확정` 뒤 파일이 바뀌면 시험이 빨강. 고치려면 상태를 `작성중` 으로 되돌리고 ①②③ 다시. ## 7. 커밋 diff --git a/resources/knowledge/original/원가계산/건설공사_표준품셈/본문/01_공통부문/제01장_적용기준/1-02_설계_및_수량.md b/resources/knowledge/original/원가계산/건설공사_표준품셈/본문/01_공통부문/제01장_적용기준/1-02_설계_및_수량.md index acd7a633..479141d7 100644 --- a/resources/knowledge/original/원가계산/건설공사_표준품셈/본문/01_공통부문/제01장_적용기준/1-02_설계_및_수량.md +++ b/resources/knowledge/original/원가계산/건설공사_표준품셈/본문/01_공통부문/제01장_적용기준/1-02_설계_및_수량.md @@ -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 설계 및 수량 diff --git a/resources/knowledge/original/원가계산/건설공사_표준품셈/본문/01_공통부문/제05장_기초공사/5-03_말뚝.md b/resources/knowledge/original/원가계산/건설공사_표준품셈/본문/01_공통부문/제05장_기초공사/5-03_말뚝.md index f404aaaa..3ba0e2e6 100644 --- a/resources/knowledge/original/원가계산/건설공사_표준품셈/본문/01_공통부문/제05장_기초공사/5-03_말뚝.md +++ b/resources/knowledge/original/원가계산/건설공사_표준품셈/본문/01_공통부문/제05장_기초공사/5-03_말뚝.md @@ -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 말뚝 diff --git a/resources/knowledge/original/원가계산/건설공사_표준품셈/본문/01_공통부문/제08장_건설기계/8-03-07_[60]기초공사용_기계.md b/resources/knowledge/original/원가계산/건설공사_표준품셈/본문/01_공통부문/제08장_건설기계/8-03-07_[60]기초공사용_기계.md index 51996f93..17ecda94 100644 --- a/resources/knowledge/original/원가계산/건설공사_표준품셈/본문/01_공통부문/제08장_건설기계/8-03-07_[60]기초공사용_기계.md +++ b/resources/knowledge/original/원가계산/건설공사_표준품셈/본문/01_공통부문/제08장_건설기계/8-03-07_[60]기초공사용_기계.md @@ -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]기초공사용 기계 diff --git a/resources/knowledge/original/원가계산/건설공사_표준품셈/본문/02_토목부문/제09장_측량/9-03_지형_및_토지측량.md b/resources/knowledge/original/원가계산/건설공사_표준품셈/본문/02_토목부문/제09장_측량/9-03_지형_및_토지측량.md index 98500f32..0b1ced6b 100644 --- a/resources/knowledge/original/원가계산/건설공사_표준품셈/본문/02_토목부문/제09장_측량/9-03_지형_및_토지측량.md +++ b/resources/knowledge/original/원가계산/건설공사_표준품셈/본문/02_토목부문/제09장_측량/9-03_지형_및_토지측량.md @@ -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 지형 및 토지측량 diff --git a/resources/knowledge/original/원가계산/건설공사_표준품셈/본문/02_토목부문/제09장_측량/9-05-01_항공사진촬영.md b/resources/knowledge/original/원가계산/건설공사_표준품셈/본문/02_토목부문/제09장_측량/9-05-01_항공사진촬영.md index 4b05dc3a..25353432 100644 --- a/resources/knowledge/original/원가계산/건설공사_표준품셈/본문/02_토목부문/제09장_측량/9-05-01_항공사진촬영.md +++ b/resources/knowledge/original/원가계산/건설공사_표준품셈/본문/02_토목부문/제09장_측량/9-05-01_항공사진촬영.md @@ -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년 보완) diff --git a/resources/knowledge/original/원가계산/건설공사_표준품셈/본문/02_토목부문/제09장_측량/9-05-04_수치지도_작성.md b/resources/knowledge/original/원가계산/건설공사_표준품셈/본문/02_토목부문/제09장_측량/9-05-04_수치지도_작성.md index d08908ea..61bbf824 100644 --- a/resources/knowledge/original/원가계산/건설공사_표준품셈/본문/02_토목부문/제09장_측량/9-05-04_수치지도_작성.md +++ b/resources/knowledge/original/원가계산/건설공사_표준품셈/본문/02_토목부문/제09장_측량/9-05-04_수치지도_작성.md @@ -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년 보완) diff --git a/resources/knowledge/original/원가계산/건설공사_표준품셈/본문/04_기계설비부문/제13장_플랜트설비공사/13-01_플랜트_배관.md b/resources/knowledge/original/원가계산/건설공사_표준품셈/본문/04_기계설비부문/제13장_플랜트설비공사/13-01_플랜트_배관.md index 3f93a6dc..11e94fa4 100644 --- a/resources/knowledge/original/원가계산/건설공사_표준품셈/본문/04_기계설비부문/제13장_플랜트설비공사/13-01_플랜트_배관.md +++ b/resources/knowledge/original/원가계산/건설공사_표준품셈/본문/04_기계설비부문/제13장_플랜트설비공사/13-01_플랜트_배관.md @@ -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 플랜트 배관 diff --git a/resources/knowledge/original/원가계산/건설공사_표준품셈/본문/04_기계설비부문/제13장_플랜트설비공사/13-02_플랜트_용접.md b/resources/knowledge/original/원가계산/건설공사_표준품셈/본문/04_기계설비부문/제13장_플랜트설비공사/13-02_플랜트_용접.md index 869c33ff..2c933603 100644 --- a/resources/knowledge/original/원가계산/건설공사_표준품셈/본문/04_기계설비부문/제13장_플랜트설비공사/13-02_플랜트_용접.md +++ b/resources/knowledge/original/원가계산/건설공사_표준품셈/본문/04_기계설비부문/제13장_플랜트설비공사/13-02_플랜트_용접.md @@ -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 플랜트 용접 diff --git a/resources/knowledge/original/원가계산/건설공사_표준품셈/본문/04_기계설비부문/제13장_플랜트설비공사/13-06_수력발전_기계설비.md b/resources/knowledge/original/원가계산/건설공사_표준품셈/본문/04_기계설비부문/제13장_플랜트설비공사/13-06_수력발전_기계설비.md index 37531c7a..dc3c1fd2 100644 --- a/resources/knowledge/original/원가계산/건설공사_표준품셈/본문/04_기계설비부문/제13장_플랜트설비공사/13-06_수력발전_기계설비.md +++ b/resources/knowledge/original/원가계산/건설공사_표준품셈/본문/04_기계설비부문/제13장_플랜트설비공사/13-06_수력발전_기계설비.md @@ -9,8 +9,8 @@ PDF쪽: 846~879 작성: sub_laptop_3 검증: sub_laptop_4 원천: 글자층 -기계검사: 불통 · ①수 결손 0 허구 1 · ②글자 빠짐 0 더함 57 -확정지문: +기계검사: 통과 · ①수 결손 0 허구 0 · ②글자 빠짐 0 더함 0 +확정지문: 09ec78fbdcbe843ca11071b1243e8d98535c8b6c0e84cfe8f9ba2d9a96caff31 --- ## 13-6 수력발전 기계설비 diff --git a/resources/knowledge/original/원가계산/산림_표준품셈/본문/제01장_적용기준/1-02_설계_및_수량.md b/resources/knowledge/original/원가계산/산림_표준품셈/본문/제01장_적용기준/1-02_설계_및_수량.md index 85a1a901..9b8c3be3 100644 --- a/resources/knowledge/original/원가계산/산림_표준품셈/본문/제01장_적용기준/1-02_설계_및_수량.md +++ b/resources/knowledge/original/원가계산/산림_표준품셈/본문/제01장_적용기준/1-02_설계_및_수량.md @@ -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. 설계 및 수량 diff --git a/resources/knowledge/original/원가계산/산림_표준품셈/본문/제08장_방제/8-02_약제주입_기준.md b/resources/knowledge/original/원가계산/산림_표준품셈/본문/제08장_방제/8-02_약제주입_기준.md index 9fe4c005..8779d320 100644 --- a/resources/knowledge/original/원가계산/산림_표준품셈/본문/제08장_방제/8-02_약제주입_기준.md +++ b/resources/knowledge/original/원가계산/산림_표준품셈/본문/제08장_방제/8-02_약제주입_기준.md @@ -9,8 +9,8 @@ PDF쪽: 113~123 작성: sub_laptop_2 검증: sub_laptop_3 원천: 글자층 -기계검사: 불통 · ①수 결손 0 허구 117 · ②글자 빠짐 0 더함 286 -확정지문: +기계검사: 통과 · ①수 결손 0 허구 0 · ②글자 빠짐 0 더함 0 +확정지문: a8582f85a26be4b1f2cc556d1a8af0a1435ca37401fb8f21b784259c8d9c9265 --- ## 8-2. 약제주입 기준 diff --git a/resources/tester/test_pum_md_locked.py b/resources/tester/test_pum_md_locked.py index e9a07743..96f7dc34 100644 --- a/resources/tester/test_pum_md_locked.py +++ b/resources/tester/test_pum_md_locked.py @@ -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(): diff --git a/resources/tester/test_pum_md_tool.py b/resources/tester/test_pum_md_tool.py index 6c4fdb15..ee9c0fcd 100644 --- a/resources/tester/test_pum_md_tool.py +++ b/resources/tester/test_pum_md_tool.py @@ -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")