feat(B08): 콘크리트 타설 갈래 — 철근 유무 자동 판정
조율 창 판정 반영. 타설 공종은 타설 방식 × 구조물 종류로 갈림. - **구조물 종류는 자동 판정** — 원단위 성분에 철근이 있으면 철근구조물. 사람이 고르는 값이 아님. 옹벽 관측 원단위의 D13·D16 에서 그대로 나옴. - **타설 방식은 설계 판단**이라 프로젝트 설정(`quantity.concrete_placing_method`)이 고르고 기본은 레디믹스트(FP-12-01-01). 잠정이며 사용자 확정 대기. 모르는 방식이면 기본으로 떨어지되 그 사실을 알림. - 옹벽 묶음의 타설 코드를 `FP-12-01-01#철근구조물` 로 좁힘. - ⚠ 묶음 넷 중 `FP-12-03`·`FP-12-25` 는 B09 일위대가가 아직 안 섬. `composite_not_ready` 로 함께 알림 — 지금 세우면 절반짜리가 됨. 검증 — 인계 48건 통과, 전체 578 passed. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -61,6 +61,11 @@ ORIGIN_HAUL = "haul"
|
||||
METHOD_TO_GROUND = {"ripping": "리핑암", "blasting": "발파암"}
|
||||
NOTE_METHOD_MISSING = "시공법 미지정으로 공종을 못 고름"
|
||||
|
||||
#: 철근으로 보는 성분 이름 조각. **정확한 낱말이 아니라 앞머리**로 본다 —
|
||||
#: 「이형철근 D13」·「원형철근」처럼 규격이 뒤에 붙기 때문이다. `철근콘크리트`는 성분 이름이
|
||||
#: 아니라 공종 이름이라 성분 목록에는 안 온다.
|
||||
REBAR_PREFIXES = ("이형철근", "원형철근", "철근")
|
||||
|
||||
#: 사면 계열 — 토공집계에 함께 실리지만 출처가 사면적이다.
|
||||
SLOPE_GROUPS = frozenset({"성토면다짐", "초류종자살포", "지장목제거", "층따기", "면고르기"})
|
||||
|
||||
@@ -86,6 +91,7 @@ class WorkItemMapping:
|
||||
structure: list[dict[str, Any]] = field(default_factory=list)
|
||||
pending_user: dict[str, Any] = field(default_factory=dict)
|
||||
composite: dict[str, Any] = field(default_factory=dict)
|
||||
concrete_placing: dict[str, Any] = field(default_factory=dict)
|
||||
|
||||
def for_earthwork(self, group: str, ground: str | None) -> dict[str, Any] | None: # noqa: D401
|
||||
"""공종+지반유형으로 찾는다. 지반이 갈리지 않는 공종은 `ground` 없는 줄이 받는다."""
|
||||
@@ -136,9 +142,33 @@ def load_mapping(path: Path | None = None) -> WorkItemMapping:
|
||||
structure=list(payload.get("structure") or []),
|
||||
pending_user=payload.get("pending_user") or {},
|
||||
composite=payload.get("composite") or {},
|
||||
concrete_placing=payload.get("concrete_placing") or {},
|
||||
)
|
||||
|
||||
|
||||
def structure_kind(structure: dict[str, Any]) -> str:
|
||||
"""콘크리트 구조물 종류 — **원단위에 철근이 있나 없나로 판정한다.**
|
||||
|
||||
사람이 고르는 값이 아니다(2026-09-07 3자 확정). 옹벽 관측 원단위에 `D13`·`D16` 이
|
||||
실려 있으므로 철근구조물로 자동으로 선다. 소형구조물 판정 기준은 아직 없다.
|
||||
"""
|
||||
for component in structure.get("components") or []:
|
||||
name = str(component.get("name") or "").strip()
|
||||
if any(name.startswith(prefix) for prefix in REBAR_PREFIXES):
|
||||
return "철근구조물"
|
||||
return "무근구조물"
|
||||
|
||||
|
||||
def placing_code(mapping: WorkItemMapping, method: str | None) -> tuple[str | None, bool]:
|
||||
"""(타설 공종코드, 기본값을 쓴 것인가). 모르는 방식이면 기본으로 떨어지되 그 사실을 알린다."""
|
||||
table = mapping.concrete_placing or {}
|
||||
codes = table.get("method_codes") or {}
|
||||
default = str(table.get("default_method") or "")
|
||||
if method in codes:
|
||||
return codes[method], False
|
||||
return codes.get(default), True
|
||||
|
||||
|
||||
def _spec_detail(structure: dict[str, Any]) -> str:
|
||||
"""규격 표기 — 저장된 제원에서 만든다. 없는 값은 적지 않는다."""
|
||||
parts: list[str] = []
|
||||
@@ -276,6 +306,7 @@ def _structure_rows(
|
||||
entry = mapping.for_structure(type_id) or {}
|
||||
code = entry.get("work_item_code")
|
||||
composite = mapping.composite_for(type_id) if code is None else None
|
||||
kind = structure_kind(structure) if composite else None
|
||||
if code is None and composite is None:
|
||||
unmatched.append(f"구조물({type_id})")
|
||||
length = float(structure.get("length_m") or 0.0)
|
||||
@@ -298,6 +329,10 @@ def _structure_rows(
|
||||
"spec_detail": _spec_detail(structure),
|
||||
# 품셈에 그 이름의 공종이 없어 여러 공종을 묶는 자리 — 빈 코드와 구별한다.
|
||||
"composite_parts": (composite or {}).get("parts"),
|
||||
# 철근이 있나 없나로 자동 판정 — 사람이 고르는 값이 아니다.
|
||||
"structure_kind": kind,
|
||||
# ⚠ 아직 일위대가가 안 선 공종 — 지금 세우면 절반짜리가 된다.
|
||||
"composite_not_ready": (composite or {}).get("not_ready"),
|
||||
"in_bill": True,
|
||||
"in_bill_reason": (composite or {}).get("why", ""),
|
||||
"origin": ORIGIN_STRUCTURE,
|
||||
|
||||
@@ -93,6 +93,10 @@ def default_settings() -> dict[str, Any]:
|
||||
# ⚠ **법이 아니라 발주 결정**이라 기본은 비워 둔다. 안 정한 자재는 「미분류」로
|
||||
# 화면에 드러난다 — 사급으로 조용히 넘기면 관급자재대가 새 나간다.
|
||||
"material_supply": {},
|
||||
# 콘크리트 타설 방식 — `ready_mixed`(FP-12-01-01) / `machine_mixed`(-02) /
|
||||
# `hand_mixed`(-03). **설계 판단**이라 사용자가 고르는 값이고, 기본은 실무가
|
||||
# 쓰는 레디믹스트로 둔다. ⚠ 잠정이며 사용자 확정 대기 항목이다.
|
||||
"concrete_placing_method": "ready_mixed",
|
||||
"dataset_versions": {},
|
||||
},
|
||||
"estimation": {
|
||||
|
||||
@@ -165,14 +165,36 @@
|
||||
{
|
||||
"type_id": "retaining_wall",
|
||||
"parts": [
|
||||
"FP-12-01 콘크리트 타설",
|
||||
"FP-12-01-01#철근구조물",
|
||||
"FP-12-04 합판거푸집",
|
||||
"FP-12-03 철근 현장가공 및 조림",
|
||||
"FP-12-25 기초잡석"
|
||||
],
|
||||
"why": "품셈 12장에 「옹벽」 공종이 없음. 실무 내역은 「반중력식옹벽 H=2.0」 한 줄이고 그 일위대가가 위 공종을 묶음.",
|
||||
"needs": "일위대가 조립은 B09 몫 — B08 은 물량과 묶음만 넘김"
|
||||
"needs": "일위대가 조립은 B09 몫 — B08 은 물량과 묶음만 넘김",
|
||||
"placing_note": "타설 코드는 프로젝트 설정의 타설 방식으로 갈림(기본 레디믹스트). 철근구조물 판정은 원단위의 D13·D16 에서 자동으로 나옴.",
|
||||
"not_ready": [
|
||||
"FP-12-03",
|
||||
"FP-12-25"
|
||||
],
|
||||
"not_ready_why": "B09 일위대가가 아직 안 섬 — 지금 세우면 절반짜리가 됨(2026-09-07 조율 창)"
|
||||
}
|
||||
]
|
||||
},
|
||||
"concrete_placing": {
|
||||
"note": "콘크리트 타설은 **타설 방식 × 구조물 종류**로 갈린다. 방식은 설계 판단이라 프로젝트 설정(`quantity.concrete_placing_method`)이 고르고, 종류는 **원단위에 철근이 있나 없나로 자동 판정**한다 — 사람이 고르는 값이 아니다(2026-09-07 3자 확정).",
|
||||
"method_codes": {
|
||||
"ready_mixed": "FP-12-01-01",
|
||||
"machine_mixed": "FP-12-01-02",
|
||||
"hand_mixed": "FP-12-01-03"
|
||||
},
|
||||
"default_method": "ready_mixed",
|
||||
"default_is_provisional": true,
|
||||
"structure_kinds": [
|
||||
"무근구조물",
|
||||
"철근구조물",
|
||||
"소형구조물"
|
||||
],
|
||||
"kind_rule": "원단위 성분에 철근(이형철근·원형철근)이 있으면 철근구조물, 없으면 무근구조물. 소형구조물 판정 기준은 미확보."
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user