diff --git a/B08_Quantity/B08_Quantity_Engine_Handoff_Spoil.py b/B08_Quantity/B08_Quantity_Engine_Handoff_Spoil.py index bf53ff5d..1a837315 100644 --- a/B08_Quantity/B08_Quantity_Engine_Handoff_Spoil.py +++ b/B08_Quantity/B08_Quantity_Engine_Handoff_Spoil.py @@ -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( diff --git a/B08_Quantity/B08_Quantity_Router_Earthwork.py b/B08_Quantity/B08_Quantity_Router_Earthwork.py index a1aff6f9..e78e5e21 100644 --- a/B08_Quantity/B08_Quantity_Router_Earthwork.py +++ b/B08_Quantity/B08_Quantity_Router_Earthwork.py @@ -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", } diff --git a/ui_template/ui_template_locale_b2.ts b/ui_template/ui_template_locale_b2.ts index d7ea2902..0f8e0e8e 100644 --- a/ui_template/ui_template_locale_b2.ts +++ b/ui_template/ui_template_locale_b2.ts @@ -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"],