feat: complete kouyu_english app codebase, A0 specifications and .gitignore

This commit is contained in:
shen
2026-09-15 15:58:33 +08:00
parent b8072673d8
commit 37c86f7ecb
136 changed files with 19042 additions and 0 deletions
@@ -0,0 +1,93 @@
import 'models.dart';
class ReviewCheckResult {
const ReviewCheckResult({required this.complete, required this.message});
final bool complete;
final String message;
}
/// Checks only the minimum information a learner needs to produce in an A0
/// review. It intentionally accepts common contractions and variable slot
/// values instead of comparing an answer with one fixed sentence.
class ReviewFeedback {
const ReviewFeedback._();
static ReviewCheckResult check(ReviewItem item, String input) {
final text = input.toLowerCase().replaceAll('’', "'");
final tokens = RegExp(
r"[a-z]+(?:'[a-z]+)?",
).allMatches(text).map((match) => match.group(0)!).toSet();
bool has(String token) => tokens.contains(token);
bool phrase(String value) => text.contains(value);
bool hasAny(Iterable<String> values) => values.any(has);
final introduction =
phrase("i'm") || phrase('i am') || phrase('my name is');
final itIs = phrase("it's") || phrase('it is');
final numberWords = RegExp(
r'\b(zero|one|two|three|four|five|six|seven|eight|nine|ten)\b',
).allMatches(text).length;
final complete = switch (item.id) {
'A0-P01' => introduction && tokens.length >= 2,
'A0-P02' => (phrase("what's your name") || phrase('what is your name')),
'A0-P03' => phrase('nice to meet you'),
'A0-P04' => phrase('how do you spell'),
'A0-P05' => phrase('how are you'),
'A0-P06' => introduction && hasAny(['good', 'okay', 'tired']),
'A0-P07' =>
phrase("what's your phone number") ||
phrase('what is your phone number'),
'A0-P08' =>
(phrase('my number is') || numberWords >= 3) && numberWords >= 3,
'A0-P09' => phrase("what's this") || phrase('what is this'),
'A0-P10' => itIs && hasAny(['book', 'pen', 'bag', 'key']),
'A0-P11' => phrase('where are you from'),
'A0-P12' => introduction && has('from') && tokens.length >= 3,
'A0-P13' => phrase('this is my') && tokens.length >= 4,
'A0-P14' => phrase('what day is it'),
'A0-P15' =>
itIs &&
hasAny([
'monday',
'tuesday',
'wednesday',
'thursday',
'friday',
'saturday',
'sunday',
]),
'A0-P16' => phrase('what time is it'),
'A0-P17' =>
itIs &&
(has('clock') ||
has("o'clock") ||
has('oclock') ||
phrase("o'clock")),
'A0-P18' => introduction && has('like') && tokens.length >= 3,
'A0-P19' => phrase('do you like'),
'A0-P20' =>
phrase('please say that again') || phrase('please speak slowly'),
_ when item.id.startsWith('A0-W') => tokens.contains(
item.target.toLowerCase(),
),
_ => tokens.length >= 2,
};
return ReviewCheckResult(
complete: complete,
message: complete ? '表达已完成,可以进入下一次复习安排。' : _hint(item.id),
);
}
static String _hint(String id) => switch (id) {
'A0-P01' => '用 I’m / I am 或 My name is 介绍一个名字。',
'A0-P02' => '试着问:What’s your name?',
'A0-P04' => '问对方怎样拼写:How do you spell …?',
'A0-P08' => '说出至少三个英文数字,也可用 My number is … 开头。',
'A0-P10' => '用 It’s / It is 加上一个物品。',
'A0-P12' => '用 I’m from … 说出一个地点。',
'A0-P17' => '用 It’s … o’clock 说整点时间。',
'A0-P18' => '用 I like … 说一项喜好。',
_ => '再补充本次目标中的关键英文词或句型。',
};
}