async function performDynamicGroupSlicing()

in projects/deliberation_at_scale/packages/orchestrator/src/tasks/handleQueuedParticipants.ts [156:271]


async function performDynamicGroupSlicing(helpers: Helpers) {
    const queuedParticipantsResult = await supabaseClient
        .from('participants')
        .select()
        .eq('active', true)
        .eq('status', 'queued')
        .order('created_at', { ascending: false });
    const allQueuedParticipants = queuedParticipantsResult.data ?? [];
    const queuedParticipants = unique(allQueuedParticipants, (participant) => {
        return participant.user_id ?? '';
    });
    const queuedParticipantIds = queuedParticipants.map((participant) => participant.id);
    const skippedParticipantIds = queuedParticipants.filter((participant) => {
        return !queuedParticipantIds.includes(participant.id);
    }).map((participant) => participant.id);
    const queuedParticipantsAmount = queuedParticipants?.length ?? 0;

    // guard: see if we need to deactive some participants
    if (skippedParticipantIds.length > 0) {
        const { data: deactivatedParticipants } = await supabaseClient
            .from("participants")
            .update({ active: false })
            .in('id', skippedParticipantIds)
            .select();

        helpers.logger.info(`Deactivated ${deactivatedParticipants?.length} skipped participants.`);
    }

    helpers.logger.info(`There are currently ${queuedParticipantsAmount} queued participants waiting for a room...`);

    // guard: check if there are enough participants to assign to a room
    if (queuedParticipantsAmount < PARTICIPANTS_PER_ROOM) {
        helpers.logger.info(`Not enough participants to assign to a room, waiting for more participants...`);
        return;
    }

    // will sort the participants randomly for now and assign four of them per room
    const shuffledParticipants = shuffle(queuedParticipants);

    // get all current topics and put them in an array so the participants can be randomly assigned
    const topicsResult = await supabaseClient.from('topics').select().eq('active', true);
    const topics = topicsResult.data ?? [];
    const topicIds = topics.map((topic) => {
        return topic.id;
    });

    // guard we cannot do anything if we do not have topics
    if (!topics) {
        helpers.logger.error('No valid topics could be found to assign to a room with participants.');
        return;
    }

    // group queued participants in different rooms
    // TODO: this is where we can add the logic for the dynamic group slicing algorithm
    const assignRoomPromises: Promise<boolean>[] = [];
    while (shuffledParticipants.length >= PARTICIPANTS_PER_ROOM && assignRoomPromises.length < MAX_ROOM_AMOUNT_PER_JOB) {
        const newRoomParticipantIds: string[] = [];
        for (let i = 0; i < PARTICIPANTS_PER_ROOM; i++) {
            const participantCandidate = shuffledParticipants.pop();
            const participantCandidateId = participantCandidate?.id;

            if (!participantCandidateId) {
                helpers.logger.error(`An invalid participant candidate was found when slicing groups: ${JSON.stringify(participantCandidate)}`);
                continue;
            }

            // add to the room
            newRoomParticipantIds.push(participantCandidateId);
        }

        // guard: make sure there are enough participants
        if (newRoomParticipantIds.length < PARTICIPANTS_PER_ROOM) {
            helpers.logger.info(`Not enough participants were found to assign to a room: ${JSON.stringify(newRoomParticipantIds)}`);
            continue;
        }

        // guard: check if these three participants were already in a room together
        // TODO: check if two people are already in a room together
        // const newRoomParticipants = allQueuedParticipants.filter((participant) => {
        //     return newRoomParticipantIds.includes(participant.id);
        // });
        // const newRoomUserIds = newRoomParticipants.map((participant) => {
        //     return participant.user_id;
        // });

        // store all the promises in an array so we can wait for them to finish
        assignRoomPromises.push(assignParticipantsToRoom({
            helpers,
            participantIds: newRoomParticipantIds,
            topicIds,
        }));
    }

    try {
        const assignResults = await Promise.allSettled(assignRoomPromises);

        // check if all promises were successful
        const successfulAssignments = assignResults.filter((result) => {
            return result.status === 'fulfilled';
        });
        const failedAssignments = assignResults.filter((result) => {
            return result.status === 'rejected';
        });

        helpers.logger.info(`Successfully assigned ${successfulAssignments.length} rooms.`);
        helpers.logger.error(`Failed to assign ${failedAssignments.length} rooms.`);

        // debug all failed reasons
        failedAssignments.forEach((failedAssignment) => {
            helpers.logger.error(`Failed to assign room:`);
            helpers.logger.error(JSON.stringify(failedAssignment));
        });
    } catch (error) {
        helpers.logger.error(`An error occured when assigning all participants to rooms: ${error}`);
    }
}