- 되돌린 값(natural_m3_by_ground)이 오면 그 값으로 서고 상태를 사유에 적음 - 안 오면 다짐 그대로 두고 「내역 수량은 자연상태여야 함」을 사유로 냄 - tmp/tests 2건 추가 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
178 lines
8.4 KiB
Python
178 lines
8.4 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_FROM_SITE = "사토장 측점까지 누가거리(갈래별 가중평균)"
|
|
DISTANCE_FROM_SETTING = "산출 조건의 대체 거리 — 사토장을 놓으면 그 값이 이김"
|
|
|
|
#: ⚠ **사토장을 놓으면 거리가 저절로 나온다**(2026-09-09 사용자 확정 — 사토장은 이미 있는
|
|
#: 측점 위에만 놓이므로 「발생점 → 사토장 측점」 누가거리). 그래서 사유도 「거리가 없다」가
|
|
#: 아니라 **「사토장을 아직 안 놓았다」**로 말한다. 설계 입력 칸은 **안 놓았을 때의 대체값**이다.
|
|
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 ""
|
|
# 갈래별로 나눠 세운다 — 덤프 단가가 토사·암으로 갈린다. 갈래가 안 오면 종전처럼 한 줄.
|
|
# ⚠⚠ **내역 수량은 자연상태다**(`config_system_design` 5-4-3). 사토 잔량은 유토곡선이
|
|
# 쌓은 다짐상태라 되돌린 값(`natural_m3_by_ground`)이 오면 **그 값으로 선다**.
|
|
# 갈래를 못 붙인 몫은 되돌릴 계수가 없어 다짐 그대로 서고, 그 사실이 사유에 남는다.
|
|
returned = spoil.get("natural_m3_by_ground") or {}
|
|
natural_basis = bool(returned)
|
|
source_map = returned if natural_basis else (spoil.get("by_ground_m3") or {})
|
|
by_ground = {
|
|
GROUND_KEYS[key]: float(value)
|
|
for key, value in source_map.items()
|
|
if key in GROUND_KEYS and float(value or 0.0) > 0
|
|
}
|
|
state_note = (
|
|
"자연상태(유토곡선이 ÷C 로 되돌린 값)"
|
|
if natural_basis
|
|
else "⚠ 다짐상태 그대로 — 되돌린 값이 아직 안 옴(내역 수량은 자연상태여야 함)"
|
|
)
|
|
# 갈래별 거리 — 사토장이 놓였으면 그 값이 이긴다(정확한 값이 이김).
|
|
by_distance = {
|
|
GROUND_KEYS[key]: float(value)
|
|
for key, value in (spoil.get("distance_by_ground_m") 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()):
|
|
leg = by_distance.get(label)
|
|
leg_blocked = blocked if leg is None else False
|
|
rows.append(
|
|
_spoil_row(
|
|
code,
|
|
amount,
|
|
distance if leg is None else leg,
|
|
leg_blocked,
|
|
reason if leg_blocked else "",
|
|
note,
|
|
ground=label,
|
|
extra=" · ".join(
|
|
(DISTANCE_FROM_SETTING if leg is None else DISTANCE_FROM_SITE, state_note)
|
|
),
|
|
)
|
|
)
|
|
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,
|
|
}
|