feat: BreakPilot PWA - Full codebase (clean push without large binaries)
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

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.
This commit is contained in:
BreakPilot Dev
2026-02-11 13:25:58 +01:00
commit 19855efacc
2512 changed files with 933814 additions and 0 deletions

View File

@@ -0,0 +1,120 @@
"""
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