{"record":{"id":"3108f4905efd0b89","repo":"koala73/worldmonitor","slug":"label-must-be-between-0-and-1-million-with-at-most-6-decimal","errorCode":null,"errorMessage":"${label} must be between 0 and 1 million with at most 6 decimal places.","messagePattern":"(.+?) must be between 0 and 1 million with at most 6 decimal places\\.","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/utils/operational-balance.ts","lineNumber":17,"sourceCode":"import type { OperationalBalance, OperationalDelivery, OperationalInput, OperationalSnapshot } from '@/types/operational-balance';\n\nexport const MAX_OPERATIONAL_DAYS = 90;\nexport const MAX_OPERATIONAL_DELIVERIES = 30;\nexport const MAX_OPERATIONAL_JSON_BYTES = 65_536;\nconst DAY_MS = 86_400_000;\nconst round = (value: number) => Math.round(value * 1e6) / 1e6;\nconst dateAt = (start: string, offset: number) => new Date(Date.parse(start) + offset * DAY_MS).toISOString().slice(0, 10);\n\nfunction record(value: unknown): Record<string, unknown> {\n  if (!value || typeof value !== 'object' || Array.isArray(value)) throw new Error('Expected a worksheet object.');\n  return value as Record<string, unknown>;\n}\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;","sourceCodeStart":1,"sourceCodeEnd":35,"githubUrl":"https://github.com/koala73/worldmonitor/blob/7d06c8633d256c18e38133030bc3613976a96ec9/src/utils/operational-balance.ts#L1-L35","documentation":"The quantity() helper validates that each quantity is a finite number between 0 and 1,000,000 that equals its own 6-decimal rounding (round(value) === value). It throws \"${label} must be between 0 and 1 million with at most 6 decimal places.\" when the value is non-numeric, negative, too large, non-finite, or has more than 6 decimal places.","triggerScenarios":"Passing NaN/Infinity, a numeric string like \"12.5\", a negative value, a value > 1e6, or a high-precision value like 0.1234567 for a quantity field.","commonSituations":"Sending form values as strings without Number() conversion; dividing quantities in ways that produce long decimals; entering micro-quantities with excessive precision; spreadsheet cells imported as text.","solutions":["Convert to a number and clamp/round to at most 6 decimal places: Math.round(v * 1e6) / 1e6.","Check 0 <= v <= 1e6 and Number.isFinite(v) before submitting.","Parse numeric strings with Number() and reject NaN upstream."],"exampleFix":"// before\nparseOperationalInput({ ..., initialStock: '12.5' });\n// after\nconst v = Number('12.5');\nparseOperationalInput({ ..., initialStock: Math.round(v * 1e6) / 1e6 });","handlingStrategy":"validation","validationCode":"function validQuantity(v: unknown): v is number {\n  return typeof v === 'number' && Number.isFinite(v) && v >= 0 && v <= 1e6 &&\n    Math.round(v * 1e6) / 1e6 === v;\n}","typeGuard":"const isQuantity = (v: unknown): v is number =>\n  typeof v === 'number' && Number.isFinite(v) && v >= 0 && v <= 1e6 &&\n  Math.round(v * 1e6) / 1e6 === v;","tryCatchPattern":"try {\n  const input = parseOperationalInput(raw);\n} catch (e) {\n  if (e instanceof Error && e.message.includes('between 0 and 1 million')) {\n    console.error('Quantity out of range or too precise:', e.message);\n  } else throw e;\n}","preventionTips":["Number() coerce form/spreadsheet values before submitting; reject NaN.","Clamp inputs to [0, 1e6] and round to 6 decimals in the UI.","Set step=\"0.000001\" max=\"1000000\" min=\"0\" on number inputs.","Avoid computing quantities with divisions that produce long decimals without rounding."],"tags":["validation","range","precision","numbers"],"backgroundTag":"value-out-of-range","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"}