feat(782-1): 건설 공통 1-2-7 운반 기초 — 계수 표 여덟 · 인력운반 로직 셋

- 계수_건설품셈_01장_적용기준.json 1-2-7 표 덧(CC000341~348): 소운반 거리 · 인력운반 기본공식 상수 · 지게운반 t · V · 벽돌운반 · 인력운반(기계설비) 왕복평균속도 · 준비작업시간 · 경사지 환산계수 α · 화물자동차 적재량 — 옛 표 목록이 1-2-5 밑에 붙였던 1-2-7 표 바로잡음
- 새 로직_건설품셈_01장_적용기준.json(차례 13 · 공통 2장 앞): GC001140 인력운반(지게) · GC001141 인력운반(기계설비) · GC001142 벽돌운반
- 지게 = N = V × T ÷ (120 × α × L + V × t) 또는 운반회수 직접 · 보통인부 = 무게 ÷ 1회 운반량 ÷ N · 무게 0 = 1-2-4 3 단위중량 표
- 새 빌더 build_로직_건설품셈_01장_적용기준.py · 지킴 시험 test_782_1_carry_127.py(재현 넷 0.547619 · 0.163556 · 0.654222 · 0.0132 · check_master)
- 기존 표 · 로직 글자 그대로 · check_master 전후 같음 · 계산전부 1461 → 1464 멈춤 0

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-11-04 18:44:25 +09:00
co-authored by Claude Opus 5.5
parent d65244b9a5
commit 187fda1055
5 changed files with 1174 additions and 1 deletions
@@ -0,0 +1,619 @@
# -*- coding: utf-8 -*-
"""건설품셈 공통 1-2-7 운반 — 계수 표 여덟(계수_건설품셈_01장_적용기준.json 에 덧) + 로직 셋(새 파일).
돌리기: ./venv/Scripts/python.exe resources/master_data/scripts/build_로직_건설품셈_01장_적용기준.py
- 원문 = 건설품셈 공통 `1-02_설계_및_수량.md` 1-2-7 운반(머리 ~ 1-2-8 앞).
- 계수 표는 1-2-4 표 뒤 · 1-3-1 표 앞에 글로 끼움(기존 표 글자 그대로) · 화물자동차 적재량은 본문 md 표에서 읽음.
- 로직 파일 `로직_건설품셈_01장_적용기준.json` 은 통째로 씀 · 차례 13(같은 차례 공통 2장보다 이름이 앞).
- 키는 `_키대장.json` 다음 번호(master_keys.issue) · 이미 넣었으면 멈춤(한 번 돌림).
"""
from __future__ import annotations
import json
import re
import sys
from decimal import Decimal
from pathlib import Path
HERE = Path(__file__).resolve().parent
MASTER = HERE.parent
sys.path.insert(0, str(HERE))
import master_keys as mk # noqa: E402
COEF = "계수_건설품셈_01장_적용기준.json"
LOGIC = "로직_건설품셈_01장_적용기준.json"
BODY = (
MASTER.parents[1]
/ "resources/knowledge/original/원가계산/건설공사_표준품셈/본문/01_공통부문/제01장_적용기준/1-02_설계_및_수량.md"
)
SRC = "건설품셈 공통 1-2-7"
HEAD = {"구분": "건설품셈 공통", "상세구분": "01장 적용기준"}
ANGLES = [6, 11, 17, 22, 27, 31, 35, 39, 42, 45]
class N(str):
"""원문 그대로의 수 글자."""
def raw(v) -> str:
if isinstance(v, N):
return str(v)
if v is None:
return "null"
if isinstance(v, list):
return "[" + ", ".join(raw(x) for x in v) + "]"
if isinstance(v, int) and not isinstance(v, bool):
return str(v)
if isinstance(v, dict):
return (
"{"
+ ", ".join(f"{json.dumps(k, ensure_ascii=False)}: {raw(x)}" for k, x in v.items())
+ "}"
)
return json.dumps(v, ensure_ascii=False)
def emit(t: dict) -> str:
"""기존 공통 1장 표 글 모양 — 머리 넷 한 줄 · 줄은 한 줄씩."""
head = ", ".join(f'"{k}": {raw(t[k])}' for k in ("키", "원문번호", "구분", "상세구분"))
out = [" {", f" {head},"]
for k in ("이름", "기준", "출처", "비고", "용도", "조건", "범위규칙", "값칸"):
if k in t:
out.append(f" {json.dumps(k, ensure_ascii=False)}: {raw(t[k])},")
out.append(' "줄": [')
out.append(",\n".join(" " + raw(r) for r in t["줄"]))
out.append(" ],")
if t["주"]:
out.append(' "주": [')
out.append(",\n".join(" " + raw(x) for x in t["주"]))
out.append(" ]")
else:
out.append(' "주": []')
out.append(" }")
return "\n".join(out)
# ── 본문 md ──────────────────────────────────────────────────────────
def section() -> list[str]:
lines = BODY.read_text(encoding="utf-8").split("\n")
a = next(i for i, x in enumerate(lines) if x.startswith("### 1-2-7 "))
b = next(i for i, x in enumerate(lines) if x.startswith("### 1-2-8 "))
return lines[a:b]
def md_tables(lines: list[str]) -> list[list[list[str]]]:
out, i = [], 0
while i < len(lines):
if not lines[i].startswith("|"):
i += 1
continue
rows = []
while i < len(lines) and lines[i].startswith("|"):
cells = [c.strip() for c in lines[i].strip().strip("|").split("|")]
if not all(re.fullmatch(r":?-+:?", c) for c in cells):
rows.append([c.replace("<br>", "\n") for c in cells])
i += 1
out.append(rows)
return out
_NUM = r"\d[\d,]*(?:\.\d+)?"
def cell(c: str):
"""값 칸 → (값, 원문) · 「-」 · 빈 칸 = (None, None)."""
c = c.strip()
if c in ("", "-"):
return None, None
if re.fullmatch(_NUM, c):
return N(c.replace(",", "")), None
m = re.fullmatch(rf"({_NUM})\s*∼\s*({_NUM})", c)
if m:
return [N(m[1].replace(",", "")), N(m[2].replace(",", ""))], None
m = re.fullmatch(rf"\(({_NUM})\)", c)
if m:
return N(m[1].replace(",", "")), c
m = re.match(rf"({_NUM})\n", c)
if m:
return N(m[1].replace(",", "")), c.replace("\n", "")
raise ValueError(f"값 칸 못 읽음 {c!r}")
TRUCK = ("6톤 차량", "8톤 차량", "11톤 차량", "20톤 트레일러")
def truck_rows(table: list[list[str]]) -> list[dict]:
"""화물자동차 적재량 — 「〃」 · 빈 종별 · 빈 단위는 윗줄 · 비고 「모자이크 포함」은 원문 묶음(타일 셋)."""
rows, kind, unit = [], "", ""
for r in table[2:]:
name, spec, u, *vals, note = r
kind = kind if name in ("", "〃") else name
unit = unit if u in ("", "〃") else u
if kind == "페인트" and spec == "":
spec = "(18ℓ)/통" # 원문 둘째 줄 = 18ℓ 통 괄호 값
row = {"종별": kind, "규격": spec.replace("\n", ""), "단위": unit}
for col, v in zip(TRUCK, vals):
value, orig = cell(v)
if value is not None:
row[col] = value
if orig is not None:
row[col + "원문"] = orig
if kind in ("타일", "크링커타일"):
row["비고"] = "모자이크 포함"
elif note:
row["비고"] = note.replace("\n", "")
rows.append(row)
return rows
def notes(lines: list[str], start: str) -> list[str]:
"""`start` 로 시작하는 줄 뒤 「[주]」 묶음(빈 줄까지)."""
i = next(i for i, x in enumerate(lines) if x.startswith(start))
i = next(j for j in range(i, len(lines)) if lines[j].strip() == "[주]")
out = []
for x in lines[i + 1 :]:
if not x.strip():
break
out.append(x.strip())
return out
# ── 계수 표 ──────────────────────────────────────────────────────────
def tables(lines: list[str], logic: dict) -> list[dict]:
md = md_tables(lines)
jige, brick, slope, truck = md[0], md[1], md[2], md[3]
assert jige[2][0] == "토사류" and brick[2][0] == "보통인부" and slope[0][0] == "경사도"
t = []
def add(label, name, src, use, cond, cols, rows, note=(), base="", extra=None):
t.append(
{
"열쇠": f"공통 1-2-7 {label}",
"이름": name,
"기준": base,
"출처": f"{SRC} {src}".strip(),
**(extra or {}),
"용도": {"공종": "운반", "대상": use, "로직키": logic.get(label, [])},
"조건": cond,
"값칸": cols,
"줄": rows,
"주": list(note),
}
)
one = [x.strip() for x in lines if re.match(r"^[가-다]\. ", x)][:3]
add(
"소운반 거리",
"소운반 — 품에 포함된 거리 · 경사면 환산",
"1",
["소운반", "환산"],
{},
{"소운반거리": "m", "직고": "m", "수평거리": "m"},
[{"소운반거리": N("20"), "직고": N("1"), "수평거리": N("6")}],
one,
base="소운반 규정",
)
add(
"인력운반 기본공식",
"인력운반 기본공식 — 1일 실작업시간 · 1회 운반량",
"2",
["인력", "작업량 조건"],
{},
{"실작업시간": "분", "1회 운반량": "㎏"},
[{"실작업시간": "480 - 30", "실작업시간원문": "480분-30분", "1회 운반량": N("25")}],
notes(lines, "2\\. "),
base="기본공식 상수",
extra={
"비고": "식 Q = N × q · N = T/(60 × L × 2/V + t) = VT/(120L + Vt) — Q 1일 운반량 · N 1일 운반횟수 · q 1회 운반량 · T 1일 실작업시간(분) · L 운반거리(m) · t 적재적하 시간(분) · V 평균왕복속도(m/hr) · 1회 운반량 25㎏ = [주] 「25㎏을 1인의 비율로」 · 3 주③"
},
)
rows = []
for kind, tt in (("토사류", jige[2][1]), ("석재류", jige[3][1])):
for road, v in zip(("양호", "보통", "불량"), jige[2][2:5]):
rows.append(
{
"종류": kind,
"운반로": road,
"적재적하시간": N(tt.removesuffix("분")),
"평균왕복속도": cell(v)[0],
}
)
add(
"지게운반",
"지게운반 — 적재적하 시간 · 평균왕복속도",
"3",
["인력", "작업량 조건"],
{"종류": "고르기", "운반로": "고르기"},
{"적재적하시간": "분", "평균왕복속도": "m/hr"},
rows,
notes(lines, "3\\. "),
base="기본공식 상수",
extra={
"비고": "원문 평균왕복속도 칸은 토사류 · 석재류 두 줄에 걸친 한 칸 — 두 종류에 같은 값"
},
)
rows = [
{"운반": "인력", "층수": N(str(i + 1)), "보통인부": cell(v)[0]}
for i, v in enumerate(brick[2][2:7])
]
assert "0.31" in brick[3][1]
rows.append({"운반": "리프트", "보통인부": N("0.31")})
add(
"벽돌운반",
"벽돌운반 — 층별 인력운반",
"4",
["인력"],
{"운반": "고르기", "층수": "수"},
{"보통인부": "인"},
rows,
notes(lines, "4\\. "),
base="1,000매당",
extra={
"비고": "리프트 줄 = 원문 비고 「리프트를 사용할 경우 보통인부 0.31인을 적용한다」 — 층수 없음(어느 층이든)"
},
)
add(
"인력운반(기계설비) 왕복평균속도",
"인력운반(기계설비) — 왕복평균속도",
"5 가",
["인력", "작업량 조건"],
{"도로상태": "고르기"},
{"왕복평균속도": "km/hr"},
[
{"도로상태": "양호", "왕복평균속도": N("2")},
{"도로상태": "보통", "왕복평균속도": N("1.5")},
{"도로상태": "불량", "왕복평균속도": N("1")},
{"도로상태": "물논", "왕복평균속도": N("0.5")},
],
["※ 도로상태 구분은 토목부분 참조"],
base="기본공식 상수",
)
add(
"인력운반(기계설비) 준비작업시간",
"인력운반(기계설비) — 준비작업시간 · 1회 운반량",
"5 가",
["인력", "작업량 조건"],
{},
{"준비작업시간": "분", "1회 운반량": "㎏"},
[{"준비작업시간": N("2"), "1회 운반량": N("25")}],
base="기본공식 상수",
extra={
"비고": "장대물 · 중량물 등 인력운반비 산출공식 — 운반비 = M/T × A(60 × 2 × L/V + t) · A 인력운반공 노임 · M 인력운반공 수(총운반량/1인당 1회 운반량) · L 운반거리(km) · V 왕복평균속도(km/hr) · T 1일 실작업시간 · t 준비작업시간"
},
)
pct, ang, alpha = slope[0][2:], slope[1][2:], slope[2][2:]
assert [int(a) for a in ang] == ANGLES
add(
"경사지 운반 환산계수",
"경사지 운반 환산계수(α)",
"5 나",
["환산"],
{"경사도": "수", "각도": "수"},
{"환산계수": ""},
[{"경사도": N(p), "각도": N(a), "환산계수": N(x)} for p, a, x in zip(pct, ang, alpha)],
["경사지 환산거리 a×L"],
base="환산계수",
extra={
"비고": "경사도 = % · 각도 = ° · 해석: 1 나 · 3 주⑤(직고 1m = 수평 6m)와 함께 지게운반(3)에도 씀"
},
)
seven = [
x.strip()
for x in lines[lines.index("7\\. 화물자동차의 적재량") :]
if re.match(r"^[가-라]\. ", x)
]
add(
"화물자동차 적재량",
"화물자동차의 적재량",
"7 라",
["작업량 조건", "운반"],
{"종별": "고르기", "규격": "고르기"},
{**{c: "단위 칸" for c in TRUCK}, "비고": ""},
truck_rows(truck),
seven,
base="차 1대당",
extra={
"비고": "줄마다 단위 칸 · 「〃」 종별 · 단위와 빈 종별 줄은 윗줄 · 괄호 값은 원문 칸(타일 8㎜ · 페인트 18ℓ — 페인트 둘째 줄 규격 「(18ℓ)/통」은 원문 괄호를 따름) · 범위 값은 [아래, 위]"
},
)
return t
# ── 로직 ────────────────────────────────────────────────────────────
def inp(name, desc, unit=None, pick=None, src=None):
d = {"이름": name, "설명": desc}
if unit:
d["단위"] = unit
if pick is not None:
d["고르기"] = pick
if src:
d["출처"] = src
return d
def weights() -> tuple[list[str], list[str]]:
data = json.loads((MASTER / COEF).read_text(encoding="utf-8"))
table = next(x for x in data["표"] if x["키"] == "CC000337")
kinds = list(dict.fromkeys(r["종별"] for r in table["줄"]))
shapes = ["해당 없음", *dict.fromkeys(r["형상"] for r in table["줄"] if "형상" in r)]
return kinds, shapes
def logics(key: dict) -> list[dict]:
kinds, shapes = weights()
angle = inp(
"경사각도", "운반로의 경사 각도 — 0 이면 평지(환산계수 1)", "°", [0, *ANGLES], f"{SRC} 5 나"
)
weight = [
inp(
"운반무게",
"운반할 자재 한 단위의 무게 — 0 이면 아래 재료의 단위중량 표 값(㎏/㎥)",
"㎏",
src="건설품셈 공통 1-2-4 3",
),
inp(
"재료종별",
"단위중량 표에서 고를 재료(운반무게 0 일 때)",
pick=kinds,
src="건설품셈 공통 1-2-4 3",
),
inp(
"재료형상",
"단위중량 표의 형상(운반무게 0 일 때)",
pick=shapes,
src="건설품셈 공통 1-2-4 3",
),
]
w_step = {
"이름": "무게",
"식": f"만약(운반무게 > 0, 운반무게, 찾기({key['CC000337']}, 종별=재료종별, 형상=재료형상).위)",
"단위": "㎏",
}
jige = {
"키": None,
"열쇠": "공통 1-2-7 인력운반(지게)",
**HEAD,
"이름": "인력운반(지게) — 건설 공통 1-2-7",
"결과단위": "원/자재 단위",
"출처": f"{SRC} 2 · 3",
"소유": "공용",
"비고": "품에 포함된 소운반 = 20m 이내(1-2-7 1 가) — 넘거나 2차 운반이면 이 로직으로 별도 계상(1 다) · 해석: 경사지 환산계수 α(5 나 표)를 지게운반에도 씀(1 나 · 3 주⑤ 직고 1m = 수평 6m) · 운반회수를 넣으면 식 대신 그 값(실무 엑셀이 1일 운반횟수를 값으로 넣은 경우) · 운반무게 0 = 재료의 단위중량 표(1-2-4 3) 위끝 · 1회 운반량 0 = 25㎏ · 실작업시간 0 = 450분",
"시험입력": {
"운반무게": 575,
"운반거리": 6,
"경사각도": 35,
"재료갈래": "석재류",
"운반로": "불량",
"회당운반량": 0,
"실작업시간": 0,
"운반회수": 42,
},
"입력": [
*weight,
inp(
"회당운반량",
"한 사람이 한 번에 지는 무게 — 0 이면 25㎏",
"㎏",
src=f"{SRC} 2 주 · 3 주③",
),
inp("운반거리", "수평 운반 거리(편도)", "m", src=f"{SRC} 2"),
angle,
inp(
"재료갈래",
"적재적하 시간 갈래(석재류 = 자갈 · 부순돌 · 조약돌 등)",
pick=["토사류", "석재류"],
src=f"{SRC} 3",
),
inp("운반로", "운반로 상태", pick=["양호", "보통", "불량"], src=f"{SRC} 3 주②"),
inp("실작업시간", "1일 실작업시간 — 0 이면 450분", "분", src=f"{SRC} 2"),
inp("운반회수", "1일 운반횟수를 직접 넣을 때 — 0 이면 식으로 셈", "회", src=f"{SRC} 2"),
],
"중간": [
w_step,
{
"이름": "운반량",
"식": f"만약(회당운반량 > 0, 회당운반량, 찾기({key['인력운반 기본공식']}).'1회 운반량')",
"단위": "㎏",
},
{
"이름": "환산계수",
"식": f"만약(경사각도 > 0, 찾기({key['경사지 운반 환산계수']}, 각도=경사각도).환산계수, 1)",
},
{"이름": "환산거리", "식": "환산계수 * 운반거리", "단위": "m"},
{
"이름": "작업시간",
"식": f"만약(실작업시간 > 0, 실작업시간, 찾기({key['인력운반 기본공식']}).실작업시간)",
"단위": "분",
},
{
"이름": "적재적하시간",
"식": f"찾기({key['지게운반']}, 종류=재료갈래, 운반로=운반로).적재적하시간",
"단위": "분",
},
{
"이름": "평균왕복속도",
"식": f"찾기({key['지게운반']}, 종류=재료갈래, 운반로=운반로).평균왕복속도",
"단위": "m/hr",
},
{
"이름": "일운반회수",
"식": "만약(운반회수 > 0, 운반회수, 평균왕복속도 * 작업시간 / (120 * 환산거리 + 평균왕복속도 * 적재적하시간))",
"단위": "회",
},
],
"호표": [
{
"종류": "인력",
"요소": "LB000002",
"이름": "보통인부",
"단위": "인",
"수량": "무게 / 운반량 / 일운반회수",
"비목": "노무비",
"출처": f"{SRC} 2",
}
],
"덧줄": [],
"끝수": None,
}
machine = {
"키": None,
"열쇠": "공통 1-2-7 인력운반(기계설비)",
**HEAD,
"이름": "인력운반(기계설비) — 건설 공통 1-2-7",
"결과단위": "원/자재 단위",
"출처": f"{SRC} 5",
"소유": "공용",
"비고": "장대물 · 중량물 등 — 운반비 = M/T × A(60 × 2 × L/V + t) · M = 운반무게 ÷ 1회 운반량 · 해석: T = 1일 실작업시간 450분(1-2-7 2) · 경사지는 환산거리 α × L(5 나)",
"시험입력": {
"운반무게": 100,
"운반거리": 0.1,
"도로상태": "보통",
"경사각도": 0,
"회당운반량": 0,
"실작업시간": 0,
},
"입력": [
*weight,
inp("회당운반량", "인력운반공 1회 운반량 — 0 이면 25㎏", "㎏", src=f"{SRC} 5 가"),
inp("운반거리", "운반 거리(편도)", "km", src=f"{SRC} 5 가"),
inp(
"도로상태",
"운반로 도로상태",
pick=["양호", "보통", "불량", "물논"],
src=f"{SRC} 5 가",
),
angle,
inp("실작업시간", "1일 실작업시간 — 0 이면 450분", "분", src=f"{SRC} 2"),
],
"중간": [
w_step,
{
"이름": "운반량",
"식": f"만약(회당운반량 > 0, 회당운반량, 찾기({key['인력운반(기계설비) 준비작업시간']}).'1회 운반량')",
"단위": "㎏",
},
{"이름": "인원", "식": "무게 / 운반량", "단위": "인"},
{
"이름": "환산계수",
"식": f"만약(경사각도 > 0, 찾기({key['경사지 운반 환산계수']}, 각도=경사각도).환산계수, 1)",
},
{"이름": "환산거리", "식": "환산계수 * 운반거리", "단위": "km"},
{
"이름": "왕복평균속도",
"식": f"찾기({key['인력운반(기계설비) 왕복평균속도']}, 도로상태=도로상태).왕복평균속도",
"단위": "km/hr",
},
{
"이름": "준비작업시간",
"식": f"찾기({key['인력운반(기계설비) 준비작업시간']}).준비작업시간",
"단위": "분",
},
{
"이름": "작업시간",
"식": f"만약(실작업시간 > 0, 실작업시간, 찾기({key['인력운반 기본공식']}).실작업시간)",
"단위": "분",
},
],
"호표": [
{
"종류": "인력",
"요소": "LB000045",
"이름": "인력운반공",
"단위": "인",
"수량": "인원 / 작업시간 * (60 * 2 * 환산거리 / 왕복평균속도 + 준비작업시간)",
"비목": "노무비",
"출처": f"{SRC} 5 가",
}
],
"덧줄": [],
"끝수": None,
}
brick = {
"키": None,
"열쇠": "공통 1-2-7 벽돌운반",
**HEAD,
"이름": "벽돌운반 — 건설 공통 1-2-7",
"결과단위": "원/1,000매",
"출처": f"{SRC} 4",
"소유": "공용",
"비고": "기본벽돌(19×9×5.7㎝)을 인력으로 층별(층고 3.6m) 운반 · 리프트를 쓰면 층수와 관계없이 보통인부 0.31인",
"입력": [
inp("운반", "운반 방법", pick=["인력", "리프트"], src=f"{SRC} 4"),
inp("층수", "올릴 층(리프트면 무관)", "층", [1, 2, 3, 4, 5], f"{SRC} 4"),
],
"중간": [],
"호표": [
{
"종류": "인력",
"요소": "LB000002",
"이름": "보통인부",
"단위": "인",
"수량": f"찾기({key['벽돌운반']}, 운반=운반, 층수=층수).보통인부",
"비목": "노무비",
"출처": f"{SRC} 4",
}
],
"덧줄": [],
"끝수": None,
}
return [jige, machine, brick]
USES = { # 표 → 그 표를 부르는 로직(열쇠)
"소운반 거리": [],
"인력운반 기본공식": ["인력운반(지게)", "인력운반(기계설비)"],
"지게운반": ["인력운반(지게)"],
"벽돌운반": ["벽돌운반"],
"인력운반(기계설비) 왕복평균속도": ["인력운반(기계설비)"],
"인력운반(기계설비) 준비작업시간": ["인력운반(기계설비)"],
"경사지 운반 환산계수": ["인력운반(지게)", "인력운반(기계설비)"],
"화물자동차 적재량": [],
}
LOGIC_LABELS = ("인력운반(지게)", "인력운반(기계설비)", "벽돌운반")
def main() -> None:
coef_path, logic_path = MASTER / COEF, MASTER / LOGIC
text = coef_path.read_text(encoding="utf-8")
if '"원문번호": "1-2-7"' in text or logic_path.exists():
raise SystemExit("이미 넣음 — 멈춤")
book = mk.load_book()
lkey = {x: mk.issue(book, "GC", f"공통 1-2-7 {x}", LOGIC) for x in LOGIC_LABELS}
uses = {label: [lkey[x] for x in v] for label, v in USES.items()}
lines = section()
made = tables(lines, uses)
ckey = {"CC000337": "CC000337"}
for t in made:
label = t["열쇠"].removeprefix("공통 1-2-7 ")
ckey[label] = mk.issue(book, "CC", t["열쇠"], COEF)
t["키"], t["원문번호"] = ckey[label], "1-2-7"
t.update(HEAD)
at = text.index(' {\n "키": "CC000001"')
new_text = text[:at] + "".join(emit(t) + ",\n" for t in made) + text[at:]
data = json.loads(new_text, parse_float=Decimal, parse_int=Decimal)
assert sum(1 for x in data["표"] if x["원문번호"] == "1-2-7") == len(made)
coef_path.write_text(new_text, encoding="utf-8", newline="\n")
rows = []
for row in logics(ckey):
label = row.pop("열쇠").removeprefix("공통 1-2-7 ")
row["키"], row["원문번호"] = lkey[label], "1-2-7"
order = ["키", "원문번호", *[k for k in row if k not in ("키", "원문번호")]]
rows.append({k: row[k] for k in order})
head = {
"그룹": "로직",
"원문": "건설품셈",
"부문": "공통",
"차례": 13,
"판": "2026",
"줄": rows,
}
logic_path.write_text(mk._store().dump(head), encoding="utf-8", newline="\n")
mk.save_book(mk.finish(book))
print("계수", [t["키"] for t in made], "로직", list(lkey.values()))
if __name__ == "__main__":
sys.stdout.reconfigure(encoding="utf-8")
main()