initial commit — cassandra v0.1
Containerised macro-strategy dashboard: 4-panel web UI (indicators,
portfolio, flash news, AI strategic log), MariaDB store, hourly
ingestion jobs, OpenRouter-backed AI analysis.
Ports the four prototype scripts in the parent dir (market_pulse,
flash_news, trading212, strategic_log) into async services backed by a
persistent DB and served via FastAPI + Jinja2 + HTMX. APScheduler runs
as a separate compose service for crash-safety and easier restarts.
Portfolio composition + position names come live from Trading 212;
news per-ticker headlines reuse those names. Tone (NOVICE/INTERMEDIATE/
PRO) and analysis style (DRY/SPECULATIVE) are env-configurable and
stored on each log row so historical entries show what produced them.
Default model is deepseek/deepseek-v4-flash (overridable via env).
Light/dark theme toggle, sans-serif for prose surfaces, monospace for
data. Bearer-token auth, OpenRouter monthly cost cap, RSS feeds auto-
disabled on consecutive failures.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 21:56:10 +01:00
|
|
|
# syntax=docker/dockerfile:1.7
|
|
|
|
|
FROM python:3.13-slim AS builder
|
|
|
|
|
|
|
|
|
|
ENV PIP_DISABLE_PIP_VERSION_CHECK=1 \
|
|
|
|
|
PIP_NO_CACHE_DIR=1 \
|
|
|
|
|
PYTHONDONTWRITEBYTECODE=1
|
|
|
|
|
|
|
|
|
|
WORKDIR /build
|
deps: add requirements.lock for reproducible builds
pyproject.toml uses range pins (>=) for all dependencies; without a
lockfile, a fresh `pip install .` on a different day could pull
materially different versions of fastapi, sqlalchemy, httpx, etc.
For a production-shaped service that's a reproducibility risk —
especially since we don't run a CI pipeline that would catch
"works on yesterday's container, fails on today's."
requirements.lock pins every transitive dep (60 packages) to the
exact versions running in the test container today. Dockerfile is
updated so both stages install from the lockfile first, then install
the project itself with --no-deps:
pip install -r requirements.lock
pip install --no-deps .
That way pyproject.toml's range pins document our compatible
upper-and-lower bounds, but the lockfile is what actually gets
installed on every build.
To bump deps later: bump pyproject.toml ranges, rebuild a fresh
venv, `pip freeze` it, save back to requirements.lock.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-28 00:07:38 +02:00
|
|
|
COPY pyproject.toml requirements.lock ./
|
initial commit — cassandra v0.1
Containerised macro-strategy dashboard: 4-panel web UI (indicators,
portfolio, flash news, AI strategic log), MariaDB store, hourly
ingestion jobs, OpenRouter-backed AI analysis.
Ports the four prototype scripts in the parent dir (market_pulse,
flash_news, trading212, strategic_log) into async services backed by a
persistent DB and served via FastAPI + Jinja2 + HTMX. APScheduler runs
as a separate compose service for crash-safety and easier restarts.
Portfolio composition + position names come live from Trading 212;
news per-ticker headlines reuse those names. Tone (NOVICE/INTERMEDIATE/
PRO) and analysis style (DRY/SPECULATIVE) are env-configurable and
stored on each log row so historical entries show what produced them.
Default model is deepseek/deepseek-v4-flash (overridable via env).
Light/dark theme toggle, sans-serif for prose surfaces, monospace for
data. Bearer-token auth, OpenRouter monthly cost cap, RSS feeds auto-
disabled on consecutive failures.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 21:56:10 +01:00
|
|
|
COPY app ./app
|
deps: add requirements.lock for reproducible builds
pyproject.toml uses range pins (>=) for all dependencies; without a
lockfile, a fresh `pip install .` on a different day could pull
materially different versions of fastapi, sqlalchemy, httpx, etc.
For a production-shaped service that's a reproducibility risk —
especially since we don't run a CI pipeline that would catch
"works on yesterday's container, fails on today's."
requirements.lock pins every transitive dep (60 packages) to the
exact versions running in the test container today. Dockerfile is
updated so both stages install from the lockfile first, then install
the project itself with --no-deps:
pip install -r requirements.lock
pip install --no-deps .
That way pyproject.toml's range pins document our compatible
upper-and-lower bounds, but the lockfile is what actually gets
installed on every build.
To bump deps later: bump pyproject.toml ranges, rebuild a fresh
venv, `pip freeze` it, save back to requirements.lock.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-28 00:07:38 +02:00
|
|
|
# requirements.lock pins every transitive dependency to the known-good
|
|
|
|
|
# versions captured by `pip freeze` against a clean install. Install
|
|
|
|
|
# from it first, then add the project itself with --no-deps so the
|
|
|
|
|
# lockfile is the single source of truth and pyproject's range pins
|
|
|
|
|
# (>=) can't drift on rebuild.
|
initial commit — cassandra v0.1
Containerised macro-strategy dashboard: 4-panel web UI (indicators,
portfolio, flash news, AI strategic log), MariaDB store, hourly
ingestion jobs, OpenRouter-backed AI analysis.
Ports the four prototype scripts in the parent dir (market_pulse,
flash_news, trading212, strategic_log) into async services backed by a
persistent DB and served via FastAPI + Jinja2 + HTMX. APScheduler runs
as a separate compose service for crash-safety and easier restarts.
Portfolio composition + position names come live from Trading 212;
news per-ticker headlines reuse those names. Tone (NOVICE/INTERMEDIATE/
PRO) and analysis style (DRY/SPECULATIVE) are env-configurable and
stored on each log row so historical entries show what produced them.
Default model is deepseek/deepseek-v4-flash (overridable via env).
Light/dark theme toggle, sans-serif for prose surfaces, monospace for
data. Bearer-token auth, OpenRouter monthly cost cap, RSS feeds auto-
disabled on consecutive failures.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 21:56:10 +01:00
|
|
|
RUN python -m venv /opt/venv \
|
|
|
|
|
&& /opt/venv/bin/pip install --upgrade pip \
|
deps: add requirements.lock for reproducible builds
pyproject.toml uses range pins (>=) for all dependencies; without a
lockfile, a fresh `pip install .` on a different day could pull
materially different versions of fastapi, sqlalchemy, httpx, etc.
For a production-shaped service that's a reproducibility risk —
especially since we don't run a CI pipeline that would catch
"works on yesterday's container, fails on today's."
requirements.lock pins every transitive dep (60 packages) to the
exact versions running in the test container today. Dockerfile is
updated so both stages install from the lockfile first, then install
the project itself with --no-deps:
pip install -r requirements.lock
pip install --no-deps .
That way pyproject.toml's range pins document our compatible
upper-and-lower bounds, but the lockfile is what actually gets
installed on every build.
To bump deps later: bump pyproject.toml ranges, rebuild a fresh
venv, `pip freeze` it, save back to requirements.lock.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-28 00:07:38 +02:00
|
|
|
&& /opt/venv/bin/pip install -r requirements.lock \
|
|
|
|
|
&& /opt/venv/bin/pip install --no-deps .
|
initial commit — cassandra v0.1
Containerised macro-strategy dashboard: 4-panel web UI (indicators,
portfolio, flash news, AI strategic log), MariaDB store, hourly
ingestion jobs, OpenRouter-backed AI analysis.
Ports the four prototype scripts in the parent dir (market_pulse,
flash_news, trading212, strategic_log) into async services backed by a
persistent DB and served via FastAPI + Jinja2 + HTMX. APScheduler runs
as a separate compose service for crash-safety and easier restarts.
Portfolio composition + position names come live from Trading 212;
news per-ticker headlines reuse those names. Tone (NOVICE/INTERMEDIATE/
PRO) and analysis style (DRY/SPECULATIVE) are env-configurable and
stored on each log row so historical entries show what produced them.
Default model is deepseek/deepseek-v4-flash (overridable via env).
Light/dark theme toggle, sans-serif for prose surfaces, monospace for
data. Bearer-token auth, OpenRouter monthly cost cap, RSS feeds auto-
disabled on consecutive failures.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 21:56:10 +01:00
|
|
|
|
|
|
|
|
FROM python:3.13-slim AS runtime
|
|
|
|
|
|
|
|
|
|
ENV PYTHONUNBUFFERED=1 \
|
|
|
|
|
PYTHONDONTWRITEBYTECODE=1 \
|
|
|
|
|
PATH="/opt/venv/bin:$PATH" \
|
|
|
|
|
TZ=UTC
|
|
|
|
|
|
|
|
|
|
RUN apt-get update \
|
|
|
|
|
&& apt-get install -y --no-install-recommends curl \
|
|
|
|
|
&& rm -rf /var/lib/apt/lists/*
|
|
|
|
|
|
|
|
|
|
COPY --from=builder /opt/venv /opt/venv
|
|
|
|
|
WORKDIR /app
|
|
|
|
|
COPY app ./app
|
|
|
|
|
COPY alembic ./alembic
|
|
|
|
|
COPY alembic.ini ./
|
|
|
|
|
|
|
|
|
|
# Default command is the web app; scheduler container overrides via `command:`.
|
|
|
|
|
EXPOSE 8000
|
|
|
|
|
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000", "--workers", "1"]
|
2026-05-25 23:58:55 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
# Test stage — same Python, same prod deps, plus dev extras (pytest +
|
|
|
|
|
# aiosqlite). Built and run only via docker-compose.test.yml; never shipped.
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
FROM python:3.13-slim AS test
|
|
|
|
|
|
|
|
|
|
ENV PYTHONUNBUFFERED=1 \
|
|
|
|
|
PYTHONDONTWRITEBYTECODE=1 \
|
|
|
|
|
PATH="/opt/venv/bin:$PATH" \
|
|
|
|
|
TZ=UTC \
|
|
|
|
|
PIP_DISABLE_PIP_VERSION_CHECK=1 \
|
|
|
|
|
PIP_NO_CACHE_DIR=1
|
|
|
|
|
|
|
|
|
|
COPY --from=builder /opt/venv /opt/venv
|
|
|
|
|
WORKDIR /app
|
deps: add requirements.lock for reproducible builds
pyproject.toml uses range pins (>=) for all dependencies; without a
lockfile, a fresh `pip install .` on a different day could pull
materially different versions of fastapi, sqlalchemy, httpx, etc.
For a production-shaped service that's a reproducibility risk —
especially since we don't run a CI pipeline that would catch
"works on yesterday's container, fails on today's."
requirements.lock pins every transitive dep (60 packages) to the
exact versions running in the test container today. Dockerfile is
updated so both stages install from the lockfile first, then install
the project itself with --no-deps:
pip install -r requirements.lock
pip install --no-deps .
That way pyproject.toml's range pins document our compatible
upper-and-lower bounds, but the lockfile is what actually gets
installed on every build.
To bump deps later: bump pyproject.toml ranges, rebuild a fresh
venv, `pip freeze` it, save back to requirements.lock.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-28 00:07:38 +02:00
|
|
|
COPY pyproject.toml requirements.lock ./
|
2026-05-25 23:58:55 +02:00
|
|
|
COPY app ./app
|
|
|
|
|
COPY alembic ./alembic
|
|
|
|
|
COPY alembic.ini ./
|
2026-05-26 00:11:18 +02:00
|
|
|
# tests/ is excluded by .dockerignore (prod-correct: never bake tests into
|
|
|
|
|
# a shipped image). docker-compose.test.yml bind-mounts ./tests:/app/tests
|
|
|
|
|
# at run time, so the suite is always available without baking it in.
|
2026-05-25 23:58:55 +02:00
|
|
|
|
deps: add requirements.lock for reproducible builds
pyproject.toml uses range pins (>=) for all dependencies; without a
lockfile, a fresh `pip install .` on a different day could pull
materially different versions of fastapi, sqlalchemy, httpx, etc.
For a production-shaped service that's a reproducibility risk —
especially since we don't run a CI pipeline that would catch
"works on yesterday's container, fails on today's."
requirements.lock pins every transitive dep (60 packages) to the
exact versions running in the test container today. Dockerfile is
updated so both stages install from the lockfile first, then install
the project itself with --no-deps:
pip install -r requirements.lock
pip install --no-deps .
That way pyproject.toml's range pins document our compatible
upper-and-lower bounds, but the lockfile is what actually gets
installed on every build.
To bump deps later: bump pyproject.toml ranges, rebuild a fresh
venv, `pip freeze` it, save back to requirements.lock.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-28 00:07:38 +02:00
|
|
|
# The lockfile already contains the dev extras (pytest, ruff, aiosqlite,
|
|
|
|
|
# ...) because it was generated against a test-stage install. Same
|
|
|
|
|
# install pattern as the builder stage: lockfile first, project --no-deps.
|
|
|
|
|
RUN /opt/venv/bin/pip install -r requirements.lock \
|
|
|
|
|
&& /opt/venv/bin/pip install --no-deps .
|
2026-05-25 23:58:55 +02:00
|
|
|
|
|
|
|
|
CMD ["pytest", "tests/", "-v"]
|