Spaces:
Sleeping
Sleeping
| import json | |
| import gradio as gr | |
| with open("model_data.json") as f: | |
| DATA = json.load(f) | |
| MUNICIPALITIES = sorted(DATA["municipalities"]) | |
| def get_base(muni, area): | |
| lk = DATA["base_lookup"].get(muni, {}) | |
| if not lk: | |
| return 2000 | |
| keys = sorted(int(k) for k in lk) | |
| lo = max((k for k in keys if k <= area), default=keys[0]) | |
| hi = min((k for k in keys if k >= area), default=keys[-1]) | |
| if lo == hi: | |
| return lk[str(lo)] | |
| t = (area - lo) / (hi - lo) | |
| return lk[str(lo)] + t * (lk[str(hi)] - lk[str(lo)]) | |
| def get_rooms_delta(rooms): | |
| rooms = float(rooms) | |
| rd = DATA["rooms_deltas"] | |
| key = str(int(rooms)) if rooms == int(rooms) else str(rooms) | |
| if key in rd: | |
| return rd[key] | |
| keys = sorted(float(k) for k in rd) | |
| lo = max((k for k in keys if k <= rooms), default=keys[0]) | |
| hi = min((k for k in keys if k >= rooms), default=keys[-1]) | |
| lo_key = str(int(lo)) if lo == int(lo) else str(lo) | |
| hi_key = str(int(hi)) if hi == int(hi) else str(hi) | |
| if lo == hi: | |
| return rd[lo_key] | |
| t = (rooms - lo) / (hi - lo) | |
| return rd[lo_key] + t * (rd[hi_key] - rd[lo_key]) | |
| def predict(municipality, area, rooms, luxury, lake_view, furnished, temporary): | |
| area = float(area) | |
| rooms = float(rooms) | |
| p = get_base(municipality, area) | |
| base_rooms = min(6.0, max(1.0, round(area / 25) * 0.5)) | |
| p += get_rooms_delta(rooms) - get_rooms_delta(base_rooms) | |
| if luxury: | |
| p += DATA["lux_delta"] | |
| if lake_view: | |
| p += DATA["sea_delta"] | |
| if furnished: | |
| p += DATA["fur_delta"] | |
| if temporary: | |
| p -= 200 | |
| p = max(500, min(12000, round(p / 10) * 10)) | |
| info = DATA["muni_info"].get(municipality, {}) | |
| tax = info.get("tax_income", 0) | |
| dens = info.get("pop_dens", 0) | |
| tags = [t for t, f in [("Luxury", luxury), ("Lake view", lake_view), | |
| ("Furnished", furnished), ("Temporary", temporary)] if f] | |
| result = f"""## CHF {p:,.0f} / month | |
| **Typical error: ± CHF 446** (model MAE, 5-fold CV) | |
| --- | |
| ### Input summary | |
| | Feature | Value | | |
| |---|---| | |
| | Municipality | {municipality} | | |
| | Living area | {area:.0f} m² | | |
| | Rooms | {rooms} | | |
| | m² per room | {area/rooms:.1f} | | |
| | Listing tags | {", ".join(tags) if tags else "None"} | | |
| ### Municipal context (BFS data) | |
| | | | | |
| |---|---| | |
| | Tax income | CHF {tax:,.0f} | | |
| | Population density | {dens:,.0f} /km² | | |
| --- | |
| ### Feature importances | |
| | Feature | Importance | | |
| |---|---| | |
| | Living area | 47.9% | | |
| | Municipality | 17.6% | | |
| | Population density | 10.9% | | |
| | m² per room | 8.7% | | |
| | Tax income | 4.3% | | |
| | NLP listing tags ★ | ~1.5% | | |
| *Gradient Boosting · R² 0.664 · trained on 2,400 real Zürich listings* | |
| """ | |
| market = sorted(DATA["market"].items(), key=lambda x: -x[1]) | |
| rows = list(market[:5]) | |
| sel = next((x for x in market if x[0] == municipality), None) | |
| if sel and sel not in rows: | |
| rows.append(sel) | |
| rows.sort(key=lambda x: -x[1]) | |
| cmp = "### Market comparison (3 rooms · 75 m²)\n\n" | |
| cmp += "| Municipality | CHF/month |\n|---|---|\n" | |
| for name, price in rows: | |
| marker = " ◀" if name == municipality else "" | |
| cmp += f"| **{name}**{marker} | {price:,} |\n" | |
| return result + "\n" + cmp | |
| with gr.Blocks(title="Zürich Rent Predictor") as demo: | |
| gr.Markdown("# Zürich Apartment Rent Predictor") | |
| gr.Markdown( | |
| "Gradient Boosting model trained on 2,400 real Canton of Zürich listings " | |
| "enriched with BFS municipal data. " | |
| "**New feature:** NLP listing tags extracted from German descriptions " | |
| "(LUXURIÖS, SEESICHT, MÖBLIERT)." | |
| ) | |
| with gr.Row(): | |
| with gr.Column(): | |
| municipality = gr.Dropdown( | |
| choices=MUNICIPALITIES, | |
| value="Zürich", | |
| label="Municipality" | |
| ) | |
| area = gr.Slider( | |
| minimum=15, maximum=300, step=5, value=75, | |
| label="Living Area (m²)" | |
| ) | |
| rooms = gr.Slider( | |
| minimum=1, maximum=6, step=0.5, value=3, | |
| label="Number of Rooms" | |
| ) | |
| gr.Markdown("**Listing tags** *(new feature — extracted from listing descriptions)*") | |
| with gr.Row(): | |
| luxury = gr.Checkbox(label="💎 Luxurious") | |
| lake_view = gr.Checkbox(label="🌊 Lake View") | |
| with gr.Row(): | |
| furnished = gr.Checkbox(label="🛋️ Furnished") | |
| temporary = gr.Checkbox(label="📅 Temporary") | |
| btn = gr.Button("Predict Rent", variant="primary") | |
| with gr.Column(): | |
| output = gr.Markdown(value="*Fill in the details and click Predict Rent.*") | |
| inputs = [municipality, area, rooms, luxury, lake_view, furnished, temporary] | |
| btn.click(fn=predict, inputs=inputs, outputs=output) | |
| for inp in inputs: | |
| if hasattr(inp, "change"): | |
| inp.change(fn=predict, inputs=inputs, outputs=output) | |
| gr.Markdown("---\nZHAW AI Applications · Canton of Zürich Rental Market · 2025") | |
| if __name__ == "__main__": | |
| demo.launch(theme=gr.themes.Default()) | |