{"record":{"id":"c9138c3fce397d41","repo":"koala73/worldmonitor","slug":"input-basis-must-be-example-or-user","errorCode":null,"errorMessage":"Input basis must be example or user.","messagePattern":"Input basis must be example or user\\.","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/utils/operational-balance.ts","lineNumber":36,"sourceCode":"  }\n  return value;\n}\nfunction date(value: unknown): string {\n  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') {\n    throw new Error('Use a valid date from 1900 through 9998.');\n  }\n  return value;\n}\n\nexport function parseOperationalInput(value: unknown): OperationalInput {\n  const input = record(value);\n  if (typeof input.operation !== 'string' || !input.operation.trim() || input.operation.length > 100) throw new Error('Enter an operation name of 1-100 characters.');\n  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.');\n  const unit = input.unit.trim();\n  const startDate = date(input.startDate);\n  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.');\n  const horizonDays = input.horizonDays as number;\n  if (input.basis !== 'example' && input.basis !== 'user') throw new Error('Input basis must be example or user.');\n  const deliveries = (value: unknown): OperationalDelivery[] => {\n    if (!Array.isArray(value) || value.length > MAX_OPERATIONAL_DELIVERIES) throw new Error('Use at most 30 deliveries per list.');\n    return value.map(value => {\n      const row = record(value);\n      const deliveryDate = date(row.date);\n      if (deliveryDate < startDate || deliveryDate > dateAt(startDate, horizonDays - 1)) throw new Error('Delivery dates must be inside the selected horizon.');\n      if (row.unit !== unit) throw new Error(`Every delivery must use ${unit}; mixed units are not converted.`);\n      return { date: deliveryDate, unit, quantity: quantity(row.quantity, 'Delivery quantity'), costUsd: row.costUsd === null || row.costUsd === undefined ? null : quantity(row.costUsd, 'Delivery cost') };\n    });\n  };\n  return { operation: input.operation.trim(), basis: input.basis, unit, startDate, horizonDays,\n    startingStock: quantity(input.startingStock, 'Starting stock'), dailyDemand: quantity(input.dailyDemand, 'Daily demand'),\n    deliveries: deliveries(input.deliveries), alternativeDeliveries: deliveries(input.alternativeDeliveries),\n    alternativeDailyDemand: input.alternativeDailyDemand === null ? null : quantity(input.alternativeDailyDemand, 'Alternative daily demand') };\n}\n\nexport function calculateOperationalBalance(value: unknown): OperationalSnapshot {\n  const input = parseOperationalInput(value);","sourceCodeStart":18,"sourceCodeEnd":54,"githubUrl":"https://github.com/koala73/worldmonitor/blob/7d06c8633d256c18e38133030bc3613976a96ec9/src/utils/operational-balance.ts#L18-L54","documentation":"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.","triggerScenarios":"Calling parseOperationalInput/calculateOperationalBalance with basis: 'User', 'demo', '', undefined, null, or a JSON export where the basis key was renamed or dropped.","commonSituations":"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'.","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."],"exampleFix":"// before\ncalculateOperationalBalance({ ...input, basis: 'demo' });\n// after\nconst basis = input.basis === 'demo' || input.basis === 'template' ? 'example' : input.basis;\ncalculateOperationalBalance({ ...input, basis });","handlingStrategy":"validation","validationCode":"const VALID_BASIS = ['example', 'user'] as const;\nif (!VALID_BASIS.includes(input.basis as any)) throw new Error(`basis must be one of ${VALID_BASIS.join(', ')}`);","typeGuard":"const isBasis = (v: unknown): v is 'example' | 'user' =>\n  v === 'example' || v === 'user';","tryCatchPattern":"try {\n  const snapshot = calculateOperationalBalance(input);\n} catch (err) {\n  if (err instanceof Error && err.message === 'Input basis must be example or user.') {\n    input.basis = 'user'; // safe default for hand-authored data\n  } else throw err;\n}","preventionTips":["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."],"tags":["validation","enum","input-validation"],"backgroundTag":"invalid-enum-value","analyzedSha":"7d06c8633d256c18e38133030bc3613976a96ec9","analyzedAt":"2026-09-15T16:44:39.439Z","contentChangedAt":"2026-09-15T16:44:39.439Z","schemaVersion":2},"datasetVersion":"2026-09-15T18:17:12.389Z"}