conflens 0.1.14__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,349 @@
1
+ Metadata-Version: 2.5
2
+ Name: conflens
3
+ Version: 0.1.14
4
+ Summary: Browse, theme-classify, and topic-model conference papers with a NiceGUI UI.
5
+ Project-URL: homepage, https://github.com/picaultj/conflens
6
+ Project-URL: repository, https://github.com/picaultj/conflens
7
+ Author-email: Jérôme Picault <jerome.picault@rte-france.com>
8
+ Maintainer-email: Jérôme Picault <jerome.picault@rte-france.com>
9
+ License: MPL-2.0
10
+ License-File: LICENSE
11
+ Keywords: LLM,conferences,report,topic analysis,topics
12
+ Classifier: Development Status :: 4 - Beta
13
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
14
+ Requires-Python: >=3.13
15
+ Requires-Dist: anthropic>=0.40
16
+ Requires-Dist: litellm>=1.40
17
+ Requires-Dist: nicegui>=2.0
18
+ Requires-Dist: openai>=1.40
19
+ Requires-Dist: python-dotenv>=1.0
20
+ Requires-Dist: python-pptx>=0.6.21
21
+ Provides-Extra: bertopic
22
+ Requires-Dist: bertopic>=0.16; extra == 'bertopic'
23
+ Description-Content-Type: text/markdown
24
+
25
+ # ConfLens: a Conference Paper Analyzer
26
+
27
+ [![Lint](https://img.shields.io/github/actions/workflow/status/picaultj/conflens/lint.yml?branch=main&logo=github&label=lint)](https://github.com/picaultj/conflens/actions/workflows/lint.yml)
28
+ [![Tests](https://img.shields.io/github/actions/workflow/status/picaultj/conflens/test.yml?branch=main&logo=github&label=tests)](https://github.com/picaultj/conflens/actions/workflows/test.yml)
29
+ [![PyPI version](https://img.shields.io/pypi/v/conflens?logo=pypi&logoColor=white&label=pypi)](https://pypi.org/project/conflens/)
30
+ [![Downloads](https://pepy.tech/badge/conflens)](https://pepy.tech/project/conflens)
31
+ [![Python versions](https://img.shields.io/pypi/pyversions/conflens?logo=python&logoColor=white)](https://pypi.org/project/conflens/)
32
+ [![Stars](https://img.shields.io/github/stars/picaultj/conflens?logo=github&style=flat)](https://github.com/picaultj/conflens/stargazers)
33
+ [![Last commit](https://img.shields.io/github/last-commit/picaultj/conflens?logo=github)](https://github.com/picaultj/conflens/commits/main)
34
+ [![Issues](https://img.shields.io/github/issues/picaultj/conflens?logo=github)](https://github.com/picaultj/conflens/issues)
35
+ [![License: MPL 2.0](https://img.shields.io/badge/license-MPL%202.0-brightgreen.svg)](LICENSE)
36
+ [![uv](https://img.shields.io/badge/managed%20by-uv-DE5FE9.svg?logo=uv&logoColor=white)](https://docs.astral.sh/uv/)
37
+ [![Built with NiceGUI](https://img.shields.io/badge/UI-NiceGUI-2b6cb0.svg)](https://nicegui.io)
38
+
39
+ A desktop-style web app (built with [NiceGUI](https://nicegui.io)) that:
40
+
41
+ 1. **Browses** papers from a chosen **source** and retrieves their abstracts:
42
+ - the [ACL Anthology](https://aclanthology.org) (default; e.g. `acl-2026`),
43
+ - **EMNLP** and **NAACL** proceedings (also on the ACL Anthology; e.g.
44
+ `emnlp-2024`, `naacl-2024`),
45
+ - **IJCAI** accepted-paper pages (e.g. <https://2026.ijcai.org/accepted-papers/>),
46
+ - **OpenReview** venues — **ICLR / NeurIPS** and more — via the public JSON API,
47
+ by venue id (e.g. `ICLR.cc/2024/Conference`, `NeurIPS.cc/2024/Conference`), and
48
+ - **PSCC** (Power Systems Computation Conference) proceedings, by year
49
+ (e.g. `2024`) — titles + PDFs (abstracts aren't published, so classification
50
+ is title-based), and
51
+ - **ISGT Europe** (IEEE PES) via the open **DBLP** index, by venue + year
52
+ (e.g. `isgteurope 2024`) — titles + DOI links, title-based (the generic
53
+ DBLP adapter works for any DBLP-indexed conference).
54
+
55
+ The scraper is pluggable — adding another conference is one adapter in
56
+ `conference_analyzer/sources.py`.
57
+ 2. **Classifies** each paper with an LLM against a customizable
58
+ **theme** (default *Agentic AI*), keeping only those whose core contribution
59
+ matches.
60
+ 3. **Discovers topics** within the selected papers (LLM-based by default, with an
61
+ optional BERTopic backend) and shows, for each topic, how many papers it
62
+ contains and a direct **PDF link** to the full text of every paper.
63
+
64
+
65
+ ![overview](docs/overview.png)
66
+
67
+ ## Contents
68
+
69
+ - [Quick start](#quick-start) · [Run with Docker](#run-with-docker) · [LLM providers](#llm-providers)
70
+ - [Architecture](#architecture) · [How it works](#how-it-works)
71
+ - [Configuration](#configuration-in-the-ui) · [Features](#features)
72
+ - [Caching](#caching) · [Cost](#cost) · [BERTopic](#optional-bertopic)
73
+ - [Development](#development)
74
+
75
+ ## Quick start
76
+
77
+ Install from PyPI (Python 3.13+) and run:
78
+
79
+ ```bash
80
+ pip install conflens
81
+ conflens # → http://localhost:6868
82
+ ```
83
+
84
+ Or from a clone, with [uv](https://docs.astral.sh/uv/):
85
+
86
+ ```bash
87
+ uv sync # Claude, OpenAI, LiteLLM work out of the box
88
+ cp .env.example .env # then fill in your provider key(s)
89
+ uv run conflens # or: uv run python run.py
90
+ ```
91
+
92
+ (`conflens` is the console command once the package is installed;
93
+ `conference-analyzer` remains as an alias.)
94
+
95
+ Then open <http://localhost:6868>.
96
+
97
+ **Configuration.** `conflens` needs an LLM provider key, supplied any of three
98
+ ways:
99
+
100
+ - **Environment variable** — `export ANTHROPIC_API_KEY=…` (or `OPENAI_API_KEY`,
101
+ plus `OPENAI_BASE_URL` for an OpenAI-compatible endpoint; `LITELLM_API_KEY`).
102
+ - **A `.env` file** in the directory you launch `conflens` from — same keys, one
103
+ `NAME=value` per line. It's loaded automatically from the current working
104
+ directory (from a clone, copy [`.env.example`](.env.example) as a starting
105
+ point). Real environment variables take precedence over `.env`.
106
+ - **The in-app “API key” field** at runtime.
107
+
108
+ OpenReview venues (ICLR / NeurIPS) additionally need `OPENREVIEW_TOKEN`, or
109
+ `OPENREVIEW_USERNAME` + `OPENREVIEW_PASSWORD` — set the same way, or typed into
110
+ the fields that appear in the UI when you pick the OpenReview source.
111
+
112
+ `uv` provisions the right Python automatically (pinned to 3.13 via
113
+ `.python-version`); you don't need to install it yourself.
114
+
115
+ ### Run with Docker
116
+
117
+ ```bash
118
+ cp .env.example .env # fill in your provider key(s)
119
+ docker compose up --build # → http://localhost:6868
120
+ ```
121
+
122
+ The image is built with `uv` on Python 3.13 and includes all LLM
123
+ providers. A named volume persists the cache (scrapes + classifications) across
124
+ restarts. To clear the cache in a container:
125
+
126
+ ```bash
127
+ docker compose run --rm app --clear-cache
128
+ ```
129
+
130
+ Plain Docker (no compose) works too:
131
+
132
+ ```bash
133
+ docker build -t conference-analyzer .
134
+ docker run --rm -p 6868:6868 --env-file .env \
135
+ -v conf-cache:/home/app/.cache/conflens conference-analyzer
136
+ ```
137
+
138
+ To also build the optional BERTopic backend into the image:
139
+ `docker build --build-arg EXTRAS="--extra bertopic" -t conference-analyzer .`
140
+
141
+ ### LLM providers
142
+
143
+ The classifier and topic engine work with three providers, chosen in the UI:
144
+
145
+ | Provider | API key (env var or the in-app field) | Notes |
146
+ |----------|----------------------------------------|-------|
147
+ | **Anthropic** (default) | `ANTHROPIC_API_KEY` | Claude models, native structured output |
148
+ | **OpenAI** | `OPENAI_API_KEY` | OpenAI or any OpenAI-compatible base URL |
149
+ | **LiteLLM** | `LITELLM_API_KEY` / `OPENAI_API_KEY` | point **LLM endpoint** at your own LiteLLM URL |
150
+
151
+ In the app, pick the
152
+ **LLM provider**, set the **Model**, and (for LiteLLM / OpenAI-compatible
153
+ servers) the **LLM endpoint**. An **API key** field overrides the env var when
154
+ set — handy for a self-hosted endpoint.
155
+
156
+ > **Note on the default event:** ACL 2026 proceedings may not be published yet.
157
+ > The app handles this gracefully and tells you so — try a past event such as
158
+ > `acl-2024` to see a full run.
159
+
160
+ ## Architecture
161
+
162
+ A NiceGUI front end drives a linear pipeline — **browse → classify →
163
+ topic-model → summarize** — over a pluggable *source* (which conference) and a
164
+ pluggable *LLM provider* (which model), with every expensive step cached on disk.
165
+
166
+ ```mermaid
167
+ flowchart LR
168
+ UI["NiceGUI UI<br/>app.py"] --> PIPE["pipeline.py"]
169
+ PIPE --> SRC["sources.py<br/>ACL · EMNLP · NAACL · IJCAI · OpenReview · PSCC · ISGT Europe"]
170
+ PIPE --> CLS["classifier.py"]
171
+ PIPE --> TOP["topics.py<br/>model + summarize"]
172
+ CLS --> LLM["llm.py<br/>Anthropic · OpenAI · LiteLLM"]
173
+ TOP --> LLM
174
+ PIPE --> RES["AnalysisResult"] --> EXP["PPTX · BibTeX · JSON · CSV"]
175
+ SRC -. cache .-> CACHE[("~/.cache/conflens")]
176
+ CLS -. cache .-> CACHE
177
+ TOP -. cache .-> CACHE
178
+ ```
179
+
180
+ See **[docs/ARCHITECTURE.md](docs/ARCHITECTURE.md)** for component, sequence,
181
+ data-model and caching diagrams, plus extension points.
182
+
183
+ ## How it works
184
+
185
+ | Stage | Module | Notes |
186
+ |-------|--------|-------|
187
+ | Sources | `conflens/sources.py` | Pluggable adapters (ACL Anthology, EMNLP, NAACL, IJCAI, OpenReview, PSCC, DBLP/ISGT Europe) behind one interface; registry + factory. |
188
+ | Scrape listing | `conflens/scraper.py` | ACL Anthology adapter: parses the event page; abstracts + authors fetched per paper and cached. |
189
+ | Near-duplicates | `conflens/dedup.py` | Flags near-identical titles (union-find + `difflib`); dependency-free. |
190
+ | Classify | `conflens/classifier.py` | Batched, structured-output calls; relevance + confidence + a one-line reason per paper (cached). |
191
+ | Topic model | `conflens/topics.py` | `llm` backend derives a taxonomy and assigns papers (primary + optional secondary topic); `bertopic` backend optional. |
192
+ | Summarize | `conflens/topics.py` | Per-topic description + common findings across the topic's papers (cached). |
193
+ | LLM providers | `conflens/llm.py` | One `structured()` interface over Anthropic / OpenAI / LiteLLM. |
194
+ | Orchestrate | `conflens/pipeline.py` | Runs the stages with progress reporting and cooperative cancel. |
195
+ | UI / exports | `conflens/app.py`, `pptx_export.py`, `bibtex.py` | NiceGUI; ECharts chart; interactive results view; PPTX / BibTeX / JSON / CSV export. |
196
+
197
+ ## Configuration (in the UI)
198
+
199
+ - **Source** — `ACL Anthology`, `EMNLP (ACL Anthology)`, `NAACL (ACL Anthology)`,
200
+ `IJCAI`, `OpenReview (ICLR / NeurIPS)`, `PSCC`, or `ISGT Europe (via DBLP)`.
201
+ Switching prefills the base URL and target below and relabels them. (ACL
202
+ Anthology, EMNLP and NAACL share one adapter — any of them accepts any
203
+ Anthology event slug, e.g. `acl-2024`, `emnlp-2023`, `naacl-2024`.)
204
+ - **Base URL** — the site/API root (e.g. `https://aclanthology.org`,
205
+ `https://2026.ijcai.org`, `https://api2.openreview.net`); change it to point at
206
+ a mirror, another year, or the v1 API host (`https://api.openreview.net`).
207
+ - **Event / target** — for ACL, a slug (`acl-2024`, `emnlp-2023`, …) or full
208
+ event URL; for IJCAI, the accepted-papers path or full URL; for OpenReview, the
209
+ **venue id** (`ICLR.cc/2024/Conference`, `NeurIPS.cc/2024/Conference`) or a
210
+ venue group URL; for PSCC, a **year** (`2024`, `2022`, …) or a full listing URL;
211
+ for ISGT Europe, a DBLP **venue + year** (`isgteurope 2024`) or proceedings key.
212
+
213
+ > **Title-based sources** — PSCC and DBLP-backed venues (ISGT Europe) don't
214
+ > expose abstracts, so classification runs on titles alone (still useful, just
215
+ > coarser). The DBLP adapter is generic: any DBLP-indexed conference works by
216
+ > passing `<venue> <year>`. IEEE-Xplore-only venues that DBLP doesn't index
217
+ > (e.g. **PowerTech**, **PES General Meeting**) can't be added without an IEEE
218
+ > Xplore API key.
219
+
220
+ > **OpenReview auth** — OpenReview gates anonymous note queries behind an
221
+ > anti-bot challenge (HTTP 403), so ICLR / NeurIPS venues need credentials:
222
+ > set `OPENREVIEW_TOKEN`, or `OPENREVIEW_USERNAME` + `OPENREVIEW_PASSWORD`
223
+ > (see `.env.example`); the adapter logs in and authenticates automatically.
224
+ - **Theme** — any phrase; defaults to *Agentic AI*.
225
+ - **Theme definition** — optional free text clarifying what the theme includes
226
+ or excludes (e.g. *"tool-using LLM agents; exclude pure RL"*). It is threaded
227
+ into classification, topic discovery, and summaries to sharpen relevance, and
228
+ is part of the classification cache key.
229
+ - **LLM provider** — Anthropic (default), OpenAI, or LiteLLM.
230
+ - **Model** — free-text; defaults per provider (e.g. `claude-opus-4-8`,
231
+ `gpt-4o-mini`). Suggestions appear as placeholder text.
232
+ - **LLM endpoint / API key** — a custom base URL (LiteLLM or any
233
+ OpenAI-compatible server) and an optional key override.
234
+ - **Topic engine** — `LLM` (no extra deps) or `BERTopic` (requires the
235
+ optional `bertopic` install).
236
+ - **Max papers**, **target topics**, **minimum confidence** — tuning knobs.
237
+
238
+ ## Features
239
+
240
+ - Per-topic bar chart of paper counts.
241
+ - For each topic, an LLM-generated **description** and **5–10 common findings**
242
+ (synthesised across the topic's papers, not paper-specific), shown above the
243
+ paper list.
244
+ - **Multi-topic assignment** — a paper can belong to a primary topic and, when it
245
+ genuinely spans two areas, a secondary one; secondary memberships show an
246
+ *"Also in: …"* line, and the CSV export lists every assigned topic.
247
+ - **Near-duplicate detection** — papers with near-identical titles (e.g. a
248
+ preprint and its camera-ready) are clustered with a dependency-free fuzzy match
249
+ and flagged with a *near-dup* badge naming the representative; the count of
250
+ groups appears in the summary and a `duplicate_of` column is added to the CSV.
251
+ - A **keyword search** that filters the papers by their title or abstract —
252
+ comma-separated keywords (each may contain spaces), matched with AND. Matched
253
+ keywords are **highlighted** in the title and abstract, topics with no match
254
+ are hidden, and a **"Search all topics"** toggle flattens every match into one
255
+ ranked list (a multi-topic paper appears once, tagged with its topics).
256
+ - A **live confidence slider** in the results view: because the raw model
257
+ judgement is cached, re-thresholding re-filters the papers, counts, and chart
258
+ instantly — no re-run.
259
+ - **Sort** papers by confidence, title, or year, and **filter by author** —
260
+ all applied live on top of the search and confidence filters.
261
+ - **Save / load a run** — the JSON export is a complete snapshot; use *"Load
262
+ saved run"* to reopen it later and browse, search, and re-threshold without
263
+ re-analysing.
264
+ - Expandable abstracts and a per-paper relevance rationale.
265
+ - One-click **PDF** links to every paper's full text.
266
+ - A **Cancel** button and elapsed-time readout for long runs; LLM calls
267
+ automatically retry transient errors (rate limits / 5xx / timeouts).
268
+ - Export results as a **PPTX** slide deck, **BibTeX**, **JSON**, or **CSV**.
269
+ The deck (built with `python-pptx`) has a title slide, a papers-per-topic
270
+ chart, and per topic an overview slide (description + common findings) plus
271
+ slides listing its papers with clickable PDF links.
272
+ - On-disk caching of scraped data so re-runs are fast (see below).
273
+
274
+ ## Caching
275
+
276
+ Everything expensive is cached on disk under `~/.cache/conflens`:
277
+
278
+ - the **event listing** is cached per event/page URL,
279
+ - each paper's **abstract + authors** is cached per paper id, and
280
+ - **classification results** are cached per *(provider + model, theme)* and keyed
281
+ by each paper's title+abstract hash. The raw model judgement is stored, so the
282
+ **minimum-confidence** threshold is applied at read time — adjusting it never
283
+ triggers a re-call.
284
+ - **topic summaries** (description + common findings) are cached per
285
+ *(provider + model, theme)* and keyed by the topic's exact paper membership.
286
+
287
+ So re-running with a **different theme** reuses the cached scrape (only
288
+ classification re-runs), and **re-running the same theme + model is essentially
289
+ free**. Changing the model or a paper's abstract re-classifies only what's
290
+ affected. Tick **“Refresh from source”** in the UI to bypass every cache and
291
+ rebuild from scratch.
292
+
293
+ To wipe the cache from the command line:
294
+
295
+ ```bash
296
+ uv run conflens --clear-cache # default ~/.cache/conflens
297
+ uv run conflens --clear-cache --cache-dir /path/to/cache
298
+ ```
299
+
300
+ Other flags: `--host`, `--port` (run `conflens --help`).
301
+
302
+ ## Cost
303
+
304
+ Classification batches ~20 papers per request, so a 150-paper run is a handful
305
+ of API calls. Pick a small model (e.g. `claude-haiku-4-5` or `gpt-4o-mini`) to
306
+ minimise cost.
307
+
308
+ ## Optional: BERTopic
309
+
310
+ ```bash
311
+ uv sync --extra bertopic
312
+ ```
313
+
314
+ Then pick **BERTopic** as the topic engine in the UI. It clusters
315
+ sentence-embeddings instead of asking the LLM to organise topics.
316
+
317
+ ## Development
318
+
319
+ ```bash
320
+ uv sync # installs the dev group (pytest + ruff) too
321
+ uv run pytest # run the test suite
322
+ uv run ruff check . # lint
323
+ uv build # build the wheel/sdist
324
+ ```
325
+
326
+ CI is GitHub Actions, one workflow per concern, each running on **every pushed
327
+ commit** and every PR:
328
+
329
+ - **`lint.yml`** — ruff on every commit.
330
+ - **`test.yml`** — pytest + package build.
331
+ - **`release.yml`** — version bump + tag on PR merge (see below).
332
+
333
+ The tests are network- and API-free (parsers run on HTML fixtures; the LLM
334
+ stages use a fake client), so they're fast and deterministic.
335
+
336
+ ### Releasing
337
+
338
+ Releases are automated (`.github/workflows/release.yml`): when a PR is **merged
339
+ into `main`**, the workflow bumps the version in `pyproject.toml`, commits it
340
+ back to `main`, creates a matching `vX.Y.Z` **tag**, then **builds and publishes
341
+ the package to [PyPI](https://pypi.org/project/conflens/)** (`pip install
342
+ conflens`). The bump is a **patch** by default — add a **`minor`** or
343
+ **`major`** label to the PR to bump those instead.
344
+
345
+ Publishing needs a repo secret **`PYPI_API_TOKEN`** (a PyPI API token; the
346
+ workflow uploads with `twine` as `__token__`). Add it under *Settings → Secrets
347
+ and variables → Actions*. Without it the version bump/tag still succeed and only
348
+ the publish step fails.
349
+
@@ -0,0 +1,20 @@
1
+ conflens/__init__.py,sha256=qPoGyihNoQ1vCTVu9JTvEonAaXCGtnXmLIlb6nuGXbQ,236
2
+ conflens/app.py,sha256=5na3cXS3TGKGuNxmM19_jqvg33S91LdGExhTpNdkFcE,37425
3
+ conflens/bibtex.py,sha256=Ew0ZjNH7xcOpm5AgPmoNLUUflrn1KBSgiq9LHORVBmI,1731
4
+ conflens/cache.py,sha256=fi86l8GkCR-qqH0WMdWEk7-ohAW4woNOYZ_rVmKDgMM,1080
5
+ conflens/classifier.py,sha256=0sdZNR1KvqOp13i_9mEg-8cuWVUjutqs-zSMy0PuMDw,6275
6
+ conflens/cli.py,sha256=MTUaWw0YksvHTLqFcrySbHjWXRTRPeDdhaMAQKK1iPY,2174
7
+ conflens/dedup.py,sha256=mxZ6flnI9ksDqbgStRO2IstjTyfpFpGK7gzfuT7NqV4,3228
8
+ conflens/llm.py,sha256=lWCpxXmi79geMeR8FBca2xPuTqed6wRHn8qh7_bxriE,12380
9
+ conflens/models.py,sha256=qPGiClMs2dFw0fC6WfpF_sCihd3tz5_AlimF_Lp26do,3995
10
+ conflens/pipeline.py,sha256=3QJgwWc7KH6_3p1HOEvEjNk4q4cMijvuv_EXBwLDZgs,8149
11
+ conflens/pptx_export.py,sha256=De_Vu0QbzZgMQKhCoSG6nuxdBa0rnPv-KLM3C7Tt4M0,9110
12
+ conflens/scraper.py,sha256=IZ_0r-csHdth-kHuPqK9_HGy0DgECksFnmv8TLENwfg,10531
13
+ conflens/sources.py,sha256=61_HYrHHVKVrzdaQWfoabMSKpX1Ik1A5Il3erYzLZ4c,34266
14
+ conflens/topics.py,sha256=TXs6nbfFL0FBIlIHV7ciX6ynlUf76x9lJhmoNf6QUjc,13583
15
+ conflens/view.py,sha256=F_9mswjZQK6c3XAV1FZEbExEOtUYkJw_YRaJ6b-nP2w,6586
16
+ conflens-0.1.14.dist-info/METADATA,sha256=3fRiLiXFK12-8VY2XQ7qDbNc1uhMHD4tWAIlnn-SALo,17518
17
+ conflens-0.1.14.dist-info/WHEEL,sha256=zOwg4jB6zX2kU910N-cMawjivD6tO8NEWvE12je1bVk,87
18
+ conflens-0.1.14.dist-info/entry_points.txt,sha256=JDyWB2Ds1ydDvByyGwrvPsZE3IRynGmjsSNgnqMl1DM,87
19
+ conflens-0.1.14.dist-info/licenses/LICENSE,sha256=Pz2eACSxkhsGfW9_iN60pgy-enjnbGTj8df8O3ebnQQ,16726
20
+ conflens-0.1.14.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.32.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,3 @@
1
+ [console_scripts]
2
+ conference-analyzer = conflens.cli:main
3
+ conflens = conflens.cli:main