Files
stack/dev/scripts/add_carrier_to_zotero.py
kert 16f3b43974
Some checks failed
CI / skinny-install (aco) (push) Successful in 1m12s
CI / skinny-install (api) (push) Successful in 30s
CI / skinny-install (bcda) (push) Successful in 36s
CI / skinny-install (bib) (push) Successful in 35s
CI / skinny-install (bls) (push) Successful in 27s
CI / skinny-install (ccw) (push) Successful in 32s
CI / skinny-install (cli) (push) Successful in 41s
CI / skinny-install (cms) (push) Successful in 37s
CI / skinny-install (conf) (push) Successful in 38s
CI / skinny-install (opps) (push) Successful in 33s
CI / skinny-install (perf) (push) Successful in 38s
CI / skinny-install (pfs) (push) Successful in 38s
CI / skinny-install (rex) (push) Successful in 34s
Deploy / build-scan-report (push) Failing after 46s
Infra CI / notebooks (push) Failing after 25s
Infra CI / zotero (push) Successful in 12s
Infra CI / docs (push) Failing after 16s
CI / lint-test (push) Failing after 11m2s
Infra CI / mc (push) Successful in 21s
Infra CI / api (push) Successful in 29s
Package Supply Chain / pkg-supply-chain (push) Failing after 41s
feat: full session — mail servers, comment pipeline, PRISMA fetch, email ingest
Mail: Maddy on DO (corwins.media+Resend, fhirworx.io+Postmark),
touchless/stateless/idempotent. Gitea SMTP via env_file. CMS inbox
at cmsupdates@mail.fhirworx.io with IMAP→bib poller.

Bib: regulations.gov v4 client, Federal Register discovery, 164K
comment backfill (running), IMAP email ingest, Zotero sync routing.

PRISMA: altcha PoW solver, CrossRef DOI resolution, 83/129 PDFs.
Zotero: schema parity, ops module, CLI, fail-fast guard.
CI: docs.Dockerfile COPY glob fix (tracks #341).
Infra: Gitea+marimo fhirworx themes, IOM/OIG modules.
2026-04-16 09:04:38 -04:00

111 lines
3.5 KiB
Python

"""Add 2010 and 2017 PFS carrier files to Zotero library.
Downloads from CMS, extracts, and creates Zotero items with proper
tags and storage-linked attachments.
Usage:
uv run python dev/scripts/add_carrier_to_zotero.py
"""
import subprocess
import zipfile
from pathlib import Path
from conf import path as _conf_path
from zot.db import TYPE_MAP, Db, generate_key, now_iso
ZOTERO_DB = str(_conf_path("db.zotero"))
ZOTERO_STORAGE = str(_conf_path("storage.zotero"))
# Files already downloaded to /tmp
CARRIER_FILES = {
2010: {
"zip": "/tmp/pfs_carrier_dl/cy2010_carrier.zip",
"extracted": "/tmp/pfs_carrier_dl/2010",
"url": "https://www.cms.gov/medicare/medicare-fee-for-service-payment/physicianfeesched/downloads/cy2010qtr1carrierfiles3.zip",
"title": "CY 2010 PFS Carrier — Cy2010 Carrier Files",
},
2017: {
"zip": "/tmp/pfs_carrier_dl/cy2017_carrier.zip",
"extracted": "/tmp/pfs_carrier_dl/2017",
"url": "https://www.cms.gov/medicare/medicare-fee-for-service-payment/physicianfeesched/downloads/cy2017-carrierfiles.zip",
"title": "CY 2017 PFS Carrier — Cy2017 Carrier Files",
},
}
def add_carrier_year(db: Db, year: int, info: dict) -> None:
"""Add one carrier year to Zotero: parent item + file attachments."""
now = now_iso()
# Create parent webpage item
parent_id = db.create_item(TYPE_MAP["webpage"], now=now)
db.set_fields(
parent_id,
{
"title": info["title"],
"date": f"{year}-01-01",
"url": info["url"],
"accessDate": now,
"websiteType": "Government Data Portal",
"websiteTitle": "Centers for Medicare & Medicaid Services",
},
)
db.sync_tags(parent_id, ["module:pfs", f"year:{year}"])
print(f"Created parent item for {info['title']}")
# Create attachments for each .TXT and .pdf file
extracted_dir = Path(info["extracted"])
att_count = 0
for filepath in sorted(extracted_dir.iterdir()):
if filepath.suffix.upper() not in (".TXT", ".PDF"):
continue
att_key = generate_key()
storage_dir = Path(ZOTERO_STORAGE) / att_key
subprocess.run(["sudo", "mkdir", "-p", str(storage_dir)], check=True)
dest = storage_dir / filepath.name
subprocess.run(["sudo", "cp", str(filepath), str(dest)], check=True)
subprocess.run(
["sudo", "chown", "-R", "100999:100999", str(storage_dir)],
check=True,
)
ext = filepath.suffix.upper()
content_type = "text/plain" if ext == ".TXT" else "application/pdf"
att_id = db.add_attachment(
parent_id,
key=att_key,
content_type=content_type,
path=f"storage:{filepath.name}",
)
db.set_field(att_id, "title", filepath.name)
att_count += 1
print(f" Added {att_count} attachments for year {year}")
def main() -> None:
for year, info in CARRIER_FILES.items():
extracted = Path(info["extracted"])
if not extracted.exists():
print(f"Extracting {info['zip']}...")
with zipfile.ZipFile(info["zip"]) as zf:
zf.extractall(extracted)
txt_count = len(list(extracted.glob("*.TXT")))
print(f"Year {year}: {txt_count} .TXT files ready")
with Db(ZOTERO_DB) as db:
for year, info in sorted(CARRIER_FILES.items()):
add_carrier_year(db, year, info)
db.commit()
print("\nDone. Committed to Zotero database.")
if __name__ == "__main__":
main()