Update Saqel Platform: 2026-09-12 13:38:49

This commit is contained in:
Hamza-Ayed
2026-09-12 13:38:49 +03:00
parent 1f31369893
commit f4d4f02853
34 changed files with 9136 additions and 386 deletions
@@ -71,6 +71,10 @@ class GuardianChildModel {
final double readinessScore;
final int examsPassed;
final int examsTotal;
final int errorTotalCount;
final int errorMasteredCount;
final int errorPendingCount;
final double errorMasteryRate;
GuardianChildModel({
required this.id,
@@ -83,6 +87,10 @@ class GuardianChildModel {
this.readinessScore = 0.0,
this.examsPassed = 0,
this.examsTotal = 0,
this.errorTotalCount = 0,
this.errorMasteredCount = 0,
this.errorPendingCount = 0,
this.errorMasteryRate = 100.0,
});
factory GuardianChildModel.fromJson(Map<String, dynamic> json) {
@@ -93,6 +101,31 @@ class GuardianChildModel {
? Map<String, dynamic>.from(json['metrics'])
: <String, dynamic>{};
final source = <String, dynamic>{...student, ...metrics, ...json};
final errorNotebook = metrics['error_notebook'] is Map
? Map<String, dynamic>.from(metrics['error_notebook'])
: <String, dynamic>{};
final errTotal = errorNotebook['total_errors'] is int
? errorNotebook['total_errors'] as int
: (source['errors_total'] is int
? source['errors_total'] as int
: int.tryParse(errorNotebook['total_errors']?.toString() ?? source['errors_total']?.toString() ?? '0') ?? 0);
final errMastered = errorNotebook['mastered_count'] is int
? errorNotebook['mastered_count'] as int
: (source['errors_mastered'] is int
? source['errors_mastered'] as int
: int.tryParse(errorNotebook['mastered_count']?.toString() ?? source['errors_mastered']?.toString() ?? '0') ?? 0);
final errPending = errorNotebook['pending_count'] is int
? errorNotebook['pending_count'] as int
: (source['errors_pending'] is int
? source['errors_pending'] as int
: int.tryParse(errorNotebook['pending_count']?.toString() ?? source['errors_pending']?.toString() ?? '0') ?? 0);
final errRate = errorNotebook['mastery_percentage'] != null
? double.tryParse(errorNotebook['mastery_percentage'].toString()) ?? 100.0
: (source['errors_mastery_rate'] != null
? double.tryParse(source['errors_mastery_rate'].toString()) ?? 100.0
: (errTotal > 0 ? (errMastered / errTotal) * 100 : 100.0));
return GuardianChildModel(
id: source['id'] is int ? source['id'] : int.tryParse(source['id']?.toString() ?? '0') ?? 0,
uuid: source['uuid']?.toString() ?? '',
@@ -106,6 +139,10 @@ class GuardianChildModel {
: (source['tawjihi_readiness_score'] != null ? double.tryParse(source['tawjihi_readiness_score'].toString()) ?? 0.0 : 0.0),
examsPassed: source['exams_passed_count'] is int ? source['exams_passed_count'] : int.tryParse(source['exams_passed_count']?.toString() ?? '0') ?? 0,
examsTotal: source['exams_total_count'] is int ? source['exams_total_count'] : int.tryParse(source['exams_total_count']?.toString() ?? '0') ?? 0,
errorTotalCount: errTotal,
errorMasteredCount: errMastered,
errorPendingCount: errPending,
errorMasteryRate: errRate,
);
}
}
@@ -210,11 +210,12 @@ class ExamRepository {
ExamRepository({ApiClient? api}) : _api = api ?? ApiClient();
Future<List<ExamModel>> getExams({int? courseId, int? lessonId, String? scope}) async {
Future<List<ExamModel>> getExams({int? courseId, int? lessonId, String? scope, String? subjectCode}) async {
final params = <String, dynamic>{};
if (courseId != null) params['course_id'] = courseId;
if (lessonId != null) params['lesson_id'] = lessonId;
if (scope != null) params['scope'] = scope;
if (subjectCode != null) params['subject_code'] = subjectCode;
final res = await _api.get(AppConfig.examsEndpoint, queryParams: params);
if (res is Map && res['data'] is List) {
@@ -225,8 +226,12 @@ class ExamRepository {
return [];
}
Future<ExamModel> getExamDetails(int examId) async {
final res = await _api.get('${AppConfig.examsEndpoint}/$examId');
Future<ExamModel> getExamDetails(int examId, {String? subjectCode}) async {
final params = <String, dynamic>{};
if (subjectCode != null && subjectCode.isNotEmpty) {
params['subject_code'] = subjectCode;
}
final res = await _api.get('${AppConfig.examsEndpoint}/$examId', queryParams: params.isNotEmpty ? params : null);
if (res is Map && res['data'] is Map) {
return ExamModel.fromJson(Map<String, dynamic>.from(res['data']));
}
File diff suppressed because it is too large Load Diff
@@ -7,39 +7,122 @@ class ErrorNotebookRepository {
ErrorNotebookRepository({ApiClient? api}) : _api = api ?? ApiClient();
Future<Map<String, dynamic>> getErrorNotebook({String? subject, String? status}) async {
final decoded = await _api.get(
'/api/student/error-notebook',
queryParams: {
if (subject != null && subject.isNotEmpty) 'subject': subject,
if (status != null && status.isNotEmpty) 'status': status,
},
);
final data = Map<String, dynamic>.from(decoded['data'] as Map? ?? const {});
final items = (data['items'] as List? ?? const [])
.map((item) => ErrorNotebookItem.fromJson(Map<String, dynamic>.from(item as Map)))
.toList();
return {
'summary': ErrorNotebookSummary.fromJson(Map<String, dynamic>.from(data['summary'] as Map? ?? const {})),
'items': items,
};
try {
final decoded = await _api.get(
'/api/student/error-notebook',
queryParams: {
if (subject != null && subject.isNotEmpty) 'subject': subject,
if (status != null && status.isNotEmpty) 'status': status,
},
);
final data = Map<String, dynamic>.from(decoded['data'] as Map? ?? const {});
final items = (data['items'] as List? ?? const [])
.map((item) => ErrorNotebookItem.fromJson(Map<String, dynamic>.from(item as Map)))
.toList();
return {
'summary': ErrorNotebookSummary.fromJson(Map<String, dynamic>.from(data['summary'] as Map? ?? const {})),
'items': items,
};
} catch (_) {
return {
'summary': ErrorNotebookSummary(
totalErrors: 0,
masteredCount: 0,
pendingCount: 0,
masteryPercentage: 100.0,
bySubject: {},
),
'items': <ErrorNotebookItem>[],
};
}
}
Future<Map<String, dynamic>> getChildErrorNotebook(int studentId) async {
try {
final decoded = await _api.get('/api/guardian/children/$studentId/error-notebook');
final data = Map<String, dynamic>.from(decoded['data'] as Map? ?? const {});
final items = (data['items'] as List? ?? const [])
.map((item) => ErrorNotebookItem.fromJson(Map<String, dynamic>.from(item as Map)))
.toList();
return {
'summary': ErrorNotebookSummary.fromJson(Map<String, dynamic>.from(data['summary'] as Map? ?? const {})),
'items': items,
};
} catch (_) {
return {
'summary': ErrorNotebookSummary(
totalErrors: 0,
masteredCount: 0,
pendingCount: 0,
masteryPercentage: 100.0,
bySubject: {},
),
'items': <ErrorNotebookItem>[],
};
}
}
Future<String?> logError({
required String subjectId,
required String subjectName,
required String topicName,
required String sourceType,
required String questionText,
required String studentWrongAnswer,
required String correctAnswer,
String? hint,
String errorCategory = 'conceptual',
int? lessonId,
List<String>? options,
}) async {
try {
final decoded = await _api.post(
'/api/student/error-notebook/log',
body: {
'subject_id': subjectId,
'subject_name': subjectName,
'topic_name': topicName,
'source_type': sourceType,
'question_text': questionText,
'student_wrong_answer': studentWrongAnswer,
'correct_answer': correctAnswer,
'socratic_hint': hint ?? '',
'error_category': errorCategory,
if (lessonId != null) 'lesson_id': lessonId,
if (options != null) 'options': options,
},
);
if (decoded is Map && decoded['status'] == 'success') {
return decoded['uuid']?.toString();
}
} catch (_) {}
return null;
}
Future<List<RemedialQuestion>> getRemediationQuiz({required String errorUuid, required String topicName}) async {
final decoded = await _api.get(
'/api/student/error-notebook/remediation-quiz',
queryParams: {'error_uuid': errorUuid, 'topic_name': topicName},
);
final data = Map<String, dynamic>.from(decoded['data'] as Map? ?? const {});
return (data['questions'] as List? ?? const [])
.map((item) => RemedialQuestion.fromJson(Map<String, dynamic>.from(item as Map)))
.toList();
try {
final decoded = await _api.get(
'/api/student/error-notebook/remediation-quiz',
queryParams: {'error_uuid': errorUuid, 'topic_name': topicName},
);
final data = Map<String, dynamic>.from(decoded['data'] as Map? ?? const {});
return (data['questions'] as List? ?? const [])
.map((item) => RemedialQuestion.fromJson(Map<String, dynamic>.from(item as Map)))
.toList();
} catch (_) {
return [];
}
}
Future<bool> resolveError(String errorUuid) async {
final decoded = await _api.post(
'/api/student/error-notebook/resolve',
body: {'error_uuid': errorUuid},
);
return decoded is Map && decoded['status'] == 'success';
try {
final decoded = await _api.post(
'/api/student/error-notebook/resolve',
body: {'error_uuid': errorUuid},
);
return decoded is Map && decoded['status'] == 'success';
} catch (_) {
return false;
}
}
}