fix(data): 숫자로 시작하는 기종 이름을 버리던 잣대 고침 · 8-1-7 손료보정을 제 표에서 읽음 — 기초단가 잣대 16 → 10

**① 기종 이름 — `(8201) 3D GNSS 머신 가이던스(굴착기용)`**
  추출기가 **첫 낱자가 숫자면 쪽번호로 보고 버리던** 탓에 8201 이름을 못 찾고 **옛 벌의 뭉개진 글**을
  이어받고 있었음(`배관파이프시 간 당(10-7)분류번호규 격7995-0050…`). 낱자가 하나도 없는 토막만
  버리게 고침 — 옛 벌에서 이어받은 이름 7 → **0** · 이름 바로잡힌 줄 1(값은 안 움직임).
  ⚠ `_build_mach.py` 는 랩탑 메인이 지은 것 — 한 줄 잣대만 고쳤음.

**② 8-1-7 손료보정 — 원문이 제 표로 고쳐졌음**
  전엔 글로 뭉개져 `덤프트럭2510` 처럼 붙어 있어 「25·10 인지 2·510 인지 못 가름」으로 세고 있었음.
  이제 `| 덤프트럭 | 25 | 10 |` 이라 **칸에서 바로** 읽음 — 기종 글은 원문이 낱자 사이에 빈칸을 두므로
  (`불도저( 1 9 톤이상 제외)`) 빈칸을 지워 맞댐. 네 줄이 한꺼번에 풀림(보정 셋 + 수 자리 가름).

**③ 이름 없는 기종을 세는 자리** — 8-3 에 `(코드) 이름` 머리글이 없어 **양쪽이 다 비어 조용히 지나가는**
  자리를 세게 함. 지금은 **0** — 8202~8204 도 머리글이 [주] 줄에 섞여 있어 둘 다 읽고 있음.

⇒ 기초단가 어긋남 23 → **17**(잣대 못 알아봄 16 → **10** · 원문 부재 7).
  남은 잣대 10 은 다 랩탑 서브 PDF 몫 — 규격 빈칸 일곱(원문이 기종 이름을 여러 줄로 쪼갬) ·
  양수기 7993-0020 셋(규격이 주연료 칸에 밀림).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QsGw1Dz9HmhuAxGisxmDPF
This commit is contained in:
2026-09-18 17:04:46 +09:00
co-authored by Claude Opus 5
parent c12367cc33
commit fd6865c19a
4 changed files with 31 additions and 72 deletions
@@ -123,7 +123,10 @@ def names_in(lines: list[str]) -> dict[str, str]:
continue
for m in _NAME_AT.finditer(line):
name = m.group(2).strip("")
if name and not name[0].isdigit():
# ⚠ 숫자로 시작하는 이름도 있다 — `(8201) 3D GNSS 머신 가이던스(굴착기용)`.
# 첫 낱자가 숫자면 버리던 잣대가 그것을 쪽번호로 보고 내버려, 옛 벌의 뭉개진 글을
# 이어받고 있었다(2026-09-18). 낱자가 하나도 없는 토막만 버린다.
if name and re.search(r"[가-힣A-Za-z]", name):
found.setdefault(m.group(1), re.sub(r"\s+", " ", name))
return found
@@ -2,7 +2,7 @@
"schema_version": "1.0",
"dataset_id": "mach_base",
"effective_date": "2026-01-01",
"generated_at": "2026-09-18T14:37:18+09:00",
"generated_at": "2026-09-18T17:02:43+09:00",
"sources": [
{
"path": "resources/knowledge/original/원가계산/건설공사_표준품셈/01_공통부문/제8장_건설기계.md",
@@ -3177,7 +3177,7 @@
},
{
"machine_code": "8201-0100",
"machine_name": "",
"machine_name": "3D GNSS 머신 가이던스(굴착기용)",
"price_thousand_krw": 55000,
"specification": "3D GNSS MG"
},
@@ -11565,7 +11565,7 @@
},
{
"machine_code": "8201-0100",
"machine_name": "배관파이프시 간 당(10-7)분류번호규 격7995-0050ø50-2.6m5,0008-3-9 [80]스마트 건설장비(8201) 3D GNSS 머신 가이던스(굴착기용)",
"machine_name": "3D GNSS 머신 가이던스(굴착기용)",
"specification": "3D GNSS MG",
"loss_coefficient_per_hour": 0.00039299999999999996,
"depreciation_coefficient_1e_minus_7": 1800,
@@ -11,16 +11,6 @@
],
"labor": {},
"machine": {
"가격 8201-0100 machine_name": {
"source": "3D GNSS 머신 가이던스(굴착기용)",
"data": "",
"why": "자료 이름이 빔 — 원문 손료표 머리 `(코드) 이름` 엔 이름 있음",
"class": "㉯ 남이 고쳐야 풀림",
"owner": "랩탑 메인",
"owner_ask": "기계 원천 벌(mach_base_2026.json) 재추출 — 그 추출기가 이 저장소에 없어 우리가 못 돌림",
"갈래": "잣대 못 알아봄",
"class_why": "제8장 8-3 (8201) 손료표 머리가 글로 뭉개져 기종 이름이 표 밖 글줄에 섞였음 — PDF 대조로 표 머리를 되살려 줄 것. 되살리면 추출기·시험이 저절로 읽음."
},
"셈 연료 원문에 없는 자료 줄": {
"source": 0,
"data": 1,
@@ -41,46 +31,6 @@
"갈래": "잣대 못 알아봄",
"class_why": "위 7993-0020 과 한 자리 — 그 줄이 고쳐지면 셈도 맞음."
},
"손료 8201-0100 machine_name": {
"source": "3D GNSS 머신 가이던스(굴착기용)",
"data": "배관파이프시 간 당(10-7)분류번호규 격7995-0050ø50-2.6m5,0008-3-9 [80]스마트 건설장비(8201) 3D GNSS 머신 가이던스(굴착기용)",
"why": "자료 이름 칸에 원문 뭉개진 손료 글이 통째로 들어감 — 자료 이름 틀림",
"class": "㉯ 남이 고쳐야 풀림",
"owner": "랩탑 메인",
"owner_ask": "기계 원천 벌(mach_base_2026.json) 재추출 — 그 추출기가 이 저장소에 없어 우리가 못 돌림",
"갈래": "잣대 못 알아봄",
"class_why": "위와 한 자리 — 뭉개진 글이 자료 이름 칸에 통째로 들어갔음."
},
"손료보정 bulldozer_under_19_ton": {
"source": "불도저(19톤이상제외)",
"data": "2510",
"why": "원문 8-1-7 가산비율 표가 글로 뭉개져 이름 칸에 수(`2510`)가 들어앉음 — 25·10 인지 2·510 인지 못 가름(같은 자리를 「손료보정 수 자리 가름」으로 이미 적어 둠). md 가 풀리면 저절로 맞음",
"class": "㉯ 남이 고쳐야 풀림",
"갈래": "잣대 못 알아봄",
"class_why": "원문 8-1-7 가산비율 표가 **글로 뭉개져** 기종 이름 칸에 수(`2510`)가 들어앉음 — 25·10 인지 2·510 인지 가를 수 없음. 값은 원문에 있으나 꼴이 뭉개져 못 읽는 자리(같은 뿌리를 「손료보정 수 자리 가름」 으로도 셈).",
"owner": "랩탑 메인",
"owner_ask": "8-1-7 가산비율 표가 글로 뭉개진 자리 — 기종 이름과 두 수(암석작업·전석섞인토사)를 갈라 줄 것. `손료보정 수 자리 가름` 과 한 자리라 함께 풀림."
},
"손료보정 crawler_excavator_or_loader": {
"source": "굴착기(무한궤도)및로더(무한궤도)",
"data": "2010",
"why": "원문 8-1-7 가산비율 표가 글로 뭉개져 이름 칸에 수(`2510`)가 들어앉음 — 25·10 인지 2·510 인지 못 가름(같은 자리를 「손료보정 수 자리 가름」으로 이미 적어 둠). md 가 풀리면 저절로 맞음",
"class": "㉯ 남이 고쳐야 풀림",
"갈래": "잣대 못 알아봄",
"class_why": "원문 8-1-7 가산비율 표가 **글로 뭉개져** 기종 이름 칸에 수(`2510`)가 들어앉음 — 25·10 인지 2·510 인지 가를 수 없음. 값은 원문에 있으나 꼴이 뭉개져 못 읽는 자리(같은 뿌리를 「손료보정 수 자리 가름」 으로도 셈).",
"owner": "랩탑 메인",
"owner_ask": "8-1-7 가산비율 표가 글로 뭉개진 자리 — 기종 이름과 두 수(암석작업·전석섞인토사)를 갈라 줄 것. `손료보정 수 자리 가름` 과 한 자리라 함께 풀림."
},
"손료보정 dump_truck": {
"source": "덤프트럭",
"data": "2510",
"why": "원문 8-1-7 가산비율 표가 글로 뭉개져 이름 칸에 수(`2510`)가 들어앉음 — 25·10 인지 2·510 인지 못 가름(같은 자리를 「손료보정 수 자리 가름」으로 이미 적어 둠). md 가 풀리면 저절로 맞음",
"class": "㉯ 남이 고쳐야 풀림",
"갈래": "잣대 못 알아봄",
"class_why": "원문 8-1-7 가산비율 표가 **글로 뭉개져** 기종 이름 칸에 수(`2510`)가 들어앉음 — 25·10 인지 2·510 인지 가를 수 없음. 값은 원문에 있으나 꼴이 뭉개져 못 읽는 자리(같은 뿌리를 「손료보정 수 자리 가름」 으로도 셈).",
"owner": "랩탑 메인",
"owner_ask": "8-1-7 가산비율 표가 글로 뭉개진 자리 — 기종 이름과 두 수(암석작업·전석섞인토사)를 갈라 줄 것. `손료보정 수 자리 가름` 과 한 자리라 함께 풀림."
},
"연료 7993-0020": {
"source": null,
"data": {
@@ -167,15 +117,6 @@
"owner": "랩탑 서브",
"owner_ask": "제8장 8-3 (7430)·(7431) 윈치 · (7811)·(7812) 엔진 · (9030) 예선 표 — PDF 에서 「기종」 칸이 한 칸에 어떻게 적혀 있는지 짚어 줄 것(「수동 싱글드럼」 처럼 한 칸인지, 줄마다 다른 이름인지). 그것이 갈리면 자료의 이어받기가 맞는지 곧 판가름남."
},
"손료보정 수 자리 가름": {
"what": "뭉개진 글의 이어 붙은 수(2510)를 통째로 맞댐",
"why": "8-1-7 가산비율 표가 글로 뭉개져 `2510` 처럼 붙음 — 25·10 인지 2·510 인지 못 가름",
"class": "㉯ 남이 고쳐야 풀림",
"owner": "랩탑 메인",
"owner_ask": "기계 원천 벌(mach_base_2026.json) 재추출 — 그 추출기가 이 저장소에 없어 우리가 못 돌림",
"갈래": "잣대 못 알아봄",
"class_why": "8-1-7 가산비율 표가 글로 뭉개져 `2510` 처럼 붙었음 — 25·10 인지 2·510 인지 PDF 로 갈라 줄 것."
},
"운전사 기종별 판단": {
"what": 121,
"why": "기종→운전사 구분(explicit_mappings)은 사람 판단 — 원문 8-1-3 5호는 기종 이름 글만 있음",
@@ -554,7 +495,7 @@
"센 날": "2026-09-18",
"셈": {
"원문 부재 / ㉰": 7,
"잣대 못 알아봄 / ㉯": 16
"잣대 못 알아봄 / ㉯": 10
},
"잣대": "두 갈래로 가름 — **원문 부재**: 값이 원문·원천에 없거나 원문 밖 말이라 채울 것이 없음 · **잣대 못 알아봄**: 값은 원문에 있는데 전사·읽기가 못 가름 ⇒ 고치면 풀림. ㉮ 영영 할 일 아님 · ㉯ 남이 고쳐야 풀림 · ㉰ 사용자·브레인 판단.",
"할 일 아님으로 뺀 줄": 34
@@ -343,7 +343,8 @@ def read_machine(path: Path) -> dict:
return out
#: 손료보정 표는 글로 뭉개짐 — 기종 글 바로 뒤에 `암석작업` `전석섞인토사` 두 수가 붙어 있음(`덤프트럭2510`).
#: 8-1-7 손료보정 표의 기종 글 — 원문이 낱자 사이에 빈칸을 두므로(`불도저( 1 9 톤이상 제외)`) **빈칸을 지워** 맞댄다.
#: 2026-09-18 md 가 제 표로 고쳐져 두 수(암석작업·전석섞인토사)를 **칸에서 바로** 읽는다(전엔 `덤프트럭2510` 처럼 글로 뭉개져 있었다).
ROCK = {"bulldozer_under_19_ton": "불도저(19톤이상제외)", "crawler_excavator_or_loader": "굴착기(무한궤도)및로더(무한궤도)", "dump_truck": "덤프트럭"} # fmt: skip
@@ -379,6 +380,11 @@ def check_machine(src: dict, bad: dict, blind: dict) -> dict:
counts[f"{label} 코드 수"] = (len(rows), len(by_code))
counts[f"{label} 원문에 없는 자료 줄"] = (0, len(set(by_code) - set(rows) - unread))
_unchecked(blind, var, records, {"machine_code", "machine_name", *fields})
# 원문 8-3 에 `(코드) 이름` 머리글이 아예 없어 **이름이 빈** 기종 — 양쪽이 다 비어 조용히 지나간다.
# 표는 있는데 이름만 없는 자리라 세어 둔다(md 에 머리글이 붙으면 저절로 빠짐).
for code, rec in sorted({r["machine_code"]: r for r in v["mach_loss_coef"]["records"]}.items()):
if not rec.get("machine_name") and source["names"].get(code[:4]) is None:
blind[f"기종 이름 없는 머리글 {code[:4]}"] = {"what": code}
# 원문이 규격 칸을 비워 둔 자리에 **자료가 앞 줄 값을 이어받아 채운** 줄 — 원문에 없는 글이라 사람이 볼 자리.
loss_by_code = {r["machine_code"]: r for r in v["mach_loss_coef"]["records"]}
for code in sorted(source.get("spec_blank", ())):
@@ -412,15 +418,24 @@ def check_machine(src: dict, bad: dict, blind: dict) -> dict:
)
text = _t("".join(source["lines"]))
rock_rows = {} # 기종 글(빈칸 지움) → (암석작업, 전석섞인토사)
for t in md_tables(source["lines"]):
if [_t(h) for h in t["headers"]][:2] != ["기종", "가산비율"]:
continue
for r in t["rows"]:
try:
rock_rows[_t(r[0])] = (_num(r[1]), _num(r[2]))
except ValueError: # 머리 곁줄(`암석작업(연암…)` · `전석섞인토사`)
continue
for rule in v["mach_rock_adj"]["rules"]:
group, pair = rule["machine_group"], f"{rule['rock_work']}{rule['boulder_mixed_soil']}"
group = rule["machine_group"]
pair = (rule["rock_work"], rule["boulder_mixed_soil"])
if group in ROCK:
ok = f"{ROCK[group]}{pair}" in text
else:
ok = pair == "00" and "불도저(19톤이상)의경우는보정하지않는다" in text
if not ok:
bad[f"손료보정 {group}"] = {"source": ROCK.get(group), "data": pair}
blind["손료보정 수 자리 가름"] = {"what": "뭉개진 글의 이어 붙은 수(2510)를 통째로 맞댐"}
got = rock_rows.get(ROCK[group])
else: # 「불도저(19톤이상)은 보정하지 않는다」 — 표에 줄이 없고 [주]①이 0 을 말한다
got = (0, 0) if "불도저(19톤이상)의경우는보정하지않는다" in text else None
if got != pair:
bad[f"손료보정 {group}"] = {"source": got, "data": list(pair)}
_unchecked(blind, "mach_rock_adj.rules", v["mach_rock_adj"]["rules"], {"machine_group", "rock_work", "boulder_mixed_soil"}) # fmt: skip
return counts