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>
176 lines
5.3 KiB
TypeScript
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)
|
|
}
|