Skip to content

Commit a18fdca

Browse files
committed
Add utility functions for grade conversion and document type normalization; implement OpenAI API key retrieval
1 parent a870dd3 commit a18fdca

File tree

11 files changed

+264
-673
lines changed

11 files changed

+264
-673
lines changed

frontend/src/components/DocumentManager.tsx

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React, { useState, useEffect, useCallback } from 'react';
1+
import React, { useState, useEffect } from 'react';
22
import { useAuth } from '../contexts/AuthContext';
33
import { getFunctions, httpsCallable } from 'firebase/functions';
44
import DocumentProcessingStatus from './DocumentProcessingStatus';
@@ -23,8 +23,8 @@ const DocumentManager: React.FC = () => {
2323

2424
const functions = getFunctions();
2525

26-
// Memoize fetchUserDocuments with useCallback
27-
const fetchUserDocuments = useCallback(async () => {
26+
// Fetch user documents
27+
const fetchUserDocuments = async () => {
2828
try {
2929
const getUserDocuments = httpsCallable(functions, 'getUserDocuments');
3030
const result = await getUserDocuments();
@@ -36,14 +36,14 @@ const DocumentManager: React.FC = () => {
3636
console.error('Error fetching documents:', err);
3737
setError('Failed to load existing documents');
3838
}
39-
}, [functions, setError]);
39+
};
4040

4141
// Fetch existing documents on component mount
4242
useEffect(() => {
4343
if (!currentUser) return;
4444

4545
fetchUserDocuments();
46-
}, [currentUser, fetchUserDocuments]);
46+
}, [currentUser]);
4747

4848
const handleFileChange = (e: React.ChangeEvent<HTMLInputElement>) => {
4949
if (e.target.files) {

frontend/src/components/DocumentProcessingStatus.tsx

Lines changed: 0 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -224,68 +224,6 @@ const DocumentProcessingStatus: React.FC<DocumentProcessingStatusProps> = ({ onP
224224
return progress;
225225
};
226226

227-
// Function to inspect document data
228-
const inspectDocumentData = async (documentId: string) => {
229-
if (!currentUser) return;
230-
231-
setStatus(`Inspecting document ${documentId}...`);
232-
233-
try {
234-
// Get document from Firestore
235-
const db = getFirestore();
236-
const docRef = doc(db, 'users', currentUser.uid, 'documents', documentId);
237-
const docSnap = await getDoc(docRef);
238-
239-
if (docSnap.exists()) {
240-
const data = docSnap.data();
241-
console.log("===== DOCUMENT DATA =====");
242-
console.log(`Document ID: ${documentId}`);
243-
console.log(`Type: ${data.documentType}`);
244-
console.log(`Status: ${data.status}`);
245-
console.log(`Name: ${data.name}`);
246-
247-
// Log text data if available (limited to first 500 chars for readability)
248-
if (data.text) {
249-
console.log(`Text length: ${data.text.length} characters`);
250-
console.log("First 500 characters:");
251-
console.log(data.text.substring(0, 500) + "...");
252-
}
253-
254-
setStatus(`Document data logged to console for ${documentId}`);
255-
} else {
256-
setError(`Document not found: ${documentId}`);
257-
}
258-
} catch (err: any) {
259-
console.error('Error inspecting document:', err);
260-
setError(`Inspection failed: ${err.message || 'Unknown error'}`);
261-
}
262-
};
263-
264-
// Function to inspect formatted data
265-
const inspectFormattedData = async () => {
266-
if (!currentUser) return;
267-
268-
setStatus('Fetching formatted data...');
269-
270-
try {
271-
const db = getFirestore();
272-
const formattedDataRef = doc(db, 'users', currentUser.uid, 'data', 'formatted_data');
273-
const docSnap = await getDoc(formattedDataRef);
274-
275-
if (docSnap.exists()) {
276-
const data = docSnap.data();
277-
console.log("===== FORMATTED DATA =====");
278-
console.log(JSON.stringify(data.formatted_data, null, 2));
279-
setStatus('Formatted data logged to console');
280-
} else {
281-
console.log("No formatted data document found");
282-
setError('No formatted data available');
283-
}
284-
} catch (err: any) {
285-
console.error('Error fetching formatted data:', err);
286-
setError(`Failed to fetch formatted data: ${err.message || 'Unknown error'}`);
287-
}
288-
};
289227

290228
const progress = calculateProgress();
291229
const canFormat = documentCounts.extracted > 0 && !isFormatting;
@@ -376,12 +314,6 @@ const DocumentProcessingStatus: React.FC<DocumentProcessingStatusProps> = ({ onP
376314
>
377315
{isFormatting ? 'Formatting...' : 'Format Documents'}
378316
</button>
379-
<button
380-
onClick={inspectFormattedData}
381-
style={{...styles.formatButton, backgroundColor: '#666', marginLeft: '10px'}}
382-
>
383-
Inspect Formatted Data
384-
</button>
385317
</div>
386318
)}
387319

@@ -430,12 +362,6 @@ const DocumentProcessingStatus: React.FC<DocumentProcessingStatusProps> = ({ onP
430362
Retry Processing
431363
</button>
432364
)}
433-
<button
434-
onClick={() => inspectDocumentData(doc.id)}
435-
style={{...styles.actionButton, backgroundColor: '#666'}}
436-
>
437-
Inspect
438-
</button>
439365
</div>
440366
{doc.error && (
441367
<div style={styles.errorMessage}>

frontend/src/components/PredictionPanel.tsx

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -128,12 +128,6 @@ const PredictionPanel: React.FC = () => {
128128
// Determine if we can make a prediction based on document status or processing completion
129129
const canPredict = documents.some(doc => doc.status === 'processed') || processingComplete;
130130

131-
// We'll keep this comment to document what we're calculating, but remove the unused variable
132-
// Get count of documents by type
133-
// const docCounts = documents.reduce((acc: Record<string, number>, doc) => {
134-
// acc[doc.documentType] = (acc[doc.documentType] || 0) + 1;
135-
// return acc;
136-
// }, {});
137131

138132
return (
139133
<div style={styles.container}>

functions-node/calculateGrade.js

Lines changed: 2 additions & 158 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
const functions = require('firebase-functions');
22
const admin = require('firebase-admin');
3+
const { getLetterGrade } = require('./utils/gradeUtils');
4+
const { calculateExactGradeStatistics, generateGradeAnalysis } = require('./utils/calculationUtils');
35

46
/**
57
* Cloud Function to calculate current grade based on submitted assignments
@@ -60,164 +62,6 @@ exports.calculateCurrentGrade = functions.https.onCall(async (data, context) =>
6062
}
6163
});
6264

63-
/**
64-
* Calculate grade statistics with precision
65-
* @param {Object} data - Formatted data from formatDataForCalculation
66-
* @returns {Object} Calculated statistics
67-
*/
68-
function calculateExactGradeStatistics(data) {
69-
const { gradeWeights, completedAssignments, remainingAssignments } = data;
70-
71-
// Initialize tracking variables
72-
const categoryStats = {};
73-
let totalWeightCovered = 0;
74-
75-
// Calculate stats for each category
76-
gradeWeights.forEach(category => {
77-
const categoryAssignments = completedAssignments.filter(
78-
a => a.category === category.name
79-
);
80-
81-
const categoryRemaining = remainingAssignments.filter(
82-
a => a.category === category.name
83-
);
84-
85-
// Calculate total points and max possible in this category
86-
let totalPoints = 0;
87-
let maxPoints = 0;
88-
89-
categoryAssignments.forEach(assignment => {
90-
// Handle numeric grades and special cases like "Dropped"
91-
if (typeof assignment.grade === 'number') {
92-
totalPoints += assignment.grade;
93-
maxPoints += assignment.maxPoints || 100;
94-
} else if (assignment.grade !== 'Dropped') {
95-
const numericGrade = parseFloat(assignment.grade);
96-
if (!isNaN(numericGrade)) {
97-
totalPoints += numericGrade;
98-
maxPoints += assignment.maxPoints || 100;
99-
}
100-
}
101-
});
102-
103-
// Calculate category average
104-
const categoryAverage = maxPoints > 0 ? (totalPoints / maxPoints) * 100 : null;
105-
106-
// Store stats
107-
categoryStats[category.name] = {
108-
completed: categoryAssignments,
109-
remaining: categoryRemaining,
110-
totalPoints,
111-
maxPoints,
112-
average: categoryAverage,
113-
weight: category.weight
114-
};
115-
116-
// Add to total weight if we have assignments in this category
117-
if (maxPoints > 0) {
118-
totalWeightCovered += category.weight;
119-
}
120-
});
121-
122-
// Calculate overall current grade
123-
let currentGradeWeighted = 0;
124-
125-
Object.values(categoryStats).forEach(stats => {
126-
if (stats.average !== null) {
127-
// Scale by weight
128-
currentGradeWeighted += (stats.average / 100) * stats.weight;
129-
}
130-
});
131-
132-
// Normalize by covered weight if needed
133-
const currentGrade = totalWeightCovered > 0
134-
? (currentGradeWeighted / totalWeightCovered) * 100
135-
: 0;
136-
137-
// Calculate max possible grade (if all remaining is 100%)
138-
let maxPossibleGrade = currentGradeWeighted;
139-
let remainingWeight = 0;
140-
141-
Object.values(categoryStats).forEach(stats => {
142-
// Count weight for categories with remaining assignments
143-
if (stats.remaining.length > 0) {
144-
const categoryRemainingWeight = stats.weight * (stats.remaining.length /
145-
(stats.completed.length + stats.remaining.length || 1));
146-
147-
remainingWeight += categoryRemainingWeight;
148-
maxPossibleGrade += categoryRemainingWeight;
149-
}
150-
});
151-
152-
// Normalize max grade
153-
maxPossibleGrade = totalWeightCovered > 0
154-
? (maxPossibleGrade / (totalWeightCovered + remainingWeight)) * 100
155-
: 100;
156-
157-
// Min grade (if all remaining is 0%)
158-
const minPossibleGrade = totalWeightCovered > 0
159-
? (currentGradeWeighted / (totalWeightCovered + remainingWeight)) * 100
160-
: 0;
161-
162-
// Format for return
163-
return {
164-
current_grade: currentGrade,
165-
current_percentage: currentGrade,
166-
letter_grade: getLetterGrade(currentGrade),
167-
max_possible_grade: maxPossibleGrade,
168-
min_possible_grade: minPossibleGrade,
169-
categorized_grades: categoryStats,
170-
analysis: generateGradeAnalysis({
171-
currentGrade,
172-
maxPossibleGrade,
173-
minPossibleGrade,
174-
letterGrade: getLetterGrade(currentGrade),
175-
categorizedGrades: categoryStats
176-
})
177-
};
178-
}
179-
180-
/**
181-
* Get letter grade from numeric value
182-
* @param {number} grade - Numeric grade
183-
* @returns {string} Letter grade
184-
*/
185-
function getLetterGrade(grade) {
186-
if (grade >= 93) return 'A';
187-
if (grade >= 90) return 'A-';
188-
if (grade >= 87) return 'B+';
189-
if (grade >= 83) return 'B';
190-
if (grade >= 80) return 'B-';
191-
if (grade >= 77) return 'C+';
192-
if (grade >= 73) return 'C';
193-
if (grade >= 70) return 'C-';
194-
if (grade >= 67) return 'D+';
195-
if (grade >= 63) return 'D';
196-
if (grade >= 60) return 'D-';
197-
return 'F';
198-
}
199-
200-
/**
201-
* Generate natural language analysis of grade stats
202-
* @param {Object} stats - Grade statistics
203-
* @returns {string} Analysis text
204-
*/
205-
function generateGradeAnalysis(stats) {
206-
const { currentGrade, maxPossibleGrade, minPossibleGrade, letterGrade } = stats;
207-
const analysis = [];
208-
209-
analysis.push(`Current grade is ${currentGrade.toFixed(1)}% (${letterGrade})`);
210-
211-
if (maxPossibleGrade > currentGrade) {
212-
analysis.push(`Maximum possible grade is ${maxPossibleGrade.toFixed(1)}%`);
213-
}
214-
215-
if (minPossibleGrade < currentGrade) {
216-
analysis.push(`Minimum possible grade is ${minPossibleGrade.toFixed(1)}%`);
217-
}
218-
219-
return analysis.join('. ');
220-
}
22165

22266
/**
22367
* Fetch structured data from Firestore

functions-node/formatDocumentsData.js

Lines changed: 2 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
const OpenAI = require('openai');
22
const functions = require('firebase-functions');
33
const admin = require('firebase-admin');
4-
const { DOCUMENT_TYPES, normalizeDocumentType } = require('./constants/documentTypes');
4+
const { DOCUMENT_TYPES, normalizeDocumentType } = require('./utils/documentUtils');
5+
const { getOpenAIApiKey } = require('./utils/apiUtils');
56

67
/**
78
* Store debugging data in Firestore
@@ -412,17 +413,3 @@ function extractGradeWeights(text) {
412413
return [];
413414
}
414415
}
415-
416-
/**
417-
* Helper function to get OpenAI API key
418-
*/
419-
function getOpenAIApiKey() {
420-
const apiKey = process.env.OPENAI_API_KEY ||
421-
(functions.config && functions.config().openai && functions.config().openai.apikey);
422-
423-
if (!apiKey) {
424-
throw new Error('OpenAI API key not configured. Please set OPENAI_API_KEY or firebase config openai.apikey');
425-
}
426-
427-
return apiKey;
428-
}

0 commit comments

Comments
 (0)