# ILPI SDS/MSDS enforcement data — October 16, 2026

## What was acquired

Official DOL bulk ZIPs for OSHA `violation`, `violation_event`, and `inspection`, retrieved October 16, 2026. ZIP members are dated 2026-10-16. Source URLs, byte lengths and SHA-256 hashes are in `source_manifest.json`; full scan counts and ZIP member lists are in `scan_audit.json`. Original ZIPs are retained in the workspace `work/` directory; the download package contains the selected raw rows, not the multi-gigabyte unfiltered ZIPs.

| Table | Rows scanned |
|---|---:|
| violation | 13,280,422 |
| violation_event | 11,679,711 |
| inspection | 5,202,096 |

The full violation scan selected **166,714 unique inspection/citation-item pairs**, associated with 160,098 inspections. The selected records' issuance dates range from **1985-12-17 to 2026-09-11**. This is observed SDS coverage, not a claim that the database begins in 1985 or that all SDS citations since 1985 are present. The retrieval date is not the latest enforcement date.

| Scope | Citation items, including source deletion indicators and unusual codes |
|---|---:|
| 1910.1200(g), core | 125,919 |
| Explicit (g) codes under 1926.59 or 1915.1200 | 39,313 |
| Narrow SDS-specific cross-references | 1,482 |
| Total | 166,714 |

## Reproduce the retrieval and filtering

Run `python3 reproduce.py` from an empty working directory with Python 3 and at least 8 GB free. No third-party packages or API key are required. This downloads the three bulk ZIPs, streams every CSV member, filters and joins the data, then writes CSV/JSON, inventory, audits and summary tables under `outputs/`. Existing ZIPs in `work/` are reused. Remove or replace them deliberately to retrieve a newer snapshot. A future run is reproducible as a procedure, but its bytes may differ because the official URLs are mutable: compare hashes. The script rebuilds data, not the editorial HTML page. The retained raw selected CSVs also permit downstream auditing without downloading the full tables again.

1. Keep all IDs and original fields as strings; do not drop leading zeroes in citation IDs.
2. Create a matching key from uppercase standard text with spaces and punctuation removed. Preserve `standard` verbatim. Examples `19101200 G08`, `19101200 G 8`, and a dotted/parenthesized spelling all resolve to the same standard family. A complete inventory of considered standard codes and row counts is supplied.
3. Include the explicit (g) family under 1910.1200, 1926.59 (zero-padded 19260059 or shorter 1926059), and 1915.1200. These construction/shipyard provisions incorporate the HazCom requirements. Never include a bare 1926.59 or 1915.1200 citation solely because the standard incorporates HazCom generally.
4. Include only the narrow SDS-specific paragraphs 1910.1200(b)(3)(ii), (b)(4)(ii), (e)(2)(i), and 1910.1450(h)(1)(ii). Broader laboratory, sealed-container, program, labeling, training and exposure-record citations require narrative evidence and are held out. Historical and current MSDS/SDS terminology does not change the (g) matching rule.
5. Inventory other paragraphs in those families, 1910.20/1910.1020, 1917.28 and 1918.90 as `review_required`; these are not automatically SDS citations. The inventory is a code-level screening ledger, not a fully reviewed citation narrative queue. No blanket inference from the words HazCom, laboratory, chemical, training, exposure record, or general duty is used.
6. Join violation events on `(activity_nr, citation_id)` and inspections on `activity_nr`. Audit uniqueness; do not silently resolve conflicting source rows. There were no duplicate selected violation keys or conflicting selected inspection joins in this snapshot; one selected citation has no matching inspection row.
7. Preserve initial and current penalties exactly as supplied. Preserve all matching events, including failure-to-abate events, in `raw_selected_events.csv` and the JSON `event_history`. Do not sum event penalties into citation penalties.
8. Derive original classification only from a matching ordinary-penalty event with `pen_fta=P` and `hist_event=Z` (issued), and only if its nonempty type values agree. Preserve `viol_type` as current classification. Never infer the original type from the citation number or the current type. If no unambiguous issued event exists, original classification is null.
9. Keep all deletion-indicated items in the dataset. Summary tables split on `active_summary_eligible`: this requires no deletion indicator, one distinct source violation row, and a plausible primary paragraph code. It is an analytical eligibility flag, not a legal finding or a determination that an inspection remains open. The snapshot uses X as its nonblank deletion indicator, while the legacy dictionary describes D; both are treated conservatively as flagged.
10. Use citation issuance calendar year for year summaries. A citation item is the inspection/citation-ID pair, not an inspection, a company, an exposed employee, or a distinct penalty payment. Grouped items may have no individually allocated penalty. Known-value sums are not comprehensive financial totals.

## Validation and missing data

- 160,973 items are eligible for the default nondeleted summaries; 5,690 carry a source deletion indicator, and 51 have unusual primary standard codes (these exclusions can overlap).
- Original classification is known for 69,517; 97,197 lack an unambiguous issued classification. 3,806 items with known originals differ from their current classification. These figures include deletion-indicated items.
- 113,630 initial penalty fields and 116,358 current penalty fields are blank. Blank, NaT and nan are null, never zero. Source penalty values are decimal strings, avoiding binary floating-point conversion.
- Current does not mean final: use final-order, contest and inspection-close fields. A settled case can change again in the source; a retrieval is a snapshot. Failure-to-abate amounts are separate fields.
- Standard-code normalization consolidates primary paragraph numbers; suffixes are preserved explicitly as `[suffix ...]`, rather than inventing a validated legal subparagraph from malformed historical codes. Primary (g) codes outside 1–11, unusually long numeric fields or nonnumeric variants are flagged for review. This is syntax screening, not proof of validity under the regulation effective on the issuance date.
- `issue_category` is a provisional code-based label, not a verified factual finding of missing SDS versus defective SDS content. Historical text and the actual citation narrative must be reviewed before publishing such factual distinctions. In particular, (g)(1) must not automatically be called a missing-SDS case.
- Inspection and citation URLs are generated from the official OSHA record URL pattern. Attempts to open sampled OSHA detail pages failed in this session; direct source-page comparison remains **unverified**. The extraction is validated against the official bulk tables, not individually against all OSHA web pages or case files.
- The current portal's event metadata says `hist_vtype` applies to F, while the legacy official dictionary says P. The bulk data populate ordinary P events with classification codes, consistent with the legacy dictionary. This implementation uses P/Z for originals and preserves the raw evidence. The discrepancy remains documented rather than silently rewriting the source metadata.

Default eligible counts by current classification, across all included scopes:

| Current classification | Citation items |
|---|---:|
| Other | 93,094 |
| Repeat | 2,628 |
| Serious | 64,971 |
| Unclassified | 119 |
| Willful | 161 |

## Coverage limits

This is a bounded extraction from the three named official files, not a complete census of historical SDS enforcement. OSHA's broader data documentation describes inspections since 1972, and its FOIA guidance points to DOL historical data since 1970; neither establishes full SDS record coverage in the downloaded snapshot. No record was manufactured to fill a missing year.

Coverage depends on federal and participating State Plan reporting, changes to information systems, historical data entry, state-specific standard coding, public-release delays and later corrections. State and federal jurisdiction fields are preserved but not expanded into an unverified jurisdiction crosswalk. State-specific legal equivalents not coded to these federal families can be missed. Bare or miscoded standards, withdrawn citations whose original standard has changed, text-only SDS allegations under other standards, and records absent from the official files cannot be recovered by this code filter. No comprehensive cross-reference/narrative search or historical CFR edition-by-edition validity audit has been completed. Mixed SDS/labeling/training paragraphs are not included without case-specific proof. A zero penalty does not establish a de minimis classification, and no de minimis conclusion is inferred.

## Files and field guide

- `sds_enforcement.csv`: one row per distinct selected citation item, raw violation columns plus derived fields and OSHA links.
- `sds_enforcement.json`: the same items with event history and joined raw inspection rows. Monetary amounts remain strings; missing values are null.
- `raw_selected_violations.csv`, `raw_selected_events.csv`, `raw_selected_inspections.csv`: source values unchanged except lowercased column headers and added scope/code fields on selected violations. Source empty/NaT/nan spellings are retained here.
- `summary_by_classification.csv`, `summary_by_original_classification.csv`, `summary_by_year.csv`, `summary_by_paragraph.csv`: counts by scope and eligibility, with known penalty counts and sums. Add only compatible eligibility/scope groups. Suffix notation is explained above.
- `standard_inventory.csv`: all screened raw codes, inclusion/review decision and row count. Unlisted standard families were not examined for SDS-specific narrative allegations.
- `source_manifest.json`, `scan_audit.json`, `validation_summary.json`: download identity, full-table scan and selected-data QA.
- `source_metadata/`: retained official catalog responses and the legacy official data dictionary.
- `reproduce.py`: standard-library retrieval, filtering, joining and summary program.
- `violations.html`, `violations-data/`, `violations.js`, `violations.css`: a local ILPI page draft, summary dashboard and browser data. Deploy together in `/msds/osha/`; the HTML reuses the existing ILPI header/footer and their site styles. No live ILPI page was changed.

`record_id` is `activity_nr:citation_id`. `scope` distinguishes the core (g) family, incorporated (g) families, and narrow cross-references. `paragraph_code` is a punctuation-free source code; `paragraph_normalized` adds a readable primary paragraph. `source_row_count`, `distinct_source_row_count`, `data_quality_flags`, `deleted_indicator_present` and `active_summary_eligible` expose the audit decisions. The original raw fields remain available alongside the derived fields.

## Authoritative sources

- [DOL Violation catalog](https://dataportal.dol.gov/datasets/10338)
- [DOL Violation Event catalog](https://dataportal.dol.gov/datasets/10357)
- [DOL Inspection catalog](https://dataportal.dol.gov/datasets/10334)
- [Official legacy OSHA enforcement dictionary](https://usdepartmentoflabor.github.io/Developer/health-and-safety/dol-osha-enforcement/)
- [1910.1200 Hazard Communication](https://www.osha.gov/laws-regs/regulations/standardnumber/1910/1910.1200)
- [1926.59 construction incorporation](https://www.osha.gov/laws-regs/regulations/standardnumber/1926/1926.59)
- [1915.1200 shipyard incorporation](https://www.osha.gov/laws-regs/regulations/standardnumber/1915/1915.1200)
- [1910.1450 laboratory standard](https://www.osha.gov/laws-regs/regulations/standardnumber/1910/1910.1450)
- [OSHA inspection-search coverage and release cautions](https://www.osha.gov/help/industry-search)

## Summary dashboard revision

The page now leads with annual and scope graphs and tables. Year-range and scope selectors apply to both graphs and tables. The dashboard uses only active_summary_eligible items; 2026 and 1985 are marked partial. Penalty coverage and sums of supplied values are shown separately. Individual records remain available in an optional expanded section. The graphs use violations-summary.json derived from summary_by_year.csv. Script-level checks verify the totals, filters and reset; the revised layout has not received a separate visual browser inspection.

## Cited-standard scope is not industry

A follow-up check of the joined inspection industry fields found 33 core 1910.1200(g) citation items in 2016–2026 at establishments coded NAICS 336611 or SIC 3731 (shipbuilding/repair); this count includes deletion-indicated items. There are 37 for 2015–2026. The explicit 1915.1200(g) series ends in 2009, and 1926.59(g) ends in 2014, in this extract. Their disappearance is not evidence of an end to SDS enforcement in those industries. See shipyard_coding_check.json for annual counts and examples. Industry codes do not independently establish legal jurisdiction for every inspected task. An inspection-adjusted trend would need a matched inspection cohort, consistent federal/State Plan coverage, industry and inspection-type controls, and suitable treatment of citation lag; the present dashboard remains a descriptive citation-count view.

## Combined SDS totals and inspection-adjusted percentages

The public dashboard now combines the core and incorporated (g) families, plus the narrow SDS-specific cross-references, into one SDS citation total. Original scope/code breakdowns remain in the research downloads. The separate 1926.59(g)/1915.1200(g) codes in this snapshot precede 2015; no formal universal change on that date has been established.

Run `python3 inspection_rates.py` after `python3 reproduce.py`, from the same directory. This scans every member of the retained `work/OSHA_inspection.zip`, verifies inspection-ID uniqueness, and uses source INSP_SCOPE A, B or C as conducted inspections. Both open and closed inspections are included. Other/blank scope values are excluded, rather than being assumed to have been conducted. Industry screening uses the same OR rules stated on the page; conflicting historical NAICS/SIC classifications are not individually adjudicated.

The percentage numerator is distinct conducted inspections with at least one eligible SDS item, joined by activity_nr and assigned to inspection OPEN_DATE calendar year. The denominator is all recorded conducted inspections from the same file, including those with no citations, in the same start-year/industry group. `percent_with_any_sds` includes the narrow SDS-specific cross-references and is the public percentage. `percent_with_sds` excludes those cross-references and is retained for sensitivity analysis. Never calculate the rate by dividing issuance-year citation items by inspection-year counts.

All 5,202,096 inspection rows were scanned; 386,699 rows with unconfirmed/nonconducted scope were excluded. There were no duplicate inspection IDs. Thirty-one eligible SDS inspection IDs were outside the conducted-inspection denominator (including missing inspection matches); they remain in issuance-year totals but do not enter the rate numerator. `inspection_rate_audit.json` records scan results; `inspection_year_rates.csv` and `inspection-year-rates.json` record exact numerator/denominator counts. Numerators never exceed denominators, and industry groups reconcile to the all-industry counts.

Observed all-industry percentages: 1989 = 16,186 / 112,468 = 14.391649%; 2000 = 2,638 / 92,022 = 2.866706%; 2024 = 1,534 / 70,400 = 2.178977%. These describe the snapshot and do not demonstrate that improved compliance caused the decline. Inspection targeting/type, jurisdiction mix, data-system changes, missing/miscoded citations and reporting differences remain potential explanations. Recent cohorts are right-censored: inspections still open or awaiting citation issuance/publication have not had equal time to acquire citations. Accordingly, recent rates can rise with future snapshots; 2026 is also a partial year. Calendar-year counts in this mixed-jurisdiction dataset are not interchangeable with published federal-only fiscal-year statistics.

## Dual-axis inspection graph

The inspection graph overlays gray bars for conducted inspections (left count axis) and an orange line for the observed percentage with SDS citations (right percentage axis). Both axes start at zero and rescale to the selected period/industry. Both series use the same inspection-start-year cohorts; no data or rate calculations changed. Independent scaling does not establish a causal relationship. The annual table retains both values.

## Penalty display and chart labels

Penalty columns refer to source violation-table initial/current values on the selected SDS citation items, not inspection-total penalties. Annual sums add only supplied current amounts on those selected items. Grouping can prevent isolating an SDS-only allocated amount, and missing/current amounts must not be interpreted as zero/final/paid. Both charts now reserve at least 72 SVG-coordinate units between displayed year labels and always retain the final year label.

## Compact annual-table ratios

The inspection columns are combined as numerator / denominator (percentage). Penalty coverage is reported current amount count / citation-item count, with positive, zero and missing counts shown separately. This is not a fine-incidence rate. The 1996 coverage is 247/3143 = 7.86% rounded, with 245 positive amounts (7.80%), two zeroes and 2,896 missing values. Run refresh_dashboard_summary.py after reproduce.py to refresh the additional dashboard counts.

## Positive-penalty column revision

The public annual table now labels positive-current-penalty item counts as SDS citations with penalty and shows count (percentage of all eligible items issued that year). Zero and missing amounts are not counted as positive, but remain in the percentage denominator. The linked header footnote explains historical missingness. The eligible data have no missing current amounts in 2016–2026; 2015 has 110 missing. The 277 (18.44%) example is for 2024; 2022 is 211 (15.09%). Total SDS penalties remains the unchanged sum of supplied current item amounts.

## Classification-by-year graph

Current source classifications are aggregated by citation issuance year for all eligible SDS items, across all included requirements and industries. Percentage mode divides each classification count by that year’s eligible item count; count mode shows the same counts as stacked columns. The five categories are Serious, Other, Repeat, Willful and Unclassified; no ordinal severity score is assigned. Year totals reconcile to the main annual item totals (160,973 overall). Run refresh_classification_summary.py after reproduce.py to recreate classification-by-year.json and classification_by_year.csv.

## Combined citation chart

The separate annual citation-total chart has been removed. The current-classification stacked chart now serves both purposes, defaulting to counts; its stacked height equals the annual eligible citation total. Percentage mode shows the classification mix, and Table view gives the five category counts and total. The separate inspection-volume/rate chart and penalty summary remain.

## Delivery paths

The deployable page is /msds/osha/citations_g.html. Its CSS and browser scripts use the citations_g prefix and reside alongside it. All datasets, audits, source metadata and retrieval scripts are under /msds/osha/data/. Original raw standard codes remain preserved, including the limited SDS-specific cross-references. Future paragraph e/f/h pages can use the same naming convention; none have been created yet. The full-site ZIP is rooted at msds/osha/. No live ILPI files were changed.

## Public upload folder

The live data folder retains browser JSON, yearly browsing files, downloadable dataset/summary CSV and JSON, this methodology and the complete research ZIP. Raw selected source rows, source metadata, audits and retrieval scripts are available inside that ZIP rather than duplicated as loose public files. A data/.htaccess sets X-Robots-Tag: noindex for this directory and descendants. No data URLs are added to the sitemap; citations_g.html is listed as the public landing page. Header delivery must be confirmed on the deployed Apache host after upload; the Python preview server does not implement .htaccess.

## Corrected public inspection IDs and source URLs

The public browser and generated dataset URL fields now derive modern OSHA public IDs from bulk ACTIVITY_NR: floor(ACTIVITY_NR/10) - 33000000 for the observed 330000000–349999999 family, then append the observed .015 public URL suffix. Older IDs are retained without this transformation. This is an empirically supported conversion, not a located official universal specification. Both URL forms for Home Pride (bulk 348430745, public 1843074.015, citation 01007B) were successfully tested by the user; the screenshot confirms standard 19101200 G01, Serious, issued 2026-02-03, initial/current $0 and final order 2026-02-10. Barker and Floor Care public IDs match independent official search rows. Not all generated URLs have been opened individually; website availability and unexamined historical exceptions remain limits. Source activity_nr/citation_id, penalty values and all summary counts are unchanged. New columns expose the public ID, conversion method and link validation status. This supersedes prior notes saying all downloaded URL fields remain in their initial unverified form.
