Obscura

ECHO Enforcement Cases

Environmental enforcement actions and penalties from the EPA's ECHO database.

echo_enforcement_cases — the dataset name to pass to the Obscura API.

Category Regulatory Columns 20 Refresh Daily Point-in-time available_date

What one row means

One EPA enforcement case activity (one ACTIVITY_ID) from EPA ECHO's `case_downloads.zip` -> `CASE_ENFORCEMENTS.csv`: a single formal or informal enforcement action (an administrative order, judicial referral, penalty, etc.) taken against a regulated facility under EPA or a delegated state/local program, as of EPA ECHO's latest weekly full-refresh bulk extract.

One row per (activity_id).

Point-in-time availability

Every Obscura dataset carries available_date: the calendar day the publisher made the row available, day-of, with no session rounding. It is the one column a backtest filters on, and it means the same thing on every dataset in the catalog.

For echo_enforcement_cases: published: available_date = status_date + 14 days, EPA ECHO's ~2-week weekly-refresh lag from a case's status-date to its appearance in the public bulk extract.

Refresh cadence

Obscura refreshes echo_enforcement_cases daily — the most frequent scheduled job that re-collects or re-exports it. This is Obscura's own pipeline cadence, not the upstream publisher's release schedule; when a row became public is recorded per row in available_date.

Schema — 20 columns

The full public column list for echo_enforcement_cases, with the meaning of every field. The same schema is served unauthenticated at https://api.obscura.trade/v1/catalog/echo_enforcement_cases.

ColumnTypeDescription
activity_idtext · not nullEPA's unique identifier for this enforcement case activity, from the `CASE_ENFORCEMENTS.csv` `ACTIVITY_ID` column. Primary key; rows without one are dropped at parse time. Stable across weekly refreshes — the upsert conflict target.
status_datedate · not nullThe case's own status/conclusion date (`CASE_STATUS_DATE`, falling back to `ACTIVITY_STATUS_DATE`) — e.g. when a Final Order was issued or a case was administratively closed. A period/event date, rehomed away from available_date; NOT the date EPA disclosed that fact publicly.
case_numbertextEPA's human-readable case docket number from `CASE_NUMBER`, formatted as `{EPA-region}-{fiscal-year}-{sequence}`. A secondary, source-facing identifier distinct from the surrogate activity_id primary key; nullable when EPA has not yet assigned one.
case_nametextFree-text case title from `CASE_NAME`, typically embedding the respondent facility/company name and the enforcement program. Display + fuzzy name-matching only — there is no facility_id/registry_id foreign key on this table linking a case to a specific echo_facilities row.
state_codetextTwo-letter US state postal abbreviation of where the enforcement action's subject facility/respondent is located, from `STATE_CODE`. Frequently blank in the source extract.
region_codetextEPA administrative region number (01-10) that owns/leads the case, from `REGION_CODE`. An EPA organizational/geographic grouping, not a full facility address — see echo_facilities for lat/long.
fiscal_yearintegerUS federal fiscal year (Oct-Sep) the case activity is attributed to, from `FISCAL_YEAR`. Not a calendar-year date and not the same fiscal year implied by status_date, which can fall in an adjacent FY near the Sep/Oct boundary.
activity_type_desctextCategory of enforcement activity from `ACTIVITY_TYPE_DESC`, e.g. "Administrative - Formal", "Judicial", distinguishing formal/informal administrative actions from judicial referrals.
activity_status_desctextCurrent lifecycle status of the case activity from `ACTIVITY_STATUS_DESC`, e.g. "Closed". The row is upserted in place as status progresses through a case's life, so a re-refresh can flip this in place under the same activity_id.
leadtextLead enforcement agency for the case from `LEAD`, e.g. "EPA", distinguishing EPA-led actions from those led by a delegated state/local co-regulator under its authorized program.
enf_outcome_desctextFormal enforcement outcome/resolution type from `ENF_OUTCOME_DESC`, e.g. "Final Order With Penalty". Independent of whether a penalty dollar amount was actually assessed (see total_penalty_assessed_amt).
total_penalty_assessed_amtdouble precisionTotal monetary penalty assessed against the respondent in this case, in USD, from `TOTAL_PENALTY_ASSESSED_AMT`. NULL for cases still in process or with no monetary penalty component.
total_cost_recovery_amtdouble precisionTotal dollar amount EPA recovered for its own cleanup/response costs in this case, in USD, from `TOTAL_COST_RECOVERY_AMT`. Distinct from total_penalty_assessed_amt (a punitive penalty) and total_comp_action_amt (compliance-action cost).
total_comp_action_amtdouble precisionTotal dollar amount the respondent was required to spend on compliance actions/injunctive relief (e.g. pollution-control equipment, remediation) as part of the case resolution, in USD, from `TOTAL_COMP_ACTION_AMT`.
voluntary_self_disclosure_flagtext'Y'/'N' text flag from `VOLUNTARY_SELF_DISCLOSURE_FLAG` indicating the violation was voluntarily self-disclosed by the respondent under EPA's Audit Policy. Stored as source text rather than boolean.
multimedia_flagtext'Y'/'N' text flag from `MULTIMEDIA_FLAG` indicating the case spans multiple environmental media/statutes rather than a single program. Stored as source text rather than boolean.
enf_summary_texttextFree-text case narrative from `ENF_SUMMARY_TEXT` describing the enforcement action — violations, respondents, and remedy. ~66% populated; the richest NLP/entity-extraction signal in the file, not derivable from any other kept column.
doj_docket_nmbrtextDepartment of Justice docket number from `DOJ_DOCKET_NMBR` for judicially-referred cases (~2.7% of rows). A court/legal cross-reference identifier distinct from case_number (the EPA docket).
hq_divisiontextEPA HQ division/program code from `HQ_DIVISION` (e.g. `CER`, `TOX`), ~90% populated. Encodes the lead environmental program/media, a categorical dimension finer than multimedia_flag.
available_datedatePUBLIC-availability date = status_date + 14 days, EPA ECHO's weekly-refresh lag between a case's status-date and its appearance in the public bulk extract. DB-generated (STORED), read-only; the point-in-time column to filter/join on — NEVER status_date itself.

Access echo_enforcement_cases

Two delivery paths, one identifier. Both require an Obscura account and an active subscription; the catalog entry and the schema above are public.

import obscura

client = obscura.Client("obs_live_…")

df = client.query(
    dataset="echo_enforcement_cases",
    start="2024-01-01",
)

Create a free account Browse all 95 datasets

Frequently asked questions

What is in the echo_enforcement_cases dataset?

Environmental enforcement actions and penalties from the EPA's ECHO database. One EPA enforcement case activity (one ACTIVITY_ID) from EPA ECHO's `case_downloads.zip` -> `CASE_ENFORCEMENTS.csv`: a single formal or informal enforcement action (an administrative order, judicial referral, penalty, etc.) taken against a regulated facility under EPA or a delegated state/local program, as of EPA ECHO's latest weekly full-refresh bulk extract.

How do I avoid look-ahead bias with echo_enforcement_cases?

Filter on echo_enforcement_cases.available_date, the day the publisher made the row public. For this dataset that date is derived as follows — published: available_date = status_date + 14 days, EPA ECHO's ~2-week weekly-refresh lag from a case's status-date to its appearance in the public bulk extract. A query of the form WHERE available_date <= '<as-of date>' never sees a row before it existed.

In what formats can I get echo_enforcement_cases?

As a Parquet bulk export (POST https://api.obscura.trade/v1/download) or as JSON from the typed query API (POST https://api.obscura.trade/v1/query), both with dataset="echo_enforcement_cases". The column schema is public at https://api.obscura.trade/v1/catalog/echo_enforcement_cases.

How often is echo_enforcement_cases updated?

Obscura refreshes echo_enforcement_cases on a daily schedule — that is the most frequent scheduled job that re-collects or re-exports the table. It is Obscura's own pipeline cadence, not the upstream publisher's release schedule; when the publisher makes a row available is described by the availability rule above, and is recorded per row in available_date.

Related datasets

CFTC Commitments of TradersThe CFTC's weekly Commitments of Traders report: how commercial, non-commercial and small traders are positioned in each… ECHO FacilitiesEPA-regulated facilities and their compliance history, from ECHO. EDGAR 13F FilingsMetadata for every 13F report, the quarterly filing where large institutional managers list their U.S. equity holdings. EDGAR 13F HoldingsThe line-item positions inside each 13F: which manager held which security, and how much, each quarter. EDGAR 8-K ItemsThe item numbers reported on each 8-K (5.02 officer changes, 2.01 acquisitions, 1.01 material agreements, and so on), so… EDGAR Dilution MetricsShare-issuance headroom and overhang metrics built from companies' XBRL filings: authorized versus outstanding shares, a… EDGAR Filing TextSignals mined from the text of filings, such as mentions of distress, litigation, going-concern doubt and restructuring. EDGAR FilingsThe master index of SEC EDGAR: every filing, with its form type, filer and date.