By Sagar Shankaran, Founder of CallSphere
Learn how to build an AI agent that generates Comparative Market Analyses by pulling comparable properties, analyzing market data, applying valuation models, and producing professional reports.
Key takeaways
A Comparative Market Analysis (CMA) is the backbone of real estate pricing. Agents compare a subject property against recently sold comparable properties ("comps") to estimate fair market value. Manually, this takes 1-3 hours per property — pulling data, adjusting for differences, and formatting a report.
An AI valuation agent compresses this to minutes by automating comp selection, adjustment calculations, and report generation while keeping a human in the loop for final review.
The first tool searches for comps within configurable parameters.
Hear it before you finish reading
Talk to a live CallSphere AI voice agent for real estate in your browser — 60 seconds, no signup.
flowchart LR
CALLER(["Buyer or Seller Lead"])
subgraph TEL["Telephony"]
SIP["Twilio SIP and PSTN"]
end
subgraph BRAIN["Real Estate AI Agent"]
STT["Streaming STT<br/>Deepgram or Whisper"]
NLU{"Intent and<br/>Entity Extraction"}
TOOLS["Tool Calls"]
TTS["Streaming TTS<br/>ElevenLabs or Rime"]
end
subgraph DATA["Live Data Plane"]
CRM[("CRM and Notes")]
CAL[("Calendar and<br/>Schedule")]
KB[("Knowledge Base<br/>and Policies")]
end
subgraph OUT["Outcomes"]
O1(["Showing scheduled"])
O2(["Lead routed to agent"])
O3(["Pre-qual handed to broker"])
end
CALLER --> SIP --> STT --> NLU
NLU -->|Lookup| TOOLS
TOOLS <--> CRM
TOOLS <--> CAL
TOOLS <--> KB
NLU --> TTS --> SIP --> CALLER
NLU -->|Resolved| O1
NLU -->|Schedule| O2
NLU -->|Escalate| O3
style CALLER fill:#f1f5f9,stroke:#64748b,color:#0f172a
style NLU fill:#4f46e5,stroke:#4338ca,color:#fff
style O1 fill:#059669,stroke:#047857,color:#fff
style O2 fill:#0ea5e9,stroke:#0369a1,color:#fff
style O3 fill:#f59e0b,stroke:#d97706,color:#1f2937
from dataclasses import dataclass
from typing import Optional
import math
@dataclass
class ComparableProperty:
address: str
sale_price: float
sale_date: str
sqft: int
bedrooms: int
bathrooms: float
lot_size: float # acres
year_built: int
distance_miles: float
property_type: str
def haversine_distance(lat1: float, lon1: float, lat2: float, lon2: float) -> float:
"""Calculate distance between two coordinates in miles."""
R = 3959 # Earth radius in miles
dlat = math.radians(lat2 - lat1)
dlon = math.radians(lon2 - lon1)
a = (
math.sin(dlat / 2) ** 2
+ math.cos(math.radians(lat1))
* math.cos(math.radians(lat2))
* math.sin(dlon / 2) ** 2
)
return R * 2 * math.asin(math.sqrt(a))
async def find_comparables(
subject_lat: float,
subject_lon: float,
subject_sqft: int,
radius_miles: float = 1.0,
sqft_tolerance: float = 0.2,
max_results: int = 6,
pool=None,
) -> list[ComparableProperty]:
"""Find recently sold properties similar to the subject."""
min_sqft = int(subject_sqft * (1 - sqft_tolerance))
max_sqft = int(subject_sqft * (1 + sqft_tolerance))
rows = await pool.fetch("""
SELECT address, sale_price, sale_date, sqft, bedrooms,
bathrooms, lot_size, year_built, latitude, longitude,
property_type
FROM sold_properties
WHERE sqft BETWEEN $1 AND $2
AND sale_date >= NOW() - INTERVAL '6 months'
ORDER BY sale_date DESC
LIMIT 50
""", min_sqft, max_sqft)
comps = []
for row in rows:
dist = haversine_distance(
subject_lat, subject_lon,
row["latitude"], row["longitude"],
)
if dist <= radius_miles:
comps.append(ComparableProperty(
address=row["address"],
sale_price=row["sale_price"],
sale_date=str(row["sale_date"]),
sqft=row["sqft"],
bedrooms=row["bedrooms"],
bathrooms=row["bathrooms"],
lot_size=row["lot_size"],
year_built=row["year_built"],
distance_miles=round(dist, 2),
property_type=row["property_type"],
))
comps.sort(key=lambda c: c.distance_miles)
return comps[:max_results]
Raw comp prices need adjustments for differences in size, features, and condition.
@dataclass
class AdjustedComp:
comp: ComparableProperty
sqft_adjustment: float
bedroom_adjustment: float
age_adjustment: float
adjusted_price: float
def calculate_adjustments(
subject_sqft: int,
subject_beds: int,
subject_year: int,
comp: ComparableProperty,
price_per_sqft_market: float = 250.0,
) -> AdjustedComp:
"""Apply standard CMA adjustments to a comparable property."""
sqft_diff = subject_sqft - comp.sqft
sqft_adj = sqft_diff * (price_per_sqft_market * 0.5)
bed_diff = subject_beds - comp.bedrooms
bed_adj = bed_diff * 10_000
age_diff = comp.year_built - subject_year # newer comp = negative adjustment
age_adj = age_diff * 1_500
adjusted = comp.sale_price + sqft_adj + bed_adj + age_adj
return AdjustedComp(
comp=comp,
sqft_adjustment=sqft_adj,
bedroom_adjustment=bed_adj,
age_adjustment=age_adj,
adjusted_price=adjusted,
)
Each adjustment uses a simplified linear model. In production systems, you would derive these multipliers from local market regression analysis rather than hard-coding them.
from agents import Agent, function_tool, Runner
@function_tool
async def generate_cma(
address: str,
sqft: int,
bedrooms: int,
year_built: int,
latitude: float,
longitude: float,
) -> str:
"""Generate a Comparative Market Analysis for a property."""
comps = await find_comparables(
subject_lat=latitude,
subject_lon=longitude,
subject_sqft=sqft,
)
if len(comps) < 3:
return "Insufficient comparable sales found. Try expanding the search radius."
adjusted = [
calculate_adjustments(sqft, bedrooms, year_built, c)
for c in comps
]
prices = [a.adjusted_price for a in adjusted]
avg_price = sum(prices) / len(prices)
low_estimate = min(prices)
high_estimate = max(prices)
report_lines = [
f"## CMA Report for {address}",
f"Estimated Value: ${avg_price:,.0f}",
f"Range: ${low_estimate:,.0f} - ${high_estimate:,.0f}",
f"Based on {len(comps)} comparable sales\n",
]
for a in adjusted:
report_lines.append(
f"- {a.comp.address}: Sold ${a.comp.sale_price:,.0f}, "
f"Adjusted ${a.adjusted_price:,.0f} "
f"({a.comp.distance_miles} mi away)"
)
return "\n".join(report_lines)
valuation_agent = Agent(
name="PropertyValuationAgent",
instructions="""You are a real estate valuation specialist.
When given property details, generate a CMA report using the tool.
Explain the adjustments clearly. Always note that this is an
estimate and recommend a professional appraisal for lending.""",
tools=[generate_cma],
)
The agent's LLM output serves as the narrative section. For a formatted PDF, you can add a report generation step.
from datetime import date
def format_cma_report(
subject_address: str,
cma_data: str,
agent_narrative: str,
) -> dict:
"""Structure a CMA report for PDF generation."""
return {
"title": f"Comparative Market Analysis - {subject_address}",
"date": str(date.today()),
"sections": [
{"heading": "Executive Summary", "body": agent_narrative},
{"heading": "Comparable Analysis", "body": cma_data},
{"heading": "Disclaimer", "body": (
"This CMA is an estimate based on recent sales data. "
"It is not a formal appraisal and should not be used "
"for lending purposes."
)},
],
}
Automated CMAs are typically within 5-10% of manually prepared analyses when sufficient comparable data exists. The main limitation is that they cannot account for interior condition, renovations, or curb appeal without additional data inputs like inspection notes or photos.
Still reading? Stop comparing — try CallSphere live.
See the real estate AI agent handle a real call — complete, industry-specific, and live in your browser. No signup.
The agent expands the search radius incrementally (1 mile, then 2, then 5) and widens the square footage tolerance. If fewer than three comps are found even after expansion, it reports insufficient data rather than generating a misleading estimate.
No. Automated CMAs are excellent for quick pricing guidance, listing price recommendations, and initial buyer analysis. Formal appraisals required by lenders must still be performed by licensed appraisers who physically inspect the property.
#PropertyValuation #CMA #RealEstateAI #Python #MarketAnalysis #AgenticAI #LearnAI #AIEngineering

Written by
Sagar Shankaran· Founder, CallSphere
LinkedInSagar Shankaran is the founder of CallSphere, where he builds production AI voice and chat agents deployed across healthcare, hospitality, real estate, and home services. He writes about agentic AI, LLM engineering, and shipping voice agents that handle real calls in production.
See how AI voice agents work for your industry. Live demo available -- no signup required.
Step-by-step build of a working agent with the OpenAI Agents SDK — Agent class, tools, handoffs, tracing — plus an eval pipeline that catches regressions before merge.
Dubai hospitality scaled AI guest service agents in 2026 across luxury and mid-tier properties. We profile rollouts at Atlantis, Address Hotels.
Real estate teams use CallSphere voice agents for lead capture, qualification, and showing booking in 2026. Here's the playbook with numbers, the integrations.
Hotels deploy CallSphere AI voice agents for reservations, guest services, and concierge in 2026. Here's the playbook with real numbers, the PMS integrations.
Smolagents lets agents write Python instead of JSON. Why code-as-action reduces tool errors and where the security trade-offs are for production deployments.
Miami real estate brokerages standardized on AI agents in 2026 driven by international buyer flow. We profile deployments in Brickell, Coral Gables.
© 2026 CallSphere Inc. All rights reserved.
Made within San Francisco