from __future__ import annotations from dataclasses import dataclass from typing import Literal, TypedDict RiskLevel = Literal["Low", "Moderate", "High", "Critical"] ENGAGEMENT_ATTENDANCE_WEIGHT = 0.4 ENGAGEMENT_HOMEWORK_WEIGHT = 0.4 ENGAGEMENT_GRADE_WEIGHT = 0.2 class RiskAssessment(TypedDict): """Structured risk output for a single student.""" attendance_risk: RiskLevel academic_risk: RiskLevel homework_risk: RiskLevel behavior_risk: RiskLevel engagement_score: float engagement_risk: RiskLevel overall_risk: RiskLevel _RISK_ORDER: dict[RiskLevel, int] = { "Low": 0, "Moderate": 1, "High": 2, "Critical": 3, } @dataclass(frozen=True) class StudentMetrics: """Input metrics used to calculate student risk levels. Scores are expected on a 0-100 scale, while behavior incidents are a non-negative count over the relevant reporting period. """ attendance: float grades: float homework: float behavior_incidents: int def validate_percentage(value: float, field_name: str) -> None: """Validate that a percentage value is within the inclusive 0-100 range. Args: value: Percentage value to validate. field_name: Name of the field being validated. Raises: ValueError: If the value is less than 0 or greater than 100. """ if value < 0 or value > 100: raise ValueError( f"{field_name} must be between 0 and 100. Got {value}." ) def attendance_risk(attendance: float) -> RiskLevel: """Classify risk from an attendance percentage.""" if attendance >= 95: return "Low" if attendance >= 90: return "Moderate" if attendance >= 80: return "High" return "Critical" def academic_risk(grades: float) -> RiskLevel: """Classify risk from an academic grade percentage.""" if grades >= 90: return "Low" if grades >= 75: return "Moderate" if grades >= 60: return "High" return "Critical" def homework_risk(homework: float) -> RiskLevel: """Classify risk from a homework completion percentage.""" if homework >= 90: return "Low" if homework >= 75: return "Moderate" if homework >= 50: return "High" return "Critical" def behavior_risk(behavior_incidents: int) -> RiskLevel: """Classify risk from the number of behavior incidents.""" if behavior_incidents <= 0: return "Low" if behavior_incidents <= 2: return "Moderate" if behavior_incidents <= 5: return "High" return "Critical" def engagement_score(attendance: float, homework: float, grades: float) -> float: """Calculate the weighted engagement score. Formula: 0.4 * attendance + 0.4 * homework + 0.2 * grades. """ return round( (ENGAGEMENT_ATTENDANCE_WEIGHT * attendance) + (ENGAGEMENT_HOMEWORK_WEIGHT * homework) + (ENGAGEMENT_GRADE_WEIGHT * grades), 2, ) def engagement_risk(score: float) -> RiskLevel: """Classify risk from the weighted engagement score. Engagement risk uses dedicated thresholds: - Low: score >= 85 - Moderate: score >= 70 and score < 85 - High: score >= 55 and score < 70 - Critical: score < 55 """ if score >= 85: return "Low" if score >= 70: return "Moderate" if score >= 55: return "High" return "Critical" def overall_risk(*risks: RiskLevel) -> RiskLevel: """Return the highest risk level across all provided risk categories.""" return max(risks, key=lambda risk: _RISK_ORDER[risk]) def assess_student_risk( attendance: float, grades: float, homework: float, behavior_incidents: int, ) -> RiskAssessment: """Create a complete risk assessment for one student. Args: attendance: Attendance percentage from 0 to 100. grades: Academic grade percentage from 0 to 100. homework: Homework completion percentage from 0 to 100. behavior_incidents: Count of behavior incidents. Returns: A dictionary containing category risk levels, engagement score, engagement risk, and the overall highest risk level. """ validate_percentage(attendance, "attendance") validate_percentage(grades, "grades") validate_percentage(homework, "homework") if behavior_incidents < 0: raise ValueError("behavior_incidents must be non-negative.") attendance_level = attendance_risk(attendance) academic_level = academic_risk(grades) homework_level = homework_risk(homework) behavior_level = behavior_risk(behavior_incidents) score = engagement_score(attendance, homework, grades) engagement_level = engagement_risk(score) return { "attendance_risk": attendance_level, "academic_risk": academic_level, "homework_risk": homework_level, "behavior_risk": behavior_level, "engagement_score": score, "engagement_risk": engagement_level, "overall_risk": overall_risk( attendance_level, academic_level, homework_level, behavior_level, engagement_level, ), } def assess_metrics(metrics: StudentMetrics) -> RiskAssessment: """Create a risk assessment from a StudentMetrics instance.""" return assess_student_risk( attendance=metrics.attendance, grades=metrics.grades, homework=metrics.homework, behavior_incidents=metrics.behavior_incidents, )