Files
breakpilot-lehrer/geo-service/models/learning_node.py
Benjamin Boenisch 5a31f52310 Initial commit: breakpilot-lehrer - Lehrer KI Platform
Services: Admin-Lehrer, Backend-Lehrer, Studio v2, Website,
Klausur-Service, School-Service, Voice-Service, Geo-Service,
BreakPilot Drive, Agent-Core

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-11 23:47:26 +01:00

121 lines
4.7 KiB
Python

"""
Learning Node Models
Pydantic models for educational content nodes
"""
from enum import Enum
from typing import Optional
from pydantic import BaseModel, Field
class LearningTheme(str, Enum):
"""Available learning themes for geographic education."""
TOPOGRAPHIE = "topographie"
LANDNUTZUNG = "landnutzung"
ORIENTIERUNG = "orientierung"
GEOLOGIE = "geologie"
HYDROLOGIE = "hydrologie"
VEGETATION = "vegetation"
class NodeType(str, Enum):
"""Type of learning node interaction."""
QUESTION = "question" # Multiple choice or open question
OBSERVATION = "observation" # Guided observation task
EXPLORATION = "exploration" # Free exploration with hints
class Position(BaseModel):
"""Geographic position for a learning node."""
latitude: float = Field(..., ge=-90, le=90, description="Latitude in degrees")
longitude: float = Field(..., ge=-180, le=180, description="Longitude in degrees")
altitude: Optional[float] = Field(None, description="Altitude in meters")
class LearningNode(BaseModel):
"""
A learning node (station) within a geographic area.
Contains educational content tied to a specific location,
including questions, hints, and explanations.
"""
id: str = Field(..., description="Unique node identifier")
aoi_id: str = Field(..., description="Parent AOI identifier")
title: str = Field(..., min_length=1, max_length=100, description="Node title")
theme: LearningTheme = Field(..., description="Learning theme")
position: dict = Field(..., description="Geographic position")
question: str = Field(..., description="Learning question or task")
hints: list[str] = Field(default_factory=list, description="Progressive hints")
answer: str = Field(..., description="Correct answer or expected observation")
explanation: str = Field(..., description="Didactic explanation")
node_type: NodeType = Field(NodeType.QUESTION, description="Interaction type")
points: int = Field(10, ge=1, le=100, description="Points awarded for completion")
approved: bool = Field(False, description="Teacher-approved for student use")
media: Optional[list[dict]] = Field(None, description="Associated media files")
tags: Optional[list[str]] = Field(None, description="Content tags")
difficulty: Optional[str] = Field(None, description="Difficulty level")
grade_level: Optional[str] = Field(None, description="Target grade level")
class Config:
json_schema_extra = {
"example": {
"id": "node-001",
"aoi_id": "550e8400-e29b-41d4-a716-446655440000",
"title": "Höhenbestimmung",
"theme": "topographie",
"position": {"latitude": 47.7085, "longitude": 9.1925},
"question": "Schätze die Höhe dieses Punktes über dem Meeresspiegel.",
"hints": [
"Schau dir die Vegetation an.",
"Vergleiche mit dem Seespiegel des Bodensees (395m).",
],
"answer": "Ca. 430 Meter über NN",
"explanation": "Die Höhe lässt sich aus der Vegetation und der relativen Position zum Bodensee abschätzen.",
"node_type": "question",
"points": 10,
"approved": True,
}
}
class LearningNodeRequest(BaseModel):
"""Request model for creating a learning node manually."""
title: str = Field(..., min_length=1, max_length=100)
theme: LearningTheme
position: Position
question: str
hints: list[str] = Field(default_factory=list)
answer: str
explanation: str
node_type: NodeType = NodeType.QUESTION
points: int = Field(10, ge=1, le=100)
tags: Optional[list[str]] = None
difficulty: Optional[str] = Field(None, pattern="^(leicht|mittel|schwer)$")
grade_level: Optional[str] = None
class LearningNodeBatch(BaseModel):
"""Batch of learning nodes for bulk operations."""
nodes: list[LearningNode]
total_points: int = 0
def calculate_total_points(self) -> int:
"""Calculate total points from all nodes."""
self.total_points = sum(node.points for node in self.nodes)
return self.total_points
class LearningProgress(BaseModel):
"""Student progress through learning nodes."""
student_id: str
aoi_id: str
completed_nodes: list[str] = Field(default_factory=list)
total_points: int = 0
started_at: Optional[str] = None
completed_at: Optional[str] = None
@property
def completion_percentage(self) -> float:
"""Calculate completion percentage."""
# Would need total node count for accurate calculation
return len(self.completed_nodes) * 10 # Placeholder