/** * The class of an unnamed release (REBUILD.md decision f; LOG.md (130)): what the headers say about its chance of a * name, from the pending ladder's own record. Size bucket × name shape × partial, 1..70, kept in step with * names.rs pending_class and the SQL pending_class() (migration 6800): the batch store sets it with the SQL, the * edge skips the classes a lease marks dropped. */ /** Size buckets: under 1 MB, 10 MB, 100 MB, 1 GB, and from 1 GB. */ export const SIZE_BUCKETS = [1_000_000, 10_000_000, 100_000_000, 1_000_000_000] /** Name shapes, 1..7: hex, digits, base32, letters only, mixed letters and digits, with separators, other. */ export const SHAPES = [ 'hex', 'digits', 'base32', 'letters', 'alnum', 'separators', 'other', ] as const export function sizeBucket(bytes: number): number { let i = 0 while (i < SIZE_BUCKETS.length && bytes >= SIZE_BUCKETS[i]) i++ return i } export function nameShape(name: string): number { const s = name.replace(/\.[A-Za-z0-9]{1,4}$/, '') if (/^[0-9a-fA-F]{16,}$/.test(s)) return 1 if (/^[0-9]{6,}$/.test(s)) return 2 if (/^[a-z2-7]{20,}$/.test(s) && /[2-7]/.test(s)) return 3 if (/^[A-Za-z]+$/.test(s)) return 4 if (/^[A-Za-z0-9]+$/.test(s)) return 5 if (/^[A-Za-z0-9._-]+$/.test(s)) return 6 return 7 } /** 1..70: (size bucket × 7 + shape − 1) × 2 + partial + 1. */ export function pendingClass(name: string, bytes: number, partial: boolean): number { return sizeBucket(bytes) * 14 + (nameShape(name) - 1) * 2 + (partial ? 1 : 0) + 1 } /** A class as droppedClasses lists it: "tool\tgroup\tklass". */ export function classKey(tool: string, group: string, klass: number): string { return `${tool}\t${group}\t${klass}` } /** A class in words ("10-100M hex partial"). */ export function describeClass(klass: number): string { const k = klass - 1 const size = Math.floor(k / 14) const shape = SHAPES[Math.floor((k % 14) / 2)] const sizes = ['<1M', '1-10M', '10-100M', '100M-1G', '1G+'] return `${sizes[size]} ${shape}${k % 2 ? ' partial' : ''}` }