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

228 lines
5.8 KiB
TypeScript

import type { db as Db } from "../db";
import type { Question, QuizRound } from "../party-types";
import {
buildOptionsWithCorrect,
buildOrderedOptions,
buildQuestionWindow,
getMostSharedGenreNames,
getTopClusterArtists,
getTopClusterTracks,
isUsableText,
type PartyAnalytics,
pickQuestionCandidate,
pickRandom,
type QuestionCandidate,
resolveQuestionSong,
} from "./question-utils";
export async function buildAudioMetadataQuestion(
dbClient: typeof Db,
analytics: PartyAnalytics,
index: number,
history: QuizRound[],
): Promise<Question | null> {
type ChoiceQuestion = Extract<Question, { type: "choice" }>;
const questions: Array<
QuestionCandidate<Omit<ChoiceQuestion, "startTimestamp" | "endTimestamp">>
> = [];
const topSong = await resolveQuestionSong(dbClient, analytics);
const topSongName = topSong?.name;
const topTracks = getTopClusterTracks(analytics);
const topTrackNames = topTracks.map((track) => track.name);
if (isUsableText(topSongName)) {
const currentSongOptions = buildOptionsWithCorrect(
topSongName,
topTrackNames,
4,
);
if (currentSongOptions) {
questions.push({
key: `audio:current-song:${topSongName}`,
subjectKey: `track:${topSongName}`,
question: {
type: "choice",
text: "What song is currently playing?",
options: currentSongOptions,
correct: 0,
points: 10,
song: topSong ?? undefined,
hideSongTitle: true,
},
});
}
}
const genreOptions = buildOrderedOptions(
getMostSharedGenreNames(analytics),
4,
);
if (genreOptions) {
const topGenre = genreOptions[0];
if (topGenre) {
questions.push({
key: `audio:genre:${topGenre}`,
subjectKey: `genre:${topGenre}`,
question: {
type: "choice",
text: "Which genre appears most in the party analytics?",
options: genreOptions,
correct: 0,
points: 10,
song: topSong ?? undefined,
},
});
}
}
const topArtists = getTopClusterArtists(analytics);
const topArtist = topArtists[0];
if (topArtist) {
const artistOptions = buildOptionsWithCorrect(topArtist, topArtists, 4);
if (artistOptions) {
questions.push({
key: `audio:artist:${topArtist}`,
subjectKey: `artist:${topArtist}`,
question: {
type: "choice",
text: "Which artist shows up most often in the shared audio data?",
options: artistOptions,
correct: 0,
points: 10,
song: topSong ?? undefined,
},
});
}
}
if (topTracks.length > 0) {
const topTrackName = topTrackNames[0];
const trackOptions = topTrackName
? buildOptionsWithCorrect(topTrackName, topTrackNames, 4)
: null;
if (trackOptions) {
questions.push({
key: `audio:track:${topTrackName}`,
subjectKey: `track:${topTrackName}`,
question: {
type: "choice",
text: "Which track looks most shared across the party?",
options: trackOptions,
correct: 0,
points: 10,
song: topSong ?? undefined,
},
});
}
}
const randomTopTrack = pickRandom(topTracks);
if (randomTopTrack) {
const randomTrackSong = await resolveQuestionSong(dbClient, analytics, {
trackName: randomTopTrack.name,
artistNames: randomTopTrack.artists?.map((artist) => artist.name),
albumName: randomTopTrack.albumName,
});
const trackArtists =
randomTopTrack.artists?.map((artist) => artist.name) ?? [];
const allArtists = topArtists.length > 0 ? topArtists : trackArtists;
const correctArtist = trackArtists[0] ?? allArtists[0];
if (correctArtist) {
const artistOptions = buildOptionsWithCorrect(
correctArtist,
allArtists,
4,
);
if (artistOptions) {
questions.push({
key: `audio:performer:${randomTopTrack.name}`,
subjectKey: `track:${randomTopTrack.name}`,
question: {
type: "choice",
text: `Who performs "${randomTopTrack.name}"?`,
options: artistOptions,
correct: 0,
points: 10,
song: randomTrackSong ?? topSong ?? undefined,
},
});
}
const trackNames = topTracks.map((t) => t.name);
const trackNameOptions = buildOptionsWithCorrect(
randomTopTrack.name,
trackNames,
4,
);
if (trackNameOptions) {
questions.push({
key: `audio:title:${randomTopTrack.name}`,
subjectKey: `track:${randomTopTrack.name}`,
question: {
type: "choice",
text: `What is the name of this track by ${correctArtist}?`,
options: trackNameOptions,
correct: 0,
points: 10,
song: randomTrackSong ?? topSong ?? undefined,
},
});
}
if (isUsableText(topSongName) && topSongName !== randomTopTrack.name) {
const alternateSongOptions = buildOptionsWithCorrect(
topSongName,
trackNames,
4,
);
if (alternateSongOptions) {
questions.push({
key: `audio:current-song:${topSongName}`,
subjectKey: `track:${topSongName}`,
question: {
type: "choice",
text: "Which song is this audio clip from?",
options: alternateSongOptions,
correct: 0,
points: 10,
song: topSong ?? undefined,
hideSongTitle: true,
},
});
}
}
}
if (randomTopTrack.albumName) {
const albumNames = topTracks
.map((track) => track.albumName)
.filter((name): name is string => Boolean(name));
const albumOptions = buildOptionsWithCorrect(
randomTopTrack.albumName,
albumNames,
4,
);
if (albumOptions) {
questions.push({
key: `audio:album:${randomTopTrack.albumName}`,
subjectKey: `track:${randomTopTrack.name}`,
question: {
type: "choice",
text: `"${randomTopTrack.name}" appears on which album?`,
options: albumOptions,
correct: 0,
points: 10,
song: randomTrackSong ?? topSong ?? undefined,
},
});
}
}
}
if (questions.length === 0) {
return null;
}
const question = pickQuestionCandidate(questions, history, index);
if (!question) return null;
return buildQuestionWindow(question);
}