feat(master_data): 건설 기계설비 7장 소요량 옮김
- 소요량_건설품셈_기계설비7장.json (본문 표 18 + 계수 표 17) - 변환 스크립트 build_소요량_건설품셈_기계설비.py · _설비_읽기 · _설비_표 · _설비_계수 · _설비_손 추가 - _토목_계수.lift 에 규칙 함수 인자 추가 Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PAdA5ThqmtVSsbzjusk1cJ
This commit is contained in:
@@ -0,0 +1,67 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""기계설비 글 속 계산용 수 → 계수 표 줄 (토목 규칙 + 기계설비 규칙)."""
|
||||
|
||||
import re
|
||||
|
||||
from _md표_읽기 import R
|
||||
from _토목_계수 import clean
|
||||
from _토목_계수 import rows_of as base_rows
|
||||
|
||||
PCT = r"([\d.]+)%"
|
||||
|
||||
|
||||
def rows_of(line):
|
||||
"""주 한 줄 → 계수 줄들 [(구분, 값, 단위, 딸린 칸)]."""
|
||||
s = clean(line)
|
||||
out = base_rows(re.sub(r"(인력품|주재료비|재료비)의\([^)]*\) ", r"\1의 ", line))
|
||||
if out:
|
||||
return out
|
||||
m = re.search(r"^(.+?)(?:은|는) 본 품에 " + PCT + r"를 (가산|감)", s)
|
||||
if m:
|
||||
return [(m.group(1), R(m.group(2)), "%", {"기준": "본 품", "가감": m.group(3)})]
|
||||
m = re.search(
|
||||
r"(\d+)본 감할 때마다 " + PCT + r"씩 감하고, (\d+)본 증할 때마다 " + PCT + r"씩 가산", s
|
||||
)
|
||||
if m:
|
||||
return [
|
||||
(
|
||||
f"튜브 {m.group(1)}본 감할 때마다",
|
||||
R(m.group(2)),
|
||||
"%",
|
||||
{"기준": "본 품", "가감": "감"},
|
||||
),
|
||||
(
|
||||
f"튜브 {m.group(3)}본 증할 때마다",
|
||||
R(m.group(4)),
|
||||
"%",
|
||||
{"기준": "본 품", "가감": "가산"},
|
||||
),
|
||||
]
|
||||
m = re.search(r"^(.+? 3절 초과하는 경우 매 1절 증가마다) " + PCT + r"씩 가산", s)
|
||||
if m:
|
||||
return [(m.group(1), R(m.group(2)), "%", {"기준": "본 품", "가감": "가산"})]
|
||||
m = re.search(r"^(.+?(?:증가마다)) " + PCT + r"씩 가산", s)
|
||||
if m:
|
||||
return [(m.group(1), R(m.group(2)), "%", {"기준": "본 품", "가감": "가산"})]
|
||||
m = re.search(r"^(.+?구조일 경우) " + PCT + r" 가산", s)
|
||||
if m:
|
||||
return [(m.group(1), R(m.group(2)), "%", {"기준": "본 품", "가감": "가산"})]
|
||||
m = re.search(
|
||||
r"(높이 6∼9m까지는) 품을 "
|
||||
+ PCT
|
||||
+ r" 가산하고 (높이 9m를 초과하는 경우 매 3m 증가마다) 품을 "
|
||||
+ PCT,
|
||||
s,
|
||||
)
|
||||
if m:
|
||||
return [
|
||||
(f"비계 사용 {m.group(1)}", R(m.group(2)), "%", {"기준": "본 품", "가감": "가산"}),
|
||||
(f"비계 사용 {m.group(3)}", R(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 []
|
||||
@@ -0,0 +1,6 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""기계설비 표별 지정 — 「항#순번」: convert 옵션(H 머리 줄 수 · L 라벨 칸 수 · M 직종 칸 · -1 = 직종 칸 없음)."""
|
||||
|
||||
SPEC = {
|
||||
"9-5-1#0": {"L": 3},
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
# -*- 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("<!--") or not L[j].strip()):
|
||||
hint += L[j] if L[j].startswith("<!--") else ""
|
||||
if not L[j].strip() and not (j + 1 < len(L) and L[j + 1].startswith("<!--")):
|
||||
break
|
||||
j += 1
|
||||
mh = re.search(r"몸 (\d+)행까지 머리", hint)
|
||||
b = base[1:-1] if base else ""
|
||||
tabs.append(
|
||||
Tab(ident, title, mok, b, rows, k, 1 + int(mh.group(1)) if mh else 1, pre)
|
||||
)
|
||||
k += 1
|
||||
base, pre, mode = "", [], ""
|
||||
i = j
|
||||
continue
|
||||
s = ln.strip()
|
||||
i += 1
|
||||
if not s or s.startswith("!["):
|
||||
continue
|
||||
mb = BLOCK.match(s)
|
||||
if mb:
|
||||
kind, rest = mb.groups()
|
||||
mode = "note" if kind in ("주", "계산예", "비고") else ""
|
||||
if kind in ("참고", "참고자료", "별표"):
|
||||
pre = [s] if not rest else [f"[{kind}] {rest}"]
|
||||
mode = ""
|
||||
continue
|
||||
first = rest if kind == "주" else f"{kind} {rest}".strip()
|
||||
if first and tabs and tabs[-1].ident == ident:
|
||||
tabs[-1].notes.append(unesc(first))
|
||||
mode = "note" if kind in ("주", "계산예") else ""
|
||||
if kind == "계산예":
|
||||
mode = "calc"
|
||||
continue
|
||||
if re.fullmatch(r"\(.*\)", s) and not s.startswith("(주"):
|
||||
base, mode = s, ""
|
||||
continue
|
||||
if mode in ("note", "calc") and tabs and tabs[-1].ident == ident:
|
||||
t = tabs[-1]
|
||||
if mode == "calc":
|
||||
t.notes.append("계산예 " + unesc(s)) if not t.notes or not t.notes[
|
||||
-1
|
||||
].startswith("계산예") else t.notes.__setitem__(
|
||||
-1, t.notes[-1] + " " + unesc(s)
|
||||
)
|
||||
elif NOTE0.match(s) or not t.notes:
|
||||
t.notes.append(unesc(s))
|
||||
else:
|
||||
t.notes[-1] += " " + unesc(s)
|
||||
else:
|
||||
pre.append(s)
|
||||
return tabs
|
||||
@@ -0,0 +1,334 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""기계설비 표 만들기 — 원문 표 하나를 조건 칸 + 값 칸의 줄로 폄 (build_소요량_건설품셈_기계설비.py 가 씀).
|
||||
|
||||
표 모양은 스스로 알아냄(라벨 칸 수 · 머리 층 · 직종 위치). 못 맞추는 표는 SPEC 으로 지정.
|
||||
"""
|
||||
|
||||
import re
|
||||
|
||||
from _md표_읽기 import N, R, join
|
||||
from _토목_표 import bigo_items, conds_out, is_unit, jn
|
||||
from _토목_읽기 import cell_val, is_num
|
||||
|
||||
MEMBER = re.compile(
|
||||
r"(공|인부|기사|기능사|운전사|운전조|측량사|조공|반장|감독원|기술자|보조원|기능공|공사)$"
|
||||
)
|
||||
MEASURE = re.compile(
|
||||
r"^(수량|시공량|소요량|사용기간|사용시간|시간|소비량|소요전력|용접봉소비량|중량|작업능력|인력)"
|
||||
)
|
||||
LABELNAME = re.compile(
|
||||
r"^(구분|규격|구경|관경|호칭|외경|내경|두께|길이|용량|치수|사이즈|Pipe Size|압력|명칭|품명|장비명|"
|
||||
r"직종|공종|공정별|작업구분|종류|형식|재질|층|이름|단위|비고|배관구분|작업횟수|기간|처리)"
|
||||
)
|
||||
VALUEISH = re.compile(r"수량|시공량|소비량|소요|인력|시간|기간|작업능력")
|
||||
DASH = ("", "-")
|
||||
|
||||
|
||||
def strip_unit(name):
|
||||
"""「배관공(인)」 → (배관공, 인)."""
|
||||
m = re.search(r"\(([^()]{1,8})\)$", name)
|
||||
if m and is_unit(m.group(1)):
|
||||
return name[: m.start()].strip(), m.group(1)
|
||||
return name, ""
|
||||
|
||||
|
||||
def is_member(x):
|
||||
return bool(MEMBER.search(re.sub(r"\([^)]*\)", "", jn(x)).strip()))
|
||||
|
||||
|
||||
def nm(x):
|
||||
"""직종 이름 — 「비계 공」 처럼 줄바꿈으로 벌어진 글은 붙임."""
|
||||
x = jn(x)
|
||||
return (
|
||||
x.replace(" ", "") if re.fullmatch(r"[가-힣 ]+", x) and is_member(x.replace(" ", "")) else x
|
||||
)
|
||||
|
||||
|
||||
def cv(x):
|
||||
"""값 칸 하나 → (값, 원문). 「a∼b」 는 [아래, 위]."""
|
||||
x = jn(x)
|
||||
m = re.fullmatch(r"(\d[\d,]*(?:\.\d+)?)\s*[∼~]\s*(\d[\d,]*(?:\.\d+)?)", x)
|
||||
if m:
|
||||
return [R(m.group(1)), R(m.group(2))], None
|
||||
return cell_val(x)
|
||||
|
||||
|
||||
def vlike(x):
|
||||
x = x.strip()
|
||||
return x in DASH or is_num(x) or bool(re.fullmatch(r"[\d.,]+\s*%?", x)) or x == "〃"
|
||||
|
||||
|
||||
def halves(rows, H):
|
||||
"""왼쪽 · 오른쪽 짝 표(머리 이름이 되풀이) → 위아래로 이음."""
|
||||
hd = rows[0]
|
||||
n = len(hd)
|
||||
if n % 2 == 0 and any(hd) and all(r[: n // 2] == r[n // 2 :] for r in rows[:H]):
|
||||
left = [r[: n // 2] for r in rows]
|
||||
right = [r[: n // 2] for r in rows[:H]] + [
|
||||
r[n // 2 :] for r in rows[H:] if any(x not in DASH for x in r[n // 2 :])
|
||||
]
|
||||
return left + right[H:]
|
||||
return rows
|
||||
|
||||
|
||||
def label_cols(rows, H):
|
||||
"""왼쪽 라벨 칸 수 L."""
|
||||
ncol, body = len(rows[0]), rows[H:]
|
||||
|
||||
def textual(c):
|
||||
vs = [r[c] for r in body if r[c] not in DASH]
|
||||
return bool(vs) and sum(not vlike(v) or v == "〃" for v in vs) * 2 >= len(vs)
|
||||
|
||||
L = 1
|
||||
while L < ncol - 1:
|
||||
c, top = L, jn(rows[0][L])
|
||||
if H > 1:
|
||||
below = [rows[r][c] for r in range(1, H - 1)]
|
||||
last = rows[H - 1][c]
|
||||
span = not any(below) and (last == "" or is_unit(jn(last)))
|
||||
ok = span and not VALUEISH.search(top) and not is_member(top)
|
||||
ok = ok and (top == "" or textual(c) or bool(LABELNAME.match(top)))
|
||||
else:
|
||||
ok = textual(c) or bool(LABELNAME.match(top) and not VALUEISH.search(top))
|
||||
ok = ok and not (top in ("수량",) or MEASURE.match(top))
|
||||
ok = ok and any(not textual(k) for k in range(c + 1, ncol))
|
||||
if not ok:
|
||||
break
|
||||
L += 1
|
||||
return L
|
||||
|
||||
|
||||
def layers(rows, H, start, ncol):
|
||||
"""머리 층 H 줄 → [층][열] 글(빈 칸은 같은 윗 무리 안에서 왼쪽 값으로 채움)."""
|
||||
out = []
|
||||
for r in range(H):
|
||||
row = [jn(x) for x in rows[r]]
|
||||
for c in range(start if r == 0 else start + 1, ncol):
|
||||
if row[c] or c == 0:
|
||||
continue
|
||||
same = all(out[q][c] == out[q][c - 1] for q in range(r))
|
||||
if row[c - 1] and same and (r == 0 or out[r - 1][c]):
|
||||
row[c] = row[c - 1]
|
||||
out.append(row)
|
||||
return out
|
||||
|
||||
|
||||
def measure_hm(entries):
|
||||
es = {e for e in entries if e}
|
||||
return bool(es) and sum(is_member(e) for e in es) * 2 >= len(es)
|
||||
|
||||
|
||||
def measure_score(entries):
|
||||
es = [e for e in entries if e]
|
||||
if not es:
|
||||
return 0
|
||||
return sum(bool(is_member(e) or MEASURE.match(e) or strip_unit(e)[1]) for e in set(es)) / len(
|
||||
set(es)
|
||||
)
|
||||
|
||||
|
||||
def convert(t, spec=None):
|
||||
"""표 → (조건 사전, 값칸 사전, 줄 목록)."""
|
||||
spec = spec or {}
|
||||
rows, H = [list(r) for r in t.rows], spec.get("H", t.hdr)
|
||||
rows = halves(rows, H)
|
||||
ncol = max(len(r) for r in rows)
|
||||
rows = [r + [""] * (ncol - len(r)) for r in rows]
|
||||
bigo = []
|
||||
for r in rows[H:]:
|
||||
if jn(r[0]) == "비고":
|
||||
bigo += bigo_items(" ".join(x for x in r[1:] if x))
|
||||
rows = rows[:H] + [r for r in rows[H:] if jn(r[0]) != "비고"]
|
||||
body = rows[H:]
|
||||
L = spec.get("L") or label_cols(rows, H)
|
||||
unitc = next(
|
||||
(c for c in range(L) if jn(rows[H - 1][c]) == "단위" or jn(rows[0][c]) == "단위"), None
|
||||
)
|
||||
lab_unit = None # 머리 없는 「〃」 단위 칸 = 앞 라벨 칸의 단위
|
||||
if unitc is None:
|
||||
unitc = next(
|
||||
(
|
||||
c
|
||||
for c in range(1, L)
|
||||
if sum(r[c] == "〃" for r in body) * 2 >= max(len(body) - 1, 1)
|
||||
),
|
||||
None,
|
||||
)
|
||||
lab_unit = unitc
|
||||
if lab_unit is not None and not any(
|
||||
measure_hm([jn(rows[r][c]) for c in range(L, ncol)]) for r in range(H)
|
||||
):
|
||||
unitc = lab_unit = None # 값 머리가 직종이 아니면 그냥 라벨 칸
|
||||
# 라벨 칸 이름
|
||||
names = {}
|
||||
for c in range(L):
|
||||
top, low = jn(rows[0][c]), jn(rows[H - 1][c]) if H > 1 else ""
|
||||
names[c] = (
|
||||
f"{top}({low})" if low and is_unit(low) else (low if c == 0 and low and H > 1 else top)
|
||||
) or "구분"
|
||||
if lab_unit is not None and any(
|
||||
measure_hm([jn(rows[r][c]) for c in range(L, ncol)]) for r in range(H)
|
||||
):
|
||||
u0 = next((jn(r[lab_unit]) for r in body if r[lab_unit] not in ("", "〃")), "")
|
||||
names[lab_unit - 1] += f"({u0})"
|
||||
lab_unit = -1
|
||||
# 직종 칸(줄에 직종이 있는 표)
|
||||
txt = [c for c in range(L) if c != unitc]
|
||||
mc = spec.get("M", None)
|
||||
head_members = any(measure_hm([jn(rows[r][c]) for c in range(L, ncol)]) for r in range(H))
|
||||
if mc is None and unitc is not None and not head_members:
|
||||
mc = max(c for c in txt if c < unitc) if any(c < unitc for c in txt) else None
|
||||
if mc is None and txt:
|
||||
last = txt[-1]
|
||||
vs = [r[last] for r in body if r[last] not in DASH]
|
||||
if vs and sum(is_member(v) for v in vs) * 2 >= len(vs):
|
||||
mc = last
|
||||
if mc == -1:
|
||||
mc = None
|
||||
if ( # 첫 칸이 값 이름인 옆으로 누운 표
|
||||
mc is None
|
||||
and L == 1
|
||||
and unitc is None
|
||||
and not head_members
|
||||
and H == 1
|
||||
and body
|
||||
and all(r[0] not in DASH and not vlike(r[0]) and not is_member(r[0]) for r in body)
|
||||
):
|
||||
mc = 0
|
||||
names[0] = jn(rows[0][0]) or "구분"
|
||||
tposed = True
|
||||
else:
|
||||
tposed = False
|
||||
# 값 열 머리 층
|
||||
vcols = list(range(L, ncol))
|
||||
lay = layers(rows, H, L, ncol)
|
||||
corner = [jn(rows[r][0]) for r in range(H)]
|
||||
named = H > 1 and all(corner) and len(set(corner)) == H
|
||||
if named:
|
||||
names[0] = corner[-1]
|
||||
lnames = [corner[r] if named else "" for r in range(H)]
|
||||
if tposed:
|
||||
lnames = [names[0]]
|
||||
mlay, ulay = None, set()
|
||||
for r in range(H):
|
||||
ents = [lay[r][c] for c in vcols]
|
||||
if ents and all(is_unit(e) for e in ents if e) and any(ents):
|
||||
ulay.add(r)
|
||||
if mc is None:
|
||||
sc = {r: measure_score([lay[r][c] for c in vcols]) for r in range(H) if r not in ulay}
|
||||
if sc:
|
||||
best = max(sc, key=lambda r: (sc[r], r))
|
||||
if sc[best] >= 0.5:
|
||||
mlay = best
|
||||
else: # 직종이 줄에 있음 — 값 이름 층은 「수량」 따위뿐
|
||||
for r in range(H):
|
||||
if (
|
||||
r not in ulay
|
||||
and lay[r]
|
||||
and all(MEASURE.match(lay[r][c]) for c in vcols if lay[r][c])
|
||||
and any(lay[r][c] for c in vcols)
|
||||
):
|
||||
mlay = r
|
||||
only = {
|
||||
r
|
||||
for r in range(H)
|
||||
if any(lay[r][c] for c in vcols)
|
||||
and all(lay[r][c] in ("", "수량", "시공량", "소요량") for c in vcols)
|
||||
}
|
||||
dropped = ulay | only | ({mlay} if mlay is not None else set())
|
||||
clay = [r for r in range(H) if r not in dropped]
|
||||
# 몸 줄 읽기
|
||||
recs, prev, prevu = [], {}, ""
|
||||
for r in body:
|
||||
lab = {}
|
||||
for c in range(L):
|
||||
if c == unitc or c == mc:
|
||||
continue
|
||||
x = jn(r[c])
|
||||
if x in ("", "〃") and not any(r[q] for q in range(c)):
|
||||
x = prev.get(c, "")
|
||||
lab[c] = x
|
||||
prev.update(lab)
|
||||
if not any(x for x in lab.values()) and not any(r[c] for c in range(L)):
|
||||
continue
|
||||
unit = ""
|
||||
if unitc is not None:
|
||||
u = jn(r[unitc])
|
||||
prevu = u if u and u != "〃" else prevu
|
||||
unit = "" if lab_unit == -1 else prevu
|
||||
member = nm(r[mc]) if mc is not None else ""
|
||||
made = 0
|
||||
for c in vcols:
|
||||
v = r[c].strip()
|
||||
if v in DASH:
|
||||
continue
|
||||
u2, meas = unit, "수량"
|
||||
if mlay is not None:
|
||||
meas, uu = strip_unit(nm(lay[mlay][c]))
|
||||
u2 = u2 or uu
|
||||
if member:
|
||||
meas = member
|
||||
for q in ulay:
|
||||
u2 = u2 or lay[q][c]
|
||||
cond = {}
|
||||
for c0 in range(L):
|
||||
if c0 in lab and lab[c0]:
|
||||
cond[names[c0]] = lab[c0]
|
||||
path = [lay[q][c] for q in clay if lay[q][c]]
|
||||
un = [q for q in clay if lnames[q] and lay[q][c]]
|
||||
if un:
|
||||
for q in un:
|
||||
cond[lnames[q]] = lay[q][c]
|
||||
path = [lay[q][c] for q in clay if lay[q][c] and not lnames[q]]
|
||||
if path:
|
||||
cond["구분" if "구분" not in cond else "세구분"] = " ".join(dict.fromkeys(path))
|
||||
recs.append((cond, meas, v, u2))
|
||||
made += 1
|
||||
if not made and mc is not None and member and not is_member(member):
|
||||
cond = {names[c0]: lab[c0] for c0 in lab if lab[c0]}
|
||||
recs.append((cond, "수량", member, unit))
|
||||
return (*pack(recs, spec, t), bigo)
|
||||
|
||||
|
||||
def pack(recs, spec, t):
|
||||
"""(조건, 값이름, 값, 단위) 목록 → 조건 · 값칸 · 줄."""
|
||||
unit_of, multi = {}, False
|
||||
groups = {}
|
||||
for cond, meas, v, u in recs:
|
||||
key = tuple(cond.items())
|
||||
g = groups.setdefault(key, {})
|
||||
if meas in g:
|
||||
multi = True
|
||||
g[meas] = (v, u)
|
||||
if unit_of.setdefault(meas, u) != u:
|
||||
multi = True
|
||||
rows, cols = [], {}
|
||||
if not multi:
|
||||
for key, g in groups.items():
|
||||
d = dict(key)
|
||||
for meas, (v, u) in g.items():
|
||||
val, orig = cv(v)
|
||||
cols.setdefault(meas, unit_of[meas])
|
||||
d[meas] = val
|
||||
if orig:
|
||||
d[meas + "원문"] = orig
|
||||
cols[meas + "원문"] = "글"
|
||||
rows.append(d)
|
||||
cn = list(dict.fromkeys(k for r in rows for k in r if k not in cols))
|
||||
else: # 값 이름이 조건으로 — 값은 「수량」 하나 · 줄마다 단위
|
||||
for cond, meas, v, u in recs:
|
||||
d = dict(cond)
|
||||
d["항목"] = meas
|
||||
val, orig = cv(v)
|
||||
d["수량"] = val
|
||||
if orig:
|
||||
d["수량원문"] = orig
|
||||
if u:
|
||||
d["단위"] = u
|
||||
rows.append(d)
|
||||
cols = {"수량": ""}
|
||||
if any("수량원문" in r for r in rows):
|
||||
cols["수량원문"] = "글"
|
||||
cn = list(dict.fromkeys(k for r in rows for k in r if k not in cols and k != "단위"))
|
||||
cond = conds_out(cn, rows)
|
||||
return cond, cols, rows
|
||||
@@ -62,13 +62,13 @@ def rows_of(line):
|
||||
return out
|
||||
|
||||
|
||||
def lift(chdir, text, out):
|
||||
def lift(chdir, text, out, rows_fn=rows_of):
|
||||
src_tabs = list(out.tables)
|
||||
for t in src_tabs:
|
||||
ident = t["열쇠"].split()[0]
|
||||
tags = {}
|
||||
for line in t["주"]:
|
||||
rs = rows_of(line)
|
||||
rs = rows_fn(line)
|
||||
if not rs:
|
||||
continue
|
||||
tag = "비고" if line.startswith("비고") else "주" + re.match(r"[①-⑳]", line).group(0)
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""건설품셈 기계설비 제7~13장 본문 md → resources/master_data/소요량_건설품셈_기계설비{7..13}장.json
|
||||
|
||||
값은 본문 md 에서만 읽음(손으로 적지 않음). 표 모양은 _설비_표.convert 가 알아내고 못 맞추는 표만 SPEC 으로 지정.
|
||||
돌리기: ./venv/Scripts/python.exe resources/master_data/scripts/build_소요량_건설품셈_기계설비.py [7 8 …]
|
||||
"""
|
||||
|
||||
import sys
|
||||
|
||||
import _토목_읽기 as base
|
||||
from _설비_계수 import rows_of
|
||||
from _설비_읽기 import DIV, read_chapter
|
||||
from _설비_표 import convert
|
||||
from _설비_손 import SPEC
|
||||
from _토목_계수 import lift
|
||||
from _토목_읽기 import ROOT, Out
|
||||
|
||||
base.DIV = DIV # 출처 앞말 「건설품셈 기계설비 」
|
||||
|
||||
|
||||
def label_of(t, count):
|
||||
n = f"{t.title} {t.mok}" if t.mok else t.title
|
||||
extra = " ".join(x for x in t.pre if not x.startswith("[별표]"))
|
||||
kinds = [p for p in t.pre if p.startswith("[")]
|
||||
if kinds:
|
||||
n += (
|
||||
" " + kinds[0].strip("[]").split("] ")[-1]
|
||||
if "]" in kinds[0] and not kinds[0].endswith("]")
|
||||
else ""
|
||||
)
|
||||
return n
|
||||
|
||||
|
||||
def build(ch, out_name):
|
||||
tabs = read_chapter(f"제{ch:02d}")
|
||||
out, seen = Out(), {}
|
||||
for t in tabs:
|
||||
key = f"{t.ident}#{t.k}"
|
||||
cond, cols, rows, bigo = convert(t, SPEC.get(key))
|
||||
lab = label_of(t, None)
|
||||
seen[(t.ident, lab)] = seen.get((t.ident, lab), 0) + 1
|
||||
if seen[(t.ident, lab)] > 1:
|
||||
lab += f" 표{seen[(t.ident, lab)]}"
|
||||
out.add(t, lab, cond, cols, rows, notes=bigo + t.notes)
|
||||
lift("", "", out, rows_of)
|
||||
out.write(ROOT / "resources/master_data" / out_name)
|
||||
return out
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
for w in sys.argv[1:] or ["7", "8", "9", "10", "11", "12", "13"]:
|
||||
o = build(int(w), f"소요량_건설품셈_기계설비{w}장.json")
|
||||
print(w, len(o.tables), "표")
|
||||
Reference in New Issue
Block a user