@hyperscale0/hsx 3.0.0 → 3.1.0
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 +8 -0
- package/LICENSE.md +356 -0
- package/NOTICE.md +8 -0
- package/dist/src/cli.d.ts.map +1 -1
- package/dist/src/cli.js +2 -1
- package/dist/src/cli.js.map +1 -1
- package/dist/src/std-bundle.js +1 -1
- package/dist/src/std-bundle.js.map +1 -1
- package/dist/src/version.d.ts +1 -1
- package/dist/src/version.js +1 -1
- package/docs/README.md +10 -10
- package/package.json +6 -7
- package/src/cli.ts +2 -1
- package/src/std-bundle.ts +1 -1
- package/src/version.ts +1 -1
- package/std/financing.hsx +15 -15
- package/LICENSE +0 -661
- package/LICENSING.md +0 -52
- package/TRADEMARKS.md +0 -35
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"std-bundle.js","sourceRoot":"","sources":["../../src/std-bundle.ts"],"names":[],"mappings":"AAAA,oEAAoE;AAEpE,MAAM,CAAC,MAAM,iBAAiB,GAAgC,IAAI,GAAG,CAAiB;IACpF,CAAC,eAAe,EAAE,u8BAAu8B,CAAC;IAC19B,CAAC,WAAW,EAAE,i5GAAi5G,CAAC;IACh6G,CAAC,iBAAiB,EAAE,suEAAsuE,CAAC;IAC3vE,CAAC,YAAY,EAAE,66DAA66D,CAAC;IAC77D,CAAC,eAAe,EAAE,
|
|
1
|
+
{"version":3,"file":"std-bundle.js","sourceRoot":"","sources":["../../src/std-bundle.ts"],"names":[],"mappings":"AAAA,oEAAoE;AAEpE,MAAM,CAAC,MAAM,iBAAiB,GAAgC,IAAI,GAAG,CAAiB;IACpF,CAAC,eAAe,EAAE,u8BAAu8B,CAAC;IAC19B,CAAC,WAAW,EAAE,i5GAAi5G,CAAC;IACh6G,CAAC,iBAAiB,EAAE,suEAAsuE,CAAC;IAC3vE,CAAC,YAAY,EAAE,66DAA66D,CAAC;IAC77D,CAAC,eAAe,EAAE,82gCAA82gC,CAAC;IACj4gC,CAAC,eAAe,EAAE,ojGAAojG,CAAC;IACvkG,CAAC,aAAa,EAAE,ohPAAohP,CAAC;IACriP,CAAC,iBAAiB,EAAE,ujCAAujC,CAAC;IAC5kC,CAAC,WAAW,EAAE,2tWAA2tW,CAAC;IAC1uW,CAAC,aAAa,EAAE,qmGAAqmG,CAAC;IACtnG,CAAC,YAAY,EAAE,0zLAA0zL,CAAC;IAC10L,CAAC,YAAY,EAAE,w1DAAw1D,CAAC;CACz2D,CAAC,CAAC"}
|
package/dist/src/version.d.ts
CHANGED
package/dist/src/version.js
CHANGED
package/docs/README.md
CHANGED
|
@@ -112,16 +112,16 @@ and a correction. No artifact is returned with diagnostics.
|
|
|
112
112
|
activation. The company chooses where funding goes and when profit becomes earned.
|
|
113
113
|
All choices use accounts, calculations and moves in the same two books.
|
|
114
114
|
|
|
115
|
-
| Tunable | Default | Selected behavior
|
|
116
|
-
| ---------------------------------- | ------------------------ |
|
|
117
|
-
| `disburse_to` | `funds` | `funds` pays down payment and principal into the hold and confirms it. `borrower` credits principal to the borrower's balance.
|
|
118
|
-
| `profit_earned` | `on_payment` | `on_payment` collects unearned profit on payment. `by_schedule` moves unearned claims to earned claims at each slice's date. `at_disbursement` makes that move at activation. Cash arrives only on payment.
|
|
119
|
-
| `apply` | `fines_profit_principal` | Collect assessed fines by overdue date, then evidenced costs, profit
|
|
120
|
-
| `payoff_rebate` | `100%` | Reverse this share of unearned profit to borrower debt. Collect the remainder and all earned profit with outstanding principal.
|
|
121
|
-
| `late_charge.fines_to` | `programOperator` | Receive fine cash and fund its refunds.
|
|
122
|
-
| `late_charge.costs_to` | `programOperator` | Receive evidenced recovery cash and fund its refunds.
|
|
123
|
-
| `collections.case.fee` | `20%` | After payment succeeds, transfer this share of the payment amount from plan capital to the agency.
|
|
124
|
-
| `lending.distribution.residual_to` | `programOperator` | Receive the cash remainder and own the claim loss remainder after weighted distribution.
|
|
115
|
+
| Tunable | Default | Selected behavior |
|
|
116
|
+
| ---------------------------------- | ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
117
|
+
| `disburse_to` | `funds` | `funds` pays down payment and principal into the hold and confirms it. `borrower` credits principal to the borrower's balance. |
|
|
118
|
+
| `profit_earned` | `on_payment` | `on_payment` collects unearned profit on payment. `by_schedule` moves unearned claims to earned claims at each slice's date. `at_disbursement` makes that move at activation. Cash arrives only on payment. |
|
|
119
|
+
| `apply` | `fines_profit_principal` | Collect assessed fines by overdue date, then evidenced costs, then each slice in position order, profit before principal. `principal_profit` collects each slice in position order, principal before profit. `pro_rata` uses the slice's fixed principal:profit ratio, caps each side by its collectible balance, and assigns the remainder without stranding cash. |
|
|
120
|
+
| `payoff_rebate` | `100%` | Reverse this share of unearned profit to borrower debt. Collect the remainder and all earned profit with outstanding principal. |
|
|
121
|
+
| `late_charge.fines_to` | `programOperator` | Receive fine cash and fund its refunds. |
|
|
122
|
+
| `late_charge.costs_to` | `programOperator` | Receive evidenced recovery cash and fund its refunds. |
|
|
123
|
+
| `collections.case.fee` | `20%` | After payment succeeds, transfer this share of the payment amount from plan capital to the agency. |
|
|
124
|
+
| `lending.distribution.residual_to` | `programOperator` | Receive the cash remainder and own the claim loss remainder after weighted distribution. |
|
|
125
125
|
|
|
126
126
|
Each slice owns separate unearned and earned claim accounts. Scheduled recognition
|
|
127
127
|
uses a dated child record so a partial payment cannot disable the maturity clock.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hyperscale0/hsx",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.1.0",
|
|
4
4
|
"description": "The strictly typed HSX language: compile total financial programs into canonical UDL.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"hsx",
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
"type": "git",
|
|
22
22
|
"url": "git+https://github.com/hyperscale0/hyperscale-hsx.git"
|
|
23
23
|
},
|
|
24
|
-
"license": "
|
|
24
|
+
"license": "SEE LICENSE IN LICENSE.md",
|
|
25
25
|
"author": "Hyperscale LLC",
|
|
26
26
|
"type": "module",
|
|
27
27
|
"sideEffects": false,
|
|
@@ -44,13 +44,12 @@
|
|
|
44
44
|
"./std/*": "./std/*"
|
|
45
45
|
},
|
|
46
46
|
"files": [
|
|
47
|
+
"LICENSE.md",
|
|
48
|
+
"NOTICE.md",
|
|
47
49
|
"AUTHORS",
|
|
48
50
|
"CHANGELOG.md",
|
|
49
|
-
"LICENSE",
|
|
50
|
-
"LICENSING.md",
|
|
51
51
|
"README.md",
|
|
52
52
|
"SECURITY.md",
|
|
53
|
-
"TRADEMARKS.md",
|
|
54
53
|
"bin",
|
|
55
54
|
"dist",
|
|
56
55
|
"docs",
|
|
@@ -77,7 +76,7 @@
|
|
|
77
76
|
"check": "bun run generate && bun run typecheck && bun run build && tsc -p playground/tsconfig.json && bun scripts/build-playground.ts && bun test test"
|
|
78
77
|
},
|
|
79
78
|
"dependencies": {
|
|
80
|
-
"@hyperscale0/udl": "3.
|
|
79
|
+
"@hyperscale0/udl": "3.1.0"
|
|
81
80
|
},
|
|
82
81
|
"devDependencies": {
|
|
83
82
|
"bun-types": "1.4.2",
|
|
@@ -88,5 +87,5 @@
|
|
|
88
87
|
"Amir Ayub",
|
|
89
88
|
"Sara AlBakaawi"
|
|
90
89
|
],
|
|
91
|
-
"gitHead": "
|
|
90
|
+
"gitHead": "a65150e9cc5a0d5506817f0feb9d64ff62b1d964"
|
|
92
91
|
}
|
package/src/cli.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { existsSync, readFileSync } from "node:fs";
|
|
2
2
|
import { readFile, writeFile } from "node:fs/promises";
|
|
3
3
|
import { headerManifest } from "./headers.ts";
|
|
4
|
+
import { HSX_VERSION } from "./version.ts";
|
|
4
5
|
import { compile } from "./compile.ts";
|
|
5
6
|
import { format } from "./format.ts";
|
|
6
7
|
import { serializeUdl } from "@hyperscale0/udl";
|
|
@@ -37,7 +38,7 @@ export async function runCli(argv: readonly string[], io: Io): Promise<number> {
|
|
|
37
38
|
return 0;
|
|
38
39
|
}
|
|
39
40
|
if (command === "--version") {
|
|
40
|
-
io.out(
|
|
41
|
+
io.out(`${HSX_VERSION} (UDL 3)`);
|
|
41
42
|
return 0;
|
|
42
43
|
}
|
|
43
44
|
if (
|
package/src/std-bundle.ts
CHANGED
|
@@ -5,7 +5,7 @@ export const BUNDLED_STD_FILES: ReadonlyMap<string, string> = new Map<string, st
|
|
|
5
5
|
["cards.hsx", "header cards\n\ninstrument cardholder(holder: party) {\n fields { holder: account of holder }\n lifecycle { states: [pending, active, suspended, closed], initial: pending }\n action create {}\n action activate { from: pending, to: active }\n action suspend { from: active, to: suspended }\n action resume { from: suspended, to: active }\n action close { from: [active, suspended], to: closed }\n}\n\ninstrument card(holder: ref<cards.cardholder>, spend_limit: money) {\n fields { holder: ref<holder>, spendLimit: money = spend_limit }\n lifecycle { states: [issued, active, frozen, cancelled], initial: issued }\n action create { requires self.holder in [active] }\n action activate { from: issued, to: active, requires self.holder in [active] }\n action freeze { from: active, to: frozen }\n action unfreeze { from: frozen, to: active, requires self.holder in [active] }\n action cancel { from: [issued, active, frozen], to: cancelled }\n}\n\ninstrument authorization(card: ref<cards.card>, merchant: party, approval: approval) {\n fields { card: ref<card>, merchant: account of merchant, amount: money, expiresAt: date, reservation: text? }\n lifecycle { states: [pending, approved, captured, declined, reversed, expired], initial: pending }\n action create {\n requires self.card in [active]\n requires self.amount <= self.card.spendLimit\n }\n action approve {\n from: pending, to: approved, deadline: { at: self.expiresAt }\n requires approval by approval; requires self.card in [active]\n moves reserve self.amount from self.card.holder.holder to merchant capture \"reservation\"\n }\n action capture { from: approved, to: captured, deadline: { at: self.expiresAt }, moves post self.reservation }\n action reverse { from: approved, to: reversed, moves void self.reservation }\n action decline { from: pending, to: declined, requires approval by approval for \"approve\" is \"declined\" }\n action expire { from: approved, to: expired, actor: clock, due: { at: self.expiresAt }, moves void self.reservation }\n action abandon { from: pending, to: expired, actor: clock, due: { at: self.expiresAt } }\n}\n\ninstrument transaction(authorization: ref<cards.authorization>) {\n fields { authorization: ref<authorization>, amount: money = self.authorization.amount }\n lifecycle { states: [posted, refunded], initial: posted }\n action create { requires self.authorization in [captured]; requires unique \"captured_authorization\" on [self.authorization] }\n action refund {\n from: posted, to: refunded, input { reason: text }\n moves self.amount from self.authorization.merchant to self.authorization.card.holder.holder\n }\n}\n\ninstrument dispute(transaction: ref<cards.transaction>, approved_by: approval, within: duration = 90d) {\n fields { transaction: ref<transaction>, evidence: text, expiresAt: date = after(self.createdAt, within) }\n lifecycle { states: [filed, reviewing, won, lost], initial: filed }\n action create { requires self.transaction in [posted]; requires unique \"disputed_transaction\" on [self.transaction] }\n action review { from: filed, to: reviewing }\n action win {\n from: reviewing, to: won, deadline: { at: self.expiresAt }\n requires approval by approved_by\n invoke: [{ reference: self.transaction, action: refund, input: { reason: { field: self.evidence } } }]\n }\n action lose { from: [filed, reviewing], to: lost, actor: clock, due: { at: self.expiresAt } }\n}\n"],
|
|
6
6
|
["collections.hsx", "header collections\n\ninstrument case(on: ref<financing.installments>[], agency: approval, overdue: duration = 3d, fee: percent = 20%) {\n summary: \"Refer an overdue installment without creating another debt.\"\n fields { plan: ref<on>, slice: ref<on.slice>, payment: ref<on.payment>, eligibleAt: date = after(self.slice.dueAt, overdue), agencyFee: money = rate(self.payment.amount, fee) }\n lifecycle { states: [opened, assigned, recovered, recalled, returned_uncollected], initial: opened }\n action create {\n due: { at: self.eligibleAt }\n requires self.plan == self.slice.plan\n requires self.slice.principalReceivable.balance > \"0\"\n requires unique \"overdue_referral\" on [self.slice]\n }\n action assign { from: opened, to: assigned, requires approval by agency }\n action recover { from: assigned, to: recovered, actor: { party: agency }, requires self.payment.plan == self.plan, invoke: [{ reference: self.payment, action: pay, input: {} }, { reference: self.id, action: pay_fee, input: {} }] }\n action pay_fee { from: recovered, to: recovered, actor: { parent: current() }, moves self.agencyFee from self.plan.capital to agency }\n action recall { from: [opened, assigned], to: recalled }\n action return_uncollected { from: assigned, to: returned_uncollected, actor: { party: agency } }\n}\n\ninstrument contact(case: ref<collections.case>[], agency: party, max_contacts: integer = 10, window: duration = 30d, contact_from: integer(0, 23) = 8, contact_until: integer(1, 23) = 20, timezone: text = \"Asia/Riyadh\") {\n constraints { contact_from: less_than(contact_until) }\n summary: \"An agency contact admitted against the locked rolling count and local hours.\"\n fields { case: ref<case>, attemptedAt: date?, evidence: text, maximum: integer = max_contacts }\n lifecycle { states: [recorded], initial: recorded }\n invariants count of { instrument: current(), reference: \"case\", anchor: self.case, states: [recorded], limit: 366, window: { field: attemptedAt, milliseconds: window } } <= self.maximum\n action create {\n actor: { party: agency }\n set: { attemptedAt: { field: self.now } }\n requires self.case in [assigned]; requires hours self.now between contact_from and contact_until timezone timezone\n }\n}\n"],
|
|
7
7
|
["escrow.hsx", "header escrow\n\ninstrument hold(payer: party, payee: party, for: ref<marketplace.order>, accept_within: duration = 48h, fee: fee = { seller: 0%, tax: 0% }, dispute: policy = { refund_after: return_verified }) {\n summary: \"Hold an order's price through delivery, acceptance and verified return.\"\n fields { payer: account of payer, payee: account of payee, order: ref<for>, price: money, held: account of self, deliveredAt: date? }\n lifecycle { states: [pending, funded, delivered, disputed, return_verified, released, refunded, cancelled], initial: pending }\n action create {\n requires self.payer == self.order.buyer; requires self.payee == self.order.listing.seller; requires self.price == self.order.price\n }\n action fund {\n from: pending, to: funded, actor: { party: payer }\n requires self.order in [placed]\n moves self.price from payer to self.held\n }\n action confirm {\n from: pending, to: funded\n requires self.held.balance == self.price\n }\n action deliver {\n from: funded, to: delivered, actor: { party: payee }\n set: { deliveredAt: { field: self.now } }\n }\n action accept {\n from: delivered, to: released, actor: { party: payer }\n input { acceptanceReference: text, titleReference: text, coverReference: text }\n deadline: { at: self.deliveredAt, offset: accept_within }\n moves self.price from self.held to payee fee fee\n }\n action expire_acceptance {\n from: delivered, to: disputed, actor: clock\n due: { at: self.deliveredAt, offset: accept_within }\n }\n action dispute {\n from: delivered, to: disputed, actor: { party: payer }\n deadline: { at: self.deliveredAt, offset: accept_within }\n }\n action verify_return { from: disputed, to: return_verified, input { returnReference: text } }\n action refund {\n from: dispute.refund_after, to: refunded\n moves self.price from self.held to payer\n }\n action cancel { from: pending, to: cancelled }\n}\n"],
|
|
8
|
-
["financing.hsx", "header financing\n\ninstrument installments(months: integer(1, 366), 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%, down_payment: percent = 0%, funds: ref, approval: approval, borrower: party = party(person), capital: party = programOperator, profit_to: party = programOperator, limits: ref<financing.limits> = object(financing.limits)) {\n summary: \"Funding, profit recognition and payment order chosen by the program.\"\n fields {\n funds: ref<funds>, borrower: account of borrower, settlementIdentity: text, borrowerLimit: ref<limits>, portfolioLimit: ref<limits.portfolio>\n months: integer = months, dates: list(date, months)\n capital: account(capital, cash, \"capital\"), profitIncome: account(profit_to, cash, \"profitIncome\"), loss: account(capital, claim, \"loss\")\n principalReturned: money?, profitReturned: money?, lossRecorded: money?\n price: money = self.funds.price\n downPayment: money = rate(self.price, down_payment)\n principal: money = subtract(self.price, self.downPayment)\n profit: money = rate(self.principal, profit)\n total: money = sum(self.principal, self.profit)\n }\n lifecycle { states: [quoted, signed, active, paid, written_off, cancelled], initial: quoted }\n invariants count of { instrument: all(financing.installments), reference: \"borrowerLimit\", anchor: self.borrowerLimit, states: [active], limit: 366 } <= self.borrowerLimit.activePlans\n invariants sum \"principalReceivable.balance\" of { instrument: all(financing.installments.slice), reference: \"borrowerLimit\", anchor: self.borrowerLimit, states: [pending, due, paid], limit: 366 } <= self.borrowerLimit.limit\n invariants sum \"principalReceivable.balance\" of { instrument: all(financing.installments.slice), reference: \"portfolioLimit\", anchor: self.portfolioLimit, states: [pending, due, paid], limit: 366 } <= self.portfolioLimit.limit\n action create { requires self.borrower == self.funds.payer; requires self.borrowerLimit.borrower == self.funds.payer }\n action sign {\n from: quoted, to: signed, input { signedOffer: text, affordabilityReference: text }\n requires count of { instrument: slice, reference: \"plan\", anchor: self.id, states: [draft], limit: 366 } == self.months\n }\n action disburse {\n from: signed, to: active\n requires approval by approval\n requires self.borrowerLimit in [approved]\n requires self.portfolioLimit in [approved]\n when disburse_to is funds {\n requires self.funds in [pending]\n moves self.downPayment from self.funds.payer to self.funds.held\n moves self.principal from self.capital to self.funds.held\n }\n when disburse_to is borrower { moves self.principal from self.capital to self.borrower }\n invoke: [{ selection: { instrument: slice, reference: \"plan\", anchor: self.id, states: [draft], limit: 366, order: [position] }, action: activate, input: {} }]\n when disburse_to is funds { invoke: [{ reference: self.funds, action: confirm, input: {} }] }\n }\n action complete {\n from: active, to: paid\n requires count of { instrument: slice, reference: \"plan\", anchor: self.id, states: [pending, due], limit: 366 } == 0\n }\n action restore { from: [active, paid], to: active, actor: { parent: payment } }\n action payoff {\n from: active, to: paid\n calculate: [\n { target: \"principalReturned\", op: \"aggregate\", selection: { instrument: slice, reference: \"plan\", anchor: self.id, states: [pending, due], limit: 366 }, measure: { sum: \"principalReceivable.balance\" } }\n ]\n 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: {} }]\n }\n action record_payoff {\n from: paid, to: paid, actor: { parent: current() }\n 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\" } }]\n 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\" } } }]\n }\n action write_off {\n from: active, to: written_off\n input { overdueSlice: ref<slice> }\n calculate: [{ target: \"lossRecorded\", op: \"aggregate\", selection: { instrument: slice, reference: \"plan\", anchor: self.id, states: [pending, due], limit: 366 }, measure: { sum: \"principalReceivable.balance\" } }]\n due: { at: input.overdueSlice.lossEligibleAt }\n requires approval by approval; requires input.overdueSlice.plan == self.id; requires input.overdueSlice.principalReceivable.balance > \"0\"\n 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\" } } }]\n }\n action record_payment {\n from: active, to: active, actor: { parent: payment }\n input { identity: text, principal: money, profit: money }\n 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\" } } }]\n }\n action cancel { from: quoted, to: cancelled }\n records {\n settlement: {\n fields { plan: ref<parent>, identity: text, principal: money, profit: money, loss: money, kind: enum(cash, loss)\n capital: account(capital, cash, \"capital\"), profitIncome: account(profit_to, cash, \"profitIncome\"), lossAccount: account(capital, claim, \"loss\") }\n lifecycle { states: [recorded], initial: recorded }\n action create {\n actor: { parent: parent }\n input { plan: ref<parent>, identity: text, principal: money, profit: money, loss: money, kind: enum(cash, loss) }\n requires unique \"settlement_identity\" on [self.plan, self.identity]\n }\n }\n slice: {\n fields {\n plan: ref<parent>, borrowerLimit: ref<limits>, portfolioLimit: ref<limits.portfolio>, position: integer(1, months)\n dueAt: date = at(self.plan.dates, self.position), lossEligibleAt: date = after(self.dueAt, 90d)\n principalBase: money = divide(self.plan.principal, months)\n principalAllocated: money = multiply(self.principalBase, months)\n principalRemainder: money = subtract(self.plan.principal, self.principalAllocated)\n profitBase: money = divide(self.plan.profit, months)\n profitAllocated: money = multiply(self.profitBase, months)\n profitRemainder: money = subtract(self.plan.profit, self.profitAllocated)\n first: integer = divide(1, self.position)\n principalAdjustment: money = multiply(self.principalRemainder, self.first)\n profitAdjustment: money = multiply(self.profitRemainder, self.first)\n principal: money = sum(self.principalBase, self.principalAdjustment)\n profit: money = sum(self.profitBase, self.profitAdjustment)\n principalReceivable: account(self, claim), profitReceivable: account(self, claim), profitEarned: account(self, claim)\n payoffIdentity: text?, payoffRebate: money?, payoffProfitTotal: money?, payoffProfit: money?\n paymentBudget: money?, principalPayment: money?, principalBasePayment: money?, principalRatio: money?, profitPayment: money?, profitBudget: money?, collectibleProfit: money?, collectibleTotal: money?, ratioDenominator: integer = sum(10000, profit)\n debt: account(borrower, claim, contra, \"debt\"), loss: account(capital, claim, \"loss\")\n }\n lifecycle { states: [draft, pending, due, paid, written_off], initial: draft }\n action create {\n 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\n }\n action activate {\n from: draft, to: pending, actor: { parent: parent }\n moves self.principal from self.debt to self.principalReceivable\n moves self.profit from self.debt to self.profitReceivable\n when profit_earned is at_disbursement { moves self.profit from self.profitReceivable to self.profitEarned }\n when profit_earned is by_schedule { invoke: [{ instrument: recognition, action: create, input: { slice: { field: self.id } } }] }\n }\n action mature {\n from: pending, to: due, actor: clock, due: { at: self.dueAt }\n }\n action collect_principal {\n from: [pending, due, paid], to: due, actor: { parent: payment }\n input { payment: ref<payment> }\n requires input.payment.plan == self.plan\n invoke: [{ instrument: principal_piece, action: create, input: { slice: { field: self.id }, payment: { field: input.payment }, limit: { field: self.principalReceivable.balance } } }]\n }\n action collect_profit {\n from: [pending, due, paid], to: due, actor: { parent: payment }\n input { payment: ref<payment> }\n requires input.payment.plan == self.plan\n when profit_earned is on_payment { invoke: [{ instrument: profit_piece, action: create, input: { slice: { field: self.id }, payment: { field: input.payment }, limit: { field: self.profitReceivable.balance } } }] }\n when profit_earned is by_schedule { invoke: [{ instrument: profit_piece, action: create, input: { slice: { field: self.id }, payment: { field: input.payment }, limit: { field: self.profitEarned.balance } } }] }\n when profit_earned is at_disbursement { invoke: [{ instrument: profit_piece, action: create, input: { slice: { field: self.id }, payment: { field: input.payment }, limit: { field: self.profitEarned.balance } } }] }\n }\n action collect_pro_rata {\n from: [pending, due, paid], to: due, actor: { parent: payment }, input { payment: ref<payment> }\n requires input.payment.plan == self.plan\n when profit_earned is on_payment { calculate: [{ target: collectibleProfit, op: sum, values: [{ field: self.profitReceivable.balance }] }] }\n when profit_earned is by_schedule { calculate: [{ target: collectibleProfit, op: sum, values: [{ field: self.profitEarned.balance }] }] }\n when profit_earned is at_disbursement { calculate: [{ target: collectibleProfit, op: sum, values: [{ field: self.profitEarned.balance }] }] }\n calculate: [\n { target: collectibleTotal, op: sum, values: [{ field: self.principalReceivable.balance }, { field: self.collectibleProfit }] }\n { target: paymentBudget, op: minimum, values: [{ field: input.payment.held.balance }, { field: self.collectibleTotal }] }\n { target: principalRatio, op: ratio, amount: { field: self.paymentBudget }, numerator: { literal: 10000 }, denominator: { field: self.ratioDenominator }, rounding: floor }\n { target: principalBasePayment, op: minimum, values: [{ field: self.principalRatio }, { field: self.principalReceivable.balance }] }\n { target: profitBudget, op: subtract, base: { field: self.paymentBudget }, subtract: [{ field: self.principalBasePayment }] }\n { target: profitPayment, op: minimum, values: [{ field: self.profitBudget }, { field: self.collectibleProfit }] }\n { target: principalPayment, op: subtract, base: { field: self.paymentBudget }, subtract: [{ field: self.profitPayment }] }\n ]\n invoke: [{ instrument: principal_piece, action: create, input: { slice: { field: self.id }, payment: { field: input.payment }, limit: { field: self.principalPayment } } }, { instrument: profit_piece, action: create, input: { slice: { field: self.id }, payment: { field: input.payment }, limit: { field: self.profitPayment } } }]\n }\n action complete {\n from: [pending, due], to: paid\n requires self.principalReceivable.balance == \"0\"; requires self.profitReceivable.balance == \"0\"; requires self.profitEarned.balance == \"0\"\n }\n action restore { from: [pending, due, paid], to: due, actor: { parent: payment } }\n action payoff {\n from: [pending, due], to: paid, actor: { parent: parent }, input { identity: text }\n set: { payoffIdentity: { field: input.identity } }\n 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 }] }]\n moves self.principalReceivable.balance from self.plan.borrower to self.plan.capital\n moves self.payoffProfit from self.plan.borrower to self.plan.profitIncome\n moves self.principalReceivable.balance from self.principalReceivable to self.debt\n moves self.profitReceivable.balance from self.profitReceivable to self.debt\n moves self.profitEarned.balance from self.profitEarned to self.debt\n }\n action write_off {\n from: [pending, due], to: written_off, actor: { parent: parent }\n moves self.principalReceivable.balance from self.principalReceivable to self.loss; moves self.profitReceivable.balance from self.profitReceivable to self.debt; moves self.profitEarned.balance from self.profitEarned to self.loss\n }\n }\n recognition: {\n fields { slice: ref<slice>, dueAt: date = after(self.slice.dueAt, 0) }\n lifecycle { states: [pending, recognised], initial: pending }\n action create { actor: { parent: slice }, input { slice: ref<slice> }, requires unique \"profit_recognition\" on [self.slice] }\n action mature { from: pending, to: recognised, actor: clock, due: { at: self.dueAt }, moves self.slice.profitReceivable.balance from self.slice.profitReceivable to self.slice.profitEarned }\n }\n payment: {\n fields { plan: ref<parent>, amount: money, paymentIdentity: text, held: account of self\n capital: account(capital, cash, \"capital\"), profitIncome: account(profit_to, cash, \"profitIncome\"), loss: account(capital, claim, \"loss\")\n outstandingPrincipal: money?, outstandingProfit: money?, outstandingFines: money?, outstandingCosts: money?, outstandingTotal: money?, principalReturned: money?, profitReturned: money?, lossRecorded: money? }\n\n lifecycle { states: [pending, paid, refunded], initial: pending }\n action create { input { plan: ref<parent>, amount: money, paymentIdentity: text } requires self.plan in [active]; requires unique \"payment_identity\" on [self.plan, self.paymentIdentity] }\n action pay {\n from: pending, to: paid\n 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\" } }]\n 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\" } }] }\n 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\" } }] }\n 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\" } }] }\n when apply is fines_profit_principal {\n 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 }] }]\n }\n when apply is principal_profit { calculate: [{ target: outstandingTotal, op: sum, values: [{ field: self.outstandingPrincipal }, { field: self.outstandingProfit }] }] }\n when apply is pro_rata { calculate: [{ target: outstandingTotal, op: sum, values: [{ field: self.outstandingPrincipal }, { field: self.outstandingProfit }] }] }\n requires self.plan in [active]; requires self.amount <= self.outstandingTotal\n moves self.amount from self.plan.borrower to self.held\n when apply is principal_profit {\n invoke: [{ selection: { instrument: slice, reference: \"plan\", anchor: self.plan, states: [pending, due, paid], limit: 366, order: [position] }, action: collect_principal, input: { payment: { field: self.id } } }, { selection: { instrument: slice, reference: \"plan\", anchor: self.plan, states: [pending, due, paid], limit: 366, order: [position] }, action: collect_profit, input: { payment: { field: self.id } } }]\n }\n when apply is fines_profit_principal {\n invoke: [{ selection: { instrument: all(financing.late_charge), reference: \"plan\", anchor: self.plan, states: [assessed, collected], limit: 366, order: [overdueAt] }, action: collect, 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: collect, input: { payment: { field: self.id } } }]\n invoke: [{ selection: { instrument: slice, reference: \"plan\", anchor: self.plan, states: [pending, due, paid], limit: 366, order: [position] }, action: collect_profit, input: { payment: { field: self.id } } }, { selection: { instrument: slice, reference: \"plan\", anchor: self.plan, states: [pending, due, paid], limit: 366, order: [position] }, action: collect_principal, input: { payment: { field: self.id } } }]\n }\n when apply is pro_rata {\n invoke: [{ selection: { instrument: slice, reference: \"plan\", anchor: self.plan, states: [pending, due, paid], limit: 366, order: [position] }, action: collect_pro_rata, input: { payment: { field: self.id } } }]\n }\n invoke: [{ reference: self.id, action: record, input: {} }]\n }\n action record {\n from: paid, to: paid, actor: { parent: current() }\n requires self.held.balance == \"0\"\n 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\" } }]\n invoke: [{ reference: self.plan, action: record_payment, input: { identity: { field: self.paymentIdentity }, principal: { field: self.principalReturned }, profit: { field: self.profitReturned } } }]\n }\n action refund {\n from: paid, to: refunded\n requires unique \"settlement_consumption\" on [self.plan, self.paymentIdentity]; requires self.plan in [active, paid]\n when apply is fines_profit_principal {\n 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 } } }]\n }\n 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: {} }]\n }\n }\n principal_piece: {\n fields { slice: ref<slice>, payment: ref<payment>, limit: money, amount: money = minimum(self.payment.held.balance, self.slice.principalReceivable.balance, self.limit) }\n lifecycle { states: [paid, refunded], initial: paid }\n action create {\n actor: { parent: slice }\n input { slice: ref<slice>, payment: ref<payment>, limit: money }\n requires unique \"principal_receipt\" on [self.slice, self.payment]\n moves self.amount from self.payment.held to self.slice.plan.capital; moves self.amount from self.slice.principalReceivable to self.slice.debt\n }\n action refund {\n from: paid, to: refunded, actor: { parent: payment }\n moves self.amount from self.slice.plan.capital to self.slice.plan.funds.payer; moves self.amount from self.slice.debt to self.slice.principalReceivable\n }\n }\n profit_piece: {\n fields { slice: ref<slice>, payment: ref<payment>, limit: money, amount: money = minimum(self.payment.held.balance, self.limit) }\n lifecycle { states: [paid, refunded], initial: paid }\n action create {\n actor: { parent: slice }\n input { slice: ref<slice>, payment: ref<payment>, limit: money }\n requires unique \"profit_receipt\" on [self.slice, self.payment]\n moves self.amount from self.payment.held to self.slice.plan.profitIncome\n when profit_earned is on_payment { moves self.amount from self.slice.profitReceivable to self.slice.debt }\n when profit_earned is by_schedule { moves self.amount from self.slice.profitEarned to self.slice.debt }\n when profit_earned is at_disbursement { moves self.amount from self.slice.profitEarned to self.slice.debt }\n }\n action refund {\n from: paid, to: refunded, actor: { parent: payment }\n moves self.amount from self.slice.plan.profitIncome to self.slice.plan.borrower\n when profit_earned is on_payment { moves self.amount from self.slice.debt to self.slice.profitReceivable }\n when profit_earned is by_schedule { moves self.amount from self.slice.debt to self.slice.profitEarned }\n when profit_earned is at_disbursement { moves self.amount from self.slice.debt to self.slice.profitEarned }\n }\n }\n }\n}\n\ninstrument 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, approval: approval = programOperator, borrower: party = party(person)) {\n summary: \"Separate approved fine and evidenced recovery claims on an overdue installment.\"\n fields {\n plan: ref<on>, slice: ref<on.slice>, borrower: account of borrower, fine: money = fine, overdueAt: date = after(self.slice.dueAt, grace)\n receivable: account(self, claim), debt: account(borrower, claim, contra, \"debt\")\n }\n lifecycle { states: [proposed, assessed, collected, waived, refunded], initial: proposed }\n action create { requires unique \"late_assessment\" on [self.slice]; requires self.borrower == self.slice.plan.borrower; requires self.plan == self.slice.plan }\n action assess {\n from: proposed, to: assessed, due: { at: self.overdueAt }\n requires self.slice.principalReceivable.balance > \"0\"; requires approval by approval\n moves self.fine from self.debt to self.receivable\n }\n action collect {\n from: [assessed, collected], to: assessed, input { payment: ref<on.payment> }\n requires input.payment in [paid]\n requires input.payment.plan == self.plan\n 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: {} }]\n }\n action finish { from: assessed, to: collected, actor: { parent: current() }, requires self.receivable.balance == \"0\" }\n action cancel { from: proposed, to: waived }\n action waive { from: assessed, to: waived, moves self.receivable.balance from self.receivable to self.debt }\n action refund {\n from: [assessed, collected], to: assessed, input { payment: ref<on.payment> }\n requires input.payment in [paid, refunded]\n requires input.payment.plan == self.plan\n 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: {} }]\n }\n records {\n receipt: {\n fields { assessment: ref<parent>, payment: ref<on.payment>, amount: money = minimum(self.payment.held.balance, self.assessment.receivable.balance) }\n lifecycle { states: [paid, refunded], initial: paid }\n action create { 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; moves self.amount from self.assessment.receivable to self.assessment.debt }\n action refund { from: paid, to: refunded, actor: { parent: parent }, moves self.amount from fines_to to self.payment.plan.borrower; moves self.amount from self.assessment.debt to self.assessment.receivable }\n }\n\n cost: {\n fields {\n plan: ref<on>, assessment: ref<parent>, overdueAt: date = after(self.assessment.slice.dueAt, grace), evidence: text, evidencedCost: money, amount: money = minimum(self.evidencedCost, cap)\n receivable: account(self, claim), debt: account(borrower, claim, contra, \"debt\")\n }\n lifecycle { states: [proposed, assessed, collected, waived, refunded], initial: proposed }\n action create { requires unique \"recovery_assessment\" on [self.assessment]; requires self.plan == self.assessment.plan }\n action assess {\n from: proposed, to: assessed, due: { at: self.assessment.overdueAt }\n requires self.assessment.slice.principalReceivable.balance > \"0\"; requires approval by approval\n moves self.amount from self.debt to self.receivable\n }\n action collect {\n from: [assessed, collected], to: assessed, input { payment: ref<on.payment> }\n requires input.payment in [paid]\n requires input.payment.plan == self.plan\n 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: {} }]\n }\n action finish { from: assessed, to: collected, actor: { parent: current() }, requires self.receivable.balance == \"0\" }\n action cancel { from: proposed, to: waived }\n action waive { from: assessed, to: waived, moves self.receivable.balance from self.receivable to self.debt }\n action refund {\n from: [assessed, collected], to: assessed, input { payment: ref<on.payment> }\n requires input.payment in [paid, refunded]\n requires input.payment.plan == self.plan\n 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: {} }]\n }\n records {\n receipt: {\n fields { assessment: ref<parent>, payment: ref<on.payment>, amount: money = minimum(self.payment.held.balance, self.assessment.receivable.balance) }\n lifecycle { states: [paid, refunded], initial: paid }\n action create { 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; moves self.amount from self.assessment.receivable to self.assessment.debt }\n action refund { from: paid, to: refunded, actor: { parent: parent }, moves self.amount from costs_to to self.payment.plan.borrower; moves self.amount from self.assessment.debt to self.assessment.receivable }\n }\n }\n }\n }\n}\n\ninstrument limits(per_borrower: money, portfolio: money, borrower: party = party(person), active_plans: integer = 1) {\n summary: \"One borrower ceiling and one shared portfolio ceiling.\"\n fields { borrower: account of borrower, limit: money = per_borrower, activePlans: integer = active_plans }\n lifecycle { states: [pending, approved], initial: pending }\n action create { requires unique \"borrower_limit\" on [self.borrower] }\n action approve { from: pending, to: approved }\n records {\n portfolio: {\n fields { key: text = \"portfolio\", limit: money = portfolio }\n lifecycle { states: [pending, approved], initial: pending }\n action create { requires unique \"portfolio_limit\" on [self.key] }\n action approve { from: pending, to: approved }\n }\n }\n}\n\ninstrument credit_line(borrower: party, lender: party, limit: money, expires: date) {\n summary: \"A revolving facility; each draw is a separate immutable advance.\"\n fields { borrower: account of borrower, lender: account of lender, debt: account(borrower, claim, contra, \"debt\"), limit: money = limit, expiresAt: date = expires }\n lifecycle { states: [pending, active, suspended, closed], initial: pending }\n action create {}\n action activate { from: pending, to: active, actor: { party: lender } }\n action suspend { from: active, to: suspended, actor: { party: lender } }\n action resume { from: suspended, to: active, actor: { party: lender } }\n 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 }\n}\n\ninstrument advance(line: ref<financing.credit_line>, amount: money = runtime) {\n summary: \"A single facility draw with an immutable principal and full repayment.\"\n fields { line: ref<line>, amount: money = amount, principalReceivable: account(self, claim) }\n lifecycle { states: [pending, drawn, repaid, cancelled], initial: pending }\n action create { requires self.amount <= self.line.limit }\n invariants sum \"principalReceivable.balance\" of { instrument: all(financing.advance), reference: \"line\", anchor: self.line, states: [drawn], limit: 366 } <= self.line.limit\n action draw {\n from: pending, to: drawn\n requires self.line in [active]\n deadline: { at: self.line.expiresAt }\n moves self.amount from self.line.lender to self.line.borrower; moves self.amount from self.line.debt to self.principalReceivable\n }\n action repay { from: drawn, to: repaid, moves self.amount from self.line.borrower to self.line.lender; moves self.amount from self.principalReceivable to self.line.debt }\n action cancel { from: pending, to: cancelled }\n}\n"],
|
|
8
|
+
["financing.hsx", "header financing\n\ninstrument installments(months: integer(1, 366), 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%, down_payment: percent = 0%, funds: ref, approval: approval, borrower: party = party(person), capital: party = programOperator, profit_to: party = programOperator, limits: ref<financing.limits> = object(financing.limits)) {\n summary: \"Funding, profit recognition and payment order chosen by the program.\"\n fields {\n funds: ref<funds>, borrower: account of borrower, settlementIdentity: text, borrowerLimit: ref<limits>, portfolioLimit: ref<limits.portfolio>\n months: integer = months, dates: list(date, months)\n capital: account(capital, cash, \"capital\"), profitIncome: account(profit_to, cash, \"profitIncome\"), loss: account(capital, claim, \"loss\")\n principalReturned: money?, profitReturned: money?, lossRecorded: money?\n price: money = self.funds.price\n downPayment: money = rate(self.price, down_payment)\n principal: money = subtract(self.price, self.downPayment)\n profit: money = rate(self.principal, profit)\n total: money = sum(self.principal, self.profit)\n }\n lifecycle { states: [quoted, signed, active, paid, written_off, cancelled], initial: quoted }\n invariants count of { instrument: all(financing.installments), reference: \"borrowerLimit\", anchor: self.borrowerLimit, states: [active], limit: 366 } <= self.borrowerLimit.activePlans\n invariants sum \"principalReceivable.balance\" of { instrument: all(financing.installments.slice), reference: \"borrowerLimit\", anchor: self.borrowerLimit, states: [pending, due, paid], limit: 366 } <= self.borrowerLimit.limit\n invariants sum \"principalReceivable.balance\" of { instrument: all(financing.installments.slice), reference: \"portfolioLimit\", anchor: self.portfolioLimit, states: [pending, due, paid], limit: 366 } <= self.portfolioLimit.limit\n action create { requires self.borrower == self.funds.payer; requires self.borrowerLimit.borrower == self.funds.payer }\n action sign {\n from: quoted, to: signed, input { signedOffer: text, affordabilityReference: text }\n requires count of { instrument: slice, reference: \"plan\", anchor: self.id, states: [draft], limit: 366 } == self.months\n }\n action disburse {\n from: signed, to: active\n requires approval by approval\n requires self.borrowerLimit in [approved]\n requires self.portfolioLimit in [approved]\n when disburse_to is funds {\n requires self.funds in [pending]\n moves self.downPayment from self.funds.payer to self.funds.held\n moves self.principal from self.capital to self.funds.held\n }\n when disburse_to is borrower { moves self.principal from self.capital to self.borrower }\n invoke: [{ selection: { instrument: slice, reference: \"plan\", anchor: self.id, states: [draft], limit: 366, order: [position] }, action: activate, input: {} }]\n when disburse_to is funds { invoke: [{ reference: self.funds, action: confirm, input: {} }] }\n }\n action complete {\n from: active, to: paid\n requires count of { instrument: slice, reference: \"plan\", anchor: self.id, states: [pending, due], limit: 366 } == 0\n }\n action restore { from: [active, paid], to: active, actor: { parent: payment } }\n action payoff {\n from: active, to: paid\n calculate: [\n { target: \"principalReturned\", op: \"aggregate\", selection: { instrument: slice, reference: \"plan\", anchor: self.id, states: [pending, due], limit: 366 }, measure: { sum: \"principalReceivable.balance\" } }\n ]\n 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: {} }]\n }\n action record_payoff {\n from: paid, to: paid, actor: { parent: current() }\n 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\" } }]\n 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\" } } }]\n }\n action write_off {\n from: active, to: written_off\n input { overdueSlice: ref<slice> }\n calculate: [{ target: \"lossRecorded\", op: \"aggregate\", selection: { instrument: slice, reference: \"plan\", anchor: self.id, states: [pending, due], limit: 366 }, measure: { sum: \"principalReceivable.balance\" } }]\n due: { at: input.overdueSlice.lossEligibleAt }\n requires approval by approval; requires input.overdueSlice.plan == self.id; requires input.overdueSlice.principalReceivable.balance > \"0\"\n 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\" } } }]\n }\n action record_payment {\n from: active, to: active, actor: { parent: payment }\n input { identity: text, principal: money, profit: money }\n 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\" } } }]\n }\n action cancel { from: quoted, to: cancelled }\n records {\n settlement: {\n fields { plan: ref<parent>, identity: text, principal: money, profit: money, loss: money, kind: enum(cash, loss)\n capital: account(capital, cash, \"capital\"), profitIncome: account(profit_to, cash, \"profitIncome\"), lossAccount: account(capital, claim, \"loss\") }\n lifecycle { states: [recorded], initial: recorded }\n action create {\n actor: { parent: parent }\n input { plan: ref<parent>, identity: text, principal: money, profit: money, loss: money, kind: enum(cash, loss) }\n requires unique \"settlement_identity\" on [self.plan, self.identity]\n }\n }\n slice: {\n fields {\n plan: ref<parent>, borrowerLimit: ref<limits>, portfolioLimit: ref<limits.portfolio>, position: integer(1, months)\n dueAt: date = at(self.plan.dates, self.position), lossEligibleAt: date = after(self.dueAt, 90d)\n principalBase: money = divide(self.plan.principal, months)\n principalAllocated: money = multiply(self.principalBase, months)\n principalRemainder: money = subtract(self.plan.principal, self.principalAllocated)\n profitBase: money = divide(self.plan.profit, months)\n profitAllocated: money = multiply(self.profitBase, months)\n profitRemainder: money = subtract(self.plan.profit, self.profitAllocated)\n first: integer = divide(1, self.position)\n principalAdjustment: money = multiply(self.principalRemainder, self.first)\n profitAdjustment: money = multiply(self.profitRemainder, self.first)\n principal: money = sum(self.principalBase, self.principalAdjustment)\n profit: money = sum(self.profitBase, self.profitAdjustment)\n principalReceivable: account(self, claim), profitReceivable: account(self, claim), profitEarned: account(self, claim)\n payoffIdentity: text?, payoffRebate: money?, payoffProfitTotal: money?, payoffProfit: money?\n paymentBudget: money?, principalPayment: money?, principalBasePayment: money?, principalRatio: money?, profitPayment: money?, profitBudget: money?, collectibleProfit: money?, collectibleTotal: money?, ratioDenominator: integer = sum(10000, profit)\n debt: account(borrower, claim, contra, \"debt\"), loss: account(capital, claim, \"loss\")\n }\n lifecycle { states: [draft, pending, due, paid, written_off], initial: draft }\n action create {\n 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\n }\n action activate {\n from: draft, to: pending, actor: { parent: parent }\n moves self.principal from self.debt to self.principalReceivable\n moves self.profit from self.debt to self.profitReceivable\n when profit_earned is at_disbursement { moves self.profit from self.profitReceivable to self.profitEarned }\n when profit_earned is by_schedule { invoke: [{ instrument: recognition, action: create, input: { slice: { field: self.id } } }] }\n }\n action mature {\n from: pending, to: due, actor: clock, due: { at: self.dueAt }\n }\n action collect {\n from: [pending, due], to: due, actor: { parent: payment }\n input { payment: ref<payment> }\n requires input.payment.plan == self.plan\n when profit_earned is on_payment { calculate: [{ target: collectibleProfit, op: sum, values: [{ field: self.profitReceivable.balance }] }] }\n when profit_earned is by_schedule { calculate: [{ target: collectibleProfit, op: sum, values: [{ field: self.profitEarned.balance }] }] }\n when profit_earned is at_disbursement { calculate: [{ target: collectibleProfit, op: sum, values: [{ field: self.profitEarned.balance }] }] }\n when apply is fines_profit_principal {\n invoke: [{ instrument: profit_piece, action: create, input: { slice: { field: self.id }, payment: { field: input.payment }, limit: { field: self.collectibleProfit } } }, { instrument: principal_piece, action: create, input: { slice: { field: self.id }, payment: { field: input.payment }, limit: { field: self.principalReceivable.balance } } }]\n }\n when apply is principal_profit {\n invoke: [{ instrument: principal_piece, action: create, input: { slice: { field: self.id }, payment: { field: input.payment }, limit: { field: self.principalReceivable.balance } } }, { instrument: profit_piece, action: create, input: { slice: { field: self.id }, payment: { field: input.payment }, limit: { field: self.collectibleProfit } } }]\n }\n }\n action collect_pro_rata {\n from: [pending, due], to: due, actor: { parent: payment }, input { payment: ref<payment> }\n requires input.payment.plan == self.plan\n when profit_earned is on_payment { calculate: [{ target: collectibleProfit, op: sum, values: [{ field: self.profitReceivable.balance }] }] }\n when profit_earned is by_schedule { calculate: [{ target: collectibleProfit, op: sum, values: [{ field: self.profitEarned.balance }] }] }\n when profit_earned is at_disbursement { calculate: [{ target: collectibleProfit, op: sum, values: [{ field: self.profitEarned.balance }] }] }\n calculate: [\n { target: collectibleTotal, op: sum, values: [{ field: self.principalReceivable.balance }, { field: self.collectibleProfit }] }\n { target: paymentBudget, op: minimum, values: [{ field: input.payment.held.balance }, { field: self.collectibleTotal }] }\n { target: principalRatio, op: ratio, amount: { field: self.paymentBudget }, numerator: { literal: 10000 }, denominator: { field: self.ratioDenominator }, rounding: floor }\n { target: principalBasePayment, op: minimum, values: [{ field: self.principalRatio }, { field: self.principalReceivable.balance }] }\n { target: profitBudget, op: subtract, base: { field: self.paymentBudget }, subtract: [{ field: self.principalBasePayment }] }\n { target: profitPayment, op: minimum, values: [{ field: self.profitBudget }, { field: self.collectibleProfit }] }\n { target: principalPayment, op: subtract, base: { field: self.paymentBudget }, subtract: [{ field: self.profitPayment }] }\n ]\n invoke: [{ instrument: principal_piece, action: create, input: { slice: { field: self.id }, payment: { field: input.payment }, limit: { field: self.principalPayment } } }, { instrument: profit_piece, action: create, input: { slice: { field: self.id }, payment: { field: input.payment }, limit: { field: self.profitPayment } } }]\n }\n action complete {\n from: [pending, due], to: paid\n requires self.principalReceivable.balance == \"0\"; requires self.profitReceivable.balance == \"0\"; requires self.profitEarned.balance == \"0\"\n }\n action restore { from: [pending, due, paid], to: due, actor: { parent: payment } }\n action payoff {\n from: [pending, due], to: paid, actor: { parent: parent }, input { identity: text }\n set: { payoffIdentity: { field: input.identity } }\n 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 }] }]\n moves self.principalReceivable.balance from self.plan.borrower to self.plan.capital\n moves self.payoffProfit from self.plan.borrower to self.plan.profitIncome\n moves self.principalReceivable.balance from self.principalReceivable to self.debt\n moves self.profitReceivable.balance from self.profitReceivable to self.debt\n moves self.profitEarned.balance from self.profitEarned to self.debt\n }\n action write_off {\n from: [pending, due], to: written_off, actor: { parent: parent }\n moves self.principalReceivable.balance from self.principalReceivable to self.loss; moves self.profitReceivable.balance from self.profitReceivable to self.debt; moves self.profitEarned.balance from self.profitEarned to self.loss\n }\n }\n recognition: {\n fields { slice: ref<slice>, dueAt: date = after(self.slice.dueAt, 0) }\n lifecycle { states: [pending, recognised], initial: pending }\n action create { actor: { parent: slice }, input { slice: ref<slice> }, requires unique \"profit_recognition\" on [self.slice] }\n action mature { from: pending, to: recognised, actor: clock, due: { at: self.dueAt }, moves self.slice.profitReceivable.balance from self.slice.profitReceivable to self.slice.profitEarned }\n }\n payment: {\n fields { plan: ref<parent>, amount: money, paymentIdentity: text, held: account of self\n capital: account(capital, cash, \"capital\"), profitIncome: account(profit_to, cash, \"profitIncome\"), loss: account(capital, claim, \"loss\")\n outstandingPrincipal: money?, outstandingProfit: money?, outstandingFines: money?, outstandingCosts: money?, outstandingTotal: money?, principalReturned: money?, profitReturned: money?, lossRecorded: money? }\n\n lifecycle { states: [pending, paid, refunded], initial: pending }\n action create { input { plan: ref<parent>, amount: money, paymentIdentity: text } requires self.plan in [active]; requires unique \"payment_identity\" on [self.plan, self.paymentIdentity] }\n action pay {\n from: pending, to: paid\n 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\" } }]\n 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\" } }] }\n 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\" } }] }\n 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\" } }] }\n when apply is fines_profit_principal {\n 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 }] }]\n }\n when apply is principal_profit { calculate: [{ target: outstandingTotal, op: sum, values: [{ field: self.outstandingPrincipal }, { field: self.outstandingProfit }] }] }\n when apply is pro_rata { calculate: [{ target: outstandingTotal, op: sum, values: [{ field: self.outstandingPrincipal }, { field: self.outstandingProfit }] }] }\n requires self.plan in [active]; requires self.amount <= self.outstandingTotal\n moves self.amount from self.plan.borrower to self.held\n when apply is principal_profit {\n invoke: [{ selection: { instrument: slice, reference: \"plan\", anchor: self.plan, states: [pending, due], limit: 366, order: [position] }, action: collect, input: { payment: { field: self.id } } }]\n }\n when apply is fines_profit_principal {\n invoke: [{ selection: { instrument: all(financing.late_charge), reference: \"plan\", anchor: self.plan, states: [assessed, collected], limit: 366, order: [overdueAt] }, action: collect, 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: collect, input: { payment: { field: self.id } } }]\n invoke: [{ selection: { instrument: slice, reference: \"plan\", anchor: self.plan, states: [pending, due], limit: 366, order: [position] }, action: collect, input: { payment: { field: self.id } } }]\n }\n when apply is pro_rata {\n invoke: [{ selection: { instrument: slice, reference: \"plan\", anchor: self.plan, states: [pending, due], limit: 366, order: [position] }, action: collect_pro_rata, input: { payment: { field: self.id } } }]\n }\n invoke: [{ reference: self.id, action: record, input: {} }]\n }\n action record {\n from: paid, to: paid, actor: { parent: current() }\n requires self.held.balance == \"0\"\n 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\" } }]\n invoke: [{ reference: self.plan, action: record_payment, input: { identity: { field: self.paymentIdentity }, principal: { field: self.principalReturned }, profit: { field: self.profitReturned } } }]\n }\n action refund {\n from: paid, to: refunded\n requires unique \"settlement_consumption\" on [self.plan, self.paymentIdentity]; requires self.plan in [active, paid]\n when apply is fines_profit_principal {\n 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 } } }]\n }\n 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: {} }]\n }\n }\n principal_piece: {\n fields { slice: ref<slice>, payment: ref<payment>, limit: money, amount: money = minimum(self.payment.held.balance, self.slice.principalReceivable.balance, self.limit) }\n lifecycle { states: [paid, refunded], initial: paid }\n action create {\n actor: { parent: slice }\n input { slice: ref<slice>, payment: ref<payment>, limit: money }\n requires unique \"principal_receipt\" on [self.slice, self.payment]\n moves self.amount from self.payment.held to self.slice.plan.capital; moves self.amount from self.slice.principalReceivable to self.slice.debt\n }\n action refund {\n from: paid, to: refunded, actor: { parent: payment }\n moves self.amount from self.slice.plan.capital to self.slice.plan.funds.payer; moves self.amount from self.slice.debt to self.slice.principalReceivable\n }\n }\n profit_piece: {\n fields { slice: ref<slice>, payment: ref<payment>, limit: money, amount: money = minimum(self.payment.held.balance, self.limit) }\n lifecycle { states: [paid, refunded], initial: paid }\n action create {\n actor: { parent: slice }\n input { slice: ref<slice>, payment: ref<payment>, limit: money }\n requires unique \"profit_receipt\" on [self.slice, self.payment]\n moves self.amount from self.payment.held to self.slice.plan.profitIncome\n when profit_earned is on_payment { moves self.amount from self.slice.profitReceivable to self.slice.debt }\n when profit_earned is by_schedule { moves self.amount from self.slice.profitEarned to self.slice.debt }\n when profit_earned is at_disbursement { moves self.amount from self.slice.profitEarned to self.slice.debt }\n }\n action refund {\n from: paid, to: refunded, actor: { parent: payment }\n moves self.amount from self.slice.plan.profitIncome to self.slice.plan.borrower\n when profit_earned is on_payment { moves self.amount from self.slice.debt to self.slice.profitReceivable }\n when profit_earned is by_schedule { moves self.amount from self.slice.debt to self.slice.profitEarned }\n when profit_earned is at_disbursement { moves self.amount from self.slice.debt to self.slice.profitEarned }\n }\n }\n }\n}\n\ninstrument 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, approval: approval = programOperator, borrower: party = party(person)) {\n summary: \"Separate approved fine and evidenced recovery claims on an overdue installment.\"\n fields {\n plan: ref<on>, slice: ref<on.slice>, borrower: account of borrower, fine: money = fine, overdueAt: date = after(self.slice.dueAt, grace)\n receivable: account(self, claim), debt: account(borrower, claim, contra, \"debt\")\n }\n lifecycle { states: [proposed, assessed, collected, waived, refunded], initial: proposed }\n action create { requires unique \"late_assessment\" on [self.slice]; requires self.borrower == self.slice.plan.borrower; requires self.plan == self.slice.plan }\n action assess {\n from: proposed, to: assessed, due: { at: self.overdueAt }\n requires self.slice.principalReceivable.balance > \"0\"; requires approval by approval\n moves self.fine from self.debt to self.receivable\n }\n action collect {\n from: [assessed, collected], to: assessed, input { payment: ref<on.payment> }\n requires input.payment in [paid]\n requires input.payment.plan == self.plan\n 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: {} }]\n }\n action finish { from: assessed, to: collected, actor: { parent: current() }, requires self.receivable.balance == \"0\" }\n action cancel { from: proposed, to: waived }\n action waive { from: assessed, to: waived, moves self.receivable.balance from self.receivable to self.debt }\n action refund {\n from: [assessed, collected], to: assessed, input { payment: ref<on.payment> }\n requires input.payment in [paid, refunded]\n requires input.payment.plan == self.plan\n 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: {} }]\n }\n records {\n receipt: {\n fields { assessment: ref<parent>, payment: ref<on.payment>, amount: money = minimum(self.payment.held.balance, self.assessment.receivable.balance) }\n lifecycle { states: [paid, refunded], initial: paid }\n action create { 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; moves self.amount from self.assessment.receivable to self.assessment.debt }\n action refund { from: paid, to: refunded, actor: { parent: parent }, moves self.amount from fines_to to self.payment.plan.borrower; moves self.amount from self.assessment.debt to self.assessment.receivable }\n }\n\n cost: {\n fields {\n plan: ref<on>, assessment: ref<parent>, overdueAt: date = after(self.assessment.slice.dueAt, grace), evidence: text, evidencedCost: money, amount: money = minimum(self.evidencedCost, cap)\n receivable: account(self, claim), debt: account(borrower, claim, contra, \"debt\")\n }\n lifecycle { states: [proposed, assessed, collected, waived, refunded], initial: proposed }\n action create { requires unique \"recovery_assessment\" on [self.assessment]; requires self.plan == self.assessment.plan }\n action assess {\n from: proposed, to: assessed, due: { at: self.assessment.overdueAt }\n requires self.assessment.slice.principalReceivable.balance > \"0\"; requires approval by approval\n moves self.amount from self.debt to self.receivable\n }\n action collect {\n from: [assessed, collected], to: assessed, input { payment: ref<on.payment> }\n requires input.payment in [paid]\n requires input.payment.plan == self.plan\n 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: {} }]\n }\n action finish { from: assessed, to: collected, actor: { parent: current() }, requires self.receivable.balance == \"0\" }\n action cancel { from: proposed, to: waived }\n action waive { from: assessed, to: waived, moves self.receivable.balance from self.receivable to self.debt }\n action refund {\n from: [assessed, collected], to: assessed, input { payment: ref<on.payment> }\n requires input.payment in [paid, refunded]\n requires input.payment.plan == self.plan\n 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: {} }]\n }\n records {\n receipt: {\n fields { assessment: ref<parent>, payment: ref<on.payment>, amount: money = minimum(self.payment.held.balance, self.assessment.receivable.balance) }\n lifecycle { states: [paid, refunded], initial: paid }\n action create { 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; moves self.amount from self.assessment.receivable to self.assessment.debt }\n action refund { from: paid, to: refunded, actor: { parent: parent }, moves self.amount from costs_to to self.payment.plan.borrower; moves self.amount from self.assessment.debt to self.assessment.receivable }\n }\n }\n }\n }\n}\n\ninstrument limits(per_borrower: money, portfolio: money, borrower: party = party(person), active_plans: integer = 1) {\n summary: \"One borrower ceiling and one shared portfolio ceiling.\"\n fields { borrower: account of borrower, limit: money = per_borrower, activePlans: integer = active_plans }\n lifecycle { states: [pending, approved], initial: pending }\n action create { requires unique \"borrower_limit\" on [self.borrower] }\n action approve { from: pending, to: approved }\n records {\n portfolio: {\n fields { key: text = \"portfolio\", limit: money = portfolio }\n lifecycle { states: [pending, approved], initial: pending }\n action create { requires unique \"portfolio_limit\" on [self.key] }\n action approve { from: pending, to: approved }\n }\n }\n}\n\ninstrument credit_line(borrower: party, lender: party, limit: money, expires: date) {\n summary: \"A revolving facility; each draw is a separate immutable advance.\"\n fields { borrower: account of borrower, lender: account of lender, debt: account(borrower, claim, contra, \"debt\"), limit: money = limit, expiresAt: date = expires }\n lifecycle { states: [pending, active, suspended, closed], initial: pending }\n action create {}\n action activate { from: pending, to: active, actor: { party: lender } }\n action suspend { from: active, to: suspended, actor: { party: lender } }\n action resume { from: suspended, to: active, actor: { party: lender } }\n 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 }\n}\n\ninstrument advance(line: ref<financing.credit_line>, amount: money = runtime) {\n summary: \"A single facility draw with an immutable principal and full repayment.\"\n fields { line: ref<line>, amount: money = amount, principalReceivable: account(self, claim) }\n lifecycle { states: [pending, drawn, repaid, cancelled], initial: pending }\n action create { requires self.amount <= self.line.limit }\n invariants sum \"principalReceivable.balance\" of { instrument: all(financing.advance), reference: \"line\", anchor: self.line, states: [drawn], limit: 366 } <= self.line.limit\n action draw {\n from: pending, to: drawn\n requires self.line in [active]\n deadline: { at: self.line.expiresAt }\n moves self.amount from self.line.lender to self.line.borrower; moves self.amount from self.line.debt to self.principalReceivable\n }\n action repay { from: drawn, to: repaid, moves self.amount from self.line.borrower to self.line.lender; moves self.amount from self.principalReceivable to self.line.debt }\n action cancel { from: pending, to: cancelled }\n}\n"],
|
|
9
9
|
["insurance.hsx", "header insurance\n\ninstrument cover(holder: party, insurer: party, commission: percent = 0%, covers: ref, premium: money = runtime) {\n summary: \"Recurring cover with separate premium slices and commission clawback.\"\n fields { covers: ref<covers>, holder: account of holder, insurer: account of insurer, expiresAt: date }\n lifecycle { states: [pending, active, cancelled, expired], initial: pending }\n action create {}\n action activate { from: pending, to: active, actor: { party: insurer } }\n action cancel {\n from: active, to: cancelled\n invoke: [{ selection: { instrument: slice, reference: \"cover\", anchor: self.id, states: [paid], limit: 366 }, action: refund, input: {} }]\n }\n action expire { from: active, to: expired, actor: clock, due: { at: self.expiresAt } }\n records {\n slice: {\n summary: \"A whole premium period, collected once and refundable only before it starts.\"\n fields {\n cover: ref<parent>, covers: ref<covers>, premium: money = premium, startsAt: date, endsAt: date\n commission: money = rate(self.premium, commission)\n carrierNet: money = subtract(self.premium, self.commission)\n }\n lifecycle { states: [pending, paid, active, expired, refunded], initial: pending }\n action create {\n requires self.covers == self.cover.covers\n requires self.startsAt < self.endsAt\n requires unique \"premium_period\" on [self.cover, self.startsAt, self.endsAt]\n }\n action collect {\n from: pending, to: paid\n requires self.cover in [active]\n moves self.premium from holder to insurer\n moves self.commission from insurer to programOperator\n }\n action start { from: paid, to: active, actor: clock, due: { at: self.startsAt } }\n action expire { from: active, to: expired, actor: clock, due: { at: self.endsAt } }\n action refund {\n from: paid, to: refunded, deadline: { at: self.startsAt }\n moves self.commission from programOperator to insurer\n moves self.premium from insurer to holder\n }\n }\n }\n}\n\ninstrument claim(cover: ref<insurance.cover>, approved_by: approval) {\n summary: \"A carrier decision reserves one claim payment before settlement.\"\n fields { cover: ref<cover>, amount: money, expiresAt: date, evidence: text, reservation: text? }\n lifecycle { states: [submitted, approved, paid, denied, expired], initial: submitted }\n action create { requires self.cover in [active] }\n action approve {\n from: submitted, to: approved, actor: { party: approved_by }\n requires approval by approved_by\n deadline: { at: self.expiresAt }\n moves reserve self.amount from self.cover.insurer to self.cover.holder capture \"reservation\"\n }\n action pay { from: approved, to: paid, deadline: { at: self.expiresAt }, moves post self.reservation }\n action deny { from: submitted, to: denied, requires approval by approved_by for approve is \"declined\" }\n action expire { from: approved, to: expired, actor: clock, due: { at: self.expiresAt }, moves void self.reservation }\n}\n"],
|
|
10
10
|
["lending.hsx", "header lending\n\ninstrument round(borrower: party, capital: party, plan: ref<financing.installments> = object(financing.installments), months: integer(1, 366) = 3, profit: percent = 3%, minimum_ticket: money = 100 SAR, investor_cap: percent = 20%, commitments: ref<lending.commitment> = object(lending.commitment)) {\n summary: \"A threshold funding round; failed rounds return each original wallet contribution.\"\n fields {\n borrower: account of borrower, capital: account(capital, cash, \"capital\"), plan: ref<plan>, price: money, closesAt: date\n months: integer = months, profit: money = rate(self.price, profit)\n minimumTicket: money = minimum_ticket, maximumTicket: money = rate(self.price, investor_cap)\n held: account of self\n }\n lifecycle { states: [open, funded, failed], initial: open }\n action create { requires unique \"funding_plan\" on [self.plan]; requires self.months == self.plan.months; requires self.profit == self.plan.profit; requires self.price == self.plan.principal; requires self.borrower == self.plan.funds.payer }\n action close {\n from: open, to: funded\n requires sum \"amount\" of { instrument: commitments, reference: \"round\", anchor: self.id, states: [committed], limit: 366 } == self.price\n requires self.held.balance == self.price\n moves self.price from self.held to self.plan.capital\n invoke: [{ selection: { instrument: commitments, reference: \"round\", anchor: self.id, states: [committed], limit: 366 }, action: fund, input: {} }]\n }\n action fail {\n from: open, to: failed, actor: clock, due: { at: self.closesAt }\n requires self.held.balance < self.price\n invoke: [{ selection: { instrument: commitments, reference: \"round\", anchor: self.id, states: [committed], limit: 366 }, action: refund, input: {} }]\n }\n}\n\ninstrument commitment(round: ref<lending.round>, wallet: ref<wallet.balance>, investor: party = party(person)) {\n summary: \"One investor's original-wallet contribution, capped cumulatively within the round.\"\n fields { round: ref<round>, wallet: ref<wallet>, amount: money, loss: account(investor, claim, \"loss\") }\n lifecycle { states: [committed, funded, withdrawn, refunded], initial: committed }\n invariants sum \"amount\" of { instrument: all(lending.commitment), reference: \"round\", anchor: self.round, states: [committed, funded], limit: 366, where: { wallet: { field: self.wallet } } } <= self.round.maximumTicket\n invariants sum \"amount\" of { instrument: all(lending.commitment), reference: \"round\", anchor: self.round, states: [committed, funded], limit: 366 } <= self.round.price\n action create {\n requires self.round in [open]\n requires self.wallet in [active]\n requires self.amount >= self.round.minimumTicket\n deadline: { at: self.round.closesAt }\n moves self.amount from self.wallet.held to self.round.held\n }\n action fund { from: committed, to: funded, actor: { parent: round } }\n action withdraw {\n from: committed, to: withdrawn\n requires self.round in [open]\n deadline: { at: self.round.closesAt }\n moves self.amount from self.round.held to self.wallet.held\n }\n action refund {\n from: committed, to: refunded, actor: { parent: round }\n moves self.amount from self.round.held to self.wallet.held\n }\n}\n\ninstrument distribution(round: ref<lending.round>, receipt: ref, commitments: ref<lending.commitment> = object(lending.commitment), mode: enum(cash, loss) = cash, residual_to: party = programOperator, fee: percent = 1%, tax: percent = 15%) {\n summary: \"Frozen weighted shares, floored once, with residual assigned to the chosen party.\"\n fields {\n round: ref<round>, source: ref<receipt>, mode: enum(cash, loss) = mode\n principal: money?, profit: money?, grossTotal: money?, charge: money?, vat: money?, net: money?\n held: account of self, losses: account(self, claim), residualLoss: account(residual_to, claim, \"loss\")\n }\n lifecycle { states: [pending, cash_ready, loss_ready, distributed], initial: pending }\n action create { requires self.round in [funded]; requires self.source.plan == self.round.plan; requires unique \"settlement_consumption\" on [self.source.plan, self.source.identity] }\n action prepare_cash {\n from: pending, to: cash_ready\n requires self.mode == \"cash\"; requires self.source.kind == \"cash\"\n calculate: [\n { target: \"principal\", op: \"sum\", values: [{ field: self.source.principal }] }\n { target: \"profit\", op: \"sum\", values: [{ field: self.source.profit }] }\n { target: \"charge\", op: \"rate\", base: { field: self.profit }, bps: { literal: fee }, rounding: \"floor\" }\n { target: \"vat\", op: \"rate\", base: { field: self.charge }, bps: { literal: tax }, rounding: \"floor\" }\n { target: \"grossTotal\", op: \"sum\", values: [{ field: self.principal }, { field: self.profit }] }\n { target: \"net\", op: \"subtract\", base: { field: self.grossTotal }, subtract: [{ field: self.charge }, { field: self.vat }] }\n ]\n }\n action prepare_loss {\n from: pending, to: loss_ready\n requires self.mode == \"loss\"; requires self.source.kind == \"loss\"\n calculate: [{ target: \"net\", op: \"sum\", values: [{ field: self.source.loss }] }]\n }\n action distribute_cash {\n from: cash_ready, to: distributed\n requires sum \"weight\" of { instrument: share, reference: \"distribution\", anchor: self.id, states: [pending], limit: 366 } == self.round.price\n moves self.principal from self.source.capital to self.held; moves self.profit from self.source.profitIncome to self.held; moves self.charge from self.held to programOperator; moves self.vat from self.held to programTax\n invoke: [{ selection: { instrument: share, reference: \"distribution\", anchor: self.id, states: [pending], limit: 366 }, action: cash, input: {} }, { reference: self.id, action: residual_cash, input: {} }]\n }\n action residual_cash {\n from: distributed, to: distributed, actor: { parent: current() }\n moves self.held.balance from self.held to residual_to\n }\n action distribute_loss {\n from: loss_ready, to: distributed\n requires sum \"weight\" of { instrument: share, reference: \"distribution\", anchor: self.id, states: [pending], limit: 366 } == self.round.price\n moves self.net from self.source.lossAccount to self.losses\n invoke: [{ selection: { instrument: share, reference: \"distribution\", anchor: self.id, states: [pending], limit: 366 }, action: loss, input: {} }, { reference: self.id, action: residual_loss, input: {} }]\n }\n action residual_loss {\n from: distributed, to: distributed, actor: { parent: current() }\n moves self.losses.balance from self.losses to self.residualLoss\n }\n records {\n share: {\n fields {\n distribution: ref<parent>, commitment: ref<commitments>\n weight: money = self.commitment.amount\n gross: money = ratio(self.distribution.net, self.weight, self.distribution.round.price)\n }\n lifecycle { states: [pending, paid], initial: pending }\n action create { requires self.distribution in [cash_ready, loss_ready]; requires self.commitment in [funded]; requires unique \"distribution_weight\" on [self.distribution, self.commitment]; requires self.commitment.round == self.distribution.round }\n action cash {\n from: pending, to: paid, actor: { parent: parent }\n moves self.gross from self.distribution.held to self.commitment.wallet.held\n }\n action loss { from: pending, to: paid, actor: { parent: parent }, moves self.gross from self.distribution.losses to self.commitment.loss }\n }\n }\n}\n"],
|
|
11
11
|
["marketplace.hsx", "header marketplace\n\ninstrument listing(seller: party) {\n summary: \"A seller's offer and its asking price.\"\n fields { seller: account of seller, title: text, price: money }\n lifecycle { states: [draft, active, withdrawn, sold], initial: draft }\n action create { actor: { party: seller } }\n action publish { from: draft, to: active, actor: { party: seller } }\n action withdraw { from: active, to: withdrawn, actor: { party: seller } }\n action sell { from: active, to: sold, actor: { party: seller } }\n}\n\ninstrument order(listing: ref<marketplace.listing>, buyer: party) {\n summary: \"A buyer's order for one listing at its declared price.\"\n fields { listing: ref<listing>, buyer: account of buyer, price: money }\n lifecycle { states: [placed, fulfilled, cancelled], initial: placed }\n action create {\n actor: { party: buyer }\n requires self.listing in [active]\n requires self.price == self.listing.price\n }\n action fulfill { from: placed, to: fulfilled }\n action cancel { from: placed, to: cancelled, actor: { party: buyer } }\n}\n"],
|
package/src/version.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export const HSX_VERSION = "3.
|
|
1
|
+
export const HSX_VERSION = "3.1.0";
|
|
2
2
|
export const HSX_TARGET_UDL_VERSION = 3;
|
package/std/financing.hsx
CHANGED
|
@@ -112,22 +112,22 @@ instrument installments(months: integer(1, 366), profit: percent, disburse_to: e
|
|
|
112
112
|
action mature {
|
|
113
113
|
from: pending, to: due, actor: clock, due: { at: self.dueAt }
|
|
114
114
|
}
|
|
115
|
-
action
|
|
116
|
-
from: [pending, due
|
|
117
|
-
input { payment: ref<payment> }
|
|
118
|
-
requires input.payment.plan == self.plan
|
|
119
|
-
invoke: [{ instrument: principal_piece, action: create, input: { slice: { field: self.id }, payment: { field: input.payment }, limit: { field: self.principalReceivable.balance } } }]
|
|
120
|
-
}
|
|
121
|
-
action collect_profit {
|
|
122
|
-
from: [pending, due, paid], to: due, actor: { parent: payment }
|
|
115
|
+
action collect {
|
|
116
|
+
from: [pending, due], to: due, actor: { parent: payment }
|
|
123
117
|
input { payment: ref<payment> }
|
|
124
118
|
requires input.payment.plan == self.plan
|
|
125
|
-
when profit_earned is on_payment {
|
|
126
|
-
when profit_earned is by_schedule {
|
|
127
|
-
when profit_earned is at_disbursement {
|
|
119
|
+
when profit_earned is on_payment { calculate: [{ target: collectibleProfit, op: sum, values: [{ field: self.profitReceivable.balance }] }] }
|
|
120
|
+
when profit_earned is by_schedule { calculate: [{ target: collectibleProfit, op: sum, values: [{ field: self.profitEarned.balance }] }] }
|
|
121
|
+
when profit_earned is at_disbursement { calculate: [{ target: collectibleProfit, op: sum, values: [{ field: self.profitEarned.balance }] }] }
|
|
122
|
+
when apply is fines_profit_principal {
|
|
123
|
+
invoke: [{ instrument: profit_piece, action: create, input: { slice: { field: self.id }, payment: { field: input.payment }, limit: { field: self.collectibleProfit } } }, { instrument: principal_piece, action: create, input: { slice: { field: self.id }, payment: { field: input.payment }, limit: { field: self.principalReceivable.balance } } }]
|
|
124
|
+
}
|
|
125
|
+
when apply is principal_profit {
|
|
126
|
+
invoke: [{ instrument: principal_piece, action: create, input: { slice: { field: self.id }, payment: { field: input.payment }, limit: { field: self.principalReceivable.balance } } }, { instrument: profit_piece, action: create, input: { slice: { field: self.id }, payment: { field: input.payment }, limit: { field: self.collectibleProfit } } }]
|
|
127
|
+
}
|
|
128
128
|
}
|
|
129
129
|
action collect_pro_rata {
|
|
130
|
-
from: [pending, due
|
|
130
|
+
from: [pending, due], to: due, actor: { parent: payment }, input { payment: ref<payment> }
|
|
131
131
|
requires input.payment.plan == self.plan
|
|
132
132
|
when profit_earned is on_payment { calculate: [{ target: collectibleProfit, op: sum, values: [{ field: self.profitReceivable.balance }] }] }
|
|
133
133
|
when profit_earned is by_schedule { calculate: [{ target: collectibleProfit, op: sum, values: [{ field: self.profitEarned.balance }] }] }
|
|
@@ -190,14 +190,14 @@ instrument installments(months: integer(1, 366), profit: percent, disburse_to: e
|
|
|
190
190
|
requires self.plan in [active]; requires self.amount <= self.outstandingTotal
|
|
191
191
|
moves self.amount from self.plan.borrower to self.held
|
|
192
192
|
when apply is principal_profit {
|
|
193
|
-
invoke: [{ selection: { instrument: slice, reference: "plan", anchor: self.plan, states: [pending, due
|
|
193
|
+
invoke: [{ selection: { instrument: slice, reference: "plan", anchor: self.plan, states: [pending, due], limit: 366, order: [position] }, action: collect, input: { payment: { field: self.id } } }]
|
|
194
194
|
}
|
|
195
195
|
when apply is fines_profit_principal {
|
|
196
196
|
invoke: [{ selection: { instrument: all(financing.late_charge), reference: "plan", anchor: self.plan, states: [assessed, collected], limit: 366, order: [overdueAt] }, action: collect, 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: collect, input: { payment: { field: self.id } } }]
|
|
197
|
-
invoke: [{ selection: { instrument: slice, reference: "plan", anchor: self.plan, states: [pending, due
|
|
197
|
+
invoke: [{ selection: { instrument: slice, reference: "plan", anchor: self.plan, states: [pending, due], limit: 366, order: [position] }, action: collect, input: { payment: { field: self.id } } }]
|
|
198
198
|
}
|
|
199
199
|
when apply is pro_rata {
|
|
200
|
-
invoke: [{ selection: { instrument: slice, reference: "plan", anchor: self.plan, states: [pending, due
|
|
200
|
+
invoke: [{ selection: { instrument: slice, reference: "plan", anchor: self.plan, states: [pending, due], limit: 366, order: [position] }, action: collect_pro_rata, input: { payment: { field: self.id } } }]
|
|
201
201
|
}
|
|
202
202
|
invoke: [{ reference: self.id, action: record, input: {} }]
|
|
203
203
|
}
|