fix: 输入校验忽略多个连续空格与大小写;统一中英文引号与空白规范化

This commit is contained in:
shenlei
2026-09-16 17:55:08 +09:00
parent 4d324903b4
commit d72959df70
10 changed files with 163 additions and 28 deletions
+18 -10
View File
@@ -1081,8 +1081,17 @@ const a0SegmentGrammarNotes = <String, String>{
String grammarNoteForSegment(String segmentId, String lessonId) =>
a0SegmentGrammarNotes[segmentId] ?? grammarNoteForLesson(lessonId);
String _normalizeDialogueInput(String response) => response
.toLowerCase()
.replaceAll('\u2019', "'")
.replaceAll('\u2018', "'")
.replaceAll('’', "'")
.replaceAll('‘', "'")
.replaceAll(RegExp(r'\s+'), ' ')
.trim();
bool matchesSegmentDialogue(String segmentId, int stage, String response) {
final text = response.toLowerCase().replaceAll('’', "'");
final text = _normalizeDialogueInput(response);
final segment = a0LessonSegments.values
.expand((segments) => segments)
.where((item) => item.id == segmentId)
@@ -1091,7 +1100,7 @@ bool matchesSegmentDialogue(String segmentId, int stage, String response) {
return false;
}
bool hasAny(Iterable<String> terms) =>
terms.any((term) => text.contains(term));
terms.any((term) => text.contains(_normalizeDialogueInput(term)));
// A phone-number report needs both the reporting frame and at least one
// spoken digit. Other segment turns need one reviewed, task-specific term.
if (segmentId == 'a0-04-c' && stage == 0) {
@@ -1102,7 +1111,7 @@ bool matchesSegmentDialogue(String segmentId, int stage, String response) {
}
bool matchesSegmentIndependent(String segmentId, String response) {
final text = response.toLowerCase().replaceAll('’', "'");
final text = _normalizeDialogueInput(response);
final segment = a0LessonSegments.values
.expand((segments) => segments)
.where((item) => item.id == segmentId)
@@ -1110,17 +1119,18 @@ bool matchesSegmentIndependent(String segmentId, String response) {
if (segment == null) return true;
if (segmentId == 'a0-04-a' || segmentId == 'a0-04-b') {
final count = segment.independentRequiredTerms
.where((term) => text.contains(term))
.where((term) => text.contains(_normalizeDialogueInput(term)))
.length;
return count >= 3;
}
if (segmentId == 'a0-04-c') {
return segment.independentRequiredTerms
.where((term) => text.contains(term))
.where((term) => text.contains(_normalizeDialogueInput(term)))
.length >=
3;
}
return segment.independentRequiredTerms.any((term) => text.contains(term));
return segment.independentRequiredTerms
.any((term) => text.contains(_normalizeDialogueInput(term)));
}
class LessonDialogue {
@@ -1156,7 +1166,8 @@ bool _containsTerm(String text, String term) {
return term.split(' + ').every((part) => _containsTerm(text, part.trim()));
}
if (term.startsWith('#')) return _matchesStructure(text, term);
final escaped = RegExp.escape(term.toLowerCase());
final normTerm = _normalizeDialogueInput(term);
final escaped = RegExp.escape(normTerm);
return RegExp('(?<![a-z])$escaped(?![a-z])').hasMatch(text);
}
@@ -1178,9 +1189,6 @@ bool _matchesStructure(String text, String token) => switch (token) {
_ => false,
};
String _normalizeDialogueInput(String response) =>
response.toLowerCase().replaceAll('\u2019', "'").replaceAll('\u2018', "'");
/// Whole-lesson and free-scene dialogues validate the same way segment
/// dialogues do: the turn has to contain the language the turn is teaching.
bool matchesDialogueStage(LessonDialogue script, int stage, String response) {