diff --git a/.vscode/snips.code-snippets b/.vscode/snips.code-snippets
index 995d03189..75330d45b 100644
--- a/.vscode/snips.code-snippets
+++ b/.vscode/snips.code-snippets
@@ -10,18 +10,18 @@
"scope": "javascript,typescript",
"prefix": "depr",
"body": ["/**", "* @deprecated $0", "*/"],
- "description": "Log output to console",
+ "description": "Mark a feature as deprecated",
},
"Page": {
- "prefix": "pasy",
- "body": [
- "export default async function Page() {",
- " return (<>${0}>);",
- "}"
- ],
- "description": "Creates a page server component with no params",
- },
+ "prefix": "pasy",
+ "body": [
+ "export default async function Page() {",
+ " return (<>${0}>);",
+ "}",
+ ],
+ "description": "Creates a page server component with no params",
+ },
"Instance Page": {
"prefix": "inpag",
@@ -67,4 +67,15 @@
"export default ${0};",
],
},
+
+ "Enum": {
+ "prefix": "enm",
+ "body": [
+ "export const $1 = {",
+ "\t$2",
+ "} as const;",
+ "",
+ "export type $1 = keyof typeof $1;",
+ ],
+ },
}
diff --git a/docs/architecture.md b/docs/architecture.md
new file mode 100644
index 000000000..7239a0a67
--- /dev/null
+++ b/docs/architecture.md
@@ -0,0 +1,102 @@
+## Architecture
+
+The SPA system is comprised of two main repositories:
+
+The first is the matching service
+
+This is a FastAPI (python) app which simply serves out a series of pre-defined algorithms.
+All of the difficult logic being handled by libraries like networkx or matchingproblems.
+As the name implies, it is used to handle matching problems, and only needs to be updated if there are new matching algorithms to add.
+
+---
+
+The second repository (this one) is a nextJS application which handles all the CRUD
+
+We broadly follow the T3 stack and architecture;
+more details can be found on [the t3 website](https://create.t3.gg/en/folder-structure-app?packages=prisma%2Ctailwind%2Ctrpc).
+This link has the correct packages already selected.
+
+The main components within this are:
+
+- the database schema,
+
+ - We use [prisma](https://www.prisma.io/) as our ORM
+ - contained in `/prisma`
+
+- the backend,
+
+ - We use [tRPC](https://trpc.io/)
+ - contained in `/src/router...`
+ - Most of the logic in the procedures in reality lives in data objects ([see below](#data-objects))
+
+- the frontend,
+ - A set of [React](https://react.dev/) components, one for each page (see [NextJS](https://nextjs.org/))
+ - contained in `/src/app`
+ - This is the display layer; in general, very little business logic lives here.
+ Anything here can run on the client, so critical information needs to be controlled and restricted so
+
+Some other critical dependencies:
+
+- [Tailwind](https://tailwindcss.com/) for styling
+- [Zod](https://tailwindcss.com/) for runtime data validation
+- [ShadCN UI](https://ui.shadcn.com/) for many common UI tasks
+
+Still, there are a few quirks unique to this application that we hand-rolled, which I will discuss in some more detail:
+
+### Data objects
+
+Previously, database calls were all done in the top level of procedures.
+There were several issues with this - a big one being that it was difficult to re-use code.
+
+This has been refactored, and we now use a system of data objects. These can be found in `/src/data-objects/`.
+
+The idea is to organise functions by the objects they refer to; business logic related to a kind of object is encapsulated in the corresponding data object.
+
+It's driven lots of very nice patterns; for instance, the authentication middleware we use is only possible because of this design.
+
+In reality, the data objects are sometimes a bit messy; significant parts are not very well organised, and need cleaning up.
+Maybe one day we will get around to it, but it hasn't been a priority.
+We aim to remove all database calls from the tRPC procedures, and instead concentrate them in the data objects.
+
+Generally, you will not need to import these directly; the relevant objects will be injected into the tRPC by the middleware.
+
+### tRPC middlewares
+
+We use a custom set of middlewares for tRPC.
+These need to change very rarely.
+
+The main idea is to abstract away a lot of the tedious boilerplate that is present in many procedures.
+
+If you wanted, for example, to write a procedure which updates an allocation instance, then you would need to
+accept the parameters which specify the instance (it's ID, essentially) and then query the database based on these.
+
+Any procedure pertaining to an instance has to do this, and so we can save a lot of work by abstracting it away. This is what a middleware does. It adds the necessary input parameters and injects objects into the tRPC context.
+
+We also have authentication middlewares - which automatically check that the user has the correct authorisation and inject a corresponding user object. If authentication fails, the procedure will error. Generally, you should try to make sure that your procedures are properly scoped wrt authorisation - it's critical to avoid accidental data leaks.
+
+### Data transfer objects
+
+There are many places in the application where _almost_ the same data is required, though it may differ slightly.
+Creating types for each individual use case proved very messy; there were lots of interfaces floating around that were used only once,
+and it made knowing what data was on each hard to track.
+Now, instead, for each type of object there is a single type that contains all the data for that object type,
+and we always pass around the full objects.
+
+These large canonical types are called Data Transfer Objects (DTOs).
+
+If you are presenting data relating to e.g. a student, you can use the `StudentDTO` type,
+rather than having to carefully hand-craft the type for what you need.
+Whilst this is a little less efficient - you may end up passing around data you don't strictly need - the benefits to code style and legibility are substantial.
+It makes moving around different parts of the application much easier, since the types are always the same.
+
+---
+
+## Deployment
+
+The matching service and the allocation app are each packaged into a docker image.
+We use docker compose to orchestrate them, along with a few chron jobs for tasks that need to run regularly.
+At some point in the future, we might move this into it's own container.
+
+Some extra details need to be provided via environment variables.
+Details of which ones are needed and what they should be can be found in `\src\env.ts`. Any variable with `.optional()` or `.default(_)` can be omitted.
+`.env.example` contains an example env file.
diff --git a/docs/dev/design.md b/docs/dev/design.md
deleted file mode 100644
index 1f991f536..000000000
--- a/docs/dev/design.md
+++ /dev/null
@@ -1,6 +0,0 @@
-- backups
- -> solution in sight, but some work needed.
-
- - automate with cron job
- - store on uni git server
- - version control it
diff --git a/docs/dev/style-guide.md b/docs/dev/style-guide.md
index f3df458ed..fdae1c2f8 100644
--- a/docs/dev/style-guide.md
+++ b/docs/dev/style-guide.md
@@ -92,3 +92,53 @@ function QueryManager({ initialData }: { initialData: TData }) {
return
{data}
;
}
```
+
+## Enums
+
+When declaring an enum type, don't use magic strings or
+
+Instead, use the following pattern:
+
+```ts
+export const MyEnum = {
+ OPTION_A: "OPTION_A",
+ OPTION_B: "OPTION_B",
+ OPTION_C: "OPTION_C",
+ // ... etc
+} as const;
+
+export type MyEnum = keyof typeof MyEnum;
+```
+
+- The dictionary and type names should both be in PascalCase and must match
+- Enum values should be in `SCREAMING_SNAKE_CASE`
+- The key and the value in the defining dict should match exactly.
+- Export the inferred type on an additional line
+
+There's a snippet in the repository for setting this up - the prefix is `enm`.
+
+When referring to an enum value, use the dictionary
+
+> Good:
+>
+> ```ts
+> fun(MyEnum.OPTION_A);
+> ```
+
+> Bad:
+>
+> ```ts
+> fun("OPTION_A");
+> ```
+
+When necessary, you should generate the related zod schema like so:
+
+```ts
+export const myEnumSchema = z.enum([
+ MyEnum.OPTION_A,
+ MyEnum.OPTION_B,
+ MyEnum.OPTION_C,
+]);
+```
+
+- The name of the schema should be the name of the enum in camelCase with `Schema` appended to the end.
diff --git a/prisma/schema/marking.prisma b/prisma/schema/marking.prisma
index b89708695..026e43ef9 100644
--- a/prisma/schema/marking.prisma
+++ b/prisma/schema/marking.prisma
@@ -3,91 +3,120 @@ enum MarkerType {
READER
}
-model UnitOfAssessment {
- id String @id @default(uuid())
- flagId String @map("flag_id")
- title String
- open Boolean @default(false)
- studentSubmissionDeadline DateTime @map("student_submission_deadline")
- markerSubmissionDeadline DateTime @map("marker_submission_deadline")
- weight Int
- allowedMarkerTypes MarkerType[]
- // ---
- allocationGroupId String @map("allocation_group_id")
- allocationSubGroupId String @map("allocation_sub_group_id")
- allocationInstanceId String @map("allocation_instance_id")
- // ---
- flag Flag @relation(fields: [flagId, allocationGroupId, allocationSubGroupId, allocationInstanceId], references: [id, allocationGroupId, allocationSubGroupId, allocationInstanceId], onDelete: Cascade, map: "unit_flag")
- allocationInstance AllocationInstance @relation(fields: [allocationGroupId, allocationSubGroupId, allocationInstanceId], references: [allocationGroupId, allocationSubGroupId, id], onDelete: Cascade, map: "unit_instance")
- assessmentCriteria AssessmentCriterion[]
- markerSubmissions MarkingSubmission[]
- finalUnitOfAssessmentGrades FinalUnitOfAssessmentGrade[]
+enum MarkingStatus {
+ PENDING
+ DONE
+ MODERATE
+ NEGOTIATE
+}
- @@map("unit_of_assessment")
+enum MarkingMethod {
+ AUTO
+ OVERRIDE
+ NEGOTIATED
+ MODERATED
}
-model AssessmentCriterion {
- id String @id @default(uuid())
- unitOfAssessmentId String @map("unit_of_assessment_id")
+enum FinalGradeMethod {
+ AUTO
+ OVERRIDE
+}
+
+model MarkingComponent {
+ id String @id @default(uuid())
+ unitOfAssessmentId String @map("unit_of_assessment_id")
title String
description String
weight Int
- layoutIndex Int @map("layout_index")
+ layoutIndex Int @map("layout_index")
// ---
- unitOfAssessment UnitOfAssessment @relation(fields: [unitOfAssessmentId], references: [id], onDelete: Cascade, map: "criterion_unit")
- scores CriterionScore[]
+ unitOfAssessment UnitOfAssessment @relation(fields: [unitOfAssessmentId], references: [id], onDelete: Cascade, map: "marking_component_uoa")
+ scores MarkingComponentSubmission[]
@@unique([title, unitOfAssessmentId])
- @@map("assessment_criterion")
+ @@map("marking_component")
}
-model MarkingSubmission {
- summary String
- grade Int
- recommendedForPrize Boolean @default(false) @map("recommended_for_prize")
- draft Boolean
- markerId String @map("marker_id")
- studentId String @map("student_id")
- unitOfAssessmentId String @map("unit_of_assessment_id")
+model MarkingComponentSubmission {
+ markerId String @map("marker_id") // marker type?
+ studentId String @map("student_id")
+ unitOfAssessmentId String @map("unit_of_assessment_id")
+ grade Int
+ justification String
// ---
- unitOfAssessment UnitOfAssessment @relation(fields: [unitOfAssessmentId], references: [id], onDelete: Cascade, map: "submission_unit")
- criterionScores CriterionScore[]
+ markingComponentId String @map("marking_component_id")
+ // ---
+ markingComponent MarkingComponent @relation(fields: [markingComponentId], references: [id], onDelete: Cascade, map: "marking_component_submission_marking_component")
+ submission UnitOfAssessmentSubmission @relation(fields: [markerId, studentId, unitOfAssessmentId], references: [markerId, studentId, unitOfAssessmentId], onDelete: Cascade, map: "marking_component_submission_uoa_submission")
- @@id([markerId, studentId, unitOfAssessmentId], name: "studentMarkerSubmission")
- @@map("assessment_marking_submission")
+ @@id([markerId, studentId, markingComponentId], name: "markingComponentSubmission")
+ @@map("marking_component_submission")
}
-model CriterionScore {
- markerId String @map("marker_id")
- studentId String @map("student_id")
- unitOfAssessmentId String @map("unit_of_assessment_id")
- grade Int
- justification String
+model UnitOfAssessment {
+ id String @id @default(uuid())
+ flagId String @map("flag_id")
+ title String
+ open Boolean @default(false)
+ defaultStudentSubmissionDeadline DateTime @map("default_student_submission_deadline")
+ // Days to mark
+ markerSubmissionDeadline DateTime @map("marker_submission_deadline")
+ defaultWeight Int @map("default_weight")
+ allowedMarkerTypes MarkerType[]
// ---
- assessmentCriterionId String @map("assessment_component_id")
+ allocationGroupId String @map("allocation_group_id")
+ allocationSubGroupId String @map("allocation_sub_group_id")
+ allocationInstanceId String @map("allocation_instance_id")
// ---
- criterion AssessmentCriterion @relation(fields: [assessmentCriterionId], references: [id], onDelete: Cascade, map: "score_criterion")
- submission MarkingSubmission @relation(fields: [markerId, studentId, unitOfAssessmentId], references: [markerId, studentId, unitOfAssessmentId], onDelete: Cascade, map: "score_submission")
+ flag Flag @relation(fields: [flagId, allocationGroupId, allocationSubGroupId, allocationInstanceId], references: [id, allocationGroupId, allocationSubGroupId, allocationInstanceId], onDelete: Cascade, map: "unit_flag")
+ allocationInstance AllocationInstance @relation(fields: [allocationGroupId, allocationSubGroupId, allocationInstanceId], references: [allocationGroupId, allocationSubGroupId, id], onDelete: Cascade, map: "uoa_instance")
+ markingComponents MarkingComponent[]
+ markerSubmissions UnitOfAssessmentSubmission[]
+ grades UnitOfAssessmentGrade[]
- @@id([markerId, studentId, assessmentCriterionId], name: "markingCriterionSubmission")
- @@map("criterion_score")
+ @@map("unit_of_assessment")
}
-model FinalUnitOfAssessmentGrade {
+model UnitOfAssessmentSubmission {
+ summary String
+ grade Int
+ recommendedForPrize Boolean @default(false) @map("recommended_for_prize")
+ draft Boolean
+ markerId String @map("marker_id")
+ studentId String @map("student_id")
+ unitOfAssessmentId String @map("unit_of_assessment_id")
+ // ---
+ unitOfAssessment UnitOfAssessment @relation(fields: [unitOfAssessmentId], references: [id], onDelete: Cascade, map: "uoa_submission_uoa")
+ criterionScores MarkingComponentSubmission[]
+
+ @@id([markerId, studentId, unitOfAssessmentId], name: "uoaSubmissionId")
+ @@map("unit_of_assessment_submission")
+}
+
+model UnitOfAssessmentGrade {
unitOfAssessmentId String @map("unit_of_assessment_id")
unitOfAssessment UnitOfAssessment @relation(fields: [unitOfAssessmentId], references: [id], onDelete: Cascade, map: "final_unit_grade_unit")
studentId String @map("student_id")
+ // ---
grade Int
comment String
+ status MarkingStatus @default(PENDING)
+ method MarkingMethod @default(AUTO)
+ //
+ submitted Boolean @default(false)
+ customDueDate DateTime? @map("custom_due_date")
+ customWeight Int? @map("custom_weight")
- @@id([studentId, unitOfAssessmentId], name: "studentAssessmentGrade")
- @@map("final_unit_of_assessment_grade")
+ @@id([studentId, unitOfAssessmentId], name: "uoaGradeId")
+ @@map("unit_of_assessment_grade")
}
model FinalGrade {
id String @id @default(uuid())
studentId String @map("student_id")
grade Int
+ comment String @default("")
+ method FinalGradeMethod @default(AUTO)
// ---
allocationGroupId String @map("allocation_group_id")
allocationSubGroupId String @map("allocation_sub_group_id")
diff --git a/prisma/schema/migrations/20260205163110_marking/migration.sql b/prisma/schema/migrations/20260205163110_marking/migration.sql
new file mode 100644
index 000000000..fe68e6f59
--- /dev/null
+++ b/prisma/schema/migrations/20260205163110_marking/migration.sql
@@ -0,0 +1,58 @@
+/*
+ Warnings:
+
+ - fixed
+
+*/
+-- CreateEnum
+CREATE TYPE "MarkingStatus" AS ENUM ('PENDING', 'DONE', 'MODERATE', 'NEGOTIATE');
+
+-- CreateEnum
+CREATE TYPE "MarkingMethod" AS ENUM ('AUTO', 'OVERRIDE', 'NEGOTIATED', 'MODERATED');
+
+-- CreateEnum
+CREATE TYPE "FinalGradeMethod" AS ENUM ('AUTO', 'OVERRIDE');
+
+-- DropForeignKey
+ALTER TABLE "criterion_score" DROP CONSTRAINT "score_criterion";
+
+-- AlterTable
+ALTER TABLE "criterion_score"
+
+RENAME COLUMN "assessment_component_id" to "marking_component_id";
+
+-- AlterTable
+ALTER TABLE "final_grade" ADD COLUMN "comment" TEXT NOT NULL DEFAULT '',
+ADD COLUMN "method" "FinalGradeMethod" NOT NULL DEFAULT 'AUTO';
+
+-- AlterTable
+ALTER TABLE "final_unit_of_assessment_grade" ADD COLUMN "custom_due_date" TIMESTAMP(3),
+ADD COLUMN "custom_weight" INTEGER,
+ADD COLUMN "method" "MarkingMethod" NOT NULL DEFAULT 'AUTO',
+ADD COLUMN "status" "MarkingStatus" NOT NULL DEFAULT 'DONE',
+ADD COLUMN "submitted" BOOLEAN NOT NULL DEFAULT false;
+
+-- AlterTable
+ALTER TABLE "student_details" ADD COLUMN "enrolled" BOOLEAN NOT NULL DEFAULT true;
+
+-- AlterTable
+ALTER TABLE "unit_of_assessment"
+RENAME COLUMN "student_submission_deadline" to "default_student_submission_deadline";
+
+ALTER TABLE "unit_of_assessment"
+RENAME COLUMN "weight" to "default_weight";
+
+-- RenameForeignKey
+ALTER TABLE "assessment_criterion" RENAME CONSTRAINT "criterion_unit" TO "marking_component_uoa";
+
+-- RenameForeignKey
+ALTER TABLE "assessment_marking_submission" RENAME CONSTRAINT "submission_unit" TO "uoa_submission_uoa";
+
+-- RenameForeignKey
+ALTER TABLE "criterion_score" RENAME CONSTRAINT "score_submission" TO "marking_component_submission_uoa_submission";
+
+-- RenameForeignKey
+ALTER TABLE "unit_of_assessment" RENAME CONSTRAINT "unit_instance" TO "uoa_instance";
+
+-- AddForeignKey
+ALTER TABLE "criterion_score" ADD CONSTRAINT "marking_component_submission_marking_component" FOREIGN KEY ("marking_component_id") REFERENCES "assessment_criterion"("id") ON DELETE CASCADE ON UPDATE CASCADE;
diff --git a/prisma/schema/migrations/20260205164917_marking_rename/migration.sql b/prisma/schema/migrations/20260205164917_marking_rename/migration.sql
new file mode 100644
index 000000000..20f74aaa2
--- /dev/null
+++ b/prisma/schema/migrations/20260205164917_marking_rename/migration.sql
@@ -0,0 +1,19 @@
+/*
+ Warnings:
+
+*/
+-- RenameTable
+ALTER TABLE "assessment_criterion"
+RENAME TO "marking_component";
+
+-- RenameTable
+ALTER TABLE "criterion_score"
+RENAME TO "marking_component_submission";
+
+-- RenameTable
+ALTER TABLE "assessment_marking_submission"
+RENAME TO "unit_of_assessment_submission";
+
+-- RenameTable
+ALTER TABLE "final_unit_of_assessment_grade"
+RENAME TO "unit_of_assessment_grade";
diff --git a/prisma/schema/migrations/20260205165612_marking_residual/migration.sql b/prisma/schema/migrations/20260205165612_marking_residual/migration.sql
new file mode 100644
index 000000000..1a43c2fe3
--- /dev/null
+++ b/prisma/schema/migrations/20260205165612_marking_residual/migration.sql
@@ -0,0 +1,16 @@
+-- AlterTable
+ALTER TABLE "marking_component" RENAME CONSTRAINT "assessment_criterion_pkey" TO "marking_component_pkey";
+
+-- AlterTable
+ALTER TABLE "marking_component_submission" RENAME CONSTRAINT "criterion_score_pkey" TO "marking_component_submission_pkey";
+
+-- AlterTable
+ALTER TABLE "unit_of_assessment_grade" RENAME CONSTRAINT "final_unit_of_assessment_grade_pkey" TO "unit_of_assessment_grade_pkey";
+
+ALTER TABLE "unit_of_assessment_grade" ALTER COLUMN "status" SET DEFAULT 'PENDING';
+
+-- AlterTable
+ALTER TABLE "unit_of_assessment_submission" RENAME CONSTRAINT "assessment_marking_submission_pkey" TO "unit_of_assessment_submission_pkey";
+
+-- RenameIndex
+ALTER INDEX "assessment_criterion_title_unit_of_assessment_id_key" RENAME TO "marking_component_title_unit_of_assessment_id_key";
diff --git a/prisma/schema/user.prisma b/prisma/schema/user.prisma
index fea47f2e8..a9dffcfd4 100644
--- a/prisma/schema/user.prisma
+++ b/prisma/schema/user.prisma
@@ -27,6 +27,7 @@ model StudentDetails {
submittedPreferences StudentSubmittedPreference[]
matchingPairs MatchingPair[]
preAllocatedProjects Project[]
+ enrolled Boolean @default(true)
@@id([userId, allocationGroupId, allocationSubGroupId, allocationInstanceId], name: "studentDetailsId", map: "student_details_id")
@@map("student_details")
diff --git a/src/data-objects/space/instance.ts b/src/data-objects/space/instance.ts
index 01b8b2b3e..829f9df35 100644
--- a/src/data-objects/space/instance.ts
+++ b/src/data-objects/space/instance.ts
@@ -65,7 +65,7 @@ export class AllocationInstance extends DataObject {
return await this.db.unitOfAssessment
.findFirstOrThrow({
where: { id: unitOfAssessmentId },
- include: { flag: true, assessmentCriteria: true },
+ include: { flag: true, markingComponents: true },
})
.then((x) => T.toUnitOfAssessmentDTO(x));
}
@@ -73,7 +73,7 @@ export class AllocationInstance extends DataObject {
public async getCriteria(
unitOfAssessmentId: string,
): Promise {
- const data = await this.db.assessmentCriterion.findMany({
+ const data = await this.db.markingComponent.findMany({
where: { unitOfAssessmentId },
orderBy: { layoutIndex: "asc" },
});
@@ -87,9 +87,7 @@ export class AllocationInstance extends DataObject {
const flagData = await this.db.flag.findMany({
where: expand(this.params),
include: {
- unitsOfAssessment: {
- include: { flag: true, assessmentCriteria: true },
- },
+ unitsOfAssessment: { include: { flag: true, markingComponents: true } },
},
});
diff --git a/src/data-objects/user/marker.ts b/src/data-objects/user/marker.ts
index 4d9519d85..837efa26a 100644
--- a/src/data-objects/user/marker.ts
+++ b/src/data-objects/user/marker.ts
@@ -1,5 +1,3 @@
-/* eslint-disable @typescript-eslint/ban-ts-comment */
-// @ts-nocheck
import {
type MarkingSubmissionDTO,
type ProjectDTO,
@@ -47,7 +45,7 @@ export class Marker extends User {
unitOfAssessmentId: string,
studentId: string,
): Promise {
- const result = await this.db.markingSubmission.findFirst({
+ const result = await this.db.unitOfAssessmentSubmission.findFirst({
where: { markerId: this.id, studentId, unitOfAssessmentId },
include: { criterionScores: true },
});
@@ -99,7 +97,7 @@ export class Marker extends User {
unitsOfAssessment: {
where: { allowedMarkerTypes: { has: "SUPERVISOR" } },
include: {
- assessmentCriteria: true,
+ markingComponents: true,
flag: true,
markerSubmissions: { where: { markerId } },
},
@@ -159,7 +157,11 @@ export class Marker extends User {
studentFlag: {
include: {
unitsOfAssessment: {
- include: { markerSubmissions: true },
+ include: {
+ markerSubmissions: true,
+ flag: true,
+ markingComponents: true,
+ },
},
},
},
@@ -235,10 +237,8 @@ export class Marker extends User {
}: Omit) {
const markerId = this.id;
await this.db.$transaction([
- this.db.markingSubmission.upsert({
- where: {
- studentMarkerSubmission: { markerId, studentId, unitOfAssessmentId },
- },
+ this.db.unitOfAssessmentSubmission.upsert({
+ where: { uoaSubmissionId: { markerId, studentId, unitOfAssessmentId } },
create: {
markerId,
studentId,
@@ -256,19 +256,19 @@ export class Marker extends User {
},
}),
- ...Object.entries(marks).map(([assessmentCriterionId, m]) =>
- this.db.criterionScore.upsert({
+ ...Object.entries(marks).map(([markingComponentId, m]) =>
+ this.db.markingComponentSubmission.upsert({
where: {
- markingCriterionSubmission: {
+ markingComponentSubmission: {
markerId,
studentId,
- assessmentCriterionId,
+ markingComponentId,
},
},
create: {
markerId,
studentId,
- assessmentCriterionId,
+ markingComponentId,
unitOfAssessmentId,
grade: m.mark ?? -1,
justification: m.justification ?? "",
@@ -290,9 +290,17 @@ export class Marker extends User {
grade: number;
comment: string;
}) {
- await this.db.finalUnitOfAssessmentGrade.upsert({
- where: { studentAssessmentGrade: { studentId, unitOfAssessmentId } },
- create: { studentId, unitOfAssessmentId, comment, grade },
+ await this.db.unitOfAssessmentGrade.upsert({
+ where: { uoaGradeId: { studentId, unitOfAssessmentId } },
+ create: {
+ studentId,
+ unitOfAssessmentId,
+ comment,
+ grade,
+ status: "DONE",
+ method: "AUTO",
+ submitted: true,
+ },
update: { studentId, unitOfAssessmentId, comment, grade },
});
}
diff --git a/src/data-objects/user/reader.ts b/src/data-objects/user/reader.ts
index 9613acdab..9fba989d4 100644
--- a/src/data-objects/user/reader.ts
+++ b/src/data-objects/user/reader.ts
@@ -56,7 +56,7 @@ export class Reader extends Marker {
studentFlag: {
include: {
unitsOfAssessment: {
- include: { assessmentCriteria: true, flag: true },
+ include: { markingComponents: true, flag: true },
},
},
},
diff --git a/src/db/transformers.ts b/src/db/transformers.ts
index 8759dead5..b0a9122b5 100644
--- a/src/db/transformers.ts
+++ b/src/db/transformers.ts
@@ -23,7 +23,6 @@ import {
type DB_AllocationGroup,
type DB_AllocationInstance,
type DB_AllocationSubGroup,
- type DB_AssessmentCriterion,
type DB_Flag,
type DB_FlagOnProject,
type DB_UnitOfAssessment,
@@ -35,10 +34,11 @@ import {
type DB_TagOnProject,
type DB_User,
type DB_UserInInstance,
- type DB_CriterionScore,
- type DB_MarkingSubmission,
DB_ReaderPreferenceType,
ExtendedReaderPreferenceType,
+ type DB_MarkingComponent,
+ type DB_UnitOfAssessmentSubmission,
+ type DB_MarkingComponentSubmission,
} from "./types";
export class Transformers {
@@ -56,7 +56,9 @@ export class Transformers {
public static toMarkingSubmissionDTO(
this: void,
- data: DB_MarkingSubmission & { criterionScores?: DB_CriterionScore[] },
+ data: DB_UnitOfAssessmentSubmission & {
+ criterionScores?: DB_MarkingComponentSubmission[];
+ },
): MarkingSubmissionDTO {
return {
markerId: data.markerId,
@@ -66,7 +68,7 @@ export class Transformers {
marks: (data.criterionScores ?? []).reduce(
(acc, val) => ({
...acc,
- [val.assessmentCriterionId]: Transformers.toScoreDTO(val),
+ [val.markingComponentId]: Transformers.toScoreDTO(val),
}),
{},
),
@@ -75,7 +77,9 @@ export class Transformers {
draft: data.draft,
};
}
- public static toScoreDTO(data: DB_CriterionScore): CriterionScoreDTO {
+ public static toScoreDTO(
+ data: DB_MarkingComponentSubmission,
+ ): CriterionScoreDTO {
return { mark: data.grade, justification: data.justification };
}
@@ -236,7 +240,7 @@ export class Transformers {
public static toAssessmentCriterionDTO(
this: void,
- data: DB_AssessmentCriterion,
+ data: DB_MarkingComponent,
): AssessmentCriterionDTO {
return {
id: data.id,
@@ -252,19 +256,19 @@ export class Transformers {
this: void,
data: DB_UnitOfAssessment & {
flag: DB_Flag;
- assessmentCriteria: DB_AssessmentCriterion[];
+ markingComponents: DB_MarkingComponent[];
},
): UnitOfAssessmentDTO {
return {
id: data.id,
title: data.title,
flag: Transformers.toFlagDTO(data.flag),
- components: data.assessmentCriteria.map((x) =>
+ components: data.markingComponents.map((x) =>
Transformers.toAssessmentCriterionDTO(x),
),
- studentSubmissionDeadline: data.studentSubmissionDeadline,
- markerSubmissionDeadline: data.markerSubmissionDeadline,
- weight: data.weight,
+ studentSubmissionDeadline: data.defaultStudentSubmissionDeadline,
+ markerSubmissionDeadline: data.defaultStudentSubmissionDeadline,
+ weight: data.defaultWeight,
isOpen: data.open,
allowedMarkerTypes: data.allowedMarkerTypes,
};
diff --git a/src/db/types.ts b/src/db/types.ts
index 799a3b1f0..3881c5467 100644
--- a/src/db/types.ts
+++ b/src/db/types.ts
@@ -20,14 +20,14 @@ export type {
AllocationGroup as DB_AllocationGroup,
AllocationInstance as DB_AllocationInstance,
AllocationSubGroup as DB_AllocationSubGroup,
- AssessmentCriterion as DB_AssessmentCriterion,
- CriterionScore as DB_CriterionScore,
FinalGrade as DB_FinalGrade,
Flag as DB_Flag,
FlagOnProject as DB_FlagOnProject,
UnitOfAssessment as DB_UnitOfAssessment,
GroupAdmin as DB_GroupAdmin,
- MarkingSubmission as DB_MarkingSubmission,
+ MarkingComponent as DB_MarkingComponent,
+ MarkingComponentSubmission as DB_MarkingComponentSubmission,
+ UnitOfAssessmentSubmission as DB_UnitOfAssessmentSubmission,
MatchingPair as DB_MatchingPair,
MatchingResult as DB_MatchingResult,
Project as DB_Project,
diff --git a/src/dto/project.ts b/src/dto/project.ts
index e65d0dce3..dfe635c5e 100644
--- a/src/dto/project.ts
+++ b/src/dto/project.ts
@@ -25,8 +25,7 @@ export const ProjectAllocationStatus = {
UNALLOCATED: "UNALLOCATED",
} as const;
-export type ProjectAllocationStatus =
- (typeof ProjectAllocationStatus)[keyof typeof ProjectAllocationStatus];
+export type ProjectAllocationStatus = keyof typeof ProjectAllocationStatus;
export const projectAllocationStatusSchema = z.enum([
ProjectAllocationStatus.UNALLOCATED,
diff --git a/src/server/routers/institution/instance/index.ts b/src/server/routers/institution/instance/index.ts
index c8ba1979d..e77176e18 100644
--- a/src/server/routers/institution/instance/index.ts
+++ b/src/server/routers/institution/instance/index.ts
@@ -1414,7 +1414,7 @@ export const instanceRouter = createTRPCRouter({
where: expand(instance.params),
include: {
unitsOfAssessment: {
- include: { flag: true, assessmentCriteria: true },
+ include: { flag: true, markingComponents: true },
orderBy: [{ markerSubmissionDeadline: "asc" }],
},
},
diff --git a/src/server/routers/marking.ts b/src/server/routers/marking.ts
index 0e8f6170d..6e0fd4b0f 100644
--- a/src/server/routers/marking.ts
+++ b/src/server/routers/marking.ts
@@ -82,19 +82,19 @@ export const markingRouter = createTRPCRouter({
const units = await db.unitOfAssessment
.findMany({
where: expand(instance.params),
- include: { flag: true, assessmentCriteria: true },
+ include: { flag: true, markingComponents: true },
})
.then((data) => data.map((x) => T.toUnitOfAssessmentDTO(x)));
- const submissions = await db.markingSubmission.findMany({
+ const submissions = await db.unitOfAssessmentSubmission.findMany({
where: {
unitOfAssessmentId: { in: units.map((e) => e.id) },
draft: false,
},
include: {
criterionScores: {
- include: { criterion: true },
- orderBy: { criterion: { layoutIndex: "asc" } },
+ include: { markingComponent: true },
+ orderBy: { markingComponent: { layoutIndex: "asc" } },
},
},
});
@@ -113,7 +113,8 @@ export const markingRouter = createTRPCRouter({
const comment =
comments.reduce((acc, val) => {
- const rest = val.criterion.title + "\t" + val.justification;
+ const rest =
+ val.markingComponent.title + "\t" + val.justification;
return acc + "\t\t" + rest;
}, "") +
"\t\tFinal Comment:\t" +
@@ -144,7 +145,7 @@ export const markingRouter = createTRPCRouter({
>,
);
- const unitFinalMarks = await db.finalUnitOfAssessmentGrade.findMany({
+ const unitFinalMarks = await db.unitOfAssessmentGrade.findMany({
where: { unitOfAssessmentId: { in: units.map((e) => e.id) } },
});
@@ -261,6 +262,8 @@ export const markingRouter = createTRPCRouter({
grade: finalMark,
...expand(instance.params),
studentId: student.id,
+ comment: "",
+ method: "AUTO",
},
});
}
diff --git a/src/server/routers/user/marker.ts b/src/server/routers/user/marker.ts
index 615b77e90..c79d1404a 100644
--- a/src/server/routers/user/marker.ts
+++ b/src/server/routers/user/marker.ts
@@ -36,7 +36,7 @@ export const markerRouter = createTRPCRouter({
.query(async ({ ctx: { db }, input: { unitOfAssessmentId } }) => {
const res = await db.unitOfAssessment.findFirstOrThrow({
where: { id: unitOfAssessmentId },
- include: { flag: true, assessmentCriteria: true },
+ include: { flag: true, markingComponents: true },
});
return T.toUnitOfAssessmentDTO(res);
@@ -145,7 +145,7 @@ export const markerRouter = createTRPCRouter({
const deadline = addWeeks(new Date(), 1);
// otherwise, if this is a doubly-marked submission, and now both are submitted then:
- const data = await db.markingSubmission.findMany({
+ const data = await db.unitOfAssessmentSubmission.findMany({
where: { studentId, unitOfAssessmentId },
include: { criterionScores: true },
});
@@ -240,7 +240,7 @@ export const markerRouter = createTRPCRouter({
return;
}
- const numSubmissions = await db.markingSubmission.count({
+ const numSubmissions = await db.unitOfAssessmentSubmission.count({
where: { studentId, unitOfAssessmentId, draft: false },
});
@@ -253,7 +253,7 @@ export const markerRouter = createTRPCRouter({
}
// otherwise, if this is a doubly-marked submission, and now both are submitted then:
- const data = await db.markingSubmission.findMany({
+ const data = await db.unitOfAssessmentSubmission.findMany({
where: { studentId, unitOfAssessmentId, draft: false },
include: { criterionScores: true },
});