# modules/flashcard_generator.py """Enhanced Flashcard Generator Module""" from typing import Dict, List, Optional from modules.api_utils import ( fetch_wikipedia_summary, fetch_wikibooks_content, fetch_wikipedia_categories, fetch_related_topics, ) import random import hashlib # For generating unique IDs def _generate_item_id(content: str) -> str: """Generates a unique ID for a flashcard based on its content.""" return hashlib.sha256(content.encode()).hexdigest() def generate_flashcard_types(topic: str, difficulty: str) -> Dict: """Generate different types of flashcards based on difficulty""" summary_data = fetch_wikipedia_summary(topic) if not summary_data: return {"error": "Topic not found", "status": False} title = summary_data.get("title", topic) extract = summary_data.get("extract", "") description = summary_data.get("description", "") # Also try to get Wikibooks content for educational material wikibooks_content = fetch_wikibooks_content(topic) flashcard_types = { "Easy": { "front": f"What is {title}?", "back": extract[:200] + "..." if len(extract) > 200 else extract, "type": "definition", }, "Medium": { "front": f"Explain the key concepts of {title}", "back": extract[:300] + "..." if len(extract) > 300 else extract, "type": "conceptual", }, "Hard": { "front": f"How does {title} relate to its field and what are its applications?", "back": extract + ( f"\n\nAdditional context: {wikibooks_content[:200]}" if wikibooks_content else "" ), "type": "analytical", }, } flashcard = flashcard_types.get(difficulty, flashcard_types["Easy"]) # Add categories as tags categories = fetch_wikipedia_categories(title) # Generate item_id item_id = _generate_item_id(flashcard["front"] + flashcard["back"]) return { "item_id": item_id, # Add unique ID "front": flashcard["front"], "back": flashcard["back"], "hint": description, "topic": title, "difficulty": difficulty, "type": flashcard["type"], "tags": categories[:3] if categories else [], "status": True, } def generate_flashcard(topic: str, difficulty: str = "Medium", adaptive_engine=None) -> Dict: """Generate an enhanced flashcard for a given topic, prioritizing review items.""" # First, check for items due for review if adaptive engine is provided if adaptive_engine: review_item_ids = adaptive_engine.get_items_due_for_review(topic=topic, limit=1) if review_item_ids: # If a review item is found, we need to reconstruct the flashcard data # This assumes that the item_id is sufficient to retrieve the original flashcard content # For simplicity, we'll generate a new one for that topic, ideally, you'd load it from storage # For this implementation, we'll try to generate a flashcard for that specific item's topic # A more robust solution would store flashcard content with the item_id in the AdaptiveEngine data # For now, let's just use the topic associated with the item_id in adaptive engine # This requires adaptive engine to store topic with item_id item_data = adaptive_engine.data["item_performance"].get(review_item_ids[0]) if item_data and item_data.get("topic"): return generate_flashcard_types(item_data["topic"], difficulty) # If no review items, or if adaptive_engine is not provided, generate a new flashcard return generate_flashcard_types(topic, difficulty) def generate_flashcard_deck( topics: List[str], difficulty: str = "Medium", adaptive_engine=None ) -> List[Dict]: """Generate a deck of flashcards, incorporating spaced repetition.""" deck = [] # Prioritize review items if adaptive engine is provided if adaptive_engine: review_item_ids = adaptive_engine.get_items_due_for_review(limit=len(topics) * 2) # Get more than needed for item_id in review_item_ids: item_data = adaptive_engine.data["item_performance"].get(item_id) if item_data and item_data.get("topic"): card = generate_flashcard_types(item_data["topic"], item_data.get("difficulty", "Medium")) if card.get("status") and card["item_id"] == item_id: # Ensure the generated card matches the item_id deck.append(card) if len(deck) >= len(topics): # Fill up to requested deck size break # Fill the rest of the deck with new flashcards if needed current_deck_size = len(deck) if current_deck_size < len(topics): new_topics_needed = len(topics) - current_deck_size new_topics = random.sample(topics, min(new_topics_needed, len(topics))) # Pick random topics for new cards for topic_name in new_topics: card = generate_flashcard_types(topic_name, difficulty) if card.get("status") and card not in deck: # Avoid duplicates deck.append(card) if len(deck) >= len(topics): break # Ensure all cards have a unique card_number and total_cards for i, card in enumerate(deck): card["card_number"] = i + 1 card["total_cards"] = len(deck) random.shuffle(deck) # Shuffle the final deck return deck def generate_smart_deck( main_topic: str, deck_size: int = 10, difficulty: str = "Medium", adaptive_engine=None ) -> List[Dict]: """Generate a smart deck with related topics, incorporating spaced repetition.""" # Start with the main topic topics_pool = [main_topic] # Add related topics related = fetch_related_topics(main_topic, deck_size - 1) topics_pool.extend(related) # Add some random topics to ensure variety topics_pool.extend(random.sample(topics_pool, min(5, len(topics_pool)))) # Add some random from pool # Remove duplicates and shuffle topics_pool = list(set(topics_pool)) random.shuffle(topics_pool) # Generate the deck using the new generate_flashcard_deck deck = generate_flashcard_deck(topics_pool[:deck_size], difficulty, adaptive_engine) return deck