import numpy as np from sentence_api.audio_metrics import AudioMetrics, analyze_samples from sentence_api.scoring import align_text, duration_similarity_score, score_attempt, tokenize def test_tokenize_supports_mixed_chinese_and_english(): assert tokenize("你好,World! Don't stop.") == ["你", "好", "world", "don't", "stop"] def test_identical_reading_with_matching_duration_scores_100(): result = score_attempt( reference_text="The meeting starts at nine.", recognized_text="The meeting starts at nine", reference_speech_duration_ms=2000, student_metrics=AudioMetrics( recording_duration_ms=2400, speech_duration_ms=2000, internal_silence_ms=0, internal_pause_ratio=0.0, ), ) assert result.overall_score == 100 assert result.duration_score == 100 assert result.missing_tokens == [] def test_alignment_reports_missing_extra_and_substituted_tokens(): result = align_text( "The meeting starts at nine", "The lesson start at nine today", ) assert result.content_score < 70 assert result.extra_tokens == ["today"] assert ("meeting", "lesson") in result.substitutions assert ("starts", "start") in result.substitutions def test_duration_score_allows_students_to_read_more_slowly(): assert duration_similarity_score(0.8) == 100 assert duration_similarity_score(1.3) == 100 assert duration_similarity_score(1.5) == 60 assert duration_similarity_score(1.8) == 20 def test_vad_excludes_leading_and_trailing_silence(): sample_rate = 16_000 silence = np.zeros(sample_rate // 2, dtype=np.float32) time = np.arange(sample_rate, dtype=np.float32) / sample_rate speech = (0.25 * np.sin(2 * np.pi * 220 * time)).astype(np.float32) metrics = analyze_samples(np.concatenate([silence, speech, silence]), sample_rate) assert metrics.recording_duration_ms == 2000 assert 900 <= metrics.speech_duration_ms <= 1050