MIF_E31221222/sigap-website/prisma/seeds/incident-logs.ts

286 lines
9.2 KiB
TypeScript

import { PrismaClient } from "@prisma/client";
import { faker } from "@faker-js/faker";
import { districtCenters } from "../data/jsons/district-center";
import { createClient } from "../../app/_utils/supabase/client";
import db from "../db";
export class IncidentLogSeeder {
constructor(
private prisma: PrismaClient,
private supabase = createClient(),
) {}
// Add run method to satisfy the Seeder interface
async run(): Promise<void> {
await db.incident_logs.deleteMany({});
await this.seed();
}
async seed() {
// Step 1: Create a mock user if needed
const user = await this.getOrCreateUser();
// Step 2: Create an event
const event = await this.createEvent(user.id);
// Step 3: Create a session
const session = await this.createSession(user.id, event.id);
// Step 4: Create locations
const locations = await this.createLocations(event.id);
// Step 5: Create incident logs for the past 24 hours
await this.createIncidentLogs(user.id, locations);
console.log("Incident logs seeded successfully");
}
private async getOrCreateUser() {
// Check if we have an existing user
const existingUser = await this.prisma.users.findFirst({
where: { email: "incident-reporter@sigap.com" },
});
if (existingUser) return existingUser;
// Get a valid role ID
const role = await this.prisma.roles.findFirst();
if (!role) {
throw new Error("No roles found. Please seed roles first.");
}
// Create a new user if none exists
return await this.prisma.users.create({
data: {
email: "incident-reporter@sigap.com",
roles_id: role.id,
is_anonymous: false,
},
});
}
private async createEvent(userId: string) {
return await this.prisma.events.create({
data: {
name: "Incident Monitoring Event",
description: "24-hour incident monitoring",
user_id: userId,
},
});
}
private async createSession(userId: string, eventId: string) {
return await this.prisma.sessions.create({
data: {
user_id: userId,
event_id: eventId,
status: "active",
},
});
}
private async createLocations(eventId: string) {
const districts = await this.prisma.districts.findMany({});
if (!districts.length) {
throw new Error("No districts found. Please seed districts first.");
}
const locations = [];
// Create 10 random locations across the districts
for (let i = 0; i < 10; i++) {
const district =
districts[Math.floor(Math.random() * districts.length)];
// Find matching district center by name
const districtCenter = districtCenters.find(
(center) =>
center.kecamatan.toLowerCase() ===
district.name.toLowerCase(),
);
// If we have matching center coordinates, use them as base point
// Otherwise generate random coordinates
let latitude, longitude;
if (districtCenter) {
// Generate random coordinates within 3-5km of district center
const radius = this.getRandomInt(3000, 5000); // 3-5 km in meters
const randomPoint = this.getRandomPointInRadius(
districtCenter.lat,
districtCenter.lng,
radius,
);
latitude = randomPoint.latitude;
longitude = randomPoint.longitude;
}
const locationType = [
"residential",
"commercial",
"public",
"transportation",
][Math.floor(Math.random() * 4)];
const address = faker.location.streetAddress();
// Insert using Supabase with PostGIS
const { data, error } = await this.supabase
.from("locations")
.insert({
district_id: district.id,
event_id: eventId,
address: address,
type: locationType,
latitude: latitude,
longitude: longitude,
location: `POINT(${longitude} ${latitude})`, // PostGIS format
})
.select();
if (error) {
console.error("Error inserting location:", error);
continue;
}
if (data && data.length > 0) {
locations.push(data[0]);
}
}
return locations;
}
private async createIncidentLogs(userId: string, locations: any[]) {
// Get crime categories
const categories = await this.prisma.crime_categories.findMany({});
if (!categories.length) {
throw new Error(
"No crime categories found. Please seed crime categories first.",
);
}
const now = new Date();
// Create 24 incidents data array
const incidentData = [];
for (let i = 0; i < 24; i++) {
const hourOffset = this.getRandomInt(0, 24); // Random hour within last 24 hours
const timestamp = new Date(
now.getTime() - hourOffset * 60 * 60 * 1000,
);
const location =
locations[Math.floor(Math.random() * locations.length)];
const category =
categories[Math.floor(Math.random() * categories.length)];
incidentData.push({
user_id: userId,
location_id: location.id,
category_id: category.id,
description: this.getRandomIncidentDescription(),
time: timestamp,
source: Math.random() > 0.3 ? "resident" : "reporter",
verified: Math.random() > 0.5,
});
}
// Bulk insert all incidents at once
const createdIncidents = await this.prisma.incident_logs.createMany({
data: incidentData,
});
console.log(`Created ${createdIncidents.count} incident logs in bulk`);
// If you need the actual created records, query them after creation
const incidents = await this.prisma.incident_logs.findMany({
where: {
user_id: userId,
time: {
gte: new Date(now.getTime() - 24 * 60 * 60 * 1000),
},
},
orderBy: {
time: "asc",
},
});
return incidents;
}
// Helper methods
private getRandomInt(min: number, max: number): number {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
private getRandomIncidentDescription(): string {
const descriptions = [
"Suspicious person loitering in the area",
"Vehicle break-in reported",
"Shoplifting incident at local store",
"Noise complaint from neighbors",
"Traffic accident with minor injuries",
"Vandalism to public property",
"Domestic dispute reported",
"Trespassing on private property",
"Armed robbery at convenience store",
"Drug-related activity observed",
"Assault reported outside nightclub",
"Missing person report filed",
"Public intoxication incident",
"Package theft from doorstep",
"Illegal dumping observed",
];
return descriptions[Math.floor(Math.random() * descriptions.length)];
}
/**
* Generates a random point within a specified radius from a center point
* @param centerLat Center latitude
* @param centerLng Center longitude
* @param radiusInMeters Radius in meters
* @returns Object containing latitude and longitude
*/
private getRandomPointInRadius(
centerLat: number,
centerLng: number,
radiusInMeters: number,
): { latitude: number; longitude: number } {
// Earth's radius in meters
const earthRadius = 6378137;
// Convert radius from meters to degrees
const radiusInDegrees = radiusInMeters / earthRadius * (180 / Math.PI);
// Generate random angle in radians
const randomAngle = Math.random() * Math.PI * 2;
// Generate random radius within the specified radius
const randomRadius = Math.sqrt(Math.random()) * radiusInDegrees;
// Calculate offset
const latOffset = randomRadius * Math.sin(randomAngle);
const lngOffset = randomRadius * Math.cos(randomAngle);
// Adjust for earth's curvature for longitude
const lngOffsetAdjusted = lngOffset /
Math.cos(centerLat * Math.PI / 180);
// Calculate final coordinates
const randomLat = centerLat + latOffset;
const randomLng = centerLng + lngOffsetAdjusted;
return {
latitude: randomLat,
longitude: randomLng,
};
}
}