# Journal Quality System

The journal quality system automatically scores academic journals encountered during research, filters out predatory publications, and provides a dashboard for exploring journal metrics.

## Overview

When you search using academic engines (ArXiv, OpenAlex, Semantic Scholar, NASA ADS), every journal is automatically scored on a 1–10 scale using real bibliometric data. The emitted scores are non-contiguous — the system only produces `{1, 4, 5, 6, 7, 8, 10}`; values 2, 3, and 9 are reserved but never assigned. Predatory journals are auto-removed from results. Scores are cached so subsequent lookups are instant.

## Quality Scale

| Score | Tier | Description | Example |
|-------|------|-------------|---------|
| 10 | Elite | Top-tier journals with h-index > 150 | Nature, Science, NEJM |
| 7–8 | Strong | Strong Q1 journals, h-index 41–150 | PLOS ONE, IEEE Trans. |
| 5–6 | Moderate | Solid journals, DOAJ-listed OA journals | Many field-specific journals |
| 4 | Default | Low h-index or unknown venue (no data in any bundled source) | Newer, niche, or unindexed journals |
| 1 | Predatory | Flagged by Stop Predatory Journals — auto-removed | SPJ list entries |

## Threshold Semantics

The threshold setting controls how aggressively the filter drops results (the filter rejects any journal whose score is below the threshold):

| Threshold | Effect |
|-----------|--------|
| **2 (default)** | Drop only predatory (score 1). Keep everything else — including default/unknown venues. |
| 3 | Same as 2 — no scores fall in the 2–3 gap. |
| 4 | Same as 2 — no scores fall in the 2–3 gap. |
| 5 | Also drop default/unknown venues (score 4). |
| 6 | Also drop the long tail of moderate journals (score 5). |
| 7+ | Keep only strong/elite journals. Aggressive — use only when you specifically want high-quality filtering. |

The default of **2** is intentionally conservative: it removes flagged predatory venues (we have positive evidence of fraud) but doesn't silently delete sources just because we don't have bibliometric data on them.

## How Scoring Works

The system uses a tiered approach — the first tier that finds the journal wins:

1. **Tier 1 — Predatory Check**: Checks against the Stop Predatory Journals lists (~2,500 entries). If flagged AND not whitelisted (DOAJ/high h-index), the result is auto-removed.

<!-- TODO(post-merge): reconcile 212K vs 280K wording — 212K is
     journal-type entries; 280K includes all OpenAlex source types
     (journals, conferences, repositories, book series). Docs should
     settle on one number with a clear qualifier. -->
2. **Tier 2 — OpenAlex Snapshot**: Looks up the journal in the bundled OpenAlex dataset (~280K sources, downloaded fresh from the OpenAlex S3 bulk dump). Scores based on h-index thresholds (>150 → 10, >75 → 8, >40 → 7, >20 → 6, >10 → 5, else → 4) with DOAJ cross-referencing. Quartile (Q1–Q4) is derived from cited_by_count percentiles per source type.

3. **Tier 3 — DOAJ Check**: For journals with ISSN not in OpenAlex, checks DOAJ status. Listed = score 5. (DOAJ retired its "Seal" in April 2025, so the former Seal = score 8 tier no longer exists.)

4. **Tier 3.5 — Institution Affiliation Salvage**: For preprints (arXiv, bioRxiv) without a journal, fall back to scoring the author's institution using ~120K OpenAlex Institutions records. Capped at 6 — never beats a real venue match.

5. **Tier 0 cache — LLM-only** (checked before Tiers 3.6/4): Returns a previously-computed LLM score if the journal has been analyzed by the same LLM within the re-analysis window (default 365 days). Tiers 1–3.5 are not cached — reference-DB lookups are already instant (100–300µs) and re-checking every time keeps scores current as the bundled data is rebuilt.

6. **Tier 3.6 — LLM Name Cleanup (off by default)**: Asks the LLM to canonicalize a dirty journal name (e.g. strip a volume reference), then retries the Tier 2 OpenAlex lookup under the cleaned name. Enable via `search.journal_reputation.enable_llm_scoring`.

7. **Tier 4 — LLM Analysis (off by default)**: Last resort for unknown journals. Uses SearXNG web search + LLM to assess reputation. Enable via `search.journal_reputation.enable_llm_scoring`. Only Tier 4 results are persisted to the per-user cache.

7. **No-signal pass-through**: When *no* tier produces a signal, `derive_quality_score` returns `None` and the source is kept without a quality tag. Distinct from predatory (score 1, auto-removed) or unknown-venue default (score 4, rendered as `[Unranked ★]`).

## Predatory-List Overrides

Tier 1 does **not** auto-remove a journal that Stop Predatory Journals flagged if **either** of the following is true:

- the journal is listed in DOAJ, **or**
- the journal's h-index exceeds `PREDATORY_WHITELIST_HINDEX` (default `10`, defined in `constants.py`).

This deliberate whitelist protects against false positives. Community predatory lists occasionally flag mainstream high-volume open-access publishers, typically because of rapid-publication or fee-structure concerns rather than actual fraud. A journal with real citation impact (h-index > 10) or a DOAJ listing is treated as legitimate enough to keep, even if a blacklist disagrees. The system prioritizes evidence of real scholarly impact over reputation heuristics, at the cost of letting a small number of borderline venues through.

If your workflow needs stricter filtering — for example in a systematic review where any blacklist hit should be a hard stop — lower `PREDATORY_WHITELIST_HINDEX` toward `0` (in `constants.py` or via a local override) so fewer flagged journals are rescued. Raising the threshold has the opposite effect, making the predatory filter more forgiving.

## Data Sources

This system is made possible by the following open academic data initiatives. All bundled data has MIT or CC0-compatible licenses:

| Source | Entries | License | What It Provides | Website |
|--------|---------|---------|-----------------|---------|
| [OpenAlex](https://openalex.org) | ~212K | CC0 Public Domain | h-index, impact factor, DOAJ status | openalex.org |
| [DOAJ](https://doaj.org) | ~22K | CC0 (metadata) | Open access verification | doaj.org |
| [Stop Predatory Journals](https://predatoryjournals.org) | ~2.5K | MIT License | Predatory journal/publisher blacklist | predatoryjournals.org |

We are grateful to these projects for making academic quality data freely available to the research community.

## Dashboard

Access the journal quality dashboard at **Analytics → Journals** or `/metrics/journals`.

Features:
- **Summary stats**: Total journals, average quality, predatory count, DOAJ listed
- **Quality distribution chart**: Bar chart showing score distribution (1-10)
- **Score source chart**: Doughnut showing OpenAlex vs DOAJ vs LLM breakdown
- **Searchable table**: 212K journals with pagination, filtering by tier/source, sortable columns
- **Trust indicators**: DOAJ checkmark, predatory warning per journal

## Source Quality Tags

Research reports include quality tags next to each source:

```
[1] Physical Review Letters [Q1 ★★★★★] (source nr: 1)
    URL: https://arxiv.org/abs/...

[2] Some Niche Journal [Q3 ★★] (source nr: 2)
    URL: https://arxiv.org/abs/...
```

## Configuration

| Setting | Default | Description |
|---------|---------|-------------|
| `search.journal_reputation.threshold` | `2` | Minimum quality score to keep a result (1-10). Default 2 = predatory only. |
| `search.journal_reputation.enable_llm_scoring` | `false` | Enable Tier 4 LLM analysis for journals not in any bundled dataset |
| `search.journal_reputation.exclude_non_published` | `false` | Drop results without journal reference |
| `search.journal_reputation.reanalysis_period` | `365` | Days before re-scoring cached journals |
| `search.journal_reputation.max_context` | `3000` | Max chars for LLM analysis context |

Per-engine enable/disable:
- `search.engine.web.arxiv.journal_reputation.enabled`
- `search.engine.web.openalex.journal_reputation.enabled`
- `search.engine.web.semantic_scholar.journal_reputation.enabled`
- `search.engine.web.nasa_ads.journal_reputation.enabled`

### Tier 4 Cost & Latency

Enabling `search.journal_reputation.enable_llm_scoring` activates Tier 4 (LLM + web-search analysis for journals that miss every bundled tier). Each unknown-journal scoring costs:

- **Latency**: roughly 3–10 s per unique journal (one SearXNG query + one LLM call; results are deduped per batch).
- **Tokens**: ~300–500 tokens per analysis depending on snippets and model.

The result is cached in the per-user `journals` table for `search.journal_reputation.reanalysis_period` days (default 365), so the cost is amortized across repeated searches of the same rare journal. Leave it off unless you regularly encounter unindexed venues and can tolerate the per-source overhead.

## Troubleshooting

### Journal score looks too low, or the journal was filtered out

- Check `search.journal_reputation.threshold`. The default of `2` only removes predatory journals — raise to `5`+ to also drop unknown / low-impact venues.
- Look up the journal in the `/metrics/journals` dashboard. If it's missing, none of OpenAlex, DOAJ, or the predatory list matched it.
- If the journal is in the predatory list but you believe it's legitimate, verify the whitelist: journals with h-index > `JOURNAL_PREDATORY_WHITELIST_HINDEX` (see `src/local_deep_research/constants.py`) or a DOAJ listing override the predatory flag automatically.

### Journal not appearing in results

- Predatory matches are auto-removed (score 1) unless whitelisted. If you need them back, raise the threshold above 1 — you'll keep the row but see the `[Predatory]` quality tag.
- If Tier 4 is disabled and the journal is unindexed, no score is generated and the source is returned without a quality tag. Enable Tier 4 (`enable_llm_scoring: true`) if you want a score for rare journals.

### Performance feels slow

- The first access after upgrade or a fresh install downloads the bundled data (~1–2 min on a normal connection). Subsequent requests use the cached local DB.
- If Tier 4 is on, each unknown journal adds 3–10 s (see the cost section above). Raise `reanalysis_period` to cache longer, or disable Tier 4 if you don't need it.

## Database Management

The journal-quality reference database (`journal_quality.db`) is built automatically on first access and lives in the user data directory:

- **Linux / macOS**: `~/.local/share/local-deep-research/journal_quality.db` (or `$XDG_DATA_HOME/local-deep-research/journal_quality.db` if set)
- **Windows**: `%APPDATA%\local-deep-research\journal_quality.db`

The file is stored `chmod 0o444` read-only and opened with SQLite `mode=ro&immutable=1` on every connection — the application never writes to it after the build step. To force a fresh rebuild (after corruption or to pick up newer upstream data), delete the file and restart. The next request will re-download and rebuild it in 1–2 minutes.

Do not edit the DB in place: the pre-commit hook at `.pre-commit-hooks/check-journal-quality-readonly.py` rejects any code that opens it writable.

## API Endpoints

| Endpoint | Method | Description |
|----------|--------|-------------|
| `/metrics/journals` | GET | Journal quality dashboard page |
| `/metrics/api/journals` | GET | Paginated journal data with filtering/sorting |

Query parameters for `/metrics/api/journals`:
- `page` (int): Page number (default 1)
- `per_page` (int): Results per page (default 50, max 200)
- `search` (string): Filter by journal name
- `tier` (string): elite, strong, moderate, low, predatory
- `score_source` (string): openalex, doaj, llm, predatory
- `sort` (string): quality, h_index, impact_factor, name, publisher
- `order` (string): asc, desc
- `include_summary` (bool): Include summary stats and chart data
