Developer docs menu

Investment dashboard for a shortlist

Advanced

Run estimates with financing inputs for several properties and produce a CSV and a one-page HTML dashboard.

Setup

This tutorial uses the jokerprice.py helper from the quickstart. Save it next to your script and set JOKERPRICE_API_KEY.

List the candidates

Create candidates.csv. Prices are SAR.

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

Estimate each one with financing

Pass an investment object with the estimate. Percentages are whole numbers. Omitted fields use defaults: 25% down, 6% rate, 25 years, 15% platform fees, 2-night stays.

Python
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"]})

Save the CSV

Python
with open("dashboard.csv", "w", newline="") as f:
    out = csv.DictWriter(f, fieldnames=results[0].keys())
    out.writeheader()
    out.writerows(results)

Render a one-page dashboard

Python
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>")

Read it critically

  • Rows with basis scenarios have no measured occupancy nearby; their figures assume 55%.
  • breakEvenOccupancy above your expected occupancy means the property loses money after debt service.
  • Fees, fixed costs and financing are your assumptions. Change FINANCE and rerun: it costs one request per property.