{"files":{"main.py":"from core import run\nfrom policy import POLICY\n\n\ndef evaluate(payload):\n    return run(payload, POLICY)\n","core.py":"from helpers import principal_cents\n\n\ndef run(payload, policy):\n    results = []\n    for invoice in payload.get('invoices', []):\n        if invoice['currency'] != policy['currency']:\n            continue\n\n        principal = principal_cents(invoice['lines'], policy['rounding'])\n        charge = principal + invoice['fee_cents']\n        if charge < policy['min_invoice_cents']:\n            continue\n\n        unique = {}\n        for event in invoice['events']:\n            eid = event['eid']\n            if eid not in unique or policy['duplicate_event'] == 'last':\n                unique[eid] = event\n        events = list(unique.values())\n        if policy['refunds_first']:\n            events = ([event for event in events if event['kind'] == 'refund']\n                      + [event for event in events if event['kind'] != 'refund'])\n\n        active = True\n        tombstoned = False\n        refunded = 0\n        ceiling = charge if policy['fee_refundable'] else principal\n        for event in events:\n            kind = event['kind']\n            if kind == 'void':\n                active = False\n                if policy['void_is_tombstone']:\n                    tombstoned = True\n            elif kind == 'restore':\n                if not tombstoned:\n                    active = True\n            elif kind == 'refund':\n                age = event['day'] - invoice['created']\n                limit = policy['max_refund_age_days']\n                within_limit = (age <= limit if policy['inclusive_limit']\n                                else age < limit)\n                if not active or age < 0 or not within_limit:\n                    continue\n                remaining = ceiling - refunded\n                amount = event['cents']\n                if policy['refund_overflow'] == 'reject' and amount > remaining:\n                    continue\n                refunded += min(amount, remaining)\n\n        if not active:\n            status = 'void'\n        elif refunded == charge:\n            status = 'refunded'\n        else:\n            status = 'open'\n        if policy['status_case'] == 'upper':\n            status = status.upper()\n\n        result = {\n            'id': invoice['id'],\n            'charge': charge,\n            'refunded': refunded,\n            'balance': charge - refunded,\n            'status': status,\n        }\n        results.append((invoice['created'], result))\n\n    if policy['order'] == 'created':\n        results.sort(key=lambda entry: (entry[0], entry[1]['id']))\n    else:\n        results.sort(key=lambda entry: entry[1]['id'])\n    return {'invoices': [result for _, result in results]}\n","policy.py":"POLICY = {\n    'currency': 'USD',\n    'rounding': 'per_line',\n    'min_invoice_cents': 10,\n    'duplicate_event': 'first',\n    'refunds_first': True,\n    'max_refund_age_days': 30,\n    'inclusive_limit': True,\n    'fee_refundable': True,\n    'refund_overflow': 'cap',\n    'void_is_tombstone': True,\n    'status_case': 'lower',\n    'order': 'id',\n}\n","helpers.py":"from decimal import Decimal\n\n\ndef _parts(value):\n    parts = Decimal(value).as_tuple()\n    coefficient = 0\n    for digit in parts.digits:\n        coefficient = coefficient * 10 + digit\n    return coefficient, parts.exponent\n\n\ndef _round_cents(coefficient, exponent):\n    exponent += 2\n    if exponent >= 0:\n        return coefficient * (10 ** exponent)\n    divisor = 10 ** (-exponent)\n    whole, remainder = divmod(coefficient, divisor)\n    return whole + int(remainder * 2 >= divisor)\n\n\ndef principal_cents(lines, rounding):\n    parts = [_parts(line) for line in lines]\n    if rounding == 'per_line':\n        return sum(_round_cents(coefficient, exponent)\n                   for coefficient, exponent in parts)\n    if not parts:\n        return 0\n    exponent = min(item[1] for item in parts)\n    coefficient = sum(value * (10 ** (scale - exponent))\n                      for value, scale in parts)\n    return _round_cents(coefficient, exponent)\n"}}