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, generateIdWithDbCounter } from '../../app/_utils/common'; 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 { console.log('🌱 Seeding crimes data...'); // Clear existing data try { // Create test user const user = await this.createUsers(); 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 monthly crime data await this.importMonthlyCrimeData(); // Import yearly crime data from CSV file await this.importYearlyCrimeData(); // Import all-year crime summaries (2020-2024) await this.importAllYearSummaries(); 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: 'sigapcompany@gmail.com' }, }); 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 user directly with Prisma (no Supabase) 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) { // 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: ICreateUser, 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; } // Helper function for chunked insertion (with default chunk size 500) 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...'); // 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(); // Prepare batch data const crimesData = []; // Process 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 = await generateIdWithDbCounter( 'crimes', { prefix: 'CR', segments: { codes: [city.id], sequentialDigits: 4, year, }, format: '{prefix}-{codes}-{sequence}-{year}', separator: '-', uniquenessStrategy: 'counter', }, /(\d{4})(?=-\d{4}$)/ // Pattern to extract the 4-digit counter ); // console.log('Creating crime ID:', crimeId); 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), score: parseFloat(record.score), }); // Keep track of unique districts for later creation of crime incidents processedDistricts.add(record.district_id); } // Batch create all crimes in chunks await this.chunkedCreateMany(crimesData); 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, }); // Prepare batch data const crimesData = []; // Process 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; } const crimeId = await generateIdWithDbCounter( 'crimes', { prefix: 'CR', segments: { codes: [city.id], sequentialDigits: 4, year, }, format: '{prefix}-{codes}-{sequence}-{year}', separator: '-', uniquenessStrategy: 'counter', }, /(\d{4})(?=-\d{4}$)/ // Pattern to extract the 4-digit counter ); 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), score: parseInt(record.score), }); } // Batch create all yearly crimes in chunks await this.chunkedCreateMany(crimesData); console.log(`Imported ${records.length} yearly crime records.`); } private async importAllYearSummaries() { console.log('Importing all-year (2020-2024) crime summaries...'); // Check if all-year summaries already exist (records with null month and null year) const existingAllYearSummaries = await this.prisma.crimes.findFirst({ where: { month: null, year: null }, }); if (existingAllYearSummaries) { console.log('All-year crime summaries already exist, skipping import.'); return; } // Read CSV file const csvFilePath = path.resolve( __dirname, '../data/excels/crimes/district_summary_2020_2024.csv' ); const fileContent = fs.readFileSync(csvFilePath, { encoding: 'utf-8' }); // Parse CSV const records = parse(fileContent, { columns: true, skip_empty_lines: true, }); // Prepare batch data const crimesData = []; 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; } // Create a unique ID for all-year summary data const crimeId = await generateIdWithDbCounter( 'crimes', { prefix: 'CR', segments: { codes: [city.id], sequentialDigits: 4, }, format: '{prefix}-{codes}-{sequence}', separator: '-', uniquenessStrategy: 'counter', }, /(\d{4})$/ // Pattern to extract the 4-digit counter at the end ); crimesData.push({ id: crimeId, district_id: districtId, level: crimeRate, method: 'kmeans', month: null, year: null, number_of_crime: parseInt(record.crime_total), score: parseFloat(record.avg_score), }); } // Batch create all all-year summaries in chunks await this.chunkedCreateMany(crimesData); console.log(`Imported ${records.length} all-year crime summaries.`); } } // 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(); }