mathpulse-api-v3test / services /wri_service.py
github-actions[bot]
๐Ÿš€ Auto-deploy backend from GitHub (552770f)
25943d2
"""
WRI CLASSIFICATION โ€” Prevention-First 5-Band System
This module implements at-risk classification based on DepEd DO No. 8, s. 2015
(Policy Guidelines on Classroom Assessment for the K to 12 Basic Education Program).
Official Passing Grade: 75 (Did Not Meet Expectations = below 75)
Prevention-first WRI thresholds (DepEd 75 is the FLOOR, not the trigger):
- WRI >= 88 โ†’ safe (On Track โ€” no intervention needed)
- WRI >= 80 โ†’ watch (Slight decline โ€” system adjusts difficulty)
- WRI >= 75 โ†’ intervene (Approaching DepEd threshold โ€” teacher notified)
- WRI >= 68 โ†’ critical (Urgent โ€” structured intervention required)
- WRI < 68 โ†’ at_risk (Near or below DepEd failing mark)
IMPORTANT: WRI is a SUPPORT TOOL, not a replacement for teacher judgment.
Final academic decisions must still be made by the teacher in accordance
with official DepEd grading policies.
"""
from typing import Optional
DEFAULT_WEIGHTS = {"w1": 0.30, "w2": 0.40, "w3": 0.30}
WEIGHT_TOLERANCE = 0.001
def compute_wri(
d: Optional[float],
g: Optional[float],
p: Optional[float],
weights: dict = None,
) -> dict:
"""
Computes the Weighted Risk Index (WRI) and returns classification.
Args:
d: Diagnostic baseline score (0-100), set once after initial assessment
g: External grades average (0-100), from teacher-imported class records
p: System performance average (0-100), from quiz/activity scores
weights: w1 (diagnostic), w2 (external), w3 (system) โ€” must sum to 1.0
Returns:
dict with keys:
wri: float (rounded to 2 decimal places) or None if D is missing
risk_status: 'safe' | 'watch' | 'intervene' | 'critical' | 'at_risk' | 'pending_assessment'
inputs: {'D': float, 'G': float, 'P': float} (actual values used, after defaults)
g_fallback: bool (True if G defaulted to D)
p_fallback: bool (True if P defaulted to D)
"""
if weights is None:
weights = DEFAULT_WEIGHTS.copy()
w1 = weights.get("w1", DEFAULT_WEIGHTS["w1"])
w2 = weights.get("w2", DEFAULT_WEIGHTS["w2"])
w3 = weights.get("w3", DEFAULT_WEIGHTS["w3"])
# Validate weights sum to 1.0
if abs((w1 + w2 + w3) - 1.0) > WEIGHT_TOLERANCE:
raise ValueError(f"Weights must sum to 1.0, got w1={w1}, w2={w2}, w3={w3}")
# Cannot compute without diagnostic baseline
if d is None:
return {
"wri": None,
"risk_status": "pending_assessment",
"inputs": {"D": None, "G": g, "P": p},
"g_fallback": False,
"p_fallback": False,
}
# Apply defaults: missing G and/or P default to D
g_fallback = g is None
p_fallback = p is None
g_val = g if g is not None else d
p_val = p if p is not None else d
# Compute WRI
wri = round((w1 * d) + (w2 * g_val) + (w3 * p_val), 2)
# 5-band prevention-first classification
if wri >= 88:
status = "safe"
elif wri >= 80:
status = "watch"
elif wri >= 75:
status = "intervene"
elif wri >= 68:
status = "critical"
else:
status = "at_risk"
return {
"wri": wri,
"risk_status": status,
"inputs": {"D": d, "G": g_val, "P": p_val},
"g_fallback": g_fallback,
"p_fallback": p_fallback,
}