feat(781-1 · 781-2): 출처 책 · 부문 · 옛 책 가림 · 「근거 조항 우리 기초에 없음」 표지(요구 628 · 629 · 630)

- 책 낱말 표(ReadEst.book_of) — 임도 · 사방 = 옛 책 「(옛)」 · 공통 · 토목 · 건축 · 기계설비 · 유지관리 = 「건설품셈 <부문>」 · 그 밖 건설품셈(부문 모름) 그대로
- 출처 풀이(Basis.settle · Excel.read_book 끝 한 곳 · 두 형식) — 건설품셈(부문 모름) 번호 → 블록 이름(안 맞으면 글 두 묶음)이 원문 제목 · 같은 번호 로직 이름과 뜻 낱말을 나누는 부문 하나 = 그 부문 · 둘 이상 = 모름 · 없음 = (옛) · 임도 옛 번호 → 지식DB 대응표 확정 한 대응(가 · 나 항 = 현행 윗마디 · 일대다 안 옮김)
- 조항 견줌(Basis.clause) 한 줄 — 책 또는 구분 같음 · (옛) = 없음
- 기초 없음 표지(Basis.no_basis) — 출처 조항(윗 · 아랫마디 포함) 로직 0 = 「근거 조항 우리 기초에 없음 — 건설품셈 공통 1-2-7 운반」 · 옛 번호 = 「옛 번호 — 대응 미확인」 · 맞춤셈 꼬리표 · 맞음 아닌 후보 글 「참고 후보 — 근거 조항 아님」 · 단가산출 근거 꼬리표 1위 대신 표지(1위 맞음이면 1위)
- 시험: 새 test_628 · test_621 단계 해시 다시 굳힘(달라진 칸 = 출처뿐 · S1 수 · 그래프 그대로) · test_m02_hopyo_basis · finger · stmate · excel 출처 기대값 새 책 꼴로(낡은 기대 「임도 = 산림품셈」 · 「건설품셈」 부문 없음)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-11-04 18:42:07 +09:00
co-authored by Claude Opus 5.5
parent 226ee92ff7
commit a8acaed6f5
11 changed files with 481 additions and 179 deletions
+152 -3
View File
@@ -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:
+5 -2
View File
@@ -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:
+24 -5
View File
@@ -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:
+6 -1
View File
@@ -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("이름")
+9 -1
View File
@@ -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 ("로직", "이름", "단위")}
+159 -159
View File
@@ -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"
}
}
@@ -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
+5 -5
View File
@@ -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["글"]
+2 -1
View File
@@ -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["되메우기"]["근거"]["직종"] == ["보통인부"]
+1 -1
View File
@@ -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["품"]} >= {"내수합판"}
+1 -1
View File
@@ -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],
}