<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Berkcan Kapusuzoglu</title>
    <description>The latest articles on DEV Community by Berkcan Kapusuzoglu (@berkcankapusuzoglu).</description>
    <link>https://dev.to/berkcankapusuzoglu</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3810494%2Fb412420d-4f7f-43e7-b3ba-edd3b7adfdfd.jpeg</url>
      <title>DEV Community: Berkcan Kapusuzoglu</title>
      <link>https://dev.to/berkcankapusuzoglu</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/berkcankapusuzoglu"/>
    <language>en</language>
    <item>
      <title>How I Built a Rental Property Deal Analyzer with FastAPI and AI</title>
      <dc:creator>Berkcan Kapusuzoglu</dc:creator>
      <pubDate>Fri, 06 Mar 2026 19:42:42 +0000</pubDate>
      <link>https://dev.to/berkcankapusuzoglu/how-i-built-a-rental-property-deal-analyzer-with-fastapi-and-ai-285l</link>
      <guid>https://dev.to/berkcankapusuzoglu/how-i-built-a-rental-property-deal-analyzer-with-fastapi-and-ai-285l</guid>
      <description>&lt;p&gt;I spent months evaluating rental properties with spreadsheets and paid calculator subscriptions before deciding to build my own tool. The result is a free, open-source web app that calculates 20+ investment metrics, scores deals on a 14-point system, projects 5-year returns, and runs optional AI analysis -- all from two files with zero build step.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Live demo:&lt;/strong&gt; &lt;a href="https://rental-property-deal-analyzer.onrender.com" rel="noopener noreferrer"&gt;https://rental-property-deal-analyzer.onrender.com&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;GitHub:&lt;/strong&gt; &lt;a href="https://github.com/berkcankapusuzoglu/Rental-Property-Deal-Analyzer" rel="noopener noreferrer"&gt;https://github.com/berkcankapusuzoglu/Rental-Property-Deal-Analyzer&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  The Problem
&lt;/h2&gt;

&lt;p&gt;If you are evaluating rental property investments, you need answers to basic questions: What is the cash-on-cash return? Does it pass the 1% rule? What is the DSCR? What is the total return over 5 years when you factor in appreciation, debt paydown, and tax benefits?&lt;/p&gt;

&lt;p&gt;Paid tools like DealCheck charge $10-20/month. Spreadsheets are fragile and tedious. I wanted something that was fast, thorough, and free.&lt;/p&gt;
&lt;h2&gt;
  
  
  The Solution
&lt;/h2&gt;

&lt;p&gt;A 6-step wizard that walks you through property info, financing, income, expenses, review, and results. You can paste a Zillow URL to auto-fill data or enter everything manually. In about 2 minutes you get a full dashboard with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;20+ metrics:&lt;/strong&gt; CoC, Cap Rate, DSCR, NOI, GRM, OER, Break-Even Occupancy, monthly cash flow, and more&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;14-point deal scorecard:&lt;/strong&gt; 7 factors scored individually with plain-English reasoning&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;5-year total return:&lt;/strong&gt; Broken into Cash Flow + Appreciation + Debt Paydown + Tax Benefits&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Strategy fit:&lt;/strong&gt; Cash Flow / Wealth Building / Low Risk / BRRRR analysis&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AI narrative analysis:&lt;/strong&gt; Optional, using free local LLMs or Claude API&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  Technical Deep-Dive
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Single-File Architecture
&lt;/h3&gt;

&lt;p&gt;The entire frontend is one HTML file: ~2,800 lines of inline CSS, HTML, and JavaScript. The backend is one Python file: ~1,000 lines of FastAPI.&lt;/p&gt;

&lt;p&gt;Why? Because rental property analysis is fundamentally a calculation problem, not a UI framework problem. The state is simple (form inputs go in, metrics come out), there is no routing, and the interactivity is a linear wizard. React or Vue would add a build step, a node_modules folder, and hundreds of dependencies for zero benefit.&lt;/p&gt;

&lt;p&gt;The JS is organized as an IIFE (Immediately Invoked Function Expression) that exposes only what needs to be global (&lt;code&gt;window.runAI&lt;/code&gt;, &lt;code&gt;window.scrapedData&lt;/code&gt;). Inside the closure: DOM helpers, state management, wizard navigation, the calculation engine, an amortization builder, a chart renderer, and event listeners.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# The entire backend is 3 routes + 1 streaming endpoint
&lt;/span&gt;&lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;FastAPI&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="nd"&gt;@app.get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;                          &lt;span class="c1"&gt;# Serve index.html
&lt;/span&gt;&lt;span class="nd"&gt;@app.post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/api/scrape&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;               &lt;span class="c1"&gt;# Zillow data extraction
&lt;/span&gt;&lt;span class="nd"&gt;@app.post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/api/analyze-ai&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;           &lt;span class="c1"&gt;# AI analysis (non-streaming)
&lt;/span&gt;&lt;span class="nd"&gt;@app.post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/api/analyze-ai-stream&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;    &lt;span class="c1"&gt;# AI analysis (SSE streaming)
&lt;/span&gt;&lt;span class="nd"&gt;@app.get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/api/models&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;                &lt;span class="c1"&gt;# List available AI models
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Zillow Scraping: 4 Extraction Strategies
&lt;/h3&gt;

&lt;p&gt;Zillow embeds property data in a &lt;code&gt;__NEXT_DATA__&lt;/code&gt; JSON blob in their HTML, but the structure varies. After reverse-engineering multiple listing pages, I identified four distinct schemas:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Strategy A -- gdpClientCache:&lt;/strong&gt; The most common. Data lives under &lt;code&gt;props.pageProps.componentProps.gdpClientCache&lt;/code&gt;, keyed by a GraphQL-style cache key.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Strategy B -- apiCache:&lt;/strong&gt; Same concept but under &lt;code&gt;apiCache&lt;/code&gt;. Different key structure, different nesting.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Strategy C -- Direct pageProps:&lt;/strong&gt; Sometimes the property data sits directly in &lt;code&gt;props.pageProps.property&lt;/code&gt; without any cache layer.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Strategy D -- componentProps:&lt;/strong&gt; A fallback where data is nested in component-specific props.&lt;/p&gt;

&lt;p&gt;Each strategy extracts and normalizes the same fields: price, beds, baths, sqft, lot size, year built, home type, tax history, zestimate, and rent zestimate.&lt;/p&gt;

&lt;p&gt;The scraper tries httpx first with realistic browser headers. When Zillow blocks the request, it falls back to Playwright headless Chromium. Even Playwright can get CAPTCHAd, so manual entry is always the final fallback.&lt;/p&gt;

&lt;h3&gt;
  
  
  AI Streaming with SSE
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;/api/analyze-ai-stream&lt;/code&gt; endpoint provides real-time streaming from three different AI providers through a unified interface.&lt;/p&gt;

&lt;p&gt;Each provider has its own SSE format:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;LM Studio&lt;/strong&gt; uses OpenAI-compatible &lt;code&gt;data: {"choices": [{"delta": {"content": "..."}}]}&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ollama&lt;/strong&gt; uses &lt;code&gt;{"message": {"content": "..."}}&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Anthropic&lt;/strong&gt; uses &lt;code&gt;event: content_block_delta&lt;/code&gt; with &lt;code&gt;{"delta": {"text": "..."}}&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The backend normalizes all three into a consistent &lt;code&gt;data: {"token": "..."}&lt;/code&gt; stream that the frontend consumes.&lt;/p&gt;

&lt;p&gt;The hardest part was handling "thinking" models. Qwen3 and DeepSeek-R1 emit &lt;code&gt;&amp;lt;think&amp;gt;...&amp;lt;/think&amp;gt;&lt;/code&gt; blocks before their actual response. The &lt;code&gt;_process_stream_token()&lt;/code&gt; function tracks state across tokens to strip these artifacts in real-time, without buffering the full response.&lt;/p&gt;

&lt;h3&gt;
  
  
  Deal Scoring Algorithm
&lt;/h3&gt;

&lt;p&gt;The 14-point scorecard evaluates 7 factors. Each gets 0, 1, or 2 points:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Factor&lt;/th&gt;
&lt;th&gt;2 points&lt;/th&gt;
&lt;th&gt;1 point&lt;/th&gt;
&lt;th&gt;0 points&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;CoC Return&lt;/td&gt;
&lt;td&gt;&amp;gt;= 8%&lt;/td&gt;
&lt;td&gt;&amp;gt;= 4%&lt;/td&gt;
&lt;td&gt;&amp;lt; 4%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cap Rate&lt;/td&gt;
&lt;td&gt;&amp;gt;= 6%&lt;/td&gt;
&lt;td&gt;&amp;gt;= 4%&lt;/td&gt;
&lt;td&gt;&amp;lt; 4%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DSCR&lt;/td&gt;
&lt;td&gt;&amp;gt;= 1.25&lt;/td&gt;
&lt;td&gt;&amp;gt;= 1.0&lt;/td&gt;
&lt;td&gt;&amp;lt; 1.0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cash Flow/Unit&lt;/td&gt;
&lt;td&gt;&amp;gt;= $200/mo&lt;/td&gt;
&lt;td&gt;&amp;gt;= $100/mo&lt;/td&gt;
&lt;td&gt;&amp;lt; $100/mo&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Break-Even Occ.&lt;/td&gt;
&lt;td&gt;&amp;lt;= 75%&lt;/td&gt;
&lt;td&gt;&amp;lt;= 85%&lt;/td&gt;
&lt;td&gt;&amp;gt; 85%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1% Rule&lt;/td&gt;
&lt;td&gt;Pass (2)&lt;/td&gt;
&lt;td&gt;--&lt;/td&gt;
&lt;td&gt;Fail (0)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;50% Rule&lt;/td&gt;
&lt;td&gt;Pass (2)&lt;/td&gt;
&lt;td&gt;--&lt;/td&gt;
&lt;td&gt;Fail (0)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Verdict: &amp;gt;= 75% of max points = Great Deal, &amp;gt;= 45% = Borderline, &amp;lt; 45% = Pass.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lessons Learned
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Single-file frontends scale further than you would think.&lt;/strong&gt; At 2,800 lines, the index.html is still navigable because the JS is organized into clear sections inside one IIFE. The mental overhead of "where does this live?" is zero.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Thinking models need runtime filtering.&lt;/strong&gt; You cannot just strip &lt;code&gt;&amp;lt;think&amp;gt;&lt;/code&gt; tags after the response is complete if you are streaming -- the user would see the raw thinking output appear and then disappear. Token-by-token filtering with state tracking solves this cleanly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The 4-pillar return model changes how people think about deals.&lt;/strong&gt; Most calculators show cash flow and cap rate. When you show someone that a property with mediocre cash flow still generates a strong total return through appreciation and debt paydown, it reframes the analysis.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try It
&lt;/h2&gt;

&lt;p&gt;The live demo requires no installation: &lt;a href="https://rental-property-deal-analyzer.onrender.com" rel="noopener noreferrer"&gt;https://rental-property-deal-analyzer.onrender.com&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To run locally:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; requirements.txt
python app.py
&lt;span class="c"&gt;# Opens at http://localhost:8000&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;GitHub:&lt;/strong&gt; &lt;a href="https://github.com/berkcankapusuzoglu/Rental-Property-Deal-Analyzer" rel="noopener noreferrer"&gt;https://github.com/berkcankapusuzoglu/Rental-Property-Deal-Analyzer&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;MIT licensed. Contributions welcome.&lt;/p&gt;

&lt;p&gt;If the tool helped you evaluate a deal, consider supporting its development: &lt;a href="https://buymeacoffee.com/bkapu" rel="noopener noreferrer"&gt;https://buymeacoffee.com/bkapu&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>fastapi</category>
      <category>python</category>
      <category>showdev</category>
    </item>
  </channel>
</rss>
