Skip to content

Commit

Permalink
feat: add course availability by session (#49)
Browse files Browse the repository at this point in the history
* Refactor CoursePrerequisite model and migrate unstructuredPrerequisite column to ProgramCourse

* Make credits column nullable in Program model

* Refactor logging missing programs and courses

* Add pdf parsable flags to Program model & create a seeder for all parsable programs

* create worker to get parsed horaire pdf data

* quick cleanup & add endpoint

* refactor variable name (CoursePrerequisite to Prerequisite)

* wip adding prerequisites

* add unstructured prerequisites

* add logging rules for worker thread

* cleanup

* add metrics & enforce logging rules

* move prereq logic to service

* add unit tests for session utils

* doc jobs process

* refactor prerequisite

* refactor prerequisite

* run seeder on prerequisite job

* change session and courseInstance model to use combined p_key

* add availability column to CourseInstance model

* wip availability and update programs seeding logic to run on jobs

* wip wip 🚔

* change availability field to an array in CourseInstance model and update related logic

* refactor course instance to delete/update old availabilities

* enhance error handling & add course prerequisite endpoint

* fix

* nullish
  • Loading branch information
mhd-hi authored Jan 21, 2025
1 parent 9c12f46 commit f41c103
Show file tree
Hide file tree
Showing 25 changed files with 797 additions and 80 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/*
Warnings:
- The primary key for the `CourseInstance` table will be changed. If it partially fails, the table could be left without primary key constraint.
- You are about to drop the column `id` on the `CourseInstance` table. All the data in the column will be lost.
- You are about to drop the column `sessionId` on the `CourseInstance` table. All the data in the column will be lost.
- The primary key for the `Session` table will be changed. If it partially fails, the table could be left without primary key constraint.
- You are about to drop the column `id` on the `Session` table. All the data in the column will be lost.
- Added the required column `sessionTrimester` to the `CourseInstance` table without a default value. This is not possible if the table is not empty.
- Added the required column `sessionYear` to the `CourseInstance` table without a default value. This is not possible if the table is not empty.
*/
-- DropForeignKey
ALTER TABLE "CourseInstance" DROP CONSTRAINT "CourseInstance_sessionId_fkey";

-- DropIndex
DROP INDEX "CourseInstance_courseId_sessionId_idx";

-- DropIndex
DROP INDEX "CourseInstance_courseId_sessionId_key";

-- DropIndex
DROP INDEX "Session_year_trimester_key";

-- AlterTable
ALTER TABLE "CourseInstance" DROP CONSTRAINT "CourseInstance_pkey",
DROP COLUMN "id",
DROP COLUMN "sessionId",
ADD COLUMN "sessionTrimester" "Trimester" NOT NULL,
ADD COLUMN "sessionYear" INTEGER NOT NULL,
ADD CONSTRAINT "CourseInstance_pkey" PRIMARY KEY ("courseId", "sessionYear", "sessionTrimester");

-- AlterTable
ALTER TABLE "Session" DROP CONSTRAINT "Session_pkey",
DROP COLUMN "id",
ADD CONSTRAINT "Session_pkey" PRIMARY KEY ("year", "trimester");

-- CreateIndex
CREATE INDEX "CourseInstance_courseId_sessionYear_sessionTrimester_idx" ON "CourseInstance"("courseId", "sessionYear", "sessionTrimester");

-- AddForeignKey
ALTER TABLE "CourseInstance" ADD CONSTRAINT "CourseInstance_sessionYear_sessionTrimester_fkey" FOREIGN KEY ("sessionYear", "sessionTrimester") REFERENCES "Session"("year", "trimester") ON DELETE RESTRICT ON UPDATE CASCADE;
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
/*
Warnings:
- Added the required column `availability` to the `CourseInstance` table without a default value. This is not possible if the table is not empty.
*/
-- CreateEnum
CREATE TYPE "Availability" AS ENUM ('JOUR', 'SOIR', 'INTENSIF');

-- AlterTable
ALTER TABLE "CourseInstance" ADD COLUMN "availability" "Availability" NOT NULL;
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
/*
Warnings:
- Changed the column `availability` on the `CourseInstance` table from a scalar field to a list field. If there are non-null values in that column, this step will fail.
*/
-- Drop the existing column
ALTER TABLE "CourseInstance" DROP COLUMN "availability";

-- Add the new column as an array of enums
ALTER TABLE "CourseInstance" ADD COLUMN "availability" "Availability"[] NOT NULL DEFAULT '{}';
22 changes: 14 additions & 8 deletions prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -17,30 +17,36 @@ enum Trimester {
}

model Session {
id String @id @default(uuid())
trimester Trimester
year Int
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
courseInstances CourseInstance[]
@@unique([year, trimester])
@@id([year, trimester])
}

enum Availability {
JOUR
SOIR
INTENSIF
}

model CourseInstance {
id String @id @default(uuid())
courseId Int
sessionId String
courseId Int
sessionYear Int
sessionTrimester Trimester
availability Availability[] @default([])
course Course @relation(fields: [courseId], references: [id])
session Session @relation(fields: [sessionId], references: [id])
session Session @relation(fields: [sessionYear, sessionTrimester], references: [year, trimester])
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@unique([courseId, sessionId])
@@index([courseId, sessionId])
@@id([courseId, sessionYear, sessionTrimester])
@@index([courseId, sessionYear, sessionTrimester])
}

model Course {
Expand Down
23 changes: 23 additions & 0 deletions prisma/seeds/data/programs-to-seed.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,28 @@
"4412",
"4288",
"4329"
],
"planificationPdfPrograms": [
"7625",
"7694",
"7084",
"6646",
"7684",
"6556",
"6557",
"7086",
"4567",
"4605",
"4563",
"4684",
"4412",
"4329",
"4288",
"0495",
"0987",
"0497",
"0488",
"0496",
"0486"
]
}
8 changes: 6 additions & 2 deletions prisma/seeds/seed.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
import { Logger } from '@nestjs/common';

import { PrismaService } from '../../src/prisma/prisma.service';
import { seedProgramPdfParserFlags } from '../../src/prisma/programs.seeder';
import {
seedProgramHorairePdfParserFlags,
seedProgramPlanificationPdfParserFlags,
} from '../../src/prisma/programs.seeder';

const prismaService = new PrismaService();

async function main() {
await seedProgramPdfParserFlags();
await seedProgramHorairePdfParserFlags();
await seedProgramPlanificationPdfParserFlags();
}

main()
Expand Down
60 changes: 60 additions & 0 deletions src/common/utils/course-instance/courseInstanceUtil.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { Availability } from '@prisma/client';

export class AvailabilityUtil {
public static parseAvailability(
availabilityCode: string,
): Availability[] | null {
const availabilityMap: Record<string, Availability> = {
J: Availability.JOUR,
S: Availability.SOIR,
I: Availability.INTENSIF,
};

const availabilities: Availability[] = [];

for (const char of availabilityCode.toUpperCase()) {
const availability = availabilityMap[char];
if (availability) {
// Prevent duplicates if the same code appears multiple times
if (!availabilities.includes(availability)) {
availabilities.push(availability);
}
} else {
// Invalid availability code detected
return null;
}
}

return availabilities;
}

// Compares two arrays of Availability enums for equality, ignoring order.
public static areAvailabilitiesEqual(
a: Availability[],
b: Availability[],
): boolean {
if (a.length !== b.length) return false;

const frequencyMapA = AvailabilityUtil.buildFrequencyMap(a);
const frequencyMapB = AvailabilityUtil.buildFrequencyMap(b);

for (const [availability, count] of frequencyMapA.entries()) {
if (frequencyMapB.get(availability) !== count) {
return false;
}
}

return true;
}

// Builds a frequency map of the Availability enums
private static buildFrequencyMap(
availabilities: Availability[],
): Map<Availability, number> {
const frequencyMap = new Map<Availability, number>();
for (const availability of availabilities) {
frequencyMap.set(availability, (frequencyMap.get(availability) ?? 0) + 1);
}
return frequencyMap;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { Injectable } from '@nestjs/common';
import { Fill, Output, Page, Text } from 'pdf2json';
import { firstValueFrom } from 'rxjs';

import { getPlanificationPdfUrl } from '../../../../constants/url';
import { CourseCodeValidationPipe } from '../../../../pipes/models/course/course-code-validation-pipe';
import { PdfParserUtil } from '../../../../utils/pdf/parser/pdfParserUtil';
import { TextExtractor } from '../../../../utils/pdf/parser/textExtractorUtil';
Expand All @@ -17,6 +18,19 @@ export class PlanificationCoursService {

constructor(private readonly httpService: HttpService) {}

public async parseProgramPlanification(
programCode: string,
): Promise<ICoursePlanification[]> {
try {
const pdfUrl = getPlanificationPdfUrl(programCode);
return await this.parsePdfFromUrl(pdfUrl);
} catch (error) {
throw new Error(
`Error parsing Planification-PDF for program: ${programCode}\n` + error,
);
}
}

public async parsePdfFromUrl(
pdfUrl: string,
): Promise<ICoursePlanification[]> {
Expand Down
2 changes: 1 addition & 1 deletion src/course-instance/course-instance.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ export class CourseInstanceController {
constructor(private readonly courseInstanceService: CourseInstanceService) {}

@Get(':id')
public getCourseInstance(
public getCourseInstanceById(
@Param('') { id }: UuidDto,
): Promise<CourseInstance | null> {
return this.courseInstanceService.getCourseInstance({ id });
Expand Down
1 change: 1 addition & 0 deletions src/course-instance/course-instance.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,6 @@ import { CourseInstanceService } from './course-instance.service';
imports: [PrismaModule],
controllers: [CourseInstanceController],
providers: [CourseInstanceService],
exports: [CourseInstanceService],
})
export class CourseInstanceModule {}
Loading

0 comments on commit f41c103

Please sign in to comment.