diff --git a/.github/workflows/pr-checks.yml b/.github/workflows/pr-checks.yml index dd5edcd..a2c264e 100644 --- a/.github/workflows/pr-checks.yml +++ b/.github/workflows/pr-checks.yml @@ -24,3 +24,9 @@ jobs: exit 1 fi + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Extension tests + run: for t in extension/*.test.js; do node "$t"; done diff --git a/.github/workflows/publish-extension.yml b/.github/workflows/publish-extension.yml index 2cbcbdb..d47b6a5 100644 --- a/.github/workflows/publish-extension.yml +++ b/.github/workflows/publish-extension.yml @@ -8,7 +8,9 @@ # manifests only; the tag records what shipped. # # A store whose secrets are missing is skipped, not failed, so stores can be -# connected one at a time. +# connected one at a time. A store that rejects an upload (for example Chrome +# while the previous version is still in review) does not stop the other +# stores or the release; the run is marked failed at the end so it shows up. # # Secrets (repo settings, Actions): # CWS_CLIENT_ID, CWS_CLIENT_SECRET, CWS_REFRESH_TOKEN Chrome Web Store API @@ -63,6 +65,9 @@ jobs: echo "version=$version" >> "$GITHUB_OUTPUT" echo "new=true" >> "$GITHUB_OUTPUT" + - name: Test + run: for t in extension/*.test.js; do node "$t"; done + - name: Build and zip if: steps.version.outputs.new == 'true' run: | @@ -77,6 +82,8 @@ jobs: ls -l release - name: Chrome Web Store + id: cws + continue-on-error: true if: steps.version.outputs.new == 'true' && env.HAS_CWS == 'true' env: CLIENT_ID: ${{ secrets.CWS_CLIENT_ID }} @@ -86,6 +93,8 @@ jobs: run: npx -y chrome-webstore-upload-cli@3 --source "release/elbi-gradesim-chrome-${{ steps.version.outputs.version }}.zip" - name: Firefox Add-ons + id: amo + continue-on-error: true if: steps.version.outputs.new == 'true' && env.HAS_AMO == 'true' env: WEB_EXT_API_KEY: ${{ secrets.AMO_JWT_ISSUER }} @@ -93,6 +102,8 @@ jobs: run: npx -y web-ext@8 sign --channel listed --source-dir extension/dist/firefox --artifacts-dir release/amo - name: Edge Add-ons + id: edge + continue-on-error: true if: steps.version.outputs.new == 'true' && env.HAS_EDGE == 'true' env: EDGE_PRODUCT_ID: ${{ secrets.EDGE_PRODUCT_ID }} @@ -120,3 +131,9 @@ jobs: run: | tag="extension-v${{ steps.version.outputs.version }}" gh release create "$tag" release/*.zip --target "$GITHUB_SHA" --title "Elbi GradeSim ${{ steps.version.outputs.version }}" --generate-notes + + - name: Fail if a store rejected the upload + if: steps.cws.outcome == 'failure' || steps.amo.outcome == 'failure' || steps.edge.outcome == 'failure' + run: | + echo "::error::Store results: Chrome=${{ steps.cws.outcome }} Firefox=${{ steps.amo.outcome }} Edge=${{ steps.edge.outcome }}. Re-run this workflow once the store accepts uploads again." + exit 1 diff --git a/AGENTS.md b/AGENTS.md index 5e7a837..e3a877c 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -2,69 +2,58 @@ **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**. +Org-wide agent defaults are in [room-tba/AGENTS.md](https://github.com/uplbtools/room-tba/blob/main/AGENTS.md). This file tailors them to the GradeSim browser extension. ## Doc map | When | Read | | --- | --- | +| How grades get in, storage keys, permissions | [extension/README.md](extension/README.md) | | 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 | +| Web app | [uplbtools/gradesim-website](https://github.com/uplbtools/gradesim-website), live at | +| Store listings | 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 +- Manifest V3 browser extension in plain JavaScript, no bundler and no root `package.json` +- Targets are Chrome, Firefox and Opera manifests. Edge and Brave install the Chrome build. +- `extension/build.sh [chrome|firefox|opera|edge|all]` writes `extension/dist//`. `edge` is an alias for `chrome`. +- Grades come from the AMIS JSON API, not page scraping. `content.js` calls `api-amis.uplb.edu.ph/api/students/grades` with the token AMIS keeps in `localStorage`, then stores only the course fields. +- No server. Everything stays in `chrome.storage.local`. ## Branches and release | Branch | Role | | --- | --- | -| **`staging`** | Default branch: integration | -| **`main`** | Release line for store submissions | +| `staging` | Default branch. All pull requests go here. | +| `main` | Release line. A push that touches `extension/` publishes to the Chrome, Firefox and Edge stores through `.github/workflows/publish-extension.yml`. | -**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. +Never push or open pull requests to `main` directly. Store review is human-gated, so do not close issues that wait on store approval. ## 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 -``` +| `for t in extension/*.test.js; do node "$t"; done` | Every change (CI runs it too) | +| `bash extension/build.sh all` | Every extension change | +| `npx -y web-ext@8 lint -s extension/dist/firefox` | Must show 0 errors and 0 warnings | +| Manual AMIS check | Load the unpacked build, log in to AMIS, press Refresh in the popup, confirm grades and GWA | +| Manifest diff | When touching permissions, update every file in `extension/manifests/` | ## 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 +- Never send grades or student data to a server. +- Keep the stored grades shape (`gradesData.student_grades`) compatible with the web app importer in gradesim-website `src/lib/importers.ts`. +- Firefox add-on review rejects `innerHTML`, `outerHTML` and `insertAdjacentHTML` with dynamic strings. Build markup with `DOMParser` or DOM calls. +- No decorative motion in the 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 +- Conventional Commits, for example `fix(content): ...`, `feat(planner): ...`, `chore(build): ...` +- Note which browsers you tested in the PR body. ## Security -- Do not add broad `` permissions without maintainer approval -- Never exfiltrate AMIS page content to third-party analytics +- Do not add `` or other broad permissions without maintainer approval. +- Never send AMIS content to analytics or third parties. diff --git a/CHANGELOG.md b/CHANGELOG.md index 0155ddd..799f64b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,55 @@ # Changelog +## 2.1.0 (2026-10-06) + +### Features +- The web app at gradesim.uplb.tools can import your grades straight from the extension when you press Import there. +- The planner follows your SP or thesis track. +- First run asks for your program, with a one line terms notice instead of a terms wall. Programs without a checklist yet show as coming soon. +- The What if tab leads with the average you need. When a target is out of reach it shows the best GWA still possible, the best honor still in reach, and a link that opens the planner on the failed course. +- The popup shows when grades were last updated, has Refresh in the main view, and says what to do when AMIS is logged out or does not answer. +- A rough guide notice in the popup and planner for programs with thin prerequisite data. + +### Fixes +- A 4.00 or 5.00 stays in the GWA but no longer counts as a completed course. PEd majors' courses are no longer dropped as PE. No honors show before any graded unit, and scholar badges need 15 units with no 5.00, 4.00 or INC. +- The popup fills the screen on Firefox for Android. +- Grades load without reloading AMIS. The extension calls the AMIS grades API once per hour with your login, and Refresh in the popup fetches right away from the open AMIS tab. +- Only the course fields GradeSim uses are saved, not the whole AMIS response. +- Export JSON saves every setting, including planner pins, petitions and theme, with a schema version. Import no longer accepts the terms for you. +- New Clear my data button in Help. +- What if and the planner count remaining units the same way. HK and NSTP complete, GE courses count by code, programs without tracks get no made up free electives, and an average above 3.00 means any passing grade is enough. +- The planner plans 18 units a sem unless you choose 21, free elective cards are never critical, Reset plan asks in a dialog, and a button shows when terms run past the right edge. +- AMIS term ids past 2029 parse. +- A one line unofficial estimate disclaimer sits under the GWA and on the planner. +- Sharper toolbar icons at 16, 32 and 48 px, and one store name, Elbi GradeSim, everywhere. + +## [2.0.4] - 2026-10-06 + +- Same code as 2.0.3, published again. + +## [2.0.3] - 2026-10-05 + +### Fixes +- Planner legend chips are added without `insertAdjacentHTML`, which the Firefox add-on review flags. + +## [2.0.2] - 2026-10-05 + +### Fixes +- Planner markup is built with `DOMParser` instead of `innerHTML`. + +## [2.0.1] - 2026-10-05 + +### Fixes +- Planner icon placeholders are swapped without assigning `outerHTML`. + +## [2.0.0] - 2026-10-05 + +### Features +- Course planner. A curriculum map of your program with prerequisites from the AMIS catalog, the critical path, retakes, petitions and what-if plans, laid out term by term. +- UPLB Tools look. New colors, self-hosted fonts, light and dark themes, and a flat plumbob icon. +- Links point to the new home at gradesim.uplb.tools and its hosted terms and privacy pages. +- Store builds for Chrome, Firefox and Edge are published from CI. + ## [1.2.1] - 2026-05-29 ### Features diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index d694729..ad97e77 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -8,28 +8,38 @@ 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 +- Expected and actual grade or GWA output -You do not need a pull request for typo reports in curriculum data: an issue is enough. +A typo in curriculum data only needs an issue, not a pull request. ## Developers -### Setup +### Build ```sh -cd extension -./build.sh chrome # or firefox, all +bash extension/build.sh all # chrome, firefox and opera +bash extension/build.sh chrome # one target ``` -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/). +Edge, Brave and Opera install the Chrome build, so `build.sh edge` is an alias for `chrome`. Load `extension/dist//` as an unpacked extension in developer mode. -### Pull requests +### Test -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. +```sh +for t in extension/*.test.js; do node "$t"; done +npx -y web-ext@8 lint -s extension/dist/firefox +``` + +CI runs the tests on every pull request. The Firefox lint should report 0 errors and 0 warnings, since addons.mozilla.org rejects or delays builds that warn. + +### Branches and pull requests + +1. Branch from `staging`. +2. Keep commits focused and use [Conventional Commits](https://www.conventionalcommits.org/), for example `fix(extension): ...` or `docs: ...`. +3. Open the pull request against `staging`. Its body must close an issue (`Closes #123`). +4. Maintainers merge `staging` into `main` to release. Every push to `main` that touches `extension/` publishes to the Chrome, Firefox and Edge stores, so never open a pull request straight to `main`. +5. Do not commit secrets or store 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. +See [CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md). Be helpful to students, and check grade logic against official UPLB rules when you can. diff --git a/PRIVACY_POLICY.md b/PRIVACY_POLICY.md index d3c1cec..1b3b614 100644 --- a/PRIVACY_POLICY.md +++ b/PRIVACY_POLICY.md @@ -1,76 +1,13 @@ # Privacy Policy for Elbi GradeSim -**Last Updated: January 8, 2026** +The full, current policy is at ****. -## Overview +In short: -Elbi GradeSim is a browser extension that helps UPLB students calculate their GWA (General Weighted Average) and simulate grades needed for Latin honors. This privacy policy explains how we handle your data. +- Elbi GradeSim reads your grades from the UPLB AMIS grades API using your own logged-in session. It never sees or stores your password. +- It keeps only the course fields it needs, in your browser's extension storage on your device. +- It sends nothing to any server. There are no analytics, ads or tracking. +- It shares your grades with the Elbi GradeSim web app at gradesim.uplb.tools only when you press Import there, and that hand-off stays inside your browser. +- Clear my data in the popup's Help, or removing the extension, deletes everything it stored. -## Data Collection - -### What We Access -- **Grades Data**: The extension accesses your grades information from the UPLB AMIS (Academic Management Information System) portal when you are logged in. - -### What We DO NOT Collect -- We do NOT collect any personal identification information -- We do NOT collect your AMIS login credentials -- We do NOT track your browsing activity -- We do NOT use analytics or tracking services -- We do NOT share any data with third parties - -## Data Storage - -- **Local Storage Only**: All your grades data is stored locally on your device using Chrome's built-in storage (`chrome.storage.local`) -- **No External Servers**: Your data is NEVER sent to any external servers -- **No Cloud Sync**: Your data is not synced across devices - -## Data Usage - -Your grades data is used exclusively for: -1. Calculating your current GWA -2. Displaying your grades history -3. Simulating grades needed for target Latin honors -4. Tracking remaining courses in your curriculum - -## Permissions Explained - -| Permission | Why We Need It | -|------------|----------------| -| `storage` | To save your grades data locally on your device | -| `host_permissions` for amis.uplb.edu.ph | To access the AMIS portal and read your grades | - -## Data Deletion - -You can delete all stored data at any time by: -1. Right-clicking the extension icon -2. Selecting "Remove from Chrome" -3. Or going to Chrome Settings > Extensions > Elbi GradeSim > Remove - -Alternatively, clear the extension's data via: -Chrome Settings > Privacy and Security > Clear Browsing Data > Cookies and other site data - -## Security - -- The extension only works on the official UPLB AMIS website (amis.uplb.edu.ph) -- We use your existing AMIS session - we never ask for or store your password -- All data processing happens locally in your browser - -## Children's Privacy - -This extension is intended for university students and does not knowingly collect information from children under 13. - -## Changes to This Policy - -We may update this privacy policy from time to time. Any changes will be reflected in the "Last Updated" date. - -## Contact - -If you have questions about this privacy policy, please open an issue on our GitHub repository. - -## Open Source - -This extension is open source. You can review the complete source code to verify our privacy practices. - ---- - -**Summary**: Your data stays on YOUR device. We don't collect, store, or share anything. +Questions go to the [GitHub issues](https://github.com/uplbtools/gradesim/issues). diff --git a/README.md b/README.md index ae75de5..5333d58 100644 --- a/README.md +++ b/README.md @@ -1,65 +1,55 @@ # 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. +Elbi GradeSim is a browser extension for UPLB students. It reads your grades from AMIS, works out your GWA, shows what you need for Latin honors, and plans the courses you still have to take. -## Repository Structure +Not affiliated with UP. Unofficial estimate. INC/DRP may not reflect correctly. Always verify with the OUR. -``` -gradesim/ -├── extension/ # Browser extension source -│ ├── 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 -├── scratch/ # Curriculum data pipeline (PDF parsing, prereq graphs) -└── README.md -``` +## Get it -The landing page lives in a separate repo: [gradesim-website](https://github.com/smmariquit/gradesim-website). +- [Chrome Web Store](https://chromewebstore.google.com/detail/elbi-gradesim-uplb-gwa-ca/mlhklblbhkikcmobmmajckjcbmdinldb) (also works in Brave) +- [Firefox Add-ons](https://addons.mozilla.org/en-US/firefox/addon/elbi-gradesim/), desktop and Android +- [Microsoft Edge Add-ons](https://microsoftedge.microsoft.com/addons/detail/elbi-gradesim-uplb-gwa-/ebiakebpglddgmkdehdjiadnjgkmgnga) -## Extension +No extension? The web app at [gradesim.uplb.tools](https://gradesim.uplb.tools) does the same math with grades you import or type in. -### Build +## What it does -```bash -cd extension -./build.sh all # Build all browsers -./build.sh chrome # Build Chrome only -./build.sh firefox # Build Firefox only -``` +- Reads your grades from AMIS while you are logged in and computes your GWA +- Shows your Latin honors standing and the average you need on your remaining units +- Lists your grades by semester or year, with per-term scholar status +- Lets you leave courses out of the GWA and record substitutions +- Plans your remaining courses term by term on a curriculum map, using real prerequisites +- Covers 30+ UPLB degree programs +- Backs up and restores all your data as a JSON file, and clears it with one button -Output goes to `extension/dist//`. Load the folder in your browser's developer mode. +## Privacy -### Install from Store +Your grades stay in your browser. The extension calls the AMIS grades API with your own login, keeps only the course fields it needs in `chrome.storage.local`, and sends nothing to any server. There are no analytics. It hands your grades to the web app only when you press Import there. Full policy at [gradesim.uplb.tools/privacy](https://gradesim.uplb.tools/privacy/). -- [Chrome Web Store](https://chromewebstore.google.com/detail/elbi-gradesim-uplb-gwa-ca/mlhklblbhkikcmobmmajckjcbmdinldb) -- [Firefox Add-ons](https://addons.mozilla.org/en-US/firefox/addon/elbi-gradesim/) +## Build from source -### Features +```sh +bash extension/build.sh all # chrome, firefox and opera +bash extension/build.sh firefox # one target; edge is an alias for chrome +for t in extension/*.test.js; do node "$t"; done +``` -- Automatic GWA calculation from AMIS -- "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 +Load `extension/dist//` as an unpacked extension in developer mode. See [CONTRIBUTING.md](CONTRIBUTING.md) for the branch flow. -## Privacy +## Repository -All data stays on your device. No servers, no analytics, no tracking. See [PRIVACY_POLICY.md](extension/PRIVACY_POLICY.md). +```text +extension/ + src/ popup, planner, content and background scripts, curriculum data + manifests/ one manifest per browser + icons/ 16, 32, 48 and 128 px icons + build.sh copies src and the right manifest into dist// + *.test.js node self-checks +scratch/ curriculum pipeline (checklist PDFs, parsers, prerequisite graphs) +``` + +The web app lives in [uplbtools/gradesim-website](https://github.com/uplbtools/gradesim-website). ## License MIT - - ---- -*If this project helped you out, consider [treating me to a coffee](https://kape.stimmie.dev) ☕* - -## 📊 Current State of the Code -- **Tech Stack:** Static / Basic Scripts -- **Repository Size:** 131 tracked files -- **Latest Update:** `8c99fa0 chore: add stale issue and PR validators` - - ---- -*☕ If you found this project useful, you can support my work at [kape.stimmie.dev](https://kape.stimmie.dev)!* \ No newline at end of file diff --git a/extension/PRIVACY_POLICY.md b/extension/PRIVACY_POLICY.md index d3c1cec..1b3b614 100644 --- a/extension/PRIVACY_POLICY.md +++ b/extension/PRIVACY_POLICY.md @@ -1,76 +1,13 @@ # Privacy Policy for Elbi GradeSim -**Last Updated: January 8, 2026** +The full, current policy is at ****. -## Overview +In short: -Elbi GradeSim is a browser extension that helps UPLB students calculate their GWA (General Weighted Average) and simulate grades needed for Latin honors. This privacy policy explains how we handle your data. +- Elbi GradeSim reads your grades from the UPLB AMIS grades API using your own logged-in session. It never sees or stores your password. +- It keeps only the course fields it needs, in your browser's extension storage on your device. +- It sends nothing to any server. There are no analytics, ads or tracking. +- It shares your grades with the Elbi GradeSim web app at gradesim.uplb.tools only when you press Import there, and that hand-off stays inside your browser. +- Clear my data in the popup's Help, or removing the extension, deletes everything it stored. -## Data Collection - -### What We Access -- **Grades Data**: The extension accesses your grades information from the UPLB AMIS (Academic Management Information System) portal when you are logged in. - -### What We DO NOT Collect -- We do NOT collect any personal identification information -- We do NOT collect your AMIS login credentials -- We do NOT track your browsing activity -- We do NOT use analytics or tracking services -- We do NOT share any data with third parties - -## Data Storage - -- **Local Storage Only**: All your grades data is stored locally on your device using Chrome's built-in storage (`chrome.storage.local`) -- **No External Servers**: Your data is NEVER sent to any external servers -- **No Cloud Sync**: Your data is not synced across devices - -## Data Usage - -Your grades data is used exclusively for: -1. Calculating your current GWA -2. Displaying your grades history -3. Simulating grades needed for target Latin honors -4. Tracking remaining courses in your curriculum - -## Permissions Explained - -| Permission | Why We Need It | -|------------|----------------| -| `storage` | To save your grades data locally on your device | -| `host_permissions` for amis.uplb.edu.ph | To access the AMIS portal and read your grades | - -## Data Deletion - -You can delete all stored data at any time by: -1. Right-clicking the extension icon -2. Selecting "Remove from Chrome" -3. Or going to Chrome Settings > Extensions > Elbi GradeSim > Remove - -Alternatively, clear the extension's data via: -Chrome Settings > Privacy and Security > Clear Browsing Data > Cookies and other site data - -## Security - -- The extension only works on the official UPLB AMIS website (amis.uplb.edu.ph) -- We use your existing AMIS session - we never ask for or store your password -- All data processing happens locally in your browser - -## Children's Privacy - -This extension is intended for university students and does not knowingly collect information from children under 13. - -## Changes to This Policy - -We may update this privacy policy from time to time. Any changes will be reflected in the "Last Updated" date. - -## Contact - -If you have questions about this privacy policy, please open an issue on our GitHub repository. - -## Open Source - -This extension is open source. You can review the complete source code to verify our privacy practices. - ---- - -**Summary**: Your data stays on YOUR device. We don't collect, store, or share anything. +Questions go to the [GitHub issues](https://github.com/uplbtools/gradesim/issues). diff --git a/extension/README.md b/extension/README.md index e53d72f..a38fe5d 100644 --- a/extension/README.md +++ b/extension/README.md @@ -1,92 +1,42 @@ -# Elbi GradeSim - UPLB GWA Calculator +# Elbi GradeSim extension -A Chrome extension for UPLB students to calculate their GWA and simulate grades needed for Latin honors. +Source for the Elbi GradeSim browser extension. Start with the [main README](../README.md) for what it does and where to install it. ![Elbi GradeSim](icons/icon128.png) -## Features +## How grades get in -### 1. Automatic GWA Calculation -- Automatically captures your grades from AMIS when you visit the Personal Information page -- Calculates your current GWA (weighted average) -- Excludes NSTP, PE, and HK courses from calculation (per UP policy) -- Shows your current Latin honor track +`src/content.js` runs on `amis.uplb.edu.ph`. When the page loads it reads the AMIS login token from the page's `localStorage` and calls `https://api-amis.uplb.edu.ph/api/students/grades?summarize=true` once. It saves the result to `chrome.storage.local`: -### 2. Grades Overview -- View all your grades organized by semester -- See course codes, titles, units, and grades -- Color-coded grades for easy visualization +| Key | Meaning | +| --- | --- | +| `gradesData` | `{ student_grades: { : { term, values: [...] } } }` with only id, grade, unit_taken, section, status, course code and title, and grade term | +| `fetchedAt` | Time of the last good fetch, in ms | +| `lastError` | `'not-logged-in'`, `'amis-error'` or `null` | -### 3. Track Detection -- Automatically detects your track (Thesis or SP) based on enrolled courses -- CMSC 200 = Thesis Track (15 free elective units) -- CMSC 190 = SP Track (18 free elective units) +It skips the automatic fetch when `fetchedAt` is under an hour old. The popup's Refresh button sends `{ type: 'FETCH_GRADES', force: true }` to the open AMIS tab, which fetches right away and replies with `{ ok, lastError, fetchedAt }`. -### 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 -- Shows remaining courses based on your selected curriculum -- Calculates required average grade for remaining courses +Other keys: `selectedProgram`, `selectedTracks`, `excludedCourses`, `substitutions`, `customCourseStatus`, `plannerPins`, `plannerPetitions`, `plannerOptions`, `theme`, `termsAccepted`. Export JSON writes all of them plus `schemaVersion`. Import restores all of them except `termsAccepted` and `lastError`. `{ type: 'CLEAR_DATA' }` to the background script clears everything. -## Installation +## Permissions -### From Chrome Web Store -1. Visit the [Chrome Web Store page](https://chrome.google.com/webstore) (Coming soon) -2. Click "Add to Chrome" +| Permission | Why | +| --- | --- | +| `storage` | Keeps grades and settings on your device | +| `amis.uplb.edu.ph` | Runs the content script on AMIS and lets the popup find the AMIS tab for Refresh | +| `api-amis.uplb.edu.ph` (Firefox only) | Lets the content script call the grades API. Chrome sends content script requests with the page's origin, and the API already allows `amis.uplb.edu.ph`, so Chrome, Edge and Opera do not need it | +| `externally_connectable` / `bridge.js` | Lets gradesim.uplb.tools ask for your grades when you press Import there | -### Developer Mode (Manual Installation) -1. Download or clone this repository -2. Run `cd extension && ./build.sh chrome` -3. Open Chrome and go to `chrome://extensions/` -4. Enable "Developer mode" (toggle in the top right) -5. Click "Load unpacked" -6. Select `extension/dist/chrome` +## Build and test -## Usage +```sh +bash build.sh all +for t in *.test.js; do node "$t"; done +npx -y web-ext@8 lint -s dist/firefox +``` -1. Log in to [AMIS](https://amis.uplb.edu.ph) -2. Go to the Personal Information page -3. Click the extension icon to view your GWA and use the What If? simulator +Load `dist/chrome` (Chrome, Edge, Brave, Opera) or `dist/firefox` as an unpacked extension. -## Supported Programs +## Curriculum data -Currently supports: -- **BS Computer Science** - University of the Philippines Los Baños - -More programs coming soon! Other colleges (CAS, CAFS, CEM, CEAT, CDC, CFNR, CHE, UPRHS) are listed but curriculum data is pending. - -## Privacy Policy - -**Your data stays on YOUR device.** - -- ✅ All data is stored locally using Chrome's built-in storage -- ✅ No external servers - everything runs in your browser -- ✅ No analytics or tracking -- ✅ No data collection or sharing with third parties -- ✅ We never see or store your AMIS credentials - -For full details, see [PRIVACY_POLICY.md](PRIVACY_POLICY.md) - -## Permissions Explained - -| Permission | Why We Need It | -|------------|----------------| -| `storage` | To save your grades data locally on your device | -| `host_permissions` | To access AMIS portal and read your grades when you're logged in | - -## Technical Details - -The extension intercepts the `grades?summarize=true` API call made by AMIS to get your grades data. This data is stored locally in your browser and never sent to any external server. - -## Contributing - -Feel free to add more curricula! Edit `curriculum.js` to add courses for other degree programs. - -## License - -MIT License +Programs live in `src/curriculum.js` and the AMIS course catalog in `src/catalog.js`. The pipeline that builds them from the official checklists is in `../scratch/`. diff --git a/extension/build.sh b/extension/build.sh index 423b7f1..eda7809 100755 --- a/extension/build.sh +++ b/extension/build.sh @@ -1,6 +1,7 @@ #!/usr/bin/env bash # Build browser-specific extension packages from shared source. -# Usage: ./build.sh [chrome|firefox|opera|edge|all] +# Usage: ./build.sh [chrome|firefox|opera|all] +# Edge installs the Chrome build, so `edge` is an alias for `chrome`. set -euo pipefail @@ -15,7 +16,7 @@ build_target() { local manifest="$MANIFESTS_DIR/manifest.${target}.json" if [ ! -f "$manifest" ]; then - echo "⚠ No manifest found for '$target' at $manifest — skipping" + echo "No manifest for '$target' at $manifest, skipping" return 1 fi @@ -25,16 +26,14 @@ 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 "$SRC_DIR"/catalog.js \ + "$SRC_DIR"/content.js "$SRC_DIR"/background.js "$SRC_DIR"/bridge.js \ + "$SRC_DIR"/curriculum.js "$SRC_DIR"/requirements.js "$SRC_DIR"/catalog.js \ "$SRC_DIR"/planner.html "$SRC_DIR"/planner.js "$SRC_DIR"/planner.css \ - "$SRC_DIR"/scheduler.js "$out/" + "$SRC_DIR"/scheduler.js "$SRC_DIR"/components.js "$out/" cp "$SRC_DIR"/tokens.css "$SRC_DIR"/plumbob.svg "$out/" cp -r "$SRC_DIR"/fonts "$out/" - [ -f "$SRC_DIR/sims.png" ] && cp "$SRC_DIR/sims.png" "$out/" - # Copy icons mkdir -p "$out/icons" cp "$ICONS_DIR"/* "$out/icons/" @@ -46,11 +45,12 @@ build_target() { } targets="${1:-all}" +[ "$targets" = "edge" ] && targets="chrome" if [ "$targets" = "all" ]; then for manifest in "$MANIFESTS_DIR"/manifest.*.json; do name=$(basename "$manifest" .json | sed 's/manifest\.//') - build_target "$name" || true + build_target "$name" done else build_target "$targets" diff --git a/extension/content.test.js b/extension/content.test.js new file mode 100644 index 0000000..6c2b87b --- /dev/null +++ b/extension/content.test.js @@ -0,0 +1,98 @@ +// Self-check for content.js. Run: node extension/content.test.js +// Loads the script into a fake AMIS page (localStorage token, mocked grades +// API, in-memory chrome.storage.local) and checks what it stores. +const assert = require('assert'); +const fs = require('fs'); +const vm = require('vm'); + +const SRC = fs.readFileSync(require.resolve('./src/content.js'), 'utf8'); + +const AMIS_RESPONSE = { + student_grades: { + 1241: { + term: '1st Semester AY 2024-2025', + secret: 'term-level extra', + values: [{ + id: 77, grade: '1.25', unit_taken: '3', section: 'A', status: 'Final', + student_number: '2022-00000', remarks: 'x', + course: { course_code: 'CMSC 12', title: 'Foundations', description: 'long text' }, + grade_term: { term: '1st Semester', ay: '2024-2025', id: 9 }, + }], + }, + }, + student: { name: 'Juan', address: 'somewhere' }, +}; + +async function page({ token, status = 200, body = AMIS_RESPONSE, stored = {} }) { + const store = { ...stored }; + const calls = []; + let listener; + const ctx = { + localStorage: { getItem: k => (k === 'auth._token.local' ? token : null) }, + fetch: async (url, opts) => { + calls.push({ url, opts }); + return { ok: status >= 200 && status < 300, status, json: async () => body }; + }, + chrome: { + storage: { local: { + get: async k => (typeof k === 'string' && k in store ? { [k]: store[k] } : {}), + set: async o => { Object.assign(store, o); }, + } }, + runtime: { onMessage: { addListener: fn => { listener = fn; } } }, + }, + }; + vm.runInNewContext(SRC, ctx); + await new Promise(r => setTimeout(r, 10)); // let the load-time fetch finish + const send = msg => new Promise(r => assert.strictEqual(listener(msg, {}, r), true)); + return { store, calls, send }; +} + +(async () => { + // Logged in: stores only the course fields, plus fetchedAt and lastError. + let p = await page({ token: 'abc' }); + assert.strictEqual(p.calls.length, 1); + assert.strictEqual(p.calls[0].opts.headers.Authorization, 'Bearer abc'); + assert.strictEqual(p.store.lastError, null); + assert.ok(Date.now() - p.store.fetchedAt < 1000); + assert.deepStrictEqual(Object.keys(p.store).sort(), ['fetchedAt', 'gradesData', 'lastError']); + assert.deepStrictEqual(JSON.parse(JSON.stringify(p.store.gradesData)), { + student_grades: { 1241: { term: '1st Semester AY 2024-2025', values: [{ + id: 77, grade: '1.25', unit_taken: '3', section: 'A', status: 'Final', + course: { course_code: 'CMSC 12', title: 'Foundations' }, + grade_term: { term: '1st Semester', ay: '2024-2025' }, + }] } }, + }); + + // Logged out: no token (Nuxt writes "false") means no request. + p = await page({ token: 'false' }); + assert.strictEqual(p.calls.length, 0); + assert.strictEqual(p.store.lastError, 'not-logged-in'); + assert.strictEqual(p.store.gradesData, undefined); + + // Expired token: AMIS answers 401. + p = await page({ token: 'Bearer old', status: 401, body: { message: 'Unauthenticated.' } }); + assert.strictEqual(p.store.lastError, 'not-logged-in'); + + // AMIS error keeps the grades already saved. + const old = { gradesData: { student_grades: {} }, fetchedAt: 1 }; + p = await page({ token: 'abc', status: 500, stored: old }); + assert.strictEqual(p.store.lastError, 'amis-error'); + assert.strictEqual(p.store.fetchedAt, 1); + assert.deepStrictEqual(p.store.gradesData, old.gradesData); + + // Grades under an hour old: no automatic fetch, but Refresh (force) fetches. + p = await page({ token: 'abc', stored: { fetchedAt: Date.now() - 60000 } }); + assert.strictEqual(p.calls.length, 0); + const res = await p.send({ type: 'FETCH_GRADES', force: true }); + assert.strictEqual(p.calls.length, 1); + assert.strictEqual(res.ok, true); + assert.strictEqual(res.lastError, null); + + // Refresh while logged out reports it. + p = await page({ token: null }); + const out = await p.send({ type: 'FETCH_GRADES', force: true }); + assert.strictEqual(out.ok, false); + assert.strictEqual(out.lastError, 'not-logged-in'); + + console.log('content.test.js: all passed'); +})().catch(e => { console.error(e); process.exit(1); }); diff --git a/extension/curriculum-data.test.js b/extension/curriculum-data.test.js new file mode 100644 index 0000000..7f3128c --- /dev/null +++ b/extension/curriculum-data.test.js @@ -0,0 +1,92 @@ +// Data checks for every available program. Run: node extension/curriculum-data.test.js +// A program that cannot pass stays `available: false` (shown as coming soon). +const assert = require('assert'); +const { UPLB_PROGRAMS, getPlannerCourses, isNonGwaCourseCode, normalizeCourseCode } = require('./src/curriculum.js'); +const { UPLB_CATALOG } = require('./src/catalog.js'); + +// Departments seen in AMIS class listings, plus slot prefixes the code generates +// (GE, HK, NSTP, FE) or the data uses (MAJ for major elective slots), and +// real departments with no class in the listings we have. +const DEPTS = new Set(Object.keys(UPLB_CATALOG).map(c => c.split(' ')[0])); +['GE', 'HK', 'NSTP', 'FE', 'MAJ', 'HIST', 'PED', 'POSC', 'SOIL'].forEach(d => DEPTS.add(d)); +const CODE_RE = /^([A-Z]{2,6}|PEd) \d{1,3}(\.\d{1,2})?[A-Z]?$/; + +function codeProblem(code) { + if (!CODE_RE.test(code)) return 'not a course code'; + if (!DEPTS.has(code.split(' ')[0].toUpperCase())) return 'unknown department'; + return null; +} + +// Rough unit totals by degree when a program states none. +function expectedTotal(p) { + if (p.totalUnitsRequired) return [p.totalUnitsRequired * 0.9, p.totalUnitsRequired * 1.1]; + if (p.code === 'DVM') return [200, 240]; + if (/^A/.test(p.code)) return [70, 80]; + return [140, 180]; +} + +const failures = []; +const fail = (prog, msg) => failures.push(`${prog}: ${msg}`); + +// The negative cases the issue names must be rejected. +['3', 'Elective', 'MID', 'rol', 'dicine', 'MAJOR', 'FIFTH', 'YEAR 3', 'CHEM 18. University Chemistry', 'MAJOR 200', 'MIDYEAR 1'] + .forEach(bad => assert.ok(codeProblem(bad), `should reject "${bad}"`)); +['CMSC 12', 'MATH 27', 'ABT 10', 'HNF 200', 'PEd 91', 'BIO 11.1', 'GE 1', 'FE 2', 'HK 1', 'NSTP 2', 'MAJ 3', 'AAE 200A'] + .forEach(good => assert.strictEqual(codeProblem(good), null, `should accept "${good}"`)); + +const available = Object.values(UPLB_PROGRAMS).filter(p => p.available); +assert.ok(available.length > 0, 'at least one program is available'); + +available.forEach(p => { + const rows = p.majorCourses || []; + if (!rows.length) fail(p.code, 'no courses'); + + rows.forEach(c => { + const why = codeProblem(c.code); + if (why) fail(p.code, `${why}: "${c.code}"`); + // Practicums and internships run a whole term (HNF 200A at 12, VETC 176 at 14). + const maxUnits = /practicum|internship|clerkship/i.test(c.title || '') ? 18 : 6; + if (!(typeof c.units === 'number' && c.units > 0 && c.units <= maxUnits)) fail(p.code, `${c.code} has ${c.units} units`); + if (!c.title || /Unknown Title/i.test(c.title)) fail(p.code, `${c.code} has no title`); + if (!(c.year >= 1 && c.year <= 6)) fail(p.code, `${c.code} has year ${c.year}`); + if (!['1', '2', 'midyear'].includes(c.sem)) fail(p.code, `${c.code} has sem ${c.sem}`); + }); + + const listed = new Set(rows.map(c => normalizeCourseCode(c.code))); + (p.requiredCodes || []).forEach(code => { + const why = codeProblem(code); + if (why) fail(p.code, `required list: ${why}: "${code}"`); + else if (!listed.has(normalizeCourseCode(code))) fail(p.code, `required ${code} is not in the course list`); + }); + + const tracks = p.tracks ? Object.keys(p.tracks) : [null]; + tracks.forEach(track => { + const plan = getPlannerCourses(p, track); + const seen = new Set(); + plan.forEach(c => { + const code = normalizeCourseCode(c.code); + if (seen.has(code)) fail(p.code, `duplicate ${code}${track ? ` (${track})` : ''}`); + seen.add(code); + }); + const units = plan.filter(c => !isNonGwaCourseCode(c.code)).reduce((s, c) => s + c.units, 0); + const [lo, hi] = expectedTotal(p); + if (units < lo || units > hi) { + fail(p.code, `${units} units${track ? ` (${track})` : ''}, expected ${p.totalUnitsRequired || `${lo} to ${hi}`}`); + } + }); +}); + +// Every SP or thesis track code must be a course of its program (#80), or +// track detection can never fire. +Object.values(UPLB_PROGRAMS).filter(p => p.tracks).forEach(p => { + const listed = new Set((p.majorCourses || []).map(c => normalizeCourseCode(c.code))); + Object.entries(p.tracks).forEach(([key, t]) => { + if (!listed.has(normalizeCourseCode(t.code))) fail(p.code, `track ${key} code ${t.code} is not in the course list`); + const tagged = (p.majorCourses || []).find(c => normalizeCourseCode(c.code) === normalizeCourseCode(t.code)); + if (tagged && tagged.track && tagged.track !== key) fail(p.code, `${t.code} is tagged ${tagged.track}, not ${key}`); + }); + if (p.defaultTrack && !p.tracks[p.defaultTrack]) fail(p.code, `default track ${p.defaultTrack} does not exist`); +}); + +assert.deepStrictEqual(failures, [], `\n${failures.join('\n')}`); +console.log(`curriculum-data.test.js: ${available.length} available programs pass`); diff --git a/extension/curriculum.test.js b/extension/curriculum.test.js index 187f44f..d924619 100644 --- a/extension/curriculum.test.js +++ b/extension/curriculum.test.js @@ -73,4 +73,18 @@ assert.strictEqual(detectTrack([{ courseCode: 'CMSC 12', grade: '1.00' }], BSCS) // Longest code wins: AAE 200A is the MFP track, not thesis AAE 200. assert.strictEqual(detectTrack([{ code: 'AAE 200A' }], UPLB_PROGRAMS.BSAAE), 'mfp'); +// Data quality for the low-confidence banner (#79). +const { getProgramDataQuality } = require('./src/curriculum.js'); +const { UPLB_CATALOG } = require('./src/catalog.js'); +const good = getProgramDataQuality('BSCS', UPLB_CATALOG); +assert.strictEqual(good.confident, true); +assert.ok(good.prereqShare >= 0.3); +assert.deepStrictEqual(good.reasons, []); +const thin = getProgramDataQuality('BAPHILO', UPLB_CATALOG); +assert.strictEqual(thin.confident, false); +assert.ok(thin.prereqShare < 0.3 && thin.reasons.length === 1); +const soon = getProgramDataQuality('ASDC', UPLB_CATALOG); +assert.strictEqual(soon.confident, false); +assert.strictEqual(getProgramDataQuality('NOPE').confident, false); + console.log('curriculum.test.js: all assertions passed'); diff --git a/extension/src/sims.png b/extension/icons/icon128.png similarity index 100% rename from extension/src/sims.png rename to extension/icons/icon128.png diff --git a/extension/icons/icon128.svg b/extension/icons/icon128.svg deleted file mode 100644 index eb36037..0000000 --- a/extension/icons/icon128.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - G - diff --git a/extension/icons/icon16.png b/extension/icons/icon16.png new file mode 100644 index 0000000..e85c292 Binary files /dev/null and b/extension/icons/icon16.png differ diff --git a/extension/icons/icon16.svg b/extension/icons/icon16.svg deleted file mode 100644 index e52f468..0000000 --- a/extension/icons/icon16.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - G - diff --git a/extension/icons/icon32.png b/extension/icons/icon32.png new file mode 100644 index 0000000..6d6402c Binary files /dev/null and b/extension/icons/icon32.png differ diff --git a/extension/icons/icon48.png b/extension/icons/icon48.png new file mode 100644 index 0000000..2d07643 Binary files /dev/null and b/extension/icons/icon48.png differ diff --git a/extension/icons/icon48.svg b/extension/icons/icon48.svg deleted file mode 100644 index 7a308df..0000000 --- a/extension/icons/icon48.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - G - diff --git a/extension/manifests/manifest.chrome.json b/extension/manifests/manifest.chrome.json index df8457e..47c56dd 100644 --- a/extension/manifests/manifest.chrome.json +++ b/extension/manifests/manifest.chrome.json @@ -1,8 +1,8 @@ { "manifest_version": 3, - "name": "Elbi GradeSim - UPLB GWA Calculator", - "version": "2.0.0", - "description": "Calculate your GWA and simulate grades needed for Latin honors at UPLB. All data stays on your device.", + "name": "Elbi GradeSim", + "version": "2.1.0", + "description": "UPLB GWA calculator and Latin honors simulator, with a planner for your remaining courses. Your grades stay on your device.", "permissions": [ "storage" ], @@ -13,11 +13,17 @@ "action": { "default_popup": "popup.html", "default_icon": { - "128": "sims.png" + "16": "icons/icon16.png", + "32": "icons/icon32.png", + "48": "icons/icon48.png", + "128": "icons/icon128.png" } }, "icons": { - "128": "sims.png" + "16": "icons/icon16.png", + "32": "icons/icon32.png", + "48": "icons/icon48.png", + "128": "icons/icon128.png" }, "background": { "service_worker": "background.js" @@ -30,7 +36,12 @@ "js": [ "content.js" ], - "run_at": "document_start" + "run_at": "document_idle" } - ] + ], + "externally_connectable": { + "matches": [ + "https://gradesim.uplb.tools/*" + ] + } } diff --git a/extension/manifests/manifest.firefox.json b/extension/manifests/manifest.firefox.json index c410da1..025a9a8 100644 --- a/extension/manifests/manifest.firefox.json +++ b/extension/manifests/manifest.firefox.json @@ -1,7 +1,7 @@ { "manifest_version": 3, - "name": "Elbi GradeSim - UPLB GWA Calculator", - "version": "2.0.0", + "name": "Elbi GradeSim", + "version": "2.1.0", "browser_specific_settings": { "gecko": { "id": "elbigradesim@stimmie.dev", @@ -16,7 +16,7 @@ "strict_min_version": "120.0" } }, - "description": "Calculate your GWA and simulate grades needed for Latin honors at UPLB. All data stays on your device.", + "description": "UPLB GWA calculator and Latin honors simulator, with a planner for your remaining courses. Your grades stay on your device.", "permissions": [ "storage" ], @@ -27,11 +27,17 @@ "action": { "default_popup": "popup.html", "default_icon": { - "128": "sims.png" + "16": "icons/icon16.png", + "32": "icons/icon32.png", + "48": "icons/icon48.png", + "128": "icons/icon128.png" } }, "icons": { - "128": "sims.png" + "16": "icons/icon16.png", + "32": "icons/icon32.png", + "48": "icons/icon48.png", + "128": "icons/icon128.png" }, "background": { "scripts": [ @@ -46,6 +52,15 @@ "js": [ "content.js" ], + "run_at": "document_idle" + }, + { + "matches": [ + "https://gradesim.uplb.tools/*" + ], + "js": [ + "bridge.js" + ], "run_at": "document_start" } ] diff --git a/extension/manifests/manifest.opera.json b/extension/manifests/manifest.opera.json index df8457e..47c56dd 100644 --- a/extension/manifests/manifest.opera.json +++ b/extension/manifests/manifest.opera.json @@ -1,8 +1,8 @@ { "manifest_version": 3, - "name": "Elbi GradeSim - UPLB GWA Calculator", - "version": "2.0.0", - "description": "Calculate your GWA and simulate grades needed for Latin honors at UPLB. All data stays on your device.", + "name": "Elbi GradeSim", + "version": "2.1.0", + "description": "UPLB GWA calculator and Latin honors simulator, with a planner for your remaining courses. Your grades stay on your device.", "permissions": [ "storage" ], @@ -13,11 +13,17 @@ "action": { "default_popup": "popup.html", "default_icon": { - "128": "sims.png" + "16": "icons/icon16.png", + "32": "icons/icon32.png", + "48": "icons/icon48.png", + "128": "icons/icon128.png" } }, "icons": { - "128": "sims.png" + "16": "icons/icon16.png", + "32": "icons/icon32.png", + "48": "icons/icon48.png", + "128": "icons/icon128.png" }, "background": { "service_worker": "background.js" @@ -30,7 +36,12 @@ "js": [ "content.js" ], - "run_at": "document_start" + "run_at": "document_idle" } - ] + ], + "externally_connectable": { + "matches": [ + "https://gradesim.uplb.tools/*" + ] + } } diff --git a/extension/planner-sweep.test.js b/extension/planner-sweep.test.js new file mode 100644 index 0000000..aa96d31 --- /dev/null +++ b/extension/planner-sweep.test.js @@ -0,0 +1,28 @@ +// Plan every available program from an empty transcript the way planner.js +// does, at the default 18-unit cap with midyear off. +// Run: node extension/planner-sweep.test.js +const assert = require('assert'); +const { UPLB_PROGRAMS, getPlannerCourses } = require('./src/curriculum.js'); +const { UPLB_CATALOG } = require('./src/catalog.js'); +const { enrichCourses, scheduleEarliest, semAt } = require('./src/scheduler.js'); + +const failures = []; +Object.values(UPLB_PROGRAMS).filter(p => p.available).forEach(p => { + const years = Math.max(...p.majorCourses.map(c => c.year)); + (p.tracks ? Object.keys(p.tracks) : [null]).forEach(track => { + const label = track ? `${p.code} (${track})` : p.code; + const r = scheduleEarliest({ + courses: enrichCourses(getPlannerCourses(p, track), UPLB_CATALOG), + passed: new Set(), + useMidyear: false, + totalUnits: p.totalUnitsRequired, + }); + if (r.unschedulable.length) failures.push(`${label}: cannot place ${r.unschedulable.join(', ')}`); + let regular = 0; + for (let t = 0; t <= r.gradTermIndex; t++) if (semAt(t, '1') !== 'midyear') regular++; + if (regular < 2 * years) failures.push(`${label}: ${regular} regular terms for a ${years}-year checklist`); + }); +}); + +assert.deepStrictEqual(failures, [], `\n${failures.join('\n')}`); +console.log('planner-sweep.test.js: every available program plans from scratch'); diff --git a/extension/requirements.test.js b/extension/requirements.test.js new file mode 100644 index 0000000..c0bfd08 --- /dev/null +++ b/extension/requirements.test.js @@ -0,0 +1,86 @@ +// What if and planner count the same courses left (#78, #94), term ids past +// 2029 parse (#86), and the GWA outlook math (#95). +// Run: node extension/requirements.test.js +const assert = require('assert'); +Object.assign(globalThis, require('./src/curriculum.js'), require('./src/scheduler.js')); +const { UPLB_CATALOG } = require('./src/catalog.js'); +const { amisTermToAbs, gradeResult, amisCourses, plannerCourseList, remainingRequirements, gwaOutlook } = require('./src/requirements.js'); + +assert.strictEqual(amisTermToAbs(1251), 2025 * 3); +assert.strictEqual(amisTermToAbs('1232'), 2023 * 3 + 1); +assert.strictEqual(amisTermToAbs(1293), 2029 * 3 + 2); +assert.strictEqual(amisTermToAbs(1301), 2030 * 3); +assert.strictEqual(amisTermToAbs(1312), 2031 * 3 + 1); +assert.strictEqual(amisTermToAbs(1254), null); +assert.strictEqual(amisTermToAbs('abc'), null); + +assert.strictEqual(gradeResult('1.00'), 'passed'); +assert.strictEqual(gradeResult('P'), 'passed'); +assert.strictEqual(gradeResult('5.00'), 'failed'); +assert.strictEqual(gradeResult('INC'), 'other'); +assert.strictEqual(gradeResult('4.00'), 'other'); +assert.strictEqual(gradeResult(null), 'nograde'); + +// A BSCS student with GE courses named by code, HK, NSTP, a PEd course and an +// outside elective, a failed then retaken course, an open 5.00, an INC, and +// two courses being taken now. +const v = (code, title, units, grade) => ({ grade, unit_taken: String(units), course: { course_code: code, title } }); +const gradesData = { student_grades: { + 1231: { values: [v('CMSC 12', 'Foundations of Computer Science', 3, '1.00'), v('CMSC 56', 'Discrete Mathematics I', 3, '1.50'), + v('MATH 27', 'Analytic Geometry and Calculus II', 3, 'INC'), v('ETHICS 1', 'Ethics and Moral Reasoning', 3, '1.25'), + v('HK 11', 'Wellness', 2, '1.00'), v('NSTP 1', 'National Service Training Program I', 3, 'P')] }, + 1232: { values: [v('CMSC 21', 'Fundamentals of Programming', 3, '5.00'), v('CMSC 22', 'Object-Oriented Programming', 3, '1.75'), + v('STS 1', 'Science, Technology and Society', 3, '1.50'), v('ARTS 1', 'Critical Perspectives in the Arts', 3, '1.25'), + v('PED 101', 'Foundations of Physical Education', 3, '1.50')] }, + 1241: { values: [v('CMSC 21', 'Fundamentals of Programming', 3, '2.00'), v('MATH 28', 'Analytic Geometry and Calculus III', 3, '2.25'), + v('CMSC 123', 'Data Structures', 3, '5.00'), v('ECON 11', 'Introductory Economics', 3, '1.75')] }, + 1251: { values: [v('CMSC 100', 'Web Programming', 3, null), v('CMSC 127', 'File Processing and Database Systems', 3, null)] }, +} }; +const BSCS = UPLB_PROGRAMS.BSCS; +const passedRows = amisCourses(gradesData).filter(r => r.result === 'passed'); +const courses = plannerCourseList(BSCS, 'sp', UPLB_CATALOG); +const left = remainingRequirements(courses, passedRows); +const leftCodes = new Set(left.left.map(c => c.code)); + +// One GE model (#94): ETHICS 1, STS 1 and ARTS 1 fill three GE slots. +assert.deepStrictEqual(left.ge, { done: 3, total: 9 }); +// HK and NSTP complete (#78), PEd and ECON 11 fill free elective slots. +assert.ok(!leftCodes.has('HK 1') && leftCodes.has('HK 2')); +assert.ok(!leftCodes.has('NSTP 1') && leftCodes.has('NSTP 2')); +assert.deepStrictEqual(left.electives, { doneUnits: 6, totalUnits: 18 }); +// Retaken course is done; open failure, INC and courses in progress are left. +assert.ok(!leftCodes.has('CMSC 21')); +['CMSC 123', 'MATH 27', 'CMSC 100', 'CMSC 127'].forEach(c => assert.ok(leftCodes.has(c), c)); +// HK and NSTP units are left to take but do not count in the GWA. +assert.strictEqual(left.units - left.gwaUnits, 2 + 3); + +// The planner's units left: schedule what the slots leave open, the way +// planner.js does, and add up every course it places or cannot place. +const plan = scheduleEarliest({ courses, passed: new Set(left.fill.keys()), useMidyear: false, totalUnits: BSCS.totalUnitsRequired }); +const byCode = new Map(courses.map(c => [c.code, c])); +const plannerUnits = [...Object.keys(plan.assignedTerm), ...plan.unschedulable] + .reduce((s, code) => s + (Number(byCode.get(code).units) || 0), 0); +assert.strictEqual(left.units, plannerUnits); + +// Programs without tracks get no made-up 15 free elective units (#78): +// BSSTAT has only major elective slots, BSFST lists two FE slots. +assert.strictEqual(remainingRequirements(plannerCourseList(UPLB_PROGRAMS.BSSTAT, null, UPLB_CATALOG), []).electives.totalUnits, 0); +assert.strictEqual(remainingRequirements(plannerCourseList(UPLB_PROGRAMS.BSFST, null, UPLB_CATALOG), []).electives.totalUnits, 6); + +// Overrides from the planner count too. +const marked = remainingRequirements(courses, passedRows, { overrides: { 'CMSC 123': 'passed', 'CMSC 12': 'failed' } }); +assert.strictEqual(marked.units, left.units); + +// Outlook (#95): a target the remaining units cannot reach shows the ceiling +// and the best honor still possible. +const out = gwaOutlook(1.9, 60, 60, 1.2); +assert.strictEqual(out.status, 'out-of-reach'); +assert.strictEqual(out.ceiling, 1.45); +assert.deepStrictEqual(out.bestHonor, ['Magna cum laude', 1.45]); +const ok = gwaOutlook(1.9, 60, 60, 1.75); +assert.strictEqual(ok.status, 'reachable'); +assert.strictEqual(Number(ok.required.toFixed(2)), 1.6); +assert.strictEqual(gwaOutlook(1.5, 60, 10, 1.75).status, 'any-pass'); +assert.strictEqual(gwaOutlook(2.5, 60, 0, 1.75).status, 'out-of-reach'); + +console.log('requirements.test.js: all assertions passed'); diff --git a/extension/scheduler.test.js b/extension/scheduler.test.js index ebfabf4..adba1bc 100644 --- a/extension/scheduler.test.js +++ b/extension/scheduler.test.js @@ -197,4 +197,24 @@ const en = enrichCourses([ assert.deepStrictEqual(en.map(c => c.code), ['CHEM 18', 'PI 10', 'CE 151', 'GE', 'GE (2)']); assert.deepStrictEqual(en.find(c => c.code === 'CE 151').pre, [['CHEM 18']]); +// Catalog units beat garbled checklist units (#79); SP and thesis keep theirs. +const units = enrichCourses([ + { code: 'CHEM 18', title: 'University Chemistry', units: 51, year: 1, sem: '1' }, + { code: 'CE 200', title: 'Thesis', units: 6, year: 4, sem: '1' }, + { code: 'CE 151', title: 'Sanitary', units: 3, year: 3, sem: '1' }, +], { ...cat, 'CE 200': { units: 1, title: 'Thesis' } }); +assert.deepStrictEqual(units.map(c => c.units), [3, 6, 3]); + +// A course AMIS only offers in midyear still gets placed when midyear is off. +const midOnly = scheduleEarliest({ + courses: [{ code: 'BA 183', units: 3, sem: '1', prereqs: [], offered: { 1: 0, 2: 0, 3: 2 } }], + passed: new Set(), useMidyear: false, +}); +assert.deepStrictEqual(midOnly.unschedulable, []); +assert.strictEqual(midOnly.assignedTerm['BA 183'], 2); + +// Default cap matches the catalog load rule: 18 a regular sem, 6 midyear. +const { DEFAULT_UNIT_CAPS } = require('./src/scheduler.js'); +assert.deepStrictEqual(DEFAULT_UNIT_CAPS, { '1': 18, '2': 18, 'midyear': 6 }); + console.log('scheduler.test.js: all assertions passed'); diff --git a/extension/src/background.js b/extension/src/background.js index 4e2b8a3..cd1ddc1 100644 --- a/extension/src/background.js +++ b/extension/src/background.js @@ -4,21 +4,38 @@ // 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 +// Clear my data (popup Help). Removes every key this extension stored. 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 }); - } - + if (!message || message.type !== 'CLEAR_DATA') return false; + chrome.storage.local.clear().then(() => sendResponse({ ok: true })); return true; }); +// Web app bridge (Chrome, Edge, Opera, Brave). The manifest lists +// https://gradesim.uplb.tools under externally_connectable, so only that site +// can send these. GRADESIM_PING says whether grades exist; GRADESIM_GET_GRADES +// returns them in the same shape as Export JSON. The data goes straight to the +// page in the browser; nothing is sent over the network. +const WEB_APP_ORIGIN = 'https://gradesim.uplb.tools'; +if (chrome.runtime.onMessageExternal) { + chrome.runtime.onMessageExternal.addListener((message, sender, sendResponse) => { + if (sender.origin !== WEB_APP_ORIGIN || !message) return false; + if (message.type !== 'GRADESIM_PING' && message.type !== 'GRADESIM_GET_GRADES') return false; + chrome.storage.local.get(['gradesData', 'selectedProgram', 'excludedCourses', 'substitutions']).then(data => { + const hasGrades = !!(data.gradesData && data.gradesData.student_grades); + if (message.type === 'GRADESIM_PING') { + sendResponse({ hasGrades }); + return; + } + sendResponse(hasGrades ? { + source: 'elbi-gradesim', + timestamp: new Date().toISOString(), + selectedProgram: data.selectedProgram || 'BSCS', + excludedCourses: data.excludedCourses || [], + substitutions: data.substitutions || {}, + gradesData: data.gradesData, + } : null); + }); + return true; // answer asynchronously + }); +} diff --git a/extension/src/bridge.js b/extension/src/bridge.js new file mode 100644 index 0000000..c3b042a --- /dev/null +++ b/extension/src/bridge.js @@ -0,0 +1,37 @@ +// extension/src/bridge.js + +// Web app bridge for Firefox, which has no externally_connectable for web +// pages. Runs only on https://gradesim.uplb.tools and answers two requests the +// page posts to itself: GRADESIM_PING (are there grades?) and +// GRADESIM_GET_GRADES (hand them over, sent when the student clicks Import). +// The reply stays inside this browser tab; nothing is sent over the network. +// Chromium browsers use externally_connectable in background.js instead. + +(function () { + 'use strict'; + + window.addEventListener('message', (event) => { + if (event.source !== window || event.origin !== window.location.origin) return; + const type = event.data && event.data.type; + if (type !== 'GRADESIM_PING' && type !== 'GRADESIM_GET_GRADES') return; + + chrome.storage.local.get(['gradesData', 'selectedProgram', 'excludedCourses', 'substitutions']).then(data => { + const hasGrades = !!(data.gradesData && data.gradesData.student_grades); + let payload = { hasGrades }; + if (type === 'GRADESIM_GET_GRADES') { + payload = hasGrades ? { + source: 'elbi-gradesim', + timestamp: new Date().toISOString(), + selectedProgram: data.selectedProgram || 'BSCS', + excludedCourses: data.excludedCourses || [], + substitutions: data.substitutions || {}, + gradesData: data.gradesData, + } : null; + } + // Firefox content scripts see the page through an Xray wrapper; cloneInto + // makes the object readable by the page. + const out = { type: 'GRADESIM_REPLY', request: type, payload }; + window.postMessage(typeof cloneInto === 'function' ? cloneInto(out, window) : out, window.location.origin); + }); + }); +})(); diff --git a/extension/src/components.js b/extension/src/components.js new file mode 100644 index 0000000..5359ee9 --- /dev/null +++ b/extension/src/components.js @@ -0,0 +1,273 @@ +// components.js - the shared UI pieces of the popup and the planner. +// Plain functions that return DOM elements. No markup strings are assigned to +// innerHTML anywhere (the Firefox add-on linter flags it), and every style +// comes from the classes in popup.css, which only use tokens.css values. +// Loaded in so the cached theme applies before the first paint. + +/* ---------- Theme, applied before first paint ---------- */ + +const THEMES = ['system', 'light', 'dark']; +const THEME_CACHE_KEY = 'gradesim:theme'; + +function setThemeAttr(theme) { + if (theme === 'light' || theme === 'dark') document.documentElement.dataset.theme = theme; + else delete document.documentElement.dataset.theme; +} + +// MV3 forbids inline scripts, so this file does the early read itself. +try { setThemeAttr(JSON.parse(localStorage.getItem(THEME_CACHE_KEY))); } catch (e) { /* no cache yet */ } + +// Wire the theme button. `saved` is the stored choice, `persist` saves a new one. +function themeToggle(btn, saved, persist) { + const apply = theme => { + setThemeAttr(theme); + try { localStorage.setItem(THEME_CACHE_KEY, JSON.stringify(theme)); } catch (e) { /* private mode */ } + btn.replaceChildren(icon({ system: 'monitor', light: 'sun', dark: 'moon' }[theme])); + btn.title = `Theme, ${theme}`; + btn.setAttribute('aria-label', btn.title); + btn.dataset.theme = theme; + }; + apply(THEMES.includes(saved) ? saved : 'system'); + btn.addEventListener('click', () => { + const next = THEMES[(THEMES.indexOf(btn.dataset.theme) + 1) % THEMES.length]; + apply(next); + persist(next); + }); +} + +/* ---------- Element builder ---------- */ + +// h('div', { class: 'x', onclick: fn, dataset: {...}, 'aria-label': '...' }, ...children) +// Children may be strings, numbers, elements, arrays, or null/false (skipped). +function h(tag, props, ...children) { + const el = document.createElement(tag); + Object.entries(props || {}).forEach(([k, v]) => { + if (v == null || v === false) return; + if (k === 'class') el.className = v; + else if (k === 'dataset') Object.assign(el.dataset, v); + else if (k.startsWith('on')) el.addEventListener(k.slice(2), v); + else if (k in el && typeof v !== 'string') el[k] = v; + else el.setAttribute(k, v === true ? '' : v); + }); + el.append(...flat(children)); + return el; +} + +function flat(children) { + return children.flat(Infinity).filter(c => c != null && c !== false).map(c => (c instanceof Node ? c : String(c))); +} + +const SVG_NS = 'http://www.w3.org/2000/svg'; +function svgEl(tag, attrs) { + const el = document.createElementNS(SVG_NS, tag); + Object.entries(attrs || {}).forEach(([k, v]) => el.setAttribute(k, v)); + return el; +} + +/* ---------- Icons (Lucide, ISC license, https://lucide.dev) ---------- */ + +const ICON_PATHS = { + check: '', + x: '', + alert: '', + award: '', + sun: '', + moon: '', + monitor: '', + lock: '', + clock: '', + retake: '', + ready: '', + planned: '', + flag: '', + info: '', + help: '', + external: '', + download: '', + upload: '', + chevron: '', + prev: '', + next: '', + target: '', + eyeOff: '', + sparkles: '', + trash: '', + list: '', +}; + +const iconCache = {}; +// The paths above are static strings; DOMParser turns them into SVG nodes once. +function icon(name) { + if (!iconCache[name]) { + const doc = new DOMParser().parseFromString( + ``, + 'image/svg+xml'); + iconCache[name] = document.importNode(doc.documentElement, true); + } + return iconCache[name].cloneNode(true); +} + +// Static pages mark icon spots with . +function hydrateIcons(root = document) { + root.querySelectorAll('[data-icon]').forEach(el => el.replaceWith(icon(el.dataset.icon))); +} + +/* ---------- Text helpers ---------- */ + +const plural = (n, one, many = `${one}s`) => `${n} ${n === 1 ? one : many}`; +const unitsText = n => plural(Number(n) || 0, 'unit'); + +/* ---------- Components ---------- */ + +// variant: primary | secondary | text | icon | chip. Extra props pass through. +function button({ text, icon: ic, variant = 'secondary', small, class: cls, ...props }) { + const base = { primary: 'btn btn-primary', secondary: 'btn btn-secondary', text: 'btn-text', icon: 'btn-icon', chip: 'header-btn' }[variant]; + return h('button', { type: 'button', class: [base, small && 'btn-sm', cls].filter(Boolean).join(' '), ...props }, + ic && icon(ic), text); +} + +// Small rounded label. tone: neutral | brand | strong | ok | warn | bad | info +function badge(text, { tone = 'neutral', icon: ic, class: cls, ...props } = {}) { + return h('span', { class: `badge badge-${tone}${cls ? ` ${cls}` : ''}`, ...props }, ic && icon(ic), text); +} + +// Latin honor track or semester scholar list. Returns null when none applies. +function honorBadge(kind) { + const map = { + summa: ['Summa cum laude track', 'strong'], + magna: ['Magna cum laude track', 'brand'], + cum: ['Cum laude track', 'brand'], + university: ['University Scholar', 'strong'], + college: ['College Scholar', 'brand'], + }; + return map[kind] ? badge(map[kind][0], { tone: map[kind][1], icon: 'award' }) : null; +} + +// Course status in the planner. Icon plus word, never color alone. +const COURSE_STATUS = { + passed: ['check', 'Passed'], + inprogress: ['clock', 'Taking now'], + failed: ['x', 'Failed'], + retake: ['retake', 'Retake'], + ready: ['ready', 'Ready'], + planned: ['planned', 'Planned'], + locked: ['lock', 'Waiting'], +}; +// variant: plain (inside a card) | pill (bordered) +function statusPill(status, { variant = 'pill', label } = {}) { + const [ic, word] = COURSE_STATUS[status] || COURSE_STATUS.planned; + return h('span', { class: `status status-${variant} st-${status}` }, icon(ic), label || word); +} + +const unitsBadge = units => h('span', { class: 'units-badge' }, unitsText(units)); + +// Label over a big value, with an optional note under it. variant: block | row +function stat(label, value, { note, variant = 'block', highlight, id } = {}) { + return h('div', { class: `stat stat-${variant}${highlight ? ' highlight' : ''}` }, + h('span', { class: 'stat-label' }, label), + h('span', { class: 'stat-value', id }, value), + note && h('span', { class: 'stat-note' }, note)); +} + +// One course line: code and title on the left, anything else on the right. +function courseRow({ code, title, units, below, aside, class: cls, ...props }) { + return h('div', { class: `course-row${cls ? ` ${cls}` : ''}`, ...props }, + h('div', { class: 'course-info' }, + h('div', { class: 'course-code' }, code), + title && h('div', { class: 'course-title' }, title), + below), + h('div', { class: 'course-aside' }, units != null && unitsBadge(units), aside)); +} + +// Callout with a tone. tone: neutral | ok | warn | bad | info +// action: an optional element (usually a button) on the right. +function notice({ tone = 'neutral', icon: ic, title, body, action, class: cls, ...props }) { + return h('div', { class: `notice notice-${tone}${cls ? ` ${cls}` : ''}`, ...props }, + ic && icon(ic), + h('div', { class: 'notice-text' }, title && h('strong', {}, title), body && h('span', {}, body)), + action && h('div', { class: 'notice-action' }, action)); +} + +// "Last updated 3 hours ago" with a Refresh button. Past an hour it says the +// grades may be out of date. fetchedAt is a ms timestamp or empty. +function syncStatus(fetchedAt, onRefresh) { + const min = fetchedAt ? Math.max(0, Math.round((Date.now() - fetchedAt) / 60000)) : null; + let text = 'Press Refresh to get your latest grades.'; + if (min != null) { + const ago = min < 1 ? 'just now' + : min < 60 ? `${plural(min, 'minute')} ago` + : min < 24 * 60 ? `${plural(Math.round(min / 60), 'hour')} ago` + : `${plural(Math.round(min / 1440), 'day')} ago`; + text = `Last updated ${ago}${min >= 60 ? ', so it may be out of date' : ''}.`; + } + return h('div', { class: `sync-status${min == null || min >= 60 ? ' stale' : ''}` }, + h('span', {}, text), + button({ variant: 'text', icon: 'retake', text: 'Refresh', class: 'refresh-btn', onclick: onRefresh })); +} + +function emptyState(text, { icon: ic, action } = {}) { + return h('div', { class: 'empty-state' }, ic && icon(ic), h('p', {}, text), action); +} + +// Tabs with the ARIA tab pattern. Buttons carry data-tab; panels are found by +// id `${data-tab}Tab`. Arrow keys, Home and End move between tabs. +function tabBar(tablist, onChange) { + const tabs = Array.from(tablist.querySelectorAll('[data-tab]')); + tablist.setAttribute('role', 'tablist'); + const panelOf = t => document.getElementById(`${t.dataset.tab}Tab`); + const select = (tab, focus) => { + tabs.forEach(t => { + const on = t === tab; + t.setAttribute('aria-selected', String(on)); + t.tabIndex = on ? 0 : -1; + t.classList.toggle('active', on); + panelOf(t).hidden = !on; + }); + if (focus) tab.focus(); + if (onChange) onChange(tab.dataset.tab); + }; + tabs.forEach(t => { + t.id = t.id || `tab-${t.dataset.tab}`; + t.setAttribute('role', 'tab'); + t.setAttribute('aria-controls', panelOf(t).id); + panelOf(t).setAttribute('role', 'tabpanel'); + panelOf(t).setAttribute('aria-labelledby', t.id); + t.addEventListener('click', () => select(t)); + }); + tablist.addEventListener('keydown', e => { + const i = tabs.indexOf(document.activeElement); + if (i < 0) return; + const to = { ArrowRight: i + 1, ArrowLeft: i - 1, Home: 0, End: tabs.length - 1 }[e.key]; + if (to === undefined) return; + e.preventDefault(); + select(tabs[(to + tabs.length) % tabs.length], true); + }); + select(tabs.find(t => t.classList.contains('active')) || tabs[0]); + return { select: name => select(tabs.find(t => t.dataset.tab === name)) }; +} + +// Native as a modal. showModal() traps focus and makes the page +// inert; this adds focus return, backdrop click, and an optional lock that +// ignores Escape (for a choice the user has to make). +function modal(dialog) { + let opener = null; + let locked = false; + dialog.addEventListener('cancel', e => { if (locked) e.preventDefault(); }); + dialog.addEventListener('click', e => { if (e.target === dialog && !locked) dialog.close(); }); + dialog.addEventListener('close', () => { + // Chrome lets Escape through without user activation even when cancel is + // prevented, so a locked dialog reopens itself. + if (locked) { dialog.showModal(); return; } + if (opener && opener.isConnected) opener.focus(); + opener = null; + }); + dialog.querySelectorAll('[data-close]').forEach(b => b.addEventListener('click', () => dialog.close())); + return { + open({ lock = false } = {}) { + locked = lock; + opener = document.activeElement; + if (!dialog.open) dialog.showModal(); + }, + close() { locked = false; dialog.close(); }, + }; +} diff --git a/extension/src/content.js b/extension/src/content.js index 0b7f1e6..d23bb89 100644 --- a/extension/src/content.js +++ b/extension/src/content.js @@ -1,160 +1,92 @@ // extension/src/content.js -// 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. +// Content script for Elbi GradeSim. Runs on amis.uplb.edu.ph. +// It calls the AMIS grades API once with the student's own login token, keeps +// only the course fields the extension reads, and saves them in +// chrome.storage.local. Nothing leaves the browser. +// +// Storage keys written here: +// gradesData { student_grades: { : { term, values: [course] } } } +// fetchedAt ms timestamp of the last successful fetch +// lastError 'not-logged-in', 'amis-error' or null -(function() { +(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'); + + const API = 'https://api-amis.uplb.edu.ph/api/students/grades?summarize=true'; + const MAX_AGE_MS = 60 * 60 * 1000; + let inFlight = null; + + // AMIS (Nuxt auth) keeps the token here and writes the string "false" on logout. + function authToken() { + const t = localStorage.getItem('auth._token.local'); + if (!t || t === 'false') return null; + return t.startsWith('Bearer ') ? t : `Bearer ${t}`; } - - // 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 (fetch accepts a string or a Request) - const url = (args[0] instanceof Request ? args[0].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; + + // Keep the AMIS shape (popup.js, planner.js and the web app read it) but drop + // every field they do not use. + function slim(data) { + const out = {}; + for (const [termId, t] of Object.entries(data.student_grades)) { + if (!t || !Array.isArray(t.values)) continue; + out[termId] = { + term: typeof t.term === 'string' ? t.term : undefined, + values: t.values.map(v => ({ + id: v.id, + grade: v.grade, + unit_taken: v.unit_taken, + section: v.section, + status: v.status, + course: { course_code: v.course?.course_code, title: v.course?.title }, + grade_term: { term: v.grade_term?.term, ay: v.grade_term?.ay }, + })), + }; } - + return { student_grades: out }; + } + + async function fetchGrades() { + const token = authToken(); + if (!token) return { lastError: 'not-logged-in' }; + const headers = { Accept: 'application/json', Authorization: token }; + const sessionId = localStorage.getItem('x-session-id'); + 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 - }); - } + const res = await fetch(API, { credentials: 'include', headers }); + if (res.status === 401 || res.status === 403) return { lastError: 'not-logged-in' }; + if (!res.ok) return { lastError: 'amis-error' }; + const data = await res.json(); + if (!data || !data.student_grades || typeof data.student_grades !== 'object') return { lastError: 'amis-error' }; + return { gradesData: slim(data), fetchedAt: Date.now(), lastError: null }; } catch (e) { - isFetching = false; + return { lastError: 'amis-error' }; } } - - // 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(); + + // Resolves to { ok, lastError, fetchedAt, skipped? }. + async function refresh(force) { + if (!force) { + const { fetchedAt } = await chrome.storage.local.get('fetchedAt'); + if (fetchedAt && Date.now() - fetchedAt < MAX_AGE_MS) return { ok: true, skipped: true, lastError: null, fetchedAt }; + } + if (!inFlight) { + inFlight = fetchGrades() + .then(async result => { + await chrome.storage.local.set(result); + return { ok: !result.lastError, lastError: result.lastError, fetchedAt: result.fetchedAt }; + }) + .finally(() => { inFlight = null; }); } + return inFlight; + } + + // The popup's Refresh button sends { type: 'FETCH_GRADES', force: true }. + chrome.runtime.onMessage.addListener((message, sender, sendResponse) => { + if (!message || message.type !== 'FETCH_GRADES') return false; + refresh(!!message.force).then(sendResponse); 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); - } + refresh(false); })(); diff --git a/extension/src/curriculum.js b/extension/src/curriculum.js index 5c1c317..dff7b76 100644 --- a/extension/src/curriculum.js +++ b/extension/src/curriculum.js @@ -10,6 +10,7 @@ const KNOWN_GE_CODES = new Set([ 'COMM 10', 'ETHICS 1', 'HIST 1', + 'HIST 1', 'HUM 3', 'KAS 1', 'KAS 1/HIST 1', @@ -30,7 +31,8 @@ function normalizeCourseCode(code) { function isNonGwaCourseCode(courseCode) { const code = normalizeCourseCode(courseCode); - return NON_GWA_PREFIXES.some(prefix => code.startsWith(prefix)); + // Word boundary so PEd and similar majors are not caught by PE. + return NON_GWA_PREFIXES.some(prefix => new RegExp(`^${prefix}\\b`).test(code)); } function isGECourse(courseCode, courseTitle) { @@ -76,7 +78,7 @@ function genericRequirementCourses(program) { }); } - const existingHK = countExisting(c => normalizeCourseCode(c.code).startsWith('HK') || normalizeCourseCode(c.code).startsWith('PE')); + const existingHK = countExisting(c => /^(HK|PE)\b/.test(normalizeCourseCode(c.code))); const hkRequired = program?.hkCoursesRequired == null ? 2 : program.hkCoursesRequired; for (let i = existingHK; i < hkRequired; i++) { const slot = defaultSlot(i); @@ -172,7 +174,7 @@ function getCompletedRequirementSlotCodes(completedCourses, program) { (program?.majorCourses || []).forEach(course => { const code = normalizeCourseCode(course.code); if (isGECourse(code, course.title)) existing.ge.add(code); - else if (code.startsWith('HK') || code.startsWith('PE')) existing.hk.add(code); + else if (/^(HK|PE)\b/.test(code)) existing.hk.add(code); else if (code.startsWith('NSTP')) existing.nstp.add(code); }); @@ -182,7 +184,7 @@ function getCompletedRequirementSlotCodes(completedCourses, program) { if (isGECourse(code, title)) { counts.ge++; if (existing.ge.has(code)) existingDone.ge++; - } else if (code.startsWith('HK') || code.startsWith('PE')) { + } else if (/^(HK|PE)\b/.test(code)) { counts.hk++; if (existing.hk.has(code)) existingDone.hk++; } else if (code.startsWith('NSTP')) { @@ -534,20 +536,22 @@ const UPLB_PROGRAMS = { "college": "CAS", "collegeName": "College of Arts and Sciences", "available": true, - "totalUnitsRequired": 129, + "totalUnitsRequired": 131, "geCoursesRequired": 9, + "hkCoursesRequired": 4, + "nstpCoursesRequired": 2, "tracks": { "thesis": { "name": "Thesis Track", "code": "MATH 200", - "freeElectiveUnits": 9, - "majorElectiveUnits": 6 + "freeElectiveUnits": 6, + "majorElectiveUnits": 9 }, "sp": { "name": "Special Problem Track", "code": "MATH 190", - "freeElectiveUnits": 9, - "majorElectiveUnits": 9 + "freeElectiveUnits": 6, + "majorElectiveUnits": 12 } }, "defaultTrack": "sp", @@ -563,7 +567,7 @@ const UPLB_PROGRAMS = { { "code": "MATH 36", "title": "Mathematical Analysis I", - "units": 3, + "units": 5, "year": 1, "sem": "1", "prereqs": [] @@ -577,8 +581,8 @@ const UPLB_PROGRAMS = { "prereqs": [] }, { - "code": "KAS 1/HIST 1", - "title": "Kasaysayan ng Pilipinas/PhilippineHistory", + "code": "KAS 1", + "title": "Kasaysayan ng Pilipinas / Philippine History", "units": 3, "year": 1, "sem": "1", @@ -593,46 +597,52 @@ const UPLB_PROGRAMS = { "prereqs": [] }, { - "code": "HK 11", - "title": "Wellness and Basic Injury Management", + "code": "MATH 37", + "title": "Mathematical Analysis II", "units": 3, "year": 1, - "sem": "1", + "sem": "2", "prereqs": [] }, { - "code": "MATH 37", - "title": "Mathematical Analysis II", + "code": "STAT 101", + "title": "Statistical Methods", "units": 3, "year": 1, "sem": "2", "prereqs": [] }, { - "code": "HK", - "title": "12/13. Human Kinetics Activities/Advanced Hu Activities 16", - "units": 3, + "code": "BIO 11.1", + "title": "Investigative Biology Laboratory", + "units": 2, + "year": 1, + "sem": "2", + "prereqs": [] + }, + { + "code": "CHEM 18.1", + "title": "University Chemistry Laboratory", + "units": 2, "year": 1, "sem": "2", "prereqs": [] }, { - "code": "3", - "title": "3 3 2 2 3 3 man Kinetics (2) 18", + "code": "ARTS 1", + "title": "Critical Perspectives in the Arts", "units": 3, "year": 1, - "sem": "midyear", + "sem": "2", "prereqs": [] }, { "code": "MATH 38", "title": "Mathematical Analysis III", - "units": 3, + "units": 5, "year": 2, "sem": "1", - "prereqs": [ - "MATH 37" - ] + "prereqs": [] }, { "code": "MATH 101", @@ -640,15 +650,12 @@ const UPLB_PROGRAMS = { "units": 3, "year": 2, "sem": "1", - "prereqs": [ - "MATH 27", - "MATH 36" - ] + "prereqs": [] }, { "code": "PHYS 51", "title": "Elements of Physics", - "units": 3, + "units": 4, "year": 2, "sem": "1", "prereqs": [] @@ -656,65 +663,49 @@ const UPLB_PROGRAMS = { { "code": "PHYS 51.1", "title": "Elements of Physics Laboratory", - "units": 3, - "year": 2, - "sem": "1", - "prereqs": [] - }, - { - "code": "GE", - "title": "Elective", - "units": 3, - "year": 2, - "sem": "1", - "prereqs": [] - }, - { - "code": "HK 12", - "title": "Unknown Title", - "units": 3, + "units": 1, "year": 2, "sem": "1", "prereqs": [] }, { - "code": "NSTP 1", - "title": "National Training Service Program I", + "code": "MATH 103", + "title": "Elementary Theory of Numbers", "units": 3, "year": 2, - "sem": "1", + "sem": "2", "prereqs": [] }, { - "code": "MATH 103", - "title": "Elementary Theory of Numbers", + "code": "MATH 138", + "title": "Introductory Topology", "units": 3, "year": 2, "sem": "2", "prereqs": [] }, { - "code": "HK", - "title": "12/13. Human Kinetics Activities/Advanced (3) Activities", + "code": "MATH 141", + "title": "Introductory Combinatorics", "units": 3, "year": 2, "sem": "2", "prereqs": [] }, { - "code": "NSTP 2", - "title": "National Training Service Program II 16", + "code": "AMAT 152", + "title": "Fundamentals of Mathematical Computing", "units": 3, "year": 2, "sem": "2", "prereqs": [] }, { - "code": "3", - "title": "3 3 3 ing 3 3 Human Kinetics (2) (3) 18", + "code": "STS 1", + "title": "Science, Technology, and Society", "units": 3, "year": 2, - "sem": "midyear", + "sem": "2", "prereqs": [] }, { @@ -723,9 +714,7 @@ const UPLB_PROGRAMS = { "units": 3, "year": 3, "sem": "1", - "prereqs": [ - "MATH 101" - ] + "prereqs": [] }, { "code": "MATH 155", @@ -733,10 +722,7 @@ const UPLB_PROGRAMS = { "units": 3, "year": 3, "sem": "1", - "prereqs": [ - "MATH 38", - "MATH 101" - ] + "prereqs": [] }, { "code": "MATH 195", @@ -744,10 +730,7 @@ const UPLB_PROGRAMS = { "units": 3, "year": 3, "sem": "1", - "prereqs": [ - "MATH 101", - "MATH 111" - ] + "prereqs": [] }, { "code": "PI 10", @@ -759,12 +742,21 @@ const UPLB_PROGRAMS = { }, { "code": "COMM 10", - "title": "Critical Perspectives in Communication Major Elective", + "title": "Critical Perspectives in Communication", "units": 3, "year": 3, "sem": "1", "prereqs": [] }, + { + "code": "MAJ 1", + "title": "Major Elective", + "units": 3, + "year": 3, + "sem": "1", + "prereqs": [], + "genericRequirement": "elective" + }, { "code": "MATH 120", "title": "Linear Algebra", @@ -773,6 +765,39 @@ const UPLB_PROGRAMS = { "sem": "2", "prereqs": [] }, + { + "code": "MATH 133", + "title": "Euclidean and Non-Euclidean Geometry", + "units": 3, + "year": 3, + "sem": "2", + "prereqs": [] + }, + { + "code": "MATH 151", + "title": "Ordinary Differential Equations", + "units": 3, + "year": 3, + "sem": "2", + "prereqs": [] + }, + { + "code": "MAJ 2", + "title": "MATH Major Elective", + "units": 3, + "year": 3, + "sem": "2", + "prereqs": [], + "genericRequirement": "elective" + }, + { + "code": "COMA 150", + "title": "Workplace Communication", + "units": 3, + "year": 3, + "sem": "2", + "prereqs": [] + }, { "code": "MATH 198", "title": "Practicum", @@ -781,15 +806,22 @@ const UPLB_PROGRAMS = { "sem": "midyear", "prereqs": [] }, + { + "code": "MATH 200", + "title": "Undergraduate Thesis", + "units": 6, + "year": 4, + "sem": "1", + "prereqs": [], + "track": "thesis" + }, { "code": "MATH 135", "title": "Projective Geometry", "units": 3, "year": 4, "sem": "1", - "prereqs": [ - "MATH 120" - ] + "prereqs": [] }, { "code": "MATH 165", @@ -797,27 +829,24 @@ const UPLB_PROGRAMS = { "units": 3, "year": 4, "sem": "1", - "prereqs": [ - "MATH 155" - ] + "prereqs": [] }, { "code": "MATH 181", - "title": "Introduction to Probability Theory Major Elective", + "title": "Introduction to Probability Theory", "units": 3, "year": 4, "sem": "1", - "prereqs": [ - "MATH 38" - ] + "prereqs": [] }, { - "code": "Elective", - "title": "", + "code": "MAJ 3", + "title": "Major Elective", "units": 3, "year": 4, "sem": "1", - "prereqs": [] + "prereqs": [], + "genericRequirement": "elective" }, { "code": "MATH 190", @@ -825,45 +854,73 @@ const UPLB_PROGRAMS = { "units": 3, "year": 4, "sem": "2", - "prereqs": [] + "prereqs": [], + "track": "sp" }, { - "code": "3", - "title": "3 3 1 3 3 13", + "code": "MATH 192", + "title": "Foundations of Mathematics", "units": 3, "year": 4, - "sem": "midyear", + "sem": "2", + "prereqs": [] + }, + { + "code": "MATH 199", + "title": "Undergraduate Seminar", + "units": 1, + "year": 4, + "sem": "2", "prereqs": [] + }, + { + "code": "MAJ 4", + "title": "Major Elective", + "units": 3, + "year": 4, + "sem": "2", + "prereqs": [], + "track": "sp", + "genericRequirement": "elective" } ], "requiredCodes": [ "MATH 20", "MATH 36", "CHEM 18", - "KAS 1/HIST 1", + "KAS 1", "ETHICS 1", "MATH 37", - "3", + "STAT 101", + "BIO 11.1", + "CHEM 18.1", + "ARTS 1", "MATH 38", "MATH 101", "PHYS 51", "PHYS 51.1", - "GE", "MATH 103", - "3", + "MATH 138", + "MATH 141", + "AMAT 152", + "STS 1", "MATH 111", "MATH 155", "MATH 195", "PI 10", "COMM 10", "MATH 120", + "MATH 133", + "MATH 151", + "COMA 150", "MATH 198", + "MATH 200", "MATH 135", "MATH 165", "MATH 181", - "Elective", "MATH 190", - "3" + "MATH 192", + "MATH 199" ] }, "BSAPMATH": { @@ -874,11 +931,13 @@ const UPLB_PROGRAMS = { "available": true, "totalUnitsRequired": 131, "geCoursesRequired": 9, + "hkCoursesRequired": 4, + "nstpCoursesRequired": 2, "tracks": { "thesis": { "name": "Thesis Track", "code": "AMAT 200", - "freeElectiveUnits": 9, + "freeElectiveUnits": 12, "majorElectiveUnits": 12 }, "sp": { @@ -901,7 +960,7 @@ const UPLB_PROGRAMS = { { "code": "MATH 36", "title": "Mathematical Analysis I", - "units": 3, + "units": 5, "year": 1, "sem": "1", "prereqs": [] @@ -915,8 +974,8 @@ const UPLB_PROGRAMS = { "prereqs": [] }, { - "code": "KAS 1/HIST 1", - "title": "Kasaysayan ng Pilipinas/Philippine History", + "code": "KAS 1", + "title": "Kasaysayan ng Pilipinas / Philippine History", "units": 3, "year": 1, "sem": "1", @@ -931,35 +990,43 @@ const UPLB_PROGRAMS = { "prereqs": [] }, { - "code": "HK 11", - "title": "Wellness and Basic Injury Management", + "code": "MATH 37", + "title": "Mathematical Analysis II", "units": 3, "year": 1, - "sem": "1", + "sem": "2", "prereqs": [] }, { - "code": "MATH 37", - "title": "Mathematical Analysis II", + "code": "STAT 101", + "title": "Statistical Methods", "units": 3, "year": 1, "sem": "2", "prereqs": [] }, { - "code": "HK 12", - "title": "Unknown Title", - "units": 3, + "code": "BIO 11.1", + "title": "Investigative Biology Laboratory", + "units": 2, + "year": 1, + "sem": "2", + "prereqs": [] + }, + { + "code": "CHEM 18.1", + "title": "University Chemistry Laboratory", + "units": 2, "year": 1, "sem": "2", "prereqs": [] }, { - "code": "3", - "title": "3 3 2 2 3 3 (2) 16", + "code": "ARTS 1", + "title": "Critical Perspectives in the Arts", "units": 3, "year": 1, - "sem": "midyear", + "sem": "2", "prereqs": [] }, { @@ -968,19 +1035,15 @@ const UPLB_PROGRAMS = { "units": 3, "year": 2, "sem": "1", - "prereqs": [ - "AMAT 105" - ] + "prereqs": [] }, { "code": "MATH 38", "title": "Mathematical Analysis III", - "units": 3, + "units": 5, "year": 2, "sem": "1", - "prereqs": [ - "MATH 37" - ] + "prereqs": [] }, { "code": "MATH 101", @@ -988,15 +1051,12 @@ const UPLB_PROGRAMS = { "units": 3, "year": 2, "sem": "1", - "prereqs": [ - "MATH 27", - "MATH 36" - ] + "prereqs": [] }, { "code": "PHYS 51", "title": "Elements of Physics", - "units": 3, + "units": 4, "year": 2, "sem": "1", "prereqs": [] @@ -1004,57 +1064,49 @@ const UPLB_PROGRAMS = { { "code": "PHYS 51.1", "title": "Elements of Physics Laboratory", - "units": 3, - "year": 2, - "sem": "1", - "prereqs": [] - }, - { - "code": "HK 12", - "title": "Unknown Title", - "units": 3, + "units": 1, "year": 2, "sem": "1", "prereqs": [] }, { - "code": "NSTP 1", - "title": "National Training Service Program I", + "code": "AMAT 105", + "title": "Matrices and Applications", "units": 3, "year": 2, - "sem": "1", + "sem": "2", "prereqs": [] }, { - "code": "AMAT 105", - "title": "Matrices and Applications", + "code": "AMAT 112", + "title": "Introduction to Mathematical Optimization", "units": 3, "year": 2, "sem": "2", "prereqs": [] }, { - "code": "GE", - "title": "Elective", + "code": "AMAT 152", + "title": "Fundamentals of Mathematical Computing", "units": 3, "year": 2, "sem": "2", "prereqs": [] }, { - "code": "HK 12", - "title": "Unknown Title", + "code": "AMAT 170", + "title": "Theory of Interest", "units": 3, "year": 2, "sem": "2", "prereqs": [] }, { - "code": "3", - "title": "3 (2) (3) 18", + "code": "STS 1", + "title": "Science, Technology, and Society", "units": 3, "year": 2, - "sem": "midyear", + "sem": "2", "prereqs": [] }, { @@ -1063,10 +1115,7 @@ const UPLB_PROGRAMS = { "units": 3, "year": 3, "sem": "1", - "prereqs": [ - "MATH 38", - "MATH 101" - ] + "prereqs": [] }, { "code": "MATH 174", @@ -1074,10 +1123,7 @@ const UPLB_PROGRAMS = { "units": 3, "year": 3, "sem": "1", - "prereqs": [ - "MATH 38", - "AMAT 152" - ] + "prereqs": [] }, { "code": "MATH 181", @@ -1085,17 +1131,16 @@ const UPLB_PROGRAMS = { "units": 3, "year": 3, "sem": "1", - "prereqs": [ - "MATH 38" - ] + "prereqs": [] }, { - "code": "Elective", - "title": "(AMAT/MATH)", + "code": "MAJ 1", + "title": "Elective (AMAT/MATH)", "units": 3, "year": 3, "sem": "1", - "prereqs": [] + "prereqs": [], + "genericRequirement": "elective" }, { "code": "COMM 10", @@ -1106,24 +1151,41 @@ const UPLB_PROGRAMS = { "prereqs": [] }, { - "code": "GE", - "title": "Elective", + "code": "MATH 155", + "title": "Advanced Calculus I", "units": 3, "year": 3, - "sem": "1", + "sem": "2", "prereqs": [] }, { - "code": "MATH 155", - "title": "Advanced Calculus I", + "code": "MATH 175", + "title": "Numerical Analysis II", + "units": 3, + "year": 3, + "sem": "2", + "prereqs": [] + }, + { + "code": "MATH 195", + "title": "Research Methods in Mathematics", "units": 3, "year": 3, "sem": "2", "prereqs": [] }, + { + "code": "MAJ 2", + "title": "Elective (AMAT/MATH)", + "units": 3, + "year": 3, + "sem": "2", + "prereqs": [], + "genericRequirement": "elective" + }, { "code": "COMA 150", - "title": "Workplace Communication 3 18", + "title": "Workplace Communication", "units": 3, "year": 3, "sem": "2", @@ -1138,20 +1200,22 @@ const UPLB_PROGRAMS = { "prereqs": [] }, { - "code": "Elective", - "title": "(AMAT/MATH)", - "units": 3, + "code": "AMAT 200", + "title": "Undergraduate Thesis", + "units": 6, "year": 4, "sem": "1", - "prereqs": [] + "prereqs": [], + "track": "thesis" }, { - "code": "Elective", - "title": "(AMAT/MATH)", + "code": "MAJ 3", + "title": "Elective (AMAT/MATH)", "units": 3, "year": 4, "sem": "1", - "prereqs": [] + "prereqs": [], + "genericRequirement": "elective" }, { "code": "PI 10", @@ -1162,20 +1226,14 @@ const UPLB_PROGRAMS = { "prereqs": [] }, { - "code": "Elective", - "title": "", - "units": 3, - "year": 4, - "sem": "1", - "prereqs": [] - }, - { - "code": "Elective", - "title": "", + "code": "MAJ 4", + "title": "Elective (AMAT/MATH)", "units": 3, "year": 4, "sem": "1", - "prereqs": [] + "prereqs": [], + "track": "sp", + "genericRequirement": "elective" }, { "code": "AMAT 190", @@ -1183,49 +1241,61 @@ const UPLB_PROGRAMS = { "units": 3, "year": 4, "sem": "2", - "prereqs": [] + "prereqs": [], + "track": "sp" }, { - "code": "3", - "title": "3 1 3 3 3 13", - "units": 3, + "code": "AMAT 199", + "title": "Undergraduate Seminar", + "units": 1, "year": 4, - "sem": "midyear", + "sem": "2", "prereqs": [] + }, + { + "code": "MAJ 5", + "title": "Elective (AMAT/MATH)", + "units": 3, + "year": 4, + "sem": "2", + "prereqs": [], + "genericRequirement": "elective" } ], "requiredCodes": [ "AMAT 19", "MATH 36", "CHEM 18", - "KAS 1/HIST 1", + "KAS 1", "ETHICS 1", "MATH 37", - "3", + "STAT 101", + "BIO 11.1", + "CHEM 18.1", + "ARTS 1", "AMAT 110", "MATH 38", "MATH 101", "PHYS 51", "PHYS 51.1", "AMAT 105", - "GE", - "3", + "AMAT 112", + "AMAT 152", + "AMAT 170", + "STS 1", "MATH 151", "MATH 174", "MATH 181", - "Elective", "COMM 10", - "GE", "MATH 155", + "MATH 175", + "MATH 195", "COMA 150", "AMAT 198", - "Elective", - "Elective", + "AMAT 200", "PI 10", - "Elective", - "Elective", "AMAT 190", - "3" + "AMAT 199" ] }, "BSSTAT": { @@ -1236,20 +1306,20 @@ const UPLB_PROGRAMS = { "available": true, "totalUnitsRequired": 143, "geCoursesRequired": 9, - "tracks": null, - "defaultTrack": null, + "hkCoursesRequired": 4, + "nstpCoursesRequired": 2, "majorCourses": [ { "code": "ETHICS 1", - "title": "Ethics and Moral Reasoning in Everyda Life", + "title": "Ethics and Moral Reasoning in Everyday Life", "units": 3, "year": 1, "sem": "1", "prereqs": [] }, { - "code": "KAS 1/HIST 1", - "title": "Kasaysayan ng Pilipinas/Philippin History", + "code": "KAS 1", + "title": "Kasaysayan ng Pilipinas / Philippine History", "units": 3, "year": 1, "sem": "1", @@ -1280,32 +1350,32 @@ const UPLB_PROGRAMS = { "prereqs": [] }, { - "code": "HK 11", - "title": "Wellness and Basic Injury Management", + "code": "ARTS 1", + "title": "Critical Perspectives in the Arts", "units": 3, "year": 1, - "sem": "1", + "sem": "2", "prereqs": [] }, { - "code": "y", - "title": "y 3 ARTS 1. Critical Perspectives in the Arts e 3", + "code": "MATH 28", + "title": "Analytic Geometry and Calculus III", "units": 3, "year": 1, "sem": "2", "prereqs": [] }, { - "code": "GE", - "title": "Elective", + "code": "CMSC 12", + "title": "Foundations of Computer Science", "units": 3, "year": 1, "sem": "2", "prereqs": [] }, { - "code": "HK 12", - "title": "Unknown Title", + "code": "STAT 162", + "title": "Experimental Designs", "units": 3, "year": 1, "sem": "2", @@ -1313,8 +1383,16 @@ const UPLB_PROGRAMS = { }, { "code": "STAT 135", - "title": "Logic and Matrix Algebra", - "units": 6, + "title": "Logic and Matrix Algebra in Statistics", + "units": 3, + "year": 1, + "sem": "midyear", + "prereqs": [] + }, + { + "code": "STAT 182", + "title": "Statistical Packages", + "units": 3, "year": 1, "sem": "midyear", "prereqs": [] @@ -1333,9 +1411,7 @@ const UPLB_PROGRAMS = { "units": 3, "year": 2, "sem": "1", - "prereqs": [ - "CMSC 12" - ] + "prereqs": [] }, { "code": "ECON 11", @@ -1351,10 +1427,7 @@ const UPLB_PROGRAMS = { "units": 3, "year": 2, "sem": "1", - "prereqs": [ - "MATH 28", - "STAT 101" - ] + "prereqs": [] }, { "code": "STAT 168", @@ -1362,40 +1435,46 @@ const UPLB_PROGRAMS = { "units": 3, "year": 2, "sem": "1", - "prereqs": [ - "STAT 162" - ] + "prereqs": [] }, { - "code": "HK 12", - "title": "Unknown Title", + "code": "MATH 182", + "title": "Introduction to Stochastic Processes", "units": 3, "year": 2, - "sem": "1", + "sem": "2", "prereqs": [] }, { - "code": "MATH 182", - "title": "Introduction to Stochastic Processes", + "code": "CMSC 22", + "title": "Object-Oriented Programming", "units": 3, "year": 2, "sem": "2", "prereqs": [] }, { - "code": "HK 12", - "title": "Unknown Title", + "code": "ABME 10", + "title": "Foundations of Entrepreneurship", "units": 3, "year": 2, "sem": "2", "prereqs": [] }, { - "code": "GE", - "title": "Elective", - "units": 6, + "code": "STAT 145", + "title": "Introductory Statistical Theory II", + "units": 3, "year": 2, - "sem": "midyear", + "sem": "2", + "prereqs": [] + }, + { + "code": "STAT 163", + "title": "Survey Designs", + "units": 3, + "year": 2, + "sem": "2", "prereqs": [] }, { @@ -1407,12 +1486,13 @@ const UPLB_PROGRAMS = { "prereqs": [] }, { - "code": "Elective", - "title": "Track", + "code": "MAJ 1", + "title": "Elective Track", "units": 3, "year": 3, "sem": "1", - "prereqs": [] + "prereqs": [], + "genericRequirement": "elective" }, { "code": "STAT 146", @@ -1420,9 +1500,7 @@ const UPLB_PROGRAMS = { "units": 3, "year": 3, "sem": "1", - "prereqs": [ - "STAT 145" - ] + "prereqs": [] }, { "code": "STAT 173", @@ -1430,9 +1508,7 @@ const UPLB_PROGRAMS = { "units": 3, "year": 3, "sem": "1", - "prereqs": [ - "STAT 163" - ] + "prereqs": [] }, { "code": "STAT 175", @@ -1440,9 +1516,7 @@ const UPLB_PROGRAMS = { "units": 3, "year": 3, "sem": "1", - "prereqs": [ - "STAT 151" - ] + "prereqs": [] }, { "code": "STAT 181", @@ -1450,38 +1524,44 @@ const UPLB_PROGRAMS = { "units": 3, "year": 3, "sem": "1", - "prereqs": [ - "STAT 145", - "STAT 182" - ] + "prereqs": [] }, { - "code": "NSTP 2", - "title": "National Training Service Program II", + "code": "MAJ 2", + "title": "Elective Track", "units": 3, "year": 3, - "sem": "1", + "sem": "2", + "prereqs": [], + "genericRequirement": "elective" + }, + { + "code": "CMSC 127", + "title": "File Processing and Database Systems", + "units": 3, + "year": 3, + "sem": "2", "prereqs": [] }, { - "code": "3", - "title": "3", + "code": "STAT 147", + "title": "Introduction to the Theory of Nonparametric Statistics", "units": 3, "year": 3, "sem": "2", "prereqs": [] }, { - "code": "Elective", - "title": "Track 3 CMSC 127. File Processing and Database Systems", + "code": "STAT 151", + "title": "Applied Regression and Correlation", "units": 3, "year": 3, "sem": "2", "prereqs": [] }, { - "code": "STAT 147", - "title": "Introduction to the Theory of", + "code": "STAT 156", + "title": "Introductory Time Series Analysis", "units": 3, "year": 3, "sem": "2", @@ -1496,12 +1576,13 @@ const UPLB_PROGRAMS = { "prereqs": [] }, { - "code": "Elective", - "title": "Track", + "code": "MAJ 3", + "title": "Elective Track", "units": 3, "year": 4, "sem": "1", - "prereqs": [] + "prereqs": [], + "genericRequirement": "elective" }, { "code": "ENG 10", @@ -1525,9 +1606,7 @@ const UPLB_PROGRAMS = { "units": 3, "year": 4, "sem": "1", - "prereqs": [ - "STAT 151" - ] + "prereqs": [] }, { "code": "STAT 190", @@ -1551,14 +1630,12 @@ const UPLB_PROGRAMS = { "units": 3, "year": 4, "sem": "1", - "prereqs": [ - "STAT 145" - ] + "prereqs": [] }, { "code": "STAT 192.1", "title": "Statistical Consulting Laboratory", - "units": 3, + "units": 1, "year": 4, "sem": "1", "prereqs": [] @@ -1572,41 +1649,70 @@ const UPLB_PROGRAMS = { "prereqs": [] }, { - "code": "3", - "title": "3 3 3 3 2 1 15", + "code": "STAT 157", + "title": "Financial Risk Analysis", "units": 3, "year": 4, - "sem": "midyear", + "sem": "2", + "prereqs": [] + }, + { + "code": "STAT 167", + "title": "Statistical Quality Control", + "units": 3, + "year": 4, + "sem": "2", + "prereqs": [] + }, + { + "code": "STAT 183", + "title": "Introductory Data Analytics", + "units": 3, + "year": 4, + "sem": "2", + "prereqs": [] + }, + { + "code": "STAT 199", + "title": "Undergraduate Seminar", + "units": 1, + "year": 4, + "sem": "2", "prereqs": [] } ], "requiredCodes": [ "ETHICS 1", - "KAS 1/HIST 1", + "KAS 1", "MATH 27", "BIO 30", "STAT 101", - "y", - "GE", + "ARTS 1", + "MATH 28", + "CMSC 12", + "STAT 162", "STAT 135", + "STAT 182", "STS 1", "CMSC 21", "ECON 11", "STAT 144", "STAT 168", "MATH 182", - "GE", + "CMSC 22", + "ABME 10", + "STAT 145", + "STAT 163", "COMM 10", - "Elective", "STAT 146", "STAT 173", "STAT 175", "STAT 181", - "3", - "Elective", + "CMSC 127", "STAT 147", + "STAT 151", + "STAT 156", "STAT 198", - "Elective", "ENG 10", "STAT 148", "STAT 165", @@ -1615,7 +1721,10 @@ const UPLB_PROGRAMS = { "STAT 174", "STAT 192.1", "PI 10", - "3" + "STAT 157", + "STAT 167", + "STAT 183", + "STAT 199" ] }, "BSMST": { @@ -1623,7 +1732,7 @@ const UPLB_PROGRAMS = { "name": "BS Mathematics and Science Teaching", "college": "CAS", "collegeName": "College of Arts and Sciences", - "available": true, + "available": false, "totalUnitsRequired": 150, "geCoursesRequired": 9, "tracks": null, @@ -1788,8 +1897,8 @@ const UPLB_PROGRAMS = { "available": true, "totalUnitsRequired": 143, "geCoursesRequired": 9, - "tracks": null, - "defaultTrack": null, + "hkCoursesRequired": 4, + "nstpCoursesRequired": 2, "majorCourses": [ { "code": "CHEM 18", @@ -1809,15 +1918,15 @@ const UPLB_PROGRAMS = { }, { "code": "MCB 11", - "title": "Biology and Applications of Microorganis", + "title": "Biology and Applications of Microorganisms", "units": 3, "year": 1, "sem": "1", "prereqs": [] }, { - "code": "KAS 1/HIST 1", - "title": "Kasaysayan ng Pilipinas/Philippine History", + "code": "KAS 1", + "title": "Kasaysayan ng Pilipinas / Philippine History", "units": 3, "year": 1, "sem": "1", @@ -1840,25 +1949,33 @@ const UPLB_PROGRAMS = { "prereqs": [] }, { - "code": "HK 11", - "title": "Wellness and Basic Injury Management", - "units": 3, + "code": "BIO 11.1", + "title": "Investigative Biology Laboratory", + "units": 2, "year": 1, - "sem": "1", + "sem": "2", "prereqs": [] }, { - "code": "BIO 11.1", - "title": "Investigative Biology Laboratory", + "code": "CHEM 18.1", + "title": "University Chemistry Laboratory", + "units": 2, + "year": 1, + "sem": "2", + "prereqs": [] + }, + { + "code": "BIO 30", + "title": "Genetics", "units": 3, "year": 1, "sem": "2", "prereqs": [] }, { - "code": "HK 12", - "title": "Unknown Title", - "units": 16, + "code": "PI 10", + "title": "The Life and Works of Jose Rizal", + "units": 3, "year": 1, "sem": "2", "prereqs": [] @@ -1873,8 +1990,8 @@ const UPLB_PROGRAMS = { }, { "code": "CHEM 40.1", - "title": "Basic Organic Chemistry Laboratory 1 5", - "units": 3, + "title": "Basic Organic Chemistry Laboratory", + "units": 1, "year": 1, "sem": "midyear", "prereqs": [] @@ -1882,7 +1999,7 @@ const UPLB_PROGRAMS = { { "code": "BIO 14", "title": "Biodiversity", - "units": 3, + "units": 5, "year": 2, "sem": "1", "prereqs": [] @@ -1914,54 +2031,54 @@ const UPLB_PROGRAMS = { { "code": "BIO 150", "title": "Ecology", - "units": 3, + "units": 4, "year": 2, "sem": "1", "prereqs": [] }, { - "code": "HK 12", - "title": "Unknown Title", - "units": 3, + "code": "PHYS 51", + "title": "Elements of Physics", + "units": 4, "year": 2, - "sem": "1", + "sem": "2", "prereqs": [] }, { - "code": "NSTP 1", - "title": "National Training Service Program I", - "units": 3, + "code": "PHYS 51.1", + "title": "Elements of Physics Laboratory", + "units": 1, "year": 2, - "sem": "1", + "sem": "2", "prereqs": [] }, { - "code": "PHYS 51", - "title": "Elements of Physics", + "code": "BIO 101", + "title": "Introductory Molecular Biology", "units": 3, "year": 2, "sem": "2", "prereqs": [] }, { - "code": "GE", - "title": "Elective (3) ABME 10. Foundations of Entrepreneurship", - "units": 3, + "code": "CHEM 160.1", + "title": "Introductory Biochemistry Laboratory", + "units": 2, "year": 2, "sem": "2", "prereqs": [] }, { - "code": "HK 12", - "title": "Unknown Title", + "code": "BIO 140", + "title": "Evolutionary Biology", "units": 3, "year": 2, "sem": "2", "prereqs": [] }, { - "code": "NSTP 2", - "title": "National Training Service Program II 19", + "code": "ABME 10", + "title": "Foundations of Entrepreneurship", "units": 3, "year": 2, "sem": "2", @@ -1993,12 +2110,30 @@ const UPLB_PROGRAMS = { }, { "code": "COMM 10", - "title": "Critical Perspectives in Communication Major Major", + "title": "Critical Perspectives in Communication", "units": 3, "year": 3, "sem": "1", "prereqs": [] }, + { + "code": "MAJ 1", + "title": "Major", + "units": 3, + "year": 3, + "sem": "1", + "prereqs": [], + "genericRequirement": "elective" + }, + { + "code": "MAJ 2", + "title": "Major", + "units": 3, + "year": 3, + "sem": "1", + "prereqs": [], + "genericRequirement": "elective" + }, { "code": "BIO 195", "title": "Research Methods in the Biological Sciences", @@ -2007,17 +2142,52 @@ const UPLB_PROGRAMS = { "sem": "2", "prereqs": [] }, + { + "code": "BIO 127", + "title": "Developmental Biology", + "units": 3, + "year": 3, + "sem": "2", + "prereqs": [] + }, { "code": "ETHICS 1", - "title": "Ethics and Moral Reasoning in Everyday", - "units": 18, + "title": "Ethics and Moral Reasoning in Everyday Life", + "units": 3, "year": 3, "sem": "2", "prereqs": [] }, + { + "code": "MAJ 3", + "title": "Major", + "units": 3, + "year": 3, + "sem": "2", + "prereqs": [], + "genericRequirement": "elective" + }, + { + "code": "MAJ 4", + "title": "Major", + "units": 3, + "year": 3, + "sem": "2", + "prereqs": [], + "genericRequirement": "elective" + }, + { + "code": "FE 1", + "title": "Free Elective", + "units": 3, + "year": 3, + "sem": "2", + "prereqs": [], + "genericRequirement": "elective" + }, { "code": "BIO 198", - "title": "Practicum 3", + "title": "Practicum", "units": 3, "year": 3, "sem": "midyear", @@ -2033,45 +2203,86 @@ const UPLB_PROGRAMS = { }, { "code": "BIO 199", - "title": "Undergraduate Seminar Major Major Major", - "units": 3, + "title": "Undergraduate Seminar", + "units": 1, "year": 4, "sem": "1", "prereqs": [] }, { - "code": "Elective", - "title": "", + "code": "MAJ 5", + "title": "Major", "units": 3, "year": 4, "sem": "1", - "prereqs": [] + "prereqs": [], + "genericRequirement": "elective" + }, + { + "code": "MAJ 6", + "title": "Major", + "units": 3, + "year": 4, + "sem": "1", + "prereqs": [], + "genericRequirement": "elective" + }, + { + "code": "MAJ 7", + "title": "Major", + "units": 3, + "year": 4, + "sem": "1", + "prereqs": [], + "genericRequirement": "elective" + }, + { + "code": "FE 2", + "title": "Free Elective", + "units": 3, + "year": 4, + "sem": "1", + "prereqs": [], + "genericRequirement": "elective" }, { "code": "BIO 200", "title": "Undergraduate Thesis", - "units": 3, + "units": 6, "year": 4, "sem": "1", "prereqs": [] }, { - "code": "Elective", - "title": "", - "units": 9, + "code": "MAJ 8", + "title": "Major", + "units": 3, "year": 4, "sem": "2", - "prereqs": [] + "prereqs": [], + "genericRequirement": "elective" + }, + { + "code": "FE 3", + "title": "Free Elective", + "units": 3, + "year": 4, + "sem": "2", + "prereqs": [], + "genericRequirement": "elective" } ], "requiredCodes": [ "CHEM 18", "MATH 25", "MCB 11", - "KAS 1/HIST 1", + "KAS 1", "ARTS 1", "STS 1", "BIO 11.1", + "CHEM 18.1", + "BIO 30", + "PI 10", "CHEM 40", "CHEM 40.1", "BIO 14", @@ -2080,19 +2291,22 @@ const UPLB_PROGRAMS = { "CHEM 160", "BIO 150", "PHYS 51", - "GE", + "PHYS 51.1", + "BIO 101", + "CHEM 160.1", + "BIO 140", + "ABME 10", "STAT 164", "BIO 120", "BIO 142", "COMM 10", "BIO 195", + "BIO 127", "ETHICS 1", "BIO 198", "COMA 150", "BIO 199", - "Elective", - "BIO 200", - "3 Major" + "BIO 200" ] }, "BSCHEM": { @@ -2103,8 +2317,8 @@ const UPLB_PROGRAMS = { "available": true, "totalUnitsRequired": 142, "geCoursesRequired": 9, - "tracks": null, - "defaultTrack": null, + "hkCoursesRequired": 4, + "nstpCoursesRequired": 2, "majorCourses": [ { "code": "CHEM 18", @@ -2147,57 +2361,68 @@ const UPLB_PROGRAMS = { "prereqs": [] }, { - "code": "KAS 1/HIST 1", - "title": "Kasaysayan ng Pilipinas/Philippine Histo", + "code": "KAS 1", + "title": "Kasaysayan ng Pilipinas / Philippine History", "units": 3, "year": 1, "sem": "1", "prereqs": [] }, { - "code": "HK 11", - "title": "Wellness and Basic Injury Management", + "code": "CHEM 19", + "title": "Chemical Structure and Properties", "units": 3, "year": 1, - "sem": "1", + "sem": "2", "prereqs": [] }, { - "code": "CHEM 19", - "title": "Chemical Structure and Properties", + "code": "CHEM 18.1", + "title": "University Chemistry Laboratory", + "units": 2, + "year": 1, + "sem": "2", + "prereqs": [] + }, + { + "code": "MATH 28", + "title": "Analytic Geometry and Calculus III", "units": 3, "year": 1, "sem": "2", "prereqs": [] }, { - "code": "HK 12", - "title": "Unknown Title", - "units": 16, + "code": "PHYS 71", + "title": "University Physics I", + "units": 4, + "year": 1, + "sem": "2", + "prereqs": [] + }, + { + "code": "PHYS 71.1", + "title": "University Physics I Laboratory", + "units": 1, "year": 1, "sem": "2", "prereqs": [] }, { "code": "CHEM 32", - "title": "Quantitative Inorganic Analysis 3", + "title": "Quantitative Inorganic Analysis", "units": 3, "year": 1, "sem": "midyear", - "prereqs": [ - "CHEM 18", - "CHEM 18.1" - ] + "prereqs": [] }, { "code": "CHEM 32.1", - "title": "Quantitative Inorganic Analysis 2 Laboratory 5", - "units": 3, + "title": "Quantitative Inorganic Analysis Laboratory", + "units": 2, "year": 1, "sem": "midyear", - "prereqs": [ - "CHEM 32" - ] + "prereqs": [] }, { "code": "CHEM 43", @@ -2205,36 +2430,27 @@ const UPLB_PROGRAMS = { "units": 3, "year": 2, "sem": "1", - "prereqs": [ - "CHEM 19", - "CHEM 32" - ] + "prereqs": [] }, { "code": "PHYS 72", "title": "University Physics II", - "units": 3, + "units": 4, "year": 2, "sem": "1", - "prereqs": [ - "PHYS 71", - "MATH 28" - ] + "prereqs": [] }, { "code": "PHYS 72.1", "title": "University Physics II Laboratory", - "units": 3, + "units": 1, "year": 2, "sem": "1", - "prereqs": [ - "PHYS 71.1", - "PHYS 72" - ] + "prereqs": [] }, { "code": "CMSC 12", - "title": "Unknown Title", + "title": "Foundations of Computer Science", "units": 3, "year": 2, "sem": "1", @@ -2246,47 +2462,43 @@ const UPLB_PROGRAMS = { "units": 3, "year": 2, "sem": "1", - "prereqs": [ - "CHEM 32", - "MATH 28", - "PHYS 72" - ] + "prereqs": [] }, { - "code": "GE", - "title": "Elective", - "units": 3, + "code": "CHEM 43.1", + "title": "Organic Chemistry I Laboratory", + "units": 2, "year": 2, - "sem": "1", + "sem": "2", "prereqs": [] }, { - "code": "HK 12", - "title": "Unknown Title", + "code": "CHEM 112", + "title": "Physical Chemistry II", "units": 3, "year": 2, - "sem": "1", + "sem": "2", "prereqs": [] }, { - "code": "CHEM 43.1", - "title": "Organic Chemistry I Laboratory", + "code": "STAT 162", + "title": "Experimental Designs", "units": 3, "year": 2, "sem": "2", "prereqs": [] }, { - "code": "COMM 10", - "title": "Critical Perspectives in", - "units": 2, + "code": "PI 10", + "title": "The Life and Works of Jose Rizal", + "units": 3, "year": 2, "sem": "2", "prereqs": [] }, { - "code": "NSTP 1", - "title": "National Training Service Program I 17 17", + "code": "COMM 10", + "title": "Critical Perspectives in Communication", "units": 3, "year": 2, "sem": "2", @@ -2298,37 +2510,32 @@ const UPLB_PROGRAMS = { "units": 3, "year": 2, "sem": "midyear", - "prereqs": [ - "CHEM 43" - ] + "prereqs": [] }, { "code": "CHEM 44.1", - "title": "Organic Chemistry II Laboratory 2 5", - "units": 3, + "title": "Organic Chemistry II Laboratory", + "units": 2, "year": 2, "sem": "midyear", - "prereqs": [ - "CHEM 44" - ] + "prereqs": [] }, { "code": "CHEM 111.1", "title": "Physical Chemistry I Laboratory", - "units": 3, + "units": 2, "year": 3, "sem": "1", - "prereqs": [ - "CHEM 111" - ] + "prereqs": [] }, { - "code": "Elective", - "title": "(NON-CHEM)", + "code": "FE 1", + "title": "Free Elective", "units": 3, "year": 3, "sem": "1", - "prereqs": [] + "prereqs": [], + "genericRequirement": "elective" }, { "code": "CHEM 137", @@ -2336,21 +2543,15 @@ const UPLB_PROGRAMS = { "units": 3, "year": 3, "sem": "1", - "prereqs": [ - "CHEM 32", - "CHEM 111" - ] + "prereqs": [] }, { "code": "CHEM 140", "title": "Organic Analysis", - "units": 3, + "units": 4, "year": 3, "sem": "1", - "prereqs": [ - "CHEM 44", - "CHEM 44.1" - ] + "prereqs": [] }, { "code": "CHEM 161A", @@ -2358,38 +2559,67 @@ const UPLB_PROGRAMS = { "units": 3, "year": 3, "sem": "1", - "prereqs": [ - "CHEM 44", - "CHEM 44.1" - ] + "prereqs": [] }, { "code": "CHEM 192", - "title": "Chemical Information, Literature and Communi", + "title": "Chemical Information, Literature and Communication", "units": 3, "year": 3, "sem": "1", "prereqs": [] }, { - "code": "NSTP 2", - "title": "National Training Service Program II", + "code": "CHEM 112.1", + "title": "Physical Chemistry II Laboratory", + "units": 2, + "year": 3, + "sem": "2", + "prereqs": [] + }, + { + "code": "CHEM 115", + "title": "Physical Chemistry III", "units": 3, "year": 3, - "sem": "1", + "sem": "2", "prereqs": [] }, { - "code": "CHEM 112.1", - "title": "Physical Chemistry II Laboratory", - "units": 15, + "code": "CHEM 137.1", + "title": "Modern Analytical Chemistry Laboratory", + "units": 2, + "year": 3, + "sem": "2", + "prereqs": [] + }, + { + "code": "CHEM 161.1", + "title": "General Biochemistry Laboratory", + "units": 2, + "year": 3, + "sem": "2", + "prereqs": [] + }, + { + "code": "CHEM 180", + "title": "Environmental Chemistry", + "units": 3, "year": 3, "sem": "2", "prereqs": [] }, { - "code": "Practicum", - "title": "Practicum 3", + "code": "CHEM 161B", + "title": "Biochemistry II", + "units": 3, + "year": 3, + "sem": "2", + "prereqs": [] + }, + { + "code": "CHEM 198", + "title": "Practicum", "units": 3, "year": 3, "sem": "midyear", @@ -2406,26 +2636,27 @@ const UPLB_PROGRAMS = { { "code": "CHEM 131", "title": "Technical Analysis I (Foods and Feeds)", - "units": 3, + "units": 4, "year": 4, "sem": "1", "prereqs": [] }, { "code": "CHEM 200", - "title": "Undegraduate Thesis", - "units": 3, + "title": "Undergraduate Thesis", + "units": 6, "year": 4, "sem": "1", "prereqs": [] }, { - "code": "Elective", - "title": "(NON-CHEM)", + "code": "FE 2", + "title": "Free Elective", "units": 3, "year": 4, "sem": "1", - "prereqs": [] + "prereqs": [], + "genericRequirement": "elective" }, { "code": "STS 1", @@ -2438,10 +2669,35 @@ const UPLB_PROGRAMS = { { "code": "CHEM 120.1", "title": "Inorganic Chemistry Laboratory", - "units": 12, + "units": 2, + "year": 4, + "sem": "2", + "prereqs": [] + }, + { + "code": "CHEM 171", + "title": "Industrial Chemistry", + "units": 3, + "year": 4, + "sem": "2", + "prereqs": [] + }, + { + "code": "CHEM 199", + "title": "Undergraduate Seminar", + "units": 1, "year": 4, "sem": "2", "prereqs": [] + }, + { + "code": "MAJ 1", + "title": "Elective (CHEM)", + "units": 3, + "year": 4, + "sem": "2", + "prereqs": [], + "genericRequirement": "elective" } ], "requiredCodes": [ @@ -2450,8 +2706,12 @@ const UPLB_PROGRAMS = { "MCB 11", "MATH 27", "ETHICS 1", - "KAS 1/HIST 1", + "KAS 1", "CHEM 19", + "CHEM 18.1", + "MATH 28", + "PHYS 71", + "PHYS 71.1", "CHEM 32", "CHEM 32.1", "CHEM 43", @@ -2459,25 +2719,32 @@ const UPLB_PROGRAMS = { "PHYS 72.1", "CMSC 12", "CHEM 111", - "GE", "CHEM 43.1", + "CHEM 112", + "STAT 162", + "PI 10", "COMM 10", "CHEM 44", "CHEM 44.1", "CHEM 111.1", - "Elective", "CHEM 137", "CHEM 140", "CHEM 161A", "CHEM 192", "CHEM 112.1", - "Practicum", + "CHEM 115", + "CHEM 137.1", + "CHEM 161.1", + "CHEM 180", + "CHEM 161B", + "CHEM 198", "CHEM 120", "CHEM 131", "CHEM 200", - "Elective", "STS 1", - "CHEM 120.1" + "CHEM 120.1", + "CHEM 171", + "CHEM 199" ] }, "BSAPPHY": { @@ -2488,13 +2755,13 @@ const UPLB_PROGRAMS = { "available": true, "totalUnitsRequired": 143, "geCoursesRequired": 9, - "tracks": null, - "defaultTrack": null, + "hkCoursesRequired": 4, + "nstpCoursesRequired": 2, "majorCourses": [ { "code": "PHYS 101", "title": "Newtonian Mechanics", - "units": 3, + "units": 4, "year": 1, "sem": "1", "prereqs": [] @@ -2524,64 +2791,76 @@ const UPLB_PROGRAMS = { "prereqs": [] }, { - "code": "KAS 1/HIST 1", - "title": "Kasaysayan ng Pilipinas/Philippin History", + "code": "KAS 1", + "title": "Kasaysayan ng Pilipinas / Philippine History", "units": 3, "year": 1, "sem": "1", "prereqs": [] }, { - "code": "HK 11", - "title": "Wellness and Basic Injury Management", - "units": 3, + "code": "PHYS 102", + "title": "Electromagnetism", + "units": 4, "year": 1, - "sem": "1", + "sem": "2", "prereqs": [] }, { - "code": "PHYS 102", - "title": "Electromagnetism", + "code": "PHYS 111", + "title": "Mathematical Methods of Physics I", + "units": 4, + "year": 1, + "sem": "2", + "prereqs": [] + }, + { + "code": "MATH 28", + "title": "Analytic Geometry and Calculus III", "units": 3, "year": 1, "sem": "2", "prereqs": [] }, { - "code": "HK 12", - "title": "Unknown Title", + "code": "COMA 150", + "title": "Workplace Communication", "units": 3, "year": 1, "sem": "2", "prereqs": [] }, + { + "code": "CHEM 18.1", + "title": "University Chemistry Laboratory", + "units": 2, + "year": 1, + "sem": "2", + "prereqs": [] + }, { "code": "APHY 10.1", "title": "Programming in Physics", - "units": 3, + "units": 1, "year": 2, "sem": "1", "prereqs": [] }, { "code": "PHYS 103", - "title": "Mechanical Waves, Optics, and Thermod", - "units": 3, + "title": "Mechanical Waves, Optics, and Thermodynamics", + "units": 4, "year": 2, "sem": "1", - "prereqs": [ - "PHYS 102" - ] + "prereqs": [] }, { "code": "PHYS 112", "title": "Mathematical Methods of Physics II", - "units": 3, + "units": 4, "year": 2, "sem": "1", - "prereqs": [ - "PHYS 111" - ] + "prereqs": [] }, { "code": "PHYS 121", @@ -2589,63 +2868,44 @@ const UPLB_PROGRAMS = { "units": 3, "year": 2, "sem": "1", - "prereqs": [ - "PHYS 101", - "PHYS 111" - ] - }, - { - "code": "ETHICS 1", - "title": "Ethics and Moral Reasoning in Everyda Life", - "units": 3, - "year": 2, - "sem": "1", - "prereqs": [] - }, - { - "code": "GE", - "title": "Elective", - "units": 3, - "year": 2, - "sem": "1", "prereqs": [] }, { - "code": "HK 12", - "title": "Unknown Title", + "code": "ETHICS 1", + "title": "Ethics and Moral Reasoning in Everyday Life", "units": 3, "year": 2, "sem": "1", "prereqs": [] }, { - "code": "NSTP 1", - "title": "National Training Service Program I", + "code": "APHY 101", + "title": "Physics in Scientific Instruments", "units": 3, "year": 2, - "sem": "1", + "sem": "2", "prereqs": [] }, { - "code": "APHY 101", - "title": "Physics in Scientific Instruments", - "units": 3, + "code": "PHYS 104", + "title": "Modern Physics", + "units": 4, "year": 2, "sem": "2", "prereqs": [] }, { - "code": "GE", - "title": "Elective", - "units": 3, + "code": "PHYS 113", + "title": "Mathematical Methods of Physics III", + "units": 4, "year": 2, "sem": "2", "prereqs": [] }, { - "code": "HK 12", - "title": "Unknown Title", - "units": 17, + "code": "PHYS 131", + "title": "Electromagnetic Theory I", + "units": 3, "year": 2, "sem": "2", "prereqs": [] @@ -2653,7 +2913,7 @@ const UPLB_PROGRAMS = { { "code": "PHYS 115", "title": "Computational Physics", - "units": 3, + "units": 4, "year": 3, "sem": "1", "prereqs": [] @@ -2664,9 +2924,7 @@ const UPLB_PROGRAMS = { "units": 3, "year": 3, "sem": "1", - "prereqs": [ - "PHYS 121" - ] + "prereqs": [] }, { "code": "PHYS 132", @@ -2674,9 +2932,7 @@ const UPLB_PROGRAMS = { "units": 3, "year": 3, "sem": "1", - "prereqs": [ - "PHYS 131" - ] + "prereqs": [] }, { "code": "PHYS 141", @@ -2684,10 +2940,7 @@ const UPLB_PROGRAMS = { "units": 3, "year": 3, "sem": "1", - "prereqs": [ - "PHYS 104", - "PHYS 113" - ] + "prereqs": [] }, { "code": "PHYS 195", @@ -2708,7 +2961,31 @@ const UPLB_PROGRAMS = { { "code": "APHY 102", "title": "Physics of Electronic Devices", - "units": 2, + "units": 3, + "year": 3, + "sem": "2", + "prereqs": [] + }, + { + "code": "PHYS 151", + "title": "Statistical Physics I", + "units": 3, + "year": 3, + "sem": "2", + "prereqs": [] + }, + { + "code": "PHYS 165", + "title": "Optical Physics", + "units": 3, + "year": 3, + "sem": "2", + "prereqs": [] + }, + { + "code": "PHYS 192.1", + "title": "Experimental Physics I", + "units": 2, "year": 3, "sem": "2", "prereqs": [] @@ -2716,14 +2993,23 @@ const UPLB_PROGRAMS = { { "code": "COMM 10", "title": "Critical Perspectives in Communication", - "units": 17, + "units": 3, "year": 3, "sem": "2", "prereqs": [] }, + { + "code": "MAJ 1", + "title": "Elective (APHY/PHYS)", + "units": 3, + "year": 3, + "sem": "2", + "prereqs": [], + "genericRequirement": "elective" + }, { "code": "APHY 198", - "title": "Practicum 3", + "title": "Practicum", "units": 3, "year": 3, "sem": "midyear", @@ -2732,7 +3018,7 @@ const UPLB_PROGRAMS = { { "code": "APHY 200", "title": "Undergraduate Thesis", - "units": 3, + "units": 6, "year": 4, "sem": "1", "prereqs": [] @@ -2743,49 +3029,85 @@ const UPLB_PROGRAMS = { "units": 3, "year": 4, "sem": "1", - "prereqs": [ - "PHYS 141" - ] + "prereqs": [] }, { - "code": "Elective", - "title": "(APHY/PHYS)", + "code": "MAJ 2", + "title": "Elective (APHY/PHYS)", "units": 3, "year": 4, "sem": "1", - "prereqs": [] + "prereqs": [], + "genericRequirement": "elective" }, { - "code": "Elective", - "title": "(APHY/PHYS)", + "code": "MAJ 3", + "title": "Elective (APHY/PHYS)", "units": 3, "year": 4, "sem": "1", - "prereqs": [] + "prereqs": [], + "genericRequirement": "elective" }, { - "code": "Elective", - "title": "(APHY/PHYS)", + "code": "MAJ 4", + "title": "Elective (APHY/PHYS)", "units": 3, "year": 4, "sem": "1", - "prereqs": [] + "prereqs": [], + "genericRequirement": "elective" }, { - "code": "Elective", - "title": "", + "code": "FE 1", + "title": "Free Elective", "units": 3, "year": 4, "sem": "1", - "prereqs": [] + "prereqs": [], + "genericRequirement": "elective" }, { "code": "APHY 191", "title": "Special Topics", - "units": 16, + "units": 3, + "year": 4, + "sem": "2", + "prereqs": [] + }, + { + "code": "APHY 199", + "title": "Undergraduate Seminar", + "units": 1, + "year": 4, + "sem": "2", + "prereqs": [] + }, + { + "code": "STS 1", + "title": "Science, Technology, and Society", + "units": 3, "year": 4, "sem": "2", "prereqs": [] + }, + { + "code": "MAJ 5", + "title": "Elective (APHY/PHYS)", + "units": 3, + "year": 4, + "sem": "2", + "prereqs": [], + "genericRequirement": "elective" + }, + { + "code": "FE 2", + "title": "Free Elective", + "units": 3, + "year": 4, + "sem": "2", + "prereqs": [], + "genericRequirement": "elective" } ], "requiredCodes": [ @@ -2793,16 +3115,21 @@ const UPLB_PROGRAMS = { "CHEM 18", "MATH 27", "ARTS 1", - "KAS 1/HIST 1", + "KAS 1", "PHYS 102", + "PHYS 111", + "MATH 28", + "COMA 150", + "CHEM 18.1", "APHY 10.1", "PHYS 103", "PHYS 112", "PHYS 121", "ETHICS 1", - "GE", "APHY 101", - "GE", + "PHYS 104", + "PHYS 113", + "PHYS 131", "PHYS 115", "PHYS 122", "PHYS 132", @@ -2810,15 +3137,16 @@ const UPLB_PROGRAMS = { "PHYS 195", "PI 10", "APHY 102", + "PHYS 151", + "PHYS 165", + "PHYS 192.1", "COMM 10", "APHY 198", "APHY 200", "PHYS 142", - "Elective", - "Elective", - "Elective", - "Elective", - "APHY 191" + "APHY 191", + "APHY 199", + "STS 1" ] }, "BACOMM": { @@ -2829,19 +3157,8 @@ const UPLB_PROGRAMS = { "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", + "hkCoursesRequired": 4, + "nstpCoursesRequired": 2, "majorCourses": [ { "code": "ARTS 1", @@ -2860,8 +3177,8 @@ const UPLB_PROGRAMS = { "prereqs": [] }, { - "code": "KAS 1/HIST 1", - "title": "Kasaysayan ng Pilipinas/Philippine History", + "code": "KAS 1", + "title": "Kasaysayan ng Pilipinas / Philippine History", "units": 3, "year": 1, "sem": "1", @@ -2892,43 +3209,43 @@ const UPLB_PROGRAMS = { "prereqs": [] }, { - "code": "HK 11", - "title": "Wellness and Basic Injury Management", + "code": "ETHICS 1", + "title": "Ethics and Moral Reasoning in Everyday Life", "units": 3, "year": 1, - "sem": "1", + "sem": "2", "prereqs": [] }, { - "code": "ETHICS 1", - "title": "Ethics and Moral Reasoning in Everyday", + "code": "STS 1", + "title": "Science, Technology, and Society", "units": 3, "year": 1, "sem": "2", "prereqs": [] }, { - "code": "HK 12", - "title": "Unknown Title", + "code": "COMA 102", + "title": "Language and Culture", "units": 3, "year": 1, "sem": "2", "prereqs": [] }, { - "code": "Life", - "title": "Life 3 3 3 3 3 (2) 18", + "code": "ENG 100", + "title": "Rereading the Literary Canons", "units": 3, "year": 1, - "sem": "midyear", + "sem": "2", "prereqs": [] }, { - "code": "GE", - "title": "Elective", + "code": "SPCM 101", + "title": "Rhetoric", "units": 3, - "year": 2, - "sem": "1", + "year": 1, + "sem": "2", "prereqs": [] }, { @@ -2972,52 +3289,55 @@ const UPLB_PROGRAMS = { "prereqs": [] }, { - "code": "HK 12", - "title": "Unknown Title", + "code": "COMA 103", + "title": "Introduction to Discourse Analysis", "units": 3, "year": 2, - "sem": "1", + "sem": "2", "prereqs": [] }, { - "code": "NSTP 1", - "title": "National Training Service Program I", + "code": "COMA 192", + "title": "Introduction to Research", "units": 3, "year": 2, - "sem": "1", + "sem": "2", "prereqs": [] }, { - "code": "COMA 103", - "title": "Introduction to Discourse Analysis", + "code": "SPCM 104", + "title": "Occasional Speeches", "units": 3, "year": 2, "sem": "2", "prereqs": [] }, { - "code": "HK 12", - "title": "Unknown Title", + "code": "MAJ 1", + "title": "Major", "units": 3, "year": 2, "sem": "2", - "prereqs": [] + "prereqs": [], + "genericRequirement": "elective" }, { - "code": "3", - "title": "3 3 3 3 3 3 (2) (3) 18", + "code": "MAJ 2", + "title": "Major", "units": 3, "year": 2, - "sem": "midyear", - "prereqs": [] + "sem": "2", + "prereqs": [], + "genericRequirement": "elective" }, { - "code": "COMA 105", - "title": "Principles of Public Relations and Advertising", + "code": "MAJ 3", + "title": "Major", "units": 3, - "year": 3, - "sem": "1", - "prereqs": [] + "year": 2, + "sem": "2", + "prereqs": [], + "genericRequirement": "elective" }, { "code": "COMA 190", @@ -3028,36 +3348,49 @@ const UPLB_PROGRAMS = { "prereqs": [] }, { - "code": "Elective", - "title": "(Foreign Language)", + "code": "MAJ 4", + "title": "Major", "units": 3, "year": 3, "sem": "1", - "prereqs": [] + "prereqs": [], + "genericRequirement": "elective" }, { - "code": "Elective", - "title": "(SOSC or PHLO)", + "code": "MAJ 5", + "title": "Major", "units": 3, "year": 3, "sem": "1", - "prereqs": [] + "prereqs": [], + "genericRequirement": "elective" }, { - "code": "Elective", - "title": "(SOSC or PHLO)", + "code": "MAJ 6", + "title": "Elective (Foreign Language)", "units": 3, "year": 3, "sem": "1", - "prereqs": [] + "prereqs": [], + "genericRequirement": "elective" }, { - "code": "Elective", - "title": "(STAT)", + "code": "MAJ 7", + "title": "Elective (SOSC or PHLO)", "units": 3, "year": 3, "sem": "1", - "prereqs": [] + "prereqs": [], + "genericRequirement": "elective" + }, + { + "code": "MAJ 8", + "title": "Elective (STAT)", + "units": 3, + "year": 3, + "sem": "1", + "prereqs": [], + "genericRequirement": "elective" }, { "code": "COMA 150", @@ -3068,33 +3401,68 @@ const UPLB_PROGRAMS = { "prereqs": [] }, { - "code": "3", - "title": "3", + "code": "COMA 193", + "title": "Workshop", "units": 3, "year": 3, - "sem": "midyear", + "sem": "2", + "prereqs": [] + }, + { + "code": "COMA 199", + "title": "Undergraduate Seminar", + "units": 1, + "year": 3, + "sem": "2", "prereqs": [] }, { - "code": "COMA", - "title": "200a. Practicum 3 1 3 3 3 16", + "code": "MAJ 9", + "title": "Major", + "units": 3, + "year": 3, + "sem": "2", + "prereqs": [], + "genericRequirement": "elective" + }, + { + "code": "MAJ 10", + "title": "Major", + "units": 3, + "year": 3, + "sem": "2", + "prereqs": [], + "genericRequirement": "elective" + }, + { + "code": "MAJ 11", + "title": "Elective (Foreign Language)", "units": 3, "year": 3, + "sem": "2", + "prereqs": [], + "genericRequirement": "elective" + }, + { + "code": "COMA 200A", + "title": "Practicum", + "units": 6, + "year": 3, "sem": "midyear", "prereqs": [] }, { - "code": "COMA 200", - "title": "Undergradute Thesis", + "code": "COMA 105", + "title": "Principles of Public Relations and Advertising", "units": 3, "year": 4, "sem": "1", "prereqs": [] }, { - "code": "COMA", - "title": "200a. Practicum", - "units": 3, + "code": "COMA 200", + "title": "Undergraduate Thesis", + "units": 6, "year": 4, "sem": "1", "prereqs": [] @@ -3109,61 +3477,87 @@ const UPLB_PROGRAMS = { }, { "code": "THEA 103", - "title": "Philippine Theatre Major Major", + "title": "Philippine Theatre", "units": 3, "year": 4, "sem": "1", "prereqs": [] }, { - "code": "GE", - "title": "Elective", + "code": "MAJ 12", + "title": "Elective (SOSC or PHLO)", + "units": 3, + "year": 4, + "sem": "1", + "prereqs": [], + "genericRequirement": "elective" + }, + { + "code": "PI 10", + "title": "The Life and Works of Jose Rizal", "units": 3, "year": 4, "sem": "2", "prereqs": [] }, { - "code": "3", - "title": "3 3 3 3 3 3 18", + "code": "HUM 104", + "title": "Culture and Arts Management", "units": 3, "year": 4, - "sem": "midyear", + "sem": "2", + "prereqs": [] + }, + { + "code": "HUM 170", + "title": "Philippine Art and Society", + "units": 3, + "year": 4, + "sem": "2", "prereqs": [] + }, + { + "code": "MAJ 13", + "title": "Elective (SOSC or PHLO)", + "units": 3, + "year": 4, + "sem": "2", + "prereqs": [], + "genericRequirement": "elective" } ], "requiredCodes": [ "ARTS 1", "COMM 10", - "KAS 1/HIST 1", + "KAS 1", "COMA 101", "HUM 100", "SPCM 102", "ETHICS 1", - "Life", - "GE", + "STS 1", + "COMA 102", + "ENG 100", + "SPCM 101", "ENG 101", "ENG 104", "HUM 101", "THEA 101", "THEA 102", "COMA 103", - "3", - "COMA 105", + "COMA 192", + "SPCM 104", "COMA 190", - "Elective", - "Elective", - "Elective", - "Elective", "COMA 150", - "3", - "COMA", + "COMA 193", + "COMA 199", + "COMA 200A", + "COMA 105", "COMA 200", - "COMA", "HUM 102", "THEA 103", - "GE", - "3" + "PI 10", + "HUM 104", + "HUM 170" ] }, "BASOCIO": { @@ -3174,12 +3568,12 @@ const UPLB_PROGRAMS = { "available": true, "totalUnitsRequired": 136, "geCoursesRequired": 9, - "tracks": null, - "defaultTrack": null, + "hkCoursesRequired": 4, + "nstpCoursesRequired": 2, "majorCourses": [ { - "code": "KAS 1/HIST 1", - "title": "Kasaysayan ng Pilipinas/Philippine History", + "code": "KAS 1", + "title": "Kasaysayan ng Pilipinas / Philippine History", "units": 3, "year": 1, "sem": "1", @@ -3203,7 +3597,7 @@ const UPLB_PROGRAMS = { }, { "code": "ANTH 10", - "title": "Introduction to Social and Cultural Anthropolog", + "title": "Introduction to Social and Cultural Anthropology", "units": 3, "year": 1, "sem": "1", @@ -3218,16 +3612,32 @@ const UPLB_PROGRAMS = { "prereqs": [] }, { - "code": "HK 11", - "title": "Wellness and Basic Injury Management", + "code": "HIST 10", + "title": "Introduction to History", "units": 3, "year": 1, - "sem": "1", + "sem": "2", + "prereqs": [] + }, + { + "code": "SOC 100", + "title": "Social Organization", + "units": 3, + "year": 1, + "sem": "2", + "prereqs": [] + }, + { + "code": "POSC 10", + "title": "Principles of Government and Politics", + "units": 3, + "year": 1, + "sem": "2", "prereqs": [] }, { - "code": "GE", - "title": "Elective", + "code": "SOC 110", + "title": "Sociology of the Family", "units": 3, "year": 1, "sem": "2", @@ -3235,7 +3645,7 @@ const UPLB_PROGRAMS = { }, { "code": "STS 1", - "title": "Science,", + "title": "Science, Technology, and Society", "units": 3, "year": 1, "sem": "midyear", @@ -3274,60 +3684,81 @@ const UPLB_PROGRAMS = { "prereqs": [] }, { - "code": "SOC 130/SOC 135", - "title": "Social Psychology/Attitudes and Persuasion", + "code": "SOC 130", + "title": "Social Psychology", "units": 3, "year": 2, "sem": "1", "prereqs": [] }, { - "code": "HK 12", - "title": "Unknown Title", + "code": "AERS 160", + "title": "Rural Sociology", "units": 3, "year": 2, - "sem": "1", + "sem": "2", "prereqs": [] }, { - "code": "NSTP 1", - "title": "National Training Service Program I", + "code": "SOC 140", + "title": "Introduction to Demography", "units": 3, "year": 2, - "sem": "1", + "sem": "2", "prereqs": [] }, { - "code": "AERS 160", - "title": "Rural Sociology", + "code": "MAJ 1", + "title": "Elective (SOSC)", "units": 3, "year": 2, "sem": "2", - "prereqs": [] + "prereqs": [], + "genericRequirement": "elective" + }, + { + "code": "MAJ 2", + "title": "Elective (SOSC)", + "units": 3, + "year": 2, + "sem": "2", + "prereqs": [], + "genericRequirement": "elective" + }, + { + "code": "FE 1", + "title": "Free Elective", + "units": 3, + "year": 2, + "sem": "2", + "prereqs": [], + "genericRequirement": "elective" }, { "code": "PI 10", - "title": "The Life", - "units": 6, + "title": "The Life and Works of Jose Rizal", + "units": 3, "year": 2, "sem": "midyear", "prereqs": [] }, { - "code": "Elective", - "title": "(SOSC)", + "code": "MAJ 3", + "title": "Elective (SOSC)", "units": 3, "year": 3, "sem": "1", - "prereqs": [] + "prereqs": [], + "genericRequirement": "elective" }, { - "code": "Elective", - "title": "(SOSC)", + "code": "MAJ 4", + "title": "Elective (SOSC)", "units": 3, "year": 3, "sem": "1", - "prereqs": [] + "prereqs": [], + "genericRequirement": "elective" }, { "code": "SOC 107", @@ -3348,7 +3779,7 @@ const UPLB_PROGRAMS = { { "code": "SOC 195", "title": "Research Methodologies in the Social Sciences", - "units": 3, + "units": 2, "year": 3, "sem": "1", "prereqs": [] @@ -3356,7 +3787,7 @@ const UPLB_PROGRAMS = { { "code": "SOC 195.1", "title": "Research Methodologies in the Social Sciences Laboratory", - "units": 3, + "units": 1, "year": 3, "sem": "1", "prereqs": [] @@ -3369,9 +3800,42 @@ const UPLB_PROGRAMS = { "sem": "2", "prereqs": [] }, + { + "code": "SOC 152", + "title": "Contemporary Sociological Theories", + "units": 3, + "year": 3, + "sem": "2", + "prereqs": [] + }, + { + "code": "SOC 192", + "title": "Introduction to Qualitative Social Research", + "units": 3, + "year": 3, + "sem": "2", + "prereqs": [] + }, + { + "code": "COMM 10", + "title": "Critical Perspectives in Communication", + "units": 3, + "year": 3, + "sem": "2", + "prereqs": [] + }, + { + "code": "FE 2", + "title": "Free Elective", + "units": 3, + "year": 3, + "sem": "2", + "prereqs": [], + "genericRequirement": "elective" + }, { "code": "SOC 198", - "title": "", + "title": "Internship", "units": 3, "year": 3, "sem": "midyear", @@ -3380,7 +3844,7 @@ const UPLB_PROGRAMS = { { "code": "SOC 199", "title": "Undergraduate Seminar", - "units": 3, + "units": 1, "year": 4, "sem": "1", "prereqs": [] @@ -3420,7 +3884,7 @@ const UPLB_PROGRAMS = { { "code": "SOC 200", "title": "Undergraduate Thesis", - "units": 3, + "units": 6, "year": 4, "sem": "1", "prereqs": [] @@ -3434,36 +3898,58 @@ const UPLB_PROGRAMS = { "prereqs": [] }, { - "code": "3", - "title": "3 f 3 3 3 15", + "code": "SOC 160", + "title": "Social Change", "units": 3, "year": 4, - "sem": "midyear", + "sem": "2", "prereqs": [] + }, + { + "code": "SOC 170", + "title": "Social Problems", + "units": 3, + "year": 4, + "sem": "2", + "prereqs": [] + }, + { + "code": "FE 3", + "title": "Free Elective", + "units": 3, + "year": 4, + "sem": "2", + "prereqs": [], + "genericRequirement": "elective" } ], "requiredCodes": [ - "KAS 1/HIST 1", + "KAS 1", "ETHICS 1", "SOC 10", "ANTH 10", "PSY 10", - "GE", + "HIST 10", + "SOC 100", + "POSC 10", + "SOC 110", "STS 1", "ARTS 1", "ECON 11", "STAT 166", "SOC 116", - "SOC 130/SOC 135", + "SOC 130", "AERS 160", + "SOC 140", "PI 10", - "Elective", - "Elective", "SOC 107", "SOC 151", "SOC 195", "SOC 195.1", "SOC 166", + "SOC 152", + "SOC 192", + "COMM 10", "SOC 198", "SOC 199", "SOC 114", @@ -3472,7 +3958,8 @@ const UPLB_PROGRAMS = { "SOC 191", "SOC 200", "SOC 112", - "3" + "SOC 160", + "SOC 170" ] }, "BAPHILO": { @@ -3483,31 +3970,35 @@ const UPLB_PROGRAMS = { "available": true, "totalUnitsRequired": 132, "geCoursesRequired": 9, + "hkCoursesRequired": 4, + "nstpCoursesRequired": 2, "tracks": { "thesis": { "name": "Thesis Track", "code": "PHLO 200", - "freeElectiveUnits": 12 + "freeElectiveUnits": 12, + "majorElectiveUnits": 24 }, "sp": { "name": "Special Problem Track", "code": "PHLO 190", - "freeElectiveUnits": 15 + "freeElectiveUnits": 12, + "majorElectiveUnits": 24 } }, "defaultTrack": "thesis", "majorCourses": [ { "code": "ETHICS 1", - "title": "Ethics and Moral Reasoning in Everyday L", + "title": "Ethics and Moral Reasoning in Everyday Life", "units": 3, "year": 1, "sem": "1", "prereqs": [] }, { - "code": "KAS 1/HIST 1", - "title": "Kasaysayan ng Pilipinas/Philippine H", + "code": "KAS 1", + "title": "Kasaysayan ng Pilipinas / Philippine History", "units": 3, "year": 1, "sem": "1", @@ -3545,57 +4036,50 @@ const UPLB_PROGRAMS = { "sem": "1", "prereqs": [] }, - { - "code": "HK 11", - "title": "Wellness and Basic Injury Management", - "units": 3, - "year": 1, - "sem": "1", - "prereqs": [] - }, { "code": "PI 10", - "title": "The Life and Works of Jose Rizal ife", + "title": "The Life and Works of Jose Rizal", "units": 3, "year": 1, "sem": "2", "prereqs": [] }, { - "code": "HK 12", - "title": "Unknown Title", + "code": "PHLO 150", + "title": "Epistemology", "units": 3, "year": 1, "sem": "2", "prereqs": [] }, { - "code": "3", - "title": "3 3 3 3 t and 3Politics 3 s (2) 18", + "code": "PHLO 171", + "title": "Ethics", "units": 3, "year": 1, - "sem": "midyear", + "sem": "2", "prereqs": [] }, { - "code": "COMM 10", - "title": "Critical Perspectives in Communication", + "code": "ECON 11", + "title": "General Economics", "units": 3, - "year": 2, - "sem": "1", + "year": 1, + "sem": "2", "prereqs": [] }, { - "code": "GE", - "title": "Elective", + "code": "MAJ 1", + "title": "Readings in Speculative Thought", "units": 3, - "year": 2, - "sem": "1", - "prereqs": [] + "year": 1, + "sem": "2", + "prereqs": [], + "genericRequirement": "elective" }, { - "code": "Elective", - "title": "", + "code": "COMM 10", + "title": "Critical Perspectives in Communication", "units": 3, "year": 2, "sem": "1", @@ -3626,43 +4110,28 @@ const UPLB_PROGRAMS = { "prereqs": [] }, { - "code": "NSTP 1", - "title": "National Training Service Program I", - "units": 3, - "year": 2, - "sem": "1", - "prereqs": [] - }, - { - "code": "HK 12", - "title": "Unknown Title", - "units": 3, - "year": 2, - "sem": "1", - "prereqs": [] - }, - { - "code": "PHLO Elective", - "title": "", + "code": "MAJ 2", + "title": "PHLO Elective", "units": 3, "year": 2, "sem": "2", - "prereqs": [] + "prereqs": [], + "genericRequirement": "elective" }, { - "code": "HK 12", - "title": "Unknown Title", + "code": "PHLO 111", + "title": "Medieval Philosophy", "units": 3, "year": 2, "sem": "2", "prereqs": [] }, { - "code": "3", - "title": "3 3 3 3 3 s (2) (3) 15", + "code": "PHLO 120", + "title": "Philosophical Reasoning", "units": 3, "year": 2, - "sem": "midyear", + "sem": "2", "prereqs": [] }, { @@ -3698,28 +4167,47 @@ const UPLB_PROGRAMS = { "prereqs": [] }, { - "code": "Elective", - "title": "", + "code": "PHLO 113", + "title": "Contemporary Philosophy", "units": 3, "year": 3, - "sem": "1", + "sem": "2", "prereqs": [] }, { - "code": "PHLO 113", - "title": "Contemporary Philosophy", + "code": "PHLO 182", + "title": "Philosophy and Semiotics", "units": 3, "year": 3, "sem": "2", "prereqs": [] }, { - "code": "3", - "title": "3 3 3 3 3 3 18", + "code": "MAJ 3", + "title": "Specialized Course (Elective in One Discipline)", "units": 3, "year": 3, - "sem": "midyear", - "prereqs": [] + "sem": "2", + "prereqs": [], + "genericRequirement": "elective" + }, + { + "code": "MAJ 4", + "title": "Elective (PHLO)", + "units": 3, + "year": 3, + "sem": "2", + "prereqs": [], + "genericRequirement": "elective" + }, + { + "code": "MAJ 5", + "title": "Elective (PHLO)", + "units": 3, + "year": 3, + "sem": "2", + "prereqs": [], + "genericRequirement": "elective" }, { "code": "PHLO 178", @@ -3737,20 +4225,57 @@ const UPLB_PROGRAMS = { "sem": "1", "prereqs": [] }, + { + "code": "MAJ 6", + "title": "Elective (PHLO)", + "units": 3, + "year": 4, + "sem": "1", + "prereqs": [], + "genericRequirement": "elective" + }, + { + "code": "MAJ 7", + "title": "Specialized Course (Elective in One Discipline)", + "units": 3, + "year": 4, + "sem": "1", + "prereqs": [], + "genericRequirement": "elective" + }, + { + "code": "MAJ 8", + "title": "Specialized Course (Elective in One Discipline)", + "units": 3, + "year": 4, + "sem": "1", + "prereqs": [], + "track": "thesis", + "genericRequirement": "elective" + }, { "code": "PHLO 190", "title": "Special Problems", - "units": 3, + "units": 6, "year": 4, "sem": "1", + "prereqs": [], + "track": "sp" + }, + { + "code": "PHLO 184", + "title": "Feminist Philosophy", + "units": 3, + "year": 4, + "sem": "2", "prereqs": [] }, { - "code": "Elective", - "title": "(PHLO) Specialized course. Elective in one discipline", + "code": "PHLO 185", + "title": "Critical Perspectives in Filipino Philosophy", "units": 3, "year": 4, - "sem": "1", + "sem": "2", "prereqs": [] }, { @@ -3762,44 +4287,55 @@ const UPLB_PROGRAMS = { "prereqs": [] }, { - "code": "3", - "title": "3 3 3 3 3 15", + "code": "PHLO 200", + "title": "Undergraduate Thesis", + "units": 6, + "year": 4, + "sem": "2", + "prereqs": [], + "track": "thesis" + }, + { + "code": "MAJ 9", + "title": "Specialized Course (Elective in One Discipline)", "units": 3, "year": 4, - "sem": "midyear", - "prereqs": [] + "sem": "2", + "prereqs": [], + "track": "sp", + "genericRequirement": "elective" } ], "requiredCodes": [ "ETHICS 1", - "KAS 1/HIST 1", + "KAS 1", "ARTS 1", "STS 1", "PHLO 11", "PHLO 12", "PI 10", - "3", + "PHLO 150", + "PHLO 171", + "ECON 11", "COMM 10", - "GE", - "Elective", "PHLO 110", "PHLO 112", "PHLO 173", - "PHLO Elective", - "3", + "PHLO 111", + "PHLO 120", "PHLO 197", "PHLO 174", "PHLO 181", "PHLO 195", - "Elective", "PHLO 113", - "3", + "PHLO 182", "PHLO 178", "PHLO 176", "PHLO 190", - "Elective", + "PHLO 184", + "PHLO 185", "PHLO 160", - "3" + "PHLO 200" ] }, "AASS": { @@ -3807,7 +4343,7 @@ const UPLB_PROGRAMS = { "name": "Associate in Arts in Sports Studies", "college": "CAS", "collegeName": "College of Arts and Sciences", - "available": true, + "available": false, "totalUnitsRequired": 78, "geCoursesRequired": 5, "tracks": null, @@ -3829,24 +4365,6 @@ const UPLB_PROGRAMS = { "sem": "2", "prereqs": [] }, - { - "code": "HK 11", - "title": "Wellness and Basic Injury Management", - "units": 2, - "year": 1, - "sem": "1", - "prereqs": [] - }, - { - "code": "HK 12", - "title": "Basic Human Kinetics Activities", - "units": 2, - "year": 1, - "sem": "2", - "prereqs": [ - "HK 11" - ] - }, { "code": "SS 110", "title": "Foundations of Sports Studies", @@ -3887,16 +4405,6 @@ const UPLB_PROGRAMS = { "PEd 91" ] }, - { - "code": "HK 12/13", - "title": "Basic or Advanced Human Kinetics Activities", - "units": 2, - "year": 2, - "sem": "1", - "prereqs": [ - "HK 11" - ] - }, { "code": "KAS 1", "title": "Kasaysayan ng Pilipinas/Philippine History", @@ -3963,14 +4471,6 @@ const UPLB_PROGRAMS = { "sem": "2", "prereqs": [] }, - { - "code": "HK 11/12/13", - "title": "Human Kinetics Activities", - "units": 2, - "year": 3, - "sem": "1", - "prereqs": [] - }, { "code": "SS 120", "title": "Sports Studies 120", @@ -4028,14 +4528,6 @@ const UPLB_PROGRAMS = { "prereqs": [ "PEd 91" ] - }, - { - "code": "HK 11/12/13", - "title": "Human Kinetics Activities", - "units": 2, - "year": 4, - "sem": "2", - "prereqs": [] } ], "requiredCodes": [ @@ -4055,14 +4547,11 @@ const UPLB_PROGRAMS = { "PEd 174", "PEd 176", "PEd 196", - "HK 11", - "HK 12", - "HK 12/13", - "HK 11/12/13", "KAS 1", "PI 100", "MATH 10" - ] + ], + "hkCoursesRequired": 5 }, "BSAGRI": { "code": "BSAGRI", @@ -4070,62 +4559,94 @@ const UPLB_PROGRAMS = { "college": "CAFS", "collegeName": "College of Agriculture and Food Science", "available": true, - "totalUnitsRequired": 142, + "totalUnitsRequired": 145, "geCoursesRequired": 9, - "tracks": null, - "defaultTrack": null, + "hkCoursesRequired": 4, + "nstpCoursesRequired": 2, "majorCourses": [ { "code": "AGRI 11", - "title": "1 AGRI", - "units": 51, + "title": "Introduction to Agriculture", + "units": 1, "year": 1, "sem": "1", "prereqs": [] }, { "code": "AGRI 21", - "title": "3 AGRI", - "units": 22, + "title": "Introduction to Animal Science", + "units": 3, "year": 1, "sem": "1", "prereqs": [] }, { "code": "AGRI 31", - "title": "3 AGRI", - "units": 32, + "title": "Fundamentals of Crop Science I", + "units": 3, "year": 1, "sem": "1", "prereqs": [] }, { "code": "AGRI 41", - "title": "3 CHEM", - "units": 18, + "title": "Principles of Crop Protection", + "units": 3, "year": 1, "sem": "1", "prereqs": [] }, { "code": "ARTS 1", - "title": "3 ETHICS", - "units": 1, + "title": "Critical Perspectives in the Arts", + "units": 3, "year": 1, "sem": "1", "prereqs": [] }, { "code": "STS 1", - "title": "3 HK 16", - "units": 11, + "title": "Science, Technology, and Society", + "units": 3, "year": 1, "sem": "1", "prereqs": [] }, { - "code": "3", - "title": "3 3 3 3 3 (2) 15", + "code": "AGRI 51", + "title": "Principles of Soil Science", + "units": 3, + "year": 1, + "sem": "2", + "prereqs": [] + }, + { + "code": "AGRI 22", + "title": "Introduction to Livestock and Poultry Production", + "units": 3, + "year": 1, + "sem": "2", + "prereqs": [] + }, + { + "code": "AGRI 32", + "title": "Fundamentals of Crop Science II", + "units": 3, + "year": 1, + "sem": "2", + "prereqs": [] + }, + { + "code": "CHEM 18", + "title": "University Chemistry", + "units": 3, + "year": 1, + "sem": "2", + "prereqs": [] + }, + { + "code": "ETHICS 1", + "title": "Ethics and Moral Reasoning in Everyday Life", "units": 3, "year": 1, "sem": "2", @@ -4133,155 +4654,314 @@ const UPLB_PROGRAMS = { }, { "code": "CHEM 18.1", - "title": "2 KAS 1/HIST", - "units": 1, + "title": "University Chemistry Laboratory", + "units": 2, "year": 2, "sem": "1", "prereqs": [] }, { "code": "ABME 10", - "title": "3 ECON", - "units": 11, + "title": "Foundations of Entrepreneurship", + "units": 3, "year": 2, "sem": "1", "prereqs": [] }, { "code": "AGRI 42", - "title": "3 BOT", - "units": 20, + "title": "Pest Management", + "units": 3, "year": 2, "sem": "1", "prereqs": [] }, { "code": "AGRI 61", - "title": "3 AGRI", - "units": 111, + "title": "Fundamentals of Agricultural Extension Communication", + "units": 3, "year": 2, "sem": "1", "prereqs": [] }, { "code": "ABT 10", - "title": "3 AGRI", - "units": 171, + "title": "Traditional and Modern Biotechnology: Principles and Applications", + "units": 3, "year": 2, "sem": "1", "prereqs": [] }, { - "code": "ELECTIVE", - "title": "GE 1 3 CHEM", - "units": 40, + "code": "KAS 1", + "title": "Kasaysayan ng Pilipinas / Philippine History", + "units": 3, "year": 2, - "sem": "1", + "sem": "2", "prereqs": [] }, { - "code": "HK 12", - "title": "OR 13 (2) CHEM", - "units": 40.1, + "code": "ECON 11", + "title": "General Economics", + "units": 3, "year": 2, - "sem": "1", + "sem": "2", "prereqs": [] }, { - "code": "NSTP", - "title": "1 17", + "code": "BOT 20", + "title": "Fundamentals of Plant Physiology", "units": 3, "year": 2, - "sem": "1", + "sem": "2", "prereqs": [] }, { - "code": "3", - "title": "3 3 3 3 3 4 1 (3) 20", + "code": "AGRI 111", + "title": "AGRI 111", "units": 3, "year": 2, "sem": "2", "prereqs": [] }, { - "code": "Midyear", - "title": "Midyear Major 198/200a 3", + "code": "AGRI 171", + "title": "Ethics, Laws and Policies in Agriculture", "units": 3, - "year": 3, - "sem": "1", + "year": 2, + "sem": "2", "prereqs": [] }, { - "code": "AGRI 199", - "title": "1 SPECIALIZATION COURSE", - "units": 3, - "year": 4, - "sem": "1", + "code": "CHEM 40", + "title": "Basic Organic Chemistry", + "units": 4, + "year": 2, + "sem": "2", "prereqs": [] }, { - "code": "MAJOR", - "title": "COURSE 4 3 SPECIALIZATION COURSE", - "units": 3, - "year": 4, - "sem": "1", + "code": "CHEM 40.1", + "title": "Basic Organic Chemistry Laboratory", + "units": 1, + "year": 2, + "sem": "2", "prereqs": [] }, { - "code": "MAJOR", - "title": "COURSE 5 3 SPECIALIZATION COURSE", + "code": "BIO 30", + "title": "Genetics", "units": 3, - "year": 4, + "year": 3, "sem": "1", "prereqs": [] }, { - "code": "MAJOR", - "title": "COURSE 6 3 Elective", + "code": "STAT 162", + "title": "Experimental Designs", "units": 3, - "year": 4, + "year": 3, "sem": "1", "prereqs": [] }, { - "code": "ABE 1", - "title": "3 MAJOR 200/MAJOR 200a", + "code": "MGT 101", + "title": "Concepts and Dynamics of Management", "units": 3, - "year": 4, + "year": 3, "sem": "1", "prereqs": [] }, { - "code": "MAJOR 200", - "title": "Unknown Title", + "code": "MAJ 1", + "title": "Major Course", + "units": 3, + "year": 3, + "sem": "1", + "prereqs": [], + "genericRequirement": "elective" + }, + { + "code": "AAE 111", + "title": "Farm Management", + "units": 3, + "year": 3, + "sem": "2", + "prereqs": [] + }, + { + "code": "AGRI 195", + "title": "AGRI 195", + "units": 3, + "year": 3, + "sem": "2", + "prereqs": [] + }, + { + "code": "AGRI 121", + "title": "AGRI 121", + "units": 3, + "year": 3, + "sem": "2", + "prereqs": [] + }, + { + "code": "MAJ 2", + "title": "Major Course", + "units": 3, + "year": 3, + "sem": "2", + "prereqs": [], + "genericRequirement": "elective" + }, + { + "code": "MAJ 3", + "title": "Major Course", + "units": 3, + "year": 3, + "sem": "2", + "prereqs": [], + "genericRequirement": "elective" + }, + { + "code": "MAJ 4", + "title": "Specialization Course", + "units": 3, + "year": 3, + "sem": "2", + "prereqs": [], + "genericRequirement": "elective" + }, + { + "code": "PI 10", + "title": "The Life and Works of Jose Rizal", + "units": 3, + "year": 3, + "sem": "2", + "prereqs": [] + }, + { + "code": "MAJ 5", + "title": "Major Practicum or Major Farm Practice (198/200a)", + "units": 3, + "year": 3, + "sem": "midyear", + "prereqs": [], + "genericRequirement": "elective" + }, + { + "code": "AGRI 199", + "title": "Colloquium in Agriculture", "units": 1, "year": 4, "sem": "1", "prereqs": [] }, { - "code": "COMM 10", - "title": "3", + "code": "MAJ 6", + "title": "Major Course", + "units": 3, + "year": 4, + "sem": "1", + "prereqs": [], + "genericRequirement": "elective" + }, + { + "code": "MAJ 7", + "title": "Major Course", + "units": 3, + "year": 4, + "sem": "1", + "prereqs": [], + "genericRequirement": "elective" + }, + { + "code": "MAJ 8", + "title": "Major Course", + "units": 3, + "year": 4, + "sem": "1", + "prereqs": [], + "genericRequirement": "elective" + }, + { + "code": "ABE 1", + "title": "Fundamentals of Agricultural and Biosystems Engineering I", "units": 3, "year": 4, "sem": "1", "prereqs": [] }, { - "code": "HK 12", - "title": "OR 13 20", - "units": 2, + "code": "MAJ 9", + "title": "Major Undergraduate Thesis or Special Problem (200/190)", + "units": 3, + "year": 4, + "sem": "1", + "prereqs": [], + "genericRequirement": "elective" + }, + { + "code": "MAJ 10", + "title": "Major Undergraduate Seminar", + "units": 1, + "year": 4, + "sem": "1", + "prereqs": [], + "genericRequirement": "elective" + }, + { + "code": "COMM 10", + "title": "Critical Perspectives in Communication", + "units": 3, "year": 4, "sem": "1", "prereqs": [] }, { - "code": "2", - "title": "2 3 4 3 3 15", + "code": "MAJ 11", + "title": "Specialization Course", "units": 3, "year": 4, "sem": "2", - "prereqs": [] + "prereqs": [], + "genericRequirement": "elective" + }, + { + "code": "MAJ 12", + "title": "Specialization Course", + "units": 3, + "year": 4, + "sem": "2", + "prereqs": [], + "genericRequirement": "elective" + }, + { + "code": "MAJ 13", + "title": "Specialization Course", + "units": 3, + "year": 4, + "sem": "2", + "prereqs": [], + "genericRequirement": "elective" + }, + { + "code": "FE 1", + "title": "Free Elective", + "units": 3, + "year": 4, + "sem": "2", + "prereqs": [], + "genericRequirement": "elective" + }, + { + "code": "MAJ 14", + "title": "Major Undergraduate Thesis or Major Farm Practice (200/200a)", + "units": 3, + "year": 4, + "sem": "2", + "prereqs": [], + "genericRequirement": "elective" } ], "requiredCodes": [ @@ -4291,23 +4971,33 @@ const UPLB_PROGRAMS = { "AGRI 41", "ARTS 1", "STS 1", - "3", + "AGRI 51", + "AGRI 22", + "AGRI 32", + "CHEM 18", + "ETHICS 1", "CHEM 18.1", "ABME 10", "AGRI 42", "AGRI 61", "ABT 10", - "ELECTIVE", - "3", - "Midyear", + "KAS 1", + "ECON 11", + "BOT 20", + "AGRI 111", + "AGRI 171", + "CHEM 40", + "CHEM 40.1", + "BIO 30", + "STAT 162", + "MGT 101", + "AAE 111", + "AGRI 195", + "AGRI 121", + "PI 10", "AGRI 199", - "MAJOR", - "MAJOR", - "MAJOR", "ABE 1", - "MAJOR 200", - "COMM 10", - "2" + "COMM 10" ] }, "BSABIO": { @@ -4318,13 +5008,13 @@ const UPLB_PROGRAMS = { "available": true, "totalUnitsRequired": 142, "geCoursesRequired": 9, - "tracks": null, - "defaultTrack": null, + "hkCoursesRequired": 4, + "nstpCoursesRequired": 2, "majorCourses": [ { "code": "ABT 11", "title": "Introduction to Agricultural Biotechnology", - "units": 3, + "units": 1, "year": 1, "sem": "1", "prereqs": [] @@ -4339,7 +5029,7 @@ const UPLB_PROGRAMS = { }, { "code": "AGRI 21", - "title": "(Lab). Introduction to Animal Science", + "title": "Introduction to Animal Science", "units": 3, "year": 1, "sem": "1", @@ -4347,7 +5037,7 @@ const UPLB_PROGRAMS = { }, { "code": "AGRI 31", - "title": "(Lab). Fundamentals of Crop Science", + "title": "Fundamentals of Crop Science I", "units": 3, "year": 1, "sem": "1", @@ -4363,7 +5053,7 @@ const UPLB_PROGRAMS = { }, { "code": "STS 1", - "title": "Science, Technology and Society", + "title": "Science, Technology, and Society", "units": 3, "year": 1, "sem": "1", @@ -4371,32 +5061,56 @@ const UPLB_PROGRAMS = { }, { "code": "AGRI 22", - "title": "Introduction to Livestock and Poultry Productio", + "title": "Introduction to Livestock and Poultry Production", "units": 3, "year": 1, "sem": "2", "prereqs": [] }, { - "code": "HK 11", - "title": "Wellness and Basic Injury Management 16", + "code": "AGRI 32", + "title": "Fundamentals of Crop Science II", "units": 3, "year": 1, "sem": "2", "prereqs": [] }, { - "code": "n", - "title": "n 3 3 2 3 3 (2) 17", + "code": "AGRI 41", + "title": "Principles of Crop Protection", "units": 3, "year": 1, - "sem": "midyear", + "sem": "2", + "prereqs": [] + }, + { + "code": "CHEM 18.1", + "title": "University Chemistry Laboratory", + "units": 2, + "year": 1, + "sem": "2", + "prereqs": [] + }, + { + "code": "KAS 1", + "title": "Kasaysayan ng Pilipinas / Philippine History", + "units": 3, + "year": 1, + "sem": "2", + "prereqs": [] + }, + { + "code": "ETHICS 1", + "title": "Ethics and Moral Reasoning in Everyday Life", + "units": 3, + "year": 1, + "sem": "2", "prereqs": [] }, { "code": "CHEM 40", "title": "Basic Organic Chemistry", - "units": 3, + "units": 4, "year": 2, "sem": "1", "prereqs": [] @@ -4404,7 +5118,7 @@ const UPLB_PROGRAMS = { { "code": "CHEM 40.1", "title": "Basic Organic Chemistry Laboratory", - "units": 3, + "units": 1, "year": 2, "sem": "1", "prereqs": [] @@ -4426,51 +5140,59 @@ const UPLB_PROGRAMS = { "prereqs": [] }, { - "code": "Elective", - "title": "GE1 (Math 10). Mathematics, Culture and Society", + "code": "BIO 30", + "title": "Genetics", "units": 3, "year": 2, "sem": "1", "prereqs": [] }, { - "code": "BIO 30", - "title": "Genetics", + "code": "CHEM 160", + "title": "Introductory Biochemistry", "units": 3, "year": 2, - "sem": "1", + "sem": "2", "prereqs": [] }, { - "code": "HK 12", - "title": "Unknown Title", + "code": "ABT 101", + "title": "Fundamentals of Agricultural Biotechnology", "units": 3, "year": 2, - "sem": "1", + "sem": "2", "prereqs": [] }, { - "code": "CHEM 160", - "title": "Introductory Biochemistry", - "units": 2, + "code": "STAT 162", + "title": "Experimental Designs I", + "units": 3, "year": 2, "sem": "2", "prereqs": [] }, { - "code": "NSTP 1", - "title": "National Service Training Program 17", + "code": "CMSC 12", + "title": "Introduction to Computer Science", "units": 3, "year": 2, "sem": "2", "prereqs": [] }, { - "code": "3", - "title": "3 3 3 3 3 3 3 (3) 21", + "code": "MATH 25", + "title": "Fundamental Calculus", "units": 3, "year": 2, - "sem": "midyear", + "sem": "2", + "prereqs": [] + }, + { + "code": "AGRI 51", + "title": "Principles of Soil Science", + "units": 3, + "year": 2, + "sem": "2", "prereqs": [] }, { @@ -4507,7 +5229,7 @@ const UPLB_PROGRAMS = { }, { "code": "ABME 10", - "title": "Cultivating the Entreprenuerial Mindset", + "title": "Cultivating the Entrepreneurial Mindset", "units": 3, "year": 3, "sem": "1", @@ -4522,48 +5244,58 @@ const UPLB_PROGRAMS = { "prereqs": [] }, { - "code": "Elective", - "title": "GE 3 (SAS 1). Self and Society", + "code": "ABT 106", + "title": "Molecular Markers", "units": 3, "year": 3, - "sem": "1", + "sem": "2", "prereqs": [] }, { - "code": "NSTP 2", - "title": "National Service Training Program Total", + "code": "ABT 104", + "title": "Experimental Techniques in Agricultural Biotechnology II", "units": 3, "year": 3, - "sem": "1", + "sem": "2", "prereqs": [] }, { - "code": "ABT 106", - "title": "Molecular Markers", - "units": 1, + "code": "FE 1", + "title": "Free Elective", + "units": 3, "year": 3, "sem": "2", - "prereqs": [] + "prereqs": [], + "genericRequirement": "elective" }, { - "code": "HK 12", - "title": "Unknown Title", + "code": "ECON 11", + "title": "General Economics", "units": 3, "year": 3, "sem": "2", "prereqs": [] }, { - "code": "3", - "title": "3", + "code": "AGRI 171", + "title": "Ethics, Laws, and Policies in Agriculture", "units": 3, "year": 3, - "sem": "midyear", + "sem": "2", "prereqs": [] }, { - "code": "ABT", - "title": "198/200a. Practicum/Major Practice logy3II 3 3 3 3 (2) 18", + "code": "MAJ 1", + "title": "Specialization Course", + "units": 3, + "year": 3, + "sem": "2", + "prereqs": [], + "genericRequirement": "elective" + }, + { + "code": "ABT 198", + "title": "Practicum", "units": 3, "year": 3, "sem": "midyear", @@ -4579,51 +5311,79 @@ const UPLB_PROGRAMS = { }, { "code": "ABT 107", - "title": "Recombinant DNA Technology SPECIALIZATION COURSE SPECIALIZAT", - "units": 4, + "title": "Recombinant DNA Technology", + "units": 3, "year": 4, "sem": "1", "prereqs": [] }, { - "code": "ABT", - "title": "200/ABT 200a. Undergraduate Thesis/Major Practice", + "code": "MAJ 2", + "title": "Specialization Course", "units": 3, "year": 4, "sem": "1", - "prereqs": [] + "prereqs": [], + "genericRequirement": "elective" + }, + { + "code": "MAJ 3", + "title": "Specialization Course", + "units": 3, + "year": 4, + "sem": "1", + "prereqs": [], + "genericRequirement": "elective" }, { - "code": "HK 12", - "title": "Unknown Title", + "code": "MAJ 4", + "title": "Specialization Course", "units": 3, "year": 4, "sem": "1", + "prereqs": [], + "genericRequirement": "elective" + }, + { + "code": "ABT 200", + "title": "Undergraduate Thesis", + "units": 6, + "year": 4, + "sem": "1", "prereqs": [] }, { - "code": "3", - "title": "3", + "code": "ABT 108", + "title": "Issues and Regulation of Agricultural Biotechnology", "units": 3, "year": 4, "sem": "2", "prereqs": [] }, { - "code": "ABT 108", - "title": "Issues and Regulation of Agricultural Biotechno", - "units": 2, + "code": "ABT 199", + "title": "Undergraduate Seminar", + "units": 1, "year": 4, "sem": "2", "prereqs": [] }, { - "code": "3", - "title": "3 logy 1 1 3 3 11", - "units": 3, + "code": "AGRI 199", + "title": "Colloquium in Agriculture", + "units": 1, "year": 4, - "sem": "midyear", + "sem": "2", "prereqs": [] + }, + { + "code": "FE 2", + "title": "Free Elective", + "units": 3, + "year": 4, + "sem": "2", + "prereqs": [], + "genericRequirement": "elective" } ], "requiredCodes": [ @@ -4634,31 +5394,39 @@ const UPLB_PROGRAMS = { "ARTS 1", "STS 1", "AGRI 22", - "n", + "AGRI 32", + "AGRI 41", + "CHEM 18.1", + "KAS 1", + "ETHICS 1", "CHEM 40", "CHEM 40.1", "AGRI 42", "MCB 11", - "Elective", "BIO 30", "CHEM 160", - "3", - "ABT 103", + "ABT 101", + "STAT 162", + "CMSC 12", + "MATH 25", + "AGRI 51", + "ABT 103", "BIO 101", "AGRI 61", "PI 10", "ABME 10", "COMM 10", - "Elective", "ABT 106", - "3", - "ABT", + "ABT 104", + "ECON 11", + "AGRI 171", + "ABT 198", "MGT 101", "ABT 107", - "ABT", - "3", + "ABT 200", "ABT 108", - "3" + "ABT 199", + "AGRI 199" ] }, "BSFST": { @@ -4669,20 +5437,20 @@ const UPLB_PROGRAMS = { "available": true, "totalUnitsRequired": 143, "geCoursesRequired": 9, - "tracks": null, - "defaultTrack": null, + "hkCoursesRequired": 4, + "nstpCoursesRequired": 2, "majorCourses": [ { "code": "FST 11", - "title": "3 GE", - "units": 1, + "title": "Fundamentals of Food Science and Technology", + "units": 3, "year": 1, "sem": "1", "prereqs": [] }, { "code": "AGRI 21", - "title": "(Lab) 3 MCB 11 (Lab)", + "title": "Introduction to Animal Science", "units": 3, "year": 1, "sem": "1", @@ -4690,15 +5458,15 @@ const UPLB_PROGRAMS = { }, { "code": "AGRI 31", - "title": "(Lab) 3 MATH", - "units": 27, + "title": "Fundamentals of Crop Science I", + "units": 3, "year": 1, "sem": "1", "prereqs": [] }, { "code": "ARTS 1", - "title": "3 CHEM 18.1 (Lab)", + "title": "Critical Perspectives in the Arts", "units": 3, "year": 1, "sem": "1", @@ -4706,47 +5474,63 @@ const UPLB_PROGRAMS = { }, { "code": "ETHICS 1", - "title": "3 GE", - "units": 2, + "title": "Ethics and Moral Reasoning in Everyday Life", + "units": 3, "year": 1, "sem": "1", "prereqs": [] }, { "code": "CHEM 18", - "title": "3 PHYS", - "units": 51, + "title": "University Chemistry", + "units": 3, "year": 1, "sem": "1", "prereqs": [] }, { - "code": "HK 11", - "title": "-2 PHYS 51.1 (Lab)", + "code": "MCB 11", + "title": "Biology and Applications of Microorganisms", "units": 3, "year": 1, - "sem": "1", + "sem": "2", + "prereqs": [] + }, + { + "code": "MATH 27", + "title": "Analytic Geometry and Calculus II", + "units": 3, + "year": 1, + "sem": "2", "prereqs": [] }, { - "code": "HK 12", - "title": "or 18", - "units": 13, + "code": "CHEM 18.1", + "title": "University Chemistry Laboratory", + "units": 2, "year": 1, - "sem": "1", + "sem": "2", "prereqs": [] }, { - "code": "3", - "title": "3 3 3 2 3 3 2 -2 19", + "code": "PHYS 51", + "title": "Elements of Physics", "units": 3, "year": 1, "sem": "2", "prereqs": [] }, + { + "code": "PHYS 51.1", + "title": "Elements of Physics Laboratory", + "units": 2, + "year": 1, + "sem": "2", + "prereqs": [] + }, { "code": "CHEM 40", - "title": "3 STAT 101 (Lab)", + "title": "Basic Organic Chemistry", "units": 3, "year": 2, "sem": "1", @@ -4754,68 +5538,87 @@ const UPLB_PROGRAMS = { }, { "code": "CHEM 40.1", - "title": "(Lab) 2 MGT", - "units": 101, + "title": "Basic Organic Chemistry Laboratory", + "units": 2, "year": 2, "sem": "1", "prereqs": [] }, { "code": "ABME 10", - "title": "3 CHEM", - "units": 160, + "title": "Foundations of Entrepreneurship", + "units": 3, "year": 2, "sem": "1", "prereqs": [] }, { "code": "CHEM 32", - "title": "3 MCB 180 (Lab)", + "title": "Quantitative Inorganic Analysis", "units": 3, "year": 2, "sem": "1", - "prereqs": [ - "CHEM 18", - "CHEM 18.1" - ] + "prereqs": [] }, { "code": "CHEM 32.1", - "title": "(Lab) 2 FST 101 (Lab)", - "units": 3, + "title": "Quantitative Inorganic Analysis Laboratory", + "units": 2, "year": 2, "sem": "1", - "prereqs": [ - "CHEM 32" - ] + "prereqs": [] }, { "code": "STS 1", - "title": "3 FST 130 (Lab)", + "title": "Science, Technology, and Society", "units": 3, "year": 2, "sem": "1", "prereqs": [] }, { - "code": "HK 12", - "title": "or 13 -2 HK 12 or", - "units": 13, + "code": "STAT 101", + "title": "Statistical Methods", + "units": 3, "year": 2, - "sem": "1", + "sem": "2", "prereqs": [] }, { - "code": "NSTP 1", - "title": "-3 NSTP 16", - "units": 2, + "code": "MGT 101", + "title": "Concepts and Dynamics of Management", + "units": 3, "year": 2, - "sem": "1", + "sem": "2", "prereqs": [] }, { - "code": "3", - "title": "3 3 3 3 3 3 -2 -3 18", + "code": "CHEM 160", + "title": "Introductory Biochemistry", + "units": 3, + "year": 2, + "sem": "2", + "prereqs": [] + }, + { + "code": "MCB 180", + "title": "MCB 180", + "units": 3, + "year": 2, + "sem": "2", + "prereqs": [] + }, + { + "code": "FST 101", + "title": "FST 101", + "units": 3, + "year": 2, + "sem": "2", + "prereqs": [] + }, + { + "code": "FST 130", + "title": "FST 130", "units": 3, "year": 2, "sem": "2", @@ -4823,23 +5626,23 @@ const UPLB_PROGRAMS = { }, { "code": "COMM 10", - "title": "3 PI", - "units": 10, + "title": "Critical Perspectives in Communication", + "units": 3, "year": 3, "sem": "1", "prereqs": [] }, { "code": "FST 102", - "title": "(Lab) 4 AGRI", - "units": 171, + "title": "Food Analysis", + "units": 4, "year": 3, "sem": "1", "prereqs": [] }, { "code": "FST 131", - "title": "(Lab) 3 AGRI 195 (Lab)", + "title": "Food Engineering II", "units": 3, "year": 3, "sem": "1", @@ -4847,15 +5650,15 @@ const UPLB_PROGRAMS = { }, { "code": "FST 140", - "title": "(Lab) 3 FST", - "units": 111, + "title": "Food Processing I", + "units": 3, "year": 3, "sem": "1", "prereqs": [] }, { "code": "FST 141", - "title": "(Lab) 3 FST 151 (Lab)", + "title": "Food Processing II", "units": 3, "year": 3, "sem": "1", @@ -4863,83 +5666,149 @@ const UPLB_PROGRAMS = { }, { "code": "FST 167", - "title": "(Lab) 3 FST 19", - "units": 200, + "title": "Food Safety", + "units": 3, "year": 3, "sem": "1", "prereqs": [] }, { - "code": "MID", - "title": "YEAR TERM", + "code": "PI 10", + "title": "The Life and Works of Jose Rizal", "units": 3, "year": 3, - "sem": "1", + "sem": "2", "prereqs": [] }, { - "code": "FST", - "title": "198", + "code": "AGRI 171", + "title": "Ethics, Laws and Policies in Agriculture", "units": 3, "year": 3, - "sem": "1", + "sem": "2", "prereqs": [] }, { - "code": "3", - "title": "3 3 3 3 3 3 18 3 3", + "code": "AGRI 195", + "title": "AGRI 195", "units": 3, "year": 3, "sem": "2", "prereqs": [] }, { - "code": "GE 3", - "title": "3 Elective", - "units": 1, - "year": 4, - "sem": "1", + "code": "FST 111", + "title": "FST 111", + "units": 3, + "year": 3, + "sem": "2", + "prereqs": [] + }, + { + "code": "FST 151", + "title": "FST 151", + "units": 3, + "year": 3, + "sem": "2", + "prereqs": [] + }, + { + "code": "FST 200", + "title": "Undergraduate Thesis", + "units": 6, + "year": 3, + "sem": "2", + "prereqs": [] + }, + { + "code": "FST 198", + "title": "FST 198", + "units": 3, + "year": 3, + "sem": "midyear", "prereqs": [] }, { "code": "FST 165", - "title": "3 Elective", - "units": 2, + "title": "Food Quality Assurance", + "units": 3, "year": 4, "sem": "1", "prereqs": [] }, { "code": "FST 170", - "title": "3 FST", - "units": 147, + "title": "Food Processing Management", + "units": 3, "year": 4, "sem": "1", "prereqs": [] }, { "code": "ABME 172", - "title": "3 FST", - "units": 175, + "title": "Product Ideation and Creation", + "units": 3, "year": 4, "sem": "1", "prereqs": [] }, { - "code": "FST 200", - "title": "", - "units": 143, + "code": "KAS 1", + "title": "Kasaysayan ng Pilipinas / Philippine History", + "units": 3, "year": 4, "sem": "1", "prereqs": [] }, { - "code": "3", - "title": "3 3 3 3 1 1 14", + "code": "FE 1", + "title": "Free Elective", + "units": 3, + "year": 4, + "sem": "2", + "prereqs": [], + "genericRequirement": "elective" + }, + { + "code": "FE 2", + "title": "Free Elective", + "units": 3, + "year": 4, + "sem": "2", + "prereqs": [], + "genericRequirement": "elective" + }, + { + "code": "FST 147", + "title": "FST 147", + "units": 3, + "year": 4, + "sem": "2", + "prereqs": [] + }, + { + "code": "FST 175", + "title": "FST 175", "units": 3, "year": 4, "sem": "2", "prereqs": [] + }, + { + "code": "FST 199", + "title": "FST 199", + "units": 1, + "year": 4, + "sem": "2", + "prereqs": [] + }, + { + "code": "AGRI 199", + "title": "Colloquium in Agriculture", + "units": 1, + "year": 4, + "sem": "2", + "prereqs": [] } ], "requiredCodes": [ @@ -4949,29 +5818,44 @@ const UPLB_PROGRAMS = { "ARTS 1", "ETHICS 1", "CHEM 18", - "3", + "MCB 11", + "MATH 27", + "CHEM 18.1", + "PHYS 51", + "PHYS 51.1", "CHEM 40", "CHEM 40.1", "ABME 10", "CHEM 32", "CHEM 32.1", "STS 1", - "3", + "STAT 101", + "MGT 101", + "CHEM 160", + "MCB 180", + "FST 101", + "FST 130", "COMM 10", "FST 102", "FST 131", "FST 140", "FST 141", "FST 167", - "MID", - "FST", - "3", - "GE 3", + "PI 10", + "AGRI 171", + "AGRI 195", + "FST 111", + "FST 151", + "FST 200", + "FST 198", "FST 165", "FST 170", "ABME 172", - "FST 200", - "3" + "KAS 1", + "FST 147", + "FST 175", + "FST 199", + "AGRI 199" ] }, "BSACHEM": { @@ -4980,10 +5864,10 @@ const UPLB_PROGRAMS = { "college": "CAFS", "collegeName": "College of Agriculture and Food Science", "available": true, - "totalUnitsRequired": 197, + "totalUnitsRequired": 190, "geCoursesRequired": 9, - "tracks": null, - "defaultTrack": null, + "hkCoursesRequired": 4, + "nstpCoursesRequired": 2, "majorCourses": [ { "code": "CHEM 18", @@ -5026,27 +5910,51 @@ const UPLB_PROGRAMS = { "prereqs": [] }, { - "code": "KAS 1/HIST 1", - "title": "Kasaysayan ng Pilipinas/Philippine History", + "code": "KAS 1", + "title": "Kasaysayan ng Pilipinas / Philippine History", "units": 3, "year": 1, "sem": "1", "prereqs": [] }, { - "code": "HK 11", - "title": "Wellness and Basic Injury Management", + "code": "CHEM 19", + "title": "Chemical Structure and Properties", "units": 3, "year": 1, - "sem": "1", + "sem": "2", "prereqs": [] }, { - "code": "CHEM 19", - "title": "Chemical Structure and Properties", - "units": 17, + "code": "MATH 28", + "title": "Analytic Geometry and Calculus III", + "units": 3, "year": 1, - "sem": "midyear", + "sem": "2", + "prereqs": [] + }, + { + "code": "BIO 30", + "title": "Genetics", + "units": 3, + "year": 1, + "sem": "2", + "prereqs": [] + }, + { + "code": "AGRI 31", + "title": "Fundamentals of Crop Science I", + "units": 3, + "year": 1, + "sem": "2", + "prereqs": [] + }, + { + "code": "CHEM 18.1", + "title": "University Chemistry Laboratory", + "units": 2, + "year": 1, + "sem": "2", "prereqs": [] }, { @@ -5063,15 +5971,12 @@ const UPLB_PROGRAMS = { "units": 3, "year": 2, "sem": "1", - "prereqs": [ - "CHEM 19", - "CHEM 32" - ] + "prereqs": [] }, { "code": "PHYS 71", "title": "University Physics I", - "units": 3, + "units": 4, "year": 2, "sem": "1", "prereqs": [] @@ -5079,7 +5984,7 @@ const UPLB_PROGRAMS = { { "code": "PHYS 71.1", "title": "University Physics I Laboratory", - "units": 3, + "units": 1, "year": 2, "sem": "1", "prereqs": [] @@ -5094,7 +5999,7 @@ const UPLB_PROGRAMS = { }, { "code": "CMSC 12", - "title": "Unknown Title", + "title": "Foundations of Computer Science", "units": 3, "year": 2, "sem": "1", @@ -5109,27 +6014,59 @@ const UPLB_PROGRAMS = { "prereqs": [] }, { - "code": "HK 12", - "title": "Unknown Title", + "code": "CHEM 32.1", + "title": "Quantitative Inorganic Analysis Laboratory", + "units": 2, + "year": 2, + "sem": "2", + "prereqs": [] + }, + { + "code": "CHEM 43.1", + "title": "Organic Chemistry I Laboratory", + "units": 2, + "year": 2, + "sem": "2", + "prereqs": [] + }, + { + "code": "PHYS 72", + "title": "University Physics II", + "units": 4, + "year": 2, + "sem": "2", + "prereqs": [] + }, + { + "code": "PHYS 72.1", + "title": "University Physics II Laboratory", + "units": 1, + "year": 2, + "sem": "2", + "prereqs": [] + }, + { + "code": "AGRI 21", + "title": "Introduction to Animal Science", "units": 3, "year": 2, - "sem": "1", + "sem": "2", "prereqs": [] }, { - "code": "tical Computing", - "title": "tical Computing", + "code": "AGRI 32", + "title": "Fundamentals of Crop Science II", "units": 3, "year": 2, "sem": "2", "prereqs": [] }, { - "code": "CHEM 32.1", - "title": "Quantitative Inorganic Analysis Laboratory", + "code": "CHEM 32", + "title": "Quantitative Inorganic Analysis", "units": 3, "year": 2, - "sem": "midyear", + "sem": "2", "prereqs": [] }, { @@ -5138,9 +6075,7 @@ const UPLB_PROGRAMS = { "units": 3, "year": 3, "sem": "1", - "prereqs": [ - "CHEM 43" - ] + "prereqs": [] }, { "code": "STAT 162", @@ -5148,9 +6083,7 @@ const UPLB_PROGRAMS = { "units": 3, "year": 3, "sem": "1", - "prereqs": [ - "STAT 101" - ] + "prereqs": [] }, { "code": "AGRI 22", @@ -5161,8 +6094,8 @@ const UPLB_PROGRAMS = { "prereqs": [] }, { - "code": "AAE 111/AAE 120", - "title": "Farm Management/Agricultural Marketing I", + "code": "AAE 111", + "title": "Farm Management", "units": 3, "year": 3, "sem": "1", @@ -5177,25 +6110,57 @@ const UPLB_PROGRAMS = { "prereqs": [] }, { - "code": "GE", - "title": "Elective", + "code": "CHEM 111", + "title": "Physical Chemistry I", "units": 3, "year": 3, - "sem": "1", + "sem": "2", + "prereqs": [] + }, + { + "code": "CHEM 131", + "title": "Technical Analysis I (Foods and Feeds)", + "units": 4, + "year": 3, + "sem": "2", "prereqs": [] }, { - "code": "NSTP 2", - "title": "National Training Service Program II", + "code": "CHEM 44.1", + "title": "Organic Chemistry II Laboratory", + "units": 2, + "year": 3, + "sem": "2", + "prereqs": [] + }, + { + "code": "COMM 10", + "title": "Critical Perspectives in Communication", "units": 3, "year": 3, - "sem": "1", + "sem": "2", "prereqs": [] }, { - "code": "CHEM 111", - "title": "Physical Chemistry I", - "units": 18, + "code": "AGRI 51", + "title": "Principles of Soil Science", + "units": 3, + "year": 3, + "sem": "2", + "prereqs": [] + }, + { + "code": "AGRI 41", + "title": "Principles of Crop Protection", + "units": 3, + "year": 3, + "sem": "2", + "prereqs": [] + }, + { + "code": "CHEM 161A", + "title": "Biochemistry I", + "units": 3, "year": 3, "sem": "midyear", "prereqs": [] @@ -5203,12 +6168,10 @@ const UPLB_PROGRAMS = { { "code": "CHEM 111.1", "title": "Physical Chemistry I Laboratory", - "units": 3, + "units": 2, "year": 4, "sem": "1", - "prereqs": [ - "CHEM 111" - ] + "prereqs": [] }, { "code": "CHEM 112", @@ -5216,9 +6179,7 @@ const UPLB_PROGRAMS = { "units": 3, "year": 4, "sem": "1", - "prereqs": [ - "CHEM 111" - ] + "prereqs": [] }, { "code": "CHEM 137", @@ -5226,10 +6187,7 @@ const UPLB_PROGRAMS = { "units": 3, "year": 4, "sem": "1", - "prereqs": [ - "CHEM 32", - "CHEM 111" - ] + "prereqs": [] }, { "code": "CHEM 161B", @@ -5237,34 +6195,104 @@ const UPLB_PROGRAMS = { "units": 3, "year": 4, "sem": "1", - "prereqs": [ - "CHEM 161A" - ] + "prereqs": [] + }, + { + "code": "CHEM 140", + "title": "Organic Analysis", + "units": 4, + "year": 4, + "sem": "1", + "prereqs": [] + }, + { + "code": "AGRI 42", + "title": "Pest Management", + "units": 3, + "year": 4, + "sem": "1", + "prereqs": [] + }, + { + "code": "MAJ 1", + "title": "Specialization", + "units": 3, + "year": 4, + "sem": "1", + "prereqs": [], + "genericRequirement": "elective" + }, + { + "code": "CHEM 112.1", + "title": "Physical Chemistry II Laboratory", + "units": 2, + "year": 4, + "sem": "2", + "prereqs": [] + }, + { + "code": "CHEM 115", + "title": "Physical Chemistry III", + "units": 3, + "year": 4, + "sem": "2", + "prereqs": [] + }, + { + "code": "CHEM 137.1", + "title": "Modern Analytical Chemistry Laboratory", + "units": 2, + "year": 4, + "sem": "2", + "prereqs": [] + }, + { + "code": "CHEM 161.1", + "title": "General Biochemistry Laboratory", + "units": 2, + "year": 4, + "sem": "2", + "prereqs": [] + }, + { + "code": "CHEM 192", + "title": "Chemical Information, Literature and Communication", + "units": 3, + "year": 4, + "sem": "2", + "prereqs": [] + }, + { + "code": "MAJ 2", + "title": "Specialization", + "units": 3, + "year": 4, + "sem": "2", + "prereqs": [], + "genericRequirement": "elective" }, { - "code": "CHEM 140", - "title": "Organic Analysis", + "code": "MAJ 3", + "title": "Specialization", "units": 3, "year": 4, - "sem": "1", - "prereqs": [ - "CHEM 44", - "CHEM 44.1" - ] + "sem": "2", + "prereqs": [], + "genericRequirement": "elective" }, { - "code": "AGRI 42", - "title": "Pest Management SPECIALIZATION", + "code": "CHEM 198", + "title": "Practicum", "units": 3, "year": 4, - "sem": "1", + "sem": "midyear", "prereqs": [] }, { "code": "CHEM 180", "title": "Environmental Chemistry", "units": 3, - "year": 4, + "year": 5, "sem": "1", "prereqs": [] }, @@ -5272,15 +6300,15 @@ const UPLB_PROGRAMS = { "code": "CHEM 120", "title": "Inorganic Chemistry", "units": 3, - "year": 4, + "year": 5, "sem": "1", "prereqs": [] }, { "code": "CHEM 199", "title": "Undergraduate Seminar", - "units": 3, - "year": 4, + "units": 1, + "year": 5, "sem": "1", "prereqs": [] }, @@ -5288,41 +6316,67 @@ const UPLB_PROGRAMS = { "code": "AGRI 171", "title": "Ethics, Laws and Policies in Agriculture", "units": 3, - "year": 4, + "year": 5, "sem": "1", "prereqs": [] }, { "code": "ACHM 200", "title": "Undergraduate Thesis", - "units": 3, - "year": 4, + "units": 6, + "year": 5, "sem": "1", "prereqs": [] }, { "code": "STS 1", - "title": "Science, Technology, and Society SPECIALIZATION", + "title": "Science, Technology, and Society", "units": 3, - "year": 4, + "year": 5, "sem": "1", "prereqs": [] }, { - "code": "CHEM 112.1", - "title": "Physical Chemistry II Laboratory", - "units": 18, - "year": 4, - "sem": "midyear", + "code": "MAJ 4", + "title": "Specialization", + "units": 3, + "year": 5, + "sem": "1", + "prereqs": [], + "genericRequirement": "elective" + }, + { + "code": "CHEM 133", + "title": "Technical Analysis II (Soils, Fertilizers, and Pesticides)", + "units": 4, + "year": 5, + "sem": "2", "prereqs": [] }, { - "code": "YEAR 3", - "title": "CHEM", - "units": 17, - "year": 4, - "sem": "midyear", + "code": "CHEM 185", + "title": "Structure and Reactivity of Agricultural Chemicals", + "units": 3, + "year": 5, + "sem": "2", + "prereqs": [] + }, + { + "code": "AGRI 199", + "title": "Colloquium in Agriculture", + "units": 1, + "year": 5, + "sem": "2", "prereqs": [] + }, + { + "code": "MAJ 5", + "title": "Specialization", + "units": 3, + "year": 5, + "sem": "2", + "prereqs": [], + "genericRequirement": "elective" } ], "requiredCodes": [ @@ -5331,8 +6385,12 @@ const UPLB_PROGRAMS = { "MCB 11", "MATH 27", "ETHICS 1", - "KAS 1/HIST 1", + "KAS 1", "CHEM 19", + "MATH 28", + "BIO 30", + "AGRI 31", + "CHEM 18.1", "ECON 11", "CHEM 43", "PHYS 71", @@ -5340,29 +6398,46 @@ const UPLB_PROGRAMS = { "BOT 20", "CMSC 12", "PI 10", - "tical Computing", "CHEM 32.1", + "CHEM 43.1", + "PHYS 72", + "PHYS 72.1", + "AGRI 21", + "AGRI 32", + "CHEM 32", "CHEM 44", "STAT 162", "AGRI 22", - "AAE 111/AAE 120", + "AAE 111", "AGRI 61", - "GE", "CHEM 111", + "CHEM 131", + "CHEM 44.1", + "COMM 10", + "AGRI 51", + "AGRI 41", + "CHEM 161A", "CHEM 111.1", "CHEM 112", "CHEM 137", "CHEM 161B", "CHEM 140", "AGRI 42", + "CHEM 112.1", + "CHEM 115", + "CHEM 137.1", + "CHEM 161.1", + "CHEM 192", + "CHEM 198", "CHEM 180", "CHEM 120", "CHEM 199", "AGRI 171", "ACHM 200", "STS 1", - "CHEM 112.1", - "FIFTH" + "CHEM 133", + "CHEM 185", + "AGRI 199" ] }, "BSABE": { @@ -5909,13 +6984,13 @@ const UPLB_PROGRAMS = { "available": true, "totalUnitsRequired": 163, "geCoursesRequired": 9, - "tracks": null, - "defaultTrack": null, + "hkCoursesRequired": 4, + "nstpCoursesRequired": 2, "majorCourses": [ { - "code": "ChE 10. Introduction to Chemical Engineering Profession", - "title": "ChE 10. Introduction to Chemical Engineering Profession", - "units": 3, + "code": "CHE 10", + "title": "Introduction to Chemical Engineering Profession", + "units": 1, "year": 1, "sem": "1", "prereqs": [] @@ -5939,7 +7014,7 @@ const UPLB_PROGRAMS = { { "code": "PHYS 51", "title": "Elements of Physics", - "units": 3, + "units": 4, "year": 1, "sem": "1", "prereqs": [] @@ -5947,7 +7022,7 @@ const UPLB_PROGRAMS = { { "code": "PHYS 51.1", "title": "Elements of Physics Laboratory", - "units": 3, + "units": 1, "year": 1, "sem": "1", "prereqs": [] @@ -5961,24 +7036,40 @@ const UPLB_PROGRAMS = { "prereqs": [] }, { - "code": "HK 11", - "title": "Wellness and Basic Injury Management", + "code": "CHEM 18.1", + "title": "University Chemistry Laboratory", + "units": 2, + "year": 1, + "sem": "2", + "prereqs": [] + }, + { + "code": "MCB 11", + "title": "Biology and Applications of Microorganisms", "units": 3, "year": 1, - "sem": "1", + "sem": "2", "prereqs": [] }, { - "code": "CHEM 18.1", - "title": "University Chemistry Laboratory", + "code": "ENSC 11", + "title": "Statics of Rigid Bodies", + "units": 3, + "year": 1, + "sem": "2", + "prereqs": [] + }, + { + "code": "MATH 28", + "title": "Analytic Geometry and Calculus III", "units": 3, "year": 1, "sem": "2", "prereqs": [] }, { - "code": "HK 12", - "title": "Unknown Title", + "code": "KAS 1", + "title": "Kasaysayan ng Pilipinas / Philippine History", "units": 3, "year": 1, "sem": "2", @@ -5986,7 +7077,7 @@ const UPLB_PROGRAMS = { }, { "code": "STS 1", - "title": "Science, Technology and 2 Society 3 GE 1", + "title": "Science, Technology and Society", "units": 3, "year": 1, "sem": "midyear", @@ -5994,16 +7085,16 @@ const UPLB_PROGRAMS = { }, { "code": "ARTS 1", - "title": "Critical Perspectives in 3 the Arts 3 3 netics (2) (3) 14", - "units": 9, + "title": "Critical Perspectives in the Arts", + "units": 3, "year": 1, "sem": "midyear", "prereqs": [] }, { - "code": "ChE 30. Fundamentals of Chemical Engineering", - "title": "ChE 30. Fundamentals of Chemical Engineering", - "units": 3, + "code": "CHE 30", + "title": "Fundamentals of Chemical Engineering", + "units": 4, "year": 2, "sem": "1", "prereqs": [] @@ -6011,7 +7102,7 @@ const UPLB_PROGRAMS = { { "code": "ENSC 10.1", "title": "Engineering Graphics Laboratory", - "units": 3, + "units": 2, "year": 2, "sem": "1", "prereqs": [] @@ -6030,25 +7121,20 @@ const UPLB_PROGRAMS = { "units": 3, "year": 2, "sem": "1", - "prereqs": [ - "CHEM 18", - "CHEM 18.1" - ] + "prereqs": [] }, { "code": "CHEM 32.1", "title": "Quantitative Inorganic Analysis Laboratory", - "units": 3, + "units": 2, "year": 2, "sem": "1", - "prereqs": [ - "CHEM 32" - ] + "prereqs": [] }, { "code": "CHEM 40", "title": "Basic Organic Chemistry", - "units": 3, + "units": 4, "year": 2, "sem": "1", "prereqs": [] @@ -6056,139 +7142,301 @@ const UPLB_PROGRAMS = { { "code": "CHEM 40.1", "title": "Basic Organic Chemistry Laboratory", - "units": 3, + "units": 1, "year": 2, "sem": "1", "prereqs": [] }, { - "code": "HK 12", - "title": "Unknown Title", + "code": "ENSC 26", + "title": "Computer Applications in Engineering", + "units": 3, + "year": 2, + "sem": "2", + "prereqs": [] + }, + { + "code": "CHE 32", + "title": "Industrial Stoichiometry", "units": 3, "year": 2, + "sem": "2", + "prereqs": [] + }, + { + "code": "ENSC 12", + "title": "Dynamics of Rigid Bodies", + "units": 3, + "year": 2, + "sem": "2", + "prereqs": [] + }, + { + "code": "ENSC 21", + "title": "Mathematical Methods in Engineering", + "units": 3, + "year": 2, + "sem": "2", + "prereqs": [] + }, + { + "code": "CHEM 111", + "title": "Physical Chemistry I", + "units": 3, + "year": 2, + "sem": "2", + "prereqs": [] + }, + { + "code": "CHEM 160", + "title": "Introductory Biochemistry", + "units": 3, + "year": 2, + "sem": "2", + "prereqs": [] + }, + { + "code": "CHE 142", + "title": "Chemical Engineering Thermodynamics I", + "units": 3, + "year": 3, + "sem": "1", + "prereqs": [] + }, + { + "code": "CHE 147", + "title": "Application of Fluid Dynamics in Chemical Engineering", + "units": 3, + "year": 3, + "sem": "1", + "prereqs": [] + }, + { + "code": "CHE 149", + "title": "Transport Phenomena", + "units": 3, + "year": 3, + "sem": "1", + "prereqs": [] + }, + { + "code": "CHE 152", + "title": "Separation Processes", + "units": 3, + "year": 3, + "sem": "1", + "prereqs": [] + }, + { + "code": "CHEM 112", + "title": "Physical Chemistry II", + "units": 3, + "year": 3, "sem": "1", "prereqs": [] }, { - "code": "NSTP 2", - "title": "National Service Training Program", + "code": "STAT 168", + "title": "Response Surface Methodology", + "units": 3, + "year": 3, + "sem": "1", + "prereqs": [] + }, + { + "code": "COMM 10", + "title": "Critical Perspectives in Communication", + "units": 3, + "year": 3, + "sem": "1", + "prereqs": [] + }, + { + "code": "CHE 143", + "title": "Chemical Engineering Thermodynamics II", + "units": 3, + "year": 3, + "sem": "2", + "prereqs": [] + }, + { + "code": "CHE 145", + "title": "Chemical Reaction Engineering", + "units": 3, + "year": 3, + "sem": "2", + "prereqs": [] + }, + { + "code": "CHE 153", + "title": "Transfer Operations I", + "units": 3, + "year": 3, + "sem": "2", + "prereqs": [] + }, + { + "code": "CHE 154", + "title": "Transfer Operations II", + "units": 3, + "year": 3, + "sem": "2", + "prereqs": [] + }, + { + "code": "ENSC 13", + "title": "Strength of Materials", + "units": 3, + "year": 3, + "sem": "2", + "prereqs": [] + }, + { + "code": "CHEM 111.1", + "title": "Physical Chemistry I Laboratory", + "units": 2, + "year": 3, + "sem": "2", + "prereqs": [] + }, + { + "code": "ENG 10", + "title": "Writing of Scientific Papers", + "units": 3, + "year": 3, + "sem": "2", + "prereqs": [] + }, + { + "code": "CHE 198", + "title": "Internship", "units": 3, - "year": 2, - "sem": "1", + "year": 3, + "sem": "midyear", "prereqs": [] }, { - "code": "ENSC 26", - "title": "Computer Applications in Engineering", + "code": "CHE 157.1", + "title": "Chemical Engineering Unit Operations Laboratory", "units": 2, - "year": 2, - "sem": "2", + "year": 4, + "sem": "1", "prereqs": [] }, { - "code": "HK 12", - "title": "Unknown Title", + "code": "CHE 191", + "title": "Special Topics", "units": 3, - "year": 2, - "sem": "2", + "year": 4, + "sem": "1", "prereqs": [] }, { - "code": "3", - "title": "3 3 3 3 3 3 3 netics (2) 21", + "code": "CHE 192", + "title": "Chemical Process Equipment Design", "units": 3, - "year": 2, - "sem": "midyear", + "year": 4, + "sem": "1", "prereqs": [] }, { - "code": "ChE 142. Chemical Engineering Thermodynamics I", - "title": "ChE 142. Chemical Engineering Thermodynamics I ChE 147. Application of Fluid Dynamics in Chemical Engineerin ChE 149. Transport Phenomena ChE 152. Separation Processes", - "units": 3, - "year": 3, + "code": "CHE 200", + "title": "Undergraduate Thesis", + "units": 6, + "year": 4, "sem": "1", "prereqs": [] }, { - "code": "CHEM 112", - "title": "Physical Chemistry II", + "code": "MAJ 1", + "title": "Cognate", "units": 3, - "year": 3, + "year": 4, "sem": "1", - "prereqs": [ - "CHEM 111" - ] + "prereqs": [], + "genericRequirement": "elective" }, { - "code": "STAT 168", - "title": "Respone Surface Methodology", + "code": "CHE 172", + "title": "Introduction to Biochemical Engineering", "units": 3, - "year": 3, + "year": 4, "sem": "1", - "prereqs": [ - "STAT 162" - ] + "prereqs": [] }, { - "code": "COMM 10", - "title": "Critical Perspectives in Communication", + "code": "CHE 180", + "title": "Agro-Industrial Waste Management", "units": 3, - "year": 3, + "year": 4, "sem": "1", "prereqs": [] }, { - "code": "II 3", - "title": "ChE", + "code": "CHE 170", + "title": "Instrumentation and Process Dynamics and Control", "units": 3, - "year": 3, + "year": 4, "sem": "2", "prereqs": [] }, { - "code": "3 ChE 198. Internship", - "title": "3 ChE 198. Internship 3 3 3 3 2 3 20", - "units": 3, - "year": 3, - "sem": "midyear", + "code": "CHE 185", + "title": "Chemical Engineering Laws, Ethics, Specifications and Contracts", + "units": 2, + "year": 4, + "sem": "2", "prereqs": [] }, { - "code": "ChE 157.1. Chemical Engineering Unit Operations Laboratory", - "title": "ChE 157.1. Chemical Engineering Unit Operations Laboratory ChE 191. Special Topics ChE 192. Chemical Process Equipment Design ChE 200/200b/200c. Undergraduate Thesis/Innovationeering/ Engineering Industry Research Cognate ChE 172. Introduction to Biochemical Engineering ChE 180. Agro-Industrial Waste Management", + "code": "CHE 193", + "title": "Plant Design", "units": 3, "year": 4, - "sem": "1", + "sem": "2", "prereqs": [] }, { - "code": "ETHICS 1", - "title": "Ethics and Moral Reasoning in Everyday Life GE", - "units": 3, + "code": "CHE 199", + "title": "Plant Inspection and Seminar", + "units": 1, "year": 4, "sem": "2", "prereqs": [] }, { - "code": "rol", - "title": "rol d Contracts 3 1 ng/ 3 3 3 3 21", - "units": 2, + "code": "MAJ 2", + "title": "Cognate", + "units": 3, "year": 4, - "sem": "midyear", + "sem": "2", + "prereqs": [], + "genericRequirement": "elective" + }, + { + "code": "ETHICS 1", + "title": "Ethics and Moral Reasoning in Everyday Life", + "units": 3, + "year": 4, + "sem": "2", "prereqs": [] } ], "requiredCodes": [ - "ChE 10. Introduction to Chemical Engineering Profession", + "CHE 10", "CHEM 18", "MATH 27", "PHYS 51", "PHYS 51.1", "PI 10", "CHEM 18.1", + "MCB 11", + "ENSC 11", + "MATH 28", + "KAS 1", "STS 1", "ARTS 1", - "ChE 30. Fundamentals of Chemical Engineering", + "CHE 30", "ENSC 10.1", "EE 1", "CHEM 32", @@ -6196,16 +7444,37 @@ const UPLB_PROGRAMS = { "CHEM 40", "CHEM 40.1", "ENSC 26", - "3", - "ChE 142. Chemical Engineering Thermodynamics I", + "CHE 32", + "ENSC 12", + "ENSC 21", + "CHEM 111", + "CHEM 160", + "CHE 142", + "CHE 147", + "CHE 149", + "CHE 152", "CHEM 112", "STAT 168", "COMM 10", - "3 ChE 143. Chemical Engineering Thermodynamics II", - "3 ChE 198. Internship", - "ChE 157.1. Chemical Engineering Unit Operations Laboratory", - "2 ChE 170. Instrumentation and Process Dynamics and Cont", - "rol" + "CHE 143", + "CHE 145", + "CHE 153", + "CHE 154", + "ENSC 13", + "CHEM 111.1", + "ENG 10", + "CHE 198", + "CHE 157.1", + "CHE 191", + "CHE 192", + "CHE 200", + "CHE 172", + "CHE 180", + "CHE 170", + "CHE 185", + "CHE 193", + "CHE 199", + "ETHICS 1" ] }, "BSCE": { @@ -6214,10 +7483,10 @@ const UPLB_PROGRAMS = { "college": "CEAT", "collegeName": "College of Engineering and Agro-Industrial Technology", "available": true, - "totalUnitsRequired": 159, + "totalUnitsRequired": 160, "geCoursesRequired": 9, - "tracks": null, - "defaultTrack": null, + "hkCoursesRequired": 4, + "nstpCoursesRequired": 2, "majorCourses": [ { "code": "ARTS 1", @@ -6230,14 +7499,14 @@ const UPLB_PROGRAMS = { { "code": "CE 10", "title": "Fundamentals of Civil Engineering", - "units": 3, + "units": 1, "year": 1, "sem": "1", "prereqs": [] }, { - "code": "GE", - "title": "Elective", + "code": "KAS 1", + "title": "Kasaysayan ng Pilipinas / Philippine History", "units": 3, "year": 1, "sem": "1", @@ -6254,7 +7523,7 @@ const UPLB_PROGRAMS = { { "code": "PHYS 71", "title": "University Physics I", - "units": 3, + "units": 4, "year": 1, "sem": "1", "prereqs": [] @@ -6262,7 +7531,7 @@ const UPLB_PROGRAMS = { { "code": "PHYS 71.1", "title": "University Physics I Laboratory", - "units": 3, + "units": 1, "year": 1, "sem": "1", "prereqs": [] @@ -6270,25 +7539,49 @@ const UPLB_PROGRAMS = { { "code": "ENSC 10.1", "title": "Engineering Graphics Laboratory", + "units": 2, + "year": 1, + "sem": "2", + "prereqs": [] + }, + { + "code": "ENSC 11", + "title": "Statics of Rigid Bodies", "units": 3, "year": 1, "sem": "2", "prereqs": [] }, { - "code": "HK 11", - "title": "Wellness and Basic Injury Management 18", + "code": "MATH 28", + "title": "Analytic Geometry and Calculus III", "units": 3, "year": 1, "sem": "2", "prereqs": [] }, { - "code": "2", - "title": "2 3 3 4 1 3 3 (2) 19", + "code": "PHYS 72", + "title": "University Physics II", + "units": 4, + "year": 1, + "sem": "2", + "prereqs": [] + }, + { + "code": "PHYS 72.1", + "title": "University Physics II Laboratory", + "units": 1, + "year": 1, + "sem": "2", + "prereqs": [] + }, + { + "code": "STS 1", + "title": "Science, Technology and Society", "units": 3, "year": 1, - "sem": "midyear", + "sem": "2", "prereqs": [] }, { @@ -6332,59 +7625,67 @@ const UPLB_PROGRAMS = { "prereqs": [] }, { - "code": "GE", - "title": "Elective", + "code": "ABE 48", + "title": "Fundamentals of Surveying", "units": 3, "year": 2, "sem": "1", "prereqs": [] }, { - "code": "ABE 48", - "title": "Fundamentals of Surveying", + "code": "CHEM 18", + "title": "University Chemistry", "units": 3, "year": 2, - "sem": "1", + "sem": "2", + "prereqs": [] + }, + { + "code": "CHEM 18.1", + "title": "University Chemistry Laboratory", + "units": 2, + "year": 2, + "sem": "2", "prereqs": [] }, { - "code": "HK 12", - "title": "Human Kinetics Activities", + "code": "CE 120", + "title": "Higher Surveying", "units": 3, "year": 2, - "sem": "1", + "sem": "2", "prereqs": [] }, { - "code": "NSTP 1", - "title": "National Service Training Program", + "code": "CE 131", + "title": "Structural Analysis I", "units": 3, "year": 2, - "sem": "1", + "sem": "2", "prereqs": [] }, { - "code": "CHEM 18", - "title": "University Chemistry", + "code": "ENSC 16", + "title": "Fluid Mechanics", "units": 3, "year": 2, "sem": "2", "prereqs": [] }, { - "code": "HK 12", - "title": "Unknown Title", + "code": "ETHICS 1", + "title": "Ethics and Moral Reasoning in Everyday Life", "units": 3, "year": 2, "sem": "2", "prereqs": [] }, { - "code": "3", - "title": "3 2 3 3 3 e 3 Kinetics (2) (3) 20", + "code": "PI 10", + "title": "The Life, Works, and Writings of Rizal", "units": 3, "year": 2, - "sem": "midyear", + "sem": "2", "prereqs": [] }, { @@ -6444,16 +7745,56 @@ const UPLB_PROGRAMS = { "prereqs": [] }, { - "code": "HK 12", - "title": "Unknown Title", + "code": "CE 122", + "title": "Transportation Engineering II", "units": 3, "year": 3, - "sem": "1", + "sem": "2", "prereqs": [] }, { - "code": "CE 122", - "title": "Transportation Engineering II", + "code": "CE 134", + "title": "Design of Reinforced Concrete Members", + "units": 4, + "year": 3, + "sem": "2", + "prereqs": [] + }, + { + "code": "CE 152", + "title": "Sanitary Engineering II", + "units": 3, + "year": 3, + "sem": "2", + "prereqs": [] + }, + { + "code": "CE 161", + "title": "Construction Materials and Testing", + "units": 3, + "year": 3, + "sem": "2", + "prereqs": [] + }, + { + "code": "CE 163", + "title": "Civil Engineering Laws, Contracts and Ethics", + "units": 2, + "year": 3, + "sem": "2", + "prereqs": [] + }, + { + "code": "ENSC 16.1", + "title": "Fluid Mechanics Laboratory", + "units": 2, + "year": 3, + "sem": "2", + "prereqs": [] + }, + { + "code": "IE 184", + "title": "Project Development and Management", "units": 3, "year": 3, "sem": "2", @@ -6477,7 +7818,7 @@ const UPLB_PROGRAMS = { }, { "code": "CE 137", - "title": "Structural Dynamics and Earhquake Engineering", + "title": "Structural Dynamics and Earthquake Engineering", "units": 3, "year": 4, "sem": "1", @@ -6494,7 +7835,7 @@ const UPLB_PROGRAMS = { { "code": "CE 164", "title": "Construction Project Planning and Management", - "units": 3, + "units": 4, "year": 4, "sem": "1", "prereqs": [] @@ -6508,56 +7849,88 @@ const UPLB_PROGRAMS = { "prereqs": [] }, { - "code": "CE", - "title": "200/200b. Thesis/Innovationeering", - "units": 3, + "code": "CE 200", + "title": "Undergraduate Thesis", + "units": 6, "year": 4, "sem": "1", "prereqs": [] }, { "code": "ABE 67", - "title": "Irrigation & Drainage Engineering", + "title": "Irrigation and Drainage Engineering", "units": 3, "year": 4, "sem": "2", "prereqs": [] }, { - "code": "ENSC 26", - "title": "Computer Applications in Engineering 19", + "code": "CE 197", + "title": "Civil Engineering Project Integration", + "units": 3, + "year": 4, + "sem": "2", + "prereqs": [] + }, + { + "code": "CE 199", + "title": "Seminar", + "units": 1, + "year": 4, + "sem": "2", + "prereqs": [] + }, + { + "code": "COMM 10", + "title": "Critical Perspectives in Communication", "units": 3, "year": 4, "sem": "2", "prereqs": [] }, { - "code": "3", - "title": "3 3 1 3 3 3 3 19", + "code": "ENG 10", + "title": "Writing of Scientific Papers", "units": 3, "year": 4, - "sem": "midyear", + "sem": "2", + "prereqs": [] + }, + { + "code": "ENSC 26", + "title": "Computer Applications in Engineering", + "units": 3, + "year": 4, + "sem": "2", "prereqs": [] } ], "requiredCodes": [ "ARTS 1", "CE 10", - "GE", + "KAS 1", "MATH 27", "PHYS 71", "PHYS 71.1", "ENSC 10.1", - "2", + "ENSC 11", + "MATH 28", + "PHYS 72", + "PHYS 72.1", + "STS 1", "EE 1", "ENSC 12", "ENSC 13", "ENSC 21", "STAT 101", - "GE", "ABE 48", "CHEM 18", - "3", + "CHEM 18.1", + "CE 120", + "CE 131", + "ENSC 16", + "ETHICS 1", + "PI 10", "ABE 57", "CE 121", "CE 132", @@ -6566,16 +7939,25 @@ const UPLB_PROGRAMS = { "CE 170", "FPPS 183", "CE 122", + "CE 134", + "CE 152", + "CE 161", + "CE 163", + "ENSC 16.1", + "IE 184", "CE 198", "CE 135", "CE 137", "CE 141", "CE 164", "CE 171", - "CE", + "CE 200", "ABE 67", - "ENSC 26", - "3" + "CE 197", + "CE 199", + "COMM 10", + "ENG 10", + "ENSC 26" ] }, "BSEE": { @@ -6584,10 +7966,10 @@ const UPLB_PROGRAMS = { "college": "CEAT", "collegeName": "College of Engineering and Agro-Industrial Technology", "available": true, - "totalUnitsRequired": 162, + "totalUnitsRequired": 156, "geCoursesRequired": 9, - "tracks": null, - "defaultTrack": null, + "hkCoursesRequired": 4, + "nstpCoursesRequired": 2, "majorCourses": [ { "code": "MATH 27", @@ -6600,7 +7982,7 @@ const UPLB_PROGRAMS = { { "code": "PHYS 71", "title": "University Physics I", - "units": 3, + "units": 4, "year": 1, "sem": "1", "prereqs": [] @@ -6608,7 +7990,7 @@ const UPLB_PROGRAMS = { { "code": "PHYS 71.1", "title": "University Physics I Laboratory", - "units": 3, + "units": 1, "year": 1, "sem": "1", "prereqs": [] @@ -6616,7 +7998,7 @@ const UPLB_PROGRAMS = { { "code": "EE 30", "title": "Introduction to Electrical Engineering", - "units": 3, + "units": 1, "year": 1, "sem": "1", "prereqs": [] @@ -6646,49 +8028,49 @@ const UPLB_PROGRAMS = { "prereqs": [] }, { - "code": "HK 11", - "title": "Wellness and Basic Injury Management", + "code": "MATH 28", + "title": "Analytic Geometry and Calculus III", "units": 3, "year": 1, - "sem": "1", + "sem": "2", "prereqs": [] }, { - "code": "NSTP 1", - "title": "National Service Training Program I", - "units": 3, + "code": "PHYS 72", + "title": "University Physics II", + "units": 4, "year": 1, - "sem": "1", + "sem": "2", "prereqs": [] }, { - "code": "MATH 28", - "title": "Analytic Geometry and Calculus III", - "units": 3, + "code": "PHYS 72.1", + "title": "University Physics II Laboratory", + "units": 1, "year": 1, "sem": "2", "prereqs": [] }, { - "code": "HK 12", - "title": "Unknown Title", - "units": 3, + "code": "ENSC 10.1", + "title": "Engineering Graphics Laboratory", + "units": 2, "year": 1, "sem": "2", "prereqs": [] }, { - "code": "3", - "title": "3 4 1 2 3 3 3 (2) (3) 19", + "code": "ENSC 11", + "title": "Statics of Rigid Bodies", "units": 3, "year": 1, - "sem": "midyear", + "sem": "2", "prereqs": [] }, { "code": "EE 40", "title": "Fundamentals of Electrical Engineering I", - "units": 3, + "units": 4, "year": 2, "sem": "1", "prereqs": [] @@ -6710,9 +8092,9 @@ const UPLB_PROGRAMS = { "prereqs": [] }, { - "code": "ENSC", - "title": "14a. Engineering Thermodynamics and Heat Transfer", - "units": 3, + "code": "ENSC 14A", + "title": "Engineering Thermodynamics and Heat Transfer", + "units": 5, "year": 2, "sem": "1", "prereqs": [] @@ -6734,35 +8116,43 @@ const UPLB_PROGRAMS = { "prereqs": [] }, { - "code": "HK 12", - "title": "Unknown Title", + "code": "EE 50", + "title": "Fundamentals of Electrical Engineering II", + "units": 4, + "year": 2, + "sem": "2", + "prereqs": [] + }, + { + "code": "EE 51", + "title": "Electromechanical Energy Conversion for DC", "units": 3, "year": 2, - "sem": "1", + "sem": "2", "prereqs": [] }, { - "code": "EE 50", - "title": "Fundamentals of Electrical Engineering II", + "code": "EE 55", + "title": "Semiconductor Devices", "units": 3, "year": 2, "sem": "2", "prereqs": [] }, { - "code": "HK 12", - "title": "Unknown Title", + "code": "ENSC 26", + "title": "Computer Applications in Engineering", "units": 3, "year": 2, "sem": "2", "prereqs": [] }, { - "code": "4", - "title": "4 3 3 3 3 3 (2) 19", + "code": "KAS 1", + "title": "Kasaysayan ng Pilipinas / Philippine History", "units": 3, "year": 2, - "sem": "midyear", + "sem": "2", "prereqs": [] }, { @@ -6776,7 +8166,7 @@ const UPLB_PROGRAMS = { { "code": "EE 65", "title": "Electronic Circuits", - "units": 3, + "units": 4, "year": 3, "sem": "1", "prereqs": [] @@ -6784,7 +8174,7 @@ const UPLB_PROGRAMS = { { "code": "EE 61", "title": "Electromechanical Energy Conversion for AC", - "units": 3, + "units": 4, "year": 3, "sem": "1", "prereqs": [] @@ -6799,7 +8189,7 @@ const UPLB_PROGRAMS = { }, { "code": "EE 66", - "title": "Signals and Noise in Electrical Engineering Network", + "title": "Signals and Noise in Electrical Engineering Networks", "units": 3, "year": 3, "sem": "1", @@ -6816,19 +8206,59 @@ const UPLB_PROGRAMS = { { "code": "EE 70", "title": "Instrumentation Engineering", + "units": 4, + "year": 3, + "sem": "2", + "prereqs": [] + }, + { + "code": "EE 75", + "title": "Digital Electronics", + "units": 4, + "year": 3, + "sem": "2", + "prereqs": [] + }, + { + "code": "EE 71", + "title": "Analysis of Power Systems", "units": 3, "year": 3, "sem": "2", "prereqs": [] }, { - "code": "EE 199", - "title": "Undergraduate Seminar 20", + "code": "EE 79", + "title": "Electrical Engineering Law, Ethics, and Contracts", + "units": 1, + "year": 3, + "sem": "2", + "prereqs": [] + }, + { + "code": "FPPS 183", + "title": "Engineering Economic Analysis", + "units": 3, + "year": 3, + "sem": "2", + "prereqs": [] + }, + { + "code": "IE 184", + "title": "Project Development and Management", "units": 3, "year": 3, "sem": "2", "prereqs": [] }, + { + "code": "EE 199", + "title": "Undergraduate Seminar", + "units": 1, + "year": 3, + "sem": "2", + "prereqs": [] + }, { "code": "EE 198", "title": "Internship", @@ -6879,26 +8309,63 @@ const UPLB_PROGRAMS = { }, { "code": "EE 200", - "title": "Unknown Title", - "units": 3, + "title": "Undergraduate Thesis", + "units": 6, "year": 4, "sem": "1", "prereqs": [] }, { - "code": "EE 200", - "title": "Unknown Title", + "code": "MAJ 1", + "title": "Specialization Course", + "units": 3, + "year": 4, + "sem": "1", + "prereqs": [], + "genericRequirement": "elective" + }, + { + "code": "MAJ 2", + "title": "Specialization Course", "units": 3, "year": 4, "sem": "2", - "prereqs": [] + "prereqs": [], + "genericRequirement": "elective" + }, + { + "code": "MAJ 3", + "title": "Specialization Course", + "units": 3, + "year": 4, + "sem": "2", + "prereqs": [], + "genericRequirement": "elective" + }, + { + "code": "FE 1", + "title": "Free Elective", + "units": 3, + "year": 4, + "sem": "2", + "prereqs": [], + "genericRequirement": "elective" + }, + { + "code": "FE 2", + "title": "Free Elective", + "units": 3, + "year": 4, + "sem": "2", + "prereqs": [], + "genericRequirement": "elective" }, { - "code": "3", - "title": "3 3 3 3 3 n 19", + "code": "EE 91", + "title": "Electrical System Design, Planning, and Estimation", "units": 4, "year": 4, - "sem": "midyear", + "sem": "2", "prereqs": [] } ], @@ -6911,15 +8378,21 @@ const UPLB_PROGRAMS = { "ARTS 1", "ETHICS 1", "MATH 28", - "3", + "PHYS 72", + "PHYS 72.1", + "ENSC 10.1", + "ENSC 11", "EE 40", "EE 45", "ENSC 12", - "ENSC", + "ENSC 14A", "ENSC 21", "STS 1", "EE 50", - "4", + "EE 51", + "EE 55", + "ENSC 26", + "KAS 1", "EE 60", "EE 65", "EE 61", @@ -6927,6 +8400,11 @@ const UPLB_PROGRAMS = { "EE 66", "ENG 10", "EE 70", + "EE 75", + "EE 71", + "EE 79", + "FPPS 183", + "IE 184", "EE 199", "EE 198", "EE 85", @@ -6934,9 +8412,8 @@ const UPLB_PROGRAMS = { "EE 86", "EE 81", "COMM 10", - "EE", - "EE", - "3" + "EE 200", + "EE 91" ] }, "BSIE": { @@ -6947,8 +8424,8 @@ const UPLB_PROGRAMS = { "available": true, "totalUnitsRequired": 160, "geCoursesRequired": 9, - "tracks": null, - "defaultTrack": null, + "hkCoursesRequired": 4, + "nstpCoursesRequired": 2, "majorCourses": [ { "code": "ARTS 1", @@ -6977,7 +8454,7 @@ const UPLB_PROGRAMS = { { "code": "IE 10", "title": "Foundations of Industrial Engineering", - "units": 3, + "units": 1, "year": 1, "sem": "1", "prereqs": [] @@ -6985,7 +8462,7 @@ const UPLB_PROGRAMS = { { "code": "PHYS 51", "title": "Elements of Physics", - "units": 3, + "units": 4, "year": 1, "sem": "1", "prereqs": [] @@ -6993,7 +8470,7 @@ const UPLB_PROGRAMS = { { "code": "PHYS 51.1", "title": "Elements of Physics Laboratory", - "units": 3, + "units": 1, "year": 1, "sem": "1", "prereqs": [] @@ -7009,57 +8486,57 @@ const UPLB_PROGRAMS = { { "code": "CHEM 18.1", "title": "University Chemistry Laboratory", - "units": 3, + "units": 2, "year": 1, "sem": "1", "prereqs": [] }, { - "code": "HK 11", - "title": "Wellness and Basic Injury Management", + "code": "ENSC 11", + "title": "Statics of Rigid Bodies", "units": 3, "year": 1, - "sem": "1", + "sem": "2", "prereqs": [] }, { - "code": "NSTP 1", - "title": "National Service Training Program I", + "code": "MATH 28", + "title": "Analytic Geometry and Calculus III", "units": 3, "year": 1, - "sem": "1", + "sem": "2", "prereqs": [] }, { - "code": "ENSC 11", - "title": "Statics of Rigid Bodies", + "code": "IE 31", + "title": "Industrial Organization and Management", "units": 3, "year": 1, "sem": "2", "prereqs": [] }, { - "code": "HK 12", - "title": "Unknown Title", + "code": "IE 21", + "title": "Industrial Processes", "units": 3, "year": 1, "sem": "2", "prereqs": [] }, { - "code": "3", - "title": "3 3 nt 3 3 ppine History 3 ed Human Kinetics (2)", + "code": "STAT 101", + "title": "Statistical Methods", "units": 3, "year": 1, - "sem": "midyear", + "sem": "2", "prereqs": [] }, { - "code": "II", - "title": "(3) 21", + "code": "KAS 1", + "title": "Kasaysayan ng Pilipinas / Philippine History", "units": 3, "year": 1, - "sem": "midyear", + "sem": "2", "prereqs": [] }, { @@ -7089,7 +8566,7 @@ const UPLB_PROGRAMS = { { "code": "IE 132", "title": "Methods of Engineering", - "units": 3, + "units": 5, "year": 2, "sem": "1", "prereqs": [] @@ -7097,38 +8574,62 @@ const UPLB_PROGRAMS = { { "code": "IE 125", "title": "Industrial Quality Control", - "units": 3, + "units": 5, "year": 2, "sem": "1", "prereqs": [] }, { - "code": "HK", - "title": "12/13. Human Kinetics Activities/Advanced Human Kineti", + "code": "ETHICS 1", + "title": "Ethics and Moral Reasoning in Everyday Life", "units": 3, "year": 2, - "sem": "1", + "sem": "2", "prereqs": [] }, { - "code": "ETHICS 1", - "title": "Ethics and Moral Reasoning in Eve", + "code": "IE 142", + "title": "Operations Research II", "units": 3, "year": 2, "sem": "2", "prereqs": [] }, { - "code": "MGT 111", - "title": "Principles of Accounting", + "code": "ENSC 12", + "title": "Dynamics of Rigid Bodies", + "units": 3, + "year": 2, + "sem": "2", + "prereqs": [] + }, + { + "code": "ENSC 10.1", + "title": "Engineering Graphics Laboratory", + "units": 2, + "year": 2, + "sem": "2", + "prereqs": [] + }, + { + "code": "IE 151", + "title": "Production Systems", + "units": 3, + "year": 2, + "sem": "2", + "prereqs": [] + }, + { + "code": "IE 150", + "title": "Systems Evaluation", "units": 3, "year": 2, "sem": "2", "prereqs": [] }, { - "code": "HK", - "title": "12/13. Human Kinetics Activities/Advanc Activities 19", + "code": "MGT 111", + "title": "Principles of Accounting", "units": 3, "year": 2, "sem": "2", @@ -7136,7 +8637,7 @@ const UPLB_PROGRAMS = { }, { "code": "IE 198", - "title": "Internship ryday Life", + "title": "Internship", "units": 3, "year": 2, "sem": "midyear", @@ -7199,20 +8700,53 @@ const UPLB_PROGRAMS = { "prereqs": [] }, { - "code": "3", - "title": "3 at Transfer 3 3 3 3 20", + "code": "ENSC 14A", + "title": "Engineering Thermodynamics and Heat Transfer", "units": 5, "year": 3, - "sem": "midyear", + "sem": "2", + "prereqs": [] + }, + { + "code": "IE 144", + "title": "Systems Simulation", + "units": 3, + "year": 3, + "sem": "2", + "prereqs": [] + }, + { + "code": "IE 184", + "title": "Project Development and Management", + "units": 3, + "year": 3, + "sem": "2", + "prereqs": [] + }, + { + "code": "IE 164", + "title": "Information Systems I", + "units": 3, + "year": 3, + "sem": "2", + "prereqs": [] + }, + { + "code": "IE 136", + "title": "Industrial Safety and Health", + "units": 3, + "year": 3, + "sem": "2", "prereqs": [] }, { - "code": "Technical Cognate", + "code": "MAJ 1", "title": "Technical Cognate", "units": 3, "year": 4, "sem": "1", - "prereqs": [] + "prereqs": [], + "genericRequirement": "elective" }, { "code": "ENG 10", @@ -7249,41 +8783,50 @@ const UPLB_PROGRAMS = { { "code": "IE 199", "title": "Undergraduate Seminar", - "units": 3, + "units": 1, "year": 4, "sem": "1", "prereqs": [] }, { - "code": "IE", - "title": "200/200b/200c. Thesis/Innovationeering/EIR", - "units": 3, + "code": "IE 200", + "title": "Undergraduate Thesis", + "units": 6, "year": 4, "sem": "1", "prereqs": [] }, + { + "code": "MAJ 2", + "title": "Technical Cognate", + "units": 3, + "year": 4, + "sem": "2", + "prereqs": [], + "genericRequirement": "elective" + }, { "code": "IE 90", "title": "Ethics for Industrial Engineers", - "units": 3, + "units": 2, "year": 4, "sem": "2", "prereqs": [] }, { - "code": "3", - "title": "3 2 tion 3 3 3", + "code": "COMM 10", + "title": "Critical Perspectives in Communication", "units": 3, "year": 4, - "sem": "midyear", + "sem": "2", "prereqs": [] }, { - "code": "IR", - "title": "3 20", + "code": "EE 1", + "title": "Basic Electrical Engineering", "units": 3, "year": 4, - "sem": "midyear", + "sem": "2", "prereqs": [] } ], @@ -7297,14 +8840,22 @@ const UPLB_PROGRAMS = { "CHEM 18", "CHEM 18.1", "ENSC 11", - "3", - "II", + "MATH 28", + "IE 31", + "IE 21", + "STAT 101", + "KAS 1", "PI 10", "ENSC 21", "IE 141", "IE 132", "IE 125", "ETHICS 1", + "IE 142", + "ENSC 12", + "ENSC 10.1", + "IE 151", + "IE 150", "MGT 111", "IE 198", "ENSC 13", @@ -7314,17 +8865,20 @@ const UPLB_PROGRAMS = { "ENSC 26", "IE 134", "ENSC 16", - "3", - "Technical Cognate", + "ENSC 14A", + "IE 144", + "IE 184", + "IE 164", + "IE 136", "ENG 10", "IE 158", "IE 165", "IE 185", "IE 199", - "IE", - "3 Technical Cognate", - "3", - "IR" + "IE 200", + "IE 90", + "COMM 10", + "EE 1" ] }, "BSME": { @@ -7335,20 +8889,20 @@ const UPLB_PROGRAMS = { "available": true, "totalUnitsRequired": 161, "geCoursesRequired": 9, - "tracks": null, - "defaultTrack": null, + "hkCoursesRequired": 4, + "nstpCoursesRequired": 2, "majorCourses": [ { - "code": "KAS 1/HIST 1", - "title": "Kasaysayan ng Pilipinas/Philippine Histor", + "code": "KAS 1", + "title": "Kasaysayan ng Pilipinas / Philippine History", "units": 3, "year": 1, "sem": "1", "prereqs": [] }, { - "code": "ME", - "title": "10*. Introduction to Mechanical Engineering", + "code": "ME 10", + "title": "Introduction to Mechanical Engineering", "units": 3, "year": 1, "sem": "1", @@ -7373,7 +8927,7 @@ const UPLB_PROGRAMS = { { "code": "CHEM 18.1", "title": "University Chemistry Laboratory", - "units": 3, + "units": 2, "year": 1, "sem": "1", "prereqs": [] @@ -7389,7 +8943,7 @@ const UPLB_PROGRAMS = { { "code": "PHYS 71", "title": "University Physics I", - "units": 3, + "units": 4, "year": 1, "sem": "1", "prereqs": [] @@ -7397,33 +8951,65 @@ const UPLB_PROGRAMS = { { "code": "PHYS 71.1", "title": "University Physics I Laboratory", + "units": 1, + "year": 1, + "sem": "1", + "prereqs": [] + }, + { + "code": "ARTS 1", + "title": "Critical Perspectives in the Arts", + "units": 3, + "year": 1, + "sem": "2", + "prereqs": [] + }, + { + "code": "EE 1", + "title": "Basic Electrical Engineering", + "units": 3, + "year": 1, + "sem": "2", + "prereqs": [] + }, + { + "code": "PI 10", + "title": "The Life, Works, and Writings of Rizal", + "units": 3, + "year": 1, + "sem": "2", + "prereqs": [] + }, + { + "code": "ENSC 11", + "title": "Statics of Rigid Bodies", "units": 3, "year": 1, - "sem": "1", + "sem": "2", "prereqs": [] }, { - "code": "HK 11", - "title": "Wellness and Basic Injury Management", + "code": "MATH 28", + "title": "Analytic Geometry and Calculus III", "units": 3, "year": 1, - "sem": "1", + "sem": "2", "prereqs": [] }, { - "code": "ARTS 1", - "title": "Critical Perspectives in the Arts y", + "code": "IE 31", + "title": "Industrial Organization and Management", "units": 3, "year": 1, "sem": "2", "prereqs": [] }, { - "code": "3", - "title": "3 3 3 3 3 3 3 (3) (2) 21", + "code": "ME 43", + "title": "Engineering Shop", "units": 3, "year": 1, - "sem": "midyear", + "sem": "2", "prereqs": [] }, { @@ -7435,9 +9021,9 @@ const UPLB_PROGRAMS = { "prereqs": [] }, { - "code": "EE", - "title": "2*. Basic Electronics Engineering", - "units": 3, + "code": "EE 2", + "title": "Basic Electronics Engineering", + "units": 4, "year": 2, "sem": "1", "prereqs": [] @@ -7467,51 +9053,59 @@ const UPLB_PROGRAMS = { "prereqs": [] }, { - "code": "ME", - "title": "113**. Manufacturing Processes", + "code": "ME 113", + "title": "Manufacturing Processes", "units": 3, "year": 2, "sem": "1", "prereqs": [] }, { - "code": "NSTP 2", - "title": "National Service Training Program", + "code": "EE 3", + "title": "DC and AC Machinery", + "units": 4, + "year": 2, + "sem": "2", + "prereqs": [] + }, + { + "code": "ENSC 15", + "title": "Fundamentals of Heat Transfer", "units": 3, "year": 2, - "sem": "1", + "sem": "2", "prereqs": [] }, { - "code": "HK 12", - "title": "Unknown Title", + "code": "ENSC 16", + "title": "Fluid Mechanics", "units": 3, "year": 2, - "sem": "1", + "sem": "2", "prereqs": [] }, { - "code": "EE 3", - "title": "Unknown Title", + "code": "ENSC 21", + "title": "Mathematical Methods in Engineering", "units": 3, "year": 2, "sem": "2", "prereqs": [] }, { - "code": "HK 12", - "title": "Unknown Title", + "code": "ME 45", + "title": "Analysis of Machine Elements", "units": 3, "year": 2, "sem": "2", "prereqs": [] }, { - "code": "4", - "title": "4 3 3 3 3 3 s (2) 19", + "code": "ME 70", + "title": "Mechanical Engineering Thermodynamics", "units": 3, "year": 2, - "sem": "midyear", + "sem": "2", "prereqs": [] }, { @@ -7531,40 +9125,40 @@ const UPLB_PROGRAMS = { "prereqs": [] }, { - "code": "ME", - "title": "53**. Machine Design for Bioproduction Systems I", + "code": "ME 53", + "title": "Machine Design for Bioproduction Systems I", "units": 3, "year": 3, "sem": "1", "prereqs": [] }, { - "code": "ME", - "title": "160*. Mechanical Vibrations", + "code": "ME 160", + "title": "Mechanical Vibrations", "units": 3, "year": 3, "sem": "1", "prereqs": [] }, { - "code": "ME", - "title": "171*. Fluid Machinery", + "code": "ME 171", + "title": "Fluid Machinery", "units": 3, "year": 3, "sem": "1", "prereqs": [] }, { - "code": "ME", - "title": "172*. Combustion Engineering", - "units": 3, + "code": "ME 172", + "title": "Combustion Engineering", + "units": 2, "year": 3, "sem": "1", "prereqs": [] }, { - "code": "ME", - "title": "175*. Refrigeration Systems", + "code": "ME 175", + "title": "Refrigeration Systems", "units": 3, "year": 3, "sem": "1", @@ -7579,24 +9173,64 @@ const UPLB_PROGRAMS = { "prereqs": [] }, { - "code": "ME 198", - "title": "Practicum", + "code": "ENG 10", + "title": "Writing of Scientific Papers", "units": 3, "year": 3, - "sem": "midyear", + "sem": "2", "prereqs": [] }, { - "code": "Elective", - "title": "GE Course", + "code": "ENSC 26", + "title": "Computer Applications in Engineering", "units": 3, - "year": 4, - "sem": "1", + "year": 3, + "sem": "2", + "prereqs": [] + }, + { + "code": "ME 154", + "title": "Design of Machinery", + "units": 3, + "year": 3, + "sem": "2", + "prereqs": [] + }, + { + "code": "ME 173.1", + "title": "Mechanical Engineering Laboratory I", + "units": 1, + "year": 3, + "sem": "2", + "prereqs": [] + }, + { + "code": "ME 176", + "title": "Air Conditioning and Ventilation Systems", + "units": 3, + "year": 3, + "sem": "2", "prereqs": [] }, { - "code": "EE", - "title": "28*. Introduction to Control Systems", + "code": "IE 184", + "title": "Project Development and Management", + "units": 3, + "year": 3, + "sem": "2", + "prereqs": [] + }, + { + "code": "ME 198", + "title": "Practicum", + "units": 3, + "year": 3, + "sem": "midyear", + "prereqs": [] + }, + { + "code": "EE 28", + "title": "Introduction to Control Systems", "units": 3, "year": 4, "sem": "1", @@ -7611,65 +9245,66 @@ const UPLB_PROGRAMS = { "prereqs": [] }, { - "code": "ME", - "title": "90*. Mechanical Engineering Laws, Ethics, Codes and", - "units": 3, + "code": "ME 90", + "title": "Mechanical Engineering Laws, Ethics, Codes and Standards", + "units": 2, "year": 4, "sem": "1", "prereqs": [] }, { - "code": "ME", - "title": "174.1*. Mechanical Engineering Laboratory II", - "units": 3, + "code": "ME 174.1", + "title": "Mechanical Engineering Laboratory II", + "units": 2, "year": 4, "sem": "1", "prereqs": [] }, { - "code": "ME", - "title": "199*. Undergraduate Seminar", - "units": 3, + "code": "ME 199", + "title": "Undergraduate Seminar", + "units": 1, "year": 4, "sem": "1", "prereqs": [] }, { - "code": "ME", - "title": "200/b/c*. Undergraduate Thesis/Engineering Industry", - "units": 3, + "code": "ME 200", + "title": "Undergraduate Thesis", + "units": 6, "year": 4, "sem": "1", "prereqs": [] }, { - "code": "Elective", - "title": "GE Course", - "units": 3, + "code": "ME 181", + "title": "Industrial Plant Engineering and Design", + "units": 4, "year": 4, "sem": "2", "prereqs": [] }, { - "code": "ME 200", - "title": "Unknown Title", - "units": 3, + "code": "ME 182", + "title": "Power Plant Engineering and Design", + "units": 4, "year": 4, "sem": "2", "prereqs": [] }, { - "code": "3", - "title": "3 3 4 4 3 3 20", + "code": "FE 1", + "title": "Free Elective", "units": 3, "year": 4, - "sem": "midyear", - "prereqs": [] + "sem": "2", + "prereqs": [], + "genericRequirement": "elective" } ], "requiredCodes": [ - "KAS 1/HIST 1", - "ME", + "KAS 1", + "ME 10", "ENSC 10.1", "CHEM 18", "CHEM 18.1", @@ -7677,34 +9312,47 @@ const UPLB_PROGRAMS = { "PHYS 71", "PHYS 71.1", "ARTS 1", - "3", + "EE 1", + "PI 10", + "ENSC 11", + "MATH 28", + "IE 31", + "ME 43", "ETHICS 1", - "EE", + "EE 2", "ENSC 12", "ENSC 13", "ENSC 14", - "ME", + "ME 113", "EE 3", - "4", + "ENSC 15", + "ENSC 16", + "ENSC 21", + "ME 45", + "ME 70", "STS 1", "IE 150", - "ME", - "ME", - "ME", - "ME", - "ME", + "ME 53", + "ME 160", + "ME 171", + "ME 172", + "ME 175", "COMM 10", + "ENG 10", + "ENSC 26", + "ME 154", + "ME 173.1", + "ME 176", + "IE 184", "ME 198", - "Elective", - "EE", + "EE 28", "STAT 101", - "ME", - "ME", - "ME", - "ME", - "Elective", - "ME", - "3" + "ME 90", + "ME 174.1", + "ME 199", + "ME 200", + "ME 181", + "ME 182" ] }, "BSMATE": { @@ -7712,7 +9360,7 @@ const UPLB_PROGRAMS = { "name": "BS Materials Engineering", "college": "CEAT", "collegeName": "College of Engineering and Agro-Industrial Technology", - "available": true, + "available": false, "totalUnitsRequired": 175, "geCoursesRequired": 9, "tracks": null, @@ -7914,7 +9562,7 @@ const UPLB_PROGRAMS = { ] }, { - "code": "PHYSICS 71", + "code": "PHYS 71", "title": "Elementary Physics I", "units": 4, "year": 4, @@ -7922,13 +9570,13 @@ const UPLB_PROGRAMS = { "prereqs": [] }, { - "code": "PHYSICS 72", + "code": "PHYS 72", "title": "Elementary Physics II", "units": 4, "year": 4, "sem": "2", "prereqs": [ - "PHYSICS 71" + "PHYS 71" ] }, { @@ -7979,8 +9627,8 @@ const UPLB_PROGRAMS = { "MATH 27", "MATH 28", "MATH 114", - "PHYSICS 71", - "PHYSICS 72", + "PHYS 71", + "PHYS 72", "CHEM 16", "CHEM 17", "ENG 10" @@ -7992,25 +9640,14 @@ const UPLB_PROGRAMS = { "college": "CEM", "collegeName": "College of Economics and Management", "available": true, - "totalUnitsRequired": 138, + "totalUnitsRequired": 135, "geCoursesRequired": 9, - "tracks": { - "thesis": { - "name": "Thesis Track", - "code": "ECON 200", - "freeElectiveUnits": 6 - }, - "sp": { - "name": "Special Problem Track", - "code": "ECON 199", - "freeElectiveUnits": 6 - } - }, - "defaultTrack": "sp", + "hkCoursesRequired": 4, + "nstpCoursesRequired": 2, "majorCourses": [ { - "code": "KAS 1/HIST 1", - "title": "Kasaysayang ng Pilipinas/Philippine History", + "code": "KAS 1", + "title": "Kasaysayan ng Pilipinas / Philippine History", "units": 3, "year": 1, "sem": "1", @@ -8026,7 +9663,7 @@ const UPLB_PROGRAMS = { }, { "code": "STS 1", - "title": "Science, Technology and Society", + "title": "Science, Technology, and Society", "units": 3, "year": 1, "sem": "1", @@ -8049,48 +9686,49 @@ const UPLB_PROGRAMS = { "prereqs": [] }, { - "code": "HK 11", - "title": "Wellness and Basic Injury Management", + "code": "MGT 101", + "title": "Concepts and Dynamics of Management", "units": 3, "year": 1, - "sem": "1", + "sem": "2", "prereqs": [] }, { - "code": "GE", - "title": "Elective", - "units": 3, + "code": "COST 10", + "title": "Introduction to Collective Action", + "units": 1, "year": 1, "sem": "2", "prereqs": [] }, { - "code": "HK 12", - "title": "Unknown Title", + "code": "ECON 101", + "title": "Intermediate Macroeconomic Theory", "units": 3, "year": 1, "sem": "2", "prereqs": [] }, { - "code": "3", - "title": "3 3 3 1 3 3 (2) 16", + "code": "ECON 102", + "title": "Intermediate Microeconomic Theory", "units": 3, "year": 1, - "sem": "midyear", + "sem": "2", "prereqs": [] }, { - "code": "GE", - "title": "Elective Language Elective", + "code": "MAJ 1", + "title": "Language Elective", "units": 3, "year": 2, "sem": "1", - "prereqs": [] + "prereqs": [], + "genericRequirement": "elective" }, { "code": "PI 10", - "title": "The Life Works and Writings of Rizal", + "title": "The Life and Works of Jose Rizal", "units": 3, "year": 2, "sem": "1", @@ -8098,7 +9736,7 @@ const UPLB_PROGRAMS = { }, { "code": "ECON 103", - "title": "Introduction to Growth Theory and Open Economy Macroeconomic", + "title": "Introduction to Growth Theory and Open Economy Macroeconomics", "units": 3, "year": 2, "sem": "1", @@ -8106,7 +9744,7 @@ const UPLB_PROGRAMS = { }, { "code": "ECON 104", - "title": "Introduction to Factor Market Analysis, General Equilibri &", + "title": "Introduction to Factor Market Analysis, General Equilibrium and Welfare Economics", "units": 3, "year": 2, "sem": "1", @@ -8121,35 +9759,44 @@ const UPLB_PROGRAMS = { "prereqs": [] }, { - "code": "NSTP 1", - "title": "National Service Training Program I HK", + "code": "ETHICS 1", + "title": "Ethics and Moral Reasoning in Everyday Life", "units": 3, "year": 2, - "sem": "1", + "sem": "2", "prereqs": [] }, { - "code": "ETHICS 1", - "title": "Ethics and Moral Reasoning in Everyday", + "code": "MAJ 2", + "title": "Language Elective", + "units": 3, + "year": 2, + "sem": "2", + "prereqs": [], + "genericRequirement": "elective" + }, + { + "code": "MGT 111", + "title": "Principles of Accounting", "units": 3, "year": 2, "sem": "2", "prereqs": [] }, { - "code": "ECON 137", - "title": "Introduction to Econometrics", + "code": "ECON 130", + "title": "Elements of Mathematical Economics", "units": 3, "year": 2, "sem": "2", "prereqs": [] }, { - "code": "Life", - "title": "Life 3 3 3 3 (3) d (2) 15", + "code": "ECON 137", + "title": "Introduction to Econometrics", "units": 3, "year": 2, - "sem": "midyear", + "sem": "2", "prereqs": [] }, { @@ -8209,78 +9856,160 @@ const UPLB_PROGRAMS = { "prereqs": [] }, { - "code": "3", - "title": "3", + "code": "ENG 10", + "title": "Writing of Scientific Papers", "units": 3, "year": 3, - "sem": "midyear", + "sem": "2", + "prereqs": [] + }, + { + "code": "ECON 141", + "title": "International Economics", + "units": 3, + "year": 3, + "sem": "2", + "prereqs": [] + }, + { + "code": "ECON 151", + "title": "Public Economics", + "units": 3, + "year": 3, + "sem": "2", "prereqs": [] }, + { + "code": "MAJ 3", + "title": "Specialization Course (Mandatory)", + "units": 3, + "year": 3, + "sem": "2", + "prereqs": [], + "genericRequirement": "elective" + }, + { + "code": "MAJ 4", + "title": "Specialization Course (Elective)", + "units": 3, + "year": 3, + "sem": "2", + "prereqs": [], + "genericRequirement": "elective" + }, { "code": "ECON 198", - "title": "Internship 3 3 3 3 3 18", + "title": "Internship", "units": 3, "year": 3, "sem": "midyear", "prereqs": [] }, { - "code": "Elective", - "title": "", + "code": "FE 1", + "title": "Free Elective", "units": 3, "year": 4, "sem": "1", - "prereqs": [] + "prereqs": [], + "genericRequirement": "elective" }, { "code": "ECON 199", "title": "Undergraduate Seminar", - "units": 3, + "units": 2, "year": 4, "sem": "1", "prereqs": [] }, { "code": "ECON 200", - "title": "Undergraduate Thesis Specialization Course", - "units": 3, + "title": "Undergraduate Thesis", + "units": 6, "year": 4, "sem": "1", "prereqs": [] }, { - "code": "Elective", - "title": "", + "code": "MAJ 5", + "title": "Specialization Course (Mandatory)", + "units": 3, + "year": 4, + "sem": "1", + "prereqs": [], + "genericRequirement": "elective" + }, + { + "code": "MAJ 6", + "title": "Specialization Course (Mandatory)", + "units": 3, + "year": 4, + "sem": "1", + "prereqs": [], + "genericRequirement": "elective" + }, + { + "code": "MAJ 7", + "title": "Specialization Course (Elective)", + "units": 3, + "year": 4, + "sem": "1", + "prereqs": [], + "genericRequirement": "elective" + }, + { + "code": "FE 2", + "title": "Free Elective", + "units": 3, + "year": 4, + "sem": "2", + "prereqs": [], + "genericRequirement": "elective" + }, + { + "code": "ECON 110", + "title": "History of Economic Doctrines", "units": 3, "year": 4, "sem": "2", "prereqs": [] }, { - "code": "3", - "title": "3 3 3 1 3 3 16", + "code": "ECON 185", + "title": "Development Economics", "units": 3, "year": 4, - "sem": "midyear", + "sem": "2", "prereqs": [] + }, + { + "code": "MAJ 8", + "title": "Specialization Course (Elective)", + "units": 3, + "year": 4, + "sem": "2", + "prereqs": [], + "genericRequirement": "elective" } ], "requiredCodes": [ - "KAS 1/HIST 1", + "KAS 1", "ARTS 1", "STS 1", "MATH 25", "ECON 11", - "GE", - "3", - "GE", + "MGT 101", + "COST 10", + "ECON 101", + "ECON 102", "PI 10", "ECON 103", "ECON 104", "STAT 101", "ETHICS 1", + "MGT 111", + "ECON 130", "ECON 137", - "Life", "ECON 115", "ECON 121", "ECON 138", @@ -8288,13 +10017,14 @@ const UPLB_PROGRAMS = { "ECON 175", "MGT 115", "COMM 10", - "3", + "ENG 10", + "ECON 141", + "ECON 151", "ECON 198", - "Elective", "ECON 199", "ECON 200", - "Elective", - "3" + "ECON 110", + "ECON 185" ] }, "BSAAE": { @@ -8303,27 +10033,29 @@ const UPLB_PROGRAMS = { "college": "CEM", "collegeName": "College of Economics and Management", "available": true, - "totalUnitsRequired": 140, + "totalUnitsRequired": 138, "geCoursesRequired": 9, + "hkCoursesRequired": 4, + "nstpCoursesRequired": 2, "tracks": { "thesis": { "name": "Thesis Track", "code": "AAE 200", "freeElectiveUnits": 0, - "majorElectiveUnits": 15 + "majorElectiveUnits": 12 }, "mfp": { "name": "Major Farm Practice Track", "code": "AAE 200A", "freeElectiveUnits": 0, - "majorElectiveUnits": 15 + "majorElectiveUnits": 12 } }, "defaultTrack": "thesis", "majorCourses": [ { - "code": "KAS 1/HIST 1", - "title": "Kasaysayan ng Pilipinas/Philippine History", + "code": "KAS 1", + "title": "Kasaysayan ng Pilipinas / Philippine History", "units": 3, "year": 1, "sem": "1", @@ -8332,7 +10064,7 @@ const UPLB_PROGRAMS = { { "code": "AAE 10", "title": "Introduction to Agricultural and Applied Economics", - "units": 3, + "units": 1, "year": 1, "sem": "1", "prereqs": [] @@ -8354,56 +10086,80 @@ const UPLB_PROGRAMS = { "prereqs": [] }, { - "code": "MATH 25", - "title": "Fundamental Calculus", + "code": "MATH 25", + "title": "Fundamental Calculus", + "units": 3, + "year": 1, + "sem": "1", + "prereqs": [] + }, + { + "code": "ECON 11", + "title": "General Economics", + "units": 3, + "year": 1, + "sem": "1", + "prereqs": [] + }, + { + "code": "COST 10", + "title": "Introduction to Collective Action", + "units": 1, + "year": 1, + "sem": "1", + "prereqs": [] + }, + { + "code": "ETHICS 1", + "title": "Ethics and Moral Reasoning in Everyday Life", "units": 3, "year": 1, - "sem": "1", + "sem": "2", "prereqs": [] }, { - "code": "ECON 11", - "title": "General Economics", + "code": "AGRI 21", + "title": "Introduction to Animal Science", "units": 3, "year": 1, - "sem": "1", + "sem": "2", "prereqs": [] }, { - "code": "HK 11", - "title": "Wellness and Basic Injury Management", + "code": "AGRI 31", + "title": "Fundamentals of Crop Science I", "units": 3, "year": 1, - "sem": "1", + "sem": "2", "prereqs": [] }, { - "code": "COST 10", - "title": "Introduction to Collective Action", + "code": "AAE 111", + "title": "Farm Management", "units": 3, "year": 1, - "sem": "1", + "sem": "2", "prereqs": [] }, { - "code": "ETHICS 1", - "title": "Ethics and Moral Reason", + "code": "ECON 102", + "title": "Intermediate Microeconomic Theory", "units": 3, "year": 1, "sem": "2", "prereqs": [] }, { - "code": "ing in Everyday Life", - "title": "ing in Everyday Life cience ence I 3 mic Theory 3 es/Advanced Human Kinetics Activities 18", - "units": 2, + "code": "STAT 101", + "title": "Statistical Methods", + "units": 3, "year": 1, - "sem": "midyear", + "sem": "2", "prereqs": [] }, { "code": "AAE 103", - "title": "Statistical Analysis of Agricultural and Applied Economics D", + "title": "Statistical Analysis of Agricultural and Applied Economics Data", "units": 3, "year": 2, "sem": "1", @@ -8450,35 +10206,43 @@ const UPLB_PROGRAMS = { "prereqs": [] }, { - "code": "HK 12", - "title": "Unknown Title", + "code": "ARTS 1", + "title": "Critical Perspectives in the Arts", "units": 3, "year": 2, - "sem": "1", + "sem": "2", "prereqs": [] }, { - "code": "NSTP 1", - "title": "National Service Training Program I", + "code": "PI 10", + "title": "The Life and Works of Jose Rizal", "units": 3, "year": 2, - "sem": "1", + "sem": "2", "prereqs": [] }, { - "code": "GE", - "title": "Elective", + "code": "HNF 141", + "title": "Food and Nutrition Systems", "units": 3, "year": 2, "sem": "2", "prereqs": [] }, { - "code": "II 18", - "title": "Unknown Title", + "code": "NRC 170", + "title": "Watershed Management", "units": 3, "year": 2, - "sem": "midyear", + "sem": "2", + "prereqs": [] + }, + { + "code": "ECON 130", + "title": "Elements of Mathematical Economics", + "units": 3, + "year": 2, + "sem": "2", "prereqs": [] }, { @@ -8499,7 +10263,7 @@ const UPLB_PROGRAMS = { }, { "code": "AAE 110", - "title": "Introduction to Agricultural and Applied Economics", + "title": "Agricultural Production Economics", "units": 3, "year": 3, "sem": "1", @@ -8523,7 +10287,7 @@ const UPLB_PROGRAMS = { }, { "code": "AAE 171", - "title": "Feasibility Studies of Agri-Based Enterprises and Agro-Indus", + "title": "Feasibility Studies of Agri-Based Enterprises and Agro-Industrial Development Projects", "units": 3, "year": 3, "sem": "1", @@ -8531,7 +10295,7 @@ const UPLB_PROGRAMS = { }, { "code": "COMM 10", - "title": "Critical Perspectives in", + "title": "Critical Perspectives in Communication", "units": 3, "year": 3, "sem": "2", @@ -8539,15 +10303,40 @@ const UPLB_PROGRAMS = { }, { "code": "AAE 195", - "title": "Introduction to Research", + "title": "Research Methods in Agricultural and Applied Economics", + "units": 3, + "year": 3, + "sem": "2", + "prereqs": [] + }, + { + "code": "AAE 161", + "title": "Food and Nutrition Economics", + "units": 3, + "year": 3, + "sem": "2", + "prereqs": [] + }, + { + "code": "AAE 123", + "title": "Agricultural and International Trade", "units": 3, "year": 3, "sem": "2", "prereqs": [] }, + { + "code": "MAJ 1", + "title": "AAE/ECON/MGT/COST Track Course", + "units": 3, + "year": 3, + "sem": "2", + "prereqs": [], + "genericRequirement": "elective" + }, { "code": "AAE 198", - "title": "Internship Communication", + "title": "Internship", "units": 3, "year": 3, "sem": "midyear", @@ -8562,40 +10351,77 @@ const UPLB_PROGRAMS = { "prereqs": [] }, { - "code": "AAE", - "title": "200/200a. Undergraduate Thesis/Major Farm Practice", - "units": 3, + "code": "AAE 200", + "title": "Undergraduate Thesis", + "units": 6, "year": 4, "sem": "1", - "prereqs": [] + "prereqs": [], + "track": "thesis" + }, + { + "code": "AAE 200A", + "title": "Major Farm Practice", + "units": 6, + "year": 4, + "sem": "1", + "prereqs": [], + "track": "mfp" }, { "code": "AAE 199", - "title": "Undergraduate Seminar Technical Track Course AAE", - "units": 3, + "title": "Undergraduate Seminar", + "units": 1, "year": 4, "sem": "1", "prereqs": [] }, + { + "code": "MAJ 2", + "title": "Technical Track Course", + "units": 3, + "year": 4, + "sem": "1", + "prereqs": [], + "genericRequirement": "elective" + }, + { + "code": "MAJ 3", + "title": "AAE/ECON/MGT/COST Track Course", + "units": 3, + "year": 4, + "sem": "1", + "prereqs": [], + "genericRequirement": "elective" + }, + { + "code": "MAJ 4", + "title": "AAE/ECON/MGT/COST Track Course", + "units": 3, + "year": 4, + "sem": "1", + "prereqs": [], + "genericRequirement": "elective" + }, { "code": "AAE 150", - "title": "Economics of Agricultura", + "title": "Economics of Agricultural Development", "units": 3, "year": 4, "sem": "2", "prereqs": [] }, { - "code": "l Development", - "title": "l Development s/Major Farm Practice ciety 3 3 15", + "code": "STS 1", + "title": "Science, Technology, and Society", "units": 3, "year": 4, - "sem": "midyear", + "sem": "2", "prereqs": [] } ], "requiredCodes": [ - "KAS 1/HIST 1", + "KAS 1", "AAE 10", "AGRI 51", "MGT 101", @@ -8603,15 +10429,22 @@ const UPLB_PROGRAMS = { "ECON 11", "COST 10", "ETHICS 1", - "ing in Everyday Life", + "AGRI 21", + "AGRI 31", + "AAE 111", + "ECON 102", + "STAT 101", "AAE 103", "AAE 120", "AAE 104", "AGRI 22", "AGRI 32", "ECON 101", - "GE", - "3", + "ARTS 1", + "PI 10", + "HNF 141", + "NRC 170", + "ECON 130", "ENG 10", "ABE 2", "AAE 110", @@ -8620,12 +10453,15 @@ const UPLB_PROGRAMS = { "AAE 171", "COMM 10", "AAE 195", + "AAE 161", + "AAE 123", "AAE 198", "AAE 151", - "AAE", + "AAE 200", + "AAE 200A", "AAE 199", "AAE 150", - "l Development" + "STS 1" ] }, "BSABME": { @@ -8634,21 +10470,10 @@ const UPLB_PROGRAMS = { "college": "CEM", "collegeName": "College of Economics and Management", "available": true, - "totalUnitsRequired": 141, + "totalUnitsRequired": 137, "geCoursesRequired": 9, - "tracks": { - "thesis": { - "name": "Thesis Track", - "code": "ABME 200", - "freeElectiveUnits": 6 - }, - "sp": { - "name": "Special Problem Track", - "code": "ABME 199", - "freeElectiveUnits": 9 - } - }, - "defaultTrack": "sp", + "hkCoursesRequired": 4, + "nstpCoursesRequired": 2, "majorCourses": [ { "code": "ABME 10", @@ -8661,7 +10486,7 @@ const UPLB_PROGRAMS = { { "code": "ABME 11", "title": "Introduction to Agribusiness Management and Entrepreneurship", - "units": 3, + "units": 1, "year": 1, "sem": "1", "prereqs": [] @@ -8676,7 +10501,7 @@ const UPLB_PROGRAMS = { }, { "code": "ABT 10", - "title": "Traditional and Modern Biotechnology", + "title": "Traditional and Modern Biotechnology: Principles and Applications", "units": 3, "year": 1, "sem": "1", @@ -8701,41 +10526,57 @@ const UPLB_PROGRAMS = { { "code": "COST 10", "title": "Introduction to Collective Action", - "units": 3, + "units": 1, "year": 1, "sem": "1", "prereqs": [] }, { - "code": "HK 11", - "title": "Wellness and Basic Injury Management", + "code": "AGRI 21", + "title": "Introduction to Animal Science", "units": 3, "year": 1, - "sem": "1", + "sem": "2", "prereqs": [] }, { - "code": "AGRI 21", - "title": "Introduction to Animal Science", + "code": "AGRI 31", + "title": "Fundamentals of Crop Science I", "units": 3, "year": 1, "sem": "2", "prereqs": [] }, { - "code": "HK 12", - "title": "Unknown Title", + "code": "MGT 101", + "title": "Concepts and Dynamics of Management", "units": 3, "year": 1, "sem": "2", "prereqs": [] }, { - "code": "3", - "title": "3 3 3 3 fe story inetics (2) 18", + "code": "MGT 111", + "title": "Principles of Accounting", "units": 3, "year": 1, - "sem": "midyear", + "sem": "2", + "prereqs": [] + }, + { + "code": "ETHICS 1", + "title": "Ethics and Moral Reasoning in Everyday Life", + "units": 3, + "year": 1, + "sem": "2", + "prereqs": [] + }, + { + "code": "KAS 1", + "title": "Kasaysayan ng Pilipinas / Philippine History", + "units": 3, + "year": 1, + "sem": "2", "prereqs": [] }, { @@ -8779,32 +10620,24 @@ const UPLB_PROGRAMS = { "prereqs": [] }, { - "code": "GE", - "title": "Elective", - "units": 3, - "year": 2, - "sem": "1", - "prereqs": [] - }, - { - "code": "HK 12", - "title": "Unknown Title", + "code": "ECON 102", + "title": "Intermediate Microeconomic Theory", "units": 3, "year": 2, - "sem": "1", + "sem": "2", "prereqs": [] }, { - "code": "NSTP 1", - "title": "National Service Training Program I", + "code": "ABE 2", + "title": "Fundamentals of Agricultural and Biosystems Engineering II", "units": 3, "year": 2, - "sem": "1", + "sem": "2", "prereqs": [] }, { - "code": "ECON 102", - "title": "Intermediate Microeconomic Theory", + "code": "MGT 113", + "title": "Managerial Accounting", "units": 3, "year": 2, "sem": "2", @@ -8812,26 +10645,26 @@ const UPLB_PROGRAMS = { }, { "code": "MGT 131", - "title": "Introduction to Human Relations and Behavior", + "title": "Introduction to Human Relations and Behavior in Organizations", "units": 3, "year": 2, "sem": "2", "prereqs": [] }, { - "code": "HK 12", - "title": "Unknown Title", + "code": "MGT 141", + "title": "Fundamentals of Operations Management", "units": 3, "year": 2, "sem": "2", "prereqs": [] }, { - "code": "3", - "title": "3 3 3 in 3 3 3 etics (2) (3) 18", + "code": "ABME 172", + "title": "Product Ideation and Creation", "units": 3, "year": 2, - "sem": "midyear", + "sem": "2", "prereqs": [] }, { @@ -8852,23 +10685,24 @@ const UPLB_PROGRAMS = { }, { "code": "AAE 111", - "title": "Farm Management Track Course", + "title": "Farm Management", "units": 3, "year": 3, "sem": "1", "prereqs": [] }, { - "code": "COMM 10", - "title": "Critical Perspective in Communication", + "code": "MAJ 1", + "title": "Track Course", "units": 3, "year": 3, "sem": "1", - "prereqs": [] + "prereqs": [], + "genericRequirement": "elective" }, { - "code": "GE", - "title": "Elective", + "code": "COMM 10", + "title": "Critical Perspectives in Communication", "units": 3, "year": 3, "sem": "1", @@ -8876,15 +10710,41 @@ const UPLB_PROGRAMS = { }, { "code": "PI 10", - "title": "The Life Works and Writing of Jose Rizal", + "title": "The Life and Works of Jose Rizal", + "units": 3, + "year": 3, + "sem": "2", + "prereqs": [] + }, + { + "code": "STAT 101", + "title": "Statistical Methods", "units": 3, "year": 3, "sem": "2", "prereqs": [] }, { - "code": "ABME", - "title": "198. 3 Internship 3 3 3 3 15", + "code": "FE 1", + "title": "Free Elective", + "units": 3, + "year": 3, + "sem": "2", + "prereqs": [], + "genericRequirement": "elective" + }, + { + "code": "MAJ 2", + "title": "Track Course", + "units": 3, + "year": 3, + "sem": "2", + "prereqs": [], + "genericRequirement": "elective" + }, + { + "code": "ABME 198", + "title": "Internship", "units": 3, "year": 3, "sem": "midyear", @@ -8900,27 +10760,38 @@ const UPLB_PROGRAMS = { }, { "code": "ARTS 1", - "title": "Critical Perspectives in the Arts Track Course", + "title": "Critical Perspectives in the Arts", "units": 3, "year": 4, "sem": "1", "prereqs": [] }, { - "code": "Elective", - "title": "", + "code": "MAJ 3", + "title": "Track Course", "units": 3, "year": 4, "sem": "1", - "prereqs": [] + "prereqs": [], + "genericRequirement": "elective" }, { - "code": "Elective", - "title": "", + "code": "FE 2", + "title": "Free Elective", "units": 3, "year": 4, "sem": "1", - "prereqs": [] + "prereqs": [], + "genericRequirement": "elective" + }, + { + "code": "FE 3", + "title": "Free Elective", + "units": 3, + "year": 4, + "sem": "1", + "prereqs": [], + "genericRequirement": "elective" }, { "code": "ABME 190", @@ -8931,12 +10802,37 @@ const UPLB_PROGRAMS = { "prereqs": [] }, { - "code": "3", - "title": "3 3 nt 3 3 15", + "code": "MGT 155", + "title": "International Marketing", "units": 3, "year": 4, - "sem": "midyear", + "sem": "2", + "prereqs": [] + }, + { + "code": "MGT 181", + "title": "Fundamentals of Strategic Business Management", + "units": 3, + "year": 4, + "sem": "2", "prereqs": [] + }, + { + "code": "STS 1", + "title": "Science, Technology, and Society", + "units": 3, + "year": 4, + "sem": "2", + "prereqs": [] + }, + { + "code": "FE 4", + "title": "Free Elective", + "units": 3, + "year": 4, + "sem": "2", + "prereqs": [], + "genericRequirement": "elective" } ], "requiredCodes": [ @@ -8948,29 +10844,35 @@ const UPLB_PROGRAMS = { "MATH 25", "COST 10", "AGRI 21", - "3", + "AGRI 31", + "MGT 101", + "MGT 111", + "ETHICS 1", + "KAS 1", "ABME 103", "AGRI 22", "AGRI 32", "MGT 121", "MGT 151", - "GE", "ECON 102", + "ABE 2", + "MGT 113", "MGT 131", - "3", + "MGT 141", + "ABME 172", "ECON 101", "MGT 109", "AAE 111", "COMM 10", - "GE", "PI 10", - "ABME", + "STAT 101", + "ABME 198", "MGT 161", "ARTS 1", - "Elective", - "Elective", "ABME 190", - "3" + "MGT 155", + "MGT 181", + "STS 1" ] }, "AAENTREP": { @@ -8978,7 +10880,7 @@ const UPLB_PROGRAMS = { "name": "Associate in Arts in Entrepreneurship", "college": "CEM", "collegeName": "College of Economics and Management", - "available": true, + "available": false, "totalUnitsRequired": 75, "geCoursesRequired": 5, "tracks": null, @@ -9021,15 +10923,7 @@ const UPLB_PROGRAMS = { "title": "Concepts and Dynamics of Management", "units": 3, "year": 1, - "sem": "1", - "prereqs": [] - }, - { - "code": "HK 11", - "title": "Wellness and Basic Injury Management", - "units": 2, - "year": 1, - "sem": "2", + "sem": "1", "prereqs": [] }, { @@ -9050,16 +10944,6 @@ const UPLB_PROGRAMS = { "MGT 101" ] }, - { - "code": "HK 12/13", - "title": "Basic or Advanced Human Kinetics Activities", - "units": 2, - "year": 2, - "sem": "1", - "prereqs": [ - "HK 11" - ] - }, { "code": "PI 10", "title": "The Life, Works, and Writings of Jose Rizal", @@ -9127,16 +11011,6 @@ const UPLB_PROGRAMS = { "sem": "2", "prereqs": [] }, - { - "code": "HK 12/13", - "title": "Basic or Advanced Human Kinetics Activities", - "units": 2, - "year": 4, - "sem": "1", - "prereqs": [ - "HK 11" - ] - }, { "code": "ETHICS 1", "title": "Ethics and Moral Reasoning for Everyday Life", @@ -9164,16 +11038,6 @@ const UPLB_PROGRAMS = { "prereqs": [ "MGT 101" ] - }, - { - "code": "HK 12/13", - "title": "Basic or Advanced Human Kinetics Activities", - "units": 2, - "year": 4, - "sem": "1", - "prereqs": [ - "HK 11" - ] } ], "requiredCodes": [ @@ -9193,10 +11057,9 @@ const UPLB_PROGRAMS = { "MGT 131", "MGT 151", "MGT 161", - "ENTR 198", - "HK 11", - "HK 12/13" - ] + "ENTR 198" + ], + "hkCoursesRequired": 4 }, "BSACCT": { "code": "BSACCT", @@ -9257,14 +11120,6 @@ const UPLB_PROGRAMS = { "sem": "2", "prereqs": [] }, - { - "code": "HK 11", - "title": "Wellness and Basic Injury Management", - "units": 2, - "year": 1, - "sem": "1", - "prereqs": [] - }, { "code": "ETHICS 1", "title": "Ethics and Moral Reasoning for Everyday Life", @@ -9315,16 +11170,6 @@ const UPLB_PROGRAMS = { "sem": "1", "prereqs": [] }, - { - "code": "HK 12/13", - "title": "Basic or Advanced Human Kinetics Activities", - "units": 2, - "year": 2, - "sem": "2", - "prereqs": [ - "HK 11" - ] - }, { "code": "BA 183", "title": "Introduction to Database Management for Accounting", @@ -9385,16 +11230,6 @@ const UPLB_PROGRAMS = { "BA 99.1" ] }, - { - "code": "HK 12/13", - "title": "Basic or Advanced Human Kinetics Activities", - "units": 2, - "year": 2, - "sem": "1", - "prereqs": [ - "HK 11" - ] - }, { "code": "MGT 141", "title": "Fundamentals of Operations Management", @@ -9425,16 +11260,6 @@ const UPLB_PROGRAMS = { "BA 99.1" ] }, - { - "code": "HK 12/13", - "title": "Basic or Advanced Human Kinetics Activities", - "units": 2, - "year": 2, - "sem": "1", - "prereqs": [ - "HK 11" - ] - }, { "code": "BA 198.1", "title": "Internship I", @@ -9719,10 +11544,9 @@ const UPLB_PROGRAMS = { "BA 190", "BA 191", "BA 198.1", - "BA 198.2", - "HK 11", - "HK 12/13" - ] + "BA 198.2" + ], + "hkCoursesRequired": 4 }, "BSFOR": { "code": "BSFOR", @@ -9730,10 +11554,10 @@ const UPLB_PROGRAMS = { "college": "CFNR", "collegeName": "College of Forestry and Natural Resources", "available": true, - "totalUnitsRequired": 143, + "totalUnitsRequired": 146, "geCoursesRequired": 9, - "tracks": null, - "defaultTrack": null, + "hkCoursesRequired": 4, + "nstpCoursesRequired": 2, "majorCourses": [ { "code": "FOR 1", @@ -9754,7 +11578,7 @@ const UPLB_PROGRAMS = { { "code": "FBS 10", "title": "Biology of Tropical Forest Plants", - "units": 3, + "units": 4, "year": 1, "sem": "1", "prereqs": [] @@ -9784,27 +11608,35 @@ const UPLB_PROGRAMS = { "prereqs": [] }, { - "code": "HK 11", - "title": "Wellness and Basic Injury Management", + "code": "SFFG 101", + "title": "Principles and Concepts of Social Forestry", "units": 3, "year": 1, - "sem": "1", + "sem": "2", "prereqs": [] }, { - "code": "SFFG 101", - "title": "Principles and Concepts of Social Forestry", + "code": "FRM 92", + "title": "Forest Engineering", "units": 3, "year": 1, "sem": "2", "prereqs": [] }, { - "code": "3", - "title": "3 3 4 3 3 3 netics Activities 19", - "units": 2, + "code": "FBS 21", + "title": "Taxonomy of Forest Plants", + "units": 4, "year": 1, - "sem": "midyear", + "sem": "2", + "prereqs": [] + }, + { + "code": "FRM 120", + "title": "Forestry Economics", + "units": 3, + "year": 1, + "sem": "2", "prereqs": [] }, { @@ -9840,8 +11672,8 @@ const UPLB_PROGRAMS = { "prereqs": [] }, { - "code": "KAS 1/HIST 1", - "title": "Kasaysayan ng Pilipinas/History of the Philippine", + "code": "KAS 1", + "title": "Kasaysayan ng Pilipinas / Philippine History", "units": 3, "year": 2, "sem": "1", @@ -9856,44 +11688,53 @@ const UPLB_PROGRAMS = { "prereqs": [] }, { - "code": "NSTP 1", - "title": "National Service Training Program I", + "code": "NRC 170", + "title": "Watershed Management", "units": 3, "year": 2, - "sem": "1", + "sem": "2", + "prereqs": [] + }, + { + "code": "FPPS 127", + "title": "Properties and Utilization of Forest Products", + "units": 4, + "year": 2, + "sem": "2", "prereqs": [] }, { - "code": "HK 12", - "title": "Unknown Title", + "code": "FBS 36", + "title": "Fundamentals of Forest Ecology", "units": 3, "year": 2, - "sem": "1", + "sem": "2", "prereqs": [] }, { - "code": "NRC 170", - "title": "Watershed Management", + "code": "SFI 103", + "title": "Fundamentals of Agroforestry", "units": 3, "year": 2, "sem": "2", "prereqs": [] }, { - "code": "3", - "title": "3 s 3 3 3 3 (3) netics Activities 19", - "units": 2, + "code": "STS 1", + "title": "Science, Technology, and Society", + "units": 3, "year": 2, - "sem": "midyear", + "sem": "2", "prereqs": [] }, { - "code": "Specialization Course", + "code": "MAJ 1", "title": "Specialization Course", "units": 3, "year": 3, "sem": "1", - "prereqs": [] + "prereqs": [], + "genericRequirement": "elective" }, { "code": "SFI 140", @@ -9906,7 +11747,7 @@ const UPLB_PROGRAMS = { { "code": "FBS 45", "title": "Forest Insect Pests and Diseases", - "units": 3, + "units": 4, "year": 3, "sem": "1", "prereqs": [] @@ -9921,12 +11762,21 @@ const UPLB_PROGRAMS = { }, { "code": "FBS 101", - "title": "Forest Biodiversity Specialization Course", + "title": "Forest Biodiversity", "units": 3, "year": 3, "sem": "1", "prereqs": [] }, + { + "code": "MAJ 2", + "title": "Specialization Course", + "units": 3, + "year": 3, + "sem": "1", + "prereqs": [], + "genericRequirement": "elective" + }, { "code": "FPPS 128", "title": "Non-Timber Forest Products", @@ -9935,6 +11785,47 @@ const UPLB_PROGRAMS = { "sem": "2", "prereqs": [] }, + { + "code": "SFI 142", + "title": "Plantation Forestry", + "units": 3, + "year": 3, + "sem": "2", + "prereqs": [] + }, + { + "code": "FBS 172", + "title": "Forest Genetics", + "units": 3, + "year": 3, + "sem": "2", + "prereqs": [] + }, + { + "code": "FOR 195", + "title": "Research Methods in Forestry and Natural Resources", + "units": 3, + "year": 3, + "sem": "2", + "prereqs": [] + }, + { + "code": "FRM 61", + "title": "Forest Biometry", + "units": 3, + "year": 3, + "sem": "2", + "prereqs": [] + }, + { + "code": "MAJ 3", + "title": "Specialization Course", + "units": 3, + "year": 3, + "sem": "2", + "prereqs": [], + "genericRequirement": "elective" + }, { "code": "FOR 198", "title": "Internship", @@ -9946,7 +11837,7 @@ const UPLB_PROGRAMS = { { "code": "FOR 200", "title": "Undergraduate Thesis", - "units": 3, + "units": 6, "year": 4, "sem": "1", "prereqs": [] @@ -9986,26 +11877,43 @@ const UPLB_PROGRAMS = { { "code": "FOR 199", "title": "Undergraduate Seminar", - "units": 3, + "units": 1, "year": 4, "sem": "1", "prereqs": [] }, { - "code": "FOR 200", - "title": "Undergraduate Thesis", + "code": "FPPS 147", + "title": "Furniture and Handicraft Production", "units": 3, "year": 4, "sem": "2", "prereqs": [] }, { - "code": "3", - "title": "3 3 3 3 3 15", + "code": "FRM 183", + "title": "Timber Production Management", "units": 3, "year": 4, - "sem": "midyear", + "sem": "2", + "prereqs": [] + }, + { + "code": "FRM 184", + "title": "Integrated Forest Resource Management", + "units": 3, + "year": 4, + "sem": "2", "prereqs": [] + }, + { + "code": "MAJ 4", + "title": "Specialization Course", + "units": 3, + "year": 4, + "sem": "2", + "prereqs": [], + "genericRequirement": "elective" } ], "requiredCodes": [ @@ -10016,21 +11924,29 @@ const UPLB_PROGRAMS = { "ARTS 1", "PI 10", "SFFG 101", - "3", + "FRM 92", + "FBS 21", + "FRM 120", "SFFG 123", "FPPS 11", "FBS 31", "NRC 140", - "KAS 1/HIST 1", + "KAS 1", "ETHICS 1", "NRC 170", - "3", - "Specialization Course", + "FPPS 127", + "FBS 36", + "SFI 103", + "STS 1", "SFI 140", "FBS 45", "SFFG 113", "FBS 101", "FPPS 128", + "SFI 142", + "FBS 172", + "FOR 195", + "FRM 61", "FOR 198", "FOR 200", "SFFG 125", @@ -10038,8 +11954,9 @@ const UPLB_PROGRAMS = { "COMM 10", "SFFG 152", "FOR 199", - "FOR 200", - "3" + "FPPS 147", + "FRM 183", + "FRM 184" ] }, "AAFOR": { @@ -10047,7 +11964,7 @@ const UPLB_PROGRAMS = { "name": "Associate in Forestry", "college": "CFNR", "collegeName": "College of Forestry and Natural Resources", - "available": true, + "available": false, "totalUnitsRequired": 72, "geCoursesRequired": 6, "tracks": null, @@ -10178,12 +12095,12 @@ const UPLB_PROGRAMS = { "available": true, "totalUnitsRequired": 140, "geCoursesRequired": 9, - "tracks": null, - "defaultTrack": null, + "hkCoursesRequired": 4, + "nstpCoursesRequired": 2, "majorCourses": [ { - "code": "KAS 1/HIST 1", - "title": "Kasaysayan ng Pilipinas/Philippine Histor", + "code": "KAS 1", + "title": "Kasaysayan ng Pilipinas / Philippine History", "units": 3, "year": 1, "sem": "1", @@ -10222,33 +12139,57 @@ const UPLB_PROGRAMS = { "prereqs": [] }, { - "code": "HK 11", - "title": "Wellness and Basic Injury Management", + "code": "HUME 112", + "title": "Sustainability Science", "units": 3, "year": 1, - "sem": "1", + "sem": "2", "prereqs": [] }, { - "code": "HUME 112", - "title": "Sustainability Science y HUME", + "code": "HUME 107", + "title": "Principles of Human Development", + "units": 3, + "year": 1, + "sem": "2", + "prereqs": [] + }, + { + "code": "HUME 105", + "title": "Humans and Their Environment", "units": 3, "year": 1, "sem": "2", "prereqs": [] }, { - "code": "HK 12", - "title": "Unknown Title", + "code": "SOC 140", + "title": "Introduction to Demography", "units": 3, "year": 1, "sem": "2", "prereqs": [] }, + { + "code": "BIO 150", + "title": "Ecology", + "units": 4, + "year": 1, + "sem": "2", + "prereqs": [] + }, { "code": "ETHICS 1", - "title": "Ethics and Moral Reasoning", - "units": 6, + "title": "Ethics and Moral Reasoning in Everyday Life", + "units": 3, + "year": 1, + "sem": "midyear", + "prereqs": [] + }, + { + "code": "ARTS 1", + "title": "Critical Perspectives in the Arts", + "units": 3, "year": 1, "sem": "midyear", "prereqs": [] @@ -10279,47 +12220,47 @@ const UPLB_PROGRAMS = { }, { "code": "PI 10", - "title": "Life and Works of Jose Rizal", + "title": "The Life and Works of Jose Rizal", "units": 3, "year": 2, "sem": "1", "prereqs": [] }, { - "code": "GE", - "title": "Elective", + "code": "STS 1", + "title": "Science, Technology, and Society", "units": 3, "year": 2, - "sem": "1", + "sem": "2", "prereqs": [] }, { - "code": "HK 12", - "title": "Unknown Title", + "code": "STAT 166", + "title": "Statistics for the Social Sciences", "units": 3, "year": 2, - "sem": "1", + "sem": "2", "prereqs": [] }, { - "code": "NSTP 1", - "title": "National Training Service Program I", + "code": "HUME 114", + "title": "Material and Energy Flows", "units": 3, "year": 2, - "sem": "1", + "sem": "2", "prereqs": [] }, { - "code": "STS 1", - "title": "Science, Technology, and Society", + "code": "CERP 161", + "title": "Planning Theory and Practice I", "units": 3, "year": 2, "sem": "2", "prereqs": [] }, { - "code": "HK 12", - "title": "Unknown Title", + "code": "HUME 113", + "title": "Community Study in Human Welfare", "units": 3, "year": 2, "sem": "2", @@ -10327,8 +12268,8 @@ const UPLB_PROGRAMS = { }, { "code": "COMM 10", - "title": "Critical Perspectives in", - "units": 9, + "title": "Critical Perspectives in Communication", + "units": 3, "year": 2, "sem": "midyear", "prereqs": [] @@ -10359,12 +12300,21 @@ const UPLB_PROGRAMS = { }, { "code": "HUME 195", - "title": "Research Methods in Human Ecology Major", + "title": "Research Methods in Human Ecology", "units": 3, "year": 3, "sem": "1", "prereqs": [] }, + { + "code": "MAJ 1", + "title": "Major", + "units": 3, + "year": 3, + "sem": "1", + "prereqs": [], + "genericRequirement": "elective" + }, { "code": "HUME 125", "title": "Human Ecological Systems Mapping", @@ -10374,71 +12324,160 @@ const UPLB_PROGRAMS = { "prereqs": [] }, { - "code": "3 Major", - "title": "3 Major 3 Major 3 Major 3 Student may take 6-9 units of major 3 courses based on their specific major program 15", - "units": 9, + "code": "HUME 124", + "title": "Environmental Health", + "units": 3, + "year": 3, + "sem": "2", + "prereqs": [] + }, + { + "code": "SDS 172", + "title": "Techniques in Community Organizing", + "units": 3, + "year": 3, + "sem": "2", + "prereqs": [] + }, + { + "code": "HFDS 110", + "title": "Migration", + "units": 3, + "year": 3, + "sem": "2", + "prereqs": [] + }, + { + "code": "MAJ 2", + "title": "Major", + "units": 3, + "year": 3, + "sem": "2", + "prereqs": [], + "genericRequirement": "elective" + }, + { + "code": "MAJ 3", + "title": "Major", + "units": 3, + "year": 3, + "sem": "midyear", + "prereqs": [], + "genericRequirement": "elective" + }, + { + "code": "MAJ 4", + "title": "Major", + "units": 3, + "year": 3, + "sem": "midyear", + "prereqs": [], + "genericRequirement": "elective" + }, + { + "code": "MAJ 5", + "title": "Major", + "units": 3, "year": 3, "sem": "midyear", + "prereqs": [], + "genericRequirement": "elective" + }, + { + "code": "HUME 123", + "title": "Climate Change Adaptation and Disaster Risk Reduction in Human Ecosystems", + "units": 3, + "year": 4, + "sem": "1", + "prereqs": [] + }, + { + "code": "HNF 141", + "title": "Food and Nutrition Systems", + "units": 3, + "year": 4, + "sem": "1", "prereqs": [] }, { - "code": "HUME 123", - "title": "Climate Change Adaptation and Disaster Risk Reduction in Hum", + "code": "MAJ 6", + "title": "Major", "units": 3, "year": 4, "sem": "1", - "prereqs": [] + "prereqs": [], + "genericRequirement": "elective" }, { - "code": "HNF 141", - "title": "Food and Nutrition Systems Major Major CERP", + "code": "MAJ 7", + "title": "Major", "units": 3, "year": 4, "sem": "1", + "prereqs": [], + "genericRequirement": "elective" + }, + { + "code": "CERP 200", + "title": "Undergraduate Thesis", + "units": 6, + "year": 4, + "sem": "1", "prereqs": [] }, { - "code": "HUME 200", - "title": "a", - "units": 3, + "code": "HUME 200A", + "title": "Supervised Field Experience", + "units": 6, "year": 4, "sem": "2", "prereqs": [] }, { - "code": "6", - "title": "6 1 3 10", - "units": 3, + "code": "HUME 199", + "title": "Seminar in Human Ecology", + "units": 1, "year": 4, - "sem": "midyear", + "sem": "2", "prereqs": [] } ], "requiredCodes": [ - "KAS 1/HIST 1", + "KAS 1", "HFDS 101", "HUME 100", "CERP 101", "SDS 101", "HUME 112", + "HUME 107", + "HUME 105", + "SOC 140", + "BIO 150", "ETHICS 1", + "ARTS 1", "HUME 110", "HUME 111", "HUME 115", "PI 10", - "GE", "STS 1", + "STAT 166", + "HUME 114", + "CERP 161", + "HUME 113", "COMM 10", "CERP 140", "HUME 122", "SDS 173", "HUME 195", "HUME 125", - "3 Major", + "HUME 124", + "SDS 172", + "HFDS 110", "HUME 123", "HNF 141", - "HUME 200", - "6" + "CERP 200", + "HUME 200A", + "HUME 199" ] }, "BSNUTRI": { @@ -10447,26 +12486,15 @@ const UPLB_PROGRAMS = { "college": "CHE", "collegeName": "College of Human Ecology", "available": true, - "totalUnitsRequired": 165, + "totalUnitsRequired": 145, "geCoursesRequired": 9, - "tracks": { - "thesis": { - "name": "Thesis Track", - "code": "NUT 200", - "freeElectiveUnits": 9 - }, - "sp": { - "name": "Special Problem Track", - "code": "NUT 190", - "freeElectiveUnits": 12 - } - }, - "defaultTrack": "sp", + "hkCoursesRequired": 4, + "nstpCoursesRequired": 2, "majorCourses": [ { "code": "ABT 11", "title": "Introduction to Agricultural Biotechnology", - "units": 3, + "units": 1, "year": 1, "sem": "1", "prereqs": [] @@ -10514,41 +12542,57 @@ const UPLB_PROGRAMS = { { "code": "CHEM 18.1", "title": "University Chemistry Laboratory", - "units": 3, + "units": 2, "year": 1, "sem": "1", "prereqs": [] }, { - "code": "HK 11", - "title": "Wellness and Basic Injury Management", + "code": "KAS 1", + "title": "Kasaysayan ng Pilipinas / Philippine History", "units": 3, "year": 1, - "sem": "1", + "sem": "2", "prereqs": [] }, { - "code": "KAS 1", - "title": "Unknown Title", + "code": "ETHICS 1", + "title": "Ethics and Moral Reasoning in Everyday Life", "units": 3, "year": 1, "sem": "2", "prereqs": [] }, { - "code": "HK 12", - "title": "Unknown Title", - "units": 3, + "code": "CHEM 40", + "title": "Organic Chemistry", + "units": 4, + "year": 1, + "sem": "2", + "prereqs": [] + }, + { + "code": "CHEM 40.1", + "title": "Organic Chemistry Laboratory", + "units": 1, "year": 1, "sem": "2", "prereqs": [] }, { - "code": "3", - "title": "3 3 3 4 1 3 4 (2) 21", + "code": "MCB 11", + "title": "Biology and Applications of Microorganisms", "units": 3, "year": 1, - "sem": "midyear", + "sem": "2", + "prereqs": [] + }, + { + "code": "HNF 101", + "title": "Food Selection and Preparation", + "units": 4, + "year": 1, + "sem": "2", "prereqs": [] }, { @@ -10570,7 +12614,7 @@ const UPLB_PROGRAMS = { { "code": "HNF 122", "title": "Food and Nutrition", - "units": 3, + "units": 4, "year": 2, "sem": "1", "prereqs": [] @@ -10594,57 +12638,49 @@ const UPLB_PROGRAMS = { { "code": "HNF 102", "title": "Meal Management", - "units": 3, - "year": 2, - "sem": "1", - "prereqs": [] - }, - { - "code": "HK 12", - "title": "Unknown Title", - "units": 3, + "units": 4, "year": 2, "sem": "1", "prereqs": [] }, { - "code": "NSTP 1", - "title": "National Service Training Program I", + "code": "STS 1", + "title": "Science, Technology, and Society", "units": 3, "year": 2, - "sem": "1", + "sem": "2", "prereqs": [] }, { - "code": "STS 1", - "title": "Science, Technology, and Society", + "code": "PI 10", + "title": "The Life and Works of Jose Rizal", "units": 3, "year": 2, "sem": "2", "prereqs": [] }, { - "code": "HK 12", - "title": "Unknown Title", + "code": "STAT 101", + "title": "Statistical Methods", "units": 3, "year": 2, "sem": "2", "prereqs": [] }, { - "code": "NSTP 2", - "title": "National Service Training Program II 20", + "code": "MGT 111", + "title": "Principles of Accounting", "units": 3, "year": 2, "sem": "2", "prereqs": [] }, { - "code": "3", - "title": "3 3 3 3 3 3 3 cs (2) (3) 21", + "code": "HNF 123", + "title": "Nutritional Assessment", "units": 3, "year": 2, - "sem": "midyear", + "sem": "2", "prereqs": [] }, { @@ -10657,7 +12693,7 @@ const UPLB_PROGRAMS = { }, { "code": "HNF 104", - "title": "Food Service System I", + "title": "Food Service Systems I", "units": 3, "year": 3, "sem": "1", @@ -10666,7 +12702,7 @@ const UPLB_PROGRAMS = { { "code": "HNF 127", "title": "Nutrition in the Life Stages", - "units": 3, + "units": 4, "year": 3, "sem": "1", "prereqs": [] @@ -10674,7 +12710,7 @@ const UPLB_PROGRAMS = { { "code": "HNF 128", "title": "Medical Nutrition Therapy I", - "units": 3, + "units": 5, "year": 3, "sem": "1", "prereqs": [] @@ -10696,52 +12732,68 @@ const UPLB_PROGRAMS = { "prereqs": [] }, { - "code": "COMM 10", - "title": "Critical Perspectives in Communication 18", + "code": "HNF 143", + "title": "Nutrition Education", "units": 3, "year": 3, "sem": "2", "prereqs": [] }, + { + "code": "HNF 129", + "title": "Medical Nutrition Therapy II", + "units": 5, + "year": 3, + "sem": "2", + "prereqs": [] + }, { "code": "HNF 200", "title": "Undergraduate Thesis", + "units": 6, + "year": 3, + "sem": "2", + "prereqs": [] + }, + { + "code": "HNF 126", + "title": "Sports Nutrition", + "units": 2, + "year": 3, + "sem": "2", + "prereqs": [] + }, + { + "code": "COMM 10", + "title": "Critical Perspectives in Communication", "units": 3, "year": 3, - "sem": "midyear", + "sem": "2", "prereqs": [] }, { "code": "HNF 199", "title": "Undergraduate Seminar", - "units": 3, + "units": 1, "year": 4, "sem": "1", "prereqs": [] }, { - "code": "HNF", - "title": "200a. Practicum I", - "units": 3, + "code": "HNF 200A", + "title": "Practicum I", + "units": 12, "year": 4, "sem": "1", "prereqs": [] }, { - "code": "HNF 200", - "title": "b", - "units": 3, + "code": "HNF 200B", + "title": "Practicum II", + "units": 12, "year": 4, "sem": "2", "prereqs": [] - }, - { - "code": "12", - "title": "12 12", - "units": 3, - "year": 4, - "sem": "midyear", - "prereqs": [] } ], "requiredCodes": [ @@ -10753,7 +12805,11 @@ const UPLB_PROGRAMS = { "CHEM 18", "CHEM 18.1", "KAS 1", - "3", + "ETHICS 1", + "CHEM 40", + "CHEM 40.1", + "MCB 11", + "HNF 101", "MGT 101", "HNF 103", "HNF 122", @@ -10761,19 +12817,24 @@ const UPLB_PROGRAMS = { "CHEM 160", "HNF 102", "STS 1", - "3", + "PI 10", + "STAT 101", + "MGT 111", + "HNF 123", "HNF 142", "HNF 104", "HNF 127", "HNF 128", "HNF 195", "HNF 105", - "COMM 10", + "HNF 143", + "HNF 129", "HNF 200", + "HNF 126", + "COMM 10", "HNF 199", - "HNF", - "HNF 200", - "12" + "HNF 200A", + "HNF 200B" ] }, "BSDC": { @@ -10782,10 +12843,10 @@ const UPLB_PROGRAMS = { "college": "CDC", "collegeName": "College of Development Communication", "available": true, - "totalUnitsRequired": 142, + "totalUnitsRequired": 145, "geCoursesRequired": 9, - "tracks": null, - "defaultTrack": null, + "hkCoursesRequired": 4, + "nstpCoursesRequired": 2, "majorCourses": [ { "code": "ARTS 1", @@ -10813,7 +12874,7 @@ const UPLB_PROGRAMS = { }, { "code": "DEVC 10", - "title": "Introduction to Development Communicatio", + "title": "Introduction to Development Communication", "units": 3, "year": 1, "sem": "1", @@ -10821,7 +12882,7 @@ const UPLB_PROGRAMS = { }, { "code": "DEVC 11", - "title": "Introductios to Media Writing for Development", + "title": "Introduction to Media Writing for Development", "units": 3, "year": 1, "sem": "1", @@ -10836,24 +12897,32 @@ const UPLB_PROGRAMS = { "prereqs": [] }, { - "code": "HK 11", - "title": "Wellness and Basic Injury Management", + "code": "KAS 1", + "title": "Kasaysayan ng Pilipinas / Philippine History", "units": 3, "year": 1, - "sem": "1", + "sem": "2", + "prereqs": [] + }, + { + "code": "ETHICS 1", + "title": "Ethics and Moral Reasoning in Everyday Life", + "units": 3, + "year": 1, + "sem": "2", "prereqs": [] }, { - "code": "NSTP 1", - "title": "National Training Service Program I", + "code": "DEVC 20", + "title": "Fundamentals of Development Journalism", "units": 3, "year": 1, - "sem": "1", + "sem": "2", "prereqs": [] }, { - "code": "KAS 1", - "title": "Unknown Title", + "code": "DEVC 30", + "title": "Fundamentals of Community Broadcasting", "units": 3, "year": 1, "sem": "2", @@ -10861,16 +12930,16 @@ const UPLB_PROGRAMS = { }, { "code": "DEVC 40", - "title": "Fundamentals of Educational Communication and", + "title": "Fundamentals of Educational Communication and Technology", "units": 3, "year": 1, "sem": "2", "prereqs": [] }, { - "code": "HK 12", - "title": "Unknown Title", - "units": 18, + "code": "DEVC 50", + "title": "Introduction to Science Communication", + "units": 3, "year": 1, "sem": "2", "prereqs": [] @@ -10884,72 +12953,74 @@ const UPLB_PROGRAMS = { "prereqs": [] }, { - "code": "GE", - "title": "Elective", + "code": "DEVC 70", + "title": "Interpersonal Communication in Development", "units": 3, "year": 2, "sem": "1", "prereqs": [] }, { - "code": "GE", - "title": "Elective", + "code": "DEVC 80", + "title": "Communication and Society", "units": 3, "year": 2, "sem": "1", "prereqs": [] }, { - "code": "DEVC 70", - "title": "Interpersonal Communication in Developme", + "code": "DEVC 101", + "title": "Introduction to Communication Theory", "units": 3, "year": 2, "sem": "1", "prereqs": [] }, { - "code": "DEVC 80", - "title": "Communication and Society", + "code": "DEVC 125", + "title": "Writing and Reporting for Development", "units": 3, "year": 2, - "sem": "1", + "sem": "2", "prereqs": [] }, { - "code": "DEVC 101", - "title": "Introduction to Communication Theory", + "code": "DEVC 135", + "title": "Multi-media Materials Planning and Design", "units": 3, "year": 2, - "sem": "1", + "sem": "2", "prereqs": [] }, { - "code": "HK 12", - "title": "Unknown Title", + "code": "STAT 166", + "title": "Statistics for the Social Sciences", "units": 3, "year": 2, - "sem": "1", + "sem": "2", "prereqs": [] }, { - "code": "3", - "title": "3", + "code": "MAJ 1", + "title": "Elective (Technical)", "units": 3, "year": 2, "sem": "2", - "prereqs": [] + "prereqs": [], + "genericRequirement": "elective" }, { - "code": "DEVC 125", - "title": "Writing and Reporting for Development", - "units": 15, + "code": "MAJ 2", + "title": "Elective (Technical)", + "units": 3, "year": 2, "sem": "2", - "prereqs": [] + "prereqs": [], + "genericRequirement": "elective" }, { - "code": "3. Visual and Audiovisual Media Production", - "title": "3. Visual and Audiovisual Media Production 3 3", + "code": "DEVC 103", + "title": "Visual and Audiovisual Media Production", "units": 3, "year": 2, "sem": "midyear", @@ -10980,48 +13051,77 @@ const UPLB_PROGRAMS = { "prereqs": [] }, { - "code": "GE", - "title": "Elective", + "code": "MAJ 3", + "title": "Elective (Technical)", "units": 3, "year": 3, "sem": "1", - "prereqs": [] + "prereqs": [], + "genericRequirement": "elective" }, { - "code": "Elective", - "title": "(Technical)", + "code": "MAJ 4", + "title": "Elective (Technical)", "units": 3, "year": 3, "sem": "1", - "prereqs": [] + "prereqs": [], + "genericRequirement": "elective" }, { - "code": "Elective", - "title": "(Technical)", + "code": "DEVC 136", + "title": "Multi-media Materials Production and Management", "units": 3, "year": 3, - "sem": "1", + "sem": "2", "prereqs": [] }, { - "code": "HK 12", - "title": "Unknown Title", + "code": "DEVC 154", + "title": "Communicating Science for Development", "units": 3, "year": 3, - "sem": "1", + "sem": "2", "prereqs": [] }, { - "code": "DEVC 136", - "title": "Multi-media Materials Production and Management", + "code": "DEVC 195", + "title": "Introduction to Communication Research", "units": 3, "year": 3, "sem": "2", "prereqs": [] }, { - "code": "Internship", - "title": "Internship 3", + "code": "MAJ 5", + "title": "Elective (Technical)", + "units": 3, + "year": 3, + "sem": "2", + "prereqs": [], + "genericRequirement": "elective" + }, + { + "code": "MAJ 6", + "title": "Elective (SOSC)", + "units": 3, + "year": 3, + "sem": "2", + "prereqs": [], + "genericRequirement": "elective" + }, + { + "code": "MAJ 7", + "title": "Elective (SOSC)", + "units": 3, + "year": 3, + "sem": "2", + "prereqs": [], + "genericRequirement": "elective" + }, + { + "code": "DEVC 198", + "title": "Internship", "units": 3, "year": 3, "sem": "midyear", @@ -11037,7 +13137,7 @@ const UPLB_PROGRAMS = { }, { "code": "DEVC 145", - "title": "Distance Learning Systems in Developmen Communication", + "title": "Distance Learning Systems in Development Communication", "units": 3, "year": 4, "sem": "1", @@ -11046,42 +13146,78 @@ const UPLB_PROGRAMS = { { "code": "DEVC 200", "title": "Undergraduate Thesis", - "units": 3, + "units": 6, "year": 4, "sem": "1", "prereqs": [] }, { - "code": "Elective", - "title": "(Technical)", + "code": "MAJ 8", + "title": "Elective (Technical)", "units": 3, "year": 4, "sem": "1", - "prereqs": [] + "prereqs": [], + "genericRequirement": "elective" }, { - "code": "Elective", - "title": "(Technical)", + "code": "MAJ 9", + "title": "Elective (Technical)", "units": 3, "year": 4, "sem": "1", - "prereqs": [] + "prereqs": [], + "genericRequirement": "elective" }, { - "code": "Elective", - "title": "(SOSC)", + "code": "MAJ 10", + "title": "Elective (SOSC)", "units": 3, "year": 4, "sem": "1", - "prereqs": [] + "prereqs": [], + "genericRequirement": "elective" }, { "code": "DEVC 128", "title": "Science Journalism", - "units": 16, + "units": 3, + "year": 4, + "sem": "2", + "prereqs": [] + }, + { + "code": "DEVC 155", + "title": "Knowledge Management for Development", + "units": 3, + "year": 4, + "sem": "2", + "prereqs": [] + }, + { + "code": "DEVC 180", + "title": "Communication Campaigns and Programs", + "units": 3, + "year": 4, + "sem": "2", + "prereqs": [] + }, + { + "code": "DEVC 199", + "title": "Undergraduate Seminar", + "units": 1, "year": 4, "sem": "2", "prereqs": [] + }, + { + "code": "MAJ 11", + "title": "Elective (SOSC)", + "units": 3, + "year": 4, + "sem": "2", + "prereqs": [], + "genericRequirement": "elective" } ], "requiredCodes": [ @@ -11091,32 +13227,34 @@ const UPLB_PROGRAMS = { "DEVC 10", "DEVC 11", "ECON 11", - "KAS 1/HIST 1", + "KAS 1", + "ETHICS 1", + "DEVC 20", + "DEVC 30", "DEVC 40", + "DEVC 50", "COMM 10", - "GE", - "GE", "DEVC 70", "DEVC 80", "DEVC 101", - "3", "DEVC 125", - "3. Visual and Audiovisual Media Production", + "DEVC 135", + "STAT 166", + "DEVC 103", "DEVC 126", "DEVC 144", "DEVC 153", - "GE", - "Elective", - "Elective", "DEVC 136", - "Internship", + "DEVC 154", + "DEVC 195", + "DEVC 198", "DEVC 127", "DEVC 145", "DEVC 200", - "Elective", - "Elective", - "Elective", - "DEVC 128" + "DEVC 128", + "DEVC 155", + "DEVC 180", + "DEVC 199" ] }, "ASDC": { @@ -11124,7 +13262,7 @@ const UPLB_PROGRAMS = { "name": "Associate of Science in Development Communication", "college": "CDC", "collegeName": "College of Development Communication", - "available": true, + "available": false, "totalUnitsRequired": 72, "geCoursesRequired": 6, "tracks": null, @@ -11213,10 +13351,10 @@ const UPLB_PROGRAMS = { "college": "CVM", "collegeName": "College of Veterinary Medicine", "available": true, - "totalUnitsRequired": 220, + "totalUnitsRequired": 221, "geCoursesRequired": 9, - "tracks": null, - "defaultTrack": null, + "hkCoursesRequired": 4, + "nstpCoursesRequired": 2, "majorCourses": [ { "code": "ETHICS 1", @@ -11227,8 +13365,8 @@ const UPLB_PROGRAMS = { "prereqs": [] }, { - "code": "KAS 1/HIST 1", - "title": "Kasaysayan ng Pilipinas/Philippine History", + "code": "KAS 1", + "title": "Kasaysayan ng Pilipinas / Philippine History", "units": 3, "year": 1, "sem": "1", @@ -11263,28 +13401,36 @@ const UPLB_PROGRAMS = { "title": "University Chemistry", "units": 3, "year": 1, - "sem": "1", + "sem": "1", + "prereqs": [] + }, + { + "code": "ARTS 1", + "title": "Critical Perspectives in the Arts", + "units": 3, + "year": 1, + "sem": "2", "prereqs": [] }, { - "code": "NSTP 1", - "title": "National Training Service Program I", - "units": 3, + "code": "CHEM 18.1", + "title": "University Chemistry Laboratory", + "units": 2, "year": 1, - "sem": "1", + "sem": "2", "prereqs": [] }, { - "code": "HK 11", - "title": "Wellness and Basic Injury Management", + "code": "STAT 164", + "title": "Statistics for the Biological Sciences", "units": 3, "year": 1, - "sem": "1", + "sem": "2", "prereqs": [] }, { - "code": "ARTS 1", - "title": "Critical Perspectives in the Arts", + "code": "ZOTC 111", + "title": "General Principles of Animal Production", "units": 3, "year": 1, "sem": "2", @@ -11293,25 +13439,17 @@ const UPLB_PROGRAMS = { { "code": "CHEM 40", "title": "Basic Organic Chemistry", - "units": 2, + "units": 4, "year": 1, "sem": "midyear", "prereqs": [] }, { - "code": "GE", - "title": "Elective", - "units": 3, - "year": 2, - "sem": "1", - "prereqs": [] - }, - { - "code": "GE", - "title": "Elective", - "units": 3, - "year": 2, - "sem": "1", + "code": "CHEM 40.1", + "title": "Basic Organic Chemistry Laboratory", + "units": 1, + "year": 1, + "sem": "midyear", "prereqs": [] }, { @@ -11341,39 +13479,55 @@ const UPLB_PROGRAMS = { { "code": "VPHY 140", "title": "Biochemical Aspects of Animal Processes", - "units": 3, + "units": 2, "year": 2, "sem": "1", "prereqs": [] }, { - "code": "HK 12", - "title": "Unknown Title", + "code": "COMM 10", + "title": "Critical Perspectives in Communication", "units": 3, "year": 2, - "sem": "1", + "sem": "2", "prereqs": [] }, { - "code": "COMM 10", - "title": "Critical Perspectives in Communicati", + "code": "VETA 101", + "title": "Macroscopic Anatomy I", + "units": 5, + "year": 2, + "sem": "2", + "prereqs": [] + }, + { + "code": "VETA 105", + "title": "Microscopic and Developmental Anatomy I", "units": 3, "year": 2, "sem": "2", "prereqs": [] }, { - "code": "HK", - "title": "12/13. Human Kinetics Activities/Advanced 2 s 17", - "units": 2, + "code": "ZOTC 113", + "title": "Principles of Animal Nutrition", + "units": 3, + "year": 2, + "sem": "2", + "prereqs": [] + }, + { + "code": "VPHY 141", + "title": "General Physiology", + "units": 4, "year": 2, "sem": "2", "prereqs": [] }, { "code": "VMCB 121", - "title": "General Microbiology on", - "units": 2, + "title": "General Microbiology", + "units": 4, "year": 2, "sem": "midyear", "prereqs": [] @@ -11381,7 +13535,7 @@ const UPLB_PROGRAMS = { { "code": "VETA 102", "title": "Macroscopic Anatomy II", - "units": 3, + "units": 4, "year": 3, "sem": "1", "prereqs": [] @@ -11389,7 +13543,7 @@ const UPLB_PROGRAMS = { { "code": "VETA 106", "title": "Microscopic and Developmental Anatomy II", - "units": 3, + "units": 4, "year": 3, "sem": "1", "prereqs": [] @@ -11397,7 +13551,7 @@ const UPLB_PROGRAMS = { { "code": "VPHY 142", "title": "Systemic Physiology", - "units": 3, + "units": 4, "year": 3, "sem": "1", "prereqs": [] @@ -11418,9 +13572,49 @@ const UPLB_PROGRAMS = { "sem": "2", "prereqs": [] }, + { + "code": "VPAR 131", + "title": "Veterinary Entomology and Protozoology", + "units": 4, + "year": 3, + "sem": "2", + "prereqs": [] + }, + { + "code": "VPHM 141", + "title": "Veterinary Pharmacology and Therapeutics I", + "units": 4, + "year": 3, + "sem": "2", + "prereqs": [] + }, + { + "code": "ZOTC 115", + "title": "Ruminant Production", + "units": 3, + "year": 3, + "sem": "2", + "prereqs": [] + }, + { + "code": "VMCB 122", + "title": "Veterinary Bacteriology and Mycology", + "units": 3, + "year": 3, + "sem": "2", + "prereqs": [] + }, { "code": "VPTH 121", "title": "General Pathology", + "units": 4, + "year": 3, + "sem": "midyear", + "prereqs": [] + }, + { + "code": "ZOTC 116", + "title": "Poultry Production", "units": 3, "year": 3, "sem": "midyear", @@ -11429,7 +13623,7 @@ const UPLB_PROGRAMS = { { "code": "VMCB 124", "title": "Fundamentals of Immunology", - "units": 3, + "units": 2, "year": 4, "sem": "1", "prereqs": [] @@ -11437,7 +13631,7 @@ const UPLB_PROGRAMS = { { "code": "VPAR 132", "title": "Veterinary Helminthology", - "units": 3, + "units": 4, "year": 4, "sem": "1", "prereqs": [] @@ -11453,7 +13647,7 @@ const UPLB_PROGRAMS = { { "code": "VPHM 142", "title": "Veterinary Pharmacology and Therapeutics II", - "units": 3, + "units": 4, "year": 4, "sem": "1", "prereqs": [] @@ -11469,7 +13663,7 @@ const UPLB_PROGRAMS = { { "code": "ZOTC 117", "title": "Equine Production", - "units": 3, + "units": 2, "year": 4, "sem": "1", "prereqs": [] @@ -11483,26 +13677,74 @@ const UPLB_PROGRAMS = { "prereqs": [] }, { - "code": "VSUR 154", - "title": "Veterinary Diagnostic Imaging", + "code": "VMED 151", + "title": "General Principles of Veterinary Medicine", + "units": 2, + "year": 4, + "sem": "2", + "prereqs": [] + }, + { + "code": "VMED 195", + "title": "Research Methods in Veterinary Medicine", + "units": 3, + "year": 4, + "sem": "2", + "prereqs": [] + }, + { + "code": "VPH 122", + "title": "Zoonoses", + "units": 2, + "year": 4, + "sem": "2", + "prereqs": [] + }, + { + "code": "VPTH 123", + "title": "Clinical Pathology", + "units": 3, + "year": 4, + "sem": "2", + "prereqs": [] + }, + { + "code": "VPHY 143", + "title": "Veterinary Endocrinology and Reproductive Physiology", + "units": 4, + "year": 4, + "sem": "2", + "prereqs": [] + }, + { + "code": "VSUR 151", + "title": "Principles of Veterinary Anesthesiology and Surgery", "units": 3, "year": 4, + "sem": "2", + "prereqs": [] + }, + { + "code": "VSUR 154", + "title": "Veterinary Diagnostic Imaging", + "units": 2, + "year": 5, "sem": "1", "prereqs": [] }, { "code": "VETC 171", "title": "Introduction to Clinics", - "units": 3, - "year": 4, + "units": 2, + "year": 5, "sem": "1", "prereqs": [] }, { "code": "VMED 152", "title": "Canine and Feline Medicine", - "units": 3, - "year": 4, + "units": 4, + "year": 5, "sem": "1", "prereqs": [] }, @@ -11510,130 +13752,173 @@ const UPLB_PROGRAMS = { "code": "VMED 162", "title": "Poultry Medicine", "units": 3, - "year": 4, + "year": 5, "sem": "1", "prereqs": [] }, { "code": "VPH 123", "title": "Food Hygiene", - "units": 3, - "year": 4, + "units": 4, + "year": 5, "sem": "1", "prereqs": [] }, { "code": "VSUR 152", "title": "Small Animal Surgery", - "units": 3, - "year": 4, + "units": 4, + "year": 5, "sem": "1", "prereqs": [] }, { - "code": "VETC 173", - "title": "Clinical Conference I", - "units": 3, - "year": 4, - "sem": "1", + "code": "VETC 172", + "title": "Clinical Clerkship", + "units": 2, + "year": 5, + "sem": "2", "prereqs": [] }, { - "code": "VETC 175", - "title": "Clinical Internship I", + "code": "VMED 155", + "title": "Theriogenology", "units": 3, - "year": 4, - "sem": "1", + "year": 5, + "sem": "2", "prereqs": [] }, { - "code": "VELD 161.1", - "title": "Laboratory Diagnostics in Veterinary Medicine", - "units": 3, - "year": 4, - "sem": "1", + "code": "VMED 161", + "title": "Equine Medicine", + "units": 2, + "year": 5, + "sem": "2", "prereqs": [] }, { - "code": "VMED 156", - "title": "Jurisprudence, Ethics and Economics", - "units": 3, - "year": 4, - "sem": "1", + "code": "VMED 163", + "title": "Ruminant Medicine", + "units": 2, + "year": 5, + "sem": "2", "prereqs": [] }, { - "code": "VMED 200", - "title": "Undergraduate Thesis", + "code": "VMED 164", + "title": "Swine Medicine", "units": 3, - "year": 4, - "sem": "1", + "year": 5, + "sem": "2", "prereqs": [] }, { - "code": "VMED 151", - "title": "General Principles of Veterinary Me", - "units": 3, - "year": 4, + "code": "VMED 165", + "title": "Aquatic and Wildlife Medicine", + "units": 2, + "year": 5, "sem": "2", "prereqs": [] }, { - "code": "YEAR 2", - "title": "VETC", + "code": "VSUR 153", + "title": "Large Animal Surgery", "units": 3, - "year": 4, + "year": 5, "sem": "2", "prereqs": [] }, { "code": "VMED 200", "title": "Undergraduate Thesis", - "units": 3, - "year": 4, + "units": 6, + "year": 5, "sem": "2", "prereqs": [] }, { - "code": "YEAR 1", - "title": "VETC", - "units": 3, - "year": 4, + "code": "VETC 173", + "title": "Clinical Conference I", + "units": 1, + "year": 6, + "sem": "1", + "prereqs": [] + }, + { + "code": "VETC 175", + "title": "Clinical Internship I", + "units": 6, + "year": 6, + "sem": "1", + "prereqs": [] + }, + { + "code": "VELD 161.1", + "title": "Laboratory Diagnostics in Veterinary Medicine", + "units": 2, + "year": 6, + "sem": "1", + "prereqs": [] + }, + { + "code": "VMED 156", + "title": "Jurisprudence, Ethics and Economics", + "units": 2, + "year": 6, + "sem": "1", + "prereqs": [] + }, + { + "code": "VETC 174", + "title": "Clinical Conference II", + "units": 1, + "year": 6, "sem": "2", "prereqs": [] }, { - "code": "dicine", - "title": "dicine cine 2 3 uctive Physiology logy & Surgery 17 2 3 2 2 3 2 3 3 20 1 14 15", - "units": 3, - "year": 4, - "sem": "midyear", + "code": "VETC 176", + "title": "Clinical Internship II", + "units": 14, + "year": 6, + "sem": "2", "prereqs": [] } ], "requiredCodes": [ "ETHICS 1", - "KAS 1/HIST 1", + "KAS 1", "PI 10", "VMED 101", "STS 1", "CHEM 18", "ARTS 1", + "CHEM 18.1", + "STAT 164", + "ZOTC 111", "CHEM 40", - "GE", - "GE", + "CHEM 40.1", "ABME 10", "ZOTC 112", "CHEM 160", "VPHY 140", "COMM 10", + "VETA 101", + "VETA 105", + "ZOTC 113", + "VPHY 141", "VMCB 121", "VETA 102", "VETA 106", "VPHY 142", "ZOTC 114", "VMCB 123", + "VPAR 131", + "VPHM 141", + "ZOTC 115", + "VMCB 122", "VPTH 121", + "ZOTC 116", "VMCB 124", "VPAR 132", "VPH 121", @@ -11641,22 +13926,32 @@ const UPLB_PROGRAMS = { "VPTH 122", "ZOTC 117", "ZOTC 119", + "VMED 151", + "VMED 195", + "VPH 122", + "VPTH 123", + "VPHY 143", + "VSUR 151", "VSUR 154", "VETC 171", "VMED 152", "VMED 162", "VPH 123", "VSUR 152", + "VETC 172", + "VMED 155", + "VMED 161", + "VMED 163", + "VMED 164", + "VMED 165", + "VSUR 153", + "VMED 200", "VETC 173", "VETC 175", "VELD 161.1", "VMED 156", - "VMED 200", - "VMED 151", - "FIFTH", - "VMED 200", - "SIXTH", - "dicine" + "VETC 174", + "VETC 176" ] } }; @@ -11693,74 +13988,11 @@ function getCurrentCurriculum() { 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.safeHTML = 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; - } - // Remember the pick per program; the planner reads it too. - window.selectedTracks = { ...(window.selectedTracks || {}), [currentProgram.code]: e.target.value }; - chrome.storage.local.set({ selectedTracks: window.selectedTracks }); - // 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 the student's track (SP or thesis) from AMIS rows. Any enrollment // counts, including the course being taken right now (no grade yet) and a // failed attempt; only a drop or withdrawal does not. Rows can be popup shape @@ -11788,7 +14020,7 @@ function detectTrack(courses, program = currentProgram) { // Get free elective units based on track function getFreeElectiveUnits(track, program = currentProgram) { if (!program?.tracks) return 15; // Default - return program.tracks[resolveTrack(program, track)]?.freeElectiveUnits || 15; + return program.tracks[resolveTrack(program, track)]?.freeElectiveUnits ?? 15; } // Check if a course is a required major course @@ -11826,6 +14058,26 @@ function getRemainingGESlots(completedCourses) { return Math.max(0, required - completed); } +// How far the planner can trust a program's data, for a low-confidence banner. +// prereqShare is the share of named courses with prerequisites from the +// checklist or the catalog (pass UPLB_CATALOG, or load catalog.js first). +// Under 30% means the plan mostly ignores course order. +function getProgramDataQuality(programCode, catalog) { + const program = UPLB_PROGRAMS[programCode]; + if (!program) return { confident: false, prereqShare: 0, reasons: ['This program is not in GradeSim yet.'] }; + const cat = catalog || (typeof UPLB_CATALOG !== 'undefined' ? UPLB_CATALOG : {}); + const named = (program.majorCourses || []).filter(c => !c.genericRequirement); + const withPrereqs = named.filter(c => { + const entry = cat[normalizeCourseCode(c.code)]; + return (c.prereqs || []).length > 0 || !!(entry && (entry.pre || entry.co || entry.standing)); + }); + const prereqShare = named.length ? withPrereqs.length / named.length : 0; + const reasons = []; + if (!program.available) reasons.push('The checklist for this program is incomplete, so the plan may miss courses.'); + if (prereqShare < 0.3) reasons.push(`Only ${Math.round(prereqShare * 100)}% of courses have known prerequisites, so the plan may put courses in the wrong order.`); + return { confident: reasons.length === 0, prereqShare, reasons }; +} + // For backwards compatibility const BSCS_CURRICULUM = UPLB_PROGRAMS["BSCS"]; @@ -11850,6 +14102,7 @@ if (typeof module !== 'undefined' && module.exports) { getRemainingCourses, countCompletedGE, getRemainingGESlots, + getProgramDataQuality, BSCS_CURRICULUM }; } diff --git a/extension/src/planner.css b/extension/src/planner.css index 8895ef1..f3789c7 100644 --- a/extension/src/planner.css +++ b/extension/src/planner.css @@ -37,21 +37,23 @@ body.planner-page { padding: 8px 24px 40px; } +/* No program picked yet: only the heading and the disclaimer. */ +.pl-no-program .pl-controls, +.pl-no-program .pl-legend, +.pl-no-program .pl-result-row, +.pl-no-program .pl-scroll { + display: none; +} + .pl-banner { display: flex; + flex-direction: column; gap: 8px; - align-items: flex-start; - padding: 10px 14px; - margin-bottom: 12px; - border: 1px solid var(--border); - border-radius: var(--radius-sm); - background: var(--bg-elevated); - color: var(--body); + max-width: 80ch; } -.pl-banner .icon { - color: var(--info); - margin-top: 2px; +.pl-banner:empty { + display: none; } /* Summary: the answer first. */ @@ -86,11 +88,16 @@ body.planner-page { } .pl-sub { + max-width: 70ch; margin-top: 4px; - color: var(--muted); + color: var(--body); font-size: 0.88rem; } +.pl-summary .disclaimer { + margin-top: 8px; +} + .pl-costs { list-style: none; display: flex; @@ -156,19 +163,6 @@ body.planner-page { width: auto; } -.pl-textbtn { - min-height: 40px; - padding: 6px 10px; - border: 0; - background: none; - color: var(--brand); - font: inherit; - font-weight: 600; - cursor: pointer; - text-decoration: underline; - text-underline-offset: 3px; -} - .pl-menu { position: relative; } @@ -246,19 +240,7 @@ body.planner-page { margin: 12px 0 6px; } -.pl-chip { - display: inline-flex; - align-items: center; - gap: 4px; - padding: 3px 9px; - border: 1px solid var(--border); - border-radius: var(--radius-pill); - background: var(--bg-card); - color: var(--body); - font-size: 0.78rem; -} - -.pl-chip.crit-chip { +.pl-legend .crit-chip { border: 2px solid var(--brand); color: var(--brand); font-weight: 600; @@ -305,13 +287,25 @@ body.planner-page { margin-top: 8px; } +.pl-result-row { + display: flex; + align-items: flex-start; + gap: 12px; + margin: 4px 0 8px; +} + .pl-result { + flex: 1; + max-width: 70ch; min-height: 1.2em; - margin: 4px 0 8px; color: var(--ink); font-weight: 500; } +.pl-scroll-hint { + margin-left: auto; +} + /* Grid */ .pl-scroll { position: relative; @@ -408,37 +402,14 @@ body.planner-page { color: var(--warn); } -.pl-tag { - padding: 1px 6px; - border-radius: var(--radius-pill); - background: var(--brand); - color: var(--on-brand); - font-family: var(--font-sans); - font-size: 0.66rem; - font-weight: 700; - text-transform: uppercase; - letter-spacing: 0.04em; -} - -.pl-tag.now { - background: var(--info); - color: var(--bg-elevated); -} - -.pl-tag.past { - background: var(--border); - color: var(--body); -} - .pl-cards { display: flex; flex-direction: column; gap: var(--pl-row-gap); } -.pl-empty { +.pl-cards .empty-state { font-size: 0.78rem; - color: var(--muted); padding: 8px 2px; } @@ -477,21 +448,14 @@ body.planner-page { gap: 4px; } -.pl-status { - display: inline-flex; - align-items: center; - gap: 4px; - font-size: 0.7rem; - font-weight: 650; - text-transform: uppercase; - letter-spacing: 0.03em; - color: var(--muted); +.pl-card .status { + font-size: 0.74rem; } -.pl-units { +.pl-card .units-badge { font-size: 0.7rem; font-weight: 600; - padding: 0 5px; + padding: 0 6px; border-radius: var(--radius-pill); background: var(--brand-tint); color: var(--body); @@ -538,8 +502,6 @@ body.planner-page { font-size: 0.66rem; font-weight: 700; color: var(--brand); - text-transform: uppercase; - letter-spacing: 0.03em; } .pl-note { @@ -559,28 +521,16 @@ body.planner-page { color: var(--warn); } -.st-passed .pl-status, -.pl-chip.st-passed .icon { color: var(--ok); } - -.st-inprogress .pl-status, -.pl-chip.st-inprogress .icon { color: var(--info); } - .pl-card.st-failed { border-color: var(--bad); } -.st-failed .pl-status, -.pl-chip.st-failed .icon { color: var(--bad); } .pl-card.st-retake { border: 1.5px dashed var(--bad); background: var(--bg-card); } -.st-retake .pl-status, -.pl-chip.st-retake .icon { color: var(--bad); } .pl-card.st-ready { border-color: var(--brand-line); } -.st-ready .pl-status, -.pl-chip.st-ready .icon { color: var(--brand); } .pl-card.st-locked { border-style: dashed; @@ -590,8 +540,10 @@ body.planner-page { .pl-card.st-locked .pl-code, .pl-card.st-locked .pl-title { color: var(--muted); } +/* Earlier attempts and what-if failures: a quieter surface, full text contrast. */ .pl-card.history { - opacity: 0.6; + background: var(--bg-card); + box-shadow: none; } .pl-card.crit { @@ -674,10 +626,9 @@ body.planner-page { .pl-detail h4 { margin: 12px 0 4px; - font-size: 0.78rem; - text-transform: uppercase; - letter-spacing: 0.04em; - color: var(--muted); + font-size: 0.85rem; + font-weight: 700; + color: var(--ink); } .pl-detail-head { @@ -701,35 +652,10 @@ body.planner-page { color: var(--ink); } -.pl-status-pill { - font-family: var(--font-sans); - font-size: 0.7rem; - font-weight: 700; - text-transform: uppercase; - padding: 1px 7px; - border: 1px solid currentColor; - border-radius: var(--radius-pill); - color: var(--muted); -} - -.pl-status-pill.st-passed { color: var(--ok); } -.pl-status-pill.st-failed, -.pl-status-pill.st-retake { color: var(--bad); } -.pl-status-pill.st-ready { color: var(--brand); } -.pl-status-pill.st-inprogress { color: var(--info); } - .pl-close { - flex: 0 0 auto; - width: 36px; - height: 36px; - display: inline-flex; - align-items: center; - justify-content: center; border: 1px solid var(--border); - border-radius: var(--radius-pill); background: var(--bg-card); color: var(--ink); - cursor: pointer; } .pl-muted { @@ -805,10 +731,11 @@ button.pl-link:hover { margin-top: 14px; } +.pl-actions .btn-text { + font-size: 0.82rem; +} + .pl-actions .btn { - display: inline-flex; - align-items: center; - gap: 5px; min-height: 40px; padding: 6px 12px; font-size: 0.82rem; @@ -944,3 +871,15 @@ button.pl-link:hover { transition: none; } } + +.pl-col-name .badge { + padding: 1px 7px; + font-size: 0.68rem; +} + +.pl-legend .badge { + padding: 2px 9px; + font-size: 0.78rem; + font-weight: 500; + color: var(--body); +} diff --git a/extension/src/planner.html b/extension/src/planner.html index 0fe0028..53404bc 100644 --- a/extension/src/planner.html +++ b/extension/src/planner.html @@ -3,10 +3,11 @@ - Course Planner - Elbi GradeSim + Course planner, Elbi GradeSim +
@@ -15,16 +16,17 @@

Elbi GradeSim Course planner

- +
- +

Loading plan

    +

    Not affiliated with UP. Unofficial estimate. INC/DRP may not reflect correctly. Always verify with the OUR.

    @@ -37,7 +39,7 @@

    Elbi GradeSim Course planner

    - +
    Plan options @@ -59,13 +61,16 @@

    Elbi GradeSim Course planner

    Each column is a term. Past terms come from your AMIS grades, future terms are the earliest plan that respects prerequisites, the sems each course is actually offered, and your unit cap.

    Select a course to see its full prerequisite chain, what it unlocks and how many courses a failure would block. Mark a course failed to see the retake and the delay.

    -

    Thin maroon lines are the critical path: courses where a one-term slip delays graduation.

    -

    Offerings come from AMIS class listings for 2023 to 2026. Free electives are not included.

    +

    Courses marked Critical, joined by thin maroon lines, are your critical path. These are the courses that would delay graduation if you slip, even by one term.

    +

    Offerings come from recent AMIS class listings. FE cards are free elective placeholders. Any course you pass outside your checklist fills one, and they are never critical.

    -

    +
    +

    +
    +
    @@ -76,7 +81,22 @@

    Elbi GradeSim Course planner

    + + + + + + + diff --git a/extension/src/planner.js b/extension/src/planner.js index e29c659..1be5c3f 100644 --- a/extension/src/planner.js +++ b/extension/src/planner.js @@ -1,7 +1,8 @@ // planner.js - curriculum map and course planner. // Framework free. Talks to the browser only through `store` below, so the same // file can run on a plain web page (gradesim.uplb.tools) with localStorage. -// Depends on globals from curriculum.js, catalog.js and scheduler.js. +// Depends on globals from components.js, curriculum.js, catalog.js and +// scheduler.js. Every piece of UI is built with the components.js helpers. /* ---------- Storage adapter ---------- */ @@ -25,45 +26,6 @@ const store = { }, }; -/* ---------- Icons (Lucide, ISC license) ---------- */ - -const ICONS = { - check: '', - x: '', - lock: '', - clock: '', - retake: '', - ready: '', - planned: '', - flag: '', - alert: '', - info: '', - chevron: '', - close: '', - sun: '', - moon: '', - monitor: '', -}; -function icon(name) { - return ``; -} - -// Build DOM from markup with DOMParser instead of assigning innerHTML, which -// the Firefox add-on linter flags. Every caller escapes dynamic text with -// esc(). SVG targets are parsed inside an so children keep the SVG -// namespace. -function setHTML(el, html) { - if (!html) { el.replaceChildren(); return; } - const isSvg = el instanceof SVGElement; - const doc = new DOMParser().parseFromString(isSvg ? `${html}` : html, 'text/html'); - const host = isSvg ? doc.body.firstElementChild : doc.body; - el.replaceChildren(...Array.from(host.childNodes, node => document.importNode(node, true))); -} - -function esc(text) { - return String(text == null ? '' : text).replace(/[&<>"']/g, ch => ({ '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }[ch])); -} - const $ = id => document.getElementById(id); /* ---------- Terms ---------- */ @@ -83,11 +45,7 @@ const ayLabel = abs => { return `AY ${ay}-${String(ay + 1).slice(2)}`; }; -// AMIS term ids are 12: 1251 = AY 2025-26 1st sem. -function amisTermToAbs(id) { - const m = String(id).match(/^12(\d)([123])$/); - return m ? (2020 + Number(m[1])) * 3 + Number(m[2]) - 1 : null; -} +// amisTermToAbs, gradeResult and the requirement slots come from requirements.js. // ponytail: month heuristic for the current term (Aug-Dec 1st, Jan-May 2nd, // Jun-Jul midyear). Only used when AMIS grades are older than the calendar. @@ -116,12 +74,7 @@ const state = { document.addEventListener('DOMContentLoaded', init); async function init() { - // Parse the static icon markup with DOMParser instead of assigning - // outerHTML, which the Firefox add-on linter flags for review. - document.querySelectorAll('[data-icon]').forEach(el => { - const doc = new DOMParser().parseFromString(icon(el.dataset.icon), 'text/html'); - el.replaceWith(document.importNode(doc.body.firstElementChild, true)); - }); + hydrateIcons(); state.data = await store.get(['gradesData', 'selectedProgram', 'substitutions', 'customCourseStatus', 'plannerPins', 'plannerPetitions', 'plannerOptions', 'theme', 'selectedTracks']); const d = state.data; @@ -129,12 +82,16 @@ async function init() { d.substitutions = d.substitutions || {}; d.plannerPins = d.plannerPins || {}; d.plannerPetitions = d.plannerPetitions || {}; - initTheme(d.theme); + themeToggle($('themeToggle'), d.theme, next => { + store.set({ theme: next }); + requestAnimationFrame(() => drawEdges()); + }); - const programCode = d.selectedProgram || 'BSCS'; + const programCode = d.selectedProgram; state.program = typeof UPLB_PROGRAMS !== 'undefined' ? UPLB_PROGRAMS[programCode] : null; if (!state.program || !state.program.majorCourses) { $('gradTerm').textContent = 'Pick your program in the extension popup first.'; + document.body.classList.add('pl-no-program'); return; } $('plannerProgram').textContent = state.program.name || programCode; @@ -151,31 +108,33 @@ async function init() { if (trackInfo) { $('plannerProgram').textContent += `, ${trackInfo.name} (${trackInfo.code})`; } - state.courses = enrichCourses(getPlannerCourses(state.program, state.track), catalog); + state.courses = plannerCourseList(state.program, state.track, catalog); state.courses.forEach(c => state.byCode.set(c.code, c)); state.graph = analyzeGraph(state.courses); - d.plannerOptions = { cap: defaultCap(), midyear: false, midyear9: false, ...(d.plannerOptions || {}) }; - - if (!d.gradesData || !d.gradesData.student_grades) { - const banner = $('plannerBanner'); - banner.classList.remove('hidden'); - setHTML(banner, `${icon('info')}No grades loaded yet. Open AMIS while logged in, then come back so your passed courses count. Until then this plan starts from scratch.`); - } + // 18 units a sem unless the student picks 21 in Plan options. + d.plannerOptions = { cap: 18, midyear: false, midyear9: false, ...(d.plannerOptions || {}) }; + + const quality = getProgramDataQuality(programCode, catalog); + $('plannerBanner').replaceChildren(...flat([ + !quality.confident && notice({ tone: 'warn', icon: 'alert', title: 'Treat this plan as a rough guide', body: quality.reasons.join(' ') }), + !(d.gradesData && d.gradesData.student_grades) && notice({ + tone: 'info', icon: 'info', + body: 'No grades loaded yet. Log in to AMIS, open the extension and press Refresh so your passed courses count. Until then this plan starts from scratch.', + }), + ])); initControls(); render(); - window.addEventListener('resize', () => drawEdges()); -} - -// 21 units when the program's own checklist already has a regular term above 18. -function defaultCap() { - const load = {}; - (state.program.majorCourses || []).forEach(c => { - if (c.sem === 'midyear') return; - const k = `${c.year}-${c.sem}`; - load[k] = (load[k] || 0) + (Number(c.units) || 0); - }); - return Object.values(load).some(u => u > 18) ? 21 : 18; + window.addEventListener('resize', () => { drawEdges(); updateScrollHint(); }); + $('plannerScroll').addEventListener('scroll', updateScrollHint, { passive: true }); + + // The popup's "See what this costs" link opens planner.html?whatif=CODE. + const prefill = normCode(new URLSearchParams(location.search).get('whatif') || ''); + if (prefill && state.byCode.has(prefill)) { + $('whatifMode').value = 'fail'; + $('whatifCourse').value = prefill; + if ($('whatifCourse').value === prefill) simulate(); + } } function save(keys) { @@ -184,39 +143,8 @@ function save(keys) { store.set(obj); } -/* ---------- Theme ---------- */ - -const THEMES = ['system', 'light', 'dark']; -function applyTheme(theme) { - if (theme === 'system') delete document.documentElement.dataset.theme; - else document.documentElement.dataset.theme = theme; - const btn = $('themeToggle'); - setHTML(btn, icon({ system: 'monitor', light: 'sun', dark: 'moon' }[theme])); - btn.title = `Theme: ${theme}`; - btn.setAttribute('aria-label', btn.title); - btn.dataset.theme = theme; -} -function initTheme(theme) { - applyTheme(THEMES.includes(theme) ? theme : 'system'); - $('themeToggle').addEventListener('click', e => { - const next = THEMES[(THEMES.indexOf(e.currentTarget.dataset.theme) + 1) % THEMES.length]; - applyTheme(next); - store.set({ theme: next }); - requestAnimationFrame(() => drawEdges()); - }); -} - /* ---------- AMIS history ---------- */ -function gradeResult(raw) { - const g = (raw == null ? '' : String(raw)).toUpperCase().trim(); - const n = parseFloat(g); - if (g === 'S' || g === 'P' || (n >= 1 && n <= 3)) return 'passed'; - if (n === 5 || g === 'F' || g === 'U') return 'failed'; - if (!g) return 'nograde'; - return 'other'; // INC, DRP, 4.00: not passed, not a fail -} - // Map AMIS attempts onto curriculum codes, with the term each happened in. function readHistory() { const sg = (state.data.gradesData && state.data.gradesData.student_grades) || {}; @@ -256,36 +184,11 @@ function readHistory() { const push = (code, a) => { (byCurr[code] = byCurr[code] || []).push(a); }; attempts.forEach(a => { if (state.byCode.has(a.code)) push(a.code, a); }); + // GE, HK, NSTP and free elective slots, and substitutions, filled the same + // way the popup What if tab counts them. const doneOrNow = attempts.filter(a => a.result === 'passed' || a.result === 'inprogress'); - const slotDone = getCompletedRequirementSlotCodes( - doneOrNow.map(a => ({ code: a.code, title: a.title })), state.program); - const kinds = [ - ['ge', a => isGECourse(a.code, a.title) && !state.byCode.has(a.code)], - ['hk', a => /^(HK|PE)\b/.test(a.code) && !state.byCode.has(a.code)], - ['nstp', a => /^NSTP\b/.test(a.code) && !state.byCode.has(a.code)], - ]; - kinds.forEach(([kind, match]) => { - const slots = state.courses.filter(c => c.genericRequirement === kind && slotDone.has(c.code)); - const taken = doneOrNow.filter(match); - slots.forEach((slot, i) => { - const a = taken[i]; - if (a) push(slot.code, { ...a, via: a.code }); - }); - }); - // Free electives: any other course with units that the curriculum does not - // name and that is not standing in for a required one. - // ponytail: one course per 3-unit slot; a 6-unit elective fills only one. - const subbed = new Set(Object.values(state.data.substitutions).map(normCode)); - const electives = doneOrNow.filter(a => a.units > 0 && !state.byCode.has(a.code) && - !subbed.has(a.code) && !kinds.some(([, match]) => match(a))); - state.courses.filter(c => c.genericRequirement === 'elective').forEach((slot, i) => { - const a = electives[i]; - if (a) push(slot.code, { ...a, via: a.code }); - }); - Object.entries(state.data.substitutions).forEach(([req, taken]) => { - const r = normCode(req); - const a = doneOrNow.find(x => x.code === normCode(taken)); - if (a && state.byCode.has(r)) push(r, { ...a, via: a.code }); + fillRequirementSlots(state.courses, doneOrNow, state.data.substitutions).forEach((a, slot) => { + if (a.code !== slot && state.byCode.has(slot)) push(slot, { ...a, via: a.code }); }); return { attempts, byCurr, latestAbs, passedOutside: new Set(doneOrNow.map(a => a.code)) }; @@ -383,8 +286,11 @@ function compute() { // Baseline with the what-if removed, for the "what changed" message. const noWhatif = wi ? run(failures.filter(f => f.source !== 'whatif'), false) : null; const slips = computeSlips(now.runOpts, now.result); + // Same count as the popup What if tab: passed AMIS courses plus manual marks. + const left = remainingRequirements(state.courses, hist.attempts.filter(a => a.result === 'passed'), + { substitutions: d.substitutions, overrides: d.customCourseStatus }); - return { hist, info, startAbs, passed: now.passed, failures, costs, now, ideal, noWhatif, slips, unitCaps }; + return { hist, info, startAbs, passed: now.passed, failures, costs, now, ideal, noWhatif, slips, unitCaps, left }; } /* ---------- Summary ---------- */ @@ -416,9 +322,9 @@ function renderSummary(v) { const why = one ? `because ${one.code} ${one.source === 'whatif' ? 'is failed in this what-if' : 'failed'}` : `because of ${v.failures.length} failed courses`; - setHTML(deltaEl, `${icon('alert')}+${termsText(delta)} ${esc(why)}`); + deltaEl.replaceChildren(icon('alert'), `${termsText(delta)} later ${why}`); } else if (v.failures.length) { - setHTML(deltaEl, `${icon('check')}No delay from ${v.failures.length === 1 ? esc(v.failures[0].code) : 'your failed courses'}`); + deltaEl.replaceChildren(icon('check'), `No delay from ${v.failures.length === 1 ? v.failures[0].code : 'your failed courses'}`); deltaEl.classList.add('ok'); } else { deltaEl.textContent = ''; @@ -426,17 +332,19 @@ function renderSummary(v) { if (!(v.failures.length && delta <= 0)) deltaEl.classList.remove('ok'); const termsLeft = r.plan.filter(p => p.courses.length).length; - const bits = []; - if (gradAbs != null) bits.push(`That is ${ayLabel(gradAbs)}`); - if (gradAbs != null) bits.push(`${termsLeft} term${termsLeft === 1 ? '' : 's'} with classes left, starting ${absLabel(v.startAbs)}`); - bits.push(`up to ${v.unitCaps['1']} units a sem`); - bits.push(state.data.plannerOptions.midyear ? `midyear up to ${v.unitCaps.midyear}` : 'midyear only where the checklist puts it'); - $('gradSub').textContent = `${bits.join(', ')}. Free electives are not counted.`; + const nowTaking = Object.values(v.info).some(i => i.status === 'inprogress'); + const sentences = []; + if (gradAbs != null) sentences.push(`That is ${ayLabel(gradAbs)}, with ${termsText(termsLeft)} of classes from ${absLabel(v.startAbs)}.`); + sentences.push(`You have ${unitsText(v.left.units)} left to pass${nowTaking ? ', counting this term' : ''}.`); + sentences.push(`The plan takes up to ${v.unitCaps['1']} units a sem and ${state.data.plannerOptions.midyear ? `up to ${v.unitCaps.midyear} in midyear` : 'midyear only where the checklist puts it'}.`); + $('gradSub').textContent = sentences.join(' '); const list = $('failCosts'); - setHTML(list, v.costs.length > 1 || (v.costs.length === 1 && v.costs[0].source !== 'whatif') - ? v.costs.map(c => `
  • ${icon(c.cost > 0 ? 'x' : 'check')}${esc(c.code)} ${c.source === 'whatif' ? '(what-if)' : c.past ? 'failed' : '(marked failed)'}: ${c.cost > 0 ? `+${termsText(c.cost)}` : 'no delay'}
  • `).join('') - : ''); + const showCosts = v.costs.length > 1 || (v.costs.length === 1 && v.costs[0].source !== 'whatif'); + list.replaceChildren(...(showCosts ? v.costs.map(c => h('li', { class: c.cost > 0 ? 'bad' : '' }, + icon(c.cost > 0 ? 'x' : 'check'), + h('strong', {}, c.code), + ` ${c.source === 'whatif' ? 'in the what-if' : c.past ? 'failed' : 'marked failed'}, ${c.cost > 0 ? `${termsText(c.cost)} later` : 'no delay'}`)) : [])); } /* ---------- Cards and columns ---------- */ @@ -454,16 +362,6 @@ function restricted(c) { return !(offeredIn(c, '1') && offeredIn(c, '2')); } -const STATUS = { - passed: ['check', 'Passed'], - inprogress: ['clock', 'Taking now'], - failed: ['x', 'Failed'], - retake: ['retake', 'Retake'], - ready: ['ready', 'Ready'], - planned: ['planned', 'Planned'], - locked: ['lock', 'Waiting'], -}; - // Nearest failed course upstream of `code`, if any. function waitingOn(code, v) { const failed = new Set(v.failures.map(f => f.code)); @@ -559,32 +457,34 @@ function orderColumns(list, primary) { } } -function cardHTML(card, v) { +function courseCard(card, v) { const c = state.byCode.get(card.code); - const [ic, label] = STATUS[card.status]; const slip = v.slips[card.code]; - const crit = !card.history && slip > 0 && !['passed', 'inprogress', 'failed'].includes(card.status); + // Free elective cards are placeholders for any course, so never critical. + const crit = !card.history && slip > 0 && c.genericRequirement !== 'elective' && !['passed', 'inprogress', 'failed'].includes(card.status); const classes = ['pl-card', `st-${card.status}`]; if (crit) classes.push('crit'); if (card.history || card.hypothetical) classes.push('history'); - let extra = ''; - if (card.waitingOn) extra = `${icon('lock')}Waiting on ${esc(card.waitingOn)}`; - else if (card.conditional) extra = `${icon('flag')}Petition needed`; - else if (card.unplaceable) extra = `${icon('alert')}Can't place`; - else if (card.via && card.via !== card.code) extra = `via ${esc(card.via)}`; - else if (card.hypothetical) extra = 'If failed here'; - const statusLabel = card.status === 'failed' && card.history ? 'Failed' : label; - const aria = `${c.code}, ${c.title}. ${statusLabel}${crit ? ', critical' : ''}. ${c.units} units, ${offeringLabel(c)}.${card.waitingOn ? ` Waiting on ${card.waitingOn}.` : ''}`; - return `
    - ${icon(ic)}${statusLabel}${esc(c.units)}u - ${esc(c.code)}${card.pinned ? ' *' : ''} - ${esc(c.title)} - ${extra} - ${esc(offeringLabel(c))}${crit ? 'Critical' : ''} -
    `; + let extra = null; + if (card.waitingOn) extra = h('span', { class: 'pl-note' }, icon('lock'), `Waiting on ${card.waitingOn}`); + else if (card.conditional) extra = h('span', { class: 'pl-note warn' }, icon('flag'), 'Petition needed'); + else if (card.unplaceable) extra = h('span', { class: 'pl-note warn' }, icon('alert'), "Can't place"); + else if (card.via && card.via !== card.code) extra = h('span', { class: 'pl-note' }, `via ${card.via}`); + else if (card.hypothetical) extra = h('span', { class: 'pl-note' }, 'If failed here'); + const statusLabel = COURSE_STATUS[card.status][1]; + const aria = `${c.code}, ${c.title}. ${statusLabel}${crit ? ', critical' : ''}. ${unitsText(c.units)}, ${offeringLabel(c)}.${card.waitingOn ? ` Waiting on ${card.waitingOn}.` : ''}`; + return h('div', { + class: classes.join(' '), role: 'button', tabindex: '0', 'aria-label': aria, + dataset: { code: card.code, ...(v.primary[card.code] === card ? { primary: '1' } : {}) }, + }, + h('span', { class: 'pl-card-top' }, statusPill(card.status, { variant: 'plain' }), unitsBadge(c.units)), + h('span', { class: 'pl-code' }, c.code, card.pinned && h('span', { class: 'pl-pin', title: 'Moved later by you' }, ' *')), + h('span', { class: 'pl-title' }, c.title), + extra, + h('span', { class: 'pl-card-foot' }, h('span', { class: 'pl-offer' }, offeringLabel(c)), crit && h('span', { class: 'pl-crit' }, 'Critical'))); } -function columnHTML(column, v, phone) { +function semesterColumn(column, v, phone) { const isPast = column.abs < v.startAbs; let name; let sub; @@ -597,24 +497,23 @@ function columnHTML(column, v, phone) { let warn = ''; if (!isPast && Number.isFinite(column.abs) && column.abs >= 0) { if (isMid && units > 6) warn = "Over 6 units in midyear needs the Dean's approval (max 9)."; - else if (!isMid && units > v.unitCaps['1']) warn = `Over your ${v.unitCaps['1']}-unit cap: needs an overload approval.`; - else if (!isMid && units > 18) warn = 'Over 18 units: allowed up to 21 when the term has lab courses.'; + else if (!isMid && units > v.unitCaps['1']) warn = `Over your ${v.unitCaps['1']}-unit cap, so it needs an overload approval.`; + else if (!isMid && units > 18) warn = 'Over 18 units, which is allowed up to 21 when the term has lab courses.'; } + const loud = warn && (isMid || units > v.unitCaps['1']); const nowTerm = column.cards.some(c => c.status === 'inprogress'); - const tag = column.abs === v.startAbs ? 'Next' - : nowTerm ? 'Now' - : isPast && column.abs >= 0 ? 'Taken' : ''; - const open = !(phone && isPast) ? ' open' : ''; - return `
    - - - ${esc(name)} ${tag} - ${esc(sub)}${warn && (isMid || units > v.unitCaps['1']) ? icon('alert') : ''}${units} units - ${warn ? `${esc(warn)}` : ''} - -
    ${column.cards.map(card => cardHTML(card, v)).join('') || '

    Nothing offered that fits

    '}
    - -
    `; + const tag = column.abs === v.startAbs ? badge('Next', { tone: 'strong' }) + : nowTerm ? badge('Now', { tone: 'info' }) + : isPast && column.abs >= 0 ? badge('Taken') : null; + return h('section', { class: `pl-col${isPast ? ' past' : ''}`, dataset: { abs: column.abs } }, + h('details', { open: !(phone && isPast) }, + h('summary', { class: 'pl-col-head' }, + h('span', { class: 'pl-col-name' }, name, tag), + h('span', { class: 'pl-col-sub' }, sub, + h('span', { class: `pl-col-units${loud ? ' warn' : ''}`, title: warn || null }, loud && icon('alert'), unitsText(units))), + warn && h('span', { class: 'visually-hidden' }, warn)), + h('div', { class: 'pl-cards' }, + column.cards.length ? column.cards.map(card => courseCard(card, v)) : emptyState('Nothing offered that fits')))); } const isPhone = () => window.matchMedia('(max-width: 719px)').matches; @@ -629,10 +528,22 @@ function render() { v.columns = list; const phone = isPhone(); document.querySelector('.pl-main').appendChild($('detail')); // may sit inside the grid on phones - setHTML($('plannerGrid'), list.map(c => columnHTML(c, v, phone)).join('')); + $('plannerGrid').replaceChildren(...list.map(c => semesterColumn(c, v, phone))); if (state.selected && !state.byCode.has(state.selected)) state.selected = null; renderDetail(); - requestAnimationFrame(() => drawEdges()); + requestAnimationFrame(() => { drawEdges(); updateScrollHint(); }); +} + +// When terms run past the right edge, say how many and offer a jump there. +function updateScrollHint() { + const sc = $('plannerScroll'); + const right = sc.getBoundingClientRect().right; + const hiddenCols = isPhone() ? 0 : Array.from(sc.querySelectorAll('.pl-col')) + .filter(col => col.getBoundingClientRect().right > right + 1).length; + $('scrollHint').replaceChildren(hiddenCols ? button({ + variant: 'chip', icon: 'next', text: `${plural(hiddenCols, 'more term')} to the right`, + onclick: () => { sc.scrollLeft = sc.scrollWidth; }, + }) : ''); } /* ---------- Edges and focus ---------- */ @@ -647,7 +558,7 @@ function drawEdges() { const svg = $('plannerArrows'); const grid = $('plannerGrid'); const v = state.view; - setHTML(svg, ''); + svg.replaceChildren(); document.querySelectorAll('.pl-card.dim, .pl-card.chain, .pl-card.focus').forEach(el => el.classList.remove('dim', 'chain', 'focus')); if (!v) return; @@ -665,7 +576,7 @@ function drawEdges() { svg.setAttribute('width', grid.scrollWidth); svg.setAttribute('height', grid.scrollHeight); const nodeOf = code => grid.querySelector(`.pl-card[data-primary][data-code="${CSS.escape(code)}"]`); - const crit = code => v.slips[code] > 0; + const crit = code => v.slips[code] > 0 && state.byCode.get(code).genericRequirement !== 'elective'; const paths = []; // Arrows travel the lattice lanes (see .pl-grid in planner.css): out of the @@ -735,27 +646,36 @@ function drawEdges() { const x2 = rb.left - gridRect.left; const y2 = rb.top + rb.height / 2 - gridRect.top; const d = routeEdge(x1, y1, x2, y2, ra, rb); - if (kind === 'chain') paths.push(``); - paths.push(``); + if (kind === 'chain') paths.push(svgEl('path', { class: 'pl-edge-halo', d })); + paths.push(svgEl('path', { class: `pl-edge ${kind}`, d, 'marker-start': 'url(#pl-tail)', 'marker-end': `url(#pl-arrow-${kind})` })); }); svg.classList.toggle('over', !!chain); - setHTML(svg, ` - - - - ${paths.join('')}`); + const marker = (id, size, ref, shape, shapeAttrs, extra = {}) => { + const m = svgEl('marker', { id, markerWidth: size, markerHeight: size, refX: ref[0], refY: ref[1], ...extra }); + m.append(svgEl(shape, shapeAttrs)); + return m; + }; + const defs = svgEl('defs'); + defs.append( + marker('pl-arrow-chain', 8, [6, 4], 'path', { d: 'M0 0 L8 4 L0 8 z', class: 'pl-arrowhead chain' }, { orient: 'auto' }), + marker('pl-arrow-crit', 6, [5, 3], 'path', { d: 'M0 0 L6 3 L0 6 z', class: 'pl-arrowhead crit' }, { orient: 'auto' }), + marker('pl-tail', 8, [4, 4], 'circle', { cx: 4, cy: 4, r: 3, class: 'pl-arrowtail' }, { markerUnits: 'userSpaceOnUse' })); + svg.replaceChildren(defs, ...paths); } /* ---------- Detail panel ---------- */ -function groupHTML(group, v) { - return group.map(code => { +// Prerequisite alternatives as course links: "A or B". +function courseLinks(group, v) { + return group.flatMap((code, i) => { const c = state.byCode.get(code); const st = v.passed.has(code) ? 'passed' : v.failures.some(f => f.code === code) ? 'failed' : c ? 'todo' : 'outside'; const ic = st === 'passed' ? 'check' : st === 'failed' ? 'x' : 'planned'; - const inner = `${icon(ic)}${esc(code)}`; - return c ? `` : `${inner}`; - }).join(' or '); + const link = c + ? h('button', { type: 'button', class: `pl-link st-${st}`, dataset: { goto: code } }, icon(ic), code) + : h('span', { class: `pl-link st-${st}`, title: 'Not in your checklist' }, icon(ic), code); + return i ? [' ', h('span', { class: 'pl-or' }, 'or'), ' ', link] : [link]; + }); } function renderDetail() { @@ -769,7 +689,6 @@ function renderDetail() { const c = state.byCode.get(code); const i = v.info[code]; const card = v.primary[code]; - const status = card ? STATUS[card.status][1] : (i.status === 'failed' ? 'Failed' : 'Planned'); const t = v.now.result.assignedTerm[code]; const slip = v.slips[code]; const blocking = state.graph.blocking[code] || 0; @@ -778,49 +697,49 @@ function renderDetail() { const groups = preGroups(c); const facts = []; - facts.push(`${c.units} units, offered ${offeringLabel(c).toLowerCase()}${c.offered ? ` (${c.offered[1]} of 4 first sems, ${c.offered[2]} of 3 second sems, ${c.offered[3]} of 2 midyears seen)` : ''}`); + facts.push(`${unitsText(c.units)}, offered ${offeringLabel(c).toLowerCase()}${c.offered ? ` (${c.offered[1]} of 4 first sems, ${c.offered[2]} of 3 second sems, ${c.offered[3]} of 2 midyears seen)` : ''}`); if (t !== undefined) facts.push(`Planned for ${absLabel(v.startAbs + t)}`); - const metrics = `
      -
    • ${blocking} course${blocking === 1 ? '' : 's'} blocked if this is failed
    • -
    • ${delay} course${delay === 1 ? '' : 's'} on the longest chain through it
    • - ${slip === undefined ? '' : slip > 0 - ? `
    • Critical. Taking it a term later delays graduation by ${slip === Infinity ? 'more than the plan can show' : termsText(termsLate(v, v.now.result.gradTermIndex, v.now.result.gradTermIndex + slip))}.
    • ` - : '
    • Has slack: taking it a term later does not move graduation.
    • '} -
    `; + const metrics = h('ul', { class: 'pl-metrics' }, + h('li', {}, h('strong', {}, blocking), ` ${blocking === 1 ? 'course' : 'courses'} blocked if this is failed`), + h('li', {}, h('strong', {}, delay), ` ${delay === 1 ? 'course' : 'courses'} on the longest chain through it`), + slip === undefined ? null : slip > 0 + ? h('li', { class: 'bad' }, h('strong', {}, 'Critical.'), ` Taking it a term later delays graduation by ${slip === Infinity ? 'more than the plan can show' : termsText(termsLate(v, v.now.result.gradTermIndex, v.now.result.gradTermIndex + slip))}.`) + : h('li', {}, 'Has slack. Taking it a term later does not move graduation.')); const reqs = []; - groups.forEach(g => reqs.push(`
  • ${groupHTML(g, v)}
  • `)); - (c.co || []).forEach(x => reqs.push(`
  • Take with ${groupHTML([x], v)} (corequisite)
  • `)); - if (c.standing) reqs.push(`
  • ${c.standing === 'senior' ? 'Senior' : 'Junior'} standing (assumed ${c.standing === 'senior' ? '75' : '50'}% of total units)
  • `); - if (c.coi) reqs.push('
  • Consent of instructor (COI) also works
  • '); - if (c.note) reqs.push(`
  • AMIS says: ${esc(c.note)}
  • `); + groups.forEach(g => reqs.push(h('li', {}, courseLinks(g, v)))); + (c.co || []).forEach(x => reqs.push(h('li', {}, 'Take with ', courseLinks([x], v), ' (corequisite)'))); + if (c.standing) reqs.push(h('li', {}, `${c.standing === 'senior' ? 'Senior' : 'Junior'} standing (assumed ${c.standing === 'senior' ? '75' : '50'}% of total units)`)); + if (c.coi) reqs.push(h('li', {}, 'Consent of instructor (COI) also works')); + if (c.note) reqs.push(h('li', { class: 'pl-muted' }, 'AMIS note ', h('q', {}, c.note))); const actions = []; const past = i.tries.length > 0; - if (i.status !== 'passed') actions.push(``); - if (i.status !== 'failed') actions.push(``); - if (i.override) actions.push(``); - if (t !== undefined) actions.push(``); - if (state.data.plannerPins[code] != null) actions.push(''); - const petition = restricted(c) && i.status !== 'passed' - ? `` - : ''; - - setHTML(panel, ` -
    -

    ${esc(c.code)} ${esc(status)}

    -

    ${esc(c.title)}

    - -
    -

    ${facts.map(esc).join('. ')}.

    - ${card && card.waitingOn ? `

    ${icon('lock')}Waiting on ${esc(card.waitingOn)}

    ` : ''} - ${metrics} -

    Requires

    - ${reqs.length ? `
      ${reqs.join('')}
    ` : '

    No prerequisites.

    '} -

    Unlocks

    - ${deps.length ? `

    ${deps.map(d => groupHTML([d], v)).join(' ')}

    ` : '

    Nothing else in your checklist.

    '} -
    ${actions.join('')}
    - ${petition}`); + if (i.status !== 'passed') actions.push(button({ icon: 'check', text: 'Mark passed', dataset: { act: 'passed' } })); + if (i.status !== 'failed') actions.push(button({ icon: 'x', text: past || i.status === 'inprogress' ? 'Mark failed' : 'Mark failed (what-if)', dataset: { act: 'failed' } })); + if (i.override) actions.push(button({ variant: 'text', text: `Use ${past ? 'AMIS status' : 'plan'} again`, dataset: { act: 'clear' } })); + if (t !== undefined) actions.push(button({ variant: 'text', text: 'Take a term later', dataset: { act: 'later' } })); + if (state.data.plannerPins[code] != null) actions.push(button({ variant: 'text', text: 'Back to earliest', dataset: { act: 'unpin' } })); + const petition = restricted(c) && i.status !== 'passed' && h('label', { class: 'pl-check pl-petition' }, + h('input', { type: 'checkbox', dataset: { act: 'petition' }, checked: !!state.data.plannerPetitions[code] }), + ' Plan on a petitioned class when it is not offered. Needs about 10 students and department, college and OVCAA approval, so treat it as conditional.'); + + panel.replaceChildren(...flat([ + h('div', { class: 'pl-detail-head' }, + h('div', {}, + h('p', { class: 'pl-detail-code' }, c.code, ' ', statusPill(card ? card.status : (i.status === 'failed' ? 'failed' : 'planned'))), + h('p', { class: 'pl-detail-title' }, c.title)), + button({ variant: 'icon', icon: 'x', class: 'pl-close', 'aria-label': 'Close details', dataset: { act: 'close' } })), + h('p', { class: 'pl-muted' }, `${facts.join('. ')}.`), + card && card.waitingOn && h('p', { class: 'pl-note' }, icon('lock'), `Waiting on ${card.waitingOn}`), + metrics, + h('h4', {}, 'Requires'), + reqs.length ? h('ul', { class: 'pl-reqs' }, reqs) : h('p', { class: 'pl-muted' }, 'No prerequisites.'), + h('h4', {}, 'Unlocks'), + deps.length ? h('p', { class: 'pl-deps' }, deps.map(d => courseLinks([d], v))) : h('p', { class: 'pl-muted' }, 'Nothing else in your checklist.'), + h('div', { class: 'pl-actions' }, actions), + petition, + ])); panel.classList.remove('hidden'); // Phone: show it inline under the selected card. Desktop: floating panel. @@ -881,18 +800,34 @@ function fillWhatifSelect(v) { const keep = sel.value || (state.whatif && state.whatif.code) || ''; const codes = Object.keys(v.now.result.assignedTerm).sort(); if (state.whatif && !codes.includes(state.whatif.code)) codes.push(state.whatif.code); - setHTML(sel, '' + codes.map(c => ``).join('')); + sel.replaceChildren(h('option', { value: '' }, 'pick a course'), ...codes.map(c => h('option', { value: c }, c))); sel.value = codes.includes(keep) ? keep : ''; } function simulate(e) { - e.preventDefault(); + if (e) e.preventDefault(); const code = $('whatifCourse').value; const mode = $('whatifMode').value; if (!code) { $('whatifResult').textContent = 'Pick a course first.'; return; } + // A course already failed on AMIS: its retake is in the plan, so report + // what that failure costs instead of failing it a second time. + const pastFail = mode === 'fail' && state.view.costs.find(c => c.code === code && c.past); + if (pastFail) { + const v = state.view; + const t = v.now.result.assignedTerm[code]; + const retake = t === undefined ? '' : ` The retake is planned for ${absLabel(v.startAbs + t)}.`; + $('whatifResult').textContent = pastFail.cost > 0 + ? `${code} is failed on AMIS, which moves graduation ${termsText(pastFail.cost)} later, to ${absLabel(v.startAbs + v.now.result.gradTermIndex)}.${retake}` + : `${code} is failed on AMIS, but it does not move graduation.${retake}`; + $('whatifClear').classList.remove('hidden'); + state.selected = code; + renderDetail(); + drawEdges(); + return; + } state.whatif = { code, mode }; render(); const v = state.view; @@ -903,7 +838,7 @@ function simulate(e) { const list = pushed.length > 5 ? `${pushed.slice(0, 5).join(', ')} and ${pushed.length - 5} more` : pushed.join(', '); const verb = mode === 'fail' ? `Failing ${code}` : `Taking ${code} ${mode === '1' ? 'a term' : 'a year'} later`; $('whatifResult').textContent = slip > 0 - ? `${verb} moves graduation to ${absLabel(v.startAbs + after.gradTermIndex)} (+${termsText(slip)}).${pushed.length ? ` Also pushed later: ${list}.` : ''}` + ? `${verb} moves graduation to ${absLabel(v.startAbs + after.gradTermIndex)}, ${termsText(slip)} later.${pushed.length ? ` It also pushes back ${list}.` : ''}` : `${verb} does not move graduation.${pushed.length ? ` It shifts ${list}.` : ' Nothing else shifts.'}`; $('whatifClear').classList.remove('hidden'); state.selected = code; @@ -938,25 +873,28 @@ function initControls() { render(); announce('Plan recomputed from scratch.'); }); + const resetDialog = modal($('resetDialog')); $('resetPlan').addEventListener('click', () => { - if (!confirm('Reset the plan? This clears courses you marked passed or failed, courses you moved, and petitions. Your AMIS grades stay.')) return; + $('planMenu').open = false; + resetDialog.open(); + }); + $('resetConfirm').addEventListener('click', () => { Object.assign(state.data, { customCourseStatus: {}, plannerPins: {}, plannerPetitions: {} }); save(['customCourseStatus', 'plannerPins', 'plannerPetitions']); state.whatif = null; $('whatifResult').textContent = ''; - $('planMenu').open = false; + resetDialog.close(); render(); + announce('Plan reset. Your AMIS grades stay.'); }); $('whatifForm').addEventListener('submit', simulate); $('whatifClear').addEventListener('click', clearWhatif); // Legend: one row of chips, built from the same status table as the cards. - const chips = ['passed', 'inprogress', 'failed', 'retake', 'ready', 'planned', 'locked'] - .map(s => `${icon(STATUS[s][0])}${STATUS[s][1]}`).join('') + - 'Critical' + - `${icon('flag')}Petition`; - const chipDoc = new DOMParser().parseFromString(chips, 'text/html'); - $('legend').prepend(...Array.from(chipDoc.body.childNodes, node => document.importNode(node, true))); + $('legend').prepend( + ...['passed', 'inprogress', 'failed', 'retake', 'ready', 'planned', 'locked'].map(st => statusPill(st)), + badge('Critical', { class: 'crit-chip', title: 'Courses that would delay graduation if you slip' }), + badge('Petition', { icon: 'flag' })); const grid = $('plannerGrid'); grid.addEventListener('click', e => { diff --git a/extension/src/popup.css b/extension/src/popup.css index a46efc9..235c93c 100644 --- a/extension/src/popup.css +++ b/extension/src/popup.css @@ -1,4 +1,5 @@ -/* Elbi GradeSim popup, UPLB Tools identity. Colors, type and radii come from tokens.css. */ +/* Elbi GradeSim popup, UPLB Tools identity. Colors, type and radii come from tokens.css. + The planner loads this file too, so the component classes from components.js live here. */ * { margin: 0; padding: 0; @@ -32,40 +33,567 @@ body { display: none !important; } +[hidden] { + display: none !important; +} + a { color: var(--brand); } -a:focus-visible, -button:focus-visible, -input:focus-visible, -select:focus-visible, -label:has(input:focus-visible) { - outline: var(--focus-ring); - outline-offset: 2px; +a:focus-visible, +button:focus-visible, +input:focus-visible, +select:focus-visible, +summary:focus-visible, +label:has(input:focus-visible) { + outline: var(--focus-ring); + outline-offset: 2px; +} + +.icon { + width: 1em; + height: 1em; + flex-shrink: 0; + vertical-align: -0.125em; +} + +h1, +h2, +h3 { + font-family: var(--font-display); + font-weight: 700; + color: var(--ink); +} + +h3 { + font-size: 0.95rem; + margin-bottom: 8px; +} + +fieldset { + border: 0; + min-width: 0; +} + +/* ---------- Components (components.js) ---------- */ + +.card { + background: var(--bg-elevated); + border: 1px solid var(--border); + border-radius: var(--radius-lg); + box-shadow: var(--shadow-sm); +} + +.list-card { + background: var(--bg-elevated); + border: 1px solid var(--border); + border-radius: var(--radius-md); + overflow: hidden; +} + +.list-card:empty { + display: none; +} + +/* Buttons, after the uplbtools.me .btn rules */ +.btn { + display: inline-flex; + align-items: center; + justify-content: center; + gap: 6px; + min-height: 44px; + padding: 8px 18px; + font: 600 0.9rem var(--font-sans); + border-radius: var(--radius-pill); + border: 1px solid transparent; + cursor: pointer; + transition: background var(--transition-fast); +} + +.btn-primary { + background: var(--brand); + color: var(--on-brand); + box-shadow: var(--shadow-sm); +} + +.btn-primary:hover { + background: var(--brand-deep); +} + +.btn-secondary { + background: var(--brand-tint); + border-color: var(--brand-line); + color: var(--brand); +} + +.btn-secondary:hover:not(:disabled) { + background: var(--brand-tint-strong); +} + +.btn-danger { + background: none; + border-color: color-mix(in srgb, var(--bad) 45%, transparent); + color: var(--bad); +} + +.btn-danger:hover { + background: color-mix(in srgb, var(--bad) 10%, transparent); +} + +.btn:disabled { + background: var(--bg-card); + border-color: var(--border); + color: var(--muted); + box-shadow: none; + cursor: not-allowed; +} + +.btn-sm { + min-height: 36px; + padding: 6px 14px; + font-size: 0.8rem; +} + +.btn-text { + display: inline-flex; + align-items: center; + gap: 4px; + min-height: 32px; + padding: 4px 2px; + border: 0; + background: none; + color: var(--brand); + font: 600 0.8rem var(--font-sans); + text-decoration: underline; + text-underline-offset: 3px; + cursor: pointer; +} + +.btn-text:hover { + color: var(--brand-deep); +} + +.btn-icon { + display: inline-flex; + align-items: center; + justify-content: center; + flex: 0 0 auto; + width: 36px; + height: 36px; + border: 0; + border-radius: var(--radius-pill); + background: none; + color: var(--muted); + font-size: 16px; + cursor: pointer; +} + +.btn-icon:hover { + background: var(--brand-tint); + color: var(--ink); +} + +/* Badges: honors, scholar lists, term tags, grade chips */ +.badge { + display: inline-flex; + align-items: center; + gap: 4px; + padding: 2px 10px; + font: 600 0.75rem var(--font-sans); + border-radius: var(--radius-pill); + border: 1px solid var(--border); + background: var(--bg-card); + color: var(--ink); + white-space: nowrap; +} + +.badge-brand { + background: var(--brand-tint); + border-color: var(--brand-line); + color: var(--brand); +} + +.badge-strong { + background: var(--brand); + border-color: var(--brand); + color: var(--on-brand); +} + +.badge-info { + background: color-mix(in srgb, var(--info) 12%, transparent); + border-color: color-mix(in srgb, var(--info) 40%, transparent); + color: var(--info); +} + +.badge-warn { + background: color-mix(in srgb, var(--warn) 8%, transparent); + border-color: color-mix(in srgb, var(--warn) 35%, transparent); + color: var(--warn); +} + +/* Course status: icon plus word, never color alone */ +.status { + display: inline-flex; + align-items: center; + gap: 4px; + font-weight: 600; + color: var(--muted); + white-space: nowrap; +} + +.status-pill { + padding: 2px 9px; + border: 1px solid var(--border); + border-radius: var(--radius-pill); + background: var(--bg-card); + color: var(--body); + font-family: var(--font-sans); + font-size: 0.78rem; + font-weight: 500; +} + +.status.st-passed .icon, +.status-plain.st-passed { color: var(--ok); } +.status.st-inprogress .icon, +.status-plain.st-inprogress { color: var(--info); } +.status.st-failed .icon, +.status.st-retake .icon, +.status-plain.st-failed, +.status-plain.st-retake { color: var(--bad); } +.status.st-ready .icon, +.status-plain.st-ready { color: var(--brand); } + +.units-badge { + font-size: 0.75rem; + color: var(--muted); + white-space: nowrap; + font-variant-numeric: tabular-nums; +} + +/* Stats */ +.stat { + display: flex; + flex-direction: column; +} + +.stat-block { + padding: 8px; + background: var(--bg-card); + border: 1px solid var(--border); + border-radius: var(--radius-md); +} + +.stat-label { + font-size: 0.75rem; + color: var(--muted); +} + +.stat-value { + font-size: 1.1rem; + font-weight: 700; + font-variant-numeric: tabular-nums; +} + +.stat-note { + font-size: 0.75rem; + color: var(--muted); +} + +.stat-row { + flex-direction: row; + justify-content: space-between; + align-items: baseline; + gap: 12px; + padding: 6px 0; + border-bottom: 1px solid var(--border); +} + +.stat-row:last-child { + border-bottom: 0; +} + +.stat-row .stat-label { + font-size: inherit; + color: var(--body); +} + +.stat-row .stat-value { + font-size: inherit; + text-align: right; +} + +.stat-row.highlight .stat-value { + color: var(--brand); +} + +/* The one number a view leads with. */ +.stat-hero .stat-label { + font-size: 0.8rem; + font-weight: 600; +} + +.stat-hero .stat-value { + font-family: var(--font-display); + font-size: 2.4rem; + font-weight: 800; + line-height: 1.1; + color: var(--brand); +} + +/* Last updated line with Refresh. */ +.sync-status { + display: flex; + align-items: center; + justify-content: space-between; + gap: 12px; + padding-top: 8px; + font-size: 0.8rem; + color: var(--muted); + text-align: left; +} + +.sync-status.stale { + color: var(--body); +} + +.sync-status .btn-text { + display: inline-flex; + align-items: center; + gap: 4px; + flex-shrink: 0; + font-weight: 600; +} + +/* Course rows */ +.course-row { + display: flex; + justify-content: space-between; + align-items: center; + gap: 8px; + padding: 8px 12px; + border-top: 1px solid var(--border); +} + +.list-card > .course-row:first-child, +.semester-header + .course-row { + border-top: 0; +} + +.course-info { + flex: 1; + min-width: 0; +} + +.course-code { + font-weight: 600; +} + +.course-title { + font-size: 0.8rem; + color: var(--muted); +} + +.course-aside { + display: flex; + align-items: center; + gap: 10px; +} + +/* Notices */ +.notice { + display: flex; + flex-wrap: wrap; + align-items: flex-start; + gap: 8px; + padding: 10px 12px; + margin-bottom: 12px; + border-radius: var(--radius-md); + border: 1px solid var(--border); + background: var(--bg-card); + color: var(--body); +} + +.notice > .icon { + margin-top: 0.2em; +} + +.notice-text { + flex: 1 1 180px; + min-width: 0; + display: flex; + flex-direction: column; + gap: 2px; +} + +.notice-action { + display: flex; + align-items: center; + gap: 8px; + flex-shrink: 0; + align-self: center; + margin-left: auto; +} + +.notice-action .btn-text { + font-weight: 600; +} + +/* One quiet line, never a banner (#36). */ +.disclaimer { + max-width: 70ch; + font-size: 0.75rem; + line-height: 1.4; + color: var(--muted); } -.icon { - width: 1em; - height: 1em; - flex-shrink: 0; - vertical-align: -0.125em; +.notice-text strong { + color: var(--ink); } -h1, -h2, -h3 { - font-family: var(--font-display); - font-weight: 700; +.notice-ok { + color: var(--ok); + border-color: color-mix(in srgb, var(--ok) 35%, transparent); + background: color-mix(in srgb, var(--ok) 8%, transparent); +} + +.notice-warn { + color: var(--warn); + border-color: color-mix(in srgb, var(--warn) 35%, transparent); + background: color-mix(in srgb, var(--warn) 8%, transparent); +} + +.notice-bad { + color: var(--bad); + border-color: color-mix(in srgb, var(--bad) 35%, transparent); + background: color-mix(in srgb, var(--bad) 8%, transparent); +} + +.notice-info > .icon { + color: var(--info); +} + +.notice-ok .notice-text strong, +.notice-warn .notice-text strong, +.notice-bad .notice-text strong { + color: inherit; +} + +.empty-state { + display: flex; + align-items: center; + gap: 6px; + color: var(--muted); + font-size: 0.85rem; +} + +/* Tabs: plain segmented row with a maroon underline */ +.tabs { + display: flex; + border-bottom: 1px solid var(--border); + margin-bottom: 12px; +} + +.tab { + flex: 1; + min-height: 40px; + background: none; + border: 0; + border-bottom: 2px solid transparent; + margin-bottom: -1px; + font: 600 0.85rem var(--font-sans); + color: var(--muted); + cursor: pointer; +} + +.tab:hover { color: var(--ink); } -h3 { - font-size: 0.95rem; - margin-bottom: 8px; +.tab[aria-selected="true"] { + color: var(--brand); + border-bottom-color: var(--brand); +} + +/* Dialogs: native opened with showModal() */ +.modal { + width: min(560px, calc(100% - 32px)); + max-height: calc(100% - 32px); + margin: auto; + padding: 18px; + color: var(--body); + background: var(--bg-elevated); + border: 1px solid var(--border); + border-radius: var(--radius-lg); + box-shadow: var(--shadow-lg); +} + +.modal[open] { + display: flex; + flex-direction: column; +} + +.modal::backdrop { + background: color-mix(in srgb, var(--bg-page) 80%, transparent); + backdrop-filter: blur(4px); +} + +.modal-header { + position: relative; + padding: 0 40px 12px 0; + margin-bottom: 12px; + border-bottom: 1px solid var(--border); +} + +.modal-header.centered { + padding: 0 40px 12px; + text-align: center; +} + +.modal-header h2 { + font-size: 1.1rem; +} + +.modal-header p { + color: var(--muted); +} + +.modal-close { + position: absolute; + top: -8px; + right: -8px; +} + +.modal-body { + flex: 1; + overflow-y: auto; + line-height: 1.55; +} + +.modal-footer { + display: flex; + flex-direction: column; + gap: 10px; + padding-top: 12px; + margin-top: 12px; + border-top: 1px solid var(--border); +} + +.modal-footer .btn { + width: 100%; +} + +.modal-actions { + flex-direction: row; + justify-content: flex-end; +} + +.modal-actions .btn { + width: auto; } -/* Header */ +/* ---------- Popup layout ---------- */ + header { display: flex; align-items: center; @@ -131,6 +659,7 @@ header h1 a:hover { } header h1 { + white-space: nowrap; font-size: 1.15rem; font-weight: 800; line-height: 1.2; @@ -166,27 +695,19 @@ header h1 { color: var(--muted); } -.credits a { +.credits a, +.credits .btn-text { color: var(--muted); + font: inherit; + min-height: 0; + padding: 0; } -.credits a:hover { +.credits a:hover, +.credits .btn-text:hover { color: var(--brand); } -/* Cards */ -.program-selector, -.gwa-section, -.loading, -.no-data, -.track-selector, -.whatif-results { - background: var(--bg-elevated); - border: 1px solid var(--border); - border-radius: var(--radius-lg); - box-shadow: var(--shadow-sm); -} - /* Program selector */ .program-selector { padding: 12px; @@ -222,6 +743,10 @@ input[type="number"] { .no-data { text-align: center; padding: 32px 20px; + background: var(--bg-elevated); + border: 1px solid var(--border); + border-radius: var(--radius-lg); + box-shadow: var(--shadow-sm); } .loading p, @@ -230,7 +755,7 @@ input[type="number"] { font-size: 1rem; } -.no-data p:first-child { +.no-data-title { display: inline-flex; align-items: center; gap: 6px; @@ -238,184 +763,77 @@ input[type="number"] { font-weight: 600; } +.steps { + display: inline-block; + margin: 0 0 16px; + padding-left: 1.4em; + text-align: left; +} + .hint { font-size: 0.85rem !important; color: var(--muted); line-height: 1.5; } -/* Buttons, after the uplbtools.me .btn rules */ -.btn { - display: inline-flex; - align-items: center; - justify-content: center; - gap: 6px; - min-height: 44px; - padding: 8px 18px; - font: 600 0.9rem var(--font-sans); - border-radius: var(--radius-pill); - border: 1px solid transparent; - cursor: pointer; - transition: background var(--transition-fast); -} - -.btn-primary { - background: var(--brand); - color: var(--on-brand); - box-shadow: var(--shadow-sm); -} - -.btn-primary:hover { - background: var(--brand-deep); -} - -.btn:disabled { - background: var(--bg-card); - border-color: var(--border); - color: var(--muted); - box-shadow: none; - cursor: not-allowed; -} - -.btn-sm { - padding: 6px 14px; - font-size: 0.8rem; -} - -.btn-secondary, -.wrapped-nav-btn { - background: var(--brand-tint); - border: 1px solid var(--brand-line); - color: var(--brand); -} - -.btn-secondary:hover, -.wrapped-nav-btn:hover:not(:disabled) { - background: var(--brand-tint-strong); -} - -/* GWA stats */ -.gwa-section { - padding: 16px; - margin-bottom: 16px; - text-align: center; -} - -.gwa-section h2 { - font-size: 0.8rem; - font-family: var(--font-sans); - font-weight: 600; - color: var(--muted); -} - -.gwa-value { - display: block; - font-family: var(--font-display); - font-size: 2.4rem; - font-weight: 800; - line-height: 1.15; - color: var(--brand); - font-variant-numeric: tabular-nums; - margin-bottom: 12px; -} - -.gwa-stats { - display: grid; - grid-template-columns: 1fr 1fr; - gap: 8px; -} - -.stat { - display: flex; - flex-direction: column; - padding: 8px; - background: var(--bg-card); - border: 1px solid var(--border); - border-radius: var(--radius-md); -} - -.stat-label { - font-size: 0.75rem; - color: var(--muted); -} - -.stat-value { - font-size: 1.1rem; - font-weight: 700; - font-variant-numeric: tabular-nums; -} - -.honor-status:not(:empty), -.scholar-badge { - display: inline-flex; - align-items: center; - gap: 4px; - padding: 2px 10px; - font-size: 0.75rem; - font-weight: 600; - border-radius: var(--radius-pill); - background: var(--brand-tint); - border: 1px solid var(--brand-line); - color: var(--brand); -} - -.honor-status:not(:empty) { - margin-top: 12px; +/* GWA */ +.gwa-section { + padding: 16px; + margin-bottom: 16px; + text-align: center; } -/* Honor tiers step from solid maroon down to a neutral chip. The text always names the tier. */ -.honor-status.summa, -.scholar-badge.university-scholar { - background: var(--brand); - border-color: var(--brand); - color: var(--on-brand); +.gwa-section h2 { + font-size: 0.8rem; + font-family: var(--font-sans); + font-weight: 600; + color: var(--muted); } -.honor-status.magna { - background: var(--brand-tint-strong); +.gwa-value { + display: block; + font-family: var(--font-display); + font-size: 2.4rem; + font-weight: 800; + line-height: 1.15; + color: var(--brand); + font-variant-numeric: tabular-nums; + margin-bottom: 12px; } -.honor-status.honor-roll, -.scholar-badge.honor-roll { - background: var(--bg-card); - border-color: var(--border); - color: var(--ink); +.gwa-section .disclaimer { + margin: 0 auto; } -/* Tabs: plain segmented row with a maroon underline */ -.tabs { - display: flex; - border-bottom: 1px solid var(--border); - margin-bottom: 12px; +/* Stats sit in one divided strip on the card surface, not in boxes. */ +.gwa-stats { + margin-top: 12px; + border-top: 1px solid var(--border); } -.tab { - flex: 1; - min-height: 40px; - background: none; - border: 0; - border-bottom: 2px solid transparent; - margin-bottom: -1px; - font: 600 0.85rem var(--font-sans); - color: var(--muted); - cursor: pointer; +.honor-status:not(:empty) { + margin-bottom: 10px; } -.tab:hover { - color: var(--ink); +/* First run: only the program picker until one is chosen (#82). */ +body.needs-program .gwa-section, +body.needs-program .content-column, +body.needs-program .credits { + display: none; } -.tab.active { - color: var(--brand); - border-bottom-color: var(--brand); +body.needs-program .program-selector { + border-color: var(--brand-line); } -.tab-content { - display: none; +body.needs-program #programLabel { + color: var(--ink); + font-size: 0.95rem; } -.tab-content.active { - display: block; +.terms-notice, +#syncNotice .notice { + margin-bottom: 12px; } /* Semester or year toggle */ @@ -440,7 +858,7 @@ input[type="number"] { cursor: pointer; } -.view-btn.active { +.view-btn[aria-pressed="true"] { background: var(--bg-elevated); color: var(--ink); box-shadow: var(--shadow-sm); @@ -449,10 +867,6 @@ input[type="number"] { /* Grade list. No inner scroll box: the popup itself scrolls. */ .semester-group { margin-bottom: 12px; - background: var(--bg-elevated); - border: 1px solid var(--border); - border-radius: var(--radius-md); - overflow: hidden; } .semester-header { @@ -464,12 +878,13 @@ input[type="number"] { padding: 8px 12px; background: var(--bg-card); border-bottom: 1px solid var(--border); - font-weight: 600; } .group-title { flex: 1; min-width: 140px; + margin: 0; + font: 600 0.85rem var(--font-sans); } .group-gwa-info { @@ -481,48 +896,8 @@ input[type="number"] { .group-gwa { color: var(--muted); - font-variant-numeric: tabular-nums; -} - -.course-item, -.remaining-item { - display: flex; - justify-content: space-between; - align-items: center; - gap: 8px; - padding: 8px 12px; - border-top: 1px solid var(--border); -} - -.semester-header + .course-item { - border-top: 0; -} - -.course-info { - flex: 1; - min-width: 0; -} - -.course-code { font-weight: 600; -} - -.course-title { - font-size: 0.8rem; - color: var(--muted); -} - -.course-grade { - display: flex; - align-items: center; - gap: 6px; -} - -.units-badge { - font-size: 0.75rem; - color: var(--muted); - min-width: 24px; - text-align: right; + font-variant-numeric: tabular-nums; } .grade-value { @@ -530,7 +905,7 @@ input[type="number"] { align-items: center; justify-content: flex-end; gap: 4px; - min-width: 56px; + min-width: 44px; font-weight: 700; font-variant-numeric: tabular-nums; color: var(--ink); @@ -549,101 +924,69 @@ input[type="number"] { color: var(--ok); } -.course-item.non-numeric { +.grade-value.pending { + font-size: 0.75rem; + font-weight: 500; + color: var(--muted); +} + +.course-row.non-numeric { background: var(--bg-card); } -.course-item.excluded { +.course-row.excluded .course-code, +.course-row.excluded .course-title, +.course-row.excluded .course-aside { opacity: 0.6; } -.course-item.excluded .course-code, -.course-item.excluded .course-title { +.course-row.excluded .course-code, +.course-row.excluded .course-title { text-decoration: line-through; } .exclude-toggle { - min-height: 32px; - min-width: 48px; - padding: 4px 10px; - border-radius: var(--radius-pill); - border: 1px solid var(--border-input); - background: var(--bg-elevated); + min-height: 28px; + text-decoration: none; + font-size: 0.75rem; + font-weight: 500; color: var(--muted); - font: 600 0.75rem var(--font-sans); - cursor: pointer; -} - -.exclude-toggle:hover, -.exclude-toggle.excluded { - border-color: var(--brand-line); - background: var(--brand-tint); - color: var(--brand); -} - -/* Notices */ -.shiftee-info, -.track-info, -.free-elective-notice, -.result-summary { - padding: 10px 12px; - margin-bottom: 12px; - border-radius: var(--radius-md); - border: 1px solid var(--border); - background: var(--bg-card); } -.shiftee-info small { - display: block; - margin-top: 4px; - color: var(--muted); +.exclude-toggle:hover { + text-decoration: underline; } -.track-info.detected, -.result-summary.achieved, -.result-summary.excellent, -.result-summary.good, -.free-elective-notice.complete { - color: var(--ok); - border-color: color-mix(in srgb, var(--ok) 35%, transparent); - background: color-mix(in srgb, var(--ok) 8%, transparent); +.course-row.excluded .exclude-toggle { + color: var(--brand); + font-weight: 600; } -.track-info.not-detected, -.shiftee-info, -.result-summary.moderate, -.result-summary.difficult, -.free-elective-notice.unavailable { - color: var(--warn); - border-color: color-mix(in srgb, var(--warn) 35%, transparent); - background: color-mix(in srgb, var(--warn) 8%, transparent); +/* What if */ +.honor-fieldset legend { + margin-bottom: 6px; + font-weight: 600; + color: var(--ink); } -.result-summary.impossible { - color: var(--bad); - border-color: color-mix(in srgb, var(--bad) 35%, transparent); - background: color-mix(in srgb, var(--bad) 8%, transparent); +.honor-options { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 6px; + margin-bottom: 16px; } -.result-summary, -.track-info { - display: flex; - align-items: center; - gap: 8px; - font-weight: 600; +.honor-options .choice { + padding: 8px 10px; } -/* What if */ -.honor-options, .track-options { display: flex; flex-direction: column; gap: 6px; - margin-bottom: 16px; } -.honor-option, -.track-option { +.choice { display: flex; align-items: center; gap: 10px; @@ -654,226 +997,98 @@ input[type="number"] { cursor: pointer; } -.honor-option:hover, -.track-option:hover { - border-color: var(--brand-line); -} - -.honor-option:has(input:checked), -.track-option:has(input:checked) { - border-color: var(--brand); - background: var(--brand-tint); -} - -input[type="radio"] { - accent-color: var(--brand); - width: 16px; - height: 16px; -} - -.honor-label, -.track-label { - display: flex; - flex-direction: column; - flex: 1; -} - -.honor-label small, -.track-label small { - color: var(--muted); - font-size: 0.8rem; -} - -.honor-label input[type="number"] { - margin-top: 6px; - padding: 6px 8px; - width: 120px; -} - -#calculateBtn { - width: 100%; - margin-bottom: 16px; -} - -.whatif-results, -.track-selector { - padding: 14px; - margin-bottom: 16px; -} - -.result-row { - display: flex; - justify-content: space-between; - gap: 12px; - padding: 6px 0; - border-bottom: 1px solid var(--border); -} - -.result-row:last-child { - border-bottom: 0; -} - -.result-row .value { - font-weight: 700; - font-variant-numeric: tabular-nums; - text-align: right; -} - -.result-row.highlight .value { - color: var(--brand); -} - -.remaining-courses { - margin-top: 16px; -} - -.remaining-courses .hint { - margin-bottom: 8px; -} - -#remainingList { - background: var(--bg-elevated); - border: 1px solid var(--border); - border-radius: var(--radius-md); - overflow: hidden; -} - -#remainingList > :first-child { - border-top: 0; -} - -#remainingList .free-elective-notice { - margin: 8px; -} - -/* Wrapped. The panel content and share card are redone in a later step. */ -.wrapped-panel { - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - gap: 8px; - min-height: 300px; - padding: 20px; - text-align: center; - background: var(--bg-elevated); - border: 1px solid var(--border); - border-radius: var(--radius-lg); - box-shadow: var(--shadow-sm); -} - -.panel-emoji { - font-size: 2.5rem; -} - -.panel-title { - font-family: var(--font-display); - font-weight: 700; - color: var(--muted); -} - -.panel-value { - font-family: var(--font-display); - font-size: 2rem; - font-weight: 800; - color: var(--brand); -} - -.panel-subtitle { - color: var(--muted); - max-width: 300px; +.choice:hover { + border-color: var(--brand-line); } -.panel-message { - padding: 8px 12px; - border-radius: var(--radius-md); +.choice:has(input:checked) { + border-color: var(--brand); background: var(--brand-tint); - color: var(--ink); - max-width: 300px; } -.highlight-course { - width: 100%; - max-width: 300px; - padding: 8px 12px; - text-align: left; - border-radius: var(--radius-md); - background: var(--bg-card); - border: 1px solid var(--border); +input[type="radio"], +input[type="checkbox"] { + accent-color: var(--brand); + width: 16px; + height: 16px; + flex-shrink: 0; } -.highlight-course .course-label { - font-size: 0.8rem; +.choice-label { + display: flex; + flex-direction: column; + flex: 1; +} + +.choice-label small { color: var(--muted); + font-size: 0.8rem; } -.highlight-course .course-name { - font-weight: 700; +.choice-label input[type="number"] { + margin-top: 6px; + padding: 6px 8px; + width: 120px; } -.grade-badges { +.whatif-results { display: flex; - flex-wrap: wrap; - justify-content: center; - gap: 6px; + flex-direction: column; + gap: 10px; + padding: 14px; + margin-bottom: 14px; } -.grade-badge { - padding: 2px 10px; - border-radius: var(--radius-pill); - border: 1px solid var(--border); - background: var(--bg-card); - font-weight: 600; +.whatif-results .notice { + margin: 0; } -.grade-badge.special { - color: var(--warn); - border-color: color-mix(in srgb, var(--warn) 35%, transparent); - background: color-mix(in srgb, var(--warn) 8%, transparent); +/* The results card stays in the page (it is a live region) but takes no + space until there is something to say. */ +.whatif-results:empty { + padding: 0; + margin: 0; + border: 0; + box-shadow: none; } -.wrapped-nav { - display: flex; - justify-content: space-between; - align-items: center; - padding: 12px 0; +.remaining-summary { + margin-bottom: 16px; } -.wrapped-nav-btn { - display: inline-flex; - align-items: center; - gap: 4px; - min-height: 36px; - padding: 6px 14px; - border-radius: var(--radius-pill); - font: 600 0.85rem var(--font-sans); - cursor: pointer; +.remaining-link { + width: 100%; + margin-top: 10px; } -.wrapped-nav-btn:disabled { - opacity: 0.45; - cursor: not-allowed; +/* Track and substitutions, folded away by default. */ +.whatif-more > summary { + display: flex; + align-items: center; + justify-content: space-between; + min-height: 44px; + padding: 0 14px; + font-weight: 600; + color: var(--ink); + cursor: pointer; + list-style: none; } -.wrapped-progress { - color: var(--muted); - font-variant-numeric: tabular-nums; +.whatif-more > summary::-webkit-details-marker { + display: none; } -.wrapped-export { - width: 100%; +.whatif-more[open] > summary .icon { + transform: rotate(180deg); } -/* Course substitutions */ +.track-selector, .substitutions-container { - padding: 14px; - margin-bottom: 16px; - background: var(--bg-elevated); - border: 1px solid var(--border); - border-radius: var(--radius-lg); - box-shadow: var(--shadow-sm); + padding: 12px 14px 14px; + border-top: 1px solid var(--border); } +/* Course substitutions */ .substitutions-list { display: flex; flex-direction: column; @@ -881,11 +1096,6 @@ input[type="radio"] { margin-bottom: 12px; } -.no-substitutions-notice { - color: var(--muted); - font-size: 0.85rem; -} - .substitution-item { display: flex; justify-content: space-between; @@ -902,21 +1112,12 @@ input[type="radio"] { font-size: 0.8rem; } -.btn-remove-sub { - display: inline-flex; - align-items: center; - justify-content: center; - width: 36px; - height: 36px; - border: 0; - border-radius: var(--radius-pill); - background: none; +.btn-remove { color: var(--bad); - font-size: 16px; - cursor: pointer; } -.btn-remove-sub:hover { +.btn-remove:hover { + color: var(--bad); background: color-mix(in srgb, var(--bad) 12%, transparent); } @@ -928,6 +1129,10 @@ input[type="radio"] { border-top: 1px solid var(--border); } +.add-sub-box .notice { + margin: 0; +} + .sub-field { display: flex; flex-direction: column; @@ -950,130 +1155,117 @@ input[type="radio"] { width: 100%; } -/* Data import and export */ -.data-actions-footer { +/* Wrapped */ +.wrapped-panel { display: flex; + flex-direction: column; + align-items: center; justify-content: center; gap: 8px; - margin-top: 16px; - padding-top: 12px; - border-top: 1px solid var(--border); + min-height: 300px; + padding: 20px; + text-align: center; } -/* Modals. Terms and help share the shell. */ -.terms-overlay { - position: fixed; - inset: 0; - z-index: 100; - display: flex; - justify-content: center; - align-items: center; - padding: 16px; - background: color-mix(in srgb, var(--bg-page) 80%, transparent); - backdrop-filter: blur(4px); +.panel-emoji { + font-size: 2.5rem; } -.terms-modal { - position: relative; - display: flex; - flex-direction: column; - width: 100%; - max-width: 560px; - max-height: 100%; - padding: 18px; - background: var(--bg-elevated); - border: 1px solid var(--border); - border-radius: var(--radius-lg); - box-shadow: var(--shadow-lg); +.panel-title { + margin: 0; + font-size: 1rem; + color: var(--muted); } -.terms-header { - padding: 0 40px 12px; - margin-bottom: 12px; - text-align: center; - border-bottom: 1px solid var(--border); +.panel-value { + font-family: var(--font-display); + font-size: 2rem; + font-weight: 800; + color: var(--brand); + font-variant-numeric: tabular-nums; } -.terms-header h2 { - font-size: 1.1rem; +.panel-subtitle { + color: var(--muted); + max-width: 300px; } -.terms-header p { - color: var(--muted); +.panel-message { + padding: 8px 12px; + border-radius: var(--radius-md); + background: var(--brand-tint); + color: var(--ink); + max-width: 300px; } -.terms-content { - flex: 1; - overflow-y: auto; - margin-bottom: 12px; - line-height: 1.55; - color: var(--body); +.highlight-course { + width: 100%; + max-width: 300px; + padding: 8px 12px; + text-align: left; + border-radius: var(--radius-md); + background: var(--bg-card); + border: 1px solid var(--border); } -.terms-section + .terms-section { - margin-top: 12px; +.highlight-label, +.highlight-detail { + font-size: 0.8rem; + color: var(--muted); } -.terms-section h3 { - margin-bottom: 4px; +.highlight-name { + font-weight: 700; } -.terms-footer { +.grade-badges { display: flex; - flex-direction: column; - gap: 10px; - padding-top: 12px; - border-top: 1px solid var(--border); + flex-wrap: wrap; + justify-content: center; + gap: 6px; } -.terms-agreement-label { +.wrapped-nav { display: flex; - align-items: flex-start; - gap: 8px; - cursor: pointer; + justify-content: space-between; + align-items: center; + padding: 12px 0; } -.terms-agreement-label input { - accent-color: var(--brand); - width: 16px; - height: 16px; - margin-top: 1px; - flex-shrink: 0; +.wrapped-progress { + color: var(--muted); + font-variant-numeric: tabular-nums; } -.terms-modal .btn { +.wrapped-export { width: 100%; } -.close-terms-btn { - position: absolute; - top: 10px; - right: 10px; - display: inline-flex; - align-items: center; +/* Data import and export */ +.data-actions-footer { + display: flex; justify-content: center; - width: 36px; - height: 36px; - border: 0; - border-radius: var(--radius-pill); - background: none; - color: var(--muted); - font-size: 18px; - cursor: pointer; + gap: 8px; + margin-top: 16px; + padding-top: 12px; + border-top: 1px solid var(--border); } -.close-terms-btn:hover { - background: var(--brand-tint); - color: var(--ink); +#dataNotice .notice { + margin: 12px 0 0; } -.help-modal { - max-width: 500px; +/* Terms and help dialogs */ +.terms-section + .terms-section { + margin-top: 12px; } -.help-modal .terms-header { - text-align: left; - padding-left: 0; +.terms-section h3 { + margin-bottom: 4px; +} + +.help-modal { + width: min(500px, calc(100% - 32px)); } .help-item { @@ -1092,30 +1284,17 @@ input[type="radio"] { border-bottom: 0; } -.help-badge { +.help-icon { + display: inline-flex; + align-items: center; + justify-content: center; flex-shrink: 0; - width: 72px; - padding: 4px; - text-align: center; - font-size: 0.7rem; - font-weight: 700; - letter-spacing: 0.02em; - color: var(--ink); - background: var(--bg-card); - border: 1px solid var(--border); + width: 32px; + height: 32px; border-radius: var(--radius-pill); -} - -.help-badge.btn-primary { - color: var(--on-brand); - background: var(--brand); - border-color: var(--brand); -} - -.help-badge.scholar { - background: var(--brand-tint-strong); - border-color: var(--brand-line); + background: var(--brand-tint); color: var(--brand); + font-size: 16px; } .help-text { @@ -1129,6 +1308,10 @@ input[type="radio"] { color: var(--ink); } +.help-text .btn { + margin-top: 8px; +} + .legend-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(120px, 1fr)); @@ -1143,14 +1326,14 @@ input[type="radio"] { font-size: 0.8rem; } -.legend-grade { +.legend-item dt { min-width: 34px; font-weight: 700; font-variant-numeric: tabular-nums; color: var(--ink); } -.legend-desc { +.legend-item dd { color: var(--muted); } diff --git a/extension/src/popup.html b/extension/src/popup.html index 36bf31a..86eca75 100644 --- a/extension/src/popup.html +++ b/extension/src/popup.html @@ -6,6 +6,7 @@ Elbi GradeSim +
    @@ -16,371 +17,274 @@

    Elbi Gr

    - - - + + +
    -
    -

    Loading grades data...

    -

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

    +
    +
    + +
    +

    Loading your grades

    +

    Reading the grades saved in this browser.

    +
    - -