← All Articles
August 6, 2026
8 min read
AI · Real Estate · Automation
AI for Real Estate: Automating Property Analysis and Comps
Real estate is a data game: comps, neighborhoods, listing sheets, market analyses. This automation turns days of manual review into hours.
Agents spend hours reading comps, analyzing neighborhoods, and extracting numbers from listing sheets to write property analyses.
This is exactly what Claude is good at: read a document, extract structured data, generate analysis, output a report.
Here’s the architecture and the economics.
The Workflow: From Listing to Automated Analysis
Day 1: Agent Finds a Property
They upload the listing sheet (PDF or image), their local market notes, and any recent sales data they already have.
Day 2: System Processes Overnight
A Lambda function does five things:
- Extracts property details from the listing (address, square footage, lot size, beds, baths, price)
- Pulls comparable sales from the MLS database (or calls an external API)
- Generates a comp analysis report
- Estimates market value using simple regression
- Emails the report back to the agent
The agent opens the email, reviews the report, adjusts if needed, and sends it to the buyer or seller. Done.
Old process: 4-6 hours of agent time, 2-3 hours of analyst time. New process: 30 minutes of agent time, upload plus review.
The Components
1. Listing Data Extraction
Use Claude to read a listing sheet and extract structured fields:
import anthropic
import json
def extract_listing_data(listing_pdf: bytes) -> dict:
"""Extract property details from listing sheet."""
client = anthropic.Anthropic()
import base64
listing_base64 = base64.standard_b64encode(listing_pdf).decode("utf-8")
message = client.messages.create(
model="claude-3-5-sonnet-20241022",
max_tokens=1024,
messages=[
{
"role": "user",
"content": [
{
"type": "document",
"source": {
"type": "base64",
"media_type": "application/pdf",
"data": listing_base64
}
},
{
"type": "text",
"text": "Extract property details and return as JSON."
}
]
}
],
response_format={
"type": "json_schema",
"json_schema": {
"name": "PropertyListing",
"schema": {
"type": "object",
"properties": {
"property_address": {"type": "string"},
"price_listed": {"type": "number"},
"bedrooms": {"type": "integer"},
"bathrooms": {"type": "number"},
"square_feet": {"type": "integer"},
"lot_size_sqft": {"type": "integer"},
"year_built": {"type": "integer"},
"property_type": {"type": "string"},
"garage_spaces": {"type": "integer"},
"pool": {"type": "boolean"},
"special_features": {"type": "array", "items": {"type": "string"}}
},
"required": ["property_address", "price_listed", "bedrooms", "bathrooms"]
}
}
}
)
return json.loads(message.content[0].text)
2. Comparable Sales Lookup
Get recent sales of similar properties:
import boto3
def find_comparable_sales(property_data: dict) -> list[dict]:
"""Find recent sales of similar properties."""
dynamodb = boto3.resource("dynamodb")
sales_table = dynamodb.Table("comparable-sales")
response = sales_table.query(
IndexName="neighborhood-type-index",
KeyConditionExpression="neighborhood = :nb AND property_type = :pt",
ExpressionAttributeValues={
":nb": extract_neighborhood(property_data["property_address"]),
":pt": property_data["property_type"]
},
ScanIndexForward=False,
Limit=20
)
similar = [
sale for sale in response["Items"]
if abs(sale["square_feet"] - property_data["square_feet"]) < 1000
and abs(sale["bedrooms"] - property_data["bedrooms"]) <= 1
and (datetime.now() - datetime.fromisoformat(sale["sale_date"])).days < 90
]
return similar[:10]
3. Comp Analysis Report Generation
Use Claude to write the analysis:
def generate_comp_analysis(
subject_property: dict,
comparable_sales: list[dict]
) -> str:
"""Generate a market analysis report."""
client = anthropic.Anthropic()
comps_text = "\n".join([
f"- {comp['address']}: {comp['beds']} bed, {comp['baths']} bath, "
f"{comp['square_feet']} sqft, sold for ${comp['sale_price']:,} on {comp['sale_date']}"
for comp in comparable_sales
])
prompt = f"""Generate a professional real estate market analysis report for:
Subject Property:
- Address: {subject_property['property_address']}
- Price Listed: ${subject_property['price_listed']:,}
- Beds: {subject_property['bedrooms']}, Baths: {subject_property['bathrooms']}
- Square Feet: {subject_property['square_feet']:,}
- Year Built: {subject_property['year_built']}
Recent Comparable Sales in {extract_neighborhood(subject_property['property_address'])}:
{comps_text}
Write a professional 3-4 paragraph analysis including:
1. Market summary (is this area hot? cooling?)
2. How this property compares to comps (premium? discount?)
3. Estimated market value based on comps
4. Key factors affecting value (location, condition, features)
5. Recommendation (overpriced? fair? underpriced?)"""
message = client.messages.create(
model="claude-3-5-sonnet-20241022",
max_tokens=1024,
messages=[{"role": "user", "content": prompt}]
)
return message.content[0].text
4. Market Value Estimation
Simple statistical approach:
import statistics
def estimate_market_value(subject_property: dict, comparable_sales: list[dict]) -> dict:
"""Estimate property value using comp analysis."""
price_per_sqft = [
comp["sale_price"] / comp["square_feet"]
for comp in comparable_sales
if comp["square_feet"] > 0
]
median_price_per_sqft = statistics.median(price_per_sqft)
estimated_value = (
subject_property["square_feet"] * median_price_per_sqft
)
adjustments = 0
if subject_property.get("pool"):
adjustments += estimated_value * 0.05
if subject_property.get("year_built") < 1970:
adjustments -= estimated_value * 0.10
adjusted_value = estimated_value + adjustments
return {
"estimated_value": round(adjusted_value),
"price_per_sqft": round(median_price_per_sqft, 2),
"listed_price": subject_property["price_listed"],
"variance": round((adjusted_value - subject_property["price_listed"]) / subject_property["price_listed"] * 100, 1),
"market_assessment": "underpriced" if adjusted_value > subject_property["price_listed"] * 1.05
else "overpriced" if adjusted_value < subject_property["price_listed"] * 0.95
else "fairly priced"
}
The Complete Pipeline
import time
import json
import boto3
s3 = boto3.client("s3")
dynamodb = boto3.resource("dynamodb")
ses = boto3.client("ses")
def lambda_handler(event, context):
"""Process listing and generate analysis."""
listing_file = event["Records"][0]["s3"]["object"]["key"]
agent_email = extract_agent_email(listing_file)
try:
response = s3.get_object(Bucket=event["Records"][0]["s3"]["bucket"]["name"], Key=listing_file)
listing_pdf = response["Body"].read()
property_data = extract_listing_data(listing_pdf)
comparable_sales = find_comparable_sales(property_data)
if not comparable_sales:
raise ValueError(f"No comparable sales found for {property_data['property_address']}")
analysis_report = generate_comp_analysis(property_data, comparable_sales)
valuation = estimate_market_value(property_data, comparable_sales)
table = dynamodb.Table("property-analyses")
table.put_item(
Item={
"property_address": property_data["property_address"],
"timestamp": int(time.time()),
"extracted_data": json.dumps(property_data),
"comparable_sales": json.dumps(comparable_sales),
"analysis_report": analysis_report,
"valuation": json.dumps(valuation),
"agent_email": agent_email
}
)
email_body = f"""
Property Analysis Report
{property_data['property_address']}
{analysis_report}
Valuation:
- Estimated Market Value: ${valuation['estimated_value']:,}
- Price per Sqft: ${valuation['price_per_sqft']}
- Listed Price: ${valuation['listed_price']:,}
- Assessment: {valuation['market_assessment']}
"""
ses.send_email(
Source="noreply@company.com",
Destination={"ToAddresses": [agent_email]},
Message={
"Subject": {"Data": f"Market Analysis: {property_data['property_address']}"},
"Body": {"Text": {"Data": email_body}}
}
)
return {"statusCode": 200, "body": "Analysis complete"}
except Exception as e:
print(f"Error: {str(e)}")
ses.send_email(
Source="noreply@company.com",
Destination={"ToAddresses": [agent_email]},
Message={
"Subject": {"Data": "Analysis Failed"},
"Body": {"Text": {"Data": f"Failed to analyze property: {str(e)}"}}
}
)
raise
Data Sources
Where do comps come from?
Option 1: MLS API
If you have MLS access (most real estate firms do), integrate their API:
import requests
def query_mls_api(address: str, property_type: str) -> list:
"""Query MLS for recent sales."""
response = requests.get(
"https://your-mls-api/comparable-sales",
params={
"address": address,
"property_type": property_type,
"days_back": 90,
"radius_miles": 2
},
headers={"Authorization": f"Bearer {mls_api_key}"}
)
return response.json()["results"]
Option 2: Public Data Plus Your Database
Aggregate public sale records and store in DynamoDB:
def refresh_comparable_sales():
pass
Option 3: Hybrid Approach
Use MLS API for real-time data, and supplement with historical data from your own database.
Cost Breakdown
For a 30-agent brokerage processing 5 properties per week (1,200 per year):
- Claude API: ~$0.30 per analysis (2 pages listing + comp report) = $360/year
- Lambda: $0.10/month = $1.20/year
- DynamoDB: $5/month = $60/year
- S3: $2/month = $24/year
Total infrastructure: ~$445/year.
Time savings: 4 hours per property the old way versus 0.5 hours the new way, a savings of 3.5 hours per property. At 1,200 properties a year and $100/hour, that’s $420,000 saved annually.
ROI: roughly 1,000x.
What Still Needs Humans
- Market expertise. Claude can analyze comps, but market context (is this neighborhood gentrifying? is the school district great?) requires local knowledge.
- Whether to buy or sell at this price is a business decision, not a valuation output. That call stays with the agent.
- Negotiation strategy. How to position the property in offers.
- Client relationship, communication, trust, closing. No pipeline does that part.
Treat Claude as a research assistant who reads all the comps in an hour. The agent still makes the decisions.
Getting Started
- Get 5-10 property listings. Have Claude extract data. Verify accuracy.
- Build comps lookup. Set up your MLS API access or database.
- Test end-to-end. Upload a listing. Get back analysis. Verify quality.
- Deploy to Lambda. Set up the S3 trigger.
- Measure impact. How much time do agents actually save? Are analyses accurate?
Most real estate teams see 50-70% time reduction for comp analysis, with no quality loss.
Advanced: Multi-Property Analysis
For investors looking at portfolios:
def analyze_portfolio(properties: list[dict]) -> dict:
"""Analyze multiple properties for investment decision."""
analyses = [
{
"property": prop,
"valuation": estimate_market_value(prop, find_comparable_sales(prop)),
"cash_flow": estimate_rental_income(prop),
"appreciation": estimate_neighborhood_growth(prop)
}
for prop in properties
]
client = anthropic.Anthropic()
summary = client.messages.create(
model="claude-3-5-sonnet-20241022",
max_tokens=1024,
messages=[{
"role": "user",
"content": f"Analyze this portfolio for investment viability: {json.dumps(analyses, indent=2)}"
}]
)
return {
"property_analyses": analyses,
"portfolio_summary": summary.content[0].text
}
Claude can analyze a 10-property portfolio in seconds. Manual analysis takes days.
The Future
This is just the beginning. Real estate is ripe for automation:
- Automated property walk-throughs (video plus Claude vision)
- Contract analysis that flags unusual terms
- Tenant qualification and automated screening
- Neighborhood analysis covering crime, schools, demographics
For now, focus on what’s proven: comp analysis, property extraction, report generation.
The team that automates this wins.
Get the free AI Readiness Checklist
15 questions to diagnose your team’s AI readiness, where you’ll see ROI fastest, and what to tackle first.
✓ Takes 5 minutes
✓ Actionable next steps
✓ No sales pitch
No spam. Unsubscribe anytime.
or
Ready to build AI that actually works?
Let’s talk about how SRE discipline transforms AI from a risky experiment into a reliable business system.
Book Your Free Discovery Call
About the author
Charles Harvey is the founder of Three Moons Network and a site reliability engineer who builds production-grade AI automation for small businesses — monitoring, cost visibility, and documentation included. He writes about his hands-on AI experiments at floggingclaude.com. Connect on LinkedIn or see the code on GitHub.