diff --git a/resources/master_data/scripts/_설비_계수.py b/resources/master_data/scripts/_설비_계수.py new file mode 100644 index 00000000..7ac1d926 --- /dev/null +++ b/resources/master_data/scripts/_설비_계수.py @@ -0,0 +1,179 @@ +# -*- coding: utf-8 -*- +"""기계설비 글 속 계산용 수 → 계수 표 줄 (토목 규칙 + 기계설비 규칙). + +주 한 줄 → [(구분, 값, 단위, 딸린 칸)]. 식 값은 값 칸에 식 글자 + 딸린 칸 「값원문」. +""" + +import re + +from _md표_읽기 import R +from _토목_계수 import clean +from _토목_계수 import rows_of as base_rows + +PCT = r"([\d.]+)%" +BASE = {"기준": "본 품"} + + +def pct(name, v, sign, **more): + return (name, R(v), "%", {**BASE, "가감": sign, **more}) + + +def formulas(s): + out = [] + m = re.search(r"(보온공 품) = (\([^ ]+\)×V/C)", s) + if m: + out.append((m.group(1), m.group(2), "", {"값원문": m.group(0)})) + m = re.search(r"(특별인부 품) = (보온공 품×[\d.]+)", s) + if m: + out.append((m.group(1), m.group(2), "", {"값원문": m.group(0)})) + m = re.search(r"다음과 같이 계산한다\. (π×Tank직경×\(Tank철판단수-1\))", s) + if m: + out.append(("원주방향 용접장", m.group(1), "", {"값원문": m.group(1)})) + return out + + +def specific(s): + out = [] + for k, v in re.findall(r"(\d+)(?:㎜)? : ([\d.]+)(?= |$)", s) if "환산기준" in s else []: + out.append((f"6㎜ 철판 환산기준 두께 {k}㎜", R(v), "", {})) + m = re.search( + r"수동용접 : " + + PCT + + r"\(공장가공\), " + + PCT + + r"\(현장가공\) 자동용접 : " + + PCT + + r"\(공장가공\), " + + PCT + + r"\(현장가공\)", + s, + ) + if m: + for nm, v in zip( + ("수동용접 공장가공", "수동용접 현장가공", "자동용접 공장가공", "자동용접 현장가공"), + m.groups(), + ): + out.append((f"작업효율 {nm}", R(v), "%", {})) + m = re.search( + r"전체배관 설치중량의 " + + PCT + + r"로 간주하여 배관하는 품으로 " + + PCT + + r" 증감할 때마다 본 품에 " + + PCT + + r"씩 가감", + s, + ) + if m: + out += [ + ("Fitting류 등 중량 비율", R(m.group(1)), "%", {"기준": "전체배관 설치중량"}), + ("증감 단위", R(m.group(2)), "%", {"기준": "전체배관 설치중량"}), + ("증감 단위당 가감", R(m.group(3)), "%", BASE), + ] + m2 = re.search(r"본 품에 " + PCT + r" 범위 내에서 감하여", s) + if m2: + out.append(pct("공장제작조립 시 감", m2.group(1), "감")) + m = re.search(r"옥내배관의 " + PCT + r"가산, 옥외매설관은 옥외배관의 " + PCT + r" 감", s) + if m: + out += [ + ("기계실배관", R(m.group(1)), "%", {"기준": "옥내배관", "가감": "가산"}), + ("옥외매설관", R(m.group(2)), "%", {"기준": "옥외배관", "가감": "감"}), + ] + m = re.search( + r"조도가 (\d+#)를 기준한 것으로 (\d+#)까지 " + PCT + r", (\d+#)까지 " + PCT + r"를 감", s + ) + if m: + out += [ + ("조도 기준", R(m.group(1)[:-1]), "#", {}), + (f"조도 {m.group(2)}까지", R(m.group(3)), "%", {**BASE, "가감": "감"}), + (f"조도 {m.group(4)}까지", R(m.group(5)), "%", {**BASE, "가감": "감"}), + ] + m = re.search(r"Gas Heating의 경우 개소당 ([\d.]+)인", s) + if m: + out.append(("Gas Heating 개소당", R(m.group(1)), "인", {})) + m = re.search(r"기름소모량은 (\d+)개소당 ([\d.]+)ℓ", s) + if m: + out.append((f"기름소모량 {m.group(1)}개소당", R(m.group(2)), "ℓ", {})) + m = re.search(r"가루비누는 ㎥당 ([\d.]+)㎏", s) + if m: + out.append(("누설 Check용 가루비누", R(m.group(1)), "㎏/㎥", {})) + m = re.search(r"1매를 ([\d.]+)㎡", s) + if m: + out.append(("3'×6' Sheet 1매", R(m.group(1)), "㎡", {})) + m = re.search(r"Pipe길이 1m에 (\d+)회 감는", s) + if m: + out.append(("철선 Pipe길이 1m당 감는 횟수", R(m.group(1)), "회", {})) + m = re.search(r"구멍지름이 (\d+)㎜이하일 때는 철골공 (\d+)인, (\d+)㎜ 이상일 때는 (\d+)인", s) + if m: + out += [ + (f"송곳뚫기 구멍지름 {m.group(1)}㎜이하 철골공", R(m.group(2)), "인", {}), + (f"송곳뚫기 구멍지름 {m.group(3)}㎜ 이상 철골공", R(m.group(4)), "인", {}), + ] + return out + + +def clauses(s): + """「… N% 가산」 꼴 절마다 한 줄 — ㉮㉯㉰ 로 갈린 것은 절별.""" + out = [] + parts = re.split(r"\s*[㉮-㉳]\s*", s) if re.search(r"[㉮-㉳]", s) else [s] + for p in parts: + rg = re.search(r"(\d+)∼(\d+)%(?:를|까지| 범위 내에서|씩)? ?(?:을 )?(가산|감|가감|증감)", p) + if rg: + out.append( + ( + re.sub(r"\s+", " ", p).strip(" ."), + [R(rg.group(1)), R(rg.group(2))], + "%", + {**BASE, "가감": rg.group(3)}, + ) + ) + continue + found = re.findall( + r"(\d[\d.]*)%(?:를|까지| 범위 내에서|씩)? ?(?:을 )?(가산|감|가감|증감)", p + ) + if not found: + continue + name = re.sub(r"\s+", " ", p).strip(" .") + for v, sign in found[:1]: + out.append((name, R(v), "%", {**BASE, "가감": sign})) + return out + + +def rows_of(line): + s = clean(line) + out = base_rows(re.sub(r"(인력품|주재료비|재료비)의\([^)]*\) ", r"\1의 ", line)) + if out: + return out + out = formulas(s) + specific(s) + if out: + return out + m = re.search(r"^(.+?)(?:은|는) 본 품에 " + PCT + r"를 (가산|감)", s) + if m: + return [pct(m.group(1), m.group(2), m.group(3))] + m = re.search( + r"(\d+)본 감할 때마다 " + PCT + r"씩 감하고, (\d+)본 증할 때마다 " + PCT + r"씩 가산", s + ) + if m: + return [ + pct(f"튜브 {m.group(1)}본 감할 때마다", m.group(2), "감"), + pct(f"튜브 {m.group(3)}본 증할 때마다", m.group(4), "가산"), + ] + m = re.search( + r"(높이 6∼9m까지는) 품을 " + + PCT + + r" 가산하고 (높이 9m를 초과하는 경우 매 3m 증가마다) 품을 " + + PCT, + s, + ) + if m: + return [ + pct(f"비계 사용 {m.group(1)}", m.group(2), "가산"), + pct(f"비계 사용 {m.group(3)}", m.group(4), "가산"), + ] + m = re.search(r"단독주택 (\d+)호당 1조 및 집단아파트 (\d+)호당 1조", s) + if m: + return [ + ("단독주택 1조당 호수", R(m.group(1)), "호", {}), + ("집단아파트 1조당 호수", R(m.group(2)), "호", {}), + ] + return clauses(s) diff --git a/resources/master_data/scripts/_설비_손.py b/resources/master_data/scripts/_설비_손.py new file mode 100644 index 00000000..a0ca2a98 --- /dev/null +++ b/resources/master_data/scripts/_설비_손.py @@ -0,0 +1,12 @@ +# -*- coding: utf-8 -*- +"""기계설비 표별 지정 — 「항#순번」: convert 옵션(H 머리 줄 수 · L 라벨 칸 수 · M 직종 칸 · -1 = 직종 칸 없음).""" + +SPEC = { + "9-5-1#0": {"L": 3}, + "13-9-2#0": {"L": 0}, + # 13-1-2 : 「구분」 「SCH No」 「직종」 은 층 이름(둘째 칸) · 오른쪽 표는 앞 표 오른쪽(구경 칸 없이 윗줄부터) + "13-1-2#0": {"pair": True, "L": 2, "LN": 1, "NAMES": {0: "구경(㎜)"}}, + "13-1-2#2": {"L": 2, "LN": 1, "NAMES": {0: "구경(㎜)"}}, + # 13-1-1 : PDF 가 넓은 표를 왼쪽 · 오른쪽 둘로 나눠 찍음 — 줄마다 이어 한 표로 + **{f"13-1-1#{k}": {"pair": True} for k in (0, 2, 4, 6, 8)}, +} diff --git a/resources/master_data/scripts/_설비_읽기.py b/resources/master_data/scripts/_설비_읽기.py new file mode 100644 index 00000000..05b565f8 --- /dev/null +++ b/resources/master_data/scripts/_설비_읽기.py @@ -0,0 +1,131 @@ +# -*- coding: utf-8 -*- +"""건설품셈 기계설비 제7~13장 md 읽기 (build_소요량_건설품셈_기계설비.py 가 씀). + +항(###) · 목(####) · 표 · 표 머리 깊이(주석) · 표 앞 글 · [주] [비고] [계산예] 글을 읽음. +""" + +import re + +from _md표_읽기 import unesc +from _토목_읽기 import ROOT + +BASE = ROOT / "resources/knowledge/original/원가계산/건설공사_표준품셈/본문/04_기계설비부문" +DIV = "건설품셈 기계설비 " +NOTE0 = re.compile(r"^[①-⑳]") +BLOCK = re.compile(r"^\[(주|비고|계산예|참고|참고자료|별표)\]\s*(.*)$") + + +class Tab: + def __init__(self, ident, title, mok, base, rows, k, hdr, pre): + self.ident, self.title, self.mok, self.base = ident, title, mok, base + self.rows, self.k, self.hdr, self.pre = rows, k, hdr, pre + self.notes: list[str] = [] + + +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 clean(ln): + ln = re.sub(r"", "", ln).rstrip() + return re.sub(r"^>\s?", "", ln) + + +def read_chapter(chdir): + """장 폴더 → 표 목록 (표마다 항 · 목 · 기준 · 머리 깊이 · 앞 글 · 주).""" + tabs = [] + for p in sorted(next(BASE.glob(chdir + "*")).glob("*.md")): + L = p.read_text(encoding="utf-8").split("\n") + L = L[[i for i, x in enumerate(L) if x.strip() == "---"][1] + 1 :] + ident = title = mok = base = "" + pre: list[str] = [] + k, mode, i = 0, "", 0 + while i < len(L): + raw = L[i] + ln = clean(raw) + m = re.match(r"^(#{2,4})\s+(.*)$", ln) + if m: + mode, base, pre = "", "", [] + d = len(m.group(1)) + if d == 2: + mm = re.match(r"^(\d+-\d+)\s+(.*)$", m.group(2)) + ident, title, mok, k = mm.group(1), title_of(mm.group(2)), "", 0 + elif 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 + elif d == 4: + mk = re.match(r"^(?:\d+|[가-힣])\.\s*(.*)$", m.group(2)) + mok = title_of(mk.group(1) if mk else m.group(2)) + i += 1 + continue + if not ident: + i += 1 + continue + if ln.startswith("|"): + j, rows = i, [] + 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 + hint = "" + while j < len(L) and (L[j].startswith("