job-radar
by borissmaznov
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
```
This server cannot be deployed
Maintenance
ActivityMaintained
ResponsivenessNo issues