Skip to content
Open
Show file tree
Hide file tree
Changes from 51 commits
Commits
Show all changes
60 commits
Select commit Hold shift + click to select a range
28a25af
basic working cad list view page
johnclary May 28, 2026
0f9e3b7
add agency to popup
johnclary May 28, 2026
8a6e163
add aggregation permissions
johnclary Jun 1, 2026
44136d4
Merge branch 'john/27761-cad-incident-links' into john/incident-view
johnclary Jun 1, 2026
f82bbb7
adds vz_incidents list view with significant todos wip
johnclary Jun 2, 2026
fb9aa65
add agency_type_short
johnclary Jun 4, 2026
6704ae9
more iterating on incident view, map, etc
johnclary Jun 4, 2026
b7a9023
rename to incidents
johnclary Jun 4, 2026
138e844
add agency filters
johnclary Jun 4, 2026
e282c0b
add missing semicolon;
johnclary Jun 4, 2026
c55876a
🤖 Export database views for john/28753-incident-list
Jun 4, 2026
cc72bfb
remove cad page
johnclary Jun 4, 2026
1a9ff05
add hasura rels to vz_incidents_view
johnclary Jun 4, 2026
e7613d9
vz -> incidents
johnclary Jun 4, 2026
71ef723
update routes with incidents
johnclary Jun 4, 2026
f4d0869
add type gaurd for points
johnclary Jun 4, 2026
11239e8
order unprocessed incidents asc
johnclary Jun 5, 2026
dc9a77c
basic incidents page
johnclary Jun 8, 2026
5a10ab6
replace deprecated type
johnclary Jun 8, 2026
dac3be3
merge upstream
johnclary Jun 30, 2026
ee54c8c
migrations wip
johnclary Jun 30, 2026
78aa578
Merge branch 'main' into john/28753-vz-incident-list
johnclary Jul 10, 2026
568ab53
update views
johnclary Jul 10, 2026
057b88d
update list view with latest changes
johnclary Jul 10, 2026
66ffd93
🤖 Export database views for john/28753-vz-incident-list
Jul 10, 2026
c7aceda
filter points with no geom for map
johnclary Jul 11, 2026
c358d8d
update map popup
johnclary Jul 11, 2026
fb66417
update search config
johnclary Jul 11, 2026
bd929ed
add latitude and longitude to view
johnclary Jul 11, 2026
a6e4325
update still-broken incident page
johnclary Jul 11, 2026
1cbbd6b
🤖 Export database views for john/28753-vz-incident-list
Jul 11, 2026
8654b9f
prune incident details page
johnclary Jul 11, 2026
dc70daf
rollback changes that have moved to incident details branch
johnclary Jul 11, 2026
1121d2b
remove cad stuff
johnclary Jul 11, 2026
1dff47e
update export name
johnclary Jul 11, 2026
ac2dfd8
add latitude and longitude to type
johnclary Jul 11, 2026
2ccfaa4
lint
johnclary Jul 11, 2026
74ed9f1
bring cad etl changes even with main
johnclary Jul 11, 2026
1bec979
remove unused icon
johnclary Jul 13, 2026
0202acb
require editor and admin permissions for vz incidents
johnclary Jul 13, 2026
c268cee
add beta flag to incident list page
johnclary Jul 14, 2026
21e7e1f
remove beta flags from ems
johnclary Jul 14, 2026
ff3d618
add timecutoff to records view and refactor incident view a bit
johnclary Jul 20, 2026
237d8d7
🤖 Export database views for john/28753-vz-incident-list
Jul 20, 2026
1366f35
lint
johnclary Jul 21, 2026
689931d
use materialized view
johnclary Jul 21, 2026
3b81fac
remove commented code
johnclary Jul 21, 2026
43e4de9
remove dupe op name
johnclary Jul 21, 2026
a09f025
remove todos
johnclary Jul 21, 2026
60e6c3d
filter nulls from cols
johnclary Jul 21, 2026
ef29ce8
fix drop statement in migra
johnclary Jul 21, 2026
8384dd0
put materalized in statements
johnclary Jul 21, 2026
0b90760
🤖 Export database views for john/28753-vz-incident-list
Jul 21, 2026
eaf953f
remove unused imports
johnclary Jul 21, 2026
fc6ca6e
fix text wrapping on beta info text
johnclary Jul 23, 2026
96ee8bd
add incident_numbers_str and enable incident number search
johnclary Jul 23, 2026
b394a1f
updated record tables col label
johnclary Jul 23, 2026
4cfd1da
add indexes
johnclary Jul 23, 2026
a452c95
🤖 Export database views for john/28753-vz-incident-list
Jul 23, 2026
257243b
add incident_numbers_str
johnclary Jul 23, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
106 changes: 106 additions & 0 deletions database/metadata/databases/default/tables/public_cad_incidents.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,15 @@ object_relationships:
- name: vz_incident
using:
foreign_key_constraint_on: vz_incident_id
- name: vz_incidents_view

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just realized that we should name this vz_incidents_list_view to be consistent with our other record views. i'm going to fix this in later because I already have a branch going.

using:
manual_configuration:
column_mapping:
vz_incident_id: id
insertion_order: null
remote_table:
name: vz_incidents_view
schema: public
array_relationships:
- name: cad_incident_groups
using:
Expand All @@ -18,3 +27,100 @@ array_relationships:
remote_table:
name: cad_incident_groups
schema: public
select_permissions:
- role: editor
permission:
columns:
- is_cancelled_call
- incident_type
- call_disposition
- priority_description
- final_problem
- master_incident_number
- initial_problem
- master_incident_id
- in_austin_full_purpose
- geom
- id
- address
- agency_type
- agency_type_short
- response_date
- time_first_unit_arrived
- time_call_closed
- created_at
- updated_at
- latitude
- longitude
- location_id
- created_by
- updated_by
- priority_number
- vz_incident_id
filter: {}
allow_aggregations: true
comment: ""
- role: readonly
permission:
columns:
- is_cancelled_call
- incident_type
- call_disposition
- priority_description
- final_problem
- master_incident_number
- initial_problem
- master_incident_id
- in_austin_full_purpose
- geom
- id
- address
- agency_type
- agency_type_short
- response_date
- time_first_unit_arrived
- time_call_closed
- created_at
- updated_at
- latitude
- longitude
- location_id
- created_by
- updated_by
- priority_number
- vz_incident_id
filter: {}
allow_aggregations: true
comment: ""
- role: vz-admin
permission:
columns:
- is_cancelled_call
- incident_type
- call_disposition
- priority_description
- final_problem
- master_incident_number
- initial_problem
- master_incident_id
- in_austin_full_purpose
- geom
- id
- address
- agency_type
- agency_type_short
- response_date
- time_first_unit_arrived
- time_call_closed
- created_at
- updated_at
- latitude
- longitude
- location_id
- created_by
- updated_by
- priority_number
- vz_incident_id
filter: {}
allow_aggregations: true
comment: ""
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
table:
name: vz_incidents_view
schema: public
select_permissions:
- role: editor
permission:
columns:
- incident_numbers
- location_ids
- record_tables
- record_tables_str
- responding_agencies
- responding_agencies_str
- id
- record_count
- in_austin_full_purpose
- address
- record_timestamp
- point_feature
filter: {}
allow_aggregations: true
comment: ""
- role: readonly
permission:
columns:
- incident_numbers
- location_ids
- record_tables
- record_tables_str
- responding_agencies
- responding_agencies_str
- id
- record_count
- in_austin_full_purpose
- address
- record_timestamp
- point_feature
filter: {}
allow_aggregations: true
comment: ""
- role: vz-admin
permission:
columns:
- incident_numbers
- location_ids
- record_tables
- record_tables_str
- responding_agencies
- responding_agencies_str
- id
- record_count
- in_austin_full_purpose
- address
- record_timestamp
- point_feature
filter: {}
allow_aggregations: true
comment: ""
1 change: 1 addition & 0 deletions database/metadata/databases/default/tables/tables.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -96,3 +96,4 @@
- "!include public_view_crash_narratives_ocr_todo.yaml"
- "!include public_vz_incident_records_view.yaml"
- "!include public_vz_incidents.yaml"
- "!include public_vz_incidents_view.yaml"
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
DROP VIEW IF EXISTS vz_incidents_view;

DROP MATERIALIZED VIEW public.vz_incident_records_view;

CREATE OR REPLACE VIEW public.vz_incident_records_view AS
SELECT

'crashes'::text AS record_table_name,
case when
-- 'AUSTIN POLICE DEPARTMENT' -> 'apd'
c.investigat_agency_id = 74
then 'apd'
else agency.label
end AS record_responding_agency,
c.id AS record_id,
c.case_id AS record_incident_number,
c.crash_timestamp AS record_timestamp,
c.address_display AS record_address,
c.position AS geom,
c.vz_incident_id AS vz_incident_id,
c.vz_incident_match_status AS vz_incident_match_status
FROM crashes c
LEFT JOIN lookups.agency agency on agency.id = c.investigat_agency_id
WHERE c.is_deleted is false
UNION ALL
SELECT
'cad_incidents'::text AS record_table_name,
ci.agency_type_short AS record_responding_agency,
ci.id AS record_id,
ci.master_incident_number AS record_incident_number,
ci.response_date AS record_timestamp,
ci.address AS record_address,
ci.geom AS geom,
ci.vz_incident_id AS vz_incident_id,
ci.vz_incident_match_status AS vz_incident_match_status
FROM cad_incidents ci
UNION ALL
SELECT
'ems__incidents'::text AS record_table_name,
'ems' AS record_responding_agency,
ems.id AS record_id,
ems.incident_number AS record_incident_number,
ems.incident_received_datetime AS record_timestamp,
ems.incident_location_address AS record_address,
ems.geometry AS geom,
ems.vz_incident_id AS vz_incident_id,
ems.vz_incident_match_status AS vz_incident_match_status
FROM ems__incidents ems
WHERE ems.is_deleted is FALSE
UNION ALL
SELECT
'afd__incidents'::text AS record_table_name,
'afd' AS record_responding_agency,
afd.id AS record_id,
afd.incident_number::text AS record_incident_number,
afd.call_datetime AS record_timestamp,
afd.address AS record_address,
afd.geometry AS geom,
afd.vz_incident_id AS vz_incident_id,
afd.vz_incident_match_status AS vz_incident_match_status
FROM afd__incidents afd;

COMMENT ON VIEW public.vz_incident_records_view IS 'Unified view of crash-related records (crashes, cad_incidents, ems__incidents, afd__incidents) exposed under a common schema for cross-type queries and geo-temporal matching.';
153 changes: 153 additions & 0 deletions database/migrations/default/1783347751354_vz_incidents_view/up.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
DROP MATERIALIZED VIEW IF EXISTS vz_incidents_view;
DROP VIEW public.vz_incident_records_view;

--
-- add additional columns to view in_austin_full_purpose, location_id, is_location_reviewed

@johnclary johnclary Jul 21, 2026

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

adding columns to the incident_records_view so that we can use it as the basis for the list view.

--
CREATE OR REPLACE VIEW public.vz_incident_records_view AS
SELECT

'crashes'::text AS record_table_name,
case when
-- 'AUSTIN POLICE DEPARTMENT' -> 'apd'
c.investigat_agency_id = 74
then 'apd'
else agency.label
end AS record_responding_agency,
c.id AS record_id,
c.case_id AS record_incident_number,
c.crash_timestamp AS record_timestamp,
c.address_display AS record_address,
c.position AS geom,
c.latitude AS latitude,
c.longitude AS longitude,
c.vz_incident_id AS vz_incident_id,
c.vz_incident_match_status AS vz_incident_match_status,
c.in_austin_full_purpose AS in_austin_full_purpose,
c.location_id AS location_id,
case when c.latitude != cris.latitude or c.longitude != cris.longitude THEN TRUE ELSE FALSE END
AS is_location_reviewed
FROM crashes c
LEFT JOIN lookups.agency agency on agency.id = c.investigat_agency_id
LEFT JOIN crashes_cris cris on c.id = cris.id
WHERE c.is_deleted is false
UNION ALL
SELECT
'cad_incidents'::text AS record_table_name,
ci.agency_type_short AS record_responding_agency,
ci.id AS record_id,
ci.master_incident_number AS record_incident_number,
ci.response_date AS record_timestamp,
ci.address AS record_address,
ci.geom AS geom,
ci.latitude AS latitude,
ci.longitude AS longitude,
ci.vz_incident_id AS vz_incident_id,
ci.vz_incident_match_status AS vz_incident_match_status,
ci.in_austin_full_purpose AS in_austin_full_purpose,
ci.location_id AS location_id,
FALSE AS is_location_reviewed
FROM cad_incidents ci
UNION ALL
SELECT
'ems__incidents'::text AS record_table_name,
'ems' AS record_responding_agency,
ems.id AS record_id,
ems.incident_number AS record_incident_number,
ems.incident_received_datetime AS record_timestamp,
ems.incident_location_address AS record_address,
ems.geometry AS geom,
ems.latitude AS latitude,
ems.longitude AS longitude,
ems.vz_incident_id AS vz_incident_id,
ems.vz_incident_match_status AS vz_incident_match_status,
ems.austin_full_purpose AS in_austin_full_purpose,
ems.location_id AS location_id,
FALSE AS is_location_reviewed
FROM ems__incidents ems
WHERE ems.is_deleted is FALSE UNION ALL
SELECT
'afd__incidents'::text AS record_table_name,
'afd' AS record_responding_agency,
afd.id AS record_id,
afd.incident_number::text AS record_incident_number,
afd.call_datetime AS record_timestamp,
afd.address AS record_address,
afd.geometry AS geom,
afd.latitude AS latitude,
afd.longitude AS longitude,
afd.vz_incident_id AS vz_incident_id,
afd.vz_incident_match_status AS vz_incident_match_status,
afd.austin_full_purpose AS in_austin_full_purpose,
afd.location_id AS location_id,
FALSE AS is_location_reviewed
FROM afd__incidents afd;


COMMENT ON VIEW public.vz_incident_records_view IS
'Unified view of crash-related records (crashes, cad_incidents, ems__incidents, afd__incidents)'
'exposed under a common schema for cross-type queries and geo-temporal matching.';


CREATE MATERIALIZED VIEW vz_incidents_view AS

@johnclary johnclary Jul 21, 2026

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After many attempts at tuning this query, i opted for a materialized view that takes ~5s to create locally.

This feels good enough while we're still in proof-of-concept. Long term, I think we'll want to consider possibly setting up a proper table with the triggers that keep it current, or maybe we can live with having this be on an hourly refresh ( a la location_crashes_view).

I'm still uncertain about what the finally shape of this view needs to be, so I think for now we can just create this materialized view once via migration and let it become stale while we further refine the future state.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 and I clocked this migration at about 7 Mississippis to apply on my machine

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this migration took only like 4 ms for me!

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it took 4 seconds for me

SELECT
id,
record_count,
incident_numbers,
record_tables,
'$' || array_to_string(record_tables, '$,$') || '$' AS record_tables_str,
responding_agencies,
'$' || array_to_string(responding_agencies, '$,$') || '$' AS responding_agencies_str,

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need these string-concatenated fields for search purposes. This is a work around for the fact that Hasura does not have a _not_contains operator on array types.

address,
location_ids,
record_timestamp,
point_feature,
latitude,
longitude,
in_austin_full_purpose
FROM (
SELECT
v.vz_incident_id as id,
COUNT(*) AS record_count,
NULLIF(
ARRAY_AGG(DISTINCT record_incident_number ORDER BY record_incident_number)
FILTER (WHERE record_incident_number IS NOT NULL),
ARRAY[]::text[]
) as incident_numbers,
NULLIF(
ARRAY_AGG(DISTINCT record_table_name ORDER BY record_table_name)
FILTER (WHERE record_table_name IS NOT NULL),
ARRAY[]::text[]
) as record_tables,
Comment on lines +113 to +122

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

are the incident numbers and record tables expected to have corresponding values in their indices? I'm trying to think through how this data is related, and if filtering out null would offset the index in an unexpected way. I might be misunderstanding how the two columns are related!

@johnclary johnclary Jul 23, 2026

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

each row will always have a record_table, but sometimes an incident number can be null. not a concern here, but you are picking up on potential weirdness with the way to way grab address and point_feature using the sorting logic below: it's possible that either one of these are null, in which case we could potentially grab the address from one member record and the point from a different record. it's an extreme edge case but definitely a detail to iron out with the VZ team.

NULLIF(
ARRAY_AGG(DISTINCT record_responding_agency ORDER BY record_responding_agency)
FILTER (WHERE record_responding_agency IS NOT NULL),
ARRAY[]::text[]
) AS responding_agencies,
(ARRAY_REMOVE(
ARRAY_AGG(v.record_address ORDER BY is_location_reviewed desc, v.record_timestamp, v.record_id), NULL

@johnclary johnclary Jul 21, 2026

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So what we're doing here is picking a single member record to serve as the representative record in the list view. We select that based on:

  • If is_location_reviewed is true, use that record: it is a crash report that has had it's position edited
  • Otherwise take the record with the earliest response date, using the arbitrary record_id as a tiebreaker

Again, this is just for the proof of concept: we may find a more sophisticated/informed way of doing this later.

))[1] AS address,
NULLIF(
ARRAY_AGG(DISTINCT location_id ORDER BY location_id)
FILTER (WHERE location_id IS NOT NULL),
ARRAY[]::text[]
) as location_ids,
MIN(v.record_timestamp) as record_timestamp,
(ARRAY_REMOVE(
ARRAY_AGG(v.geom ORDER BY is_location_reviewed desc, v.record_timestamp, v.record_id), NULL
))[1] AS point_feature,
(ARRAY_REMOVE(
ARRAY_AGG(v.latitude ORDER BY is_location_reviewed desc, v.record_timestamp, v.record_id), NULL
))[1] AS latitude,
(ARRAY_REMOVE(
ARRAY_AGG(v.longitude ORDER BY is_location_reviewed desc, v.record_timestamp, v.record_id), NULL
))[1] AS longitude,
BOOL_OR(v.in_austin_full_purpose) AS in_austin_full_purpose
FROM vz_incident_records_view v
WHERE vz_incident_id is not null
GROUP BY v.vz_incident_id
) sub;

COMMENT ON VIEW public.vz_incidents_view IS
'Aggregate view of vz_incidents which aggregates attributes from the member records '
'(crashes, cad_incidents, ems__incidents, afd__incidents) via the vz_incident_records_view';
Loading
Loading