{
  "name": "Generation Budget Estimator v0.1.0",
  "nodes": [
    {
      "id": "wf012-manual",
      "name": "Manual test",
      "type": "n8n-nodes-base.manualTrigger",
      "typeVersion": 1,
      "parameters": {},
      "position": [
        0,
        0
      ]
    },
    {
      "id": "wf012-input",
      "name": "Fictional demonstration only",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "parameters": {
        "jsCode": "// Fake data only; not approval, verified pricing, balance proof or authorization.\nreturn [{json:{\n  \"currency\": \"USD\",\n  \"rate_date\": \"2026-09-29\",\n  \"rates_are_example\": true,\n  \"input_usd_per_million\": 1,\n  \"output_usd_per_million\": 2,\n  \"max_input_tokens\": 2000,\n  \"max_output_tokens\": 1000,\n  \"spent_usd\": 1,\n  \"reserved_usd\": 0.5,\n  \"monthly_cap_usd\": 10,\n  \"additional_costs\": []\n}}];"
      },
      "position": [
        280,
        0
      ]
    },
    {
      "id": "wf012-calculate",
      "name": "Offline advisory result",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "parameters": {
        "jsCode": "function estimateGenerationBudget(input) {\n  const result = {\n    status: 'blocked',\n    budget_check: 'blocked',\n    currency: 'USD',\n    rate_date: null,\n    rates_are_example: null,\n    estimated_max_usd: null,\n    estimated_max_usd_decimal: null,\n    available_usd: null,\n    available_usd_decimal: null,\n    token_estimated_max_usd: null,\n    token_estimated_max_usd_decimal: null,\n    additional_estimated_max_usd: null,\n    additional_estimated_max_usd_decimal: null,\n    budget_sufficient: null,\n    generation_allowed: false,\n    reservation_required: true,\n    reasons: [],\n    pricing_note: 'Caller-supplied rates are not verified official or current prices.',\n    authorization_note: 'Prompt approval is not proof of budget. An authoritative budget check and reservation are still required.',\n  };\n\n  // Everything used by this function remains inside its toString() boundary.\n  const MAX_VALUE = 1_000_000_000;\n  const MONEY_SCALE = 1_000_000_000n;\n  const PER_MILLION = 1_000_000n;\n  const COST_SCALE = MONEY_SCALE * PER_MILLION;\n  const missing = [];\n  const errors = [];\n  const isRecord = value => value !== null && typeof value === 'object' && !Array.isArray(value);\n  const own = (object, key) => {\n    const descriptor = Object.getOwnPropertyDescriptor(object, key);\n    if (!descriptor) return undefined;\n    if (!Object.prototype.hasOwnProperty.call(descriptor, 'value')) {\n      throw new TypeError('Accessor input is not supported');\n    }\n    return descriptor.value;\n  };\n  const validDate = value => {\n    if (typeof value !== 'string' || !/^\\d{4}-\\d{2}-\\d{2}$/.test(value)) return false;\n    const year = Number(value.slice(0, 4));\n    const month = Number(value.slice(5, 7));\n    const day = Number(value.slice(8, 10));\n    if (year < 1 || month < 1 || month > 12 || day < 1) return false;\n    const leap = year % 4 === 0 && (year % 100 !== 0 || year % 400 === 0);\n    const days = [31, leap ? 29 : 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];\n    return day <= days[month - 1];\n  };\n  const decimalUnits = (value, field) => {\n    if (typeof value !== 'number' || !Number.isFinite(value) || value < 0 ||\n        Object.is(value, -0) || value > MAX_VALUE) {\n      errors.push(`${field}: must be a finite nonnegative numeric value <= 1000000000`);\n      return null;\n    }\n    // Expand scientific notation without doing arithmetic on the Number itself.\n    const [coefficient, exponentText] = value.toString().split('e');\n    const [whole, fraction = ''] = coefficient.split('.');\n    const exponent = exponentText === undefined ? 0 : Number(exponentText);\n    const shift = 9 + exponent - fraction.length;\n    if (shift < 0) {\n      errors.push(`${field}: exceeds 9 fractional decimal places`);\n      return null;\n    }\n    return BigInt(whole + fraction) * (10n ** BigInt(shift));\n  };\n  const tokenBound = (value, field) => {\n    if (value === undefined) {\n      missing.push(`${field}: explicit maximum token bound is required`);\n      return null;\n    }\n    if (typeof value !== 'number' || !Number.isSafeInteger(value) || value < 0 ||\n        Object.is(value, -0) || value > MAX_VALUE) {\n      errors.push(`${field}: must be a safe nonnegative integer <= 1000000000`);\n      return null;\n    }\n    return BigInt(value);\n  };\n  const decimalString = units => {\n    const negative = units < 0n;\n    const absolute = negative ? -units : units;\n    const whole = absolute / COST_SCALE;\n    const fraction = (absolute % COST_SCALE).toString().padStart(15, '0').replace(/0+$/, '');\n    return `${negative ? '-' : ''}${whole}${fraction ? `.${fraction}` : ''}`;\n  };\n  const putAmount = (field, units) => {\n    const decimal = decimalString(units);\n    result[`${field}_decimal`] = decimal;\n    result[field] = Number(decimal);\n  };\n\n  try {\n    if (!isRecord(input)) {\n      result.reasons = ['input: must be an object'];\n      return result;\n    }\n    if (own(input, 'currency') !== 'USD') errors.push('currency: must explicitly be USD');\n    const rateDate = own(input, 'rate_date');\n    if (!validDate(rateDate)) errors.push('rate_date: must be a valid YYYY-MM-DD calendar date');\n    else result.rate_date = rateDate;\n    const example = own(input, 'rates_are_example');\n    if (typeof example !== 'boolean') errors.push('rates_are_example: must explicitly be boolean');\n    else {\n      result.rates_are_example = example;\n      if (example) result.pricing_note = 'Fictional example rates only; these are not official or current prices.';\n    }\n\n    const inputRate = decimalUnits(own(input, 'input_usd_per_million'), 'input_usd_per_million');\n    const outputRate = decimalUnits(own(input, 'output_usd_per_million'), 'output_usd_per_million');\n    const inputTokens = tokenBound(own(input, 'max_input_tokens'), 'max_input_tokens');\n    const outputTokens = tokenBound(own(input, 'max_output_tokens'), 'max_output_tokens');\n    const spent = decimalUnits(own(input, 'spent_usd'), 'spent_usd');\n    const reserved = decimalUnits(own(input, 'reserved_usd'), 'reserved_usd');\n    const cap = decimalUnits(own(input, 'monthly_cap_usd'), 'monthly_cap_usd');\n\n    let extrasUnits = 0n;\n    const extras = own(input, 'additional_costs');\n    if (!Array.isArray(extras) || extras.length > 100) {\n      errors.push('additional_costs: explicit array of at most 100 known maximum charges required; use [] only for no extras');\n    } else {\n      for (let index = 0; index < extras.length; index += 1) {\n        const extra = own(extras, String(index));\n        const prefix = `additional_costs[${index}]`;\n        if (!isRecord(extra)) {\n          errors.push(`${prefix}: must be a dated known maximum charge object`);\n          continue;\n        }\n        const fields = ['kind', 'name', 'rate_date', 'max_usd'];\n        if (Reflect.ownKeys(extra).some(key => !fields.includes(key))) {\n          errors.push(`${prefix}: unsupported fields; all extra charges must have known maxima`);\n        }\n        if (!['tool', 'image', 'other'].includes(own(extra, 'kind'))) errors.push(`${prefix}.kind: must be tool, image, or other`);\n        const name = own(extra, 'name');\n        if (typeof name !== 'string' || name.trim().length === 0 || name.length > 120) errors.push(`${prefix}.name: must be a nonempty string of at most 120 characters`);\n        if (!validDate(own(extra, 'rate_date'))) errors.push(`${prefix}.rate_date: must be a valid YYYY-MM-DD calendar date`);\n        const max = decimalUnits(own(extra, 'max_usd'), `${prefix}.max_usd`);\n        if (max !== null) extrasUnits += max * PER_MILLION;\n      }\n    }\n\n    // Malformed inputs dominate missing bounds, and expose no partial estimate.\n    if (errors.length > 0) {\n      result.reasons = errors.concat(missing);\n      return result;\n    }\n    const available = (cap - spent - reserved) * PER_MILLION;\n    putAmount('available_usd', available);\n    if (missing.length > 0) {\n      result.status = 'cannot_estimate';\n      result.budget_check = 'cannot_estimate';\n      result.reasons = missing;\n      return result;\n    }\n\n    const tokenUnits = inputTokens * inputRate + outputTokens * outputRate;\n    const estimated = tokenUnits + extrasUnits;\n    putAmount('token_estimated_max_usd', tokenUnits);\n    putAmount('additional_estimated_max_usd', extrasUnits);\n    putAmount('estimated_max_usd', estimated);\n    result.budget_sufficient = estimated <= available;\n    result.status = result.budget_sufficient ? 'within_limit' : 'insufficient_budget';\n    result.budget_check = result.status;\n    result.reasons = [result.budget_sufficient\n      ? 'Maximum declared cost fits the reported unreserved budget; no spending is authorized.'\n      : 'Maximum declared cost exceeds the reported unreserved budget.'];\n    return result;\n  } catch {\n    // Getters/proxies and non-JSON exotic inputs must never yield an allow result.\n    result.status = 'blocked';\n    result.budget_check = 'blocked';\n    result.budget_sufficient = null;\n    result.reasons = ['input: unsupported or unreadable data; estimation blocked'];\n    return result;\n  }\n}\nreturn $input.all().map((item,index)=>({json:estimateGenerationBudget(item.json),pairedItem:{item:index}}));"
      },
      "position": [
        560,
        0
      ]
    }
  ],
  "connections": {
    "Manual test": {
      "main": [
        [
          {
            "node": "Fictional demonstration only",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Fictional demonstration only": {
      "main": [
        [
          {
            "node": "Offline advisory result",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "active": false,
  "pinData": {},
  "settings": {
    "executionOrder": "v1",
    "timezone": "Africa/Luanda",
    "saveDataSuccessExecution": "none",
    "saveDataErrorExecution": "none",
    "saveManualExecutions": false
  },
  "tags": []
}
