[split-required] Split 700-870 LOC files across all services

backend-lehrer (11 files):
- llm_gateway/routes/schools.py (867 → 5), recording_api.py (848 → 6)
- messenger_api.py (840 → 5), print_generator.py (824 → 5)
- unit_analytics_api.py (751 → 5), classroom/routes/context.py (726 → 4)
- llm_gateway/routes/edu_search_seeds.py (710 → 4)

klausur-service (12 files):
- ocr_labeling_api.py (845 → 4), metrics_db.py (833 → 4)
- legal_corpus_api.py (790 → 4), page_crop.py (758 → 3)
- mail/ai_service.py (747 → 4), github_crawler.py (767 → 3)
- trocr_service.py (730 → 4), full_compliance_pipeline.py (723 → 4)
- dsfa_rag_api.py (715 → 4), ocr_pipeline_auto.py (705 → 4)

website (6 pages):
- audit-checklist (867 → 8), content (806 → 6)
- screen-flow (790 → 4), scraper (789 → 5)
- zeugnisse (776 → 5), modules (745 → 4)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Benjamin Admin
2026-04-25 08:01:18 +02:00
parent b6983ab1dc
commit 34da9f4cda
106 changed files with 16500 additions and 16947 deletions

View File

@@ -0,0 +1,91 @@
'use client'
import {
ServiceModule, RiskAssessment,
SERVICE_TYPE_CONFIG, CRITICALITY_CONFIG, RELEVANCE_CONFIG,
} from './types'
interface ModuleDetailPanelProps {
module: ServiceModule;
loadingDetail: boolean;
loadingRisk: boolean;
showRiskPanel: boolean;
riskAssessment: RiskAssessment | null;
onClose: () => void;
onAssessRisk: (moduleId: string) => void;
onCloseRisk: () => void;
}
export default function ModuleDetailPanel({
module, loadingDetail, loadingRisk, showRiskPanel, riskAssessment,
onClose, onAssessRisk, onCloseRisk,
}: ModuleDetailPanelProps) {
return (
<div className="w-96 bg-white rounded-lg shadow border sticky top-6 h-fit">
<div className={`px-4 py-3 border-b ${SERVICE_TYPE_CONFIG[module.service_type]?.bgColor || 'bg-gray-100'}`}>
<div className="flex items-center justify-between">
<span className="text-lg">{SERVICE_TYPE_CONFIG[module.service_type]?.icon || '📁'}</span>
<button onClick={onClose} className="text-gray-400 hover:text-gray-600"></button>
</div>
<h3 className="font-bold text-lg mt-2">{module.display_name}</h3>
<div className="text-sm text-gray-600">{module.name}</div>
</div>
{loadingDetail ? (
<div className="p-4 text-center text-gray-500">Lade Details...</div>
) : (
<div className="p-4 space-y-4">
{module.description && (<div><div className="text-xs text-gray-500 uppercase mb-1">Beschreibung</div><div className="text-sm text-gray-700">{module.description}</div></div>)}
<div className="grid grid-cols-2 gap-2 text-sm">
{module.port && (<div><span className="text-gray-500">Port:</span><span className="ml-1 font-mono">{module.port}</span></div>)}
<div><span className="text-gray-500">Criticality:</span><span className={`ml-1 px-1.5 py-0.5 rounded text-xs ${CRITICALITY_CONFIG[module.criticality]?.bgColor || ''} ${CRITICALITY_CONFIG[module.criticality]?.color || ''}`}>{module.criticality}</span></div>
</div>
<div><div className="text-xs text-gray-500 uppercase mb-1">Tech Stack</div><div className="flex flex-wrap gap-1">{module.technology_stack.map((tech, i) => (<span key={i} className="px-2 py-0.5 bg-gray-100 text-gray-700 text-xs rounded">{tech}</span>))}</div></div>
{module.data_categories.length > 0 && (<div><div className="text-xs text-gray-500 uppercase mb-1">Daten-Kategorien</div><div className="flex flex-wrap gap-1">{module.data_categories.map((cat, i) => (<span key={i} className="px-2 py-0.5 bg-blue-50 text-blue-700 text-xs rounded">{cat}</span>))}</div></div>)}
<div className="flex flex-wrap gap-2">
{module.processes_pii && (<span className="px-2 py-1 bg-purple-100 text-purple-700 text-xs rounded">Verarbeitet PII</span>)}
{module.ai_components && (<span className="px-2 py-1 bg-pink-100 text-pink-700 text-xs rounded">AI-Komponenten</span>)}
{module.processes_health_data && (<span className="px-2 py-1 bg-red-100 text-red-700 text-xs rounded">Gesundheitsdaten</span>)}
</div>
{module.regulations && module.regulations.length > 0 && (
<div>
<div className="text-xs text-gray-500 uppercase mb-2">Applicable Regulations ({module.regulations.length})</div>
<div className="space-y-2">
{module.regulations.map((reg, i) => (
<div key={i} className="p-2 bg-gray-50 rounded text-sm">
<div className="flex justify-between items-start"><span className="font-medium">{reg.code}</span><span className={`px-1.5 py-0.5 rounded text-xs ${RELEVANCE_CONFIG[reg.relevance_level]?.bgColor || 'bg-gray-100'} ${RELEVANCE_CONFIG[reg.relevance_level]?.color || 'text-gray-700'}`}>{reg.relevance_level}</span></div>
<div className="text-gray-500 text-xs">{reg.name}</div>
{reg.notes && (<div className="text-gray-600 text-xs mt-1 italic">{reg.notes}</div>)}
</div>
))}
</div>
</div>
)}
{module.owner_team && (<div><div className="text-xs text-gray-500 uppercase mb-1">Owner</div><div className="text-sm text-gray-700">{module.owner_team}</div></div>)}
{module.repository_path && (<div><div className="text-xs text-gray-500 uppercase mb-1">Repository</div><code className="text-xs bg-gray-100 px-2 py-1 rounded block">{module.repository_path}</code></div>)}
<div className="pt-2 border-t">
<button onClick={() => onAssessRisk(module.id)} disabled={loadingRisk} className="w-full px-4 py-2 bg-gradient-to-r from-purple-600 to-pink-600 text-white rounded-lg hover:from-purple-700 hover:to-pink-700 transition disabled:opacity-50 flex items-center justify-center gap-2">
{loadingRisk ? (<><span className="animate-spin"></span>AI analysiert...</>) : (<><span>🤖</span>AI Risikobewertung</>)}
</button>
</div>
{showRiskPanel && (
<div className="mt-4 p-4 bg-gradient-to-br from-purple-50 to-pink-50 rounded-lg border border-purple-200">
<div className="flex justify-between items-center mb-3">
<h4 className="font-semibold text-purple-900 flex items-center gap-2"><span>🤖</span> AI Risikobewertung</h4>
<button onClick={onCloseRisk} className="text-purple-400 hover:text-purple-600"></button>
</div>
{loadingRisk ? (<div className="text-center py-4 text-purple-600"><div className="animate-pulse">Analysiere Compliance-Risiken...</div></div>) : riskAssessment ? (
<div className="space-y-3">
<div className="flex items-center gap-2"><span className="text-sm text-gray-600">Gesamtrisiko:</span><span className={`px-2 py-1 rounded text-sm font-medium ${riskAssessment.overall_risk === 'critical' ? 'bg-red-100 text-red-700' : riskAssessment.overall_risk === 'high' ? 'bg-orange-100 text-orange-700' : riskAssessment.overall_risk === 'medium' ? 'bg-yellow-100 text-yellow-700' : 'bg-green-100 text-green-700'}`}>{riskAssessment.overall_risk.toUpperCase()}</span><span className="text-xs text-gray-400">({Math.round(riskAssessment.confidence_score * 100)}% Konfidenz)</span></div>
{riskAssessment.risk_factors.length > 0 && (<div><div className="text-xs text-gray-500 uppercase mb-1">Risikofaktoren</div><div className="space-y-1">{riskAssessment.risk_factors.map((factor, i) => (<div key={i} className="flex items-center justify-between text-sm bg-white/50 rounded px-2 py-1"><span className="text-gray-700">{factor.factor}</span><span className={`text-xs px-1.5 py-0.5 rounded ${factor.severity === 'critical' || factor.severity === 'high' ? 'bg-red-100 text-red-600' : 'bg-yellow-100 text-yellow-600'}`}>{factor.severity}</span></div>))}</div></div>)}
{riskAssessment.compliance_gaps.length > 0 && (<div><div className="text-xs text-gray-500 uppercase mb-1">Compliance-Luecken</div><ul className="text-sm text-gray-700 space-y-1">{riskAssessment.compliance_gaps.map((gap, i) => (<li key={i} className="flex items-start gap-1"><span className="text-red-500"></span><span>{gap}</span></li>))}</ul></div>)}
{riskAssessment.recommendations.length > 0 && (<div><div className="text-xs text-gray-500 uppercase mb-1">Empfehlungen</div><ul className="text-sm text-gray-700 space-y-1">{riskAssessment.recommendations.map((rec, i) => (<li key={i} className="flex items-start gap-1"><span className="text-green-500"></span><span>{rec}</span></li>))}</ul></div>)}
</div>
) : (<div className="text-center py-4 text-gray-500 text-sm">Klicken Sie auf &quot;AI Risikobewertung&quot; um eine Analyse zu starten.</div>)}
</div>
)}
</div>
)}
</div>
)
}

View File

@@ -0,0 +1,71 @@
export interface ServiceModule {
id: string;
name: string;
display_name: string;
description: string | null;
service_type: string;
port: number | null;
technology_stack: string[];
repository_path: string | null;
docker_image: string | null;
data_categories: string[];
processes_pii: boolean;
processes_health_data: boolean;
ai_components: boolean;
criticality: string;
owner_team: string | null;
is_active: boolean;
compliance_score: number | null;
regulation_count: number;
risk_count: number;
created_at: string;
regulations?: Array<{
code: string;
name: string;
relevance_level: string;
notes: string | null;
}>;
}
export interface ModulesOverview {
total_modules: number;
modules_by_type: Record<string, number>;
modules_by_criticality: Record<string, number>;
modules_processing_pii: number;
modules_with_ai: number;
average_compliance_score: number | null;
regulations_coverage: Record<string, number>;
}
export interface RiskAssessment {
overall_risk: string;
risk_factors: Array<{ factor: string; severity: string; likelihood: string }>;
recommendations: string[];
compliance_gaps: string[];
confidence_score: number;
}
export const SERVICE_TYPE_CONFIG: Record<string, { icon: string; color: string; bgColor: string }> = {
backend: { icon: '⚙️', color: 'text-blue-700', bgColor: 'bg-blue-100' },
database: { icon: '🗄️', color: 'text-purple-700', bgColor: 'bg-purple-100' },
ai: { icon: '🤖', color: 'text-pink-700', bgColor: 'bg-pink-100' },
communication: { icon: '💬', color: 'text-green-700', bgColor: 'bg-green-100' },
storage: { icon: '📦', color: 'text-orange-700', bgColor: 'bg-orange-100' },
infrastructure: { icon: '🌐', color: 'text-gray-700', bgColor: 'bg-gray-100' },
monitoring: { icon: '📊', color: 'text-cyan-700', bgColor: 'bg-cyan-100' },
security: { icon: '🔒', color: 'text-red-700', bgColor: 'bg-red-100' },
};
export const CRITICALITY_CONFIG: Record<string, { color: string; bgColor: string }> = {
critical: { color: 'text-red-700', bgColor: 'bg-red-100' },
high: { color: 'text-orange-700', bgColor: 'bg-orange-100' },
medium: { color: 'text-yellow-700', bgColor: 'bg-yellow-100' },
low: { color: 'text-green-700', bgColor: 'bg-green-100' },
};
export const RELEVANCE_CONFIG: Record<string, { color: string; bgColor: string }> = {
critical: { color: 'text-red-700', bgColor: 'bg-red-100' },
high: { color: 'text-orange-700', bgColor: 'bg-orange-100' },
medium: { color: 'text-yellow-700', bgColor: 'bg-yellow-100' },
low: { color: 'text-green-700', bgColor: 'bg-green-100' },
};

View File

@@ -0,0 +1,110 @@
'use client'
import { useState, useEffect } from 'react'
import { ServiceModule, ModulesOverview, RiskAssessment } from './types'
const BACKEND_URL = process.env.NEXT_PUBLIC_BACKEND_URL || 'http://localhost:8000'
const API_BASE = `${BACKEND_URL}/api/v1/compliance`
export function useModulesPage() {
const [modules, setModules] = useState<ServiceModule[]>([])
const [overview, setOverview] = useState<ModulesOverview | null>(null)
const [loading, setLoading] = useState(true)
const [error, setError] = useState<string | null>(null)
const [typeFilter, setTypeFilter] = useState<string>('all')
const [criticalityFilter, setCriticalityFilter] = useState<string>('all')
const [piiFilter, setPiiFilter] = useState<boolean | null>(null)
const [aiFilter, setAiFilter] = useState<boolean | null>(null)
const [searchTerm, setSearchTerm] = useState('')
const [selectedModule, setSelectedModule] = useState<ServiceModule | null>(null)
const [loadingDetail, setLoadingDetail] = useState(false)
const [riskAssessment, setRiskAssessment] = useState<RiskAssessment | null>(null)
const [loadingRisk, setLoadingRisk] = useState(false)
const [showRiskPanel, setShowRiskPanel] = useState(false)
useEffect(() => { fetchModules(); fetchOverview() }, [])
const fetchModules = async () => {
try {
setLoading(true)
const params = new URLSearchParams()
if (typeFilter !== 'all') params.append('service_type', typeFilter)
if (criticalityFilter !== 'all') params.append('criticality', criticalityFilter)
if (piiFilter !== null) params.append('processes_pii', String(piiFilter))
if (aiFilter !== null) params.append('ai_components', String(aiFilter))
const url = `${API_BASE}/modules${params.toString() ? '?' + params.toString() : ''}`
const res = await fetch(url)
if (!res.ok) throw new Error('Failed to fetch modules')
const data = await res.json()
setModules(data.modules || [])
} catch (err) {
setError(err instanceof Error ? err.message : 'Unknown error')
} finally { setLoading(false) }
}
const fetchOverview = async () => {
try {
const res = await fetch(`${API_BASE}/modules/overview`)
if (!res.ok) throw new Error('Failed to fetch overview')
const data = await res.json()
setOverview(data)
} catch (err) { console.error('Failed to fetch overview:', err) }
}
const fetchModuleDetail = async (moduleId: string) => {
try {
setLoadingDetail(true)
const res = await fetch(`${API_BASE}/modules/${moduleId}`)
if (!res.ok) throw new Error('Failed to fetch module details')
const data = await res.json()
setSelectedModule(data)
} catch (err) { console.error('Failed to fetch module details:', err) }
finally { setLoadingDetail(false) }
}
const seedModules = async (force: boolean = false) => {
try {
const res = await fetch(`${API_BASE}/modules/seed`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ force }) })
if (!res.ok) throw new Error('Failed to seed modules')
const data = await res.json()
alert(`Seeded ${data.modules_created} modules with ${data.mappings_created} regulation mappings`)
fetchModules(); fetchOverview()
} catch (err) { alert('Failed to seed modules: ' + (err instanceof Error ? err.message : 'Unknown error')) }
}
const assessModuleRisk = async (moduleId: string) => {
setLoadingRisk(true); setShowRiskPanel(true); setRiskAssessment(null)
try {
const res = await fetch(`${API_BASE}/ai/assess-risk`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ module_id: moduleId }) })
if (res.ok) { const data = await res.json(); setRiskAssessment(data) }
else { alert('AI-Risikobewertung fehlgeschlagen') }
} catch (err) { alert('Netzwerkfehler bei AI-Risikobewertung') }
finally { setLoadingRisk(false) }
}
const filteredModules = modules.filter(m => {
if (!searchTerm) return true
const term = searchTerm.toLowerCase()
return m.name.toLowerCase().includes(term) || m.display_name.toLowerCase().includes(term) || (m.description && m.description.toLowerCase().includes(term)) || m.technology_stack.some(t => t.toLowerCase().includes(term))
})
const modulesByType = filteredModules.reduce((acc, m) => {
const type = m.service_type || 'unknown'
if (!acc[type]) acc[type] = []
acc[type].push(m)
return acc
}, {} as Record<string, ServiceModule[]>)
return {
modules, overview, loading, error,
typeFilter, setTypeFilter, criticalityFilter, setCriticalityFilter,
piiFilter, setPiiFilter, aiFilter, setAiFilter, searchTerm, setSearchTerm,
selectedModule, setSelectedModule, loadingDetail,
riskAssessment, loadingRisk, showRiskPanel, setShowRiskPanel,
filteredModules, modulesByType,
fetchModules, fetchModuleDetail, seedModules, assessModuleRisk,
}
}