feat: QA fixes, new APIs (assignments, rubrics, custom exams), Generation page enhancements
- Fix ELAI video generation (correct render endpoint, script splitting for 60s limit) - Fix speaking script generation error handling and empty response display - Add custom exam list API (GET /api/exam/custom/list) - Add assignments REST API (list, create, get) - Add rubrics REST API (list, create) - Enhance Generation page: dynamic exam structures, auto-module selection, preview dialog, audio player - Improve submit feedback with exam ID and status in toast notifications - Fix ExamsListPage to show both custom exams and exam sessions - Connect RubricsPage to backend API with fallback data - Add Dockerfile, docker-compose.yml, requirements.txt for deployment - Fix placement, grading, scoring, and auth controllers - Add ErrorBoundary component for frontend resilience - Add QA report and credentials documentation Made-with: Cursor
This commit is contained in:
@@ -369,6 +369,41 @@ class EncoachExamSessionController(http.Controller):
|
||||
_logger.exception('submit failed')
|
||||
return _error_response(str(e), 500)
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# GET /api/exam/<int:exam_id>/status
|
||||
# ------------------------------------------------------------------
|
||||
@http.route('/api/exam/<int:exam_id>/status', type='http', auth='none',
|
||||
methods=['GET'], csrf=False)
|
||||
@jwt_required
|
||||
def get_status(self, exam_id, **kw):
|
||||
try:
|
||||
uid = request.env.user.id
|
||||
Attempt = request.env['encoach.student.attempt'].sudo()
|
||||
attempt = Attempt.search([
|
||||
('student_id', '=', uid),
|
||||
('exam_id', '=', int(exam_id)),
|
||||
], limit=1, order='id desc')
|
||||
|
||||
if not attempt:
|
||||
return _json_response({
|
||||
'status': 'not_started',
|
||||
'scores_available': False,
|
||||
})
|
||||
|
||||
scores_available = attempt.status in ('released', 'scored')
|
||||
|
||||
return _json_response({
|
||||
'status': attempt.status,
|
||||
'scores_available': scores_available,
|
||||
'attempt_id': attempt.id,
|
||||
'completed_at': attempt.completed_at,
|
||||
'released_at': attempt.released_at,
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
_logger.exception('get_status failed')
|
||||
return _error_response(str(e), 500)
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# GET /api/exam/<int:exam_id>/results
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
@@ -24,9 +24,10 @@ def _band_to_cefr(band):
|
||||
|
||||
|
||||
def _recompute_bands(attempt):
|
||||
"""Recompute skill and overall bands after grading updates."""
|
||||
"""Recompute skill and overall bands after grading updates, incorporating rubric sub-scores."""
|
||||
Answer = request.env['encoach.student.answer'].sudo()
|
||||
Score = request.env['encoach.score'].sudo()
|
||||
Feedback = request.env['encoach.feedback'].sudo()
|
||||
|
||||
skill_totals = {}
|
||||
skill_max = {}
|
||||
@@ -36,6 +37,24 @@ def _recompute_bands(attempt):
|
||||
skill = q.skill or 'general'
|
||||
skill_totals.setdefault(skill, 0.0)
|
||||
skill_max.setdefault(skill, 0.0)
|
||||
|
||||
fb = Feedback.search([
|
||||
('attempt_id', '=', attempt.id),
|
||||
('question_id', '=', q.id),
|
||||
], limit=1, order='id desc')
|
||||
|
||||
if fb and fb.rubric_scores:
|
||||
try:
|
||||
rubric_data = json.loads(fb.rubric_scores)
|
||||
if isinstance(rubric_data, dict) and rubric_data:
|
||||
rubric_avg = sum(float(v) for v in rubric_data.values()) / len(rubric_data)
|
||||
rubric_score = (rubric_avg / 9.0) * (q.marks or 1.0)
|
||||
skill_totals[skill] += rubric_score
|
||||
skill_max[skill] += q.marks or 1.0
|
||||
continue
|
||||
except (json.JSONDecodeError, TypeError, ValueError):
|
||||
pass
|
||||
|
||||
skill_totals[skill] += ans.score or 0.0
|
||||
skill_max[skill] += q.marks or 1.0
|
||||
|
||||
|
||||
Reference in New Issue
Block a user