Spaces:
Sleeping
Sleeping
Download tests/test_analysis.py from stevafernandes/Seth-Austin: direct link, hf CLI and curl.
- Browser
- Download file 8.69 kB
-
https://huggingface.co/spaces/stevafernandes/Seth-Austin/resolve/main/tests/test_analysis.py
- Command line
-
hf download hf://spaces/stevafernandes/Seth-Austin/tests/test_analysis.py
-
curl -L -o test_analysis.py https://huggingface.co/spaces/stevafernandes/Seth-Austin/resolve/main/tests/test_analysis.py
8.69 kB
| import os | |
| import sys | |
| import numpy as np | |
| import pandas as pd | |
| import pytest | |
| sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) | |
| import analysis as A # noqa: E402 | |
| def df(): | |
| return A.load_players() | |
| def test_dataset_shape_and_columns(df): | |
| assert len(df) == 148 | |
| for col in A.REQUIRED_COLUMNS: | |
| assert col in df.columns | |
| assert df[A.STAT_COLUMNS + [A.SALARY_COLUMN]].isna().sum().sum() == 0 | |
| assert df[A.NAME_COLUMN].is_unique | |
| def test_first_player_is_pete_crow_armstrong(df): | |
| assert df.at[0, A.NAME_COLUMN] == "Pete Crow-Armstrong" | |
| assert df.at[0, A.SALARY_COLUMN] == 19166667 | |
| def test_percentile_rank_definition(): | |
| others = np.array([1, 2, 3, 4]) | |
| assert A.percentile_rank(others, 5) == 100.0 | |
| assert A.percentile_rank(others, 0) == 0.0 | |
| assert A.percentile_rank(others, 2.5) == 50.0 | |
| assert A.percentile_rank(others, 2) == 37.5 # one below, one tie counts half | |
| assert A.percentile_rank(others, 0, higher_is_better=False) == 100.0 | |
| def test_performance_table_directions(df): | |
| idx = A.player_index(df, "Pete Crow-Armstrong") | |
| table = A.performance_table(df, idx) | |
| assert list(table["Statistic"]) == A.STAT_COLUMNS | |
| war = table.set_index("Statistic").loc["WAR"] | |
| assert war["Rank"] == "1 of 148" | |
| assert war["Percentile"] == 100.0 | |
| k = table.set_index("Statistic").loc["K%"] | |
| # Higher strikeout rate than most players, so a low percentile and a negative z-score. | |
| assert k["Percentile"] < 50 | |
| assert k["z-score"] < 0 | |
| assert 0 <= table["Percentile"].min() and table["Percentile"].max() <= 100 | |
| def test_format_helpers(): | |
| assert A.format_stat("PA", 436.0) == "436" | |
| assert A.format_stat("K%", 0.261467) == "26.1%" | |
| assert A.format_stat("AVG", 0.28074) == "0.281" | |
| assert A.format_stat("WAR", 5.8616) == "5.9" | |
| assert A.format_stat("wRC+", 144.98) == "145" | |
| assert A.format_money(19166667) == "$19,166,667" | |
| assert A.format_money_short(19166667) == "$19.17M" | |
| assert A.format_money_short(780000) == "$780K" | |
| def test_salary_summary(df): | |
| idx = A.player_index(df, "Pete Crow-Armstrong") | |
| s = A.salary_summary(df, idx) | |
| assert s["n_players"] == 148 | |
| assert s["n_market_contracts"] + s["n_minimum_contracts"] == 148 | |
| assert s["is_market_contract"] is True | |
| assert 0 < s["salary_percentile"] < 100 | |
| assert s["salary_rank"] == int((df[A.SALARY_COLUMN] > 19166667).sum()) + 1 | |
| def test_comparables_exclude_self_and_have_k_rows(df): | |
| idx = A.player_index(df, "Pete Crow-Armstrong") | |
| comps, summary = A.comparable_players(df, idx) | |
| assert len(comps) == A.N_COMPARABLES | |
| assert "Pete Crow-Armstrong" not in comps["Player"].tolist() | |
| assert comps["Distance"].astype(float).is_monotonic_increasing | |
| salaries = df.set_index(A.NAME_COLUMN).loc[comps["Player"], A.SALARY_COLUMN] | |
| assert (salaries > A.MARKET_CONTRACT_THRESHOLD).all() | |
| assert summary["comparable_names"] == comps["Player"].tolist() | |
| assert summary["comparable_median_salary"] == float(salaries.median()) | |
| def test_cost_per_war_handles_non_positive_war(df): | |
| idx = int(df.index[df["WAR"] <= 0][0]) | |
| out = A.cost_per_war(df, idx) | |
| assert out["cost_per_war"] is None | |
| idx = A.player_index(df, "Pete Crow-Armstrong") | |
| out = A.cost_per_war(df, idx) | |
| assert out["cost_per_war"] == pytest.approx(19166667 / df.at[idx, "WAR"]) | |
| def test_percentile_within_distribution_interpolates(): | |
| q = np.linspace(0, 1, len(A.QUANTILE_GRID)) | |
| assert A.percentile_within_distribution(-1, q) == 1.0 | |
| assert A.percentile_within_distribution(2, q) == 99.0 | |
| assert A.percentile_within_distribution(0.5, q) == pytest.approx(50.0, abs=0.6) | |
| def test_load_players_rejects_bad_data(tmp_path, df): | |
| bad = df.copy() | |
| bad.loc[0, "WAR"] = np.nan | |
| path = tmp_path / "bad.csv" | |
| bad.to_csv(path, index=False) | |
| with pytest.raises(ValueError): | |
| A.load_players(str(path)) | |
| dup = df.copy() | |
| dup.loc[1, A.NAME_COLUMN] = dup.loc[0, A.NAME_COLUMN] | |
| dup.to_csv(path, index=False) | |
| with pytest.raises(ValueError): | |
| A.load_players(str(path)) | |
| def test_cached_validation_requires_matching_fingerprint(tmp_path, df): | |
| path = tmp_path / "loo.json" | |
| A.save_validation({"fingerprint": "wrong", "n_estimators": A._model_n_estimators()}, str(path)) | |
| assert A.load_cached_validation(df, str(path)) is None | |
| A.save_validation({"fingerprint": A.dataset_fingerprint(df), "n_estimators": A._model_n_estimators(), "r2_log_salary": 0.1}, str(path)) | |
| assert A.load_cached_validation(df, str(path))["r2_log_salary"] == 0.1 | |
| def test_assessment_rules(): | |
| def perf_with(pct): | |
| return pd.DataFrame({"Statistic": A.STAT_COLUMNS, "Percentile": [pct] * len(A.STAT_COLUMNS)}) | |
| comps = {"comparable_median_salary": 2_000_000} | |
| # Underpaid relative to the model and to rank. | |
| out = A.assessment(perf_with(90.0), {"salary": 1_000_000, "salary_percentile": 30.0}, | |
| {"actual_percentile_in_predicted": 10.0}, comps) | |
| assert out["model_band"] == "below" | |
| assert out["headline"].startswith("Performance supports the salary") | |
| assert out["comparables_reading"].startswith("paid less") | |
| # Inside the model range, ranks close. | |
| out = A.assessment(perf_with(60.0), {"salary": 2_100_000, "salary_percentile": 55.0}, | |
| {"actual_percentile_in_predicted": 50.0}, comps) | |
| assert out["model_band"] == "inside" | |
| assert out["headline"].startswith("Performance supports the salary") | |
| assert out["comparables_reading"].startswith("paid about the same") | |
| # Overpaid relative to the model and to rank. | |
| out = A.assessment(perf_with(20.0), {"salary": 30_000_000, "salary_percentile": 95.0}, | |
| {"actual_percentile_in_predicted": 90.0}, comps) | |
| assert out["model_band"] == "above" | |
| assert out["headline"].startswith("Performance does not support") | |
| assert out["comparables_reading"].startswith("paid more") | |
| # Conflicts produce a mixed reading. | |
| out = A.assessment(perf_with(95.0), {"salary": 30_000_000, "salary_percentile": 60.0}, | |
| {"actual_percentile_in_predicted": 90.0}, comps) | |
| assert out["headline"].startswith("The evidence is mixed") | |
| out = A.assessment(perf_with(10.0), {"salary": 5_000_000, "salary_percentile": 60.0}, | |
| {"actual_percentile_in_predicted": 40.0}, comps) | |
| assert out["headline"].startswith("The evidence is mixed") | |
| def test_model_prediction_for_pete_crow_armstrong(df): | |
| idx = A.player_index(df, "Pete Crow-Armstrong") | |
| out = A.model_salary_for_player(df, idx, n_estimators=4) | |
| assert out["n_train"] == int((df[A.SALARY_COLUMN] > A.MARKET_CONTRACT_THRESHOLD).sum()) - 1 | |
| assert out["selected_player_is_market_contract"] is True | |
| q = out["predicted_quantiles"] | |
| assert q["q10"] <= q["q25"] <= q["q50"] <= q["q75"] <= q["q90"] | |
| assert 1.0 <= out["actual_percentile_in_predicted"] <= 99.0 | |
| assert out["predicted_median_salary"] > 0 | |
| def test_analyze_player_bundle_is_json_serializable(df): | |
| result = A.analyze_player(df, "Pete Crow-Armstrong", n_estimators=4) | |
| payload = A.analysis_to_json(result) | |
| assert payload["player"]["name"] == "Pete Crow-Armstrong" | |
| assert len(payload["performance_table"]) == len(A.STAT_COLUMNS) | |
| assert isinstance(payload["assessment"]["headline"], str) | |
| def test_training_mask_excludes_minimum_contracts_and_selected_player(df): | |
| idx = A.player_index(df, "Pete Crow-Armstrong") | |
| mask = A.model_training_mask(df, idx) | |
| assert not mask[idx] | |
| assert (df.loc[mask, A.SALARY_COLUMN] > A.MARKET_CONTRACT_THRESHOLD).all() | |
| minimum_idx = int(df.index[df[A.SALARY_COLUMN] <= A.MARKET_CONTRACT_THRESHOLD][0]) | |
| assert not A.model_training_mask(df, minimum_idx)[minimum_idx] | |
| assert A.model_training_mask(df, minimum_idx).sum() == A.model_training_mask(df).sum() | |
| def test_salary_percentile_among_market_is_none_for_minimum_players(df): | |
| minimum_idx = int(df.index[df[A.SALARY_COLUMN] <= A.MARKET_CONTRACT_THRESHOLD][0]) | |
| assert A.salary_summary(df, minimum_idx)["salary_percentile_among_market"] is None | |
| idx = A.player_index(df, "Pete Crow-Armstrong") | |
| assert 0 < A.salary_summary(df, idx)["salary_percentile_among_market"] < 100 | |
| def test_analysis_to_json_replaces_non_finite_values(): | |
| payload = A.analysis_to_json({"a": float("nan"), "b": np.float64("inf"), "c": np.int64(3), "d": [np.float32(1.5)], | |
| "e": pd.DataFrame({"x": [np.nan, 2.0]})}) | |
| assert payload == {"a": None, "b": None, "c": 3, "d": [1.5], "e": [{"x": None}, {"x": 2.0}]} | |