Files
breakpilot-lehrer/admin-lehrer/lib/sdk/catalog-manager/catalog-registry.ts
Benjamin Admin b681ddb131 [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>
2026-04-24 17:28:57 +02:00

176 lines
5.3 KiB
TypeScript

/**
* SDK Catalog Manager - Central Registry
*
* Public API for browsing, searching, and converting catalog entries.
* Registry data definitions live in catalog-registry-data.ts.
*/
import type {
CatalogId,
CatalogMeta,
CatalogModule,
CatalogEntry,
CatalogStats,
CatalogOverviewStats,
CustomCatalogEntry,
CustomCatalogs,
} from './types'
import { CATALOG_REGISTRY, SYSTEM_ENTRIES_MAP } from './catalog-registry-data'
// Re-export so existing imports keep working
export { CATALOG_REGISTRY }
// =============================================================================
// HELPER: Resolve localized text fields
// =============================================================================
function resolveField(value: unknown): string {
if (value === null || value === undefined) return ''
if (typeof value === 'string') return value
if (typeof value === 'number') return String(value)
if (typeof value === 'object' && 'de' in (value as Record<string, unknown>)) {
return String((value as Record<string, string>).de || '')
}
return String(value)
}
// =============================================================================
// ENTRY CONVERTERS
// =============================================================================
function systemEntryToCatalogEntry(
catalogId: CatalogId,
data: Record<string, unknown>,
): CatalogEntry {
const meta = CATALOG_REGISTRY[catalogId]
const idValue = resolveField(data[meta.idField])
const nameValue = resolveField(data[meta.nameField])
const descValue = meta.descriptionField ? resolveField(data[meta.descriptionField]) : undefined
const catValue = meta.categoryField ? resolveField(data[meta.categoryField]) : undefined
return {
id: idValue || crypto.randomUUID(),
catalogId,
source: 'system',
data,
displayName: nameValue || idValue || '(Unbenannt)',
displayDescription: descValue,
category: catValue,
}
}
function customEntryToCatalogEntry(
catalogId: CatalogId,
entry: CustomCatalogEntry,
): CatalogEntry {
const meta = CATALOG_REGISTRY[catalogId]
const nameValue = resolveField(entry.data[meta.nameField])
const descValue = meta.descriptionField ? resolveField(entry.data[meta.descriptionField]) : undefined
const catValue = meta.categoryField ? resolveField(entry.data[meta.categoryField]) : undefined
return {
id: entry.id,
catalogId,
source: 'custom',
data: entry.data,
displayName: nameValue || '(Unbenannt)',
displayDescription: descValue,
category: catValue,
}
}
// =============================================================================
// PUBLIC API
// =============================================================================
export function getSystemEntries(catalogId: CatalogId): CatalogEntry[] {
const raw = SYSTEM_ENTRIES_MAP[catalogId] || []
return raw.map(data => systemEntryToCatalogEntry(catalogId, data))
}
export function getAllEntries(
catalogId: CatalogId,
customEntries: CustomCatalogEntry[] = [],
): CatalogEntry[] {
const system = getSystemEntries(catalogId)
const custom = customEntries.map(e => customEntryToCatalogEntry(catalogId, e))
return [...system, ...custom]
}
export function getCatalogsByModule(module: CatalogModule): CatalogMeta[] {
return Object.values(CATALOG_REGISTRY).filter(c => c.module === module)
}
export function getCatalogStats(
catalogId: CatalogId,
customEntries: CustomCatalogEntry[] = [],
): CatalogStats {
const meta = CATALOG_REGISTRY[catalogId]
return {
catalogId,
systemCount: meta.systemCount,
customCount: customEntries.length,
totalCount: meta.systemCount + customEntries.length,
}
}
export function getOverviewStats(customCatalogs: CustomCatalogs): CatalogOverviewStats {
const modules: CatalogModule[] = ['dsfa', 'vvt', 'vendor', 'ai_act', 'reference']
const byModule = {} as Record<CatalogModule, { catalogs: number; entries: number }>
let totalSystemEntries = 0
let totalCustomEntries = 0
for (const mod of modules) {
const cats = getCatalogsByModule(mod)
let entries = 0
for (const cat of cats) {
const customCount = customCatalogs[cat.id]?.length ?? 0
entries += cat.systemCount + customCount
totalSystemEntries += cat.systemCount
totalCustomEntries += customCount
}
byModule[mod] = { catalogs: cats.length, entries }
}
return {
totalCatalogs: Object.keys(CATALOG_REGISTRY).length,
totalSystemEntries,
totalCustomEntries,
totalEntries: totalSystemEntries + totalCustomEntries,
byModule,
}
}
export function searchCatalog(
catalogId: CatalogId,
query: string,
customEntries: CustomCatalogEntry[] = [],
): CatalogEntry[] {
const allEntries = getAllEntries(catalogId, customEntries)
const meta = CATALOG_REGISTRY[catalogId]
const q = query.toLowerCase().trim()
if (!q) return allEntries
return allEntries
.map(entry => {
let score = 0
for (const field of meta.searchableFields) {
const value = resolveField(entry.data[field]).toLowerCase()
if (value.includes(q)) {
score += value.startsWith(q) ? 10 : 5
}
}
// Also search display name
if (entry.displayName.toLowerCase().includes(q)) {
score += 15
}
return { entry, score }
})
.filter(r => r.score > 0)
.sort((a, b) => b.score - a.score)
.map(r => r.entry)
}