Urban health & operations (Etherdata demo)

311 Health & Sanitation Intelligence (Manhattan Demo) - Powered by Etherdata's Canonical US Census at H3 Resolution 8

Etherdata.ai publishes canonical, analysis-ready spatial datasets so teams can move from "interesting maps" to defensible decisions. This page demonstrates a simple but transformative principle: operational signals become decision-grade only when they are anchored to canonical denominators.

We use NYC 311 service requests as the operational signal and Etherdata's canonical census layer as the backbone. The result is a monitoring view that answers a question counts alone cannot: where are health and sanitation issues truly over-indexing relative to the number of people exposed?

  • Geography

    Manhattan, New York (H3 Resolution 8)

  • Operational signal

    NYC 311 health & sanitation service requests

  • Backbone

    Etherdata canonical census denominators

  • Focus

    Complaint rate, operational friction, risk index

Why the census table creates the value

A raw 311 request count is not comparable across neighborhoods. It is a mixture of: (1) exposure (how many people live there), (2) urban form (housing density, housing stock), and (3) reporting dynamics (language access, time, trust, civic habits).

Etherdata's canonical census table turns 311 from a "volume map" into a comparable measurement layer by providing:

  • Denominators (population, households, housing units) to convert counts into stable rates
  • Equity context (e.g., rent burden) to interpret who is likely most constrained
  • Join reliability via deterministic H3 indexing (no hand-built spatial joins, no drifting boundaries)

This is the core value proposition: the census layer is not another dataset. It is the scaffold that makes every other dataset more interpretable, more comparable, and more actionable.

Scope and structure

Geography

Manhattan only (county boundary polyfill -> H3 R8)

Unit of analysis

H3 resolution 8 hexagons (one row per cell)

Signal

NYC 311 service requests (health & sanitation taxonomy defined explicitly)

Backbone

Etherdata canonical census (denominators + context)

Charts (placeholders + implementation notes for Ian)

The charts below are placeholders for the current Looker Studio report outputs (4 charts). They are intentionally specified with exact field bindings so they can be embedded cleanly on the public site.

Health & Sanitation Complaint Rate

This is the primary monitoring map: health requests per 1,000 population. It is the most defensible hotspot view because it removes the dominant confounder (population exposure). In the report output, a small number of cells reach the high end of the scale (near ~561 per 1k), standing apart from the broader baseline across Manhattan.

Decision use

Nominate hotspot clusters for investigation and response planning.

Why census is essential

The denominator is what makes high meaningful and comparable.

Health & Sanitation Risk Index

The risk index is where the census layer becomes explicitly transformative. It blends three standardized components into a transparent triage score: complaint rate intensity, rent burden (vulnerability), and average time-to-close (operational friction). In the report output, the index spans roughly -2.57 to 0.64, producing a sharper prioritization map than rates alone.

Decision use

Allocate limited remediation and inspection capacity to the highest-priority cells.

Why census is essential

Vulnerability context makes prioritization more equitable and defensible.

Complaint Volume vs Population

This chart demonstrates the trap of raw counts. As population increases, complaint volume increases - predictably. The purpose is not discovery; it is explainability: it shows stakeholders why most complaints is not the same as most at-risk. Outliers above the general relationship indicate cells generating more complaints than expected for their exposure.

Decision use

Defend rate-based prioritization over count-based prioritization.

Why census is essential

Population is the exposure baseline that makes the scatter interpretable.

Equity Lens: Rent Burden vs Complaint Rate

This chart makes the equity argument legible. It asks: do elevated health and sanitation complaint rates concentrate where households are more financially constrained? In the output, most cells cluster in a rent burden band (~20-35 percent) with modest complaint rates, while a small set of outliers reach very high rates (approaching the top of the axis near ~600). These outliers are the look closer cells.

Decision use

Defend rate-based prioritization over count-based prioritization.

Why census is essential

Population is the exposure baseline that makes the scatter interpretable.

Conclusion

This Manhattan demo is intentionally simple - and that is the point. The lift is not making a map. The lift is establishing a canonical backbone that makes operational data comparable, explainable, and fair.

With Etherdata's canonical census table, NYC 311 becomes a reusable health and sanitation intelligence layer: it supports hotspot detection, operational triage, and equity-aware prioritization in a single framework, and it generalizes immediately to any other dataset that can be indexed to H3.

Starter query (BigQuery / Looker Studio)

-- NYC 311 Health & Sanitation monitoring at H3 R8, filtered to Manhattan,
-- joined to Etherdata canonical census, and enriched with geometry helpers for plotting.


WITH
-- Manhattan boundary (New York County, FIPS 061 within NY state 36)
manhattan AS (
 SELECT county_geom AS geom
 FROM `bigquery-public-data.geo_us_boundaries.counties`
 WHERE state_fips_code = '36'
   AND RIGHT(county_fips_code, 3) = '061'
 LIMIT 1
),


--  Manhattan H3 set at R8 via polyfill
manhattan_h3 AS (
 SELECT h3
 FROM manhattan m,
 UNNEST(bqcarto.h3.ST_ASH3_POLYFILL(m.geom, 8)) AS h3
),


-- Filter and spatialize 311 requests into H3 R8
sr AS (
 SELECT
   unique_key,
   created_date,
   closed_date,
   agency,
   agency_name,
   complaint_type,
   descriptor,
   location_type,
   address_type,
   city,
   borough,
   incident_zip,
   latitude,
   longitude,


   -- H3 at resolution 8 (CARTO toolbox)
   bqcarto.h3.LONGLAT_ASH3(longitude, latitude, 8) AS h3,


   -- Operational metric: time to close (hours) when closed_date is present
   SAFE_DIVIDE(TIMESTAMP_DIFF(closed_date, created_date, SECOND), 3600.0) AS hours_to_close
 FROM `bigquery-public-data.new_york_311.311_service_requests`
 WHERE created_date >= TIMESTAMP('2021-01-01')
   AND created_date <  TIMESTAMP('2022-01-01')
   AND latitude  IS NOT NULL
   AND longitude IS NOT NULL
),


--  Keep only records whose H3 falls in Manhattan polyfill
sr_manhattan AS (
 SELECT s.*
 FROM sr s
 INNER JOIN manhattan_h3 mh
   ON s.h3 = mh.h3
),


--  Define a transparent "health & sanitation" subset
sr_labeled AS (
 SELECT
   *,
   CASE
     WHEN LOWER(complaint_type) IN (
       'rodent',
       'food poisoning',
       'food establishment',
       'unsanitary condition',
       'dirty conditions',
       'standing water',
       'water system',
       'sewer',
       'air quality',
       'hazardous materials',
       'mold'
     )
     THEN 1 ELSE 0
   END AS is_health_sanitation
 FROM sr_manhattan
),


--  Aggregate 311 metrics per H3 cell
sr_agg AS (
 SELECT
   h3,


   -- Volumes
   COUNT(1) AS requests_total,
   SUM(is_health_sanitation) AS requests_health_sanitation,
   SAFE_DIVIDE(SUM(is_health_sanitation), COUNT(1)) AS health_sanitation_share,


   -- Service performance (avg time-to-close in hours)
   AVG(hours_to_close) AS avg_hours_to_close_all,
   AVG(IF(is_health_sanitation = 1, hours_to_close, NULL)) AS avg_hours_to_close_health,


   -- Closure counts (helps filter unstable averages)
   COUNTIF(closed_date IS NOT NULL) AS requests_closed_total,
   COUNTIF(is_health_sanitation = 1 AND closed_date IS NOT NULL) AS requests_closed_health
 FROM sr_labeled
 WHERE h3 IS NOT NULL
 GROUP BY h3
),


-- 4) Canonical census denominators & context (already H3-keyed)
census AS (
 SELECT
   h3,
   total_pop,
   households,
   housing_units,
   median_income,
   median_rent,
   percent_income_spent_on_rent,
   housing_built_1939_or_earlier,
   no_car
 FROM `v3layer-pro.census_tract_h3r8.nydemo`
),


-- 5) Final layer: rates + composite index + geometry helpers
final AS (
 SELECT
   a.h3,


   -- Geometry helpers for plotting
   bqcarto.h3.ST_BOUNDARY(a.h3) AS geom,
   CONCAT(
     ST_Y(ST_CENTROID(bqcarto.h3.ST_BOUNDARY(a.h3))),
     ",",
     ST_X(ST_CENTROID(bqcarto.h3.ST_BOUNDARY(a.h3)))
   ) AS latlon,


   -- 311 volumes
   a.requests_total,
   a.requests_health_sanitation,
   a.health_sanitation_share,


   -- Service performance
   a.avg_hours_to_close_all,
   a.avg_hours_to_close_health,
   a.requests_closed_total,
   a.requests_closed_health,


   -- Census context
   c.total_pop,
   c.households,
   c.housing_units,
   c.median_income,
   c.median_rent,
   c.percent_income_spent_on_rent,
   c.housing_built_1939_or_earlier,
   c.no_car,


   -- Exposure-normalized rates (per 1,000)
   SAFE_MULTIPLY(1000.0, SAFE_DIVIDE(a.requests_health_sanitation, NULLIF(c.total_pop, 0))) AS health_requests_per_1k_pop,
   SAFE_MULTIPLY(1000.0, SAFE_DIVIDE(a.requests_health_sanitation, NULLIF(c.households, 0))) AS health_requests_per_1k_hh,


   -- Composite prioritization index (ranking/monitoring; not causal)
   (
     SAFE_DIVIDE(
       (
         SAFE_MULTIPLY(1000.0, SAFE_DIVIDE(a.requests_health_sanitation, NULLIF(c.total_pop, 0)))
         - AVG(SAFE_MULTIPLY(1000.0, SAFE_DIVIDE(a.requests_health_sanitation, NULLIF(c.total_pop, 0)))) OVER()
       ),
       NULLIF(
         STDDEV_POP(SAFE_MULTIPLY(1000.0, SAFE_DIVIDE(a.requests_health_sanitation, NULLIF(c.total_pop, 0)))) OVER(),
         0
       )
     )
     +
     SAFE_DIVIDE(
       (c.percent_income_spent_on_rent - AVG(c.percent_income_spent_on_rent) OVER()),
       NULLIF(STDDEV_POP(c.percent_income_spent_on_rent) OVER(), 0)
     )
     +
     SAFE_DIVIDE(
       (a.avg_hours_to_close_health - AVG(a.avg_hours_to_close_health) OVER()),
       NULLIF(STDDEV_POP(a.avg_hours_to_close_health) OVER(), 0)
     )
   ) / 3.0 AS health_sanitation_risk_index

 FROM sr_agg a
 LEFT JOIN census c
   ON a.h3 = c.h3
)

SELECT * FROM final;