2025-06-30 18:58:24 +02:00
|
|
|
/*
|
|
|
|
SPDX-FileCopyrightText: © 2025 Hornwitser <code@hornwitser.no>
|
|
|
|
SPDX-License-Identifier: AGPL-3.0-or-later
|
|
|
|
*/
|
2025-06-11 21:05:17 +02:00
|
|
|
import type { ApiAccount, ApiSchedule, ApiScheduleEventSlot, ApiScheduleShiftSlot } from "~/shared/types/api";
|
2025-03-15 13:46:13 +01:00
|
|
|
import { toId } from "~/shared/utils/functions";
|
2025-03-05 22:15:46 +01:00
|
|
|
|
|
|
|
const locations = [
|
|
|
|
{
|
2025-06-11 21:05:17 +02:00
|
|
|
id: 1,
|
2025-03-05 22:15:46 +01:00
|
|
|
name: "Stage",
|
2025-06-11 21:05:17 +02:00
|
|
|
description: "Inside the main building.",
|
2025-06-30 15:20:57 +02:00
|
|
|
updatedAt: "d-1 18:21",
|
2025-03-05 22:15:46 +01:00
|
|
|
},
|
|
|
|
{
|
2025-06-11 21:05:17 +02:00
|
|
|
id: 2,
|
2025-03-05 22:15:46 +01:00
|
|
|
name: "Clubhouse",
|
2025-06-11 21:05:17 +02:00
|
|
|
description: "That big red building in the middle of the park.",
|
2025-06-30 15:20:57 +02:00
|
|
|
updatedAt: "d-1 18:25",
|
2025-03-05 22:15:46 +01:00
|
|
|
},
|
|
|
|
{
|
2025-06-11 21:05:17 +02:00
|
|
|
id: 4,
|
2025-03-05 22:15:46 +01:00
|
|
|
name: "Summerhouse",
|
2025-06-11 21:05:17 +02:00
|
|
|
description: "Next to the campfire by the lake",
|
2025-06-30 15:20:57 +02:00
|
|
|
updatedAt: "d-1 18:22",
|
2025-03-05 22:15:46 +01:00
|
|
|
},
|
|
|
|
{
|
2025-06-11 21:05:17 +02:00
|
|
|
id: 6,
|
2025-03-05 22:15:46 +01:00
|
|
|
name: "Campfire",
|
2025-06-11 21:05:17 +02:00
|
|
|
description: "Next to the big tree by the lake.",
|
2025-06-30 15:20:57 +02:00
|
|
|
updatedAt: "d-1 18:41",
|
2025-03-05 22:15:46 +01:00
|
|
|
},
|
|
|
|
{
|
2025-06-11 21:05:17 +02:00
|
|
|
id: 7,
|
2025-03-05 22:15:46 +01:00
|
|
|
name: "Outside",
|
2025-06-11 21:05:17 +02:00
|
|
|
description: "Takes place somewhere outside.",
|
2025-06-30 15:20:57 +02:00
|
|
|
updatedAt: "d-1 18:37",
|
2025-03-05 22:15:46 +01:00
|
|
|
}
|
|
|
|
]
|
|
|
|
|
2025-06-11 21:05:17 +02:00
|
|
|
let slotId = 1;
|
|
|
|
let eventId = 1;
|
2025-03-05 22:15:46 +01:00
|
|
|
const events = [
|
|
|
|
{
|
|
|
|
name: "Arcade",
|
|
|
|
description: "Play retro games!",
|
|
|
|
slots: [
|
|
|
|
"d1 12:00 4h clubhouse",
|
|
|
|
"d2 12:00 4h clubhouse",
|
|
|
|
"d3 12:00 4h clubhouse",
|
2025-03-10 14:40:02 +01:00
|
|
|
],
|
2025-03-05 22:15:46 +01:00
|
|
|
},
|
2025-03-10 14:40:02 +01:00
|
|
|
{ name: "Arcade Setup", crew: true, slots: ["d1 11:00 1h clubhouse"], },
|
2025-03-05 22:15:46 +01:00
|
|
|
{
|
|
|
|
name: "Bonfire Stories",
|
|
|
|
description: "Share your stories as we sit cosily around the campfire.",
|
2025-03-10 14:40:02 +01:00
|
|
|
slots: ["d2 18:00 2h campfire"],
|
|
|
|
},
|
|
|
|
{ name: "Stage Rigging", crew: true, slots: ["d1 11:00 7h30m stage"]},
|
|
|
|
{
|
|
|
|
name: "Reconfigure for DJ",
|
|
|
|
crew: true,
|
|
|
|
slots: [
|
|
|
|
"d2 19:00 1h stage",
|
|
|
|
"d3 18:45 1h15m stage",
|
|
|
|
],
|
2025-03-05 22:15:46 +01:00
|
|
|
},
|
|
|
|
{ name: "DJ Alpha", slots: ["d2 20:00 2h stage"] },
|
|
|
|
{ name: "DJ Bravo", slots: ["d3 20:00 2h stage"] },
|
|
|
|
{ name: "DJ Charlie", slots: ["d3 22:00 2h stage"] },
|
2025-03-10 14:40:02 +01:00
|
|
|
{ name: "Prepare Fursuit Games", crew: true, slots: ["d4 17:00 1h clubhouse"] },
|
2025-03-05 22:15:46 +01:00
|
|
|
{
|
|
|
|
name: "Fursuit Games",
|
|
|
|
description: "Playful time for the suiters.",
|
|
|
|
slots: ["d4 18:00 2h clubhouse"],
|
|
|
|
},
|
|
|
|
{ name: "Fishing Trip", slots: ["d3 12:00 3h30m outside"]},
|
|
|
|
{ name: "Opening", slots: ["d1 18:30 1h30m stage"]},
|
2025-03-10 20:58:33 +01:00
|
|
|
{ name: "Closing", slots: ["d5 10:00 1h stage"]},
|
|
|
|
{ name: "Stage Teardown", crew: true, slots: ["d5 11:00 4h stage"]},
|
2025-03-10 14:40:02 +01:00
|
|
|
{ name: "Setup Board Games", crew: true, slots: ["d1 11:30 30m summerhouse"]},
|
2025-03-05 22:15:46 +01:00
|
|
|
{
|
|
|
|
name: "Board Games",
|
|
|
|
slots: [
|
|
|
|
"d1 12:00 4h summerhouse",
|
|
|
|
"d2 12:00 4h summerhouse",
|
|
|
|
"d3 12:00 4h summerhouse",
|
|
|
|
"d4 12:00 4h summerhouse",
|
2025-03-10 14:40:02 +01:00
|
|
|
],
|
2025-03-05 22:15:46 +01:00
|
|
|
},
|
2025-03-10 14:40:02 +01:00
|
|
|
{ name: "Teardown Board Games", crew: true, slots: ["d4 16:00 30m summerhouse"]},
|
2025-03-05 22:15:46 +01:00
|
|
|
{ name: "📷meet", slots: ["d3 19:00 1h10m summerhouse"]},
|
2025-03-10 14:40:02 +01:00
|
|
|
{
|
|
|
|
name: "Prepare Karaoke",
|
|
|
|
crew: true,
|
|
|
|
slots: [
|
|
|
|
"d3 20:00 1h clubhouse",
|
|
|
|
"d4 20:00 1h clubhouse",
|
|
|
|
],
|
|
|
|
},
|
2025-03-05 22:15:46 +01:00
|
|
|
{
|
|
|
|
name: "Karaoke",
|
|
|
|
slots: [
|
|
|
|
"d3 21:00 2h clubhouse",
|
|
|
|
"d4 21:00 2h clubhouse",
|
|
|
|
],
|
|
|
|
},
|
|
|
|
{ name: "Dance", slots: ["d1 20:00 3h stage"]},
|
2025-03-10 14:40:02 +01:00
|
|
|
{ name: "Prepare Charity Auction", crew: true, slots: ["d4 10:00 3h stage"]},
|
2025-03-05 22:15:46 +01:00
|
|
|
{ name: "Charity Auction", slots: ["d4 13:00 2h stage"]},
|
2025-03-10 14:40:02 +01:00
|
|
|
{ name: "Tournament Preparation", crew: true, slots: ["d2 15:00 2h stage"]},
|
2025-03-05 22:15:46 +01:00
|
|
|
{ name: "Tournament", slots: ["d2 17:00 2h stage"]},
|
|
|
|
{ name: "Canoe Trip", slots: ["d4 11:00 4h30m outside"]},
|
2025-03-10 14:40:02 +01:00
|
|
|
{
|
|
|
|
name: "Dinner Preparations",
|
|
|
|
crew: true,
|
|
|
|
slots: [
|
|
|
|
"d1 14:00 2h campfire",
|
|
|
|
"d2 14:00 2h campfire",
|
|
|
|
"d3 14:00 2h campfire",
|
|
|
|
"d4 14:00 2h campfire",
|
|
|
|
],
|
|
|
|
},
|
2025-03-05 22:15:46 +01:00
|
|
|
{
|
|
|
|
name: "Dinner",
|
|
|
|
slots: [
|
|
|
|
"d1 16:00 1h campfire",
|
|
|
|
"d2 16:00 1h campfire",
|
|
|
|
"d3 16:00 1h campfire",
|
|
|
|
"d4 16:00 1h campfire",
|
|
|
|
],
|
|
|
|
},
|
2025-03-10 14:40:02 +01:00
|
|
|
{
|
|
|
|
name: "Dinner Cleanup",
|
|
|
|
crew: true,
|
|
|
|
slots: [
|
|
|
|
"d1 17:00 1h campfire",
|
|
|
|
"d2 17:00 1h campfire",
|
|
|
|
"d3 17:00 1h campfire",
|
|
|
|
"d4 17:00 1h campfire",
|
|
|
|
],
|
|
|
|
},
|
|
|
|
{ name: "Prepare Film Night", crew: true, slots: ["d4 19:00 2h stage"]},
|
2025-03-05 22:15:46 +01:00
|
|
|
{ name: "Film Night", slots: ["d4 21:00 2h stage"]},
|
2025-03-10 14:40:02 +01:00
|
|
|
{ name: "Prepare Group Photo", crew: true, slots: ["d3 17:00 1h stage"]},
|
|
|
|
{ name: "Group Photo", slots: ["d3 18:00 45m stage"]},
|
|
|
|
{ name: "Setup Artist Alley", crew: true, slots: ["d4 10:00 2h clubhouse"]},
|
2025-03-05 22:15:46 +01:00
|
|
|
{ name: "Artist Alley", slots: ["d4 12:00 4h clubhouse"]},
|
2025-03-10 14:40:02 +01:00
|
|
|
{ name: "Teardown Artist Alley", crew: true, slots: ["d4 16:00 1h clubhouse"]},
|
2025-03-10 20:58:33 +01:00
|
|
|
{ name: "Feedback Panel", slots: ["d5 12:00 1h clubhouse"]},
|
2025-06-11 21:05:17 +02:00
|
|
|
].map(({ slots, ...rest }) => ({
|
|
|
|
...rest,
|
|
|
|
id: eventId++,
|
|
|
|
slots: slots.map(slot => `${slot} ${slotId++}`),
|
|
|
|
}));
|
|
|
|
|
2025-03-05 22:15:46 +01:00
|
|
|
|
2025-06-11 21:05:17 +02:00
|
|
|
const idMedic = 1;
|
|
|
|
const idSecurity = 2;
|
|
|
|
const roles = [
|
2025-06-30 15:20:57 +02:00
|
|
|
{ id: idMedic, name: "Medic", updatedAt: "d-2 12:34" },
|
|
|
|
{ id: idSecurity, name: "Security", updatedAt: "d-2 12:39" },
|
2025-03-10 20:58:33 +01:00
|
|
|
]
|
|
|
|
|
2025-06-11 21:05:17 +02:00
|
|
|
const shifts = [
|
2025-03-10 20:58:33 +01:00
|
|
|
{
|
|
|
|
name: "Medic Early",
|
2025-06-11 21:05:17 +02:00
|
|
|
roleId: idMedic,
|
2025-03-10 20:58:33 +01:00
|
|
|
slots: [
|
|
|
|
"d1 12:00 4h",
|
|
|
|
"d2 12:00 4h",
|
|
|
|
"d3 12:00 4h",
|
|
|
|
"d4 11:00 5h",
|
|
|
|
"d5 10:00 3h",
|
|
|
|
],
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "Medic Late",
|
2025-06-11 21:05:17 +02:00
|
|
|
roleId: idMedic,
|
2025-03-10 20:58:33 +01:00
|
|
|
slots: [
|
|
|
|
"d1 16:00 7h",
|
|
|
|
"d2 16:00 6h",
|
|
|
|
"d3 16:00 8h",
|
|
|
|
"d4 16:00 7h",
|
|
|
|
],
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "Security Early",
|
2025-06-11 21:05:17 +02:00
|
|
|
roleId: idSecurity,
|
2025-03-10 20:58:33 +01:00
|
|
|
slots: [
|
|
|
|
"d1 12:00 6h",
|
|
|
|
"d2 12:00 6h",
|
|
|
|
"d3 12:00 6h",
|
|
|
|
"d4 11:00 7h",
|
|
|
|
"d5 10:00 3h",
|
|
|
|
],
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "Security Late",
|
2025-06-11 21:05:17 +02:00
|
|
|
roleId: idSecurity,
|
2025-03-10 20:58:33 +01:00
|
|
|
slots: [
|
|
|
|
"d1 18:00 5h",
|
|
|
|
"d2 18:00 4h",
|
|
|
|
"d3 18:00 6h",
|
|
|
|
"d4 18:00 5h",
|
|
|
|
],
|
|
|
|
},
|
2025-06-11 21:05:17 +02:00
|
|
|
].map(({ slots, ...rest }) => ({
|
|
|
|
...rest,
|
|
|
|
id: eventId++,
|
|
|
|
slots: slots.map(slot => `${slot} ${slotId++}`),
|
|
|
|
}));
|
2025-03-10 20:58:33 +01:00
|
|
|
|
2025-03-05 22:15:46 +01:00
|
|
|
function toIso(date: Date) {
|
2025-03-05 22:43:07 +01:00
|
|
|
return date.toISOString().replace(":00.000Z", "Z");
|
2025-03-05 22:15:46 +01:00
|
|
|
}
|
|
|
|
|
2025-06-11 21:05:17 +02:00
|
|
|
function toDate(origin: Date, day: string, start: string) {
|
2025-03-05 22:15:46 +01:00
|
|
|
const [startHours, startMinutes] = start.split(":").map(time => parseInt(time, 10));
|
|
|
|
const dayNumber = parseInt(day.slice(1));
|
|
|
|
|
|
|
|
const startDate = new Date(origin);
|
|
|
|
startDate.setUTCDate(startDate.getUTCDate() + dayNumber);
|
|
|
|
startDate.setUTCHours(startDate.getUTCHours() + startHours);
|
|
|
|
startDate.setUTCMinutes(startDate.getUTCMinutes() + startMinutes);
|
|
|
|
|
2025-06-11 21:05:17 +02:00
|
|
|
return startDate;
|
|
|
|
}
|
|
|
|
|
|
|
|
function toDates(origin: Date, day: string, start: string, duration: string) {
|
|
|
|
const startDate = toDate(origin, day, start);
|
2025-03-05 22:15:46 +01:00
|
|
|
const [_, durationHours, durationMinutes] = /(?:(\d+)h)?(?:(\d+)m)?/.exec(duration)!;
|
|
|
|
const durationTotal = parseInt(durationHours ?? "0") * 60 + parseInt(durationMinutes ?? "0")
|
|
|
|
const endDate = new Date(startDate.getTime() + durationTotal * 60e3);
|
|
|
|
|
2025-03-10 20:58:33 +01:00
|
|
|
return [startDate, endDate];
|
|
|
|
}
|
|
|
|
|
2025-06-11 21:05:17 +02:00
|
|
|
function toSlot(origin: Date, shorthand: string, counts: Map<number, number>, idToAssigned: Map<number, number[]>): ApiScheduleEventSlot {
|
|
|
|
const [day, start, duration, location, idStr] = shorthand.split(" ");
|
2025-03-10 20:58:33 +01:00
|
|
|
const [startDate, endDate] = toDates(origin, day, start, duration);
|
2025-06-11 21:05:17 +02:00
|
|
|
const id = parseInt(idStr, 10);
|
2025-03-10 20:58:33 +01:00
|
|
|
|
2025-03-05 22:15:46 +01:00
|
|
|
return {
|
2025-06-11 21:05:17 +02:00
|
|
|
id,
|
2025-03-05 22:15:46 +01:00
|
|
|
start: toIso(startDate),
|
|
|
|
end: toIso(endDate),
|
2025-06-11 21:05:17 +02:00
|
|
|
locationIds: [locations.find(l => toId(l.name) === location)!.id],
|
|
|
|
assigned: idToAssigned.get(id),
|
|
|
|
interested: counts.get(id),
|
2025-03-07 12:41:57 +01:00
|
|
|
};
|
2025-03-05 22:15:46 +01:00
|
|
|
}
|
|
|
|
|
2025-06-11 21:05:17 +02:00
|
|
|
function toShift(origin: Date, shorthand: string, idToAssigned: Map<number, number[]>): ApiScheduleShiftSlot {
|
|
|
|
const [day, start, duration, idStr] = shorthand.split(" ");
|
2025-03-10 20:58:33 +01:00
|
|
|
const [startDate, endDate] = toDates(origin, day, start, duration);
|
2025-06-11 21:05:17 +02:00
|
|
|
const id = parseInt(idStr, 10);
|
2025-03-10 20:58:33 +01:00
|
|
|
|
|
|
|
return {
|
2025-06-11 21:05:17 +02:00
|
|
|
id,
|
2025-03-10 20:58:33 +01:00
|
|
|
start: toIso(startDate),
|
|
|
|
end: toIso(endDate),
|
2025-06-11 21:05:17 +02:00
|
|
|
assigned: idToAssigned.get(id),
|
2025-03-10 20:58:33 +01:00
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2025-06-23 00:17:22 +02:00
|
|
|
function getDemoOrigin() {
|
2025-03-05 22:15:46 +01:00
|
|
|
const origin = new Date();
|
|
|
|
const utcOffset = 1;
|
2025-06-30 15:20:57 +02:00
|
|
|
origin.setUTCDate(origin.getUTCDate() - 1); // Day 0 is yesterday
|
2025-03-10 14:40:52 +01:00
|
|
|
origin.setUTCHours(-utcOffset);
|
2025-03-05 22:15:46 +01:00
|
|
|
origin.setUTCMinutes(0);
|
|
|
|
origin.setUTCSeconds(0);
|
2025-03-05 22:54:43 +01:00
|
|
|
origin.setUTCMilliseconds(0);
|
2025-06-23 00:17:22 +02:00
|
|
|
return origin;
|
|
|
|
}
|
|
|
|
|
|
|
|
export function generateDemoSchedule(): ApiSchedule {
|
|
|
|
const origin = getDemoOrigin();
|
2025-03-05 22:15:46 +01:00
|
|
|
|
2025-06-11 21:05:17 +02:00
|
|
|
const eventCounts = new Map<number, number>()
|
|
|
|
const slotCounts = new Map<number, number>()
|
2025-06-23 00:17:22 +02:00
|
|
|
const accounts = generateDemoAccounts(origin);
|
2025-03-15 20:26:43 +01:00
|
|
|
for (const account of accounts) {
|
2025-06-11 21:05:17 +02:00
|
|
|
for (const id of account.interestedEventIds ?? []) {
|
|
|
|
eventCounts.set(id, (eventCounts.get(id) ?? 0) + 1);
|
2025-03-15 20:26:43 +01:00
|
|
|
}
|
2025-06-11 21:05:17 +02:00
|
|
|
for (const id of account.interestedEventSlotIds ?? []) {
|
|
|
|
slotCounts.set(id, (slotCounts.get(id) ?? 0) + 1);
|
2025-03-15 20:26:43 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2025-06-11 21:05:17 +02:00
|
|
|
function assignSlots(events: { id: number, slots: string[] }[], count: number) {
|
|
|
|
const idToAssigned = new Map<number, number[]>();
|
|
|
|
for (const account of accounts.filter(a => a.type === "crew" || a.type === "admin")) {
|
|
|
|
const assignedIds = new Set<number>;
|
|
|
|
const usedEvents = new Set<number>;
|
|
|
|
const slotsToAdd = Math.floor(random() * count);
|
|
|
|
while (assignedIds.size < slotsToAdd) {
|
|
|
|
const event = events[Math.floor(random() * events.length)];
|
|
|
|
if (usedEvents.has(event.id)) {
|
|
|
|
continue;
|
2025-03-15 20:26:43 +01:00
|
|
|
}
|
2025-06-11 21:05:17 +02:00
|
|
|
if (event.slots.length === 1 || random() < 0.8) {
|
|
|
|
for (const slot of event.slots) {
|
|
|
|
const id = parseInt(slot.split(" ").slice(-1)[0]);
|
|
|
|
assignedIds.add(id);
|
|
|
|
usedEvents.add(event.id);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
for (const slot of event.slots) {
|
|
|
|
if (random() < 0.5) {
|
|
|
|
const id = parseInt(slot.split(" ").slice(-1)[0]);
|
|
|
|
assignedIds.add(id);
|
|
|
|
usedEvents.add(event.id);
|
|
|
|
}
|
2025-03-15 20:26:43 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2025-06-11 21:05:17 +02:00
|
|
|
for (const id of assignedIds) {
|
|
|
|
const assigned = idToAssigned.get(id);
|
|
|
|
if (assigned) {
|
|
|
|
assigned.push(account.id);
|
|
|
|
} else {
|
|
|
|
idToAssigned.set(id, [account.id]);
|
|
|
|
}
|
2025-03-15 20:26:43 +01:00
|
|
|
}
|
|
|
|
}
|
2025-06-11 21:05:17 +02:00
|
|
|
return idToAssigned;
|
2025-03-15 20:26:43 +01:00
|
|
|
}
|
2025-06-11 21:05:17 +02:00
|
|
|
seed = 2;
|
|
|
|
const eventSlotIdToAssigned = assignSlots(events, 20);
|
2025-03-15 20:26:43 +01:00
|
|
|
|
2025-06-11 21:05:17 +02:00
|
|
|
seed = 5;
|
|
|
|
const shiftSlotIdToAssigned = assignSlots(shifts, 3);
|
2025-03-15 20:26:43 +01:00
|
|
|
|
2025-03-05 22:15:46 +01:00
|
|
|
return {
|
2025-06-11 21:05:17 +02:00
|
|
|
id: 111,
|
2025-06-30 15:20:57 +02:00
|
|
|
updatedAt: toIso(toDate(origin, "d-2", "10:01")),
|
2025-03-05 22:15:46 +01:00
|
|
|
events: events.map(
|
2025-06-11 21:05:17 +02:00
|
|
|
({ id, name, crew, description, slots }) => ({
|
|
|
|
id,
|
2025-03-05 22:15:46 +01:00
|
|
|
name,
|
2025-03-10 14:40:02 +01:00
|
|
|
crew,
|
2025-03-05 22:15:46 +01:00
|
|
|
description,
|
2025-06-11 21:05:17 +02:00
|
|
|
interested: eventCounts.get(id),
|
|
|
|
slots: slots.map(shorthand => toSlot(origin, shorthand, slotCounts, eventSlotIdToAssigned)),
|
2025-06-30 15:20:57 +02:00
|
|
|
updatedAt: toIso(toDate(origin, "d-1", "15:11")),
|
2025-03-05 22:15:46 +01:00
|
|
|
})
|
|
|
|
),
|
|
|
|
locations: locations.map(
|
2025-06-11 21:05:17 +02:00
|
|
|
({ id, name, description, updatedAt }) => ({
|
|
|
|
id,
|
|
|
|
name,
|
|
|
|
description,
|
|
|
|
updatedAt: toIso(toDate(origin, ...(updatedAt.split(" ")) as [string, string])),
|
|
|
|
})
|
2025-03-05 22:15:46 +01:00
|
|
|
),
|
2025-06-11 21:05:17 +02:00
|
|
|
roles: roles.map(
|
|
|
|
({ id, name, updatedAt }) => ({
|
|
|
|
id,
|
2025-03-10 20:58:33 +01:00
|
|
|
name,
|
2025-06-11 21:05:17 +02:00
|
|
|
updatedAt: toIso(toDate(origin, ...(updatedAt.split(" ")) as [string, string])),
|
|
|
|
})
|
|
|
|
),
|
|
|
|
shifts: shifts.map(
|
|
|
|
({ id, name, roleId, slots }) => ({
|
|
|
|
id,
|
|
|
|
name,
|
|
|
|
roleId,
|
|
|
|
slots: slots.map(shorthand => toShift(origin, shorthand, shiftSlotIdToAssigned)),
|
2025-06-30 15:20:57 +02:00
|
|
|
updatedAt: toIso(toDate(origin, "d-1", "13:23")),
|
2025-03-10 20:58:33 +01:00
|
|
|
})
|
|
|
|
)
|
2025-03-07 12:41:57 +01:00
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
const names = [
|
|
|
|
"Leo", "Lisa",
|
|
|
|
"Jack", "Emily",
|
|
|
|
"Roy", "Sofia",
|
|
|
|
"Adam", "Eve",
|
|
|
|
"Max", "Rose",
|
|
|
|
"Hugo", "Maria",
|
|
|
|
"David", "Zoe",
|
|
|
|
"Hunter", "Ria",
|
|
|
|
"Sonny", "Amy",
|
|
|
|
"Kai", "Megan",
|
|
|
|
"Toby", "Katie",
|
|
|
|
"Bob", "Lucy",
|
|
|
|
];
|
|
|
|
|
|
|
|
// MINSTD random implementation for reproducible random numbers.
|
|
|
|
let seed = 1;
|
|
|
|
function random() {
|
|
|
|
const a = 48271;
|
|
|
|
const c = 0;
|
|
|
|
const m = 2 ** 31 -1;
|
|
|
|
return (seed = (a * seed + c) % m | 0) / 2 ** 31;
|
|
|
|
}
|
|
|
|
|
2025-06-23 00:17:22 +02:00
|
|
|
export function generateDemoAccounts(origin = getDemoOrigin()): ApiAccount[] {
|
2025-03-07 12:41:57 +01:00
|
|
|
seed = 1;
|
2025-06-11 21:05:17 +02:00
|
|
|
const accounts: ApiAccount[] = [];
|
2025-03-07 12:41:57 +01:00
|
|
|
|
|
|
|
for (const name of names) {
|
|
|
|
accounts.push({
|
|
|
|
id: accounts.length,
|
2025-06-23 00:17:22 +02:00
|
|
|
updatedAt: toIso(toDate(origin, "d-1", "10:41")),
|
2025-03-07 12:41:57 +01:00
|
|
|
name,
|
2025-03-15 20:26:43 +01:00
|
|
|
type: (["regular", "crew", "crew", "crew", "admin"] as const)[Math.floor(random() ** 3 * 5)],
|
2025-03-07 12:41:57 +01:00
|
|
|
});
|
2025-03-05 22:15:46 +01:00
|
|
|
}
|
2025-03-07 20:15:41 +01:00
|
|
|
|
2025-03-10 14:42:40 +01:00
|
|
|
seed = 1;
|
2025-03-07 20:15:41 +01:00
|
|
|
// These have a much higher probability of being in someone's interested list.
|
2025-06-11 21:05:17 +02:00
|
|
|
const desiredEvent = ["opening", "closing", "fursuit-games"].map(
|
|
|
|
id => events.find(e => toId(e.name) === id)!.id
|
|
|
|
);
|
2025-03-10 14:40:02 +01:00
|
|
|
const nonCrewEvents = events.filter(event => !event.crew);
|
2025-03-07 20:15:41 +01:00
|
|
|
|
|
|
|
for (const account of accounts) {
|
2025-06-11 21:05:17 +02:00
|
|
|
const interestedEventIds = new Set<number>;
|
|
|
|
const interestedSlotIds = new Set<number>;
|
|
|
|
const usedEvents = new Set<number>;
|
2025-03-07 20:15:41 +01:00
|
|
|
for (const id of desiredEvent) {
|
|
|
|
if (random() < 0.5) {
|
2025-06-11 21:05:17 +02:00
|
|
|
interestedEventIds.add(id);
|
2025-03-07 20:15:41 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
const eventsToAdd = Math.floor(random() * 10);
|
2025-06-11 21:05:17 +02:00
|
|
|
while (interestedEventIds.size + interestedSlotIds.size < eventsToAdd) {
|
2025-03-10 14:40:02 +01:00
|
|
|
const event = nonCrewEvents[Math.floor(random() * nonCrewEvents.length)];
|
2025-06-11 21:05:17 +02:00
|
|
|
if (usedEvents.has(event.id)) {
|
2025-03-07 20:15:41 +01:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (event.slots.length === 1 || random() < 0.8) {
|
2025-06-11 21:05:17 +02:00
|
|
|
interestedEventIds.add(event.id)
|
2025-03-07 20:15:41 +01:00
|
|
|
} else {
|
2025-06-11 21:05:17 +02:00
|
|
|
for (const slot of event.slots) {
|
2025-03-07 20:15:41 +01:00
|
|
|
if (random() < 0.5) {
|
2025-06-11 21:05:17 +02:00
|
|
|
const id = parseInt(slot.split(" ")[4], 10);
|
|
|
|
interestedSlotIds.add(id);
|
2025-03-07 20:15:41 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2025-06-11 21:05:17 +02:00
|
|
|
if (interestedEventIds.size) {
|
|
|
|
account.interestedEventIds = [...interestedEventIds];
|
|
|
|
}
|
|
|
|
if (interestedSlotIds.size) {
|
|
|
|
account.interestedEventSlotIds = [...interestedSlotIds];
|
2025-03-07 20:15:41 +01:00
|
|
|
}
|
|
|
|
}
|
2025-03-07 12:41:57 +01:00
|
|
|
return accounts;
|
2025-03-05 22:15:46 +01:00
|
|
|
}
|