From 20300af8293a184287823645dd760cd0469725bb Mon Sep 17 00:00:00 2001 From: umsangdon Date: Wed, 11 Nov 2026 19:17:21 +0900 Subject: [PATCH] =?UTF-8?q?feat(816-1):=20=EA=B8=B0=EA=B3=84=ED=91=9C=20?= =?UTF-8?q?=EB=8D=A4=ED=94=84=ED=8A=B8=EB=9F=AD(4=EB=A5=9C=EA=B5=AC?= =?UTF-8?q?=EB=8F=99)=201ton=20=EC=A4=84=20=C2=B7=20GF000145=20=EA=B0=80?= =?UTF-8?q?=20=EA=B7=B8=20=EA=B0=80=EA=B2=A9=EC=9D=84=20=EC=9D=BD=EC=9D=8C?= =?UTF-8?q?=20=C2=B7=20=EC=8B=9C=EA=B0=84=EB=8B=B9=20=EA=B2=BD=EB=B9=84=20?= =?UTF-8?q?=ED=95=9C=20=EA=B8=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 기계.json EQ000655(산림품셈 10-9 · 가격 28,000 천원 = 조달청 등재가격 기본값) · 키대장 EQ 656 - GF000145 손료 = EQ000655 가격 × 2.5 ton 계수(로직 · 수식 문서 한 줄씩 · 빌더 다시 변환) - M02_Machine_Hourly 의 기계 줄만의 기계경비식(기계 입력 없음) 고름 · 기계='…' 는 받는 식에만(Hourly · Doc_Import · Doc_Take) - 가져온 「덤프트럭 1.0Ton · 1톤 · 1ton」 → EQ000655(이름 · 규격 정규화로 이어짐 · 별칭 안 씀) - test_812_9 못 찾음 견본 1.0Ton → 3.0Ton(1 톤이 우리 표에 생겨 낡음) Co-Authored-By: Claude Opus 5.5 --- M02_MasterTemplete/M02_Doc_Import.py | 2 +- M02_MasterTemplete/M02_Doc_Take.py | 2 +- M02_MasterTemplete/M02_Machine_Hourly.py | 24 +++++- resources/master_data/_키대장.json | 3 +- resources/master_data/기계.json | 19 +++++ .../로직_산림품셈_10장_자재․장비_운반.json | 4 +- .../수식_산림품셈_10장_자재․장비_운반.json | 2 +- resources/tester/test_812_9_machine_link.py | 6 +- resources/tester/test_816_1_dump_1ton.py | 85 +++++++++++++++++++ 9 files changed, 136 insertions(+), 11 deletions(-) create mode 100644 resources/tester/test_816_1_dump_1ton.py diff --git a/M02_MasterTemplete/M02_Doc_Import.py b/M02_MasterTemplete/M02_Doc_Import.py index fd2053e67..a019b1b44 100644 --- a/M02_MasterTemplete/M02_Doc_Import.py +++ b/M02_MasterTemplete/M02_Doc_Import.py @@ -196,7 +196,7 @@ def hourly_link(eq: str, part: str) -> dict | None: logic = machine.logic_of(code) if not logic: return None - given = {"기계": _lit(code)} + given = {"기계": _lit(code)} if machine.takes_machine(logic) else {} for spec in logic.get("입력") or []: if spec.get("이름") != "기계": given[spec["이름"]] = _lit((spec.get("고르기") or [""])[0]) diff --git a/M02_MasterTemplete/M02_Doc_Take.py b/M02_MasterTemplete/M02_Doc_Take.py index 71693255b..e3a29d2a8 100644 --- a/M02_MasterTemplete/M02_Doc_Take.py +++ b/M02_MasterTemplete/M02_Doc_Take.py @@ -140,7 +140,7 @@ def _fixed(fx: dict, qty: str | None, rates: list[dict]) -> list[dict]: got = mh.hourly(str(link["기계"])) if not got.get("ok") or not got.get("로직"): return [_warn(f"기계 「{name}」 — {got.get('reason') or '시간당 경비 못 셈'}")] - machine = {"기계": f"'{got['원문번호']}'"} + machine = {"기계": f"'{got['원문번호']}'"} if mh.takes_machine(mh.logic_of(got["원문번호"]) or {}) else {} # fmt: skip return _pick("기계", qty, 키=got["로직"], 이름=name, 규격=spec, 입력=machine) price = fx.get("단가") or {} costs = [c for c in COSTS if Decimal(str(price.get(c) or 0))] or ["재료비"] diff --git a/M02_MasterTemplete/M02_Machine_Hourly.py b/M02_MasterTemplete/M02_Machine_Hourly.py index 957969d8b..343bbef9c 100644 --- a/M02_MasterTemplete/M02_Machine_Hourly.py +++ b/M02_MasterTemplete/M02_Machine_Hourly.py @@ -56,7 +56,7 @@ def logic_of(code: str) -> dict | None: (「조종원 없는 기종」 목록에 겹친 기계 — 진동롤러(핸드가이드식) · 고소작업차 …).""" fit = [r for r in _family() if code in [str(c) for c in _codes(r)]] if not fit: - return None + return _own(code) op = _operated(code) def order(row: dict) -> tuple: @@ -67,6 +67,25 @@ def logic_of(code: str) -> dict | None: return min(fit, key=order) +def _own(code: str) -> dict | None: + """그 기계만의 시간당 경비 식(816-1 · 4륜구동 1톤 덤프트럭 = GF000145) — 「기계」 입력 없는 + 기계경비식이 그 원문번호 기계 줄 가격(`EQ….가격`)을 읽으면 그 식 · 없으면 None.""" + rows = store.loaded()[1].index.get("EQ", {}).values() + prices = [f"{r.get('키')}.가격" for r in rows if code and str(r.get("원문번호")) == code] + for data in store.loaded()[0].values(): + for row in data.get("줄") or [] if data.get("그룹") == "로직" else []: + if (row.get("부품") or {}).get("갈래") != "기계경비식" or takes_machine(row): + continue + if any(p in str(row.get("중간")) for p in prices): + return row + return None + + +def takes_machine(logic: dict) -> bool: + """식이 「기계」 입력(원문번호 고르기)을 받는지 — 안 받는 식(GF000145)엔 기계='…' 를 안 넘김.""" + return any(i.get("이름") == "기계" for i in logic.get("입력") or []) + + def hourly(key: str) -> dict[str, Any]: """`{ok, 이름, 규격, 로직, 원문번호, 노무비, 재료비, 경비, 계}` · 못 셈 = `{ok: False, reason}`.""" if not key.startswith("EQ"): @@ -84,7 +103,8 @@ def hourly(key: str) -> dict[str, Any]: for i in logic.get("입력") or [] if i.get("이름") != "기계" ) - call = f"로직({logic['키']}, 기계='{code}'{args})" + mine = f", 기계='{code}'" if takes_machine(logic) else "" + call = f"로직({logic['키']}{mine}{args})" line = {"종류": "로직", "요소": call, "이름": "기계", "단위": "시간", "수량": "1"} holder = { "키": "", "원문번호": "", "이름": "기계 시간", "결과단위": "원/식", "출처": "자체", diff --git a/resources/master_data/_키대장.json b/resources/master_data/_키대장.json index fedf6f6d6..84f61f7b8 100644 --- a/resources/master_data/_키대장.json +++ b/resources/master_data/_키대장.json @@ -1,5 +1,5 @@ { - "다음": {"LB": 409, "EQ": 655, "RT": 20, "MN": 7000, "MT": 30719, "MO": 74, "MP": 364, "FX": 6, "CF": 159, "QF": 477, "GF": 337, "CC": 528, "GC": 1195, "QC": 2060, "GX": 5, "UA": 38}, + "다음": {"LB": 409, "EQ": 656, "RT": 20, "MN": 7000, "MT": 30719, "MO": 74, "MP": 364, "FX": 6, "CF": 159, "QF": 477, "GF": 337, "CC": 528, "GC": 1195, "QC": 2060, "GX": 5, "UA": 38}, "폐기": {"MT000001": "자재품목 휘발유 — 값이 비고 글뿐 · 유가 테이블로 감", "MT000002": "자재품목 경유 — 값이 비고 글뿐 · 유가 테이블로 감", "MT000003": "자재품목 선박용경유 — 값이 비고 글뿐 · 유가 테이블로 감", "MT000004": "자재품목 중유 — 값이 비고 글뿐 · 유가 테이블로 감", "MT000005": "자재품목 등유 — 값이 비고 글뿐 · 유가 테이블로 감", "LB000399": "이름 「〃」 — 직종이 아니라 되풀이 기호 · 소요량 13-6-9 에서 윗줄 직종으로 바로잡음", "MN": "재료_나라장터자재 — old 로 옮김 · 로직은 자재품목·품셈재료로 갈아 끼움", "LB000372": "미확보 고급기능사 — 측량노임 항공사진고급기능사 옛이름으로 옮김", "LB000393": "미확보 리베팅공 — 건설노임 철골공 옛이름으로 옮김(2010 통합표 연번10)", "LB000405": "미확보 Belt Conveyor 설치공 — 품셈 13-10-1 주② 배분으로 다섯 직종에 나눔", "MT000006": "자재품목 전력 — 재료_유가전력 전력 줄로 감", "MT000007": "자재품목 전력 — 재료_유가전력 전력 줄로 감", "LB000368": "미확보 기술사 — 기술사(건설) 옛이름으로 옮김", "LB000369": "미확보 특급기술자 — 특급기술자(건설) 옛이름으로 옮김", "LB000370": "미확보 고급기술자 — 고급기술자(건설) 옛이름으로 옮김", "LB000361": "미확보 중급기술자 — 중급기술자(건설) 옛이름으로 옮김", "LB000364": "미확보 초급기술자 — 초급기술자(건설) 옛이름으로 옮김", "LB000365": "미확보 고급숙련기술자 — 고급숙련기술자(건설) 옛이름으로 옮김", "LB000366": "미확보 중급숙련기술자 — 중급숙련기술자(건설) 옛이름으로 옮김", "LB000367": "미확보 초급숙련기술자 — 초급숙련기술자(건설) 옛이름으로 옮김", "LB000389": "미확보 기계기사 — 초급기술자(기계·설비) 옛이름으로 옮김", "LB000388": "미확보 기계산업기사 — 초급숙련기술자(기계·설비) 옛이름으로 옮김", "MO000055": {"원문번호": "주택용저압:기본_200이하", "파일": "재료_유가전력.json"}, "MO000056": {"원문번호": "주택용저압:기본_201~400", "파일": "재료_유가전력.json"}, "MO000057": {"원문번호": "주택용저압:기본_400초과", "파일": "재료_유가전력.json"}, "MO000059": {"원문번호": "주택용저압:전력량_다음200", "파일": "재료_유가전력.json"}, "MO000060": {"원문번호": "주택용저압:전력량_400초과", "파일": "재료_유가전력.json"}, "MO000061": {"원문번호": "주택용저압:월간최저요금", "파일": "재료_유가전력.json"}, "MO000062": {"원문번호": "일반용갑I저압:기본", "파일": "재료_유가전력.json"}, "MO000063": {"원문번호": "일반용갑I저압:전력량_여름철", "파일": "재료_유가전력.json"}, "MO000064": {"원문번호": "일반용갑I저압:전력량_봄가을철", "파일": "재료_유가전력.json"}, "MO000065": {"원문번호": "일반용갑I저압:전력량_겨울철", "파일": "재료_유가전력.json"}, "MO000066": {"원문번호": "산업용갑I저압:기본", "파일": "재료_유가전력.json"}, "MO000067": {"원문번호": "산업용갑I저압:전력량_여름철", "파일": "재료_유가전력.json"}, "MO000068": {"원문번호": "산업용갑I저압:전력량_봄가을철", "파일": "재료_유가전력.json"}, "MO000069": {"원문번호": "산업용갑I저압:전력량_겨울철", "파일": "재료_유가전력.json"}, "MO000070": {"원문번호": "임시전력갑:기본요금적용", "파일": "재료_유가전력.json"}, "MO000071": {"원문번호": "임시전력갑:전력량요금적용", "파일": "재료_유가전력.json"}, "MO000072": {"원문번호": "임시전력갑:월간최저요금", "파일": "재료_유가전력.json"}, "GC001145": "작업량당 기계경비 — 부르는 곳 0 · 필요하면 단가산출에 기계 하나 넣어 만듦(요구 734-4 · 735-4 · 813-2)", "GC001148": "작업량당 기계경비 — 부르는 곳 0 · 필요하면 단가산출에 기계 하나 넣어 만듦(요구 734-4 · 735-4 · 813-2)", "GC001151": "작업량당 기계경비 — 부르는 곳 0 · 필요하면 단가산출에 기계 하나 넣어 만듦(요구 734-4 · 735-4 · 813-2)", "GC001156": "작업량당 기계경비 — 부르는 곳 0 · 필요하면 단가산출에 기계 하나 넣어 만듦(요구 734-4 · 735-4 · 813-2)", "GC001159": "작업량당 기계경비 — 부르는 곳 0 · 필요하면 단가산출에 기계 하나 넣어 만듦(요구 734-4 · 735-4 · 813-2)", "GC001166": "작업량당 기계경비 — 부르는 곳 0 · 필요하면 단가산출에 기계 하나 넣어 만듦(요구 734-4 · 735-4 · 813-2)"}, "키": { "CC000001": {"원문번호": "공통 1-3-1 콘크리트 및 포장용 재료", "파일": "계수_건설품셈_01장_적용기준.json"}, @@ -1341,6 +1341,7 @@ "EQ000652": {"원문번호": "2-2-11 부착용집게(0.2~0.8㎥)", "파일": "기계.json"}, "EQ000653": {"원문번호": "2-2-11 트럭탑재형크레인", "파일": "기계.json"}, "EQ000654": {"원문번호": "2-2-11 타이어형크레인", "파일": "기계.json"}, + "EQ000655": {"원문번호": "10-9 덤프트럭(4륜구동) 1ton", "파일": "기계.json"}, "FX000001": {"원문번호": "0000001", "파일": "환율_한국은행환율.json"}, "FX000002": {"원문번호": "0000002", "파일": "환율_한국은행환율.json"}, "FX000003": {"원문번호": "0000003", "파일": "환율_한국은행환율.json"}, diff --git a/resources/master_data/기계.json b/resources/master_data/기계.json index 0377d698a..651076b64 100644 --- a/resources/master_data/기계.json +++ b/resources/master_data/기계.json @@ -39697,6 +39697,25 @@ "값": {}, "출처": {}, "비고": "「건설공사 표준품셈 공통부문, 8-3-3 [20]운반 및 하역기계, (2104)크레인(타이어)(2025)」 적용" + }, + { + "키": "EQ000655", + "원문번호": "10-9", + "원문": "산림품셈", + "구분": "임업기계", + "상세구분": "덤프트럭", + "이름": "덤프트럭(4륜구동)", + "규격": "1ton", + "단위": { + "가격": "천원" + }, + "값": { + "가격": 28000 + }, + "출처": { + "가격": "산림품셈 10-9" + }, + "비고": "원문 이름: 4륜구동 1톤 덤프트럭 · 중기가격 = 조달청 등재가격(기본값 28,000 천원 = EST 2026-07 「덤프트럭 1.0Ton」 · 바꿀 수 있음) · 작업량은 건설품셈 · 손료 및 운전경비 = 2.5톤 덤프트럭(0602-0025) · 주 연료 = 2.5톤의 80% · 시간당 경비 = GF000145" } ] } diff --git a/resources/master_data/로직_산림품셈_10장_자재․장비_운반.json b/resources/master_data/로직_산림품셈_10장_자재․장비_운반.json index 9bad8e51b..d507f13ad 100644 --- a/resources/master_data/로직_산림품셈_10장_자재․장비_운반.json +++ b/resources/master_data/로직_산림품셈_10장_자재․장비_운반.json @@ -522,7 +522,7 @@ "부품": { "갈래": "기계경비식" }, "개정": [2026], "소유": "공용", - "비고": "손료·운전경비는 2.5톤(0602-0025) · 주연료 2.5톤의 80% · 중기가격 조달청 등재가격은 요소 없음 · 작업량(건설품셈 덤프트럭 식)·10-9-1 운반량 쓰는 식 요소 없음 · 주② 제잡비 비율 원문에 없음", + "비고": "손료·운전경비는 2.5톤(0602-0025) · 주연료 2.5톤의 80% · 중기가격 조달청 등재가격 = 기계표 EQ000655 덤프트럭(4륜구동) 1ton 가격 · 작업량(건설품셈 덤프트럭 식)·10-9-1 운반량 쓰는 식 요소 없음 · 주② 제잡비 비율 원문에 없음", "입력": [ { "이름": "지역", @@ -531,7 +531,7 @@ } ], "중간": [ - { "이름": "손료", "식": "버림(EQ000069.가격 * 1000 * (EQ000069.상각비계수 + EQ000069.정비비계수 + EQ000069.관리비계수) / 10000000, 0)", "출처": "산림품셈 10-9 · 건설품셈 공통 8-1-5 12" }, + { "이름": "손료", "식": "버림(EQ000655.가격 * 1000 * (EQ000069.상각비계수 + EQ000069.정비비계수 + EQ000069.관리비계수) / 10000000, 0)", "출처": "산림품셈 10-9 · 건설품셈 공통 8-1-5 12" }, { "이름": "비율", "식": "찾기(QF000330, 구분='2.5톤 덤프 주 연료 대비').비율", "출처": "산림품셈 10-9" }, { "이름": "직종", "식": "EQ000069.조종원직종" }, { "이름": "시간환산", "식": "찾기(CC000336, 구분='시간환산').분자 / 찾기(CC000336, 구분='시간환산').분모", "출처": "건설품셈 공통 1-1-3 2(일일 작업시간 8시간)" } diff --git a/resources/master_data/수식문서/수식_산림품셈_10장_자재․장비_운반.json b/resources/master_data/수식문서/수식_산림품셈_10장_자재․장비_운반.json index 793a302ac..4b28f9a92 100644 --- a/resources/master_data/수식문서/수식_산림품셈_10장_자재․장비_운반.json +++ b/resources/master_data/수식문서/수식_산림품셈_10장_자재․장비_운반.json @@ -482,7 +482,7 @@ {"열쇠": "비율", "기호": "r", "아래": "비율", "위": "", "이름": "비율", "단위": "", "출처": "산림품셈 10-9", "연결": {"갈래": "표", "표": "QF000330", "조건": {"구분": "'2.5톤 덤프 주 연료 대비'"}, "칸": "비율"}}, {"열쇠": "직종", "기호": "S", "아래": "직종", "위": "", "이름": "직종", "단위": "", "연결": {"갈래": "요소", "키": "EQ000069", "칸": "조종원직종"}}, {"열쇠": "시간환산", "기호": "Q", "아래": "시간환산", "위": "", "이름": "시간환산", "단위": "", "출처": "건설품셈 공통 1-1-3 2(일일 작업시간 8시간)", "연결": {"갈래": "식"}}, - {"열쇠": "P_덤프트럭", "기호": "P", "아래": "덤프트럭", "위": "", "이름": "EQ000069.가격", "단위": "", "연결": {"갈래": "요소", "키": "EQ000069", "칸": "가격"}}, + {"열쇠": "P_덤프트럭", "기호": "P", "아래": "덤프트럭", "위": "", "이름": "EQ000655.가격", "단위": "", "연결": {"갈래": "요소", "키": "EQ000655", "칸": "가격"}}, {"열쇠": "k_덤프트럭", "기호": "k", "아래": "덤프트럭", "위": "", "이름": "EQ000069.상각비계수", "단위": "", "연결": {"갈래": "요소", "키": "EQ000069", "칸": "상각비계수"}}, {"열쇠": "k_덤프트럭2", "기호": "k", "아래": "덤프트럭2", "위": "", "이름": "EQ000069.정비비계수", "단위": "", "연결": {"갈래": "요소", "키": "EQ000069", "칸": "정비비계수"}}, {"열쇠": "k_덤프트럭3", "기호": "k", "아래": "덤프트럭3", "위": "", "이름": "EQ000069.관리비계수", "단위": "", "연결": {"갈래": "요소", "키": "EQ000069", "칸": "관리비계수"}}, diff --git a/resources/tester/test_812_9_machine_link.py b/resources/tester/test_812_9_machine_link.py index 036778072..cf0985253 100644 --- a/resources/tester/test_812_9_machine_link.py +++ b/resources/tester/test_812_9_machine_link.py @@ -1,7 +1,7 @@ """812-9 지킴 — 가져온 기계 잇기(`M02_Machine_Find` · 요구 718 · 719). (a) 기계표 전체에서 찾음 — 낱말표 밖 기종(로우더 · 공기 압축기 · 램 머 · 라인마커 …) · 후보 여럿이던 것 - (물탱크 · 크레인(무한궤도) · 머캐덤 · 콘크리트 펌프차 …) · 지어 맞추기 없음(1.0Ton = 우리 표에 없음) + (물탱크 · 크레인(무한궤도) · 머캐덤 · 콘크리트 펌프차 …) · 지어 맞추기 없음(3.0Ton = 우리 표에 없음) (b) 표기 정규화 · 괄호 낱말(무한궤도 · 타이어 · 일반용 · 표에 없는 낱말 발파 = 안 봄) · 범위 · 복수 규격 (c) 조합 기계 = 둘(대형브레이카 +굴착기 · 0.7+브레이카 · +부착용집게 · +진동콤팩터 · 덤프트럭(자동덮개)) (d) 별칭 먼저 — M01 기계 줄 「별칭」 칸(815-1 · 시험 안 임시 별칭) · 이름 + 규격 통째 = 그 EQ · 이름만 = @@ -104,7 +104,7 @@ def test_master_alias(name: str, spec: str, eq: str) -> None: def test_no_guess() -> None: """우리 표에 없는 규격 = 못 찾음(가까운 규격으로 안 이음) · 기종 모름 = 별칭 몫 · 여럿 = 여럿.""" - got = mf.find("덤프트럭", "1.0Ton") + got = mf.find("덤프트럭", "3.0Ton") assert got["판정"] == mf.NONE and got["까닭"] == "우리 표에 없음(규격)" and got["키"] is None assert got["후보"][0]["규격"] == "2.5ton" # 보여주기만 — 가까운 규격 먼저 for name, spec in (("에어 호스", "1.91cm"), ("모노펌프", "5-20㎥/hr")): # 승인 전 = 별칭 몫 @@ -113,7 +113,7 @@ def test_no_guess() -> None: got = mf.find("크레인(트럭)", "10Ton") # 「트럭」 = 기계표 괄호에 없는 낱말 → 안 봄 assert got["판정"] == mf.MANY and got["수"] == 2 assert mf.mark(got, "크레인(트럭)", "10Ton").startswith("참고 · 기계 후보 2 — 고르기") - assert "기계 못 찾음" in mf.mark(mf.find("덤프트럭", "1.0Ton"), "덤프트럭", "1.0Ton") + assert "기계 못 찾음" in mf.mark(mf.find("덤프트럭", "3.0Ton"), "덤프트럭", "3.0Ton") assert mf.mark(mf.find("물탱크", "5500ℓ"), "물탱크", "5500ℓ") is None diff --git a/resources/tester/test_816_1_dump_1ton.py b/resources/tester/test_816_1_dump_1ton.py new file mode 100644 index 000000000..2d60b8358 --- /dev/null +++ b/resources/tester/test_816_1_dump_1ton.py @@ -0,0 +1,85 @@ +"""816-1 지킴 — 기계표 「덤프트럭(4륜구동) 1ton」(산림품셈 10-9 · 요구 769-1 · 770-1). + +(a) 기계표 줄 · 값 — 원문 산림품셈 10-9 · 가격 28,000 천원(조달청 등재가격 기본값) · 키대장 +(b) GF000145(4륜구동 1톤 덤프트럭 시간당 기계경비)가 그 줄 가격을 읽음 · 계수 · 연료 · 조종원은 2.5 ton +(c) 잇기 — 가져온 「덤프트럭 1.0Ton · 1톤 · 1ton」 → 새 줄 · 「덤프트럭 2.5ton」 → EQ000069 그대로 +(d) 시간당 경비 한 길 — `M02_Machine_Hourly.hourly` · `M02_Doc_Import.hourly_link` = GF000145 +""" + +from __future__ import annotations + +import importlib +import sys +from decimal import Decimal +from pathlib import Path + +ROOT = Path(__file__).resolve().parents[2] +for p in (ROOT, ROOT / "resources/master_data/scripts"): + if str(p) not in sys.path: + sys.path.insert(0, str(p)) + +from M01_MasterData import M01_MasterData_Store as m01 # noqa: E402 +from M02_MasterTemplete import M02_Doc_Import as di # noqa: E402 +from M02_MasterTemplete import M02_Machine_Find as mf # noqa: E402 +from M02_MasterTemplete import M02_Machine_Hourly as mh # noqa: E402 + +ml = importlib.import_module("master_doc_link") +mc = importlib.import_module("master_doc_calc") +NEW, OLD = "EQ000655", "EQ000069" + + +def _whole(): + return m01.loaded()[1] + + +def _docs() -> dict: + return ml.load_all() + + +def test_row() -> None: + row = _whole().get(NEW) + assert (row["원문"], row["원문번호"], row["상세구분"]) == ("산림품셈", "10-9", "덤프트럭") + assert (row["이름"], row["규격"]) == ("덤프트럭(4륜구동)", "1ton") + assert row["값"] == {"가격": 28000} and row["출처"]["가격"] == "산림품셈 10-9" + assert "4륜구동 1톤 덤프트럭" in row["비고"] + + +def test_gf145_reads_price() -> None: + doc = _docs()["GF000145"] + reads = {(v["연결"].get("키"), v["연결"].get("칸")) for v in doc["변수"]} + assert (NEW, "가격") in reads and (OLD, "가격") not in reads + assert { + (OLD, c) for c in ("상각비계수", "정비비계수", "관리비계수", "주연료", "조종원") + } <= reads + got = mc.calc(doc, ml.Links(_whole(), _docs())) + old = _whole().get(OLD)["값"] + loss = int(Decimal(28000 * 1000) * sum(Decimal(str(old[k])) for k in ("상각비계수", "정비비계수", "관리비계수")) / Decimal(10**7)) # fmt: skip + assert got["셈됨"] and got["경비"] == loss == 8307 + + +def test_find() -> None: + for spec in ("1.0Ton", "1톤", "1ton"): + got = mf.find("덤프트럭", spec) + assert got["키"] == NEW and got["판정"] == mf.LINKED, spec + assert mf.find("덤프트럭", "2.5ton")["키"] == OLD + assert mf.find("덤프트럭", "15Ton")["키"] == "EQ000074" + + +def test_hourly_one_way() -> None: + assert mh.logic_of("10-9")["키"] == "GF000145" + assert mh.logic_of("0602-0025")["키"] != "GF000145" # 2.5 ton = 일반 식 그대로 + got = mh.hourly(NEW) + doc = mc.calc(_docs()["GF000145"], ml.Links(_whole(), _docs())) + assert got["ok"] and got["로직"] == "GF000145" + assert [got[k] for k in ("노무비", "재료비", "경비", "계")] == [float(doc[k]) for k in ("노무비", "재료비", "경비", "계")] # fmt: skip + link = di.hourly_link(NEW, "경비") + assert link["키"] == "GF000145" and "기계" not in link["입력"] and link["칸"] == "경비" + + +def test_gf283_option() -> None: + """10-9-1 덤프트럭 운반 「1톤 4륜구동」 옵션 = GF000145 값으로 셈.""" + links = ml.Links(_whole(), _docs()) + one = mc.calc(_docs()["GF000283"], links, {"덤프트럭": "1톤 4륜구동"}) + big = mc.calc(_docs()["GF000283"], links, {"덤프트럭": "0602-0025"}) + assert one["셈됨"] and big["셈됨"] and one["노무비"] == big["노무비"] # 조종원 = 2.5 ton + assert one["경비"] > big["경비"] # 손료 = 1 톤 가격 28,000 > 2.5 ton 21,982