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.
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."
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.
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.
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.
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)