Files
Aislo/resources/tester/test_778_3_hopyo_unit_lines.py
eomsangdonandClaude Opus 5.5 660e3318a9 feat(778-3): 단위 줄 정규화 — 단가산출 블록 · 일위대가 줄 = 노무 · 기계 · 자재 · 율 · 하위 · 값 줄(D-1 · 요구 622 · 623 · 624)
- 새 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>
2026-11-04 13:20:03 +09:00

175 lines
7.3 KiB
Python
Raw Permalink 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.
"""778-3(D-1) 단위 줄 — 단가산출 블록 · 일위대가 줄을 같은 계산 단위 꼴로(`M02_Hopyo_UnitLine`).
꼴(EST 글 · STmate 행) · 같은 기계 코드 두 묶음 따로 · 비목마다 매개값 `계수` · 율 행 · 셈 밖 ·
여덟 파일(불리는 블록) 줄 수 · 재현 · 덮음 · 할증 풀이 · 요구 624(이름 없는 율 줄 · 두 행 노무 ·
손료율 표지). 읽기만 · 서버 없음.
"""
from __future__ import annotations
import warnings
from collections import Counter
from functools import lru_cache
import pytest
from M02_MasterTemplete import M02_Hopyo_Excel as hx
from M02_MasterTemplete import M02_Hopyo_ReadEst as est
from M02_MasterTemplete import M02_Hopyo_Sangeun as sg
from M02_MasterTemplete import M02_Hopyo_Stmate as st
from M02_MasterTemplete import M02_Hopyo_UnitLine as ul
from resources.tester.helper_m02_hopyo_golden import FILES
warnings.filterwarnings("ignore", category=UserWarning, module="openpyxl")
KEYS = {"묶음", "이름", "규격", "품", "단가", "금액", "어떻게", "행", "절", "윗절"}
def test_EST_글_단위_줄():
text = [
" 1. 굴착기(무한궤도)(0.7㎥)",
" 노무비 : 59,020 ÷ 26.95 × 0.9 = 1,970.9",
" 재료비 : 19,473 ÷ 26.95 = 722.5",
" 2. 인 부",
" 보통인부 : 172,068 × 0.011인 = 1,892.7 (지주)",
" 합판(내수),대전 (12×1220=2440) 28,500. × 0.1 = 2,850.",
" 각재(외송) 500,000 × 0.038 ㎥ × 38. % = 7,220 ₩ / ㎡",
" 특별인부 : P",
" 3. 공구손료(인력품의 3%)",
" 1,892.7 × 0.03 = 56.7",
" 레미콘 : 별도계상",
" 4. 합 계",
" 보통인부 : 172,068 × 0.011인 = 1,892.7",
]
got = est.items(text, list(range(1, len(text) + 1)))
book = {"단산": [{"번": 1, "금액": {"계": 14712.8}, "항목": got}], "호표": []}
ul.settle(book)
kinds = [(u["묶음"], bool(u.get("밖"))) for u in got]
assert kinds == [
("X", False),
("L", False),
("M", False),
("M", False),
("R", False),
("M", False),
("L", True),
] # fmt: skip — 「특별인부 : P」 선언 줄 뺌
assert all(KEYS <= set(u) for u in got)
mach, labor, mat, loss, rate = got[:5]
assert mach["품"] == pytest.approx(0.9 / 26.95) and mach["계수"] == {
"재료비": pytest.approx(1 / 0.9)
}
assert mach["금액"] == pytest.approx(2693.4) and mach["재현"]
assert labor["직종"] == "보통인부" and labor["금액"] == 1892.7 and labor["재현"]
assert mat["이름"].startswith("합판") and mat["품"] == 0.1 and mat["재현"] # 끝 점 · 이름 속 =
assert loss["손료율"] == 0.38 and loss["금액"] == 7220 and loss["재현"] # 손료율 = 자재 줄 표지
assert rate["품"] == 0.03 and rate["기준값"] == 1892.7 and rate["기준"] == "인력품"
assert (
rate["재현"] and rate["절"] == "공구손료(인력품의 3%)" and rate["이름"].startswith("공구")
)
assert rate["기준꼴"] == "인력" and rate["기준줄"] == [5]
assert book["단산"][0]["줄합"] == pytest.approx(14712.8) and book["단산"][0]["덮음"]
def _row(**cells) -> list:
row = [None] * 24
for col, v in cells.items():
row[int(col[1:])] = v
return row
def test_STmate_같은_코드_두_묶음_따로_계수_율_밖():
machine = "굴삭기(0.7m3) / 0.7㎥,(암석)"
rows = [
_row(c0="제 1호표", c2=100, c6="`BDCOD|D00001"),
_row(c1=" (1) 채집"),
_row(c1=" ① 굴삭기", c2=10, c3=10, c7=0.1, c8=100, c9=100, c12="X00009_1", c23=machine),
_row(c1=" 재료비", c2=5, c4=5, c7=0.05, c8=100, c10=100, c12="X00009_1", c23=machine),
_row(c1=" (2) 상차"),
_row(c1=" 노무비", c2=20, c3=20, c7=0.2, c8=100, c9=100, c12="X00009_1", c23=machine),
_row(
c1=" 기구손료 : 30 * 2 %",
c2=0.6,
c4=0.6,
c6="&PA01M:M& * 2'%' =",
c7=2,
c8=30,
c12="PR_1",
),
_row(c1=" 기구손료 : (가공노력품의 9%)"),
_row(
c1=" 30 * 0.09 = 2.7",
c2=2.7,
c4=2.7,
c6="&RA01:M& * 0.09 =",
c7=0.09,
c8=30,
c12="RR_1",
),
_row(
c1=" 보통인부 : 1 * 0.5",
c2=50,
c3=50,
c7=0.5,
c8=100,
c9=100,
c12="L00002_1",
c13="C1_1",
c23="보통인부 / ",
), # fmt: skip
_row(c0="총계"),
]
blocks = st._blocks(rows, {})
units = blocks["D00001"]["항목"]
xs = [u for u in units if u["묶음"] == "X"]
assert [x["품"] for x in xs] == [0.1, 0.2] # 두 묶음 따로(단가 두 배 아님)
assert xs[0]["단가"]["계"] == 200 and xs[1]["단가"]["계"] == 100
assert xs[0]["계수"] == {"재료비": 0.5} and xs[0]["금액"] == 15
rate, bare = [u for u in units if u["묶음"] == "R"]
assert rate["품"] == 0.02 and rate["기준"] == "PA01M" and rate["기준값"] == 30
assert bare["이름"] == "기구손료" and bare["기준글"] == "가공노력품" and bare["품"] == 0.09
assert next(u for u in units if u["묶음"] == "L").get("밖") is True
assert [r["코드"] for r in blocks["D00001"]["자원"]] == ["X00009", "X00009", "L00002"]
@lru_cache(maxsize=1)
def _books() -> dict:
return {tag: hx.read_book(p.name, p.read_bytes()) for tag, p in FILES.items()}
def test_여덟_파일_불리는_블록_줄_재현_덮음():
kinds, outside, redo, cover = Counter(), Counter(), Counter(), Counter()
lines = Counter()
for book in _books().values():
_, first = sg.calls(book)
called = {int(k) for k in first}
for block in book["단산"]:
if int(block["번"]) not in called:
continue
cover[block["덮음"]] += 1
for u in block["항목"]:
if u.get("밖"):
outside[u["묶음"]] += 1
continue
kinds[u["묶음"]] += 1
redo[u["재현"]] += 1
for hopyo in book["호표"]:
lines.update(bool(x["재현"]) for x in hopyo["줄"])
assert dict(kinds) == {"L": 366, "X": 190, "M": 130, "R": 152, "S": 3, "E": 1}
assert dict(outside) == {"L": 2}
assert redo[True] >= 836 and sum(redo.values()) == 842 # 전 811/836
assert cover[True] == 204 and sum(cover.values()) == 206 # 남은 둘 = 엑셀 자체 흠(합이 줄 뺌)
assert lines[True] == 1344 and sum(lines.values()) == 1350
def test_할증_풀이_같은_코드_따로():
books = _books()
block = next(b for b in books["울진소광"]["단산"] if b["번"] == 1448)
xs = [u for u in block["항목"] if u["묶음"] == "X"]
assert [x["할증"] for x in xs] == [1.2, 1.2, 1.25] and all(x["재현"] for x in xs)
block = next(b for b in books["울진대흥"]["단산"] if b["번"] == 1129)
dig = [u for u in block["항목"] if u.get("코드") == "X00009"]
assert len(dig) == 2 and all(u["단가"]["계"] == 100378 and u["재현"] for u in dig)
block = next(b for b in books["봉화"]["단산"] if b["번"] == 3)
dump = next(u for u in block["항목"] if u["이름"].startswith("덤프"))
assert dump["계수"]["재료비"] == pytest.approx(0.1527, abs=1e-4) and dump["재현"]