itpdp/api/src/party/social-question-generator.ts
2026-05-16 12:51:06 +02:00

133 lines
3.7 KiB
TypeScript

import type { db as Db } from "../db";
import type { Question, QuizState } from "../party-types";
import {
buildMemberOptions,
buildMemberPairOptions,
buildQuestionWindow,
getCurrentLeader,
getMostDiverseMember,
getTopClusterTracks,
getTopTrackListener,
hasClearLeader,
type PartyAnalytics,
type PartyQuestionMember,
pickQuestionCandidate,
pickRandom,
type QuestionCandidate,
resolveQuestionSong,
} from "./question-utils";
export async function buildSocialQuestion(
dbClient: typeof Db,
quizState: QuizState,
analytics: PartyAnalytics,
members: PartyQuestionMember[],
index: number,
): Promise<Question | null> {
type ChoiceQuestion = Extract<Question, { type: "choice" }>;
const questions: Array<
QuestionCandidate<Omit<ChoiceQuestion, "startTimestamp" | "endTimestamp">>
> = [];
const topSong = await resolveQuestionSong(dbClient, analytics);
const hasMultipleMembers = members.length >= 2;
if (hasMultipleMembers && hasClearLeader(quizState)) {
const leader = getCurrentLeader(quizState, members);
const options = buildMemberOptions(leader, members);
if (options) {
questions.push({
key: "social:leader",
subjectKey: `member:${leader.userId}`,
question: {
type: "choice",
text: "Who is leading the quiz right now?",
options,
correct: 0,
points: 10,
song: topSong ?? undefined,
},
});
}
}
if (hasMultipleMembers) {
const diverse = getMostDiverseMember(analytics, members);
if (diverse) {
const options = buildMemberOptions(diverse, members);
if (options) {
questions.push({
key: "social:diverse",
subjectKey: `member:${diverse.userId}`,
question: {
type: "choice",
text: "Who looks like the most diverse listener in the party?",
options,
correct: 0,
points: 10,
song: topSong ?? undefined,
},
});
}
}
}
const topTracks = getTopClusterTracks(analytics);
const randomTrack = pickRandom(topTracks);
if (randomTrack && hasMultipleMembers) {
const topListener = getTopTrackListener(randomTrack, members);
if (topListener) {
const randomTrackSong = await resolveQuestionSong(dbClient, analytics, {
trackName: randomTrack.name,
artistNames: randomTrack.artists?.map((artist) => artist.name),
albumName: randomTrack.albumName,
});
const options = buildMemberOptions(topListener, members);
if (options) {
questions.push({
key: `social:track-listener:${randomTrack.name}`,
subjectKey: `track:${randomTrack.name}`,
question: {
type: "choice",
text: `Who listens the most to "${randomTrack.name}"?`,
options,
correct: 0,
points: 10,
song: randomTrackSong ?? topSong ?? undefined,
},
});
}
}
}
if (members.length >= 3 && analytics?.groupSummary?.mostAlignedPair) {
const topPair = analytics.groupSummary.mostAlignedPair;
const memberA = members.find((m) => m.userId === topPair.userIdA);
const memberB = members.find((m) => m.userId === topPair.userIdB);
if (memberA && memberB) {
const correctPair = `${memberA.name} & ${memberB.name}`;
const pairOptions = buildMemberPairOptions(members, correctPair);
if (pairOptions) {
questions.push({
key: `social:pair:${memberA.userId}:${memberB.userId}`,
subjectKey: `pair:${[memberA.userId, memberB.userId].sort().join("|")}`,
question: {
type: "choice",
text: "Which two players share the most musical taste?",
options: pairOptions,
correct: 0,
points: 10,
song: topSong ?? undefined,
},
});
}
}
}
if (questions.length === 0) {
return null;
}
const question = pickQuestionCandidate(questions, quizState.history, index);
if (!question) return null;
return buildQuestionWindow(question);
}