@hyperscale0/hsx 1.0.128 → 1.0.132

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/std/financing.hsx CHANGED
@@ -18,13 +18,56 @@ header financing
18
18
  // Disburse first credits an active marketplace.reservation on the same hold,
19
19
  // so list the plan in its converters; the deposit then counts as escrow credit.
20
20
  // disburse_to: seller pays principal to the seller party at activation (pay later
21
- // with the merchant paid on day one). For a return, capital refunds each paid
22
- // payment to the borrower, then runs unwind: it takes principal back to capital and
23
- // cancels open slices with their claims. Seller mode debits the seller; funds mode
24
- // needs the hold in return_verified and debits the hold, after which the payee's
25
- // escrow refund returns only the down payment. Escrow refund refuses while a live
26
- // plan funds the hold. Unwind needs no assessed or collected late charges; waive
27
- // them first.
21
+ // with the merchant paid on day one). Capital may refund any paid payment to
22
+ // the borrower. Payments stay refundable while the plan is active. The
23
+ // payment that clears the last slice completes the plan when no late charge or
24
+ // recovery cost is outstanding; otherwise expose complete to close it once they
25
+ // are settled or waived. When
26
+ // the plan leaves active (paid, written off or cancelled) it sets refundUntil to
27
+ // refund_within (default 30d) later, and payment, late charge and recovery cost
28
+ // refunds stop at that time. A refund that reopens the plan clears refundUntil, which is set
29
+ // from the calculated refundWindow so a restore can clear it. Once it passes,
30
+ // nothing can move the plan's payments or late charges, so they stop counting as
31
+ // open records. On a return, capital runs unwind: it takes the outstanding
32
+ // principal back to capital and cancels open slices with their claims. Seller
33
+ // mode debits the seller, who also returns the principal the borrower repaid;
34
+ // funds mode needs the hold in return_verified and debits the hold, after which
35
+ // the payee's escrow refund returns the down payment and the repaid principal.
36
+ // Before delivery, the Product
37
+ // operator runs unwind_undelivered on a funds-mode plan whose hold is still
38
+ // funded: principal goes back to capital and the hold's unwind_financed returns
39
+ // the down payment to the payer and closes the hold as refunded, all in one
40
+ // operation, so deliver is refused afterwards. It also cancels proposed and
41
+ // assessed late charges and recovery costs: goods that never arrived earn no
42
+ // fines. Escrow refund refuses while any plan that is
43
+ // not cancelled funds the hold, written off included, so the borrower never
44
+ // receives lender principal. A funds-mode plan writes off only once its hold
45
+ // has released principal to the seller: principal still held is not a loss, and
46
+ // unwind or unwind_undelivered returns it. unwind_undelivered needs no paid
47
+ // payments and no collected late charges; refund them first. unwind needs no
48
+ // assessed late charges; waive them first. When goods never arrive on a paid plan, the
49
+ // Product operator runs unwind_paid once the hold's deliverBy has passed, so an
50
+ // early payoff never cuts the seller's delivery window short. It refunds every
51
+ // payment, share, profit piece, fine and cost receipt and reverses the payoff's
52
+ // profit, fines and costs, cancels the plan, and returns the hold's whole price
53
+ // to the borrower; capital keeps the principal the borrower repaid. A settlement
54
+ // a lending.distribution has paid out keeps its profit and share, since investors
55
+ // hold that profit; only the other settlements' profit is refunded. unwind_paid
56
+ // selects at most max_history (default and most 366) of each record kind, so
57
+ // a payment refuses when it would leave more paid payments than max_history
58
+ // less one, keeping a settlement for the payoff, or more paid profit pieces,
59
+ // fine receipts or cost receipts than max_history; a fine or a recovery cost
60
+ // refuses past max_history unwaived fines or costs on the plan, counted across
61
+ // every late charge attachment, so a waived fine's cost keeps its own slot. A company lowers it to keep unwinds smaller. If any
62
+ // source account is short, the whole action refuses and nothing moves; a
63
+ // company that wants a partial refund writes that rule in its own program.
64
+ // returned_goods decides the split on returned goods, before or after payoff.
65
+ // keep_profit, the default, keeps the profit and fines paid so far with the
66
+ // company: unwind runs with payments still paid, and on a paid plan the Product
67
+ // operator runs return_hold after the refund window to return a return_verified
68
+ // hold's whole balance. refund_all makes unwind wait until capital has refunded
69
+ // every payment and collected charge, removes return_hold, and lets unwind_paid
70
+ // settle returned goods too.
28
71
  // Borrower- and seller-directed disbursement exclude collect_down_payment. A down_payment
29
72
  // there reduces principal but does not collect cash. Price is subject metadata,
30
73
  // not the borrower limit. The escrow payer and price must match the plan.
@@ -37,17 +80,20 @@ header financing
37
80
  // Expose late_charge.create as propose_assessment before the clock can assess;
38
81
  // provide matching plan, slice and borrower. Fine is fixed money, not a percentage.
39
82
  // Collection uses the minimum of payment cash and outstanding fine; a partial
40
- // payment does not settle the remaining fine. Recovery cost needs its own record.
83
+ // payment does not settle the remaining fine. Recovery cost needs its own record,
84
+ // which only the Product operator creates; only it cancels a proposed fine or cost.
41
85
  // Profit share credits capital's allocatedProfitPayable, not an outside agency.
42
86
  // Credit lines need adapter binding at activation; draws are ledger advances,
43
- // not evidence of outside funding. Caller-authorized limit approvals and advances
44
- // need the Product's operation authority. No clock declaration proves retries.
87
+ // not evidence of outside funding. Only the line's borrower, the Product operator
88
+ // by default, opens and closes the line and creates, draws, repays or cancels an
89
+ // advance on it. Only the Product operator creates the portfolio limit and
90
+ // approves borrower and portfolio limits. No clock declaration proves retries.
45
91
 
46
92
  // Cost: installments include cash and claim moves, child-account creation and
47
93
  // bounded invocations across slices and assessments. Month and charge limits affect
48
94
  // the bound. Run hsx cost on the selected policies; profit is not a platform tariff.
49
95
 
50
- instrument installments(months: integer(1, 366), pricing: enum(amortizing, flat_total), profit_rate: percent, charge_limit: integer(1, 3) = 3, disburse_to: enum(funds, borrower, seller) = funds, seller: party?, profit_earned: enum(on_payment, by_schedule, at_disbursement) = on_payment, apply: enum(fines_profit_principal, principal_profit, pro_rata) = fines_profit_principal, payoff_rebate: percent = 100%, write_off_after: duration = 90d, max_extension: duration = 90d, max_amendments: integer(0, 366) = 2, waiver_limit: percent = 100%, amendment_expiry: duration = 7d, payoff_quote_expiry: duration = 1h, allow_overdue: enum(allowed, blocked) = allowed, assessed_fines: enum(carry, require_waiver) = carry, down_payment: percent = 0%, funds: ref<escrow.hold>?, borrower: party = party(person), capital: party = programOperator, share: percent = 0%, limits: ref<financing.limits> = object(financing.limits), portfolio: ref<financing.portfolio_limit> = object(financing.portfolio_limit)) {
96
+ instrument installments(months: integer(1, 366), max_history: integer(2, 366) = 366, pricing: enum(amortizing, flat_total), profit_rate: percent, charge_limit: integer(1, 3) = 3, disburse_to: enum(funds, borrower, seller) = funds, seller: party?, profit_earned: enum(on_payment, by_schedule, at_disbursement) = on_payment, apply: enum(fines_profit_principal, principal_profit, pro_rata) = fines_profit_principal, payoff_rebate: percent = 100%, write_off_after: duration = 90d, max_extension: duration = 90d, max_amendments: integer(0, 366) = 2, waiver_limit: percent = 100%, amendment_expiry: duration = 7d, payoff_quote_expiry: duration = 1h, refund_within: duration = 30d, returned_goods: enum(keep_profit, refund_all) = keep_profit, allow_overdue: enum(allowed, blocked) = allowed, assessed_fines: enum(carry, require_waiver) = carry, down_payment: percent = 0%, funds: ref<escrow.hold>?, borrower: party = party(person), capital: party = programOperator, share: percent = 0%, limits: ref<financing.limits> = object(financing.limits), portfolio: ref<financing.portfolio_limit> = object(financing.portfolio_limit)) {
51
97
  dependencies {
52
98
  funds {
53
99
  selector: disburse_to, is: funds
@@ -65,9 +111,10 @@ instrument installments(months: integer(1, 366), pricing: enum(amortizing, flat_
65
111
  summary: "Funding, profit recognition and payment order chosen by the program."
66
112
  fields {
67
113
  funds: ref<funds>?, borrower: account of borrower, settlementIdentity: text, borrowerLimit: ref<limits>, portfolioLimit: ref<portfolio>
68
- months: integer = months, dates: list(date, months)
114
+ months: integer = months, dates: list(date, months), historyLimit: integer = max_history, paymentLimit: integer = subtract(max_history, 1)
69
115
  allocatedPayable: account(capital, cash, "allocatedProfitPayable"), capital: account(capital, cash, "capital"), profitIncome: account(capital, cash, "profitIncome"), loss: account(capital, claim, "loss")
70
- downPaymentRemaining: money?, downPaymentCredit: money?, principalReturned: money?, profitReturned: money?, lossRecorded: money?
116
+ downPaymentRemaining: money?, downPaymentCredit: money?, principalOutstanding: money?, principalRepaid: money?, principalReturned: money?, profitReturned: money?, lossRecorded: money?, refundWindow: date?, refundUntil: date?
117
+ openSlices: integer?, openCharges: money?, openCosts: money?, payoffRefunds: integer?
71
118
  price: money = subject.price
72
119
  downPayment: money = rate(self.price, down_payment)
73
120
  principal: money = subtract(self.price, self.downPayment)
@@ -79,6 +126,7 @@ instrument installments(months: integer(1, 366), pricing: enum(amortizing, flat_
79
126
  invariants sum "principalReceivable.balance" of { instrument: all(financing.installments.slice), reference: "portfolioLimit", anchor: self.portfolioLimit, states: [pending, due, paid], limit: 366 } <= self.portfolioLimit.limit
80
127
  action create {
81
128
  subject { price: money }
129
+ actor: { party: borrower }
82
130
  requires self.borrowerLimit.borrower == self.borrower
83
131
  when pricing is flat_total { calculate: [{ target: flatProfit, op: rate, base: { field: self.principal }, bps: { literal: profit_rate }, rounding: floor }] }
84
132
  when disburse_to is funds { requires self.borrower == self.funds.payer; requires self.price == self.funds.price }
@@ -121,11 +169,13 @@ instrument installments(months: integer(1, 366), pricing: enum(amortizing, flat_
121
169
  }
122
170
  action complete {
123
171
  from: active, to: paid
172
+ calculate: [{ target: refundWindow, op: shift, date: { field: self.now }, milliseconds: { literal: refund_within }, direction: after }]
173
+ set: { refundUntil: { field: self.refundWindow } }
124
174
  requires count of { instrument: slice, reference: "plan", anchor: self.id, states: [pending, due], limit: 366 } == 0
125
175
  requires sum "receivable.balance" of { instrument: all(financing.late_charge), reference: "plan", anchor: self.id, states: [assessed, collected], limit: 366 } == "0"
126
176
  requires sum "receivable.balance" of { instrument: all(financing.late_charge.cost), reference: "plan", anchor: self.id, states: [assessed, collected], limit: 366 } == "0"
127
177
  }
128
- action restore { from: [active, paid], to: active, actor: { parent: payment } }
178
+ action restore { from: [active, paid], to: active, actor: { parent: payment }, input { refundUntil: date? }, set: { refundUntil: { field: input.refundUntil } } }
129
179
  action payoff {
130
180
  from: active, to: paid, actor: { party: borrower }
131
181
  input { quote: ref<payoff_quote> }
@@ -133,7 +183,9 @@ instrument installments(months: integer(1, 366), pricing: enum(amortizing, flat_
133
183
  deadline: { at: input.quote.expiresAt }
134
184
  calculate: [
135
185
  { target: "principalReturned", op: "aggregate", selection: { instrument: slice, reference: "plan", anchor: self.id, states: [pending, due], limit: 366 }, measure: { sum: "principalReceivable.balance" } }
186
+ { target: refundWindow, op: shift, date: { field: self.now }, milliseconds: { literal: refund_within }, direction: after }
136
187
  ]
188
+ set: { refundUntil: { field: self.refundWindow } }
137
189
  invoke: [{ selection: { instrument: all(financing.late_charge), reference: "plan", anchor: self.id, states: [assessed, collected], limit: 366, order: [overdueAt] }, action: payoff, input: {} }, { selection: { instrument: all(financing.late_charge.cost), reference: "plan", anchor: self.id, states: [assessed, collected], limit: 366, order: [overdueAt] }, action: payoff, input: {} }]
138
190
  invoke: [{ selection: { instrument: slice, reference: "plan", anchor: self.id, states: [pending, due], limit: 366, order: [position] }, action: payoff, input: { identity: { field: self.settlementIdentity } } }, { reference: self.id, action: record_payoff, input: {} }]
139
191
  }
@@ -145,28 +197,95 @@ instrument installments(months: integer(1, 366), pricing: enum(amortizing, flat_
145
197
  action write_off {
146
198
  from: active, to: written_off, actor: { parent: write_off_request }
147
199
  input { overdueSlice: ref<slice> }
148
- calculate: [{ target: "lossRecorded", op: "aggregate", selection: { instrument: slice, reference: "plan", anchor: self.id, states: [pending, due], limit: 366 }, measure: { sum: "principalReceivable.balance" } }]
200
+ calculate: [{ target: "lossRecorded", op: "aggregate", selection: { instrument: slice, reference: "plan", anchor: self.id, states: [pending, due], limit: 366 }, measure: { sum: "principalReceivable.balance" } }, { target: refundWindow, op: shift, date: { field: self.now }, milliseconds: { literal: refund_within }, direction: after }]
201
+ set: { refundUntil: { field: self.refundWindow } }
149
202
  due: { at: input.overdueSlice.lossEligibleAt }
150
203
  requires input.overdueSlice.plan == self.id; requires input.overdueSlice.principalReceivable.balance > "0"
204
+ when disburse_to is funds { requires self.funds in [released] }
151
205
  invoke: [{ selection: { instrument: slice, reference: "plan", anchor: self.id, states: [pending, due], limit: 366, order: [position] }, action: write_off, input: {} }, { instrument: settlement, action: create, input: { plan: { field: self.id }, identity: { field: self.settlementIdentity }, principal: { literal: "0" }, profit: { literal: "0" }, loss: { field: self.lossRecorded }, kind: { literal: "loss" } } }]
152
206
  }
153
207
  action record_payment {
154
208
  from: active, to: active, actor: { parent: payment }
155
209
  input { identity: text, principal: money, profit: money }
210
+ calculate: [
211
+ { target: openSlices, op: aggregate, selection: { instrument: slice, reference: "plan", anchor: self.id, states: [pending, due], limit: 366 }, measure: count }
212
+ { target: openCharges, op: aggregate, selection: { instrument: all(financing.late_charge), reference: "plan", anchor: self.id, states: [assessed, collected], limit: 366 }, measure: { sum: "receivable.balance" } }
213
+ { target: openCosts, op: aggregate, selection: { instrument: all(financing.late_charge.cost), reference: "plan", anchor: self.id, states: [assessed, collected], limit: 366 }, measure: { sum: "receivable.balance" } }
214
+ ]
156
215
  invoke: [{ instrument: settlement, action: create, input: { plan: { field: self.id }, identity: { field: input.identity }, principal: { field: input.principal }, profit: { field: input.profit }, loss: { literal: "0" }, kind: { literal: "cash" } } }]
216
+ invoke: [{ selection: { instrument: current(), reference: "borrowerLimit", anchor: self.borrowerLimit, states: [active], limit: 1, where: { id: { field: self.id }, openSlices: { literal: 0 }, openCharges: { literal: "0" }, openCosts: { literal: "0" } } }, action: complete, input: {} }]
157
217
  }
158
218
  action cancel { from: quoted, to: cancelled }
159
219
  action void { from: signed, to: cancelled, actor: { party: borrower } }
160
220
  action unwind { allowZero: true,
161
221
  from: active, to: cancelled, actor: { party: capital }
222
+ calculate: [
223
+ { target: refundWindow, op: shift, date: { field: self.now }, milliseconds: { literal: refund_within }, direction: after }
224
+ { target: principalOutstanding, op: aggregate, selection: { instrument: slice, reference: "plan", anchor: self.id, states: [pending, due], limit: 366 }, measure: { sum: "principalReceivable.balance" } }
225
+ { target: principalRepaid, op: subtract, base: { field: self.principal }, subtract: [{ field: self.principalOutstanding }] }
226
+ ]
227
+ set: { refundUntil: { field: self.refundWindow } }
162
228
  when disburse_to is borrower { requires true == false }
163
229
  when disburse_to is funds { requires self.funds in [return_verified] }
230
+ when returned_goods is refund_all {
231
+ requires count of { instrument: payment, reference: "plan", anchor: self.id, states: [paid], limit: 366 } == 0
232
+ requires count of { instrument: all(financing.late_charge), reference: "plan", anchor: self.id, states: [collected], limit: 366 } == 0
233
+ requires count of { instrument: all(financing.late_charge.cost), reference: "plan", anchor: self.id, states: [collected], limit: 366 } == 0
234
+ }
235
+ requires count of { instrument: all(financing.late_charge), reference: "plan", anchor: self.id, states: [assessed], limit: 366 } == 0
236
+ requires count of { instrument: all(financing.late_charge.cost), reference: "plan", anchor: self.id, states: [assessed], limit: 366 } == 0
237
+ when disburse_to is funds { moves self.principalOutstanding from self.funds.held to self.capital economics { purpose: principal, sourceParty: capital } }
238
+ when disburse_to is seller {
239
+ moves self.principalOutstanding from seller to self.capital economics { purpose: principal, sourceParty: seller }
240
+ moves self.principalRepaid from seller to self.borrower economics { purpose: principal, sourceParty: seller }
241
+ }
242
+ invoke: [{ selection: { instrument: slice, reference: "plan", anchor: self.id, states: [pending, due], limit: 366, order: [position] }, action: cancel, input: {} }]
243
+ }
244
+ action unwind_undelivered { allowZero: true,
245
+ from: active, to: cancelled, actor: { party: programOperator }
246
+ calculate: [{ target: refundWindow, op: shift, date: { field: self.now }, milliseconds: { literal: refund_within }, direction: after }]
247
+ set: { refundUntil: { field: self.refundWindow } }
248
+ when disburse_to is borrower { requires true == false }
249
+ when disburse_to is seller { requires true == false }
250
+ when disburse_to is funds { requires self.funds in [funded] }
164
251
  requires count of { instrument: payment, reference: "plan", anchor: self.id, states: [paid], limit: 366 } == 0
165
- requires count of { instrument: all(financing.late_charge), reference: "plan", anchor: self.id, states: [assessed, collected], limit: 366 } == 0
166
- requires count of { instrument: all(financing.late_charge.cost), reference: "plan", anchor: self.id, states: [assessed, collected], limit: 366 } == 0
252
+ requires count of { instrument: all(financing.late_charge), reference: "plan", anchor: self.id, states: [collected], limit: 366 } == 0
253
+ requires count of { instrument: all(financing.late_charge.cost), reference: "plan", anchor: self.id, states: [collected], limit: 366 } == 0
167
254
  when disburse_to is funds { moves self.principal from self.funds.held to self.capital economics { purpose: principal, sourceParty: capital } }
168
- when disburse_to is seller { moves self.principal from seller to self.capital economics { purpose: principal, sourceParty: seller } }
169
255
  invoke: [{ selection: { instrument: slice, reference: "plan", anchor: self.id, states: [pending, due], limit: 366, order: [position] }, action: cancel, input: {} }]
256
+ invoke: [{ selection: { instrument: all(financing.late_charge.cost), reference: "plan", anchor: self.id, states: [proposed, assessed], limit: 366 }, action: cancel_undelivered, input: {} }, { selection: { instrument: all(financing.late_charge), reference: "plan", anchor: self.id, states: [proposed, assessed], limit: 366 }, action: cancel_undelivered, input: {} }]
257
+ when disburse_to is funds { invoke: [{ reference: self.funds, action: unwind_financed, input: {} }] }
258
+ }
259
+ action return_hold {
260
+ from: paid, to: paid, actor: { party: programOperator }
261
+ requires self.refundUntil < self.now
262
+ when disburse_to is borrower { requires true == false }
263
+ when disburse_to is seller { requires true == false }
264
+ when returned_goods is refund_all { requires true == false }
265
+ when disburse_to is funds {
266
+ requires self.funds in [return_verified]
267
+ invoke: [{ reference: self.funds, action: unwind_financed, input: {} }]
268
+ }
269
+ }
270
+ action unwind_paid { expansionLimit: 8192,
271
+ from: paid, to: cancelled, actor: { party: programOperator }
272
+ when disburse_to is borrower { requires true == false }
273
+ when disburse_to is seller { requires true == false }
274
+ when disburse_to is funds {
275
+ when returned_goods is keep_profit { requires self.funds in [funded] }
276
+ when returned_goods is refund_all { requires self.funds in [funded, return_verified] }
277
+ requires self.funds.deliverBy < self.now
278
+ }
279
+ invoke: [{ selection: { instrument: settlement, reference: "plan", anchor: self.id, states: [recorded], limit: max_history }, action: unwind, input: {} }, { selection: { instrument: profit_piece, reference: "plan", anchor: self.id, states: [paid], limit: max_history, where: { payment.status: { literal: "refunded" } } }, action: refund, input: {} }, { selection: { instrument: payment, reference: "plan", anchor: self.id, states: [paid], limit: max_history }, action: unwind, input: {} }, { reference: self.id, action: unwind_payoff_profit, input: {} }]
280
+ invoke: [{ selection: { instrument: all(financing.late_charge.receipt), reference: "plan", anchor: self.id, states: [paid], limit: max_history }, action: refund, input: {} }, { selection: { instrument: all(financing.late_charge.cost.receipt), reference: "plan", anchor: self.id, states: [paid], limit: max_history }, action: refund, input: {} }, { selection: { instrument: all(financing.late_charge), reference: "plan", anchor: self.id, states: [collected], limit: max_history }, action: unwind_payoff, input: {} }, { selection: { instrument: all(financing.late_charge.cost), reference: "plan", anchor: self.id, states: [collected], limit: max_history }, action: unwind_payoff, input: {} }]
281
+ invoke: [{ selection: { instrument: slice, reference: "plan", anchor: self.id, states: [pending, due, paid], limit: 366, order: [position] }, action: cancel, input: {} }]
282
+ invoke: [{ selection: { instrument: all(financing.late_charge.cost), reference: "plan", anchor: self.id, states: [proposed, assessed, collected], limit: max_history }, action: cancel_undelivered, input: {} }, { selection: { instrument: all(financing.late_charge), reference: "plan", anchor: self.id, states: [proposed, assessed, collected], limit: max_history }, action: cancel_undelivered, input: {} }]
283
+ when disburse_to is funds { invoke: [{ reference: self.funds, action: unwind_financed, input: {} }] }
284
+ }
285
+ action unwind_payoff_profit {
286
+ from: cancelled, to: cancelled, actor: { parent: current() }
287
+ calculate: [{ target: payoffRefunds, op: aggregate, selection: { instrument: settlement, reference: "plan", anchor: self.id, states: [refunded], limit: 1, where: { identity: { field: self.settlementIdentity } } }, measure: count }]
288
+ invoke: [{ selection: { instrument: slice, reference: "plan", anchor: self.id, states: [paid], limit: 366, where: { payoffIdentity: { field: self.settlementIdentity } } }, action: unwind_payoff, guard: { kind: compare, left: { field: self.payoffRefunds }, operator: "==", right: { literal: 1 } }, input: {} }]
170
289
  }
171
290
  records {
172
291
  write_off_request: {
@@ -243,7 +362,7 @@ instrument installments(months: integer(1, 366), pricing: enum(amortizing, flat_
243
362
  set: { acceptedAt: { field: self.now } }
244
363
  when assessed_fines is require_waiver { requires self.waiverPercent == 10000 }
245
364
  invoke: [{ selection: { instrument: all(financing.late_charge), reference: "plan", anchor: self.plan, states: [assessed, collected], limit: 366 }, action: waive_for_amendment, input: { amendment: { field: self.id } } }, { selection: { instrument: all(financing.late_charge.cost), reference: "plan", anchor: self.plan, states: [assessed, collected], limit: 366 }, action: waive_for_amendment, input: { amendment: { field: self.id } } }]
246
- invoke: [{ selection: { instrument: all(collections.case), reference: "plan", anchor: self.plan, states: [opened, assigned], limit: 366 }, action: recall, input: {} }, { selection: { instrument: all(financing.late_charge.cost), reference: "plan", anchor: self.plan, states: [proposed], limit: 366 }, action: cancel, input: {} }, { selection: { instrument: all(financing.late_charge), reference: "plan", anchor: self.plan, states: [proposed], limit: 366 }, action: cancel, input: {} }]
365
+ invoke: [{ selection: { instrument: all(collections.case), reference: "plan", anchor: self.plan, states: [opened, assigned], limit: 366 }, action: recall, input: {} }, { selection: { instrument: all(financing.late_charge.cost), reference: "plan", anchor: self.plan, states: [proposed], limit: 366 }, action: cancel_for_amendment, input: {} }, { selection: { instrument: all(financing.late_charge), reference: "plan", anchor: self.plan, states: [proposed], limit: 366 }, action: cancel_for_amendment, input: {} }]
247
366
  invoke: [{ selection: { instrument: slice, reference: "plan", anchor: self.plan, states: [pending, due], limit: 366, order: [position] }, action: amend, input: { amendment: { field: self.id } } }]
248
367
  invoke: [{ selection: { instrument: slice, reference: "plan", anchor: self.plan, states: [paid], limit: 366 }, action: verify_fixed_date, input: { amendment: { field: self.id } } }]
249
368
  }
@@ -266,7 +385,7 @@ instrument installments(months: integer(1, 366), pricing: enum(amortizing, flat_
266
385
  settlement: {
267
386
  fields { plan: ref<parent>, identity: text, principal: money, profit: money, loss: money, kind: enum(cash, loss), share: money?, retainedProfit: money?
268
387
  allocatedPayable: account(capital, cash, "allocatedProfitPayable"), capital: account(capital, cash, "capital"), profitIncome: account(capital, cash, "profitIncome"), lossAccount: account(capital, claim, "loss") }
269
- lifecycle { states: [recorded, refunded], initial: recorded }
388
+ lifecycle { states: [recorded, distributed, refunded], initial: recorded }
270
389
  action create { allowZero: true,
271
390
  actor: { parent: parent }
272
391
  input { plan: ref<parent>, identity: text, principal: money, profit: money, loss: money, kind: enum(cash, loss) }
@@ -278,6 +397,16 @@ instrument installments(months: integer(1, 366), pricing: enum(amortizing, flat_
278
397
  from: recorded, to: refunded, actor: { parent: payment }
279
398
  moves self.share from self.allocatedPayable to self.profitIncome economics { purpose: internal, sourceParty: capital }
280
399
  }
400
+ action distribute { from: recorded, to: distributed, actor: { parent: all(lending.distribution) } }
401
+ // profitIncome is one account per capital party, so refunding profit a
402
+ // distribution already paid out would spend other plans' profit. unwind_paid
403
+ // unwinds only settlements still recorded; each marks its payment refunded,
404
+ // and the plan refunds the profit pieces and payoff profit behind them.
405
+ action unwind { allowZero: true,
406
+ from: recorded, to: refunded, actor: { parent: parent }
407
+ moves self.share from self.allocatedPayable to self.profitIncome economics { purpose: internal, sourceParty: capital }
408
+ invoke: [{ selection: { instrument: payment, reference: "plan", anchor: self.plan, states: [paid], limit: 1, where: { paymentIdentity: { field: self.identity } } }, action: unwind, input: {} }]
409
+ }
281
410
  }
282
411
  slice: {
283
412
  revisioned: true
@@ -289,7 +418,7 @@ instrument installments(months: integer(1, 366), pricing: enum(amortizing, flat_
289
418
  profitBase: money?, profitAllocated: money?, profitRemainder: money?, profitAdjustment: money?
290
419
  principal: money, profit: money, instalment: money
291
420
  principalReceivable: account(self, claim), profitReceivable: account(self, claim), profitEarned: account(self, claim)
292
- payoffIdentity: text?, payoffRebate: money?, payoffProfitTotal: money?, payoffProfit: money?
421
+ payoffIdentity: text?, payoffRebate: money?, payoffProfitTotal: money?, payoffProfit: money?, payoffReceipt: text?
293
422
  paymentBudget: money?, principalPayment: money?, principalBasePayment: money?, principalRatio: money?, profitPayment: money?, profitBudget: money?, collectibleProfit: money?, collectibleTotal: money?, ratioDenominator: integer = sum(10000, profit_rate)
294
423
  debt: account(borrower, claim, contra, "debt"), loss: account(capital, claim, "loss")
295
424
  }
@@ -378,7 +507,7 @@ instrument installments(months: integer(1, 366), pricing: enum(amortizing, flat_
378
507
  when apply is principal_profit {
379
508
  invoke: [
380
509
  { instrument: principal_piece, action: create, input: { slice: { field: self.id }, payment: { field: input.payment }, limit: { field: self.principalReceivable.balance } } }
381
- { instrument: profit_piece, action: create, input: { slice: { field: self.id }, payment: { field: input.payment }, limit: { field: self.collectibleProfit } } }
510
+ { instrument: profit_piece, action: create, input: { plan: { field: self.plan }, slice: { field: self.id }, payment: { field: input.payment }, limit: { field: self.collectibleProfit } } }
382
511
  { selection: { instrument: current(), reference: "plan", anchor: self.plan, states: [pending, due], limit: 1, where: { id: { field: self.id }, principalReceivable.balance: { literal: "0" }, profitReceivable.balance: { literal: "0" }, profitEarned.balance: { literal: "0" } } }, action: complete, input: {} }
383
512
  ]
384
513
  }
@@ -392,7 +521,7 @@ instrument installments(months: integer(1, 366), pricing: enum(amortizing, flat_
392
521
  invoke: [
393
522
  { selection: { instrument: all(financing.late_charge), reference: "slice", anchor: self.id, states: [assessed], limit: charge_limit, order: [overdueAt] }, action: collect, input: { payment: { field: input.payment } } }
394
523
  { selection: { instrument: all(financing.late_charge.cost), reference: "plan", anchor: self.plan, states: [assessed], limit: charge_limit, order: [overdueAt], where: { assessment.slice: { field: self.id } } }, action: collect, input: { payment: { field: input.payment } } }
395
- { instrument: profit_piece, action: create, input: { slice: { field: self.id }, payment: { field: input.payment }, limit: { field: self.collectibleProfit } } }
524
+ { instrument: profit_piece, action: create, input: { plan: { field: self.plan }, slice: { field: self.id }, payment: { field: input.payment }, limit: { field: self.collectibleProfit } } }
396
525
  { instrument: principal_piece, action: create, input: { slice: { field: self.id }, payment: { field: input.payment }, limit: { field: self.principalReceivable.balance } } }
397
526
  { selection: { instrument: current(), reference: "plan", anchor: self.plan, states: [pending, due], limit: 1, where: { id: { field: self.id }, principalReceivable.balance: { literal: "0" }, profitReceivable.balance: { literal: "0" }, profitEarned.balance: { literal: "0" } } }, action: complete, input: {} }
398
527
  ]
@@ -417,7 +546,7 @@ instrument installments(months: integer(1, 366), pricing: enum(amortizing, flat_
417
546
  ]
418
547
  invoke: [
419
548
  { instrument: principal_piece, action: create, input: { slice: { field: self.id }, payment: { field: input.payment }, limit: { field: self.principalPayment } } }
420
- { instrument: profit_piece, action: create, input: { slice: { field: self.id }, payment: { field: input.payment }, limit: { field: self.profitPayment } } }
549
+ { instrument: profit_piece, action: create, input: { plan: { field: self.plan }, slice: { field: self.id }, payment: { field: input.payment }, limit: { field: self.profitPayment } } }
421
550
  { selection: { instrument: current(), reference: "plan", anchor: self.plan, states: [pending, due], limit: 1, where: { id: { field: self.id }, principalReceivable.balance: { literal: "0" }, profitReceivable.balance: { literal: "0" }, profitEarned.balance: { literal: "0" } } }, action: complete, input: {} }
422
551
  ]
423
552
  }
@@ -431,17 +560,21 @@ instrument installments(months: integer(1, 366), pricing: enum(amortizing, flat_
431
560
  set: { payoffIdentity: { field: input.identity } }
432
561
  calculate: [{ target: payoffRebate, op: rate, base: { field: self.profitReceivable.balance }, bps: { literal: payoff_rebate }, rounding: floor }, { target: payoffProfitTotal, op: sum, values: [{ field: self.profitReceivable.balance }, { field: self.profitEarned.balance }] }, { target: payoffProfit, op: subtract, base: { field: self.payoffProfitTotal }, subtract: [{ field: self.payoffRebate }] }]
433
562
  moves self.principalReceivable.balance from self.plan.borrower to self.plan.capital economics { purpose: principal, sourceParty: borrower }
434
- moves self.payoffProfit from self.plan.borrower to self.plan.profitIncome economics { recipient: capital, company: { purpose: earning, sourceParty: borrower }, participant: { purpose: pass_through, sourceParty: borrower } }
563
+ moves self.payoffProfit from self.plan.borrower to self.plan.profitIncome capture "payoffReceipt" economics { recipient: capital, company: { purpose: earning, sourceParty: borrower }, participant: { purpose: pass_through, sourceParty: borrower } }
435
564
  moves self.principalReceivable.balance from self.principalReceivable to self.debt economics { purpose: internal, sourceParty: programOperator }
436
565
  moves self.profitReceivable.balance from self.profitReceivable to self.debt economics { purpose: internal, sourceParty: programOperator }
437
566
  moves self.profitEarned.balance from self.profitEarned to self.debt economics { purpose: internal, sourceParty: programOperator }
438
567
  }
568
+ action unwind_payoff { allowZero: true,
569
+ from: paid, to: paid, actor: { parent: parent }
570
+ moves self.payoffProfit from self.plan.profitIncome to self.plan.borrower economics { recipient: capital, company: { purpose: earning, sourceParty: capital, reversalOf: self.payoffReceipt }, participant: unclassified }
571
+ }
439
572
  action write_off { allowZero: true,
440
573
  from: [pending, due], to: written_off, actor: { parent: parent }
441
574
  moves self.principalReceivable.balance from self.principalReceivable to self.loss economics { purpose: internal, sourceParty: programOperator }; moves self.profitReceivable.balance from self.profitReceivable to self.debt economics { purpose: internal, sourceParty: programOperator }; moves self.profitEarned.balance from self.profitEarned to self.loss economics { purpose: internal, sourceParty: programOperator }
442
575
  }
443
576
  action cancel { allowZero: true,
444
- from: [pending, due], to: cancelled, actor: { parent: parent }
577
+ from: [pending, due, paid], to: cancelled, actor: { parent: parent }
445
578
  moves self.principalReceivable.balance from self.principalReceivable to self.debt economics { purpose: internal, sourceParty: programOperator }; moves self.profitReceivable.balance from self.profitReceivable to self.debt economics { purpose: internal, sourceParty: programOperator }; moves self.profitEarned.balance from self.profitEarned to self.debt economics { purpose: internal, sourceParty: programOperator }
446
579
  }
447
580
  }
@@ -487,11 +620,16 @@ instrument installments(months: integer(1, 366), pricing: enum(amortizing, flat_
487
620
  action record {
488
621
  from: paid, to: paid, actor: { parent: current() }
489
622
  requires self.held.balance == "0"
623
+ requires count of { instrument: current(), reference: "plan", anchor: self.plan, states: [paid], limit: 366 } <= self.plan.paymentLimit
624
+ requires count of { instrument: profit_piece, reference: "plan", anchor: self.plan, states: [paid], limit: 366 } <= self.plan.historyLimit
625
+ requires count of { instrument: all(financing.late_charge.receipt), reference: "plan", anchor: self.plan, states: [paid], limit: 366 } <= self.plan.historyLimit
626
+ requires count of { instrument: all(financing.late_charge.cost.receipt), reference: "plan", anchor: self.plan, states: [paid], limit: 366 } <= self.plan.historyLimit
490
627
  calculate: [{ target: principalReturned, op: aggregate, selection: { instrument: principal_piece, reference: "payment", anchor: self.id, states: [paid], limit: 366 }, measure: { sum: "amount" } }, { target: profitReturned, op: aggregate, selection: { instrument: profit_piece, reference: "payment", anchor: self.id, states: [paid], limit: 366 }, measure: { sum: "amount" } }]
491
628
  invoke: [{ reference: self.plan, action: record_payment, input: { identity: { field: self.paymentIdentity }, principal: { field: self.principalReturned }, profit: { field: self.profitReturned } } }]
492
629
  }
630
+ action unwind { from: paid, to: refunded, actor: { parent: [parent, settlement] } }
493
631
  action refund {
494
- from: paid, to: refunded, actor: { party: capital }
632
+ from: paid, to: refunded, actor: { party: capital }, deadline: { at: self.plan.refundUntil }
495
633
  requires unique "settlement_consumption" on [self.plan, self.paymentIdentity]; requires self.plan in [active, paid]
496
634
  requires count of { instrument: settlement, reference: "plan", anchor: self.plan, states: [recorded], limit: 366, where: { identity: { field: self.paymentIdentity } } } == 1
497
635
  invoke: [{ selection: { instrument: settlement, reference: "plan", anchor: self.plan, states: [recorded], limit: 366, where: { identity: { field: self.paymentIdentity } } }, action: refund, input: {} }]
@@ -517,12 +655,12 @@ instrument installments(months: integer(1, 366), pricing: enum(amortizing, flat_
517
655
  }
518
656
  }
519
657
  profit_piece: {
520
- fields { cashReceipt: text?, slice: ref<slice>, payment: ref<payment>, limit: money, amount: money? }
658
+ fields { cashReceipt: text?, plan: ref<parent>, slice: ref<slice>, payment: ref<payment>, limit: money, amount: money? }
521
659
  lifecycle { states: [paid, refunded], initial: paid }
522
660
  action create { allowZero: true,
523
661
  calculate: [{ target: amount, op: minimum, values: [{ field: self.payment.held.balance }, { field: self.limit }] }]
524
662
  actor: { parent: slice }
525
- input { slice: ref<slice>, payment: ref<payment>, limit: money }
663
+ input { plan: ref<parent>, slice: ref<slice>, payment: ref<payment>, limit: money }
526
664
  requires unique "profit_receipt" on [self.slice, self.payment]
527
665
  moves self.amount from self.payment.held to self.slice.plan.profitIncome capture "cashReceipt" economics { recipient: capital, company: { purpose: earning, sourceParty: borrower }, participant: { purpose: pass_through, sourceParty: programOperator } }
528
666
  when profit_earned is on_payment { moves self.amount from self.slice.profitReceivable to self.slice.debt economics { purpose: internal, sourceParty: programOperator } }
@@ -530,7 +668,7 @@ instrument installments(months: integer(1, 366), pricing: enum(amortizing, flat_
530
668
  when profit_earned is at_disbursement { moves self.amount from self.slice.profitEarned to self.slice.debt economics { purpose: internal, sourceParty: programOperator } }
531
669
  }
532
670
  action refund { allowZero: true,
533
- from: paid, to: refunded, actor: { parent: payment }
671
+ from: paid, to: refunded, actor: { parent: [payment, parent] }
534
672
  moves self.amount from self.slice.plan.profitIncome to self.slice.plan.borrower economics { recipient: capital, company: { purpose: earning, sourceParty: capital, reversalOf: self.cashReceipt }, participant: unclassified }
535
673
  when profit_earned is on_payment { moves self.amount from self.slice.debt to self.slice.profitReceivable economics { purpose: internal, sourceParty: borrower } }
536
674
  when profit_earned is by_schedule { moves self.amount from self.slice.debt to self.slice.profitEarned economics { purpose: internal, sourceParty: borrower } }
@@ -552,11 +690,11 @@ instrument late_charge(on: ref<financing.installments>[], grace: duration = 3d,
552
690
  familyRevision: 1
553
691
  summary: "Separate fine and evidenced recovery claims on an overdue installment."
554
692
  fields {
555
- plan: ref<on>, slice: ref<on.slice>, borrower: account of borrower, fine: money = fine, overdueAt: date?
693
+ plan: ref<on>, slice: ref<on.slice>, borrower: account of borrower, fine: money = fine, overdueAt: date?, payoffAmount: money?, payoffReceipt: text?
556
694
  receivable: account(self, claim), debt: account(borrower, claim, contra, "debt")
557
695
  }
558
696
  lifecycle { states: [proposed, assessed, collected, waived], initial: proposed }
559
- action create { input { slice: ref<on.slice>? }, calculate: [{ target: overdueAt, op: shift, date: { field: self.slice.dueAt }, milliseconds: { literal: grace }, direction: after }], requires unique "late_assessment" on [self.slice, self.overdueAt]; requires self.borrower == self.slice.plan.borrower; requires self.plan == self.slice.plan }
697
+ action create { input { slice: ref<on.slice>? }, calculate: [{ target: overdueAt, op: shift, date: { field: self.slice.dueAt }, milliseconds: { literal: grace }, direction: after }, { target: payoffAmount, op: sum, values: [{ literal: "0" }] }], requires unique "late_assessment" on [self.slice, self.overdueAt]; requires self.borrower == self.slice.plan.borrower; requires self.plan == self.slice.plan; requires count of { instrument: all(financing.late_charge), reference: "plan", anchor: self.plan, states: [proposed, assessed, collected], limit: 366 } < self.plan.historyLimit }
560
698
  action assess { allowZero: true,
561
699
  from: proposed, to: assessed, actor: clock, due: { at: self.overdueAt }
562
700
  requires self.slice.principalReceivable.balance > "0"
@@ -566,16 +704,23 @@ instrument late_charge(on: ref<financing.installments>[], grace: duration = 3d,
566
704
  from: [assessed, collected], to: assessed, actor: { parent: child(on, slice) }, input { payment: ref<on.payment> }
567
705
  requires input.payment in [paid]
568
706
  requires input.payment.plan == self.plan
569
- invoke: [{ instrument: receipt, action: create, input: { assessment: { field: self.id }, payment: { field: input.payment } } }, { selection: { instrument: current(), reference: "plan", anchor: self.plan, states: [assessed], limit: 1, where: { id: { field: self.id }, receivable.balance: { literal: "0" } } }, action: finish, input: {} }]
707
+ invoke: [{ instrument: receipt, action: create, input: { plan: { field: self.plan }, assessment: { field: self.id }, payment: { field: input.payment } } }, { selection: { instrument: current(), reference: "plan", anchor: self.plan, states: [assessed], limit: 1, where: { id: { field: self.id }, receivable.balance: { literal: "0" } } }, action: finish, input: {} }]
570
708
  }
571
709
  action payoff { allowZero: true,
572
710
  from: [assessed, collected], to: collected, actor: { parent: on }
573
711
  requires self.plan in [paid]
574
- moves self.receivable.balance from self.plan.borrower to fines_to economics { recipient: fines_to, company: { purpose: earning, sourceParty: borrower }, participant: { purpose: pass_through, sourceParty: borrower } }
575
- moves self.receivable.balance from self.receivable to self.debt economics { purpose: internal, sourceParty: programOperator }
712
+ calculate: [{ target: payoffAmount, op: sum, values: [{ field: self.receivable.balance }] }]
713
+ moves self.payoffAmount from self.plan.borrower to fines_to capture "payoffReceipt" economics { recipient: fines_to, company: { purpose: earning, sourceParty: borrower }, participant: { purpose: pass_through, sourceParty: borrower } }
714
+ moves self.payoffAmount from self.receivable to self.debt economics { purpose: internal, sourceParty: programOperator }
715
+ }
716
+ action unwind_payoff { allowZero: true,
717
+ from: collected, to: collected, actor: { parent: on }
718
+ moves self.payoffAmount from fines_to to self.plan.borrower economics { recipient: fines_to, company: { purpose: earning, sourceParty: fines_to, reversalOf: self.payoffReceipt }, participant: unclassified }
576
719
  }
577
720
  action finish { from: assessed, to: collected, actor: { parent: current() }, requires self.receivable.balance == "0" }
578
- action cancel { from: proposed, to: waived }
721
+ action cancel { from: proposed, to: waived, actor: { party: programOperator } }
722
+ action cancel_for_amendment { from: proposed, to: waived, actor: { parent: child(on, amendment) } }
723
+ action cancel_undelivered { allowZero: true, from: [proposed, assessed, collected], to: waived, actor: { parent: on }, moves self.receivable.balance from self.receivable to self.debt economics { purpose: internal, sourceParty: programOperator } }
579
724
  action waive_for_amendment {
580
725
  from: [assessed, collected], to: assessed, actor: { parent: child(on, amendment) }, input { amendment: ref<on.amendment> }
581
726
  requires input.amendment.plan == self.plan; requires input.amendment in [applied]
@@ -583,7 +728,7 @@ instrument late_charge(on: ref<financing.installments>[], grace: duration = 3d,
583
728
  }
584
729
  action waive { allowZero: true, from: [assessed, collected], to: waived, actor: { parent: waiver_request }, input { reason: text, evidence: text }, moves self.receivable.balance from self.receivable to self.debt economics { purpose: internal, sourceParty: programOperator } }
585
730
  action refund {
586
- from: [assessed, collected], to: assessed, actor: { party: fines_to }, input { payment: ref<on.payment> }
731
+ from: [assessed, collected], to: assessed, actor: { party: fines_to }, input { payment: ref<on.payment> }, deadline: { at: self.plan.refundUntil }
587
732
  requires input.payment in [paid, refunded]
588
733
  requires input.payment.plan == self.plan
589
734
  invoke: [{ selection: { instrument: receipt, reference: "payment", anchor: input.payment, states: [paid], limit: 1, where: { assessment: { field: self.id } } }, action: refund, input: {} }, { selection: { instrument: current(), reference: "plan", anchor: self.plan, states: [assessed], limit: 1, where: { id: { field: self.id }, receivable.balance: { literal: "0" } } }, action: finish, input: {} }]
@@ -610,20 +755,20 @@ instrument late_charge(on: ref<financing.installments>[], grace: duration = 3d,
610
755
  }
611
756
  }
612
757
  receipt: {
613
- fields { cashReceipt: text?, assessment: ref<parent>, payment: ref<on.payment>, amount: money? }
758
+ fields { cashReceipt: text?, plan: ref<on>, assessment: ref<parent>, payment: ref<on.payment>, amount: money? }
614
759
  lifecycle { states: [paid, refunded], initial: paid }
615
- action create { allowZero: true, calculate: [{ target: amount, op: minimum, values: [{ field: self.payment.held.balance }, { field: self.assessment.receivable.balance }] }], actor: { parent: parent }, input { assessment: ref<parent>, payment: ref<on.payment> }, requires input.payment.plan == self.assessment.plan; requires unique "fine_receipt" on [self.assessment, self.payment]; moves self.amount from self.payment.held to fines_to capture "cashReceipt" economics { recipient: fines_to, company: { purpose: earning, sourceParty: borrower }, participant: { purpose: pass_through, sourceParty: programOperator } }; moves self.amount from self.assessment.receivable to self.assessment.debt economics { purpose: internal, sourceParty: programOperator } }
616
- action refund { allowZero: true, from: paid, to: refunded, actor: { parent: parent }, moves self.amount from fines_to to self.payment.plan.borrower economics { recipient: fines_to, company: { purpose: earning, sourceParty: fines_to, reversalOf: self.cashReceipt }, participant: unclassified }; moves self.amount from self.assessment.debt to self.assessment.receivable economics { purpose: internal, sourceParty: borrower } }
760
+ action create { allowZero: true, calculate: [{ target: amount, op: minimum, values: [{ field: self.payment.held.balance }, { field: self.assessment.receivable.balance }] }], actor: { parent: parent }, input { plan: ref<on>, assessment: ref<parent>, payment: ref<on.payment> }, requires input.payment.plan == self.assessment.plan; requires unique "fine_receipt" on [self.assessment, self.payment]; moves self.amount from self.payment.held to fines_to capture "cashReceipt" economics { recipient: fines_to, company: { purpose: earning, sourceParty: borrower }, participant: { purpose: pass_through, sourceParty: programOperator } }; moves self.amount from self.assessment.receivable to self.assessment.debt economics { purpose: internal, sourceParty: programOperator } }
761
+ action refund { allowZero: true, from: paid, to: refunded, actor: { parent: [parent, on] }, moves self.amount from fines_to to self.payment.plan.borrower economics { recipient: fines_to, company: { purpose: earning, sourceParty: fines_to, reversalOf: self.cashReceipt }, participant: unclassified }; moves self.amount from self.assessment.debt to self.assessment.receivable economics { purpose: internal, sourceParty: borrower } }
617
762
  }
618
763
 
619
764
  cost: {
620
765
  revisioned: true
621
766
  fields {
622
- plan: ref<on>, assessment: ref<parent>, overdueAt: date?, evidence: text, evidencedCost: money, amount: money = minimum(self.evidencedCost, cap)
767
+ plan: ref<on>, assessment: ref<parent>, overdueAt: date?, evidence: text, evidencedCost: money, amount: money = minimum(self.evidencedCost, cap), payoffAmount: money?, payoffReceipt: text?
623
768
  receivable: account(self, claim), debt: account(borrower, claim, contra, "debt")
624
769
  }
625
770
  lifecycle { states: [proposed, assessed, collected, waived], initial: proposed }
626
- action create { calculate: [{ target: overdueAt, op: shift, date: { field: self.assessment.overdueAt }, milliseconds: { literal: 0 }, direction: after }], requires unique "recovery_assessment" on [self.assessment]; requires self.plan == self.assessment.plan }
771
+ action create { actor: { party: programOperator }, calculate: [{ target: overdueAt, op: shift, date: { field: self.assessment.overdueAt }, milliseconds: { literal: 0 }, direction: after }, { target: payoffAmount, op: sum, values: [{ literal: "0" }] }], requires unique "recovery_assessment" on [self.assessment]; requires self.plan == self.assessment.plan; requires count of { instrument: all(financing.late_charge.cost), reference: "plan", anchor: self.plan, states: [proposed, assessed, collected], limit: 366 } < self.plan.historyLimit }
627
772
  action assess { allowZero: true,
628
773
  from: proposed, to: assessed, actor: clock, due: { at: self.assessment.overdueAt }
629
774
  requires self.assessment.slice.principalReceivable.balance > "0"
@@ -633,16 +778,23 @@ instrument late_charge(on: ref<financing.installments>[], grace: duration = 3d,
633
778
  from: [assessed, collected], to: assessed, actor: { parent: child(on, slice) }, input { payment: ref<on.payment> }
634
779
  requires input.payment in [paid]
635
780
  requires input.payment.plan == self.plan
636
- invoke: [{ instrument: receipt, action: create, input: { assessment: { field: self.id }, payment: { field: input.payment } } }, { selection: { instrument: current(), reference: "plan", anchor: self.plan, states: [assessed], limit: 1, where: { id: { field: self.id }, receivable.balance: { literal: "0" } } }, action: finish, input: {} }]
781
+ invoke: [{ instrument: receipt, action: create, input: { plan: { field: self.plan }, assessment: { field: self.id }, payment: { field: input.payment } } }, { selection: { instrument: current(), reference: "plan", anchor: self.plan, states: [assessed], limit: 1, where: { id: { field: self.id }, receivable.balance: { literal: "0" } } }, action: finish, input: {} }]
637
782
  }
638
783
  action payoff { allowZero: true,
639
784
  from: [assessed, collected], to: collected, actor: { parent: on }
640
785
  requires self.plan in [paid]
641
- moves self.receivable.balance from self.plan.borrower to costs_to economics { recipient: costs_to, company: { purpose: earning, sourceParty: borrower }, participant: { purpose: pass_through, sourceParty: borrower } }
642
- moves self.receivable.balance from self.receivable to self.debt economics { purpose: internal, sourceParty: programOperator }
786
+ calculate: [{ target: payoffAmount, op: sum, values: [{ field: self.receivable.balance }] }]
787
+ moves self.payoffAmount from self.plan.borrower to costs_to capture "payoffReceipt" economics { recipient: costs_to, company: { purpose: earning, sourceParty: borrower }, participant: { purpose: pass_through, sourceParty: borrower } }
788
+ moves self.payoffAmount from self.receivable to self.debt economics { purpose: internal, sourceParty: programOperator }
789
+ }
790
+ action unwind_payoff { allowZero: true,
791
+ from: collected, to: collected, actor: { parent: on }
792
+ moves self.payoffAmount from costs_to to self.plan.borrower economics { recipient: costs_to, company: { purpose: earning, sourceParty: costs_to, reversalOf: self.payoffReceipt }, participant: unclassified }
643
793
  }
644
794
  action finish { from: assessed, to: collected, actor: { parent: current() }, requires self.receivable.balance == "0" }
645
- action cancel { from: proposed, to: waived }
795
+ action cancel { from: proposed, to: waived, actor: { party: programOperator } }
796
+ action cancel_for_amendment { from: proposed, to: waived, actor: { parent: child(on, amendment) } }
797
+ action cancel_undelivered { allowZero: true, from: [proposed, assessed, collected], to: waived, actor: { parent: on }, moves self.receivable.balance from self.receivable to self.debt economics { purpose: internal, sourceParty: programOperator } }
646
798
  action waive_for_amendment {
647
799
  from: [assessed, collected], to: assessed, actor: { parent: child(on, amendment) }, input { amendment: ref<on.amendment> }
648
800
  requires input.amendment.plan == self.plan; requires input.amendment in [applied]
@@ -650,7 +802,7 @@ instrument late_charge(on: ref<financing.installments>[], grace: duration = 3d,
650
802
  }
651
803
  action waive { allowZero: true, from: [assessed, collected], to: waived, actor: { parent: waiver_request }, input { reason: text, evidence: text }, moves self.receivable.balance from self.receivable to self.debt economics { purpose: internal, sourceParty: programOperator } }
652
804
  action refund {
653
- from: [assessed, collected], to: assessed, actor: { party: costs_to }, input { payment: ref<on.payment> }
805
+ from: [assessed, collected], to: assessed, actor: { party: costs_to }, input { payment: ref<on.payment> }, deadline: { at: self.plan.refundUntil }
654
806
  requires input.payment in [paid, refunded]
655
807
  requires input.payment.plan == self.plan
656
808
  invoke: [{ selection: { instrument: receipt, reference: "payment", anchor: input.payment, states: [paid], limit: 1, where: { assessment: { field: self.id } } }, action: refund, input: {} }, { selection: { instrument: current(), reference: "plan", anchor: self.plan, states: [assessed], limit: 1, where: { id: { field: self.id }, receivable.balance: { literal: "0" } } }, action: finish, input: {} }]
@@ -677,10 +829,10 @@ instrument late_charge(on: ref<financing.installments>[], grace: duration = 3d,
677
829
  }
678
830
  }
679
831
  receipt: {
680
- fields { cashReceipt: text?, assessment: ref<parent>, payment: ref<on.payment>, amount: money? }
832
+ fields { cashReceipt: text?, plan: ref<on>, assessment: ref<parent>, payment: ref<on.payment>, amount: money? }
681
833
  lifecycle { states: [paid, refunded], initial: paid }
682
- action create { allowZero: true, calculate: [{ target: amount, op: minimum, values: [{ field: self.payment.held.balance }, { field: self.assessment.receivable.balance }] }], actor: { parent: parent }, input { assessment: ref<parent>, payment: ref<on.payment> }, requires input.payment.plan == self.assessment.plan; requires unique "cost_receipt" on [self.assessment, self.payment]; moves self.amount from self.payment.held to costs_to capture "cashReceipt" economics { recipient: costs_to, company: { purpose: earning, sourceParty: borrower }, participant: { purpose: pass_through, sourceParty: programOperator } }; moves self.amount from self.assessment.receivable to self.assessment.debt economics { purpose: internal, sourceParty: programOperator } }
683
- action refund { allowZero: true, from: paid, to: refunded, actor: { parent: parent }, moves self.amount from costs_to to self.payment.plan.borrower economics { recipient: costs_to, company: { purpose: earning, sourceParty: costs_to, reversalOf: self.cashReceipt }, participant: unclassified }; moves self.amount from self.assessment.debt to self.assessment.receivable economics { purpose: internal, sourceParty: borrower } }
834
+ action create { allowZero: true, calculate: [{ target: amount, op: minimum, values: [{ field: self.payment.held.balance }, { field: self.assessment.receivable.balance }] }], actor: { parent: parent }, input { plan: ref<on>, assessment: ref<parent>, payment: ref<on.payment> }, requires input.payment.plan == self.assessment.plan; requires unique "cost_receipt" on [self.assessment, self.payment]; moves self.amount from self.payment.held to costs_to capture "cashReceipt" economics { recipient: costs_to, company: { purpose: earning, sourceParty: borrower }, participant: { purpose: pass_through, sourceParty: programOperator } }; moves self.amount from self.assessment.receivable to self.assessment.debt economics { purpose: internal, sourceParty: programOperator } }
835
+ action refund { allowZero: true, from: paid, to: refunded, actor: { parent: [parent, on] }, moves self.amount from costs_to to self.payment.plan.borrower economics { recipient: costs_to, company: { purpose: earning, sourceParty: costs_to, reversalOf: self.cashReceipt }, participant: unclassified }; moves self.amount from self.assessment.debt to self.assessment.receivable economics { purpose: internal, sourceParty: borrower } }
684
836
  }
685
837
  }
686
838
  }
@@ -693,8 +845,8 @@ instrument limits(per_borrower: money, borrower: party = party(person), active_p
693
845
  summary: "One outstanding balance ceiling per borrower."
694
846
  fields { borrower: account of borrower, limit: money = per_borrower, activePlans: integer = active_plans }
695
847
  lifecycle { states: [pending, approved], initial: pending }
696
- action create { requires unique "borrower_limit" on [self.borrower] }
697
- action approve { from: pending, to: approved }
848
+ action create { actor: { party: borrower }, requires unique "borrower_limit" on [self.borrower] }
849
+ action approve { from: pending, to: approved, actor: { party: programOperator } }
698
850
  }
699
851
 
700
852
  instrument portfolio_limit(limit: money) {
@@ -703,8 +855,8 @@ instrument portfolio_limit(limit: money) {
703
855
  summary: "One shared ceiling on the principal the product may hold."
704
856
  fields { key: text = "portfolio", limit: money = limit }
705
857
  lifecycle { states: [pending, approved], initial: pending }
706
- action create { requires unique "portfolio_limit" on [self.key] }
707
- action approve { from: pending, to: approved }
858
+ action create { actor: { party: programOperator }, requires unique "portfolio_limit" on [self.key] }
859
+ action approve { from: pending, to: approved, actor: { party: programOperator } }
708
860
  }
709
861
 
710
862
  instrument credit_line(borrower: party = programOperator, adapter: text, limit: money, expires: date) {
@@ -712,29 +864,29 @@ instrument credit_line(borrower: party = programOperator, adapter: text, limit:
712
864
  summary: "A revolving facility from an outside lender; each draw is a separate immutable advance."
713
865
  fields { borrower: account of borrower, facility: account(borrower, cash, "facility"), debt: account(borrower, claim, contra, "debt"), limit: money = limit, expiresAt: date = expires }
714
866
  lifecycle { states: [pending, active, suspended, closed], initial: pending }
715
- action create {}
867
+ action create { actor: { party: borrower } }
716
868
  action activate {
717
869
  from: pending, to: active, actor: { party: borrower }
718
870
  subject { adapter: adapter }
719
871
  }
720
872
  action suspend { from: active, to: suspended, actor: { party: borrower } }
721
873
  action resume { from: suspended, to: active, actor: { party: borrower } }
722
- action close { from: [active, suspended], to: closed, due: { at: self.expiresAt }, requires count of { instrument: all(financing.advance), reference: "line", anchor: self.id, states: [drawn], limit: 366 } == 0 }
874
+ action close { from: [active, suspended], to: closed, actor: { party: borrower }, due: { at: self.expiresAt }, requires count of { instrument: all(financing.advance), reference: "line", anchor: self.id, states: [drawn], limit: 366 } == 0 }
723
875
  }
724
876
 
725
- instrument advance(line: ref<financing.credit_line>, amount: money = runtime) {
877
+ instrument advance(line: ref<financing.credit_line>, amount: money = runtime, borrower: party = programOperator) {
726
878
  familyRevision: 1
727
879
  summary: "A single facility draw with an immutable principal and full repayment."
728
- fields { line: ref<line>, amount: money = amount, principalReceivable: account(self, claim) }
880
+ fields { line: ref<line>, borrower: account of borrower, amount: money = amount, principalReceivable: account(self, claim) }
729
881
  lifecycle { states: [pending, drawn, repaid, cancelled], initial: pending }
730
- action create { requires self.amount <= self.line.limit }
882
+ action create { actor: { party: borrower }, requires self.borrower == self.line.borrower; requires self.amount <= self.line.limit }
731
883
  invariants sum "principalReceivable.balance" of { instrument: all(financing.advance), reference: "line", anchor: self.line, states: [drawn], limit: 366 } <= self.line.limit
732
884
  action draw {
733
- from: pending, to: drawn
885
+ from: pending, to: drawn, actor: { party: borrower }
734
886
  requires self.line in [active]
735
887
  deadline: { at: self.line.expiresAt }
736
888
  moves self.amount from self.line.facility to self.line.borrower; moves self.amount from self.line.debt to self.principalReceivable
737
889
  }
738
- action repay { from: drawn, to: repaid, moves self.amount from self.line.borrower to self.line.facility; moves self.amount from self.principalReceivable to self.line.debt }
739
- action cancel { from: pending, to: cancelled }
890
+ action repay { from: drawn, to: repaid, actor: { party: borrower }, moves self.amount from self.line.borrower to self.line.facility; moves self.amount from self.principalReceivable to self.line.debt }
891
+ action cancel { from: pending, to: cancelled, actor: { party: borrower } }
740
892
  }