Files
Pier_v1/src/core/piles.ts
T
ypshimandClaude Haiku 5.5 d61e5d082f fix(rebar): warn when the pile spacing along a 열 is above the 2250 mm maximum
The card only checked the minimum spacing. A 12000 mm footing with 5 piles per 열 gives 2650 mm, above the maximum. The card
now marks such a row ("최대 2250 mm를 넘습니다. 본수를 늘리세요") and the alert list gets a warning (P02). Tests check 5 and 6
piles on a 12000 mm footing.

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
2026-10-23 16:20:37 +09:00

95 lines
4.0 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 말뚝 열별 본수 (spec §4.6 · §5.4 · plan C4). A 열 is one row across the footing width; 본 are along the length.
* Default: each 열 gets floor((길이 − 2·단부)/간격) + 1 본, so the centre spacing is at least the minimum.
* The user may change any 열's count. Counts below the minimum spacing are allowed and reported as a warning.
*/
export interface PileGeometry {
footingLength: number;
footingWidth: number;
/** 단부 거리 (700 mm). */
end: number;
/** 최소 중심간격 (2250 mm). */
minSpacing: number;
}
/** A count outside this range is refused: fewer than two piles has no spacing, and the cap guards against typing errors. */
export const PILE_COUNT_LIMITS = { min: 2, max: 30 } as const;
/** Largest count along one length with centres at or above the minimum spacing. */
export function maxCount(length: number, g: PileGeometry): number {
return Math.floor((length - 2 * g.end) / g.minSpacing) + 1;
}
/** Number of 열 across the width. */
export function rowsAcross(g: PileGeometry): number {
return maxCount(g.footingWidth, { ...g, minSpacing: g.minSpacing });
}
export function defaultRowCounts(g: PileGeometry): number[] {
return Array.from({ length: rowsAcross(g) }, () => maxCount(g.footingLength, g));
}
/** Counts to use for a footing: the requested 열별 본수 if any, otherwise the defaults. Refuses bad input. */
export function resolveRowCounts(requested: number[] | undefined, g: PileGeometry): number[] {
if (requested === undefined) return defaultRowCounts(g);
const rows = rowsAcross(g);
if (requested.length !== rows) {
throw new Error(`열별 본수는 ${rows}개여야 합니다 (입력 ${requested.length}개).`);
}
for (const c of requested) {
if (!Number.isInteger(c) || c < PILE_COUNT_LIMITS.min || c > PILE_COUNT_LIMITS.max) {
throw new Error(`본수는 ${PILE_COUNT_LIMITS.min}–${PILE_COUNT_LIMITS.max} 사이의 정수여야 합니다 (입력 ${c}).`);
}
}
return [...requested];
}
export interface RowLayout {
count: number;
/** Centre spacing along the length, in m. */
spacing: number;
/** True when the spacing is below the minimum (warning, not refused). */
belowMinimum: boolean;
/** True when the spacing is above the maximum 2.25 m (warning: add piles). */
aboveMaximum: boolean;
}
/** Maximum centre spacing of the piles along a 열 (m), spec §4.6 (승인 2026-10-23: 2250 mm). */
export const PILE_SPACING_MAX = 2.25;
export function rowLayout(counts: number[], g: PileGeometry): RowLayout[] {
return counts.map((count) => {
const spacing = (g.footingLength - 2 * g.end) / (count - 1);
return { count, spacing, belowMinimum: spacing < g.minSpacing - 1e-9, aboveMaximum: spacing > PILE_SPACING_MAX + 1e-9 };
});
}
/** Total piles (sum of the counts along each 열). */
export function totalPiles(counts: number[]): number {
return counts.reduce((sum, c) => sum + c, 0);
}
/** Changes one 열's count by delta, staying inside the limits. Returns a new array. */
export function adjustRow(counts: number[], row: number, delta: number): number[] {
const next = [...counts];
next[row] = Math.min(PILE_COUNT_LIMITS.max, Math.max(PILE_COUNT_LIMITS.min, next[row] + delta));
return next;
}
/**
* Display string. Uniform counts keep the 1단계 form, e.g. "4@2250 × 3@2250 = 20본"
* (gaps along the length × gaps across the width). Non-uniform counts list each 열, e.g. "열1 5@2250 · 열2 6@1800 = 23본".
*/
export function layoutText(counts: number[], g: PileGeometry): string {
const layout = rowLayout(counts, g);
const total = totalPiles(counts);
const mm = (v: number) => Math.round(v * 1000);
const across = (g.footingWidth - 2 * g.end) / (counts.length - 1);
const uniform = counts.every((c) => c === counts[0]);
if (uniform) {
return `${layout[0].count - 1}@${mm(layout[0].spacing)} × ${counts.length - 1}@${mm(across)} = ${total}본`;
}
const parts = layout.map((r, j) => `열${j + 1} ${r.count - 1}@${mm(r.spacing)}`);
return `${parts.join(' · ')} = ${total}본`;
}