diff --git a/resources/master_data/scripts/_토목_계수.py b/resources/master_data/scripts/_토목_계수.py new file mode 100644 index 00000000..18c9965f --- /dev/null +++ b/resources/master_data/scripts/_토목_계수.py @@ -0,0 +1,99 @@ +# -*- coding: utf-8 -*- +"""글 속 계산용 수 → 그룹 계수 표 (build_소요량_건설품셈_토목.py 가 씀). + +표에 붙은 주(①…)와 비고 글에서 정규식으로 수를 읽음(손으로 적지 않음). 주 한 줄마다 계수 표 하나. +""" + +import re + +from _md표_읽기 import R +from _토목_표 import conds_out + +PCT = r"([\d.]+)%" + + +def clean(line): + return re.sub(r"^(비고\s*-?\s*|-\s*|[①-⑳]\s*)", "", line).strip() + + +def rows_of(line): + """주 한 줄 → 계수 줄들 [(구분, 값, 단위, 딸린 칸)].""" + s = clean(line) + out = [] + m = re.search(r"^(.+?)(?:는|은) (인력품|주재료비|재료비)의 " + PCT, s) + if m: + out.append((m.group(1), R(m.group(3)), "%", {"기준": m.group(2)})) + m2 = re.search(r"(블록 정밀절단\(전동절단기\)에 의한 시공이 아닌 경우) " + PCT, s) + if m2: + out.append((f"{m.group(1)} — {m2.group(1)}", R(m2.group(2)), "%", {"기준": m.group(2)})) + return out + m = re.search( + r"(시공량|본 품|크레인\(시간\))(?:의|을) " + PCT + r"(?:를|까지| 범위 내에서) ?(감|가산)", s + ) + if m: + out.append((s, R(m.group(2)), "%", {"기준": m.group(1), "가감": m.group(3)})) + m = re.search(r"(특별인부) (\d+)인을 감", s) + if m: + out.append((s, R(m.group(2)), "인", {"기준": m.group(1), "가감": "감"})) + m = re.search(r"절단품은 본 품의 " + PCT + r", 선단가공\(Beveling\) 품은 본 품의 " + PCT, s) + if m: + out.append(("금긋기 및 절단품", R(m.group(1)), "%", {"기준": "본 품"})) + out.append(("선단가공(Beveling) 품", R(m.group(2)), "%", {"기준": "본 품"})) + m = re.search(r"두께 ([\d.]+)㎝일 때 ([\d,]+)㎡당 살수량은 일반적으로 ([\d.]+)ton", s) + if m: + out.append(("살수량 기준 두께", R(m.group(1)), "㎝", {})) + out.append(("살수량 기준 면적", R(m.group(2)), "㎡", {})) + out.append(("살수량", R(m.group(3)), "ton", {})) + m = re.search(r"보통인부 (\d+)인기준 두께 (\d+)㎝시 ([\d,]+)㎡, 두께 (\d+)㎝시 ([\d,]+)㎡", s) + if m: + out.append(("모래 부설 일당시공량 기준 인원", R(m.group(1)), "인", {"기준": "보통인부"})) + out.append((f"모래 부설 일당시공량 두께 {m.group(2)}㎝", R(m.group(3)), "㎡", {})) + out.append((f"모래 부설 일당시공량 두께 {m.group(4)}㎝", R(m.group(5)), "㎡", {})) + m = re.search(r"굴착기\(규격 ([\d.]+)㎥, 사용시간 ([\d.]+)hr\)", s) + if m: + out.append(("크레인 대체 굴착기 규격", R(m.group(1)), "㎥", {})) + out.append(("크레인 대체 굴착기 사용시간", R(m.group(2)), "hr", {})) + m = re.search(r"호칭압력 ([\d.]+)(㎏/㎠)를 기준", s) + if m: + out.append(("호칭압력 기준", R(m.group(1)), m.group(2), {})) + m = re.search(r"강관장 ([\d.]+)m를 기준", s) + if m: + out.append(("강관장 기준", R(m.group(1)), "m", {})) + return out + + +def lift(chdir, text, out): + src_tabs = list(out.tables) + for t in src_tabs: + ident = t["열쇠"].split()[0] + tags = {} + for line in t["주"]: + rs = rows_of(line) + if not rs: + continue + tag = "비고" if line.startswith("비고") else "주" + re.match(r"[①-⑳]", line).group(0) + tags.setdefault(tag, []).extend(rs) + for tag, rs in tags.items(): + cols, rows = {"값": "구분 따름", "단위": "글"}, [] + for g, v, u, extra in rs: + rows.append({"구분": g, "값": v, "단위": u, **extra}) + for k in extra: + cols.setdefault(k, "글") + label = f"{t['이름']} 계수 {tag}" + key = f"{ident} {label}" + if key in out.keys: + continue + out.keys.add(key) + out.tables.append( + { + "열쇠": key, + "이름": label, + "기준": "", + "출처": f"{t['출처']} {tag}", + "그룹": "계수", + "조건": conds_out(["구분"], rows), + "값칸": cols, + "줄": rows, + "주": [], + } + ) diff --git a/resources/master_data/scripts/_토목_손.py b/resources/master_data/scripts/_토목_손.py new file mode 100644 index 00000000..2c71ba6d --- /dev/null +++ b/resources/master_data/scripts/_토목_손.py @@ -0,0 +1,204 @@ +# -*- coding: utf-8 -*- +"""토목 표 손 지정 — 칸 안에 줄이 여럿(
)이거나 줄마다 머리가 달라 자동으로 못 읽는 표. (build_소요량_건설품셈_토목.py 가 씀)""" + +import re + +from _md표_읽기 import N, R, join +from _토목_읽기 import is_num, nz +from _토목_표 import bigo_items, conds_out, crew, jn, matrix, members, rate, tpose + +SPECIAL = {"1": {}, "2": {}, "6": {}} + + +def finish(rows, cols, bigo=(), label=None): + names = [] + for r in rows: + for k in r: + if k not in names and k not in cols: + names.append(k) + return conds_out(names, rows), cols, rows, list(bigo), label + + +def sp(ch, key): + def deco(fn): + SPECIAL[ch][key] = fn + return fn + + return deco + + +def lines(x): + return [p.strip() for p in x.split("
")] + + +# ── 제1장 ────────────────────────────────────────────────────── +sp("1", "1-8-9#4")(lambda t: (*tpose(t, "트럭", "작업"), None)) +sp("1", "1-8-2#2")(lambda t: (*tpose(t, "형식", "크레인"), None)) +sp("1", "1-8-14#2")(lambda t: (*rate(t), None)) + + +@sp("1", "1-9-1#1") +def _(t): + R0, rows, cols = t.rows, [], {"시공량": "개소"} + heads = R0[2][5:8] + for lo, hi in ((3, 6), (6, 9)): + blk = R0[lo:hi] + c_, v = members(blk, 2, 1, 3) + cols.update(c_) + hts = lines(blk[0][4]) + for j, hd in enumerate(heads): + vs = lines(blk[0][5 + j]) + for i, hv in enumerate(hts): + if nz(vs[i]): + rows.append({"지주높이": hv, "지주 간격": hd, "시공량": N(vs[i]), **v}) + return finish(rows, cols) + + +@sp("1", "1-9-1#2") +def _(t): + R0, rows, cols = t.rows, [], {"시공량": "개"} + heads = [jn(x) for x in R0[2][5:9]] + for lo, hi, hrows in ((3, 6, [3]), (6, 10, [6, 7, 8, 9])): + c_, v = members(R0[lo:hi], 2, 1, 3) + cols.update(c_) + for r in hrows: + for j, hd in enumerate(heads): + if nz(R0[r][5 + j]): + rows.append( + { + "지주높이": jn(R0[r][4]), + "방음벽 개당 면적": hd, + "시공량": N(R0[r][5 + j]), + **v, + } + ) + return finish(rows, cols) + + +@sp("1", "1-9-2#0") +def _(t): + R0, rows, cols = t.rows, [], {"시공량": "m"} + for lo, hi in ((2, 5), (5, 8)): + blk = R0[lo:hi] + c_, v = members(blk, 3, 2, 4, 1) + cols.update(c_) + sizes = lines(blk[0][5]) + for j, kind in enumerate((R0[1][6], R0[1][7])): + vs = lines(blk[0][6 + j]) + for i, sz in enumerate(sizes): + rows.append( + { + "형식": jn(blk[0][0]), + "규격(아래폭+높이㎜)": sz, + "구간": kind, + "시공량": N(vs[i]), + **v, + } + ) + return finish(rows, cols) + + +@sp("1", "1-9-4#2") +def _(t): + R0, rows, notes = t.rows, [], [] + for row in R0[1:]: + if join(row[0]) == "산출기준": + notes = ["산출기준 " + x[len("비고 ") :] for x in bigo_items(row[1])] + continue + d = {"구분": jn(row[0]), "단위": row[1], "수량": N(row[2])} + if nz(row[3]): + d["비고"] = jn(row[3]) + rows.append(d) + return conds_out(["구분"], rows), {"수량": "단위 칸 따름", "비고": "글"}, rows, notes, None + + +# ── 제6장 ────────────────────────────────────────────────────── +@sp("6", "6-1-1#0") +def _(t): + rows = [] + for row in t.rows[2:]: + d = {"구분": jn(row[0]), "내용": jn(row[1])} + for nm, c in (("요율 품", 2), ("요율 시공량", 3)): + if nz(row[c]): + d[nm] = R(row[c].rstrip("%")) + rows.append(d) + return ( + conds_out(["구분"], rows), + {"내용": "글", "요율 품": "%", "요율 시공량": "%"}, + rows, + [], + None, + ) + + +def multi(t): + """칸 안에 관경 · 시공량이 줄줄이 든 [구분 단위 수량 관경 시공량] 표.""" + R0, rows, cols, bigo = t.rows, [], {}, [] + unit = re.search(r"\((.*)\)", join(R0[0][4])).group(1) + cols["시공량"] = unit + blocks = [] + for row in R0[1:]: + if join(row[0]) == "비고": + bigo += bigo_items(row[1]) + elif nz(row[3]): + blocks.append([row]) + else: + blocks[-1].append(row) + for blk in blocks: + c_, v = members(blk, 1, None, 2) + cols.update(c_) + ds, vs = lines(blk[0][3]), lines(blk[0][4]) + assert len(ds) == len(vs), (t.ident, ds, vs) + for d, x in zip(ds, vs): + rows.append({"관경(㎜)": d, "시공량": N(x), **v}) + return conds_out(["관경(㎜)"], rows), cols, rows, bigo, None + + +for k in ( + "6-2-1#0", + "6-2-1#1", + "6-2-2#0", + "6-2-2#1", + "6-3-1#0", + "6-3-1#1", + "6-4-1#0", + "6-4-2#0", + "6-5-1#0", + "6-5-2#0", + "6-5-3#0", + "6-5-4#0", + "6-6-1#0", + "6-6-2#0", +): + SPECIAL["6"][k] = multi +sp("6", "6-2-2#2")(lambda t: (*rate(t), None)) +sp("6", "6-5-4#1")(lambda t: (*rate(t), None)) +sp("6", "6-8-5#2")(lambda t: (*rate(t), None)) +sp("6", "6-8-4#2")(lambda t: (*matrix(t, "본관", "지관", "중량", "㎏", 2), None)) +sp("6", "6-7-5#0")(lambda t: (*crew(t, prefix_group=True), None)) + + +@sp("6", "6-3-2#0") +def _(t): + R0, rows, cols = t.rows, [], {"시공량": "개소", "용접공": "인"} + heads = [ + (jn(R0[0][4]), jn(R0[1][4]), jn(R0[2][4])), + (None, jn(R0[1][4]), jn(R0[2][5])), + (None, jn(R0[1][6]), jn(R0[2][6])), + ] + combos = [(4, "A종", "겹치기 용접"), (5, "A종", "베벨엔드용접"), (6, "B종", "겹치기 용접")] + for r in R0[3:]: + if nz(r[0]): + qty = N(r[2]) + for c, a, b in combos: + if nz(r[c]): + rows.append( + { + "관경(㎜)": jn(r[3]), + "종별": a, + "용접방법": b, + "시공량": N(r[c]), + "용접공": qty, + } + ) + return conds_out(["관경(㎜)", "종별", "용접방법"], rows), cols, rows, [], None diff --git a/resources/master_data/scripts/_토목_읽기.py b/resources/master_data/scripts/_토목_읽기.py new file mode 100644 index 00000000..594cb8ea --- /dev/null +++ b/resources/master_data/scripts/_토목_읽기.py @@ -0,0 +1,194 @@ +# -*- coding: utf-8 -*- +"""건설품셈 토목 제1 · 2 · 6장 md 읽기 · 표 만들기 · 쓰기 도구 (build_소요량_건설품셈_토목.py 가 씀). + +값은 md 에서 읽음(손으로 적지 않음). 표 모양 다섯 갈래: 인력·장비 편성표(crew) · 줄이 관경인 납작표(flat) · +요율 · 글 표 · 손 지정. +""" + +import json +import re +from pathlib import Path + +from _md표_읽기 import N, R, Raw, join, js, unesc + +ROOT = Path(__file__).resolve().parents[3] +BASE = ROOT / "resources/knowledge/original/원가계산/건설공사_표준품셈/본문/02_토목부문" +DIV = "건설품셈 토목 " +NUM = r"\d[\d,]*(?:\.\d+)?" +UNITS = "인 hr 시간 ㎜ ㎡ ㎥ m % 대 회 식 일 본 개소 개 ㎏ kg ton 톤 m/일".split() +ROLE = re.compile(r"^(수량|시공량|소요일수)") + + +class Tab: + def __init__(self, ident, title, mok, base, rows, k): + self.ident, self.title, self.mok, self.base, self.rows, self.k = ( + ident, + title, + mok, + base, + rows, + k, + ) + self.notes: list[str] = [] + self.pre = "" # 표 앞 글줄(별표 제목 등) + + +def cells(line): + return [c.strip() for c in line.strip().strip("|").split("|")] + + +def title_of(s): + return re.sub(r"\s*\((?:['‘’]\d\d[^)]*)\)\s*$", "", s).strip() + + +def read_chapter(chdir): + """장 폴더의 항 · 표 · 주 를 차례로 읽음 → (표 목록, 항 본문 줄 사전).""" + tabs, text, order = [], {}, [] + for p in sorted((next(BASE.glob(chdir + "*"))).glob("*.md")): + L = p.read_text(encoding="utf-8").split("\n") + at = [i for i, x in enumerate(L) if x.strip() == "---"] + L = L[at[1] + 1 :] + ident = title = mok = base = pre = "" + k = 0 + in_note = False + i = 0 + while i < len(L): + ln = L[i].rstrip() + m = re.match(r"^(#{2,4})\s+(.*)$", ln) + if m: + in_note = False + d = len(m.group(1)) + if d == 3: + mm = re.match(r"^(\d+-\d+-\d+)\s+(.*)$", m.group(2)) + ident, title, mok, k = mm.group(1), title_of(mm.group(2)), "", 0 + order.append(ident) + text[ident] = [] + elif d == 4: + mk = re.match(r"^(\d+)\.\s*(.*)$", m.group(2)) + mok = title_of(mk.group(2)) if mk else title_of(m.group(2)) + i += 1 + continue + if not ident: + i += 1 + continue + text[ident].append(ln) + if ln.startswith("|"): + j = i + rows = [] + while j < len(L) and L[j].startswith("|"): + c = cells(L[j]) + if not all(re.fullmatch(r":?-+:?", x) for x in c): + rows.append(c) + j += 1 + b = base if re.fullmatch(r"\(.*당\)|\(m/일\)|\(.*\)", base) else "" + tabs.append(Tab(ident, title, mok, base[1:-1] if b else "", rows, k)) + tabs[-1].pre = pre + k += 1 + base = pre = "" + in_note = False + i = j + continue + s = ln.strip() + if s == "[주]": + in_note = True + elif in_note and s and not s.startswith(("