@hyperscale0/hsx 1.0.0 → 1.0.123
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/CHANGELOG.md +5 -256
- package/LICENSE.md +356 -0
- package/NOTICE.md +8 -0
- package/README.md +44 -44
- package/bin/hsx.ts +0 -7
- package/dist/bin/hsx.d.ts +0 -4
- package/dist/bin/hsx.d.ts.map +1 -1
- package/dist/bin/hsx.js +0 -6
- package/dist/bin/hsx.js.map +1 -1
- package/dist/src/ast.d.ts +113 -308
- package/dist/src/ast.d.ts.map +1 -1
- package/dist/src/ast.js +7 -71
- package/dist/src/ast.js.map +1 -1
- package/dist/src/attachment-economics.d.ts +5 -0
- package/dist/src/attachment-economics.d.ts.map +1 -0
- package/dist/src/attachment-economics.js +55 -0
- package/dist/src/attachment-economics.js.map +1 -0
- package/dist/src/binding-contract.d.ts +22 -0
- package/dist/src/binding-contract.d.ts.map +1 -0
- package/dist/src/binding-contract.js +74 -0
- package/dist/src/binding-contract.js.map +1 -0
- package/dist/src/cli.d.ts +4 -19
- package/dist/src/cli.d.ts.map +1 -1
- package/dist/src/cli.js +89 -276
- package/dist/src/cli.js.map +1 -1
- package/dist/src/compile.d.ts +34 -66
- package/dist/src/compile.d.ts.map +1 -1
- package/dist/src/compile.js +2949 -117
- package/dist/src/compile.js.map +1 -1
- package/dist/src/cost.d.ts +8 -75
- package/dist/src/cost.d.ts.map +1 -1
- package/dist/src/cost.js +54 -364
- package/dist/src/cost.js.map +1 -1
- package/dist/src/diagnostics.d.ts +14 -9
- package/dist/src/diagnostics.d.ts.map +1 -1
- package/dist/src/diagnostics.js +39 -416
- package/dist/src/diagnostics.js.map +1 -1
- package/dist/src/examples-bundle.d.ts +3 -0
- package/dist/src/examples-bundle.d.ts.map +1 -0
- package/dist/src/examples-bundle.js +18 -0
- package/dist/src/examples-bundle.js.map +1 -0
- package/dist/src/examples.d.ts +10 -0
- package/dist/src/examples.d.ts.map +1 -0
- package/dist/src/examples.js +2 -0
- package/dist/src/examples.js.map +1 -0
- package/dist/src/format.d.ts +8 -7
- package/dist/src/format.d.ts.map +1 -1
- package/dist/src/format.js +9 -175
- package/dist/src/format.js.map +1 -1
- package/dist/src/header-source.d.ts +3 -0
- package/dist/src/header-source.d.ts.map +1 -0
- package/dist/src/header-source.js +41 -0
- package/dist/src/header-source.js.map +1 -0
- package/dist/src/headers.d.ts +64 -0
- package/dist/src/headers.d.ts.map +1 -0
- package/dist/src/headers.js +167 -0
- package/dist/src/headers.js.map +1 -0
- package/dist/src/index.d.ts +7 -11
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +4 -9
- package/dist/src/index.js.map +1 -1
- package/dist/src/keywords.d.ts +56 -0
- package/dist/src/keywords.d.ts.map +1 -0
- package/dist/src/keywords.js +133 -0
- package/dist/src/keywords.js.map +1 -0
- package/dist/src/language.d.ts +26 -0
- package/dist/src/language.d.ts.map +1 -0
- package/dist/src/language.js +440 -0
- package/dist/src/language.js.map +1 -0
- package/dist/src/lex.d.ts +13 -28
- package/dist/src/lex.d.ts.map +1 -1
- package/dist/src/lex.js +119 -145
- package/dist/src/lex.js.map +1 -1
- package/dist/src/parse.d.ts +4 -17
- package/dist/src/parse.d.ts.map +1 -1
- package/dist/src/parse.js +484 -1036
- package/dist/src/parse.js.map +1 -1
- package/dist/src/std-bundle.d.ts.map +1 -1
- package/dist/src/std-bundle.js +11 -84
- package/dist/src/std-bundle.js.map +1 -1
- package/dist/src/std-library.d.ts +1 -1
- package/dist/src/std-library.d.ts.map +1 -1
- package/dist/src/std-library.js +1 -5
- package/dist/src/std-library.js.map +1 -1
- package/dist/src/tunables.d.ts +7 -0
- package/dist/src/tunables.d.ts.map +1 -0
- package/dist/src/tunables.js +27 -0
- package/dist/src/tunables.js.map +1 -0
- package/docs/README.md +528 -26
- package/docs/assets/brand/hsx-horizontal-white.svg +1 -0
- package/docs/assets/brand/hsx-horizontal.svg +1 -0
- package/docs/assets/brand/hsx-stacked-white.svg +1 -0
- package/docs/assets/brand/hsx-stacked.svg +1 -0
- package/docs/assets/brand/manifest.json +30 -0
- package/docs/assets/hsx.svg +11 -0
- package/examples/cards.hsx +34 -0
- package/examples/collections.hsx +37 -0
- package/examples/cost-table.json +633 -385
- package/examples/escrow.hsx +24 -0
- package/examples/financing.hsx +32 -0
- package/examples/insurance.hsx +30 -0
- package/examples/lending.hsx +57 -0
- package/examples/library.hsx +15 -0
- package/examples/marketplace.hsx +32 -0
- package/examples/money.hsx +19 -0
- package/examples/rental-deposit.hsx +60 -0
- package/examples/repair-approval.hsx +44 -0
- package/examples/reporting.hsx +28 -0
- package/examples/savings.hsx +21 -0
- package/examples/serviced.hsx +25 -0
- package/examples/wallet.hsx +24 -0
- package/package.json +25 -23
- package/src/ast.ts +83 -440
- package/src/attachment-economics.ts +103 -0
- package/src/binding-contract.ts +104 -0
- package/src/cli.ts +95 -325
- package/src/compile.ts +4063 -209
- package/src/cost.ts +71 -574
- package/src/diagnostics.ts +57 -437
- package/src/examples-bundle.ts +19 -0
- package/src/examples.ts +9 -0
- package/src/format.ts +12 -256
- package/src/header-source.ts +54 -0
- package/src/headers.ts +195 -0
- package/src/index.ts +18 -33
- package/src/keywords.ts +134 -0
- package/src/language.ts +629 -0
- package/src/lex.ts +131 -192
- package/src/parse.ts +489 -1195
- package/src/std-bundle.ts +12 -88
- package/src/std-library.ts +2 -7
- package/src/tunables.ts +36 -0
- package/std/cards.hsx +85 -0
- package/std/collections.hsx +90 -0
- package/std/escrow.hsx +73 -0
- package/std/financing.hsx +683 -0
- package/std/insurance.hsx +89 -0
- package/std/lending.hsx +138 -0
- package/std/marketplace.hsx +123 -0
- package/std/money.hsx +173 -0
- package/std/reporting.hsx +753 -0
- package/std/savings.hsx +68 -0
- package/std/wallet.hsx +57 -0
- package/LICENSE +0 -661
- package/LICENSING.md +0 -52
- package/TRADEMARKS.md +0 -35
- package/dist/src/emit.d.ts +0 -34
- package/dist/src/emit.d.ts.map +0 -1
- package/dist/src/emit.js +0 -137
- package/dist/src/emit.js.map +0 -1
- package/dist/src/entry-overrides.d.ts +0 -58
- package/dist/src/entry-overrides.d.ts.map +0 -1
- package/dist/src/entry-overrides.js +0 -284
- package/dist/src/entry-overrides.js.map +0 -1
- package/dist/src/ir.d.ts +0 -66
- package/dist/src/ir.d.ts.map +0 -1
- package/dist/src/ir.js +0 -15
- package/dist/src/ir.js.map +0 -1
- package/dist/src/limits.d.ts +0 -21
- package/dist/src/limits.d.ts.map +0 -1
- package/dist/src/limits.js +0 -21
- package/dist/src/limits.js.map +0 -1
- package/dist/src/lsp/server.d.ts +0 -32
- package/dist/src/lsp/server.d.ts.map +0 -1
- package/dist/src/lsp/server.js +0 -391
- package/dist/src/lsp/server.js.map +0 -1
- package/dist/src/modules.d.ts +0 -38
- package/dist/src/modules.d.ts.map +0 -1
- package/dist/src/modules.js +0 -368
- package/dist/src/modules.js.map +0 -1
- package/dist/src/typecheck.d.ts +0 -8
- package/dist/src/typecheck.d.ts.map +0 -1
- package/dist/src/typecheck.js +0 -2958
- package/dist/src/typecheck.js.map +0 -1
- package/dist/src/version.d.ts +0 -5
- package/dist/src/version.d.ts.map +0 -1
- package/dist/src/version.js +0 -5
- package/dist/src/version.js.map +0 -1
- package/docs/guide/01-first-program.md +0 -24
- package/docs/guide/02-money.md +0 -25
- package/docs/guide/03-instruments.md +0 -22
- package/docs/guide/04-lifecycles.md +0 -25
- package/docs/guide/05-fees-and-splits.md +0 -45
- package/docs/guide/06-schedules.md +0 -22
- package/docs/guide/07-composition.md +0 -46
- package/docs/guide/08-writing-a-module.md +0 -26
- package/docs/guide/09-cost.md +0 -24
- package/docs/guide/10-diagnostics.md +0 -27
- package/docs/llms-full.txt +0 -1739
- package/docs/llms.txt +0 -45
- package/docs/playground.md +0 -54
- package/docs/reference/cli.md +0 -35
- package/docs/reference/diagnostics.md +0 -619
- package/docs/reference/grammar.md +0 -39
- package/docs/reference/std/advance.md +0 -35
- package/docs/reference/std/cancellable_booking.md +0 -35
- package/docs/reference/std/captured_payment.md +0 -43
- package/docs/reference/std/conditional_disbursement.md +0 -33
- package/docs/reference/std/credit_facility.md +0 -35
- package/docs/reference/std/held_payment.md +0 -64
- package/docs/reference/std/instant_transfer.md +0 -30
- package/docs/reference/std/metered.md +0 -27
- package/docs/reference/std/pooled_split.md +0 -26
- package/docs/reference/std/premium_forward.md +0 -39
- package/docs/reference/std/reconciled_payout.md +0 -33
- package/docs/reference/std/recurring_collection.md +0 -25
- package/docs/reference/std/rotating_pool.md +0 -42
- package/docs/reference/std/scheduled.md +0 -51
- package/docs/reference/std/security_deposit.md +0 -46
- package/docs/reference/std/settlement_batch.md +0 -42
- package/docs/reference/std/swap.md +0 -46
- package/docs/reference/std/threshold_pool.md +0 -47
- package/docs/reference/std/weighted_distribution.md +0 -39
- package/docs/reference/types.md +0 -20
- package/docs/reference/udl-output.md +0 -13
- package/docs/sessions/2026-09-02-two-instruments.hsx +0 -21
- package/docs/sessions/2026-09-02-two-instruments.md +0 -41
- package/examples/01-first-program/README.md +0 -45
- package/examples/01-first-program/tip-jar.hsx +0 -17
- package/examples/02-imports-and-archetypes/README.md +0 -96
- package/examples/02-imports-and-archetypes/photo-booth.hsx +0 -37
- package/examples/03-diagnostics/README.md +0 -68
- package/examples/03-diagnostics/corner-shop-fixed.hsx +0 -22
- package/examples/03-diagnostics/corner-shop.hsx +0 -19
- package/examples/04-complete-product/README.md +0 -84
- package/examples/04-complete-product/study-hall.hsx +0 -67
- package/examples/05-watch-club/README.md +0 -9
- package/examples/05-watch-club/watch-club.hsx +0 -124
- package/examples/README.md +0 -18
- package/examples/advance/README.md +0 -3
- package/examples/advance/advance.hsx +0 -13
- package/examples/advance/advance.udl +0 -339
- package/examples/cancellable_booking/README.md +0 -3
- package/examples/cancellable_booking/cancellable_booking.hsx +0 -14
- package/examples/cancellable_booking/cancellable_booking.udl +0 -391
- package/examples/captured_payment/README.md +0 -3
- package/examples/captured_payment/captured_payment.hsx +0 -21
- package/examples/captured_payment/captured_payment.udl +0 -677
- package/examples/conditional_disbursement/README.md +0 -3
- package/examples/conditional_disbursement/conditional_disbursement.hsx +0 -17
- package/examples/conditional_disbursement/conditional_disbursement.udl +0 -412
- package/examples/credit_facility/README.md +0 -3
- package/examples/credit_facility/credit_facility.hsx +0 -27
- package/examples/credit_facility/credit_facility.udl +0 -1373
- package/examples/held_payment/README.md +0 -3
- package/examples/held_payment/held_payment.hsx +0 -13
- package/examples/held_payment/held_payment.udl +0 -507
- package/examples/instant_transfer/README.md +0 -3
- package/examples/instant_transfer/instant_transfer.hsx +0 -16
- package/examples/instant_transfer/instant_transfer.udl +0 -364
- package/examples/metered/README.md +0 -3
- package/examples/metered/metered.hsx +0 -13
- package/examples/metered/metered.udl +0 -247
- package/examples/pooled_split/README.md +0 -3
- package/examples/pooled_split/pooled_split.hsx +0 -15
- package/examples/pooled_split/pooled_split.udl +0 -425
- package/examples/premium_forward/README.md +0 -3
- package/examples/premium_forward/premium_forward.hsx +0 -19
- package/examples/premium_forward/premium_forward.udl +0 -594
- package/examples/reconciled_payout/README.md +0 -3
- package/examples/reconciled_payout/reconciled_payout.hsx +0 -13
- package/examples/reconciled_payout/reconciled_payout.udl +0 -303
- package/examples/recurring_collection/README.md +0 -3
- package/examples/recurring_collection/recurring_collection.hsx +0 -21
- package/examples/recurring_collection/recurring_collection.udl +0 -1130
- package/examples/rotating_pool/README.md +0 -3
- package/examples/rotating_pool/rotating_pool.hsx +0 -18
- package/examples/rotating_pool/rotating_pool.udl +0 -4352
- package/examples/scheduled/README.md +0 -3
- package/examples/scheduled/scheduled.hsx +0 -12
- package/examples/scheduled/scheduled.udl +0 -335
- package/examples/security_deposit/README.md +0 -3
- package/examples/security_deposit/security_deposit.hsx +0 -21
- package/examples/security_deposit/security_deposit.udl +0 -293
- package/examples/settlement_batch/README.md +0 -3
- package/examples/settlement_batch/settlement_batch.hsx +0 -19
- package/examples/settlement_batch/settlement_batch.udl +0 -753
- package/examples/swap/README.md +0 -3
- package/examples/swap/swap.hsx +0 -19
- package/examples/swap/swap.udl +0 -876
- package/examples/threshold_pool/README.md +0 -3
- package/examples/threshold_pool/threshold_pool.hsx +0 -16
- package/examples/threshold_pool/threshold_pool.udl +0 -667
- package/examples/weighted_distribution/README.md +0 -3
- package/examples/weighted_distribution/weighted_distribution.hsx +0 -20
- package/examples/weighted_distribution/weighted_distribution.udl +0 -337
- package/skills/hsx/SKILL.md +0 -483
- package/src/emit.ts +0 -201
- package/src/entry-overrides.ts +0 -428
- package/src/ir.ts +0 -99
- package/src/limits.ts +0 -20
- package/src/lsp/server.ts +0 -460
- package/src/modules.ts +0 -446
- package/src/typecheck.ts +0 -3876
- package/src/version.ts +0 -5
- package/std/SEMANTICS.md +0 -130
- package/std/settlements/advance.hsx +0 -177
- package/std/settlements/cancellable_booking.hsx +0 -87
- package/std/settlements/captured_payment.hsx +0 -144
- package/std/settlements/conditional_disbursement.hsx +0 -185
- package/std/settlements/credit_facility.hsx +0 -167
- package/std/settlements/held_payment.hsx +0 -578
- package/std/settlements/index.hsx +0 -3
- package/std/settlements/instant_transfer.hsx +0 -190
- package/std/settlements/metered.hsx +0 -67
- package/std/settlements/pooled_split.hsx +0 -68
- package/std/settlements/premium_forward.hsx +0 -257
- package/std/settlements/reconciled_payout.hsx +0 -77
- package/std/settlements/recurring_collection.hsx +0 -27
- package/std/settlements/rotating_pool.hsx +0 -252
- package/std/settlements/scheduled.hsx +0 -641
- package/std/settlements/security_deposit.hsx +0 -166
- package/std/settlements/settlement_batch.hsx +0 -122
- package/std/settlements/swap.hsx +0 -1114
- package/std/settlements/threshold_pool.hsx +0 -221
- package/std/settlements/weighted_distribution.hsx +0 -141
|
@@ -0,0 +1,683 @@
|
|
|
1
|
+
header financing
|
|
2
|
+
|
|
3
|
+
// Economics: Principal advances and repayments are excluded from earnings. Claims, waivers,
|
|
4
|
+
// write-offs and allocations are internal. Paid profit and company charges are
|
|
5
|
+
// earnings; receipt refunds reverse captured cash. Outside recovery recipients
|
|
6
|
+
// receive payouts from held company cash, or pass-through direct customer cash.
|
|
7
|
+
|
|
8
|
+
// Expose create and sign on installments, then collect_down_payment as checkout
|
|
9
|
+
// for disburse_to: funds. Checkout requires a signed plan and pending escrow;
|
|
10
|
+
// it tops up only the missing down payment, without advancing lender principal.
|
|
11
|
+
// Disburse still collects any missing down payment, activates slices, and confirms
|
|
12
|
+
// the fully funded hold. Existing escrow credit counts once toward the down payment.
|
|
13
|
+
// Disburse first credits an active marketplace.reservation on the same hold,
|
|
14
|
+
// so list the plan in its converters; the deposit then counts as escrow credit.
|
|
15
|
+
// Borrower-directed disbursement excludes collect_down_payment. A down_payment
|
|
16
|
+
// there reduces principal but does not collect cash. Price is subject metadata,
|
|
17
|
+
// not the borrower limit. The escrow payer and price must match the plan.
|
|
18
|
+
// Expose escrow cancel to return checkout cash if financing is abandoned; plan
|
|
19
|
+
// void alone cancels the agreement and does not refund its separate escrow.
|
|
20
|
+
// Approve borrower and portfolio limits before disbursement. Create builds the
|
|
21
|
+
// dated slice records; payments need a separate payment record and pay action.
|
|
22
|
+
// Expose parent payoff separately and create a current, unexpired payoff_quote.
|
|
23
|
+
// Expose late_charge.create as propose_assessment before the clock can assess;
|
|
24
|
+
// provide matching plan, slice and borrower. Fine is fixed money, not a percentage.
|
|
25
|
+
// Collection uses the minimum of payment cash and outstanding fine; a partial
|
|
26
|
+
// payment does not settle the remaining fine. Recovery cost needs its own record.
|
|
27
|
+
// Profit share credits capital's allocatedProfitPayable, not an outside agency.
|
|
28
|
+
// Credit lines need adapter binding at activation; draws are ledger advances,
|
|
29
|
+
// not evidence of outside funding. Caller-authorized limit approvals and advances
|
|
30
|
+
// need the Product's operation authority. No clock declaration proves retries.
|
|
31
|
+
|
|
32
|
+
// Cost: installments include cash and claim moves, child-account creation and
|
|
33
|
+
// bounded invocations across slices and assessments. Month and charge limits affect
|
|
34
|
+
// the bound. Run hsx cost on the selected policies; profit is not a platform tariff.
|
|
35
|
+
|
|
36
|
+
instrument installments(months: integer(1, 366), charge_limit: integer(1, 3) = 3, profit: percent, disburse_to: enum(funds, borrower) = funds, 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)) {
|
|
37
|
+
dependencies {
|
|
38
|
+
funds {
|
|
39
|
+
selector: disburse_to, is: funds
|
|
40
|
+
message: "Attachment `{attachment}` disburses into a hold, but `funds` is not bound."
|
|
41
|
+
fix: "Bind `funds: sale` to an escrow attachment. Choose `disburse_to: borrower` only if the borrower should receive the money directly."
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
revisioned: true
|
|
45
|
+
familyRevision: 1
|
|
46
|
+
summary: "Funding, profit recognition and payment order chosen by the program."
|
|
47
|
+
fields {
|
|
48
|
+
funds: ref<funds>?, borrower: account of borrower, settlementIdentity: text, borrowerLimit: ref<limits>, portfolioLimit: ref<portfolio>
|
|
49
|
+
months: integer = months, dates: list(date, months)
|
|
50
|
+
allocatedPayable: account(capital, cash, "allocatedProfitPayable"), capital: account(capital, cash, "capital"), profitIncome: account(capital, cash, "profitIncome"), loss: account(capital, claim, "loss")
|
|
51
|
+
downPaymentRemaining: money?, downPaymentCredit: money?, principalReturned: money?, profitReturned: money?, lossRecorded: money?
|
|
52
|
+
price: money = subject.price
|
|
53
|
+
downPayment: money = rate(self.price, down_payment)
|
|
54
|
+
principal: money = subtract(self.price, self.downPayment)
|
|
55
|
+
profit: money = rate(self.principal, profit)
|
|
56
|
+
total: money = sum(self.principal, self.profit)
|
|
57
|
+
}
|
|
58
|
+
lifecycle { states: [quoted, signed, active, paid, written_off, cancelled], initial: quoted }
|
|
59
|
+
invariants count of { instrument: all(financing.installments), reference: "borrowerLimit", anchor: self.borrowerLimit, states: [active], limit: 366 } <= self.borrowerLimit.activePlans
|
|
60
|
+
invariants sum "principalReceivable.balance" of { instrument: all(financing.installments.slice), reference: "borrowerLimit", anchor: self.borrowerLimit, states: [pending, due, paid], limit: 366 } <= self.borrowerLimit.limit
|
|
61
|
+
invariants sum "principalReceivable.balance" of { instrument: all(financing.installments.slice), reference: "portfolioLimit", anchor: self.portfolioLimit, states: [pending, due, paid], limit: 366 } <= self.portfolioLimit.limit
|
|
62
|
+
action create {
|
|
63
|
+
subject { price: money }
|
|
64
|
+
requires self.borrowerLimit.borrower == self.borrower
|
|
65
|
+
when disburse_to is funds { requires self.borrower == self.funds.payer; requires self.price == self.funds.price }
|
|
66
|
+
invoke: [{ range: { count: { literal: months }, maximum: months, bind: position }, instrument: slice, action: create, input: { plan: { field: self.id }, borrowerLimit: { field: self.borrowerLimit }, portfolioLimit: { field: self.portfolioLimit }, position: { field: position } } }]
|
|
67
|
+
}
|
|
68
|
+
action sign {
|
|
69
|
+
from: quoted, to: signed, actor: { party: borrower }, input { signedOffer: text, affordabilityReference: text }
|
|
70
|
+
requires count of { instrument: slice, reference: "plan", anchor: self.id, states: [draft], limit: 366 } == self.months
|
|
71
|
+
}
|
|
72
|
+
action collect_down_payment { allowZero: true,
|
|
73
|
+
from: signed, to: signed, actor: { party: borrower }
|
|
74
|
+
when disburse_to is borrower { requires true == false }
|
|
75
|
+
when disburse_to is funds {
|
|
76
|
+
requires self.funds in [pending]
|
|
77
|
+
calculate: [{ target: downPaymentCredit, op: minimum, values: [{ field: self.funds.held.balance }, { field: self.downPayment }] }, { target: downPaymentRemaining, op: subtract, base: { field: self.downPayment }, subtract: [{ field: self.downPaymentCredit }] }]
|
|
78
|
+
moves self.downPaymentRemaining from self.borrower to self.funds.held economics { purpose: principal, sourceParty: borrower }
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
action disburse { allowZero: true,
|
|
82
|
+
from: signed, to: active, actor: { party: capital }
|
|
83
|
+
requires self.borrowerLimit in [approved]
|
|
84
|
+
requires self.portfolioLimit in [approved]
|
|
85
|
+
when disburse_to is funds {
|
|
86
|
+
requires self.funds in [pending]
|
|
87
|
+
invoke: [{ selection: { instrument: all(marketplace.reservation), reference: "funds", anchor: self.funds, states: [active], limit: 1 }, action: credit, input: { funds: { field: self.funds }, contribution: { field: self.downPayment } } }, { reference: self.id, action: fund_down_payment, input: {} }]
|
|
88
|
+
}
|
|
89
|
+
when disburse_to is borrower { moves self.principal from self.capital to self.borrower economics { purpose: principal, sourceParty: capital } }
|
|
90
|
+
invoke: [{ selection: { instrument: slice, reference: "plan", anchor: self.id, states: [draft], limit: 366, order: [position] }, action: activate, input: {} }]
|
|
91
|
+
when disburse_to is funds { invoke: [{ reference: self.funds, action: confirm, input: {} }] }
|
|
92
|
+
}
|
|
93
|
+
action fund_down_payment { allowZero: true,
|
|
94
|
+
from: active, to: active, actor: { parent: current() }
|
|
95
|
+
when disburse_to is funds {
|
|
96
|
+
calculate: [{ target: downPaymentCredit, op: minimum, values: [{ field: self.funds.held.balance }, { field: self.downPayment }] }, { target: downPaymentRemaining, op: subtract, base: { field: self.downPayment }, subtract: [{ field: self.downPaymentCredit }] }]
|
|
97
|
+
moves self.downPaymentRemaining from self.funds.payer to self.funds.held economics { purpose: principal, sourceParty: borrower }
|
|
98
|
+
moves self.principal from self.capital to self.funds.held economics { purpose: principal, sourceParty: capital }
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
action complete {
|
|
102
|
+
from: active, to: paid
|
|
103
|
+
requires count of { instrument: slice, reference: "plan", anchor: self.id, states: [pending, due], limit: 366 } == 0
|
|
104
|
+
requires sum "receivable.balance" of { instrument: all(financing.late_charge), reference: "plan", anchor: self.id, states: [assessed, collected], limit: 366 } == "0"
|
|
105
|
+
requires sum "receivable.balance" of { instrument: all(financing.late_charge.cost), reference: "plan", anchor: self.id, states: [assessed, collected], limit: 366 } == "0"
|
|
106
|
+
}
|
|
107
|
+
action restore { from: [active, paid], to: active, actor: { parent: payment } }
|
|
108
|
+
action payoff {
|
|
109
|
+
from: active, to: paid, actor: { party: borrower }
|
|
110
|
+
input { quote: ref<payoff_quote> }
|
|
111
|
+
requires input.quote.plan == self.id; requires input.quote.revision == self.productRevision
|
|
112
|
+
deadline: { at: input.quote.expiresAt }
|
|
113
|
+
calculate: [
|
|
114
|
+
{ target: "principalReturned", op: "aggregate", selection: { instrument: slice, reference: "plan", anchor: self.id, states: [pending, due], limit: 366 }, measure: { sum: "principalReceivable.balance" } }
|
|
115
|
+
]
|
|
116
|
+
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: {} }]
|
|
117
|
+
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: {} }]
|
|
118
|
+
}
|
|
119
|
+
action record_payoff {
|
|
120
|
+
from: paid, to: paid, actor: { parent: current() }
|
|
121
|
+
calculate: [{ target: profitReturned, op: aggregate, selection: { instrument: slice, reference: "plan", anchor: self.id, states: [paid], limit: 366, where: { payoffIdentity: { field: self.settlementIdentity } } }, measure: { sum: "payoffProfit" } }]
|
|
122
|
+
invoke: [{ instrument: settlement, action: create, input: { plan: { field: self.id }, identity: { field: self.settlementIdentity }, principal: { field: self.principalReturned }, profit: { field: self.profitReturned }, loss: { literal: "0" }, kind: { literal: "cash" } } }]
|
|
123
|
+
}
|
|
124
|
+
action write_off {
|
|
125
|
+
from: active, to: written_off, actor: { parent: write_off_request }
|
|
126
|
+
input { overdueSlice: ref<slice> }
|
|
127
|
+
calculate: [{ target: "lossRecorded", op: "aggregate", selection: { instrument: slice, reference: "plan", anchor: self.id, states: [pending, due], limit: 366 }, measure: { sum: "principalReceivable.balance" } }]
|
|
128
|
+
due: { at: input.overdueSlice.lossEligibleAt }
|
|
129
|
+
requires input.overdueSlice.plan == self.id; requires input.overdueSlice.principalReceivable.balance > "0"
|
|
130
|
+
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" } } }]
|
|
131
|
+
}
|
|
132
|
+
action record_payment {
|
|
133
|
+
from: active, to: active, actor: { parent: payment }
|
|
134
|
+
input { identity: text, principal: money, profit: money }
|
|
135
|
+
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" } } }]
|
|
136
|
+
}
|
|
137
|
+
action cancel { from: quoted, to: cancelled }
|
|
138
|
+
action void { from: signed, to: cancelled, actor: { party: borrower } }
|
|
139
|
+
records {
|
|
140
|
+
write_off_request: {
|
|
141
|
+
fields { plan: ref<parent>, overdueSlice: ref<slice> }
|
|
142
|
+
lifecycle { states: [requested, applied, cancelled], initial: requested }
|
|
143
|
+
action create { requires self.overdueSlice.plan == self.plan; requires self.plan in [active] }
|
|
144
|
+
action apply {
|
|
145
|
+
from: requested, to: applied, actor: { party: capital }
|
|
146
|
+
invoke: [{ reference: self.plan, action: write_off, input: { overdueSlice: { field: self.overdueSlice } } }]
|
|
147
|
+
}
|
|
148
|
+
action cancel { from: requested, to: cancelled }
|
|
149
|
+
}
|
|
150
|
+
payoff_quote: {
|
|
151
|
+
fields {
|
|
152
|
+
plan: ref<parent>, revision: text?, effectiveAt: date?, expiresAt: date = after(self.createdAt, payoff_quote_expiry)
|
|
153
|
+
principal: money?, earnedProfit: money?, unearnedProfit: money?, rebate: money?, fines: money?, costs: money?, gross: money?, amount: money?
|
|
154
|
+
}
|
|
155
|
+
lifecycle { states: [quoted], initial: quoted }
|
|
156
|
+
action create {
|
|
157
|
+
requires self.plan in [active]
|
|
158
|
+
set: { revision: { field: self.productRevision }, effectiveAt: { field: self.now } }
|
|
159
|
+
invoke: [
|
|
160
|
+
{ selection: { instrument: slice, reference: "plan", anchor: self.plan, states: [pending], limit: 366 }, action: quote_pending, input: { quote: { field: self.id } } }
|
|
161
|
+
{ selection: { instrument: slice, reference: "plan", anchor: self.plan, states: [due], limit: 366 }, action: quote_due, input: { quote: { field: self.id } } }
|
|
162
|
+
{ reference: self.id, action: record, input: {} }
|
|
163
|
+
]
|
|
164
|
+
}
|
|
165
|
+
action record {
|
|
166
|
+
from: quoted, to: quoted, actor: { parent: current() }
|
|
167
|
+
calculate: [
|
|
168
|
+
{ target: principal, op: aggregate, selection: { instrument: slice, reference: "plan", anchor: self.plan, states: [pending, due, paid], limit: 366 }, measure: { sum: "principalReceivable.balance" } }
|
|
169
|
+
{ target: earnedProfit, op: aggregate, selection: { instrument: slice, reference: "plan", anchor: self.plan, states: [pending, due, paid], limit: 366 }, measure: { sum: "profitEarned.balance" } }
|
|
170
|
+
{ target: unearnedProfit, op: aggregate, selection: { instrument: slice, reference: "plan", anchor: self.plan, states: [pending, due, paid], limit: 366 }, measure: { sum: "profitReceivable.balance" } }
|
|
171
|
+
{ target: rebate, op: aggregate, selection: { instrument: quote_piece, reference: "quote", anchor: self.id, states: [recorded], limit: 366 }, measure: { sum: "rebate" } }
|
|
172
|
+
{ target: fines, op: aggregate, selection: { instrument: all(financing.late_charge), reference: "plan", anchor: self.plan, states: [assessed, collected], limit: 366 }, measure: { sum: "receivable.balance" } }
|
|
173
|
+
{ target: costs, op: aggregate, selection: { instrument: all(financing.late_charge.cost), reference: "plan", anchor: self.plan, states: [assessed, collected], limit: 366 }, measure: { sum: "receivable.balance" } }
|
|
174
|
+
{ target: gross, op: sum, values: [{ field: self.principal }, { field: self.earnedProfit }, { field: self.unearnedProfit }, { field: self.fines }, { field: self.costs }] }
|
|
175
|
+
{ target: amount, op: subtract, base: { field: self.gross }, subtract: [{ field: self.rebate }] }
|
|
176
|
+
]
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
quote_piece: {
|
|
180
|
+
fields { quote: ref<payoff_quote>, slice: ref<slice>, rebate: money = rate(self.slice.profitReceivable.balance, payoff_rebate) }
|
|
181
|
+
lifecycle { states: [recorded], initial: recorded }
|
|
182
|
+
action create { actor: { parent: slice }, input { quote: ref<payoff_quote>, slice: ref<slice> }, requires unique "payoff_slice_quote" on [self.quote, self.slice] }
|
|
183
|
+
}
|
|
184
|
+
amendment: {
|
|
185
|
+
fields {
|
|
186
|
+
plan: ref<parent>, dates: list(date, months), agreement: text, waiverPercent: percent, waiverEvidence: text, maximumWaiver: percent = waiver_limit
|
|
187
|
+
revision: text?, acceptedAt: date?, staleReason: text?, expiresAt: date = after(self.createdAt, amendment_expiry)
|
|
188
|
+
fineDisposition: enum(carry, require_waiver) = assessed_fines, addedProfit: money = 0 SAR
|
|
189
|
+
principalSnapshot: money?, earnedProfitSnapshot: money?, unearnedProfitSnapshot: money?, finesSnapshot: money?, costsSnapshot: money?
|
|
190
|
+
extension: duration = max_extension, maximum: integer = max_amendments
|
|
191
|
+
}
|
|
192
|
+
lifecycle { states: [proposed, applied, declined, withdrawn, expired, superseded], initial: proposed }
|
|
193
|
+
action create {
|
|
194
|
+
actor: { party: capital }
|
|
195
|
+
requires self.plan in [active]; requires self.waiverPercent <= self.maximumWaiver
|
|
196
|
+
set: { revision: { field: self.productRevision } }
|
|
197
|
+
calculate: [
|
|
198
|
+
{ target: principalSnapshot, op: aggregate, selection: { instrument: slice, reference: "plan", anchor: self.plan, states: [pending, due, paid], limit: 366 }, measure: { sum: "principalReceivable.balance" } }
|
|
199
|
+
{ target: earnedProfitSnapshot, op: aggregate, selection: { instrument: slice, reference: "plan", anchor: self.plan, states: [pending, due, paid], limit: 366 }, measure: { sum: "profitEarned.balance" } }
|
|
200
|
+
{ target: unearnedProfitSnapshot, op: aggregate, selection: { instrument: slice, reference: "plan", anchor: self.plan, states: [pending, due, paid], limit: 366 }, measure: { sum: "profitReceivable.balance" } }
|
|
201
|
+
{ target: finesSnapshot, op: aggregate, selection: { instrument: all(financing.late_charge), reference: "plan", anchor: self.plan, states: [assessed, collected], limit: 366 }, measure: { sum: "receivable.balance" } }
|
|
202
|
+
{ target: costsSnapshot, op: aggregate, selection: { instrument: all(financing.late_charge.cost), reference: "plan", anchor: self.plan, states: [assessed, collected], limit: 366 }, measure: { sum: "receivable.balance" } }
|
|
203
|
+
]
|
|
204
|
+
requires count of { instrument: current(), reference: "plan", anchor: self.plan, states: [applied], limit: 366 } < self.maximum
|
|
205
|
+
invoke: [{ range: { count: { literal: months }, maximum: months, bind: position }, instrument: proposed_date, action: create, input: { amendment: { field: self.id }, position: { field: position } } }, { selection: { instrument: proposed_date, reference: "amendment", anchor: self.id, states: [recorded], limit: 366, where: { first: { literal: 0 } } }, action: check_order, input: {} }]
|
|
206
|
+
}
|
|
207
|
+
action accept {
|
|
208
|
+
from: proposed, to: applied, actor: { party: borrower }, deadline: { at: self.expiresAt }
|
|
209
|
+
requires self.plan in [active]; requires self.revision == self.productRevision
|
|
210
|
+
requires count of { instrument: current(), reference: "plan", anchor: self.plan, states: [applied], limit: 366 } < self.maximum
|
|
211
|
+
set: { acceptedAt: { field: self.now } }
|
|
212
|
+
when assessed_fines is require_waiver { requires self.waiverPercent == 10000 }
|
|
213
|
+
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 } } }]
|
|
214
|
+
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: {} }]
|
|
215
|
+
invoke: [{ selection: { instrument: slice, reference: "plan", anchor: self.plan, states: [pending, due], limit: 366, order: [position] }, action: amend, input: { amendment: { field: self.id } } }]
|
|
216
|
+
invoke: [{ selection: { instrument: slice, reference: "plan", anchor: self.plan, states: [paid], limit: 366 }, action: verify_fixed_date, input: { amendment: { field: self.id } } }]
|
|
217
|
+
}
|
|
218
|
+
action withdraw { from: proposed, to: withdrawn, actor: { party: capital } }
|
|
219
|
+
action decline { from: proposed, to: declined, actor: { party: borrower } }
|
|
220
|
+
action expire { from: proposed, to: expired, actor: clock, due: { at: self.expiresAt } }
|
|
221
|
+
action supersede { from: proposed, to: superseded, requires self.revision != self.productRevision, set: { staleReason: { literal: "economic_revision_changed" } } }
|
|
222
|
+
}
|
|
223
|
+
proposed_date: {
|
|
224
|
+
fields { amendment: ref<amendment>, position: integer(1, months), date: date = at(self.amendment.dates, self.position), first: integer = divide(1, self.position), prior: integer = subtract(self.position, 1), previousPosition: integer = sum(self.prior, self.first), previous: date = at(self.amendment.dates, self.previousPosition) }
|
|
225
|
+
lifecycle { states: [recorded], initial: recorded }
|
|
226
|
+
action create { actor: { parent: amendment }, input { amendment: ref<amendment>, position: integer(1, months) }, requires self.date >= self.previous }
|
|
227
|
+
action check_order { from: recorded, to: recorded, actor: { parent: amendment }, requires self.date > self.previous }
|
|
228
|
+
}
|
|
229
|
+
date_change: {
|
|
230
|
+
fields { amendment: ref<amendment>, slice: ref<slice>, oldDate: date, newDate: date }
|
|
231
|
+
lifecycle { states: [recorded], initial: recorded }
|
|
232
|
+
action create { actor: { parent: slice }, input { amendment: ref<amendment>, slice: ref<slice>, oldDate: date, newDate: date }, requires unique "amended_slice" on [self.amendment, self.slice] }
|
|
233
|
+
}
|
|
234
|
+
settlement: {
|
|
235
|
+
fields { plan: ref<parent>, identity: text, principal: money, profit: money, loss: money, kind: enum(cash, loss), share: money?, retainedProfit: money?
|
|
236
|
+
allocatedPayable: account(capital, cash, "allocatedProfitPayable"), capital: account(capital, cash, "capital"), profitIncome: account(capital, cash, "profitIncome"), lossAccount: account(capital, claim, "loss") }
|
|
237
|
+
lifecycle { states: [recorded, refunded], initial: recorded }
|
|
238
|
+
action create { allowZero: true,
|
|
239
|
+
actor: { parent: parent }
|
|
240
|
+
input { plan: ref<parent>, identity: text, principal: money, profit: money, loss: money, kind: enum(cash, loss) }
|
|
241
|
+
requires unique "settlement_identity" on [self.plan, self.identity]
|
|
242
|
+
calculate: [{ target: "share", op: "rate", base: { field: self.profit }, bps: { literal: share }, rounding: "floor" }, { target: retainedProfit, op: "subtract", base: { field: self.profit }, subtract: [{ field: self.share }] }]
|
|
243
|
+
moves self.share from self.profitIncome to self.allocatedPayable economics { purpose: internal, sourceParty: capital }
|
|
244
|
+
}
|
|
245
|
+
action refund { allowZero: true,
|
|
246
|
+
from: recorded, to: refunded, actor: { parent: payment }
|
|
247
|
+
moves self.share from self.allocatedPayable to self.profitIncome economics { purpose: internal, sourceParty: capital }
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
slice: {
|
|
251
|
+
revisioned: true
|
|
252
|
+
fields {
|
|
253
|
+
plan: ref<parent>, borrowerLimit: ref<limits>, portfolioLimit: ref<portfolio>, position: integer(1, months)
|
|
254
|
+
originalDueAt: date = at(self.plan.dates, self.position), dueAt: date?, nextDueAt: date?, previousDueAt: date?, previousPosition: integer?, previousDate: date?, extensionLimit: date = after(self.originalDueAt, max_extension), lossEligibleAt: date?
|
|
255
|
+
principalBase: money = divide(self.plan.principal, months)
|
|
256
|
+
principalAllocated: money = multiply(self.principalBase, months)
|
|
257
|
+
principalRemainder: money = subtract(self.plan.principal, self.principalAllocated)
|
|
258
|
+
profitBase: money = divide(self.plan.profit, months)
|
|
259
|
+
profitAllocated: money = multiply(self.profitBase, months)
|
|
260
|
+
profitRemainder: money = subtract(self.plan.profit, self.profitAllocated)
|
|
261
|
+
first: integer = divide(1, self.position)
|
|
262
|
+
principalAdjustment: money = multiply(self.principalRemainder, self.first)
|
|
263
|
+
profitAdjustment: money = multiply(self.profitRemainder, self.first)
|
|
264
|
+
principal: money = sum(self.principalBase, self.principalAdjustment)
|
|
265
|
+
profit: money = sum(self.profitBase, self.profitAdjustment)
|
|
266
|
+
principalReceivable: account(self, claim), profitReceivable: account(self, claim), profitEarned: account(self, claim)
|
|
267
|
+
payoffIdentity: text?, payoffRebate: money?, payoffProfitTotal: money?, payoffProfit: money?
|
|
268
|
+
paymentBudget: money?, principalPayment: money?, principalBasePayment: money?, principalRatio: money?, profitPayment: money?, profitBudget: money?, collectibleProfit: money?, collectibleTotal: money?, ratioDenominator: integer = sum(10000, profit)
|
|
269
|
+
debt: account(borrower, claim, contra, "debt"), loss: account(capital, claim, "loss")
|
|
270
|
+
}
|
|
271
|
+
lifecycle { states: [draft, pending, due, paid, written_off], initial: draft }
|
|
272
|
+
action create {
|
|
273
|
+
actor: { parent: parent }
|
|
274
|
+
input { plan: ref<parent>, borrowerLimit: ref<limits>, portfolioLimit: ref<portfolio>, position: integer(1, months) }
|
|
275
|
+
set: { dueAt: { field: self.originalDueAt } }
|
|
276
|
+
invoke: [{ reference: self.id, action: refresh_loss_date, input: {} }]
|
|
277
|
+
requires self.plan in [quoted]; requires unique "signed_position" on [self.plan, self.position]; requires self.borrowerLimit == self.plan.borrowerLimit; requires self.portfolioLimit == self.plan.portfolioLimit
|
|
278
|
+
}
|
|
279
|
+
action quote_pending {
|
|
280
|
+
from: pending, to: pending, actor: { parent: payoff_quote }, input { quote: ref<payoff_quote> }
|
|
281
|
+
requires input.quote.plan == self.plan
|
|
282
|
+
invoke: [{ instrument: quote_piece, action: create, input: { quote: { field: input.quote }, slice: { field: self.id } } }]
|
|
283
|
+
}
|
|
284
|
+
action quote_due {
|
|
285
|
+
from: due, to: due, actor: { parent: payoff_quote }, input { quote: ref<payoff_quote> }
|
|
286
|
+
requires input.quote.plan == self.plan
|
|
287
|
+
invoke: [{ instrument: quote_piece, action: create, input: { quote: { field: input.quote }, slice: { field: self.id } } }]
|
|
288
|
+
}
|
|
289
|
+
action refresh_loss_date {
|
|
290
|
+
from: [draft, pending, due], to: draft, actor: { parent: current() }
|
|
291
|
+
calculate: [{ target: lossEligibleAt, op: shift, date: { field: self.dueAt }, milliseconds: { literal: write_off_after }, direction: after }]
|
|
292
|
+
}
|
|
293
|
+
action amend {
|
|
294
|
+
from: [pending, due], to: pending, actor: { parent: amendment }, input { amendment: ref<amendment> }
|
|
295
|
+
requires input.amendment.plan == self.plan
|
|
296
|
+
when allow_overdue is blocked { requires self.dueAt > self.now }
|
|
297
|
+
calculate: [
|
|
298
|
+
{ target: nextDueAt, op: at, list: input.amendment.dates, position: { field: self.position } }
|
|
299
|
+
{ target: lossEligibleAt, op: shift, date: { field: self.nextDueAt }, milliseconds: { literal: write_off_after }, direction: after }
|
|
300
|
+
]
|
|
301
|
+
requires self.nextDueAt > self.now; requires self.nextDueAt >= self.dueAt; requires self.nextDueAt <= self.extensionLimit
|
|
302
|
+
set: { previousDueAt: { field: self.dueAt }, dueAt: { field: self.nextDueAt } }
|
|
303
|
+
invoke: [{ instrument: date_change, action: create, input: { amendment: { field: input.amendment }, slice: { field: self.id }, oldDate: { field: self.previousDueAt }, newDate: { field: self.nextDueAt } } }]
|
|
304
|
+
when profit_earned is by_schedule { invoke: [{ selection: { instrument: recognition, reference: "slice", anchor: self.id, states: [pending], limit: 1 }, action: reschedule, input: {} }] }
|
|
305
|
+
}
|
|
306
|
+
action verify_fixed_date {
|
|
307
|
+
from: paid, to: paid, actor: { parent: amendment }, input { amendment: ref<amendment> }
|
|
308
|
+
calculate: [{ target: nextDueAt, op: at, list: input.amendment.dates, position: { field: self.position } }]
|
|
309
|
+
requires self.nextDueAt == self.dueAt
|
|
310
|
+
}
|
|
311
|
+
action activate { allowZero: true,
|
|
312
|
+
from: draft, to: pending, actor: { parent: parent }
|
|
313
|
+
moves self.principal from self.debt to self.principalReceivable economics { purpose: internal, sourceParty: borrower }
|
|
314
|
+
moves self.profit from self.debt to self.profitReceivable economics { purpose: internal, sourceParty: borrower }
|
|
315
|
+
when profit_earned is at_disbursement { moves self.profit from self.profitReceivable to self.profitEarned economics { purpose: internal, sourceParty: programOperator } }
|
|
316
|
+
when profit_earned is by_schedule { invoke: [{ instrument: recognition, action: create, input: { slice: { field: self.id } } }] }
|
|
317
|
+
}
|
|
318
|
+
action mature {
|
|
319
|
+
from: pending, to: due, actor: clock, due: { at: self.dueAt }
|
|
320
|
+
}
|
|
321
|
+
action collect {
|
|
322
|
+
from: [pending, due], to: preserve, actor: { parent: payment }
|
|
323
|
+
input { payment: ref<payment> }
|
|
324
|
+
requires input.payment.plan == self.plan
|
|
325
|
+
when profit_earned is on_payment { calculate: [{ target: collectibleProfit, op: sum, values: [{ field: self.profitReceivable.balance }] }] }
|
|
326
|
+
when profit_earned is by_schedule { calculate: [{ target: collectibleProfit, op: sum, values: [{ field: self.profitEarned.balance }] }] }
|
|
327
|
+
when profit_earned is at_disbursement { calculate: [{ target: collectibleProfit, op: sum, values: [{ field: self.profitEarned.balance }] }] }
|
|
328
|
+
when apply is principal_profit {
|
|
329
|
+
invoke: [
|
|
330
|
+
{ instrument: principal_piece, action: create, input: { slice: { field: self.id }, payment: { field: input.payment }, limit: { field: self.principalReceivable.balance } } }
|
|
331
|
+
{ instrument: profit_piece, action: create, input: { slice: { field: self.id }, payment: { field: input.payment }, limit: { field: self.collectibleProfit } } }
|
|
332
|
+
{ 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: {} }
|
|
333
|
+
]
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
action collect_waterfall {
|
|
337
|
+
from: [pending, due], to: preserve, actor: { parent: payment }, input { payment: ref<payment> }
|
|
338
|
+
requires input.payment.plan == self.plan
|
|
339
|
+
when profit_earned is on_payment { calculate: [{ target: collectibleProfit, op: sum, values: [{ field: self.profitReceivable.balance }] }] }
|
|
340
|
+
when profit_earned is by_schedule { calculate: [{ target: collectibleProfit, op: sum, values: [{ field: self.profitEarned.balance }] }] }
|
|
341
|
+
when profit_earned is at_disbursement { calculate: [{ target: collectibleProfit, op: sum, values: [{ field: self.profitEarned.balance }] }] }
|
|
342
|
+
invoke: [
|
|
343
|
+
{ 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 } } }
|
|
344
|
+
{ 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 } } }
|
|
345
|
+
{ instrument: profit_piece, action: create, input: { slice: { field: self.id }, payment: { field: input.payment }, limit: { field: self.collectibleProfit } } }
|
|
346
|
+
{ instrument: principal_piece, action: create, input: { slice: { field: self.id }, payment: { field: input.payment }, limit: { field: self.principalReceivable.balance } } }
|
|
347
|
+
{ 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: {} }
|
|
348
|
+
]
|
|
349
|
+
}
|
|
350
|
+
action collect_pro_rata {
|
|
351
|
+
from: [pending, due], to: preserve, actor: { parent: payment }, input { payment: ref<payment> }
|
|
352
|
+
requires input.payment.plan == self.plan
|
|
353
|
+
when profit_earned is on_payment { calculate: [{ target: collectibleProfit, op: sum, values: [{ field: self.profitReceivable.balance }] }] }
|
|
354
|
+
when profit_earned is by_schedule { calculate: [{ target: collectibleProfit, op: sum, values: [{ field: self.profitEarned.balance }] }] }
|
|
355
|
+
when profit_earned is at_disbursement { calculate: [{ target: collectibleProfit, op: sum, values: [{ field: self.profitEarned.balance }] }] }
|
|
356
|
+
calculate: [
|
|
357
|
+
{ target: collectibleTotal, op: sum, values: [{ field: self.principalReceivable.balance }, { field: self.collectibleProfit }] }
|
|
358
|
+
{ target: paymentBudget, op: minimum, values: [{ field: input.payment.held.balance }, { field: self.collectibleTotal }] }
|
|
359
|
+
{ target: principalRatio, op: ratio, amount: { field: self.paymentBudget }, numerator: { literal: 10000 }, denominator: { field: self.ratioDenominator }, rounding: floor }
|
|
360
|
+
{ target: principalBasePayment, op: minimum, values: [{ field: self.principalRatio }, { field: self.principalReceivable.balance }] }
|
|
361
|
+
{ target: profitBudget, op: subtract, base: { field: self.paymentBudget }, subtract: [{ field: self.principalBasePayment }] }
|
|
362
|
+
{ target: profitPayment, op: minimum, values: [{ field: self.profitBudget }, { field: self.collectibleProfit }] }
|
|
363
|
+
{ target: principalPayment, op: subtract, base: { field: self.paymentBudget }, subtract: [{ field: self.profitPayment }] }
|
|
364
|
+
]
|
|
365
|
+
invoke: [
|
|
366
|
+
{ instrument: principal_piece, action: create, input: { slice: { field: self.id }, payment: { field: input.payment }, limit: { field: self.principalPayment } } }
|
|
367
|
+
{ instrument: profit_piece, action: create, input: { slice: { field: self.id }, payment: { field: input.payment }, limit: { field: self.profitPayment } } }
|
|
368
|
+
{ 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: {} }
|
|
369
|
+
]
|
|
370
|
+
}
|
|
371
|
+
action complete {
|
|
372
|
+
from: [pending, due], to: paid
|
|
373
|
+
requires self.principalReceivable.balance == "0"; requires self.profitReceivable.balance == "0"; requires self.profitEarned.balance == "0"
|
|
374
|
+
}
|
|
375
|
+
action restore { from: [pending, due, paid], to: pending, actor: { parent: payment } }
|
|
376
|
+
action payoff { allowZero: true,
|
|
377
|
+
from: [pending, due], to: paid, actor: { parent: parent }, input { identity: text }
|
|
378
|
+
set: { payoffIdentity: { field: input.identity } }
|
|
379
|
+
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 }] }]
|
|
380
|
+
moves self.principalReceivable.balance from self.plan.borrower to self.plan.capital economics { purpose: principal, sourceParty: borrower }
|
|
381
|
+
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 } }
|
|
382
|
+
moves self.principalReceivable.balance from self.principalReceivable to self.debt economics { purpose: internal, sourceParty: programOperator }
|
|
383
|
+
moves self.profitReceivable.balance from self.profitReceivable to self.debt economics { purpose: internal, sourceParty: programOperator }
|
|
384
|
+
moves self.profitEarned.balance from self.profitEarned to self.debt economics { purpose: internal, sourceParty: programOperator }
|
|
385
|
+
}
|
|
386
|
+
action write_off { allowZero: true,
|
|
387
|
+
from: [pending, due], to: written_off, actor: { parent: parent }
|
|
388
|
+
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 }
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
recognition: {
|
|
392
|
+
fields { slice: ref<slice>, dueAt: date = after(self.slice.dueAt, 0) }
|
|
393
|
+
lifecycle { states: [pending, recognised], initial: pending }
|
|
394
|
+
action create { actor: { parent: slice }, input { slice: ref<slice> }, requires unique "profit_recognition" on [self.slice] }
|
|
395
|
+
action reschedule { from: pending, to: pending, actor: { parent: slice }, calculate: [{ target: dueAt, op: shift, date: { field: self.slice.dueAt }, milliseconds: { literal: 0 }, direction: after }] }
|
|
396
|
+
action mature { allowZero: true, from: pending, to: recognised, actor: clock, due: { at: self.dueAt }, moves self.slice.profitReceivable.balance from self.slice.profitReceivable to self.slice.profitEarned economics { purpose: internal, sourceParty: programOperator } }
|
|
397
|
+
}
|
|
398
|
+
payment: {
|
|
399
|
+
revisioned: true
|
|
400
|
+
fields { plan: ref<parent>, amount: money, paymentIdentity: text, held: account of self
|
|
401
|
+
outstandingPrincipal: money?, outstandingProfit: money?, outstandingFines: money?, outstandingCosts: money?, outstandingTotal: money?, principalReturned: money?, profitReturned: money?, lossRecorded: money? }
|
|
402
|
+
|
|
403
|
+
lifecycle { states: [pending, paid, refunded], initial: pending }
|
|
404
|
+
action create { input { plan: ref<parent>, amount: money, paymentIdentity: text } requires self.plan in [active]; requires unique "payment_identity" on [self.plan, self.paymentIdentity] }
|
|
405
|
+
action pay {
|
|
406
|
+
expansionLimit: 8192
|
|
407
|
+
from: pending, to: paid, actor: { party: borrower }
|
|
408
|
+
calculate: [{ target: outstandingPrincipal, op: aggregate, selection: { instrument: slice, reference: "plan", anchor: self.plan, states: [pending, due, paid], limit: 366, order: [position] }, measure: { sum: "principalReceivable.balance" } }]
|
|
409
|
+
when profit_earned is on_payment { calculate: [{ target: outstandingProfit, op: aggregate, selection: { instrument: slice, reference: "plan", anchor: self.plan, states: [pending, due, paid], limit: 366, order: [position] }, measure: { sum: "profitReceivable.balance" } }] }
|
|
410
|
+
when profit_earned is by_schedule { calculate: [{ target: outstandingProfit, op: aggregate, selection: { instrument: slice, reference: "plan", anchor: self.plan, states: [pending, due, paid], limit: 366, order: [position] }, measure: { sum: "profitEarned.balance" } }] }
|
|
411
|
+
when profit_earned is at_disbursement { calculate: [{ target: outstandingProfit, op: aggregate, selection: { instrument: slice, reference: "plan", anchor: self.plan, states: [pending, due, paid], limit: 366, order: [position] }, measure: { sum: "profitEarned.balance" } }] }
|
|
412
|
+
when apply is fines_profit_principal {
|
|
413
|
+
calculate: [{ target: outstandingFines, op: aggregate, selection: { instrument: all(financing.late_charge), reference: "plan", anchor: self.plan, states: [assessed, collected], limit: 366, order: [overdueAt] }, measure: { sum: "receivable.balance" } }, { target: outstandingCosts, op: aggregate, selection: { instrument: all(financing.late_charge.cost), reference: "plan", anchor: self.plan, states: [assessed, collected], limit: 366, order: [overdueAt] }, measure: { sum: "receivable.balance" } }, { target: outstandingTotal, op: sum, values: [{ field: self.outstandingPrincipal }, { field: self.outstandingProfit }, { field: self.outstandingFines }, { field: self.outstandingCosts }] }]
|
|
414
|
+
}
|
|
415
|
+
when apply is principal_profit { calculate: [{ target: outstandingTotal, op: sum, values: [{ field: self.outstandingPrincipal }, { field: self.outstandingProfit }] }] }
|
|
416
|
+
when apply is pro_rata { calculate: [{ target: outstandingTotal, op: sum, values: [{ field: self.outstandingPrincipal }, { field: self.outstandingProfit }] }] }
|
|
417
|
+
requires self.plan in [active]; requires self.amount <= self.outstandingTotal
|
|
418
|
+
moves self.amount from self.plan.borrower to self.held economics { purpose: principal, sourceParty: borrower }
|
|
419
|
+
when apply is principal_profit {
|
|
420
|
+
invoke: [{ selection: { instrument: slice, reference: "plan", anchor: self.plan, states: [pending, due], limit: 366, order: [position] }, action: collect, guard: { kind: compare, left: { field: self.held.balance }, operator: ">", right: { literal: "0" } }, input: { payment: { field: self.id } } }]
|
|
421
|
+
}
|
|
422
|
+
when apply is fines_profit_principal {
|
|
423
|
+
invoke: [{ selection: { instrument: slice, reference: "plan", anchor: self.plan, states: [pending, due], limit: 366, order: [position] }, action: collect_waterfall, guard: { kind: compare, left: { field: self.held.balance }, operator: ">", right: { literal: "0" } }, input: { payment: { field: self.id } } }]
|
|
424
|
+
}
|
|
425
|
+
when apply is pro_rata {
|
|
426
|
+
invoke: [{ selection: { instrument: slice, reference: "plan", anchor: self.plan, states: [pending, due], limit: 366, order: [position] }, action: collect_pro_rata, guard: { kind: compare, left: { field: self.held.balance }, operator: ">", right: { literal: "0" } }, input: { payment: { field: self.id } } }]
|
|
427
|
+
}
|
|
428
|
+
invoke: [{ reference: self.id, action: record, input: {} }]
|
|
429
|
+
}
|
|
430
|
+
action record {
|
|
431
|
+
from: paid, to: paid, actor: { parent: current() }
|
|
432
|
+
requires self.held.balance == "0"
|
|
433
|
+
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" } }]
|
|
434
|
+
invoke: [{ reference: self.plan, action: record_payment, input: { identity: { field: self.paymentIdentity }, principal: { field: self.principalReturned }, profit: { field: self.profitReturned } } }]
|
|
435
|
+
}
|
|
436
|
+
action refund {
|
|
437
|
+
from: paid, to: refunded, actor: { party: capital }
|
|
438
|
+
requires unique "settlement_consumption" on [self.plan, self.paymentIdentity]; requires self.plan in [active, paid]
|
|
439
|
+
requires count of { instrument: settlement, reference: "plan", anchor: self.plan, states: [recorded], limit: 366, where: { identity: { field: self.paymentIdentity } } } == 1
|
|
440
|
+
invoke: [{ selection: { instrument: settlement, reference: "plan", anchor: self.plan, states: [recorded], limit: 366, where: { identity: { field: self.paymentIdentity } } }, action: refund, input: {} }]
|
|
441
|
+
when apply is fines_profit_principal {
|
|
442
|
+
invoke: [{ selection: { instrument: all(financing.late_charge), reference: "plan", anchor: self.plan, states: [assessed, collected], limit: 366, order: [overdueAt] }, action: refund, input: { payment: { field: self.id } } }, { selection: { instrument: all(financing.late_charge.cost), reference: "plan", anchor: self.plan, states: [assessed, collected], limit: 366, order: [overdueAt] }, action: refund, input: { payment: { field: self.id } } }]
|
|
443
|
+
}
|
|
444
|
+
invoke: [{ reference: self.plan, action: restore, input: {} }, { selection: { instrument: slice, reference: "plan", anchor: self.plan, states: [pending, due, paid], limit: 366 }, action: restore, input: {} }, { selection: { instrument: principal_piece, reference: "payment", anchor: self.id, states: [paid], limit: 366 }, action: refund, input: {} }, { selection: { instrument: profit_piece, reference: "payment", anchor: self.id, states: [paid], limit: 366 }, action: refund, input: {} }]
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
principal_piece: {
|
|
448
|
+
fields { slice: ref<slice>, payment: ref<payment>, limit: money, amount: money? }
|
|
449
|
+
lifecycle { states: [paid, refunded], initial: paid }
|
|
450
|
+
action create { allowZero: true,
|
|
451
|
+
calculate: [{ target: amount, op: minimum, values: [{ field: self.payment.held.balance }, { field: self.slice.principalReceivable.balance }, { field: self.limit }] }]
|
|
452
|
+
actor: { parent: slice }
|
|
453
|
+
input { slice: ref<slice>, payment: ref<payment>, limit: money }
|
|
454
|
+
requires unique "principal_receipt" on [self.slice, self.payment]
|
|
455
|
+
moves self.amount from self.payment.held to self.slice.plan.capital economics { purpose: principal, sourceParty: programOperator }; moves self.amount from self.slice.principalReceivable to self.slice.debt economics { purpose: internal, sourceParty: programOperator }
|
|
456
|
+
}
|
|
457
|
+
action refund { allowZero: true,
|
|
458
|
+
from: paid, to: refunded, actor: { parent: payment }
|
|
459
|
+
moves self.amount from self.slice.plan.capital to self.slice.plan.borrower economics { purpose: principal, sourceParty: capital }; moves self.amount from self.slice.debt to self.slice.principalReceivable economics { purpose: internal, sourceParty: borrower }
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
profit_piece: {
|
|
463
|
+
fields { cashReceipt: text?, slice: ref<slice>, payment: ref<payment>, limit: money, amount: money? }
|
|
464
|
+
lifecycle { states: [paid, refunded], initial: paid }
|
|
465
|
+
action create { allowZero: true,
|
|
466
|
+
calculate: [{ target: amount, op: minimum, values: [{ field: self.payment.held.balance }, { field: self.limit }] }]
|
|
467
|
+
actor: { parent: slice }
|
|
468
|
+
input { slice: ref<slice>, payment: ref<payment>, limit: money }
|
|
469
|
+
requires unique "profit_receipt" on [self.slice, self.payment]
|
|
470
|
+
moves self.amount from self.payment.held to self.slice.plan.profitIncome capture "cashReceipt" economics { recipient: capital, company: { purpose: earning, sourceParty: borrower }, participant: { purpose: participant_payout, sourceParty: programOperator } }
|
|
471
|
+
when profit_earned is on_payment { moves self.amount from self.slice.profitReceivable to self.slice.debt economics { purpose: internal, sourceParty: programOperator } }
|
|
472
|
+
when profit_earned is by_schedule { moves self.amount from self.slice.profitEarned to self.slice.debt economics { purpose: internal, sourceParty: programOperator } }
|
|
473
|
+
when profit_earned is at_disbursement { moves self.amount from self.slice.profitEarned to self.slice.debt economics { purpose: internal, sourceParty: programOperator } }
|
|
474
|
+
}
|
|
475
|
+
action refund { allowZero: true,
|
|
476
|
+
from: paid, to: refunded, actor: { parent: payment }
|
|
477
|
+
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 }
|
|
478
|
+
when profit_earned is on_payment { moves self.amount from self.slice.debt to self.slice.profitReceivable economics { purpose: internal, sourceParty: borrower } }
|
|
479
|
+
when profit_earned is by_schedule { moves self.amount from self.slice.debt to self.slice.profitEarned economics { purpose: internal, sourceParty: borrower } }
|
|
480
|
+
when profit_earned is at_disbursement { moves self.amount from self.slice.debt to self.slice.profitEarned economics { purpose: internal, sourceParty: borrower } }
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
instrument late_charge(on: ref<financing.installments>[], grace: duration = 3d, fine: money = 50 SAR, cap: money = 25 SAR, fines_to: party = programOperator, costs_to: party = programOperator, borrower: party = party(person)) {
|
|
487
|
+
parameterDiagnostics {
|
|
488
|
+
fine {
|
|
489
|
+
accepts: "a fixed amount"
|
|
490
|
+
percentage: "a percentage of overdue debt"
|
|
491
|
+
fix: "Use a fixed amount only if that is the intended policy. A percentage charge needs an authored rate calculation; do not approximate it with a cash amount."
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
revisioned: true
|
|
495
|
+
familyRevision: 1
|
|
496
|
+
summary: "Separate fine and evidenced recovery claims on an overdue installment."
|
|
497
|
+
fields {
|
|
498
|
+
plan: ref<on>, slice: ref<on.slice>, borrower: account of borrower, fine: money = fine, overdueAt: date?
|
|
499
|
+
receivable: account(self, claim), debt: account(borrower, claim, contra, "debt")
|
|
500
|
+
}
|
|
501
|
+
lifecycle { states: [proposed, assessed, collected, waived], initial: proposed }
|
|
502
|
+
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 }
|
|
503
|
+
action assess { allowZero: true,
|
|
504
|
+
from: proposed, to: assessed, actor: clock, due: { at: self.overdueAt }
|
|
505
|
+
requires self.slice.principalReceivable.balance > "0"
|
|
506
|
+
moves self.fine from self.debt to self.receivable economics { purpose: internal, sourceParty: borrower }
|
|
507
|
+
}
|
|
508
|
+
action collect {
|
|
509
|
+
from: [assessed, collected], to: assessed, actor: { parent: child(on, slice) }, input { payment: ref<on.payment> }
|
|
510
|
+
requires input.payment in [paid]
|
|
511
|
+
requires input.payment.plan == self.plan
|
|
512
|
+
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: {} }]
|
|
513
|
+
}
|
|
514
|
+
action payoff { allowZero: true,
|
|
515
|
+
from: [assessed, collected], to: collected, actor: { parent: on }
|
|
516
|
+
requires self.plan in [paid]
|
|
517
|
+
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 } }
|
|
518
|
+
moves self.receivable.balance from self.receivable to self.debt economics { purpose: internal, sourceParty: programOperator }
|
|
519
|
+
}
|
|
520
|
+
action finish { from: assessed, to: collected, actor: { parent: current() }, requires self.receivable.balance == "0" }
|
|
521
|
+
action cancel { from: proposed, to: waived }
|
|
522
|
+
action waive_for_amendment {
|
|
523
|
+
from: [assessed, collected], to: assessed, actor: { parent: child(on, amendment) }, input { amendment: ref<on.amendment> }
|
|
524
|
+
requires input.amendment.plan == self.plan; requires input.amendment in [applied]
|
|
525
|
+
invoke: [{ instrument: waiver, action: create, input: { assessment: { field: self.id }, amendment: { field: input.amendment } } }]
|
|
526
|
+
}
|
|
527
|
+
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 } }
|
|
528
|
+
action refund {
|
|
529
|
+
from: [assessed, collected], to: assessed, actor: { party: fines_to }, input { payment: ref<on.payment> }
|
|
530
|
+
requires input.payment in [paid, refunded]
|
|
531
|
+
requires input.payment.plan == self.plan
|
|
532
|
+
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: {} }]
|
|
533
|
+
}
|
|
534
|
+
records {
|
|
535
|
+
waiver_request: {
|
|
536
|
+
fields { assessment: ref<parent>, reason: text, evidence: text }
|
|
537
|
+
lifecycle { states: [requested, applied, cancelled], initial: requested }
|
|
538
|
+
action create { requires self.assessment in [assessed, collected] }
|
|
539
|
+
action apply {
|
|
540
|
+
from: requested, to: applied, actor: { party: fines_to }, input { reason: text, evidence: text }
|
|
541
|
+
invoke: [{ reference: self.assessment, action: waive, input: { reason: { field: input.reason }, evidence: { field: input.evidence } } }]
|
|
542
|
+
}
|
|
543
|
+
action cancel { from: requested, to: cancelled }
|
|
544
|
+
}
|
|
545
|
+
waiver: {
|
|
546
|
+
fields { assessment: ref<parent>, amendment: ref<on.amendment>, amount: money? }
|
|
547
|
+
lifecycle { states: [recorded], initial: recorded }
|
|
548
|
+
action create { allowZero: true,
|
|
549
|
+
actor: { parent: parent }, input { assessment: ref<parent>, amendment: ref<on.amendment> }
|
|
550
|
+
calculate: [{ target: amount, op: rate, base: { field: self.assessment.receivable.balance }, bps: { field: self.amendment.waiverPercent }, rounding: floor }]
|
|
551
|
+
requires unique "amendment_waiver" on [self.assessment, self.amendment]
|
|
552
|
+
moves self.amount from self.assessment.receivable to self.assessment.debt economics { purpose: internal, sourceParty: programOperator }
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
receipt: {
|
|
556
|
+
fields { cashReceipt: text?, assessment: ref<parent>, payment: ref<on.payment>, amount: money? }
|
|
557
|
+
lifecycle { states: [paid, refunded], initial: paid }
|
|
558
|
+
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: participant_payout, sourceParty: programOperator } }; moves self.amount from self.assessment.receivable to self.assessment.debt economics { purpose: internal, sourceParty: programOperator } }
|
|
559
|
+
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 } }
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
cost: {
|
|
563
|
+
revisioned: true
|
|
564
|
+
fields {
|
|
565
|
+
plan: ref<on>, assessment: ref<parent>, overdueAt: date?, evidence: text, evidencedCost: money, amount: money = minimum(self.evidencedCost, cap)
|
|
566
|
+
receivable: account(self, claim), debt: account(borrower, claim, contra, "debt")
|
|
567
|
+
}
|
|
568
|
+
lifecycle { states: [proposed, assessed, collected, waived], initial: proposed }
|
|
569
|
+
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 }
|
|
570
|
+
action assess { allowZero: true,
|
|
571
|
+
from: proposed, to: assessed, actor: clock, due: { at: self.assessment.overdueAt }
|
|
572
|
+
requires self.assessment.slice.principalReceivable.balance > "0"
|
|
573
|
+
moves self.amount from self.debt to self.receivable economics { purpose: internal, sourceParty: borrower }
|
|
574
|
+
}
|
|
575
|
+
action collect {
|
|
576
|
+
from: [assessed, collected], to: assessed, actor: { parent: child(on, slice) }, input { payment: ref<on.payment> }
|
|
577
|
+
requires input.payment in [paid]
|
|
578
|
+
requires input.payment.plan == self.plan
|
|
579
|
+
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: {} }]
|
|
580
|
+
}
|
|
581
|
+
action payoff { allowZero: true,
|
|
582
|
+
from: [assessed, collected], to: collected, actor: { parent: on }
|
|
583
|
+
requires self.plan in [paid]
|
|
584
|
+
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 } }
|
|
585
|
+
moves self.receivable.balance from self.receivable to self.debt economics { purpose: internal, sourceParty: programOperator }
|
|
586
|
+
}
|
|
587
|
+
action finish { from: assessed, to: collected, actor: { parent: current() }, requires self.receivable.balance == "0" }
|
|
588
|
+
action cancel { from: proposed, to: waived }
|
|
589
|
+
action waive_for_amendment {
|
|
590
|
+
from: [assessed, collected], to: assessed, actor: { parent: child(on, amendment) }, input { amendment: ref<on.amendment> }
|
|
591
|
+
requires input.amendment.plan == self.plan; requires input.amendment in [applied]
|
|
592
|
+
invoke: [{ instrument: waiver, action: create, input: { assessment: { field: self.id }, amendment: { field: input.amendment } } }]
|
|
593
|
+
}
|
|
594
|
+
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 } }
|
|
595
|
+
action refund {
|
|
596
|
+
from: [assessed, collected], to: assessed, actor: { party: costs_to }, input { payment: ref<on.payment> }
|
|
597
|
+
requires input.payment in [paid, refunded]
|
|
598
|
+
requires input.payment.plan == self.plan
|
|
599
|
+
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: {} }]
|
|
600
|
+
}
|
|
601
|
+
records {
|
|
602
|
+
waiver_request: {
|
|
603
|
+
fields { assessment: ref<parent>, reason: text, evidence: text }
|
|
604
|
+
lifecycle { states: [requested, applied, cancelled], initial: requested }
|
|
605
|
+
action create { requires self.assessment in [assessed, collected] }
|
|
606
|
+
action apply {
|
|
607
|
+
from: requested, to: applied, actor: { party: costs_to }, input { reason: text, evidence: text }
|
|
608
|
+
invoke: [{ reference: self.assessment, action: waive, input: { reason: { field: input.reason }, evidence: { field: input.evidence } } }]
|
|
609
|
+
}
|
|
610
|
+
action cancel { from: requested, to: cancelled }
|
|
611
|
+
}
|
|
612
|
+
waiver: {
|
|
613
|
+
fields { assessment: ref<parent>, amendment: ref<on.amendment>, amount: money? }
|
|
614
|
+
lifecycle { states: [recorded], initial: recorded }
|
|
615
|
+
action create { allowZero: true,
|
|
616
|
+
actor: { parent: parent }, input { assessment: ref<parent>, amendment: ref<on.amendment> }
|
|
617
|
+
calculate: [{ target: amount, op: rate, base: { field: self.assessment.receivable.balance }, bps: { field: self.amendment.waiverPercent }, rounding: floor }]
|
|
618
|
+
requires unique "amendment_waiver" on [self.assessment, self.amendment]
|
|
619
|
+
moves self.amount from self.assessment.receivable to self.assessment.debt economics { purpose: internal, sourceParty: programOperator }
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
receipt: {
|
|
623
|
+
fields { cashReceipt: text?, assessment: ref<parent>, payment: ref<on.payment>, amount: money? }
|
|
624
|
+
lifecycle { states: [paid, refunded], initial: paid }
|
|
625
|
+
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: participant_payout, sourceParty: programOperator } }; moves self.amount from self.assessment.receivable to self.assessment.debt economics { purpose: internal, sourceParty: programOperator } }
|
|
626
|
+
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 } }
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
instrument limits(per_borrower: money, borrower: party = party(person), active_plans: integer = 1) {
|
|
634
|
+
familyRevision: 1
|
|
635
|
+
scope: product
|
|
636
|
+
summary: "One outstanding balance ceiling per borrower."
|
|
637
|
+
fields { borrower: account of borrower, limit: money = per_borrower, activePlans: integer = active_plans }
|
|
638
|
+
lifecycle { states: [pending, approved], initial: pending }
|
|
639
|
+
action create { requires unique "borrower_limit" on [self.borrower] }
|
|
640
|
+
action approve { from: pending, to: approved }
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
instrument portfolio_limit(limit: money) {
|
|
644
|
+
familyRevision: 1
|
|
645
|
+
scope: product
|
|
646
|
+
summary: "One shared ceiling on the principal the product may hold."
|
|
647
|
+
fields { key: text = "portfolio", limit: money = limit }
|
|
648
|
+
lifecycle { states: [pending, approved], initial: pending }
|
|
649
|
+
action create { requires unique "portfolio_limit" on [self.key] }
|
|
650
|
+
action approve { from: pending, to: approved }
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
instrument credit_line(borrower: party = programOperator, adapter: text, limit: money, expires: date) {
|
|
654
|
+
familyRevision: 1
|
|
655
|
+
summary: "A revolving facility from an outside lender; each draw is a separate immutable advance."
|
|
656
|
+
fields { borrower: account of borrower, facility: account(borrower, cash, "facility"), debt: account(borrower, claim, contra, "debt"), limit: money = limit, expiresAt: date = expires }
|
|
657
|
+
lifecycle { states: [pending, active, suspended, closed], initial: pending }
|
|
658
|
+
action create {}
|
|
659
|
+
action activate {
|
|
660
|
+
from: pending, to: active, actor: { party: borrower }
|
|
661
|
+
subject { adapter: adapter }
|
|
662
|
+
}
|
|
663
|
+
action suspend { from: active, to: suspended, actor: { party: borrower } }
|
|
664
|
+
action resume { from: suspended, to: active, actor: { party: borrower } }
|
|
665
|
+
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 }
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
instrument advance(line: ref<financing.credit_line>, amount: money = runtime) {
|
|
669
|
+
familyRevision: 1
|
|
670
|
+
summary: "A single facility draw with an immutable principal and full repayment."
|
|
671
|
+
fields { line: ref<line>, amount: money = amount, principalReceivable: account(self, claim) }
|
|
672
|
+
lifecycle { states: [pending, drawn, repaid, cancelled], initial: pending }
|
|
673
|
+
action create { requires self.amount <= self.line.limit }
|
|
674
|
+
invariants sum "principalReceivable.balance" of { instrument: all(financing.advance), reference: "line", anchor: self.line, states: [drawn], limit: 366 } <= self.line.limit
|
|
675
|
+
action draw {
|
|
676
|
+
from: pending, to: drawn
|
|
677
|
+
requires self.line in [active]
|
|
678
|
+
deadline: { at: self.line.expiresAt }
|
|
679
|
+
moves self.amount from self.line.facility to self.line.borrower; moves self.amount from self.line.debt to self.principalReceivable
|
|
680
|
+
}
|
|
681
|
+
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 }
|
|
682
|
+
action cancel { from: pending, to: cancelled }
|
|
683
|
+
}
|