landing
All checks were successful
Deploy to Pages / build (push) Successful in 26s

This commit is contained in:
Andras Schmelczer 2026-09-11 20:34:36 +01:00
commit b9485e4097
32 changed files with 4692 additions and 65 deletions

98
benchmarks/metrics.ts Normal file
View file

@ -0,0 +1,98 @@
import type { FlooringPlan, FlooringSettings, SkirtingPlan } from '../src/lib/types';
/** Evaluate public plan output. Keep these definitions fixed when comparing search changes. */
export function flooringMetrics(plan: FlooringPlan, s: FlooringSettings) {
let shapingCuts = 0;
let ripCuts = 0;
let patternPenalty = 0;
let repeats = 0;
let similarSteps = 0;
let staircases = 0;
for (const piece of plan.pieces) {
ripCuts += piece.ripEdge === 'both' ? 2 : Number(!!piece.ripEdge);
if (!piece.shaped) continue;
const close = (a: number, b: number) => Math.abs(a - b) <= 0.01;
shapingCuts += piece.template.filter((p, i, path) => {
const q = path[(i + 1) % path.length];
return !(
(close(p.x, q.x) && (close(p.x, 0) || close(p.x, piece.length))) ||
(close(p.y, q.y) && (close(p.y, 0) || close(p.y, piece.width)))
);
}).length;
}
const history = new Map<string, number[]>();
const step = (from: number, to: number) => {
const value = (((to - from + s.length / 2) % s.length) + s.length) % s.length;
return ((Math.round(value * 1000) / 1000) % s.length) - s.length / 2;
};
const similar = Math.max(s.kerf * 2, s.length * 0.03);
for (const row of s.pattern === 'random' ? plan.rows : []) {
const recent = history.get(row.roomId) ?? [];
const phase = row.offset;
if (recent.length >= 2) {
const movement = step(recent.at(-1)!, phase);
const previous = step(recent.at(-2)!, recent.at(-1)!);
patternPenalty += Number(Math.abs(Math.abs(movement) - s.length / 2) <= 0.01);
for (let distance = 2; distance <= recent.length; distance++) {
if (Math.abs(step(recent.at(-distance)!, phase)) <= similar) {
patternPenalty += 5 - distance;
if (distance === 2) repeats++;
}
}
if (movement * previous > 0) {
patternPenalty++;
if (Math.abs(movement - previous) <= similar) {
patternPenalty += 3;
similarSteps++;
}
if (recent.length >= 3 && previous * step(recent.at(-3)!, recent.at(-2)!) > 0) {
patternPenalty += 2;
staircases++;
}
}
}
history.set(row.roomId, [...recent, phase].slice(-4));
}
return {
cuts: plan.cuts,
shapingCuts,
ripCuts,
patternPenalty,
repeats,
similarSteps,
staircases,
boards: plan.boardCount,
waste: plan.wasteArea,
errors: plan.issues.filter((issue) => issue.severity === 'error').length,
};
}
export function skirtingMetrics(plan: SkirtingPlan) {
return {
cuts: plan.cuts,
pieces: plan.pieces.length,
boards: plan.stockCount,
waste: Math.round(plan.waste * 1000) / 1000,
errors: plan.issues.filter((issue) => issue.severity === 'error').length,
};
}
export function compareFlooring(
a: ReturnType<typeof flooringMetrics>,
b: typeof a,
random = false,
) {
return (
a.errors - b.errors ||
(random ? a.patternPenalty - b.patternPenalty : 0) ||
a.cuts - b.cuts ||
a.shapingCuts - b.shapingCuts ||
a.ripCuts - b.ripCuts ||
a.patternPenalty - b.patternPenalty ||
a.boards - b.boards
);
}
export function compareSkirting(a: ReturnType<typeof skirtingMetrics>, b: typeof a) {
return a.errors - b.errors || a.cuts - b.cuts || a.waste - b.waste;
}