# Knowledge Transfer Methods

> Implements structured knowledge transfer workflows using Feynman technique, spaced repetition scheduling, active recall exercises, and teach-back protocols to achieve deep framework mastery and team-wide competency.

- Skill: `paulpas/knowledge-transfer-methods` (Agent Skill)
- Install (CLI): `npx skillmds@latest add paulpas/knowledge-transfer-methods`
- Raw SKILL.md: https://api.skillmd.com/api/skills/paulpas/knowledge-transfer-methods/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- License: MIT
- Author: paulpas (https://skillmd.com/u/paulpas)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/paulpas/knowledge-transfer-methods

---






# Knowledge Transfer Methods

Makes the model implement structured knowledge transfer workflows that convert superficial familiarity into deep framework mastery. When loaded, this skill enforces evidence-based learning techniques — Feynman explanation drafting, spaced repetition scheduling using the SM-2 algorithm, active recall quiz generation, and peer validation through structured teach-back sessions — to ensure engineering teams retain and correctly apply newly learned frameworks.

## TL;DR Checklist

- [ ] Draft a Feynman explanation of the framework in plain language, avoiding jargon
- [ ] Compare the explanation against official documentation to identify knowledge gaps
- [ ] Re-learn each identified gap using primary sources (source code, official docs)
- [ ] Generate active recall quizzes with progressive difficulty for self-testing
- [ ] Schedule spaced repetition reviews using SM-2 intervals (1d, 3d, 7d, 21d, 60d)
- [ ] Conduct a peer teach-back session where another engineer audits the explanation

---

## When to Use

Use this skill when:

- A team member has learned a new framework and needs to transfer that knowledge to colleagues through structured teaching
- Onboarding engineers onto a newly adopted framework — replace documentation reading with active Feynman-style learning
- Preparing for a framework migration where multiple engineers must reach proficiency within a fixed timeline
- Verifying that a senior engineer's understanding of a framework is deep enough to mentor others
- Building institutional knowledge so framework expertise survives team turnover

---

## When NOT to Use

Avoid this skill for:

- **One-time learning with no transfer requirement** — If only one person needs the framework and won't teach others, self-study suffices
- **Trivial APIs** — A simple utility library with 3 functions doesn't need a structured knowledge transfer protocol
- **Emergency triage situations** — Debugging a production outage requires direct source code inspection, not pedagogical methods

---

## Core Workflow

### 1. Feynman Explanation Drafting — Write in Plain Language Without Jargon

The learner produces a written explanation of the framework as if teaching it to a competent developer who has never seen it. Every technical term must be defined inline. No hand-waving, no "as you know" references. The goal is clarity that survives translation to someone unfamiliar with the domain.

```python
from __future__ import annotations

import logging
from dataclasses import dataclass, field
from pathlib import Path
from typing import Any


logger = logging.getLogger(__name__)


@dataclass(frozen=True)
class JargonTerm:
    """Represents a technical term that requires inline definition in Feynman explanations.

    Attributes:
        term: The technical term as it appears in the explanation.
        required_definition: Expected minimum length for the inline definition (in characters).
        domain: The domain this term belongs to, used for disambiguation.
    """
    term: str
    required_definition_length: int = 30
    domain: str = "general"


class FeynmanExplanationDraft:
    """Manages the creation and refinement of a Feynman-style framework explanation.

    Tracks jargon usage, structure completeness, and provides structured
    feedback for improving clarity and depth.

    Attributes:
        topic: The framework or concept being explained.
        content: The full text of the draft explanation.
        sections: Ordered list of section headings found in the explanation.
        jargon_terms: List of technical terms detected that need inline definitions.
    """

    COMMON_JARGON: list[JargonTerm] = [
        JargonTerm("abstraction", 30, "software design"),
        JargonTerm("dependency injection", 30, "software architecture"),
        JargonTerm("lifecycle", 30, "runtime behavior"),
        JargonTerm("middleware", 30, "request processing"),
        JargonTerm("pipeline", 30, "data transformation"),
        JargonTerm("singleton", 30, "object creation pattern"),
        JargonTerm("interface", 30, "type contract"),
        JargonTerm("concurrency", 30, "parallel execution"),
    ]

    def __init__(self, topic: str) -> None:
        self.topic = topic
        self.content: str = ""
        self.sections: list[str] = []
        self.jargon_terms: list[JargonTerm] = []

    def add_section(self, heading: str, content: str) -> None:
        """Append a named section to the explanation.

        Args:
            heading: The section title (will be included in sections list).
            content: The explanatory text for this section.
        """
        self.sections.append(heading)
        if self.content:
            self.content += "\n\n"
        self.content += f"## {heading}\n\n{content}"

    def analyze_jargon(self) -> list[JargonTerm]:
        """Scan the explanation for technical terms requiring inline definitions.

        Returns:
            List of JargonTerm objects found in the content that lack clear definitions.
        """
        self.jargon_terms = []
        text_lower = self.content.lower()

        for jargon in self.COMMON_JARGON:
            if jargon.term.lower() in text_lower:
                # Check if the term is defined nearby (within 200 characters)
                term_pos = text_lower.find(jargon.term.lower())
                context_before = self.content[max(0, term_pos - 200):term_pos].lower()
                context_after = self.content[term_pos:term_pos + 200].lower()

                # A definition is present if terms like "means", "is", "refers to" appear near it
                definition_indicators = ["means ", "is ", "refers to ", "defined as ", "describes "]
                has_definition = any(indicator in context_after for indicator in definition_indicators)

                if not has_definition:
                    self.jargon_terms.append(jargon)

        return self.jargon_terms

    def evaluate_clarity_score(self) -> dict[str, Any]:
        """Produce a clarity assessment of the explanation.

        Returns:
            Dictionary with scores and actionable feedback items.
        """
        score = 100
        issues: list[str] = []

        # Check for jargon without definitions
        jargon = self.analyze_jargon()
        if jargon:
            penalty = len(jargon) * 8
            score -= penalty
            issues.append(f"{len(jargon)} technical terms lack inline definitions")

        # Check section structure
        if len(self.sections) < 3:
            score -= 10
            issues.append("Explanation should have at least 3 sections (what, why, how)")

        # Check for concrete examples
        example_count = self.content.lower().count("example:") + self.content.lower().count("for instance")
        if example_count == 0:
            score -= 15
            issues.append("Add at least one concrete code example to ground the explanation")

        # Check length minimum
        word_count = len(self.content.split())
        if word_count < 200:
            score -= 10
            issues.append(f"Explanation is too brief ({word_count} words) — aim for 300+ words with depth")

        score = max(0, min(100, score))

        return {
            "clarity_score": score,
            "jargon_issues": jargon,
            "word_count": word_count,
            "section_count": len(self.sections),
            "example_count": example_count,
            "issues": issues,
            "recommendation": "revise" if score < 70 else ("improve" if score < 85 else "approve"),
        }

    def as_markdown(self) -> str:
        """Render the explanation as formatted Markdown."""
        if not self.sections:
            return f"# {self.topic}\n\n*(No sections defined yet)*"
        return f"# {self.topic}\n\n{self.content}"


# --- Usage Example ---

if __name__ == "__main__":
    draft = FeynmanExplanationDraft("FastAPI Framework")
    draft.add_section(
        "What is FastAPI?",
        "FastAPI is a modern web framework for building APIs in Python. "
        "It uses type hints and Pydantic for data validation."
    )
    draft.add_section(
        "How Does It Work?",
        "When you define a function with path decorators like @app.get(), "
        "FastAPI reads the type annotations to automatically generate the OpenAPI schema. "
        "Middleware can intercept requests before they reach your handler functions."
    )

    assessment = draft.evaluate_clarity_score()
    print(f"Clarity Score: {assessment['clarity_score']}/100")
    for issue in assessment['issues']:
        print(f"  - {issue}")
```

**Checkpoint:** The explanation has a clarity score of at least 75/100, all jargon terms are either defined inline or removed in favor of plain language, and the word count exceeds 200 words with at least 3 sections covering what, why, and how.

---

### 2. Gap Identification — Compare Explanation Against Official Documentation

Take the drafted explanation and systematically compare it against the framework's official documentation, source code, and API reference. Identify every concept that is missing, inaccurate, or oversimplified. Quantify gaps by severity: missing concepts are "critical," inaccuracies are "high," and missing examples are "medium."

```python
from __future__ import annotations

import difflib
import re
from dataclasses import dataclass, field
from enum import Enum


class GapSeverity(Enum):
    CRITICAL = "critical"     # Missing core concept — explanation is wrong without this
    HIGH = "high"             # Inaccurate description — needs correction
    MEDIUM = "medium"         # Oversimplified or missing example — good to add
    LOW = "low"               # Nice-to-have detail — optional improvement


@dataclass(frozen=True)
class KnowledgeGap:
    """Represents a single knowledge gap identified between an explanation and source material.

    Attributes:
        topic: The concept or feature that is missing or inaccurate.
        severity: How important it is to address this gap.
        explanation_text: What the learner currently believes (from their draft).
        correct_description: The accurate description from official sources.
        suggested_source: Where to find authoritative information about this topic.
    """
    topic: str
    severity: GapSeverity
    explanation_text: str
    correct_description: str
    suggested_source: str


class GapDetector:
    """Identifies knowledge gaps by comparing a learner's explanation against source material.

    Uses both textual similarity analysis and structured concept extraction to find
    missing or inaccurate information. The detector categorizes gaps by severity
    so the learner can prioritize corrections.

    Attributes:
        reference_docs: List of strings representing official documentation excerpts.
        glossary: Set of framework-specific terms that must appear with correct definitions.
    """

    def __init__(self) -> None:
        self.reference_docs: list[str] = []
        self.glossary: dict[str, str] = {}

    def add_reference_document(self, content: str) -> None:
        """Add an excerpt from official framework documentation.

        Args:
            content: Text from the framework's official docs, README, or source comments.
        """
        self.reference_docs.append(content.strip())

    def add_glossary_term(self, term: str, definition: str) -> None:
        """Register a framework-specific term with its canonical definition.

        Args:
            term: The technical term as it appears in documentation.
            definition: The authoritative definition to compare against.
        """
        self.glossary[term.lower()] = definition.strip()

    def detect_gaps(
        self, explanation: str, concepts_to_check: list[str] | None = None
    ) -> list[KnowledgeGap]:
        """Identify knowledge gaps between the learner's explanation and reference material.

        Performs three checks:
        1. Missing concepts — are key framework features absent from the explanation?
        2. Inaccuracy detection — does the explanation contradict documented behavior?
        3. Glossary compliance — are technical terms used with correct definitions?

        Args:
            explanation: The learner's Feynman-style draft explanation.
            concepts_to_check: Optional list of specific concept names to verify.

        Returns:
            List of KnowledgeGap objects sorted by severity (critical first).
        """
        gaps: list[KnowledgeGap] = []
        explanation_lower = explanation.lower()

        # Check 1: Glossary term definitions
        for term, correct_def in self.glossary.items():
            if term in explanation_lower:
                # Find the context around this term in the explanation
                pattern = re.compile(re.escape(term) + r'\s+(.*?)(?:\.|\n|$)', re.IGNORECASE | re.DOTALL)
                matches = pattern.findall(explanation)
                if matches:
                    learner_def = matches[0].strip()
                    # Simple semantic comparison — check for major keyword differences
                    correct_keywords = set(correct_def.lower().split()) & {"request", "response", "route", "middleware",
                                                                          "endpoint", "handler", "validation",
                                                                          "pydantic", "async", "uvicorn", "openapi"}
                    learner_keywords = set(learner_def.lower().split()) & correct_keywords

                    if not correct_keywords.issubset(learner_keywords | {"the", "a", "an", "is", "uses", "for"}):
                        gaps.append(KnowledgeGap(
                            topic=term,
                            severity=GapSeverity.HIGH,
                            explanation_text=f"Learner defines '{term}' as: {learner_def[:100]}",
                            correct_description=correct_def[:200],
                            suggested_source="Framework official documentation"
                        ))

        # Check 2: Reference document coverage
        if concepts_to_check:
            for concept in concepts_to_check:
                concept_lower = concept.lower()
                if concept_lower not in explanation_lower:
                    # Search reference docs for this concept to build a correct description
                    relevant_doc = self._find_relevant_document(concept)
                    gaps.append(KnowledgeGap(
                        topic=concept,
                        severity=GapSeverity.CRITICAL if any(kw in concept_lower for kw in
                            ("middleware", "lifecycle", "dependency injection", "routing", "validation"))
                        else GapSeverity.MEDIUM,
                        explanation_text=f"Concept '{concept}' not mentioned in explanation",
                        correct_description=relevant_doc[:200] if relevant_doc else "Refer to official documentation",
                        suggested_source="Framework docs — search for '" + concept + "'"
                    ))

        # Check 3: Similarity-based gap detection
        gaps.sort(key=lambda g: list(GapSeverity).index(g.severity))
        return gaps

    def _find_relevant_document(self, concept: str) -> str:
        """Find the most relevant reference document for a given concept."""
        best_match = ""
        best_ratio = 0.0

        for doc in self.reference_docs:
            ratio = difflib.SequenceMatcher(None, concept.lower(), doc[:50].lower()).ratio()
            if ratio > best_ratio and ratio < 1.0:  # Exact matches are not gaps
                best_match = doc
                best_ratio = ratio

        return best_match


# --- Usage Example ---

if __name__ == "__main__":
    detector = GapDetector()
    detector.add_reference_document(
        "FastAPI uses Pydantic for data validation. Every parameter annotated with a Pydantic model "
        "is automatically validated against the schema defined by that model's fields."
    )
    detector.add_glossary_term(
        "endpoint",
        "A function decorated with @app.get() or similar decorators that handles HTTP requests"
    )
    detector.add_glossary_term(
        "middleware",
        "Functions that wrap the request-response cycle, executing code before and after each request"
    )

    learner_explanation = (
        "FastAPI is a web framework for Python. It has endpoints that handle requests. "
        "You can use middleware to process data."
    )

    concepts = ["pydantic validation", "dependency injection", "async handlers", "OpenAPI generation"]
    gaps = detector.detect_gaps(learner_explanation, concepts)

    for gap in gaps:
        print(f"[{gap.severity.value.upper()}] {gap.topic}")
        print(f"  Current: {gap.explanation_text}")
        print(f"  Should be: {gap.correct_description[:120]}...")
        print()
```

**Checkpoint:** Every identified gap has a severity classification and a specific suggested source. Critical gaps must be resolved before the learner proceeds to teach-back — they indicate fundamental misunderstandings that would mislead colleagues.

---

### 3. Targeted Re-Learning — Study Each Gap Using Primary Sources

For every critical and high-severity gap, the learner revisits the framework's primary sources: source code, official documentation, and test suites. The re-learning must produce a corrected explanation segment that addresses the specific gap, documented with citation links to the authoritative source.

```python
from __future__ import annotations

import json
from dataclasses import dataclass, field
from pathlib import Path
from typing import Any


@dataclass(frozen=True)
class ReLearningRecord:
    """Records one instance of targeted re-learning for a knowledge gap.

    Attributes:
        gap_topic: The concept being re-learned.
        source_type: Category of primary source consulted.
        source_path: File path or URL of the source document.
        key_finding: The corrected understanding produced by this study session.
        confidence: Learner's self-assessed confidence in this correction (0.0–1.0).
    """
    gap_topic: str
    source_type: str  # "source_code", "documentation", "test_suite", "issue_tracker"
    source_path: str
    key_finding: str
    confidence: float = 0.5


class ReLearningTracker:
    """Tracks and manages the re-learning process for identified knowledge gaps.

    Each gap from the GapDetector produces a required re-learning task.
    The tracker records what was studied, where, and with what confidence level.
    Confidence below 0.7 on any critical gap requires additional study.

    Attributes:
        records: List of re-learning sessions completed so far.
        pending_gaps: Gaps that haven't been addressed yet.
    """

    def __init__(self) -> None:
        self.records: list[ReLearningRecord] = []
        self.pending_gaps: list[tuple[str, str]] = []  # (topic, severity)

    def add_pending_gap(self, topic: str, severity: str) -> None:
        """Queue a gap for re-learning.

        Args:
            topic: The concept to study.
            severity: GapSeverity string value ("critical", "high", "medium", "low").
        """
        self.pending_gaps.append((topic, severity))

    def record_session(
        self, gap_topic: str, source_type: str, source_path: str,
        key_finding: str, confidence: float = 0.5
    ) -> None:
        """Log a completed re-learning session.

        Args:
            gap_topic: Which gap this session addresses.
            source_type: What kind of primary source was consulted.
            source_path: File path or URL where the information was found.
            key_finding: The corrected understanding produced.
            confidence: Self-assessed confidence (0.0–1.0) in the correction.
        """
        self.records.append(ReLearningRecord(
            gap_topic=gap_topic,
            source_type=source_type,
            source_path=source_path,
            key_finding=key_finding,
            confidence=max(0.0, min(1.0, confidence))
        ))

    def get_unresolved_critical_gaps(self) -> list[tuple[str, str]]:
        """Return gaps that are critical/high severity and have no re-learning record with confidence >= 0.7."""
        resolved_topics = {r.gap_topic for r in self.records if r.confidence >= 0.7}
        unresolved = []

        for topic, severity in self.pending_gaps:
            if severity in ("critical", "high") and topic not in resolved_topics:
                unresolved.append((topic, severity))

        return unresolved

    def get_mastery_score(self) -> float:
        """Compute overall mastery score based on re-learning completion.

        Score = (gaps with confidence >= 0.7) / (total gaps), weighted by severity.
        Critical gaps count 3x, high 2x, medium 1x, low 0.5x.
        """
        if not self.pending_gaps:
            return 1.0

        severity_weights = {"critical": 3.0, "high": 2.0, "medium": 1.0, "low": 0.5}
        total_weight = 0.0
        resolved_weight = 0.0

        for topic, severity in self.pending_gaps:
            weight = severity_weights.get(severity, 1.0)
            total_weight += weight

            if any(r.gap_topic == topic and r.confidence >= 0.7 for r in self.records):
                resolved_weight += weight

        return resolved_weight / total_weight if total_weight > 0 else 1.0

    def generate_study_plan(self) -> str:
        """Generate a prioritized study plan based on unresolved critical gaps.

        Returns:
            Markdown-formatted study plan with sources and confidence targets.
        """
        unresolved = self.get_unresolved_critical_gaps()

        lines = ["## Re-Learning Study Plan", ""]
        for i, (topic, severity) in enumerate(unresolved, 1):
            weight = {"critical": "🔴 HIGH", "high": "🟠 MEDIUM"}.get(severity, f"{severity.upper()}")
            lines.append(f"**{i}. {topic}** [{weight}]")
            lines.append(f"   - Target confidence: ≥ 0.7")

            # Suggest sources based on gap topic keywords
            if any(kw in topic.lower() for kw in ("middleware", "lifecycle")):
                lines.append("   - Source: Framework source code (search for 'class.*Middleware')")
            elif "validation" in topic.lower():
                lines.append("   - Source: Pydantic documentation and framework test cases")
            else:
                lines.append("   - Source: Official framework documentation — search for exact concept name")

            lines.append("")

        if not unresolved:
            lines.append("All critical gaps resolved. Mastery score: {:.0%}".format(self.get_mastery_score()))

        return "\n".join(lines)


# --- Usage Example ---

if __name__ == "__main__":
    tracker = ReLearningTracker()

    # Simulate detected gaps
    tracker.add_pending_gap("Pydantic validation", "critical")
    tracker.add_pending_gap("Dependency injection", "high")
    tracker.add_pending_gap("OpenAPI schema generation", "medium")
    tracker.add_pending_gap("Async/await support", "low")

    # Record some re-learning sessions
    tracker.record_session(
        gap_topic="Pydantic validation",
        source_type="source_code",
        source_path="fastapi/dependencies/utils.py#L245",
        key_finding="FastAPI calls pydantic's model_validate() on request bodies, not just validates individual parameters.",
        confidence=0.9
    )
    tracker.record_session(
        gap_topic="Dependency injection",
        source_type="documentation",
        source_path="https://fastapi.tiangolo.com/tutorial/dependencies/",
        key_finding="FastAPI uses function parameter annotations as dependency declarations; dependencies are resolved via a DI container.",
        confidence=0.6  # Below threshold — still unresolved
    )

    unresolved = tracker.get_unresolved_critical_gaps()
    print(f"Unresolved critical/high gaps: {len(unresolved)}")
    for topic, sev in unresolved:
        print(f"  - [{sev}] {topic}")

    print(f"\nMastery Score: {tracker.get_mastery_score():.0%}")
    print("\n" + tracker.generate_study_plan())
```

**Checkpoint:** Every critical and high-severity gap has at least one re-learning record with confidence ≥ 0.7. The mastery score is above 80% before proceeding to quiz creation — below that threshold, return to Step 2 for more gap detection.

---

### 4. Active Recall Quiz Creation — Generate Progressive Difficulty Tests

Build a self-test quiz where questions progress from basic recall (define X) to application (given scenario Y, what happens?). Each question must have an answer key with the exact phrasing expected and a citation to the authoritative source. Avoid multiple choice — use short-answer format that forces genuine recall rather than recognition.

```python
from __future__ import annotations

import random
from dataclasses import dataclass, field


@dataclass(frozen=True)
class QuizQuestion:
    """Represents a single active recall question in the knowledge transfer quiz.

    Attributes:
        id: Unique identifier for this question.
        text: The question as presented to the learner.
        expected_answer: The correct answer (for grading).
        difficulty: Question difficulty level (1=easiest, 3=hardest).
        topic: The framework concept being tested.
        source_citation: Where the correct answer can be verified.
        hint: A subtle hint to nudge recall without giving away the answer.
    """
    id: str
    text: str
    expected_answer: str
    difficulty: int = 1  # 1, 2, or 3
    topic: str = ""
    source_citation: str = "Official documentation"
    hint: str = ""


@dataclass(frozen=True)
class QuizResult:
    """Results from completing a quiz.

    Attributes:
        questions: List of (question_id, user_answer, is_correct) tuples.
        total_score: Number of correct answers out of total.
        topic_scores: Per-topic accuracy breakdown.
    """
    questions: list[tuple[str, str, bool]] = field(default_factory=list)

    @property
    def total_score(self) -> int:
        return sum(1 for _, _, correct in self.questions if correct)

    @property
    def topic_scores(self) -> dict[str, tuple[int, int]]:
        """Return {topic: (correct_count, total_count)} for each topic."""
        scores: dict[str, list[bool]] = {}
        for qid, _, correct in self.questions:
            # Topic is embedded in the question's expected_answer metadata via quiz builder
            pass  # Populated by QuizBuilder
        return scores


class QuizBuilder:
    """Generates active recall quizzes from knowledge transfer topics.

    Creates questions at three difficulty levels:
    Level 1 — Recall: "What does X do?" (definition-level)
    Level 2 — Application: "Given scenario Y, what is the outcome?" (scenario-based)
    Level 3 — Synthesis: "How would you combine X and Y to solve Z?" (integration-based)

    Attributes:
        topics: Framework concepts with their canonical definitions.
        questions: Generated quiz questions.
    """

    def __init__(self, framework_name: str) -> None:
        self.framework_name = framework_name
        self.topics: dict[str, dict[str, str]] = {}  # topic → {definition, example, common_mistake}
        self.questions: list[QuizQuestion] = []
        self._question_counter = 0

    def add_topic(self, name: str, definition: str, example: str, common_mistake: str = "") -> None:
        """Register a framework concept for quiz generation.

        Args:
            name: The concept name (e.g., "middleware", "dependency injection").
            definition: Canonical definition from official docs.
            example: A concrete code example demonstrating the concept.
            common_mistake: Typical misunderstanding to test against.
        """
        self.topics[name] = {
            "definition": definition,
            "example": example,
            "common_mistake": common_mistake,
        }

    def build_quiz(self, target_questions: int = 12) -> list[QuizQuestion]:
        """Generate a balanced quiz across difficulty levels and topics.

        Distributes questions as: 40% level-1 (recall), 35% level-2 (application), 25% level-3 (synthesis).

        Args:
            target_questions: Total number of questions to generate.

        Returns:
            List of QuizQuestion objects ordered by difficulty (ascending).
        """
        self.questions = []
        self._question_counter = 0

        topic_names = list(self.topics.keys())
        if not topic_names:
            return []

        # Calculate distribution
        level1_count = int(target_questions * 0.4)
        level2_count = int(target_questions * 0.35)
        level3_count = target_questions - level1_count - level2_count

        for level, count in [(1, level1_count), (2, level2_count), (3, level3_count)]:
            for _ in range(count):
                topic = random.choice(topic_names)
                question = self._generate_question(topic, level)
                if question:
                    self.questions.append(question)

        # Shuffle within each difficulty band, then sort by overall difficulty
        self.questions.sort(key=lambda q: (q.difficulty, q.id))
        return self.questions

    def _next_id(self) -> str:
        self._question_counter += 1
        return f"Q{self._question_counter:03d}"

    def _generate_question(self, topic: str, level: int) -> QuizQuestion | None:
        """Create a single question at the specified difficulty level for a given topic."""
        info = self.topics[topic]
        qid = self._next_id()

        if level == 1:
            # Recall question — test basic definition
            return QuizQuestion(
                id=qid,
                text=f"What does the '{topic}' concept do in {self.framework_name}? Explain in one sentence.",
                expected_answer=info["definition"],
                difficulty=1,
                topic=topic,
                source_citation="Official documentation",
                hint="Think about what happens when you use this feature in your code."
            )

        elif level == 2:
            # Application question — scenario-based
            return QuizQuestion(
                id=qid,
                text=(
                    f"In {self.framework_name}, what happens if you apply '{topic}' "
                    f"to a function that accepts keyword arguments? Consider the framework's default behavior."
                ),
                expected_answer=info["definition"],
                difficulty=2,
                topic=topic,
                source_citation=f"{self.framework_name} docs — {topic} section",
                hint="Consider both the normal case and edge cases with invalid input."
            )

        else:  # level == 3
            # Synthesis question — integration challenge
            other_topics = [t for t in self.topics if t != topic]
            if not other_topics:
                return None
            other = random.choice(other_topics)
            return QuizQuestion(
                id=qid,
                text=(
                    f"How would you combine '{topic}' with '{other}' in {self.framework_name} "
                    f"to handle authenticated API requests that need input validation?"
                ),
                expected_answer=f"{info['definition']} works alongside {self.topics[other]['definition']}. "
                                f"Typically, {topic} runs first as a dependency, then validation occurs.",
                difficulty=3,
                topic=topic,
                source_citation="Integration patterns in framework documentation",
                hint=f"Common mistake: '{info.get('common_mistake', 'Order matters — check the execution sequence.')}'"
            )

    def grade_answer(self, question_id: str, user_answer: str) -> bool:
        """Grade a single answer against the expected answer.

        Uses keyword overlap scoring (not exact match) to account for paraphrasing.

        Args:
            question_id: The ID of the question being graded.
            user_answer: The learner's submitted answer.

        Returns:
            True if the answer contains enough key concepts from the expected answer.
        """
        question = next((q for q in self.questions if q.id == question_id), None)
        if not question:
            return False

        user_words = set(user_answer.lower().split())
        expected_words = set(question.expected_answer.lower().split())

        # Remove common stop words
        stop_words = {"the", "a", "an", "is", "are", "was", "were", "in", "on", "at", "to", "for",
                      "of", "and", "or", "but", "if", "it", "this", "that", "with", "by", "as"}
        user_words -= stop_words
        expected_words -= stop_words

        # Require at least 40% keyword overlap for a correct answer
        if not expected_words:
            return len(user_words) > 5  # Free-form answers need some content

        overlap = len(user_words & expected_words) / len(expected_words)
        return overlap >= 0.4


# --- Usage Example ---

if __name__ == "__main__":
    builder = QuizBuilder("FastAPI")
    builder.add_topic(
        "middleware",
        "Functions that wrap the request-response cycle, executing code before and after each request.",
        "async def middleware(request, call_next): response = await call_next(request); return response",
        "Middleware runs AFTER route handlers by default"
    )
    builder.add_topic(
        "dependency injection",
        "FastAPI resolves function parameters as dependencies automatically using type annotations.",
        "def get_db() -> Session: return Session()",
        "Dependencies are called for every request, not cached between requests"
    )
    builder.add_topic(
        "pydantic validation",
        "Pydantic models define request/response schemas and validate data at runtime.",
        "class User(BaseModel): name: str; age: int",
        "Validation happens on the entire model, not individual fields separately"
    )

    quiz = builder.build_quiz(target_questions=9)
    print(f"Generated {len(quiz)} questions:")
    for q in quiz:
        print(f"\n  [{q.difficulty}] {q.text[:80]}...")
```

**Checkpoint:** The quiz covers at least 3 distinct framework concepts with a balanced distribution of difficulty levels. Each question has an expected answer and source citation — no ambiguous or open-ended questions without grading criteria.

---

### 5. Spaced Repetition Scheduling — Calculate Review Intervals Using SM-2 Algorithm

Implement the SM-2 (SuperMemo-2) algorithm to schedule knowledge review sessions at optimally spaced intervals. Each concept gets its own review queue with an ease factor and interval that adapt based on self-rated recall performance. Integrate with calendar or task management tools for automated reminders.

```python
from __future__ import annotations

import json
from dataclasses import dataclass, field
from datetime import datetime, timedelta
from pathlib import Path
from typing import Any


@dataclass(frozen=True)
class SM2Card:
    """Represents a single flashcard in the spaced repetition system.

    Implements the SuperMemo-2 algorithm for calculating review intervals
    based on self-rated recall quality (0–5 scale).

    Attributes:
        concept: The framework concept being reviewed.
        question_text: The active recall question to answer.
        next_review_date: Date when this card should be reviewed.
        interval_days: Days between current and next review (0 for new cards).
        ease_factor: Card's stability factor, starts at 2.5, adjusts with performance.
        repetition_count: Number of consecutive successful reviews.
        last_rating: Quality rating from the last review (0–5 scale).
    """
    concept: str
    question_text: str
    next_review_date: datetime
    interval_days: int = 0
    ease_factor: float = 2.5
    repetition_count: int = 0
    last_rating: float = 0.0

    def is_due(self, now: datetime | None = None) -> bool:
        """Check if this card is due for review."""
        check_time = now or datetime.now()
        return check_time >= self.next_review_date


class SM2Scheduler:
    """Implements the SM-2 spaced repetition algorithm for framework knowledge review.

    Each concept starts as a new card (interval 0) and progresses through the learning curve
    based on self-rated recall quality. The ease factor converges toward a stable value
    that determines optimal review intervals.

    Attributes:
        cards: The deck of flashcards being managed.
        history: Log of all review sessions for audit and analysis.
    """

    # SM-2 parameters
    MIN_EASE_FACTOR = 1.3
    QUALITY_THRESHOLDS = {
        0: "completely forgotten — interval resets to 0",
        1: "answered with major difficulty — reduce ease by 0.2",
        2: "answered correctly but slowly — reduce ease by 0.1",
        3: "correct answer after hesitation",
        4: "correct answer with ease",
        5: "perfect recall — increase ease by 0.15",
    }

    def __init__(self, cards_file: Path | None = None) -> None:
        self.cards: list[SM2Card] = []
        self.history: list[dict[str, Any]] = []
        if cards_file and cards_file.exists():
            self._load(cards_file)

    def add_card(self, concept: str, question_text: str, initial_date: datetime | None = None) -> SM2Card:
        """Create a new flashcard for a framework concept.

        Args:
            concept: Name of the concept to review.
            question_text: The active recall question.
            initial_date: First review date (defaults to now).

        Returns:
            The created card object.
        """
        card = SM2Card(
            concept=concept,
            question_text=question_text,
            next_review_date=initial_date or datetime.now(),
        )
        self.cards.append(card)
        return card

    def review_card(self, card_id: int, quality: float, now: datetime | None = None) -> SM2Card:
        """Process a review of a card using the SM-2 algorithm.

        The quality rating (0–5) determines how the interval and ease factor change:
        - 0–1: Card is reset to new state (interval becomes 0)
        - 2: Small interval increase, ease decreases slightly
        - 3: Standard interval progression
        - 4: Larger interval increase
        - 5: Maximum interval increase and slight ease boost

        Args:
            card_id: Index of the card in self.cards to review.
            quality: Self-rated recall quality (0–5, where 0=none, 5=perfect).
            now: Current timestamp for calculating next review date.

        Returns:
            The updated card with new interval and ease factor.
        """
        if now is None:
            now = datetime.now()

        card = self.cards[card_id]
        quality = max(0.0, min(5.0, quality))  # Clamp to valid range

        # Record review history
        self.history.append({
            "card_id": card_id,
            "concept": card.concept,
            "quality": quality,
            "interval_before": card.interval_days,
            "ease_factor_before": card.ease_factor,
            "timestamp": now.isoformat(),
        })

        # SM-2 algorithm core
        if quality < 3:
            # Failed review — reset to beginning
            card.repetition_count = 0
            card.interval_days = 1  # Review again tomorrow
        else:
            # Successful review — apply interval update formula
            if card.repetition_count == 0:
                card.interval_days = 1
            elif

…(truncated)
