This repository has been archived on 2026-02-15. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
breakpilot-pwa/geo-service/models/learning_node.py
BreakPilot Dev 19855efacc
Some checks failed
Tests / Go Tests (push) Has been cancelled
Tests / Python Tests (push) Has been cancelled
Tests / Integration Tests (push) Has been cancelled
Tests / Go Lint (push) Has been cancelled
Tests / Python Lint (push) Has been cancelled
Tests / Security Scan (push) Has been cancelled
Tests / All Checks Passed (push) Has been cancelled
Security Scanning / Secret Scanning (push) Has been cancelled
Security Scanning / Dependency Vulnerability Scan (push) Has been cancelled
Security Scanning / Go Security Scan (push) Has been cancelled
Security Scanning / Python Security Scan (push) Has been cancelled
Security Scanning / Node.js Security Scan (push) Has been cancelled
Security Scanning / Docker Image Security (push) Has been cancelled
Security Scanning / Security Summary (push) Has been cancelled
CI/CD Pipeline / Go Tests (push) Has been cancelled
CI/CD Pipeline / Python Tests (push) Has been cancelled
CI/CD Pipeline / Website Tests (push) Has been cancelled
CI/CD Pipeline / Linting (push) Has been cancelled
CI/CD Pipeline / Security Scan (push) Has been cancelled
CI/CD Pipeline / Docker Build & Push (push) Has been cancelled
CI/CD Pipeline / Integration Tests (push) Has been cancelled
CI/CD Pipeline / Deploy to Staging (push) Has been cancelled
CI/CD Pipeline / Deploy to Production (push) Has been cancelled
CI/CD Pipeline / CI Summary (push) Has been cancelled
ci/woodpecker/manual/build-ci-image Pipeline was successful
ci/woodpecker/manual/main Pipeline failed
feat: BreakPilot PWA - Full codebase (clean push without large binaries)
All services: admin-v2, studio-v2, website, ai-compliance-sdk,
consent-service, klausur-service, voice-service, and infrastructure.
Large PDFs and compiled binaries excluded via .gitignore.
2026-02-11 13:25:58 +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