Drop-in TF-IDF + rhetorical-fingerprint + embedding analysis for any research corpus, with a static-HTML dashboard.
Project description
corpus-lens
Drop-in TF-IDF + rhetorical-fingerprint + embedding analysis for any research corpus, with a static-HTML dashboard.
You have a folder of papers, blog posts, working notes, lecture transcripts, or any other writing. You want to know:
- What is each paper actually about? (keywords + bigrams)
- What is its rhetorical posture? (hedging / assertion / empirical / theoretical / critical / quantitative density)
- Which papers are similar to which? (cosine on TF-IDF, top-K)
- Where does each paper sit in concept space? (UMAP / PaCMAP / PCA)
- How does it compare to public baselines? (arxiv categories, specific researchers)
- What clusters emerge? (force-directed similarity network)
corpus-lens gives you all of that from a single command, with a self-contained HTML dashboard you can open in any browser, host as a static site, or embed in your existing docs.
Zero hard dependencies for the core analysis (stdlib only). Embeddings need numpy + sklearn + optionally umap-learn / pacmap. No vector DB, no Docker, no LLM API calls, no telemetry.
Quickstart
pip install corpus-lens[all]
# Discover what's in your input directory + emit an editable manifest
corpus-lens prepare ./my-papers --output ./.lens
# Run the analysis (TF-IDF, fingerprint, similarity, keyword index)
corpus-lens analyze ./my-papers --output ./.lens
# Add 2D embeddings (UMAP / PaCMAP / PCA)
corpus-lens embed --output ./.lens
# Add external comparison baselines from arxiv
corpus-lens compare --output ./.lens \
--against arxiv:cs.LG --against arxiv-author:Friston
# Build the dashboard and open it
corpus-lens dashboard --output ./.lens --title "My Lab" --serve
That's it. The dashboard at http://127.0.0.1:8765/dashboard.html will show:
- Per-paper view: top-20 TF-IDF keywords, distinctive vocab, bigrams, rhetorical fingerprint, top-5 most-similar papers
- Map: UMAP / PaCMAP / PCA scatter, coloured by programme / maturity / year
- Network: force-directed similarity graph
- Timeline: lane chart of papers over time, per programme
- Heatmap: programme × rhetorical-dimension grid
- Plots: configurable scatter, programme bars, length histogram
- Compare: side-by-side with arxiv baselines
- Search by term: full inverted index
- Leaderboard: sort papers by any metric
Input formats
corpus-lens accepts your corpus in three forms.
1. Directory of files (most common)
Drop .md, .html, .htm, .txt, or .pdf files into a directory. corpus-lens prepare will discover them, extract titles / dates / metadata, and emit a manifest you can edit. PDFs require pip install corpus-lens[pdf].
2. Frontmatter-annotated files
Add YAML-ish frontmatter to your markdown / HTML to override extracted metadata:
---
title: My Paper Title
date: 2026-06-18
programme: Statistical Mechanics
maturity: preprint
tags: koopman, perturbation, spectral theory
---
# My Paper Title
The rest of the content...
3. JSON manifest
For non-file-based corpora (database dumps, API exports), write a manifest directly:
{
"items": [
{
"id": "paper-001",
"title": "Whatever",
"date": "2026-06-18",
"programme": "Some Programme",
"maturity": "preprint",
"tags": ["tag1", "tag2"],
"text": "The full plain-text body of the paper..."
}
]
}
Then corpus-lens analyze --manifest ./manifest.json --output ./.lens.
See docs/INPUT_FORMATS.md for full schema details.
What the rhetorical fingerprint measures
Each paper gets a six-axis fingerprint in markers per 1000 tokens:
| Axis | What it counts | Why it matters |
|---|---|---|
hedging |
"may", "might", "could", "perhaps", "suggests", ... | Epistemic caution, openness |
assertion |
"proves", "establishes", "demonstrates", "definitively", ... | Confidence, finality |
empirical |
"experiment", "dataset", "measure", "baseline", ... | Data-driven posture |
theoretical |
"theorem", "lemma", "proof", "axiom", ... | Formal-derivation posture |
critical |
"refutes", "fails", "mistaken", "naive", ... | Argumentative / refutational tone |
quantitative |
numbers with units (10ms, 95%, 3.2kHz) | Numeric specificity |
The lexicons are tunable; pass --lexicons to override or extend via the Python API. See docs/INTERPRETATION.md for guidance on reading the fingerprint.
Python API
The CLI is a thin wrapper. The full API is small and importable:
from corpuslens import extract, analyze, embed, compare, dashboard
items = extract.from_directory("./my-papers")
result = analyze.run(items)
embeddings = embed.run(result, methods=["pca", "umap", "pacmap"])
baselines = compare.run(["arxiv:cs.LG", "arxiv-author:Friston"], our_analysis=result)
dashboard.build("./.lens", title="My Lab")
Each function takes plain dicts/lists and returns plain dicts/lists. No frameworks, no inheritance, no DI.
Preparation guide
To get the most out of corpus-lens, spend ten minutes on preparation. See docs/PREPARATION.md for the full guide. The short version:
- One file per paper. Don't pre-concatenate. The unit of analysis is the paper.
- Use frontmatter. Title and date in YAML at the top of every file, even if the extractor would auto-detect them.
- Pick a programme label. A 1-3 word category for each paper. Loose groupings are fine; this is just colour-coding in the dashboard.
- Strip boilerplate. Page headers, footers, navigation, references sections, and author bios skew the keyword analysis. The HTML extractor drops these automatically; for markdown, put boilerplate in fenced blocks (
\```) so it's excluded. - Decide on maturity tags.
draft,preprint,published,workingare useful axes for the dashboard's colour-by-maturity view.
Comparison baselines
corpus-lens compare pulls abstracts from arxiv and applies the same fingerprint so you can position your corpus against published baselines.
corpus-lens compare --against arxiv:cs.LG # ML
corpus-lens compare --against arxiv:math.PR # probability
corpus-lens compare --against arxiv:q-bio.NC # neuroscience
corpus-lens compare --against arxiv:cond-mat.stat-mech # stat mech
corpus-lens compare --against arxiv-author:Friston
corpus-lens compare --against arxiv-author:Bengio
corpus-lens compare --against arxiv-query:cat:math.DS+AND+abs:Koopman
Each baseline becomes a row in the dashboard's Compare tab. Hover for the per-1k-token density of each rhetorical axis.
Output files
After analyze + embed + compare, your .lens/ directory contains:
.lens/
manifest.json # editable input description
corpus_analysis.json # per-paper meta + keywords + fingerprint
corpus_keyword_index.json # inverted index: term -> [{paper, tf}]
corpus_similarity.json # paper -> top-K most-similar
corpus_embedding.json # 2D coordinates per paper, per method
comparison_corpora.json # external baselines
dashboard.html # the dashboard (loads the JSON siblings)
All JSON. All plain. Open in any browser, host as a static site (GitHub Pages, Netlify), or pipe into another tool.
Hosting the dashboard
The dashboard is a single HTML file plus its JSON siblings. Drop the whole .lens/ directory anywhere that serves static files:
# GitHub Pages
gh repo create my-corpus --public
cp -r .lens/* docs/
gh-pages -d docs
# Netlify drop: drag .lens/ onto netlify.app/drop
# Vercel: vercel deploy ./.lens
# nginx / Apache: copy .lens/ to your webroot
No build step. No server-side code.
License
MIT.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file corpus_lens-0.1.0.tar.gz.
File metadata
- Download URL: corpus_lens-0.1.0.tar.gz
- Upload date:
- Size: 33.4 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.9.6
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
2a5d4d2bfaab50754d21311ee97ba7788e65aca2c6c0e966be854bd5d6415e83
|
|
| MD5 |
7d2582bcb6868f72341dd46a35c3a369
|
|
| BLAKE2b-256 |
72a67ac2962b04e13faa0f171ba90eb2e567f90ba37e42fb32f841fd2e635fb3
|
File details
Details for the file corpus_lens-0.1.0-py3-none-any.whl.
File metadata
- Download URL: corpus_lens-0.1.0-py3-none-any.whl
- Upload date:
- Size: 35.7 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.9.6
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
53b569a6f3feec12cf2b331d3e30d7b6ec75665c8fdd9f15c617d7228c38253c
|
|
| MD5 |
e15dc02416f146c60e3580540bd80725
|
|
| BLAKE2b-256 |
b9eb4467c587018f6cc899e3d08996a22be9f8f85767de3b39f96cc319a58f08
|