Case study
Oregon Provider Finder
A map-first search for Oregon's licensed clinicians, built in C# and Angular on the federal NPI registry. Misspell a city and it still finds the match; ask in a sentence and the server turns it into filters, with or without an AI key.

I'm a licensed counselor in Oregon, and I had already built a statewide provider directory in PHP. The question people brought to it was always narrower than the product around it: who practices here, in this specialty, near this place? So I rebuilt that core as a separate app in C# and Angular, against the federal NPI registry, and kept every layer small enough to read in an afternoon.
The data starts as the monthly NPPES dissemination from CMS, a 1.16 GB zip. A .NET importer streams all 9.8 million rows and keeps the 64,780 clinicians with an Oregon practice address and an active NPI. Each lands in one of seven groups from the NUCC taxonomy. The full pass takes under two minutes and peaks at 63 MB of memory. A second file adds 13,545 other practice locations, and every profile names its source and the date of the data.
An ASP.NET Core Web API on .NET 10 serves search and provider records from PostgreSQL 18 through EF Core. Postgres trigrams let a misspelled name or city still find its match. A word search with no place sorts by relevance; add a place and it sorts by distance, in statute miles from Census ZIP centroids. The API connects as a database user that can only read.
The Angular front end is standalone components and signals, with no zone.js. Search happens on a full-window Leaflet map over OpenStreetMap tiles. On a wide screen the results sit in a drawer that collapses; on a phone they are a bottom sheet with three stops. Panning the map searches the area in view. The 988 crisis line stays on screen the whole time.
Plain-words search reads a sentence like "a nurse practitioner in Salem who takes Aetna" and proposes filters. The server checks each one and drops what the registry can't answer, insurance included. With free keys it tries four model providers in turn. Without any, a rules-based reader does the job, so anyone can clone the repo and run it. Keys live in .NET user-secrets and never touch git.
The tests cover each layer: .NET unit tests for the core and the API (the API suite runs against a real Postgres test database), Angular unit tests in Vitest, and Playwright browser tests that include an axe accessibility check. GitHub Actions runs all of it on every pull request, along with a formatting check and a secret scan. Two scripts, setup.sh and dev.sh, take a fresh clone to a running app. The code is public on GitHub.
Common questions
What is the stack?
C# on .NET 10 with an ASP.NET Core Web API and EF Core, PostgreSQL 18, and an Angular front end in TypeScript with a Leaflet map. Tests run in xUnit, Vitest, and Playwright, and GitHub Actions checks every pull request.
Where does the data come from?
The federal NPPES file from CMS, the source of every NPI. The importer keeps active clinicians with an Oregon practice address and records the date of the data on every profile.
Does it need an AI key to run?
No. Plain-words search tries a chain of model providers when keys are present and falls back to a rules-based reader when they are not. Keys stay in .NET user-secrets, outside the repo.
Is it live?
It runs locally. The repository is public, and two scripts, setup.sh and dev.sh, take a fresh clone to a working app on ports 5080 and 4200.
