# 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_mode`
- `calculation_policy_version`
- `snapshot_id`

Existing rows are backfilled as:

- `calculation_mode = legacy`
- `calculation_policy_version = legacy_v1`

No historical score is recalculated by this migration.

### Score safety columns

The score tables now support:

- `max_points`
- `status`
- `excused_reason`
- `locked_at`
- `locked_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:

```text
strong_grading_enabled = false
strong_grading_class_sections = *
```

When enabled:

```text
strong_grading_enabled = true
strong_grading_class_sections = 12,15,20
```

or all sections:

```text
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`.
