HANDOFF NOTE: target project "invoice" (requirements only)

1. Interface
evaluate(payload) -> {invoices: [{id, charge, refunded, balance, status}]}
- Empty input produces invoices: [].
- Do not mutate the payload.

2. Input model
Each invoice has:
- a unique id
- an integer created day
- a currency
- lines: nonnegative decimal-dollar strings
- fee_cents: nonnegative
- ordered events, each with eid, kind (refund, void or restore), day and nonnegative cents

3. Current configuration
Updates 1-5 are merged below. Later updates replaced earlier values.
- currency = "CAD" (Update 5). Filter out invoices with any other currency.
- rounding = "per_line" (Update 1)
- min_invoice_cents = 10 (Update 3)
- duplicate_event = "first" (Update 1)
- refunds_first = true (Update 4)
- void_is_tombstone = true (Update 5)
- inclusive_limit = true (Update 1)
- max_refund_age_days = 30 (Update 1)
- refund_overflow = "cap" (Update 4)
- fee_refundable = false (Update 5)
- order = "id" (Update 5). Sort by id only.
- status_case = "lower" (Update 3)

All 12 keys have an explicit current value. None is unresolved.

4. Algorithm
1. Filter out invoices whose currency is not CAD.
2. Convert each line to cents with exact decimal ROUND_HALF_UP. Under per_line, round each line and then sum. (Under "invoice" the unrounded sum would be rounded once; that is not the current setting.)
3. charge = line cents total + fee_cents. The charge includes the fee.
4. Skip the invoice if charge < min_invoice_cents (10).
5. Deduplicate events by eid. With "first", keep the first occurrence's value at its position.
6. Stably move all refund events ahead of all other events. Relative order within each group is preserved.
7. Start the invoice as active and process events in that order:
   - void: deactivates the invoice and disables future refunds. Because void_is_tombstone is true, restore can never re-enable refunds afterwards.
   - restore: re-enables refunds only if no void tombstone applies. Effectively, once a void has occurred, restore has no effect.
   - refund: applies only if the invoice is active and age = event day - created is >= 0 and <= 30 (the limit is inclusive).
   - Earlier refunds are never undone.
8. The refund ceiling is the principal only (charge minus fee), because fee_refundable is false.
9. On overflow, cap the refund at the remaining unrefunded ceiling. Do not reject the whole event.
10. refunded = total applied refunds. balance = charge - refunded.
11. Status:
    - "void" if the invoice is inactive at the end
    - otherwise "refunded" if refunded == charge
    - otherwise "open"
    - Status is lowercase.
    - Since fee_refundable is false, refunded can never equal charge when fee_cents > 0.
12. Sort the output invoices by id ascending.

5. Notes
- Because refunds_first is true, refunds are processed before any void event, regardless of original order. Check each refund's age and ceiling at that point.
- Ambiguity to confirm with tests: "principal" is the sum of line cents without the fee.
- Ignore all archived records for other projects (archive-north/south/east/west-NN). Their settings do not apply here.

6. Unresolved items
None. Further updates may arrive; apply them as replacements of the named keys only.