HOSPITAL FACILITIES & PATIENT EXPERIENCE / PYTHON + CSV
Enrich a hospital portfolio in Python
Add facility attributes and a selected patient-experience measure to CMS-ID research records, keeping ratings, observation periods and footnotes clear.
Enrich a hospital research portfolio
Suppose you are reviewing hospitals for a regional service plan. Your portfolio has CMS facility identifiers, and you want hospital names, facility types, overall ratings and one consistent patient-experience measure. The example uses Duke University Hospital, UNC Hospitals and WakeMed’s Raleigh campus.
The optional input.csv contains three fictional project labels paired with real facility IDs. The same records appear in the portfolio list in the Python code below. The labels were invented to represent your internal projects; the hospital attributes and survey results come unchanged from the prepared product.
Download the product samples
These are smaller versions of the two main CSVs in the paid download, with the same columns. facilities.csv contains the three hospitals. patient_experience.csv keeps all 68 survey measures for each of them, so you can inspect other questions as well as the one used here. Save both files in your working folder.
The full $29 edition covers 5,419 facilities and 325,720 survey observations. Its product page lists coverage, fields and supporting files.
Code example
The code first selects H_HSP_RATING_9_10: the percentage of surveyed patients giving the hospital a 9 or 10 on a 0–10 scale. It uses the October 1, 2024–September 30, 2025 observation period, then joins one observation to each portfolio record. Facility IDs stay as text; this also supports CMS IDs that contain letters.
Paths are relative to your working folder; use from pathlib import Path; print(Path.cwd()) to check it, or supply full paths.
import csv
# Fictional project labels paired with real CMS facility identifiers.
portfolio = [
{"project": "Durham review", "facility_id": "340030"},
{"project": "Chapel Hill review", "facility_id": "340061"},
{"project": "Raleigh review", "facility_id": "340069"},
]
measure_id = "H_HSP_RATING_9_10"
period = ("2024-10-01", "2025-09-30")
with open("facilities.csv", newline="", encoding="utf-8-sig") as file:
facilities = {}
for row in csv.DictReader(file):
if row["facility_id"] in facilities:
raise ValueError("Duplicate facility identifier")
facilities[row["facility_id"]] = row
# Select one measure and observation period before joining.
experience = {}
with open("patient_experience.csv", newline="", encoding="utf-8-sig") as file:
for row in csv.DictReader(file):
if row["measure_id"] != measure_id:
continue
if (row["period_start"], row["period_end"]) != period:
continue
key = row["facility_id"]
if key in experience:
raise ValueError("More than one observation for this facility/measure/period")
experience[key] = row
results = []
for item in portfolio:
hospital = facilities.get(item["facility_id"])
survey = experience.get(item["facility_id"])
results.append({
**item,
"facility_name": hospital["facility_name"] if hospital else "",
"hospital_type": hospital["hospital_type"] if hospital else "",
"overall_stars": hospital["overall_rating"] if hospital else "",
"overall_status": hospital["overall_rating_status"] if hospital else "facility_not_found",
"overall_footnotes": hospital["overall_rating_footnote_text"] if hospital else "",
"rated_9_or_10_percent": survey["answer_percent"] if survey else "",
"survey_status": survey["answer_percent_status"] if survey else "observation_not_found",
"survey_footnotes": survey["answer_percent_footnote_text"] if survey else "",
"completed_surveys": survey["completed_surveys"] if survey else "",
"completed_surveys_status": survey["completed_surveys_status"] if survey else "observation_not_found",
"completed_surveys_footnotes": survey["completed_surveys_footnote_text"] if survey else "",
"period_start": period[0], "period_end": period[1],
})
for row in results:
percent = row["rated_9_or_10_percent"]
percentage_label = percent + "%" if percent != "" else row["survey_status"]
print(f'{row["project"]} | {row["facility_name"]} | '
f'overall: {row["overall_stars"] or row["overall_status"]} | '
f'rated 9 or 10: {percentage_label} | '
f'surveys: {row["completed_surveys"] or row["completed_surveys_status"]}')
for field in ("overall_footnotes", "survey_footnotes", "completed_surveys_footnotes"):
if row[field]:
print(f' {field}: {row[field]}')
Analysis of the output
Durham review | DUKE UNIVERSITY HOSPITAL | overall: 5 | rated 9 or 10: 78% | surveys: 1341
Chapel Hill review | UNC HOSPITALS | overall: 5 | rated 9 or 10: 77% | surveys: 2558
Raleigh review | WAKEMED, RALEIGH CAMPUS | overall: 4 | rated 9 or 10: 73% | surveys: 685
The join adds the hospital name and type, the CMS overall star rating, and the patient-rating percentage. In this source edition, the selected patient-rating percentages are 78% for Duke, 77% for UNC and 73% for WakeMed. Those results let you place the same patient-experience measure beside each project without manually finding hospitals in separate files.
The overall stars and the survey percentage answer different questions. The former summarizes several quality domains with different measurement periods; the latter describes responses to one survey question during the period selected in the code. Their scales are different, and a small percentage difference alone is not evidence of a statistically meaningful difference in care.
The result also retains completed-survey counts and footnotes. Counts repeat across survey measures, so adding the 68 rows for a hospital would count the same survey volume repeatedly. A published score can still carry a small-sample or other caution; the code prints those explanations when present.
Unavailable and inapplicable cells have blank analytical values, distinct statuses and original CMS cells. An absent facility or selected observation remains an unmatched portfolio record. None of these cases becomes a zero score. All source observations remain available in the long survey table; filtering to one measure and period is what keeps this portfolio join at one output row per input.
Using the full dataset
After purchase, extract the ZIP and replace the two sample files with the complete facilities.csv and patient_experience.csv. The filenames and columns are identical. Replace portfolio with your own records, or load your CSV:
with open("my_hospital_portfolio.csv", newline="", encoding="utf-8-sig") as file:
portfolio = list(csv.DictReader(file))
Your input needs project and facility_id, or you can adapt those names in the example. Use measures.csv to choose another survey question. Select the appropriate result column—answer percentage, linear mean or patient-survey stars—and its matching status and footnotes. Use the corresponding dates from your purchased edition.
The full download also includes the footnote lookup, field dictionaries, source-row accounting and a checked copy of this example. Facility ZIPs and county labels are not a county-FIPS mapping, and CMS facility IDs are not NPIs. If you separately establish a compatible county identifier, the ACS county profile can add resident demographic context to a regional study; that linkage is not supplied here.
Sources and versions
Sources: CMS Hospital General Information and Patient survey (HCAHPS) - Hospital. CMS labels both released August 13, 2026 and modified July 22. The survey observations cover October 2024–September 2025; the overall rating does not share one common survey period.
ACD captured the inputs October 17, 2026 and prepared this package 2026-10-17. This is the first ACD release of these inputs, with no claimed earlier-release comparison. See the source and preparation record and CMS dictionary. CMS makes the original public reporting data freely reusable; ACD sells preparation, validation and documentation.
Example checked October 17, 2026 against both these samples and the complete purchased files. Source and processing records, sample checksums · Expected result CSV. Independent preparation; no agency endorsement.