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

This commit is contained in:
2026-10-22 20:13:49 +09:00
29 changed files with 766 additions and 963 deletions
@@ -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 };
+2 -3
View File
@@ -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 나눔)
"수량 원천: 수기 입력(직접비)",
"수량 원천: 수기 입력 공종",
],
+3 -1
View File
@@ -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]
+50 -84
View File
@@ -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()")
)