Files
English/kouyu_english/lib/core/writing_feedback.dart
T
shenleiandClaude Opus 5 9ab08c25ef fix: 修正学习流程中的证据、复习与进度问题
- 独立尝试按课程要求校验内容,不再任意输入即算独立成功
- 复习节点改为学后第 1/3/7/14 天,之后每 30 天抽查;复习轮换情境
- 掌握状态按作答记录推导:需认识、可回忆、跨情境使用及最近两次无帮助
- 课程证据按实际用到的目标记录;跟读与课程对话只算辅助练习
- 复习成功不再降低已有状态
- 学完课程后进入下一节未完成课程;全部学完后引导巩固与阶段评估
- 新增 3 题基础定位,按结果设置起始课程
- 新增按课程开放的对话场景,首页推荐最少练习的场景
- 切换课程时清空上一课的步骤进度
- AI 返回 JSON 格式并按 JSON 回放历史;拼写校验忽略大小写与连字符;移除学习目标选择

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 18:52:01 +09:00

120 lines
4.7 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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'),
_ => 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 / Its 加 Monday、Tuesday 或 Wednesday。',
'a0-08-b' => '用 It is / Its 加 Thursday、Friday、Saturday 或 Sunday。',
'a0-08-c' => '用 It is / Its 加数字和 oclock,例如 Its three oclock。',
'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' => '用 Its / It is 加一个物品,例如 a pen。',
'a0-06' => '用 I’m from … 写成一个完整句子。',
'a0-07' => '用 This is my … 介绍一位家人或朋友。',
'a0-08' => '用 It’s … 写一个星期或整点时间。',
'a0-09' => '用 I like … 写出一种喜好。',
'a0-10' => '分别写姓名、来自哪里和一项喜好三部分。',
_ => '再补充一个完整英文句子。',
};
}