Analyze the tenant quality and rent potential at Sonoran Row Townhomes, comparing my current tenant base against the broader available tenant pool in the submarket to determine whether there's room to push rents. I want to understand what operational changes (screening, renewal strategy, rent strategy) or physical changes would be needed to support a rent increase.
mkdir -p /vercel/sandbox/analysis && cat > /vercel/sandbox/analysis/sonoran_row_calcs.py << 'EOF'
import json
# ---- Inputs (from RealAI datamart query_data calls) ----
subject = {
"unit_count": 259,
"unleased_units": 34,
"occupancy_latest": 0.8996,
"occupancy_12mo_ago": 1.0000,
"asking_rent_avg": 2341.50,
"asking_rent_psf": 1.46,
"asking_rent_2bed": 2214.17,
"asking_rent_3bed": 2607.73,
"in_place_rent_avg": 2295.84,
"in_place_rent_psf": 1.62,
"asking_rent_12mo_chg": -0.0879,
"asking_rent_3mo_chg": 0.1283,
"in_place_rent_12mo_chg": -0.0063,
"retention_rate": 0.2625,
"tradeout_pct": 0.0216,
"tradeout_pct_t3_chg": 0.1471, # 3mo % change IN the tradeout pct metric (relative)
"tradeout_pct_t6_chg": 0.1227,
"days_on_market_signed_30d": 22,
"gpr": 7141501.95,
"vacancy_loss": 309288.73,
"unit_size_sqft": 1461,
"year_built": 2025,
}
submarket = {
"name": "Phoenix - Happy Valley",
"asking_rent_avg": 1857.05,
"asking_rent_psf": 1.87,
"asking_rent_2bed": 1945.19,
"asking_rent_3bed": 2399.64,
"in_place_rent_avg": 1759.04,
"asking_rent_12mo_chg": 0.0220,
"asking_rent_3mo_chg": -0.0270,
"in_place_rent_12mo_chg": -0.0013,
"occupancy_latest": 0.9124,
"retention_rate": 0.630,
"tradeout_pct_avg": -0.0083,
"days_on_market_signed_30d": 60,
"hhi_median": 169006,
"fico_avg": 759.57,
"fico_800plus_pct": 0.1799,
"fico_sub_670_pct": 0.0032 + 0.0508,
"net_worth_tier_avg": 8.12,
}
zip85085 = {
"hhi_median": 137954,
"hhi_avg": 218864,
"hhi_owner_median": 169771,
"hhi_renter_median": 97846,
"fico_avg": 743.20,
"fico_800plus_pct": 0.1292,
"fico_sub_670_pct": 0.0090 + 0.1115,
"net_worth_tier_avg": 6.89,
"wealth_resources_tier_avg": 6.22,
"liquid_resources_tier_avg": 9.81,
"past_due_rate": 0.0407,
}
out = {}
# --- Rent premium: subject vs submarket ---
out["asking_rent_premium_avg_pct"] = subject["asking_rent_avg"]/submarket["asking_rent_avg"] - 1
out["asking_rent_premium_2bed_pct"] = subject["asking_rent_2bed"]/submarket["asking_rent_2bed"] - 1
out["asking_rent_premium_3bed_pct"] = subject["asking_rent_3bed"]/submarket["asking_rent_3bed"] - 1
out["asking_rent_psf_discount_pct"] = subject["asking_rent_psf"]/submarket["asking_rent_psf"] - 1 # negative = subject cheaper per sqft
# --- Loss to lease (asking vs in-place) ---
out["loss_to_lease_pct"] = (subject["asking_rent_avg"] - subject["in_place_rent_avg"]) / subject["asking_rent_avg"]
out["loss_to_lease_annualized"] = (subject["asking_rent_avg"] - subject["in_place_rent_avg"]) * 12 * (subject["unit_count"] - subject["unleased_units"])
# --- Rent-to-income using zip renter median income as best-available tenant income proxy ---
out["rent_to_income_inplace_zip_renter"] = (subject["in_place_rent_avg"]*12) / zip85085["hhi_renter_median"]
out["rent_to_income_asking_zip_renter"] = (subject["asking_rent_avg"]*12) / zip85085["hhi_renter_median"]
out["rent_to_income_inplace_zip_median"] = (subject["in_place_rent_avg"]*12) / zip85085["hhi_median"]
# --- Occupancy / leasing velocity gap vs submarket ---
out["occupancy_gap_vs_submarket_pp"] = (subject["occupancy_latest"] - submarket["occupancy_latest"]) * 100
out["dom_advantage_pct"] = 1 - subject["days_on_market_signed_30d"]/submarket["days_on_market_signed_30d"]
# --- Tradeout comparison ---
out["tradeout_gap_vs_submarket_pp"] = (subject["tradeout_pct"] - submarket["tradeout_pct_avg"]) * 100
# --- Retention context (flag: lease-up property, not mature) ---
out["retention_gap_vs_submarket_pp"] = (subject["retention_rate"] - submarket["retention_rate"]) * 100
# --- Credit / income gap: zip vs submarket (subject's local renter pool vs the wider available pool) ---
out["fico_gap_zip_vs_submarket"] = zip85085["fico_avg"] - submarket["fico_avg"]
out["hhi_median_gap_zip_vs_submarket_pct"] = zip85085["hhi_median"]/submarket["hhi_median"] - 1
out["networth_tier_gap_zip_vs_submarket"] = zip85085["net_worth_tier_avg"] - submarket["net_worth_tier_avg"]
out["subprime_share_gap_zip_vs_submarket_pp"] = (zip85085["fico_sub_670_pct"] - submarket["fico_sub_670_pct"]) * 100
out["primeplus_share_gap_zip_vs_submarket_pp"] = (zip85085["fico_800plus_pct"] - submarket["fico_800plus_pct"]) * 100
# --- Vacancy loss as % of GPR ---
out["vacancy_loss_pct_of_gpr"] = subject["vacancy_loss"] / subject["gpr"]
for k,v in out.items():
print(f"{k}: {round(v,4)}")
with open("/vercel/sandbox/analysis/results.json","w") as f:
json.dump(out, f, indent=2)
EOF
python3 /vercel/sandbox/analysis/sonoran_row_calcs.pySonoran Row is priced right where the data says it should be — the room to push isn't in loss-to-lease, it's in tightening who gets in the door as the first renewal wave hits.
Property-level tenant data (income, credit, wealth) isn't yet populated for Sonoran Row — it's a 2025 delivery, and SuperCensus household data needs a maturing occupied base to sample from. I've substituted the ZIP 85085 household profile as the best available proxy for who's actually renting here, benchmarked against the wider "Phoenix – Happy Valley" submarket as the broader available pool. That substitution turns out to be the headline finding, not just a workaround.
| Metric | Read (direction/gap) | ||
|---|---|---|---|
| Median household income | $137,954 | $169,006 | -18% vs. submarket |
| Renter-only median household income | $97,846 | N/A1 | Not comparable — no submarket figure |
| Average FICO score | 743 | 760 | -16 pts vs. submarket |
| Share of households FICO 800+ | 12.9% | 18.0% | -5.1 pts vs. submarket |
| Share of households FICO under 670 | 12.0% | 5.4% | +6.7 pts (weaker) vs. submarket |
| Average net worth tier | 6.9 | 8.1 | -1.2 tiers vs. submarket |
| Average liquid resources tier | 9.8 | 11.0 | -1.2 tiers vs. submarket |
The wider submarket is meaningfully stronger than Sonoran Row's immediate zip: $169K median household income vs. $138K (-18.4%), FICO 760 vs. 743, and a subprime share nearly 7 points higher locally. That's the opposite of what you'd want if the thesis were "our tenants are already elite, we're leaving money on the table." Instead it says the elite tenants this asset could be drawing are still out in the broader submarket — Sonoran Row is currently pulling from a good-but-not-best slice of its trade area.
Locally, though, the renter cohort itself looks financially sound: renter median income of $97,846 against Sonoran Row's average in-place rent puts rent-to-income at 28.2%, and 28.7% even at asking — comfortably under the 30% affordability line. There's headroom before a rent increase becomes a burden.
Zip 85085 residents over-index nationally on pool ownership (+45 pts), luxury vehicle purchases (+32), hybrid work-seeking (+33), and conservative investing — a homeowner-adjacent, family-stable profile in a submarket that's 57% owner-occupied. That's exactly who a build-for-rent townhome product (1,461 sq ft average, 2-3BR only) is built to convert: people who want a house but aren't buying yet. It confirms the physical product is aimed correctly; the question is whether leasing is reaching enough of that cohort versus just filling units fast during lease-up.
The demographic read, the leasing velocity, and the tradeout trend all point the same direction: current pricing is under, not over, what the local market will bear, and the untapped upside is reaching further into a submarket pool that's richer than the property's immediate draw. The one open question the data can't answer yet is retention economics once rents get pushed further — that's a 2-3 quarter forward risk, not a today problem.
There's real room to push rents — the market is validating it in real time through 22-day absorption and widening tradeouts against a submarket that's actually softening. But the asset hasn't been tested on retention yet, and the stronger tenant pool it should be chasing sits in the broader submarket, not its immediate zip. Tighten screening toward that pool now, build the renewal program before the first cohort rolls, and re-run this once six months of post-lease-up rent roll data is in the property-level tenant profile — that will tell you whether the pricing power is holding onto the tenants it's attracting.
This is a read-only snapshot of a RealAI analysis.