Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: grades by professor #225

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file modified public/database/grade_distributions.db
Binary file not shown.
2 changes: 2 additions & 0 deletions src/shared/types/Distribution.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ export type CourseSQLRow = {
Course_Number: string;
Course_Title: string;
Course_Full_Title: string;
Instructor_First: string | null;
Instructor_Last: string | null;
A: number;
A_Minus: number;
B_Plus: number;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,13 +52,14 @@ const GRADE_COLORS = {
*/
export default function GradeDistribution({ course }: GradeDistributionProps): JSX.Element {
const [semester, setSemester] = useState('Aggregate');
const [distributions, setDistributions] = useState<Record<string, Distribution>>({});
type Distributions = Record<string, { data: Distribution; instructorIncluded: boolean }>;
const [distributions, setDistributions] = useState<Distributions>({});
const [status, setStatus] = useState<DataStatusType>(DataStatus.LOADING);
const ref = useRef<HighchartsReact.RefObject>(null);

const chartData = useMemo(() => {
if (status === DataStatus.FOUND && distributions[semester]) {
return Object.entries(distributions[semester]!).map(([grade, count]) => ({
return Object.entries(distributions[semester]!.data).map(([grade, count]) => ({
y: count,
color: GRADE_COLORS[grade as LetterGrade],
}));
Expand All @@ -69,8 +70,11 @@ export default function GradeDistribution({ course }: GradeDistributionProps): J
useEffect(() => {
const fetchInitialData = async () => {
try {
const [aggregateDist, semesters] = await queryAggregateDistribution(course);
const initialDistributions: Record<string, Distribution> = { Aggregate: aggregateDist };
const [aggregateDist, semesters, instructorIncludedAggregate] =
await queryAggregateDistribution(course);
const initialDistributions: Distributions = {
Aggregate: { data: aggregateDist, instructorIncluded: instructorIncludedAggregate },
};
const semesterPromises = semesters.map(semester => querySemesterDistribution(course, semester));
const semesterDistributions = await Promise.allSettled(semesterPromises);
semesters.forEach((semester, i) => {
Expand All @@ -81,7 +85,11 @@ export default function GradeDistribution({ course }: GradeDistributionProps): J
}

if (distributionResult.status === 'fulfilled') {
initialDistributions[`${semester.season} ${semester.year}`] = distributionResult.value;
const [distribution, instructorIncluded] = distributionResult.value;
initialDistributions[`${semester.season} ${semester.year}`] = {
data: distribution,
instructorIncluded,
};
}
});
setDistributions(initialDistributions);
Expand Down Expand Up @@ -236,6 +244,14 @@ export default function GradeDistribution({ course }: GradeDistributionProps): J
))}
</select>
</div>
{distributions[semester] && !distributions[semester]!.instructorIncluded && (
<div className='mt-3 flex flex-wrap content-center items-center self-stretch justify-center gap-3'>
<Text variant='mini' className='text-theme-red italic!'>
Instructor-specific data is not available for this course
{semester !== 'Aggregate' && ` for ${semester}`}, showing course-wide data instead
</Text>
Samathingamajig marked this conversation as resolved.
Show resolved Hide resolved
</div>
)}
<HighchartsReact ref={ref} highcharts={Highcharts} options={chartOptions} />
</>
)}
Expand Down
83 changes: 46 additions & 37 deletions src/views/lib/database/queryDistribution.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,27 +3,26 @@ import type { CourseSQLRow, Distribution } from '@shared/types/Distribution';

import { initializeDB } from './initializeDB';

// TODO: in the future let's maybe refactor this to be reactive to the items in the db rather than being explicit
const allTables = [
'grade_distributions_2019_2020',
'grade_distributions_2020_2021',
'grade_distributions_2021_2022',
'grade_distributions_2022_2023',
] as const;

/**
* fetches the aggregate distribution of grades for a given course from the course db, and the semesters that we have data for
* @param course the course to fetch the distribution for
* @returns a Distribution object containing the distribution of grades for the course, and
* an array of semesters that we have the distribution for
*/
export async function queryAggregateDistribution(course: Course): Promise<[Distribution, Semester[]]> {
export async function queryAggregateDistribution(course: Course): Promise<[Distribution, Semester[], boolean]> {
const db = await initializeDB();
const query = generateQuery(course, null);
const query = generateQuery(course, null, true);

const res = db.exec(query)?.[0];
let res = db.exec(query)?.[0];
let instructorIncluded = true;
if (!res?.columns?.length) {
throw new NoDataError(course);
instructorIncluded = false;
const queryWithoutInstructor = generateQuery(course, null, false);
res = db.exec(queryWithoutInstructor)?.[0];

if (!res?.columns?.length) {
throw new NoDataError(course);
}
}

const row: Required<CourseSQLRow> = {} as Required<CourseSQLRow>;
Expand Down Expand Up @@ -79,7 +78,7 @@ export async function queryAggregateDistribution(course: Course): Promise<[Distr
semesters.push({ year: parseInt(year, 10), season: season as Semester['season'] });
});

return [distribution, semesters];
return [distribution, semesters, instructorIncluded];
}

/**
Expand All @@ -88,18 +87,19 @@ export async function queryAggregateDistribution(course: Course): Promise<[Distr
* @param semester the semester to fetch the distribution for OR null if we want the aggregate distribution
* @returns a SQL query string
*/
function generateQuery(course: Course, semester: Semester | null): string {
// const profName = course.instructors[0]?.fullName;
// eslint-disable-next-line no-nested-ternary
const yearDelta = semester ? (semester.season === 'Fall' ? 0 : -1) : 0;
function generateQuery(course: Course, semester: Semester | null, includeInstructor: boolean): string {
const profName = course.instructors[0]?.lastName;

const query = `
select * from ${semester ? `grade_distributions_${semester.year + yearDelta}_${semester.year + yearDelta + 1}` : `(select * from ${allTables.join(' union all select * from ')})`}
select * from grade_distributions
where Department_Code = '${course.department}'
and Course_Number = '${course.number}'
${includeInstructor ? `and Instructor_Last = '${profName}' collate nocase` : ''}
${semester ? `and Semester = '${semester.season} ${semester.year}'` : ''}
`;

console.log(includeInstructor, { query });

return query;
}

Expand All @@ -109,13 +109,19 @@ function generateQuery(course: Course, semester: Semester | null): string {
* @param semester the semester to fetch the distribution for
* @returns a Distribution object containing the distribution of grades for the course
*/
export async function querySemesterDistribution(course: Course, semester: Semester): Promise<Distribution> {
export async function querySemesterDistribution(course: Course, semester: Semester): Promise<[Distribution, boolean]> {
const db = await initializeDB();
const query = generateQuery(course, semester);
const query = generateQuery(course, semester, true);

const res = db.exec(query)?.[0];
let res = db.exec(query)?.[0];
let instructorIncluded = true;
if (!res?.columns?.length) {
throw new NoDataError(course);
instructorIncluded = false;
const queryWithoutInstructor = generateQuery(course, semester, false);
res = db.exec(queryWithoutInstructor)?.[0];
if (!res?.columns?.length) {
throw new NoDataError(course);
}
}

const row: Required<CourseSQLRow> = {} as Required<CourseSQLRow>;
Expand All @@ -142,21 +148,24 @@ export async function querySemesterDistribution(course: Course, semester: Semest
}
}

return {
A: row.A,
'A-': row.A_Minus,
'B+': row.B_Plus,
B: row.B,
'B-': row.B_Minus,
'C+': row.C_Plus,
C: row.C,
'C-': row.C_Minus,
'D+': row.D_Plus,
D: row.D,
'D-': row.D_Minus,
F: row.F,
Other: row.Other,
};
return [
{
A: row.A,
'A-': row.A_Minus,
'B+': row.B_Plus,
B: row.B,
'B-': row.B_Minus,
'C+': row.C_Plus,
C: row.C,
'C-': row.C_Minus,
'D+': row.D_Plus,
D: row.D,
'D-': row.D_Minus,
F: row.F,
Other: row.Other,
},
instructorIncluded,
];
}

/**
Expand Down
Loading