{"files": {"main.ts": "import {run} from './core.ts';\nimport {POLICY} from './policy.ts';\nexport function evaluate(payload: any): any { return run(payload, POLICY); }\n", "core.ts": "import {parseDecimal, addDecimal, formatDecimal} from './helpers.ts';\n\nexport function run(payload: any, policy: any): any {\n  const now = payload && typeof payload.now !== 'undefined' ? Number(payload.now) : 0;\n  const events = payload && Array.isArray(payload.events) ? payload.events : [];\n  const maxAge = Number(policy.max_age);\n  const inclusiveAge = !!policy.inclusive_age;\n  const auditDuplicate = !!policy.audit_duplicate;\n  const scopeTenant = policy.scope_key === 'tenant';\n  const sortField = policy.latest_by === 'seq' ? 'seq' : 'ts';\n  const tieLast = policy.tie_policy === 'last';\n  const gapIgnore = policy.gap_policy === 'ignore';\n  const allowResurrection = !!policy.allow_resurrection;\n  const amountAbsolute = policy.amount_mode === 'absolute';\n  const deleteWins = !!policy.delete_wins;\n  const precision = Math.max(0, Math.floor(Number(policy.precision || 0)));\n  const orderKey = policy.order === 'key';\n  const duplicates: any[] = [];\n  const seen = new Set<string>();\n  const remaining: any[] = [];\n  for (let i = 0; i < events.length; i++) {\n    const ev = events[i];\n    const age = now - Number(ev.ts);\n    if (inclusiveAge ? age > maxAge : age >= maxAge) continue;\n    const dedupKey = scopeTenant ? String(ev.tenant) + '\\u0000' + String(ev.id) : String(ev.id);\n    if (seen.has(dedupKey)) {\n      if (auditDuplicate) duplicates.push(ev.id);\n      continue;\n    }\n    seen.add(dedupKey);\n    remaining.push({ev: ev, index: i});\n  }\n  const groups = new Map<string, any>();\n  for (const item of remaining) {\n    const ev = item.ev;\n    const gkey = String(ev.tenant) + '\\u0000' + String(ev.entity);\n    let group = groups.get(gkey);\n    if (!group) {\n      group = {tenant: ev.tenant, entity: ev.entity, orderIndex: item.index, items: []};\n      groups.set(gkey, group);\n    }\n    if (item.index < group.orderIndex) group.orderIndex = item.index;\n    group.items.push(item);\n  }\n  const entities: any[] = [];\n  for (const group of groups.values()) {\n    const earliestIndex = group.orderIndex;\n    const bySort = new Map<number, any>();\n    for (const item of group.items) {\n      const sortKey = Number(item.ev[sortField]);\n      if (!bySort.has(sortKey)) {\n        bySort.set(sortKey, item);\n      } else if (tieLast) {\n        bySort.set(sortKey, item);\n      }\n    }\n    const retained = Array.from(bySort.values());\n    const hadDelete = retained.some((it: any) => it.ev.kind === 'delete');\n    const sorted = retained.slice().sort((a: any, b: any) => Number(a.ev[sortField]) - Number(b.ev[sortField]));\n    let seq = 0;\n    let value = {coeff: BigInt(0), scale: 0};\n    let deleted = false;\n    for (const item of sorted) {\n      const ev = item.ev;\n      const evSeq = Number(ev.seq);\n      if (gapIgnore && evSeq !== seq + 1) continue;\n      seq = evSeq;\n      if (ev.kind === 'delete') {\n        deleted = true;\n      } else if (ev.kind === 'set') {\n        if (!deleted || allowResurrection) {\n          deleted = false;\n          const amount = parseDecimal(typeof ev.amount === 'undefined' || ev.amount === null ? '0' : ev.amount);\n          value = amountAbsolute ? amount : addDecimal(value, amount);\n        }\n      }\n    }\n    if (deleteWins && hadDelete) deleted = true;\n    entities.push({tenant: group.tenant, entity: group.entity, value: formatDecimal(value, precision), deleted: deleted, _order: earliestIndex});\n  }\n  if (orderKey) {\n    entities.sort((a: any, b: any) => {\n      const at = String(a.tenant), bt = String(b.tenant);\n      if (at < bt) return -1;\n      if (at > bt) return 1;\n      const ae = String(a.entity), be = String(b.entity);\n      if (ae < be) return -1;\n      if (ae > be) return 1;\n      return 0;\n    });\n  } else {\n    entities.sort((a: any, b: any) => a._order - b._order);\n  }\n  const outEntities = entities.map((e: any) => ({tenant: e.tenant, entity: e.entity, value: e.value, deleted: e.deleted}));\n  return {entities: outEntities, duplicates: duplicates};\n}\n", "policy.ts": "export const POLICY: any = {\n  delete_wins: false,\n  latest_by: 'timestamp',\n  scope_key: 'global',\n  tie_policy: 'last',\n  allow_resurrection: false,\n  amount_mode: 'delta',\n  gap_policy: 'ignore',\n  precision: 2,\n  inclusive_age: false,\n  max_age: 120,\n  audit_duplicate: false,\n  order: 'input'\n};\n", "helpers.ts": "export interface DecimalValue { coeff: bigint; scale: number; }\n\nfunction pow10(n: number): bigint {\n  let r = BigInt(1);\n  for (let i = 0; i < n; i++) r *= BigInt(10);\n  return r;\n}\n\nexport function parseDecimal(input: any): DecimalValue {\n  let s = String(input).trim();\n  let exponent = 0;\n  const eIdx = s.search(/[eE]/);\n  if (eIdx >= 0) {\n    exponent = parseInt(s.slice(eIdx + 1), 10) || 0;\n    s = s.slice(0, eIdx);\n  }\n  let sign = BigInt(1);\n  if (s.startsWith('-')) { sign = BigInt(-1); s = s.slice(1); }\n  else if (s.startsWith('+')) { s = s.slice(1); }\n  const dot = s.indexOf('.');\n  let intPart = dot >= 0 ? s.slice(0, dot) : s;\n  let fracPart = dot >= 0 ? s.slice(dot + 1) : '';\n  if (intPart === '') intPart = '0';\n  const digits = (intPart + fracPart).replace(/^0+(?=\\d)/, '');\n  let coeff = BigInt(digits === '' ? '0' : digits) * sign;\n  let scale = fracPart.length - exponent;\n  if (scale < 0) { coeff *= pow10(-scale); scale = 0; }\n  return {coeff: coeff, scale: scale};\n}\n\nexport function addDecimal(a: DecimalValue, b: DecimalValue): DecimalValue {\n  const scale = Math.max(a.scale, b.scale);\n  const ac = a.coeff * pow10(scale - a.scale);\n  const bc = b.coeff * pow10(scale - b.scale);\n  return {coeff: ac + bc, scale: scale};\n}\n\nexport function formatDecimal(value: DecimalValue, precision: number): string {\n  const negative = value.coeff < BigInt(0);\n  const absCoeff = negative ? -value.coeff : value.coeff;\n  let q: bigint;\n  if (precision >= value.scale) {\n    q = absCoeff * pow10(precision - value.scale);\n  } else {\n    const divisor = pow10(value.scale - precision);\n    const half = divisor / BigInt(2);\n    q = (absCoeff + half) / divisor;\n  }\n  let qs = q.toString();\n  let out: string;\n  if (precision === 0) {\n    out = qs;\n  } else {\n    while (qs.length <= precision) qs = '0' + qs;\n    out = qs.slice(0, qs.length - precision) + '.' + qs.slice(qs.length - precision);\n  }\n  return negative && q !== BigInt(0) ? '-' + out : out;\n}\n"}}