사용자 확정(2026-09-07) — 소단은 좌측 별도 폼이 아니라 **다른 옹벽·기슭막이와 같은 자리** (구조물 배치)에서 놓는 것으로 바뀜. 기하는 그대로 두고 **입구만 옮겼음.** · 레지스트리에 **C군(사면안정) 「소단」** 신설. 옵션은 다른 C군 구간형과 같은 꼴 — 길이·기준측점 전·후 + 폭·간격(사면길이)·안쪽 기울기. · ⚠ **소단은 C군 구간형이지만 벽이 아님** — 기슭막이 제원 자리에 얹히지 않게 파이썬·TS 양쪽 벽 필터에서 뺐음. 안 뺐으면 소단을 놓는 순간 「독립 기슭막이」로 그려졌을 것임. · 구조물 목록이 바뀌면 소단 구간을 세션 사본(`berm`)으로 펴고, 달라졌을 때만 재계산함. 계산 계통(브라우저·서버·`USER_TOUCHED_KEYS`·옹벽 의무 판정)은 **한 줄도 안 바꿨음**. · ④에서 만든 좌측 별도 「소단」 패널은 걷어냈음(모듈 삭제). **안쪽 기울기 기본값 2° → 0°** (사용자 재확정). 기울이는 것은 실무이나 법령·교본 근거가 없어 기본으로 넣지 않고 폼에서 받음. 주석·지식DB 방침도 그에 맞춰 고침. **소단측구**(B군 종단배수)도 같은 꼴로 옵션을 붙였음 — 소단 위에 놓이는 시설이라 폭·간격이 소단을 따르고, 기울기만 **사면 쪽**(소단과 반대). 소단이 실제로 서면서 `PENDING_TYPE_IDS` 빈 칸이 풀려 연장 수량이 다른 구조물과 같은 길로 나옴. 자체검증 — 「근거 없는 기본값 금지」 시험에 두 타입 12개 기본값을 근거와 함께 등재. 소단측구 연장 시험을 「빈 칸」에서 「연장 100m」로 뒤집음. 전체 544 passed · 18 skipped. TS 타입 검사·ruff 통과. 화면 확인은 다음 단계. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
95 lines
4.3 KiB
Python
95 lines
4.3 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]]) -> float:
|
|
"""겹치는 구간을 합쳐 실제 덮인 길이를 낸다.
|
|
|
|
같은 시설을 겹치게 두 번 넣으면 단순 합은 그 구간을 **두 번 센다**. 연장은 「덮인
|
|
길이」라 겹침을 지우는 쪽이 맞다. 원래 합(`raw_length_m`)도 함께 내보내므로 입력이
|
|
겹쳤다는 사실은 숨지 않는다.
|
|
"""
|
|
total = 0.0
|
|
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
|
|
total += current_end - current_start
|
|
current_start, current_end = start, end
|
|
if current_start is not None:
|
|
total += current_end - current_start
|
|
return total
|
|
|
|
|
|
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]
|
|
rows.append(
|
|
{
|
|
"type_id": type_id,
|
|
"group": types[type_id].group,
|
|
"name": types[type_id].name,
|
|
"count": count,
|
|
# 겹침을 지운 실제 연장 — 수량서에 쓸 값.
|
|
"length_m": round(_merge(entries), 2),
|
|
# 입력한 구간 길이의 단순 합 — 위와 다르면 구간이 겹쳐 있다는 뜻.
|
|
"raw_length_m": round(sum(end - start for start, end in entries), 2),
|
|
}
|
|
)
|
|
rows.sort(key=lambda row: (row["group"], row["name"]))
|
|
return rows
|