refactor: streamline SQL migration scripts and enhance schema management for units and incidents
This commit is contained in:
parent
e891df87d0
commit
969d10958c
|
@ -68,60 +68,51 @@ CREATE TRIGGER on_auth_user_deleted BEFORE DELETE ON auth.users FOR EACH ROW EXE
|
|||
|
||||
CREATE TRIGGER on_auth_user_updated AFTER UPDATE ON auth.users FOR EACH ROW EXECUTE FUNCTION handle_user_update();
|
||||
|
||||
|
||||
drop trigger if exists "objects_delete_delete_prefix" on "storage"."objects";
|
||||
|
||||
drop trigger if exists "objects_insert_create_prefix" on "storage"."objects";
|
||||
|
||||
drop trigger if exists "objects_update_create_prefix" on "storage"."objects";
|
||||
|
||||
drop trigger if exists "prefixes_create_hierarchy" on "storage"."prefixes";
|
||||
DO $$
|
||||
BEGIN
|
||||
IF EXISTS (SELECT FROM pg_catalog.pg_tables
|
||||
WHERE schemaname = 'storage'
|
||||
AND tablename = 'prefixes') THEN
|
||||
|
||||
drop trigger if exists "prefixes_delete_hierarchy" on "storage"."prefixes";
|
||||
EXECUTE 'drop trigger if exists "prefixes_create_hierarchy" on "storage"."prefixes"';
|
||||
EXECUTE 'drop trigger if exists "prefixes_delete_hierarchy" on "storage"."prefixes"';
|
||||
|
||||
revoke delete on table "storage"."prefixes" from "anon";
|
||||
EXECUTE 'revoke delete on table "storage"."prefixes" from "anon"';
|
||||
EXECUTE 'revoke insert on table "storage"."prefixes" from "anon"';
|
||||
EXECUTE 'revoke references on table "storage"."prefixes" from "anon"';
|
||||
EXECUTE 'revoke select on table "storage"."prefixes" from "anon"';
|
||||
EXECUTE 'revoke trigger on table "storage"."prefixes" from "anon"';
|
||||
EXECUTE 'revoke truncate on table "storage"."prefixes" from "anon"';
|
||||
EXECUTE 'revoke update on table "storage"."prefixes" from "anon"';
|
||||
|
||||
revoke insert on table "storage"."prefixes" from "anon";
|
||||
EXECUTE 'revoke delete on table "storage"."prefixes" from "authenticated"';
|
||||
EXECUTE 'revoke insert on table "storage"."prefixes" from "authenticated"';
|
||||
EXECUTE 'revoke references on table "storage"."prefixes" from "authenticated"';
|
||||
EXECUTE 'revoke select on table "storage"."prefixes" from "authenticated"';
|
||||
EXECUTE 'revoke trigger on table "storage"."prefixes" from "authenticated"';
|
||||
EXECUTE 'revoke truncate on table "storage"."prefixes" from "authenticated"';
|
||||
EXECUTE 'revoke update on table "storage"."prefixes" from "authenticated"';
|
||||
|
||||
revoke references on table "storage"."prefixes" from "anon";
|
||||
EXECUTE 'revoke delete on table "storage"."prefixes" from "service_role"';
|
||||
EXECUTE 'revoke insert on table "storage"."prefixes" from "service_role"';
|
||||
EXECUTE 'revoke references on table "storage"."prefixes" from "service_role"';
|
||||
EXECUTE 'revoke select on table "storage"."prefixes" from "service_role"';
|
||||
EXECUTE 'revoke trigger on table "storage"."prefixes" from "service_role"';
|
||||
EXECUTE 'revoke truncate on table "storage"."prefixes" from "service_role"';
|
||||
EXECUTE 'revoke update on table "storage"."prefixes" from "service_role"';
|
||||
|
||||
revoke select on table "storage"."prefixes" from "anon";
|
||||
EXECUTE 'alter table "storage"."prefixes" drop constraint if exists "prefixes_bucketId_fkey"';
|
||||
EXECUTE 'alter table "storage"."prefixes" drop constraint if exists "prefixes_pkey"';
|
||||
|
||||
revoke trigger on table "storage"."prefixes" from "anon";
|
||||
|
||||
revoke truncate on table "storage"."prefixes" from "anon";
|
||||
|
||||
revoke update on table "storage"."prefixes" from "anon";
|
||||
|
||||
revoke delete on table "storage"."prefixes" from "authenticated";
|
||||
|
||||
revoke insert on table "storage"."prefixes" from "authenticated";
|
||||
|
||||
revoke references on table "storage"."prefixes" from "authenticated";
|
||||
|
||||
revoke select on table "storage"."prefixes" from "authenticated";
|
||||
|
||||
revoke trigger on table "storage"."prefixes" from "authenticated";
|
||||
|
||||
revoke truncate on table "storage"."prefixes" from "authenticated";
|
||||
|
||||
revoke update on table "storage"."prefixes" from "authenticated";
|
||||
|
||||
revoke delete on table "storage"."prefixes" from "service_role";
|
||||
|
||||
revoke insert on table "storage"."prefixes" from "service_role";
|
||||
|
||||
revoke references on table "storage"."prefixes" from "service_role";
|
||||
|
||||
revoke select on table "storage"."prefixes" from "service_role";
|
||||
|
||||
revoke trigger on table "storage"."prefixes" from "service_role";
|
||||
|
||||
revoke truncate on table "storage"."prefixes" from "service_role";
|
||||
|
||||
revoke update on table "storage"."prefixes" from "service_role";
|
||||
|
||||
alter table "storage"."prefixes" drop constraint "prefixes_bucketId_fkey";
|
||||
EXECUTE 'drop table "storage"."prefixes"';
|
||||
END IF;
|
||||
END $$;
|
||||
|
||||
drop function if exists "storage"."add_prefixes"(_bucket_id text, _name text);
|
||||
|
||||
|
@ -147,21 +138,41 @@ drop function if exists "storage"."search_v1_optimised"(prefix text, bucketname
|
|||
|
||||
drop function if exists "storage"."search_v2"(prefix text, bucket_name text, limits integer, levels integer, start_after text);
|
||||
|
||||
alter table "storage"."prefixes" drop constraint "prefixes_pkey";
|
||||
DO $$
|
||||
BEGIN
|
||||
IF EXISTS (SELECT 1 FROM pg_indexes WHERE schemaname = 'storage' AND indexname = 'idx_name_bucket_level_unique') THEN
|
||||
EXECUTE 'drop index "storage"."idx_name_bucket_level_unique"';
|
||||
END IF;
|
||||
|
||||
drop index if exists "storage"."idx_name_bucket_level_unique";
|
||||
IF EXISTS (SELECT 1 FROM pg_indexes WHERE schemaname = 'storage' AND indexname = 'idx_objects_lower_name') THEN
|
||||
EXECUTE 'drop index "storage"."idx_objects_lower_name"';
|
||||
END IF;
|
||||
|
||||
drop index if exists "storage"."idx_objects_lower_name";
|
||||
IF EXISTS (SELECT 1 FROM pg_indexes WHERE schemaname = 'storage' AND indexname = 'idx_prefixes_lower_name') THEN
|
||||
EXECUTE 'drop index "storage"."idx_prefixes_lower_name"';
|
||||
END IF;
|
||||
|
||||
drop index if exists "storage"."idx_prefixes_lower_name";
|
||||
IF EXISTS (SELECT 1 FROM pg_indexes WHERE schemaname = 'storage' AND indexname = 'objects_bucket_id_level_idx') THEN
|
||||
EXECUTE 'drop index "storage"."objects_bucket_id_level_idx"';
|
||||
END IF;
|
||||
|
||||
drop index if exists "storage"."objects_bucket_id_level_idx";
|
||||
IF EXISTS (SELECT 1 FROM pg_indexes WHERE schemaname = 'storage' AND indexname = 'prefixes_pkey') THEN
|
||||
EXECUTE 'drop index "storage"."prefixes_pkey"';
|
||||
END IF;
|
||||
END $$;
|
||||
|
||||
drop index if exists "storage"."prefixes_pkey";
|
||||
|
||||
drop table "storage"."prefixes";
|
||||
|
||||
alter table "storage"."objects" drop column "level";
|
||||
DO $$
|
||||
BEGIN
|
||||
IF EXISTS (
|
||||
SELECT 1
|
||||
FROM information_schema.columns
|
||||
WHERE table_schema = 'storage'
|
||||
AND table_name = 'objects'
|
||||
AND column_name = 'level'
|
||||
) THEN
|
||||
EXECUTE 'alter table "storage"."objects" drop column "level"';
|
||||
END IF;
|
||||
END $$;
|
||||
|
||||
set check_function_bodies = off;
|
||||
|
||||
|
|
|
@ -0,0 +1,204 @@
|
|||
grant delete on table "storage"."s3_multipart_uploads" to "postgres";
|
||||
|
||||
grant insert on table "storage"."s3_multipart_uploads" to "postgres";
|
||||
|
||||
grant references on table "storage"."s3_multipart_uploads" to "postgres";
|
||||
|
||||
grant select on table "storage"."s3_multipart_uploads" to "postgres";
|
||||
|
||||
grant trigger on table "storage"."s3_multipart_uploads" to "postgres";
|
||||
|
||||
grant truncate on table "storage"."s3_multipart_uploads" to "postgres";
|
||||
|
||||
grant update on table "storage"."s3_multipart_uploads" to "postgres";
|
||||
|
||||
grant delete on table "storage"."s3_multipart_uploads_parts" to "postgres";
|
||||
|
||||
grant insert on table "storage"."s3_multipart_uploads_parts" to "postgres";
|
||||
|
||||
grant references on table "storage"."s3_multipart_uploads_parts" to "postgres";
|
||||
|
||||
grant select on table "storage"."s3_multipart_uploads_parts" to "postgres";
|
||||
|
||||
grant trigger on table "storage"."s3_multipart_uploads_parts" to "postgres";
|
||||
|
||||
grant truncate on table "storage"."s3_multipart_uploads_parts" to "postgres";
|
||||
|
||||
grant update on table "storage"."s3_multipart_uploads_parts" to "postgres";
|
||||
|
||||
|
||||
-- drop type "gis"."geometry_dump";
|
||||
|
||||
-- drop type "gis"."valid_detail";
|
||||
|
||||
set check_function_bodies = off;
|
||||
|
||||
CREATE OR REPLACE FUNCTION gis.calculate_unit_incident_distances(p_unit_id character varying, p_district_id character varying DEFAULT NULL::character varying)
|
||||
RETURNS TABLE(unit_code character varying, unit_name character varying, unit_lat double precision, unit_lng double precision, incident_id character varying, incident_description text, incident_lat double precision, incident_lng double precision, category_name character varying, district_name character varying, distance_meters double precision)
|
||||
LANGUAGE plpgsql
|
||||
SECURITY DEFINER
|
||||
AS $function$
|
||||
BEGIN
|
||||
RETURN QUERY
|
||||
WITH unit_locations AS (
|
||||
SELECT
|
||||
u.code_unit,
|
||||
u.name,
|
||||
u.latitude,
|
||||
u.longitude,
|
||||
u.district_id,
|
||||
ST_SetSRID(ST_MakePoint(u.longitude, u.latitude), 4326)::geography AS location
|
||||
FROM
|
||||
units u
|
||||
WHERE
|
||||
(p_unit_id IS NULL OR u.code_unit = p_unit_id)
|
||||
AND (p_district_id IS NULL OR u.district_id = p_district_id)
|
||||
AND u.latitude IS NOT NULL
|
||||
AND u.longitude IS NOT NULL
|
||||
),
|
||||
incident_locations AS (
|
||||
SELECT
|
||||
ci.id,
|
||||
ci.description,
|
||||
ci.crime_id,
|
||||
ci.crime_category_id,
|
||||
l.latitude,
|
||||
l.longitude,
|
||||
ST_SetSRID(ST_MakePoint(l.longitude, l.latitude), 4326)::geography AS location
|
||||
FROM
|
||||
crime_incidents ci
|
||||
JOIN
|
||||
locations l ON ci.location_id = l.id
|
||||
WHERE
|
||||
l.latitude IS NOT NULL
|
||||
AND l.longitude IS NOT NULL
|
||||
)
|
||||
SELECT
|
||||
ul.code_unit as unit_code,
|
||||
ul.name as unit_name,
|
||||
ul.latitude as unit_lat,
|
||||
ul.longitude as unit_lng,
|
||||
il.id as incident_id,
|
||||
il.description as incident_description,
|
||||
il.latitude as incident_lat,
|
||||
il.longitude as incident_lng,
|
||||
cc.name as category_name,
|
||||
d.name as district_name,
|
||||
ST_Distance(ul.location, il.location) as distance_meters
|
||||
FROM
|
||||
unit_locations ul
|
||||
JOIN
|
||||
districts d ON ul.district_id = d.id
|
||||
JOIN
|
||||
crimes c ON c.district_id = d.id
|
||||
JOIN
|
||||
incident_locations il ON il.crime_id = c.id
|
||||
JOIN
|
||||
crime_categories cc ON il.crime_category_id = cc.id
|
||||
ORDER BY
|
||||
ul.code_unit,
|
||||
ul.location <-> il.location; -- Use KNN operator for efficient ordering
|
||||
END;
|
||||
$function$
|
||||
;
|
||||
|
||||
CREATE OR REPLACE FUNCTION gis.find_nearest_unit_to_incident(p_incident_id integer)
|
||||
RETURNS TABLE(unit_code text, unit_name text, distance_meters double precision)
|
||||
LANGUAGE plpgsql
|
||||
SECURITY DEFINER
|
||||
AS $function$
|
||||
BEGIN
|
||||
RETURN QUERY
|
||||
WITH incident_location AS (
|
||||
SELECT
|
||||
ci.id,
|
||||
ST_SetSRID(ST_MakePoint(
|
||||
(ci.locations->>'longitude')::float,
|
||||
(ci.locations->>'latitude')::float
|
||||
), 4326)::geography AS location
|
||||
FROM
|
||||
crime_incidents ci
|
||||
WHERE
|
||||
ci.id = p_incident_id
|
||||
AND (ci.locations->>'latitude') IS NOT NULL
|
||||
AND (ci.locations->>'longitude') IS NOT NULL
|
||||
),
|
||||
unit_locations AS (
|
||||
SELECT
|
||||
u.code_unit,
|
||||
u.name,
|
||||
ST_SetSRID(ST_MakePoint(u.longitude, u.latitude), 4326)::geography AS location
|
||||
FROM
|
||||
units u
|
||||
WHERE
|
||||
u.latitude IS NOT NULL
|
||||
AND u.longitude IS NOT NULL
|
||||
)
|
||||
SELECT
|
||||
ul.code_unit as unit_code,
|
||||
ul.name as unit_name,
|
||||
ST_Distance(ul.location, il.location) as distance_meters
|
||||
FROM
|
||||
unit_locations ul
|
||||
CROSS JOIN
|
||||
incident_location il
|
||||
ORDER BY
|
||||
ul.location <-> il.location
|
||||
LIMIT 1;
|
||||
END;
|
||||
$function$
|
||||
;
|
||||
|
||||
CREATE OR REPLACE FUNCTION gis.find_units_within_distance(p_incident_id integer, p_max_distance_meters double precision DEFAULT 5000)
|
||||
RETURNS TABLE(unit_code text, unit_name text, distance_meters double precision)
|
||||
LANGUAGE plpgsql
|
||||
SECURITY DEFINER
|
||||
AS $function$
|
||||
BEGIN
|
||||
RETURN QUERY
|
||||
WITH incident_location AS (
|
||||
SELECT
|
||||
ci.id,
|
||||
ST_SetSRID(ST_MakePoint(
|
||||
(ci.locations->>'longitude')::float,
|
||||
(ci.locations->>'latitude')::float
|
||||
), 4326)::geography AS location
|
||||
FROM
|
||||
crime_incidents ci
|
||||
WHERE
|
||||
ci.id = p_incident_id
|
||||
AND (ci.locations->>'latitude') IS NOT NULL
|
||||
AND (ci.locations->>'longitude') IS NOT NULL
|
||||
),
|
||||
unit_locations AS (
|
||||
SELECT
|
||||
u.code_unit,
|
||||
u.name,
|
||||
ST_SetSRID(ST_MakePoint(u.longitude, u.latitude), 4326)::geography AS location
|
||||
FROM
|
||||
units u
|
||||
WHERE
|
||||
u.latitude IS NOT NULL
|
||||
AND u.longitude IS NOT NULL
|
||||
)
|
||||
SELECT
|
||||
ul.code_unit as unit_code,
|
||||
ul.name as unit_name,
|
||||
ST_Distance(ul.location, il.location) as distance_meters
|
||||
FROM
|
||||
unit_locations ul
|
||||
CROSS JOIN
|
||||
incident_location il
|
||||
WHERE
|
||||
ST_DWithin(ul.location, il.location, p_max_distance_meters)
|
||||
ORDER BY
|
||||
ST_Distance(ul.location, il.location);
|
||||
END;
|
||||
$function$
|
||||
;
|
||||
|
||||
-- create type "gis"."geometry_dump" as ("path" integer[], "geom" geometry);
|
||||
|
||||
-- create type "gis"."valid_detail" as ("valid" boolean, "reason" character varying, "location" geometry);
|
||||
|
||||
|
|
@ -0,0 +1,94 @@
|
|||
-- drop type "gis"."geometry_dump";
|
||||
|
||||
-- drop type "gis"."valid_detail";
|
||||
|
||||
set check_function_bodies = off;
|
||||
|
||||
CREATE OR REPLACE FUNCTION gis.find_nearest_unit(p_incident_id character varying)
|
||||
RETURNS TABLE(unit_code character varying, unit_name character varying, distance_meters double precision)
|
||||
LANGUAGE plpgsql
|
||||
SECURITY DEFINER
|
||||
AS $function$
|
||||
BEGIN
|
||||
RETURN QUERY
|
||||
WITH incident_location AS (
|
||||
SELECT
|
||||
ci.id,
|
||||
l.location AS location
|
||||
FROM
|
||||
crime_incidents ci
|
||||
JOIN
|
||||
locations l ON ci.location_id = l.id
|
||||
WHERE
|
||||
ci.id = p_incident_id
|
||||
),
|
||||
unit_locations AS (
|
||||
SELECT
|
||||
u.code_unit,
|
||||
u.name,
|
||||
u.location
|
||||
FROM
|
||||
units u
|
||||
)
|
||||
SELECT
|
||||
ul.code_unit as unit_code,
|
||||
ul.name as unit_name,
|
||||
ST_Distance(ul.location, il.location) as distance_meters
|
||||
FROM
|
||||
unit_locations ul
|
||||
CROSS JOIN
|
||||
incident_location il
|
||||
ORDER BY
|
||||
ul.location <-> il.location
|
||||
LIMIT 1;
|
||||
END;
|
||||
$function$
|
||||
;
|
||||
|
||||
CREATE OR REPLACE FUNCTION gis.find_units_within_distance(p_incident_id character varying, p_max_distance_meters double precision DEFAULT 5000)
|
||||
RETURNS TABLE(unit_code character varying, unit_name character varying, distance_meters double precision)
|
||||
LANGUAGE plpgsql
|
||||
SECURITY DEFINER
|
||||
AS $function$
|
||||
BEGIN
|
||||
RETURN QUERY
|
||||
WITH incident_location AS (
|
||||
SELECT
|
||||
ci.id,
|
||||
l.location AS location
|
||||
FROM
|
||||
crime_incidents ci
|
||||
JOIN
|
||||
locations l ON ci.location_id = l.id
|
||||
WHERE
|
||||
ci.id = p_incident_id
|
||||
),
|
||||
unit_locations AS (
|
||||
SELECT
|
||||
u.code_unit,
|
||||
u.name,
|
||||
u.location
|
||||
FROM
|
||||
units u
|
||||
)
|
||||
SELECT
|
||||
ul.code_unit as unit_code,
|
||||
ul.name as unit_name,
|
||||
ST_Distance(ul.location, il.location) as distance_meters
|
||||
FROM
|
||||
unit_locations ul
|
||||
CROSS JOIN
|
||||
incident_location il
|
||||
WHERE
|
||||
ST_DWithin(ul.location, il.location, p_max_distance_meters)
|
||||
ORDER BY
|
||||
ST_Distance(ul.location, il.location);
|
||||
END;
|
||||
$function$
|
||||
;
|
||||
|
||||
-- create type "gis"."geometry_dump" as ("path" integer[], "geom" geometry);
|
||||
|
||||
-- create type "gis"."valid_detail" as ("valid" boolean, "reason" character varying, "location" geometry);
|
||||
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
-- drop type "gis"."geometry_dump";
|
||||
|
||||
-- drop type "gis"."valid_detail";
|
||||
|
||||
-- create type "gis"."geometry_dump" as ("path" integer[], "geom" geometry);
|
||||
|
||||
-- create type "gis"."valid_detail" as ("valid" boolean, "reason" character varying, "location" geometry);
|
||||
|
||||
|
|
@ -1,2 +1,2 @@
|
|||
-- AlterTable
|
||||
ALTER TABLE "units" ADD COLUMN "phone" TEXT;
|
||||
ALTER TABLE "units" ADD COLUMN "phone" TEXT;
|
||||
|
|
|
@ -0,0 +1,41 @@
|
|||
/*
|
||||
Warnings:
|
||||
|
||||
- You are about to drop the column `code_unit` on the `unit_statistics` table. All the data in the column will be lost.
|
||||
- A unique constraint covering the columns `[unit_id,month,year]` on the table `unit_statistics` will be added. If there are existing duplicate values, this will fail.
|
||||
- A unique constraint covering the columns `[district_id]` on the table `units` will be added. If there are existing duplicate values, this will fail.
|
||||
- Made the column `year` on table `crimes` required. This step will fail if there are existing NULL values in that column.
|
||||
- Added the required column `unit_id` to the `unit_statistics` table without a default value. This is not possible if the table is not empty.
|
||||
|
||||
*/
|
||||
-- DropForeignKey
|
||||
ALTER TABLE "unit_statistics" DROP CONSTRAINT "unit_statistics_code_unit_fkey";
|
||||
|
||||
-- DropForeignKey
|
||||
ALTER TABLE "units" DROP CONSTRAINT "units_district_id_fkey";
|
||||
|
||||
-- DropIndex
|
||||
DROP INDEX "unit_statistics_code_unit_month_year_key";
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "crimes" ALTER COLUMN "year" SET NOT NULL;
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "unit_statistics" DROP COLUMN "code_unit",
|
||||
ADD COLUMN "unit_id" UUID NOT NULL;
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "units" ADD COLUMN "id" UUID NOT NULL DEFAULT gen_random_uuid(),
|
||||
ADD CONSTRAINT "units_pkey" PRIMARY KEY ("id");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "unit_statistics_unit_id_month_year_key" ON "unit_statistics"("unit_id", "month", "year");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "units_district_id_key" ON "units"("district_id");
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "units" ADD CONSTRAINT "units_district_id_fkey" FOREIGN KEY ("district_id") REFERENCES "districts"("id") ON DELETE CASCADE ON UPDATE NO ACTION;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "unit_statistics" ADD CONSTRAINT "unit_statistics_unit_id_fkey" FOREIGN KEY ("unit_id") REFERENCES "units"("id") ON DELETE CASCADE ON UPDATE NO ACTION;
|
|
@ -0,0 +1,29 @@
|
|||
/*
|
||||
Warnings:
|
||||
|
||||
- You are about to drop the column `unit_id` on the `unit_statistics` table. All the data in the column will be lost.
|
||||
- The primary key for the `units` table will be changed. If it partially fails, the table could be left without primary key constraint.
|
||||
- You are about to drop the column `id` on the `units` table. All the data in the column will be lost.
|
||||
- A unique constraint covering the columns `[code_unit,month,year]` on the table `unit_statistics` will be added. If there are existing duplicate values, this will fail.
|
||||
- Added the required column `code_unit` to the `unit_statistics` table without a default value. This is not possible if the table is not empty.
|
||||
|
||||
*/
|
||||
-- DropForeignKey
|
||||
ALTER TABLE "unit_statistics" DROP CONSTRAINT "unit_statistics_unit_id_fkey";
|
||||
|
||||
-- DropIndex
|
||||
DROP INDEX "unit_statistics_unit_id_month_year_key";
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "unit_statistics" DROP COLUMN "unit_id",
|
||||
ADD COLUMN "code_unit" VARCHAR(20) NOT NULL;
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "units" DROP CONSTRAINT "units_pkey",
|
||||
DROP COLUMN "id";
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "unit_statistics_code_unit_month_year_key" ON "unit_statistics"("code_unit", "month", "year");
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "unit_statistics" ADD CONSTRAINT "unit_statistics_code_unit_fkey" FOREIGN KEY ("code_unit") REFERENCES "units"("code_unit") ON DELETE CASCADE ON UPDATE NO ACTION;
|
|
@ -0,0 +1,2 @@
|
|||
-- AlterTable
|
||||
ALTER TABLE "crimes" ALTER COLUMN "year" DROP NOT NULL;
|
|
@ -0,0 +1,15 @@
|
|||
/*
|
||||
Warnings:
|
||||
|
||||
- Added the required column `city_id` to the `units` table without a default value. This is not possible if the table is not empty.
|
||||
|
||||
*/
|
||||
-- DropIndex
|
||||
DROP INDEX "units_district_id_key";
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "units" ADD COLUMN "city_id" VARCHAR(20) NOT NULL,
|
||||
ADD CONSTRAINT "units_pkey" PRIMARY KEY ("code_unit");
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "units" ADD CONSTRAINT "units_city_id_fkey" FOREIGN KEY ("city_id") REFERENCES "cities"("id") ON DELETE CASCADE ON UPDATE NO ACTION;
|
|
@ -124,6 +124,7 @@ model cities {
|
|||
created_at DateTime? @default(now()) @db.Timestamptz(6)
|
||||
updated_at DateTime? @default(now()) @db.Timestamptz(6)
|
||||
districts districts[]
|
||||
units units[]
|
||||
|
||||
@@index([name], map: "idx_cities_name")
|
||||
}
|
||||
|
@ -260,8 +261,9 @@ model incident_logs {
|
|||
}
|
||||
|
||||
model units {
|
||||
code_unit String @unique @db.VarChar(20)
|
||||
code_unit String @id @unique @db.VarChar(20)
|
||||
district_id String @db.VarChar(20)
|
||||
city_id String @db.VarChar(20)
|
||||
name String @db.VarChar(100)
|
||||
description String?
|
||||
type unit_type
|
||||
|
@ -274,7 +276,8 @@ model units {
|
|||
location Unsupported("geography")
|
||||
phone String?
|
||||
unit_statistics unit_statistics[]
|
||||
districts districts @relation(fields: [district_id], references: [id])
|
||||
districts districts @relation(fields: [district_id], references: [id], onDelete: Cascade, onUpdate: NoAction)
|
||||
cities cities @relation(fields: [city_id], references: [id], onDelete: Cascade, onUpdate: NoAction)
|
||||
|
||||
@@index([name], map: "idx_units_name")
|
||||
@@index([type], map: "idx_units_type")
|
||||
|
@ -285,7 +288,6 @@ model units {
|
|||
|
||||
model unit_statistics {
|
||||
id String @id @default(dbgenerated("gen_random_uuid()")) @db.Uuid
|
||||
code_unit String @db.VarChar(20)
|
||||
crime_total Int
|
||||
crime_cleared Int
|
||||
percentage Float?
|
||||
|
@ -294,7 +296,8 @@ model unit_statistics {
|
|||
year Int
|
||||
created_at DateTime? @default(now()) @db.Timestamptz(6)
|
||||
updated_at DateTime? @default(now()) @db.Timestamptz(6)
|
||||
unit units @relation(fields: [code_unit], references: [code_unit], onDelete: Cascade, onUpdate: NoAction)
|
||||
code_unit String @db.VarChar(20)
|
||||
units units @relation(fields: [code_unit], references: [code_unit], onDelete: Cascade, onUpdate: NoAction)
|
||||
|
||||
@@unique([code_unit, month, year])
|
||||
@@index([year, month], map: "idx_unit_statistics_year_month")
|
||||
|
|
|
@ -65,39 +65,83 @@ export class CrimeIncidentsSeeder {
|
|||
}
|
||||
|
||||
/**
|
||||
* Generates well-distributed points within a district's area
|
||||
* Generates well-distributed points within a district's area with geographical constraints
|
||||
* @param centerLat - The center latitude of the district
|
||||
* @param centerLng - The center longitude of the district
|
||||
* @param landArea - Land area in square km
|
||||
* @param numPoints - Number of points to generate
|
||||
* @param districtId - ID of the district for special handling
|
||||
* @param districtName - Name of the district for constraints
|
||||
* @returns Array of {latitude, longitude, radius} points
|
||||
*/
|
||||
private generateDistributedPoints(
|
||||
centerLat: number,
|
||||
centerLng: number,
|
||||
landArea: number,
|
||||
numPoints: number
|
||||
numPoints: number,
|
||||
districtId: string,
|
||||
districtName: string
|
||||
): Array<{ latitude: number; longitude: number; radius: number }> {
|
||||
const points = [];
|
||||
|
||||
// Calculate a reasonable radius based on land area
|
||||
// Using square root of area as an approximation of district "radius"
|
||||
const areaFactor = Math.sqrt(landArea) / 100;
|
||||
const baseRadius = Math.max(0.01, Math.min(0.05, areaFactor * 0.03));
|
||||
|
||||
// Create a grid-based distribution for better coverage
|
||||
const gridSize = Math.ceil(Math.sqrt(numPoints * 1.5)); // Slightly larger grid for variety
|
||||
const gridSize = Math.ceil(Math.sqrt(numPoints * 1.5));
|
||||
|
||||
// Define district bounds approximately
|
||||
// 0.1 degrees is roughly 11km at equator
|
||||
const estimatedDistrictRadius = Math.sqrt(landArea / Math.PI) / 111;
|
||||
const bounds = {
|
||||
// Define district bounds with geographical constraints
|
||||
// Standard calculation for general districts
|
||||
let estimatedDistrictRadius = Math.sqrt(landArea / Math.PI) / 111;
|
||||
|
||||
// District-specific adjustments to avoid generating points in the ocean
|
||||
const southernCoastalDistricts = [
|
||||
'puger',
|
||||
'tempurejo',
|
||||
'ambulu',
|
||||
'gumukmas',
|
||||
'kencong',
|
||||
'wuluhan',
|
||||
'kencong',
|
||||
];
|
||||
const isCoastalDistrict = southernCoastalDistricts.some((district) =>
|
||||
districtName.toLowerCase().includes(district)
|
||||
);
|
||||
|
||||
// Default bounds
|
||||
let bounds = {
|
||||
minLat: centerLat - estimatedDistrictRadius,
|
||||
maxLat: centerLat + estimatedDistrictRadius,
|
||||
minLng: centerLng - estimatedDistrictRadius,
|
||||
maxLng: centerLng + estimatedDistrictRadius,
|
||||
};
|
||||
|
||||
// Apply special constraints for coastal districts
|
||||
if (isCoastalDistrict) {
|
||||
// Shift points northward for southern coastal districts to avoid ocean
|
||||
if (
|
||||
districtName.toLowerCase().includes('puger') ||
|
||||
districtName.toLowerCase().includes('tempurejo')
|
||||
) {
|
||||
// For Puger and Tempurejo, shift more aggressively northward
|
||||
bounds = {
|
||||
minLat: centerLat, // Don't go south of the center
|
||||
maxLat: centerLat + estimatedDistrictRadius * 1.5, // Extend more to the north
|
||||
minLng: centerLng - estimatedDistrictRadius * 0.8,
|
||||
maxLng: centerLng + estimatedDistrictRadius * 0.8,
|
||||
};
|
||||
} else {
|
||||
// For other coastal districts, shift moderately northward
|
||||
bounds = {
|
||||
minLat: centerLat - estimatedDistrictRadius * 0.5, // Less southward
|
||||
maxLat: centerLat + estimatedDistrictRadius * 1.2, // More northward
|
||||
minLng: centerLng - estimatedDistrictRadius,
|
||||
maxLng: centerLng + estimatedDistrictRadius,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
const latStep = (bounds.maxLat - bounds.minLat) / gridSize;
|
||||
const lngStep = (bounds.maxLng - bounds.minLng) / gridSize;
|
||||
|
||||
|
@ -105,7 +149,7 @@ export class CrimeIncidentsSeeder {
|
|||
let totalPoints = 0;
|
||||
for (let i = 0; i < gridSize && totalPoints < numPoints; i++) {
|
||||
for (let j = 0; j < gridSize && totalPoints < numPoints; j++) {
|
||||
// Base position within the grid cell
|
||||
// Base position within the grid cell with randomness
|
||||
const cellLat =
|
||||
bounds.minLat + (i + 0.2 + Math.random() * 0.6) * latStep;
|
||||
const cellLng =
|
||||
|
@ -121,22 +165,38 @@ export class CrimeIncidentsSeeder {
|
|||
const latitude = cellLat + (Math.random() * 2 - 1) * jitter;
|
||||
const longitude = cellLng + (Math.random() * 2 - 1) * jitter;
|
||||
|
||||
points.push({
|
||||
latitude,
|
||||
longitude,
|
||||
radius: distance * 111000, // Convert to meters (approx)
|
||||
});
|
||||
// Ensure the point is within district boundaries
|
||||
// Simple check to ensure points don't stray too far from center
|
||||
if (distance <= estimatedDistrictRadius * 1.2) {
|
||||
points.push({
|
||||
latitude,
|
||||
longitude,
|
||||
radius: distance * 111000, // Convert to meters (approx)
|
||||
});
|
||||
|
||||
totalPoints++;
|
||||
totalPoints++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add some completely random points for diversity
|
||||
// If we still need more points, add some with tighter constraints
|
||||
while (points.length < numPoints) {
|
||||
const latitude =
|
||||
centerLat + (Math.random() * 2 - 1) * estimatedDistrictRadius;
|
||||
const longitude =
|
||||
centerLng + (Math.random() * 2 - 1) * estimatedDistrictRadius;
|
||||
// For coastal districts, use more controlled distribution
|
||||
let latitude, longitude;
|
||||
|
||||
if (isCoastalDistrict) {
|
||||
// Generate points with northward bias for coastal districts
|
||||
const northBias = Math.random() * 0.7 + 0.3; // 0.3 to 1.0, favoring north
|
||||
latitude = centerLat + northBias * estimatedDistrictRadius * 0.8;
|
||||
longitude =
|
||||
centerLng + (Math.random() * 2 - 1) * estimatedDistrictRadius * 0.8;
|
||||
} else {
|
||||
// Standard distribution for non-coastal districts
|
||||
latitude =
|
||||
centerLat + (Math.random() * 2 - 1) * estimatedDistrictRadius * 0.8;
|
||||
longitude =
|
||||
centerLng + (Math.random() * 2 - 1) * estimatedDistrictRadius * 0.8;
|
||||
}
|
||||
|
||||
const latDiff = latitude - centerLat;
|
||||
const lngDiff = longitude - centerLng;
|
||||
|
@ -249,15 +309,17 @@ export class CrimeIncidentsSeeder {
|
|||
return [];
|
||||
}
|
||||
|
||||
// Generate a variable number of incidents between 10 and 25 for more variability
|
||||
const numLocations = Math.floor(Math.random() * 16) + 10; // 10-25 locations
|
||||
// Use the actual number of crimes instead of a random count
|
||||
const numLocations = crime.number_of_crime;
|
||||
|
||||
// Generate distributed locations using our new utility function
|
||||
// Update the call to the function in createIncidentsForCrime method:
|
||||
const locationPool = this.generateDistributedPoints(
|
||||
geo.latitude,
|
||||
geo.longitude,
|
||||
geo.land_area || 100, // Default to 100 km² if not available
|
||||
numLocations
|
||||
numLocations,
|
||||
district.id,
|
||||
district.name
|
||||
);
|
||||
|
||||
// List of common street names in Jember with more variety
|
||||
|
|
|
@ -32,6 +32,7 @@ interface UnitStatistics {
|
|||
interface CreateLocationDto {
|
||||
district_id: string;
|
||||
code_unit: string;
|
||||
city_id: string;
|
||||
name?: string;
|
||||
description?: string;
|
||||
address?: string;
|
||||
|
@ -101,6 +102,7 @@ export class UnitSeeder {
|
|||
|
||||
let locationData: CreateLocationDto = {
|
||||
district_id: city.districts[0].id, // This will be replaced with Patrang's ID
|
||||
city_id: city.id,
|
||||
code_unit: newId,
|
||||
name: `Polres ${city.name}`,
|
||||
description: `Unit ${city.name} is categorized as POLRES and operates in the ${city.name} area.`,
|
||||
|
@ -163,6 +165,7 @@ export class UnitSeeder {
|
|||
|
||||
const locationData: CreateLocationDto = {
|
||||
district_id: district.id,
|
||||
city_id: district.city_id,
|
||||
code_unit: newId,
|
||||
name: `Polsek ${district.name}`,
|
||||
description: `Unit ${district.name} is categorized as POLSEK and operates in the ${district.name} area.`,
|
||||
|
|
Loading…
Reference in New Issue