diff --git a/experiments/regime_aware_holdout.py b/experiments/regime_aware_holdout.py new file mode 100644 index 0000000..3ceb814 --- /dev/null +++ b/experiments/regime_aware_holdout.py @@ -0,0 +1,461 @@ +"""Holdout experiment: tripartite regime-aware vs alternatives on neg/0/pos data. + +Settles the design question: given a target variable that genuinely +spans {negative, zero, positive}, is the three-sign regime-aware +imputer (binary gate on signed class, separate base imputers per sign) +actually better than simpler alternatives? + +Four approaches tested on the same held-out real-DGP-like fixture: + +A. **Tripartite**: ``ZeroInflatedImputer`` with regime detection + (expected regime: ``THREE_SIGN``). Gate routes to positive-QRF or + negative-QRF; exact zeros come from the gate. + +B. **Binary-nonzero + single QRF**: simulates a ``y != 0`` gate. + Binary classifier (zero vs nonzero); single QRF trained on all + nonzero rows, pos and neg mixed. The QRF interpolates between the + two regimes — the failure mode we hypothesized. + +C. **Positive-only + QRF (current microplex-us bug)**: ``y > 0`` gate. + Negative training rows dropped; QRF only sees positives. Predicts + no negative values at test time. + +D. **No gate**: bare ``QRF`` on the full training set, no gate. + Zeros come out as whatever the QRF happens to predict. + +Metrics on the 20 % held-out partition: + +- **Pinball loss at q=0.5** (median quantile loss): lower is better. +- **Zero-rate MAE**: absolute difference between the predicted and + observed fraction of exact zeros. Approaches without a zero gate + (D) are penalized here. +- **Sign-match rate**: fraction of held-out records where + ``sign(pred) == sign(truth)``. C scores 0 on all true-negative + records because it can never emit a negative value. +- **KS distance** between predicted and true marginal distribution. +- **Interior-band violations**: for a DGP with a designed gap between + positive and negative regimes, the fraction of predictions that + land in the "impossible" gap. Tripartite should have zero; + approaches B and D should have nonzero. + +Usage: + + uv run python experiments/regime_aware_holdout.py \ + --output experiments/regime_aware_holdout_results.json \ + --seed 42 +""" + +from __future__ import annotations + +import argparse +import json +from dataclasses import dataclass +from pathlib import Path +from typing import Any, Dict, List + +import numpy as np +import pandas as pd + +from microimpute.models.qrf import QRF +from microimpute.models.zero_inflated import ZeroInflatedImputer + + +# ------------------------------------------------------------------- +# Synthetic data-generating process +# ------------------------------------------------------------------- + + +@dataclass +class DGPConfig: + """Parameters of the synthetic three-regime DGP. + + The target ``y`` is generated as a three-component mixture: + - ``y = 0`` with probability ``p_zero(x)`` + - ``y ~ +exp(mu_pos(x) + sigma * z)`` with probability ``p_pos(x)`` + - ``y ~ -exp(mu_neg(x) + sigma * z)`` with probability ``p_neg(x)`` + + Predictors ``x1, x2 ~ Uniform(0, 1)``. Mixing probabilities depend + on ``x1 + x2`` so there is real conditional structure the gate + classifier can learn. ``mu_pos`` and ``mu_neg`` use different + coefficients so the positive and negative regimes are distinct + populations. + + With ``gap_floor > 0`` positives are floored at exp(gap_floor) and + negatives at -exp(gap_floor), creating a clean "interior band" + between the regimes. Approaches that mix regimes will sometimes + draw into the interior; the tripartite approach cannot. + """ + + n: int = 10_000 + gap_floor: float = 2.5 # exp(2.5) ≈ 12.2 ; interior band = (-12.2, 12.2) + sigma: float = 0.6 + seed: int = 42 + + +def generate_data(config: DGPConfig) -> pd.DataFrame: + rng = np.random.default_rng(config.seed) + n = config.n + x1 = rng.uniform(0, 1, size=n) + x2 = rng.uniform(0, 1, size=n) + # Mixing probabilities: three-way softmax over linear scores. + logit_zero = 1.0 - 2.0 * (x1 + x2) # higher when x small + logit_pos = -1.0 + 3.0 * x1 # higher for large x1 + logit_neg = -1.0 + 3.0 * x2 # higher for large x2 + logits = np.stack([logit_neg, logit_zero, logit_pos], axis=1) + logits -= logits.max(axis=1, keepdims=True) + probs = np.exp(logits) + probs /= probs.sum(axis=1, keepdims=True) + # Sample regime per record. + u = rng.random(n) + cum = np.cumsum(probs, axis=1) + regime_idx = (cum >= u[:, None]).argmax(axis=1) + # 0 => negative, 1 => zero, 2 => positive. + y = np.zeros(n, dtype=float) + z = rng.standard_normal(n) + # Positive regime: distinct mu function. Hard-floor at + # exp(gap_floor) so there is a genuine empty band between the + # positive and negative regimes in the training data. + pos_mask = regime_idx == 2 + mu_pos = 1.0 + 0.5 * x1[pos_mask] + 1.5 * x2[pos_mask] + raw_pos = np.exp(mu_pos + config.sigma * z[pos_mask]) + y[pos_mask] = np.exp(config.gap_floor) + raw_pos + # Negative regime: distinct mu function; hard ceiling at + # -exp(gap_floor). + neg_mask = regime_idx == 0 + mu_neg = 1.0 + 1.5 * x1[neg_mask] + 0.5 * x2[neg_mask] + raw_neg = np.exp(mu_neg + config.sigma * z[neg_mask]) + y[neg_mask] = -(np.exp(config.gap_floor) + raw_neg) + # Zero regime: exact 0. + return pd.DataFrame({"x1": x1, "x2": x2, "y": y}) + + +def split_train_test( + df: pd.DataFrame, test_fraction: float, seed: int +) -> tuple[pd.DataFrame, pd.DataFrame]: + rng = np.random.default_rng(seed + 1) + idx = rng.permutation(len(df)) + cut = int(len(df) * (1.0 - test_fraction)) + return df.iloc[idx[:cut]].reset_index(drop=True), df.iloc[idx[cut:]].reset_index( + drop=True + ) + + +# ------------------------------------------------------------------- +# Four approaches +# ------------------------------------------------------------------- + + +def fit_tripartite(train: pd.DataFrame): + imputer = ZeroInflatedImputer( + base_imputer_class=QRF, + base_imputer_kwargs={}, + ) + result = imputer.fit(train, predictors=["x1", "x2"], imputed_variables=["y"]) + return result, imputer.get_regime("y") + + +def _build_binary_gate_split( + train: pd.DataFrame, include_negatives: bool +) -> tuple[Any, Any]: + """Approach B or C: binary nonzero/zero gate + one QRF. + + include_negatives=True → approach B (mixes pos + neg in QRF). + include_negatives=False → approach C (drops neg, QRF on positives). + """ + from sklearn.ensemble import HistGradientBoostingClassifier + + x_values = train[["x1", "x2"]].to_numpy() + y_values = train["y"].to_numpy() + + zero_atol = 1e-6 + if include_negatives: + labels = (np.abs(y_values) > zero_atol).astype(int) + else: + labels = (y_values > zero_atol).astype(int) + + clf = HistGradientBoostingClassifier(random_state=42) + clf.fit(x_values, labels) + + if include_negatives: + nonzero_mask = np.abs(y_values) > zero_atol + else: + nonzero_mask = y_values > zero_atol + + qrf = QRF(log_level="ERROR") + qrf_result = qrf.fit( + train.loc[nonzero_mask].reset_index(drop=True), + predictors=["x1", "x2"], + imputed_variables=["y"], + ) + return clf, qrf_result + + +def fit_binary_nonzero(train: pd.DataFrame): + return _build_binary_gate_split(train, include_negatives=True) + + +def fit_positive_only(train: pd.DataFrame): + return _build_binary_gate_split(train, include_negatives=False) + + +def fit_no_gate(train: pd.DataFrame): + qrf = QRF(log_level="ERROR") + return qrf.fit(train, predictors=["x1", "x2"], imputed_variables=["y"]) + + +# ------------------------------------------------------------------- +# Prediction wrappers +# ------------------------------------------------------------------- + + +def predict_tripartite(result, test: pd.DataFrame) -> np.ndarray: + preds = result.predict(test[["x1", "x2"]]) + return preds["y"].to_numpy(dtype=float) + + +def predict_binary_gate(fitted_tuple, test: pd.DataFrame) -> np.ndarray: + clf, qrf_result = fitted_tuple + x_values = test[["x1", "x2"]].to_numpy() + rng = np.random.default_rng(1234) + proba = clf.predict_proba(x_values) + # Positive class = 1 means nonzero (B) or positive (C). + pos_idx = int(np.where(clf.classes_ == 1)[0][0]) + positive_prob = proba[:, pos_idx] + u = rng.random(len(test)) + is_nonzero = u < positive_prob + out = np.zeros(len(test), dtype=float) + if is_nonzero.any(): + sub = qrf_result.predict(test.loc[is_nonzero, ["x1", "x2"]]) + if isinstance(sub, dict): + sub = next(iter(sub.values())) + out[is_nonzero] = sub["y"].to_numpy(dtype=float) + return out + + +def predict_no_gate(qrf_result, test: pd.DataFrame) -> np.ndarray: + preds = qrf_result.predict(test[["x1", "x2"]]) + if isinstance(preds, dict): + preds = next(iter(preds.values())) + return preds["y"].to_numpy(dtype=float) + + +# ------------------------------------------------------------------- +# Metrics +# ------------------------------------------------------------------- + + +def pinball_loss(pred: np.ndarray, truth: np.ndarray, q: float = 0.5) -> float: + residual = truth - pred + loss = np.where(residual >= 0, q * residual, (q - 1) * residual) + return float(loss.mean()) + + +def zero_rate_mae(pred: np.ndarray, truth: np.ndarray, atol: float = 1e-6) -> float: + pred_zero = (np.abs(pred) <= atol).mean() + true_zero = (np.abs(truth) <= atol).mean() + return float(abs(pred_zero - true_zero)) + + +def sign_match_rate(pred: np.ndarray, truth: np.ndarray, atol: float = 1e-6) -> float: + def _sign(values: np.ndarray) -> np.ndarray: + s = np.zeros_like(values, dtype=int) + s[values > atol] = 1 + s[values < -atol] = -1 + return s + + return float((_sign(pred) == _sign(truth)).mean()) + + +def ks_distance(pred: np.ndarray, truth: np.ndarray) -> float: + from scipy import stats + + ks = stats.ks_2samp(pred, truth) + return float(ks.statistic) + + +def interior_band_violation_rate( + pred: np.ndarray, gap_floor: float, atol: float = 1e-6 +) -> float: + """Fraction of predictions in the interior band ``(-exp(gap_floor), exp(gap_floor))`` + that are not exact zero (those are legitimate).""" + band = np.exp(gap_floor) + interior = (np.abs(pred) < band) & (np.abs(pred) > atol) + return float(interior.mean()) + + +# ------------------------------------------------------------------- +# Runner +# ------------------------------------------------------------------- + + +def run_experiment(config: DGPConfig) -> Dict[str, Any]: + data = generate_data(config) + train, test = split_train_test(data, test_fraction=0.2, seed=config.seed) + + # Summary of the DGP itself on the holdout. + test_y = test["y"].to_numpy(dtype=float) + dgp_summary = { + "n_train": int(len(train)), + "n_test": int(len(test)), + "test_frac_zero": float((np.abs(test_y) <= 1e-6).mean()), + "test_frac_pos": float((test_y > 1e-6).mean()), + "test_frac_neg": float((test_y < -1e-6).mean()), + } + + # Approach A: tripartite. + a_result, a_regime = fit_tripartite(train) + a_pred = predict_tripartite(a_result, test) + + # Approach B: binary nonzero + single QRF (mixes pos+neg). + b_fitted = fit_binary_nonzero(train) + b_pred = predict_binary_gate(b_fitted, test) + + # Approach C: positive-only + QRF (the microplex-us bug). + c_fitted = fit_positive_only(train) + c_pred = predict_binary_gate(c_fitted, test) + + # Approach D: no gate. + d_result = fit_no_gate(train) + d_pred = predict_no_gate(d_result, test) + + def _score( + name: str, pred: np.ndarray, extra: Dict[str, Any] = None + ) -> Dict[str, Any]: + metrics = { + "approach": name, + "pinball_loss_q50": pinball_loss(pred, test_y, 0.5), + "zero_rate_mae": zero_rate_mae(pred, test_y), + "sign_match_rate": sign_match_rate(pred, test_y), + "ks_distance": ks_distance(pred, test_y), + "interior_band_violation_rate": interior_band_violation_rate( + pred, config.gap_floor + ), + "pred_frac_zero": float((np.abs(pred) <= 1e-6).mean()), + "pred_frac_pos": float((pred > 1e-6).mean()), + "pred_frac_neg": float((pred < -1e-6).mean()), + } + if extra: + metrics.update(extra) + return metrics + + rows = [ + _score("A_tripartite", a_pred, {"detected_regime": a_regime}), + _score("B_binary_nonzero_mixed_qrf", b_pred), + _score("C_positive_only_qrf_bug", c_pred), + _score("D_no_gate_bare_qrf", d_pred), + ] + + return { + "dgp_config": { + "n": config.n, + "gap_floor": config.gap_floor, + "sigma": config.sigma, + "seed": config.seed, + }, + "dgp_summary_on_holdout": dgp_summary, + "approaches": rows, + } + + +def _aggregate(runs: List[Dict[str, Any]]) -> List[Dict[str, Any]]: + """Aggregate per-approach metrics across multiple seeds.""" + grouped: Dict[str, List[Dict[str, Any]]] = {} + for run in runs: + for row in run["approaches"]: + grouped.setdefault(row["approach"], []).append(row) + metric_keys = [ + "pinball_loss_q50", + "zero_rate_mae", + "sign_match_rate", + "ks_distance", + "interior_band_violation_rate", + "pred_frac_zero", + "pred_frac_pos", + "pred_frac_neg", + ] + aggregated = [] + for name, rows in grouped.items(): + summary: Dict[str, Any] = {"approach": name, "n_seeds": len(rows)} + for key in metric_keys: + values = np.array([r[key] for r in rows], dtype=float) + summary[f"{key}_mean"] = float(values.mean()) + summary[f"{key}_std"] = ( + float(values.std(ddof=1)) if len(values) > 1 else 0.0 + ) + # Carry forward the detected regime if all seeds agree. + regimes = {r.get("detected_regime") for r in rows} + if regimes and None not in regimes and len(regimes) == 1: + summary["detected_regime"] = next(iter(regimes)) + aggregated.append(summary) + return aggregated + + +def main(argv: List[str] | None = None) -> int: + parser = argparse.ArgumentParser(description=__doc__ or "") + parser.add_argument("--n", type=int, default=10_000) + parser.add_argument("--gap-floor", type=float, default=2.5) + parser.add_argument("--sigma", type=float, default=0.6) + parser.add_argument( + "--seeds", + type=int, + nargs="+", + default=[42, 43, 44, 45, 46], + help="Seeds to average over for multi-seed uncertainty.", + ) + parser.add_argument( + "--output", + type=Path, + default=Path(__file__).parent / "regime_aware_holdout_results.json", + ) + args = parser.parse_args(argv) + + runs = [] + for seed in args.seeds: + config = DGPConfig( + n=args.n, gap_floor=args.gap_floor, sigma=args.sigma, seed=seed + ) + runs.append(run_experiment(config)) + + aggregated = _aggregate(runs) + + output = { + "dgp_config_template": { + "n": args.n, + "gap_floor": args.gap_floor, + "sigma": args.sigma, + "seeds": args.seeds, + }, + "dgp_summary_on_holdout_per_seed": [ + {**r["dgp_summary_on_holdout"], "seed": r["dgp_config"]["seed"]} + for r in runs + ], + "aggregated_by_approach": aggregated, + "per_run_raw": runs, + } + + args.output.parent.mkdir(parents=True, exist_ok=True) + args.output.write_text(json.dumps(output, indent=2)) + + print( + f"\nMulti-seed holdout experiment ({len(args.seeds)} seeds, " + f"n={args.n} each, gap_floor={args.gap_floor})" + ) + print( + f"\n{'approach':<32}{'pinball (mean±std)':>24}" + f"{'zero_mae':>16}{'sign_hit':>16}{'ks':>14}{'interior':>14}" + ) + for row in aggregated: + print( + f"{row['approach']:<32}" + f"{row['pinball_loss_q50_mean']:>10.2f} ± {row['pinball_loss_q50_std']:<7.2f} " + f"{row['zero_rate_mae_mean']:>6.4f} ± {row['zero_rate_mae_std']:<6.4f} " + f"{row['sign_match_rate_mean']:>6.3f} ± {row['sign_match_rate_std']:<5.3f} " + f"{row['ks_distance_mean']:>5.3f} ± {row['ks_distance_std']:<5.3f} " + f"{row['interior_band_violation_rate_mean']:>5.3f} ± {row['interior_band_violation_rate_std']:<5.3f}" + ) + print(f"\nWrote {args.output}") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/experiments/regime_aware_holdout_results.json b/experiments/regime_aware_holdout_results.json new file mode 100644 index 0000000..a858cea --- /dev/null +++ b/experiments/regime_aware_holdout_results.json @@ -0,0 +1,451 @@ +{ + "dgp_config_template": { + "n": 10000, + "gap_floor": 2.5, + "sigma": 0.6, + "seeds": [ + 42, + 43, + 44, + 45, + 46 + ] + }, + "dgp_summary_on_holdout_per_seed": [ + { + "n_train": 8000, + "n_test": 2000, + "test_frac_zero": 0.1465, + "test_frac_pos": 0.425, + "test_frac_neg": 0.4285, + "seed": 42 + }, + { + "n_train": 8000, + "n_test": 2000, + "test_frac_zero": 0.155, + "test_frac_pos": 0.4125, + "test_frac_neg": 0.4325, + "seed": 43 + }, + { + "n_train": 8000, + "n_test": 2000, + "test_frac_zero": 0.18, + "test_frac_pos": 0.4115, + "test_frac_neg": 0.4085, + "seed": 44 + }, + { + "n_train": 8000, + "n_test": 2000, + "test_frac_zero": 0.15, + "test_frac_pos": 0.4145, + "test_frac_neg": 0.4355, + "seed": 45 + }, + { + "n_train": 8000, + "n_test": 2000, + "test_frac_zero": 0.157, + "test_frac_pos": 0.4125, + "test_frac_neg": 0.4305, + "seed": 46 + } + ], + "aggregated_by_approach": [ + { + "approach": "A_tripartite", + "n_seeds": 5, + "pinball_loss_q50_mean": 9.504563871730959, + "pinball_loss_q50_std": 0.13297349009556256, + "zero_rate_mae_mean": 0.007800000000000001, + "zero_rate_mae_std": 0.013349157276772192, + "sign_match_rate_mean": 0.5318999999999999, + "sign_match_rate_std": 0.01223417344980853, + "ks_distance_mean": 0.0328, + "ks_distance_std": 0.006230971031869753, + "interior_band_violation_rate_mean": 0.0, + "interior_band_violation_rate_std": 0.0, + "pred_frac_zero_mean": 0.15009999999999998, + "pred_frac_zero_std": 0.005738031021177915, + "pred_frac_pos_mean": 0.4288, + "pred_frac_pos_std": 0.01118369348650079, + "pred_frac_neg_mean": 0.42110000000000003, + "pred_frac_neg_std": 0.012466956324620708, + "detected_regime": "THREE_SIGN" + }, + { + "approach": "B_binary_nonzero_mixed_qrf", + "n_seeds": 5, + "pinball_loss_q50_mean": 9.457650761916245, + "pinball_loss_q50_std": 0.17153724206196144, + "zero_rate_mae_mean": 0.006900000000000001, + "zero_rate_mae_std": 0.008883974335847663, + "sign_match_rate_mean": 0.5318, + "sign_match_rate_std": 0.00450832563153993, + "ks_distance_mean": 0.0263, + "ks_distance_std": 0.008051397394241573, + "interior_band_violation_rate_mean": 0.005699999999999999, + "interior_band_violation_rate_std": 0.0016046806535881212, + "pred_frac_zero_mean": 0.15219999999999997, + "pred_frac_zero_std": 0.007102816342831911, + "pred_frac_pos_mean": 0.4183, + "pred_frac_pos_std": 0.006270964838045265, + "pred_frac_neg_mean": 0.4295, + "pred_frac_neg_std": 0.008329165624478848 + }, + { + "approach": "C_positive_only_qrf_bug", + "n_seeds": 5, + "pinball_loss_q50_mean": 9.429197111055897, + "pinball_loss_q50_std": 0.14365662135694904, + "zero_rate_mae_mean": 0.42210000000000003, + "zero_rate_mae_std": 0.019695811737524334, + "sign_match_rate_mean": 0.33910000000000007, + "sign_match_rate_std": 0.01307860848867339, + "ks_distance_mean": 0.4271, + "ks_distance_std": 0.010714476188783108, + "interior_band_violation_rate_mean": 0.0, + "interior_band_violation_rate_std": 0.0, + "pred_frac_zero_mean": 0.5798, + "pred_frac_zero_std": 0.00725947656515264, + "pred_frac_pos_mean": 0.4202, + "pred_frac_pos_std": 0.007259476565152621, + "pred_frac_neg_mean": 0.0, + "pred_frac_neg_std": 0.0 + }, + { + "approach": "D_no_gate_bare_qrf", + "n_seeds": 5, + "pinball_loss_q50_mean": 9.425760374755473, + "pinball_loss_q50_std": 0.17881434068382207, + "zero_rate_mae_mean": 0.014100000000000001, + "zero_rate_mae_std": 0.011690808355284929, + "sign_match_rate_mean": 0.529, + "sign_match_rate_std": 0.013355710389193072, + "ks_distance_mean": 0.0259, + "ks_distance_std": 0.005594640292279745, + "interior_band_violation_rate_mean": 0.013699999999999999, + "interior_band_violation_rate_std": 0.0029706901555025898, + "pred_frac_zero_mean": 0.1478, + "pred_frac_zero_std": 0.010545141061171253, + "pred_frac_pos_mean": 0.426, + "pred_frac_pos_std": 0.013024016277631117, + "pred_frac_neg_mean": 0.42619999999999997, + "pred_frac_neg_std": 0.009984988733093302 + } + ], + "per_run_raw": [ + { + "dgp_config": { + "n": 10000, + "gap_floor": 2.5, + "sigma": 0.6, + "seed": 42 + }, + "dgp_summary_on_holdout": { + "n_train": 8000, + "n_test": 2000, + "test_frac_zero": 0.1465, + "test_frac_pos": 0.425, + "test_frac_neg": 0.4285 + }, + "approaches": [ + { + "approach": "A_tripartite", + "pinball_loss_q50": 9.394688284129616, + "zero_rate_mae": 0.004500000000000004, + "sign_match_rate": 0.5405, + "ks_distance": 0.0325, + "interior_band_violation_rate": 0.0, + "pred_frac_zero": 0.142, + "pred_frac_pos": 0.417, + "pred_frac_neg": 0.441, + "detected_regime": "THREE_SIGN" + }, + { + "approach": "B_binary_nonzero_mixed_qrf", + "pinball_loss_q50": 9.430705168980548, + "zero_rate_mae": 0.0010000000000000009, + "sign_match_rate": 0.5345, + "ks_distance": 0.015, + "interior_band_violation_rate": 0.007, + "pred_frac_zero": 0.1455, + "pred_frac_pos": 0.42, + "pred_frac_neg": 0.4345 + }, + { + "approach": "C_positive_only_qrf_bug", + "pinball_loss_q50": 9.345162909308307, + "zero_rate_mae": 0.44300000000000006, + "sign_match_rate": 0.3335, + "ks_distance": 0.4285, + "interior_band_violation_rate": 0.0, + "pred_frac_zero": 0.5895, + "pred_frac_pos": 0.4105, + "pred_frac_neg": 0.0 + }, + { + "approach": "D_no_gate_bare_qrf", + "pinball_loss_q50": 9.156142354206747, + "zero_rate_mae": 0.010000000000000009, + "sign_match_rate": 0.5465, + "ks_distance": 0.0165, + "interior_band_violation_rate": 0.0155, + "pred_frac_zero": 0.1565, + "pred_frac_pos": 0.418, + "pred_frac_neg": 0.4255 + } + ] + }, + { + "dgp_config": { + "n": 10000, + "gap_floor": 2.5, + "sigma": 0.6, + "seed": 43 + }, + "dgp_summary_on_holdout": { + "n_train": 8000, + "n_test": 2000, + "test_frac_zero": 0.155, + "test_frac_pos": 0.4125, + "test_frac_neg": 0.4325 + }, + "approaches": [ + { + "approach": "A_tripartite", + "pinball_loss_q50": 9.524598386229703, + "zero_rate_mae": 0.0020000000000000018, + "sign_match_rate": 0.538, + "ks_distance": 0.026, + "interior_band_violation_rate": 0.0, + "pred_frac_zero": 0.153, + "pred_frac_pos": 0.4275, + "pred_frac_neg": 0.4195, + "detected_regime": "THREE_SIGN" + }, + { + "approach": "B_binary_nonzero_mixed_qrf", + "pinball_loss_q50": 9.656069408067603, + "zero_rate_mae": 0.0020000000000000018, + "sign_match_rate": 0.524, + "ks_distance": 0.0315, + "interior_band_violation_rate": 0.0065, + "pred_frac_zero": 0.153, + "pred_frac_pos": 0.4215, + "pred_frac_neg": 0.4255 + }, + { + "approach": "C_positive_only_qrf_bug", + "pinball_loss_q50": 9.671978780602487, + "zero_rate_mae": 0.4285, + "sign_match_rate": 0.329, + "ks_distance": 0.4325, + "interior_band_violation_rate": 0.0, + "pred_frac_zero": 0.5835, + "pred_frac_pos": 0.4165, + "pred_frac_neg": 0.0 + }, + { + "approach": "D_no_gate_bare_qrf", + "pinball_loss_q50": 9.5327772177011, + "zero_rate_mae": 0.022499999999999992, + "sign_match_rate": 0.5305, + "ks_distance": 0.0305, + "interior_band_violation_rate": 0.016, + "pred_frac_zero": 0.1325, + "pred_frac_pos": 0.443, + "pred_frac_neg": 0.4245 + } + ] + }, + { + "dgp_config": { + "n": 10000, + "gap_floor": 2.5, + "sigma": 0.6, + "seed": 44 + }, + "dgp_summary_on_holdout": { + "n_train": 8000, + "n_test": 2000, + "test_frac_zero": 0.18, + "test_frac_pos": 0.4115, + "test_frac_neg": 0.4085 + }, + "approaches": [ + { + "approach": "A_tripartite", + "pinball_loss_q50": 9.61320677611318, + "zero_rate_mae": 0.0315, + "sign_match_rate": 0.514, + "ks_distance": 0.036, + "interior_band_violation_rate": 0.0, + "pred_frac_zero": 0.1485, + "pred_frac_pos": 0.4395, + "pred_frac_neg": 0.412, + "detected_regime": "THREE_SIGN" + }, + { + "approach": "B_binary_nonzero_mixed_qrf", + "pinball_loss_q50": 9.310594037138305, + "zero_rate_mae": 0.022499999999999992, + "sign_match_rate": 0.532, + "ks_distance": 0.0325, + "interior_band_violation_rate": 0.003, + "pred_frac_zero": 0.1575, + "pred_frac_pos": 0.425, + "pred_frac_neg": 0.4175 + }, + { + "approach": "C_positive_only_qrf_bug", + "pinball_loss_q50": 9.310577225932665, + "zero_rate_mae": 0.38999999999999996, + "sign_match_rate": 0.362, + "ks_distance": 0.4085, + "interior_band_violation_rate": 0.0, + "pred_frac_zero": 0.57, + "pred_frac_pos": 0.43, + "pred_frac_neg": 0.0 + }, + { + "approach": "D_no_gate_bare_qrf", + "pinball_loss_q50": 9.632142894165515, + "zero_rate_mae": 0.0295, + "sign_match_rate": 0.513, + "ks_distance": 0.0255, + "interior_band_violation_rate": 0.0155, + "pred_frac_zero": 0.1505, + "pred_frac_pos": 0.437, + "pred_frac_neg": 0.4125 + } + ] + }, + { + "dgp_config": { + "n": 10000, + "gap_floor": 2.5, + "sigma": 0.6, + "seed": 45 + }, + "dgp_summary_on_holdout": { + "n_train": 8000, + "n_test": 2000, + "test_frac_zero": 0.15, + "test_frac_pos": 0.4145, + "test_frac_neg": 0.4355 + }, + "approaches": [ + { + "approach": "A_tripartite", + "pinball_loss_q50": 9.343054878316657, + "zero_rate_mae": 0.0005000000000000004, + "sign_match_rate": 0.5425, + "ks_distance": 0.0415, + "interior_band_violation_rate": 0.0, + "pred_frac_zero": 0.1495, + "pred_frac_pos": 0.441, + "pred_frac_neg": 0.4095, + "detected_regime": "THREE_SIGN" + }, + { + "approach": "B_binary_nonzero_mixed_qrf", + "pinball_loss_q50": 9.61229630411126, + "zero_rate_mae": 0.005500000000000005, + "sign_match_rate": 0.5335, + "ks_distance": 0.0205, + "interior_band_violation_rate": 0.0055, + "pred_frac_zero": 0.1445, + "pred_frac_pos": 0.4165, + "pred_frac_neg": 0.439 + }, + { + "approach": "C_positive_only_qrf_bug", + "pinball_loss_q50": 9.438016054311909, + "zero_rate_mae": 0.4285, + "sign_match_rate": 0.335, + "ks_distance": 0.4355, + "interior_band_violation_rate": 0.0, + "pred_frac_zero": 0.5785, + "pred_frac_pos": 0.4215, + "pred_frac_neg": 0.0 + }, + { + "approach": "D_no_gate_bare_qrf", + "pinball_loss_q50": 9.393878587927006, + "zero_rate_mae": 0.008000000000000007, + "sign_match_rate": 0.536, + "ks_distance": 0.0295, + "interior_band_violation_rate": 0.0125, + "pred_frac_zero": 0.142, + "pred_frac_pos": 0.4175, + "pred_frac_neg": 0.4405 + } + ] + }, + { + "dgp_config": { + "n": 10000, + "gap_floor": 2.5, + "sigma": 0.6, + "seed": 46 + }, + "dgp_summary_on_holdout": { + "n_train": 8000, + "n_test": 2000, + "test_frac_zero": 0.157, + "test_frac_pos": 0.4125, + "test_frac_neg": 0.4305 + }, + "approaches": [ + { + "approach": "A_tripartite", + "pinball_loss_q50": 9.647271033865634, + "zero_rate_mae": 0.0005000000000000004, + "sign_match_rate": 0.5245, + "ks_distance": 0.028, + "interior_band_violation_rate": 0.0, + "pred_frac_zero": 0.1575, + "pred_frac_pos": 0.419, + "pred_frac_neg": 0.4235, + "detected_regime": "THREE_SIGN" + }, + { + "approach": "B_binary_nonzero_mixed_qrf", + "pinball_loss_q50": 9.278588891283503, + "zero_rate_mae": 0.003500000000000003, + "sign_match_rate": 0.535, + "ks_distance": 0.032, + "interior_band_violation_rate": 0.0065, + "pred_frac_zero": 0.1605, + "pred_frac_pos": 0.4085, + "pred_frac_neg": 0.431 + }, + { + "approach": "C_positive_only_qrf_bug", + "pinball_loss_q50": 9.380250585124125, + "zero_rate_mae": 0.4205, + "sign_match_rate": 0.336, + "ks_distance": 0.4305, + "interior_band_violation_rate": 0.0, + "pred_frac_zero": 0.5775, + "pred_frac_pos": 0.4225, + "pred_frac_neg": 0.0 + }, + { + "approach": "D_no_gate_bare_qrf", + "pinball_loss_q50": 9.413860819777009, + "zero_rate_mae": 0.0005000000000000004, + "sign_match_rate": 0.519, + "ks_distance": 0.0275, + "interior_band_violation_rate": 0.009, + "pred_frac_zero": 0.1575, + "pred_frac_pos": 0.4145, + "pred_frac_neg": 0.428 + } + ] + } + ] +} \ No newline at end of file diff --git a/microimpute/models/zero_inflated.py b/microimpute/models/zero_inflated.py new file mode 100644 index 0000000..5ffb21a --- /dev/null +++ b/microimpute/models/zero_inflated.py @@ -0,0 +1,698 @@ +"""Regime-aware zero-inflation wrapper around base imputers. + +Tabular microdata variables often fall into distinct *regimes* based on +which of {negative, zero, positive} values appear in the training data. +Imputing them with a single regressor mixes regimes together, causing +two recurring bugs in downstream ecosystems: + +1. **Negative-dropping.** The common "fit QRF on ``y > 0``" pattern + drops negative training rows along with zeros, so the imputer + produces zero or positive values only. Variables like + ``short_term_capital_gains`` lose their entire negative tail. + +2. **Zero-crossing interpolation.** A QRF fit on all nonzero values + (both signs) learns leaf distributions that interpolate between + positive and negative training rows. Predictions for records that + the gate marks "nonzero" can land in the interval between + ``max(train_negatives)`` and ``min(train_positives)``, which is + not a region any actual record occupies. + +``ZeroInflatedImputer`` wraps any base ``Imputer`` and: + +- Detects the regime automatically at fit time from the training + distribution — no per-variable hand configuration required. +- Composes the base imputer with appropriate gate(s): + - Three-sign: gate chooses ``{neg, 0, pos}``; separate base + imputers on the positive and negative subsets. + - ZI positive / ZI negative: binary gate (``0`` vs nonzero); base + imputer on the nonzero-sign subset. + - Sign-only (no zero): binary sign gate; two base imputers. + - Single-sign or constant: no gate; direct base imputer or a + constant imputer. +- At predict time, routes each record to the base imputer of its + gate-assigned regime, guaranteeing no sign-interpolation leaks. + +The wrapper is generic over the base imputer — ``QRF`` is the obvious +default, but ``MDN``, ``OLS``, or ``Matching`` all compose the same way. + +Regime detection is parameterized by ``min_class_count`` and +``min_class_fraction``: a class with fewer observations than both +thresholds collapses into the closest adjacent regime. This avoids +fitting a full three-sign split on a variable whose negative tail is +five outlier rows — the cost-benefit flips toward the simpler +architecture. +""" + +from __future__ import annotations + +import logging +from typing import Any, Dict, List, Optional, Tuple, Type, Union + +import numpy as np +import pandas as pd +from pydantic import SkipValidation, validate_call + +from microimpute.config import RANDOM_STATE, VALIDATE_CONFIG +from microimpute.models.imputer import ( + Imputer, + ImputerResults, + _ConstantValueModel, +) +from microimpute.models.qrf import QRF + + +# Regime labels. Kept as module-level constants so downstream code can +# match on them without magic strings. +REGIME_THREE_SIGN = "THREE_SIGN" +REGIME_ZI_POSITIVE = "ZI_POSITIVE" +REGIME_ZI_NEGATIVE = "ZI_NEGATIVE" +REGIME_SIGN_ONLY = "SIGN_ONLY" +REGIME_POSITIVE_ONLY = "POSITIVE_ONLY" +REGIME_NEGATIVE_ONLY = "NEGATIVE_ONLY" +REGIME_DEGENERATE_ZERO = "DEGENERATE_ZERO" + + +def _make_classifier(kind: str, seed: int): + """Build a sklearn classifier for the zero-gate. + + ``hist_gb`` (default): ``HistGradientBoostingClassifier``. On the + isolated-log-loss benchmark over 26 zero-inflated PolicyEngine-US + target variables this Pareto-dominated a 50-tree RF on log-loss + (0.225 vs 0.310), Brier (0.071 vs 0.081), ECE (0.005 vs 0.039), + and ROC-AUC (0.809 vs 0.737). + """ + if kind == "hist_gb": + from sklearn.ensemble import HistGradientBoostingClassifier + + return HistGradientBoostingClassifier(random_state=seed) + if kind == "rf": + from sklearn.ensemble import RandomForestClassifier + + return RandomForestClassifier(n_estimators=50, random_state=seed, n_jobs=-1) + raise ValueError(f"Unknown classifier_type {kind!r}; expected 'hist_gb' or 'rf'.") + + +def _detect_regime( + y: np.ndarray, + *, + min_class_count: int, + min_class_fraction: float, + zero_atol: float, +) -> str: + """Classify the training distribution into one of seven regimes. + + A class (neg/zero/pos) counts as present iff its count is at least + ``min_class_count`` AND its fraction of total rows is at least + ``min_class_fraction``. Below both thresholds, the class collapses + into its closest adjacent regime (minority negatives merge into + zero → ZI_POSITIVE; minority zeros merge into the majority sign; + etc.). This keeps the gate architecture stable in the presence of + measurement-error outliers. + """ + n = len(y) + if n == 0: + return REGIME_DEGENERATE_ZERO + + is_zero = np.abs(y) <= zero_atol + is_pos = y > zero_atol + is_neg = y < -zero_atol + + n_zero = int(is_zero.sum()) + n_pos = int(is_pos.sum()) + n_neg = int(is_neg.sum()) + + # Apply both thresholds. + def _meaningful(count: int) -> bool: + return count >= min_class_count and (count / n) >= min_class_fraction + + has_zero = _meaningful(n_zero) + has_pos = _meaningful(n_pos) + has_neg = _meaningful(n_neg) + + if not (has_zero or has_pos or has_neg): + # All three classes are below threshold. Pick the one with the + # largest raw count as a degenerate fallback. + counts = {"zero": n_zero, "pos": n_pos, "neg": n_neg} + majority = max(counts, key=counts.get) + if majority == "zero": + return REGIME_DEGENERATE_ZERO + return REGIME_POSITIVE_ONLY if majority == "pos" else REGIME_NEGATIVE_ONLY + + if has_pos and has_neg and has_zero: + return REGIME_THREE_SIGN + if has_pos and has_neg: + return REGIME_SIGN_ONLY + if has_pos and has_zero: + return REGIME_ZI_POSITIVE + if has_neg and has_zero: + return REGIME_ZI_NEGATIVE + if has_pos: + return REGIME_POSITIVE_ONLY + if has_neg: + return REGIME_NEGATIVE_ONLY + return REGIME_DEGENERATE_ZERO + + +class ZeroInflatedImputer(Imputer): + """Imputer that wraps a base Imputer with regime-aware zero-gating. + + Args: + base_imputer_class: ``Imputer`` subclass to use for the nonzero + regression step. Defaults to ``QRF``. + base_imputer_kwargs: Keyword arguments forwarded to the base + imputer constructor. ``{}`` by default. + min_class_count: Minimum raw count per class (neg/0/pos) for + that class to be considered present. Below this, the class + collapses into an adjacent regime. Defaults to 10. + min_class_fraction: Minimum fraction of total rows per class + for that class to be considered present. Defaults to 0.01. + zero_atol: Absolute tolerance for "equals zero" in the regime + detector. Defaults to 1e-6, matching the upstream + ``_MultiSourceBase`` convention. + classifier_type: Backend for the gate classifier; + ``"hist_gb"`` (default) or ``"rf"``. + seed: Random seed. + log_level: Python logging level. + """ + + def __init__( + self, + base_imputer_class: Optional[Type[Imputer]] = None, + base_imputer_kwargs: Optional[Dict[str, Any]] = None, + min_class_count: int = 10, + min_class_fraction: float = 0.01, + zero_atol: float = 1e-6, + classifier_type: str = "hist_gb", + seed: Optional[int] = RANDOM_STATE, + log_level: Optional[str] = "WARNING", + ) -> None: + super().__init__(seed=seed, log_level=log_level) + self.base_imputer_class = base_imputer_class or QRF + self.base_imputer_kwargs = dict(base_imputer_kwargs or {}) + self.min_class_count = int(min_class_count) + self.min_class_fraction = float(min_class_fraction) + self.zero_atol = float(zero_atol) + self.classifier_type = classifier_type + + # Filled in during fit(). + self._regimes: Dict[str, str] = {} + self._per_variable: Dict[str, Dict[str, Any]] = {} + + def _fit(self, *args: Any, **kwargs: Any) -> Any: + """Abstract-method placeholder; this class overrides ``fit`` directly.""" + raise NotImplementedError( + "ZeroInflatedImputer overrides `fit` directly; `_fit` is not used." + ) + + def get_regime(self, variable: str) -> str: + """Return the detected regime label for a fitted variable.""" + if variable not in self._regimes: + raise KeyError(f"Variable {variable!r} not fitted; call fit() first.") + return self._regimes[variable] + + def fit( + self, + X_train: pd.DataFrame, + predictors: List[str], + imputed_variables: List[str], + weight_col: Optional[Union[str, np.ndarray, pd.Series]] = None, + skip_missing: bool = False, + not_numeric_categorical: Optional[List[str]] = None, + **kwargs: Any, + ) -> Any: + """Fit the regime-aware wrapper. + + Delegates non-numeric targets (categorical / boolean / + constant) to a single base imputer instance. Numeric targets + are handled per-variable: regime detection, then composition + of gate + base imputer(s) as appropriate. + + Returns a ``ZeroInflatedImputerResults`` that routes + predictions through each target's regime-specific pipeline. + """ + self._validate_data(X_train, predictors + imputed_variables) + + # Classify target variables as numeric / categorical / boolean / + # constant using the base Imputer's detector. + self.identify_target_types( + X_train, + imputed_variables, + not_numeric_categorical=not_numeric_categorical, + ) + + self.predictors = list(predictors) + self.imputed_variables = list(imputed_variables) + self._regimes = {} + self._per_variable = {} + + # Per-variable fit for numeric targets. Constant numeric + # targets (e.g. a column that is always 0 in training) are + # also treated here so their regime lands in the wrapper's + # ``_regimes`` map rather than being silently passed through. + constant_numeric_targets = [ + v + for v in imputed_variables + if v in self.constant_targets + and np.issubdtype( + pd.Series([self.constant_targets[v]["value"]]).dtype, + np.number, + ) + ] + numeric_targets = [ + v + for v in imputed_variables + if v in self.numeric_targets or v in constant_numeric_targets + ] + for var in numeric_targets: + y = X_train[var].to_numpy(dtype=float, copy=False) + regime = _detect_regime( + y, + min_class_count=self.min_class_count, + min_class_fraction=self.min_class_fraction, + zero_atol=self.zero_atol, + ) + self._regimes[var] = regime + self._per_variable[var] = self._fit_single_numeric( + X_train=X_train, + predictors=predictors, + variable=var, + regime=regime, + y=y, + ) + + # Non-numeric (categorical / boolean / constant) targets are + # handled by a single auxiliary base imputer over their union. + non_numeric = [v for v in imputed_variables if v not in numeric_targets] + if non_numeric: + aux = self.base_imputer_class( + log_level="ERROR", + **self.base_imputer_kwargs, + ) + aux_result = aux.fit( + X_train=X_train, + predictors=predictors, + imputed_variables=non_numeric, + weight_col=weight_col, + skip_missing=skip_missing, + not_numeric_categorical=not_numeric_categorical, + **kwargs, + ) + aux_bundle = {"kind": "passthrough", "result": aux_result} + else: + aux_bundle = None + + return ZeroInflatedImputerResults( + predictors=self.predictors, + imputed_variables=self.imputed_variables, + seed=self.seed, + regimes=self._regimes, + per_variable=self._per_variable, + non_numeric_bundle=aux_bundle, + log_level="WARNING", + ) + + # ------------------------------------------------------------------ + # Per-variable fit helpers + # ------------------------------------------------------------------ + + def _fit_single_numeric( + self, + *, + X_train: pd.DataFrame, + predictors: List[str], + variable: str, + regime: str, + y: np.ndarray, + ) -> Dict[str, Any]: + """Fit the gate and base imputer(s) for one numeric target. + + Returns a bundle dict with the regime, the gate classifier + (or None), and the base imputer(s) keyed by their role. + """ + X_pred = X_train[predictors].to_numpy(dtype=float, copy=False) + + if regime == REGIME_DEGENERATE_ZERO: + return {"kind": "constant", "value": 0.0} + + if regime in (REGIME_POSITIVE_ONLY, REGIME_NEGATIVE_ONLY): + # No gate; single base imputer on the full training set. + return { + "kind": "single", + "base": self._fit_base_single(X_train, predictors, variable), + } + + if regime == REGIME_ZI_POSITIVE: + labels = (y > self.zero_atol).astype(int) + clf = _make_classifier(self.classifier_type, self.seed) + clf.fit(X_pred, labels) + pos_mask = y > self.zero_atol + pos_base = self._fit_base_single( + X_train.loc[pos_mask], predictors, variable + ) + return { + "kind": "zi_positive", + "classifier": clf, + "positive_base": pos_base, + } + + if regime == REGIME_ZI_NEGATIVE: + labels = (y < -self.zero_atol).astype(int) + clf = _make_classifier(self.classifier_type, self.seed) + clf.fit(X_pred, labels) + neg_mask = y < -self.zero_atol + neg_base = self._fit_base_single( + X_train.loc[neg_mask], predictors, variable + ) + return { + "kind": "zi_negative", + "classifier": clf, + "negative_base": neg_base, + } + + if regime == REGIME_SIGN_ONLY: + # No zero class, but both signs present. Binary sign gate + # plus a base imputer per sign. + labels = (y > 0).astype(int) + clf = _make_classifier(self.classifier_type, self.seed) + clf.fit(X_pred, labels) + pos_mask = y > 0 + neg_mask = ~pos_mask + return { + "kind": "sign_only", + "classifier": clf, + "positive_base": self._fit_base_single( + X_train.loc[pos_mask], predictors, variable + ), + "negative_base": self._fit_base_single( + X_train.loc[neg_mask], predictors, variable + ), + } + + if regime == REGIME_THREE_SIGN: + # 0 / neg / pos three-way gate + two base imputers. + labels = np.where( + y > self.zero_atol, + 2, + np.where(y < -self.zero_atol, 0, 1), + ) + clf = _make_classifier(self.classifier_type, self.seed) + clf.fit(X_pred, labels) + pos_mask = y > self.zero_atol + neg_mask = y < -self.zero_atol + return { + "kind": "three_sign", + "classifier": clf, + "positive_base": self._fit_base_single( + X_train.loc[pos_mask], predictors, variable + ), + "negative_base": self._fit_base_single( + X_train.loc[neg_mask], predictors, variable + ), + } + + raise ValueError(f"Unhandled regime {regime!r}") + + def _fit_base_single( + self, + X_train: pd.DataFrame, + predictors: List[str], + variable: str, + ) -> ImputerResults: + """Fit a single base Imputer on a (possibly filtered) slice.""" + imputer = self.base_imputer_class( + log_level="ERROR", + **self.base_imputer_kwargs, + ) + return imputer.fit( + X_train=X_train, + predictors=predictors, + imputed_variables=[variable], + ) + + +class ZeroInflatedImputerResults(ImputerResults): + """Fitted regime-aware imputer ready for prediction.""" + + def __init__( + self, + predictors: List[str], + imputed_variables: List[str], + seed: int, + regimes: Dict[str, str], + per_variable: Dict[str, Dict[str, Any]], + non_numeric_bundle: Optional[Dict[str, Any]] = None, + imputed_vars_dummy_info: Optional[Dict[str, Any]] = None, + original_predictors: Optional[List[str]] = None, + log_level: Optional[str] = "WARNING", + ) -> None: + super().__init__( + predictors=predictors, + imputed_variables=imputed_variables, + seed=seed, + imputed_vars_dummy_info=imputed_vars_dummy_info, + original_predictors=original_predictors or predictors, + log_level=log_level, + ) + self._regimes = regimes + self._per_variable = per_variable + self._non_numeric_bundle = non_numeric_bundle + self._rng = np.random.default_rng(seed) + + @validate_call(config=VALIDATE_CONFIG) + def predict( + self, + X_test: pd.DataFrame, + quantiles: Optional[List[float]] = None, + return_probs: bool = False, + **kwargs: Any, + ) -> Union[pd.DataFrame, Dict[float, pd.DataFrame]]: + """Predict imputed values, routing per-variable by regime. + + For numeric targets, the gate assigns each record to zero, + positive, or negative regime (depending on the detected + regime), and the base imputer for that regime produces the + nonzero draw. Zeros are set exactly to 0.0 (no stochastic + smearing). + + For non-numeric targets (categorical / boolean / constant), + delegation is to the single auxiliary base imputer fit at + training time. + """ + if quantiles is not None: + # Quantile grid not currently supported in the wrapper; the + # regime routing only produces a single stochastic draw per + # call. Deterministic-quantile support would require the + # caller to specify quantile conditional on regime. + return { + q: self._predict_single_draw(X_test, quantile=q, **kwargs) + for q in quantiles + } + return self._predict_single_draw(X_test, quantile=None, **kwargs) + + def _predict_single_draw( + self, + X_test: pd.DataFrame, + quantile: Optional[float], + **kwargs: Any, + ) -> pd.DataFrame: + out = pd.DataFrame(index=X_test.index) + + for variable in self.imputed_variables: + regime = self._regimes.get(variable) + if regime is None: + # Non-numeric target; handled by the auxiliary bundle. + continue + bundle = self._per_variable[variable] + out[variable] = self._predict_single_variable( + X_test, variable, bundle, quantile=quantile, **kwargs + ) + + # Merge in non-numeric target predictions from the auxiliary + # single base imputer. + if self._non_numeric_bundle is not None: + aux_result = self._non_numeric_bundle["result"] + if quantile is None: + aux_preds = aux_result.predict(X_test) + else: + aux_dict = aux_result.predict(X_test, quantiles=[quantile]) + aux_preds = aux_dict[quantile] + for col in aux_preds.columns: + if col not in out.columns: + out[col] = aux_preds[col].values + + return out + + def _predict_single_variable( + self, + X_test: pd.DataFrame, + variable: str, + bundle: Dict[str, Any], + quantile: Optional[float], + **kwargs: Any, + ) -> np.ndarray: + n = len(X_test) + kind = bundle["kind"] + + if kind == "constant": + return np.full(n, bundle["value"], dtype=float) + + if kind == "single": + preds = self._invoke_base( + bundle["base"], X_test, quantile=quantile, **kwargs + ) + return preds[variable].to_numpy(dtype=float) + + X_pred = X_test[self.predictors].to_numpy(dtype=float, copy=False) + + if kind == "zi_positive": + clf = bundle["classifier"] + draw = self._bernoulli_gate_draw(clf, X_pred) + values = np.zeros(n, dtype=float) + positive_mask = draw == 1 + if positive_mask.any(): + sub_preds = self._invoke_base( + bundle["positive_base"], + X_test.loc[positive_mask], + quantile=quantile, + **kwargs, + ) + values[positive_mask] = sub_preds[variable].to_numpy(dtype=float) + return values + + if kind == "zi_negative": + clf = bundle["classifier"] + draw = self._bernoulli_gate_draw(clf, X_pred) + values = np.zeros(n, dtype=float) + negative_mask = draw == 1 + if negative_mask.any(): + sub_preds = self._invoke_base( + bundle["negative_base"], + X_test.loc[negative_mask], + quantile=quantile, + **kwargs, + ) + values[negative_mask] = sub_preds[variable].to_numpy(dtype=float) + return values + + if kind == "sign_only": + clf = bundle["classifier"] + draw = self._bernoulli_gate_draw(clf, X_pred) + positive_mask = draw == 1 + negative_mask = ~positive_mask + values = np.zeros(n, dtype=float) + if positive_mask.any(): + sub_preds = self._invoke_base( + bundle["positive_base"], + X_test.loc[positive_mask], + quantile=quantile, + **kwargs, + ) + values[positive_mask] = sub_preds[variable].to_numpy(dtype=float) + if negative_mask.any(): + sub_preds = self._invoke_base( + bundle["negative_base"], + X_test.loc[negative_mask], + quantile=quantile, + **kwargs, + ) + values[negative_mask] = sub_preds[variable].to_numpy(dtype=float) + return values + + if kind == "three_sign": + clf = bundle["classifier"] + probas = clf.predict_proba(X_pred) + # Classes are [0=neg, 1=zero, 2=pos] per the fit encoding. + cumulative = np.cumsum(probas, axis=1) + u = self._rng.random(n) + # Each row i is assigned to class argmax over k of (cumulative[i,k] >= u[i]). + class_indices = (cumulative >= u[:, None]).argmax(axis=1) + classes = clf.classes_[class_indices] + values = np.zeros(n, dtype=float) + positive_mask = classes == 2 + negative_mask = classes == 0 + if positive_mask.any(): + sub_preds = self._invoke_base( + bundle["positive_base"], + X_test.loc[positive_mask], + quantile=quantile, + **kwargs, + ) + values[positive_mask] = sub_preds[variable].to_numpy(dtype=float) + if negative_mask.any(): + sub_preds = self._invoke_base( + bundle["negative_base"], + X_test.loc[negative_mask], + quantile=quantile, + **kwargs, + ) + values[negative_mask] = sub_preds[variable].to_numpy(dtype=float) + return values + + raise ValueError(f"Unhandled bundle kind {kind!r}") + + def _invoke_base( + self, + base_result: ImputerResults, + X_slice: pd.DataFrame, + quantile: Optional[float], + **kwargs: Any, + ) -> pd.DataFrame: + """Call a base ImputerResults, returning a DataFrame.""" + if quantile is None: + result = base_result.predict(X_slice, **kwargs) + if isinstance(result, dict): + # Some base imputers always return a dict even without + # ``quantiles``; pick the first. + result = next(iter(result.values())) + return result + result = base_result.predict(X_slice, quantiles=[quantile], **kwargs) + if isinstance(result, dict): + return result[quantile] + return result + + def _bernoulli_gate_draw( + self, + classifier: Any, + X_pred: np.ndarray, + ) -> np.ndarray: + """Stochastic draw from the binary classifier's predicted proba. + + Returns an array of 0/1 integers (length ``len(X_pred)``), + matching classifier.classes_ encoding for class-1. + """ + probas = classifier.predict_proba(X_pred) + # Ensure we pull the probability for the "positive-class" index + # (which is whichever class the classifier labeled 1 at fit time). + classes = np.asarray(classifier.classes_) + if 1 in classes: + positive_idx = int(np.where(classes == 1)[0][0]) + else: + positive_idx = probas.shape[1] - 1 + positive_prob = probas[:, positive_idx] + u = self._rng.random(len(X_pred)) + return (u < positive_prob).astype(int) + + def _predict(self, *args: Any, **kwargs: Any) -> Any: + """Abstract-method placeholder. + + ``ImputerResults._predict`` is abstract; this class overrides + ``predict`` directly and never dispatches through ``_predict``, + but the abstract method still must be satisfied. + """ + raise NotImplementedError( + "ZeroInflatedImputerResults overrides `predict` directly; " + "`_predict` is not used." + ) + + +__all__ = [ + "REGIME_DEGENERATE_ZERO", + "REGIME_NEGATIVE_ONLY", + "REGIME_POSITIVE_ONLY", + "REGIME_SIGN_ONLY", + "REGIME_THREE_SIGN", + "REGIME_ZI_NEGATIVE", + "REGIME_ZI_POSITIVE", + "ZeroInflatedImputer", + "ZeroInflatedImputerResults", +] diff --git a/tests/test_models/test_zero_inflated.py b/tests/test_models/test_zero_inflated.py new file mode 100644 index 0000000..3a43a29 --- /dev/null +++ b/tests/test_models/test_zero_inflated.py @@ -0,0 +1,309 @@ +"""Regime-aware zero-inflation wrapper around base imputers. + +Tabular microdata variables fall into seven regimes based on which of +{negative, zero, positive} values appear in the training data: + +- (+, 0, −): three-sign. Gate classifies neg/0/pos; separate base imputers + on the positive and negative subsets. +- (+, 0): standard zero-inflated positive. Binary gate (0 vs nonzero); + base imputer on positives only. +- (−, 0): mirror ZI. Binary gate; base imputer on negatives only. +- (+, −): sign-only (no zero). Binary sign gate; two base imputers. +- (+): positive-only. Single base imputer; no gate. +- (−): negative-only. Single base imputer; no gate. +- (0): degenerate. Constant-zero imputer; no gate. + +The wrapper detects the regime automatically at fit time from the +training distribution (no per-variable hand configuration) and composes +the base imputer accordingly. This replaces ad-hoc per-variable zero +handling in downstream packages (policyengine-us-data, microplex-us). + +Tests here pin the public contract. They deliberately use tiny fixtures +so they run in under a second each — the correctness axes are: + +1. Regime detection from training data is correct. +2. Predictions respect the detected regime (no zero leaks, no + sign-interpolation between positive and negative regimes). +3. Fit/predict lifecycle matches the base `Imputer` contract. +4. Rare-class thresholds: tiny negative tails don't trigger a full + three-sign split unless above a configurable minimum. +""" + +from __future__ import annotations + +from typing import Dict, List + +import numpy as np +import pandas as pd +import pytest + +from microimpute.models.qrf import QRF + + +def _deterministic_frame( + n: int, + y_values: np.ndarray, + seed: int = 0, +) -> pd.DataFrame: + """Build a training DataFrame with two predictors and one target. + + Predictors are (age, income_bin) so both are in-range for random + integers / floats; the target is whatever distribution the caller + constructs via ``y_values``. + """ + rng = np.random.default_rng(seed) + return pd.DataFrame( + { + "age": rng.integers(18, 80, size=n).astype(float), + "income_bin": rng.integers(0, 4, size=n).astype(float), + "y": y_values, + } + ) + + +class TestRegimeDetection: + """Regime auto-detection from the training distribution.""" + + def test_regime_detection_is_importable(self) -> None: + from microimpute.models.zero_inflated import ZeroInflatedImputer + + assert ZeroInflatedImputer is not None + + def test_positive_plus_zero_is_zi_positive(self) -> None: + """97% zeros + 3% positives → ZI_POSITIVE regime.""" + from microimpute.models.zero_inflated import ZeroInflatedImputer + + rng = np.random.default_rng(0) + y = np.where(rng.random(500) > 0.97, rng.exponential(100, 500), 0.0) + data = _deterministic_frame(500, y) + + imputer = ZeroInflatedImputer(base_imputer_class=QRF) + imputer.fit(data, predictors=["age", "income_bin"], imputed_variables=["y"]) + assert imputer.get_regime("y") == "ZI_POSITIVE" + + def test_negative_plus_zero_is_zi_negative(self) -> None: + """97% zeros + 3% negatives → ZI_NEGATIVE regime (mirror).""" + from microimpute.models.zero_inflated import ZeroInflatedImputer + + rng = np.random.default_rng(0) + y = np.where(rng.random(500) > 0.97, -rng.exponential(100, 500), 0.0) + data = _deterministic_frame(500, y) + + imputer = ZeroInflatedImputer(base_imputer_class=QRF) + imputer.fit(data, predictors=["age", "income_bin"], imputed_variables=["y"]) + assert imputer.get_regime("y") == "ZI_NEGATIVE" + + def test_three_sign_mass_is_three_sign(self) -> None: + """Non-trivial mass in each of {neg, 0, pos} → THREE_SIGN regime. + + Fixture models a capital-gains-like distribution: 70% zero, + 15% positive, 15% negative, with distinct pos/neg means. + """ + from microimpute.models.zero_inflated import ZeroInflatedImputer + + rng = np.random.default_rng(0) + n = 1000 + u = rng.random(n) + y = np.zeros(n) + pos_mask = u > 0.85 + neg_mask = (u > 0.70) & (u <= 0.85) + y[pos_mask] = rng.exponential(500, size=pos_mask.sum()) + y[neg_mask] = -rng.exponential(300, size=neg_mask.sum()) + data = _deterministic_frame(n, y) + + imputer = ZeroInflatedImputer(base_imputer_class=QRF) + imputer.fit(data, predictors=["age", "income_bin"], imputed_variables=["y"]) + assert imputer.get_regime("y") == "THREE_SIGN" + + def test_positive_only_is_positive_only(self) -> None: + """All positive, no zeros → POSITIVE_ONLY (no gate, raw base imputer).""" + from microimpute.models.zero_inflated import ZeroInflatedImputer + + rng = np.random.default_rng(0) + y = rng.exponential(100, size=500) # strictly positive + data = _deterministic_frame(500, y) + + imputer = ZeroInflatedImputer(base_imputer_class=QRF) + imputer.fit(data, predictors=["age", "income_bin"], imputed_variables=["y"]) + assert imputer.get_regime("y") == "POSITIVE_ONLY" + + def test_constant_zero_is_degenerate(self) -> None: + """All zeros → DEGENERATE_ZERO, predictions are exactly 0.""" + from microimpute.models.zero_inflated import ZeroInflatedImputer + + data = _deterministic_frame(500, np.zeros(500)) + imputer = ZeroInflatedImputer(base_imputer_class=QRF) + imputer.fit(data, predictors=["age", "income_bin"], imputed_variables=["y"]) + assert imputer.get_regime("y") == "DEGENERATE_ZERO" + + def test_rare_negative_tail_stays_zi_positive(self) -> None: + """If negative-class count is below min_class_count, treat as ZI_POSITIVE. + + Capital gains example: 97% zero, 2.9% positive, 0.1% negative. + The negative mass is real but below the 10-sample threshold on + a 500-record fixture. Should NOT trigger three-sign; instead + collapses to ZI_POSITIVE with the few negatives discarded from + the base imputer's fit (and a warning). + """ + from microimpute.models.zero_inflated import ZeroInflatedImputer + + rng = np.random.default_rng(0) + n = 500 + u = rng.random(n) + y = np.zeros(n) + pos_mask = u > 0.971 + neg_mask = (u > 0.970) & (u <= 0.971) + y[pos_mask] = rng.exponential(100, size=pos_mask.sum()) + y[neg_mask] = -rng.exponential(50, size=neg_mask.sum()) + assert (y < 0).sum() < 10, "fixture precondition" + + data = _deterministic_frame(n, y) + imputer = ZeroInflatedImputer(base_imputer_class=QRF, min_class_count=10) + imputer.fit(data, predictors=["age", "income_bin"], imputed_variables=["y"]) + assert imputer.get_regime("y") == "ZI_POSITIVE" + + +class TestPredictionsRespectRegime: + """Predicted values must lie in the regime's support.""" + + def _fit_predict(self, y: np.ndarray, n_pred: int = 200) -> pd.Series: + from microimpute.models.zero_inflated import ZeroInflatedImputer + + data = _deterministic_frame(len(y), y, seed=1) + imputer = ZeroInflatedImputer(base_imputer_class=QRF) + result = imputer.fit( + data, predictors=["age", "income_bin"], imputed_variables=["y"] + ) + + rng = np.random.default_rng(42) + predict_data = pd.DataFrame( + { + "age": rng.integers(18, 80, size=n_pred).astype(float), + "income_bin": rng.integers(0, 4, size=n_pred).astype(float), + } + ) + predictions = result.predict(predict_data) + return predictions["y"] + + def test_zi_positive_predictions_are_zero_or_positive(self) -> None: + rng = np.random.default_rng(0) + y = np.where(rng.random(800) > 0.95, rng.exponential(200, 800), 0.0) + preds = self._fit_predict(y, n_pred=500) + assert (preds >= 0).all(), ( + f"ZI_POSITIVE predictions include {(preds < 0).sum()} negative " + f"values; min={preds.min()}." + ) + + def test_zi_negative_predictions_are_zero_or_negative(self) -> None: + rng = np.random.default_rng(0) + y = np.where(rng.random(800) > 0.95, -rng.exponential(200, 800), 0.0) + preds = self._fit_predict(y, n_pred=500) + assert (preds <= 0).all(), ( + f"ZI_NEGATIVE predictions include {(preds > 0).sum()} positive " + f"values; max={preds.max()}." + ) + + def test_positive_only_predictions_are_positive(self) -> None: + rng = np.random.default_rng(0) + y = rng.exponential(100, size=800) # strictly positive + preds = self._fit_predict(y, n_pred=500) + # Allow small fp nonneg; predictions must not be meaningfully negative. + assert (preds >= -1e-6).all(), preds.min() + + def test_constant_zero_predictions_are_all_zero(self) -> None: + preds = self._fit_predict(np.zeros(500), n_pred=300) + np.testing.assert_array_equal(preds, 0.0) + + def test_three_sign_predictions_do_not_leak_across_zero(self) -> None: + """A record whose gate says POS must draw from the positive base + imputer only — never from the negative one. Easiest observable + consequence: we can recover predictions that are strictly above + `min(train_positives)` on records the gate calls positive, and + strictly below `max(train_negatives)` on records the gate calls + negative. No record should land in the interpolated band between + max(neg) and min(pos).""" + from microimpute.models.zero_inflated import ZeroInflatedImputer + + rng = np.random.default_rng(0) + n = 2000 + u = rng.random(n) + y = np.zeros(n) + # Clear gap between regimes: positives in [100, ∞), negatives + # in (-∞, -100]. Zeros exactly 0. + pos_mask = u > 0.85 + neg_mask = (u > 0.70) & (u <= 0.85) + y[pos_mask] = 100 + rng.exponential(500, size=pos_mask.sum()) + y[neg_mask] = -(100 + rng.exponential(300, size=neg_mask.sum())) + data = _deterministic_frame(n, y, seed=2) + + imputer = ZeroInflatedImputer(base_imputer_class=QRF) + result = imputer.fit( + data, predictors=["age", "income_bin"], imputed_variables=["y"] + ) + + predict_rng = np.random.default_rng(42) + predict_data = pd.DataFrame( + { + "age": predict_rng.integers(18, 80, size=2000).astype(float), + "income_bin": predict_rng.integers(0, 4, size=2000).astype(float), + } + ) + preds = result.predict(predict_data)["y"] + + # The interpolation-free guarantee: no prediction lies strictly + # between -100 and 100 except exactly zero. (If the gate routed a + # record to the positive base, the QRF draws from training + # positives which are all >= 100; similarly for negative.) + interior = preds[(preds > -100) & (preds < 100)] + nonzero_interior = interior[np.abs(interior) > 1e-6] + assert len(nonzero_interior) == 0, ( + f"Three-sign regime leaked {len(nonzero_interior)} draws into " + f"the (-100, 100) interior: samples={nonzero_interior.head(10).tolist()}" + ) + + +class TestBaseImputerParity: + """Single-regime behavior must match the base Imputer (no gate overhead).""" + + def test_positive_only_matches_bare_qrf(self) -> None: + """For a strictly-positive target, ZeroInflatedImputer should + produce equivalent distributions to QRF directly (since regime + detection returns POSITIVE_ONLY and no gate is applied).""" + from microimpute.models.zero_inflated import ZeroInflatedImputer + + rng = np.random.default_rng(0) + y = rng.exponential(100, size=500) # strictly positive + data = _deterministic_frame(500, y, seed=3) + + bare = QRF() + bare_result = bare.fit( + data, predictors=["age", "income_bin"], imputed_variables=["y"] + ) + + wrapped = ZeroInflatedImputer(base_imputer_class=QRF) + wrapped_result = wrapped.fit( + data, predictors=["age", "income_bin"], imputed_variables=["y"] + ) + + predict_rng = np.random.default_rng(99) + predict_data = pd.DataFrame( + { + "age": predict_rng.integers(18, 80, size=300).astype(float), + "income_bin": predict_rng.integers(0, 4, size=300).astype(float), + } + ) + + bare_preds = bare_result.predict(predict_data)["y"] + wrapped_preds = wrapped_result.predict(predict_data)["y"] + + # Both should produce strictly positive draws with similar + # distributional statistics on this small fixture. + assert (bare_preds >= 0).all() + assert (wrapped_preds >= 0).all() + # The wrapper adds a little randomness via its own rng, so we + # don't require element-wise equality — just broadly similar + # means. + assert ( + abs(bare_preds.mean() - wrapped_preds.mean()) / max(bare_preds.mean(), 1.0) + < 0.25 + ) diff --git a/uv.lock b/uv.lock index 6eb8066..3ca64e5 100644 --- a/uv.lock +++ b/uv.lock @@ -1,6 +1,6 @@ version = 1 revision = 3 -requires-python = ">=3.12, <3.14" +requires-python = ">=3.12, <3.15" [[package]] name = "accessible-pygments" @@ -72,6 +72,40 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/e8/35/4a3daeb8b9fab49240d21c04d50732313295e4bd813a465d840236dd0ce1/aiohttp-3.13.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8057c98e0c8472d8846b9c79f56766bcc57e3e8ac7bfd510482332366c56c591", size = 1721120, upload-time = "2026-01-03T17:31:12.575Z" }, { url = "https://files.pythonhosted.org/packages/bc/9f/d643bb3c5fb99547323e635e251c609fbbc660d983144cfebec529e09264/aiohttp-3.13.3-cp313-cp313-win32.whl", hash = "sha256:1449ceddcdbcf2e0446957863af03ebaaa03f94c090f945411b61269e2cb5daf", size = 427383, upload-time = "2026-01-03T17:31:14.382Z" }, { url = "https://files.pythonhosted.org/packages/4e/f1/ab0395f8a79933577cdd996dd2f9aa6014af9535f65dddcf88204682fe62/aiohttp-3.13.3-cp313-cp313-win_amd64.whl", hash = "sha256:693781c45a4033d31d4187d2436f5ac701e7bbfe5df40d917736108c1cc7436e", size = 453899, upload-time = "2026-01-03T17:31:15.958Z" }, + { url = "https://files.pythonhosted.org/packages/99/36/5b6514a9f5d66f4e2597e40dea2e3db271e023eb7a5d22defe96ba560996/aiohttp-3.13.3-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:ea37047c6b367fd4bd632bff8077449b8fa034b69e812a18e0132a00fae6e808", size = 737238, upload-time = "2026-01-03T17:31:17.909Z" }, + { url = "https://files.pythonhosted.org/packages/f7/49/459327f0d5bcd8c6c9ca69e60fdeebc3622861e696490d8674a6d0cb90a6/aiohttp-3.13.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:6fc0e2337d1a4c3e6acafda6a78a39d4c14caea625124817420abceed36e2415", size = 492292, upload-time = "2026-01-03T17:31:19.919Z" }, + { url = "https://files.pythonhosted.org/packages/e8/0b/b97660c5fd05d3495b4eb27f2d0ef18dc1dc4eff7511a9bf371397ff0264/aiohttp-3.13.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c685f2d80bb67ca8c3837823ad76196b3694b0159d232206d1e461d3d434666f", size = 493021, upload-time = "2026-01-03T17:31:21.636Z" }, + { url = "https://files.pythonhosted.org/packages/54/d4/438efabdf74e30aeceb890c3290bbaa449780583b1270b00661126b8aae4/aiohttp-3.13.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:48e377758516d262bde50c2584fc6c578af272559c409eecbdd2bae1601184d6", size = 1717263, upload-time = "2026-01-03T17:31:23.296Z" }, + { url = "https://files.pythonhosted.org/packages/71/f2/7bddc7fd612367d1459c5bcf598a9e8f7092d6580d98de0e057eb42697ad/aiohttp-3.13.3-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:34749271508078b261c4abb1767d42b8d0c0cc9449c73a4df494777dc55f0687", size = 1669107, upload-time = "2026-01-03T17:31:25.334Z" }, + { url = "https://files.pythonhosted.org/packages/00/5a/1aeaecca40e22560f97610a329e0e5efef5e0b5afdf9f857f0d93839ab2e/aiohttp-3.13.3-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:82611aeec80eb144416956ec85b6ca45a64d76429c1ed46ae1b5f86c6e0c9a26", size = 1760196, upload-time = "2026-01-03T17:31:27.394Z" }, + { url = "https://files.pythonhosted.org/packages/f8/f8/0ff6992bea7bd560fc510ea1c815f87eedd745fe035589c71ce05612a19a/aiohttp-3.13.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2fff83cfc93f18f215896e3a190e8e5cb413ce01553901aca925176e7568963a", size = 1843591, upload-time = "2026-01-03T17:31:29.238Z" }, + { url = "https://files.pythonhosted.org/packages/e3/d1/e30e537a15f53485b61f5be525f2157da719819e8377298502aebac45536/aiohttp-3.13.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bbe7d4cecacb439e2e2a8a1a7b935c25b812af7a5fd26503a66dadf428e79ec1", size = 1720277, upload-time = "2026-01-03T17:31:31.053Z" }, + { url = "https://files.pythonhosted.org/packages/84/45/23f4c451d8192f553d38d838831ebbc156907ea6e05557f39563101b7717/aiohttp-3.13.3-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b928f30fe49574253644b1ca44b1b8adbd903aa0da4b9054a6c20fc7f4092a25", size = 1548575, upload-time = "2026-01-03T17:31:32.87Z" }, + { url = "https://files.pythonhosted.org/packages/6a/ed/0a42b127a43712eda7807e7892c083eadfaf8429ca8fb619662a530a3aab/aiohttp-3.13.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7b5e8fe4de30df199155baaf64f2fcd604f4c678ed20910db8e2c66dc4b11603", size = 1679455, upload-time = "2026-01-03T17:31:34.76Z" }, + { url = "https://files.pythonhosted.org/packages/2e/b5/c05f0c2b4b4fe2c9d55e73b6d3ed4fd6c9dc2684b1d81cbdf77e7fad9adb/aiohttp-3.13.3-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:8542f41a62bcc58fc7f11cf7c90e0ec324ce44950003feb70640fc2a9092c32a", size = 1687417, upload-time = "2026-01-03T17:31:36.699Z" }, + { url = "https://files.pythonhosted.org/packages/c9/6b/915bc5dad66aef602b9e459b5a973529304d4e89ca86999d9d75d80cbd0b/aiohttp-3.13.3-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:5e1d8c8b8f1d91cd08d8f4a3c2b067bfca6ec043d3ff36de0f3a715feeedf926", size = 1729968, upload-time = "2026-01-03T17:31:38.622Z" }, + { url = "https://files.pythonhosted.org/packages/11/3b/e84581290a9520024a08640b63d07673057aec5ca548177a82026187ba73/aiohttp-3.13.3-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:90455115e5da1c3c51ab619ac57f877da8fd6d73c05aacd125c5ae9819582aba", size = 1545690, upload-time = "2026-01-03T17:31:40.57Z" }, + { url = "https://files.pythonhosted.org/packages/f5/04/0c3655a566c43fd647c81b895dfe361b9f9ad6d58c19309d45cff52d6c3b/aiohttp-3.13.3-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:042e9e0bcb5fba81886c8b4fbb9a09d6b8a00245fd8d88e4d989c1f96c74164c", size = 1746390, upload-time = "2026-01-03T17:31:42.857Z" }, + { url = "https://files.pythonhosted.org/packages/1f/53/71165b26978f719c3419381514c9690bd5980e764a09440a10bb816ea4ab/aiohttp-3.13.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2eb752b102b12a76ca02dff751a801f028b4ffbbc478840b473597fc91a9ed43", size = 1702188, upload-time = "2026-01-03T17:31:44.984Z" }, + { url = "https://files.pythonhosted.org/packages/29/a7/cbe6c9e8e136314fa1980da388a59d2f35f35395948a08b6747baebb6aa6/aiohttp-3.13.3-cp314-cp314-win32.whl", hash = "sha256:b556c85915d8efaed322bf1bdae9486aa0f3f764195a0fb6ee962e5c71ef5ce1", size = 433126, upload-time = "2026-01-03T17:31:47.463Z" }, + { url = "https://files.pythonhosted.org/packages/de/56/982704adea7d3b16614fc5936014e9af85c0e34b58f9046655817f04306e/aiohttp-3.13.3-cp314-cp314-win_amd64.whl", hash = "sha256:9bf9f7a65e7aa20dd764151fb3d616c81088f91f8df39c3893a536e279b4b984", size = 459128, upload-time = "2026-01-03T17:31:49.2Z" }, + { url = "https://files.pythonhosted.org/packages/6c/2a/3c79b638a9c3d4658d345339d22070241ea341ed4e07b5ac60fb0f418003/aiohttp-3.13.3-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:05861afbbec40650d8a07ea324367cb93e9e8cc7762e04dd4405df99fa65159c", size = 769512, upload-time = "2026-01-03T17:31:51.134Z" }, + { url = "https://files.pythonhosted.org/packages/29/b9/3e5014d46c0ab0db8707e0ac2711ed28c4da0218c358a4e7c17bae0d8722/aiohttp-3.13.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:2fc82186fadc4a8316768d61f3722c230e2c1dcab4200d52d2ebdf2482e47592", size = 506444, upload-time = "2026-01-03T17:31:52.85Z" }, + { url = "https://files.pythonhosted.org/packages/90/03/c1d4ef9a054e151cd7839cdc497f2638f00b93cbe8043983986630d7a80c/aiohttp-3.13.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:0add0900ff220d1d5c5ebbf99ed88b0c1bbf87aa7e4262300ed1376a6b13414f", size = 510798, upload-time = "2026-01-03T17:31:54.91Z" }, + { url = "https://files.pythonhosted.org/packages/ea/76/8c1e5abbfe8e127c893fe7ead569148a4d5a799f7cf958d8c09f3eedf097/aiohttp-3.13.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:568f416a4072fbfae453dcf9a99194bbb8bdeab718e08ee13dfa2ba0e4bebf29", size = 1868835, upload-time = "2026-01-03T17:31:56.733Z" }, + { url = "https://files.pythonhosted.org/packages/8e/ac/984c5a6f74c363b01ff97adc96a3976d9c98940b8969a1881575b279ac5d/aiohttp-3.13.3-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:add1da70de90a2569c5e15249ff76a631ccacfe198375eead4aadf3b8dc849dc", size = 1720486, upload-time = "2026-01-03T17:31:58.65Z" }, + { url = "https://files.pythonhosted.org/packages/b2/9a/b7039c5f099c4eb632138728828b33428585031a1e658d693d41d07d89d1/aiohttp-3.13.3-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:10b47b7ba335d2e9b1239fa571131a87e2d8ec96b333e68b2a305e7a98b0bae2", size = 1847951, upload-time = "2026-01-03T17:32:00.989Z" }, + { url = "https://files.pythonhosted.org/packages/3c/02/3bec2b9a1ba3c19ff89a43a19324202b8eb187ca1e928d8bdac9bbdddebd/aiohttp-3.13.3-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3dd4dce1c718e38081c8f35f323209d4c1df7d4db4bab1b5c88a6b4d12b74587", size = 1941001, upload-time = "2026-01-03T17:32:03.122Z" }, + { url = "https://files.pythonhosted.org/packages/37/df/d879401cedeef27ac4717f6426c8c36c3091c6e9f08a9178cc87549c537f/aiohttp-3.13.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:34bac00a67a812570d4a460447e1e9e06fae622946955f939051e7cc895cfab8", size = 1797246, upload-time = "2026-01-03T17:32:05.255Z" }, + { url = "https://files.pythonhosted.org/packages/8d/15/be122de1f67e6953add23335c8ece6d314ab67c8bebb3f181063010795a7/aiohttp-3.13.3-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a19884d2ee70b06d9204b2727a7b9f983d0c684c650254679e716b0b77920632", size = 1627131, upload-time = "2026-01-03T17:32:07.607Z" }, + { url = "https://files.pythonhosted.org/packages/12/12/70eedcac9134cfa3219ab7af31ea56bc877395b1ac30d65b1bc4b27d0438/aiohttp-3.13.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:5f8ca7f2bb6ba8348a3614c7918cc4bb73268c5ac2a207576b7afea19d3d9f64", size = 1795196, upload-time = "2026-01-03T17:32:09.59Z" }, + { url = "https://files.pythonhosted.org/packages/32/11/b30e1b1cd1f3054af86ebe60df96989c6a414dd87e27ad16950eee420bea/aiohttp-3.13.3-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:b0d95340658b9d2f11d9697f59b3814a9d3bb4b7a7c20b131df4bcef464037c0", size = 1782841, upload-time = "2026-01-03T17:32:11.445Z" }, + { url = "https://files.pythonhosted.org/packages/88/0d/d98a9367b38912384a17e287850f5695c528cff0f14f791ce8ee2e4f7796/aiohttp-3.13.3-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:a1e53262fd202e4b40b70c3aff944a8155059beedc8a89bba9dc1f9ef06a1b56", size = 1795193, upload-time = "2026-01-03T17:32:13.705Z" }, + { url = "https://files.pythonhosted.org/packages/43/a5/a2dfd1f5ff5581632c7f6a30e1744deda03808974f94f6534241ef60c751/aiohttp-3.13.3-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:d60ac9663f44168038586cab2157e122e46bdef09e9368b37f2d82d354c23f72", size = 1621979, upload-time = "2026-01-03T17:32:15.965Z" }, + { url = "https://files.pythonhosted.org/packages/fa/f0/12973c382ae7c1cccbc4417e129c5bf54c374dfb85af70893646e1f0e749/aiohttp-3.13.3-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:90751b8eed69435bac9ff4e3d2f6b3af1f57e37ecb0fbeee59c0174c9e2d41df", size = 1822193, upload-time = "2026-01-03T17:32:18.219Z" }, + { url = "https://files.pythonhosted.org/packages/3c/5f/24155e30ba7f8c96918af1350eb0663e2430aad9e001c0489d89cd708ab1/aiohttp-3.13.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:fc353029f176fd2b3ec6cfc71be166aba1936fe5d73dd1992ce289ca6647a9aa", size = 1769801, upload-time = "2026-01-03T17:32:20.25Z" }, + { url = "https://files.pythonhosted.org/packages/eb/f8/7314031ff5c10e6ece114da79b338ec17eeff3a079e53151f7e9f43c4723/aiohttp-3.13.3-cp314-cp314t-win32.whl", hash = "sha256:2e41b18a58da1e474a057b3d35248d8320029f61d70a37629535b16a0c8f3767", size = 466523, upload-time = "2026-01-03T17:32:22.215Z" }, + { url = "https://files.pythonhosted.org/packages/b4/63/278a98c715ae467624eafe375542d8ba9b4383a016df8fdefe0ae28382a7/aiohttp-3.13.3-cp314-cp314t-win_amd64.whl", hash = "sha256:44531a36aa2264a1860089ffd4dce7baf875ee5a6079d5fb42e261c704ef7344", size = 499694, upload-time = "2026-01-03T17:32:24.546Z" }, ] [[package]] @@ -363,6 +397,8 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/a9/c1/dabe88f52c3e3760d861401bb994df08f672ec893b8f7592dc91626adcf3/cuda_bindings-12.9.4-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fda147a344e8eaeca0c6ff113d2851ffca8f7dfc0a6c932374ee5c47caa649c8", size = 12151019, upload-time = "2025-10-21T14:51:43.167Z" }, { url = "https://files.pythonhosted.org/packages/63/56/e465c31dc9111be3441a9ba7df1941fe98f4aa6e71e8788a3fb4534ce24d/cuda_bindings-12.9.4-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:32bdc5a76906be4c61eb98f546a6786c5773a881f3b166486449b5d141e4a39f", size = 11906628, upload-time = "2025-10-21T14:51:49.905Z" }, { url = "https://files.pythonhosted.org/packages/a3/84/1e6be415e37478070aeeee5884c2022713c1ecc735e6d82d744de0252eee/cuda_bindings-12.9.4-cp313-cp313t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:56e0043c457a99ac473ddc926fe0dc4046694d99caef633e92601ab52cbe17eb", size = 11925991, upload-time = "2025-10-21T14:51:56.535Z" }, + { url = "https://files.pythonhosted.org/packages/d1/af/6dfd8f2ed90b1d4719bc053ff8940e494640fe4212dc3dd72f383e4992da/cuda_bindings-12.9.4-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8b72ee72a9cc1b531db31eebaaee5c69a8ec3500e32c6933f2d3b15297b53686", size = 11922703, upload-time = "2025-10-21T14:52:03.585Z" }, + { url = "https://files.pythonhosted.org/packages/6c/19/90ac264acc00f6df8a49378eedec9fd2db3061bf9263bf9f39fd3d8377c3/cuda_bindings-12.9.4-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d80bffc357df9988dca279734bc9674c3934a654cab10cadeed27ce17d8635ee", size = 11924658, upload-time = "2025-10-21T14:52:10.411Z" }, ] [[package]] @@ -444,20 +480,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/f9/0b/de6f54d4a8bedfe8645c41497f3c18d749f0bd3218170c667bf4b81d0cdd/filelock-3.25.0-py3-none-any.whl", hash = "sha256:5ccf8069f7948f494968fc0713c10e5c182a9c9d9eef3a636307a20c2490f047", size = 26427, upload-time = "2026-03-01T15:08:44.593Z" }, ] -[[package]] -name = "flake8" -version = "7.3.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "mccabe" }, - { name = "pycodestyle" }, - { name = "pyflakes" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/9b/af/fbfe3c4b5a657d79e5c47a2827a362f9e1b763336a52f926126aa6dc7123/flake8-7.3.0.tar.gz", hash = "sha256:fe044858146b9fc69b551a4b490d69cf960fcb78ad1edcb84e7fbb1b4a8e3872", size = 48326, upload-time = "2025-06-20T19:31:35.838Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/9f/56/13ab06b4f93ca7cac71078fbe37fcea175d3216f31f85c3168a6bbd0bb9a/flake8-7.3.0-py2.py3-none-any.whl", hash = "sha256:b9696257b9ce8beb888cdbe31cf885c90d31928fe202be0889a7cdafad32f01e", size = 57922, upload-time = "2025-06-20T19:31:34.425Z" }, -] - [[package]] name = "frozenlist" version = "1.8.0" @@ -512,6 +534,38 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/fd/00/04ca1c3a7a124b6de4f8a9a17cc2fcad138b4608e7a3fc5877804b8715d7/frozenlist-1.8.0-cp313-cp313t-win32.whl", hash = "sha256:0f96534f8bfebc1a394209427d0f8a63d343c9779cda6fc25e8e121b5fd8555b", size = 43492, upload-time = "2025-10-06T05:37:04.915Z" }, { url = "https://files.pythonhosted.org/packages/59/5e/c69f733a86a94ab10f68e496dc6b7e8bc078ebb415281d5698313e3af3a1/frozenlist-1.8.0-cp313-cp313t-win_amd64.whl", hash = "sha256:5d63a068f978fc69421fb0e6eb91a9603187527c86b7cd3f534a5b77a592b888", size = 48034, upload-time = "2025-10-06T05:37:06.343Z" }, { url = "https://files.pythonhosted.org/packages/16/6c/be9d79775d8abe79b05fa6d23da99ad6e7763a1d080fbae7290b286093fd/frozenlist-1.8.0-cp313-cp313t-win_arm64.whl", hash = "sha256:bf0a7e10b077bf5fb9380ad3ae8ce20ef919a6ad93b4552896419ac7e1d8e042", size = 41749, upload-time = "2025-10-06T05:37:07.431Z" }, + { url = "https://files.pythonhosted.org/packages/f1/c8/85da824b7e7b9b6e7f7705b2ecaf9591ba6f79c1177f324c2735e41d36a2/frozenlist-1.8.0-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:cee686f1f4cadeb2136007ddedd0aaf928ab95216e7691c63e50a8ec066336d0", size = 86127, upload-time = "2025-10-06T05:37:08.438Z" }, + { url = "https://files.pythonhosted.org/packages/8e/e8/a1185e236ec66c20afd72399522f142c3724c785789255202d27ae992818/frozenlist-1.8.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:119fb2a1bd47307e899c2fac7f28e85b9a543864df47aa7ec9d3c1b4545f096f", size = 49698, upload-time = "2025-10-06T05:37:09.48Z" }, + { url = "https://files.pythonhosted.org/packages/a1/93/72b1736d68f03fda5fdf0f2180fb6caaae3894f1b854d006ac61ecc727ee/frozenlist-1.8.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:4970ece02dbc8c3a92fcc5228e36a3e933a01a999f7094ff7c23fbd2beeaa67c", size = 49749, upload-time = "2025-10-06T05:37:10.569Z" }, + { url = "https://files.pythonhosted.org/packages/a7/b2/fabede9fafd976b991e9f1b9c8c873ed86f202889b864756f240ce6dd855/frozenlist-1.8.0-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:cba69cb73723c3f329622e34bdbf5ce1f80c21c290ff04256cff1cd3c2036ed2", size = 231298, upload-time = "2025-10-06T05:37:11.993Z" }, + { url = "https://files.pythonhosted.org/packages/3a/3b/d9b1e0b0eed36e70477ffb8360c49c85c8ca8ef9700a4e6711f39a6e8b45/frozenlist-1.8.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:778a11b15673f6f1df23d9586f83c4846c471a8af693a22e066508b77d201ec8", size = 232015, upload-time = "2025-10-06T05:37:13.194Z" }, + { url = "https://files.pythonhosted.org/packages/dc/94/be719d2766c1138148564a3960fc2c06eb688da592bdc25adcf856101be7/frozenlist-1.8.0-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:0325024fe97f94c41c08872db482cf8ac4800d80e79222c6b0b7b162d5b13686", size = 225038, upload-time = "2025-10-06T05:37:14.577Z" }, + { url = "https://files.pythonhosted.org/packages/e4/09/6712b6c5465f083f52f50cf74167b92d4ea2f50e46a9eea0523d658454ae/frozenlist-1.8.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:97260ff46b207a82a7567b581ab4190bd4dfa09f4db8a8b49d1a958f6aa4940e", size = 240130, upload-time = "2025-10-06T05:37:15.781Z" }, + { url = "https://files.pythonhosted.org/packages/f8/d4/cd065cdcf21550b54f3ce6a22e143ac9e4836ca42a0de1022da8498eac89/frozenlist-1.8.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:54b2077180eb7f83dd52c40b2750d0a9f175e06a42e3213ce047219de902717a", size = 242845, upload-time = "2025-10-06T05:37:17.037Z" }, + { url = "https://files.pythonhosted.org/packages/62/c3/f57a5c8c70cd1ead3d5d5f776f89d33110b1addae0ab010ad774d9a44fb9/frozenlist-1.8.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:2f05983daecab868a31e1da44462873306d3cbfd76d1f0b5b69c473d21dbb128", size = 229131, upload-time = "2025-10-06T05:37:18.221Z" }, + { url = "https://files.pythonhosted.org/packages/6c/52/232476fe9cb64f0742f3fde2b7d26c1dac18b6d62071c74d4ded55e0ef94/frozenlist-1.8.0-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:33f48f51a446114bc5d251fb2954ab0164d5be02ad3382abcbfe07e2531d650f", size = 240542, upload-time = "2025-10-06T05:37:19.771Z" }, + { url = "https://files.pythonhosted.org/packages/5f/85/07bf3f5d0fb5414aee5f47d33c6f5c77bfe49aac680bfece33d4fdf6a246/frozenlist-1.8.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:154e55ec0655291b5dd1b8731c637ecdb50975a2ae70c606d100750a540082f7", size = 237308, upload-time = "2025-10-06T05:37:20.969Z" }, + { url = "https://files.pythonhosted.org/packages/11/99/ae3a33d5befd41ac0ca2cc7fd3aa707c9c324de2e89db0e0f45db9a64c26/frozenlist-1.8.0-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:4314debad13beb564b708b4a496020e5306c7333fa9a3ab90374169a20ffab30", size = 238210, upload-time = "2025-10-06T05:37:22.252Z" }, + { url = "https://files.pythonhosted.org/packages/b2/60/b1d2da22f4970e7a155f0adde9b1435712ece01b3cd45ba63702aea33938/frozenlist-1.8.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:073f8bf8becba60aa931eb3bc420b217bb7d5b8f4750e6f8b3be7f3da85d38b7", size = 231972, upload-time = "2025-10-06T05:37:23.5Z" }, + { url = "https://files.pythonhosted.org/packages/3f/ab/945b2f32de889993b9c9133216c068b7fcf257d8595a0ac420ac8677cab0/frozenlist-1.8.0-cp314-cp314-win32.whl", hash = "sha256:bac9c42ba2ac65ddc115d930c78d24ab8d4f465fd3fc473cdedfccadb9429806", size = 40536, upload-time = "2025-10-06T05:37:25.581Z" }, + { url = "https://files.pythonhosted.org/packages/59/ad/9caa9b9c836d9ad6f067157a531ac48b7d36499f5036d4141ce78c230b1b/frozenlist-1.8.0-cp314-cp314-win_amd64.whl", hash = "sha256:3e0761f4d1a44f1d1a47996511752cf3dcec5bbdd9cc2b4fe595caf97754b7a0", size = 44330, upload-time = "2025-10-06T05:37:26.928Z" }, + { url = "https://files.pythonhosted.org/packages/82/13/e6950121764f2676f43534c555249f57030150260aee9dcf7d64efda11dd/frozenlist-1.8.0-cp314-cp314-win_arm64.whl", hash = "sha256:d1eaff1d00c7751b7c6662e9c5ba6eb2c17a2306ba5e2a37f24ddf3cc953402b", size = 40627, upload-time = "2025-10-06T05:37:28.075Z" }, + { url = "https://files.pythonhosted.org/packages/c0/c7/43200656ecc4e02d3f8bc248df68256cd9572b3f0017f0a0c4e93440ae23/frozenlist-1.8.0-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:d3bb933317c52d7ea5004a1c442eef86f426886fba134ef8cf4226ea6ee1821d", size = 89238, upload-time = "2025-10-06T05:37:29.373Z" }, + { url = "https://files.pythonhosted.org/packages/d1/29/55c5f0689b9c0fb765055629f472c0de484dcaf0acee2f7707266ae3583c/frozenlist-1.8.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:8009897cdef112072f93a0efdce29cd819e717fd2f649ee3016efd3cd885a7ed", size = 50738, upload-time = "2025-10-06T05:37:30.792Z" }, + { url = "https://files.pythonhosted.org/packages/ba/7d/b7282a445956506fa11da8c2db7d276adcbf2b17d8bb8407a47685263f90/frozenlist-1.8.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:2c5dcbbc55383e5883246d11fd179782a9d07a986c40f49abe89ddf865913930", size = 51739, upload-time = "2025-10-06T05:37:32.127Z" }, + { url = "https://files.pythonhosted.org/packages/62/1c/3d8622e60d0b767a5510d1d3cf21065b9db874696a51ea6d7a43180a259c/frozenlist-1.8.0-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:39ecbc32f1390387d2aa4f5a995e465e9e2f79ba3adcac92d68e3e0afae6657c", size = 284186, upload-time = "2025-10-06T05:37:33.21Z" }, + { url = "https://files.pythonhosted.org/packages/2d/14/aa36d5f85a89679a85a1d44cd7a6657e0b1c75f61e7cad987b203d2daca8/frozenlist-1.8.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:92db2bf818d5cc8d9c1f1fc56b897662e24ea5adb36ad1f1d82875bd64e03c24", size = 292196, upload-time = "2025-10-06T05:37:36.107Z" }, + { url = "https://files.pythonhosted.org/packages/05/23/6bde59eb55abd407d34f77d39a5126fb7b4f109a3f611d3929f14b700c66/frozenlist-1.8.0-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:2dc43a022e555de94c3b68a4ef0b11c4f747d12c024a520c7101709a2144fb37", size = 273830, upload-time = "2025-10-06T05:37:37.663Z" }, + { url = "https://files.pythonhosted.org/packages/d2/3f/22cff331bfad7a8afa616289000ba793347fcd7bc275f3b28ecea2a27909/frozenlist-1.8.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:cb89a7f2de3602cfed448095bab3f178399646ab7c61454315089787df07733a", size = 294289, upload-time = "2025-10-06T05:37:39.261Z" }, + { url = "https://files.pythonhosted.org/packages/a4/89/5b057c799de4838b6c69aa82b79705f2027615e01be996d2486a69ca99c4/frozenlist-1.8.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:33139dc858c580ea50e7e60a1b0ea003efa1fd42e6ec7fdbad78fff65fad2fd2", size = 300318, upload-time = "2025-10-06T05:37:43.213Z" }, + { url = "https://files.pythonhosted.org/packages/30/de/2c22ab3eb2a8af6d69dc799e48455813bab3690c760de58e1bf43b36da3e/frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:168c0969a329b416119507ba30b9ea13688fafffac1b7822802537569a1cb0ef", size = 282814, upload-time = "2025-10-06T05:37:45.337Z" }, + { url = "https://files.pythonhosted.org/packages/59/f7/970141a6a8dbd7f556d94977858cfb36fa9b66e0892c6dd780d2219d8cd8/frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:28bd570e8e189d7f7b001966435f9dac6718324b5be2990ac496cf1ea9ddb7fe", size = 291762, upload-time = "2025-10-06T05:37:46.657Z" }, + { url = "https://files.pythonhosted.org/packages/c1/15/ca1adae83a719f82df9116d66f5bb28bb95557b3951903d39135620ef157/frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:b2a095d45c5d46e5e79ba1e5b9cb787f541a8dee0433836cea4b96a2c439dcd8", size = 289470, upload-time = "2025-10-06T05:37:47.946Z" }, + { url = "https://files.pythonhosted.org/packages/ac/83/dca6dc53bf657d371fbc88ddeb21b79891e747189c5de990b9dfff2ccba1/frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:eab8145831a0d56ec9c4139b6c3e594c7a83c2c8be25d5bcf2d86136a532287a", size = 289042, upload-time = "2025-10-06T05:37:49.499Z" }, + { url = "https://files.pythonhosted.org/packages/96/52/abddd34ca99be142f354398700536c5bd315880ed0a213812bc491cff5e4/frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:974b28cf63cc99dfb2188d8d222bc6843656188164848c4f679e63dae4b0708e", size = 283148, upload-time = "2025-10-06T05:37:50.745Z" }, + { url = "https://files.pythonhosted.org/packages/af/d3/76bd4ed4317e7119c2b7f57c3f6934aba26d277acc6309f873341640e21f/frozenlist-1.8.0-cp314-cp314t-win32.whl", hash = "sha256:342c97bf697ac5480c0a7ec73cd700ecfa5a8a40ac923bd035484616efecc2df", size = 44676, upload-time = "2025-10-06T05:37:52.222Z" }, + { url = "https://files.pythonhosted.org/packages/89/76/c615883b7b521ead2944bb3480398cbb07e12b7b4e4d073d3752eb721558/frozenlist-1.8.0-cp314-cp314t-win_amd64.whl", hash = "sha256:06be8f67f39c8b1dc671f5d83aaefd3358ae5cdcf8314552c57e7ed3e6475bdd", size = 49451, upload-time = "2025-10-06T05:37:53.425Z" }, + { url = "https://files.pythonhosted.org/packages/e0/a3/5982da14e113d07b325230f95060e2169f5311b1017ea8af2a29b374c289/frozenlist-1.8.0-cp314-cp314t-win_arm64.whl", hash = "sha256:102e6314ca4da683dca92e3b1355490fed5f313b768500084fbe6371fddfdb79", size = 42507, upload-time = "2025-10-06T05:37:54.513Z" }, { url = "https://files.pythonhosted.org/packages/9a/9a/e35b4a917281c0b8419d4207f4334c8e8c5dbf4f3f5f9ada73958d937dcc/frozenlist-1.8.0-py3-none-any.whl", hash = "sha256:0c18a16eab41e82c295618a77502e17b195883241c563b00f0aa5106fc4eaa0d", size = 13409, upload-time = "2025-10-06T05:38:16.721Z" }, ] @@ -554,7 +608,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/f3/94/ad0d435f7c48debe960c53b8f60fb41c2026b1d0fa4a99a1cb17c3461e09/greenlet-3.2.3-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:25ad29caed5783d4bd7a85c9251c651696164622494c00802a139c00d639242d", size = 271992, upload-time = "2025-06-05T16:11:23.467Z" }, { url = "https://files.pythonhosted.org/packages/93/5d/7c27cf4d003d6e77749d299c7c8f5fd50b4f251647b5c2e97e1f20da0ab5/greenlet-3.2.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:88cd97bf37fe24a6710ec6a3a7799f3f81d9cd33317dcf565ff9950c83f55e0b", size = 638820, upload-time = "2025-06-05T16:38:52.882Z" }, { url = "https://files.pythonhosted.org/packages/c6/7e/807e1e9be07a125bb4c169144937910bf59b9d2f6d931578e57f0bce0ae2/greenlet-3.2.3-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:baeedccca94880d2f5666b4fa16fc20ef50ba1ee353ee2d7092b383a243b0b0d", size = 653046, upload-time = "2025-06-05T16:41:36.343Z" }, - { url = "https://files.pythonhosted.org/packages/9d/ab/158c1a4ea1068bdbc78dba5a3de57e4c7aeb4e7fa034320ea94c688bfb61/greenlet-3.2.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:be52af4b6292baecfa0f397f3edb3c6092ce071b499dd6fe292c9ac9f2c8f264", size = 647701, upload-time = "2025-06-05T16:48:19.604Z" }, { url = "https://files.pythonhosted.org/packages/cc/0d/93729068259b550d6a0288da4ff72b86ed05626eaf1eb7c0d3466a2571de/greenlet-3.2.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:0cc73378150b8b78b0c9fe2ce56e166695e67478550769536a6742dca3651688", size = 649747, upload-time = "2025-06-05T16:13:04.628Z" }, { url = "https://files.pythonhosted.org/packages/f6/f6/c82ac1851c60851302d8581680573245c8fc300253fc1ff741ae74a6c24d/greenlet-3.2.3-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:706d016a03e78df129f68c4c9b4c4f963f7d73534e48a24f5f5a7101ed13dbbb", size = 605461, upload-time = "2025-06-05T16:12:50.792Z" }, { url = "https://files.pythonhosted.org/packages/98/82/d022cf25ca39cf1200650fc58c52af32c90f80479c25d1cbf57980ec3065/greenlet-3.2.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:419e60f80709510c343c57b4bb5a339d8767bf9aef9b8ce43f4f143240f88b7c", size = 1121190, upload-time = "2025-06-05T16:36:48.59Z" }, @@ -563,12 +616,17 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/b1/cf/f5c0b23309070ae93de75c90d29300751a5aacefc0a3ed1b1d8edb28f08b/greenlet-3.2.3-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:500b8689aa9dd1ab26872a34084503aeddefcb438e2e7317b89b11eaea1901ad", size = 270732, upload-time = "2025-06-05T16:10:08.26Z" }, { url = "https://files.pythonhosted.org/packages/48/ae/91a957ba60482d3fecf9be49bc3948f341d706b52ddb9d83a70d42abd498/greenlet-3.2.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:a07d3472c2a93117af3b0136f246b2833fdc0b542d4a9799ae5f41c28323faef", size = 639033, upload-time = "2025-06-05T16:38:53.983Z" }, { url = "https://files.pythonhosted.org/packages/6f/df/20ffa66dd5a7a7beffa6451bdb7400d66251374ab40b99981478c69a67a8/greenlet-3.2.3-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:8704b3768d2f51150626962f4b9a9e4a17d2e37c8a8d9867bbd9fa4eb938d3b3", size = 652999, upload-time = "2025-06-05T16:41:37.89Z" }, - { url = "https://files.pythonhosted.org/packages/51/b4/ebb2c8cb41e521f1d72bf0465f2f9a2fd803f674a88db228887e6847077e/greenlet-3.2.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:5035d77a27b7c62db6cf41cf786cfe2242644a7a337a0e155c80960598baab95", size = 647368, upload-time = "2025-06-05T16:48:21.467Z" }, { url = "https://files.pythonhosted.org/packages/8e/6a/1e1b5aa10dced4ae876a322155705257748108b7fd2e4fae3f2a091fe81a/greenlet-3.2.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:2d8aa5423cd4a396792f6d4580f88bdc6efcb9205891c9d40d20f6e670992efb", size = 650037, upload-time = "2025-06-05T16:13:06.402Z" }, { url = "https://files.pythonhosted.org/packages/26/f2/ad51331a157c7015c675702e2d5230c243695c788f8f75feba1af32b3617/greenlet-3.2.3-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2c724620a101f8170065d7dded3f962a2aea7a7dae133a009cada42847e04a7b", size = 608402, upload-time = "2025-06-05T16:12:51.91Z" }, { url = "https://files.pythonhosted.org/packages/26/bc/862bd2083e6b3aff23300900a956f4ea9a4059de337f5c8734346b9b34fc/greenlet-3.2.3-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:873abe55f134c48e1f2a6f53f7d1419192a3d1a4e873bace00499a4e45ea6af0", size = 1119577, upload-time = "2025-06-05T16:36:49.787Z" }, { url = "https://files.pythonhosted.org/packages/86/94/1fc0cc068cfde885170e01de40a619b00eaa8f2916bf3541744730ffb4c3/greenlet-3.2.3-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:024571bbce5f2c1cfff08bf3fbaa43bbc7444f580ae13b0099e95d0e6e67ed36", size = 1147121, upload-time = "2025-06-05T16:12:42.527Z" }, { url = "https://files.pythonhosted.org/packages/27/1a/199f9587e8cb08a0658f9c30f3799244307614148ffe8b1e3aa22f324dea/greenlet-3.2.3-cp313-cp313-win_amd64.whl", hash = "sha256:5195fb1e75e592dd04ce79881c8a22becdfa3e6f500e7feb059b1e6fdd54d3e3", size = 297603, upload-time = "2025-06-05T16:20:12.651Z" }, + { url = "https://files.pythonhosted.org/packages/d8/ca/accd7aa5280eb92b70ed9e8f7fd79dc50a2c21d8c73b9a0856f5b564e222/greenlet-3.2.3-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:3d04332dddb10b4a211b68111dabaee2e1a073663d117dc10247b5b1642bac86", size = 271479, upload-time = "2025-06-05T16:10:47.525Z" }, + { url = "https://files.pythonhosted.org/packages/55/71/01ed9895d9eb49223280ecc98a557585edfa56b3d0e965b9fa9f7f06b6d9/greenlet-3.2.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:8186162dffde068a465deab08fc72c767196895c39db26ab1c17c0b77a6d8b97", size = 683952, upload-time = "2025-06-05T16:38:55.125Z" }, + { url = "https://files.pythonhosted.org/packages/ea/61/638c4bdf460c3c678a0a1ef4c200f347dff80719597e53b5edb2fb27ab54/greenlet-3.2.3-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:f4bfbaa6096b1b7a200024784217defedf46a07c2eee1a498e94a1b5f8ec5728", size = 696917, upload-time = "2025-06-05T16:41:38.959Z" }, + { url = "https://files.pythonhosted.org/packages/67/10/b2a4b63d3f08362662e89c103f7fe28894a51ae0bc890fabf37d1d780e52/greenlet-3.2.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:02b0df6f63cd15012bed5401b47829cfd2e97052dc89da3cfaf2c779124eb892", size = 692995, upload-time = "2025-06-05T16:13:07.972Z" }, + { url = "https://files.pythonhosted.org/packages/5a/c6/ad82f148a4e3ce9564056453a71529732baf5448ad53fc323e37efe34f66/greenlet-3.2.3-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:86c2d68e87107c1792e2e8d5399acec2487a4e993ab76c792408e59394d52141", size = 655320, upload-time = "2025-06-05T16:12:53.453Z" }, + { url = "https://files.pythonhosted.org/packages/5c/4f/aab73ecaa6b3086a4c89863d94cf26fa84cbff63f52ce9bc4342b3087a06/greenlet-3.2.3-cp314-cp314-win_amd64.whl", hash = "sha256:8c47aae8fbbfcf82cc13327ae802ba13c9c36753b67e760023fd116bc124a62a", size = 301236, upload-time = "2025-06-05T16:15:20.111Z" }, ] [[package]] @@ -967,15 +1025,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/8f/8e/9ad090d3553c280a8060fbf6e24dc1c0c29704ee7d1c372f0c174aa59285/matplotlib_inline-0.1.7-py3-none-any.whl", hash = "sha256:df192d39a4ff8f21b1895d72e6a13f5fcc5099f00fa84384e0ea28c2cc0653ca", size = 9899, upload-time = "2024-04-15T13:44:43.265Z" }, ] -[[package]] -name = "mccabe" -version = "0.7.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e7/ff/0ffefdcac38932a54d2b5eed4e0ba8a408f215002cd178ad1df0f2806ff8/mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325", size = 9658, upload-time = "2022-01-24T01:14:51.113Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/27/1a/1f68f9ba0c207934b35b86a8ca3aad8395a3d6dd7921c0686e23853ff5a9/mccabe-0.7.0-py2.py3-none-any.whl", hash = "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e", size = 7350, upload-time = "2022-01-24T01:14:49.62Z" }, -] - [[package]] name = "mdit-py-plugins" version = "0.4.2" @@ -999,7 +1048,7 @@ wheels = [ [[package]] name = "microimpute" -version = "1.14.3" +version = "2.0.3" source = { editable = "." } dependencies = [ { name = "joblib" }, @@ -1020,7 +1069,6 @@ dependencies = [ [package.optional-dependencies] dev = [ { name = "build" }, - { name = "flake8" }, { name = "mypy" }, { name = "pytest" }, { name = "pytest-cov" }, @@ -1048,7 +1096,6 @@ mdn = [ [package.metadata] requires-dist = [ { name = "build", marker = "extra == 'dev'", specifier = ">=1.2.0,<2.0.0" }, - { name = "flake8", marker = "extra == 'dev'", specifier = ">=7.0.0,<8.0.0" }, { name = "furo", marker = "extra == 'docs'", specifier = ">=2024.0.0" }, { name = "h5py", marker = "extra == 'docs'", specifier = ">=3.1.0,<4.0.0" }, { name = "ipywidgets", marker = "extra == 'docs'", specifier = ">=8.0.0,<9.0.0" }, @@ -1058,20 +1105,20 @@ requires-dist = [ { name = "mypy", marker = "extra == 'dev'", specifier = ">=1.2.3,<2.0.0" }, { name = "numpy", specifier = ">=2.0.0,<3.0.0" }, { name = "optuna", specifier = ">=4.3.0,<5.0.0" }, - { name = "pandas", specifier = ">=2.2.0,<3.0.0" }, - { name = "plotly", specifier = ">=5.24.0,<6.0.0" }, - { name = "plotly", marker = "extra == 'docs'", specifier = ">=5.24.0,<6.0.0" }, + { name = "pandas", specifier = ">=2.2.0,<4.0.0" }, + { name = "plotly", specifier = ">=5.24.0,<7.0.0" }, + { name = "plotly", marker = "extra == 'docs'", specifier = ">=5.24.0,<7.0.0" }, { name = "psutil" }, { name = "pydantic", specifier = ">=2.8.0,<3.0.0" }, - { name = "pytest", marker = "extra == 'dev'", specifier = ">=8.0.0,<9.0.0" }, - { name = "pytest-cov", marker = "extra == 'dev'", specifier = ">=6.0.0,<7.0.0" }, + { name = "pytest", marker = "extra == 'dev'", specifier = ">=8.0.0,<10.0.0" }, + { name = "pytest-cov", marker = "extra == 'dev'", specifier = ">=6.0.0,<8.0.0" }, { name = "pytorch-tabular", marker = "extra == 'mdn'", specifier = ">=1.1.0" }, { name = "quantile-forest", specifier = ">=1.4.1,<1.5.0" }, { name = "requests", specifier = ">=2.32.0,<3.0.0" }, { name = "rpy2", marker = "extra == 'matching'", specifier = ">=3.5.0,<4.0.0" }, { name = "ruff", marker = "extra == 'dev'", specifier = ">=0.9.0" }, { name = "scikit-learn", specifier = ">=1.7.0,<2.0.0" }, - { name = "scipy", specifier = ">=1.16.0,<1.17.0" }, + { name = "scipy", specifier = ">=1.16.0,<2.0.0" }, { name = "statsmodels", specifier = ">=0.14.5,<0.16.0" }, { name = "torch", marker = "extra == 'mdn'", specifier = ">=2.0.0" }, { name = "towncrier", marker = "extra == 'dev'", specifier = ">=24.8.0" }, @@ -1148,6 +1195,42 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/dc/1d/b31650eab6c5778aceed46ba735bd97f7c7d2f54b319fa916c0f96e7805b/multidict-6.7.1-cp313-cp313t-win32.whl", hash = "sha256:df9f19c28adcb40b6aae30bbaa1478c389efd50c28d541d76760199fc1037c32", size = 47770, upload-time = "2026-01-26T02:45:06.754Z" }, { url = "https://files.pythonhosted.org/packages/ac/5b/2d2d1d522e51285bd61b1e20df8f47ae1a9d80839db0b24ea783b3832832/multidict-6.7.1-cp313-cp313t-win_amd64.whl", hash = "sha256:d54ecf9f301853f2c5e802da559604b3e95bb7a3b01a9c295c6ee591b9882de8", size = 53109, upload-time = "2026-01-26T02:45:08.044Z" }, { url = "https://files.pythonhosted.org/packages/3d/a3/cc409ba012c83ca024a308516703cf339bdc4b696195644a7215a5164a24/multidict-6.7.1-cp313-cp313t-win_arm64.whl", hash = "sha256:5a37ca18e360377cfda1d62f5f382ff41f2b8c4ccb329ed974cc2e1643440118", size = 45573, upload-time = "2026-01-26T02:45:09.349Z" }, + { url = "https://files.pythonhosted.org/packages/91/cc/db74228a8be41884a567e88a62fd589a913708fcf180d029898c17a9a371/multidict-6.7.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:8f333ec9c5eb1b7105e3b84b53141e66ca05a19a605368c55450b6ba208cb9ee", size = 75190, upload-time = "2026-01-26T02:45:10.651Z" }, + { url = "https://files.pythonhosted.org/packages/d5/22/492f2246bb5b534abd44804292e81eeaf835388901f0c574bac4eeec73c5/multidict-6.7.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:a407f13c188f804c759fc6a9f88286a565c242a76b27626594c133b82883b5c2", size = 44486, upload-time = "2026-01-26T02:45:11.938Z" }, + { url = "https://files.pythonhosted.org/packages/f1/4f/733c48f270565d78b4544f2baddc2fb2a245e5a8640254b12c36ac7ac68e/multidict-6.7.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:0e161ddf326db5577c3a4cc2d8648f81456e8a20d40415541587a71620d7a7d1", size = 43219, upload-time = "2026-01-26T02:45:14.346Z" }, + { url = "https://files.pythonhosted.org/packages/24/bb/2c0c2287963f4259c85e8bcbba9182ced8d7fca65c780c38e99e61629d11/multidict-6.7.1-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:1e3a8bb24342a8201d178c3b4984c26ba81a577c80d4d525727427460a50c22d", size = 245132, upload-time = "2026-01-26T02:45:15.712Z" }, + { url = "https://files.pythonhosted.org/packages/a7/f9/44d4b3064c65079d2467888794dea218d1601898ac50222ab8a9a8094460/multidict-6.7.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:97231140a50f5d447d3164f994b86a0bed7cd016e2682f8650d6a9158e14fd31", size = 252420, upload-time = "2026-01-26T02:45:17.293Z" }, + { url = "https://files.pythonhosted.org/packages/8b/13/78f7275e73fa17b24c9a51b0bd9d73ba64bb32d0ed51b02a746eb876abe7/multidict-6.7.1-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:6b10359683bd8806a200fd2909e7c8ca3a7b24ec1d8132e483d58e791d881048", size = 233510, upload-time = "2026-01-26T02:45:19.356Z" }, + { url = "https://files.pythonhosted.org/packages/4b/25/8167187f62ae3cbd52da7893f58cb036b47ea3fb67138787c76800158982/multidict-6.7.1-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:283ddac99f7ac25a4acadbf004cb5ae34480bbeb063520f70ce397b281859362", size = 264094, upload-time = "2026-01-26T02:45:20.834Z" }, + { url = "https://files.pythonhosted.org/packages/a1/e7/69a3a83b7b030cf283fb06ce074a05a02322359783424d7edf0f15fe5022/multidict-6.7.1-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:538cec1e18c067d0e6103aa9a74f9e832904c957adc260e61cd9d8cf0c3b3d37", size = 260786, upload-time = "2026-01-26T02:45:22.818Z" }, + { url = "https://files.pythonhosted.org/packages/fe/3b/8ec5074bcfc450fe84273713b4b0a0dd47c0249358f5d82eb8104ffe2520/multidict-6.7.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7eee46ccb30ff48a1e35bb818cc90846c6be2b68240e42a78599166722cea709", size = 248483, upload-time = "2026-01-26T02:45:24.368Z" }, + { url = "https://files.pythonhosted.org/packages/48/5a/d5a99e3acbca0e29c5d9cba8f92ceb15dce78bab963b308ae692981e3a5d/multidict-6.7.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:fa263a02f4f2dd2d11a7b1bb4362aa7cb1049f84a9235d31adf63f30143469a0", size = 248403, upload-time = "2026-01-26T02:45:25.982Z" }, + { url = "https://files.pythonhosted.org/packages/35/48/e58cd31f6c7d5102f2a4bf89f96b9cf7e00b6c6f3d04ecc44417c00a5a3c/multidict-6.7.1-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:2e1425e2f99ec5bd36c15a01b690a1a2456209c5deed58f95469ffb46039ccbb", size = 240315, upload-time = "2026-01-26T02:45:27.487Z" }, + { url = "https://files.pythonhosted.org/packages/94/33/1cd210229559cb90b6786c30676bb0c58249ff42f942765f88793b41fdce/multidict-6.7.1-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:497394b3239fc6f0e13a78a3e1b61296e72bf1c5f94b4c4eb80b265c37a131cd", size = 245528, upload-time = "2026-01-26T02:45:28.991Z" }, + { url = "https://files.pythonhosted.org/packages/64/f2/6e1107d226278c876c783056b7db43d800bb64c6131cec9c8dfb6903698e/multidict-6.7.1-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:233b398c29d3f1b9676b4b6f75c518a06fcb2ea0b925119fb2c1bc35c05e1601", size = 258784, upload-time = "2026-01-26T02:45:30.503Z" }, + { url = "https://files.pythonhosted.org/packages/4d/c1/11f664f14d525e4a1b5327a82d4de61a1db604ab34c6603bb3c2cc63ad34/multidict-6.7.1-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:93b1818e4a6e0930454f0f2af7dfce69307ca03cdcfb3739bf4d91241967b6c1", size = 251980, upload-time = "2026-01-26T02:45:32.603Z" }, + { url = "https://files.pythonhosted.org/packages/e1/9f/75a9ac888121d0c5bbd4ecf4eead45668b1766f6baabfb3b7f66a410e231/multidict-6.7.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:f33dc2a3abe9249ea5d8360f969ec7f4142e7ac45ee7014d8f8d5acddf178b7b", size = 243602, upload-time = "2026-01-26T02:45:34.043Z" }, + { url = "https://files.pythonhosted.org/packages/9a/e7/50bf7b004cc8525d80dbbbedfdc7aed3e4c323810890be4413e589074032/multidict-6.7.1-cp314-cp314-win32.whl", hash = "sha256:3ab8b9d8b75aef9df299595d5388b14530839f6422333357af1339443cff777d", size = 40930, upload-time = "2026-01-26T02:45:36.278Z" }, + { url = "https://files.pythonhosted.org/packages/e0/bf/52f25716bbe93745595800f36fb17b73711f14da59ed0bb2eba141bc9f0f/multidict-6.7.1-cp314-cp314-win_amd64.whl", hash = "sha256:5e01429a929600e7dab7b166062d9bb54a5eed752384c7384c968c2afab8f50f", size = 45074, upload-time = "2026-01-26T02:45:37.546Z" }, + { url = "https://files.pythonhosted.org/packages/97/ab/22803b03285fa3a525f48217963da3a65ae40f6a1b6f6cf2768879e208f9/multidict-6.7.1-cp314-cp314-win_arm64.whl", hash = "sha256:4885cb0e817aef5d00a2e8451d4665c1808378dc27c2705f1bf4ef8505c0d2e5", size = 42471, upload-time = "2026-01-26T02:45:38.889Z" }, + { url = "https://files.pythonhosted.org/packages/e0/6d/f9293baa6146ba9507e360ea0292b6422b016907c393e2f63fc40ab7b7b5/multidict-6.7.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:0458c978acd8e6ea53c81eefaddbbee9c6c5e591f41b3f5e8e194780fe026581", size = 82401, upload-time = "2026-01-26T02:45:40.254Z" }, + { url = "https://files.pythonhosted.org/packages/7a/68/53b5494738d83558d87c3c71a486504d8373421c3e0dbb6d0db48ad42ee0/multidict-6.7.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:c0abd12629b0af3cf590982c0b413b1e7395cd4ec026f30986818ab95bfaa94a", size = 48143, upload-time = "2026-01-26T02:45:41.635Z" }, + { url = "https://files.pythonhosted.org/packages/37/e8/5284c53310dcdc99ce5d66563f6e5773531a9b9fe9ec7a615e9bc306b05f/multidict-6.7.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:14525a5f61d7d0c94b368a42cff4c9a4e7ba2d52e2672a7b23d84dc86fb02b0c", size = 46507, upload-time = "2026-01-26T02:45:42.99Z" }, + { url = "https://files.pythonhosted.org/packages/e4/fc/6800d0e5b3875568b4083ecf5f310dcf91d86d52573160834fb4bfcf5e4f/multidict-6.7.1-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:17307b22c217b4cf05033dabefe68255a534d637c6c9b0cc8382718f87be4262", size = 239358, upload-time = "2026-01-26T02:45:44.376Z" }, + { url = "https://files.pythonhosted.org/packages/41/75/4ad0973179361cdf3a113905e6e088173198349131be2b390f9fa4da5fc6/multidict-6.7.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7a7e590ff876a3eaf1c02a4dfe0724b6e69a9e9de6d8f556816f29c496046e59", size = 246884, upload-time = "2026-01-26T02:45:47.167Z" }, + { url = "https://files.pythonhosted.org/packages/c3/9c/095bb28b5da139bd41fb9a5d5caff412584f377914bd8787c2aa98717130/multidict-6.7.1-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:5fa6a95dfee63893d80a34758cd0e0c118a30b8dcb46372bf75106c591b77889", size = 225878, upload-time = "2026-01-26T02:45:48.698Z" }, + { url = "https://files.pythonhosted.org/packages/07/d0/c0a72000243756e8f5a277b6b514fa005f2c73d481b7d9e47cd4568aa2e4/multidict-6.7.1-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a0543217a6a017692aa6ae5cc39adb75e587af0f3a82288b1492eb73dd6cc2a4", size = 253542, upload-time = "2026-01-26T02:45:50.164Z" }, + { url = "https://files.pythonhosted.org/packages/c0/6b/f69da15289e384ecf2a68837ec8b5ad8c33e973aa18b266f50fe55f24b8c/multidict-6.7.1-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f99fe611c312b3c1c0ace793f92464d8cd263cc3b26b5721950d977b006b6c4d", size = 252403, upload-time = "2026-01-26T02:45:51.779Z" }, + { url = "https://files.pythonhosted.org/packages/a2/76/b9669547afa5a1a25cd93eaca91c0da1c095b06b6d2d8ec25b713588d3a1/multidict-6.7.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9004d8386d133b7e6135679424c91b0b854d2d164af6ea3f289f8f2761064609", size = 244889, upload-time = "2026-01-26T02:45:53.27Z" }, + { url = "https://files.pythonhosted.org/packages/7e/a9/a50d2669e506dad33cfc45b5d574a205587b7b8a5f426f2fbb2e90882588/multidict-6.7.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e628ef0e6859ffd8273c69412a2465c4be4a9517d07261b33334b5ec6f3c7489", size = 241982, upload-time = "2026-01-26T02:45:54.919Z" }, + { url = "https://files.pythonhosted.org/packages/c5/bb/1609558ad8b456b4827d3c5a5b775c93b87878fd3117ed3db3423dfbce1b/multidict-6.7.1-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:841189848ba629c3552035a6a7f5bf3b02eb304e9fea7492ca220a8eda6b0e5c", size = 232415, upload-time = "2026-01-26T02:45:56.981Z" }, + { url = "https://files.pythonhosted.org/packages/d8/59/6f61039d2aa9261871e03ab9dc058a550d240f25859b05b67fd70f80d4b3/multidict-6.7.1-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:ce1bbd7d780bb5a0da032e095c951f7014d6b0a205f8318308140f1a6aba159e", size = 240337, upload-time = "2026-01-26T02:45:58.698Z" }, + { url = "https://files.pythonhosted.org/packages/a1/29/fdc6a43c203890dc2ae9249971ecd0c41deaedfe00d25cb6564b2edd99eb/multidict-6.7.1-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:b26684587228afed0d50cf804cc71062cc9c1cdf55051c4c6345d372947b268c", size = 248788, upload-time = "2026-01-26T02:46:00.862Z" }, + { url = "https://files.pythonhosted.org/packages/a9/14/a153a06101323e4cf086ecee3faadba52ff71633d471f9685c42e3736163/multidict-6.7.1-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:9f9af11306994335398293f9958071019e3ab95e9a707dc1383a35613f6abcb9", size = 242842, upload-time = "2026-01-26T02:46:02.824Z" }, + { url = "https://files.pythonhosted.org/packages/41/5f/604ae839e64a4a6efc80db94465348d3b328ee955e37acb24badbcd24d83/multidict-6.7.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:b4938326284c4f1224178a560987b6cf8b4d38458b113d9b8c1db1a836e640a2", size = 240237, upload-time = "2026-01-26T02:46:05.898Z" }, + { url = "https://files.pythonhosted.org/packages/5f/60/c3a5187bf66f6fb546ff4ab8fb5a077cbdd832d7b1908d4365c7f74a1917/multidict-6.7.1-cp314-cp314t-win32.whl", hash = "sha256:98655c737850c064a65e006a3df7c997cd3b220be4ec8fe26215760b9697d4d7", size = 48008, upload-time = "2026-01-26T02:46:07.468Z" }, + { url = "https://files.pythonhosted.org/packages/0c/f7/addf1087b860ac60e6f382240f64fb99f8bfb532bb06f7c542b83c29ca61/multidict-6.7.1-cp314-cp314t-win_amd64.whl", hash = "sha256:497bde6223c212ba11d462853cfa4f0ae6ef97465033e7dc9940cdb3ab5b48e5", size = 53542, upload-time = "2026-01-26T02:46:08.809Z" }, + { url = "https://files.pythonhosted.org/packages/4c/81/4629d0aa32302ef7b2ec65c75a728cc5ff4fa410c50096174c1632e70b3e/multidict-6.7.1-cp314-cp314t-win_arm64.whl", hash = "sha256:2bbd113e0d4af5db41d5ebfe9ccaff89de2120578164f86a5d17d5a576d1e5b2", size = 44719, upload-time = "2026-01-26T02:46:11.146Z" }, { url = "https://files.pythonhosted.org/packages/81/08/7036c080d7117f28a4af526d794aab6a84463126db031b007717c1a6676e/multidict-6.7.1-py3-none-any.whl", hash = "sha256:55d97cc6dae627efa6a6e548885712d4864b81110ac76fa4e534c03819fa4a56", size = 12319, upload-time = "2026-01-26T02:46:44.004Z" }, ] @@ -1657,6 +1740,36 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/92/f7/1d4ec5841505f423469efbfc381d64b7b467438cd5a4bbcbb063f3b73d27/propcache-0.4.1-cp313-cp313t-win32.whl", hash = "sha256:2ad890caa1d928c7c2965b48f3a3815c853180831d0e5503d35cf00c472f4717", size = 41396, upload-time = "2025-10-08T19:47:47.202Z" }, { url = "https://files.pythonhosted.org/packages/48/f0/615c30622316496d2cbbc29f5985f7777d3ada70f23370608c1d3e081c1f/propcache-0.4.1-cp313-cp313t-win_amd64.whl", hash = "sha256:f7ee0e597f495cf415bcbd3da3caa3bd7e816b74d0d52b8145954c5e6fd3ff37", size = 44897, upload-time = "2025-10-08T19:47:48.336Z" }, { url = "https://files.pythonhosted.org/packages/fd/ca/6002e46eccbe0e33dcd4069ef32f7f1c9e243736e07adca37ae8c4830ec3/propcache-0.4.1-cp313-cp313t-win_arm64.whl", hash = "sha256:929d7cbe1f01bb7baffb33dc14eb5691c95831450a26354cd210a8155170c93a", size = 39789, upload-time = "2025-10-08T19:47:49.876Z" }, + { url = "https://files.pythonhosted.org/packages/8e/5c/bca52d654a896f831b8256683457ceddd490ec18d9ec50e97dfd8fc726a8/propcache-0.4.1-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:3f7124c9d820ba5548d431afb4632301acf965db49e666aa21c305cbe8c6de12", size = 78152, upload-time = "2025-10-08T19:47:51.051Z" }, + { url = "https://files.pythonhosted.org/packages/65/9b/03b04e7d82a5f54fb16113d839f5ea1ede58a61e90edf515f6577c66fa8f/propcache-0.4.1-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:c0d4b719b7da33599dfe3b22d3db1ef789210a0597bc650b7cee9c77c2be8c5c", size = 44869, upload-time = "2025-10-08T19:47:52.594Z" }, + { url = "https://files.pythonhosted.org/packages/b2/fa/89a8ef0468d5833a23fff277b143d0573897cf75bd56670a6d28126c7d68/propcache-0.4.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:9f302f4783709a78240ebc311b793f123328716a60911d667e0c036bc5dcbded", size = 46596, upload-time = "2025-10-08T19:47:54.073Z" }, + { url = "https://files.pythonhosted.org/packages/86/bd/47816020d337f4a746edc42fe8d53669965138f39ee117414c7d7a340cfe/propcache-0.4.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c80ee5802e3fb9ea37938e7eecc307fb984837091d5fd262bb37238b1ae97641", size = 206981, upload-time = "2025-10-08T19:47:55.715Z" }, + { url = "https://files.pythonhosted.org/packages/df/f6/c5fa1357cc9748510ee55f37173eb31bfde6d94e98ccd9e6f033f2fc06e1/propcache-0.4.1-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ed5a841e8bb29a55fb8159ed526b26adc5bdd7e8bd7bf793ce647cb08656cdf4", size = 211490, upload-time = "2025-10-08T19:47:57.499Z" }, + { url = "https://files.pythonhosted.org/packages/80/1e/e5889652a7c4a3846683401a48f0f2e5083ce0ec1a8a5221d8058fbd1adf/propcache-0.4.1-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:55c72fd6ea2da4c318e74ffdf93c4fe4e926051133657459131a95c846d16d44", size = 215371, upload-time = "2025-10-08T19:47:59.317Z" }, + { url = "https://files.pythonhosted.org/packages/b2/f2/889ad4b2408f72fe1a4f6a19491177b30ea7bf1a0fd5f17050ca08cfc882/propcache-0.4.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8326e144341460402713f91df60ade3c999d601e7eb5ff8f6f7862d54de0610d", size = 201424, upload-time = "2025-10-08T19:48:00.67Z" }, + { url = "https://files.pythonhosted.org/packages/27/73/033d63069b57b0812c8bd19f311faebeceb6ba31b8f32b73432d12a0b826/propcache-0.4.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:060b16ae65bc098da7f6d25bf359f1f31f688384858204fe5d652979e0015e5b", size = 197566, upload-time = "2025-10-08T19:48:02.604Z" }, + { url = "https://files.pythonhosted.org/packages/dc/89/ce24f3dc182630b4e07aa6d15f0ff4b14ed4b9955fae95a0b54c58d66c05/propcache-0.4.1-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:89eb3fa9524f7bec9de6e83cf3faed9d79bffa560672c118a96a171a6f55831e", size = 193130, upload-time = "2025-10-08T19:48:04.499Z" }, + { url = "https://files.pythonhosted.org/packages/a9/24/ef0d5fd1a811fb5c609278d0209c9f10c35f20581fcc16f818da959fc5b4/propcache-0.4.1-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:dee69d7015dc235f526fe80a9c90d65eb0039103fe565776250881731f06349f", size = 202625, upload-time = "2025-10-08T19:48:06.213Z" }, + { url = "https://files.pythonhosted.org/packages/f5/02/98ec20ff5546f68d673df2f7a69e8c0d076b5abd05ca882dc7ee3a83653d/propcache-0.4.1-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:5558992a00dfd54ccbc64a32726a3357ec93825a418a401f5cc67df0ac5d9e49", size = 204209, upload-time = "2025-10-08T19:48:08.432Z" }, + { url = "https://files.pythonhosted.org/packages/a0/87/492694f76759b15f0467a2a93ab68d32859672b646aa8a04ce4864e7932d/propcache-0.4.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:c9b822a577f560fbd9554812526831712c1436d2c046cedee4c3796d3543b144", size = 197797, upload-time = "2025-10-08T19:48:09.968Z" }, + { url = "https://files.pythonhosted.org/packages/ee/36/66367de3575db1d2d3f3d177432bd14ee577a39d3f5d1b3d5df8afe3b6e2/propcache-0.4.1-cp314-cp314-win32.whl", hash = "sha256:ab4c29b49d560fe48b696cdcb127dd36e0bc2472548f3bf56cc5cb3da2b2984f", size = 38140, upload-time = "2025-10-08T19:48:11.232Z" }, + { url = "https://files.pythonhosted.org/packages/0c/2a/a758b47de253636e1b8aef181c0b4f4f204bf0dd964914fb2af90a95b49b/propcache-0.4.1-cp314-cp314-win_amd64.whl", hash = "sha256:5a103c3eb905fcea0ab98be99c3a9a5ab2de60228aa5aceedc614c0281cf6153", size = 41257, upload-time = "2025-10-08T19:48:12.707Z" }, + { url = "https://files.pythonhosted.org/packages/34/5e/63bd5896c3fec12edcbd6f12508d4890d23c265df28c74b175e1ef9f4f3b/propcache-0.4.1-cp314-cp314-win_arm64.whl", hash = "sha256:74c1fb26515153e482e00177a1ad654721bf9207da8a494a0c05e797ad27b992", size = 38097, upload-time = "2025-10-08T19:48:13.923Z" }, + { url = "https://files.pythonhosted.org/packages/99/85/9ff785d787ccf9bbb3f3106f79884a130951436f58392000231b4c737c80/propcache-0.4.1-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:824e908bce90fb2743bd6b59db36eb4f45cd350a39637c9f73b1c1ea66f5b75f", size = 81455, upload-time = "2025-10-08T19:48:15.16Z" }, + { url = "https://files.pythonhosted.org/packages/90/85/2431c10c8e7ddb1445c1f7c4b54d886e8ad20e3c6307e7218f05922cad67/propcache-0.4.1-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:c2b5e7db5328427c57c8e8831abda175421b709672f6cfc3d630c3b7e2146393", size = 46372, upload-time = "2025-10-08T19:48:16.424Z" }, + { url = "https://files.pythonhosted.org/packages/01/20/b0972d902472da9bcb683fa595099911f4d2e86e5683bcc45de60dd05dc3/propcache-0.4.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:6f6ff873ed40292cd4969ef5310179afd5db59fdf055897e282485043fc80ad0", size = 48411, upload-time = "2025-10-08T19:48:17.577Z" }, + { url = "https://files.pythonhosted.org/packages/e2/e3/7dc89f4f21e8f99bad3d5ddb3a3389afcf9da4ac69e3deb2dcdc96e74169/propcache-0.4.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:49a2dc67c154db2c1463013594c458881a069fcf98940e61a0569016a583020a", size = 275712, upload-time = "2025-10-08T19:48:18.901Z" }, + { url = "https://files.pythonhosted.org/packages/20/67/89800c8352489b21a8047c773067644e3897f02ecbbd610f4d46b7f08612/propcache-0.4.1-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:005f08e6a0529984491e37d8dbc3dd86f84bd78a8ceb5fa9a021f4c48d4984be", size = 273557, upload-time = "2025-10-08T19:48:20.762Z" }, + { url = "https://files.pythonhosted.org/packages/e2/a1/b52b055c766a54ce6d9c16d9aca0cad8059acd9637cdf8aa0222f4a026ef/propcache-0.4.1-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5c3310452e0d31390da9035c348633b43d7e7feb2e37be252be6da45abd1abcc", size = 280015, upload-time = "2025-10-08T19:48:22.592Z" }, + { url = "https://files.pythonhosted.org/packages/48/c8/33cee30bd890672c63743049f3c9e4be087e6780906bfc3ec58528be59c1/propcache-0.4.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4c3c70630930447f9ef1caac7728c8ad1c56bc5015338b20fed0d08ea2480b3a", size = 262880, upload-time = "2025-10-08T19:48:23.947Z" }, + { url = "https://files.pythonhosted.org/packages/0c/b1/8f08a143b204b418285c88b83d00edbd61afbc2c6415ffafc8905da7038b/propcache-0.4.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8e57061305815dfc910a3634dcf584f08168a8836e6999983569f51a8544cd89", size = 260938, upload-time = "2025-10-08T19:48:25.656Z" }, + { url = "https://files.pythonhosted.org/packages/cf/12/96e4664c82ca2f31e1c8dff86afb867348979eb78d3cb8546a680287a1e9/propcache-0.4.1-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:521a463429ef54143092c11a77e04056dd00636f72e8c45b70aaa3140d639726", size = 247641, upload-time = "2025-10-08T19:48:27.207Z" }, + { url = "https://files.pythonhosted.org/packages/18/ed/e7a9cfca28133386ba52278136d42209d3125db08d0a6395f0cba0c0285c/propcache-0.4.1-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:120c964da3fdc75e3731aa392527136d4ad35868cc556fd09bb6d09172d9a367", size = 262510, upload-time = "2025-10-08T19:48:28.65Z" }, + { url = "https://files.pythonhosted.org/packages/f5/76/16d8bf65e8845dd62b4e2b57444ab81f07f40caa5652b8969b87ddcf2ef6/propcache-0.4.1-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:d8f353eb14ee3441ee844ade4277d560cdd68288838673273b978e3d6d2c8f36", size = 263161, upload-time = "2025-10-08T19:48:30.133Z" }, + { url = "https://files.pythonhosted.org/packages/e7/70/c99e9edb5d91d5ad8a49fa3c1e8285ba64f1476782fed10ab251ff413ba1/propcache-0.4.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:ab2943be7c652f09638800905ee1bab2c544e537edb57d527997a24c13dc1455", size = 257393, upload-time = "2025-10-08T19:48:31.567Z" }, + { url = "https://files.pythonhosted.org/packages/08/02/87b25304249a35c0915d236575bc3574a323f60b47939a2262b77632a3ee/propcache-0.4.1-cp314-cp314t-win32.whl", hash = "sha256:05674a162469f31358c30bcaa8883cb7829fa3110bf9c0991fe27d7896c42d85", size = 42546, upload-time = "2025-10-08T19:48:32.872Z" }, + { url = "https://files.pythonhosted.org/packages/cb/ef/3c6ecf8b317aa982f309835e8f96987466123c6e596646d4e6a1dfcd080f/propcache-0.4.1-cp314-cp314t-win_amd64.whl", hash = "sha256:990f6b3e2a27d683cb7602ed6c86f15ee6b43b1194736f9baaeb93d0016633b1", size = 46259, upload-time = "2025-10-08T19:48:34.226Z" }, + { url = "https://files.pythonhosted.org/packages/c4/2d/346e946d4951f37eca1e4f55be0f0174c52cd70720f84029b02f296f4a38/propcache-0.4.1-cp314-cp314t-win_arm64.whl", hash = "sha256:ecef2343af4cc68e05131e45024ba34f6095821988a9d0a02aa7c73fcc448aa9", size = 40428, upload-time = "2025-10-08T19:48:35.441Z" }, { url = "https://files.pythonhosted.org/packages/5b/5a/bc7b4a4ef808fa59a816c17b20c4bef6884daebbdf627ff2a161da67da19/propcache-0.4.1-py3-none-any.whl", hash = "sha256:af2a6052aeb6cf17d3e46ee169099044fd8224cbaf75c76a2ef596e8163e2237", size = 13305, upload-time = "2025-10-08T19:49:00.792Z" }, ] @@ -1719,15 +1832,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/11/b1/ce1f4596211efb5410e178a803f08e59b20bedb66837dcf41e21c54f9ec1/pybtex_docutils-1.0.3-py3-none-any.whl", hash = "sha256:8fd290d2ae48e32fcb54d86b0efb8d573198653c7e2447d5bec5847095f430b9", size = 6385, upload-time = "2023-08-22T06:43:20.513Z" }, ] -[[package]] -name = "pycodestyle" -version = "2.14.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/11/e0/abfd2a0d2efe47670df87f3e3a0e2edda42f055053c85361f19c0e2c1ca8/pycodestyle-2.14.0.tar.gz", hash = "sha256:c4b5b517d278089ff9d0abdec919cd97262a3367449ea1c8b49b91529167b783", size = 39472, upload-time = "2025-06-20T18:49:48.75Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/d7/27/a58ddaf8c588a3ef080db9d0b7e0b97215cee3a45df74f3a94dbbf5c893a/pycodestyle-2.14.0-py2.py3-none-any.whl", hash = "sha256:dd6bf7cb4ee77f8e016f9c8e74a35ddd9f67e1d5fd4184d86c3b98e07099f42d", size = 31594, upload-time = "2025-06-20T18:49:47.491Z" }, -] - [[package]] name = "pycparser" version = "2.22" @@ -1813,15 +1917,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/e7/d3/c622950d87a2ffd1654208733b5bd1c5645930014abed8f4c0d74863988b/pydata_sphinx_theme-0.15.4-py3-none-any.whl", hash = "sha256:2136ad0e9500d0949f96167e63f3e298620040aea8f9c74621959eda5d4cf8e6", size = 4640157, upload-time = "2024-06-25T19:28:42.383Z" }, ] -[[package]] -name = "pyflakes" -version = "3.4.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/45/dc/fd034dc20b4b264b3d015808458391acbf9df40b1e54750ef175d39180b1/pyflakes-3.4.0.tar.gz", hash = "sha256:b24f96fafb7d2ab0ec5075b7350b3d2d2218eab42003821c06344973d3ea2f58", size = 64669, upload-time = "2025-06-20T18:45:27.834Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/c2/2f/81d580a0fb83baeb066698975cb14a618bdbed7720678566f1b046a95fe8/pyflakes-3.4.0-py2.py3-none-any.whl", hash = "sha256:f742a7dbd0d9cb9ea41e9a24a918996e8170c799fa528688d40dd582c8265f4f", size = 63551, upload-time = "2025-06-20T18:45:26.937Z" }, -] - [[package]] name = "pygments" version = "2.19.2" @@ -1943,6 +2038,9 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/a5/be/3fd5de0979fcb3994bfee0d65ed8ca9506a8a1260651b86174f6a86f52b3/pywin32-311-cp313-cp313-win32.whl", hash = "sha256:f95ba5a847cba10dd8c4d8fefa9f2a6cf283b8b88ed6178fa8a6c1ab16054d0d", size = 8705700, upload-time = "2025-07-14T20:13:26.471Z" }, { url = "https://files.pythonhosted.org/packages/e3/28/e0a1909523c6890208295a29e05c2adb2126364e289826c0a8bc7297bd5c/pywin32-311-cp313-cp313-win_amd64.whl", hash = "sha256:718a38f7e5b058e76aee1c56ddd06908116d35147e133427e59a3983f703a20d", size = 9494700, upload-time = "2025-07-14T20:13:28.243Z" }, { url = "https://files.pythonhosted.org/packages/04/bf/90339ac0f55726dce7d794e6d79a18a91265bdf3aa70b6b9ca52f35e022a/pywin32-311-cp313-cp313-win_arm64.whl", hash = "sha256:7b4075d959648406202d92a2310cb990fea19b535c7f4a78d3f5e10b926eeb8a", size = 8709318, upload-time = "2025-07-14T20:13:30.348Z" }, + { url = "https://files.pythonhosted.org/packages/c9/31/097f2e132c4f16d99a22bfb777e0fd88bd8e1c634304e102f313af69ace5/pywin32-311-cp314-cp314-win32.whl", hash = "sha256:b7a2c10b93f8986666d0c803ee19b5990885872a7de910fc460f9b0c2fbf92ee", size = 8840714, upload-time = "2025-07-14T20:13:32.449Z" }, + { url = "https://files.pythonhosted.org/packages/90/4b/07c77d8ba0e01349358082713400435347df8426208171ce297da32c313d/pywin32-311-cp314-cp314-win_amd64.whl", hash = "sha256:3aca44c046bd2ed8c90de9cb8427f581c479e594e99b5c0bb19b29c10fd6cb87", size = 9656800, upload-time = "2025-07-14T20:13:34.312Z" }, + { url = "https://files.pythonhosted.org/packages/c0/d2/21af5c535501a7233e734b8af901574572da66fcc254cb35d0609c9080dd/pywin32-311-cp314-cp314-win_arm64.whl", hash = "sha256:a508e2d9025764a8270f93111a970e1d0fbfc33f4153b388bb649b7eec4f9b42", size = 8932540, upload-time = "2025-07-14T20:13:36.379Z" }, ] [[package]] @@ -2113,6 +2211,33 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/49/ae/769dc372211835bf759319a7aae70525c6eb523e3371842c65b7ef41c9c6/rpds_py-0.26.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:dca83c498b4650a91efcf7b88d669b170256bf8017a5db6f3e06c2bf031f57e0", size = 554049, upload-time = "2025-07-01T15:55:13.004Z" }, { url = "https://files.pythonhosted.org/packages/6b/f9/4c43f9cc203d6ba44ce3146246cdc38619d92c7bd7bad4946a3491bd5b70/rpds_py-0.26.0-cp313-cp313t-win32.whl", hash = "sha256:4d11382bcaf12f80b51d790dee295c56a159633a8e81e6323b16e55d81ae37e9", size = 218428, upload-time = "2025-07-01T15:55:14.486Z" }, { url = "https://files.pythonhosted.org/packages/7e/8b/9286b7e822036a4a977f2f1e851c7345c20528dbd56b687bb67ed68a8ede/rpds_py-0.26.0-cp313-cp313t-win_amd64.whl", hash = "sha256:ff110acded3c22c033e637dd8896e411c7d3a11289b2edf041f86663dbc791e9", size = 231524, upload-time = "2025-07-01T15:55:15.745Z" }, + { url = "https://files.pythonhosted.org/packages/55/07/029b7c45db910c74e182de626dfdae0ad489a949d84a468465cd0ca36355/rpds_py-0.26.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:da619979df60a940cd434084355c514c25cf8eb4cf9a508510682f6c851a4f7a", size = 364292, upload-time = "2025-07-01T15:55:17.001Z" }, + { url = "https://files.pythonhosted.org/packages/13/d1/9b3d3f986216b4d1f584878dca15ce4797aaf5d372d738974ba737bf68d6/rpds_py-0.26.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:ea89a2458a1a75f87caabefe789c87539ea4e43b40f18cff526052e35bbb4fdf", size = 350334, upload-time = "2025-07-01T15:55:18.922Z" }, + { url = "https://files.pythonhosted.org/packages/18/98/16d5e7bc9ec715fa9668731d0cf97f6b032724e61696e2db3d47aeb89214/rpds_py-0.26.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:feac1045b3327a45944e7dcbeb57530339f6b17baff154df51ef8b0da34c8c12", size = 384875, upload-time = "2025-07-01T15:55:20.399Z" }, + { url = "https://files.pythonhosted.org/packages/f9/13/aa5e2b1ec5ab0e86a5c464d53514c0467bec6ba2507027d35fc81818358e/rpds_py-0.26.0-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b818a592bd69bfe437ee8368603d4a2d928c34cffcdf77c2e761a759ffd17d20", size = 399993, upload-time = "2025-07-01T15:55:21.729Z" }, + { url = "https://files.pythonhosted.org/packages/17/03/8021810b0e97923abdbab6474c8b77c69bcb4b2c58330777df9ff69dc559/rpds_py-0.26.0-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1a8b0dd8648709b62d9372fc00a57466f5fdeefed666afe3fea5a6c9539a0331", size = 516683, upload-time = "2025-07-01T15:55:22.918Z" }, + { url = "https://files.pythonhosted.org/packages/dc/b1/da8e61c87c2f3d836954239fdbbfb477bb7b54d74974d8f6fcb34342d166/rpds_py-0.26.0-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6d3498ad0df07d81112aa6ec6c95a7e7b1ae00929fb73e7ebee0f3faaeabad2f", size = 408825, upload-time = "2025-07-01T15:55:24.207Z" }, + { url = "https://files.pythonhosted.org/packages/38/bc/1fc173edaaa0e52c94b02a655db20697cb5fa954ad5a8e15a2c784c5cbdd/rpds_py-0.26.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:24a4146ccb15be237fdef10f331c568e1b0e505f8c8c9ed5d67759dac58ac246", size = 387292, upload-time = "2025-07-01T15:55:25.554Z" }, + { url = "https://files.pythonhosted.org/packages/7c/eb/3a9bb4bd90867d21916f253caf4f0d0be7098671b6715ad1cead9fe7bab9/rpds_py-0.26.0-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a9a63785467b2d73635957d32a4f6e73d5e4df497a16a6392fa066b753e87387", size = 420435, upload-time = "2025-07-01T15:55:27.798Z" }, + { url = "https://files.pythonhosted.org/packages/cd/16/e066dcdb56f5632713445271a3f8d3d0b426d51ae9c0cca387799df58b02/rpds_py-0.26.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:de4ed93a8c91debfd5a047be327b7cc8b0cc6afe32a716bbbc4aedca9e2a83af", size = 562410, upload-time = "2025-07-01T15:55:29.057Z" }, + { url = "https://files.pythonhosted.org/packages/60/22/ddbdec7eb82a0dc2e455be44c97c71c232983e21349836ce9f272e8a3c29/rpds_py-0.26.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:caf51943715b12af827696ec395bfa68f090a4c1a1d2509eb4e2cb69abbbdb33", size = 590724, upload-time = "2025-07-01T15:55:30.719Z" }, + { url = "https://files.pythonhosted.org/packages/2c/b4/95744085e65b7187d83f2fcb0bef70716a1ea0a9e5d8f7f39a86e5d83424/rpds_py-0.26.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:4a59e5bc386de021f56337f757301b337d7ab58baa40174fb150accd480bc953", size = 558285, upload-time = "2025-07-01T15:55:31.981Z" }, + { url = "https://files.pythonhosted.org/packages/37/37/6309a75e464d1da2559446f9c811aa4d16343cebe3dbb73701e63f760caa/rpds_py-0.26.0-cp314-cp314-win32.whl", hash = "sha256:92c8db839367ef16a662478f0a2fe13e15f2227da3c1430a782ad0f6ee009ec9", size = 223459, upload-time = "2025-07-01T15:55:33.312Z" }, + { url = "https://files.pythonhosted.org/packages/d9/6f/8e9c11214c46098b1d1391b7e02b70bb689ab963db3b19540cba17315291/rpds_py-0.26.0-cp314-cp314-win_amd64.whl", hash = "sha256:b0afb8cdd034150d4d9f53926226ed27ad15b7f465e93d7468caaf5eafae0d37", size = 236083, upload-time = "2025-07-01T15:55:34.933Z" }, + { url = "https://files.pythonhosted.org/packages/47/af/9c4638994dd623d51c39892edd9d08e8be8220a4b7e874fa02c2d6e91955/rpds_py-0.26.0-cp314-cp314-win_arm64.whl", hash = "sha256:ca3f059f4ba485d90c8dc75cb5ca897e15325e4e609812ce57f896607c1c0867", size = 223291, upload-time = "2025-07-01T15:55:36.202Z" }, + { url = "https://files.pythonhosted.org/packages/4d/db/669a241144460474aab03e254326b32c42def83eb23458a10d163cb9b5ce/rpds_py-0.26.0-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:5afea17ab3a126006dc2f293b14ffc7ef3c85336cf451564a0515ed7648033da", size = 361445, upload-time = "2025-07-01T15:55:37.483Z" }, + { url = "https://files.pythonhosted.org/packages/3b/2d/133f61cc5807c6c2fd086a46df0eb8f63a23f5df8306ff9f6d0fd168fecc/rpds_py-0.26.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:69f0c0a3df7fd3a7eec50a00396104bb9a843ea6d45fcc31c2d5243446ffd7a7", size = 347206, upload-time = "2025-07-01T15:55:38.828Z" }, + { url = "https://files.pythonhosted.org/packages/05/bf/0e8fb4c05f70273469eecf82f6ccf37248558526a45321644826555db31b/rpds_py-0.26.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:801a71f70f9813e82d2513c9a96532551fce1e278ec0c64610992c49c04c2dad", size = 380330, upload-time = "2025-07-01T15:55:40.175Z" }, + { url = "https://files.pythonhosted.org/packages/d4/a8/060d24185d8b24d3923322f8d0ede16df4ade226a74e747b8c7c978e3dd3/rpds_py-0.26.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:df52098cde6d5e02fa75c1f6244f07971773adb4a26625edd5c18fee906fa84d", size = 392254, upload-time = "2025-07-01T15:55:42.015Z" }, + { url = "https://files.pythonhosted.org/packages/b9/7b/7c2e8a9ee3e6bc0bae26bf29f5219955ca2fbb761dca996a83f5d2f773fe/rpds_py-0.26.0-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9bc596b30f86dc6f0929499c9e574601679d0341a0108c25b9b358a042f51bca", size = 516094, upload-time = "2025-07-01T15:55:43.603Z" }, + { url = "https://files.pythonhosted.org/packages/75/d6/f61cafbed8ba1499b9af9f1777a2a199cd888f74a96133d8833ce5eaa9c5/rpds_py-0.26.0-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9dfbe56b299cf5875b68eb6f0ebaadc9cac520a1989cac0db0765abfb3709c19", size = 402889, upload-time = "2025-07-01T15:55:45.275Z" }, + { url = "https://files.pythonhosted.org/packages/92/19/c8ac0a8a8df2dd30cdec27f69298a5c13e9029500d6d76718130f5e5be10/rpds_py-0.26.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ac64f4b2bdb4ea622175c9ab7cf09444e412e22c0e02e906978b3b488af5fde8", size = 384301, upload-time = "2025-07-01T15:55:47.098Z" }, + { url = "https://files.pythonhosted.org/packages/41/e1/6b1859898bc292a9ce5776016c7312b672da00e25cec74d7beced1027286/rpds_py-0.26.0-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:181ef9b6bbf9845a264f9aa45c31836e9f3c1f13be565d0d010e964c661d1e2b", size = 412891, upload-time = "2025-07-01T15:55:48.412Z" }, + { url = "https://files.pythonhosted.org/packages/ef/b9/ceb39af29913c07966a61367b3c08b4f71fad841e32c6b59a129d5974698/rpds_py-0.26.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:49028aa684c144ea502a8e847d23aed5e4c2ef7cadfa7d5eaafcb40864844b7a", size = 557044, upload-time = "2025-07-01T15:55:49.816Z" }, + { url = "https://files.pythonhosted.org/packages/2f/27/35637b98380731a521f8ec4f3fd94e477964f04f6b2f8f7af8a2d889a4af/rpds_py-0.26.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:e5d524d68a474a9688336045bbf76cb0def88549c1b2ad9dbfec1fb7cfbe9170", size = 585774, upload-time = "2025-07-01T15:55:51.192Z" }, + { url = "https://files.pythonhosted.org/packages/52/d9/3f0f105420fecd18551b678c9a6ce60bd23986098b252a56d35781b3e7e9/rpds_py-0.26.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:c1851f429b822831bd2edcbe0cfd12ee9ea77868f8d3daf267b189371671c80e", size = 554886, upload-time = "2025-07-01T15:55:52.541Z" }, + { url = "https://files.pythonhosted.org/packages/6b/c5/347c056a90dc8dd9bc240a08c527315008e1b5042e7a4cf4ac027be9d38a/rpds_py-0.26.0-cp314-cp314t-win32.whl", hash = "sha256:7bdb17009696214c3b66bb3590c6d62e14ac5935e53e929bcdbc5a495987a84f", size = 219027, upload-time = "2025-07-01T15:55:53.874Z" }, + { url = "https://files.pythonhosted.org/packages/75/04/5302cea1aa26d886d34cadbf2dc77d90d7737e576c0065f357b96dc7a1a6/rpds_py-0.26.0-cp314-cp314t-win_amd64.whl", hash = "sha256:f14440b9573a6f76b4ee4770c13f0b5921f71dde3b6fcb8dabbefd13b7fe05d7", size = 232821, upload-time = "2025-07-01T15:55:55.167Z" }, ] [[package]] @@ -2525,7 +2650,7 @@ name = "sqlalchemy" version = "2.0.41" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "greenlet", marker = "platform_machine == 'AMD64' or platform_machine == 'WIN32' or platform_machine == 'aarch64' or platform_machine == 'amd64' or platform_machine == 'ppc64le' or platform_machine == 'win32' or platform_machine == 'x86_64'" }, + { name = "greenlet", marker = "(python_full_version < '3.14' and platform_machine == 'AMD64') or (python_full_version < '3.14' and platform_machine == 'WIN32') or (python_full_version < '3.14' and platform_machine == 'aarch64') or (python_full_version < '3.14' and platform_machine == 'amd64') or (python_full_version < '3.14' and platform_machine == 'ppc64le') or (python_full_version < '3.14' and platform_machine == 'win32') or (python_full_version < '3.14' and platform_machine == 'x86_64')" }, { name = "typing-extensions" }, ] sdist = { url = "https://files.pythonhosted.org/packages/63/66/45b165c595ec89aa7dcc2c1cd222ab269bc753f1fc7a1e68f8481bd957bf/sqlalchemy-2.0.41.tar.gz", hash = "sha256:edba70118c4be3c2b1f90754d308d0b79c6fe2c0fdc52d8ddf603916f83f4db9", size = 9689424, upload-time = "2025-05-14T17:10:32.339Z" } @@ -2588,6 +2713,12 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/1e/48/973da1ee8bc0743519759e74c3615b39acdc3faf00e0a0710f8c856d8c9d/statsmodels-0.14.5-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5a085d47c8ef5387279a991633883d0e700de2b0acc812d7032d165888627bef", size = 10453538, upload-time = "2025-07-07T14:24:06.959Z" }, { url = "https://files.pythonhosted.org/packages/c7/d6/18903fb707afd31cf1edaec5201964dbdacb2bfae9a22558274647a7c88f/statsmodels-0.14.5-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9f866b2ebb2904b47c342d00def83c526ef2eb1df6a9a3c94ba5fe63d0005aec", size = 10681584, upload-time = "2025-07-07T14:24:21.038Z" }, { url = "https://files.pythonhosted.org/packages/44/d6/80df1bbbfcdc50bff4152f43274420fa9856d56e234d160d6206eb1f5827/statsmodels-0.14.5-cp313-cp313-win_amd64.whl", hash = "sha256:2a06bca03b7a492f88c8106103ab75f1a5ced25de90103a89f3a287518017939", size = 9604641, upload-time = "2025-07-07T12:08:36.23Z" }, + { url = "https://files.pythonhosted.org/packages/fd/6c/0fb40a89d715412160097c6f3387049ed88c9bd866c8838a8852c705ae2f/statsmodels-0.14.5-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:07c4dad25bbb15864a31b4917a820f6d104bdc24e5ddadcda59027390c3bed9e", size = 10211256, upload-time = "2025-10-30T13:46:58.591Z" }, + { url = "https://files.pythonhosted.org/packages/88/4a/e36fe8b19270ab3e80df357da924c6c029cab0fb9a0fbd28aaf49341707d/statsmodels-0.14.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:babb067c852e966c2c933b79dbb5d0240919d861941a2ef6c0e13321c255528d", size = 10110933, upload-time = "2025-10-30T13:47:11.774Z" }, + { url = "https://files.pythonhosted.org/packages/8a/bf/1b7e7b1a6c09a88a9c5c9e60622c050dfd08af11c2e6d4a42dbc71b32ee1/statsmodels-0.14.5-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:110194b137286173cc676d7bad0119a197778de6478fc6cbdc3b33571165ac1e", size = 10253981, upload-time = "2025-10-30T16:32:22.399Z" }, + { url = "https://files.pythonhosted.org/packages/b8/d0/f95da95524bdd99613923ca61a3036d1308cee1290e5e8acb89f51736a8c/statsmodels-0.14.5-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9c8a9c384a60c80731b278e7fd18764364c8817f4995b13a175d636f967823d1", size = 10460450, upload-time = "2025-10-30T16:32:44.985Z" }, + { url = "https://files.pythonhosted.org/packages/28/bb/59e7be0271be264b7b541baf3973f97747740950bfd5115de731f63da8ab/statsmodels-0.14.5-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:557df3a870a57248df744fdfcc444ecbc5bdbf1c042b8a8b5d8e3e797830dc2a", size = 10694060, upload-time = "2025-10-30T16:33:07.656Z" }, + { url = "https://files.pythonhosted.org/packages/8b/c0/b28d0fd0347ea38d3610052f479e4b922eb33bb8790817f93cd89e6e08ba/statsmodels-0.14.5-cp314-cp314-win_amd64.whl", hash = "sha256:95af7a9c4689d514f4341478b891f867766f3da297f514b8c4adf08f4fa61d03", size = 9648961, upload-time = "2025-10-30T13:47:24.303Z" }, ] [[package]] @@ -2662,6 +2793,11 @@ dependencies = [ wheels = [ { url = "https://files.pythonhosted.org/packages/d3/54/a2ba279afcca44bbd320d4e73675b282fcee3d81400ea1b53934efca6462/torch-2.10.0-2-cp312-none-macosx_11_0_arm64.whl", hash = "sha256:13ec4add8c3faaed8d13e0574f5cd4a323c11655546f91fbe6afa77b57423574", size = 79498202, upload-time = "2026-02-10T21:44:52.603Z" }, { url = "https://files.pythonhosted.org/packages/ec/23/2c9fe0c9c27f7f6cb865abcea8a4568f29f00acaeadfc6a37f6801f84cb4/torch-2.10.0-2-cp313-none-macosx_11_0_arm64.whl", hash = "sha256:e521c9f030a3774ed770a9c011751fb47c4d12029a3d6522116e48431f2ff89e", size = 79498254, upload-time = "2026-02-10T21:44:44.095Z" }, + { url = "https://files.pythonhosted.org/packages/b3/7a/abada41517ce0011775f0f4eacc79659bc9bc6c361e6bfe6f7052a6b9363/torch-2.10.0-3-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:98c01b8bb5e3240426dcde1446eed6f40c778091c8544767ef1168fc663a05a6", size = 915622781, upload-time = "2026-03-11T14:17:11.354Z" }, + { url = "https://files.pythonhosted.org/packages/ab/c6/4dfe238342ffdcec5aef1c96c457548762d33c40b45a1ab7033bb26d2ff2/torch-2.10.0-3-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:80b1b5bfe38eb0e9f5ff09f206dcac0a87aadd084230d4a36eea5ec5232c115b", size = 915627275, upload-time = "2026-03-11T14:16:11.325Z" }, + { url = "https://files.pythonhosted.org/packages/d8/f0/72bf18847f58f877a6a8acf60614b14935e2f156d942483af1ffc081aea0/torch-2.10.0-3-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:46b3574d93a2a8134b3f5475cfb98e2eb46771794c57015f6ad1fb795ec25e49", size = 915523474, upload-time = "2026-03-11T14:17:44.422Z" }, + { url = "https://files.pythonhosted.org/packages/f4/39/590742415c3030551944edc2ddc273ea1fdfe8ffb2780992e824f1ebee98/torch-2.10.0-3-cp314-cp314-manylinux_2_28_x86_64.whl", hash = "sha256:b1d5e2aba4eb7f8e87fbe04f86442887f9167a35f092afe4c237dfcaaef6e328", size = 915632474, upload-time = "2026-03-11T14:15:13.666Z" }, + { url = "https://files.pythonhosted.org/packages/b6/8e/34949484f764dde5b222b7fe3fede43e4a6f0da9d7f8c370bb617d629ee2/torch-2.10.0-3-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:0228d20b06701c05a8f978357f657817a4a63984b0c90745def81c18aedfa591", size = 915523882, upload-time = "2026-03-11T14:14:46.311Z" }, { url = "https://files.pythonhosted.org/packages/cc/af/758e242e9102e9988969b5e621d41f36b8f258bb4a099109b7a4b4b50ea4/torch-2.10.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:5fd4117d89ffd47e3dcc71e71a22efac24828ad781c7e46aaaf56bf7f2796acf", size = 145996088, upload-time = "2026-01-21T16:24:44.171Z" }, { url = "https://files.pythonhosted.org/packages/23/8e/3c74db5e53bff7ed9e34c8123e6a8bfef718b2450c35eefab85bb4a7e270/torch-2.10.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:787124e7db3b379d4f1ed54dd12ae7c741c16a4d29b49c0226a89bea50923ffb", size = 915711952, upload-time = "2026-01-21T16:23:53.503Z" }, { url = "https://files.pythonhosted.org/packages/6e/01/624c4324ca01f66ae4c7cd1b74eb16fb52596dce66dbe51eff95ef9e7a4c/torch-2.10.0-cp312-cp312-win_amd64.whl", hash = "sha256:2c66c61f44c5f903046cc696d088e21062644cbe541c7f1c4eaae88b2ad23547", size = 113757972, upload-time = "2026-01-21T16:24:39.516Z" }, @@ -2674,6 +2810,14 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/54/fd/b207d1c525cb570ef47f3e9f836b154685011fce11a2f444ba8a4084d042/torch-2.10.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:6021db85958db2f07ec94e1bc77212721ba4920c12a18dc552d2ae36a3eb163f", size = 915612644, upload-time = "2026-01-21T16:21:47.019Z" }, { url = "https://files.pythonhosted.org/packages/36/53/0197f868c75f1050b199fe58f9bf3bf3aecac9b4e85cc9c964383d745403/torch-2.10.0-cp313-cp313t-win_amd64.whl", hash = "sha256:ff43db38af76fda183156153983c9a096fc4c78d0cd1e07b14a2314c7f01c2c8", size = 113997015, upload-time = "2026-01-21T16:23:00.767Z" }, { url = "https://files.pythonhosted.org/packages/0e/13/e76b4d9c160e89fff48bf16b449ea324bda84745d2ab30294c37c2434c0d/torch-2.10.0-cp313-none-macosx_11_0_arm64.whl", hash = "sha256:cdf2a523d699b70d613243211ecaac14fe9c5df8a0b0a9c02add60fb2a413e0f", size = 79498248, upload-time = "2026-01-21T16:23:09.315Z" }, + { url = "https://files.pythonhosted.org/packages/4f/93/716b5ac0155f1be70ed81bacc21269c3ece8dba0c249b9994094110bfc51/torch-2.10.0-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:bf0d9ff448b0218e0433aeb198805192346c4fd659c852370d5cc245f602a06a", size = 79464992, upload-time = "2026-01-21T16:23:05.162Z" }, + { url = "https://files.pythonhosted.org/packages/69/2b/51e663ff190c9d16d4a8271203b71bc73a16aa7619b9f271a69b9d4a936b/torch-2.10.0-cp314-cp314-manylinux_2_28_aarch64.whl", hash = "sha256:233aed0659a2503b831d8a67e9da66a62c996204c0bba4f4c442ccc0c68a3f60", size = 146018567, upload-time = "2026-01-21T16:22:23.393Z" }, + { url = "https://files.pythonhosted.org/packages/5e/cd/4b95ef7f293b927c283db0b136c42be91c8ec6845c44de0238c8c23bdc80/torch-2.10.0-cp314-cp314-manylinux_2_28_x86_64.whl", hash = "sha256:682497e16bdfa6efeec8cde66531bc8d1fbbbb4d8788ec6173c089ed3cc2bfe5", size = 915721646, upload-time = "2026-01-21T16:21:16.983Z" }, + { url = "https://files.pythonhosted.org/packages/56/97/078a007208f8056d88ae43198833469e61a0a355abc0b070edd2c085eb9a/torch-2.10.0-cp314-cp314-win_amd64.whl", hash = "sha256:6528f13d2a8593a1a412ea07a99812495bec07e9224c28b2a25c0a30c7da025c", size = 113752373, upload-time = "2026-01-21T16:22:13.471Z" }, + { url = "https://files.pythonhosted.org/packages/d8/94/71994e7d0d5238393df9732fdab607e37e2b56d26a746cb59fdb415f8966/torch-2.10.0-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:f5ab4ba32383061be0fb74bda772d470140a12c1c3b58a0cfbf3dae94d164c28", size = 79850324, upload-time = "2026-01-21T16:22:09.494Z" }, + { url = "https://files.pythonhosted.org/packages/e2/65/1a05346b418ea8ccd10360eef4b3e0ce688fba544e76edec26913a8d0ee0/torch-2.10.0-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:716b01a176c2a5659c98f6b01bf868244abdd896526f1c692712ab36dbaf9b63", size = 146006482, upload-time = "2026-01-21T16:22:18.42Z" }, + { url = "https://files.pythonhosted.org/packages/1d/b9/5f6f9d9e859fc3235f60578fa64f52c9c6e9b4327f0fe0defb6de5c0de31/torch-2.10.0-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:d8f5912ba938233f86361e891789595ff35ca4b4e2ac8fe3670895e5976731d6", size = 915613050, upload-time = "2026-01-21T16:20:49.035Z" }, + { url = "https://files.pythonhosted.org/packages/66/4d/35352043ee0eaffdeff154fad67cd4a31dbed7ff8e3be1cc4549717d6d51/torch-2.10.0-cp314-cp314t-win_amd64.whl", hash = "sha256:71283a373f0ee2c89e0f0d5f446039bdabe8dbc3c9ccf35f0f784908b0acd185", size = 113995816, upload-time = "2026-01-21T16:22:05.312Z" }, ] [[package]] @@ -2752,6 +2896,8 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/ab/a8/cdf8b3e4c98132f965f88c2313a4b493266832ad47fb52f23d14d4f86bb5/triton-3.6.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:74caf5e34b66d9f3a429af689c1c7128daba1d8208df60e81106b115c00d6fca", size = 188266850, upload-time = "2026-01-20T16:00:43.041Z" }, { url = "https://files.pythonhosted.org/packages/f9/0b/37d991d8c130ce81a8728ae3c25b6e60935838e9be1b58791f5997b24a54/triton-3.6.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:10c7f76c6e72d2ef08df639e3d0d30729112f47a56b0c81672edc05ee5116ac9", size = 188289450, upload-time = "2026-01-20T16:00:49.136Z" }, { url = "https://files.pythonhosted.org/packages/35/f8/9c66bfc55361ec6d0e4040a0337fb5924ceb23de4648b8a81ae9d33b2b38/triton-3.6.0-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d002e07d7180fd65e622134fbd980c9a3d4211fb85224b56a0a0efbd422ab72f", size = 188400296, upload-time = "2026-01-20T16:00:56.042Z" }, + { url = "https://files.pythonhosted.org/packages/df/3d/9e7eee57b37c80cec63322c0231bb6da3cfe535a91d7a4d64896fcb89357/triton-3.6.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a17a5d5985f0ac494ed8a8e54568f092f7057ef60e1b0fa09d3fd1512064e803", size = 188273063, upload-time = "2026-01-20T16:01:07.278Z" }, + { url = "https://files.pythonhosted.org/packages/f6/56/6113c23ff46c00aae423333eb58b3e60bdfe9179d542781955a5e1514cb3/triton-3.6.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:46bd1c1af4b6704e554cad2eeb3b0a6513a980d470ccfa63189737340c7746a7", size = 188397994, upload-time = "2026-01-20T16:01:14.236Z" }, ] [[package]] @@ -2906,6 +3052,42 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/80/25/a3892b46182c586c202629fc2159aa13975d3741d52ebd7347fd501d48d5/yarl-1.23.0-cp313-cp313t-win32.whl", hash = "sha256:93a784271881035ab4406a172edb0faecb6e7d00f4b53dc2f55919d6c9688595", size = 88313, upload-time = "2026-03-01T22:06:37.39Z" }, { url = "https://files.pythonhosted.org/packages/43/68/8c5b36aa5178900b37387937bc2c2fe0e9505537f713495472dcf6f6fccc/yarl-1.23.0-cp313-cp313t-win_amd64.whl", hash = "sha256:dd00607bffbf30250fe108065f07453ec124dbf223420f57f5e749b04295e090", size = 94932, upload-time = "2026-03-01T22:06:39.579Z" }, { url = "https://files.pythonhosted.org/packages/c6/cc/d79ba8292f51f81f4dc533a8ccfb9fc6992cabf0998ed3245de7589dc07c/yarl-1.23.0-cp313-cp313t-win_arm64.whl", hash = "sha256:ac09d42f48f80c9ee1635b2fcaa819496a44502737660d3c0f2ade7526d29144", size = 84786, upload-time = "2026-03-01T22:06:41.988Z" }, + { url = "https://files.pythonhosted.org/packages/90/98/b85a038d65d1b92c3903ab89444f48d3cee490a883477b716d7a24b1a78c/yarl-1.23.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:21d1b7305a71a15b4794b5ff22e8eef96ff4a6d7f9657155e5aa419444b28912", size = 124455, upload-time = "2026-03-01T22:06:43.615Z" }, + { url = "https://files.pythonhosted.org/packages/39/54/bc2b45559f86543d163b6e294417a107bb87557609007c007ad889afec18/yarl-1.23.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:85610b4f27f69984932a7abbe52703688de3724d9f72bceb1cca667deff27474", size = 86752, upload-time = "2026-03-01T22:06:45.425Z" }, + { url = "https://files.pythonhosted.org/packages/24/f9/e8242b68362bffe6fb536c8db5076861466fc780f0f1b479fc4ffbebb128/yarl-1.23.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:23f371bd662cf44a7630d4d113101eafc0cfa7518a2760d20760b26021454719", size = 86291, upload-time = "2026-03-01T22:06:46.974Z" }, + { url = "https://files.pythonhosted.org/packages/ea/d8/d1cb2378c81dd729e98c716582b1ccb08357e8488e4c24714658cc6630e8/yarl-1.23.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c4a80f77dc1acaaa61f0934176fccca7096d9b1ff08c8ba9cddf5ae034a24319", size = 99026, upload-time = "2026-03-01T22:06:48.459Z" }, + { url = "https://files.pythonhosted.org/packages/0a/ff/7196790538f31debe3341283b5b0707e7feb947620fc5e8236ef28d44f72/yarl-1.23.0-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:bd654fad46d8d9e823afbb4f87c79160b5a374ed1ff5bde24e542e6ba8f41434", size = 92355, upload-time = "2026-03-01T22:06:50.306Z" }, + { url = "https://files.pythonhosted.org/packages/c1/56/25d58c3eddde825890a5fe6aa1866228377354a3c39262235234ab5f616b/yarl-1.23.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:682bae25f0a0dd23a056739f23a134db9f52a63e2afd6bfb37ddc76292bbd723", size = 106417, upload-time = "2026-03-01T22:06:52.1Z" }, + { url = "https://files.pythonhosted.org/packages/51/8a/882c0e7bc8277eb895b31bce0138f51a1ba551fc2e1ec6753ffc1e7c1377/yarl-1.23.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a82836cab5f197a0514235aaf7ffccdc886ccdaa2324bc0aafdd4ae898103039", size = 106422, upload-time = "2026-03-01T22:06:54.424Z" }, + { url = "https://files.pythonhosted.org/packages/42/2b/fef67d616931055bf3d6764885990a3ac647d68734a2d6a9e1d13de437a2/yarl-1.23.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1c57676bdedc94cd3bc37724cf6f8cd2779f02f6aba48de45feca073e714fe52", size = 101915, upload-time = "2026-03-01T22:06:55.895Z" }, + { url = "https://files.pythonhosted.org/packages/18/6a/530e16aebce27c5937920f3431c628a29a4b6b430fab3fd1c117b26ff3f6/yarl-1.23.0-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c7f8dc16c498ff06497c015642333219871effba93e4a2e8604a06264aca5c5c", size = 100690, upload-time = "2026-03-01T22:06:58.21Z" }, + { url = "https://files.pythonhosted.org/packages/88/08/93749219179a45e27b036e03260fda05190b911de8e18225c294ac95bbc9/yarl-1.23.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:5ee586fb17ff8f90c91cf73c6108a434b02d69925f44f5f8e0d7f2f260607eae", size = 98750, upload-time = "2026-03-01T22:06:59.794Z" }, + { url = "https://files.pythonhosted.org/packages/d9/cf/ea424a004969f5d81a362110a6ac1496d79efdc6d50c2c4b2e3ea0fc2519/yarl-1.23.0-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:17235362f580149742739cc3828b80e24029d08cbb9c4bda0242c7b5bc610a8e", size = 94685, upload-time = "2026-03-01T22:07:01.375Z" }, + { url = "https://files.pythonhosted.org/packages/e2/b7/14341481fe568e2b0408bcf1484c652accafe06a0ade9387b5d3fd9df446/yarl-1.23.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:0793e2bd0cf14234983bbb371591e6bea9e876ddf6896cdcc93450996b0b5c85", size = 106009, upload-time = "2026-03-01T22:07:03.151Z" }, + { url = "https://files.pythonhosted.org/packages/0a/e6/5c744a9b54f4e8007ad35bce96fbc9218338e84812d36f3390cea616881a/yarl-1.23.0-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:3650dc2480f94f7116c364096bc84b1d602f44224ef7d5c7208425915c0475dd", size = 100033, upload-time = "2026-03-01T22:07:04.701Z" }, + { url = "https://files.pythonhosted.org/packages/0c/23/e3bfc188d0b400f025bc49d99793d02c9abe15752138dcc27e4eaf0c4a9e/yarl-1.23.0-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:f40e782d49630ad384db66d4d8b73ff4f1b8955dc12e26b09a3e3af064b3b9d6", size = 106483, upload-time = "2026-03-01T22:07:06.231Z" }, + { url = "https://files.pythonhosted.org/packages/72/42/f0505f949a90b3f8b7a363d6cbdf398f6e6c58946d85c6d3a3bc70595b26/yarl-1.23.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:94f8575fbdf81749008d980c17796097e645574a3b8c28ee313931068dad14fe", size = 102175, upload-time = "2026-03-01T22:07:08.4Z" }, + { url = "https://files.pythonhosted.org/packages/aa/65/b39290f1d892a9dd671d1c722014ca062a9c35d60885d57e5375db0404b5/yarl-1.23.0-cp314-cp314-win32.whl", hash = "sha256:c8aa34a5c864db1087d911a0b902d60d203ea3607d91f615acd3f3108ac32169", size = 83871, upload-time = "2026-03-01T22:07:09.968Z" }, + { url = "https://files.pythonhosted.org/packages/a9/5b/9b92f54c784c26e2a422e55a8d2607ab15b7ea3349e28359282f84f01d43/yarl-1.23.0-cp314-cp314-win_amd64.whl", hash = "sha256:63e92247f383c85ab00dd0091e8c3fa331a96e865459f5ee80353c70a4a42d70", size = 89093, upload-time = "2026-03-01T22:07:11.501Z" }, + { url = "https://files.pythonhosted.org/packages/e0/7d/8a84dc9381fd4412d5e7ff04926f9865f6372b4c2fd91e10092e65d29eb8/yarl-1.23.0-cp314-cp314-win_arm64.whl", hash = "sha256:70efd20be968c76ece7baa8dafe04c5be06abc57f754d6f36f3741f7aa7a208e", size = 83384, upload-time = "2026-03-01T22:07:13.069Z" }, + { url = "https://files.pythonhosted.org/packages/dd/8d/d2fad34b1c08aa161b74394183daa7d800141aaaee207317e82c790b418d/yarl-1.23.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:9a18d6f9359e45722c064c97464ec883eb0e0366d33eda61cb19a244bf222679", size = 131019, upload-time = "2026-03-01T22:07:14.903Z" }, + { url = "https://files.pythonhosted.org/packages/19/ff/33009a39d3ccf4b94d7d7880dfe17fb5816c5a4fe0096d9b56abceea9ac7/yarl-1.23.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:2803ed8b21ca47a43da80a6fd1ed3019d30061f7061daa35ac54f63933409412", size = 89894, upload-time = "2026-03-01T22:07:17.372Z" }, + { url = "https://files.pythonhosted.org/packages/0c/f1/dab7ac5e7306fb79c0190766a3c00b4cb8d09a1f390ded68c85a5934faf5/yarl-1.23.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:394906945aa8b19fc14a61cf69743a868bb8c465efe85eee687109cc540b98f4", size = 89979, upload-time = "2026-03-01T22:07:19.361Z" }, + { url = "https://files.pythonhosted.org/packages/aa/b1/08e95f3caee1fad6e65017b9f26c1d79877b502622d60e517de01e72f95d/yarl-1.23.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:71d006bee8397a4a89f469b8deb22469fe7508132d3c17fa6ed871e79832691c", size = 95943, upload-time = "2026-03-01T22:07:21.266Z" }, + { url = "https://files.pythonhosted.org/packages/c0/cc/6409f9018864a6aa186c61175b977131f373f1988e198e031236916e87e4/yarl-1.23.0-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:62694e275c93d54f7ccedcfef57d42761b2aad5234b6be1f3e3026cae4001cd4", size = 88786, upload-time = "2026-03-01T22:07:23.129Z" }, + { url = "https://files.pythonhosted.org/packages/76/40/cc22d1d7714b717fde2006fad2ced5efe5580606cb059ae42117542122f3/yarl-1.23.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a31de1613658308efdb21ada98cbc86a97c181aa050ba22a808120bb5be3ab94", size = 101307, upload-time = "2026-03-01T22:07:24.689Z" }, + { url = "https://files.pythonhosted.org/packages/8f/0d/476c38e85ddb4c6ec6b20b815bdd779aa386a013f3d8b85516feee55c8dc/yarl-1.23.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:fb1e8b8d66c278b21d13b0a7ca22c41dd757a7c209c6b12c313e445c31dd3b28", size = 100904, upload-time = "2026-03-01T22:07:26.287Z" }, + { url = "https://files.pythonhosted.org/packages/72/32/0abe4a76d59adf2081dcb0397168553ece4616ada1c54d1c49d8936c74f8/yarl-1.23.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:50f9d8d531dfb767c565f348f33dd5139a6c43f5cbdf3f67da40d54241df93f6", size = 97728, upload-time = "2026-03-01T22:07:27.906Z" }, + { url = "https://files.pythonhosted.org/packages/b7/35/7b30f4810fba112f60f5a43237545867504e15b1c7647a785fbaf588fac2/yarl-1.23.0-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:575aa4405a656e61a540f4a80eaa5260f2a38fff7bfdc4b5f611840d76e9e277", size = 95964, upload-time = "2026-03-01T22:07:30.198Z" }, + { url = "https://files.pythonhosted.org/packages/2d/86/ed7a73ab85ef00e8bb70b0cb5421d8a2a625b81a333941a469a6f4022828/yarl-1.23.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:041b1a4cefacf65840b4e295c6985f334ba83c30607441ae3cf206a0eed1a2e4", size = 95882, upload-time = "2026-03-01T22:07:32.132Z" }, + { url = "https://files.pythonhosted.org/packages/19/90/d56967f61a29d8498efb7afb651e0b2b422a1e9b47b0ab5f4e40a19b699b/yarl-1.23.0-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:d38c1e8231722c4ce40d7593f28d92b5fc72f3e9774fe73d7e800ec32299f63a", size = 90797, upload-time = "2026-03-01T22:07:34.404Z" }, + { url = "https://files.pythonhosted.org/packages/72/00/8b8f76909259f56647adb1011d7ed8b321bcf97e464515c65016a47ecdf0/yarl-1.23.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:d53834e23c015ee83a99377db6e5e37d8484f333edb03bd15b4bc312cc7254fb", size = 101023, upload-time = "2026-03-01T22:07:35.953Z" }, + { url = "https://files.pythonhosted.org/packages/ac/e2/cab11b126fb7d440281b7df8e9ddbe4851e70a4dde47a202b6642586b8d9/yarl-1.23.0-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:2e27c8841126e017dd2a054a95771569e6070b9ee1b133366d8b31beb5018a41", size = 96227, upload-time = "2026-03-01T22:07:37.594Z" }, + { url = "https://files.pythonhosted.org/packages/c2/9b/2c893e16bfc50e6b2edf76c1a9eb6cb0c744346197e74c65e99ad8d634d0/yarl-1.23.0-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:76855800ac56f878847a09ce6dba727c93ca2d89c9e9d63002d26b916810b0a2", size = 100302, upload-time = "2026-03-01T22:07:39.334Z" }, + { url = "https://files.pythonhosted.org/packages/28/ec/5498c4e3a6d5f1003beb23405671c2eb9cdbf3067d1c80f15eeafe301010/yarl-1.23.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:e09fd068c2e169a7070d83d3bde728a4d48de0549f975290be3c108c02e499b4", size = 98202, upload-time = "2026-03-01T22:07:41.717Z" }, + { url = "https://files.pythonhosted.org/packages/fe/c3/cd737e2d45e70717907f83e146f6949f20cc23cd4bf7b2688727763aa458/yarl-1.23.0-cp314-cp314t-win32.whl", hash = "sha256:73309162a6a571d4cbd3b6a1dcc703c7311843ae0d1578df6f09be4e98df38d4", size = 90558, upload-time = "2026-03-01T22:07:43.433Z" }, + { url = "https://files.pythonhosted.org/packages/e1/19/3774d162f6732d1cfb0b47b4140a942a35ca82bb19b6db1f80e9e7bdc8f8/yarl-1.23.0-cp314-cp314t-win_amd64.whl", hash = "sha256:4503053d296bc6e4cbd1fad61cf3b6e33b939886c4f249ba7c78b602214fabe2", size = 97610, upload-time = "2026-03-01T22:07:45.773Z" }, + { url = "https://files.pythonhosted.org/packages/51/47/3fa2286c3cb162c71cdb34c4224d5745a1ceceb391b2bd9b19b668a8d724/yarl-1.23.0-cp314-cp314t-win_arm64.whl", hash = "sha256:44bb7bef4ea409384e3f8bc36c063d77ea1b8d4a5b2706956c0d6695f07dcc25", size = 86041, upload-time = "2026-03-01T22:07:49.026Z" }, { url = "https://files.pythonhosted.org/packages/69/68/c8739671f5699c7dc470580a4f821ef37c32c4cb0b047ce223a7f115757f/yarl-1.23.0-py3-none-any.whl", hash = "sha256:a2df6afe50dea8ae15fa34c9f824a3ee958d785fd5d089063d960bae1daa0a3f", size = 48288, upload-time = "2026-03-01T22:07:51.388Z" }, ]