diff --git a/M02_MasterTemplete/M02_Hopyo_Basis.py b/M02_MasterTemplete/M02_Hopyo_Basis.py index 01146e50a..f357a6807 100644 --- a/M02_MasterTemplete/M02_Hopyo_Basis.py +++ b/M02_MasterTemplete/M02_Hopyo_Basis.py @@ -3,7 +3,11 @@ 읽기(`read` · `sources` · `trade_names` · `SHEET` · `GLINES` · `GCHARS`) = EST 어댑터 `M02_Hopyo_ReadEst`(776-2 · 이름은 그대로 다시 냄). -견줌(`candidates`) = 같은 책 조항 같음 1.0 · 윗마디 0.9(엑셀 6-2 ⊃ 우리 6-2-2) · +출처 풀이(`settle` · 781-1) = 건설품셈(부문 모름) 번호 → 뜻 낱말로 부문 · +임도품셈(옛) → 대응표 현행 산림 번호 · +기초 없음 표지(`no_basis` · 781-2) = 출처 조항에 우리 로직 0 · 옛 번호. +견줌(`candidates`) = 같은 책(부문을 알면 같은 구분) 조항 같음 1.0 · +윗마디 0.9(엑셀 6-2 ⊃ 우리 6-2-2) · 앞 마디 같음 0.5(조항이 안 맞으면 후보 아님 · `clause`) · 조항 오타 「13-114」 = 「13-11-4」 · 건설 공통 · 토목 덤 0.1 · 직종 겹침(보통인부 뺌) 0.5 · 단위 0.15 · 직종도 뜻 낱말도 안 겹치면 버림 · 같은 점수는 글자 두 개 묶음(괄호 글 포함)이 가름 · 이름 · 낱말(괄호 글 포함 · 인력 · 기계 뺌)이 @@ -15,11 +19,13 @@ M01 · 원본 로직은 읽기만. from __future__ import annotations +import csv import re from functools import lru_cache from typing import Any from M01_MasterData import M01_MasterData_Store as m01 +from M01_MasterData import M01_MasterData_Store_Make as make from M02_MasterTemplete import M02_Hopyo_Match as hopyo_match # 서로 부름 — 부를 때만 씀 from M02_MasterTemplete import M02_Labor_Inputs as labor_inputs from M02_MasterTemplete.M02_Hopyo_ReadEst import ( # noqa: F401 — 읽기는 EST 어댑터(776-2) · 옛 이름 그대로 @@ -81,8 +87,9 @@ def _head(number: str) -> str: def clause(theirs: list[list[str]], our: dict[str, Any]) -> tuple[float, list[str], str]: """엑셀 인용 조항 `[[책, 번호]]` ↔ 우리 로직(`ours()` 한 줄) → (점수, 맞은 우리 조항 글, - 꼬리표) — 같은 책만 · 같음 1 · 윗마디 0.9(엑셀 6-2 ⊃ 우리 6-2-2) · 앞 마디 0.5.""" - nums = [n for book, n in theirs if book == our["책"]] + 꼬리표) — 같은 책(부문을 알면 같은 구분 · 「(옛)」 = 없음) · 같음 1 · + 윗마디 0.9(엑셀 6-2 ⊃ 우리 6-2-2) · 앞 마디 0.5.""" + nums = [n for book, n in theirs if book in (our["책"], our["구분"])] same = sorted(n for n in nums if n in our["조항"]) if same: return 1.0, [f"{our['구분']} {n}" for n in same], "조항 같음" @@ -96,6 +103,148 @@ def clause(theirs: list[list[str]], our: dict[str, Any]) -> tuple[float, list[st return 0.0, [], "" +# ── 출처 풀이(781-1 · 781-2) — 건설 부문 · 임도 옛 번호 대응 · 기초 없음 ─────────── +OLD = "(옛)" +_ROAD = "임도품셈" + OLD +_ROAD_MAP = make.BODY.parent / "경쟁사 프로그램/STmate 분석/30_원자료/임도품셈_판_대응표.csv" +_OLD_NO = re.compile(r"^(\d+(?:-\d+)+)(\s+[가-하])?$") +_STOP_GRAMS = frozenset("설치 해체 작업 인력 기계 장비 보완 신설 공사".split()) + + +@lru_cache(maxsize=1) +def road_map() -> dict[str, str]: + """임도품셈(옛) 번호 → 현행 산림 번호 — 지식DB 대응표 「확정」 한 대응만(일대다 · 미확인 뺌) · + 옛 번호가 가 · 나 항이면 현행 윗마디(9-13-7 → 9-13 · 그 항만 대응하므로).""" + with open(_ROAD_MAP, encoding="utf-8-sig", newline="") as f: + rows = list(csv.DictReader(f)) + out = {} + for row in rows: + old, new = _OLD_NO.match(row["과거_절"].strip()), row["현행_절"].strip() + if row["판정"] != "확정" or not old or not _OLD_NO.fullmatch(new) or " " in new: + continue + out[old.group(1)] = new.rsplit("-", 1)[0] if old.group(2) and new.count("-") > 1 else new + return out + + +def _grams(text: Any) -> set[str]: + """뜻 낱말 — 낱말 안 한글 두 글자 묶음(괄호 글 포함 · 굴삭 = 굴착).""" + words = re.findall(r"[가-힣]+", str(text or "").replace("굴삭", "굴착")) + return {w[i : i + 2] for w in words for i in range(len(w) - 1)} - _STOP_GRAMS + + +@lru_cache(maxsize=2) +def _sections(book: str) -> dict[tuple[str, str], str]: + """{(부문, 절 번호): 제목(괄호 글 · 「:」 뒤 뺌)} — 원문 본문.""" + return { + (s["division"], s["section"]): re.sub(r"\(.*?\)", "", s["title"]).split(":")[0].strip() + for s in make._all_sections(book) + } + + +@lru_cache(maxsize=2) +def _named(whole_id: int) -> dict[tuple[str, str], list[str]]: + """{(구분, 원문번호): [로직 이름]} — M01 로직.""" + out: dict[tuple[str, str], list[str]] = {} + for data in m01.loaded()[0].values(): + if data.get("그룹") == "로직": + for row in data.get("줄") or []: + key = (str(row.get("구분") or ""), str(row.get("원문번호") or "").split(" ")[0]) + out.setdefault(key, []).append(str(row.get("이름") or "")) + return out + + +def _part(number: str, name: Any, text: Any = "") -> str: + """건설품셈(부문 모름) 번호 → 원문에 그 번호가 있는 부문 중 블록 이름(안 맞으면 블록 글 · + 두 묶음)이 원문 제목 · 같은 번호 로직 이름과 뜻 낱말을 나누는 부문이 하나 = 그 부문 · + 둘 이상 = 모름 · 없음 = 「(옛)」(번호 뜻 안 맞음).""" + if not _grams(name): + return "건설품셈" + named = _named(id(m01.loaded()[1])) + texts = { + part: [title, *named.get((f"건설품셈 {part}", no), [])] + for (part, no), title in _sections("건설품셈").items() + if no == number + } + hits = [] + for mine, least in ((_grams(name), 1), (_grams(text), 2)): # 글은 두 묶음 이상 + hits = [p for p, ts in texts.items() if any(len(mine & _grams(t)) >= least for t in ts)] + if hits: + break + if len(hits) == 1: + return f"건설품셈 {hits[0]}" + return "건설품셈" if hits else "건설품셈" + OLD + + +def resolve(theirs: list[list[str]], name: Any, text: Any = "") -> list[list[str]]: + """블록 출처 → 부문 풀이 · 임도 옛 번호 대응(`road_map`) 뒤 출처.""" + out: list[list[str]] = [] + for book, number in theirs: + one = [book, number] + if book == _ROAD and number in road_map(): + one = ["산림품셈", road_map()[number]] + elif book == "건설품셈": + one = [_part(number, name, text), number] + if one not in out: + out.append(one) + return out + + +def settle(book: dict[str, Any]) -> dict[str, Any]: + """Book 단산 블록 출처를 제자리에서 풀이(줄 `근거.출처` 가 같은 리스트를 가리킴).""" + for block in book.get("단산") or []: + if block.get("출처"): + text = " ".join(str(t) for t in block.get("글") or []) + block["출처"][:] = resolve(block["출처"], block.get("이름"), text) + return book + + +def has_logic(kind: str, number: str) -> bool: + """그 구분 · 조항(윗마디 · 아랫마디 포함)에 M01 로직이 있나.""" + return _has(id(m01.loaded()[1]), kind, number) + + +@lru_cache(maxsize=512) +def _has(whole_id: int, kind: str, number: str) -> bool: + named = _named(whole_id) + parts = number.split("-") + ups = {"-".join(parts[:i]) for i in range(2, len(parts) + 1)} + return any(k == kind and (no in ups or no.startswith(number + "-")) for k, no in named) + + +def no_basis(theirs: list | None) -> dict[str, str] | None: + """출처 조항이 모두 우리 기초 밖이면 표지 `{판정, 글, 이름}` — 기초 없음(로직 0) › 옛 번호 + (「(옛)」 · 지금 원문에 없는 번호) · 로직 있는 조항 · 건설품셈(부문 모름)이 하나라도 + 있으면 None.""" + none, old = [], [] + for book, number in theirs or []: + base, _, part = book.partition(" ") + if book == "건설품셈": + return None + if book.endswith(OLD) or base not in make.BOOKS: + old.append((book.removesuffix(OLD), number)) + elif has_logic(book, number): + return None + elif (title := _sections(base).get((part, number))) is None: + old.append((book, number)) + else: + none.append((book, number, title)) + if none: + book, number, title = none[0] + where = f"{book.replace('품셈', '')} {number} {title}" + return { + "판정": "기초 없음", + "글": f"근거 조항 우리 기초에 없음 — {book} {number} {title}", + "이름": f"기초 없음 — {where}", + } + if old: + return { + "판정": "옛 번호", + "글": f"옛 번호 — 대응 미확인 — {old[0][0]} {old[0][1]}", + "이름": "옛 번호 — 근거 못 씀", + } + return None + + def _text(line: dict[str, Any]) -> str: text = f"{line.get('이름') or ''} {line.get('규격') or ''}" for old, new in _TYPOS: diff --git a/M02_MasterTemplete/M02_Hopyo_Excel.py b/M02_MasterTemplete/M02_Hopyo_Excel.py index ff1fc21e7..14f4e1cf1 100644 --- a/M02_MasterTemplete/M02_Hopyo_Excel.py +++ b/M02_MasterTemplete/M02_Hopyo_Excel.py @@ -30,6 +30,7 @@ import re from decimal import ROUND_FLOOR, Decimal, InvalidOperation from typing import Any +from M02_MasterTemplete import M02_Hopyo_Basis as source_basis # 출처 풀이(781-1) from M02_MasterTemplete import M02_Hopyo_ReadEst as hopyo_basis # EST 단가산출 어댑터(776-2) from M02_MasterTemplete import M02_Hopyo_Stmate as hopyo_stmate from M02_MasterTemplete import M02_Hopyo_UnitLine as unit_line @@ -302,7 +303,7 @@ def read_book(name: str, data: bytes) -> dict[str, Any]: sheets, xls = _open(name, data, LISTS) _OPENED = (data, sheets, xls) # 격자(`Grid.read`)가 이어 씀 — 한 번 열기 if hopyo_stmate.is_stmate(sheets): - return unit_line.settle(hopyo_stmate.read(sheets)) + return unit_line.settle(source_basis.settle(hopyo_stmate.read(sheets))) rows = sheets.get(SHEET) if rows is None: raise ExcelError(NO_SHEET) @@ -374,7 +375,9 @@ def read_book(name: str, data: bytes) -> dict[str, Any]: "단산": basis, "검산": {"어긋남": wrong, "총괄표": len(totals)}, } - return unit_line.settle(book) # 단위 줄 하위 · 재현 · 덮음 · 일위대가 줄 투영(778-3) + return unit_line.settle( + source_basis.settle(book) + ) # 출처 풀이(781-1) · 단위 줄 하위 · 재현 · 덮음 · 일위대가 줄 투영(778-3) def _json(value: Any) -> Any: diff --git a/M02_MasterTemplete/M02_Hopyo_ReadEst.py b/M02_MasterTemplete/M02_Hopyo_ReadEst.py index 2c75c78e8..aa9073e33 100644 --- a/M02_MasterTemplete/M02_Hopyo_ReadEst.py +++ b/M02_MasterTemplete/M02_Hopyo_ReadEst.py @@ -3,8 +3,9 @@ 형식(EST 글)을 아는 곳은 여기까지. 블록 = B열(머리 「산출근거」) 머리 줄 「N.이름[단위]」(앞 빈칸 없음)부터 다음 머리 앞까지 · -출처 = 「…품셈」(오타 「품줌」) 뒤 20 글자 안 절 번호 — 앞 글에 산림 · 임도 = 산림품셈 · -그 밖 = 건설품셈 · +출처 = 「…품셈」(오타 「품줌」) 뒤 20 글자 안 절 번호 — 책 = 앞 낱말(`book_of` · 781-1: 산림 · +임도(옛) · 사방(옛) · 건설 부문 · 그 밖 건설품셈(부문 모름)) · +부문 풀이 · 옛 번호 대응 = `M02_Hopyo_Basis.settle` · 직종 = 「이름 : 수」 줄의 빈칸 뺀 이름이 우리 인력(LB) 이름 · 옛이름일 때만(지금 이름으로) · 글 = 머리 뺀 몸 줄 `GLINES` 줄 · 줄마다 `GCHARS` 자 안 · 산출근거 열이 빈 줄은 그 오른쪽 ~ 돈 머리 (맨 오른쪽 묶음) 앞 칸을 빈칸으로 이어 붙임(거창 꼴 · 돈 머리 없으면 `WIDE` 열) · @@ -36,6 +37,13 @@ GLINES, GCHARS, WIDE = 200, 300, 140 _HEAD = re.compile(r"^(\d+)\.\s*(.*?)\s*\[([^\[\]]*)\]\s*$") _BOOK = re.compile(r"품[셈줌]") _TRADE = re.compile(r"^[\s*·\-]*([가-힣][가-힣\s]*?)\s*:\s*[\d,]") +_BOOK_WORDS = { # 책 낱말(781-1) — 옛 책 = 「(옛)」 · 건설 부문 낱말 = 「건설품셈 <부문>」 + "산림": "산림품셈", + "임도": "임도품셈(옛)", + "사방": "사방품셈(옛)", + **{p: f"건설품셈 {p}" for p in ("공통", "토목", "건축", "기계설비", "유지관리")}, +} +_BOOK_WORD = re.compile(r"(" + "|".join(_BOOK_WORDS) + r")\s*(?:부문|사업|공사)?\s*(?:표준)?\s*$") _TYPO_CLAUSE = re.compile(r"^(\d+)-(\d)(\d)(\d)$") # 「13-114」 = 13-11-4 _MONEY = {"합계": "계", "노무비": "노무비", "재료비": "재료비", "경비": "경비"} # 항목 풀이 — 지문(`Finger`)과 같은 규칙 @@ -108,15 +116,26 @@ def trade(name: Any) -> str | None: return names.get(flat) or names.get(re.sub(r"\(.*?\)", "", flat)) +def book_of(before: str) -> str: + """「…품셈」 앞 글 → 책(781-1) — 바로 앞 낱말 먼저 · 없으면 앞 12 자에 산림 · 임도 · + 그 밖 = 건설품셈(부문 모름).""" + hit = _BOOK_WORD.search(before) + if hit: + return _BOOK_WORDS[hit.group(1)] + if "산림" in before: + return "산림품셈" + return _BOOK_WORDS["임도"] if "임도" in before else "건설품셈" + + def sources(text: str) -> list[list[str]]: - """글 → [[책, 절 번호]] — 「건설공사표준품셈 3-2-1」 · 「산림사업표준품셈(9-14)」.""" + """글 → [[책, 절 번호]] — 「건설공사표준품셈 3-2-1」 · 「산림사업표준품셈(9-14)」 · + 책 = `book_of`.""" out = [] for hit in _BOOK.finditer(text): num = labor_inputs._NUMBER.search(text, hit.end()) if not num or num.start() - hit.end() > 20: continue - before = text[max(0, hit.start() - 12) : hit.start()] - book = "산림품셈" if "산림" in before or "임도" in before else "건설품셈" + book = book_of(text[max(0, hit.start() - 12) : hit.start()]) typo = _TYPO_CLAUSE.match(num.group()) number = "{}-{}{}-{}".format(*typo.groups()) if typo else num.group() if [book, number] not in out: diff --git a/M02_MasterTemplete/M02_Hopyo_Sangeun.py b/M02_MasterTemplete/M02_Hopyo_Sangeun.py index e6846e744..9c4875ada 100644 --- a/M02_MasterTemplete/M02_Hopyo_Sangeun.py +++ b/M02_MasterTemplete/M02_Hopyo_Sangeun.py @@ -23,6 +23,7 @@ from typing import Any from common_util.common_util_time import utc_now_text from M01_MasterData import M01_MasterData_Store as m01 +from M02_MasterTemplete import M02_Hopyo_Basis as hopyo_basis from M02_MasterTemplete import M02_Hopyo_Fit as fit from M02_MasterTemplete import M02_Hopyo_Ledger as ledger from M02_MasterTemplete import M02_Hopyo_SkPatch as skpatch @@ -169,11 +170,15 @@ class Maker: return self._units def basis(self, no: int) -> dict[str, Any] | None: - """근거 꼬리표 = 맞춤셈 1위 기초(참고).""" + """근거 꼬리표 = 맞춤셈 1위 기초(참고) · 출처 조항이 우리 기초 밖이고 1위가 맞음 아니면 + 1위 대신 표지(781-2 · 「기초 없음 — 건설 공통 1-2-7 운반」 · 「옛 번호 — 근거 못 씀」).""" if no not in self.first: # 일위대가 밖 = 맞춤셈 없음 return None entry = self.cache.get(fit.unit_key(self.first[no])) or {} top = (entry.get("차례") or [None])[0] + outside = hopyo_basis.no_basis(self.blocks[no].get("출처")) + if outside and (entry.get("판정") or {}).get("상태") != "맞음": + return {"로직": None, "이름": outside["이름"], "판정": outside["판정"]} if not top: return None name = (self.whole.logic(top) or {}).get("이름") diff --git a/M02_MasterTemplete/M02_Hopyo_Verdict.py b/M02_MasterTemplete/M02_Hopyo_Verdict.py index 54cca597f..eddf1c5a9 100644 --- a/M02_MasterTemplete/M02_Hopyo_Verdict.py +++ b/M02_MasterTemplete/M02_Hopyo_Verdict.py @@ -4,7 +4,9 @@ 같음 1위 고정(딱 맞음이 이김) → 섞인 줄 몫 먼저 · 조합(`Combine.solve` · 근거 안 닿은 덧 = 후보 끝) → 위 다섯 옵션 거꾸로(`Option`) → 위 다섯 가운데 딱 맞음(옵션까지)이 있는데 1위가 아니면 그것이 1위(조합 없는 줄) → -판정 맞음 · 다른 점 · 후보 + 후보 다섯(로직마다 옵션 판정 꼬리표). +판정 맞음 · 다른 점 · 후보 + 후보 다섯(로직마다 옵션 판정 꼬리표) · +출처 조항이 우리 기초 밖(781-2 · `Basis.no_basis`) = `기초없음` 표지 · 판정 꼬리표 · +맞음 아닌 후보 글 「참고 후보」. 글 · 꼬리표는 숫자 사실만(엑셀 · 우리 품) — 「옛 판」 같은 까닭은 짓지 않음. """ @@ -22,6 +24,7 @@ from M02_MasterTemplete import M02_Hopyo_Option as option _num = money_fmt.fmt # 꼬리표 수 — 공용 수량 꼴(1만 넘어도 지수 꼴 아님) +OUTSIDE = "참고 후보 — 근거 조항 아님" def excel_qty(efp: dict[str, Any]) -> dict[str, Any]: @@ -150,6 +153,11 @@ def block(block_: dict | None, line: dict[str, Any], got: dict[str, Any]) -> dic entry["조합"] = combo or None entry["판정"] = judge(efp, top, opts, theirs, got) entry["후보"] = candidates(efp, top, opts, theirs, got, scores, border.get("근거") or {}) + if outside := hopyo_basis.no_basis(theirs): # 781-2 — 근거 조항이 우리 기초 밖 + entry["기초없음"] = outside + entry["판정"]["꼬리표"].append(outside["글"]) + for c in entry["후보"]: + c["글"] = c["글"] if c["판정"] == "맞음" else OUTSIDE names = {x["로직"]: x for x in got["로직"]} for k in (k for k in spare if k not in top[: fit.TOP_OPT] and k in names): # 근거 안 닿은 덧 one = {key: names[k][key] for key in ("로직", "이름", "단위")} diff --git a/resources/tester/fixtures/m02_hopyo_stages.json b/resources/tester/fixtures/m02_hopyo_stages.json index 6fb8ba5a2..733d93c3d 100644 --- a/resources/tester/fixtures/m02_hopyo_stages.json +++ b/resources/tester/fixtures/m02_hopyo_stages.json @@ -1,7 +1,7 @@ { "북부청임도": { "S1": { - "해시": "2a4df1c8e7a2", + "해시": "8caa2daac3db", "꼴": "엑셀꼴", "호표": 68, "줄": 415, @@ -26,61 +26,61 @@ } }, "S2": { - "단산:46|레미콘타설(장비사용)|철근구조물-소량타설|m3": "fe6524b2bf88", - "단산:24|합판거푸집|4회|m2": "dc9227913b7d", - "단산:33|기초잡석채집,운반,다짐|발파암활용,덤프15톤,l=500m|m3": "53d20022ff32", - "단산:47|철근가공조립(현장)|type-i(소량시공)|톤": "f333a56a3d0e", - "단산:6|야면석채집및운반(계곡내)|굴삭기0.6m3,35cm,4.5톤,l=500m|m2": "c0521e16ff75", - "단산:3|고임돌채집및운반|굴삭기0.6m3(90%),4.5톤,l=500m|m3": "2521254f68a8", - "단산:10|야면석소운반|l=35cm|m2": "50eb93121621", + "단산:46|레미콘타설(장비사용)|철근구조물-소량타설|m3": "1151e48193fe", + "단산:24|합판거푸집|4회|m2": "d0ab3fbed4f5", + "단산:33|기초잡석채집,운반,다짐|발파암활용,덤프15톤,l=500m|m3": "819f524df62c", + "단산:47|철근가공조립(현장)|type-i(소량시공)|톤": "acd7eac1ba87", + "단산:6|야면석채집및운반(계곡내)|굴삭기0.6m3,35cm,4.5톤,l=500m|m2": "e17e2d8f1075", + "단산:3|고임돌채집및운반|굴삭기0.6m3(90%),4.5톤,l=500m|m3": "1f7bf09ff9f4", + "단산:10|야면석소운반|l=35cm|m2": "f25e6359f1bc", "단산:39|야면석찰붙임|l3=35cm적용,장비사용|m2": "cf4739ae6e8c", - "단산:93|구조물터파기(인력100%)|토사|m3": "c523b43c617b", + "단산:93|구조물터파기(인력100%)|토사|m3": "54003630f6ff", "단산:12|구조물되메우기(인력100%)||m3": "559c125ef42e", - "단산:13|잔토처리(인력100%)||m3": "eb0395c76e02", - "단산:22|레미콘타설(장비사용)|무근구조물|m3": "12555a58e991", - "단산:26|유로폼|보통,0~7m|m2": "a301adcd84cd", - "단산:18|문양거푸집||m2": "9f4efb5510bf", + "단산:13|잔토처리(인력100%)||m3": "047f93039717", + "단산:22|레미콘타설(장비사용)|무근구조물|m3": "118dcf75025d", + "단산:26|유로폼|보통,0~7m|m2": "fc39fc1ab93e", + "단산:18|문양거푸집||m2": "a14a6ee767b6", "단산:37|p.v.c파이프설치|ø50mm|m": "a408f8e02b28", - "단산:38|다웰바설치|ø32x1000|개": "13410747be6e", - "단산:41|휠라설치|t=15mm,1000x1500mm|m2": "23bf202fc7a4", - "단산:29|철근가공조립(현장)|연결철근,type-ix30%적용|톤": "2b96b0b322d2", - "단산:30|큰돌채집운반|공사중발생석활용50%적용|m2": "66cb28bdab1c", + "단산:38|다웰바설치|ø32x1000|개": "91a2b42d7158", + "단산:41|휠라설치|t=15mm,1000x1500mm|m2": "effcd33b16ce", + "단산:29|철근가공조립(현장)|연결철근,type-ix30%적용|톤": "6249ee0e0d8b", + "단산:30|큰돌채집운반|공사중발생석활용50%적용|m2": "0f9a534ed073", "단산:32|큰돌쌓기(메쌓기)|직경40cm이상~60cm미만|m2": "0d884c7f218d", - "단산:2|뒷채움돌채집운반뒷채움(발파)|굴삭기0.6m3(90%),4.5톤,l=500m|m3": "ec132e6186ae", + "단산:2|뒷채움돌채집운반뒷채움(발파)|굴삭기0.6m3(90%),4.5톤,l=500m|m3": "0bb8e4afd544", "단산:31|큰돌쌓기(찰쌓기)_배수공|직경40cm이상~60cm미만|m2": "b4e9d2ee8233", - "단산:21|레미콘타설(인력운반)|무근구조물-소량타설|m3": "7ee6fe1e8d9d", - "단산:25|합판거푸집|6회|m2": "b5b6ec2a0beb", - "단산:42|도로반사경설치|d=1000(sts)|개소": "3c2ee04632d3", + "단산:21|레미콘타설(인력운반)|무근구조물-소량타설|m3": "d910773366ad", + "단산:25|합판거푸집|6회|m2": "c319f56f20f4", + "단산:42|도로반사경설치|d=1000(sts)|개소": "2fff999097f5", "단산:88|야면석메붙임|l3=35cm내외,장비사용|m2": "fbc297bd41b0", "단산:92|큰돌메붙임|직경40cm이상~60cm미만|m2": "d1fad346524d", "단산:91|큰돌찰붙임|직경40cm이상~60cm미만|m2": "269c2c76c667", - "단산:57|레미콘타설(장비사용)|소형구조물|m3": "9038aaf1131e", + "단산:57|레미콘타설(장비사용)|소형구조물|m3": "634112efbf50", "단산:71|야면석찰쌓기|l3=35cm적용,장비사용|m2": "e74767626ed4", - "단산:43|면고르기||m2": "ec9f5d99380f", + "단산:43|면고르기||m2": "18265e68c8c1", "줄:대형브레이카|+굴착기(0.7m3)|시간": "fc11668f29eb", - "단산:60|와이어메시바닥깔기|#6100x100|m2": "567c16874a89", + "단산:60|와이어메시바닥깔기|#6100x100|m2": "cdc14859bfed", "단산:62|원형돌망태설치및돌채움|#8,45x45cm|m2": "9ad93f86f385", - "단산:61|조약돌채집및운반|굴삭기0.6m3(90%),4.5톤,l=500m|m3": "b07780f6fab7", + "단산:61|조약돌채집및운반|굴삭기0.6m3(90%),4.5톤,l=500m|m3": "9a1760d82e6d", "단산:63|돌망태형옹벽설치|사각돌망태|m2": "61535c440543", - "단산:65|콘크리트포장(인력)|t=20cm,종단15%미만|m3": "14fb1320b388", - "단산:64|비탈면보호공|평떼|m2": "5d9d68fa8679", - "단산:66|콘크리트포장(인력)|t=20cm,종단15%이상|m3": "4739d20909b5", + "단산:65|콘크리트포장(인력)|t=20cm,종단15%미만|m3": "8666fd34f484", + "단산:64|비탈면보호공|평떼|m2": "8aec4dbb43a0", + "단산:66|콘크리트포장(인력)|t=20cm,종단15%이상|m3": "f043bfd10a5d", "단산:70|고화매트설치||m2": "61d7220158c9", "단산:90|큰돌쌓기(찰쌓기)|직경40cm이상~60cm미만|m2": "8efce35c90b3", - "단산:23|레미콘타설(장비사용)|철근구조물|m3": "4814d3d0bb6f", - "단산:73|강관동바리|암거용-1개월사용|공/m3": "bc3f5092d701", - "단산:72|철근가공조립(현장)|type-ll|톤": "75dda2f569a2", - "단산:74|강관비계|10m이하,3개월사용|m2": "90ce7f0da95c", - "단산:49|흙깍기(토사)|굴삭기1.0m3,보통토사|m3": "a893fe169785", + "단산:23|레미콘타설(장비사용)|철근구조물|m3": "1dc7a916fd9d", + "단산:73|강관동바리|암거용-1개월사용|공/m3": "faa3be82f42f", + "단산:72|철근가공조립(현장)|type-ll|톤": "9789b670270e", + "단산:74|강관비계|10m이하,3개월사용|m2": "6a220117ffdc", + "단산:49|흙깍기(토사)|굴삭기1.0m3,보통토사|m3": "ff2987520e9b", "단산:56|잔토정리|굴삭기0.6m3|m3": "63177d8c31eb", - "단산:82|잡철물제작설치|규격철물설치(일반철재)|톤": "531650fd115f", - "단산:95|도장(철재면2회)|방청1,유성2|m2": "92867210fce0", - "단산:81|임도안전표지판설치|삼각,900mm|개소": "b28761e99520" + "단산:82|잡철물제작설치|규격철물설치(일반철재)|톤": "e5155c16079c", + "단산:95|도장(철재면2회)|방청1,유성2|m2": "685b121b9656", + "단산:81|임도안전표지판설치|삼각,900mm|개소": "3c3e7c10dea7" } }, "북부청사방": { "S1": { - "해시": "54e46e934128", + "해시": "25a4810e85a6", "꼴": "엑셀꼴", "호표": 27, "줄": 201, @@ -106,50 +106,50 @@ } }, "S2": { - "단산:22|레미콘타설(장비사용)|철근구조물|m3": "a35e3f7819d4", - "단산:20|레미콘타설(장비사용)|무근구조물|m3": "6caceddb40b1", - "단산:38|유로폼(euroform)|6회,보통|m2": "0cd2081fa29f", - "단산:53|철근가공조립(현장)|type-ii|톤": "d6d92c499d78", - "단산:4|강관비계|(10m이하)|m2": "7c1c9f2d283a", - "단산:3|강관동바리|(2.5m이하)|공/m3": "83d2a92252c6", - "단산:37|유로폼(euroform)|6회,간단|m2": "d6ac6fccfcfd", - "단산:52|철근가공조립(현장)|type-i(소량)|톤": "e3d8470f9851", + "단산:22|레미콘타설(장비사용)|철근구조물|m3": "45b36fcaaf90", + "단산:20|레미콘타설(장비사용)|무근구조물|m3": "e59301fb15aa", + "단산:38|유로폼(euroform)|6회,보통|m2": "800542213689", + "단산:53|철근가공조립(현장)|type-ii|톤": "e709206b3756", + "단산:4|강관비계|(10m이하)|m2": "24d23dce3ba6", + "단산:3|강관동바리|(2.5m이하)|공/m3": "c4aa3723a297", + "단산:37|유로폼(euroform)|6회,간단|m2": "cf8a8ed0c58b", + "단산:52|철근가공조립(현장)|type-i(소량)|톤": "4806c936aa39", "단산:49|철강재운반(할증포함)|(구역화물)|톤": "a619ce7fe816", "단산:51|철골설치||톤": "f8b5b9b49bf5", - "단산:50|철골가공조립(공장)|(대형)|톤": "ebe8740e0a1e", - "단산:42|잡철물제작||톤": "ef730a79e03f", - "단산:67|합판거푸집(6회)||m2": "b3ba20147436", - "단산:33|와이어메시바닥깔기||m2": "2f898ef1909d", - "단산:11|구조물터파기(토사)||m3": "d104c4f35461", - "단산:8|구조물되메우기및다짐||m3": "4b834cdae99f", + "단산:50|철골가공조립(공장)|(대형)|톤": "c9096c02198b", + "단산:42|잡철물제작||톤": "b6adce5aadfc", + "단산:67|합판거푸집(6회)||m2": "be3414857b5e", + "단산:33|와이어메시바닥깔기||m2": "8087ff50eb15", + "단산:11|구조물터파기(토사)||m3": "4d9dbb354eab", + "단산:8|구조물되메우기및다짐||m3": "fd24940ae106", "단산:39|잔토처리||m3": "62e430cfb337", - "단산:21|레미콘타설(장비사용)|무근구조물-소량타설|m3": "32bb4fda75e2", - "단산:66|합판거푸집(4회)||m2": "39bdcddca57c", - "단산:12|기초잡석다짐및채집운반|(9:1)(0.6m3+4.5ton)|m3": "24dcf4cf1ac0", - "단산:26|배수관부설|(d=1000mm)|m": "7c06bc3e3a91", + "단산:21|레미콘타설(장비사용)|무근구조물-소량타설|m3": "0dc1592cbcc7", + "단산:66|합판거푸집(4회)||m2": "4de052e4c56f", + "단산:12|기초잡석다짐및채집운반|(9:1)(0.6m3+4.5ton)|m3": "fdc90c605f35", + "단산:26|배수관부설|(d=1000mm)|m": "11fdb99cff27", "호표:7": "9238de708278", - "단산:19|레미콘타설(인력운반)|무근구조물-소량타설|m3": "728a28bc9224", - "단산:9|구조물터파기|인력|m3": "06855a4c159a", + "단산:19|레미콘타설(인력운반)|무근구조물-소량타설|m3": "8b2f7d368bbf", + "단산:9|구조물터파기|인력|m3": "53b9284a35b9", "단산:7|구조물되메우기|인력|m3": "8d23c8175496", "단산:40|잔토처리|인력|m3": "e488fa518c11", "단산:35|원형돌망태조립설치|산림사업품셈|조": "05d490e21236", "단산:36|원형돌망태채움|산림사업품셈|조": "c8e1f0314e45", "단산:43|조약돌채집운반|(9:1)(0.6m3+4.5ton)|m3": "1cc5362dc3b4", - "단산:10|구조물터파기(암)||m3": "0d821feb9c44", - "단산:27|배수관부설|(d=500mm)|m": "964f47b3dd45", - "단산:18|레미콘타설(무근펌프카)(130m3)|41m(80-95m3/hr)|m3": "6da4e3284f73", + "단산:10|구조물터파기(암)||m3": "396c350e2776", + "단산:27|배수관부설|(d=500mm)|m": "f2b5609dc9c3", + "단산:18|레미콘타설(무근펌프카)(130m3)|41m(80-95m3/hr)|m3": "9828a517b4f0", "줄:콘크리트펌프차|41m,80~95m3/hr|시간": "6dfaed1dd20a", - "단산:25|문양거푸집||m2": "fba1f776a83c", + "단산:25|문양거푸집||m2": "8627728779f5", "단산:46|지수판설치|산림사업품셈|m": "3ddb72dcd81d", "단산:1|p.v.c파이프설치|산림사업품셈|m": "ec1cd531e2e6", "단산:17|떼붙임(평떼)|산림사업품셈|m2": "0319c3fc3b39", "단산:60|큰돌메쌓기|40cm-60cm,(산림사업품셈)|m2": "83f1a9a2b201", "단산:5|고임돌채집및운반|(9:1)(0.6m3+4.5ton)|m3": "6f841c019f4e", - "단산:16|뒷채움잡석채집운반다짐|(9:1)(0.6m3+4.5ton)|m3": "eb182b275f99", + "단산:16|뒷채움잡석채집운반다짐|(9:1)(0.6m3+4.5ton)|m3": "5a400cb120bb", "단산:62|큰돌찰쌓기|40cm-60cm,(산림사업품셈)|m2": "3045ba240634", "단산:63|큰돌채집운반|산림사업품셈|m2": "35a75e3957bb", "단산:13|뒷채움잡석구입운반||m3": "c425998ad058", - "단산:15|뒷채움잡석뒷채움||m3": "f9d5674557e8", + "단산:15|뒷채움잡석뒷채움||m3": "f3c013a1ce17", "단산:14|뒷채움잡석구입운반|(창봉기번3)(2차)|m3": "8a37a388d59a", "단산:59|큰돌메붙임|40cm-60cm,(산림사업품셈)|m2": "1aefe1f21b3b", "단산:61|큰돌찰붙임|40cm-60cm,(산림사업품셈)|m2": "b140bb188ade" @@ -157,7 +157,7 @@ }, "거창": { "S1": { - "해시": "bf450b2215f9", + "해시": "afc5d54799b3", "꼴": "엑셀꼴", "호표": 14, "줄": 68, @@ -188,26 +188,26 @@ "호표:1": "1f6345e0085e", "호표:2": "dacbaeeaa6c9", "호표:3": "03d873100e35", - "단산:30|유로폼|보통,0~7m|m2": "8e45789b4266", - "단산:38|철근가공및조립(현장)|type-ii-1|톤": "f65df34c4c3e", - "단산:39|강관비계|10m이하(3개월)|m2": "a96380c10324", - "단산:29|유로폼|간단,0~7m|m2": "cb2624477569", + "단산:30|유로폼|보통,0~7m|m2": "501fe07f4185", + "단산:38|철근가공및조립(현장)|type-ii-1|톤": "73a54b634a0b", + "단산:39|강관비계|10m이하(3개월)|m2": "067dfdd5b146", + "단산:29|유로폼|간단,0~7m|m2": "88f29e6a4365", "호표:10": "fc2cc4cd2868", "호표:11": "998a0263fed9", "단산:36|레미콘타설(장비사용타설)|무근구조물|m3": "c0b9cfa60d77", "단산:40|레미콘타설(장비사용타설)|철근구조물|m3": "198ef508476c", - "단산:41|합판거푸집|4회|m2": "965f0578c65a", - "단산:31|철근가공및조립(현장)|type-i-1(소형)|톤": "a98e998b35c0", - "단산:42|강관동바리|2.5m이하(1개월)|공m3": "15e8a1fae939", - "단산:43|잡철물제작및설치(현장)|일반철재|톤": "9803bb826c42", + "단산:41|합판거푸집|4회|m2": "704b0fdaeadb", + "단산:31|철근가공및조립(현장)|type-i-1(소형)|톤": "54f67b634ae1", + "단산:42|강관동바리|2.5m이하(1개월)|공m3": "d52c8d76c9f2", + "단산:43|잡철물제작및설치(현장)|일반철재|톤": "b3663267beb4", "단산:44|안내표지판설치|단주식|개소": "3c6f75841d3b", "단산:37|레미콘타설(인력운반타설)|소형구조물|m3": "6c2df8e4fd6a", - "단산:45|합판거푸집|6회,간단|m2": "ec2e16734886" + "단산:45|합판거푸집|6회,간단|m2": "92ecf82007cf" } }, "봉화": { "S1": { - "해시": "4c3ba7796291", + "해시": "cca52acdd220", "꼴": "STmate", "호표": 45, "줄": 209, @@ -236,57 +236,57 @@ "S2": { "줄:굴삭기(타이어)|0.6m3,(부착용집게)|시간": "7ba959ec2e9d", "호표:1": "ee8a116be0f0", - "단산:96|레미콘타설(장비)|무근,소형|m3": "fed15560be74", - "단산:102|합판거푸집(간단)|간단|m2": "70042bc48cb6", - "단산:4|글자새김|자당6x6|자": "ff7e1309d192", - "단산:2|구조물터파기(토사)|굴삭기0.2m3|m3": "cbead3149b3c", - "단산:7|구조물되메우기|굴삭기0.2m3|m3": "3ff7d6c09304", - "단산:49|잔토처리|굴삭기0.2m3|m3": "bc633bad0c98", + "단산:96|레미콘타설(장비)|무근,소형|m3": "3bdd0e5eb270", + "단산:102|합판거푸집(간단)|간단|m2": "10209a3d5b62", + "단산:4|글자새김|자당6x6|자": "b1209218ac3b", + "단산:2|구조물터파기(토사)|굴삭기0.2m3|m3": "700f7df2e662", + "단산:7|구조물되메우기|굴삭기0.2m3|m3": "1541fd295357", + "단산:49|잔토처리|굴삭기0.2m3|m3": "face6e5be64e", "호표:5": "566b3ec95fef", - "단산:43|깬잡석채집|l=45cm내외|m2": "9382675c6d66", + "단산:43|깬잡석채집|l=45cm내외|m2": "a5d14b50213c", "단산:5|고임돌채집|기계(0.7m3,100%)|m3": "e6a8fdb7c927", "단산:6|막자갈채집|기계(0.7m3,100%)|m3": "d862c41aa425", "단산:8|기초다짐및뒤채움|소형장비사용시공|m3": "77b469a4fd17", - "단산:93|유로폼설치및해체|간단|m2": "5174b34972ac", - "단산:95|레미콘타설(장비)|무근|m3": "bff32ab35cc6", - "단산:89|콘크리트믹서사용|0.45m3|m3": "1538c2627ebb", - "단산:94|유로폼설치및해체|보통|m2": "1985ee7ba47c", - "단산:98|철근가공및조립|type-i-1,현장가공조립|톤": "f91f3febebf1", + "단산:93|유로폼설치및해체|간단|m2": "5f812b766302", + "단산:95|레미콘타설(장비)|무근|m3": "49aa20488f8e", + "단산:89|콘크리트믹서사용|0.45m3|m3": "ae4415473c75", + "단산:94|유로폼설치및해체|보통|m2": "dad85444a3f2", + "단산:98|철근가공및조립|type-i-1,현장가공조립|톤": "ddcf7db298af", "호표:9": "992b00a50c65", "호표:13": "2ecc59f87b37", - "단산:51|깬잡석채집|l=30cm내외|m2": "e2b6831be198", + "단산:51|깬잡석채집|l=30cm내외|m2": "ad266f3b3ce9", "단산:12|콘크리트(기계비빔)|무근구조물|m3": "c74e9c324b2c", "호표:17": "b50496d3f922", "호표:18": "d796d9bbd4bd", "호표:22": "2c27c0d9b1e1", - "단산:23|구조물터파기(육상토사0~1m)|기계90%+인력10%(굴삭기0.7m3)|m3": "db3c5678df4f", - "단산:24|잔토처리|굴삭기0.7m3|m3": "d315c818fc0f", + "단산:23|구조물터파기(육상토사0~1m)|기계90%+인력10%(굴삭기0.7m3)|m3": "bb04f68b5b4b", + "단산:24|잔토처리|굴삭기0.7m3|m3": "fd3b96b0b9a6", "줄:굴삭기(타이어)|0.6m3|시간": "733bdb0730f2", "호표:24": "24b7a61d2cac", - "단산:3|깬잡석채집|l=35cm내외|m2": "77cbf6e4a2ff", + "단산:3|깬잡석채집|l=35cm내외|m2": "5060402d5e7a", "호표:26": "253290e88b9f", - "단산:52|깬잡석채집|l=60cm내외|m2": "44b9b3dcffc3", + "단산:52|깬잡석채집|l=60cm내외|m2": "e85cb94d824c", "호표:28": "10cf341d2ca3", "단산:81|철근가공및조립(현장)|일반(type-i-1)|톤": "39d3a7e2e7a9", "호표:31": "82b74ab253f3", - "단산:77|합판거푸집|3회(복잡)|m2": "deaabc9a391f", + "단산:77|합판거푸집|3회(복잡)|m2": "969bc15d51cd", "호표:33": "35144fc92a5c", "호표:34": "9b6cffd83699", "호표:35": "4eb05246e0c3", - "단산:10|레미콘타설(장비사용)|소형구조물|m3": "640411e0f0a8", - "단산:90|평떼붙임||m2": "13cce5a5166b", + "단산:10|레미콘타설(장비사용)|소형구조물|m3": "55ad232a7b36", + "단산:90|평떼붙임||m2": "7929ad2a204f", "단산:91|단끊기||m": "8701e94849ea", - "단산:97|레미콘타설(장비)|철근|m3": "d419cafa4932", - "단산:103|레미콘타설(장비)|무근,버림|m3": "773debae71b8", - "단산:99|철근가공및조립|type-ii-1,현장가공조립|톤": "e6dc6a25683b", - "단산:113|강관동바리|암거용-3개월사용|공/m3": "55bd7c4f030a", + "단산:97|레미콘타설(장비)|철근|m3": "5153927fbb0e", + "단산:103|레미콘타설(장비)|무근,버림|m3": "219fde900cbf", + "단산:99|철근가공및조립|type-ii-1,현장가공조립|톤": "755490544994", + "단산:113|강관동바리|암거용-3개월사용|공/m3": "60055e2845ae", "단산:112|강관비계|3개월사용|m2": "2390793aeb4b", "호표:45": "ca2600044054" } }, "동부청영월": { "S1": { - "해시": "5dd9ce597d5d", + "해시": "3b9d2d0ecd25", "꼴": "STmate", "호표": 23, "줄": 138, @@ -313,48 +313,48 @@ }, "S2": { "호표:1": "7cf2b5da2c8d", - "단산:2058|무근콘크리트타설|인력운반타설|m3": "b661921b3a81", - "단산:56|합판거푸집(6회)|간단|m2": "6275e4b9a5fd", - "단산:2|구조물터파기(인력)|경질토사|m3": "706e398f653a", - "단산:7|구조물되메우기|(인력)|m3": "daf607a62e39", - "단산:9|잔토처리|인력100%|m3": "aef7e1b84e30", + "단산:2058|무근콘크리트타설|인력운반타설|m3": "19675e0b4a14", + "단산:56|합판거푸집(6회)|간단|m2": "f05022004d58", + "단산:2|구조물터파기(인력)|경질토사|m3": "9dc92561d1a7", + "단산:7|구조물되메우기|(인력)|m3": "65c7c4432425", + "단산:9|잔토처리|인력100%|m3": "9aae4953d9af", "줄:크레인(타이어)|10톤|시간": "96a1f07e863b", "호표:3": "6c59b8689986", - "단산:3|구조물터파기|토사(b/h90%+인력10%)|m3": "7953545f58b8", - "단산:10|잔토처리|토사(b/h100%)|m3": "fa9015056847", - "단산:37|콘크리트타설|임도품셈5-5|m3": "2d0b3ca0824f", - "단산:59|유로폼설치,해체(복잡)|수직고7.0m이하|m2": "6b60ee0cf532", - "단산:60|철근가공조립(현장)|typei|톤": "d1c00f23be62", - "단산:600|기초잡석채집및운반부설|4.5톤(l=500)|m3": "4784bc1b14b0", - "단산:1926|임도안전표지판설치|삼각,d=900|개소": "e05b7a689c1d", - "단산:411|야면석채집운반|(25x25x35cm)장비|m2": "545af7826ba4", - "단산:644|야면석찰붙임|뒷길이35cm|m2": "20cfc54d1cb3", - "단산:1361|고임돌채집운반|4.5톤덤프(l=500)|m3": "09f75762e692", - "단산:1928|잡석운반및다짐||m3": "7c507cfd7d53", + "단산:3|구조물터파기|토사(b/h90%+인력10%)|m3": "75b2a9e2758c", + "단산:10|잔토처리|토사(b/h100%)|m3": "e8f75747c06d", + "단산:37|콘크리트타설|임도품셈5-5|m3": "87510186f0de", + "단산:59|유로폼설치,해체(복잡)|수직고7.0m이하|m2": "8a4c90f550c6", + "단산:60|철근가공조립(현장)|typei|톤": "b18e9e3f49b4", + "단산:600|기초잡석채집및운반부설|4.5톤(l=500)|m3": "93506a8cfa33", + "단산:1926|임도안전표지판설치|삼각,d=900|개소": "801743f46a2e", + "단산:411|야면석채집운반|(25x25x35cm)장비|m2": "f51e051f8ca4", + "단산:644|야면석찰붙임|뒷길이35cm|m2": "8264d14c993e", + "단산:1361|고임돌채집운반|4.5톤덤프(l=500)|m3": "bedafc1f74ea", + "단산:1928|잡석운반및다짐||m3": "d358fb5fb902", "단산:1927|면고르기||m2": "5d24952044bf", - "단산:1942|구조물터파기|인력(경질토사)|m3": "b25a97237575", - "단산:1759|도로반사경설치|d=1000(sts)|개소": "8f206fca6ae6", - "단산:2071|철근콘크리트타설|장비사용타설|m3": "998daf7e771a", - "단산:58|유로폼설치,해체(보통)|수직고7.0m이하|m2": "de90b05e1449", - "단산:1854|지수판설치(pvc용접)|200x5t|m": "3aec0042517c", - "단산:1855|배수관부설및접합(p.v.c)|ø50mm-vg2일반관|m": "7200d398af99", - "단산:1466|야면석소운반(l=35cm)|(l=20m)|m2": "79a4f66ac624", - "단산:450|콘크리트소운반|(l=20m)|m3": "9d06f1876010", - "단산:1843|야면석메붙임|뒷길이35cm|m2": "e366c880bdb0", - "단산:258|측구수로관부설|500∼700kg미만|m": "d7100836ca16", - "단산:1844|콘크리트타설|장비사용타설(소형)|m3": "2d506172eebd", - "단산:1841|합판거푸집(4회)|보통|m2": "154931aee1b7", - "단산:2001|혼합골재부설|영월-산불|m3": "64a7980290a9", - "단산:1748|콘크리트포장(인력)|t=20cm|m3": "68e42fbf6871", - "단산:1752|콘크리트포장절단공|임도|m": "35f09be77f1b", - "단산:1519|뒷채움돌채집운반부설|4.5톤덤프트럭(l=500m)|m3": "4bcbbc780730", + "단산:1942|구조물터파기|인력(경질토사)|m3": "6ac8e6535db4", + "단산:1759|도로반사경설치|d=1000(sts)|개소": "591d3af92ef2", + "단산:2071|철근콘크리트타설|장비사용타설|m3": "14058d022c81", + "단산:58|유로폼설치,해체(보통)|수직고7.0m이하|m2": "55f898642a30", + "단산:1854|지수판설치(pvc용접)|200x5t|m": "b20a917b6455", + "단산:1855|배수관부설및접합(p.v.c)|ø50mm-vg2일반관|m": "e3cc2552bb96", + "단산:1466|야면석소운반(l=35cm)|(l=20m)|m2": "ad8e2f6a3486", + "단산:450|콘크리트소운반|(l=20m)|m3": "672d333cdaa1", + "단산:1843|야면석메붙임|뒷길이35cm|m2": "87734e21fbea", + "단산:258|측구수로관부설|500∼700kg미만|m": "ba4634abc727", + "단산:1844|콘크리트타설|장비사용타설(소형)|m3": "483203404b78", + "단산:1841|합판거푸집(4회)|보통|m2": "719613d07edf", + "단산:2001|혼합골재부설|영월-산불|m3": "e885ed4150e6", + "단산:1748|콘크리트포장(인력)|t=20cm|m3": "d5c1eeb72cef", + "단산:1752|콘크리트포장절단공|임도|m": "bc68722fb0a4", + "단산:1519|뒷채움돌채집운반부설|4.5톤덤프트럭(l=500m)|m3": "15726ae1a70d", "단산:81|큰돌메쌓기|뒷길이0.6~0.8m|m2": "3c6626a3d944", - "단산:1810|큰돌채집운반(뒷길이0.6~0.8m)|장비(80%)+인력(20%)|m2": "374e7a1f93a3" + "단산:1810|큰돌채집운반(뒷길이0.6~0.8m)|장비(80%)+인력(20%)|m2": "0e6ab3e3ac20" } }, "울진대흥": { "S1": { - "해시": "2a378597aa15", + "해시": "12eaa4a14b95", "꼴": "STmate", "호표": 34, "줄": 171, @@ -387,38 +387,38 @@ "호표:4": "9b0429327796", "호표:5": "5e88ce0e4c68", "호표:6": "31bd5eea19c8", - "단산:75|콘크리트믹서사용|0.45m3|m3": "b470c79eb976", + "단산:75|콘크리트믹서사용|0.45m3|m3": "8e5eb3e9f3c4", "호표:7": "3476f12e55bf", "호표:8": "b4f5087c15f6", "호표:9": "51afe3b087b1", "호표:10": "fb1dd2cf4c6a", "줄:굴삭기(0.7m3)||시간": "9c5792c9e5eb", - "호표:11": "af2de3ed3f1c", - "단산:1124|고임돌채집|기계|m3": "ae817a2ac5a0", - "단산:159|뒷채움|150m/m내외|m3": "08709d597e93", - "단산:959|막자갈채집|기계|m3": "eacff8bea289", - "단산:2200|콘크리트비빔|채움재|m3": "91472fd9fe57", + "호표:11": "ba1f37ecaab9", + "단산:1124|고임돌채집|기계|m3": "a1cf163a1f1e", + "단산:159|뒷채움|150m/m내외|m3": "7431ad34121c", + "단산:959|막자갈채집|기계|m3": "aeb66dd69acc", + "단산:2200|콘크리트비빔|채움재|m3": "190aea5c6fd3", "줄:트럭탑재형크레인|5톤|시간": "71cbed958600", "호표:17": "ad84568a1c33", "호표:18": "6f1c4c076fd4", "단산:1129|야면석채집|l3=45cm내외|m2": "a59e92e86b67", "단산:2119|야면석채집|l3=35cm내외|m2": "b0e17ce1bcaf", - "단산:1343|구조물터파기(토사)|굴삭기0.7m3|m3": "72abcf573fe3", - "단산:2139|다지기|굴삭기0.7m3|m3": "2718acaec93b", - "단산:2195|유로폼설치및해체|간단|m2": "2ada005f71d9", - "단산:2262|레미콘타설|무근(장비사용타설)|m3": "fca8b8959999", + "단산:1343|구조물터파기(토사)|굴삭기0.7m3|m3": "cba038ed3f23", + "단산:2139|다지기|굴삭기0.7m3|m3": "f2a1490aa294", + "단산:2195|유로폼설치및해체|간단|m2": "5d7d624aeca4", + "단산:2262|레미콘타설|무근(장비사용타설)|m3": "f2348dbe82cb", "호표:28": "fd2ef8bc220b", "줄:굴삭기(타이어)|0.6m3,(부착용집게,0.7~0.8m3)|시간": "94a580e075dd", "호표:29": "02e551e5e700", "호표:30": "92eda2cf44f5", - "단산:2248|콘크리트포장(b-type)|t=20cm|m2": "ad040befe954", - "단산:2251|철근가공및조립|type-ii-2,현장가공조립|톤": "b878bf35ed64", - "단산:2246|유로폼설치및해체|보통|m2": "1985ee7ba47c" + "단산:2248|콘크리트포장(b-type)|t=20cm|m2": "48cbda6f2b6d", + "단산:2251|철근가공및조립|type-ii-2,현장가공조립|톤": "9fbd11bd4b3b", + "단산:2246|유로폼설치및해체|보통|m2": "dad85444a3f2" } }, "영덕계류": { "S1": { - "해시": "3d03cb61741c", + "해시": "db15524c05a3", "꼴": "STmate", "호표": 9, "줄": 31, @@ -448,7 +448,7 @@ "호표:6": "e3b9e469607e", "호표:7": "1cd3bb8c5e6d", "호표:8": "f5f08c081e83", - "단산:150|고임돌채집|기계|m3": "7de261f760cd", + "단산:150|고임돌채집|기계|m3": "4fcbb6448588", "호표:10": "c5b7c43c3ee1", "단산:1453|인력운반|기타|톤": "355f5eac9936", "호표:12": "c46a6d7e568f", @@ -457,7 +457,7 @@ }, "울진소광": { "S1": { - "해시": "bd1310dccd12", + "해시": "17ce10ee5102", "꼴": "STmate", "호표": 25, "줄": 117, @@ -484,7 +484,7 @@ } }, "S2": { - "단산:1352|유로폼설치및해체|보통|m2": "2b8ac7770966", + "단산:1352|유로폼설치및해체|보통|m2": "deb15aff38fc", "호표:2": "217ebea3fe00", "호표:3": "98a36785cfcd", "호표:4": "a3d428a8fc6a", @@ -493,25 +493,25 @@ "호표:7": "e37dd3ced06c", "줄:트럭탑재형크레인|5톤|시간": "657e83282c02", "호표:8": "a902f332f29c", - "단산:1351|유로폼설치및해체|간단|m2": "86c99fa46000", + "단산:1351|유로폼설치및해체|간단|m2": "ce054763f5db", "호표:11": "b3ae0549b0fd", "호표:12": "39f4044d3634", "줄:굴삭기(타이어)|0.6m3|시간": "733bdb0730f2", "호표:13": "af5f52dc7ecb", - "단산:1456|구조물터파기(토사)|굴착기0.6m3|m3": "0973406f3e2a", - "단산:1474|구조물되메우기|굴착기0.6m3|m3": "d7cddaa3d1ba", + "단산:1456|구조물터파기(토사)|굴착기0.6m3|m3": "684995b0e78e", + "단산:1474|구조물되메우기|굴착기0.6m3|m3": "e25acbd5fb5f", "단산:1475|구조물잔토처리|굴착기0.6m3|m3": "5827fbbcc4b8", "단산:492|채움콘크리트|기계비빔|m3": "07f036449ef1", - "단산:91|고임돌채집|굴삭기0.7m3|m3": "d46bfcbe8564", + "단산:91|고임돌채집|굴삭기0.7m3|m3": "9787aaa738aa", "단산:408|막자갈(파쇄석)채집||m3": "946bdf1420ed", - "단산:867|막자갈채우기|150mm내외|m3": "d2df9375e449", - "단산:1336|레미콘타설|무근(장비)|m3": "9991557bff22", + "단산:867|막자갈채우기|150mm내외|m3": "3d8f7f21ec2b", + "단산:1336|레미콘타설|무근(장비)|m3": "726c0780eba3", "단산:918|채움con'c|기계비빔|m3": "0c75dd9a29d2", "단산:1448|파쇄석채집|l3=35cm내외|m2": "8f7299995319", "단산:1030|고임돌채집|굴삭기0.2m3|m3": "669ef7170a82", - "단산:1354|콘크리포장(t=20cm)포설|인력포설|m3": "ec05dd9a542f", - "단산:794|단끊기|절취없음|m": "6aa07c8cba17", - "단산:1329|평떼붙이기||m2": "0522e960d244", + "단산:1354|콘크리포장(t=20cm)포설|인력포설|m3": "4f918370f22d", + "단산:794|단끊기|절취없음|m": "c1b1ee9a9c7b", + "단산:1329|평떼붙이기||m2": "3b490d525b05", "단산:1129|떼하차비||m2": "5329db32bc8a" } } diff --git a/resources/tester/test_628_hopyo_clause_part.py b/resources/tester/test_628_hopyo_clause_part.py new file mode 100644 index 000000000..0dba667cd --- /dev/null +++ b/resources/tester/test_628_hopyo_clause_part.py @@ -0,0 +1,117 @@ +"""781-1 · 781-2(요구 628 · 629 · 630) — 출처 책 · 부문 · 옛 책 가림 · +「근거 조항 우리 기초에 없음」 표지. + +책 낱말(임도 · 사방 = 옛 책 · 건설 부문 낱말) · 부문 풀이(북부청임도 단산10 → 건설 공통 1-2-7 · +영월 「건설표준품셈 9-1」 → 번호 뜻 안 맞음 = (옛)) · 임도 옛 번호 → 대응표 현행 번호 · +조항 견줌은 책 + 부문 · 예시 1(단산10 근거 = 「기초 없음 — 건설 공통 1-2-7 운반」 · +줄 금액 94,227.7 그대로) · 번호만 같은 다른 부문 · 옛 번호 로직이 1위 · 다섯 안에 안 듦 +(봉화 깬잡석채집 · 영월 콘크리트소운반) · 여덟 파일 표지 수. +읽기만 · 서버 없음 · master_data 그대로. +""" + +from __future__ import annotations + +from collections import Counter + +import pytest + +from M01_MasterData import M01_MasterData_Store as m01 +from M02_MasterTemplete import M02_Hopyo_Basis as hb +from M02_MasterTemplete import M02_Hopyo_Fit as fit +from M02_MasterTemplete import M02_Hopyo_Match as hm +from M02_MasterTemplete import M02_Hopyo_ReadEst as est +from M02_MasterTemplete import M02_Hopyo_Sangeun as sangeun +from M02_MasterTemplete import M02_Hopyo_SkUnit as skunit +from M02_MasterTemplete import M02_Hopyo_Units as units +from M02_MasterTemplete import M02_Hopyo_Verdict as verdict +from resources.tester import helper_m02_hopyo_golden as golden + +NO_BASIS = "근거 조항 우리 기초에 없음 — 건설품셈 공통 1-2-7 운반" + + +def _block(tag: str, no: int) -> dict: + return next(b for b in golden.read(tag)["단산"] if b["번"] == no) + + +def _unit(tag: str, no: int) -> tuple[dict, dict]: + """(블록, 그 블록을 부르는 첫 셈 단위 줄).""" + for _, block, line in fit.units(golden.read(tag)): + if block and block.get("번") == no: + return block, line + raise AssertionError(f"{tag} 단산{no} 셈 단위 없음") + + +def test_책_낱말(): + assert est.sources("건설공사표준품셈 1-2-7") == [["건설품셈", "1-2-7"]] + assert est.sources("임도표준품셈 5-1-2") == [["임도품셈(옛)", "5-1-2"]] + assert est.sources("사방품셈 4-2-2") == [["사방품셈(옛)", "4-2-2"]] + assert est.sources("공통품셈 3-1-8") == [["건설품셈 공통", "3-1-8"]] + assert est.sources("토목품셈 2-7-1") == [["건설품셈 토목", "2-7-1"]] + assert est.sources("[2025년 산림사업표준품셈 9-14]") == [["산림품셈", "9-14"]] + assert est.sources("2019년건설표준품셈 3-1-3") == [["건설품셈", "3-1-3"]] + + +def test_부문_풀이_옛_번호(): + assert _block("북부청임도", 10)["출처"] == [["건설품셈 공통", "1-2-7"]] + assert _block("동부청영월", 450)["출처"] == [["건설품셈(옛)", "9-1"]] + assert hb.road_map()["5-1-2"] == "9-13" # 가 · 나 항 → 현행 윗마디 + assert hb.road_map()["3-3-1"] == "9-3-2" and "3-3-2" not in hb.road_map() # 일대다 안 옮김 + assert hb.resolve([["임도품셈(옛)", "2-3"]], "제근") == [["산림품셈", "9-21"]] + assert hb.resolve([["임도품셈(옛)", "5-5"]], "콘크리트타설") == [["임도품셈(옛)", "5-5"]] + assert hb.resolve([["건설품셈", "1-2-7"]], "") == [["건설품셈", "1-2-7"]] # 이름 없음 = 모름 + + +def test_조항_견줌_부문(): + ours = hb.ours() + for code in ("GC001004", "GC000567"): # 유지관리 · 건축 1-2-7 + assert hb.clause([["건설품셈 공통", "1-2-7"]], ours[code])[0] == 0 + assert hb.clause([["건설품셈(옛)", "1-2-7"]], ours[code])[0] == 0 + assert hb.clause([["건설품셈", "1-2-7"]], ours[code])[0] == 1.0 # 부문 모름 = 그대로 + + +def test_예시1_기초_없음(): + block, line = _unit("북부청임도", 10) + got = verdict.block(block, line, hm.targets()) + assert got["기초없음"]["판정"] == "기초 없음" + assert NO_BASIS in got["판정"]["꼬리표"] + assert all(c["글"] == verdict.OUTSIDE for c in got["후보"] if c["판정"] != "맞음") + maker = sangeun.Maker.__new__(sangeun.Maker) + maker.first, maker.blocks, maker.whole = {10: line}, {10: block}, m01.loaded()[1] + maker.cache = {fit.unit_key(line): got} + assert maker.basis(10) == { + "로직": None, + "이름": "기초 없음 — 건설 공통 1-2-7 운반", + "판정": "기초 없음", + } + judged = skunit.judge(block, units.Units(), lambda sub: None, None, "㎡") + assert judged["판정"] == "옮길 수 있음" and judged["우리금액"] == pytest.approx(94227.7, abs=1) + rows = [r for r in judged["더한줄"] if r.get("요소") == "LB000002"] + assert rows and rows[0]["수량"] == pytest.approx(0.547619, abs=1e-6) + + +@pytest.mark.parametrize( + "tag, no, wrong", + [ + ("봉화", 3, "GC000721"), + ("봉화", 43, "GC000721"), + ("봉화", 51, "GC000721"), + ("봉화", 52, "GC000721"), + ("동부청영월", 450, "GC000677"), + ], +) +def test_번호만_같은_로직_안_오름(tag, no, wrong): + block, line = _unit(tag, no) + got = verdict.block(block, line, hm.targets()) + assert got["차례"][0] != wrong and got["판정"]["상태"] != "다른 점" + for code in got["차례"]: # 다섯 안에 번호로 오른 것 없음(지문 점수로만 남을 수 있음) + our = hb.ours().get(code) + assert not our or hb.clause(block["출처"], our)[0] == 0 or our["구분"] in block["출처"][0] + + +def test_여덟_파일_표지_수(): + seen = Counter() + for tag in golden.FILES: + for block in golden.read(tag)["단산"]: + seen[(hb.no_basis(block["출처"]) or {}).get("판정")] += 1 + assert seen["기초 없음"] == 19 + assert seen["옛 번호"] == 121 diff --git a/resources/tester/test_m02_hopyo_basis.py b/resources/tester/test_m02_hopyo_basis.py index d6383a695..5cb71721b 100644 --- a/resources/tester/test_m02_hopyo_basis.py +++ b/resources/tester/test_m02_hopyo_basis.py @@ -41,12 +41,12 @@ def test_블록_수_와_근거(north): sabang = hx.read_book(SABANG.name, SABANG.read_bytes()) assert len(sabang["단산"]) == 67 assert _line(north, 93)["근거"] == { - "출처": [["건설품셈", "3-2-1"]], + "출처": [["건설품셈 공통", "3-2-1"]], # 781-1 부문 풀이 "직종": ["특별인부"], "행": [4229, 4244], } assert _line(north, 21)["근거"]["직종"] == ["콘크리트공", "보통인부"] # 「보 통 인부」 빈칸 뺌 - assert _line(north, 64)["근거"]["출처"][0] == ["건설품셈", "4-1-1"] + assert _line(north, 64)["근거"]["출처"][0] == ["건설품셈 공통", "4-1-1"] lines = [x for h in north["호표"] for x in h["줄"] if x["갈래"] == "단가산출"] assert all("근거" in x for x in lines) # 268 줄 모두 블록 있음 @@ -55,7 +55,7 @@ def test_출처_글(): assert hb.sources("[건설공사표준품셈 3-2-1 인력굴착]") == [["건설품셈", "3-2-1"]] assert hb.sources("건설표줌품셈(3-1-3) 자갈") == [["건설품셈", "3-1-3"]] assert hb.sources("[2025년 산림사업표준품셈 9-14]") == [["산림품셈", "9-14"]] - assert hb.sources("흙운반 [임도표준품셈 4-2]") == [["산림품셈", "4-2"]] + assert hb.sources("흙운반 [임도표준품셈 4-2]") == [["임도품셈(옛)", "4-2"]] # 781-1 옛 책 assert hb.sources("[건설표준품셈 준용]") == [] @@ -115,7 +115,7 @@ def test_길_응답_근거_칸(client): res = client.get(f"{URL.format('personal')}/HP-000001/3") lines = {x["이름"]: x for x in res.json()["호표"]["줄"]} dig = lines["구조물터파기(인력100%)"] - assert dig["근거"]["출처"] == [["건설품셈", "3-2-1"]] + assert dig["근거"]["출처"] == [["건설품셈 공통", "3-2-1"]] assert dig["맞춤"]["후보"][0]["근거"]["까닭"].startswith("조항 같음") assert "근거" not in lines["임도표지석(받침,글새김포함)"] # 자재 줄 assert master_hash() == before @@ -125,7 +125,7 @@ def test_블록_글_HP(north): """338-2a — 단산마다 출처 · 직종 · 글(머리 뺀 몸 줄 · 200 줄 · 300 자 안) · 꼴.""" assert north["꼴"] == "엑셀꼴" b93 = north["단산"][92] - assert b93["번"] == 93 and b93["출처"] == [["건설품셈", "3-2-1"]] + assert b93["번"] == 93 and b93["출처"] == [["건설품셈 공통", "3-2-1"]] assert b93["직종"] == ["특별인부"] assert b93["글"][0] == " [건설공사표준품셈 3-2-1 인력굴착]" assert " 특별인부 : 226,122 × (3.6+2.7+2.2)/3 ÷ 8 = 80,084.8" in b93["글"] diff --git a/resources/tester/test_m02_hopyo_excel.py b/resources/tester/test_m02_hopyo_excel.py index 0fc51df6b..75cce83e0 100644 --- a/resources/tester/test_m02_hopyo_excel.py +++ b/resources/tester/test_m02_hopyo_excel.py @@ -258,7 +258,8 @@ def test_이음_넓힘_노임(): got = hx.read_book("a.xlsx", _linked_xlsx()) lines = {x["이름"]: x for x in got["호표"][0]["줄"]} assert lines["터파기"]["참조"] == {"종류": "단산", "번": 2} and "이음" not in lines["터파기"] - assert lines["터파기"]["근거"]["출처"] == [["건설품셈", "3-2-1"]] + # 781-1 — 이름 · 글이 원문 제목과 안 맞음 = 옛 번호 + assert lines["터파기"]["근거"]["출처"] == [["건설품셈(옛)", "3-2-1"]] assert lines["되메우기"]["참조"] == {"종류": "단산", "번": 3} # 빈 비고 · 이름 같은 블록 하나 assert lines["되메우기"]["이음"] == "이름" and lines["되메우기"]["갈래"] == "단가산출" assert lines["되메우기"]["근거"]["직종"] == ["보통인부"] diff --git a/resources/tester/test_m02_hopyo_finger.py b/resources/tester/test_m02_hopyo_finger.py index 1e57d4eab..4025355a9 100644 --- a/resources/tester/test_m02_hopyo_finger.py +++ b/resources/tester/test_m02_hopyo_finger.py @@ -69,7 +69,7 @@ def test_합판_4회_지문(): fp = finger.excel(block, line) assert fp["인력"]["형틀목공"]["품"] == approx(0.1111, abs=1e-4) assert fp["인력"]["보통인부"]["품"] == approx(0.0444, abs=1e-4) - assert "4회" in fp["조건"]["횟수"] and fp["출처"] == [["건설품셈", "6-3-1"]] + assert "4회" in fp["조건"]["횟수"] and fp["출처"] == [["건설품셈 공통", "6-3-1"]] assert {m["이름"][:4] for m in fp["자재"] if m["품"]} >= {"내수합판"} diff --git a/resources/tester/test_m02_hopyo_stmate.py b/resources/tester/test_m02_hopyo_stmate.py index 671a2daf9..0491083a6 100644 --- a/resources/tester/test_m02_hopyo_stmate.py +++ b/resources/tester/test_m02_hopyo_stmate.py @@ -101,7 +101,7 @@ def test_영월_줄과_블록(books): two = {x["이름"]: x for x in yw["호표"][1]["줄"]} assert two["국가지점번호판 검증수수료"]["갈래"] == "별도계상" # 비고 「별산」 assert two["구조물터파기(인력)"]["근거"] == { - "출처": [["건설품셈", "3-1-3"]], + "출처": [["건설품셈(옛)", "3-1-3"]], # 781-1 번호 뜻 안 맞음 = 옛 번호 "직종": ["보통인부"], "행": [5, 54], }