Files
Aislo/B08_Quantity/B08_Quantity_Engine_Preparation.py
T
eomsangdonandClaude Opus 5 e90a5703ee feat(B08): 준비공·사방공 탭 + 페이지 최종 점검
⑪ 준비공·사방공 — 자리를 만들되 없는 값을 지어내지 않음.
- ⚠ 벌목은 값을 안 냄. 토공집계의 「지장목제거」로 이미 서 있어 또 세우면 같은
  나무를 두 번 벰. 참고 면적만 보이고 「다른 표에서 이미 섬」으로 가리킴.
- 못 서는 줄에 사유를 적음 — 표토제거(두께·구간 미정) · 제근(입목 본수 없음) ·
  규준틀(개소 기준 미정). 공종코드는 미리 적어 둠.
- 사방공은 레지스트리의 실제 type_id 로 봄. 없으면 「해당 없음」 — 0 을 적지 않음.
  이름을 지어내면 영영 안 걸리므로 레지스트리와 대조하는 시험을 둠.

⑫ 최종 점검 — 탭 6장 전수를 실화면에서 돌려 값으로 서는 것 확인
(토적표 65측점 · 토공집계 10줄 · 운반거리 안내 · 준비공 5줄 · 구조물 원단위 10줄 ·
자재총괄 4줄). 전체 566 passed, tsc 오류 0.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-08 00:50:35 +09:00

139 lines
5.7 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""준비공·사방공 — **자리를 만들되 없는 값을 지어내지 않는다** (B08 일감 ⑪ · PLAN 8-3).
8-3 대응표에서 ❌ 로 남아 있던 둘이다. 여기서 하는 일은 **줄을 세우고, 설 수 있는 줄은
값을 채우고, 못 서는 줄은 왜 못 서는지 적는 것**이다. 빈 표를 내면 「빠뜨린 것」과
「원래 없는 것」이 구별되지 않는다.
⚠⚠ 지장목제거와 겹치지 않는다 (이중계상)
벌목·지장목제거는 **이미 토공집계의 사면 계열로 서 있다**(`tree_removal_*` × 반영률).
여기서 또 세우면 같은 나무를 두 번 벤다. 그래서 준비공의 벌목 줄은 **값을 내지 않고
「토공집계 지장목제거로 이미 섬」이라고 가리키기만** 한다.
⚠ 값이 없는 줄의 사유를 적는다
· 표토제거(9-15) — 면적은 사면적에서 나오나 **두께·대상 구간이 설계로 안 정해져 있다**.
· 제근·뿌리다듬기(9-20~21) — 단위가 **「개」(그루 수)**인데 입목 본수를 우리가 안 든다.
· 규준틀(11-2) — **개소** 산정 기준(구조물·절성토 구간별 몇 개소)이 안 정해져 있다.
⚠ 사방공은 이 노선에 실물이 없으면 「해당 없음」이다
있는 것처럼 0 을 적지 않는다. 구조물 목록에 사방 시설이 서면 그때 값이 선다.
"""
from __future__ import annotations
from typing import Any, Iterable
#: 사방 시설로 보는 구조물 종류 — **레지스트리의 실제 `type_id` 를 쓴다**(D 그룹 + 흙막이).
#: 목록에 없으면 그 노선에 사방공이 **없는** 것이다. 이름을 지어내면 영영 안 걸린다.
EROSION_CONTROL_TYPES = frozenset(
{
"erosion_check", # 골막이
"bed_sill", # 바닥막이
"check_dam_small", # 소형사방댐(복합형)
"revetment", # 기슭막이
"soil_guard", # 흙막이
}
)
STATUS_COUNTED_ELSEWHERE = "다른 표에서 이미 섬"
STATUS_PENDING = "값을 낼 근거가 없음"
STATUS_NOT_APPLICABLE = "해당 없음"
STATUS_READY = "값 있음"
def preparation_rows(slope_totals: dict[str, float] | 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)
)
return [
{
"group": "준비공",
"item": "벌목·지장목제거",
"unit": "㎡",
"amount": None,
"status": STATUS_COUNTED_ELSEWHERE,
# ⚠ 값을 여기서 또 내면 같은 나무를 두 번 벤다. 참고로 면적만 보인다.
"reference_amount": tree_area,
"reason": "토공집계의 「지장목제거」로 이미 섬 — 여기서 또 세우면 이중계상",
"work_item_code": None,
},
{
"group": "준비공",
"item": "표토제거",
"unit": "㎥",
"amount": None,
"status": STATUS_PENDING,
"reason": "면적은 사면적에서 나오나 **두께·대상 구간**이 설계로 안 정해져 있음 (품셈 9-15)",
"work_item_code": "FP-09-15",
},
{
"group": "준비공",
"item": "제근·뿌리다듬기",
"unit": "개",
"amount": None,
"status": STATUS_PENDING,
"reason": "단위가 「개」(그루 수)인데 입목 본수를 들고 있지 않음 (품셈 9-20~21)",
"work_item_code": "FP-09-21",
},
{
"group": "준비공",
"item": "규준틀",
"unit": "개소",
"amount": None,
"status": STATUS_PENDING,
"reason": "개소 산정 기준(구조물·절성토 구간별 몇 개소)이 안 정해져 있음 (품셈 11-2)",
"work_item_code": "FP-11-02",
},
]
def erosion_rows(structures: Iterable[dict[str, Any]] = ()) -> list[dict[str, Any]]:
"""사방공 줄 — 이 노선에 사방 시설이 **있을 때만** 값이 선다."""
found = sorted(
{
str(item.get("type_id"))
for item in structures
if str(item.get("type_id")) in EROSION_CONTROL_TYPES
}
)
if not found:
return [
{
"group": "사방공",
"item": "사방 시설",
"unit": "",
"amount": None,
"status": STATUS_NOT_APPLICABLE,
"reason": "이 노선에 사방 시설이 배치돼 있지 않음 — 있는 것처럼 0 을 적지 않음",
"work_item_code": None,
}
]
return [
{
"group": "사방공",
"item": type_id,
"unit": "개소",
"amount": None,
"status": STATUS_PENDING,
"reason": "구조물 원단위가 아직 없음 — 전개식·관측값 모두 미확보",
"work_item_code": None,
}
for type_id in found
]
def build_table(
slope_totals: dict[str, float] | None = None,
structures: Iterable[dict[str, Any]] = (),
) -> dict[str, Any]:
"""화면·인계가 그대로 쓰는 모양. **못 서는 줄도 목록에 남는다.**"""
rows = preparation_rows(slope_totals) + erosion_rows(structures)
return {
"columns": ["구분", "공종", "단위", "수량", "상태", "사유"],
"rows": rows,
"ready_count": sum(1 for row in rows if row["status"] == STATUS_READY),
"pending_count": sum(1 for row in rows if row["status"] == STATUS_PENDING),
"row_count": len(rows),
}