Inspiration

In a shared flat, one person gets the electricity bill and has to work out who owes what. People move in and out mid-period, the bill may be in a language nobody in the flat reads well, and then everyone has to be chased for money. We wanted the whole chain (the bill, each person's share, and the payment) to sit in one place where every number can be traced back to the document.

What it does

Rental Bill Assistant connects a small rental workflow to utility billing:

  • Listings and tenancies. A landlord publishes a listing with explicit utility-sharing terms. A tenant searches (optionally in plain language), applies with move-in and end dates, and the landlord approves, which creates a tenancy with exact dates. Rent is shown for information only; the app does not collect rent.
  • AI bill reading. The landlord uploads a photo of an electricity statement. A DeepSeek vision model extracts the currency, total, prepayments, balance, service period and line items, and records where each value came from.
  • A check on the AI. The server re-reads the original image with Tesseract OCR and compares it with the model's amounts. A mismatch, an unreadable amount, missing service dates or inconsistent tax lines block automatic publication and send the bill to human review. The model's output is never silently "corrected".
  • Exact split. Code (not the model) allocates the amount due by each tenant's occupied days using decimal.js. Days nobody occupied stay with the landlord.
  • Pay with PayPal. Each tenant sees their own charge and the day count behind it, confirms it, and pays through PayPal Checkout. The server creates the order, captures it after approval, and re-reads the order from PayPal before marking the charge Paid.

In the demo video, a new listing goes all the way through: tenant application, approval, upload of a clearly synthetic statement, AI extraction with the OCR check, a 31-day share of EUR 1.19, and a PayPal Sandbox payment that ends in "Payment verified by the server".

How we built it

  • Web app: Next.js 16 (App Router), React 19 and TypeScript, deployed on Vercel. Prisma 6 on Supabase PostgreSQL, Supabase Auth for the landlord and tenant roles, and private Supabase Storage for uploaded statements.
  • AI extraction: DeepSeek's vision model (deepseek-flash) returns structured JSON validated with Zod. Each field carries its source evidence. DeepSeek's text model turns natural-language searches into filters, with a disclosed keyword fallback when it is unavailable.
  • OCR cross-check: Tesseract.js with bundled offline language data runs on the server and compares every extracted amount against the printed figures. Disagreement forces review.
  • Allocation: person-day allocation in decimal.js with fixed rounding rules, covered by unit tests. Zero-decimal currencies such as JPY are allocated in whole units.
  • PayPal: the REST API in Sandbox.
    • Auth: OAuth2 client credentials.
    • Orders v2: POST /v2/checkout/orders to create, POST /v2/checkout/orders/{id}/capture to capture, and GET /v2/checkout/orders/{id} to verify.
    • Each payment attempt has a stable PayPal-Request-Id, so retries are idempotent.
    • After capture the server checks status, merchant, currency, amount and custom_id against the charge. Return-URL parameters are never treated as proof of payment.
    • A verified payment locks the charge against paying twice.
    • A hard guard keeps the app on Sandbox.

Challenges we ran into

  • The vision model "fixed" bills. On a real statement whose printed table did not add up, the model rewrote a line so the arithmetic balanced. That is the opposite of what a tenant needs. We kept the model for extraction but added the independent OCR comparison, and any disagreement now blocks automatic publication.
  • Informational notes versus blocking issues. Early versions treated every note from the extractor as a blocker, so a perfectly consistent bill could never publish. We split the checks into informational notes and real blockers so a clean statement can publish in one click while bad ones still stop.
  • Payment states. A user can close the PayPal tab, come back twice, or hit a network error during capture. We modelled confirm, approve, capture and verify as explicit states. An uncertain result triggers a read-only query rather than a second charge.
  • OCR on serverless. Running Tesseract inside Vercel functions needed bounded in-process workers and bundled offline language data instead of a runtime download.

Accomplishments that we're proud of

  • The full loop runs on the live site: listing → application → approval → bill upload → AI extraction → OCR check → split → PayPal payment verified by the server, recorded in one continuous session for the video.
  • The AI is useful without being trusted with money. It reads the document; code computes the amounts; PayPal moves the money; the server verifies the result.
  • Every number on the tenant's screen can be traced back to the statement, the tenancy dates and the split rule.

What we learned

  • Giving the AI an independent check works better than prompt-tuning it into honesty. A cheap OCR comparison catches the cases that matter.
  • With PayPal Orders v2, a successful capture response is not the end of the story. Re-reading the order and comparing amount, currency, merchant and custom_id is what lets us show "Paid" with confidence.
  • Being explicit about what the product does not do (collect rent, convert currencies, guess unsupported billing cases) made the design simpler.

What's next

  • Multiple occupancy intervals per tenant and revising a published bill
  • Support for more document layouts and languages, using the same OCR gate
  • Payouts to the landlord and recurring bills
  • Moving from Sandbox to live payments, after a security review

AI tools used and how

  • DeepSeek (vision, deepseek-flash): extracts amounts, dates and line items from uploaded statements as structured JSON with per-field evidence (lib/ai/extract.ts).
  • DeepSeek (text): turns a tenant's plain-language search into structured filters (lib/ai/rental-search.ts).
  • Tesseract.js: OCR used as an independent check on the model's amounts. A mismatch forces human review (lib/ai/ocr-read.ts, lib/ai/ocr-verify.ts).
  • AI never computes or rewrites money. Allocation is deterministic decimal.js code, and payment status comes only from PayPal's verified order.
  • During development: AI coding agents (including Cursor agents and the oh-my-pi coding CLI) were used to write, test and review code under the developer's direction. Product decisions, money rules and acceptance checks were set and verified by the developer.

Built With

Share this project:

Updates

Submission history