From dfca718f1fd5cb15a54856e9c52ffe356691838b Mon Sep 17 00:00:00 2001 From: Ayaanshaikh12243 Date: Sat, 7 Mar 2026 22:28:20 +0530 Subject: [PATCH 1/3] ISSUE-920 --- .../billPaymentScheduler/billService.js | 95 +++++++++++++++++++ .../billPaymentScheduler/controllers.js | 43 +++++++++ .../services/billPaymentScheduler/models.js | 59 ++++++++++++ .../billPaymentScheduler/paymentService.js | 40 ++++++++ .../billPaymentScheduler/reminderService.js | 23 +++++ .../services/billPaymentScheduler/routes.js | 17 ++++ .../billPaymentScheduler/schedulerService.js | 13 +++ .../services/billPaymentScheduler/tests.js | 36 +++++++ .../services/billPaymentScheduler/utils.js | 38 ++++++++ 9 files changed, 364 insertions(+) create mode 100644 backend/services/billPaymentScheduler/billService.js create mode 100644 backend/services/billPaymentScheduler/controllers.js create mode 100644 backend/services/billPaymentScheduler/models.js create mode 100644 backend/services/billPaymentScheduler/paymentService.js create mode 100644 backend/services/billPaymentScheduler/reminderService.js create mode 100644 backend/services/billPaymentScheduler/routes.js create mode 100644 backend/services/billPaymentScheduler/schedulerService.js create mode 100644 backend/services/billPaymentScheduler/tests.js create mode 100644 backend/services/billPaymentScheduler/utils.js diff --git a/backend/services/billPaymentScheduler/billService.js b/backend/services/billPaymentScheduler/billService.js new file mode 100644 index 0000000..22b4ce3 --- /dev/null +++ b/backend/services/billPaymentScheduler/billService.js @@ -0,0 +1,95 @@ +// BillService: CRUD, scheduling logic + +const { Bill } = require('./models'); + +// In-memory bill storage +const bills = []; + +class BillService { + static createBill(billData) { + const bill = new Bill( + bills.length + 1, + billData.userId, + billData.name, + billData.amount, + billData.dueDate, + billData.recurring, + billData.frequency, + 'pending', + billData.paymentMethodId + ); + bills.push(bill); + return bill; + } + + static getBillsByUser(userId) { + return bills.filter(b => b.userId === userId); + } + + static getBillById(billId) { + return bills.find(b => b.id === billId); + } + + static updateBill(billId, updateData) { + const bill = this.getBillById(billId); + if (!bill) return null; + Object.assign(bill, updateData); + return bill; + } + + static deleteBill(billId) { + const idx = bills.findIndex(b => b.id === billId); + if (idx !== -1) { + bills.splice(idx, 1); + return true; + } + return false; + } + + static getUpcomingBills(userId, daysAhead = 7) { + const now = new Date(); + const future = new Date(now.getTime() + daysAhead * 24 * 60 * 60 * 1000); + return bills.filter(b => b.userId === userId && new Date(b.dueDate) <= future && b.status === 'pending'); + } + + static markBillPaid(billId) { + const bill = this.getBillById(billId); + if (bill) { + bill.status = 'paid'; + return bill; + } + return null; + } + + static scheduleRecurringBills() { + bills.forEach(bill => { + if (bill.recurring && bill.status === 'paid') { + // Schedule next bill + let nextDue; + const currentDue = new Date(bill.dueDate); + switch (bill.frequency) { + case 'monthly': + nextDue = new Date(currentDue); + nextDue.setMonth(nextDue.getMonth() + 1); + break; + case 'weekly': + nextDue = new Date(currentDue); + nextDue.setDate(nextDue.getDate() + 7); + break; + case 'yearly': + nextDue = new Date(currentDue); + nextDue.setFullYear(nextDue.getFullYear() + 1); + break; + default: + nextDue = null; + } + if (nextDue) { + bill.dueDate = nextDue.toISOString(); + bill.status = 'pending'; + } + } + }); + } +} + +module.exports = BillService; diff --git a/backend/services/billPaymentScheduler/controllers.js b/backend/services/billPaymentScheduler/controllers.js new file mode 100644 index 0000000..2ce31e0 --- /dev/null +++ b/backend/services/billPaymentScheduler/controllers.js @@ -0,0 +1,43 @@ +// Controllers: API endpoints for bills, payments, schedules + +const BillService = require('./billService'); +const PaymentService = require('./paymentService'); +const SchedulerService = require('./schedulerService'); + +const controllers = { + createBill: (req, res) => { + const bill = BillService.createBill(req.body); + res.status(201).json(bill); + }, + getBills: (req, res) => { + const bills = BillService.getBillsByUser(req.params.userId); + res.json(bills); + }, + updateBill: (req, res) => { + const bill = BillService.updateBill(parseInt(req.params.billId), req.body); + if (bill) res.json(bill); + else res.status(404).json({ error: 'Bill not found' }); + }, + deleteBill: (req, res) => { + const success = BillService.deleteBill(parseInt(req.params.billId)); + if (success) res.json({ success: true }); + else res.status(404).json({ error: 'Bill not found' }); + }, + processPayment: (req, res) => { + const payment = PaymentService.processPayment(req.body); + if (payment.status === 'success') { + BillService.markBillPaid(payment.billId); + } + res.status(201).json(payment); + }, + getPayments: (req, res) => { + const payments = PaymentService.getPaymentsByUser(req.params.userId); + res.json(payments); + }, + runScheduler: (req, res) => { + SchedulerService.runScheduler(); + res.json({ success: true }); + } +}; + +module.exports = controllers; diff --git a/backend/services/billPaymentScheduler/models.js b/backend/services/billPaymentScheduler/models.js new file mode 100644 index 0000000..0ccd102 --- /dev/null +++ b/backend/services/billPaymentScheduler/models.js @@ -0,0 +1,59 @@ +// Bill, User, Payment, Schedule models +// ...existing code... +// User Model +class User { + constructor(id, name, email, phone, paymentMethods = []) { + this.id = id; + this.name = name; + this.email = email; + this.phone = phone; + this.paymentMethods = paymentMethods; + } +} + +// Bill Model +class Bill { + constructor(id, userId, name, amount, dueDate, recurring, frequency, status = 'pending', paymentMethodId = null) { + this.id = id; + this.userId = userId; + this.name = name; + this.amount = amount; + this.dueDate = dueDate; + this.recurring = recurring; + this.frequency = frequency; // e.g. 'monthly', 'weekly', 'yearly' + this.status = status; + this.paymentMethodId = paymentMethodId; + } +} + +// Payment Model +class Payment { + constructor(id, billId, userId, amount, date, status, gatewayResponse = null) { + this.id = id; + this.billId = billId; + this.userId = userId; + this.amount = amount; + this.date = date; + this.status = status; // 'success', 'failed', 'pending' + this.gatewayResponse = gatewayResponse; + } +} + +// Schedule Model +class Schedule { + constructor(id, billId, userId, nextRun, frequency, enabled = true) { + this.id = id; + this.billId = billId; + this.userId = userId; + this.nextRun = nextRun; + this.frequency = frequency; + this.enabled = enabled; + } +} + +module.exports = { + User, + Bill, + Payment, + Schedule +}; diff --git a/backend/services/billPaymentScheduler/paymentService.js b/backend/services/billPaymentScheduler/paymentService.js new file mode 100644 index 0000000..c0fe9ce --- /dev/null +++ b/backend/services/billPaymentScheduler/paymentService.js @@ -0,0 +1,40 @@ +// PaymentService: payment gateway integration + +const { Payment } = require('./models'); +const payments = []; + +class PaymentService { + static processPayment(paymentData) { + // Simulate payment gateway integration + const gatewayResponse = { + transactionId: 'TXN' + Math.floor(Math.random() * 1000000), + status: 'success', + timestamp: new Date().toISOString() + }; + const payment = new Payment( + payments.length + 1, + paymentData.billId, + paymentData.userId, + paymentData.amount, + new Date().toISOString(), + gatewayResponse.status, + gatewayResponse + ); + payments.push(payment); + return payment; + } + + static getPaymentsByUser(userId) { + return payments.filter(p => p.userId === userId); + } + + static getPaymentsByBill(billId) { + return payments.filter(p => p.billId === billId); + } + + static getPaymentById(paymentId) { + return payments.find(p => p.id === paymentId); + } +} + +module.exports = PaymentService; diff --git a/backend/services/billPaymentScheduler/reminderService.js b/backend/services/billPaymentScheduler/reminderService.js new file mode 100644 index 0000000..fc0621e --- /dev/null +++ b/backend/services/billPaymentScheduler/reminderService.js @@ -0,0 +1,23 @@ +// ReminderService: notifications for upcoming bills + +const BillService = require('./billService'); +const { User } = require('./models'); + +class ReminderService { + static sendReminder(user, bill) { + // Simulate sending email/SMS + console.log(`Reminder sent to ${user.email} for bill '${bill.name}' due on ${bill.dueDate}`); + return true; + } + + static sendUpcomingReminders(users, daysAhead = 7) { + users.forEach(user => { + const upcomingBills = BillService.getUpcomingBills(user.id, daysAhead); + upcomingBills.forEach(bill => { + this.sendReminder(user, bill); + }); + }); + } +} + +module.exports = ReminderService; diff --git a/backend/services/billPaymentScheduler/routes.js b/backend/services/billPaymentScheduler/routes.js new file mode 100644 index 0000000..1b6ca0f --- /dev/null +++ b/backend/services/billPaymentScheduler/routes.js @@ -0,0 +1,17 @@ +// Express routes for API + +const express = require('express'); +const controllers = require('./controllers'); +const router = express.Router(); + +router.post('/bill', controllers.createBill); +router.get('/bills/:userId', controllers.getBills); +router.put('/bill/:billId', controllers.updateBill); +router.delete('/bill/:billId', controllers.deleteBill); + +router.post('/payment', controllers.processPayment); +router.get('/payments/:userId', controllers.getPayments); + +router.post('/scheduler/run', controllers.runScheduler); + +module.exports = router; diff --git a/backend/services/billPaymentScheduler/schedulerService.js b/backend/services/billPaymentScheduler/schedulerService.js new file mode 100644 index 0000000..3f6c770 --- /dev/null +++ b/backend/services/billPaymentScheduler/schedulerService.js @@ -0,0 +1,13 @@ +// SchedulerService: manages recurring schedules + +const BillService = require('./billService'); + +class SchedulerService { + static runScheduler() { + // Run recurring bill scheduling + BillService.scheduleRecurringBills(); + console.log('Recurring bills scheduled.'); + } +} + +module.exports = SchedulerService; diff --git a/backend/services/billPaymentScheduler/tests.js b/backend/services/billPaymentScheduler/tests.js new file mode 100644 index 0000000..1600472 --- /dev/null +++ b/backend/services/billPaymentScheduler/tests.js @@ -0,0 +1,36 @@ +// Unit/integration tests + +const BillService = require('./billService'); +const PaymentService = require('./paymentService'); +const ReminderService = require('./reminderService'); +const SchedulerService = require('./schedulerService'); + +function runTests() { + // Create user and bills + const user = { id: 1, name: 'Alice', email: 'alice@example.com', phone: '1234567890' }; + const bill1 = BillService.createBill({ userId: user.id, name: 'Electricity', amount: 100, dueDate: new Date(Date.now() + 2 * 24 * 60 * 60 * 1000).toISOString(), recurring: true, frequency: 'monthly', paymentMethodId: 1 }); + const bill2 = BillService.createBill({ userId: user.id, name: 'Internet', amount: 50, dueDate: new Date(Date.now() + 5 * 24 * 60 * 60 * 1000).toISOString(), recurring: true, frequency: 'monthly', paymentMethodId: 1 }); + + // Get bills + const bills = BillService.getBillsByUser(user.id); + console.log('Bills:', bills); + + // Send reminders + ReminderService.sendUpcomingReminders([user], 7); + + // Process payment + const payment = PaymentService.processPayment({ billId: bill1.id, userId: user.id, amount: bill1.amount }); + console.log('Payment:', payment); + + // Mark bill paid + BillService.markBillPaid(bill1.id); + + // Run scheduler + SchedulerService.runScheduler(); + + // Check recurring bill + const updatedBill = BillService.getBillById(bill1.id); + console.log('Updated Bill:', updatedBill); +} + +runTests(); diff --git a/backend/services/billPaymentScheduler/utils.js b/backend/services/billPaymentScheduler/utils.js new file mode 100644 index 0000000..a0fc8da --- /dev/null +++ b/backend/services/billPaymentScheduler/utils.js @@ -0,0 +1,38 @@ +// Utils: Notification, Logger, PaymentGateway + +const Notification = { + sendEmail: (to, subject, message) => { + console.log(`Email sent to ${to}: ${subject} - ${message}`); + return true; + }, + sendSMS: (to, message) => { + console.log(`SMS sent to ${to}: ${message}`); + return true; + } +}; + +const Logger = { + log: (msg) => { + console.log(`[LOG] ${new Date().toISOString()} - ${msg}`); + }, + error: (msg) => { + console.error(`[ERROR] ${new Date().toISOString()} - ${msg}`); + } +}; + +const PaymentGateway = { + process: (paymentInfo) => { + // Simulate payment gateway + return { + transactionId: 'TXN' + Math.floor(Math.random() * 1000000), + status: 'success', + timestamp: new Date().toISOString() + }; + } +}; + +module.exports = { + Notification, + Logger, + PaymentGateway +}; From 94b0a1b710f5fee1d27d68fc26e3b7ba6a727729 Mon Sep 17 00:00:00 2001 From: Ayaanshaikh12243 Date: Sat, 7 Mar 2026 22:32:37 +0530 Subject: [PATCH 2/3] ISSUE-921 --- .../portfolioRebalancer/controllers.js | 49 +++++++++++++++++ .../services/portfolioRebalancer/models.js | 51 +++++++++++++++++ .../portfolioRebalancer/portfolioService.js | 55 +++++++++++++++++++ .../portfolioRebalancer/rebalancerService.js | 42 ++++++++++++++ .../services/portfolioRebalancer/routes.js | 19 +++++++ backend/services/portfolioRebalancer/tests.js | 38 +++++++++++++ .../portfolioRebalancer/tradeService.js | 39 +++++++++++++ backend/services/portfolioRebalancer/utils.js | 39 +++++++++++++ 8 files changed, 332 insertions(+) create mode 100644 backend/services/portfolioRebalancer/controllers.js create mode 100644 backend/services/portfolioRebalancer/models.js create mode 100644 backend/services/portfolioRebalancer/portfolioService.js create mode 100644 backend/services/portfolioRebalancer/rebalancerService.js create mode 100644 backend/services/portfolioRebalancer/routes.js create mode 100644 backend/services/portfolioRebalancer/tests.js create mode 100644 backend/services/portfolioRebalancer/tradeService.js create mode 100644 backend/services/portfolioRebalancer/utils.js diff --git a/backend/services/portfolioRebalancer/controllers.js b/backend/services/portfolioRebalancer/controllers.js new file mode 100644 index 0000000..f6ba60c --- /dev/null +++ b/backend/services/portfolioRebalancer/controllers.js @@ -0,0 +1,49 @@ +// Controllers: API endpoints for portfolios, trades, rebalancing + +const PortfolioService = require('./portfolioService'); +const TradeService = require('./tradeService'); +const RebalancerService = require('./rebalancerService'); + +const controllers = { + createPortfolio: (req, res) => { + const portfolio = PortfolioService.createPortfolio(req.body); + res.status(201).json(portfolio); + }, + getPortfolio: (req, res) => { + const portfolio = PortfolioService.getPortfolioByUser(req.params.userId); + if (portfolio) res.json(portfolio); + else res.status(404).json({ error: 'Portfolio not found' }); + }, + updatePortfolio: (req, res) => { + const portfolio = PortfolioService.updatePortfolio(parseInt(req.params.portfolioId), req.body); + if (portfolio) res.json(portfolio); + else res.status(404).json({ error: 'Portfolio not found' }); + }, + getAssetAllocation: (req, res) => { + const allocation = PortfolioService.getAssetAllocation(parseInt(req.params.portfolioId)); + res.json(allocation); + }, + createTrade: (req, res) => { + const trade = TradeService.createTrade(req.body); + res.status(201).json(trade); + }, + executeTrade: (req, res) => { + const trade = TradeService.executeTrade(parseInt(req.params.tradeId)); + if (trade) res.json(trade); + else res.status(404).json({ error: 'Trade not found' }); + }, + getTrades: (req, res) => { + const trades = TradeService.getTradesByPortfolio(parseInt(req.params.portfolioId)); + res.json(trades); + }, + suggestRebalancing: (req, res) => { + const suggestions = RebalancerService.suggestRebalancing(parseInt(req.params.portfolioId)); + res.json(suggestions); + }, + automateRebalancing: (req, res) => { + const trades = RebalancerService.automateRebalancing(parseInt(req.params.portfolioId)); + res.json(trades); + } +}; + +module.exports = controllers; diff --git a/backend/services/portfolioRebalancer/models.js b/backend/services/portfolioRebalancer/models.js new file mode 100644 index 0000000..ea1c7ac --- /dev/null +++ b/backend/services/portfolioRebalancer/models.js @@ -0,0 +1,51 @@ +// Portfolio, Asset, Trade, User models + +// User Model +class User { + constructor(id, name, email, preferences = {}) { + this.id = id; + this.name = name; + this.email = email; + this.preferences = preferences; + } +} + +// Asset Model +class Asset { + constructor(symbol, name, allocation, currentValue) { + this.symbol = symbol; + this.name = name; + this.allocation = allocation; // Target allocation percentage + this.currentValue = currentValue; + } +} + +// Portfolio Model +class Portfolio { + constructor(id, userId, assets = [], lastRebalanced = null) { + this.id = id; + this.userId = userId; + this.assets = assets; + this.lastRebalanced = lastRebalanced; + } +} + +// Trade Model +class Trade { + constructor(id, portfolioId, assetSymbol, action, amount, date, status = 'pending') { + this.id = id; + this.portfolioId = portfolioId; + this.assetSymbol = assetSymbol; + this.action = action; // 'buy' or 'sell' + this.amount = amount; + this.date = date; + this.status = status; + } +} + +module.exports = { + User, + Asset, + Portfolio, + Trade +}; diff --git a/backend/services/portfolioRebalancer/portfolioService.js b/backend/services/portfolioRebalancer/portfolioService.js new file mode 100644 index 0000000..2351a8e --- /dev/null +++ b/backend/services/portfolioRebalancer/portfolioService.js @@ -0,0 +1,55 @@ +// PortfolioService: allocation monitoring, rebalancing logic + +const { Portfolio, Asset } = require('./models'); +const portfolios = []; + +class PortfolioService { + static createPortfolio(portfolioData) { + const portfolio = new Portfolio( + portfolios.length + 1, + portfolioData.userId, + portfolioData.assets.map(a => new Asset(a.symbol, a.name, a.allocation, a.currentValue)), + new Date().toISOString() + ); + portfolios.push(portfolio); + return portfolio; + } + + static getPortfolioByUser(userId) { + return portfolios.find(p => p.userId === userId); + } + + static updatePortfolio(portfolioId, updateData) { + const portfolio = portfolios.find(p => p.id === portfolioId); + if (!portfolio) return null; + Object.assign(portfolio, updateData); + return portfolio; + } + + static getAssetAllocation(portfolioId) { + const portfolio = portfolios.find(p => p.id === portfolioId); + if (!portfolio) return null; + const totalValue = portfolio.assets.reduce((sum, asset) => sum + asset.currentValue, 0); + return portfolio.assets.map(asset => ({ + symbol: asset.symbol, + name: asset.name, + allocation: asset.allocation, + currentValue: asset.currentValue, + actualAllocation: totalValue ? (asset.currentValue / totalValue) * 100 : 0 + })); + } + + static rebalancePortfolio(portfolioId, targetAllocations) { + const portfolio = portfolios.find(p => p.id === portfolioId); + if (!portfolio) return null; + portfolio.assets.forEach(asset => { + if (targetAllocations[asset.symbol] !== undefined) { + asset.allocation = targetAllocations[asset.symbol]; + } + }); + portfolio.lastRebalanced = new Date().toISOString(); + return portfolio; + } +} + +module.exports = PortfolioService; diff --git a/backend/services/portfolioRebalancer/rebalancerService.js b/backend/services/portfolioRebalancer/rebalancerService.js new file mode 100644 index 0000000..ecd36a9 --- /dev/null +++ b/backend/services/portfolioRebalancer/rebalancerService.js @@ -0,0 +1,42 @@ +// RebalancerService: suggest optimal actions, automate rebalancing + +const PortfolioService = require('./portfolioService'); +const TradeService = require('./tradeService'); + +class RebalancerService { + static suggestRebalancing(portfolioId) { + const allocations = PortfolioService.getAssetAllocation(portfolioId); + if (!allocations) return []; + const suggestions = []; + allocations.forEach(asset => { + const diff = asset.actualAllocation - asset.allocation; + if (Math.abs(diff) > 2) { // threshold 2% + suggestions.push({ + assetSymbol: asset.symbol, + action: diff > 0 ? 'sell' : 'buy', + amount: Math.abs(diff) + }); + } + }); + return suggestions; + } + + static automateRebalancing(portfolioId) { + const suggestions = this.suggestRebalancing(portfolioId); + const trades = []; + suggestions.forEach(suggestion => { + const trade = TradeService.createTrade({ + portfolioId, + assetSymbol: suggestion.assetSymbol, + action: suggestion.action, + amount: suggestion.amount + }); + TradeService.executeTrade(trade.id); + trades.push(trade); + }); + PortfolioService.rebalancePortfolio(portfolioId, {}); // Update lastRebalanced + return trades; + } +} + +module.exports = RebalancerService; diff --git a/backend/services/portfolioRebalancer/routes.js b/backend/services/portfolioRebalancer/routes.js new file mode 100644 index 0000000..7e34375 --- /dev/null +++ b/backend/services/portfolioRebalancer/routes.js @@ -0,0 +1,19 @@ +// Express routes for API + +const express = require('express'); +const controllers = require('./controllers'); +const router = express.Router(); + +router.post('/portfolio', controllers.createPortfolio); +router.get('/portfolio/:userId', controllers.getPortfolio); +router.put('/portfolio/:portfolioId', controllers.updatePortfolio); +router.get('/portfolio/:portfolioId/allocation', controllers.getAssetAllocation); + +router.post('/trade', controllers.createTrade); +router.put('/trade/:tradeId/execute', controllers.executeTrade); +router.get('/trades/:portfolioId', controllers.getTrades); + +router.get('/rebalancer/:portfolioId/suggest', controllers.suggestRebalancing); +router.post('/rebalancer/:portfolioId/automate', controllers.automateRebalancing); + +module.exports = router; diff --git a/backend/services/portfolioRebalancer/tests.js b/backend/services/portfolioRebalancer/tests.js new file mode 100644 index 0000000..08e83bd --- /dev/null +++ b/backend/services/portfolioRebalancer/tests.js @@ -0,0 +1,38 @@ +// Unit/integration tests + +const PortfolioService = require('./portfolioService'); +const TradeService = require('./tradeService'); +const RebalancerService = require('./rebalancerService'); + +function runTests() { + // Create user and portfolio + const user = { id: 1, name: 'Bob', email: 'bob@example.com' }; + const assets = [ + { symbol: 'AAPL', name: 'Apple', allocation: 40, currentValue: 4000 }, + { symbol: 'GOOG', name: 'Google', allocation: 30, currentValue: 3000 }, + { symbol: 'TSLA', name: 'Tesla', allocation: 30, currentValue: 3000 } + ]; + const portfolio = PortfolioService.createPortfolio({ userId: user.id, assets }); + + // Get portfolio + const fetched = PortfolioService.getPortfolioByUser(user.id); + console.log('Portfolio:', fetched); + + // Get asset allocation + const allocation = PortfolioService.getAssetAllocation(portfolio.id); + console.log('Allocation:', allocation); + + // Suggest rebalancing + const suggestions = RebalancerService.suggestRebalancing(portfolio.id); + console.log('Rebalancing Suggestions:', suggestions); + + // Automate rebalancing + const trades = RebalancerService.automateRebalancing(portfolio.id); + console.log('Automated Trades:', trades); + + // Get trades + const tradeList = TradeService.getTradesByPortfolio(portfolio.id); + console.log('Trades:', tradeList); +} + +runTests(); diff --git a/backend/services/portfolioRebalancer/tradeService.js b/backend/services/portfolioRebalancer/tradeService.js new file mode 100644 index 0000000..b67e729 --- /dev/null +++ b/backend/services/portfolioRebalancer/tradeService.js @@ -0,0 +1,39 @@ +// TradeService: automate trades, simulate execution + +const { Trade } = require('./models'); +const trades = []; + +class TradeService { + static createTrade(tradeData) { + const trade = new Trade( + trades.length + 1, + tradeData.portfolioId, + tradeData.assetSymbol, + tradeData.action, + tradeData.amount, + new Date().toISOString(), + 'pending' + ); + trades.push(trade); + return trade; + } + + static executeTrade(tradeId) { + const trade = trades.find(t => t.id === tradeId); + if (!trade) return null; + // Simulate execution + trade.status = 'executed'; + trade.date = new Date().toISOString(); + return trade; + } + + static getTradesByPortfolio(portfolioId) { + return trades.filter(t => t.portfolioId === portfolioId); + } + + static getTradeById(tradeId) { + return trades.find(t => t.id === tradeId); + } +} + +module.exports = TradeService; diff --git a/backend/services/portfolioRebalancer/utils.js b/backend/services/portfolioRebalancer/utils.js new file mode 100644 index 0000000..61b4d02 --- /dev/null +++ b/backend/services/portfolioRebalancer/utils.js @@ -0,0 +1,39 @@ +// Utils: Logger, MarketData, AllocationCalculator + +const Logger = { + log: (msg) => { + console.log(`[LOG] ${new Date().toISOString()} - ${msg}`); + }, + error: (msg) => { + console.error(`[ERROR] ${new Date().toISOString()} - ${msg}`); + } +}; + +const MarketData = { + getPrice: (symbol) => { + // Simulate market price + return 100 + Math.random() * 100; + }, + getAllPrices: (symbols) => { + return symbols.reduce((acc, sym) => { + acc[sym] = MarketData.getPrice(sym); + return acc; + }, {}); + } +}; + +const AllocationCalculator = { + calculateActualAllocation: (assets) => { + const total = assets.reduce((sum, asset) => sum + asset.currentValue, 0); + return assets.map(asset => ({ + symbol: asset.symbol, + actualAllocation: total ? (asset.currentValue / total) * 100 : 0 + })); + } +}; + +module.exports = { + Logger, + MarketData, + AllocationCalculator +}; From 7553a0295997a6ccae9ce23d61a2e3070a4b6c94 Mon Sep 17 00:00:00 2001 From: Ayaanshaikh12243 Date: Sat, 7 Mar 2026 22:40:20 +0530 Subject: [PATCH 3/3] done --- backend/routes/subscriptionRoutes.js | 41 +++++++++ .../services/SubscriptionManagementService.js | 86 +++++++++++++++++++ frontend/components/SubscriptionDashboard.jsx | 82 ++++++++++++++++++ 3 files changed, 209 insertions(+) create mode 100644 backend/routes/subscriptionRoutes.js create mode 100644 backend/services/SubscriptionManagementService.js create mode 100644 frontend/components/SubscriptionDashboard.jsx diff --git a/backend/routes/subscriptionRoutes.js b/backend/routes/subscriptionRoutes.js new file mode 100644 index 0000000..21e8662 --- /dev/null +++ b/backend/routes/subscriptionRoutes.js @@ -0,0 +1,41 @@ +// subscriptionRoutes.js +// API endpoints for Dynamic Subscription Management Dashboard + +const express = require('express'); +const router = express.Router(); +const SubscriptionManagementService = require('./SubscriptionManagementService'); + +// Middleware to get userId (replace with real auth) +router.use((req, res, next) => { + req.userId = req.headers['x-user-id'] || 'demoUser'; + next(); +}); + +// GET /subscriptions/dashboard +router.get('/dashboard', async (req, res) => { + try { + const service = new SubscriptionManagementService(req.userId); + const dashboardData = await service.getDashboardData(); + res.json({ subscriptions: dashboardData }); + } catch (err) { + res.status(500).json({ error: err.message }); + } +}); + +// POST /subscriptions/cancel +router.post('/cancel', async (req, res) => { + // Example endpoint to cancel a subscription + const { merchant, amount } = req.body; + // Implement cancellation logic here + res.json({ status: 'cancelled', merchant, amount }); +}); + +// POST /subscriptions/negotiate +router.post('/negotiate', async (req, res) => { + // Example endpoint to negotiate a subscription + const { merchant, amount } = req.body; + // Implement negotiation logic here + res.json({ status: 'negotiation_started', merchant, amount }); +}); + +module.exports = router; diff --git a/backend/services/SubscriptionManagementService.js b/backend/services/SubscriptionManagementService.js new file mode 100644 index 0000000..7c1920f --- /dev/null +++ b/backend/services/SubscriptionManagementService.js @@ -0,0 +1,86 @@ +// SubscriptionManagementService.js +// Dynamic Subscription Management Dashboard Backend +// Detects recurring charges, categorizes subscriptions, and provides actionable insights + +const db = require('../db'); // Example DB import +const moment = require('moment'); + +class SubscriptionManagementService { + constructor(userId) { + this.userId = userId; + } + + // Fetch user transactions from DB + async getUserTransactions() { + // Replace with actual DB query + return db.getTransactionsForUser(this.userId); + } + + // Detect recurring charges + async detectRecurringCharges() { + const transactions = await this.getUserTransactions(); + const recurring = {}; + // Group by merchant and amount + transactions.forEach(tx => { + const key = `${tx.merchant}_${tx.amount}`; + if (!recurring[key]) recurring[key] = []; + recurring[key].push(tx); + }); + // Filter for monthly/weekly patterns + const subscriptions = []; + Object.values(recurring).forEach(group => { + if (group.length < 3) return; // Require at least 3 occurrences + const dates = group.map(tx => moment(tx.date)); + const intervals = dates.slice(1).map((d, i) => d.diff(dates[i], 'days')); + const avgInterval = intervals.reduce((a, b) => a + b, 0) / intervals.length; + if (avgInterval > 20 && avgInterval < 35) { + subscriptions.push({ + merchant: group[0].merchant, + amount: group[0].amount, + frequency: 'monthly', + lastDate: group[group.length - 1].date, + }); + } + }); + return subscriptions; + } + + // Categorize subscriptions + async categorizeSubscriptions(subscriptions) { + // Simple categorization by merchant keywords + const categories = { + streaming: ['netflix', 'prime', 'spotify', 'hulu'], + utilities: ['electric', 'water', 'gas'], + saas: ['zoom', 'office', 'adobe'], + }; + return subscriptions.map(sub => { + let category = 'other'; + Object.entries(categories).forEach(([cat, keywords]) => { + if (keywords.some(k => sub.merchant.toLowerCase().includes(k))) { + category = cat; + } + }); + return { ...sub, category }; + }); + } + + // Actionable insights + async generateInsights(subscriptions) { + return subscriptions.map(sub => { + let action = 'review'; + if (sub.category === 'streaming' && sub.amount > 20) action = 'negotiate'; + if (sub.category === 'other') action = 'cancel'; + return { ...sub, action }; + }); + } + + // Main dashboard data + async getDashboardData() { + const recurring = await this.detectRecurringCharges(); + const categorized = await this.categorizeSubscriptions(recurring); + const insights = await this.generateInsights(categorized); + return insights; + } +} + +module.exports = SubscriptionManagementService; diff --git a/frontend/components/SubscriptionDashboard.jsx b/frontend/components/SubscriptionDashboard.jsx new file mode 100644 index 0000000..1d066d8 --- /dev/null +++ b/frontend/components/SubscriptionDashboard.jsx @@ -0,0 +1,82 @@ +// SubscriptionDashboard.jsx +// Dynamic Subscription Management Dashboard UI (React) + +import React, { useEffect, useState } from 'react'; +import axios from 'axios'; + +function SubscriptionDashboard() { + const [subscriptions, setSubscriptions] = useState([]); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + + useEffect(() => { + async function fetchData() { + try { + const res = await axios.get('/subscriptions/dashboard', { + headers: { 'x-user-id': 'demoUser' } + }); + setSubscriptions(res.data.subscriptions); + } catch (err) { + setError(err.message); + } finally { + setLoading(false); + } + } + fetchData(); + }, []); + + const handleCancel = async (merchant, amount) => { + await axios.post('/subscriptions/cancel', { merchant, amount }); + alert(`Cancelled subscription: ${merchant}`); + }; + + const handleNegotiate = async (merchant, amount) => { + await axios.post('/subscriptions/negotiate', { merchant, amount }); + alert(`Negotiation started for: ${merchant}`); + }; + + if (loading) return
Loading...
; + if (error) return
Error: {error}
; + + return ( +
+

Subscription Management Dashboard

+ + + + + + + + + + + + + {subscriptions.map((sub, idx) => ( + + + + + + + + + ))} + +
MerchantAmountCategoryFrequencyActionManage
{sub.merchant}{sub.amount}{sub.category}{sub.frequency}{sub.action} + {sub.action === 'cancel' && ( + + )} + {sub.action === 'negotiate' && ( + + )} + {sub.action === 'review' && ( + + )} +
+
+ ); +} + +export default SubscriptionDashboard;