From 87beee4a019012fb7a7fffa3d89a7137de62800e Mon Sep 17 00:00:00 2001 From: anudeep <69517148+anudeep-gad12@users.noreply.github.com> Date: Thu, 24 Apr 2025 03:38:00 -0500 Subject: [PATCH] Q learning --- Database/dbInitialization.js | 157 ++++------- src/Screens/CurriculumTodo/index.tsx | 383 ++++++++++++++++++--------- src/Screens/WelcomeToSeal/index.tsx | 4 +- src/prediction/sessionPrediction.tsx | 120 ++++----- src/prediction/sessionUtils.ts | 309 +++++++++++---------- 5 files changed, 538 insertions(+), 435 deletions(-) diff --git a/Database/dbInitialization.js b/Database/dbInitialization.js index 3a502c0..40f086d 100644 --- a/Database/dbInitialization.js +++ b/Database/dbInitialization.js @@ -198,7 +198,10 @@ const initializeDatabase = async () => { sequence INTEGER, content TEXT, completed INTEGER DEFAULT 0, - score INTEGER DEFAULT 0 + score INTEGER DEFAULT 0, + correct_answer TEXT, + difficulty TEXT DEFAULT 'medium', + choices TEXT )`; const curriculumImagesTableQuery = ` @@ -865,122 +868,65 @@ const updateUser = (id, updates) => { }); }; - // Combos table operations - const createCombosTable = () => { + // RL tables + export const setupRLTables = async (db) => { return new Promise((resolve, reject) => { db.transaction(tx => { + // DROP OLD UCB TABLE IF EXISTS tx.executeSql( - `CREATE TABLE IF NOT EXISTS Combos ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - score DECIMAL(12, 10), - input TEXT, - output TEXT - )`, + `DROP TABLE IF EXISTS Combos`, [], - () => { - console.log('Combos Table created successfully - in dbInitialization.'); - resolve(); - }, - (_, error) => { - console.error('Error creating table:', error); - reject(error); + () => console.log("Dropped old Combos table"), + (_, err) => { + console.error("Failed to drop Combos table:", err); + return true; } ); - }); - }); - }; - - const createScoreIndex = () => { - return new Promise((resolve, reject) => { - db.transaction(tx => { + + // CREATE RL COMBOS TABLE tx.executeSql( - `CREATE INDEX IF NOT EXISTS idx_score ON Combos(score)`, + `CREATE TABLE IF NOT EXISTS Combos ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + student_id INTEGER NOT NULL, + state TEXT NOT NULL, + action TEXT NOT NULL, + q_value REAL DEFAULT 0, + last_updated DATETIME DEFAULT CURRENT_TIMESTAMP, + UNIQUE(student_id, state, action) + )`, [], - () => { - console.log('Index created successfully.'); - resolve(); - }, - (_, error) => { - console.error('Error creating index:', error); - reject(error); - } - ); - }); - }); - }; - - const insertComboData = (score, input, output) => { - return new Promise((resolve, reject) => { - db.transaction(tx => { - tx.executeSql( - 'INSERT INTO Combos (score, input, output) VALUES (?, ?, ?)', - [score, input, output], - (_, result) => { - console.log(`A row has been inserted with rowid ${result.insertId}`); - resolve(result.insertId); - }, - (_, error) => { - console.error('Error inserting data:', error); - reject(error); + () => console.log("✅ RL Combos table created"), + (_, err) => { + console.error("Combos table create error:", err); + return true; } ); - }); - }); - }; - - const updateComboData = (score, id) => { - return new Promise((resolve, reject) => { - db.transaction(tx => { - tx.executeSql( - 'UPDATE Combos SET score = ? WHERE id = ?', - [score, id], - (_, result) => { - console.log(`Row(s) updated: ${result.rowsAffected}`); - resolve(result.rowsAffected); - }, - (_, error) => { - console.error('Error updating data:', error); - reject(error); - } - ); - }); - }); - }; - - const deleteComboData = (id) => { - return new Promise((resolve, reject) => { - db.transaction(tx => { + + // CREATE INTERACTION LOGS TABLE tx.executeSql( - 'DELETE FROM Combos WHERE id = ?', - [id], - (_, result) => { - console.log(`Row(s) deleted: ${result.rowsAffected}`); - resolve(result.rowsAffected); - }, - (_, error) => { - console.error('Error deleting data:', error); - reject(error); + `CREATE TABLE IF NOT EXISTS InteractionLogs ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + student_id INTEGER NOT NULL, + state TEXT NOT NULL, + action TEXT NOT NULL, + reward INTEGER NOT NULL, + is_correct BOOLEAN, + time_taken REAL, + emotion TEXT, + hints_used INTEGER, + timestamp DATETIME DEFAULT CURRENT_TIMESTAMP + )`, + [], + () => console.log("✅ InteractionLogs table created"), + (_, err) => { + console.error("Logs table create error:", err); + return true; } ); - }); + }, reject, resolve); }); }; - -// A prediciton algorithm has been written in the prediction/sessionPrediction.js file -// that uses the data from the Combos table to predict the next best action. -// Access the table Combos and return combo that has score closest to 1 -// const getBestComboData= () => { -// return new Promise((resolve, reject) => { -// db.transaction(tx => { -// tx.executeSql( -// 'SELECT input, output, score FROM Combos ORDER BY ABS(score - 1) LIMIT 1', -// [], -// (_, result) => { resolve(result.rows.raw()); }, -// (tx, error) => { reject(error); }, -// ); -// }); -// }); -// }; + // CRUD operations for the achievements table // Insert a new row into the achievements table @@ -1350,12 +1296,7 @@ export { updateUserSettings, getUserSettings, getAllUserSettings, - createCombosTable, - createScoreIndex, - insertComboData, - updateComboData, - deleteComboData, - // getBestComboData, + // setupRLTables, // Removed duplicate export to avoid parsing error initializeDatabase, insertImageData, insertAnswerData, diff --git a/src/Screens/CurriculumTodo/index.tsx b/src/Screens/CurriculumTodo/index.tsx index 22c654a..9a35605 100644 --- a/src/Screens/CurriculumTodo/index.tsx +++ b/src/Screens/CurriculumTodo/index.tsx @@ -1,174 +1,309 @@ -// CurriculumTodo.tsx - import React, { useState, useEffect } from 'react'; -import { View, ScrollView } from 'react-native'; -import { Appbar, Card, Title, Paragraph, List, ActivityIndicator, Checkbox, Button } from 'react-native-paper'; // Import Checkbox +import { View, ScrollView, Alert } from 'react-native'; +import { Appbar, Card, Title, Paragraph, RadioButton, Button, ActivityIndicator } from 'react-native-paper'; import BackgroundWrapper from '../../Components/BackgroundWrapper'; -import Text from '../../Components/Text'; -import styles from './styles'; import SQLite from 'react-native-sqlite-storage'; -import { loadData, predictNext } from '../../prediction/sessionUtils'; // Ensure the path is correct +import { runRLStep } from '../../prediction/sessionUtils'; +import styles from './styles'; +import { setupRLTables } from '../../../Database/dbInitialization'; const db = SQLite.openDatabase( { name: 'mydatabase.db', location: 'default' }, - () => console.log('Database opened successfully'), - error => console.error('Error opening database', error) + () => console.log("✅ DB connected"), + error => console.error("DB connect error", error) ); -interface Session { - content: string; - input_output: string; -} - -interface Prediction { - input: string; - output: string; - ucbScore: number; -} - -interface TodoItem { - title: string; - description: string; - checked: boolean; -} - const CurriculumTodo = ({ navigation }) => { - const [currentSession, setCurrentSession] = useState(null); - const [nextPrediction, setNextPrediction] = useState(null); - const [todoItems, setTodoItems] = useState([]); + const [currentQuestion, setCurrentQuestion] = useState(null); + const [selectedAnswer, setSelectedAnswer] = useState(null); + const [rlDecision, setRlDecision] = useState(null); const [isLoading, setIsLoading] = useState(true); - useEffect(() => { - const fetchDataAndPredict = async () => { - try { - const currentSessionData = await getCurrentSession(); - setCurrentSession(currentSessionData); - - const data = await loadData(); - const prediction = predictNext(data); - setNextPrediction(prediction); - - if (currentSessionData && prediction) { - const todos = generateTodoItems(currentSessionData, prediction); - setTodoItems(todos); - } else { - // Handle cases where prediction might be null - setTodoItems([]); - } - - setIsLoading(false); - } catch (error: any) { - console.error('Error fetching data:', error); - setIsLoading(false); - } - }; - fetchDataAndPredict(); + useEffect(() => { + loadInitialQuestion(); }, []); - - const getCurrentSession = (): Promise => { - return new Promise((resolve, reject) => { - db.transaction(tx => { - tx.executeSql( - 'SELECT * FROM curriculum WHERE completed = 0 ORDER BY sequence LIMIT 1', - [], - (_, { rows }) => { - if (rows.length > 0) { - resolve(rows.item(0) as Session); - } else { - reject(new Error('No incomplete curriculum items found')); + useEffect(() => { + setupRLTables(db) + .then(() => { + console.log("✅ RL Tables Ready"); + + db.transaction(tx => { + const questions = [ + { + content: "What is 2 + 2?", + input_output: "math", + sequence: 1, + difficulty: "easy", + correct_answer: "4", + choices: JSON.stringify(["2", "3", "4", "5"]) + }, + { + content: "What is the square root of 144?", + input_output: "math", + sequence: 2, + difficulty: "medium", + correct_answer: "12", + choices: JSON.stringify(["10", "11", "12", "13"]) + }, + { + content: "Integrate x dx", + input_output: "math", + sequence: 3, + difficulty: "hard", + correct_answer: "0.5x^2 + C", + choices: JSON.stringify(["x^2", "x", "0.5x^2 + C", "C"]) + }, + { + content: "What is the capital of France?", + input_output: "geo", + sequence: 4, + difficulty: "easy", + correct_answer: "Paris", + choices: JSON.stringify(["Rome", "Berlin", "Madrid", "Paris"]) + }, + { + content: "Which country has the city Timbuktu?", + input_output: "geo", + sequence: 5, + difficulty: "medium", + correct_answer: "Mali", + choices: JSON.stringify(["Niger", "Ghana", "Mali", "Chad"]) + }, + { + content: "Name the ocean east of Madagascar.", + input_output: "geo", + sequence: 6, + difficulty: "hard", + correct_answer: "Indian Ocean", + choices: JSON.stringify(["Pacific", "Atlantic", "Arctic", "Indian Ocean"]) } - }, - (_, error) => reject(error) - ); + ]; + + + questions.forEach(q => { + tx.executeSql( + `INSERT INTO curriculum (content, input_output, sequence, difficulty, correct_answer, completed, score, choices) + SELECT ?, ?, ?, ?, ?, 0, 0, ? + WHERE NOT EXISTS ( + SELECT 1 FROM curriculum WHERE content = ? AND difficulty = ? + )`, + [q.content, q.input_output, q.sequence, q.difficulty, q.correct_answer, q.choices, q.content, q.difficulty], + () => console.log(`✅ Ensured: ${q.content}`), + (_, err) => { + console.error(`Insert failed for: ${q.content}`, err); + return true; + } + ); + }); + }); + + loadInitialQuestion(); + }) + .catch(err => { + console.error("setupRLTables failed:", err); }); + }, []); + + + + const loadInitialQuestion = () => { + db.transaction(tx => { + tx.executeSql( + `SELECT * FROM curriculum WHERE completed = 0 ORDER BY sequence LIMIT 1`, + [], + (_, { rows }) => { + if (rows.length > 0) { + setCurrentQuestion(rows.item(0)); + } + setIsLoading(false); + }, + (_, err) => { + console.error("Failed to load question", err); + setIsLoading(false); + } + ); }); }; - const generateTodoItems = (currentSession: Session, prediction: Prediction): TodoItem[] => { - return [ - { title: `Complete ${currentSession.content}`, description: `Focus on the current curriculum item`, checked: false }, - { title: `Practice ${currentSession.input_output}`, description: `Reinforce your current learning`, checked: false }, - { title: `Prepare for ${prediction.input}`, description: `Get ready for the next predicted input: ${prediction.input}, output: ${prediction.output}`, checked: false }, - ]; + const checkAnswer = (): boolean => { + try { + if ( + !currentQuestion?.correct_answer || + typeof selectedAnswer !== 'string' + ) return false; + + return selectedAnswer.trim().toLowerCase() === currentQuestion.correct_answer.trim().toLowerCase(); + } catch (err) { + console.error("checkAnswer error:", err); + return false; + } }; + - const toggleCheckbox = (index: number) => { - const updatedTodos = [...todoItems]; - updatedTodos[index].checked = !updatedTodos[index].checked; - setTodoItems(updatedTodos); + const getNextQuestion = async (topic: string, difficulty: 'easy' | 'medium' | 'hard'): Promise => { + const difficulties = { + easy: ["medium", "hard"], + medium: ["easy", "hard"], + hard: ["medium", "easy"] + }; + + const queryQuestion = (diff: string): Promise => { + return new Promise((resolve, reject) => { + db.transaction(tx => { + tx.executeSql( + `SELECT * FROM curriculum WHERE completed = 0 AND input_output = ? AND difficulty = ? ORDER BY RANDOM() LIMIT 1`, + [topic, diff], + (_, { rows }) => { + if (rows.length > 0) { + resolve(rows.item(0)); + } else { + resolve(null); + } + }, + (_, err) => reject(err) + ); + }); + }); + }; + + // Try current difficulty first + let next = await queryQuestion(difficulty); + if (next) return next; + + // Try alternates + for (const diff of difficulties[difficulty] || []) { + next = await queryQuestion(diff); + if (next) return next; + } + + // Nothing left + return null; }; + + - if (isLoading) { + const handleSubmit = async () => { + try { + if (!currentQuestion) { + Alert.alert("Error", "No current question loaded."); + return; + } + + if (selectedAnswer === null || selectedAnswer === "") { + Alert.alert("Error", "Please select an answer."); + return; + } + + const isCorrect = checkAnswer(); + const topic = currentQuestion.input_output; + const difficulty = currentQuestion.difficulty ?? "medium"; + if (isCorrect) { + db.transaction(tx => { + tx.executeSql( + `UPDATE curriculum SET completed = 1 WHERE id = ?`, + [currentQuestion.id], + () => console.log(`✅ Marked question ${currentQuestion.id} as completed`), + (_, err) => console.error("Failed to update completion:", err) + ); + }); + } + + + const result = await runRLStep({ + studentId: 1, + topic, + accuracy: isCorrect ? 80 : 40, + timeTaken: 7.5, + emotion: isCorrect ? "Confident" : "Frustrated", + isCorrect, + hintsUsed: 0, + lastAction: rlDecision || "repeat", + }); + + + setRlDecision(result.nextAction); + + let nextDiff = difficulty; + if (result.nextAction === "increase_difficulty") nextDiff = "hard"; + else if (result.nextAction === "decrease_difficulty") nextDiff = "easy"; + else if (result.nextAction === "repeat") nextDiff = difficulty; + else nextDiff = "medium"; + + const nextQ = await getNextQuestion(topic, nextDiff).catch(err => { + console.error("Error getting next question:", err?.message || err); + return null; + }); + + + if (nextQ) { + setCurrentQuestion(nextQ); + setSelectedAnswer(null); + } else { + Alert.alert("You're done!", "No more questions in this track."); + } + + } catch (err: any) { + console.error("Uncaught crash in handleSubmit():", err); + Alert.alert("Crash", "Something went wrong while processing your answer."); + } + }; + + if (isLoading || !currentQuestion) { return ( - Loading your curriculum... + Loading your curriculum... ); } + const options = currentQuestion?.choices + ? JSON.parse(currentQuestion.choices) + : [currentQuestion.correct_answer]; // fallback + return ( navigation.goBack()} /> - + + - {/* uncomment this to add a button to navigate to ImageOutTest */} - {/* */} - Current Task - {currentSession && ( - <> - Content: {currentSession.content} - Focus: {currentSession.input_output} - - )} + Question + {currentQuestion.content} - Next Recommended Focus - {nextPrediction ? ( - <> - Input: {nextPrediction.input} - Output: {nextPrediction.output} - Confidence: {nextPrediction.ucbScore.toFixed(4)} - - ) : ( - No prediction available - )} + Select Answer + + {options.map((option, index) => ( + + ))} + + + - Tasks - {todoItems.length > 0 ? ( - todoItems.map((item, index) => ( - toggleCheckbox(index)} // Make the entire row clickable - left={props => ( - toggleCheckbox(index)} - /> - )} - /> - )) - ) : ( - No tasks available + {rlDecision && ( + + + AI Decision + Next Action: {rlDecision} + + )} diff --git a/src/Screens/WelcomeToSeal/index.tsx b/src/Screens/WelcomeToSeal/index.tsx index 45eae3d..ba174f4 100644 --- a/src/Screens/WelcomeToSeal/index.tsx +++ b/src/Screens/WelcomeToSeal/index.tsx @@ -8,7 +8,7 @@ import getStyles from './defaultCSS'; import {useFontContext} from '../../Contexts/FontContext'; import Seal from '../../Assets/svg/seal.svg'; import Hello from '../../Assets/svg/hello.svg'; -import SessionOptimizerComponent from '../../prediction/sessionPrediction'; +// import SessionOptimizerComponent from '../../prediction/sessionPrediction'; import useBreakTimer from '../../Hooks/BreakTimer'; const Index = ({navigation}) => { @@ -44,7 +44,7 @@ const Index = ({navigation}) => { {/* For temporary testing purpose, Session combos output */} - + {/* */} {/* */} diff --git a/src/prediction/sessionPrediction.tsx b/src/prediction/sessionPrediction.tsx index 98a8e67..70cd300 100644 --- a/src/prediction/sessionPrediction.tsx +++ b/src/prediction/sessionPrediction.tsx @@ -1,62 +1,62 @@ -import React, { useState } from 'react'; -import { View, Text, Button } from 'react-native'; -import { runTest } from './sessionUtils'; - -interface Prediction { - input: string; - output: string; - ucbScore: number; -} - -/** - * Session Optimizer Component - */ -const SessionOptimizerComponent = () => { - const [result, setResult] = useState<{ prediction?: Prediction; error?: string } | null>(null); - const [isLoading, setIsLoading] = useState(false); - - const handleRunTest = async () => { - setIsLoading(true); - setResult(null); - - try { - console.log('Starting runTest...'); - const testResult = await runTest(); - console.log('Test completed. Result:', testResult); - setResult(testResult); - } catch (error: any) { - console.error('Test failed:', error.message); - setResult({ error: error.message }); - } finally { - setIsLoading(false); - } - }; - - return ( - -