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())