← All Articles

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:

  1. Extracts property details from the listing (address, square footage, lot size, beds, baths, price)
  2. Pulls comparable sales from the MLS database (or calls an external API)
  3. Generates a comp analysis report
  4. Estimates market value using simple regression
  5. 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() # Convert PDF to base64 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.""" # This could call MLS API, Zillow API, or query your own database # Example: query your DynamoDB table of recent sales dynamodb = boto3.resource("dynamodb") sales_table = dynamodb.Table("comparable-sales") # Query by neighborhood and property type 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, # Most recent first Limit=20 ) # Filter to similar properties (within 1000 sqft, 1 bed/bath, last 90 days) 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] # Top 10 comps

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() # Build comp data for Claude 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.""" # Calculate price per square foot for each comp price_per_sqft = [ comp["sale_price"] / comp["square_feet"] for comp in comparable_sales if comp["square_feet"] > 0 ] # Use median (more robust than mean) median_price_per_sqft = statistics.median(price_per_sqft) # Estimate subject property value estimated_value = ( subject_property["square_feet"] * median_price_per_sqft ) # Adjust for special features adjustments = 0 if subject_property.get("pool"): adjustments += estimated_value * 0.05 # +5% for pool if subject_property.get("year_built") < 1970: adjustments -= estimated_value * 0.10 # -10% for age 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) # From metadata try: # Step 1: Download listing PDF response = s3.get_object(Bucket=event["Records"][0]["s3"]["bucket"]["name"], Key=listing_file) listing_pdf = response["Body"].read() # Step 2: Extract property data property_data = extract_listing_data(listing_pdf) # Step 3: Find comps comparable_sales = find_comparable_sales(property_data) if not comparable_sales: raise ValueError(f"No comparable sales found for {property_data['property_address']}") # Step 4: Generate analysis analysis_report = generate_comp_analysis(property_data, comparable_sales) # Step 5: Estimate value valuation = estimate_market_value(property_data, comparable_sales) # Step 6: Store results 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 } ) # Step 7: Email report to agent 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)}") # Send error email to agent 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:

# Zillow, Redfin, or your local MLS usually has API access 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:

# Run monthly cron to update your comps database from public sources def refresh_comparable_sales(): # Query Zillow, Redfin, or county assessor data # Store in DynamoDB for fast lookup 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):

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

Treat Claude as a research assistant who reads all the comps in an hour. The agent still makes the decisions.

Getting Started

  1. Get 5-10 property listings. Have Claude extract data. Verify accuracy.
  2. Build comps lookup. Set up your MLS API access or database.
  3. Test end-to-end. Upload a listing. Get back analysis. Verify quality.
  4. Deploy to Lambda. Set up the S3 trigger.
  5. 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 ] # Generate portfolio summary 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:

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.