Merge remote-tracking branch 'origin/main_desktop_1' into sub_desktop_1
This commit is contained in:
@@ -139,6 +139,18 @@ SLOPE_GROUPS = frozenset({"성토면다짐", "초류종자살포", "지장목제
|
||||
#: 집계 합계 줄 — 내역 줄이 아니라 검산용이다.
|
||||
SUBTOTAL_GROUPS = frozenset({"보정량계"})
|
||||
|
||||
#: ⚠⚠ **토공집계표에도 운반 줄이 있다 — 그런데 내역 줄은 운반표 쪽이다**(2026-09-09 실측).
|
||||
#: 집계표는 실무 토적집계 모양이라 「무대·도자운반·덤프운반」을 함께 싣는데, 인계본에는
|
||||
#: 운반표(`_haul_rows`, FP-10-11·FP-10-12)가 **같은 물량으로 또 실렸다** — 같은 운반이
|
||||
#: 두 줄이었다(실측: 도자 17.389·61.130, 덤프 37.511·122.417 이 두 축에 각각).
|
||||
#: ⇒ 집계 쪽은 **값은 내되 내역 줄이 아니다**. 빼지 않는 까닭은 검산(무대+도자+덤프 = 총
|
||||
#: 운반토량)이 그 값을 쓰기 때문이다 — 무대를 그렇게 둔 것과 같은 규칙이다.
|
||||
HAUL_SUMMARY_GROUPS = frozenset({"무대(종방향유용토)", "도자운반", "덤프운반"})
|
||||
NOTE_HAUL_IN_SUMMARY = (
|
||||
"집계 값 — 내역 줄은 운반표 쪽(FP-10-11·FP-10-12)이 세움. 여기서 또 세우면 같은 운반이"
|
||||
" 두 줄이 됨(검산용으로만 실림)"
|
||||
)
|
||||
|
||||
|
||||
def _latest_dataset_path(directory: Path | None = None) -> Path | None:
|
||||
folder = directory or DATASET_DIR
|
||||
|
||||
@@ -14,7 +14,9 @@ from B08_Quantity.B08_Quantity_Engine_Handoff_Mapping import (
|
||||
BLOCKED_FORMULA_MISSING,
|
||||
BLOCKED_INPUT_MISSING,
|
||||
BLOCKED_UNIT_DATA_MISSING,
|
||||
HAUL_SUMMARY_GROUPS,
|
||||
METHOD_TO_GROUND,
|
||||
NOTE_HAUL_IN_SUMMARY,
|
||||
NOTE_METHOD_MISSING,
|
||||
ORIGIN_EARTHWORK,
|
||||
ORIGIN_HAUL,
|
||||
@@ -114,7 +116,8 @@ def _earthwork_rows(
|
||||
continue
|
||||
ground = row.get("item") or None
|
||||
origin = ORIGIN_SLOPE if group in SLOPE_GROUPS else ORIGIN_EARTHWORK
|
||||
is_subtotal = group in SUBTOTAL_GROUPS
|
||||
# ⚠ 운반은 **집계에도 오르고 운반표에도 오른다** — 내역 줄은 운반표 쪽 하나뿐이다.
|
||||
is_subtotal = group in SUBTOTAL_GROUPS or group in HAUL_SUMMARY_GROUPS
|
||||
lookup_ground, method_note = _mapping_ground(ground, methods)
|
||||
entry = mapping.for_earthwork(group, lookup_ground) if method_note == "" else None
|
||||
code = (entry or {}).get("work_item_code")
|
||||
@@ -169,9 +172,9 @@ def _earthwork_rows(
|
||||
# 합계 줄과 무대 줄은 값은 내되 내역에 안 선다.
|
||||
"in_bill": bool(row.get("in_bill", True)) and not is_subtotal and mismatch is None,
|
||||
"excavation_method": methods.get(ground) if ground else None,
|
||||
"in_bill_reason": "집계 합계 줄 — 검산용"
|
||||
if is_subtotal
|
||||
else str(row.get("note") or ""),
|
||||
"in_bill_reason": NOTE_HAUL_IN_SUMMARY
|
||||
if group in HAUL_SUMMARY_GROUPS
|
||||
else ("집계 합계 줄 — 검산용" if is_subtotal else str(row.get("note") or "")),
|
||||
"origin": origin,
|
||||
}
|
||||
)
|
||||
@@ -232,9 +235,13 @@ def _haul_rows(
|
||||
"spec_detail": state_note,
|
||||
"composite_parts": None,
|
||||
"structure_kind": None,
|
||||
# 토공·운반 줄에는 갈래 축이 없다 — 그래도 **칸은 둔다**(계약이 한 모양).
|
||||
"variant_axis": None,
|
||||
"variant_value": None,
|
||||
# ⚠⚠ **갈래를 통로로 보낸다**(2026-09-09). 운반 단가가 지반 갈래로 갈리는데
|
||||
# `spec` 문자열만 보내고 있어 받는 쪽이 갈래를 못 골랐다 — 「버림」에서
|
||||
# 275,584원이 사라졌던 그 자리와 같다. 갈래는 `variant_value` 한 통로로만.
|
||||
# ⚠ 이름은 **우리 갈래 그대로**(리핑암) 보낸다 — 일위대가의 「파쇄암」과는
|
||||
# 인계본의 `ground_class_aliases` 가 이어 준다(이름을 갈면 흙깎기가 어긋난다).
|
||||
"variant_axis": "ground_class" if row.get("ground") else None,
|
||||
"variant_value": str(row.get("ground")) if row.get("ground") else None,
|
||||
"secondary_axes": None,
|
||||
"spec_class": None,
|
||||
"spec_class_basis": "",
|
||||
|
||||
@@ -53,6 +53,16 @@ COLLECTED_STONE_COMPONENT = "채집석"
|
||||
#: ⚠ 갈래를 모르는 구조물 몫은 **환산하지 않는다**(계수가 없다) — 따로 낸다.
|
||||
COLLECTED_STONE_BY_GROUND_KEY = "collected_stone_by_ground_m3"
|
||||
COLLECTED_STONE_UNKNOWN_KEY = "collected_stone_ground_unknown_m3"
|
||||
#: ⭐ **축 맞춤의 검산** — 더하는 쪽(구조물 잔토)과 빼는 쪽(채집석 공제)에 **같은 계수**가
|
||||
#: 걸리므로 사토 총량은 안 흔들린다. 실측: 잔토 126.625 ×1.15 = 145.62 담기고 공제
|
||||
#: 64.75 ×1.15 = 74.46 빠져 사토(다짐) 71.16 → ÷1.15 = **61.887**, 축 맞추기 전 61.88 과 같다.
|
||||
#: ⇒ **값을 안 흔들면서 축만 바로잡은 것**이다. 갈래가 갈리거나 모르는 몫이 섞이는
|
||||
#: 자리에서만 값이 달라진다.
|
||||
COLLECTED_STONE_AXIS_CHECK = (
|
||||
"축 맞춤 검산 — 더하는 쪽과 빼는 쪽에 같은 계수가 걸려 사토 총량은 그대로"
|
||||
"(잔토 ×C 담김 · 공제 ×C 빠짐 ⇒ ÷C 하면 같은 값)"
|
||||
)
|
||||
|
||||
COLLECTED_STONE_BASIS_NOTE = (
|
||||
"벽 입적(제자리 부피) — 자연 축으로 보고 받는 쪽이 ×C 로 다짐 축에 맞춰 뺌."
|
||||
" ⚠ 벽 입적과 원바닥 암 부피의 관계를 정한 원문이 없음(캐면 부풀고 쌓으면 공극이 생기나"
|
||||
@@ -128,7 +138,7 @@ def haul_inputs(unit_quantity_table: dict[str, Any] | None) -> dict[str, Any]:
|
||||
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,
|
||||
"collected_stone_basis": f"{COLLECTED_STONE_BASIS_NOTE} · {COLLECTED_STONE_AXIS_CHECK}",
|
||||
STRUCTURE_SPOIL_KEY: round(total, 3) if points or total else None,
|
||||
STRUCTURE_SPOIL_POINTS_KEY: sorted(points, key=lambda row: row["chainage_m"]),
|
||||
# 총량에도 상태를 적는다 — 측점값을 안 쓰는 쪽도 상태를 알아야 한다.
|
||||
|
||||
@@ -45,8 +45,17 @@ NOTE_SECTION_MISSING = (
|
||||
"그 측점의 횡단 자체가 없습니다 — 관은 놓였는데 횡단이 안 만들어진 자리라 "
|
||||
"[저장]으로는 안 풀립니다. 횡단설계에서 그 측점이 서야 합니다"
|
||||
)
|
||||
#: 관 자리에 횡단이 있는지 볼 때의 허용 오차. **아주 좁게** — 옆 측점을 「있다」로 세면
|
||||
#: 거짓 안내가 된다. 길이 찾기(0.5m)보다 좁은 것은 뜻이 다르기 때문이다.
|
||||
# ── 허용 오차 둘 — **묻는 것이 다르다.** 한 자리에 모아 둔다 ────────────────
|
||||
# ⚠ **맞추지 말 것.** 하나로 맞추면 둘 다 나빠진다(2026-09-09 세 창 확인).
|
||||
# · `LENGTH_MATCH_TOLERANCE_M` (0.5m) — 「이 관의 **길이를 어디서 가져오나**」.
|
||||
# 넉넉해도 된다. 값을 못 찾는 것보다 옆 측점 길이를 쓰는 편이 낫다.
|
||||
# · `SECTION_MATCH_TOLERANCE_M` (0.05m) — 「이 측점에 **횡단 설계가 있나**」.
|
||||
# 좁아야 한다. 없는 설계를 「있다」고 말하면 사용자가 [저장]을 눌러 보고 헤맨다.
|
||||
# ⚠ 두 값의 차이는 **증상**이지 병이 아니다. 병은 **관 자리에 측점이 없는 것**이고
|
||||
# (측점을 만드는 자리는 B05 노선 [확정] 한 곳뿐 — 관을 나중에 놓거나 옮기면 안 생긴다,
|
||||
# 계획서 3-14), 그것이 고쳐지면 두 값의 차이가 **아무 관도 안 건드린다**
|
||||
# (자기 자리에 측점이 있는 관 다섯이 이미 그 상태다).
|
||||
LENGTH_MATCH_TOLERANCE_M = 0.5
|
||||
SECTION_MATCH_TOLERANCE_M = 0.05
|
||||
|
||||
NOTE_KIND_DEFAULT = "관종을 안 정해 기본값({kind})으로 섰습니다 — 정하면 공종이 갈립니다"
|
||||
@@ -72,10 +81,16 @@ def _length_by_chainage(designs: list[dict[str, Any]], key: str) -> dict[float,
|
||||
return found
|
||||
|
||||
|
||||
def _nearest(lengths: dict[float, float], chainage: float, tolerance: float = 0.5) -> float | None:
|
||||
def _nearest(
|
||||
lengths: dict[float, float],
|
||||
chainage: float,
|
||||
tolerance: float = LENGTH_MATCH_TOLERANCE_M,
|
||||
) -> float | None:
|
||||
"""관 측점과 단면 측점이 소수점에서 어긋날 수 있어 **가까운 것**을 본다.
|
||||
|
||||
⚠ 좁게 본다(기본 0.5m) — 넓히면 옆 측점의 길이를 물어 와 조용히 틀린다.
|
||||
⚠ 「횡단이 있나」를 재는 `SECTION_MATCH_TOLERANCE_M`(0.05m)와 **다른 물음**이다 —
|
||||
위 주석을 볼 것. 맞추면 둘 다 나빠진다.
|
||||
"""
|
||||
if not lengths:
|
||||
return None
|
||||
|
||||
@@ -98,12 +98,15 @@ def preparation_rows(
|
||||
slope_totals: dict[str, float] | None = None,
|
||||
slope_rows: Iterable[dict[str, Any]] = (),
|
||||
topsoil_thickness_m: float | None = None,
|
||||
topsoil_haul_distance_m: float | None = None,
|
||||
stand_volume_class: str | None = None,
|
||||
) -> list[dict[str, Any]]:
|
||||
"""준비공 줄 — 값이 서는 것과 안 서는 것을 **한 목록에** 낸다."""
|
||||
slope = slope_totals or {}
|
||||
tree_area = float(slope.get("tree_removal_fill", 0.0)) + float(
|
||||
slope.get("tree_removal_cut", 0.0)
|
||||
)
|
||||
topsoil = _topsoil_row(slope, topsoil_thickness_m)
|
||||
return [
|
||||
{
|
||||
"group": "준비공",
|
||||
@@ -114,22 +117,23 @@ def preparation_rows(
|
||||
# ⚠ 값을 여기서 또 내면 같은 나무를 두 번 벤다. 참고로 면적만 보인다.
|
||||
"reference_amount": tree_area,
|
||||
"reason": (
|
||||
# ⚠ **소단면이 이 면적에 들어 있다** — 갈라 내지 않는다(2026-09-09 세 창 확인).
|
||||
# 별표2 타.(1) 이 「노출되는 절·성토면은 **전체면적**을 녹화」라 소단도 녹화
|
||||
# 대상이고, 갈라 내라는 원문이 없으며 실무도 한 덩이로 센다. 가르면 「그 몫으로
|
||||
# 무엇을 세나」를 우리가 정하게 되어 **근거 없는 줄**이 선다. 대신 여기 적어 둔다.
|
||||
"대상 면적에 **소단면이 포함**됨(별표2 타.(1) 「노출되는 면은 전체면적 녹화」 — "
|
||||
"원문이 가르지 않고 실무도 한 덩이로 셈). "
|
||||
"토공집계의 「지장목제거」로 이미 섬 — 여기서 또 세우면 이중계상. "
|
||||
"⚠ 다만 **공종 미확정** — 품셈 4장이 벌목을 목적별로 갈라(수확베기·단목베기·"
|
||||
"위험목 베기) 임도 지장목이 어디에 붙는지 원본이 말하지 않음. 지금은 공종코드 없이 감."
|
||||
),
|
||||
"work_item_code": None,
|
||||
},
|
||||
_topsoil_row(slope, topsoil_thickness_m),
|
||||
{
|
||||
"group": "준비공",
|
||||
"item": "제근·뿌리다듬기",
|
||||
"unit": "개",
|
||||
"amount": None,
|
||||
"status": STATUS_PENDING,
|
||||
"reason": "단위가 「개」(그루 수)인데 입목 본수를 들고 있지 않음 (품셈 9-20~21)",
|
||||
"work_item_code": "FP-09-21",
|
||||
},
|
||||
topsoil,
|
||||
# ⚠ 법이 요구하는 **운반·적치** — 제거 물량이 곧 밑수다(별표2).
|
||||
_topsoil_haul_row(topsoil, topsoil_haul_distance_m),
|
||||
_root_removal_row(slope, stand_volume_class),
|
||||
*_root_steps_rows(slope, stand_volume_class),
|
||||
_batter_frame_row(list(slope_rows)),
|
||||
_level_frame_row(list(slope_rows)),
|
||||
]
|
||||
@@ -169,6 +173,218 @@ def _topsoil_row(slope: dict[str, float], thickness_m: float | None) -> dict[str
|
||||
}
|
||||
|
||||
|
||||
#: 표토 운반·적치 — ⚠ **법이 요구하는데 우리가 제거만 세고 있던 자리**(2026-09-09).
|
||||
#: 시행규칙 별표2 Ⅰ.2.차.(6)·Ⅰ.3.카.(6):
|
||||
#: 「노면·절토대상지에 있는 입목…과 그 뿌리, **표토는 전량 제거한 후** 강우 시 유실되거나
|
||||
#: 경관에 저해되지 않도록 **최고 홍수위보다 높은 장소로 운반하고 쌓아두어야 한다**」
|
||||
#: ⇒ 제거(9-15)만 세면 **운반이 빠진다.** 물량은 제거 물량 그대로이고 **거리가 설계 입력**이다.
|
||||
#: ⚠ 거리를 지어내지 않는다 — 비면 막고 사유를 낸다.
|
||||
TOPSOIL_HAUL_LAW = (
|
||||
"⚠ 법정 의무 — 시행규칙 별표2 「표토는 전량 제거한 후 … 최고 홍수위보다 높은 장소로"
|
||||
" 운반하고 쌓아두어야 한다」. 제거만 세면 운반이 빠짐"
|
||||
)
|
||||
|
||||
|
||||
#: 제근 — ⚠ **본수가 아니라 임목축적으로 갈린다**(2026-09-09 원문 확인).
|
||||
#: 품셈 9-21 [주]① 「소림 : 임목축적이 30㎥/㏊ 이상 60 미만 · 중림 : 60 이상 90 미만 ·
|
||||
#: 밀림 : 90 이상」. ⇒ 물어야 할 값은 **본/ha 밀도가 아니라 축적 등급**이다.
|
||||
#: ⚠⚠ **그런데 원문 표에 밑수 단위 열이 없다** — 9-20(뿌리다듬기·적재)은 「10주당」이라
|
||||
#: 적혀 있는데 9-21 표에는 그 표기가 없다. 등급을 넣어도 **무엇당 값인지 모르면 못 센다.**
|
||||
#: 지어내지 않고 그 사실을 사유로 낸다(원문 확인이 필요한 자리).
|
||||
STAND_VOLUME_CLASSES = ("소림", "중림", "밀림")
|
||||
ROOT_REMOVAL_BASIS_UNKNOWN = (
|
||||
"⚠ 품셈 9-21 표에 **밑수 단위가 없음** — 9-20 은 「10주당」이라 적혀 있으나 9-21 에는"
|
||||
" 그 표기가 없어 「무엇당 값」인지 원문으로 확인해야 함. 등급을 넣어도 그 전에는 못 셈"
|
||||
)
|
||||
ROOT_REMOVAL_CLASS_MISSING = (
|
||||
"임목축적 등급이 아직 입력되지 않았습니다 — 품셈 9-21 [주]① 이 소림(30~60㎥/㏊)·"
|
||||
"중림(60~90)·밀림(90 이상)으로 가름. ⚠ 본수가 아니라 **축적**이고, 산림조사부·영림계획에서"
|
||||
" 옮겨 적는 값이라 프로그램이 만들 수 없음"
|
||||
)
|
||||
|
||||
|
||||
def _root_removal_row(slope: dict[str, float], stand_volume_class: str | None) -> dict[str, Any]:
|
||||
"""제근·뿌리다듬기 — 등급과 밑수 단위가 다 서야 값이 난다.
|
||||
|
||||
⚠ 대상 면적은 지장목제거와 같은 자리(벌개제근 연동)라 이미 서 있다 — 참고로 보인다.
|
||||
"""
|
||||
area = float(slope.get("tree_removal_fill", 0.0)) + float(slope.get("tree_removal_cut", 0.0))
|
||||
picked = str(stand_volume_class or "").strip()
|
||||
reasons = [ROOT_REMOVAL_BASIS_UNKNOWN]
|
||||
if picked in STAND_VOLUME_CLASSES:
|
||||
reasons.append(f"등급은 「{picked}」로 정해져 있음")
|
||||
else:
|
||||
reasons.append(ROOT_REMOVAL_CLASS_MISSING)
|
||||
return {
|
||||
"group": "준비공",
|
||||
"item": "제근·뿌리다듬기",
|
||||
"unit": "개",
|
||||
"amount": None,
|
||||
"status": STATUS_PENDING,
|
||||
"reason": " · ".join(reasons) + f" (대상 면적 {area:,.1f}㎡ — 벌개제근 연동)",
|
||||
"reference_amount": area,
|
||||
"work_item_code": "FP-09-21",
|
||||
}
|
||||
|
||||
|
||||
#: ⚠⚠ **품셈이 네 단계로 두었는데 우리가 한 줄만 세고 있었다**(2026-09-09).
|
||||
#: 9-20 가. 「벌개·제근 → **뿌리다듬기** → **적재** → **운반**」이 시공 과정 표준이고
|
||||
#: [주] 「본 품에서는 제근 후 **뿌리다듬기와 적재항목**에 적용한다」로 두 항목을 준다.
|
||||
#: 법도 같은 말을 한다 — 별표2 「입목…과 그 뿌리, 표토는 전량 제거한 후 … **운반하고
|
||||
#: 쌓아두어야** 한다」. ⇒ 제거만 세면 **적재·운반이 빠진다**(표토에서 이미 겪은 자리).
|
||||
#: ⚠ 적재는 9-20-2 로 코드가 있고, **운반은 그 장에 공종이 없다** — 10장 계열 어디에 붙는지
|
||||
#: 원문이 말하지 않는다. 지어내지 않고 사유로 낸다.
|
||||
ROOT_STEPS_NOTE = (
|
||||
"품셈 9-20 가. 「벌개·제근 → 뿌리다듬기 → 적재 → 운반」 · 별표2 「그 뿌리…를 전량 제거한 후"
|
||||
" 운반하고 쌓아두어야 한다」 — 제거만 세면 뒤 단계가 빠짐"
|
||||
)
|
||||
|
||||
|
||||
def _root_steps_rows(
|
||||
slope: dict[str, float], stand_volume_class: str | None
|
||||
) -> list[dict[str, Any]]:
|
||||
"""뿌리 적재·운반 — **제근과 한 벌로 가는 뒤 단계**. 밑수가 서면 함께 선다."""
|
||||
area = float(slope.get("tree_removal_fill", 0.0)) + float(slope.get("tree_removal_cut", 0.0))
|
||||
return [
|
||||
{
|
||||
"group": "준비공",
|
||||
"item": "뿌리 적재",
|
||||
"unit": "개",
|
||||
"amount": None,
|
||||
"status": STATUS_PENDING,
|
||||
"reason": (
|
||||
f"{ROOT_STEPS_NOTE} · 품셈 9-20-2 「(단위: 10주당)」 — **본수가 서야 셈**"
|
||||
f" (대상 면적 {area:,.1f}㎡ · 제근과 같은 밑수)"
|
||||
),
|
||||
"reference_amount": area,
|
||||
"work_item_code": "FP-09-20-02",
|
||||
},
|
||||
{
|
||||
"group": "준비공",
|
||||
"item": "뿌리 운반",
|
||||
"unit": "개",
|
||||
"amount": None,
|
||||
"status": STATUS_PENDING,
|
||||
"reason": (
|
||||
f"{ROOT_STEPS_NOTE} · ⚠ **9-20 장에 운반 공종이 없음** — 10장 계열 어디에"
|
||||
" 붙는지 원문이 말하지 않아 공종코드를 못 붙임"
|
||||
),
|
||||
"reference_amount": area,
|
||||
"work_item_code": None,
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
def _topsoil_haul_row(topsoil: dict[str, Any], distance_m: float | None) -> dict[str, Any]:
|
||||
"""표토 운반 — 물량은 제거 물량 그대로, 거리는 설계 입력.
|
||||
|
||||
⚠ 제거 줄이 안 서면(두께 미입력) 운반도 안 선다 — 밑수가 그 줄이기 때문이다.
|
||||
"""
|
||||
amount = topsoil.get("amount")
|
||||
distance = None
|
||||
if distance_m is not None:
|
||||
try:
|
||||
distance = float(distance_m)
|
||||
except (TypeError, ValueError):
|
||||
distance = None
|
||||
if amount is None:
|
||||
return {
|
||||
"group": "준비공",
|
||||
"item": "표토 운반·적치",
|
||||
"unit": "㎥",
|
||||
"amount": None,
|
||||
"status": STATUS_PENDING,
|
||||
"reason": f"{TOPSOIL_HAUL_LAW} · 제거 물량이 아직 안 서서 운반도 못 셈(두께 먼저)",
|
||||
"work_item_code": None,
|
||||
}
|
||||
if distance is None or distance <= 0:
|
||||
return {
|
||||
"group": "준비공",
|
||||
"item": "표토 운반·적치",
|
||||
"unit": "㎥",
|
||||
"amount": None,
|
||||
"status": STATUS_PENDING,
|
||||
"reason": (
|
||||
f"{TOPSOIL_HAUL_LAW} · 운반거리가 아직 입력되지 않았습니다 — 「최고 홍수위보다"
|
||||
f" 높은 장소」는 현장에서 정하는 자리라 품셈이 거리를 주지 않습니다"
|
||||
f" (운반할 물량 {float(amount):,.2f}㎥)"
|
||||
),
|
||||
"reference_amount": float(amount),
|
||||
"work_item_code": None,
|
||||
}
|
||||
return {
|
||||
"group": "준비공",
|
||||
"item": "표토 운반·적치",
|
||||
"unit": "㎥",
|
||||
"amount": float(amount),
|
||||
"status": STATUS_READY,
|
||||
"reason": f"{TOPSOIL_HAUL_LAW} · 제거 물량 그대로 · 운반거리 {distance:g}m(설계 입력)",
|
||||
"work_item_code": "FP-10-12",
|
||||
"haul_distance_m": distance,
|
||||
}
|
||||
|
||||
|
||||
#: 규준틀 재료 — **세는 것은 확정이고 수량만 몰랐던 자리**(2026-09-09).
|
||||
#: 품셈 11-2·11-3 [주]④ 「재료량은 **설계수량에 따른다**」 ⇒ 품셈이 값을 안 주는 것이지
|
||||
#: 「안 센다」가 아니다. 그래서 **제안값을 보이고 사용자가 고치는** 모양으로 둔다
|
||||
#: (확정 ⑨·⑩ 과 같은 틀 — 「가는 기본값이고 나 선택처럼 동작할 수 있어야 함」).
|
||||
#: ⚠ **제안값은 실무 관측값이지 법정 기준이 아니다** — 울진 소광 원단위 라이브러리 §8
|
||||
#: 「규준틀 수평 | 개소 | 각재 50×50 0.0044㎥ · 판재 T12 0.0029㎥ · 못 0.03㎏」.
|
||||
#: ⚠ **비탈 규준틀 값은 그 시트에 없다** — 수평 값을 준용하고 그 사실을 사유에 적는다.
|
||||
#: ⚠ **손율은 원문에 있다** — 품셈 11-2 [주]③ 비탈 **50%** · 11-3 [주]③ 수평 **80%**.
|
||||
FRAME_MATERIAL_SUGGESTED = {
|
||||
"각재 50×50": (0.0044, "㎥"),
|
||||
"판재 T12": (0.0029, "㎥"),
|
||||
"못": (0.03, "㎏"),
|
||||
}
|
||||
FRAME_MATERIAL_SOURCE = (
|
||||
"⚠ 실무 관측값(울진 소광 원단위 라이브러리 §8 규준틀 수평) — **법정 기준 아님**."
|
||||
" 품셈 11-2·11-3 [주]④ 는 「재료량은 설계수량에 따른다」로만 둠. 산출 조건에서 고칠 수 있음"
|
||||
)
|
||||
FRAME_LOSS_RATE = {"비탈 규준틀": 50, "수평 규준틀": 80}
|
||||
|
||||
|
||||
def frame_material_rows(
|
||||
frame_rows: list[dict[str, Any]], overrides: dict[str, Any] | None = None
|
||||
) -> list[dict[str, Any]]:
|
||||
"""규준틀 재료 — 개소 × 개소당 수량. **자재 축으로 보낸다.**
|
||||
|
||||
⚠ 개소가 안 서면 재료도 안 선다(밑수가 그 줄이다).
|
||||
⚠ 값은 **제안값**이고 산출 조건에서 덮어쓸 수 있다 — 그 사실이 사유에 적힌다.
|
||||
"""
|
||||
given = overrides or {}
|
||||
rows: list[dict[str, Any]] = []
|
||||
for frame in frame_rows:
|
||||
count = frame.get("amount")
|
||||
if not count:
|
||||
continue
|
||||
loss = FRAME_LOSS_RATE.get(str(frame.get("item")), None)
|
||||
for name, (default, unit) in FRAME_MATERIAL_SUGGESTED.items():
|
||||
raw = given.get(name)
|
||||
try:
|
||||
per_ea = (
|
||||
float(raw) if raw is not None and str(raw).strip() != "" else float(default)
|
||||
)
|
||||
except (TypeError, ValueError):
|
||||
per_ea = float(default)
|
||||
picked = "산출 조건에서 고른 값" if raw not in (None, "") else "제안값(기본)"
|
||||
rows.append(
|
||||
{
|
||||
"name": name,
|
||||
"unit": unit,
|
||||
"amount": float(count) * per_ea,
|
||||
"destination": "material",
|
||||
"source": str(frame.get("item") or "규준틀"),
|
||||
"basis": (
|
||||
f"{frame.get('item')} {float(count):g}개소 × {per_ea:g}{unit}/개소"
|
||||
f" ({picked}) · {FRAME_MATERIAL_SOURCE}"
|
||||
+ (f" · 손율 {loss}%(품셈 [주]③)" if loss else "")
|
||||
),
|
||||
}
|
||||
)
|
||||
return rows
|
||||
|
||||
|
||||
def _batter_frame_row(slope_rows: list[dict[str, Any]]) -> dict[str, Any]:
|
||||
"""비탈 규준틀 한 줄. **개소는 원문 기준으로 서고 재료는 미확보**다."""
|
||||
count, notes = batter_frame_count(slope_rows)
|
||||
@@ -178,7 +394,11 @@ def _batter_frame_row(slope_rows: list[dict[str, Any]]) -> dict[str, Any]:
|
||||
"unit": "개소",
|
||||
"amount": float(count) if count else None,
|
||||
"status": STATUS_READY if count else STATUS_PENDING,
|
||||
"reason": ("; ".join(notes) + " · 재료량은 품셈 11-2 [주]④ 「설계수량에 따른다」라 미확보"),
|
||||
"reason": (
|
||||
"; ".join(notes)
|
||||
+ " · 재료량은 품셈 11-2 [주]④ 「설계수량에 따른다」 —"
|
||||
+ " **제안값(실무 관측)으로 서고 산출 조건에서 고칠 수 있음**"
|
||||
),
|
||||
"work_item_code": "FP-11-02",
|
||||
}
|
||||
|
||||
@@ -194,7 +414,8 @@ def _level_frame_row(slope_rows: list[dict[str, Any]]) -> dict[str, Any]:
|
||||
"status": STATUS_READY if count is not None else STATUS_PENDING,
|
||||
"reason": "; ".join(notes)
|
||||
+ (
|
||||
" · 재료량은 품셈 11-3 [주]④ 「설계수량에 따른다」라 미확보"
|
||||
" · 재료량은 품셈 11-3 [주]④ 「설계수량에 따른다」 —"
|
||||
" **제안값(실무 관측)으로 서고 산출 조건에서 고칠 수 있음**"
|
||||
if count is not None
|
||||
else ""
|
||||
),
|
||||
@@ -351,10 +572,18 @@ def build_table(
|
||||
topsoil_thickness_m: float | None = None,
|
||||
names: dict[str, str] | None = None,
|
||||
ancillary_counts: dict[str, Any] | None = None,
|
||||
topsoil_haul_distance_m: float | None = None,
|
||||
stand_volume_class: str | None = None,
|
||||
) -> dict[str, Any]:
|
||||
"""화면·인계가 그대로 쓰는 모양. **못 서는 줄도 목록에 남는다.**"""
|
||||
rows = (
|
||||
preparation_rows(slope_totals, slope_rows, topsoil_thickness_m)
|
||||
preparation_rows(
|
||||
slope_totals,
|
||||
slope_rows,
|
||||
topsoil_thickness_m,
|
||||
topsoil_haul_distance_m,
|
||||
stand_volume_class,
|
||||
)
|
||||
+ erosion_rows(structures, names)
|
||||
+ ancillary_rows(ancillary_counts)
|
||||
)
|
||||
|
||||
@@ -170,6 +170,13 @@ def stone_coefficients(options: dict[str, Any], back_cm: int) -> tuple[dict[str,
|
||||
|
||||
|
||||
# 돌쌓기 전개식의 상수 — 실무 수식에 박혀 있던 값을 뺀 것.
|
||||
# ⚠⚠ **기슭막이 계열 전용이다. 다른 구조물로 넓히지 말 것**(2026-09-09).
|
||||
# 골막이는 **두께식부터 다르다** — 소광리 숨김탭 「골막이(찰)(치수조서연결)」 C26:
|
||||
# 평균두께 = {(0.45 + 0.1×H) + (0.45 + 0.4×H)} ÷ 2
|
||||
# 우리가 확정 ② 로 버린 옛 식과 **글자까지 같다.** 그 식은 틀린 식이 아니라 **골막이 식**이고,
|
||||
# 그것을 기슭막이에까지 쓰고 있었던 것이 어긋남의 정체였다.
|
||||
# ⚠ 골막이는 그 밖에도 셋이 더 다르다 — 밑수가 **돌쌓기 + 돌붙임**(기슭막이는 돌쌓기만) ·
|
||||
# 단위가 **개소당**(기슭막이는 m당) · 정면적이 **사다리꼴**. **상수만 보고 가져다 쓰지 말 것.**
|
||||
STONE_MASONRY = {
|
||||
# ⚠ **곱하는 값이 아니라 검산 참고값이다** (2026-09-08 ㉘ 에서 고침).
|
||||
# 실무 시트의 「돌쌓기 = 정면적 × 1.04」에서 그 1.04 가 **곧 기울기 몫**이다
|
||||
|
||||
@@ -138,6 +138,10 @@ async def get_earthwork_table(project_id: UUID, route_id: int) -> JSONResponse:
|
||||
{type_id: definition.name for type_id, definition in structure_type_map().items()},
|
||||
# 부대시설 개소 — 산식으로 만들지 않고 **설계자가 넣은 값**만 쓴다(확정 ⑬).
|
||||
settings.get("ancillary_counts") or {},
|
||||
# 표토 운반거리 — 별표2 가 요구하는 운반·적치의 밑수(거리는 현장값).
|
||||
settings.get("topsoil_haul_distance_m"),
|
||||
# 임목축적 등급 — 품셈 9-21 제근이 소·중·밀로 갈리는 축(본수가 아니다).
|
||||
settings.get("stand_volume_class"),
|
||||
)
|
||||
method, method_is_default = concrete_placing_method(settings)
|
||||
# ⚠ 금액에 바로 걸리는 값이라 「기본값으로 돌고 있음」을 응답에 실어 화면이 띄우게 한다.
|
||||
@@ -316,6 +320,12 @@ class QuantitySettingsBody(BaseModel):
|
||||
rubble_base_thickness_m: float | None = None
|
||||
# 구조물터파기 용수 유무 — "육상"·"용수". ⚠ 기본 육상은 **통상값**이지 사용자 확정이 아니다.
|
||||
structure_trench_water: str | None = None
|
||||
# 표토 운반거리(m) — 별표2 가 요구하는 운반·적치의 밑수. 비면 그 줄이 막힌다.
|
||||
topsoil_haul_distance_m: float | None = None
|
||||
# 임목축적 등급 — "소림"·"중림"·"밀림"(품셈 9-21 [주]①). `""` 는 「안 정함」이다.
|
||||
stand_volume_class: str | None = None
|
||||
# 규준틀 개소당 재료 — `{자재명: 수량}`. 비우면 제안값(실무 관측)이 선다.
|
||||
frame_material: dict[str, Any] | None = None
|
||||
|
||||
|
||||
#: `None` 이 「안 정함」을 뜻하는 칸 — 저장에서 **버리지 않고 그대로 덮어쓴다**.
|
||||
@@ -325,6 +335,7 @@ NULLABLE_SETTING_KEYS = (
|
||||
"bench_cut_depth_m",
|
||||
"spoil_site_distance_m",
|
||||
"rubble_base_thickness_m",
|
||||
"topsoil_haul_distance_m",
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -43,6 +43,7 @@ from common_util.common_util_project_settings import (
|
||||
rock_method,
|
||||
)
|
||||
from B08_Quantity.B08_Quantity_Engine_HaulInputs import haul_inputs
|
||||
from B08_Quantity.B08_Quantity_Engine_Preparation import frame_material_rows
|
||||
from common_util.common_util_storage import resolve_stored_project_path
|
||||
from common_util.common_util_structure_lengths import structure_lengths
|
||||
from config.config_db import run_with_connection
|
||||
@@ -251,6 +252,20 @@ async def get_handoff(project_id: UUID) -> JSONResponse:
|
||||
# 토공·운반 표는 토적표 라우터의 것을 그대로 쓴다 — 여기서 다시 만들지 않는다.
|
||||
earthwork = await _earthwork_tables(project_id)
|
||||
|
||||
# 규준틀 재료 — **개소가 선 뒤에야 설 수 있어** 준비공 표를 받은 다음 자재 축에 얹는다.
|
||||
# ⚠ 값은 **제안값(실무 관측)**이고 산출 조건에서 고칠 수 있다 — 그 사실이 줄 사유에 적힌다.
|
||||
frame_rows = [
|
||||
row
|
||||
for row in ((earthwork.get("preparation") or {}).get("rows") or [])
|
||||
if str(row.get("item") or "").endswith("규준틀")
|
||||
]
|
||||
material_table = build_material_table(
|
||||
unit_table,
|
||||
supply_map=settings.get("material_supply") or {},
|
||||
concrete_placing_method=settings.get("concrete_placing_method"),
|
||||
extra_materials=frame_material_rows(frame_rows, settings.get("frame_material") or {}),
|
||||
)
|
||||
|
||||
handoff = build_handoff(
|
||||
summary_table=earthwork.get("summary"),
|
||||
haul_table=earthwork.get("haul"),
|
||||
|
||||
@@ -75,6 +75,14 @@ export interface QuantitySettings {
|
||||
spoil_site_distance_m?: number | null;
|
||||
/** 구조물터파기 용수 — `"육상"`·`"용수"`. ⚠ 「육상」은 통상값이지 사용자 확정이 아니다. */
|
||||
structure_trench_water?: string | null;
|
||||
/** 표토 운반거리(m) — 별표2 가 요구하는 운반·적치의 밑수. 비면 그 줄이 막힌다. */
|
||||
topsoil_haul_distance_m?: number | null;
|
||||
/** 부대시설 개소 — `{항목키: 개소}`. ⚠ 산식으로 만들지 않는다(확정 13). */
|
||||
ancillary_counts?: Record<string, number | null>;
|
||||
/** 임목축적 등급 — `"소림"`·`"중림"`·`"밀림"`(품셈 9-21 [주]①). 본수가 아니라 축적이다. */
|
||||
stand_volume_class?: string | null;
|
||||
/** 규준틀 개소당 재료 — `{자재명: 수량}`. 비우면 제안값(실무 관측)이 선다. */
|
||||
frame_material?: Record<string, number | null>;
|
||||
}
|
||||
|
||||
export interface EarthworkTable {
|
||||
|
||||
@@ -91,6 +91,11 @@ async function saveQuantitySettings(projectId: string, draft: DraftSettings): Pr
|
||||
rubble_base_thickness_m: draft.rubble_base_thickness_m,
|
||||
spoil_site_distance_m: draft.spoil_site_distance_m,
|
||||
structure_trench_water: draft.structure_trench_water,
|
||||
topsoil_haul_distance_m: draft.topsoil_haul_distance_m,
|
||||
stand_volume_class: draft.stand_volume_class,
|
||||
frame_material: draft.frame_material,
|
||||
// 개소는 **통째로** 보낸다 — 지운 항목까지 그대로 가야 되돌릴 길이 있다.
|
||||
ancillary_counts: draft.ancillary_counts,
|
||||
}),
|
||||
},
|
||||
);
|
||||
@@ -110,6 +115,28 @@ function field(label: string, value: string): HTMLElement {
|
||||
return row;
|
||||
}
|
||||
|
||||
/** 규준틀 재료 칸 — 이름은 **서버 `FRAME_MATERIAL_SUGGESTED` 와 같은 낱말**이라야 한다. */
|
||||
const FRAME_MATERIAL_FIELDS: ReadonlyArray<readonly [string, string, string]> = [
|
||||
["각재 50×50", "B08_Quantity_Frame_Square", "0.0001"],
|
||||
["판재 T12", "B08_Quantity_Frame_Board", "0.0001"],
|
||||
["못", "B08_Quantity_Frame_Nail", "0.01"],
|
||||
];
|
||||
/** 채워 보이는 **제안값**(실무 관측). ⚠ 법정 기준이 아니라 「고치라고 보이는 값」이다. */
|
||||
const FRAME_MATERIAL_SUGGESTED: Record<string, number> = {
|
||||
"각재 50×50": 0.0044,
|
||||
"판재 T12": 0.0029,
|
||||
못: 0.03,
|
||||
};
|
||||
|
||||
/** 부대시설 항목 키 — 서버 `ANCILLARY_ITEMS` 와 **같은 차례·같은 낱말**이라야 한다. */
|
||||
const ANCILLARY_KEYS = [
|
||||
"national_point_sign",
|
||||
"guide_sign",
|
||||
"gate",
|
||||
"site_container",
|
||||
"flood_supplies",
|
||||
] as const;
|
||||
|
||||
/** 칸 밑에 붙는 **근거 한 줄** — 왜 그 값인지 화면에서 보이게 한다(2026-09-09 사용자 지시). */
|
||||
function hintRow(text: string): HTMLElement {
|
||||
const row = document.createElement("p");
|
||||
@@ -249,6 +276,14 @@ interface DraftSettings {
|
||||
spoil_site_distance_m: number | null;
|
||||
// 구조물터파기 용수 — "육상"·"용수". ⚠ 「육상」은 **통상값**이지 사용자 확정이 아니다.
|
||||
structure_trench_water: string;
|
||||
// 표토 운반거리(m) — 별표2 가 요구하는 운반·적치의 밑수. `null` 은 「안 정함」.
|
||||
topsoil_haul_distance_m: number | null;
|
||||
// 부대시설 개소 — `{항목키: 개소}`. ⚠ 산식으로 만들지 않는다(확정 13).
|
||||
ancillary_counts: Record<string, number | null>;
|
||||
// 임목축적 등급 — "소림"·"중림"·"밀림". ⚠ 본수가 아니라 축적이다(품셈 9-21 [주]①).
|
||||
stand_volume_class: string;
|
||||
// 규준틀 개소당 재료 — 비우면 **제안값(실무 관측)**이 선다. 값이 아니라 「고칠 수 있음」이 요점.
|
||||
frame_material: Record<string, number | null>;
|
||||
// 자재별 관급/사급 — 표 안에서 줄마다 고른 값.
|
||||
material_supply: Record<string, SupplyChoice>;
|
||||
dirty: boolean;
|
||||
@@ -486,6 +521,72 @@ function buildQuantitySidePanel(
|
||||
),
|
||||
);
|
||||
panel.append(hintRow(L("B08_Quantity_Side_Water_Hint")));
|
||||
panel.append(
|
||||
optionalNumberField(
|
||||
L("B08_Quantity_Side_TopsoilHaul_Label"),
|
||||
draft.topsoil_haul_distance_m,
|
||||
"10",
|
||||
(value) => {
|
||||
draft.topsoil_haul_distance_m = value;
|
||||
draft.dirty = true;
|
||||
},
|
||||
),
|
||||
);
|
||||
panel.append(hintRow(L("B08_Quantity_Side_TopsoilHaul_Hint")));
|
||||
panel.append(
|
||||
selectField(
|
||||
L("B08_Quantity_Side_StandVolume_Label"),
|
||||
draft.stand_volume_class,
|
||||
[
|
||||
{ value: "", label: L("B08_Quantity_StandVolume_Unset") },
|
||||
{ value: "소림", label: L("B08_Quantity_StandVolume_Low") },
|
||||
{ value: "중림", label: L("B08_Quantity_StandVolume_Mid") },
|
||||
{ value: "밀림", label: L("B08_Quantity_StandVolume_High") },
|
||||
],
|
||||
(value) => {
|
||||
draft.stand_volume_class = value;
|
||||
draft.dirty = true;
|
||||
},
|
||||
),
|
||||
);
|
||||
panel.append(hintRow(L("B08_Quantity_Side_StandVolume_Hint")));
|
||||
|
||||
// ── 부대시설 개소 — ⚠ **산식으로 만들지 않는다**(확정 13). 넣어야 줄이 선다 ──
|
||||
panel.append(field(L("B08_Quantity_Side_Ancillary"), ""));
|
||||
for (const key of ANCILLARY_KEYS) {
|
||||
panel.append(
|
||||
optionalNumberField(
|
||||
L(`B08_Quantity_Ancillary_${key}` as keyof typeof ui_locales),
|
||||
draft.ancillary_counts[key] ?? null,
|
||||
"1",
|
||||
(value) => {
|
||||
draft.ancillary_counts[key] = value;
|
||||
draft.dirty = true;
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
panel.append(hintRow(L("B08_Quantity_Side_Ancillary_Hint")));
|
||||
|
||||
// ── 규준틀 재료 — ⚠ **세는 것은 확정이고 수량만 모르던 자리**(품셈 [주]④ 「설계수량에 따른다」).
|
||||
// 그래서 **제안값을 채워 보이고 고칠 수 있게** 둔다(확정 ⑨·⑩ 과 같은 틀).
|
||||
// ⚠ 값만 박고 근거를 안 보이면 사용자 지시(「대신 페이지에 남길 것」)를 어기는 것이라
|
||||
// 칸 밑에 **관측값임**과 **손율 원문값**을 함께 적는다.
|
||||
panel.append(field(L("B08_Quantity_Side_Frame"), ""));
|
||||
for (const [key, label, step] of FRAME_MATERIAL_FIELDS) {
|
||||
panel.append(
|
||||
optionalNumberField(
|
||||
L(label as keyof typeof ui_locales),
|
||||
draft.frame_material[key] ?? FRAME_MATERIAL_SUGGESTED[key],
|
||||
step,
|
||||
(value) => {
|
||||
draft.frame_material[key] = value;
|
||||
draft.dirty = true;
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
panel.append(hintRow(L("B08_Quantity_Side_Frame_Hint")));
|
||||
|
||||
// ── 콘크리트 타설 방식 — ⚠ **금액에 바로 걸리는 값**이라 잠정임을 조용히 두지 않는다 ──
|
||||
panel.append(field(L("B08_Quantity_Side_Placing"), ""));
|
||||
@@ -742,6 +843,12 @@ export async function renderB08Quantity(root: HTMLElement): Promise<void> {
|
||||
rubble_base_thickness_m: (stored.rubble_base_thickness_m as number | null) ?? null,
|
||||
spoil_site_distance_m: (stored.spoil_site_distance_m as number | null) ?? null,
|
||||
structure_trench_water: (stored.structure_trench_water as string) ?? "",
|
||||
topsoil_haul_distance_m: (stored.topsoil_haul_distance_m as number | null) ?? null,
|
||||
stand_volume_class: (stored.stand_volume_class as string) ?? "",
|
||||
frame_material: { ...((stored.frame_material ?? {}) as Record<string, number | null>) },
|
||||
ancillary_counts: {
|
||||
...((stored.ancillary_counts ?? {}) as Record<string, number | null>),
|
||||
},
|
||||
material_supply: { ...((stored.material_supply ?? {}) as Record<string, SupplyChoice>) },
|
||||
dirty: false,
|
||||
};
|
||||
|
||||
@@ -127,6 +127,18 @@ def default_settings() -> dict[str, Any]:
|
||||
# ⚠ **사용자 확정이 아니라 통상값**이다(확정 3차 ④). 화면·사유에 그 사실을
|
||||
# 적어 두고 사용자가 뒤집을 수 있게 둔다.
|
||||
"structure_trench_water": "육상",
|
||||
# 표토 운반거리(m) — ⚠ 별표2 Ⅰ.2.차.(6) 「표토는 전량 제거한 후 … 최고 홍수위보다
|
||||
# 높은 장소로 **운반하고 쌓아두어야** 한다」. 제거만 세면 법이 요구하는 운반이 빠진다.
|
||||
# 거리는 현장값이라 품셈이 정하지 않는다 — 비면 운반 줄이 막힌 채로 선다.
|
||||
"topsoil_haul_distance_m": None,
|
||||
# 임목축적 등급 — 품셈 9-21 제근이 **소·중·밀** 셋으로 갈리는 축.
|
||||
# [주]① 「소림 30~60㎥/㏊ · 중림 60~90 · 밀림 90 이상」 — 본수가 아니라 **축적**이다.
|
||||
# ⚠ 산림조사부·영림계획에서 옮겨 적는 값이라 프로그램이 만들 수 없다. 비면 막는다.
|
||||
"stand_volume_class": None,
|
||||
# 규준틀 개소당 재료 — `{자재명: 개소당 수량}`. **비우면 제안값(실무 관측)이 선다.**
|
||||
# 품셈 11-2·11-3 [주]④ 가 「재료량은 설계수량에 따른다」라 값을 안 주는 자리이고,
|
||||
# 세는 것 자체는 확정이라 **제안값 + 고칠 수 있게**로 둔다(확정 ⑨·⑩ 과 같은 틀).
|
||||
"frame_material": {},
|
||||
"dataset_versions": {},
|
||||
},
|
||||
"estimation": {
|
||||
|
||||
@@ -674,6 +674,38 @@ export const ui_locales_b2 = {
|
||||
"⚠ 「육상」은 통상값이고 사용자 확정이 아닙니다(확정 3차 ④) — 품셈 9-13 의 18구분이 이 값으로 갈립니다",
|
||||
"⚠ “Dry” is a customary default, not a user decision — it selects one of the 18 sub-items",
|
||||
],
|
||||
B08_Quantity_Side_StandVolume_Label: ["임목축적 등급", "Stand volume class"],
|
||||
B08_Quantity_StandVolume_Unset: ["안 정함(줄이 막힘)", "Not set (row blocked)"],
|
||||
B08_Quantity_StandVolume_Low: ["소림(30~60㎥/㏊)", "Low (30–60 ㎥/ha)"],
|
||||
B08_Quantity_StandVolume_Mid: ["중림(60~90㎥/㏊)", "Mid (60–90 ㎥/ha)"],
|
||||
B08_Quantity_StandVolume_High: ["밀림(90㎥/㏊ 이상)", "High (90+ ㎥/ha)"],
|
||||
B08_Quantity_Side_StandVolume_Hint: [
|
||||
"제근(품셈 9-21)이 이 등급으로 갈립니다 — ⚠ 본수가 아니라 축적이고, 산림조사부·영림계획에서 옮겨 적는 값입니다",
|
||||
"Root removal (9-21) splits by this class — stand volume, not tree count; copied from the forest survey",
|
||||
],
|
||||
B08_Quantity_Side_Frame: ["규준틀 개소당 재료", "Batter Board Materials (per unit)"],
|
||||
B08_Quantity_Side_Frame_Hint: [
|
||||
"⚠ 채워진 값은 실무 관측값(제안값)이고 법정 기준이 아닙니다 — 품셈 11-2·11-3 [주]④ 는 「재료량은 설계수량에 따른다」로만 둡니다. 손율은 원문값(비탈 50% · 수평 80%)",
|
||||
"Pre-filled values are field observations, not a legal standard — the standard only says “per design quantity”",
|
||||
],
|
||||
B08_Quantity_Frame_Square: ["각재 50×50 (㎥/개소)", "Square timber (㎥/ea)"],
|
||||
B08_Quantity_Frame_Board: ["판재 T12 (㎥/개소)", "Board T12 (㎥/ea)"],
|
||||
B08_Quantity_Frame_Nail: ["못 (㎏/개소)", "Nails (㎏/ea)"],
|
||||
B08_Quantity_Side_Ancillary: ["부대시설 개소", "Ancillary Facilities"],
|
||||
B08_Quantity_Side_Ancillary_Hint: [
|
||||
"개소를 넣어야 줄이 섭니다 — 원문이 배치 간격을 정하지 않아 연장÷500 같은 산식을 쓰지 않습니다(확정 13)",
|
||||
"Counts are design input — the source sets no spacing rule, so no formula is used",
|
||||
],
|
||||
B08_Quantity_Ancillary_national_point_sign: ["국가지점번호판(개소)", "National point signs"],
|
||||
B08_Quantity_Ancillary_guide_sign: ["임도 안내판(개소)", "Guide signs"],
|
||||
B08_Quantity_Ancillary_gate: ["차단기(개소)", "Gates"],
|
||||
B08_Quantity_Ancillary_site_container: ["가설창고(개소)", "Site containers"],
|
||||
B08_Quantity_Ancillary_flood_supplies: ["수방대책 자재(식)", "Flood supplies (set)"],
|
||||
B08_Quantity_Side_TopsoilHaul_Label: ["표토 운반거리(m)", "Topsoil haul distance (m)"],
|
||||
B08_Quantity_Side_TopsoilHaul_Hint: [
|
||||
"⚠ 별표2 는 표토를 「전량 제거한 후 최고 홍수위보다 높은 장소로 운반하고 쌓아두어야」로 정합니다 — 거리를 넣으면 운반 줄이 섭니다",
|
||||
"The law requires hauling and stockpiling removed topsoil — enter the distance to raise that row",
|
||||
],
|
||||
B08_Quantity_Side_Placing: ["콘크리트 타설", "Concrete Placing"],
|
||||
B08_Quantity_Side_Placing_Label: ["타설 방식", "Method"],
|
||||
B08_Quantity_Placing_Unset: ["안 정함(기본값 사용)", "Not set (default)"],
|
||||
|
||||
Reference in New Issue
Block a user