Merge pull request 'fix: rewrite citation formatters — Bluebook, APA, JAMA' (#308) from feat/csl-citation-format into main
Some checks failed
CI / skinny-install (bcda) (push) Successful in 28s
CI / skinny-install (aco) (push) Successful in 49s
CI / skinny-install (api) (push) Successful in 27s
CI / lint-test (push) Failing after 1m24s
CI / skinny-install (bib) (push) Successful in 33s
CI / skinny-install (bls) (push) Successful in 25s
CI / skinny-install (cli) (push) Successful in 29s
CI / skinny-install (opps) (push) Successful in 35s
CI / skinny-install (pfs) (push) Successful in 30s
CI / skinny-install (rex) (push) Successful in 31s
Infra CI / notebooks (push) Successful in 6s
Infra CI / zotero (push) Successful in 6s
Infra CI / docs (push) Failing after 6s
Infra CI / api (push) Successful in 6s
Infra CI / mc (push) Successful in 9s
CI / skinny-install (ccw) (push) Successful in 34s
CI / skinny-install (cms) (push) Successful in 30s
CI / skinny-install (conf) (push) Successful in 26s
CI / skinny-install (perf) (push) Successful in 28s
Deploy / build-scan-report (push) Failing after 1m53s

This commit was merged in pull request #308.
This commit is contained in:
2026-03-27 05:24:38 +00:00
5 changed files with 474 additions and 116 deletions

View File

@@ -85,7 +85,7 @@ def stg_beneficiary_xref(
CCLF IP Reference
~~~~~~~~~~~~~~~~~~
CCLF IP Section 3.1 "Matching MBIs" (p.14):
:pincite:`VTWVB384 §3.1 p.14` — Matching MBIs:
"A beneficiary's MBI is unique to that beneficiary but may change
over time. CCLFs 1 through 9, A, and B, as they are provided each
@@ -94,7 +94,7 @@ def stg_beneficiary_xref(
can be used each month to cross reference records associated with the
current MBI to records associated with previous MBIs."
CCLF IP Section 5.1.1 "Creation of the Most Recent MBI field" (p.19):
:pincite:`VTWVB384 §5.1.1 p.19` — Creation of the Most Recent MBI field:
"The beneficiary XREF file (CCLF9) contains a complete history of
MBIs ever used to identify a beneficiary, for beneficiaries who have
@@ -129,7 +129,7 @@ def stg_institutional_header(
CCLF IP Reference
~~~~~~~~~~~~~~~~~~
CCLF IP Section 2.2.1 "Part A Claims Header File" (p.8-9):
:pincite:`VTWVB384 §2.2.1 p.8-9` — Part A Claims Header File:
"The Part A Claims Header File (CCLF1) contains header-level claim
information for institutional services covered by Part A and some
@@ -141,7 +141,7 @@ def stg_institutional_header(
MBI resolution: BENE_MBI_ID is joined to CCLF9._stg_beneficiary_xref
on PRVS_NUM to resolve to CURRENT_BENE_MBI_ID via COALESCE.
CCLF IP Section 5.3.1 "Calculating Total Part A and B Expenditures" (p.23):
:pincite:`VTWVB384 §5.3.1 p.23` — Calculating Total Part A and B Expenditures:
"Identify the canceled claims in the Part A Header file. These claims
are identified by CLM_ADJSMT_TYPE_CD=1. Change the sign of the
@@ -179,7 +179,7 @@ def int_institutional_header_adr(
CCLF IP Reference
~~~~~~~~~~~~~~~~~~
CCLF IP Section 4.4 "Debit/Credit Method" (p.19):
:pincite:`VTWVB384 §4.4 p.19` — Debit/Credit Method:
"The Debit/Credit method gives a full account/history of claims
processed over time. The variable CLM_ADJSMT_TYPE_CD identifies:
@@ -187,12 +187,12 @@ def int_institutional_header_adr(
1 = Cancellation Claim
2 = Adjustment Claim (adjustment to an original claim)"
CCLF IP Section 5.1.2 "Natural Key for Part A" (p.20):
:pincite:`VTWVB384 §5.1.2 p.20` — Natural Key for Part A:
Part A natural key: CLM_BLG_PRVDR_OSCAR_NUM, CLM_FROM_DT,
CLM_THRU_DT, Most Recent MBI (MR_MBI).
CCLF IP Section 5.2.1 "Related Claims in Part A Header File" (p.21-22):
:pincite:`VTWVB384 §5.2.1 p.21-22` — Related Claims in Part A Header File:
"This group of claims consists of records that are all related to one
another — they all involve a claim for services provided to a
@@ -235,7 +235,7 @@ def stg_revenue_center(
CCLF IP Reference
~~~~~~~~~~~~~~~~~~
CCLF IP Section 2.2.2 "Part A Claim Revenue Center Detail File" (p.9):
:pincite:`VTWVB384 §2.2.2 p.9` — Part A Claim Revenue Center Detail File:
"The Part A Claims Revenue Center Detail File (CCLF2) contains
line-item level detail for each claim from the Part A Claims Header
@@ -243,7 +243,7 @@ def stg_revenue_center(
service was received, and for outpatient claims the payment amount
and allowed charge amount for individual services."
CCLF IP Section 3.5 "Part A Header vs Revenue Center Expenditures" (p.16):
:pincite:`VTWVB384 §3.5 p.16` — Part A Header vs Revenue Center Expenditures:
"Both the Part A Header file (CCLF1) and the Part A Revenue Center
file (CCLF2) contain a payment field (CLM_PMT_AMT and
@@ -282,7 +282,7 @@ def int_diagnosis_pivot(
CCLF IP Reference
~~~~~~~~~~~~~~~~~~
CCLF IP Section 2.2.4 "Part A Diagnosis Code File" (p.9):
:pincite:`VTWVB384 §2.2.4 p.9` — Part A Diagnosis Code File:
"The Part A Diagnosis Code File (CCLF4) contains the diagnosis code
for the principal diagnosis as well as all secondary diagnoses that
@@ -336,7 +336,7 @@ def int_procedure_pivot(
CCLF IP Reference
~~~~~~~~~~~~~~~~~~
CCLF IP Section 2.2.3 "Part A Procedure Code File" (p.9):
:pincite:`VTWVB384 §2.2.3 p.9` — Part A Procedure Code File:
"The Part A Procedure Code File (CCLF3) contains detailed information
regarding claims from the Part A Claims Header File, such as the type
@@ -402,7 +402,7 @@ def int_institutional_medical_claim(
CCLF IP Reference
~~~~~~~~~~~~~~~~~~
CCLF IP Section 2.2 "Part A Claims Data" (p.8):
:pincite:`VTWVB384 §2.2 p.8` — Part A Claims Data:
"Part A claim data files contain claims submitted by facilities such
as hospitals, SNFs, HHAs, rehabilitation facilities, and dialysis
@@ -560,7 +560,7 @@ def stg_physician_claim(
CCLF IP Reference
~~~~~~~~~~~~~~~~~~
CCLF IP Section 2.2.6 "Part B Physician File" (p.10):
:pincite:`VTWVB384 §2.2.6 p.10` — Part B Physician File:
"The Part B Physician File (CCLF5) consists of claim-line records but
includes both claim-level and line-level information. At the claim
@@ -570,7 +570,7 @@ def stg_physician_claim(
amount, allowed charge amount, line-level diagnosis code, units of
service, primary payer, provider TIN, and rendering NPI number."
CCLF IP Section 5.3.1 (p.23):
:pincite:`VTWVB384 §5.3.1 p.23` — Section 5.3.1:
"Identify all the canceled records (line items) in the Part B
Physician file. The canceled line items are identified by
@@ -603,16 +603,16 @@ def int_physician_claim_adr(
CCLF IP Reference
~~~~~~~~~~~~~~~~~~
CCLF IP Section 5.1.2 "Natural Key for Part B Physician/DME" (p.20):
:pincite:`VTWVB384 §5.1.2 p.20` — Natural Key for Part B Physician/DME:
Part B natural key: CLM_CNTL_NUM, Most Recent MBI (MR_MBI).
CCLF IP Section 5.2.2 "Related Claims in the Part B Physician File" (p.22):
:pincite:`VTWVB384 §5.2.2 p.22` — Related Claims in the Part B Physician File:
"In the Part B Physician File, you will find original claims,
cancellation claims, and adjustment claims."
CCLF IP Section 3.2 "Dropping Denied Claims" (p.15):
:pincite:`VTWVB384 §3.2 p.15` — Dropping Denied Claims:
"For Part B Physician/DME claims, some individual line-items can be
denied, whereas other line-items are not denied. Part B claims need
@@ -652,7 +652,7 @@ def int_physician_medical_claim(
CCLF IP Reference
~~~~~~~~~~~~~~~~~~
CCLF IP Section 2.2.6 "Part B Physician File" (p.10):
:pincite:`VTWVB384 §2.2.6 p.10` — Part B Physician File:
CCLF5 contains both claim-level and line-level data in a single
file. Unlike Part A (which has separate header/line/dx/px files),
@@ -763,7 +763,7 @@ def stg_dme_claim(
CCLF IP Reference
~~~~~~~~~~~~~~~~~~
CCLF IP Section 2.2.7 "Part B DME File" (p.10):
:pincite:`VTWVB384 §2.2.7 p.10` — Part B DME File:
"The Part B DME File (CCLF6) consists of claim-line records but
includes both claim-level and line-level information. Claim-level
@@ -773,7 +773,7 @@ def stg_dme_claim(
amount, ordering NPI number, and 'paid to' NPI number."
Cancellation adjustment applied to CLM_LINE_CVRD_PD_AMT and
CLM_LINE_ALOWD_CHRG_AMT per IP Section 5.3.1.
CLM_LINE_ALOWD_CHRG_AMT per :pincite:`VTWVB384 §5.3.1` —
"""
xref = cclf___stg_beneficiary_xref
return cclf__cclf6.join(
@@ -799,7 +799,7 @@ def int_dme_claim_adr(
CCLF IP Reference
~~~~~~~~~~~~~~~~~~
CCLF IP Section 5.2.3 "Related Claims in the Part B DME File" (p.23):
:pincite:`VTWVB384 §5.2.3 p.23` — Related Claims in the Part B DME File:
"In the Part B DME File, you will find original claims, cancellation
claims, and adjustment claims. A variety of related claims are found
@@ -840,7 +840,7 @@ def int_dme_medical_claim(
CCLF IP Reference
~~~~~~~~~~~~~~~~~~
CCLF IP Section 2.2.7 "Part B DME File" (p.10):
:pincite:`VTWVB384 §2.2.7 p.10` — Part B DME File:
Key field mappings from CCLF6 (IP Table 19):
- CUR_CLM_UNIQ_ID -> claim_id
@@ -940,7 +940,7 @@ def medical_claim(
CCLF IP Reference
~~~~~~~~~~~~~~~~~~
CCLF IP Section 2.2 "Part A Claims Data" + Section 2.2.6-2.2.7 (p.8-10):
:pincite:`VTWVB384 §2.2 p.8-10` — Part A Claims Data:
The three claim types are combined into a single medical_claim table:
@@ -954,7 +954,7 @@ def medical_claim(
- Professional/DME (CCLF6): Durable Medical Equipment, Prosthetics,
Orthotics, and Supplies. claim_type = 'professional'.
CCLF IP Section 5.4 "Part A vs Part B Claims" (p.27):
:pincite:`VTWVB384 §5.4 p.27` — Part A vs Part B Claims:
"The Part A claims files will include Medicare provider payments for
some services covered under both Part A and Part B. To distinguish,
@@ -984,7 +984,7 @@ def stg_pharmacy_claim(
CCLF IP Reference
~~~~~~~~~~~~~~~~~~
CCLF IP Section 2.3 "Part D Claims Data" (p.10):
:pincite:`VTWVB384 §2.3 p.10` — Part D Claims Data:
"The Part D File (CCLF7) contains prescription drug information at
the beneficiary level. Data elements include the NDC, quantity
@@ -1015,7 +1015,7 @@ def int_pharmacy_claim_adr(
CCLF IP Reference
~~~~~~~~~~~~~~~~~~
CCLF IP Section 3.3 "Part D Data Limitations" (p.15):
:pincite:`VTWVB384 §3.3 p.15` — Part D Data Limitations:
"The Part D claims contained in the CCLF are 'final action' claims,
unlike the other claims-related files which are debit/credit claims.
@@ -1025,7 +1025,7 @@ def int_pharmacy_claim_adr(
for any given set of Related Claims the most recent claim and
delete/ignore all of the previous related claims for that event."
CCLF IP Section 5.2.4 "Related Claims in the Part D File" (p.23):
:pincite:`VTWVB384 §5.2.4 p.23` — Related Claims in the Part D File:
"In the Part D File, you will find original claims, cancellation
claims, and adjustment claims."
@@ -1057,7 +1057,7 @@ def pharmacy_claim(
CCLF IP Reference
~~~~~~~~~~~~~~~~~~
CCLF IP Section 2.3 "Part D Claims Data" (p.10):
:pincite:`VTWVB384 §2.3 p.10` — Part D Claims Data:
Key field mappings from CCLF7 (IP Table 20):
- CUR_CLM_UNIQ_ID -> claim_id
@@ -1137,14 +1137,14 @@ def stg_beneficiary_demographics(
CCLF IP Reference
~~~~~~~~~~~~~~~~~~
CCLF IP Section 2.4.1 "Beneficiary Demographics File" (p.10-12):
:pincite:`VTWVB384 §2.4.1 p.10-12` — Beneficiary Demographics File:
"This file contains the beneficiary's current MBI, first/middle/last
name, ZIP code, date of birth, sex, race, age, Medicare Status Code,
dual eligibility status, hospice begin/end dates, and date of death
if a decedent."
CCLF IP Section 3.1 (p.14):
:pincite:`VTWVB384 §3.1 p.14` — Section 3.1:
MBI resolution via CCLF9 applied before deduplication to ensure a
single row per resolved beneficiary identity.
@@ -1181,7 +1181,7 @@ def eligibility(
CCLF IP Reference
~~~~~~~~~~~~~~~~~~
CCLF IP Section 2.4.1 "Beneficiary Demographics File" (p.10):
:pincite:`VTWVB384 §2.4.1 p.10` — Beneficiary Demographics File:
Key field mappings from CCLF8 (IP Table 21):
- BENE_MBI_ID (resolved) -> person_id, member_id

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
since these follow predictable patterns.
Implements three citation styles:
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)
- ``bluebook`` — Bluebook legal citation (regulations only)
Follows the CSL field mapping conventions documented in Tierney (2026),
"Automating Bluebook Citations in Legal Scholarship" (Zotero key QPPJ8LXZ).
Examples::
Bluebook examples::
Rule (APA):
Centers for Medicare & Medicaid Services. (2025).
CY 2026 PFS Final Rule. *Federal Register*, *90*, 98452.
Rule:
Medicare and Medicaid Programs; CY 2026 Payment Policies Under
the Physician Fee Schedule, 90 Fed. Reg. 101174 (Dec. 31, 2025).
Manual (APA):
Centers for Medicare & Medicaid Services. (n.d.).
*Claims Processing Manual* (Pub. 100-04, Ch. 12).
Regulation (Bluebook):
Regulation:
42 C.F.R. § 414.22 (2025).
Download (APA):
Centers for Medicare & Medicaid Services. (2026).
*RVU26A*. https://...
Manual:
Ctrs. for Medicare & Medicaid Servs., Claims Processing Manual,
CMS Pub. 100-04, Ch. 12 (2025).
Source (APA):
CMS Innovation Center. (2025).
*PY 2026 Financial Guarantees...*. https://...
Journal article:
Porter & Watts, Visual Rulemaking, 91 N.Y.U. L. Rev. 1183 (2016).
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
@@ -44,6 +50,87 @@ if TYPE_CHECKING:
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:
"""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)
# ── Per-type formatters ──────────────────────────────────────────
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."
# ── Rule (Federal Register) ──────────────────────────────────────────
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)
parts = [f"Centers for Medicare & Medicaid Services. ({year})."]
parts.append(f"{item.title}.")
@@ -93,7 +203,37 @@ def _format_rule(item: Rule, *, style: str = "apa") -> str:
return " ".join(parts)
# ── Manual (CMS IOM) ─────────────────────────────────────────────────
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)
institution = item.institution or "Centers for Medicare & Medicaid Services"
parts = [f"{institution}. ({year})."]
@@ -114,14 +254,29 @@ def _format_manual(item: Manual, *, style: str = "apa") -> str:
return " ".join(parts)
# ── Regulation (CFR) ─────────────────────────────────────────────────
def _format_regulation(item: Regulation, *, style: str = "apa") -> str:
if style == "bluebook":
return _format_regulation_bluebook(item)
return _format_regulation_apa(item)
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})."
# APA
def _format_regulation_apa(item: Regulation) -> str:
year = _year_or_nd(item.effective_date)
parts = []
if item.cfr_section:
@@ -135,6 +290,9 @@ def _format_regulation(item: Regulation, *, style: str = "apa") -> str:
return " ".join(parts)
# ── Download (CMS data files) ────────────────────────────────────────
def _format_download(item: Download, *, style: str = "apa") -> str:
year = _year_or_nd(item.date_published)
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)
# ── Source (generic / journal articles) ───────────────────────────────
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)
institution = item.institution or "Unknown"
parts = [f"{institution}. ({year})."]
@@ -159,6 +403,9 @@ def _format_source(item: Source, *, style: str = "apa") -> str:
return " ".join(parts)
# ── Generic fallback ─────────────────────────────────────────────────
def _format_generic(item: Item, *, style: str = "apa") -> str:
year = _year_or_nd(item.date_published)
institution = item.institution or "Unknown"

View File

@@ -48,7 +48,7 @@ if TYPE_CHECKING:
# Zotero key charset: 23456789ABCDEFGHIJKLMNPQRSTUVWXYZ (no 0, 1, O, L)
_PINCITE_RE = re.compile(
r":pincite:`"
r"([23456789A-HJ-NP-Z]{8})" # group 1: item key
r"([23456789A-HJ-NP-Z]{8})" # group 1: item key (strict Zotero charset)
r"(?:\s+(.+?))?" # group 2: locator (optional)
r"`"
r"(?:\s*(?:--|—)\s*(.+))?" # group 3: note (optional)

View File

@@ -5,8 +5,27 @@ from __future__ import annotations
from bib.format import format_bibliography, format_citation
from bib.item import Download, Item, Manual, Regulation, Rule, Source
# ── Rule ─────────────────────────────────────────────────────────────
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:
r = Rule(
title="CY 2026 PFS Final Rule",
@@ -44,7 +63,25 @@ class TestFormatRule:
assert "*90*" in cite
# ── Manual ───────────────────────────────────────────────────────────
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:
m = Manual(
title="Physician Fee Schedule",
@@ -68,15 +105,33 @@ class TestFormatManual:
assert "https://example.com/manual" in cite
def test_apa_uses_institution(self) -> None:
m = Manual(
title="Test",
institution="Custom Institution",
)
m = Manual(title="Test", institution="Custom Institution")
cite = format_citation(m)
assert "Custom Institution" in cite
# ── Regulation ───────────────────────────────────────────────────────
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:
r = Regulation(
cfr_title="42",
@@ -97,23 +152,38 @@ class TestFormatRegulation:
cite = format_citation(r)
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(
cfr_title="42",
cfr_section="414.22",
cfr_part="414",
title="RVU Methodology",
effective_date="2025-01-01",
)
cite = format_citation(r, style="bluebook")
assert "42 C.F.R. § 414.22 (2025)." == cite
cite = format_citation(r)
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(
cfr_title="42",
cfr_part="414",
effective_date="2025-01-01",
url="https://ecfr.gov/title-42/part-414",
)
cite = format_citation(r, style="bluebook")
assert "42 C.F.R. pt. 414 (2025)." == cite
cite = format_citation(r)
assert "https://ecfr.gov/title-42/part-414" in cite
# ── Download ─────────────────────────────────────────────────────────
class TestFormatDownload:
@@ -139,8 +209,11 @@ class TestFormatDownload:
assert "Custom Portal" in cite
# ── Source / Journal Articles ────────────────────────────────────────
class TestFormatSource:
def test_apa(self) -> None:
def test_apa_generic(self) -> None:
s = Source(
title="PY 2026 Financial Guarantees",
institution="CMS Innovation Center",
@@ -158,39 +231,76 @@ class TestFormatSource:
assert "Unknown" in cite
class TestFormatRegulationExtra:
def test_apa_custom_title(self) -> None:
"""Line 132: regulation with title != default."""
r = Regulation(
cfr_title="42",
cfr_part="414",
title="RVU Methodology", # != "42 CFR Part 414"
effective_date="2025-01-01",
)
cite = format_citation(r)
assert "*RVU Methodology*" in cite
class TestFormatJournalArticle:
def _make_article(self, **kwargs) -> Source:
defaults = {
"title": "Skin substitute outcomes in wound healing",
"institution": "Journal of Wound Care",
"date_published": "2023-06-15",
"doc_type": "journal-article",
"extra": (
"PMID: 12345678\n"
"DOI: 10.1234/jwc.2023.001\n"
"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:
"""Line 132 branch: title == default → no extra title."""
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_three_authors(self) -> None:
s = self._make_article()
cite = format_citation(s)
assert "Smith et al." in cite
assert "(2023)" in cite
assert "Skin substitute outcomes" in cite
assert "*Journal of Wound Care*" in cite
assert "https://doi.org/10.1234/jwc.2023.001" in cite
def test_apa_with_url(self) -> None:
"""Line 134: regulation with URL."""
r = Regulation(
cfr_title="42",
cfr_part="414",
effective_date="2025-01-01",
url="https://ecfr.gov/title-42/part-414",
def test_apa_two_authors(self) -> None:
s = self._make_article(extra="Authors: Smith J; Jones K\nDOI: 10.1/x\n")
cite = format_citation(s)
assert "Smith & Jones" in cite
def test_apa_one_author(self) -> None:
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)
assert "https://ecfr.gov/title-42/part-414" in cite
cite = format_citation(s)
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:
@@ -206,7 +316,6 @@ class TestFormatGeneric:
assert "*Custom Item*" in cite
def test_generic_with_url(self) -> None:
"""Line 169: _format_generic with URL."""
item = Item(
item_type="custom",
title="Custom",

View File

@@ -113,12 +113,15 @@ class TestExtractMetaTags:
assert extract_meta_tags(fn) == []
def test_real_cclf_function(self):
"""CCLF functions now use :pincite: directives instead of bare CCLF IP Section refs."""
from aco.express.cclf import stg_beneficiary_xref
from bib.pincite import parse_pincites
tags = extract_meta_tags(stg_beneficiary_xref)
labels = {t.label for t in tags}
assert "meta:cclf-ip-s3.1" in labels
assert "meta:cclf-ip-s5.1.1" in labels
doc = stg_beneficiary_xref.__doc__ or ""
pincites = parse_pincites(doc, "aco.express.cclf.stg_beneficiary_xref")
assert len(pincites) >= 2
keys = {p.item_key for p in pincites}
assert "VTWVB384" in keys
# ── extract_meta_details ──────────────────────────────────────────
@@ -269,6 +272,7 @@ class TestTagItemsFromPipeline:
store = Store(":memory:")
item = Source(
key="VTWVB384",
title="CCLF Information Packet",
tags=["source:cms-cclf-ip", "module:aco"],
)
@@ -282,12 +286,10 @@ class TestTagItemsFromPipeline:
tagged = {k: v for k, v in report.items() if v}
assert len(tagged) > 0
# Verify the item now has fn: and meta: tags
# Verify the item now has fn: tags (meta: tags replaced by :pincite: directives)
updated = store.get(key)
fn_tags = [t for t in updated.tags if t.startswith("fn:")]
meta_tags = [t for t in updated.tags if t.startswith("meta:")]
assert len(fn_tags) > 0
assert len(meta_tags) > 0
# ── generate_docstring_refs ───────────────────────────────────────