---
title: "Building a Property Valuation Agent: Automated CMAs with AI Analysis"
description: "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."
canonical: https://callsphere.ai/blog/building-property-valuation-agent-automated-cma-ai-analysis
category: "Learn Agentic AI"
tags: ["Property Valuation", "CMA", "Real Estate AI", "Python", "Market Analysis"]
author: "CallSphere Team"
published: 2026-03-17T00:00:00.000Z
updated: 2026-05-07T12:01:06.004Z
---

# 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.

```mermaid
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
Deepgram or Whisper"]
        NLU{"Intent and
Entity Extraction"}
        TOOLS["Tool Calls"]
        TTS["Streaming TTS
ElevenLabs or Rime"]
    end
    subgraph DATA["Live Data Plane"]
        CRM[("CRM and Notes")]
        CAL[("Calendar and
Schedule")]
        KB[("Knowledge Base
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
```

```python
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  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.

## Wiring Into an Agent Tool

```python
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)  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

---

Source: https://callsphere.ai/blog/building-property-valuation-agent-automated-cma-ai-analysis
