2.8 KiB
Strong Grading Implementation Notes
Safety rule
This patch is additive and backward-compatible. Existing historical semester scores remain displayed from stored semester_scores values and are labeled as legacy by metadata. The new strong-grading machinery is present, but strong-mode finalization only activates when strong_grading_enabled is enabled through configuration.
What changed
Legacy display protection
semester_scores now supports:
calculation_modecalculation_policy_versionsnapshot_id
Existing rows are backfilled as:
calculation_mode = legacycalculation_policy_version = legacy_v1
No historical score is recalculated by this migration.
Score safety columns
The score tables now support:
max_pointsstatusexcused_reasonlocked_atlocked_by
Existing numeric score rows are marked scored. Existing blank rows are marked pending, not missing, so old data is not punished by the new policy.
Validation
A central ScoreValueValidator rejects impossible scores before storage. The default max remains 100 to preserve old behavior until item-level max points are configured.
Attendance grace
Attendance now names the one-absence grace policy explicitly instead of hiding it in (total_days + 1 - absences) / total_days. The result is intentionally equivalent.
Strong-mode lock validation
GradingLockService now checks the policy mode before locking. In legacy mode, behavior remains compatible. In strong mode, pending scores and invalid score ranges block locking.
Snapshot infrastructure
semester_score_snapshots and SemesterScoreSnapshotService were added. Strong-mode finalized scores can store calculation inputs and outputs for auditability.
Display resolver
GradeCalculationDisplayResolver resolves whether a row should be shown as Legacy Calculation or Strong Calculation.
Configuration gates
Strong mode is off by default.
Suggested configuration values:
strong_grading_enabled = false
strong_grading_class_sections = *
When enabled:
strong_grading_enabled = true
strong_grading_class_sections = 12,15,20
or all sections:
strong_grading_class_sections = *
What is intentionally not done yet
This patch does not globally replace the legacy PTAP formula. That would change grade math. The legacy formula remains the default until strong grading is intentionally activated for a class section.
This patch does not silently recalculate historical records. That would be reckless, so naturally we avoided it.
Verification
PHP syntax checks passed for the modified and new PHP files.
PHPUnit could not run in this sandbox because the PHP runtime is missing required extensions: dom, mbstring, xml, and xmlwriter.