LogicGoInfotechSpaces commited on
Commit
f80e0ed
·
verified ·
1 Parent(s): 79a8fbf

Create index.js

Browse files
Files changed (1) hide show
  1. index.js +198 -0
index.js ADDED
@@ -0,0 +1,198 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ require('dotenv').config();
2
+ const express = require('express');
3
+ const mongoose = require('mongoose');
4
+ const OpenAI = require('openai');
5
+
6
+ const { MONGO_URI, OPENAI_API_KEY, PORT = 3000 } = process.env;
7
+
8
+ if (!MONGO_URI) {
9
+ throw new Error('MONGO_URI missing in environment variables');
10
+ }
11
+
12
+ if (!OPENAI_API_KEY) {
13
+ throw new Error('OPENAI_API_KEY missing in environment variables');
14
+ }
15
+
16
+ const openai = new OpenAI({ apiKey: OPENAI_API_KEY });
17
+ const app = express();
18
+ app.use(express.json());
19
+
20
+ mongoose
21
+ .connect(MONGO_URI)
22
+ .then(() => {
23
+ console.log('MongoDB connected');
24
+ })
25
+ .catch((error) => {
26
+ console.error('Mongo connection error', error);
27
+ process.exit(1);
28
+ });
29
+
30
+ const BudgetSchema = new mongoose.Schema(
31
+ {
32
+ name: String,
33
+ status: String,
34
+ createdBy: { type: mongoose.Schema.Types.ObjectId, required: true },
35
+ period: String,
36
+ maxAmount: Number,
37
+ spendAmount: Number,
38
+ remainingAmount: Number,
39
+ notifications: [String],
40
+ rollover: mongoose.Schema.Types.Mixed,
41
+ headCategories: mongoose.Schema.Types.Mixed,
42
+ },
43
+ {
44
+ collection: 'budgets',
45
+ strict: false,
46
+ }
47
+ );
48
+
49
+ const Budget = mongoose.model('Budget', BudgetSchema);
50
+
51
+ const TransactionSchema = new mongoose.Schema(
52
+ {
53
+ user: { type: mongoose.Schema.Types.ObjectId, required: true },
54
+ type: String,
55
+ amount: Number,
56
+ date: Date,
57
+ },
58
+ {
59
+ collection: 'transactions',
60
+ strict: false,
61
+ }
62
+ );
63
+
64
+ const Transaction = mongoose.model('Transaction', TransactionSchema);
65
+
66
+ const scorePrompt = (budgets, transactions) => {
67
+ const normalizedBudgets = budgets.map((budget) => ({
68
+ name: budget.name,
69
+ status: budget.status,
70
+ period: budget.period,
71
+ maxAmount: budget.maxAmount,
72
+ spendAmount: budget.spendAmount,
73
+ remainingAmount: budget.remainingAmount,
74
+ rollover: budget.rollover,
75
+ notifications: budget.notifications,
76
+ headCategories: Array.isArray(budget.headCategories)
77
+ ? budget.headCategories.map((head) => ({
78
+ spendLimitType: head.spendLimitType,
79
+ spendAmount: head.spendAmount,
80
+ maxAmount: head.maxAmount,
81
+ remainingAmount: head.remainingAmount,
82
+ notifications: head.notifications,
83
+ }))
84
+ : [],
85
+ }));
86
+
87
+ const trimmedTransactions = transactions.map((txn) => ({
88
+ type: txn.type,
89
+ amount: txn.amount,
90
+ date: txn.date ? new Date(txn.date).toISOString().split('T')[0] : null,
91
+ }));
92
+
93
+ return `
94
+ You are a financial wellness expert. Using any available budgets and recent transactions below, rate the user's financial health on a scale of 0-100 (0 = critical, 100 = excellent). Budgets or transactions may be empty; rely on whichever data is present.
95
+
96
+ Budgets:
97
+ ${JSON.stringify(normalizedBudgets, null, 2)}
98
+
99
+ Transactions (last 30 days):
100
+ ${JSON.stringify(trimmedTransactions, null, 2)}
101
+
102
+ Consider utilization versus limits, warning notifications, rollover behavior, and transaction-level cash flow trends. Respond ONLY with valid JSON in the form {"score": number, "explanation": "text"} with score rounded to the nearest integer.
103
+ `;
104
+ };
105
+
106
+ app.get('/', (_, res) => {
107
+ res.json({ status: 'Financial Health Score service is running' });
108
+ });
109
+
110
+ app.post('/financial-score', async (req, res) => {
111
+ const { userId } = req.body;
112
+
113
+ if (!userId) {
114
+ return res.status(400).json({ error: 'userId is required in the request body' });
115
+ }
116
+
117
+ let createdBy;
118
+ try {
119
+ createdBy = new mongoose.Types.ObjectId(userId);
120
+ } catch (error) {
121
+ return res.status(400).json({ error: 'userId must be a valid Mongo ObjectId' });
122
+ }
123
+
124
+ try {
125
+ const budgets = await Budget.find({ createdBy }).lean();
126
+
127
+ const thirtyDaysAgo = new Date();
128
+ thirtyDaysAgo.setDate(thirtyDaysAgo.getDate() - 30);
129
+
130
+ const transactions = await Transaction.find({
131
+ user: createdBy,
132
+ date: { $gte: thirtyDaysAgo },
133
+ })
134
+ .sort({ date: -1 })
135
+ .limit(100)
136
+ .lean();
137
+
138
+ if (!budgets.length && !transactions.length) {
139
+ return res.json({
140
+ userId,
141
+ score: 0,
142
+ explanation: "You haven't used the app yet.\nNo budgets or transactions were found to score.",
143
+ });
144
+ }
145
+
146
+ const response = await openai.responses.create({
147
+ model: 'gpt-4o-mini',
148
+ input: scorePrompt(budgets, transactions),
149
+ temperature: 0.6,
150
+ });
151
+
152
+ const modelOutput =
153
+ response.output?.[0]?.content?.[0]?.text || response.output?.[0]?.content || '';
154
+
155
+ const extractJson = (text) => {
156
+ const trimmed = (text || '').trim();
157
+ const fenceMatch = trimmed.match(/```(?:json)?\s*([\s\S]*?)```/i);
158
+ const candidate = fenceMatch ? fenceMatch[1].trim() : trimmed;
159
+ return JSON.parse(candidate);
160
+ };
161
+
162
+ let parsed;
163
+ try {
164
+ parsed = extractJson(modelOutput);
165
+ } catch (error) {
166
+ return res.status(502).json({
167
+ error: 'Unable to parse OpenAI response',
168
+ rawResponse: modelOutput,
169
+ });
170
+ }
171
+
172
+ if (typeof parsed.score !== 'number' || !parsed.explanation) {
173
+ return res.status(502).json({
174
+ error: 'OpenAI response missing score or explanation',
175
+ rawResponse: modelOutput,
176
+ });
177
+ }
178
+
179
+ const explanationSentences = parsed.explanation
180
+ .replace(/\s+/g, ' ')
181
+ .split(/(?<=\.)\s+/)
182
+ .filter(Boolean)
183
+ .slice(0, 2);
184
+
185
+ return res.json({
186
+ userId,
187
+ score: Math.max(0, Math.min(100, Math.round(parsed.score))),
188
+ explanation: explanationSentences.join('\\n') || parsed.explanation,
189
+ });
190
+ } catch (error) {
191
+ console.error('Error generating financial score', error);
192
+ return res.status(500).json({ error: 'Failed to generate financial score' });
193
+ }
194
+ });
195
+
196
+ app.listen(PORT, () => {
197
+ console.log(`Server is running on port ${PORT}`);
198
+ });