getAcademicPerformance method
Fetches academic performance (scores) for all semesters.
Returns a list of SemesterScoreDto ordered from most recent to oldest, each containing individual course scores and semester summary statistics.
Implementation
Future<List<SemesterScoreDto>> getAcademicPerformance() async {
final response = await _studentQueryDio.get(
'QryScore.jsp',
queryParameters: {'format': '-2'},
);
final document = parse(response.data);
// Semester labels are in submit button values: "114 學年度 第 1 學期 (2025 - Fall)"
final semesterPattern = RegExp(r'(\d+)\s*學年度\s*第\s*(\d+)\s*學期');
final semesterButtons = document.querySelectorAll("input[type='submit']");
final semesterMatches = semesterButtons
.map((btn) => semesterPattern.firstMatch(btn.attributes['value'] ?? ''))
.nonNulls
.toList();
final tables = document.querySelectorAll('table');
final results = <SemesterScoreDto>[];
for (var i = 0; i < tables.length && i < semesterMatches.length; i++) {
final match = semesterMatches[i];
final semester = (
year: int.parse(match.group(1)!),
term: int.parse(match.group(2)!),
);
final rows = tables[i].querySelectorAll('tr');
final scores = <ScoreDto>[];
double? average;
double? conduct;
double? totalCredits;
double? creditsPassed;
String? note;
// Skip header row; data rows have 9+ cells, summary rows have 1-2
for (final row in rows.skip(1)) {
final cells = row.querySelectorAll('th, td');
if (cells.length >= 9) {
final scoreText = _parseCellText(cells[7]);
final (scoreValue, status) = _parseScore(scoreText);
scores.add((
number: _parseCellText(cells[0]),
courseCode: _parseCellText(cells[4]),
score: scoreValue,
status: status,
));
} else if (cells.length == 2) {
final label = cells[0].text;
final value = _parseCellText(cells[1]);
if (label.contains('Average')) {
average = double.tryParse(value ?? '');
} else if (label.contains('Conduct')) {
conduct = double.tryParse(value ?? '');
} else if (label.contains('Total Credits')) {
totalCredits = double.tryParse(value ?? '');
} else if (label.contains('Credits Passed')) {
creditsPassed = double.tryParse(value ?? '');
} else if (label.contains('Note')) {
note = value;
}
}
}
results.add((
semester: semester,
scores: scores,
average: average,
conduct: conduct,
totalCredits: totalCredits,
creditsPassed: creditsPassed,
note: note,
));
}
return results;
}