diff --git a/B08_Quantity/B08_Quantity_Engine_Pipe.py b/B08_Quantity/B08_Quantity_Engine_Pipe.py index da11bc31..9261086c 100644 --- a/B08_Quantity/B08_Quantity_Engine_Pipe.py +++ b/B08_Quantity/B08_Quantity_Engine_Pipe.py @@ -34,10 +34,21 @@ FACILITY_PIPE = "pipe" #: 연장을 못 찾은 줄의 막힌 갈래 — 「입력하면 풀림」이 아니라 **앞 단계가 내야 하는 값**이다. BLOCKED_LENGTH_MISSING = "input_missing" +#: ⚠ **둘을 갈라 말한다** — 「[저장]을 누르면 풀리는 것」과 「눌러도 안 풀리는 것」은 +#: 사용자가 할 일이 다르다. 뒤엣것에 앞 문구를 쓰면 눌러 보고 안 되어 헤맨다 +#: (2026-09-08 실측: 관 9개 중 3개가 **횡단 행 자체가 없는** 자리였다). NOTE_LENGTH_MISSING = ( "관 연장이 아직 정본에 없습니다 — 횡단설계에서 [저장]을 한 번 누르면 " "그 측점의 관 길이가 남고 값이 섭니다" ) +NOTE_SECTION_MISSING = ( + "그 측점의 횡단 자체가 없습니다 — 관은 놓였는데 횡단이 안 만들어진 자리라 " + "[저장]으로는 안 풀립니다. 횡단설계에서 그 측점이 서야 합니다" +) +#: 관 자리에 횡단이 있는지 볼 때의 허용 오차. **아주 좁게** — 옆 측점을 「있다」로 세면 +#: 거짓 안내가 된다. 길이 찾기(0.5m)보다 좁은 것은 뜻이 다르기 때문이다. +SECTION_MATCH_TOLERANCE_M = 0.05 + NOTE_KIND_DEFAULT = "관종을 안 정해 기본값({kind})으로 섰습니다 — 정하면 공종이 갈립니다" NOTE_KIND_UNKNOWN = "「{kind}」은(는) 아는 관종이 아니라 공종을 못 골랐습니다" @@ -79,6 +90,7 @@ def build_rows( pipe_points: list[dict[str, Any]], designs: list[dict[str, Any]], mapping: dict[str, Any] | None = None, + section_chainages: list[float] | None = None, ) -> dict[str, Any]: """관 줄 목록. **값이 서는 줄도, 못 서는 줄도** 함께 낸다. @@ -92,6 +104,9 @@ def build_rows( diameter_key = str(table.get("diameter_option_key") or "pipe_diameter_mm") lengths = _length_by_chainage(designs, length_key) + # 횡단이 **있는데 길이가 없는 것**과 **관 자리에 횡단이 없는 것**을 가르기 위한 목록. + # 안 주면 종전대로 「[저장]하면 풀림」 하나로만 말한다. + sections = [float(x) for x in (section_chainages or [])] rows: list[dict[str, Any]] = [] notes: list[str] = [] @@ -111,8 +126,17 @@ def build_rows( kind_note = NOTE_KIND_UNKNOWN.format(kind=stored_kind) length = _nearest(lengths, chainage) + # ⚠ **관이 놓인 그 측점**이 있는지를 본다 — 옆 측점이 있는 것은 소용없다. + # 실측(2026-09-08 `5601e828`): 관 439.55 근처에 측점 440.0 만 있었고, 0.5m 로 + # 느슨히 보면 「횡단이 있다」로 읽혀 **「[저장]하면 풀린다」는 거짓 안내**가 떴다. + # 길이는 B06 이 **관이 놓인 측점에만** 싣는다(2026-09-08 이웃 오염을 고친 뒤). + has_section = not sections or any( + abs(x - chainage) <= SECTION_MATCH_TOLERANCE_M for x in sections + ) blocked_kind = None if length else BLOCKED_LENGTH_MISSING - blocked_reason = "" if length else NOTE_LENGTH_MISSING + blocked_reason = "" + if not length: + blocked_reason = NOTE_LENGTH_MISSING if has_section else NOTE_SECTION_MISSING if code is None: blocked_kind = blocked_kind or BLOCKED_LENGTH_MISSING blocked_reason = blocked_reason or kind_note diff --git a/B08_Quantity/B08_Quantity_Router_Earthwork.py b/B08_Quantity/B08_Quantity_Router_Earthwork.py index fea10f5f..fc4aeaff 100644 --- a/B08_Quantity/B08_Quantity_Router_Earthwork.py +++ b/B08_Quantity/B08_Quantity_Router_Earthwork.py @@ -93,6 +93,8 @@ async def get_earthwork_table(project_id: UUID, route_id: int) -> JSONResponse: for row in designs if isinstance(row, dict) and (row.get("design") or {}).get("pipe_length_m") is not None ] + # 횡단이 선 측점 목록 — 관 줄이 「길이가 없음」과 「횡단 자체가 없음」을 가르는 데 쓴다. + table["section_chainages"] = [row.get("chainage_m") for row in designs if isinstance(row, dict)] table["haul"] = haul # 운반계획은 [저장]·[확정]에서 정본에 남는 값이다 — 아직 없으면 빈 표가 정직하다. table["haul_available"] = bool(plan) diff --git a/B08_Quantity/B08_Quantity_Router_Material.py b/B08_Quantity/B08_Quantity_Router_Material.py index 432e044c..ddada6c9 100644 --- a/B08_Quantity/B08_Quantity_Router_Material.py +++ b/B08_Quantity/B08_Quantity_Router_Material.py @@ -164,7 +164,11 @@ async def get_handoff(project_id: UUID) -> JSONResponse: # 준비공·사방공 — 값이 서는 줄도, 못 내는 줄도 함께 넘긴다(빼면 빠진 줄이 안 보임). preparation_table=earthwork.get("preparation"), # 배수관 — 관 정본은 `pipe_points.json`, 연장은 측점 `design.pipe_length_m` 다. - pipe_table=_pipe_table(project_root, earthwork.get("pipe_lengths") or []), + pipe_table=_pipe_table( + project_root, + earthwork.get("pipe_lengths") or [], + earthwork.get("section_chainages") or [], + ), ground_class_set=settings.get("rock_class_set"), ground_classes=rock_classes(settings), ground_methods={name: rock_method(settings, name) for name in rock_classes(settings)}, @@ -177,7 +181,11 @@ async def get_handoff(project_id: UUID) -> JSONResponse: return JSONResponse(content=handoff) -def _pipe_table(project_root: str, pipe_lengths: list[dict[str, Any]]) -> dict[str, Any]: +def _pipe_table( + project_root: str, + pipe_lengths: list[dict[str, Any]], + section_chainages: list[Any] | None = None, +) -> dict[str, Any]: """배수관 표 — 정본 셋을 읽어 잇는다. 못 읽으면 **빈 표**(줄이 안 서는 것이 정직하다). ⚠ 관 정본은 `structures.json` 이 아니라 `pipe_points.json` 이다 @@ -206,7 +214,12 @@ def _pipe_table(project_root: str, pipe_lengths: list[dict[str, Any]]) -> dict[s {"chainage_m": row.get("chainage_m"), "design": {"pipe_length_m": row.get("pipe_length_m")}} for row in pipe_lengths ] - return build_pipe_rows(points, designs, (load_mapping().pipe or {})) + return build_pipe_rows( + points, + designs, + (load_mapping().pipe or {}), + [float(x) for x in (section_chainages or []) if x is not None], + ) async def _earthwork_tables(project_id: UUID) -> dict[str, Any]: