import Foundation struct ImitationAssessmentRequest { let mediaId: String let sentence: SentenceBoundary let recordingURL: URL let locale: String? } struct TextSubstitution: Decodable { let expected: String let actual: String } struct ImitationAssessmentResult: Decodable { let attemptId: String? let scoringVersion: String? let overallScore: Float let passed: Bool? let passScore: Float? let contentScore: Float? let completenessScore: Float? let fluencyScore: Float? let pronunciationScore: Float? let prosodyScore: Float? let durationScore: Float? let pauseScore: Float? let speechRateScore: Float? let referenceText: String? let recognizedText: String? let referenceDurationMs: Int64? let referenceSpeechDurationMs: Int64? let studentRecordingDurationMs: Int64? let studentSpeechDurationMs: Int64? let durationRatio: Float? let missingTokens: [String] let extraTokens: [String] let substitutions: [TextSubstitution] let feedback: String? let details: [String: String] private enum CodingKeys: String, CodingKey { case attemptId = "attempt_id" case scoringVersion = "scoring_version" case overallScore = "overall_score" case passed case passScore = "pass_score" case contentScore = "content_score" case completenessScore = "completeness_score" case fluencyScore = "fluency_score" case pronunciationScore = "pronunciation_score" case prosodyScore = "prosody_score" case durationScore = "duration_score" case pauseScore = "pause_score" case speechRateScore = "speech_rate_score" case referenceText = "reference_text" case recognizedText = "recognized_text" case referenceDurationMs = "reference_duration_ms" case referenceSpeechDurationMs = "reference_speech_duration_ms" case studentRecordingDurationMs = "student_recording_duration_ms" case studentSpeechDurationMs = "student_speech_duration_ms" case durationRatio = "duration_ratio" case missingTokens = "missing_tokens" case extraTokens = "extra_tokens" case substitutions case feedback case details } init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) attemptId = try container.decodeIfPresent(String.self, forKey: .attemptId) scoringVersion = try container.decodeIfPresent(String.self, forKey: .scoringVersion) overallScore = try container.decodeIfPresent(Float.self, forKey: .overallScore) ?? 0 passed = try container.decodeIfPresent(Bool.self, forKey: .passed) passScore = try container.decodeIfPresent(Float.self, forKey: .passScore) contentScore = try container.decodeIfPresent(Float.self, forKey: .contentScore) completenessScore = try container.decodeIfPresent(Float.self, forKey: .completenessScore) fluencyScore = try container.decodeIfPresent(Float.self, forKey: .fluencyScore) pronunciationScore = try container.decodeIfPresent(Float.self, forKey: .pronunciationScore) prosodyScore = try container.decodeIfPresent(Float.self, forKey: .prosodyScore) durationScore = try container.decodeIfPresent(Float.self, forKey: .durationScore) pauseScore = try container.decodeIfPresent(Float.self, forKey: .pauseScore) speechRateScore = try container.decodeIfPresent(Float.self, forKey: .speechRateScore) referenceText = try container.decodeIfPresent(String.self, forKey: .referenceText) recognizedText = try container.decodeIfPresent(String.self, forKey: .recognizedText) referenceDurationMs = try container.decodeIfPresent(Int64.self, forKey: .referenceDurationMs) referenceSpeechDurationMs = try container.decodeIfPresent(Int64.self, forKey: .referenceSpeechDurationMs) studentRecordingDurationMs = try container.decodeIfPresent(Int64.self, forKey: .studentRecordingDurationMs) studentSpeechDurationMs = try container.decodeIfPresent(Int64.self, forKey: .studentSpeechDurationMs) durationRatio = try container.decodeIfPresent(Float.self, forKey: .durationRatio) missingTokens = try container.decodeIfPresent([String].self, forKey: .missingTokens) ?? [] extraTokens = try container.decodeIfPresent([String].self, forKey: .extraTokens) ?? [] substitutions = try container.decodeIfPresent([TextSubstitution].self, forKey: .substitutions) ?? [] feedback = try container.decodeIfPresent(String.self, forKey: .feedback) details = try container.decodeIfPresent([String: String].self, forKey: .details) ?? [:] } }