aifinder / app.py
joeraylo's picture
Upload 2 files
29c0144 verified
import requests
from anthropic import Anthropic
from flask import Flask, request, render_template_string
from dotenv import load_dotenv
import os
# Initialize Flask app
app = Flask(__name__)
# Load environment variables
load_dotenv()
ANTHROPIC_API_KEY = os.getenv('ANTHROPIC_API_KEY')
ATTOM_API_KEY = os.getenv('ATTOM_API_KEY')
# Initialize Anthropic client
anthropic_client = anthropic.Client(api_key=ANTHROPIC_API_KEY)
# ATTOM API base URL
ATTOM_BASE_URL = "https://api.gateway.attomdata.com/propertyapi/v1.0.0/"
def query_attom(zip_code):
headers = {"Accept": "application/json", "apikey": ATTOM_API_KEY}
# Fetch property details
endpoint = "property/detail"
params = {"postalcode": zip_code, "pagesize": "10"}
try:
response = requests.get(f"{ATTOM_BASE_URL}{endpoint}", headers=headers, params=params, timeout=30)
if response.status_code == 200:
data = response.json()
properties = data.get("property", [])
if not properties:
return f"No properties found in ZIP {zip_code}."
# Extract detailed fields including condition, lot size
detailed_info = []
for p in properties:
prop = {
"address": f"{p.get('address', {}).get('line1', 'N/A')}",
"city": p.get('address', {}).get('city', 'N/A'),
"size": p.get('building', {}).get('size', {}).get('universalsize', 'N/A'),
"lot_size": p.get('lot', {}).get('lotsize2', 'N/A'),
"quality": p.get('building', {}).get('summary', {}).get('quality', 'N/A'),
"year_built": p.get('summary', {}).get('yearbuilt', 'N/A'),
"last_modified": p.get('vintage', {}).get('lastModified', 'N/A'),
"assessed_value": (
p.get('assessment', {}).get('assessments', [{}])[0].get('tax', {}).get('ttlValue') or
p.get('assessment', {}).get('assessments', [{}])[0].get('assdTtlValue', 'N/A')
)
}
# Fetch rental potential
avm_params = {"address1": prop["address"], "address2": f"{prop['city']}, {zip_code}"}
avm_response = requests.get(f"{ATTOM_BASE_URL}avm/detail", headers=headers, params=avm_params)
if avm_response.status_code == 200:
prop["rental_potential"] = avm_response.json().get("avm", {}).get("rentamount", "N/A")
else:
prop["rental_potential"] = "N/A"
detailed_info.append(prop)
# Get sales comparables
# Enhanced comps with more details
comps_response = requests.get(
f"{ATTOM_BASE_URL}sale/snapshot",
headers=headers,
params={"postalcode": zip_code, "pagesize": "5", "propertytype": "SFR|CONDO", "startSaleSearchDate": "2024-01-01"}
)
comps = (
[{"address": c.get('address', {}).get('oneLine', 'N/A'),
"sale_amount": c.get('sale', {}).get('saleAmt', 'N/A'),
"size": c.get('building', {}).get('size', {}).get('universalsize', 'N/A'),
"sale_date": c.get('sale', {}).get('salesearchdate', 'N/A')}
for c in comps_response.json().get("property", [])]
if comps_response.status_code == 200 else "No recent sales data available."
)
return {"properties": detailed_info, "comps": comps}
else:
return f"ATTOM API Error: {response.status_code} - {response.json().get('status', {}).get('msg', 'Unknown error')}"
except requests.exceptions.RequestException as e:
return f"Request Error: {str(e)}"
# Add this import near the top with other imports
from markdown2 import Markdown
def analyze_gems(data):
if isinstance(data, str):
return data
properties = data["properties"]
comps = data["comps"]
prompt = f"""
You're a real estate investor analyzing properties in this area.
Here's a list of properties with detailed information:
{properties}
Recent sales comparables in the area:
{comps}
Identify the top 3 properties based on:
- Location value and potential
- Property size (square footage)
- Lot size (square footage)
- Quality and year built
- Rental income potential (use 'rental_potential'—if 'N/A', estimate based on size, lot size, and comps)
- Recent sales comparables
Format the output in markdown:
# Top Investment Properties
## 1. [Property Name]
- **Address:** [Address, City]
- **Details:**
- Size: [size] sqft
- Lot: [lot_size] sqft
- Assessed Value: $[assessed_value]
- Year Built: [year_built]
- **Quality:** [quality]
- **Last Updated:** [last_modified]
- **Rental Potential:** $[rental_potential]/month
- **Investment Analysis:** [brief reason including rental potential and comps]
[Repeat format for properties 2 and 3]
"""
response = anthropic_client.messages.create(
model="claude-3-opus-20240229",
max_tokens=500,
system="You are a real estate investment analyst.",
messages=[{"role": "user", "content": prompt}]
)
return response.content
# Update the HTML template
HTML_TEMPLATE = """
<!DOCTYPE html>
<html>
<head>
<title>Distressed Property Finder</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<style>
body { font-family: Arial, sans-serif; padding: 20px; max-width: 800px; margin: auto; }
input, button { padding: 10px; margin: 5px; }
.markdown-content { background: #fff; padding: 20px; }
.markdown-content h1 { color: #2c3e50; }
.markdown-content h2 { color: #34495e; }
.markdown-content strong { color: #16a085; }
.download { margin-top: 10px; }
</style>
</head>
<body>
<h1>Distressed Property Finder</h1>
<form method="POST">
<input type="text" name="zip_code" placeholder="Enter ZIP Code" required>
<button type="submit">Find Properties</button>
</form>
{% if analysis %}
<h2>Top Picks for ZIP {{ zip_code }}</h2>
<div class="markdown-content">
{{ analysis|safe }}
</div>
<a href="data:text/markdown;charset=utf-8,{{ analysis|string|urlencode }}"
download="property-report-{{ zip_code }}.md" class="download">
Download Report
</a>
{% endif %}
</body>
</html>
"""
@app.route("/", methods=["GET", "POST"])
def home():
analysis = None
try:
if request.method == "POST":
zip_code = request.form["zip_code"]
data = query_attom(zip_code)
markdown_text = analyze_gems(data)
markdowner = Markdown()
analysis = markdowner.convert(markdown_text)
except Exception as e:
analysis = f"Error processing request: {str(e)}"
return render_template_string(HTML_TEMPLATE, analysis=analysis, zip_code=request.form.get("zip_code", ""))
if __name__ == "__main__":
app.run(host="0.0.0.0", port=7860)