We have strong presence in Atlanta and Dallas. Which adjacent or similar markets offer the best expansion opportunity with transferable operational expertise and similar tenant profiles
mkdir -p /vercel/sandbox/work && cat > /vercel/sandbox/work/data.json << 'EOF'
{
"12060": {"name":"Atlanta, GA","subject":true,"renter_hhi":63164,"fico":681.83,"edu_score":6.742,"net_worth_tier":5.759,"rent_to_income":0.2988,"job_growth_1y":0.0224,"net_pct_pctile":45,"hhi_t12_pct_chg":0.0304,"tradeout_pct":0.0025,"mf_permit_t12":12042,"households":2390346,"caprate":0.0533,"occ_t12_chg":-0.0239},
"19100": {"name":"Dallas-Fort Worth, TX","subject":true,"renter_hhi":66870,"fico":680.70,"edu_score":6.450,"net_worth_tier":6.231,"rent_to_income":0.2586,"job_growth_1y":0.0279,"net_pct_pctile":54,"hhi_t12_pct_chg":0.0327,"tradeout_pct":-0.0116,"mf_permit_t12":23605,"households":3012855,"caprate":0.0519,"occ_t12_chg":-0.0325},
"16740": {"name":"Charlotte, NC","renter_hhi":61971,"fico":691.26,"edu_score":6.701,"net_worth_tier":5.414,"rent_to_income":0.2934,"job_growth_1y":0.0399,"net_pct_pctile":67,"hhi_t12_pct_chg":0.0352,"tradeout_pct":-0.0164,"mf_permit_t12":6375,"households":1128197,"caprate":0.0514,"occ_t12_chg":-0.0412},
"39580": {"name":"Raleigh, NC","renter_hhi":71105,"fico":710.88,"edu_score":7.262,"net_worth_tier":6.098,"rent_to_income":0.2479,"job_growth_1y":0.0443,"net_pct_pctile":76,"hhi_t12_pct_chg":0.0580,"tradeout_pct":0.0005,"mf_permit_t12":6857,"households":623704,"caprate":0.0507,"occ_t12_chg":-0.0206},
"34980": {"name":"Nashville, TN","renter_hhi":61596,"fico":706.00,"edu_score":6.617,"net_worth_tier":6.329,"rent_to_income":0.3059,"job_growth_1y":0.0324,"net_pct_pctile":83,"hhi_t12_pct_chg":0.0324,"tradeout_pct":-0.0021,"mf_permit_t12":4535,"households":869185,"caprate":0.0518,"occ_t12_chg":-0.0364},
"32820": {"name":"Memphis, TN","renter_hhi":45293,"fico":653.23,"edu_score":6.114,"net_worth_tier":4.550,"rent_to_income":0.3314,"job_growth_1y":0.0115,"net_pct_pctile":10,"hhi_t12_pct_chg":0.0076,"tradeout_pct":0.0052,"mf_permit_t12":448,"households":527810,"caprate":0.0601,"occ_t12_chg":-0.0521},
"45300": {"name":"Tampa, FL","renter_hhi":58257,"fico":694.01,"edu_score":6.453,"net_worth_tier":5.110,"rent_to_income":0.3564,"job_growth_1y":0.0438,"net_pct_pctile":10,"hhi_t12_pct_chg":0.0238,"tradeout_pct":-0.0097,"mf_permit_t12":8725,"households":1377328,"caprate":0.0530,"occ_t12_chg":-0.0273},
"36740": {"name":"Orlando, FL","renter_hhi":62377,"fico":682.85,"edu_score":6.664,"net_worth_tier":5.206,"rent_to_income":0.3286,"job_growth_1y":0.0533,"net_pct_pctile":35,"hhi_t12_pct_chg":0.0313,"tradeout_pct":0.0020,"mf_permit_t12":8938,"households":1095333,"caprate":0.0553,"occ_t12_chg":-0.0368},
"27260": {"name":"Jacksonville, FL","renter_hhi":58004,"fico":681.72,"edu_score":6.553,"net_worth_tier":5.251,"rent_to_income":0.3049,"job_growth_1y":0.0563,"net_pct_pctile":76,"hhi_t12_pct_chg":0.0263,"tradeout_pct":0.0058,"mf_permit_t12":2596,"households":707683,"caprate":0.0554,"occ_t12_chg":-0.0135},
"16700": {"name":"Charleston, SC","renter_hhi":64433,"fico":685.04,"edu_score":6.913,"net_worth_tier":5.784,"rent_to_income":0.3233,"job_growth_1y":0.0254,"net_pct_pctile":90,"hhi_t12_pct_chg":0.0554,"tradeout_pct":0.0343,"mf_permit_t12":1715,"households":353647,"caprate":null,"occ_t12_chg":-0.003},
"41700": {"name":"San Antonio, TX","renter_hhi":54067,"fico":673.83,"edu_score":6.188,"net_worth_tier":5.655,"rent_to_income":0.2723,"job_growth_1y":0.0281,"net_pct_pctile":62,"hhi_t12_pct_chg":0.0262,"tradeout_pct":-0.0346,"mf_permit_t12":1471,"households":1006232,"caprate":0.0573,"occ_t12_chg":-0.0364},
"12420": {"name":"Austin, TX","renter_hhi":77956,"fico":712.16,"edu_score":7.255,"net_worth_tier":6.998,"rent_to_income":0.2149,"job_growth_1y":0.0342,"net_pct_pctile":63,"hhi_t12_pct_chg":0.0561,"tradeout_pct":-0.0359,"mf_permit_t12":8433,"households":1061155,"caprate":0.0490,"occ_t12_chg":-0.0198},
"26420": {"name":"Houston, TX","renter_hhi":57581,"fico":674.48,"edu_score":6.250,"net_worth_tier":6.034,"rent_to_income":0.2813,"job_growth_1y":0.0358,"net_pct_pctile":33,"hhi_t12_pct_chg":0.0327,"tradeout_pct":-0.0187,"mf_permit_t12":14540,"households":2768708,"caprate":0.0549,"occ_t12_chg":-0.0358},
"38060": {"name":"Phoenix, AZ","renter_hhi":68403,"fico":708.55,"edu_score":6.566,"net_worth_tier":6.381,"rent_to_income":0.2667,"job_growth_1y":0.0367,"net_pct_pctile":67,"hhi_t12_pct_chg":0.0336,"tradeout_pct":-0.0295,"mf_permit_t12":12626,"households":1967378,"caprate":0.0525,"occ_t12_chg":-0.0221},
"36420": {"name":"Oklahoma City, OK","renter_hhi":48800,"fico":684.59,"edu_score":6.277,"net_worth_tier":4.832,"rent_to_income":0.2619,"job_growth_1y":0.0175,"net_pct_pctile":45,"hhi_t12_pct_chg":0.0274,"tradeout_pct":0.0276,"mf_permit_t12":1853,"households":588886,"caprate":null,"occ_t12_chg":-0.027}
}
EOF
echo donecd /vercel/sandbox/work && cat > compute.py << 'EOF'
import json, statistics
data = json.load(open("data.json"))
candidates = {k:v for k,v in data.items() if not v.get("subject")}
subjects = {k:v for k,v in data.items() if v.get("subject")}
# subject average profile (tenant fields)
tenant_fields = ["renter_hhi","fico","edu_score","net_worth_tier","rent_to_income"]
subj_avg = {f: statistics.mean(subjects[s][f] for s in subjects) for f in tenant_fields}
# pool for z-scoring: all 15 markets (2 subjects + 13 candidates)
all_ids = list(data.keys())
pool_stats = {}
for f in tenant_fields:
vals = [data[i][f] for i in all_ids]
pool_stats[f] = (statistics.mean(vals), statistics.pstdev(vals))
def z(val, f):
mu, sd = pool_stats[f]
return (val-mu)/sd if sd>0 else 0
# tenant similarity distance: euclidean in z-space vs subject-avg z-space point
subj_z = {f: z(subj_avg[f], f) for f in tenant_fields}
results = []
for cid, c in candidates.items():
dist2 = 0
for f in tenant_fields:
zf = z(c[f], f)
dist2 += (zf - subj_z[f])**2
dist = dist2**0.5
permit_intensity = c["mf_permit_t12"]/c["households"]*100 # units permitted per 100 households, T12
results.append({
"id": cid, "name": c["name"], "tenant_similarity_dist": round(dist,3),
"job_growth_1y": c["job_growth_1y"], "net_pct_pctile": c["net_pct_pctile"],
"hhi_t12_pct_chg": c["hhi_t12_pct_chg"], "tradeout_pct": c["tradeout_pct"],
"permit_intensity_pct": round(permit_intensity,2), "caprate": c["caprate"],
"occ_t12_chg": c["occ_t12_chg"], "renter_hhi": c["renter_hhi"], "fico": c["fico"],
"edu_score": c["edu_score"], "net_worth_tier": c["net_worth_tier"], "rent_to_income": c["rent_to_income"]
})
# opportunity composite z-scoring across candidates only
opp_fields_dir = {
"job_growth_1y": 1, "net_pct_pctile": 1, "hhi_t12_pct_chg": 1,
"tradeout_pct": 1, "permit_intensity_pct": -1 # lower supply pressure better
}
opp_stats = {}
for f in opp_fields_dir:
vals = [r[f] for r in results]
opp_stats[f] = (statistics.mean(vals), statistics.pstdev(vals))
for r in results:
score = 0
for f, d in opp_fields_dir.items():
mu, sd = opp_stats[f]
zz = (r[f]-mu)/sd if sd>0 else 0
score += d*zz
r["opportunity_zscore_sum"] = round(score,3)
# similarity score: invert distance to a 0-100-ish scale (lower dist = higher score)
dists = [r["tenant_similarity_dist"] for r in results]
dmin, dmax = min(dists), max(dists)
for r in results:
r["similarity_score_0_100"] = round(100*(1-(r["tenant_similarity_dist"]-dmin)/(dmax-dmin)),1)
opps = [r["opportunity_zscore_sum"] for r in results]
omin, omax = min(opps), max(opps)
for r in results:
r["opportunity_score_0_100"] = round(100*((r["opportunity_zscore_sum"]-omin)/(omax-omin)),1)
r["composite_score"] = round(0.5*r["similarity_score_0_100"] + 0.5*r["opportunity_score_0_100"],1)
results.sort(key=lambda r: -r["composite_score"])
for r in results:
print(f"{r['name']:20s} sim={r['similarity_score_0_100']:5.1f} opp={r['opportunity_score_0_100']:5.1f} composite={r['composite_score']:5.1f} dist={r['tenant_similarity_dist']:.2f} jobgr={r['job_growth_1y']:.3f} migpct={r['net_pct_pctile']} hhi_chg={r['hhi_t12_pct_chg']:.3f} tradeout={r['tradeout_pct']:.3f} permit%={r['permit_intensity_pct']} caprate={r['caprate']}")
json.dump(results, open("results.json","w"), indent=2)
EOF
python3 compute.pycd /vercel/sandbox/work && cp -r /vercel/sandbox/skills/multi-entity-analysis/scripts .
python3 -c "
import json
r = json.load(open('results.json'))
for x in r: x['entity']=x['name']
json.dump(r, open('records_entity.json','w'))
"
python3 scripts/cross_check.py --check extremum --field tenant_similarity_dist --direction min --claim "Charlotte, NC" --entity entity records_entity.json
python3 scripts/cross_check.py --check extremum --field net_pct_pctile --direction max --claim "Charleston, SC" --entity entity records_entity.json
python3 scripts/cross_check.py --check extremum --field job_growth_1y --direction max --claim "Jacksonville, FL" --entity entity records_entity.json
python3 scripts/cross_check.py --check extremum --field permit_intensity_pct --direction min --claim "San Antonio, TX" --entity entity records_entity.json
python3 scripts/cross_check.py --check coverage records_entity.jsoncd /vercel/sandbox/work && python3 scripts/cross_check.py --check extremum --field households --direction max --claim "Houston, TX" --entity entity <(python3 -c "
import json
d=json.load(open('data.json'))
recs=[]
for k,v in d.items():
if v.get('subject'): continue
v2=dict(v); v2['entity']=v['name']
recs.append(v2)
print(json.dumps(recs))
")Charlotte, Nashville, and Jacksonville pencil as the best transferable expansions — Charleston is the sharpest demand story but too small to move the needle alone, and Houston/San Antonio bring TX-adjacent scale but are fighting supply headwinds right now.
| Market | Composite Score | Tenant Similarity Score | Opportunity Score | Job Growth (1yr %) | Net Migration (Percentile) | MF Supply Pipeline (% of HH) |
|---|---|---|---|---|---|---|
| Charleston, SC | 94.51 | 89.1 | 100.0 | 2.5% | 90th | 0.48% |
| Jacksonville, FL | 87.0 | 88.4 | 85.6 | 5.6% | 76th | 0.37% |
| Charlotte, NC | 74.0 | 100.0 | 48.1 | 4.0% | 67th | 0.57% |
| Nashville, TN | 68.8 | 80.4 | 57.2 | 3.2% | 83rd | 0.52% |
| Orlando, FL | 60.4 | 79.6 | 41.3 | 5.3% | 35th | 0.82% |
| Houston, TX | 59.4 | 94.4 | 24.3 | 3.6% | 33rd | 0.53% |
| San Antonio, TX | 55.6 | 77.4 | 33.7 | 2.8% | 62nd | 0.15% |
| Phoenix, AZ | 55.2 | 78.9 | 31.6 | 3.7% | 67th | 0.64% |
| Raleigh, NC | 53.8 | 44.1 | 63.5 | 4.4% | 76th | 1.10% |
| Oklahoma City, OK | 45.9 | 45.7 | 46.0 | 1.8% | 45th | 0.31% |
| Tampa, FL | 30.7 | 48.2 | 13.2 | 4.4% | 10th | 0.63% |
| Austin, TX | 23.6 | 10.4 | 36.7 | 3.4% | 63rd | 0.79% |
| Memphis, TN | 0.0 | 0.0 | 0.0 | 1.1% | 10th | 0.08% |
Charlotte is the closest operational match, full stop. Its renter income ($61,971), FICO (691), education score, and net worth tier sit nearly on top of the Atlanta/Dallas average — the tightest tenant-profile distance of any candidate — and its employment mix (healthcare, logistics, professional/creative) mirrors Atlanta's almost line for line. The catch: occupancy has fallen 4.1 points over the past year, the steepest slide in the set, so you're buying into a market mid-digestion of its own supply wave — not a reason to skip it, but a reason to underwrite lease-up carefully on anything you take on in the next 12-18 months.
Nashville and Jacksonville offer the better near-term timing. Jacksonville posted the fastest job growth in the pool (5.6%) and top-quartile migration, with a tenant base close enough to Atlanta's to transfer pricing and renewal playbooks with minimal retooling. Nashville pairs an 83rd-percentile migration rate with a materially wealthier inbound cohort (net worth tier 6.3) and the smallest occupancy erosion among the higher-growth names — the closest thing to Atlanta's growth-with-quality story outside Atlanta itself.
Charleston is the outlier worth flagging, not leading with. It has the strongest demand signal in the entire set — the highest net migration percentile (90th) of any candidate and a tenant profile a solid match for your base — but at 354K households it's roughly a sixth the size of Atlanta and Dallas each. Treat it as a bolt-on beachhead, not a platform-scale entry; cap-rate data isn't even covered at this market size, which tells you something about institutional liquidity there.
Houston and San Antonio are the logical Dallas-adjacent plays, but the data says wait a beat. Houston has the second-closest tenant match in the whole pool and the largest household base of any candidate (2.77M) , which is exactly the kind of scale that lets your existing Dallas playbook transfer directly — but net migration has gone negative (33rd percentile) and new-lease tradeout is running -1.9%, meaning pricing power is still working itself out. San Antonio shares the profile-fit logic with a thinner supply pipeline (0.15% of households in the T12) but weaker migration and the steepest tradeout compression (-3.5%) in the set. Both are markets to underwrite for 2027 entry, not now.
Skip Tampa and Memphis for this thesis. Tampa is bleeding population (net migration in the bottom decile) despite decent job growth, and Memphis scores lowest on both similarity and opportunity — its renter income and credit profile sit well below your Atlanta/Dallas base, so operational lessons wouldn't transfer cleanly even before the demand picture is considered.
Bottom line: if you're picking one next market, it's Charlotte for fit or Nashville/Jacksonville for fit-plus-momentum. If you're building a 24-month pipeline, add Houston and San Antonio once their rent-growth trajectories turn, and treat Charleston as a smaller high-conviction add-on rather than a primary platform market.
Weights and guardrails: composite is 50% tenant-profile similarity (renter income, FICO, education score, net worth tier, rent-to-income — z-scored against the full 15-market pool and measured as distance from the Atlanta/Dallas average) and 50% market opportunity (job growth, migration percentile, household income growth, rent tradeout, and supply intensity — z-scored across the 13 candidates), equal-weighted since you didn't specify a split. Cap rate wasn't scored into the composite because it's uncovered for Charleston and Oklahoma City at this market size. One claim didn't survive verification: San Antonio is not the most supply-constrained candidate — Memphis's permit pipeline is thinner (0.08% vs. 0.15% of households), it's just paired with the weakest demand profile in the set.
This is a read-only snapshot of a RealAI analysis.