fix(B06): 관 길이를 **소유 측점에만** 싣게 — 같은 관을 두 번 셀 뻔한 자리

`pipe_length_m` 을 넣고 실화면 [저장]으로 확인하다 드러남 — **관 9개인데 값이 10곳**에 실렸음.
까닭은 `attach_culvert_sets` 가 세트를 **폭의 절반까지 옆 측점에도** 붙이기 때문임(3D·카드가
이어 그리려고). 그 자리에 길이를 실으면 수량이 **같은 관을 두 번** 셈.

- 스펙에 **놓인 누가거리(`chainage_m`)** 를 함께 얹음 — 파이썬 `attach_culvert_sets` ·
  TS `attachCulvertSets` **둘 다**(한쪽만 얹으면 세트 거울 시험이 깨짐. 실제로 깨져서 알았음).
- 길이는 그 값과 측점이 같을 때만 실음.
- 옛 값 정리 — 이미 실려 있던 이웃 측점 4곳(260.0·800.0·900.0·980.0)에서 지웠음.
  **파생값이라 병합만으로는 안 지워짐**(merge 는 키를 얹기만 함).

⚠ **따로 드러난 것 — 관 3개(439.55·620.43·720.37)는 측점 행 자체가 없음.** 그 관들은 횡단
설계도 수량도 통째로 빠져 있음. 별건으로 조사할 것(계획서에 적음).

실측 결과 — 관 길이가 실린 측점 **6곳**(85.05→10.0 · 258.12→11.0 · 534.49→9.0 · 804.30→9.0 ·
901.24→10.0 · 984.18→10.0m). 나머지 셋이 위 별건임.

시험 7건(소유 측점 판정 한 건 추가) · 전체 507 passed · 17 skipped.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-09-08 09:36:23 +09:00
co-authored by Claude Opus 5
parent 5f5e91d655
commit 8555e693ac
4 changed files with 26 additions and 3 deletions
+3
View File
@@ -218,6 +218,9 @@ export interface CulvertSideSpec {
/** 배수관 측점의 세트(배관·기슭막이·보호공) 제원 — 횡단 카드 오버레이 입력. */
export interface CulvertSet {
type: "pipe";
/** 이 시설이 **놓인** 누가거리(m) — 세트는 옆 측점에도 붙으므로 소유 측점을 가리는 열쇠다.
* (2026-09-08 — 없을 때 관 길이가 이웃 측점에도 실려 같은 관을 두 번 셀 뻔했다.) */
chainage_m?: number;
pipe_kind: string | null;
diameter_m: number;
/** 관 위 최소 토피(m) — 별표2 교량·암거 복토 50㎝ 교차 참조. B05 하향 차단 기준. */
+7 -1
View File
@@ -435,7 +435,13 @@ def attach_culvert_sets(project_root: Path, cross_sections: list[dict[str, Any]]
if abs(chainage - pipe_chainage) <= reach:
# 세월교·BOX암거·물넘이는 배수관과 그림이 달라 키를 나눈다 — 소비처가 섞이지 않는다.
kind = spec.get("type")
section[_SECTION_KEYS.get(str(kind), "culvert")] = spec
# ⚠ 스펙에 **그 시설이 놓인 누가거리**를 함께 얹는다(2026-09-08). 세트는 폭의
# 절반까지 옆 측점에도 붙으므로, 이것이 없으면 소비처가 「소유 측점」을 못 가려
# **같은 시설을 여러 측점에서 센다**(관 9개에 길이가 10곳 실렸던 자리).
section[_SECTION_KEYS.get(str(kind), "culvert")] = {
**spec,
"chainage_m": pipe_chainage,
}
attached += 1
break
return attached
+9 -1
View File
@@ -132,7 +132,15 @@ function areaRowOf(
if (!layouts) return null;
// 관 길이는 면적과 **따로** 낸다 — 폐회로 면적을 못 내는 측점(설계선이 모자란 자리)에도
// 관은 서 있고, 수량은 그 길이를 필요로 한다(2026-09-08).
const pipeLengthM = layouts.culvert?.pipe?.lengthM;
//
// ⚠ **관을 가진 측점(소유)에만 싣는다.** 옆 측점도 그 관 구간에 걸리면 레이아웃을 만들지만
// (`culvertLinkFor` — 3D·카드가 이어 그리려고), 그 자리에 길이를 실으면 **같은 관을 두 번**
// 세게 된다. 실측에서 관 9개에 값이 10곳 실렸던 자리다.
const pipeOwner =
!!section.culvert &&
(typeof section.culvert.chainage_m !== "number" ||
Math.abs(section.culvert.chainage_m - section.chainage_m) <= CHAINAGE_TOLERANCE_M);
const pipeLengthM = pipeOwner ? layouts.culvert?.pipe?.lengthM : undefined;
const pipeRow: Record<string, number> | null =
typeof pipeLengthM === "number" && pipeLengthM > 0
? { chainage_m: section.chainage_m, pipe_length_m: Number(pipeLengthM.toFixed(4)) }
+7 -1
View File
@@ -331,7 +331,13 @@ export function attachCulvertSets(
let reach = CHAINAGE_TOLERANCE_M;
if (SPAN_LINKED_TYPES.has(String(spec.type))) reach += (num(spec.span_m, 0) ?? 0) / 2;
if (Math.abs(chainage - pipeChainage) <= reach) {
section[SECTION_KEYS[String(spec.type)] ?? "culvert"] = spec;
// ⚠ 스펙에 **그 시설이 놓인 누가거리**를 함께 얹는다(2026-09-08, 짝: 파이썬
// `attach_culvert_sets`). 세트는 폭의 절반까지 옆 측점에도 붙으므로, 이것이 없으면
// 소비처가 「소유 측점」을 못 가려 **같은 시설을 여러 측점에서 센다**.
section[SECTION_KEYS[String(spec.type)] ?? "culvert"] = {
...spec,
chainage_m: pipeChainage,
};
attached += 1;
break;
}