viewof overviewYear = Inputs.select(surveyYears, {
label: "Survey year",
value: d3.max(surveyYears),
format: (year) => String(year)
})Historical Treatment Facilities
Explore historical SAMHSA treatment facility surveys and directory listings.
d3 = await import("https://cdn.jsdelivr.net/npm/d3@7/+esm")
Plot = await import("https://cdn.jsdelivr.net/npm/@observablehq/plot@0.6/+esm")
topojson = await import("https://cdn.jsdelivr.net/npm/topojson-client@3/+esm")
metadata = await FileAttachment("data/dashboard/dashboard_metadata.json").json()
trends = await FileAttachment("data/dashboard/survey_trends.csv").csv({typed: true})
directoryStatus = await FileAttachment("data/dashboard/directory_year_status.csv").csv({typed: true})
population = await FileAttachment("data/dashboard/state_population.csv").csv({typed: true})
downloads = await FileAttachment("data/dashboard/download_catalog.csv").csv({typed: true})
cbpComparison = await FileAttachment("data/dashboard/cbp_comparison_trends.csv").csv({typed: true})
statesTopo = await d3.json("https://cdn.jsdelivr.net/npm/us-atlas@3/states-10m.json")
countiesTopo = await d3.json("https://cdn.jsdelivr.net/npm/us-atlas@3/counties-10m.json")
stateFeatures = topojson.feature(statesTopo, statesTopo.objects.states).features
countyFeatures = topojson.feature(countiesTopo, countiesTopo.objects.counties).features
stateAbbrByFips = new Map(Object.entries(metadata.state_fips).map(([state, fips]) => [String(fips).padStart(2, "0"), state]))
stateNameByFips = new Map(stateFeatures.map((feature) => [String(feature.id).padStart(2, "0"), feature.properties.name]))
stateOptions = Object.keys(metadata.state_fips).sort()
populationByKey = new Map(population.map((row) => [`${row.survey_year}|${row.state}`, +row.population]))
statusByYear = new Map(directoryStatus.map((row) => [+row.survey_year, row]))
surveyYears = metadata.survey_years.map(Number).sort((a, b) => a - b)
directoryYears = metadata.directory_survey_years.map(Number).sort((a, b) => a - b)
fmtInt = (value) => Number.isFinite(+value)
? new Intl.NumberFormat("en-US", {maximumFractionDigits: 0}).format(+value)
: "Not available"
fmtRate = (value) => Number.isFinite(+value)
? new Intl.NumberFormat("en-US", {maximumFractionDigits: 1}).format(+value)
: "Not available"
fmtPct = (value) => Number.isFinite(+value)
? new Intl.NumberFormat("en-US", {style: "percent", maximumFractionDigits: 1}).format(+value)
: "Not available"
normalizeMulti = (value) => value == null ? [] : Array.isArray(value) ? value : [value]Treatment facility data Quality assurance is ongoing. Historical data, not a current treatment locator.
Source: N-SSATS/N-SUMHSS public-use facility surveys. The 2021 redesign is shown as a break.
nationalValue = (metric, category, year = overviewYear) => trends.find((row) =>
row.geography_type === "national" &&
row.geography === "US" &&
row.survey_year === year &&
row.metric === metric &&
row.category === category
)
overviewCards = [
["Total facilities", nationalValue("Total facilities", "All facilities")],
["OTP", nationalValue("OTP status", "OTP")],
["Non-OTP", nationalValue("OTP status", "Non-OTP")],
["For-profit", nationalValue("Ownership", "For-profit")],
["Nonprofit", nationalValue("Ownership", "Nonprofit")],
["Government", nationalValue("Ownership", "Government")]
]html`<div class="stat-grid">${overviewCards.map(([label, row]) => html`
<div class="stat-card">
<span>${label}</span>
<strong>${row?.available ? fmtInt(row.facility_count) : "Not available"}</strong>
</div>
`)}</div>`viewof overviewMetric = Inputs.select(metadata.trend_metrics, {
label: "Chart",
value: "Total facilities"
})overviewRowsRaw = trends.filter((row) =>
row.geography_type === "national" &&
row.geography === "US" &&
row.metric === overviewMetric &&
row.available
)
segmentSeries = (rows, key = (row) => row.category) => {
const output = []
for (const [series, values] of d3.groups(rows, key)) {
let segment = 0
let previous = null
for (const row of values.slice().sort((a, b) => a.survey_year - b.survey_year)) {
if (previous !== null && (row.survey_year - previous > 1 || (previous <= 2020 && row.survey_year >= 2021))) segment += 1
output.push({...row, seriesSegment: `${series}|${segment}`})
previous = row.survey_year
}
}
return output
}
overviewRows = segmentSeries(overviewRowsRaw)
overviewChart = Plot.plot({
width: 980,
height: 420,
marginLeft: 78,
x: {label: "Survey year", tickFormat: d3.format("d")},
y: {label: "Facilities", grid: true},
color: {legend: overviewMetric !== "Total facilities"},
marks: [
Plot.ruleY([0], {stroke: "#d8d1d4"}),
Plot.ruleX([2020.5], {stroke: "#5b1f2e", strokeDasharray: "5,4"}),
Plot.line(overviewRows, {x: "survey_year", y: "facility_count", stroke: "category", z: "seriesSegment", strokeWidth: 2.5}),
Plot.dot(overviewRows, {
x: "survey_year", y: "facility_count", stroke: "category", r: 3,
title: (row) => `${row.category}\n${row.survey_year}\n${fmtInt(row.facility_count)} facilities`
})
]
})Source: Public-use facility surveys. Choose one characteristic and compare up to three geographies.
viewof trendMetric = Inputs.select(metadata.trend_metrics, {
label: "Characteristic",
value: "Ownership"
})geographyOptions = [
"national|US",
...metadata.regions.map((region) => `region|${region}`),
...stateOptions.map((state) => `state|${state}`)
]
geographyLabel = (value) => {
const [type, geography] = value.split("|")
if (type === "national") return "United States"
if (type === "region") return `${geography} region`
return stateNameByFips.get(metadata.state_fips[geography]) ?? geography
}
comparisonOptions = ["none|", ...geographyOptions]
comparisonLabel = (value) => value === "none|" ? "None" : geographyLabel(value)viewof trendGeographyPrimary = Inputs.select(geographyOptions, {
label: "Primary geography",
value: "national|US",
format: geographyLabel
})trendGeographySelection = [...new Set([
trendGeographyPrimary, trendGeographyCompare1, trendGeographyCompare2
].filter((value) => value !== "none|"))]
trendPerCapita = trendScale === "Per 100,000"
trendChartRowsRaw = trendGeographySelection.flatMap((selection) => {
const [type, geography] = selection.split("|")
return trends.filter((row) =>
row.geography_type === type && row.geography === geography &&
row.metric === trendMetric && row.category === trendCategory && row.available
).map((row) => ({
...row,
geographyLabel: geographyLabel(selection),
value: trendPerCapita ? row.facilities_per_100000 : row.facility_count
}))
})
trendChartRows = segmentSeries(trendChartRowsRaw, (row) => row.geographyLabel)
trendYLabel = trendPerCapita ? "Facilities per 100,000 residents" : "Facility count"
trendChart = Plot.plot({
width: 980,
height: 450,
marginLeft: 82,
x: {label: "Survey year", tickFormat: d3.format("d")},
y: {label: trendYLabel, grid: true},
color: {legend: true},
marks: [
Plot.ruleY([0], {stroke: "#d8d1d4"}),
Plot.ruleX([2020.5], {stroke: "#5b1f2e", strokeDasharray: "5,4"}),
Plot.line(trendChartRows, {x: "survey_year", y: "value", stroke: "geographyLabel", z: "seriesSegment", strokeWidth: 2.5}),
Plot.dot(trendChartRows, {
x: "survey_year", y: "value", stroke: "geographyLabel", r: 3,
title: (row) => `${row.geographyLabel}\n${row.survey_year}\n${trendYLabel}: ${trendPerCapita ? fmtRate(row.value) : fmtInt(row.value)}`
})
]
})safeFile = (value) => String(value).toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "")
trendCsv = {
const csv = d3.csvFormat(trendChartRowsRaw.map((row) => ({
survey_year: row.survey_year,
geography: row.geographyLabel,
metric: row.metric,
category: row.category,
facility_count: row.facility_count,
population: row.population ?? "",
facilities_per_100000: row.facilities_per_100000 ?? "",
source: row.source
})))
const href = URL.createObjectURL(new Blob([csv], {type: "text/csv;charset=utf-8"}))
return html`<a class="btn btn-outline-primary" href="${href}" download="treatment-facility-trend.csv">Download plotted CSV</a>`
}
trendPng = {
const button = html`<button class="btn btn-primary" type="button">Download PNG</button>`
button.onclick = async () => {
const canvas = document.createElement("canvas")
canvas.width = 1600
canvas.height = 900
const context = canvas.getContext("2d")
context.fillStyle = "#ffffff"
context.fillRect(0, 0, 1600, 900)
context.fillStyle = "#35101b"
context.font = "bold 36px Arial, sans-serif"
context.fillText(`${trendMetric}: ${trendCategory}`, 70, 62)
context.fillStyle = "#4f4650"
context.font = "20px Arial, sans-serif"
context.fillText(`${trendYLabel} | N-SSATS/N-SUMHSS public-use facility surveys`, 70, 100)
const clone = trendChart.cloneNode(true)
clone.setAttribute("xmlns", "http://www.w3.org/2000/svg")
const blob = new Blob([new XMLSerializer().serializeToString(clone)], {type: "image/svg+xml;charset=utf-8"})
const url = URL.createObjectURL(blob)
const image = new Image()
await new Promise((resolve, reject) => { image.onload = resolve; image.onerror = reject; image.src = url })
context.drawImage(image, 70, 155, 1460, 620)
URL.revokeObjectURL(url)
context.fillStyle = "#5b1f2e"
context.font = "bold 16px Arial, sans-serif"
context.fillText("QUALITY ASSURANCE IS ONGOING. HISTORICAL DATA ONLY.", 70, 825)
context.fillStyle = "#6b5f67"
context.font = "14px Arial, sans-serif"
context.fillText(`Generated ${new Date().toISOString().slice(0, 10)} | PUF trends are not silently combined with directory listings.`, 70, 858)
const png = await new Promise((resolve) => canvas.toBlob(resolve, "image/png"))
const href = URL.createObjectURL(png)
const link = document.createElement("a")
link.href = href
link.download = `treatment-facility-${safeFile(trendMetric)}-${safeFile(trendCategory)}.png`
link.click()
setTimeout(() => URL.revokeObjectURL(href), 1000)
}
return button
}Source: Published SAMHSA directory listings. Filters describe listed facilities, not the PUF trend universe.
viewof directoryYear = Inputs.select(directoryYears, {
label: "Survey year",
value: d3.max(directoryYears),
format: (year) => `${year} survey (${statusByYear.get(year)?.directory_year ?? ""} directory)`
})toBoolean = (value) => String(value).toLowerCase() === "true"
toNullableBoolean = (value) => value === "" || value == null ? null : toBoolean(value)
directoryRows = await d3.csv(`data/dashboard/directory_facilities_${directoryYear}.csv`, (row) => ({
...row,
survey_year: +row.survey_year,
directory_year: +row.directory_year,
state_fips: String(row.state_fips || "").padStart(2, "0"),
county_fips: row.county_fips ? String(row.county_fips).padStart(5, "0") : "",
accepts_medicaid: toNullableBoolean(row.accepts_medicaid),
accepts_medicare: toNullableBoolean(row.accepts_medicare),
accepts_private_insurance: toNullableBoolean(row.accepts_private_insurance),
center_type_substance_use: toNullableBoolean(row.center_type_substance_use),
center_type_detoxification: toNullableBoolean(row.center_type_detoxification),
center_type_transitional_housing: toNullableBoolean(row.center_type_transitional_housing),
center_type_mental_health: toNullableBoolean(row.center_type_mental_health),
center_type_cooccurring: toNullableBoolean(row.center_type_cooccurring),
setting_outpatient: toNullableBoolean(row.setting_outpatient),
setting_residential: toNullableBoolean(row.setting_residential),
setting_hospital: toNullableBoolean(row.setting_hospital),
service_otp: toNullableBoolean(row.service_otp),
service_methadone: toNullableBoolean(row.service_methadone),
service_buprenorphine: toNullableBoolean(row.service_buprenorphine),
service_naltrexone: toNullableBoolean(row.service_naltrexone),
service_telehealth: toNullableBoolean(row.service_telehealth),
otp_available: toBoolean(row.otp_available),
has_parser_warning: toBoolean(row.has_parser_warning)
}))
currentDirectoryStatus = statusByYear.get(directoryYear)
availableOwnership = currentDirectoryStatus?.ownership_coverage >= 0.95
? [...new Set(directoryRows.map((row) => row.ownership_group).filter(Boolean))].sort()
: []Filter directory listings
viewof ownershipFilters = Inputs.checkbox(availableOwnership, {
label: availableOwnership.length ? "Ownership" : "Ownership not available for this year"
})centerChoices = [
["Substance use", "center_type_substance_use"],
["Detoxification", "center_type_detoxification"],
["Transitional housing", "center_type_transitional_housing"],
["Mental health", "center_type_mental_health"],
["Co-occurring", "center_type_cooccurring"]
]
availableCenterChoices = centerChoices.filter((choice) =>
directoryRows.some((row) => row[choice[1]] !== null)
)
viewof centerFilters = Inputs.checkbox(availableCenterChoices.map((row) => row[1]), {
label: availableCenterChoices.length ? "Center type" : "Center type not available for this year",
format: (value) => centerChoices.find((row) => row[1] === value)?.[0] ?? value
})settingChoices = [
["Outpatient", "setting_outpatient"],
["Residential", "setting_residential"],
["Hospital inpatient", "setting_hospital"]
]
availableSettingChoices = settingChoices.filter((choice) =>
directoryRows.some((row) => row[choice[1]] !== null)
)
viewof settingFilters = Inputs.checkbox(availableSettingChoices.map((row) => row[1]), {
label: availableSettingChoices.length ? "Care setting" : "Care setting not available for this year",
format: (value) => settingChoices.find((row) => row[1] === value)?.[0] ?? value
})paymentChoices = [
["Medicaid", "accepts_medicaid"],
["Medicare", "accepts_medicare"],
["Private insurance", "accepts_private_insurance"]
]
availablePaymentChoices = paymentChoices.filter((choice) =>
directoryRows.some((row) => row[choice[1]] !== null)
)
viewof paymentFilters = Inputs.checkbox(availablePaymentChoices.map((row) => row[1]), {
label: availablePaymentChoices.length ? "Payment" : "Payment not available for this year",
format: (value) => paymentChoices.find((row) => row[1] === value)?.[0] ?? value
})serviceChoices = [
["OTP", "service_otp"],
["Methadone", "service_methadone"],
["Buprenorphine", "service_buprenorphine"],
["Naltrexone", "service_naltrexone"],
["Telehealth", "service_telehealth"]
]
availableServiceChoices = serviceChoices.filter((choice) =>
directoryRows.some((row) => row[choice[1]] !== null)
)
viewof serviceFilters = Inputs.checkbox(availableServiceChoices.map((row) => row[1]), {
label: availableServiceChoices.length ? "Services" : "Services not available for this year",
format: (value) => serviceChoices.find((row) => row[1] === value)?.[0] ?? value
})matchesAnyField = (row, selected) => selected.length === 0 || selected.some((field) => row[field] === true)
matchesServices = (row, selected) => selected.length === 0 || (
serviceLogic === "Offers all selected"
? selected.every((field) => row[field] === true)
: selected.some((field) => row[field] === true)
)
rowPasses = (row) => (
(normalizeMulti(ownershipFilters).length === 0 || normalizeMulti(ownershipFilters).includes(row.ownership_group)) &&
matchesAnyField(row, normalizeMulti(centerFilters)) &&
matchesAnyField(row, normalizeMulti(settingFilters)) &&
matchesAnyField(row, normalizeMulti(paymentFilters)) &&
matchesServices(row, normalizeMulti(serviceFilters))
)
filteredDirectoryRows = directoryRows.filter(rowPasses)
activeFilters = [
...normalizeMulti(ownershipFilters),
...normalizeMulti(centerFilters).map((value) => centerChoices.find((row) => row[1] === value)?.[0]),
...normalizeMulti(settingFilters).map((value) => settingChoices.find((row) => row[1] === value)?.[0]),
...normalizeMulti(paymentFilters).map((value) => paymentChoices.find((row) => row[1] === value)?.[0]),
...normalizeMulti(serviceFilters).map((value) => serviceChoices.find((row) => row[1] === value)?.[0])
].filter(Boolean)
stateCountsFiltered = d3.rollup(filteredDirectoryRows, (rows) => rows.length, (row) => row.state)
stateMapData = stateFeatures
.filter((feature) => stateAbbrByFips.has(String(feature.id).padStart(2, "0")))
.map((feature) => {
const fips = String(feature.id).padStart(2, "0")
const state = stateAbbrByFips.get(fips)
const count = stateCountsFiltered.get(state) ?? 0
const denominator = populationByKey.get(`${directoryYear}|${state}`)
const rate = denominator > 0 ? count / denominator * 100000 : NaN
return {...feature, state, count, denominator, rate, value: stateScale === "Per 100,000" ? rate : count}
})
stateMap = Plot.plot({
width: 980,
height: 560,
projection: "albers-usa",
color: {scheme: "purples", legend: true, label: stateScale},
marks: [
Plot.geo(stateMapData, {
fill: "value", stroke: "white",
title: (row) => `${row.properties.name}\n${fmtInt(row.count)} facilities\n${fmtRate(row.rate)} per 100,000`
})
]
})
selectedStateRows = filteredDirectoryRows.filter((row) => row.state === selectedState)
selectedStateOtpAvailable = toBoolean(currentDirectoryStatus?.otp_available)
selectedStateCards = [
["Facilities", selectedStateRows.length],
["OTP", selectedStateOtpAvailable ? selectedStateRows.filter((row) => row.service_otp === true).length : null],
["For-profit", availableOwnership.length ? selectedStateRows.filter((row) => row.ownership_group === "For-profit").length : null],
["Nonprofit", availableOwnership.length ? selectedStateRows.filter((row) => row.ownership_group === "Nonprofit").length : null],
["Government", availableOwnership.length ? selectedStateRows.filter((row) => row.ownership_group === "Government").length : null]
]html`<div class="filter-summary">${activeFilters.length ? activeFilters.map((value) => html`<span>${value}</span>`) : html`<span>No filters</span>`}</div>`html`<h3>${stateNameByFips.get(metadata.state_fips[selectedState]) ?? selectedState}</h3>
<div class="stat-grid compact">${selectedStateCards.map(([label, value]) => html`
<div class="stat-card"><span>${label}</span><strong>${value === null ? "Not available" : fmtInt(value)}</strong></div>
`)}</div>`Source: Published SAMHSA directory listings. Preliminary geography.
viewof countyState = Inputs.select(stateOptions, {
label: "State",
value: "OR",
format: (state) => stateNameByFips.get(metadata.state_fips[state]) ?? state
})html`<p class="county-context">
<strong>${directoryYear} survey year</strong>
<span>${activeFilters.length ? activeFilters.join(", ") : "No facility filters"}</span>
<span>Change year and facility filters in State Explorer.</span>
</p>`countyStateFips = String(metadata.state_fips[countyState]).padStart(2, "0")
countyStateRows = filteredDirectoryRows.filter((row) => row.state === countyState)
countyAssignedRows = countyStateRows.filter((row) => row.county_fips)
countyCounts = d3.rollup(countyAssignedRows, (rows) => rows.length, (row) => row.county_fips)
countyMapData = countyFeatures
.filter((feature) => String(feature.id).padStart(5, "0").startsWith(countyStateFips))
.map((feature) => {
const fips = String(feature.id).padStart(5, "0")
return {...feature, fips, count: countyCounts.get(fips) ?? 0}
})
countyHigh = countyAssignedRows.filter((row) => row.geocode_confidence === "high").length
countyFallback = countyAssignedRows.filter((row) => row.geocode_method === "zip_crosswalk").length
countyUnassigned = countyStateRows.length - countyAssignedRows.length
countyMap = Plot.plot({
width: 980,
height: 590,
projection: {type: "mercator", domain: {type: "FeatureCollection", features: countyMapData}},
color: {scheme: "purples", legend: true, label: "Facility count"},
marks: [
Plot.geo(countyMapData, {
fill: "count", stroke: "white",
title: (row) => `County FIPS ${row.fips}\n${fmtInt(row.count)} facilities`
})
]
})html`<div class="stat-grid compact">
<div class="stat-card"><span>Filtered state total</span><strong>${fmtInt(countyStateRows.length)}</strong></div>
<div class="stat-card"><span>County assigned</span><strong>${fmtInt(countyAssignedRows.length)}</strong></div>
<div class="stat-card"><span>Unassigned</span><strong>${fmtInt(countyUnassigned)}</strong></div>
<div class="stat-card"><span>High confidence</span><strong>${fmtInt(countyHigh)}</strong></div>
<div class="stat-card"><span>ZIP fallback</span><strong>${fmtInt(countyFallback)}</strong></div>
</div>`Sources: County Business Patterns establishments, published SAMHSA directory listings, and N-SSATS/N-SUMHSS public-use survey records. Counts are shown separately.
viewof comparisonGeography = Inputs.select(["US", ...stateOptions], {
label: "Geography",
value: "US",
format: (value) => value === "US" ? "United States" : value
})comparisonGeoType = comparisonGeography === "US" ? "national" : "state"
comparisonRows = cbpComparison.filter((row) =>
row.geography_type === comparisonGeoType &&
row.geography === comparisonGeography
)
comparisonSurveyRows = trends.filter((row) =>
row.metric === "Total facilities" &&
row.category === "All facilities" &&
row.geography_type === comparisonGeoType &&
row.geography === comparisonGeography &&
row.available
)
comparisonSeries = [
...comparisonSurveyRows.map((row) => ({
year: +row.survey_year,
value: +row.facility_count,
source: "N-SSATS/N-SUMHSS survey"
})),
...comparisonRows.map((row) => ({
year: +row.year,
value: +row.directory_count,
source: "SAMHSA directory"
})),
...comparisonRows.filter((row) => row.cbp_published_count != null && row.cbp_published_count !== "" && Number.isFinite(+row.cbp_published_count)).map((row) => ({
year: +row.year,
value: +row.cbp_published_count,
source: +row.year <= 2016 ? "CBP establishments" : "CBP published cells"
}))
]
comparisonBounds = comparisonRows.filter((row) =>
+row.year >= 2017 &&
row.cbp_lower_bound != null && row.cbp_lower_bound !== "" &&
row.cbp_upper_bound != null && row.cbp_upper_bound !== "" &&
Number.isFinite(+row.cbp_lower_bound) &&
Number.isFinite(+row.cbp_upper_bound)
)
comparisonChart = Plot.plot({
width: 920,
height: 440,
marginLeft: 70,
x: {label: "Year", tickFormat: d3.format("d")},
y: {label: "Facilities or establishments", grid: true},
color: {legend: true},
marks: [
Plot.areaY(comparisonBounds, {
x: "year",
y1: "cbp_lower_bound",
y2: "cbp_upper_bound",
fill: "#9c7a32",
fillOpacity: 0.18
}),
Plot.ruleX([2017], {stroke: "#6b5f67", strokeDasharray: "5,4"}),
Plot.line(comparisonSeries, {x: "year", y: "value", stroke: "source"}),
Plot.dot(comparisonSeries, {
x: "year",
y: "value",
stroke: "source",
r: 2.3,
title: (row) => `${row.source}\n${row.year}: ${fmtInt(row.value)}`
})
]
})html`<div class="chart-wrap">${comparisonChart}</div>
<p class="compact-note">Through 2016, the CBP series uses published county cells for NAICS 621420 and 623220. Beginning in 2017, omitted county-industry cells are not treated as zero; the shaded area shows bounds allowing zero to two establishments per omitted cell.</p>`Directory downloads and survey-reconstruction resources remain separate.
html`<div class="download-list">${downloads.map((row) => html`
<a href="${row.url}" target="_blank" rel="noopener">
<span>${row.dataset}</span><strong>${row.format}</strong>
</a>
`)}</div>`Which source should I use?
Use the public-use survey data for nationally comparable facility characteristics such as ownership, OTP status, treatment setting, payment acceptance, and services. Use the directory listings when address or county geography is required. Directory and survey counts can differ because the products have different inclusion and publication rules.
Survey redesign and missing values
N-SUMHSS replaced N-SSATS and N-MHSS in 2021. The dashboard retains substance-use and combined-focus N-SUMHSS facilities for substance-use trends and places a visible break at 2021. A characteristic that was not asked or cannot be harmonized is shown as unavailable, not zero.
County assignment
Street-level Census geocodes are treated as high confidence. ZIP-to-county assignments are retained as lower-confidence fallbacks. The county cards always show assigned and unassigned records so mapped totals can be reconciled to the filtered state total.
Reproducibility
This dashboard verifies a pinned checksum manifest from samhsa-treatment-facility-data, then generates only compact browser assets. Raw cleaning and PDF parsing are not duplicated here. The data repository and historical directory parser provide the complete provenance and QA files.