Merge remote-tracking branch 'origin/main_desktop_1' into main_laptop_1
This commit is contained in:
@@ -191,13 +191,33 @@ def _haul_rows(
|
||||
in_bill = bool(row.get("in_bill", True)) and entry.get("in_bill", True)
|
||||
if code is None and in_bill:
|
||||
unmatched.append(f"운반({equipment})")
|
||||
# ⚠⚠ **내역서 수량은 자연상태다** — 유토곡선은 다짐상태로 쌓고(운반거리를 그 기준으로
|
||||
# 재야 맞는다) 내역에 오르는 수량은 되돌린 값이다(`config_system_design` 5-4-3
|
||||
# 「운반거리 산정 시 모든 수량은 다짐상태로 환산해 계산하고, **내역서에 적용하는
|
||||
# 수량은 자연상태로 한다**」). 되돌린 값이 없으면 갈래를 못 붙인 것이라 **다짐 그대로
|
||||
# 두고 사유를 낸다** — 토사 계수로 눅이면 근거 없이 금액이 움직인다.
|
||||
compacted = float(row.get("volume_m3") or 0.0)
|
||||
natural = row.get("natural_m3")
|
||||
state_note = ""
|
||||
if isinstance(natural, (int, float)) and float(natural) > 0:
|
||||
quantity, factor = float(natural), row.get("conversion_c")
|
||||
state_note = (
|
||||
f"다짐 {compacted:,.2f}㎥ ÷ C {factor} = 자연상태"
|
||||
if factor
|
||||
else f"다짐 {compacted:,.2f}㎥ 을 되돌린 자연상태"
|
||||
)
|
||||
else:
|
||||
quantity = compacted
|
||||
state_note = (
|
||||
"⚠ 다짐상태 그대로 — 갈래를 못 붙여 되돌릴 계수가 없음(내역 수량은 자연상태여야 함)"
|
||||
)
|
||||
rows.append(
|
||||
{
|
||||
"work_item_code": code,
|
||||
"name": f"{equipment} 운반",
|
||||
"spec": str(row.get("ground") or ""),
|
||||
"unit": "㎥",
|
||||
"quantity": float(row.get("volume_m3") or 0.0),
|
||||
"quantity": quantity,
|
||||
# 운반에는 반영률 개념이 없다 — 그래서 `None` 이다(0 이 아니다).
|
||||
"quantity_gross": None,
|
||||
"application_ratio_pct": None,
|
||||
@@ -209,7 +229,7 @@ def _haul_rows(
|
||||
"excavation_method": None,
|
||||
"station_from": None,
|
||||
"station_to": None,
|
||||
"spec_detail": "",
|
||||
"spec_detail": state_note,
|
||||
"composite_parts": None,
|
||||
"structure_kind": None,
|
||||
# 토공·운반 줄에는 갈래 축이 없다 — 그래도 **칸은 둔다**(계약이 한 모양).
|
||||
|
||||
@@ -25,9 +25,28 @@ from B08_Quantity.B08_Quantity_Engine_Handoff_Mapping import (
|
||||
)
|
||||
|
||||
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 = (
|
||||
@@ -53,17 +72,47 @@ def spoil_haul_rows(
|
||||
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 (spoil.get("by_ground_m3") or {}).items()
|
||||
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, 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=" · ".join(
|
||||
(DISTANCE_FROM_SETTING if leg is None else DISTANCE_FROM_SITE, state_note)
|
||||
),
|
||||
)
|
||||
)
|
||||
if unknown > 0:
|
||||
rows.append(
|
||||
@@ -111,7 +160,7 @@ def _spoil_row(
|
||||
"station_from": None,
|
||||
"station_to": None,
|
||||
"excavation_method": None,
|
||||
"spec_detail": " · ".join(part for part in (note, extra) if part),
|
||||
"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,
|
||||
|
||||
@@ -7,13 +7,15 @@
|
||||
실무 내역에는 서는 줄이다 — 울진 대흥 1공구 토적집계 D12~D14:
|
||||
`구조물터파기 토사 1,248 / 암 30 ㎥` · `되메우기 739 ㎥`
|
||||
|
||||
⚠ **품셈 9-13 은 18구분이다**(토질 3 × 육상/용수 × 심도 3). 우리는 **심도만** 안다
|
||||
(터파기 깊이 = 직고 + 기초 깊이). 토질·용수는 저장 제원에 칸이 없으므로 **지어내지 않고**
|
||||
상위 코드로 세운 뒤 사유를 붙인다 — 심도 갈래는 미리 갈라 두어 칸이 생기는 날 바로 붙는다.
|
||||
⚠ **품셈 9-13 은 18구분이다**(토질 3 × 육상/용수 × 심도 3). 세 축이 다 서면 자식 코드로
|
||||
내려간다 — 토질은 측점 설계값(`design.ground_type`), 심도는 구조물 제원(직고 + 기초 깊이),
|
||||
용수는 산출 조건 칸(기본 「육상」은 **통상값**이고 사용자 확정이 아니다). 하나라도 없으면
|
||||
**지어내지 않고** 상위 코드로 세운 뒤 사유를 붙인다.
|
||||
|
||||
⚠ **잔토는 내역 줄로 세우지 않는다** — 사토로 실어 내는 몫이라 유토곡선(B06)이 세야 겹치지
|
||||
않는다. 다만 **지금 그 통로가 없다**(채집석 공제만 있다). 값을 버리지 않고 `in_bill=False`
|
||||
로 넘기며 그 사실을 사유에 적는다 — 빼 버리면 빠진 줄을 아무도 못 찾는다.
|
||||
않는다. **그 통로는 2026-09-09 에 생겼다**(`Engine_HaulInputs` → 유토곡선 사토 가산 →
|
||||
「사토 운반」 줄). 여기서는 값과 사유만 넘기고 `in_bill=False` 로 둔다 — 빼 버리면 빠진 줄을
|
||||
아무도 못 찾는다.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
@@ -77,8 +79,17 @@ def trench_child_code(parent: str | None, ground: str | None, water: str, band:
|
||||
|
||||
|
||||
SPOIL_REASON = (
|
||||
"사토로 실어 내는 몫이라 유토곡선(B06)이 세야 겹치지 않음 — ⚠ 지금 그 통로가 없어"
|
||||
" 어디에도 안 실림. 값을 버리지 않고 사유와 함께 넘김"
|
||||
"사토로 실어 내는 몫이라 유토곡선(B06)이 세야 겹치지 않음 — 값을 버리지 않고 사유와"
|
||||
" 함께 넘김(유토곡선이 사토에 얹어 「사토 운반」 줄로 섬)"
|
||||
)
|
||||
#: ⚠ **값 옆에 두는 사실** — 코드 주석에만 두면 화면에서 안 보인다(오늘 여러 번 나온 자리).
|
||||
#: 되메우기·잔토는 **제자리 기하 부피**로 셈한다. 엄밀히는 되메움에 **쓰이는 흙**이
|
||||
#: 다짐부피 ÷ C 라 조금 더 들고 그만큼 잔토가 줄어야 한다. **실무 정본도 그냥 빼므로**
|
||||
#: (터파기 1.55 − 되메우기 0.30 = 잔토 1.25) 값은 그대로 두고 **사실만 적어 둔다** —
|
||||
#: 「잔토가 조금 많다」가 나중에 올라오면 이 문장이 답이다.
|
||||
STATE_NOTE = (
|
||||
"제자리 기하 부피(자연상태) — ⚠ 되메움에 쓰이는 흙은 다짐부피 ÷ C 라 조금 더 들지만"
|
||||
" 실무 정본도 그냥 빼므로 그대로 둠"
|
||||
)
|
||||
|
||||
|
||||
@@ -274,7 +285,7 @@ def structure_earthwork_rows(
|
||||
work_item_code=code,
|
||||
name=BACKFILL_GROUP,
|
||||
spec="구조물",
|
||||
spec_detail="구조물 전개 합",
|
||||
spec_detail=f"구조물 전개 합 · {STATE_NOTE}",
|
||||
quantity=backfill,
|
||||
in_bill=code is not None,
|
||||
in_bill_reason="" if code else "품셈 공종을 아직 못 이었습니다",
|
||||
@@ -285,7 +296,7 @@ def structure_earthwork_rows(
|
||||
_row(
|
||||
name=SPOIL_GROUP,
|
||||
spec="구조물",
|
||||
spec_detail="구조물 전개 합 (터파기 − 되메우기)",
|
||||
spec_detail=f"구조물 전개 합 (터파기 − 되메우기) · {STATE_NOTE}",
|
||||
quantity=spoil,
|
||||
in_bill=False,
|
||||
in_bill_reason=SPOIL_REASON,
|
||||
|
||||
@@ -30,8 +30,34 @@ from B08_Quantity.B08_Quantity_Engine_UnitQuantity import COLLECTED_STONE_KEY, G
|
||||
#: 유토곡선이 읽는 칸 이름 — 양쪽이 같은 낱말을 써야 인계에서 어긋나지 않는다.
|
||||
STRUCTURE_SPOIL_KEY = "structure_spoil_m3"
|
||||
STRUCTURE_SPOIL_POINTS_KEY = "structure_spoil_points"
|
||||
#: ⚠⚠ **이 값은 자연상태(본바닥)다.** 유토곡선 잔량은 **다짐상태**라 그냥 더하면 상태가
|
||||
#: 섞인다(2026-09-09 랩탑 메인 지적). 받는 쪽이 `× C` 로 맞춰 담을 수 있게 **상태를 값으로
|
||||
#: 적어 보낸다** — 말로만 두면 다음 사람이 못 본다.
|
||||
#: 까닭: 터파기·되메우기가 **제자리 기하 부피**(H × 폭 × 연장)이고 잔토는 그 차이다.
|
||||
#: ⚠ 엄밀히는 되메움에 **쓰이는 흙**은 다짐부피 ÷ C 라 조금 더 든다. 실무 정본 시트도
|
||||
#: 그냥 빼므로(터파기 1.55 − 되메우기 0.30 = 잔토 1.25) 여기서도 그대로 두되,
|
||||
#: **그 사실을 적어 둔다** — 값을 임의로 보정하지 않는다.
|
||||
VOLUME_BASIS_KEY = "volume_basis"
|
||||
VOLUME_BASIS_NATURAL = "natural"
|
||||
|
||||
SPOIL_COMPONENT = "잔토처리"
|
||||
COLLECTED_STONE_COMPONENT = "채집석"
|
||||
|
||||
#: 채집석 공제를 **갈래별로** 낸다 (2026-09-09 세 창 확정 — 축을 맞추기로).
|
||||
#: ⚠ 이 값은 **벽 입적(제자리 완성 부피)**이다 — 자연·다짐 어느 축으로도 환산한 적 없다.
|
||||
#: 굳이 가르면 자연 축 쪽이라 받는 쪽이 **×C 로 다짐 축에 맞춰** 뺀다.
|
||||
#: ⚠⚠ **벽 입적과 원바닥 암 부피의 관계를 정한 원문이 없다** — 캐면 부풀고(L) 벽에 쌓으면
|
||||
#: 공극이 생기는데 그 값이 품셈·교본·지식DB 어디에도 없다. **실무 시트도 그냥 뺀다**
|
||||
#: (울진 「채집석 −274.66」이 사토에서 바로 빠진다). 그 가정은 어느 쪽으로 가도 남으므로
|
||||
#: **가정은 그대로 두고 축만 맞춘다** — 축이 다른 값을 그냥 빼는 것은 우리가 만드는 어긋남이다.
|
||||
#: ⚠ 갈래를 모르는 구조물 몫은 **환산하지 않는다**(계수가 없다) — 따로 낸다.
|
||||
COLLECTED_STONE_BY_GROUND_KEY = "collected_stone_by_ground_m3"
|
||||
COLLECTED_STONE_UNKNOWN_KEY = "collected_stone_ground_unknown_m3"
|
||||
COLLECTED_STONE_BASIS_NOTE = (
|
||||
"벽 입적(제자리 부피) — 자연 축으로 보고 받는 쪽이 ×C 로 다짐 축에 맞춰 뺌."
|
||||
" ⚠ 벽 입적과 원바닥 암 부피의 관계를 정한 원문이 없음(캐면 부풀고 쌓으면 공극이 생기나"
|
||||
" 그 값이 어디에도 없음). 실무 시트는 그냥 뺌 — 우리는 축만 맞춤"
|
||||
)
|
||||
|
||||
|
||||
def _center(structure: dict[str, Any]) -> float | None:
|
||||
@@ -50,17 +76,33 @@ def haul_inputs(unit_quantity_table: dict[str, Any] | None) -> dict[str, Any]:
|
||||
if not unit_quantity_table:
|
||||
return {
|
||||
COLLECTED_STONE_KEY: None,
|
||||
COLLECTED_STONE_BY_GROUND_KEY: {},
|
||||
COLLECTED_STONE_UNKNOWN_KEY: None,
|
||||
STRUCTURE_SPOIL_KEY: None,
|
||||
STRUCTURE_SPOIL_POINTS_KEY: [],
|
||||
VOLUME_BASIS_KEY: VOLUME_BASIS_NATURAL,
|
||||
}
|
||||
total = 0.0
|
||||
points: list[dict[str, Any]] = []
|
||||
stone_by_ground: dict[str, float] = {}
|
||||
stone_unknown = 0.0
|
||||
for structure in unit_quantity_table.get("structures") or []:
|
||||
amount = 0.0
|
||||
stone = 0.0
|
||||
for component in structure.get("components") or []:
|
||||
if str(component.get("name") or "") != SPOIL_COMPONENT:
|
||||
name = str(component.get("name") or "")
|
||||
if name == COLLECTED_STONE_COMPONENT:
|
||||
stone += float(component.get("amount") or 0.0)
|
||||
if name != SPOIL_COMPONENT:
|
||||
continue
|
||||
amount += float(component.get("amount") or 0.0)
|
||||
# 채집석 — 그 구조물의 지반 갈래로 담는다. 갈래를 못 가른 구조물 몫은 따로 둔다.
|
||||
if stone > 0:
|
||||
ground = str(structure.get("ground_type") or "")
|
||||
if ground:
|
||||
stone_by_ground[ground] = stone_by_ground.get(ground, 0.0) + stone
|
||||
else:
|
||||
stone_unknown += stone
|
||||
if amount <= 0:
|
||||
continue
|
||||
total += amount
|
||||
@@ -73,6 +115,7 @@ def haul_inputs(unit_quantity_table: dict[str, Any] | None) -> dict[str, Any]:
|
||||
{
|
||||
"chainage_m": chainage,
|
||||
"spoil_m3": round(amount, 3),
|
||||
VOLUME_BASIS_KEY: VOLUME_BASIS_NATURAL,
|
||||
"ground_type": ground,
|
||||
"ground_label": GROUND_TYPE_LABEL.get(str(ground)) if ground else None,
|
||||
"ground_basis": str(structure.get("ground_type_basis") or ""),
|
||||
@@ -81,6 +124,13 @@ def haul_inputs(unit_quantity_table: dict[str, Any] | None) -> dict[str, Any]:
|
||||
collected = unit_quantity_table.get(COLLECTED_STONE_KEY)
|
||||
return {
|
||||
COLLECTED_STONE_KEY: collected,
|
||||
COLLECTED_STONE_BY_GROUND_KEY: {
|
||||
key: round(value, 3) for key, value in sorted(stone_by_ground.items())
|
||||
},
|
||||
COLLECTED_STONE_UNKNOWN_KEY: round(stone_unknown, 3) if stone_unknown else 0.0,
|
||||
"collected_stone_basis": COLLECTED_STONE_BASIS_NOTE,
|
||||
STRUCTURE_SPOIL_KEY: round(total, 3) if points or total else None,
|
||||
STRUCTURE_SPOIL_POINTS_KEY: sorted(points, key=lambda row: row["chainage_m"]),
|
||||
# 총량에도 상태를 적는다 — 측점값을 안 쓰는 쪽도 상태를 알아야 한다.
|
||||
VOLUME_BASIS_KEY: VOLUME_BASIS_NATURAL,
|
||||
}
|
||||
|
||||
@@ -369,7 +369,9 @@ SLOPE_DIR = Path(__file__).resolve().parents[1] / "resources" / "data_masonry"
|
||||
SLOPE_PREFIX = "masonry_slope_"
|
||||
#: 사용자가 덮어쓰는 칸 — 값이 있으면 **그 값이 이긴다**(확정 ⑨).
|
||||
FACE_SLOPE_KEYS = ("face_slope_ratio",)
|
||||
#: 성토/절토를 아직 저장이 안 갖고 있다 — 어느 칸을 잠정으로 쓰는지 한 곳에 둔다.
|
||||
#: 성토/절토는 **측점 단면유형(`design.section_mode`) + 설치 측**으로 판정한다
|
||||
#: (`common_util_structure_face_role` 한 벌). 못 가르는 자리(`both_cut` + 「자동」)만 남아
|
||||
#: 그때 쓸 칸을 여기 한 곳에 둔다 — **눅여 쓰는 것이 아니라 종전값으로 서고 사유를 적는다.**
|
||||
DEFAULT_SLOPE_FACE = "성토"
|
||||
LEGACY_FACE_SLOPE_RATIO = 0.3
|
||||
|
||||
@@ -751,9 +753,9 @@ def stone_masonry(
|
||||
backfill_share = picked.get("backfill_ratio")
|
||||
body_ratio = 1.0 - float(backfill_share) if backfill_share is not None else 2.0 / 3.0
|
||||
kind_label = str(picked.get("kind") or "")
|
||||
# ⚠ `face_slope_ratio` 는 **레지스트리에 없는 키**다 — 즉 지금은 늘 기본 0.3 으로 돈다.
|
||||
# 상수로 두는 것이 아니라 「칸이 생기면 바로 받는다」는 뜻으로 남겨 둔다.
|
||||
# (키 이름 어긋남으로 저장값이 안 닿던 `back_len_cm` 사고와 구별할 것 — 이쪽은 **칸 자체가 없다**.)
|
||||
# ⚠ `face_slope_ratio` 는 **2026-09-09 에 칸이 생겼다**(돌쌓기 계열 여섯 종류 · 표준도
|
||||
# 제원 폼). 빈 값이 「자동」의 뜻이라 비어 있으면 아래 표준경사표가 돌고, 채우면 그 값이
|
||||
# 이긴다. (키 이름 어긋남으로 저장값이 안 닿던 `back_len_cm` 사고와 구별할 것.)
|
||||
# **기본 0.3 의 근거** — 교본 7-3 돌흙막이 기준: 「돌 찰쌓기 3.0m 이하 **1:0.3** /
|
||||
# 돌 메쌓기 2.0m 이하 **1:0.3** / 큰돌쌓기 **1:0.3 이상**(전도 방지)」
|
||||
# (`지식DB 02_상세설계/구조물/돌쌓기.md §1`, 값은 `data_masonry` 의 `face_slope`).
|
||||
|
||||
@@ -169,6 +169,10 @@ def _spoil_of(plan: dict[str, Any] | None, settings: dict[str, Any]) -> dict[str
|
||||
⚠ `spoil_m3` 는 **공제·가산이 끝난 값**이다(채집석 공제는 빼고 구조물 잔토는 더한 뒤).
|
||||
여기서 또 만지면 두 번 셈이 된다.
|
||||
⚠ 자연방토(`natural_spoil_m3`)는 실어 내지 않는 몫이라 **뺀다**.
|
||||
⚠⚠ **상태가 갈린다.** 유토곡선 잔량은 **다짐상태**이고 품셈 운반(10-11·10-12)의 밑수는
|
||||
**자연상태**다(식이 `f = 1/L` 을 스스로 곱한다). 잔량이 되돌린 값
|
||||
(`natural_m3_by_ground`)을 들고 오면 **그것을 쓰고**, 없으면 다짐값으로 서되 그 사실을
|
||||
근거에 적는다 — 조용히 쓰면 상태가 어긋난 물량이 단가에 물린다(2026-09-09 두 창 확인).
|
||||
"""
|
||||
haul_plan = (plan or {}).get("haul_plan") if isinstance(plan, dict) else None
|
||||
source = haul_plan if isinstance(haul_plan, dict) else (plan or {})
|
||||
@@ -180,13 +184,24 @@ def _spoil_of(plan: dict[str, Any] | None, settings: dict[str, Any]) -> dict[str
|
||||
# 토사로 눅이면 덤프 단가가 임의로 정해진다.
|
||||
grounds: dict[str, float] = {}
|
||||
unknown = 0.0
|
||||
# 잔량마다 **사토장까지 거리**가 실려 올 수 있다(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")
|
||||
# ⚠ 잔량은 **다짐상태**로만 읽는다 — 되돌리는 자리는 아래 한 곳뿐이다.
|
||||
# 두 곳에서 되돌리면 ÷C 가 두 번 걸린다.
|
||||
for key in ("ea_m3", "rr_m3", "br_m3"):
|
||||
value = float(residual.get(key) 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:
|
||||
@@ -198,14 +213,18 @@ def _spoil_of(plan: dict[str, Any] | None, settings: dict[str, Any]) -> dict[str
|
||||
if deducted:
|
||||
note_parts.append(f"채집석 {float(deducted):,.2f}㎥ 빠진 뒤")
|
||||
# ⚠ **상태를 값으로 낸다**(2026-09-09) — 잔량은 유토곡선이 쌓은 **다짐상태**이고,
|
||||
# 내역서에 오르는 수량은 **자연상태**다(config 5-4-3 인용). 여기서 ÷C 한 값을 함께 내
|
||||
# 받는 쪽이 **또 환산하지 않게** 한다. 갈래를 못 붙인 몫은 계수가 없어 **환산하지 않는다** —
|
||||
# 토사 계수로 눅이면 근거 없이 금액이 움직인다.
|
||||
# 내역서에 오르는 수량은 **자연상태**다(`config_system_design` 5-4-3 「운반거리 산정 시
|
||||
# 모든 수량은 다짐상태로 환산해 계산하고, **내역서에 적용하는 수량은 자연상태로 한다**」).
|
||||
# 여기서 ÷C 한 값을 함께 내 받는 쪽이 **또 환산하지 않게** 한다.
|
||||
# ⚠ 갈래를 못 붙인 몫은 계수가 없어 **환산하지 않는다** — 토사 계수로 눅이면 근거 없이
|
||||
# 금액이 움직인다. 그 사실을 사유로 낸다.
|
||||
natural_by_ground = {
|
||||
key: round(value / _COMPACTED_FACTOR[key], 3)
|
||||
for key, value in grounds.items()
|
||||
if key in _COMPACTED_FACTOR
|
||||
}
|
||||
if unknown > 0:
|
||||
note_parts.append(f"⚠ 갈래를 못 붙인 {unknown:,.2f}㎥ 는 상태도 못 되돌림")
|
||||
return {
|
||||
"volume_m3": round(volume, 3),
|
||||
"volume_basis": "compacted",
|
||||
@@ -215,6 +234,10 @@ def _spoil_of(plan: dict[str, Any] | None, settings: dict[str, Any]) -> dict[str
|
||||
"natural_m3_by_ground": natural_by_ground,
|
||||
"natural_volume_basis": "natural",
|
||||
"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)
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -338,6 +338,7 @@ def attach_machine_share(
|
||||
title_code: str,
|
||||
choices: dict[tuple[str, str], Decimal] | None = None,
|
||||
machines: dict[str, str] | None = None,
|
||||
sources: dict[str, str] | None = None,
|
||||
) -> Decimal:
|
||||
"""시공능력 공식(8-1-4)으로 **장비 몫**을 붙인다. 붙인 비율(%)을 돌려준다.
|
||||
|
||||
@@ -382,7 +383,13 @@ def attach_machine_share(
|
||||
title_code,
|
||||
hourly_code,
|
||||
machine_hours_per_unit(factors) * share,
|
||||
note=factors.formula_text,
|
||||
# 계수를 남의 절에서 빌려 왔으면 **그 사실을 줄 비고에 적는다.**
|
||||
note=factors.formula_text
|
||||
+ (
|
||||
f" · {(sources or {}).get(work_item_code, '')}"
|
||||
if (sources or {}).get(work_item_code)
|
||||
else ""
|
||||
),
|
||||
)
|
||||
)
|
||||
cycle_factors[work_item_code] = factors
|
||||
|
||||
@@ -0,0 +1,195 @@
|
||||
"""B09 원가계산 — **「다른 절과 동일」 참조**를 따라가 계수를 잇는다 (2026-09-09).
|
||||
|
||||
품셈은 같은 계수를 되풀이 적지 않고 **다른 절을 가리킨다.**
|
||||
|
||||
9-13-1 육상토사(0~1m) 장비(90%) 유압식백호우 | k 0.9 | f 0.77 | E 0.60 | ㎝ 20(135°)
|
||||
9-13-2 육상토사(1~2m) 장비(90%) 유압식백호우 | **「육상토사(0~1m)와 동일」**
|
||||
9-13-10 용수 암절취(0~1m) 들어내기 … | k 0.55 「**육상과동일**」
|
||||
|
||||
그 자리를 안 따라가면 **장비 몫 90%가 통째로 안 붙고 인력 10%만 선다** — 2026-09-09
|
||||
실측으로 구조물터파기 여덟 갈래가 전부 그 모양이었다(「단가가 일부만 섰습니다 — 붙은 몫 10%」).
|
||||
|
||||
⚠ **값을 옮겨 적지 않는다.** 가리키는 절의 계수를 **그때그때 읽어** 쓴다. 옮겨 적으면
|
||||
품셈이 개정될 때 한쪽만 고쳐진다.
|
||||
|
||||
⚠ **어디서 온 값인지 남긴다.** 화면이 「9-13-1 과 동일(품셈 원문)」을 그대로 보여야
|
||||
나중에 누가 봐도 근거를 되짚을 수 있다(오늘 규칙).
|
||||
|
||||
⚠ **못 따라가는 참조는 따라간 척하지 않는다.**
|
||||
· **자기 자신을 가리키는 것** — 9-13-14 가 「육상 발파암(1~2m)와 동일」이라 적었는데
|
||||
그 절이 곧 육상 발파암(1~2m)이다(원문 오기로 보이나 **고쳐 읽지 않는다**).
|
||||
· **가리키는 절을 못 찾는 것 · 그 절도 계수가 없는 것.**
|
||||
이 셋은 사유를 남기고 **빈 채로 둔다.**
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import re
|
||||
|
||||
from decimal import Decimal
|
||||
from typing import Any
|
||||
|
||||
#: 「…와 동일」 — 앞의 이름이 가리키는 절이다.
|
||||
_NAMED = re.compile(r"^(?P<name>.+?)\s*(?:와|과)\s*동일$")
|
||||
|
||||
#: 「육상과동일」 — 이름이 아니라 **한 낱말만 바꾸라**는 지시다(용수 → 육상).
|
||||
_SWAP_WORDS = (("용수", "육상"),)
|
||||
|
||||
#: 이 이름들만 계수로 본다. 참조가 가리키는 것도 결국 이 넷이다.
|
||||
_FACTOR_HEADS = {
|
||||
"k": "K",
|
||||
"f": "f",
|
||||
"e": "E",
|
||||
"cm": "Cm",
|
||||
"㎝": "Cm",
|
||||
"cm(sec)": "Cm",
|
||||
"㎝(sec)": "Cm",
|
||||
}
|
||||
|
||||
|
||||
def _clean(cell: Any) -> str:
|
||||
return " ".join(str(cell or "").split())
|
||||
|
||||
|
||||
def _normalize_name(text: str) -> str:
|
||||
"""절 이름 비교용 — 공백과 물결표기 차이를 지운다(「0~1m」·「0-1m」)."""
|
||||
return re.sub(r"[\s~~〜–—-]", "", str(text))
|
||||
|
||||
|
||||
def _row_has_machine(cells: list[str]) -> bool:
|
||||
"""그 줄이 **기계 줄**인가 — 계수가 와야 할 자리인지 가른다."""
|
||||
from B09_Estimation.B09_Estimation_MachineProductivity import resolve_machine
|
||||
|
||||
return any(resolve_machine(cell) is not None for cell in cells)
|
||||
|
||||
|
||||
def _find_reference(node: dict[str, Any]) -> tuple[str, str] | None:
|
||||
"""이 절이 가리키는 이름과 그 원문 문구. 참조가 없으면 `None`.
|
||||
|
||||
⚠ **기계 줄에 붙은 참조만 본다.** 한 표 안에 참조가 둘 이상 있고 **가리키는 곳이
|
||||
서로 다르다** — 9-13-11 은 「치즐소모량 … 육상과동일」과 「들어내기 유압식백호우 …
|
||||
용수 암절취(0~1m)와 동일」을 함께 적는다. 아무 줄에서나 주우면 **용수 자리에 육상
|
||||
계수**가 붙어 작업효율이 0.375 대신 0.50 으로 서고 금액이 조용히 틀린다
|
||||
(2026-09-09 실측으로 잡았다).
|
||||
"""
|
||||
own_name = str(node.get("name", ""))
|
||||
for table in node.get("tables", []):
|
||||
for row in table.get("raw_row") or []:
|
||||
cells = [_clean(cell) for cell in row]
|
||||
if not _row_has_machine(cells):
|
||||
continue
|
||||
for cell in cells:
|
||||
text = _clean(cell)
|
||||
if not text or len(text) > 40:
|
||||
continue
|
||||
# ⚠ **낱말 바꾸기를 먼저 본다.** 「육상과동일」은 「육상」이라는 절을
|
||||
# 가리키는 것이 아니라 **제 이름에서 용수를 육상으로 바꾸라**는 뜻이다.
|
||||
# 이름 규칙(「…와 동일」)을 먼저 태우면 「육상」이라는 없는 절을 찾다가
|
||||
# 놓친다(2026-09-09 실측: 네 갈래가 그렇게 빠졌다).
|
||||
for source, target in _SWAP_WORDS:
|
||||
# 문구에 적힌 낱말은 **가리키는 쪽**(육상)이고, 제 이름에 있는 낱말이
|
||||
# **바꿀 쪽**(용수)이다. 둘을 뒤집어 보면 영영 못 찾는다.
|
||||
if text in (f"{target}과동일", f"{target}과 동일") and source in own_name:
|
||||
return own_name.replace(source, target), text
|
||||
matched = _NAMED.match(text)
|
||||
if matched:
|
||||
return matched.group("name").strip(), text
|
||||
return None
|
||||
|
||||
|
||||
def _factor_values(node: dict[str, Any]) -> dict[str, Decimal]:
|
||||
"""그 절이 **스스로 적어 둔** 계수들. 참조는 안 따라간다(한 걸음만 간다)."""
|
||||
from B09_Estimation.B09_Estimation_MachineProductivity import parse_measure
|
||||
|
||||
values: dict[str, Decimal] = {}
|
||||
for table in node.get("tables", []):
|
||||
for row in table.get("raw_row") or []:
|
||||
cells = [_clean(cell) for cell in row]
|
||||
if not cells:
|
||||
continue
|
||||
for index, cell in enumerate(cells):
|
||||
factor = _FACTOR_HEADS.get(cell.lower().replace(" ", ""))
|
||||
if factor is None or factor in values:
|
||||
continue
|
||||
for candidate in cells[index + 1 :]:
|
||||
parsed = parse_measure(candidate)
|
||||
if parsed is not None:
|
||||
values[factor] = parsed
|
||||
break
|
||||
return values
|
||||
|
||||
|
||||
def reference_factor_values(
|
||||
master: dict[str, Any],
|
||||
) -> tuple[dict[tuple[str, str], Decimal], dict[str, str], dict[str, str], dict[str, str]]:
|
||||
"""참조를 따라가 얻은 계수들.
|
||||
|
||||
돌려주는 것 넷 — (공종코드, 계수) → 값 · 공종코드 → 근거 한 줄 · 공종코드 → 못 따라간
|
||||
사유 · 공종코드 → **원문 참조 문구 그대로**(그 줄을 「못 붙은 줄」 목록에서 빼는 데 쓴다).
|
||||
"""
|
||||
nodes = {str(n.get("work_item_code", "")): n for n in master.get("work_items", [])}
|
||||
by_name: dict[str, list[str]] = {}
|
||||
for code, node in nodes.items():
|
||||
by_name.setdefault(_normalize_name(node.get("name", "")), []).append(code)
|
||||
|
||||
values: dict[tuple[str, str], Decimal] = {}
|
||||
provenance: dict[str, str] = {}
|
||||
failures: dict[str, str] = {}
|
||||
raw_texts: dict[str, str] = {}
|
||||
|
||||
def resolve(code: str, seen: tuple[str, ...]) -> tuple[dict[str, Decimal], list[str], str]:
|
||||
"""그 절의 계수를 푼다 — 스스로 적은 것 + 참조를 따라간 것.
|
||||
|
||||
⚠ **참조는 사슬로 이어진다** — 9-13-11(용수 암절취 1~2m)은 「육상과동일」로
|
||||
9-13-8 을 가리키고, 그 절은 다시 「육상 암절취(0~1m)와 동일」로 9-13-7 을
|
||||
가리킨다. 한 걸음만 가면 가운데서 멈춘다(2026-09-09 실측).
|
||||
⚠ **돈 자리는 멈춘다** — 자기 자신이나 이미 지나온 절로 돌아가면 사슬이 도는
|
||||
것이라 따라간 척하지 않는다.
|
||||
"""
|
||||
node = nodes.get(code)
|
||||
if node is None:
|
||||
return {}, [], f"공종 {code} 을 못 찾았습니다"
|
||||
own = _factor_values(node)
|
||||
if len(own) >= 4:
|
||||
return own, [], ""
|
||||
found = _find_reference(node)
|
||||
if found is None:
|
||||
return own, [], ""
|
||||
target_name, raw_text = found
|
||||
matches = [m for m in by_name.get(_normalize_name(target_name), []) if m != code]
|
||||
if not matches:
|
||||
return own, [], f"「{raw_text}」가 가리키는 절을 못 찾았습니다"
|
||||
if len(matches) > 1:
|
||||
return own, [], f"「{raw_text}」가 가리키는 절이 여럿입니다 — 하나로 못 좁혔습니다"
|
||||
target = matches[0]
|
||||
if target in seen:
|
||||
return own, [], f"「{raw_text}」가 이미 지나온 절을 다시 가리킵니다 — 사슬이 돕니다"
|
||||
borrowed, path, why = resolve(target, (*seen, code))
|
||||
if why:
|
||||
return own, [], f"「{raw_text}」를 따라갔으나 {why}"
|
||||
merged = {**borrowed, **own}
|
||||
missing = [key for key in ("K", "f", "E", "Cm") if key not in merged]
|
||||
if missing:
|
||||
return own, [], f"「{raw_text}」를 따라갔으나 계수가 없습니다 — {', '.join(missing)}"
|
||||
step = f"「{raw_text}」 → {nodes[target].get('name', target)}"
|
||||
return merged, [step, *path], ""
|
||||
|
||||
for code, node in nodes.items():
|
||||
if _find_reference(node) is None:
|
||||
continue
|
||||
own = _factor_values(node)
|
||||
if len(own) >= 4:
|
||||
continue # 스스로 다 적어 둔 절 — 참조는 곁말이다
|
||||
merged, path, why = resolve(code, ())
|
||||
if why:
|
||||
failures[code] = why
|
||||
continue
|
||||
for key, value in merged.items():
|
||||
if key not in own:
|
||||
values[(code, key)] = value
|
||||
provenance[code] = "계수 출처: " + " · ".join(path) + " (품셈 원문 표기 그대로)"
|
||||
own_ref = _find_reference(node)
|
||||
if own_ref:
|
||||
raw_texts[code] = own_ref[1]
|
||||
|
||||
return values, provenance, failures, raw_texts
|
||||
@@ -411,6 +411,11 @@ def match_table(
|
||||
if match_packed_rows(node, table, catalog, result, basis_quantity, unit):
|
||||
return
|
||||
|
||||
# ⚠ **묶음 배분율은 다음 줄로 이어진다.** 품셈 표는 묶음 머리를 **병합해** 적는다 —
|
||||
# 「인력(10%) | 할석공 2.0」 다음 줄이 「보통인부 1.0」이라 그 줄엔 딱지가 없다.
|
||||
# 이어 주지 않으면 그 줄만 **100%로 서서** 조용히 열 배가 된다(2026-09-09 실측:
|
||||
# 구조물터파기 보통인부가 0.1 대신 1.0 으로 서 단가가 224,305원/㎥ 이었다).
|
||||
carried_ratio: Decimal | None = None
|
||||
for index, row in enumerate(table.get("raw_row", [])):
|
||||
cells = [str(c) for c in row]
|
||||
if not cells:
|
||||
@@ -435,6 +440,10 @@ def match_table(
|
||||
group_ratio = _group_ratio_of(name_cell)
|
||||
name_cell = cells[1]
|
||||
value_cells = cells[2:]
|
||||
# 새 묶음 머리를 만났다 — 여기서부터 이 배분율이 이어진다(없으면 끊는다).
|
||||
carried_ratio = group_ratio
|
||||
else:
|
||||
group_ratio = carried_ratio
|
||||
|
||||
# 제잡비 비율 줄 — 자원이 아니라 **노무비에 붙는 경비율**이다(품셈 [주]③).
|
||||
if "제잡비" in _normalize(name_cell):
|
||||
|
||||
@@ -98,6 +98,10 @@ class UnitPriceBuild:
|
||||
#: 반드시 말해야 한다.** 안 말하면 조용히 싼 단가가 내역서에 그대로 든다
|
||||
#: (2026-09-09 실측: 일위대가가 선 141 공종 중 **70 공종**이 이 자리였다).
|
||||
unattached: dict[str, list[str]] = field(default_factory=dict)
|
||||
#: 공종코드 → **계수를 어디서 가져왔는지** 한 줄. 「…와 동일」 참조를 따라간 자리다.
|
||||
#: ⚠ 값이 남의 절에서 온 것이면 **화면이 그렇게 말해야** 한다 — 안 그러면 나중에
|
||||
#: 「이 숫자 어디서 왔지」로 되짚을 길이 없다.
|
||||
factor_sources: dict[str, str] = field(default_factory=dict)
|
||||
#: 배분율 표인데 일부 몫만 붙은 공종 — 「단가가 일부만 섬」. 값은 붙은 몫(%).
|
||||
partial_ratio: dict[str, Decimal] = field(default_factory=dict)
|
||||
#: 시공능력 공식으로 장비 몫을 세운 공종 — 산출근거를 화면에 그대로 보인다.
|
||||
@@ -348,9 +352,16 @@ def build_unit_prices(
|
||||
scan_range_factors,
|
||||
)
|
||||
|
||||
from B09_Estimation.B09_Estimation_MachineProductivity_Reference import (
|
||||
reference_factor_values,
|
||||
)
|
||||
|
||||
master = load_work_item_master()
|
||||
if factor_choices is None:
|
||||
factor_choices = chosen_values(scan_range_factors(master))
|
||||
# 「…와 동일」 참조로 이어 온 계수 — **사용자가 고른 값이 있으면 그것이 이긴다.**
|
||||
borrowed, borrow_note, borrow_fail, borrow_text = reference_factor_values(master)
|
||||
factor_choices = {**borrowed, **factor_choices}
|
||||
if machine_picks is None:
|
||||
machine_picks = machine_choices()
|
||||
if axis is None:
|
||||
@@ -359,14 +370,43 @@ def build_unit_prices(
|
||||
names = {w["work_item_code"]: w.get("name", "") for w in master.get("work_items", [])}
|
||||
|
||||
build = UnitPriceBuild()
|
||||
build.factor_sources = dict(borrow_note)
|
||||
for failed_code, why in borrow_fail.items():
|
||||
build.factor_sources.setdefault(failed_code, f"⚠ {why}")
|
||||
build.component_gaps = dict(axis.partial_items)
|
||||
# ⚠ **참조로 이미 푼 줄은 「못 붙은 줄」이 아니다.** 안 걷어 내면 다 풀린 공종이
|
||||
# 계속 반쪽으로 보이고, 그 표시를 믿고 막아 둔 금액이 영영 안 선다.
|
||||
resolved_rows = {code: text for code, text in borrow_text.items() if code in borrow_note}
|
||||
for unmatched_row in axis.unmatched:
|
||||
# ⚠ 지역 이름을 조심할 것 — 바로 위 `names` 는 **공종 이름표**다. 같은 이름을 쓰면
|
||||
# 그 표가 리스트로 덮여 조립이 통째로 터진다(2026-09-09 실측).
|
||||
labels = build.unattached.setdefault(unmatched_row.work_item_code, [])
|
||||
label = " ".join(str(unmatched_row.cell).split())
|
||||
reference_text = resolved_rows.get(unmatched_row.work_item_code)
|
||||
if reference_text and reference_text in label:
|
||||
continue # 그 줄은 참조를 따라가 값을 얻었다
|
||||
if label and label not in labels:
|
||||
labels.append(label)
|
||||
|
||||
# ⚠ **거두는 자리는 「못 붙은 줄」을 다 모은 뒤다.** 앞에서 거두면 목록이 비어 있어
|
||||
# **전부 거둬지고**, 깨기(대형브레이커)가 빠진 암 계열까지 「다 찼다」로 선다
|
||||
# (2026-09-09 실측). 남은 줄이 하나도 없을 때만 거둔다.
|
||||
solved_codes = {
|
||||
code
|
||||
for code in borrow_note
|
||||
if code in axis.partial_items and not build.unattached.get(code)
|
||||
}
|
||||
for code in solved_codes:
|
||||
build.component_gaps.pop(code, None)
|
||||
|
||||
# 참조는 풀렸는데 **다른 줄이 남은** 공종은 막힌 채로 두되 **사유를 고쳐 적는다** —
|
||||
# 「백호우 줄을 못 읽었다」는 이미 푼 이야기라 그대로 두면 사람을 엉뚱한 데로 보낸다.
|
||||
for code in borrow_note:
|
||||
remaining = build.unattached.get(code)
|
||||
if remaining and code in build.component_gaps:
|
||||
build.component_gaps[code] = (
|
||||
f"{', '.join(remaining[:3])} 줄이 아직 안 붙었습니다 (계수 참조는 풀렸습니다)"
|
||||
)
|
||||
missing_basis = load_basis_missing()
|
||||
wages = load_operator_wages()
|
||||
_add_labor_titles(build.book, wages)
|
||||
@@ -490,6 +530,7 @@ def build_unit_prices(
|
||||
title_code,
|
||||
factor_choices,
|
||||
machine_picks,
|
||||
build.factor_sources,
|
||||
)
|
||||
|
||||
# ⚠ **배분율이 있는 표는 「몇 %가 실제로 붙었나」를 세어 둔다.**
|
||||
@@ -497,7 +538,7 @@ def build_unit_prices(
|
||||
# 조용히 서면 내역서가 틀린 줄 모른다(2026-09-08 실측: 측구터파기 39,575.6원/㎥
|
||||
# 이 인력 10 % 몫만이었다). 0 으로 때우는 것과 같은 종류의 사고다.
|
||||
# 값을 못 읽은 자원 줄이 있으면 **일부만 선 단가**다 — 금액을 만들지 않는다.
|
||||
if work_item_code in axis.partial_items:
|
||||
if work_item_code in axis.partial_items and work_item_code not in solved_codes:
|
||||
build.partial_ratio.setdefault(work_item_code, _ZERO)
|
||||
|
||||
# ⚠ 공식은 있는데 **아무것도 안 붙은** 제목은 남기지 않는다 — 「상세 줄이 없어
|
||||
|
||||
@@ -12,17 +12,46 @@
|
||||
"user_override": "저장 제원에 `face_slope_ratio` 가 있으면 **그 값이 이긴다**(사용자 확정 ⑨ 「자동 판정 + 사용자가 바꿀 수 있게」).",
|
||||
"no_write_back": "⚠ 자동으로 고른 값을 **저장에 박지 않는다.** 「정한 적 없음」과 「0.3 으로 정함」이 구별돼야 한다.",
|
||||
"boundary": "⚠ 원문 표기는 `∼1.5 · ∼3 · ∼5 · ∼7 · 7이상` 이다. 「∼」를 **이하**로 읽어 경계값은 **앞 칸**에 넣는다(직고 3.0m → `~3` 칸). 7.0m 은 `~7` 칸이고 그보다 커야 `7이상` 칸이다.",
|
||||
"face_unknown": "⚠ 성토/절토를 **저장이 아직 안 갖고 있다.** 구조물 레지스트리의 설치 측 기본이 「자동(성토 쪽)」이고 사면 계열도 층따기가 성토 전용이라 **성토 열로 잠정 적용**하고, 근거 문구에 그 사실을 적는다 — 값이 들어오면 그 칸만 바꾸면 된다."
|
||||
"face_unknown": "⚠ 성토/절토는 **측점 단면유형(`design.section_mode`)과 설치 측으로 판정**한다(2026-09-08 `common_util_structure_face_role` 한 벌로 이음 — 그 전에는 성토 열로 잠정 적용했다). 판정이 안 되는 자리(`both_cut` + 설치 측 「자동」)만 남고, 그때는 **성토로 눅이지 않고** 종전값(1:0.3)으로 서되 **왜 못 갈랐는지**를 근거에 적는다."
|
||||
},
|
||||
"steps_m": [1.5, 3.0, 5.0, 7.0],
|
||||
"steps_m": [
|
||||
1.5,
|
||||
3.0,
|
||||
5.0,
|
||||
7.0
|
||||
],
|
||||
"table": {
|
||||
"메쌓기": {
|
||||
"성토": [0.3, 0.35, 0.4, 0.45, 0.5],
|
||||
"절토": [0.25, 0.3, 0.35, 0.4, 0.45]
|
||||
"성토": [
|
||||
0.3,
|
||||
0.35,
|
||||
0.4,
|
||||
0.45,
|
||||
0.5
|
||||
],
|
||||
"절토": [
|
||||
0.25,
|
||||
0.3,
|
||||
0.35,
|
||||
0.4,
|
||||
0.45
|
||||
]
|
||||
},
|
||||
"찰쌓기": {
|
||||
"성토": [0.25, 0.3, 0.35, 0.4, 0.45],
|
||||
"절토": [0.2, 0.25, 0.3, 0.35, 0.4]
|
||||
"성토": [
|
||||
0.25,
|
||||
0.3,
|
||||
0.35,
|
||||
0.4,
|
||||
0.45
|
||||
],
|
||||
"절토": [
|
||||
0.2,
|
||||
0.25,
|
||||
0.3,
|
||||
0.35,
|
||||
0.4
|
||||
]
|
||||
}
|
||||
},
|
||||
"not_here": {
|
||||
|
||||
@@ -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