{"record":{"id":"c7bdcd54f5e0dffd","repo":"koala73/worldmonitor","slug":"enter-one-quantity-unit-of-1-24-characters","errorCode":null,"errorMessage":"Enter one quantity unit of 1-24 characters.","messagePattern":"Enter one quantity unit of 1-24 characters\\.","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/utils/operational-balance.ts","lineNumber":31,"sourceCode":"}\nfunction quantity(value: unknown, label: string): number {\n  if (value === '' || value === null || value === undefined) throw new Error(`${label} is required.`);\n  if (typeof value !== 'number' || !Number.isFinite(value) || value < 0 || value > 1e6 || round(value) !== value) {\n    throw new Error(`${label} must be between 0 and 1 million with at most 6 decimal places.`);\n  }\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),","sourceCodeStart":13,"sourceCodeEnd":49,"githubUrl":"https://github.com/koala73/worldmonitor/blob/7d06c8633d256c18e38133030bc3613976a96ec9/src/utils/operational-balance.ts#L13-L49","documentation":"parseOperationalInput validates the unit field against the Unicode-aware regex /^[\\p{L}\\p{N} %./-]{1,24}$/u and requires it to be non-blank after trimming. It throws 'Enter one quantity unit of 1-24 characters.' when the unit is missing, not a string, contains characters outside letters/digits/space/%/.///- , exceeds 24 characters, or is only whitespace.","triggerScenarios":"Passing units with symbols like 'kg/h²', 'µg', '°C', or quotes; empty or blank unit; a descriptive unit sentence longer than 24 characters; undefined unit from a mismatched field name.","commonSituations":"Users typing free-text units with special characters; localization introducing non-ASCII symbols outside the allowed Unicode letter/number classes; spreadsheet cells containing 'kilograms (metric)' as the unit.","solutions":["Use a short unit within letters, digits, space, %, ., /, or - (e.g. 'kg', 'l/day', 'rounds').","Strip or replace disallowed characters and trim before submission.","Enforce the same character class in the form input pattern: [\\p{L}\\p{N} %./-]{1,24}."],"exampleFix":"// before\nparseOperationalInput({ ..., unit: 'µg/m³' });\n// after\nparseOperationalInput({ ..., unit: 'kg' });","handlingStrategy":"validation","validationCode":"const UNIT_RE = /^[\\p{L}\\p{N} %./-]{1,24}$/u;\nconst unit = (payload as any)?.unit;\nif (typeof unit !== 'string' || !UNIT_RE.test(unit) || !unit.trim()) {\n  throw new Error('Unit must be 1-24 chars: letters, digits, space, %, ., /, -');\n}","typeGuard":"const hasValidUnit = (w: unknown): w is { unit: string } & Record<string, unknown> =>\n  w !== null && typeof w === 'object' && typeof (w as any).unit === 'string' &&\n  /^[\\p{L}\\p{N} %./-]{1,24}$/u.test((w as any).unit) && (w as any).unit.trim().length > 0;","tryCatchPattern":"try {\n  const input = parseOperationalInput(raw);\n} catch (e) {\n  if (e instanceof Error && e.message.includes('quantity unit of 1-24')) {\n    console.error('Unit missing, blank, has disallowed characters, or too long:', e.message);\n  } else throw e;\n}","preventionTips":["Offer a unit dropdown of known-good values (kg, l, rounds) instead of free text.","Add pattern=\"[\\p{L}\\p{N} %./-]{1,24}\" to the unit input.","Replace symbols like µ, °, ² with allowed spellings (ug, C, m2).","Trim the unit and reject whitespace-only values in the UI before submission."],"tags":["validation","format","regex"],"backgroundTag":"invalid-argument-format","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"}