From cbe46e7d1c624c7741429144c8fe5fed49279039 Mon Sep 17 00:00:00 2001 From: John Clary Date: Thu, 19 Feb 2026 15:00:10 -0500 Subject: [PATCH 01/18] adds more columns to locations list vie --- .../tables/public_locations_list_view.yaml | 24 +++++++- .../down.sql | 57 +++++++++++++++++++ .../up.sql | 40 +++++++++++++ editor/configs/locationsListViewColumns.tsx | 40 +++++++++++++ editor/configs/locationsListViewTable.ts | 8 +-- editor/types/locationsList.ts | 18 ++++-- 6 files changed, 175 insertions(+), 12 deletions(-) create mode 100644 database/migrations/default/1771530231604_locations_list_view_more_cols/down.sql create mode 100644 database/migrations/default/1771530231604_locations_list_view_more_cols/up.sql 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..69671b6b3 --- /dev/null +++ b/database/migrations/default/1771530231604_locations_list_view_more_cols/down.sql @@ -0,0 +1,57 @@ +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_district, + locations.location_group, + 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/editor/configs/locationsListViewColumns.tsx b/editor/configs/locationsListViewColumns.tsx index a06367acf..d94efd6b0 100644 --- a/editor/configs/locationsListViewColumns.tsx +++ b/editor/configs/locationsListViewColumns.tsx @@ -28,4 +28,44 @@ export const locationsListViewColumns: ColDataCardDef[] = [ label: "Non-CR3 crashes", sortable: true, }, + { + path: "apd_sectors", + label: "APD sector(s)", + sortable: false, + }, + { + path: "area_eng_areas", + label: "Area engineer area(s)", + sortable: false, + }, + { + path: "signal_eng_areas", + label: "Signal engineer area(s)", + sortable: false, + }, + { + path: "council_districts", + label: "Council district(s)", + sortable: false, + }, + { + path: "is_hin", + label: "High injury network location", + sortable: true, + }, + { + path: "is_signalized", + label: "Signalized", + sortable: true, + }, + { + path: "location_group", + label: "Location group", + sortable: true, + }, + { + path: "street_levels", + label: "Street levels", + sortable: false, + }, ]; diff --git a/editor/configs/locationsListViewTable.ts b/editor/configs/locationsListViewTable.ts index 058adcbc1..1cd2ab10b 100644 --- a/editor/configs/locationsListViewTable.ts +++ b/editor/configs/locationsListViewTable.ts @@ -16,9 +16,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, }, ], }, @@ -42,7 +42,7 @@ const locationsListViewFiltercards: FilterGroup[] = [ ]; export const locationsListViewQueryConfig: QueryConfig = { - _version: 2, + _version: 3, exportable: true, exportFilename: "locations", tableName: "locations_list_view", 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[]; }; From 310bdd3347481772fbb715d1cc64c07dd627eb25 Mon Sep 17 00:00:00 2001 From: John Clary Date: Thu, 19 Feb 2026 17:14:36 -0500 Subject: [PATCH 02/18] adds more filter cards to locations list --- editor/configs/locationsListViewColumns.tsx | 15 +- editor/configs/locationsListViewTable.ts | 164 +++++++++++++++++++- editor/schema/queryBuilder.ts | 2 + editor/types/queryBuilder.ts | 5 +- 4 files changed, 181 insertions(+), 5 deletions(-) diff --git a/editor/configs/locationsListViewColumns.tsx b/editor/configs/locationsListViewColumns.tsx index d94efd6b0..a6766db3f 100644 --- a/editor/configs/locationsListViewColumns.tsx +++ b/editor/configs/locationsListViewColumns.tsx @@ -17,6 +17,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", @@ -32,15 +38,16 @@ export const locationsListViewColumns: ColDataCardDef[] = [ path: "apd_sectors", label: "APD sector(s)", sortable: false, + defaultHidden: true, }, { path: "area_eng_areas", - label: "Area engineer area(s)", + label: "Area engineer", sortable: false, }, { path: "signal_eng_areas", - label: "Signal engineer area(s)", + label: "Signal engineer", sortable: false, }, { @@ -52,20 +59,24 @@ export const locationsListViewColumns: ColDataCardDef[] = [ path: "is_hin", label: "High injury network location", sortable: true, + defaultHidden: true, }, { path: "is_signalized", label: "Signalized", sortable: true, + defaultHidden: true, }, { path: "location_group", label: "Location group", sortable: true, + defaultHidden: true, }, { path: "street_levels", label: "Street levels", sortable: false, + defaultHidden: true, }, ]; diff --git a/editor/configs/locationsListViewTable.ts b/editor/configs/locationsListViewTable.ts index 1cd2ab10b..1dbfb1bcc 100644 --- a/editor/configs/locationsListViewTable.ts +++ b/editor/configs/locationsListViewTable.ts @@ -1,7 +1,169 @@ import { QueryConfig, FilterGroup } from "@/types/queryBuilder"; import { DEFAULT_QUERY_LIMIT } from "@/utils/constants"; +// -- NORTH, CENTRAL, SOUTH +// {CENTRAL,NORTHEAST,NORTHWEST}{SOUTH} + 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, + }, + ], + }, + ], + }, { id: "geography_filter_card", label: "Jurisdiction", @@ -42,7 +204,7 @@ const locationsListViewFiltercards: FilterGroup[] = [ ]; export const locationsListViewQueryConfig: QueryConfig = { - _version: 3, + _version: 4, exportable: true, exportFilename: "locations", tableName: "locations_list_view", 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/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 */ From 305365a098bbb106844b63c71aaca8ba058026fe Mon Sep 17 00:00:00 2001 From: John Clary Date: Fri, 20 Feb 2026 12:44:56 -0500 Subject: [PATCH 03/18] wip --- editor/configs/locationsListViewColumns.tsx | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/editor/configs/locationsListViewColumns.tsx b/editor/configs/locationsListViewColumns.tsx index a6766db3f..f762a79d5 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 { formatArrayToString } from "@/utils/formatters"; export const locationsListViewColumns: ColDataCardDef[] = [ { @@ -39,21 +40,25 @@ export const locationsListViewColumns: ColDataCardDef[] = [ label: "APD sector(s)", sortable: false, defaultHidden: true, + valueFormatter: formatArrayToString, }, { path: "area_eng_areas", label: "Area engineer", sortable: false, + valueFormatter: formatArrayToString, }, { path: "signal_eng_areas", label: "Signal engineer", sortable: false, + valueFormatter: formatArrayToString, }, { path: "council_districts", label: "Council district(s)", sortable: false, + valueFormatter: formatArrayToString, }, { path: "is_hin", @@ -78,5 +83,7 @@ export const locationsListViewColumns: ColDataCardDef[] = [ label: "Street levels", sortable: false, defaultHidden: true, + valueFormatter: formatArrayToString, }, + // todo: move these columns to locationColumns and import them here? and show them on the location details page ]; From 623fafd9343a27e1a03674d170a4e67dc98cb619 Mon Sep 17 00:00:00 2001 From: John Clary Date: Fri, 20 Feb 2026 13:38:43 -0500 Subject: [PATCH 04/18] add dropdown menu shadow --- editor/styles/global.scss | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) 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"; From 4d2f61dcad41109540e7d0ed9253fecb657ecda1 Mon Sep 17 00:00:00 2001 From: John Clary Date: Fri, 20 Feb 2026 14:03:23 -0500 Subject: [PATCH 05/18] add street level filters and use yes/no formatter --- editor/configs/locationsListViewColumns.tsx | 10 +-- editor/configs/locationsListViewTable.ts | 84 +++++++++++++++++++++ editor/utils/formHelpers.ts | 12 +-- editor/utils/formatters.ts | 10 +++ 4 files changed, 99 insertions(+), 17 deletions(-) diff --git a/editor/configs/locationsListViewColumns.tsx b/editor/configs/locationsListViewColumns.tsx index f762a79d5..64594e043 100644 --- a/editor/configs/locationsListViewColumns.tsx +++ b/editor/configs/locationsListViewColumns.tsx @@ -1,7 +1,7 @@ import Link from "next/link"; import { ColDataCardDef } from "@/types/types"; import { LocationsListRow } from "@/types/locationsList"; -import { formatArrayToString } from "@/utils/formatters"; +import { formatArrayToString, formatYesNoString } from "@/utils/formatters"; export const locationsListViewColumns: ColDataCardDef[] = [ { @@ -65,18 +65,14 @@ export const locationsListViewColumns: ColDataCardDef[] = [ label: "High injury network location", sortable: true, defaultHidden: true, + valueFormatter: formatYesNoString, }, { path: "is_signalized", label: "Signalized", sortable: true, defaultHidden: true, - }, - { - path: "location_group", - label: "Location group", - sortable: true, - defaultHidden: true, + valueFormatter: formatYesNoString, }, { path: "street_levels", diff --git a/editor/configs/locationsListViewTable.ts b/editor/configs/locationsListViewTable.ts index 1dbfb1bcc..b2107939a 100644 --- a/editor/configs/locationsListViewTable.ts +++ b/editor/configs/locationsListViewTable.ts @@ -164,6 +164,90 @@ const locationsListViewFiltercards: FilterGroup[] = [ }, ], }, + // street levels + { + id: "street_levels_filter_card", + label: "Street level", + groupOperator: "_or", + filterGroups: [ + { + id: "street_leavels_1", + label: "1", + groupOperator: "_and", + enabled: false, + inverted: false, + filters: [ + { + id: "street_leavels_1", + column: "street_levels", + operator: "_contains", + value: [1], + }, + ], + }, + { + id: "street_leavels_2", + label: "2", + groupOperator: "_and", + enabled: false, + inverted: false, + filters: [ + { + id: "street_leavels_2", + column: "street_levels", + operator: "_contains", + value: [2], + }, + ], + }, + { + id: "street_leavels_3", + label: "3", + groupOperator: "_and", + enabled: false, + inverted: false, + filters: [ + { + id: "street_leavels_3", + column: "street_levels", + operator: "_contains", + value: [3], + }, + ], + }, + { + id: "street_leavels_4", + label: "4", + groupOperator: "_and", + enabled: false, + inverted: false, + filters: [ + { + id: "street_leavels_4", + column: "street_levels", + operator: "_contains", + value: [1], + }, + ], + }, + { + id: "street_leavels_5", + label: "5", + groupOperator: "_and", + enabled: false, + inverted: false, + filters: [ + { + id: "street_leavels_5", + column: "street_levels", + operator: "_contains", + value: [5], + }, + ], + }, + ], + }, + // jurisdiction { id: "geography_filter_card", label: "Jurisdiction", diff --git a/editor/utils/formHelpers.ts b/editor/utils/formHelpers.ts index eb79e5d73..471934cf9 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 @@ -145,7 +137,7 @@ 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"; +}; + From d50a2e5ab9bbaf82fab45ceba2c41d14e1ec3255 Mon Sep 17 00:00:00 2001 From: John Clary Date: Mon, 23 Feb 2026 11:17:23 -0500 Subject: [PATCH 06/18] add street level labels --- editor/configs/locationsListViewTable.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/editor/configs/locationsListViewTable.ts b/editor/configs/locationsListViewTable.ts index b2107939a..868a0d2d1 100644 --- a/editor/configs/locationsListViewTable.ts +++ b/editor/configs/locationsListViewTable.ts @@ -172,7 +172,7 @@ const locationsListViewFiltercards: FilterGroup[] = [ filterGroups: [ { id: "street_leavels_1", - label: "1", + label: "1 - Local streets", groupOperator: "_and", enabled: false, inverted: false, @@ -187,7 +187,7 @@ const locationsListViewFiltercards: FilterGroup[] = [ }, { id: "street_leavels_2", - label: "2", + label: "2 - Collector streets", groupOperator: "_and", enabled: false, inverted: false, @@ -202,7 +202,7 @@ const locationsListViewFiltercards: FilterGroup[] = [ }, { id: "street_leavels_3", - label: "3", + label: "3 - Minor or major arterials", groupOperator: "_and", enabled: false, inverted: false, @@ -217,7 +217,7 @@ const locationsListViewFiltercards: FilterGroup[] = [ }, { id: "street_leavels_4", - label: "4", + label: "4 - Major arterials or frontage roads", groupOperator: "_and", enabled: false, inverted: false, @@ -232,7 +232,7 @@ const locationsListViewFiltercards: FilterGroup[] = [ }, { id: "street_leavels_5", - label: "5", + label: "5 - Highways and freeways ", groupOperator: "_and", enabled: false, inverted: false, From 86a18d61adc570c0b27d59c05d6574a80b1d82e5 Mon Sep 17 00:00:00 2001 From: John Clary Date: Mon, 23 Feb 2026 11:35:28 -0500 Subject: [PATCH 07/18] add high inj net filter --- editor/configs/locationsListViewTable.ts | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/editor/configs/locationsListViewTable.ts b/editor/configs/locationsListViewTable.ts index 868a0d2d1..57d4a3f7a 100644 --- a/editor/configs/locationsListViewTable.ts +++ b/editor/configs/locationsListViewTable.ts @@ -246,6 +246,28 @@ const locationsListViewFiltercards: FilterGroup[] = [ ], }, ], + }, + { + 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 { From 1884c2b7542908b538885025022c29401cc8d89c Mon Sep 17 00:00:00 2001 From: Vision Zero View Bot Date: Mon, 23 Feb 2026 16:36:59 +0000 Subject: [PATCH 08/18] =?UTF-8?q?=F0=9F=A4=96=20Export=20database=20views?= =?UTF-8?q?=20for=20john/26710-loc-list-filters?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- database/views/locations_list_view.sql | 8 +++++++- database/views/materialized/location_crashes_view.sql | 4 +++- 2 files changed, 10 insertions(+), 2 deletions(-) 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..2434aa691 100644 --- a/database/views/materialized/location_crashes_view.sql +++ b/database/views/materialized/location_crashes_view.sql @@ -1,10 +1,11 @@ --- 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 crashes.record_locator, crashes.cris_crash_id, + crashes.id AS crash_pk, 'CR3'::text AS type, crashes.location_id, crashes.case_id, @@ -87,6 +88,7 @@ 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, From 5c4a28945af90c99335faceb7474d121b2253934 Mon Sep 17 00:00:00 2001 From: John Clary Date: Mon, 23 Feb 2026 11:41:12 -0500 Subject: [PATCH 09/18] remove a todo --- editor/utils/formHelpers.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/editor/utils/formHelpers.ts b/editor/utils/formHelpers.ts index 471934cf9..eb97ebbb0 100644 --- a/editor/utils/formHelpers.ts +++ b/editor/utils/formHelpers.ts @@ -135,7 +135,6 @@ export const renderColumnValue = >( ); } - // todo: this should probably be a valueFormatter? 😵‍💫 if (column.inputType === "yes_no") { return formatYesNoString(record[column.path]); } From 66b2a795c5fc57c7abf172349731aa85a3371ab0 Mon Sep 17 00:00:00 2001 From: John Clary Date: Mon, 23 Feb 2026 11:41:18 -0500 Subject: [PATCH 10/18] remove a comment --- editor/configs/locationsListViewTable.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/editor/configs/locationsListViewTable.ts b/editor/configs/locationsListViewTable.ts index 57d4a3f7a..f04b4a2ff 100644 --- a/editor/configs/locationsListViewTable.ts +++ b/editor/configs/locationsListViewTable.ts @@ -1,9 +1,6 @@ import { QueryConfig, FilterGroup } from "@/types/queryBuilder"; import { DEFAULT_QUERY_LIMIT } from "@/utils/constants"; -// -- NORTH, CENTRAL, SOUTH -// {CENTRAL,NORTHEAST,NORTHWEST}{SOUTH} - const locationsListViewFiltercards: FilterGroup[] = [ // area_eng_areas { @@ -247,7 +244,7 @@ const locationsListViewFiltercards: FilterGroup[] = [ }, ], }, - { + { id: "high_inj_net", label: "High injury network", groupOperator: "_and", From af6a0d62ebf886492121c22a18efe7a8a1f673fc Mon Sep 17 00:00:00 2001 From: John Clary Date: Thu, 5 Mar 2026 13:38:24 -0500 Subject: [PATCH 11/18] move col defs around and add to deets card --- editor/configs/locationColumns.ts | 45 ++++++++++++++++- editor/configs/locationDataCard.ts | 5 ++ editor/configs/locationsListViewColumns.tsx | 56 +++------------------ editor/configs/locationsListViewTable.ts | 2 +- editor/queries/location.ts | 6 +++ editor/types/locations.ts | 9 +++- 6 files changed, 72 insertions(+), 51 deletions(-) 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..ceff54495 100644 --- a/editor/configs/locationDataCard.ts +++ b/editor/configs/locationDataCard.ts @@ -6,4 +6,9 @@ export const locationCardColumns = [ 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 ]; diff --git a/editor/configs/locationsListViewColumns.tsx b/editor/configs/locationsListViewColumns.tsx index 64594e043..eac88d83d 100644 --- a/editor/configs/locationsListViewColumns.tsx +++ b/editor/configs/locationsListViewColumns.tsx @@ -1,7 +1,7 @@ import Link from "next/link"; import { ColDataCardDef } from "@/types/types"; import { LocationsListRow } from "@/types/locationsList"; -import { formatArrayToString, formatYesNoString } from "@/utils/formatters"; +import { locationColumns } from "@/configs/locationColumns"; export const locationsListViewColumns: ColDataCardDef[] = [ { @@ -35,51 +35,11 @@ export const locationsListViewColumns: ColDataCardDef[] = [ label: "Non-CR3 crashes", sortable: true, }, - { - path: "apd_sectors", - label: "APD sector(s)", - sortable: false, - defaultHidden: true, - valueFormatter: formatArrayToString, - }, - { - path: "area_eng_areas", - label: "Area engineer", - sortable: false, - valueFormatter: formatArrayToString, - }, - { - path: "signal_eng_areas", - label: "Signal engineer", - sortable: false, - valueFormatter: formatArrayToString, - }, - { - path: "council_districts", - label: "Council district(s)", - sortable: false, - valueFormatter: formatArrayToString, - }, - { - path: "is_hin", - label: "High injury network location", - sortable: true, - defaultHidden: true, - valueFormatter: formatYesNoString, - }, - { - path: "is_signalized", - label: "Signalized", - sortable: true, - defaultHidden: true, - valueFormatter: formatYesNoString, - }, - { - path: "street_levels", - label: "Street levels", - sortable: false, - defaultHidden: true, - valueFormatter: formatArrayToString, - }, - // todo: move these columns to locationColumns and import them here? and show them on the location details page + 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 f04b4a2ff..1b2b10c88 100644 --- a/editor/configs/locationsListViewTable.ts +++ b/editor/configs/locationsListViewTable.ts @@ -307,7 +307,7 @@ const locationsListViewFiltercards: FilterGroup[] = [ ]; export const locationsListViewQueryConfig: QueryConfig = { - _version: 4, + _version: 6, 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/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[]; }; From 294a9aca42d1d3eb21eaf0723287ddc2f626c84f Mon Sep 17 00:00:00 2001 From: John Clary Date: Thu, 5 Mar 2026 13:49:24 -0500 Subject: [PATCH 12/18] add council distr filter --- editor/configs/locationsListViewTable.ts | 178 +++++++++++++++++++++-- 1 file changed, 168 insertions(+), 10 deletions(-) diff --git a/editor/configs/locationsListViewTable.ts b/editor/configs/locationsListViewTable.ts index 1b2b10c88..4285de22a 100644 --- a/editor/configs/locationsListViewTable.ts +++ b/editor/configs/locationsListViewTable.ts @@ -168,14 +168,14 @@ const locationsListViewFiltercards: FilterGroup[] = [ groupOperator: "_or", filterGroups: [ { - id: "street_leavels_1", + id: "street_levels_1", label: "1 - Local streets", groupOperator: "_and", enabled: false, inverted: false, filters: [ { - id: "street_leavels_1", + id: "street_levels_1", column: "street_levels", operator: "_contains", value: [1], @@ -183,14 +183,14 @@ const locationsListViewFiltercards: FilterGroup[] = [ ], }, { - id: "street_leavels_2", + id: "street_levels_2", label: "2 - Collector streets", groupOperator: "_and", enabled: false, inverted: false, filters: [ { - id: "street_leavels_2", + id: "street_levels_2", column: "street_levels", operator: "_contains", value: [2], @@ -198,14 +198,14 @@ const locationsListViewFiltercards: FilterGroup[] = [ ], }, { - id: "street_leavels_3", + id: "street_levels_3", label: "3 - Minor or major arterials", groupOperator: "_and", enabled: false, inverted: false, filters: [ { - id: "street_leavels_3", + id: "street_levels_3", column: "street_levels", operator: "_contains", value: [3], @@ -213,14 +213,14 @@ const locationsListViewFiltercards: FilterGroup[] = [ ], }, { - id: "street_leavels_4", + id: "street_levels_4", label: "4 - Major arterials or frontage roads", groupOperator: "_and", enabled: false, inverted: false, filters: [ { - id: "street_leavels_4", + id: "street_levels_4", column: "street_levels", operator: "_contains", value: [1], @@ -228,14 +228,14 @@ const locationsListViewFiltercards: FilterGroup[] = [ ], }, { - id: "street_leavels_5", + id: "street_levels_5", label: "5 - Highways and freeways ", groupOperator: "_and", enabled: false, inverted: false, filters: [ { - id: "street_leavels_5", + id: "street_levels_5", column: "street_levels", operator: "_contains", value: [5], @@ -304,6 +304,164 @@ 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 = { From fe769bc83622201c177ef4e0ea313063fcd1919d Mon Sep 17 00:00:00 2001 From: John Clary Date: Thu, 5 Mar 2026 13:54:17 -0500 Subject: [PATCH 13/18] add apd sector and remove location id --- editor/configs/locationDataCard.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/editor/configs/locationDataCard.ts b/editor/configs/locationDataCard.ts index ceff54495..58dec830a 100644 --- a/editor/configs/locationDataCard.ts +++ b/editor/configs/locationDataCard.ts @@ -1,7 +1,6 @@ import { locationColumns } from "./locationColumns"; export const locationCardColumns = [ - locationColumns.location_id, locationColumns.cr3_crash_count, locationColumns.non_cr3_crash_count, locationColumns.total_est_comp_cost, @@ -10,5 +9,6 @@ export const locationCardColumns = [ locationColumns.signal_eng_areas, locationColumns.is_signalized, locationColumns.council_districts, - locationColumns.is_hin + locationColumns.is_hin, + locationColumns.apd_sectors ]; From 69e498488a0293d60d48e93f7ec59adac0c43d65 Mon Sep 17 00:00:00 2001 From: John Clary Date: Thu, 5 Mar 2026 14:18:32 -0500 Subject: [PATCH 14/18] handle passing Barray of strings into gql filter --- editor/utils/queryBuilder.ts | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/editor/utils/queryBuilder.ts b/editor/utils/queryBuilder.ts index cb43fcd1b..673d1081a 100644 --- a/editor/utils/queryBuilder.ts +++ b/editor/utils/queryBuilder.ts @@ -42,11 +42,18 @@ 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 arr; + return `[${items.join(", ")}]`; }; /** From 59a42b509986586f4ab6e065fe5b6800d8442213 Mon Sep 17 00:00:00 2001 From: John Clary Date: Tue, 10 Mar 2026 13:28:27 -0400 Subject: [PATCH 15/18] set config version to 3 --- editor/configs/locationsListViewTable.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/editor/configs/locationsListViewTable.ts b/editor/configs/locationsListViewTable.ts index 4285de22a..92bcb6f7b 100644 --- a/editor/configs/locationsListViewTable.ts +++ b/editor/configs/locationsListViewTable.ts @@ -465,7 +465,7 @@ const locationsListViewFiltercards: FilterGroup[] = [ ]; export const locationsListViewQueryConfig: QueryConfig = { - _version: 6, + _version: 3, exportable: true, exportFilename: "locations", tableName: "locations_list_view", From 2255ec557f05a5743422221d7abebc009570e647 Mon Sep 17 00:00:00 2001 From: John Clary Date: Tue, 10 Mar 2026 13:29:33 -0400 Subject: [PATCH 16/18] refresh down migra --- .../1771530231604_locations_list_view_more_cols/down.sql | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) 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 index 69671b6b3..bb1a2130b 100644 --- a/database/migrations/default/1771530231604_locations_list_view_more_cols/down.sql +++ b/database/migrations/default/1771530231604_locations_list_view_more_cols/down.sql @@ -35,8 +35,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 From d7e85e97b332a8d73c295c20da9dfaa84e352d6b Mon Sep 17 00:00:00 2001 From: Vision Zero View Bot Date: Tue, 10 Mar 2026 17:35:54 +0000 Subject: [PATCH 17/18] =?UTF-8?q?=F0=9F=A4=96=20Export=20database=20views?= =?UTF-8?q?=20for=20john/26710-loc-list-filters?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- database/views/_lookup_tables_view.sql | 3 +- database/views/crash_injury_metrics_view.sql | 3 +- database/views/crashes_change_log_view.sql | 3 +- database/views/crashes_list_view.sql | 66 +++++++------ database/views/fatalities_view.sql | 3 +- .../materialized/location_crashes_view.sql | 97 ++++++++++--------- database/views/people_list_view.sql | 3 +- database/views/person_injury_metrics_view.sql | 3 +- .../views/socrata_export_crashes_view.sql | 80 +++++++-------- database/views/socrata_export_people_view.sql | 3 +- database/views/unit_injury_metrics_view.sql | 3 +- database/views/unit_types_involved_view.sql | 3 +- .../views/view_crash_narratives_ocr_todo.sql | 3 +- 13 files changed, 147 insertions(+), 126 deletions(-) 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/materialized/location_crashes_view.sql b/database/views/materialized/location_crashes_view.sql index 2434aa691..429d433d8 100644 --- a/database/views/materialized/location_crashes_view.sql +++ b/database/views/materialized/location_crashes_view.sql @@ -2,7 +2,8 @@ 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, @@ -37,51 +38,55 @@ 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 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 From 2156d5b37f3c786e5b367d06af6dccec66b29f87 Mon Sep 17 00:00:00 2001 From: John Clary Date: Tue, 10 Mar 2026 13:39:45 -0400 Subject: [PATCH 18/18] fix else block in arrayToStringRep --- editor/utils/queryBuilder.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/editor/utils/queryBuilder.ts b/editor/utils/queryBuilder.ts index 673d1081a..bdb70a56a 100644 --- a/editor/utils/queryBuilder.ts +++ b/editor/utils/queryBuilder.ts @@ -52,7 +52,9 @@ const arrayToStringRep = (arr: number[] | string[]): string => { if (typeof arr[0] === "string") { // add literal quotes to array values items = arr.map((val) => `"${val}"`); - } else arr; + } else { + items = arr; + } return `[${items.join(", ")}]`; };