auto: 2026-10-05 13:49 (EOMSANGDON-HOME)
This commit is contained in:
@@ -23,6 +23,7 @@ from fastapi import APIRouter
|
||||
from fastapi.responses import JSONResponse
|
||||
|
||||
from B04_PreProcess.B04_PreProcess_Engine_Watershed_Export import drainage_dir
|
||||
from B05_Profile.B05_Profile_Structures_Schema import structure_type_map
|
||||
from common_util.common_util_drainage_context import DrainageContext, load_drainage_context
|
||||
from common_util.common_util_drainage_detail import DrainageDetail, build_detail
|
||||
from common_util.common_util_drainage_pipes import (
|
||||
@@ -30,6 +31,7 @@ from common_util.common_util_drainage_pipes import (
|
||||
PIPE_SOURCE_USER,
|
||||
PipePoint,
|
||||
carry_facility_attributes,
|
||||
carry_indices,
|
||||
clear_pipe_points,
|
||||
load_pipe_points,
|
||||
parse_pipe_points,
|
||||
@@ -64,14 +66,40 @@ def _build(
|
||||
if points:
|
||||
_retag(detail.pipes, points)
|
||||
rebuilt = [PipePoint(chainage_m=pipe.chainage_m, source=pipe.reason) for pipe in detail.pipes]
|
||||
# 원본에 없던 자리 = 이번에 새로 놓인 관(자동 배치 · 간격 보충) — 등록부 기본값을 받을 관
|
||||
fresh = [
|
||||
pick is None
|
||||
for pick in carry_indices(
|
||||
[point.chainage_m for point in rebuilt], [point.chainage_m for point in points or []]
|
||||
)
|
||||
]
|
||||
# 계산기는 chainage만 다뤄 시설 종류·구간·옵션이 사라진다 — 원본에서 되붙인다
|
||||
# (2026-08-17 계곡 통과 시설: 배관/BOX암거/물넘이/세월교).
|
||||
carried = carry_facility_attributes(rebuilt, points or [])
|
||||
# 되붙인 뒤에 추천을 얹는다 — 순서가 뒤바뀌면 승계된 사용자 값이 추천에 덮인다.
|
||||
_apply_recommendations(detail, carried)
|
||||
_apply_new_defaults([point for point, new in zip(carried, fresh) if new])
|
||||
return detail, carried
|
||||
|
||||
|
||||
def _apply_new_defaults(points: list[PipePoint]) -> None:
|
||||
"""새로 놓인 관에만 등록부 기본값을 **빈 키에** 채움(PLAN 46-7 · 추천 뒤 — 추천 관경이 이김).
|
||||
|
||||
시설이 생길 때 기본값을 칸에 실제 값으로 넣어 두고 사용자가 고침 — 이미 있던 관의 빈 칸은 그대로
|
||||
(옛 프로젝트 빈칸 = 미확정 유지 · 사용자 결정 2026-10-05).
|
||||
"""
|
||||
types = structure_type_map()
|
||||
for point in points:
|
||||
definition = types.get(point.facility)
|
||||
if definition is None:
|
||||
continue
|
||||
options = dict(point.options or {})
|
||||
for field in definition.options:
|
||||
if field.key not in options and field.default not in (None, ""):
|
||||
options[field.key] = field.default
|
||||
point.options = options or None
|
||||
|
||||
|
||||
def _retag(pipes: list[StructureCandidate], points: list[PipePoint]) -> None:
|
||||
"""확정 좌표로 되돌아온 관에 원래의 생성 사유를 다시 붙인다."""
|
||||
by_chainage = {round(point.chainage_m, 2): point.source for point in points}
|
||||
|
||||
@@ -382,9 +382,10 @@
|
||||
"label": "월류 폭",
|
||||
"input": "number",
|
||||
"unit": "m",
|
||||
"default": null,
|
||||
"required": true,
|
||||
"phase": "detail"
|
||||
"default": 5,
|
||||
"required": false,
|
||||
"phase": "detail",
|
||||
"default_basis": "물넘이 월류 폭 기본 5m — 사용자 확정값(엔진 FORD_PAVEMENT_DEFAULT_WIDTH_M 과 같음)"
|
||||
},
|
||||
{
|
||||
"key": "ford_slope_pct",
|
||||
@@ -458,9 +459,10 @@
|
||||
"label": "월류 폭",
|
||||
"input": "number",
|
||||
"unit": "m",
|
||||
"default": null,
|
||||
"required": true,
|
||||
"phase": "detail"
|
||||
"default": 10,
|
||||
"required": false,
|
||||
"phase": "detail",
|
||||
"default_basis": "세월교 월류 폭 기본 10m — 사용자 확정값(엔진 FORD_DEFAULT_WIDTH_M 과 같음)"
|
||||
},
|
||||
{
|
||||
"key": "wing_in",
|
||||
|
||||
@@ -53,7 +53,6 @@ import {
|
||||
optionalSelect,
|
||||
OUTLET_REVET_KEYS,
|
||||
putNumber,
|
||||
REVET_COMMON_DEFAULTS,
|
||||
stepper,
|
||||
WING_IN_KEYS,
|
||||
WING_OUT_KEYS,
|
||||
@@ -246,11 +245,11 @@ export function createFacilityOptionsForm(
|
||||
root.hidden = true;
|
||||
|
||||
// ── 배관 본체 — 관종 > 관경 한 행(2026-08-17 사용자 지시 4) ──────────────
|
||||
// 관종 파형강관·관경 1000(사용자 확정 기본)은 **제안으로만** — 「안 정함」이 실제 상태(판정 ③).
|
||||
const pipeMaterial = optionalSelect("— 안 정함 (제안 파형강관) —", ["흄관", "VR관", "파형강관"]);
|
||||
// 기본값(관종 파형강관 · 관경 1000 · 등록부)은 시설이 생길 때 칸에 실제 값으로 들어옴(PLAN 46-7).
|
||||
const pipeMaterial = optionalSelect("— 안 정함 —", ["흄관", "VR관", "파형강관"]);
|
||||
const pipeDiameter = document.createElement("select");
|
||||
pipeDiameter.replaceChildren(
|
||||
new Option("— 안 정함 (제안 Ø1000) —", ""),
|
||||
new Option("— 안 정함 —", ""),
|
||||
...["800", "1000", "1200", "1500"].map((size) => new Option(`Ø${size}`, size)),
|
||||
);
|
||||
// 관보호공 날개벽 — **개소당 원단위**가 형식마다 붙박이라 고르는 칸이 있어야 물량이 선다
|
||||
@@ -264,10 +263,10 @@ export function createFacilityOptionsForm(
|
||||
|
||||
// ── 유입구 — 집수정 또는 기슭막이 택일(사용자 지시 5~9) ─────────────────
|
||||
const inletGroup = group("유입구");
|
||||
// 「안 정함」이면 기슭막이 칸을 보이되(제안) 값은 안 실음 — 기본값을 몰래 확정으로 안 바꿈(판정 ③).
|
||||
// 「안 정함」이면 기슭막이 칸을 보이되 값은 안 실음.
|
||||
const inletType = document.createElement("select");
|
||||
inletType.replaceChildren(
|
||||
new Option("— 안 정함 (제안 기슭막이) —", ""),
|
||||
new Option("— 안 정함 —", ""),
|
||||
...INLET_KINDS.map((kind) => new Option(kind.label, kind.label)),
|
||||
);
|
||||
const basinForm = optionalSelect("선택", [
|
||||
@@ -285,7 +284,7 @@ export function createFacilityOptionsForm(
|
||||
]);
|
||||
const basinMaterialField = labeled("형태", basinMaterial, "inlet_basin_material");
|
||||
// 집수정 종방향 길이 — 3D 예상형상용, 전후 동일 배분·기본 2m(2026-08-23 사용자).
|
||||
const basinLength = numberInput("0.1", "0", "제안 2");
|
||||
const basinLength = numberInput("0.1", "0");
|
||||
// 기준측점 전 · 후 — 옛 횡단 조정창에만 있던 칸을 폼으로(계획서 24-4a).
|
||||
const basinSplit = createBasinSplitFields(basinLength);
|
||||
basinSplit.onChange(() => emit());
|
||||
@@ -298,11 +297,7 @@ export function createFacilityOptionsForm(
|
||||
];
|
||||
const height = (): string =>
|
||||
String(pipeWallMinHeightM(Number(pipeDiameter.value || "1000") / 1000));
|
||||
const inletRevet = createRevetmentFields(INLET_REVET_KEYS, {
|
||||
...REVET_COMMON_DEFAULTS,
|
||||
form: "돌쌓기(찰)",
|
||||
height,
|
||||
});
|
||||
const inletRevet = createRevetmentFields(INLET_REVET_KEYS, { height });
|
||||
// 첫 행 = [구조][형태] — 구조에 따라 집수정 형태와 기슭막이 형태가 갈아 끼워진다
|
||||
// (2026-08-17 사용자 지시 3).
|
||||
// 조정창에서 옮겨 오는 행(단 수·옵션)이 붙을 자리(2026-08-29 사용자 지시 4).
|
||||
@@ -323,12 +318,8 @@ export function createFacilityOptionsForm(
|
||||
|
||||
// ── 유출구 — 구조 선택지는 기슭막이뿐이지만 양식을 맞춘다(사용자 지시 2·4) ──
|
||||
const outletGroup = group("유출구");
|
||||
const outletType = optionalSelect("— 안 정함 (제안 기슭막이) —", ["기슭막이"]);
|
||||
const outletRevet = createRevetmentFields(OUTLET_REVET_KEYS, {
|
||||
...REVET_COMMON_DEFAULTS,
|
||||
form: "돌쌓기(메)",
|
||||
height,
|
||||
});
|
||||
const outletType = optionalSelect("— 안 정함 —", ["기슭막이"]);
|
||||
const outletRevet = createRevetmentFields(OUTLET_REVET_KEYS, { height });
|
||||
outletGroup.body.append(
|
||||
grid(labeled("구조", outletType, "outlet_type"), outletRevet.formField),
|
||||
...outletRevet.rows,
|
||||
@@ -349,10 +340,10 @@ export function createFacilityOptionsForm(
|
||||
// 나눈다(2026-08-30 사용자). 설치 측이 한쪽이면 그 칸만 뜬다.
|
||||
// 좌·우가 저장 채널(유입/유출) 중 어느 쪽인지는 측점 지형이 정하므로 이름표만
|
||||
// 바꿔 단다(`setRevetSideLabels`) — 값은 언제나 그 채널 키로 오간다.
|
||||
const revetSide = optionalSelect("— 안 정함 (제안 양쪽) —", ["양쪽", "좌", "우"]);
|
||||
const revetSide = optionalSelect("— 안 정함 —", ["양쪽", "좌", "우"]);
|
||||
const revetTopRow = grid(labeled("설치 측", revetSide, "side"));
|
||||
const revetInlet = createRevetSideGroup(INLET_REVET_KEYS, "돌쌓기(메)");
|
||||
const revetOutlet = createRevetSideGroup(OUTLET_REVET_KEYS, "돌쌓기(메)");
|
||||
const revetInlet = createRevetSideGroup(INLET_REVET_KEYS);
|
||||
const revetOutlet = createRevetSideGroup(OUTLET_REVET_KEYS);
|
||||
/** 좌·우 이름표 — B06이 그 측점의 벽 방향을 알려 주면 바뀐다. 기본은 유입=좌. */
|
||||
let revetSideLabels: { inlet: "좌" | "우"; outlet: "좌" | "우" } = {
|
||||
inlet: "좌",
|
||||
@@ -387,12 +378,12 @@ export function createFacilityOptionsForm(
|
||||
|
||||
// ── BOX암거 — 본체 규격(프리셋 + 사용자 지정)·날개벽(유입·유출 개별) ────
|
||||
// 폭·높이 자유 입력은 "사용자 지정"을 골랐을 때만 펼친다(2026-08-17 사용자 확정).
|
||||
const boxSize = optionalSelect(`— 안 정함 (제안 ${BOX_SIZE_PRESETS[0][0]}) —`, [
|
||||
const boxSize = optionalSelect("— 안 정함 —", [
|
||||
...BOX_SIZE_PRESETS.map(([label]) => label),
|
||||
BOX_SIZE_CUSTOM,
|
||||
]);
|
||||
const boxWidth = numberInput("0.1", "0", `제안 ${BOX_SIZE_PRESETS[0][1]}`);
|
||||
const boxHeight = numberInput("0.1", "0", `제안 ${BOX_SIZE_PRESETS[0][2]}`);
|
||||
const boxWidth = numberInput("0.1", "0");
|
||||
const boxHeight = numberInput("0.1", "0");
|
||||
const boxCustomRow = grid(
|
||||
labeled("본체 폭 (m)", boxWidth, "body_width_m"),
|
||||
labeled("본체 높이 (m)", boxHeight, "body_height_m"),
|
||||
@@ -415,33 +406,14 @@ export function createFacilityOptionsForm(
|
||||
boxWidth.value = String(preset[1]);
|
||||
boxHeight.value = String(preset[2]);
|
||||
}
|
||||
// 「안 정함」도 폭·높이 칸을 접음 — 값이 없는 상태 그대로(제안은 빈 보기 이름).
|
||||
// 「안 정함」도 폭·높이 칸을 접음 — 값이 없는 상태 그대로.
|
||||
boxCustomRow.hidden = boxSize.value !== BOX_SIZE_CUSTOM;
|
||||
}
|
||||
|
||||
// ── 물넘이·세월교 — 월류 폭·높이·경사·두께·길이·련 수(`_Ford` · 기본값은 제안으로만).
|
||||
// ── 물넘이·세월교 — 월류 폭·높이·경사·두께·길이·련 수(`_Ford`).
|
||||
// 기본값(등록부)은 시설이 생길 때 칸에 실제 값으로 들어옴 — 제안값 단추 없음(PLAN 46-7).
|
||||
const ford = createFordFields(() => emit());
|
||||
|
||||
// [제안값 넣기] — 누른 때만 빈 칸에 제안값(관종·관경·구조·집수정 길이·설치 측·BOX 규격·날개벽·월류).
|
||||
// 벽 칸(기슭막이)은 벽마다 단추가 따로 있음. 기본값을 몰래 확정으로 바꾸지 않는다(2026-09-14 판정 ③).
|
||||
const suggest = document.createElement("button");
|
||||
suggest.type = "button";
|
||||
suggest.className = "b05-structure__suggest";
|
||||
suggest.textContent = "제안값 넣기";
|
||||
suggest.addEventListener("click", () => {
|
||||
if (!pipeMaterial.value) pipeMaterial.value = "파형강관";
|
||||
if (!pipeDiameter.value) pipeDiameter.value = "1000";
|
||||
if (!inletType.value) inletType.value = INLET_KINDS[0].label;
|
||||
if (!outletType.value) outletType.value = "기슭막이";
|
||||
if (!basinLength.value) basinLength.value = "2";
|
||||
if (!revetSide.value) revetSide.value = "양쪽";
|
||||
if (current === "box_culvert" && !boxSize.value) boxSize.value = BOX_SIZE_PRESETS[0][0];
|
||||
wingInFields.fillSuggested();
|
||||
wingOutFields.fillSuggested();
|
||||
if (current === "ford_pavement" || current === "ford_bridge") ford.fillSuggested(current);
|
||||
emit();
|
||||
});
|
||||
|
||||
// 10-A ⑲ 저장 규칙을 폼 머리에 드러냄 — 병합은 `_Drainage_Facility_Merge`(2026-09-14 브레인 판정).
|
||||
const saveNote = document.createElement("p");
|
||||
saveNote.className = "b05-structure__owner-note";
|
||||
@@ -451,7 +423,6 @@ export function createFacilityOptionsForm(
|
||||
// 세월교·물넘이 항목은 **관종·관경 바로 다음**에 둔다(2026-08-30 사용자 지시 2) —
|
||||
// 월류 폭·높이 → 바닥 경사 → 수량 → 개략 단면 결과. 다른 시설에서는 전부 숨는다.
|
||||
root.append(
|
||||
grid(suggest),
|
||||
saveNote,
|
||||
pipeRow,
|
||||
wingRow,
|
||||
@@ -593,7 +564,7 @@ export function createFacilityOptionsForm(
|
||||
}
|
||||
const text = (key: string): string =>
|
||||
options[key] !== undefined ? String(options[key]) : "";
|
||||
// ⚠ 저장된 값만 칸에 — 없으면 「안 정함」 · 제안은 빈 보기 이름·회색 글씨(2026-09-14 판정 ③).
|
||||
// 받은 값만 칸에 — 없으면 「안 정함」(새 시설은 부르는 쪽이 등록부 기본값을 실어 줌 · 46-7).
|
||||
pipeMaterial.value = text("pipe_kind");
|
||||
pipeDiameter.value = text("pipe_diameter_mm");
|
||||
// 병합 목록: 저장된 정본 둘(inlet_type·inlet_structure)로 라벨을 되찾는다.
|
||||
@@ -616,7 +587,7 @@ export function createFacilityOptionsForm(
|
||||
inletRevet.write(options);
|
||||
outletRevet.write(options);
|
||||
// 저장된 폭·높이가 프리셋과 맞으면 그 프리셋을, 아니면 "사용자 지정"을 고른다.
|
||||
// 값이 없으면 「안 정함」(첫 프리셋 2.0×2.0 은 제안으로만).
|
||||
// 값이 없으면 「안 정함」.
|
||||
const savedWidth = Number.parseFloat(text("body_width_m"));
|
||||
const savedHeight = Number.parseFloat(text("body_height_m"));
|
||||
const hasSaved =
|
||||
|
||||
@@ -212,27 +212,13 @@ export interface RevetmentKeys {
|
||||
after?: string;
|
||||
}
|
||||
|
||||
/** 부위별 **제안값** — 형태는 유입 돌쌓기(찰)·유출 돌쌓기(메), 길이 10m. 관 벽 높이는 관경 기준
|
||||
* (함수로 받아 볼 때마다 셈).
|
||||
* ⚠ 제안값은 **칸에 채우지 않고 회색 글씨로만** 보임 — 사용자가 적거나 [제안값 넣기]를 누른 때만
|
||||
* 값이 됨(2026-09-14 브레인 판정 「기본값을 몰래 확정으로 바꾸지 않는다」). 칸에 채워 두면 저장이
|
||||
* 그 값을 적어 **미확정이 저장 한 번에 확정으로 바뀜** — 사용자가 한 일이 없는데 금액이 섬. */
|
||||
/** 높이 칸 안내 — 관 벽은 비우면 관경 기준 최소 높이(엔진 `pipe_wall_min_height_m` · 함수로 받아
|
||||
* 볼 때마다 셈) · 빈 글이면 「설계자 입력」. 형태 · 길이 · 전후 기본값은 등록부가 시설이 생길 때
|
||||
* 칸에 실제 값으로 넣음(PLAN 46-7 · 제안값 단추 없음). */
|
||||
export interface RevetmentDefaults {
|
||||
form: string;
|
||||
length: string;
|
||||
height: string | (() => string);
|
||||
before: string;
|
||||
after: string;
|
||||
}
|
||||
|
||||
/** 부위와 무관한 공통 제안 치수 — 길이 10m(전 5·후 5)·높이 2.5m(독립 기슭막이 · 관 벽은 관경 기준). */
|
||||
export const REVET_COMMON_DEFAULTS = {
|
||||
length: "10",
|
||||
height: "2.5",
|
||||
before: "5",
|
||||
after: "5",
|
||||
} as const;
|
||||
|
||||
export const INLET_REVET_KEYS: RevetmentKeys = {
|
||||
form: "inlet_revet_form",
|
||||
length: "inlet_revet_length_m",
|
||||
@@ -273,16 +259,12 @@ function sabangBasisLines(): string[] {
|
||||
];
|
||||
}
|
||||
|
||||
export function createRevetSideGroup(keys: RevetmentKeys, formDefault: string): RevetSideGroup {
|
||||
export function createRevetSideGroup(keys: RevetmentKeys): RevetSideGroup {
|
||||
const box = group("기슭막이");
|
||||
const kind = document.createElement("select");
|
||||
kind.replaceChildren(new Option("기슭막이", "기슭막이"));
|
||||
// 높이는 제안 없음 — 설계자 입력(계획홍수위 + 여유고 · 2026-09-14 브레인 판정). 옛 2.5 는 근거 없어 걷음.
|
||||
const fields = createRevetmentFields(keys, {
|
||||
...REVET_COMMON_DEFAULTS,
|
||||
form: formDefault,
|
||||
height: "",
|
||||
});
|
||||
// 높이는 기본값 없음 — 설계자 입력(계획홍수위 + 여유고 · 2026-09-14 브레인 판정).
|
||||
const fields = createRevetmentFields(keys, { height: "" });
|
||||
const basis = document.createElement("p");
|
||||
basis.className = "b05-route__note";
|
||||
basis.replaceChildren(
|
||||
@@ -326,24 +308,24 @@ export function createRevetmentFields(
|
||||
keys: RevetmentKeys,
|
||||
defaults: RevetmentDefaults,
|
||||
): RevetmentFields {
|
||||
// ⚠ 제안값은 **회색 글씨(placeholder)로만** — 칸은 비워 둠(`RevetmentDefaults` 주석).
|
||||
const suggestHeight = (): string =>
|
||||
const wallFloor = (): string =>
|
||||
typeof defaults.height === "function" ? defaults.height() : defaults.height;
|
||||
const form = document.createElement("select");
|
||||
form.replaceChildren(
|
||||
new Option(`제안 ${defaults.form}`, ""),
|
||||
new Option("— 안 정함 —", ""),
|
||||
...REVET_FORMS.map((value) => new Option(value, value)),
|
||||
);
|
||||
const length = keys.length ? numberInput("0.1", "0", `제안 ${defaults.length}`) : null;
|
||||
// 제안이 없는 칸(독립 기슭막이 높이)은 「설계자 입력」 — 값을 지어내지 않음.
|
||||
const heightHint = (): string => (suggestHeight() ? `제안 ${suggestHeight()}` : "설계자 입력");
|
||||
const length = keys.length ? numberInput("0.1", "0") : null;
|
||||
// 관 벽은 비우면 관경 기준 최소 높이로 섬(엔진) · 독립 기슭막이 높이는 「설계자 입력」.
|
||||
const heightHint = (): string =>
|
||||
wallFloor() ? `비우면 관경 기준 ${wallFloor()}` : "설계자 입력";
|
||||
const height = numberInput("0.1", "0", heightHint());
|
||||
// 관 벽 높이 제안은 관경을 따라감 — 칸을 볼 때마다 다시 셈.
|
||||
// 관 벽 기준 높이는 관경을 따라감 — 칸을 볼 때마다 다시 셈.
|
||||
height.addEventListener("focus", () => (height.placeholder = heightHint()));
|
||||
// 기준측점 전/후(2026-08-23 사용자) — 구조물 패널(D군)과 같은 연동:
|
||||
// 전 + 후 = 길이, 나중에 고친 쪽이 살아남고 반대쪽이 재배분된다.
|
||||
const before = keys.before && length ? numberInput("0.1", "0", `제안 ${defaults.before}`) : null;
|
||||
const after = keys.after && length ? numberInput("0.1", "0", `제안 ${defaults.after}`) : null;
|
||||
const before = keys.before && length ? numberInput("0.1", "0") : null;
|
||||
const after = keys.after && length ? numberInput("0.1", "0") : null;
|
||||
let lastSplitEdit: "before" | "after" = "before";
|
||||
const readNumber = (input: HTMLInputElement | null): number => {
|
||||
const value = input ? Number.parseFloat(input.value) : Number.NaN;
|
||||
@@ -367,25 +349,10 @@ export function createRevetmentFields(
|
||||
const beforeField = before ? labeled("기준측점 전 (m)", stepper(before, 0.5), keys.before) : null;
|
||||
const afterField = after ? labeled("기준측점 후 (m)", stepper(after, 0.5), keys.after) : null;
|
||||
|
||||
// [제안값 넣기] — **누른 때만** 빈 칸에 제안값을 넣음(적은 칸은 안 건드림) · 층따기 단추와 같은 모양.
|
||||
const fill = document.createElement("button");
|
||||
fill.type = "button";
|
||||
fill.className = "b05-structure__suggest";
|
||||
fill.textContent = "제안값 넣기";
|
||||
fill.addEventListener("click", () => {
|
||||
if (!form.value) form.value = defaults.form;
|
||||
if (length && !length.value) length.value = defaults.length;
|
||||
if (!height.value) height.value = suggestHeight();
|
||||
if (before && !before.value) before.value = defaults.before;
|
||||
if (after && !after.value) after.value = defaults.after;
|
||||
form.dispatchEvent(new Event("change"));
|
||||
});
|
||||
|
||||
// 형태는 그룹 첫 행(구조 옆)에 따로 놓이므로 여기 행에서는 뺀다.
|
||||
const rows = [
|
||||
grid(...[lengthField, heightField].filter((item): item is HTMLLabelElement => item !== null)),
|
||||
...(beforeField && afterField ? [grid(beforeField, afterField)] : []),
|
||||
grid(fill),
|
||||
];
|
||||
|
||||
return {
|
||||
@@ -393,7 +360,7 @@ export function createRevetmentFields(
|
||||
rows,
|
||||
lengthInput: length,
|
||||
write(options) {
|
||||
// 저장된 값만 칸에 — 없으면 비워 두고 제안값은 회색 글씨로(몰래 채우지 않음).
|
||||
// 받은 값만 칸에 — 없으면 비워 둠(새 시설은 부르는 쪽이 등록부 기본값을 실어 줌 · 46-7).
|
||||
const text = (key: string): string =>
|
||||
options[key] !== undefined ? String(options[key]) : "";
|
||||
form.value = text(keys.form);
|
||||
@@ -447,8 +414,8 @@ export function createBasinSplitFields(length: HTMLInputElement): {
|
||||
read: (target: Record<string, string | number>) => void;
|
||||
onChange: (handler: () => void) => void;
|
||||
} {
|
||||
const before = numberInput("0.1", "0", "제안 1");
|
||||
const after = numberInput("0.1", "0", "제안 1");
|
||||
const before = numberInput("0.1", "0");
|
||||
const after = numberInput("0.1", "0");
|
||||
let last: HTMLInputElement = before;
|
||||
const sync = (): void => {
|
||||
const total = Number.parseFloat(length.value);
|
||||
@@ -528,10 +495,6 @@ export const WING_OUT_KEYS: WingKeys = {
|
||||
angle: "wing_out_angle_deg",
|
||||
};
|
||||
|
||||
/** 날개벽 기본 제원 — 설치 있음·짧은쪽 높이 1m·길이 2m·각도 45°
|
||||
* (2026-08-17 사용자 확정). 유입·유출이 같은 값을 쓴다. */
|
||||
const WING_DEFAULTS = { install: "있음", height: "1", length: "2", angle: "45" } as const;
|
||||
|
||||
interface WingFields {
|
||||
root: HTMLFieldSetElement;
|
||||
/** 횡단도에서 그 측 날개벽·측벽을 고를 때 칸을 세우는 자리(B06 전용, 2026-08-30).
|
||||
@@ -541,18 +504,16 @@ interface WingFields {
|
||||
syncVisibility: () => void;
|
||||
write: (options: Record<string, string | number>) => void;
|
||||
read: (target: Record<string, string | number>) => void;
|
||||
/** [제안값 넣기] — 빈 칸에만 기본 제원을 넣음. */
|
||||
fillSuggested: () => void;
|
||||
}
|
||||
|
||||
/** 날개벽 한 벌(설치 유무 + 짧은쪽 높이·길이·각도) — 유입·유출이 공유하는 조각.
|
||||
* 설치 "없음"이면 제원 칸을 접는다(기슭막이 보호공 치수와 같은 패턴).
|
||||
* ⚠ 기본 제원(있음·1·2·45, 사용자 확정)은 **제안으로만** — 칸은 비워 둠(2026-09-14 판정 ③). */
|
||||
* 기본 제원(있음·1·2·45, 사용자 확정)은 등록부 기본값 — 시설이 생길 때 칸에 실제 값(PLAN 46-7). */
|
||||
export function createWingFields(title: string, keys: WingKeys): WingFields {
|
||||
const install = optionalSelect(`— 안 정함 (제안 ${WING_DEFAULTS.install}) —`, ["있음", "없음"]);
|
||||
const height = numberInput("0.1", "0", `제안 ${WING_DEFAULTS.height}`);
|
||||
const length = numberInput("0.1", "0", `제안 ${WING_DEFAULTS.length}`);
|
||||
const angle = numberInput("1", "0", `제안 ${WING_DEFAULTS.angle}`);
|
||||
const install = optionalSelect("— 안 정함 —", ["있음", "없음"]);
|
||||
const height = numberInput("0.1", "0");
|
||||
const length = numberInput("0.1", "0");
|
||||
const angle = numberInput("1", "0");
|
||||
|
||||
// 숫자 칸은 기슭막이·집수정과 같은 [-][값][+] 묶음으로 맞춘다(2026-08-30 사용자).
|
||||
const heightField = labeled("짧은쪽 높이 (m)", stepper(height, 0.1), keys.height);
|
||||
@@ -594,12 +555,5 @@ export function createWingFields(title: string, keys: WingKeys): WingFields {
|
||||
putNumber(target, keys.length, length.value);
|
||||
putNumber(target, keys.angle, angle.value);
|
||||
},
|
||||
fillSuggested() {
|
||||
if (!install.value) install.value = WING_DEFAULTS.install;
|
||||
if (!height.value) height.value = WING_DEFAULTS.height;
|
||||
if (!length.value) length.value = WING_DEFAULTS.length;
|
||||
if (!angle.value) angle.value = WING_DEFAULTS.angle;
|
||||
syncVisibility();
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -3,10 +3,9 @@
|
||||
* 물넘이포장·세월교 칸 묶음 — 월류 폭·높이·바닥 경사·포장 두께·길이·련 수 + 개략 단면 줄.
|
||||
*
|
||||
* 서브폼 본체(`_Drainage_Facility.ts`)가 700줄에 닿아 떼어 냄(2026-09-14).
|
||||
* ⚠ 기본값을 칸에 안 채움(2026-09-14 브레인 판정 「기본값을 몰래 확정으로 바꾸지 않는다」):
|
||||
* 월류 폭 세월교 10m·물넘이 5m(사용자 확정)는 **회색 제안**, 월류 높이는 설계유량·폭으로 되짚은
|
||||
* **필요 최소 수심을 회색 제안**으로만 — 사용자가 적거나 [제안값 넣기]를 누른 때만 값.
|
||||
* 적은 높이가 필요 수심보다 낮으면 여전히 되돌림(계산값 미만은 안 받음).
|
||||
* 월류 폭 기본값(세월교 10m · 물넘이 5m · 등록부)은 시설이 생길 때 칸에 실제 값으로 들어옴
|
||||
* (PLAN 46-7 · 제안값 단추 없음). 빈 칸 안내 = 엔진이 비운 칸을 읽는 값(폭 기본 · 필요 수심).
|
||||
* 적은 높이가 필요 수심보다 낮으면 되돌림(계산값 미만은 안 받음).
|
||||
* ========================================================================== */
|
||||
|
||||
import {
|
||||
@@ -35,7 +34,6 @@ export interface FordFields {
|
||||
sync: () => void;
|
||||
write: (kind: FordKind, options: Record<string, string | number>) => void;
|
||||
read: (kind: FordKind, target: Record<string, string | number>) => void;
|
||||
fillSuggested: (kind: FordKind) => void;
|
||||
}
|
||||
|
||||
export function createFordFields(emit: () => void): FordFields {
|
||||
@@ -66,14 +64,15 @@ export function createFordFields(emit: () => void): FordFields {
|
||||
/** 현재 조건(설계유량·월류 폭)의 필요 최소 수심(m). 계산 불가면 null. */
|
||||
let minDepthM: number | null = null;
|
||||
|
||||
const suggestedWidth = (kind: FordKind): number =>
|
||||
/** 폭을 비우면 엔진이 쓰는 폭(`B06_Section_Engine_Culvert.FORD_*_DEFAULT_WIDTH_M` 짝). */
|
||||
const emptyWidth = (kind: FordKind): number =>
|
||||
kind === "ford_bridge" ? FORD_BRIDGE_DEFAULT_WIDTH_M : FORD_PAVEMENT_DEFAULT_WIDTH_M;
|
||||
|
||||
function sync(): void {
|
||||
const widthM = Number.parseFloat(width.value || width.dataset.suggested || "");
|
||||
const widthM = Number.parseFloat(width.value || width.dataset.empty || "");
|
||||
const section = designFlowM3s !== null ? fordSection(designFlowM3s, widthM) : null;
|
||||
minDepthM = section ? Number(section.depthM.toFixed(2)) : null;
|
||||
height.placeholder = minDepthM !== null ? `제안 필요 수심 ${minDepthM.toFixed(2)}` : "";
|
||||
height.placeholder = minDepthM !== null ? `필요 수심 ${minDepthM.toFixed(2)} 이상` : "";
|
||||
if (designFlowM3s === null) {
|
||||
summary.textContent =
|
||||
"담당 유역의 설계유량이 아직 없습니다 — [유역 분석] 후 개략 단면이 나옵니다.";
|
||||
@@ -121,8 +120,8 @@ export function createFordFields(emit: () => void): FordFields {
|
||||
options[key] !== undefined ? String(options[key]) : "";
|
||||
count.value = kind === "ford_bridge" ? text("pipe_count") : "";
|
||||
width.value = text("ford_width_m");
|
||||
width.dataset.suggested = String(suggestedWidth(kind));
|
||||
width.placeholder = `제안 ${suggestedWidth(kind)}`;
|
||||
width.dataset.empty = String(emptyWidth(kind));
|
||||
width.placeholder = `비우면 ${emptyWidth(kind)}`;
|
||||
height.value = text("ford_height_m");
|
||||
slope.value = text("ford_slope_pct");
|
||||
thickness.value = text("thickness_cm");
|
||||
@@ -142,10 +141,5 @@ export function createFordFields(emit: () => void): FordFields {
|
||||
const pipes = Number.parseInt(count.value, 10);
|
||||
if (Number.isFinite(pipes) && pipes > 0) target.pipe_count = pipes;
|
||||
},
|
||||
fillSuggested(kind) {
|
||||
if (!width.value) width.value = String(suggestedWidth(kind));
|
||||
sync();
|
||||
if (!height.value && minDepthM !== null) height.value = minDepthM.toFixed(2);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -61,9 +61,8 @@ interface OptionShape {
|
||||
}
|
||||
|
||||
/**
|
||||
* 등록부 옵션 입력 한 칸 — **저장된 값만 칸에 넣고, 등록부 기본값은 제안으로만** 보임
|
||||
* (숫자·글 칸은 회색 글씨, 고르기 칸은 빈 보기 이름). 2026-09-14 브레인 판정 「기본값을 몰래
|
||||
* 확정으로 바꾸지 않는다」 — 칸에 채워 두면 저장이 그 값을 적어 사용자가 한 일 없이 확정이 됨.
|
||||
* 등록부 옵션 입력 한 칸 — **받은 값만 칸에 넣음**. 새 구조물은 부르는 쪽이 등록부 기본값을 값으로
|
||||
* 실어 줌(PLAN 46-7 · 제안값 단추 없음) · 옛 기록의 빈 칸은 빈 칸 그대로(미확정).
|
||||
* 고르기 칸은 늘 빈 보기를 둠 — 첫 보기를 미리 고르지 않음.
|
||||
*/
|
||||
export function optionControl(
|
||||
@@ -71,15 +70,10 @@ export function optionControl(
|
||||
stored: string | number | undefined,
|
||||
): HTMLInputElement | HTMLSelectElement {
|
||||
const value = stored === undefined || stored === null ? "" : String(stored);
|
||||
const suggested = option.default === null || option.default === "" ? "" : String(option.default);
|
||||
// 툴팁 — 표가 아직 안 읽는 칸이면 그 사유가 먼저(넣어도 안 바뀜을 알아야 함), 아니면 비울 때의 뜻.
|
||||
const tip = option.not_in_table ?? option.empty_means ?? "";
|
||||
if (option.input === "select") {
|
||||
const blank = suggested
|
||||
? `— 안 정함 (제안 ${suggested}) —`
|
||||
: option.required
|
||||
? "— 선택 —"
|
||||
: "— 안 정함 —";
|
||||
const blank = option.required ? "— 선택 —" : "— 안 정함 —";
|
||||
const element = select([["", blank], ...option.choices.map((c) => [c, c] as [string, string])]);
|
||||
element.value = value;
|
||||
if (tip) element.title = tip;
|
||||
@@ -89,7 +83,7 @@ export function optionControl(
|
||||
option.input === "number" ? numberInput("0.1", "0") : document.createElement("input");
|
||||
if (option.input !== "number") element.type = "text";
|
||||
element.value = value;
|
||||
element.placeholder = suggested ? `제안 ${suggested}` : option.required ? "필수 입력" : "";
|
||||
element.placeholder = option.required ? "필수 입력" : "";
|
||||
if (tip) element.title = tip;
|
||||
const limit = option.warn_above;
|
||||
if (typeof limit === "number") {
|
||||
@@ -105,24 +99,15 @@ export function optionControl(
|
||||
return element;
|
||||
}
|
||||
|
||||
/** [제안값 넣기] — **누른 때만** 빈 칸에 등록부 기본값을 넣음(적은 칸은 안 건드림) · 층따기 단추와 같은 모양. */
|
||||
export function suggestButton(
|
||||
entries: ReadonlyArray<{ input: HTMLInputElement | HTMLSelectElement; suggested: string }>,
|
||||
): HTMLButtonElement {
|
||||
const button = document.createElement("button");
|
||||
button.type = "button";
|
||||
button.className = "b05-structure__suggest";
|
||||
button.textContent = "제안값 넣기";
|
||||
button.addEventListener("click", () => {
|
||||
const filled = entries.filter((entry) => entry.suggested && !entry.input.value);
|
||||
filled.forEach((entry) => {
|
||||
entry.input.value = entry.suggested;
|
||||
entry.input.dispatchEvent(new Event("input", { bubbles: true }));
|
||||
});
|
||||
// 반영은 한 번만 — 칸마다 저장 흐름이 돌지 않게 마지막 칸만 change 를 울림.
|
||||
filled.at(-1)?.input.dispatchEvent(new Event("change", { bubbles: true }));
|
||||
});
|
||||
return button;
|
||||
/** 새 구조물 칸에 실을 등록부 기본값(PLAN 46-7) — 기본값이 있는 칸만 · 없는 칸은 빈 칸(미확정). */
|
||||
export function optionDefaults(
|
||||
options: ReadonlyArray<{ key: string; default: string | number | null }>,
|
||||
): Record<string, string | number> {
|
||||
return Object.fromEntries(
|
||||
options
|
||||
.filter((option) => option.default !== null && option.default !== "")
|
||||
.map((option) => [option.key, option.default as string | number]),
|
||||
);
|
||||
}
|
||||
|
||||
/** 측점번호 + 잔여거리 두 칸 묶음. */
|
||||
|
||||
@@ -22,7 +22,7 @@ import {
|
||||
} from "./B05_Profile_Api_Structures";
|
||||
import type { PipeFacility } from "../B04_PreProcess/B04_PreProcess_Api_Fetch";
|
||||
import { type FacilityAttributes } from "./B05_Profile_UI_Drainage_Facility";
|
||||
import { field } from "./B05_Profile_UI_Structures_Fields";
|
||||
import { field, optionDefaults } from "./B05_Profile_UI_Structures_Fields";
|
||||
import { renderOptionFields as renderOptionFieldsInto } from "./B05_Profile_UI_Structures_Panel_Options";
|
||||
import { bindSummaryPickers } from "./B05_Profile_UI_Structures_Groups";
|
||||
import { buildStructuresForm } from "./B05_Profile_UI_Structures_Form";
|
||||
@@ -239,11 +239,11 @@ export function createStructuresSection(
|
||||
|
||||
/** 종류에 맞춰 부속 옵션 서브폼을 켠다(계곡 통과 시설·독립 기슭막이, 값은 인자로). */
|
||||
function syncFacilityForm(
|
||||
options: Record<string, string | number> = {},
|
||||
options?: Record<string, string | number>,
|
||||
designFlow: number | null = null,
|
||||
): void {
|
||||
const kind = facilityFormKind(currentType());
|
||||
facilityOptions.setFacility(kind, options, designFlow);
|
||||
facilityOptions.setFacility(kind, options ?? newDefaults(), designFlow);
|
||||
// 「구조물 옵션 보이기」에서 끈 옵션은 시설 폼에서도 가림(값은 옛 값 그대로 저장)
|
||||
facilityOptions.setHiddenOptions(
|
||||
kind,
|
||||
@@ -318,11 +318,16 @@ export function createStructuresSection(
|
||||
}
|
||||
|
||||
/** 타입의 옵션 스키마대로 입력 칸을 다시 그린다 — 그리는 규칙은 `_Structures_Panel_Options`. */
|
||||
function renderOptionFields(values: Record<string, string | number> = {}): void {
|
||||
renderOptionFieldsInto(optionContext, values);
|
||||
function renderOptionFields(values?: Record<string, string | number>): void {
|
||||
renderOptionFieldsInto(optionContext, values ?? newDefaults());
|
||||
regroup();
|
||||
}
|
||||
|
||||
/** 값 없이 그리면 새 구조물 — 등록부 기본값을 칸에 실제 값으로(PLAN 46-7 · 옛 기록은 받은 값만). */
|
||||
function newDefaults(): Record<string, string | number> {
|
||||
return optionDefaults(currentType()?.options ?? []);
|
||||
}
|
||||
|
||||
/** 고른 구조물군의 타입을 종류 목록에 채운다 — A군은 계곡 통과 시설(managed_by)도
|
||||
* 포함한다(추가 시 관 지점 정본으로 간다, 2026-08-17 통합). 첫 항목은 빈칸 —
|
||||
* 초기·리셋 상태를 "안 고름"으로 드러낸다. */
|
||||
@@ -639,11 +644,9 @@ export function createStructuresSection(
|
||||
endFields.write(isInterval ? chainageM + DEFAULT_INTERVAL_LENGTH_M : null, step);
|
||||
memoField.value = "";
|
||||
syncPlacementFields();
|
||||
// 기본값을 값으로 안 채움 — 칸은 비우고 제안으로만(브레인 판정 ②).
|
||||
// 새 구조물 — 등록부 기본값을 칸에 실제 값으로(PLAN 46-7 · 사용자가 고치면 됨).
|
||||
renderOptionFields();
|
||||
// BOX암거도 기본값을 안 실음 — 제안으로만(판정 ③ · 옛 「[추가]만 눌러도 제원」 걷음).
|
||||
// 비운 칸은 하류(횡단도)가 등록부 기본값으로 그림 — 저장만 안 함.
|
||||
syncFacilityForm({});
|
||||
syncFacilityForm();
|
||||
// 임시 배치도 즉시 진행(A군).
|
||||
if (type.managed_by) commitTempPipe();
|
||||
syncButtons();
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
|
||||
import { isB05Option, type StructureType } from "./B05_Profile_Api_Structures";
|
||||
import type { PipeFacility } from "../B04_PreProcess/B04_PreProcess_Api_Fetch";
|
||||
import { field, optionControl, suggestButton } from "./B05_Profile_UI_Structures_Fields";
|
||||
import { field, optionControl } from "./B05_Profile_UI_Structures_Fields";
|
||||
import { isOptionVisible } from "./B05_Profile_UI_Structures_Summary";
|
||||
|
||||
/** 패널이 쥔 값 중 옵션 그리기에 필요한 것들. 이름은 분리 전 지역변수와 같다. */
|
||||
@@ -56,12 +56,9 @@ export function renderOptionFields(
|
||||
return;
|
||||
}
|
||||
optionRow.hidden = false;
|
||||
const suggestions: Array<{ input: HTMLInputElement | HTMLSelectElement; suggested: string }> = [];
|
||||
shown.forEach((option) => {
|
||||
// ⭐ 저장된 값만 칸에 · 등록부 기본값은 제안(회색 글씨·빈 보기 이름)으로만 — 고르기 칸은
|
||||
// 첫 보기를 미리 안 고름(2026-09-14 브레인 판정 ①②, `optionControl` 주석).
|
||||
// 받은 값만 칸에 — 새 구조물은 부르는 쪽(패널)이 등록부 기본값을 `values` 로 실어 줌(PLAN 46-7).
|
||||
const input = optionControl(option, values[option.key]);
|
||||
suggestions.push({ input, suggested: String(option.default ?? "") });
|
||||
const label = `${option.unit ? `${option.label} (${option.unit})` : option.label}${option.not_in_table ? " · 표에 안 쓰임" : ""}`;
|
||||
// `enabled:false` 는 칸을 남기고 잠그기만 한다 — 값은 기본값이 그대로 저장된다
|
||||
// (고를 수 없는 칸이라 제안이 아니라 프로그램 값).
|
||||
@@ -113,9 +110,6 @@ export function renderOptionFields(
|
||||
input.addEventListener("change", () => refresh(true));
|
||||
}
|
||||
});
|
||||
if (suggestions.some((entry) => entry.suggested)) {
|
||||
optionRow.append(suggestButton(suggestions));
|
||||
}
|
||||
ctx.syncOptionLock();
|
||||
ctx.syncRangeDisplay();
|
||||
}
|
||||
|
||||
@@ -221,11 +221,6 @@
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
/* 근거 줄로 옆 칸이 길어져도 [제안값 넣기]가 세로로 늘지 않게. */
|
||||
.b05-structure__suggest {
|
||||
align-self: start;
|
||||
}
|
||||
|
||||
/* 시작·기준·종료 측점 = 3행. 한 행은 [라벨][측점][+거리] 가로 배치
|
||||
* (2026-08-17 사용자 지시 2). */
|
||||
.b05-structure__position-row {
|
||||
|
||||
@@ -0,0 +1,86 @@
|
||||
"""새 구조물 기본값 — 「제안값 넣기」 걷음 · 생길 때 등록부 기본값을 칸에 실제 값으로 (PLAN 46-7).
|
||||
|
||||
못박는 것
|
||||
- 자동 배치로 새로 놓인 관 = 등록부 기본값을 빈 키에 채움 · 추천 관경이 기본값을 이김.
|
||||
- 이미 있던 관(원본에 있던 자리)의 빈 칸은 그대로(옛 프로젝트 빈칸 = 미확정 유지).
|
||||
- 월류 폭 기본값(세월교 10 · 물넘이 5)은 등록부에 · 엔진 상수와 같음.
|
||||
- 화면: 제안값 단추 · 「제안 …」 글이 없음 · 값 없이 그리면 등록부 기본값(`newDefaults`).
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
from B04_PreProcess import B04_PreProcess_Router_Basins as basins
|
||||
from B05_Profile.B05_Profile_Structures_Schema import structure_type_map
|
||||
from B06_Section.B06_Section_Engine_Culvert import (
|
||||
FORD_DEFAULT_WIDTH_M,
|
||||
FORD_PAVEMENT_DEFAULT_WIDTH_M,
|
||||
)
|
||||
from common_util.common_util_drainage_pipes import PipePoint
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[2]
|
||||
|
||||
|
||||
def _defaults(type_id: str) -> dict:
|
||||
return {
|
||||
o.key: o.default
|
||||
for o in structure_type_map()[type_id].options
|
||||
if o.default not in (None, "")
|
||||
}
|
||||
|
||||
|
||||
def test_새_관만_기본값_빈_키에만():
|
||||
fresh = PipePoint(chainage_m=10.0, source="stream", options={"pipe_diameter_mm": 800})
|
||||
basins._apply_new_defaults([fresh])
|
||||
expected = {**_defaults("pipe"), "pipe_diameter_mm": 800} # 추천(먼저 들어간 값)이 이김
|
||||
assert fresh.options == expected
|
||||
assert fresh.options["pipe_kind"] == "파형강관" and fresh.options["inlet_type"] == "기슭막이"
|
||||
box = PipePoint(chainage_m=20.0, source="stream", facility="box_culvert")
|
||||
basins._apply_new_defaults([box])
|
||||
assert box.options["body_width_m"] == 2.0 and box.options["wing_in"] == "있음"
|
||||
|
||||
|
||||
def test_원본에_있던_관은_빈칸_그대로(monkeypatch):
|
||||
"""`_build` — 원본 목록에 있던 자리는 기본값을 안 받고, 새 자리만 받음."""
|
||||
|
||||
class Pipe:
|
||||
def __init__(self, chainage_m: float, reason: str) -> None:
|
||||
self.chainage_m, self.reason = chainage_m, reason
|
||||
|
||||
class Detail:
|
||||
pipes = [Pipe(100.0, "stream"), Pipe(300.0, "spacing")]
|
||||
basins: list = []
|
||||
|
||||
monkeypatch.setattr(basins, "build_detail", lambda *_: Detail())
|
||||
monkeypatch.setattr(basins, "drainage_dir", lambda _: "x")
|
||||
|
||||
class Ctx:
|
||||
vertices: list = []
|
||||
|
||||
old = PipePoint(chainage_m=100.0, source="stream") # 옛 관 — 옵션 없음
|
||||
_, points = basins._build("p", Ctx(), [old])
|
||||
by = {p.chainage_m: p for p in points}
|
||||
assert by[100.0].options is None # 옛 빈칸 그대로
|
||||
assert by[300.0].options == _defaults("pipe") # 새로 보충된 관 = 기본값
|
||||
|
||||
|
||||
def test_월류_폭_기본값은_등록부_엔진과_같음():
|
||||
assert _defaults("ford_bridge")["ford_width_m"] == FORD_DEFAULT_WIDTH_M
|
||||
assert _defaults("ford_pavement")["ford_width_m"] == FORD_PAVEMENT_DEFAULT_WIDTH_M
|
||||
|
||||
|
||||
def test_화면에_제안값_단추_제안_글이_없음():
|
||||
files = [
|
||||
"B05_Profile/B05_Profile_UI_Drainage_Facility.ts",
|
||||
"B05_Profile/B05_Profile_UI_Drainage_Facility_Fields.ts",
|
||||
"B05_Profile/B05_Profile_UI_Drainage_Facility_Ford.ts",
|
||||
"B05_Profile/B05_Profile_UI_Structures_Fields.ts",
|
||||
"B05_Profile/B05_Profile_UI_Structures_Panel_Options.ts",
|
||||
]
|
||||
for name in files:
|
||||
text = (ROOT / name).read_text(encoding="utf-8")
|
||||
assert "제안값 넣기" not in text and "b05-structure__suggest" not in text, name
|
||||
assert "`제안 " not in text and '"제안 ' not in text and "(제안 " not in text, name
|
||||
panel = (ROOT / "B05_Profile/B05_Profile_UI_Structures_Panel.ts").read_text(encoding="utf-8")
|
||||
assert "values ?? newDefaults()" in panel and "options ?? newDefaults()" in panel
|
||||
@@ -42,5 +42,6 @@ def test_TS_가_쓰는_칸이_파일에_있다() -> None:
|
||||
|
||||
|
||||
def test_B05_높이_칸은_제안_없이_설계자_입력() -> None:
|
||||
assert 'height: "",' in FIELDS and '"설계자 입력"' in FIELDS
|
||||
# 독립 기슭막이 높이 = 기본값 없음 · 「설계자 입력」 안내(제안값 단추 걷음 · PLAN 46-7)
|
||||
assert 'createRevetmentFields(keys, { height: "" })' in FIELDS and '"설계자 입력"' in FIELDS
|
||||
assert "기슭막이 높이 없음 — 근입만 그림" in CHROME
|
||||
|
||||
Reference in New Issue
Block a user