[split-required] Split 58 monoliths across Python, Go, TypeScript (Phases 1-3)

Phase 1 — Python (klausur-service): 5 monoliths → 36 files
- dsfa_corpus_ingestion.py (1,828 LOC → 5 files)
- cv_ocr_engines.py (2,102 LOC → 7 files)
- cv_layout.py (3,653 LOC → 10 files)
- vocab_worksheet_api.py (2,783 LOC → 8 files)
- grid_build_core.py (1,958 LOC → 6 files)

Phase 2 — Go (edu-search-service, school-service): 8 monoliths → 19 files
- staff_crawler.go (1,402 → 4), policy/store.go (1,168 → 3)
- policy_handlers.go (700 → 2), repository.go (684 → 2)
- search.go (592 → 2), ai_extraction_handlers.go (554 → 2)
- seed_data.go (591 → 2), grade_service.go (646 → 2)

Phase 3 — TypeScript (admin-lehrer): 45 monoliths → 220+ files
- sdk/types.ts (2,108 → 16 domain files)
- ai/rag/page.tsx (2,686 → 14 files)
- 22 page.tsx files split into _components/ + _hooks/
- 11 component files split into sub-components
- 10 SDK data catalogs added to loc-exceptions
- Deleted dead backup index_original.ts (4,899 LOC)

All original public APIs preserved via re-export facades.
Zero new errors: Python imports verified, Go builds clean,
TypeScript tsc --noEmit shows only pre-existing errors.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Benjamin Admin
2026-04-24 17:28:57 +02:00
parent 9ba420fa91
commit b681ddb131
251 changed files with 30016 additions and 25037 deletions

View File

@@ -0,0 +1,226 @@
/**
* LossChart & ProgressRing Components
*
* SVG-based visualizations for training metrics:
* - LossChart: Line chart for training/validation loss curves
* - ProgressRing: Circular progress indicator
*/
'use client'
import type { TrainingDataPoint } from './training-metrics-types'
/**
* Simple line chart component for loss visualization
*/
export function LossChart({
data,
height = 200,
className = ''
}: {
data: TrainingDataPoint[]
height?: number
className?: string
}) {
if (data.length === 0) {
return (
<div className={`flex items-center justify-center bg-slate-50 rounded-lg ${className}`} style={{ height }}>
<span className="text-slate-400 text-sm">Keine Daten</span>
</div>
)
}
// Calculate bounds
const losses = data.map(d => d.loss)
const valLosses = data.filter(d => d.val_loss !== undefined).map(d => d.val_loss!)
const allLosses = [...losses, ...valLosses]
const minLoss = Math.min(...allLosses) * 0.9
const maxLoss = Math.max(...allLosses) * 1.1
const lossRange = maxLoss - minLoss || 1
// SVG dimensions
const width = 400
const padding = { top: 20, right: 20, bottom: 30, left: 50 }
const chartWidth = width - padding.left - padding.right
const chartHeight = height - padding.top - padding.bottom
// Generate path for loss line
const generatePath = (values: number[]) => {
if (values.length === 0) return ''
return values
.map((loss, idx) => {
const x = padding.left + (idx / (values.length - 1 || 1)) * chartWidth
const y = padding.top + chartHeight - ((loss - minLoss) / lossRange) * chartHeight
return `${idx === 0 ? 'M' : 'L'} ${x} ${y}`
})
.join(' ')
}
const lossPath = generatePath(losses)
const valLossPath = valLosses.length > 0 ? generatePath(valLosses) : ''
// Y-axis labels
const yLabels = [maxLoss, (maxLoss + minLoss) / 2, minLoss].map(v => v.toFixed(3))
return (
<svg viewBox={`0 0 ${width} ${height}`} className={`w-full ${className}`} preserveAspectRatio="xMidYMid meet">
{/* Grid lines */}
{[0, 0.5, 1].map((ratio, idx) => (
<line
key={idx}
x1={padding.left}
y1={padding.top + chartHeight * ratio}
x2={width - padding.right}
y2={padding.top + chartHeight * ratio}
stroke="#e2e8f0"
strokeWidth="1"
/>
))}
{/* Y-axis labels */}
{yLabels.map((label, idx) => (
<text
key={idx}
x={padding.left - 5}
y={padding.top + (idx / 2) * chartHeight + 4}
textAnchor="end"
className="fill-slate-500 text-xs"
>
{label}
</text>
))}
{/* X-axis label */}
<text
x={width / 2}
y={height - 5}
textAnchor="middle"
className="fill-slate-500 text-xs"
>
Epoche / Schritt
</text>
{/* Y-axis label */}
<text
x={12}
y={height / 2}
textAnchor="middle"
className="fill-slate-500 text-xs"
transform={`rotate(-90, 12, ${height / 2})`}
>
Loss
</text>
{/* Training loss line */}
<path
d={lossPath}
fill="none"
stroke="#8b5cf6"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
/>
{/* Validation loss line (dashed) */}
{valLossPath && (
<path
d={valLossPath}
fill="none"
stroke="#22c55e"
strokeWidth="2"
strokeDasharray="5,5"
strokeLinecap="round"
strokeLinejoin="round"
/>
)}
{/* Data points */}
{data.map((point, idx) => {
const x = padding.left + (idx / (data.length - 1 || 1)) * chartWidth
const y = padding.top + chartHeight - ((point.loss - minLoss) / lossRange) * chartHeight
return (
<circle
key={idx}
cx={x}
cy={y}
r="3"
fill="#8b5cf6"
className="hover:r-4 transition-all"
>
<title>Epoch {point.epoch}, Step {point.step}: {point.loss.toFixed(4)}</title>
</circle>
)
})}
{/* Legend */}
<g transform={`translate(${padding.left}, ${height - 25})`}>
<line x1="0" y1="0" x2="20" y2="0" stroke="#8b5cf6" strokeWidth="2" />
<text x="25" y="4" className="fill-slate-600 text-xs">Training Loss</text>
{valLossPath && (
<>
<line x1="120" y1="0" x2="140" y2="0" stroke="#22c55e" strokeWidth="2" strokeDasharray="5,5" />
<text x="145" y="4" className="fill-slate-600 text-xs">Val Loss</text>
</>
)}
</g>
</svg>
)
}
/**
* Progress ring component
*/
export function ProgressRing({
progress,
size = 80,
strokeWidth = 6,
className = ''
}: {
progress: number
size?: number
strokeWidth?: number
className?: string
}) {
const radius = (size - strokeWidth) / 2
const circumference = radius * 2 * Math.PI
const offset = circumference - (progress / 100) * circumference
return (
<svg width={size} height={size} className={className}>
{/* Background circle */}
<circle
cx={size / 2}
cy={size / 2}
r={radius}
fill="none"
stroke="#e2e8f0"
strokeWidth={strokeWidth}
/>
{/* Progress circle */}
<circle
cx={size / 2}
cy={size / 2}
r={radius}
fill="none"
stroke="#8b5cf6"
strokeWidth={strokeWidth}
strokeDasharray={circumference}
strokeDashoffset={offset}
strokeLinecap="round"
transform={`rotate(-90 ${size / 2} ${size / 2})`}
className="transition-all duration-300"
/>
{/* Center text */}
<text
x={size / 2}
y={size / 2}
textAnchor="middle"
dominantBaseline="middle"
className="fill-slate-900 font-bold text-lg"
>
{progress.toFixed(0)}%
</text>
</svg>
)
}