Inspiration
I kept thinking about Emergency Department (ED) discharges that look routine on paper and aren't. I built SilentCatch around two of them.
The first was a Computed Tomography (CT) scan, which is a detailed X-ray scan, done to rule out a rib fracture. The radiologist noted a 7.2 millimeter (mm) nodule in the Right Upper Lobe (RUL), meaning the upper part of the right lung, on image slice number 42. That note never became a follow-up order in the discharge packet. The patient came back two years later with Stage IV cancer.
The second was about creatinine, a waste marker in blood that shows how the kidneys are doing. It went from 0.62 to 0.88 to 1.16 milligrams per deciliter (mg/dL, a lab concentration unit) in 36 hours. That value stayed inside the printed normal range, so it looked fine. But it was an 87% rise. A painkiller called Ketorolac was prescribed on top of it, which risks Acute Tubular Necrosis (ATN), meaning damage to the small tubes in the kidney.
Normal Electronic Health Record (EHR) warnings, meaning the hospital's digital chart system, miss both cases because they check one thing at a time. I wanted something that reads the scan, the labs, and the draft discharge packet together, right before sign-off.
What it does
SilentCatch sits in front of the Sign & Discharge Patient click. When I run an encounter like ED-104, which is one of my test cases, it pulls the radiology key image and report, the 48-hour lab series, and the draft discharge summary with prescriptions, which I refer to as Rx.
If the packet is clean, it stays quiet and clears. If it finds a contradiction across those sources, it blocks sign-off and shows me why. It draws a red line from the warning card back to the exact quote in the chart, attaches the medical guideline it used, and lets me apply the fix. I click "Apply patch & re-verify" and it checks the corrected packet again in place. When the second run is clean, it unlocks sign-off. Everything runs on 12 synthetic test cases I wrote myself. Synthetic means made-up, not from real patients. No real patient data has touched it.
How we built it
I built a 4-stage pipeline, meaning data passes through four steps in order, with a strict rule-based check at the end.
Stage 1 is vision. NVIDIA Nemotron 3.5 Super Vision-Language (VL) 120B, where VL means it can read images plus text and 120B means 120 billion parameters, reads the key slice image, which is a Portable Network Graphics (PNG) file, plus the full report and pulls out measurements and the Region of Interest (ROI), meaning the area to focus on.
Stage 2 is a triage gate on Nemotron 3 Nano 30B, a smaller and faster model. It clears easy cases quickly, but I overrode it with fixed rules: anything 4 mm or larger, or any lab jump 25% or more, always escalates no matter what the model says.
Stage 3 is evidence. Nano writes 1 to 2 search queries naming the guideline group, like Fleischner Society for lung nodules, or Kidney Disease: Improving Global Outcomes (KDIGO) for kidney injury, or the Food and Drug Administration (FDA), the US drug regulator. My code scrubs Protected Health Information (PHI), meaning private patient details like names, Medical Record Numbers (MRN), and Dates of Birth (DOB), then searches through Tavily, a search Application Programming Interface (API), meaning a service my code can call. It only allows results from eight trusted medical sources.
Stage 4 is Nemotron 3 Ultra 550B doing the final reasoning and proposing the fix. After that, nothing shows up without passing my span-guard check. It requires an exact match for every quote, calculates the character positions for highlighting in the screen, and checks that the required Current Procedural Terminology (CPT) code, meaning the billing code for a procedure like 71250 for a chest CT, or drug is actually missing. One automatic retry if it fails, then it blocks.
The backend, meaning the server side, is FastAPI, a Python web framework, streaming updates over Server-Sent Events (SSE), a way for the server to send live updates to the browser. The frontend, meaning what you see, is Next.js 14 at /console with three columns: scan viewer, lab trend with Ordinary Least Squares (OLS) math, which is basic line-fitting to see the direction of labs, plus KDIGO kidney bands, and the discharge editor. I ship both from one Docker image, where Docker is a tool to package the app so it runs the same everywhere. make judge-demo runs the demo offline with no keys needed.
Challenges we ran into
Strict output was the most annoying part. I had no schema-enforced JSON mode, where JSON means JavaScript Object Notation, the structured format code uses to exchange data, so getting clean structured output meant writing a parser that strips formatting plus one retry carrying the error back. That whole module could have been deleted with proper schema support.
Model names drifted from the docs, so on startup I now resolve exact IDs from the model list API and fall back to hardcoded defaults. Latency hurt too. My one live test of ED-104 against Nebius, the cloud Graphics Processing Unit (GPU) host, where GPU means the chips that run Artificial Intelligence (AI) models, took about 25 seconds end to end against a 20 second timeout. It works for a pre-signoff check with a progress bar. It would never work inline.
The other hard part was honesty in the screen. I had to make sure test-mode numbers could never be mistaken for real model accuracy or live speed. The benchmarks tab now shows the run mode, the score source, and per-case history explicitly, and a would-be pass with failed live inference reports as INCOMPLETE and blocks discharge.
Accomplishments that we're proud of
I got the layered setup to match Ultra-only routing on my test cases at 44% lower estimated cost, about $0.90 per 1,000 discharges, because Nano clears the easy cases before Ultra runs. 12 out of 12 fixture checks, 100% catch rate, 100% precision, 0 false alarms in replay mode. Those are wiring checks on my own test data, not a clinical study, but they mean the routing and verification hold. I am also proud the fix loop actually works. Warning to patched to VERIFIED_SAFE in one click, with the packet updating and re-checking clean. And the test base: 67 backend tests pass, plus a Playwright automated browser suite, where Playwright is a tool that controls a browser to test clicks like a user. The whole demo runs with one Docker command and works offline.
What we learned
I learned to put fixed math first and models last. The OLS lab trend, the 4 mm / 25% rules, and the exact-quote verifier catch things I would never trust a model to enforce alone. I learned that token usage in every response is load-bearing. My $0.05 per discharge and $2.00 per day spend limits depend on it. Without that, the cost tracker does not work. And I learned that for clinical tools, labeling matters as much as accuracy. Saying "blocked," "verified," "replayed," "not validated" in plain words kept me from overselling what 12 made-up cases can prove.
What's next for SilentCatch
The image I already build supports two roles. Live checking for the sign-off block, and a nightly re-check where every signed discharge gets reviewed again and the scored JavaScript Object Notation (JSON) file lands for review. The entry point is jobs/retrospective.sh --live, split by case across scheduled jobs. I want to run that on a schedule and study the misses. After that I want to grow beyond 12 test cases, bring down Ultra wait time, add a real load signal so backup routing is not just timeout-based, and get the PHI scrubber reviewed properly before anyone even thinks about real data. It stays a prototype until it earns anything else.
Log in or sign up for Devpost to join the conversation.