batches.py upsert_mark previously never advanced a submission or batch to
'complete', and never validated an award against the question's max.
- Reject (422) an awarded_marks that exceeds the question's max_marks — only when
a max is actually set (0/None = not-yet-scored AI/unmapped question, unvalidatable).
- _advance_completion: a submission with a mark for every markable (leaf) question
→ 'complete'; a batch whose every non-absent submission is complete → 'complete'.
Container questions and absent students are excluded; the helper only promotes,
never regresses, so it is safe on every upsert.
Adds test_upsert_mark_rejects_over_max and test_upsert_mark_completes_submission_and_batch.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>