| import requests
|
| from anthropic import Anthropic
|
| from flask import Flask, request, render_template_string
|
| from dotenv import load_dotenv
|
| import os
|
|
|
|
|
| app = Flask(__name__)
|
|
|
|
|
| load_dotenv()
|
| ANTHROPIC_API_KEY = os.getenv('ANTHROPIC_API_KEY')
|
| ATTOM_API_KEY = os.getenv('ATTOM_API_KEY')
|
|
|
|
|
| anthropic_client = anthropic.Client(api_key=ANTHROPIC_API_KEY)
|
|
|
|
|
| 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}
|
|
|
|
|
| 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}."
|
|
|
|
|
| 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')
|
| )
|
| }
|
|
|
| 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)
|
|
|
|
|
|
|
| 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)}"
|
|
|
|
|
| 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
|
|
|
|
|
| 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) |