File size: 6,674 Bytes
5af4179
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
# 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