Skip to content
Learn Agentic AI
Learn Agentic AI12 min read4 views

Building a Property Valuation Agent: Automated CMAs with AI Analysis

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.

What Is an Automated CMA and Why Automate It?

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.

Finding Comparable Properties

The first tool searches for comps within configurable parameters.

flowchart TD
    START["Building a Property Valuation Agent: Automated CM…"] --> A
    A["What Is an Automated CMA and Why Automa…"]
    A --> B
    B["Finding Comparable Properties"]
    B --> C
    C["Applying Valuation Adjustments"]
    C --> D
    D["Wiring Into an Agent Tool"]
    D --> E
    E["Generating a Professional Report"]
    E --> F
    F["FAQ"]
    F --> DONE["Key Takeaways"]
    style START fill:#4f46e5,stroke:#4338ca,color:#fff
    style DONE fill:#059669,stroke:#047857,color:#fff
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]

Applying Valuation Adjustments

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.

See AI Voice Agents Handle Real Calls

Book a free demo or calculate how much you can save with AI voice automation.

Wiring Into an Agent Tool

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],
)

Generating a Professional Report

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."
            )},
        ],
    }

FAQ

How accurate are automated CMAs compared to manual ones?

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.

How do you handle markets with few comparable sales?

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.

Should the AI agent replace professional appraisals?

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

Share
C

Written by

CallSphere Team

Expert insights on AI voice agents and customer communication automation.

Try CallSphere AI Voice Agents

See how AI voice agents work for your industry. Live demo available -- no signup required.

Related Articles You May Like

AI Interview Prep

7 AI Coding Interview Questions From Anthropic, Meta & OpenAI (2026 Edition)

Real AI coding interview questions from Anthropic, Meta, and OpenAI in 2026. Includes implementing attention from scratch, Anthropic's progressive coding screens, Meta's AI-assisted round, and vector search — with solution approaches.

Learn Agentic AI

Building a Multi-Agent Data Pipeline: Ingestion, Transformation, and Analysis Agents

Build a three-agent data pipeline with ingestion, transformation, and analysis agents that process data from APIs, CSVs, and databases using Python.

Learn Agentic AI

AI Agents for Real Estate: Property Search, Mortgage Calculators, and Viewing Automation

Build real estate AI agents with multi-agent property search, suburb intelligence, mortgage and investment calculators, and automated viewing scheduling for PropTech platforms.

Learn Agentic AI

Building a Research Agent with Web Search and Report Generation: Complete Tutorial

Build a research agent that searches the web, extracts and synthesizes data, and generates formatted reports using OpenAI Agents SDK and web search tools.

Learn Agentic AI

OpenAI Agents SDK in 2026: Building Multi-Agent Systems with Handoffs and Guardrails

Complete tutorial on the OpenAI Agents SDK covering agent creation, tool definitions, handoff patterns between specialist agents, and input/output guardrails for safe AI systems.

Learn Agentic AI

Agentic AI Market Hits $9 Billion in 2026: Complete Industry Analysis and Forecast

Deep analysis of the $9 billion agentic AI market in 2026 covering CAGR projections at 45.5%, key players, market segments, geographic distribution, and growth drivers.