Merge remote-tracking branch 'origin/sub_laptop_3' into main_laptop_1
This commit is contained in:
@@ -34,12 +34,7 @@ const applied = sug.applySuggestions(pipes, suggestions);
|
||||
|
||||
process.stdout.write(
|
||||
JSON.stringify({
|
||||
quietMs: live.QUIET_MS,
|
||||
modes: [
|
||||
live.parseDrainageMode(null),
|
||||
live.parseDrainageMode("auto"),
|
||||
live.parseDrainageMode("x"),
|
||||
],
|
||||
noAutoMode: live.QUIET_MS === undefined && live.parseDrainageMode === undefined, // 326-6
|
||||
stale: [live.draftMakesStale(null), live.draftMakesStale({ rev: 1 })],
|
||||
accept: [
|
||||
live.acceptLive({ status: "success", generation: 3 }, at(3), at(3)),
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* 349 배수유역 자동/수동 흐름 헬퍼 — 화면 파일 `B05_Profile_UI_Drainage_Live.ts` · `_Run.ts` ·
|
||||
/* 349 · 326-6 배수유역 [업데이트] 흐름 헬퍼 — 화면 파일 `B05_Profile_UI_Drainage_Live.ts` · `_Run.ts` ·
|
||||
* `_Suggest.ts` 를 그 자리에서 트랜스파일해 가짜 DOM · 시계 · fetch 로 돌리고 단계별 상태를 JSON 으로 냄.
|
||||
*/
|
||||
const fs = require("fs");
|
||||
@@ -86,21 +86,10 @@ const flush = async () => {
|
||||
/* ---- 모듈 읽기(그 자리 트랜스파일 · 바깥 모듈은 가짜) ---- */
|
||||
const state = new Map();
|
||||
const detailCalls = [];
|
||||
const detailAutos = []; // [자동 제안](369) 방식 — 없으면 null
|
||||
let records = 0; // 사진첩 한 걸음(369)
|
||||
let detailStale = false; // detail-basins 가 409 낡음으로 답함(369 — 화면이 몰랐던 낡음)
|
||||
let modeRow = 1; // ⚙ · [자동 제안] 창에서 고를 줄 — ⚙ 0 수동 · 1 자동 / 제안 0 전체 · 1 지정 포함
|
||||
const stubs = {
|
||||
"@config/config_frontend": { API_BASE_URL: "/api" },
|
||||
"@ui/ui_template_progress": { createProgressCircle: () => ({ root: el("progress") }) },
|
||||
// ⚙ 창 — 둘째 줄(자동) 라디오를 고름
|
||||
"@ui/ui_template_modal": {
|
||||
openModal: (options) => {
|
||||
const body = el("modal");
|
||||
options.mount(body, () => undefined);
|
||||
body.children[modeRow].children[0].fire("change");
|
||||
},
|
||||
},
|
||||
"@ui/ui_template_locale": {
|
||||
currentLanguageIndex: 0,
|
||||
ui_locales: new Proxy({}, { get: (_target, key) => [key, key] }),
|
||||
@@ -110,10 +99,8 @@ const stubs = {
|
||||
writeStateRaw: (key, value) => state.set(key, value),
|
||||
},
|
||||
"../B04_PreProcess/B04_PreProcess_Api_Fetch": {
|
||||
computeDetailBasins: async (projectId, points, auto) => {
|
||||
computeDetailBasins: async (projectId, points) => {
|
||||
detailCalls.push(points);
|
||||
detailAutos.push(auto ?? null);
|
||||
if (detailStale) throw new Error("배수유역 낡음 — 노선이 분석 때와 다릅니다.");
|
||||
return { basins: [], pipe_points: points ?? [], stale: null };
|
||||
},
|
||||
},
|
||||
@@ -145,6 +132,7 @@ async function main() {
|
||||
const header = el("header");
|
||||
const root = el("root");
|
||||
const propose = el("button");
|
||||
const update = el("button");
|
||||
header.querySelector = (selector) => (selector === ".b05-drainage__propose" ? propose : null);
|
||||
root.querySelector = (selector) =>
|
||||
selector === ".b05-drainage__viewport"
|
||||
@@ -163,7 +151,7 @@ async function main() {
|
||||
const status = el("status");
|
||||
const runner = createDetailRunner({
|
||||
projectId: () => "p1",
|
||||
analyzeButton: el("button"),
|
||||
updateButton: update,
|
||||
status,
|
||||
showProgress: () => undefined,
|
||||
apply: (response) => {
|
||||
@@ -177,6 +165,7 @@ async function main() {
|
||||
});
|
||||
live = createDrainageLive({
|
||||
root,
|
||||
updateButton: update,
|
||||
basinList: el("list"),
|
||||
pipeRequest: () => pipes,
|
||||
runner,
|
||||
@@ -188,13 +177,12 @@ async function main() {
|
||||
return runner.run(() => new Promise((resolve) => (savedAnswer = resolve)), true);
|
||||
},
|
||||
});
|
||||
const [veil, busy] = viewport.children;
|
||||
const update = veil.children[0];
|
||||
const gear = header.children[0];
|
||||
const [busy] = viewport.children;
|
||||
const look = () => ({
|
||||
stale: live.stale,
|
||||
blur: root.classList.contains("is-stale"),
|
||||
veil: !veil.hidden,
|
||||
pending: update.classList.contains("is-pending"),
|
||||
off: update.disabled,
|
||||
label: update.textContent,
|
||||
busy: !busy.hidden,
|
||||
fetches: calls.length,
|
||||
applyOff: live.suggest.root.children[0].children[1].disabled,
|
||||
@@ -221,11 +209,14 @@ async function main() {
|
||||
...extra,
|
||||
});
|
||||
|
||||
// 수동 — 꼭짓점 옮김 = 흐림 + [업데이트] · 판 번호만 바뀐 같은 노선 = 그대로
|
||||
out.start = look();
|
||||
// 꼭짓점 옮김 = 낡음 표시만 · 저절로 셈 없음 · 판 번호만 바뀐 같은 노선 = 그대로
|
||||
stage(draft(1, 50));
|
||||
out.moved = look();
|
||||
stage(draft(2, 50));
|
||||
out.sameAgain = look();
|
||||
advance(60000);
|
||||
out.noAuto = look();
|
||||
// [업데이트] → 요청 하나 · 묶음이 같은 입력으로 다시 돌아도 답을 받음
|
||||
update.fire("click");
|
||||
out.sent = { ...look(), body: calls[0].body };
|
||||
@@ -244,7 +235,7 @@ async function main() {
|
||||
await flush();
|
||||
out.applied = { points: detailCalls[0], remembered };
|
||||
|
||||
// 다시 옮김 → 요청 → 그 사이 또 옮김 = 앞 요청 끊김 · 다시 흐림
|
||||
// 다시 옮김 → 요청 → 그 사이 또 옮김 = 앞 요청 끊김 · 다시 낡음
|
||||
stage(draft(4, 80));
|
||||
update.fire("click");
|
||||
stage(draft(5, 90));
|
||||
@@ -253,64 +244,36 @@ async function main() {
|
||||
live.suggest.root.children[0].children[1].fire("click");
|
||||
await flush();
|
||||
out.staleApply = detailCalls.length;
|
||||
update.fire("click");
|
||||
// [업데이트] 약속 — 답이 화면에 오르면 참(B06 [>] 확인 창 뒤 이동 · 326-6)
|
||||
const done = live.refresh();
|
||||
answer(calls[2]);
|
||||
out.promised = await done;
|
||||
await flush();
|
||||
out.fresh = look();
|
||||
// 낡지 않아도 [업데이트] = 다시 셈(노선 · 관 그대로 · 326-6)
|
||||
update.fire("click");
|
||||
out.again = { ...look(), route: calls[3].body.route };
|
||||
answer(calls[3]);
|
||||
await flush();
|
||||
out.againDone = look();
|
||||
|
||||
// 노선 [저장] 뒤 — 서버 답이 「낡음」 = 흐림 + [업데이트](수동)
|
||||
// 노선 [저장] 뒤 — 서버 답이 「낡음」 = 낡음 표시 · 저절로 셈 없음
|
||||
stage(null);
|
||||
await runner.run(async () => ({ basins: [], pipe_points: pipes, stale: "배수유역 낡음" }));
|
||||
out.savedStale = { ...look(), status: status.textContent };
|
||||
|
||||
// ⚙ 자동 — 바꾸는 순간 3 s 뒤 셈 · 흐림 막 · [업데이트] 없음
|
||||
gear.fire("click");
|
||||
out.autoSwitch = look();
|
||||
advance(2999);
|
||||
out.auto2999 = look();
|
||||
advance(1);
|
||||
out.auto3000 = { ...look(), route: calls[3].body.route };
|
||||
answer(calls[3]);
|
||||
await flush();
|
||||
out.autoDone = look();
|
||||
// 자동 · 꼭짓점 옮김 = 조용히 3 s 뒤 · 그 사이 또 옮기면 처음부터
|
||||
stage(draft(6, 120));
|
||||
advance(2000);
|
||||
stage(draft(7, 130));
|
||||
advance(2000);
|
||||
out.autoRestart = look();
|
||||
advance(1000);
|
||||
advance(60000);
|
||||
out.savedStaleNoAuto = look();
|
||||
update.fire("click");
|
||||
answer(calls[4]);
|
||||
await flush();
|
||||
out.autoMoved = { ...look(), mode: state.get("drainage-mode") };
|
||||
// 자동 · 저장 뒤 낡음 = 3 s 뒤 조용히
|
||||
await runner.run(async () => ({ basins: [], pipe_points: pipes, stale: "배수유역 낡음" }));
|
||||
out.autoSavedStale = look();
|
||||
advance(3000);
|
||||
answer(calls[5]);
|
||||
await flush();
|
||||
out.autoSavedDone = look();
|
||||
out.savedDone = look();
|
||||
|
||||
// 349-0i 되돌리기 — 자동: 끌기 → 낡음 → Ctrl+Z(초안 없어짐) = 3 s 셈 대신 저장분
|
||||
// 저장분 답에도 관경 제안 하나 — [적용] 켜짐은 낡음 · 관경/시설 제안 유무로 갈림(369)
|
||||
// 되돌리기 — 끌기 → [업데이트] 요청 중 → Ctrl+Z = 초안용 답 끊고 저장분 · 표시는 답 올 때까지 그대로
|
||||
const savedOk = {
|
||||
basins: [{ chainage_m: 100, recommended_facility: "pipe", recommended_diameter_mm: 1200 }],
|
||||
pipe_points: pipes,
|
||||
stale: null,
|
||||
};
|
||||
stage(draft(20, 300));
|
||||
advance(1000);
|
||||
stage(null);
|
||||
out.autoUndoWaiting = { ...look(), savedLoads };
|
||||
advance(5000);
|
||||
out.autoUndoNoFetch = look();
|
||||
savedAnswer(savedOk);
|
||||
await flush();
|
||||
out.autoUndo = { ...look(), savedLoads };
|
||||
|
||||
// 수동: 끌기 → [업데이트] 요청 중 → Ctrl+Z = 초안용 답 끊고 저장분 · 흐림은 답 올 때까지
|
||||
modeRow = 0;
|
||||
gear.fire("click");
|
||||
stage(draft(21, 310));
|
||||
update.fire("click");
|
||||
const draftCall = calls[calls.length - 1];
|
||||
@@ -319,64 +282,17 @@ async function main() {
|
||||
savedAnswer(savedOk);
|
||||
await flush();
|
||||
out.undo = { ...look(), savedLoads };
|
||||
// 저장분 자체가 낡음 = 흐림 · [업데이트] 그대로
|
||||
// 저장분 자체가 낡음 = 낡음 표시 그대로
|
||||
stage(draft(22, 320));
|
||||
stage(null);
|
||||
savedAnswer({ basins: [], pipe_points: pipes, stale: "배수유역 낡음" });
|
||||
await flush();
|
||||
out.undoSavedStale = { ...look(), savedLoads, mode: state.get("drainage-mode") };
|
||||
// [자동 제안] — 낡은 동안 끔 · 누름은 패널 후보 목록(510 · 여기 안 셈) · [배관 배치 초기화] = 관 목록 없이(세류선만)
|
||||
out.undoSavedStale = { ...look(), savedLoads };
|
||||
// [자동 제안] — 낡은 동안 끔 · 새 답이면 켬
|
||||
out.proposeStale = { off: propose.disabled, title: propose.title };
|
||||
const beforeStale = detailCalls.length;
|
||||
propose.fire("click");
|
||||
await flush();
|
||||
out.proposeStale.calls = detailCalls.length - beforeStale;
|
||||
live.markFresh();
|
||||
out.proposeFresh = { off: propose.disabled, title: propose.title };
|
||||
const n0 = detailCalls.length;
|
||||
const remembered0 = remembered;
|
||||
const records0 = records;
|
||||
propose.fire("click");
|
||||
await flush();
|
||||
live.resetToStreams();
|
||||
await flush();
|
||||
out.propose = {
|
||||
autos: detailAutos.slice(n0),
|
||||
points: detailCalls.slice(n0).map((points) => (points === null ? null : points.length)),
|
||||
remembered: remembered - remembered0,
|
||||
records: records - records0,
|
||||
};
|
||||
// 화면이 몰랐던 낡음 — 빠른 길이 409 낡음이면 곧바로 지금 노선으로 셈 길로 다시
|
||||
detailStale = true;
|
||||
const fallbackFrom = calls.length;
|
||||
live.resetToStreams();
|
||||
await flush();
|
||||
const fallbackCall = calls[calls.length - 1];
|
||||
out.proposeFallback = {
|
||||
sent: calls.length - fallbackFrom,
|
||||
points: fallbackCall.body.points,
|
||||
status: status.textContent,
|
||||
};
|
||||
answer(fallbackCall);
|
||||
await flush();
|
||||
detailStale = false;
|
||||
// 저장 분석이 옛 노선(서버 낡음 답) → [업데이트] 뒤 [자동 제안] = 지금 노선으로 셈 길에 방식 실음
|
||||
await runner.run(async () => ({ basins: [], pipe_points: pipes, stale: "배수유역 낡음" }));
|
||||
update.fire("click");
|
||||
answer(calls[calls.length - 1]);
|
||||
await flush();
|
||||
const viaDetail = detailCalls.length;
|
||||
live.resetToStreams();
|
||||
const resetCall = calls[calls.length - 1];
|
||||
answer(resetCall);
|
||||
await flush();
|
||||
out.proposeLive = {
|
||||
resetPoints: resetCall.body.points,
|
||||
resetAuto: resetCall.body.auto ?? null,
|
||||
detailCalls: detailCalls.length - viaDetail,
|
||||
remembered: remembered - remembered0,
|
||||
records: records - records0,
|
||||
};
|
||||
out.records = records;
|
||||
// 입력 지문 — 판 번호 · 관 차례는 안 봄 · 꼭짓점 · 관 자리(cm) · id 는 봄
|
||||
const { inputKey } = load(path.join(B05, "logic", "B05_Profile_Drainage_Live.ts"));
|
||||
const a = { id: "a1", chainage_m: 100 };
|
||||
|
||||
@@ -69,7 +69,6 @@ const { createFacilityStore } = load("B05_Profile", "B05_Profile_UI_Drainage_Fac
|
||||
pipeRequest: () => [{ chainage_m: 150, source: "user" }],
|
||||
addPipes: (picks) =>
|
||||
picks.forEach((item) => added.push([item.chainage_m, item.stream_label?.stream ?? null])),
|
||||
redraw: () => undefined,
|
||||
});
|
||||
await propose.open();
|
||||
const [head, list] = propose.root.children;
|
||||
@@ -80,7 +79,7 @@ const { createFacilityStore } = load("B05_Profile", "B05_Profile_UI_Drainage_Fac
|
||||
rows: list.children.map((row) => row.children[0].children[1].text.trim()),
|
||||
title: title.textContent,
|
||||
addOff: add.disabled,
|
||||
candidates: propose.candidates(),
|
||||
noCircles: propose.candidates === undefined, // 점선 원 걷음(326-6)
|
||||
};
|
||||
boxes[1].checked = true;
|
||||
boxes[1].fire("change");
|
||||
@@ -88,7 +87,7 @@ const { createFacilityStore } = load("B05_Profile", "B05_Profile_UI_Drainage_Fac
|
||||
boxes[2].fire("change");
|
||||
add.fire("click");
|
||||
out.added = added;
|
||||
out.afterClose = { hidden: propose.root.hidden, candidates: propose.candidates() };
|
||||
out.afterClose = { hidden: propose.root.hidden };
|
||||
|
||||
// 관 속성 보관소 — 응답 라벨이 다음 요청에 실림(라벨만 있는 맨 배관도 보관)
|
||||
const store = createFacilityStore();
|
||||
|
||||
@@ -16,7 +16,7 @@ SPOTS = {
|
||||
"B09_Estimation/B09_Estimation_Sangeun.py": ['"수기 줄" if'],
|
||||
"B09_Estimation/B09_Estimation_UI_Tabs_Base.ts": ["견적 단가는 수기 입력"],
|
||||
"B09_Estimation/B09_Estimation_UI_Tabs_Total.ts": ['"수기 입력"'],
|
||||
"ui_template/ui_template_locale_b2.ts": [
|
||||
"ui_template/ui_template_locale_b5.ts": [ # B08~B11 칸 = b5(512-2 나눔)
|
||||
"수량 원천: 수기 입력(직접비)",
|
||||
"수량 원천: 수기 입력 공종",
|
||||
],
|
||||
|
||||
@@ -107,7 +107,9 @@ def test_다시_나눔은_세션_초안으로_계산만_저장_안_함():
|
||||
load = _body(PANEL, "const loadSaved = ", "const analyze = ")
|
||||
assert "readPendingPipes(projectId as string)" in load
|
||||
assert "computeDetailBasins(projectId as string, pending)" in load
|
||||
analyze = _body(PANEL, "const analyze = ", "analyzeButton.addEventListener")
|
||||
analyze = _body(
|
||||
PANEL, "const analyze = ", "const live = createDrainageLive("
|
||||
) # 326-6 [유역 재분할] 걷음
|
||||
assert "computeDetailBasins(" in analyze
|
||||
run = RUN[RUN.index("async run(") :]
|
||||
assert "saveDetailPipePoints" not in run + load + analyze
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"""347-2 · 347-3 배수유역 자동/수동 · 제안 — 화면 쪽 순수 규칙(logic/ 두 파일).
|
||||
|
||||
- 기본 수동 · 자동 = 3 초 조용한 뒤 · 노선 초안이 있을 때만 낡음.
|
||||
- 자동 모드 없음(요구 326-6 · [업데이트]만) · 노선 초안이 있을 때만 낡음.
|
||||
- 늦은 답 버림: 옛 차례 · 서버가 그만둔 답 · 답 차례가 다른 것은 안 올림.
|
||||
- 제안: 관경(배관 · 추천 ≠ 지금) · 시설(BOX암거 · 세월교) · 관 자리(기존 관 근처 뺌) ·
|
||||
[적용] = 한 번에 얹음 · 입력 목록은 안 고침(자동 바꿈 없음).
|
||||
@@ -30,8 +30,7 @@ def r():
|
||||
|
||||
|
||||
def test_mode_and_stale(r):
|
||||
assert r["quietMs"] == 3000
|
||||
assert r["modes"] == ["manual", "auto", "manual"]
|
||||
assert r["noAutoMode"]
|
||||
assert r["stale"] == [False, True]
|
||||
|
||||
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
"""349 배수유역 자동/수동 흐름 — 화면 파일(`B05_Profile_UI_Drainage_Live` · `_Run` · `_Suggest`)을
|
||||
"""349 · 326-6 배수유역 [업데이트] 흐름 — 화면 파일(`B05_Profile_UI_Drainage_Live` · `_Run` · `_Suggest`)을
|
||||
가짜 DOM · 시계 · fetch 로 돌림(`helper_349_drainage_live_flow.cjs`).
|
||||
|
||||
- 수동: 꼭짓점 옮김 = 흐림 + [업데이트] · [업데이트] 뒤 묶음이 같은 노선 · 관으로 다시 돌아도 답을 받음
|
||||
(입력 지문) · 답이 오면 흐림 · 「계산 중」 사라짐 · 제안 「제안 N건」 · [적용].
|
||||
- 요청 중 또 옮기면 앞 요청 끊고 다시 흐림.
|
||||
- 노선 [저장] 뒤 서버 답이 「낡음」 = 수동은 흐림 + [업데이트] · 자동은 3 s 뒤 조용히.
|
||||
- ⚙ 자동: 흐림 없음 · 3 s 조용한 뒤 셈 · 그 사이 또 옮기면 처음부터 · [업데이트] 막 없음(349-0e).
|
||||
- 낡은 동안 제안 [적용] 끔(두 모드) · 새 답이 오면 다시 켬(349-0e).
|
||||
- 되돌리기로 초안이 없어지면 저장분 다시 받기 · 그 답의 낡음대로 · 초안용 답 끊음 · 자동도 3 s 셈 대신(349-0i).
|
||||
- 꼭짓점 옮김 = 옛 그림 그대로 · [업데이트] 단추에 낡음 점만 · 저절로 셈 없음(시계 60 s 흘려도).
|
||||
- [업데이트] = 뒤에서 조용히 셈(단추만 「계산 중…」 · 구석 표시) · 묶음이 같은 입력으로 다시 돌아도 답을 받음.
|
||||
- 낡지 않아도 [업데이트] = 다시 셈(326-6) · 약속은 답이 오르면 참(B06 [>] 확인 창 뒤 이동).
|
||||
- 요청 중 또 옮기면 앞 요청 끊고 다시 낡음 · 낡은 동안 제안 [적용] · [자동 제안] 끔.
|
||||
- 노선 [저장] 뒤 서버 답이 「낡음」 = 낡음 표시 · 저절로 셈 없음.
|
||||
- 되돌리기로 초안이 없어지면 저장분 다시 받기 · 그 답의 낡음대로 · 초안용 답 끊음(349-0i).
|
||||
"""
|
||||
|
||||
import json
|
||||
@@ -33,30 +32,40 @@ def r():
|
||||
|
||||
|
||||
STALE_TITLE = "노선이 바뀌어 제안이 낡음 — 새 계산 뒤 적용"
|
||||
IDLE = "업데이트"
|
||||
BUSY = "계산 중…"
|
||||
|
||||
|
||||
def state(stale, veil, busy, fetches, auto=False):
|
||||
def state(stale, busy, fetches):
|
||||
return {
|
||||
"stale": stale,
|
||||
"blur": stale and not auto, # 자동 = 흐림 없음
|
||||
"veil": veil,
|
||||
"pending": stale, # 낡음 = 단추 점(흐림 · 덮개 없음)
|
||||
"off": busy, # 셈 동안 단추만 잠금
|
||||
"label": BUSY if busy else IDLE,
|
||||
"busy": busy,
|
||||
"fetches": fetches,
|
||||
"applyOff": stale,
|
||||
}
|
||||
|
||||
|
||||
def look(row):
|
||||
return {k: v for k, v in row.items() if k != "applyTitle"}
|
||||
def look(row, *skip):
|
||||
return {k: v for k, v in row.items() if k not in ("applyTitle", *skip)}
|
||||
|
||||
|
||||
def test_manual_update_answer_lands_after_same_rerun(r):
|
||||
assert look(r["moved"]) == state(True, True, False, 0)
|
||||
assert look(r["sameAgain"]) == state(True, True, False, 0) # 판 번호만 바뀜 — 다시 안 흐림
|
||||
assert r["sent"]["fetches"] == 1 and r["sent"]["busy"] and not r["sent"]["veil"]
|
||||
def test_moved_keeps_old_picture_no_auto(r):
|
||||
assert look(r["start"]) == {**state(False, False, 0), "applyOff": True} # 제안 없음
|
||||
assert look(r["moved"]) == state(True, False, 0)
|
||||
assert look(r["sameAgain"]) == state(True, False, 0) # 판 번호만 바뀜
|
||||
assert look(r["noAuto"]) == state(True, False, 0) # 60 s 지나도 셈 없음
|
||||
|
||||
|
||||
def test_update_answer_lands_after_same_rerun(r):
|
||||
assert look(r["sent"], "body") == state(True, True, 1)
|
||||
assert r["sent"]["body"]["route"]["rev"] == 2 # 지금 초안 노선으로
|
||||
rerun = r["rerunWhileBusy"]
|
||||
assert not rerun["aborted"] and rerun["busy"] # 같은 입력 다시 돎 — 요청 그대로
|
||||
assert look(r["answered"]) == {**state(False, False, False, 1), "applied": 1}
|
||||
assert look(r["answered"], "applied") == state(False, False, 1)
|
||||
assert r["answered"]["applied"] == 1
|
||||
|
||||
|
||||
def test_suggestions_and_apply(r):
|
||||
@@ -65,92 +74,49 @@ def test_suggestions_and_apply(r):
|
||||
assert len(r["suggest"]["rows"]) == 1
|
||||
points = {p["chainage_m"]: p for p in r["applied"]["points"]}
|
||||
assert points[100]["options"]["pipe_diameter_mm"] == 1200
|
||||
assert 200 not in points # 관 자리 후보는 [적용]이 안 더함 — [자동 제안] 몫(369)
|
||||
assert 200 not in points
|
||||
assert r["applied"]["remembered"] == 1
|
||||
|
||||
|
||||
def test_자동_제안_두_방식_초기화_세류선만_한_걸음(r):
|
||||
assert r["proposeStale"] == {
|
||||
"off": True,
|
||||
"title": "B05_Drainage_Btn_Propose_Stale",
|
||||
"calls": 0, # 낡은 동안 끔 — 눌러도 요청 없음
|
||||
}
|
||||
assert r["proposeFresh"] == {"off": False, "title": "B05_Drainage_Btn_Propose_Tip"}
|
||||
# [자동 제안] 누름 = 패널 후보 목록(510 · test_510) — 여기선 초기화만 초안
|
||||
assert r["propose"] == {
|
||||
"autos": [None],
|
||||
"points": [None], # 초기화 = 목록 없이(세류선만)
|
||||
"remembered": 1,
|
||||
"records": 1, # 답마다 사진첩 한 걸음
|
||||
}
|
||||
# 저장 분석이 옛 노선 — 지금 노선으로 셈 길(detail-basins 안 부름) · 같은 초안 · 한 걸음
|
||||
assert r["proposeLive"] == {
|
||||
"resetPoints": None,
|
||||
"resetAuto": None,
|
||||
"detailCalls": 0,
|
||||
"remembered": 3, # 앞 「409 → 다시」 한 번 포함
|
||||
"records": 3,
|
||||
}
|
||||
fallback = r["proposeFallback"]
|
||||
assert fallback["sent"] == 1 and fallback["points"] is None
|
||||
assert "낡음" not in fallback["status"] # 409 글을 오류로 안 띄움(조용히 다시)
|
||||
assert r["records"] == 1 # [적용] 한 번 = 사진첩 한 걸음
|
||||
|
||||
|
||||
def test_move_while_busy_cancels(r):
|
||||
moved = r["movedWhileBusy"]
|
||||
assert moved["aborted"] and moved["veil"] and not moved["busy"]
|
||||
assert look(r["fresh"]) == state(False, False, False, 3)
|
||||
assert moved["aborted"] and look(moved, "aborted") == state(True, False, 2)
|
||||
assert r["promised"] is True
|
||||
assert look(r["fresh"]) == state(False, False, 3)
|
||||
|
||||
|
||||
def test_update_always_recomputes(r):
|
||||
assert look(r["again"], "route") == {**state(False, True, 4), "applyOff": False}
|
||||
assert look(r["againDone"]) == state(False, False, 4)
|
||||
|
||||
|
||||
def test_stale_blocks_apply(r):
|
||||
assert r["movedWhileBusy"]["applyOff"]
|
||||
assert r["movedWhileBusy"]["applyTitle"] == STALE_TITLE
|
||||
assert r["staleApply"] == 1 # 눌러도 옛 제안이 안 들어감(앞 [적용] 한 번뿐)
|
||||
assert not r["fresh"]["applyOff"] and r["fresh"]["applyTitle"] != STALE_TITLE
|
||||
assert r["fresh"]["applyTitle"] != STALE_TITLE
|
||||
assert r["proposeStale"] == {"off": True, "title": "B05_Drainage_Btn_Propose_Stale"}
|
||||
assert r["proposeFresh"] == {"off": False, "title": "B05_Drainage_Btn_Propose_Tip"}
|
||||
|
||||
|
||||
def test_saved_stale_shows_update(r):
|
||||
assert {k: v for k, v in look(r["savedStale"]).items() if k != "status"} == state(
|
||||
True, True, False, 3
|
||||
)
|
||||
def test_saved_stale_marks_only(r):
|
||||
assert look(r["savedStale"], "status") == state(True, False, 4)
|
||||
assert r["savedStale"]["status"] == "배수유역 낡음"
|
||||
|
||||
|
||||
def test_auto_mode(r):
|
||||
assert look(r["autoSwitch"]) == state(
|
||||
True, False, False, 3, True
|
||||
) # 자동 — 흐림 없음 · [업데이트] 없음
|
||||
assert r["auto2999"]["fetches"] == 3
|
||||
assert r["auto3000"]["fetches"] == 4 and r["auto3000"]["route"] is None
|
||||
assert r["auto3000"]["busy"] and not r["auto3000"]["blur"] # 계산 중 = 구석 표시만
|
||||
assert look(r["autoDone"]) == state(False, False, False, 4)
|
||||
assert look(r["autoRestart"]) == state(
|
||||
True, False, False, 4, True
|
||||
) # 2 s + 2 s — 처음부터 다시 3 s
|
||||
assert look(r["autoMoved"]) == {**state(False, False, False, 5), "mode": "auto"}
|
||||
assert look(r["autoSavedStale"]) == state(True, False, False, 5, True)
|
||||
assert look(r["autoSavedDone"]) == state(False, False, False, 6)
|
||||
assert look(r["savedStaleNoAuto"]) == state(True, False, 4)
|
||||
assert look(r["savedDone"]) == state(False, False, 5)
|
||||
|
||||
|
||||
def test_undo_back_to_saved(r):
|
||||
# 자동 — 끌기 뒤 1 s 에 되돌리기: 3 s 셈 없음 · 저장분 하나 · 답 오면 낡음 풂
|
||||
assert look(r["autoUndoWaiting"]) == {**state(True, False, False, 6, True), "savedLoads": 2}
|
||||
assert r["autoUndoNoFetch"]["fetches"] == 6
|
||||
assert look(r["autoUndo"]) == {**state(False, False, False, 6), "savedLoads": 2}
|
||||
# 수동 — [업데이트] 요청 중 되돌리기: 초안용 요청 끊음 · 저장분 답 올 때까지 흐림 그대로
|
||||
waiting = r["undoWaiting"]
|
||||
assert waiting["aborted"] and waiting["stale"] and waiting["blur"] and not waiting["busy"]
|
||||
assert look(r["undo"]) == {**state(False, False, False, 7), "savedLoads": 3}
|
||||
# 저장분 자체가 낡음 = 흐림 · [업데이트] · [적용] 꺼짐 그대로
|
||||
assert look(r["undoSavedStale"]) == {
|
||||
**state(True, True, False, 7),
|
||||
"savedLoads": 4,
|
||||
"mode": "manual",
|
||||
}
|
||||
assert waiting["aborted"] and look(waiting, "aborted", "savedLoads") == state(True, False, 6)
|
||||
assert look(r["undo"], "savedLoads") == state(False, False, 6)
|
||||
assert r["undo"]["savedLoads"] == 2
|
||||
assert look(r["undoSavedStale"], "savedLoads") == state(True, False, 6)
|
||||
assert r["undoSavedStale"]["savedLoads"] == 3
|
||||
|
||||
|
||||
def test_input_key(r):
|
||||
assert look(r["keys"]) == {
|
||||
assert r["keys"] == {
|
||||
"rev": True,
|
||||
"order": True,
|
||||
"node": False,
|
||||
|
||||
@@ -7,7 +7,9 @@ LOCALE = Path(__file__).resolve().parents[2] / "ui_template" / "ui_template_loca
|
||||
|
||||
def test_drainage_buttons_are_nouns():
|
||||
text = LOCALE.read_text(encoding="utf-8")
|
||||
assert 'B05_Drainage_Btn_Analyze: ["유역 재분할",' in text
|
||||
assert 'B05_Drainage_Btn_DeleteSelected: ["관 삭제",' in text
|
||||
# [유역 재분할] · [관 삭제] · [배관 배치 초기화] 걷음 → [업데이트] 하나(요구 326-6)
|
||||
assert "B05_Drainage_Btn_Analyze" not in text
|
||||
assert "B05_Drainage_Btn_DeleteSelected" not in text
|
||||
assert "B05_Drainage_Btn_Auto:" not in text
|
||||
assert "유역 다시 나누기" not in text
|
||||
assert "선택한 관 삭제" not in text
|
||||
|
||||
@@ -80,6 +80,7 @@ SESSION_PULLS = (
|
||||
"ui_template/ui_template_locale_b2.ts",
|
||||
"ui_template/ui_template_locale_b3.ts",
|
||||
"ui_template/ui_template_locale_b4.ts",
|
||||
"ui_template/ui_template_locale_b5.ts",
|
||||
"ui_template/ui_template_locale_common.ts",
|
||||
"ui_template/ui_template_locale_m1.ts",
|
||||
"ui_template/ui_template_locale_m2.ts",
|
||||
|
||||
@@ -46,7 +46,7 @@ def test_6b_host_save_클래스_없음():
|
||||
|
||||
|
||||
def test_4_2_안_쓰는_글_없음():
|
||||
locale = _read(ROOT / "ui_template" / "ui_template_locale_b2.ts")
|
||||
locale = _read(ROOT / "ui_template" / "ui_template_locale_b5.ts") # B08 칸 = b5(512-2 나눔)
|
||||
for key in (
|
||||
"B08_Quantity_Side_Pending",
|
||||
"B08_Quantity_Btn_Confirm",
|
||||
|
||||
@@ -152,7 +152,7 @@ def test_화면_고른_것만_넣음_라벨_왕복():
|
||||
"측점 420 m — 간격 보충(흐름이 모이는 자리)",
|
||||
] # held 는 목록 밖
|
||||
assert got["title"] == "자동 제안 3건 · 있는 관 1 뺌"
|
||||
assert got["addOff"] is True and got["candidates"] == [50, 320, 420]
|
||||
assert got["addOff"] is True and got["noCircles"] # 배수유역도 점선 원 없음(326-6)
|
||||
assert got["added"] == [[320, "S4"], [420, None]] # 고른 둘만
|
||||
assert got["afterClose"] == {"hidden": True, "candidates": []}
|
||||
assert got["afterClose"] == {"hidden": True}
|
||||
assert got["carried"] == {"stream": "S4", "order": 1} # 응답 → 보관소 → 요청
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
"""512-2(요구 326-6) 배수유역도 [업데이트] — 점선 원 · 단추 셋 걷음 · 노선선 = 답의 노선 · B06 [>] 확인 창.
|
||||
|
||||
- 서버 답 `route_xy` = 그 답을 셈한 노선(사업지 m) — 화면 노선선 · 관 자리 · 유역이 늘 짝
|
||||
(초안 노선 [업데이트] 뒤 저장 노선을 그려 생기던 「노선 곁 빈 곳」).
|
||||
- 낡은 답(세부 유역 없음)이 와도 그림이 있으면 옛 그림 그대로 · 관만 받음.
|
||||
- 흐름(낡음 · 늘 다시 셈 · 저절로 셈 없음)은 test_349 · 화면 실측은 PLAN 512-2.
|
||||
"""
|
||||
|
||||
from pathlib import Path
|
||||
from uuid import uuid4
|
||||
|
||||
from B04_PreProcess import B04_PreProcess_Router_Basins as basins
|
||||
from common_util.common_util_drainage_context import DrainageContext
|
||||
from common_util.common_util_drainage_detail import DrainageDetail, pipes_from_chainages
|
||||
from common_util.common_util_route_geometry import RouteVertex
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[2]
|
||||
B05 = ROOT / "B05_Profile"
|
||||
|
||||
|
||||
def _read(path: Path) -> str:
|
||||
return path.read_text(encoding="utf-8")
|
||||
|
||||
|
||||
def test_답에_셈한_노선이_실림():
|
||||
vertices = [RouteVertex(10.0, 20.0, 100.0, 0.0), RouteVertex(310.1234, 20.0, 90.0, 300.1234)]
|
||||
context = DrainageContext(stored_path="x", project_root=ROOT, vertices=vertices)
|
||||
detail = DrainageDetail(route_lonlat=[], pipes=pipes_from_chainages(vertices, [100.0]))
|
||||
points = basins.carry_facility_attributes(basins._rebuilt(detail), [])
|
||||
payload = basins._payload(uuid4(), context, detail, points, False)
|
||||
assert payload["route_xy"] == [[10.0, 20.0], [310.123, 20.0]]
|
||||
|
||||
|
||||
def test_단추는_업데이트_자동제안_둘():
|
||||
chrome = _read(B05 / "B05_Profile_UI_Drainage_Chrome.ts")
|
||||
assert "header.append(updateButton, proposeButton);" in chrome
|
||||
for gone in ("analyzeButton", "deleteButton", "autoButton"):
|
||||
assert gone not in chrome
|
||||
locale = _read(ROOT / "ui_template" / "ui_template_locale_b2.ts")
|
||||
assert "B05_Drainage_Btn_Auto:" not in locale and "B05_Drainage_Btn_Analyze" not in locale
|
||||
|
||||
|
||||
def test_점선_원_없음():
|
||||
render = _read(B05 / "B05_Profile_UI_Drainage_Render.ts")
|
||||
assert "candidates" not in render and "setLineDash" not in render
|
||||
assert "candidates" not in _read(B05 / "B05_Profile_UI_Drainage_Propose.ts")
|
||||
|
||||
|
||||
def test_노선선은_답의_노선_낡은_답은_옛_그림():
|
||||
panel = _read(B05 / "B05_Profile_UI_Drainage_Panel.ts")
|
||||
apply = panel[panel.index("function apply(") : panel.index("function takePipes(")]
|
||||
assert apply.index("if (keep) return void takePipes(response);") < apply.index("basins = ")
|
||||
assert "const keep = !!response.stale && basins.length > 0;" in apply
|
||||
assert "takeRoute(answered, false)" in apply
|
||||
assert "if (!routeFromAnswer) takeRoute(points, true);" in panel # 페이지 노선 = 첫 답 전만
|
||||
|
||||
|
||||
def test_B06_다음은_낡으면_확인_뒤_업데이트():
|
||||
shared = _read(ROOT / "A00_Common" / "design" / "design_shared.ts")
|
||||
go = shared[shared.index("export function confirmAndGo(") :]
|
||||
go = go[: go.index("\n}\n", go.index("async function drainageFirst("))]
|
||||
assert "void drainageFirst(shared).then((ready) => (ready ? go() : undefined));" in go
|
||||
first = go[go.index("async function drainageFirst(") :]
|
||||
assert (
|
||||
first.index("side?.stale()")
|
||||
< first.index("showConfirmDialog(")
|
||||
< first.index("side.update()")
|
||||
)
|
||||
Reference in New Issue
Block a user