fix(b08): 헛단위 뺌 — [주] 인용 「‘13-3. 기초다짐 및 뒤채움’ 항을 적용한다」를 제목으로 읽던 것(표 F0405·F0406 → 13-4-1 · F0418 → 13-5-2) · 부록 사례 표 일곱이 본문 절 번호와 겹쳐 붙던 것(이름 닮음 0.3 아래면 못 붙인 표 목록 · 진짜 제목은 0.34 이상) · 산림 목록 627 → 620 · 단가표 전후 대조: 헛단위 B-FP-13-03 셋 빠짐 · B-FP-13-05-02 가 메붙임+찰붙임 합으로 약 두 배(쓰는 곳 0 · KnownGaps 「쓰면 안 됨」 · 갈래 가르기 대기) · 나머지 단가 불변 · 시험 셋(브레인 · 서브 훑기)

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BW6Jdsh18WPtYUR6THZJqn
This commit is contained in:
2026-09-17 17:28:29 +09:00
co-authored by Claude Opus 5
parent e057ceb442
commit 47f188b238
11 changed files with 315 additions and 6011 deletions
@@ -26,6 +26,7 @@
from __future__ import annotations
import difflib
import hashlib
import json
import re
@@ -146,10 +147,16 @@ def renumber_from_body(
return fixed
#: ⚠ [주] 속 **인용** — 「‘13-3. 기초다짐 및 뒤채움’ 항을 적용한다」(13-4-1 [주]④ · 13-5-2 [주]⑤).
#: 「번호. 이름」 꼴이라 제목으로 읽혀 표 셋(F0405·F0406·F0418)이 13-3 에 붙고 없는 갈래 3줄이 섰음
#: (2026-09-17 서브 훑기 · 브레인). 닫는 따옴표 뒤에 「항·을·를」이 오면 인용.
_CITATION_RE = re.compile(r"[’”]\s*(?:항|을|를)")
def _is_section_title(section: str, by_number: dict[str, dict[str, Any]]) -> bool:
"""진짜 절 제목인가 — 「번호. 이름」, 또는 마침표 없이 번호 뒤 글이 목차 절 이름과 같음."""
"""진짜 절 제목인가 — 「번호. 이름」(인용 아님), 또는 마침표 없이 번호 뒤 글이 목차 절 이름과 같음."""
if re.match(r"^\s*\d+(?:-\d+){0,3}\.\s", section):
return True
return not _CITATION_RE.search(section)
number = section_number(section)
node = by_number.get(number) if number else None
if node is None:
@@ -158,6 +165,35 @@ def _is_section_title(section: str, by_number: dict[str, dict[str, Any]]) -> boo
return "".join(tail.split()) == "".join(str(node.get("name") or "").split())
#: 목차 이름과 본문 제목이 **글자만** 다른 자리(「깍기」·「깎기」 · 「㎝」·「cm」)를 같게 봄 — 이름 닮음 잴 때만.
_NAME_GLYPHS = (("", "cm"), ("", "·"), ("", "·"), ("", "~"), ("", "~"), ("", ""))
#: 이름 닮음 아래턱 — 부록 사례(「4-1. 소나무재선충병방제」 ↔ 목차 4-1 수확베기)는 0.09 이하 ·
#: 진짜 제목의 글자 차이(「비탈면 고르기」 ↔ 「비탈면 면고르기」 0.96 · 「면벽」 ↔ 「면벅」 0.5)는 0.34 이상(2026-09-17 전수).
NAME_LIKENESS_FLOOR = 0.3
def _plain(name: str) -> str:
text = "".join(str(name or "").split())
for glyph, same in _NAME_GLYPHS:
text = text.replace(glyph, same)
return text
def title_names_node(section: str, node: dict[str, Any]) -> bool:
"""「번호. 이름」 제목의 이름이 그 번호 목차 줄 이름을 가리키는가 — 번호만 겹친 **딴 문서 표**를 거름.
⚠ 부록 사례 표 일곱(F0455·F0456·F0472~F0476)이 본문 절 번호와 겹쳐 그 절에 붙어
없는 공종 줄(4-1 수확베기 ← 소나무재선충병방제)이 섰음(2026-09-17 브레인 헛단위). 못 붙인 표로 목록에 남김.
"""
found = re.match(r"^\s*\d+(?:-\d+){0,3}\.\s+(.+)$", section)
if found is None:
return True # 마침표 없는 제목은 `_is_section_title` 이 이미 목차 이름과 같은지 봤음
title, name = _plain(found.group(1)), _plain(node.get("name"))
if title.startswith(name) or name.startswith(title):
return True
return difflib.SequenceMatcher(None, title, name).ratio() >= NAME_LIKENESS_FLOOR
def section_number(section: str) -> str | None:
"""`"9-3-1. 인력"` → `"9-3-1"`. 번호가 없으면 `None`."""
m = re.match(r"^\s*(\d+(?:-\d+){0,3})[.\s]", section + " ")
@@ -208,6 +244,8 @@ def build() -> tuple[
number = section_number(section)
chapter = number.split("-")[0] if number else None
node = by_number.get(number) if number else None
if node is not None and not title_names_node(section, node):
node = None # 번호만 겹친 딴 문서 표(부록 사례) — 못 붙인 표 목록으로
entry = table_entry(
table, section, number, chapter, source_lines, node and node["name"], report
)
@@ -123,7 +123,7 @@ FORM_JUDGMENTS: dict[str, tuple[str, str, str]] = {
# ── 제13장 구조물 ───────────────────────────────────────────────
"F0400": ("13-2-3", "reference", "큰돌 직경별 뒷길이 기준(㎝)"),
"F0406": (
"13-3",
"13-4-1", # 2026-09-17 절 바로잡음 — [주]④ 인용을 13-3 제목으로 읽었던 것(표는 13-4-1 메쌓기 아래)
"reference",
"〈돌쌓기의 개수 및 중량의 표준〉 — [주] 인력 돌쌓기·돌붙임 기준",
),