update final design

This commit is contained in:
Mann Patel
2025-02-16 09:52:09 -07:00
parent 807dd273fd
commit 966d3a2232
6 changed files with 440 additions and 236 deletions

138
SerialToSerialBT.ino Normal file
View File

@@ -0,0 +1,138 @@
#include <LiquidCrystal.h>
#include <ArduinoJson.h>
// LCD Pin Configuration
LiquidCrystal lcd(14, 27, 26, 25, 33, 32);
// JSON parsing buffer
StaticJsonDocument<2048> jsonDoc;
// Financial tracking structures
struct FinancialSummary {
float bankBalance;
float totalIncome;
float totalExpenses;
float netAmount;
int transactionCount;
};
FinancialSummary financialData = {0, 0, 0, 0, 0};
// Display states
enum DisplayState {
BANK_BALANCE,
INCOME_EXPENSES,
TRANSACTION_COUNT
};
DisplayState currentState = BANK_BALANCE;
void setup() {
// Initialize serial communication
Serial.begin(115200);
// Initialize LCD
lcd.begin(16, 2);
lcd.clear();
// Display startup message
lcd.setCursor(0, 0);
lcd.print("Finance Tracker");
lcd.setCursor(0, 1);
lcd.print("Waiting for Data");
}
void parseFinancialData(String jsonString) {
// Reset financial data
financialData = {0, 0, 0, 0, 0};
// Parse JSON
DeserializationError error = deserializeJson(jsonDoc, jsonString);
if (error) {
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("JSON Error");
Serial.print("JSON parsing failed: ");
Serial.println(error.c_str());
return;
}
// Parse financial data
financialData.bankBalance = jsonDoc["bank_balance"].as<float>();
financialData.totalIncome = jsonDoc["total_income"].as<float>();
financialData.totalExpenses = jsonDoc["total_expenses"].as<float>();
financialData.netAmount = jsonDoc["net_amount"].as<float>();
// Parse transaction count
JsonArray transactions = jsonDoc["recent_transactions"].as<JsonArray>();
financialData.transactionCount = transactions.size();
// Display initial information
displayFinancialInfo();
}
void displayFinancialInfo() {
// Cycle through different display states
switch (currentState) {
case BANK_BALANCE:
displayBankBalance();
break;
case INCOME_EXPENSES:
displayIncomeExpenses();
break;
case TRANSACTION_COUNT:
displayTransactionCount();
break;
}
// Rotate to next state
currentState = static_cast<DisplayState>((currentState + 1) % 3);
}
void displayBankBalance() {
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Bank Balance:");
lcd.setCursor(0, 1);
lcd.print("$");
lcd.print(financialData.bankBalance, 2);
}
void displayIncomeExpenses() {
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Income: $");
lcd.print(financialData.totalIncome, 2);
lcd.setCursor(0, 1);
lcd.print("Expense: $");
lcd.print(financialData.totalExpenses, 2);
}
void displayTransactionCount() {
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Transactions:");
lcd.setCursor(0, 1);
lcd.print("Total: ");
lcd.print(financialData.transactionCount);
}
void loop() {
// Check for incoming serial data
if (Serial.available()) {
String receivedData = Serial.readStringUntil('\n');
// Attempt to parse received JSON
parseFinancialData(receivedData);
}
// Display rotation every 3 seconds
static unsigned long lastDisplayTime = 0;
unsigned long currentTime = millis();
if (currentTime - lastDisplayTime >= 3000) {
displayFinancialInfo();
lastDisplayTime = currentTime;
}
}

View File

@@ -92,19 +92,6 @@ const DashboardContent = () => {
const { accounts, transactions, loading } = usePlaid(); // Use the Plaid context const { accounts, transactions, loading } = usePlaid(); // Use the Plaid context
const [reminders, setReminders] = useState([]); const [reminders, setReminders] = useState([]);
// useEffect(() => {
// const fetchReminders = async () => {
// const { data, error } = await supabase
// .from('reminders')
// .select('*')
// .order('due_date', { ascending: true });
// if (data) setReminders(data);
// };
// fetchReminders();
// }, []);
// Process Plaid data for charts // Process Plaid data for charts
const processChartData = () => { const processChartData = () => {
if (!transactions?.length) return []; if (!transactions?.length) return [];
@@ -308,7 +295,7 @@ const DashboardLayout = ({ children }) => {
{/* Logo and Brand */} {/* Logo and Brand */}
<div className="flex items-center"> <div className="flex items-center">
<DollarSign className="h-8 w-8 text-green-600" /> <DollarSign className="h-8 w-8 text-green-600" />
<span className="ml-2 text-xl font-bold text-gray-900"></span> <span className="ml-2 text-xl font-bold text-gray-900">Fin Track</span>
</div> </div>
{/* User Info and Logout */} {/* User Info and Logout */}

View File

@@ -4,6 +4,7 @@ import { usePlaid } from '../PlaidProvider';
import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, Legend, ResponsiveContainer } from 'recharts'; import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, Legend, ResponsiveContainer } from 'recharts';
import { DollarSign, ChevronLeft } from 'lucide-react'; import { DollarSign, ChevronLeft } from 'lucide-react';
const ExpenseTracker = () => { const ExpenseTracker = () => {
const navigate = useNavigate(); const navigate = useNavigate();
const { const {

View File

@@ -1,221 +1,83 @@
// QuizPage.jsx // QuizPage.jsx
import React, { useState, useEffect } from 'react'; import React, { useState, useEffect } from 'react';
import { useNavigate } from 'react-router-dom'; import { useNavigate } from 'react-router-dom';
import { ChevronLeft, DollarSign, BookOpen, Award, Check, X } from 'lucide-react'; import { ChevronLeft, BookOpen, Award } from 'lucide-react';
import { supabase } from '../supabaseClient'; import { supabase } from '../supabaseClient';
import { initializeQuizDatabase } from '../quizInsert';
const QuizPage = () => { const QuizPage = () => {
const navigate = useNavigate(); const navigate = useNavigate();
const [quizzes, setQuizzes] = useState([]);
const [currentQuiz, setCurrentQuiz] = useState(null); const [currentQuiz, setCurrentQuiz] = useState(null);
const [currentQuestionIndex, setCurrentQuestionIndex] = useState(0); const [currentQuestionIndex, setCurrentQuestionIndex] = useState(0);
const [selectedAnswer, setSelectedAnswer] = useState(null); const [selectedAnswer, setSelectedAnswer] = useState(null);
const [score, setScore] = useState(0); const [score, setScore] = useState(0);
const [showResult, setShowResult] = useState(false); const [showResult, setShowResult] = useState(false);
const [quizStarted, setQuizStarted] = useState(false); const [quizStarted, setQuizStarted] = useState(false);
const [loading, setLoading] = useState(true);
const [initializationStatus, setInitializationStatus] = useState('');
// Sample quiz data - Replace with Supabase data useEffect(() => {
const quizzes = [ initializeAndFetchQuizzes();
{ }, []);
id: 1,
title: "Banking & Finance Essentials", const initializeAndFetchQuizzes = async () => {
description: "Understand the key concepts of personal finance and banking.", try {
questions: [ setLoading(true);
{ setInitializationStatus('Initializing quiz database...');
question: "What is compound interest?",
options: [ const initResult = await initializeQuizDatabase();
"Interest earned on the initial deposit only", if (!initResult.success) {
"Interest earned on both the initial deposit and previously earned interest", throw new Error('Failed to initialize quiz database');
"A fee charged by banks for loans", }
"A one-time interest payment on savings"
], setInitializationStatus('Fetching quizzes...');
correctAnswer: 1, await fetchQuizzes();
explanation: "Compound interest is calculated on both the principal amount and accumulated interest over time."
}, setInitializationStatus('');
{ setLoading(false);
question: "Which of the following negatively impacts your credit score?", } catch (error) {
options: [ console.error('Error initializing quizzes:', error);
"Paying credit card bills on time", setInitializationStatus('Error loading quizzes. Please try again.');
"Having multiple credit cards", setLoading(false);
"Missing loan payments",
"Checking your credit score"
],
correctAnswer: 2,
explanation: "Missing loan or credit payments lowers your credit score as it reflects poor financial responsibility."
},
{
question: "What is the primary purpose of a credit score?",
options: [
"To determine your annual tax rate",
"To assess your trustworthiness as a borrower",
"To calculate your net worth",
"To set your bank account interest rate"
],
correctAnswer: 1,
explanation: "A credit score helps lenders assess your creditworthiness before approving loans or credit cards."
},
{
question: "Which financial tool helps you save money on unnecessary expenses?",
options: [
"A budgeting app",
"A credit card",
"A payday loan",
"A lottery ticket"
],
correctAnswer: 0,
explanation: "Budgeting apps help track expenses and reduce unnecessary spending."
},
{
question: "What is an overdraft fee?",
options: [
"A fee for depositing money in your bank",
"A penalty for withdrawing more money than you have in your account",
"A charge for opening a new bank account",
"A bonus for using your debit card frequently"
],
correctAnswer: 1,
explanation: "An overdraft fee is charged when you spend more than what is available in your account."
},
{
question: "What does APR stand for?",
options: [
"Annual Percentage Rate",
"Average Payment Ratio",
"Automated Payment Record",
"Account Processing Report"
],
correctAnswer: 0,
explanation: "APR (Annual Percentage Rate) represents the cost of borrowing, including interest and fees."
}
]
},
{
id: 2,
title: "Insurance & Healthcare Knowledge",
description: "Learn about insurance and healthcare essentials.",
questions: [
{
question: "Which type of insurance helps cover the cost of medical expenses?",
options: [
"Home insurance",
"Health insurance",
"Auto insurance",
"Travel insurance"
],
correctAnswer: 1,
explanation: "Health insurance covers medical expenses, including doctor visits, hospital stays, and medications."
},
{
question: "What is a deductible in an insurance policy?",
options: [
"A discount offered for early payments",
"The amount you pay out-of-pocket before insurance kicks in",
"The total coverage amount of an insurance policy",
"The fine print in an insurance contract"
],
correctAnswer: 1,
explanation: "A deductible is the amount you must pay before your insurance starts covering expenses."
},
{
question: "What does premium mean in an insurance policy?",
options: [
"The total amount covered by the policy",
"The amount you pay for insurance coverage, usually monthly or yearly",
"A special discount given to policyholders",
"A type of investment plan"
],
correctAnswer: 1,
explanation: "A premium is the regular payment you make to keep your insurance policy active."
},
{
question: "Which of the following is NOT typically covered by standard health insurance?",
options: [
"Doctor visits",
"Prescription medication",
"Cosmetic surgery",
"Emergency room visits"
],
correctAnswer: 2,
explanation: "Cosmetic surgery is usually not covered unless it is medically necessary."
},
{
question: "What does life insurance primarily provide?",
options: [
"Coverage for medical expenses",
"Financial support for dependents after the policyholders death",
"Investment opportunities",
"Legal protection against lawsuits"
],
correctAnswer: 1,
explanation: "Life insurance provides financial security to the policyholders beneficiaries in the event of death."
}
]
},
{
id: 3,
title: "Food Safety & Consumer Awareness",
description: "Stay informed about food safety and smart consumer choices.",
questions: [
{
question: "Which of the following should you NOT do to prevent food poisoning?",
options: [
"Wash your hands before preparing food",
"Leave perishable food at room temperature for hours",
"Store raw meat separately from cooked food",
"Cook meat to the proper internal temperature"
],
correctAnswer: 1,
explanation: "Leaving perishable food at room temperature can lead to bacterial growth and foodborne illnesses."
},
{
question: "What does the expiration date on food packaging indicate?",
options: [
"The last day the food should be sold",
"The date after which the food is unsafe to eat",
"The best quality before a certain date, but it may still be safe after",
"A suggestion for when to buy new food"
],
correctAnswer: 2,
explanation: "Expiration dates often indicate peak freshness, but many foods are still safe to consume after this date."
},
{
question: "Which of the following is the safest way to thaw frozen food?",
options: [
"Leaving it on the kitchen counter",
"Placing it in the refrigerator overnight",
"Soaking it in hot water",
"Leaving it under direct sunlight"
],
correctAnswer: 1,
explanation: "Refrigerator thawing is the safest method as it prevents bacterial growth."
},
{
question: "What temperature should poultry be cooked to in order to ensure food safety?",
options: [
"120°F (49°C)",
"145°F (63°C)",
"165°F (74°C)",
"200°F (93°C)"
],
correctAnswer: 2,
explanation: "Poultry should be cooked to at least 165°F (74°C) to kill harmful bacteria."
},
{
question: "Which of these common kitchen habits increases the risk of cross-contamination?",
options: [
"Using separate cutting boards for meat and vegetables",
"Washing hands after handling raw food",
"Using the same knife for raw chicken and fresh vegetables without washing it",
"Refrigerating leftovers within two hours"
],
correctAnswer: 2,
explanation: "Using the same knife for raw meat and vegetables without washing it can transfer harmful bacteria."
}
]
} }
]; };
const handleStartQuiz = () => { const fetchQuizzes = async () => {
setCurrentQuiz(quizzes[0]); try {
const { data: quizData, error } = await supabase
.from('quiz_questions')
.select('*')
.order('id');
if (error) throw error;
const groupedQuizzes = quizData.reduce((acc, question) => {
if (!acc[question.category]) {
acc[question.category] = {
title: question.category,
description: `Test your knowledge about ${question.category}`,
questions: []
};
}
acc[question.category].questions.push({
question: question.question,
options: question.options,
correctAnswer: question.correct_answer,
explanation: question.explanation
});
return acc;
}, {});
setQuizzes(Object.values(groupedQuizzes));
} catch (error) {
console.error('Error fetching quizzes:', error);
throw error;
}
};
const handleStartQuiz = (quiz) => {
setCurrentQuiz(quiz);
setQuizStarted(true); setQuizStarted(true);
setCurrentQuestionIndex(0); setCurrentQuestionIndex(0);
setScore(0); setScore(0);
@@ -226,21 +88,46 @@ const QuizPage = () => {
setSelectedAnswer(answerIndex); setSelectedAnswer(answerIndex);
}; };
const handleNextQuestion = () => { const handleNextQuestion = async () => {
// Calculate score
if (selectedAnswer === currentQuiz.questions[currentQuestionIndex].correctAnswer) { if (selectedAnswer === currentQuiz.questions[currentQuestionIndex].correctAnswer) {
setScore(score + 1); setScore(score + 1);
} }
// Move to next question or show results
if (currentQuestionIndex + 1 < currentQuiz.questions.length) { if (currentQuestionIndex + 1 < currentQuiz.questions.length) {
setCurrentQuestionIndex(currentQuestionIndex + 1); setCurrentQuestionIndex(currentQuestionIndex + 1);
setSelectedAnswer(null); setSelectedAnswer(null);
} else { } else {
try {
const { data: { user } } = await supabase.auth.getUser();
if (user) {
await supabase.from('quiz_results').insert({
user_id: user.id,
quiz_category: currentQuiz.title,
score: score + (selectedAnswer === currentQuiz.questions[currentQuestionIndex].correctAnswer ? 1 : 0),
total_questions: currentQuiz.questions.length,
completed_at: new Date().toISOString()
});
}
} catch (error) {
console.error('Error saving quiz result:', error);
}
setShowResult(true); setShowResult(true);
} }
}; };
if (loading) {
return (
<div className="min-h-screen flex items-center justify-center">
<div className="flex flex-col items-center gap-4">
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-gray-900"></div>
{initializationStatus && (
<p className="text-gray-600">{initializationStatus}</p>
)}
</div>
</div>
);
}
return ( return (
<div className="min-h-screen bg-gray-100"> <div className="min-h-screen bg-gray-100">
{/* Navigation Bar */} {/* Navigation Bar */}
@@ -268,27 +155,28 @@ const QuizPage = () => {
// Quiz Selection Screen // Quiz Selection Screen
<div className="bg-white rounded-lg shadow-lg p-6"> <div className="bg-white rounded-lg shadow-lg p-6">
<div className="text-center mb-8"> <div className="text-center mb-8">
<h1 className="text-2xl font-bold mb-2">Financial Independence Quiz</h1> <h1 className="text-2xl font-bold mb-2">Knowledge Quiz</h1>
<p className="text-gray-600">Test your knowledge and learn about personal finance</p> <p className="text-gray-600">Select a quiz to test your knowledge</p>
</div> </div>
<div className="space-y-6"> <div className="space-y-6">
<div className="flex items-start gap-4 p-4 bg-purple-50 rounded-lg"> {quizzes.map((quiz, index) => (
<div className="bg-purple-100 p-3 rounded-lg"> <div key={index} className="flex items-start gap-4 p-4 bg-purple-50 rounded-lg">
<Award className="h-6 w-6 text-purple-600" /> <div className="bg-purple-100 p-3 rounded-lg">
<Award className="h-6 w-6 text-purple-600" />
</div>
<div className="flex-1">
<h3 className="font-semibold">{quiz.title}</h3>
<p className="text-sm text-gray-600 mt-1">{quiz.description}</p>
<button
onClick={() => handleStartQuiz(quiz)}
className="mt-4 bg-purple-600 text-white rounded-lg px-4 py-2 text-sm font-medium hover:bg-purple-700 transition-colors"
>
Start Quiz
</button>
</div>
</div> </div>
<div> ))}
<h3 className="font-semibold">Budgeting Basics</h3>
<p className="text-sm text-gray-600 mt-1">Learn the fundamentals of personal budgeting and financial planning</p>
</div>
</div>
<button
onClick={handleStartQuiz}
className="w-full bg-purple-600 text-white rounded-lg py-3 font-medium hover:bg-purple-700 transition-colors"
>
Start Quiz
</button>
</div> </div>
</div> </div>
) : showResult ? ( ) : showResult ? (

View File

@@ -0,0 +1,87 @@
// quizInsert.js
import { supabase } from './supabaseClient';
const quizQuestions = [
{
category: "Banking",
question: "What is compound interest?",
options: [
"Interest earned on the initial deposit only",
"Interest earned on both the initial deposit and previously earned interest",
"A fee charged by banks for loans",
"A one-time interest payment on savings"
],
correct_answer: 1,
explanation: "Compound interest is calculated on both the principal amount and accumulated interest over time."
},
{
category: "Banking",
question: "Which of the following negatively impacts your credit score?",
options: [
"Paying credit card bills on time",
"Having multiple credit cards",
"Missing loan payments",
"Checking your credit score"
],
correct_answer: 2,
explanation: "Missing loan or credit payments lowers your credit score as it reflects poor financial responsibility."
},
{
category: "Investment",
question: "What is diversification in investing?",
options: [
"Putting all money in one stock",
"Spreading investments across different assets",
"Only investing in bonds",
"Only investing in real estate"
],
correct_answer: 1,
explanation: "Diversification means spreading investments across different assets to reduce risk."
},
{
category: "Budgeting",
question: "What is the 50/30/20 budgeting rule?",
options: [
"50% needs, 30% wants, 20% savings",
"50% savings, 30% needs, 20% wants",
"50% wants, 30% savings, 20% needs",
"50% needs, 30% savings, 20% wants"
],
correct_answer: 0,
explanation: "The 50/30/20 rule suggests spending 50% of income on needs, 30% on wants, and 20% on savings."
}
];
const initializeQuizDatabase = async () => {
try {
// Check if questions already exist
const { data: existing, error: checkError } = await supabase
.from('quiz_questions')
.select('id')
.limit(1);
if (checkError) throw checkError;
// If no questions exist, insert them
if (!existing || existing.length === 0) {
const { data, error } = await supabase
.from('quiz_questions')
.insert(quizQuestions)
.select();
if (error) throw error;
console.log('Quiz questions initialized successfully');
return { success: true, data };
}
console.log('Quiz questions already exist');
return { success: true, data: existing };
} catch (error) {
console.error('Error initializing quiz database:', error);
return { success: false, error };
}
};
export { initializeQuizDatabase };

103
test.py Normal file
View File

@@ -0,0 +1,103 @@
import serial
import json
import requests
import time
from datetime import datetime, timedelta
class PlaidDataSender:
def __init__(self, access_token, serial_port='/dev/tty.usbserial-0001', baud_rate=115200):
self.access_token = access_token
self.serial_port = serial_port
self.baud_rate = baud_rate
self.api_base_url = 'http://localhost:3000'
def fetch_financial_data(self):
"""Fetch real financial data from Plaid API"""
headers = {'plaid-access-token': self.access_token}
# Fetch accounts
accounts_response = requests.get(
f'{self.api_base_url}/api/accounts',
headers=headers
)
accounts_data = accounts_response.json()
# Fetch transactions
transactions_response = requests.get(
f'{self.api_base_url}/api/transactions',
headers=headers
)
transactions_data = transactions_response.json()
# Calculate totals
total_balance = sum(account['balances']['current'] for account in accounts_data['accounts'])
transactions = transactions_data['transactions']
total_income = sum(trans['amount'] for trans in transactions if trans['amount'] > 0)
total_expenses = sum(trans['amount'] for trans in transactions if trans['amount'] < 0)
# Format data for ESP32
financial_summary = {
"bank_balance": round(total_balance, 2),
"total_income": round(total_income, 2),
"total_expenses": round(abs(total_expenses), 2),
"net_amount": round(total_income + total_expenses, 2),
"recent_transactions": [
{
"name": trans['merchant_name'] or trans['name'],
"amount": trans['amount'],
"type": "income" if trans['amount'] > 0 else "expense",
"date": trans['date']
}
for trans in sorted(transactions, key=lambda x: x['date'], reverse=True)[:5]
]
}
return financial_summary
def run(self):
"""Run the data sender"""
try:
# Setup serial connection
with serial.Serial(self.serial_port, self.baud_rate, timeout=1) as ser:
print(f"Connected to ESP32 on {self.serial_port}")
time.sleep(2) # Wait for connection to establish
while True:
try:
# Fetch latest financial data
financial_data = self.fetch_financial_data()
# Convert to JSON and send
json_data = json.dumps(financial_data)
ser.write((json_data + '\n').encode('utf-8'))
print(f"Sent financial data: {json_data}")
time.sleep(10) # Wait 5 seconds before next update
except requests.RequestException as e:
print(f"API Error: {e}")
time.sleep(5)
except Exception as e:
print(f"Error sending data: {e}")
time.sleep(1)
except serial.SerialException as e:
print(f"Serial connection error: {e}")
except KeyboardInterrupt:
print("\nStopped by user")
def main():
# Replace with your Plaid access token
access_token = "access-sandbox-1331c29e-2e5a-492b-ac03-623c4a7aa146"
# Create and run the sender
sender = PlaidDataSender(
access_token=access_token,
serial_port='/dev/tty.usbserial-0001', # Hardcoded serial port
baud_rate=115200
)
sender.run()
if __name__ == "__main__":
main()