Module 9 – Validation Rules & Business Logic (MCP)
This module implements validation and business rules to ensure data integrity and correct workflow behavior in the Attendance System.
Why Validation Matters
- Prevent invalid attendance entries (wrong date/hour/subject)
- Enforce staff–subject authorization before marking attendance
- Avoid timetable conflicts and duplicate sessions
- Ensure student exists and belongs to the semester
1. Check: Staff is Assigned to the Subject
# file: mcp_server/validators.py
async def staff_assigned(db, staff_id: int, subject_id: int) -> bool:
q = "SELECT 1 FROM staff_subjects WHERE staff_id = :sid AND subject_id = :sub"
row = await db.fetch_one(q, values={'sid': staff_id, 'sub': subject_id})
return bool(row)
# Usage inside FastAPI route
if not await staff_assigned(db, staff_id, subject_id):
raise HTTPException(status_code=403, detail='Staff not assigned to this subject')
2. Check: Timetable Slot Exists
-- Verify a timetable slot exists for semester/day/hour/subject
SELECT 1 FROM timetable_slots
WHERE semester_id = :semester_id AND day_of_week = :day AND slot_order = :hour AND subject_id = :subject_id;
3. Check: Student Belongs to Semester
# Python helper
async def student_in_semester(db, student_id: int, semester_id: int) -> bool:
q = 'SELECT 1 FROM students WHERE id = :sid AND semester = :sem'
return bool(await db.fetch_one(q, values={'sid': student_id, 'sem': semester_id}))
# Raise error if not
if not await student_in_semester(db, student_id, semester_id):
raise HTTPException(400, 'Student not enrolled in this semester')
4. Prevent Duplicate Attendance for Same Session
-- Check if attendance row already exists for that student/session/hour
SELECT 1 FROM attendance
WHERE subject_id = :subject_id AND student_id = :student_id AND date = :date AND hour = :hour;
5. Business Rule: Attendance Window
Only allow marking attendance within a sensible window (e.g., same day ±1 day) unless an admin overrides.
from datetime import datetime, timedelta
def within_attendance_window(date_str: str) -> bool:
d = datetime.fromisoformat(date_str).date()
today = datetime.utcnow().date()
return abs((today - d).days) <= 1
if not within_attendance_window(payload['date']):
raise HTTPException(400, 'Attendance can only be marked within 1 day of class')
6. Conflict Checker for Timetable Changes
-- Prevent two subjects assigned to same semester/day/hour
SELECT COUNT(*) FROM timetable_slots
WHERE semester_id = :semester_id AND day_of_week = :day AND slot_order = :hour;
-- if count > 0 then conflict exists
7. Consolidated FastAPI Example: Mark Attendance (with Validation)
@app.post('/api/attendance/mark')
async def mark_attendance(sessionId: str, studentId: int, status: str, db: Database = Depends(get_db), user=Depends(get_current_user)):
# fetch session metadata
ses = await db.fetch_one('SELECT * FROM attendance_sessions WHERE id = :sid', values={'sid': sessionId})
if not ses:
raise HTTPException(404, 'Session not found')
# verify staff
if not await staff_assigned(db, user.staff_id, ses['subject_id']):
raise HTTPException(403, 'Not authorized')
# verify student in semester
if not await student_in_semester(db, studentId, ses['semester_id']):
raise HTTPException(400, 'Student not in semester')
# prevent duplicates
exists = await db.fetch_one('SELECT 1 FROM attendance WHERE subject_id=:sub AND student_id=:stu AND date=:d AND hour=:h', values={'sub': ses['subject_id'],'stu': studentId,'d': ses['date'],'h': ses['hour']})
if exists:
raise HTTPException(409, 'Attendance already marked')
# insert
await db.execute('INSERT INTO attendance(subject_id, staff_id, date, hour, student_id, status) VALUES(:sub, :staff, :d, :h, :stu, :status)', values={'sub': ses['subject_id'],'staff': user.staff_id,'d': ses['date'],'h': ses['hour'],'stu': studentId,'status': status})
return { 'status':'ok' }
Testing Tips
- Use unit tests to test each validator independently.
- Simulate edge cases: staff not assigned, wrong date, duplicate submissions.
- Log and audit all failed validation attempts for security review.
No comments:
Post a Comment