diff --git a/database/metadata/databases/default/tables/public_locations_list_view.yaml b/database/metadata/databases/default/tables/public_locations_list_view.yaml index 4968bffd7..bda0da17f 100644 --- a/database/metadata/databases/default/tables/public_locations_list_view.yaml +++ b/database/metadata/databases/default/tables/public_locations_list_view.yaml @@ -5,12 +5,18 @@ select_permissions: - role: editor permission: columns: + - apd_sectors + - area_eng_areas + - signal_eng_areas + - street_levels - cr3_crash_count - crash_count - non_cr3_crash_count - total_est_comp_cost + - is_hin + - is_signalized - location_id - - council_district + - council_districts - location_group - location_name filter: {} @@ -19,12 +25,18 @@ select_permissions: - role: readonly permission: columns: + - apd_sectors + - area_eng_areas + - signal_eng_areas + - street_levels - cr3_crash_count - crash_count - non_cr3_crash_count - total_est_comp_cost + - is_hin + - is_signalized - location_id - - council_district + - council_districts - location_group - location_name filter: {} @@ -33,12 +45,18 @@ select_permissions: - role: vz-admin permission: columns: + - apd_sectors + - area_eng_areas + - signal_eng_areas + - street_levels - cr3_crash_count - crash_count - non_cr3_crash_count - total_est_comp_cost + - is_hin + - is_signalized - location_id - - council_district + - council_districts - location_group - location_name filter: {} diff --git a/database/migrations/default/1771530231604_locations_list_view_more_cols/down.sql b/database/migrations/default/1771530231604_locations_list_view_more_cols/down.sql new file mode 100644 index 000000000..bb1a2130b --- /dev/null +++ b/database/migrations/default/1771530231604_locations_list_view_more_cols/down.sql @@ -0,0 +1,63 @@ +CREATE OR REPLACE VIEW locations_list_view AS WITH cr3_comp_costs AS ( + SELECT + crashes_list_view.location_id, + sum(crashes_list_view.est_comp_cost_crash_based) AS cr3_comp_costs_total + FROM crashes_list_view + WHERE crashes_list_view.crash_timestamp > (now() - '5 years'::interval) + GROUP BY crashes_list_view.location_id +), + +cr3_crash_counts AS ( + SELECT + crashes.location_id, + count(crashes.location_id) AS crash_count + FROM crashes + WHERE + crashes.private_dr_fl = false + AND crashes.location_id IS NOT null + AND crashes.crash_timestamp > (now() - '5 years'::interval) + GROUP BY crashes.location_id +), + +non_cr3_crash_counts AS ( + SELECT + atd_apd_blueform.location_id, + count(atd_apd_blueform.location_id) AS crash_count, + count(atd_apd_blueform.location_id) * 10000 AS noncr3_comp_costs_total + FROM atd_apd_blueform + WHERE + atd_apd_blueform.location_id IS NOT null + AND atd_apd_blueform.is_deleted = false + AND atd_apd_blueform.case_timestamp > (now() - '5 years'::interval) + GROUP BY atd_apd_blueform.location_id +) + +SELECT + locations.location_id, + locations.location_name, + locations.council_districts, + locations.location_group, + locations.is_signalized, + locations.signal_eng_areas, + locations.area_eng_areas, + locations.street_levels, + locations.apd_sectors, + locations.is_hin, + coalesce( + cr3_comp_costs.cr3_comp_costs_total + non_cr3_crash_counts.noncr3_comp_costs_total, + 0::bigint + ) AS total_est_comp_cost, + coalesce( + cr3_crash_counts.crash_count, 0::bigint + ) AS cr3_crash_count, + coalesce( + non_cr3_crash_counts.crash_count, 0::bigint + ) AS non_cr3_crash_count, + coalesce(cr3_crash_counts.crash_count, 0::bigint) + + coalesce(non_cr3_crash_counts.crash_count, 0::bigint) AS crash_count +FROM locations +LEFT JOIN cr3_crash_counts ON locations.location_id::text = cr3_crash_counts.location_id +LEFT JOIN + non_cr3_crash_counts + ON locations.location_id::text = non_cr3_crash_counts.location_id::text +LEFT JOIN cr3_comp_costs ON locations.location_id::text = cr3_comp_costs.location_id; diff --git a/database/migrations/default/1771530231604_locations_list_view_more_cols/up.sql b/database/migrations/default/1771530231604_locations_list_view_more_cols/up.sql new file mode 100644 index 000000000..ae416b538 --- /dev/null +++ b/database/migrations/default/1771530231604_locations_list_view_more_cols/up.sql @@ -0,0 +1,40 @@ +drop view locations_list_view; +create or replace view locations_list_view as + WITH cr3_comp_costs AS ( + SELECT crashes_list_view.location_id, + sum(crashes_list_view.est_comp_cost_crash_based) AS cr3_comp_costs_total + FROM crashes_list_view + WHERE crashes_list_view.crash_timestamp > (now() - '5 years'::interval) + GROUP BY crashes_list_view.location_id + ), cr3_crash_counts AS ( + SELECT crashes.location_id, + count(crashes.location_id) AS crash_count + FROM crashes + WHERE crashes.private_dr_fl = false AND crashes.location_id IS NOT NULL AND crashes.crash_timestamp > (now() - '5 years'::interval) + GROUP BY crashes.location_id + ), non_cr3_crash_counts AS ( + SELECT atd_apd_blueform.location_id, + count(atd_apd_blueform.location_id) AS crash_count, + count(atd_apd_blueform.location_id) * 10000 AS noncr3_comp_costs_total + FROM atd_apd_blueform + WHERE atd_apd_blueform.location_id IS NOT NULL AND atd_apd_blueform.is_deleted = false AND atd_apd_blueform.case_timestamp > (now() - '5 years'::interval) + GROUP BY atd_apd_blueform.location_id + ) + SELECT locations.location_id, + locations.location_name, + locations.council_districts, + locations.location_group, + locations.is_signalized, + locations.signal_eng_areas, + locations.area_eng_areas, + locations.street_levels, + locations.apd_sectors, + locations.is_hin, + COALESCE(cr3_comp_costs.cr3_comp_costs_total + non_cr3_crash_counts.noncr3_comp_costs_total, 0::bigint) AS total_est_comp_cost, + COALESCE(cr3_crash_counts.crash_count, 0::bigint) AS cr3_crash_count, + COALESCE(non_cr3_crash_counts.crash_count, 0::bigint) AS non_cr3_crash_count, + COALESCE(cr3_crash_counts.crash_count, 0::bigint) + COALESCE(non_cr3_crash_counts.crash_count, 0::bigint) AS crash_count + FROM locations + LEFT JOIN cr3_crash_counts ON locations.location_id::text = cr3_crash_counts.location_id + LEFT JOIN non_cr3_crash_counts ON locations.location_id::text = non_cr3_crash_counts.location_id::text + LEFT JOIN cr3_comp_costs ON locations.location_id::text = cr3_comp_costs.location_id; diff --git a/database/views/_lookup_tables_view.sql b/database/views/_lookup_tables_view.sql index 8c40bac39..fa191e63b 100644 --- a/database/views/_lookup_tables_view.sql +++ b/database/views/_lookup_tables_view.sql @@ -1,6 +1,7 @@ -- Most recent migration: database/migrations/default/1727451511064_init/up.sql -CREATE OR REPLACE VIEW _lookup_tables_view AS SELECT DISTINCT table_name +CREATE OR REPLACE VIEW _lookup_tables_view AS +SELECT DISTINCT table_name FROM information_schema.columns WHERE table_schema::name = 'lookups'::name ORDER BY table_name; diff --git a/database/views/crash_injury_metrics_view.sql b/database/views/crash_injury_metrics_view.sql index b77f83dea..8c587a25d 100644 --- a/database/views/crash_injury_metrics_view.sql +++ b/database/views/crash_injury_metrics_view.sql @@ -1,6 +1,7 @@ -- Most recent migration: database/migrations/default/1727451511064_init/up.sql -CREATE OR REPLACE VIEW crash_injury_metrics_view AS SELECT +CREATE OR REPLACE VIEW crash_injury_metrics_view AS +SELECT crashes.id, crashes.cris_crash_id, COALESCE( diff --git a/database/views/crashes_change_log_view.sql b/database/views/crashes_change_log_view.sql index 61a0c5888..82554aa88 100644 --- a/database/views/crashes_change_log_view.sql +++ b/database/views/crashes_change_log_view.sql @@ -1,6 +1,7 @@ -- Most recent migration: database/migrations/default/1727451511064_init/up.sql -CREATE OR REPLACE VIEW crashes_change_log_view AS SELECT +CREATE OR REPLACE VIEW crashes_change_log_view AS +SELECT concat('crash_', change_log_crashes.id) AS id, 'crash'::text AS record_type, change_log_crashes.record_id AS crash_pk, diff --git a/database/views/crashes_list_view.sql b/database/views/crashes_list_view.sql index 005ef1589..5673edfa1 100644 --- a/database/views/crashes_list_view.sql +++ b/database/views/crashes_list_view.sql @@ -73,38 +73,40 @@ SELECT to_char((crashes.crash_timestamp AT TIME ZONE 'US/Central'::text), 'dy'::text) ) AS crash_day_of_week FROM crashes -LEFT JOIN LATERAL (SELECT - crash_injury_metrics_view_1.id, - crash_injury_metrics_view_1.cris_crash_id, - crash_injury_metrics_view_1.unkn_injry_count, - crash_injury_metrics_view_1.nonincap_injry_count, - crash_injury_metrics_view_1.poss_injry_count, - crash_injury_metrics_view_1.non_injry_count, - crash_injury_metrics_view_1.sus_serious_injry_count, - crash_injury_metrics_view_1.tot_injry_count, - crash_injury_metrics_view_1.fatality_count, - crash_injury_metrics_view_1.vz_fatality_count, - crash_injury_metrics_view_1.law_enf_fatality_count, - crash_injury_metrics_view_1.cris_fatality_count, - crash_injury_metrics_view_1.motor_vehicle_fatality_count, - crash_injury_metrics_view_1.motor_vehicle_sus_serious_injry_count, - crash_injury_metrics_view_1.motorcycle_fatality_count, - crash_injury_metrics_view_1.motorcycle_sus_serious_count, - crash_injury_metrics_view_1.bicycle_fatality_count, - crash_injury_metrics_view_1.bicycle_sus_serious_injry_count, - crash_injury_metrics_view_1.pedestrian_fatality_count, - crash_injury_metrics_view_1.pedestrian_sus_serious_injry_count, - crash_injury_metrics_view_1.micromobility_fatality_count, - crash_injury_metrics_view_1.micromobility_sus_serious_injry_count, - crash_injury_metrics_view_1.other_fatality_count, - crash_injury_metrics_view_1.other_sus_serious_injry_count, - crash_injury_metrics_view_1.crash_injry_sev_id, - crash_injury_metrics_view_1.years_of_life_lost, - crash_injury_metrics_view_1.est_comp_cost_crash_based, - crash_injury_metrics_view_1.est_total_person_comp_cost -FROM crash_injury_metrics_view crash_injury_metrics_view_1 -WHERE crashes.id = crash_injury_metrics_view_1.id -LIMIT 1) crash_injury_metrics_view ON TRUE +LEFT JOIN LATERAL ( + SELECT + crash_injury_metrics_view_1.id, + crash_injury_metrics_view_1.cris_crash_id, + crash_injury_metrics_view_1.unkn_injry_count, + crash_injury_metrics_view_1.nonincap_injry_count, + crash_injury_metrics_view_1.poss_injry_count, + crash_injury_metrics_view_1.non_injry_count, + crash_injury_metrics_view_1.sus_serious_injry_count, + crash_injury_metrics_view_1.tot_injry_count, + crash_injury_metrics_view_1.fatality_count, + crash_injury_metrics_view_1.vz_fatality_count, + crash_injury_metrics_view_1.law_enf_fatality_count, + crash_injury_metrics_view_1.cris_fatality_count, + crash_injury_metrics_view_1.motor_vehicle_fatality_count, + crash_injury_metrics_view_1.motor_vehicle_sus_serious_injry_count, + crash_injury_metrics_view_1.motorcycle_fatality_count, + crash_injury_metrics_view_1.motorcycle_sus_serious_count, + crash_injury_metrics_view_1.bicycle_fatality_count, + crash_injury_metrics_view_1.bicycle_sus_serious_injry_count, + crash_injury_metrics_view_1.pedestrian_fatality_count, + crash_injury_metrics_view_1.pedestrian_sus_serious_injry_count, + crash_injury_metrics_view_1.micromobility_fatality_count, + crash_injury_metrics_view_1.micromobility_sus_serious_injry_count, + crash_injury_metrics_view_1.other_fatality_count, + crash_injury_metrics_view_1.other_sus_serious_injry_count, + crash_injury_metrics_view_1.crash_injry_sev_id, + crash_injury_metrics_view_1.years_of_life_lost, + crash_injury_metrics_view_1.est_comp_cost_crash_based, + crash_injury_metrics_view_1.est_total_person_comp_cost + FROM crash_injury_metrics_view crash_injury_metrics_view_1 + WHERE crashes.id = crash_injury_metrics_view_1.id + LIMIT 1 +) crash_injury_metrics_view ON TRUE LEFT JOIN geocode_status ON crashes.id = geocode_status.id LEFT JOIN lookups.collsn ON crashes.fhe_collsn_id = collsn.id LEFT JOIN lookups.injry_sev ON crash_injury_metrics_view.crash_injry_sev_id = injry_sev.id diff --git a/database/views/fatalities_view.sql b/database/views/fatalities_view.sql index 51bb68952..d4c09f02c 100644 --- a/database/views/fatalities_view.sql +++ b/database/views/fatalities_view.sql @@ -1,6 +1,7 @@ -- Most recent migration: database/migrations/default/1727451511064_init/up.sql -CREATE OR REPLACE VIEW fatalities_view AS SELECT +CREATE OR REPLACE VIEW fatalities_view AS +SELECT people.id AS person_id, crashes.id AS crash_pk, crashes.cris_crash_id, diff --git a/database/views/locations_list_view.sql b/database/views/locations_list_view.sql index 82fc78e02..a28dc1067 100644 --- a/database/views/locations_list_view.sql +++ b/database/views/locations_list_view.sql @@ -37,8 +37,14 @@ non_cr3_crash_counts AS ( SELECT locations.location_id, locations.location_name, - locations.council_district, + locations.council_districts, locations.location_group, + locations.is_signalized, + locations.signal_eng_areas, + locations.area_eng_areas, + locations.street_levels, + locations.apd_sectors, + locations.is_hin, coalesce( cr3_comp_costs.cr3_comp_costs_total + non_cr3_crash_counts.noncr3_comp_costs_total, 0::bigint diff --git a/database/views/materialized/location_crashes_view.sql b/database/views/materialized/location_crashes_view.sql index 13c09c86a..429d433d8 100644 --- a/database/views/materialized/location_crashes_view.sql +++ b/database/views/materialized/location_crashes_view.sql @@ -1,10 +1,12 @@ --- Most recent migration: database/migrations/default/1767430748998_crash_address_display_trigger/up.sql +-- Most recent migration: database/migrations/default/1771445809119_update_location_crashes_view/up.sql DROP MATERIALIZED VIEW IF EXISTS location_crashes_view; -CREATE MATERIALIZED VIEW location_crashes_view AS SELECT +CREATE MATERIALIZED VIEW location_crashes_view AS +SELECT crashes.record_locator, crashes.cris_crash_id, + crashes.id AS crash_pk, 'CR3'::text AS type, crashes.location_id, crashes.case_id, @@ -36,57 +38,62 @@ CREATE MATERIALIZED VIEW location_crashes_view AS SELECT crash_units.veh_body_styl_desc, crash_units.veh_unit_desc FROM crashes -LEFT JOIN LATERAL (SELECT - units.crash_pk, - string_agg(movt.label, ','::text) AS movement_desc, - string_agg(trvl_dir.label, ','::text) AS travel_direction, - string_agg(veh_body_styl.label, ','::text) AS veh_body_styl_desc, - string_agg(unit_desc.label, ','::text) AS veh_unit_desc -FROM units -LEFT JOIN lookups.movt movt ON units.movement_id = movt.id -LEFT JOIN lookups.trvl_dir trvl_dir ON units.veh_trvl_dir_id = trvl_dir.id -LEFT JOIN lookups.veh_body_styl veh_body_styl ON units.veh_body_styl_id = veh_body_styl.id -LEFT JOIN lookups.unit_desc unit_desc ON units.unit_desc_id = unit_desc.id -WHERE crashes.id = units.crash_pk -GROUP BY units.crash_pk) crash_units ON true -LEFT JOIN LATERAL (SELECT - crash_injury_metrics_view_1.id, - crash_injury_metrics_view_1.cris_crash_id, - crash_injury_metrics_view_1.unkn_injry_count, - crash_injury_metrics_view_1.nonincap_injry_count, - crash_injury_metrics_view_1.poss_injry_count, - crash_injury_metrics_view_1.non_injry_count, - crash_injury_metrics_view_1.sus_serious_injry_count, - crash_injury_metrics_view_1.tot_injry_count, - crash_injury_metrics_view_1.fatality_count, - crash_injury_metrics_view_1.vz_fatality_count, - crash_injury_metrics_view_1.law_enf_fatality_count, - crash_injury_metrics_view_1.cris_fatality_count, - crash_injury_metrics_view_1.motor_vehicle_fatality_count, - crash_injury_metrics_view_1.motor_vehicle_sus_serious_injry_count, - crash_injury_metrics_view_1.motorcycle_fatality_count, - crash_injury_metrics_view_1.motorcycle_sus_serious_count, - crash_injury_metrics_view_1.bicycle_fatality_count, - crash_injury_metrics_view_1.bicycle_sus_serious_injry_count, - crash_injury_metrics_view_1.pedestrian_fatality_count, - crash_injury_metrics_view_1.pedestrian_sus_serious_injry_count, - crash_injury_metrics_view_1.micromobility_fatality_count, - crash_injury_metrics_view_1.micromobility_sus_serious_injry_count, - crash_injury_metrics_view_1.other_fatality_count, - crash_injury_metrics_view_1.other_sus_serious_injry_count, - crash_injury_metrics_view_1.crash_injry_sev_id, - crash_injury_metrics_view_1.years_of_life_lost, - crash_injury_metrics_view_1.est_comp_cost_crash_based, - crash_injury_metrics_view_1.est_total_person_comp_cost -FROM crash_injury_metrics_view crash_injury_metrics_view_1 -WHERE crashes.id = crash_injury_metrics_view_1.id -LIMIT 1) crash_injury_metrics_view ON true +LEFT JOIN LATERAL ( + SELECT + units.crash_pk, + string_agg(movt.label, ','::text) AS movement_desc, + string_agg(trvl_dir.label, ','::text) AS travel_direction, + string_agg(veh_body_styl.label, ','::text) AS veh_body_styl_desc, + string_agg(unit_desc.label, ','::text) AS veh_unit_desc + FROM units + LEFT JOIN lookups.movt movt ON units.movement_id = movt.id + LEFT JOIN lookups.trvl_dir trvl_dir ON units.veh_trvl_dir_id = trvl_dir.id + LEFT JOIN lookups.veh_body_styl veh_body_styl ON units.veh_body_styl_id = veh_body_styl.id + LEFT JOIN lookups.unit_desc unit_desc ON units.unit_desc_id = unit_desc.id + WHERE crashes.id = units.crash_pk + GROUP BY units.crash_pk +) crash_units ON true +LEFT JOIN LATERAL ( + SELECT + crash_injury_metrics_view_1.id, + crash_injury_metrics_view_1.cris_crash_id, + crash_injury_metrics_view_1.unkn_injry_count, + crash_injury_metrics_view_1.nonincap_injry_count, + crash_injury_metrics_view_1.poss_injry_count, + crash_injury_metrics_view_1.non_injry_count, + crash_injury_metrics_view_1.sus_serious_injry_count, + crash_injury_metrics_view_1.tot_injry_count, + crash_injury_metrics_view_1.fatality_count, + crash_injury_metrics_view_1.vz_fatality_count, + crash_injury_metrics_view_1.law_enf_fatality_count, + crash_injury_metrics_view_1.cris_fatality_count, + crash_injury_metrics_view_1.motor_vehicle_fatality_count, + crash_injury_metrics_view_1.motor_vehicle_sus_serious_injry_count, + crash_injury_metrics_view_1.motorcycle_fatality_count, + crash_injury_metrics_view_1.motorcycle_sus_serious_count, + crash_injury_metrics_view_1.bicycle_fatality_count, + crash_injury_metrics_view_1.bicycle_sus_serious_injry_count, + crash_injury_metrics_view_1.pedestrian_fatality_count, + crash_injury_metrics_view_1.pedestrian_sus_serious_injry_count, + crash_injury_metrics_view_1.micromobility_fatality_count, + crash_injury_metrics_view_1.micromobility_sus_serious_injry_count, + crash_injury_metrics_view_1.other_fatality_count, + crash_injury_metrics_view_1.other_sus_serious_injry_count, + crash_injury_metrics_view_1.crash_injry_sev_id, + crash_injury_metrics_view_1.years_of_life_lost, + crash_injury_metrics_view_1.est_comp_cost_crash_based, + crash_injury_metrics_view_1.est_total_person_comp_cost + FROM crash_injury_metrics_view crash_injury_metrics_view_1 + WHERE crashes.id = crash_injury_metrics_view_1.id + LIMIT 1 +) crash_injury_metrics_view ON true LEFT JOIN lookups.collsn ON crashes.fhe_collsn_id = collsn.id WHERE crashes.is_deleted = false UNION ALL SELECT null::text AS record_locator, aab.form_id AS cris_crash_id, + null::integer AS crash_pk, 'NON-CR3'::text AS type, aab.location_id, aab.case_id::text AS case_id, diff --git a/database/views/people_list_view.sql b/database/views/people_list_view.sql index 31d1ba00b..0bafb8501 100644 --- a/database/views/people_list_view.sql +++ b/database/views/people_list_view.sql @@ -1,6 +1,7 @@ -- Most recent migration: database/migrations/default/1727451511064_init/up.sql -CREATE OR REPLACE VIEW people_list_view AS SELECT +CREATE OR REPLACE VIEW people_list_view AS +SELECT people.id, people.created_at, people.created_by, diff --git a/database/views/person_injury_metrics_view.sql b/database/views/person_injury_metrics_view.sql index 6270fcf61..40c6ce44b 100644 --- a/database/views/person_injury_metrics_view.sql +++ b/database/views/person_injury_metrics_view.sql @@ -1,6 +1,7 @@ -- Most recent migration: database/migrations/default/1727451511064_init/up.sql -CREATE OR REPLACE VIEW person_injury_metrics_view AS SELECT +CREATE OR REPLACE VIEW person_injury_metrics_view AS +SELECT people.id, units.id AS unit_id, crashes.id AS crash_pk, diff --git a/database/views/socrata_export_crashes_view.sql b/database/views/socrata_export_crashes_view.sql index ba4b595d1..a2fc3d044 100644 --- a/database/views/socrata_export_crashes_view.sql +++ b/database/views/socrata_export_crashes_view.sql @@ -78,44 +78,48 @@ SELECT ) AS crash_fatal_fl, collsn.label AS collsn_desc FROM crashes -LEFT JOIN LATERAL (SELECT - crash_injury_metrics_view.id, - crash_injury_metrics_view.cris_crash_id, - crash_injury_metrics_view.unkn_injry_count, - crash_injury_metrics_view.nonincap_injry_count, - crash_injury_metrics_view.poss_injry_count, - crash_injury_metrics_view.non_injry_count, - crash_injury_metrics_view.sus_serious_injry_count, - crash_injury_metrics_view.tot_injry_count, - crash_injury_metrics_view.fatality_count, - crash_injury_metrics_view.vz_fatality_count, - crash_injury_metrics_view.law_enf_fatality_count, - crash_injury_metrics_view.cris_fatality_count, - crash_injury_metrics_view.motor_vehicle_fatality_count, - crash_injury_metrics_view.motor_vehicle_sus_serious_injry_count, - crash_injury_metrics_view.motorcycle_fatality_count, - crash_injury_metrics_view.motorcycle_sus_serious_count, - crash_injury_metrics_view.bicycle_fatality_count, - crash_injury_metrics_view.bicycle_sus_serious_injry_count, - crash_injury_metrics_view.pedestrian_fatality_count, - crash_injury_metrics_view.pedestrian_sus_serious_injry_count, - crash_injury_metrics_view.micromobility_fatality_count, - crash_injury_metrics_view.micromobility_sus_serious_injry_count, - crash_injury_metrics_view.other_fatality_count, - crash_injury_metrics_view.other_sus_serious_injry_count, - crash_injury_metrics_view.crash_injry_sev_id, - crash_injury_metrics_view.years_of_life_lost, - crash_injury_metrics_view.est_comp_cost_crash_based, - crash_injury_metrics_view.est_total_person_comp_cost -FROM crash_injury_metrics_view -WHERE crashes.id = crash_injury_metrics_view.id -LIMIT 1) cimv ON TRUE -LEFT JOIN LATERAL (SELECT - unit_aggregates_1.id, - unit_aggregates_1.units_involved -FROM unit_aggregates unit_aggregates_1 -WHERE crashes.id = unit_aggregates_1.id -LIMIT 1) unit_aggregates ON TRUE +LEFT JOIN LATERAL ( + SELECT + crash_injury_metrics_view.id, + crash_injury_metrics_view.cris_crash_id, + crash_injury_metrics_view.unkn_injry_count, + crash_injury_metrics_view.nonincap_injry_count, + crash_injury_metrics_view.poss_injry_count, + crash_injury_metrics_view.non_injry_count, + crash_injury_metrics_view.sus_serious_injry_count, + crash_injury_metrics_view.tot_injry_count, + crash_injury_metrics_view.fatality_count, + crash_injury_metrics_view.vz_fatality_count, + crash_injury_metrics_view.law_enf_fatality_count, + crash_injury_metrics_view.cris_fatality_count, + crash_injury_metrics_view.motor_vehicle_fatality_count, + crash_injury_metrics_view.motor_vehicle_sus_serious_injry_count, + crash_injury_metrics_view.motorcycle_fatality_count, + crash_injury_metrics_view.motorcycle_sus_serious_count, + crash_injury_metrics_view.bicycle_fatality_count, + crash_injury_metrics_view.bicycle_sus_serious_injry_count, + crash_injury_metrics_view.pedestrian_fatality_count, + crash_injury_metrics_view.pedestrian_sus_serious_injry_count, + crash_injury_metrics_view.micromobility_fatality_count, + crash_injury_metrics_view.micromobility_sus_serious_injry_count, + crash_injury_metrics_view.other_fatality_count, + crash_injury_metrics_view.other_sus_serious_injry_count, + crash_injury_metrics_view.crash_injry_sev_id, + crash_injury_metrics_view.years_of_life_lost, + crash_injury_metrics_view.est_comp_cost_crash_based, + crash_injury_metrics_view.est_total_person_comp_cost + FROM crash_injury_metrics_view + WHERE crashes.id = crash_injury_metrics_view.id + LIMIT 1 +) cimv ON TRUE +LEFT JOIN LATERAL ( + SELECT + unit_aggregates_1.id, + unit_aggregates_1.units_involved + FROM unit_aggregates unit_aggregates_1 + WHERE crashes.id = unit_aggregates_1.id + LIMIT 1 +) unit_aggregates ON TRUE LEFT JOIN lookups.collsn ON crashes.fhe_collsn_id = collsn.id LEFT JOIN locations location ON crashes.location_id = location.location_id::text WHERE diff --git a/database/views/socrata_export_people_view.sql b/database/views/socrata_export_people_view.sql index a7673d194..fe89f612e 100644 --- a/database/views/socrata_export_people_view.sql +++ b/database/views/socrata_export_people_view.sql @@ -1,6 +1,7 @@ -- Most recent migration: database/migrations/default/1727451511064_init/up.sql -CREATE OR REPLACE VIEW socrata_export_people_view AS SELECT +CREATE OR REPLACE VIEW socrata_export_people_view AS +SELECT people.id, people.unit_id, crashes.id AS crash_pk, diff --git a/database/views/unit_injury_metrics_view.sql b/database/views/unit_injury_metrics_view.sql index 4676e53e9..6b71e1799 100644 --- a/database/views/unit_injury_metrics_view.sql +++ b/database/views/unit_injury_metrics_view.sql @@ -1,6 +1,7 @@ -- Most recent migration: database/migrations/default/1727451511064_init/up.sql -CREATE OR REPLACE VIEW unit_injury_metrics_view AS SELECT +CREATE OR REPLACE VIEW unit_injury_metrics_view AS +SELECT units.id, units.crash_pk, COALESCE(SUM(person_injury_metrics_view.unkn_injry), 0::bigint) AS unkn_injry_count, diff --git a/database/views/unit_types_involved_view.sql b/database/views/unit_types_involved_view.sql index 73d010dfa..326374a0d 100644 --- a/database/views/unit_types_involved_view.sql +++ b/database/views/unit_types_involved_view.sql @@ -36,7 +36,8 @@ crash_summaries AS ( SELECT distinct_unit_types.crash_pk, ARRAY_AGG( - distinct_unit_types.unit_type ORDER BY distinct_unit_types.unit_type + distinct_unit_types.unit_type + ORDER BY distinct_unit_types.unit_type ) AS unit_types_array, SUM(distinct_unit_types.unit_count) AS total_units FROM distinct_unit_types diff --git a/database/views/view_crash_narratives_ocr_todo.sql b/database/views/view_crash_narratives_ocr_todo.sql index 73649ff79..8d7f199fb 100644 --- a/database/views/view_crash_narratives_ocr_todo.sql +++ b/database/views/view_crash_narratives_ocr_todo.sql @@ -1,6 +1,7 @@ -- Most recent migration: database/migrations/default/1727451511064_init/up.sql -CREATE OR REPLACE VIEW view_crash_narratives_ocr_todo AS SELECT +CREATE OR REPLACE VIEW view_crash_narratives_ocr_todo AS +SELECT id, cris_crash_id FROM crashes diff --git a/editor/configs/locationColumns.ts b/editor/configs/locationColumns.ts index 10835b2b3..67b187116 100644 --- a/editor/configs/locationColumns.ts +++ b/editor/configs/locationColumns.ts @@ -1,6 +1,10 @@ import { ColDataCardDef } from "@/types/types"; import { Location } from "@/types/locations"; -import { formatArrayToString, formatDollars } from "@/utils/formatters"; +import { + formatArrayToString, + formatDollars, + formatYesNoString, +} from "@/utils/formatters"; export const locationColumns = { location_id: { @@ -25,4 +29,43 @@ export const locationColumns = { label: "ASMP Street level(s)", valueFormatter: formatArrayToString, }, + apd_sectors: { + path: "apd_sectors", + label: "APD sector(s)", + sortable: false, + defaultHidden: false, + valueFormatter: formatArrayToString, + }, + area_eng_areas: { + path: "area_eng_areas", + label: "Area engineer", + sortable: false, + valueFormatter: formatArrayToString, + }, + signal_eng_areas: { + path: "signal_eng_areas", + label: "Signal engineer", + sortable: false, + valueFormatter: formatArrayToString, + }, + council_districts: { + path: "council_districts", + label: "Council district(s)", + sortable: false, + valueFormatter: formatArrayToString, + }, + is_hin: { + path: "is_hin", + label: "High injury network", + sortable: true, + defaultHidden: false, + valueFormatter: formatYesNoString, + }, + is_signalized: { + path: "is_signalized", + label: "Signalized", + sortable: true, + defaultHidden: false, + valueFormatter: formatYesNoString, + }, } satisfies Record>; diff --git a/editor/configs/locationDataCard.ts b/editor/configs/locationDataCard.ts index 5e6a5c74c..58dec830a 100644 --- a/editor/configs/locationDataCard.ts +++ b/editor/configs/locationDataCard.ts @@ -1,9 +1,14 @@ import { locationColumns } from "./locationColumns"; export const locationCardColumns = [ - locationColumns.location_id, locationColumns.cr3_crash_count, locationColumns.non_cr3_crash_count, locationColumns.total_est_comp_cost, locationColumns.street_level, + locationColumns.area_eng_areas, + locationColumns.signal_eng_areas, + locationColumns.is_signalized, + locationColumns.council_districts, + locationColumns.is_hin, + locationColumns.apd_sectors ]; diff --git a/editor/configs/locationsListViewColumns.tsx b/editor/configs/locationsListViewColumns.tsx index a06367acf..eac88d83d 100644 --- a/editor/configs/locationsListViewColumns.tsx +++ b/editor/configs/locationsListViewColumns.tsx @@ -1,6 +1,7 @@ import Link from "next/link"; import { ColDataCardDef } from "@/types/types"; import { LocationsListRow } from "@/types/locationsList"; +import { locationColumns } from "@/configs/locationColumns"; export const locationsListViewColumns: ColDataCardDef[] = [ { @@ -17,6 +18,12 @@ export const locationsListViewColumns: ColDataCardDef[] = [ path: "location_name", label: "Location", sortable: true, + style: { + maxWidth: "30rem", + overflow: "hidden", + textOverflow: "ellipsis", + whiteSpace: "nowrap", + }, }, { path: "cr3_crash_count", @@ -28,4 +35,11 @@ export const locationsListViewColumns: ColDataCardDef[] = [ label: "Non-CR3 crashes", sortable: true, }, + locationColumns.area_eng_areas, + locationColumns.signal_eng_areas, + locationColumns.is_signalized, + locationColumns.council_districts, + { ...locationColumns.street_level, defaultHidden: true }, + { ...locationColumns.apd_sectors, defaultHidden: true }, + { ...locationColumns.is_hin, defaultHidden: true }, ]; diff --git a/editor/configs/locationsListViewTable.ts b/editor/configs/locationsListViewTable.ts index 058adcbc1..92bcb6f7b 100644 --- a/editor/configs/locationsListViewTable.ts +++ b/editor/configs/locationsListViewTable.ts @@ -2,6 +2,271 @@ import { QueryConfig, FilterGroup } from "@/types/queryBuilder"; import { DEFAULT_QUERY_LIMIT } from "@/utils/constants"; const locationsListViewFiltercards: FilterGroup[] = [ + // area_eng_areas + { + id: "area_eng_areas_filter_card", + label: "Area engineer", + groupOperator: "_or", + filterGroups: [ + { + id: "area_eng_areas_north", + label: "North", + groupOperator: "_and", + enabled: false, + inverted: false, + filters: [ + { + id: "area_eng_areas_north", + column: "area_eng_areas", + operator: "_contains", + value: ["NORTH"], + }, + ], + }, + { + id: "area_eng_areas_central", + label: "Central", + groupOperator: "_and", + enabled: false, + inverted: false, + filters: [ + { + id: "area_eng_areas_central", + column: "area_eng_areas", + operator: "_contains", + value: ["CENTRAL"], + }, + ], + }, + { + id: "area_eng_areas_south", + label: "South", + groupOperator: "_and", + enabled: false, + inverted: false, + filters: [ + { + id: "area_eng_areas_south", + column: "area_eng_areas", + operator: "_contains", + value: ["SOUTH"], + }, + ], + }, + ], + }, + // signal_eng_areas + { + id: "signal_eng_areas_filter_card", + label: "Signal engineer", + groupOperator: "_or", + filterGroups: [ + { + id: "signal_eng_areas_northeast", + label: "Northeast", + groupOperator: "_and", + enabled: false, + inverted: false, + filters: [ + { + id: "signal_eng_areas_northeast", + column: "signal_eng_areas", + operator: "_contains", + value: ["NORTHEAST"], + }, + ], + }, + { + id: "signal_eng_areas_northwest", + label: "Northwest", + groupOperator: "_and", + enabled: false, + inverted: false, + filters: [ + { + id: "signal_eng_areas_northwest", + column: "signal_eng_areas", + operator: "_contains", + value: ["NORTHWEST"], + }, + ], + }, + { + id: "signal_eng_areas_central", + label: "Central", + groupOperator: "_and", + enabled: false, + inverted: false, + filters: [ + { + id: "signal_eng_areas_central", + column: "signal_eng_areas", + operator: "_contains", + value: ["CENTRAL"], + }, + ], + }, + { + id: "signal_eng_areas_soutb", + label: "South", + groupOperator: "_and", + enabled: false, + inverted: false, + filters: [ + { + id: "signal_eng_areas_south", + column: "signal_eng_areas", + operator: "_contains", + value: ["SOUTH"], + }, + ], + }, + ], + }, + // is_signalized + { + id: "is_signalized", + label: "Signalized intersection", + groupOperator: "_or", + filterGroups: [ + { + id: "is_signalized_yes", + label: "Yes", + groupOperator: "_and", + enabled: false, + inverted: false, + filters: [ + { + id: "is_signalized_yes", + column: "is_signalized", + operator: "_eq", + value: true, + }, + ], + }, + { + id: "is_signalized_no", + label: "No", + groupOperator: "_and", + enabled: false, + inverted: false, + filters: [ + { + id: "is_signalized_no", + column: "is_signalized", + operator: "_neq", + value: true, + }, + ], + }, + ], + }, + // street levels + { + id: "street_levels_filter_card", + label: "Street level", + groupOperator: "_or", + filterGroups: [ + { + id: "street_levels_1", + label: "1 - Local streets", + groupOperator: "_and", + enabled: false, + inverted: false, + filters: [ + { + id: "street_levels_1", + column: "street_levels", + operator: "_contains", + value: [1], + }, + ], + }, + { + id: "street_levels_2", + label: "2 - Collector streets", + groupOperator: "_and", + enabled: false, + inverted: false, + filters: [ + { + id: "street_levels_2", + column: "street_levels", + operator: "_contains", + value: [2], + }, + ], + }, + { + id: "street_levels_3", + label: "3 - Minor or major arterials", + groupOperator: "_and", + enabled: false, + inverted: false, + filters: [ + { + id: "street_levels_3", + column: "street_levels", + operator: "_contains", + value: [3], + }, + ], + }, + { + id: "street_levels_4", + label: "4 - Major arterials or frontage roads", + groupOperator: "_and", + enabled: false, + inverted: false, + filters: [ + { + id: "street_levels_4", + column: "street_levels", + operator: "_contains", + value: [1], + }, + ], + }, + { + id: "street_levels_5", + label: "5 - Highways and freeways ", + groupOperator: "_and", + enabled: false, + inverted: false, + filters: [ + { + id: "street_levels_5", + column: "street_levels", + operator: "_contains", + value: [5], + }, + ], + }, + ], + }, + { + id: "high_inj_net", + label: "High injury network", + groupOperator: "_and", + filterGroups: [ + { + id: "high_inj_net_true", + label: "High injury network locations only", + groupOperator: "_and", + enabled: false, + inverted: false, + filters: [ + { + id: "high_inj_net_true", + column: "is_hin", + operator: "_eq", + value: true, + }, + ], + }, + ], + }, + // jurisdiction { id: "geography_filter_card", label: "Jurisdiction", @@ -16,9 +281,9 @@ const locationsListViewFiltercards: FilterGroup[] = [ filters: [ { id: "in_austin_full_purpose", - column: "council_district", - operator: "_gt", - value: 0, + column: "council_districts", + operator: "_is_null", + value: false, }, ], }, @@ -39,10 +304,168 @@ const locationsListViewFiltercards: FilterGroup[] = [ }, ], }, + // council district + { + id: "council_districts_filter_card", + label: "Council district", + groupOperator: "_or", + filterGroups: [ + { + id: "council_districts_1", + label: "1", + groupOperator: "_and", + enabled: false, + inverted: false, + filters: [ + { + id: "council_districts_1", + column: "council_districts", + operator: "_contains", + value: [1], + }, + ], + }, + { + id: "council_districts_2", + label: "2", + groupOperator: "_and", + enabled: false, + inverted: false, + filters: [ + { + id: "council_districts_2", + column: "council_districts", + operator: "_contains", + value: [2], + }, + ], + }, + { + id: "council_districts_3", + label: "3", + groupOperator: "_and", + enabled: false, + inverted: false, + filters: [ + { + id: "council_districts_3", + column: "council_districts", + operator: "_contains", + value: [3], + }, + ], + }, + { + id: "council_districts_4", + label: "4", + groupOperator: "_and", + enabled: false, + inverted: false, + filters: [ + { + id: "council_districts_4", + column: "council_districts", + operator: "_contains", + value: [1], + }, + ], + }, + { + id: "council_districts_5", + label: "5", + groupOperator: "_and", + enabled: false, + inverted: false, + filters: [ + { + id: "council_districts_5", + column: "council_districts", + operator: "_contains", + value: [5], + }, + ], + }, +{ + id: "council_districts_6", + label: "6", + groupOperator: "_and", + enabled: false, + inverted: false, + filters: [ + { + id: "council_districts_6", + column: "council_districts", + operator: "_contains", + value: [6], + }, + ], + }, +{ + id: "council_districts_7", + label: "7", + groupOperator: "_and", + enabled: false, + inverted: false, + filters: [ + { + id: "council_districts_7", + column: "council_districts", + operator: "_contains", + value: [7], + }, + ], + }, +{ + id: "council_districts_8", + label: "8", + groupOperator: "_and", + enabled: false, + inverted: false, + filters: [ + { + id: "council_districts_5", + column: "council_districts", + operator: "_contains", + value: [8], + }, + ], + }, +{ + id: "council_districts_9", + label: "9", + groupOperator: "_and", + enabled: false, + inverted: false, + filters: [ + { + id: "council_districts_5", + column: "council_districts", + operator: "_contains", + value: [9], + }, + ], + }, +{ + id: "council_districts_10", + label: "10", + groupOperator: "_and", + enabled: false, + inverted: false, + filters: [ + { + id: "council_districts_10", + column: "council_districts", + operator: "_contains", + value: [10], + }, + ], + }, + ], + }, ]; export const locationsListViewQueryConfig: QueryConfig = { - _version: 2, + _version: 3, exportable: true, exportFilename: "locations", tableName: "locations_list_view", diff --git a/editor/queries/location.ts b/editor/queries/location.ts index 4e0c72dcb..206f1edf7 100644 --- a/editor/queries/location.ts +++ b/editor/queries/location.ts @@ -14,6 +14,12 @@ export const GET_LOCATION = gql` non_cr3_crash_count total_est_comp_cost } + apd_sectors + area_eng_areas + signal_eng_areas + council_districts + is_signalized + is_hin location_notes( where: { is_deleted: { _eq: false } } order_by: { created_at: asc } diff --git a/editor/schema/queryBuilder.ts b/editor/schema/queryBuilder.ts index 0afc4fe02..eb0acd6ce 100644 --- a/editor/schema/queryBuilder.ts +++ b/editor/schema/queryBuilder.ts @@ -12,6 +12,7 @@ const FilterValue = z.union([ z.number(), z.boolean(), z.array(z.number()), + z.array(z.string()), ]); const Filter = z.object({ @@ -27,6 +28,7 @@ const Filter = z.object({ "_ilike", "_in", "_nin", + "_contains", ]), value: FilterValue, column: z.string(), diff --git a/editor/styles/global.scss b/editor/styles/global.scss index cd6531036..ccbda1876 100644 --- a/editor/styles/global.scss +++ b/editor/styles/global.scss @@ -412,7 +412,6 @@ to make sure it renders on top is a common fix */ } @keyframes pulse { - 0%, 100% { opacity: 1; @@ -423,4 +422,8 @@ to make sure it renders on top is a common fix */ } } +.dropdown-menu { + box-shadow: 0 8px 24px rgba(0, 0, 0, 0.12); +} + @import "bootstrap/scss/bootstrap.scss"; diff --git a/editor/types/locations.ts b/editor/types/locations.ts index 2453ab052..9449d24cb 100644 --- a/editor/types/locations.ts +++ b/editor/types/locations.ts @@ -8,5 +8,12 @@ export type Location = { geometry: MultiPolygon | null; street_levels: string[] | null; locations_list_view: LocationsListRow | null; - location_notes: LocationNote[] + location_notes: LocationNote[]; + apd_sectors?: string[]; + area_eng_areas?: string[]; + council_districts?: number[]; + is_hin?: boolean; + is_signalized?: boolean; + location_group?: number; + signal_eng_areas?: string[]; }; diff --git a/editor/types/locationsList.ts b/editor/types/locationsList.ts index d29bf9d6f..a1ded9919 100644 --- a/editor/types/locationsList.ts +++ b/editor/types/locationsList.ts @@ -1,7 +1,15 @@ export type LocationsListRow = { - location_id: string; - location_name: string | null; - cr3_crash_count: number | null; - non_cr3_crash_count: number | null; - total_est_comp_cost: number | null; + location_id?: string; + location_name?: string | null; + cr3_crash_count?: number | null; + non_cr3_crash_count?: number | null; + total_est_comp_cost?: number | null; + apd_sectors?: string[]; + area_eng_areas?: string[]; + council_districts?: number[]; + is_hin?: boolean; + is_signalized?: boolean; + location_group?: number; + signal_eng_areas?: string[]; + street_levels?: number[]; }; diff --git a/editor/types/queryBuilder.ts b/editor/types/queryBuilder.ts index 0f06cb507..40b5e5f1d 100644 --- a/editor/types/queryBuilder.ts +++ b/editor/types/queryBuilder.ts @@ -5,7 +5,7 @@ import { AllowedPageSize, ExportPageSize } from "@/utils/constants"; * The types we currently support as filter values * */ -export type FilterValue = string | number | boolean | number[]; +export type FilterValue = string | number | boolean | number[] | string[]; /** * Interface for a single filter that can be @@ -30,7 +30,8 @@ export interface Filter { | "_is_null" | "_ilike" | "_in" - | "_nin"; + | "_nin" + | "_contains"; /** * The filter value */ diff --git a/editor/utils/formHelpers.ts b/editor/utils/formHelpers.ts index eb79e5d73..eb97ebbb0 100644 --- a/editor/utils/formHelpers.ts +++ b/editor/utils/formHelpers.ts @@ -1,5 +1,6 @@ import { ReactNode } from "react"; import { ColDataCardDef, InputType } from "@/types/types"; +import { formatYesNoString } from "@/utils/formatters"; /** * Retrieve a value from an object given a dot-noted path string. @@ -83,15 +84,6 @@ const renderValueToString = (value: unknown): string => { return String(value); }; -/** - * Convert truthy values to 'Yes', `null` and `undefined` to "", and - * any other falsey value to "No" - */ -const renderYesNoString = (value: unknown): string => { - if (value === null || value === undefined) return ""; - return value ? "Yes" : "No"; -}; - /** * Get a columns's value from a record * @param record - the record object @@ -143,9 +135,8 @@ export const renderColumnValue = >( ); } - // todo: this should probably be a valueFormatter? 😵‍💫 if (column.inputType === "yes_no") { - return renderYesNoString(record[column.path]); + return formatYesNoString(record[column.path]); } return renderValueToString(getRecordValue(record, column)); diff --git a/editor/utils/formatters.ts b/editor/utils/formatters.ts index e09c2e5c5..b7d829fb1 100644 --- a/editor/utils/formatters.ts +++ b/editor/utils/formatters.ts @@ -130,3 +130,13 @@ export const formatUserNameFromEmail = (value: unknown): string => { return formatted; }; + +/** + * Convert truthy values to 'Yes', `null` and `undefined` to "", and + * any other falsey value to "No" + */ +export const formatYesNoString = (value: unknown): string => { + if (value === null || value === undefined) return ""; + return value ? "Yes" : "No"; +}; + diff --git a/editor/utils/queryBuilder.ts b/editor/utils/queryBuilder.ts index cb43fcd1b..bdb70a56a 100644 --- a/editor/utils/queryBuilder.ts +++ b/editor/utils/queryBuilder.ts @@ -42,11 +42,20 @@ const quoteWrapAndEscape = (value: string): FilterValue => `"${value.replaceAll('"', '\\"')}"`; /** - * Create a string representation of an array of numbers - * [1, 2] => "[1, 2]" + * Create a string representation of an array of numbers or strings. + * [1, 2] => "[1, 2]" + * ["1", "2"] => '["1", "2"]' */ -const arrayToStringRep = (arr: number[]): string => { - return `[${arr}]`; +const arrayToStringRep = (arr: number[] | string[]): string => { + if (arr.length === 0) return "[]"; + let items: number[] | string[] = []; + if (typeof arr[0] === "string") { + // add literal quotes to array values + items = arr.map((val) => `"${val}"`); + } else { + items = arr; + } + return `[${items.join(", ")}]`; }; /**