429 lines
11 KiB
TypeScript
429 lines
11 KiB
TypeScript
import {
|
|
PrismaClient,
|
|
crime_rates,
|
|
events,
|
|
session_status,
|
|
users,
|
|
} from '@prisma/client';
|
|
import fs from 'fs';
|
|
import path from 'path';
|
|
import { parse } from 'csv-parse/sync';
|
|
import { generateId } from '../../app/_utils/common';
|
|
|
|
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();
|
|
|
|
// Create 5 events
|
|
const events = await this.createEvents(user);
|
|
|
|
// Create 5 sessions with completed status
|
|
await this.createSessions(user, events);
|
|
|
|
// Import monthly crime data
|
|
await this.importMonthlyCrimeData();
|
|
|
|
// Import yearly crime data from CSV file
|
|
await this.importYearlyCrimeData();
|
|
|
|
console.log('✅ Crime seeding completed successfully.');
|
|
} catch (error) {
|
|
console.error('❌ Error seeding crimes:', error);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
private async createUsers() {
|
|
// Check if test users already exist
|
|
const existingUser = await this.prisma.users.findFirst({
|
|
where: { email: 'admin@sigap.id' },
|
|
});
|
|
|
|
if (existingUser) {
|
|
console.log('Users already exist, skipping creation.');
|
|
return existingUser;
|
|
}
|
|
|
|
// Get admin role ID
|
|
let roleId = await this.prisma.roles.findFirst({
|
|
where: { name: 'admin' },
|
|
});
|
|
|
|
if (!roleId) {
|
|
roleId = await this.prisma.roles.create({
|
|
data: {
|
|
name: 'admin',
|
|
description: 'Administrator role',
|
|
},
|
|
});
|
|
}
|
|
|
|
// Create test users
|
|
const user = await this.prisma.users.create({
|
|
data: {
|
|
email: `admin@sigap.id`,
|
|
roles_id: roleId.id,
|
|
encrypted_password: 'sigap123',
|
|
confirmed_at: new Date(),
|
|
email_confirmed_at: new Date(),
|
|
is_anonymous: false,
|
|
},
|
|
});
|
|
|
|
return user;
|
|
}
|
|
|
|
private async createEvents(user: users) {
|
|
// Check if events already exist
|
|
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: users, events: events) {
|
|
// Check if sessions already exist
|
|
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 importMonthlyCrimeData() {
|
|
console.log('Importing monthly crime data...');
|
|
|
|
// Check if crimes already exist
|
|
const existingCrimes = await this.prisma.crimes.findFirst();
|
|
if (existingCrimes) {
|
|
console.log('Crimes data already exists, skipping import.');
|
|
return;
|
|
}
|
|
|
|
// Read CSV file
|
|
const csvFilePath = path.resolve(
|
|
__dirname,
|
|
'../data/excels/crimes/crime_monthly.csv'
|
|
);
|
|
const fileContent = fs.readFileSync(csvFilePath, { encoding: 'utf-8' });
|
|
|
|
// Parse CSV
|
|
const records = parse(fileContent, {
|
|
columns: true,
|
|
skip_empty_lines: true,
|
|
});
|
|
|
|
// Store unique district IDs to avoid duplicates
|
|
const processedDistricts = new Set<string>();
|
|
|
|
// Import records
|
|
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);
|
|
// Create a unique ID for monthly crime data
|
|
const crimeId = generateId({
|
|
prefix: 'CR',
|
|
segments: {
|
|
codes: [city.id],
|
|
sequentialDigits: 4,
|
|
year,
|
|
},
|
|
format: '{prefix}-{codes}-{sequence}-{year}',
|
|
separator: '-',
|
|
randomSequence: false,
|
|
uniquenessStrategy: 'counter',
|
|
});
|
|
|
|
await this.prisma.crimes.create({
|
|
data: {
|
|
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),
|
|
score: parseFloat(record.score),
|
|
},
|
|
});
|
|
|
|
// Keep track of unique districts for later creation of crime incidents
|
|
processedDistricts.add(record.district_id);
|
|
}
|
|
|
|
console.log(`Imported ${records.length} monthly crime records.`);
|
|
}
|
|
|
|
private async importYearlyCrimeData() {
|
|
console.log('Importing yearly crime data...');
|
|
|
|
// Check if yearly summaries already exist (records with null month)
|
|
const existingYearlySummary = await this.prisma.crimes.findFirst({
|
|
where: { month: null },
|
|
});
|
|
|
|
if (existingYearlySummary) {
|
|
console.log('Yearly crime data already exists, skipping import.');
|
|
return;
|
|
}
|
|
|
|
// Read CSV file
|
|
const csvFilePath = path.resolve(
|
|
__dirname,
|
|
'../data/excels/crimes/crime_yearly.csv'
|
|
);
|
|
const fileContent = fs.readFileSync(csvFilePath, { encoding: 'utf-8' });
|
|
|
|
// Parse CSV
|
|
const records = parse(fileContent, {
|
|
columns: true,
|
|
skip_empty_lines: true,
|
|
});
|
|
|
|
// Import records
|
|
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;
|
|
}
|
|
|
|
// Create a unique ID for yearly crime data
|
|
const crimeId = generateId({
|
|
prefix: 'CR',
|
|
segments: {
|
|
codes: [city.id],
|
|
sequentialDigits: 4,
|
|
year,
|
|
},
|
|
format: '{prefix}-{codes}-{sequence}-{year}',
|
|
separator: '-',
|
|
randomSequence: false,
|
|
uniquenessStrategy: 'counter',
|
|
});
|
|
|
|
await this.prisma.crimes.create({
|
|
data: {
|
|
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),
|
|
score: parseInt(record.score),
|
|
},
|
|
});
|
|
}
|
|
|
|
console.log(`Imported ${records.length} yearly crime records.`);
|
|
}
|
|
|
|
// private async generateYearlyCrimeSummaries() {
|
|
// console.log('Generating yearly crime summaries...');
|
|
|
|
// // Check if yearly summaries already exist (records with null month)
|
|
// const existingYearlySummary = await this.prisma.crimes.findFirst({
|
|
// where: { month: null },
|
|
// });
|
|
|
|
// if (existingYearlySummary) {
|
|
// console.log('Yearly summaries already exist, skipping generation.');
|
|
// return;
|
|
// }
|
|
|
|
// // Get all districts and years combinations
|
|
// const districtsYears = await this.prisma.crimes.findMany({
|
|
// select: {
|
|
// district_id: true,
|
|
// year: true,
|
|
// },
|
|
// distinct: ['district_id', 'year'],
|
|
// });
|
|
|
|
// // For each district and year, calculate yearly summary
|
|
// for (const { district_id, year } of districtsYears) {
|
|
// // Calculate sum of crimes for the district and year
|
|
// const result = await this.prisma.crimes.aggregate({
|
|
// _sum: {
|
|
// number_of_crime: true,
|
|
// },
|
|
// where: {
|
|
// district_id: district_id,
|
|
// year: year,
|
|
// month: {
|
|
// not: null,
|
|
// },
|
|
// },
|
|
// });
|
|
|
|
// if (!result || !result._sum.number_of_crime) {
|
|
// console.log(
|
|
// `No monthly data found for district ${district_id} in year ${year}. Skipping...`
|
|
// );
|
|
// continue;
|
|
// }
|
|
|
|
// const totalCrimes = result._sum.number_of_crime;
|
|
|
|
// // Get average level based on monthly data (use the most common level)
|
|
// const levelResult = await this.prisma.crimes
|
|
// .groupBy({
|
|
// by: ['level'],
|
|
// where: {
|
|
// district_id: district_id,
|
|
// year: year,
|
|
// month: {
|
|
// not: null,
|
|
// },
|
|
// },
|
|
// _count: {
|
|
// level: true,
|
|
// },
|
|
// orderBy: {
|
|
// _count: {
|
|
// level: 'desc',
|
|
// },
|
|
// },
|
|
// take: 1,
|
|
// })
|
|
// .then((results) =>
|
|
// results.map((result) => ({
|
|
// level: result.level,
|
|
// count: result._count.level,
|
|
// }))
|
|
// );
|
|
|
|
// if (levelResult.length === 0) {
|
|
// console.log(
|
|
// `No level data found for district ${district_id} in year ${year}. Skipping...`
|
|
// );
|
|
// continue;
|
|
// }
|
|
|
|
// const level = levelResult[0].level || 'low';
|
|
|
|
// const city = await this.prisma.cities.findFirst({
|
|
// where: {
|
|
// districts: {
|
|
// some: {
|
|
// id: district_id,
|
|
// },
|
|
// },
|
|
// },
|
|
// });
|
|
|
|
// if (!city) {
|
|
// console.error(`City not found for district ${district_id}`);
|
|
// continue;
|
|
// }
|
|
|
|
// // Create yearly summary record
|
|
// const newCrimeId = generateId({
|
|
// prefix: 'CR',
|
|
// segments: {
|
|
// codes: [city.id],
|
|
// sequentialDigits: 4,
|
|
// year,
|
|
// },
|
|
// format: '{prefix}-{codes}-{sequence}-{year}',
|
|
// separator: '-',
|
|
// randomSequence: false,
|
|
// uniquenessStrategy: 'counter',
|
|
// });
|
|
|
|
// await this.prisma.crimes.create({
|
|
// data: {
|
|
// id: newCrimeId,
|
|
// district_id: district_id as string,
|
|
// level: level as crime_rates,
|
|
// method: 'kmeans',
|
|
// month: null,
|
|
// year: year as number,
|
|
// number_of_crime: totalCrimes,
|
|
// score: 100 - Math.min(totalCrimes, 100), // Simple score calculation
|
|
// },
|
|
// });
|
|
// }
|
|
|
|
// console.log(
|
|
// `Generated yearly summaries for ${districtsYears.length} district-year combinations.`
|
|
// );
|
|
// }
|
|
}
|
|
|
|
// 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();
|
|
}
|