# 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 ![Beispiel 1](screenshot1.png) 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 ![Beispiel 2](screenshot2.png) 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.