Phantom Census
Inspiration
A health ministry's coverage map will tell you a district has three maternity clinics, so planners cross it off the list. But the survey data for that same district shows women still giving birth without medical care. Both things can't be true at once.
The explanation is that some of those clinics aren't really there. They show up in a registration record, a budget line, or an old directory scrape, but not on the ground. We started calling them phantoms. The problem with a phantom is that it makes an underserved district look covered, so the doctor, the ambulance, and the funding all get routed somewhere else. We wanted to find these gaps and give planners an honest map of where care is actually missing.
What it does
Phantom Census takes a messy registry of healthcare facilities and figures out which ones are real, which are duplicates of each other, and which are fabricated. From there it scores every district twice: once treating the registry at face value, and once after the phantom facilities are removed. The difference between those two scores is the hidden gap.
The planner-facing app is a map of India's districts. A single toggle switches between the raw view and the adjusted view. When you flip it, districts that were relying on phantom facilities turn redder, which is the whole point: you can see exactly where the official numbers were covering for a shortage.
One deliberate constraint runs through the entire system. No language model is involved in deciding whether a facility is real or in computing any score. We track this as token_usage: 0 in the code. If a planner moves a maternity ward based on our output, they need to be able to answer "how do you know that clinic is fake," and "the AI said so" doesn't hold up when the stakes are that high. Every verdict comes from a deterministic test with an evidence trail a clinician can read and reproduce.
How we built it
The whole thing runs on Databricks. Spark and Delta Lake on Unity Catalog handle ingestion of the facility registry, India Post geography, and NFHS-5 health-burden indicators, and store the resulting gold tables.
Before judging anything, an entity-resolution step groups rows that describe the same real facility. Four directory snapshots of "St Anne Hospital" with different doctor counts and specialty lists should be one entity, not four. We do this with blocked record linkage (lat/long grid, pincode, and source ID for blocking, then name similarity, phone, website, and geographic distance for matching).
The detection engine is structured as a Prosecutor, a Defender, and an Adjudicator. It runs seven independent existence tests, then rolls the per-row results up to a per-entity verdict: real if any snapshot looks real, phantom only if all of them fail, and contested otherwise. Each facility and capability also gets a trust grade of strong, partial, weak, or no-claim.
Desert scoring converts those verdicts into a per-district score, pre-renders the map as Folium tile layers, and writes everything to Lakebase (managed Postgres). A React app reads from Lakebase and serves the map and the toggle.
For a district $d$ with $N(d)$ registered facilities, $p(d)$ of them judged phantom, a capacity scale $N_{\max}$, and a survey-derived burden weight $b(d)$:
$$ s_{\text{raw}}(d) = b(d)\left(1 - \frac{N(d)}{N_{\max}}\right) \qquad s_{\text{adj}}(d) = b(d)\left(1 - \frac{N(d) - p(d)}{N_{\max}}\right) $$
Since $p(d) \ge 0$, the adjusted score is always at least as high as the raw score, and they're equal only when a district has no phantoms. Removing fake clinics makes a district's score go up, meaning it looks worse off than the registry suggested. That's the signal planners care about.
We developed it using Linked-Intent Development, where each behavior is tagged with a spec annotation tied to a written requirement, so the code and its tests stay tied to the original intent.
Challenges we ran into
At one point the pipeline reported around 10,000 distinct "capabilities." It turned out the registry's capability field wasn't a clean category, it was extracted prose averaging over 1,200 characters per row, full of sentences like "Houses Gurgaon's first stroke centre" and "100% painless dental treatments." We had been splitting that text on commas. The fix was to stop parsing the prose and instead normalize against the structured specialties field, where the top 50 clinical codes already cover about 90% of mentions.
Geography was harder than expected. 84% of Indian PIN codes (16,388 of 19,561) cover more than one post office, with a median spread of about 15 km between them. Our first distance check measured each facility against its PIN centroid and ended up flagging 19% of all facilities as suspicious, when the real phantom rate is closer to 3%. It was penalizing real clinics for being in large postal zones. We changed the distance test to use the nearest post office instead of the centroid (which dropped the 75th-percentile error from 23 km to under 5 km) and demoted it from a hard veto to supporting evidence. We left the only true veto power with the point-in-district-polygon check, which has no arbitrary distance threshold to tune.
We also had a check that flagged facilities as "too old to plausibly exist," and it started accusing real hospitals of being fake. Madras General Hospital was founded in 1664 and SSKM Kolkata in 1707. We lowered the floor to 1600 and made the upper bound relative to the run year rather than a hardcoded constant.
Separately, the live map briefly showed only the adjusted layer and not the raw one. We assumed a bug in our rendering code and spent time there before realizing the deployed notebook in the workspace was an old version that predated the two-layer render. The data was fine; the deployment had drifted.
Accomplishments that we're proud of
We kept the verdict path completely free of language models without sacrificing accuracy, which means every judgment the system makes can be explained and audited. We're also proud that the geography and capability fixes came from actually measuring our own false-positive rates against the data rather than guessing at thresholds. And the raw-versus-adjusted toggle communicates the core idea in about one second, which matters for getting non-technical planners to trust and use it.
What we learned
The detection logic was never the hard part. The hard part was resisting the urge to let any single noisy signal decide something on its own. An extracted text field exploded into thousands of fake categories, a centroid distance flagged one in five real clinics, and a date check accused a 360-year-old hospital of not existing. Every one of those pushed us toward the same answer: be conservative about what gets to condemn a facility, prefer reviewed deterministic rules over inference, and keep an evidence trail for everything.
The deployment drift taught us a related lesson the hard way. A pipeline that quietly produces partial results is more dangerous than one that fails loudly, so we added a completeness guard that turns a half-rendered map into an outright build failure instead of a silent omission.
What's next for Phantom Census
The immediate next step is finishing the entity-resolution and trust-grade work so the per-facility view shows planners every directory snapshot behind an entity and how much to trust each capability claim. Beyond that, we want to define the pipeline and notebooks as code in a Databricks Asset Bundle so the deployed workspace can't drift from the repository again. Longer term, the same approach extends past healthcare to any domain where official registries can quietly overstate what exists on the ground, including schools, water points, and infrastructure.
Built With
- amazon-web-services
- databricks-apps
- express.js
- folium
- geoboundaries
- geopandas
- github
- javascript
- lakebase
- numpy
- pandas
- pyarrow
- python
- react
- sql
- sqlalchemy
- typescript
- uc
Log in or sign up for Devpost to join the conversation.