| 1 |
|
|---|
| 2 | -- Validation function for Exam_Results.data
|
|---|
| 3 | CREATE OR REPLACE FUNCTION fn_validate_exam_data(p_data jsonb)
|
|---|
| 4 | RETURNS boolean
|
|---|
| 5 | LANGUAGE plpgsql IMMUTABLE AS $$
|
|---|
| 6 | BEGIN
|
|---|
| 7 | RETURN (
|
|---|
| 8 | jsonb_typeof(p_data) = 'array'
|
|---|
| 9 | AND jsonb_array_length(p_data) > 0
|
|---|
| 10 | AND NOT EXISTS (
|
|---|
| 11 | SELECT 1
|
|---|
| 12 | FROM jsonb_array_elements(p_data) AS elem
|
|---|
| 13 | WHERE
|
|---|
| 14 | elem->>'index' IS NULL
|
|---|
| 15 | OR elem->>'index' !~ '^\d{10}$'
|
|---|
| 16 | OR (elem->>'points') IS NULL
|
|---|
| 17 | OR (elem->>'points')::numeric < 0
|
|---|
| 18 | )
|
|---|
| 19 | );
|
|---|
| 20 | END;
|
|---|
| 21 | $$;
|
|---|
| 22 |
|
|---|
| 23 |
|
|---|
| 24 | -- Validation function for Aggregated_Course_Edition_Results.data
|
|---|
| 25 | CREATE OR REPLACE FUNCTION fn_validate_aggregated_data(p_data jsonb)
|
|---|
| 26 | RETURNS boolean
|
|---|
| 27 | LANGUAGE plpgsql IMMUTABLE AS $$
|
|---|
| 28 | BEGIN
|
|---|
| 29 | RETURN (
|
|---|
| 30 | jsonb_typeof(p_data) = 'array'
|
|---|
| 31 | AND jsonb_array_length(p_data) > 0
|
|---|
| 32 | AND NOT EXISTS (
|
|---|
| 33 | SELECT 1
|
|---|
| 34 | FROM jsonb_array_elements(p_data) AS elem
|
|---|
| 35 | WHERE
|
|---|
| 36 | elem->>'index' IS NULL
|
|---|
| 37 | OR elem->>'index' !~ '^\d{10}$'
|
|---|
| 38 |
|
|---|
| 39 | OR (elem->>'total_pts') IS NULL
|
|---|
| 40 | OR (elem->>'total_pts')::numeric < 0
|
|---|
| 41 |
|
|---|
| 42 | OR (elem->>'max_pts') IS NULL
|
|---|
| 43 | OR (elem->>'max_pts')::numeric <= 0
|
|---|
| 44 |
|
|---|
| 45 | OR (elem->>'percentage') IS NULL
|
|---|
| 46 | OR (elem->>'percentage')::numeric < 0
|
|---|
| 47 | OR (elem->>'percentage')::numeric > 100
|
|---|
| 48 |
|
|---|
| 49 | OR (elem->'passed') IS NULL
|
|---|
| 50 | OR jsonb_typeof(elem->'passed') <> 'boolean'
|
|---|
| 51 |
|
|---|
| 52 | OR (
|
|---|
| 53 | elem->'grade' IS NOT NULL
|
|---|
| 54 | AND jsonb_typeof(elem->'grade') <> 'null'
|
|---|
| 55 | AND (
|
|---|
| 56 | jsonb_typeof(elem->'grade') <> 'number'
|
|---|
| 57 | OR (elem->>'grade')::int NOT BETWEEN 6 AND 10
|
|---|
| 58 | )
|
|---|
| 59 | )
|
|---|
| 60 | )
|
|---|
| 61 | );
|
|---|
| 62 | END;
|
|---|
| 63 | $$;
|
|---|
| 64 |
|
|---|
| 65 |
|
|---|
| 66 |
|
|---|
| 67 | CREATE OR REPLACE FUNCTION fn_percentage_to_grade(p_pct numeric)
|
|---|
| 68 | RETURNS integer
|
|---|
| 69 | LANGUAGE plpgsql IMMUTABLE AS $$
|
|---|
| 70 | BEGIN
|
|---|
| 71 | RETURN CASE
|
|---|
| 72 | WHEN p_pct >= 91 THEN 10
|
|---|
| 73 | WHEN p_pct >= 81 THEN 9
|
|---|
| 74 | WHEN p_pct >= 71 THEN 8
|
|---|
| 75 | WHEN p_pct >= 61 THEN 7
|
|---|
| 76 | WHEN p_pct >= 51 THEN 6
|
|---|
| 77 | ELSE NULL
|
|---|
| 78 | END;
|
|---|
| 79 | END;
|
|---|
| 80 | $$;
|
|---|
| 81 |
|
|---|
| 82 |
|
|---|
| 83 |
|
|---|
| 84 |
|
|---|
| 85 |
|
|---|
| 86 | -- FOR v_student_registration_state VIEW
|
|---|
| 87 | CREATE OR REPLACE FUNCTION fn_student_registration_state(p_student_id INTEGER)
|
|---|
| 88 | RETURNS TABLE (
|
|---|
| 89 | student_id INTEGER,
|
|---|
| 90 | academic_program_code TEXT,
|
|---|
| 91 | course_code TEXT,
|
|---|
| 92 | course_name TEXT,
|
|---|
| 93 | credits INTEGER,
|
|---|
| 94 | mandatory BOOLEAN,
|
|---|
| 95 | curriculum_semester SMALLINT,
|
|---|
| 96 | already_passed BOOLEAN,
|
|---|
| 97 | currently_enrolled BOOLEAN,
|
|---|
| 98 | prerequisites_met BOOLEAN,
|
|---|
| 99 | missing_prereq_count BIGINT,
|
|---|
| 100 | credit_headroom BIGINT,
|
|---|
| 101 | would_exceed_cap BOOLEAN,
|
|---|
| 102 | can_enroll BOOLEAN,
|
|---|
| 103 | enrollment_status TEXT
|
|---|
| 104 | )
|
|---|
| 105 | LANGUAGE plpgsql STABLE
|
|---|
| 106 | AS $$
|
|---|
| 107 | BEGIN
|
|---|
| 108 | RETURN QUERY
|
|---|
| 109 | WITH
|
|---|
| 110 | active_pe AS (
|
|---|
| 111 | SELECT pe.id AS pe_id, pe.academic_program_code
|
|---|
| 112 | FROM Program_Enrollment pe
|
|---|
| 113 | WHERE pe.student_id = p_student_id
|
|---|
| 114 | AND pe.date_disenrollment IS NULL
|
|---|
| 115 | AND pe.finished IS NOT TRUE
|
|---|
| 116 | ),
|
|---|
| 117 | passed_courses AS (
|
|---|
| 118 | SELECT DISTINCT ed.course_code AS passed_code
|
|---|
| 119 | FROM active_pe ape
|
|---|
| 120 | JOIN Semester_Enrollment se ON se.program_enrollment_id = ape.pe_id
|
|---|
| 121 | JOIN Course_Enrollment ce ON ce.semester_enrollment_id = se.id
|
|---|
| 122 | JOIN Course_Edition ed ON ed.id = ce.course_edition_id
|
|---|
| 123 | JOIN Student_Grade sg ON sg.course_enrollment_id = ce.id
|
|---|
| 124 | ),
|
|---|
| 125 | passed_with_equiv AS (
|
|---|
| 126 | SELECT passed_code FROM passed_courses
|
|---|
| 127 | UNION
|
|---|
| 128 | SELECT eq.course2_code
|
|---|
| 129 | FROM passed_courses pc
|
|---|
| 130 | JOIN Course_Equivalence eq ON eq.course1_code = pc.passed_code
|
|---|
| 131 | UNION
|
|---|
| 132 | SELECT eq.course1_code
|
|---|
| 133 | FROM passed_courses pc
|
|---|
| 134 | JOIN Course_Equivalence eq ON eq.course2_code = pc.passed_code
|
|---|
| 135 | ),
|
|---|
| 136 | current_enrollments AS (
|
|---|
| 137 | SELECT ed.course_code AS curr_code
|
|---|
| 138 | FROM active_pe ape
|
|---|
| 139 | JOIN Semester_Enrollment se ON se.program_enrollment_id = ape.pe_id
|
|---|
| 140 | AND se.academic_year = 2025 AND (se.semester::int) = 1 -- cast to int, compare to 1
|
|---|
| 141 | JOIN Course_Enrollment ce ON ce.semester_enrollment_id = se.id
|
|---|
| 142 | JOIN Course_Edition ed ON ed.id = ce.course_edition_id
|
|---|
| 143 | ),
|
|---|
| 144 | current_credits AS (
|
|---|
| 145 | SELECT COALESCE(SUM(c.credits), 0) AS credits_consumed
|
|---|
| 146 | FROM active_pe ape
|
|---|
| 147 | LEFT JOIN Semester_Enrollment se ON se.program_enrollment_id = ape.pe_id
|
|---|
| 148 | AND se.academic_year = 2025 AND (se.semester::int) = 1
|
|---|
| 149 | LEFT JOIN Course_Enrollment ce ON ce.semester_enrollment_id = se.id
|
|---|
| 150 | LEFT JOIN Course_Edition ed ON ed.id = ce.course_edition_id
|
|---|
| 151 | LEFT JOIN Course c ON c.code = ed.course_code
|
|---|
| 152 | ),
|
|---|
| 153 | prereq_check AS (
|
|---|
| 154 | SELECT
|
|---|
| 155 | cur.course_code AS prereq_course,
|
|---|
| 156 | COUNT(cp.predecessor_course_code) AS total_prereqs,
|
|---|
| 157 | COUNT(pwe.passed_code) AS met_prereqs
|
|---|
| 158 | FROM active_pe ape
|
|---|
| 159 | CROSS JOIN Curriculum cur
|
|---|
| 160 | LEFT JOIN Course_Prerequisite cp ON cp.successor_course_code = cur.course_code
|
|---|
| 161 | LEFT JOIN passed_with_equiv pwe ON pwe.passed_code = cp.predecessor_course_code
|
|---|
| 162 | WHERE cur.academic_program_code = ape.academic_program_code
|
|---|
| 163 | GROUP BY cur.course_code
|
|---|
| 164 | )
|
|---|
| 165 | SELECT
|
|---|
| 166 | p_student_id,
|
|---|
| 167 | ape.academic_program_code,
|
|---|
| 168 | cur.course_code,
|
|---|
| 169 | c.name,
|
|---|
| 170 | c.credits,
|
|---|
| 171 | cur.mandatory,
|
|---|
| 172 | cur.semester,
|
|---|
| 173 | (pwe.passed_code IS NOT NULL) AS already_passed,
|
|---|
| 174 | (ce_now.curr_code IS NOT NULL) AS currently_enrolled,
|
|---|
| 175 | COALESCE(pc.total_prereqs IS NULL OR pc.met_prereqs = pc.total_prereqs, TRUE) AS prerequisites_met,
|
|---|
| 176 | COALESCE(pc.total_prereqs - pc.met_prereqs, 0) AS missing_prereq_count,
|
|---|
| 177 | 40 - cc.credits_consumed AS credit_headroom,
|
|---|
| 178 | (40 - cc.credits_consumed) < c.credits AS would_exceed_cap,
|
|---|
| 179 | CASE
|
|---|
| 180 | WHEN pwe.passed_code IS NOT NULL THEN FALSE
|
|---|
| 181 | WHEN ce_now.curr_code IS NOT NULL THEN FALSE
|
|---|
| 182 | WHEN pc.total_prereqs IS NOT NULL AND pc.met_prereqs < pc.total_prereqs THEN FALSE
|
|---|
| 183 | WHEN (40 - cc.credits_consumed) < c.credits THEN FALSE
|
|---|
| 184 | ELSE TRUE
|
|---|
| 185 | END AS can_enroll,
|
|---|
| 186 | CASE
|
|---|
| 187 | WHEN pwe.passed_code IS NOT NULL THEN 'already_passed'
|
|---|
| 188 | WHEN ce_now.curr_code IS NOT NULL THEN 'currently_enrolled'
|
|---|
| 189 | WHEN pc.total_prereqs IS NOT NULL AND pc.met_prereqs < pc.total_prereqs THEN 'prerequisites_not_met'
|
|---|
| 190 | WHEN (40 - cc.credits_consumed) < c.credits THEN 'credit_cap_exceeded'
|
|---|
| 191 | ELSE 'eligible'
|
|---|
| 192 | END AS enrollment_status
|
|---|
| 193 | FROM active_pe ape
|
|---|
| 194 | CROSS JOIN Curriculum cur
|
|---|
| 195 | JOIN Course c ON c.code = cur.course_code
|
|---|
| 196 | LEFT JOIN passed_with_equiv pwe ON pwe.passed_code = cur.course_code
|
|---|
| 197 | LEFT JOIN current_enrollments ce_now ON ce_now.curr_code = cur.course_code
|
|---|
| 198 | LEFT JOIN prereq_check pc ON pc.prereq_course = cur.course_code
|
|---|
| 199 | CROSS JOIN current_credits cc
|
|---|
| 200 | WHERE cur.academic_program_code = ape.academic_program_code;
|
|---|
| 201 | END;
|
|---|
| 202 | $$;
|
|---|
| 203 |
|
|---|
| 204 |
|
|---|
| 205 |
|
|---|
| 206 |
|
|---|
| 207 |
|
|---|
| 208 | -- FOR v_teaching_assistant_eligibility VIEW
|
|---|
| 209 | CREATE OR REPLACE FUNCTION fn_teaching_assistant_eligibility(p_edition_id INTEGER)
|
|---|
| 210 | RETURNS TABLE (
|
|---|
| 211 | student_id INTEGER,
|
|---|
| 212 | student_index TEXT,
|
|---|
| 213 | student_name TEXT,
|
|---|
| 214 | edition_id INTEGER,
|
|---|
| 215 | academic_year SMALLINT,
|
|---|
| 216 | semester SMALLINT,
|
|---|
| 217 | course_code TEXT,
|
|---|
| 218 | course_name TEXT,
|
|---|
| 219 | eligible_supervisors TEXT,
|
|---|
| 220 | times_enrolled BIGINT
|
|---|
| 221 | )
|
|---|
| 222 | LANGUAGE plpgsql STABLE
|
|---|
| 223 | AS $$
|
|---|
| 224 | DECLARE
|
|---|
| 225 | var_course_code TEXT;
|
|---|
| 226 | var_academic_year SMALLINT;
|
|---|
| 227 | var_semester SMALLINT;
|
|---|
| 228 | BEGIN
|
|---|
| 229 | SELECT ed.course_code, ed.academic_year, ed.semester
|
|---|
| 230 | INTO var_course_code, var_academic_year, var_semester
|
|---|
| 231 | FROM Course_Edition ed
|
|---|
| 232 | WHERE ed.id = p_edition_id;
|
|---|
| 233 |
|
|---|
| 234 | IF NOT FOUND THEN
|
|---|
| 235 | RETURN;
|
|---|
| 236 | END IF;
|
|---|
| 237 |
|
|---|
| 238 | RETURN QUERY
|
|---|
| 239 | WITH
|
|---|
| 240 | course_passers AS (
|
|---|
| 241 | SELECT DISTINCT pe.student_id
|
|---|
| 242 | FROM Program_Enrollment pe
|
|---|
| 243 | JOIN Semester_Enrollment se ON se.program_enrollment_id = pe.id
|
|---|
| 244 | JOIN Course_Enrollment ce ON ce.semester_enrollment_id = se.id
|
|---|
| 245 | JOIN Course_Edition ed ON ed.id = ce.course_edition_id
|
|---|
| 246 | JOIN Student_Grade sg ON sg.course_enrollment_id = ce.id
|
|---|
| 247 | WHERE ed.course_code = var_course_code
|
|---|
| 248 | ),
|
|---|
| 249 | currently_enrolled AS (
|
|---|
| 250 | SELECT DISTINCT pe.student_id
|
|---|
| 251 | FROM Program_Enrollment pe
|
|---|
| 252 | JOIN Semester_Enrollment se ON se.program_enrollment_id = pe.id
|
|---|
| 253 | AND se.academic_year = var_academic_year
|
|---|
| 254 | AND (se.semester::int) = var_semester -- compare to SMALLINT variable, cast semester to int
|
|---|
| 255 | JOIN Course_Enrollment ce ON ce.semester_enrollment_id = se.id
|
|---|
| 256 | WHERE ce.course_edition_id = p_edition_id
|
|---|
| 257 | ),
|
|---|
| 258 | existing_tas AS (
|
|---|
| 259 | SELECT ta.student_id
|
|---|
| 260 | FROM Teaching_Assistant ta
|
|---|
| 261 | JOIN Teaches tt ON tt.teacher_id = ta.teacher_id
|
|---|
| 262 | WHERE tt.course_edition_id = p_edition_id
|
|---|
| 263 | ),
|
|---|
| 264 | supervisors AS (
|
|---|
| 265 | SELECT STRING_AGG(m.name || ' ' || m.surname, ', ' ORDER BY m.surname) AS names
|
|---|
| 266 | FROM Teaches t
|
|---|
| 267 | JOIN Member m ON m.id = t.teacher_id
|
|---|
| 268 | WHERE t.course_edition_id = p_edition_id
|
|---|
| 269 | ),
|
|---|
| 270 | times_taken AS (
|
|---|
| 271 | SELECT
|
|---|
| 272 | pe.student_id,
|
|---|
| 273 | COUNT(DISTINCT ce.id) AS cnt
|
|---|
| 274 | FROM Program_Enrollment pe
|
|---|
| 275 | JOIN Semester_Enrollment se ON se.program_enrollment_id = pe.id
|
|---|
| 276 | JOIN Course_Enrollment ce ON ce.semester_enrollment_id = se.id
|
|---|
| 277 | JOIN Course_Edition ed ON ed.id = ce.course_edition_id
|
|---|
| 278 | WHERE ed.course_code = var_course_code
|
|---|
| 279 | GROUP BY pe.student_id
|
|---|
| 280 | )
|
|---|
| 281 | SELECT
|
|---|
| 282 | s.id,
|
|---|
| 283 | s.index,
|
|---|
| 284 | m.name || ' ' || m.surname,
|
|---|
| 285 | p_edition_id,
|
|---|
| 286 | var_academic_year,
|
|---|
| 287 | var_semester,
|
|---|
| 288 | c.code,
|
|---|
| 289 | c.name,
|
|---|
| 290 | supervisors.names,
|
|---|
| 291 | COALESCE(times_taken.cnt, 0)::BIGINT
|
|---|
| 292 | FROM course_passers cp
|
|---|
| 293 | JOIN Student s ON s.id = cp.student_id
|
|---|
| 294 | JOIN Member m ON m.id = s.id
|
|---|
| 295 | CROSS JOIN Course c
|
|---|
| 296 | CROSS JOIN supervisors
|
|---|
| 297 | LEFT JOIN currently_enrolled ce ON ce.student_id = cp.student_id
|
|---|
| 298 | LEFT JOIN existing_tas et ON et.student_id = cp.student_id
|
|---|
| 299 | LEFT JOIN times_taken ON times_taken.student_id = cp.student_id
|
|---|
| 300 | WHERE ce.student_id IS NULL
|
|---|
| 301 | AND et.student_id IS NULL
|
|---|
| 302 | AND c.code = var_course_code;
|
|---|
| 303 | END;
|
|---|
| 304 | $$; |
|---|