feat(b06,b08): 다단 추가 기슭막이 수량 — 서버 재계산이 선 다단 벽 목록(design.extra_walls)을 남기고 B08 이 줄을 세움 · 높이 안 적은 단은 미확정(④)
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016VBGFXB9AbJBwXP19z75Qq
This commit is contained in:
@@ -14,8 +14,10 @@
|
||||
* 실행: node <번들> <입력.json> <출력.json>
|
||||
* 입력 { detail: 종횡단 상세(API와 같은 꼴), context: { earthwork_conversion,
|
||||
* natural_spoil_min_ground_slope, haul_equipment_limits } }
|
||||
* 출력 { areas: [{ chainage_m, cut_area_m2, … }], mass_haul: {…} | null }
|
||||
* 출력 { areas: [{ chainage_m, cut_area_m2, … }], mass_haul: {…} | null,
|
||||
* extra_walls: [{ chainage_m, extra_walls: [선 다단 벽…] }] }
|
||||
* — areas 는 **구조물 트림이 있는 측점만**. 나머지는 표준 계산값이 이미 맞다.
|
||||
* — extra_walls 는 관 주인 측점마다(빈 목록 포함) · B08 이 다단 기슭막이 줄을 세움(④).
|
||||
* 끝 코드: 0 성공 / 2 인자 오류
|
||||
* ========================================================================== */
|
||||
|
||||
@@ -23,7 +25,11 @@ import { readFileSync, writeFileSync } from "node:fs";
|
||||
import { computeMassHaul, massHaulPayload } from "@util/common_util_mass_haul";
|
||||
import { computeHaulPlan, haulPlanPayload } from "@util/common_util_mass_haul_balance";
|
||||
import type { CrossSection, SectionDetailResponse } from "./B06_Section_Api_Fetch";
|
||||
import { applyStructureAreaRows, structureAreaRows } from "./B06_Section_Structure_Layouts";
|
||||
import {
|
||||
applyStructureAreaRows,
|
||||
extraWallRows,
|
||||
structureAreaRows,
|
||||
} from "./B06_Section_Structure_Layouts";
|
||||
|
||||
interface ServerCalcInput {
|
||||
detail?: SectionDetailResponse;
|
||||
@@ -112,4 +118,7 @@ const massHaul = result
|
||||
? massHaulPayload(result, plan ? { haul_plan: haulPlanPayload(plan) } : null)
|
||||
: null;
|
||||
|
||||
writeFileSync(outputPath, JSON.stringify({ areas, mass_haul: massHaul }));
|
||||
// 선 다단 벽 목록(④) — 관 연장처럼 기하가 세운 결과를 정본에 남겨 B08 이 줄을 세움.
|
||||
const extraWalls = extraWallRows(sections);
|
||||
|
||||
writeFileSync(outputPath, JSON.stringify({ areas, mass_haul: massHaul, extra_walls: extraWalls }));
|
||||
|
||||
@@ -245,7 +245,13 @@ async def _recompute(project_id: UUID | str, route_id: int) -> int:
|
||||
output = {}
|
||||
rows = output.get("areas")
|
||||
mass_haul = output.get("mass_haul")
|
||||
if not fixed and not rows and not mass_haul:
|
||||
# 선 다단 벽 목록(④) — 목록째 얹음(수가 아니라 `_AREA_KEYS` 로는 못 거름). 주인 측점엔 빈 목록도.
|
||||
extra_walls = [
|
||||
(float(row["chainage_m"]), {"extra_walls": row["extra_walls"]})
|
||||
for row in output.get("extra_walls") or []
|
||||
if isinstance(row, dict) and isinstance(row.get("extra_walls"), list)
|
||||
]
|
||||
if not fixed and not rows and not mass_haul and not extra_walls:
|
||||
return 0
|
||||
|
||||
updated = 0
|
||||
@@ -281,6 +287,10 @@ async def _recompute(project_id: UUID | str, route_id: int) -> int:
|
||||
updated = await merge_cross_section_designs(
|
||||
connection, route_id=route_id, entries=area_entries, replace=False
|
||||
)
|
||||
if extra_walls:
|
||||
await merge_cross_section_designs(
|
||||
connection, route_id=route_id, entries=extra_walls, replace=False
|
||||
)
|
||||
if isinstance(mass_haul, dict):
|
||||
await merge_longitudinal_section_data(
|
||||
connection, route_id=route_id, data_patch={"mass_haul": mass_haul}
|
||||
|
||||
@@ -13,9 +13,14 @@
|
||||
import { computeStructureAreas } from "@util/common_util_cross_structure_areas";
|
||||
import type { CrossSection } from "./B06_Section_Api_Fetch";
|
||||
import { computeBoxLayout, DEFAULT_BOX_SIDE_ADJUST } from "./B06_Section_UI_Cross_Box_Geom";
|
||||
import { REVET_EMBED_DEPTH_M } from "./B06_Section_UI_Cross_Culvert_Const";
|
||||
import { DEFAULT_BASIN_ADJUST, ZERO_ADJUST } from "./B06_Section_UI_Cross_Culvert_Types";
|
||||
import type { WallAdjust } from "./B06_Section_UI_Cross_Culvert_Types";
|
||||
import { computeCardCulvert, culvertLinkFor } from "./B06_Section_UI_Cross_Culvert_Wire";
|
||||
import type { WallAdjust, WallLayout } from "./B06_Section_UI_Cross_Culvert_Types";
|
||||
import {
|
||||
computeCardCulvert,
|
||||
culvertLinkFor,
|
||||
tierSpanOf,
|
||||
} from "./B06_Section_UI_Cross_Culvert_Wire";
|
||||
import type {
|
||||
ExtraWallControl,
|
||||
InletStructureControl,
|
||||
@@ -219,6 +224,62 @@ export function structureAreaRows(
|
||||
.filter((row): row is Record<string, number> => !!row);
|
||||
}
|
||||
|
||||
/** 선 다단 벽 한 매 — B08 이 줄을 세우는 값(파이썬 `B08_Quantity_Engine_Pipe.facility_structures`). */
|
||||
export interface BuiltExtraWall {
|
||||
key: string;
|
||||
side: "outlet" | "basin";
|
||||
form: string;
|
||||
/** 순수 높이(m) — 바닥~상단(근입 0.5 포함). 지형에 맞춰 선 값. */
|
||||
height_m: number;
|
||||
/** 사용자가 높이·형태를 적었나 — 안 적었으면 B08 이 미확정으로 셈. */
|
||||
height_set: boolean;
|
||||
form_set: boolean;
|
||||
before_m: number;
|
||||
after_m: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* 관(·독립 기슭막이) 주인 측점마다 **실제로 선** 다단 벽 목록(④, 2026-09-14).
|
||||
*
|
||||
* 단 수·높이는 지형이 정해 요청보다 적게 설 수 있음 — 그래서 요청 칸(`extra_wall_counts`)이 아니라
|
||||
* 기하가 세운 결과를 남김(관 연장 `pipe_length_m` 과 같은 길). 주인 측점에는 빈 목록도 실어 옛 값을 지움.
|
||||
*/
|
||||
export function extraWallRows(
|
||||
sections: readonly CrossSection[],
|
||||
): Array<{ chainage_m: number; extra_walls: BuiltExtraWall[] }> {
|
||||
const rows: Array<{ chainage_m: number; extra_walls: BuiltExtraWall[] }> = [];
|
||||
for (const section of sections) {
|
||||
if (!section.culvert) continue;
|
||||
const owner = pipeOwnerChainage(section, sections);
|
||||
if (owner !== null && Math.abs(owner - section.chainage_m) > CHAINAGE_TOLERANCE_M) continue;
|
||||
const culvert = computeStoredLayouts(section, sections)?.culvert;
|
||||
if (!culvert) continue;
|
||||
const walls: BuiltExtraWall[] = [];
|
||||
const built: Array<["outlet" | "basin", WallLayout[], WallAdjust[]]> = [
|
||||
["outlet", culvert.extraWalls, culvert.revetShift.extras],
|
||||
["basin", culvert.basinExtras, culvert.revetShift.basinExtras],
|
||||
];
|
||||
for (const [side, list, applied] of built) {
|
||||
list.forEach((wall, index) => {
|
||||
const key = `${side === "basin" ? "bextra" : "extra"}${index}`;
|
||||
const span = tierSpanOf(section, key);
|
||||
walls.push({
|
||||
key,
|
||||
side,
|
||||
form: wall.form ?? "",
|
||||
height_m: Number((wall.height + REVET_EMBED_DEPTH_M).toFixed(3)),
|
||||
height_set: applied[index]?.h != null,
|
||||
form_set: section.design?.revet_adjust?.[key]?.m != null,
|
||||
before_m: span.beforeM,
|
||||
after_m: span.afterM,
|
||||
});
|
||||
});
|
||||
}
|
||||
rows.push({ chainage_m: section.chainage_m, extra_walls: walls });
|
||||
}
|
||||
return rows;
|
||||
}
|
||||
|
||||
/** 낸 면적을 측점 자료에 도로 얹는다 — 유토곡선이 고쳐진 면적 위에서 쌓이도록. */
|
||||
export function applyStructureAreaRows(
|
||||
sections: readonly CrossSection[],
|
||||
|
||||
Reference in New Issue
Block a user