cata-centavo 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (127) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +159 -0
  3. package/dist/bin/cata-centavo.js +226 -0
  4. package/dist/bin/cata-centavo.js.map +1 -0
  5. package/dist/cli/dispatch.js +67 -0
  6. package/dist/cli/dispatch.js.map +1 -0
  7. package/dist/cli/doctor.js +178 -0
  8. package/dist/cli/doctor.js.map +1 -0
  9. package/dist/cli/init.js +125 -0
  10. package/dist/cli/init.js.map +1 -0
  11. package/dist/cli/progress.js +85 -0
  12. package/dist/cli/progress.js.map +1 -0
  13. package/dist/cli/sync.js +26 -0
  14. package/dist/cli/sync.js.map +1 -0
  15. package/dist/config.js +119 -0
  16. package/dist/config.js.map +1 -0
  17. package/dist/core/account.js +12 -0
  18. package/dist/core/account.js.map +1 -0
  19. package/dist/core/accounts.js +91 -0
  20. package/dist/core/accounts.js.map +1 -0
  21. package/dist/core/aggregate.js +83 -0
  22. package/dist/core/aggregate.js.map +1 -0
  23. package/dist/core/balance.js +68 -0
  24. package/dist/core/balance.js.map +1 -0
  25. package/dist/core/bill-rows.js +132 -0
  26. package/dist/core/bill-rows.js.map +1 -0
  27. package/dist/core/bill.js +103 -0
  28. package/dist/core/bill.js.map +1 -0
  29. package/dist/core/category-source.js +26 -0
  30. package/dist/core/category-source.js.map +1 -0
  31. package/dist/core/category.js +72 -0
  32. package/dist/core/category.js.map +1 -0
  33. package/dist/core/consent.js +19 -0
  34. package/dist/core/consent.js.map +1 -0
  35. package/dist/core/contracts.js +9 -0
  36. package/dist/core/contracts.js.map +1 -0
  37. package/dist/core/counterparty.js +69 -0
  38. package/dist/core/counterparty.js.map +1 -0
  39. package/dist/core/date.js +25 -0
  40. package/dist/core/date.js.map +1 -0
  41. package/dist/core/description.js +58 -0
  42. package/dist/core/description.js.map +1 -0
  43. package/dist/core/diagnose.js +32 -0
  44. package/dist/core/diagnose.js.map +1 -0
  45. package/dist/core/mcc.js +124 -0
  46. package/dist/core/mcc.js.map +1 -0
  47. package/dist/core/refresh.js +142 -0
  48. package/dist/core/refresh.js.map +1 -0
  49. package/dist/core/self-transfer.js +22 -0
  50. package/dist/core/self-transfer.js.map +1 -0
  51. package/dist/core/taxonomy-tree.js +148 -0
  52. package/dist/core/taxonomy-tree.js.map +1 -0
  53. package/dist/core/taxonomy.js +70 -0
  54. package/dist/core/taxonomy.js.map +1 -0
  55. package/dist/core/transaction.js +2 -0
  56. package/dist/core/transaction.js.map +1 -0
  57. package/dist/core/transactions.js +46 -0
  58. package/dist/core/transactions.js.map +1 -0
  59. package/dist/logging.js +93 -0
  60. package/dist/logging.js.map +1 -0
  61. package/dist/mcp/cursor.js +64 -0
  62. package/dist/mcp/cursor.js.map +1 -0
  63. package/dist/mcp/format.js +38 -0
  64. package/dist/mcp/format.js.map +1 -0
  65. package/dist/mcp/server.js +50 -0
  66. package/dist/mcp/server.js.map +1 -0
  67. package/dist/mcp/source.js +2 -0
  68. package/dist/mcp/source.js.map +1 -0
  69. package/dist/mcp/tools/accounts.js +144 -0
  70. package/dist/mcp/tools/accounts.js.map +1 -0
  71. package/dist/mcp/tools/balance.js +96 -0
  72. package/dist/mcp/tools/balance.js.map +1 -0
  73. package/dist/mcp/tools/bill-summary-format.js +161 -0
  74. package/dist/mcp/tools/bill-summary-format.js.map +1 -0
  75. package/dist/mcp/tools/bill-summary.js +105 -0
  76. package/dist/mcp/tools/bill-summary.js.map +1 -0
  77. package/dist/mcp/tools/bills.js +144 -0
  78. package/dist/mcp/tools/bills.js.map +1 -0
  79. package/dist/mcp/tools/closing-days.js +90 -0
  80. package/dist/mcp/tools/closing-days.js.map +1 -0
  81. package/dist/mcp/tools/list-transactions.js +122 -0
  82. package/dist/mcp/tools/list-transactions.js.map +1 -0
  83. package/dist/mcp/tools/result.js +12 -0
  84. package/dist/mcp/tools/result.js.map +1 -0
  85. package/dist/mcp/tools/set-category.js +74 -0
  86. package/dist/mcp/tools/set-category.js.map +1 -0
  87. package/dist/mcp/tools/sources.js +73 -0
  88. package/dist/mcp/tools/sources.js.map +1 -0
  89. package/dist/mcp/tools/transaction-details.js +146 -0
  90. package/dist/mcp/tools/transaction-details.js.map +1 -0
  91. package/dist/mcp/tools/transaction-input.js +51 -0
  92. package/dist/mcp/tools/transaction-input.js.map +1 -0
  93. package/dist/mcp/tools/transactions.js +186 -0
  94. package/dist/mcp/tools/transactions.js.map +1 -0
  95. package/dist/pluggy/client.js +123 -0
  96. package/dist/pluggy/client.js.map +1 -0
  97. package/dist/pluggy/errors.js +152 -0
  98. package/dist/pluggy/errors.js.map +1 -0
  99. package/dist/pluggy/mapper.js +155 -0
  100. package/dist/pluggy/mapper.js.map +1 -0
  101. package/dist/pluggy/money.js +53 -0
  102. package/dist/pluggy/money.js.map +1 -0
  103. package/dist/pluggy/transaction-mapper.js +124 -0
  104. package/dist/pluggy/transaction-mapper.js.map +1 -0
  105. package/dist/pluggy/transport.js +170 -0
  106. package/dist/pluggy/transport.js.map +1 -0
  107. package/dist/pluggy/wire.js +207 -0
  108. package/dist/pluggy/wire.js.map +1 -0
  109. package/dist/storage/categories.js +101 -0
  110. package/dist/storage/categories.js.map +1 -0
  111. package/dist/storage/category-sql.js +37 -0
  112. package/dist/storage/category-sql.js.map +1 -0
  113. package/dist/storage/closing-days.js +27 -0
  114. package/dist/storage/closing-days.js.map +1 -0
  115. package/dist/storage/db.js +162 -0
  116. package/dist/storage/db.js.map +1 -0
  117. package/dist/storage/diagnostics.js +57 -0
  118. package/dist/storage/diagnostics.js.map +1 -0
  119. package/dist/storage/harvest.js +87 -0
  120. package/dist/storage/harvest.js.map +1 -0
  121. package/dist/storage/migrations.js +125 -0
  122. package/dist/storage/migrations.js.map +1 -0
  123. package/dist/storage/transaction-row.js +74 -0
  124. package/dist/storage/transaction-row.js.map +1 -0
  125. package/dist/storage/transactions.js +213 -0
  126. package/dist/storage/transactions.js.map +1 -0
  127. package/package.json +70 -0
@@ -0,0 +1,68 @@
1
+ function currenciesIn(accounts) {
2
+ return [...new Set(accounts.map((account) => account.currency))].sort();
3
+ }
4
+ function sumBalances(accounts) {
5
+ const totals = {
6
+ cashCents: 0,
7
+ creditUsedCents: 0,
8
+ investedCents: 0,
9
+ loanCents: 0,
10
+ hasInvestments: false,
11
+ hasLoans: false,
12
+ };
13
+ for (const account of accounts) {
14
+ switch (account.type) {
15
+ case "BANK":
16
+ totals.cashCents += account.amountCents;
17
+ break;
18
+ case "CREDIT":
19
+ totals.creditUsedCents += account.amountCents;
20
+ break;
21
+ case "INVESTMENT":
22
+ totals.investedCents += account.amountCents;
23
+ totals.hasInvestments = true;
24
+ break;
25
+ case "LOAN":
26
+ totals.loanCents += account.amountCents;
27
+ totals.hasLoans = true;
28
+ break;
29
+ }
30
+ }
31
+ return totals;
32
+ }
33
+ function createSummary(currency, accountsCounted, totals) {
34
+ let investedFields;
35
+ if (totals.hasInvestments) {
36
+ investedFields = { investedCents: totals.investedCents };
37
+ }
38
+ else {
39
+ investedFields = {};
40
+ }
41
+ let loanFields;
42
+ if (totals.hasLoans) {
43
+ loanFields = { loanCents: totals.loanCents };
44
+ }
45
+ else {
46
+ loanFields = {};
47
+ }
48
+ return {
49
+ cashCents: totals.cashCents,
50
+ creditUsedCents: totals.creditUsedCents,
51
+ currency,
52
+ accountsCounted,
53
+ ...investedFields,
54
+ ...loanFields,
55
+ };
56
+ }
57
+ export function summarize(accounts) {
58
+ const currencies = currenciesIn(accounts);
59
+ if (currencies.length !== 1) {
60
+ return { ok: false, currencies };
61
+ }
62
+ const currency = currencies[0];
63
+ if (currency === undefined) {
64
+ return { ok: false, currencies };
65
+ }
66
+ return { ok: true, summary: createSummary(currency, accounts.length, sumBalances(accounts)) };
67
+ }
68
+ //# sourceMappingURL=balance.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"balance.js","sourceRoot":"","sources":["../../src/core/balance.ts"],"names":[],"mappings":"AAwBA,SAAS,YAAY,CAAC,QAA4B;IAChD,OAAO,CAAC,GAAG,IAAI,GAAG,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;AAC1E,CAAC;AAED,SAAS,WAAW,CAAC,QAA4B;IAC/C,MAAM,MAAM,GAAW;QACrB,SAAS,EAAE,CAAC;QACZ,eAAe,EAAE,CAAC;QAClB,aAAa,EAAE,CAAC;QAChB,SAAS,EAAE,CAAC;QACZ,cAAc,EAAE,KAAK;QACrB,QAAQ,EAAE,KAAK;KAChB,CAAC;IAEF,KAAK,MAAM,OAAO,IAAI,QAAQ,EAAE,CAAC;QAC/B,QAAQ,OAAO,CAAC,IAAI,EAAE,CAAC;YACrB,KAAK,MAAM;gBACT,MAAM,CAAC,SAAS,IAAI,OAAO,CAAC,WAAW,CAAC;gBACxC,MAAM;YACR,KAAK,QAAQ;gBACX,MAAM,CAAC,eAAe,IAAI,OAAO,CAAC,WAAW,CAAC;gBAC9C,MAAM;YACR,KAAK,YAAY;gBACf,MAAM,CAAC,aAAa,IAAI,OAAO,CAAC,WAAW,CAAC;gBAC5C,MAAM,CAAC,cAAc,GAAG,IAAI,CAAC;gBAC7B,MAAM;YACR,KAAK,MAAM;gBACT,MAAM,CAAC,SAAS,IAAI,OAAO,CAAC,WAAW,CAAC;gBACxC,MAAM,CAAC,QAAQ,GAAG,IAAI,CAAC;gBACvB,MAAM;QACV,CAAC;IACH,CAAC;IAED,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,SAAS,aAAa,CAAC,QAAgB,EAAE,eAAuB,EAAE,MAAc;IAC9E,IAAI,cAAsE,CAAC;IAC3E,IAAI,MAAM,CAAC,cAAc,EAAE,CAAC;QAC1B,cAAc,GAAG,EAAE,aAAa,EAAE,MAAM,CAAC,aAAa,EAAE,CAAC;IAC3D,CAAC;SAAM,CAAC;QACN,cAAc,GAAG,EAAE,CAAC;IACtB,CAAC;IAED,IAAI,UAA8D,CAAC;IACnE,IAAI,MAAM,CAAC,QAAQ,EAAE,CAAC;QACpB,UAAU,GAAG,EAAE,SAAS,EAAE,MAAM,CAAC,SAAS,EAAE,CAAC;IAC/C,CAAC;SAAM,CAAC;QACN,UAAU,GAAG,EAAE,CAAC;IAClB,CAAC;IAED,OAAO;QACL,SAAS,EAAE,MAAM,CAAC,SAAS;QAC3B,eAAe,EAAE,MAAM,CAAC,eAAe;QACvC,QAAQ;QACR,eAAe;QACf,GAAG,cAAc;QACjB,GAAG,UAAU;KACd,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,SAAS,CAAC,QAA4B;IACpD,MAAM,UAAU,GAAG,YAAY,CAAC,QAAQ,CAAC,CAAC;IAE1C,IAAI,UAAU,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC5B,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,UAAU,EAAE,CAAC;IACnC,CAAC;IAED,MAAM,QAAQ,GAAG,UAAU,CAAC,CAAC,CAAC,CAAC;IAC/B,IAAI,QAAQ,KAAK,SAAS,EAAE,CAAC;QAC3B,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,UAAU,EAAE,CAAC;IACnC,CAAC;IAED,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,aAAa,CAAC,QAAQ,EAAE,QAAQ,CAAC,MAAM,EAAE,WAAW,CAAC,QAAQ,CAAC,CAAC,EAAE,CAAC;AAChG,CAAC"}
@@ -0,0 +1,132 @@
1
+ import { isSelfTransfer } from "./self-transfer.js";
2
+ /** Sums posted rows in bill sign, where a purchase increases the amount due. */
3
+ export function derivePostedCents(rows) {
4
+ let postedCents = 0;
5
+ for (const row of rows) {
6
+ if (isSelfTransfer(row)) {
7
+ continue;
8
+ }
9
+ postedCents -= row.amountCents;
10
+ }
11
+ return postedCents;
12
+ }
13
+ /**
14
+ * Derives future instalments from explicit rows and each open-cycle position.
15
+ * Only the larger total is subtracted from utilized credit.
16
+ */
17
+ export function deriveBillCommitment(partition, utilizationCents) {
18
+ let materializedCents = 0;
19
+ for (const row of partition.futureRows) {
20
+ materializedCents -= row.amountCents;
21
+ }
22
+ const impliedCents = deriveImpliedCents(partition.openCycleRows, partition.wrappedInstalmentRowIds);
23
+ const futureCents = Math.max(materializedCents, impliedCents);
24
+ return {
25
+ materializedCents,
26
+ impliedCents,
27
+ futureCents,
28
+ committedCents: utilizationCents - futureCents,
29
+ };
30
+ }
31
+ /**
32
+ * Dedupes bulk-posted plans inside one open cycle.
33
+ *
34
+ * Raw `description | instalmentTotal` is safe only at this boundary. A wrapped
35
+ * counter needs rows from two cycles, while a counter embedded in the raw
36
+ * description gives each instalment a different key.
37
+ *
38
+ * The highest posted position contributes its unposted tail. Every other
39
+ * distinct position in the same cycle contributes once.
40
+ */
41
+ function deriveImpliedCents(rows, wrappedInstalmentRowIds) {
42
+ const plans = new Map();
43
+ for (const row of rows) {
44
+ addToOpenCyclePlans(plans, wrappedInstalmentRowIds, row);
45
+ }
46
+ let impliedCents = 0;
47
+ for (const plan of plans.values()) {
48
+ const otherOpenCycleInstalments = plan.instalmentNumbers.size - 1;
49
+ const unpostedInstalments = plan.instalmentTotal - plan.highestInstalmentNumber;
50
+ impliedCents -= plan.amountCents * (otherOpenCycleInstalments + unpostedInstalments);
51
+ }
52
+ return impliedCents;
53
+ }
54
+ function addToOpenCyclePlans(plans, wrappedInstalmentRowIds, row) {
55
+ if (row.instalmentNumber === null || row.instalmentTotal === null) {
56
+ return;
57
+ }
58
+ if (wrappedInstalmentRowIds.has(row.id)) {
59
+ return;
60
+ }
61
+ const key = `${row.description}|${row.instalmentTotal}`;
62
+ const plan = plans.get(key);
63
+ if (plan === undefined) {
64
+ plans.set(key, {
65
+ amountCents: row.amountCents,
66
+ highestInstalmentNumber: row.instalmentNumber,
67
+ instalmentNumbers: new Set([row.instalmentNumber]),
68
+ instalmentTotal: row.instalmentTotal,
69
+ });
70
+ return;
71
+ }
72
+ plan.instalmentNumbers.add(row.instalmentNumber);
73
+ if (row.instalmentNumber > plan.highestInstalmentNumber) {
74
+ plan.amountCents = row.amountCents;
75
+ plan.highestInstalmentNumber = row.instalmentNumber;
76
+ }
77
+ }
78
+ /** Separates rows that affect the open cycle from rows assigned to later cycles. */
79
+ export function partitionBillRows(rows, openCycle, openBillId) {
80
+ const openCycleRows = [];
81
+ const futureRows = [];
82
+ const completedInstalmentDates = new Map();
83
+ const wrappedInstalmentRowIds = new Set();
84
+ for (const row of rows) {
85
+ trackInstalmentHistory(row, completedInstalmentDates, wrappedInstalmentRowIds);
86
+ if (belongsToOpenBill(row, openBillId)) {
87
+ openCycleRows.push(row);
88
+ continue;
89
+ }
90
+ if (row.billId !== null) {
91
+ continue;
92
+ }
93
+ if (belongsToFutureCycle(row, openCycle)) {
94
+ futureRows.push(row);
95
+ continue;
96
+ }
97
+ openCycleRows.push(row);
98
+ }
99
+ return { openCycleRows, futureRows, wrappedInstalmentRowIds };
100
+ }
101
+ /**
102
+ * Marks a counter that restarted after the same raw description completed.
103
+ *
104
+ * This has a designed false positive: if two separate instalment purchases
105
+ * share a description and the first one completed, the second one's real
106
+ * remainder is zeroed. The feed has no plan identifier that can separate them.
107
+ */
108
+ function trackInstalmentHistory(row, completedInstalmentDates, wrappedInstalmentRowIds) {
109
+ const completedDate = completedInstalmentDates.get(row.description);
110
+ if (completedDate !== undefined && completedDate < row.localDate) {
111
+ wrappedInstalmentRowIds.add(row.id);
112
+ }
113
+ if (!isCompletedInstalment(row)) {
114
+ return;
115
+ }
116
+ if (completedDate === undefined || row.localDate < completedDate) {
117
+ completedInstalmentDates.set(row.description, row.localDate);
118
+ }
119
+ }
120
+ function isCompletedInstalment(row) {
121
+ if (row.instalmentNumber === null || row.instalmentTotal === null) {
122
+ return false;
123
+ }
124
+ return row.instalmentNumber === row.instalmentTotal;
125
+ }
126
+ function belongsToOpenBill(row, openBillId) {
127
+ return openBillId !== null && row.billId === openBillId;
128
+ }
129
+ function belongsToFutureCycle(row, openCycle) {
130
+ return row.billForecastDate === "0001-01" || (row.billForecastDate !== null && row.billForecastDate > openCycle);
131
+ }
132
+ //# sourceMappingURL=bill-rows.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bill-rows.js","sourceRoot":"","sources":["../../src/core/bill-rows.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,103 @@
1
+ export const ClosingDateSource = {
2
+ openBill: "open-bill",
3
+ lastClosed: "last-closed",
4
+ local: "local",
5
+ dueDate: "due-date",
6
+ };
7
+ const MONTH_LENGTHS = {
8
+ 1: 31,
9
+ 2: 28,
10
+ 3: 31,
11
+ 4: 30,
12
+ 5: 31,
13
+ 6: 30,
14
+ 7: 31,
15
+ 8: 31,
16
+ 9: 30,
17
+ 10: 31,
18
+ 11: 30,
19
+ 12: 31,
20
+ };
21
+ export function identifyOpenCycle(bills, storedDay, balanceDueDate, today) {
22
+ const latestBill = newestBill(bills);
23
+ if (latestBill !== null) {
24
+ if (billIsOpen(latestBill, today)) {
25
+ return {
26
+ openCycle: cycleOf(latestBill.dueDate),
27
+ source: ClosingDateSource.openBill,
28
+ };
29
+ }
30
+ return {
31
+ openCycle: followingCycle(cycleOf(latestBill.dueDate)),
32
+ source: ClosingDateSource.lastClosed,
33
+ };
34
+ }
35
+ if (storedDay !== null) {
36
+ return {
37
+ openCycle: cycleFromStoredDay(storedDay, today),
38
+ source: ClosingDateSource.local,
39
+ };
40
+ }
41
+ if (balanceDueDate !== null) {
42
+ return {
43
+ openCycle: followingCycle(cycleOf(balanceDueDate)),
44
+ source: ClosingDateSource.dueDate,
45
+ };
46
+ }
47
+ return null;
48
+ }
49
+ function billIsOpen(bill, today) {
50
+ if (bill.closingDate !== null) {
51
+ return bill.closingDate >= today;
52
+ }
53
+ return bill.dueDate > today;
54
+ }
55
+ /** The bill the rest of the list is measured against: latest closing date, or due date when the connector omits it. */
56
+ export function newestBill(bills) {
57
+ let newest = null;
58
+ for (const candidate of bills) {
59
+ if (newest === null || billDate(candidate) > billDate(newest)) {
60
+ newest = candidate;
61
+ }
62
+ }
63
+ return newest;
64
+ }
65
+ function billDate(bill) {
66
+ if (bill.closingDate !== null) {
67
+ return bill.closingDate;
68
+ }
69
+ return bill.dueDate;
70
+ }
71
+ function cycleFromStoredDay(storedDay, today) {
72
+ const currentCycle = cycleOf(today);
73
+ const year = Number(currentCycle.slice(0, 4));
74
+ const month = Number(currentCycle.slice(5, 7));
75
+ const closingDay = Math.min(storedDay, daysInMonth(year, month));
76
+ const closingDate = `${currentCycle}-${String(closingDay).padStart(2, "0")}`;
77
+ if (today >= closingDate) {
78
+ return followingCycle(currentCycle);
79
+ }
80
+ return currentCycle;
81
+ }
82
+ function cycleOf(date) {
83
+ return date.slice(0, 7);
84
+ }
85
+ function followingCycle(cycle) {
86
+ let year = Number(cycle.slice(0, 4));
87
+ let month = Number(cycle.slice(5, 7)) + 1;
88
+ if (month === 13) {
89
+ year += 1;
90
+ month = 1;
91
+ }
92
+ return `${String(year).padStart(4, "0")}-${String(month).padStart(2, "0")}`;
93
+ }
94
+ function daysInMonth(year, month) {
95
+ if (month === 2 && isLeapYear(year)) {
96
+ return 29;
97
+ }
98
+ return MONTH_LENGTHS[month] ?? 31;
99
+ }
100
+ function isLeapYear(year) {
101
+ return year % 400 === 0 || (year % 4 === 0 && year % 100 !== 0);
102
+ }
103
+ //# sourceMappingURL=bill.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bill.js","sourceRoot":"","sources":["../../src/core/bill.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,26 @@
1
+ /**
2
+ * The derivation, in precedence order. This array is the single source of
3
+ * truth: `storage/category-sql.ts` generates the SQL `COALESCE` from it and
4
+ * `resolveCategory` walks it, so the order and the reported provenance cannot
5
+ * drift apart (design, "The derivation query").
6
+ */
7
+ export const BRANCHES = ["override", "counterparty", "pluggy", "snapshot", "learned", "mcc"];
8
+ /** A harvested Pluggy answer is still a Pluggy answer. */
9
+ const REPORTED = {
10
+ override: "override",
11
+ counterparty: "counterparty",
12
+ pluggy: "pluggy",
13
+ snapshot: "pluggy",
14
+ learned: "learned",
15
+ mcc: "mcc",
16
+ };
17
+ export function resolveCategory(columns) {
18
+ for (const branch of BRANCHES) {
19
+ const value = columns[branch];
20
+ if (value !== null) {
21
+ return { category: value, categorySrc: REPORTED[branch] };
22
+ }
23
+ }
24
+ return { category: null, categorySrc: null };
25
+ }
26
+ //# sourceMappingURL=category-source.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"category-source.js","sourceRoot":"","sources":["../../src/core/category-source.ts"],"names":[],"mappings":"AAEA;;;;;GAKG;AACH,MAAM,CAAC,MAAM,QAAQ,GAAG,CAAC,UAAU,EAAE,cAAc,EAAE,QAAQ,EAAE,UAAU,EAAE,SAAS,EAAE,KAAK,CAAU,CAAC;AAOtG,0DAA0D;AAC1D,MAAM,QAAQ,GAA6C;IACzD,QAAQ,EAAE,UAAU;IACpB,YAAY,EAAE,cAAc;IAC5B,MAAM,EAAE,QAAQ;IAChB,QAAQ,EAAE,QAAQ;IAClB,OAAO,EAAE,SAAS;IAClB,GAAG,EAAE,KAAK;CACX,CAAC;AASF,MAAM,UAAU,eAAe,CAAC,OAAuB;IACrD,KAAK,MAAM,MAAM,IAAI,QAAQ,EAAE,CAAC;QAC9B,MAAM,KAAK,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;QAC9B,IAAI,KAAK,KAAK,IAAI,EAAE,CAAC;YACnB,OAAO,EAAE,QAAQ,EAAE,KAAmB,EAAE,WAAW,EAAE,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC;QAC1E,CAAC;IACH,CAAC;IACD,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,WAAW,EAAE,IAAI,EAAE,CAAC;AAC/C,CAAC"}
@@ -0,0 +1,72 @@
1
+ /**
2
+ * The project's closed category list: Pluggy's 22 top-level categories, taken
3
+ * verbatim from `GET /categories` (ADR §12.4 step 2 — copy a taxonomy already
4
+ * validated in production rather than invent one). Every child category rolls
5
+ * up to its top-level ancestor; the full 130-entry tree and that roll-up
6
+ * belong with the `cache.db` seed, not here.
7
+ *
8
+ * A `const` object plus a derived union, not an `enum` (ADR §13): `enum` is not
9
+ * erasable syntax, and a string `enum` is nominal, so a category arriving as
10
+ * JSON could not be assigned without a cast — exactly where validation belongs.
11
+ *
12
+ * **Pluggy's id is the value, not the key.** Two reasons. Named keys make the
13
+ * MCC table in `mcc.ts` readable and typo-proof at compile time. And `"10000000"`
14
+ * is a canonical array index as a JavaScript property name, so as a key it would
15
+ * enumerate *before* `"01000000"` in every `Object.keys`; as a value the
16
+ * declaration order survives.
17
+ *
18
+ * **The ids are not uniform width.** All 22 top-level ids are 8 digits, but the
19
+ * taxonomy underneath them is not: Insurance's four children use 9, starting at
20
+ * `"200100000"` (Life insurance). Anything deriving a parent by slicing an id at
21
+ * a fixed offset works on 126 of the 130 entries and breaks on those four.
22
+ */
23
+ export const CATEGORIES = {
24
+ income: { id: "01000000", en: "Income", pt: "Renda" },
25
+ loansAndFinancing: { id: "02000000", en: "Loans and financing", pt: "Empréstimos e financiamento" },
26
+ investments: { id: "03000000", en: "Investments", pt: "Investimentos" },
27
+ samePersonTransfer: { id: "04000000", en: "Same person transfer", pt: "Transferência mesma titularidade" },
28
+ transfers: { id: "05000000", en: "Transfers", pt: "Transferências" },
29
+ legalObligations: { id: "06000000", en: "Legal obligations", pt: "Obrigações legais" },
30
+ services: { id: "07000000", en: "Services", pt: "Serviços" },
31
+ shopping: { id: "08000000", en: "Shopping", pt: "Compras" },
32
+ digitalServices: { id: "09000000", en: "Digital services", pt: "Serviços digitais" },
33
+ groceries: { id: "10000000", en: "Groceries", pt: "Supermercado" },
34
+ foodAndDrinks: { id: "11000000", en: "Food and drinks", pt: "Alimentos e bebidas" },
35
+ travel: { id: "12000000", en: "Travel", pt: "Viagens" },
36
+ donations: { id: "13000000", en: "Donations", pt: "Doações" },
37
+ gambling: { id: "14000000", en: "Gambling", pt: "Apostas" },
38
+ taxes: { id: "15000000", en: "Taxes", pt: "Impostos" },
39
+ bankFees: { id: "16000000", en: "Bank fees", pt: "Taxas bancárias" },
40
+ housing: { id: "17000000", en: "Housing", pt: "Moradia" },
41
+ healthcare: { id: "18000000", en: "Healthcare", pt: "Saúde" },
42
+ transportation: { id: "19000000", en: "Transportation", pt: "Transporte" },
43
+ insurance: { id: "20000000", en: "Insurance", pt: "Seguros" },
44
+ leisure: { id: "21000000", en: "Leisure", pt: "Lazer" },
45
+ other: { id: "99999999", en: "Other", pt: "Outros" },
46
+ };
47
+ const BY_ID = new Map(Object.values(CATEGORIES).map((category) => [category.id, category]));
48
+ /** Every valid category id, in Pluggy's own order. */
49
+ export const CATEGORY_IDS = Object.values(CATEGORIES).map((category) => category.id);
50
+ /**
51
+ * The guard a tool boundary validates with. An agent that invents
52
+ * `alimentacao` and `alimentação` in one database breaks every aggregate.
53
+ */
54
+ export function isCategoryId(value) {
55
+ return BY_ID.has(value);
56
+ }
57
+ /**
58
+ * What a read tool's `categories` filter accepts: one of the 22, or `"none"`
59
+ * for the rows the derivation could not categorize.
60
+ *
61
+ * `"none"` exists because after the enrichment stops, "show me what has no
62
+ * category so I can fix it" is the workflow, and the aggregate's null group
63
+ * samples ten ids rather than listing them (design D6).
64
+ */
65
+ export function isCategoryFilterValue(value) {
66
+ return value === "none" || isCategoryId(value);
67
+ }
68
+ /** The category behind an id, or `undefined` when the id is not one of ours. */
69
+ export function categoryById(id) {
70
+ return BY_ID.get(id);
71
+ }
72
+ //# sourceMappingURL=category.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"category.js","sourceRoot":"","sources":["../../src/core/category.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,19 @@
1
+ /**
2
+ * `revoked` outranks `expired` when both apply: revocation is an act and
3
+ * expiry is the clock running out, and the act is what the user has to answer
4
+ * for. `null` means the endpoint answered with no consent at all, which is
5
+ * `unknown` rather than `revoked` — we never report revocation from absence.
6
+ */
7
+ export function consentState(consent, now) {
8
+ if (consent === null) {
9
+ return "unknown";
10
+ }
11
+ if (consent.revokedAt !== null) {
12
+ return "revoked";
13
+ }
14
+ if (consent.expiresAt !== null && consent.expiresAt.getTime() <= now.getTime()) {
15
+ return "expired";
16
+ }
17
+ return "active";
18
+ }
19
+ //# sourceMappingURL=consent.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"consent.js","sourceRoot":"","sources":["../../src/core/consent.ts"],"names":[],"mappings":"AAIA;;;;;GAKG;AACH,MAAM,UAAU,YAAY,CAAC,OAAuB,EAAE,GAAS;IAC7D,IAAI,OAAO,KAAK,IAAI,EAAE,CAAC;QACrB,OAAO,SAAS,CAAC;IACnB,CAAC;IAED,IAAI,OAAO,CAAC,SAAS,KAAK,IAAI,EAAE,CAAC;QAC/B,OAAO,SAAS,CAAC;IACnB,CAAC;IAED,IAAI,OAAO,CAAC,SAAS,KAAK,IAAI,IAAI,OAAO,CAAC,SAAS,CAAC,OAAO,EAAE,IAAI,GAAG,CAAC,OAAO,EAAE,EAAE,CAAC;QAC/E,OAAO,SAAS,CAAC;IACnB,CAAC;IAED,OAAO,QAAQ,CAAC;AAClB,CAAC"}
@@ -0,0 +1,9 @@
1
+ /**
2
+ * The interfaces core requires of whoever serves it. They live here, not beside
3
+ * their implementations, because a contract belongs to its consumer (ADR §6).
4
+ *
5
+ * Nothing in this file imports from `pluggy/`, `storage/` or `mcp/`, and that
6
+ * includes the Pluggy SDK's types: the vocabulary here is ours (§14.0).
7
+ */
8
+ export {};
9
+ //# sourceMappingURL=contracts.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"contracts.js","sourceRoot":"","sources":["../../src/core/contracts.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG"}
@@ -0,0 +1,69 @@
1
+ import { isCategoryId } from "./category.js";
2
+ /**
3
+ * A document we accept from a human. Both lengths are usable manually; only a
4
+ * CNPJ is ever learned from data (design D4).
5
+ */
6
+ export function isDocument(value) {
7
+ return value.length === 11 || value.length === 14;
8
+ }
9
+ export function isCnpj(value) {
10
+ return value.length === 14;
11
+ }
12
+ /**
13
+ * A document's category, when its rows agree by a true majority.
14
+ *
15
+ * A plurality is not enough. `mcc.ts` maps by plurality and says so, but an MCC
16
+ * is a line of business shared by thousands of merchants, while a CNPJ is one
17
+ * merchant — a 12-of-25 CNPJ mapping is a coin flip applied retroactively to
18
+ * everything that merchant ever sold you.
19
+ */
20
+ export function learnCounterparties(rows) {
21
+ const result = [];
22
+ for (const [document, counts] of tally(rows)) {
23
+ const winner = majorityOf(document, counts);
24
+ if (winner !== null) {
25
+ result.push(winner);
26
+ }
27
+ }
28
+ return result;
29
+ }
30
+ /** How many rows each CNPJ carries per category. Anything else is dropped here. */
31
+ function tally(rows) {
32
+ const byDocument = new Map();
33
+ for (const row of rows) {
34
+ if (!isLearnable(row)) {
35
+ continue;
36
+ }
37
+ let counts = byDocument.get(row.document);
38
+ if (counts === undefined) {
39
+ counts = new Map();
40
+ byDocument.set(row.document, counts);
41
+ }
42
+ counts.set(row.category, (counts.get(row.category) ?? 0) + 1);
43
+ }
44
+ return byDocument;
45
+ }
46
+ /** Narrows to a row we are willing to learn from: a CNPJ carrying a known category. */
47
+ // Stryker disable next-line ConditionalExpression: the null checks are defensive. The caller's SQL already filters `document IS NOT NULL`, and this function is public enough that a second caller should not have to know that.
48
+ function isLearnable(row) {
49
+ return row.document !== null && isCnpj(row.document) && row.category !== null && isCategoryId(row.category);
50
+ }
51
+ /** The category holding a true majority of a document's rows, or nothing. */
52
+ function majorityOf(document, counts) {
53
+ let samples = 0;
54
+ let agreeing = 0;
55
+ let category = null;
56
+ for (const [candidate, count] of counts) {
57
+ samples += count;
58
+ // Stryker disable next-line EqualityOperator: `>=` only changes which of two tied categories wins, and a tie can never clear the majority test below — both would be dropped either way.
59
+ if (count > agreeing) {
60
+ agreeing = count;
61
+ category = candidate;
62
+ }
63
+ }
64
+ if (category === null || agreeing * 2 <= samples) {
65
+ return null;
66
+ }
67
+ return { document, category, samples, agreeing };
68
+ }
69
+ //# sourceMappingURL=counterparty.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"counterparty.js","sourceRoot":"","sources":["../../src/core/counterparty.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAI7C;;;GAGG;AACH,MAAM,UAAU,UAAU,CAAC,KAAa;IACtC,OAAO,KAAK,CAAC,MAAM,KAAK,EAAE,IAAI,KAAK,CAAC,MAAM,KAAK,EAAE,CAAC;AACpD,CAAC;AAED,MAAM,UAAU,MAAM,CAAC,KAAa;IAClC,OAAO,KAAK,CAAC,MAAM,KAAK,EAAE,CAAC;AAC7B,CAAC;AAcD;;;;;;;GAOG;AACH,MAAM,UAAU,mBAAmB,CAAC,IAA4B;IAC9D,MAAM,MAAM,GAA0B,EAAE,CAAC;IAEzC,KAAK,MAAM,CAAC,QAAQ,EAAE,MAAM,CAAC,IAAI,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;QAC7C,MAAM,MAAM,GAAG,UAAU,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;QAC5C,IAAI,MAAM,KAAK,IAAI,EAAE,CAAC;YACpB,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QACtB,CAAC;IACH,CAAC;IAED,OAAO,MAAM,CAAC;AAChB,CAAC;AAID,mFAAmF;AACnF,SAAS,KAAK,CAAC,IAA4B;IACzC,MAAM,UAAU,GAAG,IAAI,GAAG,EAA0B,CAAC;IAErD,KAAK,MAAM,GAAG,IAAI,IAAI,EAAE,CAAC;QACvB,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,EAAE,CAAC;YACtB,SAAS;QACX,CAAC;QAED,IAAI,MAAM,GAAG,UAAU,CAAC,GAAG,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;QAC1C,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;YACzB,MAAM,GAAG,IAAI,GAAG,EAAE,CAAC;YACnB,UAAU,CAAC,GAAG,CAAC,GAAG,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;QACvC,CAAC;QACD,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,QAAQ,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;IAChE,CAAC;IAED,OAAO,UAAU,CAAC;AACpB,CAAC;AAED,uFAAuF;AACvF,iOAAiO;AACjO,SAAS,WAAW,CAAC,GAAgB;IACnC,OAAO,GAAG,CAAC,QAAQ,KAAK,IAAI,IAAI,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC,IAAI,GAAG,CAAC,QAAQ,KAAK,IAAI,IAAI,YAAY,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;AAC9G,CAAC;AAED,6EAA6E;AAC7E,SAAS,UAAU,CAAC,QAAgB,EAAE,MAAsB;IAC1D,IAAI,OAAO,GAAG,CAAC,CAAC;IAChB,IAAI,QAAQ,GAAG,CAAC,CAAC;IACjB,IAAI,QAAQ,GAAsB,IAAI,CAAC;IAEvC,KAAK,MAAM,CAAC,SAAS,EAAE,KAAK,CAAC,IAAI,MAAM,EAAE,CAAC;QACxC,OAAO,IAAI,KAAK,CAAC;QACjB,yLAAyL;QACzL,IAAI,KAAK,GAAG,QAAQ,EAAE,CAAC;YACrB,QAAQ,GAAG,KAAK,CAAC;YACjB,QAAQ,GAAG,SAAS,CAAC;QACvB,CAAC;IACH,CAAC;IAED,IAAI,QAAQ,KAAK,IAAI,IAAI,QAAQ,GAAG,CAAC,IAAI,OAAO,EAAE,CAAC;QACjD,OAAO,IAAI,CAAC;IACd,CAAC;IACD,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,OAAO,EAAE,QAAQ,EAAE,CAAC;AACnD,CAAC"}
@@ -0,0 +1,25 @@
1
+ const SAO_PAULO = new Intl.DateTimeFormat("en-CA", {
2
+ timeZone: "America/Sao_Paulo",
3
+ year: "numeric",
4
+ month: "2-digit",
5
+ day: "2-digit",
6
+ });
7
+ /**
8
+ * The calendar day an instant belongs to, in Brazil.
9
+ *
10
+ * Stored transaction dates use this same timezone so range filters and the
11
+ * injected clock compare values in one calendar rather than mixing UTC with
12
+ * Brazilian local time.
13
+ */
14
+ export function localDayOf(instant) {
15
+ const date = new Date(instant);
16
+ if (Number.isNaN(date.getTime())) {
17
+ throw new RangeError(`Invalid date: ${instant}`);
18
+ }
19
+ return SAO_PAULO.format(date);
20
+ }
21
+ /** Today, in the same units every stored `localDate` is in. */
22
+ export function todayIn(clock) {
23
+ return localDayOf(clock.now().toISOString());
24
+ }
25
+ //# sourceMappingURL=date.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"date.js","sourceRoot":"","sources":["../../src/core/date.ts"],"names":[],"mappings":"AAEA,MAAM,SAAS,GAAG,IAAI,IAAI,CAAC,cAAc,CAAC,OAAO,EAAE;IACjD,QAAQ,EAAE,mBAAmB;IAC7B,IAAI,EAAE,SAAS;IACf,KAAK,EAAE,SAAS;IAChB,GAAG,EAAE,SAAS;CACf,CAAC,CAAC;AAEH;;;;;;GAMG;AACH,MAAM,UAAU,UAAU,CAAC,OAAe;IACxC,MAAM,IAAI,GAAG,IAAI,IAAI,CAAC,OAAO,CAAC,CAAC;IAC/B,IAAI,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC,EAAE,CAAC;QACjC,MAAM,IAAI,UAAU,CAAC,iBAAiB,OAAO,EAAE,CAAC,CAAC;IACnD,CAAC;IAED,OAAO,SAAS,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;AAChC,CAAC;AAED,+DAA+D;AAC/D,MAAM,UAAU,OAAO,CAAC,KAAY;IAClC,OAAO,UAAU,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,WAAW,EAAE,CAAC,CAAC;AAC/C,CAAC"}
@@ -0,0 +1,58 @@
1
+ /**
2
+ * Payment acquirers that stamp themselves onto the merchant name.
3
+ *
4
+ * This list is data and is expected to grow as real cases appear. Adding one
5
+ * is one line here, one case in the table test, and a cache migration bump.
6
+ */
7
+ const ACQUIRER_PREFIXES = ["PAG*", "PG *", "PG*", "CIELO*", "REDE*", "STONE*", "MP *", "PAGSEGURO*"];
8
+ /** Corporate suffixes that carry no information about who was paid. */
9
+ const LEGAL_SUFFIXES = ["LTDA", "ME", "EIRELI", "SA", "S/A", "EPP"];
10
+ const TRAILING_INSTALMENT = /\s+\d{1,2}\/\d{1,2}$/u;
11
+ const TRAILING_SEQUENCE = /\s+\d{5,}$/u;
12
+ function stripAcquirerPrefix(value) {
13
+ for (const prefix of ACQUIRER_PREFIXES) {
14
+ if (value.startsWith(prefix)) {
15
+ return value.slice(prefix.length).trim();
16
+ }
17
+ }
18
+ return value;
19
+ }
20
+ function stripTrailingMarker(value) {
21
+ let result = value;
22
+ if (TRAILING_INSTALMENT.test(result)) {
23
+ result = result.replace(TRAILING_INSTALMENT, "").trim();
24
+ }
25
+ if (TRAILING_SEQUENCE.test(result)) {
26
+ result = result.replace(TRAILING_SEQUENCE, "").trim();
27
+ }
28
+ return result;
29
+ }
30
+ function stripLegalSuffixes(value) {
31
+ let result = value;
32
+ let changed = true;
33
+ while (changed) {
34
+ changed = false;
35
+ for (const suffix of LEGAL_SUFFIXES) {
36
+ if (result === suffix) {
37
+ result = "";
38
+ changed = true;
39
+ break;
40
+ }
41
+ if (result.endsWith(` ${suffix}`)) {
42
+ result = result.slice(0, -suffix.length - 1).trim();
43
+ changed = true;
44
+ break;
45
+ }
46
+ }
47
+ }
48
+ return result;
49
+ }
50
+ /** Normalize merchant descriptions into a stable grouping key. */
51
+ export function normalizeDescription(input) {
52
+ const withoutAccents = input.normalize("NFD").replace(/[\u0300-\u036f]/gu, "");
53
+ const uppercased = withoutAccents.toUpperCase().replace(/\s+/gu, " ").trim();
54
+ const withoutPrefix = stripAcquirerPrefix(uppercased);
55
+ const withoutMarker = stripTrailingMarker(withoutPrefix);
56
+ return stripLegalSuffixes(withoutMarker).replace(/\s+/gu, " ").trim();
57
+ }
58
+ //# sourceMappingURL=description.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"description.js","sourceRoot":"","sources":["../../src/core/description.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,MAAM,iBAAiB,GAAsB,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,QAAQ,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,YAAY,CAAC,CAAC;AAExH,uEAAuE;AACvE,MAAM,cAAc,GAAsB,CAAC,MAAM,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,KAAK,EAAE,KAAK,CAAC,CAAC;AAEvF,MAAM,mBAAmB,GAAG,uBAAuB,CAAC;AACpD,MAAM,iBAAiB,GAAG,aAAa,CAAC;AAExC,SAAS,mBAAmB,CAAC,KAAa;IACxC,KAAK,MAAM,MAAM,IAAI,iBAAiB,EAAE,CAAC;QACvC,IAAI,KAAK,CAAC,UAAU,CAAC,MAAM,CAAC,EAAE,CAAC;YAC7B,OAAO,KAAK,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,CAAC;QAC3C,CAAC;IACH,CAAC;IAED,OAAO,KAAK,CAAC;AACf,CAAC;AAED,SAAS,mBAAmB,CAAC,KAAa;IACxC,IAAI,MAAM,GAAG,KAAK,CAAC;IAEnB,IAAI,mBAAmB,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC;QACrC,MAAM,GAAG,MAAM,CAAC,OAAO,CAAC,mBAAmB,EAAE,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;IAC1D,CAAC;IAED,IAAI,iBAAiB,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC;QACnC,MAAM,GAAG,MAAM,CAAC,OAAO,CAAC,iBAAiB,EAAE,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;IACxD,CAAC;IAED,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,SAAS,kBAAkB,CAAC,KAAa;IACvC,IAAI,MAAM,GAAG,KAAK,CAAC;IACnB,IAAI,OAAO,GAAG,IAAI,CAAC;IAEnB,OAAO,OAAO,EAAE,CAAC;QACf,OAAO,GAAG,KAAK,CAAC;QAEhB,KAAK,MAAM,MAAM,IAAI,cAAc,EAAE,CAAC;YACpC,IAAI,MAAM,KAAK,MAAM,EAAE,CAAC;gBACtB,MAAM,GAAG,EAAE,CAAC;gBACZ,OAAO,GAAG,IAAI,CAAC;gBACf,MAAM;YACR,CAAC;YAED,IAAI,MAAM,CAAC,QAAQ,CAAC,IAAI,MAAM,EAAE,CAAC,EAAE,CAAC;gBAClC,MAAM,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;gBACpD,OAAO,GAAG,IAAI,CAAC;gBACf,MAAM;YACR,CAAC;QACH,CAAC;IACH,CAAC;IAED,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,kEAAkE;AAClE,MAAM,UAAU,oBAAoB,CAAC,KAAa;IAChD,MAAM,cAAc,GAAG,KAAK,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,OAAO,CAAC,mBAAmB,EAAE,EAAE,CAAC,CAAC;IAC/E,MAAM,UAAU,GAAG,cAAc,CAAC,WAAW,EAAE,CAAC,OAAO,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC;IAC7E,MAAM,aAAa,GAAG,mBAAmB,CAAC,UAAU,CAAC,CAAC;IACtD,MAAM,aAAa,GAAG,mBAAmB,CAAC,aAAa,CAAC,CAAC;IAEzD,OAAO,kBAAkB,CAAC,aAAa,CAAC,CAAC,OAAO,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC;AACxE,CAAC"}