Fixed scientists importing, and added linking by name

This commit is contained in:
Tim Howitz 2025-05-25 11:57:51 +01:00
parent db6ba00ded
commit 7311e379e8
3 changed files with 95 additions and 23 deletions

View File

@ -3,4 +3,4 @@
- [x] Import earthquakes - [x] Import earthquakes
- [x] Import observatories - [x] Import observatories
- [ ] Import requests - [ ] Import requests
- [ ] Import scientists - [x] Import scientists

6
importersOrder.md Normal file
View File

@ -0,0 +1,6 @@
1. Import users
2. Import scientists
3. Import observatories
4. Import earthquakes
5. Import artefacts
6. Import requests

View File

@ -2,21 +2,18 @@ import { parse } from "csv-parse/sync";
import fs from "fs/promises"; import fs from "fs/promises";
import { NextResponse } from "next/server"; import { NextResponse } from "next/server";
import path from "path"; import path from "path";
import { prisma } from "@utils/prisma"; import { prisma } from "@utils/prisma";
import { getRandomNumber } from "@utils/maths";
// Path to CSV file
const csvFilePath = path.resolve(process.cwd(), "public/scientists.csv"); const csvFilePath = path.resolve(process.cwd(), "public/scientists.csv");
type CsvRow = { type CsvRow = {
Name: string; Name: string;
Level?: string; Level: string;
UserId: string; SuperiorName: string;
SuperiorId?: string;
}; };
function normalizeLevel(level: string | undefined): string { function normalizeLevel(level: string | undefined): string {
// Only allow JUNIOR, SENIOR; default JUNIOR
if (!level || !level.trim()) return "JUNIOR"; if (!level || !level.trim()) return "JUNIOR";
const lv = level.trim().toUpperCase(); const lv = level.trim().toUpperCase();
return ["JUNIOR", "SENIOR"].includes(lv) ? lv : "JUNIOR"; return ["JUNIOR", "SENIOR"].includes(lv) ? lv : "JUNIOR";
@ -24,31 +21,100 @@ function normalizeLevel(level: string | undefined): string {
export async function POST() { export async function POST() {
try { try {
// 1. Read the CSV file
const fileContent = await fs.readFile(csvFilePath, "utf8"); const fileContent = await fs.readFile(csvFilePath, "utf8");
// 2. Parse the CSV
const records: CsvRow[] = parse(fileContent, { const records: CsvRow[] = parse(fileContent, {
columns: true, columns: true,
skip_empty_lines: true, skip_empty_lines: true,
}); });
// 3. Transform each record for Prisma const failedImports: { row: CsvRow; reason: string }[] = [];
// todo add senior scientists first
const scientists = records.map((row) => ({
name: row.Name,
level: normalizeLevel(row.Level),
userId: parseInt(row.UserId, 10),
// todo get superior id by name from db
superiorId: row.SuperiorId && row.SuperiorId.trim() !== "" ? parseInt(row.SuperiorId, 10) : null,
}));
// 4. Bulk create scientists in database // Fetch all users to match names
await prisma.scientist.createMany({ const users = await prisma.user.findMany({
data: scientists, select: { id: true, name: true },
}); });
return NextResponse.json({ success: true, count: scientists.length }); // Process seniors first to ensure superiors exist for juniors
const seniorScientists = records.filter((row) => normalizeLevel(row.Level) === "SENIOR");
const juniorScientists = records.filter((row) => normalizeLevel(row.Level) === "JUNIOR");
const seniorScientistNames = new Map<string, number>(); // Map name to ID
const seniors = await Promise.all(
seniorScientists.map(async (row) => {
const user = users.find((u) => u.name === row.Name);
if (!user) {
failedImports.push({ row, reason: `User not found: ${row.Name}` });
return null;
}
return {
name: row.Name,
level: normalizeLevel(row.Level),
userId: user.id,
superiorId: null, // Seniors have no superior
};
})
);
const validSeniors = seniors.filter((senior): senior is NonNullable<typeof senior> => senior !== null);
// Create senior scientists and store their IDs
for (const senior of validSeniors) {
try {
const scientist = await prisma.scientist.create({ data: senior });
seniorScientistNames.set(senior.name, scientist.id);
} catch (error: any) {
failedImports.push({
row: { Name: senior.name, Level: senior.level, SuperiorName: "None" },
reason: `Failed to create senior scientist: ${error.message}`,
});
}
}
// Process junior scientists
const juniors = await Promise.all(
juniorScientists.map(async (row) => {
const user = users.find((u) => u.name === row.Name);
if (!user) {
failedImports.push({ row, reason: `User not found: ${row.Name}` });
return null;
}
let superiorId: number | null = null;
if (row.SuperiorName && row.SuperiorName.trim() !== "None") {
superiorId = seniorScientistNames.get(row.SuperiorName.trim()) || null;
if (!superiorId) {
failedImports.push({ row, reason: `Superior not found: ${row.SuperiorName}` });
return null;
}
}
return {
name: row.Name,
level: normalizeLevel(row.Level),
userId: user.id,
superiorId,
};
})
);
const validJuniors = juniors.filter((junior): junior is NonNullable<typeof junior> => junior !== null);
// Bulk create junior scientists
await prisma.scientist.createMany({
data: validJuniors,
});
if (failedImports.length > 0) {
console.warn("Failed imports:", failedImports);
await fs.writeFile(path.resolve(process.cwd(), "failed_imports_scientists.json"), JSON.stringify(failedImports, null, 2));
}
return NextResponse.json({
success: true,
count: validSeniors.length + validJuniors.length,
failedCount: failedImports.length,
});
} catch (error: any) { } catch (error: any) {
console.error(error); console.error(error);
return NextResponse.json({ success: false, error: error.message }, { status: 500 }); return NextResponse.json({ success: false, error: error.message }, { status: 500 });