- 옮김: B04 2 · B05 23 · B06 11 · A00_Common/design 13 (9,299 줄) · 이미 logic/ 에 있던 16 파일 그대로 - 안 옮김: 묶음 입구(package.json build:*) · 파이썬 · 순수지만 계산 아닌 8 (보기 결선 · 콜백 · API 타입 · 갈아끼움 알림판) - import 266 줄(142 파일) · 시험 · 주석 · 짝 표시 문자열 경로 고침 · B05 · B06 CLAUDE.md 짝 줄 logic/ - 시험 도우미 새 경로: 가짜 require 열쇠 · B05()/B06() 부름 · 형제 import 자리 · tsc 묶어 돌리는 시험 · 정규식 · rglob - design_view_cross 줄 한도 시험 685 → 688 (긴 import 경로 줄바꿈 3줄) - 확인: typecheck 0 · 서버 묶음 넷(corridor · server-calc · b07-extent · b07-note) 다시 지어 코드 같음(//#region 경로 주석만 다름) · 관련 시험 270 파일 — 남은 실패 19 는 옮기기 전과 같음 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014utrjfoW831Vc5wJUd13kL
150 lines
5.9 KiB
TypeScript
150 lines
5.9 KiB
TypeScript
/* =============================================================================
|
|
* B05_Profile_Pipeline_Detail.ts
|
|
* 계산 묶음(PLAN 346-1 · 346-2) 출력 → 종단도 · 유토곡선이 읽는 상세 꼴(`SectionDetailResponse`).
|
|
* 노선 초안 동안 종단도가 저장 노선 상세 대신 이 상세를 그림(지반 · 측점 · 횡단 면적이 새 노선 것).
|
|
*
|
|
* uphillSide 측점 상단측(좌 · 우 지반 평균이 높은 쪽) — 서버 단면 유형 기본값(`{쪽}_cut`) 재료.
|
|
* ⚠ ① 짝 — `B05_Profile_Engine_Sections_Core.py` `uphill_side` · 거울 시험
|
|
* `resources/tester/test_346_2_pipeline_cross_page.py`.
|
|
* crossWithUphill cross 단계 입력에 단면 유형 기본값(상단측)을 얹음 — 서버 노선 저장이 단면 유형 ·
|
|
* 측구 방향을 새 지형으로 정하는 것과 같은 규칙(넘기는 칸 = 부르는 쪽 입력).
|
|
* draftSectionDetail ground · profile · cross → 상세(측점 · 지반 · 횡단 설계 · 얹힌 시설 제원).
|
|
*
|
|
* 순수 — 화면 · fetch · 세션 없음.
|
|
* ========================================================================== */
|
|
|
|
import type {
|
|
CrossSection,
|
|
SectionDetailResponse,
|
|
SectionStation,
|
|
} from "../../B06_Section/B06_Section_Api_Types";
|
|
import type {
|
|
CrossOutput,
|
|
GroundOutput,
|
|
PipelineStation,
|
|
} from "../../A00_Common/design/logic/design_pipeline_types";
|
|
import type { ProfileAlignment } from "./B05_Profile_UI_Profile_Alignment";
|
|
import { stationLabel } from "../../common_util/common_util_station";
|
|
import { stationIdOf } from "./B05_Profile_Pipeline_Grade";
|
|
|
|
/** 측점에 얹힌 시설 제원(저장 상세에서 측점 열쇠로 옮김) — 낡음 판정 · 그림이 읽음. */
|
|
export type SectionSpecs = Partial<
|
|
Pick<
|
|
CrossSection,
|
|
| "structure"
|
|
| "culvert"
|
|
| "ford"
|
|
| "box"
|
|
| "ford_pavement"
|
|
| "revetment"
|
|
| "pavement"
|
|
| "underdrain"
|
|
>
|
|
>;
|
|
|
|
/** cross 단계 입력 꼴(`B06_Section_Cross_Stage` `CrossStageInput`) — 칸은 그 단계 몫. */
|
|
export interface CrossInput {
|
|
standard: unknown;
|
|
byKey: Record<string, Record<string, unknown>>;
|
|
}
|
|
|
|
const mean = (values: number[]): number => values.reduce((sum, v) => sum + v, 0) / values.length;
|
|
|
|
/** 상단측 — 좌 · 우 유효 지반 평균이 높은 쪽 · 한쪽이 비거나 차이가 1e-6 안이면 null(서버와 같음). */
|
|
export function uphillSide(station: PipelineStation): "left" | "right" | null {
|
|
const valid = (side: "left" | "right"): number[] =>
|
|
(station.ground?.[side] ?? [])
|
|
.filter((sample) => sample.offsetM > 1e-9 && sample.z !== null && Number.isFinite(sample.z))
|
|
.map((sample) => sample.z as number);
|
|
const left = valid("left");
|
|
const right = valid("right");
|
|
if (!left.length || !right.length) return null;
|
|
const gap = mean(left) - mean(right);
|
|
if (Math.abs(gap) <= 1e-6) return null;
|
|
return gap > 0 ? "left" : "right";
|
|
}
|
|
|
|
/** 측점마다 단면 유형 기본값(상단측 깎기 · 모르면 좌) — 입력이 정한 단면 유형은 그대로. */
|
|
export function crossWithUphill(
|
|
input: CrossInput | null,
|
|
stations: ReadonlyArray<PipelineStation>,
|
|
): CrossInput | null {
|
|
if (!input) return null;
|
|
const byKey: CrossInput["byKey"] = {};
|
|
for (const station of stations)
|
|
byKey[station.key] = {
|
|
sectionMode: `${uphillSide(station) ?? "left"}_cut`,
|
|
...input.byKey[station.key],
|
|
};
|
|
return { standard: input.standard, byKey };
|
|
}
|
|
|
|
const kindOf = (station: PipelineStation): SectionStation["kind"] =>
|
|
station.key === "end:BP"
|
|
? "bp"
|
|
: station.key === "end:EP"
|
|
? "ep"
|
|
: station.kind === "grid"
|
|
? "regular"
|
|
: "irregular";
|
|
|
|
/** 묶음 출력 → 상세. 저장 상세의 종단 머리(이름 · 계획선 묶음)는 그대로 · 측점 · 지반 · 횡단은 새 노선. */
|
|
export function draftSectionDetail(
|
|
saved: SectionDetailResponse,
|
|
ground: GroundOutput,
|
|
profile: ProfileAlignment,
|
|
cross: CrossOutput | null,
|
|
specs: ReadonlyMap<string, SectionSpecs>,
|
|
intervalM: number,
|
|
): SectionDetailResponse {
|
|
const sections: CrossSection[] = ground.stations.map((station) => {
|
|
const own = specs.get(station.key) ?? {};
|
|
const center = station.ground?.centerZ ?? null;
|
|
const head: SectionStation = {
|
|
station_id: stationIdOf(station.chainageM),
|
|
chainage_m: station.chainageM,
|
|
label: stationLabel(station.chainageM, intervalM),
|
|
kind: kindOf(station),
|
|
...(own.structure ? { structure: own.structure } : {}),
|
|
center_z: center,
|
|
azimuth_deg: station.azimuthDeg,
|
|
plan_radius_m: station.planRadiusM,
|
|
curve_outer_side: station.curveOuterSide,
|
|
curve_widening_m: station.curveWideningM,
|
|
center_x: station.x,
|
|
center_y: station.y,
|
|
uphill_side: uphillSide(station),
|
|
frame: { left_xy: station.left },
|
|
};
|
|
const sample = (offset: number, z: number | null) => ({
|
|
offset_m: offset,
|
|
elevation_m: z,
|
|
valid: z !== null && Number.isFinite(z),
|
|
});
|
|
const samples = [
|
|
...[...(station.ground?.right ?? [])].reverse().map((s) => sample(-s.offsetM, s.z)),
|
|
sample(0, center),
|
|
...(station.ground?.left ?? []).map((s) => sample(s.offsetM, s.z)),
|
|
];
|
|
const design = cross?.byKey[station.key]?.detail as CrossSection["design"] | undefined;
|
|
return { ...head, ...own, samples, ...(design ? { design } : {}), preview: true };
|
|
});
|
|
const { chainageM, z } = ground.longitudinal;
|
|
return {
|
|
longitudinal: {
|
|
...saved.longitudinal,
|
|
length_m: chainageM[chainageM.length - 1] ?? 0,
|
|
samples: chainageM.map((chainage, index) => ({
|
|
chainage_m: chainage,
|
|
elevation_m: z[index],
|
|
z: z[index],
|
|
valid: z[index] !== null,
|
|
})),
|
|
stations: sections.map(({ samples: _s, design: _d, preview: _p, ...head }) => head),
|
|
profile_alignment: profile,
|
|
},
|
|
cross_sections: sections,
|
|
balloon_offsets: saved.balloon_offsets,
|
|
};
|
|
}
|