Research paper discovery tool for biomedical AI labs. Aggregates papers from multiple sources, scores them by relevance, and delivers a personalized feed.
- Multi-source fetching: arXiv, Semantic Scholar, bioRxiv, Papers With Code, PubMed, OpenAlex
- Onboarding wizard: describe your research, get auto-generated topic configurations from curated templates
- Relevance scoring: keyword matching (include/exclude, author/venue boosts, recency) plus OpenAlex-derived signals that don't depend on literal wording — concept-tag overlap, citation count, and whether a paper cites something your own research already builds on (
topic.seed_works) - Full-text evidence tiers: papers whose keyword score alone is ambiguous get a full-text check (via Europe PMC / Unpaywall) instead of trusting the abstract — each paper is labeled
confirmed/mentioned/keyword_onlywith a quoted snippet, shown in the digest and Slack message so ranking isn't an opaque number - Deduplication: cross-source fuzzy dedup by title similarity and DOI/arXiv ID matching, plus an OpenAlex canonical-work pass that catches preprint-vs-published-version pairs with different DOIs
- Digest page: filter by topic, sort by relevance or date, filter by source
- Feedback loop: upvote/flag papers to calibrate future results
- Automated digest: GitHub Actions cron job for a weekly digest, posted to Slack
- Backend: Python, FastAPI, SQLModel (SQLite), OpenRouter LLM
- Frontend: React 18, Vite, CSS Modules (dark theme)
- Sources: arXiv, Semantic Scholar, bioRxiv, Papers With Code, PubMed, OpenAlex
- System dependency:
poppler-utils(pdftotext) — used by the full-text evidence pass to extract text from open-access PDFs. Install viaapt-get install poppler-utils(Linux) orbrew install poppler(macOS).
cp .env.example .envOpen .env and fill in the values below.
| Variable | Required | Where to get it | What happens without it |
|---|---|---|---|
JWT_SECRET |
Yes | Any random string (e.g. openssl rand -hex 32) |
App crashes on startup |
APP_PASSWORD |
Yes | Pick any password (used to log in) | App crashes on startup |
OPENROUTER_API_KEY |
For onboarding | openrouter.ai/keys | App runs, but the onboarding LLM step fails |
S2_API_KEY |
No | semanticscholar.org/product/api | App runs on free-tier Semantic Scholar rate limits |
DATABASE_URL |
No | SQLAlchemy URL | Defaults to sqlite:///data/paperwatch.db |
FRONTEND_URL |
No | Your frontend URL | Only needed if frontend runs on a different domain |
SLACK_WEBHOOK_URL |
No | Slack app settings | Disables the weekly digest post to Slack — the digest still runs and saves to data/digest_*.md either way |
EMAIL_* |
No | Your SMTP credentials | Disables email digest notifications |
The minimum to get the app running locally: set JWT_SECRET, APP_PASSWORD, and optionally OPENROUTER_API_KEY if you want to use the onboarding wizard.
pip install -r requirements.txt
uvicorn backend.main:app --reload
# API available at http://localhost:8000cd frontend
npm install
npm run dev
# UI available at http://localhost:5173Open http://localhost:5173, log in with the username of your choice and the APP_PASSWORD you set.
app/ Core domain logic
sources/ Paper fetchers (arxiv, biorxiv, s2, pwc)
db.py SQLite tables + helpers
models.py Pydantic models (Paper, Topic)
scoring.py Keyword scoring engine
dedup.py Cross-source deduplication
backend/ FastAPI API layer
api/ Route handlers (onboarding, papers, topics, users)
main.py App factory + entrypoint
config/ YAML topic templates and defaults
frontend/ React + Vite
src/pages/ Login, Onboarding, Digest
src/components/ PaperCard, Header
tests/ pytest suite
Tracks new papers written by the lab and new papers citing the lab, using OpenAlex. Lab-authored papers are posted to Slack directly. Citing papers are too noisy to post individually, so instead they're published as a static digest page — grouped by which lab paper is cited — at https://yolaatar.github.io/ourak/ (GitHub Pages, from the gh-pages branch), with Slack just getting a one-line pointer when there's something new.
- Edit
config/lab.yaml. Lab papers are matched by affiliation keyword (e.g. "NeuroPoly") and/or by listed authors (ORCID preferred, or OpenAlex author IDs). To find IDs:python -m app.lab_watch --find "Full Name" "Other Name"
- Preview without posting or saving anything:
python -m app.lab_watch --dry-run
- Run for real: posts new lab-authored papers to
SLACK_WEBHOOK_URL(or prints if unset), silently no-ops if there aren't any, and marks papers as seen in the DB so the next run only reports new ones:Addpython -m app.lab_watch
--authored-onlyto skip the citation pipeline (fetch_lab_workspages through every work the lab has ever published, so it's the expensive part) — used for the daily run below.
Papers also get linked to the lab-papers and citing-lab topics, so they show up in the web feed.
Two workflows share the same seen-papers state (the lab-watch-state branch, created on first run — nothing needs to be hosted) and the same concurrency: lab-watch group, so they never race each other:
.github/workflows/lab-watch-daily.yml— daily at 12:00 UTC,--authored-only. Cheap, and only posts when there's actually a new lab paper..github/workflows/lab-watch.yml— weekly, Mondays 13:00 UTC, full run including the citation pipeline. Also regenerates the citations digest page and force-pushes it togh-pages.
Setup: add a SLACK_WEBHOOK_URL repo secret (Slack app with Incoming Webhooks enabled, added to a channel). OPENALEX_EMAIL and OPENALEX_API_KEY secrets are optional but recommended (anonymous OpenAlex requests get rate-limited under load). To test either one, trigger it manually from the Actions tab with "dry run" checked.
GitHub Pages: enabled once, pointing at the gh-pages branch, root — no further setup needed, the weekly workflow keeps it updated. LAB_CITATIONS_URL (set in lab-watch.yml) is what gets linked in the Slack pointer message; update it if the Pages URL ever changes (e.g. a custom domain).
python -m pytest tests/ -x -q