From 4e6cf98d1e55f009ab908e26883b5e581118beff Mon Sep 17 00:00:00 2001 From: unisA02 Date: Mon, 1 Jun 2026 17:31:15 +0800 Subject: [PATCH 01/32] add messenger link of tester to footer --- package-lock.json | 6 ++++++ popup.css | 39 +++++++++++++++++++++++++++++++++++++++ popup.html | 5 +++++ 3 files changed, 50 insertions(+) create mode 100644 package-lock.json diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..9f66df0 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,6 @@ +{ + "name": "gradesim", + "lockfileVersion": 3, + "requires": true, + "packages": {} +} diff --git a/popup.css b/popup.css index 58a5679..57c44d0 100644 --- a/popup.css +++ b/popup.css @@ -1173,4 +1173,43 @@ header h1 { border-color: var(--warning); background: rgba(255, 255, 0, 0.2); color: var(--warning); +} + +.footer { + text-align: center; + padding: 12px; + margin-top: 16px; + border-top: 2px dashed var(--gem-dark); +} + +.footer a { + display: inline-block; + color: var(--bg-dark); + text-decoration: none; + font-size: 0.85rem; + font-weight: 600; + padding: 8px 16px; + background: linear-gradient(180deg, var(--gem-light) 0%, var(--gem-mid) 50%, var(--gem-dark) 100%); + border: 2px solid var(--gem-dark); + box-shadow: + inset -2px -2px 0 var(--gem-dark), + inset 2px 2px 0 rgba(255,255,255,0.3), + 0 4px 0 var(--gem-dark); + transition: all 0.1s; +} + +.footer a:hover { + transform: translateY(-2px); + box-shadow: + inset -2px -2px 0 var(--gem-dark), + inset 2px 2px 0 rgba(255,255,255,0.3), + 0 6px 0 var(--gem-dark); +} + +.footer a:active { + transform: translateY(2px); + box-shadow: + inset -2px -2px 0 var(--gem-dark), + inset 2px 2px 0 rgba(255,255,255,0.3), + 0 2px 0 var(--gem-dark); } \ No newline at end of file diff --git a/popup.html b/popup.html index c9337f3..f956c76 100644 --- a/popup.html +++ b/popup.html @@ -204,6 +204,11 @@

Remaining Courses

+ + + From a88aceab89609ece4dc0205a05ddc649433238b7 Mon Sep 17 00:00:00 2001 From: Ken Ramiscal Date: Mon, 1 Jun 2026 21:44:10 +0800 Subject: [PATCH 02/32] feat: add new stat display - add wrapped panel for current semester --- extension/src/popup.js | 495 ++++++++++++++++++++++----------------- extension/src/types.d.ts | 48 ++++ 2 files changed, 331 insertions(+), 212 deletions(-) create mode 100644 extension/src/types.d.ts diff --git a/extension/src/popup.js b/extension/src/popup.js index b5f9ee1..86a3586 100644 --- a/extension/src/popup.js +++ b/extension/src/popup.js @@ -30,25 +30,25 @@ document.addEventListener('DOMContentLoaded', async () => { const noDataEl = document.getElementById('noData'); const mainContentEl = document.getElementById('mainContent'); const refreshBtn = document.getElementById('refreshBtn'); - + // Tab handling const tabs = document.querySelectorAll('.tab'); tabs.forEach(tab => { tab.addEventListener('click', () => { tabs.forEach(t => t.classList.remove('active')); tab.classList.add('active'); - + document.querySelectorAll('.tab-content').forEach(content => { content.classList.remove('active'); content.classList.add('hidden'); }); - + const tabId = tab.dataset.tab + 'Tab'; document.getElementById(tabId).classList.remove('hidden'); document.getElementById(tabId).classList.add('active'); }); }); - + // Program selection handling const programSelect = document.getElementById('programSelect'); if (programSelect) { @@ -61,22 +61,22 @@ document.addEventListener('DOMContentLoaded', async () => { // Initialize with default program (BSCS) setCurrentProgram('BSCS'); } - + programSelect.addEventListener('change', (e) => { const programCode = e.target.value; setCurrentProgram(programCode); chrome.storage.local.set({ selectedProgram: programCode }); - + // Recalculate remaining courses with new program if (window.gradesData && window.gradesData.completedCourses) { displayRemainingCourses(window.gradesData.completedCourses); } }); } - + // Note: Track selection event listeners are now attached dynamically // in updateTrackOptionsUI() when the program is set/changed - + // View toggle handling (Semester/Year) document.querySelectorAll('.view-btn').forEach(btn => { btn.addEventListener('click', () => { @@ -90,40 +90,43 @@ document.addEventListener('DOMContentLoaded', async () => { } }); }); - + // Load grades data loadGradesData(); - + // Refresh button refreshBtn.addEventListener('click', () => { loadGradesData(); }); - + // Calculate button document.getElementById('calculateBtn').addEventListener('click', calculateWhatIf); - + // Custom GWA input handling document.getElementById('customGWA').addEventListener('focus', () => { document.querySelector('input[value="custom"]').checked = true; }); }); -// Parse the AMIS data structure into a flat array of courses +/** + * @returns {import("./types").CourseData[]} + * Parse the AMIS data structure into a flat array of courses +*/ function parseAMISData(data) { const courses = []; - + if (!data || !data.student_grades) { return courses; } - + const studentGrades = data.student_grades; - + // Iterate through each term (1231, 1232, etc.) for (const [termId, termData] of Object.entries(studentGrades)) { if (!termData || !termData.values) continue; - + const termInfo = termData.term || `Term ${termId}`; - + // Iterate through each course in the term for (const courseData of termData.values) { courses.push({ @@ -141,7 +144,7 @@ function parseAMISData(data) { }); } } - + return courses; } @@ -149,24 +152,27 @@ async function loadGradesData() { const loadingEl = document.getElementById('loading'); const noDataEl = document.getElementById('noData'); const mainContentEl = document.getElementById('mainContent'); - + loadingEl.classList.remove('hidden'); noDataEl.classList.add('hidden'); mainContentEl.classList.add('hidden'); - + try { // Get data from storage let result = await chrome.storage.local.get(['gradesData']); - + // If no data, wait a moment and try again (data might still be loading) if (!result.gradesData) { await new Promise(resolve => setTimeout(resolve, 1000)); result = await chrome.storage.local.get(['gradesData']); } - - // Parse the AMIS data structure + + /** + * Parse the AMIS data structure + * @type {import("./types").CourseData[]} + */ const courses = parseAMISData(result.gradesData); - + if (courses && courses.length > 0) { displayGradesData(courses); loadingEl.classList.add('hidden'); @@ -184,24 +190,24 @@ async function loadGradesData() { function displayGradesData(courses) { // Calculate GWA with exclusions const { gwa, totalUnits, totalCourses, gradesBySemester, completedCourses, excludedUnits, excludedCount } = calculateGWA(courses, excludedCourses); - + // Display GWA document.getElementById('currentGWA').textContent = gwa.toFixed(4); document.getElementById('totalUnits').textContent = excludedCount > 0 ? `${totalUnits} (${excludedUnits} excl.)` : totalUnits; document.getElementById('totalCourses').textContent = excludedCount > 0 ? `${totalCourses} (${excludedCount} excl.)` : totalCourses; - + // Display honor status displayHonorStatus(gwa); - + // Display grades list (pass courses for toggle functionality) displayGradesList(gradesBySemester, courses); - + // Display remaining courses displayRemainingCourses(completedCourses); - + // Display Wrapped feature displayWrapped(courses); - + // Store calculated data for What If window.gradesData = { gwa, @@ -220,40 +226,40 @@ function calculateGWA(courses, excludedIds = new Set()) { let excludedCount = 0; const gradesBySemester = {}; const completedCourses = []; - + // Courses to exclude from GWA calculation (by prefix) const excludedPrefixes = ['NSTP', 'HK', 'PE']; // Non-numeric grades that should be displayed but not counted in GWA const nonNumericGrades = ['S', 'U', 'INC', 'DRP', 'W', 'P', 'DFG']; - + courses.forEach(course => { const courseCode = course.courseCode || ''; const courseId = String(course.id || `${course.courseCode}-${course.termId}`); const gradeStr = (course.grade || '').toString().toUpperCase().trim(); - + // Skip NSTP, HK, PE courses entirely - const isPrefixExcluded = excludedPrefixes.some(prefix => + const isPrefixExcluded = excludedPrefixes.some(prefix => courseCode.toUpperCase().startsWith(prefix) ); if (isPrefixExcluded) { return; } - + const units = course.units || 0; - + // Check if it's a non-numeric grade (S, U, INC, etc.) const isNonNumericGrade = nonNumericGrades.includes(gradeStr); - + // Group by semester (for display - includes all courses) const semKey = course.term || `${course.academicYear} - ${course.semester}`; if (!gradesBySemester[semKey]) { gradesBySemester[semKey] = []; } - + // Add course to display with special handling for S/U grades const displayCourse = { ...course, isNonNumeric: isNonNumericGrade }; gradesBySemester[semKey].push(displayCourse); - + // For non-numeric grades, add to completed but don't calculate GWA if (isNonNumericGrade) { // S (Satisfactory) counts as passed, U (Unsatisfactory) does not @@ -267,15 +273,15 @@ function calculateGWA(courses, excludedIds = new Set()) { } return; } - + // Skip courses without valid numeric grades const grade = parseFloat(course.grade); if (isNaN(grade) || grade < 1.0 || grade > 5.0) { return; } - + if (units === 0) return; - + // Check if user-excluded (for shiftees) const isUserExcluded = excludedIds.has(courseId); if (isUserExcluded) { @@ -284,12 +290,12 @@ function calculateGWA(courses, excludedIds = new Set()) { excludedCount++; return; } - + // Add to GWA calculation totalWeightedGrade += grade * units; totalUnits += units; totalCourses++; - + completedCourses.push({ code: course.courseCode, title: course.courseTitle, @@ -297,15 +303,15 @@ function calculateGWA(courses, excludedIds = new Set()) { grade: grade }); }); - + const gwa = totalUnits > 0 ? totalWeightedGrade / totalUnits : 0; - + return { gwa, totalUnits, totalCourses, gradesBySemester, completedCourses, excludedUnits, excludedCount }; } function displayHonorStatus(gwa) { const honorEl = document.getElementById('honorStatus'); - + if (gwa <= 1.20) { honorEl.textContent = '🏆 Summa Cum Laude Track'; honorEl.className = 'honor-status summa'; @@ -333,7 +339,7 @@ function displayHonorStatus(gwa) { function displayGradesList(gradesBySemester, allCourses) { const listEl = document.getElementById('gradesList'); listEl.innerHTML = ''; - + // Group courses based on current view mode let groupedCourses; if (currentView === 'year') { @@ -343,7 +349,7 @@ function displayGradesList(gradesBySemester, allCourses) { courses.forEach(course => { // Use the academicYear field directly from course data const yearKey = course.academicYear ? `AY ${course.academicYear}` : 'Unknown Year'; - + if (!groupedCourses[yearKey]) { groupedCourses[yearKey] = []; } @@ -354,22 +360,22 @@ function displayGradesList(gradesBySemester, allCourses) { // Use semester grouping as-is groupedCourses = gradesBySemester; } - + // Sort groups (semesters or years) const sortedGroups = Object.keys(groupedCourses).sort().reverse(); - + sortedGroups.forEach(groupKey => { const courses = groupedCourses[groupKey]; - + // Calculate GWA for this group (semester or year) const groupGWA = calculateGroupGWA(courses); - + const semesterGroup = document.createElement('div'); semesterGroup.className = 'semester-group'; - + const header = document.createElement('div'); header.className = 'semester-header'; - + // Build header with group GWA and scholar status let scholarStatus = ''; let scholarClass = ''; @@ -385,7 +391,7 @@ function displayGradesList(gradesBySemester, allCourses) { scholarClass = 'honor-roll'; } } - + header.innerHTML = ` ${sanitizeText(groupKey)} @@ -394,17 +400,17 @@ function displayGradesList(gradesBySemester, allCourses) { `; semesterGroup.appendChild(header); - + courses.forEach(course => { const courseId = String(course.id || `${course.courseCode}-${course.termId}`); const isExcluded = excludedCourses.has(courseId); const gradeStr = (course.grade || '').toString().toUpperCase().trim(); const isNonNumeric = course.isNonNumeric || ['S', 'U', 'INC', 'DRP', 'W', 'P', 'DFG'].includes(gradeStr); - + const item = document.createElement('div'); item.className = 'course-item' + (isExcluded ? ' excluded' : '') + (isNonNumeric ? ' non-numeric' : ''); item.dataset.courseId = courseId; - + let gradeDisplay, gradeClass; if (isNonNumeric) { gradeDisplay = gradeStr; @@ -423,14 +429,14 @@ function displayGradesList(gradesBySemester, allCourses) { else if (grade <= 2.00) gradeClass = 'good'; else if (grade >= 5.00) gradeClass = 'failed'; } - + // Don't show exclude button for non-numeric grades (they don't affect GWA anyway) const excludeButton = isNonNumeric ? '' : ` `; - + item.innerHTML = `
${sanitizeText(course.courseCode || course.code)}
@@ -442,13 +448,13 @@ function displayGradesList(gradesBySemester, allCourses) { ${gradeDisplay}
`; - + semesterGroup.appendChild(item); }); - + listEl.appendChild(semesterGroup); }); - + // Add event listeners to toggle buttons listEl.querySelectorAll('.exclude-toggle').forEach(btn => { btn.addEventListener('click', (e) => { @@ -463,25 +469,25 @@ function displayGradesList(gradesBySemester, allCourses) { function calculateGroupGWA(courses) { let totalWeightedGrade = 0; let totalUnits = 0; - + courses.forEach(course => { const courseId = String(course.id || `${course.courseCode}-${course.termId}`); - + // Skip excluded courses if (excludedCourses.has(courseId)) { return; } - + const grade = parseFloat(course.grade); if (isNaN(grade) || grade < 1.0 || grade > 5.0) return; - + const units = course.units || 0; if (units === 0) return; - + totalWeightedGrade += grade * units; totalUnits += units; }); - + const gwa = totalUnits > 0 ? totalWeightedGrade / totalUnits : 0; return { gwa, totalUnits }; } @@ -490,10 +496,10 @@ function displayRemainingCourses(completedCourses) { const listEl = document.getElementById('remainingList'); const trackInfoEl = document.getElementById('trackInfo'); listEl.innerHTML = ''; - + // Get current curriculum const curriculum = getCurrentCurriculum(); - + // Check if program has curriculum data if (!curriculum.available) { listEl.innerHTML = ` @@ -505,10 +511,10 @@ function displayRemainingCourses(completedCourses) { trackInfoEl.innerHTML = ''; return; } - + // Detect track based on completed courses const detectedTrack = detectTrack(completedCourses); - + // Update track info display and radio buttons if (curriculum.tracks) { if (detectedTrack) { @@ -521,7 +527,7 @@ function displayRemainingCourses(completedCourses) { trackInfoEl.className = 'track-info not-detected'; trackInfoEl.innerHTML = `⚠ Track not yet detected. Defaulting to SP Track. Select your track below:`; } - + // Set the radio button to match current track const trackRadio = document.querySelector(`input[name="track"][value="${currentTrack}"]`); if (trackRadio) { @@ -530,29 +536,29 @@ function displayRemainingCourses(completedCourses) { } else { trackInfoEl.innerHTML = ''; } - + // Get free elective units based on current track const freeElectiveUnitsTotal = getFreeElectiveUnits(currentTrack); - + // Get completed course codes (normalized) const completedCodes = new Set(completedCourses.map(c => c.code.toUpperCase().trim())); - + // Filter remaining required courses from curriculum const remaining = (curriculum.majorCourses || []).filter(course => { const code = course.code.toUpperCase().trim(); return !completedCodes.has(code); }); - + // Calculate remaining units from required courses let remainingUnits = remaining.reduce((sum, c) => sum + c.units, 0); - + // Count completed GE courses (title starts with "(GE)") - const completedGECount = completedCourses.filter(c => + const completedGECount = completedCourses.filter(c => c.title && c.title.trim().startsWith("(GE)") ).length; const geRequired = curriculum.geCoursesRequired || 9; const remainingGESlots = Math.max(0, geRequired - completedGECount); - + // Calculate free elective units used // Free electives = courses that are NOT required AND NOT GE courses const requiredCodesSet = new Set((curriculum.requiredCodes || []).map(c => c.toUpperCase().trim())); @@ -562,10 +568,10 @@ function displayRemainingCourses(completedCourses) { const isGE = c.title && c.title.trim().startsWith("(GE)"); return !isRequired && !isGE; }); - + const freeElectiveUnitsTaken = freeElectives.reduce((sum, c) => sum + c.units, 0); const freeElectiveUnitsRemaining = Math.max(0, freeElectiveUnitsTotal - freeElectiveUnitsTaken); - + // Display remaining required courses remaining.forEach(course => { const item = document.createElement('div'); @@ -577,7 +583,7 @@ function displayRemainingCourses(completedCourses) { `; listEl.appendChild(item); }); - + // Add GE notice if remaining if (remainingGESlots > 0) { const geNotice = document.createElement('div'); @@ -588,7 +594,7 @@ function displayRemainingCourses(completedCourses) { `; listEl.appendChild(geNotice); } - + // Add free elective notice const notice = document.createElement('div'); notice.className = 'free-elective-notice'; @@ -608,7 +614,7 @@ function displayRemainingCourses(completedCourses) { notice.style.color = 'var(--gem-light)'; } listEl.appendChild(notice); - + // Store remaining courses for What If calculation window.remainingCourses = remaining; window.freeElectiveUnitsRemaining = freeElectiveUnitsRemaining; @@ -620,16 +626,16 @@ function displayRemainingCourses(completedCourses) { async function toggleCourseExclusion(courseId, allCourses) { // Ensure courseId is a string for consistent comparison const id = String(courseId); - + if (excludedCourses.has(id)) { excludedCourses.delete(id); } else { excludedCourses.add(id); } - + // Save to storage await chrome.storage.local.set({ excludedCourses: Array.from(excludedCourses) }); - + // Recalculate and redisplay displayGradesData(allCourses); } @@ -640,7 +646,7 @@ function calculateWhatIf() { showResults('error', 'Please select a target honor.'); return; } - + let targetGWA; if (targetRadio.value === 'custom') { targetGWA = parseFloat(document.getElementById('customGWA').value); @@ -651,39 +657,39 @@ function calculateWhatIf() { } else { targetGWA = parseFloat(targetRadio.value); } - + const data = window.gradesData; const remaining = window.remainingCourses || []; const freeElectiveUnits = window.freeElectiveUnitsRemaining || 0; - + // Calculate remaining units (excluding PE/NSTP) let remainingUnits = remaining.reduce((sum, c) => sum + c.units, 0) + freeElectiveUnits; - + // Add GE remaining units (assume 3 units each) const remainingGEUnits = (window.remainingGESlots || 0) * 3; remainingUnits += remainingGEUnits; - + // Current weighted sum const currentWeightedSum = data.gwa * data.totalUnits; - + // Total units after completion const totalUnitsAfter = data.totalUnits + remainingUnits; - + // Check if already achieved if (data.gwa <= targetGWA) { showResults('achieved', targetGWA, data.gwa, data.totalUnits, remainingUnits, data.gwa); return; } - + // Required weighted sum for target GWA const requiredWeightedSum = targetGWA * totalUnitsAfter; - + // Remaining weighted sum needed const remainingWeightedSumNeeded = requiredWeightedSum - currentWeightedSum; - + // Required average grade for remaining courses const requiredAvgGrade = remainingUnits > 0 ? remainingWeightedSumNeeded / remainingUnits : 0; - + // Determine feasibility if (requiredAvgGrade < 1.0) { // Impossible - would need grades better than 1.0 @@ -701,23 +707,23 @@ function showResults(status, targetGWA, currentGWA, unitsCompleted, unitsRemaini const resultsEl = document.getElementById('whatifResults'); const contentEl = document.getElementById('resultsContent'); resultsEl.classList.remove('hidden'); - + const honorNames = { 1.20: 'Summa Cum Laude', 1.45: 'Magna Cum Laude', 1.75: 'Cum Laude', 2.00: 'Honor Roll' }; - + if (status === 'error') { contentEl.innerHTML = `
${targetGWA}
`; return; } - + const targetName = honorNames[targetGWA] || `GWA of ${targetGWA.toFixed(2)}`; - + let statusClass, statusMessage, gradeDisplay; - + switch (status) { case 'achieved': statusClass = 'achieved'; @@ -751,7 +757,7 @@ function showResults(status, targetGWA, currentGWA, unitsCompleted, unitsRemaini gradeDisplay = requiredGrade.toFixed(4); break; } - + contentEl.innerHTML = `
${statusMessage} @@ -789,12 +795,12 @@ function initializeWrapped() { const prevBtn = document.getElementById('wrappedPrev'); const nextBtn = document.getElementById('wrappedNext'); const exportBtn = document.getElementById('wrappedExport'); - + if (prevBtn && nextBtn) { prevBtn.addEventListener('click', () => navigateWrapped(-1)); nextBtn.addEventListener('click', () => navigateWrapped(1)); } - + if (exportBtn) { exportBtn.addEventListener('click', exportWrappedToPNG); } @@ -815,27 +821,27 @@ function generateWrappedData(courses) { specialGrades: [], semesterGWAs: {} // Track GWA per semester }; - + const nonNumericGrades = ['S', 'U', 'INC', 'DRP', 'W', 'P', 'DFG']; const excludedPrefixes = ['NSTP', 'HK', 'PE']; let totalWeighted = 0; - + // Temporary storage for semester calculations const semesterData = {}; - + courses.forEach(course => { const courseCode = course.courseCode || ''; const gradeStr = (course.grade || '').toString().toUpperCase().trim(); const semKey = course.term || `${course.academicYear} - ${course.semester}`; - + // Skip excluded prefixes if (excludedPrefixes.some(prefix => courseCode.toUpperCase().startsWith(prefix))) { return; } - + // Track all distinct grades (including special ones) data.distinctGrades.add(gradeStr); - + // Track special grades if (nonNumericGrades.includes(gradeStr)) { if (!data.specialGrades.includes(gradeStr)) { @@ -843,69 +849,69 @@ function generateWrappedData(courses) { } return; } - + const grade = parseFloat(course.grade); const units = course.units || 0; - + if (isNaN(grade) || grade < 1.0 || grade > 5.0 || units === 0) return; - + data.totalCourses++; - + // Track semester GWA data if (!semesterData[semKey]) { semesterData[semKey] = { totalWeighted: 0, totalUnits: 0 }; } semesterData[semKey].totalWeighted += grade * units; semesterData[semKey].totalUnits += units; - + // Track grade distribution const gradeKey = grade.toFixed(2); data.gradeDistribution[gradeKey] = (data.gradeDistribution[gradeKey] || 0) + 1; - + // Track retakes (same course code appearing multiple times) const codeNorm = courseCode.toUpperCase().trim(); data.retakes[codeNorm] = (data.retakes[codeNorm] || 0) + 1; - + // Track fails if (grade === 5.0) { data.fails++; } - + // Track highest grade course if (!data.highestGradeCourse || grade < data.highestGradeCourse.grade) { data.highestGradeCourse = { code: courseCode, title: course.courseTitle, grade }; } - + // Track lowest grade course (excluding 5.0) if (grade < 5.0) { if (!data.lowestGradeCourse || grade > data.lowestGradeCourse.grade) { data.lowestGradeCourse = { code: courseCode, title: course.courseTitle, grade }; } } - + totalWeighted += grade * units; data.totalUnits += units; }); - + // Find most retaked course let maxRetakes = 0; Object.entries(data.retakes).forEach(([code, count]) => { if (count > maxRetakes) { maxRetakes = count; const courseInfo = courses.find(c => c.courseCode.toUpperCase() === code); - data.mostRetakedCourse = { - code, - title: courseInfo?.courseTitle || code, - count + data.mostRetakedCourse = { + code, + title: courseInfo?.courseTitle || code, + count }; } }); - + // Only set if actually retaken if (maxRetakes <= 1) data.mostRetakedCourse = null; - + data.gwa = data.totalUnits > 0 ? totalWeighted / data.totalUnits : 0; - + // Calculate semester GWAs Object.entries(semesterData).forEach(([sem, semData]) => { if (semData.totalUnits > 0) { @@ -915,7 +921,7 @@ function generateWrappedData(courses) { }; } }); - + // Find best and "needs improvement" semesters let bestSem = null, worstSem = null; Object.entries(data.semesterGWAs).forEach(([sem, semInfo]) => { @@ -928,38 +934,42 @@ function generateWrappedData(courses) { }); data.bestSemester = bestSem; data.needsWorkSemester = worstSem; - + return data; } function generateWrappedPanels(data) { const panels = []; - - // Panel 1: GWA Overview + + // Panel 1: Current Semester stats + panels.push(generateCurrentSemesterPanel(data)) + + // Panel 2: Overall GWA Overview panels.push(generateGWAPanel(data)); - - // Panel 2: Best/Toughest Semester + + + // Panel 3: Best/Toughest Semester panels.push(generateSemesterPanel(data)); - - // Panel 3: Grade Collector + + // Panel 4: Grade Collector panels.push(generateCollectorPanel(data)); - - // Panel 4: Perseverance (Fails) + + // Panel 5: Perseverance (Fails) panels.push(generatePerseverancePanel(data)); - - // Panel 5: Course Highlights + + // Panel 6: Course Highlights panels.push(generateHighlightsPanel(data)); - - // Panel 6: Graduation Progress + + // Panel 7: Graduation Progress panels.push(generateProgressPanel(data)); - + return panels; } function generateGWAPanel(data) { const gwa = data.gwa; let emoji, title, message; - + if (gwa === 0) { emoji = "📚"; title = "LOADING..."; @@ -993,7 +1003,7 @@ function generateGWAPanel(data) { title = "COMEBACK ARC"; message = "Mababa man ngayon, pwede pa 'yan i-improve. Marami nang naka-recover from this. Kaya mo 'yan."; } - + return `
${emoji}
${title}
@@ -1003,9 +1013,70 @@ function generateGWAPanel(data) { `; } +/** + * @param {import("./types").WrappedData} data + */ +function generateCurrentSemesterPanel(data) { + const numSemesters = Object.keys(data.semesterGWAs).length; + + if (numSemesters === 0) { + return ` +
📅
+
SEMESTER STATS
+
--
+
No semester data yet
+
Check back once grades are in!
+ `; + } + + const latestTermKey = Object.keys(data.semesterGWAs)[ + Object.keys(data.semesterGWAs).length - 1 + ]; + const latestTermData = data.semesterGWAs[latestTermKey]; + + // alert(latestTermData.gwa) + + let emoji, title, message; + + if (latestTermData.gwa <= 1.45) { + emoji = "⭐⭐⭐"; + title = "UNIVERSITY SCHOLAR"; + message = "Congrats, binasic mo 'tong sem nato 🥳"; + } else if (latestTermData.gwa <= 1.75) { + emoji = "⭐⭐"; + title = "COLLEGE SCHOLAR"; + message = "Sheesh, napakaangas!!"; + } else if (latestTermData.gwa <= 2.00) { + emoji = "⭐"; + title = "HONOR ROLLZ 🛼"; + message = "One of the hardworking people... Keep it up!!!1"; + } else if (latestTermData.gwa <= 2.50) { + emoji = "🌠"; + title = "PASANG-ALAM"; + message = "Passing is passing! May mga sem na mabigat talaga, wag ka maguilty. Nasa UP ka pa rin."; + } else if (latestTermData.gwa <= 3.00) { + emoji = "🌙"; + title = "SURVIVAL MODE"; + message = "Congrats for barely surviving. Hope for the best para sa next sem!!"; + } else { + emoji = "🌑"; + title = "COMEBACK ARC"; + message = "Mababa man ngayon, pwede pa 'yan i-improve. Marami nang naka-recover from this. Kaya mo 'yan."; + } + + return ` +
${emoji}
+
${title}
+
${latestTermData.gwa > 0 ? latestTermData.gwa.toFixed(4) : '--'}
+
GWA for ${latestTermKey}
+
${message}
+ `; + +} + function generateSemesterPanel(data) { const numSemesters = Object.keys(data.semesterGWAs).length; - + if (numSemesters === 0) { return `
📅
@@ -1015,10 +1086,10 @@ function generateSemesterPanel(data) {
Check back once grades are in!
`; } - + let content = `
📅
SEMESTER STATS
`; - + // Best semester if (data.bestSemester) { const best = data.bestSemester; @@ -1028,7 +1099,7 @@ function generateSemesterPanel(data) { else if (best.gwa <= 1.75) reaction = 'Solid sem'; else if (best.gwa <= 2.00) reaction = 'Good one'; else reaction = 'Best so far'; - + content += `
🏆 Best Semester
@@ -1037,9 +1108,9 @@ function generateSemesterPanel(data) {
`; } - + // "Needs work" semester (only if different from best) - if (data.needsWorkSemester && data.bestSemester && + if (data.needsWorkSemester && data.bestSemester && data.needsWorkSemester.name !== data.bestSemester.name) { const tough = data.needsWorkSemester; let reaction = ''; @@ -1047,7 +1118,7 @@ function generateSemesterPanel(data) { else if (tough.gwa >= 2.5) reaction = 'Challenging'; else if (tough.gwa >= 2.0) reaction = 'Tough load'; else reaction = 'Room to grow'; - + content += `
📈 Toughest Semester
@@ -1056,7 +1127,7 @@ function generateSemesterPanel(data) {
`; } - + // Overall message const semCount = Object.keys(data.semesterGWAs).length; let overallMsg = ''; @@ -1065,9 +1136,9 @@ function generateSemesterPanel(data) { else if (semCount >= 4) overallMsg = `${semCount} sems in — you're halfway through!`; else if (semCount >= 2) overallMsg = `${semCount} semesters down, many more to go.`; else overallMsg = `Just getting started. Enjoy the ride!`; - + content += `
${overallMsg}
`; - + return content; } @@ -1075,9 +1146,9 @@ function generateCollectorPanel(data) { const numericGrades = Object.keys(data.gradeDistribution).length; const specialCount = data.specialGrades.length; const totalDistinct = data.distinctGrades.size; - + let emoji, title, message; - + // Check for "mega collector" status (many distinct grades including special ones) if (totalDistinct >= 10) { emoji = "🎰"; @@ -1100,7 +1171,7 @@ function generateCollectorPanel(data) { title = "STEADY GRADES"; message = "You stick to a range. Predictable in a good way — you know what you're doing."; } - + // Build grade badges let badgesHtml = '
'; const sortedGrades = Array.from(data.distinctGrades).sort(); @@ -1109,7 +1180,7 @@ function generateCollectorPanel(data) { badgesHtml += `${sanitizeText(grade)}`; }); badgesHtml += '
'; - + return `
${emoji}
${title}
@@ -1123,7 +1194,7 @@ function generateCollectorPanel(data) { function generatePerseverancePanel(data) { const fails = data.fails; let emoji, title, message; - + if (fails === 0) { emoji = "🏅"; title = "CLEAN RECORD"; @@ -1145,7 +1216,7 @@ function generatePerseverancePanel(data) { title = "SURVIVOR"; message = `${fails} 5.0s and still pushing through? That takes real grit. Respect.`; } - + return `
${emoji}
${title}
@@ -1158,7 +1229,7 @@ function generatePerseverancePanel(data) { function generateHighlightsPanel(data) { let content = `
🫴🫴
YOUR HIGHLIGHTS
`; - + if (data.highestGradeCourse) { content += `
@@ -1168,7 +1239,7 @@ function generateHighlightsPanel(data) {
`; } - + if (data.lowestGradeCourse && data.lowestGradeCourse.grade < 5.0) { content += `
@@ -1178,7 +1249,7 @@ function generateHighlightsPanel(data) {
`; } - + if (data.mostRetakedCourse && data.mostRetakedCourse.count > 1) { content += `
@@ -1188,13 +1259,13 @@ function generateHighlightsPanel(data) {
`; } - + if (!data.highestGradeCourse && !data.lowestGradeCourse) { content += `
Complete some subjects to see your highlights!
`; } - + content += `
Every subject is part of your journey
`; - + return content; } @@ -1203,9 +1274,9 @@ function generateProgressPanel(data) { const completed = data.totalUnits; const remaining = Math.max(0, totalRequired - completed); const percentage = Math.min(100, Math.round((completed / totalRequired) * 100)); - + let emoji, title, message; - + if (percentage >= 100) { emoji = "🎓"; title = "GRADUATION READY"; @@ -1231,7 +1302,7 @@ function generateProgressPanel(data) { title = "JUST STARTING"; message = `${remaining} units to complete. Welcome to the journey — it's a marathon, not a sprint.`; } - + return `
${emoji}
${title}
@@ -1253,12 +1324,12 @@ function updateWrappedDisplay() { const progressEl = document.getElementById('wrappedProgress'); const prevBtn = document.getElementById('wrappedPrev'); const nextBtn = document.getElementById('wrappedNext'); - + if (!panelEl || !wrappedPanels.length) return; - + panelEl.innerHTML = wrappedPanels[wrappedCurrentPanel]; progressEl.textContent = `${wrappedCurrentPanel + 1} / ${wrappedPanels.length}`; - + prevBtn.disabled = wrappedCurrentPanel === 0; nextBtn.disabled = wrappedCurrentPanel === wrappedPanels.length - 1; } @@ -1275,11 +1346,11 @@ function navigateWrapped(direction) { async function exportWrappedToPNG() { const panel = document.getElementById('wrappedPanel'); if (!panel) return; - + // Create a canvas - Instagram story dimensions (1080x1920) but scaled down const canvas = document.createElement('canvas'); const ctx = canvas.getContext('2d'); - + // Compact story dimensions const scale = 2; const width = 360; @@ -1287,10 +1358,10 @@ async function exportWrappedToPNG() { canvas.width = width * scale; canvas.height = height * scale; ctx.scale(scale, scale); - + // Transparent background - don't fill anything ctx.clearRect(0, 0, width, height); - + // Get panel content const emoji = panel.querySelector('.panel-emoji')?.textContent || ''; const title = panel.querySelector('.panel-title')?.textContent || ''; @@ -1298,24 +1369,24 @@ async function exportWrappedToPNG() { const subtitle = panel.querySelector('.panel-subtitle')?.textContent || ''; const highlightCourses = panel.querySelectorAll('.highlight-course'); const badges = panel.querySelectorAll('.grade-badge'); - + // Calculate content height for centering let contentHeight = 120; // Base height for emoji + title + value if (subtitle) contentHeight += 30; if (badges.length > 0) contentHeight += 50; if (highlightCourses.length > 0) contentHeight += highlightCourses.length * 55; - + // Start Y position to center content let currentY = Math.max(80, (height - contentHeight) / 2 - 40); - + // Draw content ctx.textAlign = 'center'; - + // Strava-like orange colors for export const stravaOrange = '#fc5200'; const stravaOrangeLight = '#ff7a33'; const stravaOrangeDark = '#e04800'; - + // Helper for text with dark outline for contrast on any background function drawTextWithOutline(text, x, y, fillColor, outlineColor = '#000000') { ctx.lineWidth = 4; @@ -1326,13 +1397,13 @@ async function exportWrappedToPNG() { ctx.fillStyle = fillColor; ctx.fillText(text, x, y); } - + // Helper for word wrapping function wrapText(text, maxWidth) { const words = text.split(' '); const lines = []; let currentLine = ''; - + words.forEach(word => { const testLine = currentLine + word + ' '; if (ctx.measureText(testLine).width > maxWidth && currentLine !== '') { @@ -1345,28 +1416,28 @@ async function exportWrappedToPNG() { if (currentLine.trim()) lines.push(currentLine.trim()); return lines; } - + // Header with branding at top ctx.font = '8px "Press Start 2P", monospace'; drawTextWithOutline('Elbi GradeSim', width / 2, 64, stravaOrangeLight); ctx.font = '10px "Press Start 2P", monospace'; drawTextWithOutline('amis.stimmie.dev', width / 2, 80, stravaOrange); - + // Emoji ctx.font = '56px Arial'; drawTextWithOutline(emoji, width / 2, currentY, '#ffffff'); currentY += 50; - + // Title (removed - just showing data, not descriptors) // Skip title, go straight to value - + // Value (the main data point) if (value && value !== '--') { ctx.font = 'bold 28px "Press Start 2P", monospace'; drawTextWithOutline(value, width / 2, currentY + 20, stravaOrange); currentY += 60; } - + // Subtitle (brief context) if (subtitle) { ctx.font = '16px "VT323", monospace'; @@ -1377,20 +1448,20 @@ async function exportWrappedToPNG() { }); currentY += 10; } - + // Grade badges if present (for collector panel) if (badges.length > 0) { currentY += 15; const badgeWidth = 44; const maxPerRow = Math.floor((width - 60) / badgeWidth); const rows = Math.ceil(badges.length / maxPerRow); - + let badgeIndex = 0; for (let row = 0; row < rows; row++) { const badgesInRow = Math.min(maxPerRow, badges.length - badgeIndex); const totalRowWidth = badgesInRow * badgeWidth; let badgeX = (width - totalRowWidth) / 2; - + for (let i = 0; i < badgesInRow && badgeIndex < badges.length; i++) { ctx.fillStyle = 'rgba(0, 0, 0, 0.6)'; ctx.strokeStyle = stravaOrange; @@ -1408,14 +1479,14 @@ async function exportWrappedToPNG() { } currentY += 10; } - + // Highlight courses (for semester stats and highlights panels) if (highlightCourses.length > 0) { currentY += 10; highlightCourses.forEach(course => { const name = course.querySelector('.course-name')?.textContent || ''; const grade = course.querySelector('.course-grade')?.textContent || ''; - + // Compact highlight box with dark background for contrast ctx.fillStyle = 'rgba(0, 0, 0, 0.6)'; ctx.strokeStyle = stravaOrange; @@ -1424,33 +1495,33 @@ async function exportWrappedToPNG() { ctx.roundRect(50, currentY, width - 100, 45, 6); ctx.fill(); ctx.stroke(); - + // Course name ctx.font = '16px "VT323", monospace'; ctx.textAlign = 'center'; drawTextWithOutline(name, width / 2, currentY + 18, stravaOrange); - + // Grade info ctx.font = '14px "VT323", monospace'; drawTextWithOutline(grade, width / 2, currentY + 35, stravaOrangeLight); - + currentY += 55; }); - + // Check if this is the highlights panel (has Best Performance or Room to Grow) - const isHighlightsPanel = title.includes('HIGHLIGHTS') || - Array.from(highlightCourses).some(c => + const isHighlightsPanel = title.includes('HIGHLIGHTS') || + Array.from(highlightCourses).some(c => c.querySelector('.course-label')?.textContent?.includes('Best Performance') || c.querySelector('.course-label')?.textContent?.includes('Room to Grow') ); - + // Check if this is the semester panel (has Best Semester or Toughest Semester) - const isSemesterPanel = title.includes('SEMESTER') || - Array.from(highlightCourses).some(c => + const isSemesterPanel = title.includes('SEMESTER') || + Array.from(highlightCourses).some(c => c.querySelector('.course-label')?.textContent?.includes('Best Semester') || c.querySelector('.course-label')?.textContent?.includes('Toughest Semester') ); - + if (isHighlightsPanel) { ctx.font = '18px "VT323", monospace'; drawTextWithOutline('My lowest and highest grade', width / 2, currentY + 10, stravaOrangeLight); @@ -1459,7 +1530,7 @@ async function exportWrappedToPNG() { drawTextWithOutline('My lowest and highest semester', width / 2, currentY + 10, stravaOrangeLight); } } - + // Download const link = document.createElement('a'); link.download = `elbi-wrapped-${wrappedCurrentPanel + 1}.png`; @@ -1470,4 +1541,4 @@ async function exportWrappedToPNG() { // Initialize wrapped navigation on page load document.addEventListener('DOMContentLoaded', () => { initializeWrapped(); -}); \ No newline at end of file +}); diff --git a/extension/src/types.d.ts b/extension/src/types.d.ts new file mode 100644 index 0000000..2be58ca --- /dev/null +++ b/extension/src/types.d.ts @@ -0,0 +1,48 @@ +export type WrappedData = { + bestSemeseter: SemesterStats | null, + distinctGrades: Set; + fails: number; + gradeDistribution: Object; + gwa: number; + highestGradeCourse: GradeCourseHighlight | null, + lowestGradecourse: GradeCourseHighlight | null, + mostRetakedCourse: GradeCourseHighlight | null, + needsWorkSemester: SemesterStats | null + retakes: Object, + specialGrades: string[], + semesterGWAs: { + // key is a long string, not term id + [key:string]: { + gwa: number, + units: number + } + }, + totalUnits: number; + totalCourses: number, +} + +export type SemesterStats = { + name: string, + gwa: number, + units: number +} + +export type GradeCourseHighlight = { + code: string, + title: string, + grade: number +} + +export type CourseData = { + id: unknown, + courseCode: string, + courseTitle: string, + units: number, + grade: unknown, + section: string, + termId: unknown, + term: string, + semester: string, + academicYear: string, + status: string +} From 2d682cf483eaacfd749768002c267975546a4f79 Mon Sep 17 00:00:00 2001 From: Stimmie <137183321+smmariquit@users.noreply.github.com> Date: Fri, 3 Jul 2026 20:37:16 +0800 Subject: [PATCH 03/32] docs: add AGENTS.md tailored from Room TBA org practices (#45) --- AGENTS.md | 70 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 70 insertions(+) create mode 100644 AGENTS.md diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..d880d10 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,70 @@ +# GradeSim Extension Agent Guide + +**Human developers:** start with [README.md](README.md). + +Org-wide agent defaults: see [room-tba/AGENTS.md](https://github.com/uplbtools/room-tba/blob/main/AGENTS.md). This file tailors that playbook to the **GradeSim browser extension**. + +## Doc map + +| When | Read | +| --- | --- | +| Build all browsers | `extension/build.sh` | +| Per-browser manifests | `extension/manifests/` | +| Landing / marketing site | [uplbtools/gradesim-website](https://github.com/uplbtools/gradesim-website) | +| Store listings | Chrome Web Store, Firefox Add-ons URLs in README | + +## Stack + +- **Platform:** Manifest V3 **browser extension** (vanilla JavaScript — no bundler in root) +- **Targets:** Chrome, Firefox, Opera, Edge (separate manifests) +- **Build:** `extension/build.sh [chrome|firefox|opera|edge|all]` → `extension/dist//` +- **Host permission:** `amis.uplb.edu.ph` — content scripts read AMIS DOM locally +- **No server** — 100% client-side; no `package.json` scripts at repo root + +## Branches and release + +| Branch | Role | +| --- | --- | +| **`staging`** | Default branch — integration | +| **`main`** | Release line for store submissions | + +**Ship flow:** merge to `main`, build with `./build.sh all`, upload zips to each browser store. Store review is human-gated — do not auto-close issues that depend on store approval. + +There is no Vercel/CI deploy — verification is local build + manual load in browser dev mode. + +## Verify before done + +| Step | When | +| --- | --- | +| `cd extension && ./build.sh chrome` | Minimum before any extension change | +| `./build.sh all` | Before store release | +| Manual AMIS | Load unpacked extension; open AMIS schedule page; confirm GWA/simulator still works | +| Manifest diff | When touching permissions — review all files in `extension/manifests/` | + +## Architecture (short) + +``` +extension/ + src/ # Popup, content scripts, curriculum data + manifests/ # Per-browser manifest.json variants + icons/ + build.sh # Copies src + manifest → dist// +website/ # Legacy copy — canonical website is gradesim-website repo +``` + +## Extension rules + +- **Privacy:** Never send grades or student data to a server — local `chrome.storage` / `browser.storage` only +- **AMIS DOM scraping:** AMIS markup changes break the extension — prefer defensive selectors; add fixture HTML tests when possible +- **Multi-browser parity:** A manifest or permission change often needs all four manifest files updated +- **No decorative UI motion** in popup — keep the panel calm and readable + +## Commits + +- Conventional Commits: `fix(content): …`, `feat(curriculum): …`, `chore(build): …` +- Note which browsers were manually tested in the PR body + +## Security + +- Do not add broad `` permissions without maintainer approval +- Never exfiltrate AMIS page content to third-party analytics From 70e6d6e0e02fa11cc4e20314b693a0e90fff761a Mon Sep 17 00:00:00 2001 From: smmariquit Date: Sat, 4 Jul 2026 08:17:54 +0800 Subject: [PATCH 04/32] chore: add MIT license and contributor governance files Adds LICENSE, CONTRIBUTING.md, and CODE_OF_CONDUCT.md for OSS list acceptance. --- CODE_OF_CONDUCT.md | 134 +++++++++++++++++++++++++++++++++++++++++++++ CONTRIBUTING.md | 35 ++++++++++++ LICENSE | 21 +++++++ 3 files changed, 190 insertions(+) create mode 100644 CODE_OF_CONDUCT.md create mode 100644 CONTRIBUTING.md create mode 100644 LICENSE diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..223f124 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,134 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +We as members, contributors, and leaders pledge to make participation in our +community a harassment-free experience for everyone, regardless of age, body +size, visible or invisible disability, ethnicity, sex characteristics, gender +identity and expression, level of experience, education, socio-economic status, +nationality, personal appearance, race, caste, color, religion, or sexual +identity and orientation. + +We pledge to act and interact in ways that contribute to an open, welcoming, +diverse, inclusive, and healthy community. + +## Our Standards + +Examples of behavior that contributes to a positive environment for our +community include: + +- Demonstrating empathy and kindness toward other people +- Being respectful of differing opinions, viewpoints, and experiences +- Giving and gracefully accepting constructive feedback +- Accepting responsibility and apologizing to those affected by our mistakes, + and learning from the experience +- Focusing on what is best not just for us as individuals, but for the overall + community + +Examples of unacceptable behavior include: + +- The use of sexualized language or imagery, and sexual attention or advances of + any kind +- Trolling, insulting or derogatory comments, and personal or political attacks +- Public or private harassment +- Publishing others' private information, such as a physical or email address, + without their explicit permission +- Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Enforcement Responsibilities + +Community leaders are responsible for clarifying and enforcing our standards of +acceptable behavior and will take appropriate and fair corrective action in +response to any behavior that they deem inappropriate, threatening, offensive, +or harmful. + +Community leaders have the right and responsibility to remove, edit, or reject +comments, commits, code, wiki edits, issues, and other contributions that are +not aligned to this Code of Conduct, and will communicate reasons for moderation +decisions when appropriate. + +## Scope + +This Code of Conduct applies within all community spaces, and also applies when +an individual is officially representing the community in public spaces. +Examples of representing our community include using an official email address, +posting via an official social media account, or acting as an appointed +representative at an online or offline event. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported to the community leaders responsible for enforcement at +[Discord](https://discord.uplbtools.me) or by opening a private security +contact issue for maintainers. + +All complaints will be reviewed and investigated promptly and fairly. + +All community leaders are obligated to respect the privacy and security of the +reporter of any incident. + +## Enforcement Guidelines + +Community leaders will follow these Community Impact Guidelines in determining +the consequences for any action they deem in violation of this Code of Conduct: + +### 1. Correction + +**Community Impact**: Use of inappropriate language or other behavior deemed +unprofessional or unwelcome in the community. + +**Consequence**: A private, written warning from community leaders, providing +clarity around the nature of the violation and an explanation of why the +behavior was inappropriate. A public apology may be requested. + +### 2. Warning + +**Community Impact**: A violation through a single incident or series of +actions. + +**Consequence**: A warning with consequences for continued behavior. No +interaction with the people involved, including unsolicited interaction with +those enforcing the Code of Conduct, for a specified period of time. This +includes avoiding interactions in community spaces as well as external channels +like social media. Violating these terms may lead to a temporary or permanent +ban. + +### 3. Temporary Ban + +**Community Impact**: A serious violation of community standards, including +sustained inappropriate behavior. + +**Consequence**: A temporary ban from any sort of interaction or public +communication with the community for a specified period of time. No public or +private interaction with the people involved, including unsolicited interaction +with those enforcing the Code of Conduct, is allowed during this period. +Violating these terms may lead to a permanent ban. + +### 4. Permanent Ban + +**Community Impact**: Demonstrating a pattern of violation of community +standards, including sustained inappropriate behavior, harassment of an +individual, or aggression toward or disparagement of classes of individuals. + +**Consequence**: A permanent ban from any sort of public interaction within the +community. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], +version 2.1, available at +[https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1]. + +Community Impact Guidelines were inspired by +[Mozilla's code of conduct enforcement ladder][mozilla]. + +For answers to common questions about this code of conduct, see the FAQ at +[https://www.contributor-covenant.org/faq][faq]. Translations are available at +[https://www.contributor-covenant.org/translations][translations]. + +[homepage]: https://www.contributor-covenant.org +[v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html +[mozilla]: https://github.com/mozilla/diversity +[faq]: https://www.contributor-covenant.org/faq +[translations]: https://www.contributor-covenant.org/translations diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..6252d0a --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,35 @@ +# Contributing to GradeSim + +Thanks for helping UPLB students track grades and plan for Latin honors. + +## Report issues + +Open a [GitHub issue](https://github.com/uplbtools/gradesim/issues) with: + +- Browser and extension version +- Steps to reproduce +- Expected vs actual grade or GWA output + +You do not need a pull request for typo reports in curriculum data — an issue is enough. + +## Developers + +### Setup + +```sh +cd extension +./build.sh chrome # or firefox, all +``` + +Load `extension/dist//` via your browser’s developer / unpacked extension UI. + +### Pull requests + +1. Branch from **`staging`**. +2. Keep commits focused; use [Conventional Commits](https://www.conventionalcommits.org/) (`fix(extension): …`, `docs: …`). +3. Open a PR to **`staging`** and link any related issue. +4. Do not commit secrets or store listing credentials. + +## Code of conduct + +See [CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md). Be helpful to students; verify grade logic against official UPLB rules when possible. diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..8122745 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 Simonee Ezekiel Mariquit + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. From 2fb2a8f1309a43a365ec91b78e8425e9a226c962 Mon Sep 17 00:00:00 2001 From: smmariquit Date: Sat, 4 Jul 2026 08:20:54 +0800 Subject: [PATCH 05/32] docs: highlight Chrome and Firefox store listings --- CONTRIBUTING.md | 2 +- README.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 6252d0a..e13820e 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -21,7 +21,7 @@ cd extension ./build.sh chrome # or firefox, all ``` -Load `extension/dist//` via your browser’s developer / unpacked extension UI. +Load `extension/dist//` via your browser’s developer / unpacked extension UI, or install from the [Chrome Web Store](https://chromewebstore.google.com/detail/elbi-gradesim-uplb-gwa-ca/mlhklblbhkikcmobmmajckjcbmdinldb) or [Firefox Add-ons](https://addons.mozilla.org/en-US/firefox/addon/elbi-gradesim/). ### Pull requests diff --git a/README.md b/README.md index f83fc36..60e3c55 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Elbi GradeSim -A browser extension for UPLB students to calculate their GWA and simulate grades needed for Latin honors. Available on Chrome, Firefox, Opera, and Edge. +A browser extension for UPLB students to calculate their GWA and simulate grades needed for Latin honors. Available on **Chrome**, **Firefox**, Opera, and Edge. ## Repository Structure From 9cf8956a0bd6b65a4a1d94f83fa77a61849dceaf Mon Sep 17 00:00:00 2001 From: smmariquit Date: Sat, 4 Jul 2026 19:32:15 +0800 Subject: [PATCH 06/32] docs(legal): add privacy policy and terms of service --- TERMS.md | 32 ++++++++++++++++++++++++++++++++ extension/src/popup.html | 8 ++++++++ popup.css | 24 ++++++++++++++++++++++++ popup.html | 5 +++++ 4 files changed, 69 insertions(+) create mode 100644 TERMS.md diff --git a/TERMS.md b/TERMS.md new file mode 100644 index 0000000..9fb1233 --- /dev/null +++ b/TERMS.md @@ -0,0 +1,32 @@ +# Terms of Service for Elbi GradeSim + +**Last Updated: July 4, 2026** + +## Overview + +Elbi GradeSim is a free browser extension for UPLB students. By installing and using the extension, you agree to these terms. + +## Permitted use + +- Use the extension for personal academic planning on the official UPLB AMIS portal. +- Do not reverse-engineer, redistribute, or misrepresent the extension as an official UPLB product. + +## No official affiliation + +GradeSim is an independent student tool. It is **not** affiliated with, endorsed by, or operated by the University of the Philippines Los Baños or the AMIS system. + +## No warranty + +The extension is provided **as is** without warranty. Grade calculations and honor simulations are estimates — always verify against official registrar records. + +## Data + +Your grades data stays on your device. See [PRIVACY_POLICY.md](PRIVACY_POLICY.md) for details. + +## Changes + +We may update these terms. Continued use after the "Last Updated" date constitutes acceptance. + +## Contact + +Questions: open an issue on the project GitHub repository. diff --git a/extension/src/popup.html b/extension/src/popup.html index c9337f3..73ff921 100644 --- a/extension/src/popup.html +++ b/extension/src/popup.html @@ -206,6 +206,14 @@

Remaining Courses

+ + diff --git a/popup.css b/popup.css index 57c44d0..194dbee 100644 --- a/popup.css +++ b/popup.css @@ -1212,4 +1212,28 @@ header h1 { inset -2px -2px 0 var(--gem-dark), inset 2px 2px 0 rgba(255,255,255,0.3), 0 2px 0 var(--gem-dark); +} + +.legal-links { + margin-top: 10px; + font-size: 0.7rem; + font-weight: 400; + border-top: none; + padding: 0; +} + +.legal-links a { + display: inline; + background: none; + border: none; + box-shadow: none; + color: var(--text-muted); + padding: 0 4px; + font-weight: 500; +} + +.legal-links a:hover { + color: var(--text-secondary); + transform: none; + box-shadow: none; } \ No newline at end of file diff --git a/popup.html b/popup.html index f956c76..28901a4 100644 --- a/popup.html +++ b/popup.html @@ -207,6 +207,11 @@

Remaining Courses

From aed89a5ec90b4dec6f5b7829a01ea94f62701492 Mon Sep 17 00:00:00 2001 From: Stimmie <137183321+smmariquit@users.noreply.github.com> Date: Sun, 5 Jul 2026 01:35:04 +0800 Subject: [PATCH 07/32] Add baseline Cursor skills and rules --- .cursor/rules/baseline.mdc | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 .cursor/rules/baseline.mdc diff --git a/.cursor/rules/baseline.mdc b/.cursor/rules/baseline.mdc new file mode 100644 index 0000000..7573dcc --- /dev/null +++ b/.cursor/rules/baseline.mdc @@ -0,0 +1,14 @@ +--- +description: Baseline agent behavior for all smmariquit repos +alwaysApply: true +--- + +# Baseline + +- Read the README and existing code before large changes. Match project style. +- Minimize diff scope. Do not refactor unrelated code. +- Never commit secrets (.env, tokens, keys). Warn if asked to commit them. +- Only create git commits when the user asks. +- Run the project's existing lint/build/test scripts when you change code. +- If `.cursor/skills/` exists in this repo, read relevant skills before implementing. +- Definition of done: changes in the real repo branch, not a scratchpad or uncommitted local-only work. From 3167a8cd341385e1efd5afd9e8d98092e3051248 Mon Sep 17 00:00:00 2001 From: Stimmie <137183321+smmariquit@users.noreply.github.com> Date: Sun, 5 Jul 2026 01:35:06 +0800 Subject: [PATCH 08/32] Add baseline Cursor skills and rules --- .cursor/skills/repo-baseline/SKILL.md | 32 +++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 .cursor/skills/repo-baseline/SKILL.md diff --git a/.cursor/skills/repo-baseline/SKILL.md b/.cursor/skills/repo-baseline/SKILL.md new file mode 100644 index 0000000..b706f4c --- /dev/null +++ b/.cursor/skills/repo-baseline/SKILL.md @@ -0,0 +1,32 @@ +--- +name: repo-baseline +description: Default workflow for smmariquit repositories. Use at the start of any task — read README, match conventions, minimal diffs, no secrets in commits, verify before redoing infra work. Check for stack-specific skills in this repo (.cursor/skills/). +--- + +# Repo baseline + +## Start of task + +1. Read `README.md` (and `CONTRIBUTING.md` if present). +2. List `.cursor/skills/` — load any skill that matches the task. +3. Check `package.json` / `pyproject.toml` / `Cargo.toml` for scripts before inventing commands. + +## While working + +- Reuse existing functions and patterns; don't reimplement parallel utilities. +- Prefer extending current abstractions over new files. +- Comments only for non-obvious logic. + +## Git + +- Commit only when asked. +- No `--no-verify` unless the user explicitly requests it. +- Don't amend pushed commits unless the user asks. + +## Cross-repo infra + +If the task touches Cloudflare, Vercel, or DNS across multiple repos, check `smmariquit/tutorials` for `MIGRATION-HANDOFF.md` and `MIGRATION-STATUS.md` before redoing migration work. + +## MCP vs shell + +For Cloudflare or Vercel tasks: try the enabled MCP tools first (after auth). Fall back to `gh`, `wrangler`, or `curl` if MCP lacks scope or returns auth errors — report which path was used. From 0ce810f2b5b0db61ba9a1335f99ccdcb85458ed5 Mon Sep 17 00:00:00 2001 From: Stimmie <137183321+smmariquit@users.noreply.github.com> Date: Sun, 5 Jul 2026 01:57:01 +0800 Subject: [PATCH 09/32] Add Cursor baseline, ponytail, and caveman rules --- .cursor/rules/ponytail.mdc | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 .cursor/rules/ponytail.mdc diff --git a/.cursor/rules/ponytail.mdc b/.cursor/rules/ponytail.mdc new file mode 100644 index 0000000..db435a7 --- /dev/null +++ b/.cursor/rules/ponytail.mdc @@ -0,0 +1,36 @@ +--- +description: Ponytail, lazy senior dev mode. Always pick the simplest solution that works. +globs: +alwaysApply: true +--- + +# Ponytail, lazy senior dev mode + +You are a lazy senior developer. Lazy means efficient, not careless. The best code is the code never written. + +Before writing any code, stop at the first rung that holds: + +1. Does this need to be built at all? (YAGNI) +2. Does it already exist in this codebase? Reuse the helper, util, or pattern that's already here, don't re-write it. +3. Does the standard library already do this? Use it. +4. Does a native platform feature cover it? Use it. +5. Does an already-installed dependency solve it? Use it. +6. Can this be one line? Make it one line. +7. Only then: write the minimum code that works. + +The ladder runs after you understand the problem, not instead of it: read the task and the code it touches, trace the real flow end to end, then climb. + +Bug fix = root cause, not symptom: a report names a symptom. Grep every caller of the function you touch and fix the shared function once — one guard there is a smaller diff than one per caller, and patching only the path the ticket names leaves a sibling caller still broken. + +Rules: + +- No abstractions that weren't explicitly requested. +- No new dependency if it can be avoided. +- No boilerplate nobody asked for. +- Deletion over addition. Boring over clever. Fewest files possible. +- Shortest working diff wins, but only once you understand the problem. The smallest change in the wrong place isn't lazy, it's a second bug. +- Question complex requests: "Do you actually need X, or does Y cover it?" +- Pick the edge-case-correct option when two stdlib approaches are the same size, lazy means less code, not the flimsier algorithm. +- Mark intentional simplifications with a `ponytail:` comment. If the shortcut has a known ceiling (global lock, O(n²) scan, naive heuristic), the comment names the ceiling and the upgrade path. + +Not lazy about: understanding the problem (read it fully and trace the real flow before picking a rung, a small diff you don't understand is just laziness dressed up as efficiency), input validation at trust boundaries, error handling that prevents data loss, security, accessibility, the calibration real hardware needs (the platform is never the spec ideal, a clock drifts, a sensor reads off), anything explicitly requested. Lazy code without its check is unfinished: non-trivial logic leaves ONE runnable check behind, the smallest thing that fails if the logic breaks (an assert-based demo/self-check or one small test file; no frameworks, no fixtures). Trivial one-liners need no test. From 4d21a2543e0e352ceffc4f589c6a616251c9c1f3 Mon Sep 17 00:00:00 2001 From: Stimmie <137183321+smmariquit@users.noreply.github.com> Date: Sun, 5 Jul 2026 01:57:03 +0800 Subject: [PATCH 10/32] Add Cursor baseline, ponytail, and caveman rules --- .cursor/rules/caveman.mdc | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 .cursor/rules/caveman.mdc diff --git a/.cursor/rules/caveman.mdc b/.cursor/rules/caveman.mdc new file mode 100644 index 0000000..0fec0ca --- /dev/null +++ b/.cursor/rules/caveman.mdc @@ -0,0 +1,20 @@ +--- +description: "Caveman mode — terse communication, 65% fewer output tokens (measured), full technical accuracy" +alwaysApply: true +--- + +Respond terse like smart caveman. All technical substance stay. Only fluff die. + +Rules: +- Drop: articles (a/an/the), filler (just/really/basically), pleasantries, hedging +- Fragments OK. Short synonyms. Technical terms exact. Code unchanged. +- Pattern: [thing] [action] [reason]. [next step]. +- Not: "Sure! I'd be happy to help you with that." +- Yes: "Bug in auth middleware. Fix:" + +Switch level: /caveman lite|full|ultra|wenyan +Stop: "stop caveman" or "normal mode" + +Auto-Clarity: drop caveman for security warnings, irreversible actions, user confused. Resume after. + +Boundaries: code/commits/PRs written normal. From 904f6e14996a89bd61624d1d337ad53ae80333a9 Mon Sep 17 00:00:00 2001 From: Stimmie <137183321+smmariquit@users.noreply.github.com> Date: Sun, 5 Jul 2026 01:57:06 +0800 Subject: [PATCH 11/32] Add Cursor baseline, ponytail, and caveman rules From 228c46fb731fe022846ac56d7587593ec07139bd Mon Sep 17 00:00:00 2001 From: Stimmie <137183321+smmariquit@users.noreply.github.com> Date: Sun, 5 Jul 2026 02:22:00 +0800 Subject: [PATCH 12/32] Add Cursor baseline, ponytail, and caveman rules --- .cursor/rules/no-ai-slop.mdc | 53 ++++++++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 .cursor/rules/no-ai-slop.mdc diff --git a/.cursor/rules/no-ai-slop.mdc b/.cursor/rules/no-ai-slop.mdc new file mode 100644 index 0000000..98b8ff3 --- /dev/null +++ b/.cursor/rules/no-ai-slop.mdc @@ -0,0 +1,53 @@ +--- +description: Louis Rossmann anti-AI-slop prose rules (realrossmanngroup/no_ai_slop_writing_rules). Apply when writing READMEs, docs, PR bodies, comments, blog/MDX — not when writing code. +alwaysApply: true +--- + +# No AI slop (Louis Rossmann rules) + +Source: https://github.com/realrossmanngroup/no_ai_slop_writing_rules + +**Scope:** prose only (README, docs, MDX, PR descriptions, issue text, comments). Code, configs, and commit subjects are exempt unless they contain marketing copy. + +## Voice + +Researcher who opened the device and read the filing. Contempt through precision, not adjectives. Every claim needs a testable detail: amount, date, part number, named source. + +## 24 rules (non-negotiable for prose) + +1. **No em dashes.** Use period, semicolon, comma, or restructure. +2. **No unsourced statistics.** Real number + attribution, or cut it. +3. **No parenthetical clarifications in headings.** +4. **No intensifiers.** Banned: significantly, dramatically, extremely, incredibly, truly, absolutely, literally, remarkably, exceptionally. +5. **No hollow statements.** Every claim ends on a concrete, verifiable detail or gets deleted. +6. **No repeated talking points.** Say it once. +7. **Vary structure.** Three identical section shapes = machine output. +8. **No narrating references.** No "as discussed above" / "as we will see." +9. **No performative urgency** without a concrete deadline or penalty in the same sentence. +10. **No scare quotes** on normal words. +11. **No filler phrases.** Banned: "In today's world", "It's important to note", "When it comes to", "At the end of the day", "In the realm of", "It goes without saying", "This is where X comes in", "Look no further", "Our team of experts." +12. **Never start with "Whether you're."** +13. **Write like a researcher, not a copywriter.** If a sentence works on any generic site unchanged, delete or anchor it to a fact. +14. **No synthetic enthusiasm.** No exclamation-mark cheerleading. +15. **No weasel words.** "May potentially", "helps ensure", "can help to" — commit or cut. +16. **No dramatic headings.** Name what the section contains, not a tease ("Economic impact of shortened lifespans" not "The Hidden Cost of…"). +17. **No fabricated case studies or scenarios.** +18. **No fabricated dates or milestones.** +19. **No fabricated attributions.** No "Senator X argued…" without a real source. +20. **No AI transitions.** Banned: Furthermore, Moreover, Notwithstanding, That being said, At its core, In essence, It is worth noting that, In the landscape of, To put it simply. +21. **No AI verbs.** Banned: delve, leverage, utilize, facilitate, foster, bolster, underscore, unveil, navigate (metaphorical), streamline, endeavour, ascertain, elucidate. Use: explore, use, help, strengthen, highlight, reveal, manage, simplify, try, find out, explain. +22. **No academic AI tells.** Banned: shed light on, pave the way for, a myriad of, a plethora of, paramount, pertaining to, prior to/subsequent to, in light of, with respect to, in terms of, the fact that. +23. **Quote sources accurately.** Long quotes (>~15 words) as indented blocks with attribution. +24. **No research-process narration.** Don't write "could not be located" or list what you failed to find. Omit unsupported facts silently. + +## Self-check before returning prose + +- Scan for em dashes and banned words above. +- Every number real? Every sentence ends on a detail? +- Headings descriptive, not dramatic? +- Read aloud: would you say this to a colleague? + +## Conflicts + +- **Code:** ponytail still wins (minimal diff). +- **Chat:** caveman can compress replies; these rules apply when drafting publishable prose inside the reply. From 096cac42b6e06bbc7ecc24c6922ab02379d3604d Mon Sep 17 00:00:00 2001 From: Stimmie <137183321+smmariquit@users.noreply.github.com> Date: Sun, 5 Jul 2026 02:22:02 +0800 Subject: [PATCH 13/32] Add Cursor baseline, ponytail, and caveman rules --- .cursor/skills/no-ai-slop/SKILL.md | 35 ++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 .cursor/skills/no-ai-slop/SKILL.md diff --git a/.cursor/skills/no-ai-slop/SKILL.md b/.cursor/skills/no-ai-slop/SKILL.md new file mode 100644 index 0000000..1540061 --- /dev/null +++ b/.cursor/skills/no-ai-slop/SKILL.md @@ -0,0 +1,35 @@ +--- +name: no-ai-slop +description: Louis Rossmann anti-AI-slop rules for prose (README, docs, MDX, PRs, blog). From realrossmanngroup/no_ai_slop_writing_rules. Use when writing or editing any user-facing text. Not for code generation. +--- + +# No AI slop (Rossmann) + +Upstream: https://github.com/realrossmanngroup/no_ai_slop_writing_rules + +Read `.cursor/rules/no-ai-slop.mdc` before drafting prose. + +## Quick WRONG → RIGHT + +| Slop | Fix | +|------|-----| +| "This significantly impacted users." | "Apple replaced 11 million batteries in 2018." | +| "Furthermore, the policy was robust." | "The FTC voted 5-0 in July 2021 to enforce repair restrictions." | +| "The Hidden Cost of Repair Bans" | "Economic impact of manufacturer repair restrictions" | +| "It's important to note that…" | Start on the fact. | +| "Not just a tool — a paradigm shift" | State what actually changed, with evidence. | + +## When to apply + +- README, CONTRIBUTING, docs, MDX articles, PR/issue bodies, marketing copy +- Before publishing agent-drafted prose + +## When to skip + +- Source code, types, configs, SQL +- One-line commit messages (unless they're essays) + +## With other rules + +- `eductools` `writing-tone.mdc` and this rule align: specific, cited, no glaze. +- Run a self-check pass after drafting; fix banned patterns before handing text back. From 490029de9b9d2cbbf4f478b97321607e43eadbce Mon Sep 17 00:00:00 2001 From: smmariquit Date: Sun, 5 Jul 2026 03:29:20 +0800 Subject: [PATCH 14/32] Remove AI slop from prose (mechanical deslop pass) --- AGENTS.md | 22 +++++++++++----------- CODE_OF_CONDUCT.md | 10 +++++----- CONTRIBUTING.md | 2 +- README.md | 2 +- TERMS.md | 2 +- extension/README.md | 10 +++++----- 6 files changed, 24 insertions(+), 24 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index d880d10..35ca452 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -15,31 +15,31 @@ Org-wide agent defaults: see [room-tba/AGENTS.md](https://github.com/uplbtools/r ## Stack -- **Platform:** Manifest V3 **browser extension** (vanilla JavaScript — no bundler in root) +- **Platform:** Manifest V3 **browser extension** (vanilla JavaScript: no bundler in root) - **Targets:** Chrome, Firefox, Opera, Edge (separate manifests) - **Build:** `extension/build.sh [chrome|firefox|opera|edge|all]` → `extension/dist//` -- **Host permission:** `amis.uplb.edu.ph` — content scripts read AMIS DOM locally -- **No server** — 100% client-side; no `package.json` scripts at repo root +- **Host permission:** `amis.uplb.edu.ph`: content scripts read AMIS DOM locally +- **No server**: 100% client-side; no `package.json` scripts at repo root ## Branches and release | Branch | Role | | --- | --- | -| **`staging`** | Default branch — integration | +| **`staging`** | Default branch: integration | | **`main`** | Release line for store submissions | -**Ship flow:** merge to `main`, build with `./build.sh all`, upload zips to each browser store. Store review is human-gated — do not auto-close issues that depend on store approval. +**Ship flow:** merge to `main`, build with `./build.sh all`, upload zips to each browser store. Store review is human-gated: do not auto-close issues that depend on store approval. -There is no Vercel/CI deploy — verification is local build + manual load in browser dev mode. +There is no Vercel/CI deploy: verification is local build + manual load in browser dev mode. ## Verify before done | Step | When | | --- | --- | -| `cd extension && ./build.sh chrome` | Minimum before any extension change | +| `cd extension &&./build.sh chrome` | Minimum before any extension change | | `./build.sh all` | Before store release | | Manual AMIS | Load unpacked extension; open AMIS schedule page; confirm GWA/simulator still works | -| Manifest diff | When touching permissions — review all files in `extension/manifests/` | +| Manifest diff | When touching permissions: review all files in `extension/manifests/` | ## Architecture (short) @@ -54,10 +54,10 @@ website/ # Legacy copy — canonical website is gradesim-website repo ## Extension rules -- **Privacy:** Never send grades or student data to a server — local `chrome.storage` / `browser.storage` only -- **AMIS DOM scraping:** AMIS markup changes break the extension — prefer defensive selectors; add fixture HTML tests when possible +- **Privacy:** Never send grades or student data to a server: local `chrome.storage` / `browser.storage` only +- **AMIS DOM scraping:** AMIS markup changes break the extension: prefer defensive selectors; add fixture HTML tests when possible - **Multi-browser parity:** A manifest or permission change often needs all four manifest files updated -- **No decorative UI motion** in popup — keep the panel calm and readable +- **No decorative UI motion** in popup: keep the panel calm and readable ## Commits diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index 223f124..ce5215e 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -21,20 +21,20 @@ community include: - Being respectful of differing opinions, viewpoints, and experiences - Giving and gracefully accepting constructive feedback - Accepting responsibility and apologizing to those affected by our mistakes, - and learning from the experience + and learning from the experience - Focusing on what is best not just for us as individuals, but for the overall - community + community Examples of unacceptable behavior include: - The use of sexualized language or imagery, and sexual attention or advances of - any kind + any kind - Trolling, insulting or derogatory comments, and personal or political attacks - Public or private harassment - Publishing others' private information, such as a physical or email address, - without their explicit permission + without their explicit permission - Other conduct which could reasonably be considered inappropriate in a - professional setting + professional setting ## Enforcement Responsibilities diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index e13820e..d694729 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -10,7 +10,7 @@ Open a [GitHub issue](https://github.com/uplbtools/gradesim/issues) with: - Steps to reproduce - Expected vs actual grade or GWA output -You do not need a pull request for typo reports in curriculum data — an issue is enough. +You do not need a pull request for typo reports in curriculum data: an issue is enough. ## Developers diff --git a/README.md b/README.md index 60e3c55..d30373e 100644 --- a/README.md +++ b/README.md @@ -44,7 +44,7 @@ Output goes to `extension/dist//`. Load the folder in your browser's de - "What If?" simulator for Latin honor targets - Grades overview by semester or year - 30+ UPLB degree programs supported -- 100% local — no data leaves your device +- 100% local: no data leaves your device ## Website diff --git a/TERMS.md b/TERMS.md index 9fb1233..99f5157 100644 --- a/TERMS.md +++ b/TERMS.md @@ -17,7 +17,7 @@ GradeSim is an independent student tool. It is **not** affiliated with, endorsed ## No warranty -The extension is provided **as is** without warranty. Grade calculations and honor simulations are estimates — always verify against official registrar records. +The extension is provided **as is** without warranty. Grade calculations and honor simulations are estimates: always verify against official registrar records. ## Data diff --git a/extension/README.md b/extension/README.md index 196dc1e..9beec54 100644 --- a/extension/README.md +++ b/extension/README.md @@ -25,11 +25,11 @@ A Chrome extension for UPLB students to calculate their GWA and simulate grades ### 4. "What If?" Simulator - Calculate what grades you need in remaining courses to achieve a target GWA - Preset targets: - - **Summa Cum Laude**: GWA ≤ 1.20 - - **Magna Cum Laude**: GWA ≤ 1.45 - - **Cum Laude**: GWA ≤ 1.75 - - **Honor Roll**: GWA ≤ 2.00 - - **Custom**: Enter any target GWA + - **Summa Cum Laude**: GWA ≤ 1.20 + - **Magna Cum Laude**: GWA ≤ 1.45 + - **Cum Laude**: GWA ≤ 1.75 + - **Honor Roll**: GWA ≤ 2.00 + - **Custom**: Enter any target GWA - Shows remaining courses based on your selected curriculum - Calculates required average grade for remaining courses From 3c7e4411e38f67714b92313db1c5c6188b0f9ff1 Mon Sep 17 00:00:00 2001 From: smmariquit Date: Sun, 5 Jul 2026 04:09:44 +0800 Subject: [PATCH 15/32] Remove remaining AI slop (fuck-slop pass) --- AGENTS.md | 10 +++++----- README.md | 28 ++++++++++++++-------------- 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 35ca452..5e7a837 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -45,11 +45,11 @@ There is no Vercel/CI deploy: verification is local build + manual load in brows ``` extension/ - src/ # Popup, content scripts, curriculum data - manifests/ # Per-browser manifest.json variants - icons/ - build.sh # Copies src + manifest → dist// -website/ # Legacy copy — canonical website is gradesim-website repo + src/ # Popup, content scripts, curriculum data + manifests/ # Per-browser manifest.json variants + icons/ + build.sh # Copies src + manifest → dist// +website/ # Legacy copy: canonical website is gradesim-website repo ``` ## Extension rules diff --git a/README.md b/README.md index d30373e..5760473 100644 --- a/README.md +++ b/README.md @@ -6,17 +6,17 @@ A browser extension for UPLB students to calculate their GWA and simulate grades ``` gradesim/ -├── extension/ # Browser extension source -│ ├── src/ # Shared source (popup, content scripts, curriculum data) -│ ├── manifests/ # Per-browser manifest files -│ ├── icons/ # Extension icons -│ └── build.sh # Build script for each browser target -├── website/ # Next.js landing page -│ ├── app/ # Pages and components -│ └── public/ -│ ├── curricula/ # Curriculum images by college (PNG) -│ ├── chrome.svg # Browser logos -│ └── firefox.svg +├── extension/ # Browser extension source +│ ├── src/ # Shared source (popup, content scripts, curriculum data) +│ ├── manifests/ # Per-browser manifest files +│ ├── icons/ # Extension icons +│ └── build.sh # Build script for each browser target +├── website/ # Next.js landing page +│ ├── app/ # Pages and components +│ └── public/ +│ ├── curricula/ # Curriculum images by college (PNG) +│ ├── chrome.svg # Browser logos +│ └── firefox.svg └── README.md ``` @@ -26,9 +26,9 @@ gradesim/ ```bash cd extension -./build.sh all # Build all browsers -./build.sh chrome # Build Chrome only -./build.sh firefox # Build Firefox only +./build.sh all # Build all browsers +./build.sh chrome # Build Chrome only +./build.sh firefox # Build Firefox only ``` Output goes to `extension/dist//`. Load the folder in your browser's developer mode. From 8201e29c4cf3f1af03b103a8c1f097211b373c40 Mon Sep 17 00:00:00 2001 From: smmariquit Date: Mon, 6 Jul 2026 07:52:11 +0800 Subject: [PATCH 16/32] chore: remove stale pre-reorg root duplicates and website leftovers Extension source lives in extension/src; website moved to the gradesim-website repo. Root popup/curriculum/manifest copies were frozen at the initial commit and drifted from canonical source. Co-Authored-By: Claude Fable 5 --- .gitignore | 3 + README.md | 21 +- background.js | 22 - content.js | 158 --- curriculum.js | 2283 ----------------------------------------- curriculum_temp.js | 1423 ------------------------- icons/icon128.svg | 4 - icons/icon16.svg | 4 - icons/icon48.svg | 4 - manifest.chrome.json | 32 - manifest.firefox.json | 44 - manifest.json | 32 - manifest.opera.json | 32 - popup.css | 1239 ---------------------- popup.html | 222 ---- popup.js | 1473 -------------------------- popup.zip | Bin 65025 -> 0 bytes sims.png | Bin 8185 -> 0 bytes 18 files changed, 7 insertions(+), 6989 deletions(-) delete mode 100644 background.js delete mode 100644 content.js delete mode 100644 curriculum.js delete mode 100644 curriculum_temp.js delete mode 100644 icons/icon128.svg delete mode 100644 icons/icon16.svg delete mode 100644 icons/icon48.svg delete mode 100644 manifest.chrome.json delete mode 100644 manifest.firefox.json delete mode 100644 manifest.json delete mode 100644 manifest.opera.json delete mode 100644 popup.css delete mode 100644 popup.html delete mode 100644 popup.js delete mode 100644 popup.zip delete mode 100644 sims.png diff --git a/.gitignore b/.gitignore index 9bbbbf1..e18d293 100644 --- a/.gitignore +++ b/.gitignore @@ -20,3 +20,6 @@ npm-debug.log* # Cloned source (already restructured into extension/) gradesim/ + +# Website lives in separate repo (gradesim-website) +website/ diff --git a/README.md b/README.md index 5760473..4d94678 100644 --- a/README.md +++ b/README.md @@ -7,19 +7,16 @@ A browser extension for UPLB students to calculate their GWA and simulate grades ``` gradesim/ ├── extension/ # Browser extension source -│ ├── src/ # Shared source (popup, content scripts, curriculum data) +│ ├── src/ # Shared source (popup, planner, content scripts, curriculum data) │ ├── manifests/ # Per-browser manifest files │ ├── icons/ # Extension icons │ └── build.sh # Build script for each browser target -├── website/ # Next.js landing page -│ ├── app/ # Pages and components -│ └── public/ -│ ├── curricula/ # Curriculum images by college (PNG) -│ ├── chrome.svg # Browser logos -│ └── firefox.svg +├── scratch/ # Curriculum data pipeline (PDF parsing, prereq graphs) └── README.md ``` +The landing page lives in a separate repo: [gradesim-website](https://github.com/smmariquit/gradesim-website). + ## Extension ### Build @@ -46,16 +43,6 @@ Output goes to `extension/dist//`. Load the folder in your browser's de - 30+ UPLB degree programs supported - 100% local: no data leaves your device -## Website - -```bash -cd website -npm install -npm run dev -``` - -The landing page links to both store listings and displays curriculum data. - ## Privacy All data stays on your device. No servers, no analytics, no tracking. See [PRIVACY_POLICY.md](extension/PRIVACY_POLICY.md). diff --git a/background.js b/background.js deleted file mode 100644 index e6a8278..0000000 --- a/background.js +++ /dev/null @@ -1,22 +0,0 @@ -// Background service worker for Elbi GradeSim -// Privacy: All data is stored locally using chrome.storage.local -// No data is ever sent to external servers - -// Store for grades data (in-memory cache) -let gradesData = null; - -// Listen for messages from content script -chrome.runtime.onMessage.addListener((message, sender, sendResponse) => { - if (message.type === 'GRADES_DATA') { - gradesData = message.data; - // Store locally for popup access - chrome.storage.local.set({ gradesData: message.data }); - } - - if (message.type === 'GET_GRADES_DATA') { - sendResponse({ data: gradesData }); - } - - return true; -}); - diff --git a/content.js b/content.js deleted file mode 100644 index 1f65399..0000000 --- a/content.js +++ /dev/null @@ -1,158 +0,0 @@ -// Content script for Elbi GradeSim -// Runs on amis.uplb.edu.ph pages -// Privacy: Only captures grades data that the user already has access to. -// All data is stored locally on the user's device and never sent to external servers. - -(function() { - 'use strict'; - - // Flag to track if we already have data this session - let hasData = false; - let isFetching = false; - - // Get auth token from localStorage (user's own session) - function getAuthToken() { - const token = localStorage.getItem('auth._token.local'); - if (token) { - return token.startsWith('Bearer ') ? token : `Bearer ${token}`; - } - return null; - } - - // Get session ID (user's own session) - function getSessionId() { - return localStorage.getItem('x-session-id'); - } - - // Intercept fetch requests to capture grades data from AMIS - // This only captures responses that the page already receives - const originalFetch = window.fetch; - window.fetch = async function(...args) { - const response = await originalFetch.apply(this, args); - - // Skip if we're the ones fetching - if (isFetching) { - return response; - } - - // Check if this is the grades API call - const url = args[0]?.toString() || ''; - if (url.includes('api-amis.uplb.edu.ph/api/students/grades')) { - try { - const clonedResponse = response.clone(); - const data = await clonedResponse.json(); - - // Only send if we got valid data (not an error response) - if (data && data.student_grades) { - hasData = true; - chrome.runtime.sendMessage({ - type: 'GRADES_DATA', - data: data - }); - } - } catch (e) { - // Silently ignore parsing errors - } - } - - return response; - }; - - // Also intercept XMLHttpRequest for compatibility - const originalXHROpen = XMLHttpRequest.prototype.open; - const originalXHRSend = XMLHttpRequest.prototype.send; - - XMLHttpRequest.prototype.open = function(method, url, ...rest) { - this._url = url; - return originalXHROpen.apply(this, [method, url, ...rest]); - }; - - XMLHttpRequest.prototype.send = function(...args) { - this.addEventListener('load', function() { - if (this._url && this._url.includes('api-amis.uplb.edu.ph/api/students/grades')) { - try { - const data = JSON.parse(this.responseText); - if (data && data.student_grades) { - hasData = true; - chrome.runtime.sendMessage({ - type: 'GRADES_DATA', - data: data - }); - } - } catch (e) { - // Silently ignore parsing errors - } - } - }); - return originalXHRSend.apply(this, args); - }; - - // Function to fetch grades (only if we don't have data yet) - async function fetchGradesData() { - if (hasData || isFetching) { - return; - } - - const token = getAuthToken(); - const sessionId = getSessionId(); - - if (!token) { - return; // User not logged in - } - - isFetching = true; - - const headers = { - 'Accept': 'application/json', - 'Content-Type': 'application/json', - 'Authorization': token, - }; - - if (sessionId) { - headers['X-Session-Id'] = sessionId; - } - - try { - const response = await originalFetch('https://api-amis.uplb.edu.ph/api/students/grades?summarize=true', { - method: 'GET', - credentials: 'include', - headers: headers - }); - - isFetching = false; - - if (!response.ok) { - return; - } - - const data = await response.json(); - - if (data && data.student_grades) { - hasData = true; - chrome.runtime.sendMessage({ - type: 'GRADES_DATA', - data: data - }); - } - } catch (e) { - isFetching = false; - } - } - - // Listen for messages from popup - chrome.runtime.onMessage.addListener((message, sender, sendResponse) => { - if (message.type === 'FETCH_GRADES') { - if (message.force) { - hasData = false; - isFetching = false; - } - fetchGradesData(); - } - return true; - }); - - // Auto-fetch once on page load (with delay to ensure page is ready) - if (window.location.hostname.includes('amis.uplb.edu.ph')) { - setTimeout(fetchGradesData, 2000); - } -})(); diff --git a/curriculum.js b/curriculum.js deleted file mode 100644 index aa8ea83..0000000 --- a/curriculum.js +++ /dev/null @@ -1,2283 +0,0 @@ -// UPLB Curriculum Database -// Complete undergraduate programs for University of the Philippines Los Baños -// NOTE: This is UPLB only - not UP Diliman, UP Manila, UP Baguio, etc. - -const UPLB_PROGRAMS = { - // ============================================================================ - // COLLEGE OF ARTS AND SCIENCES (CAS) - // ============================================================================ - - "BSCS": { - code: "BSCS", - name: "BS Computer Science", - college: "CAS", - collegeName: "College of Arts and Sciences", - available: true, - totalUnitsRequired: 130, - geCoursesRequired: 9, - tracks: { - thesis: { name: "Thesis Track", code: "CMSC 200", freeElectiveUnits: 15 }, - sp: { name: "Special Problem Track", code: "CMSC 190", freeElectiveUnits: 18 } - }, - defaultTrack: "sp", - majorCourses: [ - { code: "MATH 27", title: "Analytic Geometry and Calculus I", units: 3 }, - { code: "MATH 28", title: "Analytic Geometry and Calculus II", units: 3 }, - { code: "CMSC 12", title: "Foundations of Computer Science", units: 3 }, - { code: "CMSC 21", title: "Fundamentals of Programming", units: 3 }, - { code: "CMSC 22", title: "Object-Oriented Programming", units: 3 }, - { code: "CMSC 23", title: "Mobile Computing", units: 3 }, - { code: "CMSC 56", title: "Discrete Mathematical Structures in Computer Science I", units: 3 }, - { code: "CMSC 57", title: "Discrete Mathematical Structures in Computer Science II", units: 3 }, - { code: "CMSC 100", title: "Web Programming", units: 3 }, - { code: "CMSC 123", title: "Data Structures", units: 3 }, - { code: "CMSC 124", title: "Design and Implementation of Programming Languages", units: 3 }, - { code: "CMSC 125", title: "Operating Systems", units: 3 }, - { code: "CMSC 127", title: "File Processing and Database Systems", units: 3 }, - { code: "CMSC 128", title: "Introduction to Software Engineering", units: 3 }, - { code: "CMSC 130", title: "Logic Design and Digital Computer Circuits", units: 3 }, - { code: "CMSC 131", title: "Introduction to Computer Organization and Machine Level Programming", units: 3 }, - { code: "CMSC 132", title: "Computer Architecture", units: 3 }, - { code: "CMSC 137", title: "Data Communications and Networking", units: 3 }, - { code: "CMSC 141", title: "Automata and Language Theory", units: 3 }, - { code: "CMSC 142", title: "Design and Analysis of Algorithms", units: 3 }, - { code: "CMSC 150", title: "Numerical and Symbolic Computation", units: 3 }, - { code: "CMSC 170", title: "Introduction to Artificial Intelligence", units: 3 }, - { code: "CMSC 173", title: "Human Computer Interaction", units: 3 }, - { code: "CMSC 180", title: "Introduction to Parallel Computing", units: 3 }, - { code: "CMSC 190", title: "Special Problem", units: 3, track: "sp" }, - { code: "CMSC 200", title: "Undergraduate Thesis", units: 6, track: "thesis" }, - { code: "STAT 101", title: "Statistical Methods", units: 3 }, - { code: "ENG 10", title: "Writing of Scientific Papers", units: 3 }, - ], - requiredCodes: [ - "MATH 27", "MATH 28", "CMSC 12", "CMSC 21", "CMSC 22", "CMSC 23", - "CMSC 56", "CMSC 57", "CMSC 100", "CMSC 123", "CMSC 124", "CMSC 125", - "CMSC 127", "CMSC 128", "CMSC 130", "CMSC 131", "CMSC 132", "CMSC 137", - "CMSC 141", "CMSC 142", "CMSC 150", "CMSC 170", "CMSC 173", "CMSC 180", - "STAT 101", "ENG 10" - ] - }, - - "BSMATH": { - code: "BSMATH", - name: "BS Mathematics", - college: "CAS", - collegeName: "College of Arts and Sciences", - available: true, - totalUnitsRequired: 129, - geCoursesRequired: 9, - tracks: { - thesis: { name: "Thesis Track", code: "MATH 200", freeElectiveUnits: 9, majorElectiveUnits: 6 }, - sp: { name: "Special Problem Track", code: "MATH 190", freeElectiveUnits: 9, majorElectiveUnits: 9 } - }, - defaultTrack: "sp", - majorCourses: [ - // First Year - First Semester - { code: "MATH 20", title: "The Landscape of Mathematics", units: 3 }, - { code: "MATH 36", title: "Mathematical Analysis I", units: 5 }, - { code: "BIO 11.1", title: "Investigative Biology Laboratory", units: 2 }, - // First Year - Second Semester - { code: "MATH 37", title: "Mathematical Analysis II", units: 3 }, - { code: "STAT 101", title: "Statistical Methods", units: 3 }, - { code: "CHEM 18", title: "University Chemistry", units: 3 }, - { code: "CHEM 18.1", title: "University Chemistry Laboratory", units: 3 }, - // Second Year - First Semester - { code: "MATH 38", title: "Mathematical Analysis III", units: 5 }, - { code: "MATH 101", title: "Logic and Set Theory", units: 3 }, - { code: "PHYS 51", title: "Elements of Physics", units: 4 }, - { code: "PHYS 51.1", title: "Elements of Physics Laboratory", units: 1 }, - // Second Year - Second Semester - { code: "MATH 103", title: "Elementary Theory of Numbers", units: 3 }, - { code: "MATH 138", title: "Introductory Topology", units: 3 }, - { code: "MATH 141", title: "Introductory Combinatorics", units: 3 }, - { code: "AMAT 152", title: "Fundamentals of Mathematical Computing", units: 3 }, - // Third Year - First Semester - { code: "MATH 111", title: "Modern Algebra I", units: 3 }, - { code: "MATH 155", title: "Advanced Calculus I", units: 3 }, - { code: "MATH 195", title: "Research Methods in Mathematics", units: 3 }, - { code: "COMA 150", title: "Workplace Communication", units: 3 }, - // Third Year - Second Semester - { code: "MATH 120", title: "Linear Algebra", units: 3 }, - { code: "MATH 133", title: "Euclidean and Non-Euclidean Geometry", units: 3 }, - { code: "MATH 151", title: "Ordinary Differential Equations", units: 3 }, - // Third Year - Midyear - { code: "MATH 198", title: "Practicum", units: 3 }, - // Fourth Year - { code: "MATH 135", title: "Projective Geometry", units: 3 }, - { code: "MATH 165", title: "Complex Analysis I", units: 3 }, - { code: "MATH 181", title: "Introduction to Probability Theory", units: 3 }, - { code: "MATH 192", title: "Foundations of Mathematics", units: 3 }, - { code: "MATH 199", title: "Undergraduate Seminar", units: 1 }, - { code: "MATH 190", title: "Special Problems", units: 3, track: "sp" }, - { code: "MATH 200", title: "Undergraduate Thesis", units: 6, track: "thesis" }, - ], - requiredCodes: [ - "MATH 20", "MATH 36", "MATH 37", "MATH 38", - "MATH 101", "MATH 103", "MATH 111", "MATH 120", "MATH 133", "MATH 135", - "MATH 138", "MATH 141", "MATH 151", "MATH 155", "MATH 165", "MATH 181", - "MATH 192", "MATH 195", "MATH 198", "MATH 199", - "AMAT 152", "BIO 11.1", "CHEM 18", "CHEM 18.1", "COMA 150", - "PHYS 51", "PHYS 51.1", "STAT 101" - ] - }, - - "BSAPMATH": { - code: "BSAPMATH", - name: "BS Applied Mathematics", - college: "CAS", - collegeName: "College of Arts and Sciences", - available: true, - totalUnitsRequired: 131, - geCoursesRequired: 9, - tracks: { - thesis: { name: "Thesis Track", code: "AMAT 200", freeElectiveUnits: 9, majorElectiveUnits: 12 }, - sp: { name: "Special Problem Track", code: "AMAT 190", freeElectiveUnits: 12, majorElectiveUnits: 15 } - }, - defaultTrack: "sp", - majorCourses: [ - // First Year - { code: "AMAT 19", title: "Finite Mathematics", units: 3 }, - { code: "MATH 36", title: "Mathematical Analysis I", units: 5 }, - { code: "MATH 37", title: "Mathematical Analysis II", units: 3 }, - { code: "MATH 38", title: "Mathematical Analysis III", units: 5 }, - { code: "BIO 11.1", title: "Investigative Biology Laboratory", units: 2 }, - { code: "CHEM 18", title: "University Chemistry", units: 3 }, - { code: "CHEM 18.1", title: "University Chemistry Laboratory", units: 2 }, - { code: "STAT 101", title: "Statistical Methods", units: 3 }, - // Second Year - { code: "AMAT 110", title: "Mathematical Modeling", units: 3 }, - { code: "AMAT 105", title: "Matrices and Applications", units: 3 }, - { code: "AMAT 112", title: "Introduction to Mathematical Optimization", units: 3 }, - { code: "AMAT 152", title: "Fundamentals of Mathematical Computing", units: 3 }, - { code: "AMAT 170", title: "Theory of Interest", units: 4 }, - { code: "MATH 101", title: "Logic and Set Theory", units: 3 }, - { code: "PHYS 51", title: "Elements of Physics", units: 4 }, - { code: "PHYS 51.1", title: "Elements of Physics Laboratory", units: 1 }, - // Third Year - { code: "MATH 151", title: "Ordinary Differential Equations", units: 3 }, - { code: "MATH 155", title: "Advanced Calculus I", units: 3 }, - { code: "MATH 174", title: "Numerical Analysis I", units: 3 }, - { code: "MATH 175", title: "Numerical Analysis II", units: 3 }, - { code: "MATH 181", title: "Introduction to Probability Theory", units: 3 }, - { code: "MATH 195", title: "Research Methods in Mathematics", units: 3 }, - { code: "COMA 150", title: "Workplace Communication", units: 3 }, - // Fourth Year - { code: "AMAT 198", title: "Practicum", units: 3 }, - { code: "AMAT 199", title: "Undergraduate Seminar", units: 1 }, - { code: "AMAT 190", title: "Special Problems", units: 3, track: "sp" }, - { code: "AMAT 200", title: "Undergraduate Thesis", units: 3, track: "thesis" }, - ], - requiredCodes: [ - "AMAT 19", "MATH 36", "MATH 37", "MATH 38", - "BIO 11.1", "CHEM 18", "CHEM 18.1", "STAT 101", - "AMAT 110", "AMAT 105", "AMAT 112", "AMAT 152", "AMAT 170", - "MATH 101", "PHYS 51", "PHYS 51.1", - "MATH 151", "MATH 155", "MATH 174", "MATH 175", "MATH 181", "MATH 195", - "COMA 150", "AMAT 198", "AMAT 199" - ] - }, - - "BSSTAT": { - code: "BSSTAT", - name: "BS Statistics", - college: "CAS", - collegeName: "College of Arts and Sciences", - available: true, - totalUnitsRequired: 143, - geCoursesRequired: 9, - tracks: null, - defaultTrack: null, - majorCourses: [ - // First Year - { code: "MATH 27", title: "Analytic Geometry and Calculus II", units: 3 }, - { code: "MATH 28", title: "Analytic Geometry and Calculus III", units: 3 }, - { code: "STAT 101", title: "Statistical Methods", units: 3 }, - { code: "STAT 135", title: "Logic and Matrix Algebra in Statistics", units: 3 }, - { code: "STAT 162", title: "Experimental Designs", units: 3 }, - { code: "STAT 182", title: "Statistical Packages", units: 3 }, - { code: "BIO 30", title: "Genetics", units: 3 }, - { code: "CMSC 12", title: "Foundations of Computer Science", units: 3 }, - // Second Year - { code: "CMSC 21", title: "Fundamentals of Programming", units: 3 }, - { code: "CMSC 22", title: "Object-Oriented Programming", units: 3 }, - { code: "MATH 182", title: "Introduction to Stochastic Processes", units: 3 }, - { code: "STAT 144", title: "Introductory Statistical Theory I", units: 3 }, - { code: "STAT 145", title: "Introductory Statistical Theory II", units: 3 }, - { code: "STAT 163", title: "Survey Designs", units: 3 }, - { code: "STAT 168", title: "Response Surface Methodology", units: 3 }, - // Third Year - { code: "CMSC 127", title: "File Processing and Database Systems", units: 3 }, - { code: "STAT 146", title: "Introductory Statistical Theory III", units: 3 }, - { code: "STAT 147", title: "Introduction to the Theory of Nonparametric Statistics", units: 4 }, - { code: "STAT 151", title: "Applied Regression and Correlation", units: 3 }, - { code: "STAT 156", title: "Introductory Time Series Analysis", units: 3 }, - { code: "STAT 173", title: "Survey Operations", units: 3 }, - { code: "STAT 174", title: "Introductory Biostatistics", units: 3 }, - { code: "STAT 175", title: "Analysis of Multivariate Data", units: 3 }, - { code: "STAT 181", title: "Statistical Computing", units: 3 }, - { code: "STAT 192.1", title: "Statistical Consulting Laboratory", units: 1 }, - { code: "STAT 198", title: "Practicum", units: 3 }, - // Fourth Year - { code: "ENG 10", title: "Writing of Scientific Papers", units: 3 }, - { code: "STAT 148", title: "Introductory Bayesian Statistics", units: 3 }, - { code: "STAT 157", title: "Financial Risk Analysis", units: 3 }, - { code: "STAT 165", title: "Categorical Data Analysis", units: 3 }, - { code: "STAT 167", title: "Statistical Quality Control", units: 3 }, - { code: "STAT 183", title: "Introductory Data Analytics", units: 3 }, - { code: "STAT 190", title: "Special Problem", units: 3 }, - { code: "STAT 191", title: "Special Topics", units: 3 }, - { code: "STAT 199", title: "Undergraduate Seminar", units: 1 }, - ], - requiredCodes: [ - "MATH 27", "MATH 28", "STAT 101", "STAT 135", "STAT 162", "STAT 182", - "BIO 30", "CMSC 12", "CMSC 21", "CMSC 22", "CMSC 127", - "MATH 182", "STAT 144", "STAT 145", "STAT 146", "STAT 147", - "STAT 151", "STAT 156", "STAT 163", "STAT 168", "STAT 173", "STAT 174", "STAT 175", - "STAT 181", "STAT 192.1", "STAT 198", - "ENG 10", "STAT 148", "STAT 157", "STAT 165", "STAT 167", "STAT 183", - "STAT 190", "STAT 191", "STAT 199" - ] - }, - - "BSMST": { - code: "BSMST", - name: "BS Mathematics and Science Teaching", - college: "CAS", - collegeName: "College of Arts and Sciences", - available: true, - totalUnitsRequired: 150, - geCoursesRequired: 9, - tracks: null, - defaultTrack: null, - majorCourses: [ - { code: "MATH 27", title: "Analytic Geometry and Calculus I", units: 3 }, - { code: "MATH 28", title: "Analytic Geometry and Calculus II", units: 3 }, - { code: "MATH 36", title: "Mathematical Analysis I", units: 3 }, - { code: "MATH 37", title: "Mathematical Analysis II", units: 3 }, - { code: "MATH 114", title: "Differential Equations", units: 3 }, - { code: "STAT 101", title: "Statistical Methods", units: 3 }, - { code: "EDUC 100", title: "The Teaching Profession", units: 3 }, - { code: "EDUC 101", title: "The Child and Adolescent Learner", units: 3 }, - { code: "EDUC 102", title: "Facilitating Learning", units: 3 }, - { code: "EDUC 103", title: "Curriculum Development", units: 3 }, - { code: "EDUC 110", title: "Principles of Teaching", units: 3 }, - { code: "EDUC 160", title: "Educational Assessment", units: 3 }, - { code: "EDUC 190", title: "Practice Teaching", units: 6, gradeType: "S/U" }, - { code: "ENG 10", title: "Writing of Scientific Papers", units: 3 }, - ], - requiredCodes: [ - "MATH 27", "MATH 28", "MATH 36", "MATH 37", "MATH 114", - "STAT 101", "EDUC 100", "EDUC 101", "EDUC 102", "EDUC 103", "EDUC 110", - "EDUC 160", "EDUC 190", "ENG 10" - ] - }, - - "BSBIO": { - code: "BSBIO", - name: "BS Biology", - college: "CAS", - collegeName: "College of Arts and Sciences", - available: true, - totalUnitsRequired: 143, - geCoursesRequired: 9, - tracks: null, - defaultTrack: null, - majorCourses: [ - // First Year - { code: "BIO 11.1", title: "Investigative Biology Laboratory", units: 2 }, - { code: "BIO 14", title: "Biodiversity", units: 5 }, - { code: "BIO 30", title: "Genetics", units: 3 }, - { code: "CHEM 18", title: "University Chemistry", units: 3 }, - { code: "CHEM 18.1", title: "University Chemistry Laboratory", units: 2 }, - { code: "CHEM 40", title: "Basic Organic Chemistry", units: 4 }, - { code: "CHEM 40.1", title: "Basic Organic Chemistry Laboratory", units: 1 }, - { code: "MATH 25", title: "Fundamental Calculus", units: 3 }, - { code: "MCB 11", title: "Biology and Applications of Microorganisms", units: 3 }, - // Second Year - { code: "BOT 14", title: "University Botany", units: 3 }, - { code: "ZOO 14", title: "University Zoology", units: 3 }, - { code: "CHEM 160", title: "Introductory Biochemistry", units: 3 }, - { code: "CHEM 160.1", title: "Introductory Biochemistry Laboratory", units: 2 }, - { code: "BIO 150", title: "Ecology", units: 4 }, - { code: "PHYS 51", title: "Elements of Physics", units: 4 }, - { code: "PHYS 51.1", title: "Elements of Physics Laboratory", units: 1 }, - { code: "BIO 101", title: "Introductory Molecular Biology", units: 3 }, - { code: "BIO 140", title: "Evolutionary Biology", units: 3 }, - // Third Year - { code: "STAT 164", title: "Statistics for the Biological Sciences", units: 3 }, - { code: "BIO 120", title: "Cell Biology", units: 3 }, - { code: "BIO 142", title: "Principles of Systematic Biology", units: 3 }, - { code: "BIO 195", title: "Research Methods in the Biological Sciences", units: 3 }, - { code: "BIO 127", title: "Developmental Biology", units: 3 }, - { code: "BIO 198", title: "Practicum", units: 3 }, - // Fourth Year - { code: "COMA 150", title: "Workplace Communication", units: 3 }, - { code: "BIO 199", title: "Undergraduate Seminar", units: 1 }, - { code: "BIO 200", title: "Undergraduate Thesis", units: 6 }, - ], - requiredCodes: [ - "BIO 11.1", "BIO 14", "BIO 30", "BIO 101", "BIO 120", "BIO 127", - "BIO 140", "BIO 142", "BIO 150", "BIO 195", "BIO 198", "BIO 199", "BIO 200", - "BOT 14", "ZOO 14", "CHEM 18", "CHEM 18.1", "CHEM 40", "CHEM 40.1", - "CHEM 160", "CHEM 160.1", "COMA 150", "MATH 25", "MCB 11", - "PHYS 51", "PHYS 51.1", "STAT 164" - ] - }, - - "BSCHEM": { - code: "BSCHEM", - name: "BS Chemistry", - college: "CAS", - collegeName: "College of Arts and Sciences", - available: true, - totalUnitsRequired: 142, - geCoursesRequired: 9, - tracks: null, - defaultTrack: null, - majorCourses: [ - // First Year - { code: "CHEM 18", title: "University Chemistry", units: 3 }, - { code: "CHEM 18.1", title: "University Chemistry Laboratory", units: 2 }, - { code: "CHEM 19", title: "Chemical Structure and Properties", units: 3 }, - { code: "CHEM 32", title: "Quantitative Inorganic Analysis", units: 3 }, - { code: "CHEM 32.1", title: "Quantitative Inorganic Analysis Laboratory", units: 2 }, - { code: "MCB 11", title: "Biology and Applications of Microorganisms", units: 3 }, - { code: "MATH 27", title: "Analytic Geometry and Calculus II", units: 3 }, - { code: "MATH 28", title: "Analytic Geometry and Calculus III", units: 3 }, - { code: "PHYS 71", title: "University Physics I", units: 4 }, - { code: "PHYS 71.1", title: "University Physics I Laboratory", units: 1 }, - // Second Year - { code: "CHEM 43", title: "Organic Chemistry I", units: 3 }, - { code: "CHEM 43.1", title: "Organic Chemistry I Laboratory", units: 2 }, - { code: "CHEM 44", title: "Organic Chemistry II", units: 3 }, - { code: "CHEM 44.1", title: "Organic Chemistry II Laboratory", units: 2 }, - { code: "CHEM 111", title: "Physical Chemistry I", units: 3 }, - { code: "CHEM 161A", title: "Biochemistry I", units: 3 }, - { code: "PHYS 72", title: "University Physics II", units: 4 }, - { code: "PHYS 72.1", title: "University Physics II Laboratory", units: 1 }, - { code: "CMSC 12", title: "Foundations of Computer Science", units: 3 }, - { code: "AMAT 152", title: "Fundamentals of Mathematical Computing", units: 3 }, - { code: "STAT 162", title: "Experimental Designs", units: 3 }, - // Third Year - { code: "CHEM 111.1", title: "Physical Chemistry I Laboratory", units: 2 }, - { code: "CHEM 112", title: "Physical Chemistry II", units: 3 }, - { code: "CHEM 112.1", title: "Physical Chemistry II Laboratory", units: 2 }, - { code: "CHEM 115", title: "Physical Chemistry III", units: 3 }, - { code: "CHEM 137", title: "Modern Analytical Chemistry", units: 3 }, - { code: "CHEM 137.1", title: "Modern Analytical Chemistry Laboratory", units: 2 }, - { code: "CHEM 140", title: "Organic Analysis", units: 4 }, - { code: "CHEM 161B", title: "Biochemistry II", units: 3 }, - { code: "CHEM 161.1", title: "General Biochemistry Laboratory", units: 2 }, - { code: "CHEM 180", title: "Environmental Chemistry", units: 3 }, - { code: "CHEM 192", title: "Chemical Information, Literature and Communication", units: 3 }, - { code: "CHEM 198", title: "Practicum", units: 3 }, - // Fourth Year - { code: "CHEM 120", title: "Inorganic Chemistry", units: 3 }, - { code: "CHEM 120.1", title: "Inorganic Chemistry Laboratory", units: 2 }, - { code: "CHEM 131", title: "Technical Analysis I (Foods and Feeds)", units: 4 }, - { code: "CHEM 171", title: "Industrial Chemistry", units: 3 }, - { code: "CHEM 199", title: "Undergraduate Seminar", units: 1 }, - { code: "CHEM 200", title: "Undergraduate Thesis", units: 6 }, - ], - requiredCodes: [ - "CHEM 18", "CHEM 18.1", "CHEM 19", "CHEM 32", "CHEM 32.1", - "CHEM 43", "CHEM 43.1", "CHEM 44", "CHEM 44.1", - "CHEM 111", "CHEM 111.1", "CHEM 112", "CHEM 112.1", "CHEM 115", - "CHEM 120", "CHEM 120.1", "CHEM 131", "CHEM 137", "CHEM 137.1", - "CHEM 140", "CHEM 161A", "CHEM 161B", "CHEM 161.1", - "CHEM 171", "CHEM 180", "CHEM 192", "CHEM 198", "CHEM 199", "CHEM 200", - "AMAT 152", "CMSC 12", "MATH 27", "MATH 28", "MCB 11", - "PHYS 71", "PHYS 71.1", "PHYS 72", "PHYS 72.1", "STAT 162" - ] - }, - - "BSAPPHY": { - code: "BSAPPHY", - name: "BS Applied Physics", - college: "CAS", - collegeName: "College of Arts and Sciences", - available: true, - totalUnitsRequired: 143, - geCoursesRequired: 9, - tracks: null, - defaultTrack: null, - majorCourses: [ - // First Year - { code: "PHYS 101", title: "Newtonian Mechanics", units: 4 }, - { code: "PHYS 102", title: "Electromagnetism", units: 4 }, - { code: "PHYS 111", title: "Mathematical Methods of Physics I", units: 4 }, - { code: "CHEM 18", title: "University Chemistry", units: 3 }, - { code: "CHEM 18.1", title: "University Chemistry Laboratory", units: 2 }, - { code: "MATH 27", title: "Analytic Geometry and Calculus II", units: 3 }, - { code: "MATH 28", title: "Analytic Geometry and Calculus III", units: 3 }, - { code: "COMA 150", title: "Workplace Communication", units: 3 }, - // Second Year - { code: "APHY 10.1", title: "Programming in Physics", units: 1 }, - { code: "APHY 101", title: "Physics in Scientific Instruments", units: 3 }, - { code: "PHYS 103", title: "Mechanical Waves, Optics, and Thermodynamics", units: 4 }, - { code: "PHYS 104", title: "Modern Physics", units: 4 }, - { code: "PHYS 112", title: "Mathematical Methods of Physics II", units: 4 }, - { code: "PHYS 113", title: "Mathematical Methods of Physics III", units: 3 }, - { code: "PHYS 121", title: "Theoretical Mechanics I", units: 3 }, - { code: "PHYS 131", title: "Electromagnetic Theory I", units: 3 }, - // Third Year - { code: "APHY 102", title: "Physics of Electronic Devices", units: 3 }, - { code: "APHY 115", title: "Computational Physics", units: 4 }, - { code: "PHYS 122", title: "Theoretical Mechanics II", units: 3 }, - { code: "PHYS 132", title: "Electromagnetic Theory II", units: 3 }, - { code: "PHYS 141", title: "Quantum Physics I", units: 3 }, - { code: "PHYS 151", title: "Statistical Physics I", units: 3 }, - { code: "PHYS 165", title: "Optical Physics", units: 3 }, - { code: "PHYS 192.1", title: "Experimental Physics I", units: 2 }, - { code: "PHYS 195", title: "Research Methods in Physics", units: 3 }, - { code: "APHY 198", title: "Practicum", units: 3 }, - // Fourth Year - { code: "APHY 191", title: "Special Topics", units: 3 }, - { code: "APHY 199", title: "Undergraduate Seminar", units: 1 }, - { code: "APHY 200", title: "Undergraduate Thesis", units: 6 }, - { code: "PHYS 142", title: "Quantum Physics II", units: 3 }, - ], - requiredCodes: [ - "PHYS 101", "PHYS 102", "PHYS 103", "PHYS 104", - "PHYS 111", "PHYS 112", "PHYS 113", "PHYS 121", "PHYS 122", - "PHYS 131", "PHYS 132", "PHYS 141", "PHYS 142", "PHYS 151", - "PHYS 165", "PHYS 192.1", "PHYS 195", - "APHY 10.1", "APHY 101", "APHY 102", "APHY 115", - "APHY 191", "APHY 198", "APHY 199", "APHY 200", - "CHEM 18", "CHEM 18.1", "COMA 150", "MATH 27", "MATH 28" - ] - }, - - "BACOMM": { - code: "BACOMM", - name: "BA Communication Arts", - college: "CAS", - collegeName: "College of Arts and Sciences", - available: true, - totalUnitsRequired: 145, - geCoursesRequired: 9, - tracks: { - thesis: { name: "Thesis Track", code: "COMA 200", freeElectiveUnits: 6 }, - sp: { name: "Special Problem Track", code: "COMA 190", freeElectiveUnits: 9 } - }, - defaultTrack: "thesis", - majorCourses: [ - // First Year - { code: "COMA 101", title: "Introduction to Communication", units: 3 }, - { code: "COMA 151", title: "Audio-Visual Literacy", units: 3 }, - { code: "ENG 100", title: "Advanced Expository Writing", units: 3 }, - { code: "ENG 113", title: "Advanced Composition", units: 3 }, - { code: "SPCM 11", title: "Fundamentals of Speech Communication", units: 3 }, - { code: "PHLO 12", title: "Logic", units: 3 }, - { code: "AH 11", title: "Western Art History and Appreciation", units: 3 }, - // Second Year - { code: "COMA 10", title: "Introduction to Language", units: 3 }, - { code: "COMA 102", title: "Communication Theories", units: 3 }, - { code: "COMA 103", title: "Introduction to Communication Research", units: 3 }, - { code: "COMA 104", title: "Development Communication", units: 3 }, - { code: "COMA 114", title: "Writing for Development Communication", units: 3 }, - { code: "COMA 152", title: "Production Design", units: 3 }, - { code: "COMA 161", title: "Basic Principles of Radio Production", units: 3 }, - { code: "COMA 170", title: "Principles of Advertising", units: 3 }, - { code: "COMA 175", title: "Public Relations", units: 3 }, - { code: "STAT 101", title: "Statistical Methods", units: 3 }, - { code: "LIT 101", title: "Literary Criticism", units: 3 }, - { code: "SPCM 112", title: "Interpersonal Communication", units: 3 }, - // Third Year - { code: "COMA 105", title: "Communication Ethics", units: 3 }, - { code: "COMA 160", title: "Media and Society", units: 3 }, - { code: "COMA 162", title: "Digital Audio Production", units: 3 }, - { code: "COMA 163", title: "Photography", units: 3 }, - { code: "COMA 164", title: "Video Production", units: 3 }, - { code: "COMA 165", title: "Web Design", units: 3 }, - { code: "COMA 171", title: "Editing for Print", units: 3 }, - { code: "COMA 192", title: "Extension Communication", units: 3 }, - { code: "COMA 197", title: "Practicum Seminar", units: 3 }, - { code: "COMA 198", title: "Practicum", units: 3 }, - // Fourth Year - { code: "COMA 190", title: "Special Problems", units: 3, track: "sp" }, - { code: "COMA 199", title: "Undergraduate Seminar", units: 3 }, - { code: "COMA 200", title: "Undergraduate Thesis", units: 6, track: "thesis" }, - { code: "COMA 200a", title: "Practicum", units: 3 }, - ], - requiredCodes: [ - "COMA 10", "COMA 101", "COMA 102", "COMA 103", "COMA 104", "COMA 105", - "COMA 114", "COMA 151", "COMA 152", "COMA 160", "COMA 161", "COMA 162", - "COMA 163", "COMA 164", "COMA 165", "COMA 170", "COMA 171", "COMA 175", - "COMA 192", "COMA 197", "COMA 198", "COMA 199", "COMA 200a", - "AH 11", "ENG 100", "ENG 113", "LIT 101", "PHLO 12", - "SPCM 11", "SPCM 112", "STAT 101" - ] - }, - - "BASOCIO": { - code: "BASOCIO", - name: "BA Sociology", - college: "CAS", - collegeName: "College of Arts and Sciences", - available: true, - totalUnitsRequired: 136, - geCoursesRequired: 9, - tracks: null, - defaultTrack: null, - majorCourses: [ - // First Year - { code: "SOC 10", title: "Introduction to Sociology", units: 3 }, - { code: "SOC 110", title: "Cultural Anthropology for Sociologists", units: 3 }, - { code: "PSYC 10", title: "Introduction to Psychology", units: 3 }, - { code: "ECON 11", title: "Introduction to Microeconomics", units: 3 }, - // Second Year - { code: "SOC 100", title: "Philippine Social and Cultural History", units: 3 }, - { code: "SOC 112", title: "Classical Sociological Theory", units: 3 }, - { code: "SOC 113", title: "Contemporary Sociological Theory", units: 3 }, - { code: "SOC 120", title: "Family and Society", units: 3 }, - { code: "SOC 130", title: "Social Stratification and Mobility", units: 3 }, - { code: "SOC 140", title: "Social Problems", units: 3 }, - { code: "SOC 170", title: "Sociological Statistics", units: 3 }, - { code: "ECON 12", title: "Introduction to Macroeconomics", units: 3 }, - { code: "PHLO 101", title: "Ethics", units: 3 }, - { code: "POLSC 14", title: "Philippine Politics and Government", units: 3 }, - // Third Year - { code: "SOC 111", title: "Sociology of Knowledge", units: 3 }, - { code: "SOC 150", title: "Political Sociology", units: 3 }, - { code: "SOC 160", title: "Sociology of Development", units: 3 }, - { code: "SOC 161", title: "Rural Sociology", units: 3 }, - { code: "SOC 171", title: "Qualitative Methods of Social Research", units: 3 }, - { code: "SOC 172", title: "Quantitative Methods of Social Research", units: 3 }, - { code: "SOC 180", title: "Demography", units: 3 }, - { code: "SOC 195", title: "Social Movements", units: 3 }, - { code: "SOC 198", title: "Practicum", units: 3 }, - // Fourth Year - { code: "SOC 191", title: "Special Topics", units: 3 }, - { code: "SOC 199", title: "Undergraduate Seminar", units: 3 }, - { code: "SOC 200", title: "Undergraduate Thesis", units: 6 }, - ], - requiredCodes: [ - "SOC 10", "SOC 100", "SOC 110", "SOC 111", "SOC 112", "SOC 113", - "SOC 120", "SOC 130", "SOC 140", "SOC 150", "SOC 160", "SOC 161", - "SOC 170", "SOC 171", "SOC 172", "SOC 180", "SOC 191", "SOC 195", - "SOC 198", "SOC 199", "SOC 200", - "ECON 11", "ECON 12", "PHLO 101", "POLSC 14", "PSYC 10" - ] - }, - - "BAPHILO": { - code: "BAPHILO", - name: "BA Philosophy", - college: "CAS", - collegeName: "College of Arts and Sciences", - available: true, - totalUnitsRequired: 132, - geCoursesRequired: 9, - tracks: { - thesis: { name: "Thesis Track", code: "PHLO 200", freeElectiveUnits: 12 }, - sp: { name: "Special Problem Track", code: "PHLO 190", freeElectiveUnits: 15 } - }, - defaultTrack: "thesis", - majorCourses: [ - // First Year - { code: "PHLO 11", title: "Introduction to Philosophy", units: 3 }, - { code: "PHLO 12", title: "Logic", units: 3 }, - { code: "ENG 100", title: "Advanced Expository Writing", units: 3 }, - // Second Year - { code: "PHLO 101", title: "Ethics", units: 3 }, - { code: "PHLO 104", title: "Political Philosophy", units: 3 }, - { code: "PHLO 110", title: "Ancient Philosophy", units: 3 }, - { code: "PHLO 112", title: "Modern Philosophy", units: 3 }, - { code: "PHLO 115", title: "Philosophy in the Philippines", units: 3 }, - // Third Year - { code: "PHLO 103", title: "Philosophy of Religion", units: 3 }, - { code: "PHLO 105", title: "Aesthetics", units: 3 }, - { code: "PHLO 113", title: "Contemporary Philosophy", units: 3 }, - { code: "PHLO 114", title: "Oriental Philosophy", units: 3 }, - { code: "PHLO 120", title: "Metaphysics", units: 3 }, - { code: "PHLO 121", title: "Epistemology", units: 3 }, - { code: "PHLO 125", title: "Philosophy of Science", units: 3 }, - { code: "PHLO 192", title: "Research Methods in Philosophy", units: 3 }, - // Fourth Year - { code: "PHLO 127", title: "Philosophy of Language", units: 3 }, - { code: "PHLO 190", title: "Special Problems", units: 3, track: "sp" }, - { code: "PHLO 197", title: "Seminar in Philosophy", units: 3 }, - { code: "PHLO 200", title: "Undergraduate Thesis", units: 6, track: "thesis" }, - ], - requiredCodes: [ - "PHLO 11", "PHLO 12", "PHLO 101", "PHLO 103", "PHLO 104", "PHLO 105", - "PHLO 110", "PHLO 112", "PHLO 113", "PHLO 114", "PHLO 115", - "PHLO 120", "PHLO 121", "PHLO 125", "PHLO 127", - "PHLO 192", "PHLO 197", - "ENG 100" - ] - }, - - "AASS": { - code: "AASS", - name: "Associate in Arts in Sports Studies", - college: "CAS", - collegeName: "College of Arts and Sciences", - available: true, - totalUnitsRequired: 78, - geCoursesRequired: 5, - tracks: null, - defaultTrack: null, - majorCourses: [ - // First Year First Semester - { code: "SS 102", title: "Introduction to Sports Studies", units: 3 }, - { code: "PEd 91", title: "Physical Education 91", units: 3 }, - { code: "HK 11", title: "Wellness and Basic Injury Management", units: 2 }, - { code: "HK 12", title: "Basic Human Kinetics Activities", units: 2 }, - // First Year Second Semester - { code: "SS 110", title: "Foundations of Sports Studies", units: 3 }, - { code: "PEd 92", title: "Physical Education 92", units: 3 }, - { code: "SS 103", title: "Sports Science Fundamentals", units: 3 }, - { code: "PEd 151", title: "Physical Education 151", units: 3 }, - { code: "HK 12/13", title: "Basic or Advanced Human Kinetics Activities", units: 2 }, - // First Year Midyear - { code: "KAS 1", title: "Kasaysayan ng Pilipinas/Philippine History", units: 3 }, - // Second Year First Semester - { code: "SS 104", title: "Sports Studies 104", units: 3 }, - { code: "PEd 93", title: "Physical Education 93", units: 3 }, - { code: "SS 130", title: "Sports Studies 130", units: 3 }, - { code: "PEd 174", title: "Physical Education 174", units: 3 }, - { code: "PEd 176", title: "Physical Education 176", units: 3 }, - { code: "MATH 10", title: "Mathematics 10", units: 3 }, - { code: "HK 11/12/13", title: "Human Kinetics Activities", units: 2 }, - // Second Year Second Semester - { code: "SS 120", title: "Sports Studies 120", units: 3 }, - { code: "PI 100", title: "The Life, Works, and Writings of Jose Rizal", units: 3 }, - { code: "PEd 172", title: "Physical Education 172", units: 3 }, - { code: "PEd 100", title: "Physical Education 100", units: 3 }, - { code: "PEd 196", title: "Physical Education 196", units: 3 }, - { code: "PEd 130.1", title: "Physical Education 130.1", units: 3 }, - { code: "HK 11/12/13", title: "Human Kinetics Activities", units: 2 }, - ], - requiredCodes: [ - "SS 102", "SS 103", "SS 104", "SS 110", "SS 120", "SS 130", - "PEd 91", "PEd 92", "PEd 93", "PEd 100", "PEd 130.1", - "PEd 151", "PEd 172", "PEd 174", "PEd 176", "PEd 196", - "HK 11", "HK 12", "HK 12/13", "HK 11/12/13", - "KAS 1", "PI 100", "MATH 10" - ] - }, - - // ============================================================================ - // COLLEGE OF AGRICULTURE AND FOOD SCIENCE (CAFS) - // ============================================================================ - - "BSAGRI": { - code: "BSAGRI", - name: "BS Agriculture", - college: "CAFS", - collegeName: "College of Agriculture and Food Science", - available: true, - totalUnitsRequired: 142, - geCoursesRequired: 9, - tracks: null, - defaultTrack: null, - majorCourses: [ - // Year 1 Sem 1 - { code: "AGRI 11", title: "Introduction to Agricultural Science", units: 1 }, - { code: "AGRI 21", title: "Introduction to Animal Science", units: 3 }, - { code: "AGRI 31", title: "Fundamentals of Crop Science I", units: 3 }, - { code: "CHEM 18", title: "University Chemistry", units: 3 }, - { code: "CHEM 18.1", title: "University Chemistry Laboratory", units: 2 }, - // Year 1 Sem 2 - { code: "AGRI 41", title: "Principles of Crop Protection", units: 3 }, - { code: "AGRI 51", title: "Principles of Soil Science", units: 3 }, - { code: "AGRI 22", title: "Introduction to Livestock and Poultry Production", units: 3 }, - { code: "AGRI 32", title: "Fundamentals of Crop Science II", units: 3 }, - // Year 2 Sem 1 - { code: "AGRI 61", title: "Agricultural Extension Communication", units: 3 }, - { code: "BOT 20", title: "Plant Physiology", units: 3 }, - { code: "AGRI 42", title: "Pest Management", units: 3 }, - { code: "ECON 11", title: "General Economics", units: 3 }, - { code: "MGT 101", title: "Concepts and Dynamics of Management", units: 3 }, - // Year 2 Sem 2 - { code: "ABE 1", title: "Introduction to Agricultural Engineering", units: 3 }, - { code: "CHEM 40", title: "Organic Chemistry I", units: 4 }, - { code: "CHEM 40.1", title: "Organic Chemistry I Laboratory", units: 1 }, - { code: "AGRI 111", title: "Crop Production Systems", units: 3 }, - { code: "AGRI 171", title: "Ethics, Laws and Policies in Agriculture", units: 3 }, - // Year 3 Sem 1 - { code: "BIO 30", title: "Genetics", units: 3 }, - { code: "ABT 10", title: "Introduction to Agricultural Biotechnology", units: 3 }, - { code: "AGRI 121", title: "Farm Business Accounting", units: 3 }, - { code: "STAT 162", title: "Experimental Designs I", units: 3 }, - // Year 3 Sem 2 - { code: "AAE 111", title: "Farm Management", units: 3 }, - { code: "AGRI 195", title: "Research Methods in Agriculture", units: 3 }, - // Year 3 Midyear - { code: "AGRI 198", title: "Practicum/Major Farm Practice", units: 3, gradeType: "S/U" }, - // Year 4 Sem 1 - { code: "AGRI 199", title: "Undergraduate Seminar", units: 1 }, - // Year 4 Sem 2 - { code: "AGRI 200", title: "Undergraduate Thesis", units: 3 }, - ], - requiredCodes: [ - "AGRI 11", "AGRI 21", "AGRI 31", "AGRI 22", "AGRI 32", "AGRI 41", "AGRI 42", - "AGRI 51", "AGRI 61", "AGRI 111", "AGRI 121", "AGRI 171", "AGRI 195", - "AGRI 198", "AGRI 199", "AGRI 200", - "CHEM 18", "CHEM 18.1", "CHEM 40", "CHEM 40.1", "BOT 20", "BIO 30", - "ABE 1", "ABT 10", "AAE 111", "ECON 11", "MGT 101", "STAT 162" - ] - }, - - "BSABIO": { - code: "BSABIO", - name: "BS Agricultural Biotechnology", - college: "CAFS", - collegeName: "College of Agriculture and Food Science", - available: true, - totalUnitsRequired: 142, - geCoursesRequired: 9, - tracks: null, - defaultTrack: null, - majorCourses: [ - // Year 1 Sem 1 - { code: "ABT 11", title: "Introduction to Agricultural Biotechnology", units: 1 }, - { code: "CHEM 18", title: "University Chemistry", units: 3 }, - { code: "CHEM 18.1", title: "University Chemistry Laboratory", units: 2 }, - { code: "AGRI 21", title: "Introduction to Animal Science", units: 3 }, - { code: "AGRI 31", title: "Fundamentals of Crop Science I", units: 3 }, - // Year 1 Sem 2 - { code: "CHEM 40", title: "Basic Organic Chemistry", units: 4 }, - { code: "CHEM 40.1", title: "Basic Organic Chemistry Laboratory", units: 1 }, - { code: "AGRI 22", title: "Introduction to Livestock and Poultry Production", units: 3 }, - { code: "MATH 25", title: "Fundamental Calculus", units: 3 }, - { code: "BIO 30", title: "Genetics", units: 3 }, - // Year 2 Sem 1 - { code: "ABT 101", title: "Fundamentals of Agricultural Biotechnology", units: 3 }, - { code: "CHEM 160", title: "Introductory Biochemistry", units: 3 }, - { code: "AGRI 32", title: "Fundamentals of Crop Science II", units: 3 }, - { code: "MCB 11", title: "Biology and Applications of Microorganisms", units: 3 }, - { code: "AGRI 41", title: "Principles of Crop Protection", units: 3 }, - // Year 2 Sem 2 - { code: "ABT 103", title: "Experimental Techniques in Agricultural Biotechnology I", units: 3 }, - { code: "AGRI 51", title: "Principles of Soil Science", units: 3 }, - { code: "CMSC 12", title: "Foundations of Computer Science", units: 3 }, - { code: "BIO 101", title: "Introductory Molecular Biology", units: 3 }, - { code: "AGRI 42", title: "Pest Management", units: 3 }, - // Year 3 Sem 1 - { code: "ABT 104", title: "Experimental Techniques in Agricultural Biotechnology", units: 3 }, - { code: "STAT 162", title: "Experimental Designs I", units: 3 }, - { code: "AGRI 61", title: "Agricultural Extension Communication", units: 3 }, - { code: "ABME 10", title: "Foundations of Entrepreneurship", units: 3 }, - // Year 3 Sem 2 - { code: "ABT 106", title: "Molecular Markers", units: 3 }, - { code: "ABT 107", title: "Recombinant DNA Technology", units: 3 }, - { code: "ECON 11", title: "General Economics", units: 3 }, - { code: "AGRI 171", title: "Ethics, Laws, and Policies in Agriculture", units: 3 }, - // Year 3 Midyear - { code: "ABT 198", title: "Practicum/Major Farm Practice", units: 3, gradeType: "S/U" }, - // Year 4 Sem 1 - { code: "MGT 101", title: "Concepts and Dynamics of Management", units: 3 }, - { code: "ABT 200", title: "Undergraduate Thesis/Major Farm Practice", units: 3 }, - // Year 4 Sem 2 - { code: "ABT 108", title: "Issues and Regulation of Agricultural Biotechnology", units: 3 }, - { code: "ABT 199", title: "Undergraduate Seminar", units: 1 }, - { code: "AGRI 199", title: "Colloquium in Agriculture", units: 1 }, - { code: "ABT 190", title: "Undergraduate Thesis/Special Problems", units: 3 }, - ], - requiredCodes: [ - "ABT 11", "ABT 101", "ABT 103", "ABT 104", "ABT 106", "ABT 107", "ABT 108", - "ABT 198", "ABT 199", "ABT 200", "ABT 190", - "CHEM 18", "CHEM 18.1", "CHEM 40", "CHEM 40.1", "CHEM 160", - "AGRI 21", "AGRI 22", "AGRI 31", "AGRI 32", "AGRI 41", "AGRI 42", - "AGRI 51", "AGRI 61", "AGRI 171", "AGRI 199", - "MATH 25", "BIO 30", "BIO 101", "MCB 11", "CMSC 12", "STAT 162", - "ABME 10", "ECON 11", "MGT 101" - ] - }, - - "BSFST": { - code: "BSFST", - name: "BS Food Science and Technology", - college: "CAFS", - collegeName: "College of Agriculture and Food Science", - available: true, - totalUnitsRequired: 143, - geCoursesRequired: 9, - tracks: null, - defaultTrack: null, - majorCourses: [ - // Year 1 Sem 1 - { code: "FST 11", title: "Introduction to Food Science and Technology", units: 3 }, - { code: "AGRI 21", title: "Introduction to Animal Science", units: 3 }, - { code: "AGRI 31", title: "Fundamentals of Crop Science I", units: 3 }, - { code: "CHEM 18", title: "University Chemistry", units: 3 }, - { code: "CHEM 18.1", title: "University Chemistry Laboratory", units: 2 }, - // Year 1 Sem 2 - { code: "MCB 11", title: "Biology and Applications of Microorganisms", units: 3 }, - { code: "MATH 27", title: "Analytic Geometry and Calculus II", units: 3 }, - { code: "CHEM 40", title: "Basic Organic Chemistry", units: 4 }, - { code: "CHEM 40.1", title: "Basic Organic Chemistry Laboratory", units: 1 }, - { code: "PHYS 51", title: "Elements of Physics", units: 3 }, - { code: "PHYS 51.1", title: "Elements of Physics Laboratory", units: 2 }, - // Year 2 Sem 1 - { code: "ABME 10", title: "Foundations of Entrepreneurship", units: 3 }, - { code: "CHEM 32", title: "Quantitative Inorganic Analysis", units: 3 }, - { code: "CHEM 32.1", title: "Quantitative Inorganic Analysis Laboratory", units: 2 }, - { code: "STAT 101", title: "Statistical Methods", units: 3 }, - // Year 2 Sem 2 - { code: "MGT 101", title: "Concepts and Dynamics of Management", units: 3 }, - { code: "CHEM 160", title: "Introductory Biochemistry", units: 3 }, - { code: "MCB 180", title: "Food Microbiology", units: 3 }, - { code: "FST 101", title: "Food Chemistry", units: 3 }, - { code: "FST 130", title: "Food Processing I", units: 3 }, - // Year 3 Sem 1 - { code: "FST 102", title: "Food Analysis", units: 4 }, - { code: "FST 131", title: "Food Processing II", units: 3 }, - { code: "FST 140", title: "Food Quality Management", units: 3 }, - { code: "FST 141", title: "Food Engineering", units: 3 }, - { code: "FST 167", title: "Sensory Evaluation", units: 3 }, - // Year 3 Sem 2 - { code: "AGRI 171", title: "Ethics, Laws, and Policies in Agriculture", units: 3 }, - { code: "AGRI 195", title: "Research Methods in Agriculture", units: 3 }, - { code: "FST 111", title: "Food Preservation", units: 3 }, - { code: "FST 151", title: "Food Packaging", units: 3 }, - { code: "FST 200", title: "Undergraduate Thesis", units: 3 }, - // Year 3 Midyear - { code: "FST 198", title: "Practicum", units: 3, gradeType: "S/U" }, - // Year 4 Sem 1 - { code: "FST 165", title: "Food Product Development", units: 3 }, - { code: "FST 170", title: "Food Plant Design", units: 3 }, - { code: "ABME 172", title: "Product Ideation and Creation", units: 3 }, - // Year 4 Sem 2 - { code: "FST 147", title: "Food Safety and Regulation", units: 3 }, - { code: "FST 175", title: "Food Service Management", units: 3 }, - { code: "FST 199", title: "Undergraduate Seminar", units: 1 }, - { code: "AGRI 199", title: "Colloquium in Agriculture", units: 1 }, - ], - requiredCodes: [ - "FST 11", "FST 101", "FST 102", "FST 111", "FST 130", "FST 131", - "FST 140", "FST 141", "FST 147", "FST 151", "FST 165", "FST 167", - "FST 170", "FST 175", "FST 198", "FST 199", "FST 200", - "CHEM 18", "CHEM 18.1", "CHEM 32", "CHEM 32.1", "CHEM 40", "CHEM 40.1", "CHEM 160", - "AGRI 21", "AGRI 31", "AGRI 171", "AGRI 195", "AGRI 199", - "MCB 11", "MCB 180", "MATH 27", "PHYS 51", "PHYS 51.1", - "STAT 101", "MGT 101", "ABME 10", "ABME 172" - ] - }, - - "BSACHEM": { - code: "BSACHEM", - name: "BS Agricultural Chemistry", - college: "CAFS", - collegeName: "College of Agriculture and Food Science", - available: true, - totalUnitsRequired: 197, - geCoursesRequired: 9, - tracks: null, - defaultTrack: null, - majorCourses: [ - // Year 1 Sem 1 - { code: "CHEM 16", title: "General Chemistry I", units: 4 }, - { code: "MATH 27", title: "Analytic Geometry and Calculus I", units: 3 }, - // Year 1 Sem 2 - { code: "CHEM 17", title: "General Chemistry II", units: 4 }, - { code: "MATH 28", title: "Analytic Geometry and Calculus II", units: 3 }, - { code: "PHYSICS 71", title: "Elementary Physics I", units: 4 }, - { code: "AGCHEM 10", title: "General Agricultural Chemistry", units: 3 }, - // Year 2 Sem 1 - { code: "CHEM 26", title: "Analytical Chemistry I", units: 5 }, - { code: "CHEM 40", title: "Organic Chemistry I", units: 5 }, - { code: "PHYSICS 72", title: "Elementary Physics II", units: 4 }, - { code: "MATH 36", title: "Mathematical Analysis I", units: 3 }, - // Year 2 Sem 2 - { code: "CHEM 27", title: "Analytical Chemistry II", units: 4 }, - { code: "CHEM 41", title: "Organic Chemistry II", units: 5 }, - { code: "CHEM 31", title: "Elementary Biochemistry", units: 5 }, - { code: "BIO 30", title: "Cellular and Molecular Biology", units: 3 }, - // Year 3 Sem 1 - { code: "CHEM 160", title: "Physical Chemistry I", units: 4 }, - { code: "AGCHEM 100", title: "Soil Chemistry", units: 4 }, - { code: "AGCHEM 101", title: "Soil Fertility and Plant Nutrition", units: 4 }, - { code: "AGCHEM 120", title: "Food Chemistry", units: 4 }, - // Year 3 Sem 2 - { code: "CHEM 161", title: "Physical Chemistry II", units: 4 }, - { code: "AGCHEM 102", title: "Advanced Soil Chemistry", units: 3 }, - { code: "AGCHEM 110", title: "Plant Biochemistry", units: 4 }, - { code: "AGCHEM 140", title: "Environmental Chemistry", units: 3 }, - { code: "AGCHEM 150", title: "Advanced Instrumental Methods of Analysis", units: 5 }, - // Year 4 Sem 1 - { code: "AGCHEM 111", title: "Principles of Plant Chemical Regulation", units: 3 }, - { code: "AGCHEM 121", title: "Food Analysis", units: 4 }, - { code: "AGCHEM 130", title: "Pesticide Chemistry", units: 4 }, - { code: "AGCHEM 141", title: "Environmental Toxicology", units: 3 }, - // Year 4 Sem 2 - { code: "AGCHEM 122", title: "Industrial Food Chemistry", units: 3 }, - { code: "AGCHEM 151", title: "Radiochemistry", units: 3 }, - { code: "AGCHEM 190", title: "Special Problem", units: 3 }, - // Year 5 Sem 1 - { code: "AGCHEM 200", title: "Undergraduate Thesis", units: 6 }, - { code: "ENG 10", title: "Writing of Scientific Papers", units: 3 }, - // Year 5 Sem 2 (MIDYEAR) - { code: "AGCHEM 191", title: "On-the-Job Training", units: 3, gradeType: "S/U" }, - // Support courses - { code: "STAT 101", title: "Statistical Methods", units: 3 }, - { code: "CMSC 11", title: "Introduction to Computer Science I", units: 3 }, - ], - requiredCodes: [ - "CHEM 16", "CHEM 17", "CHEM 26", "CHEM 27", "CHEM 40", "CHEM 41", "CHEM 31", - "CHEM 160", "CHEM 161", "MATH 27", "MATH 28", "MATH 36", - "PHYSICS 71", "PHYSICS 72", "BIO 30", "STAT 101", "CMSC 11", - "AGCHEM 10", "AGCHEM 100", "AGCHEM 101", "AGCHEM 102", "AGCHEM 110", "AGCHEM 111", - "AGCHEM 120", "AGCHEM 121", "AGCHEM 122", "AGCHEM 130", "AGCHEM 140", "AGCHEM 141", - "AGCHEM 150", "AGCHEM 151", "AGCHEM 190", "AGCHEM 191", "AGCHEM 200", "ENG 10" - ] - }, - - // ============================================================================ - // COLLEGE OF ENGINEERING AND AGRO-INDUSTRIAL TECHNOLOGY (CEAT) - // ============================================================================ - - "BSABE": { - code: "BSABE", - name: "BS Agricultural and Biosystems Engineering", - college: "CEAT", - collegeName: "College of Engineering and Agro-Industrial Technology", - available: true, - totalUnitsRequired: 169, - geCoursesRequired: 9, - tracks: null, - defaultTrack: null, - majorCourses: [ - // Year 1 Sem 1 - { code: "ABE 10", title: "Introduction to Agricultural and Biosystems Engineering", units: 1 }, - { code: "ENSC 10.1", title: "Engineering Graphics Laboratory", units: 2 }, - { code: "CHEM 18", title: "University Chemistry", units: 3 }, - { code: "CHEM 18.1", title: "University Chemistry Laboratory", units: 2 }, - { code: "MATH 27", title: "Analytic Geometry and Calculus II", units: 3 }, - // Year 1 Sem 2 - { code: "ABE 30", title: "Engineering Surveying", units: 3 }, - { code: "ABE 32", title: "Computer-Aided Design", units: 2 }, - { code: "CHEM 40", title: "Basic Organic Chemistry", units: 4 }, - { code: "CHEM 40.1", title: "Basic Organic Chemistry Laboratory", units: 1 }, - { code: "MATH 28", title: "Analytic Geometry and Calculus III", units: 3 }, - { code: "PHYS 51", title: "Elements of Physics", units: 4 }, - { code: "PHYS 51.1", title: "Elements of Physics Laboratory", units: 1 }, - // Year 2 Sem 1 - { code: "ABE 40", title: "Engineering Survey Camp", units: 1, gradeType: "S/U" }, - { code: "ABE 42", title: "Engineering Mechanics", units: 4 }, - { code: "ABE 43", title: "Introduction to Numerical Methods and Computing", units: 3 }, - { code: "ABE 48", title: "Engineering Materials", units: 2 }, - { code: "ENSC 21", title: "Mathematical Methods in Engineering", units: 3 }, - { code: "PHYS 52", title: "Advanced Physics for Engineers", units: 3 }, - { code: "PHYS 52.1", title: "Advanced Physics for Engineers Laboratory", units: 1 }, - { code: "EE 1", title: "Basic Electrical Engineering", units: 3 }, - // Year 2 Sem 2 - { code: "ABE 52", title: "Structural Mechanics", units: 3 }, - { code: "ABE 54", title: "Thermodynamics I", units: 3 }, - { code: "ABE 56", title: "Fluid Mechanics", units: 3 }, - { code: "ABE 57", title: "Engineering Geology", units: 3 }, - { code: "ABE 58", title: "Basic Electronics", units: 3 }, - { code: "ENSC 22", title: "Differential Equations for Engineering", units: 3 }, - { code: "SOIL 51", title: "Fundamentals of Soil Science", units: 3 }, - // Year 3 Sem 1 - { code: "ABE 61", title: "Engineering Hydrology", units: 3 }, - { code: "ABE 62", title: "Hydraulic Engineering", units: 3 }, - { code: "ABE 63", title: "Thermodynamics II", units: 3 }, - { code: "ABE 64", title: "Transport Processes", units: 3 }, - { code: "ABE 66", title: "Instrumentation and Controls", units: 3 }, - // Year 3 Sem 2 - { code: "ABE 72", title: "Drainage and Land Development", units: 3 }, - { code: "ABE 73", title: "Irrigation Engineering", units: 3 }, - { code: "ABE 74", title: "Agricultural Waste Management", units: 3 }, - { code: "ABE 75", title: "Aquacultural Engineering", units: 3 }, - { code: "ABE 76", title: "Refrigeration and Air Conditioning", units: 3 }, - { code: "ABE 67", title: "Structural Engineering", units: 3 }, - { code: "ENG 10", title: "Writing of Scientific Papers", units: 3 }, - // Year 3 Midyear - { code: "ABE 198", title: "Field Practice", units: 3, gradeType: "S/U" }, - // Year 4 Sem 1 - { code: "ABE 81", title: "Agricultural Machinery and Equipment", units: 3 }, - { code: "ABE 82", title: "Postharvest and Food Engineering", units: 3 }, - { code: "ABE 83", title: "Soil and Water Conservation Engineering", units: 3 }, - { code: "ABE 84", title: "Agricultural Structures", units: 3 }, - { code: "ABE 199", title: "Seminar", units: 1 }, - { code: "ABE 200", title: "Undergraduate Thesis/Innovationeering", units: 3 }, - // Year 4 Sem 2 - { code: "ABE 88", title: "Biosystems Engineering", units: 3 }, - { code: "ABE 186", title: "ABE Codes and Standards, and Contracts", units: 2 }, - { code: "ABE 190", title: "Design Project", units: 3 }, - ], - requiredCodes: [ - "ABE 10", "ABE 30", "ABE 32", "ABE 40", "ABE 42", "ABE 43", "ABE 48", - "ABE 52", "ABE 54", "ABE 56", "ABE 57", "ABE 58", - "ABE 61", "ABE 62", "ABE 63", "ABE 64", "ABE 66", "ABE 67", - "ABE 72", "ABE 73", "ABE 74", "ABE 75", "ABE 76", - "ABE 81", "ABE 82", "ABE 83", "ABE 84", "ABE 88", - "ABE 186", "ABE 190", "ABE 198", "ABE 199", "ABE 200", - "ENSC 10.1", "ENSC 21", "ENSC 22", "EE 1", - "CHEM 18", "CHEM 18.1", "CHEM 40", "CHEM 40.1", - "MATH 27", "MATH 28", "PHYS 51", "PHYS 51.1", "PHYS 52", "PHYS 52.1", - "SOIL 51", "ENG 10" - ] - }, - - "BSCHE": { - code: "BSCHE", - name: "BS Chemical Engineering", - college: "CEAT", - collegeName: "College of Engineering and Agro-Industrial Technology", - available: true, - totalUnitsRequired: 163, - geCoursesRequired: 9, - tracks: null, - defaultTrack: null, - majorCourses: [ - // Year 1 Sem 1 - { code: "ChE 10", title: "Introduction to Chemical Engineering Profession", units: 1 }, - { code: "CHEM 18", title: "University Chemistry", units: 3 }, - { code: "CHEM 18.1", title: "University Chemistry Laboratory", units: 2 }, - { code: "MATH 27", title: "Analytic Geometry and Calculus II", units: 3 }, - { code: "PHYS 51", title: "Elements of Physics", units: 4 }, - { code: "PHYS 51.1", title: "Elements of Physics Laboratory", units: 1 }, - { code: "MCB 11", title: "Biology and Applications of Microorganisms", units: 3 }, - // Year 1 Sem 2 - { code: "CHEM 32", title: "Quantitative Inorganic Analysis", units: 3 }, - { code: "CHEM 32.1", title: "Quantitative Inorganic Analysis Laboratory", units: 2 }, - { code: "CHEM 40", title: "Basic Organic Chemistry", units: 4 }, - { code: "CHEM 40.1", title: "Basic Organic Chemistry Laboratory", units: 1 }, - { code: "MATH 28", title: "Analytic Geometry and Calculus III", units: 3 }, - // Year 2 Sem 1 - { code: "ChE 30", title: "Fundamentals of Chemical Engineering", units: 4 }, - { code: "ENSC 10.1", title: "Engineering Graphics Laboratory", units: 2 }, - { code: "EE 1", title: "Basic Electrical Engineering", units: 3 }, - { code: "ENSC 11", title: "Statics of Rigid Bodies", units: 3 }, - { code: "CHEM 111", title: "Physical Chemistry I", units: 3 }, - { code: "CHEM 111.1", title: "Physical Chemistry I Laboratory", units: 3 }, - { code: "CHEM 160", title: "Introductory Biochemistry", units: 3 }, - // Year 2 Sem 2 - { code: "ENSC 26", title: "Computer Applications in Engineering", units: 3 }, - { code: "ChE 32", title: "Industrial Stoichiometry", units: 3 }, - { code: "ENSC 12", title: "Dynamics of Rigid Bodies", units: 3 }, - { code: "ENSC 21", title: "Mathematical Methods in Engineering", units: 3 }, - { code: "CHEM 112", title: "Physical Chemistry II", units: 3 }, - // Year 3 Sem 1 - { code: "ChE 142", title: "Chemical Engineering Thermodynamics I", units: 3 }, - { code: "ChE 147", title: "Application of Fluid Dynamics in Chemical Engineering", units: 3 }, - { code: "ChE 149", title: "Transport Phenomena", units: 3 }, - { code: "ChE 152", title: "Separation Processes", units: 3 }, - { code: "ENSC 13", title: "Strength of Materials", units: 3 }, - { code: "STAT 168", title: "Response Surface Methodology", units: 3 }, - // Year 3 Sem 2 - { code: "ChE 143", title: "Chemical Engineering Thermodynamics II", units: 3 }, - { code: "ChE 145", title: "Chemical Reaction Engineering", units: 3 }, - { code: "ChE 153", title: "Transfer Operations I", units: 3 }, - { code: "ChE 154", title: "Transfer Operations II", units: 3 }, - { code: "ChE 172", title: "Introduction to Biochemical Engineering", units: 3 }, - { code: "ChE 180", title: "Agro-Industrial Waste Management", units: 3 }, - { code: "ENG 10", title: "Writing of Scientific Papers", units: 3 }, - // Year 3 Midyear - { code: "ChE 198", title: "Internship", units: 3, gradeType: "S/U" }, - // Year 4 Sem 1 - { code: "ChE 157.1", title: "Chemical Engineering Unit Operations Laboratory", units: 2 }, - { code: "ChE 191", title: "Special Topics", units: 3 }, - { code: "ChE 192", title: "Chemical Process Equipment Design", units: 3 }, - { code: "ChE 200", title: "Undergraduate Thesis/Innovationeering/EIR", units: 3 }, - // Year 4 Sem 2 - { code: "ChE 170", title: "Instrumentation and Process Dynamics and Control", units: 3 }, - { code: "ChE 185", title: "Chemical Engineering Laws, Ethics, Specifications and Contracts", units: 2 }, - { code: "ChE 193", title: "Plant Design", units: 3 }, - { code: "ChE 199", title: "Plant Inspection and Seminar", units: 1 }, - ], - requiredCodes: [ - "ChE 10", "ChE 30", "ChE 32", "ChE 142", "ChE 143", "ChE 145", "ChE 147", "ChE 149", - "ChE 152", "ChE 153", "ChE 154", "ChE 157.1", "ChE 170", "ChE 172", "ChE 180", - "ChE 185", "ChE 191", "ChE 192", "ChE 193", "ChE 198", "ChE 199", "ChE 200", - "CHEM 18", "CHEM 18.1", "CHEM 32", "CHEM 32.1", "CHEM 40", "CHEM 40.1", - "CHEM 111", "CHEM 111.1", "CHEM 112", "CHEM 160", - "ENSC 10.1", "ENSC 11", "ENSC 12", "ENSC 13", "ENSC 21", "ENSC 26", - "MATH 27", "MATH 28", "PHYS 51", "PHYS 51.1", "MCB 11", "EE 1", "STAT 168", "ENG 10" - ] - }, - - "BSCE": { - code: "BSCE", - name: "BS Civil Engineering", - college: "CEAT", - collegeName: "College of Engineering and Agro-Industrial Technology", - available: true, - totalUnitsRequired: 159, - geCoursesRequired: 9, - tracks: null, - defaultTrack: null, - majorCourses: [ - // Year 1 Sem 1 - { code: "CE 10", title: "Fundamentals of Civil Engineering", units: 1 }, - { code: "ENSC 10.1", title: "Engineering Graphics Laboratory", units: 2 }, - { code: "MATH 27", title: "Analytic Geometry and Calculus II", units: 3 }, - { code: "PHYS 71", title: "University Physics I", units: 4 }, - { code: "PHYS 71.1", title: "University Physics Laboratory", units: 1 }, - // Year 1 Sem 2 - { code: "ABE 48", title: "Fundamentals of Surveying", units: 3 }, - { code: "ENSC 11", title: "Statics of Rigid Bodies", units: 3 }, - { code: "MATH 28", title: "Analytic Geometry and Calculus III", units: 4 }, - { code: "PHYS 72", title: "University Physics II", units: 4 }, - { code: "PHYS 72.1", title: "University Physics II Laboratory", units: 1 }, - // Year 2 Sem 1 - { code: "EE 1", title: "Basic Electrical Engineering", units: 3 }, - { code: "ENSC 12", title: "Dynamics of Rigid Bodies", units: 3 }, - { code: "ENSC 13", title: "Strength of Materials", units: 3 }, - { code: "ENSC 21", title: "Mathematical Methods in Engineering", units: 3 }, - { code: "STAT 101", title: "Statistical Methods", units: 3 }, - // Year 2 Sem 2 - { code: "CHEM 18", title: "University Chemistry", units: 3 }, - { code: "CHEM 18.1", title: "University Chemistry Laboratory", units: 2 }, - { code: "CE 120", title: "Higher Surveying", units: 3 }, - { code: "CE 131", title: "Structural Analysis I", units: 3 }, - { code: "ENSC 16", title: "Fluid Mechanics", units: 3 }, - // Year 3 Sem 1 - { code: "ABE 57", title: "Field Hydrology", units: 3 }, - { code: "CE 121", title: "Transportation Engineering I", units: 3 }, - { code: "CE 132", title: "Structural Analysis I", units: 3 }, - { code: "CE 133", title: "Structural Analysis II", units: 3 }, - { code: "CE 151", title: "Sanitary Engineering I", units: 3 }, - { code: "CE 170", title: "Geotechnical Engineering", units: 3 }, - { code: "FPPS 183", title: "Engineering Economic Analysis", units: 3 }, - // Year 3 Sem 2 - { code: "CE 122", title: "Transportation Engineering II", units: 3 }, - { code: "CE 134", title: "Design of Reinforced Concrete Members", units: 4 }, - { code: "CE 152", title: "Sanitary Engineering II", units: 3 }, - { code: "CE 161", title: "Construction Materials and Testing", units: 3 }, - { code: "CE 163", title: "Civil Engineering Laws, Contracts and Ethics", units: 2 }, - { code: "ENSC 16.1", title: "Fluid Mechanics Laboratory", units: 2 }, - { code: "IE 184", title: "Project Development and Management", units: 3 }, - // Year 3 Midyear - { code: "CE 198", title: "Internship", units: 3, gradeType: "S/U" }, - // Year 4 Sem 1 - { code: "CE 135", title: "Design of Steel Members", units: 3 }, - { code: "CE 137", title: "Structural Dynamics and Earthquake Engineering", units: 3 }, - { code: "CE 141", title: "Hydraulic Engineering", units: 3 }, - { code: "CE 164", title: "Construction Project Planning and Management", units: 4 }, - { code: "CE 171", title: "Foundation Engineering", units: 3 }, - { code: "CE 200", title: "Undergraduate Thesis/Innovationeering", units: 3 }, - // Year 4 Sem 2 - { code: "ABE 67", title: "Irrigation and Drainage Engineering I", units: 3 }, - { code: "CE 197", title: "Civil Engineering Project Integration", units: 3 }, - { code: "CE 199", title: "Undergraduate Seminar", units: 1 }, - { code: "ENG 10", title: "Writing of Scientific Papers", units: 3 }, - { code: "ENSC 26", title: "Computer Applications in Engineering", units: 3 }, - ], - requiredCodes: [ - "CE 10", "CE 120", "CE 121", "CE 122", "CE 131", "CE 132", "CE 133", "CE 134", "CE 135", - "CE 137", "CE 141", "CE 151", "CE 152", "CE 161", "CE 163", "CE 164", "CE 170", "CE 171", - "CE 197", "CE 198", "CE 199", "CE 200", - "ENSC 10.1", "ENSC 11", "ENSC 12", "ENSC 13", "ENSC 16", "ENSC 16.1", "ENSC 21", "ENSC 26", - "MATH 27", "MATH 28", "PHYS 71", "PHYS 71.1", "PHYS 72", "PHYS 72.1", - "CHEM 18", "CHEM 18.1", "ABE 48", "ABE 57", "ABE 67", - "STAT 101", "EE 1", "FPPS 183", "IE 184", "ENG 10" - ] - }, - - "BSEE": { - code: "BSEE", - name: "BS Electrical Engineering", - college: "CEAT", - collegeName: "College of Engineering and Agro-Industrial Technology", - available: true, - totalUnitsRequired: 162, - geCoursesRequired: 9, - tracks: null, - defaultTrack: null, - majorCourses: [ - // Year 1 Sem 1 - { code: "MATH 27", title: "Analytic Geometry and Calculus II", units: 3 }, - { code: "PHYS 71", title: "University Physics I", units: 4 }, - { code: "PHYS 71.1", title: "University Physics I Laboratory", units: 1 }, - { code: "EE 30", title: "Introduction to Electrical Engineering", units: 3 }, - // Year 1 Sem 2 - { code: "MATH 28", title: "Analytic Geometry and Calculus III", units: 3 }, - { code: "PHYS 72", title: "University Physics II", units: 4 }, - { code: "PHYS 72.1", title: "University Physics II Laboratory", units: 1 }, - { code: "ENSC 10.1", title: "Engineering Graphics Laboratory", units: 2 }, - { code: "ENSC 11", title: "Statics of Rigid Bodies", units: 3 }, - // Year 2 Sem 1 - { code: "EE 40", title: "Fundamentals of Electrical Engineering I", units: 4 }, - { code: "EE 45", title: "Fundamentals of Engineering Electromagnetics", units: 3 }, - { code: "ENSC 12", title: "Dynamics of Rigid Bodies", units: 3 }, - { code: "ENSC 14a", title: "Engineering Thermodynamics and Heat Transfer", units: 5 }, - { code: "ENSC 21", title: "Mathematical Methods in Engineering", units: 3 }, - // Year 2 Sem 2 - { code: "EE 50", title: "Fundamentals of Electrical Engineering II", units: 4 }, - { code: "EE 51", title: "Electromechanical Energy Conversion for DC", units: 3 }, - { code: "EE 55", title: "Semiconductor Devices", units: 3 }, - { code: "ENSC 26", title: "Computer Applications in Engineering", units: 3 }, - // Year 3 Sem 1 - { code: "EE 60", title: "Signals and Systems", units: 3 }, - { code: "EE 65", title: "Electronic Circuits", units: 4 }, - { code: "EE 61", title: "Electromechanical Energy Conversion for AC", units: 4 }, - { code: "EE 62", title: "Principles of Power Systems", units: 3 }, - { code: "EE 66", title: "Signals and Noise in Electrical Engineering Networks", units: 3 }, - { code: "ENG 10", title: "Writing of Scientific Papers", units: 3 }, - // Year 3 Sem 2 - { code: "EE 70", title: "Instrumentation Engineering", units: 4 }, - { code: "EE 75", title: "Digital Electronics", units: 4 }, - { code: "EE 71", title: "Analysis of Power Systems", units: 3 }, - { code: "EE 79", title: "Electrical Engineering Law, Ethics, and Contracts", units: 1 }, - { code: "FPPS 183", title: "Engineering Economic Analysis", units: 3 }, - { code: "IE 184", title: "Project Development and Management", units: 3 }, - { code: "EE 199", title: "Undergraduate Seminar", units: 1 }, - // Year 3 Midyear - { code: "EE 198", title: "Internship", units: 3, gradeType: "S/U" }, - // Year 4 Sem 1 - { code: "EE 85", title: "Industrial Electronics", units: 3 }, - { code: "EE 80", title: "Control Systems Analysis", units: 3 }, - { code: "EE 86", title: "Electronic Communication Systems I", units: 3 }, - { code: "EE 81", title: "Maintenance of Electrical Equipment and Devices", units: 3 }, - { code: "EE 200", title: "Undergraduate Thesis/Innovationeering/EIR", units: 3 }, - // Year 4 Sem 2 - { code: "EE 91", title: "Electrical System Design, Planning, and Estimation", units: 4 }, - ], - requiredCodes: [ - "EE 30", "EE 40", "EE 45", "EE 50", "EE 51", "EE 55", "EE 60", "EE 61", "EE 62", - "EE 65", "EE 66", "EE 70", "EE 71", "EE 75", "EE 79", "EE 80", "EE 81", - "EE 85", "EE 86", "EE 91", "EE 198", "EE 199", "EE 200", - "ENSC 10.1", "ENSC 11", "ENSC 12", "ENSC 14a", "ENSC 21", "ENSC 26", - "MATH 27", "MATH 28", "PHYS 71", "PHYS 71.1", "PHYS 72", "PHYS 72.1", - "FPPS 183", "IE 184", "ENG 10" - ] - }, - - "BSIE": { - code: "BSIE", - name: "BS Industrial Engineering", - college: "CEAT", - collegeName: "College of Engineering and Agro-Industrial Technology", - available: true, - totalUnitsRequired: 160, - geCoursesRequired: 9, - tracks: null, - defaultTrack: null, - majorCourses: [ - // Year 1 Sem 1 - { code: "IE 10", title: "Foundations of Industrial Engineering", units: 1 }, - { code: "MATH 27", title: "Analytic Geometry and Calculus II", units: 3 }, - { code: "PHYS 51", title: "Elements of Physics", units: 4 }, - { code: "PHYS 51.1", title: "Elements of Physics Laboratory", units: 1 }, - { code: "CHEM 18", title: "University Chemistry", units: 3 }, - { code: "CHEM 18.1", title: "University Chemistry Laboratory", units: 2 }, - // Year 1 Sem 2 - { code: "ENSC 11", title: "Statics of Rigid Bodies", units: 3 }, - { code: "MATH 28", title: "Analytic Geometry and Calculus III", units: 3 }, - { code: "IE 31", title: "Industrial Organization and Management", units: 3 }, - { code: "IE 21", title: "Industrial Processes", units: 3 }, - { code: "STAT 101", title: "Statistical Methods", units: 3 }, - // Year 2 Sem 1 - { code: "ENSC 21", title: "Mathematical Methods in Engineering", units: 3 }, - { code: "IE 141", title: "Operations Research I", units: 3 }, - { code: "IE 132", title: "Methods of Engineering", units: 5 }, - { code: "IE 125", title: "Industrial Quality Control", units: 5 }, - // Year 2 Sem 2 - { code: "IE 142", title: "Operations Research II", units: 3 }, - { code: "ENSC 12", title: "Dynamics of Rigid Bodies", units: 3 }, - { code: "ENSC 10.1", title: "Engineering Graphics Laboratory", units: 2 }, - { code: "IE 151", title: "Production Systems", units: 3 }, - { code: "IE 150", title: "Systems Evaluation", units: 3 }, - { code: "MGT 111", title: "Principles of Accounting", units: 3 }, - // Year 2 Midyear - { code: "IE 198", title: "Internship", units: 3, gradeType: "S/U" }, - // Year 3 Sem 1 - { code: "ENSC 13", title: "Strength of Materials", units: 3 }, - { code: "IE 143", title: "Stochastic Processes in Industrial Engineering", units: 3 }, - { code: "ABE 43", title: "Engineering Shop", units: 3 }, - { code: "IE 152", title: "Manufacturing Planning and Design", units: 3 }, - { code: "ENSC 26", title: "Computer Applications in Engineering", units: 3 }, - { code: "IE 134", title: "Ergonomics", units: 3 }, - // Year 3 Sem 2 - { code: "ENSC 16", title: "Fluid Mechanics", units: 3 }, - { code: "ENSC 14a", title: "Engineering Thermodynamics and Heat Transfer", units: 5 }, - { code: "IE 144", title: "Systems Simulation", units: 3 }, - { code: "IE 184", title: "Project Development and Management", units: 3 }, - { code: "IE 164", title: "Information Systems I", units: 3 }, - { code: "IE 136", title: "Industrial Safety and Health", units: 3 }, - // Year 4 Sem 1 - { code: "ENG 10", title: "Writing of Scientific Papers", units: 3 }, - { code: "IE 158", title: "Supply Chain Engineering", units: 3 }, - { code: "IE 165", title: "Information Systems II", units: 3 }, - { code: "IE 185", title: "Industrial Systems Design", units: 3 }, - { code: "IE 199", title: "Undergraduate Seminar", units: 1 }, - { code: "IE 200", title: "Thesis/Innovationeering/EIR", units: 3 }, - // Year 4 Sem 2 - { code: "IE 90", title: "Ethics for Industrial Engineers", units: 2 }, - { code: "EE 1", title: "Basic Electrical Engineering", units: 3 }, - ], - requiredCodes: [ - "IE 10", "IE 21", "IE 31", "IE 90", "IE 125", "IE 132", "IE 134", "IE 136", - "IE 141", "IE 142", "IE 143", "IE 144", "IE 150", "IE 151", "IE 152", - "IE 158", "IE 164", "IE 165", "IE 184", "IE 185", "IE 198", "IE 199", "IE 200", - "ENSC 10.1", "ENSC 11", "ENSC 12", "ENSC 13", "ENSC 14a", "ENSC 16", "ENSC 21", "ENSC 26", - "MATH 27", "MATH 28", "PHYS 51", "PHYS 51.1", "CHEM 18", "CHEM 18.1", - "STAT 101", "MGT 111", "ABE 43", "EE 1", "ENG 10" - ] - }, - - "BSME": { - code: "BSME", - name: "BS Mechanical Engineering", - college: "CEAT", - collegeName: "College of Engineering and Agro-Industrial Technology", - available: true, - totalUnitsRequired: 161, - geCoursesRequired: 9, - tracks: null, - defaultTrack: null, - majorCourses: [ - // Year 1 Sem 1 - { code: "ME 10", title: "Introduction to Mechanical Engineering", units: 3 }, - { code: "ENSC 10.1", title: "Engineering Graphics Laboratory", units: 3 }, - { code: "CHEM 18", title: "University Chemistry", units: 3 }, - { code: "CHEM 18.1", title: "University Chemistry Laboratory", units: 2 }, - { code: "MATH 27", title: "Analytic Geometry and Calculus II", units: 3 }, - { code: "PHYS 71", title: "University Physics I", units: 4 }, - { code: "PHYS 71.1", title: "University Physics I Laboratory", units: 1 }, - // Year 1 Sem 2 - { code: "EE 1", title: "Basic Electrical Engineering", units: 3 }, - { code: "ENSC 11", title: "Statics of Rigid Bodies", units: 3 }, - { code: "MATH 28", title: "Analytic Geometry and Calculus III", units: 3 }, - { code: "IE 31", title: "Industrial Organization and Management", units: 3 }, - { code: "ME 43", title: "Engineering Shop", units: 3 }, - // Year 2 Sem 1 - { code: "EE 2", title: "Basic Electronics Engineering", units: 4 }, - { code: "ENSC 12", title: "Dynamics of Rigid Bodies", units: 3 }, - { code: "ENSC 13", title: "Strength of Materials", units: 3 }, - { code: "ENSC 14", title: "Basic Thermodynamics", units: 3 }, - { code: "ME 113", title: "Manufacturing Processes", units: 3 }, - // Year 2 Sem 2 - { code: "EE 3", title: "DC and AC Machinery", units: 4 }, - { code: "ENSC 15", title: "Fundamentals of Heat Transfer", units: 3 }, - { code: "ENSC 16", title: "Fluid Mechanics", units: 3 }, - { code: "ENSC 21", title: "Mathematical Methods in Engineering", units: 3 }, - { code: "ME 45", title: "Analysis of Machine Elements", units: 3 }, - { code: "ME 70", title: "Mechanical Engineering Thermodynamics", units: 3 }, - // Year 3 Sem 1 - { code: "ME 53", title: "Machine Design for Bioproduction Systems I", units: 3 }, - { code: "ME 160", title: "Mechanical Vibrations", units: 3 }, - { code: "ME 171", title: "Fluid Machinery", units: 3 }, - { code: "ME 172", title: "Combustion Engineering", units: 2 }, - { code: "ME 175", title: "Refrigeration Systems", units: 3 }, - // Year 3 Sem 2 - { code: "ENSC 26", title: "Computer Applications in Engineering", units: 3 }, - { code: "ME 154", title: "Design of Machinery", units: 3 }, - { code: "ME 173.1", title: "Mechanical Engineering Laboratory I", units: 1 }, - { code: "ME 176", title: "Air Conditioning and Ventilation Systems", units: 3 }, - { code: "IE 184", title: "Project Development and Management", units: 3 }, - // Year 3 Midyear - { code: "ME 198", title: "Practicum", units: 3, gradeType: "S/U" }, - // Year 4 Sem 1 - { code: "EE 28", title: "Introduction to Control Systems", units: 3 }, - { code: "STAT 101", title: "Statistical Methods", units: 3 }, - { code: "ME 90", title: "Mechanical Engineering Laws, Ethics, Codes and Standards", units: 2 }, - { code: "ME 174.1", title: "Mechanical Engineering Laboratory II", units: 2 }, - { code: "ME 199", title: "Undergraduate Seminar", units: 1 }, - { code: "ME 200", title: "Undergraduate Thesis/Engineering Industry Research", units: 3 }, - // Year 4 Sem 2 - { code: "ME 181", title: "Industrial Plant Engineering and Design", units: 4 }, - { code: "ME 182", title: "Power Plant Engineering and Design", units: 4 }, - ], - requiredCodes: [ - "ME 10", "ME 43", "ME 45", "ME 53", "ME 70", "ME 90", "ME 113", - "ME 154", "ME 160", "ME 171", "ME 172", "ME 173.1", "ME 174.1", - "ME 175", "ME 176", "ME 181", "ME 182", "ME 198", "ME 199", "ME 200", - "ENSC 10.1", "ENSC 11", "ENSC 12", "ENSC 13", "ENSC 14", "ENSC 15", "ENSC 16", "ENSC 21", "ENSC 26", - "EE 1", "EE 2", "EE 3", "EE 28", - "MATH 27", "MATH 28", "PHYS 71", "PHYS 71.1", - "CHEM 18", "CHEM 18.1", "STAT 101", "IE 31", "IE 184" - ] - }, - - "BSMATE": { - code: "BSMATE", - name: "BS Materials Engineering", - college: "CEAT", - collegeName: "College of Engineering and Agro-Industrial Technology", - available: true, - totalUnitsRequired: 175, - geCoursesRequired: 9, - tracks: null, - defaultTrack: null, - majorCourses: [ - { code: "MATE 21", title: "Engineering Drawing", units: 2 }, - { code: "MATE 31", title: "Introduction to Materials Science", units: 3 }, - { code: "MATE 41", title: "Structure of Materials", units: 4 }, - { code: "MATE 101", title: "Physical Metallurgy", units: 4 }, - { code: "MATE 111", title: "Mechanical Behavior of Materials", units: 4 }, - { code: "MATE 121", title: "Ceramic Materials", units: 3 }, - { code: "MATE 131", title: "Polymer Materials", units: 3 }, - { code: "MATE 141", title: "Electronic Materials", units: 3 }, - { code: "MATE 151", title: "Corrosion Engineering", units: 3 }, - { code: "MATE 161", title: "Materials Characterization", units: 4 }, - { code: "MATE 171", title: "Materials Processing", units: 4 }, - { code: "MATE 181", title: "Materials Selection and Design", units: 3 }, - { code: "MATE 190", title: "Materials Engineering Design", units: 4 }, - { code: "MATE 191", title: "Engineering Internship", units: 3, gradeType: "S/U" }, - { code: "ENSC 21", title: "Statics of Rigid Bodies", units: 3 }, - { code: "ENSC 22", title: "Dynamics of Rigid Bodies", units: 3 }, - { code: "ENSC 24", title: "Thermodynamics I", units: 3 }, - { code: "MATH 27", title: "Analytic Geometry and Calculus I", units: 3 }, - { code: "MATH 28", title: "Analytic Geometry and Calculus II", units: 3 }, - { code: "MATH 114", title: "Differential Equations", units: 3 }, - { code: "PHYSICS 71", title: "Elementary Physics I", units: 4 }, - { code: "PHYSICS 72", title: "Elementary Physics II", units: 4 }, - { code: "CHEM 16", title: "General Chemistry I", units: 4 }, - { code: "CHEM 17", title: "General Chemistry II", units: 4 }, - { code: "ENG 10", title: "Writing of Scientific Papers", units: 3 }, - ], - requiredCodes: [ - "MATE 21", "MATE 31", "MATE 41", "MATE 101", "MATE 111", "MATE 121", - "MATE 131", "MATE 141", "MATE 151", "MATE 161", "MATE 171", "MATE 181", - "MATE 190", "MATE 191", "ENSC 21", "ENSC 22", "ENSC 24", - "MATH 27", "MATH 28", "MATH 114", - "PHYSICS 71", "PHYSICS 72", "CHEM 16", "CHEM 17", "ENG 10" - ] - }, - - // ============================================================================ - // COLLEGE OF ECONOMICS AND MANAGEMENT (CEM) - // ============================================================================ - - "BSECON": { - code: "BSECON", - name: "BS Economics", - college: "CEM", - collegeName: "College of Economics and Management", - available: true, - totalUnitsRequired: 138, - geCoursesRequired: 9, - tracks: { - thesis: { name: "Thesis Track", code: "ECON 200", freeElectiveUnits: 6 }, - sp: { name: "Special Problem Track", code: "ECON 199", freeElectiveUnits: 6 } - }, - defaultTrack: "sp", - majorCourses: [ - // First Year First Semester - { code: "KAS 1", title: "Kasaysayan ng Pilipinas/Philippine History", units: 3 }, - { code: "ARTS 1", title: "Critical Perspectives in the Arts", units: 3 }, - { code: "STS 1", title: "Science, Technology, and Society", units: 3 }, - { code: "MATH 25", title: "Fundamental Calculus", units: 3 }, - { code: "ECON 11", title: "General Economics", units: 3 }, - { code: "HK 11", title: "Wellness and Basic Injury Management", units: 2 }, - // First Year Second Semester - { code: "MGT 101", title: "Concepts and Dynamics of Management", units: 3 }, - { code: "COST 10", title: "Introduction to Collective Action", units: 1 }, - { code: "ECON 101", title: "Intermediate Macroeconomic Theory", units: 3 }, - { code: "ECON 102", title: "Intermediate Microeconomic Theory", units: 3 }, - { code: "HK 12/13", title: "Basic or Advanced Human Kinetics Activities", units: 2 }, - // Second Year First Semester - { code: "PI 10", title: "The Life and Works of Jose Rizal", units: 3 }, - { code: "ECON 103", title: "Introduction to Growth Theory and Open Economy Macroeconomics", units: 3 }, - { code: "ECON 104", title: "Introduction to Factor Market Analysis, General Equilibrium, and Welfare Economics", units: 3 }, - { code: "STAT 101", title: "Statistical Methods", units: 3 }, - { code: "HK 12/13", title: "Human Kinetics Activities/Advanced Human Kinetics Activities", units: 2 }, - // Second Year Second Semester - { code: "ETHICS 1", title: "Ethics and Moral Reasoning in Everyday Life", units: 3 }, - { code: "MGT 111", title: "Principles of Accounting", units: 3 }, - { code: "ECON 130", title: "Elements of Mathematical Economics", units: 3 }, - { code: "ECON 137", title: "Introduction to Econometrics", units: 3 }, - // Third Year First Semester - { code: "ECON 115", title: "Philippine Economic History", units: 3 }, - { code: "ECON 121", title: "Money and Banking", units: 3 }, - { code: "ECON 138", title: "Intermediate Econometrics", units: 3 }, - { code: "ECON 170", title: "Intermediate Environmental Economics", units: 3 }, - { code: "ECON 175", title: "Benefit Cost Analysis", units: 3 }, - { code: "MGT 115", title: "Introduction to Financial Management", units: 3 }, - // Third Year Second Semester - { code: "COMM 10", title: "Critical Perspectives in Communication", units: 3 }, - { code: "ENG 10", title: "Writing of Scientific Papers", units: 3 }, - { code: "ECON 141", title: "International Economics", units: 3 }, - { code: "ECON 151", title: "Public Economics", units: 3 }, - // Third Year Midyear - { code: "ECON 198", title: "Internship", units: 3, gradeType: "S/U" }, - // Fourth Year First Semester - { code: "ECON 199", title: "Undergraduate Seminar", units: 1 }, - { code: "ECON 200", title: "Undergraduate Thesis", units: 3 }, - // Fourth Year Second Semester - { code: "ECON 110", title: "History of Economic Doctrines", units: 3 }, - { code: "ECON 185", title: "Development Economics", units: 3 }, - ], - requiredCodes: [ - "KAS 1", "ARTS 1", "STS 1", "MATH 25", "ECON 11", "HK 11", - "MGT 101", "COST 10", "ECON 101", "ECON 102", "HK 12/13", - "PI 10", "ECON 103", "ECON 104", "STAT 101", - "ETHICS 1", "MGT 111", "ECON 130", "ECON 137", - "ECON 115", "ECON 121", "ECON 138", "ECON 170", "ECON 175", "MGT 115", - "COMM 10", "ENG 10", "ECON 141", "ECON 151", - "ECON 198", "ECON 199", "ECON 200", "ECON 110", "ECON 185" - ] - }, - - "BSAAE": { - code: "BSAAE", - name: "BS Agricultural and Applied Economics", - college: "CEM", - collegeName: "College of Economics and Management", - available: true, - totalUnitsRequired: 140, - geCoursesRequired: 9, - tracks: { - thesis: { name: "Thesis Track", code: "AAE 200", freeElectiveUnits: 9 }, - sp: { name: "Special Problem Track", code: "AAE 199", freeElectiveUnits: 12 } - }, - defaultTrack: "sp", - majorCourses: [ - // Year 1 Sem 1 - { code: "AAE 10", title: "Introduction to Agricultural and Applied Economics", units: 3 }, - { code: "MATH 27", title: "Analytic Geometry and Calculus II", units: 3 }, - { code: "MGT 1", title: "Fundamentals of Accounting I", units: 3 }, - // Year 1 Sem 2 - { code: "ECON 11", title: "Introduction to Microeconomics", units: 3 }, - { code: "MATH 28", title: "Analytic Geometry and Calculus III", units: 3 }, - { code: "ENG 10", title: "Writing of Scientific Papers", units: 3 }, - // Year 2 Sem 1 - { code: "AAE 103", title: "Principles of Crop Production", units: 3 }, - { code: "ECON 12", title: "Introduction to Macroeconomics", units: 3 }, - { code: "ECON 110", title: "Mathematics for Economists I", units: 3 }, - { code: "STAT 101", title: "Elementary Statistics", units: 3 }, - // Year 2 Sem 2 - { code: "AAE 104", title: "Principles of Animal Production", units: 3 }, - { code: "AAE 120", title: "Agricultural Economics Survey", units: 3 }, - { code: "ECON 101", title: "Microeconomic Theory", units: 3 }, - { code: "ECON 111", title: "Mathematics for Economists II", units: 3 }, - { code: "ECON 115", title: "Introduction to Econometrics", units: 3 }, - // Year 3 Sem 1 - { code: "AAE 125", title: "Agricultural Marketing", units: 3 }, - { code: "AAE 130", title: "Agricultural Production Economics", units: 3 }, - { code: "AAE 140", title: "Natural Resource Economics", units: 3 }, - { code: "ECON 102", title: "Macroeconomic Theory", units: 3 }, - { code: "ECON 116", title: "Econometric Methods", units: 3 }, - // Year 3 Sem 2 - { code: "AAE 145", title: "Environmental Economics", units: 3 }, - { code: "AAE 150", title: "Agricultural Finance", units: 3 }, - { code: "AAE 155", title: "Agribusiness Management", units: 3 }, - { code: "AAE 160", title: "Economic Planning and Development", units: 3 }, - { code: "AAE 185", title: "Agricultural and Applied Economics Research", units: 3 }, - // Year 3 Midyear - { code: "AAE 198", title: "Practicum", units: 3, gradeType: "S/U" }, - // Year 4 Sem 1 - { code: "AAE 165", title: "Agricultural Policy", units: 3 }, - { code: "AAE 199", title: "Special Problem", units: 3, track: "sp" }, - { code: "AAE 200", title: "Undergraduate Thesis", units: 6, track: "thesis" }, - ], - requiredCodes: [ - "AAE 10", "AAE 103", "AAE 104", "AAE 120", "AAE 125", "AAE 130", - "AAE 140", "AAE 145", "AAE 150", "AAE 155", "AAE 160", "AAE 165", - "AAE 185", "AAE 198", - "ECON 11", "ECON 12", "ECON 101", "ECON 102", "ECON 110", "ECON 111", - "ECON 115", "ECON 116", - "MATH 27", "MATH 28", "STAT 101", "MGT 1", "ENG 10" - ] - }, - - "BSABME": { - code: "BSABME", - name: "BS Agribusiness Management and Entrepreneurship", - college: "CEM", - collegeName: "College of Economics and Management", - available: true, - totalUnitsRequired: 141, - geCoursesRequired: 9, - tracks: { - thesis: { name: "Thesis Track", code: "ABME 200", freeElectiveUnits: 6 }, - sp: { name: "Special Problem Track", code: "ABME 199", freeElectiveUnits: 9 } - }, - defaultTrack: "sp", - majorCourses: [ - // Year 1 Sem 1 - { code: "ABME 10", title: "Introduction to Agribusiness Management", units: 3 }, - { code: "MATH 27", title: "Analytic Geometry and Calculus II", units: 3 }, - { code: "MGT 1", title: "Fundamentals of Accounting I", units: 3 }, - // Year 1 Sem 2 - { code: "ECON 11", title: "Introduction to Microeconomics", units: 3 }, - { code: "MATH 28", title: "Analytic Geometry and Calculus III", units: 3 }, - { code: "MGT 2", title: "Fundamentals of Accounting II", units: 3 }, - // Year 2 Sem 1 - { code: "ABME 101", title: "Principles of Crop Production", units: 3 }, - { code: "ABME 105", title: "Agribusiness Products Survey", units: 3 }, - { code: "ECON 12", title: "Introduction to Macroeconomics", units: 3 }, - { code: "MGT 101", title: "Organization and Management", units: 3 }, - { code: "STAT 101", title: "Elementary Statistics", units: 3 }, - // Year 2 Sem 2 - { code: "ABME 102", title: "Principles of Animal Production", units: 3 }, - { code: "ABME 110", title: "Agribusiness Marketing", units: 3 }, - { code: "ABME 120", title: "Financial Management", units: 3 }, - { code: "ABME 130", title: "Information Technology for Agribusiness", units: 3 }, - { code: "ECON 101", title: "Microeconomic Theory", units: 3 }, - // Year 3 Sem 1 - { code: "ABME 111", title: "Agricultural Sales", units: 3 }, - { code: "ABME 122", title: "Credit Management and Rural Banking", units: 3 }, - { code: "ABME 140", title: "Production Management", units: 3 }, - { code: "ABME 150", title: "Human Resource Management", units: 3 }, - { code: "ECON 102", title: "Macroeconomic Theory", units: 3 }, - // Year 3 Sem 2 - { code: "ABME 160", title: "Commercial Law and Ethics", units: 3 }, - { code: "ABME 170", title: "Strategic Management", units: 3 }, - { code: "ABME 172", title: "Product Ideation and Creation", units: 3 }, - { code: "ABME 180", title: "Statistics for Management", units: 3 }, - { code: "ABME 195", title: "Seminar", units: 1 }, - { code: "ENG 10", title: "Writing of Scientific Papers", units: 3 }, - // Year 3 Midyear - { code: "ABME 198", title: "On-the-Job Training", units: 3, gradeType: "S/U" }, - // Year 4 Sem 1 - { code: "ABME 171", title: "Entrepreneurship", units: 3 }, - { code: "ABME 185", title: "Operations Research", units: 3 }, - { code: "ABME 190", title: "Feasibility Study", units: 3 }, - { code: "ABME 199", title: "Special Problem", units: 3, track: "sp" }, - { code: "ABME 200", title: "Undergraduate Thesis", units: 6, track: "thesis" }, - ], - requiredCodes: [ - "ABME 10", "ABME 101", "ABME 102", "ABME 105", "ABME 110", "ABME 111", - "ABME 120", "ABME 122", "ABME 130", "ABME 140", "ABME 150", "ABME 160", - "ABME 170", "ABME 171", "ABME 172", "ABME 180", "ABME 185", "ABME 190", - "ABME 195", "ABME 198", - "ECON 11", "ECON 12", "ECON 101", "ECON 102", - "MGT 1", "MGT 2", "MGT 101", "MATH 27", "MATH 28", "STAT 101", "ENG 10" - ] - }, - - "AAENTREP": { - code: "AAENTREP", - name: "Associate in Arts in Entrepreneurship", - college: "CEM", - collegeName: "College of Economics and Management", - available: true, - totalUnitsRequired: 75, - geCoursesRequired: 5, - tracks: null, - defaultTrack: null, - majorCourses: [ - // First Year First Semester - { code: "KAS 1", title: "Kasaysayan ng Pilipinas/Philippine History", units: 3 }, - { code: "COMM 10", title: "Critical Perspectives in Communication", units: 3 }, - { code: "STS 1", title: "Science, Technology, and Society", units: 3 }, - { code: "ABME 10", title: "Foundations of Entrepreneurship", units: 3 }, - { code: "MGT 101", title: "Concepts and Dynamics of Management", units: 3 }, - { code: "HK 11", title: "Wellness and Basic Injury Management", units: 2 }, - // First Year Second Semester - { code: "ARTS 1", title: "Critical Perspectives in the Arts", units: 3 }, - { code: "MGT 111", title: "Principles of Accounting", units: 3 }, - { code: "HK 12/13", title: "Basic or Advanced Human Kinetics Activities", units: 2 }, - // First Year Midyear - { code: "PI 10", title: "The Life, Works, and Writings of Jose Rizal", units: 3 }, - // Second Year First Semester - { code: "ABME 172", title: "Product Ideation and Creation", units: 3 }, - { code: "ABME 174", title: "Business Planning", units: 3 }, - { code: "ABME 115", title: "Entrepreneurial Finance", units: 3 }, - { code: "MGT 113", title: "Managerial Accounting", units: 3 }, - { code: "MGT 151", title: "Introduction to Marketing Management", units: 3 }, - { code: "ENTR 198", title: "Internship", units: 3, gradeType: "S/U" }, - { code: "HK 12/13", title: "Basic or Advanced Human Kinetics Activities", units: 2 }, - // Second Year Second Semester - { code: "ETHICS 1", title: "Ethics and Moral Reasoning for Everyday Life", units: 3 }, - { code: "MGT 161", title: "Business Law", units: 3 }, - { code: "MGT 131", title: "Introduction to Human Relations and Behavior in Organization", units: 3 }, - { code: "HK 12/13", title: "Basic or Advanced Human Kinetics Activities", units: 2 }, - ], - requiredCodes: [ - "KAS 1", "COMM 10", "STS 1", "ARTS 1", "ETHICS 1", "PI 10", - "ABME 10", "ABME 172", "ABME 174", "ABME 115", - "MGT 101", "MGT 111", "MGT 113", "MGT 131", "MGT 151", "MGT 161", - "ENTR 198", "HK 11", "HK 12/13" - ] - }, - - "BSACCT": { - code: "BSACCT", - name: "BS Accountancy", - college: "CEM", - collegeName: "College of Economics and Management", - available: true, - totalUnitsRequired: 166, - geCoursesRequired: 9, - tracks: null, - defaultTrack: null, - majorCourses: [ - // First Year First Semester - { code: "KAS 1", title: "Kasaysayan ng Pilipinas/Philippine History", units: 3 }, - { code: "STS 1", title: "Science, Technology, and Society", units: 3 }, - { code: "MATH 25", title: "Fundamental Calculus", units: 3 }, - { code: "ECON 11", title: "General Economics", units: 3 }, - { code: "MGT 101", title: "Concepts and Dynamics of Management", units: 3 }, - { code: "BA 99.1", title: "Fundamental Accounting Theory and Practice I", units: 3 }, - { code: "HK 11", title: "Wellness and Basic Injury Management", units: 2 }, - // First Year Second Semester - { code: "ETHICS 1", title: "Ethics and Moral Reasoning for Everyday Life", units: 3 }, - { code: "ARTS 1", title: "Critical Perspectives in the Arts", units: 3 }, - { code: "COMM 10", title: "Critical Perspectives in Communication", units: 3 }, - { code: "STAT 101", title: "Statistical Methods", units: 3 }, - { code: "ECON 101", title: "Intermediate Macroeconomic Theory", units: 3 }, - { code: "BA 99.2", title: "Fundamental Accounting Theory and Practice II", units: 3 }, - { code: "HK 12/13", title: "Basic or Advanced Human Kinetics Activities", units: 2 }, - // First Year Midyear - { code: "BA 183", title: "Introduction to Database Management for Accounting", units: 3 }, - // Second Year First Semester - { code: "ECON 102", title: "Intermediate Microeconomic Theory", units: 3 }, - { code: "MGT 121", title: "Quantitative Business Analysis", units: 3 }, - { code: "MGT 131", title: "Introduction to Human Relations and Behavior in Organization", units: 3 }, - { code: "MGT 151", title: "Introduction to Marketing Management", units: 3 }, - { code: "BA 114.1", title: "Accounting Theory and Practice I", units: 6 }, - { code: "HK 12/13", title: "Basic or Advanced Human Kinetics Activities", units: 2 }, - // Second Year Second Semester - { code: "MGT 141", title: "Fundamentals of Operations Management", units: 3 }, - { code: "BA 114.2", title: "Accounting Theory and Practice II", units: 6 }, - { code: "BA 116", title: "Cost Accounting", units: 3 }, - { code: "HK 12/13", title: "Basic or Advanced Human Kinetics Activities", units: 2 }, - // Second Year Midyear - { code: "BA 198.1", title: "Internship I", units: 3, gradeType: "S/U" }, - // Third Year First Semester - { code: "PI 10", title: "The Life, Works, and Writings of Jose Rizal", units: 3 }, - { code: "BA 117", title: "Managerial Cost Accounting and Control", units: 3 }, - { code: "BA 118.1", title: "Advanced Accounting I", units: 6 }, - { code: "BA 127", title: "Tax Accounting I", units: 3 }, - { code: "BA 141", title: "Business Finance I", units: 3 }, - { code: "BA 161", title: "Law on Business Transactions", units: 3 }, - // Third Year Second Semester - { code: "ENG 10", title: "Writing of Scientific Papers", units: 3 }, - { code: "BA 142", title: "Business Finance II", units: 3 }, - { code: "BA 118.2", title: "Advanced Accounting II", units: 6 }, - { code: "BA 120.1", title: "Audit Theory", units: 3 }, - { code: "BA 128", title: "Tax Accounting II", units: 3 }, - { code: "BA 162", title: "Law on Business Associations", units: 3 }, - // Third Year Midyear - { code: "BA 198.2", title: "Internship II", units: 3, gradeType: "S/U" }, - // Fourth Year First Semester - { code: "MGT 117", title: "Investment Management", units: 3 }, - { code: "MGT 181", title: "Strategic Business Management", units: 3 }, - { code: "BA 122", title: "Computerized Accounting and Auditing Theory and Practice", units: 3 }, - { code: "BA 164", title: "Law on Negotiable Instruments and Banking Related Laws", units: 3 }, - { code: "BA 184.1", title: "Computer-based Accounting Systems", units: 3 }, - { code: "BA 190", title: "Special Problems", units: 1 }, - // Fourth Year Second Semester - { code: "BA 120.2", title: "Audit Practice", units: 6 }, - { code: "BA 167", title: "Sales and Bailments and other Special Laws", units: 3 }, - { code: "BA 184.2", title: "Audit of Computer-based Accounting Systems", units: 3 }, - { code: "BA 191", title: "Special Topics", units: 1 }, - ], - requiredCodes: [ - "KAS 1", "STS 1", "MATH 25", "ECON 11", "ECON 101", "ECON 102", - "ETHICS 1", "ARTS 1", "COMM 10", "PI 10", "ENG 10", - "STAT 101", "MGT 101", "MGT 121", "MGT 131", "MGT 141", "MGT 151", "MGT 117", "MGT 181", - "BA 99.1", "BA 99.2", "BA 114.1", "BA 114.2", "BA 116", "BA 117", - "BA 118.1", "BA 118.2", "BA 120.1", "BA 120.2", "BA 122", - "BA 127", "BA 128", "BA 141", "BA 142", - "BA 161", "BA 162", "BA 164", "BA 167", - "BA 183", "BA 184.1", "BA 184.2", "BA 190", "BA 191", - "BA 198.1", "BA 198.2", "HK 11", "HK 12/13" - ] - }, - - // ============================================================================ - // COLLEGE OF FORESTRY AND NATURAL RESOURCES (CFNR) - // ============================================================================ - - "BSFOR": { - code: "BSFOR", - name: "BS Forestry", - college: "CFNR", - collegeName: "College of Forestry and Natural Resources", - available: true, - totalUnitsRequired: 143, - geCoursesRequired: 9, - tracks: null, - defaultTrack: null, - majorCourses: [ - // Year 1 Sem 1 - { code: "FOR 1", title: "Introduction to Forests and Forestry", units: 3 }, - { code: "SFI 100", title: "Geology and Forest Soils", units: 3 }, - { code: "FBS 10", title: "Biology of Tropical Forest Plants", units: 4 }, - { code: "ECON 11", title: "General Economics", units: 3 }, - // Year 1 Sem 2 - { code: "SFFG 101", title: "Principles and Concepts of Social Forestry", units: 3 }, - { code: "FRM 92", title: "Forest Engineering", units: 3 }, - { code: "FBS 21", title: "Taxonomy of Forest Plants", units: 4 }, - { code: "FRM 120", title: "Forestry Economics", units: 3 }, - // Year 2 Sem 1 - { code: "SFFG 123", title: "Forest Policy and Institution", units: 3 }, - { code: "FPPS 11", title: "Wood Structure and Identification", units: 3 }, - { code: "FBS 31", title: "Plant Physiology", units: 3 }, - { code: "NRC 140", title: "Introduction to Geographic Information Systems", units: 3 }, - // Year 2 Sem 2 - { code: "NRC 170", title: "Watershed Management", units: 3 }, - { code: "FPPS 127", title: "Properties and Utilization of Forest Products", units: 4 }, - { code: "FBS 36", title: "Fundamentals of Forest Ecology", units: 3 }, - { code: "SFI 103", title: "Fundamentals of Agroforestry", units: 3 }, - // Year 3 Sem 1 - { code: "SFI 140", title: "Tropical Silviculture", units: 3 }, - { code: "FBS 45", title: "Forest Insect Pests and Diseases", units: 4 }, - { code: "SFFG 113", title: "Forestry Extension", units: 3 }, - { code: "FBS 101", title: "Forest Biodiversity", units: 3 }, - // Year 3 Sem 2 - { code: "FPPS 128", title: "Non-Timber Forest Products", units: 3 }, - { code: "SFI 142", title: "Plantation Forestry", units: 3 }, - { code: "FBS 172", title: "Forest Genetics", units: 3 }, - { code: "FOR 195", title: "Research Methods in Forestry and Natural Resources", units: 3 }, - { code: "FRM 61", title: "Forest Biometry", units: 3 }, - // Year 3 Midyear - { code: "FOR 198", title: "Internship", units: 3, gradeType: "S/U" }, - // Year 4 Sem 1 - { code: "FOR 200", title: "Undergraduate Thesis", units: 3 }, - { code: "SFFG 125", title: "Political Economy and Administration of Forestry", units: 3 }, - { code: "FRM 131", title: "Forest-based Enterprise Development and Management", units: 3 }, - { code: "SFFG 152", title: "Sociology of Natural Resources", units: 3 }, - { code: "FOR 199", title: "Undergraduate Seminar", units: 1 }, - // Year 4 Sem 2 - { code: "FPPS 147", title: "Furniture and Handicraft Production", units: 3 }, - { code: "FRM 183", title: "Timber Production Management", units: 3 }, - { code: "FRM 184", title: "Integrated Forest Resource Management", units: 3 }, - ], - requiredCodes: [ - "FOR 1", "FOR 195", "FOR 198", "FOR 199", "FOR 200", - "SFI 100", "SFI 103", "SFI 140", "SFI 142", - "FBS 10", "FBS 21", "FBS 31", "FBS 36", "FBS 45", "FBS 101", "FBS 172", - "SFFG 101", "SFFG 113", "SFFG 123", "SFFG 125", "SFFG 152", - "FPPS 11", "FPPS 127", "FPPS 128", "FPPS 147", - "FRM 61", "FRM 92", "FRM 120", "FRM 131", "FRM 183", "FRM 184", - "NRC 140", "NRC 170", "ECON 11" - ] - }, - - "AAFOR": { - code: "AAFOR", - name: "Associate in Forestry", - college: "CFNR", - collegeName: "College of Forestry and Natural Resources", - available: true, - totalUnitsRequired: 72, - geCoursesRequired: 6, - tracks: null, - defaultTrack: null, - majorCourses: [ - { code: "FOR 1", title: "Introduction to Forestry", units: 3 }, - { code: "FOR 21", title: "Dendrology", units: 3 }, - { code: "FOR 31", title: "Forest Mensuration", units: 4 }, - { code: "FOR 41", title: "Silvics and Silviculture", units: 4 }, - { code: "FOR 51", title: "Forest Ecology", units: 3 }, - { code: "FOR 101", title: "Forest Management", units: 4 }, - { code: "FOR 111", title: "Forest Utilization", units: 4 }, - { code: "FOR 121", title: "Forest Protection", units: 3 }, - { code: "BIO 1", title: "General Biology I", units: 3 }, - { code: "CHEM 16", title: "General Chemistry I", units: 4 }, - { code: "MATH 17", title: "Algebra and Trigonometry", units: 5 }, - ], - requiredCodes: [ - "FOR 1", "FOR 21", "FOR 31", "FOR 41", "FOR 51", "FOR 101", "FOR 111", - "FOR 121", "BIO 1", "CHEM 16", "MATH 17" - ] - }, - - // ============================================================================ - // COLLEGE OF HUMAN ECOLOGY (CHE) - // ============================================================================ - - "BSHE": { - code: "BSHE", - name: "BS Human Ecology", - college: "CHE", - collegeName: "College of Human Ecology", - available: true, - totalUnitsRequired: 140, - geCoursesRequired: 9, - tracks: null, - defaultTrack: null, - majorCourses: [ - // Year 1 Sem 1 - { code: "HFDS 101", title: "Family and the Environment", units: 3 }, - { code: "HUME 100", title: "Introduction to Human Ecology", units: 3 }, - { code: "CERP 101", title: "Fundamentals of Human Settlements", units: 3 }, - { code: "SDS 101", title: "Introduction to Social Development", units: 3 }, - // Year 1 Sem 2 - { code: "HUME 112", title: "Sustainability Science", units: 3 }, - { code: "HUME 107", title: "Principles of Human Development", units: 3 }, - { code: "HUME 105", title: "Humans and Their Environment", units: 3 }, - { code: "SOC 140", title: "Introduction to Demography", units: 3 }, - { code: "BIO 150", title: "Ecology", units: 4 }, - // Year 2 Sem 1 - { code: "HUME 110", title: "Ecology and Value Systems", units: 3 }, - { code: "HUME 111", title: "Human Ecological Perspectives in Development", units: 3 }, - { code: "HUME 113", title: "Community Study in Human Welfare", units: 3 }, - // Year 2 Sem 2 - { code: "STAT 166", title: "Statistics for the Social Sciences", units: 3 }, - { code: "HUME 114", title: "Material and Energy Flows", units: 3 }, - { code: "CERP 161", title: "Planning Theory and Practice I", units: 3 }, - { code: "HUME 115", title: "Social Policies", units: 3 }, - // Year 3 Sem 1 - { code: "CERP 140", title: "Fundamentals of Environmental Economics", units: 3 }, - { code: "HUME 122", title: "Human Needs and the Built Environment", units: 3 }, - { code: "HUME 123", title: "Climate Change Adaptation and Disaster Risk Reduction in Human Ecosystems", units: 3 }, - { code: "SDS 172", title: "Techniques in Community Organizing", units: 3 }, - { code: "HUME 195", title: "Research Methods in Human Ecology", units: 3 }, - // Year 3 Sem 2 - { code: "HUME 125", title: "Human Ecological Systems Mapping", units: 3 }, - { code: "HUME 124", title: "Environmental Health", units: 3 }, - { code: "SDS 173", title: "Consumer Education", units: 3 }, - { code: "HFDS 110", title: "Migration", units: 3 }, - // Year 4 Sem 1 - { code: "HNF 141", title: "Food and Nutrition Systems", units: 3 }, - { code: "CERP 200", title: "Undergraduate Thesis", units: 3 }, - // Year 4 Sem 2 - { code: "HUME 200a", title: "Supervised Field Experience", units: 6 }, - { code: "HUME 199", title: "Seminar in Human Ecology", units: 1 }, - ], - requiredCodes: [ - "HUME 100", "HUME 105", "HUME 107", "HUME 110", "HUME 111", "HUME 112", - "HUME 113", "HUME 114", "HUME 115", "HUME 122", "HUME 123", "HUME 124", - "HUME 125", "HUME 195", "HUME 199", "HUME 200a", - "HFDS 101", "HFDS 110", "HNF 141", - "CERP 101", "CERP 140", "CERP 161", "CERP 200", - "SDS 101", "SDS 172", "SDS 173", - "SOC 140", "BIO 150", "STAT 166" - ] - }, - - "BSNUTRI": { - code: "BSNUTRI", - name: "BS Nutrition", - college: "CHE", - collegeName: "College of Human Ecology", - available: true, - totalUnitsRequired: 165, - geCoursesRequired: 9, - tracks: { - thesis: { name: "Thesis Track", code: "NUT 200", freeElectiveUnits: 9 }, - sp: { name: "Special Problem Track", code: "NUT 190", freeElectiveUnits: 12 } - }, - defaultTrack: "sp", - majorCourses: [ - { code: "NUT 11", title: "Fundamentals of Nutrition", units: 3 }, - { code: "NUT 100", title: "Principles of Nutrition", units: 3 }, - { code: "NUT 101", title: "Advanced Nutrition", units: 3 }, - { code: "NUT 110", title: "Nutrition Assessment", units: 3 }, - { code: "NUT 120", title: "Diet Therapy", units: 4 }, - { code: "NUT 130", title: "Community Nutrition", units: 3 }, - { code: "NUT 140", title: "Food Service Management", units: 3 }, - { code: "NUT 150", title: "Nutrition Education", units: 3 }, - { code: "NUT 160", title: "Nutrition Research Methods", units: 3 }, - { code: "NUT 170", title: "Sports Nutrition", units: 3 }, - { code: "NUT 180", title: "Nutrition in the Life Cycle", units: 3 }, - { code: "NUT 190", title: "Special Problem", units: 3, track: "sp" }, - { code: "NUT 191", title: "Dietetic Internship", units: 6, gradeType: "S/U" }, - { code: "NUT 200", title: "Undergraduate Thesis", units: 6, track: "thesis" }, - { code: "CHEM 16", title: "General Chemistry I", units: 4 }, - { code: "CHEM 17", title: "General Chemistry II", units: 4 }, - { code: "CHEM 40", title: "Organic Chemistry I", units: 5 }, - { code: "CHEM 31", title: "Elementary Biochemistry", units: 5 }, - { code: "BIO 1", title: "General Biology I", units: 3 }, - { code: "STAT 101", title: "Statistical Methods", units: 3 }, - { code: "ENG 10", title: "Writing of Scientific Papers", units: 3 }, - ], - requiredCodes: [ - "NUT 11", "NUT 100", "NUT 101", "NUT 110", "NUT 120", "NUT 130", "NUT 140", - "NUT 150", "NUT 160", "NUT 170", "NUT 180", "NUT 191", - "CHEM 16", "CHEM 17", "CHEM 40", "CHEM 31", "BIO 1", "STAT 101", "ENG 10" - ] - }, - - // ============================================================================ - // COLLEGE OF DEVELOPMENT COMMUNICATION (CDC) - // ============================================================================ - - "BSDC": { - code: "BSDC", - name: "BS Development Communication", - college: "CDC", - collegeName: "College of Development Communication", - available: true, - totalUnitsRequired: 142, - geCoursesRequired: 9, - tracks: null, - defaultTrack: null, - majorCourses: [ - // Year 1 Sem 1 - { code: "DEVC 10", title: "Introduction to Development Communication", units: 3 }, - { code: "DEVC 11", title: "Introduction to Media Writing for Development", units: 3 }, - // Year 1 Sem 2 - { code: "DEVC 20", title: "Fundamentals of Development Journalism", units: 3 }, - { code: "DEVC 30", title: "Fundamentals of Community Broadcasting", units: 3 }, - { code: "DEVC 40", title: "Fundamentals of Educational Communication", units: 3 }, - { code: "DEVC 50", title: "Introduction to Science Communication", units: 3 }, - // Year 2 Sem 1 - { code: "COMM 10", title: "Critical Perspectives in Communication", units: 3 }, - { code: "DEVC 70", title: "Interpersonal Communication in Development", units: 3 }, - { code: "DEVC 80", title: "Communication and Society", units: 3 }, - { code: "DEVC 103", title: "Visual and Audiovisual Media Production", units: 3 }, - // Year 2 Sem 2 - { code: "DEVC 101", title: "Introduction to Communication Theory", units: 3 }, - { code: "DEVC 125", title: "Writing and Reporting for Development", units: 3 }, - { code: "DEVC 135", title: "Multi-media Materials Planning and Design", units: 3 }, - { code: "STAT 166", title: "Statistics for the Social Sciences", units: 3 }, - // Year 3 Sem 1 - { code: "DEVC 126", title: "Participatory Development Journalism", units: 3 }, - { code: "DEVC 144", title: "Media-based Learning Systems", units: 3 }, - { code: "DEVC 153", title: "Managing Information for Development", units: 3 }, - // Year 3 Sem 2 - { code: "DEVC 136", title: "Multi-media Materials Production and Management", units: 3 }, - { code: "DEVC 154", title: "Communicating Science for Development", units: 3 }, - { code: "DEVC 195", title: "Introduction to Communication Research", units: 3 }, - // Year 3 Midyear - { code: "DEVC 198", title: "Internship", units: 3, gradeType: "S/U" }, - // Year 4 Sem 1 - { code: "DEVC 127", title: "Data Journalism for Development", units: 3 }, - { code: "DEVC 145", title: "Distance Learning Systems in Development Communication", units: 3 }, - { code: "DEVC 200", title: "Undergraduate Thesis", units: 3 }, - // Year 4 Sem 2 - { code: "DEVC 128", title: "Science Journalism", units: 3 }, - { code: "DEVC 155", title: "Knowledge Management for Development", units: 3 }, - { code: "DEVC 180", title: "Communication Campaigns and Programs", units: 3 }, - { code: "DEVC 199", title: "Undergraduate Seminar", units: 1 }, - ], - requiredCodes: [ - "DEVC 10", "DEVC 11", "DEVC 20", "DEVC 30", "DEVC 40", "DEVC 50", - "DEVC 70", "DEVC 80", "DEVC 101", "DEVC 103", "DEVC 125", "DEVC 126", - "DEVC 127", "DEVC 128", "DEVC 135", "DEVC 136", "DEVC 144", "DEVC 145", - "DEVC 153", "DEVC 154", "DEVC 155", "DEVC 180", "DEVC 195", - "DEVC 198", "DEVC 199", "DEVC 200", "COMM 10", "STAT 166" - ] - }, - - "ASDC": { - code: "ASDC", - name: "Associate of Science in Development Communication", - college: "CDC", - collegeName: "College of Development Communication", - available: true, - totalUnitsRequired: 72, - geCoursesRequired: 6, - tracks: null, - defaultTrack: null, - majorCourses: [ - { code: "DEVC 10", title: "Introduction to Development Communication", units: 3 }, - { code: "DEVC 20", title: "Communication Theories", units: 3 }, - { code: "DEVC 30", title: "Communication Research Methods", units: 3 }, - { code: "DEVC 40", title: "Development Journalism", units: 3 }, - { code: "DEVC 50", title: "Development Broadcasting", units: 3 }, - { code: "DEVC 60", title: "Educational Communication", units: 3 }, - { code: "ENG 10", title: "Writing of Scientific Papers", units: 3 }, - ], - requiredCodes: [ - "DEVC 10", "DEVC 20", "DEVC 30", "DEVC 40", "DEVC 50", "DEVC 60", "ENG 10" - ] - }, - - // ============================================================================ - // COLLEGE OF VETERINARY MEDICINE (CVM) - // ============================================================================ - - "DVM": { - code: "DVM", - name: "Doctor of Veterinary Medicine", - college: "CVM", - collegeName: "College of Veterinary Medicine", - available: true, - totalUnitsRequired: 220, - geCoursesRequired: 9, - tracks: null, - defaultTrack: null, - majorCourses: [ - { code: "VET 11", title: "Veterinary Anatomy I", units: 5 }, - { code: "VET 12", title: "Veterinary Anatomy II", units: 5 }, - { code: "VET 21", title: "Veterinary Physiology I", units: 4 }, - { code: "VET 22", title: "Veterinary Physiology II", units: 4 }, - { code: "VET 31", title: "Veterinary Biochemistry", units: 5 }, - { code: "VET 41", title: "Veterinary Microbiology", units: 5 }, - { code: "VET 51", title: "Veterinary Parasitology", units: 5 }, - { code: "VET 61", title: "Veterinary Pathology", units: 5 }, - { code: "VET 71", title: "Veterinary Pharmacology", units: 4 }, - { code: "VET 101", title: "Veterinary Medicine I", units: 4 }, - { code: "VET 102", title: "Veterinary Medicine II", units: 4 }, - { code: "VET 111", title: "Veterinary Surgery I", units: 4 }, - { code: "VET 112", title: "Veterinary Surgery II", units: 4 }, - { code: "VET 121", title: "Theriogenology", units: 4 }, - { code: "VET 131", title: "Veterinary Public Health", units: 3 }, - { code: "VET 141", title: "Poultry Diseases", units: 3 }, - { code: "VET 151", title: "Swine Diseases", units: 3 }, - { code: "VET 161", title: "Ruminant Diseases", units: 3 }, - { code: "VET 171", title: "Small Animal Medicine", units: 3 }, - { code: "VET 181", title: "Veterinary Radiology", units: 3 }, - { code: "VET 190", title: "Clinical Clerkship", units: 12, gradeType: "S/U" }, - { code: "VET 200", title: "Thesis/Special Problem", units: 6 }, - { code: "CHEM 16", title: "General Chemistry I", units: 4 }, - { code: "CHEM 17", title: "General Chemistry II", units: 4 }, - { code: "CHEM 40", title: "Organic Chemistry I", units: 5 }, - { code: "CHEM 31", title: "Elementary Biochemistry", units: 5 }, - { code: "BIO 1", title: "General Biology I", units: 3 }, - { code: "PHYSICS 71", title: "Elementary Physics I", units: 4 }, - { code: "STAT 101", title: "Statistical Methods", units: 3 }, - { code: "ENG 10", title: "Writing of Scientific Papers", units: 3 }, - ], - requiredCodes: [ - "VET 11", "VET 12", "VET 21", "VET 22", "VET 31", "VET 41", "VET 51", - "VET 61", "VET 71", "VET 101", "VET 102", "VET 111", "VET 112", "VET 121", - "VET 131", "VET 141", "VET 151", "VET 161", "VET 171", "VET 181", "VET 190", - "CHEM 16", "CHEM 17", "CHEM 40", "CHEM 31", "BIO 1", "PHYSICS 71", - "STAT 101", "ENG 10" - ] - }, -}; - -// Group programs by college -const COLLEGES = { - "CAS": { name: "College of Arts and Sciences", programs: [] }, - "CAFS": { name: "College of Agriculture and Food Science", programs: [] }, - "CEAT": { name: "College of Engineering and Agro-Industrial Technology", programs: [] }, - "CFNR": { name: "College of Forestry and Natural Resources", programs: [] }, - "CEM": { name: "College of Economics and Management", programs: [] }, - "CHE": { name: "College of Human Ecology", programs: [] }, - "CVM": { name: "College of Veterinary Medicine", programs: [] }, - "CDC": { name: "College of Development Communication", programs: [] }, - "CPAf": { name: "College of Public Affairs and Development", programs: [] }, -}; - -// Populate colleges with programs -Object.values(UPLB_PROGRAMS).forEach(program => { - if (COLLEGES[program.college]) { - COLLEGES[program.college].programs.push(program); - } -}); - -// Current selected program (default to BSCS) -let currentProgram = UPLB_PROGRAMS["BSCS"]; - -// Get current curriculum based on selected program -function getCurrentCurriculum() { - return currentProgram; -} - -// Set current program -function setCurrentProgram(programCode) { - if (UPLB_PROGRAMS[programCode]) { - currentProgram = UPLB_PROGRAMS[programCode]; - updateTrackOptionsUI(); - updateCurriculumHint(); - return true; - } - return false; -} - -// Update the track options UI based on current program -function updateTrackOptionsUI() { - const trackSelector = document.getElementById('trackSelector'); - const trackOptions = document.getElementById('trackOptions'); - - if (!trackSelector || !trackOptions) return; - - // If program has no tracks, hide the entire track selector - if (!currentProgram.tracks) { - trackSelector.style.display = 'none'; - return; - } - - trackSelector.style.display = 'block'; - - // Build track options HTML - let html = ''; - for (const [trackKey, trackInfo] of Object.entries(currentProgram.tracks)) { - const isChecked = trackKey === (currentProgram.defaultTrack || 'sp') ? 'checked' : ''; - let electiveInfo = `${trackInfo.freeElectiveUnits} free elective units`; - if (trackInfo.majorElectiveUnits) { - electiveInfo += ` • ${trackInfo.majorElectiveUnits} major elective units`; - } - html += ` - - `; - } - - trackOptions.innerHTML = html; - - // Re-attach event listeners to the new radio buttons - document.querySelectorAll('input[name="track"]').forEach(radio => { - radio.addEventListener('change', (e) => { - if (typeof currentTrack !== 'undefined') { - currentTrack = e.target.value; - } - // Recalculate remaining courses with new track - if (window.gradesData && window.gradesData.completedCourses) { - displayRemainingCourses(window.gradesData.completedCourses); - } - }); - }); -} - -// Update the curriculum hint text -function updateCurriculumHint() { - const hintEl = document.getElementById('curriculumHint'); - if (hintEl && currentProgram) { - hintEl.textContent = `Based on ${currentProgram.name} curriculum`; - } -} - -// Detect which track the student is on based on completed courses -function detectTrack(completedCourses) { - if (!currentProgram.tracks) return null; - - const codes = completedCourses.map(c => c.code.toUpperCase().trim()); - - for (const [trackKey, track] of Object.entries(currentProgram.tracks)) { - if (codes.some(code => code.startsWith(track.code.toUpperCase()))) { - return trackKey; - } - } - - return null; -} - -// Get free elective units based on track -function getFreeElectiveUnits(track) { - if (!currentProgram.tracks) return 15; // Default - - if (track && currentProgram.tracks[track]) { - return currentProgram.tracks[track].freeElectiveUnits; - } - - // Return default track's units - const defaultTrack = currentProgram.defaultTrack || Object.keys(currentProgram.tracks)[0]; - return currentProgram.tracks[defaultTrack]?.freeElectiveUnits || 15; -} - -// Check if a course is a GE course (title starts with "(GE)") -function isGECourse(courseTitle) { - return courseTitle && courseTitle.trim().startsWith("(GE)"); -} - -// Check if a course is a required major course -function isRequiredCourse(courseCode) { - if (!currentProgram.requiredCodes) return false; - - const normalized = courseCode.toUpperCase().trim(); - return currentProgram.requiredCodes.some(code => - code.toUpperCase() === normalized - ); -} - -// Get remaining required courses -function getRemainingCourses(completedCourses) { - if (!currentProgram.majorCourses) return []; - - const completedCodes = new Set( - completedCourses.map(c => c.code.toUpperCase().trim()) - ); - - return currentProgram.majorCourses.filter(course => - !completedCodes.has(course.code.toUpperCase()) - ); -} - -// Count completed GE courses -function countCompletedGE(completedCourses) { - return completedCourses.filter(c => isGECourse(c.title)).length; -} - -// Get remaining GE slots -function getRemainingGESlots(completedCourses) { - const completed = countCompletedGE(completedCourses); - const required = currentProgram.geCoursesRequired || 9; - return Math.max(0, required - completed); -} - -// For backwards compatibility -const BSCS_CURRICULUM = UPLB_PROGRAMS["BSCS"]; diff --git a/curriculum_temp.js b/curriculum_temp.js deleted file mode 100644 index e48935f..0000000 --- a/curriculum_temp.js +++ /dev/null @@ -1,1423 +0,0 @@ -// UPLB Curriculum Database -// Complete undergraduate programs for University of the Philippines Los Baños -// NOTE: This is UPLB only - not UP Diliman, UP Manila, UP Baguio, etc. - -const UPLB_PROGRAMS = { - // ============================================================================ - // COLLEGE OF ARTS AND SCIENCES (CAS) - // ============================================================================ - - "BSCS": { - code: "BSCS", - name: "BS Computer Science", - college: "CAS", - collegeName: "College of Arts and Sciences", - available: true, - totalUnitsRequired: 140, - geCoursesRequired: 9, - tracks: { - thesis: { name: "Thesis Track", code: "CMSC 200", freeElectiveUnits: 15 }, - sp: { name: "Special Problem Track", code: "CMSC 190", freeElectiveUnits: 18 } - }, - defaultTrack: "sp", - majorCourses: [ - { code: "MATH 27", title: "Analytic Geometry and Calculus I", units: 3 }, - { code: "MATH 28", title: "Analytic Geometry and Calculus I", units: 3 }, - { code: "CMSC 12", title: "Foundations of Computer Science", units: 3 }, - { code: "CMSC 21", title: "Fundamentals of Programming", units: 3 }, - { code: "CMSC 22", title: "Object-Oriented Programming", units: 3 }, - { code: "CMSC 23", title: "Mobile Computing", units: 3 }, - { code: "CMSC 56", title: "Discrete Mathematical Structures in Computer Science I", units: 3 }, - { code: "CMSC 57", title: "Discrete Mathematical Structures in Computer Science II", units: 3 }, - { code: "CMSC 100", title: "Web Programming", units: 3 }, - { code: "CMSC 123", title: "Data Structures", units: 3 }, - { code: "CMSC 124", title: "Design and Implementation of Programming Languages", units: 3 }, - { code: "CMSC 125", title: "Operating Systems", units: 3 }, - { code: "CMSC 127", title: "File Processing and Database Systems", units: 3 }, - { code: "CMSC 128", title: "Introduction to Software Engineering", units: 3 }, - { code: "CMSC 130", title: "Logic Design and Digital Computer Circuits", units: 3 }, - { code: "CMSC 131", title: "Introduction to Computer Organization and Machine Level Programming", units: 3 }, - { code: "CMSC 132", title: "Computer Architecture", units: 3 }, - { code: "CMSC 137", title: "Data Communications and Networking", units: 3 }, - { code: "CMSC 141", title: "Automata and Language Theory", units: 3 }, - { code: "CMSC 142", title: "Design and Analysis of Algorithms", units: 3 }, - { code: "CMSC 150", title: "Numerical and Symbolic Computation", units: 3 }, - { code: "CMSC 170", title: "Introduction to Artificial Intelligence", units: 3 }, - { code: "CMSC 173", title: "Human Computer Interaction", units: 3 }, - { code: "CMSC 180", title: "Introduction to Parallel Computing", units: 3 }, - { code: "CMSC 190", title: "Special Problem", units: 3, track: "sp" }, - { code: "CMSC 200", title: "Undergraduate Thesis", units: 6, track: "thesis" }, - { code: "STAT 101", title: "Statistical Methods", units: 3 }, - { code: "ENG 10", title: "Writing of Scientific Papers", units: 3 }, - ], - requiredCodes: [ - "MATH 27", "MATH 28", "CMSC 12", "CMSC 21", "CMSC 22", "CMSC 23", - "CMSC 56", "CMSC 57", "CMSC 100", "CMSC 123", "CMSC 124", "CMSC 125", - "CMSC 127", "CMSC 128", "CMSC 130", "CMSC 131", "CMSC 132", "CMSC 137", - "CMSC 141", "CMSC 142", "CMSC 150", "CMSC 170", "CMSC 173", "CMSC 180", - "STAT 101", "ENG 10" - ] - }, - - "BSMATH": { - code: "BSMATH", - name: "BS Mathematics", - college: "CAS", - collegeName: "College of Arts and Sciences", - available: true, - totalUnitsRequired: 140, - geCoursesRequired: 9, - tracks: { - thesis: { name: "Thesis Track", code: "MATH 200", freeElectiveUnits: 15 }, - sp: { name: "Special Problem Track", code: "MATH 190", freeElectiveUnits: 18 } - }, - defaultTrack: "sp", - majorCourses: [ - { code: "MATH 27", title: "Analytic Geometry and Calculus I", units: 3 }, - { code: "MATH 28", title: "Analytic Geometry and Calculus I", units: 3 }, - { code: "MATH 36", title: "Mathematical Analysis I", units: 3 }, - { code: "MATH 37", title: "Mathematical Analysis II", units: 3 }, - { code: "MATH 38", title: "Mathematical Analysis III", units: 3 }, - { code: "MATH 40", title: "Linear Algebra", units: 3 }, - { code: "MATH 100.1", title: "Abstract Algebra I", units: 3 }, - { code: "MATH 114", title: "Differential Equations", units: 3 }, - { code: "MATH 121.1", title: "Real Analysis I", units: 3 }, - { code: "MATH 122.1", title: "Complex Analysis I", units: 3 }, - { code: "MATH 171.1", title: "Linear Programming", units: 3 }, - { code: "MATH 176", title: "Numerical Analysis I", units: 3 }, - { code: "MATH 190", title: "Special Problem", units: 3, track: "sp" }, - { code: "MATH 200", title: "Undergraduate Thesis", units: 6, track: "thesis" }, - { code: "CMSC 21", title: "Fundamentals of Programming", units: 3 }, - { code: "STAT 101", title: "Statistical Methods", units: 3 }, - { code: "ENG 10", title: "Writing of Scientific Papers", units: 3 }, - ], - requiredCodes: [ - "MATH 27", "MATH 28", "MATH 36", "MATH 37", "MATH 38", "MATH 40", - "MATH 100.1", "MATH 114", "MATH 121.1", "MATH 122.1", "MATH 171.1", "MATH 176", - "CMSC 21", "STAT 101", "ENG 10" - ] - }, - - "BSAPMATH": { - code: "BSAPMATH", - name: "BS Applied Mathematics", - college: "CAS", - collegeName: "College of Arts and Sciences", - available: true, - totalUnitsRequired: 140, - geCoursesRequired: 9, - tracks: { - thesis: { name: "Thesis Track", code: "AMAT 200", freeElectiveUnits: 15 }, - sp: { name: "Special Problem Track", code: "AMAT 190", freeElectiveUnits: 18 } - }, - defaultTrack: "sp", - majorCourses: [ - { code: "MATH 27", title: "Analytic Geometry and Calculus I", units: 3 }, - { code: "MATH 28", title: "Analytic Geometry and Calculus I", units: 3 }, - { code: "MATH 36", title: "Mathematical Analysis I", units: 3 }, - { code: "MATH 37", title: "Mathematical Analysis II", units: 3 }, - { code: "MATH 38", title: "Mathematical Analysis III", units: 3 }, - { code: "MATH 40", title: "Linear Algebra", units: 3 }, - { code: "MATH 114", title: "Differential Equations", units: 3 }, - { code: "AMAT 131", title: "Mathematical Modeling", units: 3 }, - { code: "AMAT 151", title: "Operations Research I", units: 3 }, - { code: "AMAT 152", title: "Operations Research II", units: 3 }, - { code: "MATH 176", title: "Numerical Analysis I", units: 3 }, - { code: "AMAT 167", title: "Mathematical Finance", units: 3 }, - { code: "AMAT 190", title: "Special Problem", units: 3, track: "sp" }, - { code: "AMAT 200", title: "Undergraduate Thesis", units: 6, track: "thesis" }, - { code: "CMSC 21", title: "Fundamentals of Programming", units: 3 }, - { code: "STAT 101", title: "Statistical Methods", units: 3 }, - { code: "ENG 10", title: "Writing of Scientific Papers", units: 3 }, - ], - requiredCodes: [ - "MATH 27", "MATH 28", "MATH 36", "MATH 37", "MATH 38", "MATH 40", - "MATH 114", "AMAT 131", "AMAT 151", "AMAT 152", "MATH 176", "AMAT 167", - "CMSC 21", "STAT 101", "ENG 10" - ] - }, - - "BSSTAT": { - code: "BSSTAT", - name: "BS Statistics", - college: "CAS", - collegeName: "College of Arts and Sciences", - available: true, - totalUnitsRequired: 140, - geCoursesRequired: 9, - tracks: { - thesis: { name: "Thesis Track", code: "STAT 200", freeElectiveUnits: 15 }, - sp: { name: "Special Problem Track", code: "STAT 190", freeElectiveUnits: 18 } - }, - defaultTrack: "sp", - majorCourses: [ - { code: "MATH 27", title: "Analytic Geometry and Calculus I", units: 3 }, - { code: "MATH 28", title: "Analytic Geometry and Calculus I", units: 3 }, - { code: "MATH 40", title: "Linear Algebra", units: 3 }, - { code: "STAT 101", title: "Statistical Methods", units: 3 }, - { code: "STAT 115", title: "Probability Theory", units: 3 }, - { code: "STAT 116", title: "Statistical Inference I", units: 3 }, - { code: "STAT 117", title: "Statistical Inference II", units: 3 }, - { code: "STAT 121", title: "Regression Analysis", units: 3 }, - { code: "STAT 126", title: "Design and Analysis of Experiments", units: 3 }, - { code: "STAT 131", title: "Sample Survey Designs", units: 3 }, - { code: "STAT 136", title: "Nonparametric Statistical Methods", units: 3 }, - { code: "STAT 137", title: "Multivariate Analysis", units: 3 }, - { code: "STAT 138", title: "Time Series Analysis", units: 3 }, - { code: "STAT 145", title: "Stochastic Processes", units: 3 }, - { code: "STAT 150", title: "Statistical Computing", units: 3 }, - { code: "STAT 190", title: "Special Problem", units: 3, track: "sp" }, - { code: "STAT 200", title: "Undergraduate Thesis", units: 6, track: "thesis" }, - { code: "CMSC 21", title: "Fundamentals of Programming", units: 3 }, - { code: "ENG 10", title: "Writing of Scientific Papers", units: 3 }, - ], - requiredCodes: [ - "MATH 27", "MATH 28", "MATH 40", "STAT 101", "STAT 115", "STAT 116", - "STAT 117", "STAT 121", "STAT 126", "STAT 131", "STAT 136", "STAT 137", - "STAT 138", "STAT 145", "STAT 150", "CMSC 21", "ENG 10" - ] - }, - - "BSMST": { - code: "BSMST", - name: "BS Mathematics and Science Teaching", - college: "CAS", - collegeName: "College of Arts and Sciences", - available: true, - totalUnitsRequired: 150, - geCoursesRequired: 9, - tracks: null, - defaultTrack: null, - majorCourses: [ - { code: "MATH 27", title: "Analytic Geometry and Calculus I", units: 3 }, - { code: "MATH 28", title: "Analytic Geometry and Calculus I", units: 3 }, - { code: "MATH 36", title: "Mathematical Analysis I", units: 3 }, - { code: "MATH 37", title: "Mathematical Analysis II", units: 3 }, - { code: "MATH 40", title: "Linear Algebra", units: 3 }, - { code: "MATH 114", title: "Differential Equations", units: 3 }, - { code: "STAT 101", title: "Statistical Methods", units: 3 }, - { code: "EDUC 100", title: "The Teaching Profession", units: 3 }, - { code: "EDUC 101", title: "The Child and Adolescent Learner", units: 3 }, - { code: "EDUC 102", title: "Facilitating Learning", units: 3 }, - { code: "EDUC 103", title: "Curriculum Development", units: 3 }, - { code: "EDUC 110", title: "Principles of Teaching", units: 3 }, - { code: "EDUC 160", title: "Educational Assessment", units: 3 }, - { code: "EDUC 190", title: "Practice Teaching", units: 6, gradeType: "S/U" }, - { code: "ENG 10", title: "Writing of Scientific Papers", units: 3 }, - ], - requiredCodes: [ - "MATH 27", "MATH 28", "MATH 36", "MATH 37", "MATH 40", "MATH 114", - "STAT 101", "EDUC 100", "EDUC 101", "EDUC 102", "EDUC 103", "EDUC 110", - "EDUC 160", "EDUC 190", "ENG 10" - ] - }, - - "BSBIO": { - code: "BSBIO", - name: "BS Biology", - college: "CAS", - collegeName: "College of Arts and Sciences", - available: true, - totalUnitsRequired: 155, - geCoursesRequired: 9, - tracks: { - thesis: { name: "Thesis Track", code: "BIO 200", freeElectiveUnits: 12 }, - sp: { name: "Special Problem Track", code: "BIO 190", freeElectiveUnits: 15 } - }, - defaultTrack: "sp", - majorCourses: [ - { code: "BIO 1", title: "General Biology I", units: 3 }, - { code: "BIO 2", title: "General Biology II", units: 3 }, - { code: "BIO 30", title: "Cell Biology", units: 4 }, - { code: "BIO 31", title: "Genetics", units: 4 }, - { code: "BIO 100", title: "Taxonomy", units: 3 }, - { code: "BIO 110", title: "Developmental Biology", units: 4 }, - { code: "BIO 120", title: "Ecology", units: 4 }, - { code: "BIO 130", title: "Molecular Biology", units: 4 }, - { code: "BIO 140", title: "Physiology", units: 4 }, - { code: "BIO 150", title: "Evolution", units: 3 }, - { code: "BIO 190", title: "Special Problem", units: 3, track: "sp" }, - { code: "BIO 200", title: "Undergraduate Thesis", units: 6, track: "thesis" }, - { code: "CHEM 16", title: "General Chemistry I", units: 4 }, - { code: "CHEM 17", title: "General Chemistry II", units: 4 }, - { code: "CHEM 40", title: "Organic Chemistry I", units: 5 }, - { code: "CHEM 41", title: "Organic Chemistry II", units: 5 }, - { code: "CHEM 31", title: "Elementary Biochemistry", units: 5 }, - { code: "MATH 27", title: "Analytic Geometry and Calculus I", units: 3 }, - { code: "PHYSICS 71", title: "Elementary Physics I", units: 4 }, - { code: "PHYSICS 72", title: "Elementary Physics II", units: 4 }, - { code: "STAT 101", title: "Statistical Methods", units: 3 }, - { code: "ENG 10", title: "Writing of Scientific Papers", units: 3 }, - ], - requiredCodes: [ - "BIO 1", "BIO 2", "BIO 30", "BIO 31", "BIO 100", "BIO 110", "BIO 120", - "BIO 130", "BIO 140", "BIO 150", "CHEM 16", "CHEM 17", "CHEM 40", - "CHEM 41", "CHEM 31", "MATH 27", "PHYSICS 71", "PHYSICS 72", "STAT 101", "ENG 10" - ] - }, - - "BSCHEM": { - code: "BSCHEM", - name: "BS Chemistry", - college: "CAS", - collegeName: "College of Arts and Sciences", - available: true, - totalUnitsRequired: 155, - geCoursesRequired: 9, - tracks: { - thesis: { name: "Thesis Track", code: "CHEM 200", freeElectiveUnits: 12 }, - sp: { name: "Special Problem Track", code: "CHEM 190", freeElectiveUnits: 15 } - }, - defaultTrack: "sp", - majorCourses: [ - { code: "CHEM 16", title: "General Chemistry I", units: 4 }, - { code: "CHEM 17", title: "General Chemistry II", units: 4 }, - { code: "CHEM 26", title: "Analytical Chemistry I", units: 5 }, - { code: "CHEM 27", title: "Analytical Chemistry II", units: 5 }, - { code: "CHEM 40", title: "Organic Chemistry I", units: 5 }, - { code: "CHEM 41", title: "Organic Chemistry II", units: 5 }, - { code: "CHEM 110", title: "Inorganic Chemistry", units: 3 }, - { code: "CHEM 120", title: "Physical Chemistry I", units: 4 }, - { code: "CHEM 121", title: "Physical Chemistry II", units: 4 }, - { code: "CHEM 130", title: "Instrumental Methods of Analysis", units: 4 }, - { code: "CHEM 140", title: "Biochemistry I", units: 4 }, - { code: "CHEM 141", title: "Biochemistry II", units: 4 }, - { code: "CHEM 190", title: "Special Problem", units: 3, track: "sp" }, - { code: "CHEM 200", title: "Undergraduate Thesis", units: 6, track: "thesis" }, - { code: "MATH 27", title: "Analytic Geometry and Calculus I", units: 3 }, - { code: "MATH 28", title: "Analytic Geometry and Calculus I", units: 3 }, - { code: "PHYSICS 71", title: "Elementary Physics I", units: 4 }, - { code: "PHYSICS 72", title: "Elementary Physics II", units: 4 }, - { code: "STAT 101", title: "Statistical Methods", units: 3 }, - { code: "ENG 10", title: "Writing of Scientific Papers", units: 3 }, - ], - requiredCodes: [ - "CHEM 16", "CHEM 17", "CHEM 26", "CHEM 27", "CHEM 40", "CHEM 41", - "CHEM 110", "CHEM 120", "CHEM 121", "CHEM 130", "CHEM 140", "CHEM 141", - "MATH 27", "MATH 28", "PHYSICS 71", "PHYSICS 72", "STAT 101", "ENG 10" - ] - }, - - "BSAPPHY": { - code: "BSAPPHY", - name: "BS Applied Physics", - college: "CAS", - collegeName: "College of Arts and Sciences", - available: true, - totalUnitsRequired: 155, - geCoursesRequired: 9, - tracks: { - thesis: { name: "Thesis Track", code: "PHYSICS 200", freeElectiveUnits: 12 }, - sp: { name: "Special Problem Track", code: "PHYSICS 190", freeElectiveUnits: 15 } - }, - defaultTrack: "sp", - majorCourses: [ - { code: "PHYSICS 71", title: "Elementary Physics I", units: 4 }, - { code: "PHYSICS 72", title: "Elementary Physics II", units: 4 }, - { code: "PHYSICS 73", title: "Elementary Physics III", units: 4 }, - { code: "PHYSICS 101", title: "Mathematical Methods of Physics I", units: 3 }, - { code: "PHYSICS 102", title: "Mathematical Methods of Physics II", units: 3 }, - { code: "PHYSICS 111", title: "Classical Mechanics", units: 3 }, - { code: "PHYSICS 121", title: "Electricity and Magnetism", units: 3 }, - { code: "PHYSICS 131", title: "Introduction to Quantum Mechanics", units: 3 }, - { code: "PHYSICS 141", title: "Thermodynamics and Statistical Mechanics", units: 3 }, - { code: "PHYSICS 151", title: "Electronics", units: 4 }, - { code: "PHYSICS 161", title: "Optics", units: 3 }, - { code: "PHYSICS 171", title: "Solid State Physics", units: 3 }, - { code: "PHYSICS 190", title: "Special Problem", units: 3, track: "sp" }, - { code: "PHYSICS 200", title: "Undergraduate Thesis", units: 6, track: "thesis" }, - { code: "MATH 27", title: "Analytic Geometry and Calculus I", units: 3 }, - { code: "MATH 28", title: "Analytic Geometry and Calculus I", units: 3 }, - { code: "MATH 36", title: "Mathematical Analysis I", units: 3 }, - { code: "MATH 37", title: "Mathematical Analysis II", units: 3 }, - { code: "MATH 114", title: "Differential Equations", units: 3 }, - { code: "ENG 10", title: "Writing of Scientific Papers", units: 3 }, - ], - requiredCodes: [ - "PHYSICS 71", "PHYSICS 72", "PHYSICS 73", "PHYSICS 101", "PHYSICS 102", - "PHYSICS 111", "PHYSICS 121", "PHYSICS 131", "PHYSICS 141", "PHYSICS 151", - "PHYSICS 161", "PHYSICS 171", "MATH 27", "MATH 28", "MATH 36", "MATH 37", - "MATH 114", "ENG 10" - ] - }, - - "ABCOMM": { - code: "ABCOMM", - name: "AB Communication Arts", - college: "CAS", - collegeName: "College of Arts and Sciences", - available: true, - totalUnitsRequired: 140, - geCoursesRequired: 9, - tracks: { - thesis: { name: "Thesis Track", code: "COMM 200", freeElectiveUnits: 15 }, - sp: { name: "Special Problem Track", code: "COMM 190", freeElectiveUnits: 18 } - }, - defaultTrack: "sp", - majorCourses: [ - { code: "COMM 10", title: "Introduction to Communication", units: 3 }, - { code: "COMM 20", title: "Principles of Speech Communication", units: 3 }, - { code: "COMM 30", title: "Writing for Various Media", units: 3 }, - { code: "COMM 100", title: "Theories of Communication", units: 3 }, - { code: "COMM 110", title: "Rhetorical Criticism", units: 3 }, - { code: "COMM 120", title: "Intercultural Communication", units: 3 }, - { code: "COMM 130", title: "Organizational Communication", units: 3 }, - { code: "COMM 140", title: "Mass Communication", units: 3 }, - { code: "COMM 150", title: "Communication Research", units: 3 }, - { code: "COMM 160", title: "Seminar in Communication Arts", units: 3 }, - { code: "COMM 190", title: "Special Problem", units: 3, track: "sp" }, - { code: "COMM 200", title: "Undergraduate Thesis", units: 6, track: "thesis" }, - { code: "ENG 10", title: "Writing of Scientific Papers", units: 3 }, - { code: "ENG 11", title: "English for Academic Writing", units: 3 }, - { code: "FIL 40", title: "Panitikang Filipino", units: 3 }, - ], - requiredCodes: [ - "COMM 10", "COMM 20", "COMM 30", "COMM 100", "COMM 110", "COMM 120", - "COMM 130", "COMM 140", "COMM 150", "COMM 160", "ENG 10", "ENG 11", "FIL 40" - ] - }, - - "ABSOCIO": { - code: "ABSOCIO", - name: "AB Sociology", - college: "CAS", - collegeName: "College of Arts and Sciences", - available: true, - totalUnitsRequired: 140, - geCoursesRequired: 9, - tracks: { - thesis: { name: "Thesis Track", code: "SOC 200", freeElectiveUnits: 15 }, - sp: { name: "Special Problem Track", code: "SOC 190", freeElectiveUnits: 18 } - }, - defaultTrack: "sp", - majorCourses: [ - { code: "SOC 10", title: "Introduction to Sociology", units: 3 }, - { code: "SOC 100", title: "Classical Sociological Theory", units: 3 }, - { code: "SOC 101", title: "Contemporary Sociological Theory", units: 3 }, - { code: "SOC 110", title: "Social Research Methods I", units: 3 }, - { code: "SOC 111", title: "Social Research Methods II", units: 3 }, - { code: "SOC 120", title: "Sociology of Philippine Society", units: 3 }, - { code: "SOC 130", title: "Rural Sociology", units: 3 }, - { code: "SOC 140", title: "Urban Sociology", units: 3 }, - { code: "SOC 150", title: "Social Stratification", units: 3 }, - { code: "SOC 160", title: "Sociology of Development", units: 3 }, - { code: "SOC 190", title: "Special Problem", units: 3, track: "sp" }, - { code: "SOC 200", title: "Undergraduate Thesis", units: 6, track: "thesis" }, - { code: "STAT 101", title: "Statistical Methods", units: 3 }, - { code: "ENG 10", title: "Writing of Scientific Papers", units: 3 }, - ], - requiredCodes: [ - "SOC 10", "SOC 100", "SOC 101", "SOC 110", "SOC 111", "SOC 120", - "SOC 130", "SOC 140", "SOC 150", "SOC 160", "STAT 101", "ENG 10" - ] - }, - - "ABPHILO": { - code: "ABPHILO", - name: "AB Philosophy", - college: "CAS", - collegeName: "College of Arts and Sciences", - available: true, - totalUnitsRequired: 140, - geCoursesRequired: 9, - tracks: { - thesis: { name: "Thesis Track", code: "PHILO 200", freeElectiveUnits: 15 }, - sp: { name: "Special Problem Track", code: "PHILO 190", freeElectiveUnits: 18 } - }, - defaultTrack: "sp", - majorCourses: [ - { code: "PHILO 1", title: "Introduction to Philosophy", units: 3 }, - { code: "PHILO 10", title: "Logic", units: 3 }, - { code: "PHILO 100", title: "Ancient Philosophy", units: 3 }, - { code: "PHILO 101", title: "Medieval Philosophy", units: 3 }, - { code: "PHILO 102", title: "Modern Philosophy", units: 3 }, - { code: "PHILO 103", title: "Contemporary Philosophy", units: 3 }, - { code: "PHILO 110", title: "Ethics", units: 3 }, - { code: "PHILO 120", title: "Metaphysics", units: 3 }, - { code: "PHILO 130", title: "Epistemology", units: 3 }, - { code: "PHILO 140", title: "Philosophy of Mind", units: 3 }, - { code: "PHILO 150", title: "Filipino Philosophy", units: 3 }, - { code: "PHILO 190", title: "Special Problem", units: 3, track: "sp" }, - { code: "PHILO 200", title: "Undergraduate Thesis", units: 6, track: "thesis" }, - { code: "ENG 10", title: "Writing of Scientific Papers", units: 3 }, - ], - requiredCodes: [ - "PHILO 1", "PHILO 10", "PHILO 100", "PHILO 101", "PHILO 102", "PHILO 103", - "PHILO 110", "PHILO 120", "PHILO 130", "PHILO 140", "PHILO 150", "ENG 10" - ] - }, - - // ============================================================================ - // COLLEGE OF AGRICULTURE AND FOOD SCIENCE (CAFS) - // ============================================================================ - - "BSAGRI": { - code: "BSAGRI", - name: "BS Agriculture", - college: "CAFS", - collegeName: "College of Agriculture and Food Science", - available: true, - totalUnitsRequired: 160, - geCoursesRequired: 9, - tracks: { - thesis: { name: "Thesis Track", code: "AGRI 200", freeElectiveUnits: 12 }, - sp: { name: "Special Problem Track", code: "AGRI 190", freeElectiveUnits: 15 } - }, - defaultTrack: "sp", - majorCourses: [ - { code: "AGRI 10", title: "Introduction to Agriculture", units: 3 }, - { code: "AGRI 111", title: "Crop Science", units: 3 }, - { code: "AGRI 112", title: "Soil Science", units: 4 }, - { code: "AGRI 120", title: "Plant Pathology", units: 3 }, - { code: "AGRI 121", title: "Agricultural Entomology", units: 3 }, - { code: "AGRI 130", title: "Animal Science", units: 4 }, - { code: "AGRI 131", title: "Animal Nutrition", units: 3 }, - { code: "AGRI 140", title: "Agricultural Economics", units: 3 }, - { code: "AGRI 150", title: "Farm Management", units: 3 }, - { code: "AGRI 160", title: "Agricultural Extension", units: 3 }, - { code: "AGRI 170", title: "Seed Technology", units: 3 }, - { code: "AGRI 180", title: "Irrigation and Drainage", units: 3 }, - { code: "AGRI 190", title: "Special Problem", units: 3, track: "sp" }, - { code: "AGRI 191", title: "On-the-Job Training", units: 3, gradeType: "S/U" }, - { code: "AGRI 200", title: "Undergraduate Thesis", units: 6, track: "thesis" }, - { code: "CHEM 16", title: "General Chemistry I", units: 4 }, - { code: "CHEM 17", title: "General Chemistry II", units: 4 }, - { code: "BIO 1", title: "General Biology I", units: 3 }, - { code: "STAT 101", title: "Statistical Methods", units: 3 }, - { code: "ENG 10", title: "Writing of Scientific Papers", units: 3 }, - ], - requiredCodes: [ - "AGRI 10", "AGRI 111", "AGRI 112", "AGRI 120", "AGRI 121", "AGRI 130", - "AGRI 131", "AGRI 140", "AGRI 150", "AGRI 160", "AGRI 170", "AGRI 180", - "AGRI 191", "CHEM 16", "CHEM 17", "BIO 1", "STAT 101", "ENG 10" - ] - }, - - "BSABIO": { - code: "BSABIO", - name: "BS Agricultural Biotechnology", - college: "CAFS", - collegeName: "College of Agriculture and Food Science", - available: true, - totalUnitsRequired: 160, - geCoursesRequired: 9, - tracks: { - thesis: { name: "Thesis Track", code: "ABIO 200", freeElectiveUnits: 12 }, - sp: { name: "Special Problem Track", code: "ABIO 190", freeElectiveUnits: 15 } - }, - defaultTrack: "sp", - majorCourses: [ - { code: "ABIO 10", title: "Introduction to Agricultural Biotechnology", units: 3 }, - { code: "ABIO 100", title: "Principles of Biotechnology", units: 3 }, - { code: "ABIO 110", title: "Plant Biotechnology", units: 4 }, - { code: "ABIO 120", title: "Animal Biotechnology", units: 4 }, - { code: "ABIO 130", title: "Microbial Biotechnology", units: 4 }, - { code: "ABIO 140", title: "Molecular Biology Techniques", units: 4 }, - { code: "ABIO 150", title: "Genetic Engineering", units: 3 }, - { code: "ABIO 160", title: "Bioinformatics", units: 3 }, - { code: "ABIO 170", title: "Biosafety and Bioethics", units: 3 }, - { code: "ABIO 190", title: "Special Problem", units: 3, track: "sp" }, - { code: "ABIO 191", title: "On-the-Job Training", units: 3, gradeType: "S/U" }, - { code: "ABIO 200", title: "Undergraduate Thesis", units: 6, track: "thesis" }, - { code: "CHEM 16", title: "General Chemistry I", units: 4 }, - { code: "CHEM 17", title: "General Chemistry II", units: 4 }, - { code: "CHEM 40", title: "Organic Chemistry I", units: 5 }, - { code: "CHEM 31", title: "Elementary Biochemistry", units: 5 }, - { code: "BIO 1", title: "General Biology I", units: 3 }, - { code: "BIO 31", title: "Genetics", units: 4 }, - { code: "STAT 101", title: "Statistical Methods", units: 3 }, - { code: "ENG 10", title: "Writing of Scientific Papers", units: 3 }, - ], - requiredCodes: [ - "ABIO 10", "ABIO 100", "ABIO 110", "ABIO 120", "ABIO 130", "ABIO 140", - "ABIO 150", "ABIO 160", "ABIO 170", "ABIO 191", "CHEM 16", "CHEM 17", - "CHEM 40", "CHEM 31", "BIO 1", "BIO 31", "STAT 101", "ENG 10" - ] - }, - - "BSFST": { - code: "BSFST", - name: "BS Food Science and Technology", - college: "CAFS", - collegeName: "College of Agriculture and Food Science", - available: true, - totalUnitsRequired: 165, - geCoursesRequired: 9, - tracks: { - thesis: { name: "Thesis Track", code: "FST 200", freeElectiveUnits: 9 }, - sp: { name: "Special Problem Track", code: "FST 190", freeElectiveUnits: 12 } - }, - defaultTrack: "sp", - majorCourses: [ - { code: "FST 10", title: "Introduction to Food Science and Technology", units: 3 }, - { code: "FST 100", title: "Food Chemistry", units: 4 }, - { code: "FST 101", title: "Food Analysis", units: 4 }, - { code: "FST 110", title: "Food Microbiology", units: 4 }, - { code: "FST 120", title: "Food Processing I", units: 4 }, - { code: "FST 121", title: "Food Processing II", units: 4 }, - { code: "FST 130", title: "Food Engineering", units: 3 }, - { code: "FST 140", title: "Food Quality Assurance", units: 3 }, - { code: "FST 150", title: "Sensory Evaluation of Foods", units: 3 }, - { code: "FST 160", title: "Food Packaging", units: 3 }, - { code: "FST 170", title: "Food Product Development", units: 3 }, - { code: "FST 180", title: "Food Laws and Regulations", units: 2 }, - { code: "FST 190", title: "Special Problem", units: 3, track: "sp" }, - { code: "FST 191", title: "On-the-Job Training", units: 3, gradeType: "S/U" }, - { code: "FST 200", title: "Undergraduate Thesis", units: 6, track: "thesis" }, - { code: "CHEM 16", title: "General Chemistry I", units: 4 }, - { code: "CHEM 17", title: "General Chemistry II", units: 4 }, - { code: "CHEM 40", title: "Organic Chemistry I", units: 5 }, - { code: "CHEM 31", title: "Elementary Biochemistry", units: 5 }, - { code: "STAT 101", title: "Statistical Methods", units: 3 }, - { code: "ENG 10", title: "Writing of Scientific Papers", units: 3 }, - ], - requiredCodes: [ - "FST 10", "FST 100", "FST 101", "FST 110", "FST 120", "FST 121", - "FST 130", "FST 140", "FST 150", "FST 160", "FST 170", "FST 180", "FST 191", - "CHEM 16", "CHEM 17", "CHEM 40", "CHEM 31", "STAT 101", "ENG 10" - ] - }, - - "BSACHEM": { - code: "BSACHEM", - name: "BS Agricultural Chemistry", - college: "CAFS", - collegeName: "College of Agriculture and Food Science", - available: true, - totalUnitsRequired: 160, - geCoursesRequired: 9, - tracks: { - thesis: { name: "Thesis Track", code: "AGCHEM 200", freeElectiveUnits: 12 }, - sp: { name: "Special Problem Track", code: "AGCHEM 190", freeElectiveUnits: 15 } - }, - defaultTrack: "sp", - majorCourses: [ - { code: "AGCHEM 10", title: "Introduction to Agricultural Chemistry", units: 3 }, - { code: "AGCHEM 100", title: "Soil Chemistry", units: 4 }, - { code: "AGCHEM 110", title: "Plant Biochemistry", units: 4 }, - { code: "AGCHEM 120", title: "Food Chemistry", units: 4 }, - { code: "AGCHEM 130", title: "Pesticide Chemistry", units: 3 }, - { code: "AGCHEM 140", title: "Environmental Chemistry", units: 3 }, - { code: "AGCHEM 150", title: "Instrumental Analysis", units: 4 }, - { code: "AGCHEM 190", title: "Special Problem", units: 3, track: "sp" }, - { code: "AGCHEM 191", title: "On-the-Job Training", units: 3, gradeType: "S/U" }, - { code: "AGCHEM 200", title: "Undergraduate Thesis", units: 6, track: "thesis" }, - { code: "CHEM 16", title: "General Chemistry I", units: 4 }, - { code: "CHEM 17", title: "General Chemistry II", units: 4 }, - { code: "CHEM 26", title: "Analytical Chemistry I", units: 5 }, - { code: "CHEM 40", title: "Organic Chemistry I", units: 5 }, - { code: "CHEM 41", title: "Organic Chemistry II", units: 5 }, - { code: "CHEM 31", title: "Elementary Biochemistry", units: 5 }, - { code: "MATH 27", title: "Analytic Geometry and Calculus I", units: 3 }, - { code: "PHYSICS 71", title: "Elementary Physics I", units: 4 }, - { code: "STAT 101", title: "Statistical Methods", units: 3 }, - { code: "ENG 10", title: "Writing of Scientific Papers", units: 3 }, - ], - requiredCodes: [ - "AGCHEM 10", "AGCHEM 100", "AGCHEM 110", "AGCHEM 120", "AGCHEM 130", - "AGCHEM 140", "AGCHEM 150", "AGCHEM 191", "CHEM 16", "CHEM 17", "CHEM 26", - "CHEM 40", "CHEM 41", "CHEM 31", "MATH 27", "PHYSICS 71", "STAT 101", "ENG 10" - ] - }, - - // ============================================================================ - // COLLEGE OF ENGINEERING AND AGRO-INDUSTRIAL TECHNOLOGY (CEAT) - // ============================================================================ - - "BSABE": { - code: "BSABE", - name: "BS Agricultural and Biosystems Engineering", - college: "CEAT", - collegeName: "College of Engineering and Agro-Industrial Technology", - available: true, - totalUnitsRequired: 180, - geCoursesRequired: 9, - tracks: null, - defaultTrack: null, - majorCourses: [ - { code: "ABE 21", title: "Engineering Drawing", units: 2 }, - { code: "ABE 31", title: "Computer-Aided Drafting", units: 2 }, - { code: "ABE 51", title: "Surveying", units: 3 }, - { code: "ABE 100", title: "Principles of Agricultural Engineering", units: 3 }, - { code: "ABE 111", title: "Farm Power and Machinery", units: 4 }, - { code: "ABE 121", title: "Soil and Water Conservation Engineering", units: 4 }, - { code: "ABE 131", title: "Irrigation and Drainage Engineering", units: 4 }, - { code: "ABE 141", title: "Agricultural Structures", units: 4 }, - { code: "ABE 151", title: "Post-Harvest Engineering", units: 4 }, - { code: "ABE 161", title: "Food Engineering", units: 4 }, - { code: "ABE 171", title: "Biosystems Engineering", units: 3 }, - { code: "ABE 181", title: "Environmental Engineering", units: 3 }, - { code: "ABE 190", title: "Engineering Design Project", units: 3 }, - { code: "ABE 191", title: "Engineering Internship", units: 3, gradeType: "S/U" }, - { code: "ABE 200", title: "Undergraduate Thesis", units: 6 }, - { code: "MATH 27", title: "Analytic Geometry and Calculus I", units: 3 }, - { code: "MATH 28", title: "Analytic Geometry and Calculus I", units: 3 }, - { code: "MATH 36", title: "Mathematical Analysis I", units: 3 }, - { code: "MATH 114", title: "Differential Equations", units: 3 }, - { code: "PHYSICS 71", title: "Elementary Physics I", units: 4 }, - { code: "PHYSICS 72", title: "Elementary Physics II", units: 4 }, - { code: "CHEM 16", title: "General Chemistry I", units: 4 }, - { code: "ENG 10", title: "Writing of Scientific Papers", units: 3 }, - ], - requiredCodes: [ - "ABE 21", "ABE 31", "ABE 51", "ABE 100", "ABE 111", "ABE 121", "ABE 131", - "ABE 141", "ABE 151", "ABE 161", "ABE 171", "ABE 181", "ABE 190", "ABE 191", - "MATH 27", "MATH 28", "MATH 36", "MATH 114", "PHYSICS 71", "PHYSICS 72", - "CHEM 16", "ENG 10" - ] - }, - - "BSCHE": { - code: "BSCHE", - name: "BS Chemical Engineering", - college: "CEAT", - collegeName: "College of Engineering and Agro-Industrial Technology", - available: true, - totalUnitsRequired: 180, - geCoursesRequired: 9, - tracks: null, - defaultTrack: null, - majorCourses: [ - { code: "CHE 21", title: "Material and Energy Balances", units: 4 }, - { code: "CHE 31", title: "Fluid Mechanics", units: 3 }, - { code: "CHE 41", title: "Heat Transfer", units: 3 }, - { code: "CHE 51", title: "Mass Transfer Operations I", units: 4 }, - { code: "CHE 52", title: "Mass Transfer Operations II", units: 4 }, - { code: "CHE 101", title: "Chemical Engineering Thermodynamics I", units: 3 }, - { code: "CHE 102", title: "Chemical Engineering Thermodynamics II", units: 3 }, - { code: "CHE 111", title: "Chemical Reaction Engineering", units: 4 }, - { code: "CHE 121", title: "Process Control", units: 3 }, - { code: "CHE 131", title: "Chemical Engineering Plant Design", units: 4 }, - { code: "CHE 141", title: "Biochemical Engineering", units: 3 }, - { code: "CHE 151", title: "Environmental Engineering", units: 3 }, - { code: "CHE 190", title: "Engineering Design Project", units: 3 }, - { code: "CHE 191", title: "Engineering Internship", units: 3, gradeType: "S/U" }, - { code: "CHEM 16", title: "General Chemistry I", units: 4 }, - { code: "CHEM 17", title: "General Chemistry II", units: 4 }, - { code: "CHEM 26", title: "Analytical Chemistry I", units: 5 }, - { code: "CHEM 40", title: "Organic Chemistry I", units: 5 }, - { code: "CHEM 120", title: "Physical Chemistry I", units: 4 }, - { code: "MATH 27", title: "Analytic Geometry and Calculus I", units: 3 }, - { code: "MATH 28", title: "Analytic Geometry and Calculus I", units: 3 }, - { code: "MATH 36", title: "Mathematical Analysis I", units: 3 }, - { code: "MATH 114", title: "Differential Equations", units: 3 }, - { code: "PHYSICS 71", title: "Elementary Physics I", units: 4 }, - { code: "PHYSICS 72", title: "Elementary Physics II", units: 4 }, - { code: "ENG 10", title: "Writing of Scientific Papers", units: 3 }, - ], - requiredCodes: [ - "CHE 21", "CHE 31", "CHE 41", "CHE 51", "CHE 52", "CHE 101", "CHE 102", - "CHE 111", "CHE 121", "CHE 131", "CHE 141", "CHE 151", "CHE 190", "CHE 191", - "CHEM 16", "CHEM 17", "CHEM 26", "CHEM 40", "CHEM 120", - "MATH 27", "MATH 28", "MATH 36", "MATH 114", "PHYSICS 71", "PHYSICS 72", "ENG 10" - ] - }, - - "BSCE": { - code: "BSCE", - name: "BS Civil Engineering", - college: "CEAT", - collegeName: "College of Engineering and Agro-Industrial Technology", - available: true, - totalUnitsRequired: 180, - geCoursesRequired: 9, - tracks: null, - defaultTrack: null, - majorCourses: [ - { code: "CE 21", title: "Engineering Drawing", units: 2 }, - { code: "CE 31", title: "Surveying I", units: 3 }, - { code: "CE 32", title: "Surveying II", units: 3 }, - { code: "CE 41", title: "Engineering Mechanics - Statics", units: 3 }, - { code: "CE 42", title: "Engineering Mechanics - Dynamics", units: 3 }, - { code: "CE 51", title: "Mechanics of Deformable Bodies", units: 4 }, - { code: "CE 101", title: "Structural Theory I", units: 4 }, - { code: "CE 102", title: "Structural Theory II", units: 4 }, - { code: "CE 111", title: "Reinforced Concrete Design", units: 4 }, - { code: "CE 112", title: "Steel Design", units: 4 }, - { code: "CE 121", title: "Geotechnical Engineering I", units: 4 }, - { code: "CE 122", title: "Geotechnical Engineering II", units: 4 }, - { code: "CE 131", title: "Hydraulics", units: 4 }, - { code: "CE 132", title: "Hydrology", units: 3 }, - { code: "CE 141", title: "Highway and Transportation Engineering", units: 4 }, - { code: "CE 151", title: "Construction Methods and Management", units: 3 }, - { code: "CE 161", title: "Engineering Economy", units: 3 }, - { code: "CE 190", title: "Civil Engineering Design", units: 4 }, - { code: "CE 191", title: "Engineering Internship", units: 3, gradeType: "S/U" }, - { code: "MATH 27", title: "Analytic Geometry and Calculus I", units: 3 }, - { code: "MATH 28", title: "Analytic Geometry and Calculus I", units: 3 }, - { code: "MATH 36", title: "Mathematical Analysis I", units: 3 }, - { code: "MATH 114", title: "Differential Equations", units: 3 }, - { code: "PHYSICS 71", title: "Elementary Physics I", units: 4 }, - { code: "PHYSICS 72", title: "Elementary Physics II", units: 4 }, - { code: "CHEM 16", title: "General Chemistry I", units: 4 }, - { code: "ENG 10", title: "Writing of Scientific Papers", units: 3 }, - ], - requiredCodes: [ - "CE 21", "CE 31", "CE 32", "CE 41", "CE 42", "CE 51", "CE 101", "CE 102", - "CE 111", "CE 112", "CE 121", "CE 122", "CE 131", "CE 132", "CE 141", - "CE 151", "CE 161", "CE 190", "CE 191", "MATH 27", "MATH 28", "MATH 36", - "MATH 114", "PHYSICS 71", "PHYSICS 72", "CHEM 16", "ENG 10" - ] - }, - - "BSEE": { - code: "BSEE", - name: "BS Electrical Engineering", - college: "CEAT", - collegeName: "College of Engineering and Agro-Industrial Technology", - available: true, - totalUnitsRequired: 180, - geCoursesRequired: 9, - tracks: null, - defaultTrack: null, - majorCourses: [ - { code: "EE 21", title: "Electrical Circuits I", units: 4 }, - { code: "EE 22", title: "Electrical Circuits II", units: 4 }, - { code: "EE 31", title: "Electronics I", units: 4 }, - { code: "EE 32", title: "Electronics II", units: 4 }, - { code: "EE 41", title: "Digital Systems", units: 4 }, - { code: "EE 51", title: "Electromagnetic Theory", units: 3 }, - { code: "EE 101", title: "Electrical Machines I", units: 4 }, - { code: "EE 102", title: "Electrical Machines II", units: 4 }, - { code: "EE 111", title: "Power Systems I", units: 4 }, - { code: "EE 112", title: "Power Systems II", units: 4 }, - { code: "EE 121", title: "Control Systems", units: 4 }, - { code: "EE 131", title: "Communications I", units: 4 }, - { code: "EE 141", title: "Microprocessor Systems", units: 4 }, - { code: "EE 151", title: "Instrumentation and Measurements", units: 3 }, - { code: "EE 190", title: "Electrical Engineering Design", units: 4 }, - { code: "EE 191", title: "Engineering Internship", units: 3, gradeType: "S/U" }, - { code: "MATH 27", title: "Analytic Geometry and Calculus I", units: 3 }, - { code: "MATH 28", title: "Analytic Geometry and Calculus I", units: 3 }, - { code: "MATH 36", title: "Mathematical Analysis I", units: 3 }, - { code: "MATH 114", title: "Differential Equations", units: 3 }, - { code: "PHYSICS 71", title: "Elementary Physics I", units: 4 }, - { code: "PHYSICS 72", title: "Elementary Physics II", units: 4 }, - { code: "CHEM 16", title: "General Chemistry I", units: 4 }, - { code: "ENG 10", title: "Writing of Scientific Papers", units: 3 }, - ], - requiredCodes: [ - "EE 21", "EE 22", "EE 31", "EE 32", "EE 41", "EE 51", "EE 101", "EE 102", - "EE 111", "EE 112", "EE 121", "EE 131", "EE 141", "EE 151", "EE 190", "EE 191", - "MATH 27", "MATH 28", "MATH 36", "MATH 114", "PHYSICS 71", "PHYSICS 72", - "CHEM 16", "ENG 10" - ] - }, - - "BSIE": { - code: "BSIE", - name: "BS Industrial Engineering", - college: "CEAT", - collegeName: "College of Engineering and Agro-Industrial Technology", - available: true, - totalUnitsRequired: 175, - geCoursesRequired: 9, - tracks: null, - defaultTrack: null, - majorCourses: [ - { code: "IE 21", title: "Engineering Drawing", units: 2 }, - { code: "IE 31", title: "Work Study and Motion Analysis", units: 3 }, - { code: "IE 41", title: "Engineering Economy", units: 3 }, - { code: "IE 51", title: "Engineering Statistics", units: 3 }, - { code: "IE 101", title: "Operations Research I", units: 3 }, - { code: "IE 102", title: "Operations Research II", units: 3 }, - { code: "IE 111", title: "Production Planning and Control", units: 4 }, - { code: "IE 121", title: "Facilities Planning and Design", units: 4 }, - { code: "IE 131", title: "Quality Control", units: 3 }, - { code: "IE 141", title: "Supply Chain Management", units: 3 }, - { code: "IE 151", title: "Systems Simulation", units: 3 }, - { code: "IE 161", title: "Ergonomics", units: 3 }, - { code: "IE 171", title: "Engineering Management", units: 3 }, - { code: "IE 190", title: "Industrial Engineering Design", units: 4 }, - { code: "IE 191", title: "Engineering Internship", units: 3, gradeType: "S/U" }, - { code: "MATH 27", title: "Analytic Geometry and Calculus I", units: 3 }, - { code: "MATH 28", title: "Analytic Geometry and Calculus I", units: 3 }, - { code: "MATH 36", title: "Mathematical Analysis I", units: 3 }, - { code: "MATH 114", title: "Differential Equations", units: 3 }, - { code: "PHYSICS 71", title: "Elementary Physics I", units: 4 }, - { code: "PHYSICS 72", title: "Elementary Physics II", units: 4 }, - { code: "CHEM 16", title: "General Chemistry I", units: 4 }, - { code: "ENG 10", title: "Writing of Scientific Papers", units: 3 }, - ], - requiredCodes: [ - "IE 21", "IE 31", "IE 41", "IE 51", "IE 101", "IE 102", "IE 111", "IE 121", - "IE 131", "IE 141", "IE 151", "IE 161", "IE 171", "IE 190", "IE 191", - "MATH 27", "MATH 28", "MATH 36", "MATH 114", "PHYSICS 71", "PHYSICS 72", - "CHEM 16", "ENG 10" - ] - }, - - "BSME": { - code: "BSME", - name: "BS Mechanical Engineering", - college: "CEAT", - collegeName: "College of Engineering and Agro-Industrial Technology", - available: true, - totalUnitsRequired: 180, - geCoursesRequired: 9, - tracks: null, - defaultTrack: null, - majorCourses: [ - { code: "ME 21", title: "Engineering Drawing", units: 2 }, - { code: "ME 31", title: "Engineering Mechanics - Statics", units: 3 }, - { code: "ME 32", title: "Engineering Mechanics - Dynamics", units: 3 }, - { code: "ME 41", title: "Mechanics of Materials", units: 4 }, - { code: "ME 51", title: "Thermodynamics I", units: 4 }, - { code: "ME 52", title: "Thermodynamics II", units: 4 }, - { code: "ME 101", title: "Fluid Mechanics", units: 4 }, - { code: "ME 111", title: "Machine Design I", units: 4 }, - { code: "ME 112", title: "Machine Design II", units: 4 }, - { code: "ME 121", title: "Heat Transfer", units: 4 }, - { code: "ME 131", title: "Power Plant Engineering", units: 3 }, - { code: "ME 141", title: "Refrigeration and Air Conditioning", units: 3 }, - { code: "ME 151", title: "Manufacturing Processes", units: 4 }, - { code: "ME 161", title: "Dynamics of Machinery", units: 3 }, - { code: "ME 171", title: "Control Systems", units: 3 }, - { code: "ME 190", title: "Mechanical Engineering Design", units: 4 }, - { code: "ME 191", title: "Engineering Internship", units: 3, gradeType: "S/U" }, - { code: "MATH 27", title: "Analytic Geometry and Calculus I", units: 3 }, - { code: "MATH 28", title: "Analytic Geometry and Calculus I", units: 3 }, - { code: "MATH 36", title: "Mathematical Analysis I", units: 3 }, - { code: "MATH 114", title: "Differential Equations", units: 3 }, - { code: "PHYSICS 71", title: "Elementary Physics I", units: 4 }, - { code: "PHYSICS 72", title: "Elementary Physics II", units: 4 }, - { code: "CHEM 16", title: "General Chemistry I", units: 4 }, - { code: "ENG 10", title: "Writing of Scientific Papers", units: 3 }, - ], - requiredCodes: [ - "ME 21", "ME 31", "ME 32", "ME 41", "ME 51", "ME 52", "ME 101", "ME 111", - "ME 112", "ME 121", "ME 131", "ME 141", "ME 151", "ME 161", "ME 171", - "ME 190", "ME 191", "MATH 27", "MATH 28", "MATH 36", "MATH 114", - "PHYSICS 71", "PHYSICS 72", "CHEM 16", "ENG 10" - ] - }, - - "BSMATE": { - code: "BSMATE", - name: "BS Materials Engineering", - college: "CEAT", - collegeName: "College of Engineering and Agro-Industrial Technology", - available: true, - totalUnitsRequired: 175, - geCoursesRequired: 9, - tracks: null, - defaultTrack: null, - majorCourses: [ - { code: "MATE 21", title: "Engineering Drawing", units: 2 }, - { code: "MATE 31", title: "Introduction to Materials Science", units: 3 }, - { code: "MATE 41", title: "Structure of Materials", units: 4 }, - { code: "MATE 101", title: "Physical Metallurgy", units: 4 }, - { code: "MATE 111", title: "Mechanical Behavior of Materials", units: 4 }, - { code: "MATE 121", title: "Ceramic Materials", units: 3 }, - { code: "MATE 131", title: "Polymer Materials", units: 3 }, - { code: "MATE 141", title: "Electronic Materials", units: 3 }, - { code: "MATE 151", title: "Corrosion Engineering", units: 3 }, - { code: "MATE 161", title: "Materials Characterization", units: 4 }, - { code: "MATE 171", title: "Materials Processing", units: 4 }, - { code: "MATE 181", title: "Materials Selection and Design", units: 3 }, - { code: "MATE 190", title: "Materials Engineering Design", units: 4 }, - { code: "MATE 191", title: "Engineering Internship", units: 3, gradeType: "S/U" }, - { code: "MATH 27", title: "Analytic Geometry and Calculus I", units: 3 }, - { code: "MATH 28", title: "Analytic Geometry and Calculus I", units: 3 }, - { code: "MATH 114", title: "Differential Equations", units: 3 }, - { code: "PHYSICS 71", title: "Elementary Physics I", units: 4 }, - { code: "PHYSICS 72", title: "Elementary Physics II", units: 4 }, - { code: "CHEM 16", title: "General Chemistry I", units: 4 }, - { code: "CHEM 17", title: "General Chemistry II", units: 4 }, - { code: "ENG 10", title: "Writing of Scientific Papers", units: 3 }, - ], - requiredCodes: [ - "MATE 21", "MATE 31", "MATE 41", "MATE 101", "MATE 111", "MATE 121", - "MATE 131", "MATE 141", "MATE 151", "MATE 161", "MATE 171", "MATE 181", - "MATE 190", "MATE 191", "MATH 27", "MATH 28", "MATH 114", - "PHYSICS 71", "PHYSICS 72", "CHEM 16", "CHEM 17", "ENG 10" - ] - }, - - // ============================================================================ - // COLLEGE OF ECONOMICS AND MANAGEMENT (CEM) - // ============================================================================ - - "BSECON": { - code: "BSECON", - name: "BS Economics", - college: "CEM", - collegeName: "College of Economics and Management", - available: true, - totalUnitsRequired: 140, - geCoursesRequired: 9, - tracks: { - thesis: { name: "Thesis Track", code: "ECON 200", freeElectiveUnits: 12 }, - sp: { name: "Special Problem Track", code: "ECON 190", freeElectiveUnits: 15 } - }, - defaultTrack: "sp", - majorCourses: [ - { code: "ECON 11", title: "Introductory Economics (Micro)", units: 3 }, - { code: "ECON 12", title: "Introductory Economics (Macro)", units: 3 }, - { code: "ECON 100", title: "Microeconomic Theory", units: 3 }, - { code: "ECON 101", title: "Macroeconomic Theory", units: 3 }, - { code: "ECON 110", title: "Mathematical Economics", units: 3 }, - { code: "ECON 111", title: "Econometrics I", units: 3 }, - { code: "ECON 112", title: "Econometrics II", units: 3 }, - { code: "ECON 120", title: "Development Economics", units: 3 }, - { code: "ECON 130", title: "Public Finance", units: 3 }, - { code: "ECON 140", title: "International Economics", units: 3 }, - { code: "ECON 150", title: "Monetary Economics", units: 3 }, - { code: "ECON 160", title: "Labor Economics", units: 3 }, - { code: "ECON 170", title: "History of Economic Thought", units: 3 }, - { code: "ECON 190", title: "Special Problem", units: 3, track: "sp" }, - { code: "ECON 200", title: "Undergraduate Thesis", units: 6, track: "thesis" }, - { code: "MATH 27", title: "Analytic Geometry and Calculus I", units: 3 }, - { code: "STAT 101", title: "Statistical Methods", units: 3 }, - { code: "ENG 10", title: "Writing of Scientific Papers", units: 3 }, - ], - requiredCodes: [ - "ECON 11", "ECON 12", "ECON 100", "ECON 101", "ECON 110", "ECON 111", - "ECON 112", "ECON 120", "ECON 130", "ECON 140", "ECON 150", "ECON 160", - "ECON 170", "MATH 27", "STAT 101", "ENG 10" - ] - }, - - "BSAE": { - code: "BSAE", - name: "BS Agricultural Economics", - college: "CEM", - collegeName: "College of Economics and Management", - available: true, - totalUnitsRequired: 145, - geCoursesRequired: 9, - tracks: { - thesis: { name: "Thesis Track", code: "AGEC 200", freeElectiveUnits: 12 }, - sp: { name: "Special Problem Track", code: "AGEC 190", freeElectiveUnits: 15 } - }, - defaultTrack: "sp", - majorCourses: [ - { code: "ECON 11", title: "Introductory Economics (Micro)", units: 3 }, - { code: "ECON 12", title: "Introductory Economics (Macro)", units: 3 }, - { code: "AGEC 100", title: "Microeconomic Theory for Agricultural Economics", units: 3 }, - { code: "AGEC 101", title: "Macroeconomic Theory for Agricultural Economics", units: 3 }, - { code: "AGEC 110", title: "Agricultural Production Economics", units: 3 }, - { code: "AGEC 120", title: "Agricultural Marketing", units: 3 }, - { code: "AGEC 130", title: "Agricultural Finance", units: 3 }, - { code: "AGEC 140", title: "Agricultural Policy", units: 3 }, - { code: "AGEC 150", title: "Agricultural Development", units: 3 }, - { code: "AGEC 160", title: "Natural Resource Economics", units: 3 }, - { code: "AGEC 170", title: "Food and Agricultural Trade", units: 3 }, - { code: "AGEC 180", title: "Quantitative Methods in Agricultural Economics", units: 3 }, - { code: "AGEC 190", title: "Special Problem", units: 3, track: "sp" }, - { code: "AGEC 191", title: "On-the-Job Training", units: 3, gradeType: "S/U" }, - { code: "AGEC 200", title: "Undergraduate Thesis", units: 6, track: "thesis" }, - { code: "AGRI 10", title: "Introduction to Agriculture", units: 3 }, - { code: "MATH 27", title: "Analytic Geometry and Calculus I", units: 3 }, - { code: "STAT 101", title: "Statistical Methods", units: 3 }, - { code: "ENG 10", title: "Writing of Scientific Papers", units: 3 }, - ], - requiredCodes: [ - "ECON 11", "ECON 12", "AGEC 100", "AGEC 101", "AGEC 110", "AGEC 120", - "AGEC 130", "AGEC 140", "AGEC 150", "AGEC 160", "AGEC 170", "AGEC 180", - "AGEC 191", "AGRI 10", "MATH 27", "STAT 101", "ENG 10" - ] - }, - - "BSABM": { - code: "BSABM", - name: "BS Agribusiness Management", - college: "CEM", - collegeName: "College of Economics and Management", - available: true, - totalUnitsRequired: 145, - geCoursesRequired: 9, - tracks: { - thesis: { name: "Thesis Track", code: "ABM 200", freeElectiveUnits: 12 }, - sp: { name: "Special Problem Track", code: "ABM 190", freeElectiveUnits: 15 } - }, - defaultTrack: "sp", - majorCourses: [ - { code: "ECON 11", title: "Introductory Economics (Micro)", units: 3 }, - { code: "ECON 12", title: "Introductory Economics (Macro)", units: 3 }, - { code: "ABM 100", title: "Principles of Management", units: 3 }, - { code: "ABM 110", title: "Agribusiness Management", units: 3 }, - { code: "ABM 120", title: "Agribusiness Marketing", units: 3 }, - { code: "ABM 130", title: "Agribusiness Finance", units: 3 }, - { code: "ABM 140", title: "Agribusiness Operations Management", units: 3 }, - { code: "ABM 150", title: "Agribusiness Entrepreneurship", units: 3 }, - { code: "ABM 160", title: "Agribusiness Strategic Management", units: 3 }, - { code: "ABM 170", title: "Agribusiness Supply Chain Management", units: 3 }, - { code: "ABM 180", title: "Agribusiness Information Systems", units: 3 }, - { code: "ABM 190", title: "Special Problem", units: 3, track: "sp" }, - { code: "ABM 191", title: "On-the-Job Training", units: 3, gradeType: "S/U" }, - { code: "ABM 200", title: "Undergraduate Thesis", units: 6, track: "thesis" }, - { code: "AGRI 10", title: "Introduction to Agriculture", units: 3 }, - { code: "MATH 27", title: "Analytic Geometry and Calculus I", units: 3 }, - { code: "STAT 101", title: "Statistical Methods", units: 3 }, - { code: "ACCTG 11", title: "Fundamentals of Accounting", units: 3 }, - { code: "ENG 10", title: "Writing of Scientific Papers", units: 3 }, - ], - requiredCodes: [ - "ECON 11", "ECON 12", "ABM 100", "ABM 110", "ABM 120", "ABM 130", - "ABM 140", "ABM 150", "ABM 160", "ABM 170", "ABM 180", "ABM 191", - "AGRI 10", "MATH 27", "STAT 101", "ACCTG 11", "ENG 10" - ] - }, - - // ============================================================================ - // COLLEGE OF FORESTRY AND NATURAL RESOURCES (CFNR) - // ============================================================================ - - "BSFOR": { - code: "BSFOR", - name: "BS Forestry", - college: "CFNR", - collegeName: "College of Forestry and Natural Resources", - available: true, - totalUnitsRequired: 175, - geCoursesRequired: 9, - tracks: { - thesis: { name: "Thesis Track", code: "FOR 200", freeElectiveUnits: 9 }, - sp: { name: "Special Problem Track", code: "FOR 190", freeElectiveUnits: 12 } - }, - defaultTrack: "sp", - majorCourses: [ - { code: "FOR 1", title: "Introduction to Forestry", units: 3 }, - { code: "FOR 21", title: "Dendrology", units: 3 }, - { code: "FOR 31", title: "Forest Mensuration", units: 4 }, - { code: "FOR 41", title: "Silvics and Silviculture", units: 4 }, - { code: "FOR 51", title: "Forest Ecology", units: 3 }, - { code: "FOR 101", title: "Forest Management", units: 4 }, - { code: "FOR 111", title: "Forest Utilization", units: 4 }, - { code: "FOR 121", title: "Forest Protection", units: 3 }, - { code: "FOR 131", title: "Wood Science and Technology", units: 4 }, - { code: "FOR 141", title: "Forest Policy and Administration", units: 3 }, - { code: "FOR 151", title: "Watershed Management", units: 3 }, - { code: "FOR 161", title: "Social Forestry", units: 3 }, - { code: "FOR 171", title: "Forest Economics", units: 3 }, - { code: "FOR 181", title: "Forest Engineering", units: 4 }, - { code: "FOR 190", title: "Special Problem", units: 3, track: "sp" }, - { code: "FOR 191", title: "Forestry Field Practice", units: 6, gradeType: "S/U" }, - { code: "FOR 200", title: "Undergraduate Thesis", units: 6, track: "thesis" }, - { code: "BIO 1", title: "General Biology I", units: 3 }, - { code: "CHEM 16", title: "General Chemistry I", units: 4 }, - { code: "STAT 101", title: "Statistical Methods", units: 3 }, - { code: "ENG 10", title: "Writing of Scientific Papers", units: 3 }, - ], - requiredCodes: [ - "FOR 1", "FOR 21", "FOR 31", "FOR 41", "FOR 51", "FOR 101", "FOR 111", - "FOR 121", "FOR 131", "FOR 141", "FOR 151", "FOR 161", "FOR 171", "FOR 181", - "FOR 191", "BIO 1", "CHEM 16", "STAT 101", "ENG 10" - ] - }, - - // ============================================================================ - // COLLEGE OF HUMAN ECOLOGY (CHE) - // ============================================================================ - - "BSHE": { - code: "BSHE", - name: "BS Human Ecology", - college: "CHE", - collegeName: "College of Human Ecology", - available: true, - totalUnitsRequired: 145, - geCoursesRequired: 9, - tracks: { - thesis: { name: "Thesis Track", code: "HE 200", freeElectiveUnits: 12 }, - sp: { name: "Special Problem Track", code: "HE 190", freeElectiveUnits: 15 } - }, - defaultTrack: "sp", - majorCourses: [ - { code: "HE 10", title: "Introduction to Human Ecology", units: 3 }, - { code: "HE 100", title: "Family Resource Management", units: 3 }, - { code: "HE 110", title: "Consumer Economics", units: 3 }, - { code: "HE 120", title: "Human Development", units: 3 }, - { code: "HE 130", title: "Family Studies", units: 3 }, - { code: "HE 140", title: "Community Development", units: 3 }, - { code: "HE 150", title: "Housing and Interior Design", units: 3 }, - { code: "HE 160", title: "Clothing and Textiles", units: 3 }, - { code: "HE 170", title: "Food and Nutrition", units: 3 }, - { code: "HE 180", title: "Research Methods in Human Ecology", units: 3 }, - { code: "HE 190", title: "Special Problem", units: 3, track: "sp" }, - { code: "HE 191", title: "Practicum", units: 3, gradeType: "S/U" }, - { code: "HE 200", title: "Undergraduate Thesis", units: 6, track: "thesis" }, - { code: "STAT 101", title: "Statistical Methods", units: 3 }, - { code: "ENG 10", title: "Writing of Scientific Papers", units: 3 }, - ], - requiredCodes: [ - "HE 10", "HE 100", "HE 110", "HE 120", "HE 130", "HE 140", "HE 150", - "HE 160", "HE 170", "HE 180", "HE 191", "STAT 101", "ENG 10" - ] - }, - - "BSNUTRI": { - code: "BSNUTRI", - name: "BS Nutrition", - college: "CHE", - collegeName: "College of Human Ecology", - available: true, - totalUnitsRequired: 165, - geCoursesRequired: 9, - tracks: { - thesis: { name: "Thesis Track", code: "NUT 200", freeElectiveUnits: 9 }, - sp: { name: "Special Problem Track", code: "NUT 190", freeElectiveUnits: 12 } - }, - defaultTrack: "sp", - majorCourses: [ - { code: "NUT 11", title: "Fundamentals of Nutrition", units: 3 }, - { code: "NUT 100", title: "Principles of Nutrition", units: 3 }, - { code: "NUT 101", title: "Advanced Nutrition", units: 3 }, - { code: "NUT 110", title: "Nutrition Assessment", units: 3 }, - { code: "NUT 120", title: "Diet Therapy", units: 4 }, - { code: "NUT 130", title: "Community Nutrition", units: 3 }, - { code: "NUT 140", title: "Food Service Management", units: 3 }, - { code: "NUT 150", title: "Nutrition Education", units: 3 }, - { code: "NUT 160", title: "Nutrition Research Methods", units: 3 }, - { code: "NUT 170", title: "Sports Nutrition", units: 3 }, - { code: "NUT 180", title: "Nutrition in the Life Cycle", units: 3 }, - { code: "NUT 190", title: "Special Problem", units: 3, track: "sp" }, - { code: "NUT 191", title: "Dietetic Internship", units: 6, gradeType: "S/U" }, - { code: "NUT 200", title: "Undergraduate Thesis", units: 6, track: "thesis" }, - { code: "CHEM 16", title: "General Chemistry I", units: 4 }, - { code: "CHEM 17", title: "General Chemistry II", units: 4 }, - { code: "CHEM 40", title: "Organic Chemistry I", units: 5 }, - { code: "CHEM 31", title: "Elementary Biochemistry", units: 5 }, - { code: "BIO 1", title: "General Biology I", units: 3 }, - { code: "STAT 101", title: "Statistical Methods", units: 3 }, - { code: "ENG 10", title: "Writing of Scientific Papers", units: 3 }, - ], - requiredCodes: [ - "NUT 11", "NUT 100", "NUT 101", "NUT 110", "NUT 120", "NUT 130", "NUT 140", - "NUT 150", "NUT 160", "NUT 170", "NUT 180", "NUT 191", - "CHEM 16", "CHEM 17", "CHEM 40", "CHEM 31", "BIO 1", "STAT 101", "ENG 10" - ] - }, - - // ============================================================================ - // COLLEGE OF DEVELOPMENT COMMUNICATION (CDC) - // ============================================================================ - - "BSDC": { - code: "BSDC", - name: "BS Development Communication", - college: "CDC", - collegeName: "College of Development Communication", - available: true, - totalUnitsRequired: 150, - geCoursesRequired: 9, - tracks: { - thesis: { name: "Thesis Track", code: "DEVC 200", freeElectiveUnits: 12 }, - sp: { name: "Special Problem Track", code: "DEVC 190", freeElectiveUnits: 15 } - }, - defaultTrack: "sp", - majorCourses: [ - { code: "DEVC 10", title: "Introduction to Development Communication", units: 3 }, - { code: "DEVC 20", title: "Communication Theories", units: 3 }, - { code: "DEVC 30", title: "Communication Research Methods", units: 3 }, - { code: "DEVC 40", title: "Development Journalism", units: 3 }, - { code: "DEVC 50", title: "Development Broadcasting", units: 3 }, - { code: "DEVC 60", title: "Educational Communication", units: 3 }, - { code: "DEVC 100", title: "Development Communication Campaigns", units: 3 }, - { code: "DEVC 110", title: "Science Communication", units: 3 }, - { code: "DEVC 120", title: "Community Media", units: 3 }, - { code: "DEVC 130", title: "New Media and ICT4D", units: 3 }, - { code: "DEVC 140", title: "Communication Planning and Management", units: 3 }, - { code: "DEVC 150", title: "Risk and Crisis Communication", units: 3 }, - { code: "DEVC 160", title: "Environmental Communication", units: 3 }, - { code: "DEVC 190", title: "Special Problem", units: 3, track: "sp" }, - { code: "DEVC 191", title: "Academic Field Instruction", units: 6, gradeType: "S/U" }, - { code: "DEVC 200", title: "Undergraduate Thesis", units: 6, track: "thesis" }, - { code: "STAT 101", title: "Statistical Methods", units: 3 }, - { code: "ENG 10", title: "Writing of Scientific Papers", units: 3 }, - ], - requiredCodes: [ - "DEVC 10", "DEVC 20", "DEVC 30", "DEVC 40", "DEVC 50", "DEVC 60", - "DEVC 100", "DEVC 110", "DEVC 120", "DEVC 130", "DEVC 140", "DEVC 150", - "DEVC 160", "DEVC 191", "STAT 101", "ENG 10" - ] - }, - - "ASDC": { - code: "ASDC", - name: "Associate of Science in Development Communication", - college: "CDC", - collegeName: "College of Development Communication", - available: true, - totalUnitsRequired: 72, - geCoursesRequired: 6, - tracks: null, - defaultTrack: null, - majorCourses: [ - { code: "DEVC 10", title: "Introduction to Development Communication", units: 3 }, - { code: "DEVC 20", title: "Communication Theories", units: 3 }, - { code: "DEVC 30", title: "Communication Research Methods", units: 3 }, - { code: "DEVC 40", title: "Development Journalism", units: 3 }, - { code: "DEVC 50", title: "Development Broadcasting", units: 3 }, - { code: "DEVC 60", title: "Educational Communication", units: 3 }, - { code: "ENG 10", title: "Writing of Scientific Papers", units: 3 }, - ], - requiredCodes: [ - "DEVC 10", "DEVC 20", "DEVC 30", "DEVC 40", "DEVC 50", "DEVC 60", "ENG 10" - ] - }, - - // ============================================================================ - // COLLEGE OF VETERINARY MEDICINE (CVM) - // ============================================================================ - - "DVM": { - code: "DVM", - name: "Doctor of Veterinary Medicine", - college: "CVM", - collegeName: "College of Veterinary Medicine", - available: true, - totalUnitsRequired: 220, - geCoursesRequired: 9, - tracks: null, - defaultTrack: null, - majorCourses: [ - { code: "VET 11", title: "Veterinary Anatomy I", units: 5 }, - { code: "VET 12", title: "Veterinary Anatomy II", units: 5 }, - { code: "VET 21", title: "Veterinary Physiology I", units: 4 }, - { code: "VET 22", title: "Veterinary Physiology II", units: 4 }, - { code: "VET 31", title: "Veterinary Biochemistry", units: 5 }, - { code: "VET 41", title: "Veterinary Microbiology", units: 5 }, - { code: "VET 51", title: "Veterinary Parasitology", units: 5 }, - { code: "VET 61", title: "Veterinary Pathology", units: 5 }, - { code: "VET 71", title: "Veterinary Pharmacology", units: 4 }, - { code: "VET 101", title: "Veterinary Medicine I", units: 4 }, - { code: "VET 102", title: "Veterinary Medicine II", units: 4 }, - { code: "VET 111", title: "Veterinary Surgery I", units: 4 }, - { code: "VET 112", title: "Veterinary Surgery II", units: 4 }, - { code: "VET 121", title: "Theriogenology", units: 4 }, - { code: "VET 131", title: "Veterinary Public Health", units: 3 }, - { code: "VET 141", title: "Poultry Diseases", units: 3 }, - { code: "VET 151", title: "Swine Diseases", units: 3 }, - { code: "VET 161", title: "Ruminant Diseases", units: 3 }, - { code: "VET 171", title: "Small Animal Medicine", units: 3 }, - { code: "VET 181", title: "Veterinary Radiology", units: 3 }, - { code: "VET 190", title: "Clinical Clerkship", units: 12, gradeType: "S/U" }, - { code: "VET 200", title: "Thesis/Special Problem", units: 6 }, - { code: "CHEM 16", title: "General Chemistry I", units: 4 }, - { code: "CHEM 17", title: "General Chemistry II", units: 4 }, - { code: "CHEM 40", title: "Organic Chemistry I", units: 5 }, - { code: "CHEM 31", title: "Elementary Biochemistry", units: 5 }, - { code: "BIO 1", title: "General Biology I", units: 3 }, - { code: "PHYSICS 71", title: "Elementary Physics I", units: 4 }, - { code: "STAT 101", title: "Statistical Methods", units: 3 }, - { code: "ENG 10", title: "Writing of Scientific Papers", units: 3 }, - ], - requiredCodes: [ - "VET 11", "VET 12", "VET 21", "VET 22", "VET 31", "VET 41", "VET 51", - "VET 61", "VET 71", "VET 101", "VET 102", "VET 111", "VET 112", "VET 121", - "VET 131", "VET 141", "VET 151", "VET 161", "VET 171", "VET 181", "VET 190", - "CHEM 16", "CHEM 17", "CHEM 40", "CHEM 31", "BIO 1", "PHYSICS 71", - "STAT 101", "ENG 10" - ] - }, -}; - -// Group programs by college -const COLLEGES = { - "CAS": { name: "College of Arts and Sciences", programs: [] }, - "CAFS": { name: "College of Agriculture and Food Science", programs: [] }, - "CEAT": { name: "College of Engineering and Agro-Industrial Technology", programs: [] }, - "CFNR": { name: "College of Forestry and Natural Resources", programs: [] }, - "CEM": { name: "College of Economics and Management", programs: [] }, - "CHE": { name: "College of Human Ecology", programs: [] }, - "CVM": { name: "College of Veterinary Medicine", programs: [] }, - "CDC": { name: "College of Development Communication", programs: [] }, - "CPAf": { name: "College of Public Affairs and Development", programs: [] }, -}; - -// Populate colleges with programs -Object.values(UPLB_PROGRAMS).forEach(program => { - if (COLLEGES[program.college]) { - COLLEGES[program.college].programs.push(program); - } -}); - -// Current selected program (default to BSCS) -let currentProgram = UPLB_PROGRAMS["BSCS"]; - -// Get current curriculum based on selected program -function getCurrentCurriculum() { - return currentProgram; -} - -// Set current program -function setCurrentProgram(programCode) { - if (UPLB_PROGRAMS[programCode]) { - currentProgram = UPLB_PROGRAMS[programCode]; - return true; - } - return false; -} - -// Detect which track the student is on based on completed courses -function detectTrack(completedCourses) { - if (!currentProgram.tracks) return null; - - const codes = completedCourses.map(c => c.code.toUpperCase().trim()); - - for (const [trackKey, track] of Object.entries(currentProgram.tracks)) { - if (codes.some(code => code.startsWith(track.code.toUpperCase()))) { - return trackKey; - } - } - - return null; -} - -// Get free elective units based on track -function getFreeElectiveUnits(track) { - if (!currentProgram.tracks) return 15; // Default - - if (track && currentProgram.tracks[track]) { - return currentProgram.tracks[track].freeElectiveUnits; - } - - // Return default track's units - const defaultTrack = currentProgram.defaultTrack || Object.keys(currentProgram.tracks)[0]; - return currentProgram.tracks[defaultTrack]?.freeElectiveUnits || 15; -} - -// Check if a course is a GE course (title starts with "(GE)") -function isGECourse(courseTitle) { - return courseTitle && courseTitle.trim().startsWith("(GE)"); -} - -// Check if a course is a required major course -function isRequiredCourse(courseCode) { - if (!currentProgram.requiredCodes) return false; - - const normalized = courseCode.toUpperCase().trim(); - return currentProgram.requiredCodes.some(code => - code.toUpperCase() === normalized - ); -} - -// Get remaining required courses -function getRemainingCourses(completedCourses) { - if (!currentProgram.majorCourses) return []; - - const completedCodes = new Set( - completedCourses.map(c => c.code.toUpperCase().trim()) - ); - - return currentProgram.majorCourses.filter(course => - !completedCodes.has(course.code.toUpperCase()) - ); -} - -// Count completed GE courses -function countCompletedGE(completedCourses) { - return completedCourses.filter(c => isGECourse(c.title)).length; -} - -// Get remaining GE slots -function getRemainingGESlots(completedCourses) { - const completed = countCompletedGE(completedCourses); - const required = currentProgram.geCoursesRequired || 9; - return Math.max(0, required - completed); -} - -// For backwards compatibility -const BSCS_CURRICULUM = UPLB_PROGRAMS["BSCS"]; diff --git a/icons/icon128.svg b/icons/icon128.svg deleted file mode 100644 index eb36037..0000000 --- a/icons/icon128.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - G - diff --git a/icons/icon16.svg b/icons/icon16.svg deleted file mode 100644 index e52f468..0000000 --- a/icons/icon16.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - G - diff --git a/icons/icon48.svg b/icons/icon48.svg deleted file mode 100644 index 7a308df..0000000 --- a/icons/icon48.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - G - diff --git a/manifest.chrome.json b/manifest.chrome.json deleted file mode 100644 index 699b52e..0000000 --- a/manifest.chrome.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "manifest_version": 3, - "name": "Elbi GradeSim - UPLB GWA Calculator", - "version": "1.1.1", - "description": "Calculate your GWA and simulate grades needed for Latin honors at UPLB. All data stays on your device.", - "permissions": [ - "storage" - ], - "host_permissions": [ - "https://amis.uplb.edu.ph/*", - "https://api-amis.uplb.edu.ph/*" - ], - "action": { - "default_popup": "popup.html", - "default_icon": { - "128": "sims.png" - } - }, - "icons": { - "128": "sims.png" - }, - "background": { - "service_worker": "background.js" - }, - "content_scripts": [ - { - "matches": ["https://amis.uplb.edu.ph/*"], - "js": ["content.js"], - "run_at": "document_start" - } - ] -} diff --git a/manifest.firefox.json b/manifest.firefox.json deleted file mode 100644 index d5796d7..0000000 --- a/manifest.firefox.json +++ /dev/null @@ -1,44 +0,0 @@ -{ - "manifest_version": 3, - "name": "Elbi GradeSim - UPLB GWA Calculator", - "version": "1.1.1", - "browser_specific_settings": { - "gecko": { - "id": "elbigradesim@stimmie.dev", - "strict_min_version": "109.0", - "data_collection_permissions": { - "required": ["none"] - } - }, - "gecko_android": { - "strict_min_version": "120.0" - } - }, - "description": "Calculate your GWA and simulate grades needed for Latin honors at UPLB. All data stays on your device.", - "permissions": [ - "storage" - ], - "host_permissions": [ - "https://amis.uplb.edu.ph/*", - "https://api-amis.uplb.edu.ph/*" - ], - "action": { - "default_popup": "popup.html", - "default_icon": { - "128": "sims.png" - } - }, - "icons": { - "128": "sims.png" - }, - "background": { - "scripts": ["background.js"] - }, - "content_scripts": [ - { - "matches": ["https://amis.uplb.edu.ph/*"], - "js": ["content.js"], - "run_at": "document_start" - } - ] -} diff --git a/manifest.json b/manifest.json deleted file mode 100644 index 699b52e..0000000 --- a/manifest.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "manifest_version": 3, - "name": "Elbi GradeSim - UPLB GWA Calculator", - "version": "1.1.1", - "description": "Calculate your GWA and simulate grades needed for Latin honors at UPLB. All data stays on your device.", - "permissions": [ - "storage" - ], - "host_permissions": [ - "https://amis.uplb.edu.ph/*", - "https://api-amis.uplb.edu.ph/*" - ], - "action": { - "default_popup": "popup.html", - "default_icon": { - "128": "sims.png" - } - }, - "icons": { - "128": "sims.png" - }, - "background": { - "service_worker": "background.js" - }, - "content_scripts": [ - { - "matches": ["https://amis.uplb.edu.ph/*"], - "js": ["content.js"], - "run_at": "document_start" - } - ] -} diff --git a/manifest.opera.json b/manifest.opera.json deleted file mode 100644 index 699b52e..0000000 --- a/manifest.opera.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "manifest_version": 3, - "name": "Elbi GradeSim - UPLB GWA Calculator", - "version": "1.1.1", - "description": "Calculate your GWA and simulate grades needed for Latin honors at UPLB. All data stays on your device.", - "permissions": [ - "storage" - ], - "host_permissions": [ - "https://amis.uplb.edu.ph/*", - "https://api-amis.uplb.edu.ph/*" - ], - "action": { - "default_popup": "popup.html", - "default_icon": { - "128": "sims.png" - } - }, - "icons": { - "128": "sims.png" - }, - "background": { - "service_worker": "background.js" - }, - "content_scripts": [ - { - "matches": ["https://amis.uplb.edu.ph/*"], - "js": ["content.js"], - "run_at": "document_start" - } - ] -} diff --git a/popup.css b/popup.css deleted file mode 100644 index 194dbee..0000000 --- a/popup.css +++ /dev/null @@ -1,1239 +0,0 @@ -/* Elbi GradeSim - Sims-Inspired Pixelated Theme */ - -@import url('https://fonts.googleapis.com/css2?family=VT323&family=Press+Start+2P&family=Nunito:wght@400;600;700&display=swap'); - -:root { - /* Primary Green - muted shades */ - --gem-light: #6ec96e; - --gem-mid: #4a9c4a; - --gem-dark: #2d6b2d; - --gem-glow: rgba(74, 156, 74, 0.25); - - /* UI Colors - neutral grays */ - --bg-dark: #1a1a1f; - --bg-mid: #22222a; - --bg-light: #2a2a35; - --card-bg: #16161c; - --card-border: #3a3a45; - - --text-primary: #d8d8dc; - --text-secondary: #8a8a94; - --text-accent: #6ec96e; - - --success: #6ec96e; - --warning: #d4a84b; - --danger: #c75050; - --info: #5b8fc9; - - /* Pixel border */ - --pixel-border: 2px solid; - - /* Fonts */ - --font-header: 'Press Start 2P', cursive; - --font-body: 'Nunito', sans-serif; - --font-mono: 'VT323', monospace; -} - -* { - margin: 0; - padding: 0; - box-sizing: border-box; -} - -body { - font-family: var(--font-body); - background: linear-gradient(180deg, var(--bg-dark) 0%, var(--bg-mid) 100%); - color: var(--text-primary); - width: 460px; - min-height: 500px; - max-height: 600px; - overflow-y: auto; - font-size: 11px; - letter-spacing: 0.2px; -} - -.container { - padding: 16px; -} - -/* Header with Plumbob */ -header { - text-align: center; - margin-bottom: 20px; - padding-bottom: 16px; - border-bottom: 3px solid var(--gem-dark); - position: relative; -} - -header::before { - content: ''; - display: block; - width: 40px; - height: 50px; - margin: 0 auto 10px; - background: url('sims.png') center/contain no-repeat; - filter: drop-shadow(0 0 8px var(--gem-glow)); - animation: plumbob-float 2s ease-in-out infinite; -} - -@keyframes plumbob-float { - 0%, 100% { transform: translateY(0); } - 50% { transform: translateY(-5px); } -} - -header h1 { - font-family: 'Press Start 2P', cursive; - color: var(--gem-light); - font-size: 0.85rem; - margin-bottom: 6px; - text-shadow: 1px 1px 0 var(--gem-dark); -} - -.subtitle { - color: var(--text-secondary); - font-size: 0.95rem; -} - -/* Credits */ -.credits { - text-align: center; - color: var(--text-secondary); - font-size: 0.95rem; - margin-bottom: 16px; -} - -/* Program Selector */ -.program-selector { - margin-bottom: 16px; - padding: 12px; - background: var(--card-bg); - border: 2px solid var(--card-border); -} - -.program-selector label { - display: block; - margin-bottom: 6px; - color: var(--gem-light); - font-size: 0.85rem; - text-transform: uppercase; - letter-spacing: 0.5px; -} - -.program-selector select { - width: 100%; - padding: 8px 10px; - background: var(--bg-dark); - border: 2px solid var(--gem-dark); - color: var(--text-primary); - font-family: var(--font-body); - font-size: 0.95rem; - cursor: pointer; - appearance: none; - background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' fill='%236ec96e' viewBox='0 0 16 16'%3E%3Cpath d='M8 11L3 6h10l-5 5z'/%3E%3C/svg%3E"); - background-repeat: no-repeat; - background-position: right 10px center; -} - -.program-selector select:focus { - outline: none; - border-color: var(--gem-light); - box-shadow: 0 0 10px var(--gem-glow); -} - -.program-selector select option { - background: var(--bg-dark); - color: var(--text-primary); - padding: 8px; -} - -.program-selector select option:disabled { - color: var(--text-secondary); -} - -.program-selector select optgroup { - background: var(--bg-mid); - color: var(--gem-light); - font-weight: bold; - font-style: normal; -} - -.hidden { - display: none !important; -} - -/* Loading & No Data States */ -.loading, .no-data { - text-align: center; - padding: 40px 20px; - background: var(--card-bg); - border: 3px solid var(--card-border); - border-radius: 4px; -} - -.loading p, .no-data p { - margin-bottom: 12px; - font-size: 1.2rem; -} - -.hint { - font-size: 1rem; - color: var(--text-secondary); - line-height: 1.5; -} - -.hint a { - color: var(--gem-light); - text-decoration: none; -} - -.hint a:hover { - text-decoration: underline; -} - -/* Pixel Buttons */ -.btn { - padding: 10px 20px; - border: none; - cursor: pointer; - font-family: var(--font-body); - font-size: 0.9rem; - font-weight: 600; - text-transform: uppercase; - letter-spacing: 0.5px; - position: relative; - transition: all 0.1s; - background: linear-gradient(180deg, var(--gem-light) 0%, var(--gem-mid) 50%, var(--gem-dark) 100%); - color: var(--bg-dark); - border: 2px solid var(--gem-dark); - box-shadow: - inset -2px -2px 0 var(--gem-dark), - inset 2px 2px 0 rgba(255,255,255,0.3), - 0 4px 0 var(--gem-dark); -} - -.btn:hover { - transform: translateY(-2px); - box-shadow: - inset -2px -2px 0 var(--gem-dark), - inset 2px 2px 0 rgba(255,255,255,0.3), - 0 6px 0 var(--gem-dark); -} - -.btn:active { - transform: translateY(2px); - box-shadow: - inset -2px -2px 0 var(--gem-dark), - inset 2px 2px 0 rgba(255,255,255,0.3), - 0 2px 0 var(--gem-dark); -} - -.btn-primary { - background: linear-gradient(180deg, var(--gem-light) 0%, var(--gem-mid) 50%, var(--gem-dark) 100%); -} - -.btn-secondary { - background: linear-gradient(180deg, #666 0%, #444 50%, #222 100%); - color: var(--text-primary); - border-color: #222; - box-shadow: - inset -2px -2px 0 #222, - inset 2px 2px 0 rgba(255,255,255,0.2), - 0 4px 0 #111; -} - -/* GWA Section - Main Stats Panel */ -.gwa-section { - background: var(--card-bg); - border: 3px solid var(--gem-mid); - padding: 20px; - text-align: center; - margin-bottom: 20px; - position: relative; - box-shadow: - inset 0 0 20px rgba(0, 212, 0, 0.1), - 0 0 15px var(--gem-glow); -} - -.gwa-section::before { - content: '◆ GWA STATUS ◆'; - position: absolute; - top: -12px; - left: 50%; - transform: translateX(-50%); - background: var(--card-bg); - padding: 0 12px; - font-size: 0.9rem; - color: var(--gem-light); - letter-spacing: 2px; -} - -.gwa-section h2 { - font-size: 1.1rem; - font-weight: normal; - margin-bottom: 12px; - color: var(--text-secondary); -} - -.gwa-display { - margin-bottom: 16px; - padding: 10px; - background: linear-gradient(180deg, #001100 0%, #002200 100%); - border: 2px solid var(--gem-dark); -} - -.gwa-value { - font-family: 'Press Start 2P', cursive; - font-size: 1.6rem; - color: var(--gem-light); - text-shadow: 0 0 6px var(--gem-glow); -} - -.gwa-stats { - display: flex; - justify-content: space-around; - padding-top: 16px; - border-top: 2px dashed var(--gem-dark); -} - -.stat { - display: flex; - flex-direction: column; - align-items: center; -} - -.stat-label { - font-size: 0.9rem; - color: var(--text-secondary); - margin-bottom: 4px; -} - -.stat-value { - font-size: 1.2rem; - font-weight: bold; - color: var(--gem-light); -} - -.honor-status { - margin-top: 12px; - padding: 8px 16px; - display: inline-block; - font-size: 1rem; - text-transform: uppercase; - letter-spacing: 1px; - border: 2px solid; -} - -.honor-status.summa { - background: linear-gradient(180deg, #ffd700 0%, #ffaa00 100%); - border-color: #aa7700; - color: #332200; - text-shadow: 1px 1px 0 rgba(255,255,255,0.5); -} - -.honor-status.magna { - background: linear-gradient(180deg, #e0e0e0 0%, #a0a0a0 100%); - border-color: #606060; - color: #222; -} - -.honor-status.cum-laude { - background: linear-gradient(180deg, #cd8500 0%, #8b4513 100%); - border-color: #5a2d0a; - color: #fff; -} - -.honor-status.honor-roll { - background: linear-gradient(180deg, #6699ff 0%, #3366cc 100%); - border-color: #224488; - color: #fff; -} - -/* Tabs */ -.tabs { - display: flex; - gap: 4px; - margin-bottom: 16px; -} - -.tab { - flex: 1; - padding: 10px; - background: var(--card-bg); - border: 2px solid var(--card-border); - border-bottom: none; - cursor: pointer; - font-family: var(--font-body); - font-size: 0.9rem; - color: var(--text-secondary); - text-transform: uppercase; - letter-spacing: 0.5px; - transition: all 0.2s; -} - -.tab:hover { - color: var(--gem-light); - background: var(--bg-light); -} - -.tab.active { - color: var(--gem-light); - background: var(--bg-light); - border-color: var(--gem-mid); - border-bottom: 2px solid var(--bg-light); - margin-bottom: -2px; -} - -.tab-content { - display: none; - background: var(--bg-light); - border: 2px solid var(--gem-mid); - border-top: none; - padding: 16px; -} - -.tab-content.active { - display: block; -} - -/* View Toggle (Semester/Year) */ -.view-toggle { - display: flex; - gap: 4px; - margin-bottom: 12px; -} - -.view-btn { - flex: 1; - padding: 6px 10px; - background: var(--card-bg); - border: 2px solid var(--card-border); - color: var(--text-secondary); - font-family: var(--font-body); - font-size: 0.85rem; - cursor: pointer; - transition: all 0.15s; -} - -.view-btn:hover { - border-color: var(--gem-dark); - color: var(--text-primary); -} - -.view-btn.active { - background: var(--gem-dark); - border-color: var(--gem-mid); - color: var(--gem-light); -} - -/* Grades List */ -.grades-list { - max-height: 250px; - overflow-y: auto; -} - -.semester-group { - margin-bottom: 12px; -} - -.semester-header { - display: flex; - justify-content: space-between; - align-items: center; - flex-wrap: wrap; - gap: 8px; - background: linear-gradient(90deg, var(--gem-dark) 0%, transparent 100%); - color: var(--gem-light); - padding: 8px 12px; - font-size: 1rem; - text-transform: uppercase; - letter-spacing: 1px; - border-left: 4px solid var(--gem-light); -} - -.semester-header .group-title { - flex: 1; - min-width: 120px; -} - -.semester-header .group-gwa-info { - display: flex; - align-items: center; - gap: 8px; - font-size: 0.9rem; -} - -.semester-header .group-gwa { - color: var(--text-primary); - font-weight: normal; - text-transform: none; -} - -.scholar-badge { - font-size: 0.75rem; - padding: 2px 6px; - border-radius: 2px; - text-transform: none; - letter-spacing: 0; -} - -.scholar-badge.university-scholar { - background: linear-gradient(180deg, #ffd700 0%, #ffaa00 100%); - color: #332200; -} - -.scholar-badge.college-scholar { - background: linear-gradient(180deg, #e0e0e0 0%, #a0a0a0 100%); - color: #222; -} - -.scholar-badge.honor-roll { - background: linear-gradient(180deg, #6699ff 0%, #3366cc 100%); - color: #fff; -} - -.course-item { - display: flex; - justify-content: space-between; - align-items: center; - padding: 10px 12px; - background: var(--card-bg); - border: 1px solid var(--card-border); - border-top: none; -} - -.course-item:nth-child(odd) { - background: rgba(0, 0, 0, 0.2); -} - -.course-info { - flex: 1; -} - -.course-code { - font-size: 0.95rem; - color: var(--text-primary); -} - -.course-title { - font-size: 0.8rem; - color: var(--text-secondary); -} - -.course-grade { - display: flex; - align-items: center; - gap: 10px; -} - -.grade-value { - font-family: 'Press Start 2P', cursive; - font-size: 0.65rem; - min-width: 45px; - text-align: center; - padding: 3px 6px; - border: 2px solid; -} - -.grade-value.excellent { - color: var(--gem-light); - border-color: var(--gem-dark); - background: rgba(0, 100, 0, 0.3); -} - -.grade-value.good { - color: var(--info); - border-color: #226; - background: rgba(0, 50, 100, 0.3); -} - -.grade-value.passing { - color: var(--warning); - border-color: #660; - background: rgba(100, 100, 0, 0.3); -} - -.grade-value.failed { - color: var(--danger); - border-color: #600; - background: rgba(100, 0, 0, 0.3); -} - -.grade-value.satisfactory { - color: var(--gem-light); - border-color: var(--gem-dark); - background: rgba(0, 100, 0, 0.3); - font-size: 0.9rem; -} - -.grade-value.unsatisfactory { - color: var(--danger); - border-color: #600; - background: rgba(100, 0, 0, 0.3); - font-size: 0.9rem; -} - -.grade-value.other-grade { - color: var(--text-secondary); - border-color: var(--card-border); - background: rgba(50, 50, 50, 0.3); - font-size: 0.9rem; -} - -.course-item.non-numeric { - opacity: 0.85; -} - -.units-badge { - background: var(--bg-dark); - padding: 3px 6px; - font-size: 0.75rem; - color: var(--text-secondary); - border: 1px solid var(--card-border); -} - -/* What If Section */ -.whatif-container h3 { - margin-bottom: 10px; - color: var(--gem-light); - font-size: 1rem; - text-transform: uppercase; - letter-spacing: 0.5px; -} - -.honor-options { - display: flex; - flex-direction: column; - gap: 6px; - margin-bottom: 16px; -} - -.honor-option { - display: flex; - align-items: center; - padding: 10px 12px; - background: var(--card-bg); - border: 2px solid var(--card-border); - cursor: pointer; - transition: all 0.2s; -} - -.honor-option:hover { - border-color: var(--gem-mid); - background: rgba(0, 100, 0, 0.2); -} - -.honor-option:has(input:checked) { - border-color: var(--gem-light); - background: rgba(0, 100, 0, 0.3); - box-shadow: inset 0 0 10px var(--gem-glow); -} - -.honor-option input[type="radio"] { - margin-right: 12px; - accent-color: var(--gem-light); - width: 16px; - height: 16px; -} - -.honor-label { - display: flex; - flex-direction: column; - flex: 1; -} - -.honor-label strong { - font-size: 0.95rem; - color: var(--text-primary); -} - -.honor-label small { - color: var(--text-secondary); - font-size: 0.8rem; -} - -.honor-label input[type="number"] { - margin-top: 6px; - padding: 8px; - background: var(--bg-dark); - border: 2px solid var(--card-border); - color: var(--gem-light); - font-family: 'VT323', monospace; - font-size: 1.1rem; - width: 120px; -} - -.honor-label input[type="number"]:focus { - outline: none; - border-color: var(--gem-mid); -} - -#calculateBtn { - width: 100%; - margin-bottom: 20px; -} - -/* What If Results */ -.whatif-results { - background: var(--card-bg); - border: 2px solid var(--card-border); - padding: 16px; - margin-bottom: 20px; -} - -.whatif-results h3 { - margin-bottom: 12px; - border-bottom: 2px dashed var(--card-border); - padding-bottom: 8px; -} - -.result-summary { - padding: 12px; - margin-bottom: 12px; - font-size: 1.1rem; - border: 2px solid; -} - -.result-summary.achieved, -.result-summary.excellent { - background: rgba(0, 100, 0, 0.3); - border-color: var(--gem-mid); - color: var(--gem-light); -} - -.result-summary.good { - background: rgba(0, 100, 0, 0.2); - border-color: var(--gem-dark); - color: var(--gem-light); -} - -.result-summary.moderate { - background: rgba(100, 100, 0, 0.2); - border-color: #666600; - color: var(--warning); -} - -.result-summary.difficult { - background: rgba(100, 80, 0, 0.2); - border-color: #664400; - color: #ffaa00; -} - -.result-summary.impossible { - background: rgba(100, 0, 0, 0.3); - border-color: #660000; - color: var(--danger); -} - -.result-details { - font-size: 1rem; -} - -.result-row { - display: flex; - justify-content: space-between; - align-items: center; - padding: 8px 0; - border-bottom: 1px dashed var(--card-border); - gap: 12px; -} - -.result-row:last-child { - border-bottom: none; -} - -.result-row span:first-child { - flex-shrink: 0; -} - -.result-row .value { - color: var(--gem-light); - font-weight: bold; - text-align: right; -} - -.result-row.highlight { - background: rgba(0, 100, 0, 0.2); - padding: 12px; - margin: 12px -12px 0; - border: 2px solid var(--gem-dark); - flex-direction: column; - align-items: stretch; - gap: 8px; -} - -.result-row.highlight span:first-child { - color: var(--text-secondary); - font-size: 0.95rem; -} - -.result-row.highlight .value { - font-family: 'VT323', monospace; - font-size: 1.3rem; - text-align: center; - padding: 8px; - background: var(--bg-dark); - border: 1px solid var(--gem-dark); - word-break: break-word; -} - -/* Track Selector */ -.track-selector { - margin-bottom: 20px; - padding: 16px; - background: var(--card-bg); - border: 2px solid var(--card-border); -} - -.track-selector h3 { - font-size: 1.1rem; - margin-bottom: 10px; - color: var(--gem-light); - text-transform: uppercase; -} - -.track-info { - font-size: 1rem; - margin-bottom: 12px; - padding: 8px 12px; - border: 2px solid; -} - -.track-info.detected { - background: rgba(0, 100, 0, 0.2); - border-color: var(--gem-dark); - color: var(--gem-light); -} - -.track-info.not-detected { - background: rgba(100, 100, 0, 0.2); - border-color: #666600; - color: var(--warning); -} - -.track-options { - display: flex; - flex-direction: column; - gap: 6px; -} - -.track-option { - display: flex; - align-items: center; - padding: 10px 12px; - border: 2px solid var(--card-border); - cursor: pointer; - transition: all 0.2s ease; - background: transparent; -} - -.track-option:hover { - border-color: var(--gem-mid); - background: rgba(0, 100, 0, 0.1); -} - -.track-option:has(input:checked) { - border-color: var(--gem-light); - background: rgba(0, 100, 0, 0.2); -} - -.track-option input[type="radio"] { - margin-right: 10px; - accent-color: var(--gem-light); -} - -.track-label { - display: flex; - flex-direction: column; -} - -.track-label strong { - font-size: 0.95rem; - color: var(--text-primary); -} - -.track-label small { - color: var(--text-secondary); - font-size: 0.8rem; -} - -/* Remaining Courses */ -.remaining-courses { - margin-top: 20px; -} - -.remaining-courses h3 { - margin-bottom: 4px; - color: var(--gem-light); - text-transform: uppercase; -} - -.remaining-courses .hint { - margin-bottom: 12px; -} - -#remainingList { - max-height: 200px; - overflow-y: auto; -} - -.remaining-item { - display: flex; - justify-content: space-between; - align-items: center; - padding: 8px 12px; - background: var(--card-bg); - border: 1px solid var(--card-border); - margin-bottom: 2px; - font-size: 1rem; -} - -.remaining-item:nth-child(odd) { - background: rgba(0, 0, 0, 0.2); -} - -.remaining-item .course-code { - font-size: 1rem; -} - -.free-elective-notice { - padding: 12px; - background: rgba(0, 50, 100, 0.3); - border: 2px solid #224488; - margin-top: 12px; - font-size: 1rem; - color: var(--info); -} - -/* Custom Scrollbar */ -::-webkit-scrollbar { - width: 8px; -} - -::-webkit-scrollbar-track { - background: var(--bg-dark); - border: 1px solid var(--card-border); -} - -::-webkit-scrollbar-thumb { - background: linear-gradient(180deg, var(--gem-mid) 0%, var(--gem-dark) 100%); - border: 1px solid var(--gem-dark); -} - -::-webkit-scrollbar-thumb:hover { - background: linear-gradient(180deg, var(--gem-light) 0%, var(--gem-mid) 100%); -} - -/* Pixel corners decoration */ -.gwa-section::after { - content: ''; - position: absolute; - bottom: -3px; - right: -3px; - width: 10px; - height: 10px; - background: var(--gem-mid); - clip-path: polygon(100% 0, 100% 100%, 0 100%); -} - -/* Excluded Course Styles (for shiftees) */ -.course-item.excluded { - opacity: 0.5; - background: rgba(100, 0, 0, 0.15) !important; - border-left: 3px solid var(--danger); -} - -.course-item.excluded .course-code, -.course-item.excluded .course-title { - text-decoration: line-through; -} - -.exclude-toggle { - background: transparent; - border: 2px solid var(--card-border); - color: var(--text-secondary); - padding: 3px 6px; - font-family: var(--font-body); - font-size: 0.7rem; - cursor: pointer; - transition: all 0.15s; - margin-right: 6px; - min-width: 55px; -} - -.exclude-toggle:hover { - border-color: var(--gem-light); - color: var(--gem-light); -} - -.exclude-toggle.excluded { - background: rgba(255, 68, 68, 0.2); - border-color: var(--danger); - color: var(--danger); -} - -.exclude-toggle.excluded:hover { - background: rgba(0, 100, 0, 0.2); - border-color: var(--gem-light); - color: var(--gem-light); -} - -/* Shiftee Info Banner */ -.shiftee-info { - background: rgba(100, 100, 0, 0.2); - border: 2px solid #666600; - color: var(--warning); - padding: 10px 12px; - margin-bottom: 12px; - font-size: 0.95rem; -} - -.shiftee-info strong { - color: var(--warning); -} - -.shiftee-info small { - color: var(--text-secondary); - display: block; - margin-top: 4px; -} -/* Wrapped Feature */ - -.wrapped-container { - display: flex; - flex-direction: column; - min-height: 350px; -} - -.wrapped-panel { - flex: 1; - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - text-align: center; - padding: 20px; - min-height: 300px; - background: linear-gradient(135deg, var(--bg-dark) 0%, var(--bg-mid) 50%, var(--bg-light) 100%); - border: 3px solid var(--gem-mid); - box-shadow: 0 0 20px var(--gem-glow), inset 0 0 30px rgba(0, 212, 0, 0.1); - animation: panelGlow 2s ease-in-out infinite alternate; -} - -@keyframes panelGlow { - 0% { box-shadow: 0 0 20px var(--gem-glow), inset 0 0 30px rgba(0, 212, 0, 0.1); } - 100% { box-shadow: 0 0 35px var(--gem-glow), inset 0 0 40px rgba(0, 212, 0, 0.15); } -} - -.wrapped-panel .panel-emoji { - font-size: 3rem; - margin-bottom: 12px; - animation: bounce 1s ease-in-out infinite; -} - -@keyframes bounce { - 0%, 100% { transform: translateY(0); } - 50% { transform: translateY(-10px); } -} - -.wrapped-panel .panel-title { - font-family: var(--font-header); - font-size: 0.7rem; - color: var(--gem-light); - margin-bottom: 12px; - text-shadow: 0 0 6px var(--gem-glow); - line-height: 1.5; - word-wrap: break-word; - max-width: 300px; - text-align: center; -} - -.wrapped-panel .panel-value { - font-family: var(--font-header); - font-size: 1.6rem; - color: var(--text-primary); - margin-bottom: 12px; - text-shadow: 1px 1px 0 var(--gem-dark); -} - -.wrapped-panel .panel-subtitle { - font-family: var(--font-body); - font-size: 1rem; - color: var(--text-secondary); - max-width: 280px; - line-height: 1.4; -} - -.wrapped-panel .panel-message { - font-family: var(--font-body); - font-size: 0.95rem; - color: var(--gem-light); - margin-top: 15px; - padding: 10px 15px; - background: rgba(0, 212, 0, 0.15); - border: 2px solid var(--gem-mid); - max-width: 300px; - line-height: 1.5; -} - -.wrapped-panel .highlight-course { - background: rgba(0, 212, 0, 0.2); - padding: 8px 12px; - margin: 8px 0; - border-left: 4px solid var(--gem-mid); - text-align: left; - width: 100%; - max-width: 280px; - font-family: var(--font-body); -} - -.wrapped-panel .highlight-course .course-label { - font-family: var(--font-body); - font-size: 0.9rem; - color: var(--text-secondary); -} - -.wrapped-panel .highlight-course .course-name { - font-family: var(--font-body); - font-size: 1.1rem; - color: var(--gem-light); - font-weight: bold; -} - -.wrapped-panel .highlight-course .course-grade { - font-family: var(--font-body); - font-size: 1rem; - color: var(--text-primary); -} - -/* Wrapped Navigation */ -.wrapped-nav { - display: flex; - justify-content: space-between; - align-items: center; - padding: 15px 10px; - background: var(--card-bg); - border: 2px solid var(--card-border); - border-top: none; -} - -.wrapped-nav-btn { - background: linear-gradient(180deg, var(--gem-light) 0%, var(--gem-mid) 100%); - color: var(--bg-dark); - border: none; - padding: 8px 16px; - font-family: var(--font-body); - font-size: 0.85rem; - font-weight: bold; - cursor: pointer; - transition: all 0.2s; -} - -.wrapped-nav-btn:hover:not(:disabled) { - transform: scale(1.05); - box-shadow: 0 0 15px var(--gem-glow); -} - -.wrapped-nav-btn:disabled { - background: var(--bg-light); - color: var(--text-secondary); - cursor: not-allowed; -} - -.wrapped-progress { - font-family: 'Press Start 2P', cursive; - font-size: 0.55rem; - color: var(--gem-light); -} - -/* Export button */ -.wrapped-export { - margin-top: 15px; - width: 100%; - font-size: 1rem !important; -} - -/* Grade collector badges */ -.grade-badges { - display: flex; - flex-wrap: wrap; - justify-content: center; - gap: 8px; - margin: 10px 0; -} - -.grade-badge { - padding: 5px 10px; - font-size: 0.9rem; - border: 2px solid var(--gem-mid); - background: rgba(0, 212, 0, 0.2); - color: var(--gem-light); -} - -.grade-badge.special { - border-color: var(--warning); - background: rgba(255, 255, 0, 0.2); - color: var(--warning); -} - -.footer { - text-align: center; - padding: 12px; - margin-top: 16px; - border-top: 2px dashed var(--gem-dark); -} - -.footer a { - display: inline-block; - color: var(--bg-dark); - text-decoration: none; - font-size: 0.85rem; - font-weight: 600; - padding: 8px 16px; - background: linear-gradient(180deg, var(--gem-light) 0%, var(--gem-mid) 50%, var(--gem-dark) 100%); - border: 2px solid var(--gem-dark); - box-shadow: - inset -2px -2px 0 var(--gem-dark), - inset 2px 2px 0 rgba(255,255,255,0.3), - 0 4px 0 var(--gem-dark); - transition: all 0.1s; -} - -.footer a:hover { - transform: translateY(-2px); - box-shadow: - inset -2px -2px 0 var(--gem-dark), - inset 2px 2px 0 rgba(255,255,255,0.3), - 0 6px 0 var(--gem-dark); -} - -.footer a:active { - transform: translateY(2px); - box-shadow: - inset -2px -2px 0 var(--gem-dark), - inset 2px 2px 0 rgba(255,255,255,0.3), - 0 2px 0 var(--gem-dark); -} - -.legal-links { - margin-top: 10px; - font-size: 0.7rem; - font-weight: 400; - border-top: none; - padding: 0; -} - -.legal-links a { - display: inline; - background: none; - border: none; - box-shadow: none; - color: var(--text-muted); - padding: 0 4px; - font-weight: 500; -} - -.legal-links a:hover { - color: var(--text-secondary); - transform: none; - box-shadow: none; -} \ No newline at end of file diff --git a/popup.html b/popup.html deleted file mode 100644 index 28901a4..0000000 --- a/popup.html +++ /dev/null @@ -1,222 +0,0 @@ - - - - - - Elbi GradeSim - - - -
-
-

Elbi GradeSim

-
- -
-

Loading grades data...

-

Make sure you're logged into AMIS and have visited the Personal Information page.

-
- - - - - - - -
- - - - - diff --git a/popup.js b/popup.js deleted file mode 100644 index b5f9ee1..0000000 --- a/popup.js +++ /dev/null @@ -1,1473 +0,0 @@ -/** - * Elbi GradeSim - Popup Script - * Processes grades data locally. No data is collected, transmitted, or shared. - */ - -// Sanitize text to prevent XSS -function sanitizeText(text) { - if (typeof text !== 'string') return ''; - const div = document.createElement('div'); - div.textContent = text; - return div.innerHTML; -} - -// Store current track selection -let currentTrack = 'sp'; - -// Store excluded courses - Set of course IDs -let excludedCourses = new Set(); - -// Store current view mode -let currentView = 'semester'; - -document.addEventListener('DOMContentLoaded', async () => { - // Load excluded courses from storage - const savedExclusions = await chrome.storage.local.get(['excludedCourses']); - if (savedExclusions.excludedCourses) { - excludedCourses = new Set(savedExclusions.excludedCourses); - } - const loadingEl = document.getElementById('loading'); - const noDataEl = document.getElementById('noData'); - const mainContentEl = document.getElementById('mainContent'); - const refreshBtn = document.getElementById('refreshBtn'); - - // Tab handling - const tabs = document.querySelectorAll('.tab'); - tabs.forEach(tab => { - tab.addEventListener('click', () => { - tabs.forEach(t => t.classList.remove('active')); - tab.classList.add('active'); - - document.querySelectorAll('.tab-content').forEach(content => { - content.classList.remove('active'); - content.classList.add('hidden'); - }); - - const tabId = tab.dataset.tab + 'Tab'; - document.getElementById(tabId).classList.remove('hidden'); - document.getElementById(tabId).classList.add('active'); - }); - }); - - // Program selection handling - const programSelect = document.getElementById('programSelect'); - if (programSelect) { - // Load saved program preference - const savedProgram = await chrome.storage.local.get(['selectedProgram']); - if (savedProgram.selectedProgram) { - programSelect.value = savedProgram.selectedProgram; - setCurrentProgram(savedProgram.selectedProgram); - } else { - // Initialize with default program (BSCS) - setCurrentProgram('BSCS'); - } - - programSelect.addEventListener('change', (e) => { - const programCode = e.target.value; - setCurrentProgram(programCode); - chrome.storage.local.set({ selectedProgram: programCode }); - - // Recalculate remaining courses with new program - if (window.gradesData && window.gradesData.completedCourses) { - displayRemainingCourses(window.gradesData.completedCourses); - } - }); - } - - // Note: Track selection event listeners are now attached dynamically - // in updateTrackOptionsUI() when the program is set/changed - - // View toggle handling (Semester/Year) - document.querySelectorAll('.view-btn').forEach(btn => { - btn.addEventListener('click', () => { - document.querySelectorAll('.view-btn').forEach(b => b.classList.remove('active')); - btn.classList.add('active'); - currentView = btn.dataset.view; - // Re-render grades list with new view - if (window.gradesData && window.gradesData.courses) { - const { gradesBySemester } = calculateGWA(window.gradesData.courses, excludedCourses); - displayGradesList(gradesBySemester, window.gradesData.courses); - } - }); - }); - - // Load grades data - loadGradesData(); - - // Refresh button - refreshBtn.addEventListener('click', () => { - loadGradesData(); - }); - - // Calculate button - document.getElementById('calculateBtn').addEventListener('click', calculateWhatIf); - - // Custom GWA input handling - document.getElementById('customGWA').addEventListener('focus', () => { - document.querySelector('input[value="custom"]').checked = true; - }); -}); - -// Parse the AMIS data structure into a flat array of courses -function parseAMISData(data) { - const courses = []; - - if (!data || !data.student_grades) { - return courses; - } - - const studentGrades = data.student_grades; - - // Iterate through each term (1231, 1232, etc.) - for (const [termId, termData] of Object.entries(studentGrades)) { - if (!termData || !termData.values) continue; - - const termInfo = termData.term || `Term ${termId}`; - - // Iterate through each course in the term - for (const courseData of termData.values) { - courses.push({ - id: courseData.id, - courseCode: courseData.course?.course_code || 'Unknown', - courseTitle: courseData.course?.title || 'Unknown', - units: parseInt(courseData.unit_taken) || 0, - grade: courseData.grade, - section: courseData.section, - termId: termId, - term: termInfo, - semester: courseData.grade_term?.term || '', - academicYear: courseData.grade_term?.ay || '', - status: courseData.status - }); - } - } - - return courses; -} - -async function loadGradesData() { - const loadingEl = document.getElementById('loading'); - const noDataEl = document.getElementById('noData'); - const mainContentEl = document.getElementById('mainContent'); - - loadingEl.classList.remove('hidden'); - noDataEl.classList.add('hidden'); - mainContentEl.classList.add('hidden'); - - try { - // Get data from storage - let result = await chrome.storage.local.get(['gradesData']); - - // If no data, wait a moment and try again (data might still be loading) - if (!result.gradesData) { - await new Promise(resolve => setTimeout(resolve, 1000)); - result = await chrome.storage.local.get(['gradesData']); - } - - // Parse the AMIS data structure - const courses = parseAMISData(result.gradesData); - - if (courses && courses.length > 0) { - displayGradesData(courses); - loadingEl.classList.add('hidden'); - mainContentEl.classList.remove('hidden'); - } else { - loadingEl.classList.add('hidden'); - noDataEl.classList.remove('hidden'); - } - } catch (error) { - loadingEl.classList.add('hidden'); - noDataEl.classList.remove('hidden'); - } -} - -function displayGradesData(courses) { - // Calculate GWA with exclusions - const { gwa, totalUnits, totalCourses, gradesBySemester, completedCourses, excludedUnits, excludedCount } = calculateGWA(courses, excludedCourses); - - // Display GWA - document.getElementById('currentGWA').textContent = gwa.toFixed(4); - document.getElementById('totalUnits').textContent = excludedCount > 0 ? `${totalUnits} (${excludedUnits} excl.)` : totalUnits; - document.getElementById('totalCourses').textContent = excludedCount > 0 ? `${totalCourses} (${excludedCount} excl.)` : totalCourses; - - // Display honor status - displayHonorStatus(gwa); - - // Display grades list (pass courses for toggle functionality) - displayGradesList(gradesBySemester, courses); - - // Display remaining courses - displayRemainingCourses(completedCourses); - - // Display Wrapped feature - displayWrapped(courses); - - // Store calculated data for What If - window.gradesData = { - gwa, - totalUnits, - totalCourses, - completedCourses, - courses - }; -} - -function calculateGWA(courses, excludedIds = new Set()) { - let totalWeightedGrade = 0; - let totalUnits = 0; - let totalCourses = 0; - let excludedUnits = 0; - let excludedCount = 0; - const gradesBySemester = {}; - const completedCourses = []; - - // Courses to exclude from GWA calculation (by prefix) - const excludedPrefixes = ['NSTP', 'HK', 'PE']; - // Non-numeric grades that should be displayed but not counted in GWA - const nonNumericGrades = ['S', 'U', 'INC', 'DRP', 'W', 'P', 'DFG']; - - courses.forEach(course => { - const courseCode = course.courseCode || ''; - const courseId = String(course.id || `${course.courseCode}-${course.termId}`); - const gradeStr = (course.grade || '').toString().toUpperCase().trim(); - - // Skip NSTP, HK, PE courses entirely - const isPrefixExcluded = excludedPrefixes.some(prefix => - courseCode.toUpperCase().startsWith(prefix) - ); - if (isPrefixExcluded) { - return; - } - - const units = course.units || 0; - - // Check if it's a non-numeric grade (S, U, INC, etc.) - const isNonNumericGrade = nonNumericGrades.includes(gradeStr); - - // Group by semester (for display - includes all courses) - const semKey = course.term || `${course.academicYear} - ${course.semester}`; - if (!gradesBySemester[semKey]) { - gradesBySemester[semKey] = []; - } - - // Add course to display with special handling for S/U grades - const displayCourse = { ...course, isNonNumeric: isNonNumericGrade }; - gradesBySemester[semKey].push(displayCourse); - - // For non-numeric grades, add to completed but don't calculate GWA - if (isNonNumericGrade) { - // S (Satisfactory) counts as passed, U (Unsatisfactory) does not - if (gradeStr === 'S') { - completedCourses.push({ - code: course.courseCode, - title: course.courseTitle, - units: units, - grade: 'S' - }); - } - return; - } - - // Skip courses without valid numeric grades - const grade = parseFloat(course.grade); - if (isNaN(grade) || grade < 1.0 || grade > 5.0) { - return; - } - - if (units === 0) return; - - // Check if user-excluded (for shiftees) - const isUserExcluded = excludedIds.has(courseId); - if (isUserExcluded) { - // Track excluded stats but don't add to GWA - excludedUnits += units; - excludedCount++; - return; - } - - // Add to GWA calculation - totalWeightedGrade += grade * units; - totalUnits += units; - totalCourses++; - - completedCourses.push({ - code: course.courseCode, - title: course.courseTitle, - units: units, - grade: grade - }); - }); - - const gwa = totalUnits > 0 ? totalWeightedGrade / totalUnits : 0; - - return { gwa, totalUnits, totalCourses, gradesBySemester, completedCourses, excludedUnits, excludedCount }; -} - -function displayHonorStatus(gwa) { - const honorEl = document.getElementById('honorStatus'); - - if (gwa <= 1.20) { - honorEl.textContent = '🏆 Summa Cum Laude Track'; - honorEl.className = 'honor-status summa'; - honorEl.style.display = 'inline-block'; - } else if (gwa <= 1.45) { - honorEl.textContent = '🥈 Magna Cum Laude Track'; - honorEl.className = 'honor-status magna'; - honorEl.style.display = 'inline-block'; - } else if (gwa <= 1.75) { - honorEl.textContent = '🥉 Cum Laude Track'; - honorEl.className = 'honor-status cum-laude'; - honorEl.style.display = 'inline-block'; - } else if (gwa <= 2.00) { - honorEl.textContent = '📜 Honor Roll Track'; - honorEl.className = 'honor-status honor-roll'; - honorEl.style.display = 'inline-block'; - } else { - // Hide the element when not on any honor track - honorEl.textContent = ''; - honorEl.className = 'honor-status'; - honorEl.style.display = 'none'; - } -} - -function displayGradesList(gradesBySemester, allCourses) { - const listEl = document.getElementById('gradesList'); - listEl.innerHTML = ''; - - // Group courses based on current view mode - let groupedCourses; - if (currentView === 'year') { - // Group by academic year using course.academicYear directly - groupedCourses = {}; - Object.entries(gradesBySemester).forEach(([semester, courses]) => { - courses.forEach(course => { - // Use the academicYear field directly from course data - const yearKey = course.academicYear ? `AY ${course.academicYear}` : 'Unknown Year'; - - if (!groupedCourses[yearKey]) { - groupedCourses[yearKey] = []; - } - groupedCourses[yearKey].push(course); - }); - }); - } else { - // Use semester grouping as-is - groupedCourses = gradesBySemester; - } - - // Sort groups (semesters or years) - const sortedGroups = Object.keys(groupedCourses).sort().reverse(); - - sortedGroups.forEach(groupKey => { - const courses = groupedCourses[groupKey]; - - // Calculate GWA for this group (semester or year) - const groupGWA = calculateGroupGWA(courses); - - const semesterGroup = document.createElement('div'); - semesterGroup.className = 'semester-group'; - - const header = document.createElement('div'); - header.className = 'semester-header'; - - // Build header with group GWA and scholar status - let scholarStatus = ''; - let scholarClass = ''; - if (groupGWA.gwa > 0) { - if (groupGWA.gwa <= 1.45) { - scholarStatus = '🎓 University Scholar'; - scholarClass = 'university-scholar'; - } else if (groupGWA.gwa <= 1.75) { - scholarStatus = '📚 College Scholar'; - scholarClass = 'college-scholar'; - } else if (groupGWA.gwa <= 2.00) { - scholarStatus = '📜 Honor Roll'; - scholarClass = 'honor-roll'; - } - } - - header.innerHTML = ` - ${sanitizeText(groupKey)} - - GWA: ${groupGWA.gwa > 0 ? groupGWA.gwa.toFixed(4) : 'N/A'} - ${scholarStatus ? `${scholarStatus}` : ''} - - `; - semesterGroup.appendChild(header); - - courses.forEach(course => { - const courseId = String(course.id || `${course.courseCode}-${course.termId}`); - const isExcluded = excludedCourses.has(courseId); - const gradeStr = (course.grade || '').toString().toUpperCase().trim(); - const isNonNumeric = course.isNonNumeric || ['S', 'U', 'INC', 'DRP', 'W', 'P', 'DFG'].includes(gradeStr); - - const item = document.createElement('div'); - item.className = 'course-item' + (isExcluded ? ' excluded' : '') + (isNonNumeric ? ' non-numeric' : ''); - item.dataset.courseId = courseId; - - let gradeDisplay, gradeClass; - if (isNonNumeric) { - gradeDisplay = gradeStr; - if (gradeStr === 'S') { - gradeClass = 'satisfactory'; - } else if (gradeStr === 'U') { - gradeClass = 'unsatisfactory'; - } else { - gradeClass = 'other-grade'; - } - } else { - const grade = parseFloat(course.grade); - gradeDisplay = grade.toFixed(2); - gradeClass = 'passing'; - if (grade <= 1.50) gradeClass = 'excellent'; - else if (grade <= 2.00) gradeClass = 'good'; - else if (grade >= 5.00) gradeClass = 'failed'; - } - - // Don't show exclude button for non-numeric grades (they don't affect GWA anyway) - const excludeButton = isNonNumeric ? '' : ` - - `; - - item.innerHTML = ` -
-
${sanitizeText(course.courseCode || course.code)}
-
${sanitizeText(course.courseTitle || course.title || '')}
-
-
- ${excludeButton} - ${course.units}u - ${gradeDisplay} -
- `; - - semesterGroup.appendChild(item); - }); - - listEl.appendChild(semesterGroup); - }); - - // Add event listeners to toggle buttons - listEl.querySelectorAll('.exclude-toggle').forEach(btn => { - btn.addEventListener('click', (e) => { - e.stopPropagation(); - const courseId = btn.dataset.courseId; - toggleCourseExclusion(courseId, allCourses); - }); - }); -} - -// Calculate GWA for a specific group of courses (semester or year) -function calculateGroupGWA(courses) { - let totalWeightedGrade = 0; - let totalUnits = 0; - - courses.forEach(course => { - const courseId = String(course.id || `${course.courseCode}-${course.termId}`); - - // Skip excluded courses - if (excludedCourses.has(courseId)) { - return; - } - - const grade = parseFloat(course.grade); - if (isNaN(grade) || grade < 1.0 || grade > 5.0) return; - - const units = course.units || 0; - if (units === 0) return; - - totalWeightedGrade += grade * units; - totalUnits += units; - }); - - const gwa = totalUnits > 0 ? totalWeightedGrade / totalUnits : 0; - return { gwa, totalUnits }; -} - -function displayRemainingCourses(completedCourses) { - const listEl = document.getElementById('remainingList'); - const trackInfoEl = document.getElementById('trackInfo'); - listEl.innerHTML = ''; - - // Get current curriculum - const curriculum = getCurrentCurriculum(); - - // Check if program has curriculum data - if (!curriculum.available) { - listEl.innerHTML = ` -
- ⚠ Curriculum Not Available -
The curriculum for ${curriculum.name} is coming soon. Only GWA calculation is available. -
- `; - trackInfoEl.innerHTML = ''; - return; - } - - // Detect track based on completed courses - const detectedTrack = detectTrack(completedCourses); - - // Update track info display and radio buttons - if (curriculum.tracks) { - if (detectedTrack) { - currentTrack = detectedTrack; - const trackInfo = curriculum.tracks[detectedTrack]; - const trackName = trackInfo ? `${trackInfo.name} (${trackInfo.code})` : detectedTrack; - trackInfoEl.className = 'track-info detected'; - trackInfoEl.innerHTML = `✓ Detected: ${trackName}`; - } else { - trackInfoEl.className = 'track-info not-detected'; - trackInfoEl.innerHTML = `⚠ Track not yet detected. Defaulting to SP Track. Select your track below:`; - } - - // Set the radio button to match current track - const trackRadio = document.querySelector(`input[name="track"][value="${currentTrack}"]`); - if (trackRadio) { - trackRadio.checked = true; - } - } else { - trackInfoEl.innerHTML = ''; - } - - // Get free elective units based on current track - const freeElectiveUnitsTotal = getFreeElectiveUnits(currentTrack); - - // Get completed course codes (normalized) - const completedCodes = new Set(completedCourses.map(c => c.code.toUpperCase().trim())); - - // Filter remaining required courses from curriculum - const remaining = (curriculum.majorCourses || []).filter(course => { - const code = course.code.toUpperCase().trim(); - return !completedCodes.has(code); - }); - - // Calculate remaining units from required courses - let remainingUnits = remaining.reduce((sum, c) => sum + c.units, 0); - - // Count completed GE courses (title starts with "(GE)") - const completedGECount = completedCourses.filter(c => - c.title && c.title.trim().startsWith("(GE)") - ).length; - const geRequired = curriculum.geCoursesRequired || 9; - const remainingGESlots = Math.max(0, geRequired - completedGECount); - - // Calculate free elective units used - // Free electives = courses that are NOT required AND NOT GE courses - const requiredCodesSet = new Set((curriculum.requiredCodes || []).map(c => c.toUpperCase().trim())); - const freeElectives = completedCourses.filter(c => { - const code = c.code.toUpperCase().trim(); - const isRequired = requiredCodesSet.has(code); - const isGE = c.title && c.title.trim().startsWith("(GE)"); - return !isRequired && !isGE; - }); - - const freeElectiveUnitsTaken = freeElectives.reduce((sum, c) => sum + c.units, 0); - const freeElectiveUnitsRemaining = Math.max(0, freeElectiveUnitsTotal - freeElectiveUnitsTaken); - - // Display remaining required courses - remaining.forEach(course => { - const item = document.createElement('div'); - item.className = 'remaining-item'; - item.setAttribute('role', 'listitem'); - item.innerHTML = ` - ${sanitizeText(course.code)} - ${course.units}u - `; - listEl.appendChild(item); - }); - - // Add GE notice if remaining - if (remainingGESlots > 0) { - const geNotice = document.createElement('div'); - geNotice.className = 'free-elective-notice'; - geNotice.innerHTML = ` - GE Courses: ${remainingGESlots} more GE course(s) needed -
Completed ${completedGECount}/${geRequired} required GE courses. - `; - listEl.appendChild(geNotice); - } - - // Add free elective notice - const notice = document.createElement('div'); - notice.className = 'free-elective-notice'; - const trackLabel = currentTrack === 'thesis' ? 'Thesis' : 'SP'; - if (freeElectiveUnitsRemaining > 0) { - notice.innerHTML = ` - Free Electives: ${freeElectiveUnitsRemaining} units remaining -
Taken ${freeElectiveUnitsTaken}/${freeElectiveUnitsTotal} free elective units (${trackLabel} track). - `; - } else { - notice.innerHTML = ` - Free Electives: ✓ Complete -
Taken ${freeElectiveUnitsTaken}/${freeElectiveUnitsTotal} free elective units (${trackLabel} track). - `; - notice.style.background = 'rgba(0, 100, 0, 0.3)'; - notice.style.borderColor = 'var(--gem-dark)'; - notice.style.color = 'var(--gem-light)'; - } - listEl.appendChild(notice); - - // Store remaining courses for What If calculation - window.remainingCourses = remaining; - window.freeElectiveUnitsRemaining = freeElectiveUnitsRemaining; - window.remainingGESlots = remainingGESlots; - window.currentTrack = currentTrack; -} - -// Toggle course exclusion (for shiftees) -async function toggleCourseExclusion(courseId, allCourses) { - // Ensure courseId is a string for consistent comparison - const id = String(courseId); - - if (excludedCourses.has(id)) { - excludedCourses.delete(id); - } else { - excludedCourses.add(id); - } - - // Save to storage - await chrome.storage.local.set({ excludedCourses: Array.from(excludedCourses) }); - - // Recalculate and redisplay - displayGradesData(allCourses); -} - -function calculateWhatIf() { - const targetRadio = document.querySelector('input[name="targetHonor"]:checked'); - if (!targetRadio) { - showResults('error', 'Please select a target honor.'); - return; - } - - let targetGWA; - if (targetRadio.value === 'custom') { - targetGWA = parseFloat(document.getElementById('customGWA').value); - if (isNaN(targetGWA) || targetGWA < 1.0 || targetGWA > 5.0) { - showResults('error', 'Please enter a valid GWA between 1.00 and 5.00'); - return; - } - } else { - targetGWA = parseFloat(targetRadio.value); - } - - const data = window.gradesData; - const remaining = window.remainingCourses || []; - const freeElectiveUnits = window.freeElectiveUnitsRemaining || 0; - - // Calculate remaining units (excluding PE/NSTP) - let remainingUnits = remaining.reduce((sum, c) => sum + c.units, 0) + freeElectiveUnits; - - // Add GE remaining units (assume 3 units each) - const remainingGEUnits = (window.remainingGESlots || 0) * 3; - remainingUnits += remainingGEUnits; - - // Current weighted sum - const currentWeightedSum = data.gwa * data.totalUnits; - - // Total units after completion - const totalUnitsAfter = data.totalUnits + remainingUnits; - - // Check if already achieved - if (data.gwa <= targetGWA) { - showResults('achieved', targetGWA, data.gwa, data.totalUnits, remainingUnits, data.gwa); - return; - } - - // Required weighted sum for target GWA - const requiredWeightedSum = targetGWA * totalUnitsAfter; - - // Remaining weighted sum needed - const remainingWeightedSumNeeded = requiredWeightedSum - currentWeightedSum; - - // Required average grade for remaining courses - const requiredAvgGrade = remainingUnits > 0 ? remainingWeightedSumNeeded / remainingUnits : 0; - - // Determine feasibility - if (requiredAvgGrade < 1.0) { - // Impossible - would need grades better than 1.0 - showResults('impossible-low', targetGWA, data.gwa, data.totalUnits, remainingUnits, requiredAvgGrade); - } else if (requiredAvgGrade > 5.0) { - // Impossible - would need grades worse than 5.0 - showResults('impossible-high', targetGWA, data.gwa, data.totalUnits, remainingUnits, requiredAvgGrade); - } else { - // Possible - show required grade - showResults('possible', targetGWA, data.gwa, data.totalUnits, remainingUnits, requiredAvgGrade); - } -} - -function showResults(status, targetGWA, currentGWA, unitsCompleted, unitsRemaining, requiredGrade) { - const resultsEl = document.getElementById('whatifResults'); - const contentEl = document.getElementById('resultsContent'); - resultsEl.classList.remove('hidden'); - - const honorNames = { - 1.20: 'Summa Cum Laude', - 1.45: 'Magna Cum Laude', - 1.75: 'Cum Laude', - 2.00: 'Honor Roll' - }; - - if (status === 'error') { - contentEl.innerHTML = `
${targetGWA}
`; - return; - } - - const targetName = honorNames[targetGWA] || `GWA of ${targetGWA.toFixed(2)}`; - - let statusClass, statusMessage, gradeDisplay; - - switch (status) { - case 'achieved': - statusClass = 'achieved'; - statusMessage = `🎉 You've already achieved ${targetName}!`; - gradeDisplay = 'Maintain your current performance!'; - break; - case 'impossible-low': - statusClass = 'impossible'; - statusMessage = `❌ ${targetName} is no longer achievable.`; - gradeDisplay = `Would require avg grade of ${requiredGrade.toFixed(2)} (below 1.00 - impossible)`; - break; - case 'impossible-high': - statusClass = 'impossible'; - statusMessage = `❌ ${targetName} is not achievable with remaining courses.`; - gradeDisplay = `Would require avg grade of ${requiredGrade.toFixed(2)} (above 5.00)`; - break; - case 'possible': - if (requiredGrade <= 1.25) { - statusClass = 'excellent'; - statusMessage = `✨ ${targetName} is achievable with excellent performance!`; - } else if (requiredGrade <= 1.75) { - statusClass = 'good'; - statusMessage = `✅ ${targetName} is achievable with very good performance.`; - } else if (requiredGrade <= 2.50) { - statusClass = 'moderate'; - statusMessage = `⚡ ${targetName} is achievable with good performance.`; - } else { - statusClass = 'difficult'; - statusMessage = `⚠️ ${targetName} is challenging but possible.`; - } - gradeDisplay = requiredGrade.toFixed(4); - break; - } - - contentEl.innerHTML = ` -
- ${statusMessage} -
-
-
- Current GWA - ${currentGWA.toFixed(4)} -
-
- Target GWA - ≤ ${targetGWA.toFixed(2)} -
-
- Units Completed - ${unitsCompleted} -
-
- Units Remaining - ${unitsRemaining} -
-
- Required Avg Grade - ${gradeDisplay} -
-
- `; -} -// Wrapped Feature - -let wrappedCurrentPanel = 0; -let wrappedPanels = []; - -function initializeWrapped() { - const prevBtn = document.getElementById('wrappedPrev'); - const nextBtn = document.getElementById('wrappedNext'); - const exportBtn = document.getElementById('wrappedExport'); - - if (prevBtn && nextBtn) { - prevBtn.addEventListener('click', () => navigateWrapped(-1)); - nextBtn.addEventListener('click', () => navigateWrapped(1)); - } - - if (exportBtn) { - exportBtn.addEventListener('click', exportWrappedToPNG); - } -} - -function generateWrappedData(courses) { - const data = { - gwa: 0, - totalUnits: 0, - distinctGrades: new Set(), - gradeDistribution: {}, - fails: 0, - retakes: {}, - highestGradeCourse: null, - lowestGradeCourse: null, - mostRetakedCourse: null, - totalCourses: 0, - specialGrades: [], - semesterGWAs: {} // Track GWA per semester - }; - - const nonNumericGrades = ['S', 'U', 'INC', 'DRP', 'W', 'P', 'DFG']; - const excludedPrefixes = ['NSTP', 'HK', 'PE']; - let totalWeighted = 0; - - // Temporary storage for semester calculations - const semesterData = {}; - - courses.forEach(course => { - const courseCode = course.courseCode || ''; - const gradeStr = (course.grade || '').toString().toUpperCase().trim(); - const semKey = course.term || `${course.academicYear} - ${course.semester}`; - - // Skip excluded prefixes - if (excludedPrefixes.some(prefix => courseCode.toUpperCase().startsWith(prefix))) { - return; - } - - // Track all distinct grades (including special ones) - data.distinctGrades.add(gradeStr); - - // Track special grades - if (nonNumericGrades.includes(gradeStr)) { - if (!data.specialGrades.includes(gradeStr)) { - data.specialGrades.push(gradeStr); - } - return; - } - - const grade = parseFloat(course.grade); - const units = course.units || 0; - - if (isNaN(grade) || grade < 1.0 || grade > 5.0 || units === 0) return; - - data.totalCourses++; - - // Track semester GWA data - if (!semesterData[semKey]) { - semesterData[semKey] = { totalWeighted: 0, totalUnits: 0 }; - } - semesterData[semKey].totalWeighted += grade * units; - semesterData[semKey].totalUnits += units; - - // Track grade distribution - const gradeKey = grade.toFixed(2); - data.gradeDistribution[gradeKey] = (data.gradeDistribution[gradeKey] || 0) + 1; - - // Track retakes (same course code appearing multiple times) - const codeNorm = courseCode.toUpperCase().trim(); - data.retakes[codeNorm] = (data.retakes[codeNorm] || 0) + 1; - - // Track fails - if (grade === 5.0) { - data.fails++; - } - - // Track highest grade course - if (!data.highestGradeCourse || grade < data.highestGradeCourse.grade) { - data.highestGradeCourse = { code: courseCode, title: course.courseTitle, grade }; - } - - // Track lowest grade course (excluding 5.0) - if (grade < 5.0) { - if (!data.lowestGradeCourse || grade > data.lowestGradeCourse.grade) { - data.lowestGradeCourse = { code: courseCode, title: course.courseTitle, grade }; - } - } - - totalWeighted += grade * units; - data.totalUnits += units; - }); - - // Find most retaked course - let maxRetakes = 0; - Object.entries(data.retakes).forEach(([code, count]) => { - if (count > maxRetakes) { - maxRetakes = count; - const courseInfo = courses.find(c => c.courseCode.toUpperCase() === code); - data.mostRetakedCourse = { - code, - title: courseInfo?.courseTitle || code, - count - }; - } - }); - - // Only set if actually retaken - if (maxRetakes <= 1) data.mostRetakedCourse = null; - - data.gwa = data.totalUnits > 0 ? totalWeighted / data.totalUnits : 0; - - // Calculate semester GWAs - Object.entries(semesterData).forEach(([sem, semData]) => { - if (semData.totalUnits > 0) { - data.semesterGWAs[sem] = { - gwa: semData.totalWeighted / semData.totalUnits, - units: semData.totalUnits - }; - } - }); - - // Find best and "needs improvement" semesters - let bestSem = null, worstSem = null; - Object.entries(data.semesterGWAs).forEach(([sem, semInfo]) => { - if (!bestSem || semInfo.gwa < bestSem.gwa) { - bestSem = { name: sem, gwa: semInfo.gwa, units: semInfo.units }; - } - if (!worstSem || semInfo.gwa > worstSem.gwa) { - worstSem = { name: sem, gwa: semInfo.gwa, units: semInfo.units }; - } - }); - data.bestSemester = bestSem; - data.needsWorkSemester = worstSem; - - return data; -} - -function generateWrappedPanels(data) { - const panels = []; - - // Panel 1: GWA Overview - panels.push(generateGWAPanel(data)); - - // Panel 2: Best/Toughest Semester - panels.push(generateSemesterPanel(data)); - - // Panel 3: Grade Collector - panels.push(generateCollectorPanel(data)); - - // Panel 4: Perseverance (Fails) - panels.push(generatePerseverancePanel(data)); - - // Panel 5: Course Highlights - panels.push(generateHighlightsPanel(data)); - - // Panel 6: Graduation Progress - panels.push(generateProgressPanel(data)); - - return panels; -} - -function generateGWAPanel(data) { - const gwa = data.gwa; - let emoji, title, message; - - if (gwa === 0) { - emoji = "📚"; - title = "LOADING..."; - message = "Freshie ka ba? Or di pa nag-uupload ng grades si registrar. Either way, good luck sa journey mo!"; - } else if (gwa <= 1.20) { - emoji = "🏆"; - title = "SUMMA MATERIAL"; - message = "Grabe naman 'to?? Penge tips naman. Seryoso, pano mo nagagawa 'yan habang may social life??"; - } else if (gwa <= 1.45) { - emoji = "⭐"; - title = "MAGNA TINGZ"; - message = "Consistent high grades! Ikaw yung type na maayos notes tapos hinahanap ka ng groupmates pag may exam."; - } else if (gwa <= 1.75) { - emoji = "🎯"; - title = "CUM LAUDE SZN"; - message = "Solid GWA! Di ka nag-slack off pero di rin naman nagpaka-robot. Balance talaga."; - } else if (gwa <= 2.00) { - emoji = "🌟"; - title = "HONOR ROLL"; - message = "Pasok sa honors! May hirap-hirap pero kinaya mo naman. Proud of u!"; - } else if (gwa <= 2.50) { - emoji = "💪"; - title = "PASANG-ALAM"; - message = "Passing is passing! May mga sem na mabigat talaga, wag ka maguilty. Nasa UP ka pa rin."; - } else if (gwa <= 3.00) { - emoji = "🎮"; - title = "SURVIVAL MODE"; - message = "Nandito ka pa, that counts. Minsan ganyan talaga UP — basta graduate, panalo."; - } else { - emoji = "🌱"; - title = "COMEBACK ARC"; - message = "Mababa man ngayon, pwede pa 'yan i-improve. Marami nang naka-recover from this. Kaya mo 'yan."; - } - - return ` -
${emoji}
-
${title}
-
${gwa > 0 ? gwa.toFixed(4) : '--'}
-
My Elbi GWA
-
${message}
- `; -} - -function generateSemesterPanel(data) { - const numSemesters = Object.keys(data.semesterGWAs).length; - - if (numSemesters === 0) { - return ` -
📅
-
SEMESTER STATS
-
--
-
No semester data yet
-
Check back once grades are in!
- `; - } - - let content = `
📅
-
SEMESTER STATS
`; - - // Best semester - if (data.bestSemester) { - const best = data.bestSemester; - let reaction = ''; - if (best.gwa <= 1.25) reaction = 'Your peak'; - else if (best.gwa <= 1.50) reaction = 'Nice run!'; - else if (best.gwa <= 1.75) reaction = 'Solid sem'; - else if (best.gwa <= 2.00) reaction = 'Good one'; - else reaction = 'Best so far'; - - content += ` -
-
🏆 Best Semester
-
${sanitizeText(best.name)}
-
GWA: ${best.gwa.toFixed(4)} — ${reaction}
-
- `; - } - - // "Needs work" semester (only if different from best) - if (data.needsWorkSemester && data.bestSemester && - data.needsWorkSemester.name !== data.bestSemester.name) { - const tough = data.needsWorkSemester; - let reaction = ''; - if (tough.gwa >= 3.0) reaction = 'Rough one'; - else if (tough.gwa >= 2.5) reaction = 'Challenging'; - else if (tough.gwa >= 2.0) reaction = 'Tough load'; - else reaction = 'Room to grow'; - - content += ` -
-
📈 Toughest Semester
-
${sanitizeText(tough.name)}
-
GWA: ${tough.gwa.toFixed(4)} — ${reaction}
-
- `; - } - - // Overall message - const semCount = Object.keys(data.semesterGWAs).length; - let overallMsg = ''; - if (semCount >= 8) overallMsg = `${semCount} semesters completed — almost there!`; - else if (semCount >= 6) overallMsg = `${semCount} sems done. The end is in sight.`; - else if (semCount >= 4) overallMsg = `${semCount} sems in — you're halfway through!`; - else if (semCount >= 2) overallMsg = `${semCount} semesters down, many more to go.`; - else overallMsg = `Just getting started. Enjoy the ride!`; - - content += `
${overallMsg}
`; - - return content; -} - -function generateCollectorPanel(data) { - const numericGrades = Object.keys(data.gradeDistribution).length; - const specialCount = data.specialGrades.length; - const totalDistinct = data.distinctGrades.size; - - let emoji, title, message; - - // Check for "mega collector" status (many distinct grades including special ones) - if (totalDistinct >= 10) { - emoji = "🎰"; - title = "FULL COLLECTION"; - message = "You've seen it all — from 1.0 to 5.0, plus S, U, INC, DRP... your transcript tells a story."; - } else if (totalDistinct >= 7) { - emoji = "🃏"; - title = "VARIETY PACK"; - message = "A bit of everything! Shows you've taken different kinds of subjects with different outcomes."; - } else if (totalDistinct >= 4) { - emoji = "🎲"; - title = "MIXED BAG"; - message = "Some ups, some downs. Pretty normal for most students tbh."; - } else if (numericGrades === 1 && data.gwa <= 1.5) { - emoji = "🎯"; - title = "ONE-TRACK MIND"; - message = "Same high grade over and over? That's rare consistency. How."; - } else { - emoji = "📊"; - title = "STEADY GRADES"; - message = "You stick to a range. Predictable in a good way — you know what you're doing."; - } - - // Build grade badges - let badgesHtml = '
'; - const sortedGrades = Array.from(data.distinctGrades).sort(); - sortedGrades.forEach(grade => { - const isSpecial = ['S', 'U', 'INC', 'DRP', 'W', 'P', 'DFG'].includes(grade); - badgesHtml += `${sanitizeText(grade)}`; - }); - badgesHtml += '
'; - - return ` -
${emoji}
-
${title}
-
${totalDistinct}
-
distinct grades collected
- ${badgesHtml} -
${message}
- `; -} - -function generatePerseverancePanel(data) { - const fails = data.fails; - let emoji, title, message; - - if (fails === 0) { - emoji = "🏅"; - title = "CLEAN RECORD"; - message = "Zero 5.0s? That's actually impressive. Not everyone can say that."; - } else if (fails === 1) { - emoji = "💫"; - title = "ONE SETBACK"; - message = "One 5.0 isn't the end of the world. It happens to plenty of us. Bounce back season."; - } else if (fails <= 3) { - emoji = "🔥"; - title = "STILL STANDING"; - message = `${fails} failed subjects but you're still here. Every retake is a chance to do better.`; - } else if (fails <= 5) { - emoji = "🦅"; - title = "FIGHTING SPIRIT"; - message = `${fails} times down but not out. The fact that you're still going says a lot.`; - } else { - emoji = "💎"; - title = "SURVIVOR"; - message = `${fails} 5.0s and still pushing through? That takes real grit. Respect.`; - } - - return ` -
${emoji}
-
${title}
-
${fails}
-
${fails === 1 ? 'subject to retake' : 'subjects to retake'}
-
${message}
- `; -} - -function generateHighlightsPanel(data) { - let content = `
🫴🫴
-
YOUR HIGHLIGHTS
`; - - if (data.highestGradeCourse) { - content += ` -
-
🏆 Best Performance
-
${sanitizeText(data.highestGradeCourse.code)}
-
Grade: ${data.highestGradeCourse.grade.toFixed(2)}
-
- `; - } - - if (data.lowestGradeCourse && data.lowestGradeCourse.grade < 5.0) { - content += ` -
-
📈 Room to Grow
-
${sanitizeText(data.lowestGradeCourse.code)}
-
Grade: ${data.lowestGradeCourse.grade.toFixed(2)}
-
- `; - } - - if (data.mostRetakedCourse && data.mostRetakedCourse.count > 1) { - content += ` -
-
🔄 Persistence Award
-
${sanitizeText(data.mostRetakedCourse.code)}
-
Taken ${data.mostRetakedCourse.count}x — you didn't give up
-
- `; - } - - if (!data.highestGradeCourse && !data.lowestGradeCourse) { - content += `
Complete some subjects to see your highlights!
`; - } - - content += `
Every subject is part of your journey
`; - - return content; -} - -function generateProgressPanel(data) { - const totalRequired = currentProgram?.totalUnitsRequired || 155; - const completed = data.totalUnits; - const remaining = Math.max(0, totalRequired - completed); - const percentage = Math.min(100, Math.round((completed / totalRequired) * 100)); - - let emoji, title, message; - - if (percentage >= 100) { - emoji = "🎓"; - title = "GRADUATION READY"; - message = "All units done! Time to march. Congratulations — you made it."; - } else if (percentage >= 80) { - emoji = "🚀"; - title = "ALMOST THERE"; - message = `${remaining} units left. The finish line is in sight. Last push na 'to.`; - } else if (percentage >= 60) { - emoji = "⚡"; - title = "PAST HALFWAY"; - message = `${remaining} units to go. More than half done — you've got momentum.`; - } else if (percentage >= 40) { - emoji = "🌤️"; - title = "MAKING PROGRESS"; - message = `${remaining} units remaining. Take it one sem at a time.`; - } else if (percentage >= 20) { - emoji = "🌅"; - title = "EARLY DAYS"; - message = `${remaining} units ahead of you. Plenty of time to figure things out.`; - } else { - emoji = "🌱"; - title = "JUST STARTING"; - message = `${remaining} units to complete. Welcome to the journey — it's a marathon, not a sprint.`; - } - - return ` -
${emoji}
-
${title}
-
${percentage}%
-
${completed} / ${totalRequired} units passed
-
${message}
- `; -} - -function displayWrapped(courses) { - const data = generateWrappedData(courses); - wrappedPanels = generateWrappedPanels(data); - wrappedCurrentPanel = 0; - updateWrappedDisplay(); -} - -function updateWrappedDisplay() { - const panelEl = document.getElementById('wrappedPanel'); - const progressEl = document.getElementById('wrappedProgress'); - const prevBtn = document.getElementById('wrappedPrev'); - const nextBtn = document.getElementById('wrappedNext'); - - if (!panelEl || !wrappedPanels.length) return; - - panelEl.innerHTML = wrappedPanels[wrappedCurrentPanel]; - progressEl.textContent = `${wrappedCurrentPanel + 1} / ${wrappedPanels.length}`; - - prevBtn.disabled = wrappedCurrentPanel === 0; - nextBtn.disabled = wrappedCurrentPanel === wrappedPanels.length - 1; -} - -function navigateWrapped(direction) { - const newIndex = wrappedCurrentPanel + direction; - if (newIndex >= 0 && newIndex < wrappedPanels.length) { - wrappedCurrentPanel = newIndex; - updateWrappedDisplay(); - } -} - -// Export wrapped panel to PNG -async function exportWrappedToPNG() { - const panel = document.getElementById('wrappedPanel'); - if (!panel) return; - - // Create a canvas - Instagram story dimensions (1080x1920) but scaled down - const canvas = document.createElement('canvas'); - const ctx = canvas.getContext('2d'); - - // Compact story dimensions - const scale = 2; - const width = 360; - const height = 640; - canvas.width = width * scale; - canvas.height = height * scale; - ctx.scale(scale, scale); - - // Transparent background - don't fill anything - ctx.clearRect(0, 0, width, height); - - // Get panel content - const emoji = panel.querySelector('.panel-emoji')?.textContent || ''; - const title = panel.querySelector('.panel-title')?.textContent || ''; - const value = panel.querySelector('.panel-value')?.textContent || ''; - const subtitle = panel.querySelector('.panel-subtitle')?.textContent || ''; - const highlightCourses = panel.querySelectorAll('.highlight-course'); - const badges = panel.querySelectorAll('.grade-badge'); - - // Calculate content height for centering - let contentHeight = 120; // Base height for emoji + title + value - if (subtitle) contentHeight += 30; - if (badges.length > 0) contentHeight += 50; - if (highlightCourses.length > 0) contentHeight += highlightCourses.length * 55; - - // Start Y position to center content - let currentY = Math.max(80, (height - contentHeight) / 2 - 40); - - // Draw content - ctx.textAlign = 'center'; - - // Strava-like orange colors for export - const stravaOrange = '#fc5200'; - const stravaOrangeLight = '#ff7a33'; - const stravaOrangeDark = '#e04800'; - - // Helper for text with dark outline for contrast on any background - function drawTextWithOutline(text, x, y, fillColor, outlineColor = '#000000') { - ctx.lineWidth = 4; - ctx.strokeStyle = outlineColor; - ctx.lineJoin = 'round'; - ctx.miterLimit = 2; - ctx.strokeText(text, x, y); - ctx.fillStyle = fillColor; - ctx.fillText(text, x, y); - } - - // Helper for word wrapping - function wrapText(text, maxWidth) { - const words = text.split(' '); - const lines = []; - let currentLine = ''; - - words.forEach(word => { - const testLine = currentLine + word + ' '; - if (ctx.measureText(testLine).width > maxWidth && currentLine !== '') { - lines.push(currentLine.trim()); - currentLine = word + ' '; - } else { - currentLine = testLine; - } - }); - if (currentLine.trim()) lines.push(currentLine.trim()); - return lines; - } - - // Header with branding at top - ctx.font = '8px "Press Start 2P", monospace'; - drawTextWithOutline('Elbi GradeSim', width / 2, 64, stravaOrangeLight); - ctx.font = '10px "Press Start 2P", monospace'; - drawTextWithOutline('amis.stimmie.dev', width / 2, 80, stravaOrange); - - // Emoji - ctx.font = '56px Arial'; - drawTextWithOutline(emoji, width / 2, currentY, '#ffffff'); - currentY += 50; - - // Title (removed - just showing data, not descriptors) - // Skip title, go straight to value - - // Value (the main data point) - if (value && value !== '--') { - ctx.font = 'bold 28px "Press Start 2P", monospace'; - drawTextWithOutline(value, width / 2, currentY + 20, stravaOrange); - currentY += 60; - } - - // Subtitle (brief context) - if (subtitle) { - ctx.font = '16px "VT323", monospace'; - const subtitleLines = wrapText(subtitle, width - 60); - subtitleLines.forEach(line => { - drawTextWithOutline(line, width / 2, currentY + 10, stravaOrangeLight); - currentY += 18; - }); - currentY += 10; - } - - // Grade badges if present (for collector panel) - if (badges.length > 0) { - currentY += 15; - const badgeWidth = 44; - const maxPerRow = Math.floor((width - 60) / badgeWidth); - const rows = Math.ceil(badges.length / maxPerRow); - - let badgeIndex = 0; - for (let row = 0; row < rows; row++) { - const badgesInRow = Math.min(maxPerRow, badges.length - badgeIndex); - const totalRowWidth = badgesInRow * badgeWidth; - let badgeX = (width - totalRowWidth) / 2; - - for (let i = 0; i < badgesInRow && badgeIndex < badges.length; i++) { - ctx.fillStyle = 'rgba(0, 0, 0, 0.6)'; - ctx.strokeStyle = stravaOrange; - ctx.lineWidth = 2; - ctx.beginPath(); - ctx.roundRect(badgeX, currentY, 40, 24, 4); - ctx.fill(); - ctx.stroke(); - ctx.font = '16px "VT323", monospace'; - drawTextWithOutline(badges[badgeIndex].textContent, badgeX + 20, currentY + 17, stravaOrange); - badgeX += badgeWidth; - badgeIndex++; - } - currentY += 30; - } - currentY += 10; - } - - // Highlight courses (for semester stats and highlights panels) - if (highlightCourses.length > 0) { - currentY += 10; - highlightCourses.forEach(course => { - const name = course.querySelector('.course-name')?.textContent || ''; - const grade = course.querySelector('.course-grade')?.textContent || ''; - - // Compact highlight box with dark background for contrast - ctx.fillStyle = 'rgba(0, 0, 0, 0.6)'; - ctx.strokeStyle = stravaOrange; - ctx.lineWidth = 2; - ctx.beginPath(); - ctx.roundRect(50, currentY, width - 100, 45, 6); - ctx.fill(); - ctx.stroke(); - - // Course name - ctx.font = '16px "VT323", monospace'; - ctx.textAlign = 'center'; - drawTextWithOutline(name, width / 2, currentY + 18, stravaOrange); - - // Grade info - ctx.font = '14px "VT323", monospace'; - drawTextWithOutline(grade, width / 2, currentY + 35, stravaOrangeLight); - - currentY += 55; - }); - - // Check if this is the highlights panel (has Best Performance or Room to Grow) - const isHighlightsPanel = title.includes('HIGHLIGHTS') || - Array.from(highlightCourses).some(c => - c.querySelector('.course-label')?.textContent?.includes('Best Performance') || - c.querySelector('.course-label')?.textContent?.includes('Room to Grow') - ); - - // Check if this is the semester panel (has Best Semester or Toughest Semester) - const isSemesterPanel = title.includes('SEMESTER') || - Array.from(highlightCourses).some(c => - c.querySelector('.course-label')?.textContent?.includes('Best Semester') || - c.querySelector('.course-label')?.textContent?.includes('Toughest Semester') - ); - - if (isHighlightsPanel) { - ctx.font = '18px "VT323", monospace'; - drawTextWithOutline('My lowest and highest grade', width / 2, currentY + 10, stravaOrangeLight); - } else if (isSemesterPanel) { - ctx.font = '18px "VT323", monospace'; - drawTextWithOutline('My lowest and highest semester', width / 2, currentY + 10, stravaOrangeLight); - } - } - - // Download - const link = document.createElement('a'); - link.download = `elbi-wrapped-${wrappedCurrentPanel + 1}.png`; - link.href = canvas.toDataURL('image/png'); - link.click(); -} - -// Initialize wrapped navigation on page load -document.addEventListener('DOMContentLoaded', () => { - initializeWrapped(); -}); \ No newline at end of file diff --git a/popup.zip b/popup.zip deleted file mode 100644 index 73d14705276ff69b642e7af639bf846025e71a2d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 65025 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 fpdHj!({==j+E00000NkvXXu0mjf&=k*= From 6b698b68d32f1da1c005ebc2783d6ff8240d7606 Mon Sep 17 00:00:00 2001 From: smmariquit Date: Mon, 6 Jul 2026 07:56:24 +0800 Subject: [PATCH 17/32] feat: add graduation scheduler engine Pure greedy topological scheduler: earliest feasible term per remaining course given prereqs, sem offerings (1st/2nd/midyear), and unit caps. Critical-path-first priority, notBefore delays, cycle/bad-data guards. Runs under node for the self-check in scheduler.test.js. Co-Authored-By: Claude Fable 5 --- extension/scheduler.test.js | 81 +++++++++++++++++ extension/src/scheduler.js | 172 ++++++++++++++++++++++++++++++++++++ 2 files changed, 253 insertions(+) create mode 100644 extension/scheduler.test.js create mode 100644 extension/src/scheduler.js diff --git a/extension/scheduler.test.js b/extension/scheduler.test.js new file mode 100644 index 0000000..72886cd --- /dev/null +++ b/extension/scheduler.test.js @@ -0,0 +1,81 @@ +// Self-check for scheduler.js. Run: node extension/scheduler.test.js +const assert = require('assert'); +const { scheduleEarliest, semAt, termLabel } = require('./src/scheduler.js'); + +const C = (code, units, sem, prereqs = []) => ({ code, title: code, units, sem, prereqs }); + +// Synthetic curriculum: A -> B -> C chain across alternating sems, D midyear-only. +const COURSES = [ + C('A', 3, '1'), + C('B', 3, '2', ['A']), + C('CC', 3, '1', ['B']), + C('D', 3, 'midyear'), + C('E', 3, '1'), +]; + +// helpers +assert.strictEqual(semAt(0, '1'), '1'); +assert.strictEqual(semAt(1, '1'), '2'); +assert.strictEqual(semAt(2, '1'), 'midyear'); +assert.strictEqual(semAt(3, '1'), '1'); +assert.strictEqual(termLabel(0, 2026, '1'), '2026 1st Sem'); +assert.strictEqual(termLabel(1, 2026, '1'), '2027 2nd Sem'); +assert.strictEqual(termLabel(2, 2026, '1'), '2027 Midyear'); +assert.strictEqual(termLabel(3, 2026, '1'), '2027 1st Sem'); + +// baseline: A,E @ t0; B @ t1; D @ t2 (midyear); CC @ t3 (next 1st sem) +let r = scheduleEarliest({ courses: COURSES, passed: new Set(), startSem: '1' }); +assert.strictEqual(r.assignedTerm['A'], 0); +assert.strictEqual(r.assignedTerm['E'], 0); +assert.strictEqual(r.assignedTerm['B'], 1); +assert.strictEqual(r.assignedTerm['D'], 2); +assert.strictEqual(r.assignedTerm['CC'], 3); +assert.strictEqual(r.gradTermIndex, 3); +assert.deepStrictEqual(r.unschedulable, []); + +// fail cascade: having passed A+B, failing B (removed from passed) pushes +// CC from t0 to t3 — a full offering cycle later. +const withB = scheduleEarliest({ courses: COURSES, passed: new Set(['A', 'B']), startSem: '1' }); +assert.strictEqual(withB.assignedTerm['CC'], 0); +const failB = scheduleEarliest({ courses: COURSES, passed: new Set(['A']), startSem: '1' }); +assert.strictEqual(failB.assignedTerm['B'], 1); +assert.strictEqual(failB.assignedTerm['CC'], 3); + +// unit cap: only 6 units fit in 1st sem, third course waits for next 1st sem +const capped = scheduleEarliest({ + courses: [C('X1', 3, '1'), C('X2', 3, '1'), C('X3', 3, '1')], + passed: new Set(), + unitCaps: { '1': 6, '2': 21, 'midyear': 6 }, + startSem: '1', +}); +const t0Count = ['X1', 'X2', 'X3'].filter(c => capped.assignedTerm[c] === 0).length; +assert.strictEqual(t0Count, 2); +assert.strictEqual(Math.max(...['X1', 'X2', 'X3'].map(c => capped.assignedTerm[c])), 3); + +// notBefore delay honored, and offering still respected (t3 is next 1st sem) +const delayed = scheduleEarliest({ courses: [C('A', 3, '1')], passed: new Set(), notBefore: { A: 1 }, startSem: '1' }); +assert.strictEqual(delayed.assignedTerm['A'], 3); + +// unknown prereq dropped with a warning, course still schedules +const unk = scheduleEarliest({ courses: [C('F', 3, '2', ['GHOST 1'])], passed: new Set(), startSem: '1' }); +assert.strictEqual(unk.assignedTerm['F'], 1); +assert.ok(unk.warnings.some(w => w.includes('GHOST 1'))); + +// prereq cycle terminates and reports both as unschedulable +const cyc = scheduleEarliest({ + courses: [C('P', 3, '1', ['Q']), C('Q', 3, '1', ['P'])], + passed: new Set(), + startSem: '1', +}); +assert.deepStrictEqual(cyc.unschedulable.sort(), ['P', 'Q']); + +// critical path first under tight caps: chain head beats leaf course +const crit = scheduleEarliest({ + courses: [C('H1', 3, '1'), C('H2', 3, '2', ['H1']), C('H3', 3, '1', ['H2']), C('LEAF', 3, '1')], + passed: new Set(), + unitCaps: { '1': 3, '2': 21, 'midyear': 6 }, + startSem: '1', +}); +assert.strictEqual(crit.assignedTerm['H1'], 0); // chain of 3 outranks LEAF + +console.log('scheduler.test.js: all assertions passed'); diff --git a/extension/src/scheduler.js b/extension/src/scheduler.js new file mode 100644 index 0000000..e515501 --- /dev/null +++ b/extension/src/scheduler.js @@ -0,0 +1,172 @@ +/** + * Elbi GradeSim - Graduation Scheduler + * Pure functions, no DOM or chrome.* — also runs under node for tests. + * + * Computes the earliest feasible term for every remaining course given + * prerequisites, semester offerings (1st/2nd/midyear), and per-term unit caps. + */ + +const SEM_CYCLE = ['1', '2', 'midyear']; +const DEFAULT_UNIT_CAPS = { '1': 21, '2': 21, 'midyear': 6 }; + +function normCode(code) { + return (code || '').toUpperCase().trim(); +} + +// Which semester falls at termIndex terms after startSem. +function semAt(termIndex, startSem) { + const start = SEM_CYCLE.indexOf(startSem); + return SEM_CYCLE[(start + termIndex) % SEM_CYCLE.length]; +} + +// ponytail: curriculum data has one `sem` per course (its slot in the model +// curriculum), used here as "the sem it is offered". Missing sem = offered +// both regular sems. Upgrade path: real per-course offering lists. +function offeredIn(course, sem) { + if (!course.sem) return sem !== 'midyear'; + return course.sem === sem; +} + +// Human label for a term. Year advances when wrapping 1st sem -> 2nd sem +// (2nd sem and midyear run in the next calendar year). +function termLabel(termIndex, startYear, startSem) { + let year = startYear; + let sem = startSem; + for (let i = 0; i < termIndex; i++) { + if (sem === '1') { sem = '2'; year += 1; } + else if (sem === '2') { sem = 'midyear'; } + else { sem = '1'; } + } + const names = { '1': '1st Sem', '2': '2nd Sem', 'midyear': 'Midyear' }; + return `${year} ${names[sem]}`; +} + +// Courses the planner can schedule: real codes only, no electives/GE slots. +function plannableCourses(courses) { + return (courses || []).filter(c => + c.code && c.code !== 'Elective' && !(c.title || '').startsWith('(GE)') + ); +} + +/** + * Greedy topological scheduler. + * + * @param {Object} opts + * @param {Array} opts.courses curriculum majorCourses ({code,title,units,sem,prereqs}) + * @param {Set} opts.passed codes already passed (normalized or not) + * @param {Object} [opts.notBefore] {CODE: minTermIndex} — delay mechanism + * @param {Object} [opts.unitCaps] per-sem unit caps + * @param {string} [opts.startSem] sem of termIndex 0 ('1'|'2'|'midyear') + * @param {number} [opts.maxTerms] + * @returns {{plan: Array, gradTermIndex: number, assignedTerm: Object, + * unschedulable: Array, warnings: Array}} + */ +function scheduleEarliest(opts) { + const courses = plannableCourses(opts.courses); + const passed = new Set(Array.from(opts.passed || []).map(normCode)); + const notBefore = opts.notBefore || {}; + const unitCaps = opts.unitCaps || DEFAULT_UNIT_CAPS; + const startSem = opts.startSem || '1'; + const maxTerms = opts.maxTerms || 30; + const warnings = []; + + const byCode = new Map(); + courses.forEach(c => byCode.set(normCode(c.code), c)); + + // Prereqs restricted to known courses; unknown ones can't gate anything. + const prereqsOf = new Map(); + const dependentsOf = new Map(); + byCode.forEach((course, code) => { + const known = []; + (course.prereqs || []).forEach(pr => { + const prCode = normCode(pr); + if (byCode.has(prCode) || passed.has(prCode)) { + known.push(prCode); + if (byCode.has(prCode)) { + if (!dependentsOf.has(prCode)) dependentsOf.set(prCode, []); + dependentsOf.get(prCode).push(code); + } + } else { + warnings.push(`${code}: unknown prerequisite "${pr}" ignored`); + } + }); + prereqsOf.set(code, known); + }); + + // Critical-path priority: longest chain of dependents, memoized DFS. + const chainMemo = new Map(); + function chainLength(code, visiting) { + if (chainMemo.has(code)) return chainMemo.get(code); + if (visiting.has(code)) return 0; // cycle — reported via unschedulable later + visiting.add(code); + let best = 0; + (dependentsOf.get(code) || []).forEach(dep => { + best = Math.max(best, chainLength(dep, visiting)); + }); + visiting.delete(code); + chainMemo.set(code, best + 1); + return best + 1; + } + + const remaining = new Set(); + byCode.forEach((c, code) => { if (!passed.has(code)) remaining.add(code); }); + + const assignedTerm = {}; + const plan = []; + let emptyStreak = 0; + + for (let t = 0; t < maxTerms && remaining.size > 0; t++) { + const sem = semAt(t, startSem); + const cap = unitCaps[sem] != null ? unitCaps[sem] : DEFAULT_UNIT_CAPS[sem]; + + const candidates = Array.from(remaining).filter(code => { + const course = byCode.get(code); + if (!offeredIn(course, sem)) return false; + if ((notBefore[code] || 0) > t) return false; + return prereqsOf.get(code).every(pr => + passed.has(pr) || (assignedTerm[pr] !== undefined && assignedTerm[pr] < t) + ); + }); + + candidates.sort((a, b) => { + const diff = chainLength(b, new Set()) - chainLength(a, new Set()); + if (diff !== 0) return diff; + return (byCode.get(b).units || 0) - (byCode.get(a).units || 0); + }); + + const placed = []; + let units = 0; + candidates.forEach(code => { + const courseUnits = byCode.get(code).units || 3; + if (units + courseUnits > cap) return; + units += courseUnits; + placed.push(code); + assignedTerm[code] = t; + remaining.delete(code); + }); + + if (placed.length > 0) { + plan.push({ termIndex: t, sem, courses: placed, units }); + emptyStreak = 0; + } else { + // A full offering cycle with no progress and nothing merely waiting on + // notBefore means the rest can never be placed (cycles, bad data, + // course bigger than its sem's cap). + const stillWaiting = Array.from(remaining).some(code => (notBefore[code] || 0) > t); + emptyStreak = stillWaiting ? 0 : emptyStreak + 1; + if (emptyStreak >= SEM_CYCLE.length) break; + } + } + + const unschedulable = Array.from(remaining); + if (unschedulable.length > 0) { + warnings.push(`could not place: ${unschedulable.join(', ')}`); + } + + const gradTermIndex = plan.length > 0 ? plan[plan.length - 1].termIndex : -1; + return { plan, gradTermIndex, assignedTerm, unschedulable, warnings }; +} + +if (typeof module !== 'undefined') { + module.exports = { scheduleEarliest, semAt, offeredIn, termLabel, plannableCourses, normCode, DEFAULT_UNIT_CAPS }; +} From 48d0a07b6eb7001efd5a655bb52ced5a2d94756b Mon Sep 17 00:00:00 2001 From: smmariquit Date: Mon, 6 Jul 2026 07:56:24 +0800 Subject: [PATCH 18/32] feat: port course planner from main into extension/src planner.html/js/css were stranded at repo root on main during the reorg and never shipped. Re-wire the popup Planner tab and add the files (plus scheduler.js) to the build copy list. Co-Authored-By: Claude Fable 5 --- extension/build.sh | 4 +- extension/src/planner.css | 160 +++++++++++++++++++ extension/src/planner.html | 36 +++++ extension/src/planner.js | 309 +++++++++++++++++++++++++++++++++++++ extension/src/popup.html | 12 ++ extension/src/popup.js | 8 + 6 files changed, 528 insertions(+), 1 deletion(-) create mode 100644 extension/src/planner.css create mode 100644 extension/src/planner.html create mode 100644 extension/src/planner.js diff --git a/extension/build.sh b/extension/build.sh index cf596df..2efaa3f 100755 --- a/extension/build.sh +++ b/extension/build.sh @@ -26,7 +26,9 @@ build_target() { # Copy shared source cp "$SRC_DIR"/popup.js "$SRC_DIR"/popup.css "$SRC_DIR"/popup.html \ "$SRC_DIR"/content.js "$SRC_DIR"/background.js \ - "$SRC_DIR"/curriculum.js "$out/" + "$SRC_DIR"/curriculum.js \ + "$SRC_DIR"/planner.html "$SRC_DIR"/planner.js "$SRC_DIR"/planner.css \ + "$SRC_DIR"/scheduler.js "$out/" [ -f "$SRC_DIR/sims.png" ] && cp "$SRC_DIR/sims.png" "$out/" diff --git a/extension/src/planner.css b/extension/src/planner.css new file mode 100644 index 0000000..c4fb4a6 --- /dev/null +++ b/extension/src/planner.css @@ -0,0 +1,160 @@ +/* planner.css */ + +/* Full screen planner overrides */ +body.planner-page { + width: 100vw; + height: 100vh; + max-width: none; + max-height: none; + overflow: hidden; + display: flex; + flex-direction: column; +} + +.planner-full-container { + display: flex; + flex-direction: column; + height: 100%; + padding: 20px; +} + +.planner-scroll-wrapper { + flex: 1; + overflow: auto; + border: 2px solid var(--card-border); + background: var(--bg-dark); + position: relative; + scrollbar-width: thin; + scrollbar-color: var(--gem-dark) var(--bg-dark); +} + +.planner-arrows { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + pointer-events: none; + z-index: 1; +} + +.planner-grid { + display: flex; + gap: 32px; + padding: 32px; + position: relative; + z-index: 2; + min-width: max-content; +} + +.planner-column { + display: flex; + flex-direction: column; + gap: 20px; + width: 180px; + min-height: 300px; + padding: 8px; + border-radius: 8px; + transition: background 0.2s; +} + +.planner-column.drag-over { + background: rgba(255, 255, 255, 0.05); +} + +.planner-col-header { + text-align: center; + font-weight: bold; + color: var(--gem-mid); + font-size: 1rem; + padding-bottom: 8px; + border-bottom: 2px dashed var(--gem-dark); + margin-bottom: 8px; +} + +.planner-node { + background: var(--card-bg); + border: 2px solid var(--card-border); + padding: 12px; + border-radius: 4px; + font-size: 0.9rem; + text-align: center; + position: relative; + box-shadow: 2px 2px 0 var(--card-border); + transition: all 0.2s; +} + +.planner-node:hover { + transform: translateY(-2px); + box-shadow: 2px 4px 0 var(--gem-mid); + border-color: var(--gem-mid); + z-index: 3; +} + +.planner-node.completed { + border-color: var(--gem-mid); + background: rgba(0, 100, 0, 0.2); + color: var(--gem-light); + box-shadow: 2px 2px 0 var(--gem-dark); +} + +.planner-node.locked { + opacity: 0.5; + background: var(--bg-mid); +} + +.planner-node.available { + border-color: var(--info); + box-shadow: 2px 2px 0 rgba(115, 156, 194, 0.5); +} + +.planner-node-code { + font-weight: bold; + margin-bottom: 6px; + display: block; +} + +.planner-node-title { + font-size: 0.8rem; + color: var(--text-secondary); + display: block; + /* Allow text wrapping in full screen */ + white-space: normal; +} + +.planner-completed-legend { + display: inline-block; + width: 12px; + height: 12px; + background: rgba(0, 100, 0, 0.2); + border: 1px solid var(--gem-mid); + margin-left: 10px; + margin-right: 4px; +} + +.planner-locked-legend { + display: inline-block; + width: 12px; + height: 12px; + background: var(--bg-mid); + border: 1px solid var(--card-border); + opacity: 0.5; + margin-left: 10px; + margin-right: 4px; +.planner-node { + cursor: grab; +} + +.planner-node:active { + cursor: grabbing; +} + +.planner-node.dragging { + opacity: 0.4; + box-shadow: 0 0 12px var(--gem-mid) !important; +} + +.planner-node.invalid-timing { + border-color: #ff4444 !important; + box-shadow: 0 0 8px rgba(255, 68, 68, 0.8) !important; +} diff --git a/extension/src/planner.html b/extension/src/planner.html new file mode 100644 index 0000000..93dca1f --- /dev/null +++ b/extension/src/planner.html @@ -0,0 +1,36 @@ + + + + + + Course Planner - Elbi GradeSim + + + + +
+
+

Elbi GradeSim Course Planner

+
+
+

+ Arrows indicate prerequisites. + Passed + Locked + Failed +

+

+ Pro Tip: 🖱️ Drag and drop courses to plan your schedule. Right-click any course to toggle its status (Passed -> Failed -> Planned). Courses scheduled before their prerequisites will glow red. +

+
+
+ +
+ +
+
+
+ + + + diff --git a/extension/src/planner.js b/extension/src/planner.js new file mode 100644 index 0000000..dc8a32e --- /dev/null +++ b/extension/src/planner.js @@ -0,0 +1,309 @@ +// planner.js + +function sanitizeText(text) { + if (typeof text !== 'string') return ''; + const div = document.createElement('div'); + div.textContent = text; + return div.innerHTML; +} + +let globalData = {}; +let curriculum = null; + +document.addEventListener('DOMContentLoaded', async () => { + globalData = await chrome.storage.local.get(['gradesData', 'selectedProgram', 'substitutions', 'plannedSchedule', 'customCourseStatus']); + const program = globalData.selectedProgram || 'BSCS'; + curriculum = typeof UPLB_PROGRAMS !== 'undefined' ? UPLB_PROGRAMS[program] : null; + + if (!globalData.plannedSchedule) globalData.plannedSchedule = {}; + if (!globalData.customCourseStatus) globalData.customCourseStatus = {}; + if (!globalData.substitutions) globalData.substitutions = {}; + + if (!curriculum || !curriculum.majorCourses) { + document.getElementById('plannerGrid').innerHTML = '

Curriculum data not available.

'; + return; + } + + initDefaultSchedule(); + renderPlannerFullscreen(); + + window.addEventListener('resize', () => { + drawArrows(curriculum.majorCourses, window.nodeEls, document.getElementById('plannerArrows'), document.getElementById('plannerGrid')); + }); +}); + +function initDefaultSchedule() { + curriculum.majorCourses.forEach(course => { + if (!course.code || course.code === "Elective" || course.title.startsWith("(GE)")) return; + const code = course.code.toUpperCase().trim(); + if (!globalData.plannedSchedule[code]) { + const year = course.year || 1; + let sem = course.sem || "1"; + if (sem === "midyear") sem = "3"; + globalData.plannedSchedule[code] = `Y${year}-${sem}`; + } + }); +} + +async function savePlannerData() { + await chrome.storage.local.set({ + plannedSchedule: globalData.plannedSchedule, + customCourseStatus: globalData.customCourseStatus + }); +} + +function getTermTime(termKey) { + if (termKey === 'Unplanned') return 999; + const match = termKey.match(/Y(\d+)-(\d+)/); + if (match) { + return parseInt(match[1]) * 3 + parseInt(match[2]); + } + return 999; +} + +function renderPlannerFullscreen() { + const gridEl = document.getElementById('plannerGrid'); + const svgEl = document.getElementById('plannerArrows'); + gridEl.innerHTML = ''; + svgEl.innerHTML = ''; + + // Create columns + const colKeys = ['Unplanned']; + for (let y = 1; y <= 6; y++) { + colKeys.push(`Y${y}-1`); + colKeys.push(`Y${y}-2`); + colKeys.push(`Y${y}-3`); + } + + const columns = {}; + colKeys.forEach(k => columns[k] = []); + + const nodeEls = {}; + window.nodeEls = nodeEls; + + // Base AMIS passed/failed + const amisPassed = new Set(); + const amisFailed = new Set(); + + if (globalData.gradesData && globalData.gradesData.student_grades) { + for (const [termId, termData] of Object.entries(globalData.gradesData.student_grades)) { + if (!termData || !termData.values) continue; + for (const courseData of termData.values) { + const gradeStr = (courseData.grade || '').toString().toUpperCase().trim(); + const code = (courseData.course?.course_code || '').toUpperCase().trim(); + const grade = parseFloat(gradeStr); + if (gradeStr === 'S' || gradeStr === 'P' || (grade >= 1.0 && grade <= 3.0)) { + amisPassed.add(code); + } else if (grade === 5.0) { + amisFailed.add(code); + } + } + } + // Add substitutions + for (const [reqCode, takenCode] of Object.entries(globalData.substitutions)) { + if (amisPassed.has(takenCode.toUpperCase().trim())) { + amisPassed.add(reqCode.toUpperCase().trim()); + } + } + } + + curriculum.majorCourses.forEach(course => { + if (!course.code || course.code === "Elective" || course.title.startsWith("(GE)")) return; + const code = course.code.toUpperCase().trim(); + let termKey = globalData.plannedSchedule[code] || 'Unplanned'; + if (!columns[termKey]) termKey = 'Unplanned'; + columns[termKey].push(course); + }); + + colKeys.forEach(key => { + // Hide empty Y5 and Y6 columns to save space, but show if they have courses + if (key.match(/Y[56]/) && columns[key].length === 0) return; + // Hide empty midyears + if (key.endsWith('-3') && columns[key].length === 0) return; + + const colEl = document.createElement('div'); + colEl.className = 'planner-column'; + colEl.dataset.term = key; + + let colName = key; + if (key.startsWith('Y')) { + const [y, s] = key.split('-'); + colName = `Y${y} ${s === '3' ? 'Mid' : 'S' + s}`; + } + colEl.innerHTML = `
${colName}
`; + + // Drag & Drop + colEl.addEventListener('dragover', e => { + e.preventDefault(); + colEl.classList.add('drag-over'); + }); + colEl.addEventListener('dragleave', e => { + colEl.classList.remove('drag-over'); + }); + colEl.addEventListener('drop', e => { + e.preventDefault(); + colEl.classList.remove('drag-over'); + const draggedCode = e.dataTransfer.getData('text/plain'); + if (draggedCode && globalData.plannedSchedule[draggedCode] !== key) { + globalData.plannedSchedule[draggedCode] = key; + savePlannerData(); + renderPlannerFullscreen(); + } + }); + + columns[key].forEach(course => { + const code = course.code.toUpperCase().trim(); + + let status = 'planned'; + if (globalData.customCourseStatus[code]) { + status = globalData.customCourseStatus[code]; + } else if (amisPassed.has(code)) { + status = 'passed'; + } else if (amisFailed.has(code)) { + status = 'failed'; + } + + let prereqsMet = true; + let timingValid = true; + + if (status !== 'passed') { + const prereqs = course.prereqs || []; + prereqs.forEach(pr => { + const prCode = pr.toUpperCase().trim(); + const prStatus = globalData.customCourseStatus[prCode] || (amisPassed.has(prCode) ? 'passed' : 'planned'); + if (prStatus !== 'passed') prereqsMet = false; + + if (prStatus !== 'passed') { + const thisTime = getTermTime(key); + const prTime = getTermTime(globalData.plannedSchedule[prCode] || 'Unplanned'); + if (prTime >= thisTime && thisTime !== 999) { + timingValid = false; + } + } + }); + } + + let nodeClass = status === 'passed' ? 'completed' : (status === 'failed' ? 'failed' : (prereqsMet ? 'available' : 'locked')); + if (!timingValid) nodeClass += ' invalid-timing'; + + const nodeEl = document.createElement('div'); + nodeEl.className = `planner-node ${nodeClass}`; + nodeEl.id = `planner-node-${code.replace(/[^A-Z0-9]/g, '-')}`; + nodeEl.draggable = true; + + nodeEl.innerHTML = ` + ${sanitizeText(course.code)} + ${sanitizeText(course.title)} + `; + + nodeEl.addEventListener('dragstart', e => { + e.dataTransfer.setData('text/plain', code); + setTimeout(() => nodeEl.classList.add('dragging'), 0); + }); + nodeEl.addEventListener('dragend', () => { + nodeEl.classList.remove('dragging'); + }); + + nodeEl.addEventListener('contextmenu', e => { + e.preventDefault(); + const current = globalData.customCourseStatus[code] || (amisPassed.has(code) ? 'passed' : (amisFailed.has(code) ? 'failed' : 'planned')); + let next = 'planned'; + if (current === 'planned' || current === 'locked' || current === 'available') next = 'passed'; + else if (current === 'passed') next = 'failed'; + else if (current === 'failed') next = 'planned'; + + globalData.customCourseStatus[code] = next; + savePlannerData(); + renderPlannerFullscreen(); + }); + + colEl.appendChild(nodeEl); + nodeEls[code] = nodeEl; + }); + + gridEl.appendChild(colEl); + }); + + setTimeout(() => drawArrows(curriculum.majorCourses, nodeEls, svgEl, gridEl), 100); +} + +function drawArrows(courses, nodeEls, svgEl, gridEl) { + svgEl.innerHTML = ''; + + const defs = document.createElementNS("http://www.w3.org/2000/svg", "defs"); + const marker = document.createElementNS("http://www.w3.org/2000/svg", "marker"); + marker.setAttribute("id", "arrowhead"); + marker.setAttribute("markerWidth", "10"); + marker.setAttribute("markerHeight", "7"); + marker.setAttribute("refX", "9"); + marker.setAttribute("refY", "3.5"); + marker.setAttribute("orient", "auto"); + + const polygon = document.createElementNS("http://www.w3.org/2000/svg", "polygon"); + polygon.setAttribute("points", "0 0, 10 3.5, 0 7"); + polygon.setAttribute("fill", "#5c8f5c"); + + marker.appendChild(polygon); + defs.appendChild(marker); + svgEl.appendChild(defs); + + const gridRect = gridEl.getBoundingClientRect(); + svgEl.style.width = gridEl.scrollWidth + 'px'; + svgEl.style.height = gridEl.scrollHeight + 'px'; + + courses.forEach(course => { + const targetCode = course.code.toUpperCase().trim(); + const targetNode = nodeEls[targetCode]; + if (!targetNode) return; + + const prereqs = course.prereqs || []; + prereqs.forEach(prereq => { + const sourceCode = prereq.toUpperCase().trim(); + const sourceNode = nodeEls[sourceCode]; + + if (sourceNode) { + const sourceRect = sourceNode.getBoundingClientRect(); + const targetRect = targetNode.getBoundingClientRect(); + + let startX, startY, endX, endY, pathD; + + if (sourceRect.right <= targetRect.left) { + // Forward arrow + startX = sourceRect.right - gridRect.left; + startY = sourceRect.top + (sourceRect.height / 2) - gridRect.top; + endX = targetRect.left - gridRect.left; + endY = targetRect.top + (targetRect.height / 2) - gridRect.top; + + const distanceX = endX - startX; + let drop = 0; + if (distanceX > 250) { + const skipCount = Math.floor(distanceX / 200); + drop = skipCount * 40; + } + + const curveOffset = Math.max(30, distanceX / 2); + pathD = `M ${startX} ${startY} C ${startX + curveOffset} ${startY + drop}, ${endX - curveOffset} ${endY + drop}, ${endX} ${endY}`; + } else { + // Backward or same column arrow + startX = sourceRect.left - gridRect.left; + startY = sourceRect.top + (sourceRect.height / 2) - gridRect.top; + endX = targetRect.right - gridRect.left; + endY = targetRect.top + (targetRect.height / 2) - gridRect.top; + + const curveOffset = Math.max(40, (startX - endX) / 2 + 20); + pathD = `M ${startX} ${startY} C ${startX - curveOffset} ${startY}, ${endX + curveOffset} ${endY}, ${endX} ${endY}`; + } + + const path = document.createElementNS("http://www.w3.org/2000/svg", "path"); + path.setAttribute("d", pathD); + path.setAttribute("fill", "none"); + path.setAttribute("stroke", "#5c8f5c"); + path.setAttribute("stroke-width", "2"); + path.setAttribute("marker-end", "url(#arrowhead)"); + path.setAttribute("opacity", "0.6"); + + svgEl.appendChild(path); + } + }); + }); +} diff --git a/extension/src/popup.html b/extension/src/popup.html index 73ff921..c41f2ea 100644 --- a/extension/src/popup.html +++ b/extension/src/popup.html @@ -110,6 +110,7 @@

My Elbi GWA

+
@@ -189,6 +190,17 @@

Remaining Courses

+ + +