koala73/worldmonitor · error
Input basis must be example or user.
Error message
Input basis must be example or user.
What it means
parseOperationalInput requires input.basis to be exactly the string 'example' or 'user', marking whether the worksheet is the bundled demo or user-authored data. Any other value (other strings, wrong casing, missing field) throws, because downstream logic branches on these two enum values.
Solutions
- Set basis to exactly 'example' or 'user' (lowercase) in the input object.
- Normalize/whitelist the value at the UI layer before calling the parser (e.g. map the toggle state to these two literals).
- For imported JSON, validate value.schema === 'worldmonitor-operational-worksheet/v1' and the basis field before parsing the input.
- If migrating old data, map legacy basis values to the two supported literals.
Example fix
// before
calculateOperationalBalance({ ...input, basis: 'demo' });
// after
const basis = input.basis === 'demo' || input.basis === 'template' ? 'example' : input.basis;
calculateOperationalBalance({ ...input, basis }); Defensive patterns
Strategy: validation
Validate before calling
const VALID_BASIS = ['example', 'user'] as const;
if (!VALID_BASIS.includes(input.basis as any)) throw new Error(`basis must be one of ${VALID_BASIS.join(', ')}`); Type guard
const isBasis = (v: unknown): v is 'example' | 'user' => v === 'example' || v === 'user';
Try / catch
try {
const snapshot = calculateOperationalBalance(input);
} catch (err) {
if (err instanceof Error && err.message === 'Input basis must be example or user.') {
input.basis = 'user'; // safe default for hand-authored data
} else throw err;
} Prevention
- Drive the basis from a fixed two-option UI control, never free text.
- Keep the literals 'example' and 'user' in a shared const/type used by both producer and parser.
- When importing JSON, validate schema version first, then basis.
When it happens
Trigger: Calling parseOperationalInput/calculateOperationalBalance with basis: 'User', 'demo', '', undefined, null, or a JSON export where the basis key was renamed or dropped.
Common situations: Hand-edited worksheet JSON with an invented basis value; a new UI toggle wired to different strings than the parser expects; older exported worksheets from a previous schema version using 'template' instead of 'example'.
Understand the failure class
Background: Invalid enum value errors: "Unknown type", "Invalid scope", "must be one of" — when a string is not on the library's allowed list — this error's family across 23 libraries.
Related errors
- Invalid scorecard bloc selection.
- countryCode must be a 2-letter ISO 3166-1 alpha-2 code
- chokepointId must be a canonical chokepoint id
- iso2 must be a 2-letter uppercase ISO country code
- INVALID_NAME
AI-assisted analysis of koala73/worldmonitor@7d06c8633d (2026-09-15).
Data as JSON: /api/errors/c9138c3fce397d41.
Report an issue: GitHub.
Appendix: source
Thrown at src/utils/operational-balance.ts:36
}
return value;
}
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);View on GitHub (pinned to 7d06c8633d)