Merge remote-tracking branch 'origin/sub_laptop_3' into main_laptop_1

This commit is contained in:
2026-11-13 21:21:06 +09:00
2 changed files with 42 additions and 7 deletions
+9 -6
View File
@@ -5,7 +5,7 @@
일위 구성 줄 꼴(원파일 · 엑셀 같은 꼴 — 읽기기 답 `일위` 블록 `줄`):
{차례, 코드(자원 · PR · RR · C1 · T1 · 빈), 갈래(M L S X B D W · 율 · 닫기 · 구분 · 글), 이름, 규격, 단위,
수량(1 차), 단가 {T L M S}?(셈에 쓴 자원 단가 · 중기 조종원 = 시간 환산), 금액 {T L M S}?(줄 금액),
율? {밑, 대상(M L S T), 곱(RR)}, 밑줄?(율 밑수에 듦), 관급?, 별도계상?(자재 이름 · 규격 「별도계상」 · 「별산」)}
율? {밑, 대상(M L S T), 곱(RR)}, 밑줄?(율 밑수에 듦), 관급?, 별도계상?(자재 · 경비 · 일식 이름 · 규격 「별도계상」 · 「별산」)}
블록 = {코드, 이름, 규격, 단위, 저장, 줄, 줄합 {T L M S}(다시 셈 · 끝전 뒤), 표지}
율 줄 이름 = 지은 글(「노무비의 3%」 · 원문 이름 칸은 못 읽음).
HP 호표(`hopyo`) = 엑셀 일위대가표 읽기(`M02_Hopyo_Stmate.read`)와 같은 꼴 — 글 · 구분 · 닫기 줄 뺌.
@@ -123,9 +123,8 @@ def lines(rows: list[dict], res: dict[str, dict], opt: dict) -> tuple[list[dict]
one["밑줄"] = True
if row.get("KWNGP") == "1":
one["관급"] = True
if kind == "M" and re.search(
r"별도계상|별산", one["이름"] + one["규격"]
): # 엑셀 비고 「별산…」 짝
apart = re.search(r"별도계상|별산", one["이름"] + one["규격"])
if kind in ("M", "S", "W") and apart: # 엑셀 비고 「별산…」 짝
one["별도계상"] = True
out.append(one)
before = dict(s)
@@ -260,6 +259,10 @@ def hopyo(blocks: list[dict]) -> list[dict[str, Any]]:
line = {"차례": len(rows), **{k: one[k] for k in ("이름", "규격", "수량", "단위")}}
line |= {"단가": _hp(one.get("단가")), "금액": _hp(one.get("금액")), "코드": code}
line["비고"] = "별산" if one.get("별도계상") else ""
if rate := one.get(
"율"
): # 숨은 칸 율 밑 · 대상(828-2 `M02_Hopyo_Fixed._ratio` 와 같은 글)
line["율"] = {k: WORD[rate[k]] for k in ("밑", "대상")}
for k in ("계", "노무비", "재료비", "경비"):
line["단가"].setdefault(k, 0)
line["금액"].setdefault(k, 0)
@@ -320,8 +323,8 @@ def materials(saved: dict):
for b in saved.get("일위") or []:
head = f"{b.get('이름') or ''} {b.get('규격') or ''}"
for ln in b.get("줄") or []:
if ln.get("갈래") != "M":
continue
if ln.get("갈래") != "M" or str(ln.get("단위") or "").strip().endswith("%"):
continue # 율 자원(「잡재료비 재료비의 %」) = 재료 아님
res = {k: ln.get(k) or "" for k in ("이름", "규격", "단위", "코드")} | {"종류": "자재"}
with mf.clues_on(saved.get("단서"), head):
link.m01("재료", res, "")
+33 -1
View File
@@ -211,7 +211,39 @@ def test_line_shape() -> None:
kinds = {ln["갈래"] for t in stc.FILES for x in parsed(t)["일위"] for ln in x["줄"]}
assert kinds <= {"M", "L", "S", "X", "B", "D", "W", "율", "닫기", "구분", "글"}
apart = [ln for x in parsed("동부청영월")["일위"] for ln in x["줄"] if ln.get("별도계상")]
assert len(apart) == 17 # 엑셀 비고 「별산…」 17 줄과 같음(분석 813 2 장)
assert len(apart) == 18 # 엑셀 비고 「별산…」 줄과 같음(자재 17 + 경비 S11039 · 828-2)
def test_same_lines_as_excel_reader() -> None:
"""828-2 엑셀 읽기기(`M02_San_Xlsx_Ilwi` 숨은 칸) 일위 줄 = 원파일 줄(코드 · 갈래 · 수량 · 율 · 표시 · 136 · 706)."""
from M02_MasterTemplete import M02_San_Book as book
from M02_MasterTemplete import M02_San_Xlsx as sx
def key(x: dict) -> tuple:
r, code = x.get("율") or {}, x["코드"]
qty = None if x["갈래"] == "율" else round(float(x["수량"] or 0), 6)
flags = tuple(bool(x.get(k)) for k in ("밑줄", "관급", "별도계상"))
return code[:1] if code[:1] in "MLSXBDW" else code, x["갈래"], qty, r.get("밑"), r.get("대상"), flags # fmt: skip
hops = lines = 0
for xt, st in PAIR.items():
path = golden.FILES[xt]
if not path.exists():
pytest.skip(f"실무 엑셀 없음 {xt}")
xl = sx.read_sheets(book.sheets(path.name, path.read_bytes()))
nat = parsed(st)["일위"]
by_code = {b["코드"]: b for b in nat}
by_name = {(_flat(b["이름"]), _flat(b["규격"])): b for b in nat}
for b in xl["일위"]:
head = (_flat(b["이름"]), _flat(b["규격"]))
y = by_code.get(b["코드"])
if (
y is None or (_flat(y["이름"]), _flat(y["규격"])) != head
): # 울진대흥 = 코드 새로 매김
y = by_name[head]
assert [key(x) for x in b["줄"]] == [key(x) for x in y["줄"]], (xt, b["코드"])
hops, lines = hops + 1, lines + len(b["줄"])
assert (hops, lines) == (136, 706)
# ── ④ 중기 구성 ──────────────────────────────────────────────────────────