feat(B08): 임목파쇄 켜는 칸 (확정 5차 5번) — 기본 꺼짐 · 근주이식은 칸 없음
- 「기본 안 셈」이되 「현장에 따라 파쇄가 적용될 필요 있음」이라 **임목파쇄만** 켤 수 있게 둠 - ⚠ 안 켠 프로젝트에는 **줄 자체가 없음** — 빈 칸이 「세야 함」으로 읽히지 않게 (부대시설은 법정 의무라 늘 서는 것과 다른 자리) - 켜도 부피는 지어내지 않음 — 넣어야 값이 서고 FP-08-11(이동식 임목 파쇄)이 붙음 - 근주이식(FP-14-02)은 **칸도 안 만듦** — 켤 자리가 없으면 물을 일도 없음 - 설정 `wood_chipping_enabled`·`wood_chipping_volume_m3` + 화면 칸 둘 - tmp/tests/test_b08_wood_chipping.py 신설(5건) Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -246,6 +246,48 @@ ROOT_STEPS_NOTE = (
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
#: ⭐ 2026-09-09 **사용자 확정 5차 5번** — 근주이식·임목파쇄는 **기본 안 셈**.
|
||||||
|
#: ⚠ 다만 「**현장에 따라 파쇄가 적용될 필요 있음**」이라 **임목파쇄만 켤 수 있는 칸**을 둔다.
|
||||||
|
#: **기본은 꺼짐**이고, **근주이식은 칸도 안 만든다**(켤 자리가 없으면 물을 일도 없다).
|
||||||
|
#: ⚠ 켜도 **부피는 지어내지 않는다** — 실무는 부피(영월 78㎥ @56,124)로 세고, 그 부피를
|
||||||
|
#: 우리가 든 곳이 없다. 켜면 줄이 서고 **수량 칸이 비어 사유로 드러난다.**
|
||||||
|
CHIPPING_ITEM = "임목파쇄"
|
||||||
|
CHIPPING_CODE = "FP-08-11" # 이동식 임목 파쇄
|
||||||
|
CHIPPING_OFF_NOTE = (
|
||||||
|
"기본 안 셈(확정 5차 5번) — 현장에 따라 필요하면 산출 조건에서 켤 것."
|
||||||
|
" 근주이식(FP-14-02)은 칸도 두지 않음"
|
||||||
|
)
|
||||||
|
CHIPPING_ON_NOTE = (
|
||||||
|
"켜져 있음(확정 5차 5번) — ⚠ **파쇄할 부피(㎥)를 든 곳이 없어** 물량이 안 섬."
|
||||||
|
" 실무는 부피로 셈(영월 78㎥). 산출 조건에 부피를 넣으면 값이 섬"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def chipping_rows(enabled: Any, volume_m3: Any) -> list[dict[str, Any]]:
|
||||||
|
"""임목파쇄 — **켰을 때만** 줄이 선다. 끄면 줄 자체를 안 낸다.
|
||||||
|
|
||||||
|
⚠ 끈 상태에서 줄을 세우면 **빈 칸이 「세야 함」으로 읽힌다**(부대시설과 다른 자리 —
|
||||||
|
그쪽은 법정 의무라 줄이 늘 서고, 이쪽은 **셀지 말지가 설계 판단**이다).
|
||||||
|
"""
|
||||||
|
if not bool(enabled):
|
||||||
|
return []
|
||||||
|
try:
|
||||||
|
amount = float(volume_m3) if volume_m3 not in (None, "") else None
|
||||||
|
except (TypeError, ValueError):
|
||||||
|
amount = None
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
"group": "준비공",
|
||||||
|
"item": CHIPPING_ITEM,
|
||||||
|
"unit": "㎥",
|
||||||
|
"amount": amount,
|
||||||
|
"status": STATUS_READY if amount and amount > 0 else STATUS_PENDING,
|
||||||
|
"reason": CHIPPING_ON_NOTE if not amount else "산출 조건에서 넣은 부피 (확정 5차 5번)",
|
||||||
|
"work_item_code": CHIPPING_CODE,
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
def _root_steps_rows(
|
def _root_steps_rows(
|
||||||
slope: dict[str, float], stand_volume_class: str | None
|
slope: dict[str, float], stand_volume_class: str | None
|
||||||
) -> list[dict[str, Any]]:
|
) -> list[dict[str, Any]]:
|
||||||
@@ -588,6 +630,8 @@ def build_table(
|
|||||||
ancillary_counts: dict[str, Any] | None = None,
|
ancillary_counts: dict[str, Any] | None = None,
|
||||||
topsoil_haul_distance_m: float | None = None,
|
topsoil_haul_distance_m: float | None = None,
|
||||||
stand_volume_class: str | None = None,
|
stand_volume_class: str | None = None,
|
||||||
|
chipping_enabled: Any = False,
|
||||||
|
chipping_volume_m3: Any = None,
|
||||||
) -> dict[str, Any]:
|
) -> dict[str, Any]:
|
||||||
"""화면·인계가 그대로 쓰는 모양. **못 서는 줄도 목록에 남는다.**"""
|
"""화면·인계가 그대로 쓰는 모양. **못 서는 줄도 목록에 남는다.**"""
|
||||||
rows = (
|
rows = (
|
||||||
@@ -599,6 +643,7 @@ def build_table(
|
|||||||
stand_volume_class,
|
stand_volume_class,
|
||||||
)
|
)
|
||||||
+ erosion_rows(structures, names)
|
+ erosion_rows(structures, names)
|
||||||
|
+ chipping_rows(chipping_enabled, chipping_volume_m3)
|
||||||
+ ancillary_rows(ancillary_counts)
|
+ ancillary_rows(ancillary_counts)
|
||||||
)
|
)
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -142,6 +142,9 @@ async def get_earthwork_table(project_id: UUID, route_id: int) -> JSONResponse:
|
|||||||
settings.get("topsoil_haul_distance_m"),
|
settings.get("topsoil_haul_distance_m"),
|
||||||
# 임목축적 등급 — 품셈 9-21 제근이 소·중·밀로 갈리는 축(본수가 아니다).
|
# 임목축적 등급 — 품셈 9-21 제근이 소·중·밀로 갈리는 축(본수가 아니다).
|
||||||
settings.get("stand_volume_class"),
|
settings.get("stand_volume_class"),
|
||||||
|
# 임목파쇄 — 기본 꺼짐. 켠 프로젝트에서만 줄이 선다(확정 5차 5번).
|
||||||
|
settings.get("wood_chipping_enabled"),
|
||||||
|
settings.get("wood_chipping_volume_m3"),
|
||||||
)
|
)
|
||||||
method, method_is_default = concrete_placing_method(settings)
|
method, method_is_default = concrete_placing_method(settings)
|
||||||
# ⚠ 금액에 바로 걸리는 값이라 「기본값으로 돌고 있음」을 응답에 실어 화면이 띄우게 한다.
|
# ⚠ 금액에 바로 걸리는 값이라 「기본값으로 돌고 있음」을 응답에 실어 화면이 띄우게 한다.
|
||||||
@@ -409,6 +412,9 @@ class QuantitySettingsBody(BaseModel):
|
|||||||
stand_volume_class: str | None = None
|
stand_volume_class: str | None = None
|
||||||
# 규준틀 개소당 재료 — `{자재명: 수량}`. 비우면 제안값(실무 관측)이 선다.
|
# 규준틀 개소당 재료 — `{자재명: 수량}`. 비우면 제안값(실무 관측)이 선다.
|
||||||
frame_material: dict[str, Any] | None = None
|
frame_material: dict[str, Any] | None = None
|
||||||
|
# 임목파쇄 — 기본 꺼짐(확정 5차 5번). 켜면 줄이 서고, 부피를 넣으면 값이 선다.
|
||||||
|
wood_chipping_enabled: bool | None = None
|
||||||
|
wood_chipping_volume_m3: float | None = None
|
||||||
|
|
||||||
|
|
||||||
#: `None` 이 「안 정함」을 뜻하는 칸 — 저장에서 **버리지 않고 그대로 덮어쓴다**.
|
#: `None` 이 「안 정함」을 뜻하는 칸 — 저장에서 **버리지 않고 그대로 덮어쓴다**.
|
||||||
@@ -419,6 +425,7 @@ NULLABLE_SETTING_KEYS = (
|
|||||||
"spoil_site_distance_m",
|
"spoil_site_distance_m",
|
||||||
"rubble_base_thickness_m",
|
"rubble_base_thickness_m",
|
||||||
"topsoil_haul_distance_m",
|
"topsoil_haul_distance_m",
|
||||||
|
"wood_chipping_volume_m3",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -83,6 +83,10 @@ export interface QuantitySettings {
|
|||||||
stand_volume_class?: string | null;
|
stand_volume_class?: string | null;
|
||||||
/** 규준틀 개소당 재료 — `{자재명: 수량}`. 비우면 제안값(실무 관측)이 선다. */
|
/** 규준틀 개소당 재료 — `{자재명: 수량}`. 비우면 제안값(실무 관측)이 선다. */
|
||||||
frame_material?: Record<string, number | null>;
|
frame_material?: Record<string, number | null>;
|
||||||
|
/** 임목파쇄 — 기본 꺼짐(확정 5차 5번). 켜야 줄이 선다. */
|
||||||
|
wood_chipping_enabled?: boolean | null;
|
||||||
|
/** 파쇄 부피(㎥) — 켜도 이 값이 없으면 줄만 서고 사유가 남는다. */
|
||||||
|
wood_chipping_volume_m3?: number | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface EarthworkTable {
|
export interface EarthworkTable {
|
||||||
|
|||||||
@@ -94,6 +94,8 @@ async function saveQuantitySettings(projectId: string, draft: DraftSettings): Pr
|
|||||||
topsoil_haul_distance_m: draft.topsoil_haul_distance_m,
|
topsoil_haul_distance_m: draft.topsoil_haul_distance_m,
|
||||||
stand_volume_class: draft.stand_volume_class,
|
stand_volume_class: draft.stand_volume_class,
|
||||||
frame_material: draft.frame_material,
|
frame_material: draft.frame_material,
|
||||||
|
wood_chipping_enabled: draft.wood_chipping_enabled,
|
||||||
|
wood_chipping_volume_m3: draft.wood_chipping_volume_m3,
|
||||||
// 개소는 **통째로** 보낸다 — 지운 항목까지 그대로 가야 되돌릴 길이 있다.
|
// 개소는 **통째로** 보낸다 — 지운 항목까지 그대로 가야 되돌릴 길이 있다.
|
||||||
ancillary_counts: draft.ancillary_counts,
|
ancillary_counts: draft.ancillary_counts,
|
||||||
}),
|
}),
|
||||||
@@ -284,6 +286,9 @@ interface DraftSettings {
|
|||||||
stand_volume_class: string;
|
stand_volume_class: string;
|
||||||
// 규준틀 개소당 재료 — 비우면 **제안값(실무 관측)**이 선다. 값이 아니라 「고칠 수 있음」이 요점.
|
// 규준틀 개소당 재료 — 비우면 **제안값(실무 관측)**이 선다. 값이 아니라 「고칠 수 있음」이 요점.
|
||||||
frame_material: Record<string, number | null>;
|
frame_material: Record<string, number | null>;
|
||||||
|
// 임목파쇄 — **기본 꺼짐**(확정 5차 5번). 켜야 줄이 선다. 근주이식은 칸 자체가 없다.
|
||||||
|
wood_chipping_enabled: boolean;
|
||||||
|
wood_chipping_volume_m3: number | null;
|
||||||
// 자재별 관급/사급 — 표 안에서 줄마다 고른 값.
|
// 자재별 관급/사급 — 표 안에서 줄마다 고른 값.
|
||||||
material_supply: Record<string, SupplyChoice>;
|
material_supply: Record<string, SupplyChoice>;
|
||||||
dirty: boolean;
|
dirty: boolean;
|
||||||
@@ -588,6 +593,36 @@ function buildQuantitySidePanel(
|
|||||||
}
|
}
|
||||||
panel.append(hintRow(L("B08_Quantity_Side_Frame_Hint")));
|
panel.append(hintRow(L("B08_Quantity_Side_Frame_Hint")));
|
||||||
|
|
||||||
|
// ── 임목파쇄 — ⚠ **기본 꺼짐**(확정 5차 5번). 「셀지 말지가 설계 판단」이라 켜야 줄이 선다.
|
||||||
|
// ⚠ 근주이식은 **칸 자체를 안 만든다** — 켤 자리가 없으면 물을 일도 없다.
|
||||||
|
panel.append(field(L("B08_Quantity_Side_Chipping"), ""));
|
||||||
|
panel.append(
|
||||||
|
selectField(
|
||||||
|
L("B08_Quantity_Chipping_Label"),
|
||||||
|
draft.wood_chipping_enabled ? "on" : "",
|
||||||
|
[
|
||||||
|
{ value: "", label: L("B08_Quantity_Chipping_Off") },
|
||||||
|
{ value: "on", label: L("B08_Quantity_Chipping_On") },
|
||||||
|
],
|
||||||
|
(value) => {
|
||||||
|
draft.wood_chipping_enabled = value === "on";
|
||||||
|
draft.dirty = true;
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
panel.append(
|
||||||
|
optionalNumberField(
|
||||||
|
L("B08_Quantity_Chipping_Volume"),
|
||||||
|
draft.wood_chipping_volume_m3,
|
||||||
|
"1",
|
||||||
|
(value) => {
|
||||||
|
draft.wood_chipping_volume_m3 = value;
|
||||||
|
draft.dirty = true;
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
panel.append(hintRow(L("B08_Quantity_Side_Chipping_Hint")));
|
||||||
|
|
||||||
// ── 콘크리트 타설 방식 — ⚠ **금액에 바로 걸리는 값**이라 잠정임을 조용히 두지 않는다 ──
|
// ── 콘크리트 타설 방식 — ⚠ **금액에 바로 걸리는 값**이라 잠정임을 조용히 두지 않는다 ──
|
||||||
panel.append(field(L("B08_Quantity_Side_Placing"), ""));
|
panel.append(field(L("B08_Quantity_Side_Placing"), ""));
|
||||||
panel.append(
|
panel.append(
|
||||||
@@ -846,6 +881,8 @@ export async function renderB08Quantity(root: HTMLElement): Promise<void> {
|
|||||||
topsoil_haul_distance_m: (stored.topsoil_haul_distance_m as number | null) ?? null,
|
topsoil_haul_distance_m: (stored.topsoil_haul_distance_m as number | null) ?? null,
|
||||||
stand_volume_class: (stored.stand_volume_class as string) ?? "",
|
stand_volume_class: (stored.stand_volume_class as string) ?? "",
|
||||||
frame_material: { ...((stored.frame_material ?? {}) as Record<string, number | null>) },
|
frame_material: { ...((stored.frame_material ?? {}) as Record<string, number | null>) },
|
||||||
|
wood_chipping_enabled: Boolean(stored.wood_chipping_enabled),
|
||||||
|
wood_chipping_volume_m3: (stored.wood_chipping_volume_m3 as number | null) ?? null,
|
||||||
ancillary_counts: {
|
ancillary_counts: {
|
||||||
...((stored.ancillary_counts ?? {}) as Record<string, number | null>),
|
...((stored.ancillary_counts ?? {}) as Record<string, number | null>),
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -139,6 +139,11 @@ def default_settings() -> dict[str, Any]:
|
|||||||
# 품셈 11-2·11-3 [주]④ 가 「재료량은 설계수량에 따른다」라 값을 안 주는 자리이고,
|
# 품셈 11-2·11-3 [주]④ 가 「재료량은 설계수량에 따른다」라 값을 안 주는 자리이고,
|
||||||
# 세는 것 자체는 확정이라 **제안값 + 고칠 수 있게**로 둔다(확정 ⑨·⑩ 과 같은 틀).
|
# 세는 것 자체는 확정이라 **제안값 + 고칠 수 있게**로 둔다(확정 ⑨·⑩ 과 같은 틀).
|
||||||
"frame_material": {},
|
"frame_material": {},
|
||||||
|
# 임목파쇄 — **기본 꺼짐**(확정 5차 5번). 현장에 따라 켤 수 있는 칸.
|
||||||
|
# ⚠ 근주이식은 칸도 두지 않는다 — 「셀지 말지가 설계 판단」이라 켤 자리가 없으면
|
||||||
|
# 물을 일도 없다. 켜도 **부피는 지어내지 않는다**(아래 칸이 비면 줄만 서고 사유).
|
||||||
|
"wood_chipping_enabled": False,
|
||||||
|
"wood_chipping_volume_m3": None,
|
||||||
"dataset_versions": {},
|
"dataset_versions": {},
|
||||||
},
|
},
|
||||||
"estimation": {
|
"estimation": {
|
||||||
|
|||||||
@@ -686,6 +686,15 @@ export const ui_locales_b2 = {
|
|||||||
"제근(품셈 9-21)이 이 등급으로 갈립니다 — ⚠ 본수가 아니라 축적이고, 산림조사부·영림계획에서 옮겨 적는 값입니다",
|
"제근(품셈 9-21)이 이 등급으로 갈립니다 — ⚠ 본수가 아니라 축적이고, 산림조사부·영림계획에서 옮겨 적는 값입니다",
|
||||||
"Root removal (9-21) splits by this class — stand volume, not tree count; copied from the forest survey",
|
"Root removal (9-21) splits by this class — stand volume, not tree count; copied from the forest survey",
|
||||||
],
|
],
|
||||||
|
B08_Quantity_Side_Chipping: ["임목파쇄", "Wood Chipping"],
|
||||||
|
B08_Quantity_Chipping_Label: ["임목파쇄를 셀 것인가", "Count wood chipping"],
|
||||||
|
B08_Quantity_Chipping_Off: ["안 셈(기본)", "No (default)"],
|
||||||
|
B08_Quantity_Chipping_On: ["셈", "Yes"],
|
||||||
|
B08_Quantity_Chipping_Volume: ["파쇄 부피(㎥)", "Chipping volume (㎥)"],
|
||||||
|
B08_Quantity_Side_Chipping_Hint: [
|
||||||
|
"기본은 안 셉니다(확정 5차 5번) — 현장에 따라 필요하면 켜세요. ⚠ 켜도 부피를 넣어야 값이 섭니다(실무는 부피로 셈)",
|
||||||
|
"Off by default — turn on per site. Volume must be entered for the row to carry a quantity",
|
||||||
|
],
|
||||||
B08_Quantity_Side_Frame: ["규준틀 개소당 재료", "Batter Board Materials (per unit)"],
|
B08_Quantity_Side_Frame: ["규준틀 개소당 재료", "Batter Board Materials (per unit)"],
|
||||||
B08_Quantity_Side_Frame_Hint: [
|
B08_Quantity_Side_Frame_Hint: [
|
||||||
"⚠ 채워진 값은 실무 관측값(제안값)이고 법정 기준이 아닙니다 — 품셈 11-2·11-3 [주]④ 는 「재료량은 설계수량에 따른다」로만 둡니다. 손율은 원문값(비탈 50% · 수평 80%)",
|
"⚠ 채워진 값은 실무 관측값(제안값)이고 법정 기준이 아닙니다 — 품셈 11-2·11-3 [주]④ 는 「재료량은 설계수량에 따른다」로만 둡니다. 손율은 원문값(비탈 50% · 수평 80%)",
|
||||||
|
|||||||
Reference in New Issue
Block a user