"""Seed auto-pick scoring: combine ASR accuracy, loudness/clipping and audio problems into 0-100. score = weighted mean of the available sub-scores (each 0-100). Without ASR the accuracy weight is dropped and the rest re-normalised ("degraded" result). accuracy = ASR round-trip word accuracy (routes.stt._word_accuracy) loudness = 100 - |dBFS - target| * LOUD_PEN_PER_DB (capped) - CLIP_PEN if peak hits full scale problems = 100 - sum of per-kind penalties from core.audio.audio_problems """ from __future__ import annotations W_ACCURACY = 0.5 W_LOUDNESS = 0.2 W_PROBLEMS = 0.3 LOUD_PEN_PER_DB = 4.0 # points lost per dB away from the target level LOUD_PEN_MAX = 60.0 CLIP_PEN = 30.0 # peak at/above CLIP_PEAK_DBFS CLIP_PEAK_DBFS = -0.1 PROBLEM_PENALTY = {"silence": 25.0, "level_jump": 20.0, "clipping": 10.0} PROBLEM_PENALTY_DEFAULT = 10.0 DEFAULT_COUNT = 8 MAX_COUNT = 16 def loudness_score(info: dict) -> float: dbfs, peak, target = info.get("dbfs"), info.get("peak_dbfs"), info.get("target_dbfs", -20.0) if dbfs is None: return 0.0 s = 100.0 - min(LOUD_PEN_MAX, abs(dbfs - target) * LOUD_PEN_PER_DB) if peak is not None and peak >= CLIP_PEAK_DBFS: s -= CLIP_PEN return max(0.0, s) def problems_score(problems: list[dict]) -> float: pen = sum(PROBLEM_PENALTY.get(p.get("kind"), PROBLEM_PENALTY_DEFAULT) for p in problems) return max(0.0, 100.0 - pen) def combine(accuracy: float | None, loudness: float, problems: float) -> float: parts = [(W_LOUDNESS, loudness), (W_PROBLEMS, problems)] if accuracy is not None: parts.append((W_ACCURACY, accuracy)) return round(sum(w * v for w, v in parts) / sum(w for w, _ in parts), 1) def pick_winner(candidates: list[dict]) -> int | None: """Seed with the highest score (ties: lowest seed); errored candidates are skipped.""" ok = [c for c in candidates if not c.get("error") and c.get("score") is not None] return min(ok, key=lambda c: (-c["score"], c["seed"]))["seed"] if ok else None def seed_list(seeds, count) -> list[int]: """Validate the request: explicit seeds win, else 1..count; bounded to MAX_COUNT.""" if seeds is not None: if not isinstance(seeds, list) or not seeds: raise ValueError("seeds must be a non-empty list") out = [] for s in seeds: if isinstance(s, bool) or not isinstance(s, int) or not 0 <= s <= 9999: raise ValueError("seeds must be integers 0-9999") if s not in out: out.append(s) if len(out) > MAX_COUNT: raise ValueError(f"at most {MAX_COUNT} seeds") return out n = DEFAULT_COUNT if count is None else count if isinstance(n, bool) or not isinstance(n, int) or not 1 <= n <= MAX_COUNT: raise ValueError(f"count must be 1-{MAX_COUNT}") return list(range(1, n + 1))