- 「사토장 정지」는 실무가 사토 운반 단가 안 조각(1/3)으로 넣으므로 수량 줄을 안 세움 (세우면 같은 품을 두 번 셈) - 사토장 사면 보호공은 실무 내역·사방공 집계표에 행이 없어 안 셈. 교본 6장 3절은 요구하나 프로젝트 규칙상 실무가 안 세면 안 세는 쪽이 기본 — 뒤집히면 이 문장만 지움 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
140 lines
6.2 KiB
Python
140 lines
6.2 KiB
Python
"""사토 — 실어 내는 줄 (2026-09-08).
|
|
|
|
⚠⚠ **유토곡선이 사토를 내는데 아무도 실어 내지 않고 있었다.** 운반 줄은 `blocks[].bands`
|
|
와 `transfers` 에서만 만들어지는데, 사토는 `residuals(kind="spoil")` 로 남아 **어느 쪽에도
|
|
없다.** 그래서 구조물 잔토를 사토에 얹어도(126.63㎥) **덤프 물량이 하나도 안 늘었다** —
|
|
채집석 공제도 사토를 줄이는 값이라 **끝까지 금액에 안 나타났다**.
|
|
|
|
실무에는 서는 줄이다 — 울진 대흥 1공구 토적집계 `D32 사토 1,281㎥`.
|
|
|
|
⚠ **거리는 품셈이 정하지 않는다.** 사토장까지 거리는 설계 입력(`spoil_site_distance_m`)이고,
|
|
안 정했으면 **막고 사유를 낸다** — 임의 거리를 넣으면 그대로 금액이 된다.
|
|
⚠ **지반 갈래는 유토곡선이 준 것만 쓴다.** 잔량이 갈래별 물량(`ea/rr/br`)을 들고 오면
|
|
**갈래마다 한 줄**로 세운다 — 덤프 단가가 토사·암으로 갈리기 때문이다. 갈래를 못 붙인
|
|
몫(`ground_unknown_m3`)은 **따로 한 줄**로 세우고 막는다. 토사로 눅이면 임의 단가가 된다.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from typing import Any
|
|
|
|
from B08_Quantity.B08_Quantity_Engine_Handoff_Mapping import (
|
|
BLOCKED_INPUT_MISSING,
|
|
ORIGIN_HAUL,
|
|
WorkItemMapping,
|
|
)
|
|
|
|
SPOIL_NAME = "사토 운반"
|
|
#: ⚠ **사토장 사면 물량은 안 센다**(2026-09-09 세 창 확인). 교본 6장 3절은 「완료 구간 비탈면을
|
|
#: 다듬고 보호공」을 요구하나 **실무 내역·사방공 집계표에 사토장 행이 아예 없다**(비탈 다듬기·
|
|
#: 면고르기 둘 다 0). 프로젝트 규칙이 「기본값 = 현행 법령·행정규칙, 교본은 과거 참조」이고
|
|
#: 실무가 안 세므로 안 세는 쪽이 기본이다 — **뒤집히면 이 문장만 지우면 된다.**
|
|
#: ⚠ **「사토장 정지」도 별도 줄이 아니다** — 실무 단가산출근거가 「사토장정지(굴삭기 0.7㎥)
|
|
#: 1/3 적용」으로 **사토 운반 단가 안 조각**으로 넣는다(랩탑 보조 실물 확인). 수량 줄을
|
|
#: 따로 세우면 같은 품을 두 번 센다.
|
|
SPOIL_SCOPE_NOTE = (
|
|
"사토장 정지는 이 단가 안 조각(실무 「사토장정지 1/3 적용」) · 사토장 사면 보호공은"
|
|
" 실무 집계표에 행이 없어 안 셈(교본 6장 3절은 요구 — 뒤집으면 그때 셈)"
|
|
)
|
|
SPOIL_EQUIPMENT = "dump_truck"
|
|
DISTANCE_MISSING = (
|
|
"사토장까지 운반거리가 저장에 없어 값이 서지 않음 — 품셈이 정하는 값이 아니라 설계 입력임"
|
|
"(임의 거리를 넣으면 그대로 금액이 됨)"
|
|
)
|
|
GROUND_UNKNOWN = (
|
|
"지반 갈래를 못 붙인 몫 — 구조물 잔토 가운데 걸친 측점의 지반이 섞여 못 가른 것."
|
|
" ⚠ 토사로 눅이면 덤프 단가가 임의로 정해짐"
|
|
)
|
|
#: 잔량이 들고 오는 갈래 키 ↔ 우리 갈래 이름(흙깎기·운반이 쓰는 그 낱말).
|
|
GROUND_KEYS = {"ea_m3": "토사", "rr_m3": "리핑암", "br_m3": "발파암"}
|
|
|
|
|
|
def spoil_haul_rows(
|
|
haul_table: dict[str, Any] | None, mapping: WorkItemMapping
|
|
) -> list[dict[str, Any]]:
|
|
"""사토를 실어 내는 줄 하나. 사토가 없으면 줄도 없다."""
|
|
spoil = (haul_table or {}).get("spoil") or {}
|
|
volume = float(spoil.get("volume_m3") or 0.0)
|
|
if volume <= 0:
|
|
return []
|
|
distance = spoil.get("distance_m")
|
|
entry = mapping.for_haul(SPOIL_EQUIPMENT) or {}
|
|
code = entry.get("work_item_code")
|
|
blocked = distance is None or float(distance) <= 0
|
|
reason = DISTANCE_MISSING if blocked else ""
|
|
note = spoil.get("note") or ""
|
|
# 갈래별로 나눠 세운다 — 덤프 단가가 토사·암으로 갈린다. 갈래가 안 오면 종전처럼 한 줄.
|
|
by_ground = {
|
|
GROUND_KEYS[key]: float(value)
|
|
for key, value in (spoil.get("by_ground_m3") or {}).items()
|
|
if key in GROUND_KEYS and float(value or 0.0) > 0
|
|
}
|
|
unknown = float(spoil.get("ground_unknown_m3") or 0.0)
|
|
if by_ground or unknown > 0:
|
|
rows: list[dict[str, Any]] = []
|
|
for label, amount in sorted(by_ground.items()):
|
|
rows.append(
|
|
_spoil_row(code, amount, distance, blocked, reason, note, ground=label, extra="")
|
|
)
|
|
if unknown > 0:
|
|
rows.append(
|
|
_spoil_row(
|
|
code,
|
|
unknown,
|
|
distance,
|
|
True,
|
|
GROUND_UNKNOWN,
|
|
note,
|
|
ground=None,
|
|
extra=GROUND_UNKNOWN,
|
|
)
|
|
)
|
|
return rows
|
|
return [_spoil_row(code, volume, distance, blocked, reason, note, None, GROUND_UNKNOWN)]
|
|
|
|
|
|
def _spoil_row(
|
|
code: str | None,
|
|
volume: float,
|
|
distance: Any,
|
|
blocked: bool,
|
|
reason: str,
|
|
note: str,
|
|
ground: str | None,
|
|
extra: str,
|
|
) -> dict[str, Any]:
|
|
"""사토 운반 줄 하나 — 갈래마다 같은 모양으로 낸다."""
|
|
return {
|
|
"work_item_code": code,
|
|
"name": SPOIL_NAME,
|
|
"spec": " · ".join(
|
|
part for part in (ground or "", "" if blocked else f"{float(distance):g}m") if part
|
|
),
|
|
"unit": "㎥",
|
|
"quantity": round(volume, 3),
|
|
"quantity_gross": None,
|
|
"application_ratio_pct": None,
|
|
"application_ratio_breakdown": None,
|
|
"quantity_breakdown": None,
|
|
"ground_class": ground,
|
|
"haul_distance_m": None if blocked else float(distance),
|
|
"haul_equipment": SPOIL_EQUIPMENT,
|
|
"station_from": None,
|
|
"station_to": None,
|
|
"excavation_method": None,
|
|
"spec_detail": " · ".join(part for part in (note, extra, SPOIL_SCOPE_NOTE) if part),
|
|
"composite_parts": None,
|
|
"structure_kind": None,
|
|
"blocked_kind": BLOCKED_INPUT_MISSING if blocked else None,
|
|
"blocked_reason": reason,
|
|
"variant_axis": None,
|
|
"variant_value": None,
|
|
"secondary_axes": None,
|
|
"spec_class": None,
|
|
"spec_class_basis": "",
|
|
"composite_not_ready": None,
|
|
"in_bill": not blocked and code is not None,
|
|
"in_bill_reason": reason,
|
|
"origin": ORIGIN_HAUL,
|
|
}
|