feat(B05): 노선 추천 — 알약 여섯(짧은 노선 · 흙 균형) · 남는 흙 비교값 · 같은 노선 알림 줄 · 비중 고정 시험 (PLAN 23-4c 화면 · 26-2c)
- 알약: 예상노선 추적 · 토공 최소 · 기울기 순한 · 짧은 노선(short) · 흙 균형(balance) · 비중 계산 — 짧은 노선 · 흙 균형은 비중 없음 - 비교값에 「남는 흙」(surplus_m3 · + 남음 / − 모자람) - 응답 similar · band_width_m → 비교값 아래 「이 지형은 제약 안에서 갈 길이 폭 …」 한 줄 + 흐린 안내 · 없으면 줄 숨김 - 시험: 가짜 응답 · 여섯 이름 · 남는 흙 · 알림 줄 · 비중 계산 사유 · 왕복 시험에 short · balance 더함 Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -298,6 +298,63 @@ const weightLocks = {
|
||||
},
|
||||
};
|
||||
|
||||
// ⑨ 추천 갈래 여섯 · 남는 흙 · 같은 노선 알림(계획서 23-4c 화면).
|
||||
const withExtras = (extra, metricExtra = {}) =>
|
||||
model.normalizeRouteInitial(
|
||||
{ ...plainResponse, ...extra, metrics: { ...plainResponse.metrics, ...metricExtra } },
|
||||
"short",
|
||||
null,
|
||||
);
|
||||
const sixth = {
|
||||
labels: Object.fromEntries(
|
||||
["follow", "earthwork", "gentle", "short", "balance", "weighted"].map((k) => [k, model.LOGIC_LABEL[k]]),
|
||||
),
|
||||
surplus: [122.4, -578.2, 0, 0.4].map((v) => model.surplusText(v)),
|
||||
metricsPlus: model.metricsLine(withExtras({}, { surplus_m3: 122.4 }).metrics),
|
||||
metricsMinus: model.metricsLine(withExtras({}, { surplus_m3: -578 }).metrics),
|
||||
metricsNone: model.metricsLine(withExtras({}).metrics),
|
||||
similarTwo: model.similarLine(
|
||||
withExtras({
|
||||
band_width_m: 13.5,
|
||||
similar: [
|
||||
{ logic: "earthwork", max_gap_m: 0.0 },
|
||||
{ logic: "gentle", max_gap_m: 6.1 },
|
||||
],
|
||||
}),
|
||||
),
|
||||
similarOne: model.similarLine(
|
||||
withExtras({ band_width_m: 13.54, similar: [{ logic: "balance", max_gap_m: 6.1 }] }),
|
||||
),
|
||||
similarNoBand: model.similarLine(withExtras({ similar: [{ logic: "weighted", max_gap_m: 9.1 }] })),
|
||||
similarEmpty: model.similarLine(withExtras({ band_width_m: 13.5, similar: [] })),
|
||||
similarMissing: model.similarLine(withExtras({})),
|
||||
similarNull: model.similarLine(null),
|
||||
similarUnknown: withExtras({ similar: [{ logic: "nope", max_gap_m: 1 }, { logic: "short", max_gap_m: "x" }] }).similar,
|
||||
bandParsed: withExtras({ band_width_m: 13.5 }).bandWidthM,
|
||||
hint: model.SIMILAR_HINT,
|
||||
weightedViolation: model.violationsLine({
|
||||
...plain.metrics,
|
||||
violations: [
|
||||
{
|
||||
kind: "grade",
|
||||
station: "No.9+0.0",
|
||||
value: 29.1,
|
||||
limit: 14,
|
||||
reason: "비중 계산 — 예상노선 몫만큼 제약을 막지 않음(측점 사이 지반이 상한을 넘음)",
|
||||
},
|
||||
],
|
||||
}),
|
||||
weightedViolationReason: model.violationText({
|
||||
kind: "grade",
|
||||
station: "No.9+0.0",
|
||||
value: 29.1,
|
||||
limit: 14,
|
||||
reason: "비중 계산 — 예상노선 몫만큼 제약을 막지 않음",
|
||||
}),
|
||||
shortLabel: model.choiceLabel("short", { expected: 0, earthwork: 0, grade: 0 }),
|
||||
balanceLabel: model.choiceLabel("balance", null),
|
||||
};
|
||||
|
||||
// ④ 캐시 왕복 — 결과 통 프로젝트 칸. 꼴이 틀린 값은 안 읽는다.
|
||||
model.writeInitialChoice("p-1", plain);
|
||||
const readBack = model.readInitialChoice("p-1");
|
||||
@@ -341,5 +398,6 @@ process.stdout.write(
|
||||
historyRecs,
|
||||
sweep,
|
||||
weightLocks,
|
||||
sixth,
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -246,3 +246,59 @@ def test_비중_값_칸_직접_입력(results):
|
||||
"ok": False,
|
||||
"weights": {"expected": 70, "earthwork": 20, "grade": 10},
|
||||
}
|
||||
|
||||
|
||||
def test_추천_갈래_여섯_이름과_가중치_없음(results):
|
||||
six = results["sixth"]
|
||||
assert six["labels"] == {
|
||||
"follow": "예상노선 추적",
|
||||
"earthwork": "토공 최소",
|
||||
"gentle": "기울기 순한",
|
||||
"short": "짧은 노선",
|
||||
"balance": "흙 균형",
|
||||
"weighted": "비중 계산",
|
||||
}
|
||||
# 짧은 노선 · 흙 균형은 비중이 없다(weights 0/0/0 이 와도 이름만)
|
||||
assert six["shortLabel"] == "짧은 노선" and six["balanceLabel"] == "흙 균형"
|
||||
page = os.path.join(HERE, "..", "..", "B05_Profile", "B05_Profile_UI_RouteEdit_Recommend.ts")
|
||||
with open(page, encoding="utf-8") as handle:
|
||||
text = handle.read()
|
||||
order = [
|
||||
text.index(f'"{name}"') for name in ("follow", "earthwork", "gentle", "short", "balance")
|
||||
]
|
||||
assert order == sorted(order) # 알약 순서: 추적 · 토공 · 기울기 · 짧은 · 균형 · 비중
|
||||
|
||||
|
||||
def test_비교값_남는_흙(results):
|
||||
six = results["sixth"]
|
||||
assert six["surplus"] == ["+122㎥(남음)", "−578㎥(모자람)", "0㎥(균형)", "0㎥(균형)"]
|
||||
assert six["metricsPlus"].endswith(
|
||||
"성토 900㎥ · 남는 흙 +122㎥(남음) · 기준 초과 2구간 · 이격 3.3m"
|
||||
)
|
||||
assert "남는 흙 −578㎥(모자람)" in six["metricsMinus"]
|
||||
# 서버가 안 주면 칸이 없다(옛 답) — 기존 비교값 글 그대로
|
||||
assert "남는 흙" not in six["metricsNone"]
|
||||
|
||||
|
||||
def test_같은_노선_알림_줄(results):
|
||||
six = results["sixth"]
|
||||
assert six["similarTwo"] == (
|
||||
"이 지형은 제약 안에서 갈 길이 폭 13.5m 한 띠뿐 — "
|
||||
"토공 최소 · 기울기 순한과 거의 같은 노선(가장 먼 거리 6.1m)"
|
||||
)
|
||||
assert six["similarOne"] == (
|
||||
"이 지형은 제약 안에서 갈 길이 폭 13.5m 한 띠뿐 — 흙 균형과 거의 같은 노선(가장 먼 거리 6.1m)"
|
||||
)
|
||||
# 띠 폭이 없어도 줄은 선다 · 받침 없는 이름은 「와」
|
||||
assert "폭" not in six["similarNoBand"] and "비중 계산과 거의" in six["similarNoBand"]
|
||||
# 없으면 줄 숨김 — 빈 목록 · 칸 없음 · 선택 없음
|
||||
assert six["similarEmpty"] == "" and six["similarMissing"] == "" and six["similarNull"] == ""
|
||||
assert six["similarUnknown"] == [] # 이름 · 거리를 모르는 항목은 버림
|
||||
assert six["bandParsed"] == 13.5
|
||||
assert "제약 값을 바꾸면" in six["hint"]
|
||||
|
||||
|
||||
def test_비중_계산_넘은_구간_사유_그대로(results):
|
||||
six = results["sixth"]
|
||||
assert six["weightedViolation"] == "제약 못 지킨 구간 1곳: No.9+0.0 29.1%(비중 계산)"
|
||||
assert six["weightedViolationReason"] == "No.9+0.0 29.1%(비중 계산)"
|
||||
|
||||
@@ -43,6 +43,8 @@ LOGICS: list[tuple[str, dict[str, float] | None]] = [
|
||||
("follow", None),
|
||||
("earthwork", None),
|
||||
("gentle", None),
|
||||
("short", None),
|
||||
("balance", None),
|
||||
("weighted", {"expected": 50, "earthwork": 25, "grade": 25}),
|
||||
]
|
||||
|
||||
|
||||
Reference in New Issue
Block a user