Implement intelligent merging for duplicate vocabulary items

This commit is contained in:
jonasgaudian
2026-02-17 22:23:12 +01:00
parent 3c1e71d805
commit 3966901da2

View File

@@ -335,7 +335,6 @@ class VocabularyViewModel @Inject constructor(
}
fun deleteVocabularyItemsById(list: List<Int>) {
Log.d(TAG, "Deleting vocabulary items with IDs: $list")
viewModelScope.launch {
@@ -407,18 +406,116 @@ class VocabularyViewModel @Inject constructor(
}
/**
* Handles the merging of two duplicate vocabulary items.
* Placeholder logic: Deletes the new item, effectively keeping the original.
* A full implementation would merge properties like stage, categories, and features.
* Handles the merging of two duplicate vocabulary items intelligently.
* Merges learning progress (stages, counts), categories, and features.
* Keeps the existing item and updates it with merged data.
*
* @param newItem The duplicate item to merge (will be deleted)
* @param existingItem The original item to keep and update
*/
fun mergeDuplicateItems(newItem: VocabularyItem, existingItem: VocabularyItem) {
Log.d(TAG, "mergeDuplicateItems: Merging ${newItem.id} into ${existingItem.id}. (Placeholder logic)")
//TODO
// Placeholder: For now, this just deletes the new item.
// A full implementation would merge stages, categories, etc., and update the existing item based on the rules.
Log.d(TAG, "mergeDuplicateItems: Intelligently merging ${newItem.id} into ${existingItem.id}")
viewModelScope.launch {
statusService.showSuccessMessage("Items merged!", 2)
try {
// 1. Get states for both items
val newState = vocabularyRepository.getVocabularyItemStateById(newItem.id)
val existingState = vocabularyRepository.getVocabularyItemStateById(existingItem.id)
// 2. Get stages for both items
val newStage = vocabularyRepository.getVocabularyItemStage(newItem.id).first()
val existingStage = vocabularyRepository.getVocabularyItemStage(existingItem.id).first()
// 3. Merge learning progress - keep the better stage (higher in progression)
val stageOrder = listOf(
VocabularyStage.NEW, VocabularyStage.STAGE_1, VocabularyStage.STAGE_2,
VocabularyStage.STAGE_3, VocabularyStage.STAGE_4, VocabularyStage.STAGE_5,
VocabularyStage.LEARNED
)
val mergedStage = if (stageOrder.indexOf(newStage) > stageOrder.indexOf(existingStage)) {
newStage
} else {
existingStage
}
// 4. Merge answer counts and timestamps - keep higher counts and most recent timestamps
val mergedCorrectCount = (newState?.correctAnswerCount ?: 0) + (existingState?.correctAnswerCount ?: 0)
val mergedIncorrectCount = (newState?.incorrectAnswerCount ?: 0) + (existingState?.incorrectAnswerCount ?: 0)
val mergedLastCorrect = listOfNotNull(
newState?.lastCorrectAnswer,
existingState?.lastCorrectAnswer
).maxOrNull()
val mergedLastIncorrect = listOfNotNull(
newState?.lastIncorrectAnswer,
existingState?.lastIncorrectAnswer
).maxOrNull()
// 5. Merge features - prefer non-null, non-empty features
val mergedFeatures = when {
!existingItem.features.isNullOrBlank() -> existingItem.features
!newItem.features.isNullOrBlank() -> newItem.features
else -> null
}
// 6. Merge zipf frequencies - prefer non-null values
val mergedZipfFirst = existingItem.zipfFrequencyFirst ?: newItem.zipfFrequencyFirst
val mergedZipfSecond = existingItem.zipfFrequencySecond ?: newItem.zipfFrequencySecond
// 7. Keep the earlier creation date
val mergedCreatedAt = listOfNotNull(
newItem.createdAt,
existingItem.createdAt
).minOrNull() ?: existingItem.createdAt
// 8. Get categories for both items and merge them
val allMappings = vocabularyRepository.getCategoryMappings()
val newCategories = allMappings.filter { it.vocabularyItemId == newItem.id }.map { it.categoryId }
val existingCategories = allMappings.filter { it.vocabularyItemId == existingItem.id }.map { it.categoryId }
val mergedCategories = (newCategories + existingCategories).distinct()
// 9. Update the existing item with merged data
val updatedItem = existingItem.copy(
features = mergedFeatures,
zipfFrequencyFirst = mergedZipfFirst,
zipfFrequencySecond = mergedZipfSecond,
createdAt = mergedCreatedAt
)
vocabularyRepository.editVocabularyItem(updatedItem)
// 10. Update stage if it changed
if (mergedStage != existingStage) {
vocabularyRepository.changeVocabularyItemStage(updatedItem, mergedStage)
}
// 11. Update state with merged progress
val mergedState = eu.gaudian.translator.model.VocabularyItemState(
vocabularyItemId = existingItem.id,
lastCorrectAnswer = mergedLastCorrect,
lastIncorrectAnswer = mergedLastIncorrect,
correctAnswerCount = mergedCorrectCount,
incorrectAnswerCount = mergedIncorrectCount
)
vocabularyRepository.saveVocabularyItemState(mergedState)
// 12. Add any new categories to the existing item
val categoriesToAdd = mergedCategories - existingCategories.toSet()
categoriesToAdd.forEach { categoryId ->
vocabularyRepository.addVocabularyItemToList(existingItem.id, categoryId)
}
// 13. Finally, delete the duplicate item
deleteVocabularyItemsById(listOf(newItem.id))
statusService.showSuccessMessage("Items merged successfully! Progress and categories preserved.", 3)
Log.i(TAG, "mergeDuplicateItems: Successfully merged ${newItem.id} into ${existingItem.id}. " +
"Stage: $existingStage$mergedStage, Correct: ${existingState?.correctAnswerCount ?: 0}$mergedCorrectCount, " +
"Categories: ${existingCategories.size}${mergedCategories.size}")
} catch (e: Exception) {
statusService.showErrorMessage("Error merging items: ${e.message}")
Log.e(TAG, "Error in mergeDuplicateItems: ${e.message}")
}
}
}