diff --git a/.importlinter b/.importlinter index 3959aa5d..aef764a2 100644 --- a/.importlinter +++ b/.importlinter @@ -16,7 +16,6 @@ source_modules = aai_cli.config aai_cli.config_builder aai_cli.context - aai_cli.der aai_cli.environments aai_cli.errors aai_cli.eval_data @@ -76,7 +75,6 @@ source_modules = aai_cli.client aai_cli.config aai_cli.config_builder - aai_cli.der aai_cli.environments aai_cli.errors aai_cli.eval_data diff --git a/Formula/assembly.rb b/Formula/assembly.rb index d7d65928..7e1d21f1 100644 --- a/Formula/assembly.rb +++ b/Formula/assembly.rb @@ -13,17 +13,19 @@ class Assembly < Formula sha256 cellar: :any, arm64_sonoma: "daa913744f7176cf27100ea3860b2eba2c72438737eada3dfd7122860ac8d38b" end - depends_on "pkgconf" => :build # cffi / cryptography native builds - depends_on "rust" => :build # pydantic-core, jiter, cryptography + depends_on "pkgconf" => :build # cffi native build (sounddevice; +cryptography on Linux) + depends_on "rust" => :build # pydantic-core, jiter (+cryptography on Linux) depends_on "cloudflared" # public quick-tunnel for `assembly share` depends_on "ffmpeg" # decode non-WAV/URL audio (transcribe/stream) - depends_on "libyaml" # pyyaml native extension - depends_on "openssl@3" # cryptography linkage depends_on "portaudio" # sounddevice (audio capture) depends_on "python@3.13" - # Linux-only keyring backend (D-Bus Secret Service); not built on macOS. + # Linux-only keyring backend (D-Bus Secret Service); not built on macOS, which + # uses the Keychain backend instead. secretstorage pulls in cryptography (a + # Rust+OpenSSL source build), so both it and openssl@3 stay off the macOS bottle. on_linux do + depends_on "openssl@3" # cryptography linkage + resource "jeepney" do url "https://files.pythonhosted.org/packages/7b/6f/357efd7602486741aa73ffc0617fb310a29b588ed0fd69c2399acbb85b0c/jeepney-0.9.0.tar.gz" sha256 "cf0e9e845622b81e4a28df94c40345400256ec608d0e55bb8a3feaa9163f5732" @@ -33,6 +35,11 @@ class Assembly < Formula url "https://files.pythonhosted.org/packages/1c/03/e834bcd866f2f8a49a85eaff47340affa3bfa391ee9912a952a1faa68c7b/secretstorage-3.5.0.tar.gz" sha256 "f04b8e4689cbce351744d5537bf6b1329c6fc68f91fa666f60a380edddcd11be" end + + resource "cryptography" do + url "https://files.pythonhosted.org/packages/9f/a9/db8f313fdcd85d767d4973515e1db101f9c71f95fced83233de224673757/cryptography-48.0.0.tar.gz" + sha256 "5c3932f4436d1cccb036cb0eaef46e6e2db91035166f1ad6505c3c9d5a635920" + end end resource "annotated-doc" do @@ -80,11 +87,6 @@ class Assembly < Formula sha256 "08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44" end - resource "cryptography" do - url "https://files.pythonhosted.org/packages/9f/a9/db8f313fdcd85d767d4973515e1db101f9c71f95fced83233de224673757/cryptography-48.0.0.tar.gz" - sha256 "5c3932f4436d1cccb036cb0eaef46e6e2db91035166f1ad6505c3c9d5a635920" - end - resource "distro" do url "https://files.pythonhosted.org/packages/fc/f8/98eea607f65de6527f8a2e8885fc8015d3e6f5775df186e443e0964a11c3/distro-1.9.0.tar.gz" sha256 "2fa77c6fd8940f116ee1d6b94a2f90b13b5ea8d019b98bc8bafdcabcdd9bdbed" @@ -145,11 +147,6 @@ class Assembly < Formula sha256 "ae9c051469102a61ef0927100baeeb4546f78d180c9b0948281d08eaf44c191e" end - resource "joblib" do - url "https://files.pythonhosted.org/packages/41/f2/d34e8b3a08a9cc79a50b2208a93dce981fe615b64d5a4d4abee421d898df/joblib-1.5.3.tar.gz" - sha256 "8561a3269e6801106863fd0d6d84bb737be9e7631e33aaed3fb9ce5953688da3" - end - resource "keyring" do url "https://files.pythonhosted.org/packages/43/4b/674af6ef2f97d56f0ab5153bf0bfa28ccb6c3ed4d1babf4305449668807b/keyring-25.7.0.tar.gz" sha256 "fe01bd85eb3f8fb3dd0405defdeac9a5b4f6f0439edbb3149577f244a2e8245b" @@ -170,16 +167,6 @@ class Assembly < Formula sha256 "48e8f4d9e7e5878571ecf6f2b4e57634f93cd474cc8cfbd2376f2d11b396e30d" end - resource "narwhals" do - url "https://files.pythonhosted.org/packages/62/3c/c4ef2164a71c1a63d7f1ae411c4082c5fa872405106db60a4b7114989ad7/narwhals-2.22.1.tar.gz" - sha256 "d62920805a0a43b7ff8b54b0c0d3142d796f8a9301836ada37e573d6a33cbcd9" - end - - resource "numpy" do - url "https://files.pythonhosted.org/packages/d0/ad/fed0499ce6a338d2a03ebae59cd15093910c8875328855781952abf6c2fe/numpy-2.4.6.tar.gz" - sha256 "f3a3570c4a2a16746ac2c31a7c7c7b0c186b95ce902e33db6f28094ed7387dda" - end - resource "openai" do url "https://files.pythonhosted.org/packages/3c/a6/5815fe2e2aca74b36c650d1bd43b69827cee568073d0d2d9b6fc5aaac80c/openai-2.41.0.tar.gz" sha256 "db5c362acd6604b84f076abbefa66826ea4b46ecba2954ed866e6a149a1352c0" @@ -190,11 +177,6 @@ class Assembly < Formula sha256 "ff452ff5a3e828ce110190feff1178bb1f2ea2281fa2075aadb987c2fb221661" end - resource "pandas" do - url "https://files.pythonhosted.org/packages/f8/87/4341c6252d1c47b08768c3d25ac487362bf403f0313ddae4a2a26c9b1b4c/pandas-3.0.3.tar.gz" - sha256 "696a4a00a2a2a35d4e5deb3fc946641b96c944f02230e4f76137fe35d806c4fc" - end - resource "platformdirs" do url "https://files.pythonhosted.org/packages/d7/47/e4501f49c178ae1d9f4a75073fda4204f52647993f075a9db4d14930e0c5/platformdirs-4.10.0.tar.gz" sha256 "31e761a6a0ca04faf7353ea759bdba55652be214725111e5aac52dfa29d4bef7" @@ -205,21 +187,6 @@ class Assembly < Formula sha256 "28cde192929c8e7321de85de1ddbe736f1375148b02f2e17edd840042b1be855" end - resource "pyannote-core" do - url "https://files.pythonhosted.org/packages/a3/be/4a35ea31c685aef801f7f35c193e7766ca1bb948ae497a625cbfaa8c31ba/pyannote_core-6.0.1.tar.gz" - sha256 "4b4ada3276f6df4e073fa79166636e3597d0dcb5a0fe26014a3477867cc033fb" - end - - resource "pyannote-database" do - url "https://files.pythonhosted.org/packages/65/45/6210274c187cc457e854be8b56c6819fa14376f27e7e2b6021b2aa02449a/pyannote_database-6.1.1.tar.gz" - sha256 "bbe76da738257a9e64061123d9694ad7e949c4f171d91a9269606d873528cd10" - end - - resource "pyannote-metrics" do - url "https://files.pythonhosted.org/packages/74/ba/7dbc2f790d5e321e46dc1e250ff00b69cdefc0c5695b811e96a4b932cddd/pyannote_metrics-4.1.tar.gz" - sha256 "afe24c54ee0799e8cfbe8ee85fa517793c3450bb7eae8fedd1a77ccec0343f7e" - end - resource "pycparser" do url "https://files.pythonhosted.org/packages/1b/7d/92392ff7815c21062bea51aa7b87d45576f649f16458d78b7cf94b9ab2e6/pycparser-3.0.tar.gz" sha256 "600f49d217304a5902ac3c37e1281c9fe94e4d0489de643a9504c5cdfdfc6b29" @@ -245,21 +212,11 @@ class Assembly < Formula sha256 "6757cd03768053ff99f3039c1a36d6c0aa0b263438fcab17520b30a303a82b5f" end - resource "python-dateutil" do - url "https://files.pythonhosted.org/packages/66/c0/0c8b6ad9f17a802ee498c46e004a0eb49bc148f2fd230864601a86dcf6db/python-dateutil-2.9.0.post0.tar.gz" - sha256 "37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3" - end - resource "python-dotenv" do url "https://files.pythonhosted.org/packages/82/ed/0301aeeac3e5353ef3d94b6ec08bbcabd04a72018415dcb29e588514bba8/python_dotenv-1.2.2.tar.gz" sha256 "2c371a91fbd7ba082c2c1dc1f8bf89ca22564a087c2c287cd9b662adde799cf3" end - resource "pyyaml" do - url "https://files.pythonhosted.org/packages/05/8e/961c0007c59b8dd7729d542c61a4d537767a59645b82a0b521206e1e25c2/pyyaml-6.0.3.tar.gz" - sha256 "d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f" - end - resource "questionary" do url "https://files.pythonhosted.org/packages/f6/45/eafb0bba0f9988f6a2520f9ca2df2c82ddfa8d67c95d6625452e97b204a5/questionary-2.1.1.tar.gz" sha256 "3d7e980292bb0107abaa79c68dd3eee3c561b83a0f89ae482860b181c8bd412d" @@ -275,46 +232,21 @@ class Assembly < Formula sha256 "edd07a4824c6b40189fb7ac9bc4c52536e9780fbbfbddf6f1e2502c31b068c36" end - resource "scikit-learn" do - url "https://files.pythonhosted.org/packages/fa/6f/37092bdb25f712817231799fc5674d8e704066a8a70c1d2d40517e18b4ab/scikit_learn-1.9.0.tar.gz" - sha256 "8833266989d3a5110178a9fae30783675460724d0e1efb13b14901d2c660c557" - end - - resource "scipy" do - url "https://files.pythonhosted.org/packages/7a/97/5a3609c4f8d58b039179648e62dd220f89864f56f7357f5d4f45c29eb2cc/scipy-1.17.1.tar.gz" - sha256 "95d8e012d8cb8816c226aef832200b1d45109ed4464303e997c5b13122b297c0" - end - resource "shellingham" do url "https://files.pythonhosted.org/packages/58/15/8b3609fd3830ef7b27b655beb4b4e9c62313a4e8da8c676e142cc210d58e/shellingham-1.5.4.tar.gz" sha256 "8dbca0739d487e5bd35ab3ca4b36e11c4078f3a234bfce294b0a0291363404de" end - resource "six" do - url "https://files.pythonhosted.org/packages/94/e7/b2c673351809dca68a0e064b6af791aa332cf192da575fd474ed7d6f16a2/six-1.17.0.tar.gz" - sha256 "ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81" - end - resource "sniffio" do url "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz" sha256 "f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc" end - resource "sortedcontainers" do - url "https://files.pythonhosted.org/packages/e8/c4/ba2f8066cceb6f23394729afe52f3bf7adec04bf9ed2c820b39e19299111/sortedcontainers-2.4.0.tar.gz" - sha256 "25caa5a06cc30b6b83d11423433f65d1f9d76c4c6a0c90e3379eaa43b9bfdb88" - end - resource "sounddevice" do url "https://files.pythonhosted.org/packages/2a/f9/2592608737553638fca98e21e54bfec40bf577bb98a61b2770c912aab25e/sounddevice-0.5.5.tar.gz" sha256 "22487b65198cb5bf2208755105b524f78ad173e5ab6b445bdab1c989f6698df3" end - resource "threadpoolctl" do - url "https://files.pythonhosted.org/packages/b7/4d/08c89e34946fce2aec4fbb45c9016efd5f4d7f24af8e5d93296e935631d8/threadpoolctl-3.6.0.tar.gz" - sha256 "8ab8b4aa3491d812b623328249fab5302a68d2d71745c8a4c719a2fcaba9f44e" - end - resource "tomli-w" do url "https://files.pythonhosted.org/packages/19/75/241269d1da26b624c0d5e110e8149093c759b7a286138f4efd61a60e75fe/tomli_w-1.2.0.tar.gz" sha256 "2dd14fac5a47c27be9cd4c976af5a12d87fb1f0b4512f81d69cce3b35ae25021" @@ -340,11 +272,6 @@ class Assembly < Formula sha256 "ba561c48a67c5958007083d386c3295464928b01faa735ab8547c5692e87f464" end - resource "tzdata" do - url "https://files.pythonhosted.org/packages/ba/19/1b9b0e29f30c6d35cb345486df41110984ea67ae69dddbc0e8a100999493/tzdata-2026.2.tar.gz" - sha256 "9173fde7d80d9018e02a662e168e5a2d04f87c41ea174b139fbef642eda62d10" - end - resource "wcwidth" do url "https://files.pythonhosted.org/packages/2c/ee/afaf0f85a9a18fe47a67f1e4422ed6cf1fe642f0ae0a2f81166231303c52/wcwidth-0.7.0.tar.gz" sha256 "90e3a7ea092341c44b99562e75d09e4d5160fe7a3974c6fb842a101a95e7eed0" @@ -369,8 +296,8 @@ def install # --help imports the full command tree, so a resource missing from this # formula fails here instead of on the user's first real command. assert_match "transcribe", shell_output("#{bin}/assembly --help") - # The `assembly eval` scoring stack (jiwer/pyannote) is imported lazily, so + # The `assembly eval` WER scoring stack (jiwer) is imported lazily, so # --help can't catch it being absent — probe it directly. - system libexec/"bin/python", "-c", "import jiwer, pyannote.metrics" + system libexec/"bin/python", "-c", "import jiwer" end end diff --git a/README.md b/README.md index 3b35cebb..ff7a53dc 100644 --- a/README.md +++ b/README.md @@ -62,7 +62,7 @@ neither. - **Real-time streaming**: `assembly stream` transcribes the microphone, a file, or a URL live — on macOS it can capture system audio too. - **Voice agent**: `assembly agent` runs a full-duplex spoken conversation in your terminal (use headphones). - **LLM Gateway**: `assembly llm` prompts an LLM over a transcript, stdin, or a live stream (`assembly stream --llm "summarize as I talk"`). -- **Model evaluation**: `assembly eval` transcribes a Hugging Face dataset (with built-in aliases for common benchmarks: `assembly eval tedlium`) or a local `.csv`/`.jsonl` manifest and scores WER against its references (plus DER with `--speaker-labels`) — handy for picking a speech model. +- **Model evaluation**: `assembly eval` transcribes a Hugging Face dataset (with built-in aliases for common benchmarks: `assembly eval tedlium`) or a local `.csv`/`.jsonl` manifest and scores WER against its references — handy for picking a speech model. - **Starter apps**: `assembly init` scaffolds a self-contained FastAPI + HTML app (`audio-transcription`, `live-captions`, `voice-agent`). - **Code generation**: add `--show-code` to `transcribe`/`stream`/`agent` to print the equivalent Python SDK script instead of running. - **Account self-service**: `assembly keys` / `balance` / `usage` / `limits` / `sessions` / `audit` via browser login. diff --git a/aai_cli/commands/evaluate.py b/aai_cli/commands/evaluate.py index 2c9e20a6..2a04b511 100644 --- a/aai_cli/commands/evaluate.py +++ b/aai_cli/commands/evaluate.py @@ -1,9 +1,8 @@ """`assembly eval` — transcribe an evaluation dataset and score it against references. -WER (via jiwer) against the dataset's reference texts; with ``--speaker-labels`` -also DER (via pyannote.metrics) against its reference speaker turns. The module -is named ``evaluate`` because importing a module named ``eval`` would shadow the -builtin; the command itself registers as ``eval``. +WER (via jiwer) against the dataset's reference texts. The module is named +``evaluate`` because importing a module named ``eval`` would shadow the builtin; +the command itself registers as ``eval``. """ from __future__ import annotations @@ -16,9 +15,9 @@ import typer from rich.console import RenderableType -from aai_cli import client, der, eval_data, help_panels, jsonshape, options, output, wer +from aai_cli import client, eval_data, help_panels, jsonshape, options, output, wer from aai_cli.context import AppState, run_command -from aai_cli.errors import CLIError, NotAuthenticated, UsageError +from aai_cli.errors import CLIError, NotAuthenticated from aai_cli.help_text import examples_epilog app = typer.Typer() @@ -36,45 +35,28 @@ def _pct(value: object) -> str: return f"{jsonshape.as_float(value):.2%}" -def _hypothesis_turns(transcript: aai.Transcript) -> list[der.Turn]: - """The transcript's diarized utterances as DER hypothesis turns (ms → seconds).""" - return [ - der.Turn( - speaker=str(getattr(utterance, "speaker", "")), - start=jsonshape.as_float(getattr(utterance, "start", None)) / 1000, - end=jsonshape.as_float(getattr(utterance, "end", None)) / 1000, - ) - for utterance in jsonshape.object_list(getattr(transcript, "utterances", None)) - ] - - @dataclass(frozen=True) class _ItemResult: - """One scored row: the emitted dict plus the scores kept for pooling.""" + """One scored row: the emitted dict plus the score kept for pooling.""" row: dict[str, object] words: wer.Score | None - speakers: der.DerScore | None def _failed_result(item: eval_data.EvalItem, err: CLIError) -> _ItemResult: """A row whose transcription failed: the error rides along, no scores pooled.""" - return _ItemResult(row={"item": item.item_id, "error": err.message}, words=None, speakers=None) + return _ItemResult(row={"item": item.item_id, "error": err.message}, words=None) -def _score_item( - item: eval_data.EvalItem, transcript: aai.Transcript, *, collar: float -) -> _ItemResult: - row: dict[str, object] = {"item": item.item_id} - words: wer.Score | None = None - speakers: der.DerScore | None = None - if item.reference is not None: - words = wer.score(item.reference, str(transcript.text or "")) - row.update({"words": words.words, "errors": words.errors, "wer": words.wer}) - if item.turns is not None: - speakers = der.score(item.turns, _hypothesis_turns(transcript), collar=collar) - row["der"] = speakers.der - return _ItemResult(row=row, words=words, speakers=speakers) +def _score_item(item: eval_data.EvalItem, transcript: aai.Transcript) -> _ItemResult: + words = wer.score(item.reference, str(transcript.text or "")) + row: dict[str, object] = { + "item": item.item_id, + "words": words.words, + "errors": words.errors, + "wer": words.wer, + } + return _ItemResult(row=row, words=words) def _pooled_metrics(results: list[_ItemResult]) -> dict[str, object]: @@ -84,15 +66,6 @@ def _pooled_metrics(results: list[_ItemResult]) -> dict[str, object]: if word_scores: total = wer.pooled(word_scores) metrics.update({"words": total.words, "errors": total.errors, "wer": total.wer}) - der_scores = [result.speakers for result in results if result.speakers is not None] - if der_scores: - pooled = der.pooled(der_scores) - metrics["der"] = pooled.der - metrics["der_breakdown"] = { - "missed": pooled.missed / pooled.total, - "false_alarm": pooled.false_alarm / pooled.total, - "confusion": pooled.confusion / pooled.total, - } return metrics @@ -193,13 +166,6 @@ def _summary(payload: dict[str, object]) -> str: parts.append( f"WER {_pct(payload.get('wer'))} ({errors} {noun} / {payload.get('words')} words)" ) - if "der" in payload: - breakdown = jsonshape.as_mapping(payload.get("der_breakdown")) or {} - parts.append( - f"DER {_pct(payload.get('der'))} (missed {_pct(breakdown.get('missed'))} · " - f"false alarm {_pct(breakdown.get('false_alarm'))} · " - f"confusion {_pct(breakdown.get('confusion'))})" - ) return output.heading(" ".join(parts)) @@ -214,12 +180,10 @@ def _pct_cell(row: dict[str, object], key: str) -> str: def _render(payload: dict[str, object]) -> RenderableType: has_wer = "wer" in payload - has_der = "der" in payload has_failed = "failed" in payload columns = [ "ITEM", *(["WORDS", "ERRORS", "WER"] if has_wer else []), - *(["DER"] if has_der else []), *(["ERROR"] if has_failed else []), ] table = output.data_table(*columns) @@ -227,8 +191,6 @@ def _render(payload: dict[str, object]) -> RenderableType: cells = [str(row.get("item"))] if has_wer: cells += [_cell(row, "words"), _cell(row, "errors"), _pct_cell(row, "wer")] - if has_der: - cells.append(_pct_cell(row, "der")) if has_failed: cells.append(_cell(row, "error")) table.add_row(*cells) @@ -251,10 +213,6 @@ def _render(payload: dict[str, object]) -> RenderableType: "Compare models on your own audio", "assembly eval calls.csv --speech-model universal-3-pro", ), - ( - "Score diarization too (WER + DER)", - "assembly eval agent-calls.jsonl --speaker-labels", - ), ( "More rows, transcribed four at a time", "assembly eval librispeech --limit 50 --concurrency 4", @@ -263,10 +221,6 @@ def _render(payload: dict[str, object]) -> RenderableType: "Evaluate non-English audio", "assembly eval commonvoice --subset fr --language-code fr", ), - ( - "DER on a diarization benchmark", - "assembly eval callhome --speaker-labels", - ), ] ), ) @@ -295,18 +249,6 @@ def evaluate( language_code: str | None = typer.Option( None, "--language-code", help="Force a language (e.g. en_us)." ), - speaker_labels: bool = typer.Option( - False, - "--speaker-labels", - help="Diarize and also score DER against the dataset's reference speaker turns (speakers/timestamps_start/timestamps_end columns, in seconds).", - ), - collar: float | None = typer.Option( - None, - "--collar", - min=0.0, - help="DER forgiveness (seconds) around each reference turn boundary " - "(default: 1.0; needs --speaker-labels).", - ), concurrency: int = typer.Option( 1, "--concurrency", @@ -321,8 +263,7 @@ def evaluate( text; both are normalized first (lowercased, punctuation stripped) so style differences don't count as errors, and the summary pools total errors over total reference words. Handy for picking a model: run once per - --speech-model and compare. --speaker-labels also diarizes and scores DER - against reference speaker turns. + --speech-model and compare. Datasets come from the Hugging Face Hub (any public dataset its viewer serves with audio + reference columns; gated ones need HF_TOKEN), a local @@ -333,16 +274,10 @@ def evaluate( (meetings), gigaspeech, peoples (real-world US English), commonvoice (English; --subset fr etc. for its 98 other locales), voxpopuli (parliament speech), switchboard (phone calls), expresso (expressive - speech), loquacious, and callhome (phone calls with speaker turns, for - --speaker-labels). + speech), loquacious, and callhome (phone calls). """ def body(state: AppState, json_mode: bool) -> None: - if collar is not None and not speaker_labels: - raise UsageError( - "--collar only applies when diarization is being scored.", - suggestion="Add --speaker-labels.", - ) # Resolve credentials before any dataset download: a signed-out user must # not pull the whole dataset only to fail at the first transcription. api_key = state.resolve_api_key() @@ -353,12 +288,10 @@ def body(state: AppState, json_mode: bool) -> None: audio_column=audio_column, text_column=text_column, limit=limit, - with_speakers=speaker_labels, ) transcription_config = aai.TranscriptionConfig( speech_models=[speech_model.value] if speech_model else None, language_code=language_code, - speaker_labels=speaker_labels or None, ) outcomes = _transcripts( api_key, @@ -371,7 +304,7 @@ def body(state: AppState, json_mode: bool) -> None: results = [ _failed_result(item, outcome) if isinstance(outcome, CLIError) - else _score_item(item, outcome, collar=collar if collar is not None else 1.0) + else _score_item(item, outcome) for item, outcome in zip( data.items, outcomes, diff --git a/aai_cli/der.py b/aai_cli/der.py deleted file mode 100644 index e9de35a9..00000000 --- a/aai_cli/der.py +++ /dev/null @@ -1,95 +0,0 @@ -"""Diarization error rate (DER) scoring for `assembly eval --speaker-labels`. - -A thin shim over :mod:`pyannote.metrics` — the de-facto standard DER -implementation, including its optimal reference↔hypothesis speaker mapping — -so the alignment math is never re-derived here. pyannote drags numpy/scipy/ -pandas along, so it is imported lazily inside :func:`score`; the dataclasses -here stay import-cheap for the command layer. No SDK, no Rich. -""" - -from __future__ import annotations - -from dataclasses import dataclass -from typing import TYPE_CHECKING - -from pydantic import TypeAdapter - -if TYPE_CHECKING: - from pyannote.core import Annotation - -# pyannote types the metric call as a bare float; with ``detailed=True`` it -# actually returns the components dict — validate that shape instead of casting. -_COMPONENTS: TypeAdapter[dict[str, float]] = TypeAdapter(dict[str, float]) - - -@dataclass(frozen=True) -class Turn: - """One speaker turn: who spoke from ``start`` to ``end`` (seconds).""" - - speaker: str - start: float - end: float - - -@dataclass(frozen=True) -class DerScore: - """DER components in seconds of speech; pooled across files for corpus DER.""" - - missed: float - false_alarm: float - confusion: float - total: float - - @property - def der(self) -> float: - return (self.missed + self.false_alarm + self.confusion) / self.total - - -def _annotation(turns: list[Turn]) -> Annotation: - from pyannote.core import Annotation, Segment - - annotation = Annotation() - # The list index keys each track, so two overlapping turns by the same - # speaker don't collapse into one. - for track, turn in enumerate(turns): - annotation[Segment(turn.start, turn.end), track] = turn.speaker - return annotation - - -def score(reference: list[Turn], hypothesis: list[Turn], *, collar: float = 0.0) -> DerScore: - """Score hypothesis speaker turns against the reference diarization. - - The caller guarantees a non-empty reference (the dataset loader rejects - rows without speaker turns), so ``DerScore.der`` is always well-defined. - ``collar`` forgives that many seconds around each reference turn boundary. - """ - from pyannote.core import Segment, Timeline - from pyannote.metrics.diarization import DiarizationErrorRate - - metric = DiarizationErrorRate(collar=collar) - # An explicit evaluation extent (audio start through the last turn either - # side heard); without it pyannote warns about approximating the UEM. - extent = max(turn.end for turn in [*reference, *hypothesis]) - components: object = metric( - _annotation(reference), - _annotation(hypothesis), - uem=Timeline([Segment(0.0, extent)]), - detailed=True, - ) - detail = _COMPONENTS.validate_python(components) - return DerScore( - missed=detail["missed detection"], - false_alarm=detail["false alarm"], - confusion=detail["confusion"], - total=detail["total"], - ) - - -def pooled(scores: list[DerScore]) -> DerScore: - """Corpus-level score: error seconds over total reference speech seconds.""" - return DerScore( - missed=sum(item.missed for item in scores), - false_alarm=sum(item.false_alarm for item in scores), - confusion=sum(item.confusion for item in scores), - total=sum(item.total for item in scores), - ) diff --git a/aai_cli/eval_data.py b/aai_cli/eval_data.py index 128e79f2..6bb9808b 100644 --- a/aai_cli/eval_data.py +++ b/aai_cli/eval_data.py @@ -11,11 +11,6 @@ API ingests directly. Gated/private datasets authenticate via ``HF_TOKEN``. The common benchmarks also have short **aliases** (``ALIASES``) that fill in the hub id plus the subset/split/audio-column defaults each set needs. - -With ``with_speakers`` (the ``--speaker-labels`` flag), rows must also carry -diarization references as the parallel ``speakers`` / ``timestamps_start`` / -``timestamps_end`` arrays the Hugging Face diarization datasets use (seconds); -reference text then becomes optional, since diarization sets often have none. """ from __future__ import annotations @@ -26,7 +21,7 @@ from dataclasses import dataclass from pathlib import Path -from aai_cli import der, eval_hf_api, jsonshape, wer +from aai_cli import eval_hf_api, jsonshape, wer from aai_cli.errors import APIError, CLIError, UsageError _MANIFEST_SUFFIXES = (".csv", ".jsonl") @@ -38,9 +33,6 @@ # and manifest tools use (HF audio datasets, Common Voice, NeMo manifests). _AUDIO_COLUMNS = ("audio", "audio_filepath", "audio_url", "path", "file") _TEXT_COLUMNS = ("text", "sentence", "transcription", "transcript", "normalized_text") -# Diarization references: the parallel-array convention the Hugging Face -# diarization datasets (diarizers-community/*) use, in seconds. -_SPEAKER_COLUMNS = ("speakers", "timestamps_start", "timestamps_end") @dataclass(frozen=True) @@ -78,13 +70,11 @@ class Alias: @dataclass(frozen=True) class EvalItem: - """One evaluation row: an audio source (path or URL) plus its references — - text for WER, speaker turns for DER (each optional, never both absent).""" + """One evaluation row: an audio source (path or URL) plus its reference text.""" item_id: str audio: str - reference: str | None - turns: list[der.Turn] | None = None + reference: str @dataclass(frozen=True) @@ -104,7 +94,6 @@ def load( audio_column: str | None = None, text_column: str | None = None, limit: int, - with_speakers: bool = False, ) -> EvalDataset: """Load evaluation items from a local manifest or a Hugging Face dataset id.""" path = Path(dataset) @@ -118,7 +107,6 @@ def load( audio_column=audio_column, text_column=text_column, limit=limit, - with_speakers=with_speakers, ) alias = ALIASES.get(dataset) if alias is not None: @@ -133,7 +121,6 @@ def load( audio_column=audio_column, text_column=text_column, limit=limit, - with_speakers=with_speakers, ) @@ -164,22 +151,9 @@ def _resolve_columns( *, audio_column: str | None, text_column: str | None, - with_speakers: bool, -) -> tuple[str, str | None]: - """The (audio, text) columns to read. Text is only optional when speaker - turns are being scored instead — diarization datasets often carry no text.""" +) -> tuple[str, str]: + """The (audio, text) columns to read.""" audio_col = _pick_column(columns, audio_column, _AUDIO_COLUMNS, "--audio-column") - if with_speakers: - missing = [name for name in _SPEAKER_COLUMNS if name not in columns] - if missing: - raise UsageError( - f"--speaker-labels needs speaker-turn columns; missing: {', '.join(missing)} " - f"(columns: {', '.join(columns)}).", - suggestion="Rows carry parallel speakers/timestamps_start/timestamps_end " - "arrays in seconds (arrays need a .jsonl manifest, not .csv).", - ) - if text_column is None and not any(name in columns for name in _TEXT_COLUMNS): - return audio_col, None return audio_col, _pick_column(columns, text_column, _TEXT_COLUMNS, "--text-column") @@ -193,33 +167,10 @@ def _checked_reference(item_id: str, reference: str) -> str: return reference -def _row_reference(cells: dict[str, object], text_col: str | None, item_id: str) -> str | None: - if text_col is None: - return None +def _row_reference(cells: dict[str, object], text_col: str, item_id: str) -> str: return _checked_reference(item_id, str(cells.get(text_col) or "")) -def _row_turns(cells: dict[str, object], item_id: str) -> list[der.Turn]: - """The row's reference speaker turns from the parallel-array columns.""" - speakers = jsonshape.object_list(cells.get("speakers")) - starts = jsonshape.object_list(cells.get("timestamps_start")) - ends = jsonshape.object_list(cells.get("timestamps_end")) - if not speakers or len(starts) != len(speakers) or len(ends) != len(speakers): - raise UsageError( - f"{item_id} needs non-empty, equal-length speakers/timestamps_start/" - "timestamps_end arrays.", - suggestion="Each row lists who spoke plus matching start/end seconds.", - ) - return [ - der.Turn( - speaker=str(speakers[i]), - start=jsonshape.as_float(starts[i]), - end=jsonshape.as_float(ends[i]), - ) - for i in range(len(speakers)) - ] - - # ---------------------------------------------------------------- local manifests @@ -248,7 +199,6 @@ def _load_manifest( audio_column: str | None, text_column: str | None, limit: int, - with_speakers: bool, ) -> EvalDataset: if not path.is_file(): raise CLIError( @@ -271,12 +221,9 @@ def _load_manifest( list(rows[0]), audio_column=audio_column, text_column=text_column, - with_speakers=with_speakers, ) items = [ - _manifest_item( - path, index, row, audio_col=audio_col, text_col=text_col, with_speakers=with_speakers - ) + _manifest_item(path, index, row, audio_col=audio_col, text_col=text_col) for index, row in enumerate(rows[:limit], start=1) ] return EvalDataset(label=path.name, items=items) @@ -288,8 +235,7 @@ def _manifest_item( row: dict[str, object], *, audio_col: str, - text_col: str | None, - with_speakers: bool, + text_col: str, ) -> EvalItem: audio = str(row.get(audio_col) or "") if not audio: @@ -311,7 +257,6 @@ def _manifest_item( item_id=item_id, audio=audio, reference=_row_reference(row, text_col, item_id), - turns=_row_turns(row, item_id) if with_speakers else None, ) @@ -338,8 +283,7 @@ def _hf_item( split_name: str, *, audio_col: str, - text_col: str | None, - with_speakers: bool, + text_col: str, ) -> EvalItem: cells = jsonshape.as_mapping(row.get("row")) or {} item_id = f"{split_name}[{row.get('row_idx')}]" @@ -347,7 +291,6 @@ def _hf_item( item_id=item_id, audio=_audio_source(cells.get(audio_col), column=audio_col, item_id=item_id), reference=_row_reference(cells, text_col, item_id), - turns=_row_turns(cells, item_id) if with_speakers else None, ) @@ -359,7 +302,6 @@ def _load_hf( audio_column: str | None, text_column: str | None, limit: int, - with_speakers: bool, ) -> EvalDataset: if not _HF_ID_RE.match(dataset): raise UsageError( @@ -381,14 +323,8 @@ def _load_hf( list(jsonshape.as_mapping(rows[0].get("row")) or {}), audio_column=audio_column, text_column=text_column, - with_speakers=with_speakers, ) return EvalDataset( label=f"{dataset} · {config}/{split_name}", - items=[ - _hf_item( - row, split_name, audio_col=audio_col, text_col=text_col, with_speakers=with_speakers - ) - for row in rows - ], + items=[_hf_item(row, split_name, audio_col=audio_col, text_col=text_col) for row in rows], ) diff --git a/aai_cli/wer.py b/aai_cli/wer.py index b1d59233..df281ece 100644 --- a/aai_cli/wer.py +++ b/aai_cli/wer.py @@ -4,10 +4,10 @@ the alignment math is never re-derived here. Texts are normalized the way ASR benchmarks conventionally are (lowercase, punctuation stripped, whitespace collapsed) so a transcript isn't penalized for casing or punctuation style. -jiwer is imported lazily inside the scoring functions (mirroring ``der.py``'s -lazy pyannote import) so this module stays import-cheap for the command layer — -an install that's missing the eval scoring stack must still run every other -command. No SDK, no Rich: the command layer owns all rendering. +jiwer is imported lazily inside the scoring functions so this module stays +import-cheap for the command layer — an install that's missing the eval scoring +stack must still run every other command. No SDK, no Rich: the command layer +owns all rendering. """ from __future__ import annotations diff --git a/pyproject.toml b/pyproject.toml index b19ea4bd..bdf40452 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -49,11 +49,9 @@ dependencies = [ "packaging>=24.0", # audioop (used for PCM resampling) left the stdlib in 3.13; this backport provides it. "audioop-lts>=0.2; python_version >= '3.13'", - # `assembly eval` scoring: jiwer for WER, pyannote.metrics for DER — the de-facto - # standard implementations of each. pyannote drags numpy/scipy/pandas, so der.py - # imports it lazily to keep CLI startup fast. + # `assembly eval` WER scoring uses jiwer (the de-facto standard WER + # implementation); wer.py imports it lazily to keep CLI startup fast. "jiwer>=4.0", - "pyannote-metrics>=4.0", ] [project.urls] diff --git a/tests/__snapshots__/test_cli_output_snapshots.ambr b/tests/__snapshots__/test_cli_output_snapshots.ambr index fcc007a1..18b8ede0 100644 --- a/tests/__snapshots__/test_cli_output_snapshots.ambr +++ b/tests/__snapshots__/test_cli_output_snapshots.ambr @@ -247,8 +247,7 @@ text; both are normalized first (lowercased, punctuation stripped) so style differences don't count as errors, and the summary pools total errors over total reference words. Handy for picking a model: run once per - --speech-model and compare. --speaker-labels also diarizes and scores DER - against reference speaker turns. + --speech-model and compare. Datasets come from the Hugging Face Hub (any public dataset its viewer serves with audio + reference columns; gated ones need HF_TOKEN), a local @@ -259,8 +258,7 @@ (meetings), gigaspeech, peoples (real-world US English), commonvoice (English; --subset fr etc. for its 98 other locales), voxpopuli (parliament speech), switchboard (phone calls), expresso (expressive - speech), loquacious, and callhome (phone calls with speaker turns, for - --speaker-labels). + speech), loquacious, and callhome (phone calls). ╭─ Arguments ──────────────────────────────────────────────────────────────────╮ │ * dataset TEXT Hugging Face dataset id, or a local .csv/.jsonl │ @@ -268,37 +266,27 @@ │ [required] │ ╰──────────────────────────────────────────────────────────────────────────────╯ ╭─ Options ────────────────────────────────────────────────────────────────────╮ - │ --split TEXT Hugging Face split to │ + │ --split TEXT Hugging Face split to │ │ score (default: test). │ - │ --subset TEXT Hugging Face │ + │ --subset TEXT Hugging Face │ │ config/subset name (e.g. │ │ a language). │ - │ --limit INTEGER RANGE Rows to evaluate (1-100). │ - │ [1<=x<=100] [default: 10] │ - │ --audio-column TEXT Audio column name │ + │ --limit INTEGER RANGE Rows to evaluate (1-100). │ + │ [1<=x<=100] [default: 10] │ + │ --audio-column TEXT Audio column name │ │ (default: auto-detect). │ - │ --text-column TEXT Reference text column │ + │ --text-column TEXT Reference text column │ │ name (default: │ │ auto-detect). │ - │ --speech-model [universal-3-pro|univer Speech model to evaluate. │ - │ sal-2] │ - │ --language-code TEXT Force a language (e.g. │ + │ --speech-model [universal-3-pro|univers Speech model to evaluate. │ + │ al-2] │ + │ --language-code TEXT Force a language (e.g. │ │ en_us). │ - │ --speaker-labels Diarize and also score │ - │ DER against the dataset's │ - │ reference speaker turns │ - │ (speakers/timestamps_sta… │ - │ columns, in seconds). │ - │ --collar FLOAT RANGE [x>=0.0] DER forgiveness (seconds) │ - │ around each reference │ - │ turn boundary (default: │ - │ 1.0; needs │ - │ --speaker-labels). │ - │ --concurrency INTEGER RANGE [x>=1] How many items to │ + │ --concurrency INTEGER RANGE [x>=1] How many items to │ │ transcribe at once │ │ (sequential by default). │ │ [default: 1] │ - │ --json -j Output the rows and │ + │ --json -j Output the rows and │ │ summary as one JSON │ │ object. │ │ --help Show this message and │ @@ -310,14 +298,10 @@ $ assembly eval tedlium Compare models on your own audio $ assembly eval calls.csv --speech-model universal-3-pro - Score diarization too (WER + DER) - $ assembly eval agent-calls.jsonl --speaker-labels More rows, transcribed four at a time $ assembly eval librispeech --limit 50 --concurrency 4 Evaluate non-English audio $ assembly eval commonvoice --subset fr --language-code fr - DER on a diarization benchmark - $ assembly eval callhome --speaker-labels diff --git a/tests/test_der.py b/tests/test_der.py deleted file mode 100644 index cfcffd69..00000000 --- a/tests/test_der.py +++ /dev/null @@ -1,73 +0,0 @@ -"""DER scoring (`aai_cli.der`): pyannote-backed alignment, collar, pooling.""" - -import dataclasses - -import pytest - -from aai_cli import der - - -def _assign(obj, attribute, value): - setattr(obj, attribute, value) - - -def test_turns_and_scores_are_immutable_values(): - with pytest.raises(dataclasses.FrozenInstanceError): - _assign(der.Turn(speaker="a", start=0.0, end=1.0), "speaker", "b") - score = der.DerScore(missed=0.0, false_alarm=0.0, confusion=0.0, total=1.0) - with pytest.raises(dataclasses.FrozenInstanceError): - _assign(score, "total", 2.0) - - -REF = [ - der.Turn(speaker="alice", start=0.0, end=10.0), - der.Turn(speaker="bob", start=10.0, end=20.0), -] -HYP = [der.Turn(speaker="A", start=0.0, end=12.0), der.Turn(speaker="B", start=12.0, end=20.0)] - - -def test_optimal_speaker_mapping_scores_only_the_overlap_error(): - # alice↔A and bob↔B map optimally; the 10-12s stretch is confusion. - score = der.score(REF, HYP) - assert score == der.DerScore(missed=0.0, false_alarm=0.0, confusion=2.0, total=20.0) - assert score.der == 0.1 - - -def test_collar_forgives_boundary_slop(): - # A 4s collar swallows the 10-12s confusion around the 10s boundary. - score = der.score(REF, HYP, collar=4.0) - assert score == der.DerScore(missed=0.0, false_alarm=0.0, confusion=0.0, total=12.0) - assert score.der == 0.0 - - -def test_empty_hypothesis_is_all_missed_detection(): - score = der.score(REF, []) - assert score == der.DerScore(missed=20.0, false_alarm=0.0, confusion=0.0, total=20.0) - assert score.der == 1.0 - - -def test_hypothesis_speech_beyond_the_reference_is_false_alarm(): - # The evaluation extent covers the hypothesis too — speech past the - # reference's end counts as false alarm rather than being cropped away. - score = der.score( - [der.Turn(speaker="a", start=0.0, end=10.0)], - [der.Turn(speaker="A", start=0.0, end=15.0)], - ) - assert score == der.DerScore(missed=0.0, false_alarm=5.0, confusion=0.0, total=10.0) - assert score.der == 0.5 - - -def test_overlapping_turns_by_one_speaker_keep_their_own_tracks(): - turns = [der.Turn(speaker="a", start=0.0, end=10.0), der.Turn(speaker="a", start=5.0, end=15.0)] - assert der.score(turns, turns).der == 0.0 - - -def test_pooled_sums_components(): - total = der.pooled( - [ - der.DerScore(missed=1.0, false_alarm=2.0, confusion=3.0, total=10.0), - der.DerScore(missed=0.0, false_alarm=0.0, confusion=0.0, total=10.0), - ] - ) - assert total == der.DerScore(missed=1.0, false_alarm=2.0, confusion=3.0, total=20.0) - assert total.der == 0.3 diff --git a/tests/test_eval_command.py b/tests/test_eval_command.py index 93310fd5..5a384b04 100644 --- a/tests/test_eval_command.py +++ b/tests/test_eval_command.py @@ -1,4 +1,4 @@ -"""`assembly eval` behavior: WER/DER scoring, rendering, flags, error paths. +"""`assembly eval` behavior: WER scoring, rendering, flags, error paths. The transcription boundary (`client.transcribe`) is mocked; datasets are real temp manifests so the command exercises the loader end to end. @@ -28,8 +28,8 @@ def _auth(): config.set_api_key("default", "sk_live") -def _transcript(text, utterances=None): - return SimpleNamespace(text=text, utterances=utterances) +def _transcript(text): + return SimpleNamespace(text=text) def _write_wer_manifest(tmp_path): @@ -40,29 +40,6 @@ def _write_wer_manifest(tmp_path): ) -def _write_speaker_manifest(tmp_path, *, with_text=False): - (tmp_path / "a.wav").write_bytes(b"fake-audio") - row = { - "audio": "a.wav", - "speakers": ["alice", "bob"], - "timestamps_start": [0.0, 10.0], - "timestamps_end": [10.0, 20.0], - } - if with_text: - row["text"] = "hello there" - (tmp_path / "manifest.jsonl").write_text(json.dumps(row), encoding="utf-8") - - -def _diarized_utterances(): - # Against the manifest's alice 0-10s / bob 10-20s reference: 2s of confusion - # around the 10s boundary. The default 1s collar forgives 10-10.5s and trims - # 2s of boundary speech from the 20s total -> DER 1.5/18 (see test_der.py). - return [ - SimpleNamespace(speaker="A", start=0, end=12000), - SimpleNamespace(speaker="B", start=12000, end=20000), - ] - - def _mock_transcribe(mocker, results): return mocker.patch( "aai_cli.commands.evaluate.client.transcribe", @@ -92,7 +69,6 @@ def test_wer_table_with_per_file_and_pooled_scores(tmp_path, mocker): assert "1 errors" not in result.output assert "manifest.csv" in result.output assert "default model" in result.output - assert "DER" not in result.output def test_summary_pluralizes_errors(tmp_path, mocker): @@ -117,7 +93,6 @@ def test_json_payload_shape(tmp_path, mocker): assert payload["words"] == 4 assert payload["errors"] == 1 assert payload["wer"] == 0.25 - assert "der" not in payload assert payload["rows"][0] == {"item": "a.wav", "words": 2, "errors": 0, "wer": 0.0} assert payload["rows"][1] == {"item": "b.wav", "words": 2, "errors": 1, "wer": 0.5} assert "failed" not in payload # only present when a row failed @@ -131,9 +106,7 @@ def test_speech_model_flag_reaches_config_and_output(tmp_path, mocker, model): result = runner.invoke(app, ["eval", "manifest.csv", "--speech-model", model, "--json"]) assert result.exit_code == 0 assert _payload_of(result)["speech_model"] == model - tx_config = tx.call_args.kwargs["config"] - assert tx_config.speech_models == [model] - assert tx_config.speaker_labels is None # not requested -> omitted, not False + assert tx.call_args.kwargs["config"].speech_models == [model] def test_no_speech_model_leaves_speech_models_unset(tmp_path, mocker): @@ -163,88 +136,6 @@ def test_speech_model_named_in_human_header(tmp_path, mocker): assert "default model" not in result.output -def test_speaker_labels_scores_der_only_when_dataset_has_no_text(tmp_path, mocker): - _auth() - _write_speaker_manifest(tmp_path) - tx = _mock_transcribe(mocker, [_transcript("ignored", _diarized_utterances())]) - result = runner.invoke(app, ["eval", "manifest.jsonl", "--speaker-labels"]) - assert result.exit_code == 0 - assert tx.call_args.kwargs["config"].speaker_labels is True - assert "DER" in result.output - assert "8.33%" in result.output # 1.5/18 under the default 1s collar - # The summary names each DER component; here the error is pure confusion. - assert "missed 0.00%" in result.output - assert "false alarm 0.00%" in result.output - assert "confusion 8.33%" in result.output - assert "WER" not in result.output - assert "WORDS" not in result.output - - -def test_speaker_labels_with_text_scores_both_metrics(tmp_path, mocker): - _auth() - _write_speaker_manifest(tmp_path, with_text=True) - _mock_transcribe(mocker, [_transcript("hello there", _diarized_utterances())]) - result = runner.invoke(app, ["eval", "manifest.jsonl", "--speaker-labels", "--json"]) - assert result.exit_code == 0 - payload = _payload_of(result) - assert payload["wer"] == 0.0 - assert payload["der"] == 1.5 / 18 # default 1s collar - assert payload["der_breakdown"] == {"missed": 0.0, "false_alarm": 0.0, "confusion": 1.5 / 18} - assert payload["rows"][0]["wer"] == 0.0 - assert payload["rows"][0]["der"] == 1.5 / 18 - - -def test_both_metrics_render_in_one_table(tmp_path, mocker): - _auth() - _write_speaker_manifest(tmp_path, with_text=True) - _mock_transcribe(mocker, [_transcript("hello there", _diarized_utterances())]) - result = runner.invoke(app, ["eval", "manifest.jsonl", "--speaker-labels"]) - assert "WER" in result.output - assert "DER" in result.output - assert "8.33%" in result.output - - -def test_der_counts_leading_speech_the_model_missed(tmp_path, mocker): - _auth() - (tmp_path / "a.wav").write_bytes(b"fake-audio") - row = { - "audio": "a.wav", - "speakers": ["alice"], - "timestamps_start": [0.0], - "timestamps_end": [10.0], - } - (tmp_path / "m.jsonl").write_text(json.dumps(row), encoding="utf-8") - # The hypothesis starts 1000ms in (1.0s exactly after ms→s conversion): - # 1s missed over 10s of reference speech, scored without collar forgiveness. - utterances = [SimpleNamespace(speaker="A", start=1000, end=10000)] - _mock_transcribe(mocker, [_transcript("x", utterances)]) - result = runner.invoke(app, ["eval", "m.jsonl", "--speaker-labels", "--collar", "0", "--json"]) - payload = _payload_of(result) - assert payload["der"] == 0.1 - # All missed detection — distinguishes the breakdown keys from one another. - assert payload["der_breakdown"] == {"missed": 0.1, "false_alarm": 0.0, "confusion": 0.0} - - -def test_der_counts_trailing_hypothesis_speech_as_false_alarm(tmp_path, mocker): - _auth() - (tmp_path / "a.wav").write_bytes(b"fake-audio") - row = { - "audio": "a.wav", - "speakers": ["alice"], - "timestamps_start": [0.0], - "timestamps_end": [10.0], - } - (tmp_path / "m.jsonl").write_text(json.dumps(row), encoding="utf-8") - # The hypothesis keeps "speaking" 5s past the 10s reference: 5s of false - # alarm over 10s of reference speech, scored without collar forgiveness. - utterances = [SimpleNamespace(speaker="A", start=0, end=15000)] - _mock_transcribe(mocker, [_transcript("x", utterances)]) - result = runner.invoke(app, ["eval", "m.jsonl", "--speaker-labels", "--collar", "0", "--json"]) - payload = _payload_of(result) - assert payload["der"] == 0.5 - assert payload["der_breakdown"] == {"missed": 0.0, "false_alarm": 0.5, "confusion": 0.0} - - def _assign(obj, attribute, value): setattr(obj, attribute, value) @@ -252,21 +143,11 @@ def _assign(obj, attribute, value): def test_item_results_are_immutable(): from aai_cli.commands.evaluate import _ItemResult - result = _ItemResult(row={}, words=None, speakers=None) + result = _ItemResult(row={}, words=None) with pytest.raises(dataclasses.FrozenInstanceError): _assign(result, "words", None) -def test_collar_flag_loosens_der(tmp_path, mocker): - _auth() - _write_speaker_manifest(tmp_path) - _mock_transcribe(mocker, [_transcript("ignored", _diarized_utterances())]) - result = runner.invoke( - app, ["eval", "manifest.jsonl", "--speaker-labels", "--collar", "4.0", "--json"] - ) - assert _payload_of(result)["der"] == 0.0 - - def test_missing_transcript_text_scores_as_all_deletions(tmp_path, mocker): _auth() (tmp_path / "a.wav").write_bytes(b"fake-audio") @@ -291,7 +172,6 @@ def test_loader_defaults(tmp_path, mocker): assert result.exit_code == 0 kwargs = load.call_args.kwargs assert kwargs["limit"] == 10 - assert kwargs["with_speakers"] is False assert kwargs["split"] is None and kwargs["subset"] is None assert kwargs["audio_column"] is None and kwargs["text_column"] is None @@ -348,16 +228,6 @@ def fake_status(message, *, json_mode, quiet): assert seen == ["[1/2] Transcribing a.wav…", "[2/2] Transcribing b.wav…"] -def test_collar_without_speaker_labels_is_a_usage_error(mocker): - # Mirrors transcribe's --speakers-expected guard: a silently inert flag is a bug. - tx = mocker.patch("aai_cli.commands.evaluate.client.transcribe", autospec=True) - result = runner.invoke(app, ["eval", "org/ds", "--collar", "0.5"]) - assert result.exit_code == 2 - assert "--collar only applies" in result.output - assert "Add --speaker-labels." in result.output - tx.assert_not_called() - - def test_missing_manifest_is_a_usage_failure(tmp_path): _auth() result = runner.invoke(app, ["eval", "nope.csv"]) diff --git a/tests/test_eval_data_hf.py b/tests/test_eval_data_hf.py index ea38a59c..72c42311 100644 --- a/tests/test_eval_data_hf.py +++ b/tests/test_eval_data_hf.py @@ -10,7 +10,7 @@ import httpx2 as httpx import pytest -from aai_cli import der, eval_data, eval_hf_api +from aai_cli import eval_data, eval_hf_api from aai_cli.errors import APIError, UsageError # ------------------------------------------------------- Hugging Face datasets @@ -96,13 +96,6 @@ def test_hf_unusable_audio_cell_errors(monkeypatch): assert "test[0]" in exc.value.message and "no audio URL" in exc.value.message -def test_hf_speaker_rows(monkeypatch): - row = _hf_row(speakers=["alice"], timestamps_start=[0.5], timestamps_end=[2.0]) - _hf_handler(monkeypatch, splits=_ONE_SPLIT, rows=[row]) - data = eval_data.load("org/ds", limit=1, with_speakers=True) - assert data.items[0].turns == [der.Turn(speaker="alice", start=0.5, end=2.0)] - - def test_hf_single_named_config_auto_picked(monkeypatch): seen = [] splits = [{"config": "clean", "split": "test"}] diff --git a/tests/test_eval_data_manifest.py b/tests/test_eval_data_manifest.py index 9709e57d..aa8c5109 100644 --- a/tests/test_eval_data_manifest.py +++ b/tests/test_eval_data_manifest.py @@ -9,7 +9,7 @@ import pytest -from aai_cli import der, eval_data +from aai_cli import eval_data from aai_cli.errors import CLIError, UsageError # ---------------------------------------------------------------- local manifests @@ -48,7 +48,6 @@ def test_csv_manifest_loads_items(tmp_path): assert [item.item_id for item in data.items] == ["a.wav", "b.wav"] assert data.items[0].audio == str(tmp_path / "a.wav") assert data.items[0].reference == "hello there" - assert data.items[0].turns is None def test_manifest_respects_limit(tmp_path): @@ -198,84 +197,3 @@ def test_split_and_subset_rejected_for_manifests(tmp_path): eval_data.load(str(manifest), split="test", limit=10) with pytest.raises(UsageError, match="local manifests"): eval_data.load(str(manifest), subset="default", limit=10) - - -# ------------------------------------------------- manifests with speaker turns - - -def _speaker_row(audio, **extra): - return { - "audio": audio, - "speakers": ["alice", "bob"], - "timestamps_start": [0.0, 10.0], - "timestamps_end": [10.0, 20.0], - **extra, - } - - -def test_speaker_manifest_loads_turns_without_text(tmp_path): - _write_audio(tmp_path, "a.wav") - manifest = tmp_path / "m.jsonl" - _write_jsonl(manifest, [_speaker_row("a.wav")]) - data = eval_data.load(str(manifest), limit=10, with_speakers=True) - assert data.items[0].reference is None - assert data.items[0].turns == [ - der.Turn(speaker="alice", start=0.0, end=10.0), - der.Turn(speaker="bob", start=10.0, end=20.0), - ] - - -def test_speaker_manifest_with_text_scores_both(tmp_path): - _write_audio(tmp_path, "a.wav") - manifest = tmp_path / "m.jsonl" - _write_jsonl(manifest, [_speaker_row("a.wav", text="hello world")]) - data = eval_data.load(str(manifest), limit=10, with_speakers=True) - assert data.items[0].reference == "hello world" - assert data.items[0].turns is not None - - -def test_speaker_manifest_with_explicit_text_column(tmp_path): - _write_audio(tmp_path, "a.wav") - manifest = tmp_path / "m.jsonl" - _write_jsonl(manifest, [_speaker_row("a.wav", ref="hello world")]) - data = eval_data.load(str(manifest), text_column="ref", limit=10, with_speakers=True) - assert data.items[0].reference == "hello world" - - -def test_speakers_requested_but_columns_missing(tmp_path): - manifest = tmp_path / "m.jsonl" - _write_jsonl(manifest, [{"audio": "a.wav", "text": "x", "speakers": ["a"]}]) - with pytest.raises(UsageError) as exc: - eval_data.load(str(manifest), limit=10, with_speakers=True) - assert "missing: timestamps_start, timestamps_end" in exc.value.message - assert exc.value.suggestion is not None and ".jsonl" in exc.value.suggestion - - -def test_speaker_arrays_of_unequal_length_rejected(tmp_path): - _write_audio(tmp_path, "a.wav") - manifest = tmp_path / "m.jsonl" - row = _speaker_row("a.wav") - row["timestamps_end"] = [10.0] - _write_jsonl(manifest, [row]) - with pytest.raises(UsageError, match="equal-length"): - eval_data.load(str(manifest), limit=10, with_speakers=True) - - -def test_empty_speaker_arrays_rejected(tmp_path): - _write_audio(tmp_path, "a.wav") - manifest = tmp_path / "m.jsonl" - row = {"audio": "a.wav", "speakers": [], "timestamps_start": [], "timestamps_end": []} - _write_jsonl(manifest, [row]) - with pytest.raises(UsageError, match="non-empty"): - eval_data.load(str(manifest), limit=10, with_speakers=True) - - -def test_csv_cells_cannot_hold_speaker_arrays(tmp_path): - _write_audio(tmp_path, "a.wav") - manifest = tmp_path / "m.csv" - manifest.write_text( - 'audio,speakers,timestamps_start,timestamps_end\na.wav,"[""alice""]","[0.0]","[10.0]"\n', - encoding="utf-8", - ) - with pytest.raises(UsageError, match=r"equal-length|non-empty"): - eval_data.load(str(manifest), limit=10, with_speakers=True) diff --git a/tests/test_import_hygiene.py b/tests/test_import_hygiene.py index b9ce603d..4129e348 100644 --- a/tests/test_import_hygiene.py +++ b/tests/test_import_hygiene.py @@ -1,10 +1,10 @@ """The CLI's import chain must stay free of the heavy eval scoring stack. -`assembly eval`'s WER/DER scoring pulls in jiwer and pyannote.metrics (which -drags numpy/scipy/pandas). Those imports are lazy by design (`wer.py`/`der.py`), -so every other command keeps working on an install that doesn't ship the -scoring stack — a module-scope `import jiwer` once crashed *every* invocation -(even `--help`) of a Homebrew install whose formula lacked those resources. +`assembly eval`'s WER scoring pulls in jiwer (and its rapidfuzz backend). That +import is lazy by design (`wer.py`), so every other command keeps working on an +install that doesn't ship the scoring stack — a module-scope `import jiwer` once +crashed *every* invocation (even `--help`) of a Homebrew install whose formula +lacked those resources. """ from __future__ import annotations @@ -15,7 +15,7 @@ _PROBE = """ import sys import aai_cli.main -heavy = {"jiwer", "rapidfuzz", "pyannote", "numpy", "scipy", "pandas", "sklearn"} +heavy = {"jiwer", "rapidfuzz"} loaded = sorted(heavy & {name.split(".")[0] for name in sys.modules}) assert not loaded, f"CLI import eagerly loaded: {loaded}" """ diff --git a/uv.lock b/uv.lock index ee36f30c..4874e92e 100644 --- a/uv.lock +++ b/uv.lock @@ -29,7 +29,6 @@ dependencies = [ { name = "openai" }, { name = "packaging" }, { name = "platformdirs" }, - { name = "pyannote-metrics" }, { name = "pydantic" }, { name = "questionary" }, { name = "rich" }, @@ -81,7 +80,6 @@ requires-dist = [ { name = "openai", specifier = ">=2.41.0" }, { name = "packaging", specifier = ">=24.0" }, { name = "platformdirs", specifier = ">=4.10.0" }, - { name = "pyannote-metrics", specifier = ">=4.0" }, { name = "pydantic", specifier = ">=2.13.4" }, { name = "questionary", specifier = ">=2.0.1" }, { name = "rich", specifier = ">=15.0.0" }, @@ -1011,15 +1009,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/69/c9/172c525330c739a068c01050759a6f855ce16212db10a0359e690a03ac48/jiwer-4.0.0-py3-none-any.whl", hash = "sha256:7efaf0bd336b095d99ddef9dd67e1ee829d75d58aa2a81d9639870b01d6d95ea", size = 23034, upload-time = "2025-06-19T16:05:21.821Z" }, ] -[[package]] -name = "joblib" -version = "1.5.3" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/41/f2/d34e8b3a08a9cc79a50b2208a93dce981fe615b64d5a4d4abee421d898df/joblib-1.5.3.tar.gz", hash = "sha256:8561a3269e6801106863fd0d6d84bb737be9e7631e33aaed3fb9ce5953688da3", size = 331603, upload-time = "2025-12-15T08:41:46.427Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/7b/91/984aca2ec129e2757d1e4e3c81c3fcda9d0f85b74670a094cc443d9ee949/joblib-1.5.3-py3-none-any.whl", hash = "sha256:5fc3c5039fc5ca8c0276333a188bbd59d6b7ab37fe6632daa76bc7f9ec18e713", size = 309071, upload-time = "2025-12-15T08:41:44.973Z" }, -] - [[package]] name = "keyring" version = "25.7.0" @@ -1255,15 +1244,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/79/7b/2c79738432f5c924bef5071f933bcc9efd0473bac3b4aa584a6f7c1c8df8/mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505", size = 4963, upload-time = "2025-04-22T14:54:22.983Z" }, ] -[[package]] -name = "narwhals" -version = "2.22.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/9c/1c/c80cb7719721a44846c6301ef118434bae30a423924bfad3a47f16bdc064/narwhals-2.22.0.tar.gz", hash = "sha256:6486282bb7e4b4ab55963efbd8be1451b764cc4874b74d1fd625eba9dc60b86f", size = 417565, upload-time = "2026-06-01T13:34:36.249Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/1f/b6/e7cdde7b8e90d5dff25b622f95833ef26567ad184c977278b93a1cbd5717/narwhals-2.22.0-py3-none-any.whl", hash = "sha256:1421797ede01789cc1537619dbc3f36f840737240f748fdb24a60a0225fc80be", size = 453815, upload-time = "2026-06-01T13:34:34.127Z" }, -] - [[package]] name = "nodeenv" version = "1.10.0" @@ -1273,67 +1253,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/88/b2/d0896bdcdc8d28a7fc5717c305f1a861c26e18c05047949fb371034d98bd/nodeenv-1.10.0-py2.py3-none-any.whl", hash = "sha256:5bb13e3eed2923615535339b3c620e76779af4cb4c6a90deccc9e36b274d3827", size = 23438, upload-time = "2025-12-20T14:08:52.782Z" }, ] -[[package]] -name = "numpy" -version = "2.4.6" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d0/ad/fed0499ce6a338d2a03ebae59cd15093910c8875328855781952abf6c2fe/numpy-2.4.6.tar.gz", hash = "sha256:f3a3570c4a2a16746ac2c31a7c7c7b0c186b95ce902e33db6f28094ed7387dda", size = 20735807, upload-time = "2026-05-18T23:37:14.07Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/95/2a/3d7b5ac8aac24feaf9ad7ed58f45b0bbc06d37e4338ae84c9f2298b570f9/numpy-2.4.6-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:001fbb8e08d942dd57599e781f2472269ee7f2755fae407b4f67b2f0b17da3f1", size = 16689119, upload-time = "2026-05-18T23:33:54.065Z" }, - { url = "https://files.pythonhosted.org/packages/ea/12/92c4c131527599e8288d6918e888d88726f84d805d784b771f32408aeaef/numpy-2.4.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ebfb099f8dcf083deef3ac1ca4c1503f387cf76296fcb3816b66f5ecb5f54fdb", size = 14699246, upload-time = "2026-05-18T23:33:57.621Z" }, - { url = "https://files.pythonhosted.org/packages/ad/fe/c0a6b7b2ca128a8fb228575147073b660656734b8ebe4d76c8fd748dcc79/numpy-2.4.6-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:3213d622a0283a39a93d188f3cf72b26862df52fbb4ca3697f51705016523d41", size = 5204410, upload-time = "2026-05-18T23:34:00.302Z" }, - { url = "https://files.pythonhosted.org/packages/f3/d4/9770d14ba719432bb90a421bfd443872ed0f70f7264b64bec12ea363d5fd/numpy-2.4.6-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:357cc07a6d7b0b182ff02249616a03742827ebb1277546b5c7cd7f7620a45698", size = 6551240, upload-time = "2026-05-18T23:34:02.852Z" }, - { url = "https://files.pythonhosted.org/packages/c9/c6/50a46a6205feba2343f1d6d17438107c5dc491ed1c736e6ea68689fd906b/numpy-2.4.6-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5f9fb9157b4ce2971008323afe46053787b526ef624fea915b261468a8421a0f", size = 15671012, upload-time = "2026-05-18T23:34:05.485Z" }, - { url = "https://files.pythonhosted.org/packages/99/60/14115e6364fa676c5397c2ad3004e527e9aa487abf5d0706ec81bbd08529/numpy-2.4.6-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:90f9849678c75fe7afa2d348ac842c168b0a4d3d61919687216dfc547976d853", size = 16645538, upload-time = "2026-05-18T23:34:09.265Z" }, - { url = "https://files.pythonhosted.org/packages/ae/c5/693cbe59e57db94d2231fa519ca3978dc9e19da5a8f088588f5c6e947ff2/numpy-2.4.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:c1a2af6c6ef86344a6b0db6b97834208bf598db514f2b155042439b62605601a", size = 17020706, upload-time = "2026-05-18T23:34:13.053Z" }, - { url = "https://files.pythonhosted.org/packages/ef/fc/85b7c4eff9b4966ade25c2273cf7e7012e92366c032058653934b37de044/numpy-2.4.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e5805d5a22fd19c8ccff10a9561f9df94436b0545619ea579db2d3c35294bce2", size = 18368541, upload-time = "2026-05-18T23:34:17.024Z" }, - { url = "https://files.pythonhosted.org/packages/f6/81/e1b27545deedce7f4a0b348618c6b62d74e36a4dc9ccd42f3eb2f85eee32/numpy-2.4.6-cp312-cp312-win32.whl", hash = "sha256:e3eeb0aabd6bd5ce64faae67e9935203a6991b4bc2a485a767fbafb2c5125f45", size = 5962825, upload-time = "2026-05-18T23:34:20.3Z" }, - { url = "https://files.pythonhosted.org/packages/ab/ca/feab00bd44aa5fe1ad2c18f08b4d3bb92e26484b0b1d1443897809ed528c/numpy-2.4.6-cp312-cp312-win_amd64.whl", hash = "sha256:d8e8286dd7cea7895157318d1b91cdacac64c479f3cbc8dce548331728484751", size = 12321687, upload-time = "2026-05-18T23:34:23.095Z" }, - { url = "https://files.pythonhosted.org/packages/63/cf/5a6d34850a39d1093558564f77ee8e8e0bee5061151b8f05a55711001ec7/numpy-2.4.6-cp312-cp312-win_arm64.whl", hash = "sha256:4081eb135ac24158bd51cdfbef16f1c64df7063b1143f24731387137c092bec8", size = 10221482, upload-time = "2026-05-18T23:34:25.876Z" }, - { url = "https://files.pythonhosted.org/packages/fb/82/bdab26d7438c6791ca31b7c024ca37c1eab8b726ba236129005cd4a06e45/numpy-2.4.6-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:511dbaf848decaaaf4b4ca48032619fb3138710c4bf7da7617765edad1ef96b0", size = 16684648, upload-time = "2026-05-18T23:34:29.41Z" }, - { url = "https://files.pythonhosted.org/packages/1b/30/a80189bcc7f5e4258b3fbc3968d909d1756f54d023299ecc39ad6fdb9ef8/numpy-2.4.6-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:bf162abab1c1a736333192707cef898e735a5ca00f38f27eeedf44b39d9e85eb", size = 14693902, upload-time = "2026-05-18T23:34:33.013Z" }, - { url = "https://files.pythonhosted.org/packages/97/12/70b5d0d7c15e1ebb8a6a84a8caa1d19e181d84fb58bb6d70aca29099dec1/numpy-2.4.6-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:043191bfa8eab18c776647b62723ac9dddece59743b13f49b2016094129c2b3f", size = 5198992, upload-time = "2026-05-18T23:34:36.132Z" }, - { url = "https://files.pythonhosted.org/packages/ba/8c/ebd2a8f8a83541f8d38cc5667e8c2b69cecfd30da6e45693e8158857d44b/numpy-2.4.6-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:6180d8b35af935aed8ece3a85e0a43f87393ae0ac87c8d2c8bd2c993f7270ef3", size = 6546944, upload-time = "2026-05-18T23:34:38.484Z" }, - { url = "https://files.pythonhosted.org/packages/bb/c5/7b863a97a91671a0338f4253bd3b5a3d3852f0692dae91711c9f4a10e787/numpy-2.4.6-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:72fbe16c6fac95aedf5937fa873445cec2110be35d8a4e9433d7501fd98dae6b", size = 15669392, upload-time = "2026-05-18T23:34:41.257Z" }, - { url = "https://files.pythonhosted.org/packages/a5/9d/3584b9984ca4c047aea75214ce1a4c4c73d849bd71b604264b7f5653f8a8/numpy-2.4.6-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a7830bab239b79cda9c08c2da014761cafb48da6150e1da17ac06283f43b6089", size = 16633220, upload-time = "2026-05-18T23:34:45.075Z" }, - { url = "https://files.pythonhosted.org/packages/05/ae/7c67fba23bd98caec7c99261f3a16072ade14813486b0282cb29846de832/numpy-2.4.6-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ef4aea96ce4d3b074422cb4f2f64e216bf9e213004bb58ecfdf50ea02ea8eb9a", size = 17020800, upload-time = "2026-05-18T23:34:49.065Z" }, - { url = "https://files.pythonhosted.org/packages/d9/5d/3b6725cb31d983c5e66916f5d36f6d7e5521129e4c4404d64f918292a5b6/numpy-2.4.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:dfa20cc6ca228e6b155b11da03825975ce66aea520985dbbddf0f2a5a495c605", size = 18357600, upload-time = "2026-05-18T23:34:52.709Z" }, - { url = "https://files.pythonhosted.org/packages/f7/da/2ccc6c2fe8898dee01d90c75c5f5f914a23daf99e3e0f59516a08760c8b5/numpy-2.4.6-cp313-cp313-win32.whl", hash = "sha256:56b39e5e0622a09a25bf5baf62f4bcf0cb8a41ae6e2819cf49bbc5a74c083f91", size = 5961134, upload-time = "2026-05-18T23:34:55.618Z" }, - { url = "https://files.pythonhosted.org/packages/b5/cd/9cc4dc876fb065d5c220aae4d5e14826b2715331bb7618ce1fb07a679d99/numpy-2.4.6-cp313-cp313-win_amd64.whl", hash = "sha256:c4fc99836233ea196540b17ab0983aff60ed07941751930f5f4d05bc3b3b7359", size = 12318598, upload-time = "2026-05-18T23:34:58.928Z" }, - { url = "https://files.pythonhosted.org/packages/39/1e/c0bcba1f8694116485fe28fd1be698c278fcda4141c5b0e53a2aed8b12a8/numpy-2.4.6-cp313-cp313-win_arm64.whl", hash = "sha256:a7c711e21628b52034bb5ab8d1bce291f752fcc5e92accc615778acee1ff4778", size = 10222272, upload-time = "2026-05-18T23:35:02.167Z" }, - { url = "https://files.pythonhosted.org/packages/63/6d/cc5619247c8f4204e507f5883528372e4ac4bb189e579fb859a12e480b1f/numpy-2.4.6-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:112b06a867b235ef466ed3508ddf0238050df9c727cafb5301ac385b899189a1", size = 14821197, upload-time = "2026-05-18T23:35:05.468Z" }, - { url = "https://files.pythonhosted.org/packages/00/58/f1c39161c87d9e9bed660f1ed4bafc0e403d5ec9650b6dd77aead07d489b/numpy-2.4.6-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:eaf7fa2de5c0be8ae6ff8e9bea2ccd725e980541244521d8d4b5f3354a27babe", size = 5326287, upload-time = "2026-05-18T23:35:08.693Z" }, - { url = "https://files.pythonhosted.org/packages/af/57/3917ab0fd97f271a8694513581b8a36c655f111c446852c302f04ccdb6fc/numpy-2.4.6-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:7265a2f3d436e54ef9f2b52b5c937e6be778781bd97a590319d7348f1c1ca997", size = 6646763, upload-time = "2026-05-18T23:35:11.459Z" }, - { url = "https://files.pythonhosted.org/packages/eb/0f/037e64c494b67581ae18193d770adef354c41f3f2c8ebf865602d949bf8f/numpy-2.4.6-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f74a575920ab21fe304421a3fc28793d82e299cae9eccb37084e9fc7f3617c20", size = 15728070, upload-time = "2026-05-18T23:35:14.79Z" }, - { url = "https://files.pythonhosted.org/packages/21/a6/5d2bae9c9542eb4df16dc9c46dc79c186e9bad53805dfa5399a6023c6db0/numpy-2.4.6-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ede83e07a75dd06bc501566c1eca2afc0d61677c1472ac9ad93fdee6e638a48d", size = 16681752, upload-time = "2026-05-18T23:35:18.836Z" }, - { url = "https://files.pythonhosted.org/packages/92/14/23d1dfb410ae362cd59ce53e936b1513d545eb40db3949ced632e19a459e/numpy-2.4.6-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:68bb27509ac1b9a3443094260f6326150663b06abe40b73a2f81160623da5b67", size = 17086024, upload-time = "2026-05-18T23:35:22.52Z" }, - { url = "https://files.pythonhosted.org/packages/4b/6e/23595a2c642cdf3bc567877064bdd7f91c8b0038a4453cf2daf7248eafe9/numpy-2.4.6-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:a0df0043bdb289bde1f62da130d20df23d58b45429f752bc7a8fc5325a225ecd", size = 18403398, upload-time = "2026-05-18T23:35:26.398Z" }, - { url = "https://files.pythonhosted.org/packages/8a/90/0ac3bc947217e66dec77e7cbc6a1979d1af70b6461b82f620d3bccd5e4c8/numpy-2.4.6-cp313-cp313t-win32.whl", hash = "sha256:29a287e0cf63ff528da061de6b9f64a4618da591ca1046aafc54062e40ca7eab", size = 6084971, upload-time = "2026-05-18T23:35:29.387Z" }, - { url = "https://files.pythonhosted.org/packages/77/71/5673e351671a1d2bd6063b91b44f70c0affea7d1516fa7a6572941ba4aa1/numpy-2.4.6-cp313-cp313t-win_amd64.whl", hash = "sha256:25c692919ac5a01f170a3bfcd62d745b24fd095c353d50812637d6fcab442e75", size = 12458532, upload-time = "2026-05-18T23:35:32.175Z" }, - { url = "https://files.pythonhosted.org/packages/3f/88/19d3503c5046e688f049274b27a3ef3d771152fa80d3ba3d01a3dff61abe/numpy-2.4.6-cp313-cp313t-win_arm64.whl", hash = "sha256:1e978ec1e8bd0e0e4de6bb75de9d30cbb74db6b6a2bb727618613703ca0167dd", size = 10291881, upload-time = "2026-05-18T23:35:35.465Z" }, - { url = "https://files.pythonhosted.org/packages/f8/91/3ab2044d05fd16d343c5ac2e69b127f1b2854040dd20b193257c78028bd3/numpy-2.4.6-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:06ca2f61ec4385a07a6977c55ba998a4466c123642b4a32694d3128fce18c079", size = 16683458, upload-time = "2026-05-18T23:35:38.353Z" }, - { url = "https://files.pythonhosted.org/packages/8e/62/764ce66fa4147ae6d73071a3abf804ffe606f174618697c571acdf26a7c9/numpy-2.4.6-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:38efbc8de75c7a0fc1ac190162d892787f3f47b57cc291231aafee36b80982b7", size = 14704559, upload-time = "2026-05-18T23:35:42.14Z" }, - { url = "https://files.pythonhosted.org/packages/60/61/23f27c172f022e04025b7dc2367f4d63c1a398120607ec896228649a6f48/numpy-2.4.6-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:d581b735e177fdcdce6fed8e7e8880a3fb6ee4e3653a3ac6af01c6f4c03effc5", size = 5209716, upload-time = "2026-05-18T23:35:45.377Z" }, - { url = "https://files.pythonhosted.org/packages/03/71/21cf70dc6ea3e3acb95fc53a265b2fc248b981f0194ceb5b475271b8809d/numpy-2.4.6-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:0a041d3d761dc3c35cc56ce0351506a02bcbc25f7b169f652435141a17db9096", size = 6543947, upload-time = "2026-05-18T23:35:47.926Z" }, - { url = "https://files.pythonhosted.org/packages/d5/91/64288395ee1799bd2e0b04a305dce9666da90c961e1f3fe982a05ee1c036/numpy-2.4.6-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:40fdc1ae7125e518ea98e53e69a4ebc27e1fd50510c47b7ea130cf21e5e1d42b", size = 15685197, upload-time = "2026-05-18T23:35:50.863Z" }, - { url = "https://files.pythonhosted.org/packages/f3/eb/ebffaa97dc55502df69584a8f0dcf07f69a3e0b3e2323670a2722db9aa39/numpy-2.4.6-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a2c306dea656c12c68f51f4cea133cbe78ca7435eb28c735eac1d3ebe73be6e8", size = 16638245, upload-time = "2026-05-18T23:35:54.752Z" }, - { url = "https://files.pythonhosted.org/packages/b8/0b/54f9da33128d7e350fab89c7455902eeae70349ee52bddb448dc4a576f45/numpy-2.4.6-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:33111801a01c12a8a1e3721f0a9232f8cfc8ae2c6b7098167e6f623c6073f402", size = 17036587, upload-time = "2026-05-18T23:35:58.355Z" }, - { url = "https://files.pythonhosted.org/packages/b6/f0/fdebc1052db1cc37c64beb22072d67cd6d1c71adca1299f53dec2b5e20d3/numpy-2.4.6-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:ae506e6902902557576a26ff33eda8695e7ecb3cb36c3b573a0765dee114ebdb", size = 18363226, upload-time = "2026-05-18T23:36:02.845Z" }, - { url = "https://files.pythonhosted.org/packages/aa/b4/298628d98c72b57e57f7165ae6a481a1deaf6f3c28262a6e4c739c275930/numpy-2.4.6-cp314-cp314-win32.whl", hash = "sha256:aaf159caa35993cb1f56fb9b8e4610d35758e7ca005412eb1daa856a78c9c4b1", size = 6010196, upload-time = "2026-05-18T23:36:05.92Z" }, - { url = "https://files.pythonhosted.org/packages/df/ac/46de6dda46478f7942f839e094970be2d4a861e005c4b3bf07c92e291a09/numpy-2.4.6-cp314-cp314-win_amd64.whl", hash = "sha256:b507f5c4c1d508876d1819b6bf9a49d365b96320b5d4993426b33a23ca4b8261", size = 12450334, upload-time = "2026-05-18T23:36:09.107Z" }, - { url = "https://files.pythonhosted.org/packages/78/92/b8b798ac784102c0da830d2257d59358e3d3d90d1e2b3f2575dad976c5cf/numpy-2.4.6-cp314-cp314-win_arm64.whl", hash = "sha256:6f41ae150c4e32db4f3310cdaf64b1593a03dbabe29eec77fc9b50fe64061df6", size = 10495678, upload-time = "2026-05-18T23:36:12.766Z" }, - { url = "https://files.pythonhosted.org/packages/30/34/ec28d1aa8115971537c01469ab2011ee96827930f0a124de1000cc2a7ed7/numpy-2.4.6-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:ece3d2cfe132e7d51f44a832b303895e6f2d499c5e74dfbdb06ee246147a304a", size = 14823672, upload-time = "2026-05-18T23:36:16.473Z" }, - { url = "https://files.pythonhosted.org/packages/16/bd/f6d1fede4e54e8042a7ff97bb495510f3c220f94bcd9e8b228e87c92cc0d/numpy-2.4.6-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:e3e5193ef5a3dc73bceee50f7fdc2c90dbb76c42df8d8fae3d1067a583df579e", size = 5328731, upload-time = "2026-05-18T23:36:19.767Z" }, - { url = "https://files.pythonhosted.org/packages/f4/f0/e105b9e2fd728a9910103884decd6951d9dd73896b914a98d9a231de02ee/numpy-2.4.6-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:17f9ade344e7d9b464a084d69bcf18fc691cb1db67c62ed80820bf4926d78f0e", size = 6649805, upload-time = "2026-05-18T23:36:22.266Z" }, - { url = "https://files.pythonhosted.org/packages/82/dd/1206a7ca6ab15e3f02069707ca96222e202af681bb73756da7527f3cb837/numpy-2.4.6-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9cd5ffd25db4e7ba6a375693b3fc0fc1791ec636c17db3720da19bde7180ec43", size = 15730496, upload-time = "2026-05-18T23:36:25.713Z" }, - { url = "https://files.pythonhosted.org/packages/51/e7/38d3ea825dcab85a591734decb2f6c67caa7c8367d374df1a1c3842f9b07/numpy-2.4.6-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7d92c3819208a60205a12a245c91ad70cb0a85336659b19b834205573ac8456e", size = 16679616, upload-time = "2026-05-18T23:36:29.652Z" }, - { url = "https://files.pythonhosted.org/packages/93/b7/caabfdf53edf663e0b4eb74d7d405d83baef09eb5e83bcd32d601d72b93e/numpy-2.4.6-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e85b752a1e912b70eaad4fafbd4d1238007ab221de2009b9a2f5ae7461239895", size = 17085145, upload-time = "2026-05-18T23:36:33.449Z" }, - { url = "https://files.pythonhosted.org/packages/f9/45/68d7c33a6bcf3e5aa3bdbd57a367e6f615286dfd6482f97e8ffeb734306e/numpy-2.4.6-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:29cb7f67d10b479ff07c17d33e39f78c07f71c40ef30d63c153d340e96cd3fb4", size = 18403813, upload-time = "2026-05-18T23:36:37.369Z" }, - { url = "https://files.pythonhosted.org/packages/9c/50/0753655aa844c99cd9e018aacf76f130f1bd81d881bb74bc0aef5d73a8ba/numpy-2.4.6-cp314-cp314t-win32.whl", hash = "sha256:260a5d70215b61ab4fadf5c7baacd64821842975eea312125ed3c39a6391b063", size = 6156982, upload-time = "2026-05-18T23:36:40.817Z" }, - { url = "https://files.pythonhosted.org/packages/b2/d4/7c67becf668f973cb490cec3e98dfd799d866f9c989a54d355672cfa0db6/numpy-2.4.6-cp314-cp314t-win_amd64.whl", hash = "sha256:81a1cca95ed5bb92aa8b10dd2cdc9a0d3853a50fad926c28b5d7e8ea54389627", size = 12638908, upload-time = "2026-05-18T23:36:43.996Z" }, - { url = "https://files.pythonhosted.org/packages/43/bb/e1c71a4295b1b1d1393d50dbb4f2a36283c6859d9d3892e84f00ec5a91d5/numpy-2.4.6-cp314-cp314t-win_arm64.whl", hash = "sha256:0c9136e14ed34a9e343a31c533d78a9813a69a3148332bce5e9821cb2f996e66", size = 10565867, upload-time = "2026-05-18T23:36:47.114Z" }, -] - [[package]] name = "openai" version = "2.41.0" @@ -1362,58 +1281,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/df/b2/87e62e8c3e2f4b32e5fe99e0b86d576da1312593b39f47d8ceef365e95ed/packaging-26.2-py3-none-any.whl", hash = "sha256:5fc45236b9446107ff2415ce77c807cee2862cb6fac22b8a73826d0693b0980e", size = 100195, upload-time = "2026-04-24T20:15:22.081Z" }, ] -[[package]] -name = "pandas" -version = "3.0.3" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "numpy" }, - { name = "python-dateutil" }, - { name = "tzdata", marker = "sys_platform == 'emscripten' or sys_platform == 'win32'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/f8/87/4341c6252d1c47b08768c3d25ac487362bf403f0313ddae4a2a26c9b1b4c/pandas-3.0.3.tar.gz", hash = "sha256:696a4a00a2a2a35d4e5deb3fc946641b96c944f02230e4f76137fe35d806c4fc", size = 4651414, upload-time = "2026-05-11T18:54:29.21Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/24/f1/392f8c5bfc16f66a0d2d41561c01627c228fe7ed2a0d056ef11315042570/pandas-3.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:fed2ff7fd9779120e388e285fc029bd5cf9490cdd2e4166a9ee22c0e49a9ab09", size = 10357846, upload-time = "2026-05-11T18:52:36.143Z" }, - { url = "https://files.pythonhosted.org/packages/cf/3d/b16412745651e855f357e5e66930248688378853a6e2698a214e331fba1f/pandas-3.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b168fc218fd80a6cbdbdbc1a97ddc7889ed057d7eb45f50d866ceab5f39904c4", size = 9899550, upload-time = "2026-05-11T18:52:38.976Z" }, - { url = "https://files.pythonhosted.org/packages/31/a8/fa2535168fffcedf67f4f6de28d2dd903a747ca7c8ea6989451aaeb3a92f/pandas-3.0.3-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0383c72c75cdcca61a9e116e611143902dbfd08bff356829c2f6d1cf40a9ca8c", size = 10412965, upload-time = "2026-05-11T18:52:41.915Z" }, - { url = "https://files.pythonhosted.org/packages/65/b6/09b01cdbc15224e2850365192d17b7bdebb8bdbd8780ed221fcdf0d9a515/pandas-3.0.3-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6dc0b3fd2169c9157deed50b4d519553a3655c8c6a96027136d654592be973a9", size = 10894600, upload-time = "2026-05-11T18:52:45.02Z" }, - { url = "https://files.pythonhosted.org/packages/c9/a4/2eb28f2fccb4ced4a2c79ab2a5dee9ade1ebf44922ebad6fea158c9f95d4/pandas-3.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:7e65d5407dc0b394f509699650e4a2ec01c0514f21850f453fa60f3be79a5dbf", size = 11422824, upload-time = "2026-05-11T18:52:48.058Z" }, - { url = "https://files.pythonhosted.org/packages/f8/45/830bb57f533a4604b355e07edcb8ea18cf88b5f94e5fca92f27052d7c597/pandas-3.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:f8894dc474d648fe7b6ff0ca9b0bd73950d19952bc1a6534540762c5d79d305c", size = 11950889, upload-time = "2026-05-11T18:52:50.905Z" }, - { url = "https://files.pythonhosted.org/packages/b9/c5/fc1b368f303087d20e8c9bf3d6ceb186263cfac0ade735cd938538bea839/pandas-3.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:c7be265b62cef88e253a941e4698604973736dcfe242fdb5198f0f7bc473cdcc", size = 9755463, upload-time = "2026-05-11T18:52:53.386Z" }, - { url = "https://files.pythonhosted.org/packages/86/bd/fda8f9705b1b09c6ebe14bfc0fa0e4ec8584d54ea673628f157ff55131af/pandas-3.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:557409bc4178e70ee8d9ddb494798e51ebf6ea59330f6be22c51bab2a7db6c49", size = 9066158, upload-time = "2026-05-11T18:52:56.038Z" }, - { url = "https://files.pythonhosted.org/packages/c5/90/62d8302883c44308c477e222c3daf7c813a34c8e96985882fbd53d964352/pandas-3.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:67b3b64c11910cfa29f4e94a14d3bff9ee693b6fc76055e7cad549cee0aec5fa", size = 10331071, upload-time = "2026-05-11T18:52:58.838Z" }, - { url = "https://files.pythonhosted.org/packages/7f/ae/6a6493c783a101f165e4356953ba3c74d6f77f0042fa7d753da9dfbb640c/pandas-3.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:39436b377d56d2a2e52d0395bdbee171f01068e99af5250509aceeb929f765c7", size = 9875690, upload-time = "2026-05-11T18:53:01.431Z" }, - { url = "https://files.pythonhosted.org/packages/62/7c/5df8e9f56c69a2769fbe9382a5ef8f2658c007e376434e1e2cbb57ad895f/pandas-3.0.3-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d4be06d68f9ddcfc645b87534911da79a8fbffc7573c80e0edcf42a5020624d8", size = 10381634, upload-time = "2026-05-11T18:53:04.393Z" }, - { url = "https://files.pythonhosted.org/packages/99/68/1237369725aa617bb358263d535803e3053fdbc593513ec5ed9c9896b5b6/pandas-3.0.3-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a4eeb6830daf35a71cc09649bd823e2b542dac246cdee9614c6e4bd65028cd6a", size = 10891243, upload-time = "2026-05-11T18:53:07.643Z" }, - { url = "https://files.pythonhosted.org/packages/25/93/77d108e8af7222b4a503ebde0e30215b1c2e4f8e53a526431890f22d5586/pandas-3.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:1928e07221f82db493cd4af1e23c1bfca524a19a4699887975bff68f49a72bfb", size = 11388659, upload-time = "2026-05-11T18:53:10.634Z" }, - { url = "https://files.pythonhosted.org/packages/d0/bd/eff5b4399f332ac386c853f6cd2bd3fa2ca0061b9f36ecd9c4d7c4265649/pandas-3.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:51b1fe551acb77dac643c6fda86084d8d446c10fe64b06a9cc29c4cc8540e7f2", size = 11942880, upload-time = "2026-05-11T18:53:13.536Z" }, - { url = "https://files.pythonhosted.org/packages/2c/20/559ace4200982c3887d0b86bfd0d856a2143ef8ddab63cc07934951a964c/pandas-3.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:a82d532a3351d435432cd913edbccaf8b8e01d4dd0e5ced5a8d2e8ecd94c7e44", size = 9757091, upload-time = "2026-05-11T18:53:16.306Z" }, - { url = "https://files.pythonhosted.org/packages/3a/66/69055a09fe200f29f922a3eeec4804611900b95f52d932ece3393c3c0c19/pandas-3.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:275c14e0fce14a2ec20eee474aecd305478ea3c1e6f6a9d8fe219a165542717e", size = 9057282, upload-time = "2026-05-11T18:53:18.768Z" }, - { url = "https://files.pythonhosted.org/packages/57/0e/efe801b0e6811e8e650cd21b7f2608e30f08a7067e2bf6e8752b0d56ee3c/pandas-3.0.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:46997386d528eb40376ecd6b033cf4a8a1e5282580f68f43de875b78cba2199d", size = 10767016, upload-time = "2026-05-11T18:53:21.227Z" }, - { url = "https://files.pythonhosted.org/packages/ea/dc/eb55135a1d5f0f0519f28da1f609a206d2cad1f9c35c32d51e38dd7261ae/pandas-3.0.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:261e308dfb22448384b7580cf719d2f998fe2966c92893c3e77d14008af1f066", size = 10420210, upload-time = "2026-05-11T18:53:23.982Z" }, - { url = "https://files.pythonhosted.org/packages/c6/3e/b1d5d955ce33ffecb407465a60bc32769d74fcf68224b7ae67ae11d4dea4/pandas-3.0.3-cp313-cp313t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:dd1a5d1def6a46002e964510bdc67c368aa0951df5d1d9f8365336f5a1f490cd", size = 10336126, upload-time = "2026-05-11T18:53:26.731Z" }, - { url = "https://files.pythonhosted.org/packages/f5/76/a01261711ab60a22d71b862f0de20e4c504bf80457270ad8cb42110f6abc/pandas-3.0.3-cp313-cp313t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d72828c20c6d6e83e1e22a6a3b47b326b71664112fa9705dcbccfd7a39b62085", size = 10728051, upload-time = "2026-05-11T18:53:29.125Z" }, - { url = "https://files.pythonhosted.org/packages/e9/21/ea191195e587b18cf682e97f433f81b2d0fbe341380e80a3e0d6e4403c8e/pandas-3.0.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:d26cbe1fcfc12e8fd900e2454163e466b2d3af84f7c75481df7683ffc073d870", size = 11350796, upload-time = "2026-05-11T18:53:32.056Z" }, - { url = "https://files.pythonhosted.org/packages/64/69/f0eaaf54939f0e8c6768fd06be9af2cef9b36048b96dfb9e1b2c685a807e/pandas-3.0.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:3e91cec1879ada0624fc3dc9953c5cbd60208e59c0db28f540c5d6d47502422f", size = 11799741, upload-time = "2026-05-11T18:53:34.985Z" }, - { url = "https://files.pythonhosted.org/packages/45/a4/865e0e510cae5fc2194de4db28be638952de942571ba9125934fd9c01d47/pandas-3.0.3-cp313-cp313t-win_amd64.whl", hash = "sha256:08d789b41f87e0905880e293cedf6197ce71fe67cc081358b1e148a491b9bd13", size = 10499958, upload-time = "2026-05-11T18:53:37.857Z" }, - { url = "https://files.pythonhosted.org/packages/86/54/effdcc3c0ff7a08037889200e148ebe94c16c4f653be078c7b3675955df1/pandas-3.0.3-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:3650109c0f22879df8bd6179ab9ee3d7f1d1d4e7e0094a3f0032d9f51e2e64ac", size = 10336065, upload-time = "2026-05-11T18:53:41.099Z" }, - { url = "https://files.pythonhosted.org/packages/68/10/bf2d6738d72748b961a3751ab89522d58c54efc36a8e1a12161216cd45cf/pandas-3.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:bab900348131a7db1f69a7309ef141fd5680f1487094193bcbbb61791573bf8f", size = 9926101, upload-time = "2026-05-11T18:53:43.515Z" }, - { url = "https://files.pythonhosted.org/packages/ae/e9/e35cf11c8a136e757b956f5f0efdcaa50aecde85ea055f1898dfc68262f3/pandas-3.0.3-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ba7e08b9ac1d54569cd1e256e3668975ed624d6826f7b68df0342b012007bddb", size = 10457553, upload-time = "2026-05-11T18:53:46.394Z" }, - { url = "https://files.pythonhosted.org/packages/58/3b/1cdec6772bdbaf7b25dab360c59f03cadf05492dd724c6540af905389b07/pandas-3.0.3-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9d71c63ae4ebdbf70209742096f1fc46a83a0613c99d4b23766cced9ff8cd62a", size = 10914065, upload-time = "2026-05-11T18:53:49.134Z" }, - { url = "https://files.pythonhosted.org/packages/c4/c2/1ef644445fcd72e3627bceec77e3560636f87ddce4ed841afe76b83b5bf9/pandas-3.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:e3a2ec42c98ffa2565a67e08e218d06d72576d758d90facb7c00805194d8f360", size = 11459188, upload-time = "2026-05-11T18:53:52.527Z" }, - { url = "https://files.pythonhosted.org/packages/7e/49/4d8d4f42cbc9c4adc7a1870f269c02cbd6cd40d059622c06fb298addcbad/pandas-3.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:335f62418ed562cfc3c49e9e196375c28b729dcef8543abf4f9438e381bf3c76", size = 11982966, upload-time = "2026-05-11T18:53:55.043Z" }, - { url = "https://files.pythonhosted.org/packages/38/55/792619469bab9882d8bbd5865d45a72f6478762d04a9af4bf0d08c503e95/pandas-3.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:3c20a521bbb85902f79f7270c80a59e1b5452d96d170c034f207181870f97ac5", size = 9876755, upload-time = "2026-05-11T18:53:58.067Z" }, - { url = "https://files.pythonhosted.org/packages/2a/af/33c469653b0ba03b50c3a98192d4c07f0c75c66b263ceb097fce0ee97d31/pandas-3.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:a2d2dff8a04f3917b55ab3910c32990f8ddf7eceba114947838cefa976a68977", size = 9198658, upload-time = "2026-05-11T18:54:00.733Z" }, - { url = "https://files.pythonhosted.org/packages/a2/fa/b8c257bd76b8bd060c3a9151c1fca05e9b9c5e3af5d0f549c0356f6d143d/pandas-3.0.3-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:0d589105b3c14645af1738ff279b2995102d8f7a03b0a66dc8d95550eb513e04", size = 10787242, upload-time = "2026-05-11T18:54:03.564Z" }, - { url = "https://files.pythonhosted.org/packages/54/eb/f19206ffb0bf1919002969aa448b4702c6594845156a6f8050674855aac3/pandas-3.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:13fc1e853d9e04743d11ba75a985ccbc2a317fe07d8af61e445a6fd24dacd6a6", size = 10436369, upload-time = "2026-05-11T18:54:06.311Z" }, - { url = "https://files.pythonhosted.org/packages/fd/24/c7c39fb4fe22b71a0c2d78bf0c585c600092d85f94f086d2b3b2f6ca27e2/pandas-3.0.3-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:819959dab7bbd0049c15623fbac4e29a191b9528160a61fb1032242d8ced2d9c", size = 10358306, upload-time = "2026-05-11T18:54:09.085Z" }, - { url = "https://files.pythonhosted.org/packages/16/ec/dd2a9eb7fa1204df88c0864164e35b228ac581062ac612ba0a67fd812e4c/pandas-3.0.3-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:60ae316d3fd75d1858d450d0db0103ea2be3e7d4a95ec2f064f7e2ae63f7b028", size = 10758394, upload-time = "2026-05-11T18:54:11.956Z" }, - { url = "https://files.pythonhosted.org/packages/95/6e/00c61ea8e85b4f6d8d35e11852a1a4998fc7fafc91c6a602d1cc9c972d64/pandas-3.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:bd3a518890b400d32f9023722dc9a9a5c969f00b415419a3c06c043f09bb5d7d", size = 11375717, upload-time = "2026-05-11T18:54:14.539Z" }, - { url = "https://files.pythonhosted.org/packages/31/89/8fc1c268969fac43688d65fd92e67df24bd128d53cb4d2eee534cd307399/pandas-3.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:9c39be2d709d01fa972a0cabc522389fceca4f3969332ba25a7d6c5802cf976a", size = 11828897, upload-time = "2026-05-11T18:54:17.146Z" }, - { url = "https://files.pythonhosted.org/packages/56/3b/e7d20dea247a3e6dc0bd8a6953854afbedc03951def4e7371e05e7263e25/pandas-3.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4db8c527972a821cf5286b40ccc57642a39bc62e62022b42f99f8a67fca8c3a1", size = 10900855, upload-time = "2026-05-11T18:54:19.72Z" }, - { url = "https://files.pythonhosted.org/packages/0f/54/68a0978d1ef8502b8492099beaa6e7a0c1b32e3b5d4f677f5810cb08711c/pandas-3.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:b2c95f8bfc1ee412bf482605d7bfd30c12d1d26bd59fdd91efeef1d4718decb1", size = 9466464, upload-time = "2026-05-11T18:54:22.754Z" }, -] - [[package]] name = "pathspec" version = "1.1.1" @@ -1469,51 +1336,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/84/03/0d3ce49e2505ae70cf43bc5bb3033955d2fc9f932163e84dc0779cc47f48/prompt_toolkit-3.0.52-py3-none-any.whl", hash = "sha256:9aac639a3bbd33284347de5ad8d68ecc044b91a762dc39b7c21095fcd6a19955", size = 391431, upload-time = "2025-08-27T15:23:59.498Z" }, ] -[[package]] -name = "pyannote-core" -version = "6.0.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "numpy" }, - { name = "pandas" }, - { name = "sortedcontainers" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/a3/be/4a35ea31c685aef801f7f35c193e7766ca1bb948ae497a625cbfaa8c31ba/pyannote_core-6.0.1.tar.gz", hash = "sha256:4b4ada3276f6df4e073fa79166636e3597d0dcb5a0fe26014a3477867cc033fb", size = 327540, upload-time = "2025-09-16T09:24:39.081Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ea/57/ecf62344b9b81debd0ca95ed987135e93d1b039507f8174f52d1d19d8c6b/pyannote_core-6.0.1-py3-none-any.whl", hash = "sha256:924550d6ecf6b05ad13bf3f66f59c29fc740cf1c62a6fca860ac2e66908203e5", size = 57505, upload-time = "2025-09-16T09:24:37.798Z" }, -] - -[[package]] -name = "pyannote-database" -version = "6.1.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pandas" }, - { name = "pyannote-core" }, - { name = "pyyaml" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/65/45/6210274c187cc457e854be8b56c6819fa14376f27e7e2b6021b2aa02449a/pyannote_database-6.1.1.tar.gz", hash = "sha256:bbe76da738257a9e64061123d9694ad7e949c4f171d91a9269606d873528cd10", size = 112225, upload-time = "2025-12-07T06:33:10.296Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/04/bf/6a6f5abaa4d9f803f34c9883ef5e316624eac6be0eaa87720216be9bba12/pyannote_database-6.1.1-py3-none-any.whl", hash = "sha256:36460c70ce9f50ff25c9ea365bc83ad625bb6b2494deccf6bd3fc750686ae684", size = 53735, upload-time = "2025-12-07T06:33:11.578Z" }, -] - -[[package]] -name = "pyannote-metrics" -version = "4.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "numpy" }, - { name = "pandas" }, - { name = "pyannote-core" }, - { name = "pyannote-database" }, - { name = "scikit-learn" }, - { name = "scipy" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/74/ba/7dbc2f790d5e321e46dc1e250ff00b69cdefc0c5695b811e96a4b932cddd/pyannote_metrics-4.1.tar.gz", hash = "sha256:afe24c54ee0799e8cfbe8ee85fa517793c3450bb7eae8fedd1a77ccec0343f7e", size = 883419, upload-time = "2026-05-06T16:33:10.163Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ca/d3/9cd1df66aa9be47a36df46f007e198bbdec3ddebc1bd8833c7a9e8a554a9/pyannote_metrics-4.1-py3-none-any.whl", hash = "sha256:34a54b7671f61709c1865d0484843e5b46ea3c4e4e5260ab065e5b3156c733d3", size = 52116, upload-time = "2026-05-06T16:33:08.586Z" }, -] - [[package]] name = "pycparser" version = "3.0" @@ -1740,18 +1562,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/ca/31/d4e37e9e550c2b92a9cbc2e4d0b7420a27224968580b5a447f420847c975/pytest_xdist-3.8.0-py3-none-any.whl", hash = "sha256:202ca578cfeb7370784a8c33d6d05bc6e13b4f25b5053c30a152269fd10f0b88", size = 46396, upload-time = "2025-07-01T13:30:56.632Z" }, ] -[[package]] -name = "python-dateutil" -version = "2.9.0.post0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "six" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/66/c0/0c8b6ad9f17a802ee498c46e004a0eb49bc148f2fd230864601a86dcf6db/python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 342432, upload-time = "2024-03-01T18:36:20.211Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892, upload-time = "2024-03-01T18:36:18.57Z" }, -] - [[package]] name = "python-discovery" version = "1.3.1" @@ -1991,106 +1801,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/15/19/016553f86f207450aebebc2b2b5088d086b901cc8186c02ac4284db3bd88/ruff-0.15.16-py3-none-win_arm64.whl", hash = "sha256:8cd61783afb39638a7133ef0d2dfb1e91277593962f81b5a8423eb0b888a6121", size = 11134555, upload-time = "2026-06-04T16:33:00.136Z" }, ] -[[package]] -name = "scikit-learn" -version = "1.9.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "joblib" }, - { name = "narwhals" }, - { name = "numpy" }, - { name = "scipy" }, - { name = "threadpoolctl" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/fa/6f/37092bdb25f712817231799fc5674d8e704066a8a70c1d2d40517e18b4ab/scikit_learn-1.9.0.tar.gz", hash = "sha256:8833266989d3a5110178a9fae30783675460724d0e1efb13b14901d2c660c557", size = 7750767, upload-time = "2026-06-02T11:54:32.706Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ac/20/75f915ff375d6249e6550ac740fdbbd66159a068fd3af1400ff62036b07a/scikit_learn-1.9.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:2bd41b0d201bc81575531b96b713d3eb5e5f50fb0b82101ff0f92294fdc236ac", size = 8741122, upload-time = "2026-06-02T11:53:24.08Z" }, - { url = "https://files.pythonhosted.org/packages/cc/d5/2b5148f2279196775e1db2aeb85d14b70ac80e7e32b3b28e7ebeafb0901d/scikit_learn-1.9.0-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:5be45aa4a42a68a533913a6ed736cf309de2226411c79ef8d609a5456f1939b1", size = 8261512, upload-time = "2026-06-02T11:53:27.183Z" }, - { url = "https://files.pythonhosted.org/packages/a0/ee/5adbc77656b71f9456a2f5a7a9fdb4bcf9207a6b962889f1c2f9323afa4e/scikit_learn-1.9.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5e50ed4da51974e86e940690e9a3d82e729b62b5a49f7c9bac534d515d39d86f", size = 8837603, upload-time = "2026-06-02T11:53:30.328Z" }, - { url = "https://files.pythonhosted.org/packages/6c/c2/63fdda36c56437eeb44aaf9493c8bcd62ce230ab1598924fc626ffbfa943/scikit_learn-1.9.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:056c92bb67ad4c28463c2f2653d9701449201e7e7a9e94e321be0f71c4fef2b8", size = 9132097, upload-time = "2026-06-02T11:53:33.456Z" }, - { url = "https://files.pythonhosted.org/packages/83/a4/c8e67227c680e2259c8864ae72ff48b06e16a6f51253a22167aa02a8aa4e/scikit_learn-1.9.0-cp312-cp312-win_amd64.whl", hash = "sha256:4306775fad04cc4b472a1b15af1ae9cede1540fbfcc17fbce3767cd8dc7ae283", size = 8211173, upload-time = "2026-06-02T11:53:36.602Z" }, - { url = "https://files.pythonhosted.org/packages/cf/fd/3c0863792e98e67e9184aa4029288a175935eb65443afcd30d4f143450cf/scikit_learn-1.9.0-cp312-cp312-win_arm64.whl", hash = "sha256:26e22435f63bcdcf396b574273f29f13dd531f5ea035801f5be10ba1540a4e60", size = 7867451, upload-time = "2026-06-02T11:53:39.075Z" }, - { url = "https://files.pythonhosted.org/packages/3c/01/cf3310626b6d48d3e9be69a1223f9180360b5e6edb045f50fade723ce494/scikit_learn-1.9.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:80746d63bd4b6eaca54d36fe5feaf4d28bb38dc6f9470f81c7cad7c40155f119", size = 8705188, upload-time = "2026-06-02T11:53:41.964Z" }, - { url = "https://files.pythonhosted.org/packages/3e/04/5acd7ae280c5f93b6ac5ef6cdec14eef4c8d1cd91d85b3292989c94d96b1/scikit_learn-1.9.0-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:5b934c45c252844a91d69fda3a34cff5e7307e1db10d77cb10a3980312c74713", size = 8228299, upload-time = "2026-06-02T11:53:44.817Z" }, - { url = "https://files.pythonhosted.org/packages/0c/39/ffe829a5b8ecb40a518724a997794657fdc354ada5e8fe8e64d998c0bac9/scikit_learn-1.9.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:38c3dcb9a1ffb85505ec53d54c7b4aea0cff70050425a7760c2af661ac85df05", size = 8789690, upload-time = "2026-06-02T11:53:47.461Z" }, - { url = "https://files.pythonhosted.org/packages/1f/88/8dab5de10c638c083772a6be83a3d8106ced492f74a928c8693638e5bb50/scikit_learn-1.9.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:da76d09304a4706db7cc1e3ebaa3b6b98a67365cc11d2996c4f1e58ba47df714", size = 9087723, upload-time = "2026-06-02T11:53:50.702Z" }, - { url = "https://files.pythonhosted.org/packages/20/3f/7917ca72464038f6240ec70c29f94862d08a34a74291ae4d4ec5eb8186a0/scikit_learn-1.9.0-cp313-cp313-win_amd64.whl", hash = "sha256:5808d98f15c6bf6d9d96d2348c1997392a5888ce7097e664105f930c4bca1277", size = 8184330, upload-time = "2026-06-02T11:53:53.396Z" }, - { url = "https://files.pythonhosted.org/packages/78/c7/15739eb2f61fda3c54639e9942414e5a19ad8a8d1f5a3266afad7cb7df80/scikit_learn-1.9.0-cp313-cp313-win_arm64.whl", hash = "sha256:d77f54c017633791bc0225a43e2f8d03745fdcfe4880268fcc4df15f505dec2e", size = 7840653, upload-time = "2026-06-02T11:53:56.035Z" }, - { url = "https://files.pythonhosted.org/packages/f4/7d/c9a35cf59b20a86fec24d306f1547b78dec194b08d367ce2a3e4854169d9/scikit_learn-1.9.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:9656acd4e93f74e0b66c8a36c88830a99252dfa900044d36bc2212ae89a47162", size = 8713289, upload-time = "2026-06-02T11:53:58.788Z" }, - { url = "https://files.pythonhosted.org/packages/3c/a7/552a7821597c632b907f7bfe8f36f9f572777af8ef8a48353041cf8e091a/scikit_learn-1.9.0-cp314-cp314-macosx_12_0_arm64.whl", hash = "sha256:24360002ae845e7866522b0a5bbf690802e7bc388cac8663502e78aa98598aa2", size = 8245141, upload-time = "2026-06-02T11:54:01.694Z" }, - { url = "https://files.pythonhosted.org/packages/7d/79/f4a0c4fe9711154cddabf913471153af79056382ddc612cfe5ee0ff4b72e/scikit_learn-1.9.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5162ad10a418c8a282dde04c9aa06965de3e9a65f33c1440c0ae69bb1a09d913", size = 8847671, upload-time = "2026-06-02T11:54:04.448Z" }, - { url = "https://files.pythonhosted.org/packages/f0/af/4d72d9e475ac83719160c662619e4bf7b95c19507cd582e7d0167a3c3dae/scikit_learn-1.9.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1fea2cc5677ab49d6f5bade978c866da44957b712d92e9635e8b4f723013c3cb", size = 9118104, upload-time = "2026-06-02T11:54:07.205Z" }, - { url = "https://files.pythonhosted.org/packages/a2/d5/6a58eea2cb9abbb9b3f2bb8b2cfb3243d1152d69f442d256c7af71304769/scikit_learn-1.9.0-cp314-cp314-win_amd64.whl", hash = "sha256:64fa347efc1c839c487433e40c5144d38c336e8a2b59c81aa8660373945c2673", size = 8290674, upload-time = "2026-06-02T11:54:10.087Z" }, - { url = "https://files.pythonhosted.org/packages/65/5b/d4c879cf358f1187141cf90ced473f087183489090244f50c124a2ee478b/scikit_learn-1.9.0-cp314-cp314-win_arm64.whl", hash = "sha256:1b944b6db288f6b926e3650026ddafb988929de95d11fc2cc5fa117773c9ba42", size = 7978807, upload-time = "2026-06-02T11:54:12.769Z" }, - { url = "https://files.pythonhosted.org/packages/8a/43/bfae3121ec67ae09150d453c442c7c1cc166e9aefe056e6ab3b7728a5cfc/scikit_learn-1.9.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:4ccacf04ca5f4b492158a5f28afe0ace43f81b2571e4b9a66d34848b46128949", size = 9031941, upload-time = "2026-06-02T11:54:15.436Z" }, - { url = "https://files.pythonhosted.org/packages/75/b0/20a4546eb17f3b25d3c66df15810411c14ed5065bcfab50b53c96fb627b2/scikit_learn-1.9.0-cp314-cp314t-macosx_12_0_arm64.whl", hash = "sha256:ee1a8db2c18c08e34c7412d4b10be1cac214cd4ea7dc9715a6a327eb49a37c96", size = 8613528, upload-time = "2026-06-02T11:54:18.842Z" }, - { url = "https://files.pythonhosted.org/packages/18/3c/e440e039bb82cd19004edaaad00acbde0fb9b461083c3ecf37941c557312/scikit_learn-1.9.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:147e9329ef0e39f75d4cffa02b2aa48d827832684926cd5210d9a2cb5c57246b", size = 8855050, upload-time = "2026-06-02T11:54:21.699Z" }, - { url = "https://files.pythonhosted.org/packages/43/26/b341b8dab5998da6270a3a42c2152c578501354d36f944b5856757035ef8/scikit_learn-1.9.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5bad8f8b9950321b54c965fdcbac6c6c55e79e16646b49977bcf3668d3870a1a", size = 9097190, upload-time = "2026-06-02T11:54:24.454Z" }, - { url = "https://files.pythonhosted.org/packages/fb/de/b650b4d69b84468cfa2e28a3ff7b8103743029e6446ce1a97fe060ef688c/scikit_learn-1.9.0-cp314-cp314t-win_amd64.whl", hash = "sha256:78fc56eafd4edb9575d2d8950d1dd152061abb573341a1cb7e099fc40f6c6666", size = 8963204, upload-time = "2026-06-02T11:54:27.428Z" }, - { url = "https://files.pythonhosted.org/packages/ee/f3/ff83d76d7418112e5a61326443cdda87be3545dd8d6599c95b2481a4419e/scikit_learn-1.9.0-cp314-cp314t-win_arm64.whl", hash = "sha256:051075bda8b7aab87b1906ab3d4740a1e1224a19d7b3781a576736edc94e76aa", size = 8222661, upload-time = "2026-06-02T11:54:30.192Z" }, -] - -[[package]] -name = "scipy" -version = "1.17.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "numpy" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/7a/97/5a3609c4f8d58b039179648e62dd220f89864f56f7357f5d4f45c29eb2cc/scipy-1.17.1.tar.gz", hash = "sha256:95d8e012d8cb8816c226aef832200b1d45109ed4464303e997c5b13122b297c0", size = 30573822, upload-time = "2026-02-23T00:26:24.851Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/35/48/b992b488d6f299dbe3f11a20b24d3dda3d46f1a635ede1c46b5b17a7b163/scipy-1.17.1-cp312-cp312-macosx_10_14_x86_64.whl", hash = "sha256:35c3a56d2ef83efc372eaec584314bd0ef2e2f0d2adb21c55e6ad5b344c0dcb8", size = 31610954, upload-time = "2026-02-23T00:17:49.855Z" }, - { url = "https://files.pythonhosted.org/packages/b2/02/cf107b01494c19dc100f1d0b7ac3cc08666e96ba2d64db7626066cee895e/scipy-1.17.1-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:fcb310ddb270a06114bb64bbe53c94926b943f5b7f0842194d585c65eb4edd76", size = 28172662, upload-time = "2026-02-23T00:18:01.64Z" }, - { url = "https://files.pythonhosted.org/packages/cf/a9/599c28631bad314d219cf9ffd40e985b24d603fc8a2f4ccc5ae8419a535b/scipy-1.17.1-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:cc90d2e9c7e5c7f1a482c9875007c095c3194b1cfedca3c2f3291cdc2bc7c086", size = 20344366, upload-time = "2026-02-23T00:18:12.015Z" }, - { url = "https://files.pythonhosted.org/packages/35/f5/906eda513271c8deb5af284e5ef0206d17a96239af79f9fa0aebfe0e36b4/scipy-1.17.1-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:c80be5ede8f3f8eded4eff73cc99a25c388ce98e555b17d31da05287015ffa5b", size = 22704017, upload-time = "2026-02-23T00:18:21.502Z" }, - { url = "https://files.pythonhosted.org/packages/da/34/16f10e3042d2f1d6b66e0428308ab52224b6a23049cb2f5c1756f713815f/scipy-1.17.1-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e19ebea31758fac5893a2ac360fedd00116cbb7628e650842a6691ba7ca28a21", size = 32927842, upload-time = "2026-02-23T00:18:35.367Z" }, - { url = "https://files.pythonhosted.org/packages/01/8e/1e35281b8ab6d5d72ebe9911edcdffa3f36b04ed9d51dec6dd140396e220/scipy-1.17.1-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:02ae3b274fde71c5e92ac4d54bc06c42d80e399fec704383dcd99b301df37458", size = 35235890, upload-time = "2026-02-23T00:18:49.188Z" }, - { url = "https://files.pythonhosted.org/packages/c5/5c/9d7f4c88bea6e0d5a4f1bc0506a53a00e9fcb198de372bfe4d3652cef482/scipy-1.17.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8a604bae87c6195d8b1045eddece0514d041604b14f2727bbc2b3020172045eb", size = 35003557, upload-time = "2026-02-23T00:18:54.74Z" }, - { url = "https://files.pythonhosted.org/packages/65/94/7698add8f276dbab7a9de9fb6b0e02fc13ee61d51c7c3f85ac28b65e1239/scipy-1.17.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:f590cd684941912d10becc07325a3eeb77886fe981415660d9265c4c418d0bea", size = 37625856, upload-time = "2026-02-23T00:19:00.307Z" }, - { url = "https://files.pythonhosted.org/packages/a2/84/dc08d77fbf3d87d3ee27f6a0c6dcce1de5829a64f2eae85a0ecc1f0daa73/scipy-1.17.1-cp312-cp312-win_amd64.whl", hash = "sha256:41b71f4a3a4cab9d366cd9065b288efc4d4f3c0b37a91a8e0947fb5bd7f31d87", size = 36549682, upload-time = "2026-02-23T00:19:07.67Z" }, - { url = "https://files.pythonhosted.org/packages/bc/98/fe9ae9ffb3b54b62559f52dedaebe204b408db8109a8c66fdd04869e6424/scipy-1.17.1-cp312-cp312-win_arm64.whl", hash = "sha256:f4115102802df98b2b0db3cce5cb9b92572633a1197c77b7553e5203f284a5b3", size = 24547340, upload-time = "2026-02-23T00:19:12.024Z" }, - { url = "https://files.pythonhosted.org/packages/76/27/07ee1b57b65e92645f219b37148a7e7928b82e2b5dbeccecb4dff7c64f0b/scipy-1.17.1-cp313-cp313-macosx_10_14_x86_64.whl", hash = "sha256:5e3c5c011904115f88a39308379c17f91546f77c1667cea98739fe0fccea804c", size = 31590199, upload-time = "2026-02-23T00:19:17.192Z" }, - { url = "https://files.pythonhosted.org/packages/ec/ae/db19f8ab842e9b724bf5dbb7db29302a91f1e55bc4d04b1025d6d605a2c5/scipy-1.17.1-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:6fac755ca3d2c3edcb22f479fceaa241704111414831ddd3bc6056e18516892f", size = 28154001, upload-time = "2026-02-23T00:19:22.241Z" }, - { url = "https://files.pythonhosted.org/packages/5b/58/3ce96251560107b381cbd6e8413c483bbb1228a6b919fa8652b0d4090e7f/scipy-1.17.1-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:7ff200bf9d24f2e4d5dc6ee8c3ac64d739d3a89e2326ba68aaf6c4a2b838fd7d", size = 20325719, upload-time = "2026-02-23T00:19:26.329Z" }, - { url = "https://files.pythonhosted.org/packages/b2/83/15087d945e0e4d48ce2377498abf5ad171ae013232ae31d06f336e64c999/scipy-1.17.1-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:4b400bdc6f79fa02a4d86640310dde87a21fba0c979efff5248908c6f15fad1b", size = 22683595, upload-time = "2026-02-23T00:19:30.304Z" }, - { url = "https://files.pythonhosted.org/packages/b4/e0/e58fbde4a1a594c8be8114eb4aac1a55bcd6587047efc18a61eb1f5c0d30/scipy-1.17.1-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2b64ca7d4aee0102a97f3ba22124052b4bd2152522355073580bf4845e2550b6", size = 32896429, upload-time = "2026-02-23T00:19:35.536Z" }, - { url = "https://files.pythonhosted.org/packages/f5/5f/f17563f28ff03c7b6799c50d01d5d856a1d55f2676f537ca8d28c7f627cd/scipy-1.17.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:581b2264fc0aa555f3f435a5944da7504ea3a065d7029ad60e7c3d1ae09c5464", size = 35203952, upload-time = "2026-02-23T00:19:42.259Z" }, - { url = "https://files.pythonhosted.org/packages/8d/a5/9afd17de24f657fdfe4df9a3f1ea049b39aef7c06000c13db1530d81ccca/scipy-1.17.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:beeda3d4ae615106d7094f7e7cef6218392e4465cc95d25f900bebabfded0950", size = 34979063, upload-time = "2026-02-23T00:19:47.547Z" }, - { url = "https://files.pythonhosted.org/packages/8b/13/88b1d2384b424bf7c924f2038c1c409f8d88bb2a8d49d097861dd64a57b2/scipy-1.17.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6609bc224e9568f65064cfa72edc0f24ee6655b47575954ec6339534b2798369", size = 37598449, upload-time = "2026-02-23T00:19:53.238Z" }, - { url = "https://files.pythonhosted.org/packages/35/e5/d6d0e51fc888f692a35134336866341c08655d92614f492c6860dc45bb2c/scipy-1.17.1-cp313-cp313-win_amd64.whl", hash = "sha256:37425bc9175607b0268f493d79a292c39f9d001a357bebb6b88fdfaff13f6448", size = 36510943, upload-time = "2026-02-23T00:20:50.89Z" }, - { url = "https://files.pythonhosted.org/packages/2a/fd/3be73c564e2a01e690e19cc618811540ba5354c67c8680dce3281123fb79/scipy-1.17.1-cp313-cp313-win_arm64.whl", hash = "sha256:5cf36e801231b6a2059bf354720274b7558746f3b1a4efb43fcf557ccd484a87", size = 24545621, upload-time = "2026-02-23T00:20:55.871Z" }, - { url = "https://files.pythonhosted.org/packages/6f/6b/17787db8b8114933a66f9dcc479a8272e4b4da75fe03b0c282f7b0ade8cd/scipy-1.17.1-cp313-cp313t-macosx_10_14_x86_64.whl", hash = "sha256:d59c30000a16d8edc7e64152e30220bfbd724c9bbb08368c054e24c651314f0a", size = 31936708, upload-time = "2026-02-23T00:19:58.694Z" }, - { url = "https://files.pythonhosted.org/packages/38/2e/524405c2b6392765ab1e2b722a41d5da33dc5c7b7278184a8ad29b6cb206/scipy-1.17.1-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:010f4333c96c9bb1a4516269e33cb5917b08ef2166d5556ca2fd9f082a9e6ea0", size = 28570135, upload-time = "2026-02-23T00:20:03.934Z" }, - { url = "https://files.pythonhosted.org/packages/fd/c3/5bd7199f4ea8556c0c8e39f04ccb014ac37d1468e6cfa6a95c6b3562b76e/scipy-1.17.1-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:2ceb2d3e01c5f1d83c4189737a42d9cb2fc38a6eeed225e7515eef71ad301dce", size = 20741977, upload-time = "2026-02-23T00:20:07.935Z" }, - { url = "https://files.pythonhosted.org/packages/d9/b8/8ccd9b766ad14c78386599708eb745f6b44f08400a5fd0ade7cf89b6fc93/scipy-1.17.1-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:844e165636711ef41f80b4103ed234181646b98a53c8f05da12ca5ca289134f6", size = 23029601, upload-time = "2026-02-23T00:20:12.161Z" }, - { url = "https://files.pythonhosted.org/packages/6d/a0/3cb6f4d2fb3e17428ad2880333cac878909ad1a89f678527b5328b93c1d4/scipy-1.17.1-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:158dd96d2207e21c966063e1635b1063cd7787b627b6f07305315dd73d9c679e", size = 33019667, upload-time = "2026-02-23T00:20:17.208Z" }, - { url = "https://files.pythonhosted.org/packages/f3/c3/2d834a5ac7bf3a0c806ad1508efc02dda3c8c61472a56132d7894c312dea/scipy-1.17.1-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:74cbb80d93260fe2ffa334efa24cb8f2f0f622a9b9febf8b483c0b865bfb3475", size = 35264159, upload-time = "2026-02-23T00:20:23.087Z" }, - { url = "https://files.pythonhosted.org/packages/4d/77/d3ed4becfdbd217c52062fafe35a72388d1bd82c2d0ba5ca19d6fcc93e11/scipy-1.17.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:dbc12c9f3d185f5c737d801da555fb74b3dcfa1a50b66a1a93e09190f41fab50", size = 35102771, upload-time = "2026-02-23T00:20:28.636Z" }, - { url = "https://files.pythonhosted.org/packages/bd/12/d19da97efde68ca1ee5538bb261d5d2c062f0c055575128f11a2730e3ac1/scipy-1.17.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:94055a11dfebe37c656e70317e1996dc197e1a15bbcc351bcdd4610e128fe1ca", size = 37665910, upload-time = "2026-02-23T00:20:34.743Z" }, - { url = "https://files.pythonhosted.org/packages/06/1c/1172a88d507a4baaf72c5a09bb6c018fe2ae0ab622e5830b703a46cc9e44/scipy-1.17.1-cp313-cp313t-win_amd64.whl", hash = "sha256:e30bdeaa5deed6bc27b4cc490823cd0347d7dae09119b8803ae576ea0ce52e4c", size = 36562980, upload-time = "2026-02-23T00:20:40.575Z" }, - { url = "https://files.pythonhosted.org/packages/70/b0/eb757336e5a76dfa7911f63252e3b7d1de00935d7705cf772db5b45ec238/scipy-1.17.1-cp313-cp313t-win_arm64.whl", hash = "sha256:a720477885a9d2411f94a93d16f9d89bad0f28ca23c3f8daa521e2dcc3f44d49", size = 24856543, upload-time = "2026-02-23T00:20:45.313Z" }, - { url = "https://files.pythonhosted.org/packages/cf/83/333afb452af6f0fd70414dc04f898647ee1423979ce02efa75c3b0f2c28e/scipy-1.17.1-cp314-cp314-macosx_10_14_x86_64.whl", hash = "sha256:a48a72c77a310327f6a3a920092fa2b8fd03d7deaa60f093038f22d98e096717", size = 31584510, upload-time = "2026-02-23T00:21:01.015Z" }, - { url = "https://files.pythonhosted.org/packages/ed/a6/d05a85fd51daeb2e4ea71d102f15b34fedca8e931af02594193ae4fd25f7/scipy-1.17.1-cp314-cp314-macosx_12_0_arm64.whl", hash = "sha256:45abad819184f07240d8a696117a7aacd39787af9e0b719d00285549ed19a1e9", size = 28170131, upload-time = "2026-02-23T00:21:05.888Z" }, - { url = "https://files.pythonhosted.org/packages/db/7b/8624a203326675d7746a254083a187398090a179335b2e4a20e2ddc46e83/scipy-1.17.1-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:3fd1fcdab3ea951b610dc4cef356d416d5802991e7e32b5254828d342f7b7e0b", size = 20342032, upload-time = "2026-02-23T00:21:09.904Z" }, - { url = "https://files.pythonhosted.org/packages/c9/35/2c342897c00775d688d8ff3987aced3426858fd89d5a0e26e020b660b301/scipy-1.17.1-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:7bdf2da170b67fdf10bca777614b1c7d96ae3ca5794fd9587dce41eb2966e866", size = 22678766, upload-time = "2026-02-23T00:21:14.313Z" }, - { url = "https://files.pythonhosted.org/packages/ef/f2/7cdb8eb308a1a6ae1e19f945913c82c23c0c442a462a46480ce487fdc0ac/scipy-1.17.1-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:adb2642e060a6549c343603a3851ba76ef0b74cc8c079a9a58121c7ec9fe2350", size = 32957007, upload-time = "2026-02-23T00:21:19.663Z" }, - { url = "https://files.pythonhosted.org/packages/0b/2e/7eea398450457ecb54e18e9d10110993fa65561c4f3add5e8eccd2b9cd41/scipy-1.17.1-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:eee2cfda04c00a857206a4330f0c5e3e56535494e30ca445eb19ec624ae75118", size = 35221333, upload-time = "2026-02-23T00:21:25.278Z" }, - { url = "https://files.pythonhosted.org/packages/d9/77/5b8509d03b77f093a0d52e606d3c4f79e8b06d1d38c441dacb1e26cacf46/scipy-1.17.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:d2650c1fb97e184d12d8ba010493ee7b322864f7d3d00d3f9bb97d9c21de4068", size = 35042066, upload-time = "2026-02-23T00:21:31.358Z" }, - { url = "https://files.pythonhosted.org/packages/f9/df/18f80fb99df40b4070328d5ae5c596f2f00fffb50167e31439e932f29e7d/scipy-1.17.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:08b900519463543aa604a06bec02461558a6e1cef8fdbb8098f77a48a83c8118", size = 37612763, upload-time = "2026-02-23T00:21:37.247Z" }, - { url = "https://files.pythonhosted.org/packages/4b/39/f0e8ea762a764a9dc52aa7dabcfad51a354819de1f0d4652b6a1122424d6/scipy-1.17.1-cp314-cp314-win_amd64.whl", hash = "sha256:3877ac408e14da24a6196de0ddcace62092bfc12a83823e92e49e40747e52c19", size = 37290984, upload-time = "2026-02-23T00:22:35.023Z" }, - { url = "https://files.pythonhosted.org/packages/7c/56/fe201e3b0f93d1a8bcf75d3379affd228a63d7e2d80ab45467a74b494947/scipy-1.17.1-cp314-cp314-win_arm64.whl", hash = "sha256:f8885db0bc2bffa59d5c1b72fad7a6a92d3e80e7257f967dd81abb553a90d293", size = 25192877, upload-time = "2026-02-23T00:22:39.798Z" }, - { url = "https://files.pythonhosted.org/packages/96/ad/f8c414e121f82e02d76f310f16db9899c4fcde36710329502a6b2a3c0392/scipy-1.17.1-cp314-cp314t-macosx_10_14_x86_64.whl", hash = "sha256:1cc682cea2ae55524432f3cdff9e9a3be743d52a7443d0cba9017c23c87ae2f6", size = 31949750, upload-time = "2026-02-23T00:21:42.289Z" }, - { url = "https://files.pythonhosted.org/packages/7c/b0/c741e8865d61b67c81e255f4f0a832846c064e426636cd7de84e74d209be/scipy-1.17.1-cp314-cp314t-macosx_12_0_arm64.whl", hash = "sha256:2040ad4d1795a0ae89bfc7e8429677f365d45aa9fd5e4587cf1ea737f927b4a1", size = 28585858, upload-time = "2026-02-23T00:21:47.706Z" }, - { url = "https://files.pythonhosted.org/packages/ed/1b/3985219c6177866628fa7c2595bfd23f193ceebbe472c98a08824b9466ff/scipy-1.17.1-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:131f5aaea57602008f9822e2115029b55d4b5f7c070287699fe45c661d051e39", size = 20757723, upload-time = "2026-02-23T00:21:52.039Z" }, - { url = "https://files.pythonhosted.org/packages/c0/19/2a04aa25050d656d6f7b9e7b685cc83d6957fb101665bfd9369ca6534563/scipy-1.17.1-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:9cdc1a2fcfd5c52cfb3045feb399f7b3ce822abdde3a193a6b9a60b3cb5854ca", size = 23043098, upload-time = "2026-02-23T00:21:56.185Z" }, - { url = "https://files.pythonhosted.org/packages/86/f1/3383beb9b5d0dbddd030335bf8a8b32d4317185efe495374f134d8be6cce/scipy-1.17.1-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6e3dcd57ab780c741fde8dc68619de988b966db759a3c3152e8e9142c26295ad", size = 33030397, upload-time = "2026-02-23T00:22:01.404Z" }, - { url = "https://files.pythonhosted.org/packages/41/68/8f21e8a65a5a03f25a79165ec9d2b28c00e66dc80546cf5eb803aeeff35b/scipy-1.17.1-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a9956e4d4f4a301ebf6cde39850333a6b6110799d470dbbb1e25326ac447f52a", size = 35281163, upload-time = "2026-02-23T00:22:07.024Z" }, - { url = "https://files.pythonhosted.org/packages/84/8d/c8a5e19479554007a5632ed7529e665c315ae7492b4f946b0deb39870e39/scipy-1.17.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:a4328d245944d09fd639771de275701ccadf5f781ba0ff092ad141e017eccda4", size = 35116291, upload-time = "2026-02-23T00:22:12.585Z" }, - { url = "https://files.pythonhosted.org/packages/52/52/e57eceff0e342a1f50e274264ed47497b59e6a4e3118808ee58ddda7b74a/scipy-1.17.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:a77cbd07b940d326d39a1d1b37817e2ee4d79cb30e7338f3d0cddffae70fcaa2", size = 37682317, upload-time = "2026-02-23T00:22:18.513Z" }, - { url = "https://files.pythonhosted.org/packages/11/2f/b29eafe4a3fbc3d6de9662b36e028d5f039e72d345e05c250e121a230dd4/scipy-1.17.1-cp314-cp314t-win_amd64.whl", hash = "sha256:eb092099205ef62cd1782b006658db09e2fed75bffcae7cc0d44052d8aa0f484", size = 37345327, upload-time = "2026-02-23T00:22:24.442Z" }, - { url = "https://files.pythonhosted.org/packages/07/39/338d9219c4e87f3e708f18857ecd24d22a0c3094752393319553096b98af/scipy-1.17.1-cp314-cp314t-win_arm64.whl", hash = "sha256:200e1050faffacc162be6a486a984a0497866ec54149a01270adc8a59b7c7d21", size = 25489165, upload-time = "2026-02-23T00:22:29.563Z" }, -] - [[package]] name = "secretstorage" version = "3.5.0" @@ -2181,15 +1891,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/4e/18/dc99a152bea18a898a8ac387bfeb9ec0829e0f5bed11cfec2e2ca189c5a2/syrupy-5.2.0-py3-none-any.whl", hash = "sha256:798cb493a6e20f4839e58ea8f10eb1b0d85684c676442f79786e219bf32618e6", size = 51828, upload-time = "2026-05-16T21:11:34.984Z" }, ] -[[package]] -name = "threadpoolctl" -version = "3.6.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/b7/4d/08c89e34946fce2aec4fbb45c9016efd5f4d7f24af8e5d93296e935631d8/threadpoolctl-3.6.0.tar.gz", hash = "sha256:8ab8b4aa3491d812b623328249fab5302a68d2d71745c8a4c719a2fcaba9f44e", size = 21274, upload-time = "2025-03-13T13:49:23.031Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/32/d5/f9a850d79b0851d1d4ef6456097579a9005b31fea68726a4ae5f2d82ddd9/threadpoolctl-3.6.0-py3-none-any.whl", hash = "sha256:43a0b8fd5a2928500110039e43a5eed8480b918967083ea48dc3ab9f13c4a7fb", size = 18638, upload-time = "2025-03-13T13:49:21.846Z" }, -] - [[package]] name = "time-machine" version = "3.2.0" @@ -2355,15 +2056,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl", hash = "sha256:4ed1cacbdc298c220f1bd249ed5287caa16f34d44ef4e9c3d0cbad5b521545e7", size = 14611, upload-time = "2025-10-01T02:14:40.154Z" }, ] -[[package]] -name = "tzdata" -version = "2026.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ba/19/1b9b0e29f30c6d35cb345486df41110984ea67ae69dddbc0e8a100999493/tzdata-2026.2.tar.gz", hash = "sha256:9173fde7d80d9018e02a662e168e5a2d04f87c41ea174b139fbef642eda62d10", size = 198254, upload-time = "2026-04-24T15:22:08.651Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ce/e4/dccd7f47c4b64213ac01ef921a1337ee6e30e8c6466046018326977efd95/tzdata-2026.2-py2.py3-none-any.whl", hash = "sha256:bbe9af844f658da81a5f95019480da3a89415801f6cc966806612cc7169bffe7", size = 349321, upload-time = "2026-04-24T15:22:05.876Z" }, -] - [[package]] name = "urllib3" version = "2.7.0"