jaz-cli 2.3.0 → 2.6.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.
Files changed (33) hide show
  1. package/assets/skills/api/SKILL.md +35 -34
  2. package/assets/skills/api/references/errors.md +15 -7
  3. package/assets/skills/api/references/feature-glossary.md +2 -0
  4. package/assets/skills/api/references/field-map.md +3 -3
  5. package/assets/skills/conversion/SKILL.md +1 -1
  6. package/assets/skills/transaction-recipes/SKILL.md +158 -14
  7. package/assets/skills/transaction-recipes/references/asset-disposal.md +174 -0
  8. package/assets/skills/transaction-recipes/references/bad-debt-provision.md +145 -0
  9. package/assets/skills/transaction-recipes/references/building-blocks.md +25 -2
  10. package/assets/skills/transaction-recipes/references/capital-wip.md +167 -0
  11. package/assets/skills/transaction-recipes/references/dividend.md +111 -0
  12. package/assets/skills/transaction-recipes/references/employee-accruals.md +154 -0
  13. package/assets/skills/transaction-recipes/references/fixed-deposit.md +164 -0
  14. package/assets/skills/transaction-recipes/references/fx-revaluation.md +135 -0
  15. package/assets/skills/transaction-recipes/references/hire-purchase.md +190 -0
  16. package/assets/skills/transaction-recipes/references/intercompany.md +150 -0
  17. package/assets/skills/transaction-recipes/references/provisions.md +142 -0
  18. package/dist/calc/amortization.js +122 -0
  19. package/dist/calc/asset-disposal.js +151 -0
  20. package/dist/calc/blueprint.js +46 -0
  21. package/dist/calc/depreciation.js +200 -0
  22. package/dist/calc/ecl.js +101 -0
  23. package/dist/calc/fixed-deposit.js +169 -0
  24. package/dist/calc/format.js +494 -0
  25. package/dist/calc/fx-reval.js +93 -0
  26. package/dist/calc/lease.js +146 -0
  27. package/dist/calc/loan.js +107 -0
  28. package/dist/calc/provision.js +128 -0
  29. package/dist/calc/types.js +21 -0
  30. package/dist/calc/validate.js +48 -0
  31. package/dist/commands/calc.js +252 -0
  32. package/dist/index.js +2 -0
  33. package/package.json +3 -2
@@ -0,0 +1,146 @@
1
+ /**
2
+ * IFRS 16 lease calculator.
3
+ *
4
+ * Compliance references:
5
+ * - IFRS 16.26: Initial measurement — PV of lease payments
6
+ * - IFRS 16.36-37: Subsequent measurement — effective interest method
7
+ * - IFRS 16.31-32: ROU depreciation — straight-line over lease term
8
+ *
9
+ * Uses `financial` package for PV calculation.
10
+ * Final period penny adjustment closes lease liability to exactly zero.
11
+ */
12
+ import { pv } from 'financial';
13
+ import { round2, addMonths } from './types.js';
14
+ import { validatePositive, validatePositiveInteger, validateDateFormat, validateRate } from './validate.js';
15
+ import { journalStep, fixedAssetStep, fmtCapsuleAmount, fmtAmt } from './blueprint.js';
16
+ export function calculateLease(inputs) {
17
+ const { monthlyPayment, termMonths, annualRate, usefulLifeMonths, startDate, currency } = inputs;
18
+ validatePositive(monthlyPayment, 'Monthly payment');
19
+ validateRate(annualRate, 'Annual rate (IBR)');
20
+ validatePositiveInteger(termMonths, 'Term (months)');
21
+ if (usefulLifeMonths !== undefined)
22
+ validatePositiveInteger(usefulLifeMonths, 'Useful life (months)');
23
+ validateDateFormat(startDate);
24
+ const isHirePurchase = usefulLifeMonths !== undefined && usefulLifeMonths > termMonths;
25
+ const monthlyRate = annualRate / 100 / 12;
26
+ // PV of an ordinary annuity (payments at end of period)
27
+ // pv() returns negative, negate for positive value
28
+ const presentValue = round2(-pv(monthlyRate, termMonths, monthlyPayment));
29
+ // ROU depreciation: straight-line over lease term (IFRS 16.31-32)
30
+ // For hire purchase (ownership transfers): depreciate over useful life, not lease term
31
+ const depreciationMonths = isHirePurchase ? usefulLifeMonths : termMonths;
32
+ const monthlyRouDepreciation = round2(presentValue / depreciationMonths);
33
+ const initialJournal = {
34
+ description: 'Initial recognition — IFRS 16 lease',
35
+ lines: [
36
+ { account: 'Right-of-Use Asset', debit: presentValue, credit: 0 },
37
+ { account: 'Lease Liability', debit: 0, credit: presentValue },
38
+ ],
39
+ };
40
+ // Liability unwinding schedule (effective interest method, IFRS 16.36-37)
41
+ const schedule = [];
42
+ let liability = presentValue;
43
+ let totalInterest = 0;
44
+ let totalPrincipal = 0;
45
+ for (let i = 1; i <= termMonths; i++) {
46
+ const openingBalance = round2(liability);
47
+ const isFinal = i === termMonths;
48
+ let interest;
49
+ let principalPortion;
50
+ let periodPayment;
51
+ if (isFinal) {
52
+ // Final period: close liability to exactly zero
53
+ interest = round2(openingBalance * monthlyRate);
54
+ principalPortion = openingBalance;
55
+ periodPayment = round2(principalPortion + interest);
56
+ }
57
+ else {
58
+ interest = round2(openingBalance * monthlyRate);
59
+ principalPortion = round2(monthlyPayment - interest);
60
+ periodPayment = monthlyPayment;
61
+ }
62
+ liability = round2(openingBalance - principalPortion);
63
+ totalInterest = round2(totalInterest + interest);
64
+ totalPrincipal = round2(totalPrincipal + principalPortion);
65
+ const date = startDate ? addMonths(startDate, i) : null;
66
+ const journal = {
67
+ description: `Lease payment — Month ${i} of ${termMonths}`,
68
+ lines: [
69
+ { account: 'Lease Liability', debit: principalPortion, credit: 0 },
70
+ { account: 'Interest Expense — Leases', debit: interest, credit: 0 },
71
+ { account: 'Cash / Bank Account', debit: 0, credit: periodPayment },
72
+ ],
73
+ };
74
+ schedule.push({
75
+ period: i,
76
+ date,
77
+ openingBalance,
78
+ payment: periodPayment,
79
+ interest,
80
+ principal: principalPortion,
81
+ closingBalance: liability,
82
+ journal,
83
+ });
84
+ }
85
+ // ROU depreciation total (final month absorbs rounding)
86
+ const totalDepreciation = round2(monthlyRouDepreciation * (termMonths - 1) +
87
+ (presentValue - monthlyRouDepreciation * (termMonths - 1)));
88
+ // Build blueprint for agent execution
89
+ let blueprint = null;
90
+ if (startDate) {
91
+ const depNote = isHirePurchase
92
+ ? `Register Right-of-Use Asset in Fixed Asset register: cost ${presentValue}, salvage 0, life ${depreciationMonths} months (straight-line over useful life, not lease term). Jaz will auto-post monthly depreciation of ${monthlyRouDepreciation}.`
93
+ : `Register Right-of-Use Asset in Fixed Asset register: cost ${presentValue}, salvage 0, life ${termMonths} months (straight-line). Jaz will auto-post monthly depreciation of ${monthlyRouDepreciation}.`;
94
+ const steps = [
95
+ journalStep(1, initialJournal.description, startDate, initialJournal.lines),
96
+ fixedAssetStep(2, depNote, startDate),
97
+ ...schedule.map((row, idx) => journalStep(idx + 3, row.journal.description, row.date, row.journal.lines)),
98
+ ];
99
+ const capsuleType = isHirePurchase ? 'Hire Purchase' : 'Lease Accounting';
100
+ const capsuleName = isHirePurchase
101
+ ? `Hire Purchase — ${fmtCapsuleAmount(monthlyPayment, currency)}/month — ${termMonths} months — useful life ${depreciationMonths} months`
102
+ : `IFRS 16 Lease — ${fmtCapsuleAmount(monthlyPayment, currency)}/month — ${termMonths} months`;
103
+ const c = currency ?? undefined;
104
+ const workingsLines = [
105
+ `${isHirePurchase ? 'Hire Purchase' : 'IFRS 16 Lease'} Workings`,
106
+ `Monthly payment: ${fmtAmt(monthlyPayment, c)} | IBR: ${annualRate}% p.a. (${round2(monthlyRate * 100)}% monthly)`,
107
+ `Lease term: ${termMonths} months | PV of payments: ${fmtAmt(presentValue, c)} (IFRS 16.26)`,
108
+ ];
109
+ if (isHirePurchase) {
110
+ workingsLines.push(`Useful life: ${usefulLifeMonths} months | ROU depreciation: ${fmtAmt(monthlyRouDepreciation, c)}/month over ${depreciationMonths} months`);
111
+ }
112
+ else {
113
+ workingsLines.push(`ROU depreciation: ${fmtAmt(monthlyRouDepreciation, c)}/month (SL over ${termMonths} months)`);
114
+ }
115
+ workingsLines.push(`Total cash payments: ${fmtAmt(round2(totalInterest + totalPrincipal), c)} | Total interest: ${fmtAmt(totalInterest, c)}`, `Method: Effective interest (IFRS 16.36-37), ROU straight-line (IFRS 16.31-32)`, `Rounding: 2dp per period, final period closes liability to $0.00`);
116
+ blueprint = {
117
+ capsuleType,
118
+ capsuleName,
119
+ capsuleDescription: workingsLines.join('\n'),
120
+ tags: [capsuleType],
121
+ customFields: isHirePurchase ? { 'HP Agreement #': null } : { 'Lease Contract #': null },
122
+ steps,
123
+ };
124
+ }
125
+ return {
126
+ type: 'lease',
127
+ currency: currency ?? null,
128
+ inputs: {
129
+ monthlyPayment,
130
+ termMonths,
131
+ annualRate,
132
+ usefulLifeMonths: usefulLifeMonths ?? null,
133
+ startDate: startDate ?? null,
134
+ },
135
+ presentValue,
136
+ monthlyRouDepreciation,
137
+ depreciationMonths,
138
+ isHirePurchase,
139
+ totalCashPayments: round2(totalInterest + totalPrincipal),
140
+ totalInterest,
141
+ totalDepreciation,
142
+ initialJournal,
143
+ schedule,
144
+ blueprint,
145
+ };
146
+ }
@@ -0,0 +1,107 @@
1
+ /**
2
+ * Loan amortization calculator.
3
+ * Uses the `financial` package for PMT calculation.
4
+ * IFRS-compliant rounding: 2 decimals per period, final period closes to zero.
5
+ */
6
+ import { pmt } from 'financial';
7
+ import { round2, addMonths } from './types.js';
8
+ import { validatePositive, validatePositiveInteger, validateDateFormat, validateRate } from './validate.js';
9
+ import { journalStep, cashInStep, fmtCapsuleAmount, fmtAmt } from './blueprint.js';
10
+ export function calculateLoan(inputs) {
11
+ const { principal, annualRate, termMonths, startDate, currency } = inputs;
12
+ validatePositive(principal, 'Principal');
13
+ validateRate(annualRate, 'Annual rate');
14
+ validatePositiveInteger(termMonths, 'Term (months)');
15
+ validateDateFormat(startDate);
16
+ const monthlyRate = annualRate / 100 / 12;
17
+ // PMT returns negative (cash outflow convention), negate for positive value
18
+ const payment = round2(-pmt(monthlyRate, termMonths, principal));
19
+ const schedule = [];
20
+ let balance = principal;
21
+ let totalInterest = 0;
22
+ let totalPrincipal = 0;
23
+ for (let i = 1; i <= termMonths; i++) {
24
+ const openingBalance = round2(balance);
25
+ const isFinal = i === termMonths;
26
+ let interest;
27
+ let principalPortion;
28
+ let periodPayment;
29
+ if (isFinal) {
30
+ // Final period: close balance to exactly zero
31
+ interest = round2(openingBalance * monthlyRate);
32
+ principalPortion = openingBalance;
33
+ periodPayment = round2(principalPortion + interest);
34
+ }
35
+ else {
36
+ interest = round2(openingBalance * monthlyRate);
37
+ principalPortion = round2(payment - interest);
38
+ periodPayment = payment;
39
+ }
40
+ balance = round2(openingBalance - principalPortion);
41
+ totalInterest = round2(totalInterest + interest);
42
+ totalPrincipal = round2(totalPrincipal + principalPortion);
43
+ const date = startDate ? addMonths(startDate, i) : null;
44
+ const journal = {
45
+ description: `Loan payment — Month ${i} of ${termMonths}`,
46
+ lines: [
47
+ { account: 'Loan Payable', debit: principalPortion, credit: 0 },
48
+ { account: 'Interest Expense', debit: interest, credit: 0 },
49
+ { account: 'Cash / Bank Account', debit: 0, credit: periodPayment },
50
+ ],
51
+ };
52
+ schedule.push({
53
+ period: i,
54
+ date,
55
+ openingBalance,
56
+ payment: periodPayment,
57
+ interest,
58
+ principal: principalPortion,
59
+ closingBalance: balance,
60
+ journal,
61
+ });
62
+ }
63
+ // Build blueprint for agent execution
64
+ let blueprint = null;
65
+ if (startDate) {
66
+ const steps = [
67
+ cashInStep(1, 'Record loan proceeds received from bank', startDate, [
68
+ { account: 'Cash / Bank Account', debit: principal, credit: 0 },
69
+ { account: 'Loan Payable', debit: 0, credit: principal },
70
+ ]),
71
+ ...schedule.map((row, idx) => journalStep(idx + 2, row.journal.description, row.date, row.journal.lines)),
72
+ ];
73
+ const c = currency ?? undefined;
74
+ const workings = [
75
+ `Loan Amortization Workings`,
76
+ `Principal: ${fmtAmt(principal, c)} | Rate: ${annualRate}% p.a. (${round2(monthlyRate * 100)}% monthly)`,
77
+ `Term: ${termMonths} months | Monthly payment: ${fmtAmt(payment, c)}`,
78
+ `Total payments: ${fmtAmt(round2(totalInterest + totalPrincipal), c)} | Total interest: ${fmtAmt(totalInterest, c)}`,
79
+ `Method: PMT formula, constant payment amortization`,
80
+ `Rounding: 2dp per period, final period closes balance to $0.00`,
81
+ ].join('\n');
82
+ blueprint = {
83
+ capsuleType: 'Loan Repayment',
84
+ capsuleName: `Bank Loan — ${fmtCapsuleAmount(principal, currency)} — ${annualRate}% — ${termMonths} months`,
85
+ capsuleDescription: workings,
86
+ tags: ['Bank Loan'],
87
+ customFields: { 'Loan Reference': null },
88
+ steps,
89
+ };
90
+ }
91
+ return {
92
+ type: 'loan',
93
+ currency: currency ?? null,
94
+ inputs: {
95
+ principal,
96
+ annualRate,
97
+ termMonths,
98
+ startDate: startDate ?? null,
99
+ },
100
+ monthlyPayment: payment,
101
+ totalPayments: round2(totalInterest + totalPrincipal),
102
+ totalInterest,
103
+ totalPrincipal,
104
+ schedule,
105
+ blueprint,
106
+ };
107
+ }
@@ -0,0 +1,128 @@
1
+ /**
2
+ * IAS 37 Provision calculator — PV measurement + discount unwinding.
3
+ *
4
+ * Calculates the present value of a future obligation and generates
5
+ * the periodic unwinding schedule (Dr Finance Cost / Cr Provision).
6
+ *
7
+ * Compliance references:
8
+ * - IAS 37.36: Best estimate of expenditure required to settle
9
+ * - IAS 37.45: PV when effect of time value is material
10
+ * - IAS 37.60: Unwinding of discount = finance cost
11
+ *
12
+ * Uses `financial` package for PV calculation (same as lease calculator).
13
+ * Final period penny adjustment closes provision to the full nominal amount.
14
+ *
15
+ * Use cases:
16
+ * - Warranty obligations
17
+ * - Legal claims / litigation
18
+ * - Decommissioning / restoration
19
+ * - Restructuring provisions
20
+ * - Onerous contracts
21
+ */
22
+ import { pv } from 'financial';
23
+ import { round2, addMonths } from './types.js';
24
+ import { validatePositive, validatePositiveInteger, validateDateFormat, validateRate } from './validate.js';
25
+ import { journalStep, cashOutStep, fmtCapsuleAmount, fmtAmt } from './blueprint.js';
26
+ export function calculateProvision(inputs) {
27
+ const { amount, annualRate, termMonths, startDate, currency } = inputs;
28
+ validatePositive(amount, 'Estimated future outflow');
29
+ validateRate(annualRate, 'Discount rate');
30
+ validatePositiveInteger(termMonths, 'Term (months)');
31
+ validateDateFormat(startDate);
32
+ const monthlyRate = annualRate / 100 / 12;
33
+ // PV of a single future cash flow (IAS 37.45)
34
+ // pv(rate, nper, pmt, fv) — pmt=0, fv=amount → returns negative, negate
35
+ const presentValue = round2(-pv(monthlyRate, termMonths, 0, amount));
36
+ const totalUnwinding = round2(amount - presentValue);
37
+ // Initial recognition journal (Dr Expense / Cr Provision at PV)
38
+ const initialJournal = {
39
+ description: `Initial provision recognition at PV (IAS 37)`,
40
+ lines: [
41
+ { account: 'Provision Expense', debit: presentValue, credit: 0 },
42
+ { account: 'Provision for Obligations', debit: 0, credit: presentValue },
43
+ ],
44
+ };
45
+ // Unwinding schedule (effective interest method, IAS 37.60)
46
+ const schedule = [];
47
+ let provisionBalance = presentValue;
48
+ let totalInterest = 0;
49
+ for (let i = 1; i <= termMonths; i++) {
50
+ const openingBalance = round2(provisionBalance);
51
+ const isFinal = i === termMonths;
52
+ let interest;
53
+ if (isFinal) {
54
+ // Final period: close to nominal amount exactly
55
+ interest = round2(amount - openingBalance);
56
+ }
57
+ else {
58
+ interest = round2(openingBalance * monthlyRate);
59
+ }
60
+ provisionBalance = round2(openingBalance + interest);
61
+ totalInterest = round2(totalInterest + interest);
62
+ const date = startDate ? addMonths(startDate, i) : null;
63
+ const journal = {
64
+ description: `Provision unwinding — Month ${i} of ${termMonths}`,
65
+ lines: [
66
+ { account: 'Finance Cost — Unwinding', debit: interest, credit: 0 },
67
+ { account: 'Provision for Obligations', debit: 0, credit: interest },
68
+ ],
69
+ };
70
+ schedule.push({
71
+ period: i,
72
+ date,
73
+ openingBalance,
74
+ payment: 0, // no cash movement until settlement
75
+ interest,
76
+ principal: 0, // reuse ScheduleRow — principal not applicable
77
+ closingBalance: provisionBalance,
78
+ journal,
79
+ });
80
+ }
81
+ // Build blueprint for agent execution
82
+ let blueprint = null;
83
+ if (startDate) {
84
+ const settlementDate = addMonths(startDate, termMonths);
85
+ const steps = [
86
+ journalStep(1, initialJournal.description, startDate, initialJournal.lines),
87
+ ...schedule.map((row, idx) => journalStep(idx + 2, row.journal.description, row.date, row.journal.lines)),
88
+ cashOutStep(termMonths + 2, 'Settlement — pay the obligation', settlementDate, [
89
+ { account: 'Provision for Obligations', debit: amount, credit: 0 },
90
+ { account: 'Cash / Bank Account', debit: 0, credit: amount },
91
+ ]),
92
+ ];
93
+ const c = currency ?? undefined;
94
+ const workings = [
95
+ `IAS 37 Provision Workings`,
96
+ `Nominal obligation: ${fmtAmt(amount, c)} | Discount rate: ${annualRate}% p.a. (${round2(monthlyRate * 100)}% monthly)`,
97
+ `Term to settlement: ${termMonths} months | PV at recognition: ${fmtAmt(presentValue, c)}`,
98
+ `Total unwinding (finance cost): ${fmtAmt(totalUnwinding, c)}`,
99
+ `Method: PV of single future outflow (IAS 37.45), unwinding via effective interest (IAS 37.60)`,
100
+ `Settlement: ${fmtAmt(amount, c)} cash out on ${settlementDate}`,
101
+ `Rounding: 2dp per period, final period closes to nominal amount`,
102
+ ].join('\n');
103
+ blueprint = {
104
+ capsuleType: 'Provisions',
105
+ capsuleName: `Provision — ${fmtCapsuleAmount(amount, currency)} — ${annualRate}% — ${termMonths} months`,
106
+ capsuleDescription: workings,
107
+ tags: ['Provision', 'IAS 37'],
108
+ customFields: { 'Obligation Type': null, 'Expected Settlement Date': null },
109
+ steps,
110
+ };
111
+ }
112
+ return {
113
+ type: 'provision',
114
+ currency: currency ?? null,
115
+ inputs: {
116
+ amount,
117
+ annualRate,
118
+ termMonths,
119
+ startDate: startDate ?? null,
120
+ },
121
+ presentValue,
122
+ nominalAmount: amount,
123
+ totalUnwinding,
124
+ initialJournal,
125
+ schedule,
126
+ blueprint,
127
+ };
128
+ }
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Shared types for jaz calc financial calculators.
3
+ */
4
+ /**
5
+ * Round to 2 decimal places (cents).
6
+ *
7
+ * Uses Math.round() (asymmetric, rounds 0.5 away from zero). This is the
8
+ * standard JavaScript rounding and works correctly for accounting amounts
9
+ * up to ~$90 trillion (Number.MAX_SAFE_INTEGER / 100). Final-period
10
+ * corrections in each calculator absorb any cumulative drift, ensuring
11
+ * balances always close to exactly $0.00.
12
+ */
13
+ export function round2(n) {
14
+ return Math.round(n * 100) / 100;
15
+ }
16
+ /** Advance a date by N months. Returns YYYY-MM-DD string. */
17
+ export function addMonths(dateStr, months) {
18
+ const d = new Date(dateStr + 'T00:00:00');
19
+ d.setMonth(d.getMonth() + months);
20
+ return d.toISOString().slice(0, 10);
21
+ }
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Input validation for jaz calc financial calculators.
3
+ * Throws CalcValidationError with user-friendly messages.
4
+ */
5
+ export class CalcValidationError extends Error {
6
+ constructor(message) {
7
+ super(message);
8
+ this.name = 'CalcValidationError';
9
+ }
10
+ }
11
+ export function validatePositive(value, name) {
12
+ if (!Number.isFinite(value) || value <= 0) {
13
+ throw new CalcValidationError(`${name} must be a positive number (got ${value})`);
14
+ }
15
+ }
16
+ export function validateNonNegative(value, name) {
17
+ if (!Number.isFinite(value) || value < 0) {
18
+ throw new CalcValidationError(`${name} must be zero or positive (got ${value})`);
19
+ }
20
+ }
21
+ export function validatePositiveInteger(value, name) {
22
+ if (!Number.isInteger(value) || value < 1) {
23
+ throw new CalcValidationError(`${name} must be a positive integer (got ${value})`);
24
+ }
25
+ }
26
+ export function validateSalvageLessThanCost(salvage, cost) {
27
+ if (salvage >= cost) {
28
+ throw new CalcValidationError(`Salvage value (${salvage}) must be less than cost (${cost})`);
29
+ }
30
+ }
31
+ export function validateDateFormat(date) {
32
+ if (!date)
33
+ return;
34
+ if (!/^\d{4}-\d{2}-\d{2}$/.test(date)) {
35
+ throw new CalcValidationError(`Date must be YYYY-MM-DD format (got "${date}")`);
36
+ }
37
+ const d = new Date(date + 'T00:00:00');
38
+ if (isNaN(d.getTime())) {
39
+ throw new CalcValidationError(`Invalid date: "${date}"`);
40
+ }
41
+ }
42
+ export function validateRate(rate, name = 'Rate') {
43
+ validateNonNegative(rate, name);
44
+ if (rate > 100) {
45
+ // Warning only — rates >100% are rare but legal (e.g. hyperinflation)
46
+ process.stderr.write(`Warning: ${name} is ${rate}% — are you sure this isn't a decimal? (e.g. 6 for 6%, not 0.06)\n`);
47
+ }
48
+ }