Skip to content

Commit

Permalink
Added classification calculation logic
Browse files Browse the repository at this point in the history
  • Loading branch information
felpsey committed Jan 1, 2025
1 parent 3c3b9c2 commit aa96995
Show file tree
Hide file tree
Showing 4 changed files with 141 additions and 86 deletions.
78 changes: 28 additions & 50 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,50 +1,28 @@
# React + TypeScript + Vite

This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.

Currently, two official plugins are available:

- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh
- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh

## Expanding the ESLint configuration

If you are developing a production application, we recommend updating the configuration to enable type aware lint rules:

- Configure the top-level `parserOptions` property like this:

```js
export default tseslint.config({
languageOptions: {
// other options...
parserOptions: {
project: ['./tsconfig.node.json', './tsconfig.app.json'],
tsconfigRootDir: import.meta.dirname,
},
},
})
```

- Replace `tseslint.configs.recommended` to `tseslint.configs.recommendedTypeChecked` or `tseslint.configs.strictTypeChecked`
- Optionally add `...tseslint.configs.stylisticTypeChecked`
- Install [eslint-plugin-react](https://github.com/jsx-eslint/eslint-plugin-react) and update the config:

```js
// eslint.config.js
import react from 'eslint-plugin-react'

export default tseslint.config({
// Set the react version
settings: { react: { version: '18.3' } },
plugins: {
// Add the react plugin
react,
},
rules: {
// other rules...
// Enable its recommended rules
...react.configs.recommended.rules,
...react.configs['jsx-runtime'].rules,
},
})
```
# OU Degree Estimator

This project aims to automate the calculation of the grade of degree programmes issued by the (Open University)[https://open.ac.uk].

## Usage

### Hosted

The application is accessible on GitHub pages at https://felpsey.github.io/ou-degree-estimator

### Local

#### Prerequisites

- Node v20.16.0
- Modern Web Browser

#### Guide

1. Clone this repository
2. Run `npm install && npm run dev`
3. Open http://localhost:5173/ou-degree-estimator in a web browser

# Disclaimer

This project provides estimations only, and does not account for academic discretion by the University. Additionally, this project is not affiliated with the Open University in any capacity.

The algorithms used to compute estimated grades apply the logic and criteria outlined in the "Understanding your Class of Honours Guidance Document: Undergraduate Bachelor's Degrees" policy (Open University, 2023), available (here)[https://help.open.ac.uk/documents/policies/working-out-your-class-of-honours/files/252/Understanding%20your%20Class%20of%20Honours%20Bachelors%20May%2023.pdf].
28 changes: 17 additions & 11 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,14 @@ import { PlusIcon } from '@heroicons/react/24/solid';
import NewModuleModal from './components/NewModuleModal';
import ModuleTable from './components/ModuleTable';
import ProgrammeResultCard from './components/ProgrammeResultCard';

import { Module } from './domain/Module';
import { Programme } from './domain/Programme';


function App() {
const [isModalOpen, setIsModalOpen] = useState<boolean>(false);
const [modules, setModules] = useState<Module[]>([]);
const [programme, setProgramme] = useState<Programme>(new Programme("Example programme", modules));
const [programme, setProgramme] = useState<Programme>(new Programme("Example programme"));

// Decode search params when the component is mounted
useEffect(() => {
Expand All @@ -25,9 +25,9 @@ function App() {
// Update encoded search params when modules state is changed
useEffect(() => {
if (programme.modules.length !== 0) {
updateSearchParams()
// updateSearchParams()
}
}, [modules]);
}, [programme]);

function decodeSearchParams(): void {
let currentUrl: URL = new URL(document.location.href);
Expand All @@ -43,23 +43,29 @@ function App() {
return new Module(module._code, module._credits, module._stage, module._grade);
});

setModules(structuredModuleData);
setProgramme(programme.modules.structuredModuleData);

Check failure on line 46 in src/App.tsx

View workflow job for this annotation

GitHub Actions / deploy

Property 'structuredModuleData' does not exist on type 'Module[]'.
}

function encodeModuleData(): string {
// Convert modules array to JSON then base64 encode
return btoa(JSON.stringify(modules));
return btoa(JSON.stringify(programme));
}

function newModuleEntry(): void {
console.log(programme)
setIsModalOpen(true);
}

function addModuleData(module: Module): void {
// Copy contents of existing modules array and append new module object
setModules([...modules, module]);
}
setProgramme((currentProgramme) => {
// Create a new array with the added module
const updatedModules = [...currentProgramme.modules, module];

// Return a new Programme instance with updated modules
return new Programme(currentProgramme.name, updatedModules);

Check failure on line 64 in src/App.tsx

View workflow job for this annotation

GitHub Actions / deploy

Property 'name' does not exist on type 'Programme'.
});

console.log(programme);
}

function updateSearchParams(): void {

Check failure on line 70 in src/App.tsx

View workflow job for this annotation

GitHub Actions / deploy

'updateSearchParams' is declared but its value is never read.
// Encode module data in memory
Expand Down Expand Up @@ -94,7 +100,7 @@ function App() {
</div>

<div className='container mt-4 p-2'>
<ModuleTable modules={modules} />
<ModuleTable modules={programme.modules} />
</div>

<div className='container mt-4 p-2'>
Expand Down
6 changes: 5 additions & 1 deletion src/components/ProgrammeResultCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,11 @@ function ProgrammeResultCard({programme}: ProgrammeResultCardProps) {

return (
<div>
<p>Degree Classification...</p>
<h1 className='font-bold'>Summary</h1>
<p>Total Credits: {programme.totalCredits }</p>
<p>Total Weighted Credits: { programme.totalWeightedGrade }</p>
<p>Classification: { programme.finalGrade }</p>
<p>Borderline Classification: {programme.borderlineClassification }</p>
</div>
)
}
Expand Down
115 changes: 91 additions & 24 deletions src/domain/Programme.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,42 +6,57 @@ export class Programme {
private _totalCredits: number = 0;
private _totalWeightedGrade: number = 0;
private _finalGrade: string = "";
private _borderlineClassification: string = "";

constructor(title:string, modules: Array<Module>) {
constructor(title: string, modules: Module[] = []) {
this._title = title;
this._modules = modules;

modules.forEach(module => {
this._totalCredits = this._totalCredits + module.credits
});

this._totalCredits = this.calculateTotalCredits();
this._totalWeightedGrade = this.calculateTotalWeightedGrade();

this._finalGrade = this.calculateFinalGrade();
this._borderlineClassification = this.calculateBorderlineClassification();
}

get title() {
return this._title
}

get modules() {
get modules(): Module[] {
return this._modules;
}

get finalGrade() {
this._finalGrade = this.calculateFinalGrade();

return this._finalGrade;
}

get totalWeightedGrade() {
this._totalWeightedGrade = calculateTotalWeightedGrade();
this._totalWeightedGrade = this.calculateTotalWeightedGrade();

return this._totalWeightedGrade;
}

get totalCredits() {
this._totalCredits = this.calculateTotalCredits();

console.log(this._totalCredits);

return this._totalCredits;
}

get borderlineClassification() {
this._borderlineClassification = this.calculateBorderlineClassification();

return this._borderlineClassification;
}

set title(value: string) {
this._title = value;
}

set modules(value: Array<Module>) {
this._modules = value;
set modules(modules: Module[]) {
this._modules = modules;
}

hasEnoughCredits(): boolean {
Expand All @@ -53,26 +68,28 @@ export class Programme {
}

calculateFinalGrade(): string {
switch (this._totalWeightedGrade) {
case 360<630:
this._finalGrade = "First Class"

switch (true) {
case this._totalCredits <359:
this._finalGrade = "Not Enough Credits"
break;
case 631<900:
this._finalGrade = "Upper Second Class (2:1)"

case this._totalWeightedGrade >= 360 && this._totalWeightedGrade <= 630:
this._finalGrade = "First Class";
break;
case 901<1170:
this._finalGrade = "Lower Second Class (2:2)"

case this._totalWeightedGrade >= 631 && this._totalWeightedGrade <= 900:
this._finalGrade = "Upper Second Class (2:1)";
break;
case 1171<1440:
this._finalGrade = "Third Class"

case this._totalWeightedGrade >= 901 && this._totalWeightedGrade <= 1170:
this._finalGrade = "Lower Second Class (2:2)";
break;
case this._totalWeightedGrade >= 1171 && this._totalWeightedGrade <= 1440:
this._finalGrade = "Third Class";
break;
default:
this._finalGrade = "No Classification";
break;
}

return this._finalGrade;
}

calculateTotalWeightedGrade(): number {
Expand All @@ -84,4 +101,54 @@ export class Programme {

return totalWeightedGrade;
}

calculateTotalCredits(): number {
let totalCredits: number = 0;

this._modules.forEach(module => {
totalCredits = totalCredits + module.credits
});

return totalCredits;
}

calculateBorderlineClassification(): string {
let creditsAtGrade1 = 0;
let creditsAtGrade2 = 0;
let creditsAtGrade3 = 0;

this._modules.forEach(module => {
if (module.stage == 3) {
if (module.grade == 1) {
creditsAtGrade1 = module.credits + creditsAtGrade1
}
if (module.grade == 2) {
creditsAtGrade2 = module.credits + creditsAtGrade2
}
if (module.grade == 3) {
creditsAtGrade3 = module.credits + creditsAtGrade3
}
}
});

if (this._totalWeightedGrade >= 631 && this._totalWeightedGrade <= 690) {
if (creditsAtGrade1 >= 60) {
return "First Class"
}
}

if (this._totalWeightedGrade >= 901 && this._totalWeightedGrade <= 960) {
if (creditsAtGrade1 >= 60 || creditsAtGrade2 >= 60) {
return "Upper Second Class (2:1)"
}
}

if (this._totalWeightedGrade >= 1171 && this._totalWeightedGrade <= 1230) {
if (creditsAtGrade1 >= 60 || creditsAtGrade2 >= 60 || creditsAtGrade3 >= 60) {
return "Lower Second Class (2:2)"
}
}

return this._finalGrade;
}
}

0 comments on commit aa96995

Please sign in to comment.