Files
Aislo/resources/master_data/scripts/_토목_읽기.py
T
eomsangdonandClaude Sonnet 5 a09fbdfd78 feat(master_data): 건설 토목 1장 2장 6장 소요량 옮김
- 소요량_건설품셈_토목1장(116표) · 토목2장(9표) · 토목6장(73표) — md 표 73 · 8 · 50 개 모두 + 글 속 계수 표
- 변환 스크립트 build_소요량_건설품셈_토목.py + _토목_읽기 · _토목_표 · _토목_손 · _토목_계수
- check_master 틀 · 본문 · 로직 0건

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PAdA5ThqmtVSsbzjusk1cJ
2026-09-19 17:16:42 +09:00

195 lines
6.5 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# -*- 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(("<!--", "![")):
if re.match(r"^[①-⑳]", s):
(tabs[-1].notes if tabs and tabs[-1].ident == ident else []).append(unesc(s))
elif tabs and tabs[-1].ident == ident and tabs[-1].notes:
tabs[-1].notes[-1] += " " + unesc(s)
elif re.fullmatch(r"\(.*\)", s) and not s.startswith("(주"):
base = s
elif s.startswith("<별표>"):
pre = s
i += 1
return tabs, text, order
def unit_split(h):
"""머리 「배관공(수도)(인)」 → (이름, 단위)."""
h = join(h)
m = re.search(r"\(([^()]*)\)$", h)
if m and m.group(1) in UNITS:
return h[: m.start()].strip(), m.group(1)
return h, ""
def fill_right(row, start):
out, prev = [], ""
for c in row[start:]:
prev = c if c else prev
out.append(prev)
return out
def is_num(x):
return bool(re.fullmatch(NUM, x.strip()))
class Out:
def __init__(self):
self.tables, self.keys = [], set()
def add(
self, t, label, cond, cols, rows, *, src=None, base=None, group=None, rule=None, notes=None
):
ident = t.ident if t else src
key = f"{ident} {label}"
assert key not in self.keys, key
self.keys.add(key)
d = {
"열쇠": key,
"이름": label,
"기준": (t.base if t else "") if base is None else base,
"출처": DIV + (src or ident),
}
if group:
d["그룹"] = group
d["조건"] = cond
if rule:
d["범위규칙"] = rule
d["값칸"] = cols
d["줄"] = rows
d["주"] = list(t.notes if (t and notes is None) else (notes or []))
self.tables.append(d)
def write(self, path):
L = ["{", ' "그룹": "소요량",', ' "원문": "건설품셈",', ' "판": "2026",', ' "표": [']
for ti, t in enumerate(self.tables):
L.append(" {")
keys = list(t)
for ki, k in enumerate(keys):
end = "," if ki < len(keys) - 1 else ""
v = t[k]
if k in ("줄", "주") and v:
L.append(f" {js(k)}: [")
L += [
f" {js(x)}" + ("," if xi < len(v) - 1 else "")
for xi, x in enumerate(v)
]
L.append(f" ]{end}")
else:
L.append(f" {js(k)}: {js(v)}{end}")
L.append(" }" + ("," if ti < len(self.tables) - 1 else ""))
L += [" ]", "}", ""]
Path(path).write_text("\n".join(L), encoding="utf-8", newline="\n")
def kind_of(vals):
"""조건 칸 종류 — 모두 수면 수 · 아니면 고르기."""
return "수" if all(is_num(v) for v in vals) else "고르기"
def nz(x):
return x not in ("", "-")
def cell_val(x):
"""값 칸 하나 → (값, 원문 or None). 수 하나가 든 글 = 수 + 원문 · 그 밖 글은 글."""
x = join(x)
if is_num(x):
return N(x), None
nums = re.findall(NUM, x)
if len(nums) == 1 and re.search(r"[가-힣%a-zA-Z]", x) and not re.search(r"[∼~\s]", x):
return R(nums[0]), x
return x, None