diff --git a/resources/master_data/scripts/build_기계_건설품셈.py b/resources/master_data/scripts/build_기계_건설품셈.py
new file mode 100644
index 00000000..dd2fc3da
--- /dev/null
+++ b/resources/master_data/scripts/build_기계_건설품셈.py
@@ -0,0 +1,639 @@
+"""건설품셈 공통 제8장 8-3(손료) · 8-4(운전경비) · 8-5(기계가격) 표 → resources/master_data/기계_건설품셈.json
+
+본문 md(확정) 표를 읽어 기계 한 줄로 합침. 값은 md 에서만 · 계산·추정 없음.
+실행: python resources/master_data/scripts/build_기계_건설품셈.py [--dry]
+"""
+
+import json
+import re
+import sys
+from pathlib import Path
+
+REPO = Path(__file__).resolve().parents[3]
+DIR = (
+ REPO
+ / "resources/knowledge/original/원가계산/건설공사_표준품셈/본문/01_공통부문/제08장_건설기계"
+)
+OUT = REPO / "resources/master_data/기계_건설품셈.json"
+SRC = "건설품셈 공통 "
+NUM = re.compile(r"^-?\d+(?:,\d{3})*(?:\.\d+)?$")
+
+# ---------------------------------------------------------------- md 읽기
+
+
+def cell_join(s, name=False):
+ """
줄바꿈 이음. 숫자·연산자 사이는 붙이고 그 밖은 한 칸."""
+ parts = s.split("
")
+ out = parts[0].strip()
+ for p in parts[1:]:
+ p = p.strip()
+ if not out or not p:
+ out += p
+ continue
+ a, b = out[-1], p[0]
+ if (a.isdigit() and b.isdigit()) or b in "×)" or a in "(×∼~" or (name and b == "("):
+ out += p
+ else:
+ out += " " + p
+ return out
+
+
+def read_md(path):
+ """표 목록 [{h3, h4, rows, notes}] — rows 는 구분줄 뺀 셀 목록."""
+ L = path.read_text(encoding="utf8").split("\n")
+ L = L[L.index("---", 1) + 1 :]
+ tabs, h3, h4, i = [], "", "", 0
+ while i < len(L):
+ ln = L[i]
+ if ln.startswith("### "):
+ h3, h4 = ln[4:].strip(), ""
+ elif ln.startswith("#### "):
+ h4 = ln[5:].strip()
+ elif ln.startswith("|"):
+ rows = []
+ while i < len(L) and L[i].startswith("|"):
+ if not re.match(r"^\|[\s\-|:]+\|$", L[i]):
+ rows.append([c.strip() for c in L[i].strip()[1:-1].split("|")])
+ i += 1
+ notes, comments = [], []
+ while i < len(L) and not L[i].startswith(("#", "|")):
+ t = L[i].strip()
+ if t.startswith("