Skip to content

Commit

Permalink
feat: Init grade distributions
Browse files Browse the repository at this point in the history
  • Loading branch information
mathhulk committed Nov 27, 2024
1 parent 1f2f584 commit dc41ac3
Show file tree
Hide file tree
Showing 17 changed files with 687 additions and 890 deletions.
4 changes: 4 additions & 0 deletions apps/backend/src/modules/grade-distribution/controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ enum Letter {

interface Grade {
letter: Letter;
percentage: number;
count: number;
}

Expand Down Expand Up @@ -96,10 +97,13 @@ export const getDistribution = (distributions: GradeDistributionType[]) => {
}
);

const total = Object.values(distribution).reduce((acc, count) => acc + count);

return Object.entries(distribution).map(
([field, count]) =>
({
letter: letters[field],
percentage: count / total,
count,
}) as Grade
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export default gql`
type Grade @cacheControl(maxAge: 1) {
letter: String!
percentage: Float!
count: Int!
}
Expand Down
16 changes: 15 additions & 1 deletion apps/backend/src/scripts/update-catalog.ts
Original file line number Diff line number Diff line change
Expand Up @@ -246,7 +246,9 @@ const updateTerms = async () => {
// Get all previous terms
let currentTerm: TermType | undefined = terms[0];

while (currentTerm) {
let i = 5;

while (i > 0) {
console.log(currentTerm.name);

[currentTerm] = await queryPage<TermType>(
Expand All @@ -262,6 +264,8 @@ const updateTerms = async () => {
);

if (currentTerm) terms.push(currentTerm);

i--;
}

console.log(`Received ${terms.length} terms from SIS API.`);
Expand All @@ -275,6 +279,16 @@ const updateTerms = async () => {
for (let i = 0; i < terms.length; i += batchSize) {
const batch = terms.slice(i, i + batchSize);

batch.forEach((doc, index) => {
const instance = new TermModel(doc);
const error = instance.validateSync();
if (error) {
console.error(`Validation error for document ${index}:`, error);
} else {
console.log(`Document ${index} is valid.`);
}
});

console.log(`Inserting batch ${i / batchSize + 1}...`);

await TermModel.insertMany(batch, { ordered: false });
Expand Down
3 changes: 2 additions & 1 deletion apps/frontend/schema.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ type Query {
givenName: String
familyName: String
): GradeDistribution!
catalog(year: Int!, semester: Semester!): [Class!]!
catalog(year: Int!, semester: Semester!, query: String): [Class!]!
ping: String! @deprecated(reason: "test")
schedules: [Schedule]
schedule(id: ID!): Schedule
Expand Down Expand Up @@ -101,6 +101,7 @@ type GradeDistribution {

type Grade {
letter: String!
percentage: Float!
count: Int!
}

Expand Down
4 changes: 2 additions & 2 deletions apps/frontend/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ const Course = {

const Catalog = lazy(() => import("@/app/Catalog"));
const Enrollment = lazy(() => import("@/app/Enrollment"));
const Grades = lazy(() => import("@/app/Grades"));
const GradeDistributions = lazy(() => import("@/app/GradeDistributions"));
const About = lazy(() => import("@/app/About"));
const Discover = lazy(() => import("@/app/Discover"));
const Plan = lazy(() => import("@/app/Plan"));
Expand Down Expand Up @@ -92,7 +92,7 @@ const router = createBrowserRouter([
element: <Layout footer={false} />,
children: [
{
element: <Grades />,
element: <GradeDistributions />,
path: "grades",
},
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,12 @@
display: flex;
flex-direction: column;
overflow: auto;
gap: 24px;

.grid {
display: grid;
gap: 24px;
grid-template-columns: repeat(auto-fit, minmax(384px, 1fr));
}
}
}
}
261 changes: 261 additions & 0 deletions apps/frontend/src/app/GradeDistributions/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,261 @@
import { useCallback, useEffect, useMemo, useState } from "react";

import { useApolloClient } from "@apollo/client";
import {
Bar,
BarChart,
CartesianGrid,
Legend,
Line,
LineChart,
ResponsiveContainer,
Tooltip,
XAxis,
YAxis,
} from "recharts";

import { Boundary, LoadingIndicator } from "@repo/theme";

import {
GradeDistribution,
READ_GRADE_DISTRIBUTION,
ReadGradeDistributionResponse,
Semester,
} from "@/lib/api";
import { colors } from "@/lib/section";

import styles from "./GradeDistributions.module.scss";

// const data = [
// {
// grade: "A",
// percentage: 20,
// average: 25,
// },
// {
// grade: "B",
// percentage: 15,
// average: 20,
// },
// {
// grade: "C",
// percentage: 10,
// average: 15,
// },
// {
// grade: "D",
// percentage: 5,
// average: 10,
// },
// {
// grade: "F",
// percentage: 2.5,
// average: 5,
// },
// {
// grade: "Pass",
// percentage: 35,
// average: 20,
// },
// {
// grade: "Not pass",
// percentage: 17.5,
// average: 5,
// },
// ];

interface Output {
color: string;
gradeDistribution: GradeDistribution;
input: {
subject: string;
courseNumber: string;
number?: string;
year?: number;
semester?: Semester;
givenName?: string;
familyName?: string;
};
}

const input = [
{
subject: "COMPSCI",
courseNumber: "61B",
},
{
subject: "COMPSCI",
courseNumber: "61B",
number: "001",
year: 2024,
semester: "Spring",
},
{
subject: "COMPSCI",
courseNumber: "61B",
year: 2024,
semester: "Spring",
},
{
subject: "COMPSCI",
courseNumber: "61A",
year: 2024,
semester: "Spring",
givenName: "John",
familyName: "DeNero",
},
// {
// subject: "COMPSCI",
// courseNumber: "61A",
// givenName: "Joshua",
// familyName: "Hug",
// },
];

export default function GradeDistributions() {
const client = useApolloClient();
const [loading, setLoading] = useState(false);

const [output, setOutput] = useState<Output[] | null>(null);

const initialize = useCallback(async () => {
setLoading(true);

const responses = await Promise.all(
input.map((variables) =>
client.query<ReadGradeDistributionResponse>({
query: READ_GRADE_DISTRIBUTION,
variables,
})
)
);

const output = responses.map(
(response, index) =>
({
color: colors[Math.floor(Math.random() * colors.length)],
gradeDistribution: response.data.grade,
input: input[index],
}) as Output
);

setOutput(output);

setLoading(false);
}, []);

useEffect(() => {
initialize();
}, [initialize]);

const data = useMemo(
() =>
output?.reduce(
(acc, output, index) => {
output.gradeDistribution.distribution.forEach((grade) => {
const column = acc.find((item) => item.letter === grade.letter);
const percent = Math.round(grade.percentage * 100);

if (!column) {
acc.push({ letter: grade.letter, [index]: percent });

return;
}

column[index] = percent;
});

return acc;
},
[] as {
letter: string;
[key: number]: number;
}[]
),
[output]
);

console.log(data);

return (
<div className={styles.root}>
<div className={styles.panel}></div>
{loading ? (
<Boundary>
<LoadingIndicator size="lg" />
</Boundary>
) : (
<div className={styles.view}>
<ResponsiveContainer width="100%" height={256}>
<BarChart width={730} height={250} data={data}>
<CartesianGrid
strokeDasharray="3 3"
vertical={false}
stroke="var(--border-color)"
/>
<XAxis
dataKey="letter"
fill="var(--label-color)"
tickMargin={8}
/>
<YAxis />
<Legend />
<Tooltip />
{output?.map((_, index) => (
<Bar dataKey={index} fill={output[index].color} key={index} />
))}
</BarChart>
</ResponsiveContainer>
<ResponsiveContainer width="100%" height={256}>
<LineChart width={730} height={250} data={data}>
<CartesianGrid
strokeDasharray="3 3"
vertical={false}
stroke="var(--border-color)"
/>
<XAxis
dataKey="letter"
fill="var(--label-color)"
tickMargin={8}
/>
<YAxis />
<Legend />
<Tooltip />
{output?.map((_, index) => (
<Line
dataKey={index}
stroke={output[index].color}
key={index}
type="natural"
dot={false}
/>
))}
</LineChart>
</ResponsiveContainer>
<div className={styles.grid}>
{output?.map((_, index) => (
<ResponsiveContainer width="100%" height={256} key={index}>
<BarChart width={730} height={250} data={data}>
<CartesianGrid
strokeDasharray="3 3"
vertical={false}
stroke="var(--border-color)"
/>
<XAxis
dataKey="letter"
fill="var(--label-color)"
tickMargin={8}
/>
<YAxis />
<Legend />
<Tooltip cursor={{ fill: "var(--backdrop-color)" }} />
<Bar dataKey={index} fill={output[index].color} />
</BarChart>
</ResponsiveContainer>
))}
</div>
</div>
)}
</div>
);
}
Loading

0 comments on commit dc41ac3

Please sign in to comment.