Skip to content

Commit c2197d4

Browse files
Create dbh service for grades (#3059)
1 parent 6d62bb2 commit c2197d4

File tree

2 files changed

+202
-0
lines changed

2 files changed

+202
-0
lines changed
Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
import * as z from "zod"
2+
import { filterSchema, institutionFilter, taskFilter } from "./filters"
3+
import { schemas } from "@dotkomonline/grades-db/schemas"
4+
5+
const BASE_URL = "https://dbh.hkdir.no"
6+
const TABLE_URL = `${BASE_URL}/api/Tabeller/hentJSONTabellData`
7+
8+
const API_VERSION = 1
9+
const STATUS_LINE = false // Should extra information about the API response be included?
10+
const CODE_TEXT = true // Should names of related resources be included?
11+
const DECIMAL_SEPERATOR = "."
12+
13+
const Semesters = schemas.SemesterSchema.enum
14+
const StudyLevels = schemas.StudyLevelSchema.enum
15+
const TeachingLanguages = schemas.TeachingLanguageSchema.enum
16+
17+
const parseSemester = (semester: number) => {
18+
switch (semester) {
19+
case 1:
20+
return Semesters.SPRING
21+
case 2:
22+
return Semesters.SUMMER
23+
case 3:
24+
return Semesters.FALL
25+
default:
26+
return null
27+
}
28+
}
29+
30+
const parseStudyLevel = (level: string) => {
31+
switch (level) {
32+
case "LN": // DBH does not differentiate between bachelor level courses, we assume foundation as fallback
33+
return StudyLevels.FOUNDATION
34+
case "HN":
35+
return StudyLevels.MASTER
36+
case "FU":
37+
return StudyLevels.PHD
38+
case "VS":
39+
return StudyLevels.CONTINUING_EDUCATION //???? Not sure if forkurs maps to continuing education
40+
default:
41+
return StudyLevels.UNKNOWN
42+
}
43+
}
44+
45+
const parseTeachingLanguage = (language: string) => {
46+
switch (language) {
47+
case "NOR":
48+
return [TeachingLanguages.NORWEGIAN]
49+
case "ENG":
50+
return [TeachingLanguages.ENGLISH]
51+
default:
52+
return []
53+
}
54+
}
55+
56+
const ApiGradeSchema = z.object({
57+
Emnekode: z.coerce.string(),
58+
Årstall: z.coerce.number(),
59+
Karakter: z.coerce.string(),
60+
Semester: z.coerce.number(),
61+
"Antall kandidater totalt": z.coerce.number(),
62+
})
63+
const ParsedGradeSchema = ApiGradeSchema.transform((input) => {
64+
return {
65+
code: input.Emnekode,
66+
year: input.Årstall,
67+
grade: input.Karakter,
68+
semester: parseSemester(input.Semester),
69+
count: input["Antall kandidater totalt"],
70+
}
71+
})
72+
73+
const ApiCourseSchema = z.object({
74+
Årstall: z.coerce.number(),
75+
Semester: z.coerce.number(),
76+
Emnekode: z.coerce.string(),
77+
Emnenavn: z.coerce.string(),
78+
Nivåkode: z.coerce.string(),
79+
"Underv.språk": z.coerce.string(),
80+
})
81+
const ParsedCourseSchema = ApiCourseSchema.transform((input) => {
82+
return {
83+
year: input.Årstall,
84+
semester: parseSemester(input.Semester),
85+
code: input.Emnekode.split("-")[0], // DBH has versioning in their course codes, this ensures only the actual course code remains
86+
norwegianName: input.Emnenavn,
87+
studyLevel: parseStudyLevel(input.Nivåkode),
88+
teachingLanguges: parseTeachingLanguage(input["Underv.språk"]),
89+
}
90+
})
91+
92+
const QuerySchema = z.object({
93+
tabell_id: z.number(),
94+
api_versjon: z.number().default(API_VERSION),
95+
statuslinje: z.string().default(STATUS_LINE ? "J" : "N"),
96+
kodetekst: z.string().default(CODE_TEXT ? "J" : "N"),
97+
desimal_seperator: z.string().default(DECIMAL_SEPERATOR),
98+
sortBy: z.array(z.string()).default([]),
99+
variabler: z.array(z.string()).default(["*"]),
100+
filter: z.array(filterSchema).default([]),
101+
groupBy: z.array(z.string()).optional(),
102+
begrensning: z.coerce.string().optional(),
103+
})
104+
105+
const fetchData = async (
106+
tableId: number,
107+
sortBy: string[],
108+
options?: {
109+
groupBy?: string[]
110+
filters?: z.infer<typeof filterSchema>[]
111+
}
112+
) => {
113+
const query = QuerySchema.parse({
114+
tabell_id: tableId,
115+
sortBy,
116+
...(options?.filters !== undefined ? { filter: options.filters } : {}),
117+
...(options?.groupBy !== undefined ? { groupBy: options.groupBy } : {}),
118+
})
119+
120+
const res = await fetch(TABLE_URL, {
121+
method: "POST",
122+
headers: {
123+
"Content-Type": "application/json",
124+
},
125+
body: JSON.stringify(query),
126+
})
127+
128+
if (res.status === 204) {
129+
return []
130+
}
131+
132+
return await res.json()
133+
}
134+
135+
const fetchAllGrades = () => {
136+
const tableId = 308
137+
138+
const groupBy = ["Institusjonskode", "Emnekode", "Karakter", "Årstall", "Semester", "Avdelingskode"]
139+
const sortBy = ["Emnekode", "Årstall", "Semester"]
140+
const filters = [institutionFilter]
141+
142+
return fetchData(tableId, sortBy, { groupBy, filters })
143+
}
144+
145+
export const getAllGrades = async () => {
146+
const grades = await fetchAllGrades()
147+
148+
return z.array(ParsedGradeSchema).parse(grades)
149+
}
150+
151+
const fetchAllCourses = async () => {
152+
const tableId = 208
153+
154+
const sortBy = ["Emnekode", "Årstall", "Semester"]
155+
const filters = [institutionFilter, taskFilter]
156+
157+
return fetchData(tableId, sortBy, { filters })
158+
}
159+
160+
export const getAllCourses = async () => {
161+
const courses = await fetchAllCourses()
162+
163+
return z.array(ParsedCourseSchema).parse(courses)
164+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import * as z from "zod"
2+
3+
const INSTITUTION_ID = 1150 // ID for NTNU in DBH databases
4+
5+
export const filterFilter = {
6+
TOP: "top",
7+
ALL: "all",
8+
ITEM: "item",
9+
BETWEEN: "between",
10+
LIKE: "like",
11+
LESSTHAN: "lessthan",
12+
} as const
13+
14+
export const filterSchema = z.object({
15+
variabel: z.string(),
16+
selection: z.object({
17+
filter: z.nativeEnum(filterFilter),
18+
values: z.array(z.coerce.string()),
19+
exclude: z.array(z.coerce.string()).default([""]),
20+
}),
21+
})
22+
23+
export const institutionFilter = filterSchema.parse({
24+
variabel: "Institusjonskode",
25+
selection: {
26+
filter: filterFilter.ITEM,
27+
values: [INSTITUTION_ID],
28+
},
29+
})
30+
31+
export const taskFilter = filterSchema.parse({
32+
variabel: "Oppgave (ny fra h2012)",
33+
selection: {
34+
filter: filterFilter.ALL,
35+
values: ["*"],
36+
exclude: ["1", "2"],
37+
},
38+
})

0 commit comments

Comments
 (0)