feat(801-5): M01 로직 단위 글 건설품셈 원문 꼴 — 시간당 ㎥ · ㎡ → hr · 초 → sec · mm · cm · kg → ㎜ · ㎝ · ㎏ · TON · t → ton · km/hr → ㎞/hr
- 대응표 master_unit_text.RULES(규칙마다 원문 근거 절) · 빌더 build_단위표기_801_5(--check · --list · 셈 지문 전후 같을 때만 씀) - M01 로직 53 개 · 칸 79 개 글만 바꿈(변수 입력 · 중간 단위 · 결과단위) · 계수 · 소요량 표 값칸 · 호표 줄 단위 · 원문이 두 꼴을 다 쓰는 단위(㎞ · km · 분 · min)는 안 건드림 - M02 사본 새로 씀 도구 M02_Copy_Units(M02_Copy_Symbols 의 fill 만 바꿔 끼움) — 사본 16 문서 28 칸 · 금액 전후 같음 - 검토 목록 docs/raw/요구_1104/_목록_801-5_단위표기.xlsx - 옛 시험 기대 고침: test_796_3 굴착기 cm 단위 초 → sec(원문 표 머리 「싸이클시간(Sec)」) - 새 test_801_5_units 6 Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,142 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""M01 로직 단위 글을 건설품셈 원문 꼴로 (PLAN 801-5 · 요구 663-2) — 글만 바꿈 · 셈 지문 그대로.
|
||||
|
||||
돌리기: ./venv/Scripts/python.exe resources/master_data/scripts/build_단위표기_801_5.py [--check | --list 파일.xlsx]
|
||||
- 대상 = M01 로직 줄의 입력 · 중간 변수 `단위` · `결과단위` — 규칙 · 근거 절 = `master_unit_text.RULES`.
|
||||
- 계수 · 소요량 표 값칸 · 호표 줄 단위 · 사용자가 이미 원문 꼴로 쓴 칸은 안 건드림(이미 같으면 그대로 — 두 번 돌려도 같음).
|
||||
- 쓰기 전 지문 대조 = 줄마다 엔진 셈 · 끝수 지문이 전후 같을 때만 씀(다르면 멈춤).
|
||||
- `--check` = 쓰지 않고 쓸 것이 남았는지만(남았으면 종료 코드 1) · `--list` = 검토 목록 xlsx 만 씀(마스터 안 씀).
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import copy
|
||||
import hashlib
|
||||
import json
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
HERE = Path(__file__).resolve().parent
|
||||
sys.path.insert(0, str(HERE.parents[2]))
|
||||
import master_auto as ma # noqa: E402
|
||||
import master_cut as mcut # noqa: E402
|
||||
import master_formula as mf # noqa: E402
|
||||
import master_unit_text as ut # noqa: E402
|
||||
|
||||
from M01_MasterData import M01_MasterData_Store as st # noqa: E402
|
||||
|
||||
|
||||
def plan(files: dict[str, dict]) -> tuple[list[dict], list[dict], dict[str, dict]]:
|
||||
"""(저장 묶음, 목록 줄들, 새 files) — 읽기 전용."""
|
||||
batch, rows, new_files = [], [], {}
|
||||
for name, data in files.items():
|
||||
if data.get("그룹") != "로직":
|
||||
new_files[name] = data
|
||||
continue
|
||||
changes, new_data = [], copy.deepcopy(data)
|
||||
for row in new_data.get("줄", []):
|
||||
hit = False
|
||||
for kind in ("입력", "중간"):
|
||||
for spec in row.get(kind) or []:
|
||||
now = ut.canon(spec.get("단위"))
|
||||
if now != (spec.get("단위") or ""):
|
||||
rows.append(_item(row, kind, spec["이름"], spec["단위"], now))
|
||||
spec["단위"], hit = now, True
|
||||
if row.get("결과단위"):
|
||||
now = ut.canon(row["결과단위"])
|
||||
if now != row["결과단위"]:
|
||||
rows.append(_item(row, "결과단위", "", row["결과단위"], now))
|
||||
row["결과단위"], hit = now, True
|
||||
if hit:
|
||||
changes.append({"op": "edit", "key": row["키"], "row": row})
|
||||
new_files[name] = new_data
|
||||
if changes:
|
||||
batch.append({"file": name, "version": st.read(name)[1], "changes": changes})
|
||||
return batch, rows, new_files
|
||||
|
||||
|
||||
def _item(row: dict, kind: str, var: str, before: str, after: str) -> dict:
|
||||
return {
|
||||
"로직": row["키"],
|
||||
"이름": row.get("이름", ""),
|
||||
"칸": kind,
|
||||
"변수": var,
|
||||
"지금": before,
|
||||
"원문 꼴": after,
|
||||
"규칙": ut.rule_of(before),
|
||||
}
|
||||
|
||||
|
||||
def fingerprint(whole: mf.Master, row: dict) -> str:
|
||||
"""줄 하나의 엔진 셈 · 끝수 지문 — 멈춤도 지문."""
|
||||
try:
|
||||
key = str(row["키"])
|
||||
raw = mf.run(
|
||||
whole,
|
||||
key,
|
||||
{
|
||||
**ma.values(whole, row),
|
||||
**{k: v for k, v in ma.defaults(whole, row).items() if v is not None},
|
||||
},
|
||||
)
|
||||
text = json.dumps(raw, default=str, sort_keys=True, ensure_ascii=False)
|
||||
text += json.dumps(
|
||||
mcut.engine(whole, key, raw), default=str, sort_keys=True, ensure_ascii=False
|
||||
)
|
||||
except Exception as e: # noqa: BLE001
|
||||
text = f"ERR {type(e).__name__} {e}"
|
||||
return hashlib.sha256(text.encode()).hexdigest()[:16]
|
||||
|
||||
|
||||
def differing(old: dict[str, dict], new: dict[str, dict]) -> list[str]:
|
||||
"""지문이 바뀐 로직 키들 — 고친 줄만 대조."""
|
||||
before, after = mf.Master(old), mf.Master(new)
|
||||
out = []
|
||||
for name, data in new.items():
|
||||
if data.get("그룹") != "로직":
|
||||
continue
|
||||
was = {r["키"]: r for r in old[name]["줄"]}
|
||||
for row in data["줄"]:
|
||||
if row != was[row["키"]] and fingerprint(before, was[row["키"]]) != fingerprint(
|
||||
after, row
|
||||
):
|
||||
out.append(row["키"])
|
||||
return out
|
||||
|
||||
|
||||
def write_list(rows: list[dict], path: Path) -> None:
|
||||
import openpyxl
|
||||
|
||||
book = openpyxl.Workbook()
|
||||
sheet = book.active
|
||||
sheet.title = "바뀜"
|
||||
cols = ["로직", "이름", "칸", "변수", "지금", "원문 꼴", "규칙"]
|
||||
sheet.append(cols)
|
||||
for r in rows:
|
||||
sheet.append([r[c] for c in cols])
|
||||
rule = book.create_sheet("대응표")
|
||||
rule.append(["규칙", "바꿀 글", "근거 절(건설공사 표준품셈 본문 아래)", "바뀐 칸 수"])
|
||||
for name, _, to, _, files in ut.RULES:
|
||||
rule.append([name, to, " · ".join(files), sum(name in r["규칙"] for r in rows)])
|
||||
path.parent.mkdir(parents=True, exist_ok=True)
|
||||
book.save(path)
|
||||
|
||||
|
||||
def main() -> None:
|
||||
files, _ = st.loaded()
|
||||
batch, rows, new_files = plan(files)
|
||||
print(f"쓸 것 — 로직 {len({r['로직'] for r in rows})} · 칸 {len(rows)}")
|
||||
if "--list" in sys.argv:
|
||||
write_list(rows, Path(sys.argv[sys.argv.index("--list") + 1]))
|
||||
return
|
||||
if "--check" in sys.argv:
|
||||
raise SystemExit(1 if batch else 0)
|
||||
if batch:
|
||||
bad = differing(files, new_files)
|
||||
if bad:
|
||||
raise SystemExit(f"셈 지문이 달라지는 로직 {len(bad)} — 멈춤: {bad[:10]}")
|
||||
print(st.save(batch))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,89 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""M01 로직 단위 글 → 건설품셈 원문 꼴(PLAN 801-5 · 요구 663-2) — 글만 바꿈 · 셈 · 환산 안 바꿈.
|
||||
|
||||
`canon(단위 글)` = 아래 `RULES` 를 차례로 적용(이미 원문 꼴이면 그대로 · 두 번 돌려도 같음).
|
||||
`RULES` 한 줄 = (이름, 지금 글 정규식, 바꿀 글, 원문 글, 근거 절들) — 근거 절 = `EVIDENCE_ROOT` 아래 파일 ·
|
||||
그 파일 안에 원문 글이 실제로 있어야 함(시험 `test_801_5_units`). 원문이 두 꼴을 다 쓰는 단위(㎞ · km · 분 · min)는 안 건드림.
|
||||
단위 칸 중 원문 표를 그대로 옮긴 계수 · 소요량 표 값칸 · 호표 줄 단위(요소 단위)는 대상 아님.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import re
|
||||
from pathlib import Path
|
||||
|
||||
EVIDENCE_ROOT = Path("resources/knowledge/original/원가계산/건설공사_표준품셈/본문")
|
||||
_A = r"(?<![A-Za-z])" # 영문 낱말 한가운데는 안 건드림
|
||||
_Z = r"(?![A-Za-z])"
|
||||
|
||||
# (이름, 지금 글 정규식, 바꿀 글, 원문 글, 근거 절들)
|
||||
RULES: list[tuple[str, str, str, str, list[str]]] = [
|
||||
(
|
||||
"시간당 ㎥ · ㎡ → hr",
|
||||
r"(?<=[㎥㎡])/시간(?![가-힣])",
|
||||
"/hr",
|
||||
"/hr",
|
||||
[
|
||||
"01_공통부문/제08장_건설기계/8-03-05_[40]콘크리트기계.md", # ㎥/hr
|
||||
"02_토목부문/제07장_항만공사/7-02_사석.md", # ㎡/hr
|
||||
],
|
||||
),
|
||||
(
|
||||
"사이클 시간 초 → sec",
|
||||
r"(?<![가-힣])초(?![가-힣])",
|
||||
"sec",
|
||||
"sec",
|
||||
[
|
||||
"01_공통부문/제05장_기초공사/5-02_연약지반처리.md", # cm : 1회 싸이클 타임(sec)
|
||||
"01_공통부문/제08장_건설기계/8-02-02_리퍼(유압식).md",
|
||||
],
|
||||
),
|
||||
(
|
||||
"mm → ㎜",
|
||||
rf"{_A}mm{_Z}",
|
||||
"㎜",
|
||||
"㎜",
|
||||
["04_기계설비부문/제13장_플랜트설비공사/13-01_플랜트_배관.md"],
|
||||
),
|
||||
(
|
||||
"cm → ㎝",
|
||||
rf"{_A}cm{_Z}",
|
||||
"㎝",
|
||||
"㎝",
|
||||
["01_공통부문/제08장_건설기계/8-03-06_[50]골재생산기계_등.md"],
|
||||
),
|
||||
("kg → ㎏", rf"{_A}kg{_Z}", "㎏", "㎏", ["01_공통부문/제01장_적용기준/1-02_설계_및_수량.md"]),
|
||||
(
|
||||
"km/hr → ㎞/hr",
|
||||
rf"{_A}km/hr",
|
||||
"㎞/hr",
|
||||
"㎞/hr",
|
||||
["01_공통부문/제08장_건설기계/8-02-09_롤러.md"],
|
||||
),
|
||||
(
|
||||
"TON · t → ton",
|
||||
rf"{_A}(?:TON|t){_Z}",
|
||||
"ton",
|
||||
"ton",
|
||||
["01_공통부문/제08장_건설기계/8-02-12_크러셔.md"],
|
||||
),
|
||||
]
|
||||
_COMPILED = [(name, re.compile(pat), to, tok, files) for name, pat, to, tok, files in RULES]
|
||||
|
||||
|
||||
def canon(unit) -> str:
|
||||
"""원문 꼴 단위 글 — 규칙에 안 걸리면 그대로."""
|
||||
text = str(unit or "")
|
||||
for _, pat, to, _, _ in _COMPILED:
|
||||
text = pat.sub(to, text)
|
||||
return text
|
||||
|
||||
|
||||
def rule_of(before: str) -> str:
|
||||
"""바뀐 단위가 걸린 규칙 이름들(검토 목록 · 대응표용)."""
|
||||
out, text = [], str(before or "")
|
||||
for name, pat, to, _, _ in _COMPILED:
|
||||
if pat.search(text):
|
||||
out.append(name)
|
||||
text = pat.sub(to, text)
|
||||
return " · ".join(out)
|
||||
Reference in New Issue
Block a user