update final design
This commit is contained in:
138
SerialToSerialBT.ino
Normal file
138
SerialToSerialBT.ino
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -92,19 +92,6 @@ const DashboardContent = () => {
|
||||
const { accounts, transactions, loading } = usePlaid(); // Use the Plaid context
|
||||
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
|
||||
const processChartData = () => {
|
||||
if (!transactions?.length) return [];
|
||||
@@ -308,7 +295,7 @@ const DashboardLayout = ({ children }) => {
|
||||
{/* Logo and Brand */}
|
||||
<div className="flex items-center">
|
||||
<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>
|
||||
|
||||
{/* User Info and Logout */}
|
||||
|
||||
@@ -4,6 +4,7 @@ import { usePlaid } from '../PlaidProvider';
|
||||
import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, Legend, ResponsiveContainer } from 'recharts';
|
||||
import { DollarSign, ChevronLeft } from 'lucide-react';
|
||||
|
||||
|
||||
const ExpenseTracker = () => {
|
||||
const navigate = useNavigate();
|
||||
const {
|
||||
|
||||
@@ -1,221 +1,83 @@
|
||||
// QuizPage.jsx
|
||||
import React, { useState, useEffect } from 'react';
|
||||
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 { initializeQuizDatabase } from '../quizInsert';
|
||||
|
||||
const QuizPage = () => {
|
||||
const navigate = useNavigate();
|
||||
const [quizzes, setQuizzes] = useState([]);
|
||||
const [currentQuiz, setCurrentQuiz] = useState(null);
|
||||
const [currentQuestionIndex, setCurrentQuestionIndex] = useState(0);
|
||||
const [selectedAnswer, setSelectedAnswer] = useState(null);
|
||||
const [score, setScore] = useState(0);
|
||||
const [showResult, setShowResult] = useState(false);
|
||||
const [quizStarted, setQuizStarted] = useState(false);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [initializationStatus, setInitializationStatus] = useState('');
|
||||
|
||||
// Sample quiz data - Replace with Supabase data
|
||||
const quizzes = [
|
||||
{
|
||||
id: 1,
|
||||
title: "Banking & Finance Essentials",
|
||||
description: "Understand the key concepts of personal finance and banking.",
|
||||
questions: [
|
||||
{
|
||||
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"
|
||||
],
|
||||
correctAnswer: 1,
|
||||
explanation: "Compound interest is calculated on both the principal amount and accumulated interest over time."
|
||||
},
|
||||
{
|
||||
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"
|
||||
],
|
||||
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 policyholder’s death",
|
||||
"Investment opportunities",
|
||||
"Legal protection against lawsuits"
|
||||
],
|
||||
correctAnswer: 1,
|
||||
explanation: "Life insurance provides financial security to the policyholder’s 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."
|
||||
}
|
||||
]
|
||||
useEffect(() => {
|
||||
initializeAndFetchQuizzes();
|
||||
}, []);
|
||||
|
||||
const initializeAndFetchQuizzes = async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
setInitializationStatus('Initializing quiz database...');
|
||||
|
||||
const initResult = await initializeQuizDatabase();
|
||||
if (!initResult.success) {
|
||||
throw new Error('Failed to initialize quiz database');
|
||||
}
|
||||
|
||||
setInitializationStatus('Fetching quizzes...');
|
||||
await fetchQuizzes();
|
||||
|
||||
setInitializationStatus('');
|
||||
setLoading(false);
|
||||
} catch (error) {
|
||||
console.error('Error initializing quizzes:', error);
|
||||
setInitializationStatus('Error loading quizzes. Please try again.');
|
||||
setLoading(false);
|
||||
}
|
||||
];
|
||||
};
|
||||
|
||||
const handleStartQuiz = () => {
|
||||
setCurrentQuiz(quizzes[0]);
|
||||
const fetchQuizzes = async () => {
|
||||
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);
|
||||
setCurrentQuestionIndex(0);
|
||||
setScore(0);
|
||||
@@ -226,21 +88,46 @@ const QuizPage = () => {
|
||||
setSelectedAnswer(answerIndex);
|
||||
};
|
||||
|
||||
const handleNextQuestion = () => {
|
||||
// Calculate score
|
||||
const handleNextQuestion = async () => {
|
||||
if (selectedAnswer === currentQuiz.questions[currentQuestionIndex].correctAnswer) {
|
||||
setScore(score + 1);
|
||||
}
|
||||
|
||||
// Move to next question or show results
|
||||
if (currentQuestionIndex + 1 < currentQuiz.questions.length) {
|
||||
setCurrentQuestionIndex(currentQuestionIndex + 1);
|
||||
setSelectedAnswer(null);
|
||||
} 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);
|
||||
}
|
||||
};
|
||||
|
||||
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 (
|
||||
<div className="min-h-screen bg-gray-100">
|
||||
{/* Navigation Bar */}
|
||||
@@ -268,27 +155,28 @@ const QuizPage = () => {
|
||||
// Quiz Selection Screen
|
||||
<div className="bg-white rounded-lg shadow-lg p-6">
|
||||
<div className="text-center mb-8">
|
||||
<h1 className="text-2xl font-bold mb-2">Financial Independence Quiz</h1>
|
||||
<p className="text-gray-600">Test your knowledge and learn about personal finance</p>
|
||||
<h1 className="text-2xl font-bold mb-2">Knowledge Quiz</h1>
|
||||
<p className="text-gray-600">Select a quiz to test your knowledge</p>
|
||||
</div>
|
||||
|
||||
<div className="space-y-6">
|
||||
<div className="flex items-start gap-4 p-4 bg-purple-50 rounded-lg">
|
||||
<div className="bg-purple-100 p-3 rounded-lg">
|
||||
<Award className="h-6 w-6 text-purple-600" />
|
||||
{quizzes.map((quiz, index) => (
|
||||
<div key={index} className="flex items-start gap-4 p-4 bg-purple-50 rounded-lg">
|
||||
<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>
|
||||
<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>
|
||||
) : showResult ? (
|
||||
|
||||
87
frontend/src/quizInsert.js
Normal file
87
frontend/src/quizInsert.js
Normal 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
103
test.py
Normal 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()
|
||||
Reference in New Issue
Block a user