milestone 9

This commit is contained in:
Stefan Hardegger
2025-11-25 14:16:25 +01:00
parent de4e7c4c6e
commit 9a30d7c4e5
10 changed files with 1225 additions and 32 deletions

View File

@@ -178,8 +178,26 @@ export async function startLearningSession(
}
}
const newHanzi = await prisma.hanzi.findMany({
// First, get all available new hanzi IDs (lightweight query)
const availableNewHanzi = await prisma.hanzi.findMany({
where: newHanziWhereClause,
select: { id: true },
})
// Randomly select N hanzi IDs from all available
// Fisher-Yates shuffle
const shuffledIds = [...availableNewHanzi]
for (let i = shuffledIds.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1))
;[shuffledIds[i], shuffledIds[j]] = [shuffledIds[j], shuffledIds[i]]
}
const selectedNewHanziIds = shuffledIds.slice(0, neededCards).map(h => h.id)
// Now fetch full details for the randomly selected hanzi
const newHanzi = await prisma.hanzi.findMany({
where: {
id: { in: selectedNewHanziIds },
},
include: {
forms: {
where: { isDefault: true },
@@ -191,7 +209,6 @@ export async function startLearningSession(
},
hskLevels: true,
},
take: neededCards,
})
// Create initial progress for new cards
@@ -223,6 +240,13 @@ export async function startLearningSession(
}
}
// Shuffle final card set (in case new cards were added after initial shuffle)
// Fisher-Yates shuffle
for (let i = selectedCards.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1))
;[selectedCards[i], selectedCards[j]] = [selectedCards[j], selectedCards[i]]
}
// Create learning session
const learningSession = await prisma.learningSession.create({
data: {
@@ -262,12 +286,14 @@ export async function startLearningSession(
if (!pinyinTranscription) continue
const correctPinyin = pinyinTranscription.value
const characterCount = hanzi.simplified.length
// Get HSK level for this hanzi
const hskLevel = hanzi.hskLevels[0]?.level || "new-1"
// Get other hanzi from same HSK level for wrong answers
const sameHskHanzi = await prisma.hanzi.findMany({
// Get ALL available hanzi IDs from same HSK level (lightweight query)
// This prevents always fetching the same alphabetically-first hanzi
const allSameHskIds = await prisma.hanzi.findMany({
where: {
id: { not: hanzi.id },
hskLevels: {
@@ -276,6 +302,32 @@ export async function startLearningSession(
},
},
},
select: {
id: true,
simplified: true, // Need this for character count filtering
},
})
// Filter to same character count
const sameCharCountIds = allSameHskIds.filter(
h => h.simplified.length === characterCount
)
// Shuffle ALL matching IDs
const shuffledIds = [...sameCharCountIds]
for (let i = shuffledIds.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1))
;[shuffledIds[i], shuffledIds[j]] = [shuffledIds[j], shuffledIds[i]]
}
// Take first 50 from shuffled (or all if less than 50)
const selectedIds = shuffledIds.slice(0, 50).map(h => h.id)
// Fetch full details for selected IDs
let candidatesForWrongAnswers = await prisma.hanzi.findMany({
where: {
id: { in: selectedIds },
},
include: {
forms: {
where: { isDefault: true },
@@ -286,11 +338,57 @@ export async function startLearningSession(
},
},
},
take: 10, // Get extra to ensure enough unique options
})
// If not enough candidates, get more from any HSK level with same character count
if (candidatesForWrongAnswers.length < 10) {
const additionalAllIds = await prisma.hanzi.findMany({
where: {
id: {
not: hanzi.id,
notIn: candidatesForWrongAnswers.map(h => h.id),
},
},
select: {
id: true,
simplified: true,
},
})
const additionalSameCharIds = additionalAllIds.filter(
h => h.simplified.length === characterCount
)
// Shuffle additional IDs
const shuffledAdditional = [...additionalSameCharIds]
for (let i = shuffledAdditional.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1))
;[shuffledAdditional[i], shuffledAdditional[j]] = [shuffledAdditional[j], shuffledAdditional[i]]
}
const additionalSelectedIds = shuffledAdditional.slice(0, 30).map(h => h.id)
const additionalHanzi = await prisma.hanzi.findMany({
where: {
id: { in: additionalSelectedIds },
},
include: {
forms: {
where: { isDefault: true },
include: {
transcriptions: {
where: { type: "pinyin" },
},
},
},
},
})
candidatesForWrongAnswers = [...candidatesForWrongAnswers, ...additionalHanzi]
}
// Convert to HanziOption format
const hanziOptions: HanziOption[] = sameHskHanzi
const hanziOptions: HanziOption[] = candidatesForWrongAnswers
.map(h => {
const form = h.forms[0]
const pinyin = form?.transcriptions[0]