From b79778a855c3532298837a723fd60046d7f2ff64 Mon Sep 17 00:00:00 2001 From: lokeshramchand-ctrl Date: Fri, 22 Aug 2025 05:24:47 +0530 Subject: [PATCH] OHO-13(Almost Completed Mail) --- Backend/index.js | 141 +++++++++++------------ Backend/testing.js | 53 ++++----- Frontend/lib/other_pages/enviroment.dart | 2 +- 3 files changed, 93 insertions(+), 103 deletions(-) diff --git a/Backend/index.js b/Backend/index.js index b10da02..53ecd34 100644 --- a/Backend/index.js +++ b/Backend/index.js @@ -30,17 +30,13 @@ const User = mongoose.model('User', userSchema); const transactionSchema = new mongoose.Schema({ userId: { type: mongoose.Schema.Types.ObjectId, ref: 'User', required: true }, - description: { type: String, required: true }, amount: { type: Number, required: true }, category: { type: String, default: "Other" }, date: { type: Date, default: Date.now }, - type: { type: String, default: "unknown" }, vendor: String, // Optional vendor/store name - source: { type: String, enum: ['manual', 'voice', 'email'], required: true }, - referenceNumber: { type: String, unique: true, sparse: true }, // Unique transaction id for deduplication }); @@ -164,7 +160,7 @@ app.post('/api/transaction/add', async (req, res) => { const { description, amount, userId } = req.body; if (!userId) return res.status(400).json({ error: 'Missing userId' }); - const predictRes = await axios.post('http://10.231.8.114:5000/api/predict', { + const predictRes = await axios.post('http://192.168.1.101:5000/api/predict', { description, }); const category = predictRes.data.category || 'Other'; @@ -213,7 +209,7 @@ app.get('/api/transactions/recent', async (req, res) => { res.status(500).json({ error: err.message }); } }); - +//VOICE app.post('/api/transactions/voice', async (req, res) => { try { const { voiceInput } = req.body; @@ -231,7 +227,7 @@ app.post('/api/transactions/voice', async (req, res) => { .trim(); const description = cleaned || 'misc'; // Step 3: Predict category using your Flask API - const predictRes = await axios.post('http://10.231.8.114:5000/api/predict', { + const predictRes = await axios.post('http://192.168.1.101:5000/api/predict', { description, }); const category = predictRes.data?.category || 'Other'; @@ -266,40 +262,41 @@ function parseBankMessage(snippet) { vendor: null, type: null, date: null, + referenceNumber: null, }; - // Amount (handles Rs/INR) + // Amount const amtMatch = snippet.match(/(?:Rs\.?|INR)\s*([\d,]+\.?\d*)/i); if (amtMatch) result.amount = parseFloat(amtMatch[1].replace(/,/g, "")); - // Debit / Credit if (/debited/i.test(snippet)) result.type = "debit"; else if (/credited/i.test(snippet)) result.type = "credit"; - // Vendor (look for "to" or "at") - // ✅ Extract vendor name cleanly (ignore VPAs and emails) - const vendorMatch = snippet.match(/to\s+([A-Za-z0-9@.\s&-]+)/i); - if (vendorMatch) { - let vendorRaw = vendorMatch[1].trim(); - // Remove email-like or VPA patterns (e.g., xxxx@xxxx) - vendorRaw = vendorRaw.replace(/[a-z0-9._%+-]+@[a-z0-9.-]+/gi, '').trim(); - // Collapse multiple spaces - vendorRaw = vendorRaw.replace(/\s{2,}/g, ' '); - result.vendor = vendorRaw; - } else { - const atMatch = snippet.match(/at\s+([A-Za-z0-9\s&.-]+)/i); - if (atMatch) { - let vendorRaw = atMatch[1].trim(); - vendorRaw = vendorRaw.replace(/[a-z0-9._%+-]+@[a-z0-9.-]+/gi, '').trim(); - vendorRaw = vendorRaw.replace(/\s{2,}/g, ' '); - result.vendor = vendorRaw; - } - } - // Date (dd-mm-yy or dd/mm/yyyy) - const dateMatch = snippet.match(/on\s+(\d{2}[-/]\d{2}[-/]\d{2,4})/); + // Date + const dateMatch = snippet.match(/on\s+(\d{2}[-/]\d{2}[-/]\d{2,4})/i); if (dateMatch) result.date = dateMatch[1]; + // Reference Number (flexible regex) + const refMatch = snippet.match(/(?:reference number is|UPI reference number is|Ref No:?)\s*([A-Za-z0-9]+)/i); + if (refMatch) result.referenceNumber = refMatch[1].trim(); + + // Vendor extraction - remove emails, pick last uppercase phrase + let vendorCandidate = snippet.replace(/[a-z0-9._%+-]+@[a-z0-9.-]+/gi, '').trim(); + + const vendorWords = vendorCandidate.match(/(?:\b[A-Z]{2,}\b(?:\s)?)+/g); + if (vendorWords && vendorWords.length > 0) { + result.vendor = vendorWords[vendorWords.length - 1].trim(); + } else { + // Fallback: extract after "to " or "at " + const toMatch = snippet.match(/to\s+([A-Za-z0-9\s&.-]+)/i); + if (toMatch) { + let v = toMatch[1].trim(); + v = v.replace(/[a-z0-9._%+-]+@[a-z0-9.-]+/gi, '').trim(); + v = v.replace(/\s{2,}/g, ' '); + result.vendor = v; + } + } return result; } @@ -327,7 +324,18 @@ async function fetchBankEmails(accessToken, bankEmails) { id: msg.id, }); - const snippet = fullMessage.data.snippet || ""; + let body = ""; + if (fullMessage.data.payload?.parts) { + for (const part of fullMessage.data.payload.parts) { + if (part.mimeType === "text/plain" && part.body?.data) { + body += Buffer.from(part.body.data, "base64").toString("utf-8"); + } + if (part.mimeType === "text/html" && part.body?.data) { + body += Buffer.from(part.body.data, "base64").toString("utf-8"); + } + } + } + const snippet = body || fullMessage.data.snippet || ""; allEmails.push({ from: bankEmail, snippet, @@ -372,6 +380,7 @@ app.post('/api/sync-gmail', async (req, res) => { for (const email of emails) { let parsed; try { + // console.log('📧 Processing email snippet:', email.snippet); parsed = parseBankMessage(email.snippet); } catch (parseError) { console.error('❌ Error parsing email snippet:', parseError, 'Snippet:', email.snippet); @@ -383,16 +392,9 @@ app.post('/api/sync-gmail', async (req, res) => { continue; } - // Extract unique transaction reference number from snippet - let referenceNumber = null; - const refMatch = email.snippet.match(/(?:reference number is|Ref\s*[:No]*\s*)([A-Za-z0-9]+)/i); - if (refMatch) { - referenceNumber = refMatch[1]; - } - let category = 'Other'; try { - const predictRes = await axios.post('http://10.231.8.114:5000/api/predict', { + const predictRes = await axios.post('http://192.168.1.101:5000/api/predict', { description: parsed.vendor || "Unknown" }, { timeout: 5000 }); // Optional timeout @@ -415,10 +417,9 @@ app.post('/api/sync-gmail', async (req, res) => { const dateValue = parseDateString(parsed.date) || new Date(); try { - if (referenceNumber) { - // Upsert transaction based on unique referenceNumber for emails, mark source as 'email' + if (parsed.referenceNumber) { const updatedTxn = await Transaction.findOneAndUpdate( - { referenceNumber }, // search criteria for deduplication + { referenceNumber: parsed.referenceNumber }, { userId, description: parsed.vendor || "Unknown Vendor", @@ -427,44 +428,42 @@ app.post('/api/sync-gmail', async (req, res) => { date: dateValue, vendor: parsed.vendor, category, - source: "email", // mark source explicitly + source: "email", bank: email.from, - referenceNumber, + referenceNumber: parsed.referenceNumber, }, { upsert: true, new: true, setDefaultsOnInsert: true } ); savedTxns.push(updatedTxn); - } else { - // No reference number available, just save normally; source still 'email' - const newTxn = new Transaction({ - userId, - description: parsed.vendor || "Unknown Vendor", - amount: parsed.amount, - type: parsed.type || "unknown", - date: dateValue, - vendor: parsed.vendor, - category, - source: "email", // mark source explicitly - bank: email.from, - }); - await newTxn.save(); - savedTxns.push(newTxn); - } - } catch (saveError) { - console.error('❌ Error saving transaction to database:', saveError); + } else { + const newTxn = new Transaction({ + userId, + description: parsed.vendor || "Unknown Vendor", + amount: parsed.amount, + type: parsed.type || "unknown", + date: dateValue, + vendor: parsed.vendor, + category, + source: "email", // mark source explicitly + bank: email.from, + }); + await newTxn.save(); + savedTxns.push(newTxn); } - + } catch (saveError) { + console.error('❌ Error saving transaction to database:', saveError); } - return res.json({ - success: true, - count: savedTxns.length, - transactions: savedTxns, - }); - } catch (error) { - console.error('❌ Unexpected error in /api/sync-gmail:', error); - return res.status(500).json({ error: 'Failed to fetch and save Gmail messages' }); } + return res.json({ + success: true, + count: savedTxns.length, + transactions: savedTxns, + }); +} catch (error) { + console.error('❌ Unexpected error in /api/sync-gmail:', error); + return res.status(500).json({ error: 'Failed to fetch and save Gmail messages' }); +} }); /* ---------- SERVER ---------- */ diff --git a/Backend/testing.js b/Backend/testing.js index 1430814..59c5c2b 100644 --- a/Backend/testing.js +++ b/Backend/testing.js @@ -1,19 +1,5 @@ const testEmails = [ - `Dear Customer, -Rs.60.00 has been debited from account 5488 to VPA pavithramart.63434836@hdfcbank PAVITHRA MART on 24-07-25. -Your UPI transaction reference number is 108642900372. -If you did not authorize this transaction, please report it immediately by calling 18002586161 Or SMS BLOCK UPI to 7308080808. -Warm Regards, -HDFC Bank`, - - `Rs.75.00 has been debited to VPA pavithramart.63434836@hdfcbank PAVITHRA MART on 01-08-25. -Your UPI transaction reference number is 109128631655.`, - - `Rs.299.00 credited to account 1234 from AmazonMarketplace on 19-08-25. -UPI reference number is 123456789012.`, - - `INR 150.00 debited at STARBUCKS on 20-08-25. -Ref No: 9876543210`, + `Dear Customer, Rs.20.00 has been debited from account 5488 to VPA paytm.s1eeznf@pty SHS CANTEEN 1 on 18-08-25. Your UPI transaction reference number is 109983023268. If you did not authorize this ` ]; function parseBankMessage(snippet) { @@ -35,32 +21,37 @@ function parseBankMessage(snippet) { // Date const dateMatch = snippet.match(/on\s+(\d{2}[-/]\d{2}[-/]\d{2,4})/i); - if (dateMatch) result.date = dateMatch[1]; + if (dateMatch) result.date = dateMatch; - // Reference Number (flexible regex) - const refMatch = snippet.match(/(?:reference number is|UPI reference number is|Ref No:?)\s*([A-Za-z0-9]+)/i); - if (refMatch) result.referenceNumber = refMatch[1].trim(); + // Reference number + const refMatch = snippet.match(/reference number is\s*([\d]+)/i); + if (refMatch) result.referenceNumber = refMatch.trim(); - // Vendor extraction - remove emails, pick last uppercase phrase - let vendorCandidate = snippet.replace(/[a-z0-9._%+-]+@[a-z0-9.-]+/gi, '').trim(); + // Improved vendor extraction: + // Try to extract vendor from phrases like "to " or "at " - const vendorWords = vendorCandidate.match(/(?:\b[A-Z]{2,}\b(?:\s)?)+/g); - if (vendorWords && vendorWords.length > 0) { - result.vendor = vendorWords[vendorWords.length - 1].trim(); + let vendor = null; + + const toMatch = snippet.match(/to\s+([A-Za-z0-9\s.&'-]+)/i); + if (toMatch) { + vendor = toMatch.replace(/[a-z0-9._%+-]+@[a-z0-9.-]+/gi, '').trim(); } else { - // Fallback: extract after "to " or "at " - const toMatch = snippet.match(/to\s+([A-Za-z0-9\s&.-]+)/i); - if (toMatch) { - let v = toMatch[1].trim(); - v = v.replace(/[a-z0-9._%+-]+@[a-z0-9.-]+/gi, '').trim(); - v = v.replace(/\s{2,}/g, ' '); - result.vendor = v; + const atMatch = snippet.match(/at\s+([A-Za-z0-9\s.&'-]+)/i); + if (atMatch) { + vendor = atMatch.replace(/[a-z0-9._%+-]+@[a-z0-9.-]+/gi, '').trim(); } } + // Clean extra spaces or trailing punctuation + if (vendor) { + vendor = vendor.replace(/\s{2,}/g, ' ').replace(/[.,]$/, '').trim(); + } + result.vendor = vendor || "Unknown"; + return result; } + console.log("=== Bank Email Regex Test ==="); testEmails.forEach((email, idx) => { console.log(`\n-- Email ${idx + 1} --`); diff --git a/Frontend/lib/other_pages/enviroment.dart b/Frontend/lib/other_pages/enviroment.dart index 3c4bde3..3cca84a 100644 --- a/Frontend/lib/other_pages/enviroment.dart +++ b/Frontend/lib/other_pages/enviroment.dart @@ -1,5 +1,5 @@ class Environment { - static const String baseUrl = 'http://10.231.8.114:3000'; + static const String baseUrl = 'http://192.168.1.101:3000'; static const String serverClientId = "278433619849-cg6f7gk5cc45rgu1ojrlkf794lm4udgn.apps.googleusercontent.com"; }