Files

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
? '需要写一个完整的英文句子。'
: '需要用完整英文回答题目要求。';
}