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>
121 lines
4.7 KiB
Python
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
|