Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"codespaces": {
"openFiles": [
"README.md",
"stellenscout/app.py"
"immermatch/app.py"
]
},
"vscode": {
Expand All @@ -19,7 +19,7 @@
},
"updateContentCommand": "[ -f packages.txt ] && sudo apt update && sudo apt upgrade -y && sudo xargs apt install -y <packages.txt; [ -f requirements.txt ] && pip3 install --user -r requirements.txt; pip3 install --user streamlit; echo '✅ Packages installed and Requirements met'",
"postAttachCommand": {
"server": "streamlit run stellenscout/app.py --server.enableCORS false --server.enableXsrfProtection false"
"server": "streamlit run immermatch/app.py --server.enableCORS false --server.enableXsrfProtection false"
},
"portsAttributes": {
"8501": {
Expand Down
4 changes: 2 additions & 2 deletions .env.example
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# StellenScout Environment Variables
# Immermatch Environment Variables
# Copy this file to .env and fill in your API keys

# Google AI Studio API Key (required)
Expand All @@ -18,7 +18,7 @@ SUPABASE_SERVICE_KEY=your_supabase_service_role_key_here
# Resend (required for daily digest emails)
# Get yours at: https://resend.com/
RESEND_API_KEY=re_your_api_key_here
RESEND_FROM=StellenScout <digest@yourdomain.com>
RESEND_FROM=Immermatch <digest@yourdomain.com>

# App URL (required for email verification and unsubscribe links)
APP_URL=https://your-app.streamlit.app
Expand Down
2 changes: 1 addition & 1 deletion .github/ISSUE_TEMPLATE/bug_report.yml
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ body:
id: environment
attributes:
label: Environment
description: How are you running StellenScout?
description: How are you running Immermatch?
options:
- Self-hosted (local)
- Self-hosted (server/VPS)
Expand Down
2 changes: 1 addition & 1 deletion .github/ISSUE_TEMPLATE/config.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
blank_issues_enabled: false
contact_links:
- name: Questions & Discussions
url: https://github.com/TheTrueAI/stellenscout/issues
url: https://github.com/TheTrueAI/immermatch/issues
about: For general questions, please open an issue using the Question template.
2 changes: 1 addition & 1 deletion .github/ISSUE_TEMPLATE/feature_request.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ body:
- type: markdown
attributes:
value: |
Have an idea to make StellenScout better? We'd love to hear it!
Have an idea to make Immermatch better? We'd love to hear it!

- type: textarea
id: problem
Expand Down
2 changes: 1 addition & 1 deletion .github/ISSUE_TEMPLATE/question.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ body:
- type: markdown
attributes:
value: |
Have a question? Please check the [README](https://github.com/TheTrueAI/stellenscout#readme) and [AGENTS.md](https://github.com/TheTrueAI/stellenscout/blob/main/AGENTS.md) first.
Have a question? Please check the [README](https://github.com/TheTrueAI/immermatch#readme) and [AGENTS.md](https://github.com/TheTrueAI/immermatch/blob/main/AGENTS.md) first.

- type: textarea
id: question
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ jobs:
cache: pip
- run: pip install -e ".[test]"
- name: Type check (mypy)
run: mypy stellenscout/ daily_task.py
run: mypy immermatch/ daily_task.py

test:
runs-on: ubuntu-latest
Expand All @@ -46,7 +46,7 @@ jobs:
cache: pip
- run: pip install -e ".[test]"
- name: Tests
run: pytest -v --cov=stellenscout --cov-report=term
run: pytest -v --cov=immermatch --cov-report=term

audit:
runs-on: ubuntu-latest
Expand Down
4 changes: 2 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ wheels/
*.egg-info/
.installed.cfg
*.egg
stellenscout.egg-info/
immermatch.egg-info/

# IDE
.idea/
Expand All @@ -42,7 +42,7 @@ Thumbs.db
*.pdf
output/
*cv*.md
.stellenscout_cache/
.immermatch_cache/

# Streamlit secrets (contains API keys)
.streamlit/secrets.toml
Expand Down
20 changes: 10 additions & 10 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Agent Architectures

This document defines the persona, context, and instruction sets for the AI agents used in StellenScout.
This document defines the persona, context, and instruction sets for the AI agents used in Immermatch.

**LLM Provider:** Google AI Studio (Gemini)
**Model:** gemini-3-flash-preview
Expand Down Expand Up @@ -278,10 +278,10 @@ class EvaluatedJob(BaseModel):

## 7. Caching (`cache.py`)

All pipeline results are cached to JSON in `.stellenscout_cache/` to minimize API usage across runs.
All pipeline results are cached to JSON in `.immermatch_cache/` to minimize API usage across runs.

```
.stellenscout_cache/
.immermatch_cache/
├── profile.json # keyed by CV hash (SHA-256)
├── queries.json # keyed by profile hash + location
├── jobs.json # date-stamped, merged with existing jobs
Expand All @@ -304,7 +304,7 @@ Three-phase UI flow:
- **Phase C (Results):** Filterable/sortable job cards, career summary, newsletter subscription

Features:
- Session-scoped cache directories under `.stellenscout_cache/<cv_file_hash>/`
- Session-scoped cache directories under `.immermatch_cache/<cv_file_hash>/`
- Auto-cleanup of session caches older than 24 hours (max 50 sessions)
- Sidebar: status panel, min score slider, secondary CV uploader, legal links
- GDPR consent checkbox required before CV upload (consent text versioned as `_CONSENT_TEXT_VERSION`)
Expand Down Expand Up @@ -492,7 +492,7 @@ Schema setup: run `python setup_db.py` to check tables and print migration SQL.
- Shared fixtures in `tests/conftest.py`: `sample_profile`, `sample_job`, `sample_evaluation`, `sample_evaluated_job`
- Test fixtures (text files) live in `tests/fixtures/`
- Run: `pytest tests/ -v`
- Coverage: `pytest tests/ --cov=stellenscout --cov-report=term-missing`
- Coverage: `pytest tests/ --cov=immermatch --cov-report=term-missing`

---

Expand All @@ -502,11 +502,11 @@ Schema setup: run `python setup_db.py` to check tables and print migration SQL.

**Model:** Open Source + Hosted Paid Service (open-core)

The source code is publicly available on GitHub under AGPL-3.0. The AGPL requires that anyone who hosts a modified version of StellenScout must also release their source code — this protects against competitors forking the project and running a closed-source competing service.
The source code is publicly available on GitHub under AGPL-3.0. The AGPL requires that anyone who hosts a modified version of Immermatch must also release their source code — this protects against competitors forking the project and running a closed-source competing service.

### What users pay for

StellenScout is **free to self-host** (bring your own API keys). The official hosted version at the project domain charges a subscription fee for:
Immermatch is **free to self-host** (bring your own API keys). The official hosted version at the project domain charges a subscription fee for:
- Managed hosting (no API key setup required)
- SerpAPI & Gemini API quota included
- Daily digest email infrastructure
Expand Down Expand Up @@ -540,7 +540,7 @@ source .venv/bin/activate
# Test: pytest tests/ -x -q
# Lint: ruff check . && ruff format --check .
# Types: mypy .
# Run app: streamlit run stellenscout/app.py
# Run app: streamlit run immermatch/app.py
# All: ruff check . && mypy . && pytest tests/ -x -q
```

Expand All @@ -549,14 +549,14 @@ source .venv/bin/activate
- **Always activate the virtual environment** (`source .venv/bin/activate`) before running any command (`pytest`, `ruff`, `mypy`, `streamlit`, etc.). The project's dependencies are installed only in `.venv`.
- Use `google-genai` package, NOT the deprecated `google.generativeai`
- Gemini model: `gemini-3-flash-preview`
- Pydantic models live in `stellenscout/models.py` — follow existing patterns
- Pydantic models live in `immermatch/models.py` — follow existing patterns
- All external services (Gemini, SerpAPI, Supabase, Resend) must be mocked in tests — no API keys needed to run `pytest`
- Shared test fixtures in `tests/conftest.py`: `sample_profile`, `sample_job`, `sample_evaluation`, `sample_evaluated_job`
- Test fixture files (sample CVs, etc.) live in `tests/fixtures/`
- All DB writes use the admin client (`get_admin_client()`), never the anon client
- Log subscriber UUIDs, never email addresses
- All `st.error()` calls must show generic messages; real exceptions go to `logger.exception()`
- Follow the test file naming convention: `tests/test_<module>.py` for `stellenscout/<module>.py`
- Follow the test file naming convention: `tests/test_<module>.py` for `immermatch/<module>.py`
- After implementing changes, always run `pytest tests/ -x -q` to verify nothing is broken

### Development workflow
Expand Down
14 changes: 7 additions & 7 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Contributing to StellenScout
# Contributing to Immermatch

Thanks for your interest in contributing! This guide covers setup, conventions, and the PR process.

Expand All @@ -12,8 +12,8 @@ Thanks for your interest in contributing! This guide covers setup, conventions,
### Installation

```bash
git clone https://github.com/TheTrueAI/stellenscout.git
cd stellenscout
git clone https://github.com/TheTrueAI/immermatch.git
cd immermatch
python -m venv .venv
source .venv/bin/activate
pip install -e ".[dev,test]"
Expand All @@ -32,7 +32,7 @@ No API keys are needed for running tests — all external services are mocked.
2. Make your changes and run quality checks:
```bash
ruff check . && ruff format --check .
mypy stellenscout/ daily_task.py
mypy immermatch/ daily_task.py
pytest tests/ -x -q
```

Expand All @@ -59,8 +59,8 @@ No API keys are needed for running tests — all external services are mocked.
- **All external services must be mocked** — Gemini, SerpApi, Supabase, and Resend should never be called in tests
- **Shared fixtures** are in `tests/conftest.py`: `sample_profile`, `sample_job`, `sample_evaluation`, `sample_evaluated_job`
- **Test fixture files** (sample CVs, etc.) go in `tests/fixtures/`
- **File naming:** `tests/test_<module>.py` for `stellenscout/<module>.py`
- **Pydantic models** live in `stellenscout/models.py` — follow existing patterns
- **File naming:** `tests/test_<module>.py` for `immermatch/<module>.py`
- **Pydantic models** live in `immermatch/models.py` — follow existing patterns

## Project Conventions

Expand All @@ -78,7 +78,7 @@ No API keys are needed for running tests — all external services are mocked.

## Reporting Issues

- Search [existing issues](https://github.com/TheTrueAI/stellenscout/issues) first
- Search [existing issues](https://github.com/TheTrueAI/immermatch/issues) first
- Use the provided issue templates (bug report, feature request, question)
- For bugs: include steps to reproduce, expected vs actual behavior, and your environment (Python version, OS)

Expand Down
24 changes: 12 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
# StellenScout
# Immermatch

[![CI](https://github.com/TheTrueAI/stellenscout/actions/workflows/ci.yml/badge.svg)](https://github.com/TheTrueAI/stellenscout/actions/workflows/ci.yml)
[![CI](https://github.com/TheTrueAI/immermatch/actions/workflows/ci.yml/badge.svg)](https://github.com/TheTrueAI/immermatch/actions/workflows/ci.yml)
[![License: AGPL-3.0](https://img.shields.io/badge/License-AGPL--3.0-blue.svg)](LICENSE)
[![Python 3.10+](https://img.shields.io/badge/Python-3.10%2B-green.svg)](https://www.python.org/)

AI-powered job matching for the European market. Upload your CV, and the app uses Google Gemini to analyze your profile, searches for relevant jobs via Google Jobs, and scores each listing against your skills and experience.

---
![Screenshot of the StellenScout web interface showing AI-powered job matching results](https://github.com/user-attachments/assets/e450bfb6-6aa0-4c24-aa53-be8695146b03)
![Screenshot of the Immermatch web interface showing AI-powered job matching results](https://github.com/user-attachments/assets/e450bfb6-6aa0-4c24-aa53-be8695146b03)

---
## Features
Expand All @@ -32,8 +32,8 @@ AI-powered job matching for the European market. Upload your CV, and the app use
### Setup

```bash
git clone https://github.com/TheTrueAI/stellenscout.git
cd stellenscout
git clone https://github.com/TheTrueAI/immermatch.git
cd immermatch
python -m venv .venv
source .venv/bin/activate
pip install -e .
Expand All @@ -49,7 +49,7 @@ cp .env.example .env
### Run the App

```bash
streamlit run stellenscout/app.py
streamlit run immermatch/app.py
```

## How It Works
Expand All @@ -75,7 +75,7 @@ Copy `.env.example` to `.env` and fill in your keys. The app also supports `.str
| `SUPABASE_KEY` | For newsletter | Supabase anon/publishable key |
| `SUPABASE_SERVICE_KEY` | For newsletter | Supabase service-role key (bypasses RLS) |
| `RESEND_API_KEY` | For newsletter | Resend API key ([get one](https://resend.com/)) |
| `RESEND_FROM` | For newsletter | Sender address, e.g. `StellenScout <digest@yourdomain.com>` |
| `RESEND_FROM` | For newsletter | Sender address, e.g. `Immermatch <digest@yourdomain.com>` |
| `APP_URL` | For newsletter | Public app URL for email verification links |
| `IMPRESSUM_NAME` | For newsletter | Legal notice: your full name (§ 5 DDG) |
| `IMPRESSUM_ADDRESS` | For newsletter | Legal notice: your postal address |
Expand Down Expand Up @@ -117,7 +117,7 @@ See `AGENTS.md` §10 for the full email lifecycle (double opt-in, auto-expiry, u

```bash
pip install -e ".[test]"
pytest tests/ -v --cov=stellenscout --cov-report=term-missing
pytest tests/ -v --cov=immermatch --cov-report=term-missing
```

All external services (Gemini, SerpApi, Supabase, Resend) are mocked — no API keys needed to run the test suite.
Expand All @@ -126,7 +126,7 @@ Linting and type checking:

```bash
ruff check . && ruff format --check .
mypy stellenscout/ daily_task.py
mypy immermatch/ daily_task.py
```

Pre-commit hooks are available for automatic quality gates:
Expand All @@ -139,7 +139,7 @@ pre-commit install --hook-type pre-commit --hook-type pre-push
## Project Structure

```
stellenscout/
immermatch/
app.py # Streamlit web UI
llm.py # Gemini client and retry logic
cv_parser.py # CV text extraction (PDF/DOCX/MD/TXT)
Expand All @@ -161,7 +161,7 @@ tests/ # tests (all mocked)

## Privacy & Data Handling

StellenScout is designed with GDPR compliance in mind:
Immermatch is designed with GDPR compliance in mind:

- **Session-scoped caching** — CV data is cached locally per session and auto-cleaned after 24 hours
- **Double opt-in** — Newsletter subscriptions require email verification
Expand All @@ -178,4 +178,4 @@ See [CONTRIBUTING.md](CONTRIBUTING.md) for development setup, coding conventions

## License

[AGPL-3.0](LICENSE) — You're free to use, modify, and self-host StellenScout. If you host a modified version, you must release your changes under the same license.
[AGPL-3.0](LICENSE) — You're free to use, modify, and self-host Immermatch. If you host a modified version, you must release your changes under the same license.
6 changes: 3 additions & 3 deletions ROADMAP.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# StellenScout — Next Steps Roadmap
# Immermatch — Next Steps Roadmap

Based on the current state (private repo, hosted on Streamlit Community Cloud) and the goals outlined in AGENTS.md, here's a prioritized plan:

Expand Down Expand Up @@ -41,7 +41,7 @@ Based on the current state (private repo, hosted on Streamlit Community Cloud) a
- [x] **Test the full digest cycle** — subscribe, verify, receive digest, unsubscribe
- [x] **Create a welcome email after successful subscription** — explain what to expect, how to contact support, link to privacy policy, show some example matches
- [x] **Add unsubscribe link to digest emails** — include unique tokenized URL to securely identify subscriber without exposing email
- [x] **Make digest email prettier** — use HTML formatting, add Stellenscout logo, style job listings for better readability
- [x] **Make digest email prettier** — use HTML formatting, add Immermatch logo, style job listings for better readability

### 1.3 — UX Quick Wins
- [ ] **Personalize the UI** — greet user by first name extracted from CV profile
Expand Down Expand Up @@ -75,7 +75,7 @@ Based on the current state (private repo, hosted on Streamlit Community Cloud) a

### 2.3 — Migrate Off Streamlit Community Cloud
- [ ] **Move to a VPS or PaaS** — Railway, Fly.io, or Hetzner (Streamlit Cloud has no custom domain, no Stripe webhooks, limited control)
- [ ] **Custom domain** — e.g., `stellenscout.de` or `stellenscout.eu`
- [ ] **Custom domain** — e.g., `immermatch.de` or `immermatch.eu`
- [ ] **Reverse proxy** — Caddy or nginx with automatic HTTPS
- [ ] **Docker Compose setup** — `app`, `daily-task` (cron), `postgres` (or keep Supabase)

Expand Down
16 changes: 8 additions & 8 deletions daily_task.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
#!/usr/bin/env python3
"""StellenScout daily digest — designed to run in GitHub Actions.
"""Immermatch daily digest — designed to run in GitHub Actions.

Per-subscriber pipeline:
1. Expire subscriptions past their 30-day window.
Expand Down Expand Up @@ -28,7 +28,7 @@

load_dotenv()

from stellenscout.db import (
from immermatch.db import (
expire_subscriptions,
get_active_subscribers_with_profiles,
get_job_ids_by_urls,
Expand All @@ -38,14 +38,14 @@
purge_inactive_subscribers,
upsert_jobs,
)
from stellenscout.db import (
from immermatch.db import (
get_admin_client as get_db,
)
from stellenscout.emailer import send_daily_digest
from stellenscout.evaluator_agent import evaluate_all_jobs
from stellenscout.llm import create_client
from stellenscout.models import CandidateProfile, EvaluatedJob, JobListing
from stellenscout.search_agent import search_all_queries
from immermatch.emailer import send_daily_digest
from immermatch.evaluator_agent import evaluate_all_jobs
from immermatch.llm import create_client
from immermatch.models import CandidateProfile, EvaluatedJob, JobListing
from immermatch.search_agent import search_all_queries

logging.basicConfig(
level=logging.INFO,
Expand Down
3 changes: 3 additions & 0 deletions immermatch/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
"""Immermatch: AI-powered job matching for the European market."""

__version__ = "0.1.0"
Loading