Three connected fixes after the user spotted the 2026-05-28 IT log
cutting off mid-sentence:
1. translation: bump max_tokens 4000 → 8000.
call_llm()'s default cap was 4000, which is what the English log
generator itself uses as its ceiling. Italian expands roughly 15-25 %
over English in tokens, so any near-cap English source produced an
IT translation that hit finish_reason=length and returned a
truncated body — silently, because _call_provider() only raises when
content is fully empty. The strategic_log_translations table has
dozens of rows where completion_tokens landed at exactly 4000 with
content well under half the source length. 8000 gives ample
headroom for any of the five LANGUAGES we ship (en/it/es/fr/de).
2. log.html: localise the chat sidebar strings.
user_lang was already passed into the template by pages.py, so an
inline {% if user_lang == 'it' %} keeps it simple. Covers the
"Ask Cassandra" title, the "grounded on…" hint, the helper lede,
the textarea placeholder, and the Send button label.
3. chat endpoint: append respond_in_clause(user.lang) to the system
prompt. The chat conversation can now happen in IT — the model's
first reply lands in the right language even when the user's first
turn is short.
scripts/backfill_truncated_translations.py: one-off cleanup utility.
Scans strategic_log_translations for rows whose translated content is
< 70 % of the English source (the truncation signal — IT *expands*
beyond English, so a shorter translation is always suspect), deletes
them, and re-translates via the now-uncapped service. Supports --date,
--since, --all and --dry-run. The 2026-05-28 fan-out has already been
re-translated (13/13 rows). Other historical dates still hold older
truncations; the user can decide whether to backfill those (the script
is idempotent).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
||
|---|---|---|
| alembic | ||
| app | ||
| config | ||
| docs | ||
| scripts | ||
| tasks | ||
| tests | ||
| .dockerignore | ||
| .env.example | ||
| .gitignore | ||
| alembic.ini | ||
| docker-compose.override.yml | ||
| docker-compose.prod.yml | ||
| docker-compose.test.yml | ||
| docker-compose.yml | ||
| Dockerfile | ||
| pyproject.toml | ||
| README.md | ||
| requirements.lock | ||
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:
- Landing: https://read.markets
- App: https://app.read.markets
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 /— dashboardGET /portfolio/{name}— portfolio detailGET /news— news feedGET /log— strategic-log archiveGET /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).