HatmanStack commited on
Commit
31b602c
·
1 Parent(s): 924784f

fix: address critical and medium post-audit findings

Browse files

Critical/High:
- Remove dead _get_model() cached wrapper (never called)
- Add [build-system] to pyproject.toml for reliable editable installs
- Fix devcontainer.json to use pyproject.toml instead of deleted requirements.txt
- Add scikit-learn and scikeras as [train] optional deps

Medium:
- Move streamlit import in config.py to lazy import inside configure_page()
- Remove unreachable QueryExecutionError catch blocks in 1_home_team.py
- Add uv cache step to CI workflow
- Fix actions/checkout and actions/setup-python to stable v4/v5

Low:
- Remove duplicated --cov-fail-under from CI (pyproject.toml is single source)
- Fix SIM105 indentation in pyproject.toml
- Fix compile_model.py arr type to match str|float sliced values
- Add dist/ and build/ to .gitignore

.devcontainer/devcontainer.json CHANGED
@@ -17,7 +17,7 @@
17
  ]
18
  }
19
  },
20
- "updateContentCommand": "[ -f packages.txt ] && sudo apt update && sudo apt upgrade -y && sudo xargs apt install -y <packages.txt; [ -f requirements.txt ] && pip3 install --user -r requirements.txt; pip3 install --user streamlit; echo 'Packages installed and Requirements met'",
21
  "postAttachCommand": {
22
  "server": "streamlit run app.py"
23
  },
 
17
  ]
18
  }
19
  },
20
+ "updateContentCommand": "[ -f packages.txt ] && sudo apt update && sudo apt upgrade -y && sudo xargs apt install -y <packages.txt; pip3 install --user -e '.[dev]'; echo 'Packages installed and requirements met'",
21
  "postAttachCommand": {
22
  "server": "streamlit run app.py"
23
  },
.github/workflows/ci.yml CHANGED
@@ -11,21 +11,28 @@ jobs:
11
  runs-on: ubuntu-latest
12
 
13
  steps:
14
- - uses: actions/checkout@v6
15
 
16
  - name: Set up Python
17
- uses: actions/setup-python@v6
18
  with:
19
  python-version: "3.11"
20
 
21
  - name: Install uv
22
  run: pip install uv
23
 
 
 
 
 
 
 
 
24
  - name: Install dependencies
25
  run: uv pip install -e ".[dev]" --system
26
 
27
  - name: Run tests
28
- run: pytest --cov=src --cov-report=term-missing --cov-fail-under=70
29
 
30
  - name: Run ruff
31
  run: ruff check src/ tests/
 
11
  runs-on: ubuntu-latest
12
 
13
  steps:
14
+ - uses: actions/checkout@v4
15
 
16
  - name: Set up Python
17
+ uses: actions/setup-python@v5
18
  with:
19
  python-version: "3.11"
20
 
21
  - name: Install uv
22
  run: pip install uv
23
 
24
+ - name: Cache uv packages
25
+ uses: actions/cache@v4
26
+ with:
27
+ path: ~/.cache/uv
28
+ key: uv-${{ runner.os }}-${{ hashFiles('pyproject.toml') }}
29
+ restore-keys: uv-${{ runner.os }}-
30
+
31
  - name: Install dependencies
32
  run: uv pip install -e ".[dev]" --system
33
 
34
  - name: Run tests
35
+ run: pytest --cov=src --cov-report=term-missing
36
 
37
  - name: Run ruff
38
  run: ruff check src/ tests/
.gitignore CHANGED
@@ -18,6 +18,8 @@ htmlcov/
18
 
19
  # Packaging
20
  *.egg-info/
 
 
21
 
22
  # uv
23
  uv.lock
 
18
 
19
  # Packaging
20
  *.egg-info/
21
+ dist/
22
+ build/
23
 
24
  # uv
25
  uv.lock
pages/1_home_team.py CHANGED
@@ -8,7 +8,6 @@ import streamlit as st
8
  from src.config import DIFFICULTY_PRESETS, PLAYER_COLUMNS, configure_page
9
  from src.database.connection import (
10
  DatabaseConnectionError,
11
- QueryExecutionError,
12
  load_data,
13
  )
14
  from src.database.queries import get_players_by_full_names, search_player_by_name
@@ -64,12 +63,8 @@ def find_player(search_term: str) -> list[str]:
64
  results = search_player_by_name(data, validated_term)
65
  return [player[0] for player in results]
66
  except DatabaseConnectionError as e:
67
- st.error("Could not connect to database. Please try again later.")
68
- logger.error("Database connection error: %s", e)
69
- return []
70
- except QueryExecutionError as e:
71
- st.error("Error searching for players. Please try again.")
72
- logger.error("Query error: %s", e)
73
  return []
74
 
75
 
@@ -92,12 +87,8 @@ def find_home_team() -> pd.DataFrame:
92
  st.session_state.home_team_df = df
93
  return df
94
  except DatabaseConnectionError as e:
95
- st.error("Could not connect to database. Please try again later.")
96
- logger.error("Database connection error: %s", e)
97
- return pd.DataFrame(columns=PLAYER_COLUMNS)
98
- except QueryExecutionError as e:
99
- st.error("Error loading team data. Please try again.")
100
- logger.error("Query error: %s", e)
101
  return pd.DataFrame(columns=PLAYER_COLUMNS)
102
 
103
 
 
8
  from src.config import DIFFICULTY_PRESETS, PLAYER_COLUMNS, configure_page
9
  from src.database.connection import (
10
  DatabaseConnectionError,
 
11
  load_data,
12
  )
13
  from src.database.queries import get_players_by_full_names, search_player_by_name
 
63
  results = search_player_by_name(data, validated_term)
64
  return [player[0] for player in results]
65
  except DatabaseConnectionError as e:
66
+ st.error("Could not load player data. Please try again later.")
67
+ logger.error("Data load error: %s", e)
 
 
 
 
68
  return []
69
 
70
 
 
87
  st.session_state.home_team_df = df
88
  return df
89
  except DatabaseConnectionError as e:
90
+ st.error("Could not load player data. Please try again later.")
91
+ logger.error("Data load error: %s", e)
 
 
 
 
92
  return pd.DataFrame(columns=PLAYER_COLUMNS)
93
 
94
 
pages/2_play_game.py CHANGED
@@ -25,7 +25,6 @@ from src.database.queries import get_away_team_by_stats
25
  from src.ml.model import (
26
  ModelLoadError,
27
  analyze_team_stats,
28
- get_winner_model,
29
  predict_winner,
30
  )
31
  from src.state.session import get_away_stats, get_home_team_df, init_session_state
@@ -41,11 +40,6 @@ def _load_nba_data() -> pd.DataFrame:
41
  return load_data()
42
 
43
 
44
- @st.cache_resource
45
- def _get_model(): # type: ignore[no-untyped-def]
46
- return get_winner_model()
47
-
48
-
49
  # Initialize session state BEFORE any access
50
  init_session_state()
51
 
 
25
  from src.ml.model import (
26
  ModelLoadError,
27
  analyze_team_stats,
 
28
  predict_winner,
29
  )
30
  from src.state.session import get_away_stats, get_home_team_df, init_session_state
 
40
  return load_data()
41
 
42
 
 
 
 
 
 
43
  # Initialize session state BEFORE any access
44
  init_session_state()
45
 
pyproject.toml CHANGED
@@ -1,3 +1,7 @@
 
 
 
 
1
  [project]
2
  name = "streamlit-nba"
3
  version = "1.1.0"
@@ -21,6 +25,10 @@ dev = [
21
  "pandas-stubs>=2.0.0",
22
  "pre-commit>=3.0.0",
23
  ]
 
 
 
 
24
 
25
  [tool.mypy]
26
  python_version = "3.11"
@@ -75,7 +83,7 @@ select = [
75
  ignore = [
76
  "S101", # assert used (ok in tests)
77
  "PLR0913", # too many arguments
78
- "SIM105", # prefer explicit try-except over contextlib.suppress
79
  "PLR2004", # magic numbers ok in game logic
80
  "S311", # standard pseudo-random generators ok for game logic
81
  "E501", # line length (handled by formatter or ignored)
@@ -83,6 +91,7 @@ ignore = [
83
 
84
  [tool.ruff.lint.per-file-ignores]
85
  "tests/*" = ["S101", "ARG001", "ARG002", "PLR2004", "PLC0415"]
 
86
 
87
  [tool.pytest.ini_options]
88
  testpaths = ["tests"]
 
1
+ [build-system]
2
+ requires = ["setuptools>=68.0"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
  [project]
6
  name = "streamlit-nba"
7
  version = "1.1.0"
 
25
  "pandas-stubs>=2.0.0",
26
  "pre-commit>=3.0.0",
27
  ]
28
+ train = [
29
+ "scikit-learn>=1.3.0",
30
+ "scikeras>=0.12.0",
31
+ ]
32
 
33
  [tool.mypy]
34
  python_version = "3.11"
 
83
  ignore = [
84
  "S101", # assert used (ok in tests)
85
  "PLR0913", # too many arguments
86
+ "SIM105", # prefer explicit try-except over contextlib.suppress
87
  "PLR2004", # magic numbers ok in game logic
88
  "S311", # standard pseudo-random generators ok for game logic
89
  "E501", # line length (handled by formatter or ignored)
 
91
 
92
  [tool.ruff.lint.per-file-ignores]
93
  "tests/*" = ["S101", "ARG001", "ARG002", "PLR2004", "PLC0415"]
94
+ "src/config.py" = ["PLC0415"] # lazy import of streamlit in configure_page()
95
 
96
  [tool.pytest.ini_options]
97
  testpaths = ["tests"]
scripts/compile_model.py CHANGED
@@ -95,7 +95,7 @@ def create_stats(roster: pd.DataFrame, schedule: pd.DataFrame) -> list[np.ndarra
95
 
96
  # Combine home and away stats for each game
97
  for i in range(len(home_stats)):
98
- arr: list[float] = []
99
 
100
  for row in home_stats[i]:
101
  arr.extend(row[1:]) # Skip team name column
 
95
 
96
  # Combine home and away stats for each game
97
  for i in range(len(home_stats)):
98
+ arr: list[str | float] = []
99
 
100
  for row in home_stats[i]:
101
  arr.extend(row[1:]) # Skip team name column
src/config.py CHANGED
@@ -3,8 +3,6 @@
3
  import logging
4
  from typing import Final
5
 
6
- import streamlit as st
7
-
8
  # Database column names for player data
9
  PLAYER_COLUMNS: Final[list[str]] = [
10
  "FULL_NAME",
@@ -93,5 +91,7 @@ def setup_logging(level: int = logging.INFO) -> logging.Logger:
93
 
94
  def configure_page() -> None:
95
  """Configure Streamlit page settings and logging."""
 
 
96
  setup_logging()
97
  st.set_page_config(layout="wide")
 
3
  import logging
4
  from typing import Final
5
 
 
 
6
  # Database column names for player data
7
  PLAYER_COLUMNS: Final[list[str]] = [
8
  "FULL_NAME",
 
91
 
92
  def configure_page() -> None:
93
  """Configure Streamlit page settings and logging."""
94
+ import streamlit as st
95
+
96
  setup_logging()
97
  st.set_page_config(layout="wide")