课程内容 - 重写全部 21 个 A1 单元的听读材料,把本单元理解词织进听/读文本, 单元内理解词复现率从约 20% 提升到约 77%(各单元 56–96%)。 - 修正 U01 房间号与机场大巴同为 thirty 的撞车(改为 17/30/40)。 - 校验器容差按级别读取(A1 为 8%),materials 覆盖率、字数、 选项子串等校验全部通过;course_content_test 通过。 黑夜模式 - app_theme 拆分明/暗两套调色板,AppColors 随亮度切换; main 用 theme/darkTheme/themeMode + builder 镜像已解析亮度; 主题偏好持久化到快照;进度页新增“外观主题”切换。 文档 - COURSE-PACK-JSON.md 更新 A1 词池覆盖(840/933)与材料复现约定。 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
121 lines
4.7 KiB
Dart
121 lines
4.7 KiB
Dart
class WritingCheckResult {
|
||
const WritingCheckResult({required this.complete, required this.message});
|
||
|
||
final bool complete;
|
||
final String message;
|
||
}
|
||
|
||
/// A deliberately small offline checker for teaching tasks. It checks only
|
||
/// the essential A0 information, accepts variable names/places, and never
|
||
/// claims to grade pronunciation or nuanced grammar.
|
||
class WritingFeedback {
|
||
const WritingFeedback._();
|
||
|
||
static String _normalize(String input) => input
|
||
.toLowerCase()
|
||
.replaceAll('’', "'")
|
||
.replaceAll('‘', "'")
|
||
.replaceAll(RegExp(r'\s+'), ' ')
|
||
.trim();
|
||
|
||
static WritingCheckResult check(
|
||
String lessonId,
|
||
String input, {
|
||
String? segmentId,
|
||
}) {
|
||
final text = _normalize(input);
|
||
final words = RegExp(
|
||
r"[a-z]+(?:'[a-z]+)?",
|
||
).allMatches(text).map((match) => match.group(0)!).toSet();
|
||
bool has(String word) => words.contains(_normalize(word));
|
||
|
||
bool hasPhrase(String phrase) => text.contains(_normalize(phrase));
|
||
|
||
bool hasAny(Iterable<String> choices) => choices.any(has);
|
||
final hasIntroduction =
|
||
hasPhrase("i'm") || hasPhrase('i am') || hasPhrase('my name is');
|
||
final hasItIs = hasPhrase("it's") || hasPhrase('it is');
|
||
|
||
final complete = switch (segmentId) {
|
||
'a0-04-a' => hasAll(words, ['zero', 'one', 'two', 'three']),
|
||
'a0-04-b' => hasAll(words, ['six', 'seven', 'eight']),
|
||
'a0-04-c' =>
|
||
RegExp(
|
||
r'\b(one|two|three|four|five|six|seven|eight|nine|zero)\b',
|
||
).allMatches(text).length >=
|
||
3,
|
||
'a0-08-a' => hasItIs && hasAny(['monday', 'tuesday', 'wednesday']),
|
||
'a0-08-b' =>
|
||
hasItIs && hasAny(['thursday', 'friday', 'saturday', 'sunday']),
|
||
'a0-08-c' => hasItIs && (has('oclock') || hasPhrase("o'clock")),
|
||
_ => switch (lessonId) {
|
||
'a0-01' => hasAny(['hello', 'hi']) && hasIntroduction,
|
||
'a0-02' =>
|
||
(hasPhrase('my name') || hasIntroduction) &&
|
||
RegExp(r'[a-z](?:[ -]?[a-z]){2,}').hasMatch(text),
|
||
'a0-03' => hasIntroduction && hasAny(['good', 'okay', 'tired']),
|
||
'a0-04' =>
|
||
RegExp(
|
||
r'\b(zero|one|two|three|four|five|six|seven|eight|nine|ten)\b',
|
||
).allMatches(text).length >=
|
||
3,
|
||
'a0-05' => hasItIs && hasAny(['book', 'pen', 'bag', 'key']),
|
||
'a0-06' => hasIntroduction && has('from') && words.length >= 3,
|
||
'a0-07' =>
|
||
hasPhrase('this is my') &&
|
||
hasAny(['mother', 'father', 'sister', 'brother', 'friend']),
|
||
'a0-08' =>
|
||
hasItIs &&
|
||
(hasAny([
|
||
'monday',
|
||
'tuesday',
|
||
'wednesday',
|
||
'thursday',
|
||
'friday',
|
||
'saturday',
|
||
'sunday',
|
||
]) ||
|
||
has('clock')),
|
||
'a0-09' => hasIntroduction && has('like') && words.length >= 3,
|
||
'a0-10' =>
|
||
hasIntroduction && has('from') && has('like') && has('water'),
|
||
_ => words.length >= 2,
|
||
},
|
||
};
|
||
|
||
if (complete) {
|
||
return const WritingCheckResult(
|
||
complete: true,
|
||
message: '这句已经表达完整。继续在对话里用一次吧。',
|
||
);
|
||
}
|
||
return WritingCheckResult(
|
||
complete: false,
|
||
message: _hintFor(segmentId ?? lessonId),
|
||
);
|
||
}
|
||
|
||
static bool hasAll(Set<String> words, Iterable<String> required) =>
|
||
required.every(words.contains);
|
||
|
||
static String _hintFor(String lessonId) => switch (lessonId) {
|
||
'a0-04-a' => '请写出 zero、one、two、three 四个数字。',
|
||
'a0-04-b' => '请写出 six、seven、eight 三个数字。',
|
||
'a0-04-c' => '用英文一个一个写出三个数字,例如 one-three-nine。',
|
||
'a0-08-a' => '用 It is / It’s 加 Monday、Tuesday 或 Wednesday。',
|
||
'a0-08-b' => '用 It is / It’s 加 Thursday、Friday、Saturday 或 Sunday。',
|
||
'a0-08-c' => '用 It is / It’s 加数字和 o’clock,例如 It’s three o’clock。',
|
||
'a0-01' => '试着同时写一句问候和姓名,例如:Hello. I’m …',
|
||
'a0-02' => '写出姓名并拼写一遍,例如 My name is Shen. S-H-E-N.',
|
||
'a0-03' => '用 I’m / I am 加上你的状态,例如 good 或 tired。',
|
||
'a0-04' => '用英文写出三个数字,例如 one-three-nine。',
|
||
'a0-05' => '用 It’s / It is 加一个物品,例如 a pen。',
|
||
'a0-06' => '用 I’m from … 写成一个完整句子。',
|
||
'a0-07' => '用 This is my … 介绍一位家人或朋友。',
|
||
'a0-08' => '用 It’s … 写一个星期或整点时间。',
|
||
'a0-09' => '用 I like … 写出一种喜好。',
|
||
'a0-10' => '分别写姓名、来自哪里和喜好三部分,喜好用 water:I like water.',
|
||
_ => '再补充一个完整英文句子。',
|
||
};
|
||
}
|