diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..99a4800 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,35 @@ +# Python +__pycache__/ +*.pyc +*.pyo +*.pyd +.Python + +# Virtual environments +.venv/ +venv/ + +# Development tools +.idea/ +.vscode/ +*.egg-info/ + +# uv cache +.uv/ + +# Local output +dist/ + +# Don't bake data into the image — mount it at runtime +# data.csv is excluded here so it must be provided via docker-compose volume +data.csv + +# Git +.git/ +.gitignore + +# Docker itself +Dockerfile +docker-compose.yml +.dockerignore + diff --git a/.gitignore b/.gitignore index 29caf69..1c69b55 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ .idea +.venv __pycache__/ diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..84a4b81 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,40 @@ +# syntax=docker/dockerfile:1 +FROM python:3.13-slim + +# Pull uv binary from the official image (no pip overhead) +COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /usr/local/bin/ + +# Non-root user for security +RUN adduser --disabled-password --no-create-home appuser + +WORKDIR /app + +# Install dependencies first (layer is cached unless lock file changes) +COPY pyproject.toml uv.lock ./ +RUN uv sync --frozen --no-dev --no-install-project + +# Copy application source +COPY src/ ./src/ + +# data.csv is NOT baked into the image — it is mounted at runtime via docker-compose +# so updates to the CSV don't require a rebuild. + +RUN chown -R appuser:appuser /app +USER appuser + +# WORKDIR=/app → gunicorn CWD is /app → data.csv resolves to /app/data.csv (mounted volume) +# PYTHONPATH=/app/src → 'app' module resolves to src/app.py +ENV PYTHONPATH=/app/src + +EXPOSE 8000 + +HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \ + CMD python -c "import urllib.request; urllib.request.urlopen('http://localhost:8000')" + +CMD ["uv", "run", "--no-dev", "gunicorn", \ + "--workers", "2", \ + "--bind", "0.0.0.0:8000", \ + "--access-logfile", "-", \ + "--error-logfile", "-", \ + "app:app"] + diff --git a/dist/index.html b/dist/index.html new file mode 100644 index 0000000..d72648a --- /dev/null +++ b/dist/index.html @@ -0,0 +1,506 @@ + + + + + + + The Hope Pub Quiz + + + + + + + +
+
+

Performance Dashboard

+

The Hope Pub Quiz

+

Trying to convince ourselves we're not stupid

+
+
+ +
+ + +
+

At a Glance

+
+ +
+ 23 + Quizzes Played + Since Mar 2025 +
+ +
+ 61st + Avg. Relative Percentile + Lower = better finishing position +
+ +
+ 4th place + Best Finish + Out of 12 teams +
+ +
+ 68th + Recent Form + ↑ declining vs overall (lower is better) +
+ +
+ 5 + Top-Half Finishes + 22% of all games +
+ +
+ 4 wks + Best Streak + Consecutive weeks attended +
+ +
+
+ + +
+ + +
+

More Stats

+
+ +
+
Top-half finishes
+
5 of 23 (22%)
+
+ +
+
Average teams per night
+
11.8
+
+ +
+
Average squad size
+
2.9 players
+
+ +
+
Average Scattergories score
+
6.9 pts
+
+ +
+
Best Scattergories score
+
11 pts
+
+ +
+
Longest individual streak
+
14 weeks (Jay)
+
+ +
+
+ + +
+

Squad

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ Player + + Appearances + + Avg. Relative Percentile + + Spent +
+ Jay + + 22 + + 62nd + + £66.00 +
+ Ciaran + + 20 + + 62nd + + £60.00 +
+ Sam + + 9 + + 59th + + £27.00 +
+ Ellora + + 5 + + 52nd + + £15.00 +
+ Drew + + 3 + + 56th + + £9.00 +
+ Tom + + 2 + + 62nd + + £6.00 +
+ Chloe + + 2 + + 75th + + £6.00 +
+ Theo + + 1 + + 50th + + £3.00 +
+ Jamie + + 1 + + 50th + + £3.00 +
+ Christine + + 1 + + 50th + + £3.00 +
+ Mide + + 1 + + 100th + + £3.00 +
+ Total + + 67 + + — + + £201.00 +
+
+ +
+ + +
+

Charts

+
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ + + + + + + \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index 59ca144..1e0719b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -6,6 +6,7 @@ readme = "README.md" requires-python = ">=3.13" dependencies = [ "flask>=3.1.1", + "gunicorn>=23.0.0", "pandas>=2.3.0", "plotly>=6.1.2", "scikit-learn>=1.7.0", diff --git a/src/templates/index.html b/src/templates/index.html index 26707bf..8c16ab5 100644 --- a/src/templates/index.html +++ b/src/templates/index.html @@ -15,7 +15,7 @@

Performance Dashboard

-

🍺 The Hope Pub Quiz

+

The Hope Pub Quiz

Trying to convince ourselves we're not stupid

diff --git a/uv.lock b/uv.lock index 104a16d..b0eb93e 100644 --- a/uv.lock +++ b/uv.lock @@ -49,6 +49,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/ec/f9/7f9263c5695f4bd0023734af91bedb2ff8209e8de6ead162f35d8dc762fd/flask-3.1.2-py3-none-any.whl", hash = "sha256:ca1d8112ec8a6158cc29ea4858963350011b5c846a414cdb7a954aa9e967d03c", size = 103308, upload-time = "2025-08-19T21:03:19.499Z" }, ] +[[package]] +name = "gunicorn" +version = "25.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "packaging" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/66/13/ef67f59f6a7896fdc2c1d62b5665c5219d6b0a9a1784938eb9a28e55e128/gunicorn-25.1.0.tar.gz", hash = "sha256:1426611d959fa77e7de89f8c0f32eed6aa03ee735f98c01efba3e281b1c47616", size = 594377, upload-time = "2026-02-13T11:09:58.989Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/da/73/4ad5b1f6a2e21cf1e85afdaad2b7b1a933985e2f5d679147a1953aaa192c/gunicorn-25.1.0-py3-none-any.whl", hash = "sha256:d0b1236ccf27f72cfe14bce7caadf467186f19e865094ca84221424e839b8b8b", size = 197067, upload-time = "2026-02-13T11:09:57.146Z" }, +] + [[package]] name = "itsdangerous" version = "2.2.0" @@ -272,6 +284,7 @@ version = "0.1.0" source = { virtual = "." } dependencies = [ { name = "flask" }, + { name = "gunicorn" }, { name = "pandas" }, { name = "plotly" }, { name = "scikit-learn" }, @@ -281,6 +294,7 @@ dependencies = [ [package.metadata] requires-dist = [ { name = "flask", specifier = ">=3.1.1" }, + { name = "gunicorn", specifier = ">=23.0.0" }, { name = "pandas", specifier = ">=2.3.0" }, { name = "plotly", specifier = ">=6.1.2" }, { name = "scikit-learn", specifier = ">=1.7.0" },