단끊기는 공정 네 줄을 토질 갈래마다 더하고 [주]⑤ 작업반장 1인/보통인부 20인을 더함. 원문 합계 줄은 절취를 ㎥당으로 더한 값이라 안 씀(까닭을 적은 공종만 섬). 같은 모양 뭉기기 13-12-1 은 안 더함. 「단위:」 없이 분모만 적은 본문 밑수를 읽어 조림·시비·떼심기 22표가 밑수 없음에서 풀림. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BW6Jdsh18WPtYUR6THZJqn
121 lines
5.1 KiB
Python
121 lines
5.1 KiB
Python
"""B09 원가계산 — **공정 줄을 더해 한 품이 되는 표** 읽기 (자원 축 보조, 2026-09-13).
|
|
|
|
공정별 | 보통인부(인) | 비고
|
|
보통토사 | 경질ㆍ고사점토 및 자갈섞인 점토 | 호박돌 섞인 토사
|
|
절 취 | 2.4 | 3.3 | 5.4
|
|
수평잡기 및 단정리 | 0.34 | 0.34 | 0.34
|
|
…
|
|
합계 | 2.03 | 2.09 | 2.23
|
|
|
|
행은 공정, 열은 토질 갈래, 자원은 머리 한 칸. 갈래마다 공정 줄을 **더한 것**이 한 품이다.
|
|
지금은 「숫자 칸이 자원 열보다 많다」로 표째 버려져 단끊기 5-16-1 이 한 줄도 안 섰다.
|
|
|
|
⚠ **모양만으로는 「더하는 표」인지 못 가른다** — 뭉기기 13-12-1 은 같은 모양인데 줄마다 단위가
|
|
달라(절취 ㎥ · 면고르기 시간/㎡) 더하면 틀린다. 그래서 **사람이 적은 공종만** 더한다.
|
|
⚠ 원문 합계 줄은 안 읽는다 — 줄 합과 다르면 **까닭을 적어 둔 공종만** 서고, 아니면 막는다.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from decimal import Decimal
|
|
from typing import Any
|
|
|
|
from B09_Estimation.B09_Estimation_ResourceAxis import (
|
|
AxisResult,
|
|
ResourceCatalog,
|
|
ResourceRow,
|
|
UnmatchedRow,
|
|
parse_amount,
|
|
)
|
|
|
|
_TOTAL_LABELS = ("계", "합계", "소계", "총계")
|
|
|
|
#: 공종 → 근거 · 원문 합계 줄과 다를 때의 까닭 · [주]가 더하라는 직종(코드, 보통인부 몇 인당 1인).
|
|
PROCESS_SUM: dict[str, dict[str, Any]] = {
|
|
"FP-05-16-01": {
|
|
"basis": (
|
|
"품셈 5-16-1 표 머리 (인/100m당) · [주]④ 「수평잡기 및 단정리, 잡석 및 뿌리정리,"
|
|
" 성토면고르기, 고르기품은 100m당 품」 · [주]③ 절취량 0.15㎥/m 기준"
|
|
),
|
|
"total_mismatch": (
|
|
"원문 합계 줄(2.03·2.09·2.23)은 절취를 ㎥당(2.4÷15=0.16)으로 더한 값이라 줄 합과 다름"
|
|
" — 100m당 줄을 더함(STmate 단끊기 산출과 같음)"
|
|
),
|
|
"per_workers": ("1001", Decimal(20), "[주]⑤ 작업반장 1인/보통인부 20인 가산"),
|
|
},
|
|
}
|
|
|
|
|
|
def _tight(text: Any) -> str:
|
|
return "".join(str(text or "").split())
|
|
|
|
|
|
def match_process_sum_table(
|
|
node: dict[str, Any],
|
|
table: dict[str, Any],
|
|
catalog: ResourceCatalog,
|
|
result: AxisResult,
|
|
basis_quantity: Decimal | None,
|
|
unit: str,
|
|
) -> bool:
|
|
"""적어 둔 공종의 공정 합산 표를 읽는다. 그 공종이 아니면 `False`."""
|
|
from B09_Estimation.B09_Estimation_ResourceAxis_Transposed import transposed_columns
|
|
|
|
code = str(node.get("work_item_code") or "")
|
|
spec = PROCESS_SUM.get(code)
|
|
if spec is None:
|
|
return False
|
|
table_id = str(table.get("pum_table_id", ""))
|
|
rows = [[str(c).strip() for c in row] for row in (table.get("raw_row") or [])]
|
|
columns = transposed_columns(table, catalog)
|
|
|
|
def block(cell: str, why: str) -> bool:
|
|
result.unmatched.append(UnmatchedRow(code, table_id, cell, why))
|
|
result.partial_items[code] = why
|
|
return True
|
|
|
|
labels = [cell for cell in (rows[0] if rows else []) if cell]
|
|
if len(columns) != 1 or len(labels) < 2 or basis_quantity in (None, 0):
|
|
return block(code, "공정 합산 표 모양이 아닙니다(자원 한 칸 · 갈래 줄 · 밑수)")
|
|
sums = [Decimal(0)] * len(labels)
|
|
totals: list[Decimal] | None = None
|
|
for cells in rows[1:]:
|
|
numbers = [value for value in (parse_amount(c) for c in cells[1:]) if value is not None]
|
|
if not cells or not cells[0] or not numbers:
|
|
continue
|
|
if len(numbers) != len(labels):
|
|
return block(cells[0], f"값 {len(numbers)} 개가 갈래 {len(labels)} 개와 안 맞습니다")
|
|
if _tight(cells[0]) in _TOTAL_LABELS:
|
|
totals = numbers
|
|
continue
|
|
sums = [total + value for total, value in zip(sums, numbers)]
|
|
if totals is not None and totals != sums and not spec.get("total_mismatch"):
|
|
return block("합계", f"원문 합계 {totals} 가 줄 합 {sums} 와 다릅니다")
|
|
|
|
entry = columns[0][1]
|
|
members = [(entry, Decimal(1))]
|
|
if spec.get("per_workers"):
|
|
chief_code, workers, _note = spec["per_workers"]
|
|
chief = next((e for e in catalog.entries if e.code == chief_code), None)
|
|
if chief is None:
|
|
return block(chief_code, "[주]가 더하라는 직종이 카탈로그에 없습니다")
|
|
members.append((chief, Decimal(1) / workers))
|
|
for label, total in zip(labels, sums):
|
|
for member, ratio in members:
|
|
result.rows.append(
|
|
ResourceRow(
|
|
work_item_code=code,
|
|
pum_table_id=table_id,
|
|
pum_form=str(table.get("pum_form", "")),
|
|
resource_kind=member.kind,
|
|
resource_code=member.code,
|
|
resource_name=member.name,
|
|
resource_spec=member.spec,
|
|
amount=total * ratio / basis_quantity,
|
|
amount_unit=unit,
|
|
raw_row_index=0,
|
|
variant=label,
|
|
)
|
|
)
|
|
return True
|