import { NextRequest, NextResponse } from 'next/server' /** * Abitur-Archiv API Route * Extends abitur-docs with theme search and enhanced filtering */ const BACKEND_URL = process.env.BACKEND_URL || 'http://localhost:8000' export async function GET(request: NextRequest) { try { const { searchParams } = new URL(request.url) // Check for theme/semantic search const thema = searchParams.get('thema') if (thema) { // Use semantic search endpoint return await handleSemanticSearch(thema, searchParams) } // Forward all query params to backend abitur-docs const queryString = searchParams.toString() const url = `${BACKEND_URL}/api/abitur-docs/${queryString ? `?${queryString}` : ''}` const response = await fetch(url, { method: 'GET', headers: { 'Content-Type': 'application/json', }, }) if (!response.ok) { // Return mock data for development if backend is not available if (response.status === 404 || response.status === 502) { return NextResponse.json(getMockDocuments(searchParams)) } throw new Error(`Backend responded with ${response.status}`) } const data = await response.json() // If backend returns empty, use mock data for demo if (data.documents && Array.isArray(data.documents) && data.documents.length === 0 && data.total === 0) { return NextResponse.json(getMockDocuments(searchParams)) } // Enhance response with theme information return NextResponse.json({ ...data, themes: extractThemes(data.documents || []) }) } catch (error) { console.error('Abitur-Archiv error:', error) return NextResponse.json(getMockDocuments(new URL(request.url).searchParams)) } } async function handleSemanticSearch(thema: string, searchParams: URLSearchParams) { try { // Try to call RAG search endpoint const url = `${BACKEND_URL}/api/rag/search` const response = await fetch(url, { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ query: thema, collection: 'abitur_documents', limit: parseInt(searchParams.get('limit') || '20'), filters: { fach: searchParams.get('fach') || undefined, jahr: searchParams.get('jahr') ? parseInt(searchParams.get('jahr')!) : undefined, bundesland: searchParams.get('bundesland') || undefined, niveau: searchParams.get('niveau') || undefined, typ: searchParams.get('typ') || undefined, } }), }) if (response.ok) { const data = await response.json() return NextResponse.json({ documents: data.results || [], total: data.total || 0, page: 1, limit: parseInt(searchParams.get('limit') || '20'), total_pages: 1, search_query: thema }) } } catch (error) { console.log('RAG search not available, falling back to mock') } // Fallback to filtered mock data return NextResponse.json(getMockDocumentsWithTheme(thema, searchParams)) } function getMockDocuments(searchParams: URLSearchParams) { const page = parseInt(searchParams.get('page') || '1') const limit = parseInt(searchParams.get('limit') || '20') const fach = searchParams.get('fach') const jahr = searchParams.get('jahr') const bundesland = searchParams.get('bundesland') const niveau = searchParams.get('niveau') const typ = searchParams.get('typ') // Generate mock documents const allDocs = generateMockDocs() // Apply filters let filtered = allDocs if (fach) filtered = filtered.filter(d => d.fach === fach) if (jahr) filtered = filtered.filter(d => d.jahr === parseInt(jahr)) if (bundesland) filtered = filtered.filter(d => d.bundesland === bundesland) if (niveau) filtered = filtered.filter(d => d.niveau === niveau) if (typ) filtered = filtered.filter(d => d.typ === typ) // Paginate const start = (page - 1) * limit const docs = filtered.slice(start, start + limit) return { documents: docs, total: filtered.length, page, limit, total_pages: Math.ceil(filtered.length / limit), themes: extractThemes(docs) } } function getMockDocumentsWithTheme(thema: string, searchParams: URLSearchParams) { const limit = parseInt(searchParams.get('limit') || '20') const allDocs = generateMockDocs() // Simple theme matching (in production this would be semantic search) const themaLower = thema.toLowerCase() let filtered = allDocs // Match theme to aufgabentyp keywords if (themaLower.includes('gedicht')) { filtered = filtered.filter(d => d.themes?.includes('gedichtanalyse')) } else if (themaLower.includes('drama')) { filtered = filtered.filter(d => d.themes?.includes('dramenanalyse')) } else if (themaLower.includes('prosa') || themaLower.includes('roman')) { filtered = filtered.filter(d => d.themes?.includes('prosaanalyse')) } else if (themaLower.includes('eroerterung')) { filtered = filtered.filter(d => d.themes?.includes('eroerterung')) } else if (themaLower.includes('text') || themaLower.includes('analyse')) { filtered = filtered.filter(d => d.themes?.includes('textanalyse')) } // Apply additional filters const fach = searchParams.get('fach') const jahr = searchParams.get('jahr') if (fach) filtered = filtered.filter(d => d.fach === fach) if (jahr) filtered = filtered.filter(d => d.jahr === parseInt(jahr)) return { documents: filtered.slice(0, limit), total: filtered.length, page: 1, limit, total_pages: Math.ceil(filtered.length / limit), search_query: thema, themes: extractThemes(filtered) } } function generateMockDocs() { const faecher = ['deutsch', 'englisch'] const jahre = [2021, 2022, 2023, 2024, 2025] const niveaus: Array<'eA' | 'gA'> = ['eA', 'gA'] const typen: Array<'aufgabe' | 'erwartungshorizont'> = ['aufgabe', 'erwartungshorizont'] const aufgabentypen = [ { nummer: 'I', themes: ['textanalyse', 'sachtext'] }, { nummer: 'II', themes: ['gedichtanalyse', 'lyrik'] }, { nummer: 'III', themes: ['prosaanalyse', 'epik'] }, ] const docs = [] let id = 1 for (const jahr of jahre) { for (const fach of faecher) { for (const niveau of niveaus) { for (const aufgabe of aufgabentypen) { for (const typ of typen) { const suffix = typ === 'erwartungshorizont' ? '_EWH' : '' const dateiname = `${jahr}_${capitalize(fach)}_${niveau}_Aufgabe_${aufgabe.nummer}${suffix}.pdf` docs.push({ id: `doc-${id++}`, dateiname, original_dateiname: dateiname, bundesland: 'niedersachsen', fach, jahr, niveau, typ, aufgaben_nummer: aufgabe.nummer, themes: aufgabe.themes, status: 'indexed' as const, confidence: 0.92 + Math.random() * 0.08, file_path: `/api/education/abitur-archiv/file/${dateiname}`, file_size: Math.floor(Math.random() * 500000) + 100000, indexed: true, vector_ids: [`vec-${id}-1`, `vec-${id}-2`], created_at: new Date(Date.now() - Math.random() * 365 * 24 * 60 * 60 * 1000).toISOString(), updated_at: new Date().toISOString(), }) } } } } } return docs } function extractThemes(documents: any[]) { const themeCounts = new Map() for (const doc of documents) { const themes = doc.themes || [] for (const theme of themes) { themeCounts.set(theme, (themeCounts.get(theme) || 0) + 1) } } return Array.from(themeCounts.entries()) .map(([label, count]) => ({ label: capitalize(label), count, aufgabentyp: label, })) .sort((a, b) => b.count - a.count) .slice(0, 10) } function capitalize(str: string): string { return str.charAt(0).toUpperCase() + str.slice(1) }