The numbers behind a judicial auction are in the appraisal, not the portal
A serverless pipeline reads the appraisals behind judicial property auctions, extracts thirty-seven fields and works out the return across several scenarios.
- Year
- 2025
- Client
- A property investor specialising in judicial auctions
A property at auction is appraised with numbers the ministry portal does not publish. The online record says where it is and what the opening bid is; to know whether it is worth it you need the gross and usable floor area, the floor, the land registry details and the rateable value, the condition of the property and of the building, the ownership shares, any unauthorised works and whether they have been regularised, the mortgages and seizures attached to the proceedings, and the value and restoration cost estimated by the judge's surveyor.
All of it exists in one place: the attached appraisal, a PDF of twenty-four pages at the median that in some cases runs to eight hundred and eighty-one. Across a pool of roughly fourteen thousand auctions open at the same time, reading them by hand means choosing in advance which ones to look at, which is deciding without knowing.
The first obstacle is that the raw material is dirty. Twenty-six per cent of auctions have no appraisal attached at all: the portal publishes them anyway, and for those no extraction will help. Of the appraisals that do exist, 27.5% are pure scans with no text layer: before you can read one you have to recognise it, and that is where Textract comes in. The rest are documents written by different people to different conventions, where the same fact changes name, position and format from one court to the next.

A chain of prompts, not a single prompt
Extraction is not one question put to a model. It is a chain: first a classifier establishes whether the appraisal concerns a single lot, a multi-property lot or a multi-lot sale, and which of the six types it falls into, because which floor area to read and where depends on that answer. Only then do the right prompts for that case come into play, around thirty-seven specialised ones instead of a single general one left to guess. Every value comes back with a confidence score alongside it, not just a number: whoever reads it knows which fields can be taken as they are and which need checking.
Prompts and models are editable from the interface. Whoever knows the domain, meaning whoever knows how an appraisal is written, can correct a prompt or assign a different model to a single field from the panel, without raising a request with engineering and without waiting for a release. That is the difference between a system that improves every day and one that improves when there is time.
What can be adjusted, and what cannot
The business assumptions are configuration: company overhead, the estimated duration of a renovation, the thresholds beyond which splitting a property pays off, all changed on the fly, because they change with the market and with the company. What is fixed by law, namely stamp duty rates, VAT and the property tax multipliers, deliberately stays in the code. The distinction is not a technical one: it exists so that nobody, from the panel, can adjust a statute.
A business parameter can be changed in an afternoon. A tax rate cannot, and the software has to know that.

With the fields extracted and the assumptions in place, the system chooses the scenarios to calculate on its own: new build, renovation, splitting, regularisation. The choice follows the client's decision table, starting from land registry category, condition and floor area, so that comparing two auctions is comparing like scenarios rather than two different readings of the same problem.
Reading the same appraisal twenty-eight times
A chain of specialised prompts has a cost you can measure: the same appraisal is read about twenty-eight times by the various steps. So the model cost is tracked per extraction, with prices refreshed automatically every week, and prompt caching is explicit rather than hoped for. On a system like this the lever is not the price per token: it is how often you avoid having the same pages read again.
What changes in the end is not how quickly an appraisal can be read: it is that you no longer have to pick one. The scope of the analysis stops being how much time a person has and goes back to being how many auctions there are.
A serverless pipeline on AWS: Lambda for the individual steps, Step Functions to orchestrate the extraction, Textract for OCR with SNS and SQS handling the asynchronous completion of jobs, S3 for the documents and Secrets Manager for the keys. The code is TypeScript end to end in a pnpm monorepo with six services plus shared code, with Zod validating every boundary and ts-pattern for case-based logic, which here is not an affectation: there are six lot types, and getting one wrong means reading the wrong floor area. Data lives in PostgreSQL with Drizzle ORM, fifteen tables and sixty-five versioned migrations. The control panel is Next.js 15 and React 19 on Vercel, with shadcn/Radix and Tailwind. The language models go through a provider-agnostic gateway: the model is configuration, not a decision baked in at build time, and it can be changed per prompt without shipping anything.
Stack: AWS Lambda, Step Functions, Textract, SQS, S3, TypeScript, pnpm monorepo, Zod, ts-pattern, PostgreSQL, Drizzle ORM, Next.js 15, React 19, Vercel, LLM gateway
A serverless pipeline on AWS: Lambda for the individual steps, Step Functions to orchestrate the extraction, Textract for OCR with SNS and SQS handling the asynchronous completion of jobs, S3 for the documents and Secrets Manager for the keys. The code is TypeScript end to end in a pnpm monorepo with six services plus shared code, with Zod validating every boundary and ts-pattern for case-based logic, which here is not an affectation: there are six lot types, and getting one wrong means reading the wrong floor area. Data lives in PostgreSQL with Drizzle ORM, fifteen tables and sixty-five versioned migrations. The control panel is Next.js 15 and React 19 on Vercel, with shadcn/Radix and Tailwind. The language models go through a provider-agnostic gateway: the model is configuration, not a decision baked in at build time, and it can be changed per prompt without shipping anything.
Stack: AWS Lambda, Step Functions, Textract, SQS, S3, TypeScript, pnpm monorepo, Zod, ts-pattern, PostgreSQL, Drizzle ORM, Next.js 15, React 19, Vercel, LLM gateway
- Year
- 2025
- Client
- A property investor specialising in judicial auctions
Got something similar in mind?
Tell us what you want to build and we will help you find the shortest way there.






