@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.
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../../src/th/parameters-brackets.ts","../../src/th/parameters-special.ts","../../src/th/parameters.ts","../../src/th/types/special-input.ts","../../src/th/units.ts","../../src/th/stages/allowances-donations.ts","../../src/th/stages/allowances-family.ts","../../src/th/stages/allowances-insurance.ts","../../src/th/stages/allowances-retirement.ts","../../src/th/stages/allowances.ts","../../src/th/stages/assessable.ts","../../src/th/stages/bracket-eval.ts","../../src/th/stages/combine.ts","../../src/th/stages/expenses-40-1-2.ts","../../src/th/stages/expenses-40-3.ts","../../src/th/stages/expenses-40-4.ts","../../src/th/stages/electable-expense.ts","../../src/th/stages/expenses-40-5.ts","../../src/th/stages/expenses-40-6.ts","../../src/th/stages/expenses-40-7.ts","../../src/th/stages/expenses-40-8.ts","../../src/th/stages/expenses.ts","../../src/th/stages/minimum-tax.ts","../../src/th/validate.ts","../../src/th/stages/dividend-credit.ts","../../src/th/stages/final-withholding-source.ts","../../src/th/stages/final-withholding.ts","../../src/th/stages/real-estate-expense.ts","../../src/th/stages/real-estate.ts","../../src/th/stages/regular-income.ts","../../src/th/stages/separate-sheet.ts","../../src/th/stages/special-regimes.ts","../../src/th/stages/pipeline.ts","../../src/th/calculate.ts","../../src/th/system.ts"],"sourcesContent":["import type { TaxBracket } from \"../core/brackets\"\n\n/**\n * Section 48(1) progressive net-income brackets for Tax Year 2026 (THB major units, rates as ratios).\n *\n * @statute Thailand Revenue Code Section 48(1); Royal Decree No. 650.\n */\nexport const TH_2026_BRACKETS: readonly TaxBracket[] = [\n\t{ upTo: 150_000, rate: 0.0 },\n\t{ upTo: 300_000, rate: 0.05 },\n\t{ upTo: 500_000, rate: 0.1 },\n\t{ upTo: 750_000, rate: 0.15 },\n\t{ upTo: 1_000_000, rate: 0.2 },\n\t{ upTo: 2_000_000, rate: 0.25 },\n\t{ upTo: 5_000_000, rate: 0.3 },\n\t{ upTo: null, rate: 0.35 },\n]\n","import { TH_2026_BRACKETS } from \"./parameters-brackets\"\nimport type { ThParameters } from \"./parameters-types\"\n\n/**\n * Section 40(4) final withholding rates for Tax Year 2026.\n *\n * @statute Thailand Revenue Code Sections 40(4), 50(2)(g), 57 ter.\n */\nexport const TH_2026_FINAL_WITHHOLDING: ThParameters[\"finalWithholding\"] = {\n\tinterestRate: 0.15,\n\tdividendRate: 0.1,\n\t// Assumption: the issue gives no digital asset rate; 15% mirrors interest.\n\tdigitalAssetRate: 0.15,\n}\n\n/**\n * Section 48(5) separate sheet parameters for Tax Year 2026: exemption, expenses, and schedule.\n * The schedule is the Section 48(1) ladder without its 0% tier (same upper bounds and rates).\n *\n * @statute Thailand Revenue Code Section 48(5), Section 42(1).\n */\nexport const TH_2026_SEPARATE_SHEET: ThParameters[\"separateSheet\"] = {\n\tminYearsOfService: 5,\n\texemptionWageDays: 400,\n\texemptionCeiling: 600_000,\n\texpensePerYearOfService: 7_000,\n\tremainderExpenseRate: 0.5,\n\tbrackets: TH_2026_BRACKETS.filter((bracket) => bracket.rate > 0),\n}\n\n/**\n * Section 48(4) real estate transfer parameters for Tax Year 2026: the commercial holding-year\n * expense table, the flat inheritance/gift rate, the 10-year holding cap, the annualised schedule\n * (no 0% tier, per the Revenue Department's worked examples), and the 20% tax cap.\n *\n * @statute Thailand Revenue Code Section 48(4), Ministerial Regulation No. 126.\n */\nexport const TH_2026_REAL_ESTATE: ThParameters[\"realEstate\"] = {\n\texpenseTable: [\n\t\t{ minYears: 1, rate: 0.92 },\n\t\t{ minYears: 2, rate: 0.84 },\n\t\t{ minYears: 3, rate: 0.77 },\n\t\t{ minYears: 4, rate: 0.71 },\n\t\t{ minYears: 5, rate: 0.65 },\n\t\t{ minYears: 6, rate: 0.6 },\n\t\t{ minYears: 7, rate: 0.55 },\n\t\t{ minYears: 8, rate: 0.5 },\n\t],\n\tinheritanceExpenseRate: 0.5,\n\tmaxHoldingYears: 10,\n\tbrackets: TH_2026_SEPARATE_SHEET.brackets,\n\ttaxCapRate: 0.2,\n}\n","import { TH_2026_BRACKETS } from \"./parameters-brackets\"\nimport {\n\tTH_2026_FINAL_WITHHOLDING,\n\tTH_2026_REAL_ESTATE,\n\tTH_2026_SEPARATE_SHEET,\n} from \"./parameters-special\"\nimport type { ThParameters } from \"./parameters-types\"\n\n/**\n * Thailand Statutory Tax Parameters for Tax Year 2026 (Revenue Code ภ.ง.ด.90/91).\n *\n * @statute Thailand Revenue Code Sections 40, 42 bis, 47, 47 bis, 48(1)–(5); Royal Decrees No. 11, 650.\n */\nexport const TH_2026_PARAMETERS: ThParameters = {\n\ttaxYear: 2026,\n\tcurrency: \"THB\",\n\texpenses: {\n\t\tsection40_1_2: { rate: 0.5, ceiling: 100_000 },\n\t\tsection40_3: { rate: 0.5, ceiling: 100_000 },\n\t\tsection40_4: { rate: 0.0 },\n\t\tsection40_5: {\n\t\t\tbuilding: 0.3,\n\t\t\tagriculturalLand: 0.2,\n\t\t\tnonAgriculturalLand: 0.15,\n\t\t\tvehicles: 0.3,\n\t\t\tother: 0.1,\n\t\t},\n\t\tsection40_6: {\n\t\t\tmedical: 0.6,\n\t\t\tother: 0.3,\n\t\t},\n\t\tsection40_7: { rate: 0.6 },\n\t\tsection40_8: {\n\t\t\tgeneralRate: 0.6,\n\t\t\tentertainerFirstTierRate: 0.6,\n\t\t\tentertainerFirstTierThreshold: 300_000,\n\t\t\tentertainerSecondTierRate: 0.4,\n\t\t\tentertainerCeiling: 600_000,\n\t\t},\n\t},\n\tallowances: {\n\t\tpersonal: 60_000,\n\t\tspouse: 60_000,\n\t\tchild: {\n\t\t\tbasePerChild: 30_000,\n\t\t\tbonusSecondPlusBorn2018OrLater: 30_000,\n\t\t\tbonusCutoffYear: 2018,\n\t\t},\n\t\tparent: {\n\t\t\tperPerson: 30_000,\n\t\t\tmaxCount: 4,\n\t\t},\n\t\tdisabledPerPerson: 60_000,\n\t\tsocialSecurityFundCeiling: 10_500,\n\t\tlifeInsurance: {\n\t\t\tcombinedCeiling: 100_000,\n\t\t\thealthCeiling: 25_000,\n\t\t},\n\t\tparentHealthInsuranceCeiling: 15_000,\n\t\tspouseLifeInsuranceCeiling: 10_000,\n\t\tmortgageInterestCeiling: 100_000,\n\t\tretirementCluster: {\n\t\t\tcombinedCeiling: 500_000,\n\t\t\trmf: { maxRate: 0.3, ceiling: 500_000 },\n\t\t\tssf: { maxRate: 0.3, ceiling: 200_000 },\n\t\t\tpvd: { maxRate: 0.15, ceiling: 500_000 },\n\t\t\tgpf: { maxRate: 0.3, ceiling: 500_000 },\n\t\t\tnsf: { ceiling: 30_000 },\n\t\t\tpensionLifeInsurance: { maxRate: 0.15, ceiling: 200_000 },\n\t\t},\n\t\tthaiEsg: { maxRate: 0.3, ceiling: 300_000 },\n\t\tdonations: {\n\t\t\tdonation2xMultiplier: 2,\n\t\t\tdonation2xMaxRate: 0.1,\n\t\t\tdonation1xMaxRate: 0.1,\n\t\t},\n\t},\n\tbrackets: TH_2026_BRACKETS,\n\tminimumGrossTax: {\n\t\trate: 0.005,\n\t\tincomeThreshold: 120_000,\n\t\ttaxThreshold: 5_000,\n\t},\n\tfinalWithholding: TH_2026_FINAL_WITHHOLDING,\n\tseparateSheet: TH_2026_SEPARATE_SHEET,\n\trealEstate: TH_2026_REAL_ESTATE,\n}\n","/**\n * Election values for a Section 40(4) source: keep source withholding as final tax, or include\n * the income in the regular progressive computation. Plain `\"final\"`/`\"include\"` strings are\n * accepted wherever {@link ThFinalWithholdingElection} is expected.\n *\n * @statute Thailand Revenue Code Sections 40(4), 50(2)(g), 57 ter.\n */\nexport const ThFinalWithholdingElection = {\n\tFinal: \"final\",\n\tInclude: \"include\",\n} as const\n\n/** Union of {@link ThFinalWithholdingElection} values. */\nexport type ThFinalWithholdingElection =\n\t(typeof ThFinalWithholdingElection)[keyof typeof ThFinalWithholdingElection]\n\n/**\n * Section 40(4) interest or digital asset income with an optional final withholding election.\n * Units: THB major units.\n *\n * @statute Thailand Revenue Code Sections 40(4), 50(2)(g), 57 ter.\n */\nexport interface ThSection40_4Source {\n\t/** Gross amount received, in THB (non-negative, finite). */\n\treadonly amount: number\n\t/** Defaults to `\"include\"`. */\n\treadonly election?: ThFinalWithholdingElection\n}\n\n/**\n * Section 40(4) dividend income with election and optional Thai corporate tax rate.\n * Units: THB major units; `corpTaxRate` is a ratio.\n *\n * @statute Thailand Revenue Code Sections 40(4), 47 bis.\n */\nexport interface ThSection40_4Dividend extends ThSection40_4Source {\n\t/** Corporate income tax rate in [0, 1) paid by the distributing company (Section 47 bis). */\n\treadonly corpTaxRate?: number\n}\n\n/**\n * Structured Section 40(4) input splitting interest, dividends, and digital assets.\n * Units: THB major units.\n *\n * @statute Thailand Revenue Code Section 40(4).\n */\nexport interface ThSection40_4Input {\n\t/** Other Section 40(4) income always included in regular brackets. */\n\treadonly amount?: number\n\t/** Interest; a bare number means included in regular brackets. */\n\treadonly interest?: number | ThSection40_4Source\n\t/** Dividends; a bare number means included, with no Section 47 bis credit. */\n\treadonly dividends?: number | ThSection40_4Dividend\n\t/** Digital asset income; a bare number means included in regular brackets. */\n\treadonly digitalAssets?: number | ThSection40_4Source\n}\n\n/**\n * Kind of Section 48(5) lump sum. Only labour-law `\"severance\"` carries the 400-day / 600,000 THB\n * exemption; other `\"retirement\"` payments (e.g. a pension fund lump sum) get none.\n * Plain `\"severance\"`/`\"retirement\"` strings are accepted wherever\n * {@link ThSeparateSheetPaymentType} is expected.\n *\n * @statute Thailand Revenue Code Section 48(5); Section 42(1) (severance exemption).\n */\nexport const ThSeparateSheetPaymentType = {\n\tSeverance: \"severance\",\n\tRetirement: \"retirement\",\n} as const\n\n/** Union of {@link ThSeparateSheetPaymentType} values. */\nexport type ThSeparateSheetPaymentType =\n\t(typeof ThSeparateSheetPaymentType)[keyof typeof ThSeparateSheetPaymentType]\n\n/**\n * Section 48(5) one-time severance or retirement lump sum.\n * Units: THB major units; `yearsOfService` is a count.\n *\n * @statute Thailand Revenue Code Section 48(5), Section 42(1).\n */\nexport interface ThSeparateSheetInput {\n\t/** Lump sum received, in THB. */\n\treadonly amount: number\n\t/** Completed years of service (non-negative safe integer; callers round part-years up). */\n\treadonly yearsOfService: number\n\t/** Last daily wage in THB, basis for the 400-day exemption. */\n\treadonly lastDailyWage: number\n\t/** Defaults to `\"severance\"`; `\"retirement\"` payments receive no exemption. */\n\treadonly paymentType?: ThSeparateSheetPaymentType\n}\n\n/**\n * How a Section 48(4) property was acquired. Inherited or gifted property takes a flat 50%\n * expense; commercially acquired (or other) property takes the holding-year table. The two are\n * never combined. Plain `\"commercial\"`/`\"inheritance_or_gift\"` strings are accepted wherever\n * {@link ThRealEstateAcquisitionType} is expected.\n *\n * @statute Thailand Revenue Code Section 48(4); Ministerial Regulation No. 126.\n */\nexport const ThRealEstateAcquisitionType = {\n\tCommercial: \"commercial\",\n\tInheritanceOrGift: \"inheritance_or_gift\",\n} as const\n\n/** Union of {@link ThRealEstateAcquisitionType} values. */\nexport type ThRealEstateAcquisitionType =\n\t(typeof ThRealEstateAcquisitionType)[keyof typeof ThRealEstateAcquisitionType]\n\n/**\n * Which expense deduction a Section 48(4) computation used, reported in the breakdown.\n *\n * @statute Thailand Revenue Code Section 48(4); Ministerial Regulation No. 126.\n */\nexport const ThRealEstateExpenseBasis = {\n\tHoldingPeriodTable: \"holding_period_table\",\n\tFlatInheritance: \"flat_inheritance\",\n} as const\n\n/** Union of {@link ThRealEstateExpenseBasis} values. */\nexport type ThRealEstateExpenseBasis =\n\t(typeof ThRealEstateExpenseBasis)[keyof typeof ThRealEstateExpenseBasis]\n\n/**\n * Section 48(4) real estate transfer subject to separate computation.\n * Units: THB major units; `holdingYears` is a count.\n *\n * @statute Thailand Revenue Code Section 48(4).\n */\nexport interface ThRealEstateTransferInput {\n\t/**\n\t * Official appraised value in THB (ราคาประเมินทุนทรัพย์, Section 49 bis), not the actual sale\n\t * price.\n\t */\n\treadonly grossTransferValue: number\n\t/**\n\t * Years the property was held (positive safe integer). Callers round part-years up; the\n\t * calculation counts at most 10 years.\n\t */\n\treadonly holdingYears: number\n\t/** Defaults to `\"commercial\"`; `\"inheritance_or_gift\"` uses a flat 50% expense. */\n\treadonly acquisitionType?: ThRealEstateAcquisitionType\n}\n","import { fromMinorUnits, toMinorUnits } from \"../core/money\"\nimport { TH_2026_PARAMETERS } from \"./parameters\"\n\n/**\n * Converts a THB major-unit amount to integer satang.\n *\n * Purpose: Single THB-bound entry point so stages never repeat the currency threading.\n * Math: Decimal-exact conversion via `toMinorUnits` (half away from zero beyond 2 decimals).\n * Units: THB major units in, integer satang out.\n * Throws: TaxValidationError if the amount is negative (unless allowed), non-finite, or unsafe.\n *\n * @param amount Amount in THB.\n * @param field Diagnostic field name reported on failure.\n * @statute Thailand Revenue Code Section 40 (amounts expressed in baht).\n */\nexport function toSatang(amount: number, field?: string): number {\n\treturn toMinorUnits(amount, TH_2026_PARAMETERS.currency, field)\n}\n\n/**\n * Converts integer satang back to THB major units.\n *\n * Purpose: Counterpart of {@link toSatang}.\n * Math: `satang / 100`, fixed to two decimals.\n * Units: Integer satang in, THB major units out.\n * Throws: TaxValidationError if the input is not a safe integer (or is negative without\n * `allowNegative`).\n *\n * @param satang Integer satang.\n * @param field Diagnostic field name reported on failure.\n * @param options Set `allowNegative` for refund balances.\n * @statute Thailand Revenue Code Section 40 (amounts expressed in baht).\n */\nexport function fromSatang(\n\tsatang: number,\n\tfield?: string,\n\toptions?: { readonly allowNegative?: boolean },\n): number {\n\treturn fromMinorUnits(satang, TH_2026_PARAMETERS.currency, field, options)\n}\n\n/**\n * Sums non-negative THB amounts exactly in integer satang.\n *\n * Purpose: Avoids binary float drift when aggregating gross amounts.\n * Math: Converts each amount to satang, adds, converts back to major units.\n * Units: THB major units in and out.\n * Throws: TaxValidationError if any amount is negative or non-finite, or the sum is not a safe integer.\n *\n * @param values Amounts in THB.\n * @param field Diagnostic field name reported on failure.\n * @statute Thailand Revenue Code Section 40 (gross income aggregation).\n */\nexport function sumThAmounts(values: readonly number[], field?: string): number {\n\treturn fromSatang(\n\t\tvalues.reduce((sum, value) => sum + toSatang(value, field), 0),\n\t\tfield,\n\t)\n}\n","import type { ClampedAmount } from \"../../core/caps\"\nimport { validateMonetaryAmount } from \"../../core/money\"\nimport { TH_2026_PARAMETERS } from \"../parameters\"\nimport { ThAllowanceField, type ThDonationsBreakdown } from \"../types\"\n\nexport interface ThDonationAllowancesResult {\n\treadonly breakdown: ThDonationsBreakdown\n\treadonly allowedDonationsTotal: number\n\treadonly netTaxableIncome: number\n}\n\n/**\n * Calculates qualified 2x and general 1x donation allowances.\n *\n * Math: 2x donations capped at 10% of net before donations; 1x donations capped at 10% of remaining net.\n * Units: THB major units.\n *\n * @statute Thailand Revenue Code Section 47(7); Royal Decrees No. 428, 558.\n */\nexport function calculateDonationAllowances(\n\tdonation2x: number | undefined,\n\tdonation1x: number | undefined,\n\tnetBeforeDonations: number,\n): ThDonationAllowancesResult {\n\tconst p = TH_2026_PARAMETERS.allowances.donations\n\tconst net = Math.max(0, netBeforeDonations)\n\n\tconst raw2x = donation2x ?? 0\n\tvalidateMonetaryAmount(raw2x, ThAllowanceField.Donation2x)\n\tconst eligible2x = raw2x * p.donation2xMultiplier\n\tconst cap2x = Math.floor(net * p.donation2xMaxRate)\n\tconst allowed2x = Math.min(eligible2x, cap2x)\n\tconst clamped2x: ClampedAmount = {\n\t\trequested: raw2x,\n\t\tallowed: allowed2x,\n\t\tcap: cap2x,\n\t\twasClamped: eligible2x > cap2x,\n\t}\n\n\tconst remainingNet = Math.max(0, net - allowed2x)\n\tconst raw1x = donation1x ?? 0\n\tvalidateMonetaryAmount(raw1x, ThAllowanceField.Donation1x)\n\tconst cap1x = Math.floor(remainingNet * p.donation1xMaxRate)\n\tconst allowed1x = Math.min(raw1x, cap1x)\n\tconst clamped1x: ClampedAmount = {\n\t\trequested: raw1x,\n\t\tallowed: allowed1x,\n\t\tcap: cap1x,\n\t\twasClamped: raw1x > cap1x,\n\t}\n\n\tconst breakdown: ThDonationsBreakdown = {\n\t\tdonation2x: clamped2x,\n\t\tdonation1x: clamped1x,\n\t\ttotal: allowed2x + allowed1x,\n\t}\n\n\tconst netTaxableIncome = Math.max(0, remainingNet - allowed1x)\n\n\treturn {\n\t\tbreakdown,\n\t\tallowedDonationsTotal: breakdown.total,\n\t\tnetTaxableIncome,\n\t}\n}\n","import { type ClampedAmount, clampCap } from \"../../core/caps\"\nimport { validateMonetaryAmount } from \"../../core/money\"\nimport { TH_2026_PARAMETERS } from \"../parameters\"\nimport {\n\tThAllowanceField,\n\ttype ThAllowancesInput,\n\ttype ThChild,\n\ttype ThChildrenInput,\n\ttype ThFilingStatus,\n\ttype ThParentsInput,\n} from \"../types\"\n\nexport interface ThFamilyAllowancesResult {\n\treadonly personal: ClampedAmount\n\treadonly spouse: ClampedAmount\n\treadonly children: ClampedAmount\n\treadonly parents: ClampedAmount\n\treadonly disabled: ClampedAmount\n}\n\nconst unclamped = (n: number): ClampedAmount => ({\n\trequested: n,\n\tallowed: n,\n\tcap: n,\n\twasClamped: false,\n})\n\nfunction countBonusChildren(children: readonly ThChild[], cutoff: number): number {\n\tconst sorted = [...children].sort((a, b) => (a.birthYear ?? 0) - (b.birthYear ?? 0))\n\tlet bonus = 0\n\tfor (let i = 1; i < sorted.length; i++) {\n\t\tif ((sorted[i]?.birthYear ?? 0) >= cutoff) bonus++\n\t}\n\treturn bonus\n}\n\nfunction computeChildAllowance(children: ThAllowancesInput[\"children\"]): ClampedAmount {\n\tif (!children) return unclamped(0)\n\tconst p = TH_2026_PARAMETERS.allowances.child\n\tif (Array.isArray(children)) {\n\t\tconst bonus = countBonusChildren(children, p.bonusCutoffYear)\n\t\treturn unclamped(children.length * p.basePerChild + bonus * p.bonusSecondPlusBorn2018OrLater)\n\t}\n\tconst counts = children as ThChildrenInput\n\tvalidateMonetaryAmount(counts.totalCount ?? 0, ThAllowanceField.Children)\n\tconst count = Math.max(0, Math.floor(counts.totalCount ?? 0))\n\tconst bonusReq = Math.max(0, Math.floor(counts.secondPlusBorn2018OrLaterCount ?? 0))\n\tconst bonus = Math.min(bonusReq, Math.max(0, count - 1))\n\treturn unclamped(count * p.basePerChild + bonus * p.bonusSecondPlusBorn2018OrLater)\n}\n\nfunction computeParentCount(\n\tparents: ThParentsInput | number | undefined,\n\tstatus: ThFilingStatus,\n): number {\n\tif (parents === undefined) return 0\n\tif (typeof parents === \"number\") {\n\t\tvalidateMonetaryAmount(parents, ThAllowanceField.Parents)\n\t\treturn Math.floor(parents)\n\t}\n\tlet count = (parents.taxpayerFather ? 1 : 0) + (parents.taxpayerMother ? 1 : 0)\n\tif (status === \"married_no_income_spouse\") {\n\t\tcount += (parents.spouseFather ? 1 : 0) + (parents.spouseMother ? 1 : 0)\n\t}\n\treturn count\n}\n\n/**\n * Calculates family allowances (personal, spouse, children, parents, disabled).\n *\n * @statute Thailand Revenue Code Section 47(1)(a)–(f).\n */\nexport function calculateFamilyAllowances(\n\tinput: ThAllowancesInput = {},\n\tfilingStatus: ThFilingStatus = \"single\",\n): ThFamilyAllowancesResult {\n\tconst p = TH_2026_PARAMETERS.allowances\n\tconst personalReq = input.personal ?? p.personal\n\tconst personal = clampCap(personalReq, p.personal, ThAllowanceField.Personal)\n\n\tconst isSpouseEligible =\n\t\tfilingStatus === \"married_no_income_spouse\" || filingStatus === \"married_joint\"\n\tconst spouseRequested = input.spouse ?? (isSpouseEligible ? p.spouse : 0)\n\tconst spouse = isSpouseEligible\n\t\t? clampCap(spouseRequested, p.spouse, ThAllowanceField.Spouse)\n\t\t: {\n\t\t\t\trequested: spouseRequested,\n\t\t\t\tallowed: 0,\n\t\t\t\tcap: 0,\n\t\t\t\twasClamped: spouseRequested > 0,\n\t\t\t}\n\n\tconst children = computeChildAllowance(input.children)\n\tconst parentCount = computeParentCount(input.parents, filingStatus)\n\tconst parentsRequested = parentCount * p.parent.perPerson\n\tconst parents = clampCap(\n\t\tparentsRequested,\n\t\tp.parent.perPerson * p.parent.maxCount,\n\t\tThAllowanceField.Parents,\n\t)\n\n\tconst disabledRequested = (input.disabledCareCount ?? 0) * p.disabledPerPerson\n\tvalidateMonetaryAmount(disabledRequested, ThAllowanceField.Disabled)\n\tconst disabled = unclamped(disabledRequested)\n\n\treturn { personal, spouse, children, parents, disabled }\n}\n","import { type ClampedAmount, clampCap } from \"../../core/caps\"\nimport { validateMonetaryAmount } from \"../../core/money\"\nimport { TH_2026_PARAMETERS } from \"../parameters\"\nimport { ThAllowanceField, type ThAllowancesInput, type ThFilingStatus } from \"../types\"\n\nexport interface ThInsuranceAllowancesResult {\n\treadonly socialSecurity: ClampedAmount\n\treadonly lifeInsurance: ClampedAmount\n\treadonly parentHealthInsurance: ClampedAmount\n\treadonly spouseLifeInsurance: ClampedAmount\n\treadonly mortgageInterest: ClampedAmount\n}\n\n/**\n * Calculates insurance, social security, and housing mortgage interest allowances.\n *\n * @statute Thailand Revenue Code Section 47(1)(d), (g), (h), (j).\n */\nexport function calculateInsuranceAllowances(\n\tinput: ThAllowancesInput = {},\n\tfilingStatus: ThFilingStatus = \"single\",\n): ThInsuranceAllowancesResult {\n\tconst p = TH_2026_PARAMETERS.allowances\n\n\tconst socialSecurity = clampCap(\n\t\tinput.socialSecurity ?? 0,\n\t\tp.socialSecurityFundCeiling,\n\t\tThAllowanceField.SocialSecurity,\n\t)\n\n\tconst healthClamped = clampCap(\n\t\tinput.healthInsurance ?? 0,\n\t\tp.lifeInsurance.healthCeiling,\n\t\tThAllowanceField.HealthInsurance,\n\t)\n\tconst rawLife = input.lifeInsurance ?? 0\n\tif (input.lifeInsurance !== undefined) {\n\t\tvalidateMonetaryAmount(rawLife, ThAllowanceField.LifeHealthCombined)\n\t}\n\tconst lifeCombinedRequested = rawLife + (input.healthInsurance ?? 0)\n\tconst lifeEffectiveRequested = rawLife + healthClamped.allowed\n\tconst lifeCombined = clampCap(\n\t\tlifeEffectiveRequested,\n\t\tp.lifeInsurance.combinedCeiling,\n\t\tThAllowanceField.LifeHealthCombined,\n\t)\n\tconst lifeInsurance: ClampedAmount = {\n\t\trequested: lifeCombinedRequested,\n\t\tallowed: lifeCombined.allowed,\n\t\tcap: p.lifeInsurance.combinedCeiling,\n\t\twasClamped: lifeCombinedRequested > lifeCombined.allowed,\n\t}\n\n\tconst parentHealthInsurance = clampCap(\n\t\tinput.parentHealthInsurance ?? 0,\n\t\tp.parentHealthInsuranceCeiling,\n\t\tThAllowanceField.ParentHealthInsurance,\n\t)\n\n\tconst isSpouseEligible = filingStatus === \"married_no_income_spouse\"\n\tconst spouseLifeReq = input.spouseLifeInsurance ?? 0\n\tconst spouseLifeInsurance: ClampedAmount = isSpouseEligible\n\t\t? clampCap(spouseLifeReq, p.spouseLifeInsuranceCeiling, ThAllowanceField.SpouseLifeInsurance)\n\t\t: {\n\t\t\t\trequested: spouseLifeReq,\n\t\t\t\tallowed: 0,\n\t\t\t\tcap: 0,\n\t\t\t\twasClamped: spouseLifeReq > 0,\n\t\t\t}\n\n\tconst mortgageInterest = clampCap(\n\t\tinput.mortgageInterest ?? 0,\n\t\tp.mortgageInterestCeiling,\n\t\tThAllowanceField.MortgageInterest,\n\t)\n\n\treturn {\n\t\tsocialSecurity,\n\t\tlifeInsurance,\n\t\tparentHealthInsurance,\n\t\tspouseLifeInsurance,\n\t\tmortgageInterest,\n\t}\n}\n","import { type ClampedAmount, clampCap } from \"../../core/caps\"\nimport { TH_2026_PARAMETERS } from \"../parameters\"\nimport {\n\tThAllowanceField,\n\ttype ThAllowancesInput,\n\ttype ThRetirementClusterBreakdown,\n} from \"../types\"\n\nexport interface ThRetirementAllowancesResult {\n\treadonly retirementCluster: ThRetirementClusterBreakdown\n\treadonly thaiEsg: ClampedAmount\n\treadonly allowedClusterTotal: number\n\treadonly allowedThaiEsgTotal: number\n}\n\n/**\n * Calculates Retirement Cluster and ThaiESG allowances.\n *\n * @statute Thailand Revenue Code Section 47; Ministerial Regulations No. 126, 390.\n */\nexport function calculateRetirementAllowances(\n\tinput: ThAllowancesInput = {},\n\tassessableIncome: number,\n\twageBase: number,\n): ThRetirementAllowancesResult {\n\tconst p = TH_2026_PARAMETERS.allowances.retirementCluster\n\tconst currency = TH_2026_PARAMETERS.currency\n\tconst wage = Math.max(wageBase, 0)\n\n\tconst rmf = clampCap(\n\t\tinput.rmf ?? 0,\n\t\t{\n\t\t\tbase: assessableIncome,\n\t\t\trate: p.rmf.maxRate,\n\t\t\tceiling: p.rmf.ceiling,\n\t\t\tcurrency,\n\t\t},\n\t\tThAllowanceField.Rmf,\n\t)\n\tconst ssf = clampCap(\n\t\tinput.ssf ?? 0,\n\t\t{\n\t\t\tbase: assessableIncome,\n\t\t\trate: p.ssf.maxRate,\n\t\t\tceiling: p.ssf.ceiling,\n\t\t\tcurrency,\n\t\t},\n\t\tThAllowanceField.Ssf,\n\t)\n\tconst pvd = clampCap(\n\t\tinput.pvd ?? 0,\n\t\t{ base: wage, rate: p.pvd.maxRate, ceiling: p.pvd.ceiling, currency },\n\t\tThAllowanceField.Pvd,\n\t)\n\tconst gpf = clampCap(\n\t\tinput.gpf ?? 0,\n\t\t{ base: wage, rate: p.gpf.maxRate, ceiling: p.gpf.ceiling, currency },\n\t\tThAllowanceField.Gpf,\n\t)\n\tconst nsf = clampCap(input.nsf ?? 0, p.nsf.ceiling, ThAllowanceField.Nsf)\n\tconst pensionLifeInsurance = clampCap(\n\t\tinput.pensionLifeInsurance ?? 0,\n\t\t{\n\t\t\tbase: assessableIncome,\n\t\t\trate: p.pensionLifeInsurance.maxRate,\n\t\t\tceiling: p.pensionLifeInsurance.ceiling,\n\t\t\tcurrency,\n\t\t},\n\t\tThAllowanceField.PensionLife,\n\t)\n\n\tconst individualSum =\n\t\trmf.allowed +\n\t\tssf.allowed +\n\t\tpvd.allowed +\n\t\tgpf.allowed +\n\t\tnsf.allowed +\n\t\tpensionLifeInsurance.allowed\n\tconst combined = clampCap(individualSum, p.combinedCeiling, ThAllowanceField.RetirementCluster)\n\n\tconst retirementCluster: ThRetirementClusterBreakdown = {\n\t\trmf,\n\t\tssf,\n\t\tpvd,\n\t\tgpf,\n\t\tnsf,\n\t\tpensionLifeInsurance,\n\t\tcombined,\n\t}\n\n\tconst thaiEsgParam = TH_2026_PARAMETERS.allowances.thaiEsg\n\tconst thaiEsg = clampCap(\n\t\tinput.thaiEsg ?? 0,\n\t\t{\n\t\t\tbase: assessableIncome,\n\t\t\trate: thaiEsgParam.maxRate,\n\t\t\tceiling: thaiEsgParam.ceiling,\n\t\t\tcurrency,\n\t\t},\n\t\tThAllowanceField.ThaiEsg,\n\t)\n\n\treturn {\n\t\tretirementCluster,\n\t\tthaiEsg,\n\t\tallowedClusterTotal: combined.allowed,\n\t\tallowedThaiEsgTotal: thaiEsg.allowed,\n\t}\n}\n","import type { ThAllowancesBreakdown, ThAllowancesInput, ThFilingStatus } from \"../types\"\nimport { calculateDonationAllowances } from \"./allowances-donations\"\nimport { calculateFamilyAllowances } from \"./allowances-family\"\nimport { calculateInsuranceAllowances } from \"./allowances-insurance\"\nimport { calculateRetirementAllowances } from \"./allowances-retirement\"\n\nexport interface ThAllowancesResult {\n\treadonly breakdown: ThAllowancesBreakdown\n\treadonly netTaxableIncome: number\n}\n\n/**\n * Aggregates all personal, family, insurance, retirement, and donation allowances.\n *\n * @statute Thailand Revenue Code Section 47.\n */\nexport function calculateAllowances(\n\tinput: ThAllowancesInput = {},\n\tfilingStatus: ThFilingStatus = \"single\",\n\tassessableTotal: number,\n\texpensesTotal: number,\n\twageBase: number,\n): ThAllowancesResult {\n\tconst fam = calculateFamilyAllowances(input, filingStatus)\n\tconst ins = calculateInsuranceAllowances(input, filingStatus)\n\tconst ret = calculateRetirementAllowances(input, assessableTotal, wageBase)\n\n\tconst nonDonationTotal =\n\t\tfam.personal.allowed +\n\t\tfam.spouse.allowed +\n\t\tfam.children.allowed +\n\t\tfam.parents.allowed +\n\t\tfam.disabled.allowed +\n\t\tins.socialSecurity.allowed +\n\t\tins.lifeInsurance.allowed +\n\t\tins.parentHealthInsurance.allowed +\n\t\tins.spouseLifeInsurance.allowed +\n\t\tins.mortgageInterest.allowed +\n\t\tret.allowedClusterTotal +\n\t\tret.allowedThaiEsgTotal\n\n\tconst netBeforeDonations = Math.max(0, assessableTotal - expensesTotal - nonDonationTotal)\n\tconst don = calculateDonationAllowances(input.donation2x, input.donation1x, netBeforeDonations)\n\n\tconst total = nonDonationTotal + don.allowedDonationsTotal\n\n\tconst breakdown: ThAllowancesBreakdown = {\n\t\tpersonal: fam.personal,\n\t\tspouse: fam.spouse,\n\t\tchildren: fam.children,\n\t\tparents: fam.parents,\n\t\tdisabled: fam.disabled,\n\t\tsocialSecurity: ins.socialSecurity,\n\t\tlifeInsurance: ins.lifeInsurance,\n\t\tparentHealthInsurance: ins.parentHealthInsurance,\n\t\tspouseLifeInsurance: ins.spouseLifeInsurance,\n\t\tmortgageInterest: ins.mortgageInterest,\n\t\tretirementCluster: ret.retirementCluster,\n\t\tthaiEsg: ret.thaiEsg,\n\t\tdonations: don.breakdown,\n\t\ttotal,\n\t}\n\n\treturn {\n\t\tbreakdown,\n\t\tnetTaxableIncome: don.netTaxableIncome,\n\t}\n}\n","import { validateMonetaryAmount } from \"../../core/money\"\nimport {\n\tThAssessableField,\n\ttype ThAssessableIncomeBreakdown,\n\ttype ThAssessableIncomeInput,\n\ttype ThExpenseElectableIncome,\n\ttype ThSection40_5Input,\n\ttype ThSection40_6Input,\n\ttype ThSection40_8Input,\n} from \"../types\"\n\nexport function extractElectableAmount(\n\tval: number | ThExpenseElectableIncome | undefined,\n\tfield: string,\n): number {\n\tif (val === undefined) return 0\n\tconst amount = typeof val === \"number\" ? val : val.amount\n\tvalidateMonetaryAmount(amount, field)\n\tif (typeof val === \"object\" && val.actualExpense !== undefined) {\n\t\tvalidateMonetaryAmount(val.actualExpense, `${field}.actualExpense`)\n\t}\n\treturn amount\n}\n\nfunction sumSection40_5(input: ThSection40_5Input | undefined): number {\n\tif (!input) return 0\n\treturn (\n\t\textractElectableAmount(input.building, ThAssessableField.Section40_5_Building) +\n\t\textractElectableAmount(input.agriculturalLand, ThAssessableField.Section40_5_AgriculturalLand) +\n\t\textractElectableAmount(\n\t\t\tinput.nonAgriculturalLand,\n\t\t\tThAssessableField.Section40_5_NonAgriculturalLand,\n\t\t) +\n\t\textractElectableAmount(input.vehicles, ThAssessableField.Section40_5_Vehicles) +\n\t\textractElectableAmount(input.other, ThAssessableField.Section40_5_Other)\n\t)\n}\n\nfunction sumSection40_6(input: ThSection40_6Input | undefined): number {\n\tif (!input) return 0\n\treturn (\n\t\textractElectableAmount(input.medical, ThAssessableField.Section40_6_Medical) +\n\t\textractElectableAmount(input.law, ThAssessableField.Section40_6_Law) +\n\t\textractElectableAmount(input.engineering, ThAssessableField.Section40_6_Engineering) +\n\t\textractElectableAmount(input.architecture, ThAssessableField.Section40_6_Architecture) +\n\t\textractElectableAmount(input.accounting, ThAssessableField.Section40_6_Accounting) +\n\t\textractElectableAmount(input.fineArts, ThAssessableField.Section40_6_FineArts) +\n\t\textractElectableAmount(input.other, ThAssessableField.Section40_6_Other)\n\t)\n}\n\nfunction sumSection40_8(\n\tinput: number | ThExpenseElectableIncome | ThSection40_8Input | undefined,\n): number {\n\tif (input === undefined) return 0\n\tif (typeof input === \"number\" || \"amount\" in input) {\n\t\treturn extractElectableAmount(\n\t\t\tinput as number | ThExpenseElectableIncome,\n\t\t\tThAssessableField.Section40_8,\n\t\t)\n\t}\n\treturn (\n\t\textractElectableAmount(input.general, ThAssessableField.Section40_8_General) +\n\t\textractElectableAmount(input.entertainer, ThAssessableField.Section40_8_Entertainer)\n\t)\n}\n\n/**\n * Normalizes and validates assessable income across Section 40(1)–40(8).\n *\n * @statute Thailand Revenue Code Section 40(1)–(8).\n */\nexport function calculateAssessableIncome(\n\tinput: ThAssessableIncomeInput = {},\n): ThAssessableIncomeBreakdown {\n\tconst s1 = extractElectableAmount(input.section40_1, ThAssessableField.Section40_1)\n\tconst s2 = extractElectableAmount(input.section40_2, ThAssessableField.Section40_2)\n\tconst s3 = extractElectableAmount(input.section40_3, ThAssessableField.Section40_3)\n\tconst s4 = extractElectableAmount(\n\t\ttypeof input.section40_4 === \"number\" ? input.section40_4 : input.section40_4?.amount,\n\t\tThAssessableField.Section40_4,\n\t)\n\tconst s5 = sumSection40_5(input.section40_5)\n\tconst s6 = sumSection40_6(input.section40_6)\n\tconst s7 = extractElectableAmount(input.section40_7, ThAssessableField.Section40_7)\n\tconst s8 = sumSection40_8(input.section40_8)\n\n\tconst total = s1 + s2 + s3 + s4 + s5 + s6 + s7 + s8\n\tconst grossNonEmploymentIncome = s2 + s3 + s4 + s5 + s6 + s7 + s8\n\n\treturn {\n\t\tsection40_1: s1,\n\t\tsection40_2: s2,\n\t\tsection40_3: s3,\n\t\tsection40_4: s4,\n\t\tsection40_5: s5,\n\t\tsection40_6: s6,\n\t\tsection40_7: s7,\n\t\tsection40_8: s8,\n\t\ttotal,\n\t\tgrossNonEmploymentIncome,\n\t}\n}\n","import {\n\ttype BracketEvaluationResult,\n\tevaluateBrackets,\n\ttype TaxBracket,\n} from \"../../core/brackets\"\nimport { fromMinorUnits, toMinorUnits } from \"../../core/money\"\nimport { TH_2026_PARAMETERS } from \"../parameters\"\nimport { ThBracketField } from \"../types\"\n\n/**\n * Evaluates an arbitrary Thai statutory bracket schedule (major-unit bounds) in minor units.\n *\n * Math: Converts bounds and incomes to satang, slices through `evaluateBrackets`, converts back.\n * Units: Inputs and outputs in THB major units; arithmetic in integer satang.\n * Throws: TaxValidationError if incomes are negative or non-finite.\n *\n * @statute Thailand Revenue Code Sections 48(1), 48(4), 48(5).\n */\nexport function evaluateThBracketSet(\n\ttaxableIncome: number,\n\tgrossIncome: number,\n\tbrackets: readonly TaxBracket[],\n): {\n\ttotalTax: number\n\tbrackets: BracketEvaluationResult\n} {\n\tconst currency = TH_2026_PARAMETERS.currency\n\tconst minorBrackets = brackets.map((b) => ({\n\t\tupTo: b.upTo === null ? null : toMinorUnits(b.upTo, currency),\n\t\trate: b.rate,\n\t}))\n\tconst taxableMinor = toMinorUnits(taxableIncome, currency, ThBracketField.TaxableIncome)\n\tconst grossMinor = toMinorUnits(grossIncome, currency, ThBracketField.GrossIncome)\n\tconst res = evaluateBrackets(taxableMinor, minorBrackets, grossMinor)\n\n\tconst slices = res.slices.map((s) => ({\n\t\tbracketIndex: s.bracketIndex,\n\t\tlowerBound: fromMinorUnits(s.lowerBound, currency),\n\t\tupperBound: s.upperBound !== null ? fromMinorUnits(s.upperBound, currency) : null,\n\t\trate: s.rate,\n\t\ttaxableAmount: fromMinorUnits(s.taxableAmount, currency),\n\t\ttax: fromMinorUnits(s.tax, currency),\n\t}))\n\n\tconst totalTax = fromMinorUnits(res.totalTax, currency)\n\treturn {\n\t\ttotalTax,\n\t\tbrackets: {\n\t\t\ttotalTax,\n\t\t\tslices,\n\t\t\teffectiveRate: res.effectiveRate,\n\t\t\tmarginalRate: res.marginalRate,\n\t\t},\n\t}\n}\n\n/**\n * Evaluates the regular progressive tax brackets for Thailand PIT.\n *\n * Math: Slices taxable income through `TH_2026_PARAMETERS.brackets`.\n * Units: THB major units.\n * Throws: TaxValidationError if incomes are negative or non-finite.\n *\n * @statute Thailand Revenue Code Section 48(1).\n */\nexport function evaluateThBrackets(\n\ttaxableIncome: number,\n\tgrossIncome: number,\n): {\n\ttotalTax: number\n\tbrackets: BracketEvaluationResult\n} {\n\treturn evaluateThBracketSet(taxableIncome, grossIncome, TH_2026_PARAMETERS.brackets)\n}\n","import { type ThCombinedTaxBreakdown, ThCombineField, ThTaxField } from \"../types\"\nimport { fromSatang, toSatang } from \"../units\"\n\n/** Component taxes, credits, and payments to combine. Units: THB major units. */\nexport interface ThCombineInput {\n\t/** Section 48(1)/48(2) tax on the regular schedule. */\n\treadonly regularTax: number\n\t/** Section 48(5) separate sheet tax. */\n\treadonly separateSheetTax: number\n\t/** Section 48(4) real estate transfer tax. */\n\treadonly realEstateTax: number\n\t/** Section 40(4) tax withheld at source and elected final. */\n\treadonly finalWithholdingTax: number\n\t/** Section 47 bis dividend tax credit. */\n\treadonly dividendTaxCredit: number\n\t/** Other tax already withheld/paid, credited against the liability. */\n\treadonly withholdingTax: number\n}\n\n/**\n * Combines regular, separate sheet, real estate, and final withholding taxes into one liability.\n *\n * Purpose: Produces the single audited total and the amount still payable (or refundable).\n * Math: totalCombinedTax = regular + separate sheet + real estate + final withholding (before credits);\n * netTaxPayable = totalCombinedTax - final withholding (already withheld at source)\n * - Section 47 bis dividend credit - withholding tax paid. May be negative (refund).\n * Units: Inputs and outputs in THB major units; arithmetic in integer satang.\n * Throws: TaxValidationError if any component is negative or non-finite.\n *\n * @param input Component taxes, credits, and payments in THB.\n * @statute Thailand Revenue Code Sections 40(4), 47 bis, 48(1)–(5).\n */\nexport function combineThLiability(input: ThCombineInput): ThCombinedTaxBreakdown {\n\tconst regular = toSatang(input.regularTax, ThCombineField.RegularTax)\n\tconst separateSheet = toSatang(input.separateSheetTax, ThCombineField.SeparateSheetTax)\n\tconst realEstate = toSatang(input.realEstateTax, ThCombineField.RealEstateTax)\n\tconst finalWithholding = toSatang(input.finalWithholdingTax, ThCombineField.FinalWithholdingTax)\n\tconst dividendCredit = toSatang(input.dividendTaxCredit, ThCombineField.DividendTaxCredit)\n\tconst withheld = toSatang(input.withholdingTax, ThTaxField.WithholdingTax)\n\n\tconst total = regular + separateSheet + realEstate + finalWithholding\n\tconst credits = finalWithholding + dividendCredit + withheld\n\treturn {\n\t\ttotalCombinedTax: fromSatang(total, ThTaxField.TotalTax),\n\t\tnetTaxPayable: fromSatang(total - credits, ThTaxField.NetTaxPayable, { allowNegative: true }),\n\t}\n}\n","import { type ClampedAmount, clampCap } from \"../../core/caps\"\nimport { TH_2026_PARAMETERS } from \"../parameters\"\nimport { ThExpenseField } from \"../types\"\n\n/**\n * Calculates shared Section 40(1) and Section 40(2) expense deduction.\n *\n * Math: 50% of combined 40(1) + 40(2) income, clamped to 100,000 THB ceiling.\n * Units: THB major units.\n *\n * @statute Thailand Revenue Code Section 42 bis.\n */\nexport function calculateExpenses40_1_2(s1: number, s2: number): ClampedAmount {\n\tconst combined = s1 + s2\n\tconst requested = Math.floor(combined * TH_2026_PARAMETERS.expenses.section40_1_2.rate)\n\treturn clampCap(\n\t\trequested,\n\t\tTH_2026_PARAMETERS.expenses.section40_1_2.ceiling,\n\t\tThExpenseField.Section40_1_2,\n\t)\n}\n","import { type ClampedAmount, clampCap } from \"../../core/caps\"\nimport { TH_2026_PARAMETERS } from \"../parameters\"\nimport { type ThExpenseElectableIncome, ThExpenseField } from \"../types\"\n\n/**\n * Calculates Section 40(3) royalties/goodwill/copyright expense deduction.\n *\n * Math: Standard 50% capped at 100,000 THB or actual expenses capped at income.\n * Units: THB major units.\n *\n * @statute Thailand Revenue Code Section 42 ter; Royal Decree No. 11 Section 4.\n */\nexport function calculateExpenses40_3(\n\tinput: number | ThExpenseElectableIncome | undefined,\n\tincomeAmount: number,\n): ClampedAmount {\n\tif (incomeAmount <= 0) {\n\t\treturn { requested: 0, allowed: 0, cap: 0, wasClamped: false }\n\t}\n\tconst isActual = typeof input === \"object\" && input.expenseElection === \"actual\"\n\tif (isActual) {\n\t\tconst requested = input.actualExpense ?? 0\n\t\treturn clampCap(requested, incomeAmount, ThExpenseField.Section40_3)\n\t}\n\tconst requested = Math.floor(incomeAmount * TH_2026_PARAMETERS.expenses.section40_3.rate)\n\treturn clampCap(\n\t\trequested,\n\t\tTH_2026_PARAMETERS.expenses.section40_3.ceiling,\n\t\tThExpenseField.Section40_3,\n\t)\n}\n","import type { ClampedAmount } from \"../../core/caps\"\n\n/**\n * Calculates Section 40(4) expense deduction (0% statutory).\n *\n * Math: Thailand Revenue Code permits 0% expense deductions for interest, dividends, crypto.\n * Units: THB major units.\n *\n * @statute Thailand Revenue Code Section 40(4).\n */\nexport function calculateExpenses40_4(_incomeAmount: number): ClampedAmount {\n\treturn {\n\t\trequested: 0,\n\t\tallowed: 0,\n\t\tcap: 0,\n\t\twasClamped: false,\n\t}\n}\n","import { type ClampedAmount, clampCap } from \"../../core/caps\"\nimport type { ThExpenseElectableIncome } from \"../types\"\n\n/**\n * Computes expense deduction for an electable income item (standard % vs actual).\n *\n * Math: If actual, clamp to item income. If standard, apply percentage of item income.\n * Units: THB major units.\n *\n * @param item Income item number or configuration object.\n * @param standardRate Statutory percentage rate for standard deduction.\n * @param field Diagnostic field name.\n */\nexport function calculateElectableItemExpense(\n\titem: number | ThExpenseElectableIncome | undefined,\n\tstandardRate: number,\n\tfield: string,\n): ClampedAmount {\n\tif (!item) {\n\t\treturn { requested: 0, allowed: 0, cap: 0, wasClamped: false }\n\t}\n\tconst amount = typeof item === \"number\" ? item : item.amount\n\tif (amount <= 0) {\n\t\treturn { requested: 0, allowed: 0, cap: 0, wasClamped: false }\n\t}\n\tconst isActual = typeof item === \"object\" && item.expenseElection === \"actual\"\n\tif (isActual) {\n\t\tconst req = item.actualExpense ?? 0\n\t\treturn clampCap(req, amount, field)\n\t}\n\tconst req = Math.floor(amount * standardRate)\n\treturn { requested: req, allowed: req, cap: amount, wasClamped: false }\n}\n","import type { ClampedAmount } from \"../../core/caps\"\nimport { TH_2026_PARAMETERS } from \"../parameters\"\nimport { ThExpenseField, type ThSection40_5Input } from \"../types\"\nimport { calculateElectableItemExpense } from \"./electable-expense\"\n\n/**\n * Calculates Section 40(5) rental expense deductions across property sub-categories.\n *\n * Math: Standard percentage per property class or actual expenses capped at income.\n * Units: THB major units.\n *\n * @statute Thailand Royal Decree No. 11 Section 5.\n */\nexport function calculateExpenses40_5(input: ThSection40_5Input | undefined): ClampedAmount {\n\tif (!input) {\n\t\treturn { requested: 0, allowed: 0, cap: 0, wasClamped: false }\n\t}\n\tconst rates = TH_2026_PARAMETERS.expenses.section40_5\n\tconst b = calculateElectableItemExpense(\n\t\tinput.building,\n\t\trates.building,\n\t\tThExpenseField.Section40_5_Building,\n\t)\n\tconst a = calculateElectableItemExpense(\n\t\tinput.agriculturalLand,\n\t\trates.agriculturalLand,\n\t\tThExpenseField.Section40_5_AgriculturalLand,\n\t)\n\tconst na = calculateElectableItemExpense(\n\t\tinput.nonAgriculturalLand,\n\t\trates.nonAgriculturalLand,\n\t\tThExpenseField.Section40_5_NonAgriculturalLand,\n\t)\n\tconst v = calculateElectableItemExpense(\n\t\tinput.vehicles,\n\t\trates.vehicles,\n\t\tThExpenseField.Section40_5_Vehicles,\n\t)\n\tconst o = calculateElectableItemExpense(\n\t\tinput.other,\n\t\trates.other,\n\t\tThExpenseField.Section40_5_Other,\n\t)\n\n\tconst requested = b.requested + a.requested + na.requested + v.requested + o.requested\n\tconst allowed = b.allowed + a.allowed + na.allowed + v.allowed + o.allowed\n\tconst cap = b.cap + a.cap + na.cap + v.cap + o.cap\n\n\treturn {\n\t\trequested,\n\t\tallowed,\n\t\tcap,\n\t\twasClamped: requested > allowed,\n\t}\n}\n","import type { ClampedAmount } from \"../../core/caps\"\nimport { TH_2026_PARAMETERS } from \"../parameters\"\nimport { ThExpenseField, type ThSection40_6Input } from \"../types\"\nimport { calculateElectableItemExpense } from \"./electable-expense\"\n\n/**\n * Calculates Section 40(6) liberal professions expense deduction.\n *\n * Math: Medical 60%, law/engineering/architecture/accounting/fine arts/other 30%, or actual.\n * Units: THB major units.\n *\n * @statute Thailand Royal Decree No. 11 Section 6.\n */\nexport function calculateExpenses40_6(input: ThSection40_6Input | undefined): ClampedAmount {\n\tif (!input) {\n\t\treturn { requested: 0, allowed: 0, cap: 0, wasClamped: false }\n\t}\n\tconst rates = TH_2026_PARAMETERS.expenses.section40_6\n\tconst med = calculateElectableItemExpense(\n\t\tinput.medical,\n\t\trates.medical,\n\t\tThExpenseField.Section40_6_Medical,\n\t)\n\tconst law = calculateElectableItemExpense(input.law, rates.other, ThExpenseField.Section40_6_Law)\n\tconst eng = calculateElectableItemExpense(\n\t\tinput.engineering,\n\t\trates.other,\n\t\tThExpenseField.Section40_6_Engineering,\n\t)\n\tconst arch = calculateElectableItemExpense(\n\t\tinput.architecture,\n\t\trates.other,\n\t\tThExpenseField.Section40_6_Architecture,\n\t)\n\tconst acc = calculateElectableItemExpense(\n\t\tinput.accounting,\n\t\trates.other,\n\t\tThExpenseField.Section40_6_Accounting,\n\t)\n\tconst art = calculateElectableItemExpense(\n\t\tinput.fineArts,\n\t\trates.other,\n\t\tThExpenseField.Section40_6_FineArts,\n\t)\n\tconst oth = calculateElectableItemExpense(\n\t\tinput.other,\n\t\trates.other,\n\t\tThExpenseField.Section40_6_Other,\n\t)\n\n\tconst requested =\n\t\tmed.requested +\n\t\tlaw.requested +\n\t\teng.requested +\n\t\tarch.requested +\n\t\tacc.requested +\n\t\tart.requested +\n\t\toth.requested\n\tconst allowed =\n\t\tmed.allowed + law.allowed + eng.allowed + arch.allowed + acc.allowed + art.allowed + oth.allowed\n\tconst cap = med.cap + law.cap + eng.cap + arch.cap + acc.cap + art.cap + oth.cap\n\n\treturn {\n\t\trequested,\n\t\tallowed,\n\t\tcap,\n\t\twasClamped: requested > allowed,\n\t}\n}\n","import type { ClampedAmount } from \"../../core/caps\"\nimport { TH_2026_PARAMETERS } from \"../parameters\"\nimport { type ThExpenseElectableIncome, ThExpenseField } from \"../types\"\nimport { calculateElectableItemExpense } from \"./electable-expense\"\n\n/**\n * Calculates Section 40(7) contracting expense deduction.\n *\n * Math: Standard 60% or actual expenses capped at income.\n * Units: THB major units.\n *\n * @statute Thailand Royal Decree No. 11 Section 7.\n */\nexport function calculateExpenses40_7(\n\tinput: number | ThExpenseElectableIncome | undefined,\n): ClampedAmount {\n\treturn calculateElectableItemExpense(\n\t\tinput,\n\t\tTH_2026_PARAMETERS.expenses.section40_7.rate,\n\t\tThExpenseField.Section40_7,\n\t)\n}\n","import { type ClampedAmount, clampCap } from \"../../core/caps\"\nimport { TH_2026_PARAMETERS } from \"../parameters\"\nimport { type ThExpenseElectableIncome, ThExpenseField, type ThSection40_8Input } from \"../types\"\nimport { calculateElectableItemExpense } from \"./electable-expense\"\n\nfunction calculateEntertainerExpense(\n\titem: number | ThExpenseElectableIncome | undefined,\n): ClampedAmount {\n\tif (!item) {\n\t\treturn { requested: 0, allowed: 0, cap: 0, wasClamped: false }\n\t}\n\tconst amount = typeof item === \"number\" ? item : item.amount\n\tif (amount <= 0) {\n\t\treturn { requested: 0, allowed: 0, cap: 0, wasClamped: false }\n\t}\n\tif (typeof item === \"object\" && item.expenseElection === \"actual\") {\n\t\treturn clampCap(item.actualExpense ?? 0, amount, ThExpenseField.Section40_8_Entertainer)\n\t}\n\tconst p = TH_2026_PARAMETERS.expenses.section40_8\n\tconst tier1 = Math.min(amount, p.entertainerFirstTierThreshold)\n\tconst tier2 = Math.max(0, amount - p.entertainerFirstTierThreshold)\n\tconst computed =\n\t\tMath.floor(tier1 * p.entertainerFirstTierRate) + Math.floor(tier2 * p.entertainerSecondTierRate)\n\treturn clampCap(computed, p.entertainerCeiling, ThExpenseField.Section40_8_Entertainer)\n}\n\n/**\n * Calculates Section 40(8) business, commerce, and entertainer expense deduction.\n *\n * Math: General 60% or actual; entertainer tiered 60%/40% up to 600,000 THB or actual.\n * Units: THB major units.\n *\n * @statute Thailand Royal Decree No. 11 Section 8.\n */\nexport function calculateExpenses40_8(\n\tinput: number | ThExpenseElectableIncome | ThSection40_8Input | undefined,\n): ClampedAmount {\n\tif (!input) {\n\t\treturn { requested: 0, allowed: 0, cap: 0, wasClamped: false }\n\t}\n\tif (typeof input === \"number\" || \"amount\" in input) {\n\t\treturn calculateElectableItemExpense(\n\t\t\tinput as number | ThExpenseElectableIncome,\n\t\t\tTH_2026_PARAMETERS.expenses.section40_8.generalRate,\n\t\t\tThExpenseField.Section40_8_General,\n\t\t)\n\t}\n\tconst gen = calculateElectableItemExpense(\n\t\tinput.general,\n\t\tTH_2026_PARAMETERS.expenses.section40_8.generalRate,\n\t\tThExpenseField.Section40_8_General,\n\t)\n\tconst ent = calculateEntertainerExpense(input.entertainer)\n\tconst requested = gen.requested + ent.requested\n\tconst allowed = gen.allowed + ent.allowed\n\tconst cap = gen.cap + ent.cap\n\treturn {\n\t\trequested,\n\t\tallowed,\n\t\tcap,\n\t\twasClamped: requested > allowed,\n\t}\n}\n","import type {\n\tThAssessableIncomeBreakdown,\n\tThAssessableIncomeInput,\n\tThExpenseDeductionsBreakdown,\n} from \"../types\"\nimport { calculateExpenses40_1_2 } from \"./expenses-40-1-2\"\nimport { calculateExpenses40_3 } from \"./expenses-40-3\"\nimport { calculateExpenses40_4 } from \"./expenses-40-4\"\nimport { calculateExpenses40_5 } from \"./expenses-40-5\"\nimport { calculateExpenses40_6 } from \"./expenses-40-6\"\nimport { calculateExpenses40_7 } from \"./expenses-40-7\"\nimport { calculateExpenses40_8 } from \"./expenses-40-8\"\n\n/**\n * Aggregates all Section 40(1)–40(8) statutory expense deductions.\n *\n * @statute Thailand Revenue Code Sections 42 bis, 42 ter, 43–46; Royal Decree No. 11.\n */\nexport function calculateExpenseDeductions(\n\tincome: ThAssessableIncomeInput = {},\n\tassessable: ThAssessableIncomeBreakdown,\n): ThExpenseDeductionsBreakdown {\n\tconst e1_2 = calculateExpenses40_1_2(assessable.section40_1, assessable.section40_2)\n\tconst e3 = calculateExpenses40_3(income.section40_3, assessable.section40_3)\n\tconst e4 = calculateExpenses40_4(assessable.section40_4)\n\tconst e5 = calculateExpenses40_5(income.section40_5)\n\tconst e6 = calculateExpenses40_6(income.section40_6)\n\tconst e7 = calculateExpenses40_7(income.section40_7)\n\tconst e8 = calculateExpenses40_8(income.section40_8)\n\n\tconst total =\n\t\te1_2.allowed + e3.allowed + e4.allowed + e5.allowed + e6.allowed + e7.allowed + e8.allowed\n\n\treturn {\n\t\tsection40_1_2: e1_2,\n\t\tsection40_3: e3,\n\t\tsection40_4: e4,\n\t\tsection40_5: e5,\n\t\tsection40_6: e6,\n\t\tsection40_7: e7,\n\t\tsection40_8: e8,\n\t\ttotal,\n\t}\n}\n","import { applyPercentage, fromMinorUnits, toMinorUnits } from \"../../core/money\"\nimport { TH_2026_PARAMETERS } from \"../parameters\"\nimport { type ThMinimumGrossTaxBreakdown, ThTaxField } from \"../types\"\n\n/**\n * Calculates Section 48(2) Minimum Gross Tax on Section 40(2)–40(8) income.\n *\n * Math: 0.5% of gross Section 40(2)–(8) if gross >= 120,000 THB; payable only if the 0.5% tax exceeds 5,000 THB\n * and exceeds the progressive tax.\n * Units: THB major units.\n *\n * @statute Thailand Revenue Code Section 48(2).\n */\nexport function calculateMinimumGrossTax(\n\tgrossNonEmploymentIncome: number,\n\tprogressiveTax: number,\n): ThMinimumGrossTaxBreakdown {\n\tconst p = TH_2026_PARAMETERS.minimumGrossTax\n\tif (grossNonEmploymentIncome < p.incomeThreshold) {\n\t\treturn {\n\t\t\tgrossEligibleIncome: grossNonEmploymentIncome,\n\t\t\trate: p.rate,\n\t\t\tcalculatedTax: 0,\n\t\t\tthreshold: p.taxThreshold,\n\t\t\tisApplicable: false,\n\t\t\twasApplied: false,\n\t\t}\n\t}\n\n\tconst currency = TH_2026_PARAMETERS.currency\n\tconst minorIncome = toMinorUnits(grossNonEmploymentIncome, currency, ThTaxField.MinimumGrossTax)\n\tconst minorTax = applyPercentage(minorIncome, p.rate)\n\tconst calculatedTax = fromMinorUnits(minorTax, currency)\n\n\tconst isApplicable = calculatedTax > p.taxThreshold\n\tconst wasApplied = isApplicable && calculatedTax > progressiveTax\n\n\treturn {\n\t\tgrossEligibleIncome: grossNonEmploymentIncome,\n\t\trate: p.rate,\n\t\tcalculatedTax,\n\t\tthreshold: p.taxThreshold,\n\t\tisApplicable,\n\t\twasApplied,\n\t}\n}\n","import { TaxValidationError } from \"../core/money\"\nimport { ThSpecialRegimeField } from \"./types\"\n\n/**\n * Validates a Thai corporate income tax rate used for the Section 47 bis gross-up.\n *\n * Purpose: Keeps `rate / (1 - rate)` finite before any stage uses the rate.\n * Math: Requires `0 <= rate < 1`.\n * Units: Rate ratio (0.2 = 20%).\n * Throws: TaxValidationError if rate is NaN, non-finite, negative, or at least 1.\n *\n * @param rate Corporate income tax rate ratio.\n * @statute Thailand Revenue Code Section 47 bis.\n */\nexport function validateCorpTaxRate(rate: number): void {\n\tif (!Number.isFinite(rate) || rate < 0 || rate >= 1) {\n\t\tthrow new TaxValidationError(\n\t\t\tThSpecialRegimeField.CorpTaxRate,\n\t\t\trate,\n\t\t\t\"Corporate tax rate must be finite and satisfy 0 <= rate < 1\",\n\t\t)\n\t}\n}\n\n/**\n * Resolves an optional typed choice (election, payment type, acquisition type) at runtime.\n *\n * Purpose: TypeScript unions are erased at runtime, so untyped callers could otherwise pass an\n * unknown string and silently get the default behaviour.\n * Math: Returns `fallback` when `value` is `undefined`; otherwise requires membership in `choices`.\n * Units: Dimensionless string enum.\n * Throws: TaxValidationError (reported against `field`) if `value` is not one of `choices`.\n *\n * @param value Caller-supplied value, possibly outside the declared union.\n * @param choices Typed const object whose values are the permitted choices.\n * @param fallback Choice used when `value` is `undefined`.\n * @param field Diagnostic field path reported on failure.\n * @statute Jurisdiction-neutral input bound (AGENTS rule 8); no statutory citation.\n */\nexport function resolveChoice<T extends string>(\n\tvalue: string | undefined,\n\tchoices: Readonly<Record<string, T>>,\n\tfallback: T,\n\tfield: ThSpecialRegimeField,\n): T {\n\tif (value === undefined) return fallback\n\tconst match = Object.values(choices).find((choice) => choice === value)\n\tif (match === undefined) {\n\t\tthrow new TaxValidationError(field, value, \"Value is not a supported choice\")\n\t}\n\treturn match\n}\n","import { applyGrossUp, validateMonetaryAmount } from \"../../core/money\"\nimport { ThCombineField, type ThDividendTaxCreditBreakdown, ThSpecialRegimeField } from \"../types\"\nimport { fromSatang, toSatang } from \"../units\"\nimport { validateCorpTaxRate } from \"../validate\"\n\n/**\n * Calculates the Section 47 bis dividend tax credit for dividends included in regular income.\n *\n * Purpose: Recovers the corporate tax already paid on dividends taxed again in the regular schedule.\n * Math: credit = dividend * corpTaxRate / (1 - corpTaxRate), exact in satang and rounded half-up.\n * The grossed-up amount (dividend + credit) is assessable; the credit offsets tax payable.\n * Units: Inputs and outputs in THB major units; arithmetic in integer satang.\n * Throws: TaxValidationError if dividend is negative/non-finite or the rate is invalid.\n *\n * @param includedDividends Dividends elected `\"include\"`, in THB.\n * @param corpTaxRate Corporate tax rate in [0, 1); `undefined` means no credit.\n * @statute Thailand Revenue Code Section 47 bis.\n */\nexport function calculateDividendTaxCredit(\n\tincludedDividends: number,\n\tcorpTaxRate: number | undefined,\n): ThDividendTaxCreditBreakdown {\n\tvalidateMonetaryAmount(includedDividends, ThSpecialRegimeField.Dividends)\n\tif (corpTaxRate === undefined) {\n\t\treturn {\n\t\t\tisApplicable: false,\n\t\t\tdividendAmount: includedDividends,\n\t\t\tcorpTaxRate: 0,\n\t\t\tcredit: 0,\n\t\t\tgrossedUpAmount: includedDividends,\n\t\t}\n\t}\n\tvalidateCorpTaxRate(corpTaxRate)\n\tconst dividendSatang = toSatang(includedDividends, ThSpecialRegimeField.Dividends)\n\tconst creditSatang = applyGrossUp(dividendSatang, corpTaxRate, ThSpecialRegimeField.Dividends)\n\treturn {\n\t\tisApplicable: true,\n\t\tdividendAmount: includedDividends,\n\t\tcorpTaxRate,\n\t\tcredit: fromSatang(creditSatang, ThCombineField.DividendTaxCredit),\n\t\tgrossedUpAmount: fromSatang(dividendSatang + creditSatang, ThCombineField.DividendTaxCredit),\n\t}\n}\n","import { applyPercentage } from \"../../core/money\"\nimport {\n\tThFinalWithholdingElection,\n\ttype ThFinalWithholdingSourceBreakdown,\n\ttype ThSection40_4Dividend,\n\ttype ThSection40_4Source,\n\ttype ThSpecialRegimeField,\n} from \"../types\"\nimport { fromSatang, toSatang } from \"../units\"\nimport { resolveChoice } from \"../validate\"\nimport { extractElectableAmount } from \"./assessable\"\n\nexport type ThSourceInput = number | ThSection40_4Source | ThSection40_4Dividend | undefined\n\n/** One Section 40(4) source after its election is applied. Units: integer satang. */\nexport interface ResolvedSource {\n\treadonly breakdown: ThFinalWithholdingSourceBreakdown\n\treadonly satang: number\n\treadonly taxSatang: number\n}\n\nfunction electionOf(input: ThSourceInput, field: ThSpecialRegimeField): ThFinalWithholdingElection {\n\tconst election = typeof input === \"object\" ? input.election : undefined\n\treturn resolveChoice(\n\t\telection,\n\t\tThFinalWithholdingElection,\n\t\tThFinalWithholdingElection.Include,\n\t\tfield,\n\t)\n}\n\n/**\n * Resolves one Section 40(4) source: validates its amount and election, withholds if final.\n *\n * Purpose: Applies the per-source final withholding election.\n * Math: tax = amount * rate (satang, half-up) when the election is `\"final\"`, otherwise 0.\n * Units: Input in THB major units; `satang` and `taxSatang` in integer satang.\n * Throws: TaxValidationError for a negative/non-finite amount or an unknown election value.\n *\n * @param input Bare amount, source object, or undefined.\n * @param field Diagnostic path of the amount.\n * @param electionField Diagnostic path of the election.\n * @param rate Final withholding rate (ratio).\n * @statute Thailand Revenue Code Sections 40(4), 50(2)(g), 57 ter.\n */\nexport function resolveSource(\n\tinput: ThSourceInput,\n\tfield: ThSpecialRegimeField,\n\telectionField: ThSpecialRegimeField,\n\trate: number,\n): ResolvedSource {\n\tconst amount = extractElectableAmount(input, field)\n\tconst election = electionOf(input, electionField)\n\tconst satang = toSatang(amount, field)\n\tconst isFinal = election === ThFinalWithholdingElection.Final\n\tconst taxSatang = isFinal ? applyPercentage(satang, rate) : 0\n\treturn { breakdown: { election, amount, rate, tax: fromSatang(taxSatang) }, satang, taxSatang }\n}\n","import { TH_2026_PARAMETERS } from \"../parameters\"\nimport {\n\tThAssessableField,\n\ttype ThFinalWithholdingBreakdown,\n\tThFinalWithholdingElection,\n\ttype ThSection40_4Input,\n\tThSpecialRegimeField,\n} from \"../types\"\nimport { fromSatang, toSatang } from \"../units\"\nimport { validateCorpTaxRate } from \"../validate\"\nimport { extractElectableAmount } from \"./assessable\"\nimport { type ResolvedSource, resolveSource } from \"./final-withholding-source\"\n\n/**\n * Section 40(4) split into regular-bracket income and final-withheld income.\n * Units: THB major units.\n *\n * @statute Thailand Revenue Code Sections 40(4), 47 bis, 50(2)(g), 57 ter.\n */\nexport interface ThResolvedSection40_4 {\n\t/** Section 40(4) income included in regular brackets, before any dividend gross-up. */\n\treadonly includedAmount: number\n\t/** Dividends elected `\"include\"`. */\n\treadonly includedDividends: number\n\t/** Corporate tax rate (ratio) for included dividends, if given. */\n\treadonly corpTaxRate: number | undefined\n\t/** Audit trail of the sources elected `\"final\"` and their withheld tax. */\n\treadonly finalWithholding: ThFinalWithholdingBreakdown\n}\n\nfunction toStructuredInput(input: number | ThSection40_4Input | undefined): ThSection40_4Input {\n\treturn typeof input === \"number\" ? { amount: input } : (input ?? {})\n}\n\nfunction sumSatang(sources: readonly ResolvedSource[], seed = 0): number {\n\treturn sources.reduce((sum, source) => sum + source.satang, seed)\n}\n\n/**\n * Splits Section 40(4) income into regular-bracket income and final-withheld income.\n *\n * Purpose: Applies each source's election so only included income reaches the regular schedule.\n * Math: For each source elected `\"final\"`, tax = amount * rate (satang, half-up) and the income is\n * excluded from regular brackets. `\"include\"` (default) adds the income to Section 40(4)\n * assessable income at a 0% expense rate.\n * Units: Inputs and outputs in THB major units; arithmetic in integer satang.\n * Throws: TaxValidationError for negative/non-finite amounts or an invalid `corpTaxRate`.\n *\n * @param input Bare amount, structured sources, or undefined.\n * @statute Thailand Revenue Code Sections 40(4), 50(2)(g), 56, 57 ter.\n */\nexport function resolveSection40_4(\n\tinput: number | ThSection40_4Input | undefined,\n): ThResolvedSection40_4 {\n\tconst section = toStructuredInput(input)\n\tconst rates = TH_2026_PARAMETERS.finalWithholding\n\tconst base = extractElectableAmount(section.amount, ThAssessableField.Section40_4)\n\tconst interest = resolveSource(\n\t\tsection.interest,\n\t\tThSpecialRegimeField.Interest,\n\t\tThSpecialRegimeField.InterestElection,\n\t\trates.interestRate,\n\t)\n\tconst dividends = resolveSource(\n\t\tsection.dividends,\n\t\tThSpecialRegimeField.Dividends,\n\t\tThSpecialRegimeField.DividendsElection,\n\t\trates.dividendRate,\n\t)\n\tconst digital = resolveSource(\n\t\tsection.digitalAssets,\n\t\tThSpecialRegimeField.DigitalAssets,\n\t\tThSpecialRegimeField.DigitalAssetsElection,\n\t\trates.digitalAssetRate,\n\t)\n\tconst corpTaxRate =\n\t\ttypeof section.dividends === \"object\" ? section.dividends.corpTaxRate : undefined\n\tif (corpTaxRate !== undefined) validateCorpTaxRate(corpTaxRate)\n\n\tconst sources = [interest, dividends, digital]\n\tconst isFinal = (source: ResolvedSource): boolean =>\n\t\tsource.breakdown.election === ThFinalWithholdingElection.Final\n\tconst finalSources = sources.filter(isFinal)\n\tconst includedSatang = sumSatang(\n\t\tsources.filter((source) => !isFinal(source)),\n\t\ttoSatang(base, ThAssessableField.Section40_4),\n\t)\n\tconst isDividendIncluded = !isFinal(dividends)\n\treturn {\n\t\tincludedAmount: fromSatang(includedSatang),\n\t\tincludedDividends: isDividendIncluded ? dividends.breakdown.amount : 0,\n\t\tcorpTaxRate: isDividendIncluded ? corpTaxRate : undefined,\n\t\tfinalWithholding: {\n\t\t\tinterest: interest.breakdown,\n\t\t\tdividends: dividends.breakdown,\n\t\t\tdigitalAssets: digital.breakdown,\n\t\t\ttotalAmount: fromSatang(sumSatang(finalSources)),\n\t\t\ttotalTax: fromSatang(finalSources.reduce((sum, source) => sum + source.taxSatang, 0)),\n\t\t},\n\t}\n}\n","import { TH_2026_PARAMETERS } from \"../parameters\"\nimport { ThRealEstateAcquisitionType, ThRealEstateExpenseBasis } from \"../types\"\n\n/**\n * Caps the holding period used by Section 48(4) at the statutory maximum.\n *\n * Purpose: More than 10 years of holding counts as 10 years.\n * Math: min(requestedYears, `realEstate.maxHoldingYears`).\n * Units: Dimensionless year count (callers round part-years up).\n * Throws: Never; `requestedYears` is validated by the caller.\n *\n * @param requestedYears Validated positive safe integer holding period.\n * @statute Thailand Revenue Code Section 48(4); Ministerial Regulation No. 126.\n */\nexport function effectiveHoldingYears(requestedYears: number): number {\n\treturn Math.min(requestedYears, TH_2026_PARAMETERS.realEstate.maxHoldingYears)\n}\n\n/**\n * Selects the Section 48(4) expense rate for the acquisition type.\n *\n * Purpose: Inherited or gifted property takes a flat rate; commercial or other property takes the\n * holding-year table. The two are never combined.\n * Math: Flat `inheritanceExpenseRate`, or the last `expenseTable` row with `minYears <= years`.\n * Units: Rate ratio; `years` is the effective (capped) holding period.\n * Throws: Never; the acquisition type is validated by the caller.\n *\n * @param acquisitionType Validated acquisition type.\n * @param years Effective holding years.\n * @statute Thailand Revenue Code Section 48(4); Ministerial Regulation No. 126.\n */\nexport function resolveRealEstateExpense(\n\tacquisitionType: ThRealEstateAcquisitionType,\n\tyears: number,\n): { readonly basis: ThRealEstateExpenseBasis; readonly rate: number } {\n\tconst rules = TH_2026_PARAMETERS.realEstate\n\tif (acquisitionType === ThRealEstateAcquisitionType.InheritanceOrGift) {\n\t\treturn { basis: ThRealEstateExpenseBasis.FlatInheritance, rate: rules.inheritanceExpenseRate }\n\t}\n\tconst rate = rules.expenseTable.reduce((acc, row) => (years >= row.minYears ? row.rate : acc), 0)\n\treturn { basis: ThRealEstateExpenseBasis.HoldingPeriodTable, rate }\n}\n","import type { TaxBracket } from \"../../core/brackets\"\nimport {\n\tapplyPercentage,\n\tmultiplySafeInteger,\n\troundHalfUp,\n\tvalidateMonetaryAmount,\n\tvalidateSafeIntegerCount,\n} from \"../../core/money\"\nimport { TH_2026_PARAMETERS } from \"../parameters\"\nimport {\n\tThRealEstateAcquisitionType,\n\ttype ThRealEstateBreakdown,\n\tThRealEstateExpenseBasis,\n\ttype ThRealEstateTransferInput,\n\tThSpecialRegimeField,\n} from \"../types\"\nimport { fromSatang, toSatang } from \"../units\"\nimport { resolveChoice } from \"../validate\"\nimport { evaluateThBracketSet, evaluateThBrackets } from \"./bracket-eval\"\nimport { effectiveHoldingYears, resolveRealEstateExpense } from \"./real-estate-expense\"\n\nconst EMPTY_BREAKDOWN: ThRealEstateBreakdown = {\n\tgrossTransferValue: 0,\n\tacquisitionType: ThRealEstateAcquisitionType.Commercial,\n\texpenseBasis: ThRealEstateExpenseBasis.HoldingPeriodTable,\n\trequestedHoldingYears: 0,\n\teffectiveHoldingYears: 0,\n\texpenseRate: 0,\n\texpense: 0,\n\tnetGain: 0,\n\tannualGain: 0,\n\tbrackets: evaluateThBrackets(0, 0).brackets,\n\tannualTax: 0,\n\ttaxBeforeCap: 0,\n\ttaxCap: 0,\n\twasClamped: false,\n\ttax: 0,\n}\n\n/**\n * Scales every finite annualised-schedule bracket bound by the holding period.\n *\n * Math: `years * tax(net / years)` equals the tax on `net` against bounds multiplied by `years`\n * (each slice `rate * clip(net/years - lo, 0, width) * years = rate * clip(net - lo*years, 0, width*years)`),\n * so the annualisation needs no intermediate rounding.\n * Units: THB major units; scaling performed in integer satang.\n * Throws: TaxValidationError if a scaled bound exceeds the safe integer range (unreachable\n * for the capped holding period).\n */\nfunction scaleBracketsByYears(years: number): readonly TaxBracket[] {\n\treturn TH_2026_PARAMETERS.realEstate.brackets.map((bracket) => {\n\t\tif (bracket.upTo === null) return bracket\n\t\tconst scaled = multiplySafeInteger(\n\t\t\ttoSatang(bracket.upTo),\n\t\t\tyears,\n\t\t\tThSpecialRegimeField.RealEstateHoldingYears,\n\t\t)\n\t\treturn { upTo: fromSatang(scaled), rate: bracket.rate }\n\t})\n}\n\n/**\n * Clamps real estate tax at the statutory share of the gross transfer value.\n *\n * Purpose: Applies the Section 48(4) ceiling on the separately computed tax.\n * Math: taxCap = grossMinor * `realEstate.taxCapRate` (20%, half-up); tax = min(taxBeforeCap, taxCap).\n * Units: Integer minor units (satang) in and out.\n * Throws: TaxValidationError if `grossMinor` is not a non-negative safe integer.\n * Note: Unreachable end to end (top rate 35% of at most 50% net is 17.5% < 20%); kept as the\n * statutory guard.\n *\n * @param taxBeforeCap Uncapped tax in satang.\n * @param grossMinor Gross transfer value in satang.\n * @statute Thailand Revenue Code Section 48(4).\n */\nexport function applyRealEstateCap(\n\ttaxBeforeCap: number,\n\tgrossMinor: number,\n): { taxCap: number; tax: number; wasClamped: boolean } {\n\tconst taxCap = applyPercentage(grossMinor, TH_2026_PARAMETERS.realEstate.taxCapRate)\n\treturn { taxCap, tax: Math.min(taxBeforeCap, taxCap), wasClamped: taxBeforeCap > taxCap }\n}\n\n/**\n * Calculates Section 48(4) tax on an inherited, gifted, or sold real estate transfer.\n *\n * Purpose: Taxes the gain as if earned evenly over the holding period, then scales back up.\n * `grossTransferValue` must be the official appraised value (ราคาประเมินทุนทรัพย์, Section 49 bis),\n * not the actual sale price. `holdingYears` is a positive safe integer; callers round part-years\n * up, and more than 10 years counts as 10.\n * Math: years = min(holdingYears, 10); expense = gross * 50% for inheritance/gift, otherwise\n * gross * table(years) (never combined); net = gross - expense; tax = years * bracketTax(net / years)\n * on the schedule without a 0% tier (5% from the first baht), evaluated exactly as bracketTax of\n * `net` against bounds multiplied by `years` (no per-year rounding); tax is clamped at 20% of gross\n * (which the 35% top rate on at most 50% net can never reach). `annualGain` and `annualTax` are\n * rounded for display.\n * Units: Inputs and outputs in THB major units; arithmetic in integer satang.\n * Throws: TaxValidationError for a negative/non-finite value, a non-positive/non-safe-integer\n * holding period, or an unknown `acquisitionType`.\n *\n * @statute Thailand Revenue Code Section 48(4), Section 42(5), Section 49 bis.\n */\nexport function calculateRealEstateTransfer(\n\tinput: ThRealEstateTransferInput | undefined,\n): ThRealEstateBreakdown {\n\tif (input === undefined) return EMPTY_BREAKDOWN\n\tvalidateMonetaryAmount(input.grossTransferValue, ThSpecialRegimeField.RealEstateGrossValue)\n\tvalidateSafeIntegerCount(input.holdingYears, ThSpecialRegimeField.RealEstateHoldingYears, 1)\n\tconst acquisitionType = resolveChoice(\n\t\tinput.acquisitionType,\n\t\tThRealEstateAcquisitionType,\n\t\tThRealEstateAcquisitionType.Commercial,\n\t\tThSpecialRegimeField.RealEstateAcquisitionType,\n\t)\n\n\tconst years = effectiveHoldingYears(input.holdingYears)\n\tconst { basis, rate: expenseRate } = resolveRealEstateExpense(acquisitionType, years)\n\tconst gross = toSatang(input.grossTransferValue, ThSpecialRegimeField.RealEstateGrossValue)\n\tconst expense = applyPercentage(gross, expenseRate)\n\tconst netGain = gross - expense\n\tconst evaluated = evaluateThBracketSet(\n\t\tfromSatang(netGain),\n\t\tfromSatang(netGain),\n\t\tscaleBracketsByYears(years),\n\t)\n\tconst taxBeforeCap = toSatang(evaluated.totalTax)\n\tconst capped = applyRealEstateCap(taxBeforeCap, gross)\n\treturn {\n\t\tgrossTransferValue: input.grossTransferValue,\n\t\tacquisitionType,\n\t\texpenseBasis: basis,\n\t\trequestedHoldingYears: input.holdingYears,\n\t\teffectiveHoldingYears: years,\n\t\texpenseRate,\n\t\texpense: fromSatang(expense),\n\t\tnetGain: fromSatang(netGain),\n\t\tannualGain: fromSatang(roundHalfUp(netGain / years)),\n\t\tbrackets: evaluated.brackets,\n\t\tannualTax: fromSatang(roundHalfUp(taxBeforeCap / years)),\n\t\ttaxBeforeCap: fromSatang(taxBeforeCap),\n\t\ttaxCap: fromSatang(capped.taxCap),\n\t\twasClamped: capped.wasClamped,\n\t\ttax: fromSatang(capped.tax),\n\t}\n}\n","import { validateMonetaryAmount } from \"../../core/money\"\nimport { ThAssessableField, type ThAssessableIncomeInput } from \"../types\"\nimport { fromSatang, toSatang } from \"../units\"\n\n/** Special-regime amounts folded into the regular schedule. Units: THB major units. */\nexport interface ThSpecialIncome {\n\t/** Ineligible Section 48(5) lump sum added to Section 40(1). */\n\treadonly lumpSum: number\n\t/** Section 40(4) income elected `\"include\"` (final-withheld sources excluded). */\n\treadonly section40_4: number\n\t/** Section 47 bis dividend tax credit, added as gross-up to Section 40(4). */\n\treadonly dividendCredit: number\n}\n\n/**\n * Assembles the income that flows through the regular progressive schedule.\n *\n * Math: Section 40(1) = declared Section 40(1) + ineligible Section 48(5) lump sum;\n * Section 40(4) = included Section 40(4) income (final-withheld sources excluded)\n * + Section 47 bis gross-up. Other categories pass through untouched.\n * Units: THB major units; the Section 40(1) sum is performed in integer satang.\n * Throws: TaxValidationError if the declared Section 40(1) is negative or non-finite.\n *\n * @param income Declared Section 40 income.\n * @param special Lump sum, included Section 40(4) income, and dividend gross-up.\n * @statute Thailand Revenue Code Sections 40(1), 40(4), 47 bis, 48(5).\n */\nexport function buildRegularIncome(\n\tincome: ThAssessableIncomeInput | undefined,\n\tspecial: ThSpecialIncome,\n): ThAssessableIncomeInput {\n\tconst declared = income?.section40_1 ?? 0\n\tvalidateMonetaryAmount(declared, ThAssessableField.Section40_1)\n\tconst section40_1 =\n\t\ttoSatang(declared, ThAssessableField.Section40_1) +\n\t\ttoSatang(special.lumpSum, ThAssessableField.Section40_1)\n\tconst section40_4 =\n\t\ttoSatang(special.section40_4, ThAssessableField.Section40_4) +\n\t\ttoSatang(special.dividendCredit, ThAssessableField.Section40_4)\n\treturn {\n\t\t...income,\n\t\tsection40_1: fromSatang(section40_1),\n\t\tsection40_4: fromSatang(section40_4),\n\t}\n}\n","import {\n\tapplyPercentage,\n\tmultiplySafeInteger,\n\tvalidateMonetaryAmount,\n\tvalidateSafeIntegerCount,\n} from \"../../core/money\"\nimport { TH_2026_PARAMETERS } from \"../parameters\"\nimport {\n\ttype ThSeparateSheetBreakdown,\n\ttype ThSeparateSheetInput,\n\tThSeparateSheetPaymentType,\n\tThSpecialRegimeField,\n} from \"../types\"\nimport { fromSatang, toSatang } from \"../units\"\nimport { resolveChoice } from \"../validate\"\nimport { evaluateThBracketSet } from \"./bracket-eval\"\n\nconst EMPTY_INPUT: ThSeparateSheetInput = { amount: 0, yearsOfService: 0, lastDailyWage: 0 }\n\n/**\n * Calculates the Section 48(5) separate sheet for one-time severance/retirement lump sums.\n *\n * Purpose: Applies the severance exemption, then taxes an eligible remainder on its own schedule\n * or returns the ineligible remainder for regular treatment.\n * Math: For `paymentType` severance (the default) at ANY length of service:\n * exemption = min(amount, 400 * lastDailyWage, 600,000); `\"retirement\"` payments get none.\n * remainder = amount - exemption. With yearsOfService >= 5: fixed = min(7,000 * years, remainder);\n * percentage expense = 50% of (remainder - fixed), half-up; net = remainder - fixed - percentage,\n * taxed on the separate schedule (5%..35%, no 0% tier). With fewer than 5 years only the remainder\n * is returned in `addedToSection40_1` for the regular computation.\n * Units: Inputs and outputs in THB major units; arithmetic in integer satang.\n * Throws: TaxValidationError for negative/non-finite money, a non-safe-integer/negative `yearsOfService`,\n * an unknown `paymentType`, or a wage/years value whose scaled satang amount exceeds safe integers.\n *\n * @statute Thailand Revenue Code Section 48(5); Section 42(1) (severance exemption).\n */\nexport function calculateSeparateSheet(\n\tinput: ThSeparateSheetInput = EMPTY_INPUT,\n): ThSeparateSheetBreakdown {\n\tconst rules = TH_2026_PARAMETERS.separateSheet\n\tvalidateMonetaryAmount(input.amount, ThSpecialRegimeField.SeparateSheetAmount)\n\tvalidateMonetaryAmount(input.lastDailyWage, ThSpecialRegimeField.SeparateSheetLastDailyWage)\n\tvalidateSafeIntegerCount(input.yearsOfService, ThSpecialRegimeField.SeparateSheetYears)\n\tconst paymentType = resolveChoice(\n\t\tinput.paymentType,\n\t\tThSeparateSheetPaymentType,\n\t\tThSeparateSheetPaymentType.Severance,\n\t\tThSpecialRegimeField.SeparateSheetPaymentType,\n\t)\n\n\tconst years = input.yearsOfService\n\tconst isEligible = years >= rules.minYearsOfService\n\tconst amountSatang = toSatang(input.amount, ThSpecialRegimeField.SeparateSheetAmount)\n\tconst wageSatang = toSatang(input.lastDailyWage, ThSpecialRegimeField.SeparateSheetLastDailyWage)\n\tconst isSeverance = paymentType === ThSeparateSheetPaymentType.Severance\n\tconst wageCap = isSeverance\n\t\t? multiplySafeInteger(\n\t\t\t\twageSatang,\n\t\t\t\trules.exemptionWageDays,\n\t\t\t\tThSpecialRegimeField.SeparateSheetLastDailyWage,\n\t\t\t)\n\t\t: 0\n\tconst exemption = isSeverance\n\t\t? Math.min(amountSatang, wageCap, toSatang(rules.exemptionCeiling))\n\t\t: 0\n\tconst remainder = amountSatang - exemption\n\tconst fixedCap = multiplySafeInteger(\n\t\ttoSatang(rules.expensePerYearOfService),\n\t\tyears,\n\t\tThSpecialRegimeField.SeparateSheetYears,\n\t)\n\tconst fixed = isEligible ? Math.min(remainder, fixedCap) : 0\n\tconst percentage = isEligible ? applyPercentage(remainder - fixed, rules.remainderExpenseRate) : 0\n\tconst net = fromSatang(isEligible ? remainder - fixed - percentage : 0)\n\n\tconst evaluated = evaluateThBracketSet(net, net, rules.brackets)\n\treturn {\n\t\tamount: input.amount,\n\t\tyearsOfService: years,\n\t\tlastDailyWage: input.lastDailyWage,\n\t\tisEligible,\n\t\taddedToSection40_1: isEligible ? 0 : fromSatang(remainder),\n\t\twageBasedCap: fromSatang(wageCap),\n\t\texemptionCeiling: rules.exemptionCeiling,\n\t\tpaymentType,\n\t\texemption: fromSatang(exemption),\n\t\tremainder: fromSatang(remainder),\n\t\tfixedExpense: fromSatang(fixed),\n\t\tpercentageExpense: fromSatang(percentage),\n\t\tnetTaxable: net,\n\t\tbrackets: evaluated.brackets,\n\t\ttax: evaluated.totalTax,\n\t}\n}\n","import {\n\ttype ThAssessableIncomeInput,\n\tThBracketField,\n\ttype ThDividendTaxCreditBreakdown,\n\ttype ThFinalWithholdingBreakdown,\n\ttype ThRealEstateBreakdown,\n\ttype ThSeparateSheetBreakdown,\n\ttype ThTaxInput,\n} from \"../types\"\nimport { sumThAmounts } from \"../units\"\nimport { calculateDividendTaxCredit } from \"./dividend-credit\"\nimport { resolveSection40_4 } from \"./final-withholding\"\nimport { calculateRealEstateTransfer } from \"./real-estate\"\nimport { buildRegularIncome } from \"./regular-income\"\nimport { calculateSeparateSheet } from \"./separate-sheet\"\n\n/** Outcome of every special regime plus the income left for the regular schedule. */\nexport interface ThSpecialRegimes {\n\t/** Income flowing through the regular progressive schedule (Section 40(1)–(8)). */\n\treadonly regularIncome: ThAssessableIncomeInput\n\t/** Gross amounts taxed outside the regular schedule: final 40(4), eligible 48(5), 48(4). */\n\treadonly grossOutsideRegular: number\n\treadonly finalWithholding: ThFinalWithholdingBreakdown\n\treadonly dividendTaxCredit: ThDividendTaxCreditBreakdown\n\treadonly separateSheet: ThSeparateSheetBreakdown\n\treadonly realEstate: ThRealEstateBreakdown\n}\n\n/**\n * Resolves Section 40(4) elections, the Section 47 bis credit, the Section 48(5) separate sheet,\n * and the Section 48(4) real estate transfer, then assembles the remaining regular income.\n *\n * Math: Regular Section 40(4) = included sources + dividend gross-up; ineligible lump sum joins\n * Section 40(1). Gross outside the regular schedule = final-elected 40(4) + eligible lump sum (or, if\n * ineligible, its severance exemption) + real estate gross transfer value.\n * Units: THB major units; sums performed in integer satang.\n * Throws: TaxValidationError for any invalid special-regime input.\n *\n * @statute Thailand Revenue Code Sections 40(4), 47 bis, 48(4), 48(5).\n */\nexport function resolveSpecialRegimes(input: ThTaxInput): ThSpecialRegimes {\n\tconst section40_4 = resolveSection40_4(input.income?.section40_4)\n\tconst dividendTaxCredit = calculateDividendTaxCredit(\n\t\tsection40_4.includedDividends,\n\t\tsection40_4.corpTaxRate,\n\t)\n\tconst separateSheet = calculateSeparateSheet(input.separateSheet)\n\tconst realEstate = calculateRealEstateTransfer(input.realEstateTransfer)\n\treturn {\n\t\tregularIncome: buildRegularIncome(input.income, {\n\t\t\tlumpSum: separateSheet.addedToSection40_1,\n\t\t\tsection40_4: section40_4.includedAmount,\n\t\t\tdividendCredit: dividendTaxCredit.credit,\n\t\t}),\n\t\tgrossOutsideRegular: sumThAmounts(\n\t\t\t[\n\t\t\t\tsection40_4.finalWithholding.totalAmount,\n\t\t\t\tseparateSheet.isEligible ? separateSheet.amount : separateSheet.exemption,\n\t\t\t\trealEstate.grossTransferValue,\n\t\t\t],\n\t\t\tThBracketField.GrossIncome,\n\t\t),\n\t\tfinalWithholding: section40_4.finalWithholding,\n\t\tdividendTaxCredit,\n\t\tseparateSheet,\n\t\trealEstate,\n\t}\n}\n","import { TaxValidationError, thb, validateMonetaryAmount } from \"../../core/money\"\nimport type { TaxResult } from \"../../core/system\"\nimport { TH_2026_PARAMETERS } from \"../parameters\"\nimport { ThBracketField, type ThBreakdown, ThTaxField, type ThTaxInput } from \"../types\"\nimport { sumThAmounts } from \"../units\"\nimport { calculateAllowances } from \"./allowances\"\nimport { calculateAssessableIncome } from \"./assessable\"\nimport { evaluateThBrackets } from \"./bracket-eval\"\nimport { combineThLiability } from \"./combine\"\nimport { calculateExpenseDeductions } from \"./expenses\"\nimport { calculateMinimumGrossTax } from \"./minimum-tax\"\nimport { resolveSpecialRegimes } from \"./special-regimes\"\n\nfunction validateYear(year?: number): void {\n\tif (year !== undefined && year !== TH_2026_PARAMETERS.taxYear) {\n\t\tthrow new TaxValidationError(\n\t\t\tThTaxField.TaxYear,\n\t\t\tyear,\n\t\t\t\"Unsupported tax year; only 2026 is currently supported\",\n\t\t)\n\t}\n}\n\n/**\n * Executes full Thailand PIT calculation pipeline for ภ.ง.ด.90/91.\n *\n * @statute Thailand Revenue Code Sections 40, 42 bis, 47, 47 bis, 48(1)–(5).\n */\nexport function runThTaxPipeline(input: ThTaxInput = {}): TaxResult<\"THB\", ThBreakdown> {\n\tvalidateYear(input.taxYear)\n\tconst filingStatus = input.filingStatus ?? \"single\"\n\tconst withholding = input.withholdingTax ?? 0\n\tvalidateMonetaryAmount(withholding, ThTaxField.WithholdingTax)\n\n\tconst special = resolveSpecialRegimes(input)\n\tconst income = special.regularIncome\n\n\tconst assessable = calculateAssessableIncome(income)\n\tconst expenses = calculateExpenseDeductions(income, assessable)\n\tconst netIncomeAfterExpenses = Math.max(0, assessable.total - expenses.total)\n\n\tconst allowancesRes = calculateAllowances(\n\t\tinput.allowances,\n\t\tfilingStatus,\n\t\tassessable.total,\n\t\texpenses.total,\n\t\tassessable.section40_1,\n\t)\n\n\tconst evalBrackets = evaluateThBrackets(allowancesRes.netTaxableIncome, assessable.total)\n\tconst progressiveTax = evalBrackets.totalTax\n\n\tconst minimumGrossTax = calculateMinimumGrossTax(\n\t\tassessable.grossNonEmploymentIncome,\n\t\tprogressiveTax,\n\t)\n\n\tconst regularTax = minimumGrossTax.wasApplied ? minimumGrossTax.calculatedTax : progressiveTax\n\tconst combined = combineThLiability({\n\t\tregularTax,\n\t\tseparateSheetTax: special.separateSheet.tax,\n\t\trealEstateTax: special.realEstate.tax,\n\t\tfinalWithholdingTax: special.finalWithholding.totalTax,\n\t\tdividendTaxCredit: special.dividendTaxCredit.credit,\n\t\twithholdingTax: withholding,\n\t})\n\tconst grossIncome = sumThAmounts(\n\t\t[assessable.total, special.grossOutsideRegular],\n\t\tThBracketField.GrossIncome,\n\t)\n\tconst effectiveRate = grossIncome > 0 ? combined.totalCombinedTax / grossIncome : 0\n\n\tconst breakdown: ThBreakdown = {\n\t\tassessableIncome: assessable,\n\t\texpenseDeductions: expenses,\n\t\tnetIncomeAfterExpenses,\n\t\tallowances: allowancesRes.breakdown,\n\t\tnetTaxableIncome: allowancesRes.netTaxableIncome,\n\t\tprogressiveTax,\n\t\tprogressiveBrackets: evalBrackets.brackets,\n\t\tminimumGrossTax,\n\t\twithholdingTax: withholding,\n\t\tnetTaxPayable: combined.netTaxPayable,\n\t\tregularTax,\n\t\tseparateSheet: special.separateSheet,\n\t\trealEstate: special.realEstate,\n\t\tfinalWithholding: special.finalWithholding,\n\t\tdividendTaxCredit: special.dividendTaxCredit,\n\t\ttotalCombinedTax: combined.totalCombinedTax,\n\t}\n\n\treturn {\n\t\tjurisdiction: \"TH\",\n\t\ttaxYear: TH_2026_PARAMETERS.taxYear,\n\t\tcurrency: TH_2026_PARAMETERS.currency,\n\t\tgrossIncome: thb(grossIncome, ThBracketField.GrossIncome),\n\t\ttaxableIncome: thb(allowancesRes.netTaxableIncome, ThBracketField.TaxableIncome),\n\t\ttotalTax: thb(combined.totalCombinedTax, ThTaxField.TotalTax),\n\t\teffectiveRate,\n\t\tmarginalRate: evalBrackets.brackets.marginalRate,\n\t\tnetTaxPayable: thb(combined.netTaxPayable, ThTaxField.NetTaxPayable, {\n\t\t\tallowNegative: true,\n\t\t}),\n\t\tbreakdown,\n\t}\n}\n","import type { TaxResult } from \"../core/system\"\nimport { runThTaxPipeline } from \"./stages/pipeline\"\nimport type { ThBreakdown, ThTaxInput } from \"./types\"\n\n/**\n * Calculates Thailand Personal Income Tax (ภ.ง.ด.90/91) under Revenue Code.\n *\n * Defaults to tax year 2026 and single filing status.\n *\n * @param input Taxpayer assessable income, allowances, filing status, and withholding.\n * @returns Fully typed, immutable TaxResult with statutory breakdown.\n *\n * @statute Thailand Revenue Code Sections 40, 42 bis, 47, 48(1)–(2).\n */\nexport function calculateThTax(input: ThTaxInput = {}): TaxResult<\"THB\", ThBreakdown> {\n\treturn runThTaxPipeline(input)\n}\n","import type { TaxResult, TaxSystem } from \"../core/system\"\nimport { calculateThTax } from \"./calculate\"\nimport type { ThBreakdown, ThTaxInput } from \"./types\"\n\n/**\n * Thailand Tax System implementation for 2026.\n *\n * Implements TaxSystem contract for `@trajectonomics/tax/th`.\n */\nexport const th: TaxSystem<ThTaxInput, \"THB\", ThBreakdown> = {\n\tjurisdiction: \"TH\",\n\ttaxYear: 2026,\n\tcurrency: \"THB\",\n\tcalculate(input: ThTaxInput = {}): TaxResult<\"THB\", ThBreakdown> {\n\t\treturn calculateThTax(input)\n\t},\n}\n"],"mappings":"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"}
@@ -0,0 +1,170 @@
1
+ /**
2
+ * Specification for a single statutory tax bracket tier.
3
+ */
4
+ interface TaxBracket {
5
+ /** Upper threshold of this bracket tier in integer minor units (or null for unbounded top tier). */
6
+ readonly upTo: number | null;
7
+ /** Statutory tax rate ratio for this bracket tier (e.g. 0.05 for 5%). */
8
+ readonly rate: number;
9
+ }
10
+ /**
11
+ * Result of evaluating income falling within a single tax bracket tier.
12
+ */
13
+ interface BracketSlice {
14
+ /** Zero-based index of this bracket tier. */
15
+ readonly bracketIndex: number;
16
+ /** Lower income boundary of this slice in minor units. */
17
+ readonly lowerBound: number;
18
+ /** Upper income boundary of this slice in minor units (null if top tier). */
19
+ readonly upperBound: number | null;
20
+ /** Statutory rate applied to this slice. */
21
+ readonly rate: number;
22
+ /** Portions of taxable income falling strictly into this slice. */
23
+ readonly taxableAmount: number;
24
+ /** Tax computed for this slice in minor units. */
25
+ readonly tax: number;
26
+ }
27
+ /**
28
+ * Aggregated result of progressive bracket evaluation.
29
+ */
30
+ interface BracketEvaluationResult {
31
+ /** Total progressive tax computed across all slices in integer minor units. */
32
+ readonly totalTax: number;
33
+ /** Slice-by-slice detailed breakdown for auditability. */
34
+ readonly slices: readonly BracketSlice[];
35
+ /** Effective tax rate ratio (totalTax / grossIncome, or totalTax / taxableAmount). */
36
+ readonly effectiveRate: number;
37
+ /** Marginal tax rate on the next unit of ordinary income. */
38
+ readonly marginalRate: number;
39
+ }
40
+ /**
41
+ * Currencies the tax engine accounts in: one statutory currency per supported jurisdiction.
42
+ *
43
+ * Purpose: Compile-time gate for every monetary API. Callers map their own currency codes
44
+ * onto this union; there is no runtime validation or fallback.
45
+ * @statute Thai Revenue Code (THB) and US Internal Revenue Code (USD) both require
46
+ * amounts to be stated in the domestic currency.
47
+ */
48
+ type TaxSupportedCurrency = "THB" | "USD";
49
+ /**
50
+ * Result of clamping an amount against a statutory cap ceiling.
51
+ */
52
+ interface ClampedAmount {
53
+ /** Original requested or eligible amount before cap. */
54
+ readonly requested: number;
55
+ /** Allowed amount after statutory cap clamping. */
56
+ readonly allowed: number;
57
+ /** Effective statutory cap applied. */
58
+ readonly cap: number;
59
+ /** Whether the requested amount exceeded the cap and was clamped. */
60
+ readonly wasClamped: boolean;
61
+ }
62
+ /**
63
+ * Options defining a percentage-based and/or ceiling-capped statutory limit.
64
+ */
65
+ interface StatutoryCapOptions {
66
+ /** Base amount on which percentage is calculated (e.g. assessable income). */
67
+ readonly base?: number;
68
+ /** Statutory percentage rate ratio (e.g. 0.15 for 15%). */
69
+ readonly rate?: number;
70
+ /** Absolute monetary ceiling (e.g. 500,000 THB). */
71
+ readonly ceiling?: number;
72
+ /** Optional supported currency (THB or USD) for major/minor conversion. */
73
+ readonly currency?: TaxSupportedCurrency;
74
+ /** Diagnostic field name for error reporting. */
75
+ readonly field?: string;
76
+ }
77
+ /**
78
+ * Options controlling monetary validation.
79
+ */
80
+ interface ValidateAmountOptions {
81
+ /** If true, negative values will not throw an error (e.g. net payable after refundable credits). */
82
+ readonly allowNegative?: boolean;
83
+ }
84
+ /**
85
+ * Validates a monetary input against numerical boundaries (NaN, Infinity, negative).
86
+ *
87
+ * @param value Number to validate.
88
+ * @param field Diagnostic field name for error reporting.
89
+ * @param options Validation options.
90
+ * @throws {TaxValidationError} if value is NaN, non-finite, or negative without allowNegative.
91
+ */
92
+ declare function validateMonetaryAmount(value: number, field: string, options?: ValidateAmountOptions): void;
93
+ /**
94
+ * Branded nominal type representing a monetary value in a specific currency.
95
+ * Enforces compile-time type safety preventing accidental cross-currency mixing.
96
+ */
97
+ type Money<C extends TaxSupportedCurrency = TaxSupportedCurrency> = number & {
98
+ readonly __currencyBrand: C;
99
+ };
100
+ /**
101
+ * Constructs a branded Money value with immediate input boundary validation.
102
+ *
103
+ * @param amount Numerical amount in major units.
104
+ * @param _currency Currency code tag.
105
+ * @param field Diagnostic field name.
106
+ * @param options Validation options (e.g. allowNegative for refund balances).
107
+ * @returns Branded Money value.
108
+ * @throws {TaxValidationError} if amount is NaN, infinite, or negative without allowNegative.
109
+ */
110
+ declare function money<C extends TaxSupportedCurrency>(amount: number, _currency?: C, field?: string, options?: ValidateAmountOptions): Money<C>;
111
+ /**
112
+ * Constructs a branded Thai Baht (THB) money value with input boundary validation.
113
+ *
114
+ * @param amount Amount in Baht.
115
+ * @param field Diagnostic field name.
116
+ * @param options Validation options (e.g. allowNegative for refund balances).
117
+ * @returns Branded Money<"THB"> value.
118
+ * @throws {TaxValidationError} if amount is NaN, infinite, or negative without allowNegative.
119
+ */
120
+ declare function thb(amount: number, field?: string, options?: ValidateAmountOptions): Money<"THB">;
121
+ /**
122
+ * Constructs a branded US Dollar (USD) money value with input boundary validation.
123
+ *
124
+ * @param amount Amount in Dollars.
125
+ * @param field Diagnostic field name.
126
+ * @param options Validation options (e.g. allowNegative for refund balances).
127
+ * @returns Branded Money<"USD"> value.
128
+ * @throws {TaxValidationError} if amount is NaN, infinite, or negative without allowNegative.
129
+ */
130
+ declare function usd(amount: number, field?: string, options?: ValidateAmountOptions): Money<"USD">;
131
+ /**
132
+ * Standard ISO 3166-1 alpha-2 jurisdiction country code or string literal.
133
+ */
134
+ type JurisdictionCode = "TH" | "US" | (string & {});
135
+ /**
136
+ * Standardized TaxResult envelope returned by all jurisdiction calculation engines.
137
+ */
138
+ interface TaxResult<C extends TaxSupportedCurrency = TaxSupportedCurrency, TBreakdown = unknown> {
139
+ /** Statutory jurisdiction country code (e.g. TH, US). */
140
+ readonly jurisdiction: JurisdictionCode;
141
+ /** Applicable tax year (constant, never derived from Date). */
142
+ readonly taxYear: number;
143
+ /** Currency used in this calculation. */
144
+ readonly currency: C;
145
+ /** Gross income assessed in minor units. */
146
+ readonly grossIncome: Money<C>;
147
+ /** Taxable income after allowable deductions in minor units. */
148
+ readonly taxableIncome: Money<C>;
149
+ /** Total progressive tax liability computed before credits. */
150
+ readonly totalTax: Money<C>;
151
+ /** Effective tax rate ratio (totalTax / grossIncome). */
152
+ readonly effectiveRate: number;
153
+ /** Marginal tax rate on the next unit of ordinary income. */
154
+ readonly marginalRate: number;
155
+ /** Net tax payable after credits and withholding in minor units. */
156
+ readonly netTaxPayable: Money<C>;
157
+ /** Jurisdiction-specific detailed breakdown for auditability. */
158
+ readonly breakdown: TBreakdown;
159
+ }
160
+ /**
161
+ * Standardized contract that every jurisdiction tax engine implements.
162
+ */
163
+ interface TaxSystem<TInput, C extends TaxSupportedCurrency = TaxSupportedCurrency, TBreakdown = unknown> {
164
+ readonly jurisdiction: JurisdictionCode;
165
+ readonly taxYear: number;
166
+ readonly currency: C;
167
+ calculate(input: TInput): TaxResult<C, TBreakdown>;
168
+ }
169
+ export { money as a, ValidateAmountOptions as c, StatutoryCapOptions as d, TaxSupportedCurrency as f, TaxBracket as h, Money as i, validateMonetaryAmount as l, BracketSlice as m, TaxResult as n, thb as o, BracketEvaluationResult as p, TaxSystem as r, usd as s, JurisdictionCode as t, ClampedAmount as u };
170
+ //# sourceMappingURL=types-798sLpt6.d.cts.map
@@ -0,0 +1,170 @@
1
+ /**
2
+ * Specification for a single statutory tax bracket tier.
3
+ */
4
+ interface TaxBracket {
5
+ /** Upper threshold of this bracket tier in integer minor units (or null for unbounded top tier). */
6
+ readonly upTo: number | null;
7
+ /** Statutory tax rate ratio for this bracket tier (e.g. 0.05 for 5%). */
8
+ readonly rate: number;
9
+ }
10
+ /**
11
+ * Result of evaluating income falling within a single tax bracket tier.
12
+ */
13
+ interface BracketSlice {
14
+ /** Zero-based index of this bracket tier. */
15
+ readonly bracketIndex: number;
16
+ /** Lower income boundary of this slice in minor units. */
17
+ readonly lowerBound: number;
18
+ /** Upper income boundary of this slice in minor units (null if top tier). */
19
+ readonly upperBound: number | null;
20
+ /** Statutory rate applied to this slice. */
21
+ readonly rate: number;
22
+ /** Portions of taxable income falling strictly into this slice. */
23
+ readonly taxableAmount: number;
24
+ /** Tax computed for this slice in minor units. */
25
+ readonly tax: number;
26
+ }
27
+ /**
28
+ * Aggregated result of progressive bracket evaluation.
29
+ */
30
+ interface BracketEvaluationResult {
31
+ /** Total progressive tax computed across all slices in integer minor units. */
32
+ readonly totalTax: number;
33
+ /** Slice-by-slice detailed breakdown for auditability. */
34
+ readonly slices: readonly BracketSlice[];
35
+ /** Effective tax rate ratio (totalTax / grossIncome, or totalTax / taxableAmount). */
36
+ readonly effectiveRate: number;
37
+ /** Marginal tax rate on the next unit of ordinary income. */
38
+ readonly marginalRate: number;
39
+ }
40
+ /**
41
+ * Currencies the tax engine accounts in: one statutory currency per supported jurisdiction.
42
+ *
43
+ * Purpose: Compile-time gate for every monetary API. Callers map their own currency codes
44
+ * onto this union; there is no runtime validation or fallback.
45
+ * @statute Thai Revenue Code (THB) and US Internal Revenue Code (USD) both require
46
+ * amounts to be stated in the domestic currency.
47
+ */
48
+ type TaxSupportedCurrency = "THB" | "USD";
49
+ /**
50
+ * Result of clamping an amount against a statutory cap ceiling.
51
+ */
52
+ interface ClampedAmount {
53
+ /** Original requested or eligible amount before cap. */
54
+ readonly requested: number;
55
+ /** Allowed amount after statutory cap clamping. */
56
+ readonly allowed: number;
57
+ /** Effective statutory cap applied. */
58
+ readonly cap: number;
59
+ /** Whether the requested amount exceeded the cap and was clamped. */
60
+ readonly wasClamped: boolean;
61
+ }
62
+ /**
63
+ * Options defining a percentage-based and/or ceiling-capped statutory limit.
64
+ */
65
+ interface StatutoryCapOptions {
66
+ /** Base amount on which percentage is calculated (e.g. assessable income). */
67
+ readonly base?: number;
68
+ /** Statutory percentage rate ratio (e.g. 0.15 for 15%). */
69
+ readonly rate?: number;
70
+ /** Absolute monetary ceiling (e.g. 500,000 THB). */
71
+ readonly ceiling?: number;
72
+ /** Optional supported currency (THB or USD) for major/minor conversion. */
73
+ readonly currency?: TaxSupportedCurrency;
74
+ /** Diagnostic field name for error reporting. */
75
+ readonly field?: string;
76
+ }
77
+ /**
78
+ * Options controlling monetary validation.
79
+ */
80
+ interface ValidateAmountOptions {
81
+ /** If true, negative values will not throw an error (e.g. net payable after refundable credits). */
82
+ readonly allowNegative?: boolean;
83
+ }
84
+ /**
85
+ * Validates a monetary input against numerical boundaries (NaN, Infinity, negative).
86
+ *
87
+ * @param value Number to validate.
88
+ * @param field Diagnostic field name for error reporting.
89
+ * @param options Validation options.
90
+ * @throws {TaxValidationError} if value is NaN, non-finite, or negative without allowNegative.
91
+ */
92
+ declare function validateMonetaryAmount(value: number, field: string, options?: ValidateAmountOptions): void;
93
+ /**
94
+ * Branded nominal type representing a monetary value in a specific currency.
95
+ * Enforces compile-time type safety preventing accidental cross-currency mixing.
96
+ */
97
+ type Money<C extends TaxSupportedCurrency = TaxSupportedCurrency> = number & {
98
+ readonly __currencyBrand: C;
99
+ };
100
+ /**
101
+ * Constructs a branded Money value with immediate input boundary validation.
102
+ *
103
+ * @param amount Numerical amount in major units.
104
+ * @param _currency Currency code tag.
105
+ * @param field Diagnostic field name.
106
+ * @param options Validation options (e.g. allowNegative for refund balances).
107
+ * @returns Branded Money value.
108
+ * @throws {TaxValidationError} if amount is NaN, infinite, or negative without allowNegative.
109
+ */
110
+ declare function money<C extends TaxSupportedCurrency>(amount: number, _currency?: C, field?: string, options?: ValidateAmountOptions): Money<C>;
111
+ /**
112
+ * Constructs a branded Thai Baht (THB) money value with input boundary validation.
113
+ *
114
+ * @param amount Amount in Baht.
115
+ * @param field Diagnostic field name.
116
+ * @param options Validation options (e.g. allowNegative for refund balances).
117
+ * @returns Branded Money<"THB"> value.
118
+ * @throws {TaxValidationError} if amount is NaN, infinite, or negative without allowNegative.
119
+ */
120
+ declare function thb(amount: number, field?: string, options?: ValidateAmountOptions): Money<"THB">;
121
+ /**
122
+ * Constructs a branded US Dollar (USD) money value with input boundary validation.
123
+ *
124
+ * @param amount Amount in Dollars.
125
+ * @param field Diagnostic field name.
126
+ * @param options Validation options (e.g. allowNegative for refund balances).
127
+ * @returns Branded Money<"USD"> value.
128
+ * @throws {TaxValidationError} if amount is NaN, infinite, or negative without allowNegative.
129
+ */
130
+ declare function usd(amount: number, field?: string, options?: ValidateAmountOptions): Money<"USD">;
131
+ /**
132
+ * Standard ISO 3166-1 alpha-2 jurisdiction country code or string literal.
133
+ */
134
+ type JurisdictionCode = "TH" | "US" | (string & {});
135
+ /**
136
+ * Standardized TaxResult envelope returned by all jurisdiction calculation engines.
137
+ */
138
+ interface TaxResult<C extends TaxSupportedCurrency = TaxSupportedCurrency, TBreakdown = unknown> {
139
+ /** Statutory jurisdiction country code (e.g. TH, US). */
140
+ readonly jurisdiction: JurisdictionCode;
141
+ /** Applicable tax year (constant, never derived from Date). */
142
+ readonly taxYear: number;
143
+ /** Currency used in this calculation. */
144
+ readonly currency: C;
145
+ /** Gross income assessed in minor units. */
146
+ readonly grossIncome: Money<C>;
147
+ /** Taxable income after allowable deductions in minor units. */
148
+ readonly taxableIncome: Money<C>;
149
+ /** Total progressive tax liability computed before credits. */
150
+ readonly totalTax: Money<C>;
151
+ /** Effective tax rate ratio (totalTax / grossIncome). */
152
+ readonly effectiveRate: number;
153
+ /** Marginal tax rate on the next unit of ordinary income. */
154
+ readonly marginalRate: number;
155
+ /** Net tax payable after credits and withholding in minor units. */
156
+ readonly netTaxPayable: Money<C>;
157
+ /** Jurisdiction-specific detailed breakdown for auditability. */
158
+ readonly breakdown: TBreakdown;
159
+ }
160
+ /**
161
+ * Standardized contract that every jurisdiction tax engine implements.
162
+ */
163
+ interface TaxSystem<TInput, C extends TaxSupportedCurrency = TaxSupportedCurrency, TBreakdown = unknown> {
164
+ readonly jurisdiction: JurisdictionCode;
165
+ readonly taxYear: number;
166
+ readonly currency: C;
167
+ calculate(input: TInput): TaxResult<C, TBreakdown>;
168
+ }
169
+ export { money as a, ValidateAmountOptions as c, StatutoryCapOptions as d, TaxSupportedCurrency as f, TaxBracket as h, Money as i, validateMonetaryAmount as l, BracketSlice as m, TaxResult as n, thb as o, BracketEvaluationResult as p, TaxSystem as r, usd as s, JurisdictionCode as t, ClampedAmount as u };
170
+ //# sourceMappingURL=types-798sLpt6.d.mts.map