Skip to content

sanmisanFan/citadel

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

167 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Citadel

Citadel is a research-paper review tool. Upload a PDF and the backend extracts its references, scores each citation's relevance with an LLM, builds citation/author/venue graphs, flags anomalies (low-relevance citations, suspicious author/venue communities, statcheck mismatches), and the frontend visualizes the results alongside the PDF.

The Python package is CITation; the FastAPI app is CITation.main:app.

Architecture

  • Backend — FastAPI app in backend/src/CITation/. Exposes a WebSocket (/ws/process_pdf) that runs the full pipeline on an uploaded PDF, plus REST endpoints for batch abstract extraction. Pipeline steps live in data/ (extraction, enrichment, graph building) and anomalies/ (relevance, author/venue suspicion, statcheck).
  • Frontend — React + Ant Design app in src/. PDF viewer in src/components/pdfContainer/, graph and anomaly visualizations in src/components/visContainer/.
  • GROBID (optional but recommended) — used for reference extraction, citation-mention location, formula coordinate extraction (for statcheck highlights), and abstract recovery for missing-metadata references.
  • PDF → markdown cascade — when markdown is needed (GROBID missing or unable to locate citation mentions), the backend tries olmocr first and falls back to pymupdf4llm. olmocr is not a hard dependency; install it separately if you want to use it.

Prerequisites

  • Python ≥ 3.11 with uv
  • Node.js (for the React frontend)
  • OPENAI_API_KEY set in the environment
  • S2_API_KEY set in the environment (Semantic Scholar API key)
  • Docker (optional, to run GROBID locally)

Setup

Install Python and JS dependencies:

uv sync
npm install

(Optional) start GROBID:

docker run --rm -p 8070:8070 lfoppiano/grobid:0.8.0

Override the GROBID URL with GROBID_URL if it's not on localhost:8070.

(Optional) install olmocr for higher-fidelity PDF → markdown conversion. It's heavy (vision model, expects a GPU), so it's not in pyproject.toml. Add it to this project's venv if you want it:

uv add olmocr

The backend invokes it via python -m olmocr.pipeline by default; override with OLMOCR_CMD (e.g. OLMOCR_CMD="my-olmocr-wrapper") and the per-PDF timeout with OLMOCR_TIMEOUT (seconds, default 900). When olmocr isn't available the backend silently falls back to pymupdf4llm.

Running

In separate terminals:

# Backend (FastAPI, http://localhost:8000)
uv run fastapi dev backend/src/CITation/main.py

# Frontend (React, http://localhost:3000)
npm start

The frontend talks to the backend at localhost:8000 by default; override with the BACKEND_URL env var when starting npm start. CORS is configured for localhost:3000 only.

Deployment

If you're deploying this on a web-facing server, you should have the frontend built in production mode and served by the Python API. To do this, first run npm run build, then copy the resulting build folder into backend/src/CITation. Then run uv run fastapi run.

API

  • WebSocket /ws/process_pdf — main pipeline. The client sends paper metadata (JSON), file metadata (JSON), then PDF bytes. The server streams info progress events and a final end event with {citations, authors, venues, anomalous, authorGraph}.
  • POST /api/extract_metadata — pre-fills the upload form. Accepts a PDF multipart upload, reads the first two pages with PyMuPDF, and asks gpt-4o-mini (JSON mode) for {title, authors, year}. The frontend calls this as soon as a PDF is dropped so the user reviews/edits rather than retypes manuscript metadata; fields stay editable and a warning surfaces if extraction fails.
  • POST /api/extract_abstract — single-PDF abstract extraction via GROBID. Requires GROBID.
  • POST /api/extract_abstracts — batch version.

Anomaly Anchors

Anomaly payloads now include explicit anchor metadata so the frontend can resolve each issue to the exact citation marker or statistical expression in the rendered PDF, instead of relying on page-only navigation.

  • Citation anomalies expose anchors, where each anchor represents one detected in-text marker occurrence with {page, marker_bbox, page_width, page_height, ref_label, sentence}.
  • Citation records returned from WebSocket /ws/process_pdf expose cite_positions, one entry per in-text [N] marker. The PDF viewer uses these to draw marker-level overlays and associate them with issue IDs.
  • When GROBID coordinates are missing, the frontend falls back to the anchor sentence and marker label to resolve the highlight in the PDF text layer.
  • The anomalies list and detail card now derive their page label from every anchor of the issue: a single page renders as Page: N, multiple pages as Pages: i, j, k, …. This also fixes a bug where a paragraph that cited several references on different pages could show the wrong page for one of them — assign_scores_to_enriched_papers previously rebuilt each mention dict from a text-only assessment lookup, which dropped occurrences and let one ref's page overwrite another's. The lookup is now keyed by (ref, excerpt) and the original mention (including its occurrences) is preserved.
  • Multi-ref groups like [22,30,32] are now parsed correctly. GROBID emits these as three sibling <ref> tags whose visible labels are "[22,", "30,", and "32]". The previous _extract_numeric_label used re.fullmatch(r"\[?(\d+)\]?", …), which failed on the first two (trailing comma) and fell back to GROBID's target="#b…" attribute — that mapping is off-by-one in some papers, so marker "22" was being bucketed under mentions[21] and surfaced as a stray anchor on page 2. The label parser now uses re.match(r"\[?(\d+)", …) (leading digit run), so each marker in a list binds to the visible number; author-year style labels like "Smith 2020 [5]" still skip the visible-label path because they don't start with [ or a digit.
  • The anomalies list is sorted by page-then-category: items are ordered by the earliest page they appear on, then within a page by category severity (testFailureselfCitationcitationRingunreferencedlowRelevancy). Issues with no page (e.g. unreferenced bibliography entries) sink to the bottom; insertion order from the pipeline breaks ties.
  • ref_id (which becomes cite_number shown in the UI) is recovered through a three-step fallback when the <biblStruct> has no <label> element: first the leading [N] of <note type="raw_reference">, then a xml:id → PDF label map harvested from the fulltext endpoint's <ref type="bibr" target="#bN">[K]</ref> markers, and only finally enumerate(..., 1). GROBID can drop a non-publication entry (e.g. a software citation like [1] 2008-2026. GROBID. https://github.com/kermitt2/grobid…) from the bibl list and also omit <label> and strip the [N] prefix from the raw reference text — that combination defeats both the label and the raw-reference fallbacks, so the fulltext label map is what keeps cite_number aligned with the source PDF numbering. The pipeline now issues the fulltext call before processReferences and threads the map through extract_references_with_grobid(..., label_map=…).

Tests

uv run pytest

Backend tests live in backend/tests/. Mark slow tests with @pytest.mark.slow.

Legacy scripts

The top-level *.py files (statcheck.py, synthetic.py, stackinggraph.py, pubmedPaperExtraction.py, florMatrix.py) and backend_scripts/ are earlier standalone tools that predate the integrated FastAPI pipeline. They are kept for reference but are not part of the running application. See backend_scripts/README.md for the historical script-by-script pipeline.

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

Packages

Used by

Contributors

Languages