Merge remote-tracking branch 'origin/dev' into sub_laptop_4
This commit is contained in:
@@ -46,6 +46,7 @@ BOOKS = (
|
||||
"한국은행환율",
|
||||
"조달청제비율",
|
||||
"품셈재료",
|
||||
"질의회신",
|
||||
"자체",
|
||||
)
|
||||
DIVISIONS = {
|
||||
@@ -246,8 +247,8 @@ def check_form(name: str, data: dict) -> list[str]:
|
||||
out.append(f"{where} · 키 모양이 「{tid}」 + 6자리 아님")
|
||||
if merged and row.get(merged) not in data.get(merged, {}):
|
||||
out.append(f"{where} · {merged} 「{row.get(merged)}」 이 머리에 없음")
|
||||
if division and not number.startswith(division + " "):
|
||||
out.append(f"{where} · 원문번호 앞에 부문 「{division}」 없음")
|
||||
if number and mk.section_no(number) != number:
|
||||
out.append(f"{where} · 원문번호는 절 번호만 — 「{mk.section_no(number)}」")
|
||||
if want and (row.get("구분"), row.get("상세구분")) != want:
|
||||
out.append(
|
||||
f"{where} · 구분·상세구분 「{row.get('구분')} · {row.get('상세구분')}」"
|
||||
@@ -423,7 +424,10 @@ def check_body(files: dict[str, dict]) -> list[str]:
|
||||
for name, data in files.items():
|
||||
for table in (data.get("표") or []) if data.get("그룹") in mf.TABLE_GROUPS else []:
|
||||
where = f"{name} · {table.get('키')} {table.get('원문번호')}"
|
||||
md, ident, text = section_of(str(table.get("출처", "")))
|
||||
source = str(table.get("출처", ""))
|
||||
if not source.startswith(("산림품셈", "건설품셈")):
|
||||
continue # 품셈 본문이 아닌 출처(질의회신 등) — 절 대조 대상 아님
|
||||
md, ident, text = section_of(source)
|
||||
if not text:
|
||||
out.append(f"{where} · 본문 절 못 찾음 「{table.get('출처')}」")
|
||||
continue
|
||||
@@ -489,25 +493,29 @@ def check_logics(files: dict[str, dict], whole: mf.Master, book: dict | None = N
|
||||
|
||||
|
||||
def duplicate_keys(files: dict[str, dict], book: dict | None = None) -> list[str]:
|
||||
"""키 겹침(전체) · 원문번호 겹침(테이블 안 — `ID:원문번호` 찾기가 먼저 것만 봄) · 대장 대조."""
|
||||
"""키 겹침(전체) · 원문번호 겹침(인력·재료 — 번호가 곧 그 줄의 이름) · 대장 대조.
|
||||
표·로직·기계의 원문번호는 절 번호·분류번호라 한 절에 여럿이 겹쳐도 됨
|
||||
(겹친 번호를 「ID:원문번호」 로 부르면 `master_formula` 가 잡음)."""
|
||||
book = mk.load_book() if book is None else book
|
||||
first, numbers, out = {}, {}, []
|
||||
for name, data in files.items():
|
||||
rows = data.get("표") if data.get("그룹") in mf.TABLE_GROUPS else data.get("줄")
|
||||
one_each = data.get("그룹") in ("인력", "재료")
|
||||
for row in rows or []:
|
||||
key, number = str(row.get("키")), str(row.get("원문번호"))
|
||||
if key in first:
|
||||
out.append(f"{name} · 키 겹침 「{key}」 — {first[key]} 에도 있음")
|
||||
first.setdefault(key, name)
|
||||
if number and (key[:2], number) in numbers:
|
||||
out.append(
|
||||
f"{name} · {key} · 원문번호 겹침 「{number}」 — {numbers[key[:2], number]}"
|
||||
)
|
||||
numbers.setdefault((key[:2], number), key)
|
||||
if one_each and number:
|
||||
if (key[:2], number) in numbers:
|
||||
out.append(
|
||||
f"{name} · {key} · 원문번호 겹침 「{number}」 — {numbers[key[:2], number]}"
|
||||
)
|
||||
numbers.setdefault((key[:2], number), key)
|
||||
had = book["키"].get(key)
|
||||
if had is None:
|
||||
out.append(f"{name} · {key} · 대장에 없는 키")
|
||||
elif had["원문번호"] != number:
|
||||
elif number not in (had["원문번호"], mk.section_no(had["원문번호"])):
|
||||
out.append(f"{name} · {key} · 대장 원문번호 「{had['원문번호']}」 와 다름")
|
||||
elif int(key[2:]) >= book["다음"].get(key[:2], 0):
|
||||
out.append(f"{name} · {key} · 대장 다음 번호보다 큼")
|
||||
|
||||
@@ -229,10 +229,10 @@ MACH_WORDS = (
|
||||
)
|
||||
FIRST = ("0201-0020", "0201-0040", "0201-0070", "0101-", "0602-", "1306-", "1305-", "0230-")
|
||||
LOGICS = [
|
||||
"공통 8 기계 시간당 경비",
|
||||
"공통 8 기계 시간당 경비(연료 없음)",
|
||||
"공통 8 기계 시간당 경비(조종원 없음)",
|
||||
"공통 8 기계 시간당 손료",
|
||||
"GC000268", # 공통 8 기계 시간당 경비
|
||||
"GC000276", # 공통 8 기계 시간당 경비(연료 없음)
|
||||
"GC000272", # 공통 8 기계 시간당 경비(조종원 없음)
|
||||
"GC000273", # 공통 8 기계 시간당 손료
|
||||
]
|
||||
|
||||
|
||||
@@ -247,7 +247,7 @@ def mach_key(name, size) -> str:
|
||||
def machine_cost(master: mf.Master, mid: str, region="전국평균"):
|
||||
"""로직으로 계산 — 고르기 밖 기계는 메모리에서만 고르기에 더함."""
|
||||
for key in LOGICS:
|
||||
row = master.logic("GC:" + key)
|
||||
row = master.logic(key)
|
||||
spec = next(i for i in row["입력"] if i["이름"] == "기계")
|
||||
added = mid not in spec["고르기"]
|
||||
if added:
|
||||
@@ -255,7 +255,7 @@ def machine_cost(master: mf.Master, mid: str, region="전국평균"):
|
||||
try:
|
||||
given = {"기계": mid, "지역": region}
|
||||
given = {k: v for k, v in given.items() if any(i["이름"] == k for i in row["입력"])}
|
||||
return key, mf.run(master, "GC:" + key, given)
|
||||
return key, mf.run(master, key, given)
|
||||
except (mf.FormulaError, ArithmeticError, KeyError, TypeError):
|
||||
continue
|
||||
finally:
|
||||
@@ -314,50 +314,50 @@ def machine(estx: Estx, files: dict, master: mf.Master):
|
||||
|
||||
|
||||
# ── (4) 일위대가 ──────────────────────────────────────────────────────
|
||||
# (ESTX 제목 코드, 로직 원문번호, 입력[, 단위 환산 배수 — 우리 값 × 배수 = ESTX 단위])
|
||||
# (ESTX 제목 코드, 로직 키, 입력[, 단위 환산 배수 — 우리 값 × 배수 = ESTX 단위])
|
||||
REGION = {"지역": "전국평균", "보정작업": "해당 없음"}
|
||||
PAIRS = [
|
||||
(
|
||||
"U0000001217",
|
||||
"공통 3-2-1 굴착(인력/토사)",
|
||||
"GC000083", # 공통 3-2-1 굴착(인력/토사)
|
||||
{"토질": "보통토사", "넓은구역가산": 0, "용수감": 0, "잔토처리": "없음"},
|
||||
),
|
||||
(
|
||||
"U0000001218",
|
||||
"공통 3-2-1 굴착(인력/토사)",
|
||||
"GC000083", # 공통 3-2-1 굴착(인력/토사)
|
||||
{"토질": "경질토사", "넓은구역가산": 0, "용수감": 0, "잔토처리": "없음"},
|
||||
),
|
||||
("U0000001665", "공통 3-9-1 벌목", {"나무높이": 4, "집재거리": 100, **REGION}, "0.1"),
|
||||
("U0000001666", "공통 3-9-1 벌목", {"나무높이": 6, "집재거리": 100, **REGION}, "0.1"),
|
||||
("U0000001667", "공통 3-9-1 벌목", {"나무높이": 10, "집재거리": 100, **REGION}, "0.1"),
|
||||
("U0000001260", "공통 4-1-1 잔디붙임", {"시공법": "평떼"}),
|
||||
("U0000001261", "공통 4-1-1 잔디붙임", {"시공법": "줄떼"}),
|
||||
("U0000001665", "GC000129", {"나무높이": 4, "집재거리": 100, **REGION}, "0.1"),
|
||||
("U0000001666", "GC000129", {"나무높이": 6, "집재거리": 100, **REGION}, "0.1"),
|
||||
("U0000001667", "GC000129", {"나무높이": 10, "집재거리": 100, **REGION}, "0.1"),
|
||||
("U0000001260", "GC000143", {"시공법": "평떼"}),
|
||||
("U0000001261", "GC000143", {"시공법": "줄떼"}),
|
||||
(
|
||||
"U0000002799",
|
||||
"공통 4-3-4 식재(나무높이)",
|
||||
"GC000154", # 공통 4-3-4 식재(나무높이)
|
||||
{"구분": "인력시공", "나무높이": "2.0이하", "지주목": "세움", **REGION},
|
||||
),
|
||||
(
|
||||
"U0000002802",
|
||||
"공통 4-3-4 식재(나무높이)",
|
||||
"GC000154", # 공통 4-3-4 식재(나무높이)
|
||||
{"구분": "인력시공", "나무높이": "3.0이하", "지주목": "세움", **REGION},
|
||||
),
|
||||
(
|
||||
"U0000002693",
|
||||
"공통 4-3-2 굴취(나무높이)",
|
||||
"GC000152", # 공통 4-3-2 굴취(나무높이)
|
||||
{"구분": "인력시공", "나무높이": "2.0이하", "야생감": 0, "분": "분 있음", **REGION},
|
||||
),
|
||||
(
|
||||
"U0000003316",
|
||||
"공통 4-3-2 굴취(나무높이)",
|
||||
"GC000152", # 공통 4-3-2 굴취(나무높이)
|
||||
{"구분": "기계시공", "나무높이": "2.0이하", "야생감": 0, "분": "분 있음", **REGION},
|
||||
),
|
||||
("U0000002794", "공통 4-2-2 식재(단식)", {"나무높이": "0.3미만"}),
|
||||
("U0000002633", "공통 4-2-3 식재(군식)", {"나무높이": "1.2~1.5이하"}),
|
||||
("U0000002661", "공통 4-1-4 초화류 식재", {"등급": "보통", "특수화단감": 0}),
|
||||
("U0000002794", "GC000149", {"나무높이": "0.3미만"}),
|
||||
("U0000002633", "GC000150", {"나무높이": "1.2~1.5이하"}),
|
||||
("U0000002661", "GC000146", {"등급": "보통", "특수화단감": 0}),
|
||||
(
|
||||
"U0000001171",
|
||||
"공통 6-1-1 레디믹스트콘크리트 타설",
|
||||
"GC000198", # 공통 6-1-1 레디믹스트콘크리트 타설
|
||||
{
|
||||
"타설방법": "인력운반타설",
|
||||
"구조물": "무근구조물",
|
||||
@@ -368,7 +368,7 @@ PAIRS = [
|
||||
),
|
||||
(
|
||||
"U0000001172",
|
||||
"공통 6-1-1 레디믹스트콘크리트 타설",
|
||||
"GC000198", # 공통 6-1-1 레디믹스트콘크리트 타설
|
||||
{
|
||||
"타설방법": "인력운반타설",
|
||||
"구조물": "철근구조물",
|
||||
@@ -377,10 +377,10 @@ PAIRS = [
|
||||
"지역": "전국평균",
|
||||
},
|
||||
),
|
||||
("U0000003127", "공통 6-2-2 철근 현장가공", {"Type": "Type-Ⅰ"}),
|
||||
("U0000003127", "GC000206", {"Type": "Type-Ⅰ"}),
|
||||
(
|
||||
"U0000001068",
|
||||
"공통 6-3-1 합판거푸집",
|
||||
"GC000211", # 공통 6-3-1 합판거푸집
|
||||
{
|
||||
"유형": "복잡",
|
||||
"사용횟수": "3회",
|
||||
@@ -394,24 +394,24 @@ PAIRS = [
|
||||
"구멍땜": 0,
|
||||
},
|
||||
),
|
||||
("U0000001101", "공통 2-7-1 강관비계 설치 및 해체", {"높이": "10m이하", "보호망": "시공"}),
|
||||
("U0000001101", "GC000022", {"높이": "10m이하", "보호망": "시공"}),
|
||||
(
|
||||
"U0000001988",
|
||||
"유지관리 1-2-12 예초",
|
||||
"GC001009", # 유지관리 1-2-12 예초
|
||||
{"연간시공횟수": "연3회 이상", "경사도": "25˚ 미만", "그물막": "필요 없음"},
|
||||
),
|
||||
("U0000001958", "공통 3-7-3 천연섬유사면보호공 설치", {}),
|
||||
("U0000001668", "공통 6-1-6 콘크리트 치핑", {}),
|
||||
("U0000001703", "공통 5-3-4 말뚝두부정리(콘크리트)", {"규격": "ø400", "지역": "전국평균"}),
|
||||
("U0000001694", "공통 5-3-3 말뚝두부정리(강관)", {"규격": "ø400", "지역": "전국평균"}),
|
||||
("U0000003394", "토목 6-2-1 타이튼 접합 및 부설 인력", {"관경": 80, "시공조건": "시공조건 A"}),
|
||||
("U0000077626", "토목 6-3-1 부설 인력", {"관경": 80, "시공조건": "시공조건 A"}),
|
||||
("U0000001958", "GC000117", {}),
|
||||
("U0000001668", "GC000204", {}),
|
||||
("U0000001703", "GC000193", {"규격": "ø400", "지역": "전국평균"}),
|
||||
("U0000001694", "GC000192", {"규격": "ø400", "지역": "전국평균"}),
|
||||
("U0000003394", "GC000383", {"관경": 80, "시공조건": "시공조건 A"}),
|
||||
("U0000077626", "GC000388", {"관경": 80, "시공조건": "시공조건 A"}),
|
||||
(
|
||||
"U0000003527",
|
||||
"토목 6-8-1 주철제 게이트 제수밸브 부설 및 접합 인력",
|
||||
"GC000411", # 토목 6-8-1 주철제 게이트 제수밸브 부설 및 접합 인력
|
||||
{"관경": 50, "시공조건": "시공조건 A"},
|
||||
),
|
||||
("U0000001289", "건축 11-2-6 녹막이 페인트칠", {"시공면": "벽", "비계층수": 0}),
|
||||
("U0000001289", "GC000714", {"시공면": "벽", "비계층수": 0}),
|
||||
]
|
||||
|
||||
|
||||
@@ -449,11 +449,11 @@ def unit_price(estx: Estx, master: mf.Master):
|
||||
h = heads[code]
|
||||
k = Decimal(scale[0]) if scale else Decimal(1)
|
||||
temp, put = with_estx_materials(master, estx, code)
|
||||
row = temp.logic("GC:" + key)
|
||||
row = temp.logic(key)
|
||||
given = {k: v for k, v in given.items() if any(i["이름"] == k for i in row.get("입력", []))}
|
||||
given = {k: Decimal(str(v)) if isinstance(v, (int, float)) else v for k, v in given.items()}
|
||||
try:
|
||||
res = mf.run(temp, "GC:" + key, given)
|
||||
res = mf.run(temp, key, given)
|
||||
res = {c: res[c] * k for c in ("계", *mf.COST_ITEMS)}
|
||||
except (mf.FormulaError, ArithmeticError, KeyError, TypeError) as e:
|
||||
print(f" {code} {h['Name']} {h.get('Size')} | {key} | 멈춤: {str(e)[:120]}")
|
||||
|
||||
@@ -233,17 +233,24 @@ class Master:
|
||||
def __init__(self, files: dict[str, dict]):
|
||||
self.index: dict[str, dict[str, dict]] = {} # 테이블ID → {키: 줄}
|
||||
self.numbers: dict[str, dict[str, dict]] = {} # 테이블ID → {원문번호: 줄}
|
||||
self.twice: set[tuple[str, str]] = set() # 한 절에 여럿 — 번호로 못 부름
|
||||
for data in files.values():
|
||||
rows = data.get("표") if data.get("그룹") in TABLE_GROUPS else data.get("줄")
|
||||
for row in rows or []:
|
||||
key = str(row.get("키"))
|
||||
self.index.setdefault(key[:2], {}).setdefault(key, row)
|
||||
if row.get("원문번호"):
|
||||
self.numbers.setdefault(key[:2], {}).setdefault(str(row["원문번호"]), row)
|
||||
number = str(row["원문번호"])
|
||||
slot = self.numbers.setdefault(key[:2], {})
|
||||
if number in slot:
|
||||
self.twice.add((key[:2], number))
|
||||
slot.setdefault(number, row)
|
||||
|
||||
def get(self, ref: str) -> dict:
|
||||
tid, _, number = ref.partition(":")
|
||||
if number:
|
||||
if (tid, number) in self.twice:
|
||||
raise FormulaError(f"원문번호 겹침 「{ref}」 — 키로 적을 것")
|
||||
row = self.numbers.get(tid, {}).get(number)
|
||||
else:
|
||||
row = self.index.get(ref[:2], {}).get(ref)
|
||||
|
||||
@@ -45,6 +45,15 @@ def id_of(ref: str) -> str:
|
||||
return ref[:2] if ref[:2] in GROUP_OF else ""
|
||||
|
||||
|
||||
SECTION = re.compile(r"^(?:(?:공통|토목|건축|기계설비|유지관리)\s+)?(\d+(?:-\d+)*[가-힣]?)(?:\s|$)")
|
||||
|
||||
|
||||
def section_no(number: str) -> str:
|
||||
"""줄에 적는 원문번호 — 절 번호만(부문·제목 뗌) · 절 번호 모양이 아니면 그대로."""
|
||||
m = SECTION.match(str(number))
|
||||
return m.group(1) if m else str(number)
|
||||
|
||||
|
||||
def load_book(path: Path = BOOK) -> dict:
|
||||
if path.is_file():
|
||||
return json.loads(path.read_text(encoding="utf-8"))
|
||||
@@ -97,14 +106,15 @@ def finish(book: dict) -> dict:
|
||||
def stamp(
|
||||
book: dict, tid: str, rows: list[dict], file: str, extra: dict | None = None
|
||||
) -> list[dict]:
|
||||
"""빌더가 만든 줄(「열쇠」 = 원문번호) → 「키」 · 「원문번호」 · extra 칸을 앞에 둔 줄."""
|
||||
"""빌더가 만든 줄(「열쇠」) → 「키」 · 「원문번호」(절 번호만) · extra 칸을 앞에 둔 줄.
|
||||
대장은 옛 열쇠(부문·제목까지)를 그대로 지님 — 키를 다시 찾는 열쇠라서."""
|
||||
out = []
|
||||
for row in rows:
|
||||
row = dict(row)
|
||||
number = str(row.pop("열쇠", row.get("원문번호")))
|
||||
row.pop("원문번호", None)
|
||||
key = row.pop("키", None) or issue(book, tid, number, file)
|
||||
out.append({"키": key, "원문번호": number, **(extra or {}), **row})
|
||||
out.append({"키": key, "원문번호": section_no(number), **(extra or {}), **row})
|
||||
return out
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user