Merge remote-tracking branch 'origin/main_laptop_1' into main_desktop_1

This commit is contained in:
2026-09-08 09:29:10 +09:00
5 changed files with 40 additions and 8 deletions
+4
View File
@@ -467,6 +467,8 @@ export interface CrossDesign {
/** 측점별 **암 절토 경사비**(1:n 의 n) — 사용자가 카드에 넣은 값(2026-09-07).
* 0 은 「표준값을 씀」이다. 계산에 쓰이는 값이 아니라 **입력을 되싣는 자리**다. */
cut_slope_ratio_user?: number;
/** 배수관 연장(m) — 기하가 **m 단위 올림까지** 끝낸 값(2026-09-08). 수량(B08)이 읽는다. */
pipe_length_m?: number;
}
export interface CrossDesignResponse {
@@ -504,6 +506,8 @@ export interface CrossSectionPatch {
display_half_width_m?: number;
/** 측점별 암 절토 경사비(2026-09-07). 0 = 표준값으로 되돌림. */
cut_slope_ratio_user?: number;
/** 배수관 연장(m) — 구조물 면적과 같은 길로 정본에 실린다(2026-09-08). */
pipe_length_m?: number;
inlet_structure?: "auto" | "revet" | "I" | "L" | "U";
basin_adjust?: {
innerWidthM: number;
+4
View File
@@ -175,6 +175,10 @@ class CrossSectionPatch(BaseModel):
fill_area_m2: float | None = Field(default=None, ge=0)
cut_soil_area_m2: float | None = Field(default=None, ge=0)
cut_rock_area_m2: float | None = Field(default=None, ge=0)
# 배수관 연장(m) — 브라우저 기하가 **m 단위 올림까지** 끝낸 값. 수량(B08)이 배수관
# 공종을 세려면 정본에 있어야 한다(2026-09-08). 계산은 서버가 같은 코드를 Node 로
# 돌려 내므로 한 벌이다(`B06_Section_Server_Calc_Node`).
pipe_length_m: float | None = Field(default=None, ge=0)
class SectionConfirmRequest(BaseModel):
@@ -52,7 +52,15 @@ ROOT = Path(__file__).resolve().parents[1]
BUNDLE = ROOT / "config" / "server_calc_node" / "B06_Section_Server_Calc_Node.js"
_NPM_SCRIPT = "build:server-calc"
# 정본에 얹는 값만 받는다 — Node 가 다른 키를 내도 설계 데이터에 흘리지 않는다.
_AREA_KEYS = ("cut_area_m2", "fill_area_m2", "cut_soil_area_m2", "cut_rock_area_m2")
# ⚠ **TS 쪽 `STRUCTURE_ROW_KEYS` 와 짝이다.** 한쪽만 늘리면 Node 가 값을 내도 여기서 조용히
# 버려진다(2026-09-08 관 길이를 더하며 실제로 걸린 자리). 시험이 두 목록을 대조한다.
_AREA_KEYS = (
"cut_area_m2",
"fill_area_m2",
"cut_soil_area_m2",
"cut_rock_area_m2",
"pipe_length_m",
)
def _mass_haul_context() -> dict[str, Any]:
+21 -5
View File
@@ -107,12 +107,20 @@ export function trimOfLayouts(layouts: StoredLayouts) {
);
}
/** 정본에 얹는 면적 키 — 이 넷만 오간다. */
export const STRUCTURE_AREA_KEYS = [
/**
* 정본에 얹는 키 — 구조물이 선 측점에서만 나오는 값들.
*
* 면적 넷에 **관 길이**를 더했다(2026-09-08). 관 길이는 브라우저 기하가 **m 단위 올림까지**
* 끝낸 값인데 정본에 없어 **수량이 배수관 연장을 못 냈다**(B08 창 보고). 계산을 서버로
* 옮기거나 새로 짜지 않고, **이미 서버가 Node 로 돌리는 이 다리에 한 줄 더 실었다**
* (CLAUDE.md 5장 — 계산은 한 벌).
*/
export const STRUCTURE_ROW_KEYS = [
"cut_area_m2",
"fill_area_m2",
"cut_soil_area_m2",
"cut_rock_area_m2",
"pipe_length_m",
] as const;
/** 측점 하나의 폐회로 면적 — 구조물이 없으면 null(표준 계산값이 이미 맞다). */
@@ -122,9 +130,16 @@ function areaRowOf(
): Record<string, number> | null {
const layouts = computeStoredLayouts(section, sections);
if (!layouts) return null;
// 관 길이는 면적과 **따로** 낸다 — 폐회로 면적을 못 내는 측점(설계선이 모자란 자리)에도
// 관은 서 있고, 수량은 그 길이를 필요로 한다(2026-09-08).
const pipeLengthM = layouts.culvert?.pipe?.lengthM;
const pipeRow: Record<string, number> | null =
typeof pipeLengthM === "number" && pipeLengthM > 0
? { chainage_m: section.chainage_m, pipe_length_m: Number(pipeLengthM.toFixed(4)) }
: null;
const trim = trimOfLayouts(layouts);
const design = layouts.design;
if (!trim || !Array.isArray(design.design_line) || design.design_line.length < 2) return null;
if (!trim || !Array.isArray(design.design_line) || design.design_line.length < 2) return pipeRow;
const ground = section.samples
.filter((sample) => sample.valid !== false && typeof sample.elevation_m === "number")
.map((sample) => ({ offset: sample.offset_m ?? 0, elevation: sample.elevation_m as number }))
@@ -136,9 +151,10 @@ function areaRowOf(
rockBoundaryOffsetM:
typeof design.rock_boundary_offset_m === "number" ? design.rock_boundary_offset_m : null,
});
if (!areas) return null;
if (!areas) return pipeRow;
const round = (value: number): number => Number(value.toFixed(4));
const row: Record<string, number> = {
...(pipeRow ?? {}),
chainage_m: section.chainage_m,
cut_area_m2: round(areas.cutAreaM2),
fill_area_m2: round(areas.fillAreaM2),
@@ -173,7 +189,7 @@ export function applyStructureAreaRows(
const design = sections.find((item) => item.chainage_m === row.chainage_m)?.design as
Record<string, unknown> | undefined;
if (!design) continue;
for (const key of STRUCTURE_AREA_KEYS) {
for (const key of STRUCTURE_ROW_KEYS) {
if (typeof row[key] === "number") design[key] = row[key];
}
}
+2 -2
View File
@@ -29,7 +29,7 @@ import {
} from "@util/common_util_cross_berm";
import {
applyStructureAreaRows,
STRUCTURE_AREA_KEYS,
STRUCTURE_ROW_KEYS,
structureAreaRows,
} from "./B06_Section_Structure_Layouts";
import { crossDesignChoices } from "./B06_Section_Cross_Design_Session";
@@ -393,7 +393,7 @@ export function collectSectionEdits(ctx: SectionPersistContext): {
const design = section.design as Record<string, unknown> | undefined;
if (!design) continue;
let patch: CrossSectionPatch | null = null;
for (const key of STRUCTURE_AREA_KEYS) {
for (const key of STRUCTURE_ROW_KEYS) {
if (typeof design[key] !== "number") continue;
patch = patch ?? patchFor(section.chainage_m);
patch[key] = design[key] as number;