feat(B08): 사토 운반 거리 — 사토장 측점 거리가 오면 그 값이 이김
- 2026-09-09 사용자 확정: 사토장은 **이미 있는 측점 위에만** 놓임 ⇒ 거리는 「발생점 → 사토장 측점」 누가거리로 저절로 나옴. 가정할 것이 없어짐 - 잔량이 `spoil_haul_distance_m` 을 들고 오면 갈래별 **가중평균**을 내어 그 거리로 줄이 섬 (실무 내역이 운반수단 × 지반별 평균 하나를 올리는 서식) - 없는 갈래만 산출 조건의 대체 거리로 서고, 거리 출처를 값 옆에 적음 - 사유·화면 도움말을 「거리가 없음」 → 「사토장을 아직 안 놓았음」으로 고침 (사토장이 서면 낡을 문구였음) - tmp/tests 2건 추가 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -37,8 +37,16 @@ SPOIL_SCOPE_NOTE = (
|
||||
" 실무 집계표에 행이 없어 안 셈(교본 6장 3절은 요구 — 뒤집으면 그때 셈)"
|
||||
)
|
||||
SPOIL_EQUIPMENT = "dump_truck"
|
||||
#: 거리가 어디서 왔나 — 값 옆에 적는다(사토장에서 온 것과 대체 입력을 가른다).
|
||||
DISTANCE_FROM_SITE = "사토장 측점까지 누가거리(갈래별 가중평균)"
|
||||
DISTANCE_FROM_SETTING = "산출 조건의 대체 거리 — 사토장을 놓으면 그 값이 이김"
|
||||
|
||||
#: ⚠ **사토장을 놓으면 거리가 저절로 나온다**(2026-09-09 사용자 확정 — 사토장은 이미 있는
|
||||
#: 측점 위에만 놓이므로 「발생점 → 사토장 측점」 누가거리). 그래서 사유도 「거리가 없다」가
|
||||
#: 아니라 **「사토장을 아직 안 놓았다」**로 말한다. 설계 입력 칸은 **안 놓았을 때의 대체값**이다.
|
||||
DISTANCE_MISSING = (
|
||||
"사토장까지 운반거리가 저장에 없어 값이 서지 않음 — 품셈이 정하는 값이 아니라 설계 입력임"
|
||||
"사토장을 아직 안 놓았고 대체 거리도 없어 값이 서지 않음 — 사토장을 노선에 놓으면"
|
||||
" 그 측점까지 거리가 계산되고, 그 전에는 산출 조건의 「사토장까지 거리」가 대신 씀"
|
||||
"(임의 거리를 넣으면 그대로 금액이 됨)"
|
||||
)
|
||||
GROUND_UNKNOWN = (
|
||||
@@ -69,12 +77,29 @@ def spoil_haul_rows(
|
||||
for key, value in (spoil.get("by_ground_m3") or {}).items()
|
||||
if key in GROUND_KEYS and float(value or 0.0) > 0
|
||||
}
|
||||
# 갈래별 거리 — 사토장이 놓였으면 그 값이 이긴다(정확한 값이 이김).
|
||||
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, blocked, reason, note, ground=label, extra="")
|
||||
_spoil_row(
|
||||
code,
|
||||
amount,
|
||||
distance if leg is None else leg,
|
||||
leg_blocked,
|
||||
reason if leg_blocked else "",
|
||||
note,
|
||||
ground=label,
|
||||
extra=DISTANCE_FROM_SETTING if leg is None else DISTANCE_FROM_SITE,
|
||||
)
|
||||
)
|
||||
if unknown > 0:
|
||||
rows.append(
|
||||
|
||||
@@ -176,9 +176,15 @@ def _spoil_of(plan: dict[str, Any] | None, settings: dict[str, Any]) -> dict[str
|
||||
grounds: dict[str, float] = {}
|
||||
unknown = 0.0
|
||||
natural_basis = True
|
||||
# 잔량마다 **사토장까지 거리**가 실려 올 수 있다(2026-09-09 사용자 확정 — 사토장은 이미
|
||||
# 있는 측점 위에만 놓이므로 「발생점 → 사토장 측점」 누가거리로 그냥 나온다).
|
||||
# 갈래별 **가중평균**을 낸다 — 실무 내역이 (운반수단 × 지반)별 평균 하나를 올린다.
|
||||
work: dict[str, float] = {}
|
||||
metered: dict[str, float] = {}
|
||||
for residual in source.get("residuals") or []:
|
||||
if str(residual.get("kind") or "") != "spoil":
|
||||
continue
|
||||
leg_distance = residual.get("spoil_haul_distance_m")
|
||||
# 되돌린 값이 오면 그것이 이긴다 — 우리가 다시 환산하지 않는다(계수가 저쪽에 있다).
|
||||
returned = residual.get("natural_m3_by_ground")
|
||||
source_map = returned if isinstance(returned, dict) else residual
|
||||
@@ -188,6 +194,9 @@ def _spoil_of(plan: dict[str, Any] | None, settings: dict[str, Any]) -> dict[str
|
||||
value = float(source_map.get(key) or source_map.get(key[:2]) or 0.0)
|
||||
if value > 0:
|
||||
grounds[key] = grounds.get(key, 0.0) + value
|
||||
if isinstance(leg_distance, (int, float)) and float(leg_distance) > 0:
|
||||
work[key] = work.get(key, 0.0) + value * float(leg_distance)
|
||||
metered[key] = metered.get(key, 0.0) + value
|
||||
unknown += float(residual.get("ground_unknown_m3") or 0.0)
|
||||
note_parts = [f"사토 {total:,.2f}㎥"]
|
||||
if natural > 0:
|
||||
@@ -210,6 +219,10 @@ def _spoil_of(plan: dict[str, Any] | None, settings: dict[str, Any]) -> dict[str
|
||||
"note": " · ".join(note_parts),
|
||||
"by_ground_m3": {key: round(value, 3) for key, value in grounds.items()},
|
||||
"ground_unknown_m3": round(unknown, 3),
|
||||
# 갈래별 가중평균 거리 — 사토장이 놓였을 때만 찬다. 비면 설정 거리로 떨어진다.
|
||||
"distance_by_ground_m": {
|
||||
key: round(work[key] / metered[key], 2) for key in work if metered.get(key)
|
||||
},
|
||||
# 어느 상태의 물량인가 — 받는 쪽이 단가와 맞는지 스스로 볼 수 있게 값으로 적는다.
|
||||
"volume_basis": "natural" if natural_basis else "compacted",
|
||||
}
|
||||
|
||||
@@ -663,8 +663,8 @@ export const ui_locales_b2 = {
|
||||
],
|
||||
B08_Quantity_Side_SpoilDistance_Label: ["사토장까지 거리(m)", "Distance to spoil site (m)"],
|
||||
B08_Quantity_Side_SpoilDistance_Hint: [
|
||||
"비어 있으면 사토 운반 줄이 막힙니다 — 품셈이 정하는 값이 아니라 현장값입니다",
|
||||
"Blank keeps the spoil haul row blocked — this is a site value, not from the standard",
|
||||
"사토장을 노선에 놓으면 그 측점까지 거리가 계산되어 이 값은 안 씁니다 — 안 놓았을 때의 대체값입니다",
|
||||
"Used only until a spoil site is placed on the route — then the distance is computed from that station",
|
||||
],
|
||||
B08_Quantity_Side_Water_Label: ["구조물터파기 용수", "Structure trench water"],
|
||||
B08_Quantity_Water_Dry: ["육상(용수 없음)", "Dry"],
|
||||
|
||||
Reference in New Issue
Block a user