🏠 Property Lead Tracker

Which channel finds your tenant?

Python pandas Chart.js Jinja2 Power BI (DAX) Claude API

📋 The Lead Problem This Solves

Challenge: A rental agency receives enquiries through five different doors: PropertyGuru chats, Mudah messages, Google and Meta ad leads, and WhatsApp referrals. Each platform reports only its own numbers. None of them can answer the one question the owner is paying to answer: which channel actually produces signed tenants, and what does each tenant cost to find? Without that, marketing budget is spent on the channel that looks busiest, not the one that works.

Solution: Property Lead Tracker collects every enquiry in one place, labels each one with where it came from, what it cost, and whether it led to a signed tenant, and follows it all the way through. It then answers three plain business questions: where the best leads come from, how much a tenant costs from each channel, and where to put next month's budget. The same data feeds two reports: a detailed dashboard for the agent, and a simple one-page report for each landlord.

What the business gets

💡 The Headline Answer: Cost Per Tenancy by Channel

Six months of data, every enquiry tagged with its source and followed to its outcome. Volume means nothing until it converts.

ChannelEnquiriesQualifiedTenanciesCost per tenancyVerdict
WhatsApp referral10167%2RM 0Champion
PropertyGuru29341%2RM 1,350Workhorse
Google Ads11550%2RM 1,800Quality engine
Meta Ads17227%1RM 2,400Rooms only
Mudah.my45915%0UndefinedVolume trap

The cheapest enquiries (Mudah, 40% of all volume) produced zero tenants. The most expensive enquiries (Google Ads, RM31 each) became the second-cheapest tenancies, because half of them were qualified. That is the difference between counting leads and counting tenants.

🖥️ What It Does

📊 Source comparison

Ranks all five channels by cost per enquiry, qualified rate, cost per viewing, and cost per tenancy. The single table the owner reads to decide where the budget goes.

🏠 Listing health and stale flags

Watches how fast each unit attracts enquiries and flags any whose interest has dried up (one dropped 89% after two weeks) for a price review instead of more ad spend.

📞 Lead quality, including phone calls

Keeps track of both where a lead came from (a portal, an ad, a friend's referral) and how it reached you (a chat, a WhatsApp, a phone call), so even phone enquiries get counted. Most tools miss those.

⏰ After-hours leak tracking

Surfaces that one in five enquiries arrives after 8pm or at weekends, and those go unanswered three times as often, roughly RM700 of paid leads dropped.

📝 White-label owner reports

Generates a plain-language one-page report per listing from the same data, branded per agency through a single CSS skin block. No rebuild to add a landlord or a firm.

🔁 Monthly insight notes

Threshold rules turn the month's numbers into written recommendations (reprice this listing, cap Meta to rooms, recover the after-hours leak) the agent edits and sends.

🤖 The Intelligence Layer

🧮 Automatic monthly notes

A simple set of rules reads each month's numbers and writes a plain note whenever something needs attention: too many enquiries going unanswered, a unit going quiet, or one channel costing far more per lead than the others. Every note points back to a real number, so it always agrees with the dashboard.

✍️ Optional polish by Claude AI

The draft notes can be tidied into smooth, ready-to-send wording by Claude AI, with every figure kept exactly as is. The system works fine without this step, so there is no extra cost unless you switch it on.

🎯 Clear rules, not a mystery model

For tidy enquiry records like these, a clear rule beats a complicated model: it is consistent, you can check it, and it is free. The owner can see exactly why a unit was flagged or a channel was called a waste of money.

📐 Every number ties back to the data

Every figure on every page comes from the same set of 1,140 enquiries. Change the data and the dashboard, the reports, and the notes all update together.

📊 Why not just a spreadsheet?

The honest part first: a spreadsheet can hold all of this. The data is four CSV files, and a disciplined agent could log every enquiry in a Sheet and build pivot tables. So the value is not storage. It is the five things that quietly break when a spreadsheet is the whole system.

1. It enforces the categories

The attribution only works because every enquiry carries a controlled source, contact method, qualification rule, and outcome from a fixed list. In a real Sheet, by week three one row says "FB", the next "facebook", the next "fb ads", the outcome column is half blank, and "qualified?" is filled by gut feel. The analysis dies from dirty categories, not missing formulas. Dropdowns, validation, and a written rule keep six months of data comparable.

2. It watches, so you don't have to ask

The stale-listing flag and the after-hours leak surfaced on their own, because the system compares every listing's recent pace to its opening fortnight and timestamps every enquiry. In a Sheet, those insights exist only if the agent thinks to build that exact pivot, this month, and remembers to check it. The busy agent never does, which is precisely why the leak existed. Monitoring beats analysis-on-demand.

3. It gets the cost maths right every time

Cost per tenancy joins enquiries to listings to two differently-allocated cost tables (ad spend by segment, platform fees by days listed). In a Sheet that is VLOOKUP chains that silently break when a row is inserted, a listing renamed, or a month added. The number still displays; it is just wrong, and nobody knows. A real data model recalculates correctly by construction.

4. The owner reports are automatic

Ten landlords each expect a branded monthly report. In a Sheet that is ten copy-paste-screenshot sessions a month, with stale numbers and formatting drift. The system generates ten white-label one-pagers from the same data in seconds, and an eleventh listing means zero extra work. This alone is hours saved every month, and it is the deliverable that keeps owners and their exclusive listings loyal.

5. It scales by adding rows, not rebuilding

Ten listings is a spreadsheet's comfort zone. Fifty listings, three agents, two agency brands is where the Sheet becomes the full-time job of one unlucky person. The system absorbs growth by adding rows.

The fair threshold, stated openly: if you are one agent with three listings and twenty enquiries a month, use a Sheet, you do not need this. The breakpoint is when volume, recurrence, and multiple audiences arrive together: hundreds of enquiries, monthly owner reports, and budget decisions worth real ringgit. Our demo agency crossed it at 1,140 enquiries, where the Mudah inversion was invisible in raw rows and obvious in the dashboard. And the design concedes the point gracefully: Google Sheets stays the capture layer. We are not replacing the spreadsheet; we put enforcement underneath it and intelligence on top of it.

🛠️ Technical Architecture

Data and analysis

Python pandas NumPy Seeded synthetic generator

Front-end surfaces

Chart.js Vanilla JS (5-tab SPA) Jinja2 owner-report template Refined Trust design system

Modeling and insight

Power BI data model + DAX spec Threshold-rule insight engine Claude API (optional polish)

How it fits together

📖 Run It Yourself

Prerequisites

Quick start

# Clone and enter the project git clone https://github.com/lyven81/ai-project.git cd ai-project/projects/property-lead-tracker # (Optional) regenerate the synthetic dataset (seed-fixed, reproducible) python generate_dataset.py # Generate the white-label owner reports (two listings, two agency skins) python owner-reports/generate_owner_reports.py # Generate the monthly insight notes for every month python insights/generate_insights.py --all # (Optional) polish the notes through Claude export ANTHROPIC_API_KEY=your_key_here python insights/generate_insights.py --month 2026-03 --polish

Then open in a browser

📊 Key Metrics

1,140
Enquiries tracked
5
Lead channels compared
7
Tenancies attributed
RM 0–2,400
Cost-per-tenancy range

Business value