Property Valuation Management System
Purpose-Built Valuation Database & Import Platform
Sole developer — scoping, architecture, build, data migration, and production deployment
↗ View liveThe problem
The client is a commercial property valuation firm with over 50 years of trading history and a national database of thousands of properties. Their entire property book lived inside the 'Title Descriptions' module of a general-purpose project management SaaS — a tool never designed to be a valuation database.
Valuations were stored as hardcoded repeating columns, duplicated three times across 'Current', 'Previous' and 'History' blocks. A ninth supplementary valuation would have required restructuring the entire dataset, and every new valuation cycle meant manually shifting data between blocks.
Desktop valuation reports couldn't be attached to the year they belonged to, any bulk update meant exporting the full register to Excel and re-uploading it wholesale, and the firm was paying a recurring per-user licence for a platform whose CRM, invoicing and project features went entirely unused.
What I built
Domain model
The core architectural decision was moving from a flat column structure to a normalised, year-based relational model. Each valuation is an individual record keyed by year and cycle, with value, category and component breakdowns. Current, Previous and Historical status is derived rather than stored — which eliminated the manual data-shifting entirely and lets the dataset grow without schema changes. Legacy provenance is retained on each record so migrated data stays traceable.
Property register
The operational centre of the product: server-side search, sort and pagination handling page sizes up to 5,000 rows, with around 70 query parameters spanning building, billing, contact, valuation, objection and admin fields. On top of that, an advanced filter builder grouped by domain area, a drag-to-reorder column picker with persisted ordering, a year selector that re-projects the dynamic valuation columns, and saved search templates with personal and shared scopes.
Import and migration pipeline
The highest-risk subsystem, and the one that made migration off the legacy platform possible. Five different Excel workbook shapes are auto-detected and normalised through a dedicated pipeline. Around it: dry-run validation, staged rows persisted per job, per-row failure capture with reasons, profile-scoped duplicate detection, a job history log, and live progress streamed to the browser over SignalR. Culture is pinned to en-ZA and currency-formatted cells are recovered rather than discarded — both hard-won fidelity fixes now locked behind tests.
Export
Four export profiles — standard, custom-column, bulk valuation and bulk supplementary — each honouring the active filter set or an explicit selection, and each written with stable identifiers and a hidden profile marker so an edited workbook round-trips cleanly back in.
Objections and appeals
Per-cycle tracking of objection action, reference and details, outcomes, hearing dates, appeal status including third-party, VAB status and Section 78 enquiries, feeding an operational dashboard covering upcoming hearings and open matters.
Platform
JWT authentication with three enforced roles, server-side forced first-password change, inactive-user lockout, optimistic concurrency tokens on business records, and a field-level audit trail written by an EF Core interceptor — with import execution context attached so bulk-imported changes remain attributable.
Architecture
.NET 9 Clean Architecture (Domain → Application → Infrastructure → API) with MediatR for CQRS, FluentValidation, and a Result<T> pattern for explicit error flow rather than exception control.
The Angular 19 frontend is standalone-component and signal-based, styled with Tailwind CSS 4, and consumes a typed API client generated from the OpenAPI contract — so a backend contract change surfaces as a compile error in the frontend rather than a runtime bug in front of the client.
The system runs against PostgreSQL in development and SQL Server in production, deployed to the client's own Windows Server via IIS. CI builds, applies migrations to a fresh SQL Server instance, asserts there are no pending model changes, and runs the test suite before anything reaches the client host.
Impact
- ▸The firm moved entirely off the legacy platform, eliminating a recurring per-user annual licence in exchange for a system it owns outright, source code included.
- ▸Decades of valuation history migrated with zero data loss, validated through dry runs before each commit.
- ▸The manual per-cycle data-shifting workflow was removed rather than automated — the model no longer requires it.
- ▸Bulk maintenance that previously meant a full-register export now runs against a filtered subset with a change preview and full audit attribution.
- ▸Live in daily production use by the client's team.
Tech stack
By the numbers
- Backend source
- ~13,300 lines of C#
- Frontend source
- ~12,200 lines of TS/HTML
- REST endpoints
- 38 across 6 controllers
- Domain entities
- 19
- Register query parameters
- ~70
- Excel formats supported
- 5, auto-detected
- Automated tests
- 78 backend, 44 frontend
- Team size
- 1