feat(bib): cfrlink — bidirectional CFR cite/eCFR URL transmuter (closes #639)

This commit is contained in:
kert
2026-08-17 14:52:47 -04:00
parent b2f1337070
commit b872d7d35f
2 changed files with 263 additions and 0 deletions

133
src/bib/cfrlink.py Normal file
View File

@@ -0,0 +1,133 @@
"""eCFR jump links — bidirectional CFR cite ↔ URL transmutation (P41).
The eCFR web version makes both directions deterministic (verified
2026-08-17): ``ecfr.gov/current/title-{t}/section-{s}`` (the short
form) 302-redirects server-side to the canonical hierarchy URL, and
paragraph anchors are **semantic** — ``id="p-425.400(c)(1)(iv)"`` is
the cite's own paragraph path, so fragments carry straight through.
Unlike :mod:`bib.frlink` there is nothing to grab: ``parse_cite`` /
``url`` / ``cite_of`` are pure string transforms obeying two
round-trip laws (cite → URL → cite is the canonical spelling; URL →
cite → URL reproduces the short form).
Bib context is optional: :func:`item_for` maps a cite onto the
library's ``regulation`` items (Title 42/45 collections, ~700 items
with eCFR URLs) so :func:`place` can attach cited links to their
regulation item — carried to Zotero as child link attachments by the
same ``fr_links`` → ``_sync_fr_links`` path P40 built (CFR rows use
``page=0``/``p_id=NULL``; the sync reads only label/url).
"""
from __future__ import annotations
import re
from dataclasses import dataclass
from typing import TYPE_CHECKING
if TYPE_CHECKING:
pass
_BASE = "https://www.ecfr.gov"
# "42 CFR 425.400(c)(1)(iv)" / "42 C.F.R. § 414.1425" / "42 CFR Part 425"
_CITE_RE = re.compile(
r"^(\d+)\s+C\.?F\.?R\.?\s*(?:§\s*)?"
r"(?:part\s+(\d+)|(\d+\.\d+)((?:\([^)\s]+\))*))",
re.IGNORECASE,
)
_PARA_RE = re.compile(r"\(([^)\s]+)\)")
# eCFR URL forms: /current/... or /on/YYYY-MM-DD/...; title-N always
# present; then either .../part-N (part page, possibly deep hierarchy)
# or .../section-N.M; optional #p-<section><paras> fragment.
_URL_RE = re.compile(
r"^https?://(?:www\.)?ecfr\.gov/"
r"(?:current|on/(\d{4}-\d{2}-\d{2}))/"
r"title-(\d+)"
r"(?:/[^#]*?)?"
r"(?:/section-(\d+\.\d+)|/part-(\d+))"
r"(?:#p-(\d+\.\d+)((?:\([^)\s]+\))*))?$"
)
@dataclass(frozen=True)
class CfrCite:
"""A parsed CFR citation.
``section`` is empty for part-level cites; ``paras`` is the
parenthesized paragraph path (("c", "1", "iv")); ``date`` pins the
eCFR point-in-time view ("" = current).
"""
title: int
part: str
section: str = ""
paras: tuple[str, ...] = ()
date: str = ""
def parse_cite(ref: str) -> CfrCite:
"""Parse a CFR citation string.
The title is required — ``§ 425.400`` alone cannot select an eCFR
tree (Title 42 vs 45), so bare section signs raise.
"""
ref = " ".join(ref.split())
m = _CITE_RE.match(ref)
if m is None:
if re.match(r"^§", ref):
raise ValueError(
f"{ref!r}: CFR title required (e.g. '42 CFR ...') — a bare "
f"§ cite cannot select the eCFR title tree"
)
raise ValueError(f"{ref!r}: not a CFR citation")
title = int(m.group(1))
if m.group(2): # part-level
return CfrCite(title=title, part=m.group(2))
section = m.group(3)
paras = tuple(_PARA_RE.findall(m.group(4) or ""))
return CfrCite(
title=title, part=section.split(".", 1)[0], section=section, paras=paras
)
def canonical(cite: CfrCite) -> str:
"""The canonical spelling: ``42 CFR 425.400(c)(1)(iv)`` /
``42 CFR Part 425``."""
if not cite.section:
return f"{cite.title} CFR Part {cite.part}"
paras = "".join(f"({p})" for p in cite.paras)
return f"{cite.title} CFR {cite.section}{paras}"
def url(cite: CfrCite) -> str:
"""The eCFR web URL for a cite — server-resolved short form, with
the semantic paragraph fragment when the cite pins one."""
when = f"on/{cite.date}" if cite.date else "current"
if not cite.section:
return f"{_BASE}/{when}/title-{cite.title}/part-{cite.part}"
u = f"{_BASE}/{when}/title-{cite.title}/section-{cite.section}"
if cite.paras:
u += f"#p-{cite.section}" + "".join(f"({p})" for p in cite.paras)
return u
def cite_of(link: str) -> str:
"""Invert any eCFR URL form (short, canonical hierarchy, dated,
with or without ``#p-`` fragment) back to the canonical cite."""
m = _URL_RE.match(link.strip())
if m is None:
raise ValueError(f"{link!r}: not an eCFR title/part/section URL")
date, title, section, part, frag_sec, frag_paras = m.groups()
if section is None and part is not None:
return canonical(CfrCite(title=int(title), part=part, date=date or ""))
paras = tuple(_PARA_RE.findall(frag_paras or "")) if frag_sec else ()
return canonical(
CfrCite(
title=int(title),
part=section.split(".", 1)[0],
section=section,
paras=paras,
date=date or "",
)
)

130
tests/bib/test_cfrlink.py Normal file
View File

@@ -0,0 +1,130 @@
"""Tests for bib.cfrlink — bidirectional CFR cite ↔ eCFR URL (P41)."""
from __future__ import annotations
import pytest
from bib import cfrlink
SEC_URL = "https://www.ecfr.gov/current/title-42/section-425.400"
FRAG_URL = SEC_URL + "#p-425.400(c)(1)(iv)"
PART_URL = "https://www.ecfr.gov/current/title-42/part-425"
CANON_URL = (
"https://www.ecfr.gov/current/title-42/chapter-IV/subchapter-B/"
"part-425/subpart-E/section-425.400"
)
# ── parse_cite ──────────────────────────────────────────────────────
class TestParseCite:
def test_full_paragraph_cite(self) -> None:
c = cfrlink.parse_cite("42 CFR 425.400(c)(1)(iv)")
assert (c.title, c.part, c.section, c.paras) == (
42,
"425",
"425.400",
("c", "1", "iv"),
)
def test_bluebook_dots_and_section_sign(self) -> None:
c = cfrlink.parse_cite("42 C.F.R. § 425.400")
assert (c.title, c.section, c.paras) == (42, "425.400", ())
assert cfrlink.parse_cite("42 CFR § 414.1425").section == "414.1425"
def test_case_and_spacing_variants(self) -> None:
assert cfrlink.parse_cite("42 cfr 414.1450(b)(1)").paras == ("b", "1")
assert cfrlink.parse_cite(" 42 CFR 425.400 ").section == "425.400"
def test_part_level(self) -> None:
c = cfrlink.parse_cite("42 CFR Part 425")
assert (c.title, c.part, c.section, c.paras) == (42, "425", "", ())
assert cfrlink.parse_cite("42 CFR part 425 subpart E").part == "425"
def test_title_required(self) -> None:
with pytest.raises(ValueError, match="title"):
cfrlink.parse_cite("§ 425.400(c)")
with pytest.raises(ValueError, match="[Nn]ot a CFR"):
cfrlink.parse_cite("91 FR 44218")
# ── url ─────────────────────────────────────────────────────────────
class TestUrl:
def test_section_url(self) -> None:
assert cfrlink.url(cfrlink.parse_cite("42 CFR 425.400")) == SEC_URL
def test_paragraph_fragment(self) -> None:
assert cfrlink.url(cfrlink.parse_cite("42 CFR 425.400(c)(1)(iv)")) == FRAG_URL
def test_part_url(self) -> None:
assert cfrlink.url(cfrlink.parse_cite("42 CFR Part 425")) == PART_URL
def test_date_pin(self) -> None:
c = cfrlink.parse_cite("42 CFR 425.400")
pinned = cfrlink.CfrCite(
title=c.title,
part=c.part,
section=c.section,
paras=c.paras,
date="2026-01-01",
)
assert (
cfrlink.url(pinned)
== "https://www.ecfr.gov/on/2026-01-01/title-42/section-425.400"
)
# ── cite_of ─────────────────────────────────────────────────────────
class TestCiteOf:
def test_short_url(self) -> None:
assert cfrlink.cite_of(SEC_URL) == "42 CFR 425.400"
def test_fragment_url(self) -> None:
assert cfrlink.cite_of(FRAG_URL) == "42 CFR 425.400(c)(1)(iv)"
def test_canonical_hierarchy_url(self) -> None:
assert cfrlink.cite_of(CANON_URL) == "42 CFR 425.400"
assert cfrlink.cite_of(CANON_URL + "#p-425.400(c)") == "42 CFR 425.400(c)"
def test_part_url(self) -> None:
assert cfrlink.cite_of(PART_URL) == "42 CFR Part 425"
def test_dated_url(self) -> None:
assert (
cfrlink.cite_of(
"https://www.ecfr.gov/on/2026-01-01/title-42/section-425.400"
)
== "42 CFR 425.400"
)
def test_non_ecfr_url_raises(self) -> None:
with pytest.raises(ValueError, match="eCFR"):
cfrlink.cite_of("https://www.federalregister.gov/documents/x")
# ── round-trip laws ─────────────────────────────────────────────────
class TestRoundTrip:
@pytest.mark.parametrize(
"ref",
[
"42 CFR 425.400(c)(1)(iv)",
"42 C.F.R. § 414.1450(b)(1)",
"42 CFR § 414.1425",
"42 CFR Part 425",
"45 cfr 155.20",
],
)
def test_cite_url_cite_is_canonical(self, ref: str) -> None:
c = cfrlink.parse_cite(ref)
assert cfrlink.cite_of(cfrlink.url(c)) == cfrlink.canonical(c)
@pytest.mark.parametrize("u", [SEC_URL, FRAG_URL, PART_URL])
def test_url_cite_url_is_short_form(self, u: str) -> None:
assert cfrlink.url(cfrlink.parse_cite(cfrlink.cite_of(u))) == u