# edu-search-service

Spezialisierter Suchdienst für deutsche Bildungsinhalte - eine Alternative zu Tavily, optimiert für den deutschen Bildungssektor.

## Übersicht

Der edu-search-service crawlt, extrahiert und indiziert Bildungsinhalte von deutschen Bildungsquellen (Kultusministerien, Bildungsserver, wissenschaftliche Studien, etc.) und stellt eine Such-API bereit.

### Features

- **BM25 Keyword-Suche** mit German Analyzer (OpenSearch)
- **Semantic Search** mit Embeddings (OpenAI oder Ollama)
- **Hybrid Search** kombiniert BM25 + Vektor-Ähnlichkeit
- **Automatisches Tagging** für Dokumenttyp, Fächer, Schulstufe, Bundesland
- **Trust-Score** basierend auf Domain-Reputation und Content-Qualität
- **Rate-Limited Crawler** mit robots.txt Respekt
- **Admin API** für Seed-Verwaltung und Crawl-Steuerung

## Architektur

```
┌─────────────────────────────────────────────────────────────────────┐
│                        edu-search-service                           │
├─────────────────────────────────────────────────────────────────────┤
│                                                                     │
│   ┌─────────┐    ┌───────────┐    ┌────────┐    ┌─────────┐       │
│   │ Crawler │───▶│ Extractor │───▶│ Tagger │───▶│ Indexer │       │
│   └─────────┘    └───────────┘    └────────┘    └─────────┘       │
│        │                                              │             │
│        ▼                                              ▼             │
│   ┌─────────┐                                  ┌────────────┐      │
│   │  Seeds  │                                  │ OpenSearch │      │
│   └─────────┘                                  └────────────┘      │
│                                                      │             │
│                     ┌────────────┐                   │             │
│                     │ Search API │◀──────────────────┘             │
│                     └────────────┘                                 │
│                                                                     │
└─────────────────────────────────────────────────────────────────────┘
```

## Komponenten

### Crawler (`internal/crawler/`)
- Rate-Limited HTTP Client (Standard: 0.2 req/sec pro Domain)
- Denylist-Support für ungewünschte Domains
- **Seeds aus Backend-API** (primär) oder lokale Seed-Files (Fallback)
- URL-Normalisierung und Deduplication
- Seed-Metadaten: Trust-Boost, Crawl-Tiefe, Kategorie, Bundesland
- **Crawl-Status-Feedback** an Backend (Dokumentenzahl, Dauer, Fehler)

### Robots (`internal/robots/`)
- **robots.txt Parser** mit Caching (24h TTL)
- Unterstützt `Disallow`, `Allow`, `Crawl-delay`
- Wildcard-Patterns (`*`) und End-Anchors (`$`)
- User-Agent-spezifische Regeln
- Leniente Behandlung bei fehlenden robots.txt

### Extractor (`internal/extractor/`)
- HTML-Extraktion mit goquery
- **PDF-Textextraktion** mit ledongthuc/pdf Bibliothek
  - `ExtractPDF()` - Standard-Extraktion mit GetPlainText
  - `ExtractPDFWithMetadata()` - Seiten-weise Extraktion für mehr Kontrolle
  - Fallback-Extraktion bei beschädigten PDFs
  - Automatische Titel-Erkennung (erste signifikante Zeile)
  - Heading-Erkennung (All-Caps, nummerierte Zeilen)
- Metadaten-Extraktion (og:title, description, etc.)
- Content-Feature-Berechnung (Ad-Density, Link-Density)
- Sprach-Erkennung (Deutsch/Englisch)

### Tagger (`internal/tagger/`)
- Regelbasiertes Tagging via YAML-Konfiguration
- DocType-Erkennung (Lehrplan, Arbeitsblatt, Studie, etc.)
- Fächer-Erkennung (Mathematik, Deutsch, etc.)
- Schulstufen-Erkennung (Grundschule, Sek I/II, etc.)
- Bundesland-Erkennung aus URL-Patterns
- Trust-Score-Berechnung

### Quality (`internal/quality/`)
- **Multi-Faktor Quality-Score** (0-1)
  - Content Length (20%)
  - Heading Structure (15%)
  - Link/Ad Quality (15%)
  - Text-to-HTML Ratio (15%)
  - Metadata Presence (10%)
  - Language Clarity (10%)
  - Content Freshness (10%)
  - PDF-Specific Signals (5%)
- Konfigurierbare Gewichtungen
- Date-Indicator-Extraktion für Frische-Bewertung

### Indexer (`internal/indexer/`)
- OpenSearch 2.11 Client
- German Analyzer für BM25
- Bulk-Indexierung
- Custom Mapping für Bildungsdokumente

### Search (`internal/search/`)
- Multi-Match Query mit Boosting
- Filter für alle Taxonomie-Felder
- Function-Score mit Trust/Quality-Boosting
- Highlighting-Support
- **Drei Suchmodi:**
  - `keyword` - Klassische BM25-Suche (Default)
  - `semantic` - Reine Vektor-Ähnlichkeitssuche (k-NN)
  - `hybrid` - Kombination aus BM25 und Vektor-Score

### Embedding (`internal/embedding/`)
- **OpenAI Provider** - `text-embedding-3-small` (1536 Dimensionen)
- **Ollama Provider** - Lokale Modelle (z.B. `nomic-embed-text`, 384-768 Dim.)
- Batch-Embedding für effiziente Indexierung
- Automatische Text-Kürzung (max. 30.000 Zeichen)

### Scheduler (`internal/scheduler/`)
- **Automatisches Crawling** in konfigurierbaren Intervallen
- Default: täglich um 2:00 Uhr (minimale Auswirkung)
- Manuelles Triggern via Admin-API
- Status-Tracking (letzter Lauf, nächster Lauf, Ergebnis)

## API Endpoints

### Public Endpoints

| Method | Endpoint | Beschreibung |
|--------|----------|--------------|
| GET | `/v1/health` | Health Check (kein Auth) |
| POST | `/v1/search` | Suche ausführen |
| GET | `/v1/document` | Einzeldokument abrufen |

### Admin Endpoints (Auth erforderlich)

| Method | Endpoint | Beschreibung |
|--------|----------|--------------|
| GET | `/v1/admin/seeds` | Alle Seeds abrufen |
| POST | `/v1/admin/seeds` | Neuen Seed erstellen |
| PUT | `/v1/admin/seeds/:id` | Seed aktualisieren |
| DELETE | `/v1/admin/seeds/:id` | Seed löschen |
| GET | `/v1/admin/stats` | Crawl-Statistiken |
| POST | `/v1/admin/crawl/start` | Crawl starten |

## API Dokumentation

### POST /v1/search

**Request Body:**
```json
{
  "q": "Lehrplan Mathematik Gymnasium",
  "mode": "keyword",
  "limit": 10,
  "offset": 0,
  "filters": {
    "language": ["de"],
    "doc_type": ["Lehrplan"],
    "school_level": ["Gymnasium"],
    "state": ["BY", "NW"],
    "subjects": ["Mathematik"],
    "min_trust_score": 0.5
  },
  "include": {
    "snippets": true,
    "highlights": true
  }
}
```

**Such-Modi (`mode`):**
| Mode | Beschreibung |
|------|--------------|
| `keyword` | BM25-Textsuche (Default) |
| `semantic` | Vektor-Ähnlichkeitssuche via Embeddings |
| `hybrid` | Kombination: 70% BM25 + 30% Vektor-Score |

> **Hinweis:** `semantic` und `hybrid` Modi erfordern `SEMANTIC_SEARCH_ENABLED=true` und konfigurierte Embedding-Provider.

**Response:**
```json
{
  "query_id": "q-12345",
  "results": [
    {
      "doc_id": "uuid-...",
      "title": "Lehrplan Mathematik Gymnasium Bayern",
      "url": "https://www.isb.bayern.de/...",
      "domain": "isb.bayern.de",
      "language": "de",
      "doc_type": "Lehrplan",
      "school_level": "Gymnasium",
      "subjects": ["Mathematik"],
      "scores": {
        "bm25": 12.5,
        "trust": 0.85,
        "quality": 0.9,
        "final": 10.6
      },
      "snippet": "Der Lehrplan für das Fach Mathematik...",
      "highlights": ["<em>Lehrplan</em> für das Fach <em>Mathematik</em>..."]
    }
  ],
  "pagination": {
    "limit": 10,
    "offset": 0,
    "total_estimate": 156
  }
}
```

### Filter-Optionen

| Filter | Werte |
|--------|-------|
| `language` | `de`, `en` |
| `doc_type` | `Lehrplan`, `Arbeitsblatt`, `Unterrichtsentwurf`, `Erlass_Verordnung`, `Pruefung_Abitur`, `Studie_Bericht`, `Sonstiges` |
| `school_level` | `Grundschule`, `Sek_I`, `Gymnasium`, `Berufsschule`, `Hochschule`, `Alle`, `NA` |
| `state` | `BW`, `BY`, `BE`, `BB`, `HB`, `HH`, `HE`, `MV`, `NI`, `NW`, `RP`, `SL`, `SN`, `ST`, `SH`, `TH` |
| `subjects` | `Mathematik`, `Deutsch`, `Englisch`, `Geschichte`, `Physik`, `Biologie`, `Chemie`, etc. |

## Konfiguration

### Umgebungsvariablen

| Variable | Beschreibung | Default |
|----------|--------------|---------|
| `PORT` | Server Port | `8084` |
| `OPENSEARCH_URL` | OpenSearch URL | `http://opensearch:9200` |
| `OPENSEARCH_USERNAME` | OpenSearch User | `admin` |
| `OPENSEARCH_PASSWORD` | OpenSearch Passwort | `admin` |
| `INDEX_NAME` | Index Name | `bp_documents_v1` |
| `USER_AGENT` | Crawler User Agent | `BreakpilotEduCrawler/1.0` |
| `RATE_LIMIT_PER_SEC` | Requests pro Sekunde/Domain | `0.2` |
| `MAX_DEPTH` | Max Crawl-Tiefe | `4` |
| `MAX_PAGES_PER_RUN` | Max Seiten pro Crawl | `500` |
| `SEEDS_DIR` | Seed-Dateien Verzeichnis | `./seeds` |
| `RULES_DIR` | Tagging-Regeln Verzeichnis | `./rules` |
| `EDU_SEARCH_API_KEY` | API Key für Auth | `` |
| `BACKEND_URL` | URL zum Python Backend | `http://backend:8000` |
| `SEEDS_FROM_API` | Seeds aus API laden | `true` |
| **Semantic Search** | | |
| `SEMANTIC_SEARCH_ENABLED` | Semantic Search aktivieren | `false` |
| `EMBEDDING_PROVIDER` | Provider: `openai`, `ollama`, `none` | `none` |
| `OPENAI_API_KEY` | API Key für OpenAI Embeddings | `` |
| `EMBEDDING_MODEL` | Embedding-Modell | `text-embedding-3-small` |
| `EMBEDDING_DIMENSION` | Vektor-Dimension | `1536` |
| `OLLAMA_URL` | Ollama Server URL | `http://ollama:11434` |
| **Scheduler** | | |
| `SCHEDULER_ENABLED` | Automatisches Crawling aktivieren | `false` |
| `SCHEDULER_INTERVAL` | Crawl-Intervall | `24h` (täglich) |

## Installation & Start

### Docker (empfohlen)

```bash
# Im edu-search-service Verzeichnis
docker compose up -d

# Logs anzeigen
docker compose logs -f edu-search

# Nur der Service (OpenSearch extern)
docker build -t edu-search-service .
docker run -p 8084:8084 \
  -e OPENSEARCH_URL=http://host.docker.internal:9200 \
  edu-search-service
```

### Lokal (Entwicklung)

```bash
# Dependencies installieren
go mod download

# Service starten
go run cmd/server/main.go

# Tests ausführen
go test -v ./...
```

## Seed-Kategorien

| Kategorie | Beschreibung | Beispiele |
|-----------|--------------|-----------|
| `federal` | Bundesweite Institutionen | KMK, BMBF, IQB |
| `states` | Landeskultusbehörden | Kultusministerien, Landesinstitute |
| `science` | Wissenschaftliche Studien | PISA, IGLU, TIMSS |
| `universities` | Hochschulen | Pädagogische Hochschulen |
| `schools` | Schulen direkt | Schulhomepages |
| `portals` | Bildungsportale | Lehrer-Online, 4teachers |
| `eu` | EU-Bildungsprogramme | Erasmus+, Eurydice |
| `authorities` | Schulbehörden | Regierungspräsidien |

## Tagging-Regeln

Die YAML-Regeldateien im `rules/` Verzeichnis definieren das Tagging:

- `doc_type_rules.yaml` - Dokumenttyp-Erkennung
- `subject_rules.yaml` - Fächer-Erkennung
- `level_rules.yaml` - Schulstufen-Erkennung
- `trust_rules.yaml` - Trust-Score-Berechnung

### Beispiel: doc_type_rules.yaml

```yaml
doc_types:
  Lehrplan:
    strong_terms:
      - Lehrplan
      - Kernlehrplan
      - Bildungsplan
    medium_terms:
      - Curriculum
      - Kompetenzerwartungen
    url_patterns:
      - /lehrplan
      - /kernlehrplan

priority_order:
  - Pruefung_Abitur
  - Lehrplan
  - Arbeitsblatt
```

## Projektstruktur

```
edu-search-service/
├── cmd/
│   └── server/
│       └── main.go          # Entry Point
├── internal/
│   ├── api/
│   │   └── handlers/
│   │       ├── handlers.go       # Search & Health Handler
│   │       └── admin_handlers.go # Admin API Handler
│   ├── config/
│   │   └── config.go        # Konfiguration
│   ├── crawler/
│   │   ├── crawler.go       # URL Fetcher
│   │   └── api_client.go    # Backend API Client (Seeds)
│   ├── robots/
│   │   └── robots.go        # robots.txt Parser & Checker
│   ├── embedding/
│   │   └── embedding.go     # Embedding Provider (OpenAI/Ollama)
│   ├── extractor/
│   │   └── extractor.go     # HTML/PDF Extraktion
│   ├── indexer/
│   │   └── mapping.go       # OpenSearch Indexer
│   ├── pipeline/
│   │   └── pipeline.go      # Crawl Orchestrierung
│   ├── quality/
│   │   └── quality.go       # Multi-Faktor Quality Scoring
│   ├── scheduler/
│   │   └── scheduler.go     # Automatisches Crawl-Scheduling
│   ├── search/
│   │   └── search.go        # Search Service (Keyword/Semantic/Hybrid)
│   └── tagger/
│       └── tagger.go        # Regelbasiertes Tagging
├── rules/
│   ├── doc_type_rules.yaml
│   ├── subject_rules.yaml
│   ├── level_rules.yaml
│   └── trust_rules.yaml
├── seeds/
│   ├── federal.txt
│   ├── states.txt
│   └── denylist.txt
├── Dockerfile
├── docker-compose.yml
├── go.mod
└── README.md
```

## Abhängigkeiten

| Package | Version | Beschreibung | Lizenz |
|---------|---------|--------------|--------|
| `github.com/gin-gonic/gin` | v1.9+ | HTTP Framework | MIT |
| `github.com/opensearch-project/opensearch-go/v2` | v2.3+ | OpenSearch Client | Apache-2.0 |
| `github.com/PuerkitoBio/goquery` | v1.8+ | HTML Parser | BSD-3-Clause |
| `github.com/ledongthuc/pdf` | v0.0.0-20240201 | PDF Text Extraktion | MIT |
| `gopkg.in/yaml.v3` | v3.0+ | YAML Parser | MIT |
| `github.com/google/uuid` | v1.4+ | UUID Generation | BSD-3-Clause |
| `golang.org/x/net` | v0.19+ | HTML Utilities | BSD-3-Clause |

## Tests ausführen

```bash
# Alle Tests
go test -v ./...

# Mit Coverage
go test -cover ./...

# Nur Tagger Tests
go test -v ./internal/tagger/...

# Nur Crawler Tests
go test -v ./internal/crawler/...
```

## Lizenz

Proprietär - BreakPilot GmbH

## Kontakt

- Security Issues: security@breakpilot.com
- Bugs: https://github.com/breakpilot/edu-search-service/issues
