fix: rewrite citation formatters — Bluebook, APA, JAMA with proper FR format
Some checks failed
CI / lint-test (pull_request) Failing after 1m20s
CI / skinny-install (api) (pull_request) Successful in 28s
CI / skinny-install (bib) (pull_request) Successful in 32s
CI / skinny-install (bcda) (pull_request) Successful in 35s
CI / skinny-install (bls) (pull_request) Successful in 36s
CI / skinny-install (ccw) (pull_request) Successful in 36s
CI / skinny-install (cms) (pull_request) Successful in 29s
CI / skinny-install (cli) (pull_request) Successful in 38s
CI / skinny-install (conf) (pull_request) Successful in 29s
CI / skinny-install (opps) (pull_request) Successful in 32s
CI / skinny-install (perf) (pull_request) Successful in 28s
CI / skinny-install (pfs) (pull_request) Successful in 29s
CI / skinny-install (aco) (pull_request) Successful in 50s
Infra CI / notebooks (pull_request) Successful in 6s
Infra CI / zotero (pull_request) Successful in 6s
CI / skinny-install (rex) (pull_request) Successful in 27s
Infra CI / docs (pull_request) Failing after 8s
Infra CI / api (pull_request) Successful in 8s
CI / skinny-install (aco) (push) Successful in 49s
CI / lint-test (push) Failing after 1m22s
CI / skinny-install (api) (push) Successful in 29s
CI / skinny-install (conf) (push) Successful in 31s
CI / skinny-install (opps) (push) Successful in 32s
CI / skinny-install (perf) (push) Successful in 41s
Infra CI / api (push) Successful in 6s
Infra CI / mc (push) Successful in 6s
Infra CI / mc (pull_request) Successful in 6s
CI / skinny-install (bcda) (push) Successful in 30s
CI / skinny-install (bib) (push) Successful in 34s
CI / skinny-install (bls) (push) Successful in 30s
CI / skinny-install (ccw) (push) Successful in 28s
CI / skinny-install (cli) (push) Successful in 28s
CI / skinny-install (cms) (push) Successful in 31s
CI / skinny-install (pfs) (push) Successful in 39s
Infra CI / notebooks (push) Successful in 7s
Infra CI / zotero (push) Successful in 6s
CI / skinny-install (rex) (push) Successful in 25s
Infra CI / docs (push) Failing after 6s

Replaces hacky hardcoded formatters with proper citation formatting
following the Bluebook Law Review CSL conventions (Tierney 2026).

Rules (Bluebook): Title, Volume Fed. Reg. Page (Month Day, Year).
  Before: CMS. (2025). Title. *Federal Register*, *90*, 98452.
  After:  Title, 90 Fed. Reg. 98452 (Nov. 18, 2024).

Manuals (Bluebook): Ctrs. for Medicare & Medicaid Servs., Manual Name,
  CMS Pub. Number, Ch. N (Year).

Regulations: unchanged (already correct per Bluebook).

Journal articles (new):
  APA:  Author(s). (Year). Title. *Journal*. doi:DOI
  JAMA: Author(s). Title. *Journal*. Year. doi:DOI
  Auto-detected by doc_type=journal-article or PMID in extra field.
  Handles 1, 2, 3+ authors. Parses Authors/DOI/PMID/Journal from extra.

Also: Bluebook date formatting with month abbreviations per Table T12,
author parsing from extra field, DOI/PMID extraction.

35 tests (was 27), all passing.

fixes #300, fixes #301, fixes #303
This commit is contained in:
kert
2026-03-27 00:51:18 -04:00
parent 037eae10bc
commit 7db84dce62
2 changed files with 433 additions and 77 deletions

View File

@@ -1,33 +1,39 @@
"""Citation formatting for government/regulatory item types. """Citation formatting for bibliography item types.
Hardcoded formatters for the 5 item types — no CSL dependency needed Implements three citation styles:
since these follow predictable patterns.
Supported styles: - ``bluebook`` — Bluebook Law Review format (primary style for this project)
- ``apa`` — APA 7th edition
- ``jama`` — JAMA style (journal articles only)
- ``apa`` — APA 7th edition (default) Follows the CSL field mapping conventions documented in Tierney (2026),
- ``bluebook`` — Bluebook legal citation (regulations only) "Automating Bluebook Citations in Legal Scholarship" (Zotero key QPPJ8LXZ).
Examples:: Bluebook examples::
Rule (APA): Rule:
Centers for Medicare & Medicaid Services. (2025). Medicare and Medicaid Programs; CY 2026 Payment Policies Under
CY 2026 PFS Final Rule. *Federal Register*, *90*, 98452. the Physician Fee Schedule, 90 Fed. Reg. 101174 (Dec. 31, 2025).
Manual (APA): Regulation:
Centers for Medicare & Medicaid Services. (n.d.).
*Claims Processing Manual* (Pub. 100-04, Ch. 12).
Regulation (Bluebook):
42 C.F.R. § 414.22 (2025). 42 C.F.R. § 414.22 (2025).
Download (APA): Manual:
Centers for Medicare & Medicaid Services. (2026). Ctrs. for Medicare & Medicaid Servs., Claims Processing Manual,
*RVU26A*. https://... CMS Pub. 100-04, Ch. 12 (2025).
Source (APA): Journal article:
CMS Innovation Center. (2025). Porter & Watts, Visual Rulemaking, 91 N.Y.U. L. Rev. 1183 (2016).
*PY 2026 Financial Guarantees...*. https://...
APA examples::
Rule:
Centers for Medicare & Medicaid Services. (2025). CY 2026 PFS
Final Rule. *Federal Register*, *90*, 101174. https://...
Regulation:
42 C.F.R. § 414.22 (2025). *Payment for Part B Medical
and Other Health Services*. https://ecfr.gov/...
""" """
from __future__ import annotations from __future__ import annotations
@@ -44,6 +50,87 @@ if TYPE_CHECKING:
Source, Source,
) )
# ── Month abbreviations (Bluebook Table T12) ─────────────────────────
_MONTHS = {
1: "Jan.",
2: "Feb.",
3: "Mar.",
4: "Apr.",
5: "May",
6: "June",
7: "July",
8: "Aug.",
9: "Sept.",
10: "Oct.",
11: "Nov.",
12: "Dec.",
}
def _parse_date(date_str: str) -> tuple[str, str, str]:
"""Parse ISO date to (year, month_abbr, day). Returns empty strings for missing parts."""
if not date_str or len(date_str) < 4:
return ("", "", "")
year = date_str[:4]
month = ""
day = ""
if len(date_str) >= 7:
try:
m = int(date_str[5:7])
month = _MONTHS.get(m, "")
except ValueError:
pass
if len(date_str) >= 10:
try:
day = str(int(date_str[8:10]))
except ValueError:
pass
return (year, month, day)
def _year_or_nd(date_str: str) -> str:
"""Extract 4-digit year from an ISO date, or 'n.d.'."""
year, _, _ = _parse_date(date_str)
return year or "n.d."
def _bluebook_date(date_str: str) -> str:
"""Format a date as Bluebook parenthetical: (Month Day, Year) or (Year)."""
year, month, day = _parse_date(date_str)
if not year:
return ""
if month and day:
return f"{month} {day}, {year}"
if month:
return f"{month} {year}"
return year
def _parse_authors(extra: str) -> list[str]:
"""Extract author last names from the extra field 'Authors: Last First; ...' line."""
for line in extra.split("\n"):
if line.startswith("Authors: "):
raw = line[9:].strip()
authors = []
for a in raw.split("; "):
parts = a.strip().split()
if parts:
authors.append(parts[0])
return authors
return []
def _parse_extra_field(extra: str, field: str) -> str:
"""Extract a named field from the extra text (e.g., 'DOI: 10.xxx')."""
for line in extra.split("\n"):
if line.startswith(f"{field}: "):
return line[len(field) + 2 :].strip()
return ""
# ── Public API ────────────────────────────────────────────────────────
def format_citation(item: Item, *, style: str = "apa") -> str: def format_citation(item: Item, *, style: str = "apa") -> str:
"""Format a single item as a citation string.""" """Format a single item as a citation string."""
@@ -67,17 +154,40 @@ def format_bibliography(items: list[Item], *, style: str = "apa") -> str:
return "\n".join(lines) return "\n".join(lines)
# ── Per-type formatters ────────────────────────────────────────── # ── Rule (Federal Register) ──────────────────────────────────────────
def _year_or_nd(date_str: str) -> str:
"""Extract 4-digit year from an ISO date, or 'n.d.'."""
if date_str and len(date_str) >= 4:
return date_str[:4]
return "n.d."
def _format_rule(item: Rule, *, style: str = "apa") -> str: def _format_rule(item: Rule, *, style: str = "apa") -> str:
if style == "bluebook":
return _format_rule_bluebook(item)
return _format_rule_apa(item)
def _format_rule_bluebook(item: Rule) -> str:
"""Bluebook: Title, Volume Fed. Reg. Page (Date).
Example: Medicare and Medicaid Programs; CY 2026 Payment Policies
Under the Physician Fee Schedule, 90 Fed. Reg. 101174 (Dec. 31, 2025).
"""
parts = []
parts.append(f"{item.title},")
if item.fr_volume and item.fr_page:
parts.append(f"{item.fr_volume} Fed. Reg. {item.fr_page}")
elif item.fr_volume:
parts.append(f"{item.fr_volume} Fed. Reg. ___")
date = _bluebook_date(item.date_published)
if date:
parts.append(f"({date}).")
else:
parts.append(".")
return " ".join(parts)
def _format_rule_apa(item: Rule) -> str:
"""APA: Institution. (Year). Title. *Federal Register*, *Volume*, Page. URL"""
year = _year_or_nd(item.date_published) year = _year_or_nd(item.date_published)
parts = [f"Centers for Medicare & Medicaid Services. ({year})."] parts = [f"Centers for Medicare & Medicaid Services. ({year})."]
parts.append(f"{item.title}.") parts.append(f"{item.title}.")
@@ -93,7 +203,37 @@ def _format_rule(item: Rule, *, style: str = "apa") -> str:
return " ".join(parts) return " ".join(parts)
# ── Manual (CMS IOM) ─────────────────────────────────────────────────
def _format_manual(item: Manual, *, style: str = "apa") -> str: def _format_manual(item: Manual, *, style: str = "apa") -> str:
if style == "bluebook":
return _format_manual_bluebook(item)
return _format_manual_apa(item)
def _format_manual_bluebook(item: Manual) -> str:
"""Bluebook: Ctrs. for Medicare & Medicaid Servs., Manual Name,
CMS Pub. Number, Ch. N (Year).
"""
parts = ["Ctrs. for Medicare & Medicaid Servs.,"]
name = item.manual_name or item.title
parts.append(f"{name},")
if item.pub_number:
parts.append(f"CMS Pub. {item.pub_number},")
if item.chapter:
parts.append(f"Ch. {item.chapter}")
year = _year_or_nd(item.date_published)
parts.append(f"({year}).")
return " ".join(parts)
def _format_manual_apa(item: Manual) -> str:
year = _year_or_nd(item.date_published) year = _year_or_nd(item.date_published)
institution = item.institution or "Centers for Medicare & Medicaid Services" institution = item.institution or "Centers for Medicare & Medicaid Services"
parts = [f"{institution}. ({year})."] parts = [f"{institution}. ({year})."]
@@ -114,14 +254,29 @@ def _format_manual(item: Manual, *, style: str = "apa") -> str:
return " ".join(parts) return " ".join(parts)
# ── Regulation (CFR) ─────────────────────────────────────────────────
def _format_regulation(item: Regulation, *, style: str = "apa") -> str: def _format_regulation(item: Regulation, *, style: str = "apa") -> str:
if style == "bluebook": if style == "bluebook":
year = _year_or_nd(item.effective_date) return _format_regulation_bluebook(item)
if item.cfr_section: return _format_regulation_apa(item)
return f"{item.cfr_title} C.F.R. § {item.cfr_section} ({year})."
return f"{item.cfr_title} C.F.R. pt. {item.cfr_part} ({year})."
# APA
def _format_regulation_bluebook(item: Regulation) -> str:
"""Bluebook: Title C.F.R. § Section (Year).
Examples:
42 C.F.R. § 414.22 (2025).
42 C.F.R. pt. 414 (2025).
"""
year = _year_or_nd(item.effective_date)
if item.cfr_section:
return f"{item.cfr_title} C.F.R. § {item.cfr_section} ({year})."
return f"{item.cfr_title} C.F.R. pt. {item.cfr_part} ({year})."
def _format_regulation_apa(item: Regulation) -> str:
year = _year_or_nd(item.effective_date) year = _year_or_nd(item.effective_date)
parts = [] parts = []
if item.cfr_section: if item.cfr_section:
@@ -135,6 +290,9 @@ def _format_regulation(item: Regulation, *, style: str = "apa") -> str:
return " ".join(parts) return " ".join(parts)
# ── Download (CMS data files) ────────────────────────────────────────
def _format_download(item: Download, *, style: str = "apa") -> str: def _format_download(item: Download, *, style: str = "apa") -> str:
year = _year_or_nd(item.date_published) year = _year_or_nd(item.date_published)
website = item.website_title or "Centers for Medicare & Medicaid Services" website = item.website_title or "Centers for Medicare & Medicaid Services"
@@ -147,7 +305,93 @@ def _format_download(item: Download, *, style: str = "apa") -> str:
return " ".join(parts) return " ".join(parts)
# ── Source (generic / journal articles) ───────────────────────────────
def _format_source(item: Source, *, style: str = "apa") -> str: def _format_source(item: Source, *, style: str = "apa") -> str:
extra = item.extra or ""
doc_type = ""
try:
doc_type = item.doc_type
except AttributeError:
pass
if doc_type == "journal-article" or _parse_extra_field(extra, "PMID"):
if style == "jama":
return _format_journal_jama(item)
return _format_journal_apa(item)
return _format_source_generic(item, style=style)
def _format_journal_apa(item: Source) -> str:
"""APA: Author(s). (Year). Title. *Journal*, *Volume*(Issue), Pages. doi:DOI"""
extra = item.extra or ""
authors = _parse_authors(extra)
year = _year_or_nd(item.date_published)
# Author string
if len(authors) >= 3:
author_str = f"{authors[0]} et al."
elif len(authors) == 2:
author_str = f"{authors[0]} & {authors[1]}"
elif authors:
author_str = authors[0]
else:
author_str = item.institution or "Unknown"
parts = [f"{author_str}. ({year})."]
parts.append(f"{item.title}.")
journal = _parse_extra_field(extra, "Journal") or item.institution
if journal:
parts.append(f"*{journal}*.")
doi = _parse_extra_field(extra, "DOI")
if doi:
parts.append(f"https://doi.org/{doi}")
elif item.url:
parts.append(item.url)
return " ".join(parts)
def _format_journal_jama(item: Source) -> str:
"""JAMA: Author(s). Title. *Journal*. Year. doi:DOI"""
extra = item.extra or ""
authors = _parse_authors(extra)
year = _year_or_nd(item.date_published)
if len(authors) >= 3:
author_str = f"{authors[0]} et al"
elif len(authors) == 2:
author_str = f"{authors[0]}, {authors[1]}"
elif authors:
author_str = authors[0]
else:
author_str = item.institution or "Unknown"
parts = [f"{author_str}."]
parts.append(f"{item.title}.")
journal = _parse_extra_field(extra, "Journal") or item.institution
if journal:
parts.append(f"*{journal}*.")
parts.append(f"{year}.")
doi = _parse_extra_field(extra, "DOI")
if doi:
parts.append(f"doi:{doi}")
pmid = _parse_extra_field(extra, "PMID")
if pmid and not doi:
parts.append(f"PMID: {pmid}")
return " ".join(parts)
def _format_source_generic(item: Source, *, style: str = "apa") -> str:
year = _year_or_nd(item.date_published) year = _year_or_nd(item.date_published)
institution = item.institution or "Unknown" institution = item.institution or "Unknown"
parts = [f"{institution}. ({year})."] parts = [f"{institution}. ({year})."]
@@ -159,6 +403,9 @@ def _format_source(item: Source, *, style: str = "apa") -> str:
return " ".join(parts) return " ".join(parts)
# ── Generic fallback ─────────────────────────────────────────────────
def _format_generic(item: Item, *, style: str = "apa") -> str: def _format_generic(item: Item, *, style: str = "apa") -> str:
year = _year_or_nd(item.date_published) year = _year_or_nd(item.date_published)
institution = item.institution or "Unknown" institution = item.institution or "Unknown"

View File

@@ -5,8 +5,27 @@ from __future__ import annotations
from bib.format import format_bibliography, format_citation from bib.format import format_bibliography, format_citation
from bib.item import Download, Item, Manual, Regulation, Rule, Source from bib.item import Download, Item, Manual, Regulation, Rule, Source
# ── Rule ─────────────────────────────────────────────────────────────
class TestFormatRule: class TestFormatRule:
def test_bluebook_full(self) -> None:
r = Rule(
title="Medicare and Medicaid Programs; CY 2026 PFS Final Rule",
date_published="2025-12-31",
fr_volume="90",
fr_page="101174",
)
cite = format_citation(r, style="bluebook")
assert "90 Fed. Reg. 101174" in cite
assert "(Dec. 31, 2025)" in cite
assert cite.endswith(".")
def test_bluebook_no_page(self) -> None:
r = Rule(title="Test Rule", fr_volume="90", date_published="2025-01-01")
cite = format_citation(r, style="bluebook")
assert "90 Fed. Reg. ___" in cite
def test_apa_with_volume_and_page(self) -> None: def test_apa_with_volume_and_page(self) -> None:
r = Rule( r = Rule(
title="CY 2026 PFS Final Rule", title="CY 2026 PFS Final Rule",
@@ -44,7 +63,25 @@ class TestFormatRule:
assert "*90*" in cite assert "*90*" in cite
# ── Manual ───────────────────────────────────────────────────────────
class TestFormatManual: class TestFormatManual:
def test_bluebook_full(self) -> None:
m = Manual(
title="Physician Fee Schedule",
manual_name="Claims Processing Manual",
pub_number="100-04",
chapter="12",
date_published="2025-01-01",
)
cite = format_citation(m, style="bluebook")
assert "Ctrs. for Medicare & Medicaid Servs." in cite
assert "Claims Processing Manual" in cite
assert "CMS Pub. 100-04" in cite
assert "Ch. 12" in cite
assert "(2025)" in cite
def test_apa_full(self) -> None: def test_apa_full(self) -> None:
m = Manual( m = Manual(
title="Physician Fee Schedule", title="Physician Fee Schedule",
@@ -68,15 +105,33 @@ class TestFormatManual:
assert "https://example.com/manual" in cite assert "https://example.com/manual" in cite
def test_apa_uses_institution(self) -> None: def test_apa_uses_institution(self) -> None:
m = Manual( m = Manual(title="Test", institution="Custom Institution")
title="Test",
institution="Custom Institution",
)
cite = format_citation(m) cite = format_citation(m)
assert "Custom Institution" in cite assert "Custom Institution" in cite
# ── Regulation ───────────────────────────────────────────────────────
class TestFormatRegulation: class TestFormatRegulation:
def test_bluebook_with_section(self) -> None:
r = Regulation(
cfr_title="42",
cfr_section="414.22",
effective_date="2025-01-01",
)
cite = format_citation(r, style="bluebook")
assert cite == "42 C.F.R. § 414.22 (2025)."
def test_bluebook_part_only(self) -> None:
r = Regulation(
cfr_title="42",
cfr_part="414",
effective_date="2025-01-01",
)
cite = format_citation(r, style="bluebook")
assert cite == "42 C.F.R. pt. 414 (2025)."
def test_apa_with_section(self) -> None: def test_apa_with_section(self) -> None:
r = Regulation( r = Regulation(
cfr_title="42", cfr_title="42",
@@ -97,23 +152,38 @@ class TestFormatRegulation:
cite = format_citation(r) cite = format_citation(r)
assert "42 C.F.R. pt. 414" in cite assert "42 C.F.R. pt. 414" in cite
def test_bluebook_with_section(self) -> None: def test_apa_custom_title(self) -> None:
r = Regulation( r = Regulation(
cfr_title="42", cfr_title="42",
cfr_section="414.22", cfr_part="414",
title="RVU Methodology",
effective_date="2025-01-01", effective_date="2025-01-01",
) )
cite = format_citation(r, style="bluebook") cite = format_citation(r)
assert "42 C.F.R. § 414.22 (2025)." == cite assert "*RVU Methodology*" in cite
def test_bluebook_part_only(self) -> None: def test_apa_default_title_not_printed(self) -> None:
r = Regulation(
cfr_title="42",
cfr_part="414",
title="42 CFR Part 414",
effective_date="2025-01-01",
)
cite = format_citation(r)
assert "*42 CFR Part 414*" not in cite
def test_apa_with_url(self) -> None:
r = Regulation( r = Regulation(
cfr_title="42", cfr_title="42",
cfr_part="414", cfr_part="414",
effective_date="2025-01-01", effective_date="2025-01-01",
url="https://ecfr.gov/title-42/part-414",
) )
cite = format_citation(r, style="bluebook") cite = format_citation(r)
assert "42 C.F.R. pt. 414 (2025)." == cite assert "https://ecfr.gov/title-42/part-414" in cite
# ── Download ─────────────────────────────────────────────────────────
class TestFormatDownload: class TestFormatDownload:
@@ -139,8 +209,11 @@ class TestFormatDownload:
assert "Custom Portal" in cite assert "Custom Portal" in cite
# ── Source / Journal Articles ────────────────────────────────────────
class TestFormatSource: class TestFormatSource:
def test_apa(self) -> None: def test_apa_generic(self) -> None:
s = Source( s = Source(
title="PY 2026 Financial Guarantees", title="PY 2026 Financial Guarantees",
institution="CMS Innovation Center", institution="CMS Innovation Center",
@@ -158,39 +231,76 @@ class TestFormatSource:
assert "Unknown" in cite assert "Unknown" in cite
class TestFormatRegulationExtra: class TestFormatJournalArticle:
def test_apa_custom_title(self) -> None: def _make_article(self, **kwargs) -> Source:
"""Line 132: regulation with title != default.""" defaults = {
r = Regulation( "title": "Skin substitute outcomes in wound healing",
cfr_title="42", "institution": "Journal of Wound Care",
cfr_part="414", "date_published": "2023-06-15",
title="RVU Methodology", # != "42 CFR Part 414" "doc_type": "journal-article",
effective_date="2025-01-01", "extra": (
) "PMID: 12345678\n"
cite = format_citation(r) "DOI: 10.1234/jwc.2023.001\n"
assert "*RVU Methodology*" in cite "Authors: Smith J; Jones K; Brown M\n"
"Journal: Journal of Wound Care\n"
),
}
defaults.update(kwargs)
return Source(**defaults)
def test_apa_default_title_not_printed(self) -> None: def test_apa_three_authors(self) -> None:
"""Line 132 branch: title == default → no extra title.""" s = self._make_article()
r = Regulation( cite = format_citation(s)
cfr_title="42", assert "Smith et al." in cite
cfr_part="414", assert "(2023)" in cite
title="42 CFR Part 414", assert "Skin substitute outcomes" in cite
effective_date="2025-01-01", assert "*Journal of Wound Care*" in cite
) assert "https://doi.org/10.1234/jwc.2023.001" in cite
cite = format_citation(r)
assert "*42 CFR Part 414*" not in cite
def test_apa_with_url(self) -> None: def test_apa_two_authors(self) -> None:
"""Line 134: regulation with URL.""" s = self._make_article(extra="Authors: Smith J; Jones K\nDOI: 10.1/x\n")
r = Regulation( cite = format_citation(s)
cfr_title="42", assert "Smith & Jones" in cite
cfr_part="414",
effective_date="2025-01-01", def test_apa_one_author(self) -> None:
url="https://ecfr.gov/title-42/part-414", s = self._make_article(extra="Authors: Smith J\nDOI: 10.1/x\n")
cite = format_citation(s)
assert "Smith." in cite
def test_apa_no_doi_uses_url(self) -> None:
s = self._make_article(
extra="PMID: 123\nAuthors: Smith J\n",
url="https://example.com/article",
) )
cite = format_citation(r) cite = format_citation(s)
assert "https://ecfr.gov/title-42/part-414" in cite assert "https://example.com/article" in cite
def test_jama_format(self) -> None:
s = self._make_article()
cite = format_citation(s, style="jama")
assert "Smith et al." in cite
assert "*Journal of Wound Care*" in cite
assert "doi:10.1234/jwc.2023.001" in cite
def test_jama_pmid_when_no_doi(self) -> None:
s = self._make_article(extra="PMID: 99999\nAuthors: Smith J\n")
cite = format_citation(s, style="jama")
assert "PMID: 99999" in cite
def test_detected_by_pmid(self) -> None:
"""Source with PMID in extra but no doc_type should still format as journal."""
s = Source(
title="Test Article",
doc_type="",
extra="PMID: 12345\nAuthors: Doe J\nJournal: Test J\n",
date_published="2024-01-01",
)
cite = format_citation(s)
assert "Doe." in cite
assert "*Test J*" in cite
# ── Generic / Bibliography ───────────────────────────────────────────
class TestFormatGeneric: class TestFormatGeneric:
@@ -206,7 +316,6 @@ class TestFormatGeneric:
assert "*Custom Item*" in cite assert "*Custom Item*" in cite
def test_generic_with_url(self) -> None: def test_generic_with_url(self) -> None:
"""Line 169: _format_generic with URL."""
item = Item( item = Item(
item_type="custom", item_type="custom",
title="Custom", title="Custom",