@trajectonomics/tax 0.0.0-stage → 0.1.0-alpha.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/LICENSE +21 -0
- package/README.md +48 -2
- package/dist/clamp-BKG7kjVf.cjs +509 -0
- package/dist/clamp-BKG7kjVf.cjs.map +1 -0
- package/dist/clamp-Bse4u0H-.mjs +426 -0
- package/dist/clamp-Bse4u0H-.mjs.map +1 -0
- package/dist/index.cjs +27 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +102 -0
- package/dist/index.d.mts +102 -0
- package/dist/index.mjs +15 -0
- package/dist/index.mjs.map +1 -0
- package/dist/th/index.cjs +1480 -0
- package/dist/th/index.cjs.map +1 -0
- package/dist/th/index.d.cts +636 -0
- package/dist/th/index.d.mts +636 -0
- package/dist/th/index.mjs +1473 -0
- package/dist/th/index.mjs.map +1 -0
- package/dist/types-798sLpt6.d.cts +170 -0
- package/dist/types-798sLpt6.d.mts +170 -0
- package/dist/us/index.cjs +2921 -0
- package/dist/us/index.cjs.map +1 -0
- package/dist/us/index.d.cts +649 -0
- package/dist/us/index.d.mts +649 -0
- package/dist/us/index.mjs +2897 -0
- package/dist/us/index.mjs.map +1 -0
- package/package.json +78 -5
|
@@ -0,0 +1,426 @@
|
|
|
1
|
+
// src/core/money/errors.ts
|
|
2
|
+
/**
|
|
3
|
+
* Error thrown when input values violate mathematical boundaries or statutory constraints.
|
|
4
|
+
* Exposes field, raw value, and validation reason for transparency and structured error reporting.
|
|
5
|
+
*/
|
|
6
|
+
var TaxValidationError = class TaxValidationError extends Error {
|
|
7
|
+
field;
|
|
8
|
+
value;
|
|
9
|
+
reason;
|
|
10
|
+
constructor(field, value, reason) {
|
|
11
|
+
super(`Tax validation error on field '${field}': ${reason} (received: ${String(value)})`);
|
|
12
|
+
this.name = "TaxValidationError";
|
|
13
|
+
this.field = field;
|
|
14
|
+
this.value = value;
|
|
15
|
+
this.reason = reason;
|
|
16
|
+
Object.setPrototypeOf(this, TaxValidationError.prototype);
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
// src/core/money/currency.ts
|
|
20
|
+
/**
|
|
21
|
+
* Minor-unit precision (fractional decimal digits) per supported currency.
|
|
22
|
+
*
|
|
23
|
+
* Units: THB 2 (1 baht = 100 satang), USD 2 (1 dollar = 100 cents).
|
|
24
|
+
* Internal to the money module; not part of the public package API.
|
|
25
|
+
*/
|
|
26
|
+
const CURRENCY_PRECISION = {
|
|
27
|
+
THB: 2,
|
|
28
|
+
USD: 2
|
|
29
|
+
};
|
|
30
|
+
// src/core/money/validate.ts
|
|
31
|
+
/**
|
|
32
|
+
* Validates a monetary input against numerical boundaries (NaN, Infinity, negative).
|
|
33
|
+
*
|
|
34
|
+
* @param value Number to validate.
|
|
35
|
+
* @param field Diagnostic field name for error reporting.
|
|
36
|
+
* @param options Validation options.
|
|
37
|
+
* @throws {TaxValidationError} if value is NaN, non-finite, or negative without allowNegative.
|
|
38
|
+
*/
|
|
39
|
+
function validateMonetaryAmount(value, field, options) {
|
|
40
|
+
if (Number.isNaN(value)) throw new TaxValidationError(field, value, "Value cannot be NaN");
|
|
41
|
+
if (!Number.isFinite(value)) throw new TaxValidationError(field, value, "Value must be a finite number");
|
|
42
|
+
if (!options?.allowNegative && value < 0) throw new TaxValidationError(field, value, "Value cannot be negative");
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Validates a statutory percentage rate (e.g. 0.05 for 5%).
|
|
46
|
+
*
|
|
47
|
+
* @param rate Rate ratio between 0 and 1.
|
|
48
|
+
* @param field Diagnostic field name.
|
|
49
|
+
* @throws {TaxValidationError} if rate is NaN, non-finite, or outside [0, 1].
|
|
50
|
+
*/
|
|
51
|
+
function validateRate(rate, field) {
|
|
52
|
+
if (Number.isNaN(rate) || !Number.isFinite(rate)) throw new TaxValidationError(field, rate, "Rate must be a finite number");
|
|
53
|
+
if (rate < 0 || rate > 1) throw new TaxValidationError(field, rate, "Statutory rate ratio must be between 0 and 1 (inclusive)");
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Validates that an amount is an exact integer minor unit.
|
|
57
|
+
*
|
|
58
|
+
* @param minorUnits Integer minor units.
|
|
59
|
+
* @param field Diagnostic field name.
|
|
60
|
+
* @param options Validation options.
|
|
61
|
+
* @throws {TaxValidationError} if not a safe integer.
|
|
62
|
+
*/
|
|
63
|
+
function validateIntegerMinorUnits(minorUnits, field, options) {
|
|
64
|
+
validateMonetaryAmount(minorUnits, field, options);
|
|
65
|
+
if (!Number.isSafeInteger(minorUnits)) throw new TaxValidationError(field, minorUnits, "Minor units amount must be a safe integer");
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Validates a statutory count (children, years of service, holding years).
|
|
69
|
+
*
|
|
70
|
+
* Purpose: Guards integer counts that later multiply money, rejecting fractions and unsafe values.
|
|
71
|
+
* Math: Requires `Number.isSafeInteger(value)` and `value >= min`.
|
|
72
|
+
* Units: Dimensionless count (not currency).
|
|
73
|
+
* Throws: TaxValidationError if value is not a safe integer or is below `min`.
|
|
74
|
+
*
|
|
75
|
+
* @param value Count to validate.
|
|
76
|
+
* @param field Diagnostic field name.
|
|
77
|
+
* @param min Inclusive lower bound (defaults to 0).
|
|
78
|
+
* @statute Jurisdiction-neutral input bound (AGENTS rule 8); no statutory citation.
|
|
79
|
+
*/
|
|
80
|
+
function validateSafeIntegerCount(value, field, min = 0) {
|
|
81
|
+
if (!Number.isSafeInteger(value)) throw new TaxValidationError(field, value, "Count must be a safe integer");
|
|
82
|
+
if (value < min) throw new TaxValidationError(field, value, `Count must be at least ${min}`);
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Multiplies two integers and fails loudly if the product leaves the safe integer range.
|
|
86
|
+
*
|
|
87
|
+
* Purpose: Prevents silent precision loss when scaling minor units by a count (years, days).
|
|
88
|
+
* Math: `a * b`, requiring `Number.isSafeInteger(a * b)`.
|
|
89
|
+
* Units: Typically integer minor units times a dimensionless count; result in minor units.
|
|
90
|
+
* Throws: TaxValidationError (reported against `field`) if the product is not a safe integer.
|
|
91
|
+
*
|
|
92
|
+
* @param a Integer factor (e.g. minor units).
|
|
93
|
+
* @param b Integer factor (e.g. a validated count).
|
|
94
|
+
* @param field Diagnostic field name of the count or amount being scaled.
|
|
95
|
+
* @statute Jurisdiction-neutral input bound (AGENTS rule 8); no statutory citation.
|
|
96
|
+
*/
|
|
97
|
+
function multiplySafeInteger(a, b, field) {
|
|
98
|
+
const product = a * b;
|
|
99
|
+
if (!Number.isSafeInteger(product)) throw new TaxValidationError(field, b, "Scaled amount exceeds the safe integer range");
|
|
100
|
+
return product + 0;
|
|
101
|
+
}
|
|
102
|
+
// src/core/money/minor-units.ts
|
|
103
|
+
function scaleDecimalString(abs, precision) {
|
|
104
|
+
const factor = 10 ** precision;
|
|
105
|
+
if (String(abs).includes("e")) return abs * factor;
|
|
106
|
+
const parts = String(abs).split(".");
|
|
107
|
+
const intPart = parts[0] ?? "0";
|
|
108
|
+
const decPart = (parts[1] ?? "").padEnd(precision, "0");
|
|
109
|
+
const whole = intPart + decPart.slice(0, precision);
|
|
110
|
+
const fraction = decPart.slice(precision);
|
|
111
|
+
return Number(whole + (fraction.length > 0 ? `.${fraction}` : ""));
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Converts a major-unit monetary amount to integer minor units (satang/cent).
|
|
115
|
+
*
|
|
116
|
+
* Math: Decimal string expansion and half-away-from-zero rounding to eliminate IEEE 754 float drift.
|
|
117
|
+
* Units: Input is major currency unit (e.g. THB, USD); output is integer minor units (e.g. Satang, Cents).
|
|
118
|
+
*
|
|
119
|
+
* @param amount Amount in major units.
|
|
120
|
+
* @param currency Supported statutory currency (THB or USD).
|
|
121
|
+
* @param field Diagnostic field name.
|
|
122
|
+
* @param options Validation options (e.g. allowNegative for refund balances).
|
|
123
|
+
* @returns Integer minor units.
|
|
124
|
+
* @throws {TaxValidationError} if amount is NaN, infinite, or results in a non-safe integer.
|
|
125
|
+
* @example toMinorUnits(1.14, "USD") // 114 (114 cents)
|
|
126
|
+
*/
|
|
127
|
+
function toMinorUnits(amount, currency, field = "amount", options) {
|
|
128
|
+
validateMonetaryAmount(amount, field, options);
|
|
129
|
+
const precision = CURRENCY_PRECISION[currency];
|
|
130
|
+
const sign = amount < 0 ? -1 : 1;
|
|
131
|
+
const scaled = scaleDecimalString(Math.abs(amount), precision);
|
|
132
|
+
const result = sign * Math.round(scaled) + 0;
|
|
133
|
+
if (!Number.isSafeInteger(result)) throw new TaxValidationError(field, amount, "Conversion to minor units failed or exceeded safe integer bounds");
|
|
134
|
+
return result;
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Converts an integer minor unit amount back to major currency units.
|
|
138
|
+
*
|
|
139
|
+
* @param minorUnits Integer minor units.
|
|
140
|
+
* @param currency Supported statutory currency (THB or USD).
|
|
141
|
+
* @param field Diagnostic field name.
|
|
142
|
+
* @param options Validation options (e.g. allowNegative for refund balances).
|
|
143
|
+
* @returns Amount in major units.
|
|
144
|
+
* @throws {TaxValidationError} if minorUnits is not a safe integer.
|
|
145
|
+
* @example fromMinorUnits(1055, "THB") // 10.55
|
|
146
|
+
*/
|
|
147
|
+
function fromMinorUnits(minorUnits, currency, field = "minorUnits", options) {
|
|
148
|
+
validateIntegerMinorUnits(minorUnits, field, options);
|
|
149
|
+
const precision = CURRENCY_PRECISION[currency];
|
|
150
|
+
const factor = 10 ** precision;
|
|
151
|
+
return (minorUnits < 0 ? -1 : 1) * Number((Math.abs(minorUnits) / factor).toFixed(precision)) + 0;
|
|
152
|
+
}
|
|
153
|
+
// src/core/money/rounding.ts
|
|
154
|
+
/**
|
|
155
|
+
* Rounds a number to the nearest integer, breaking ties away from zero (standard half-up).
|
|
156
|
+
*
|
|
157
|
+
* Math: Rounds fractional halves away from zero (e.g. 1.5 -> 2, -1.5 -> -2).
|
|
158
|
+
*
|
|
159
|
+
* @param value Floating-point value.
|
|
160
|
+
* @returns Rounded integer.
|
|
161
|
+
*/
|
|
162
|
+
function roundHalfUp(value) {
|
|
163
|
+
if (value < 0) return -Math.round(Math.abs(value)) + 0;
|
|
164
|
+
return Math.round(value) + 0;
|
|
165
|
+
}
|
|
166
|
+
function parseRateRational(rate) {
|
|
167
|
+
const parts = (rate.toString().includes("e") ? rate.toFixed(10).replace(/\.?0+$/, "") : rate.toString()).split(".");
|
|
168
|
+
const decimals = parts[1] ? parts[1].length : 0;
|
|
169
|
+
return {
|
|
170
|
+
num: BigInt(parts.join("")),
|
|
171
|
+
den: 10n ** BigInt(decimals)
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* Applies a statutory percentage rate to an integer minor unit amount using exact rational arithmetic.
|
|
176
|
+
*
|
|
177
|
+
* Math: Exact rational scaling via BigInt `(minorUnits * num + modeAdjust) / den`
|
|
178
|
+
* to eliminate IEEE 754 binary floating-point drift on exact statutory tax rates.
|
|
179
|
+
* Units: Input and output are integer minor units (satang/cents).
|
|
180
|
+
*
|
|
181
|
+
* @param minorUnits Integer minor units amount.
|
|
182
|
+
* @param rate Statutory rate ratio (e.g. 0.05 for 5%, 0.35 for 35%).
|
|
183
|
+
* @param rounding Rounding mode (`half-up`, `floor`, or `ceil`).
|
|
184
|
+
* @param options Validation options (e.g. allowNegative).
|
|
185
|
+
* @returns Computed tax in integer minor units.
|
|
186
|
+
* @throws {TaxValidationError} if minorUnits is not a safe integer or rate is outside [0, 1].
|
|
187
|
+
* @example applyPercentage(90, 0.35) // 32 (exact statutory satang calculation)
|
|
188
|
+
*/
|
|
189
|
+
function applyPercentage(minorUnits, rate, rounding = "half-up", options) {
|
|
190
|
+
validateIntegerMinorUnits(minorUnits, "minorUnits", options);
|
|
191
|
+
validateRate(rate, "rate");
|
|
192
|
+
const { num, den } = parseRateRational(rate);
|
|
193
|
+
const prod = BigInt(minorUnits) * num;
|
|
194
|
+
let result;
|
|
195
|
+
if (rounding === "floor") result = prod >= 0n ? prod / den : (prod - (den - 1n)) / den;
|
|
196
|
+
else if (rounding === "ceil") result = prod >= 0n ? (prod + (den - 1n)) / den : prod / den;
|
|
197
|
+
else result = prod >= 0n ? (prod * 2n + den) / (den * 2n) : (prod * 2n - den) / (den * 2n);
|
|
198
|
+
return Number(result) + 0;
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* Applies the statutory gross-up ratio `rate / (1 - rate)` to an integer minor unit amount.
|
|
202
|
+
*
|
|
203
|
+
* Purpose: Recovers the underlying tax from a net amount (e.g. a dividend tax credit).
|
|
204
|
+
* Math: Exact BigInt rational `(minorUnits * num) / (den - num)` where `rate = num / den`,
|
|
205
|
+
* rounded half-up (away from zero) so ties such as 0.5 satang never suffer binary float drift.
|
|
206
|
+
* Units: Input and output are integer minor units (satang/cents).
|
|
207
|
+
*
|
|
208
|
+
* @param minorUnits Non-negative integer minor units amount (the net amount).
|
|
209
|
+
* @param rate Underlying rate ratio in [0, 1) (e.g. 0.2 for a 20% corporate tax rate).
|
|
210
|
+
* @param field Diagnostic field name reported on failure (defaults to `"minorUnits"`).
|
|
211
|
+
* @returns Gross-up amount (`minorUnits * rate / (1 - rate)`) in integer minor units.
|
|
212
|
+
* @throws {TaxValidationError} if minorUnits is not a non-negative safe integer, rate is outside
|
|
213
|
+
* [0, 1), or the result is not a safe integer.
|
|
214
|
+
* @statute Jurisdiction-neutral; Thailand Revenue Code Section 47 bis is the first consumer.
|
|
215
|
+
* @example applyGrossUp(100_000, 0.2) // 25_000 (1,000.00 -> 250.00 credit at 20%)
|
|
216
|
+
*/
|
|
217
|
+
function applyGrossUp(minorUnits, rate, field = "minorUnits") {
|
|
218
|
+
validateIntegerMinorUnits(minorUnits, field);
|
|
219
|
+
validateRate(rate, "rate");
|
|
220
|
+
if (rate >= 1) throw new TaxValidationError("rate", rate, "Gross-up rate must be strictly less than 1");
|
|
221
|
+
const { num, den } = parseRateRational(rate);
|
|
222
|
+
const divisor = den - num;
|
|
223
|
+
const result = Number((BigInt(minorUnits) * num * 2n + divisor) / (divisor * 2n)) + 0;
|
|
224
|
+
if (!Number.isSafeInteger(result)) throw new TaxValidationError(field, minorUnits, "Gross-up result exceeds the safe integer range");
|
|
225
|
+
return result;
|
|
226
|
+
}
|
|
227
|
+
// src/core/money/types.ts
|
|
228
|
+
/**
|
|
229
|
+
* Constructs a branded Money value with immediate input boundary validation.
|
|
230
|
+
*
|
|
231
|
+
* @param amount Numerical amount in major units.
|
|
232
|
+
* @param _currency Currency code tag.
|
|
233
|
+
* @param field Diagnostic field name.
|
|
234
|
+
* @param options Validation options (e.g. allowNegative for refund balances).
|
|
235
|
+
* @returns Branded Money value.
|
|
236
|
+
* @throws {TaxValidationError} if amount is NaN, infinite, or negative without allowNegative.
|
|
237
|
+
*/
|
|
238
|
+
function money(amount, _currency, field = "amount", options) {
|
|
239
|
+
validateMonetaryAmount(amount, field, options);
|
|
240
|
+
return amount;
|
|
241
|
+
}
|
|
242
|
+
/**
|
|
243
|
+
* Constructs a branded Thai Baht (THB) money value with input boundary validation.
|
|
244
|
+
*
|
|
245
|
+
* @param amount Amount in Baht.
|
|
246
|
+
* @param field Diagnostic field name.
|
|
247
|
+
* @param options Validation options (e.g. allowNegative for refund balances).
|
|
248
|
+
* @returns Branded Money<"THB"> value.
|
|
249
|
+
* @throws {TaxValidationError} if amount is NaN, infinite, or negative without allowNegative.
|
|
250
|
+
*/
|
|
251
|
+
function thb(amount, field = "amount", options) {
|
|
252
|
+
validateMonetaryAmount(amount, field, options);
|
|
253
|
+
return amount;
|
|
254
|
+
}
|
|
255
|
+
/**
|
|
256
|
+
* Constructs a branded US Dollar (USD) money value with input boundary validation.
|
|
257
|
+
*
|
|
258
|
+
* @param amount Amount in Dollars.
|
|
259
|
+
* @param field Diagnostic field name.
|
|
260
|
+
* @param options Validation options (e.g. allowNegative for refund balances).
|
|
261
|
+
* @returns Branded Money<"USD"> value.
|
|
262
|
+
* @throws {TaxValidationError} if amount is NaN, infinite, or negative without allowNegative.
|
|
263
|
+
*/
|
|
264
|
+
function usd(amount, field = "amount", options) {
|
|
265
|
+
validateMonetaryAmount(amount, field, options);
|
|
266
|
+
return amount;
|
|
267
|
+
}
|
|
268
|
+
// src/core/brackets/marginal.ts
|
|
269
|
+
/**
|
|
270
|
+
* Determines the marginal tax rate that applies to the next unit of ordinary income.
|
|
271
|
+
*
|
|
272
|
+
* @param brackets Ordered progressive tax brackets.
|
|
273
|
+
* @param nextUnit Amount of income on the boundary transition (taxableAmount + 1).
|
|
274
|
+
* @returns Marginal statutory rate ratio.
|
|
275
|
+
*/
|
|
276
|
+
function marginalRateAt(brackets, nextUnit) {
|
|
277
|
+
let lower = 0;
|
|
278
|
+
for (const bracket of brackets) {
|
|
279
|
+
const upper = bracket.upTo;
|
|
280
|
+
if (nextUnit > lower && (upper === null || !Number.isFinite(upper) || nextUnit <= upper)) return bracket.rate;
|
|
281
|
+
if (upper === null || !Number.isFinite(upper)) break;
|
|
282
|
+
lower = upper;
|
|
283
|
+
}
|
|
284
|
+
return brackets[0]?.rate ?? 0;
|
|
285
|
+
}
|
|
286
|
+
// src/core/brackets/slice.ts
|
|
287
|
+
/**
|
|
288
|
+
* Computes progressive tax and slice boundaries for a single bracket tier.
|
|
289
|
+
*
|
|
290
|
+
* @param lowerBound Lower threshold in minor units.
|
|
291
|
+
* @param bracket Current bracket specification.
|
|
292
|
+
* @param taxableAmount Total taxable income in minor units.
|
|
293
|
+
* @param index Bracket index.
|
|
294
|
+
* @returns Computed BracketSlice.
|
|
295
|
+
*/
|
|
296
|
+
function computeBracketSlice(lowerBound, bracket, taxableAmount, index) {
|
|
297
|
+
const upperBound = bracket.upTo;
|
|
298
|
+
let sliceTaxable = 0;
|
|
299
|
+
if (taxableAmount > lowerBound) {
|
|
300
|
+
if (upperBound === null || !Number.isFinite(upperBound)) sliceTaxable = taxableAmount - lowerBound;
|
|
301
|
+
else sliceTaxable = Math.min(taxableAmount, upperBound) - lowerBound;
|
|
302
|
+
}
|
|
303
|
+
const sliceTax = applyPercentage(sliceTaxable, bracket.rate);
|
|
304
|
+
return {
|
|
305
|
+
bracketIndex: index,
|
|
306
|
+
lowerBound,
|
|
307
|
+
upperBound,
|
|
308
|
+
rate: bracket.rate,
|
|
309
|
+
taxableAmount: sliceTaxable,
|
|
310
|
+
tax: sliceTax
|
|
311
|
+
};
|
|
312
|
+
}
|
|
313
|
+
// src/core/brackets/evaluate.ts
|
|
314
|
+
function validateSingleBracket(bracket, index, isLast, prevBound) {
|
|
315
|
+
validateRate(bracket.rate, `bracket[${index}].rate`);
|
|
316
|
+
if (bracket.upTo === null) {
|
|
317
|
+
if (!isLast) throw new TaxValidationError(`bracket[${index}].upTo`, bracket.upTo, "Only the final bracket tier may have upTo: null (unbounded)");
|
|
318
|
+
return prevBound;
|
|
319
|
+
}
|
|
320
|
+
validateMonetaryAmount(bracket.upTo, `bracket[${index}].upTo`);
|
|
321
|
+
if (!Number.isSafeInteger(bracket.upTo) || bracket.upTo <= prevBound) throw new TaxValidationError(`bracket[${index}].upTo`, bracket.upTo, `Bracket bound must be a safe integer greater than ${prevBound}`);
|
|
322
|
+
return bracket.upTo;
|
|
323
|
+
}
|
|
324
|
+
function validateBrackets(brackets) {
|
|
325
|
+
if (brackets.length === 0) throw new TaxValidationError("brackets", brackets, "Bracket list cannot be empty");
|
|
326
|
+
let prevBound = 0;
|
|
327
|
+
for (let i = 0; i < brackets.length; i++) {
|
|
328
|
+
const bracket = brackets[i];
|
|
329
|
+
if (!bracket) continue;
|
|
330
|
+
prevBound = validateSingleBracket(bracket, i, i === brackets.length - 1, prevBound);
|
|
331
|
+
}
|
|
332
|
+
const last = brackets[brackets.length - 1];
|
|
333
|
+
if (last && last.upTo !== null) throw new TaxValidationError("brackets", brackets, "Final bracket tier must have upTo: null");
|
|
334
|
+
}
|
|
335
|
+
/**
|
|
336
|
+
* Evaluates progressive tax brackets slice-by-slice across multiple tiers.
|
|
337
|
+
*
|
|
338
|
+
* Math: Slices taxable income across progressive tiers and sums integer tax per slice.
|
|
339
|
+
* Units: All inputs and outputs must be integer minor units (satang/cents).
|
|
340
|
+
*
|
|
341
|
+
* @param taxableAmount Taxable income in integer minor units.
|
|
342
|
+
* @param brackets Ordered progressive statutory tax brackets.
|
|
343
|
+
* @param grossIncome Optional gross income in minor units for statutory effective rate calculation.
|
|
344
|
+
* @param field Diagnostic field name.
|
|
345
|
+
* @returns Evaluated bracket result including total tax, slices, effective rate, and marginal rate.
|
|
346
|
+
* @throws {TaxValidationError} if inputs are invalid.
|
|
347
|
+
*/
|
|
348
|
+
function evaluateBrackets(taxableAmount, brackets, grossIncome, field = "taxableAmount") {
|
|
349
|
+
validateMonetaryAmount(taxableAmount, field);
|
|
350
|
+
if (grossIncome !== void 0) validateMonetaryAmount(grossIncome, "grossIncome");
|
|
351
|
+
validateBrackets(brackets);
|
|
352
|
+
let totalTax = 0;
|
|
353
|
+
let lowerBound = 0;
|
|
354
|
+
const slices = [];
|
|
355
|
+
for (let i = 0; i < brackets.length; i++) {
|
|
356
|
+
const bracket = brackets[i];
|
|
357
|
+
if (!bracket) continue;
|
|
358
|
+
const slice = computeBracketSlice(lowerBound, bracket, taxableAmount, i);
|
|
359
|
+
slices.push(slice);
|
|
360
|
+
totalTax += slice.tax;
|
|
361
|
+
if (bracket.upTo === null || !Number.isFinite(bracket.upTo)) break;
|
|
362
|
+
lowerBound = bracket.upTo;
|
|
363
|
+
}
|
|
364
|
+
const baseForEffective = grossIncome ?? taxableAmount;
|
|
365
|
+
const effectiveRate = baseForEffective > 0 ? totalTax / baseForEffective : 0;
|
|
366
|
+
const marginalRate = marginalRateAt(brackets, taxableAmount + 1);
|
|
367
|
+
return {
|
|
368
|
+
totalTax,
|
|
369
|
+
slices,
|
|
370
|
+
effectiveRate,
|
|
371
|
+
marginalRate
|
|
372
|
+
};
|
|
373
|
+
}
|
|
374
|
+
// src/core/caps/resolve.ts
|
|
375
|
+
/**
|
|
376
|
+
* Resolves a statutory cap limit from a formula configuration (percentage, ceiling, or min of both).
|
|
377
|
+
*
|
|
378
|
+
* @param options Statutory cap options specifying base, rate, and/or ceiling.
|
|
379
|
+
* @param defaultField Default diagnostic field name.
|
|
380
|
+
* @returns Resolved cap numerical ceiling.
|
|
381
|
+
* @throws {TaxValidationError} if inputs are invalid.
|
|
382
|
+
*/
|
|
383
|
+
function resolveCap(options, defaultField = "cap") {
|
|
384
|
+
const fieldName = options.field ?? defaultField;
|
|
385
|
+
const { base, rate, ceiling, currency } = options;
|
|
386
|
+
let computedCap = Number.POSITIVE_INFINITY;
|
|
387
|
+
if (base !== void 0 && rate !== void 0) {
|
|
388
|
+
validateMonetaryAmount(base, `${fieldName}Base`);
|
|
389
|
+
validateRate(rate, `${fieldName}Rate`);
|
|
390
|
+
if (currency) computedCap = fromMinorUnits(applyPercentage(toMinorUnits(base, currency, `${fieldName}Base`), rate), currency);
|
|
391
|
+
else if (Number.isSafeInteger(base)) computedCap = applyPercentage(base, rate);
|
|
392
|
+
else computedCap = roundHalfUp(base * rate);
|
|
393
|
+
}
|
|
394
|
+
if (ceiling !== void 0) {
|
|
395
|
+
validateMonetaryAmount(ceiling, `${fieldName}Ceiling`);
|
|
396
|
+
computedCap = Math.min(computedCap, ceiling);
|
|
397
|
+
}
|
|
398
|
+
validateMonetaryAmount(computedCap, `${fieldName}Cap`);
|
|
399
|
+
return computedCap;
|
|
400
|
+
}
|
|
401
|
+
// src/core/caps/clamp.ts
|
|
402
|
+
/**
|
|
403
|
+
* Clamps a requested deduction, allowance, or expense against a statutory cap.
|
|
404
|
+
*
|
|
405
|
+
* @param requested Original requested amount.
|
|
406
|
+
* @param capOrOptions Numeric cap or StatutoryCapOptions specification.
|
|
407
|
+
* @param field Diagnostic field name.
|
|
408
|
+
* @returns Breakdown-ready ClampedAmount structure.
|
|
409
|
+
* @throws {TaxValidationError} if inputs are negative, NaN, or non-finite.
|
|
410
|
+
* @example clampCap(150_000, 100_000, "allowance") // { requested: 150000, allowed: 100000, cap: 100000, wasClamped: true }
|
|
411
|
+
*/
|
|
412
|
+
function clampCap(requested, capOrOptions, field = "amount") {
|
|
413
|
+
validateMonetaryAmount(requested, field);
|
|
414
|
+
const cap = typeof capOrOptions === "number" ? capOrOptions : resolveCap(capOrOptions, field);
|
|
415
|
+
validateMonetaryAmount(cap, `${field}Cap`);
|
|
416
|
+
const wasClamped = requested > cap;
|
|
417
|
+
return {
|
|
418
|
+
requested,
|
|
419
|
+
allowed: wasClamped ? cap : requested,
|
|
420
|
+
cap,
|
|
421
|
+
wasClamped
|
|
422
|
+
};
|
|
423
|
+
}
|
|
424
|
+
export { usd as a, roundHalfUp as c, multiplySafeInteger as d, validateMonetaryAmount as f, thb as i, fromMinorUnits as l, TaxValidationError as m, evaluateBrackets as n, applyGrossUp as o, validateSafeIntegerCount as p, money as r, applyPercentage as s, clampCap as t, toMinorUnits as u };
|
|
425
|
+
|
|
426
|
+
//# sourceMappingURL=clamp-Bse4u0H-.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"clamp-Bse4u0H-.mjs","names":[],"sources":["../src/core/money/errors.ts","../src/core/money/currency.ts","../src/core/money/validate.ts","../src/core/money/minor-units.ts","../src/core/money/rounding.ts","../src/core/money/types.ts","../src/core/brackets/marginal.ts","../src/core/brackets/slice.ts","../src/core/brackets/evaluate.ts","../src/core/caps/resolve.ts","../src/core/caps/clamp.ts"],"sourcesContent":["/**\n * Error thrown when input values violate mathematical boundaries or statutory constraints.\n * Exposes field, raw value, and validation reason for transparency and structured error reporting.\n */\nexport class TaxValidationError extends Error {\n\treadonly field: string\n\treadonly value: unknown\n\treadonly reason: string\n\n\tconstructor(field: string, value: unknown, reason: string) {\n\t\tsuper(`Tax validation error on field '${field}': ${reason} (received: ${String(value)})`)\n\t\tthis.name = \"TaxValidationError\"\n\t\tthis.field = field\n\t\tthis.value = value\n\t\tthis.reason = reason\n\t\tObject.setPrototypeOf(this, TaxValidationError.prototype)\n\t}\n}\n","/**\n * Currencies the tax engine accounts in: one statutory currency per supported jurisdiction.\n *\n * Purpose: Compile-time gate for every monetary API. Callers map their own currency codes\n * onto this union; there is no runtime validation or fallback.\n * @statute Thai Revenue Code (THB) and US Internal Revenue Code (USD) both require\n * amounts to be stated in the domestic currency.\n */\nexport type TaxSupportedCurrency = \"THB\" | \"USD\"\n\n/**\n * Minor-unit precision (fractional decimal digits) per supported currency.\n *\n * Units: THB 2 (1 baht = 100 satang), USD 2 (1 dollar = 100 cents).\n * Internal to the money module; not part of the public package API.\n */\nexport const CURRENCY_PRECISION: Readonly<Record<TaxSupportedCurrency, number>> = {\n\tTHB: 2,\n\tUSD: 2,\n}\n","import { TaxValidationError } from \"./errors\"\n\n/**\n * Options controlling monetary validation.\n */\nexport interface ValidateAmountOptions {\n\t/** If true, negative values will not throw an error (e.g. net payable after refundable credits). */\n\treadonly allowNegative?: boolean\n}\n\n/**\n * Validates a monetary input against numerical boundaries (NaN, Infinity, negative).\n *\n * @param value Number to validate.\n * @param field Diagnostic field name for error reporting.\n * @param options Validation options.\n * @throws {TaxValidationError} if value is NaN, non-finite, or negative without allowNegative.\n */\nexport function validateMonetaryAmount(\n\tvalue: number,\n\tfield: string,\n\toptions?: ValidateAmountOptions,\n): void {\n\tif (Number.isNaN(value)) {\n\t\tthrow new TaxValidationError(field, value, \"Value cannot be NaN\")\n\t}\n\tif (!Number.isFinite(value)) {\n\t\tthrow new TaxValidationError(field, value, \"Value must be a finite number\")\n\t}\n\tif (!options?.allowNegative && value < 0) {\n\t\tthrow new TaxValidationError(field, value, \"Value cannot be negative\")\n\t}\n}\n\n/**\n * Validates a statutory percentage rate (e.g. 0.05 for 5%).\n *\n * @param rate Rate ratio between 0 and 1.\n * @param field Diagnostic field name.\n * @throws {TaxValidationError} if rate is NaN, non-finite, or outside [0, 1].\n */\nexport function validateRate(rate: number, field: string): void {\n\tif (Number.isNaN(rate) || !Number.isFinite(rate)) {\n\t\tthrow new TaxValidationError(field, rate, \"Rate must be a finite number\")\n\t}\n\tif (rate < 0 || rate > 1) {\n\t\tthrow new TaxValidationError(\n\t\t\tfield,\n\t\t\trate,\n\t\t\t\"Statutory rate ratio must be between 0 and 1 (inclusive)\",\n\t\t)\n\t}\n}\n\n/**\n * Validates that an amount is an exact integer minor unit.\n *\n * @param minorUnits Integer minor units.\n * @param field Diagnostic field name.\n * @param options Validation options.\n * @throws {TaxValidationError} if not a safe integer.\n */\nexport function validateIntegerMinorUnits(\n\tminorUnits: number,\n\tfield: string,\n\toptions?: ValidateAmountOptions,\n): void {\n\tvalidateMonetaryAmount(minorUnits, field, options)\n\tif (!Number.isSafeInteger(minorUnits)) {\n\t\tthrow new TaxValidationError(field, minorUnits, \"Minor units amount must be a safe integer\")\n\t}\n}\n\n/**\n * Validates a statutory count (children, years of service, holding years).\n *\n * Purpose: Guards integer counts that later multiply money, rejecting fractions and unsafe values.\n * Math: Requires `Number.isSafeInteger(value)` and `value >= min`.\n * Units: Dimensionless count (not currency).\n * Throws: TaxValidationError if value is not a safe integer or is below `min`.\n *\n * @param value Count to validate.\n * @param field Diagnostic field name.\n * @param min Inclusive lower bound (defaults to 0).\n * @statute Jurisdiction-neutral input bound (AGENTS rule 8); no statutory citation.\n */\nexport function validateSafeIntegerCount(value: number, field: string, min = 0): void {\n\tif (!Number.isSafeInteger(value)) {\n\t\tthrow new TaxValidationError(field, value, \"Count must be a safe integer\")\n\t}\n\tif (value < min) {\n\t\tthrow new TaxValidationError(field, value, `Count must be at least ${min}`)\n\t}\n}\n\n/**\n * Multiplies two integers and fails loudly if the product leaves the safe integer range.\n *\n * Purpose: Prevents silent precision loss when scaling minor units by a count (years, days).\n * Math: `a * b`, requiring `Number.isSafeInteger(a * b)`.\n * Units: Typically integer minor units times a dimensionless count; result in minor units.\n * Throws: TaxValidationError (reported against `field`) if the product is not a safe integer.\n *\n * @param a Integer factor (e.g. minor units).\n * @param b Integer factor (e.g. a validated count).\n * @param field Diagnostic field name of the count or amount being scaled.\n * @statute Jurisdiction-neutral input bound (AGENTS rule 8); no statutory citation.\n */\nexport function multiplySafeInteger(a: number, b: number, field: string): number {\n\tconst product = a * b\n\tif (!Number.isSafeInteger(product)) {\n\t\tthrow new TaxValidationError(field, b, \"Scaled amount exceeds the safe integer range\")\n\t}\n\treturn product + 0\n}\n","import { CURRENCY_PRECISION, type TaxSupportedCurrency } from \"./currency\"\nimport { TaxValidationError } from \"./errors\"\nimport {\n\ttype ValidateAmountOptions,\n\tvalidateIntegerMinorUnits,\n\tvalidateMonetaryAmount,\n} from \"./validate\"\n\nfunction scaleDecimalString(abs: number, precision: number): number {\n\tconst factor = 10 ** precision\n\tif (String(abs).includes(\"e\")) {\n\t\treturn abs * factor\n\t}\n\tconst parts = String(abs).split(\".\")\n\tconst intPart = parts[0] ?? \"0\"\n\tconst decPart = (parts[1] ?? \"\").padEnd(precision, \"0\")\n\tconst whole = intPart + decPart.slice(0, precision)\n\tconst fraction = decPart.slice(precision)\n\treturn Number(whole + (fraction.length > 0 ? `.${fraction}` : \"\"))\n}\n\n/**\n * Converts a major-unit monetary amount to integer minor units (satang/cent).\n *\n * Math: Decimal string expansion and half-away-from-zero rounding to eliminate IEEE 754 float drift.\n * Units: Input is major currency unit (e.g. THB, USD); output is integer minor units (e.g. Satang, Cents).\n *\n * @param amount Amount in major units.\n * @param currency Supported statutory currency (THB or USD).\n * @param field Diagnostic field name.\n * @param options Validation options (e.g. allowNegative for refund balances).\n * @returns Integer minor units.\n * @throws {TaxValidationError} if amount is NaN, infinite, or results in a non-safe integer.\n * @example toMinorUnits(1.14, \"USD\") // 114 (114 cents)\n */\nexport function toMinorUnits(\n\tamount: number,\n\tcurrency: TaxSupportedCurrency,\n\tfield = \"amount\",\n\toptions?: ValidateAmountOptions,\n): number {\n\tvalidateMonetaryAmount(amount, field, options)\n\tconst precision = CURRENCY_PRECISION[currency]\n\tconst sign = amount < 0 ? -1 : 1\n\tconst abs = Math.abs(amount)\n\tconst scaled = scaleDecimalString(abs, precision)\n\tconst result = sign * Math.round(scaled) + 0\n\tif (!Number.isSafeInteger(result)) {\n\t\tthrow new TaxValidationError(\n\t\t\tfield,\n\t\t\tamount,\n\t\t\t\"Conversion to minor units failed or exceeded safe integer bounds\",\n\t\t)\n\t}\n\treturn result\n}\n\n/**\n * Converts an integer minor unit amount back to major currency units.\n *\n * @param minorUnits Integer minor units.\n * @param currency Supported statutory currency (THB or USD).\n * @param field Diagnostic field name.\n * @param options Validation options (e.g. allowNegative for refund balances).\n * @returns Amount in major units.\n * @throws {TaxValidationError} if minorUnits is not a safe integer.\n * @example fromMinorUnits(1055, \"THB\") // 10.55\n */\nexport function fromMinorUnits(\n\tminorUnits: number,\n\tcurrency: TaxSupportedCurrency,\n\tfield = \"minorUnits\",\n\toptions?: ValidateAmountOptions,\n): number {\n\tvalidateIntegerMinorUnits(minorUnits, field, options)\n\tconst precision = CURRENCY_PRECISION[currency]\n\tconst factor = 10 ** precision\n\tconst sign = minorUnits < 0 ? -1 : 1\n\tconst absUnits = Math.abs(minorUnits)\n\treturn sign * Number((absUnits / factor).toFixed(precision)) + 0\n}\n","import { TaxValidationError } from \"./errors\"\nimport { type ValidateAmountOptions, validateIntegerMinorUnits, validateRate } from \"./validate\"\n\n/**\n * Supported rounding modes for statutory tax computations.\n */\nexport type RoundingMode = \"half-up\" | \"floor\" | \"ceil\"\n\n/**\n * Rounds a number to the nearest integer, breaking ties away from zero (standard half-up).\n *\n * Math: Rounds fractional halves away from zero (e.g. 1.5 -> 2, -1.5 -> -2).\n *\n * @param value Floating-point value.\n * @returns Rounded integer.\n */\nexport function roundHalfUp(value: number): number {\n\tif (value < 0) {\n\t\tconst res = -Math.round(Math.abs(value))\n\t\treturn res + 0\n\t}\n\treturn Math.round(value) + 0\n}\n\nfunction parseRateRational(rate: number): { num: bigint; den: bigint } {\n\tconst rateStr = rate.toString().includes(\"e\")\n\t\t? rate.toFixed(10).replace(/\\.?0+$/, \"\")\n\t\t: rate.toString()\n\tconst parts = rateStr.split(\".\")\n\tconst decimals = parts[1] ? parts[1].length : 0\n\treturn {\n\t\tnum: BigInt(parts.join(\"\")),\n\t\tden: 10n ** BigInt(decimals),\n\t}\n}\n\n/**\n * Applies a statutory percentage rate to an integer minor unit amount using exact rational arithmetic.\n *\n * Math: Exact rational scaling via BigInt `(minorUnits * num + modeAdjust) / den`\n * to eliminate IEEE 754 binary floating-point drift on exact statutory tax rates.\n * Units: Input and output are integer minor units (satang/cents).\n *\n * @param minorUnits Integer minor units amount.\n * @param rate Statutory rate ratio (e.g. 0.05 for 5%, 0.35 for 35%).\n * @param rounding Rounding mode (`half-up`, `floor`, or `ceil`).\n * @param options Validation options (e.g. allowNegative).\n * @returns Computed tax in integer minor units.\n * @throws {TaxValidationError} if minorUnits is not a safe integer or rate is outside [0, 1].\n * @example applyPercentage(90, 0.35) // 32 (exact statutory satang calculation)\n */\nexport function applyPercentage(\n\tminorUnits: number,\n\trate: number,\n\trounding: RoundingMode = \"half-up\",\n\toptions?: ValidateAmountOptions,\n): number {\n\tvalidateIntegerMinorUnits(minorUnits, \"minorUnits\", options)\n\tvalidateRate(rate, \"rate\")\n\n\tconst { num, den } = parseRateRational(rate)\n\tconst m = BigInt(minorUnits)\n\tconst prod = m * num\n\n\tlet result: bigint\n\tif (rounding === \"floor\") {\n\t\tresult = prod >= 0n ? prod / den : (prod - (den - 1n)) / den\n\t} else if (rounding === \"ceil\") {\n\t\tresult = prod >= 0n ? (prod + (den - 1n)) / den : prod / den\n\t} else {\n\t\t// half-up: away from zero\n\t\tresult = prod >= 0n ? (prod * 2n + den) / (den * 2n) : (prod * 2n - den) / (den * 2n)\n\t}\n\n\treturn Number(result) + 0\n}\n\n/**\n * Applies the statutory gross-up ratio `rate / (1 - rate)` to an integer minor unit amount.\n *\n * Purpose: Recovers the underlying tax from a net amount (e.g. a dividend tax credit).\n * Math: Exact BigInt rational `(minorUnits * num) / (den - num)` where `rate = num / den`,\n * rounded half-up (away from zero) so ties such as 0.5 satang never suffer binary float drift.\n * Units: Input and output are integer minor units (satang/cents).\n *\n * @param minorUnits Non-negative integer minor units amount (the net amount).\n * @param rate Underlying rate ratio in [0, 1) (e.g. 0.2 for a 20% corporate tax rate).\n * @param field Diagnostic field name reported on failure (defaults to `\"minorUnits\"`).\n * @returns Gross-up amount (`minorUnits * rate / (1 - rate)`) in integer minor units.\n * @throws {TaxValidationError} if minorUnits is not a non-negative safe integer, rate is outside\n * [0, 1), or the result is not a safe integer.\n * @statute Jurisdiction-neutral; Thailand Revenue Code Section 47 bis is the first consumer.\n * @example applyGrossUp(100_000, 0.2) // 25_000 (1,000.00 -> 250.00 credit at 20%)\n */\nexport function applyGrossUp(minorUnits: number, rate: number, field = \"minorUnits\"): number {\n\tvalidateIntegerMinorUnits(minorUnits, field)\n\tvalidateRate(rate, \"rate\")\n\tif (rate >= 1) {\n\t\tthrow new TaxValidationError(\"rate\", rate, \"Gross-up rate must be strictly less than 1\")\n\t}\n\tconst { num, den } = parseRateRational(rate)\n\tconst divisor = den - num\n\tconst result = Number((BigInt(minorUnits) * num * 2n + divisor) / (divisor * 2n)) + 0\n\tif (!Number.isSafeInteger(result)) {\n\t\tthrow new TaxValidationError(\n\t\t\tfield,\n\t\t\tminorUnits,\n\t\t\t\"Gross-up result exceeds the safe integer range\",\n\t\t)\n\t}\n\treturn result\n}\n","import type { TaxSupportedCurrency } from \"./currency\"\nimport { type ValidateAmountOptions, validateMonetaryAmount } from \"./validate\"\n\n/**\n * Branded nominal type representing a monetary value in a specific currency.\n * Enforces compile-time type safety preventing accidental cross-currency mixing.\n */\nexport type Money<C extends TaxSupportedCurrency = TaxSupportedCurrency> = number & {\n\treadonly __currencyBrand: C\n}\n\n/**\n * Constructs a branded Money value with immediate input boundary validation.\n *\n * @param amount Numerical amount in major units.\n * @param _currency Currency code tag.\n * @param field Diagnostic field name.\n * @param options Validation options (e.g. allowNegative for refund balances).\n * @returns Branded Money value.\n * @throws {TaxValidationError} if amount is NaN, infinite, or negative without allowNegative.\n */\nexport function money<C extends TaxSupportedCurrency>(\n\tamount: number,\n\t_currency?: C,\n\tfield = \"amount\",\n\toptions?: ValidateAmountOptions,\n): Money<C> {\n\tvalidateMonetaryAmount(amount, field, options)\n\treturn amount as Money<C>\n}\n\n/**\n * Constructs a branded Thai Baht (THB) money value with input boundary validation.\n *\n * @param amount Amount in Baht.\n * @param field Diagnostic field name.\n * @param options Validation options (e.g. allowNegative for refund balances).\n * @returns Branded Money<\"THB\"> value.\n * @throws {TaxValidationError} if amount is NaN, infinite, or negative without allowNegative.\n */\nexport function thb(\n\tamount: number,\n\tfield = \"amount\",\n\toptions?: ValidateAmountOptions,\n): Money<\"THB\"> {\n\tvalidateMonetaryAmount(amount, field, options)\n\treturn amount as Money<\"THB\">\n}\n\n/**\n * Constructs a branded US Dollar (USD) money value with input boundary validation.\n *\n * @param amount Amount in Dollars.\n * @param field Diagnostic field name.\n * @param options Validation options (e.g. allowNegative for refund balances).\n * @returns Branded Money<\"USD\"> value.\n * @throws {TaxValidationError} if amount is NaN, infinite, or negative without allowNegative.\n */\nexport function usd(\n\tamount: number,\n\tfield = \"amount\",\n\toptions?: ValidateAmountOptions,\n): Money<\"USD\"> {\n\tvalidateMonetaryAmount(amount, field, options)\n\treturn amount as Money<\"USD\">\n}\n","import type { TaxBracket } from \"./types\"\n\n/**\n * Determines the marginal tax rate that applies to the next unit of ordinary income.\n *\n * @param brackets Ordered progressive tax brackets.\n * @param nextUnit Amount of income on the boundary transition (taxableAmount + 1).\n * @returns Marginal statutory rate ratio.\n */\nexport function marginalRateAt(brackets: readonly TaxBracket[], nextUnit: number): number {\n\tlet lower = 0\n\tfor (const bracket of brackets) {\n\t\tconst upper = bracket.upTo\n\t\tif (nextUnit > lower && (upper === null || !Number.isFinite(upper) || nextUnit <= upper)) {\n\t\t\treturn bracket.rate\n\t\t}\n\t\tif (upper === null || !Number.isFinite(upper)) {\n\t\t\tbreak\n\t\t}\n\t\tlower = upper\n\t}\n\treturn brackets[0]?.rate ?? 0\n}\n","import { applyPercentage } from \"../money\"\nimport type { BracketSlice, TaxBracket } from \"./types\"\n\n/**\n * Computes progressive tax and slice boundaries for a single bracket tier.\n *\n * @param lowerBound Lower threshold in minor units.\n * @param bracket Current bracket specification.\n * @param taxableAmount Total taxable income in minor units.\n * @param index Bracket index.\n * @returns Computed BracketSlice.\n */\nexport function computeBracketSlice(\n\tlowerBound: number,\n\tbracket: TaxBracket,\n\ttaxableAmount: number,\n\tindex: number,\n): BracketSlice {\n\tconst upperBound = bracket.upTo\n\tlet sliceTaxable = 0\n\n\tif (taxableAmount > lowerBound) {\n\t\tif (upperBound === null || !Number.isFinite(upperBound)) {\n\t\t\tsliceTaxable = taxableAmount - lowerBound\n\t\t} else {\n\t\t\tsliceTaxable = Math.min(taxableAmount, upperBound) - lowerBound\n\t\t}\n\t}\n\n\tconst sliceTax = applyPercentage(sliceTaxable, bracket.rate)\n\n\treturn {\n\t\tbracketIndex: index,\n\t\tlowerBound,\n\t\tupperBound,\n\t\trate: bracket.rate,\n\t\ttaxableAmount: sliceTaxable,\n\t\ttax: sliceTax,\n\t}\n}\n","import { TaxValidationError, validateMonetaryAmount, validateRate } from \"../money\"\nimport { marginalRateAt } from \"./marginal\"\nimport { computeBracketSlice } from \"./slice\"\nimport type { BracketEvaluationResult, BracketSlice, TaxBracket } from \"./types\"\n\nfunction validateSingleBracket(\n\tbracket: TaxBracket,\n\tindex: number,\n\tisLast: boolean,\n\tprevBound: number,\n): number {\n\tvalidateRate(bracket.rate, `bracket[${index}].rate`)\n\tif (bracket.upTo === null) {\n\t\tif (!isLast) {\n\t\t\tthrow new TaxValidationError(\n\t\t\t\t`bracket[${index}].upTo`,\n\t\t\t\tbracket.upTo,\n\t\t\t\t\"Only the final bracket tier may have upTo: null (unbounded)\",\n\t\t\t)\n\t\t}\n\t\treturn prevBound\n\t}\n\tvalidateMonetaryAmount(bracket.upTo, `bracket[${index}].upTo`)\n\tif (!Number.isSafeInteger(bracket.upTo) || bracket.upTo <= prevBound) {\n\t\tthrow new TaxValidationError(\n\t\t\t`bracket[${index}].upTo`,\n\t\t\tbracket.upTo,\n\t\t\t`Bracket bound must be a safe integer greater than ${prevBound}`,\n\t\t)\n\t}\n\treturn bracket.upTo\n}\n\nfunction validateBrackets(brackets: readonly TaxBracket[]): void {\n\tif (brackets.length === 0) {\n\t\tthrow new TaxValidationError(\"brackets\", brackets, \"Bracket list cannot be empty\")\n\t}\n\tlet prevBound = 0\n\tfor (let i = 0; i < brackets.length; i++) {\n\t\tconst bracket = brackets[i]\n\t\tif (!bracket) continue\n\t\tprevBound = validateSingleBracket(bracket, i, i === brackets.length - 1, prevBound)\n\t}\n\tconst last = brackets[brackets.length - 1]\n\tif (last && last.upTo !== null) {\n\t\tthrow new TaxValidationError(\"brackets\", brackets, \"Final bracket tier must have upTo: null\")\n\t}\n}\n\n/**\n * Evaluates progressive tax brackets slice-by-slice across multiple tiers.\n *\n * Math: Slices taxable income across progressive tiers and sums integer tax per slice.\n * Units: All inputs and outputs must be integer minor units (satang/cents).\n *\n * @param taxableAmount Taxable income in integer minor units.\n * @param brackets Ordered progressive statutory tax brackets.\n * @param grossIncome Optional gross income in minor units for statutory effective rate calculation.\n * @param field Diagnostic field name.\n * @returns Evaluated bracket result including total tax, slices, effective rate, and marginal rate.\n * @throws {TaxValidationError} if inputs are invalid.\n */\nexport function evaluateBrackets(\n\ttaxableAmount: number,\n\tbrackets: readonly TaxBracket[],\n\tgrossIncome?: number,\n\tfield = \"taxableAmount\",\n): BracketEvaluationResult {\n\tvalidateMonetaryAmount(taxableAmount, field)\n\tif (grossIncome !== undefined) {\n\t\tvalidateMonetaryAmount(grossIncome, \"grossIncome\")\n\t}\n\tvalidateBrackets(brackets)\n\n\tlet totalTax = 0\n\tlet lowerBound = 0\n\tconst slices: BracketSlice[] = []\n\n\tfor (let i = 0; i < brackets.length; i++) {\n\t\tconst bracket = brackets[i]\n\t\tif (!bracket) continue\n\n\t\tconst slice = computeBracketSlice(lowerBound, bracket, taxableAmount, i)\n\t\tslices.push(slice)\n\t\ttotalTax += slice.tax\n\n\t\tif (bracket.upTo === null || !Number.isFinite(bracket.upTo)) {\n\t\t\tbreak\n\t\t}\n\t\tlowerBound = bracket.upTo\n\t}\n\n\tconst baseForEffective = grossIncome ?? taxableAmount\n\tconst effectiveRate = baseForEffective > 0 ? totalTax / baseForEffective : 0\n\tconst marginalRate = marginalRateAt(brackets, taxableAmount + 1)\n\n\treturn {\n\t\ttotalTax,\n\t\tslices,\n\t\teffectiveRate,\n\t\tmarginalRate,\n\t}\n}\n","import {\n\tapplyPercentage,\n\tfromMinorUnits,\n\troundHalfUp,\n\ttoMinorUnits,\n\tvalidateMonetaryAmount,\n\tvalidateRate,\n} from \"../money\"\nimport type { StatutoryCapOptions } from \"./types\"\n\n/**\n * Resolves a statutory cap limit from a formula configuration (percentage, ceiling, or min of both).\n *\n * @param options Statutory cap options specifying base, rate, and/or ceiling.\n * @param defaultField Default diagnostic field name.\n * @returns Resolved cap numerical ceiling.\n * @throws {TaxValidationError} if inputs are invalid.\n */\nexport function resolveCap(options: StatutoryCapOptions, defaultField = \"cap\"): number {\n\tconst fieldName = options.field ?? defaultField\n\tconst { base, rate, ceiling, currency } = options\n\n\tlet computedCap = Number.POSITIVE_INFINITY\n\n\tif (base !== undefined && rate !== undefined) {\n\t\tvalidateMonetaryAmount(base, `${fieldName}Base`)\n\t\tvalidateRate(rate, `${fieldName}Rate`)\n\t\tif (currency) {\n\t\t\tconst minorBase = toMinorUnits(base, currency, `${fieldName}Base`)\n\t\t\tconst minorCap = applyPercentage(minorBase, rate)\n\t\t\tcomputedCap = fromMinorUnits(minorCap, currency)\n\t\t} else if (Number.isSafeInteger(base)) {\n\t\t\tcomputedCap = applyPercentage(base, rate)\n\t\t} else {\n\t\t\tcomputedCap = roundHalfUp(base * rate)\n\t\t}\n\t}\n\n\tif (ceiling !== undefined) {\n\t\tvalidateMonetaryAmount(ceiling, `${fieldName}Ceiling`)\n\t\tcomputedCap = Math.min(computedCap, ceiling)\n\t}\n\n\tvalidateMonetaryAmount(computedCap, `${fieldName}Cap`)\n\treturn computedCap\n}\n","import { validateMonetaryAmount } from \"../money\"\nimport { resolveCap } from \"./resolve\"\nimport type { ClampedAmount, StatutoryCapOptions } from \"./types\"\n\n/**\n * Clamps a requested deduction, allowance, or expense against a statutory cap.\n *\n * @param requested Original requested amount.\n * @param capOrOptions Numeric cap or StatutoryCapOptions specification.\n * @param field Diagnostic field name.\n * @returns Breakdown-ready ClampedAmount structure.\n * @throws {TaxValidationError} if inputs are negative, NaN, or non-finite.\n * @example clampCap(150_000, 100_000, \"allowance\") // { requested: 150000, allowed: 100000, cap: 100000, wasClamped: true }\n */\nexport function clampCap(\n\trequested: number,\n\tcapOrOptions: number | StatutoryCapOptions,\n\tfield = \"amount\",\n): ClampedAmount {\n\tvalidateMonetaryAmount(requested, field)\n\n\tconst cap = typeof capOrOptions === \"number\" ? capOrOptions : resolveCap(capOrOptions, field)\n\n\tvalidateMonetaryAmount(cap, `${field}Cap`)\n\n\tconst wasClamped = requested > cap\n\tconst allowed = wasClamped ? cap : requested\n\n\treturn {\n\t\trequested,\n\t\tallowed,\n\t\tcap,\n\t\twasClamped,\n\t}\n}\n"],"mappings":"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"}
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
const require_clamp = require("./clamp-BKG7kjVf.cjs");
|
|
3
|
+
// src/core/system/runner.ts
|
|
4
|
+
/**
|
|
5
|
+
* Standard runner that executes tax calculation on a given TaxSystem instance.
|
|
6
|
+
*
|
|
7
|
+
* @param system TaxSystem implementation.
|
|
8
|
+
* @param input Statutory input parameters.
|
|
9
|
+
* @returns Standardized TaxResult envelope.
|
|
10
|
+
*/
|
|
11
|
+
function calculateTax(system, input) {
|
|
12
|
+
return system.calculate(input);
|
|
13
|
+
}
|
|
14
|
+
exports.TaxValidationError = require_clamp.TaxValidationError;
|
|
15
|
+
exports.applyPercentage = require_clamp.applyPercentage;
|
|
16
|
+
exports.calculateTax = calculateTax;
|
|
17
|
+
exports.clampCap = require_clamp.clampCap;
|
|
18
|
+
exports.evaluateBrackets = require_clamp.evaluateBrackets;
|
|
19
|
+
exports.fromMinorUnits = require_clamp.fromMinorUnits;
|
|
20
|
+
exports.money = require_clamp.money;
|
|
21
|
+
exports.roundHalfUp = require_clamp.roundHalfUp;
|
|
22
|
+
exports.thb = require_clamp.thb;
|
|
23
|
+
exports.toMinorUnits = require_clamp.toMinorUnits;
|
|
24
|
+
exports.usd = require_clamp.usd;
|
|
25
|
+
exports.validateMonetaryAmount = require_clamp.validateMonetaryAmount;
|
|
26
|
+
|
|
27
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.cjs","names":[],"sources":["../src/core/system/runner.ts"],"sourcesContent":["import type { TaxSupportedCurrency } from \"../money\"\nimport type { TaxResult, TaxSystem } from \"./types\"\n\n/**\n * Standard runner that executes tax calculation on a given TaxSystem instance.\n *\n * @param system TaxSystem implementation.\n * @param input Statutory input parameters.\n * @returns Standardized TaxResult envelope.\n */\nexport function calculateTax<TInput, C extends TaxSupportedCurrency, TBreakdown>(\n\tsystem: TaxSystem<TInput, C, TBreakdown>,\n\tinput: TInput,\n): TaxResult<C, TBreakdown> {\n\treturn system.calculate(input)\n}\n"],"mappings":";;;;;;;;;;AAUA,SAAgB,aACf,QACA,OAC2B;CAC3B,OAAO,OAAO,UAAU,KAAK;AAC9B"}
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { a as money, c as ValidateAmountOptions, d as StatutoryCapOptions, f as TaxSupportedCurrency, h as TaxBracket, i as Money, l as validateMonetaryAmount, m as BracketSlice, n as TaxResult, o as thb, p as BracketEvaluationResult, r as TaxSystem, s as usd, t as JurisdictionCode, u as ClampedAmount } from "./types-798sLpt6.cjs";
|
|
2
|
+
/**
|
|
3
|
+
* Evaluates progressive tax brackets slice-by-slice across multiple tiers.
|
|
4
|
+
*
|
|
5
|
+
* Math: Slices taxable income across progressive tiers and sums integer tax per slice.
|
|
6
|
+
* Units: All inputs and outputs must be integer minor units (satang/cents).
|
|
7
|
+
*
|
|
8
|
+
* @param taxableAmount Taxable income in integer minor units.
|
|
9
|
+
* @param brackets Ordered progressive statutory tax brackets.
|
|
10
|
+
* @param grossIncome Optional gross income in minor units for statutory effective rate calculation.
|
|
11
|
+
* @param field Diagnostic field name.
|
|
12
|
+
* @returns Evaluated bracket result including total tax, slices, effective rate, and marginal rate.
|
|
13
|
+
* @throws {TaxValidationError} if inputs are invalid.
|
|
14
|
+
*/
|
|
15
|
+
export declare function evaluateBrackets(taxableAmount: number, brackets: readonly TaxBracket[], grossIncome?: number, field?: string): BracketEvaluationResult;
|
|
16
|
+
/**
|
|
17
|
+
* Clamps a requested deduction, allowance, or expense against a statutory cap.
|
|
18
|
+
*
|
|
19
|
+
* @param requested Original requested amount.
|
|
20
|
+
* @param capOrOptions Numeric cap or StatutoryCapOptions specification.
|
|
21
|
+
* @param field Diagnostic field name.
|
|
22
|
+
* @returns Breakdown-ready ClampedAmount structure.
|
|
23
|
+
* @throws {TaxValidationError} if inputs are negative, NaN, or non-finite.
|
|
24
|
+
* @example clampCap(150_000, 100_000, "allowance") // { requested: 150000, allowed: 100000, cap: 100000, wasClamped: true }
|
|
25
|
+
*/
|
|
26
|
+
export declare function clampCap(requested: number, capOrOptions: number | StatutoryCapOptions, field?: string): ClampedAmount;
|
|
27
|
+
/**
|
|
28
|
+
* Error thrown when input values violate mathematical boundaries or statutory constraints.
|
|
29
|
+
* Exposes field, raw value, and validation reason for transparency and structured error reporting.
|
|
30
|
+
*/
|
|
31
|
+
export declare class TaxValidationError extends Error {
|
|
32
|
+
readonly field: string;
|
|
33
|
+
readonly value: unknown;
|
|
34
|
+
readonly reason: string;
|
|
35
|
+
constructor(field: string, value: unknown, reason: string);
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Converts a major-unit monetary amount to integer minor units (satang/cent).
|
|
39
|
+
*
|
|
40
|
+
* Math: Decimal string expansion and half-away-from-zero rounding to eliminate IEEE 754 float drift.
|
|
41
|
+
* Units: Input is major currency unit (e.g. THB, USD); output is integer minor units (e.g. Satang, Cents).
|
|
42
|
+
*
|
|
43
|
+
* @param amount Amount in major units.
|
|
44
|
+
* @param currency Supported statutory currency (THB or USD).
|
|
45
|
+
* @param field Diagnostic field name.
|
|
46
|
+
* @param options Validation options (e.g. allowNegative for refund balances).
|
|
47
|
+
* @returns Integer minor units.
|
|
48
|
+
* @throws {TaxValidationError} if amount is NaN, infinite, or results in a non-safe integer.
|
|
49
|
+
* @example toMinorUnits(1.14, "USD") // 114 (114 cents)
|
|
50
|
+
*/
|
|
51
|
+
export declare function toMinorUnits(amount: number, currency: TaxSupportedCurrency, field?: string, options?: ValidateAmountOptions): number;
|
|
52
|
+
/**
|
|
53
|
+
* Converts an integer minor unit amount back to major currency units.
|
|
54
|
+
*
|
|
55
|
+
* @param minorUnits Integer minor units.
|
|
56
|
+
* @param currency Supported statutory currency (THB or USD).
|
|
57
|
+
* @param field Diagnostic field name.
|
|
58
|
+
* @param options Validation options (e.g. allowNegative for refund balances).
|
|
59
|
+
* @returns Amount in major units.
|
|
60
|
+
* @throws {TaxValidationError} if minorUnits is not a safe integer.
|
|
61
|
+
* @example fromMinorUnits(1055, "THB") // 10.55
|
|
62
|
+
*/
|
|
63
|
+
export declare function fromMinorUnits(minorUnits: number, currency: TaxSupportedCurrency, field?: string, options?: ValidateAmountOptions): number;
|
|
64
|
+
/**
|
|
65
|
+
* Supported rounding modes for statutory tax computations.
|
|
66
|
+
*/
|
|
67
|
+
type RoundingMode = "half-up" | "floor" | "ceil";
|
|
68
|
+
/**
|
|
69
|
+
* Rounds a number to the nearest integer, breaking ties away from zero (standard half-up).
|
|
70
|
+
*
|
|
71
|
+
* Math: Rounds fractional halves away from zero (e.g. 1.5 -> 2, -1.5 -> -2).
|
|
72
|
+
*
|
|
73
|
+
* @param value Floating-point value.
|
|
74
|
+
* @returns Rounded integer.
|
|
75
|
+
*/
|
|
76
|
+
export declare function roundHalfUp(value: number): number;
|
|
77
|
+
/**
|
|
78
|
+
* Applies a statutory percentage rate to an integer minor unit amount using exact rational arithmetic.
|
|
79
|
+
*
|
|
80
|
+
* Math: Exact rational scaling via BigInt `(minorUnits * num + modeAdjust) / den`
|
|
81
|
+
* to eliminate IEEE 754 binary floating-point drift on exact statutory tax rates.
|
|
82
|
+
* Units: Input and output are integer minor units (satang/cents).
|
|
83
|
+
*
|
|
84
|
+
* @param minorUnits Integer minor units amount.
|
|
85
|
+
* @param rate Statutory rate ratio (e.g. 0.05 for 5%, 0.35 for 35%).
|
|
86
|
+
* @param rounding Rounding mode (`half-up`, `floor`, or `ceil`).
|
|
87
|
+
* @param options Validation options (e.g. allowNegative).
|
|
88
|
+
* @returns Computed tax in integer minor units.
|
|
89
|
+
* @throws {TaxValidationError} if minorUnits is not a safe integer or rate is outside [0, 1].
|
|
90
|
+
* @example applyPercentage(90, 0.35) // 32 (exact statutory satang calculation)
|
|
91
|
+
*/
|
|
92
|
+
export declare function applyPercentage(minorUnits: number, rate: number, rounding?: RoundingMode, options?: ValidateAmountOptions): number;
|
|
93
|
+
/**
|
|
94
|
+
* Standard runner that executes tax calculation on a given TaxSystem instance.
|
|
95
|
+
*
|
|
96
|
+
* @param system TaxSystem implementation.
|
|
97
|
+
* @param input Statutory input parameters.
|
|
98
|
+
* @returns Standardized TaxResult envelope.
|
|
99
|
+
*/
|
|
100
|
+
export declare function calculateTax<TInput, C extends TaxSupportedCurrency, TBreakdown>(system: TaxSystem<TInput, C, TBreakdown>, input: TInput): TaxResult<C, TBreakdown>;
|
|
101
|
+
export { type BracketEvaluationResult, type BracketSlice, type ClampedAmount, type JurisdictionCode, type Money, type RoundingMode, type StatutoryCapOptions, type TaxBracket, type TaxResult, type TaxSupportedCurrency, type TaxSystem, type ValidateAmountOptions, money, thb, usd, validateMonetaryAmount };
|
|
102
|
+
//# sourceMappingURL=index.d.cts.map
|