feat(B09): 제잡비를 경비로 붙임 + ㉥ 물빼기 파이프 가드 + 성분 빠짐 차단 확대
제잡비 (품셈 13-6-1·13-7-1 [주]③, 원문은 메인이 찾아 줌) - **노무비 합계 × 비율이 경비(J)로** 붙음. 재료·노무가 아님 - 기본은 **아랫단**(물빼기 파이프 미설치). 「상한」이라 산출근거에 그 사실을 적음 - `PriceDetail.percent_of_labor` 신설 — 밑수가 3분할 전체인 `percent_of_parent` 와 다름. 비율 줄은 참조를 풀기 전에 처리(자기 자신을 가리켜 순환으로 잡혔음) - 표시 합계에도 넣음 — 안 넣으면 화면 합계와 실제 단가가 어긋남 ㉥ 가드 신설 — 제잡비 윗단(파이프 설치)을 쓰면서 물구멍을 자재로 또 실으면 멈춤 (원문이 「상단에는 파이프 설치의 노무비·재료비를 포함한다」고 명시). 짝 시험 4건 — 아랫단·파이프 없음·비슷한 이름은 안 걸림 성분 빠짐 차단 확대 - 시간·대수로 세는 줄(장비 몫)을 못 맞추면 그 공종 금액을 안 만듦. 초본류 시비가 「트럭(2.5t) 2.6시간」을 빼고 33.1원/㎡ 로 서 있었음 - 자재 줄(kg·매)은 이미 알려진 미결이라 막지 않고 드러내기만 함 - 요약의 「의심 저가」에서 막아 둔 공종은 제외 — 새로 살펴야 할 것과 섞이지 않게 뭉친 표 두 곳 수정 - 칸 전체를 한 이름으로 먼저 시도 — 「굴착기 (무한궤도)」가 안 풀려 찰쌓기 13-6-2 의 장비 몫이 빠지고 공종이 막혀 있었음 - ⚠ 라벨 줄 판정에 **순수 숫자 칸** 검사 추가 — 「자재 | 종자 | kg | 0.025」를 라벨 줄로 오해해 씨앗뿜어붙이기 표를 가로챘음(회귀, 시험이 잡음) 결과: 자원 축 354 → 418, 일위대가 176 → 203, 막힌 공종 32 찰쌓기 13-6-2 = 118,794.9원/㎡ (노무 + 굴착기 0.48h + 제잡비 3 %) 검증: pytest 190 통과(신규 6) Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -319,6 +319,11 @@ def match_packed_rows(
|
||||
# 자원으로 **안 풀리는** 줄이면 갈래 라벨 줄로 본다.
|
||||
if not labels:
|
||||
return False
|
||||
# ⚠ **라벨 줄에는 순수 숫자 칸이 없다.** 「35cm 이하」는 수를 품되 순수 수가 아니고,
|
||||
# 「자재 | 종 자 | | kg | 0.025」는 순수 수(0.025)가 있는 **자료 줄**이다.
|
||||
# 이 구분을 안 두면 씨앗뿜어붙이기 표를 라벨 줄로 오해해 표째 가로챈다(2026-09-08 회귀).
|
||||
if any(parse_amount(cell) is not None for cell in rows[0]):
|
||||
return False
|
||||
# ⚠ **첫 줄의 어느 칸이라도 자원이면 라벨 줄이 아니다.** 첫 칸만 보면 기초잡석
|
||||
# 12-25 처럼 「소할(30%) | 할석공(인) | 0.2 × 30%」인 표를 라벨 줄로 오해해
|
||||
# 표째 가로챈다(2026-09-08: 그 탓에 기초잡석이 다시 막혔다).
|
||||
@@ -352,10 +357,29 @@ def match_packed_rows(
|
||||
for index, cells in enumerate(rows[1:], start=1):
|
||||
if not cells or not cells[0]:
|
||||
continue
|
||||
names = _packed_names(cells[0])
|
||||
# 제잡비 비율 줄 — 자원이 아니라 **노무비에 붙는 경비율**이다(품셈 [주]③).
|
||||
# 갈래마다 같은 값이 되풀이되므로 **첫 값 칸**만 본다. 「9(9) 3(3)」 = 윗단 9 · 아랫단 3.
|
||||
if "제잡비" in _normalize_label(cells[0]).replace(" ", ""):
|
||||
for cell in cells[1:]:
|
||||
numbers = _packed_numbers(cell)
|
||||
if numbers:
|
||||
upper = numbers[0]
|
||||
lower = numbers[1] if len(numbers) > 1 else numbers[0]
|
||||
result.overhead_ratio[work_item_code] = (upper, lower)
|
||||
break
|
||||
continue
|
||||
|
||||
# 규격은 **옆 칸**에 있을 수 있다 — 「굴착기+부착용 집게 | 0.6㎥ | 시간 | …」.
|
||||
side_specs = [cell for cell in cells[1:3] if cell]
|
||||
resolved = [_resolve_packed(catalog, name, side_specs) for name in names]
|
||||
# ⚠ **칸 전체를 한 이름으로 먼저 본다** — 「굴착기 (무한궤도)」처럼 이름 안에
|
||||
# 공백이 있으면 쪼개서 보다가 통째로 못 푼다(2026-09-08: 찰쌓기 13-6-2 의
|
||||
# 장비 몫이 그래서 빠지고 공종이 막혔다).
|
||||
whole = _resolve_packed(catalog, _normalize_label(cells[0]), side_specs)
|
||||
if whole:
|
||||
names, resolved = [_normalize_label(cells[0])], [whole]
|
||||
else:
|
||||
names = _packed_names(cells[0])
|
||||
resolved = [_resolve_packed(catalog, name, side_specs) for name in names]
|
||||
if not all(resolved):
|
||||
if _packed_numbers(" ".join(cells[1:])):
|
||||
# 수가 있는데 이름을 못 풀었다 — 그 몫이 빠진 채 서면 안 된다.
|
||||
|
||||
Reference in New Issue
Block a user