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
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.
111 lines
3.5 KiB
Python
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()
|