feat: mobile polish, UI upgrades, contact cards, Iris improvements, contact verification#50
Merged
Conversation
Built after a string of incidents this sprint that all had one thing in common: nobody knew something was wrong until it was too late. Real incidents that triggered this build: API & Deployment - Jun 16 10:02 PM — backend went down after PR #48 changed the Railway start command. Nobody knew for 4+ hours. Found at 2 AM by manually checking. This dashboard shows a pulsing red "API Down since 10:02 PM" pill the moment it happens. - Joel's revert commit showed "Deployment successful" in Railway but the API was still returning 502. The agents service was running the cron worker (tsx src/index.ts) instead of the FastAPI server. Dashboard would have shown the API still down despite Railway saying online. Signal Quality - Test signal "Ignore Me 4" appeared in production for days undetected. Dashboard flags any signal matching /ignore|test signal/ instantly. - Signal titles containing raw HTML tags slipped through. Dashboard scans all signals for /<[^>]+>/ and surfaces the count. - Signals attributed to wrong hospitals — only caught by manually comparing signal hospital_id vs expected. Now visible per hospital. Contact Health - Jefferson Health showed 0 contacts — only found by hitting the API directly. Hospital Coverage panel flags any hospital with 0 contacts. - LinkedIn post URLs (/posts/) were saved as profile URLs. Michael's pipeline was generating slugs by guessing from names. Contact Health panel counts broken LinkedIn URLs per hospital. - AI error messages saved as contact names — scraper failed silently and stored the error text. Dashboard surfaces total broken contacts. - Joseph Impicciche still showing as Ascension CEO months after retiring Dec 31 2025. Dashboard + LinkedIn verifier (PR #47) address this. - Prior employer empty for all contacts — Danielle's original May 20 request that went unnoticed for weeks. Coordination - "I fixed it locally" — Michael tested on his machine but not on prod. No way to verify without manually curling the API after every push. Dashboard auto-refreshes every 30s and shows live production state. - Joel, Michael, and Juan were pinging each other constantly: "Did it merge?" "Is it live?" "Did the contacts update?" Dashboard answers all of these without anyone having to ask. What the dashboard does: - API Health — live ping every 30s, shows exact time it went down - Total Signals + Pending Review — from /status - Hospital Coverage — signal count per hospital, flags < 3 as warning, 0 as error - Contact Health — contacts + broken LinkedIn URLs per hospital - Signal Quality — HTML titles, test signals, null LinkedIn, post URLs - Open GitHub Issues — live from GitHub API, grouped by assignee (Joel / Michael / Juan) - Audit Log — timestamped session events (API up/down transitions) - Dark/light mode toggle — persisted to localStorage Admin only — shown in sidebar nav only when isAdmin = true. Reads entirely from existing API endpoints, no new backend work needed.
This directly answers the 15-tabs problem from the pitch.
Before: Danielle hit the review queue and saw a title + AI summary
with no way to verify — one source, no context, blind approval.
After: every signal card now shows the original article link so she
can read the actual story. And with one click on "More coverage" she
gets 3–5 articles on the same story pulled live from Becker's, Modern
Healthcare, Fierce Healthcare, Healthcare Finance News, and the broader
web — all in the same card, no tab-hopping.
The whole pitch is built around replacing 15 tabs with one place.
This is that promise made real inside the review queue. She doesn't
just approve a summary — she reads the story, cross-references coverage
from multiple publishers, and makes a real call. Human judgment with
AI doing the legwork.
New API route: /api/signals/coverage
- POST { title, hospital_name }
- 3 parallel Serper searches across healthcare publications + general web
- Deduplicates by URL, returns up to 5 results with source domain + snippet
Joel — this is on feat/mobile-polish. Merge PR #50 when ready.
Danielle's review queue now visually separates real signals from noise: - Real signals (green border) appear at the top with Approve, More coverage, Dismiss - Noise signals (grayed out) are pushed to the bottom with an "Irrelevant — dismiss all" separator - "Dismiss all" button wipes all noise in one click - Each noise card has a "Move to relevant" button in case the AI mislabeled a good article --- Joel — backend bug to fix before demo: Some signals are showing up with a 75% confidence score AND tagged as filtered_out at the same time. These contradict each other. A 75% score means the AI thinks the article is relevant — filtered_out means it flagged it as noise. The pipeline is sending both on the same article. Example: https://www.fiercehealthcare.com/providers/ftc-clears-ascensions-39b-amsurg-purchase-requires-some-asc-divestitures This is a real Ascension M&A story but lands in noise because filtered_out overrides the score on our end. Fix: if signal_type or tier is filtered_out, confidence score should be below 30% so it gets skipped at ingest and never reaches the review queue. Danielle has "Move to relevant" to rescue mislabeled articles for now but the root cause needs to be fixed in the pipeline.
…g wrong field name causing 422
…ch from dashboard, no terminal needed
Signals tagged filtered_out (tier or signal_type) now excluded before display and category filtering — garbage never reaches the feed.
When the AI search doesn't find a LinkedIn URL, show a paste field so the reviewer can add it before approving. Approve sends it as an override to the PATCH endpoint so it's saved with linkedin_verified: true.
DigestTracker now captures ae_id from URL params (?utm_source=digest&ae_id=xxx)
instead of using a hardcoded ID. Views stored in Supabase digest_views table
and forwarded to backend endpoint. DigestRoster shows Danielle each AE's
last digest open time on the home page admin view.
Requires Joel to:
1. CREATE TABLE digest_views (ae_id uuid PRIMARY KEY, digest_id text, viewed_at timestamptz DEFAULT now());
2. Append ?utm_source=digest&digest_id={id}&ae_id={ae_id} to all digest links
Owner
|
Updates approved |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
UI Improvements
Iris Chat
Dev Dashboard
Contact Restore
Test plan
🤖 Generated with Claude Code