244 lines
8.7 KiB
Dart
244 lines
8.7 KiB
Dart
|
|
import 'speaking_scenario.dart';
|
||
|
|
import 'pronunciation_assessment.dart';
|
||
|
|
|
||
|
|
class SpeakingStats {
|
||
|
|
final int totalSessions;
|
||
|
|
final int totalMinutes;
|
||
|
|
final double averageScore;
|
||
|
|
final Map<SpeakingScenario, int> scenarioStats;
|
||
|
|
final Map<SpeakingDifficulty, int> difficultyStats;
|
||
|
|
final List<SpeakingProgressData> progressData;
|
||
|
|
final SpeakingSkillAnalysis skillAnalysis;
|
||
|
|
final DateTime lastUpdated;
|
||
|
|
|
||
|
|
const SpeakingStats({
|
||
|
|
required this.totalSessions,
|
||
|
|
required this.totalMinutes,
|
||
|
|
required this.averageScore,
|
||
|
|
required this.scenarioStats,
|
||
|
|
required this.difficultyStats,
|
||
|
|
required this.progressData,
|
||
|
|
required this.skillAnalysis,
|
||
|
|
required this.lastUpdated,
|
||
|
|
});
|
||
|
|
|
||
|
|
factory SpeakingStats.fromJson(Map<String, dynamic> json) {
|
||
|
|
final scenarioStatsJson = json['scenarioStats'] as Map<String, dynamic>? ?? {};
|
||
|
|
final scenarioStats = <SpeakingScenario, int>{};
|
||
|
|
for (final scenario in SpeakingScenario.values) {
|
||
|
|
final v = scenarioStatsJson[scenario.name];
|
||
|
|
scenarioStats[scenario] = (v is num) ? v.toInt() : 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
final difficultyStatsLegacy = json['difficultyStats'] as Map<String, dynamic>?;
|
||
|
|
final levelStatsBackend = json['stats_by_level'] as Map<String, dynamic>?;
|
||
|
|
final difficultyStats = <SpeakingDifficulty, int>{};
|
||
|
|
if (difficultyStatsLegacy != null) {
|
||
|
|
for (final difficulty in SpeakingDifficulty.values) {
|
||
|
|
final v = difficultyStatsLegacy[difficulty.name];
|
||
|
|
difficultyStats[difficulty] = (v is num) ? v.toInt() : 0;
|
||
|
|
}
|
||
|
|
} else {
|
||
|
|
for (final difficulty in SpeakingDifficulty.values) {
|
||
|
|
final key = {
|
||
|
|
SpeakingDifficulty.beginner: 'beginner',
|
||
|
|
SpeakingDifficulty.elementary: 'elementary',
|
||
|
|
SpeakingDifficulty.intermediate: 'intermediate',
|
||
|
|
SpeakingDifficulty.upperIntermediate: 'upper_intermediate',
|
||
|
|
SpeakingDifficulty.advanced: 'advanced',
|
||
|
|
}[difficulty]!;
|
||
|
|
final entry = levelStatsBackend?[key];
|
||
|
|
int count = 0;
|
||
|
|
if (entry is Map) {
|
||
|
|
final c = entry['count'];
|
||
|
|
if (c is num) count = c.toInt();
|
||
|
|
}
|
||
|
|
difficultyStats[difficulty] = count;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
final avgScores = json['average_scores'] as Map<String, dynamic>? ?? {};
|
||
|
|
final averageScore = (json['averageScore'] as num?)?.toDouble() ?? (avgScores['overall'] as num?)?.toDouble() ?? 0.0;
|
||
|
|
|
||
|
|
final skillAnalysisJson = json['skillAnalysis'] as Map<String, dynamic>?;
|
||
|
|
final skillAnalysis = skillAnalysisJson != null
|
||
|
|
? SpeakingSkillAnalysis.fromJson(skillAnalysisJson)
|
||
|
|
: SpeakingSkillAnalysis(
|
||
|
|
criteriaScores: {
|
||
|
|
PronunciationCriteria.accuracy: (avgScores['accuracy'] as num?)?.toDouble() ?? 0.0,
|
||
|
|
PronunciationCriteria.fluency: (avgScores['fluency'] as num?)?.toDouble() ?? 0.0,
|
||
|
|
PronunciationCriteria.completeness: (avgScores['completeness'] as num?)?.toDouble() ?? 0.0,
|
||
|
|
PronunciationCriteria.prosody: (avgScores['prosody'] as num?)?.toDouble() ?? 0.0,
|
||
|
|
},
|
||
|
|
commonErrors: {},
|
||
|
|
strengths: const [],
|
||
|
|
weaknesses: const [],
|
||
|
|
recommendations: const [],
|
||
|
|
improvementRate: 0.0,
|
||
|
|
lastAnalyzed: DateTime.now(),
|
||
|
|
);
|
||
|
|
|
||
|
|
return SpeakingStats(
|
||
|
|
totalSessions: (json['totalSessions'] as int?) ?? (json['total_records'] as num?)?.toInt() ?? 0,
|
||
|
|
totalMinutes: (json['totalMinutes'] as int?) ?? (json['total_duration'] as num?)?.toInt() ?? 0,
|
||
|
|
averageScore: averageScore,
|
||
|
|
scenarioStats: scenarioStats,
|
||
|
|
difficultyStats: difficultyStats,
|
||
|
|
progressData: (json['progressData'] as List<dynamic>? ?? [])
|
||
|
|
.map((e) => SpeakingProgressData.fromJson(e as Map<String, dynamic>))
|
||
|
|
.toList(),
|
||
|
|
skillAnalysis: skillAnalysis,
|
||
|
|
lastUpdated: (json['lastUpdated'] is String)
|
||
|
|
? DateTime.parse(json['lastUpdated'] as String)
|
||
|
|
: DateTime.now(),
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
Map<String, dynamic> toJson() {
|
||
|
|
final scenarioStatsJson = <String, int>{};
|
||
|
|
for (final entry in scenarioStats.entries) {
|
||
|
|
scenarioStatsJson[entry.key.name] = entry.value;
|
||
|
|
}
|
||
|
|
|
||
|
|
final difficultyStatsJson = <String, int>{};
|
||
|
|
for (final entry in difficultyStats.entries) {
|
||
|
|
difficultyStatsJson[entry.key.name] = entry.value;
|
||
|
|
}
|
||
|
|
|
||
|
|
return {
|
||
|
|
'totalSessions': totalSessions,
|
||
|
|
'totalMinutes': totalMinutes,
|
||
|
|
'averageScore': averageScore,
|
||
|
|
'scenarioStats': scenarioStatsJson,
|
||
|
|
'difficultyStats': difficultyStatsJson,
|
||
|
|
'progressData': progressData.map((e) => e.toJson()).toList(),
|
||
|
|
'skillAnalysis': skillAnalysis.toJson(),
|
||
|
|
'lastUpdated': lastUpdated.toIso8601String(),
|
||
|
|
};
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
class SpeakingProgressData {
|
||
|
|
final DateTime date;
|
||
|
|
final double averageScore;
|
||
|
|
final int sessionCount;
|
||
|
|
final int totalMinutes;
|
||
|
|
final Map<PronunciationCriteria, double> criteriaScores;
|
||
|
|
|
||
|
|
const SpeakingProgressData({
|
||
|
|
required this.date,
|
||
|
|
required this.averageScore,
|
||
|
|
required this.sessionCount,
|
||
|
|
required this.totalMinutes,
|
||
|
|
required this.criteriaScores,
|
||
|
|
});
|
||
|
|
|
||
|
|
factory SpeakingProgressData.fromJson(Map<String, dynamic> json) {
|
||
|
|
final criteriaScoresJson = json['criteriaScores'] as Map<String, dynamic>? ?? {};
|
||
|
|
final criteriaScores = <PronunciationCriteria, double>{};
|
||
|
|
for (final criteria in PronunciationCriteria.values) {
|
||
|
|
criteriaScores[criteria] = (criteriaScoresJson[criteria.name] as num?)?.toDouble() ?? 0.0;
|
||
|
|
}
|
||
|
|
|
||
|
|
return SpeakingProgressData(
|
||
|
|
date: DateTime.parse(json['date'] as String),
|
||
|
|
averageScore: (json['averageScore'] as num).toDouble(),
|
||
|
|
sessionCount: (json['sessionCount'] as num).toInt(),
|
||
|
|
totalMinutes: (json['totalMinutes'] as num).toInt(),
|
||
|
|
criteriaScores: criteriaScores,
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
Map<String, dynamic> toJson() {
|
||
|
|
final criteriaScoresJson = <String, double>{};
|
||
|
|
for (final entry in criteriaScores.entries) {
|
||
|
|
criteriaScoresJson[entry.key.name] = entry.value;
|
||
|
|
}
|
||
|
|
|
||
|
|
return {
|
||
|
|
'date': date.toIso8601String(),
|
||
|
|
'averageScore': averageScore,
|
||
|
|
'sessionCount': sessionCount,
|
||
|
|
'totalMinutes': totalMinutes,
|
||
|
|
'criteriaScores': criteriaScoresJson,
|
||
|
|
};
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
class SpeakingSkillAnalysis {
|
||
|
|
final Map<PronunciationCriteria, double> criteriaScores;
|
||
|
|
final Map<String, int> commonErrors;
|
||
|
|
final List<String> strengths;
|
||
|
|
final List<String> weaknesses;
|
||
|
|
final List<String> recommendations;
|
||
|
|
final double improvementRate; // 改进速度
|
||
|
|
final DateTime lastAnalyzed;
|
||
|
|
|
||
|
|
const SpeakingSkillAnalysis({
|
||
|
|
required this.criteriaScores,
|
||
|
|
required this.commonErrors,
|
||
|
|
required this.strengths,
|
||
|
|
required this.weaknesses,
|
||
|
|
required this.recommendations,
|
||
|
|
required this.improvementRate,
|
||
|
|
required this.lastAnalyzed,
|
||
|
|
});
|
||
|
|
|
||
|
|
factory SpeakingSkillAnalysis.fromJson(Map<String, dynamic> json) {
|
||
|
|
final criteriaScoresJson = json['criteriaScores'] as Map<String, dynamic>? ?? {};
|
||
|
|
final criteriaScores = <PronunciationCriteria, double>{};
|
||
|
|
for (final criteria in PronunciationCriteria.values) {
|
||
|
|
criteriaScores[criteria] = (criteriaScoresJson[criteria.name] as num?)?.toDouble() ?? 0.0;
|
||
|
|
}
|
||
|
|
|
||
|
|
return SpeakingSkillAnalysis(
|
||
|
|
criteriaScores: criteriaScores,
|
||
|
|
commonErrors: Map<String, int>.from(json['commonErrors'] ?? {}),
|
||
|
|
strengths: List<String>.from(json['strengths'] ?? []),
|
||
|
|
weaknesses: List<String>.from(json['weaknesses'] ?? []),
|
||
|
|
recommendations: List<String>.from(json['recommendations'] ?? []),
|
||
|
|
improvementRate: (json['improvementRate'] as num?)?.toDouble() ?? 0.0,
|
||
|
|
lastAnalyzed: (json['lastAnalyzed'] is String)
|
||
|
|
? DateTime.parse(json['lastAnalyzed'] as String)
|
||
|
|
: DateTime.now(),
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
Map<String, dynamic> toJson() {
|
||
|
|
final criteriaScoresJson = <String, double>{};
|
||
|
|
for (final entry in criteriaScores.entries) {
|
||
|
|
criteriaScoresJson[entry.key.name] = entry.value;
|
||
|
|
}
|
||
|
|
|
||
|
|
return {
|
||
|
|
'criteriaScores': criteriaScoresJson,
|
||
|
|
'commonErrors': commonErrors,
|
||
|
|
'strengths': strengths,
|
||
|
|
'weaknesses': weaknesses,
|
||
|
|
'recommendations': recommendations,
|
||
|
|
'improvementRate': improvementRate,
|
||
|
|
'lastAnalyzed': lastAnalyzed.toIso8601String(),
|
||
|
|
};
|
||
|
|
}
|
||
|
|
|
||
|
|
String get overallLevel {
|
||
|
|
final averageScore = criteriaScores.values.reduce((a, b) => a + b) / criteriaScores.length;
|
||
|
|
if (averageScore >= 90) return '优秀';
|
||
|
|
if (averageScore >= 80) return '良好';
|
||
|
|
if (averageScore >= 70) return '中等';
|
||
|
|
if (averageScore >= 60) return '及格';
|
||
|
|
return '需要改进';
|
||
|
|
}
|
||
|
|
|
||
|
|
PronunciationCriteria get strongestSkill {
|
||
|
|
return criteriaScores.entries
|
||
|
|
.reduce((a, b) => a.value > b.value ? a : b)
|
||
|
|
.key;
|
||
|
|
}
|
||
|
|
|
||
|
|
PronunciationCriteria get weakestSkill {
|
||
|
|
return criteriaScores.entries
|
||
|
|
.reduce((a, b) => a.value < b.value ? a : b)
|
||
|
|
.key;
|
||
|
|
}
|
||
|
|
}
|