Skip to content

Commit

Permalink
Merge pull request #714 from UTDNebula/major-names
Browse files Browse the repository at this point in the history
Dynamically determine the starting year for degree validation
  • Loading branch information
cubetastic33 authored Oct 17, 2023
2 parents 7904cac + 99903bb commit 31d0bdb
Show file tree
Hide file tree
Showing 3 changed files with 120 additions and 107 deletions.
29 changes: 19 additions & 10 deletions src/components/planner/Sidebar/Sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import { Course, DraggableCourse, GetDragIdByCourse } from '../types';
import useFuse from '../useFuse';

import 'react-loading-skeleton/dist/skeleton.css';
import { useSemestersContext } from '@components/planner/SemesterContext';

export interface CourseSelectorContainerProps {
planId: string;
Expand All @@ -42,18 +43,26 @@ function CourseSelectorContainer({
dragActive,
courseDragged,
}: CourseSelectorContainerProps) {
const { data: validationData, error } = trpc.validator.degreeValidator.useQuery(planId, {
// TODO: Fix validator retries.
// Validator kept retrying when the "REPORT ERROR" button was clicked on. At some point it should stop retrying.
retry: false,
trpc: {
context: {
// Isolate the validation request from others. Validator is an external service so its reliability
// should not affect other requets.
skipBatch: true,
const { allSemesters: semesters } = useSemestersContext();
const { data: validationData, error } = trpc.validator.degreeValidator.useQuery(
{
planId,
// If they started during a Spring semester, it should use the previous numeric year's degree plan
startYear: semesters[0].code.year - (semesters[0].code.semester === 's' ? 1 : 0),
},
{
// TODO: Fix validator retries.
// Validator kept retrying when the "REPORT ERROR" button was clicked on. At some point it should stop retrying.
retry: false,
trpc: {
context: {
// Isolate the validation request from others. Validator is an external service so its reliability
// should not affect other requets.
skipBatch: true,
},
},
},
});
);

const validatorError = useMemo(
() => error?.data?.code === 'INTERNAL_SERVER_ERROR',
Expand Down
194 changes: 98 additions & 96 deletions src/server/trpc/router/validator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -310,115 +310,117 @@ export const validatorRouter = router({
}
}),
// Protected route: ensures session user is same as plan owner
degreeValidator: protectedProcedure.input(z.string().min(1)).query(async ({ ctx, input }) => {
// Fetch current plan
const planData = await ctx.prisma.plan.findUnique({
where: {
id: input,
},
select: {
name: true,
id: true,
userId: true,
semesters: {
include: {
courses: true,
degreeValidator: protectedProcedure
.input(z.object({ planId: z.string().min(1), startYear: z.number() }))
.query(async ({ ctx, input: { planId, startYear } }) => {
// Fetch current plan
const planData = await ctx.prisma.plan.findUnique({
where: {
id: planId,
},
select: {
name: true,
id: true,
userId: true,
semesters: {
include: {
courses: true,
},
},
transferCredits: true,
},
transferCredits: true,
},
});

if (!planData) {
throw new TRPCError({
code: 'NOT_FOUND',
message: 'Plan not found',
});
}

if (ctx.session.user.id !== planData.userId) {
throw new TRPCError({ code: 'FORBIDDEN' });
}
if (!planData) {
throw new TRPCError({
code: 'NOT_FOUND',
message: 'Plan not found',
});
}

const { semesters, transferCredits } = planData;

// Get degree requirements
const degreeRequirements = await ctx.prisma.degreeRequirements.findFirst({
where: {
plan: { id: planData.id },
},
});

// Get bypasses
const bypasses = degreeRequirements?.bypasses ?? [];

// Remove invalidCourses
const removeInvalidCoursesFromSemesters = () => {
return semesters.map((sem) => {
const courses = sem.courses
.reduce((acc, curr) => [...acc, curr.code], [] as string[])
.filter((c) => {
const [possiblePrefix, possibleCode] = c.split(' ');
if (Number.isNaN(Number(possibleCode)) || !Number.isNaN(Number(possiblePrefix))) {
return false;
}
return true;
});
return { ...sem, courses };
});
};
if (ctx.session.user.id !== planData.userId) {
throw new TRPCError({ code: 'FORBIDDEN' });
}

const semestersWithCourses = removeInvalidCoursesFromSemesters();
const { semesters, transferCredits } = planData;

if (!degreeRequirements?.major || degreeRequirements.major === 'undecided') {
return { plan: planData, validation: [], bypasses: [] };
}
// Get degree requirements
const degreeRequirements = await ctx.prisma.degreeRequirements.findFirst({
where: {
plan: { id: planData.id },
},
});

// TODO: will we always ignore odd credits such as 'PSY 1---'?
const regex = /([a-z0-9])* ([a-z0-9]){4}$/gi;
const validTransferCredits = transferCredits.filter((credit) => credit.match(regex) !== null);

const body = {
courses: [...semestersWithCourses.flatMap((s) => s.courses), ...validTransferCredits],
requirements: {
year: 2022,
majors: [degreeRequirements.major],
minors: [],
},
bypasses,
};

const res = await fetch(`${env.NEXT_PUBLIC_VALIDATOR}/validate`, {
method: 'POST',
body: JSON.stringify(body),
headers: {
'content-type': 'application/json',
},
});

if (!res.ok) {
const errorMsg = await res.json();
if (res.status == 404) {
throw new TRPCError({
code: 'NOT_FOUND',
message: errorMsg.error,
cause: new DegreeNotFound(errorMsg),
// Get bypasses
const bypasses = degreeRequirements?.bypasses ?? [];

// Remove invalidCourses
const removeInvalidCoursesFromSemesters = () => {
return semesters.map((sem) => {
const courses = sem.courses
.reduce((acc, curr) => [...acc, curr.code], [] as string[])
.filter((c) => {
const [possiblePrefix, possibleCode] = c.split(' ');
if (Number.isNaN(Number(possibleCode)) || !Number.isNaN(Number(possiblePrefix))) {
return false;
}
return true;
});
return { ...sem, courses };
});
};

const semestersWithCourses = removeInvalidCoursesFromSemesters();

if (!degreeRequirements?.major || degreeRequirements.major === 'undecided') {
return { plan: planData, validation: [], bypasses: [] };
}

const error = new DegreeValidationError(
`Validator fetch failed with stats: ${res.status}, err: ${errorMsg}`,
);
throw new TRPCError({
code: 'INTERNAL_SERVER_ERROR',
message: error.message,
cause: error,
// TODO: will we always ignore odd credits such as 'PSY 1---'?
const regex = /([a-z0-9])* ([a-z0-9]){4}$/gi;
const validTransferCredits = transferCredits.filter((credit) => credit.match(regex) !== null);

const body = {
courses: [...semestersWithCourses.flatMap((s) => s.courses), ...validTransferCredits],
requirements: {
year: startYear,
majors: [degreeRequirements.major],
minors: [],
},
bypasses,
};

const res = await fetch(`${env.NEXT_PUBLIC_VALIDATOR}/validate`, {
method: 'POST',
body: JSON.stringify(body),
headers: {
'content-type': 'application/json',
},
});
}

const validationData = await res.json();
return { plan: planData, validation: validationData, bypasses };
}),
if (!res.ok) {
const errorMsg = await res.json();
if (res.status == 404) {
throw new TRPCError({
code: 'NOT_FOUND',
message: errorMsg.error,
cause: new DegreeNotFound(errorMsg),
});
}

const error = new DegreeValidationError(
`Validator fetch failed with stats: ${res.status}, err: ${errorMsg}`,
);
throw new TRPCError({
code: 'INTERNAL_SERVER_ERROR',
message: error.message,
cause: error,
});
}

const validationData = await res.json();
return { plan: planData, validation: validationData, bypasses };
}),
});

type CourseOptions = {
Expand Down
4 changes: 3 additions & 1 deletion validator/degree_solver.py
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,8 @@ def load_requirements(
# Logic for adding majors
for input_req in degree_requirements_input.majors:
# Get major data from json
year = degree_requirements_input.year
# We don't support degrees earlier than 2022
year = max([degree_requirements_input.year, 2022])
if input_req not in degree_plans[str(year)]:
# Check if the years before this one have it
y = year
Expand All @@ -209,6 +210,7 @@ def load_requirements(
year != degree_requirements_input.year
): # The using_year has been replaced to a working year
break
print("Error: Could not find the degree")
raise DegreeNotFoundException
requirements_data = degree_plans[str(year)][input_req]["requirements"][
"major"
Expand Down

0 comments on commit 31d0bdb

Please sign in to comment.