@classytic/ca-tax 0.0.20 → 0.0.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +62 -0
- package/README.md +16 -0
- package/dist/forms/index.d.mts +2 -2
- package/dist/forms/index.mjs +2 -2
- package/dist/forms.mjs +3277 -490
- package/dist/index.d.mts +835 -5
- package/dist/index.mjs +3 -3
- package/dist/index3.d.mts +271 -10
- package/dist/index4.d.mts +3 -3
- package/dist/t2/index.d.mts +3 -3
- package/dist/t2/index.mjs +3 -3
- package/dist/t2.mjs +202 -40
- package/docs/STATUTORY-DATA.md +109 -0
- package/package.json +3 -3
package/dist/t2.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Ft as AT1_SCHEDULE_21_POOLS, ot as SCHEDULE_1_LINE_BY_NUMBER } from "./forms.mjs";
|
|
2
2
|
//#region src/t2/rates/rate-book.ts
|
|
3
3
|
/**
|
|
4
4
|
* Resolve the table in effect for `taxYear` — the entry with the greatest
|
|
@@ -69,11 +69,34 @@ function extendRateBook(base, overrides) {
|
|
|
69
69
|
* days after 2020-06-30). See rates/general-rate-bands.ts for the full table.
|
|
70
70
|
* - research/sources/tra-spec/AT1-Chapter3-2026.4-full.txt §3.2.3.2 —
|
|
71
71
|
* BUSINESS_LIMIT $500,000, in effect since 2009-04-01.
|
|
72
|
+
* - research/sources/tra-forms/pdf/AT1SCH01-small-business-deduction-TRA11723.pdf
|
|
73
|
+
* page 1, "Calculation of the Alberta Small Business Deduction", column F
|
|
74
|
+
* ("SBD Rate") — SMALL_BUSINESS_RATE, by subtraction. See the gap note.
|
|
72
75
|
* gaps:
|
|
73
|
-
* - SMALL_BUSINESS_RATE (2%)
|
|
74
|
-
* no small-business rate at all: its only rate multipliers are
|
|
75
|
-
* general-rate bands above.
|
|
76
|
-
*
|
|
76
|
+
* - SMALL_BUSINESS_RATE (2%) is stated DIRECTLY by no vendored source. The AT1
|
|
77
|
+
* spec states no small-business rate at all: its only rate multipliers are
|
|
78
|
+
* the five general-rate bands above.
|
|
79
|
+
*
|
|
80
|
+
* It is now corroborated arithmetically, which is a real improvement on the
|
|
81
|
+
* "appears in NO vendored source" this note used to say. AT1 Schedule 1's
|
|
82
|
+
* own calculation table pre-prints an SBD rate per period, and that rate is
|
|
83
|
+
* the general rate MINUS the small-business rate:
|
|
84
|
+
*
|
|
85
|
+
* 2009-04-01 .070 = 10% − 3% 2019-07-01 .090 = 11% − 2%
|
|
86
|
+
* 2015-07-01 .090 = 12% − 3% 2020-01-01 .080 = 10% − 2%
|
|
87
|
+
* 2017-01-01 .100 = 12% − 2% 2020-07-01 .060 = 8% − 2%
|
|
88
|
+
*
|
|
89
|
+
* Six bands against the general table's five: the extra split at
|
|
90
|
+
* 2017-01-01 is the small-business rate itself moving from 3% to 2%, which
|
|
91
|
+
* the general table has no reason to record. So 2% is right for every year
|
|
92
|
+
* this book covers, and 3% would be needed for a pre-2017 year — which the
|
|
93
|
+
* book does not have an entry for and must not silently apply 2% to.
|
|
94
|
+
*
|
|
95
|
+
* `AT1_SCHEDULE_1_RATE_PERIODS` carries the printed table, and a test
|
|
96
|
+
* cross-checks it against these two constants in both directions, so a
|
|
97
|
+
* future edit to either cannot quietly break the agreement. Still worth
|
|
98
|
+
* confirming against the Alberta Corporate Tax Act before filing a
|
|
99
|
+
* pre-2017 year.
|
|
77
100
|
*/
|
|
78
101
|
const AB_TAX_2024 = Object.freeze({
|
|
79
102
|
GENERAL_RATE: .08,
|
|
@@ -257,6 +280,67 @@ function computeAlbertaTax(input, rates = AB_TAX_2024) {
|
|
|
257
280
|
};
|
|
258
281
|
}
|
|
259
282
|
//#endregion
|
|
283
|
+
//#region src/t2/at1/schedules/schedule21-current-year-loss.ts
|
|
284
|
+
/** Lines that are subtotals on the printed form and have no NetFile line code. */
|
|
285
|
+
const SCHEDULE_21_PART_1_PRINT_ONLY = ["013", "015"];
|
|
286
|
+
/**
|
|
287
|
+
* Net capital losses deducted — the CAPITAL pool's applied amount times the
|
|
288
|
+
* inclusion rate.
|
|
289
|
+
*
|
|
290
|
+
* Shared, not duplicated: Schedule 21 line 003 and Schedule 12 line 066 must
|
|
291
|
+
* state the same figure ("Schedule 21 line 061 x Inclusion Rate" is printed over
|
|
292
|
+
* 066), and computing it in two places is how they would drift.
|
|
293
|
+
*/
|
|
294
|
+
function allowableCapitalLossApplied(applied, inclusionRate = .5) {
|
|
295
|
+
return Math.round(inclusionRate * Math.max(0, Math.round(applied)));
|
|
296
|
+
}
|
|
297
|
+
/** A positive whole-dollar amount — every deduction in this part is "entered as a positive". */
|
|
298
|
+
const positive = (v) => Math.max(0, Math.round(v ?? 0));
|
|
299
|
+
/** Normalise −0, which `Math.min(0, …)` and negation both produce. */
|
|
300
|
+
const noNegativeZero = (v) => v + 0;
|
|
301
|
+
function computeCurrentYearNonCapitalLoss(input) {
|
|
302
|
+
const issues = [];
|
|
303
|
+
const netIncome = Math.round(input.netIncome);
|
|
304
|
+
const optional = (v) => v === void 0 ? void 0 : positive(v);
|
|
305
|
+
const l002 = optional(input.rifeDeducted);
|
|
306
|
+
const l003 = input.capitalLossApplied === void 0 ? void 0 : allowableCapitalLossApplied(input.capitalLossApplied, input.inclusionRate);
|
|
307
|
+
const l005 = optional(input.taxableDividendsDeductible);
|
|
308
|
+
const l007 = optional(input.partVI1TaxDeductible);
|
|
309
|
+
const l011 = optional(input.prospectorsShares);
|
|
310
|
+
const l012 = optional(input.nonQualifiedSecurities);
|
|
311
|
+
const l017 = positive(input.section110_5Additions);
|
|
312
|
+
const l019 = optional(input.farmLoss);
|
|
313
|
+
const l013 = [
|
|
314
|
+
l002,
|
|
315
|
+
l003,
|
|
316
|
+
l005,
|
|
317
|
+
l007,
|
|
318
|
+
l011,
|
|
319
|
+
l012
|
|
320
|
+
].reduce((n, v) => n + (v ?? 0), 0);
|
|
321
|
+
const l015 = noNegativeZero(Math.min(0, netIncome - l013));
|
|
322
|
+
const l021 = noNegativeZero(Math.min(0, l015 - l017 + (l019 ?? 0)));
|
|
323
|
+
if (input.federalSection110_5Additions !== void 0 && l017 > positive(input.federalSection110_5Additions)) issues.push(`AT1 Schedule 21 line 017 (${l017}) exceeds federal T2 line 355 (${positive(input.federalSection110_5Additions)}). The specification allows this only to the extent it does not increase AT1 lines 070/071 — confirm before filing.`);
|
|
324
|
+
return {
|
|
325
|
+
lines: {
|
|
326
|
+
"001": netIncome,
|
|
327
|
+
...l002 !== void 0 ? { "002": l002 } : {},
|
|
328
|
+
...l003 !== void 0 ? { "003": l003 } : {},
|
|
329
|
+
...l005 !== void 0 ? { "005": l005 } : {},
|
|
330
|
+
...l007 !== void 0 ? { "007": l007 } : {},
|
|
331
|
+
...l011 !== void 0 ? { "011": l011 } : {},
|
|
332
|
+
...l012 !== void 0 ? { "012": l012 } : {},
|
|
333
|
+
"013": l013,
|
|
334
|
+
"015": l015,
|
|
335
|
+
"017": l017,
|
|
336
|
+
...l019 !== void 0 ? { "019": l019 } : {},
|
|
337
|
+
"021": l021
|
|
338
|
+
},
|
|
339
|
+
currentYearLoss: noNegativeZero(-l021),
|
|
340
|
+
issues
|
|
341
|
+
};
|
|
342
|
+
}
|
|
343
|
+
//#endregion
|
|
260
344
|
//#region src/t2/at1/filing/at1-schedule-line-items.ts
|
|
261
345
|
/**
|
|
262
346
|
* Schedules this module can put into a filing payload. Exported so callers and
|
|
@@ -377,7 +461,27 @@ function schedule17Values(result) {
|
|
|
377
461
|
values
|
|
378
462
|
};
|
|
379
463
|
}
|
|
380
|
-
/**
|
|
464
|
+
/**
|
|
465
|
+
* Field ids per category, from the verified field map.
|
|
466
|
+
*
|
|
467
|
+
* ── KNOWN GAP: 054 is filed short by line 053 ───────────────────────────────
|
|
468
|
+
*
|
|
469
|
+
* Shares are not like the other five. The page prints TWO grids: the shares
|
|
470
|
+
* grid leaves its column D unnumbered, and the shares gain is struck below it
|
|
471
|
+
* at 054 *after* line 053 has added federal Schedule 6 line 160 in. See
|
|
472
|
+
* `../forms/schedule18.ts`, where 054 is now modelled as that sum.
|
|
473
|
+
*
|
|
474
|
+
* This builder still files the raw `category.gain` at 054, and files nothing
|
|
475
|
+
* at 053 at all, because `AlbertaSchedule18Input` has no field for the federal
|
|
476
|
+
* amount — so a corporation with a Schedule 6 line 160 figure transmits a
|
|
477
|
+
* shares gain missing it, and `computeAlbertaSchedule18` carries the same
|
|
478
|
+
* shortfall into line 062 and on to the taxable gain at 076.
|
|
479
|
+
*
|
|
480
|
+
* Closing it needs an optional `federalScheduleSixSharesAddition` on the input
|
|
481
|
+
* (defaulting to 0, so existing callers are unaffected), added to the shares
|
|
482
|
+
* gain in the compute and emitted here as 053. Left as a separate change
|
|
483
|
+
* rather than smuggled into a form-definition fix.
|
|
484
|
+
*/
|
|
381
485
|
const S18_CATEGORY_FIELDS = {
|
|
382
486
|
shares: {
|
|
383
487
|
proceeds: "002",
|
|
@@ -651,7 +755,7 @@ function schedule4970Values(result) {
|
|
|
651
755
|
*/
|
|
652
756
|
function schedule12LossDeductions(alberta, federal = {}, inclusionRate = .5) {
|
|
653
757
|
const applied = (c) => Math.max(0, Math.round(c?.appliedCurrentYear ?? 0));
|
|
654
|
-
const allowable = (c) =>
|
|
758
|
+
const allowable = (c) => allowableCapitalLossApplied(c?.appliedCurrentYear ?? 0, inclusionRate);
|
|
655
759
|
return {
|
|
656
760
|
nonCapital: {
|
|
657
761
|
alberta: applied(alberta.nonCapital),
|
|
@@ -836,7 +940,14 @@ function schedule21Values$1(input) {
|
|
|
836
940
|
lineItemId: at1LineItemId("021", fieldId),
|
|
837
941
|
value
|
|
838
942
|
});
|
|
839
|
-
if (input.
|
|
943
|
+
if (input.currentYearLoss) {
|
|
944
|
+
const printOnly = new Set(SCHEDULE_21_PART_1_PRINT_ONLY);
|
|
945
|
+
for (const [line, value] of Object.entries(input.currentYearLoss.lines)) {
|
|
946
|
+
if (printOnly.has(line) || value === void 0) continue;
|
|
947
|
+
if (!(line === "001" || line === "017" || line === "021" || line === "003") && value === 0) continue;
|
|
948
|
+
put(line, value);
|
|
949
|
+
}
|
|
950
|
+
} else if (input.currentYearNonCapitalLoss !== void 0) put("021", -Math.abs(input.currentYearNonCapitalLoss) + 0);
|
|
840
951
|
for (const [inputKey, poolKey] of Object.entries(POOL_KEY)) {
|
|
841
952
|
const c = input[inputKey];
|
|
842
953
|
if (!c) continue;
|
|
@@ -1111,32 +1222,20 @@ function schedule20Values(input) {
|
|
|
1111
1222
|
put("046", input.maximum.gainsComponent);
|
|
1112
1223
|
put("048", input.maximum.maximumDeduction);
|
|
1113
1224
|
}
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
lineItemId: at1LineItemId("020", "092"),
|
|
1122
|
-
value: cf.charitable
|
|
1123
|
-
});
|
|
1124
|
-
if (cf.toCanadaOrProvince !== void 0) values.push({
|
|
1125
|
-
lineItemId: at1LineItemId("020", "094"),
|
|
1126
|
-
value: cf.toCanadaOrProvince
|
|
1127
|
-
});
|
|
1128
|
-
if (cf.culturalProperty !== void 0) values.push({
|
|
1129
|
-
lineItemId: at1LineItemId("020", "096"),
|
|
1130
|
-
value: cf.culturalProperty
|
|
1131
|
-
});
|
|
1132
|
-
if (cf.ecologicalLand !== void 0) values.push({
|
|
1133
|
-
lineItemId: at1LineItemId("020", "098"),
|
|
1134
|
-
value: cf.ecologicalLand
|
|
1135
|
-
});
|
|
1136
|
-
if (cf.medicine !== void 0) values.push({
|
|
1137
|
-
lineItemId: at1LineItemId("020", "100"),
|
|
1138
|
-
value: cf.medicine
|
|
1225
|
+
let occurrence = 0;
|
|
1226
|
+
for (const cf of input.gifts?.carryforward ?? []) {
|
|
1227
|
+
if (cf.yearOfOrigin === void 0) continue;
|
|
1228
|
+
occurrence += 1;
|
|
1229
|
+
const row = (field, value) => values.push({
|
|
1230
|
+
lineItemId: at1LineItemId("020", field, occurrence),
|
|
1231
|
+
value
|
|
1139
1232
|
});
|
|
1233
|
+
row("090", cf.yearOfOrigin);
|
|
1234
|
+
if (cf.charitable !== void 0) row("092", cf.charitable);
|
|
1235
|
+
if (cf.toCanadaOrProvince !== void 0) row("094", cf.toCanadaOrProvince);
|
|
1236
|
+
if (cf.culturalProperty !== void 0) row("096", cf.culturalProperty);
|
|
1237
|
+
if (cf.ecologicalLand !== void 0) row("098", cf.ecologicalLand);
|
|
1238
|
+
if (cf.medicine !== void 0) row("100", cf.medicine);
|
|
1140
1239
|
}
|
|
1141
1240
|
return {
|
|
1142
1241
|
scheduleId: "020",
|
|
@@ -3049,7 +3148,15 @@ function computeAlbertaReturn(input) {
|
|
|
3049
3148
|
federalTaxableIncome: input.federalTaxableIncome,
|
|
3050
3149
|
allocationFactor,
|
|
3051
3150
|
activeBusinessIncome: input.activeBusinessIncome,
|
|
3052
|
-
...input.businessLimit !== void 0 ? { businessLimit: input.businessLimit } : {}
|
|
3151
|
+
...input.businessLimit !== void 0 ? { businessLimit: input.businessLimit } : {},
|
|
3152
|
+
...input.status !== void 0 ? { status: input.status } : {},
|
|
3153
|
+
...input.wasCcpcThroughoutYear !== void 0 ? { wasCcpcThroughoutYear: input.wasCcpcThroughoutYear } : {},
|
|
3154
|
+
...input.isAssociated !== void 0 ? { isAssociated: input.isAssociated } : {},
|
|
3155
|
+
...input.allocatedBusinessLimit !== void 0 ? { allocatedBusinessLimit: input.allocatedBusinessLimit } : {},
|
|
3156
|
+
...input.periodStart && input.periodEnd ? {
|
|
3157
|
+
periodStart: input.periodStart,
|
|
3158
|
+
periodEnd: input.periodEnd
|
|
3159
|
+
} : {}
|
|
3053
3160
|
}, rates);
|
|
3054
3161
|
const issues = [];
|
|
3055
3162
|
let iegGroup;
|
|
@@ -3105,14 +3212,25 @@ function computeAlbertaReturn(input) {
|
|
|
3105
3212
|
}
|
|
3106
3213
|
const schedulePayloads = [];
|
|
3107
3214
|
const sched = input.schedules;
|
|
3215
|
+
const schedule4 = sched?.foreignInvestmentTaxCredit;
|
|
3216
|
+
const otherDeductionsCredits = sched?.otherDeductionsCredits ? computeSchedule3({
|
|
3217
|
+
...sched.otherDeductionsCredits,
|
|
3218
|
+
mad: {
|
|
3219
|
+
taxPayableBeforeDeduction: albertaTax.basicTax,
|
|
3220
|
+
line070: albertaTax.smallBusinessDeduction,
|
|
3221
|
+
line071: input.manufacturingDeduction ?? 0,
|
|
3222
|
+
line072: schedule4?.totalAllowableCredit ?? 0,
|
|
3223
|
+
line074: input.politicalContributionsTaxCredit ?? 0
|
|
3224
|
+
}
|
|
3225
|
+
}) : void 0;
|
|
3108
3226
|
if (sched?.smallBusinessDeduction) {
|
|
3109
3227
|
schedulePayloads.push(schedule1Values$1(sched.smallBusinessDeduction));
|
|
3110
3228
|
issues.push(...sched.smallBusinessDeduction.result.issues);
|
|
3111
3229
|
}
|
|
3112
3230
|
if (sched?.allocation) schedulePayloads.push(schedule2Values$1(sched.allocation));
|
|
3113
|
-
if (
|
|
3114
|
-
schedulePayloads.push(schedule3Values(
|
|
3115
|
-
issues.push(...
|
|
3231
|
+
if (otherDeductionsCredits) {
|
|
3232
|
+
schedulePayloads.push(schedule3Values(otherDeductionsCredits));
|
|
3233
|
+
issues.push(...otherDeductionsCredits.issues);
|
|
3116
3234
|
}
|
|
3117
3235
|
if (sched?.foreignInvestmentTaxCredit) {
|
|
3118
3236
|
schedulePayloads.push(schedule4Values(sched.foreignInvestmentTaxCredit));
|
|
@@ -3178,9 +3296,12 @@ const at1Engine = {
|
|
|
3178
3296
|
return raw;
|
|
3179
3297
|
},
|
|
3180
3298
|
compute(input) {
|
|
3299
|
+
const iso = (d) => d.toISOString().slice(0, 10);
|
|
3181
3300
|
const result = computeAlbertaReturn({
|
|
3182
3301
|
...input,
|
|
3183
|
-
taxYear: input.taxYear ?? input.period.end.getFullYear()
|
|
3302
|
+
taxYear: input.taxYear ?? input.period.end.getFullYear(),
|
|
3303
|
+
periodStart: input.periodStart ?? iso(input.period.start),
|
|
3304
|
+
periodEnd: input.periodEnd ?? iso(input.period.end)
|
|
3184
3305
|
});
|
|
3185
3306
|
return {
|
|
3186
3307
|
jurisdiction: "CA-AB",
|
|
@@ -3726,6 +3847,23 @@ const AT1_EDI_LINE_ITEMS = [
|
|
|
3726
3847
|
fmt: "text"
|
|
3727
3848
|
}
|
|
3728
3849
|
];
|
|
3850
|
+
/**
|
|
3851
|
+
* Every Line-Item ID the EDI schedule can file.
|
|
3852
|
+
*
|
|
3853
|
+
* Derived from the table above rather than restated, so the two cannot
|
|
3854
|
+
* disagree. Exported because a consumer's INTERFACE needs it: the return
|
|
3855
|
+
* editor labels these boxes "Software Certification Code (line EDI001)", and
|
|
3856
|
+
* that number is a promise to the preparer that what they type lands there.
|
|
3857
|
+
*
|
|
3858
|
+
* Every other schedule's citations are checked against a `FormDefinition`.
|
|
3859
|
+
* The EDI schedule has none — it exists only in the Net File XML, and spec
|
|
3860
|
+
* §3.3.6.1 is not vendored into this package — so this list is what makes the
|
|
3861
|
+
* claim checkable at all. A wrong line number is worse than no line number:
|
|
3862
|
+
* it files a real figure against the wrong box and nothing looks broken.
|
|
3863
|
+
*/
|
|
3864
|
+
const AT1_EDI_LINES = AT1_EDI_LINE_ITEMS.map((li) => li.id);
|
|
3865
|
+
/** The FIELD half of each EDI id — `EDI001001` → `001`, which is what a preparer sees. */
|
|
3866
|
+
const AT1_EDI_FIELDS = AT1_EDI_LINES.map((id) => id.slice(3, 6));
|
|
3729
3867
|
//#endregion
|
|
3730
3868
|
//#region src/t2/at1/filing/at1-netfile-renderer.ts
|
|
3731
3869
|
/**
|
|
@@ -5508,7 +5646,7 @@ const nn$22 = (v) => Math.max(0, v ?? 0);
|
|
|
5508
5646
|
* side is a real override and must not be swallowed by the default, so the
|
|
5509
5647
|
* check is on `undefined` rather than falsiness.
|
|
5510
5648
|
*/
|
|
5511
|
-
function
|
|
5649
|
+
function resolveCarryforwardRow(federal, alberta) {
|
|
5512
5650
|
const f = federal ?? {};
|
|
5513
5651
|
const a = alberta ?? {};
|
|
5514
5652
|
const pick = (k) => a[k] !== void 0 ? a[k] : f[k];
|
|
@@ -5526,6 +5664,30 @@ function resolveCarryforward(federal, alberta) {
|
|
|
5526
5664
|
}
|
|
5527
5665
|
return out;
|
|
5528
5666
|
}
|
|
5667
|
+
/**
|
|
5668
|
+
* Resolve every carryforward row, and drop the ones that say nothing.
|
|
5669
|
+
*
|
|
5670
|
+
* The row count is the LONGER of the two sides: an Alberta-only year (a
|
|
5671
|
+
* carryforward federal does not recognise) must survive, and so must a federal
|
|
5672
|
+
* year with no Alberta override.
|
|
5673
|
+
*
|
|
5674
|
+
* A row that resolves to `{}` is discarded rather than filed. `schedule20Values`
|
|
5675
|
+
* gates each occurrence on `yearOfOrigin` being present — 090 is mandatory
|
|
5676
|
+
* whenever any of 092-100 has a value — so an empty row would either transmit
|
|
5677
|
+
* nothing and still consume an occurrence number, shifting every row after it,
|
|
5678
|
+
* or transmit figures with no year against them. Neither is a return TRA can
|
|
5679
|
+
* read.
|
|
5680
|
+
*/
|
|
5681
|
+
function resolveCarryforward(federal, alberta) {
|
|
5682
|
+
const f = federal ?? [];
|
|
5683
|
+
const a = alberta ?? [];
|
|
5684
|
+
const rows = [];
|
|
5685
|
+
for (let i = 0; i < Math.max(f.length, a.length); i += 1) {
|
|
5686
|
+
const row = resolveCarryforwardRow(f[i], a[i]);
|
|
5687
|
+
if (Object.keys(row).length > 0) rows.push(row);
|
|
5688
|
+
}
|
|
5689
|
+
return rows;
|
|
5690
|
+
}
|
|
5529
5691
|
function computeSchedule20(input) {
|
|
5530
5692
|
const openingBalance = nn$22(input.openingBalance);
|
|
5531
5693
|
const expired = nn$22(input.expired);
|
|
@@ -10068,4 +10230,4 @@ function computeMpDeduction(input, rates = MP_RATES_2024) {
|
|
|
10068
10230
|
};
|
|
10069
10231
|
}
|
|
10070
10232
|
//#endregion
|
|
10071
|
-
export { computeSchedule6 as $,
|
|
10233
|
+
export { computeSchedule6 as $, resolveAlbertaTaxRates as $i, resolveCcaRates as $n, computeIeg as $r, dividendsDeductibleS112 as $t, LRIP_INVESTMENT_CORPORATION_MULTIPLE as A, at1LineItemId as Ai, AT1_RESERVE_LINES as An, renderRsiHeader as Ar, computeExcessIfe as At, computeZetm as B, schedule21Values$1 as Bi, CLASS_14_1_MINIMUM_DEDUCTION as Bn, assertAt1MandatoryComplete as Br, assessEifel as Bt, runConformance as C, computeSchedule3 as Ci, AT1_DONATION_INCOME_RATE as Cn, RSI_NEGATIVE_PREFIX as Cr, EIFEL_STANDARD_RATIO_FROM as Ct, SCHEDULE_88_MAX_URLS as D, computeAt4970 as Di, SECTION_34_2_GROSS_UP as Dn, formatRsiDate as Dr, computeBorrowings as Dt, computeSchedule101 as E, computeAllocationFactor as Ei, AT1_DISPOSITION_CATEGORIES as En, formatRsiAmount as Er, ratioOfPermissibleExpenses as Et, LARGE_CORPORATION_THRESHOLD as F, schedule16Values as Fi, computeAlbertaSchedule13 as Fn, AT1_EDI_LINES as Fr, computeLossPortionFromIfe as Ft, computeCde as G, allowableCapitalLossApplied as Gi, computeClass13 as Gn, xmlEscape as Gr, PROVINCE_RATES_2024 as Gt, computeBusinessLimitAllocation as H, schedule2Values$1 as Hi, CLASS_14_1_TRANSITIONAL_RATE as Hn, assertCriticalFields as Hr, computeRifeUnderSubsection111_8 as Ht, computeTaxableCapital as I, schedule17Values as Ii, UnsupportedCcaClassError as In, At1CriticalFieldMissingError as Ir, computePartnershipIfe as It, computeCumulativeForeignResource as J, computeAlbertaSbd as Ji, computeClass141RecaptureReduction as Jn, allocateIegEvenly as Jr, resolveProvinceRates as Jt, computeCee as K, computeCurrentYearNonCapitalLoss as Ki, computeClass14 as Kn, at1Engine as Kr, PROVINCE_RATE_BOOK as Kt, computeSchedule31 as L, schedule18Values as Li, computeCcaClass as Ln, At1MandatoryFieldMissingError as Lr, computePartnershipIfeAddBack as Lt, computeSchedule54 as M, schedule12LossDeductions as Mi, computeAlbertaSchedule17 as Mn, renderAt1NetFile as Mr, computeInterestAndFinancingExpenses as Mt, computeGrip as N, schedule12Values as Ni, assistanceFrom as Nn, AT1_CRITICAL_MANDATORY_FIELDS as Nr, computeInterestAndFinancingRevenues as Nt, normalizeSchedule88 as O, AT1_SCHEDULES_WITHOUT_BUILDERS as Oi, computeAlbertaSchedule18 as On, formatRsiText as Or, computeCapitalizedIfe as Ot, computeSchedule43 as P, schedule13Values as Pi, computeAlbertaSchedule16 as Pn, AT1_EDI_FIELDS as Pr, computeLoans as Pt, computeSpecifiedForeignExploration as Q, AB_TAX_RATE_BOOK as Qi, isDecliningBalanceClass as Qn, IEG_2024 as Qr, computeTaxableIncome as Qt, ITC_RECAPTURE_PERIOD_YEARS as R, schedule1Values$1 as Ri, computeCcaSchedule as Rn, At1TaxPayableMismatchError as Rr, computeResourceIfe as Rt, formatConformanceReport as S, schedule4Values as Si, AT1_DONATION_GAIN_RATE as Sn, RSI_DELIMITER as Sr, EIFEL_STANDARD_RATIO as St, computeFederalT2 as T, SINGLE_JURISDICTION_ALBERTA_FACTOR as Ti, computeSchedule20 as Tn, RsiLineItemError as Tr, computeEifelLimitation as Tt, computeSchedule21 as U, schedule4970Values as Ui, MAX_LEASEHOLD_PERIODS as Un, at1TaxPayableDeductions as Ur, FOREIGN_TAX_CREDIT_GROSS_UP as Ut, allocateEvenly as V, schedule29Values as Vi, CLASS_14_1_RECAPTURE_REDUCTION_RATE as Vn, assertAt1TaxPayableReconciles as Vr, computeEifelCapacity as Vt, computeSchedule13 as W, SCHEDULE_21_PART_1_PRINT_ONLY as Wi, MIN_LEASEHOLD_PERIODS as Wn, at1YesNo as Wr, computeAdjustedTaxableIncome as Wt, computeForeignExploration as X, computeDayWeightedGeneralTax as Xi, CCA_DECLINING_BALANCE_RATES_2024 as Xn, computeIegAgreement as Xr, dayWeightedRate as Xt, computeDepletion as Y, AB_GENERAL_RATE_BANDS as Yi, leaseholdPeriods as Yn, allocateIegExpenditureLimit as Yr, blendProvinceRateTable as Yt, computeSchedule12ResourceDeductions as Z, AB_TAX_2024 as Zi, CCA_RATE_BOOK as Zn, computeIegGroupFigures as Zr, charitableDonationsDeduction as Zt, QC_TAX_2024 as _, computeFedeSuccessor as _i, computeRifeContinuity as _n, toRsiHeader as _r, terminalLossDeduction as _t, computeCanadianMPProfits as a, computeCcogpeRegular as ai, computeAggregateInvestmentIncome as an, albertaDispositionAdjustments as ar, computePart4Rdtoh as at, T2_LINE_META as b, schedule15Values as bi, computeLimitedPartnershipLossRow as bn, toRsiSchedule as br, partVI1DeductionMultiple as bt, computeSmallManufacturerTest as c, computeCdeSuccessor as ci, CORP_TAX_2024 as cn, albertaResourceDeductionDifference as cr, amortizationAddBack as ct, t2Engine as d, computeCfreRegular as di, federalSchedulePayloads as dn, reconcileAlbertaNetIncome as dr, computeSchedule1 as dt, earliestRateYear as ea, computeIegBaseAmount as ei, netCapitalLossApplied as en, albertaAbilDifference as er, computeProvincialAllocation as et, renderCo17DraftReturn as f, computeCfreSuccessor as fi, parseT2LineItemId as fn, LossCarrybackError as fr, deferredIncomeTaxProvisionAddBack as ft, computeQuebecTax as g, computeFedeRegular as gi, computeOtherLossByYearOfOrigin as gn, validateAt1Transmitter as gr, recaptureAddBack as gt, computeQuebecAllocationFactor as h, computeEdaSuccessor as hi, computeNonCapitalLossByYearOfOrigin as hn, assertAt1TransmitterValid as hr, mealsAndEntertainmentAddBack as ht, SMALL_MANUFACTURER_INCOME_THRESHOLD as i, resolveRates as ia, computeAlbertaSchedule15 as ii, computeAdjustedAggregateInvestmentIncome as in, albertaCurrentYearLoss as ir, REFUNDABLE_PART_I_RATE as it, LRIP_INVESTMENT_INCOME_FACTOR as j, schedule10Values as ji, AT1_RESERVE_TOTAL_LINES as jn, renderRsiLineItem as jr, computeExemptIfe as jt, computeSchedule55 as k, AT1_SCHEDULES_WITH_BUILDERS as ki, AT1_RESERVE_KINDS as kn, renderAt1Rsi as kr, computeClause95Amounts as kt, computeT2Settlement as l, computeCeeRegular as li, CORP_TAX_RATE_BOOK as ln, albertaTerminalLossDifference as lr, assertSchedule1Fileable as lt, computeQuebecReturn as m, computeEdaRegular as mi, T2_CERTIFICATION_FIXTURES as mn, At1TransmitterInvalidError as mr, incomeTaxProvisionAddBack as mt, MP_GROSS_REVENUE_THRESHOLD as n, hasExactRateYear as na, computeIegEligibleExpenditures as ni, computeCcpcActiveBusinessTax as nn, albertaCcaDifference as nr, computeSchedule4Losses as nt, computeMpDeduction as o, computeCcogpeSuccessor as oi, computeBusinessLimit as on, albertaRecaptureDifference as or, computeSchedule2 as ot, co17Engine as p, computeCmedb as pi, t2LineItemId as pn, computeLossCarryback as pr, findSchedule1LineDefects as pt, computeCogpe as q, computeAlbertaTax as qi, computeClass141AdditionalAllowance as qn, computeAlbertaReturn as qr, isSchedule5Province as qt, MP_RATES_2024 as r, latestRateYear as ra, iegT661SourceLine as ri, computePartITax as rn, albertaCcaScheduleAdjustments as rr, PART_IV_RATE as rt, computePart2MPProfits as s, computeCdeRegular as si, computeSBD as sn, albertaReserveDifference as sr, Schedule1NotFileableError as st, MP_EXCLUDED_ACTIVITIES as t, extendRateBook as ta, computeIegReductionFactor as ti, nonCapitalLossApplied as tn, albertaCapitalGainDifference as tr, computeSchedule5 as tt, renderT2DraftReturn as u, computeCeeSuccessor as ui, resolveCorpTaxRates as un, computeSchedule12 as ur, ccaDeduction as ut, QC_TAX_RATE_BOOK as v, computeSfedeCountryRegular as vi, computeLossSchedule as vn, toRsiJacketSchedules as vr, PART_VI_1_DEDUCTION_BANDS as vt, runConformanceSuite as w, schedule3Values as wi, computeDonationMaximum as wn, RSI_WORD_GAP as wr, EIFEL_TRANSITIONAL_RATIO as wt, foldT2Lines as x, computeSchedule4 as xi, computeLimitedPartnershipLosses as xn, RSI_COLUMN_GAP as xr, EIFEL_FIRST_YEAR_START as xt, resolveQuebecTaxRates as y, computeSfedeCountrySuccessor as yi, computeLossContinuity as yn, toRsiLineItems as yr, computePartVI1Deduction as yt, computeItcRecapture as z, schedule20Values as zi, computeSchedule8 as zn, albertaBalanceUnpaid as zr, EIFEL_EFFECTIVE_FROM as zt };
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
# Statutory data — provenance and revision
|
|
2
|
+
|
|
3
|
+
Every tax figure this package ships has to say **where it came from** and **when it
|
|
4
|
+
was last checked**, in a form a machine can verify. This file is the convention;
|
|
5
|
+
`tests/statutory-data-provenance.test.ts` is the enforcement.
|
|
6
|
+
|
|
7
|
+
The convention is adapted from Equisoft's [`tax-ca`][tax-ca], which puts a
|
|
8
|
+
`Sources` / `Revised` header on each data file. That is a good idea and we took it.
|
|
9
|
+
We added enforcement because they have none — their own coverage is 15 of 34 files,
|
|
10
|
+
and the RESP grants, the CPI, and the pension increase factors all carry live
|
|
11
|
+
numbers with no header while the build stays green. A convention nothing checks is
|
|
12
|
+
documentation of intent, not provenance.
|
|
13
|
+
|
|
14
|
+
## The block
|
|
15
|
+
|
|
16
|
+
Every module that ships statutory data carries an `@statutory-data` block at the
|
|
17
|
+
end of its leading JSDoc:
|
|
18
|
+
|
|
19
|
+
```ts
|
|
20
|
+
/**
|
|
21
|
+
* …the module's ordinary doc comment…
|
|
22
|
+
*
|
|
23
|
+
* @statutory-data
|
|
24
|
+
* book: AB_TAX_RATE_BOOK
|
|
25
|
+
* covers: 2024
|
|
26
|
+
* status: partially-sourced
|
|
27
|
+
* verified: 2026-09-09
|
|
28
|
+
* sources:
|
|
29
|
+
* - research/sources/tra-spec/AT1-Chapter3-2026.4-full.txt §3.2.3.1 line 068 —
|
|
30
|
+
* GENERAL_RATE 8%, as band F of the day-weighted table.
|
|
31
|
+
* gaps:
|
|
32
|
+
* - SMALL_BUSINESS_RATE (2%) appears in NO vendored source.
|
|
33
|
+
*/
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
| Field | Meaning |
|
|
37
|
+
|---|---|
|
|
38
|
+
| `book` | The exported `RateBook` this describes. Omit for effective-dated tables. |
|
|
39
|
+
| `covers` | The years (or effective window) the shipped data holds. **Checked against the real book**, not trusted. |
|
|
40
|
+
| `status` | `primary-sourced` · `partially-sourced` · `reference-only` |
|
|
41
|
+
| `verified` | ISO date the values were last checked against a cited source, or the literal `never`. |
|
|
42
|
+
| `sources` | Cited documents. A `research/sources/...` path **must exist on disk**. |
|
|
43
|
+
| `gaps` | What is *not* sourced. Required unless `status: primary-sourced`. |
|
|
44
|
+
|
|
45
|
+
`verified: never` is a legitimate, honest value. It says nobody has checked these
|
|
46
|
+
figures against a primary document — which is true of most of this package — and it
|
|
47
|
+
is loud precisely because it is allowed rather than faked. Writing today's date to
|
|
48
|
+
make a header look finished is the one thing this convention exists to prevent.
|
|
49
|
+
|
|
50
|
+
## What the test enforces
|
|
51
|
+
|
|
52
|
+
Four things, three of which `tax-ca`'s layout cannot check at all:
|
|
53
|
+
|
|
54
|
+
1. **A `RateBook` export requires a block.** Statutory data is identified
|
|
55
|
+
*structurally*, not from a list, so a new rate table without provenance fails.
|
|
56
|
+
You cannot forget.
|
|
57
|
+
2. **Cited vendored documents must exist.** `tax-ca` cites URLs — one of them
|
|
58
|
+
`taxtips.ca`, a secondary source; one a dead `cra-arc.gc.ca` link — and nothing
|
|
59
|
+
notices when they rot. Our primary documents are checked into
|
|
60
|
+
`research/sources/`, so a citation is a path that resolves or fails the build.
|
|
61
|
+
(Skipped when the research tree is absent, e.g. installed from npm.)
|
|
62
|
+
3. **`covers:` must match the book's actual years,** read from the imported book.
|
|
63
|
+
4. **The debt roster is pinned.** The set of `reference-only` and `verified: never`
|
|
64
|
+
files is asserted explicitly. It can shrink; it cannot grow without a
|
|
65
|
+
deliberate edit a reviewer sees.
|
|
66
|
+
|
|
67
|
+
It does **not** judge whether a rate is correct. It asserts that every rate
|
|
68
|
+
declares its authority and that the declaration is not stale in a way a machine can
|
|
69
|
+
see. Correctness is what the vendored documents and the engine suites are for.
|
|
70
|
+
|
|
71
|
+
## Current position
|
|
72
|
+
|
|
73
|
+
> **Every reference rate book in this package stops at 2024.**
|
|
74
|
+
|
|
75
|
+
That is asserted by the test, not merely written here. The rate book is a *seam*:
|
|
76
|
+
a host supplies authoritative figures per year through `CORP_TAX_RATE_BOOK` and its
|
|
77
|
+
siblings, and `apps/server` does exactly that. But a fork that installs this package
|
|
78
|
+
and files a 2025+ return against the shipped values will file wrong numbers, so the
|
|
79
|
+
fact is pinned where it cannot be missed.
|
|
80
|
+
|
|
81
|
+
| File | Status | Verified |
|
|
82
|
+
|---|---|---|
|
|
83
|
+
| `t2/at1/rates/general-rate-bands.ts` | primary-sourced | 2026-09-09 |
|
|
84
|
+
| `gst-hst/rates.ts` | primary-sourced | 2026-07-04 |
|
|
85
|
+
| `t2/at1/rates/alberta-rates.ts` | partially-sourced | 2026-09-09 |
|
|
86
|
+
| `t2/rates/corporate-rates.ts` | partially-sourced | never |
|
|
87
|
+
| `t2/rates/cca-rates.ts` | partially-sourced | never |
|
|
88
|
+
| `t2/co17/rates/quebec-rates.ts` | **reference-only** | never |
|
|
89
|
+
| `t2/rates/province-rates.ts` | **reference-only** | never |
|
|
90
|
+
|
|
91
|
+
`province-rates.ts` is the largest gap: 22 figures (11 provinces × general +
|
|
92
|
+
small-business), none sourced, several stale since 2024. A host **must** supply its
|
|
93
|
+
own `PROVINCE_RATE_BOOK` before filing a T2 with a Schedule 5.
|
|
94
|
+
|
|
95
|
+
## Revising a year
|
|
96
|
+
|
|
97
|
+
1. Add a **new** `RateBookEntry` for the year. Never mutate a shipped year — a
|
|
98
|
+
reprint of a 2024 return must stay correct forever.
|
|
99
|
+
2. Update `covers:` to match. The test fails until it does.
|
|
100
|
+
3. Update `sources:` and set `verified:` to the date you actually checked. If you
|
|
101
|
+
did not check against a primary document, leave it `never` and record the gap.
|
|
102
|
+
4. Prefer **vendoring** the document into `research/sources/` over citing a URL.
|
|
103
|
+
A checked-in document is the difference between a citation that can be verified
|
|
104
|
+
in five years and one that 404s.
|
|
105
|
+
5. If the change moves a file between statuses, update the pinned rosters in
|
|
106
|
+
`tests/statutory-data-provenance.test.ts` in the same commit — that diff is the
|
|
107
|
+
reviewable record of provenance debt changing.
|
|
108
|
+
|
|
109
|
+
[tax-ca]: https://github.com/Equisoft/tax-ca
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@classytic/ca-tax",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.21",
|
|
4
4
|
"description": "Canadian corporate tax engine for @classytic/tax-core — federal T2, Alberta AT1 and Québec CO-17 with form definitions traced to the published forms, plus GST/HST/PST/QST with dated provincial regimes.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -69,13 +69,13 @@
|
|
|
69
69
|
"format:baseline": "biome check --write src/ tests/"
|
|
70
70
|
},
|
|
71
71
|
"peerDependencies": {
|
|
72
|
-
"@classytic/primitives": ">=0.
|
|
72
|
+
"@classytic/primitives": ">=0.27.0",
|
|
73
73
|
"@classytic/tax-core": ">=0.3.0"
|
|
74
74
|
},
|
|
75
75
|
"devDependencies": {
|
|
76
76
|
"@biomejs/biome": "^2.4.10",
|
|
77
77
|
"@classytic/dev-tools": "^0.2.1",
|
|
78
|
-
"@classytic/primitives": "^0.
|
|
78
|
+
"@classytic/primitives": "^0.27.0",
|
|
79
79
|
"@classytic/tax-core": "^0.3.0",
|
|
80
80
|
"@types/node": "^25.6.0",
|
|
81
81
|
"tsdown": "^0.22.3",
|