fix(B06 표준 횡단): 364-6 칸 열기만으로 암 표준값이 토사 값으로 바뀌던 것 — 편집 없이 B05 = B06 누가토량
- 까닭: 표준 횡단 칸이 열릴 때 공통 칸을 토사 값으로 펼쳐 세션을 고침(암 측구 상폭 0.69 → 0.9 · 암 횡단경사 3 → 3~5) → B06 다시 셈이 암 측점 절토를 키움 · 성토 그대로 - 고침: 열기 · 저장값 복원 · 초기 값 · 다른 프로젝트 불러오기 = 그룹 값 그대로 · 공통 칸은 고칠 때만 펼침 - 검증_sub7 노선 304: 칸 열기 전후 절토 면적 합 402.9 → 444.4 ㎡(56/67 측점) → 고친 뒤 같음 · 서버 셈 = B05 셈 - 시험 test_364_6_std_panel_same_value.py 6(옛 코드 4 실패) · 관련 25 · typecheck Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TteK8mfYZetk5P3bEZkyVZ
This commit is contained in:
@@ -245,20 +245,6 @@ function spread(state: StandardCrossSection, spec: NumberFieldSpec, value: numbe
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 그룹마다 공통값이 다르게 저장돼 있을 수 있다(옛 프로젝트) — **토사 값을 기준**으로
|
|
||||||
* 한 벌로 맞춘다(2026-09-04 사용자 확정). 화면은 값 하나를 보이는데 저장분이 셋으로
|
|
||||||
* 갈려 있으면 어느 값이 나갔는지 알 수 없기 때문이다.
|
|
||||||
*/
|
|
||||||
function unifyCommon(state: StandardCrossSection): void {
|
|
||||||
const soil = state.soil;
|
|
||||||
if (!soil) return;
|
|
||||||
for (const spec of FIELD_SPECS) {
|
|
||||||
if (spec.scope !== "common") continue;
|
|
||||||
spread(state, spec, spec.get(soil));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 표준 횡단면 설정 패널을 만든다.
|
* 표준 횡단면 설정 패널을 만든다.
|
||||||
* @param projectId 세션 캐시 스코프.
|
* @param projectId 세션 캐시 스코프.
|
||||||
@@ -384,8 +370,7 @@ export function createStandardPanel(
|
|||||||
const renderBody = (): void => {
|
const renderBody = (): void => {
|
||||||
body.replaceChildren(buildDetails());
|
body.replaceChildren(buildDetails());
|
||||||
};
|
};
|
||||||
// 옛 저장분이 그룹마다 다른 공통값을 갖고 있으면 토사 기준으로 한 벌로 맞춘다.
|
// 열기 · 복원 · 불러오기 = 그룹 값 그대로(서버가 그 값으로 셈) — 공통 칸은 고칠 때만 펼침(364-6).
|
||||||
unifyCommon(state);
|
|
||||||
persist();
|
persist();
|
||||||
if (!hadSession) seedPageHistory(stateKey("std-cross", projectId)); // 비었던 칸 채움 = 씨앗(364-1)
|
if (!hadSession) seedPageHistory(stateKey("std-cross", projectId)); // 비었던 칸 채움 = 씨앗(364-1)
|
||||||
renderBody();
|
renderBody();
|
||||||
@@ -395,7 +380,6 @@ export function createStandardPanel(
|
|||||||
(Object.keys(source) as StandardCrossKey[]).forEach((key) => {
|
(Object.keys(source) as StandardCrossKey[]).forEach((key) => {
|
||||||
if (source[key]) state[key] = JSON.parse(JSON.stringify(source[key])) as StandardCrossGroup;
|
if (source[key]) state[key] = JSON.parse(JSON.stringify(source[key])) as StandardCrossGroup;
|
||||||
});
|
});
|
||||||
unifyCommon(state);
|
|
||||||
persist();
|
persist();
|
||||||
renderBody();
|
renderBody();
|
||||||
};
|
};
|
||||||
@@ -412,7 +396,6 @@ export function createStandardPanel(
|
|||||||
(Object.keys(fresh) as StandardCrossKey[]).forEach((key) => {
|
(Object.keys(fresh) as StandardCrossKey[]).forEach((key) => {
|
||||||
state[key] = fresh[key];
|
state[key] = fresh[key];
|
||||||
});
|
});
|
||||||
unifyCommon(state);
|
|
||||||
persist();
|
persist();
|
||||||
renderBody();
|
renderBody();
|
||||||
},
|
},
|
||||||
@@ -447,7 +430,6 @@ export function createStandardPanel(
|
|||||||
(Object.keys(stored) as StandardCrossKey[]).forEach((key) => {
|
(Object.keys(stored) as StandardCrossKey[]).forEach((key) => {
|
||||||
if (stored[key]) state[key] = JSON.parse(JSON.stringify(stored[key])) as StandardCrossGroup;
|
if (stored[key]) state[key] = JSON.parse(JSON.stringify(stored[key])) as StandardCrossGroup;
|
||||||
});
|
});
|
||||||
unifyCommon(state);
|
|
||||||
applied = JSON.stringify(state);
|
applied = JSON.stringify(state);
|
||||||
showPending();
|
showPending();
|
||||||
renderBody();
|
renderBody();
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,8 @@
|
|||||||
|
/* 364-6 시험 묶음 진입점 — 표준 횡단 패널 열기 전후 같은 다시 셈인지 잼(vite ssr). */
|
||||||
|
export { refreshCrossDesigns } from "../../B06_Section/B06_Section_Cross_Refresh";
|
||||||
|
export {
|
||||||
|
createStandardPanel,
|
||||||
|
effectiveStandardCross,
|
||||||
|
rememberStandardDefaults,
|
||||||
|
} from "../../B06_Section/B06_Section_UI_Standard_Panel";
|
||||||
|
export { readState, writeState } from "../../A00_Common/b_page_state";
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
/* 364-6 헬퍼 — vite ssr 로 묶은 진짜 모듈(helper_364_6_entry.ts)로 표준 횡단 칸 열기 전후 다시 셈을 잼.
|
||||||
|
* node helper_364_6_std_panel.mjs <묶음.mjs> <고정 자료.json>
|
||||||
|
* 고정 자료 = 검증_sub7 노선 304(모두 리핑암)의 서버 상세 4 측점 + config 표준 횡단.
|
||||||
|
*/
|
||||||
|
import { readFileSync } from "node:fs";
|
||||||
|
import { pathToFileURL } from "node:url";
|
||||||
|
|
||||||
|
const store = () => {
|
||||||
|
const m = new Map();
|
||||||
|
return {
|
||||||
|
getItem: (k) => (m.has(k) ? m.get(k) : null),
|
||||||
|
setItem: (k, v) => m.set(k, String(v)),
|
||||||
|
removeItem: (k) => m.delete(k),
|
||||||
|
key: (i) => [...m.keys()][i] ?? null,
|
||||||
|
get length() {
|
||||||
|
return m.size;
|
||||||
|
},
|
||||||
|
clear: () => m.clear(),
|
||||||
|
};
|
||||||
|
};
|
||||||
|
globalThis.window = globalThis;
|
||||||
|
globalThis.sessionStorage = store();
|
||||||
|
globalThis.localStorage = store();
|
||||||
|
// 패널 DOM — 무엇을 불러도 받아 주는 빈 객체
|
||||||
|
const any = new Proxy(function () {}, {
|
||||||
|
get: (_t, k) => (k === Symbol.toPrimitive ? () => 0 : any),
|
||||||
|
apply: () => any,
|
||||||
|
construct: () => any,
|
||||||
|
});
|
||||||
|
globalThis.document = any;
|
||||||
|
globalThis.requestAnimationFrame = (run) => setTimeout(run, 0);
|
||||||
|
|
||||||
|
const [bundlePath, fixturePath] = process.argv.slice(2);
|
||||||
|
const m = await import(pathToFileURL(bundlePath).href);
|
||||||
|
const fixture = JSON.parse(readFileSync(fixturePath, "utf8"));
|
||||||
|
const raw = JSON.stringify(fixture.detail);
|
||||||
|
const standard = fixture.standard_cross_section;
|
||||||
|
const RID = fixture.route_id;
|
||||||
|
const KEYS = ["cut_area_m2", "fill_area_m2", "ditch_area_m2"];
|
||||||
|
const areas = (sections) =>
|
||||||
|
sections.map((s) => Object.fromEntries(KEYS.map((k) => [k, s.design?.[k] ?? null])));
|
||||||
|
const saved = fixture.detail.longitudinal.profile_alignment?.edits ?? {};
|
||||||
|
const edits = {
|
||||||
|
station_offsets: saved.station_offsets ?? {},
|
||||||
|
curve_radii: saved.curve_radii ?? {},
|
||||||
|
};
|
||||||
|
const recompute = async (pid) => {
|
||||||
|
const detail = JSON.parse(raw);
|
||||||
|
await m.refreshCrossDesigns({ projectId: pid, routeId: RID, detail, edits });
|
||||||
|
return areas(detail.cross_sections);
|
||||||
|
};
|
||||||
|
|
||||||
|
/** B05 = 칸 없음(config 기본값) → B06 = 칸 열림. seed = 저장 표준 횡단을 세션에 먼저 채운 경우. */
|
||||||
|
async function scenario(pid, seed) {
|
||||||
|
m.rememberStandardDefaults(pid, standard);
|
||||||
|
if (seed) m.writeState("std-cross", seed, pid);
|
||||||
|
const b05 = await recompute(pid);
|
||||||
|
m.createStandardPanel(pid, standard, () => {});
|
||||||
|
const b06 = await recompute(pid);
|
||||||
|
return { b05, b06, session: m.readState("std-cross", pid) };
|
||||||
|
}
|
||||||
|
|
||||||
|
const out = {
|
||||||
|
server: areas(fixture.detail.cross_sections),
|
||||||
|
standard,
|
||||||
|
config: await scenario("p364a", null),
|
||||||
|
stored: await scenario("p364b", standard),
|
||||||
|
};
|
||||||
|
process.stdout.write(JSON.stringify(out));
|
||||||
@@ -0,0 +1,84 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""364-6 — 편집 없이 B06 를 열어도 B05 와 같은 누가토량.
|
||||||
|
|
||||||
|
까닭: B06 표준 횡단 칸이 열릴 때 공통 칸을 토사 값으로 펼쳐 암 측구 상폭 0.69 → 0.9 ·
|
||||||
|
암 횡단경사 3 → 3~5 로 세션을 고쳤음 → 다시 셈이 그 값으로 절토를 키움(성토 그대로).
|
||||||
|
못박는 것
|
||||||
|
- 브라우저 다시 셈(칸 없음 = B05) = 서버 저장 면적.
|
||||||
|
- 칸 열림(B06) 뒤 다시 셈 = B05 값 · 세션 표준 횡단 = 서버가 쓴 값 그대로.
|
||||||
|
- 저장 표준 횡단이 세션에 먼저 채워진 길도 같음.
|
||||||
|
|
||||||
|
Node 헬퍼 `helper_364_6_std_panel.mjs` 가 vite ssr 로 묶은 진짜 모듈(`helper_364_6_entry.ts`)을 부름.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import json
|
||||||
|
import shutil
|
||||||
|
import subprocess
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
HERE = Path(__file__).resolve().parent
|
||||||
|
ROOT = HERE.parents[1]
|
||||||
|
FIXTURE = HERE / "fixtures" / "b06_364_6_rock_route_304.json"
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(scope="module")
|
||||||
|
def out(tmp_path_factory):
|
||||||
|
work = tmp_path_factory.mktemp("b364_6")
|
||||||
|
build = subprocess.run(
|
||||||
|
[
|
||||||
|
"node",
|
||||||
|
str(ROOT / "config" / "node_modules" / "vite" / "bin" / "vite.js"),
|
||||||
|
"build",
|
||||||
|
"--configLoader",
|
||||||
|
"runner",
|
||||||
|
"--ssr",
|
||||||
|
"../resources/tester/helper_364_6_entry.ts",
|
||||||
|
"--outDir",
|
||||||
|
str(work),
|
||||||
|
"--logLevel",
|
||||||
|
"error",
|
||||||
|
],
|
||||||
|
cwd=ROOT,
|
||||||
|
capture_output=True,
|
||||||
|
text=True,
|
||||||
|
encoding="utf-8",
|
||||||
|
timeout=300,
|
||||||
|
)
|
||||||
|
assert build.returncode == 0, build.stderr
|
||||||
|
bundle = work / "bundle.mjs"
|
||||||
|
shutil.copy(work / "helper_364_6_entry.js", bundle)
|
||||||
|
proc = subprocess.run(
|
||||||
|
["node", str(HERE / "helper_364_6_std_panel.mjs"), str(bundle), str(FIXTURE)],
|
||||||
|
cwd=ROOT,
|
||||||
|
capture_output=True,
|
||||||
|
text=True,
|
||||||
|
encoding="utf-8",
|
||||||
|
timeout=120,
|
||||||
|
)
|
||||||
|
assert proc.returncode == 0, proc.stderr
|
||||||
|
return json.loads(proc.stdout)
|
||||||
|
|
||||||
|
|
||||||
|
def _close(a: list, b: list) -> bool:
|
||||||
|
return all(abs((x[k] or 0) - (y[k] or 0)) < 1e-6 for x, y in zip(a, b, strict=True) for k in x)
|
||||||
|
|
||||||
|
|
||||||
|
def test_고정_자료는_암_측구가_토사와_다름(out):
|
||||||
|
std = out["standard"]
|
||||||
|
assert std["rock"]["ditch"]["top_width_m"] != std["soil"]["ditch"]["top_width_m"]
|
||||||
|
|
||||||
|
|
||||||
|
def test_B05_다시_셈은_서버_면적(out):
|
||||||
|
assert _close(out["config"]["b05"], out["server"])
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("case", ["config", "stored"])
|
||||||
|
def test_B06_칸_열어도_B05_값(case, out):
|
||||||
|
assert _close(out[case]["b06"], out[case]["b05"])
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("case", ["config", "stored"])
|
||||||
|
def test_칸_열기는_표준_횡단을_안_고침(case, out):
|
||||||
|
assert out[case]["session"] == out["standard"]
|
||||||
Reference in New Issue
Block a user