Merge remote-tracking branch 'origin/sub_laptop_3' into main_laptop_1
This commit is contained in:
@@ -0,0 +1,218 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""건설품셈 공통 1장 나머지(782-2) — 1-5-4 · 1-5-6 로직 둘 + 계수 표 넣기 · 기존 할증 표 「성격」 칸.
|
||||
|
||||
`build_로직_건설품셈_01장_적용기준.py 나머지` 가 부름 · 한 번 돌림(이미 넣었으면 멈춤).
|
||||
- 계수 표 = `_적용기준_나머지_표.all_tables()` · 절 차례 자리에 끼움(처음으로 절 번호가 더 큰 기존 표 앞 · 없으면 끝).
|
||||
- 로직 = 로직_건설품셈_01장_적용기준.json 끝에 덧 · 키 = master_keys.issue(adopt 는 로직을 받지 않음).
|
||||
- 1-2-9 소규모 · 1-4 할증 로직 = 만들지 않음(PLAN 783-1).
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import re
|
||||
from decimal import Decimal
|
||||
|
||||
import _적용기준_나머지_표 as T
|
||||
import build_로직_건설품셈_01장_적용기준 as base
|
||||
import build_로직_건설품셈_08장_건설기계 as b08
|
||||
import master_keys as mk
|
||||
|
||||
MASTER = base.MASTER
|
||||
COEF, LOGIC, HEAD = base.COEF, base.LOGIC, base.HEAD
|
||||
SRC4, SRC6 = "건설품셈 공통 1-5-4", "건설품셈 공통 1-5-6"
|
||||
|
||||
# 기존 1-3 · 1-4 표 → 성격(같은 성격 묶음 · 1-4-2 이중 적용 불가) — 기본 = 같은 표 · 딸린 표(정의 · 한도)는 본 표와 같음
|
||||
NATURE = {
|
||||
**{f"CC{n:06d}": "재료 할증" for n in range(1, 9)},
|
||||
"CC000009": "작업지연 — 현장조건",
|
||||
"CC000010": "작업지연 — 열차의 운행빈도",
|
||||
"CC000011": "작업지연 — 건물 층수",
|
||||
"CC000012": "작업지연 — 건물 층수",
|
||||
**{f"CC{n:06d}": "지세/지형 — 지세" for n in range(13, 20)},
|
||||
"CC000020": "지세/지형 — 도심지",
|
||||
"CC000021": "위험 — 고소작업",
|
||||
"CC000022": "위험 — 교량상 작업",
|
||||
**{f"CC{n:06d}": "위험 — 터널내 작업" for n in range(23, 26)},
|
||||
"CC000026": "위험 — 유해 작업",
|
||||
"CC000027": "위험 — 유해 작업",
|
||||
"CC000028": T.SMALL,
|
||||
"CC000029": "작업제한 — 폭염시 휴게시간",
|
||||
"CC000030": "작업환경 — 야간",
|
||||
"CC000031": "작업환경 — 특수작업",
|
||||
"CC000032": "작업환경 — 기타",
|
||||
"CC000033": "작업환경 — 기타",
|
||||
}
|
||||
|
||||
|
||||
def inp(name, desc, unit=None, pick=None, src=None):
|
||||
return base.inp(name, desc, unit, pick, src)
|
||||
|
||||
|
||||
def logics(K: dict) -> list[dict]:
|
||||
waste, water = K["건설폐기물 발생량"], K["공사용수"]
|
||||
w_rows = T.t_1_5_4()[0]["줄"]
|
||||
builds = list(dict.fromkeys(r["구조"] for r in w_rows))
|
||||
kinds = [r["구분"] for r in T.t_1_5_6()[0]["줄"] if r["구분"] != "잡용수"]
|
||||
env = {
|
||||
"열쇠": "공통 1-5-4 건설폐기물 발생량",
|
||||
**HEAD,
|
||||
"이름": "건설폐기물 발생량 — 건설 공통 1-5-4",
|
||||
"결과단위": "TON",
|
||||
"출처": f"{SRC4} 2",
|
||||
"소유": "공용",
|
||||
"비고": "발생량 = 면적 × 단위면적당 발생량(TON/㎡) × (1 + 증가율 ÷ 100) · 증가율 = 폐콘크리트류만(주③ 파일 시공 15% · 20% 이내 · 주④ 세륜 시설 개소당 3% 이내 — 설계자가 넣음 · 0 = 증가 없음) · 원문 칸 「-」 = 줄 없음(멈춤) · 건축물 해체는 설계도서 산출 우선(원문 2) · 주⑤ 현장 여건 조정",
|
||||
"시험입력": {
|
||||
"공사": "신축",
|
||||
"구조": "철근콘크리트조",
|
||||
"폐기물": "폐콘크리트류",
|
||||
"면적": 1000,
|
||||
"증가율": 0,
|
||||
},
|
||||
"입력": [
|
||||
inp("공사", "신축 · 해체", pick=["신축", "해체"], src=f"{SRC4} 2"),
|
||||
inp("구조", "건물 구조(주거용 = 단독주택 · 아파트)", pick=builds, src=f"{SRC4} 2"),
|
||||
inp("폐기물", "건설폐기물 종류", pick=list(T.WASTE), src=f"{SRC4} 2"),
|
||||
inp("면적", "단위면적 산출 면적", "㎡", src=f"{SRC4} 2"),
|
||||
inp(
|
||||
"증가율",
|
||||
"폐콘크리트 수량 증가(%) — 주③ 파일 15 · 20% 이내 · 주④ 세륜 개소당 3% 이내 · 0 = 없음",
|
||||
"%",
|
||||
src=f"{SRC4} 2 주③ ④",
|
||||
),
|
||||
],
|
||||
"중간": [
|
||||
{
|
||||
"이름": "원단위",
|
||||
"식": f"찾기({waste}, 공사=공사, 구조=구조, 폐기물=폐기물).발생량",
|
||||
"단위": "TON/㎡",
|
||||
},
|
||||
{
|
||||
"이름": "증가",
|
||||
"식": "만약(폐기물 == '폐콘크리트류', 증가율 / 100, 0)",
|
||||
},
|
||||
],
|
||||
"결과": {"식": "면적 * 원단위 * (1 + 증가)", "출처": f"{SRC4} 2"},
|
||||
"끝수": None,
|
||||
}
|
||||
use = {
|
||||
"열쇠": "공통 1-5-6 공사용수",
|
||||
**HEAD,
|
||||
"이름": "공사용수 — 건설 공통 1-5-6",
|
||||
"결과단위": "㎥",
|
||||
"출처": f"{SRC6} 3",
|
||||
"소유": "공용",
|
||||
"비고": "용수량 = 작업량 × 단위당 용수(㎥) × (1 + 잡용수율 ÷ 100) · 양생 물 포함([주]) · 해석: 잡용수 = 원문 「사용량비의 40∼50%」 — 잡용수율 0 = 더하지 않음(설계자가 40∼50 넣음)",
|
||||
"시험입력": {"구분": "콘크리트혼합 및 양생", "작업량": 100, "잡용수율": 0},
|
||||
"입력": [
|
||||
inp("구분", "용수 구분", pick=kinds, src=f"{SRC6} 3"),
|
||||
inp("작업량", "구분의 단위 분모 수량(㎡ · ㎥ · 1,000매 · 표면적 ㎡)", src=f"{SRC6} 3"),
|
||||
inp(
|
||||
"잡용수율",
|
||||
"잡용수(%) — 원문 사용량비의 40∼50% · 0 = 더하지 않음",
|
||||
"%",
|
||||
src=f"{SRC6} 3",
|
||||
),
|
||||
],
|
||||
"중간": [
|
||||
{"이름": "단위용수", "식": f"찾기({water}, 구분=구분).수량", "단위": "㎥"},
|
||||
],
|
||||
"결과": {"식": "작업량 * 단위용수 * (1 + 잡용수율 / 100)", "출처": f"{SRC6} 3"},
|
||||
"끝수": None,
|
||||
}
|
||||
return [env, use]
|
||||
|
||||
|
||||
def emit(t: dict) -> str:
|
||||
"""기존 표 글 모양 + 「성격」 칸(용도 다음 줄)."""
|
||||
text = base.emit(t)
|
||||
if "성격" in t:
|
||||
text = re.sub(
|
||||
r'(\n "용도": [^\n]*\n)',
|
||||
lambda m: m[1] + f' "성격": {json.dumps(t["성격"], ensure_ascii=False)},\n',
|
||||
text,
|
||||
count=1,
|
||||
)
|
||||
return text
|
||||
|
||||
|
||||
def _sec(no: str) -> tuple[int, ...]:
|
||||
return b08._sec(no)
|
||||
|
||||
|
||||
def insert_tables(text: str, made: list[dict]) -> str:
|
||||
"""새 표 → 처음으로 절 번호가 더 큰 기존 표 앞(없으면 끝)."""
|
||||
starts = [
|
||||
(m.start(), m[1])
|
||||
for m in re.finditer(r' \{\n "키": "CC\d+", "원문번호": "([^"]+)"', text)
|
||||
]
|
||||
pieces, last, left = [], 0, list(made)
|
||||
for at, no in starts:
|
||||
due = [t for t in left if _sec(t["원문번호"]) < _sec(no)]
|
||||
if due:
|
||||
pieces += [text[last:at], "".join(emit(t) + ",\n" for t in due)]
|
||||
left, last = [t for t in left if t not in due], at
|
||||
tail = text.rindex("\n ]\n}")
|
||||
pieces.append(text[last:tail])
|
||||
if left:
|
||||
pieces.append(",\n" + ",\n".join(emit(t) for t in left))
|
||||
pieces.append(text[tail:])
|
||||
return "".join(pieces)
|
||||
|
||||
|
||||
def add_nature(text: str) -> str:
|
||||
"""기존 1-3 · 1-4 표에 「성격」 한 줄 — 다른 글자는 그대로."""
|
||||
for key, nature in NATURE.items():
|
||||
pat = re.compile(
|
||||
rf'( "키": "{key}",[^\n]*\n(?: (?!"용도")[^\n]*\n)*? "용도": [^\n]*\n)'
|
||||
)
|
||||
text, n = pat.subn(
|
||||
lambda m: m[1] + f' "성격": {json.dumps(nature, ensure_ascii=False)},\n',
|
||||
text,
|
||||
count=1,
|
||||
)
|
||||
assert n == 1, key
|
||||
return text
|
||||
|
||||
|
||||
def run() -> None:
|
||||
coef_path = MASTER / COEF
|
||||
text = coef_path.read_text(encoding="utf-8")
|
||||
if '"원문번호": "1-5-4"' in text:
|
||||
raise SystemExit("이미 넣음 — 멈춤")
|
||||
old = json.loads(text, parse_float=Decimal, parse_int=Decimal)["표"]
|
||||
book = mk.load_book()
|
||||
made = T.all_tables()
|
||||
K = {}
|
||||
for t in made:
|
||||
t["키"] = mk.issue(book, "CC", t.pop("열쇠"), COEF)
|
||||
K[t["이름"]] = K[t["키"]] = t["키"]
|
||||
t.update(HEAD)
|
||||
K["건설폐기물 발생량"] = next(
|
||||
t["키"] for t in made if t["원문번호"] == "1-5-4" and "발생량" in t["이름"]
|
||||
)
|
||||
K["공사용수"] = next(
|
||||
t["키"] for t in made if t["원문번호"] == "1-5-6" and "공사용수" in t["이름"]
|
||||
)
|
||||
|
||||
rows = []
|
||||
for r in logics(K):
|
||||
r["키"] = mk.issue(book, "GC", r.pop("열쇠"), LOGIC)
|
||||
r["원문번호"] = r["출처"].split()[2]
|
||||
head = ["키", "원문번호"]
|
||||
rows.append({k: r[k] for k in head + [k for k in r if k not in head]})
|
||||
for t in made:
|
||||
t["용도"]["로직키"] = [
|
||||
r["키"] for r in rows if t["키"] in json.dumps(r, ensure_ascii=False)
|
||||
]
|
||||
|
||||
new = add_nature(insert_tables(text, made))
|
||||
data = json.loads(new, parse_float=Decimal, parse_int=Decimal)["표"]
|
||||
keep = {t["키"]: {k: v for k, v in t.items() if k != "성격"} for t in data}
|
||||
assert [keep[t["키"]] for t in old] == old # 기존 표 = 성격 칸만 더함
|
||||
assert len(data) == len(old) + len(made)
|
||||
assert sum("성격" in t for t in data) == len(NATURE) + 2
|
||||
coef_path.write_text(new, encoding="utf-8", newline="\n")
|
||||
b08.append_rows(LOGIC, rows)
|
||||
mk.save_book(mk.finish(book))
|
||||
print("계수", made[0]["키"], "~", made[-1]["키"], len(made), "· 로직", [r["키"] for r in rows])
|
||||
@@ -0,0 +1,553 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""건설품셈 공통 1장 나머지 — 규칙 · 계수만 절의 계수 표(782-2) · `build_로직_건설품셈_01장_적용기준.py 나머지` 가 부름.
|
||||
|
||||
- 절 범위 = `### N-N-N` 머리부터 다음 같은 · 윗 단계 머리 앞까지(요구 630).
|
||||
- 절마다 「규칙 글」 표 하나(원문 글 그대로 · 항 = 「N.」 · 번호 없는 머리 글 = 「머리」) + 수 · 표가 있는 절은 수 표.
|
||||
- 1-2-9 소규모 · 1-4-2 중복가산 = 계수로만(로직은 PLAN 783-1) · 성격 칸 = 같은 성격 묶음(1-4-2 이중 적용 불가 판정).
|
||||
- 값은 원문 글자 그대로(N) · 「-」 칸 = 값 없음(줄 · 칸을 두지 않음) · 범위 = [아래, 위].
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import re
|
||||
from pathlib import Path
|
||||
|
||||
import build_로직_건설품셈_01장_적용기준 as base
|
||||
|
||||
N, cell = base.N, base.cell
|
||||
CH1 = base.BODY.parent
|
||||
FILES = {
|
||||
"1-2": "1-02_설계_및_수량.md",
|
||||
"1-3": "1-03_재료_및_노임의_할증.md",
|
||||
"1-4": "1-04_품의_할증.md",
|
||||
"1-5": "1-05_기타.md",
|
||||
}
|
||||
SMALL = "작업시간 제한 · 소규모" # 1-2-9 ↔ 1-4-6 1 같은 성격(_제안_634 1-2)
|
||||
|
||||
|
||||
def section(no: str, md: Path | None = None) -> tuple[str, list[str]]:
|
||||
"""(절 이름, 절 줄) — 머리 줄부터 다음 같은 · 윗 단계 머리 앞까지."""
|
||||
md = md or CH1 / FILES[no.rsplit("-", 1)[0]]
|
||||
lines = md.read_text(encoding="utf-8").split("\n")
|
||||
head = re.compile(rf"^(#+) {re.escape(no)} (.+)$")
|
||||
a = next(i for i, x in enumerate(lines) if head.match(x))
|
||||
level = len(head.match(lines[a])[1])
|
||||
b = next(
|
||||
(j for j in range(a + 1, len(lines)) if re.match(rf"^#{{1,{level}}} ", lines[j])),
|
||||
len(lines),
|
||||
)
|
||||
name = re.sub(r"\s*\(['‘].*$", "", head.match(lines[a])[2]).strip()
|
||||
return name, lines[a:b]
|
||||
|
||||
|
||||
def _plain(x: str) -> str:
|
||||
x = re.sub(r"<!--.*?-->", "", x)
|
||||
x = re.sub(r"^(\d+)\\\.", r"\1.", x.strip())
|
||||
return x.strip()
|
||||
|
||||
|
||||
def rules(lines: list[str]) -> list[dict]:
|
||||
"""규칙 글 — 표 · 그림 · 주석 줄을 뺀 원문 글을 항(「N.」)마다 한 줄."""
|
||||
out, item = [], {"항": "머리", "글": []}
|
||||
for x in lines[1:]:
|
||||
if x.startswith(("|", "![")) or re.fullmatch(r"\s*<!--.*-->\s*", x):
|
||||
continue
|
||||
m = re.match(r"^(\d+)\\\. ", x)
|
||||
if m:
|
||||
out.append(item)
|
||||
item = {"항": m[1], "글": []}
|
||||
if _plain(x):
|
||||
item["글"].append(_plain(x))
|
||||
out.append(item)
|
||||
return [{"항": r["항"], "규칙": "\n".join(r["글"])} for r in out if r["글"]]
|
||||
|
||||
|
||||
def table(no, label, name, src, cols, rows, *, cond=None, note=(), base_="", extra=None, 대상=None):
|
||||
return {
|
||||
"열쇠": f"공통 {no} {label}",
|
||||
"원문번호": no,
|
||||
"이름": name,
|
||||
"기준": base_,
|
||||
"출처": f"건설품셈 공통 {no} {src}".strip(),
|
||||
**(extra or {}),
|
||||
"용도": {"공종": section(no)[0], "대상": 대상 or ["작업량 조건"], "로직키": []},
|
||||
"조건": cond or {},
|
||||
"값칸": cols,
|
||||
"줄": rows,
|
||||
"주": list(note),
|
||||
}
|
||||
|
||||
|
||||
def rule_table(no: str, extra: dict | None = None) -> dict:
|
||||
name, lines = section(no)
|
||||
return table(
|
||||
no,
|
||||
"규칙 글",
|
||||
f"{name} — 규칙 글",
|
||||
"",
|
||||
{"규칙": "글"},
|
||||
rules(lines),
|
||||
cond={"항": "고르기"},
|
||||
base_="원문 글 그대로",
|
||||
extra={
|
||||
"비고": "계산 칸 없는 규칙 · 기준 글 — 항 = 원문 「N.」 · 머리 = 번호 없는 글",
|
||||
**(extra or {}),
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
def notes(lines: list[str], after: int = 0) -> list[str]:
|
||||
"""`after` 줄 뒤 첫 「[주]」 묶음(빈 줄까지)."""
|
||||
i = next(j for j in range(after, len(lines)) if lines[j].strip() == "[주]")
|
||||
out = []
|
||||
for x in lines[i + 1 :]:
|
||||
if not x.strip():
|
||||
break
|
||||
if not x.startswith("<!--"):
|
||||
out.append(_plain(x))
|
||||
return out
|
||||
|
||||
|
||||
def md_tables(lines: list[str]) -> list[list[list[str]]]:
|
||||
return base.md_tables(lines)
|
||||
|
||||
|
||||
def num(c: str):
|
||||
"""값 칸 — 수 · 범위 · 「-」(None)."""
|
||||
c = c.replace("\n", "").strip()
|
||||
return cell(c)[0] if c not in ("", "-") else None
|
||||
|
||||
|
||||
# ── 1-2-2 단위표준 ───────────────────────────────────────────────────
|
||||
def t_1_2_2() -> list[dict]:
|
||||
_, lines = section("1-2-2")
|
||||
unit, money = md_tables(lines)
|
||||
rows, kind = [], ""
|
||||
for r in unit[2:]:
|
||||
name, su, sd, qu, qd, note = r
|
||||
kind = name or kind
|
||||
row = {"종목": kind}
|
||||
for col, v in (("규격단위", su), ("수량단위", qu), ("비고", note)):
|
||||
if v not in ("", "-"):
|
||||
row[col] = v
|
||||
for col, v in (("규격소수자리", sd), ("수량소수자리", qd)):
|
||||
if v == "1 위": # 원문 그대로 「1 위」
|
||||
row[col], row[col + "원문"] = N("1"), v
|
||||
elif v not in ("", "-"):
|
||||
row[col] = N(v)
|
||||
rows.append(dict(sorted(row.items(), key=lambda kv: list(UNIT_ORDER).index(kv[0]))))
|
||||
i = lines.index("2\\. 금액의 단위표준")
|
||||
t1 = table(
|
||||
"1-2-2",
|
||||
"설계서의 단위 및 소수의 표준",
|
||||
"설계서의 단위 및 소수의 표준",
|
||||
"1",
|
||||
{
|
||||
"규격단위": "글",
|
||||
"규격소수자리": "자리",
|
||||
"수량단위": "글",
|
||||
"수량소수자리": "자리",
|
||||
"비고": "글",
|
||||
},
|
||||
rows,
|
||||
cond={"종목": "고르기"},
|
||||
note=notes(lines),
|
||||
extra={
|
||||
"비고": "원문 둘째 줄 이하 규격(말뚝 · 돌망태 · 관류)은 윗줄 종목 · 같은 종목 여러 줄 = 원문 그대로 · 「-」 = 칸 없음"
|
||||
},
|
||||
)
|
||||
t2 = table(
|
||||
"1-2-2",
|
||||
"금액의 단위표준",
|
||||
"금액의 단위표준",
|
||||
"2",
|
||||
{"단위": "글", "자리": "원", "비고": "글"},
|
||||
[{"종목": a, "단위": u, "자리": num(d), "비고": n} for a, u, d, n in money[1:]],
|
||||
cond={"종목": "고르기"},
|
||||
note=notes(lines, i),
|
||||
)
|
||||
return [t1, t2]
|
||||
|
||||
|
||||
UNIT_ORDER = (
|
||||
"종목",
|
||||
"규격단위",
|
||||
"규격소수자리",
|
||||
"규격소수자리원문",
|
||||
"수량단위",
|
||||
"수량소수자리",
|
||||
"수량소수자리원문",
|
||||
"비고",
|
||||
)
|
||||
|
||||
|
||||
# ── 1-2-3 토질 ───────────────────────────────────────────────────────
|
||||
def t_1_2_3() -> list[dict]:
|
||||
_, lines = section("1-2-3")
|
||||
md = md_tables(lines)
|
||||
rate = md[1]
|
||||
rows = []
|
||||
for kind, lv, cv in rate[1:]:
|
||||
row = {"종별": kind, "L": num(lv)}
|
||||
if re.search(r"\d", cv):
|
||||
row["C"] = num(cv)
|
||||
else:
|
||||
row["C원문"] = cv # 「별도 설계」
|
||||
rows.append(row)
|
||||
i = next(j for j, x in enumerate(lines) if x.startswith("다. 체적의 변화율"))
|
||||
f_rows = [
|
||||
{
|
||||
"기준이 되는 q": "자연상태의 체적",
|
||||
"자연상태의 체적": N("1"),
|
||||
"흐트러진상태의 체적": "L",
|
||||
"다져진후의 체적": "C",
|
||||
},
|
||||
{
|
||||
"기준이 되는 q": "흐트러진상태의 체적",
|
||||
"자연상태의 체적": "1/L",
|
||||
"흐트러진상태의 체적": N("1"),
|
||||
"다져진후의 체적": "C/L",
|
||||
},
|
||||
]
|
||||
assert md[2][2][0] == "자연상태의 체적" and md[2][3][3] == "C/L"
|
||||
stone = next(x for x in lines if x.startswith("다. 모암을 발파하여"))
|
||||
assert "10∼40%" in stone
|
||||
return [
|
||||
table(
|
||||
"1-2-3",
|
||||
"체적의 변화율",
|
||||
"체적의 변화율 L · C",
|
||||
"3 다",
|
||||
{"L": "", "C": ""},
|
||||
rows,
|
||||
cond={"종별": "고르기"},
|
||||
note=notes(lines, i),
|
||||
base_="L = 흐트러진 상태의 체적 ÷ 자연상태의 체적 · C = 다져진 상태의 체적 ÷ 자연상태의 체적",
|
||||
extra={"비고": "범위 = [아래, 위] · 폐콘크리트 C = 원문 「별도 설계」(값 없음)"},
|
||||
대상=["작업량 조건", "환산"],
|
||||
),
|
||||
table(
|
||||
"1-2-3",
|
||||
"체적환산계수",
|
||||
"체적환산계수(f) — 구하는 Q ÷ 기준이 되는 q",
|
||||
"3 라",
|
||||
{"자연상태의 체적": "식", "흐트러진상태의 체적": "식", "다져진후의 체적": "식"},
|
||||
f_rows,
|
||||
cond={"기준이 되는 q": "고르기"},
|
||||
extra={"비고": "값칸 = 구하는 Q · L · C = 체적의 변화율 표"},
|
||||
대상=["작업량 조건", "환산"],
|
||||
),
|
||||
table(
|
||||
"1-2-3",
|
||||
"파쇄된 돌 발생량",
|
||||
"토취장 및 골재원 — 규격품으로 못 쓰는 파쇄된 돌 발생량",
|
||||
"4 다",
|
||||
{"발생량": "%"},
|
||||
[{"발생량": [N("10"), N("40")], "발생량원문": "10∼40%를 표준"}],
|
||||
base_="모암을 발파하여 깬돌 등 규격품을 채취할 경우",
|
||||
extra={"비고": "유용하면 경비 별도 계상 · 발생량은 무대(無代)"},
|
||||
),
|
||||
rule_table("1-2-3"),
|
||||
]
|
||||
|
||||
|
||||
# ── 1-2-4 · 1-2-5 · 1-2-6 ────────────────────────────────────────────
|
||||
def t_1_2_4() -> list[dict]:
|
||||
_, lines = section("1-2-4")
|
||||
seven = next(x for x in lines if x.startswith("각 항목에 명시되어 있는 잡재료"))
|
||||
assert "2∼5%" in seven
|
||||
return [
|
||||
table(
|
||||
"1-2-4",
|
||||
"잡재료 및 소모재료",
|
||||
"잡재료 및 소모재료 — 명시 없는 잡재료 별도 계상 한도",
|
||||
"7",
|
||||
{"주재료비 대비": "%"},
|
||||
[
|
||||
{
|
||||
"주재료비 대비": [N("2"), N("5")],
|
||||
"주재료비 대비원문": "주재료비(재료비의 할증수량 제외)의 2∼5%까지",
|
||||
}
|
||||
],
|
||||
extra={"비고": "산정근거 명시 · 항목에 명시된 잡재료는 그대로 계상"},
|
||||
대상=["재료"],
|
||||
),
|
||||
rule_table("1-2-4"),
|
||||
]
|
||||
|
||||
|
||||
def t_1_2_5() -> list[dict]:
|
||||
_, lines = section("1-2-5")
|
||||
rows = []
|
||||
for cond, text in md_tables(lines)[0][1:]:
|
||||
m = re.fullmatch(r"보통인부 (\d+)인∼(\d+)인에 1인", text)
|
||||
rows.append(
|
||||
{
|
||||
"현장작업조건": cond,
|
||||
"보통인부": [N(m[1]), N(m[2])],
|
||||
"작업반장": N("1"),
|
||||
"보통인부원문": text,
|
||||
}
|
||||
)
|
||||
return [
|
||||
table(
|
||||
"1-2-5",
|
||||
"작업반장",
|
||||
"작업반장 — 현장작업조건별 작업반장 수(참고)",
|
||||
"2",
|
||||
{"보통인부": "인", "작업반장": "인"},
|
||||
rows,
|
||||
cond={"현장작업조건": "고르기"},
|
||||
base_="보통인부 인원 범위당 작업반장 1인",
|
||||
extra={"비고": "원문 [참고] 표 · 다수 보통인부 지휘통제가 필요할 때 계상할 수 있음"},
|
||||
대상=["인력"],
|
||||
),
|
||||
rule_table("1-2-5"),
|
||||
]
|
||||
|
||||
|
||||
def t_1_2_6() -> list[dict]:
|
||||
_, lines = section("1-2-6")
|
||||
one = next(x for x in lines if x.startswith("일반공구 및 시험용"))
|
||||
assert "인력품(노임할증과 작업시간 증가에 의하지 않은 품 할증 제외)의 3%까지" in one
|
||||
return [
|
||||
table(
|
||||
"1-2-6",
|
||||
"공구손료",
|
||||
"공구손료 — 명시 없는 공구손료 계상 한도",
|
||||
"1",
|
||||
{"인력품 대비": "%"},
|
||||
[
|
||||
{
|
||||
"인력품 대비": N("3"),
|
||||
"인력품 대비원문": "인력품(노임할증과 작업시간 증가에 의하지 않은 품 할증 제외)의 3%까지",
|
||||
}
|
||||
],
|
||||
extra={"비고": "특수공구 · 검사용 특수계측기류 손료는 별도 계상 · 상한(「까지」)"},
|
||||
대상=["기계"],
|
||||
),
|
||||
rule_table("1-2-6"),
|
||||
]
|
||||
|
||||
|
||||
# ── 1-2-8 · 1-2-9 ────────────────────────────────────────────────────
|
||||
def t_1_2_8() -> list[dict]:
|
||||
_, lines = section("1-2-8")
|
||||
digits, sample = md_tables(lines)
|
||||
heads = digits[0][1:]
|
||||
rows = [{"일당시공량": h, "소수자리": N(v)} for h, v in zip(heads, digits[1][1:])]
|
||||
sizes = [re.search(r"\(([\d,]+)㎡\)", h)[1] for h in sample[1][3:8]]
|
||||
ex, kind = [], ""
|
||||
for r in sample[2:6]:
|
||||
kind = r[0] or kind
|
||||
for h, size, v in zip(heads, sizes, r[3:8]):
|
||||
ex.append(
|
||||
{
|
||||
"구분": kind,
|
||||
"단위": r[1],
|
||||
"수량": N(r[2]),
|
||||
"일당시공량": h,
|
||||
"예시 시공량": N(size.replace(",", "")),
|
||||
"품": N(v),
|
||||
}
|
||||
)
|
||||
calc = [_plain(r[0]) for r in sample[6:8]]
|
||||
assert calc[0].startswith("※ 인력품") and "8(hr)" in calc[1]
|
||||
return [
|
||||
table(
|
||||
"1-2-8",
|
||||
"시공단위 품 소수자리",
|
||||
"시공단위 품 산정 — 일당시공량 단위별 소수자리",
|
||||
"3 가",
|
||||
{"소수자리": "자리"},
|
||||
rows,
|
||||
cond={"일당시공량": "고르기"},
|
||||
extra={
|
||||
"비고": "품의 규격 · 단위수량을 고려해 소수자리 정도를 조정할 수 있음(원문 「참고하여」)"
|
||||
},
|
||||
),
|
||||
table(
|
||||
"1-2-8",
|
||||
"시공단위 품 산정 식",
|
||||
"시공단위 품 산정 식 — 인력품 · 장비품",
|
||||
"3 가",
|
||||
{"식": "식", "단위": "글"},
|
||||
[
|
||||
{"구분": "인력", "식": "인력(인) ÷ 시공량(일당)", "단위": "인"},
|
||||
{"구분": "장비", "식": "장비(대) × 8(hr) ÷ 시공량(일당)", "단위": "hr"},
|
||||
],
|
||||
cond={"구분": "고르기"},
|
||||
note=calc,
|
||||
extra={"비고": "모든 로직의 품 환산 공통 규칙 — 일당시공량 기준 품을 시공단위 품으로"},
|
||||
),
|
||||
table(
|
||||
"1-2-8",
|
||||
"시공단위 품 표기 예시",
|
||||
"시공단위 품 — 소수자리 표기 예시(참고)",
|
||||
"3 가 [참고]",
|
||||
{"예시 시공량": "㎡", "품": "인 · hr"},
|
||||
ex,
|
||||
cond={"구분": "고르기", "수량": "수", "일당시공량": "고르기"},
|
||||
extra={"비고": "원문 [참고] 표를 줄로 폄 — 예시 시공량 = 원문 머리 괄호(㎡)"},
|
||||
),
|
||||
rule_table("1-2-8"),
|
||||
]
|
||||
|
||||
|
||||
def t_1_2_9() -> list[dict]:
|
||||
_, lines = section("1-2-9")
|
||||
small, sample = md_tables(lines)
|
||||
rows = [{"구분": N(a), "적용조건": b, "적용시공량": c} for a, b, c in small[1:]]
|
||||
note = notes(lines)
|
||||
ex, group, case = [], "", ""
|
||||
for r in sample[1:]:
|
||||
group = r[0].replace("\n", " ") or group
|
||||
case = r[1] or case
|
||||
row = {"적용": group, "예시": case, "규격": r[2], "현장수량": N(r[3])}
|
||||
for col, v in (("일당시공량", r[4]), ("적용시공량", r[5]), ("소요일수", r[6])):
|
||||
if v not in ("", "-"):
|
||||
row[col] = N(v)
|
||||
if r[7] not in ("", "-"):
|
||||
row["일수편차"], row["일수편차원문"] = num(r[7].split("\n")[0]), r[7].replace("\n", "")
|
||||
ex.append(row)
|
||||
return [
|
||||
table(
|
||||
"1-2-9",
|
||||
"소규모 적용시공량",
|
||||
"소규모(작업물량 제한) — 적용시공량",
|
||||
"",
|
||||
{"적용조건": "글", "적용시공량": "식"},
|
||||
rows,
|
||||
cond={"구분": "수"},
|
||||
note=note,
|
||||
base_="A 시공량(총 시공량) · B 1일시공량(표준품셈) · Q 적용시공량",
|
||||
extra={
|
||||
"비고": "「시공량/일」 명시 항목 · 재료량 제외 · A ≧ B = 미적용 · 로직(적용 배)은 PLAN 783-1 프로젝트 조정(B09)",
|
||||
"성격": SMALL,
|
||||
},
|
||||
대상=["작업량 조건", "할증"],
|
||||
),
|
||||
table(
|
||||
"1-2-9",
|
||||
"다수 규격 예시",
|
||||
"소규모(작업물량 제한) — 동일 항목 다수 규격 예시",
|
||||
"주③",
|
||||
{
|
||||
"현장수량": "m",
|
||||
"일당시공량": "m",
|
||||
"적용시공량": "m",
|
||||
"소요일수": "일",
|
||||
"일수편차": "",
|
||||
},
|
||||
ex,
|
||||
cond={"적용": "고르기", "예시": "고르기", "규격": "고르기"},
|
||||
extra={
|
||||
"비고": "원문 예시 표를 줄로 폄 · 일수편차 = 예시 첫 줄에만(원문 병합 칸) · 계 줄 = 원문 그대로",
|
||||
"성격": SMALL,
|
||||
},
|
||||
대상=["작업량 조건", "할증"],
|
||||
),
|
||||
]
|
||||
|
||||
|
||||
# ── 1-3-2 · 1-4-1 · 1-4-2 ────────────────────────────────────────────
|
||||
def t_1_3_4() -> list[dict]:
|
||||
return [
|
||||
rule_table("1-3-2"),
|
||||
rule_table("1-4-1"),
|
||||
rule_table(
|
||||
"1-4-2",
|
||||
{
|
||||
"비고": "W = 기본품 × (1 + a1 + … + an) · 합산(곱 아님) · 동일 성격 이중 적용 불가 — 성격 = 계수 표 「성격」 칸 · 로직(할증 배)은 PLAN 783-1 프로젝트 조정(B09)",
|
||||
},
|
||||
),
|
||||
]
|
||||
|
||||
|
||||
# ── 1-5 기타 ─────────────────────────────────────────────────────────
|
||||
WASTE = ("폐콘크리트류", "폐금속류", "폐보드류", "폐목재류", "폐합성수지류", "혼합폐기물")
|
||||
WATER_UNIT = "단위 칸"
|
||||
|
||||
|
||||
def t_1_5() -> list[dict]:
|
||||
out = []
|
||||
for n in range(1, 11):
|
||||
no = f"1-5-{n}"
|
||||
if n == 4:
|
||||
out += t_1_5_4()
|
||||
elif n == 6:
|
||||
out += t_1_5_6()
|
||||
out.append(rule_table(no))
|
||||
return out
|
||||
|
||||
|
||||
def t_1_5_4() -> list[dict]:
|
||||
_, lines = section("1-5-4")
|
||||
(md,) = md_tables(lines)
|
||||
assert [h.replace("\n", "") for h in md[0][3:]] == list(WASTE)
|
||||
rows, work, use = [], "", ""
|
||||
for r in md[1:]:
|
||||
work, use = r[0] or work, r[1].replace("\n", "") or use
|
||||
for kind, v in zip(WASTE, r[3:]):
|
||||
if num(v) is not None:
|
||||
rows.append(
|
||||
{"공사": work, "용도": use, "구조": r[2], "폐기물": kind, "발생량": num(v)}
|
||||
)
|
||||
return [
|
||||
table(
|
||||
"1-5-4",
|
||||
"건설폐기물 발생량",
|
||||
"환경관리비 — 건설폐기물 단위면적당 발생량",
|
||||
"2",
|
||||
{"발생량": "TON/㎡"},
|
||||
rows,
|
||||
cond={"공사": "고르기", "용도": "고르기", "구조": "고르기", "폐기물": "고르기"},
|
||||
note=notes(lines),
|
||||
base_="㎡당",
|
||||
extra={
|
||||
"비고": "원문 칸 「-」 = 줄 없음 · 건축물 해체는 설계도서 산출 우선(원문 2) · 주③ ④ = 폐콘크리트 수량 증가(이내)"
|
||||
},
|
||||
)
|
||||
]
|
||||
|
||||
|
||||
def t_1_5_6() -> list[dict]:
|
||||
_, lines = section("1-5-6")
|
||||
(md,) = md_tables(lines)
|
||||
rows = []
|
||||
for kind, unit, v in md[1:]:
|
||||
if re.fullmatch(r"[\d.]+", v):
|
||||
rows.append({"구분": kind, "단위": unit, "수량": N(v)})
|
||||
else:
|
||||
m = re.search(r"(\d+)∼(\d+)%", v)
|
||||
rows.append({"구분": kind, "단위": unit, "수량": [N(m[1]), N(m[2])], "수량원문": v})
|
||||
return [
|
||||
table(
|
||||
"1-5-6",
|
||||
"공사용수",
|
||||
"사용료 — 공사용수 소요량",
|
||||
"3",
|
||||
{"수량": WATER_UNIT},
|
||||
rows,
|
||||
cond={"구분": "고르기"},
|
||||
note=notes(lines),
|
||||
extra={
|
||||
"비고": "줄마다 단위 칸 · 잡용수 = 원문 「사용량비의 40∼50%」(범위 [아래, 위] %)"
|
||||
},
|
||||
)
|
||||
]
|
||||
|
||||
|
||||
def all_tables() -> list[dict]:
|
||||
"""원문 절 차례 — 같은 절은 만든 차례."""
|
||||
return [
|
||||
*t_1_2_2(),
|
||||
*t_1_2_3(),
|
||||
*t_1_2_4(),
|
||||
*t_1_2_5(),
|
||||
*t_1_2_6(),
|
||||
*t_1_2_8(),
|
||||
*t_1_2_9(),
|
||||
*t_1_3_4(),
|
||||
*t_1_5(),
|
||||
]
|
||||
@@ -2,6 +2,7 @@
|
||||
"""건설품셈 공통 1-2-7 운반 — 계수 표 여덟(계수_건설품셈_01장_적용기준.json 에 덧) + 로직 셋(새 파일).
|
||||
|
||||
돌리기: ./venv/Scripts/python.exe resources/master_data/scripts/build_로직_건설품셈_01장_적용기준.py
|
||||
- `… 나머지` = 782-2 1장 나머지(`_적용기준_나머지_표.py` · `_적용기준_나머지_로직.py`).
|
||||
- 원문 = 건설품셈 공통 `1-02_설계_및_수량.md` 1-2-7 운반(머리 ~ 1-2-8 앞).
|
||||
- 계수 표는 1-2-4 표 뒤 · 1-3-1 표 앞에 글로 끼움(기존 표 글자 그대로) · 화물자동차 적재량은 본문 md 표에서 읽음.
|
||||
- 로직 파일 `로직_건설품셈_01장_적용기준.json` 은 통째로 씀 · 차례 13(같은 차례 공통 2장보다 이름이 앞).
|
||||
@@ -616,4 +617,9 @@ def main() -> None:
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.stdout.reconfigure(encoding="utf-8")
|
||||
main()
|
||||
if sys.argv[1:] == ["나머지"]: # 782-2 1장 나머지 — 규칙 · 계수만 절 표 · 1-5-4 · 1-5-6 로직
|
||||
import _적용기준_나머지_로직
|
||||
|
||||
_적용기준_나머지_로직.run()
|
||||
else:
|
||||
main()
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""마스터 데이터 검사 도구 (`resources/master_data/_틀.md`).
|
||||
|
||||
돌리기: ./venv/Scripts/python.exe resources/master_data/scripts/check_master.py [틀|본문|로직|조합] [파일 …]
|
||||
돌리기: ./venv/Scripts/python.exe resources/master_data/scripts/check_master.py [틀|본문|로직|조합|목차] [파일 …]
|
||||
./venv/Scripts/python.exe resources/master_data/scripts/check_master.py 계산 GF000123 뒷길이=35 돌=견치돌 …
|
||||
|
||||
(1) 틀 — 파일 이름 · 머리 칸 · 줄 칸 · 출처 모양 · 키 모양·겹침 · 대장
|
||||
(2) 본문(`check_master_body.py`) — 표형 요소의 출처 절을 본문 md 에서 찾아 수를 맞댐 · 결손(본문 표에 있고 요소에 없음)·허구(요소에 있고 본문 절에 없음)
|
||||
(3) 로직 — 변수가 가리키는 요소·표·칸 · 찾기 조건 이름 · 로직 입력 · 재료 고르기 조건 · 돌고 도는 참조 · 끊긴 키
|
||||
(4) 조합 — 일위대가 조합이 담은 로직 키 · 같은 로직 두 번 · 빈 조합 · 조합을 담음 (`master_combo.py`)
|
||||
(목차) 건설품셈 공통 1장 원문 절 목록 ↔ 마스터 전수 — 통째 빠진 절 · 로직 없는 품·표·식 절(`check_master_toc.py`)
|
||||
(배) 로직 결과단위 배 ↔ 부르는 표 기준배(10㎡당 표를 원/㎡ 로직이 「/ 10」 없이 씀) (`master_basis.py`)
|
||||
(5) 계산 — 로직 키와 입력값으로 호표 줄별 수량·단가·금액 · 비목 합 (`master_formula.py`)
|
||||
수 뽑기는 `resources/knowledge/original/_pipeline/pum_md_tool.py` 의 것을 씀.
|
||||
@@ -35,6 +36,7 @@ import master_keys as mk # noqa: E402
|
||||
|
||||
# (2) 본문은 따로 모듈 — 부르는 쪽은 `cm.check_body` · `cm.DIVISIONS` 그대로
|
||||
from check_master_body import DIVISIONS, check_body # noqa: E402, F401
|
||||
from check_master_toc import check_toc # noqa: E402
|
||||
|
||||
BOOKS = (
|
||||
"산림품셈",
|
||||
@@ -562,6 +564,8 @@ def main(argv: list[str]) -> int:
|
||||
report["조합"] = mcb.check_all(files, whole)
|
||||
if mode in ("단위", "전부"):
|
||||
report["단위"] = check_units(files, whole)
|
||||
if mode in ("목차", "전부") and "계수_건설품셈_01장_적용기준.json" in files:
|
||||
report["목차"] = check_toc(files) # 공통 1장 원문 절 목록 ↔ 마스터 전수
|
||||
if mode in ("배", "전부"): # 결과단위 배 ↔ 부르는 표 기준배(읽을 때 파생 · `master_basis`)
|
||||
report["배"] = mbs.check_times(files)
|
||||
for title, found in report.items():
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""마스터 데이터 검사 — 원문 절 목록 ↔ 마스터 전수 대조(건설품셈 공통 1장) · `check_master.py` 가 부름.
|
||||
|
||||
- 절 = 원문 `###` 머리 전부 · 범위 = 머리부터 다음 같은 · 윗 단계 머리 앞까지(요구 630).
|
||||
- 가이드(1-1 · 1-2-1) 빼고 계산 대상 절마다 마스터 키(계수 · 로직)가 하나 이상 — 없으면 「통째 빠짐」.
|
||||
- 품 · 표 · 식 절(_대조_632 판정)은 로직이 있어야 함 · 1-2-9 · 1-4-2 = PLAN 783-1 로 미룸 표지.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import re
|
||||
from pathlib import Path
|
||||
|
||||
HERE = Path(__file__).resolve().parent
|
||||
CH1 = (
|
||||
HERE.parents[2]
|
||||
/ "resources/knowledge/original/원가계산/건설공사_표준품셈/본문/01_공통부문/제01장_적용기준"
|
||||
)
|
||||
WHO = ("건설품셈 공통", "01장 적용기준")
|
||||
GUIDE = ("1-1-", "1-2-1") # 사용자 판정(요구 632) — 가이드
|
||||
CALC = ("1-2-7", "1-2-9", "1-4-2", "1-5-4", "1-5-6") # 품 · 표 · 식(_대조_632 2장)
|
||||
DEFER = {"1-2-9": "783-1", "1-4-2": "783-1"} # 로직 = 프로젝트 조정(B09) 승인 뒤
|
||||
|
||||
|
||||
def sections(folder: Path = CH1) -> list[tuple[str, str]]:
|
||||
"""(절 번호, 이름) — 원문 차례."""
|
||||
out = []
|
||||
for md in sorted(folder.glob("1-*.md")):
|
||||
for line in md.read_text(encoding="utf-8").split("\n"):
|
||||
m = re.match(r"^### (\d+-\d+-\d+) (.+)$", line)
|
||||
if m:
|
||||
out.append((m[1], re.sub(r"\s*\(['‘].*$", "", m[2]).strip()))
|
||||
return out
|
||||
|
||||
|
||||
def _guide(no: str) -> bool:
|
||||
return no.startswith(GUIDE[0]) or no == GUIDE[1]
|
||||
|
||||
|
||||
def table(files: dict[str, dict], folder: Path = CH1) -> list[dict]:
|
||||
"""절마다 {절 · 이름 · 쓰임 · 판정 · 계수 · 로직 · 표지}."""
|
||||
keys: dict[str, dict[str, list[str]]] = {}
|
||||
for data in files.values():
|
||||
group = data.get("그룹")
|
||||
for row in data.get("표") or data.get("줄") or []:
|
||||
if (row.get("구분"), row.get("상세구분")) != WHO:
|
||||
continue
|
||||
kind = "로직" if group == "로직" else "계수"
|
||||
keys.setdefault(str(row.get("원문번호")), {}).setdefault(kind, []).append(row["키"])
|
||||
out = []
|
||||
for no, name in sections(folder):
|
||||
got = keys.pop(no, {})
|
||||
out.append(
|
||||
{
|
||||
"절": no,
|
||||
"이름": name,
|
||||
"쓰임": "가이드" if _guide(no) else "계산 대상",
|
||||
"판정": "품·표·식" if no in CALC else "규칙·계수만",
|
||||
"계수": got.get("계수", []),
|
||||
"로직": got.get("로직", []),
|
||||
"표지": f"{DEFER[no]} 로 미룸" if no in DEFER else "",
|
||||
}
|
||||
)
|
||||
for no, got in keys.items(): # 원문 목차에 없는 원문번호
|
||||
out.append(
|
||||
{
|
||||
"절": no,
|
||||
"이름": "",
|
||||
"쓰임": "목차 밖",
|
||||
"계수": got.get("계수", []),
|
||||
"로직": got.get("로직", []),
|
||||
}
|
||||
)
|
||||
return out
|
||||
|
||||
|
||||
def check_toc(files: dict[str, dict], folder: Path = CH1) -> list[str]:
|
||||
found = []
|
||||
for r in table(files, folder):
|
||||
where = f"공통 1장 {r['절']} {r['이름']}"
|
||||
if r["쓰임"] == "목차 밖":
|
||||
found.append(f"{where} · 원문 목차에 없는 원문번호 ← {r['계수'] + r['로직']}")
|
||||
elif r["쓰임"] == "계산 대상" and not r["계수"] and not r["로직"]:
|
||||
found.append(f"{where} · 통째 빠짐 — 계수 · 로직 키 없음")
|
||||
elif (
|
||||
r["쓰임"] == "계산 대상" and r["판정"] == "품·표·식" and not r["로직"] and not r["표지"]
|
||||
):
|
||||
found.append(f"{where} · 품·표·식 절인데 로직 없음")
|
||||
return found
|
||||
Reference in New Issue
Block a user