- 새 M02_Hopyo_UnitLine — 단위 줄 꼴 · 자체 검산(줄 재현 0.5 % · 줄합 = 블록 금액 덮음) · 값 줄 → 하위 가름 · 율 줄 기준꼴 · 기준줄 · 할증 풀이 · 일위대가 줄 투영(묶음 · 재현 · % 기준) · 두 어댑터 끝에 settle - ReadEst.items — 줄 금액 · 율 · 값 줄 · 합계 · 소계 절 = 밖 · 끝 점 「28,500. ×」 · 이름 속 = · / 자재 · 「(지주)」 · 「\/㎡」 금액 꼬리 · 「직종 : P」 선언 줄 뺌 · 자재 손료율 표지 · 기계 비목마다 매개값 계수 · 절 = 제목 - Stmate — 같은 기계 코드 두 묶음 따로(울진대흥 1129 · 2119 · 울진소광 1448 단가 두 배 흠) · 비목마다 H 다르면 계수(봉화 덤프 (Cm-t1)/Cm · 영월 물탱크 ×0.722) · PR · RR 율 행(기준 표지 PA01M 꼴) · 이름 없는 율 행 = 위 라벨 줄 이름 · 기준글 · G 열 「* N'%'」 자원 = 율 줄 · 자재 손료율 · D 행 = 하위 · N 열 C = 밖 - Store.BOOK 3 — 읽기 꼴 바뀜(옛 판 HP = 다시 올림) - 여덟 파일 불리는 블록: 줄 재현 811/836 → 836/842 · 덮음 201 → 204/206(남은 둘 = 엑셀 합이 진동로울러 노무 뺌) · 일위대가 줄 1,344/1,350 - 새 시험 test_778_3_hopyo_unit_lines Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
399 lines
19 KiB
Python
399 lines
19 KiB
Python
"""M02 일위대가 실무자료 STmate 꼴 읽기(PLAN 338-2b) — 「일위대가표」 · 「단가산출근거」 시트 +
|
||
목록표(노무비 · 중기 · 재료비 · 경비 · 단가산출근거) → 엑셀꼴과 같은 HP 꼴
|
||
(`M02_Hopyo_Excel.read_book`).
|
||
|
||
일위대가표 = A 열 「제N호표」 줄(B~Z 열 「BDCOD|B…」 = 호표 코드) → 다음 줄 A 이름 · B 규격 ·
|
||
D 단위 → 구성 줄(A 이름 · B 규격 · C 수량 · D 단위 · E 계 · G 노무비 · I 재료비 · K 경비 단가 ·
|
||
다음 열 금액 · M 비고) → 「합계」 줄에서 끝.
|
||
줄 코드 = M~Z 열에서 `_CODE` 꼴 첫 것(판마다 열 수가 달라도 읽힘 · 하이퍼링크 안 읽음) ·
|
||
코드 없는 줄 = 호표 `근거글`.
|
||
갈래 = D 단가산출(참조 단산 · 번 = 코드 숫자) · B 호표(참조 일위 · 번 = 호표 코드 표) · L 노임 ·
|
||
X 기계 · M 자재(비고 「별산」 · 규격 「별도계상」 · 값 없음 = 별도계상) · S · W 별도계상 ·
|
||
PR · 단위 % = % · 그 밖 기타.
|
||
단가산출근거 = 「BDCOD|D…」 머리 → 다음 머리 · 「총계」 앞까지 한 블록 · 글 = B 열(머리 이름 줄 ·
|
||
소계 · 계 뺌) · 자원 = M 열 「X00000_n」 줄(품 = H 열 · 이름 · 규격 = X 열 「이름 / 규격」 ·
|
||
비면 목록표 · 기계 셋 줄(노무비 · 재료비 · 경비)은 하나로 · 절 = 그 앞 번호 절 제목) ·
|
||
머리 이름 · 규격 · 단위 = 단가산출근거목록표 · 출처 = 블록 글 `ReadEst.sources` ·
|
||
직종 = 자원 L 이름(우리 인력 · 옛이름 = 지금 이름) ·
|
||
금액 = 머리 줄 C~F(1 단위) · 항목(778-3 · 단위 줄 꼴 = `M02_Hopyo_UnitLine`) = 행마다
|
||
자원 L 노무 · X 기계 · M 자재 · S 경비 = E 값(품 = H · 단가 I~L · 줄 금액 C · 어떻게 「QTY」) ·
|
||
기계 셋 줄 = 같은 코드 · 절 · 아직 빈 비목일 때만 하나(같은 코드 두 묶음 = 따로 ·
|
||
비목마다 H 가 다르면 `계수`) · G 열 「&자원& * N'%'」 = R 율(기준값 = I) ·
|
||
PR · RR 행 = R 율(PR = N % · RR = 곱 · 기준 = G 열 표지 「&PA01M:M&」 앞) ·
|
||
D 코드 행 = S 하위 · N 열 「C…」 = `밖`(소계(제외금액)) · 자원 = 자원 코드 줄 옛 칸.
|
||
검산 안 함 — `검산 {어긋남: [], 총괄표: 0}`.
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
import re
|
||
from typing import Any
|
||
|
||
from M02_MasterTemplete import M02_Hopyo_Excel as hopyo_excel # 서로 부름 — 부를 때만 씀
|
||
from M02_MasterTemplete import M02_Hopyo_ReadEst as hopyo_est
|
||
from M02_MasterTemplete import M02_Hopyo_UnitLine as unit_line
|
||
|
||
SHEET = "일위대가표"
|
||
CALC = "단가산출근거"
|
||
LISTS = ("단가산출근거목록표", "노무비목록표", "중기목록표", "재료비목록표", "경비목록표")
|
||
SHEETS = (SHEET, CALC, *LISTS)
|
||
_CODE = re.compile(r"^(?:COD\|)?([A-Z]{1,2}\d{5}|PR[A-Z]?\d+|[TC]\d+)(?:_\d+)?(?: →)?$")
|
||
_META = re.compile(r"(?<![A-Z])COD\|([A-Z]{1,2}\d{5})") # 숨은 칸 「`COD|L00001」
|
||
_HEAD_B = re.compile(r"BDCOD\|(B\d{5})")
|
||
_HEAD_D = re.compile(r"BDCOD\|(D\d{5})")
|
||
_RES = re.compile(r"^([LMXS])(\d{5})_\d+$")
|
||
_RES_A = re.compile(r"^([LMXS]\d{5})[LMS]$") # A 열 「L00002L」
|
||
_RES_G = re.compile(r"&([LMXS]\d{5})[LMS]&") # G 열 식 「&L00002L&」
|
||
_SECTION = re.compile(r"^\s*(?:\d+\s*[.)]|[①-⑩])\s*(.*\S)")
|
||
_COLS = {"합계": 4, "노무비": 6, "재료비": 8, "경비": 10}
|
||
_CALC_AMOUNT = {"합계": 2, "노무비": 3, "재료비": 4, "경비": 5} # 단가산출근거 C~F 금액
|
||
_CALC_PRICE = {"합계": 8, "노무비": 9, "재료비": 10, "경비": 11} # I~L 단가(T · L · M · S)
|
||
NOTE, CODE_COLS, RES_COL, QTY_COL, NAME_COL = 12, range(12, 26), 12, 7, 23
|
||
FORMULA_COL, OUT_COL = 6, 13 # G 식 · N 합 갈 곳(C = 셈 밖 「소계(제외금액)」)
|
||
COSTS = ("노무비", "재료비", "경비")
|
||
_KIND = {"L": "L", "X": "X", "M": "M", "S": "E"} # 자원 코드 머리 → 단위 줄 묶음(S 경비 = 값)
|
||
_RES_CODE = re.compile(r"^[LMXS]\d{5}$")
|
||
_RATE = re.compile(r"^(?:PR|RR)[A-Z]?_") # 율 행 「PR_1」 · 「RRS_(8)_1」
|
||
_SUB_D = re.compile(r"^(D\d{5})_\d+$") # 다른 블록 부름
|
||
_PCT_REF = re.compile(r"&([A-Z][A-Z0-9_]*?)(?::[A-Z])?&\s*\*\s*[\d.]+\s*'%'\s*=") # 율 꼴 자원 행
|
||
_LOSS = re.compile(r"\*\s*([\d.]+)\s*'%'") # 자재 손료율 「* 0.443'%'」
|
||
_BASE_WORD = re.compile(r"([가-힣]+)의\s*[\d.]+\s*%") # 율 기준글 「(인건비의 3%)」
|
||
_NUMBERED = re.compile(r"^\s*(?:\d+|[가-하])\s*[.)]\s*")
|
||
_TAG = re.compile(r"&([A-Z][A-Z0-9_]*?)(?::[A-Z])?&") # 율 기준 표지 「&PA01M:M&」 → PA01M
|
||
|
||
|
||
def is_stmate(names: Any) -> bool:
|
||
return SHEET in names and CALC in names
|
||
|
||
|
||
def _cell(row: list[Any], col: int) -> Any:
|
||
return row[col] if col < len(row) else None
|
||
|
||
|
||
def _s(value: Any) -> str:
|
||
return hopyo_excel._text(value)
|
||
|
||
|
||
def _flat(value: Any) -> str:
|
||
return re.sub(r"\s+", "", str(value or ""))
|
||
|
||
|
||
def _code(row: list[Any], cols: Any = CODE_COLS) -> str | None:
|
||
for col in cols:
|
||
hit = _CODE.match(_s(_cell(row, col)))
|
||
if hit:
|
||
return hit.group(1)
|
||
return None
|
||
|
||
|
||
def _calc_money(row: list[Any], cols: dict[str, int]) -> dict[str, int | float | None]:
|
||
return {k: hopyo_excel._out(v) for k, v in hopyo_excel._money(row, cols, 0).items()}
|
||
|
||
|
||
def _add(into: dict[str, Any], more: dict[str, Any]) -> None:
|
||
"""기계 셋 줄(노무비 · 재료비 · 경비) 단가 · 금액을 한 항목에 더함."""
|
||
for k, v in more.items():
|
||
if v is not None:
|
||
into[k] = hopyo_excel._out(hopyo_excel._num(into.get(k) or 0) + hopyo_excel._num(v))
|
||
|
||
|
||
def _lists(sheets: dict[str, list[list[Any]]]) -> dict[str, dict[str, str]]:
|
||
"""목록표 → {코드: {이름, 규격, 단위}} — B 이름 · C 규격 · D 단위 · 코드 = 숨은 칸
|
||
「COD|…」 · 없으면 코드 꼴 첫 칸(판마다 A 열 · 뒤 열)."""
|
||
out: dict[str, dict[str, str]] = {}
|
||
for title in LISTS:
|
||
for row in sheets.get(title) or []:
|
||
meta = _META.search(" ".join(_s(v) for v in row))
|
||
code = meta.group(1) if meta else _code(row, range(len(row)))
|
||
if code and _s(_cell(row, 1)):
|
||
out[code] = {
|
||
"이름": _s(row[1]),
|
||
"규격": _s(_cell(row, 2)),
|
||
"단위": _s(_cell(row, 3)),
|
||
}
|
||
return out
|
||
|
||
|
||
# ── 단가산출근거 블록 ──────────────────────────────────────────────────
|
||
def _part(row: list[Any]) -> str | None:
|
||
"""기계 셋 줄 가운데 이 줄 비목(I~L 단가 칸 · 노무비 › 재료비 › 경비)."""
|
||
return next((k for k in COSTS if _cell(row, _CALC_PRICE[k]) not in (None, "")), None)
|
||
|
||
|
||
def _same_machine(last: dict[str, Any] | None, code: str, title: str, part: str | None) -> bool:
|
||
"""바로 앞 기계 줄과 한 기계(셋 줄)인가 — 같은 코드 · 절 · 이 비목이 아직 빔
|
||
(같은 코드 두 묶음 = 따로 · 울진대흥 1129)."""
|
||
return bool(
|
||
last
|
||
and last["묶음"] == "X"
|
||
and last["코드"] == code
|
||
and last["절"] == title
|
||
and part
|
||
and part not in last["_q"]
|
||
)
|
||
|
||
|
||
def _unit(row: list[Any], code: str, title: str, info: dict[str, str]) -> dict[str, Any]:
|
||
"""자원 행 → 단위 줄(L 노무 · X 기계 · M 자재 · S 경비 = E 값 · G 열 「'%'」 = R 율)."""
|
||
name, _, spec = _s(_cell(row, NAME_COL)).partition("/")
|
||
qty = hopyo_excel._num(_cell(row, QTY_COL))
|
||
q = float(qty) if qty is not None else None
|
||
one = {
|
||
"묶음": _KIND.get(code[0], "E"),
|
||
"코드": code,
|
||
"이름": name.strip() or info.get("이름", ""),
|
||
"규격": spec.strip() if name.strip() else info.get("규격", ""),
|
||
"단위": info.get("단위", ""),
|
||
"품": q,
|
||
"절": title,
|
||
}
|
||
if rate := _PCT_REF.search(_s(_cell(row, FORMULA_COL))): # 「기구손료 : &L00020& * 2'%'」
|
||
base = hopyo_excel._num(_cell(row, _CALC_PRICE["합계"]))
|
||
more = {"기준": rate.group(1), "기준값": float(base) if base is not None else None}
|
||
return {**one, "묶음": "R", "품": q / 100 if q is not None else None, **more}
|
||
if one["묶음"] == "M" and (loss := _LOSS.search(_s(_cell(row, FORMULA_COL)))):
|
||
one["손료율"] = round(float(loss.group(1)) / 100, 9) # 「&M00081& * 1.03'㎡'* 0.443'%'」
|
||
return one
|
||
|
||
|
||
def _rate_row(row: list[Any], code: str, text: str, label: str) -> dict[str, Any]:
|
||
"""PR · RR 율 행 → 단위 줄 R(기준 = G 열 표지 「&PA01M:M&」 앞 · PR = N % · RR = 곱) · 이름이
|
||
없는 식 줄(「210,212.3 * 0.09」)은 위 라벨 줄(「기구손료 : (가공노력품의 9%)」)에서 이름 ·
|
||
기준글 · 못 찾으면 이름 빈 줄 + `이름없음`."""
|
||
qty = hopyo_excel._num(_cell(row, QTY_COL))
|
||
base = hopyo_excel._num(_cell(row, _CALC_PRICE["합계"]))
|
||
tag = _TAG.search(_s(_cell(row, FORMULA_COL)))
|
||
rate = float(qty) / (100 if code.startswith("PR") else 1) if qty is not None else None
|
||
name = text.split(":")[0] if ":" in text else ""
|
||
if not re.search(r"[가-힣A-Za-z]", name):
|
||
name = _NUMBERED.sub("", label.split(":")[0] if ":" in label else label)
|
||
word = _BASE_WORD.search(f"{text} {label}")
|
||
return {
|
||
"묶음": "R",
|
||
"코드": code,
|
||
"이름": name.strip()[:40],
|
||
"규격": "",
|
||
"품": rate,
|
||
"기준": tag.group(1) if tag else "",
|
||
"기준값": float(base) if base is not None else None,
|
||
**({"기준글": word.group(1)} if word else {}),
|
||
**({} if name.strip() else {"이름없음": True}),
|
||
}
|
||
|
||
|
||
def _blocks(rows: list[list[Any]], table: dict[str, dict[str, str]]) -> dict[str, dict[str, Any]]:
|
||
"""{D 코드: {번, 코드, 이름, 규격, 단위, 행, 출처, 직종, 글, 자원, 금액, 항목}}."""
|
||
names = hopyo_est.trade_names()
|
||
out: dict[str, dict[str, Any]] = {}
|
||
block: dict[str, Any] | None = None
|
||
title = label = "" # 절 제목 · 율 행 위 라벨 줄(코드 · 금액 없는 글)
|
||
for at, row in enumerate(rows, 1):
|
||
head = next(
|
||
(hit for v in row[:8] if (hit := _HEAD_D.search(_s(v)))), None
|
||
) # 머리 = G 열(판마다 B 열 글이 다름)
|
||
if head:
|
||
code = head.group(1)
|
||
info = table.get(code) or {}
|
||
block = out.setdefault(
|
||
code,
|
||
{
|
||
"번": int(code[1:]),
|
||
"코드": code,
|
||
"이름": info.get("이름", ""),
|
||
"규격": info.get("규격", ""),
|
||
"단위": info.get("단위", ""),
|
||
"행": [at, at],
|
||
"글": [],
|
||
"금액": _calc_money(row, _CALC_AMOUNT),
|
||
"항목": [],
|
||
},
|
||
)
|
||
title = label = ""
|
||
continue
|
||
if block is None:
|
||
continue
|
||
first = _flat(_cell(row, 0))
|
||
if first.startswith("총계"):
|
||
block["행"][1] = at
|
||
block = None
|
||
continue
|
||
text, cell = _s(_cell(row, 1)), _s(_cell(row, RES_COL))
|
||
res = _RES.match(cell)
|
||
code = res.group(1) + res.group(2) if res else None
|
||
if not code:
|
||
alt = _RES_A.match(first) or _RES_G.search(_s(_cell(row, FORMULA_COL)))
|
||
code = alt.group(1) if alt else None
|
||
rate = None if code else _RATE.match(cell)
|
||
sub = None if code or rate else _SUB_D.match(cell)
|
||
if not text and not code:
|
||
continue
|
||
block["행"][1] = at
|
||
if cell == block["코드"]: # 머리 이름 줄 「이름 규격 / 단위」
|
||
if not block["이름"]:
|
||
name, _, unit = text.rpartition("/")
|
||
block["이름"], block["단위"] = name.strip() or text, unit.strip()
|
||
continue
|
||
if text and _flat(text) not in ("소계", "계"):
|
||
if len(block["글"]) < hopyo_est.GLINES:
|
||
block["글"].append(text[: hopyo_est.GCHARS])
|
||
if (sec := _SECTION.match(text)) and not re.match(r"^\s*\d+\.\d", text):
|
||
title = sec.group(1).strip()
|
||
money = _calc_money(row, _CALC_AMOUNT)
|
||
place = {
|
||
"단가": _calc_money(row, _CALC_PRICE),
|
||
"금액": money.get("계"),
|
||
"행": at,
|
||
"절": title,
|
||
"윗절": title,
|
||
**({"밖": True} if _s(_cell(row, OUT_COL)).startswith("C") else {}),
|
||
}
|
||
if rate:
|
||
block["항목"].append({**_rate_row(row, cell, text, label), **place, "어떻게": "율"})
|
||
continue
|
||
if code or sub or money.get("계") is not None:
|
||
label = "" # 라벨은 바로 위 글 줄만
|
||
elif text:
|
||
label = text.strip()
|
||
if sub and sub.group(1) != block["코드"] and money.get("계"):
|
||
qty = hopyo_excel._num(_cell(row, QTY_COL))
|
||
one = {"묶음": "S", "코드": sub.group(1), "이름": text[:40], "규격": ""}
|
||
more = {"품": float(qty) if qty is not None else None, "어떻게": "QTY"}
|
||
block["항목"].append(
|
||
{**one, **more, **place, "하위": {"종류": "단산", "번": int(sub.group(1)[1:])}}
|
||
)
|
||
continue
|
||
if not code:
|
||
continue
|
||
last, part = block["항목"][-1] if block["항목"] else None, _part(row)
|
||
if code[0] == "X" and _same_machine(last, code, title, part):
|
||
_add(last["단가"], place["단가"]) # 기계 노무비 · 재료비 · 경비 셋 줄 = 하나
|
||
last["금액"] = hopyo_excel._out(
|
||
hopyo_excel._num(last["금액"] or 0) + hopyo_excel._num(place["금액"] or 0)
|
||
)
|
||
last["_q"][part] = hopyo_excel._num(_cell(row, QTY_COL))
|
||
continue
|
||
one = _unit(row, code, title, table.get(code) or {})
|
||
who = hopyo_est.trade(one["이름"]) if one["묶음"] == "L" else None
|
||
one = {**one, **({"직종": who} if one["묶음"] == "L" else {}), "밑품": one["품"]}
|
||
one.update(율=[], 인자=[], 어떻게="율" if one["묶음"] == "R" else "QTY", **place)
|
||
if one["묶음"] == "X":
|
||
one["_q"] = {part: hopyo_excel._num(_cell(row, QTY_COL))} if part else {}
|
||
block["항목"].append(one)
|
||
for block in out.values():
|
||
for one in block["항목"]:
|
||
if "_q" in one:
|
||
unit_line.machine_done(one)
|
||
block["자원"] = [ # 옛 칸(S1 투영) — 자원 코드 줄만
|
||
{"갈래": one["코드"][0], **{k: one[k] for k in ("코드", "이름", "규격", "품", "절")}}
|
||
for one in block["항목"]
|
||
if _RES_CODE.match(one["코드"])
|
||
]
|
||
block["출처"] = hopyo_est.sources(" ".join(block["글"]))
|
||
trades = [names.get(_flat(r["이름"])) for r in block["자원"] if r["갈래"] == "L"]
|
||
block["직종"] = list(dict.fromkeys(t for t in trades if t))
|
||
return out
|
||
|
||
|
||
# ── 일위대가표 ────────────────────────────────────────────────────────
|
||
def _hopyos(rows: list[list[Any]]) -> tuple[list[dict[str, Any]], list[list[dict[str, Any]]]]:
|
||
hopyos: list[dict[str, Any]] = []
|
||
raw: list[list[dict[str, Any]]] = []
|
||
want_head = done = False
|
||
for at, row in enumerate(rows, 1):
|
||
first = _s(_cell(row, 0))
|
||
hit = hopyo_excel._HOPYO.fullmatch(_flat(first))
|
||
if hit:
|
||
code = next((m.group(1) for v in row[1:26] if (m := _HEAD_B.search(_s(v)))), None)
|
||
hopyos.append({"번": int(hit.group(1)), "코드": code, "행": [at, at], "근거글": []})
|
||
raw.append([])
|
||
want_head, done = True, False
|
||
continue
|
||
if not hopyos or done:
|
||
continue
|
||
hopyo = hopyos[-1]
|
||
if want_head:
|
||
hopyo.update(이름=first, 규격=_s(_cell(row, 1)), 단위=_s(_cell(row, 3)))
|
||
hopyo["금액"] = {}
|
||
want_head = False
|
||
continue
|
||
if _flat(first).startswith("합계") or _flat(first) == "계":
|
||
hopyo["금액"] = hopyo_excel._money(row, _COLS, 1)
|
||
done = True # 끝 — 다음 호표까지 줄 안 받음
|
||
continue
|
||
if not first:
|
||
continue
|
||
code = _code(row)
|
||
hopyo["행"][1] = at
|
||
if not code:
|
||
if re.search(r"[0-9A-Za-z가-힣]", first):
|
||
hopyo["근거글"].append(f"{first} {_s(_cell(row, 1))}".strip())
|
||
continue
|
||
if code[0] in "TC" and code[1].isdigit():
|
||
continue # 계 · 소계 · 계약단가
|
||
line = {
|
||
"차례": len(raw[-1]),
|
||
"이름": first,
|
||
"규격": _s(_cell(row, 1)),
|
||
"수량": hopyo_excel._num(_cell(row, 2)),
|
||
"단위": _s(_cell(row, 3)),
|
||
"단가": hopyo_excel._money(row, _COLS, 0),
|
||
"금액": hopyo_excel._money(row, _COLS, 1),
|
||
"비고": _s(_cell(row, NOTE)),
|
||
"코드": code,
|
||
"행": at,
|
||
}
|
||
line["갈래"] = hopyo_excel.kind_of(line) # 갈래 한 규칙(776-2)
|
||
line["참조"] = (
|
||
{"종류": "단산", "번": int(code[1:]), "코드": code}
|
||
if code[0] == "D"
|
||
else {"종류": "일위", "번": None, "코드": code}
|
||
if code[0] == "B"
|
||
else None
|
||
)
|
||
raw[-1].append(line)
|
||
return hopyos, raw
|
||
|
||
|
||
def read(sheets: dict[str, list[list[Any]]]) -> dict[str, Any]:
|
||
"""STmate 시트 → HP 꼴 `{꼴, 시트, 호표, 단산, 검산}`(값은 JSON 수)."""
|
||
hopyos, raw = _hopyos(sheets.get(SHEET) or [])
|
||
if not hopyos:
|
||
raise hopyo_excel.ExcelError(f"{SHEET} 시트에 호표(제N호표)가 없음")
|
||
table = _lists(sheets)
|
||
blocks = _blocks(sheets.get(CALC) or [], table)
|
||
by_code = {h["코드"]: h["번"] for h in hopyos if h["코드"]}
|
||
for line in (one for lines in raw for one in lines):
|
||
ref = line["참조"] or {}
|
||
if ref.get("종류") == "일위":
|
||
ref["번"] = by_code.get(ref["코드"])
|
||
elif ref.get("종류") == "단산" and (block := blocks.get(ref["코드"])):
|
||
line["근거"] = {k: block[k] for k in ("출처", "직종", "행")}
|
||
out = []
|
||
for hopyo, lines in zip(hopyos, raw, strict=True):
|
||
for key in ("이름", "규격", "단위"):
|
||
hopyo.setdefault(key, "")
|
||
if not hopyo["근거글"]:
|
||
del hopyo["근거글"]
|
||
out.append({**hopyo_excel._json(hopyo), "줄": [hopyo_excel._json(x) for x in lines]})
|
||
keep = (
|
||
"번",
|
||
"코드",
|
||
"이름",
|
||
"규격",
|
||
"단위",
|
||
"행",
|
||
"출처",
|
||
"직종",
|
||
"글",
|
||
"자원",
|
||
"금액",
|
||
"항목",
|
||
)
|
||
return {
|
||
"꼴": "STmate",
|
||
"시트": SHEET,
|
||
"호표": out,
|
||
"단산": [{k: b[k] for k in keep} for b in blocks.values()],
|
||
"검산": {"어긋남": [], "총괄표": 0},
|
||
}
|