Redlands Smart Home Finder – Technical Details
Back to DemoHigh-Level System Overview
- Purpose: Score and rank Redlands-area properties against user-defined home search preferences, then display the best matches on a map and in result cards.
- Business problem: Converts subjective search criteria into a repeatable ranking API that can explain tradeoffs across affordability, commute, amenities, property condition, and resale factors.
- Architecture: Razor Page + ArcGIS map UI → versioned API →
IHomeScoringServiceandISavedSearchService→ repositories → PostgreSQL via EF Core. The scoring hot path dispatches to a native C++ kernel when the shared library is present, with a managed C# fallback that is functionally identical. - Technologies: .NET 10, Razor Pages, ArcGIS JavaScript SDK, Bootstrap, vanilla JavaScript, EF Core/Npgsql, Google OAuth cookie authentication, C++20/CMake native scoring library, P/Invoke interop.
- Deployment model: Runs in the Portfolio web app on Render with PostgreSQL backing property and saved-search data. The managed C# scoring path is active on Render (no native build step in the current Dockerfile); the native kernel activates locally after a CMake build and can be enabled in production by adding a build stage to the Dockerfile.
Frontend Architecture
The page uses Razor for initial layout and wwwroot/js/HomeFinder/app.js for client behavior. It is not a SPA; preference sliders, map graphics, property cards, and saved-search interactions are coordinated with modular vanilla JavaScript.
- Map UI: ArcGIS JavaScript SDK renders property pins and popup content for selected scored properties.
- State management: Slider weights and current result sets are held in browser state and sent to the API as
HomeSearchPreferencesDto. - API communication:
api-config.jsdefines/api/v1/homefinder/search,/property/{id}, and/searches;api-fetch.jsredirects API 401 responses to the login page. - Authentication handling: The page relies on cookie authentication; saved-search calls require the authenticated portfolio identity resolved server-side.
- UX decisions: Preference sliders expose scoring tradeoffs directly so the ranking model is explainable during a technical walkthrough.
API Layer
- Controller:
HomeFinderControlleris versioned at/api/v1/homefinderand currently requires[Authorize(Policy = "Authenticated")]. - Endpoints:
POST /api/v1/homefinder/searchscores top properties;GET /api/v1/homefinder/property/{id}returns one property;/searchesendpoints create, list, retrieve, and delete saved searches. - Backward compatibility:
/scoreand/properties/{id}aliases are retained but hidden from API exploration. - Authorization flow: The controller gets the current user through
IUserProfileService.GetCurrentUserId()for saved-search ownership instead of trusting request body identity. - Error handling: Not-found property/search paths return 404, duplicate saved searches return 409, and unauthenticated saved-search calls return 401.
- OpenAPI: Versioning and response metadata are declared with attributes so the API surface remains discoverable and migration-friendly.
Services and Business Logic
- Scoring service:
HomeScoringServiceretrieves filtered property data fromIPropertyRepository, scores each property across ten weighted dimensions, sorts by composite score descending, and assigns sequential rank before returning the top-N result set as DTOs. - Native C++ scoring kernel: The compute-intensive ranking math is extracted into a native shared library (
portfolio_scoring.dll/libportfolio_scoring.so) built with C++20,/arch:AVX2, and-O3 -march=haswell -ffast-math. The library exposes two C-ABI functions —ScoreProperty(single) andScorePropertyBatch(SIMD-friendly batch) — over blittable structs withPack = 8layout to eliminate marshalling overhead. - P/Invoke interop layer:
NativeScoringInteropdeclares the raw[DllImport]signatures.NativeScoringBridgeis a safe static façade that probesNativeLibrary.TryLoadat startup, exposesIsAvailable, and maps managedPropertyandHomeSearchPreferencesDtoobjects to the native structs. Both types areinternaltoPortfolio.Services; the test project accesses them via[assembly: InternalsVisibleTo]. - Transparent fallback:
GetTopPropertiesAsyncchecksNativeScoringBridge.IsAvailableand branches to eitherScoreWithNativeKernel(native) or the managed LINQ scoring helpers. The managed path is functionally identical to the native kernel and is the active path on Render today. - Saved-search service:
SavedSearchServiceowns named search persistence, uniqueness rules, timestamps, and owner scoping. - Separation of concerns: The controller handles HTTP translation; services own business rules and DTO mapping; repositories own EF Core queries.
- Extensibility: The scoring model is isolated behind
IHomeScoringService, allowing future replacement with richer spatial, ML, or search-index scoring without changing the controller contract. The native kernel can be extended independently of the managed API surface.
Data Access Layer
- Repository pattern:
IPropertyRepositoryandISavedSearchRepositoryisolate EF Core access from business logic. - Database: PostgreSQL is accessed through EF Core with the Npgsql provider, fluent mappings, migrations, and parameterized LINQ queries.
- Ownership: Saved searches are scoped to the current user through repository predicates, not by client-provided owner fields.
- Query behavior: Property scoring currently operates over repository-provided property data; future scale could add indexed search, precomputed feature vectors, or database-side filtering before scoring.
- Caching: No dedicated property cache is currently implemented; the design favors correctness and simple repository boundaries over cache invalidation complexity.
Infrastructure and Deployment
- Hosting: Render hosts the ASP.NET Core app; PostgreSQL connection strings and Google OAuth secrets are injected through configuration.
- Authentication infrastructure: Cookie authentication uses Google as the challenge scheme, with API requests receiving 401 instead of HTML redirects.
- Shared Redis infrastructure: Home Finder persists through PostgreSQL rather than Redis, while the host application uses Redis for geocoding caches, batch job state, and production Data Protection keys when configured.
- Database operations: EF Core migrations run at startup, supporting automated schema rollout for portfolio-scale deployments.
- Native library deployment: The C++ scoring library is built separately via CMake. On Render the native library is absent and the managed C# fallback runs automatically — no application code changes required. To activate the native path in production, a CMake build stage would be added before the dotnet publish stage in the Dockerfile and the resulting
libportfolio_scoring.socopied to the output directory. The post-build CMake target handles this automatically for local development. - Containerization: The same application can run from the root Dockerfile and Docker Compose; Kubernetes manifests provide replica, service, and HPA definitions.
- Cloud evolution: For larger scale, the property ranking path could move toward managed PostgreSQL read replicas, search indexes, Azure Cache for Redis, or a dedicated scoring service. The native kernel could be containerized as a sidecar gRPC service if scoring load justified isolation from the web process.
Engineering Decisions and Tradeoffs
- Weighted scoring: A transparent ten-dimension scoring formula is easy to explain and test, but less adaptive than ML-based recommendation models. Every sub-score is a pure function of entity fields and preference weights, making unit testing and parity checks between the managed and native paths straightforward.
- Native C++ fast path with managed fallback: Extracting the scoring kernel to a native library demonstrates SIMD-friendly data layout, P/Invoke interop, and deployment awareness — the same decision a backend team faces when introducing a Rust or C++ compute module. The fallback design means the feature degrades gracefully rather than failing in environments where the native build is unavailable.
- Blittable struct layout:
PropertyInputNative,PreferencesInputNative, andScoreOutputNativeuse[StructLayout(LayoutKind.Sequential, Pack = 8)]to match the C header exactly, avoiding runtime marshalling copies. Any field addition requires a synchronized change in both the C header and the managed struct. - Server-side ranking: Keeps scoring rules authoritative and avoids exposing full property datasets to the browser, at the cost of server CPU per search. The native batch path amortizes that cost across AVX2 vector lanes when the library is present.
- Authenticated saved searches: Provides durable user value and ownership guarantees, but requires login even for some interactions in the current controller configuration.
- Future improvements: Add spatial indexes, bounding-box filtering, cached property feature vectors, pagination, search telemetry, background recomputation of market-derived signals, and a Dockerfile CMake build stage to activate the native kernel on Render.
Interview Discussion Points
- How would you keep scoring explainable as ranking factors become more complex?
- Which parts should move into database queries, a search engine, or a separate scoring service at larger scale?
- How do you enforce saved-search ownership without trusting client-supplied user ids?
- What indexes would matter for property search once the dataset grows beyond in-memory scoring?
- How would you design A/B testing or telemetry around ranking quality?
- Why keep the managed C# fallback instead of requiring the native library?
- What are the risks of a blittable struct ABI shared between C++ and .NET, and how would you version it safely?
- How would you isolate the native scoring kernel as a sidecar service if it needed to scale independently of the web process?