t10 docker + demo: containerised recommend app, compose one-command demo — completes M1 - #5
Merged
Merged
Conversation
… service w/ migrations entrypoint + demo mode, composition root extracted for main/demo, seed source as fixed-series fixture, README run-me
…ontainer, pinned uv, honest demo ordering - torch from the pytorch-cpu index: pypi's linux resolution dragged 2.9gb of nvidia/cuda wheels + triton into a cpu-only image for no benefit - drop root: uid 10001, /models chowned so a fresh weights volume inherits it - pin the uv binary to 0.9.9 (the version that wrote uv.lock) - :latest under --frozen is the non-reproducibility the lockfile exists to prevent - readme/compose: the one-shot demo is the alternative to `up`, not the sequel; running it after `up` printed "nothing delivered" because the scheduler had already claimed the anomaly. also drop the false "within a minute or two" (explain ticks every 5 min) and add restart: unless-stopped - test_composition: the client fixture returned from inside `async with`, so tests got a closed client; delenv HOST/PORT/DEMO_MODE so a sourced app env can't fail the defaults assertion - **/ anchor the cache patterns in .dockerignore (nested ones were shipping) - composition root is composition.py now, not main.py; backfill's docstring claimed the demo passes a fixed `end`, which the seed source ignores
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Final M1 task.
docker compose upon a clean machine → migrations apply →/health/ready200 → a seeded anomaly flows to Telegram end-to-end.what
packages/recommend/Dockerfile— python:3.14-slim + the uv binary; two-phaseuv sync(deps layer cached on the lockfile, then source + workspace install), so a code change doesn't re-download torch. WORKDIR is the recommend package so alembic resolves; ENTRYPOINT applies migrations then execs the CMD.docker-entrypoint.sh—alembic upgrade headthenexec "$@"(migrations are idempotent, safe every boot).docker-compose.yml— the existingdbplus a newappservice: builds the image, waits for db health, reaches Postgres by service name, binds 0.0.0.0, runs in demo mode, persists FinBERT weights in a volume, and health-checks/health/readywith a 240s start period for the first model download. (Also fixed the db volume mount to the pg18+ layout,/var/lib/postgresql.)composition.py— extracted the composition root out ofmain.pyintobuild_components(settings), shared by serve and demo.select_bar_source/select_news_sourcebranch ondemo_mode: offline seed bars + null news vs live Massive/Finnhub. Everything else (FinBERT, Anthropic, Telegram, scheduler) is identical to production.demo.py— one-shotbackfill → explain → notify, prints each step.docker compose run --rm app python -m hodlin_recommend.demodelivers the anomaly in one command, no waiting for ticks.connectors/offline.py—NullNewsSource(the anomaly self-cites, so zero news still explains).connectors/seed_bars.py— the seed source now returns its full committed series regardless of the requested window: it's a fixed June-2024 fixture standing in for "recent history", and the scheduled backfill asks for the last N days, which those dates never fall inside. Symbol/interval still filter; the live providers still honour the window.config.py— non-secret runtime toggles with defaults (host,port,demo_mode); secrets stay default-less (D17).main.pynow bindssettings.host/port.demo mode
Prices come from the committed seed CSV and news is skipped, so only Anthropic + Telegram need real credentials — the Finnhub/Massive placeholders stay as-is.
cp .env.example .env, fill two creds,docker compose up.verified locally (real Docker)
Image builds;
docker compose upapplies migrations (→ 0001);/health/readyreturns 200; the scheduled backfill ran (50 seed bars, the BTC-USD down anomaly detected,notifyticking); the demo command runs and — on a deliberately fake Anthropic key — the explain step catches the 401 asLLMUnavailableand degrades to an error outcome with no crash and no key leaked (D26). With real creds, explain + notify complete the delivery.tests
tests/test_composition.py— source selection perdemo_mode+ the non-secret defaults; hermetic (_env_file=None), no live API.M1 acceptance
With this merged, T1–T10 are all green; the import-linter boundary holds; the recommend domain holds no keys/authority;
uv.lockis committed; CI passes.second review round (fc29565)
A pre-merge review pass over the full branch found no must-fix but eight real should-fixes, all now applied:
torchpulls ~2.9 GB ofnvidia-*wheels +tritonundersys_platform == 'linux', i.e. on every Docker build, for CPU-only FinBERT inference. Routedtorchto the pytorch-cpu index via[[tool.uv.index]]+[tool.uv.sources](explicit = true, so nothing else resolves there). 37 CUDA packages left the lock;.venv4.8 GB → 1.1 GB. Re-resolution moved torch 2.12.1 → 2.13.0 — the opt-in real-FinBERT test passes on it./modelscreated and chowned in the image so a fresh weights volume inherits that ownership and HF can populate it unprivileged. Verified in the running container:uid=10001(hodlin),/modelswritable.uv.lock);:latestunderuv sync --frozenis exactly the non-reproducibility the lockfile exists to prevent.docker compose up— but by then the scheduler has already claimed and notified the anomaly, so the reviewer's next command printsNothing delivered(reproduced). It's now the primary path, withupas the "run it as a real service" alternative and the notify-once semantics stated. Also dropped compose's false "within a minute or two" (EXPLAIN_EVERY_S = 300, no immediate first fire) and addedrestart: unless-stopped.clientfixture didasync with ...: return c, handing out an already-closed client (passed only because no test made a request) →yield+ anis_closedassertion. And_env_file=Noneblocks dotenv but notos.environ, soHOST=0.0.0.0 DEMO_MODE=true— what this branch's own compose exports — failed the defaults test; nowdelenv'd..dockerignorecache patterns are context-root-anchored, so nestedpackages/*/.ruff_cachewere shipping →**/anchored.config.py/scheduler.py/serving/app.pystill namedmain.pyas the composition root, andbackfill.py's docstring claimed the demo passes a fixedendthat the seed source ignores.Re-verified on the rebuilt image (throwaway Postgres, dummy credentials): migrations apply,
/health/ready200, demo-first on a fresh DB gives 50 bars + 1 anomaly, explain degrades to an error outcome on a bad key with no crash and no credential iningest_runs.detail(0 rows matched a scan for the fake values), image carries no caches or.env. Gate green (117 passed) and CI green.Deliberately deferred as optional: eager FinBERT construction in the demo path (demo mode never calls it), unused
Componentsfields,AsyncExitStackassembly ordering, and the unclosedAsyncAnthropictransport.