| """Bit-equal port of `quality-scorer/src/lib/scoring.js`. |
| |
| `compute_report(raw)` is the single source of truth for both the offline ingest |
| pipeline and the live `/analyze` endpoint. Parity vs the JS twin is asserted by |
| `tests/test_scoring_parity.py`. |
| """ |
|
|
| from __future__ import annotations |
|
|
| import math |
|
|
| from .signals import SIGNALS, clamp, evaluate_signal |
|
|
| |
| WEIGHT: dict[str, float] = { |
| "silence": 1.0, |
| "clipping": 1.0, |
| "noise": 0.9, |
| "truncation": 0.85, |
| "channel": 0.7, |
| "duration": 0.6, |
| "dynamics": 0.4, |
| } |
|
|
| FAIL_PHRASE: dict[str, str] = { |
| "silence": "mostly dead air", |
| "clipping": "hard clipping", |
| "noise": "noise-dominated spectrum", |
| "truncation": "cut off mid-phrase", |
| "duration": "length out of range", |
| "channel": "collapsed stereo channel", |
| "dynamics": "no dynamic range", |
| } |
|
|
|
|
| def _js_round(x: float) -> int: |
| """JavaScript `Math.round` semantics: half rounds toward +β. |
| |
| Python's built-in `round()` uses banker's rounding (half-to-even), which |
| diverges on .5 boundaries. Composite scores are derived from continuous |
| severity sums so .5 is rare, but parity demands the JS rule. |
| """ |
| return math.floor(x + 0.5) |
|
|
|
|
| def compute_report(raw: dict) -> dict: |
| """Map raw signal values β the full Track-report shape consumed by the UI. |
| |
| Verdict is precision-first: any CRITICAL signal failing β DROP, regardless |
| of the composite score. Dynamics can fail without forcing a drop. |
| """ |
| signals: list[dict] = [] |
| for s in SIGNALS: |
| ev = evaluate_signal(s["id"], raw[s["id"]]) |
| signals.append({ |
| "id": s["id"], |
| "label": s["label"], |
| "short": s["short"], |
| "critical": s["critical"], |
| "threshold": s["threshold"], |
| "blurb": s["blurb"], |
| "value": raw[s["id"]], |
| **ev, |
| }) |
|
|
| penalty = 0.0 |
| for s in signals: |
| penalty += (s["severity"] ** 1.4) * WEIGHT.get(s["id"], 0.5) * 27 |
| score = _js_round(clamp(100 - penalty, 0, 100)) |
|
|
| failed = [s for s in signals if s["status"] == "fail"] |
| critical_fails = sorted( |
| (s for s in failed if s["critical"]), |
| key=lambda x: x["severity"], |
| reverse=True, |
| ) |
| verdict = "drop" if critical_fails else "keep" |
| primary_fail = critical_fails[0]["id"] if verdict == "drop" else None |
|
|
| if verdict == "drop": |
| w = critical_fails[0] |
| extra = f" Β· +{len(critical_fails) - 1} more" if len(critical_fails) > 1 else "" |
| reason = ( |
| f"Dropped β {FAIL_PHRASE[w['id']]} " |
| f"({w['label'].lower()} {w['display']}){extra}." |
| ) |
| else: |
| warns = [s for s in signals if s["status"] == "warn"] |
| if warns: |
| plural = "s" if len(warns) > 1 else "" |
| reason = ( |
| f"Kept β within bounds; {len(warns)} signal{plural} " |
| "flagged for review." |
| ) |
| else: |
| reason = "Kept β all technical signals within bounds." |
|
|
| return { |
| "score": score, |
| "verdict": verdict, |
| "primaryFail": primary_fail, |
| "reason": reason, |
| "signals": signals, |
| "failModes": [s["id"] for s in failed], |
| } |
|
|