pickLowestFreeSlot fills gaps before extending (deterministic, compact, predictable for admins reading the controller). Range is a parameter (HeatSync passes 0-200) so it stays adapter-generic. Ignores duplicate/ out-of-range/non-integer used slots; reports slot_exhausted when full. slotCapacity for 'controller full N/N' messaging. 8 unit tests.
44 lines
1.7 KiB
TypeScript
44 lines
1.7 KiB
TypeScript
// Pure, dependency-free slot allocation for the Door epic. No Supabase/React.
|
|
// Some controllers key cards by an integer "slot" (the verified HeatSync/23b
|
|
// firmware: 0-200). The slot space is per door connection. This decides which
|
|
// slot a newly granted card should take; the action layer holds the actual
|
|
// allocation state (door_card_slots) and the DB unique constraint arbitrates
|
|
// concurrent grants. Kept adapter-generic: the range is a parameter, not a
|
|
// HeatSync constant, even though HeatSync is the first caller.
|
|
|
|
// Default range matches the HeatSync firmware (slots 0-200 inclusive, 201
|
|
// usable). The user chose the full range with no reserved slots.
|
|
export const HEATSYNC_SLOT_MIN = 0
|
|
export const HEATSYNC_SLOT_MAX = 200
|
|
|
|
export type SlotPick =
|
|
| { ok: true; slot: number }
|
|
| { ok: false; reason: 'slot_exhausted' }
|
|
|
|
// Lowest free slot first: deterministic (testable), compact, and predictable
|
|
// for an admin reading the controller's user list. `used` may contain
|
|
// duplicates or out-of-range values; both are ignored.
|
|
export function pickLowestFreeSlot(
|
|
used: number[],
|
|
min: number = HEATSYNC_SLOT_MIN,
|
|
max: number = HEATSYNC_SLOT_MAX,
|
|
): SlotPick {
|
|
const taken = new Set<number>()
|
|
for (const n of used) {
|
|
if (Number.isInteger(n)) taken.add(n)
|
|
}
|
|
for (let slot = min; slot <= max; slot++) {
|
|
if (!taken.has(slot)) return { ok: true, slot }
|
|
}
|
|
return { ok: false, reason: 'slot_exhausted' }
|
|
}
|
|
|
|
// Total addressable slots for a range, for capacity messaging
|
|
// ("controller is full, 201/201 slots used").
|
|
export function slotCapacity(
|
|
min: number = HEATSYNC_SLOT_MIN,
|
|
max: number = HEATSYNC_SLOT_MAX,
|
|
): number {
|
|
return max < min ? 0 : max - min + 1
|
|
}
|