133 lines
4.5 KiB
Dart
133 lines
4.5 KiB
Dart
import '../models.dart';
|
|
import 'answer_rules.dart';
|
|
|
|
/// One stage-assessment task, from the level's `assessment` JSON. Choice
|
|
/// tasks carry `choices` and `answerIndex`; open tasks (writing, speaking)
|
|
/// carry an `accept` rule in the language of `answer_rules.dart` and the
|
|
/// `guide` shown after a wrong answer.
|
|
class AssessmentTask {
|
|
const AssessmentTask({
|
|
required this.id,
|
|
required this.skill,
|
|
required this.prompt,
|
|
this.audio,
|
|
this.choices = const [],
|
|
this.answerIndex,
|
|
this.accept = const [],
|
|
this.guide = '',
|
|
});
|
|
|
|
factory AssessmentTask.fromJson(Map<String, dynamic> json) => AssessmentTask(
|
|
id: json['id'] as String,
|
|
skill: AssessmentSkill.values.byName(json['skill'] as String),
|
|
prompt: json['prompt'] as String? ?? '',
|
|
audio: json['audio'] as String?,
|
|
choices: [for (final choice in json['choices'] as List? ?? const []) '$choice'],
|
|
answerIndex: json['answerIndex'] as int?,
|
|
accept: [
|
|
for (final group in json['accept'] as List? ?? const [])
|
|
[for (final term in group as List) '$term'],
|
|
],
|
|
guide: json['guide'] as String? ?? '',
|
|
);
|
|
|
|
final String id;
|
|
final AssessmentSkill skill;
|
|
final String prompt;
|
|
final String? audio;
|
|
final List<String> choices;
|
|
final int? answerIndex;
|
|
final List<List<String>> accept;
|
|
final String guide;
|
|
}
|
|
|
|
extension AssessmentSkillLabel on AssessmentSkill {
|
|
String get label => switch (this) {
|
|
AssessmentSkill.listening => '听力',
|
|
AssessmentSkill.speaking => '口语',
|
|
AssessmentSkill.reading => '阅读',
|
|
AssessmentSkill.writing => '写作',
|
|
};
|
|
}
|
|
|
|
/// A stage-assessment pack. A replacement pack ([replaces] set) assesses the
|
|
/// same abilities as its original with different people, places and values,
|
|
/// and its results count toward the original.
|
|
class AssessmentPack {
|
|
const AssessmentPack({
|
|
required this.id,
|
|
required this.level,
|
|
required this.tasks,
|
|
this.replaces,
|
|
});
|
|
|
|
factory AssessmentPack.fromJson(Map<String, dynamic> json) => AssessmentPack(
|
|
id: json['id'] as String,
|
|
level: json['level'] as String? ?? '',
|
|
replaces: json['replaces'] as String?,
|
|
tasks: [
|
|
for (final task in json['tasks'] as List? ?? const [])
|
|
AssessmentTask.fromJson(task as Map<String, dynamic>),
|
|
],
|
|
);
|
|
|
|
final String id;
|
|
final String level;
|
|
final String? replaces;
|
|
final List<AssessmentTask> tasks;
|
|
|
|
List<AssessmentTask> forSkill(AssessmentSkill skill) =>
|
|
tasks.where((task) => task.skill == skill).toList();
|
|
}
|
|
|
|
/// Every loaded assessment pack, in file order; filled by `CourseRepository`.
|
|
final List<AssessmentPack> assessmentPacks = <AssessmentPack>[];
|
|
|
|
/// The A0 stage assessments, in the order they unlock.
|
|
List<AssessmentPack> get a0AssessmentPacks => [
|
|
for (final pack in assessmentPacks)
|
|
if (pack.level == 'A0' && pack.replaces == null) pack,
|
|
];
|
|
|
|
/// The A0 replacement packs.
|
|
List<AssessmentPack> get a0ReplacementPacks => [
|
|
for (final pack in assessmentPacks)
|
|
if (pack.level == 'A0' && pack.replaces != null) pack,
|
|
];
|
|
|
|
/// The replacement pack of [packId], if it has one.
|
|
AssessmentPack? replacementFor(String packId) =>
|
|
assessmentPacks.where((pack) => pack.replaces == packId).firstOrNull;
|
|
|
|
/// The pack whose results [packId] counts toward: the original of a
|
|
/// replacement pack, otherwise [packId] itself.
|
|
String canonicalAssessmentPackId(String packId) =>
|
|
assessmentPacks
|
|
.where((pack) => pack.id == packId)
|
|
.firstOrNull
|
|
?.replaces ??
|
|
packId;
|
|
|
|
/// Local check of an open task with the same rule engine as the lessons: it
|
|
/// accepts variable names and places, but requires the communicative
|
|
/// information the task names.
|
|
bool checkOpenAssessmentAnswer(AssessmentTask task, String input) =>
|
|
task.accept.isNotEmpty && matchesRule(input, task.accept);
|
|
|
|
/// What a correct answer to [task] looks like, shown when reviewing a
|
|
/// wrong answer after the assessment. [answer] is what the learner gave.
|
|
String assessmentAnswerGuide(AssessmentTask task, String answer) {
|
|
final index = task.answerIndex;
|
|
if (index != null && index >= 0 && index < task.choices.length) {
|
|
final correct = task.choices[index];
|
|
if (task.skill == AssessmentSkill.listening && answer == correct) {
|
|
return '正确答案:$correct(选对了,但作答前没有播放音频,所以不计分)';
|
|
}
|
|
return '正确答案:$correct';
|
|
}
|
|
if (task.guide.isNotEmpty) return task.guide;
|
|
return task.skill == AssessmentSkill.writing
|
|
? '需要写一个完整的英文句子。'
|
|
: '需要用完整英文回答题目要求。';
|
|
}
|