PROJECT / 2026
Genomlyst
Reading a BRF's Annual Report
Swedish web app for homebuyers that reads a housing association's annual report, recomputes and cross-checks its figures with page references, grades the association with versioned rules in code, and compares up to four associations.
- stack
- React 19 · TypeScript · Vite · React Router · Tailwind CSS 4 · shadcn/ui · Hono · Cloudflare Workers · Cloudflare D1 · Cloudflare R2 · Zod · OpenAI Responses API · Sign in with ChatGPT · pdf.js · Vitest · pnpm

01 / Overview
Anyone bidding on a bostadsrätt has to judge the association's finances from a 20-60 page annual report, usually within days, and most buyers cannot read one well. Genomlyst reads the årsredovisning and recomputes the key figures from the report's raw numbers instead of trusting the printed ones, shows any conflict between the two, and cites the page every figure and flag came from. The grade is set by published, versioned rules in code and always shown with its breakdown. The AI only explains it and never sets it. A buyer can compare two to four associations side by side and see what the apartment would cost per month in two years from the association's loan resets and their own mortgage. The public demo runs three example reports with the names changed. The full service runs locally for now, and the AI is meant to run on the user's own ChatGPT plan through Sign in with ChatGPT.
02 / Key features
- –Figures recomputed from the report's raw numbers and cross-checked against what the association printed, with conflicts shown instead of hidden
- –Every figure and flag carries the page it came from
- –Grade from A to E set by versioned rules in code (Regler 2026.1) with published thresholds and sources, always shown with its points per group: debt, operations, and income and cash
- –Each figure drawn on a ruler with Svag, Okej and Bra zones, so the report reads as one picture instead of a table
- –Compare two to four associations side by side on the same figures, with the best value in each row marked
- –Monthly cost projection from the association's loan resets, the buyer's own loan, rate and amortisation, including the effect of a one-point rate rise and the tax deduction
- –Swedish-language AI summary and chat about the report, grounded in the extracted figures
- –Uploaded PDFs are never shared, and only the extracted facts are cached
03 / Challenges
- –Buyers act on a grade, so it has to be reproducible and explainable: a language model that scores its own reading gives a different answer each run
- –Annual reports are long PDFs with e-signature pages, attachments and layouts that vary between associations, and every figure has to point back to the right page
- –A false conflict on a correct report costs more trust than a missed one, so the cross-check has to be careful about what it calls a mismatch
- –The AI runs on the user's own ChatGPT plan, which limits how a model call may be made and leaves no inference cost on the project
04 / Solution
The model only extracts: the PDF is prepared by dropping e-signature verification pages and embedded attachments while keeping a page map, then a streamed Responses API call returns a Zod-validated extraction. Everything after that is plain TypeScript in a core package with no I/O: metrics, cross-checks, bands, the grade, flags and the monthly cost. The grade follows published bands with sources and caps such as a loss-making streak, and the rules page renders its thresholds straight from the same code, with a test that checks the two stay in step. The extraction is cached on the file hash, schema version, prompt version and model, so the same report is never read twice. The app is a Vite and React front end served by a Hono worker on Cloudflare, with the sign-in tokens stored encrypted (AES-GCM) in D1 and never sent to the browser.
05 / Outcome
A working app with a public static demo at genomlyst.berkay.se, built around three example reports with the names changed: grade with breakdown, page-referenced figures, a three-way comparison and a monthly cost projection. The full service runs locally while it waits for Sign in with ChatGPT to be available for web services.
06 / Screens
01 / 03

