Restaurant Health Inspection Intelligence avatar

Restaurant Health Inspection Intelligence

Pricing

from $5.00 / 1,000 record scrapeds

Go to Apify Store
Restaurant Health Inspection Intelligence

Restaurant Health Inspection Intelligence

Find failed inspections, repeated violations, closures, and compliance trends from official public records (NYC, Chicago). Returns normalized records plus derived intelligence: risk score, repeat-offender flags, and likely service needs.

Pricing

from $5.00 / 1,000 record scrapeds

Rating

0.0

(0)

Developer

Aiden Mason

Aiden Mason

Maintained by Community

Actor stats

0

Bookmarked

2

Total users

1

Monthly active users

7 days ago

Last modified

Share

Find failed inspections, repeated violations, closures, reinspections, and restaurant compliance trends from official public records — and turn them into structured, commercially useful intelligence.

This Apify Actor collects restaurant health inspection records from official public open-data sources, normalizes them into one consistent schema, and adds a derived intelligence layer: risk scores, repeat-offender flags, declining-performance detection, reinspection opportunities, and likely service needs (pest control, deep cleaning, food-safety consulting, equipment maintenance, training).

It is positioned as inspection intelligence, not just a scraper. Every record includes a source URL and source record ID so you can verify it with the original agency.


Supported jurisdictions (v1)

JurisdictionSourcePlatformRecordsUpdated
New York City (DOHMH)DOHMH Restaurant Inspection Results (43nn-pn8j)Socrata~296,000Daily
Chicago (CDPH)Food Inspections (4ijn-s7e5)Socrata~313,000Frequent

Both are official, free, no-login public open-data endpoints accessed via direct HTTP (Socrata SODA API). No proxies, no browser automation, no CAPTCHA, no authentication required.

More Socrata jurisdictions (Seattle/King County, NYS, San Francisco, Dallas, Toronto) share the same connector and are on the expansion roadmap.


What it answers

  • Which restaurants failed an inspection this week / month?
  • Which restaurants have repeated violations?
  • Which restaurants have declining scores?
  • Which restaurant chains/locations have the most violations?
  • Which businesses received emergency closures?
  • Which restaurants were reinspected?
  • Which newly inspected restaurants may need compliance services (pest control, cleaning, consulting, maintenance, training)?

Input

See .actor/input_schema.json for the full, UI-documented schema. Key fields:

FieldTypeDescription
jurisdictionenumnyc or chicago (required)
restaurantNamestringCase-insensitive contains match on business name
zipCodestringFilter to one ZIP/postal code
dateFrom / dateTodate (YYYY-MM-DD)Inspection date range (leave empty for no bound)
inspectionResultsmulti-selectpass/fail/conditional/closed/pending/not_scored/unknown
minScore / maxScorenumberInspection score range (NYC; higher = more violations)
violationCategoriesmulti-selectfood_temp/food_protection/pest_control/sanitation/plumbing_water/equipment/hygiene/structural/signage/other
criticalViolationsOnlybooleanKeep only inspections with ≥1 critical violation
closureOnlybooleanKeep only closures
reinspectionOpportunityOnlybooleanFail/conditional with no later pass
maxResultsintegerCap on returned records (default 1000; 0 = up to 100,000)
sortOrderenumdate_desc/date_asc/score_desc/risk_desc
includeViolationDetailsbooleanPopulate the nested violations array (default true)
includeHistoricalInspectionsbooleanAggregate per-business history for intelligence (default true)
keywordstringSubstring match on violation description text (e.g. rodent)
socrataAppTokenstring (secret)Optional free token to raise rate limits; not required
requestDelayMsintegerPolite delay between paginated requests (default 150)

Example input

{
"jurisdiction": "nyc",
"dateFrom": "2026-06-01",
"dateTo": "2026-06-30",
"inspectionResults": ["fail", "conditional"],
"criticalViolationsOnly": true,
"maxResults": 1000,
"sortOrder": "risk_desc"
}

Output

Each dataset row is one inspection (NYC's per-violation source rows are aggregated into one record per inspection). Records use a stable normalized schema (see .actor/dataset_schema.json, which also defines the Apify Output-tab views). Unavailable values are null.

Core fields

record_id, source_record_id, source_url, jurisdiction, county, state, restaurant_name, legal_business_name, facility_type, chain_name, address, city, postal_code, latitude, longitude, license_number, inspection_id, inspection_date (ISO 8601), inspection_type, inspection_result, inspection_score, inspection_grade, risk_level, closure_status, closure_date, reopen_date, reinspection_required, reinspection_date, total_violations, critical_violation_count, noncritical_violation_count, violations[], inspector_notes, source_platform, scraped_at.

Violation objects (violations[])

violation_code, violation_description, violation_category, severity (critical/major/minor/informational/unknown), critical, corrected_on_site, repeat_violation.

Intelligence fields (derived, documented, configurable)

lead_category, risk_score (0–100), repeat_offender, recent_failure, recent_closure, declining_performance, reinspection_opportunity, likely_service_needs[], inspection_frequency_days, days_since_last_inspection, days_since_last_failure, location_inspection_count, chain_inspection_count, violation_trend (improving/stable/worsening/unknown), data_completeness_score.

Original source values are preserved in _src_* fields (e.g. _src_inspection_type, _src_action, _src_cuisine) so nothing is silently overwritten.

Intelligence rules (documented thresholds)

FieldRuleDefault threshold
recent_failureBusiness has a failed inspection within N days of now30 days
repeat_offender≥ N failed/conditional inspections within trailing M months2 / 12 months
recent_closureBusiness has a closure within N days of now90 days
declining_performanceLatest score worse than average of prior two by ≥ Δ (NYC); or result rank worsens (Chicago)Δ = 7
reinspection_opportunityLatest inspection fail/conditional with no later pass in the returned set
likely_service_needsViolation categories in the last 90 days mapped to services90 days
risk_scoreWeighted: recent_failure +25, repeat_offender +20, recent_closure +15, declining +10, reinspection +15, critical-density up to +15, score up to +10 (capped 100)
lead_categoryPrimary service need, or compliance_monitoring for closures, else research/none

Category → service mapping: pest_control→pest_control, sanitation/structural→deep_cleaning, plumbing_water/equipment→equipment_maintenance, food_temp/food_protection→food_safety_consulting, hygiene→training, signage→compliance_monitoring.

Thresholds are configurable via input overrides (recentFailureDays, recentClosureDays, repeatMonths) and DEFAULT_THRESHOLDS in src/intelligence/index.js.

Derived fields are estimates ("likely"/"possible"). They never assert a business is unsafe. Always verify with the original agency.

Example output

See docs/example_output.json for five full enriched records. Example snippet:

{
"record_id": "nyc:50188053:2026-07-09:preopening",
"restaurant_name": "LILEO",
"city": "BROOKLYN",
"state": "NY",
"inspection_date": "2026-07-09",
"inspection_result": "pending",
"inspection_grade": "N",
"inspection_score": 33,
"critical_violation_count": 2,
"risk_score": 45,
"recent_failure": false,
"likely_service_needs": ["food_safety_consulting"],
"lead_category": "food_safety_consulting",
"source_url": "https://data.cityofnewyork.us/Health/DOHMH-New-York-City-Restaurant-Inspection-Results/43nn-pn8j",
"source_record_id": "camis:50188053",
"scraped_at": "2026-07-13T17:00:00.000Z"
}

Run locally

Requirements: Node.js 18+.

npm install
# Unit + integration tests (Node test runner)
npm test
# Live validation against the NYC source (writes docs/05-validation-report.md)
node scripts/validate.js
# Run the Actor locally (uses Apify local storage)
APIFY_LOCAL_STORAGE_DIR=./storage node src/main.js # provide input via storage/default_value.json

Example local input (storage/key_value_stores/default/INPUT.json):

{ "jurisdiction": "nyc", "dateFrom": "2026-06-01", "dateTo": "2026-06-30", "maxResults": 500 }

Deploy

apify login
apify create # or push this folder
apify push

The Dockerfile builds on apify/actor-node:20. Dependencies are production-only in the image.


Example API call

curl -X POST "https://api.apify.com/v2/acts/<your-username>/restaurant-health-inspection-intelligence/runs?token=$APIFY_TOKEN" \
-H "Content-Type: application/json" \
-d '{"jurisdiction":"nyc","inspectionResults":["fail"],"criticalViolationsOnly":true,"dateFrom":"2026-06-01","maxResults":1000}'

Architecture

src/
main.js # Actor entry: input validation, orchestration, dataset push, OUTPUT summary
sources/
socrata.js # Reusable Socrata SODA client (pagination, retries, rate limiting)
nyc.js # NYC config + per-violation -> per-inspection aggregation + normalization
chicago.js # Chicago config + violation-blob parser + normalization
index.js # Source registry + collect()
normalization/
dates.js # ISO 8601 date normalization
results.js # Result/severity/inspection-type controlled vocabularies
violations.js # Violation parsing + service-category classification
addresses.js # Address/postal/dedup-key helpers
intelligence/
index.js # Risk scoring, repeat offenders, trends, likely service needs
utils/
filters.js # Post-aggregation filtering + sorting + maxResults
monitoring.js # Source-change / data-quality warnings
.actor/ # Apify Actor definition (publish config)
actor.json # Name, version, memory limits, input/output/dataset schema wiring
input_schema.json # UI-documented input schema
dataset_schema.json # Dataset item JSON Schema + Output-tab views (overview/leads/compliance)
output_schema.json # OUTPUT run-summary schema
docs/ # Research + validation deliverables
tests/ # 122 automated tests (Node test runner)
scripts/validate.js # Live 100-record validation + 1000-record smoke test

Source-specific extraction is kept separate from shared normalization and intelligence logic, so new Socrata jurisdictions plug in via a config + normalizer without touching the core.


Required Actor features

Input schema ✓ · Dataset output ✓ · Pagination ✓ · Request retries ✓ · Rate limiting ✓ · Duplicate detection (record_id) ✓ · Date filtering ✓ · Result filtering ✓ · Score filtering ✓ · Restaurant-name filtering ✓ · Violation parsing ✓ · Severity classification ✓ · Max-result limits ✓ · Clear logs ✓ · Run statistics (OUTPUT) ✓ · Graceful missing-field handling ✓ · Source-link preservation ✓ · Error reporting (circuit breaker + warnings) ✓ · Data-quality rules ✓.

Monitoring & source-change detection

The Actor logs warnings (and records them in the OUTPUT summary) for: sudden zero-result runs, raw-rows-but-no-normalized (schema drift), missing required fields (>5%), unmapped inspection_result values, and pagination accounting mismatches.

Validation

A live validation run against NYC DOHMH (see docs/05-validation-report.md) validated 100 inspections field-by-field: 100% match on restaurant name, address, inspection date, result, score, grade, violation text, closure status, source URL, and record ID. A 1000-record smoke test passed (1,446 inspections processed without crashing).


  • Uses only public records from official open-data portals. No authentication, CAPTCHA, paywall, or access-control circumvention.
  • Does not collect or republish inspector personal information or unnecessary personal data.
  • Preserves inspection dates and result context; old inspections are not presented as current conditions.
  • Distinguishes open / closed / reopened and corrected vs. historical violations.
  • Derived scores are estimates and never label a business as dangerous. Users must verify with the original agency (NYC DOHMH / CDPH via the preserved source_url).
  • Data is attributed to the issuing agency; every record carries source_url and source_record_id.

See docs/03-source-recommendation.md for the full access & compliance assessment.


Known limitations (v1)

  • v1 fully supports NYC; Chicago is supported as an expansion target (no numeric score, so Chicago risk uses result-rank decline and the violation-text blob is parsed heuristically).
  • chain_name is intentionally null — chain ownership is not inferred from name similarity without a documented rule.
  • The dataset reflects each source's rolling window (NYC ~3 years of active establishments).
  • Socrata unauthenticated requests are rate-limited; provide a free socrataAppToken for high-volume runs.

Changelog

  • 1.0.0 — Initial release. NYC + Chicago Socrata adapters, normalized schema, intelligence layer, 122 tests, live 100-record validation (100% match).