feat(patterns): tutorial system
This commit is contained in:
@@ -52,33 +52,45 @@ async def load_categories(session: AsyncSession, data_dir: Path) -> dict[str, Ca
|
||||
|
||||
|
||||
async def load_patterns(session: AsyncSession, data_dir: Path) -> dict[str, Pattern]:
|
||||
"""Load patterns from YAML file."""
|
||||
patterns_file = data_dir / "patterns" / "patterns.yaml"
|
||||
if not patterns_file.exists():
|
||||
print(f"Warning: {patterns_file} not found")
|
||||
return {}
|
||||
|
||||
with open(patterns_file) as f:
|
||||
data = yaml.safe_load(f)
|
||||
"""Load patterns from YAML files.
|
||||
|
||||
Supports both:
|
||||
- Legacy single file: patterns/patterns.yaml
|
||||
- Individual files: patterns/<slug>.yaml (preferred for tutorials)
|
||||
"""
|
||||
patterns_dir = data_dir / "patterns"
|
||||
patterns: dict[str, Pattern] = {}
|
||||
for item in data.get("patterns", []):
|
||||
result = await session.execute(select(Pattern).where(Pattern.slug == item["slug"]))
|
||||
existing = result.scalar_one_or_none()
|
||||
|
||||
if existing:
|
||||
existing.name = item["name"]
|
||||
existing.description = item.get("description")
|
||||
existing.when_to_use = item.get("when_to_use")
|
||||
patterns[item["slug"]] = existing
|
||||
else:
|
||||
pattern = Pattern(
|
||||
name=item["name"],
|
||||
slug=item["slug"],
|
||||
description=item.get("description"),
|
||||
when_to_use=item.get("when_to_use"),
|
||||
)
|
||||
session.add(pattern)
|
||||
# First, try loading individual pattern files (preferred for tutorials)
|
||||
individual_files = list(patterns_dir.glob("*.yaml"))
|
||||
# Filter out the legacy patterns.yaml file
|
||||
individual_files = [f for f in individual_files if f.name != "patterns.yaml"]
|
||||
|
||||
if individual_files:
|
||||
for pattern_file in individual_files:
|
||||
with open(pattern_file) as f:
|
||||
item = yaml.safe_load(f)
|
||||
|
||||
if not item or "slug" not in item:
|
||||
print(f" Warning: Skipping {pattern_file.name} - missing slug")
|
||||
continue
|
||||
|
||||
pattern = await _upsert_pattern(session, item)
|
||||
patterns[item["slug"]] = pattern
|
||||
print(f" Loaded: {item['name']}")
|
||||
|
||||
# Fall back to legacy patterns.yaml if no individual files found
|
||||
legacy_file = patterns_dir / "patterns.yaml"
|
||||
if legacy_file.exists():
|
||||
with open(legacy_file) as f:
|
||||
data = yaml.safe_load(f)
|
||||
|
||||
for item in data.get("patterns", []):
|
||||
# Skip if already loaded from individual file
|
||||
if item["slug"] in patterns:
|
||||
continue
|
||||
|
||||
pattern = await _upsert_pattern(session, item)
|
||||
patterns[item["slug"]] = pattern
|
||||
|
||||
await session.flush()
|
||||
@@ -86,6 +98,41 @@ async def load_patterns(session: AsyncSession, data_dir: Path) -> dict[str, Patt
|
||||
return patterns
|
||||
|
||||
|
||||
async def _upsert_pattern(session: AsyncSession, item: dict[str, Any]) -> Pattern:
|
||||
"""Insert or update a single pattern from YAML data."""
|
||||
result = await session.execute(select(Pattern).where(Pattern.slug == item["slug"]))
|
||||
existing = result.scalar_one_or_none()
|
||||
|
||||
if existing:
|
||||
pattern = existing
|
||||
else:
|
||||
pattern = Pattern(slug=item["slug"])
|
||||
session.add(pattern)
|
||||
|
||||
# Core fields
|
||||
pattern.name = item["name"]
|
||||
pattern.description = item.get("description")
|
||||
pattern.when_to_use = item.get("when_to_use")
|
||||
|
||||
# Tutorial content fields
|
||||
pattern.metaphor = item.get("metaphor")
|
||||
pattern.core_concept = item.get("core_concept")
|
||||
pattern.visualization = item.get("visualization")
|
||||
pattern.code_template = item.get("code_template")
|
||||
|
||||
# Structured data fields (JSONB)
|
||||
pattern.recognition_signals = item.get("recognition_signals")
|
||||
pattern.common_mistakes = item.get("common_mistakes")
|
||||
pattern.variations = item.get("variations")
|
||||
pattern.related_patterns = item.get("related_patterns")
|
||||
pattern.prerequisite_patterns = item.get("prerequisite_patterns")
|
||||
|
||||
# Difficulty level
|
||||
pattern.difficulty_level = item.get("difficulty_level")
|
||||
|
||||
return pattern
|
||||
|
||||
|
||||
async def load_question(
|
||||
session: AsyncSession,
|
||||
question_file: Path,
|
||||
|
||||
Reference in New Issue
Block a user