diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json
new file mode 100644
index 0000000000000000000000000000000000000000..b6d1d796b9cba39356eb282a50bbdbf3fc6fe1a3
--- /dev/null
+++ b/.devcontainer/devcontainer.json
@@ -0,0 +1,49 @@
+// For format details, see https://aka.ms/devcontainer.json. For config options, see the
+// README at: https://github.com/devcontainers/templates/tree/main/src/docker-existing-docker-compose
+{
+ "name": "Existing Docker Compose (Extend)",
+
+ // Update the 'dockerComposeFile' list if you have more compose files or use different names.
+ // The .devcontainer/docker-compose.yml file contains any overrides you need/want to make.
+ "dockerComposeFile": [
+ "../docker-compose.yml",
+ "docker-compose.yml"
+ ],
+
+ // The 'service' property is the name of the service for the container that VS Code should
+ // use. Update this value and .devcontainer/docker-compose.yml to the real service name.
+ "service": "streamlit",
+
+ // The optional 'workspaceFolder' property is the path VS Code should open by default when
+ // connected. This is typically a file mount in .devcontainer/docker-compose.yml
+ "workspaceFolder": "/app",
+ "customizations": {
+ "vscode": {
+ "extensions": [
+ "ms-python.python",
+ "ms-toolsai.jupyter"
+ ]
+ }
+ }
+
+ // Features to add to the dev container. More info: https://containers.dev/features.
+ // "features": {},
+
+ // Use 'forwardPorts' to make a list of ports inside the container available locally.
+ // "forwardPorts": [],
+
+ // Uncomment the next line if you want start specific services in your Docker Compose config.
+ // "runServices": [],
+
+ // Uncomment the next line if you want to keep your containers running after VS Code shuts down.
+ // "shutdownAction": "none",
+
+ // Uncomment the next line to run commands after the container is created.
+ // "postCreateCommand": "cat /etc/os-release",
+
+ // Configure tool-specific properties.
+ // "customizations": {},
+
+ // Uncomment to connect as an existing user other than the container default. More info: https://aka.ms/dev-containers-non-root.
+ // "remoteUser": "devcontainer"
+}
diff --git a/.devcontainer/docker-compose.yml b/.devcontainer/docker-compose.yml
new file mode 100644
index 0000000000000000000000000000000000000000..8bfff8fc16cc08f596e18afe663ebdd28ae9a864
--- /dev/null
+++ b/.devcontainer/docker-compose.yml
@@ -0,0 +1,27 @@
+version: '3.8'
+services:
+ # Update this to the name of the service you want to work with in your docker-compose.yml file
+ streamlit:
+ # Uncomment if you want to override the service's Dockerfile to one in the .devcontainer
+ # folder. Note that the path of the Dockerfile and context is relative to the *primary*
+ # docker-compose.yml file (the first in the devcontainer.json "dockerComposeFile"
+ # array). The sample below assumes your primary file is in the root of your project.
+ #
+ build:
+ dockerfile: ./Dockerfile
+ context: ./
+ target: development
+
+ volumes:
+ # Update this to wherever you want VS Code to mount the folder of your project
+ - .:/app:cached
+
+ # Uncomment the next four lines if you will use a ptrace-based debugger like C++, Go, and Rust.
+ # cap_add:
+ # - SYS_PTRACE
+ # security_opt:
+ # - seccomp:unconfined
+
+ # Overrides default command so things don't shut down after the process ends.
+ command: ["/bin/sh -c \"while sleep 1000; do :; done\""]
+
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..d3b1030ff582ae1c3554e60c137ec328202f592d
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,166 @@
+# Byte-compiled / optimized / DLL files
+__pycache__/
+*.py[cod]
+*$py.class
+
+# C extensions
+*.so
+
+# Distribution / packaging
+.Python
+build/
+develop-eggs/
+dist/
+downloads/
+eggs/
+.eggs/
+lib/
+lib64/
+parts/
+sdist/
+var/
+wheels/
+share/python-wheels/
+*.egg-info/
+.installed.cfg
+*.egg
+MANIFEST
+
+# PyInstaller
+# Usually these files are written by a python script from a template
+# before PyInstaller builds the exe, so as to inject date/other infos into it.
+*.manifest
+*.spec
+
+# Installer logs
+pip-log.txt
+pip-delete-this-directory.txt
+
+# Unit test / coverage reports
+htmlcov/
+.tox/
+.nox/
+.coverage
+.coverage.*
+.cache
+nosetests.xml
+coverage.xml
+*.cover
+*.py,cover
+.hypothesis/
+.pytest_cache/
+cover/
+
+# Translations
+*.mo
+*.pot
+
+# Django stuff:
+*.log
+local_settings.py
+db.sqlite3
+db.sqlite3-journal
+
+# Flask stuff:
+instance/
+.webassets-cache
+
+# Scrapy stuff:
+.scrapy
+
+# Sphinx documentation
+docs/_build/
+
+# PyBuilder
+.pybuilder/
+target/
+
+# Jupyter Notebook
+.ipynb_checkpoints
+
+# IPython
+profile_default/
+ipython_config.py
+
+# pyenv
+# For a library or package, you might want to ignore these files since the code is
+# intended to run in multiple environments; otherwise, check them in:
+# .python-version
+
+# pipenv
+# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
+# However, in case of collaboration, if having platform-specific dependencies or dependencies
+# having no cross-platform support, pipenv may install dependencies that don't work, or not
+# install all needed dependencies.
+#Pipfile.lock
+
+# poetry
+# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
+# This is especially recommended for binary packages to ensure reproducibility, and is more
+# commonly ignored for libraries.
+# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
+#poetry.lock
+
+# pdm
+# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
+#pdm.lock
+# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
+# in version control.
+# https://pdm.fming.dev/#use-with-ide
+.pdm.toml
+
+# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
+__pypackages__/
+
+# Celery stuff
+celerybeat-schedule
+celerybeat.pid
+
+# SageMath parsed files
+*.sage.py
+
+# Environments
+.env
+.venv
+env/
+venv/
+ENV/
+env.bak/
+venv.bak/
+
+# Spyder project settings
+.spyderproject
+.spyproject
+
+# Rope project settings
+.ropeproject
+
+# mkdocs documentation
+/site
+
+# mypy
+.mypy_cache/
+.dmypy.json
+dmypy.json
+
+# Pyre type checker
+.pyre/
+
+# pytype static type analyzer
+.pytype/
+
+# Cython debug symbols
+cython_debug/
+
+# PyCharm
+# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
+# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
+# and can be added to the global gitignore or merged into this file. For a more nuclear
+# option (not recommended) you can uncomment the following to ignore the entire idea folder.
+#.idea/
+
+# Streamlit
+secrets.toml
+
+.ipynb_checkpoints
+*.ipynb
diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 0000000000000000000000000000000000000000..55f9833195ab50bedfe862edada9246b98f5b984
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,37 @@
+FROM python:3.11-slim as base
+
+ENV APP_BASE_PATH="/app"
+ENV APP_SRC_PATH=${APP_BASE_PATH}/src
+ENV PYTHONPATH="${APP_SRC_PATH}:${PYTHONPATH}"
+WORKDIR $APP_BASE_PATH
+
+RUN apt-get update && apt-get install -y \
+ build-essential \
+ curl \
+ software-properties-common \
+ git \
+ && rm -rf /var/lib/apt/lists/*
+
+RUN pip3 install pip-tools
+COPY ./pyproject.toml .
+
+FROM base as pip-service
+
+COPY ./requirements.txt .
+RUN pip3 install -r requirements.txt
+
+FROM pip-service as service-setup
+EXPOSE 8501
+HEALTHCHECK CMD curl --fail http://localhost:8501/_stcore/health
+ENTRYPOINT ["./start.sh"]
+
+FROM pip-service as service
+WORKDIR $APP_SRC_PATH
+COPY ./src .
+
+FROM service-setup as development
+WORKDIR $APP_BASE_PATH
+COPY ./dev-requirements.txt .
+RUN pip3 install -r dev-requirements.txt
+
+WORKDIR $APP_SRC_PATH
\ No newline at end of file
diff --git a/README.md b/README.md
index 2039e128de70ba0a25157f2f7f9a2a7bf542bcbd..fb280d5abbb0ef0fb6f22be4c15eb31d726b0bf4 100644
--- a/README.md
+++ b/README.md
@@ -1,12 +1,55 @@
---
-title: Nfl Playoff Challenge Streamlit
-emoji: š»
-colorFrom: pink
-colorTo: gray
+title: YFDash
+emoji: š
+colorFrom: indigo
+colorTo: blue
sdk: streamlit
sdk_version: 1.29.0
-app_file: app.py
+app_file: src/Home.py
pinned: false
---
Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
+
+# nfl-playoff-challenge-steamlit Streamlit Application
+
+Template Repo for Streamlit Application - https://github.com/JonSolow/streamlit-template
+
+Here are some of the features of the template:
+
+- VSCode .devcontainer for local development: [Documentation](https://code.visualstudio.com/docs/devcontainers/containers)
+ - docker-compose.yml
+ - Dockerfile
+- Linting Configuration [TO-DO] (allows for clean code and quicker detection of possible bugs as one of the first steps in Shift-left testing)
+ - [Black](https://black.readthedocs.io/en/stable/index.html)
+ - [ruff](https://beta.ruff.rs/docs/)
+ - [mypy](https://mypy.readthedocs.io/en/stable/index.html)
+- Unit Tests
+ - [pytest](https://docs.pytest.org/)
+
+# Start Here to Develop
+
+1. Prerequisites
+
+- Install [Visual Studio Code](https://code.visualstudio.com/)
+- Install [Visual Studo Code Extension - Dev containers](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-containers)
+- Install [Docker](https://www.docker.com/)
+
+2. Confirm Docker is installed by executing command `docker -v`
+3. Open VSCode to a New Window and open this repository's directory
+4. You may see a notification that the Folder containers a Dev Container configuration file. If so, click on "Reopen in Container"
+
+- If you do not see this notification, press `F1` key and begin typing the following until you can see the option "Dev Containers: Rebuild and reopen in Container".
+- This action will reopen the VSCode within a Docker container suitable to develop and locally run the application.
+
+5. The dev container will start up the Streamlit application.
+
+- To access the application, navigate to http://localhost:8501
+ - The container forwards the port 8501 where the Streamlit application is hosted
+- Any changes made to the code will be reflected in the Streamlit application when you refresh.
+
+6. Now inside the VSCode dev container, to run tests, execute `./tests/run_tests.sh`
+
+- This script has an optional argument `-f` for "fix mode" which allows for configuration of black and ruff to automatically apply fixes.
+
+7. As functions are added to the application, unit tests can/should be added in `tests/unit`, with existing support utilizing the `pytest` library.
diff --git a/dev-requirements.txt b/dev-requirements.txt
new file mode 100644
index 0000000000000000000000000000000000000000..fffeab12f165383ca04ccd8ae7c838bc03c5b767
--- /dev/null
+++ b/dev-requirements.txt
@@ -0,0 +1,463 @@
+#
+# This file is autogenerated by pip-compile with Python 3.11
+# by the following command:
+#
+# pip-compile --extra=dev --output-file=dev-requirements.txt pyproject.toml
+#
+altair==5.2.0
+ # via streamlit
+annotated-types==0.6.0
+ # via pydantic
+anyio==4.1.0
+ # via
+ # httpcore
+ # jupyter-server
+argon2-cffi==23.1.0
+ # via jupyter-server
+argon2-cffi-bindings==21.2.0
+ # via argon2-cffi
+arrow==1.3.0
+ # via isoduration
+asttokens==2.4.1
+ # via stack-data
+async-lru==2.0.4
+ # via jupyterlab
+attrs==23.1.0
+ # via
+ # jsonschema
+ # referencing
+babel==2.13.1
+ # via jupyterlab-server
+beautifulsoup4==4.12.2
+ # via
+ # nfl-playoff-challenge-steamlit (pyproject.toml)
+ # nbconvert
+black==23.11.0
+ # via nfl-playoff-challenge-steamlit (pyproject.toml)
+bleach==6.1.0
+ # via nbconvert
+blinker==1.7.0
+ # via streamlit
+cachetools==5.3.2
+ # via streamlit
+certifi==2023.11.17
+ # via
+ # httpcore
+ # httpx
+ # requests
+cffi==1.16.0
+ # via argon2-cffi-bindings
+charset-normalizer==3.3.2
+ # via requests
+click==8.1.7
+ # via
+ # black
+ # streamlit
+comm==0.2.0
+ # via
+ # ipykernel
+ # ipywidgets
+debugpy==1.8.0
+ # via ipykernel
+decorator==5.1.1
+ # via ipython
+defusedxml==0.7.1
+ # via nbconvert
+duckdb==0.9.2
+ # via nfl-playoff-challenge-steamlit (pyproject.toml)
+executing==2.0.1
+ # via stack-data
+fastjsonschema==2.19.0
+ # via nbformat
+fqdn==1.5.1
+ # via jsonschema
+gitdb==4.0.11
+ # via gitpython
+gitpython==3.1.40
+ # via streamlit
+h11==0.14.0
+ # via httpcore
+html5lib==1.1
+ # via nfl-playoff-challenge-steamlit (pyproject.toml)
+httpcore==0.17.3
+ # via httpx
+httpx==0.24.1
+ # via httpx-oauth
+httpx-oauth==0.13.0
+ # via streamlit-oauth
+idna==3.6
+ # via
+ # anyio
+ # httpx
+ # jsonschema
+ # requests
+importlib-metadata==6.9.0
+ # via streamlit
+iniconfig==2.0.0
+ # via pytest
+ipykernel==6.27.1
+ # via
+ # jupyter
+ # jupyter-console
+ # jupyterlab
+ # qtconsole
+ipython==8.18.1
+ # via
+ # ipykernel
+ # ipywidgets
+ # jupyter-console
+ipywidgets==8.1.1
+ # via jupyter
+isoduration==20.11.0
+ # via jsonschema
+jedi==0.19.1
+ # via ipython
+jinja2==3.1.2
+ # via
+ # altair
+ # jupyter-server
+ # jupyterlab
+ # jupyterlab-server
+ # nbconvert
+ # pydeck
+json5==0.9.14
+ # via jupyterlab-server
+jsonpointer==2.4
+ # via jsonschema
+jsonschema[format-nongpl]==4.20.0
+ # via
+ # altair
+ # jupyter-events
+ # jupyterlab-server
+ # nbformat
+jsonschema-specifications==2023.11.2
+ # via jsonschema
+jupyter==1.0.0
+ # via nfl-playoff-challenge-steamlit (pyproject.toml)
+jupyter-client==8.6.0
+ # via
+ # ipykernel
+ # jupyter-console
+ # jupyter-server
+ # nbclient
+ # qtconsole
+jupyter-console==6.6.3
+ # via jupyter
+jupyter-core==5.5.0
+ # via
+ # ipykernel
+ # jupyter-client
+ # jupyter-console
+ # jupyter-server
+ # jupyterlab
+ # nbclient
+ # nbconvert
+ # nbformat
+ # qtconsole
+jupyter-events==0.9.0
+ # via jupyter-server
+jupyter-lsp==2.2.1
+ # via jupyterlab
+jupyter-server==2.11.1
+ # via
+ # jupyter-lsp
+ # jupyterlab
+ # jupyterlab-server
+ # notebook
+ # notebook-shim
+jupyter-server-terminals==0.4.4
+ # via jupyter-server
+jupyterlab==4.0.9
+ # via notebook
+jupyterlab-pygments==0.3.0
+ # via nbconvert
+jupyterlab-server==2.25.2
+ # via
+ # jupyterlab
+ # notebook
+jupyterlab-widgets==3.0.9
+ # via ipywidgets
+lxml==4.9.3
+ # via nfl-playoff-challenge-steamlit (pyproject.toml)
+lxml-stubs==0.4.0
+ # via nfl-playoff-challenge-steamlit (pyproject.toml)
+markdown-it-py==3.0.0
+ # via rich
+markupsafe==2.1.3
+ # via
+ # jinja2
+ # nbconvert
+matplotlib-inline==0.1.6
+ # via
+ # ipykernel
+ # ipython
+mdurl==0.1.2
+ # via markdown-it-py
+mistune==3.0.2
+ # via nbconvert
+mypy==1.7.1
+ # via nfl-playoff-challenge-steamlit (pyproject.toml)
+mypy-extensions==1.0.0
+ # via
+ # black
+ # mypy
+nbclient==0.9.0
+ # via nbconvert
+nbconvert==7.11.0
+ # via
+ # jupyter
+ # jupyter-server
+nbformat==5.9.2
+ # via
+ # jupyter-server
+ # nbclient
+ # nbconvert
+nest-asyncio==1.5.8
+ # via ipykernel
+notebook==7.0.6
+ # via jupyter
+notebook-shim==0.2.3
+ # via
+ # jupyterlab
+ # notebook
+numpy==1.26.2
+ # via
+ # nfl-playoff-challenge-steamlit (pyproject.toml)
+ # altair
+ # pandas
+ # pandas-stubs
+ # pyarrow
+ # pydeck
+ # streamlit
+overrides==7.4.0
+ # via jupyter-server
+packaging==23.2
+ # via
+ # altair
+ # black
+ # ipykernel
+ # jupyter-server
+ # jupyterlab
+ # jupyterlab-server
+ # nbconvert
+ # pytest
+ # qtconsole
+ # qtpy
+ # streamlit
+pandas==2.1.3
+ # via
+ # nfl-playoff-challenge-steamlit (pyproject.toml)
+ # altair
+ # streamlit
+pandas-stubs==2.1.1.230928
+ # via nfl-playoff-challenge-steamlit (pyproject.toml)
+pandocfilters==1.5.0
+ # via nbconvert
+parso==0.8.3
+ # via jedi
+pathspec==0.11.2
+ # via black
+pexpect==4.9.0
+ # via ipython
+pillow==10.1.0
+ # via streamlit
+platformdirs==4.0.0
+ # via
+ # black
+ # jupyter-core
+pluggy==1.3.0
+ # via pytest
+prometheus-client==0.19.0
+ # via jupyter-server
+prompt-toolkit==3.0.41
+ # via
+ # ipython
+ # jupyter-console
+protobuf==4.25.1
+ # via streamlit
+psutil==5.9.6
+ # via ipykernel
+ptyprocess==0.7.0
+ # via
+ # pexpect
+ # terminado
+pure-eval==0.2.2
+ # via stack-data
+pyarrow==14.0.1
+ # via streamlit
+pycparser==2.21
+ # via cffi
+pydantic==2.5.2
+ # via nfl-playoff-challenge-steamlit (pyproject.toml)
+pydantic-core==2.14.5
+ # via pydantic
+pydeck==0.8.1b0
+ # via streamlit
+pygments==2.17.2
+ # via
+ # ipython
+ # jupyter-console
+ # nbconvert
+ # qtconsole
+ # rich
+pytest==7.4.3
+ # via nfl-playoff-challenge-steamlit (pyproject.toml)
+python-dateutil==2.8.2
+ # via
+ # arrow
+ # jupyter-client
+ # pandas
+ # streamlit
+python-dotenv==1.0.0
+ # via streamlit-oauth
+python-json-logger==2.0.7
+ # via jupyter-events
+pytz==2023.3.post1
+ # via pandas
+pyyaml==6.0.1
+ # via jupyter-events
+pyzmq==25.1.1
+ # via
+ # ipykernel
+ # jupyter-client
+ # jupyter-console
+ # jupyter-server
+ # qtconsole
+qtconsole==5.5.1
+ # via jupyter
+qtpy==2.4.1
+ # via qtconsole
+referencing==0.31.1
+ # via
+ # jsonschema
+ # jsonschema-specifications
+ # jupyter-events
+requests==2.31.0
+ # via
+ # jupyterlab-server
+ # streamlit
+rfc3339-validator==0.1.4
+ # via
+ # jsonschema
+ # jupyter-events
+rfc3986-validator==0.1.1
+ # via
+ # jsonschema
+ # jupyter-events
+rich==13.7.0
+ # via streamlit
+rpds-py==0.13.2
+ # via
+ # jsonschema
+ # referencing
+ruff==0.1.6
+ # via nfl-playoff-challenge-steamlit (pyproject.toml)
+send2trash==1.8.2
+ # via jupyter-server
+six==1.16.0
+ # via
+ # asttokens
+ # bleach
+ # html5lib
+ # python-dateutil
+ # rfc3339-validator
+smmap==5.0.1
+ # via gitdb
+sniffio==1.3.0
+ # via
+ # anyio
+ # httpcore
+ # httpx
+soupsieve==2.5
+ # via beautifulsoup4
+stack-data==0.6.3
+ # via ipython
+streamlit==1.29.0
+ # via
+ # nfl-playoff-challenge-steamlit (pyproject.toml)
+ # streamlit-oauth
+streamlit-oauth==0.1.5
+ # via nfl-playoff-challenge-steamlit (pyproject.toml)
+tenacity==8.2.3
+ # via streamlit
+terminado==0.18.0
+ # via
+ # jupyter-server
+ # jupyter-server-terminals
+tinycss2==1.2.1
+ # via nbconvert
+toml==0.10.2
+ # via streamlit
+toolz==0.12.0
+ # via altair
+tornado==6.4
+ # via
+ # ipykernel
+ # jupyter-client
+ # jupyter-server
+ # jupyterlab
+ # notebook
+ # streamlit
+ # terminado
+traitlets==5.14.0
+ # via
+ # comm
+ # ipykernel
+ # ipython
+ # ipywidgets
+ # jupyter-client
+ # jupyter-console
+ # jupyter-core
+ # jupyter-events
+ # jupyter-server
+ # jupyterlab
+ # matplotlib-inline
+ # nbclient
+ # nbconvert
+ # nbformat
+ # qtconsole
+types-beautifulsoup4==4.12.0.7
+ # via nfl-playoff-challenge-steamlit (pyproject.toml)
+types-html5lib==1.1.11.15
+ # via types-beautifulsoup4
+types-python-dateutil==2.8.19.14
+ # via arrow
+types-pytz==2023.3.1.1
+ # via pandas-stubs
+types-requests==2.31.0.10
+ # via nfl-playoff-challenge-steamlit (pyproject.toml)
+typing-extensions==4.8.0
+ # via
+ # mypy
+ # pydantic
+ # pydantic-core
+ # streamlit
+tzdata==2023.3
+ # via pandas
+tzlocal==5.2
+ # via streamlit
+uri-template==1.3.0
+ # via jsonschema
+urllib3==2.1.0
+ # via
+ # requests
+ # types-requests
+validators==0.22.0
+ # via streamlit
+watchdog==3.0.0
+ # via streamlit
+wcwidth==0.2.12
+ # via prompt-toolkit
+webcolors==1.13
+ # via jsonschema
+webencodings==0.5.1
+ # via
+ # bleach
+ # html5lib
+ # tinycss2
+websocket-client==1.6.4
+ # via jupyter-server
+widgetsnbextension==4.0.9
+ # via ipywidgets
+zipp==3.17.0
+ # via importlib-metadata
diff --git a/docker-compose.yml b/docker-compose.yml
new file mode 100644
index 0000000000000000000000000000000000000000..9579b5cf3ccbee9802cb1f4ea8ee9bbadf5289d5
--- /dev/null
+++ b/docker-compose.yml
@@ -0,0 +1,11 @@
+services:
+ streamlit:
+ build:
+ dockerfile: ./Dockerfile
+ context: ./
+ target: service
+ ports:
+ - '8501:8501'
+ environment:
+ - USER_ID=1000
+ - GROUP_ID=1000
\ No newline at end of file
diff --git a/pyproject.toml b/pyproject.toml
new file mode 100644
index 0000000000000000000000000000000000000000..7418c9b8042283a845b21237232fddd599854ea3
--- /dev/null
+++ b/pyproject.toml
@@ -0,0 +1,48 @@
+[build-system]
+requires = ["setuptools"]
+
+[project]
+requires-python = ">=3.9"
+version = "1"
+name = "nfl-playoff-challenge-steamlit"
+dependencies = [
+ "streamlit",
+ "beautifulsoup4",
+ "duckdb",
+ "html5lib",
+ "lxml",
+ "numpy",
+ "pandas",
+ "pydantic",
+ "streamlit-oauth",
+ ]
+
+[project.optional-dependencies]
+dev = [
+ "black",
+ "jupyter",
+ "lxml-stubs",
+ "mypy",
+ "pytest",
+ "pandas-stubs",
+ "ruff",
+ "types-beautifulsoup4",
+ "types-requests",
+ ]
+
+[tool.black]
+line-length = 120
+target-version = ["py311"]
+
+[tool.ruff]
+line-length = 120
+src = ["src"]
+
+[tool.mypy]
+python_version = "3.11"
+
+[[tool.mypy.overrides]]
+module = [
+ 'streamlit_oauth'
+]
+ignore_missing_imports = true
\ No newline at end of file
diff --git a/regenerate_requirements.sh b/regenerate_requirements.sh
new file mode 100755
index 0000000000000000000000000000000000000000..03c027e948470a0ac2a54ba7e5fa7a20c498f706
--- /dev/null
+++ b/regenerate_requirements.sh
@@ -0,0 +1,19 @@
+
+#!/bin/bash
+
+set -ex
+
+ADDITIONAL_ARGS=$@
+
+pip-compile \
+ -o requirements.txt \
+ $ADDITIONAL_ARGS \
+ pyproject.toml
+
+pip-compile \
+ --extra=dev \
+ -o dev-requirements.txt \
+ $ADDITIONAL_ARGS \
+ pyproject.toml
+
+python update_streamlit_version.py
diff --git a/requirements.txt b/requirements.txt
new file mode 100644
index 0000000000000000000000000000000000000000..e86c119eec6c859023293c85fc13039858e413f0
--- /dev/null
+++ b/requirements.txt
@@ -0,0 +1,167 @@
+#
+# This file is autogenerated by pip-compile with Python 3.11
+# by the following command:
+#
+# pip-compile --output-file=requirements.txt pyproject.toml
+#
+altair==5.2.0
+ # via streamlit
+annotated-types==0.6.0
+ # via pydantic
+anyio==4.1.0
+ # via httpcore
+attrs==23.1.0
+ # via
+ # jsonschema
+ # referencing
+beautifulsoup4==4.12.2
+ # via nfl-playoff-challenge-steamlit (pyproject.toml)
+blinker==1.7.0
+ # via streamlit
+cachetools==5.3.2
+ # via streamlit
+certifi==2023.11.17
+ # via
+ # httpcore
+ # httpx
+ # requests
+charset-normalizer==3.3.2
+ # via requests
+click==8.1.7
+ # via streamlit
+duckdb==0.9.2
+ # via nfl-playoff-challenge-steamlit (pyproject.toml)
+gitdb==4.0.11
+ # via gitpython
+gitpython==3.1.40
+ # via streamlit
+h11==0.14.0
+ # via httpcore
+html5lib==1.1
+ # via nfl-playoff-challenge-steamlit (pyproject.toml)
+httpcore==0.17.3
+ # via httpx
+httpx==0.24.1
+ # via httpx-oauth
+httpx-oauth==0.13.0
+ # via streamlit-oauth
+idna==3.6
+ # via
+ # anyio
+ # httpx
+ # requests
+importlib-metadata==6.9.0
+ # via streamlit
+jinja2==3.1.2
+ # via
+ # altair
+ # pydeck
+jsonschema==4.20.0
+ # via altair
+jsonschema-specifications==2023.11.2
+ # via jsonschema
+lxml==4.9.3
+ # via nfl-playoff-challenge-steamlit (pyproject.toml)
+markdown-it-py==3.0.0
+ # via rich
+markupsafe==2.1.3
+ # via jinja2
+mdurl==0.1.2
+ # via markdown-it-py
+numpy==1.26.2
+ # via
+ # nfl-playoff-challenge-steamlit (pyproject.toml)
+ # altair
+ # pandas
+ # pyarrow
+ # pydeck
+ # streamlit
+packaging==23.2
+ # via
+ # altair
+ # streamlit
+pandas==2.1.3
+ # via
+ # nfl-playoff-challenge-steamlit (pyproject.toml)
+ # altair
+ # streamlit
+pillow==10.1.0
+ # via streamlit
+protobuf==4.25.1
+ # via streamlit
+pyarrow==14.0.1
+ # via streamlit
+pydantic==2.5.2
+ # via nfl-playoff-challenge-steamlit (pyproject.toml)
+pydantic-core==2.14.5
+ # via pydantic
+pydeck==0.8.1b0
+ # via streamlit
+pygments==2.17.2
+ # via rich
+python-dateutil==2.8.2
+ # via
+ # pandas
+ # streamlit
+python-dotenv==1.0.0
+ # via streamlit-oauth
+pytz==2023.3.post1
+ # via pandas
+referencing==0.31.1
+ # via
+ # jsonschema
+ # jsonschema-specifications
+requests==2.31.0
+ # via streamlit
+rich==13.7.0
+ # via streamlit
+rpds-py==0.13.2
+ # via
+ # jsonschema
+ # referencing
+six==1.16.0
+ # via
+ # html5lib
+ # python-dateutil
+smmap==5.0.1
+ # via gitdb
+sniffio==1.3.0
+ # via
+ # anyio
+ # httpcore
+ # httpx
+soupsieve==2.5
+ # via beautifulsoup4
+streamlit==1.29.0
+ # via
+ # nfl-playoff-challenge-steamlit (pyproject.toml)
+ # streamlit-oauth
+streamlit-oauth==0.1.5
+ # via nfl-playoff-challenge-steamlit (pyproject.toml)
+tenacity==8.2.3
+ # via streamlit
+toml==0.10.2
+ # via streamlit
+toolz==0.12.0
+ # via altair
+tornado==6.4
+ # via streamlit
+typing-extensions==4.8.0
+ # via
+ # pydantic
+ # pydantic-core
+ # streamlit
+tzdata==2023.3
+ # via pandas
+tzlocal==5.2
+ # via streamlit
+urllib3==2.1.0
+ # via requests
+validators==0.22.0
+ # via streamlit
+watchdog==3.0.0
+ # via streamlit
+webencodings==0.5.1
+ # via html5lib
+zipp==3.17.0
+ # via importlib-metadata
diff --git a/src/.streamlit/config.toml b/src/.streamlit/config.toml
new file mode 100644
index 0000000000000000000000000000000000000000..c9b8c45f3578a756e8c2bd7978828c781cdf29fe
--- /dev/null
+++ b/src/.streamlit/config.toml
@@ -0,0 +1,2 @@
+[theme]
+base="dark"
\ No newline at end of file
diff --git a/src/Home.py b/src/Home.py
new file mode 100644
index 0000000000000000000000000000000000000000..edb6745d66963fa4fa19d1b2738fd4d0f6559591
--- /dev/null
+++ b/src/Home.py
@@ -0,0 +1,27 @@
+import streamlit as st
+
+from config import DEFAULT_ICON, LEAGUE_NAME
+from login_component import get_authorization_button
+from page_selector import remove_seasonal_pages
+
+
+def get_app():
+ keeper_title = f"{LEAGUE_NAME}"
+ st.set_page_config(page_title=keeper_title, page_icon=DEFAULT_ICON)
+ get_authorization_button()
+ st.markdown(
+ f"""
+ Welcome {LEAGUE_NAME}!
+
+ Navigate between pages using the left sidebar.
+
+ If the sidebar is not visible, click the **>** in the upper left corner to open.
+
+ """
+ )
+
+ remove_seasonal_pages()
+
+
+if __name__ == "__main__":
+ get_app()
diff --git a/src/config.py b/src/config.py
new file mode 100644
index 0000000000000000000000000000000000000000..6f7537fedfb211a8e63ac0cc308172bafa7bcbfe
--- /dev/null
+++ b/src/config.py
@@ -0,0 +1,3 @@
+LEAGUE_NAME = "LOFG"
+DEFAULT_ICON = "š"
+LEAGUE_NUMBER_TEAMS = 12
diff --git a/src/domain/__init__.py b/src/domain/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/src/domain/conferences.py b/src/domain/conferences.py
new file mode 100644
index 0000000000000000000000000000000000000000..37ad30515366643624dfff2eaea3d48513c13a71
--- /dev/null
+++ b/src/domain/conferences.py
@@ -0,0 +1,17 @@
+from dataclasses import dataclass
+from typing import List
+
+
+@dataclass
+class NFLConference:
+ name: str
+ short_name: str
+
+ def __post_init__(self):
+ ALL_CONFERENCES.append(self)
+
+
+ALL_CONFERENCES: List[NFLConference] = []
+
+NFC = NFLConference(name="National Football Conference", short_name="NFC")
+AFC = NFLConference(name="American Football Conference", short_name="AFC")
diff --git a/src/domain/divisions.py b/src/domain/divisions.py
new file mode 100644
index 0000000000000000000000000000000000000000..3db7c027b0c8c3f8f2f96a8546c09b76df28afb8
--- /dev/null
+++ b/src/domain/divisions.py
@@ -0,0 +1,24 @@
+from dataclasses import dataclass
+from typing import List
+from domain import conferences
+
+
+@dataclass
+class NFLDivision:
+ name: str
+ conference: conferences.NFLConference
+
+ def __post_init__(self):
+ ALL_DIVISIONS.append(self)
+
+
+ALL_DIVISIONS: List[NFLDivision] = []
+
+NFCWest = NFLDivision(name="NFC West", conference=conferences.NFC)
+NFCNorth = NFLDivision(name="NFC North", conference=conferences.NFC)
+NFCSouth = NFLDivision(name="NFC South", conference=conferences.NFC)
+NFCEast = NFLDivision(name="NFC East", conference=conferences.NFC)
+AFCWest = NFLDivision(name="AFC West", conference=conferences.AFC)
+AFCNorth = NFLDivision(name="AFC North", conference=conferences.AFC)
+AFCSouth = NFLDivision(name="AFC South", conference=conferences.AFC)
+AFCEast = NFLDivision(name="AFC East", conference=conferences.AFC)
diff --git a/src/domain/teams.py b/src/domain/teams.py
new file mode 100644
index 0000000000000000000000000000000000000000..5df64f8f5be7e12ea38091902836f39fd871ac52
--- /dev/null
+++ b/src/domain/teams.py
@@ -0,0 +1,286 @@
+from dataclasses import dataclass
+from urllib.parse import urljoin
+from typing import List
+from domain.conferences import NFLConference
+from domain import divisions
+
+
+@dataclass
+class NFLTeam:
+ team_name: str
+ team_short_name: str
+ city: str
+ division: divisions.NFLDivision
+ footballguys_short_name: str = ""
+ url: str = ""
+ injury_report_suffix: str = "/team/injury-report/"
+
+ def __post_init__(self):
+ self.footballguys_short_name = (
+ self.team_short_name if self.footballguys_short_name == "" else self.footballguys_short_name
+ )
+ self.conference: NFLConference = self.division.conference
+ self.injury_report_url = urljoin(self.url, self.injury_report_suffix)
+ self.team_full_name = " ".join([self.city, self.team_name])
+
+ ALL_TEAMS.append(self)
+
+
+ALL_TEAMS: List[NFLTeam] = []
+
+
+arizona_cardinals = NFLTeam(
+ team_name="Cardinals",
+ team_short_name="ARI",
+ city="Arizona",
+ division=divisions.NFCWest,
+ url="https://www.azcardinals.com/",
+)
+
+atlanta_falcons = NFLTeam(
+ team_name="Falcons",
+ team_short_name="ATL",
+ city="Atlanta",
+ division=divisions.NFCSouth,
+ url="https://www.atlantafalcons.com/",
+)
+
+baltimore_ravens = NFLTeam(
+ team_name="Ravens",
+ team_short_name="BAL",
+ city="Baltimore",
+ division=divisions.AFCNorth,
+ url="https://www.baltimoreravens.com/",
+)
+
+buffalo_bills = NFLTeam(
+ team_name="Bills",
+ team_short_name="BUF",
+ city="Buffalo",
+ division=divisions.AFCEast,
+ url="https://www.buffalobills.com/",
+)
+
+carolina_panthers = NFLTeam(
+ team_name="Panthers",
+ team_short_name="CAR",
+ city="Carolina",
+ division=divisions.NFCSouth,
+ url="https://www.panthers.com/",
+)
+
+chicago_bears = NFLTeam(
+ team_name="Bears",
+ team_short_name="CHI",
+ city="Chicago",
+ division=divisions.NFCNorth,
+ url="https://www.chicagobears.com/",
+)
+
+cincinnati_bengals = NFLTeam(
+ team_name="Bengals",
+ team_short_name="CIN",
+ city="Cincinnati",
+ division=divisions.AFCNorth,
+ url="https://www.bengals.com/",
+)
+
+cleveland_browns = NFLTeam(
+ team_name="Browns",
+ team_short_name="CLE",
+ city="Cleveland",
+ division=divisions.AFCNorth,
+ url="https://www.clevelandbrowns.com/",
+)
+
+dallas_cowboys = NFLTeam(
+ team_name="Cowboys",
+ team_short_name="DAL",
+ city="Dallas",
+ division=divisions.NFCEast,
+ url="https://www.dallascowboys.com/",
+)
+
+denver_broncos = NFLTeam(
+ team_name="Broncos",
+ team_short_name="DEN",
+ city="Denver",
+ division=divisions.AFCWest,
+ url="https://www.denverbroncos.com/",
+)
+
+detroit_lions = NFLTeam(
+ team_name="Lions",
+ team_short_name="DET",
+ city="Detroit",
+ division=divisions.NFCNorth,
+ url="https://www.detroitlions.com/",
+)
+
+green_bay_packers = NFLTeam(
+ team_name="Packers",
+ team_short_name="GB",
+ city="Green Bay",
+ division=divisions.NFCNorth,
+ url="https://www.packers.com/",
+)
+
+houston_texans = NFLTeam(
+ team_name="Texans",
+ team_short_name="HOU",
+ city="Houston",
+ division=divisions.AFCSouth,
+ url="https://www.houstontexans.com/",
+)
+
+indianapolis_colts = NFLTeam(
+ city="Indianapolis",
+ team_name="Colts",
+ team_short_name="IND",
+ division=divisions.AFCSouth,
+ url="https://www.colts.com/",
+)
+
+jacksonville_jaguars = NFLTeam(
+ city="Jacksonville",
+ team_name="Jaguars",
+ team_short_name="JAX",
+ division=divisions.AFCSouth,
+ url="https://www.jaguars.com/",
+)
+
+kansas_city_chiefs = NFLTeam(
+ city="Kansas City",
+ team_name="Chiefs",
+ team_short_name="KC",
+ division=divisions.AFCWest,
+ url="https://www.chiefs.com/",
+)
+
+las_vegas_raiders = NFLTeam(
+ city="Las Vegas",
+ team_name="Raiders",
+ team_short_name="LV",
+ division=divisions.AFCWest,
+ url="https://www.raiders.com/",
+)
+
+los_angeles_chargers = NFLTeam(
+ city="Los Angeles",
+ team_name="Chargers",
+ team_short_name="LAC",
+ division=divisions.AFCWest,
+ url="https://www.chargers.com/",
+)
+
+los_angeles_rams = NFLTeam(
+ city="Los Angeles",
+ team_name="Rams",
+ team_short_name="LAR",
+ division=divisions.NFCWest,
+ url="https://www.therams.com/",
+)
+
+miami_dolphins = NFLTeam(
+ city="Miami",
+ team_name="Dolphins",
+ team_short_name="MIA",
+ division=divisions.AFCEast,
+ url="https://www.miamidolphins.com/",
+)
+
+minnesota_vikings = NFLTeam(
+ city="Minnesota",
+ team_name="Vikings",
+ team_short_name="MIN",
+ division=divisions.NFCNorth,
+ url="https://www.vikings.com/",
+)
+
+new_england_patriots = NFLTeam(
+ city="New England",
+ team_name="Patriots",
+ team_short_name="NE",
+ division=divisions.AFCEast,
+ url="https://www.patriots.com/",
+)
+
+new_orleans_saints = NFLTeam(
+ city="New Orleans",
+ team_name="Saints",
+ team_short_name="NO",
+ division=divisions.NFCSouth,
+ url="https://www.neworleanssaints.com/",
+)
+
+new_york_giants = NFLTeam(
+ city="New York",
+ team_name="Giants",
+ team_short_name="NYG",
+ division=divisions.NFCEast,
+ url="https://www.giants.com/",
+)
+
+new_york_jets = NFLTeam(
+ city="New York",
+ team_name="Jets",
+ team_short_name="NYJ",
+ division=divisions.AFCEast,
+ url="https://www.newyorkjets.com/",
+)
+
+philadelphia_eagles = NFLTeam(
+ city="Philadelphia",
+ team_name="Eagles",
+ team_short_name="PHI",
+ division=divisions.NFCEast,
+ url="https://www.philadelphiaeagles.com/",
+)
+
+pittsburgh_steelers = NFLTeam(
+ city="Pittsburgh",
+ team_name="Steelers",
+ team_short_name="PIT",
+ division=divisions.AFCNorth,
+ url="https://www.steelers.com/",
+)
+
+san_francisco_49ers = NFLTeam(
+ city="San Francisco",
+ team_name="49ers",
+ team_short_name="SF",
+ division=divisions.NFCWest,
+ url="https://www.49ers.com/",
+)
+
+seattle_seahawks = NFLTeam(
+ city="Seattle",
+ team_name="Seahawks",
+ team_short_name="SEA",
+ division=divisions.NFCWest,
+ url="https://www.seahawks.com/",
+)
+
+tampa_bay_buccaneers = NFLTeam(
+ city="Tampa Bay",
+ team_name="Buccaneers",
+ team_short_name="TB",
+ division=divisions.NFCSouth,
+ url="https://www.buccaneers.com/",
+)
+
+tennessee_titans = NFLTeam(
+ city="Tennessee",
+ team_name="Titans",
+ team_short_name="TEN",
+ division=divisions.AFCSouth,
+ url="https://www.tennesseetitans.com/",
+)
+
+washington_football_team = NFLTeam(
+ city="Washington",
+ team_name="Commanders",
+ team_short_name="WAS",
+ division=divisions.NFCEast,
+ url="https://www.commanders.com/",
+)
diff --git a/src/login_component.py b/src/login_component.py
new file mode 100644
index 0000000000000000000000000000000000000000..cd3d750f42a76a951e033cbf58860119bf5d3961
--- /dev/null
+++ b/src/login_component.py
@@ -0,0 +1,53 @@
+import streamlit as st
+
+from streamlit_oauth import OAuth2Component
+import os
+
+# # Load environment variables from .env file
+# from dotenv import load_dotenv
+# load_dotenv()
+
+# Set environment variables
+AUTHORIZE_URL = os.environ.get("AUTHORIZE_URL")
+TOKEN_URL = os.environ.get("TOKEN_URL")
+REFRESH_TOKEN_URL = os.environ.get("REFRESH_TOKEN_URL")
+REVOKE_TOKEN_URL = os.environ.get("REVOKE_TOKEN_URL")
+CLIENT_ID = os.environ.get("CLIENT_ID")
+CLIENT_SECRET = os.environ.get("CLIENT_SECRET")
+REDIRECT_URI = os.environ.get("REDIRECT_URI")
+SCOPE = os.environ.get("SCOPE")
+
+ENABLE_LOGIN = os.environ.get("ENABLE_LOGIN", False)
+
+# Create OAuth2Component instance
+oauth2 = OAuth2Component(CLIENT_ID, CLIENT_SECRET, AUTHORIZE_URL, TOKEN_URL, REFRESH_TOKEN_URL, REVOKE_TOKEN_URL)
+
+
+def is_token_in_session() -> bool:
+ return "token" in st.session_state
+
+
+def get_authorization_button():
+ if not ENABLE_LOGIN:
+ return
+ # Check if token exists in session state
+ if not is_token_in_session():
+ # If not, show authorize button
+ result = oauth2.authorize_button("Login", REDIRECT_URI, SCOPE)
+ if result and "token" in result:
+ # If authorization successful, save token in session state
+ st.session_state.token = result.get("token")
+ st.rerun()
+ else:
+ # # If token exists in session state, allow logout
+ st.session_state["token"]
+ if st.button("Logout"):
+ del st.session_state.token
+ st.rerun()
+ # # If token exists in session state, show the token
+ # token = st.session_state["token"]
+ # if st.button("Refresh Token"):
+ # # If refresh token button is clicked, refresh the token
+ # token = oauth2.refresh_token(token)
+ # st.session_state.token = token
+ # st.rerun()
diff --git a/src/maximum_roster_strategy/__init__.py b/src/maximum_roster_strategy/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/src/maximum_roster_strategy/data_loader.py b/src/maximum_roster_strategy/data_loader.py
new file mode 100644
index 0000000000000000000000000000000000000000..96f64907af827738330155754aac0888ab1e237d
--- /dev/null
+++ b/src/maximum_roster_strategy/data_loader.py
@@ -0,0 +1,19 @@
+import os
+import pandas as pd
+
+
+MRS_SHEET_ID = os.environ.get("MRS_SHEET_ID")
+
+
+def get_google_sheet_data() -> pd.DataFrame:
+ return get_sheet_data(0)
+
+
+def get_sheet_data(sheet_id: int = 0):
+ sheet_url = f"https://docs.google.com/spreadsheet/ccc?key={MRS_SHEET_ID}&output=csv&gid={sheet_id}"
+ df = pd.read_csv(sheet_url)
+ return df
+
+
+def get_timeslot_labels() -> pd.DataFrame:
+ return get_sheet_data(1875906423)
diff --git a/src/page_selector.py b/src/page_selector.py
new file mode 100644
index 0000000000000000000000000000000000000000..e6faf67fef6777ad8788f7d8692b2c71286d5a53
--- /dev/null
+++ b/src/page_selector.py
@@ -0,0 +1,40 @@
+from streamlit.source_util import _on_pages_changed, get_pages
+
+# Adapted from https://discuss.streamlit.io/t/how-to-hide-all-pages-before-login/32508
+# Note this code is intended to remove pages at app load time, not based on login
+
+SEASON_MODE = "season"
+OFFSEASON_MODE = "offseason"
+
+CURRENT_MODE = SEASON_MODE
+
+MAIN_PAGE_FILE = "Home.py"
+
+MODE_PAGE_EXCLUSION_MAP = {
+ SEASON_MODE: [
+ "Keepers",
+ "ECR",
+ "League_Simulation",
+ "Keeper_Rules",
+ "Maximum_Roster_Strategy",
+ ],
+ OFFSEASON_MODE: [
+ "Practice_Reports",
+ "League_Simulation",
+ "Maximum_Roster_Strategy",
+ ],
+}
+
+
+def remove_seasonal_pages():
+ all_pages = get_pages(MAIN_PAGE_FILE)
+ pages_to_remove = MODE_PAGE_EXCLUSION_MAP[CURRENT_MODE]
+
+ page_keys_to_remove = []
+ for k, v in all_pages.items():
+ if v["page_name"] in pages_to_remove:
+ page_keys_to_remove.append(k)
+ for k_remove in page_keys_to_remove:
+ del all_pages[k_remove]
+
+ _on_pages_changed.send()
diff --git a/src/pages/10_Player_News.py b/src/pages/10_Player_News.py
new file mode 100644
index 0000000000000000000000000000000000000000..893b4dcbc2cc1e58b92e0d72a676eec7cd265012
--- /dev/null
+++ b/src/pages/10_Player_News.py
@@ -0,0 +1,53 @@
+import datetime
+import streamlit as st
+
+from config import DEFAULT_ICON
+from shared_page import common_page_config
+
+from queries.nbcsports.player_news import get_player_news_window_hours
+
+
+@st.cache_data(ttl=60 * 60 * 24)
+def load_data():
+ data = get_player_news_window_hours(24)
+ teams_list = sorted(filter(None, data.Team.unique()))
+ position_list = data.Position.unique()
+ data_load_time_str = datetime.datetime.utcnow().strftime("%m/%d/%Y %I:%M %p")
+ return data, teams_list, position_list, data_load_time_str
+
+
+def get_page():
+ page_title = "Player News - Last 24 Hours"
+ st.set_page_config(page_title=page_title, page_icon=DEFAULT_ICON, layout="wide")
+ common_page_config()
+ st.title(page_title)
+ if st.button("Refresh Data"):
+ st.cache_data.clear()
+ data, teams_list, position_list, data_load_time_str = load_data()
+ st.write(f"Data loaded as of: {data_load_time_str} UTC")
+
+ teams_selected = st.multiselect("Team:", teams_list, placeholder="Select a team to filter") or teams_list
+
+ with st.container():
+ filtered_data = data[(data.Team.isin(teams_selected))]
+ st.dataframe(
+ filtered_data,
+ hide_index=True,
+ height=35 * (len(filtered_data) + 1) + 12,
+ use_container_width=True,
+ column_order=[
+ "Date/Time",
+ "Name",
+ "Headline",
+ "Team",
+ "Position",
+ ],
+ column_config={
+ "Date/Time": st.column_config.DatetimeColumn(format="MM-DD HH:mm"),
+ "Team": st.column_config.TextColumn(width="small"),
+ },
+ )
+
+
+if __name__ == "__main__":
+ get_page()
diff --git a/src/pages/11_Next_Gen_Stats.py b/src/pages/11_Next_Gen_Stats.py
new file mode 100644
index 0000000000000000000000000000000000000000..4f7981473d2b3bf24e0ea76edb41ca92d4d2156b
--- /dev/null
+++ b/src/pages/11_Next_Gen_Stats.py
@@ -0,0 +1,70 @@
+import streamlit as st
+
+from config import DEFAULT_ICON
+from shared_page import common_page_config
+
+from streamlit_filter import get_multiselect_for_df_column
+from queries.nflverse.github_data import get_nextgen_stats, get_current_tables, SEASON
+
+
+hide_columns = [
+ "season",
+ "season_type",
+ "player_gsis_id",
+ "player_first_name",
+ "player_last_name",
+ "player_jersey_number",
+ "player_short_name",
+]
+
+
+def get_page():
+ page_title = f"Next Gen Stats - {SEASON}"
+ st.set_page_config(page_title=page_title, page_icon=DEFAULT_ICON, layout="wide")
+ common_page_config()
+ st.title(page_title)
+
+ stat_category = st.selectbox("Stat Category", ["Passing", "Rushing", "Receiving"])
+ ngs_table_name = f"nextgen_stats_ngs_{stat_category.lower()}"
+ current_tables_list = get_current_tables()
+
+ if ngs_table_name not in current_tables_list:
+ st.write("Data not loaded.")
+ st.write("Check loaded data [here](./Load_Data)")
+ return
+ data = get_nextgen_stats(SEASON, stat_category)
+
+ season_or_week = st.selectbox("Season or Weekly Stats", ["Season", "Week"])
+ if season_or_week == "Season":
+ data = data[data["week"] == 0]
+ data.drop(columns=["week"], inplace=True)
+ else:
+ data = data[data["week"] > 0]
+ week_selection = st.slider(
+ "Filter Week Range:",
+ min_value=data["week"].min(),
+ max_value=data["week"].max(),
+ value=(data["week"].min(), data["week"].max()),
+ step=1,
+ )
+ data = data[data["week"].between(*week_selection)]
+
+ data.drop(columns=hide_columns, inplace=True)
+ positions_selected = get_multiselect_for_df_column(data, "player_position")
+ teams_selected = get_multiselect_for_df_column(data, "team_abbr")
+
+ data = data[(data["player_position"].isin(positions_selected) & data["team_abbr"].isin(teams_selected))]
+
+ with st.container():
+ filtered_data = data
+ st.dataframe(
+ filtered_data,
+ hide_index=True,
+ # height=35 * (len(filtered_data) + 1) + 12,
+ use_container_width=False,
+ column_config={},
+ )
+
+
+if __name__ == "__main__":
+ get_page()
diff --git a/src/pages/1_Keepers.py b/src/pages/1_Keepers.py
new file mode 100644
index 0000000000000000000000000000000000000000..613503b9afdef7d49f022c288a19f1c2bbff7c95
--- /dev/null
+++ b/src/pages/1_Keepers.py
@@ -0,0 +1,178 @@
+import os
+import numpy as np
+import pandas as pd
+import streamlit as st
+
+from config import DEFAULT_ICON, LEAGUE_NAME, LEAGUE_NUMBER_TEAMS
+from shared_page import common_page_config
+from streamlit_filter import filter_dataframe
+
+
+KEEPER_DATA_URL = "../../tests/mocks/2023_keepers.csv"
+HEADSHOT_DATA_URL = "../../tests/mocks/2023_player_headshots.csv"
+
+
+def load_player_ids() -> pd.DataFrame:
+ df = pd.read_csv(r"https://raw.githubusercontent.com/dynastyprocess/data/master/files/db_playerids.csv")
+ df["merge_id"] = df["yahoo_id"].combine_first(df["stats_id"])
+ return df
+
+
+def load_adp() -> pd.DataFrame:
+ df = pd.read_csv(r"https://raw.githubusercontent.com/dynastyprocess/data/master/files/db_fpecr_latest.csv")
+ df = df.loc[
+ df.fp_page == "/nfl/rankings/ppr-superflex-cheatsheets.php",
+ [
+ "yahoo_id",
+ "ecr",
+ "sd",
+ ],
+ ]
+ return df
+
+
+def convert_ecr_to_round_val(ecr_float: float, round_offset: float = 1.0, pick_offset: float = -1.0) -> float:
+ # As a float, store pick 1 of round 1 as 1.0
+ return round_offset + (ecr_float + pick_offset) / LEAGUE_NUMBER_TEAMS
+
+
+def add_opinionated_keeper_value(df: pd.DataFrame):
+ # Manual Hack for overranking of backup QBs
+ df.loc[
+ df["name"].isin(
+ [
+ "Teddy Bridgewater",
+ "Davis Mills",
+ "Andy Dalton",
+ "Tyler Huntley",
+ "Mike White",
+ "Gardner Minshew",
+ "Colt McCoy",
+ "Sam Darnold",
+ "Carson Wentz",
+ "Trey Lance",
+ "Taylor Heinicke",
+ ]
+ ),
+ ["ecr"],
+ ] = np.nan
+
+ df["ecr"] = df["ecr"].apply(convert_ecr_to_round_val)
+ # Convert sd without offset to show as pure pick diff
+ df["sd"] = df["sd"].apply(lambda x: convert_ecr_to_round_val(x, 0, 0))
+ # assumes midround keeper
+ # fill -99 for players that are not ranked in ecr
+ df["value_keeper"] = (df["keeper_cost"] + 0.5 - df["ecr"]).fillna(-99)
+
+
+@st.cache_data(ttl=60 * 60 * 24)
+def load_data():
+ data = pd.read_csv(os.path.join(os.path.dirname(__file__), KEEPER_DATA_URL), index_col=0)
+ # Hack to get position, replace with better position from yahoo api in future
+ data["position"] = data["eligible_positions"].apply(lambda x: eval(x)[0])
+ data.columns = data.columns.str.lower()
+ teams_list = sorted(list(data["team_name"].unique()))
+
+ # Merge player ids
+ df_player_ids = load_player_ids()
+ data = data.merge(df_player_ids, how="left", left_on="player_id", right_on="merge_id", suffixes=("", "_ids"))
+
+ # Merge ADP
+ df_adp = load_adp()
+ data = data.merge(df_adp, how="left", left_on="player_id", right_on="yahoo_id", suffixes=("", "_adp"))
+ add_opinionated_keeper_value(data)
+ return data, teams_list
+
+
+def filtered_keeper_dataframe(data: pd.DataFrame, teams_list: list[str]):
+ teams_selected = st.multiselect("Team:", teams_list, placeholder="Select a user team to filter")
+ teams_filter = data["team_name"].isin(teams_selected) if teams_selected else data["team_name"].isin(teams_list)
+
+ eligible_options = [True, False]
+ is_eligible_selected = st.multiselect(
+ "Keeper Eligible:", eligible_options, placeholder="Select True to filter eligible only"
+ )
+ eligible_filter = (
+ data["eligible"].isin(is_eligible_selected) if is_eligible_selected else data["eligible"].isin(eligible_options)
+ )
+ is_advanced = st.checkbox("Show Advanced View")
+
+ id_cols = [
+ "team_name",
+ "headshot_url",
+ "name",
+ ]
+
+ id_cols_advanced = [
+ "team",
+ "position",
+ ]
+
+ cost_cols = [
+ "keeper_cost",
+ "eligible",
+ ]
+
+ cost_cols_advanced = [
+ "years_eligible",
+ ]
+
+ adp_cols: list[str] = []
+
+ adp_cols_advanced = [
+ "ecr",
+ "value_keeper",
+ ]
+
+ if is_advanced:
+ show_columns = id_cols + id_cols_advanced + cost_cols + cost_cols_advanced + adp_cols + adp_cols_advanced
+ else:
+ show_columns = id_cols + cost_cols + adp_cols
+
+ data_with_filters_applied = data.loc[teams_filter & eligible_filter, show_columns]
+
+ filtered_data = filter_dataframe(data_with_filters_applied)
+ st.dataframe(
+ filtered_data,
+ hide_index=True,
+ height=35 * (len(filtered_data) + 1) + 12,
+ use_container_width=True,
+ column_config={
+ "team_name": st.column_config.TextColumn(label="League Team", help="Name of fantasy League team."),
+ "headshot_url": st.column_config.ImageColumn(label="", help="Player image"),
+ "name": st.column_config.TextColumn(label="Name", help="Player's name"),
+ "team": st.column_config.TextColumn(label="NFL Team"),
+ "position": st.column_config.TextColumn(label="Position", help="Player's position"),
+ "keeper_cost": st.column_config.NumberColumn(
+ label="Keeper Cost", help="Draft Round Cost to keep player. See Rules for details."
+ ),
+ "eligible": st.column_config.CheckboxColumn(label="Eligible", help="Is player eligible to be keeper?"),
+ "years_eligible": st.column_config.NumberColumn(
+ label="Years Eligible",
+ help="Number of further consecutive seasons player can be kept (subject to maximum of 2)",
+ ),
+ "ecr": st.column_config.NumberColumn(
+ label="ECR",
+ help="Player's average draft round.pick Expert Consensus Rank (ECR) for PPR - Superflex League",
+ ),
+ "value_keeper": st.column_config.NumberColumn(
+ label="Value Keeper",
+ help="Approx. number of draft rounds of keeper value vs ECR PPR - Superflex League",
+ ),
+ },
+ )
+
+
+def get_keeper_app():
+ keeper_title = f"{LEAGUE_NAME} Keeper Options"
+ st.set_page_config(page_title=keeper_title, page_icon=DEFAULT_ICON, layout="wide")
+ common_page_config()
+ st.title(keeper_title)
+ data, teams_list = load_data()
+
+ with st.container():
+ filtered_keeper_dataframe(data, teams_list)
+
+
+if __name__ == "__main__":
+ get_keeper_app()
diff --git a/src/pages/3_ECR.py b/src/pages/3_ECR.py
new file mode 100644
index 0000000000000000000000000000000000000000..4feec5f4c009a70381cdd97135ba32c1dad2551f
--- /dev/null
+++ b/src/pages/3_ECR.py
@@ -0,0 +1,88 @@
+import os
+import pandas as pd
+import streamlit as st
+
+from config import DEFAULT_ICON
+from shared_page import common_page_config
+from streamlit_filter import filter_dataframe
+
+
+KEEPER_DATA_URL = "../../tests/mocks/2023_keepers.csv"
+HEADSHOT_DATA_URL = "../../tests/mocks/2023_player_headshots.csv"
+
+
+def load_adp() -> pd.DataFrame:
+ df = pd.read_csv(r"https://raw.githubusercontent.com/dynastyprocess/data/master/files/db_fpecr_latest.csv")
+ df["ranking_type"] = df["fp_page"].apply(lambda x: os.path.split(x)[-1].replace(".php", ""))
+ return df
+
+
+@st.cache_data(ttl=60 * 60 * 24)
+def load_data():
+ # Merge ADP
+ data = load_adp()
+ ranking_type_list = sorted(list(data.ranking_type.unique()))
+ return data, ranking_type_list
+
+
+def filtered_ecr_dataframe(data: pd.DataFrame, ranking_type_list: list[str]):
+ default_ix = ranking_type_list.index("ppr-superflex-cheatsheets")
+ ranking_type_selected = st.selectbox("ECR Format:", ranking_type_list, index=default_ix)
+ ranking_type_filter = data["ranking_type"] == ranking_type_selected
+
+ is_advanced = st.checkbox("Show Advanced View")
+
+ id_cols = [
+ # "player_square_image_url",
+ "player",
+ "pos",
+ "team",
+ ]
+
+ id_cols_advanced = [
+ "bye",
+ "player_owned_yahoo",
+ ]
+
+ adp_cols: list[str] = [
+ "ecr",
+ ]
+
+ adp_cols_advanced = ["sd", "best", "worst"]
+
+ if is_advanced:
+ show_columns = id_cols + id_cols_advanced + adp_cols + adp_cols_advanced
+ else:
+ show_columns = id_cols + adp_cols
+
+ data_filtered_by_ranking_type = data.loc[ranking_type_filter]
+ latest_scrape_date = data_filtered_by_ranking_type.scrape_date.max()
+ st.write(f"Scraped data as of: {latest_scrape_date}")
+
+ filtered_data = filter_dataframe(data.loc[ranking_type_filter, show_columns])
+ st.dataframe(
+ filtered_data,
+ hide_index=True,
+ height=35 * (len(filtered_data) + 1) + 12,
+ use_container_width=True,
+ column_config={
+ # "player_square_image_url": st.column_config.ImageColumn(label="", help="Player image"),
+ },
+ )
+
+ st.write("Source: https://github.com/dynastyprocess/data")
+
+
+def get_keeper_app():
+ keeper_title = "Expert Consensus Rankings"
+ st.set_page_config(page_title=keeper_title, page_icon=DEFAULT_ICON, layout="wide")
+ common_page_config()
+ st.title(keeper_title)
+ data, ecr_type_list = load_data()
+
+ with st.container():
+ filtered_ecr_dataframe(data, ecr_type_list)
+
+
+if __name__ == "__main__":
+ get_keeper_app()
diff --git a/src/pages/4_Practice_Reports.py b/src/pages/4_Practice_Reports.py
new file mode 100644
index 0000000000000000000000000000000000000000..14a1fe495e3dddd00b6201d3e324399ffe29c628
--- /dev/null
+++ b/src/pages/4_Practice_Reports.py
@@ -0,0 +1,66 @@
+import datetime
+import streamlit as st
+
+from config import DEFAULT_ICON
+from shared_page import common_page_config
+
+from queries.nfl_teams.practice_reports import scrape_all_team_injury_report, CURRENT_SEASON, CURRENT_WEEK
+from streamlit_filter import filter_dataframe
+
+
+@st.cache_data(ttl=60 * 60 * 1)
+def load_data():
+ data = scrape_all_team_injury_report()
+ teams_list = list(data.Team.unique())
+ position_list = list(data.Position.unique())
+ status_list = list(data.game_status.unique())
+ last_practice_day_list = list(data["Last Practice Day"].unique())
+ data_load_time_str = datetime.datetime.utcnow().strftime("%m/%d/%Y %I:%M %p")
+ return data, teams_list, position_list, status_list, last_practice_day_list, data_load_time_str
+
+
+def get_page():
+ page_title = f"Team Practice Reports - {CURRENT_SEASON} Week {CURRENT_WEEK}"
+ st.set_page_config(page_title=page_title, page_icon=DEFAULT_ICON, layout="wide")
+ common_page_config()
+ st.title(page_title)
+ if st.button("Refresh Data"):
+ st.cache_data.clear()
+ data, teams_list, position_list, status_list, last_practice_day_list, data_load_time_str = load_data()
+ st.write(f"Data loaded as of: {data_load_time_str} UTC")
+ teams_selected = st.multiselect("Team:", teams_list, placeholder="Select a team to filter") or teams_list
+ positions_selected = (
+ st.multiselect("Position:", position_list, placeholder="Select a position to filter") or position_list
+ )
+ status_selected = (
+ st.multiselect("Game Status:", status_list, placeholder="Select a game status to filter") or status_list
+ )
+ last_practice_day_selected = (
+ st.multiselect(
+ "Last Practice Day:", last_practice_day_list, placeholder="Select a day of last team practice to filter"
+ )
+ or last_practice_day_list
+ )
+
+ with st.container():
+ filtered_data = filter_dataframe(
+ data[
+ (
+ data.Team.isin(teams_selected)
+ & data.Position.isin(positions_selected)
+ & data.game_status.isin(status_selected)
+ & data["Last Practice Day"].isin(last_practice_day_selected)
+ )
+ ]
+ )
+ st.dataframe(
+ filtered_data,
+ hide_index=True,
+ height=35 * (len(filtered_data) + 1) + 12,
+ use_container_width=False,
+ column_config={},
+ )
+
+
+if __name__ == "__main__":
+ get_page()
diff --git a/src/pages/50_League_Simulation.py b/src/pages/50_League_Simulation.py
new file mode 100644
index 0000000000000000000000000000000000000000..a5eb96f046db4d6245bb9846ad3dd371e76fd3c7
--- /dev/null
+++ b/src/pages/50_League_Simulation.py
@@ -0,0 +1,26 @@
+import streamlit as st
+
+from config import DEFAULT_ICON
+from shared_page import common_page_config
+
+from login_component import is_token_in_session
+
+
+def get_page():
+ page_title = "Yahoo FF League Simulation"
+ st.set_page_config(page_title=page_title, page_icon=DEFAULT_ICON, layout="wide")
+ common_page_config()
+ st.title(page_title)
+
+ if not is_token_in_session():
+ st.write(
+ "You must authorize the application to access your account in order to use this feature."
+ " Please click Login button above."
+ )
+
+ else:
+ st.write("Logged in. Feature to go here")
+
+
+if __name__ == "__main__":
+ get_page()
diff --git a/src/pages/5_Targets.py b/src/pages/5_Targets.py
new file mode 100644
index 0000000000000000000000000000000000000000..8b0f0c6d147beacf859aff330a331e0d4bf3dffa
--- /dev/null
+++ b/src/pages/5_Targets.py
@@ -0,0 +1,56 @@
+import datetime
+import numpy as np
+import streamlit as st
+
+from config import DEFAULT_ICON
+from shared_page import common_page_config
+
+from queries.footballguys.constants import YEAR
+from queries.footballguys.refresh import request_stat
+from streamlit_filter import filter_dataframe
+
+
+@st.cache_data(ttl=60 * 60 * 24)
+def load_data():
+ stat_name = "targets"
+ data = request_stat(stat_name)
+ data_load_time_str = datetime.datetime.utcnow().strftime("%m/%d/%Y %I:%M %p")
+ return data, data_load_time_str
+
+
+def get_page():
+ page_title = f"Player Targets - {YEAR}"
+ st.set_page_config(page_title=page_title, page_icon=DEFAULT_ICON, layout="wide")
+ common_page_config()
+ st.title(page_title)
+ if st.button("Refresh Data"):
+ st.cache_data.clear()
+ data, data_load_time_str = load_data()
+ st.write(f"Data loaded as of: {data_load_time_str} UTC")
+
+ selected_subtotals = st.selectbox("Show:", ["Player Totals", "Position Totals"], index=0)
+ if selected_subtotals == "Player Totals":
+ data = data[~data.name.str.contains(" Totals")]
+ elif selected_subtotals == "Position Totals":
+ data = data[data.name.str.contains(" Totals")]
+
+ value_types = st.selectbox("Counts / Percent:", ["Counts", "Percent"], index=0)
+ if value_types == "Percent":
+ numerical_data = data.select_dtypes(include=np.number)
+ numerical_cols = numerical_data.columns
+ df_percent_values = numerical_data / data.groupby("TEAM").transform(sum).select_dtypes(include=np.number)
+ data.loc[:, numerical_cols] = df_percent_values
+
+ with st.container():
+ filtered_data = filter_dataframe(data)
+ st.dataframe(
+ filtered_data,
+ hide_index=True,
+ height=35 * (len(filtered_data) + 1) + 12,
+ use_container_width=False,
+ column_config={},
+ )
+
+
+if __name__ == "__main__":
+ get_page()
diff --git a/src/pages/6_Redzone_Opportunities.py b/src/pages/6_Redzone_Opportunities.py
new file mode 100644
index 0000000000000000000000000000000000000000..3c892b4f5feca520b97d302d13c83d0b7458d1bb
--- /dev/null
+++ b/src/pages/6_Redzone_Opportunities.py
@@ -0,0 +1,56 @@
+import datetime
+import numpy as np
+import streamlit as st
+
+from config import DEFAULT_ICON
+from shared_page import common_page_config
+
+from queries.footballguys.constants import YEAR
+from queries.footballguys.refresh import request_stat
+from streamlit_filter import filter_dataframe
+
+
+@st.cache_data(ttl=60 * 60 * 24)
+def load_data():
+ stat_name = "redzone"
+ data = request_stat(stat_name)
+ data_load_time_str = datetime.datetime.utcnow().strftime("%m/%d/%Y %I:%M %p")
+ return data, data_load_time_str
+
+
+def get_page():
+ page_title = f"Player Redzone Opportunities - {YEAR}"
+ st.set_page_config(page_title=page_title, page_icon=DEFAULT_ICON, layout="wide")
+ common_page_config()
+ st.title(page_title)
+ if st.button("Refresh Data"):
+ st.cache_data.clear()
+ data, data_load_time_str = load_data()
+ st.write(f"Data loaded as of: {data_load_time_str} UTC")
+
+ selected_subtotals = st.selectbox("Show:", ["Player Totals", "Position Totals"], index=0)
+ if selected_subtotals == "Player Totals":
+ data = data[~data.name.str.contains(" Totals")]
+ elif selected_subtotals == "Position Totals":
+ data = data[data.name.str.contains(" Totals")]
+
+ value_types = st.selectbox("Counts / Percent:", ["Counts", "Percent"], index=0)
+ if value_types == "Percent":
+ numerical_data = data.select_dtypes(include=np.number)
+ numerical_cols = numerical_data.columns
+ df_percent_values = numerical_data / data.groupby("TEAM").transform(sum).select_dtypes(include=np.number)
+ data.loc[:, numerical_cols] = df_percent_values
+
+ with st.container():
+ filtered_data = filter_dataframe(data)
+ st.dataframe(
+ filtered_data,
+ hide_index=True,
+ height=35 * (len(filtered_data) + 1) + 12,
+ use_container_width=False,
+ column_config={},
+ )
+
+
+if __name__ == "__main__":
+ get_page()
diff --git a/src/pages/7_Snap_Counts.py b/src/pages/7_Snap_Counts.py
new file mode 100644
index 0000000000000000000000000000000000000000..4e82db1b75d2738b314872e2e1466b6c0364b533
--- /dev/null
+++ b/src/pages/7_Snap_Counts.py
@@ -0,0 +1,69 @@
+import datetime
+import streamlit as st
+
+from config import DEFAULT_ICON
+from shared_page import common_page_config
+
+from queries.footballguys.constants import YEAR
+from queries.nflverse.github_data import get_snap_counts, get_current_tables, SEASON
+
+
+def load_data():
+ data = get_snap_counts(YEAR)
+ data = data[data.fantasy_position]
+ teams_list = sorted(data.team.unique())
+ position_list = data.position.unique()
+ weeks_list = sorted(data.week.unique())
+ data_load_time_str = datetime.datetime.utcnow().strftime("%m/%d/%Y %I:%M %p")
+ return data, teams_list, position_list, weeks_list, data_load_time_str
+
+
+def get_page():
+ page_title = f"Snap Counts and Percentages - {YEAR}"
+ st.set_page_config(page_title=page_title, page_icon=DEFAULT_ICON, layout="wide")
+ common_page_config()
+ st.title(page_title)
+ if f"snap_counts_snap_counts_{SEASON}" not in get_current_tables():
+ st.write("Data not loaded.")
+ st.write("Check loaded data [here](./Load_Data)")
+ return
+ data, teams_list, position_list, weeks_list, data_load_time_str = load_data()
+ st.write(f"Data loaded as of: {data_load_time_str} UTC")
+ teams_selected = st.multiselect("Team:", teams_list, placeholder="Select a team to filter") or teams_list
+ positions_selected = (
+ st.multiselect("Position:", position_list, placeholder="Select a position to filter") or position_list
+ )
+ weeks_selected = st.multiselect("Week:", weeks_list, placeholder="Select a week to filter") or weeks_list
+
+ with st.container():
+ filtered_data = data[
+ (data.team.isin(teams_selected) & data.position.isin(positions_selected) & data.week.isin(weeks_selected))
+ ]
+ st.dataframe(
+ filtered_data,
+ hide_index=True,
+ # height=35 * (len(filtered_data) + 1) + 12,
+ use_container_width=False,
+ column_order=[
+ "season",
+ "game_type",
+ "week",
+ "player",
+ "position",
+ "team",
+ "opponent",
+ "offense_snaps",
+ "offense_pct",
+ "defense_snaps",
+ "defense_pct",
+ "st_snaps",
+ "st_pct",
+ ],
+ column_config={
+ "season": st.column_config.TextColumn(help="Year of NFL Season"),
+ },
+ )
+
+
+if __name__ == "__main__":
+ get_page()
diff --git a/src/pages/80_Maximum_Roster_Strategy.py b/src/pages/80_Maximum_Roster_Strategy.py
new file mode 100644
index 0000000000000000000000000000000000000000..ab6c78fddc9d9e53ecc2085e4717a00819014081
--- /dev/null
+++ b/src/pages/80_Maximum_Roster_Strategy.py
@@ -0,0 +1,165 @@
+import pandas as pd
+import streamlit as st
+import streamlit.components.v1 as components
+
+
+from config import DEFAULT_ICON
+from shared_page import common_page_config, get_local_style
+from maximum_roster_strategy import data_loader
+
+
+MINIMUM_WEEK = 6
+MAXIMUM_WEEK = 7
+
+MIN_TIER = 1
+MAX_TIER = 4
+
+POSITION_OPTIONS = ["RB", "WR", "TE", "QB"]
+
+POSITION_ABBR_FULL_NAME_MAP = {
+ "RB": "Running Backs",
+ "WR": "Wide Receivers",
+ "TE": "Tight Ends",
+ "QB": "Quarterbacks (Superflex / 2QB Leagues Only)",
+}
+
+
+@st.cache_data(ttl=5 * 60)
+def load_data():
+ return data_loader.get_google_sheet_data(), data_loader.get_timeslot_labels()
+
+
+def get_player_grid_div(player_series: pd.Series) -> str:
+ player_notes = player_series["Hold Condition"]
+ if (outcome := player_series["Outcome"]) == "Drop":
+ player_class = "drop-player"
+ elif outcome == "Light Hold":
+ player_class = "light-hold-player"
+ elif outcome == "Hold":
+ player_class = "hold-player"
+ else:
+ player_class = "undetermined-player"
+
+ if isinstance(player_weekly_note := player_series["Article Notes"], str):
+ player_notes += "
" + player_weekly_note
+ return f"""
+
+
+ {player_series["Formatted"]}
+
+
+ {player_notes}
+
+
+"""
+
+
+def get_time_slot_div(time_slot_list: list[str]) -> str:
+ code_str = ""
+ for time_slot_idx, time_slot in enumerate(time_slot_list):
+ code_str += f"""
{time_slot}
\n"""
+ return code_str
+
+
+def get_tier_div(tier_str: str | int, tier_num: str | int) -> str:
+ return f"""Tier {tier_str}
"""
+
+
+def get_player_container(df_players: pd.DataFrame, slot_number: int | str) -> str:
+ if len(df_players) == 0:
+ player_code_str = "
"
+ else:
+ player_code_str = "\n".join(df_players.apply(get_player_grid_div, axis=1).tolist())
+ return f"""{player_code_str}
"""
+
+
+def get_position_breakdown(df: pd.DataFrame, position_abbr: str, position_full_str: str, time_slots: list[str]):
+ with st.container():
+ st.header(position_full_str)
+ df_pos = df[df["Position"] == position_abbr]
+
+ grid_code_str = ""
+ grid_code_str += get_time_slot_div(time_slots)
+
+ tier_list = list(range(MIN_TIER, MAX_TIER + 1))
+ slot_number = 0
+ for tier_idx, tier in enumerate(tier_list):
+ grid_code_str += get_tier_div(tier, tier_idx + 1)
+ for time_slot in time_slots:
+ df_tier_slot = df_pos[(df_pos["TimeSlotName"] == time_slot) & (df_pos["Tier"] == tier)]
+ slot_number += 1
+ grid_code_str += get_player_container(df_tier_slot, slot_number)
+
+ components.html(
+ f"""
+{get_local_style()}
+
+ {grid_code_str}
+
+
+Colors Legend:
+
Drop Player
|
+
Light Hold Player
|
+
Strong Hold Player
+
+""",
+ height=1000,
+ scrolling=True,
+ )
+
+
+def get_page():
+ page_title = "Maximum Roster Strategy"
+ st.set_page_config(page_title=page_title, page_icon=DEFAULT_ICON, layout="wide")
+ common_page_config()
+ st.title(page_title)
+
+ with st.expander(label="Instructions"):
+ st.write(
+ """
+To get started with MRS: https://solowfantasyfootball.wordpress.com/2023/09/07/maximum-roster-strategy-explained/
+
+Players are organized by game time slot, position, and tier.
+
+Pick up a player during their game's time slot for potential upside if particular circumstances are met.
+
+After the game, players will be colored by outcome: Drop (Red), Light Hold (Yellow), or Strong Hold (Green)."""
+ )
+ col_select, week_select = st.columns(2, gap="small")
+ url_params = st.experimental_get_query_params()
+ initial_position_index = 0
+ if url_position := url_params.get("position"):
+ selected_position = url_position[0]
+ if selected_position in POSITION_OPTIONS:
+ initial_position_index = POSITION_OPTIONS.index(selected_position)
+
+ week_options = list(range(MAXIMUM_WEEK, MINIMUM_WEEK - 1, -1))
+ initial_week_index = 0
+ if url_week := url_params.get("week"):
+ try:
+ selected_week = int(url_week[0])
+ except Exception:
+ st.warning("Week parameter must be integer value", icon="ā ļø")
+ selected_week = MAXIMUM_WEEK
+ if selected_week in week_options:
+ initial_week_index = week_options.index(selected_week)
+
+ with col_select:
+ position = st.selectbox(label="Position", options=POSITION_OPTIONS, index=initial_position_index)
+ with week_select:
+ week = st.selectbox(label="Week", options=week_options, index=initial_week_index)
+ url_params.update({"position": position, "week": week})
+ st.experimental_set_query_params(**url_params)
+ if st.experimental_get_query_params().get("refresh"):
+ st.cache_data.clear()
+ df_mrs, all_time_slots_df = load_data()
+ df_mrs = df_mrs[df_mrs["Week"] == week]
+ current_week_timeslots = (
+ all_time_slots_df[all_time_slots_df["Week"] == week].sort_values("WeekTimeSlotIndex").TimeSlotName.tolist()
+ )
+
+ get_position_breakdown(df_mrs, position, POSITION_ABBR_FULL_NAME_MAP[position], current_week_timeslots)
+
+
+if __name__ == "__main__":
+ get_page()
diff --git a/src/pages/8_FTN_Charting.py b/src/pages/8_FTN_Charting.py
new file mode 100644
index 0000000000000000000000000000000000000000..3c8699291363dce55f76462d03344e69696e3353
--- /dev/null
+++ b/src/pages/8_FTN_Charting.py
@@ -0,0 +1,40 @@
+import datetime
+import streamlit as st
+
+from config import DEFAULT_ICON
+from shared_page import common_page_config
+
+from queries.footballguys.constants import YEAR
+from queries.nflverse.github_data import get_ftn_charting, get_current_tables, SEASON
+
+
+def load_data():
+ data = get_ftn_charting(YEAR)
+ data_load_time_str = datetime.datetime.utcnow().strftime("%m/%d/%Y %I:%M %p")
+ return data, data_load_time_str
+
+
+def get_page():
+ page_title = f"FTN Charting - {YEAR}"
+ st.set_page_config(page_title=page_title, page_icon=DEFAULT_ICON, layout="wide")
+ common_page_config()
+ st.title(page_title)
+ if f"ftn_charting_ftn_charting_{SEASON}" not in get_current_tables():
+ st.write("Data not loaded.")
+ st.write("Check loaded data [here](./Load_Data)")
+ return
+ data, data_load_time_str = load_data()
+ st.write(f"Data loaded as of: {data_load_time_str} UTC")
+
+ with st.container():
+ filtered_data = data
+ st.dataframe(
+ filtered_data,
+ hide_index=True,
+ # height=35 * (len(filtered_data) + 1) + 12,
+ use_container_width=False,
+ )
+
+
+if __name__ == "__main__":
+ get_page()
diff --git a/src/pages/98_Load_Data.py b/src/pages/98_Load_Data.py
new file mode 100644
index 0000000000000000000000000000000000000000..837184794fc890c8690d22703b2e4a94c7b628d1
--- /dev/null
+++ b/src/pages/98_Load_Data.py
@@ -0,0 +1,35 @@
+import duckdb
+import streamlit as st
+
+from config import DEFAULT_ICON
+from shared_page import common_page_config
+
+from queries.nflverse.github_data import load_assets, get_current_tables
+
+
+def get_page():
+ page_title = "Data Loader"
+ st.set_page_config(page_title=page_title, page_icon=DEFAULT_ICON, layout="wide")
+ common_page_config()
+ st.title(page_title)
+
+ current_tables_list = get_current_tables()
+
+ if st.button("Refresh Data"):
+ load_assets()
+ st.rerun()
+
+ if selected_table := st.selectbox("Describe a table:", current_tables_list, index=0):
+ describe_df = duckdb.sql(f"DESCRIBE {selected_table}").df()
+ st.dataframe(
+ describe_df,
+ hide_index=True,
+ use_container_width=True,
+ )
+
+ if st.checkbox("Explore data"):
+ st.dataframe(duckdb.sql(f"SELECT * FROM {selected_table} LIMIT 50").df())
+
+
+if __name__ == "__main__":
+ get_page()
diff --git a/src/pages/99_Keeper_Rules.py b/src/pages/99_Keeper_Rules.py
new file mode 100644
index 0000000000000000000000000000000000000000..21ac9ee78894c0030b676e215ca4e030673e9157
--- /dev/null
+++ b/src/pages/99_Keeper_Rules.py
@@ -0,0 +1,37 @@
+import streamlit as st
+
+from config import DEFAULT_ICON, LEAGUE_NAME
+from shared_page import common_page_config
+
+
+page_title = f"{LEAGUE_NAME} Keeper Rules"
+st.set_page_config(page_title=page_title, page_icon=DEFAULT_ICON)
+common_page_config()
+st.title(page_title)
+st.markdown(
+ """
+***Note: First LOFG keeper season was 2019***
+
+1. Teams can keep up to 3 players from the prior season.
+ - Note: changed from 2 to 3 beginning with 2022 season
+2. Players are kept at the expense of a draft pick.
+ - The round of that draft pick will depend on when the player was drafted in the prior season.
+3. Players drafted in the 1st round of the prior season will not be eligible to keep.
+4. Players drafted in rounds 2 and after can be kept at the expense of one higher round
+ - Ex: player drafted in round 2 can be kept for your 1st round pick.
+5. Players who were not drafted and were picked up during the season can be kept for 10th round picks (FA Keeper cost)
+ - Changed beginning 2022 from 9th round
+6. Post-Draft Acquired Keeper cost Minimum -
+ - Players drafted by other teams have a keeper cost of minimum of round player drafted and FA Keeper Cost.
+ - This rewards people who drafted the players, versus those who picked them up off waivers.
+7. If you select 2 players with the same keeper cost, one of the players would count as one round higher.
+ - Ex: Two players both have keeper cost of 10th round.
+ - One of those players would instead be counted as your 9th round pick if both are kept.
+8. Players can not be kept for more than 2 consecutive seasons
+ - Ex: player drafted in 2019 in 6th round, 2020 for 5th round, and 2021 for 4th round - can't be kept in 2022
+ - Exception to the above rule is that players originally drafted in 2nd round can only be kept for one season.
+9. Players traded in a season are not subject to the FA Keeper cost minimum cost
+In other words, keeper rights transfer with the trade
+
+ """
+)
diff --git a/src/pages/9_Team_Formations.py b/src/pages/9_Team_Formations.py
new file mode 100644
index 0000000000000000000000000000000000000000..81d6258a8de74cc0856412efbedfa9ee40212a3a
--- /dev/null
+++ b/src/pages/9_Team_Formations.py
@@ -0,0 +1,83 @@
+import datetime
+import pandas as pd
+import streamlit as st
+
+from config import DEFAULT_ICON
+from shared_page import common_page_config
+
+from queries.footballguys.constants import YEAR
+from queries.nflverse.github_data import get_pbp_participation, get_current_tables, SEASON
+
+
+def load_data():
+ data = get_pbp_participation(YEAR)
+ teams_list = sorted(filter(None, data.possession_team.unique()))
+ # position_list = data.position.unique()
+ # weeks_list = sorted(data.week.unique())
+ data_load_time_str = datetime.datetime.utcnow().strftime("%m/%d/%Y %I:%M %p")
+ return data, teams_list, data_load_time_str
+
+
+def get_page():
+ page_title = f"Team Formations - {YEAR}"
+ st.set_page_config(page_title=page_title, page_icon=DEFAULT_ICON, layout="wide")
+ common_page_config()
+ st.title(page_title)
+ if f"ftn_charting_ftn_charting_{SEASON}" not in get_current_tables():
+ st.write("Data not loaded.")
+ st.write("Check loaded data [here](./Load_Data)")
+ return
+ data, teams_list, data_load_time_str = load_data()
+ st.write(f"Data loaded as of: {data_load_time_str} UTC")
+ default_groups = [
+ "down",
+ "play_type",
+ "offense_personnel",
+ ]
+ group_options = [
+ "week",
+ "down",
+ "qtr",
+ "ydstogo",
+ "play_type",
+ "pass_length",
+ "pass_location",
+ "possession_team",
+ "offense_formation",
+ "offense_personnel",
+ "number_of_pass_rushers",
+ "defenders_in_box",
+ "defense_personnel",
+ ]
+ group_by_selected = st.multiselect("Group by:", group_options) or default_groups
+ team_selected = st.selectbox("Team:", teams_list)
+ week_selection = st.slider(
+ "Filter Week Range:",
+ min_value=data["week"].min(),
+ max_value=data["week"].max(),
+ value=(data["week"].min(), data["week"].max()),
+ step=1,
+ )
+
+ with st.container():
+ filtered_data = data[
+ (data.possession_team == team_selected)
+ & (data.play_type.isin(["pass", "run"]))
+ & (data["week"].between(*week_selection))
+ ]
+
+ st.dataframe(
+ pd.pivot_table(
+ filtered_data,
+ values="count_col",
+ index=group_by_selected,
+ columns="week",
+ aggfunc={"count_col": "sum"},
+ # margins=True,
+ ),
+ use_container_width=False,
+ )
+
+
+if __name__ == "__main__":
+ get_page()
diff --git a/src/queries/__init__.py b/src/queries/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/src/queries/footballguys/__init__.py b/src/queries/footballguys/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/src/queries/footballguys/constants.py b/src/queries/footballguys/constants.py
new file mode 100644
index 0000000000000000000000000000000000000000..721a69b447bfe7aa4ab11e358c18e3db4445117f
--- /dev/null
+++ b/src/queries/footballguys/constants.py
@@ -0,0 +1,34 @@
+from typing import List, Mapping
+
+# constants relevant to parsing from footballguys
+
+SNAP_PAGE_POSITON_ORDER: List[str] = [
+ "QB",
+ "RB",
+ "WR",
+ "TE",
+ "DT",
+ "DE",
+ "ILB",
+ "OLB",
+ "CB",
+ "S",
+]
+
+POSITIONS_TO_OFFENSE_DEFENSE: Mapping[str, str] = {
+ "QB": "OFF",
+ "RB": "OFF",
+ "WR": "OFF",
+ "TE": "OFF",
+ "DT": "DEF",
+ "DE": "DEF",
+ "ILB": "DEF",
+ "OLB": "DEF",
+ "S": "DEF",
+ "CB": "DEF",
+}
+
+
+BASE_URL = "https://www.footballguys.com/stats"
+
+YEAR = 2023
diff --git a/src/queries/footballguys/helpers.py b/src/queries/footballguys/helpers.py
new file mode 100644
index 0000000000000000000000000000000000000000..7ee73fa9661d3d82f17c5919701ee805ca7f92a4
--- /dev/null
+++ b/src/queries/footballguys/helpers.py
@@ -0,0 +1,130 @@
+from io import StringIO
+import lxml.html
+import pandas as pd
+import requests
+from typing import List
+from queries.footballguys import constants as fbgc
+
+
+def url_to_pandas(url) -> List[pd.DataFrame]:
+ page = requests.get(url)
+ table = pd.read_html(StringIO(page.text.replace("
", "-")))
+ return table
+
+
+def create_html_table_from_header_body(header_html_str: str, body_html_str: str):
+ return f"""
+
+{header_html_str}
+{body_html_str}
+
+"""
+
+
+def extract_snaps_to_pandas(url: str):
+ root = lxml.html.document_fromstring(requests.get(url).text)
+ table_element_list = root.xpath("""//*[@id="stats_snapcounts_data"]/div/table""")
+ assert isinstance(table_element_list, list)
+ table_element = table_element_list[0]
+ assert isinstance(table_element, lxml.html.HtmlElement)
+ table_child_list = table_element.getchildren()
+ assert len(table_child_list) % 2 == 0 # check is even
+ half_len = int(len(table_child_list) / 2)
+ df_list = []
+ for i in range(half_len):
+ table_html = create_html_table_from_header_body(
+ lxml.html.tostring(table_child_list[2 * i]), lxml.html.tostring(table_child_list[2 * i + 1])
+ ).replace("\\n", "")
+ df = pd.read_html(table_html)[0]
+ # First column contains name and is initially labeled as each position, example "Quarterback"
+ # Insert column at front called POS and fill with current first column label
+ position_name = df.columns[0]
+ df.insert(0, "POS", position_name)
+ df.rename(columns={position_name: "name"}, inplace=True)
+ df_list.append(df)
+ return df_list
+
+
+def add_snap_off_def_column(team_snap_df: pd.DataFrame):
+ off_def = team_snap_df["POS"].apply(lambda x: fbgc.POSITIONS_TO_OFFENSE_DEFENSE[x])
+ team_snap_df.insert(0, "OFF/DEF", off_def)
+
+
+def add_snap_position_column(
+ team_snap_df_list: List[pd.DataFrame],
+ position_name_array: List[str] = fbgc.SNAP_PAGE_POSITON_ORDER,
+):
+ # blank player names between positions, so we can use cumsum
+ # 8/22/23 - We are currently failing here because snap counts are incorrectly not split by position atm
+ assert len(team_snap_df_list) == len(position_name_array)
+ for pos_idx, pos_df in enumerate(team_snap_df_list):
+ pos_df.insert(0, "POS", position_name_array[pos_idx])
+
+
+def set_multilevel_columns(df):
+ new_cols = [tuple(x.split("-")) if "-" in x else (x, x) for x in df.columns]
+ df.columns = pd.MultiIndex.from_tuples(new_cols)
+
+
+def parse_snaps(team_short_name: str, base_url: str = fbgc.BASE_URL, year: int = fbgc.YEAR) -> pd.DataFrame:
+ print(f"Attempting to parse snaps for {team_short_name}")
+ team_snap_df_list = parse_team_page(team_short_name, base_url, "snap-counts", year)
+ team_snap_df = pd.concat(team_snap_df_list)
+ # add_snap_off_def_column(team_snap_df)
+ split_snap_count_percents(team_snap_df)
+ team_snap_df.dropna(subset=["name"], inplace=True)
+ # set_multilevel_columns(team_snap_df)
+ return team_snap_df
+
+
+def add_targets_position(team_df: pd.DataFrame):
+ # fill blanks up by reversing index, fill down, and re-reversing
+ positions = team_df.name.apply(lambda x: x.replace(" Totals", "") if " Totals" in x else None)[::-1].ffill()[::-1]
+ team_df.insert(0, "POS", positions)
+
+
+def parse_targets(team_short_name: str, base_url: str = fbgc.BASE_URL, year: int = fbgc.YEAR) -> pd.DataFrame:
+ # snaps are index 2
+ print(f"Attempting to parse targets for {team_short_name}")
+ team_df = parse_team_page(team_short_name, base_url, "targets", year)[0]
+ add_targets_position(team_df)
+ return team_df[team_df.name.notna()]
+
+
+def parse_redzone(team_short_name: str, base_url: str = fbgc.BASE_URL, year: int = fbgc.YEAR) -> pd.DataFrame:
+ # snaps are index 3
+ print(f"Attempting to parse redzone for {team_short_name}")
+ team_df = parse_team_page(team_short_name, base_url, "redzone", year)[0]
+ add_targets_position(team_df)
+ return team_df[team_df.name.notna()]
+
+
+def split_snap_count_percents(team_snap_df: pd.DataFrame):
+ for week in range(1, 18):
+ if f"Wk {week}" not in team_snap_df.columns:
+ continue
+ # if values are all NaN column will be dtype float 64 and should skip
+ if team_snap_df[f"Wk {week}"].dtype == float:
+ team_snap_df[f"{week}-count"] = 0
+ team_snap_df[f"{week}-%"] = 0.0
+ else:
+ week_split = team_snap_df[f"Wk {week}"].astype(str).str.split("-")
+ week_count = week_split.apply(lambda x: 0 if len(x) == 1 or x[0] == "" else int(x[0]))
+ week_pct = week_split.apply(lambda x: 0.0 if len(x) == 1 else float(x[1].strip("%")) / 100.0)
+ team_snap_df[f"{week}-count"] = week_count
+ team_snap_df[f"{week}-%"] = week_pct
+ team_snap_df.drop(columns=f"Wk {week}", inplace=True)
+
+
+def parse_team_page(
+ team_short_name: str,
+ base_url: str,
+ stat_name: str,
+ year: int,
+) -> List[pd.DataFrame]:
+ url = f"{base_url}/{stat_name}/teams?team={team_short_name}&year={year}"
+ if stat_name == "snap-counts":
+ all_tables = extract_snaps_to_pandas(url)
+ else:
+ all_tables = url_to_pandas(url)
+ return all_tables
diff --git a/src/queries/footballguys/refresh.py b/src/queries/footballguys/refresh.py
new file mode 100644
index 0000000000000000000000000000000000000000..1733f8a732258bda1c890071188d29dc19268bb3
--- /dev/null
+++ b/src/queries/footballguys/refresh.py
@@ -0,0 +1,46 @@
+from domain.teams import NFLTeam, ALL_TEAMS
+from queries.footballguys.helpers import parse_snaps, parse_targets, parse_redzone
+from typing import List, Callable, Optional
+import pandas as pd
+
+
+def add_team_name_columns(team_df: pd.DataFrame, team_short_name: str, team_name: str):
+ team_df.insert(0, "TEAM", team_short_name)
+ team_df.insert(1, "TEAM_NAME", team_name)
+
+
+def apply_intended_column_sorting(df: pd.DataFrame, first_columns: List[str]) -> pd.DataFrame:
+ first_columns_in_df = [col for col in first_columns if col in df.columns]
+ remaining_columns = [col for col in df.columns if col not in first_columns_in_df]
+ return df[first_columns_in_df + remaining_columns]
+
+
+def get_all_teams_stat_type(
+ all_teams_list: List[NFLTeam],
+ parsing_function: Callable,
+ store_key: str,
+ intended_first_columns: Optional[List[str]] = None,
+):
+ team_df_list = []
+ for team in all_teams_list:
+ team_df = parsing_function(team.footballguys_short_name)
+ add_team_name_columns(team_df, team.team_short_name, team.team_name)
+ team_df_list.append(team_df)
+ df = pd.concat(team_df_list)
+ if intended_first_columns:
+ df = apply_intended_column_sorting(df, intended_first_columns)
+ print(f"footballguy {store_key} loaded")
+ return df
+
+
+def request_stat(stat_name: str) -> pd.DataFrame:
+ intended_col_sort = None
+ if stat_name == "targets":
+ parse_fxn = parse_targets
+ intended_col_sort = ["TEAM", "TEAM_NAME", "POS", "name", "total"]
+ elif stat_name == "snap-counts":
+ parse_fxn = parse_snaps
+ elif stat_name == "redzone":
+ parse_fxn = parse_redzone
+ intended_col_sort = ["TEAM", "TEAM_NAME", "POS", "name", "total"]
+ return get_all_teams_stat_type(ALL_TEAMS, parse_fxn, stat_name, intended_col_sort)
diff --git a/src/queries/nbcsports/player_news.py b/src/queries/nbcsports/player_news.py
new file mode 100644
index 0000000000000000000000000000000000000000..d4a45740211ccb9f20fae09dcc0bc9abf303db2e
--- /dev/null
+++ b/src/queries/nbcsports/player_news.py
@@ -0,0 +1,52 @@
+from bs4 import BeautifulSoup
+import datetime
+import pandas as pd
+import requests
+from typing import Mapping
+
+NEWS_URL = "https://www.nbcsports.com/fantasy/football/player-news"
+
+
+def find_soup_text_with_default(soup, element: str, find_search_map: Mapping[str, str]):
+ find_result = soup.find(element, find_search_map)
+ if not find_result:
+ return ""
+ return find_result.text.strip()
+
+
+def parse_player_div(player_div):
+ return {
+ "Date/Time": player_div.find("div", {"class": "PlayerNewsPost-date"}).get("data-date"),
+ "Name": find_soup_text_with_default(player_div, "div", {"class": "PlayerNewsPost-name"}),
+ "Team": find_soup_text_with_default(player_div, "span", {"class": "PlayerNewsPost-team-abbr"}).upper(),
+ "Position": find_soup_text_with_default(player_div, "span", {"class": "PlayerNewsPost-position"}).title(),
+ "Headline": find_soup_text_with_default(player_div, "div", {"class": "PlayerNewsPost-headline"}),
+ "Analysis": find_soup_text_with_default(player_div, "div", {"class": "PlayerNewsPost-analysis"}),
+ }
+
+
+def get_nfl_player_news(page_number: int = 1) -> pd.DataFrame:
+ url = f"{NEWS_URL}?p={page_number}"
+ request_page = requests.get(url)
+ soup = BeautifulSoup(request_page.content)
+ player_div_list = soup.find_all("div", {"class": "PlayerNewsPost"})
+ if not player_div_list:
+ return pd.DataFrame()
+ parsed_player_list = [parse_player_div(d) for d in player_div_list]
+ df = pd.DataFrame(parsed_player_list)
+ df["Date/Time"] = pd.to_datetime(df["Date/Time"])
+ return df
+
+
+def get_player_news_window_hours(hours: int = 1):
+ end_date = datetime.datetime.now(datetime.timezone.utc) - datetime.timedelta(hours=hours)
+ page = 1
+ max_pages = 20
+ date_reached = False
+ df_list = []
+ while page < max_pages and not date_reached:
+ last_news = get_nfl_player_news(page)
+ df_list.append(last_news)
+ date_reached = min(last_news["Date/Time"]) < end_date
+ page += 1
+ return pd.concat(df_list)
diff --git a/src/queries/nfl_teams/__init__.py b/src/queries/nfl_teams/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/src/queries/nfl_teams/practice_reports.py b/src/queries/nfl_teams/practice_reports.py
new file mode 100644
index 0000000000000000000000000000000000000000..24968c328948a5e25ad90a2294ad80aae20d35b0
--- /dev/null
+++ b/src/queries/nfl_teams/practice_reports.py
@@ -0,0 +1,123 @@
+from bs4 import BeautifulSoup
+import datetime
+from multiprocessing import Pool
+import numpy as np
+import pandas as pd
+from pydantic import BaseModel, Field
+import requests
+from typing import Optional
+from urllib.parse import urljoin
+
+from domain.teams import ALL_TEAMS, NFLTeam
+
+
+MULTIPROCESSING_ENABLED = False
+
+PRACTICE_WEEK = {
+ "Mon": 0,
+ "Tue": 1,
+ "Wed": 2,
+ "Thu": 3,
+ "Fri": 4,
+ "Sat": 5,
+ "Sun": 6,
+ "Monday": 0,
+ "Tuesday": 1,
+ "Wednesday": 2,
+ "Thursday": 3,
+ "Friday": 4,
+ "Saturday": 5,
+ "Sunday": 6,
+}
+
+
+DAY_OF_WEEK_STRING_MAPPING = {
+ "Monday": "Mon",
+ "Tuesday": "Tue",
+ "Wednesday": "Wed",
+ "Thursday": "Thu",
+ "Friday": "Fri",
+ "Saturday": "Sat",
+ "Sunday": "Sun",
+}
+
+
+WEEK_1_BEGIN_DATE = datetime.datetime(2023, 9, 4)
+CURRENT_DATE = datetime.datetime.now()
+CURRENT_WEEK = max(1, int(1 + (CURRENT_DATE - WEEK_1_BEGIN_DATE).days / 7))
+CURRENT_SEASON = 2023
+
+
+class PracticeReportRawRow(BaseModel):
+ Team: str
+ Player: str
+ Position: str
+ Injury: str
+ Sun: Optional[str] = None
+ Mon: Optional[str] = None
+ Tue: Optional[str] = None
+ Wed: Optional[str] = None
+ Thu: Optional[str] = None
+ Fri: Optional[str] = None
+ Sat: Optional[str] = None
+ game_status: str = Field(alias="Game Status")
+
+ @classmethod
+ def replace_nan(self, value) -> str:
+ if isinstance(value, float):
+ if np.isnan(value):
+ return ""
+ return value
+
+ @classmethod
+ def from_raw(cls, input_dict) -> "PracticeReportRawRow":
+ return cls(**{DAY_OF_WEEK_STRING_MAPPING.get(k, k): cls.replace_nan(v) for k, v in input_dict.items()})
+
+
+def get_injury_report_dataframe(team: NFLTeam):
+ injury_report_url = urljoin(team.injury_report_url, f"week/REG-{CURRENT_WEEK}")
+ report_request = requests.get(injury_report_url)
+ report_soup = BeautifulSoup(report_request.content)
+ team_names_spans = report_soup.find_all("span", {"class": "nfl-o-injury-report__club-name"})
+ assert team_names_spans
+ team_names_str = [x.get_text() for x in team_names_spans]
+ assert team_names_str[0] == team.team_full_name
+ tables = report_soup.find_all("table")
+ df_report = pd.read_html(str(tables))[0]
+ return df_report
+
+
+def scrape_team_injury_report(team: NFLTeam) -> pd.DataFrame:
+ print(f"Scraping Injury Report for: {team.team_full_name}")
+ try:
+ team_report = get_injury_report_dataframe(team)
+ except Exception:
+ print(f"Failed to scrape practice report for: {team.team_full_name}")
+ return pd.DataFrame()
+ validated_row_list = []
+ for df_row_dict in team_report.to_dict("records"):
+ row_to_add = df_row_dict
+ row_to_add["Team"] = team.team_full_name
+ validated_row_list.append(PracticeReportRawRow.from_raw(row_to_add))
+ validated_df = pd.DataFrame([x.dict() for x in validated_row_list])
+ # drop all na columns
+ validated_df.dropna(axis=1, how="all", inplace=True)
+ # replace day of week with practice day from 1-3
+ day_idx = 1
+ last_practice_day = None
+ for col in validated_df.columns:
+ if col in PRACTICE_WEEK:
+ validated_df.rename(columns={col: str(day_idx)}, inplace=True)
+ day_idx += 1
+ last_practice_day = col
+ validated_df["Last Practice Day"] = last_practice_day
+ return validated_df
+
+
+def scrape_all_team_injury_report() -> pd.DataFrame:
+ if MULTIPROCESSING_ENABLED:
+ with Pool() as pool:
+ team_df_list = pool.map(scrape_team_injury_report, ALL_TEAMS)
+ else:
+ team_df_list = [scrape_team_injury_report(team) for team in ALL_TEAMS]
+ return pd.concat(team_df_list)
diff --git a/src/queries/nflverse/__init__.py b/src/queries/nflverse/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/src/queries/nflverse/github_data.py b/src/queries/nflverse/github_data.py
new file mode 100644
index 0000000000000000000000000000000000000000..5c21f83600ce502369bafc66407d0abe538b7e3e
--- /dev/null
+++ b/src/queries/nflverse/github_data.py
@@ -0,0 +1,123 @@
+import duckdb
+import pandas as pd
+import os
+from typing import Callable
+
+
+duckdb.default_connection.execute("SET GLOBAL pandas_analyze_sample=100000")
+
+BASE_URL = "https://github.com/nflverse/nflverse-data/releases/download/"
+
+
+FANTASY_POSITIONS = [
+ "QB",
+ "RB",
+ "WR",
+ "TE",
+ "FB",
+ "K",
+]
+
+
+def get_snap_counts(season_int: int) -> pd.DataFrame:
+ df = duckdb.sql(f"SELECT * from snap_counts_snap_counts_{season_int}").df()
+ df["fantasy_position"] = df["position"].isin(FANTASY_POSITIONS)
+ return df
+
+
+def get_play_by_play(season_int: int) -> pd.DataFrame:
+ df = duckdb.sql(f"SELECT * from pbp_play_by_play_{season_int}").df()
+ return df
+
+
+def get_player_stats(season_int: int) -> pd.DataFrame:
+ df = duckdb.sql("SELECT * from player_stats_player_stats").df()
+ return df
+
+
+def get_ftn_charting(season_int: int) -> pd.DataFrame:
+ df = duckdb.sql(f"SELECT * from ftn_charting_ftn_charting_{season_int}").df()
+ return df
+
+
+def get_pbp_participation(season_int: int) -> pd.DataFrame:
+ df = duckdb.sql(
+ f"""
+ SELECT
+ a.*
+ , b.*
+ , 1 as count_col
+ from pbp_participation_pbp_participation_{season_int} a
+ left join pbp_play_by_play_{season_int} b
+ on a.play_id = b.play_id
+ and a.nflverse_game_id = b.game_id
+ where b.week is not null
+"""
+ ).df()
+ return df
+
+
+def get_nextgen_stats(season_int: int, stat_category: str) -> pd.DataFrame:
+ df = duckdb.sql(f"SELECT * from nextgen_stats_ngs_{stat_category} where season = {season_int}").df()
+ return df
+
+
+SEASON = "2023"
+
+NFLVERSE_ASSETS = [
+ ("ftn_charting", f"ftn_charting_{SEASON}.parquet"),
+ ("espn_data", "qbr_season_level.parquet"),
+ ("espn_data", "qbr_week_level.parquet"),
+ ("players", "players.parquet"),
+ ("pbp_participation", f"pbp_participation_{SEASON}.parquet"),
+ ("snap_counts", f"snap_counts_{SEASON}.parquet"),
+ ("player_stats", f"player_stats_{SEASON}.parquet"),
+ ("player_stats", f"player_stats_def_{SEASON}.parquet"),
+ ("player_stats", f"player_stats_kicking_{SEASON}.parquet"),
+ ("pfr_advstats", "advstats_season_def.parquet"),
+ ("pfr_advstats", "advstats_season_pass.parquet"),
+ ("pfr_advstats", "advstats_season_rec.parquet"),
+ ("pfr_advstats", "advstats_season_rush.parquet"),
+ ("pfr_advstats", f"advstats_week_def_{SEASON}.parquet"),
+ ("pfr_advstats", f"advstats_week_pass_{SEASON}.parquet"),
+ ("pfr_advstats", f"advstats_week_rec_{SEASON}.parquet"),
+ ("pfr_advstats", f"advstats_week_rush_{SEASON}.parquet"),
+ ("pbp", f"play_by_play_{SEASON}.parquet"),
+ ("nextgen_stats", "ngs_passing.parquet"),
+ ("nextgen_stats", "ngs_receiving.parquet"),
+ ("nextgen_stats", "ngs_rushing.parquet"),
+]
+
+
+class NflVerseDataAsset:
+ def __init__(
+ self,
+ release_tag: str,
+ asset_name: str,
+ dataframe_mutation_fxn: Callable[[pd.DataFrame], pd.DataFrame] = lambda x: x,
+ ):
+ self.release_tag = release_tag
+ self.asset_name = asset_name
+ self.dataframe_mutation_fxn = dataframe_mutation_fxn
+ self.table_name = f"{release_tag}_{asset_name.rsplit('.', 1)[0]}"
+
+ def load_parquet_asset_to_df(self) -> pd.DataFrame:
+ location = os.path.join(BASE_URL, self.release_tag, self.asset_name)
+ df = pd.read_parquet(location)
+ return df
+
+ def register_asset_to_duckdb(self) -> None:
+ df = self.load_parquet_asset_to_df()
+ df = self.dataframe_mutation_fxn(df)
+ duckdb.register(self.table_name, df)
+
+
+def load_assets():
+ for tag, asset in NFLVERSE_ASSETS:
+ asset = NflVerseDataAsset(tag, asset)
+ asset.register_asset_to_duckdb()
+
+
+def get_current_tables() -> list[str]:
+ current_tables_df = duckdb.sql("SHOW TABLES").df()
+ return current_tables_df["name"].tolist()
diff --git a/src/shared_page.py b/src/shared_page.py
new file mode 100644
index 0000000000000000000000000000000000000000..acd77a420d0290b0948a60469db886604cf6e6ec
--- /dev/null
+++ b/src/shared_page.py
@@ -0,0 +1,22 @@
+import os
+import streamlit as st
+
+from page_selector import remove_seasonal_pages
+from login_component import get_authorization_button
+
+
+def get_local_style():
+ code_str = ""
+ with open(os.path.join(os.path.dirname(__file__), "style.css")) as f:
+ code_str = "".format(f.read())
+ return code_str
+
+
+def local_css():
+ return st.markdown(get_local_style(), unsafe_allow_html=True)
+
+
+def common_page_config():
+ local_css()
+ get_authorization_button()
+ remove_seasonal_pages()
diff --git a/src/start.sh b/src/start.sh
new file mode 100755
index 0000000000000000000000000000000000000000..3f020d6784c010b824ce7e0bcc19548bddb7b8fd
--- /dev/null
+++ b/src/start.sh
@@ -0,0 +1,3 @@
+#!/bin/bash
+
+streamlit run Home.py --server.port=8501 --server.address=0.0.0.0
diff --git a/src/streamlit_filter.py b/src/streamlit_filter.py
new file mode 100644
index 0000000000000000000000000000000000000000..0a08037f7589c41aeb14731d68dd3f7183781221
--- /dev/null
+++ b/src/streamlit_filter.py
@@ -0,0 +1,102 @@
+# https://blog.streamlit.io/auto-generate-a-dataframe-filtering-ui-in-streamlit-with-filter_dataframe/
+
+from pandas.api.types import (
+ is_categorical_dtype,
+ is_datetime64_any_dtype,
+ is_numeric_dtype,
+ is_object_dtype,
+)
+import pandas as pd
+import streamlit as st
+
+
+def filter_dataframe(df: pd.DataFrame, force_on: bool = False, force_on_columns: list[str] = []) -> pd.DataFrame:
+ """
+ Adds a UI on top of a dataframe to let viewers filter columns
+
+ Args:
+ df (pd.DataFrame): Original dataframe
+
+ Returns:
+ pd.DataFrame: Filtered dataframe
+ """
+ if force_on:
+ modify = True
+ else:
+ modify = st.checkbox("Add more filters")
+
+ if not modify:
+ return df
+
+ df = df.copy()
+
+ # Try to convert datetimes into a standard format (datetime, no timezone)
+ for col in df.columns:
+ if is_object_dtype(df[col]):
+ try:
+ df[col] = pd.to_datetime(df[col])
+ except Exception:
+ pass
+
+ if is_datetime64_any_dtype(df[col]):
+ df[col] = df[col].dt.tz_localize(None)
+
+ modification_container = st.container()
+
+ with modification_container:
+ to_filter_columns = st.multiselect("Filter dataframe on", df.columns) + force_on_columns
+ for column in to_filter_columns:
+ left, right = st.columns((1, 20))
+ # Treat columns with < 17 unique values as categorical
+ if is_categorical_dtype(df[column]) or df[column].nunique() < 50:
+ user_cat_input = right.multiselect(
+ f"Values for {column}",
+ df[column].unique(),
+ default=list(df[column].unique()),
+ )
+ df = df[df[column].isin(user_cat_input)]
+ elif is_numeric_dtype(df[column]):
+ _min = float(df[column].min())
+ _max = float(df[column].max())
+ step = (_max - _min) / 100
+ user_num_input = right.slider(
+ f"Values for {column}",
+ min_value=_min,
+ max_value=_max,
+ value=(_min, _max),
+ step=step,
+ )
+ df = df[df[column].between(*user_num_input)]
+ elif is_datetime64_any_dtype(df[column]):
+ user_date_input = right.date_input(
+ f"Values for {column}",
+ value=(
+ df[column].min(),
+ df[column].max(),
+ ),
+ )
+ if isinstance(user_date_input, tuple):
+ if len(user_date_input) == 2:
+ user_date_input_dt = tuple(map(pd.to_datetime, user_date_input))
+ start_date, end_date = user_date_input_dt
+ df = df.loc[df[column].between(start_date, end_date)]
+ else:
+ user_text_input = right.text_input(
+ f"Substring or regex in {column}",
+ )
+ if user_text_input:
+ df = df[df[column].astype(str).str.contains(user_text_input)]
+
+ return df
+
+
+def get_multiselect_for_df_column(df: pd.DataFrame, column_name: str) -> list:
+ options_list = sorted(df[column_name].unique().tolist())
+ if len(options_list) > 1:
+ selected = (
+ st.multiselect(column_name.title(), options_list, placeholder=f"Select a {column_name} to filter")
+ or options_list
+ )
+ else:
+ selected = options_list
+ return selected
diff --git a/src/style.css b/src/style.css
new file mode 100644
index 0000000000000000000000000000000000000000..79d1bd7f6c21e1ac9c72bacde7a08efcbadf54c2
--- /dev/null
+++ b/src/style.css
@@ -0,0 +1,150 @@
+.mrs-grid-player {
+ font-size: x-small;
+ margin-bottom: 5px;
+ padding-left: 4px;
+ padding-right: 4px;
+}
+
+.tier {
+ text-align: center;
+}
+
+.timeslot {
+ text-align: center;
+}
+
+.tier1 {
+ border: 2px solid blue;
+}
+.tier2 {
+ border: 2px solid gold;
+}
+
+.tier3 {
+ border: 2px solid silver;
+}
+
+.tier4 {
+ border: 2px solid brown;
+}
+
+.drop-player {
+ color: red;
+}
+
+.light-hold-player {
+ color: yellow;
+}
+
+.hold-player {
+ color: rgba(68, 235, 62, 0.774);
+}
+
+.playerslot {
+ border-left: 1px solid white;
+ border-right: 1px solid white;
+}
+
+.grid-legend {
+ color: white;
+ border: 1px solid white;
+ display: flex;
+ gap: 5px;
+ justify-content: center;
+ font-size: x-small;
+}
+
+.grid-container-6 {
+ display: grid;
+ grid-template-columns: repeat(6, 1fr);
+ grid-template-rows: repeat(9, auto);
+ grid-column-gap: 0px;
+ grid-row-gap: 10px;
+ color: white;
+
+ .timeslot1 { grid-area: 1 / 1 / 2 / 2; }
+ .timeslot2 { grid-area: 1 / 2 / 2 / 3; }
+ .timeslot3 { grid-area: 1 / 3 / 2 / 4; }
+ .timeslot4 { grid-area: 1 / 4 / 2 / 5; }
+ .timeslot5 { grid-area: 1 / 5 / 2 / 6; }
+ .timeslot6 { grid-area: 1 / 6 / 2 / 7; }
+ .tier1 { grid-area: 2 / 1 / 3 / 7; }
+ .playerslot1 { grid-area: 3 / 1 / 4 / 2; }
+ .playerslot2 { grid-area: 3 / 2 / 4 / 3; }
+ .playerslot3 { grid-area: 3 / 3 / 4 / 4; }
+ .playerslot4 { grid-area: 3 / 4 / 4 / 5; }
+ .playerslot5 { grid-area: 3 / 5 / 4 / 6; }
+ .playerslot6 { grid-area: 3 / 6 / 4 / 7; }
+ .tier2 { grid-area: 4 / 1 / 5 / 7; }
+ .playerslot7 { grid-area: 5 / 1 / 6 / 2; }
+ .playerslot8 { grid-area: 5 / 2 / 6 / 3; }
+ .playerslot9 { grid-area: 5 / 3 / 6 / 4; }
+ .playerslot10 { grid-area: 5 / 4 / 6 / 5; }
+ .playerslot11 { grid-area: 5 / 5 / 6 / 6; }
+ .playerslot12 { grid-area: 5 / 6 / 6 / 7; }
+ .tier3 { grid-area: 6 / 1 / 7 / 7; }
+ .playerslot13 { grid-area: 7 / 1 / 8 / 2; }
+ .playerslot14 { grid-area: 7 / 2 / 8 / 3; }
+ .playerslot15 { grid-area: 7 / 3 / 8 / 4; }
+ .playerslot16 { grid-area: 7 / 4 / 8 / 5; }
+ .playerslot17 { grid-area: 7 / 5 / 8 / 6; }
+ .playerslot18 { grid-area: 7 / 6 / 8 / 7; }
+ .tier4 { grid-area: 8 / 1 / 9 / 7; }
+ .playerslot19 { grid-area: 9 / 1 / 10 / 2; }
+ .playerslot20 { grid-area: 9 / 2 / 10 / 3; }
+ .playerslot21 { grid-area: 9 / 3 / 10 / 4; }
+ .playerslot22 { grid-area: 9 / 4 / 10 / 5; }
+ .playerslot23 { grid-area: 9 / 5 / 10 / 6; }
+ .playerslot24 { grid-area: 9 / 6 / 10 / 7; }
+
+}
+
+.grid-container-7 {
+ display: grid;
+ grid-template-columns: repeat(7, 1fr);
+ grid-template-rows: repeat(9, auto);
+ grid-column-gap: 0px;
+ grid-row-gap: 10px;
+ color: white;
+
+ .timeslot1 { grid-area: 1 / 1 / 2 / 2; }
+ .timeslot2 { grid-area: 1 / 2 / 2 / 3; }
+ .timeslot3 { grid-area: 1 / 3 / 2 / 4; }
+ .timeslot4 { grid-area: 1 / 4 / 2 / 5; }
+ .timeslot5 { grid-area: 1 / 5 / 2 / 6; }
+ .timeslot6 { grid-area: 1 / 6 / 2 / 7; }
+ .timeslot7 { grid-area: 1 / 7 / 2 / 8; }
+ .tier1 { grid-area: 2 / 1 / 3 / 8; }
+ .playerslot1 { grid-area: 3 / 1 / 4 / 2; }
+ .playerslot2 { grid-area: 3 / 2 / 4 / 3; }
+ .playerslot3 { grid-area: 3 / 3 / 4 / 4; }
+ .playerslot4 { grid-area: 3 / 4 / 4 / 5; }
+ .playerslot5 { grid-area: 3 / 5 / 4 / 6; }
+ .playerslot6 { grid-area: 3 / 6 / 4 / 7; }
+ .playerslot7 { grid-area: 3 / 7 / 4 / 8; }
+ .tier2 { grid-area: 4 / 1 / 5 / 8; }
+ .playerslot8 { grid-area: 5 / 1 / 6 / 2; }
+ .playerslot9 { grid-area: 5 / 2 / 6 / 3; }
+ .playerslot10 { grid-area: 5 / 3 / 6 / 4; }
+ .playerslot11 { grid-area: 5 / 4 / 6 / 5; }
+ .playerslot12 { grid-area: 5 / 5 / 6 / 6; }
+ .playerslot13 { grid-area: 5 / 6 / 6 / 7; }
+ .playerslot14 { grid-area: 5 / 7 / 6 / 8; }
+ .tier3 { grid-area: 6 / 1 / 7 / 8; }
+ .playerslot15 { grid-area: 7 / 1 / 8 / 2; }
+ .playerslot16 { grid-area: 7 / 2 / 8 / 3; }
+ .playerslot17 { grid-area: 7 / 3 / 8 / 4; }
+ .playerslot18 { grid-area: 7 / 4 / 8 / 5; }
+ .playerslot19 { grid-area: 7 / 5 / 8 / 6; }
+ .playerslot20 { grid-area: 7 / 6 / 8 / 7; }
+ .playerslot21 { grid-area: 7 / 7 / 8 / 8; }
+ .tier4 { grid-area: 8 / 1 / 9 / 8; }
+ .playerslot22 { grid-area: 9 / 1 / 10 / 2; }
+ .playerslot23 { grid-area: 9 / 2 / 10 / 3; }
+ .playerslot24 { grid-area: 9 / 3 / 10 / 4; }
+ .playerslot25 { grid-area: 9 / 4 / 10 / 5; }
+ .playerslot26 { grid-area: 9 / 5 / 10 / 6; }
+ .playerslot27 { grid-area: 9 / 6 / 10 / 7; }
+ .playerslot28 { grid-area: 9 / 7 / 10 / 8; }
+
+}
\ No newline at end of file
diff --git a/tests/contract/test_nbcsports_player_news.py b/tests/contract/test_nbcsports_player_news.py
new file mode 100644
index 0000000000000000000000000000000000000000..0d5c44e02c9b489b022d2939221b89f0d2ec101e
--- /dev/null
+++ b/tests/contract/test_nbcsports_player_news.py
@@ -0,0 +1,13 @@
+import pytest
+
+from queries.nbcsports import player_news
+
+
+@pytest.mark.parametrize("page_number", [(1), (2)])
+def test_get_nfl_player_news(page_number: int):
+ _ = player_news.get_nfl_player_news(page_number)
+
+
+@pytest.mark.parametrize("hours", [(1), (10)])
+def test_get_player_news_window_hours(hours: int):
+ _ = player_news.get_player_news_window_hours(hours)
diff --git a/tests/contract/test_nfl_teams_practice_reports.py b/tests/contract/test_nfl_teams_practice_reports.py
new file mode 100644
index 0000000000000000000000000000000000000000..21cacaae49227ac05b0d34917b5e2ad2d88c2373
--- /dev/null
+++ b/tests/contract/test_nfl_teams_practice_reports.py
@@ -0,0 +1,9 @@
+import pytest
+
+from domain import teams
+from queries.nfl_teams import practice_reports
+
+
+@pytest.mark.parametrize("team", [(x) for x in teams.ALL_TEAMS])
+def test_scrape_team_injury_report(team):
+ _ = practice_reports.scrape_team_injury_report(team)
diff --git a/tests/mocks/2023_keepers.csv b/tests/mocks/2023_keepers.csv
new file mode 100644
index 0000000000000000000000000000000000000000..259fd16319db0eebe597900cdbe2ae54225b1b5a
--- /dev/null
+++ b/tests/mocks/2023_keepers.csv
@@ -0,0 +1,222 @@
+,player_id,name,is_keeper,headshot_url,position_type,eligible_positions,selected_position,team_key,team_name,manager,status,season,pick,round,team_key_draft,years_eligible,keeper_cost,eligible
+0,25812,Kirk Cousins,False,https://s.yimg.com/iu/api/res/1.2/1ru7GVFJz3q2wjSqH_klXA--~C/YXBwaWQ9eXNwb3J0cztjaD0yMzM2O2NyPTE7Y3c9MTc5MDtkeD04NTc7ZHk9MDtmaT11bGNyb3A7aD02MDtxPTEwMDt3PTQ2/https://s.yimg.com/xe/i/us/sp/v/nfl_cutout/players_l/09122022/25812.png,O,"['QB', 'Q/W/R/T']",QB,414.l.474186.t.6,Just Enough Cooks,Gabe,,2022,52.0,5.0,414.l.474186.t.6,2,4.0,True
+1,32692,Justin Jefferson,True,https://s.yimg.com/iu/api/res/1.2/uKAl5.v0hNtF.Gofx7AY5w--~C/YXBwaWQ9eXNwb3J0cztjaD0yMzM2O2NyPTE7Y3c9MTc5MDtkeD04NTc7ZHk9MDtmaT11bGNyb3A7aD02MDtxPTEwMDt3PTQ2/https://s.yimg.com/xe/i/us/sp/v/nfl_cutout/players_l/09122022/32692.png,O,"['WR', 'W/R/T', 'Q/W/R/T']",WR,414.l.474186.t.6,Just Enough Cooks,Gabe,,2022,148.0,13.0,414.l.474186.t.6,0,12.0,False
+2,29399,Tyreek Hill,False,https://s.yimg.com/iu/api/res/1.2/IziJ.2wiZw0IpFOiD2kLsg--~C/YXBwaWQ9eXNwb3J0cztjaD0yMzM2O2NyPTE7Y3c9MTc5MDtkeD04NTc7ZHk9MDtmaT11bGNyb3A7aD02MDtxPTEwMDt3PTQ2/https://s.yimg.com/xe/i/us/sp/v/nfl_cutout/players_l/09092022/29399.png,O,"['WR', 'W/R/T', 'Q/W/R/T']",WR,414.l.474186.t.6,Just Enough Cooks,Gabe,,2022,21.0,2.0,414.l.474186.t.6,2,1.0,True
+3,30120,Mike Williams,True,https://s.yimg.com/iu/api/res/1.2/i9Vi4Ipm6kqfxD38LGUQeA--~C/YXBwaWQ9eXNwb3J0cztjaD0yMzM2O2NyPTE7Y3c9MTc5MDtkeD04NTc7ZHk9MDtmaT11bGNyb3A7aD02MDtxPTEwMDt3PTQ2/https://s.yimg.com/xe/i/us/sp/v/nfl_cutout/players_l/09092022/30120.png,O,"['WR', 'W/R/T', 'Q/W/R/T']",WR,414.l.474186.t.6,Just Enough Cooks,Gabe,,2022,76.0,7.0,414.l.474186.t.6,1,6.0,True
+4,30423,Austin Ekeler,False,https://s.yimg.com/iu/api/res/1.2/Wsn35gKIUHOaQEseGWkfRw--~C/YXBwaWQ9eXNwb3J0cztjaD0yMzM2O2NyPTE7Y3c9MTc5MDtkeD04NTc7ZHk9MDtmaT11bGNyb3A7aD02MDtxPTEwMDt3PTQ2/https://s.yimg.com/xe/i/us/sp/v/nfl_cutout/players_l/09092022/30423.png,O,"['RB', 'W/R/T', 'Q/W/R/T']",RB,414.l.474186.t.6,Just Enough Cooks,Gabe,,2022,4.0,1.0,414.l.474186.t.6,2,0.0,False
+5,31906,Devin Singletary,False,https://s.yimg.com/iu/api/res/1.2/NBqz6HmEaIx19gOlB39KPw--~C/YXBwaWQ9eXNwb3J0cztjaD0yMzM2O2NyPTE7Y3c9MTc5MDtkeD04NTc7ZHk9MDtmaT11bGNyb3A7aD02MDtxPTEwMDt3PTQ2/https://s.yimg.com/xe/i/us/sp/v/nfl_cutout/players_l/08152023/31906.png,O,"['RB', 'W/R/T', 'Q/W/R/T']",RB,414.l.474186.t.6,Just Enough Cooks,Gabe,,2022,100.0,9.0,414.l.474186.t.6,2,8.0,True
+6,30259,George Kittle,False,https://s.yimg.com/iu/api/res/1.2/OaU1XRgACHFYep3esYgmSw--~C/YXBwaWQ9eXNwb3J0cztjaD0yMzM2O2NyPTE7Y3c9MTc5MDtkeD04NTc7ZHk9MDtmaT11bGNyb3A7aD02MDtxPTEwMDt3PTQ2/https://s.yimg.com/xe/i/us/sp/v/nfl_cutout/players_l/09062022/30259.png,O,"['TE', 'W/R/T', 'Q/W/R/T']",TE,414.l.474186.t.6,Just Enough Cooks,Gabe,,2022,45.0,4.0,414.l.474186.t.6,2,3.0,True
+7,33514,Chuba Hubbard,False,https://s.yimg.com/iu/api/res/1.2/Dbzue7q8.gz13D7Jx.oddQ--~C/YXBwaWQ9eXNwb3J0cztjaD0yMzM2O2NyPTE7Y3c9MTc5MDtkeD04NTc7ZHk9MDtmaT11bGNyb3A7aD02MDtxPTEwMDt3PTQ2/https://s.yimg.com/xe/i/us/sp/v/nfl_cutout/players_l/08232022/33514.png,O,"['RB', 'W/R/T', 'Q/W/R/T']",W/R/T,414.l.474186.t.6,Just Enough Cooks,Gabe,,2022,193.0,17.0,414.l.474186.t.9,2,10.0,True
+8,32010,Gardner Minshew,False,https://s.yimg.com/iu/api/res/1.2/W5BQWsS0IkvLsIXQTVziuQ--~C/YXBwaWQ9eXNwb3J0cztjaD0yMzM2O2NyPTE7Y3c9MTc5MDtkeD04NTc7ZHk9MDtmaT11bGNyb3A7aD02MDtxPTEwMDt3PTQ2/https://s.yimg.com/xe/i/us/sp/v/nfl_cutout/players_l/08172023/32010.png,O,"['QB', 'Q/W/R/T']",Q/W/R/T,414.l.474186.t.6,Just Enough Cooks,Gabe,,2022,,,,2,10.0,True
+9,34007,George Pickens,False,https://s.yimg.com/iu/api/res/1.2/2.Q7wW_ectu5_akSvvUDvA--~C/YXBwaWQ9eXNwb3J0cztjaD0yMzM2O2NyPTE7Y3c9MTc5MDtkeD04NTc7ZHk9MDtmaT11bGNyb3A7aD02MDtxPTEwMDt3PTQ2/https://s.yimg.com/xe/i/us/sp/v/nfl_cutout/players_l/09022022/34007.png,O,"['WR', 'W/R/T', 'Q/W/R/T']",BN,414.l.474186.t.6,Just Enough Cooks,Gabe,,2022,124.0,11.0,414.l.474186.t.6,2,10.0,True
+10,32723,Jalen Hurts,True,https://s.yimg.com/iu/api/res/1.2/ZdHzqdvnato.MAwHy9jiWQ--~C/YXBwaWQ9eXNwb3J0cztjaD0yMzM2O2NyPTE7Y3c9MTc5MDtkeD04NTc7ZHk9MDtmaT11bGNyb3A7aD02MDtxPTEwMDt3PTQ2/https://s.yimg.com/xe/i/us/sp/v/nfl_cutout/players_l/09052022/32723.png,O,"['QB', 'Q/W/R/T']",BN,414.l.474186.t.6,Just Enough Cooks,Gabe,,2022,93.0,8.0,414.l.474186.t.6,0,7.0,False
+11,33971,Jahan Dotson,False,https://s.yimg.com/iu/api/res/1.2/rY7UBr8dJhyBD2zKeDnkAQ--~C/YXBwaWQ9eXNwb3J0cztjaD0yMzM2O2NyPTE7Y3c9MTc5MDtkeD04NTc7ZHk9MDtmaT11bGNyb3A7aD02MDtxPTEwMDt3PTQ2/https://s.yimg.com/xe/i/us/sp/v/nfl_cutout/players_l/08172023/33971.png,O,"['WR', 'W/R/T', 'Q/W/R/T']",BN,414.l.474186.t.6,Just Enough Cooks,Gabe,,2022,172.0,15.0,414.l.474186.t.6,2,14.0,True
+12,32719,Chase Claypool,False,https://s.yimg.com/iu/api/res/1.2/GDmycY7ed0Qgt7AIyKjswg--~C/YXBwaWQ9eXNwb3J0cztjaD0yMzM2O2NyPTE7Y3c9MTc5MDtkeD04NTc7ZHk9MDtmaT11bGNyb3A7aD02MDtxPTEwMDt3PTQ2/https://s.yimg.com/xe/i/us/sp/v/nfl_cutout/players_l/08082023/32719.png,O,"['WR', 'W/R/T', 'Q/W/R/T']",BN,414.l.474186.t.6,Just Enough Cooks,Gabe,,2022,117.0,10.0,414.l.474186.t.6,2,9.0,True
+13,33408,Kadarius Toney,False,https://s.yimg.com/iu/api/res/1.2/YgPRacRoiUOxNMcGvJ1qnw--~C/YXBwaWQ9eXNwb3J0cztjaD0yMzM2O2NyPTE7Y3c9MTc5MDtkeD04NTc7ZHk9MDtmaT11bGNyb3A7aD02MDtxPTEwMDt3PTQ2/https://s.yimg.com/xe/i/us/sp/v/nfl_cutout/players_l/08152023/33408.png,O,"['WR', 'W/R/T', 'Q/W/R/T']",BN,414.l.474186.t.6,Just Enough Cooks,Gabe,,2022,130.0,11.0,414.l.474186.t.7,2,10.0,True
+14,31928,Dawson Knox,False,https://s.yimg.com/iu/api/res/1.2/WJptQ610wbYZVND2GLJ1iQ--~C/YXBwaWQ9eXNwb3J0cztjaD0yMzM2O2NyPTE7Y3c9MTc5MDtkeD04NTc7ZHk9MDtmaT11bGNyb3A7aD02MDtxPTEwMDt3PTQ2/https://s.yimg.com/xe/i/us/sp/v/nfl_cutout/players_l/08162022/31928.png,O,"['TE', 'W/R/T', 'Q/W/R/T']",BN,414.l.474186.t.6,Just Enough Cooks,Gabe,,2022,89.0,8.0,414.l.474186.t.1,2,7.0,True
+15,27540,Odell Beckham Jr.,False,https://s.yimg.com/iu/api/res/1.2/oIT1EIDlFGEwA81VkFcX7A--~C/YXBwaWQ9eXNwb3J0cztjaD0yMzM2O2NyPTE7Y3c9MTc5MDtkeD04NTc7ZHk9MDtmaT11bGNyb3A7aD02MDtxPTEwMDt3PTQ2/https://s.yimg.com/xe/i/us/sp/v/nfl_cutout/players_l/02162023/27540.png,O,"['WR', 'W/R/T', 'Q/W/R/T']",BN,414.l.474186.t.6,Just Enough Cooks,Gabe,,2022,,,,2,10.0,True
+16,31137,Daniel Carlson,False,https://s.yimg.com/iu/api/res/1.2/60tYJJYqBaWMBgRufxtW2A--~C/YXBwaWQ9eXNwb3J0cztjaD0yMzM2O2NyPTE7Y3c9MTc5MDtkeD04NTc7ZHk9MDtmaT11bGNyb3A7aD02MDtxPTEwMDt3PTQ2/https://s.yimg.com/xe/i/us/sp/v/nfl_cutout/players_l/09092022/31137.png,K,['K'],K,414.l.474186.t.6,Just Enough Cooks,Gabe,,2022,196.0,17.0,414.l.474186.t.6,2,16.0,True
+17,100017,New England,False,https://s.yimg.com/lq/i/us/sp/v/nfl/teams/1/50x50w/nwe.gif,DT,['DEF'],DEF,414.l.474186.t.6,Just Enough Cooks,Gabe,,2022,208.0,18.0,414.l.474186.t.11,2,10.0,True
+18,32671,Joe Burrow,False,https://s.yimg.com/iu/api/res/1.2/QJXYvZ8pWkDD6JymTnrKBw--~C/YXBwaWQ9eXNwb3J0cztjaD0yMzM2O2NyPTE7Y3c9MTc5MDtkeD04NTc7ZHk9MDtmaT11bGNyb3A7aD02MDtxPTEwMDt3PTQ2/https://s.yimg.com/xe/i/us/sp/v/nfl_cutout/players_l/08242022/32671.png,O,"['QB', 'Q/W/R/T']",QB,414.l.474186.t.7,Short Stafford,Adam Bush,,2022,15.0,2.0,414.l.474186.t.7,2,1.0,True
+19,27581,Davante Adams,False,https://s.yimg.com/iu/api/res/1.2/UTwCcrglF9.BGBwncQo1Qg--~C/YXBwaWQ9eXNwb3J0cztjaD0yMzM2O2NyPTE7Y3c9MTc5MDtkeD04NTc7ZHk9MDtmaT11bGNyb3A7aD02MDtxPTEwMDt3PTQ2/https://s.yimg.com/xe/i/us/sp/v/nfl_cutout/players_l/08142023/27581.1.png,O,"['WR', 'W/R/T', 'Q/W/R/T']",WR,414.l.474186.t.7,Short Stafford,Adam Bush,,2022,10.0,1.0,414.l.474186.t.7,2,0.0,False
+20,31883,A.J. Brown,True,https://s.yimg.com/iu/api/res/1.2/n1WlNUDEwJAfqfgp0shFNw--~C/YXBwaWQ9eXNwb3J0cztjaD0yMzM2O2NyPTE7Y3c9MTc5MDtkeD04NTc7ZHk9MDtmaT11bGNyb3A7aD02MDtxPTEwMDt3PTQ2/https://s.yimg.com/xe/i/us/sp/v/nfl_cutout/players_l/09022022/31883.png,O,"['WR', 'W/R/T', 'Q/W/R/T']",WR,414.l.474186.t.7,Short Stafford,Adam Bush,,2022,63.0,6.0,414.l.474186.t.7,0,5.0,False
+21,31268,Allen Lazard,False,https://s.yimg.com/iu/api/res/1.2/MHhzpDypkk5FcSkgaIwydw--~C/YXBwaWQ9eXNwb3J0cztjaD0yMzM2O2NyPTE7Y3c9MTc5MDtkeD04NTc7ZHk9MDtmaT11bGNyb3A7aD02MDtxPTEwMDt3PTQ2/https://s.yimg.com/xe/i/us/sp/v/nfl_cutout/players_l/08022023/31268.png,O,"['WR', 'W/R/T', 'Q/W/R/T']",WR,414.l.474186.t.7,Short Stafford,Adam Bush,,2022,82.0,7.0,414.l.474186.t.7,2,6.0,True
+22,30117,Leonard Fournette,False,https://s.yimg.com/iu/api/res/1.2/YKNFujBNRe_snft4cpYzyA--~C/YXBwaWQ9eXNwb3J0cztjaD0yMzM2O2NyPTE7Y3c9MTc5MDtkeD04NTc7ZHk9MDtmaT11bGNyb3A7aD02MDtxPTEwMDt3PTQ2/https://s.yimg.com/xe/i/us/sp/v/nfl_cutout/players_l/09082022/30117.png,O,"['RB', 'W/R/T', 'Q/W/R/T']",RB,414.l.474186.t.7,Short Stafford,Adam Bush,NA,2022,34.0,3.0,414.l.474186.t.7,2,2.0,True
+23,34357,Zonovan Knight,False,https://s.yimg.com/iu/api/res/1.2/th5obr_yU_FnvBc.oRysIQ--~C/YXBwaWQ9eXNwb3J0cztjaD0yMzM2O2NyPTE7Y3c9MTc5MDtkeD04NTc7ZHk9MDtmaT11bGNyb3A7aD02MDtxPTEwMDt3PTQ2/https://s.yimg.com/xe/i/us/sp/v/nfl_cutout/players_l/09022022/34357.png,O,"['RB', 'W/R/T', 'Q/W/R/T']",RB,414.l.474186.t.7,Short Stafford,Adam Bush,,2022,,,,2,10.0,True
+24,31019,Dallas Goedert,True,https://s.yimg.com/iu/api/res/1.2/2EigEb4xahJnP3QtYDZg2w--~C/YXBwaWQ9eXNwb3J0cztjaD0yMzM2O2NyPTE7Y3c9MTc5MDtkeD04NTc7ZHk9MDtmaT11bGNyb3A7aD02MDtxPTEwMDt3PTQ2/https://s.yimg.com/xe/i/us/sp/v/nfl_cutout/players_l/09052022/31019.png,O,"['TE', 'W/R/T', 'Q/W/R/T']",TE,414.l.474186.t.7,Short Stafford,Adam Bush,,2022,106.0,9.0,414.l.474186.t.7,1,8.0,True
+25,33996,Kenneth Walker III,False,https://s.yimg.com/iu/api/res/1.2/P3hgQ_501q_FSeopp00fXQ--~C/YXBwaWQ9eXNwb3J0cztjaD0yMzM2O2NyPTE7Y3c9MTc5MDtkeD04NTc7ZHk9MDtmaT11bGNyb3A7aD02MDtxPTEwMDt3PTQ2/https://s.yimg.com/xe/i/us/sp/v/nfl_cutout/players_l/07252023/33996.png,O,"['RB', 'W/R/T', 'Q/W/R/T']",W/R/T,414.l.474186.t.7,Short Stafford,Adam Bush,,2022,111.0,10.0,414.l.474186.t.7,2,9.0,True
+26,34030,Desmond Ridder,False,https://s.yimg.com/iu/api/res/1.2/DUrt_8tP5WTT_gHETdjCzg--~C/YXBwaWQ9eXNwb3J0cztjaD0yMzM2O2NyPTE7Y3c9MTc5MDtkeD04NTc7ZHk9MDtmaT11bGNyb3A7aD02MDtxPTEwMDt3PTQ2/https://s.yimg.com/xe/i/us/sp/v/nfl_cutout/players_l/08192022/34030.png,O,"['QB', 'Q/W/R/T']",Q/W/R/T,414.l.474186.t.7,Short Stafford,Adam Bush,,2022,188.0,16.0,414.l.474186.t.3,2,10.0,True
+27,24060,Colt McCoy,False,https://s.yimg.com/iu/api/res/1.2/ad_RXu.DQLICW71zcK05SQ--~C/YXBwaWQ9eXNwb3J0cztjaD0yMzM2O2NyPTE7Y3c9MTc5MDtkeD04NTc7ZHk9MDtmaT11bGNyb3A7aD02MDtxPTEwMDt3PTQ2/https://s.yimg.com/xe/i/us/sp/v/nfl_cutout/players_l/09122022/24060.png,O,"['QB', 'Q/W/R/T']",BN,414.l.474186.t.7,Short Stafford,Adam Bush,Q,2022,,,,2,10.0,True
+28,33989,Christian Watson,False,https://s.yimg.com/iu/api/res/1.2/6IIpLPG5eq.B8RZTCtwmhQ--~C/YXBwaWQ9eXNwb3J0cztjaD0yMzM2O2NyPTE7Y3c9MTc5MDtkeD04NTc7ZHk9MDtmaT11bGNyb3A7aD02MDtxPTEwMDt3PTQ2/https://s.yimg.com/xe/i/us/sp/v/nfl_cutout/players_l/08302022/33989.png,O,"['WR', 'W/R/T', 'Q/W/R/T']",BN,414.l.474186.t.7,Short Stafford,Adam Bush,,2022,183.0,16.0,414.l.474186.t.7,2,15.0,True
+29,29360,Demarcus Robinson,False,https://s.yimg.com/iu/api/res/1.2/M2p6MNVcinIE1ybyAMI5yw--~C/YXBwaWQ9eXNwb3J0cztjaD0yMzM2O2NyPTE7Y3c9MTc5MDtkeD04NTc7ZHk9MDtmaT11bGNyb3A7aD02MDtxPTEwMDt3PTQ2/https://s.yimg.com/xe/i/us/sp/v/nfl_cutout/players_l/11282022/29360.png,O,"['WR', 'W/R/T', 'Q/W/R/T']",BN,414.l.474186.t.7,Short Stafford,Adam Bush,Q,2022,,,,2,10.0,True
+30,33443,Pat Freiermuth,False,https://s.yimg.com/iu/api/res/1.2/8K1q_4oL5zFCQOAUbcU_TQ--~C/YXBwaWQ9eXNwb3J0cztjaD0yMzM2O2NyPTE7Y3c9MTc5MDtkeD04NTc7ZHk9MDtmaT11bGNyb3A7aD02MDtxPTEwMDt3PTQ2/https://s.yimg.com/xe/i/us/sp/v/nfl_cutout/players_l/09062022/33443.png,O,"['TE', 'W/R/T', 'Q/W/R/T']",BN,414.l.474186.t.7,Short Stafford,Adam Bush,Q,2022,160.0,14.0,414.l.474186.t.11,2,10.0,True
+31,30153,Curtis Samuel,False,https://s.yimg.com/iu/api/res/1.2/3RkjwLTDVeSKnGZAIaPL0Q--~C/YXBwaWQ9eXNwb3J0cztjaD0yMzM2O2NyPTE7Y3c9MTc5MDtkeD04NTc7ZHk9MDtmaT11bGNyb3A7aD02MDtxPTEwMDt3PTQ2/https://s.yimg.com/xe/i/us/sp/v/nfl_cutout/players_l/08172023/30153.png,O,"['WR', 'W/R/T', 'Q/W/R/T']",BN,414.l.474186.t.7,Short Stafford,Adam Bush,,2022,,,,2,10.0,True
+32,32846,K.J. Osborn,False,https://s.yimg.com/iu/api/res/1.2/FHiM_yRbyAmJAkQwfopEDQ--~C/YXBwaWQ9eXNwb3J0cztjaD0yMzM2O2NyPTE7Y3c9MTc5MDtkeD04NTc7ZHk9MDtmaT11bGNyb3A7aD02MDtxPTEwMDt3PTQ2/https://s.yimg.com/xe/i/us/sp/v/nfl_cutout/players_l/09122022/32846.png,O,"['WR', 'W/R/T', 'Q/W/R/T']",BN,414.l.474186.t.7,Short Stafford,Adam Bush,,2022,215.0,18.0,414.l.474186.t.10,2,10.0,True
+33,30426,Younghoe Koo,False,https://s.yimg.com/iu/api/res/1.2/5SBc_xWFvSQDka.GZA77pw--~C/YXBwaWQ9eXNwb3J0cztjaD0yMzM2O2NyPTE7Y3c9MTc5MDtkeD04NTc7ZHk9MDtmaT11bGNyb3A7aD02MDtxPTEwMDt3PTQ2/https://s.yimg.com/xe/i/us/sp/v/nfl_cutout/players_l/08192022/30426.png,K,['K'],K,414.l.474186.t.7,Short Stafford,Adam Bush,,2022,201.0,17.0,414.l.474186.t.11,2,10.0,True
+34,100021,Philadelphia,False,https://s.yimg.com/lq/i/us/sp/v/nfl/teams/1/50x50w/phi.gif,DT,['DEF'],DEF,414.l.474186.t.7,Short Stafford,Adam Bush,,2022,202.0,17.0,414.l.474186.t.7,2,16.0,True
+35,100019,New York,False,https://s.yimg.com/lq/i/us/sp/v/nfl/teams/1/50x50w/nyg_2.gif,DT,['DEF'],BN,414.l.474186.t.7,Short Stafford,Adam Bush,,2022,,,,2,10.0,True
+36,33991,Breece Hall,False,https://s.yimg.com/iu/api/res/1.2/fknRjJHFW3aHaUGO8ssHZg--~C/YXBwaWQ9eXNwb3J0cztjaD0yMzM2O2NyPTE7Y3c9MTc5MDtkeD04NTc7ZHk9MDtmaT11bGNyb3A7aD02MDtxPTEwMDt3PTQ2/https://s.yimg.com/xe/i/us/sp/v/nfl_cutout/players_l/09022022/33991.png,O,"['RB', 'W/R/T', 'Q/W/R/T']",IR,414.l.474186.t.7,Short Stafford,Adam Bush,Q,2022,39.0,4.0,414.l.474186.t.7,2,3.0,True
+37,30123,Patrick Mahomes,False,https://s.yimg.com/iu/api/res/1.2/uGaZr6pMT6iteP6VNUutXA--~C/YXBwaWQ9eXNwb3J0cztjaD0yMzM2O2NyPTE7Y3c9MTc5MDtkeD04NTc7ZHk9MDtmaT11bGNyb3A7aD02MDtxPTEwMDt3PTQ2/https://s.yimg.com/xe/i/us/sp/v/nfl_cutout/players_l/09052022/30123.png,O,"['QB', 'Q/W/R/T']",QB,414.l.474186.t.1,Solow Red Cups,Jon,Q,2022,8.0,1.0,414.l.474186.t.1,2,0.0,False
+38,31896,DK Metcalf,False,https://s.yimg.com/iu/api/res/1.2/fQ3vz6SwKcRb0sOGr6xjUA--~C/YXBwaWQ9eXNwb3J0cztjaD0yMzM2O2NyPTE7Y3c9MTc5MDtkeD04NTc7ZHk9MDtmaT11bGNyb3A7aD02MDtxPTEwMDt3PTQ2/https://s.yimg.com/xe/i/us/sp/v/nfl_cutout/players_l/07252023/31896.png,O,"['WR', 'W/R/T', 'Q/W/R/T']",WR,414.l.474186.t.1,Solow Red Cups,Jon,,2022,41.0,4.0,414.l.474186.t.1,2,3.0,True
+39,33500,Amon-Ra St. Brown,True,https://s.yimg.com/iu/api/res/1.2/znSCRKLz1GvciatCpJ40Hw--~C/YXBwaWQ9eXNwb3J0cztjaD0yMzM2O2NyPTE7Y3c9MTc5MDtkeD04NTc7ZHk9MDtmaT11bGNyb3A7aD02MDtxPTEwMDt3PTQ2/https://s.yimg.com/xe/i/us/sp/v/nfl_cutout/players_l/08302022/33500.png,O,"['WR', 'W/R/T', 'Q/W/R/T']",WR,414.l.474186.t.1,Solow Red Cups,Jon,,2022,200.0,17.0,414.l.474186.t.1,1,16.0,True
+40,32523,Greg Dortch,False,https://s.yimg.com/iu/api/res/1.2/1n5zauQsXxaPrKLMTsOFlw--~C/YXBwaWQ9eXNwb3J0cztjaD0yMzM2O2NyPTE7Y3c9MTc5MDtkeD04NTc7ZHk9MDtmaT11bGNyb3A7aD02MDtxPTEwMDt3PTQ2/https://s.yimg.com/xe/i/us/sp/v/nfl_cutout/players_l/09122022/32523.png,O,"['WR', 'W/R/T', 'Q/W/R/T']",WR,414.l.474186.t.1,Solow Red Cups,Jon,,2022,,,,2,10.0,True
+41,30972,Saquon Barkley,False,https://s.yimg.com/iu/api/res/1.2/mfHTfkJ107RvBncwqjnr9A--~C/YXBwaWQ9eXNwb3J0cztjaD0yMzM2O2NyPTE7Y3c9MTc5MDtkeD04NTc7ZHk9MDtmaT11bGNyb3A7aD02MDtxPTEwMDt3PTQ2/https://s.yimg.com/xe/i/us/sp/v/nfl_cutout/players_l/09102022/30972.1.png,O,"['RB', 'W/R/T', 'Q/W/R/T']",RB,414.l.474186.t.1,Solow Red Cups,Jon,,2022,17.0,2.0,414.l.474186.t.1,2,1.0,True
+42,33508,Rhamondre Stevenson,False,https://s.yimg.com/iu/api/res/1.2/y3pOEJ5ze52.TwQwLfzJUw--~C/YXBwaWQ9eXNwb3J0cztjaD0yMzM2O2NyPTE7Y3c9MTc5MDtkeD04NTc7ZHk9MDtmaT11bGNyb3A7aD02MDtxPTEwMDt3PTQ2/https://s.yimg.com/xe/i/us/sp/v/nfl_cutout/players_l/09122022/33508.png,O,"['RB', 'W/R/T', 'Q/W/R/T']",RB,414.l.474186.t.1,Solow Red Cups,Jon,,2022,56.0,5.0,414.l.474186.t.1,2,4.0,True
+43,31127,Tyler Conklin,False,https://s.yimg.com/iu/api/res/1.2/4melcS_WZ1rv3aEFcKLvkw--~C/YXBwaWQ9eXNwb3J0cztjaD0yMzM2O2NyPTE7Y3c9MTc5MDtkeD04NTc7ZHk9MDtmaT11bGNyb3A7aD02MDtxPTEwMDt3PTQ2/https://s.yimg.com/xe/i/us/sp/v/nfl_cutout/players_l/09022022/31127.png,O,"['TE', 'W/R/T', 'Q/W/R/T']",TE,414.l.474186.t.1,Solow Red Cups,Jon,,2022,,,,2,10.0,True
+44,33413,Travis Etienne Jr.,True,https://s.yimg.com/iu/api/res/1.2/D1RbhndjK1UiNP_iBg119g--~C/YXBwaWQ9eXNwb3J0cztjaD0yMzM2O2NyPTE7Y3c9MTc5MDtkeD04NTc7ZHk9MDtmaT11bGNyb3A7aD02MDtxPTEwMDt3PTQ2/https://s.yimg.com/xe/i/us/sp/v/nfl_cutout/players_l/09022022/33413.png,O,"['RB', 'W/R/T', 'Q/W/R/T']",W/R/T,414.l.474186.t.1,Solow Red Cups,Jon,,2022,113.0,10.0,414.l.474186.t.1,1,9.0,True
+45,30977,Josh Allen,True,https://s.yimg.com/iu/api/res/1.2/2QJbc1u1X6VJqFGDiBcSHw--~C/YXBwaWQ9eXNwb3J0cztjaD0yMzM2O2NyPTE7Y3c9MTc5MDtkeD04NTc7ZHk9MDtmaT11bGNyb3A7aD02MDtxPTEwMDt3PTQ2/https://s.yimg.com/xe/i/us/sp/v/nfl_cutout/players_l/08162022/30977.png,O,"['QB', 'Q/W/R/T']",Q/W/R/T,414.l.474186.t.1,Solow Red Cups,Jon,,2022,32.0,3.0,414.l.474186.t.1,0,2.0,False
+46,31857,Marquise Brown,False,https://s.yimg.com/iu/api/res/1.2/.CMTa_qhHj5x9VgqxEceSg--~C/YXBwaWQ9eXNwb3J0cztjaD0yMzM2O2NyPTE7Y3c9MTc5MDtkeD04NTc7ZHk9MDtmaT11bGNyb3A7aD02MDtxPTEwMDt3PTQ2/https://s.yimg.com/xe/i/us/sp/v/nfl_cutout/players_l/08222022/31857.png,O,"['WR', 'W/R/T', 'Q/W/R/T']",BN,414.l.474186.t.1,Solow Red Cups,Jon,,2022,65.0,6.0,414.l.474186.t.1,2,5.0,True
+47,34047,Rachaad White,False,https://s.yimg.com/iu/api/res/1.2/WwOHaEZZDPASCr3MR7iLEw--~C/YXBwaWQ9eXNwb3J0cztjaD0yMzM2O2NyPTE7Y3c9MTc5MDtkeD04NTc7ZHk9MDtmaT11bGNyb3A7aD02MDtxPTEwMDt3PTQ2/https://s.yimg.com/xe/i/us/sp/v/nfl_cutout/players_l/08182023/34047.png,O,"['RB', 'W/R/T', 'Q/W/R/T']",BN,414.l.474186.t.1,Solow Red Cups,Jon,,2022,128.0,11.0,414.l.474186.t.1,2,10.0,True
+48,33975,Kenny Pickett,False,https://s.yimg.com/iu/api/res/1.2/4FNm5KnyTOOqgi8IPlu2yg--~C/YXBwaWQ9eXNwb3J0cztjaD0yMzM2O2NyPTE7Y3c9MTc5MDtkeD04NTc7ZHk9MDtmaT11bGNyb3A7aD02MDtxPTEwMDt3PTQ2/https://s.yimg.com/xe/i/us/sp/v/nfl_cutout/players_l/09022022/33975.png,O,"['QB', 'Q/W/R/T']",BN,414.l.474186.t.1,Solow Red Cups,Jon,,2022,152.0,13.0,414.l.474186.t.1,2,12.0,True
+49,30199,Kareem Hunt,False,https://s.yimg.com/iu/api/res/1.2/qImyalciOvrIvq611mTuxQ--~C/YXBwaWQ9eXNwb3J0cztjaD0yMzM2O2NyPTE7Y3c9MTc5MDtkeD04NTc7ZHk9MDtmaT11bGNyb3A7aD02MDtxPTEwMDt3PTQ2/https://s.yimg.com/xe/i/us/sp/v/nfl_cutout/players_l/09132022/30199.1.png,O,"['RB', 'W/R/T', 'Q/W/R/T']",BN,414.l.474186.t.1,Solow Red Cups,Jon,NA,2022,72.0,6.0,414.l.474186.t.1,2,5.0,True
+50,33538,Kenneth Gainwell,False,https://s.yimg.com/iu/api/res/1.2/Qa8DZYmDuoB9.VvFRQ6bBA--~C/YXBwaWQ9eXNwb3J0cztjaD0yMzM2O2NyPTE7Y3c9MTc5MDtkeD04NTc7ZHk9MDtmaT11bGNyb3A7aD02MDtxPTEwMDt3PTQ2/https://s.yimg.com/xe/i/us/sp/v/nfl_cutout/players_l/09052022/33538.png,O,"['RB', 'W/R/T', 'Q/W/R/T']",BN,414.l.474186.t.1,Solow Red Cups,Jon,,2022,137.0,12.0,414.l.474186.t.1,2,11.0,True
+51,32877,Isaiah Hodgins,False,https://s.yimg.com/iu/api/res/1.2/_20CSLAepNKXOc.ELTKcpg--~C/YXBwaWQ9eXNwb3J0cztjaD0yMzM2O2NyPTE7Y3c9MTc5MDtkeD04NTc7ZHk9MDtmaT11bGNyb3A7aD02MDtxPTEwMDt3PTQ2/https://s.yimg.com/xe/i/us/sp/v/nfl_cutout/players_l/11142022/32877.png,O,"['WR', 'W/R/T', 'Q/W/R/T']",BN,414.l.474186.t.1,Solow Red Cups,Jon,,2022,,,,2,10.0,True
+52,34079,Isaiah Spiller,False,https://s.yimg.com/iu/api/res/1.2/UtIYVmtNsvlANKEF2kwS.A--~C/YXBwaWQ9eXNwb3J0cztjaD0yMzM2O2NyPTE7Y3c9MTc5MDtkeD04NTc7ZHk9MDtmaT11bGNyb3A7aD02MDtxPTEwMDt3PTQ2/https://s.yimg.com/xe/i/us/sp/v/nfl_cutout/players_l/09022022/34079.png,O,"['RB', 'W/R/T', 'Q/W/R/T']",BN,414.l.474186.t.1,Solow Red Cups,Jon,,2022,209.0,18.0,414.l.474186.t.1,2,17.0,True
+53,9526,Graham Gano,False,https://s.yimg.com/iu/api/res/1.2/7_9bZ4Hysr0w4YsNg8rUOg--~C/YXBwaWQ9eXNwb3J0cztjaD0yMzM2O2NyPTE7Y3c9MTc5MDtkeD04NTc7ZHk9MDtmaT11bGNyb3A7aD02MDtxPTEwMDt3PTQ2/https://s.yimg.com/xe/i/us/sp/v/nfl_cutout/players_l/09102022/9526.1.png,K,['K'],K,414.l.474186.t.1,Solow Red Cups,Jon,,2022,,,,2,10.0,True
+54,100012,Kansas City,False,https://s.yimg.com/lq/i/us/sp/v/nfl/teams/1/50x50w/kan.gif,DT,['DEF'],DEF,414.l.474186.t.1,Solow Red Cups,Jon,,2022,,,,2,10.0,True
+55,33998,Wan'Dale Robinson,False,https://s.yimg.com/iu/api/res/1.2/0Z.sjWpUZoE4iq.6_wPWzA--~C/YXBwaWQ9eXNwb3J0cztjaD0yMzM2O2NyPTE7Y3c9MTc5MDtkeD04NTc7ZHk9MDtmaT11bGNyb3A7aD02MDtxPTEwMDt3PTQ2/https://s.yimg.com/xe/i/us/sp/v/nfl_cutout/players_l/09022022/33998.png,O,"['WR', 'W/R/T', 'Q/W/R/T']",IR,414.l.474186.t.1,Solow Red Cups,Jon,Q,2022,161.0,14.0,414.l.474186.t.1,2,13.0,True
+56,33399,Justin Fields,True,https://s.yimg.com/iu/api/res/1.2/bBjs6pBZ9oDYeFwr4OeCiA--~C/YXBwaWQ9eXNwb3J0cztjaD0yMzM2O2NyPTE7Y3c9MTc5MDtkeD04NTc7ZHk9MDtmaT11bGNyb3A7aD02MDtxPTEwMDt3PTQ2/https://s.yimg.com/xe/i/us/sp/v/nfl_cutout/players_l/08032022/33399.png,O,"['QB', 'Q/W/R/T']",QB,414.l.474186.t.2,Gold Standard,Daniel,,2022,86.0,8.0,414.l.474186.t.2,1,7.0,True
+57,28534,Stefon Diggs,True,https://s.yimg.com/iu/api/res/1.2/80tJkiyYc0vrrvUjfh2auw--~C/YXBwaWQ9eXNwb3J0cztjaD0yMzM2O2NyPTE7Y3c9MTc5MDtkeD04NTc7ZHk9MDtmaT11bGNyb3A7aD02MDtxPTEwMDt3PTQ2/https://s.yimg.com/xe/i/us/sp/v/nfl_cutout/players_l/10012020/28534.1.png,O,"['WR', 'W/R/T', 'Q/W/R/T']",WR,414.l.474186.t.2,Gold Standard,Daniel,,2022,35.0,3.0,414.l.474186.t.2,0,2.0,False
+58,33398,DeVonta Smith,False,https://s.yimg.com/iu/api/res/1.2/6hdcVBZqvse4ZSmDUfFEdQ--~C/YXBwaWQ9eXNwb3J0cztjaD0yMzM2O2NyPTE7Y3c9MTc5MDtkeD04NTc7ZHk9MDtmaT11bGNyb3A7aD02MDtxPTEwMDt3PTQ2/https://s.yimg.com/xe/i/us/sp/v/nfl_cutout/players_l/09052022/33398.png,O,"['WR', 'W/R/T', 'Q/W/R/T']",WR,414.l.474186.t.2,Gold Standard,Daniel,,2022,83.0,7.0,414.l.474186.t.2,2,6.0,True
+59,33965,Garrett Wilson,False,https://s.yimg.com/iu/api/res/1.2/VSzU4ioKKF77t7yr5uQLNg--~C/YXBwaWQ9eXNwb3J0cztjaD0yMzM2O2NyPTE7Y3c9MTc5MDtkeD04NTc7ZHk9MDtmaT11bGNyb3A7aD02MDtxPTEwMDt3PTQ2/https://s.yimg.com/xe/i/us/sp/v/nfl_cutout/players_l/09022022/33965.png,O,"['WR', 'W/R/T', 'Q/W/R/T']",WR,414.l.474186.t.2,Gold Standard,Daniel,,2022,206.0,18.0,414.l.474186.t.2,2,17.0,True
+60,32732,AJ Dillon,True,https://s.yimg.com/iu/api/res/1.2/Fuzt08n6yShzPhmZk3dGPQ--~C/YXBwaWQ9eXNwb3J0cztjaD0yMzM2O2NyPTE7Y3c9MTc5MDtkeD04NTc7ZHk9MDtmaT11bGNyb3A7aD02MDtxPTEwMDt3PTQ2/https://s.yimg.com/xe/i/us/sp/v/nfl_cutout/players_l/08302022/32732.png,O,"['RB', 'W/R/T', 'Q/W/R/T']",RB,414.l.474186.t.2,Gold Standard,Daniel,,2022,62.0,6.0,414.l.474186.t.2,1,5.0,True
+61,30202,D'Onta Foreman,False,https://s.yimg.com/iu/api/res/1.2/xwIHvz1F6dEQPoWawE5Vrg--~C/YXBwaWQ9eXNwb3J0cztjaD0yMzM2O2NyPTE7Y3c9MTc5MDtkeD04NTc7ZHk9MDtmaT11bGNyb3A7aD02MDtxPTEwMDt3PTQ2/https://s.yimg.com/xe/i/us/sp/v/nfl_cutout/players_l/08082023/30202.png,O,"['RB', 'W/R/T', 'Q/W/R/T']",RB,414.l.474186.t.2,Gold Standard,Daniel,,2022,198.0,17.0,414.l.474186.t.5,2,10.0,True
+62,30136,Evan Engram,False,https://s.yimg.com/iu/api/res/1.2/o6Ww.Q9ytv48B9LNAHMv8A--~C/YXBwaWQ9eXNwb3J0cztjaD0yMzM2O2NyPTE7Y3c9MTc5MDtkeD04NTc7ZHk9MDtmaT11bGNyb3A7aD02MDtxPTEwMDt3PTQ2/https://s.yimg.com/xe/i/us/sp/v/nfl_cutout/players_l/09022022/30136.png,O,"['TE', 'W/R/T', 'Q/W/R/T']",TE,414.l.474186.t.2,Gold Standard,Daniel,,2022,,,,2,10.0,True
+63,34207,Isiah Pacheco,False,https://s.yimg.com/iu/api/res/1.2/MLVZ02I_Cn_qG5u2gcG3iw--~C/YXBwaWQ9eXNwb3J0cztjaD0yMzM2O2NyPTE7Y3c9MTc5MDtkeD04NTc7ZHk9MDtmaT11bGNyb3A7aD02MDtxPTEwMDt3PTQ2/https://s.yimg.com/xe/i/us/sp/v/nfl_cutout/players_l/09022022/34207.png,O,"['RB', 'W/R/T', 'Q/W/R/T']",W/R/T,414.l.474186.t.2,Gold Standard,Daniel,Q,2022,134.0,12.0,414.l.474186.t.2,2,11.0,True
+64,27560,Teddy Bridgewater,False,https://s.yimg.com/iu/api/res/1.2/V4npHYJxJlm0HzNwXMTnag--~C/YXBwaWQ9eXNwb3J0cztjaD0yMzM2O2NyPTE7Y3c9MTc5MDtkeD04NTc7ZHk9MDtmaT11bGNyb3A7aD02MDtxPTEwMDt3PTQ2/https://s.yimg.com/xe/i/us/sp/v/nfl_cutout/players_l/09092022/27560.png,O,"['QB', 'Q/W/R/T']",Q/W/R/T,414.l.474186.t.2,Gold Standard,Daniel,,2022,,,,2,10.0,True
+65,32756,Zack Moss,False,https://s.yimg.com/iu/api/res/1.2/WxwF7udgeEEeuQ0Wdmo85w--~C/YXBwaWQ9eXNwb3J0cztjaD0yMzM2O2NyPTE7Y3c9MTc5MDtkeD04NTc7ZHk9MDtmaT11bGNyb3A7aD02MDtxPTEwMDt3PTQ2/https://s.yimg.com/xe/i/us/sp/v/nfl_cutout/players_l/08172023/32756.png,O,"['RB', 'W/R/T', 'Q/W/R/T']",BN,414.l.474186.t.2,Gold Standard,Daniel,,2022,,,,2,10.0,True
+66,30295,Aaron Jones,False,https://s.yimg.com/iu/api/res/1.2/ez8dGQIh4snWNJ.0GsSe3Q--~C/YXBwaWQ9eXNwb3J0cztjaD0yMzM2O2NyPTE7Y3c9MTc5MDtkeD04NTc7ZHk9MDtmaT11bGNyb3A7aD02MDtxPTEwMDt3PTQ2/https://s.yimg.com/xe/i/us/sp/v/nfl_cutout/players_l/08302022/30295.png,O,"['RB', 'W/R/T', 'Q/W/R/T']",BN,414.l.474186.t.2,Gold Standard,Daniel,,2022,14.0,2.0,414.l.474186.t.2,2,1.0,True
+67,26804,Latavius Murray,False,https://s.yimg.com/iu/api/res/1.2/QbmUSNHnwM45rtIqakYvfQ--~C/YXBwaWQ9eXNwb3J0cztjaD0yMzM2O2NyPTE7Y3c9MTc5MDtkeD04NTc7ZHk9MDtmaT11bGNyb3A7aD02MDtxPTEwMDt3PTQ2/https://s.yimg.com/xe/i/us/sp/v/nfl_cutout/players_l/08012023/26804.png,O,"['RB', 'W/R/T', 'Q/W/R/T']",BN,414.l.474186.t.2,Gold Standard,Daniel,,2022,,,,2,10.0,True
+68,31107,Dalton Schultz,False,https://s.yimg.com/iu/api/res/1.2/UKeiRn.vKvlPKMjjXmQAwQ--~C/YXBwaWQ9eXNwb3J0cztjaD0yMzM2O2NyPTE7Y3c9MTc5MDtkeD04NTc7ZHk9MDtmaT11bGNyb3A7aD02MDtxPTEwMDt3PTQ2/https://s.yimg.com/xe/i/us/sp/v/nfl_cutout/players_l/08152023/31107.png,O,"['TE', 'W/R/T', 'Q/W/R/T']",BN,414.l.474186.t.2,Gold Standard,Daniel,,2022,59.0,5.0,414.l.474186.t.2,2,4.0,True
+69,30285,Isaiah McKenzie,False,https://s.yimg.com/iu/api/res/1.2/ElcW35CLgX7r2P_CB_7yJw--~C/YXBwaWQ9eXNwb3J0cztjaD0yMzM2O2NyPTE7Y3c9MTc5MDtkeD04NTc7ZHk9MDtmaT11bGNyb3A7aD02MDtxPTEwMDt3PTQ2/https://s.yimg.com/xe/i/us/sp/v/nfl_cutout/players_l/08162022/30285.png,O,"['WR', 'W/R/T', 'Q/W/R/T']",BN,414.l.474186.t.2,Gold Standard,Daniel,,2022,110.0,10.0,414.l.474186.t.2,2,9.0,True
+70,30266,Jake Elliott,False,https://s.yimg.com/iu/api/res/1.2/cJxU0FO_kfryoXKrAKY9iA--~C/YXBwaWQ9eXNwb3J0cztjaD0yMzM2O2NyPTE7Y3c9MTc5MDtkeD04NTc7ZHk9MDtmaT11bGNyb3A7aD02MDtxPTEwMDt3PTQ2/https://s.yimg.com/xe/i/us/sp/v/nfl_cutout/players_l/09052022/30266.png,K,['K'],K,414.l.474186.t.2,Gold Standard,Daniel,,2022,,,,2,10.0,True
+71,100028,Washington,False,https://s.yimg.com/cv/apiv2/default/nfl/20200909/50x50/cr/washington.gif,DT,['DEF'],DEF,414.l.474186.t.2,Gold Standard,Daniel,,2022,,,,2,10.0,True
+72,100002,Buffalo,False,https://s.yimg.com/lq/i/us/sp/v/nfl/teams/1/50x50w/buf.gif,DT,['DEF'],BN,414.l.474186.t.2,Gold Standard,Daniel,,2022,107.0,9.0,414.l.474186.t.2,2,8.0,True
+73,32675,Tua Tagovailoa,True,https://s.yimg.com/iu/api/res/1.2/S0TI9tF0kmgmb5qpOEbV8w--~C/YXBwaWQ9eXNwb3J0cztjaD0yMzM2O2NyPTE7Y3c9MTc5MDtkeD04NTc7ZHk9MDtmaT11bGNyb3A7aD02MDtxPTEwMDt3PTQ2/https://s.yimg.com/xe/i/us/sp/v/nfl_cutout/players_l/09092022/32675.png,O,"['QB', 'Q/W/R/T']",QB,414.l.474186.t.10,The Jenbots,Jen,,2022,98.0,9.0,414.l.474186.t.10,1,8.0,True
+74,27277,Adam Thielen,False,https://s.yimg.com/iu/api/res/1.2/eB_EQ1lpxyq3QGcTGyk1mQ--~C/YXBwaWQ9eXNwb3J0cztjaD0yMzM2O2NyPTE7Y3c9MTc5MDtkeD04NTc7ZHk9MDtmaT11bGNyb3A7aD02MDtxPTEwMDt3PTQ2/https://s.yimg.com/xe/i/us/sp/v/nfl_cutout/players_l/07272023/27277.png,O,"['WR', 'W/R/T', 'Q/W/R/T']",WR,414.l.474186.t.10,The Jenbots,Jen,,2022,74.0,7.0,414.l.474186.t.10,2,6.0,True
+75,33394,Jaylen Waddle,True,https://s.yimg.com/iu/api/res/1.2/H7eEPYlww.bcZ9r9gY8fPg--~C/YXBwaWQ9eXNwb3J0cztjaD0yMzM2O2NyPTE7Y3c9MTc5MDtkeD04NTc7ZHk9MDtmaT11bGNyb3A7aD02MDtxPTEwMDt3PTQ2/https://s.yimg.com/xe/i/us/sp/v/nfl_cutout/players_l/09092022/33394.png,O,"['WR', 'W/R/T', 'Q/W/R/T']",WR,414.l.474186.t.10,The Jenbots,Jen,,2022,122.0,11.0,414.l.474186.t.10,1,10.0,True
+76,30994,DJ Moore,False,https://s.yimg.com/iu/api/res/1.2/3K7Sh.Q0fLpdyh7g2QD.nA--~C/YXBwaWQ9eXNwb3J0cztjaD0yMzM2O2NyPTE7Y3c9MTc5MDtkeD04NTc7ZHk9MDtmaT11bGNyb3A7aD02MDtxPTEwMDt3PTQ2/https://s.yimg.com/xe/i/us/sp/v/nfl_cutout/players_l/08082023/30994.png,O,"['WR', 'W/R/T', 'Q/W/R/T']",WR,414.l.474186.t.10,The Jenbots,Jen,,2022,47.0,4.0,414.l.474186.t.10,2,3.0,True
+77,31005,Nick Chubb,False,https://s.yimg.com/iu/api/res/1.2/btOicZxjBcHhRuOC9nIV1g--~C/YXBwaWQ9eXNwb3J0cztjaD0yMzM2O2NyPTE7Y3c9MTc5MDtkeD04NTc7ZHk9MDtmaT11bGNyb3A7aD02MDtxPTEwMDt3PTQ2/https://s.yimg.com/xe/i/us/sp/v/nfl_cutout/players_l/09132022/31005.1.png,O,"['RB', 'W/R/T', 'Q/W/R/T']",RB,414.l.474186.t.10,The Jenbots,Jen,,2022,23.0,2.0,414.l.474186.t.10,2,1.0,True
+78,29238,Ezekiel Elliott,False,https://s.yimg.com/iu/api/res/1.2/DUODUN2PLd2SHxwOt85rLg--~C/YXBwaWQ9eXNwb3J0cztjaD0yMzM2O2NyPTE7Y3c9MTc5MDtkeD04NTc7ZHk9MDtmaT11bGNyb3A7aD02MDtxPTEwMDt3PTQ2/https://s.yimg.com/xe/i/us/sp/v/nfl_cutout/players_l/08092022/29238.png,O,"['RB', 'W/R/T', 'Q/W/R/T']",RB,414.l.474186.t.10,The Jenbots,Jen,,2022,26.0,3.0,414.l.474186.t.10,2,2.0,True
+79,34036,Greg Dulcich,False,https://s.yimg.com/iu/api/res/1.2/BpDTU3QlurqLMDIerAVnnQ--~C/YXBwaWQ9eXNwb3J0cztjaD0yMzM2O2NyPTE7Y3c9MTc5MDtkeD04NTc7ZHk9MDtmaT11bGNyb3A7aD02MDtxPTEwMDt3PTQ2/https://s.yimg.com/xe/i/us/sp/v/nfl_cutout/players_l/08262022/34036.png,O,"['TE', 'W/R/T', 'Q/W/R/T']",TE,414.l.474186.t.10,The Jenbots,Jen,Q,2022,,,,2,10.0,True
+80,31905,David Montgomery,False,https://s.yimg.com/iu/api/res/1.2/wRdrI7HtYiZfB8U243TfhQ--~C/YXBwaWQ9eXNwb3J0cztjaD0yMzM2O2NyPTE7Y3c9MTc5MDtkeD04NTc7ZHk9MDtmaT11bGNyb3A7aD02MDtxPTEwMDt3PTQ2/https://s.yimg.com/xe/i/us/sp/v/nfl_cutout/players_l/08112023/31905.png,O,"['RB', 'W/R/T', 'Q/W/R/T']",W/R/T,414.l.474186.t.10,The Jenbots,Jen,,2022,50.0,5.0,414.l.474186.t.10,2,4.0,True
+81,29235,Jared Goff,False,https://s.yimg.com/iu/api/res/1.2/Ho6Q1LTikrPbhrhWo2bwCQ--~C/YXBwaWQ9eXNwb3J0cztjaD0yMzM2O2NyPTE7Y3c9MTc5MDtkeD04NTc7ZHk9MDtmaT11bGNyb3A7aD02MDtxPTEwMDt3PTQ2/https://s.yimg.com/xe/i/us/sp/v/nfl_cutout/players_l/08302022/29235.png,O,"['QB', 'Q/W/R/T']",Q/W/R/T,414.l.474186.t.10,The Jenbots,Jen,,2022,71.0,6.0,414.l.474186.t.10,2,5.0,True
+82,29341,Chris Moore,False,https://s.yimg.com/iu/api/res/1.2/UDZSy6kjfiMqc.GHwYV9WQ--~C/YXBwaWQ9eXNwb3J0cztjaD0yMzM2O2NyPTE7Y3c9MTc5MDtkeD04NTc7ZHk9MDtmaT11bGNyb3A7aD02MDtxPTEwMDt3PTQ2/https://s.yimg.com/xe/i/us/sp/v/nfl_cutout/players_l/09052022/29341.png,O,"['WR', 'W/R/T', 'Q/W/R/T']",BN,414.l.474186.t.10,The Jenbots,Jen,,2022,,,,2,10.0,True
+83,30247,Jamaal Williams,False,https://s.yimg.com/iu/api/res/1.2/NIDjtpLVqfsxwMAUB.yA9Q--~C/YXBwaWQ9eXNwb3J0cztjaD0yMzM2O2NyPTE7Y3c9MTc5MDtkeD04NTc7ZHk9MDtmaT11bGNyb3A7aD02MDtxPTEwMDt3PTQ2/https://s.yimg.com/xe/i/us/sp/v/nfl_cutout/players_l/08082023/30247.png,O,"['RB', 'W/R/T', 'Q/W/R/T']",BN,414.l.474186.t.10,The Jenbots,Jen,,2022,143.0,12.0,414.l.474186.t.10,2,11.0,True
+84,30995,Hayden Hurst,False,https://s.yimg.com/iu/api/res/1.2/Rdja2nigOEqsGZjx6nNDtw--~C/YXBwaWQ9eXNwb3J0cztjaD0yMzM2O2NyPTE7Y3c9MTc5MDtkeD04NTc7ZHk9MDtmaT11bGNyb3A7aD02MDtxPTEwMDt3PTQ2/https://s.yimg.com/xe/i/us/sp/v/nfl_cutout/players_l/07272023/30995.png,O,"['TE', 'W/R/T', 'Q/W/R/T']",BN,414.l.474186.t.10,The Jenbots,Jen,,2022,,,,2,10.0,True
+85,24822,Andy Dalton,False,https://s.yimg.com/iu/api/res/1.2/Ohoq1e_3muN0RKhB63.mVw--~C/YXBwaWQ9eXNwb3J0cztjaD0yMzM2O2NyPTE7Y3c9MTc5MDtkeD04NTc7ZHk9MDtmaT11bGNyb3A7aD02MDtxPTEwMDt3PTQ2/https://s.yimg.com/xe/i/us/sp/v/nfl_cutout/players_l/07272023/24822.png,O,"['QB', 'Q/W/R/T']",BN,414.l.474186.t.10,The Jenbots,Jen,,2022,,,,2,10.0,True
+86,34008,Alec Pierce,False,https://s.yimg.com/iu/api/res/1.2/8OKDszvwdUD9QqJoN5h6DA--~C/YXBwaWQ9eXNwb3J0cztjaD0yMzM2O2NyPTE7Y3c9MTc5MDtkeD04NTc7ZHk9MDtmaT11bGNyb3A7aD02MDtxPTEwMDt3PTQ2/https://s.yimg.com/xe/i/us/sp/v/nfl_cutout/players_l/09012022/34008.png,O,"['WR', 'W/R/T', 'Q/W/R/T']",BN,414.l.474186.t.10,The Jenbots,Jen,,2022,,,,2,10.0,True
+87,33477,Nico Collins,False,https://s.yimg.com/iu/api/res/1.2/e6yVOlCA1IHCbRbTinF_5A--~C/YXBwaWQ9eXNwb3J0cztjaD0yMzM2O2NyPTE7Y3c9MTc5MDtkeD04NTc7ZHk9MDtmaT11bGNyb3A7aD02MDtxPTEwMDt3PTQ2/https://s.yimg.com/xe/i/us/sp/v/nfl_cutout/players_l/09052022/33477.png,O,"['WR', 'W/R/T', 'Q/W/R/T']",BN,414.l.474186.t.10,The Jenbots,Jen,Q,2022,146.0,13.0,414.l.474186.t.10,2,12.0,True
+88,33973,Treylon Burks,False,https://s.yimg.com/iu/api/res/1.2/HY6f8mRVR6_LrFEFNrwfFA--~C/YXBwaWQ9eXNwb3J0cztjaD0yMzM2O2NyPTE7Y3c9MTc5MDtkeD04NTc7ZHk9MDtmaT11bGNyb3A7aD02MDtxPTEwMDt3PTQ2/https://s.yimg.com/xe/i/us/sp/v/nfl_cutout/players_l/08182023/33973.png,O,"['WR', 'W/R/T', 'Q/W/R/T']",BN,414.l.474186.t.10,The Jenbots,Jen,,2022,136.0,12.0,414.l.474186.t.11,2,10.0,True
+89,34344,Cameron Dicker,False,https://s.yimg.com/iu/api/res/1.2/XAI1iHGCrUs2Y71Z4dZOAA--~C/YXBwaWQ9eXNwb3J0cztjaD0yMzM2O2NyPTE7Y3c9MTc5MDtkeD04NTc7ZHk9MDtmaT11bGNyb3A7aD02MDtxPTEwMDt3PTQ2/https://s.yimg.com/xe/i/us/sp/v/nfl_cutout/players_l/08112023/34344.png,K,['K'],K,414.l.474186.t.10,The Jenbots,Jen,,2022,,,,2,10.0,True
+90,100007,Denver,False,https://s.yimg.com/lq/i/us/sp/v/nfl/teams/1/50x50w/den.gif,DT,['DEF'],DEF,414.l.474186.t.10,The Jenbots,Jen,,2022,170.0,15.0,414.l.474186.t.10,2,14.0,True
+91,33637,Ben Skowronek,False,https://s.yimg.com/iu/api/res/1.2/uLa2iCk.CU0BqrQxRABn7g--~C/YXBwaWQ9eXNwb3J0cztjaD0yMzM2O2NyPTE7Y3c9MTc5MDtkeD04NTc7ZHk9MDtmaT11bGNyb3A7aD02MDtxPTEwMDt3PTQ2/https://s.yimg.com/xe/i/us/sp/v/nfl_cutout/players_l/09082022/33637.png,O,"['WR', 'W/R/T', 'Q/W/R/T']",IR,414.l.474186.t.10,The Jenbots,Jen,Q,2022,,,,2,10.0,True
+92,29369,Dak Prescott,False,https://s.yimg.com/iu/api/res/1.2/KE1Rw3r7nA8anql98tZI4Q--~C/YXBwaWQ9eXNwb3J0cztjaD0yMzM2O2NyPTE7Y3c9MTc5MDtkeD04NTc7ZHk9MDtmaT11bGNyb3A7aD02MDtxPTEwMDt3PTQ2/https://s.yimg.com/xe/i/us/sp/v/nfl_cutout/players_l/08092022/29369.png,O,"['QB', 'Q/W/R/T']",QB,414.l.474186.t.3,Donāt Fear the Keeper,David,,2022,20.0,2.0,414.l.474186.t.3,2,1.0,True
+93,30197,Chris Godwin,False,https://s.yimg.com/iu/api/res/1.2/roNVzlSNZyCVTP_YD2gH.Q--~C/YXBwaWQ9eXNwb3J0cztjaD0yMzM2O2NyPTE7Y3c9MTc5MDtkeD04NTc7ZHk9MDtmaT11bGNyb3A7aD02MDtxPTEwMDt3PTQ2/https://s.yimg.com/xe/i/us/sp/v/nfl_cutout/players_l/08182023/30197.png,O,"['WR', 'W/R/T', 'Q/W/R/T']",WR,414.l.474186.t.3,Donāt Fear the Keeper,David,,2022,68.0,6.0,414.l.474186.t.3,2,5.0,True
+94,32798,Gabe Davis,False,https://s.yimg.com/iu/api/res/1.2/FpnVozvf7BEniT.DSR1bbg--~C/YXBwaWQ9eXNwb3J0cztjaD0yMzM2O2NyPTE7Y3c9MTc5MDtkeD04NTc7ZHk9MDtmaT11bGNyb3A7aD02MDtxPTEwMDt3PTQ2/https://s.yimg.com/xe/i/us/sp/v/nfl_cutout/players_l/08162022/32798.png,O,"['WR', 'W/R/T', 'Q/W/R/T']",WR,414.l.474186.t.3,Donāt Fear the Keeper,David,,2022,29.0,3.0,414.l.474186.t.3,2,2.0,True
+95,34659,Rashid Shaheed,False,https://s.yimg.com/iu/api/res/1.2/Yv.lEisn6gZ.6d9gJUFRhg--~C/YXBwaWQ9eXNwb3J0cztjaD0yMzM2O2NyPTE7Y3c9MTc5MDtkeD04NTc7ZHk9MDtmaT11bGNyb3A7aD02MDtxPTEwMDt3PTQ2/https://s.yimg.com/xe/i/us/sp/v/nfl_cutout/players_l/09112022/34659.png,O,"['WR', 'W/R/T', 'Q/W/R/T']",WR,414.l.474186.t.3,Donāt Fear the Keeper,David,,2022,,,,2,10.0,True
+96,30154,Dalvin Cook,False,https://s.yimg.com/iu/api/res/1.2/PEteLdlAzix1arTWEMNdqQ--~C/YXBwaWQ9eXNwb3J0cztjaD0yMzM2O2NyPTE7Y3c9MTc5MDtkeD04NTc7ZHk9MDtmaT11bGNyb3A7aD02MDtxPTEwMDt3PTQ2/https://s.yimg.com/xe/i/us/sp/v/nfl_cutout/players_l/09122022/30154.png,O,"['RB', 'W/R/T', 'Q/W/R/T']",RB,414.l.474186.t.3,Donāt Fear the Keeper,David,Q,2022,5.0,1.0,414.l.474186.t.3,2,0.0,False
+97,26652,Cordarrelle Patterson,True,https://s.yimg.com/iu/api/res/1.2/iGQ1kha0acy.9HNIc2OXiw--~C/YXBwaWQ9eXNwb3J0cztjaD0yMzM2O2NyPTE7Y3c9MTc5MDtkeD04NTc7ZHk9MDtmaT11bGNyb3A7aD02MDtxPTEwMDt3PTQ2/https://s.yimg.com/xe/i/us/sp/v/nfl_cutout/players_l/08192022/26652.png,O,"['RB', 'W/R/T', 'Q/W/R/T']",RB,414.l.474186.t.3,Donāt Fear the Keeper,David,,2022,101.0,9.0,414.l.474186.t.3,1,8.0,True
+98,29269,Hunter Henry,False,https://s.yimg.com/iu/api/res/1.2/NEm1DAWA.yuFGnX1llY2CA--~C/YXBwaWQ9eXNwb3J0cztjaD0yMzM2O2NyPTE7Y3c9MTc5MDtkeD04NTc7ZHk9MDtmaT11bGNyb3A7aD02MDtxPTEwMDt3PTQ2/https://s.yimg.com/xe/i/us/sp/v/nfl_cutout/players_l/09122022/29269.png,O,"['TE', 'W/R/T', 'Q/W/R/T']",TE,414.l.474186.t.3,Donāt Fear the Keeper,David,,2022,167.0,14.0,414.l.474186.t.10,2,10.0,True
+99,31051,Michael Gallup,True,https://s.yimg.com/iu/api/res/1.2/rc2qooGsh7s6eFSb2i4UIg--~C/YXBwaWQ9eXNwb3J0cztjaD0yMzM2O2NyPTE7Y3c9MTc5MDtkeD04NTc7ZHk9MDtmaT11bGNyb3A7aD02MDtxPTEwMDt3PTQ2/https://s.yimg.com/xe/i/us/sp/v/nfl_cutout/players_l/08092022/31051.png,O,"['WR', 'W/R/T', 'Q/W/R/T']",W/R/T,414.l.474186.t.3,Donāt Fear the Keeper,David,Q,2022,116.0,10.0,414.l.474186.t.3,1,9.0,True
+100,33389,Trevor Lawrence,False,https://s.yimg.com/iu/api/res/1.2/dTvmt7jY2XE8d0K6jMnytQ--~C/YXBwaWQ9eXNwb3J0cztjaD0yMzM2O2NyPTE7Y3c9MTc5MDtkeD04NTc7ZHk9MDtmaT11bGNyb3A7aD02MDtxPTEwMDt3PTQ2/https://s.yimg.com/xe/i/us/sp/v/nfl_cutout/players_l/09022022/33389.png,O,"['QB', 'Q/W/R/T']",Q/W/R/T,414.l.474186.t.3,Donāt Fear the Keeper,David,,2022,44.0,4.0,414.l.474186.t.3,2,3.0,True
+101,32736,Antonio Gibson,False,https://s.yimg.com/iu/api/res/1.2/6ofK.flGUG.stOTsHIfLjQ--~C/YXBwaWQ9eXNwb3J0cztjaD0yMzM2O2NyPTE7Y3c9MTc5MDtkeD04NTc7ZHk9MDtmaT11bGNyb3A7aD02MDtxPTEwMDt3PTQ2/https://s.yimg.com/xe/i/us/sp/v/nfl_cutout/players_l/08172023/32736.1.png,O,"['RB', 'W/R/T', 'Q/W/R/T']",BN,414.l.474186.t.3,Donāt Fear the Keeper,David,Q,2022,53.0,5.0,414.l.474186.t.3,2,4.0,True
+102,32003,Darius Slayton,False,https://s.yimg.com/iu/api/res/1.2/E8si81KuaPrD2lFQ0GtLbw--~C/YXBwaWQ9eXNwb3J0cztjaD0yMzM2O2NyPTE7Y3c9MTc5MDtkeD04NTc7ZHk9MDtmaT11bGNyb3A7aD02MDtxPTEwMDt3PTQ2/https://s.yimg.com/xe/i/us/sp/v/nfl_cutout/players_l/09102022/32003.1.png,O,"['WR', 'W/R/T', 'Q/W/R/T']",BN,414.l.474186.t.3,Donāt Fear the Keeper,David,,2022,,,,2,10.0,True
+103,28839,Taylor Heinicke,False,https://s.yimg.com/iu/api/res/1.2/89aG76v4MRKpCk2hfNizJw--~C/YXBwaWQ9eXNwb3J0cztjaD0yMzM2O2NyPTE7Y3c9MTc5MDtkeD04NTc7ZHk9MDtmaT11bGNyb3A7aD02MDtxPTEwMDt3PTQ2/https://s.yimg.com/xe/i/us/sp/v/nfl_cutout/players_l/07272023/28839.png,O,"['QB', 'Q/W/R/T']",BN,414.l.474186.t.3,Donāt Fear the Keeper,David,,2022,,,,2,10.0,True
+104,31934,Alexander Mattison,False,https://s.yimg.com/iu/api/res/1.2/4Td_gb.cVkIChxsrHAqvjA--~C/YXBwaWQ9eXNwb3J0cztjaD0yMzM2O2NyPTE7Y3c9MTc5MDtkeD04NTc7ZHk9MDtmaT11bGNyb3A7aD02MDtxPTEwMDt3PTQ2/https://s.yimg.com/xe/i/us/sp/v/nfl_cutout/players_l/09122022/31934.png,O,"['RB', 'W/R/T', 'Q/W/R/T']",BN,414.l.474186.t.3,Donāt Fear the Keeper,David,,2022,125.0,11.0,414.l.474186.t.3,2,10.0,True
+105,33113,Juwan Johnson,False,https://s.yimg.com/iu/api/res/1.2/_eJHA9kCwXPo6vNRfEt2sQ--~C/YXBwaWQ9eXNwb3J0cztjaD0yMzM2O2NyPTE7Y3c9MTc5MDtkeD04NTc7ZHk9MDtmaT11bGNyb3A7aD02MDtxPTEwMDt3PTQ2/https://s.yimg.com/xe/i/us/sp/v/nfl_cutout/players_l/09112022/33113.png,O,"['TE', 'W/R/T', 'Q/W/R/T']",BN,414.l.474186.t.3,Donāt Fear the Keeper,David,,2022,,,,2,10.0,True
+106,30973,Sam Darnold,False,https://s.yimg.com/iu/api/res/1.2/IIsm16mUSjOkQICx7Duc8w--~C/YXBwaWQ9eXNwb3J0cztjaD0yMzM2O2NyPTE7Y3c9MTc5MDtkeD04NTc7ZHk9MDtmaT11bGNyb3A7aD02MDtxPTEwMDt3PTQ2/https://s.yimg.com/xe/i/us/sp/v/nfl_cutout/players_l/07262023/30973.png,O,"['QB', 'Q/W/R/T']",BN,414.l.474186.t.3,Donāt Fear the Keeper,David,,2022,,,,2,10.0,True
+107,29236,Carson Wentz,False,https://s.yimg.com/iu/api/res/1.2/CZ4byGFTeTX_PjRU9.kRuQ--~C/YXBwaWQ9eXNwb3J0cztjaD0yMzM2O2NyPTE7Y3c9MTc5MDtkeD04NTc7ZHk9MDtmaT11bGNyb3A7aD02MDtxPTEwMDt3PTQ2/https://s.yimg.com/xe/i/us/sp/v/nfl_cutout/players_l/09022022/29236.png,O,"['QB', 'Q/W/R/T']",BN,414.l.474186.t.3,Donāt Fear the Keeper,David,NA,2022,149.0,13.0,414.l.474186.t.3,2,12.0,True
+108,8432,Nick Folk,False,https://s.yimg.com/iu/api/res/1.2/F0y5frwLiZA3eW7iKP_jMw--~C/YXBwaWQ9eXNwb3J0cztjaD0yMzM2O2NyPTE7Y3c9MTc5MDtkeD04NTc7ZHk9MDtmaT11bGNyb3A7aD02MDtxPTEwMDt3PTQ2/https://s.yimg.com/xe/i/us/sp/v/nfl_cutout/players_l/09122022/8432.png,K,['K'],K,414.l.474186.t.3,Donāt Fear the Keeper,David,,2022,,,,2,10.0,True
+109,100027,Tampa Bay,False,https://s.yimg.com/lq/i/us/sp/v/nfl/teams/1/50x50w/tam.gif,DT,['DEF'],DEF,414.l.474186.t.3,Donāt Fear the Keeper,David,,2022,140.0,12.0,414.l.474186.t.3,2,11.0,True
+110,33437,Rondale Moore,False,https://s.yimg.com/iu/api/res/1.2/yx6mu2I6XLuhhF0bScblZA--~C/YXBwaWQ9eXNwb3J0cztjaD0yMzM2O2NyPTE7Y3c9MTc5MDtkeD04NTc7ZHk9MDtmaT11bGNyb3A7aD02MDtxPTEwMDt3PTQ2/https://s.yimg.com/xe/i/us/sp/v/nfl_cutout/players_l/09122022/33437.png,O,"['WR', 'W/R/T', 'Q/W/R/T']",IR,414.l.474186.t.3,Donāt Fear the Keeper,David,,2022,164.0,14.0,414.l.474186.t.3,2,13.0,True
+111,7200,Aaron Rodgers,False,https://s.yimg.com/iu/api/res/1.2/wBl5RxV59_1mMQkQHhlz_A--~C/YXBwaWQ9eXNwb3J0cztjaD0yMzM2O2NyPTE7Y3c9MTc5MDtkeD04NTc7ZHk9MDtmaT11bGNyb3A7aD02MDtxPTEwMDt3PTQ2/https://s.yimg.com/xe/i/us/sp/v/nfl_cutout/players_l/08022023/7200.png,O,"['QB', 'Q/W/R/T']",QB,414.l.474186.t.11,Fired on my day off,Cobi,,2022,33.0,3.0,414.l.474186.t.11,2,2.0,True
+112,32687,CeeDee Lamb,True,https://s.yimg.com/iu/api/res/1.2/AaMdFg_1O0IbpNi3gFRxGg--~C/YXBwaWQ9eXNwb3J0cztjaD0yMzM2O2NyPTE7Y3c9MTc5MDtkeD04NTc7ZHk9MDtmaT11bGNyb3A7aD02MDtxPTEwMDt3PTQ2/https://s.yimg.com/xe/i/us/sp/v/nfl_cutout/players_l/08092022/32687.png,O,"['WR', 'W/R/T', 'Q/W/R/T']",WR,414.l.474186.t.11,Fired on my day off,Cobi,,2022,81.0,7.0,414.l.474186.t.11,0,6.0,False
+113,32695,Brandon Aiyuk,True,https://s.yimg.com/iu/api/res/1.2/mLlExLggroKYPjOXnGp50A--~C/YXBwaWQ9eXNwb3J0cztjaD0yMzM2O2NyPTE7Y3c9MTc5MDtkeD04NTc7ZHk9MDtmaT11bGNyb3A7aD02MDtxPTEwMDt3PTQ2/https://s.yimg.com/xe/i/us/sp/v/nfl_cutout/players_l/09062022/32695.png,O,"['WR', 'W/R/T', 'Q/W/R/T']",WR,414.l.474186.t.11,Fired on my day off,Cobi,,2022,88.0,8.0,414.l.474186.t.11,0,7.0,False
+114,29288,Tyler Boyd,False,https://s.yimg.com/iu/api/res/1.2/8eGzZ4Z.xXR0iPQotLXLRQ--~C/YXBwaWQ9eXNwb3J0cztjaD0yMzM2O2NyPTE7Y3c9MTc5MDtkeD04NTc7ZHk9MDtmaT11bGNyb3A7aD02MDtxPTEwMDt3PTQ2/https://s.yimg.com/xe/i/us/sp/v/nfl_cutout/players_l/08242022/29288.png,O,"['WR', 'W/R/T', 'Q/W/R/T']",WR,414.l.474186.t.11,Fired on my day off,Cobi,Q,2022,129.0,11.0,414.l.474186.t.11,2,10.0,True
+115,30218,James Conner,True,https://s.yimg.com/iu/api/res/1.2/3XWaWcJhci_WmQzjkaJk2w--~C/YXBwaWQ9eXNwb3J0cztjaD0yMzM2O2NyPTE7Y3c9MTc5MDtkeD04NTc7ZHk9MDtmaT11bGNyb3A7aD02MDtxPTEwMDt3PTQ2/https://s.yimg.com/xe/i/us/sp/v/nfl_cutout/players_l/09122022/30218.png,O,"['RB', 'W/R/T', 'Q/W/R/T']",RB,414.l.474186.t.11,Fired on my day off,Cobi,,2022,64.0,6.0,414.l.474186.t.11,1,5.0,True
+116,32722,Cam Akers,True,https://s.yimg.com/iu/api/res/1.2/yt8Azi2B44fcaSIY45M3WQ--~C/YXBwaWQ9eXNwb3J0cztjaD0yMzM2O2NyPTE7Y3c9MTc5MDtkeD04NTc7ZHk9MDtmaT11bGNyb3A7aD02MDtxPTEwMDt3PTQ2/https://s.yimg.com/xe/i/us/sp/v/nfl_cutout/players_l/09082022/32722.png,O,"['RB', 'W/R/T', 'Q/W/R/T']",RB,414.l.474186.t.11,Fired on my day off,Cobi,,2022,118.0,10.0,414.l.474186.t.8,1,9.0,True
+117,32713,Cole Kmet,False,https://s.yimg.com/iu/api/res/1.2/5OVkUh_qhJlTyp6RY6xXXQ--~C/YXBwaWQ9eXNwb3J0cztjaD0yMzM2O2NyPTE7Y3c9MTc5MDtkeD04NTc7ZHk9MDtmaT11bGNyb3A7aD02MDtxPTEwMDt3PTQ2/https://s.yimg.com/xe/i/us/sp/v/nfl_cutout/players_l/08032022/32713.png,O,"['TE', 'W/R/T', 'Q/W/R/T']",TE,414.l.474186.t.11,Fired on my day off,Cobi,,2022,95.0,8.0,414.l.474186.t.10,2,7.0,True
+118,33412,Najee Harris,False,https://s.yimg.com/iu/api/res/1.2/OR95VsfQ4o4K6THKBgp5yA--~C/YXBwaWQ9eXNwb3J0cztjaD0yMzM2O2NyPTE7Y3c9MTc5MDtkeD04NTc7ZHk9MDtmaT11bGNyb3A7aD02MDtxPTEwMDt3PTQ2/https://s.yimg.com/xe/i/us/sp/v/nfl_cutout/players_l/09062022/33412.png,O,"['RB', 'W/R/T', 'Q/W/R/T']",W/R/T,414.l.474186.t.11,Fired on my day off,Cobi,,2022,9.0,1.0,414.l.474186.t.11,2,0.0,False
+119,25785,Russell Wilson,False,https://s.yimg.com/iu/api/res/1.2/FAeFxNvgxIInhiyZhYp6FQ--~C/YXBwaWQ9eXNwb3J0cztjaD0yMzM2O2NyPTE7Y3c9MTc5MDtkeD04NTc7ZHk9MDtmaT11bGNyb3A7aD02MDtxPTEwMDt3PTQ2/https://s.yimg.com/xe/i/us/sp/v/nfl_cutout/players_l/08262022/25785.png,O,"['QB', 'Q/W/R/T']",Q/W/R/T,414.l.474186.t.11,Fired on my day off,Cobi,Q,2022,16.0,2.0,414.l.474186.t.11,2,1.0,True
+120,34107,Tyler Allgeier,False,https://s.yimg.com/iu/api/res/1.2/5tRMs86CGOTiPIS1sSl.qw--~C/YXBwaWQ9eXNwb3J0cztjaD0yMzM2O2NyPTE7Y3c9MTc5MDtkeD04NTc7ZHk9MDtmaT11bGNyb3A7aD02MDtxPTEwMDt3PTQ2/https://s.yimg.com/xe/i/us/sp/v/nfl_cutout/players_l/08192022/34107.png,O,"['RB', 'W/R/T', 'Q/W/R/T']",BN,414.l.474186.t.11,Fired on my day off,Cobi,,2022,171.0,15.0,414.l.474186.t.8,2,10.0,True
+121,31852,Noah Fant,False,https://s.yimg.com/iu/api/res/1.2/DjDtCvS.YcRZ1AIzoA8OAw--~C/YXBwaWQ9eXNwb3J0cztjaD0yMzM2O2NyPTE7Y3c9MTc5MDtkeD04NTc7ZHk9MDtmaT11bGNyb3A7aD02MDtxPTEwMDt3PTQ2/https://s.yimg.com/xe/i/us/sp/v/nfl_cutout/players_l/09082022/31852.png,O,"['TE', 'W/R/T', 'Q/W/R/T']",BN,414.l.474186.t.11,Fired on my day off,Cobi,,2022,,,,2,10.0,True
+122,28654,Raheem Mostert,False,https://s.yimg.com/iu/api/res/1.2/5B8rSiAkQu_C.T1FzwOuYQ--~C/YXBwaWQ9eXNwb3J0cztjaD0yMzM2O2NyPTE7Y3c9MTc5MDtkeD04NTc7ZHk9MDtmaT11bGNyb3A7aD02MDtxPTEwMDt3PTQ2/https://s.yimg.com/xe/i/us/sp/v/nfl_cutout/players_l/10132022/28654.png,O,"['RB', 'W/R/T', 'Q/W/R/T']",BN,414.l.474186.t.11,Fired on my day off,Cobi,,2022,112.0,10.0,414.l.474186.t.11,2,9.0,True
+123,33422,Elijah Moore,True,https://s.yimg.com/iu/api/res/1.2/Vkh0utH3qjiaFHtrNHSQRQ--~C/YXBwaWQ9eXNwb3J0cztjaD0yMzM2O2NyPTE7Y3c9MTc5MDtkeD04NTc7ZHk9MDtmaT11bGNyb3A7aD02MDtxPTEwMDt3PTQ2/https://s.yimg.com/xe/i/us/sp/v/nfl_cutout/players_l/08022023/33422.png,O,"['WR', 'W/R/T', 'Q/W/R/T']",BN,414.l.474186.t.11,Fired on my day off,Cobi,,2022,114.0,10.0,414.l.474186.t.4,1,9.0,True
+124,30182,Cooper Kupp,False,https://s.yimg.com/iu/api/res/1.2/r4cot0junNdG6BI.2PKXDA--~C/YXBwaWQ9eXNwb3J0cztjaD0yMzM2O2NyPTE7Y3c9MTc5MDtkeD04NTc7ZHk9MDtmaT11bGNyb3A7aD02MDtxPTEwMDt3PTQ2/https://s.yimg.com/xe/i/us/sp/v/nfl_cutout/players_l/09082022/30182.png,O,"['WR', 'W/R/T', 'Q/W/R/T']",BN,414.l.474186.t.11,Fired on my day off,Cobi,Q,2022,2.0,1.0,414.l.474186.t.10,2,0.0,False
+125,33391,Trey Lance,True,https://s.yimg.com/iu/api/res/1.2/H0GY3TXYTlkKJdLioJmvig--~C/YXBwaWQ9eXNwb3J0cztjaD0yMzM2O2NyPTE7Y3c9MTc5MDtkeD04NTc7ZHk9MDtmaT11bGNyb3A7aD02MDtxPTEwMDt3PTQ2/https://s.yimg.com/xe/i/us/sp/v/nfl_cutout/players_l/09062022/33391.png,O,"['QB', 'Q/W/R/T']",BN,414.l.474186.t.11,Fired on my day off,Cobi,,2022,91.0,8.0,414.l.474186.t.5,1,7.0,True
+126,31977,Matt Gay,False,https://s.yimg.com/iu/api/res/1.2/lHuyp4cfi0SGZO3XhtX5PA--~C/YXBwaWQ9eXNwb3J0cztjaD0yMzM2O2NyPTE7Y3c9MTc5MDtkeD04NTc7ZHk9MDtmaT11bGNyb3A7aD02MDtxPTEwMDt3PTQ2/https://s.yimg.com/xe/i/us/sp/v/nfl_cutout/players_l/08172023/31977.png,K,['K'],K,414.l.474186.t.11,Fired on my day off,Cobi,,2022,182.0,16.0,414.l.474186.t.2,2,10.0,True
+127,100030,Jacksonville,False,https://s.yimg.com/xe/ipt/50x50w.3.gif,DT,['DEF'],DEF,414.l.474186.t.11,Fired on my day off,Cobi,,2022,,,,2,10.0,True
+128,100024,Los Angeles,False,https://s.yimg.com/lq/i/us/sp/v/nfl/teams/1/50x50w/sdg3.jpg,DT,['DEF'],BN,414.l.474186.t.11,Fired on my day off,Cobi,,2022,211.0,18.0,414.l.474186.t.5,2,10.0,True
+129,31833,Kyler Murray,False,https://s.yimg.com/iu/api/res/1.2/.c7dDweSvYsX9He5BCXNBw--~C/YXBwaWQ9eXNwb3J0cztjaD0yMzM2O2NyPTE7Y3c9MTc5MDtkeD04NTc7ZHk9MDtmaT11bGNyb3A7aD02MDtxPTEwMDt3PTQ2/https://s.yimg.com/xe/i/us/sp/v/nfl_cutout/players_l/09122022/31833.png,O,"['QB', 'Q/W/R/T']",IR,414.l.474186.t.11,Fired on my day off,Cobi,Q,2022,11.0,1.0,414.l.474186.t.2,2,0.0,False
+130,34218,Brock Purdy,False,https://s.yimg.com/iu/api/res/1.2/g0DerZ8KoS3DfFhcmgN6eA--~C/YXBwaWQ9eXNwb3J0cztjaD0yMzM2O2NyPTE7Y3c9MTc5MDtkeD04NTc7ZHk9MDtmaT11bGNyb3A7aD02MDtxPTEwMDt3PTQ2/https://s.yimg.com/xe/i/us/sp/v/nfl_cutout/players_l/09062022/34218.png,O,"['QB', 'Q/W/R/T']",QB,414.l.474186.t.5,Only Bad Juju Here,Will,Q,2022,,,,2,10.0,True
+131,30175,JuJu Smith-Schuster,False,https://s.yimg.com/iu/api/res/1.2/VkzzBWy2DIUaQe_tV7U4vw--~C/YXBwaWQ9eXNwb3J0cztjaD0yMzM2O2NyPTE7Y3c9MTc5MDtkeD04NTc7ZHk9MDtmaT11bGNyb3A7aD02MDtxPTEwMDt3PTQ2/https://s.yimg.com/xe/i/us/sp/v/nfl_cutout/players_l/08082023/30175.png,O,"['WR', 'W/R/T', 'Q/W/R/T']",WR,414.l.474186.t.5,Only Bad Juju Here,Will,,2022,54.0,5.0,414.l.474186.t.5,2,4.0,True
+132,33393,Ja'Marr Chase,True,https://s.yimg.com/iu/api/res/1.2/BW26kQyt4fyE_Jh6VoLb_g--~C/YXBwaWQ9eXNwb3J0cztjaD0yMzM2O2NyPTE7Y3c9MTc5MDtkeD04NTc7ZHk9MDtmaT11bGNyb3A7aD02MDtxPTEwMDt3PTQ2/https://s.yimg.com/xe/i/us/sp/v/nfl_cutout/players_l/08242022/33393.png,O,"['WR', 'W/R/T', 'Q/W/R/T']",WR,414.l.474186.t.5,Only Bad Juju Here,Will,,2022,43.0,4.0,414.l.474186.t.5,1,3.0,True
+133,32704,Michael Pittman Jr.,True,https://s.yimg.com/iu/api/res/1.2/NraGNpZU47CL52KvePBgBQ--~C/YXBwaWQ9eXNwb3J0cztjaD0yMzM2O2NyPTE7Y3c9MTc5MDtkeD04NTc7ZHk9MDtmaT11bGNyb3A7aD02MDtxPTEwMDt3PTQ2/https://s.yimg.com/xe/i/us/sp/v/nfl_cutout/players_l/09012022/32704.png,O,"['WR', 'W/R/T', 'Q/W/R/T']",WR,414.l.474186.t.5,Only Bad Juju Here,Will,,2022,115.0,10.0,414.l.474186.t.5,1,9.0,True
+134,27624,Jerick McKinnon,False,https://s.yimg.com/iu/api/res/1.2/v.6YTAtt5Bx_r8IsbuF4Gw--~C/YXBwaWQ9eXNwb3J0cztjaD0yMzM2O2NyPTE7Y3c9MTc5MDtkeD04NTc7ZHk9MDtmaT11bGNyb3A7aD02MDtxPTEwMDt3PTQ2/https://s.yimg.com/xe/i/us/sp/v/nfl_cutout/players_l/09052022/27624.png,O,"['RB', 'W/R/T', 'Q/W/R/T']",RB,414.l.474186.t.5,Only Bad Juju Here,Will,,2022,,,,2,10.0,True
+135,30161,Joe Mixon,False,https://s.yimg.com/iu/api/res/1.2/UmC_omZu1fTvyauPzw.TiA--~C/YXBwaWQ9eXNwb3J0cztjaD0yMzM2O2NyPTE7Y3c9MTc5MDtkeD04NTc7ZHk9MDtmaT11bGNyb3A7aD02MDtxPTEwMDt3PTQ2/https://s.yimg.com/xe/i/us/sp/v/nfl_cutout/players_l/08242022/30161.png,O,"['RB', 'W/R/T', 'Q/W/R/T']",RB,414.l.474186.t.5,Only Bad Juju Here,Will,,2022,6.0,1.0,414.l.474186.t.5,2,0.0,False
+136,26686,Travis Kelce,False,https://s.yimg.com/iu/api/res/1.2/ROLrLEfO0ysGN.bsgSza3Q--~C/YXBwaWQ9eXNwb3J0cztjaD0yMzM2O2NyPTE7Y3c9MTc5MDtkeD04NTc7ZHk9MDtmaT11bGNyb3A7aD02MDtxPTEwMDt3PTQ2/https://s.yimg.com/xe/i/us/sp/v/nfl_cutout/players_l/09052022/26686.png,O,"['TE', 'W/R/T', 'Q/W/R/T']",TE,414.l.474186.t.5,Only Bad Juju Here,Will,,2022,19.0,2.0,414.l.474186.t.5,2,1.0,True
+137,32725,J.K. Dobbins,True,https://s.yimg.com/iu/api/res/1.2/TM_L8PnmPlQ5lh_cDwmqNw--~C/YXBwaWQ9eXNwb3J0cztjaD0yMzM2O2NyPTE7Y3c9MTc5MDtkeD04NTc7ZHk9MDtmaT11bGNyb3A7aD02MDtxPTEwMDt3PTQ2/https://s.yimg.com/xe/i/us/sp/v/nfl_cutout/players_l/08222022/32725.png,O,"['RB', 'W/R/T', 'Q/W/R/T']",W/R/T,414.l.474186.t.5,Only Bad Juju Here,Will,,2022,99.0,9.0,414.l.474186.t.8,1,8.0,True
+138,32685,Jerry Jeudy,False,https://s.yimg.com/iu/api/res/1.2/bPjROeGlioyNPk0xHBu7_w--~C/YXBwaWQ9eXNwb3J0cztjaD0yMzM2O2NyPTE7Y3c9MTc5MDtkeD04NTc7ZHk9MDtmaT11bGNyb3A7aD02MDtxPTEwMDt3PTQ2/https://s.yimg.com/xe/i/us/sp/v/nfl_cutout/players_l/08262022/32685.png,O,"['WR', 'W/R/T', 'Q/W/R/T']",Q/W/R/T,414.l.474186.t.5,Only Bad Juju Here,Will,,2022,78.0,7.0,414.l.474186.t.5,2,6.0,True
+139,33966,Chris Olave,False,https://s.yimg.com/iu/api/res/1.2/YZPLrSB.eOYS8Q5zM_uLrg--~C/YXBwaWQ9eXNwb3J0cztjaD0yMzM2O2NyPTE7Y3c9MTc5MDtkeD04NTc7ZHk9MDtmaT11bGNyb3A7aD02MDtxPTEwMDt3PTQ2/https://s.yimg.com/xe/i/us/sp/v/nfl_cutout/players_l/09112022/33966.png,O,"['WR', 'W/R/T', 'Q/W/R/T']",BN,414.l.474186.t.5,Only Bad Juju Here,Will,,2022,102.0,9.0,414.l.474186.t.5,2,8.0,True
+140,32231,Jakobi Meyers,False,https://s.yimg.com/iu/api/res/1.2/9wyBwt_A4z1ltXnpNbKD5g--~C/YXBwaWQ9eXNwb3J0cztjaD0yMzM2O2NyPTE7Y3c9MTc5MDtkeD04NTc7ZHk9MDtmaT11bGNyb3A7aD02MDtxPTEwMDt3PTQ2/https://s.yimg.com/xe/i/us/sp/v/nfl_cutout/players_l/09122022/32231.png,O,"['WR', 'W/R/T', 'Q/W/R/T']",BN,414.l.474186.t.5,Only Bad Juju Here,Will,,2022,174.0,15.0,414.l.474186.t.5,2,14.0,True
+141,30150,Zay Jones,False,https://s.yimg.com/iu/api/res/1.2/I3r.AujpA.yEcNZPQr26jw--~C/YXBwaWQ9eXNwb3J0cztjaD0yMzM2O2NyPTE7Y3c9MTc5MDtkeD04NTc7ZHk9MDtmaT11bGNyb3A7aD02MDtxPTEwMDt3PTQ2/https://s.yimg.com/xe/i/us/sp/v/nfl_cutout/players_l/09022022/30150.png,O,"['WR', 'W/R/T', 'Q/W/R/T']",BN,414.l.474186.t.5,Only Bad Juju Here,Will,,2022,,,,2,10.0,True
+142,31803,John Wolford,False,https://s.yimg.com/iu/api/res/1.2/Tv_XW9BxUjJ4.U8FJN3IGQ--~C/YXBwaWQ9eXNwb3J0cztjaD0yMzM2O2NyPTE7Y3c9MTc5MDtkeD04NTc7ZHk9MDtmaT11bGNyb3A7aD02MDtxPTEwMDt3PTQ2/https://s.yimg.com/xe/i/us/sp/v/nfl_cutout/players_l/07272023/31803.png,O,"['QB', 'Q/W/R/T']",BN,414.l.474186.t.5,Only Bad Juju Here,Will,,2022,,,,2,10.0,True
+143,30614,Taysom Hill,False,https://s.yimg.com/iu/api/res/1.2/p.1N.FtMvV4GHcMxHdjUww--~C/YXBwaWQ9eXNwb3J0cztjaD0yMzM2O2NyPTE7Y3c9MTc5MDtkeD04NTc7ZHk9MDtmaT11bGNyb3A7aD02MDtxPTEwMDt3PTQ2/https://s.yimg.com/xe/i/us/sp/v/nfl_cutout/players_l/09112022/30614.png,O,"['QB', 'TE', 'W/R/T', 'Q/W/R/T']",BN,414.l.474186.t.5,Only Bad Juju Here,Will,,2022,,,,2,10.0,True
+144,33455,Davis Mills,False,https://s.yimg.com/iu/api/res/1.2/DDzxF1vCU7TQ6cMzzRhyhA--~C/YXBwaWQ9eXNwb3J0cztjaD0yMzM2O2NyPTE7Y3c9MTc5MDtkeD04NTc7ZHk9MDtmaT11bGNyb3A7aD02MDtxPTEwMDt3PTQ2/https://s.yimg.com/xe/i/us/sp/v/nfl_cutout/players_l/09052022/33455.png,O,"['QB', 'Q/W/R/T']",BN,414.l.474186.t.5,Only Bad Juju Here,Will,,2022,158.0,14.0,414.l.474186.t.2,2,10.0,True
+145,31074,Nyheim Hines,False,https://s.yimg.com/iu/api/res/1.2/TIZ15raJKQHYY1QNCMTPSg--~C/YXBwaWQ9eXNwb3J0cztjaD0yMzM2O2NyPTE7Y3c9MTc5MDtkeD04NTc7ZHk9MDtmaT11bGNyb3A7aD02MDtxPTEwMDt3PTQ2/https://s.yimg.com/xe/i/us/sp/v/nfl_cutout/players_l/08012023/31074.png,O,"['RB', 'W/R/T', 'Q/W/R/T']",BN,414.l.474186.t.5,Only Bad Juju Here,Will,NA,2022,126.0,11.0,414.l.474186.t.5,2,10.0,True
+146,33537,Evan McPherson,False,https://s.yimg.com/iu/api/res/1.2/hwv0TonIqav8wiFggZSj6g--~C/YXBwaWQ9eXNwb3J0cztjaD0yMzM2O2NyPTE7Y3c9MTc5MDtkeD04NTc7ZHk9MDtmaT11bGNyb3A7aD02MDtxPTEwMDt3PTQ2/https://s.yimg.com/xe/i/us/sp/v/nfl_cutout/players_l/08242022/33537.png,K,['K'],K,414.l.474186.t.5,Only Bad Juju Here,Will,,2022,150.0,13.0,414.l.474186.t.5,2,12.0,True
+147,100025,San Francisco,False,https://s.yimg.com/lq/i/us/sp/v/nfl/teams/1/50x50w/sfo.gif,DT,['DEF'],DEF,414.l.474186.t.5,Only Bad Juju Here,Will,,2022,139.0,12.0,414.l.474186.t.5,2,11.0,True
+148,32993,Tyler Huntley,False,https://s.yimg.com/iu/api/res/1.2/alizLiWws0hc6yNV_gUqEQ--~C/YXBwaWQ9eXNwb3J0cztjaD0yMzM2O2NyPTE7Y3c9MTc5MDtkeD04NTc7ZHk9MDtmaT11bGNyb3A7aD02MDtxPTEwMDt3PTQ2/https://s.yimg.com/xe/i/us/sp/v/nfl_cutout/players_l/08222022/32993.png,O,"['QB', 'Q/W/R/T']",QB,414.l.474186.t.12,Watson Watsoff,Ely,,2022,,,,2,10.0,True
+149,26699,Keenan Allen,False,https://s.yimg.com/iu/api/res/1.2/gCbEcOZk_Lh8EHBnSx1isQ--~C/YXBwaWQ9eXNwb3J0cztjaD0yMzM2O2NyPTE7Y3c9MTc5MDtkeD04NTc7ZHk9MDtmaT11bGNyb3A7aD02MDtxPTEwMDt3PTQ2/https://s.yimg.com/xe/i/us/sp/v/nfl_cutout/players_l/09092022/26699.png,O,"['WR', 'W/R/T', 'Q/W/R/T']",WR,414.l.474186.t.12,Watson Watsoff,Ely,,2022,36.0,3.0,414.l.474186.t.12,2,2.0,True
+150,26664,Robert Woods,False,https://s.yimg.com/iu/api/res/1.2/yqQBSqw646fbFVhB.jGzHQ--~C/YXBwaWQ9eXNwb3J0cztjaD0yMzM2O2NyPTE7Y3c9MTc5MDtkeD04NTc7ZHk9MDtmaT11bGNyb3A7aD02MDtxPTEwMDt3PTQ2/https://s.yimg.com/xe/i/us/sp/v/nfl_cutout/players_l/08152023/26664.png,O,"['WR', 'W/R/T', 'Q/W/R/T']",WR,414.l.474186.t.12,Watson Watsoff,Ely,,2022,84.0,7.0,414.l.474186.t.12,2,6.0,True
+151,31981,Hunter Renfrow,False,https://s.yimg.com/iu/api/res/1.2/u8BAIFQqXyupdqQAw_Hm6Q--~C/YXBwaWQ9eXNwb3J0cztjaD0yMzM2O2NyPTE7Y3c9MTc5MDtkeD04NTc7ZHk9MDtmaT11bGNyb3A7aD02MDtxPTEwMDt3PTQ2/https://s.yimg.com/xe/i/us/sp/v/nfl_cutout/players_l/09092022/31981.png,O,"['WR', 'W/R/T', 'Q/W/R/T']",WR,414.l.474186.t.12,Watson Watsoff,Ely,,2022,77.0,7.0,414.l.474186.t.3,2,6.0,True
+152,31885,Miles Sanders,False,https://s.yimg.com/iu/api/res/1.2/9lD_ronp9xhQnVee4jjypA--~C/YXBwaWQ9eXNwb3J0cztjaD0yMzM2O2NyPTE7Y3c9MTc5MDtkeD04NTc7ZHk9MDtmaT11bGNyb3A7aD02MDtxPTEwMDt3PTQ2/https://s.yimg.com/xe/i/us/sp/v/nfl_cutout/players_l/07272023/31885.png,O,"['RB', 'W/R/T', 'Q/W/R/T']",RB,414.l.474186.t.12,Watson Watsoff,Ely,,2022,61.0,6.0,414.l.474186.t.12,2,5.0,True
+153,30180,Alvin Kamara,False,https://s.yimg.com/iu/api/res/1.2/viS6OEQEIhOpJ7DMpz6dBw--~C/YXBwaWQ9eXNwb3J0cztjaD0yMzM2O2NyPTE7Y3c9MTc5MDtkeD04NTc7ZHk9MDtmaT11bGNyb3A7aD02MDtxPTEwMDt3PTQ2/https://s.yimg.com/xe/i/us/sp/v/nfl_cutout/players_l/09112022/30180.png,O,"['RB', 'W/R/T', 'Q/W/R/T']",RB,414.l.474186.t.12,Watson Watsoff,Ely,,2022,13.0,2.0,414.l.474186.t.12,2,1.0,True
+154,28592,Darren Waller,False,https://s.yimg.com/iu/api/res/1.2/huxiwRVZLOt1t2B.XWU0zg--~C/YXBwaWQ9eXNwb3J0cztjaD0yMzM2O2NyPTE7Y3c9MTc5MDtkeD04NTc7ZHk9MDtmaT11bGNyb3A7aD02MDtxPTEwMDt3PTQ2/https://s.yimg.com/xe/i/us/sp/v/nfl_cutout/players_l/08072023/28592.png,O,"['TE', 'W/R/T', 'Q/W/R/T']",TE,414.l.474186.t.12,Watson Watsoff,Ely,,2022,60.0,5.0,414.l.474186.t.12,2,4.0,True
+155,34054,Brian Robinson,False,https://s.yimg.com/iu/api/res/1.2/Ryadq.naQHcX5OZnqTXSZQ--~C/YXBwaWQ9eXNwb3J0cztjaD0yMzM2O2NyPTE7Y3c9MTc5MDtkeD04NTc7ZHk9MDtmaT11bGNyb3A7aD02MDtxPTEwMDt3PTQ2/https://s.yimg.com/xe/i/us/sp/v/nfl_cutout/players_l/08172023/34054.png,O,"['RB', 'W/R/T', 'Q/W/R/T']",W/R/T,414.l.474186.t.12,Watson Watsoff,Ely,Q,2022,157.0,14.0,414.l.474186.t.12,2,13.0,True
+156,5228,Tom Brady,False,https://s.yimg.com/iu/api/res/1.2/Ol_o6nNm9irLqsJ.2ZxQHg--~C/YXBwaWQ9eXNwb3J0cztjaD0yMzM2O2NyPTE7Y3c9MTc5MDtkeD04NTc7ZHk9MDtmaT11bGNyb3A7aD02MDtxPTEwMDt3PTQ2/https://s.yimg.com/xe/i/us/sp/v/nfl_cutout/players_l/09082022/5228.png,O,"['QB', 'Q/W/R/T']",Q/W/R/T,414.l.474186.t.12,Watson Watsoff,Ely,NA,2022,37.0,4.0,414.l.474186.t.12,2,3.0,True
+157,26650,DeAndre Hopkins,False,https://s.yimg.com/iu/api/res/1.2/BSf2ob2XIUFixJRPTv850w--~C/YXBwaWQ9eXNwb3J0cztjaD0yMzM2O2NyPTE7Y3c9MTc5MDtkeD04NTc7ZHk9MDtmaT11bGNyb3A7aD02MDtxPTEwMDt3PTQ2/https://s.yimg.com/xe/i/us/sp/v/nfl_cutout/players_l/09122022/26650.png,O,"['WR', 'W/R/T', 'Q/W/R/T']",BN,414.l.474186.t.12,Watson Watsoff,Ely,,2022,109.0,10.0,414.l.474186.t.12,2,9.0,True
+158,30118,Corey Davis,False,https://s.yimg.com/iu/api/res/1.2/9VJg4F5pJoJ32he4nwbCsw--~C/YXBwaWQ9eXNwb3J0cztjaD0yMzM2O2NyPTE7Y3c9MTc5MDtkeD04NTc7ZHk9MDtmaT11bGNyb3A7aD02MDtxPTEwMDt3PTQ2/https://s.yimg.com/xe/i/us/sp/v/nfl_cutout/players_l/09022022/30118.png,O,"['WR', 'W/R/T', 'Q/W/R/T']",BN,414.l.474186.t.12,Watson Watsoff,Ely,,2022,,,,2,10.0,True
+159,31960,Tony Pollard,False,https://s.yimg.com/iu/api/res/1.2/S63xm6zuI1a5XIIfzYtO3w--~C/YXBwaWQ9eXNwb3J0cztjaD0yMzM2O2NyPTE7Y3c9MTc5MDtkeD04NTc7ZHk9MDtmaT11bGNyb3A7aD02MDtxPTEwMDt3PTQ2/https://s.yimg.com/xe/i/us/sp/v/nfl_cutout/players_l/08092022/31960.png,O,"['RB', 'W/R/T', 'Q/W/R/T']",BN,414.l.474186.t.12,Watson Watsoff,Ely,Q,2022,85.0,8.0,414.l.474186.t.12,2,7.0,True
+160,31002,Lamar Jackson,True,https://s.yimg.com/iu/api/res/1.2/0hA844ZfAfY8qn_CfORdtw--~C/YXBwaWQ9eXNwb3J0cztjaD0yMzM2O2NyPTE7Y3c9MTc5MDtkeD04NTc7ZHk9MDtmaT11bGNyb3A7aD02MDtxPTEwMDt3PTQ2/https://s.yimg.com/xe/i/us/sp/v/nfl_cutout/players_l/08222022/31002.png,O,"['QB', 'Q/W/R/T']",BN,414.l.474186.t.12,Watson Watsoff,Ely,,2022,12.0,1.0,414.l.474186.t.12,1,0.0,False
+161,29344,Tyler Higbee,False,https://s.yimg.com/iu/api/res/1.2/H7IhC3F_OhDIHM.6hyR4PQ--~C/YXBwaWQ9eXNwb3J0cztjaD0yMzM2O2NyPTE7Y3c9MTc5MDtkeD04NTc7ZHk9MDtmaT11bGNyb3A7aD02MDtxPTEwMDt3PTQ2/https://s.yimg.com/xe/i/us/sp/v/nfl_cutout/players_l/09082022/29344.png,O,"['TE', 'W/R/T', 'Q/W/R/T']",BN,414.l.474186.t.12,Watson Watsoff,Ely,,2022,,,,2,10.0,True
+162,31424,Gus Edwards,False,https://s.yimg.com/iu/api/res/1.2/QRCYxnhZdLNzkmTD37IyYQ--~C/YXBwaWQ9eXNwb3J0cztjaD0yMzM2O2NyPTE7Y3c9MTc5MDtkeD04NTc7ZHk9MDtmaT11bGNyb3A7aD02MDtxPTEwMDt3PTQ2/https://s.yimg.com/xe/i/us/sp/v/nfl_cutout/players_l/08222022/31424.png,O,"['RB', 'W/R/T', 'Q/W/R/T']",BN,414.l.474186.t.12,Watson Watsoff,Ely,,2022,205.0,18.0,414.l.474186.t.12,2,17.0,True
+163,26534,Justin Tucker,False,https://s.yimg.com/iu/api/res/1.2/J2_kNRYOP0qqVUjUAmlY5w--~C/YXBwaWQ9eXNwb3J0cztjaD0yMzM2O2NyPTE7Y3c9MTc5MDtkeD04NTc7ZHk9MDtmaT11bGNyb3A7aD02MDtxPTEwMDt3PTQ2/https://s.yimg.com/xe/i/us/sp/v/nfl_cutout/players_l/08222022/26534.png,K,['K'],K,414.l.474186.t.12,Watson Watsoff,Ely,,2022,132.0,11.0,414.l.474186.t.12,2,10.0,True
+164,7520,Robbie Gould,False,https://s.yimg.com/iu/api/res/1.2/2zhwD2efs8sK15WRX0dwjw--~C/YXBwaWQ9eXNwb3J0cztjaD0yMzM2O2NyPTE7Y3c9MTc5MDtkeD04NTc7ZHk9MDtmaT11bGNyb3A7aD02MDtxPTEwMDt3PTQ2/https://s.yimg.com/xe/i/us/sp/v/nfl_cutout/players_l/09062022/7520.png,K,['K'],BN,414.l.474186.t.12,Watson Watsoff,Ely,NA,2022,,,,2,10.0,True
+165,100006,Dallas,False,https://s.yimg.com/cv/apiv2/default/nfl/20200908/50x50/cr/cowboys.png,DT,['DEF'],DEF,414.l.474186.t.12,Watson Watsoff,Ely,,2022,181.0,16.0,414.l.474186.t.12,2,15.0,True
+166,27591,Jarvis Landry,False,https://s.yimg.com/iu/api/res/1.2/MRwafrUVqUkGohu_PCZ8.A--~C/YXBwaWQ9eXNwb3J0cztjaD0yMzM2O2NyPTE7Y3c9MTc5MDtkeD04NTc7ZHk9MDtmaT11bGNyb3A7aD02MDtxPTEwMDt3PTQ2/https://s.yimg.com/xe/i/us/sp/v/nfl_cutout/players_l/09112022/27591.png,O,"['WR', 'W/R/T', 'Q/W/R/T']",IR,414.l.474186.t.12,Watson Watsoff,Ely,NA,2022,144.0,12.0,414.l.474186.t.9,2,10.0,True
+167,26662,Geno Smith,False,https://s.yimg.com/iu/api/res/1.2/E9txpiDl4SjiijhfG_larA--~C/YXBwaWQ9eXNwb3J0cztjaD0yMzM2O2NyPTE7Y3c9MTc5MDtkeD04NTc7ZHk9MDtmaT11bGNyb3A7aD02MDtxPTEwMDt3PTQ2/https://s.yimg.com/xe/i/us/sp/v/nfl_cutout/players_l/07252023/26662.png,O,"['QB', 'Q/W/R/T']",QB,414.l.474186.t.4,Show Me the Mooney,Jeff,,2022,162.0,14.0,414.l.474186.t.4,2,13.0,True
+168,32703,Tee Higgins,True,https://s.yimg.com/iu/api/res/1.2/eelMCXUfHYBxtuwYpAzObQ--~C/YXBwaWQ9eXNwb3J0cztjaD0yMzM2O2NyPTE7Y3c9MTc5MDtkeD04NTc7ZHk9MDtmaT11bGNyb3A7aD02MDtxPTEwMDt3PTQ2/https://s.yimg.com/xe/i/us/sp/v/nfl_cutout/players_l/08242022/32703.png,O,"['WR', 'W/R/T', 'Q/W/R/T']",WR,414.l.474186.t.4,Show Me the Mooney,Jeff,,2022,31.0,3.0,414.l.474186.t.4,1,2.0,True
+169,31210,Richie James,False,https://s.yimg.com/iu/api/res/1.2/G3M0VC0mAmv2kt.xlBAmAQ--~C/YXBwaWQ9eXNwb3J0cztjaD0yMzM2O2NyPTE7Y3c9MTc5MDtkeD04NTc7ZHk9MDtmaT11bGNyb3A7aD02MDtxPTEwMDt3PTQ2/https://s.yimg.com/xe/i/us/sp/v/nfl_cutout/players_l/08152023/31210.png,O,"['WR', 'W/R/T', 'Q/W/R/T']",WR,414.l.474186.t.4,Show Me the Mooney,Jeff,,2022,,,,2,10.0,True
+170,31898,Diontae Johnson,True,https://s.yimg.com/iu/api/res/1.2/rWYS3gd7L1OSmCvaDD5O_g--~C/YXBwaWQ9eXNwb3J0cztjaD0yMzM2O2NyPTE7Y3c9MTc5MDtkeD04NTc7ZHk9MDtmaT11bGNyb3A7aD02MDtxPTEwMDt3PTQ2/https://s.yimg.com/xe/i/us/sp/v/nfl_cutout/players_l/09062022/31898.png,O,"['WR', 'W/R/T', 'Q/W/R/T']",WR,414.l.474186.t.4,Show Me the Mooney,Jeff,,2022,42.0,4.0,414.l.474186.t.4,1,3.0,True
+171,29279,Derrick Henry,False,https://s.yimg.com/iu/api/res/1.2/axybdmTm92jaZUw5ZMNwog--~C/YXBwaWQ9eXNwb3J0cztjaD0yMzM2O2NyPTE7Y3c9MTc5MDtkeD04NTc7ZHk9MDtmaT11bGNyb3A7aD02MDtxPTEwMDt3PTQ2/https://s.yimg.com/xe/i/us/sp/v/nfl_cutout/players_l/08182023/29279.png,O,"['RB', 'W/R/T', 'Q/W/R/T']",RB,414.l.474186.t.4,Show Me the Mooney,Jeff,,2022,7.0,1.0,414.l.474186.t.4,2,0.0,False
+172,32705,D'Andre Swift,False,https://s.yimg.com/iu/api/res/1.2/MKPJLDRXqjHsT2XLdZ0aJg--~C/YXBwaWQ9eXNwb3J0cztjaD0yMzM2O2NyPTE7Y3c9MTc5MDtkeD04NTc7ZHk9MDtmaT11bGNyb3A7aD02MDtxPTEwMDt3PTQ2/https://s.yimg.com/xe/i/us/sp/v/nfl_cutout/players_l/08042023/32705.png,O,"['RB', 'W/R/T', 'Q/W/R/T']",RB,414.l.474186.t.4,Show Me the Mooney,Jeff,,2022,18.0,2.0,414.l.474186.t.4,2,1.0,True
+173,31840,T.J. Hockenson,False,https://s.yimg.com/iu/api/res/1.2/gDcUMhQ1yyyobQf7E8QcxA--~C/YXBwaWQ9eXNwb3J0cztjaD0yMzM2O2NyPTE7Y3c9MTc5MDtkeD04NTc7ZHk9MDtmaT11bGNyb3A7aD02MDtxPTEwMDt3PTQ2/https://s.yimg.com/xe/i/us/sp/v/nfl_cutout/players_l/08092023/31840.png,O,"['TE', 'W/R/T', 'Q/W/R/T']",TE,414.l.474186.t.4,Show Me the Mooney,Jeff,,2022,90.0,8.0,414.l.474186.t.4,2,7.0,True
+174,33605,Khalil Herbert,False,https://s.yimg.com/iu/api/res/1.2/qdOtFpdIP3YkVtjGnwskIg--~C/YXBwaWQ9eXNwb3J0cztjaD0yMzM2O2NyPTE7Y3c9MTc5MDtkeD04NTc7ZHk9MDtmaT11bGNyb3A7aD02MDtxPTEwMDt3PTQ2/https://s.yimg.com/xe/i/us/sp/v/nfl_cutout/players_l/08032022/33605.png,O,"['RB', 'W/R/T', 'Q/W/R/T']",W/R/T,414.l.474186.t.4,Show Me the Mooney,Jeff,,2022,127.0,11.0,414.l.474186.t.4,2,10.0,True
+175,31838,Daniel Jones,False,https://s.yimg.com/iu/api/res/1.2/kZt6H9OCdJ_0aE3Bq3eC7g--~C/YXBwaWQ9eXNwb3J0cztjaD0yMzM2O2NyPTE7Y3c9MTc5MDtkeD04NTc7ZHk9MDtmaT11bGNyb3A7aD02MDtxPTEwMDt3PTQ2/https://s.yimg.com/xe/i/us/sp/v/nfl_cutout/players_l/09102022/31838.1.png,O,"['QB', 'Q/W/R/T']",Q/W/R/T,414.l.474186.t.4,Show Me the Mooney,Jeff,,2022,55.0,5.0,414.l.474186.t.4,2,4.0,True
+176,33465,Joshua Palmer,False,https://s.yimg.com/iu/api/res/1.2/JAFW6yNN.sTLPmKLZ8T.0Q--~C/YXBwaWQ9eXNwb3J0cztjaD0yMzM2O2NyPTE7Y3c9MTc5MDtkeD04NTc7ZHk9MDtmaT11bGNyb3A7aD02MDtxPTEwMDt3PTQ2/https://s.yimg.com/xe/i/us/sp/v/nfl_cutout/players_l/09092022/33465.png,O,"['WR', 'W/R/T', 'Q/W/R/T']",BN,414.l.474186.t.4,Show Me the Mooney,Jeff,,2022,186.0,16.0,414.l.474186.t.4,2,15.0,True
+177,28457,Tyler Lockett,False,https://s.yimg.com/iu/api/res/1.2/NYan.iJFfM0a3h1aJ_Qb.w--~C/YXBwaWQ9eXNwb3J0cztjaD0yMzM2O2NyPTE7Y3c9MTc5MDtkeD04NTc7ZHk9MDtmaT11bGNyb3A7aD02MDtxPTEwMDt3PTQ2/https://s.yimg.com/xe/i/us/sp/v/nfl_cutout/players_l/07252023/28457.png,O,"['WR', 'W/R/T', 'Q/W/R/T']",BN,414.l.474186.t.4,Show Me the Mooney,Jeff,,2022,103.0,9.0,414.l.474186.t.4,2,8.0,True
+178,33403,Mac Jones,False,https://s.yimg.com/iu/api/res/1.2/VVXTgj1Tqq52qPX_2eEPFA--~C/YXBwaWQ9eXNwb3J0cztjaD0yMzM2O2NyPTE7Y3c9MTc5MDtkeD04NTc7ZHk9MDtmaT11bGNyb3A7aD02MDtxPTEwMDt3PTQ2/https://s.yimg.com/xe/i/us/sp/v/nfl_cutout/players_l/09122022/33403.png,O,"['QB', 'Q/W/R/T']",BN,414.l.474186.t.4,Show Me the Mooney,Jeff,,2022,151.0,13.0,414.l.474186.t.4,2,12.0,True
+179,30142,David Njoku,False,https://s.yimg.com/iu/api/res/1.2/eGmGNbYH6BDTB6ZQQnFJQw--~C/YXBwaWQ9eXNwb3J0cztjaD0yMzM2O2NyPTE7Y3c9MTc5MDtkeD04NTc7ZHk9MDtmaT11bGNyb3A7aD02MDtxPTEwMDt3PTQ2/https://s.yimg.com/xe/i/us/sp/v/nfl_cutout/players_l/09132022/30142.png,O,"['TE', 'W/R/T', 'Q/W/R/T']",BN,414.l.474186.t.4,Show Me the Mooney,Jeff,,2022,138.0,12.0,414.l.474186.t.4,2,11.0,True
+180,32814,DeeJay Dallas,False,https://s.yimg.com/iu/api/res/1.2/bGFcLBcr0zcHTeUMUOCsmQ--~C/YXBwaWQ9eXNwb3J0cztjaD0yMzM2O2NyPTE7Y3c9MTc5MDtkeD04NTc7ZHk9MDtmaT11bGNyb3A7aD02MDtxPTEwMDt3PTQ2/https://s.yimg.com/xe/i/us/sp/v/nfl_cutout/players_l/07252023/32814.png,O,"['RB', 'W/R/T', 'Q/W/R/T']",BN,414.l.474186.t.4,Show Me the Mooney,Jeff,,2022,,,,2,10.0,True
+181,32858,Tyler Bass,False,https://s.yimg.com/iu/api/res/1.2/bG28u.Fasm8RfRkARyNM.g--~C/YXBwaWQ9eXNwb3J0cztjaD0yMzM2O2NyPTE7Y3c9MTc5MDtkeD04NTc7ZHk9MDtmaT11bGNyb3A7aD02MDtxPTEwMDt3PTQ2/https://s.yimg.com/xe/i/us/sp/v/nfl_cutout/players_l/08162022/32858.png,K,['K'],K,414.l.474186.t.4,Show Me the Mooney,Jeff,,2022,199.0,17.0,414.l.474186.t.4,2,16.0,True
+182,100005,Cleveland,False,https://s.yimg.com/lq/i/us/sp/v/nfl/teams/1/50x50w/cle.gif,DT,['DEF'],DEF,414.l.474186.t.4,Show Me the Mooney,Jeff,,2022,,,,2,10.0,True
+183,100004,Cincinnati,False,https://s.yimg.com/lq/i/us/sp/v/nfl/teams/1/50x50w/cin_2.gif,DT,['DEF'],BN,414.l.474186.t.4,Show Me the Mooney,Jeff,,2022,210.0,18.0,414.l.474186.t.4,2,17.0,True
+184,100016,Minnesota,False,https://s.yimg.com/xe/ipt/50x50w.6.gif,DT,['DEF'],BN,414.l.474186.t.4,Show Me the Mooney,Jeff,,2022,,,,2,10.0,True
+185,27564,Derek Carr,True,https://s.yimg.com/iu/api/res/1.2/k2EXQR3Hz7GtngIYEn3VIQ--~C/YXBwaWQ9eXNwb3J0cztjaD0yMzM2O2NyPTE7Y3c9MTc5MDtkeD04NTc7ZHk9MDtmaT11bGNyb3A7aD02MDtxPTEwMDt3PTQ2/https://s.yimg.com/xe/i/us/sp/v/nfl_cutout/players_l/08082023/27564.png,O,"['QB', 'Q/W/R/T']",QB,414.l.474186.t.9,Sutton On The Dock Of The Bay,Devin,,2022,121.0,11.0,414.l.474186.t.9,0,10.0,False
+186,28392,Amari Cooper,False,https://s.yimg.com/iu/api/res/1.2/T2xt0gxTthA0vH6ako9vrw--~C/YXBwaWQ9eXNwb3J0cztjaD0yMzM2O2NyPTE7Y3c9MTc5MDtkeD04NTc7ZHk9MDtmaT11bGNyb3A7aD02MDtxPTEwMDt3PTQ2/https://s.yimg.com/xe/i/us/sp/v/nfl_cutout/players_l/09132022/28392.png,O,"['WR', 'W/R/T', 'Q/W/R/T']",WR,414.l.474186.t.9,Sutton On The Dock Of The Bay,Devin,Q,2022,80.0,7.0,414.l.474186.t.9,2,6.0,True
+187,32727,Van Jefferson,False,https://s.yimg.com/iu/api/res/1.2/nOtC5FZStdnLKxi6eX9H9Q--~C/YXBwaWQ9eXNwb3J0cztjaD0yMzM2O2NyPTE7Y3c9MTc5MDtkeD04NTc7ZHk9MDtmaT11bGNyb3A7aD02MDtxPTEwMDt3PTQ2/https://s.yimg.com/xe/i/us/sp/v/nfl_cutout/players_l/09082022/32727.png,O,"['WR', 'W/R/T', 'Q/W/R/T']",WR,414.l.474186.t.9,Sutton On The Dock Of The Bay,Devin,,2022,,,,2,10.0,True
+188,31010,Courtland Sutton,True,https://s.yimg.com/iu/api/res/1.2/qjs5FP7W_un0vxmqlUowUQ--~C/YXBwaWQ9eXNwb3J0cztjaD0yMzM2O2NyPTE7Y3c9MTc5MDtkeD04NTc7ZHk9MDtmaT11bGNyb3A7aD02MDtxPTEwMDt3PTQ2/https://s.yimg.com/xe/i/us/sp/v/nfl_cutout/players_l/08262022/31010.png,O,"['WR', 'W/R/T', 'Q/W/R/T']",WR,414.l.474186.t.9,Sutton On The Dock Of The Bay,Devin,,2022,73.0,7.0,414.l.474186.t.9,1,6.0,True
+189,34019,James Cook,False,https://s.yimg.com/iu/api/res/1.2/Kwq2eYBk7s_hzE.QTsZXaA--~C/YXBwaWQ9eXNwb3J0cztjaD0yMzM2O2NyPTE7Y3c9MTc5MDtkeD04NTc7ZHk9MDtmaT11bGNyb3A7aD02MDtxPTEwMDt3PTQ2/https://s.yimg.com/xe/i/us/sp/v/nfl_cutout/players_l/08102022/34019.png,O,"['RB', 'W/R/T', 'Q/W/R/T']",RB,414.l.474186.t.9,Sutton On The Dock Of The Bay,Devin,,2022,97.0,9.0,414.l.474186.t.9,2,8.0,True
+190,33495,Michael Carter,False,https://s.yimg.com/iu/api/res/1.2/Qm1_5db1YOsD5hWtGXQlUg--~C/YXBwaWQ9eXNwb3J0cztjaD0yMzM2O2NyPTE7Y3c9MTc5MDtkeD04NTc7ZHk9MDtmaT11bGNyb3A7aD02MDtxPTEwMDt3PTQ2/https://s.yimg.com/xe/i/us/sp/v/nfl_cutout/players_l/09022022/33495.png,O,"['RB', 'W/R/T', 'Q/W/R/T']",RB,414.l.474186.t.9,Sutton On The Dock Of The Bay,Devin,,2022,96.0,8.0,414.l.474186.t.9,2,7.0,True
+191,31056,Mark Andrews,False,https://s.yimg.com/iu/api/res/1.2/y5Rn0Y2I20WZF_AhPnbrIA--~C/YXBwaWQ9eXNwb3J0cztjaD0yMzM2O2NyPTE7Y3c9MTc5MDtkeD04NTc7ZHk9MDtmaT11bGNyb3A7aD02MDtxPTEwMDt3PTQ2/https://s.yimg.com/xe/i/us/sp/v/nfl_cutout/players_l/08222022/31056.png,O,"['TE', 'W/R/T', 'Q/W/R/T']",TE,414.l.474186.t.9,Sutton On The Dock Of The Bay,Devin,,2022,25.0,3.0,414.l.474186.t.9,2,2.0,True
+192,31394,Jeff Wilson Jr.,False,https://s.yimg.com/iu/api/res/1.2/We7VtHVodVD_TH5jUJmmmg--~C/YXBwaWQ9eXNwb3J0cztjaD0yMzM2O2NyPTE7Y3c9MTc5MDtkeD04NTc7ZHk9MDtmaT11bGNyb3A7aD02MDtxPTEwMDt3PTQ2/https://s.yimg.com/xe/i/us/sp/v/nfl_cutout/players_l/11072022/31394.png,O,"['RB', 'W/R/T', 'Q/W/R/T']",W/R/T,414.l.474186.t.9,Sutton On The Dock Of The Bay,Devin,,2022,168.0,14.0,414.l.474186.t.9,2,13.0,True
+193,30125,Deshaun Watson,False,https://s.yimg.com/iu/api/res/1.2/6I6UVE5j_yQKbJvBeWm9zQ--~C/YXBwaWQ9eXNwb3J0cztjaD0yMzM2O2NyPTE7Y3c9MTc5MDtkeD04NTc7ZHk9MDtmaT11bGNyb3A7aD02MDtxPTEwMDt3PTQ2/https://s.yimg.com/xe/i/us/sp/v/nfl_cutout/players_l/09132022/30125.png,O,"['QB', 'Q/W/R/T']",Q/W/R/T,414.l.474186.t.9,Sutton On The Dock Of The Bay,Devin,,2022,195.0,17.0,414.l.474186.t.8,2,10.0,True
+194,33473,Amari Rodgers,False,https://s.yimg.com/iu/api/res/1.2/2RZA8J2wPaxhCTkKX07_bQ--~C/YXBwaWQ9eXNwb3J0cztjaD0yMzM2O2NyPTE7Y3c9MTc5MDtkeD04NTc7ZHk9MDtmaT11bGNyb3A7aD02MDtxPTEwMDt3PTQ2/https://s.yimg.com/xe/i/us/sp/v/nfl_cutout/players_l/11232022/33473.png,O,"['WR', 'W/R/T', 'Q/W/R/T']",BN,414.l.474186.t.9,Sutton On The Dock Of The Bay,Devin,,2022,,,,2,10.0,True
+195,31868,Deebo Samuel,False,https://s.yimg.com/iu/api/res/1.2/OGE5MT8Euxg9JAackPF6pw--~C/YXBwaWQ9eXNwb3J0cztjaD0yMzM2O2NyPTE7Y3c9MTc5MDtkeD04NTc7ZHk9MDtmaT11bGNyb3A7aD02MDtxPTEwMDt3PTQ2/https://s.yimg.com/xe/i/us/sp/v/nfl_cutout/players_l/09062022/31868.png,O,"['WR', 'W/R/T', 'Q/W/R/T']",BN,414.l.474186.t.9,Sutton On The Dock Of The Bay,Devin,,2022,24.0,2.0,414.l.474186.t.9,2,1.0,True
+196,32711,Jonathan Taylor,False,https://s.yimg.com/iu/api/res/1.2/G7TZVr_1W8fZLPEuYpLwkg--~C/YXBwaWQ9eXNwb3J0cztjaD0yMzM2O2NyPTE7Y3c9MTc5MDtkeD04NTc7ZHk9MDtmaT11bGNyb3A7aD02MDtxPTEwMDt3PTQ2/https://s.yimg.com/xe/i/us/sp/v/nfl_cutout/players_l/09012022/32711.png,O,"['RB', 'W/R/T', 'Q/W/R/T']",BN,414.l.474186.t.9,Sutton On The Dock Of The Bay,Devin,Q,2022,1.0,1.0,414.l.474186.t.9,2,0.0,False
+197,34063,Dameon Pierce,False,https://s.yimg.com/iu/api/res/1.2/FOZCdiPoZ3wxE6WNqFVA4g--~C/YXBwaWQ9eXNwb3J0cztjaD0yMzM2O2NyPTE7Y3c9MTc5MDtkeD04NTc7ZHk9MDtmaT11bGNyb3A7aD02MDtxPTEwMDt3PTQ2/https://s.yimg.com/xe/i/us/sp/v/nfl_cutout/players_l/09022022/34063.png,O,"['RB', 'W/R/T', 'Q/W/R/T']",BN,414.l.474186.t.9,Sutton On The Dock Of The Bay,Devin,Q,2022,48.0,4.0,414.l.474186.t.9,2,3.0,True
+198,30227,Samaje Perine,False,https://s.yimg.com/iu/api/res/1.2/t09WyiAn4WAEPdK8fOopUQ--~C/YXBwaWQ9eXNwb3J0cztjaD0yMzM2O2NyPTE7Y3c9MTc5MDtkeD04NTc7ZHk9MDtmaT11bGNyb3A7aD02MDtxPTEwMDt3PTQ2/https://s.yimg.com/xe/i/us/sp/v/nfl_cutout/players_l/08102023/30227.png,O,"['RB', 'W/R/T', 'Q/W/R/T']",BN,414.l.474186.t.9,Sutton On The Dock Of The Bay,Devin,,2022,179.0,15.0,414.l.474186.t.2,2,10.0,True
+199,33423,Javonte Williams,False,https://s.yimg.com/iu/api/res/1.2/u7lRRElEY9wFdB4IDuRPBw--~C/YXBwaWQ9eXNwb3J0cztjaD0yMzM2O2NyPTE7Y3c9MTc5MDtkeD04NTc7ZHk9MDtmaT11bGNyb3A7aD02MDtxPTEwMDt3PTQ2/https://s.yimg.com/xe/i/us/sp/v/nfl_cutout/players_l/08262022/33423.png,O,"['RB', 'W/R/T', 'Q/W/R/T']",BN,414.l.474186.t.9,Sutton On The Dock Of The Bay,Devin,Q,2022,28.0,3.0,414.l.474186.t.6,2,2.0,True
+200,30997,Rashaad Penny,True,https://s.yimg.com/iu/api/res/1.2/VaIszPrtdc5voaBaitswxg--~C/YXBwaWQ9eXNwb3J0cztjaD0yMzM2O2NyPTE7Y3c9MTc5MDtkeD04NTc7ZHk9MDtmaT11bGNyb3A7aD02MDtxPTEwMDt3PTQ2/https://s.yimg.com/xe/i/us/sp/v/nfl_cutout/players_l/08042023/30997.png,O,"['RB', 'W/R/T', 'Q/W/R/T']",BN,414.l.474186.t.9,Sutton On The Dock Of The Bay,Devin,,2022,119.0,10.0,414.l.474186.t.10,1,9.0,True
+201,30346,Harrison Butker,False,https://s.yimg.com/iu/api/res/1.2/kRja96cMmbAodp9m4xmo2Q--~C/YXBwaWQ9eXNwb3J0cztjaD0yMzM2O2NyPTE7Y3c9MTc5MDtkeD04NTc7ZHk9MDtmaT11bGNyb3A7aD02MDtxPTEwMDt3PTQ2/https://s.yimg.com/xe/i/us/sp/v/nfl_cutout/players_l/09052022/30346.png,K,['K'],K,414.l.474186.t.9,Sutton On The Dock Of The Bay,Devin,,2022,212.0,18.0,414.l.474186.t.3,2,10.0,True
+202,100033,Baltimore,False,https://s.yimg.com/lq/i/us/sp/v/nfl/teams/1/50x50w/bal_2.gif,DT,['DEF'],DEF,414.l.474186.t.9,Sutton On The Dock Of The Bay,Devin,,2022,185.0,16.0,414.l.474186.t.1,2,10.0,True
+203,34042,Malik Willis,False,https://s.yimg.com/iu/api/res/1.2/.htls9vA4b6ZtFaw0_rmbw--~C/YXBwaWQ9eXNwb3J0cztjaD0yMzM2O2NyPTE7Y3c9MTc5MDtkeD04NTc7ZHk9MDtmaT11bGNyb3A7aD02MDtxPTEwMDt3PTQ2/https://s.yimg.com/xe/i/us/sp/v/nfl_cutout/players_l/08182023/34042.png,O,"['QB', 'Q/W/R/T']",QB,414.l.474186.t.8,Robotron 7000,Ben,,2022,153.0,13.0,414.l.474186.t.11,2,10.0,True
+204,27535,Mike Evans,False,https://s.yimg.com/iu/api/res/1.2/.7Fzudy9HGhlMQCxWxBAEg--~C/YXBwaWQ9eXNwb3J0cztjaD0yMzM2O2NyPTE7Y3c9MTc5MDtkeD04NTc7ZHk9MDtmaT11bGNyb3A7aD02MDtxPTEwMDt3PTQ2/https://s.yimg.com/xe/i/us/sp/v/nfl_cutout/players_l/08182023/27535.png,O,"['WR', 'W/R/T', 'Q/W/R/T']",WR,414.l.474186.t.8,Robotron 7000,Ben,,2022,22.0,2.0,414.l.474186.t.8,2,1.0,True
+205,31017,Christian Kirk,False,https://s.yimg.com/iu/api/res/1.2/Wom.mgKC6gbvZILJMAEiJQ--~C/YXBwaWQ9eXNwb3J0cztjaD0yMzM2O2NyPTE7Y3c9MTc5MDtkeD04NTc7ZHk9MDtmaT11bGNyb3A7aD02MDtxPTEwMDt3PTQ2/https://s.yimg.com/xe/i/us/sp/v/nfl_cutout/players_l/09022022/31017.png,O,"['WR', 'W/R/T', 'Q/W/R/T']",WR,414.l.474186.t.8,Robotron 7000,Ben,Q,2022,75.0,7.0,414.l.474186.t.8,2,6.0,True
+206,33963,Drake London,False,https://s.yimg.com/iu/api/res/1.2/I2R4uFJcaejHiOMt_q6tIw--~C/YXBwaWQ9eXNwb3J0cztjaD0yMzM2O2NyPTE7Y3c9MTc5MDtkeD04NTc7ZHk9MDtmaT11bGNyb3A7aD02MDtxPTEwMDt3PTQ2/https://s.yimg.com/xe/i/us/sp/v/nfl_cutout/players_l/08192022/33963.png,O,"['WR', 'W/R/T', 'Q/W/R/T']",WR,414.l.474186.t.8,Robotron 7000,Ben,,2022,104.0,9.0,414.l.474186.t.1,2,8.0,True
+207,31856,Josh Jacobs,False,https://s.yimg.com/iu/api/res/1.2/6q.PFWRMhuvredK21Mel5Q--~C/YXBwaWQ9eXNwb3J0cztjaD0yMzM2O2NyPTE7Y3c9MTc5MDtkeD04NTc7ZHk9MDtmaT11bGNyb3A7aD02MDtxPTEwMDt3PTQ2/https://s.yimg.com/xe/i/us/sp/v/nfl_cutout/players_l/09092022/31856.png,O,"['RB', 'W/R/T', 'Q/W/R/T']",RB,414.l.474186.t.8,Robotron 7000,Ben,,2022,51.0,5.0,414.l.474186.t.8,2,4.0,True
+208,30121,Christian McCaffrey,False,https://s.yimg.com/iu/api/res/1.2/NMM4SpK6RcmQaRbcmxcRUA--~C/YXBwaWQ9eXNwb3J0cztjaD0yMzM2O2NyPTE7Y3c9MTc5MDtkeD04NTc7ZHk9MDtmaT11bGNyb3A7aD02MDtxPTEwMDt3PTQ2/https://s.yimg.com/xe/i/us/sp/v/nfl_cutout/players_l/07262023/30121.png,O,"['RB', 'W/R/T', 'Q/W/R/T']",RB,414.l.474186.t.8,Robotron 7000,Ben,,2022,3.0,1.0,414.l.474186.t.8,2,0.0,False
+209,30157,Gerald Everett,False,https://s.yimg.com/iu/api/res/1.2/X6vxfBP9CfXbn3X6T5.ulg--~C/YXBwaWQ9eXNwb3J0cztjaD0yMzM2O2NyPTE7Y3c9MTc5MDtkeD04NTc7ZHk9MDtmaT11bGNyb3A7aD02MDtxPTEwMDt3PTQ2/https://s.yimg.com/xe/i/us/sp/v/nfl_cutout/players_l/09092022/30157.png,O,"['TE', 'W/R/T', 'Q/W/R/T']",TE,414.l.474186.t.8,Robotron 7000,Ben,,2022,,,,2,10.0,True
+210,31908,Terry McLaurin,False,https://s.yimg.com/iu/api/res/1.2/6ubX.FQD1vkEgx_QRp0BIQ--~C/YXBwaWQ9eXNwb3J0cztjaD0yMzM2O2NyPTE7Y3c9MTc5MDtkeD04NTc7ZHk9MDtmaT11bGNyb3A7aD02MDtxPTEwMDt3PTQ2/https://s.yimg.com/xe/i/us/sp/v/nfl_cutout/players_l/08172023/31908.png,O,"['WR', 'W/R/T', 'Q/W/R/T']",W/R/T,414.l.474186.t.8,Robotron 7000,Ben,,2022,27.0,3.0,414.l.474186.t.8,2,2.0,True
+211,32676,Justin Herbert,True,https://s.yimg.com/iu/api/res/1.2/MkjpSwRr3oxLnp364DoFQw--~C/YXBwaWQ9eXNwb3J0cztjaD0yMzM2O2NyPTE7Y3c9MTc5MDtkeD04NTc7ZHk9MDtmaT11bGNyb3A7aD02MDtxPTEwMDt3PTQ2/https://s.yimg.com/xe/i/us/sp/v/nfl_cutout/players_l/09092022/32676.png,O,"['QB', 'Q/W/R/T']",Q/W/R/T,414.l.474186.t.8,Robotron 7000,Ben,Q,2022,94.0,8.0,414.l.474186.t.8,0,7.0,False
+212,31141,Mike White,False,https://s.yimg.com/iu/api/res/1.2/Pipjb9dyDTzyvf1DNmvaQg--~C/YXBwaWQ9eXNwb3J0cztjaD0yMzM2O2NyPTE7Y3c9MTc5MDtkeD04NTc7ZHk9MDtmaT11bGNyb3A7aD02MDtxPTEwMDt3PTQ2/https://s.yimg.com/xe/i/us/sp/v/nfl_cutout/players_l/08102023/31141.png,O,"['QB', 'Q/W/R/T']",BN,414.l.474186.t.8,Robotron 7000,Ben,,2022,,,,2,10.0,True
+213,34120,Kyren Williams,False,https://s.yimg.com/iu/api/res/1.2/eXr3FPJ36vxaxtgECBvrrA--~C/YXBwaWQ9eXNwb3J0cztjaD0yMzM2O2NyPTE7Y3c9MTc5MDtkeD04NTc7ZHk9MDtmaT11bGNyb3A7aD02MDtxPTEwMDt3PTQ2/https://s.yimg.com/xe/i/us/sp/v/nfl_cutout/players_l/09082022/34120.png,O,"['RB', 'W/R/T', 'Q/W/R/T']",BN,414.l.474186.t.8,Robotron 7000,Ben,,2022,,,,2,10.0,True
+214,34088,Romeo Doubs,False,https://s.yimg.com/iu/api/res/1.2/PNVAqx8vKPCkvep7QF55xg--~C/YXBwaWQ9eXNwb3J0cztjaD0yMzM2O2NyPTE7Y3c9MTc5MDtkeD04NTc7ZHk9MDtmaT11bGNyb3A7aD02MDtxPTEwMDt3PTQ2/https://s.yimg.com/xe/i/us/sp/v/nfl_cutout/players_l/08302022/34088.png,O,"['WR', 'W/R/T', 'Q/W/R/T']",BN,414.l.474186.t.8,Robotron 7000,Ben,,2022,142.0,12.0,414.l.474186.t.8,2,11.0,True
+215,34009,Skyy Moore,False,https://s.yimg.com/iu/api/res/1.2/UNwknWtbcShl2pH_EzW.9A--~C/YXBwaWQ9eXNwb3J0cztjaD0yMzM2O2NyPTE7Y3c9MTc5MDtkeD04NTc7ZHk9MDtmaT11bGNyb3A7aD02MDtxPTEwMDt3PTQ2/https://s.yimg.com/xe/i/us/sp/v/nfl_cutout/players_l/09022022/34009.png,O,"['WR', 'W/R/T', 'Q/W/R/T']",BN,414.l.474186.t.8,Robotron 7000,Ben,,2022,159.0,14.0,414.l.474186.t.7,2,10.0,True
+216,33967,Jameson Williams,False,https://s.yimg.com/iu/api/res/1.2/Yf4GNRg6hfpOwcphhsWmuA--~C/YXBwaWQ9eXNwb3J0cztjaD0yMzM2O2NyPTE7Y3c9MTc5MDtkeD04NTc7ZHk9MDtmaT11bGNyb3A7aD02MDtxPTEwMDt3PTQ2/https://s.yimg.com/xe/i/us/sp/v/nfl_cutout/players_l/08302022/33967.png,O,"['WR', 'W/R/T', 'Q/W/R/T']",BN,414.l.474186.t.8,Robotron 7000,Ben,SUSP,2022,190.0,16.0,414.l.474186.t.8,2,15.0,True
+217,30996,Calvin Ridley,False,https://s.yimg.com/iu/api/res/1.2/YiOVcPN_6AWTwPob4gWpRg--~C/YXBwaWQ9eXNwb3J0cztjaD0yMzM2O2NyPTE7Y3c9MTc5MDtkeD04NTc7ZHk9MDtmaT11bGNyb3A7aD02MDtxPTEwMDt3PTQ2/https://s.yimg.com/xe/i/us/sp/v/nfl_cutout/players_l/12072022/30996.png,O,"['WR', 'W/R/T', 'Q/W/R/T']",BN,414.l.474186.t.8,Robotron 7000,Ben,,2022,,,,2,10.0,True
+218,34078,Zamir White,False,https://s.yimg.com/iu/api/res/1.2/4ynGBO1xwnxQipSY6Gszbg--~C/YXBwaWQ9eXNwb3J0cztjaD0yMzM2O2NyPTE7Y3c9MTc5MDtkeD04NTc7ZHk9MDtmaT11bGNyb3A7aD02MDtxPTEwMDt3PTQ2/https://s.yimg.com/xe/i/us/sp/v/nfl_cutout/players_l/09022022/34078.png,O,"['RB', 'W/R/T', 'Q/W/R/T']",BN,414.l.474186.t.8,Robotron 7000,Ben,,2022,,,,2,10.0,True
+219,27369,Brett Maher,False,https://s.yimg.com/iu/api/res/1.2/Jd6XOuB8nwONFYN9ZDnkog--~C/YXBwaWQ9eXNwb3J0cztjaD0yMzM2O2NyPTE7Y3c9MTc5MDtkeD04NTc7ZHk9MDtmaT11bGNyb3A7aD02MDtxPTEwMDt3PTQ2/https://s.yimg.com/xe/i/us/sp/v/nfl_cutout/players_l/09222022/27369.png,K,['K'],K,414.l.474186.t.8,Robotron 7000,Ben,,2022,,,,2,10.0,True
+220,100020,New York,False,https://s.yimg.com/lq/i/us/sp/v/nfl/teams/1/50x50w/nyj.gif,DT,['DEF'],DEF,414.l.474186.t.8,Robotron 7000,Ben,,2022,,,,2,10.0,True
diff --git a/tests/run_tests.sh b/tests/run_tests.sh
new file mode 100755
index 0000000000000000000000000000000000000000..a3d5bca44b256079b6265d548204d001fc286236
--- /dev/null
+++ b/tests/run_tests.sh
@@ -0,0 +1,41 @@
+#!/bin/bash
+
+# Arguments for each test
+BLACK_ARGS="--check"
+RUFF_ARGS="check"
+MYPY_ARGS=""
+PYTEST_ARGS=""
+
+# Use -f for
+while getopts 'f' OPTION; do
+ case "$OPTION" in
+ f)
+ echo "Fix mode"
+ BLACK_ARGS=""
+ RUFF_ARGS+=" --fix"
+ ;;
+ esac
+done
+shift "$(($OPTIND -1))"
+
+testheader () {
+ echo -e '\n'
+ echo "*"$emptyvar{1..20}
+ echo $1
+ echo "*"$emptyvar{1..20}
+}
+
+APPDIR=/app/src
+TESTSDIR=/app/tests
+
+testheader "black"
+black $BLACK_ARGS $APPDIR $TESTSDIR
+
+testheader "ruff"
+ruff $RUFF_ARGS $APPDIR $TESTSDIR
+
+testheader "mypy"
+mypy $MYPY_ARGS $APPDIR $TESTSDIR
+
+testheader "pytest"
+pytest $PYTEST_ARGS $TESTSDIR/unit
\ No newline at end of file
diff --git a/tests/unit/test_empty_example.py b/tests/unit/test_empty_example.py
new file mode 100644
index 0000000000000000000000000000000000000000..a3e981b51f30e3de506d8f4d04287c632f1a56f1
--- /dev/null
+++ b/tests/unit/test_empty_example.py
@@ -0,0 +1,2 @@
+def test_nothing():
+ pass
diff --git a/update_streamlit_version.py b/update_streamlit_version.py
new file mode 100644
index 0000000000000000000000000000000000000000..704666862907e5efb07b2fe29f8121eb4f00a582
--- /dev/null
+++ b/update_streamlit_version.py
@@ -0,0 +1,22 @@
+import pathlib
+import pkg_resources
+import re
+
+
+package_name = "streamlit"
+
+with pathlib.Path("requirements.txt").open() as requirements_txt:
+ for requirement in pkg_resources.parse_requirements(requirements_txt):
+ if requirement.name == package_name:
+ version = requirement.specs[0][-1]
+ break
+print(version)
+
+
+with open("README.md", "r") as f:
+ contents = f.read()
+ replaced = re.sub(r"sdk_version:.*", f"sdk_version: {version}", contents, 1)
+
+
+with open("README.md", "w") as f:
+ f.write(replaced)