Files
esh-pfi-infrastructure/stacks/news-digest/Dockerfile
T
vh 40f1e0ee00 news-digest: real article summaries + per-desk collapse
Two upgrades to make the digest actually readable:

1) Article-grounded 2-3 sentence summaries (everywhere)

   The old prompt got just the title + miniflux's content excerpt,
   which for HN/Lobsters/wire feeds is barely more than the title
   itself — so summaries paraphrased the title and added nothing.

   Now every URL gets fetched and main-content-extracted via
   trafilatura on a parallel pre-pass (10 workers, ~15s for ~50
   URLs). Extracted text caches to /output/.article-cache.json with
   a 7-day TTL so repeat runs in the same window don't re-pull.

   Headlines also get summarized now — one batched LLM call per
   category (world / local). Rendered as a paragraph below the
   title with source + time on the right rail.

   Prompt rewrites tell the model to pull names/numbers/places
   from the body and explicitly forbid restating the title.
   Result: real specifics ("71% saw no pay increase globally",
   "third time in less than two weeks", "Islamabad and Moscow
   intermediaries") instead of title paraphrase.

2) Per-desk collapse buttons

   Chevron next to .desk-count toggles a .is-collapsed class.
   Collapsed state is per-device (localStorage by section id) since
   collapse is a viewing preference, not content state.
2026-04-28 11:29:15 -07:00

52 lines
2.1 KiB
Docker

# news-digest — base image for two containers in this stack:
#
# news-digest-worker — runs alpine's busybox crond + the one-shot
# digest.py per fire (default ENTRYPOINT).
# news-digest-web — runs uvicorn web:app (overridden in compose)
# to serve /output as static + the tiny
# hidden-items API at /api/*.
#
# Single image, two roles selected via compose `command:`.
# Bind-mounted /output is the shared canvas: worker writes HTML, web
# serves it.
FROM python:3.12-alpine
ENV PYTHONUNBUFFERED=1 \
PYTHONDONTWRITEBYTECODE=1 \
PIP_DISABLE_PIP_VERSION_CHECK=1
# tzdata so $TZ works for cron + datetime; tini so signals propagate cleanly.
RUN apk add --no-cache tzdata tini bash curl
# fastapi + uvicorn[standard] for the web container; requests + jinja2
# for the worker. Both shipped in both containers — neither set is
# heavy enough to justify splitting the image.
# trafilatura: main-content extractor for the article-summary upgrade
# (worker only — it pulls lxml + a handful of HTML utils, ~80 MB total).
# libxml2-dev/libxslt-dev are for lxml's musl wheels. apk caches are
# cleaned in the same RUN to keep the layer small.
RUN apk add --no-cache --virtual .build-deps gcc musl-dev libxml2-dev libxslt-dev \
&& apk add --no-cache libxml2 libxslt \
&& pip install --no-cache-dir \
requests jinja2 trafilatura \
'fastapi>=0.115' 'uvicorn[standard]>=0.30' \
&& apk del .build-deps
WORKDIR /app
COPY digest.py /app/digest.py
COPY seed-headlines.py /app/seed-headlines.py
COPY web.py /app/web.py
COPY templates /app/templates
COPY entrypoint.sh /usr/local/bin/entrypoint.sh
COPY run-digest.sh /usr/local/bin/run-digest.sh
COPY crontab /etc/crontabs/root
RUN chmod +x /usr/local/bin/entrypoint.sh /usr/local/bin/run-digest.sh
# Sentinel + first-run output dir
VOLUME /output
# Default ENTRYPOINT runs the worker (cron). The web container in
# compose overrides both entrypoint and command to launch uvicorn.
ENTRYPOINT ["/sbin/tini", "--", "/usr/local/bin/entrypoint.sh"]