import Foundation import Speech /// One language's showing in the sampling pass: how well `SpeechTranscriber` /// transcribed the first few seconds of audio in that locale. struct LanguageCandidate: Equatable { /// BCP-47 identifier of the locale (e.g. `en-US`, `ru-RU`). let localeID: String /// Character-count-weighted mean of the per-run transcription confidence, /// 0…1. A locale that produced no text scores 0. let meanConfidence: Double /// Number of characters the transcript came out to — the tie-breaker and the /// reason an empty transcript can't win. let transcriptLength: Int } /// Pure, testable language selection for a voice note. No `SpeechAnalyzer` calls /// live here: the service transcribes a ~15 s sample in each enabled language, /// turns each into a `LanguageCandidate`, and this picks the winner. Keeping it /// side-effect-free is what makes the (undocumented, cross-locale) confidence /// calibration unit-testable. enum LanguagePicker { /// The confidence assigned to a transcript that carried text but no /// `transcriptionConfidence` attribute at all — the graceful-degradation case /// for `DictationTranscriber`, which need not emit per-run confidence. A /// mid-scale value so such a candidate is ranked by transcript length against /// its peers rather than being zeroed out and always losing to a confidence- /// reporting engine. Two confidence-less candidates then tie here and fall /// through to the length tie-break in `pick`. static let neutralConfidence = 0.5 /// Character-count-weighted mean confidence over an attributed transcript. /// Only runs that actually carry a `transcriptionConfidence` attribute /// contribute, weighted by their character length; a transcript with no /// confidence anywhere (or no characters) scores 0. static func meanConfidence(of attributed: AttributedString) -> Double { var weighted = 0.0 var totalCharacters = 0 for run in attributed.runs { guard let confidence = run.transcriptionConfidence else { continue } let length = attributed[run.range].characters.count guard length > 0 else { continue } weighted += confidence * Double(length) totalCharacters += length } return totalCharacters > 0 ? weighted / Double(totalCharacters) : 0 } /// Whether any run in the transcript carries a `transcriptionConfidence` /// attribute — i.e. whether `meanConfidence` measured anything real. static func hasConfidence(of attributed: AttributedString) -> Bool { attributed.runs.contains { $0.transcriptionConfidence != nil } } /// The confidence used to *rank* a candidate. Prefers the real weighted mean; /// when the transcript has text but no confidence attribute anywhere (an engine /// that doesn't emit it), falls back to `neutralConfidence` so the candidate /// competes on length instead of being zeroed. An empty transcript scores 0 — /// it can never win against real text. static func rankingConfidence(of attributed: AttributedString) -> Double { if hasConfidence(of: attributed) { return meanConfidence(of: attributed) } return attributed.characters.isEmpty ? 0 : neutralConfidence } /// The best candidate, or nil when given nothing. Argmax on `meanConfidence`; /// a zero-length transcript can only win if every candidate is empty. Ties /// break deterministically — higher confidence, then longer transcript, then /// lexicographically smaller `localeID` — so the same samples always yield the /// same pick. static func pick(from candidates: [LanguageCandidate]) -> LanguageCandidate? { candidates.max { lhs, rhs in if lhs.meanConfidence != rhs.meanConfidence { return lhs.meanConfidence < rhs.meanConfidence } if lhs.transcriptLength != rhs.transcriptLength { return lhs.transcriptLength < rhs.transcriptLength } // `max` keeps the greater element; invert so the smaller localeID wins. return lhs.localeID > rhs.localeID } } }