koala73/worldmonitor · error
Use at most 30 deliveries per list.
Error message
Use at most 30 deliveries per list.
What it means
The inner deliveries() validator in parseOperationalInput caps each delivery list at MAX_OPERATIONAL_DELIVERIES (30) rows. A longer array is rejected rather than silently truncated, so users get an explicit message instead of a quietly incomplete plan.
Solutions
- Reduce the deliveries array to 30 or fewer entries before calling the parser.
- Split long schedules across multiple worksheets or aggregate deliveries that share a date.
- If the data legitimately needs more rows, request a MAX_OPERATIONAL_DELIVERIES bump upstream instead of bypassing validation.
- Validate array length in the import UI and surface a user-facing warning before parse.
Example fix
// before
calculateOperationalBalance({ ...input, deliveries: allDeliveries });
// after
const deliveries = allDeliveries.slice(0, 30);
if (allDeliveries.length > 30) console.warn('Truncated deliveries to the 30-row worksheet limit.');
calculateOperationalBalance({ ...input, deliveries }); Defensive patterns
Strategy: validation
Validate before calling
for (const key of ['deliveries', 'alternativeDeliveries'] as const) {
if (Array.isArray(input[key]) && input[key].length > 30) {
throw new Error(`${key} has ${input[key].length} rows; the worksheet allows at most 30.`);
}
} Type guard
const withinDeliveryCap = (v: unknown): v is unknown[] => Array.isArray(v) && v.length <= 30;
Try / catch
try {
const snapshot = calculateOperationalBalance(input);
} catch (err) {
if (err instanceof Error && err.message === 'Use at most 30 deliveries per list.') {
input.deliveries = input.deliveries.slice(0, 30);
} else throw err;
} Prevention
- Enforce a hard maxLength of 30 rows in the delivery table UI.
- Chunk or aggregate bulk imports before parsing.
- Add a row counter next to the add-delivery button.
When it happens
Trigger: Calling parseOperationalInput/calculateOperationalBalance with input.deliveries or input.alternativeDeliveries as an array of 31+ delivery objects; bulk-importing a CSV/log of deliveries without chunking.
Common situations: Importing a season-long supply schedule; a scripted generator producing hundreds of rows; merging multiple worksheets' delivery arrays into one.
Understand the failure class
Background: "value must be between 0 and 1" / "out of range" / "must not be negative" errors: fixing range-validation failures across open-source libraries — this error's family across 42 libraries.
Related errors
- COUNTRIES_LIMIT_EXCEEDED
- TICKERS_LIMIT_EXCEEDED
- batch exceeds ${COMPANY_MONITORING_LIMITS.maxImportBatchByte
- invalid_limit
- Worksheet JSON must be no larger than 64 KiB.
AI-assisted analysis of koala73/worldmonitor@7d06c8633d (2026-09-15).
Data as JSON: /api/errors/431eef6875b06d82.
Report an issue: GitHub.
Appendix: source
Thrown at src/utils/operational-balance.ts:38
}
function date(value: unknown): string {
if (typeof value !== 'string' || !/^\d{4}-\d{2}-\d{2}$/.test(value) || !Number.isFinite(Date.parse(value)) || new Date(value).toISOString().slice(0, 10) !== value || value < '1900-01-01' || value > '9998-12-31') {
throw new Error('Use a valid date from 1900 through 9998.');
}
return value;
}
export function parseOperationalInput(value: unknown): OperationalInput {
const input = record(value);
if (typeof input.operation !== 'string' || !input.operation.trim() || input.operation.length > 100) throw new Error('Enter an operation name of 1-100 characters.');
if (typeof input.unit !== 'string' || !/^[\p{L}\p{N} %./-]{1,24}$/u.test(input.unit) || !input.unit.trim()) throw new Error('Enter one quantity unit of 1-24 characters.');
const unit = input.unit.trim();
const startDate = date(input.startDate);
if (!Number.isInteger(input.horizonDays) || (input.horizonDays as number) < 1 || (input.horizonDays as number) > MAX_OPERATIONAL_DAYS) throw new Error('Horizon must be 1-90 whole days.');
const horizonDays = input.horizonDays as number;
if (input.basis !== 'example' && input.basis !== 'user') throw new Error('Input basis must be example or user.');
const deliveries = (value: unknown): OperationalDelivery[] => {
if (!Array.isArray(value) || value.length > MAX_OPERATIONAL_DELIVERIES) throw new Error('Use at most 30 deliveries per list.');
return value.map(value => {
const row = record(value);
const deliveryDate = date(row.date);
if (deliveryDate < startDate || deliveryDate > dateAt(startDate, horizonDays - 1)) throw new Error('Delivery dates must be inside the selected horizon.');
if (row.unit !== unit) throw new Error(`Every delivery must use ${unit}; mixed units are not converted.`);
return { date: deliveryDate, unit, quantity: quantity(row.quantity, 'Delivery quantity'), costUsd: row.costUsd === null || row.costUsd === undefined ? null : quantity(row.costUsd, 'Delivery cost') };
});
};
return { operation: input.operation.trim(), basis: input.basis, unit, startDate, horizonDays,
startingStock: quantity(input.startingStock, 'Starting stock'), dailyDemand: quantity(input.dailyDemand, 'Daily demand'),
deliveries: deliveries(input.deliveries), alternativeDeliveries: deliveries(input.alternativeDeliveries),
alternativeDailyDemand: input.alternativeDailyDemand === null ? null : quantity(input.alternativeDailyDemand, 'Alternative daily demand') };
}
export function calculateOperationalBalance(value: unknown): OperationalSnapshot {
const input = parseOperationalInput(value);
const balance = (deliveries: OperationalDelivery[], demand: number): OperationalBalance => {
let stock = input.startingStock;View on GitHub (pinned to 7d06c8633d)