B08 창 요청(2026-09-08) — 연장(m)만 내면 산출근거에 **「어디부터 어디까지」**를 못 적음.
실무 내역은 「산마루측구 40m」 한 줄이라 없어도 서지만, 있으면 검산이 쉬워짐.
`_merge` 가 길이 대신 **합친 구간 목록**을 돌려주게 하고, 행에 `spans`
(`[{start_m, end_m}, …]`)를 실음. **길이는 그 목록의 합**이라 둘이 갈릴 수 없음.
⚠ 겹침은 **합쳐서** 냄 — 데스크탑 실측: 산마루측구 둘(80~120 · 100~140)이 **60.0m**
로 합쳐져 내역서에 감(단순 합 80.0m). 구조물별로 셌으면 **20m 를 더 셀** 자리였음.
그 규칙이 이 함수 한 벌에만 있어야 하는 까닭이기도 함(측구 제외·관 소관 제외도 같음).
시험 5건 — 안 겹치면 그대로 · 겹치면 합침(그 실측 그대로) · 맞닿은 두 구간은 하나 ·
품은 구간은 사라짐 · **행의 길이가 구간 합과 같은지**. 전체 513 passed · 17 skipped.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
104 lines
5.0 KiB
Python
104 lines
5.0 KiB
Python
"""구조물 정본에서 **시설별 연장(m)** 을 낸다 — B군 배수시설 수량의 입력 (계획서 3-6).
|
|
|
|
왜 연장만인가 — B군(측구·산마루측구·도수로·맹암거 등)의 수량 단위가 **전부 m** 이다
|
|
(맹암거 12-10 · L형 측구 12-9-1 · 산마루측구 12-9-2 · 소단측구 12-9-3, 지식DB
|
|
`04_수량분석정보/배수공_수량.md`). 그래서 단면 기하 없이 구간 길이만으로 셈이 된다.
|
|
별표2의 「횡단면도 각 측점 기입 물량」 목록에도 B군은 없어 도면에 그릴 의무가 없다
|
|
(2026-09-07 조사).
|
|
|
|
**수량서 양식에 안 묶인다** — 여기서는 종류별 연장·개소만 내고, 어느 코드·어느 칸에
|
|
넣을지는 B08 이 정한다. B08 은 실무 xlsx 양식으로 재작업 예정이라 그 사이에 두는 것이다.
|
|
"""
|
|
|
|
from pathlib import Path
|
|
from typing import Any
|
|
|
|
from B05_Profile.B05_Profile_Structures_Repository import load_structures
|
|
from B05_Profile.B05_Profile_Structures_Schema import structure_type_map
|
|
|
|
# 아직 셈하지 않는 타입 — 지금은 없다.
|
|
#
|
|
# 소단측구(`ditch_berm`)는 계획서 3-9 로 **소단이 실제로 서면서 빈 칸이 풀렸다**
|
|
# (2026-09-07). 이제 다른 구조물과 같은 길로 연장이 나온다.
|
|
PENDING_TYPE_IDS: frozenset[str] = frozenset()
|
|
|
|
|
|
def _merge(spans: list[tuple[float, float]]) -> list[tuple[float, float]]:
|
|
"""겹치는 구간을 **합쳐** 실제 덮인 구간 목록을 낸다.
|
|
|
|
같은 시설을 겹치게 두 번 넣으면 단순 합은 그 구간을 **두 번 센다**. 연장은 「덮인
|
|
길이」라 겹침을 지우는 쪽이 맞다. 원래 합(`raw_length_m`)도 함께 내보내므로 입력이
|
|
겹쳤다는 사실은 숨지 않는다.
|
|
|
|
합친 **구간 자체**를 돌려준다 — 길이만 내면 산출근거에 「어디부터 어디까지」를 못 적는다
|
|
(2026-09-08 B08 창 요청). 길이는 부르는 쪽이 이 목록에서 더한다.
|
|
"""
|
|
merged: list[tuple[float, float]] = []
|
|
current_start: float | None = None
|
|
current_end = 0.0
|
|
for start, end in sorted(spans):
|
|
if current_start is None:
|
|
current_start, current_end = start, end
|
|
continue
|
|
if start <= current_end:
|
|
current_end = max(current_end, end)
|
|
continue
|
|
merged.append((current_start, current_end))
|
|
current_start, current_end = start, end
|
|
if current_start is not None:
|
|
merged.append((current_start, current_end))
|
|
return merged
|
|
|
|
|
|
def structure_lengths(project_root: str | Path) -> list[dict[str, Any]]:
|
|
"""구간형 구조물의 시설별 연장을 돌려준다 (기준점 순, 종류별 한 줄).
|
|
|
|
빼는 것 둘 —
|
|
· `managed_by` 타입(배관 등): 구조물 정본이 아니라 관 지점 정본 소관이다.
|
|
· `design_owner` 타입(측구 = 횡단 설계): 횡단이 이미 터파기 단면적까지 셈하므로
|
|
여기서 또 세면 **같은 것을 두 번 계상**한다(2026-09-07 사용자 확정).
|
|
(`PENDING_TYPE_IDS` 는 지금 비어 있다 — 소단측구가 3-9 로 풀렸다.)
|
|
|
|
시작·종료는 늘 있다 — 구간형은 스키마가 둘 다 없으면 저장을 막는다
|
|
(`StructureInstance.validate_placement_fields`).
|
|
"""
|
|
types = structure_type_map()
|
|
spans: dict[str, list[tuple[float, float]]] = {}
|
|
counts: dict[str, int] = {}
|
|
|
|
for structure in load_structures(str(project_root))[1]:
|
|
definition = types.get(structure.type_id)
|
|
if definition is None or definition.placement != "interval":
|
|
continue
|
|
if definition.managed_by or definition.design_owner:
|
|
continue
|
|
if structure.type_id in PENDING_TYPE_IDS:
|
|
continue
|
|
start, end = float(structure.start_m), float(structure.end_m)
|
|
counts[structure.type_id] = counts.get(structure.type_id, 0) + 1
|
|
spans.setdefault(structure.type_id, []).append((min(start, end), max(start, end)))
|
|
|
|
rows: list[dict[str, Any]] = []
|
|
for type_id, count in counts.items():
|
|
entries = spans[type_id]
|
|
merged = _merge(entries)
|
|
rows.append(
|
|
{
|
|
"type_id": type_id,
|
|
"group": types[type_id].group,
|
|
"name": types[type_id].name,
|
|
"count": count,
|
|
# 겹침을 지운 실제 연장 — 수량서에 쓸 값.
|
|
"length_m": round(sum(end - start for start, end in merged), 2),
|
|
# 입력한 구간 길이의 단순 합 — 위와 다르면 구간이 겹쳐 있다는 뜻.
|
|
"raw_length_m": round(sum(end - start for start, end in entries), 2),
|
|
# 겹침을 지운 **구간 목록**(누가거리 m) — 산출근거에 「어디부터 어디까지」를
|
|
# 적는 자리다. 길이는 이 목록의 합과 같다(2026-09-08 B08 창 요청).
|
|
"spans": [
|
|
{"start_m": round(start, 2), "end_m": round(end, 2)} for start, end in merged
|
|
],
|
|
}
|
|
)
|
|
rows.sort(key=lambda row: (row["group"], row["name"]))
|
|
return rows
|