Geopolitical and Macro Analyst for the upcoming big crisis of 2026 https://read.markets
Find a file
Giorgio Gilestro f3ac65f8f7 auth: affirmative versioned sign-up acknowledgement (EN + IT)
Replaces the passive "by signing in you agree…" paragraph on /login with
an active un-pre-ticked checkbox over three short statements that the
user actively accepts. Each acceptance is recorded against a code-level
version pointer, so a future wording revision bumps the version and
prompts every user again.

- New app/legal.py::ACKNOWLEDGEMENT_VERSION (currently 1).
- New UserAcknowledgement model + migration 0027 (user_id FK CASCADE,
  version, lang, accepted_at, composite index on user_id+version).
- auth_service: has_acknowledged_current() and record_acknowledgement()
  helpers; POST /login validates the checkbox, falls through to a 400
  with the localised error otherwise, and writes a row iff the user has
  no current-version row (so existing-already-accepted users don't
  produce duplicates).
- GET /login: language detection mirrors the landing's
  detect_public_lang(); ?lang=en|it overrides; stamps the rtm.lang
  cookie; passes the locale dict + version into the template.
- login.html: EN/IT pill, localised lede/banner/legal footer, required
  checkbox in an acknowledgement block, hidden lang+ack_version fields.
  Submit disabled until the box is ticked (UX polish; the server check
  is what carries weight).
- locales/{en,it}.yaml: new auth.ack.* section with TODO(legal) marker.
  Wording matches the brief's substance pending solicitor sign-off.
- tests/test_signup_acknowledgement.py: 10 tests (EN + IT rejection,
  one row per acceptance, displayed-lang recorded, idempotent on
  current version, version-bump writes new row, helper unit tests).

The acknowledgement strengthens the user-civil-claim vector — combined
with the liability cap and the Ltd, it makes "I was misled into thinking
this was advice" much harder to argue. It does NOT move the regulatory
perimeter, which is governed by the content discipline shipped in
47dce1a. Belt-and-braces, not a substitute.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-29 20:50:38 +02:00
alembic auth: affirmative versioned sign-up acknowledgement (EN + IT) 2026-05-29 20:50:38 +02:00
app auth: affirmative versioned sign-up acknowledgement (EN + IT) 2026-05-29 20:50:38 +02:00
config add ECB Data Portal source; group-aware stale thresholds 2026-05-15 23:13:58 +01:00
docs docs: mobile responsiveness design spec 2026-05-28 18:30:42 +02:00
scripts scripts: one-off purge of unclean IndicatorSummary rows 2026-05-29 13:56:47 +02:00
tasks phase G: data minimisation + passwordless auth + DeepSeek-first LLM 2026-05-18 14:16:57 +01:00
tests auth: affirmative versioned sign-up acknowledgement (EN + IT) 2026-05-29 20:50:38 +02:00
.dockerignore initial commit — cassandra v0.1 2026-05-15 21:56:10 +01:00
.env.example compliance: flag-gate AI portfolio + cloud sync + Stripe; de-risk prompts; harden reviewer 2026-05-29 19:57:12 +02:00
.gitignore docs: mobile responsiveness design spec 2026-05-28 18:30:42 +02:00
alembic.ini initial commit — cassandra v0.1 2026-05-15 21:56:10 +01:00
docker-compose.override.yml sync: encrypted cloud backup for portfolios + settings UX rework 2026-05-23 16:15:54 +02:00
docker-compose.prod.yml deploy: uvicorn --proxy-headers so https stays https behind NPM 2026-05-22 21:47:48 +01:00
docker-compose.test.yml test: standalone test container, isolated from the live prod stack 2026-05-25 23:58:55 +02:00
docker-compose.yml deploy: mount app/ + alembic from host in base compose 2026-05-25 12:49:27 +02:00
Dockerfile deps: add requirements.lock for reproducible builds 2026-05-28 00:07:38 +02:00
pyproject.toml i18n: bilingual landing page (EN / IT) with auto-detect routing 2026-05-29 17:10:45 +02:00
README.md deploy: split compose into base (prod-ready) + dev override 2026-05-22 21:30:28 +01:00
requirements.lock deps: add requirements.lock for reproducible builds 2026-05-28 00:07:38 +02:00

Read the Markets

Containerised macro-strategy dashboard — hourly market data, RSS news, Trading 212 portfolio, and an AI-generated strategic log written by Cassandra, the in-product seer. Read-only by design.

Production:

The Python package is still named cassandra and several internal identifiers (cookie names, advisory-lock keys, CASSANDRA_TOKEN env var, CSS filename) keep the legacy name on purpose — renaming them would invalidate live sessions / locks / configs for no user benefit. See app/branding.py for the brand single-source-of-truth.

Quick start (local dev)

cp .env.example .env       # fill in API keys; set CASSANDRA_TOKEN if exposing
docker compose up --build  # db + app + scheduler + daily backup sidecar
open http://localhost:8000/  # or whichever CASSANDRA_PORT you set

docker-compose.override.yml is auto-loaded and adds the host port binding so the app is reachable on localhost.

Production (VPS, NPM-fronted)

Always invoke with explicit -f flags — that way the dev override is skipped and the prod overlay (no host port, joins the external intranet Docker network, uvicorn on port 80) is applied:

docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d --build

Point Nginx Proxy Manager at upstream readmarkets-app-1:80.

Architecture

  • app (FastAPI + Jinja2 + HTMX) — web dashboard on port 8000
  • scheduler (APScheduler) — hourly ingestion jobs (market, news, portfolio, AI log)
  • db (MariaDB 11) — quotes, headlines, portfolio snapshots, strategic logs, job runs
  • backup (sidecar) — daily mariadb-dump to ./backup/

See /home/gg/.claude/plans/ok-i-think-this-tidy-lake.md for the design plan.

Config

File Purpose
config/default.toml Universal data tables: indicator groups, RSS feeds, keyword presets
config/portfolio.toml User-specific portfolios (overrides default.toml)
.env Secrets and runtime knobs — mounted read-only into containers

Endpoints

  • GET / — dashboard
  • GET /portfolio/{name} — portfolio detail
  • GET /news — news feed
  • GET /log — strategic-log archive
  • GET /api/health — job status (last success / failure per job)

All authenticated routes require Authorization: Bearer $CASSANDRA_TOKEN if the env is set; if unset, the app is open (LAN-only mode).