674 lines
17 KiB
TypeScript
674 lines
17 KiB
TypeScript
import {
|
|
crime_incidents,
|
|
crime_rates,
|
|
crimes,
|
|
events,
|
|
PrismaClient,
|
|
session_status,
|
|
users,
|
|
} from "@prisma/client";
|
|
import fs from "fs";
|
|
import path from "path";
|
|
import { parse } from "csv-parse/sync";
|
|
import { generateId, generateIdWithDbCounter } from "../../app/_utils/common";
|
|
import { CRegex } from "../../app/_utils/const/regex";
|
|
import db from "../db";
|
|
|
|
interface ICreateUser {
|
|
id: string;
|
|
email: string;
|
|
roles_id: string;
|
|
confirmed_at: Date | null;
|
|
email_confirmed_at: Date | null;
|
|
last_sign_in_at: Date | null;
|
|
phone: string | null;
|
|
updated_at: Date | null;
|
|
created_at: Date | null;
|
|
app_metadata: any;
|
|
invited_at: Date | null;
|
|
user_metadata: any;
|
|
is_anonymous: boolean;
|
|
}
|
|
|
|
export class CrimesSeeder {
|
|
constructor(private prisma: PrismaClient) {}
|
|
|
|
async run(): Promise<void> {
|
|
console.log("🌱 Seeding crimes data...");
|
|
|
|
try {
|
|
// Create test user
|
|
const user = await this.createUsers();
|
|
|
|
await db.crime_incidents.deleteMany();
|
|
await db.crimes.deleteMany();
|
|
|
|
if (!user) {
|
|
throw new Error("Failed to create user");
|
|
}
|
|
|
|
// Create 5 events
|
|
const events = await this.createEvents(user);
|
|
|
|
// Create 5 sessions with completed status
|
|
await this.createSessions(user, events);
|
|
|
|
// Import original crime data with source_type = 'general'
|
|
await this.importMonthlyCrimeData();
|
|
await this.importYearlyCrimeData();
|
|
await this.importAllYearSummaries();
|
|
|
|
// Import new crime data by type with appropriate source_type
|
|
await this.importMonthlyCrimeDataByType();
|
|
await this.importYearlyCrimeDataByType();
|
|
await this.importSummaryByType();
|
|
|
|
console.log("✅ Crime seeding completed successfully.");
|
|
} catch (error) {
|
|
console.error("❌ Error seeding crimes:", error);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
private async createUsers() {
|
|
const existingUser = await this.prisma.users.findFirst({
|
|
where: { email: "sigapcompany@gmail.com" },
|
|
});
|
|
|
|
if (existingUser) {
|
|
console.log("Users already exist, skipping creation.");
|
|
return existingUser;
|
|
}
|
|
|
|
let roleId = await this.prisma.roles.findFirst({
|
|
where: { name: "admin" },
|
|
});
|
|
|
|
if (!roleId) {
|
|
roleId = await this.prisma.roles.create({
|
|
data: {
|
|
name: "admin",
|
|
description: "Administrator role",
|
|
},
|
|
});
|
|
}
|
|
|
|
const newUser = await this.prisma.users.create({
|
|
data: {
|
|
email: "sigapcompany@gmail.com",
|
|
roles_id: roleId.id,
|
|
confirmed_at: new Date(),
|
|
email_confirmed_at: new Date(),
|
|
last_sign_in_at: null,
|
|
phone: null,
|
|
updated_at: new Date(),
|
|
created_at: new Date(),
|
|
app_metadata: {},
|
|
invited_at: null,
|
|
user_metadata: {},
|
|
is_anonymous: false,
|
|
profile: {
|
|
create: {
|
|
first_name: "Admin",
|
|
last_name: "Sigap",
|
|
username: "adminsigap",
|
|
},
|
|
},
|
|
},
|
|
});
|
|
|
|
return newUser;
|
|
}
|
|
|
|
private async createEvents(user: ICreateUser) {
|
|
const existingEvent = await this.prisma.events.findFirst({
|
|
where: {
|
|
user_id: user.id,
|
|
},
|
|
});
|
|
|
|
if (existingEvent) {
|
|
console.log("Events already exist, skipping creation.");
|
|
return existingEvent;
|
|
}
|
|
|
|
const event = await this.prisma.events.create({
|
|
data: {
|
|
name: `Crime Inserting Event For Crime 2020 - 2024`,
|
|
description: `Event for inserting crimes in region Jember`,
|
|
user_id: user.id,
|
|
},
|
|
});
|
|
|
|
return event;
|
|
}
|
|
|
|
private async createSessions(user: ICreateUser, events: events) {
|
|
const existingSession = await this.prisma.sessions.findFirst();
|
|
|
|
if (existingSession) {
|
|
console.log("Sessions already exist, skipping creation.");
|
|
return;
|
|
}
|
|
|
|
const newSession = await this.prisma.sessions.create({
|
|
data: {
|
|
user_id: user.id,
|
|
event_id: events.id,
|
|
status: session_status.completed,
|
|
},
|
|
});
|
|
|
|
return newSession;
|
|
}
|
|
|
|
private async chunkedCreateMany(data: any[], chunkSize: number = 500) {
|
|
for (let i = 0; i < data.length; i += chunkSize) {
|
|
const chunk = data.slice(i, i + chunkSize);
|
|
await this.prisma.crimes.createMany({
|
|
data: chunk,
|
|
});
|
|
}
|
|
}
|
|
|
|
private async importMonthlyCrimeData() {
|
|
console.log("Importing monthly crime data...");
|
|
|
|
const existingCrimes = await this.prisma.crimes.findFirst({
|
|
where: {
|
|
source_type: "cbu",
|
|
},
|
|
});
|
|
|
|
if (existingCrimes) {
|
|
console.log("General crimes data already exists, skipping import.");
|
|
return;
|
|
}
|
|
|
|
const csvFilePath = path.resolve(
|
|
__dirname,
|
|
"../data/excels/crimes/crime_monthly_by_unit.csv",
|
|
);
|
|
const fileContent = fs.readFileSync(csvFilePath, { encoding: "utf-8" });
|
|
|
|
const records = parse(fileContent, {
|
|
columns: true,
|
|
skip_empty_lines: true,
|
|
});
|
|
|
|
const processedDistricts = new Set<string>();
|
|
const crimesData: Array<Partial<crimes>> = [];
|
|
|
|
for (const record of records) {
|
|
const crimeRate = record.level.toLowerCase() as crime_rates;
|
|
|
|
const city = await this.prisma.cities.findFirst({
|
|
where: {
|
|
name: "Jember",
|
|
},
|
|
});
|
|
|
|
if (!city) {
|
|
console.error("City not found: Jember");
|
|
return;
|
|
}
|
|
|
|
const year = parseInt(record.year);
|
|
const crimeId = await generateIdWithDbCounter(
|
|
"crimes",
|
|
{
|
|
prefix: "CR",
|
|
segments: {
|
|
codes: [city.id],
|
|
sequentialDigits: 4,
|
|
year,
|
|
},
|
|
format: "{prefix}-{codes}-{sequence}-{year}",
|
|
separator: "-",
|
|
uniquenessStrategy: "counter",
|
|
},
|
|
CRegex.CR_YEAR_SEQUENCE,
|
|
);
|
|
|
|
crimesData.push({
|
|
id: crimeId,
|
|
district_id: record.district_id,
|
|
level: crimeRate,
|
|
method: record.method || null,
|
|
month: parseInt(record.month_num),
|
|
year: parseInt(record.year),
|
|
number_of_crime: parseInt(record.number_of_crime),
|
|
crime_cleared: parseInt(record.crime_cleared) || 0,
|
|
score: parseFloat(record.score),
|
|
source_type: "cbu",
|
|
});
|
|
|
|
processedDistricts.add(record.district_id);
|
|
}
|
|
|
|
await this.chunkedCreateMany(crimesData);
|
|
|
|
console.log(`Imported ${records.length} monthly crime records.`);
|
|
}
|
|
|
|
private async importYearlyCrimeData() {
|
|
console.log("Importing yearly crime data...");
|
|
|
|
const existingYearlySummary = await this.prisma.crimes.findFirst({
|
|
where: {
|
|
month: null,
|
|
source_type: "cbu",
|
|
},
|
|
});
|
|
|
|
if (existingYearlySummary) {
|
|
console.log("Yearly crime data already exists, skipping import.");
|
|
return;
|
|
}
|
|
|
|
const csvFilePath = path.resolve(
|
|
__dirname,
|
|
"../data/excels/crimes/crime_yearly_by_unit.csv",
|
|
);
|
|
const fileContent = fs.readFileSync(csvFilePath, { encoding: "utf-8" });
|
|
|
|
const records = parse(fileContent, {
|
|
columns: true,
|
|
skip_empty_lines: true,
|
|
});
|
|
|
|
const crimesData: Array<Partial<crimes>> = [];
|
|
|
|
for (const record of records) {
|
|
const crimeRate = record.level.toLowerCase() as crime_rates;
|
|
const year = parseInt(record.year);
|
|
|
|
const city = await this.prisma.cities.findFirst({
|
|
where: {
|
|
districts: {
|
|
some: {
|
|
id: record.district_id,
|
|
},
|
|
},
|
|
},
|
|
});
|
|
|
|
if (!city) {
|
|
console.error(`City not found for district ${record.district_id}`);
|
|
continue;
|
|
}
|
|
|
|
const crimeId = await generateIdWithDbCounter(
|
|
"crimes",
|
|
{
|
|
prefix: "CR",
|
|
segments: {
|
|
codes: [city.id],
|
|
sequentialDigits: 4,
|
|
year,
|
|
},
|
|
format: "{prefix}-{codes}-{sequence}-{year}",
|
|
separator: "-",
|
|
uniquenessStrategy: "counter",
|
|
},
|
|
CRegex.CR_YEAR_SEQUENCE,
|
|
);
|
|
|
|
crimesData.push({
|
|
id: crimeId,
|
|
district_id: record.district_id,
|
|
level: crimeRate,
|
|
method: record.method || "kmeans",
|
|
month: null,
|
|
year: year,
|
|
number_of_crime: parseInt(record.number_of_crime),
|
|
crime_cleared: parseInt(record.crime_cleared) || 0,
|
|
avg_crime: parseFloat(record.avg_crime) || 0,
|
|
score: parseInt(record.score),
|
|
source_type: "cbu",
|
|
});
|
|
}
|
|
|
|
await this.chunkedCreateMany(crimesData);
|
|
|
|
console.log(`Imported ${records.length} yearly crime records.`);
|
|
}
|
|
|
|
private async importAllYearSummaries() {
|
|
console.log("Importing all-year (2020-2024) crime summaries...");
|
|
|
|
const existingAllYearSummaries = await this.prisma.crimes.findFirst({
|
|
where: {
|
|
month: null,
|
|
year: null,
|
|
source_type: "cbu",
|
|
},
|
|
});
|
|
|
|
if (existingAllYearSummaries) {
|
|
console.log("All-year crime summaries already exist, skipping import.");
|
|
return;
|
|
}
|
|
|
|
const csvFilePath = path.resolve(
|
|
__dirname,
|
|
"../data/excels/crimes/crime_summary_by_unit.csv",
|
|
);
|
|
const fileContent = fs.readFileSync(csvFilePath, { encoding: "utf-8" });
|
|
|
|
const records = parse(fileContent, {
|
|
columns: true,
|
|
skip_empty_lines: true,
|
|
});
|
|
|
|
const crimesData: Array<Partial<crimes>> = [];
|
|
|
|
for (const record of records) {
|
|
const crimeRate = record.level.toLowerCase() as crime_rates;
|
|
const districtId = record.district_id;
|
|
|
|
const city = await this.prisma.cities.findFirst({
|
|
where: {
|
|
districts: {
|
|
some: {
|
|
id: districtId,
|
|
},
|
|
},
|
|
},
|
|
});
|
|
|
|
if (!city) {
|
|
console.error(`City not found for district ${districtId}`);
|
|
continue;
|
|
}
|
|
|
|
const crimeId = await generateIdWithDbCounter(
|
|
"crimes",
|
|
{
|
|
prefix: "CR",
|
|
segments: {
|
|
codes: [city.id],
|
|
sequentialDigits: 4,
|
|
},
|
|
format: "{prefix}-{codes}-{sequence}",
|
|
separator: "-",
|
|
uniquenessStrategy: "counter",
|
|
},
|
|
CRegex.CR_SEQUENCE_END,
|
|
);
|
|
|
|
crimesData.push({
|
|
id: crimeId,
|
|
district_id: districtId,
|
|
level: crimeRate,
|
|
method: "kmeans",
|
|
month: null,
|
|
year: null,
|
|
number_of_crime: parseInt(record.crime_total),
|
|
crime_cleared: parseInt(record.crime_cleared) || 0,
|
|
avg_crime: parseFloat(record.avg_crime) || 0,
|
|
score: parseFloat(record.score),
|
|
source_type: "cbu",
|
|
});
|
|
}
|
|
|
|
await this.chunkedCreateMany(crimesData);
|
|
|
|
console.log(`Imported ${records.length} all-year crime summaries.`);
|
|
}
|
|
|
|
private async importMonthlyCrimeDataByType() {
|
|
console.log("Importing monthly crime data by type...");
|
|
|
|
const existingCrimeByType = await this.prisma.crimes.findFirst({
|
|
where: {
|
|
source_type: "cbt",
|
|
},
|
|
});
|
|
|
|
if (existingCrimeByType) {
|
|
console.log("Crime data by type already exists, skipping import.");
|
|
return;
|
|
}
|
|
|
|
const csvFilePath = path.resolve(
|
|
__dirname,
|
|
"../data/excels/crimes/crime_monthly_by_type.csv",
|
|
);
|
|
const fileContent = fs.readFileSync(csvFilePath, { encoding: "utf-8" });
|
|
|
|
const records = parse(fileContent, {
|
|
columns: true,
|
|
skip_empty_lines: true,
|
|
});
|
|
|
|
const crimesData: Array<Partial<crimes>> = [];
|
|
|
|
for (const record of records) {
|
|
const crimeRate = record.level.toLowerCase() as crime_rates;
|
|
|
|
const city = await this.prisma.cities.findFirst({
|
|
where: {
|
|
name: "Jember",
|
|
},
|
|
});
|
|
|
|
if (!city) {
|
|
console.error("City not found: Jember");
|
|
continue;
|
|
}
|
|
|
|
const year = parseInt(record.year);
|
|
const crimeId = await generateIdWithDbCounter(
|
|
"crimes",
|
|
{
|
|
prefix: "CR",
|
|
segments: {
|
|
codes: [city.id],
|
|
sequentialDigits: 4,
|
|
year,
|
|
},
|
|
format: "{prefix}-{codes}-{sequence}-{year}",
|
|
separator: "-",
|
|
uniquenessStrategy: "counter",
|
|
},
|
|
CRegex.CR_YEAR_SEQUENCE,
|
|
);
|
|
|
|
crimesData.push({
|
|
id: crimeId,
|
|
district_id: record.district_id,
|
|
level: crimeRate,
|
|
method: record.method || "kmeans",
|
|
month: parseInt(record.month_num),
|
|
year: parseInt(record.year),
|
|
number_of_crime: parseInt(record.number_of_crime),
|
|
crime_cleared: parseInt(record.crime_cleared) || 0,
|
|
score: parseFloat(record.score),
|
|
source_type: "cbt",
|
|
});
|
|
}
|
|
|
|
await this.chunkedCreateMany(crimesData);
|
|
|
|
console.log(`Imported ${records.length} monthly crime by type records.`);
|
|
}
|
|
|
|
private async importYearlyCrimeDataByType() {
|
|
console.log("Importing yearly crime data by type...");
|
|
|
|
const existingYearlySummary = await this.prisma.crimes.findFirst({
|
|
where: {
|
|
month: null,
|
|
source_type: "cbt",
|
|
},
|
|
});
|
|
|
|
if (existingYearlySummary) {
|
|
console.log("Yearly crime data by type already exists, skipping import.");
|
|
return;
|
|
}
|
|
|
|
const csvFilePath = path.resolve(
|
|
__dirname,
|
|
"../data/excels/crimes/crime_yearly_by_type.csv",
|
|
);
|
|
const fileContent = fs.readFileSync(csvFilePath, { encoding: "utf-8" });
|
|
|
|
const records = parse(fileContent, {
|
|
columns: true,
|
|
skip_empty_lines: true,
|
|
});
|
|
|
|
const crimesData: Array<Partial<crimes>> = [];
|
|
|
|
for (const record of records) {
|
|
const crimeRate = record.level.toLowerCase() as crime_rates;
|
|
const year = parseInt(record.year);
|
|
|
|
const city = await this.prisma.cities.findFirst({
|
|
where: {
|
|
districts: {
|
|
some: {
|
|
id: record.district_id,
|
|
},
|
|
},
|
|
},
|
|
});
|
|
|
|
if (!city) {
|
|
console.error(`City not found for district ${record.district_id}`);
|
|
continue;
|
|
}
|
|
|
|
const crimeId = await generateIdWithDbCounter(
|
|
"crimes",
|
|
{
|
|
prefix: "CR",
|
|
segments: {
|
|
codes: [city.id],
|
|
sequentialDigits: 4,
|
|
year,
|
|
},
|
|
format: "{prefix}-{codes}-{sequence}-{year}",
|
|
separator: "-",
|
|
uniquenessStrategy: "counter",
|
|
},
|
|
CRegex.CR_YEAR_SEQUENCE,
|
|
);
|
|
|
|
crimesData.push({
|
|
id: crimeId,
|
|
district_id: record.district_id,
|
|
level: crimeRate,
|
|
method: record.method || "kmeans",
|
|
month: null,
|
|
year: year,
|
|
number_of_crime: parseInt(record.number_of_crime),
|
|
crime_cleared: parseInt(record.crime_cleared) || 0,
|
|
avg_crime: parseFloat(record.avg_crime) || 0,
|
|
score: parseInt(record.score),
|
|
source_type: "cbt",
|
|
});
|
|
}
|
|
|
|
await this.chunkedCreateMany(crimesData);
|
|
|
|
console.log(`Imported ${records.length} yearly crime by type records.`);
|
|
}
|
|
|
|
private async importSummaryByType() {
|
|
console.log("Importing crime summary by type...");
|
|
|
|
const existingSummary = await this.prisma.crimes.findFirst({
|
|
where: {
|
|
source_type: "cbt",
|
|
},
|
|
});
|
|
|
|
if (existingSummary) {
|
|
console.log("Crime summary by type already exists, skipping import.");
|
|
return;
|
|
}
|
|
|
|
const csvFilePath = path.resolve(
|
|
__dirname,
|
|
"../data/excels/crimes/crime_summary_by_type.csv",
|
|
);
|
|
const fileContent = fs.readFileSync(csvFilePath, { encoding: "utf-8" });
|
|
|
|
const records = parse(fileContent, {
|
|
columns: true,
|
|
skip_empty_lines: true,
|
|
});
|
|
|
|
const crimesData: Array<Partial<crimes>> = [];
|
|
|
|
for (const record of records) {
|
|
const crimeRate = record.level.toLowerCase() as crime_rates;
|
|
|
|
const city = await this.prisma.cities.findFirst({
|
|
where: {
|
|
name: "Jember",
|
|
},
|
|
});
|
|
|
|
if (!city) {
|
|
console.error("City not found: Jember");
|
|
continue;
|
|
}
|
|
|
|
const crimeId = await generateIdWithDbCounter(
|
|
"crimes",
|
|
{
|
|
prefix: "CR",
|
|
segments: {
|
|
codes: [city.id],
|
|
sequentialDigits: 4,
|
|
},
|
|
format: "{prefix}-{codes}-{sequence}",
|
|
separator: "-",
|
|
uniquenessStrategy: "counter",
|
|
},
|
|
CRegex.CR_SEQUENCE_END,
|
|
);
|
|
|
|
crimesData.push({
|
|
id: crimeId,
|
|
district_id: record.district_id,
|
|
level: crimeRate,
|
|
method: "kmeans",
|
|
month: null,
|
|
year: null,
|
|
number_of_crime: parseInt(record.crime_total),
|
|
crime_cleared: parseInt(record.crime_cleared) || 0,
|
|
avg_crime: parseFloat(record.avg_crime) || 0,
|
|
score: parseFloat(record.score),
|
|
source_type: "cbt",
|
|
});
|
|
}
|
|
|
|
await this.chunkedCreateMany(crimesData);
|
|
|
|
console.log(`Imported ${records.length} crime summary by type records.`);
|
|
}
|
|
}
|
|
|
|
// This allows the file to be executed standalone for testing
|
|
if (require.main === module) {
|
|
const testSeeder = async () => {
|
|
const prisma = new PrismaClient();
|
|
const seeder = new CrimesSeeder(prisma);
|
|
try {
|
|
await seeder.run();
|
|
} catch (e) {
|
|
console.error("Error during seeding:", e);
|
|
process.exit(1);
|
|
} finally {
|
|
await prisma.$disconnect();
|
|
}
|
|
};
|
|
|
|
testSeeder();
|
|
}
|