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 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 "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 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 can be used each month to cross reference records associated with the
current MBI to records associated with previous MBIs." 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 "The beneficiary XREF file (CCLF9) contains a complete history of
MBIs ever used to identify a beneficiary, for beneficiaries who have MBIs ever used to identify a beneficiary, for beneficiaries who have
@@ -129,7 +129,7 @@ def stg_institutional_header(
CCLF IP Reference 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 "The Part A Claims Header File (CCLF1) contains header-level claim
information for institutional services covered by Part A and some 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 MBI resolution: BENE_MBI_ID is joined to CCLF9._stg_beneficiary_xref
on PRVS_NUM to resolve to CURRENT_BENE_MBI_ID via COALESCE. 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 "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 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 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 "The Debit/Credit method gives a full account/history of claims
processed over time. The variable CLM_ADJSMT_TYPE_CD identifies: processed over time. The variable CLM_ADJSMT_TYPE_CD identifies:
@@ -187,12 +187,12 @@ def int_institutional_header_adr(
1 = Cancellation Claim 1 = Cancellation Claim
2 = Adjustment Claim (adjustment to an original 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, Part A natural key: CLM_BLG_PRVDR_OSCAR_NUM, CLM_FROM_DT,
CLM_THRU_DT, Most Recent MBI (MR_MBI). 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 "This group of claims consists of records that are all related to one
another — they all involve a claim for services provided to a another — they all involve a claim for services provided to a
@@ -235,7 +235,7 @@ def stg_revenue_center(
CCLF IP Reference 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 "The Part A Claims Revenue Center Detail File (CCLF2) contains
line-item level detail for each claim from the Part A Claims Header 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 service was received, and for outpatient claims the payment amount
and allowed charge amount for individual services." 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 "Both the Part A Header file (CCLF1) and the Part A Revenue Center
file (CCLF2) contain a payment field (CLM_PMT_AMT and file (CCLF2) contain a payment field (CLM_PMT_AMT and
@@ -282,7 +282,7 @@ def int_diagnosis_pivot(
CCLF IP Reference 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 "The Part A Diagnosis Code File (CCLF4) contains the diagnosis code
for the principal diagnosis as well as all secondary diagnoses that for the principal diagnosis as well as all secondary diagnoses that
@@ -336,7 +336,7 @@ def int_procedure_pivot(
CCLF IP Reference 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 "The Part A Procedure Code File (CCLF3) contains detailed information
regarding claims from the Part A Claims Header File, such as the type 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 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 "Part A claim data files contain claims submitted by facilities such
as hospitals, SNFs, HHAs, rehabilitation facilities, and dialysis as hospitals, SNFs, HHAs, rehabilitation facilities, and dialysis
@@ -560,7 +560,7 @@ def stg_physician_claim(
CCLF IP Reference 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 "The Part B Physician File (CCLF5) consists of claim-line records but
includes both claim-level and line-level information. At the claim 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 amount, allowed charge amount, line-level diagnosis code, units of
service, primary payer, provider TIN, and rendering NPI number." 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 "Identify all the canceled records (line items) in the Part B
Physician file. The canceled line items are identified by Physician file. The canceled line items are identified by
@@ -603,16 +603,16 @@ def int_physician_claim_adr(
CCLF IP Reference 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). 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, "In the Part B Physician File, you will find original claims,
cancellation claims, and adjustment 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 "For Part B Physician/DME claims, some individual line-items can be
denied, whereas other line-items are not denied. Part B claims need 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 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 CCLF5 contains both claim-level and line-level data in a single
file. Unlike Part A (which has separate header/line/dx/px files), 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 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 "The Part B DME File (CCLF6) consists of claim-line records but
includes both claim-level and line-level information. Claim-level 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." amount, ordering NPI number, and 'paid to' NPI number."
Cancellation adjustment applied to CLM_LINE_CVRD_PD_AMT and 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 xref = cclf___stg_beneficiary_xref
return cclf__cclf6.join( return cclf__cclf6.join(
@@ -799,7 +799,7 @@ def int_dme_claim_adr(
CCLF IP Reference 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 "In the Part B DME File, you will find original claims, cancellation
claims, and adjustment claims. A variety of related claims are found 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 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): Key field mappings from CCLF6 (IP Table 19):
- CUR_CLM_UNIQ_ID -> claim_id - CUR_CLM_UNIQ_ID -> claim_id
@@ -940,7 +940,7 @@ def medical_claim(
CCLF IP Reference 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: 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, - Professional/DME (CCLF6): Durable Medical Equipment, Prosthetics,
Orthotics, and Supplies. claim_type = 'professional'. 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 "The Part A claims files will include Medicare provider payments for
some services covered under both Part A and Part B. To distinguish, 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 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 Part D File (CCLF7) contains prescription drug information at
the beneficiary level. Data elements include the NDC, quantity the beneficiary level. Data elements include the NDC, quantity
@@ -1015,7 +1015,7 @@ def int_pharmacy_claim_adr(
CCLF IP Reference 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, "The Part D claims contained in the CCLF are 'final action' claims,
unlike the other claims-related files which are debit/credit 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 for any given set of Related Claims the most recent claim and
delete/ignore all of the previous related claims for that event." 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 "In the Part D File, you will find original claims, cancellation
claims, and adjustment claims." claims, and adjustment claims."
@@ -1057,7 +1057,7 @@ def pharmacy_claim(
CCLF IP Reference 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): Key field mappings from CCLF7 (IP Table 20):
- CUR_CLM_UNIQ_ID -> claim_id - CUR_CLM_UNIQ_ID -> claim_id
@@ -1137,14 +1137,14 @@ def stg_beneficiary_demographics(
CCLF IP Reference 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 "This file contains the beneficiary's current MBI, first/middle/last
name, ZIP code, date of birth, sex, race, age, Medicare Status Code, name, ZIP code, date of birth, sex, race, age, Medicare Status Code,
dual eligibility status, hospice begin/end dates, and date of death dual eligibility status, hospice begin/end dates, and date of death
if a decedent." 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 MBI resolution via CCLF9 applied before deduplication to ensure a
single row per resolved beneficiary identity. single row per resolved beneficiary identity.
@@ -1181,7 +1181,7 @@ def eligibility(
CCLF IP Reference 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): Key field mappings from CCLF8 (IP Table 21):
- BENE_MBI_ID (resolved) -> person_id, member_id - 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 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

@@ -48,7 +48,7 @@ if TYPE_CHECKING:
# Zotero key charset: 23456789ABCDEFGHIJKLMNPQRSTUVWXYZ (no 0, 1, O, L) # Zotero key charset: 23456789ABCDEFGHIJKLMNPQRSTUVWXYZ (no 0, 1, O, L)
_PINCITE_RE = re.compile( _PINCITE_RE = re.compile(
r":pincite:`" 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"(?:\s+(.+?))?" # group 2: locator (optional)
r"`" r"`"
r"(?:\s*(?:--|—)\s*(.+))?" # group 3: note (optional) 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.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",

View File

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