fix(B05): 노선 추천 창 반입 · 사토 표기를 누가토량(±)으로 바꿈(78)

- 한 줄에서 반입 · 사토 뺌 — 누가토량 +/− 만
- 후보 표 반입 칸 → 누가토량(±) 칸
- 토공 최소 도움말 문구도 누가토량으로

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019EMfGERKUg5nMMNQsyVZ2j
This commit is contained in:
2026-10-07 23:15:46 +09:00
co-authored by Claude Opus 5.5
parent 0deeb52e9a
commit 11148c60fb
3 changed files with 11 additions and 15 deletions
@@ -55,9 +55,9 @@ const LOGIC_HELP: Record<ShownLogic, HelpEntry> = {
"후보 중 절토 + 성토 어림 양이 가장 적은 것을 고름",
"흙 양 = 계획고를 상한 안으로 맞춘 선과 옆경사 단면(노폭 4m)으로 어림",
],
when: "토공량 · 사토 처리를 줄여야 할 때",
when: "토공량 · 누가토량(±)을 줄여야 할 때",
caution: "비탈면 흙은 빠진 어림값 · 연장이 길어지는지 비교값으로 함께 볼 것",
profile: "후보마다 균형 종단을 세워 절 + 성 + 반입 + 사토 ㎥ 로 비교",
profile: "후보마다 균형 종단을 세워 절토 + 성토 + 누가토량(± 크기) ㎥ 로 비교",
},
gentle: {
summary: "종단기울기가 가장 순한 노선",
@@ -13,11 +13,9 @@ export interface BalanceProfile {
stationsM: number[];
groundM: number[];
planM: number[];
/** 절토(자연) · 성토 · 반입 · 사토 ㎥. */
/** 절토(자연) · 성토 ㎥. */
cutM3: number;
fillM3: number;
borrowM3: number;
spoilM3: number;
/** 누가토량 끝값(절토 다짐 − 성토) · 허용 ±㎥. */
massEndM3: number;
massToleranceM3: number;
@@ -43,7 +41,7 @@ export function parseBalance(raw: unknown): BalanceProfile | null {
const stationsM = numbers(p.stations_m);
const groundM = numbers(p.ground_m);
const planM = numbers(p.plan_m);
const need = [p.cut_m3, p.fill_m3, p.borrow_m3, p.spoil_m3, p.max_grade_pct].map(num);
const need = [p.cut_m3, p.fill_m3, p.max_grade_pct].map(num);
if (stationsM.length < 2 || groundM.length !== stationsM.length) return null;
if (planM.length !== stationsM.length || need.some((value) => value === null)) return null;
return {
@@ -52,8 +50,6 @@ export function parseBalance(raw: unknown): BalanceProfile | null {
planM,
cutM3: num(p.cut_m3)!,
fillM3: num(p.fill_m3)!,
borrowM3: num(p.borrow_m3)!,
spoilM3: num(p.spoil_m3)!,
massEndM3: num(p.mass_end_m3) ?? 0,
massToleranceM3: num(p.mass_tolerance_m3) ?? 500,
maxGradePct: num(p.max_grade_pct)!,
@@ -68,12 +64,11 @@ const m3 = (value: number): string => `${Math.round(value).toLocaleString("ko-KR
const pct = (value: number): string => `${value.toFixed(1)}%`;
const signedM3 = (value: number): string => `${value > 0 ? "+" : ""}${m3(value)}`;
/** 한 줄 — 「균형 종단: 절 7,646 · 성 7,944 · 반입 0 · 사토 848㎥ · 누가토량 +212㎥(허용 ±500㎥) · …」. */
/** 한 줄 — 「균형 종단: 절 7,646 · 성 7,944㎥ · 누가토량 +212㎥(허용 ±500㎥) · …」. */
export function balanceLine(balance: BalanceProfile | null): string {
if (!balance) return "";
const parts = [
`균형 종단: 절 ${m3(balance.cutM3)} · 성 ${m3(balance.fillM3)}`,
`반입 ${m3(balance.borrowM3)} · 사토 ${m3(balance.spoilM3)}`,
`누가토량 ${signedM3(balance.massEndM3)}(허용 ±${m3(balance.massToleranceM3)})`,
`최대 종단경사 ${pct(balance.maxGradePct)}(상한 ${pct(balance.gradeLimitPct)})`,
];
@@ -123,17 +118,17 @@ export function profileSvg(
<path class="is-plan" d="${planM.length ? path(planM) : ""}" vector-effect="non-scaling-stroke" /></svg>`;
}
/** 이 모달에서 셈한 후보들 — 갈래 이름 · 절 · 성 · 반입 · 최대 종단경사 표. 비면 빈 글. */
/** 이 모달에서 셈한 후보들 — 갈래 이름 · 절 · 성 · 누가토량(±) · 최대 종단경사 표. 비면 빈 글. */
export function candidatesTable(rows: Array<[string, BalanceProfile]>): string {
if (!rows.length) return "";
const body = rows
.map(
([name, b]) =>
`<tr><th scope="row">${name}</th><td>${m3(b.cutM3)}</td><td>${m3(b.fillM3)}</td>` +
`<td>${m3(b.borrowM3)}</td><td>${pct(b.maxGradePct)}</td></tr>`,
`<td>${signedM3(b.massEndM3)}</td><td>${pct(b.maxGradePct)}</td></tr>`,
)
.join("");
return `<table><thead><tr><th scope="col">후보</th><th scope="col">절토</th>
<th scope="col">성토</th><th scope="col">반입</th><th scope="col">최대 종단경사</th></tr></thead>
<th scope="col">성토</th><th scope="col">누가토량</th><th scope="col">최대 종단경사</th></tr></thead>
<tbody>${body}</tbody></table>`;
}
+3 -2
View File
@@ -112,12 +112,13 @@ def test_균형_종단은_캐시에_안_남는다() -> None:
)
assert proc.returncode == 0, proc.stderr
result = json.loads(proc.stdout)["balance63"]
assert result["parsed"]["borrowM3"] == 0 and result["parsed"]["spoilM3"] == 39.9
assert result["parsed"]["planM"] == [100, 101.5, 101]
assert "balance" not in result["cached"]
assert result["cached"]["metrics"]["cutM3"] is not None # 비교값은 그대로 캐시
assert result["missing"] is None and result["broken"] is None
assert "반입 0㎥" in result["line"] and "덮개 모자란 관 1/2" in result["line"]
assert "반입" not in result["line"] and "사토" not in result["line"]
assert "덮개 모자란 관 1/2" in result["line"]
assert "누가토량 -40㎥(허용 ±500㎥)" in result["line"]
assert result["svg"].count("<path") == 2
assert '<th scope="row">토공 최소</th>' in result["table"]
assert '<th scope="col">누가토량</th>' in result["table"] and "반입" not in result["table"]