لوحة استثمار لقائمة مختصرة
متقدم
شغّل التقديرات مع مدخلات التمويل لعدة عقارات، واستخرج ملف CSV ولوحة HTML من صفحة واحدة.
الإعداد
يستخدم هذا الدرس الأداة المساعدة jokerprice.py من درس البدء السريع. احفظها بجوار نصك البرمجي واضبط JOKERPRICE_API_KEY.
اكتب قائمة العقارات المرشحة
أنشئ الملف candidates.csv. الأسعار بالريال السعودي.
name,latitude,longitude,bedrooms,type_id,purchase_price
Al Malqa apartment,24.8094,46.6112,2,6,950000
North Jeddah apartment,21.5675,39.1293,3,6,1100000
Al Khobar villa,26.2794,50.2083,4,7,2400000قدّر كل عقار مع التمويل
مرّر كائن investment مع التقدير. تُكتب النسب أعدادًا صحيحة، وتأخذ الحقول المحذوفة القيم الافتراضية: دفعة أولى 25٪، وفائدة 6٪، ومدة 25 عامًا، ورسوم منصة 15٪، وإقامات من ليلتين.
import csv
from jokerprice import api
FINANCE = {"downPaymentPct": 30, "loanRatePct": 6.5, "loanTermYears": 25, "platformFeePct": 15,
"cleaningPerStay": 120, "avgStayNights": 2, "monthlyFixedCosts": 900, "setupCost": 60000}
results = []
for row in csv.DictReader(open("candidates.csv")):
est = api("POST", "/api/v1/estimate", json={
"latitude": float(row["latitude"]), "longitude": float(row["longitude"]),
"bedrooms": int(row["bedrooms"]), "typeId": row["type_id"],
"investment": {**FINANCE, "purchasePrice": float(row["purchase_price"])},
})["estimate"]
inv = est["investment"]
# A measured projection when available; otherwise the middle (55%) occupancy scenario, labelled as such.
r = inv["projection"] or (inv["scenarios"][1] if len(inv["scenarios"]) > 1 else None)
results.append({"name": row["name"], "basis": inv["basis"], "comps": est["asking"]["count"],
"adr": r and round(r["adr"]), "occupancy": r and round(r["occupancy"], 3),
"revenue": r and round(r["revenue"]), "noi": r and round(r["netOperatingIncome"]),
"cap_rate": r and r["capRate"], "cash_on_cash": r and r["cashOnCash"],
"monthly_cash_flow": r and round(r["monthlyCashFlow"]), "break_even_occupancy": r and r["breakEvenOccupancy"]})احفظ ملف CSV
with open("dashboard.csv", "w", newline="") as f:
out = csv.DictWriter(f, fieldnames=results[0].keys())
out.writeheader()
out.writerows(results)أنشئ لوحة من صفحة واحدة
from html import escape
pct = lambda v: "-" if v is None else f"{v:.1%}"
sar = lambda v: "-" if v is None else f"SAR {v:,.0f}"
rows = "".join(
f"<tr><td>{escape(r['name'])}</td><td>{r['basis']}</td><td>{r['comps']}</td><td>{sar(r['revenue'])}</td>"
f"<td>{sar(r['noi'])}</td><td>{pct(r['cap_rate'])}</td><td>{pct(r['cash_on_cash'])}</td>"
f"<td>{sar(r['monthly_cash_flow'])}</td><td>{pct(r['break_even_occupancy'])}</td></tr>"
for r in sorted(results, key=lambda r: r["cap_rate"] or -1, reverse=True))
open("dashboard.html", "w").write(
"<meta charset=utf-8><title>Shortlist</title><style>body{font:14px system-ui;margin:2rem}td,th{padding:6px 10px;border-bottom:1px solid #ddd;text-align:right}td:first-child,th:first-child{text-align:left}</style>"
"<h1>Shortlist</h1><table><tr><th>Property</th><th>Basis</th><th>Comps</th><th>Revenue</th><th>NOI</th><th>Cap rate</th><th>Cash on cash</th><th>Cash flow / month</th><th>Break-even occ.</th></tr>"
+ rows + "</table>")اقرأها بعين ناقدة
- الصفوف التي أساسها
scenariosلا يتوفر لها إشغال مقيس في المنطقة، وتفترض أرقامها إشغالًا بنسبة 55٪. - إذا تجاوزت قيمة
breakEvenOccupancyالإشغال الذي تتوقعه، فالعقار يخسر بعد سداد أقساط الدين. - الرسوم والتكاليف الثابتة والتمويل افتراضاتك أنت. غيّر
FINANCEوأعد التشغيل، فكل عقار يكلف طلبًا واحدًا.