Skip to main content
Glama
README.md
# Job Radar

Sammelt Stellenanzeigen aus mehreren Quellen, filtert sie mit nachvollziehbaren Regeln, speichert sie in
SQLite mit Volltextsuche und macht sie über eine REST-API, einen **MCP-Server** für LLM-Agenten und
**RAG-Fragen** nutzbar.

Der Job-Radar läuft seit September 2026 produktiv als n8n-Workflow: zweimal täglich, mit LLM-Auswertung
jeder Anzeige und Zustellung als Telegram-Karte. Dieses Repository ist sein Kern als Python-Service –
dieselben Regeln, aber getestet, mit SQL-Analysen, API, MCP, Docker und CI.

```mermaid
flowchart LR
    A[Adzuna API] --> R
    B[Arbeitnow API] --> R
    C[JSearch · RapidAPI] --> R
    R[Regeln<br/>classify.py] --> S[(SQLite + FTS5)]
    S --> API[REST-API<br/>FastAPI]
    S --> MCP[MCP-Server<br/>für LLM-Agenten]
    S --> RAG[RAG<br/>Retrieval + LLM]
    S --> SQL[SQL-Analysen]
```

## Was drin ist

| Modul | Aufgabe |
|---|---|
| `sources.py` | Adzuna, Arbeitnow, JSearch. Fällt eine Quelle aus, laufen die anderen weiter. |
| `classify.py` | Regeln: vor Ort nur Wien, sonst nur voll remote (hybrid zählt nicht); gesuchte AI-/LLM-Rollen (nur im Titel), Seniorität, Wochenstunden (Limit 20 h für Studierende in Österreich), Gehalt brutto/netto mit 14 Gehältern, Hinweise zu Sprache und Arbeitserlaubnis. |
| `store.py` | SQLite, Deduplizierung über Quellen hinweg (gleicher Titel + Firma), FTS5-Index mit BM25-Ranking, Statusverlauf. |
| `analytics.py` | SQL: Median-Gehalt per Window Functions, Skill-Nachfrage, Top-Firmen, Bewerbungs-Funnel per JOIN. |
| `api.py` | FastAPI: `/jobs`, `/jobs/{id}`, `/search`, `/stats`, `/ask`, Status setzen. |
| `mcp_server.py` | MCP-Tools `search_jobs`, `get_job`, `job_stats`, `mark_job` – etwa für Claude. |
| `rag.py` | Retrieval über FTS5, Antwort nur aus den gefundenen Anzeigen mit Quellen `[id]`; strukturierte Extraktion als JSON. |

## Schnellstart

```bash
python -m venv .venv
.venv\Scripts\activate            # Windows; unter Linux/macOS: source .venv/bin/activate
pip install -e ".[dev]"

jobradar import data/sample_jobs.json   # fiktive Beispieldaten, keine API-Schlüssel nötig
jobradar list
jobradar search "RAG Python"
jobradar stats
jobradar serve                          # http://localhost:8000/docs
```

Echte Daten: Schlüssel als Umgebungsvariablen setzen (siehe `.env.example`) und `jobradar fetch` ausführen.
Fragen an die eigene Datenbank: mit `GEMINI_API_KEY` z. B.
`jobradar ask "Welche Teilzeitstellen mit Python gibt es in Wien?"`

### Als MCP-Server in Claude Code

```bash
claude mcp add job-radar -- jobradar --db C:/pfad/zu/jobradar.db mcp
```

Danach kann Claude selbst suchen („Welche AI-Jobs in Wien sind neu?“), Statistiken abrufen und Stellen als
`applied` markieren.

### Docker

```bash
docker build -t job-radar .
docker run -p 8000:8000 -v jobradar-data:/data job-radar
```

## Tests

```bash
pytest          # Regeln, Speicherung, SQL-Analysen, Quellen (gemockt), API, RAG, MCP
ruff check .
```

Die CI (GitHub Actions) führt Linting und Tests bei jedem Push aus und baut das Docker-Image.

## Designentscheidungen

- **Regeln statt LLM beim Filtern.** Günstig, schnell und jede Entscheidung ist erklärbar. Das LLM kommt erst
  bei der Auswertung der wenigen relevanten Anzeigen zum Einsatz.
- **FTS5 mit BM25 statt Vektordatenbank.** Für einige tausend Anzeigen reicht Volltextsuche, ohne zusätzliche
  Infrastruktur. Embeddings wären der nächste Schritt, sobald semantische Suche gebraucht wird.
- **LLM als austauschbare Funktion.** `prompt -> text`: Tests laufen ohne API-Schlüssel, der Anbieter lässt
  sich wechseln.
- **Fiktive Beispieldaten.** Im Repository liegen keine fremden Anzeigentexte.

## Struktur

```
src/jobradar/   sources · classify · store · analytics · pipeline · rag · api · mcp_server · cli
tests/          pytest, eine Datei pro Modul
data/           sample_jobs.json (fiktiv); die Datenbank selbst wird nicht eingecheckt
```