File size: 10,636 Bytes
c9319a8 51f94af c9319a8 51f94af c9319a8 51f94af c9319a8 51f94af c9319a8 51f94af c9319a8 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 | # Documentation
## Abgabe 3: Conversational Agent – Apartment Price Prediction
---
## 1. Project Summary
Die App ermöglicht es Nutzern, ihren Wohnungswunsch auf Deutsch zu beschreiben und erhält eine Mietpreisschätzung für den Kanton Zürich. Ein LLM (Claude von Anthropic) extrahiert strukturierte Daten (Zimmer, Fläche, Ort) aus dem Freitext. Diese werden mit Gemeindedaten aus dem BFS-Datensatz ergänzt und an ein trainiertes Random-Forest-Regressionsmodell übergeben. Das Modell liefert die Preisschätzung; ein zweiter LLM-Schritt erklärt das Ergebnis in natürlicher Sprache auf Deutsch.
---
## 2. Files Used
| Datei | Zweck |
|-------|-------|
| `app.py` | Hauptanwendung: Pipeline, LLM-Calls, Gradio-Interface |
| `random_forest_regression.pkl` | Gespeichertes Regressionsmodell (aus GitHub-Repo) |
| `bfs_municipality_and_tax_data.csv` | Gemeindedaten Kanton Zürich (Bevölkerung, Steuern, etc.) |
| `requirements.txt` | Python-Abhängigkeiten |
| `documentation.md` | Diese Dokumentation |
| `README.md` | Hugging Face Space Konfiguration und Beschreibung |
---
## 3. Numeric Prediction Part
### 3.1 Reused Model
**Verwendetes Modell:** `random_forest_regression.pkl`
**Was das Modell vorhersagt:**
Die monatliche Bruttomiete (CHF) einer Wohnung im Kanton Zürich, basierend auf Wohnungsmerkmalen und Gemeindedaten.
**Input Features (exakt 7, in dieser Reihenfolge):**
1. `rooms` – Anzahl Zimmer
2. `area_m2` – Wohnfläche in m²
3. `pop` – Bevölkerung der Gemeinde
4. `pop_dens` – Bevölkerungsdichte (Einwohner/km²)
5. `frg_pct` – Ausländeranteil in Prozent
6. `emp` – Anzahl Beschäftigte in der Gemeinde
7. `tax_income` – Steuerbares Einkommen (Medianeinkommen der Gemeinde)
### 3.2 Prediction Logic
Der Ortsname aus der LLM-Extraktion wird mittels `match_town()` einem kanonischen BFS-Gemeindenamen zugeordnet (zunächst exakter Vergleich in Kleinbuchstaben, dann Teilstring-Matching). Die entsprechende Zeile aus `bfs_municipality_and_tax_data.csv` liefert die Gemeindedaten. Diese werden zusammen mit Zimmer und Fläche als DataFrame an `model.predict()` übergeben.
---
## 4. LLM Extraction Part
### 4.1 Goal
Das LLM soll aus einem deutschen Freitext drei strukturierte Werte extrahieren: die Anzahl Zimmer, die Wohnfläche in m² und den Gemeindenamen. Diese drei Werte werden benötigt, um die Modellvorhersage zu erstellen.
### 4.2 Prompt Design
Es wurde ein System-Prompt mit einer Developer-Instruction verwendet. Der Prompt:
- Definiert die Rolle explizit als «Datenextraktionssystem»
- Fordert strikte JSON-Ausgabe ohne Markdown und ohne Erklärung
- Nennt alle drei Pflichtfelder mit Typ und Beispiel
- Gibt an, `null` für fehlende Werte zu verwenden
```
Du bist ein Datenextraktionssystem für Schweizer Wohnungsanfragen.
Extrahiere aus dem Benutzertext genau drei Werte und gib sie als JSON zurück:
- "rooms": Anzahl Zimmer als Dezimalzahl (z.B. 3.5)
- "area_m2": Wohnfläche in m² als Zahl
- "town": Name der Gemeinde oder Stadt als String
Gib NUR gültiges JSON zurück, kein Markdown, keine Erklärung.
Beispiel: {"rooms": 3.5, "area_m2": 85, "town": "Winterthur"}
Falls ein Wert fehlt oder unklar ist, setze ihn auf null.
```
### 4.3 Expected Output Format
```json
{"rooms": 3.5, "area_m2": 85, "town": "Winterthur"}
```
### 4.4 Validation
Die Funktion `parse_json_response()` prüft:
1. Ob die Antwort nicht leer ist
2. Ob allfällige Markdown-Code-Blöcke (` ```json `) entfernt werden
3. Ob der String valides JSON ist (`json.loads`)
4. Ob alle drei Pflichtfelder vorhanden sind
Zusätzlich prüft `run_pipeline()`, ob alle drei Felder den Wert `null` haben und gibt eine benutzerfreundliche Fehlermeldung aus.
---
## 5. LLM Explanation Part
### 5.1 Goal
Nach der Preisberechnung soll das LLM eine kurze, verständliche Antwort auf Deutsch generieren. Das Modell erklärt den vorhergesagten Preis in Zusammenhang mit den Wohnungsmerkmalen – ohne einen eigenen neuen Preis zu berechnen.
### 5.2 Prompt Design
Der System-Prompt:
- Definiert die Rolle als «freundlicher Schweizer Wohnungsberater»
- Verlangt 2–3 Sätze auf Deutsch
- Fordert Erwähnung von Zimmer, Fläche, Ort und geschätztem Preis
- Verlangt einen Unsicherheitshinweis (Zustand, Mikrolage)
- Untersagt eine eigene Preisberechnung
- Fordert striktes JSON mit dem Schlüssel `"answer"`
### 5.3 Expected Output Format
```json
{"answer": "Für eine 3.5-Zimmer-Wohnung mit 85 m² in Winterthur schätzt das Modell eine monatliche Miete von rund CHF 2'450. Winterthur ist eine gut erschlossene Stadt mit moderaten Mietpreisen. Beachte, dass Faktoren wie Zustand der Wohnung und Mikrolage nicht im Modell enthalten sind und den tatsächlichen Preis beeinflussen können."}
```
---
## 6. End-to-End Pipeline
1. Nutzer gibt deutschen Wohnungswunsch ein (z.B. «Ich suche eine 3.5-Zimmer-Wohnung mit 85 m² in Winterthur.»)
2. `extract_preferences()`: LLM (Claude Haiku) extrahiert `rooms`, `area_m2`, `town` als JSON
3. `parse_json_response()` validiert das JSON und prüft alle Pflichtfelder
4. `run_pipeline()` prüft auf `null`-Werte und gibt Fehlermeldung bei fehlenden Angaben
5. `match_town()` ordnet den Ortsnamen einem kanonischen BFS-Gemeindenamen zu
6. `predict_apartment_price()` lädt Gemeindedaten und übergibt alle 7 Features an das Modell
7. `generate_explanation()`: LLM generiert eine deutsche Erklärung des Preises
8. Die App gibt drei Ausgaben zurück: extrahierte JSON-Parameter, numerische Mietschätzung, natürlichsprachliche Antwort
---
## 7. Test Cases
| Test Input | Extraktion korrekt? | Preis zurückgegeben? | Erklärung zurückgegeben? | Anmerkungen |
|------------|---------------------|----------------------|--------------------------|-------------|
| `ich suche eine 5 zimmer wohnung in uster mit mindestens 500 quadratmeter` | Ja – rooms: 5, area_m2: 500, town: "Uster" | Ja – CHF 3'757/Monat | Ja | Auch ohne Grossschreibung und formale Sätze werden alle Felder korrekt extrahiert |
| `ich suche eine 2 zimmer wohnung in uster mit mindestens 50 quadratmeter` | Ja – rooms: 2, area_m2: 50, town: "Uster" | Ja – CHF 1'578/Monat | Ja | Kleinere Wohnung, plausibel tieferer Preis |
| `2 zimmer 100 quadratmeter uster` | Ja – rooms: 2, area_m2: 100, town: "Uster" | Ja – CHF 2'461/Monat | Ja | Sehr kurze Eingabe ohne Satzstruktur, LLM extrahiert trotzdem korrekt |
| `Ich möchte eine Wohnung in Zürich.` | Teilweise – town: "Zürich", rooms: null, area_m2: null | Nein | Nein | Fehlermeldung: fehlende Angaben Zimmer und Fläche |
| `Ich suche etwas in Tokio.` | Ja – town: "Tokio" | Nein | Nein | match_town() findet keinen Match, klare Fehlermeldung ausgegeben |
---
## 8. Errors and Problems
**Problem 1: LLM gibt Markdown zurück statt reines JSON**
- **Ursache:** Manche Claude-Varianten umhüllen JSON mit ` ```json ... ``` `
- **Fix:** `parse_json_response()` erkennt Markdown-Blöcke und entfernt sie vor dem Parsen
**Problem 2: Ortsname aus LLM stimmt nicht exakt mit BFS-Namen überein**
- **Ursache:** Nutzer schreibt z.B. «Zürich» statt «Zürich (Kreis 1)» oder «Zuerich»
- **Fix:** `match_town()` versucht zunächst exakten Lowercase-Match, dann Teilstring-Matching
**Problem 3: Fehlende API-Keys im Deployment**
- **Ursache:** Hugging Face Secrets nicht gesetzt
- **Fix:** `call_llm_json()` wirft expliziten `ValueError` mit Hinweis auf fehlende Env-Variable; die App zeigt eine verständliche Fehlermeldung
**Problem 4: Modell-Feature-Namen müssen exakt übereinstimmen**
- **Ursache:** Das gespeicherte Modell erwartet Spalten `area_m2`, nicht `area` wie im Projekt 1
- **Fix:** Features in `predict_apartment_price()` exakt nach Vorgabe des Modells benannt (`rooms, area_m2, pop, pop_dens, frg_pct, emp, tax_income`)
---
## 9. Deployment Notes
### 9.1 Files included
- `app.py`
- `random_forest_regression.pkl`
- `bfs_municipality_and_tax_data.csv`
- `requirements.txt`
- `README.md`
- `documentation.md`
### 9.2 Secrets / Environment Variables
- `OPENAI_API_KEY` – Pflicht: OpenAI API Key
- `OPENAI_MODEL` – Optional: Modell-ID (Standard: `gpt-4o-mini`)
### 9.3 Deployment Result
Der Space läuft erfolgreich auf Hugging Face. Die Gradio-App startet korrekt, das Modell und die CSV werden beim Start geladen. Alle drei Ausgaben (extrahierte Parameter, Preis, Erklärung) werden korrekt angezeigt.
### 9.4 Screenshots
**Beispiel 1:** Grosse Wohnung mit informeller Eingabe

Eingabe: «ich suche eine 5 zimmer wohnung in uster mit mindestens 500 quadratmeter» – Das LLM extrahiert korrekt `rooms: 5`, `area_m2: 500`, `town: "Uster"`, obwohl die Eingabe keine formale Satzstruktur hat. Das Modell schätzt CHF 3'757/Monat; die Erklärung weist auf Unsicherheiten durch Zustand und Mikrolage hin.
**Beispiel 2:** Kleine Wohnung, gleicher Ort

Eingabe: «ich suche eine 2 zimmer wohnung in uster mit mindestens 50 quadratmeter» – Alle drei Felder werden korrekt erkannt (`rooms: 2`, `area_m2: 50`, `town: "Uster"`). Der deutlich tiefere Preis von CHF 1'578/Monat im Vergleich zu Beispiel 1 ist plausibel und zeigt, dass das Modell auf Fläche und Zimmerzahl reagiert.
---
## 10. Reflection
Die Kombination aus Regressionsmodell und LLM funktioniert gut für strukturierte Vorhersagen mit natürlichsprachiger Ein- und Ausgabe. Der stärkste Vorteil ist die Flexibilität: Nutzer müssen keine Formulare ausfüllen, sondern können sich frei ausdrücken. Die fragile Stelle ist die Ortsnamenerkennung – das Modell kennt nur Gemeinden im Kanton Zürich, und der LLM extrahiert möglicherweise Ortsnamen aus anderen Kantonen oder Ländern. Die deutsche Eingabe ist wichtig, da das Modell auf deutschsprachigen Schweizer Kontext ausgerichtet ist. Fehlende Informationen wie Etage, Baujahr, Ausstattung oder genaue Strassenlage beeinflussen den Preis stark, sind aber nicht im Modell enthalten. Als nächste Verbesserung würde sich ein Fuzzy-Matching für Ortsnamen sowie eine Validierung der Wertebereiche (z.B. Fläche > 0) eignen.
---
## 11. Responsible Use Note
Die Preisschätzung ist ein statistischer Schätzwert auf Basis historischer Daten und dient nur zur Orientierung – sie ersetzt keine professionelle Immobilienberatung. Das Modell kennt keine aktuellen Marktentwicklungen, den Zustand der Wohnung, die Etage oder die genaue Mikrolage. Das LLM kann Angaben aus dem Text falsch interpretieren, insbesondere bei unklaren oder mehrdeutigen Formulierungen. Alle ausgegebenen Preise sollten daher kritisch hinterfragt und mit aktuellen Inseraten verglichen werden.
|