@firela/billclaw-core 0.1.3

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 (139) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +109 -0
  3. package/dist/billclaw.d.ts +76 -0
  4. package/dist/billclaw.d.ts.map +1 -0
  5. package/dist/billclaw.js +205 -0
  6. package/dist/billclaw.js.map +1 -0
  7. package/dist/credentials/index.d.ts +8 -0
  8. package/dist/credentials/index.d.ts.map +1 -0
  9. package/dist/credentials/index.js +8 -0
  10. package/dist/credentials/index.js.map +1 -0
  11. package/dist/credentials/keychain.d.ts +92 -0
  12. package/dist/credentials/keychain.d.ts.map +1 -0
  13. package/dist/credentials/keychain.js +172 -0
  14. package/dist/credentials/keychain.js.map +1 -0
  15. package/dist/credentials/store.d.ts +76 -0
  16. package/dist/credentials/store.d.ts.map +1 -0
  17. package/dist/credentials/store.js +144 -0
  18. package/dist/credentials/store.js.map +1 -0
  19. package/dist/errors/errors.d.ts +92 -0
  20. package/dist/errors/errors.d.ts.map +1 -0
  21. package/dist/errors/errors.js +315 -0
  22. package/dist/errors/errors.js.map +1 -0
  23. package/dist/errors/index.d.ts +7 -0
  24. package/dist/errors/index.d.ts.map +1 -0
  25. package/dist/errors/index.js +7 -0
  26. package/dist/errors/index.js.map +1 -0
  27. package/dist/exporters/beancount.d.ts +42 -0
  28. package/dist/exporters/beancount.d.ts.map +1 -0
  29. package/dist/exporters/beancount.js +141 -0
  30. package/dist/exporters/beancount.js.map +1 -0
  31. package/dist/exporters/index.d.ts +8 -0
  32. package/dist/exporters/index.d.ts.map +1 -0
  33. package/dist/exporters/index.js +8 -0
  34. package/dist/exporters/index.js.map +1 -0
  35. package/dist/exporters/ledger.d.ts +42 -0
  36. package/dist/exporters/ledger.d.ts.map +1 -0
  37. package/dist/exporters/ledger.js +139 -0
  38. package/dist/exporters/ledger.js.map +1 -0
  39. package/dist/index.d.ts +23 -0
  40. package/dist/index.d.ts.map +1 -0
  41. package/dist/index.js +34 -0
  42. package/dist/index.js.map +1 -0
  43. package/dist/models/config.d.ts +552 -0
  44. package/dist/models/config.d.ts.map +1 -0
  45. package/dist/models/config.js +168 -0
  46. package/dist/models/config.js.map +1 -0
  47. package/dist/models/index.d.ts +7 -0
  48. package/dist/models/index.d.ts.map +1 -0
  49. package/dist/models/index.js +8 -0
  50. package/dist/models/index.js.map +1 -0
  51. package/dist/runtime/index.d.ts +7 -0
  52. package/dist/runtime/index.d.ts.map +1 -0
  53. package/dist/runtime/index.js +7 -0
  54. package/dist/runtime/index.js.map +1 -0
  55. package/dist/runtime/types.d.ts +110 -0
  56. package/dist/runtime/types.d.ts.map +1 -0
  57. package/dist/runtime/types.js +85 -0
  58. package/dist/runtime/types.js.map +1 -0
  59. package/dist/security/audit.d.ts +148 -0
  60. package/dist/security/audit.d.ts.map +1 -0
  61. package/dist/security/audit.js +286 -0
  62. package/dist/security/audit.js.map +1 -0
  63. package/dist/security/index.d.ts +7 -0
  64. package/dist/security/index.d.ts.map +1 -0
  65. package/dist/security/index.js +7 -0
  66. package/dist/security/index.js.map +1 -0
  67. package/dist/services/event-emitter.d.ts +171 -0
  68. package/dist/services/event-emitter.d.ts.map +1 -0
  69. package/dist/services/event-emitter.js +287 -0
  70. package/dist/services/event-emitter.js.map +1 -0
  71. package/dist/services/index.d.ts +8 -0
  72. package/dist/services/index.d.ts.map +1 -0
  73. package/dist/services/index.js +8 -0
  74. package/dist/services/index.js.map +1 -0
  75. package/dist/sources/gmail/bill-recognizer.d.ts +71 -0
  76. package/dist/sources/gmail/bill-recognizer.d.ts.map +1 -0
  77. package/dist/sources/gmail/bill-recognizer.js +341 -0
  78. package/dist/sources/gmail/bill-recognizer.js.map +1 -0
  79. package/dist/sources/gmail/email-parser.d.ts +68 -0
  80. package/dist/sources/gmail/email-parser.d.ts.map +1 -0
  81. package/dist/sources/gmail/email-parser.js +238 -0
  82. package/dist/sources/gmail/email-parser.js.map +1 -0
  83. package/dist/sources/gmail/gmail-fetch.d.ts +54 -0
  84. package/dist/sources/gmail/gmail-fetch.d.ts.map +1 -0
  85. package/dist/sources/gmail/gmail-fetch.js +300 -0
  86. package/dist/sources/gmail/gmail-fetch.js.map +1 -0
  87. package/dist/sources/gmail/index.d.ts +7 -0
  88. package/dist/sources/gmail/index.d.ts.map +1 -0
  89. package/dist/sources/gmail/index.js +7 -0
  90. package/dist/sources/gmail/index.js.map +1 -0
  91. package/dist/sources/index.d.ts +8 -0
  92. package/dist/sources/index.d.ts.map +1 -0
  93. package/dist/sources/index.js +8 -0
  94. package/dist/sources/index.js.map +1 -0
  95. package/dist/sources/plaid/index.d.ts +7 -0
  96. package/dist/sources/plaid/index.d.ts.map +1 -0
  97. package/dist/sources/plaid/index.js +7 -0
  98. package/dist/sources/plaid/index.js.map +1 -0
  99. package/dist/sources/plaid/plaid-sync.d.ts +42 -0
  100. package/dist/sources/plaid/plaid-sync.d.ts.map +1 -0
  101. package/dist/sources/plaid/plaid-sync.js +182 -0
  102. package/dist/sources/plaid/plaid-sync.js.map +1 -0
  103. package/dist/storage/cache.d.ts +134 -0
  104. package/dist/storage/cache.d.ts.map +1 -0
  105. package/dist/storage/cache.js +239 -0
  106. package/dist/storage/cache.js.map +1 -0
  107. package/dist/storage/index.d.ts +11 -0
  108. package/dist/storage/index.d.ts.map +1 -0
  109. package/dist/storage/index.js +11 -0
  110. package/dist/storage/index.js.map +1 -0
  111. package/dist/storage/indexes.d.ts +136 -0
  112. package/dist/storage/indexes.d.ts.map +1 -0
  113. package/dist/storage/indexes.js +294 -0
  114. package/dist/storage/indexes.js.map +1 -0
  115. package/dist/storage/locking.d.ts +103 -0
  116. package/dist/storage/locking.d.ts.map +1 -0
  117. package/dist/storage/locking.js +158 -0
  118. package/dist/storage/locking.js.map +1 -0
  119. package/dist/storage/streaming.d.ts +102 -0
  120. package/dist/storage/streaming.d.ts.map +1 -0
  121. package/dist/storage/streaming.js +245 -0
  122. package/dist/storage/streaming.js.map +1 -0
  123. package/dist/storage/transaction-storage.d.ts +101 -0
  124. package/dist/storage/transaction-storage.d.ts.map +1 -0
  125. package/dist/storage/transaction-storage.js +193 -0
  126. package/dist/storage/transaction-storage.js.map +1 -0
  127. package/dist/sync/index.d.ts +7 -0
  128. package/dist/sync/index.d.ts.map +1 -0
  129. package/dist/sync/index.js +7 -0
  130. package/dist/sync/index.js.map +1 -0
  131. package/dist/sync/sync-service.d.ts +42 -0
  132. package/dist/sync/sync-service.d.ts.map +1 -0
  133. package/dist/sync/sync-service.js +112 -0
  134. package/dist/sync/sync-service.js.map +1 -0
  135. package/dist/test-fixtures.d.ts +38 -0
  136. package/dist/test-fixtures.d.ts.map +1 -0
  137. package/dist/test-fixtures.js +137 -0
  138. package/dist/test-fixtures.js.map +1 -0
  139. package/package.json +68 -0
@@ -0,0 +1,341 @@
1
+ /**
2
+ * Bill Recognition Service - Framework-agnostic bill detection from email content
3
+ *
4
+ * Analyzes email content to determine if it's a bill and extracts relevant information.
5
+ * Uses keyword matching, sender detection, and confidence scoring.
6
+ */
7
+ /**
8
+ * Default bill detection keywords
9
+ * Includes both phrases and individual words for better matching
10
+ */
11
+ export const DEFAULT_KEYWORDS = [
12
+ // Phrases
13
+ "invoice",
14
+ "statement",
15
+ "bill due",
16
+ "receipt",
17
+ "payment due",
18
+ "amount due",
19
+ "past due",
20
+ "payment received",
21
+ "your bill",
22
+ "account statement",
23
+ "credit card statement",
24
+ "utility bill",
25
+ "electric bill",
26
+ "water bill",
27
+ "internet bill",
28
+ "phone bill",
29
+ "insurance premium",
30
+ "subscription",
31
+ "membership",
32
+ // Individual words for better matching
33
+ "premium",
34
+ "due",
35
+ "notice",
36
+ "invoice",
37
+ "statement",
38
+ "bill",
39
+ "receipt",
40
+ "payment",
41
+ "subscription",
42
+ "membership",
43
+ ];
44
+ /**
45
+ * Known billing domains (extracted from common service providers)
46
+ */
47
+ export const KNOWN_BILLING_DOMAINS = [
48
+ "@netflix.com",
49
+ "@paypal.com",
50
+ "@amazon.com",
51
+ "@apple.com",
52
+ "@google.com",
53
+ "@spotify.com",
54
+ "@att.com",
55
+ "@verizon.com",
56
+ "@comcast.com",
57
+ "@pge.com",
58
+ "@chase.com",
59
+ "@citibank.com",
60
+ "@amex.com",
61
+ "@discover.com",
62
+ "@capitalone.com",
63
+ "@geico.com",
64
+ "@progressive.com",
65
+ "@statefarm.com",
66
+ ];
67
+ /**
68
+ * Default bill type patterns for classification
69
+ * Ordered by specificity (more specific patterns first)
70
+ */
71
+ const DEFAULT_BILL_TYPE_PATTERNS = {
72
+ Utility: ["electric", "gas", "water", "utility", "power", "energy", "pge"],
73
+ Internet: ["internet", "broadband", "wifi", "fiber", "xfinity", "comcast"],
74
+ Phone: [
75
+ "phone",
76
+ "wireless",
77
+ "mobile",
78
+ "cellular",
79
+ "att",
80
+ "verizon",
81
+ "t-mobile",
82
+ ],
83
+ Insurance: [
84
+ "insurance",
85
+ "premium",
86
+ "coverage",
87
+ "geico",
88
+ "progressive",
89
+ "state farm",
90
+ ],
91
+ Subscription: [
92
+ "subscription",
93
+ "membership",
94
+ "netflix",
95
+ "spotify",
96
+ "amazon prime",
97
+ ],
98
+ "Credit Card": ["credit card", "visa", "mastercard", "amex", "discover"],
99
+ Housing: ["rent", "lease", "mortgage", "housing", "apartment"],
100
+ Loan: ["loan", "financing", "student loan"],
101
+ Invoice: ["invoice", "billing", "amount due", "payment due"],
102
+ Receipt: ["receipt", "purchase", "order confirmation"],
103
+ Generic: ["statement"], // Most generic, should be last
104
+ };
105
+ /**
106
+ * Extract domain from email address
107
+ */
108
+ export function extractDomain(email) {
109
+ const match = email.toLowerCase().match(/@([^@\s]+)$/);
110
+ return match ? match[1] : "";
111
+ }
112
+ /**
113
+ * Check if sender is in whitelist
114
+ */
115
+ function isWhitelistedSender(from, whitelist) {
116
+ const fromLower = from.toLowerCase();
117
+ for (const entry of whitelist) {
118
+ const entryLower = entry.toLowerCase();
119
+ // Exact match (e.g., "billing@paypal.com")
120
+ if (fromLower === entryLower) {
121
+ return true;
122
+ }
123
+ // Domain match (e.g., "@paypal.com" matches "billing@paypal.com")
124
+ if (entryLower.startsWith("@") && fromLower.endsWith(entryLower)) {
125
+ return true;
126
+ }
127
+ // Wildcard pattern (e.g., "*@paypal.com")
128
+ if (entryLower.startsWith("*@") &&
129
+ fromLower.endsWith(entryLower.slice(1))) {
130
+ return true;
131
+ }
132
+ }
133
+ return false;
134
+ }
135
+ /**
136
+ * Check if text contains any keywords (case-insensitive)
137
+ */
138
+ function containsKeywords(text, keywords) {
139
+ const textLower = text.toLowerCase();
140
+ return keywords.some((keyword) => textLower.includes(keyword.toLowerCase()));
141
+ }
142
+ /**
143
+ * Extract amount from text using regex patterns
144
+ */
145
+ function extractAmount(text) {
146
+ // Pattern 1: $123.45 or $1,234.56
147
+ const usdPattern = /\$\s*([0-9,]+\.?\d*)/g;
148
+ const usdMatch = usdPattern.exec(text);
149
+ if (usdMatch) {
150
+ const amount = parseFloat(usdMatch[1].replace(/,/g, ""));
151
+ if (!isNaN(amount) && amount > 0) {
152
+ return { amount, currency: "USD" };
153
+ }
154
+ }
155
+ // Pattern 2: €123,45 or €123.45
156
+ const eurPattern = /€\s*([0-9.,]+(?:[,.]\d{2})?)/g;
157
+ const eurMatch = eurPattern.exec(text);
158
+ if (eurMatch) {
159
+ const amountStr = eurMatch[1].replace(/\./g, "").replace(/,/, ".");
160
+ const amount = parseFloat(amountStr);
161
+ if (!isNaN(amount) && amount > 0) {
162
+ return { amount, currency: "EUR" };
163
+ }
164
+ }
165
+ // Pattern 3: £123.45
166
+ const gbpPattern = /£\s*([0-9,]+\.?\d*)/g;
167
+ const gbpMatch = gbpPattern.exec(text);
168
+ if (gbpMatch) {
169
+ const amount = parseFloat(gbpMatch[1].replace(/,/g, ""));
170
+ if (!isNaN(amount) && amount > 0) {
171
+ return { amount, currency: "GBP" };
172
+ }
173
+ }
174
+ // Pattern 4: Amount: 123.45 USD
175
+ const amountPattern = /amount[:\s]+([0-9,]+\.?\d*)\s*(USD|EUR|GBP|CAD|AUD)?/i;
176
+ const amountMatch = amountPattern.exec(text);
177
+ if (amountMatch) {
178
+ const amount = parseFloat(amountMatch[1].replace(/,/g, ""));
179
+ const currency = amountMatch[2]?.toUpperCase() || "USD";
180
+ if (!isNaN(amount) && amount > 0) {
181
+ return { amount, currency };
182
+ }
183
+ }
184
+ return null;
185
+ }
186
+ /**
187
+ * Extract date from text using regex patterns
188
+ */
189
+ function extractDate(text) {
190
+ // Pattern 1: MM/DD/YYYY or DD/MM/YYYY
191
+ const datePattern1 = /\b(0[1-9]|1[0-2])\/(0[1-9]|[12]\d|3[01])\/(20\d{2})\b/g;
192
+ const match1 = datePattern1.exec(text);
193
+ if (match1) {
194
+ return match1[0];
195
+ }
196
+ // Pattern 2: YYYY-MM-DD
197
+ const datePattern2 = /\b(20\d{2})-(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01])\b/g;
198
+ const match2 = datePattern2.exec(text);
199
+ if (match2) {
200
+ return match2[0];
201
+ }
202
+ // Pattern 3: "Due Date: January 15, 2024" or "Due: Jan 15"
203
+ const dueDatePattern = /(?:due|payment|expire)[s:\s]+([A-Za-z]+)\s+(\d{1,2}),?\s*(20\d{2})?/i;
204
+ const match3 = dueDatePattern.exec(text);
205
+ if (match3) {
206
+ const month = match3[1];
207
+ const day = match3[2];
208
+ const year = match3[3] || new Date().getFullYear();
209
+ return `${month} ${day}, ${year}`;
210
+ }
211
+ return null;
212
+ }
213
+ /**
214
+ * Classify bill type based on content
215
+ * Returns the type with the most matching keywords
216
+ */
217
+ function classifyBillType(subject, body, patterns) {
218
+ const content = `${subject} ${body}`.toLowerCase();
219
+ // Score each bill type by number of matching keywords
220
+ const scores = [];
221
+ for (const [billType, keywords] of Object.entries(patterns)) {
222
+ const matchCount = keywords.filter((keyword) => content.includes(keyword.toLowerCase())).length;
223
+ if (matchCount > 0) {
224
+ scores.push({ type: billType, score: matchCount });
225
+ }
226
+ }
227
+ // Return type with highest score, or undefined if no matches
228
+ if (scores.length === 0) {
229
+ return undefined;
230
+ }
231
+ // Sort by score descending, then by specificity (longer keyword matches first)
232
+ scores.sort((a, b) => {
233
+ if (b.score !== a.score)
234
+ return b.score - a.score;
235
+ // Tie-breaker: prefer types that come first (more specific patterns defined first)
236
+ return 0;
237
+ });
238
+ return scores[0].type;
239
+ }
240
+ /**
241
+ * Recognize if an email is a bill and extract relevant information
242
+ */
243
+ export function recognizeBill(email, config) {
244
+ const reasons = [];
245
+ let confidence = 0;
246
+ // Use custom keywords or defaults
247
+ const keywords = config.keywords.length > 0 ? config.keywords : DEFAULT_KEYWORDS;
248
+ // Use custom bill type patterns or defaults
249
+ const billTypePatterns = config.billTypePatterns || DEFAULT_BILL_TYPE_PATTERNS;
250
+ // Check 1: Sender whitelist (strongest signal)
251
+ const isWhitelisted = isWhitelistedSender(email.from, config.senderWhitelist);
252
+ if (isWhitelisted) {
253
+ confidence += 0.4;
254
+ reasons.push("Sender is in whitelist");
255
+ }
256
+ // Check 2: Known billing domain
257
+ const isKnownBillingDomain = KNOWN_BILLING_DOMAINS.some((d) => {
258
+ const billingDomain = d.startsWith("@") ? d : `@${d}`;
259
+ return email.from.toLowerCase().endsWith(billingDomain);
260
+ });
261
+ if (isKnownBillingDomain) {
262
+ confidence += 0.25;
263
+ reasons.push("Known billing domain");
264
+ }
265
+ // Check 2.5: Sender contains "billing" - strong signal
266
+ if (email.from.toLowerCase().includes("billing")) {
267
+ confidence += 0.3;
268
+ reasons.push("Sender is billing address");
269
+ }
270
+ // Check 3: Keyword matching in subject and body
271
+ const subjectHasKeywords = containsKeywords(email.subject, keywords);
272
+ const bodyHasKeywords = containsKeywords(email.body, keywords);
273
+ if (subjectHasKeywords) {
274
+ confidence += 0.2;
275
+ reasons.push("Subject contains bill keywords");
276
+ }
277
+ if (bodyHasKeywords) {
278
+ confidence += 0.1;
279
+ reasons.push("Body contains bill keywords");
280
+ }
281
+ // Extract amount and date
282
+ const amountInfo = extractAmount(email.subject + " " + email.body);
283
+ const dueDate = extractDate(email.subject + " " + email.body);
284
+ // Bonus: Has amount
285
+ if (amountInfo) {
286
+ confidence += 0.05;
287
+ reasons.push("Contains amount");
288
+ }
289
+ // Check required fields
290
+ if (config.requireAmount && !amountInfo) {
291
+ return {
292
+ isBill: false,
293
+ confidence: 0,
294
+ reasons: ["Missing required amount"],
295
+ };
296
+ }
297
+ if (config.requireDate && !dueDate) {
298
+ return {
299
+ isBill: false,
300
+ confidence: 0,
301
+ reasons: ["Missing required date"],
302
+ };
303
+ }
304
+ // Classify bill type
305
+ const billType = classifyBillType(email.subject, email.body, billTypePatterns);
306
+ // Extract merchant name (from sender name before email)
307
+ const merchantMatch = email.from.match(/^"?([^"<>@]+)"?\s*</);
308
+ const merchant = merchantMatch
309
+ ? merchantMatch[1].trim()
310
+ : email.from.split("@")[0];
311
+ // Determine if it's a bill based on confidence threshold
312
+ const threshold = config.confidenceThreshold || 0.5;
313
+ const isBill = confidence >= threshold;
314
+ return {
315
+ isBill,
316
+ confidence: Math.min(confidence, 1),
317
+ amount: amountInfo?.amount,
318
+ currency: amountInfo?.currency,
319
+ dueDate: dueDate || undefined,
320
+ billType,
321
+ merchant,
322
+ reasons,
323
+ };
324
+ }
325
+ /**
326
+ * Recognize bills from multiple emails
327
+ */
328
+ export function recognizeBills(emails, config) {
329
+ return emails.map((email) => recognizeBill(email, config));
330
+ }
331
+ /**
332
+ * Filter emails to only bills based on recognition
333
+ */
334
+ export function filterToBills(emails, config) {
335
+ const results = emails.map((email) => ({
336
+ email,
337
+ recognition: recognizeBill(email, config),
338
+ }));
339
+ return results.filter(({ recognition }) => recognition.isBill);
340
+ }
341
+ //# sourceMappingURL=bill-recognizer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bill-recognizer.js","sourceRoot":"","sources":["../../../src/sources/gmail/bill-recognizer.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,68 @@
1
+ /**
2
+ * Email Parser Service - Extract structured transaction data from bill emails
3
+ *
4
+ * Parses email content to extract amounts, dates, merchants, and line items.
5
+ * Handles HTML tables, plain text lists, and various email formats.
6
+ */
7
+ import type { BillRecognition } from "./bill-recognizer.js";
8
+ export interface ParsedTransaction {
9
+ transactionId: string;
10
+ accountId: string;
11
+ date: string;
12
+ amount: number;
13
+ currency: string;
14
+ category: string[];
15
+ merchantName: string;
16
+ paymentChannel: string;
17
+ pending: boolean;
18
+ description?: string;
19
+ lineItems?: LineItem[];
20
+ }
21
+ export interface LineItem {
22
+ description: string;
23
+ amount: number;
24
+ quantity?: number;
25
+ date?: string;
26
+ }
27
+ /**
28
+ * Parse HTML table from email body
29
+ * Many billing emails contain transaction data in HTML tables
30
+ */
31
+ export declare function parseHTMLTable(html: string): LineItem[] | null;
32
+ /**
33
+ * Parse plain text list from email body
34
+ * Some bills use plain text with amounts on each line
35
+ */
36
+ export declare function parseTextList(text: string): LineItem[] | null;
37
+ /**
38
+ * Extract multiple amounts from text and return the largest
39
+ * Useful for finding the total amount in a bill
40
+ */
41
+ export declare function extractTotalAmount(text: string): number | null;
42
+ /**
43
+ * Extract currency from text
44
+ */
45
+ export declare function extractCurrency(text: string): string;
46
+ /**
47
+ * Parse bill email into structured transaction data
48
+ */
49
+ export declare function parseBillToTransaction(accountId: string, emailId: string, emailSubject: string, emailBody: string, emailFrom: string, emailDate: string, recognition: BillRecognition): ParsedTransaction;
50
+ /**
51
+ * Parse multiple bill emails into transactions
52
+ */
53
+ export declare function parseBillsToTransactions(accountId: string, emails: Array<{
54
+ id: string;
55
+ subject: string;
56
+ body: string;
57
+ from: string;
58
+ date: string;
59
+ }>, recognitions: BillRecognition[]): ParsedTransaction[];
60
+ /**
61
+ * Extract account number from email content
62
+ */
63
+ export declare function extractAccountNumber(text: string): string | undefined;
64
+ /**
65
+ * Extract due date from email content
66
+ */
67
+ export declare function extractDueDate(text: string): string | undefined;
68
+ //# sourceMappingURL=email-parser.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"email-parser.d.ts","sourceRoot":"","sources":["../../../src/sources/gmail/email-parser.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAA;AAE3D,MAAM,WAAW,iBAAiB;IAChC,aAAa,EAAE,MAAM,CAAA;IACrB,SAAS,EAAE,MAAM,CAAA;IACjB,IAAI,EAAE,MAAM,CAAA;IACZ,MAAM,EAAE,MAAM,CAAA;IACd,QAAQ,EAAE,MAAM,CAAA;IAChB,QAAQ,EAAE,MAAM,EAAE,CAAA;IAClB,YAAY,EAAE,MAAM,CAAA;IACpB,cAAc,EAAE,MAAM,CAAA;IACtB,OAAO,EAAE,OAAO,CAAA;IAChB,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,SAAS,CAAC,EAAE,QAAQ,EAAE,CAAA;CACvB;AAED,MAAM,WAAW,QAAQ;IACvB,WAAW,EAAE,MAAM,CAAA;IACnB,MAAM,EAAE,MAAM,CAAA;IACd,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,IAAI,CAAC,EAAE,MAAM,CAAA;CACd;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,QAAQ,EAAE,GAAG,IAAI,CAgD9D;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,QAAQ,EAAE,GAAG,IAAI,CAmB7D;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CA4B9D;AAED;;GAEG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAkBpD;AAED;;GAEG;AACH,wBAAgB,sBAAsB,CACpC,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,MAAM,EACf,YAAY,EAAE,MAAM,EACpB,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,MAAM,EACjB,WAAW,EAAE,eAAe,GAC3B,iBAAiB,CAgDnB;AA0BD;;GAEG;AACH,wBAAgB,wBAAwB,CACtC,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,KAAK,CAAC;IACZ,EAAE,EAAE,MAAM,CAAA;IACV,OAAO,EAAE,MAAM,CAAA;IACf,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,MAAM,CAAA;CACb,CAAC,EACF,YAAY,EAAE,eAAe,EAAE,GAC9B,iBAAiB,EAAE,CAYrB;AAED;;GAEG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAiBrE;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAoB/D"}
@@ -0,0 +1,238 @@
1
+ /**
2
+ * Email Parser Service - Extract structured transaction data from bill emails
3
+ *
4
+ * Parses email content to extract amounts, dates, merchants, and line items.
5
+ * Handles HTML tables, plain text lists, and various email formats.
6
+ */
7
+ /**
8
+ * Parse HTML table from email body
9
+ * Many billing emails contain transaction data in HTML tables
10
+ */
11
+ export function parseHTMLTable(html) {
12
+ // Look for <table> elements
13
+ const tableMatch = html.match(/<table[^>]*>([\s\S]*?)<\/table>/gi);
14
+ if (!tableMatch) {
15
+ return null;
16
+ }
17
+ const lineItems = [];
18
+ for (const table of tableMatch) {
19
+ // Find all <tr> elements
20
+ const rowMatches = table.match(/<tr[^>]*>([\s\S]*?)<\/tr>/gi);
21
+ if (!rowMatches) {
22
+ continue;
23
+ }
24
+ for (const row of rowMatches) {
25
+ // Extract <td> or <th> content
26
+ const cellMatches = row.match(/<t[dh][^>]*>([\s\S]*?)<\/t[dh]>/gi);
27
+ if (!cellMatches || cellMatches.length < 2) {
28
+ continue;
29
+ }
30
+ // Clean cell content (remove HTML tags)
31
+ const cleanCells = cellMatches.map((cell) => cell.replace(/<[^>]+>/g, "").trim());
32
+ // Look for amount in cells (contains $ or number with decimals)
33
+ let amount = 0;
34
+ let description = "";
35
+ for (const cell of cleanCells) {
36
+ const amountMatch = cell.match(/\$?([0-9,]+\.\d{2})/);
37
+ if (amountMatch && !amount) {
38
+ amount = parseFloat(amountMatch[1].replace(/,/g, ""));
39
+ }
40
+ else if (cell && cell.length > 2 && !description) {
41
+ description = cell;
42
+ }
43
+ }
44
+ if (amount > 0 && description) {
45
+ lineItems.push({ description, amount });
46
+ }
47
+ }
48
+ }
49
+ return lineItems.length > 0 ? lineItems : null;
50
+ }
51
+ /**
52
+ * Parse plain text list from email body
53
+ * Some bills use plain text with amounts on each line
54
+ */
55
+ export function parseTextList(text) {
56
+ const lines = text.split("\n");
57
+ const lineItems = [];
58
+ for (const line of lines) {
59
+ // Look for lines with amounts (format: Description $XX.XX or $XX)
60
+ // Allow alphanumeric descriptions (e.g., "Item 1")
61
+ // Cents are optional (e.g., $20 or $20.00)
62
+ const match = line.match(/^([A-Za-z0-9\s&]+)\s+\$?([0-9,]+(?:\.\d{2})?)/);
63
+ if (match) {
64
+ const description = match[1].trim();
65
+ const amount = parseFloat(match[2].replace(/,/g, ""));
66
+ if (amount > 0) {
67
+ lineItems.push({ description, amount });
68
+ }
69
+ }
70
+ }
71
+ return lineItems.length > 0 ? lineItems : null;
72
+ }
73
+ /**
74
+ * Extract multiple amounts from text and return the largest
75
+ * Useful for finding the total amount in a bill
76
+ */
77
+ export function extractTotalAmount(text) {
78
+ const amounts = [];
79
+ // Match various amount formats: $123.45, 123.45 USD, etc.
80
+ const patterns = [
81
+ /\$\s*([0-9,]+\.\d{2})/g, // $123.45
82
+ /([0-9,]+\.\d{2})\s*USD/gi, // 123.45 USD
83
+ /total[:\s]+\$?\s*([0-9,]+\.\d{2})/gi, // Total: $123.45
84
+ /amount[:\s]+\$?\s*([0-9,]+\.\d{2})/gi, // Amount: $123.45
85
+ /balance due[:\s]+\$?\s*([0-9,]+\.\d{2})/gi, // Balance Due: $123.45
86
+ ];
87
+ for (const pattern of patterns) {
88
+ let match;
89
+ while ((match = pattern.exec(text)) !== null) {
90
+ const amount = parseFloat(match[1].replace(/,/g, ""));
91
+ if (!isNaN(amount) && amount > 0) {
92
+ amounts.push(amount);
93
+ }
94
+ }
95
+ }
96
+ if (amounts.length === 0) {
97
+ return null;
98
+ }
99
+ // Return the largest amount (likely the total)
100
+ return Math.max(...amounts);
101
+ }
102
+ /**
103
+ * Extract currency from text
104
+ */
105
+ export function extractCurrency(text) {
106
+ // Check more specific currency symbols first (before generic $)
107
+ if (text.includes("C$") || /cad/i.test(text)) {
108
+ return "CAD";
109
+ }
110
+ if (text.includes("A$") || /aud/i.test(text)) {
111
+ return "AUD";
112
+ }
113
+ if (text.includes("€") || /eur/i.test(text)) {
114
+ return "EUR";
115
+ }
116
+ if (text.includes("£") || /gbp/i.test(text)) {
117
+ return "GBP";
118
+ }
119
+ if (text.includes("$") || /usd/i.test(text)) {
120
+ return "USD";
121
+ }
122
+ return "USD"; // Default
123
+ }
124
+ /**
125
+ * Parse bill email into structured transaction data
126
+ */
127
+ export function parseBillToTransaction(accountId, emailId, emailSubject, emailBody, emailFrom, emailDate, recognition) {
128
+ // Use recognition data if available
129
+ const amount = recognition.amount
130
+ ? Math.round(recognition.amount * 100) // Convert to cents
131
+ : (() => {
132
+ const totalAmount = extractTotalAmount(emailSubject + " " + emailBody);
133
+ return totalAmount ? Math.round(totalAmount * 100) : 0;
134
+ })();
135
+ const currency = recognition.currency || extractCurrency(emailBody);
136
+ // Extract merchant name (prioritize recognition, then email extraction)
137
+ const merchantMatch = emailFrom.match(/^"?([^"<>@]+)"?\s*</);
138
+ const merchantName = recognition.merchant ||
139
+ (merchantMatch ? merchantMatch[1].trim() : emailFrom.split("@")[0]);
140
+ // Extract date (prefer recognition due date, fall back to email date)
141
+ const date = recognition.dueDate || emailDate;
142
+ // Determine category based on bill type
143
+ const category = getCategoryForBillType(recognition.billType);
144
+ // Parse line items if available
145
+ let lineItems;
146
+ const htmlTable = parseHTMLTable(emailBody);
147
+ if (htmlTable) {
148
+ lineItems = htmlTable;
149
+ }
150
+ else {
151
+ const textList = parseTextList(emailBody);
152
+ if (textList) {
153
+ lineItems = textList;
154
+ }
155
+ }
156
+ return {
157
+ transactionId: `${accountId}_email_${emailId}`,
158
+ accountId,
159
+ date,
160
+ amount,
161
+ currency,
162
+ category,
163
+ merchantName,
164
+ paymentChannel: "email",
165
+ pending: false,
166
+ description: emailSubject,
167
+ lineItems,
168
+ };
169
+ }
170
+ /**
171
+ * Map bill type to transaction category
172
+ */
173
+ function getCategoryForBillType(billType) {
174
+ if (!billType) {
175
+ return ["bills"];
176
+ }
177
+ const categoryMap = {
178
+ "Credit Card": ["credit-card", "bills"],
179
+ Utility: ["utilities", "bills"],
180
+ Internet: ["internet", "utilities"],
181
+ Phone: ["phone", "utilities"],
182
+ Insurance: ["insurance", "bills"],
183
+ Subscription: ["subscriptions", "entertainment"],
184
+ Housing: ["housing", "rent"],
185
+ Loan: ["loan", "debt"],
186
+ Invoice: ["bills"],
187
+ Receipt: ["purchases"],
188
+ };
189
+ return categoryMap[billType] || ["bills"];
190
+ }
191
+ /**
192
+ * Parse multiple bill emails into transactions
193
+ */
194
+ export function parseBillsToTransactions(accountId, emails, recognitions) {
195
+ return emails.map((email, index) => parseBillToTransaction(accountId, email.id, email.subject, email.body, email.from, email.date, recognitions[index]));
196
+ }
197
+ /**
198
+ * Extract account number from email content
199
+ */
200
+ export function extractAccountNumber(text) {
201
+ // Common patterns for account numbers
202
+ const patterns = [
203
+ /account\s*(?:number|#|no\.?)[:\s]+([A-Z0-9-]{4,})/i,
204
+ /ending\s+in\s+(\d{4})/i,
205
+ /\*{3,}\s*(\d{4})/i, // *** 1234 or ***1234
206
+ /card\s*(?:number|#|no\.?)[:\s]+([A-Z0-9-]{4,})/i,
207
+ ];
208
+ for (const pattern of patterns) {
209
+ const match = pattern.exec(text);
210
+ if (match) {
211
+ return match[1];
212
+ }
213
+ }
214
+ return undefined;
215
+ }
216
+ /**
217
+ * Extract due date from email content
218
+ */
219
+ export function extractDueDate(text) {
220
+ // Pattern: "Due: MM/DD/YYYY" or "Due Date: January 15, 2024"
221
+ const patterns = [
222
+ /(?:due|payment)\s*(?:date|by)?[:\s]+([A-Za-z]+)\s+(\d{1,2}),?\s*(\d{4})?/i,
223
+ /(?:due|payment)\s*(?:date|by)?[:\s]+(\d{1,2})\/(\d{1,2})\/(\d{4})/i,
224
+ /(?:due|payment)\s*(?:date|by)?[:\s]+(\d{4})-(\d{1,2})-(\d{1,2})/i,
225
+ ];
226
+ for (const pattern of patterns) {
227
+ const match = pattern.exec(text);
228
+ if (match) {
229
+ // Reconstruct the date string
230
+ if (match[0].includes("/")) {
231
+ return match[0]; // Already in MM/DD/YYYY format
232
+ }
233
+ return match.slice(1).join(" "); // "January 15 2024" format
234
+ }
235
+ }
236
+ return undefined;
237
+ }
238
+ //# sourceMappingURL=email-parser.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"email-parser.js","sourceRoot":"","sources":["../../../src/sources/gmail/email-parser.ts"],"names":[],"mappings":"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"}