Releases: jbiter/citationer
Release list
v4.6.1 - Dedup year=None Fix
Dedup Fix
Fixed dedup engine wrongly merging records without year data.
Tests: 628 passing, coverage 80.83%.
v4.6.0 — Interactive Save
Interactive Save
Interactive wizard now saves real report files (was previously a placeholder).
Tests: 622 passing, coverage 80.80%.
v4.5.0 — Report Templates
Report Templates
New simple template: concise summary with top-5 authors/keywords and last 5 years. Academic template unchanged.
Tests: 610 passing, coverage 80.86%.
v4.4.0 — Funding Analysis
Funding Analysis
New citationer stats funding subcommand: funding rate, top-N funders, yearly trend.
Tests: 582 passing, coverage 80.81%.
v4.3.0 — Standalone Binary
Standalone Binary
PyInstaller-packaged executable for Linux, macOS, and Windows. Built automatically on tag push.
Tests: 562 passing.
v4.2.0 — Documentation Site
Documentation Site
MkDocs site deployed to GitHub Pages with changelog, command reference, and installation guide.
Bug Fixes
CI workflow fix for MkDocs build path.
Tests: 562 passing.
v4.1.2 — Bug Fixes
v4.1.1 — Fix BUG-001 (db_loader crash)
v4.1.1 — Bug Fix Release
🐛 Fix: BUG-001 (Critical)
load_records_from_db() no longer crashes when records have NULL year or citation_count.
Root cause: SQLite stores nullable INTEGER columns as empty strings "" when not set. The loader passed these "" values directly to Pydantic's Record constructor, which expects int | None and rejected empty strings with int_parsing validation errors.
Impact: After importing any bibliographic file with incomplete metadata (missing year or citation count), all subsequent analysis commands (stats, text, network, trend) crashed.
Fix: Added _opt_int() helper in src/citationer/utils/db_loader.py:103-111 that converts empty strings, None, or whitespace-only strings to None before passing to Pydantic.
def _opt_int(value):
if value is None:
return None
if isinstance(value, int):
return value
if value == "" or str(value).strip() == "":
return None
return value✅ Regression Tests
3 new tests in tests/test_database.py::TestBug001Regression:
test_load_with_null_year— Pydantic Record accepts NULL yeartest_load_with_empty_string_year— Manual SQL UPDATE sets int columns to NULL → load succeedstest_round_trip_orphan_record— Full round-trip with all year/citation_count fields missing
📊 Status
- Tests: 562 passing (was 559)
- Coverage: 80.49% (CI gate 80% met)
- Lint: ruff clean
🐞 Remaining Defects (not fixed in 4.1.1)
- #16 BUG-002: Institution.name_en not serialized
- #17 BUG-003: en/zh keywords not split on load
- #19 BUG-004: trend baseline median polluted by burst
- #20 BUG-005: trend strategy recomputes in second pass
Closes #15
v4.1.0 — Test Coverage 80% (P4-1 Complete)
v4.1.0 — Test Coverage 80% (P4-1 Complete)
🎯 Major Milestone
Test coverage increased from 35% → 80.51% (CI gate at 80% enforced). Test count grew from 153 to 559 passing tests.
📊 Coverage Breakdown
Newly tested modules (90-100% coverage)
utils/database.py: 70% → 100%utils/serialization.py: 90% → 100%utils/db_loader.py: 67% → 98%analysis/trend.py: 15% → 93%parsers/cnki.py: 19% → 93%parsers/pubmed.py: 14% → 93%parsers/wos.py: 40% → 92%viz/charts.py: 0% → 89%cli/help.py: 0% → 100%cli/main.py: 0% → 92%
Test additions
- 5 new test files for low-coverage CLI modules
- 17 new test files total (excluding conftest)
- Comprehensive
conftest.pywith shared fixtures - End-to-end CLI testing via Typer
CliRunner
🐛 Defects Discovered (not fixed in this release)
Documented via GitHub issues, scheduled for v4.1.1 or v4.2.0:
| # | Severity | Description |
|---|---|---|
| #15 | 🔴 Critical | empty-string int fields crash db_loader |
| #16 | 🟠 Major | Institution.name_en not serialized |
| #17 | 🟡 Minor | en/zh keywords not split back on load |
| #19 | 🟠 Major | trend baseline median polluted by burst |
| #20 | 🟢 Suggestion | trend strategy recomputes in second pass |
🔧 CI Improvements
- Coverage gate:
--cov-fail-under=80 - All ruff checks pass (lint clean)
- All 559 tests pass on Python 3.11/3.12
📦 Installation
pipx install citationer
# or
pip install citationer🛠 Development
git clone https://github.com/jbiter/citationer.git
cd citationer
pip install -e ".[all,dev]"
pytest tests/ --cov=src/citationer