auto: 2026-09-30 07:36 (ESD_LAPTOP)

This commit is contained in:
2026-09-30 07:36:32 +09:00
parent bf21676ef5
commit 559d445e4e
6 changed files with 304 additions and 13 deletions
@@ -51,6 +51,68 @@ export function rebalanceWeights(
return { expected: out.expected, earthwork: out.earthwork, grade: out.grade };
}
/** 비중마다 고정 표시 — 고정한 값은 다른 비중을 움직여도 안 바뀐다. */
export type WeightLocks = Record<WeightKey, boolean>;
export const NO_WEIGHT_LOCKS: WeightLocks = { expected: false, earthwork: false, grade: false };
const lockedKeys = (locks: WeightLocks): WeightKey[] => WEIGHT_KEYS.filter((name) => locks[name]);
/** 나머지 둘이 고정돼 저절로 정해지는 비중 — 없으면 null. 이 비중은 잠김으로 보인다. */
export function derivedWeightKey(locks: WeightLocks): WeightKey | null {
const free = WEIGHT_KEYS.filter((name) => !locks[name]);
return free.length === 1 ? free[0] : null;
}
/** 이 비중의 고정 단추를 새로 누를 수 있나 — 셋 다 고정은 막는다(마지막 하나는 저절로 잠김).
* 이미 고정한 것을 푸는 것은 늘 된다. */
export function canLockWeight(locks: WeightLocks, key: WeightKey): boolean {
return locks[key] || lockedKeys(locks).length < 2;
}
/** 슬라이더로 한 비중을 옮긴다 — 고정한 것은 그대로, 안 고정한 것끼리 합 100 을 맞춘다.
* 고정하지 않은 것이 셋이면 나머지 둘이 지금 비율대로(`rebalanceWeights`), 둘이면 다른 하나가 남는 몫을
* 받고, 이 비중 혼자면(나머지 둘 고정) 값이 정해져 있어 안 움직인다. 값은 남는 몫 안으로 잘린다. */
export function moveWeight(
weights: RouteInitialWeights,
locks: WeightLocks,
key: WeightKey,
value: number,
): RouteInitialWeights {
if (locks[key]) return { ...weights };
const fixedSum = lockedKeys(locks).reduce((sum, name) => sum + weights[name], 0);
const room = 100 - fixedSum;
const free = WEIGHT_KEYS.filter((name) => !locks[name]);
if (free.length === WEIGHT_KEYS.length) return rebalanceWeights(weights, key, value);
const out = { ...weights };
if (free.length === 1) {
out[key] = room;
return out;
}
const other = free.find((name) => name !== key)!;
const next = Math.min(room, Math.max(0, Math.round(Number.isFinite(value) ? value : 0)));
out[key] = next;
out[other] = room - next;
return out;
}
/** 값 칸에 직접 넣은 숫자 — 0~100 정수이고 고정 규칙 아래 합 100 을 맞출 수 있을 때만 받는다.
* 못 맞추면 `ok: false`(칸에 빨간 테두리 · 서버에 안 보냄). 고정한 비중은 고칠 수 없다. */
export function inputWeight(
weights: RouteInitialWeights,
locks: WeightLocks,
key: WeightKey,
value: number,
): { ok: boolean; weights: RouteInitialWeights } {
const same = { ok: false, weights: { ...weights } };
if (locks[key] || !Number.isInteger(value) || value < 0 || value > 100) return same;
const room = 100 - lockedKeys(locks).reduce((sum, name) => sum + weights[name], 0);
const free = WEIGHT_KEYS.filter((name) => !locks[name]);
if (free.length === 1) return value === room ? { ok: true, weights: { ...weights } } : same;
if (value > room) return same;
return { ok: true, weights: moveWeight(weights, locks, key, value) };
}
/** 결과 비교값 — 서버가 셈해 준 것을 보이기만 한다. 없는 칸은 null. */
export interface InitialMetrics {
lengthM: number | null;