"""155-5 산근 텍스트 수식 — 엑셀 단가산출근거 꼴 글 줄(`B09_Estimation_Sangeun_Text`). - 엑셀 6호표 뒷채움: 「보통인부 : 165,545 * 0.018 = 2,979.8」 · 중기 노 · 재 · 경 세 줄 · 「계 : 노무비 6,878 + 재료비 538 + 경비 912 = 8,328」(줄 ROUNDDOWN 1 · 계 ROUNDDOWN 0) - 엑셀 7호표 막자갈채집: 작업량 Q = ROUND 2 중간 줄 · 「55,700 * 1 / 21.59 * 2 = 5,159.7」 - % 덧줄 = 「공구손료 (노무비의 %) : 6,878.8 * 3 % = 206.3」(규격 = 149-4 `pct_extras`) - 단가 없음 = 「단가 없음」 글 · 계 그 비목 빈칸 · 글 속 금액 = 산근 풀이(`Sangeun.Solver`) 줄 값 """ import copy import sys from pathlib import Path ROOT = Path(__file__).resolve().parents[2] if str(ROOT) not in sys.path: sys.path.insert(0, str(ROOT)) from B09_Estimation import B09_Estimation_Sangeun as sg # noqa: E402 from B09_Estimation import B09_Estimation_Sangeun_Text as st # noqa: E402 from M02_MasterTemplete import M02_Item_Prices as item_prices # noqa: E402 BASE = "GF000218" # 기초다짐 및 뒷채움(기계) — 보통인부 + 굴착기 0.2㎥ 로직 줄 GIVEN = {"규격": 150, "지역": "전국평균", "보정작업": "해당 없음"} EXCEL_EQ = {"labor": 55700, "material": 7695, "expense": 13041} # 중기목록표 굴삭기 0.2㎥ def _whole(edit=None): """사본 단가 묶음 — `edit(로직 줄)` 로 GF000218 을 엑셀 견본 꼴로 바꿈(수량 · 중간 · 덧줄).""" files = copy.deepcopy(item_prices.to_json(item_prices.collect([BASE]))) if edit: for data in files.values(): for row in data.get("줄") or []: if isinstance(row, dict) and row.get("키") == BASE: edit(row) return item_prices.load_prices(files) def _excel(qty_labor: str, qty_eq: str, mids=(), extras=()): def edit(row): labor, eq = row["호표"][0], row["호표"][1] row["호표"] = [{**labor, "수량": qty_labor}, {**eq, "수량": qty_eq}] row["중간"] = [{"이름": n, "식": f} for n, f in mids] row["덧줄"] = list(extras) return edit def _prices(labor): return lambda key: EXCEL_EQ if key.startswith("EQ") else labor.get(key) def test_엑셀_6호표_뒷채움_같은_글(): whole = _whole(_excel("0.018", "0.07")) got = st.render_sangeun_text(whole, BASE, GIVEN, _prices({"LB000002": 165545})) assert got[:3] == ["기초다짐 및 뒷채움(기계) / ㎥", "산림품셈 13-3-2 적용", "규격 : 150 mm"] assert got[-5:] == [ "보통인부 : 165,545 * 0.018 = 2,979.8", "굴착기(무한궤도) 0.2㎥ 노무비 : 55,700 * 0.07 = 3,899.0", "굴착기(무한궤도) 0.2㎥ 재료비 : 7,695 * 0.07 = 538.6", "굴착기(무한궤도) 0.2㎥ 경비 : 13,041 * 0.07 = 912.8", "계 : 노무비 6,878 + 재료비 538 + 경비 912 = 8,328", ] def test_엑셀_7호표_작업량_Q_중간_줄(): whole = _whole(_excel("0", "1 / Q * 2", mids=[("Q", "21.594")])) got = st.render_sangeun_text(whole, BASE, GIVEN, _prices({"LB000002": 165545})) assert "Q = 21.594 = 21.59" in got # ROUND(식, 2) 뒤 쓰임 assert "굴착기(무한궤도) 0.2㎥ 노무비 : 55,700 * 1 / 21.59 * 2 = 5,159.7" in got def test_퍼센트_덧줄_노무비의_퍼센트(): tool = {"이름": "공구손료", "식": "노무비 * 3 / 100", "비목": "경비"} whole = _whole(_excel("0.018", "0.07", extras=[tool])) got = st.render_sangeun_text(whole, BASE, GIVEN, _prices({"LB000002": 165545})) assert got[-2:] == [ "공구손료 (노무비의 %) : 6,878.8 * 3 % = 206.3", # 밑수 = 인력 + 중기 노무비 "계 : 노무비 6,878 + 재료비 538 + 경비 1,119 = 8,535", ] def test_배_켜쌓기율_꼬리_단가_없음(): whole = _whole(_excel("0.018", "0.07")) got = st.render_sangeun_text(whole, BASE, GIVEN, _prices({}), times=10, rate=90) assert "보통인부 : 단가 없음 * 0.018 / 10 * 90 % = 빈칸" in got assert got[-1] == "계 : 노무비 빈칸 + 재료비 48 + 경비 82 = 빈칸" def _solver_prices(whole, code, given): """산근 풀이와 같은 단가 — 키마다 같은 수(노무 · 자재 1,000 · 중기 엑셀 값).""" sheets = {"labor": [], "machine": [], "compare": []} asked = [] def price_of(key): asked.append(key) return EXCEL_EQ if key.startswith("EQ") else 1000.0 st.render_sangeun_text(whole, code, given, price_of) for key in dict.fromkeys(asked): if key.startswith("EQ"): sheets["machine"].append({"key": key, **EXCEL_EQ}) elif key.startswith("LB"): sheets["labor"].append({"key": key, "price": 1000.0}) else: sheets["compare"].append({"key": key, "applied": 1000.0}) return sheets, price_of def test_글_속_금액은_산근_풀이_값과_같음(): for code, given in ((BASE, GIVEN), ("GF000288", {})): # 중기 · 노무 · % 덧줄 · 배 1,000 whole = item_prices.load_prices(item_prices.to_json(item_prices.collect([code]))) sheets, price_of = _solver_prices(whole, code, given) block = sg.Solver(sheets).add(whole, {"code": code, "given": given}, "s1") times = sg._unit(code)[1] # 결과단위 배(「원/1,000㎡」) — 산근 풀이와 같은 1 단위 got = st.render_sangeun_text(whole, code, given, price_of, times=times) parts = " + ".join(f"{st.PART_NAME[p]} {block[p]:,.0f}" for p in sg.PARTS) assert got[-1] == f"계 : {parts} = {block['sum']:,.0f}", code def test_자재_자리표시_키는_실제_키로(): """「MO:휘발유:{지역}」 자리표시 = 대비표 키(MO…)로 물음 — 산근 풀이 흠(H4 · 155 데스크탑 보조가 고침)과 달리 단가가 나옴 · Q 중간 줄 · 「줄.'이름'」 % 덧줄.""" whole = item_prices.load_prices(item_prices.to_json(item_prices.collect(["GF000062"]))) asked = [] def price_of(key): asked.append(key) return 1000.0 got = st.render_sangeun_text(whole, "GF000062", {}, price_of) assert not [k for k in asked if ":" in k] and any(k.startswith("MO") for k in asked) assert "Q = 아키야윈치(임업용 윈치) 집재(집재거리 = 50m 내외).수집량 = 8 ㎥" in got # 157-1 assert "일수 = 1 / Q = 1 / 8 = 0.125" in got assert "휘발유(아키아윈치(휘발유)) : 1,000 * 6.5 * 0.125 = 812.5" in got