DYNAMIC · EXTENT: 0 AGENCIES
Crimes Against Persons 0
Crimes Against Property 0
Crimes Against Society 0

Click a feature or use search to view details.

Overview About NIBRS Data Pipeline Using the App Tech & Data

What this app is

MI Crime Analysis is a custom Spatial Intelligence Dashboard for analysing Michigan crime data reported under the FBI’s National Incident-Based Reporting System (NIBRS). Every point on the map is a Michigan law-enforcement agency, symbolised by agency type and carrying a full offence profile for the reporting year.

The app is built on the ArcGIS Maps SDK for JavaScript v5 and the Calcite Design System v5. It demonstrates server-side filtering, live extent-based analytics, multi-source search, and the modern arcgis-* web-component architecture — all published from a hosted feature service on ArcGIS Online.

Who it’s for

Crime analysts, researchers, journalists, municipal officials, and citizens who need an honest, agency-level view of Michigan crime statistics — without scrolling through PDF tables or writing SQL.

Key ideas
Agency-level granularity · live extent analytics · server-side definitionExpression filtering · multi-source search · Calcite v5 UI.

What is NIBRS?

The National Incident-Based Reporting System is the FBI’s modern incident-level crime-reporting standard, administered through the Uniform Crime Reporting (UCR) Program. On January 1, 2021 NIBRS replaced the older Summary Reporting System (SRS) as the national standard for reporting crime to the FBI.

Where SRS counted only the single most serious offence per incident (the “hierarchy rule”), NIBRS captures every offence, every victim, every offender, every weapon, every piece of property, and every arrestee tied to an incident — giving analysts far richer signal.

Group A vs Group B offences

NIBRS offences are split into two groups:

  • Group A — 52 offences in 23 categories (assault, burglary, drug offences, etc.). Full incident detail is reported whenever these occur.
  • Group B — 10 additional offences (DUI, drunkenness, disorderly conduct, etc.). Only reported when an arrest is made.

Three offence categories

Group A offences roll up into the three high-level categories shown in the Crimes Summary panel:

  • Crimes Against Persons — assault, homicide, sex offences, human trafficking, kidnapping
  • Crimes Against Property — burglary, larceny, motor-vehicle theft, fraud, arson, vandalism
  • Crimes Against Society — drug/narcotic offences, gambling, prostitution, weapon-law violations

Agency types in this dataset

  • City
  • Township
  • County (Metropolitan)
  • County (Nonmetropolitan)
  • State Police
  • University / College
  • Tribal
  • Other
Authoritative references
FBI — NIBRS program page · FBI Crime Data Explorer (CDE) · UCR Program overview

From raw CSV to hosted feature service

The map you’re looking at is the end of a seven-step pipeline that turns the FBI’s raw NIBRS CSV drop into a geospatial feature service. The pipeline runs once per reporting year (2022 → 2023 → 2024 …) and is fully documented so next year’s refresh is a checklist, not a research project.

  • Download the FBI NIBRS CSV bundle for the state + year (e.g. MI-2023.zip).
  • Unzip into C:\NIBRS\MI-YYYY\MI\ — every CSV lives in one flat folder.
  • Install PostgreSQL locally (14+) and open DBeaver with a connection to localhost:5432.
  • Create a clean per-year database: nibrs_YYYY_michigan. One database per year keeps schema drift contained.
  • Run an adapted postgres_setup.sql that creates the nibrs schema plus every lookup and fact table.
  • Primary keys and foreign keys are added at the end of the script — that matches the order rows will be loaded and avoids FK friction mid-load.
  • The schema is derived directly from the FBI NIBRS data dictionary, so field names and types match the published standard.
  • Use straight COPY (not \COPY) so the command runs inside DBeaver’s JDBC session.
  • Forward-slash Windows paths: 'C:/NIBRS/MI-YYYY/MI/FILE.csv'.
  • Standard clause: WITH (FORMAT csv, HEADER true, NULL '').
  • For the rare file with special encoding (e.g. 2023 NIBRS_BIAS_LIST), add ENCODING 'WIN1251' to that one COPY.

Before the bulk load, disable referential-integrity triggers on every FK-constrained table:

ALTER TABLE tablename DISABLE TRIGGER ALL;

Then COPY the “straight” fact tables — the ones whose CSV aligns perfectly with the target schema — in an order that minimises FK friction:

  1. nibrs_monthnibrs_incidentnibrs_offense
  2. nibrs_property, nibrs_property_desc, nibrs_suspect_using, nibrs_suspected_drug
  3. nibrs_victim (+ circumstances, + injury, + offense), nibrs_weapon, nibrs_bias_motivation

Some CSVs don’t load cleanly. Typical symptoms:

  • ERROR: extra data after last expected column — trailing empty column (seen in 2023 Group B arrestee).
  • ERROR: missing data for column "X" — fewer CSV fields than target (e.g. 2024 Group B arrestee omits high age_range_* fields).
  • ERROR: invalid input syntax for type smallint: 'NS'/'BB' — non-numeric literals in numeric columns.

The fix is a TEMP staging pattern:

CREATE TEMP TABLE temp_arrestee (columns as TEXT matching CSV order exactly);

COPY temp_arrestee FROM 'C:/NIBRS/MI-YYYY/MI/arrestee.csv'
  WITH (FORMAT csv, HEADER true, NULL '');

INSERT INTO nibrs_arrestee (col1, col2, …)
SELECT
  CAST(…)::bigint,
  CASE WHEN txt_col ~ '^[0-9]+$' THEN txt_col::smallint ELSE NULL END,
  CASE WHEN sex_code IN ('', 'NS') THEN NULL ELSE LEFT(sex_code, 1) END,
  …
FROM temp_arrestee;

Used on arrestee, arrestee_groupb, victim, and sometimes offender depending on the year.

  • Re-enable every trigger disabled in Step 4: ALTER TABLE … ENABLE TRIGGER ALL;
  • Row-count check: SELECT COUNT(*) FROM nibrs_… vs the CSV header count.
  • Spot-check problematic records — age fields, Group B rows with trailing commas.
  • Confirm no orphan FKs now that integrity is back on.
  • Build the agency-offense pivot view — one row per agency with 60+ offence columns plus the three Crimes-Against rollups. Name it by year: agency_offense_pivot_summary_2023.
  • (Optional) Materialise the view to a table with a PK on agency_id for snappy ArcGIS joins.
  • In ArcGIS Pro, join the Michigan agency point layer (from the gazetteer geodatabase) to the pivot view on agency_id. Reconcile nibrs_missing_in_police and police_extra mismatches before publishing.
  • Publish the joined layer as a hosted feature service in ArcGIS Online. That feature service is what this web map consumes.

The core query is agency_offense_category_summary — a PostgreSQL view that joins agencies to incidents, offences, and offence types, then SUMs each NIBRS offence category into its own column with a CASE expression:

CREATE OR REPLACE VIEW nibrs.agency_offense_category_summary AS
SELECT
    a.agency_id,
    COUNT(o.offense_id) AS total_offenses,

    -- Category summaries (one CASE per NIBRS offence category)
    COALESCE(SUM(CASE WHEN ot.offense_category_name = 'Homicide Offenses'
        THEN 1 ELSE 0 END), 0) AS "Homicide_Offenses",
    COALESCE(SUM(CASE WHEN ot.offense_category_name = 'Sex Offenses'
        THEN 1 ELSE 0 END), 0) AS "Sex_Offenses",
    COALESCE(SUM(CASE WHEN ot.offense_category_name = 'Larceny/Theft Offenses'
        THEN 1 ELSE 0 END), 0) AS "Larceny_Theft_Offenses",
    COALESCE(SUM(CASE WHEN ot.offense_category_name = 'Robbery'
        THEN 1 ELSE 0 END), 0) AS "Robbery",
    COALESCE(SUM(CASE WHEN ot.offense_category_name = 'Assault Offenses'
        THEN 1 ELSE 0 END), 0) AS "Assault_Offenses",
    -- … (28 more CASE rollups: Burglary, Motor_Vehicle_Theft,
    --       Arson, Fraud_Offenses, Drug_Narcotic_Offenses,
    --       Weapon_Law_Violations, Human_Trafficking, …)
    COALESCE(SUM(CASE WHEN ot.offense_category_name = 'All Other Offenses'
        THEN 1 ELSE 0 END), 0) AS "All_Other_Offenses"

FROM nibrs.agencies a
LEFT JOIN nibrs.nibrs_incident     i  ON a.agency_id       = i.agency_id
LEFT JOIN nibrs.nibrs_offense      o  ON i.incident_id     = o.incident_id
LEFT JOIN nibrs.nibrs_offense_type ot ON o.offense_type_id = ot.offense_type_id
GROUP BY a.agency_id
ORDER BY a.agency_id;

The full script lives on the workstation at C:\Users\gregg\AppData\Roaming\DBeaverData\workspace6\General\Scripts\agency_offense_category_summary.sql and contains all 33 category rollups. For the published feature service the view is joined against the Michigan gazetteer agency point layer on agency_id and the three high-level NIBRS crime-category totals (Persons / Property / Society) are added as computed fields.

The hosted feature service that powers this map has 65 attributes per agency, grouped in three bands. Fields 1–13 are agency metadata and population; the next block is the three high-level Crimes-Against rollups; the remainder are the individual NIBRS offences, organised so Group A maps cleanly to the three categories used in the sidebar bar chart.

Agency metadata & rollups
  1. OBJECTID
  2. Agency ID
  3. State
  4. Address
  5. City
  6. County
  7. Zip Code
  8. Agency Type
  9. Agency Name
  10. NCIC Agency Name
  11. UCR Agency Name
  12. Pub Agency Name
  13. Population
  14. Total Offenses
  15. Crimes Against Persons
  16. Crimes Against Property
  17. Crimes Against Society
Crimes Against Persons (1–23)
  1. Assault Offenses
  2. Aggravated Assault
  3. Simple Assault
  4. Intimidation
  5. Homicide Offenses
  6. Murder Nonneg Manslaughter
  7. Negligent Manslaughter
  8. Justifiable Homicide
  9. Human Trafficking Offenses
  10. Commercial Sex Acts
  11. Involuntary Servitude
  12. Kidnapping Abduction
  13. Sex Offenses
  14. Rape
  15. Sodomy
  16. Sexual Assault With Object
  17. Fondling
  18. Incest
  19. Statutory Rape
Crimes Against Property (24–55)
  1. Arson
  2. Bribery
  3. Burglary Breaking Entering
  4. Counterfeiting Forgery
  5. Destruction Damage Vandalism
  6. Embezzlement
  7. Extortion Blackmail
  8. Fraud Offenses
  9. Credit Card Atm Fraud
  10. False Pretenses Swindle
  11. Impersonation
  12. Welfare Fraud
  13. Wire Fraud
  14. Identity Theft
  15. Hacking Computer Invasion
  16. Larceny Theft Offenses
  17. Pocket Picking
  18. Purse Snatching
  19. Shoplifting
  20. Theft From Building
  21. Theft From Coin Op
  22. Theft From Motor Vehicle
  23. Theft Of Vehicle Parts
  24. All Other Larceny
  25. Motor Vehicle Theft
  26. Robbery
  27. Stolen Property Offenses
  28. Animal Cruelty
  29. Drug Narcotic Offenses
  30. Drug Narcotic Violations
  31. Drug Equipment Violations
  32. Gambling Offenses
Crimes Against Society (56–65)
  1. Betting Wagering
  2. Operating Promoting Gambling
  3. Gambling Equipment Violations
  4. Sports Tampering
  5. Pornography Obscene Material
  6. Prostitution Offenses
  7. Prostitution
  8. Assisting Promoting Prostitution
  9. Purchasing Prostitution
  10. Weapon Law Violations

The bars in the Crimes Summary panel are server-side SUMs over the red/blue/gold blocks above. The popup shows all 65 fields, grouped the same way, so every individual offence is visible per agency.

Yearly refresh tips
Diff the CSV headers against the prior year the moment the FBI drop lands — arrestee/victim/offender columns drift most. Keep the column-list COPY habit even for files that used to load clean; it prevents silent mis-alignment.

Navigating the map

  • Pan / zoom with the mouse or the + / − buttons in the top-left corner.
  • Home button returns the view to the full Michigan extent.
  • Search (top-right) accepts Agency ID or Agency Name (zooms to the point) and County name (zooms to the county boundary).
  • Scale bar in the bottom-left shows dual-unit (metric / imperial) scale.

Filtering by agency type

Use the Filter dropdown in the header to isolate a specific agency type (e.g. only City agencies, or only State Police). The filter applies as a server-side definitionExpression on the feature layer, so the map reflects the true filter state even for agencies that haven’t yet loaded into the client cache.

The Crimes Summary panel

The coloured bars are live, not static. A reactive watcher on the view’s extent re-queries the visible features on every pan / zoom and sums:

  • Crimes Against Persons (red)
  • Crimes Against Property (blue)
  • Crimes Against Society (gold)

The agency count above the bars tells you how many agencies contributed to the visible totals.

Inspecting a single agency

Click any agency point (or pick one via Search) and the Details card replaces its placeholder with the full agency profile — name, population, total offences, and the native Feature widget with its amCharts 5 bar charts comparing violent- and property-crime rates to the national average.

Layers & Legend

Top-right chevron icons expand the Layer List and Legend panels. Use them to toggle overlays or confirm what each symbol means.

Technology stack

  • ArcGIS Maps SDK for JavaScript v5@arcgis/core ES modules + arcgis-* web components.
  • Calcite Design System v5@esri/calcite-components for every UI primitive (shell, navigation, panel, dialog, tabs, accordion).
  • TypeScript in strict mode.
  • Vite 8 (Rolldown bundler) with HTTPS dev server.
  • AWS Amplify Gen 1 — static hosting with CI/CD from the main branch on GitHub.

Map and data sources

  • WebMap — hosted on allthingsspatial.maps.arcgis.com.
  • Basemap — ArcGIS Location Platform v5 slash-format styles (arcgis/…).
  • Feature service — the published pivot layer (Step 7 of the pipeline) with per-agency offence counts and the three Crimes-Against rollups.
  • Raw data — FBI UCR / NIBRS annual CSV drop for Michigan.
Authoritative data source
FBI Uniform Crime Reporting Program — NIBRS program page · Crime Data Explorer.

Credits

Designed, built, and maintained by All Things Spatial, LLC. The pipeline, schema, and ETL approach were refined across the Michigan 2022, 2023, and 2024 NIBRS drops and are now a repeatable yearly checklist.