fix(B06): 채집석 공제도 축을 맞춰 뺌 — 갈래마다 ×C
채집석은 **벽 입적(제자리 부피)**이라 자연 축이고 유토곡선은 다짐 축임. 축이 다른 값을 그냥 빼고 있었음(리핑암이면 13% 어긋남). - 갈래별 값(`collected_stone_by_ground_m3`)이 오면 갈래마다 ×C 해서 뺌 - 갈래를 못 가른 몫은 계수가 없어 **환산하지 않고** 그대로 뺌 - 갈래가 아예 안 오면 종전처럼 총량을 그대로 뺌 (안 온 것과 0 은 다름) - 통로 정리 — `_mass_haul_context` 가 B08 입력 dict 를 통째로 받게 함 (칸이 늘 때마다 인자를 늘리다 빠뜨리던 자리) ⚠ 벽 입적과 원바닥 암 부피의 관계를 정한 원문이 없음 — 그 가정은 그대로 두고 **축만** 맞춘 것임(세 창 합의, 근거 문구는 B08 이 값과 함께 보냄). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -348,10 +348,22 @@ function bandOutline(
|
||||
*
|
||||
* 돌려주는 값은 **실제로 뺀 양(㎥)**. 사토가 모자라면 받은 값보다 작다.
|
||||
*/
|
||||
function applyCollectedStoneDeduction(residuals: HaulResidual[], deduction: number | null): number {
|
||||
if (deduction === null || !Number.isFinite(deduction) || deduction <= 0) return 0;
|
||||
function applyCollectedStoneDeduction(
|
||||
residuals: HaulResidual[],
|
||||
deduction: number | null,
|
||||
byGround?: Record<string, number> | null,
|
||||
groundUnknownM3?: number | null,
|
||||
conversion?: EarthworkConversion | null,
|
||||
): number {
|
||||
// ⚠ **축을 맞춘 뒤 뺀다**(2026-09-09 세 창 확정) — 채집석은 **벽 입적(제자리 부피)**이라
|
||||
// 자연 축이고 이 곡선은 다짐 축이다. 갈래별 값이 오면 **갈래마다 ×C** 해서 뺀다.
|
||||
// 갈래를 못 가른 몫은 **계수가 없어 환산하지 않고** 그대로 뺀다(근거 없이 안 눅인다).
|
||||
// 갈래가 아예 안 오면 종전처럼 총량을 그대로 뺀다 — 값이 안 오는 것과 0 은 다르다.
|
||||
const converted = compactedStoneDeduction(byGround, groundUnknownM3, conversion);
|
||||
const amountToTake = converted === null ? deduction : converted;
|
||||
if (amountToTake === null || !Number.isFinite(amountToTake) || amountToTake <= 0) return 0;
|
||||
const spoils = residuals.filter((residual) => residual.kind === "spoil");
|
||||
let left = deduction;
|
||||
let left = amountToTake;
|
||||
const take = (residual: HaulResidual, amount: number): void => {
|
||||
if (amount <= 0) return;
|
||||
const before = residual.volume_m3;
|
||||
@@ -375,7 +387,7 @@ function applyCollectedStoneDeduction(residuals: HaulResidual[], deduction: numb
|
||||
residual.natural_m3 -= amount;
|
||||
take(residual, amount);
|
||||
}
|
||||
return deduction - Math.max(left, 0);
|
||||
return amountToTake - Math.max(left, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -417,6 +429,33 @@ const GROUND_OF_BUCKET = {
|
||||
br_m3: "blasting_rock",
|
||||
} as const;
|
||||
|
||||
/** 환산계수 이름 ↔ 채집석이 오는 갈래 이름. B08 이 `soil`·`ripping_rock`·`blasting_rock` 로 보낸다. */
|
||||
const STONE_GROUND_KEYS = ["soil", "ripping_rock", "blasting_rock"] as const;
|
||||
|
||||
/**
|
||||
* 채집석 공제를 **다짐 축**으로 옮긴다 — 갈래마다 ×C, 갈래를 모르는 몫은 그대로.
|
||||
* 갈래가 하나도 안 오면 `null`(= 「갈래를 안 보냈음」)이라 부르는 쪽이 총량으로 되돌아간다.
|
||||
*/
|
||||
function compactedStoneDeduction(
|
||||
byGround: Record<string, number> | null | undefined,
|
||||
groundUnknownM3: number | null | undefined,
|
||||
conversion: EarthworkConversion | null | undefined,
|
||||
): number | null {
|
||||
if (!byGround) return null;
|
||||
const entries = STONE_GROUND_KEYS.map((key) => [key, Number(byGround[key] ?? 0)] as const).filter(
|
||||
([, value]) => Number.isFinite(value) && value > 0,
|
||||
);
|
||||
const unknown = Number(groundUnknownM3 ?? 0);
|
||||
const hasUnknown = Number.isFinite(unknown) && unknown > 0;
|
||||
if (!entries.length && !hasUnknown) return null;
|
||||
let total = hasUnknown ? unknown : 0;
|
||||
for (const [ground, value] of entries) {
|
||||
const factor = conversion?.[ground]?.compacted;
|
||||
total += typeof factor === "number" && factor > 0 ? value * factor : value;
|
||||
}
|
||||
return total;
|
||||
}
|
||||
|
||||
/**
|
||||
* **자연상태 → 다짐상태**(× C). 구조물 잔토를 곡선에 얹기 전에 한 번만 거친다.
|
||||
*
|
||||
@@ -545,6 +584,10 @@ export function computeHaulPlan(
|
||||
limits: HaulEquipmentLimit[] | undefined,
|
||||
options?: {
|
||||
collected_stone_deduction_m3?: number | null;
|
||||
/** 갈래별 채집석(㎥) — **벽 입적(자연 축)**이라 여기서 ×C 해 다짐 축에 맞춘다. */
|
||||
collected_stone_by_ground_m3?: Record<string, number> | null;
|
||||
/** 갈래를 못 가른 채집석(㎥) — 계수가 없어 **환산하지 않고** 그대로 뺀다. */
|
||||
collected_stone_ground_unknown_m3?: number | null;
|
||||
structure_spoil_m3?: number | null;
|
||||
/** 측점별 구조물 잔토 — 오면 **이쪽이 이긴다**(그 자리 잔량에 얹어 운반거리를 맞춘다). */
|
||||
structure_spoil_points?: Array<{
|
||||
@@ -752,7 +795,13 @@ export function computeHaulPlan(
|
||||
options?.conversion ?? null,
|
||||
);
|
||||
const deductionInput = options?.collected_stone_deduction_m3 ?? null;
|
||||
const deducted = applyCollectedStoneDeduction(settled, deductionInput);
|
||||
const deducted = applyCollectedStoneDeduction(
|
||||
settled,
|
||||
deductionInput,
|
||||
options?.collected_stone_by_ground_m3 ?? null,
|
||||
options?.collected_stone_ground_unknown_m3 ?? null,
|
||||
options?.conversion ?? null,
|
||||
);
|
||||
const remaining = settled.filter((residual) => residual.volume_m3 > EPSILON);
|
||||
remaining.forEach((residual, index) => {
|
||||
residual.index = index + 1;
|
||||
|
||||
Reference in New Issue
Block a user