LabSwish Counseling Agency Hub — Functional Specification
Version: 1.0 (prototype handoff) Source: "LabSwish.com — Revised Product Roadmap" (September 2, 2026) plus brand guide Prototype: This repo. Every screen described here is clickable with realistic mock data. Nothing is wired to a backend.
Read this document alongside the running prototype. Where the spec says "see /orders", open that route and click around. Where a screen is marked Proposal, it is our suggested design, not a decided requirement.
1. Product thesis
LabSwish is the single hub through which counseling agencies (SUD/behavioral-health programs) order, track, and act on drug testing. The hub sits between the agency and the laboratories:
- Ordering is the front door. The Create/Schedule Drug Test flow is the primary action on every screen. Everything else (reports, alerts, analytics) exists because orders flowed through the hub.
- The hub is lab-neutral. It compares labs on price, turnaround time, panel coverage, and payer acceptance, then routes the order to whichever lab fits. LabSwish curates the lab catalog.
- Results become actions. Positives, dilutes, first-ever positives, diagnosis mismatches, and late reports fire alerts so counselors know what to do next.
- Volume unlocks benefits. Because member agencies route testing through the hub, LabSwish negotiates supply, EHR, and coding discounts on their behalf.
Design language from the brand guide: "Apple meets Mayo Clinic." Deep Navy, Bright Blue, Teal; Poppins headings, Inter body; light mode only.
2. Users and roles
| Role | Sees | Can do |
|---|---|---|
| Admin (Executive Director) | Everything incl. billing, integrations, users | Configure org, connectors, roles; all clinical actions |
| Clinical Supervisor | All clients, all programs, all alerts | Create orders, review reports, ack/resolve alerts, run randomizer, manage alert rules |
| Counselor | Own caseload only | Create orders for own clients, review own reports, ack alerts on own clients |
| Front Desk | Check-in, scheduling; no results | Run kiosk check-in, mark specimens collected, print requisitions |
| Billing | Orders and invoices; no clinical detail | Export order/cost data, view lab pricing |
Patient / client is not a hub user. Their only surface is the self check-in kiosk (Section 5.7), which stores no PHI on the device.
Role scoping is implemented in the prototype as static labels on /settings (Users & roles tab). Developers must enforce this server-side on every query.
3. Information architecture
Nine sections, grouped in the sidebar:
Daily work
/ Dashboard
/orders Orders
/reports Reports
/alerts Alerts
/analytics Program Analytics (+ /analytics/turnaround)
/randomizer Randomizer
Services
/drug-intelligence Drug Intelligence
/clia-waived CLIA-Waived / UA Revenue
/member-benefits Member Benefits
Administration
/settings Settings / Integrations
/spec Developer Spec (this document)
Outside the shell
/check-in Patient-facing self check-in kiosk (Proposal)
Global header (every page): page title, global search (client, accession, order), Create Drug Test primary button, alerts bell with count, user avatar.
Priority from the roadmap: everything under Daily work plus lab selection is Must Have. The Services group is lighter-weight in v1.
4. Data model
Types live in lib/types.ts and are the reference schema. Mock instances live in lib/mock-data.ts. Summary of entities and the relationships that matter:
Organization ─┬─ Program[] (string list; unit for analytics, pools, access scoping)
├─ OrgUser[] (role-scoped)
├─ Integration[] (lab + EHR connectors)
└─ Patient[]
└─ Order[] ──1:0..1── Report
└─ Alert[] (also from Order for late reports)
Lab ─ LabPanel[] (analyte list, cash price, insured estimate, analog coverage)
RandomizerPool ─ RandomDraw[] (seeded, auditable; each draw pre-creates Orders with isRandom=true)
TrendSignal (from DrugTrends.us; optional programComparison)
CupProduct, MemberBenefit (services catalog)
4.1 Patient
id, clientCode, firstName, lastName, dob, sex, program, counselor, payer (insured | cash-pay | grant-funded), diagnoses[] (ICD-10), history { totalTests, consecutiveNegatives, knownPositives[] }
history.knownPositives drives the first-positive alert rule. diagnoses drives diagnosis-mismatch. Both are derived and should be maintained by the results pipeline, not edited by hand.
4.2 Order
id, accession?, patientId, labId, panelId, testingNeed, analytesOfConcern[], payer, status, createdAt, scheduledFor?, collectedAt?, expectedBy?, orderedBy, notes?, isRandom
Status lifecycle:
scheduled → collected → in-transit → at-lab → (Report received)
↘ delayed (now > expectedBy and no report)
any → cancelled
expectedBy = collectedAt + lab.expectedTatHours. The "delayed" transition is a scheduled job, not a user action.
4.3 Report
id, orderId, accession, patientId, labId, panelId, collectedAt, reportedAt, tatHours, flags[], positives[], validity { creatinine, specificGravity, ph, dilute }, analytes[] { analyte, code, result POS|NEG|ND, value, cutoff, units }, reviewedBy?, reviewedAt?, pdfUrl
reportedAt is when the result arrived in LabSwish (HL7/API message time, or PDF upload time). tatHours = reportedAt - collectedAt. See Open Decision 8.2 on fairness for manual-upload labs.
4.4 Alert
id, type, severity, status (open | acknowledged | resolved), patientId, reportId?, orderId?, title, detail, ruleExplanation, createdAt, acknowledgedBy?, acknowledgedAt?
ruleExplanation is required and is shown to the user verbatim ("Why this fired"). Every rule must produce one.
4.5 Lab and LabPanel
Lab: id, name, shortName, location, certifications[], expectedTatHours, observedTatHours, reportsThisMonth, acceptsInsurance, inNetworkPayers[], cashPayFriendly, panels[], cutoffNotes, integration { status live|pending|manual, method HL7|API|SFTP|Portal upload, lastSyncAt }, lastVerifiedAt
observedTatHours is computed per-organization from that org's reports (rolling 30 days). expectedTatHours is the lab's published claim. Show both.
4.6 Analytics rollups
DrugStat (per analyte: positives, tests, rate, trend, highRisk), CoOccurrence (analyte pairs), DailyTat, monthly positivity, breakdowns by program and age band. In production these are materialized views or a nightly job, not live aggregation over reports.
5. Screens and flows
5.1 Dashboard (/)
Purpose: answer "what needs my attention today" and make ordering one click away.
Layout (top to bottom):
- Hero banner (navy): greeting, count of open alerts, "Rising this month" analyte strip, and the primary Create Drug Test button.
- Summary tiles (4): Tests this month (with delta), Positivity rate, Active alerts (critical count), Avg turnaround (delta). Each tile links to its section.
- Drug of Choice (large card): horizontal bar chart of positivity by analyte, last 30 days, high-risk analytes in red. Links to
/analytics. - Positivity trend (6 months) and Co-occurrence (which analytes appear together) side by side.
- Right column: Alerts needing action (top 4 open, by severity), Outstanding reports (delayed first), Recent reports.
Rules: demographics do not appear on the dashboard (they live in Analytics). Numbers are for the user's scope (counselor sees own caseload).
5.2 Create / Schedule Drug Test (dialog, any page)
The front door. Three steps in a modal:
Step 1 — Who and why
- Client (searchable select; shows client code and program)
- Payer type: Insured / Cash-pay / Grant-funded (toggle group; pre-filled from client, editable)
- Testing need (select): Standard panel · Large fentanyl/adulterant panel · Specific drug of concern · Confirmation of a presumptive positive · Compliance monitoring
- Collection: Today (walk-in) or Schedule for a date
Step 2 — Specific concerns
- Multi-select chips of analytes (fentanyl analogs, xylazine, nitazenes, medetomidine, gabapentin, kratom, etc.). High-risk chips styled red.
- Optional max turnaround (e.g. "need result within 48h" for court dates)
- Free-text note to lab
Step 3 — Choose a lab (Lab Selection Tool)
- Ranked list of labs. Each card shows: recommended badge (top eligible), fit score, cash price or insured estimate, expected vs observed TAT, panel name and analyte count, certifications, integration status, and plain-English reasons ("Lowest cash price for this panel", "Covers fentanyl analogs").
- Ineligible labs are shown greyed with the reason ("Does not bill insurance", "Panel does not include XYL"). Never silently hide a lab.
- Selecting a lab and confirming creates the Order and prints/downloads the requisition.
Matching logic (v1) is in lib/lab-match.ts:
- Filter: accepts payer type; some panel covers every analyte of concern; meets max TAT if set; if analogs requested, panel must include analogs.
- Score 0–100 = weighted blend of price fit, TAT fit (observed), coverage, need fit, payer fit. Cash-pay weights price at 40%; insured weights TAT 30% and coverage 25%.
- Top eligible = Recommended.
Weights are a starting point for the team to tune, not a requirement. See Open Decision 8.1.
5.3 Orders (/orders)
Purpose: everything ordered but not yet resulted.
- Filter tabs: All · Scheduled · Pending (collected/in-transit/at-lab) · Delayed. Lab filter select. Search.
- Table columns: Client (name, code, program), Lab, Panel, Status badge, Collected, Expected by (red if past), Random badge if
isRandom. - Row click opens Order detail sheet: timeline of status changes, lab and panel detail, analytes of concern, payer, notes, requisition download, Cancel, and "Nudge lab" for delayed orders (sends templated message to lab contact; logs it).
- Delayed orders appear first in the default sort.
5.4 Reports (/reports)
Purpose: completed results, review, and the daily digest.
- Filter tabs: All · Unreviewed · Positive · Dilute. Program filter. Search by client or accession.
- Table: Client, Collected → Reported (TAT), Lab, Result summary (positive analyte chips or "Negative"), validity flags (Dilute / Invalid / Inconsistent), Reviewed status.
- Row click opens Report viewer (sheet): header with client, lab, accession, TAT; validity block (creatinine, SG, pH with normal ranges); full analyte table with POS/NEG, value vs cutoff; linked alerts; original PDF; Mark reviewed (records reviewer and time).
- Preview daily digest button shows the email supervisors and counselors receive: new results, open alerts, delayed reports, scoped to the recipient's role.
5.5 Alerts (/alerts)
Purpose: turn results into actions.
Inbox layout: filter tabs (Open · Acknowledged · Resolved · All), severity chips, list on the left, detail on the right.
Detail shows: severity, type, client, linked report/order, Why this fired (the ruleExplanation), timestamps, and actions: Acknowledge, Resolve, Open report, View client analytics, Create follow-up test.
Alert rules (v1) — configurable on/off in Settings:
| Type | Severity | Fires when |
|---|---|---|
high-risk-drug | Critical | Positive for FEN, XYL, NIT, or any analyte on the program's high-risk list |
first-positive | High | Positive for an analyte not in patient.history.knownPositives |
diagnosis-mismatch | High | Positive analyte class not consistent with the client's ICD-10 diagnoses (e.g. F10.20 alcohol-only client positive for opioids) |
dilute-specimen | Medium | validity.dilute true (creatinine < 20 mg/dL and SG < 1.003) |
profile-inconsistent | Medium | Prescribed medication expected (e.g. BUP for MAT client) but result negative, or vice versa |
outstanding-report | Medium | now > order.expectedBy + 12h and no report |
| Missed random window | Medium (off by default) | Client drawn by randomizer did not check in inside the window |
Critical alerts notify immediately (in-app + email/SMS per user prefs). Others batch into the daily digest.
5.6 Program Analytics (/analytics, /analytics/turnaround)
Purpose: "what is actually happening inside your population." Analytics are about actions, so every view answers a question.
Tabs:
- Drug of Choice: positivity by analyte table (positives, tested, rate, trend); by program (stacked bars); by age band. High-risk analytes flagged.
- Positivity trends: monthly line, overall and per analyte.
- Combinations: co-occurrence pairs (e.g. FEN + XYL), count and share.
- First-positive events: clients newly positive for an analyte this period (list; feeds the alert).
- Monthly summary: exportable board/funder report.
Program filter and Export (CSV/PDF) on every tab.
Turnaround time (/analytics/turnaround): average TAT trend (daily), per-lab expected vs observed, delayed count, and the on-time rate. Links to delayed orders. TAT definition is spelled out on the page.
5.7 Randomizer (/randomizer) and Self Check-In (/check-in)
Randomizer is the existing product brought into the hub as a core section.
- Pools: name, program, member count, cadence (weekly/biweekly/monthly), draw size, next draw, active toggle, Draw now.
- Recent draws: who was selected, who has checked in, window remaining, and the seed (so any draw can be reproduced for court/audit).
- Fairness rules displayed: equal probability per draw; no client exempt more than two consecutive draws; staff cannot see the next draw before it runs.
- Each draw pre-creates Orders with
isRandom=trueandstatus=scheduled.
Self check-in — Proposal. The roadmap marks this "important if feasible" with no defined workflow. Proposed v1:
- Client is notified (SMS/app push) at draw time with the collection window. No PHI in the message.
- Client arrives; front-desk kiosk shows a rotating QR. Client scans, or staff enters the 6-digit code from the client's message.
- Identity confirmation: last name + DOB (v1). Optional v2: photo match to EHR photo, staff attestation.
- The pre-created Random order moves to
collectedwith timestamp; requisition prints. Missed windows generate the no-show alert.
The kiosk at /check-in is a full-screen, navy, large-touch-target page outside the app shell. Nothing is stored on the device. Identity-verification depth is Open Decision 8.4.
5.8 Drug Intelligence (/drug-intelligence)
Purpose: "what is coming toward your program," powered by DrugTrends.us.
- Feed of signals filtered by scope: Regional · National · Emerging. Each card: scope, region, source, date, title, summary, direction (rising/stable/falling), analyte chip.
- When the analyte is on the agency's panels, show Your program vs benchmark bars with the delta.
- When it is not, say so and point to the ordering flow ("add to specific-concern list; lab comparison will surface labs that cover it").
- Sidebar: Rising in your program (own positivity deltas) and Coverage gaps (regional substances your panels miss).
- Regional and national signals are public on DrugTrends.us (brand awareness). Program-vs-benchmark is members-only.
5.9 CLIA-Waived / UA Revenue (/clia-waived)
Purpose: help agencies do point-of-care screening in-house and see the revenue case.
- Cups at member pricing: catalog cards (panel count, box size, adulterant strip, member vs list price), Add box.
- UA revenue calculator: cups/month, avg reimbursement (CPT 80305), percent billable → cup cost, gross, net/month, annualized.
- Setup checklist: CLIA Certificate of Waiver, SOP and competency log, CPT 80305 billing, confirmations through the hub. Progress indicator. Disclaimer: not legal/billing advice.
Presumptive positives from cups should flow back into Create Drug Test as testingNeed = confirmation-only.
5.10 Member Benefits (/member-benefits)
Cards per benefit: vendor, category (cups, EHR, coding, CLIA support), headline, detail, savings, status (Active / Coming soon), CTA (Open or Notify me). Explainer: membership is free for agencies routing definitive testing through the hub; volume earns the discounts. Partner names are placeholders until contracts exist.
5.11 Settings / Integrations (/settings)
Tabs:
- Integrations: Laboratory connections and EHR connections tables (system, method, status, last sync, Sync now / Set up), Request connector. Each connector is built once and reused across member agencies.
- Users & roles: table with role, access scope, last active, status; Invite user.
- Alert rules: toggle each rule; Notifications (daily digest on/off, send time; critical always immediate).
- Organization: agency name, region (drives regional Drug Intelligence), programs list.
6. Integrations
| System | Direction | Method (prototype assumption) | Notes |
|---|---|---|---|
| Emerald National Laboratory (LIMSABC) | Orders out, results in | HL7 v2 ORM/ORU over VPN | Reference connector; historical pain points should be documented by the team before rebuild |
| Precision Tox Partners (Orchard Harvest) | Orders out, results in | REST API + results webhook | |
| ClearWater Clinical Labs | Results in | Manual PDF upload | TAT measured at upload; see 8.2 |
| Riverbend EHR (Kipu) | Demographics + diagnoses in; results/documents out | FHIR R4 Patient, Condition, DiagnosticReport | Diagnoses feed the mismatch rule |
| Other EHRs (TheraNest, SimplePractice, Netsmart) | Same | Connector per system, reused across agencies | |
| DrugTrends.us | Signals in | Internal API | Regional/national/emerging signals with analyte codes |
| Notifications | Out | Email (digest, critical), SMS (check-in codes, critical) | No PHI in SMS |
Connector principle: build once, reuse across agencies. A connector is a LabSwish asset, not an agency-level project.
7. Non-functional requirements
- PHI / HIPAA. All PHI encrypted at rest and in transit. Audit log on every read and write of patient, order, report, alert (who, when, what). Role scoping enforced server-side on every query. Kiosk stores nothing locally. SMS messages carry no PHI.
- Auditability. Randomizer draws are seeded and reproducible. Alert acknowledgements record user and time. Report reviews record user and time.
- Performance. Dashboard and analytics read from pre-aggregated rollups (nightly plus incremental on new report). Result ingest to alert should be under 1 minute.
- Lab catalog curation. Lab pricing, panels, and TAT claims are curated by LabSwish with a
lastVerifiedAtdate shown to users. Review cadence is an open decision. - Accessibility. WCAG 2.1 AA. All status conveyed by color also has text. Kiosk has 44px+ touch targets.
- Browser support. Evergreen desktop browsers; tablet for kiosk.
8. Open decisions
These are the things the roadmap left open or that surfaced while building. Each has a prototype default so you can react to something concrete.
- Lab recommendation vs filter-only. The prototype shows a ranked list with a single Recommended pick and shows ineligible labs greyed with reasons. Alternative: filter only, no recommendation, to stay visibly neutral. Weights in
lib/lab-match.tsare a starting point. - TAT fairness for manual-upload labs. TAT ends at
reportedAt= arrival in LabSwish. For portal-only labs that is upload time, which penalizes them for staff delay. Options: (a) accept and disclose (prototype default; the TAT page says so), (b) let staff enter the lab's report date on upload, (c) exclude manual labs from TAT comparisons. - Diagnosis-mismatch rule depth. v1 uses coarse ICD-10 class → analyte class mapping. Needs clinical review to define the mapping table and avoid noisy alerts.
- Self check-in identity verification. Proposal uses last name + DOB. Options with more assurance (photo match, staff attestation, ID scan) add friction and hardware. Decide the v1 bar with compliance.
- Counselor scope. Prototype assumes counselors see only their own caseload. Confirm whether program-level visibility is needed for coverage when a counselor is out.
- What is public on DrugTrends.us. Prototype: regional/national/emerging signals public; program-vs-benchmark members-only. Confirm.
- Grant-funded payer handling. Treated like cash-pay for lab matching (price-weighted). Confirm whether grant contracts dictate specific labs.
- Randomizer fairness rules. "No client exempt more than two consecutive draws" is the prototype's rule. Confirm with court/program contracts.
- Digest recipients and timing. 07:00 local, supervisors get all programs, counselors get caseload. Confirm.
- Lab catalog review cadence. Monthly? Quarterly? Who owns it?
9. Out of scope for v1
- Patient portal beyond kiosk check-in
- Billing/claims submission (coding review is a member benefit, not a hub feature)
- Prescription monitoring (PDMP) integration
- Mobile native apps
- Dark mode (brand guide is light-only)
10. Repo map for developers
app/(app)/ Routes inside the app shell (one folder per section)
app/check-in/ Kiosk page outside the shell
components/shell/ Sidebar, header, page wrapper, ProposalNote
components/orders/ Create Drug Test dialog, orders table, order detail sheet
components/reports/ Reports table, report viewer, digest preview
components/alerts/ Alerts inbox
components/dashboard/ Summary tiles, drug modules, work modules
components/analytics/ Program analytics, turnaround analytics
components/randomizer/ Randomizer, check-in kiosk
components/intelligence/ Drug Intelligence
components/clia/ CLIA-Waived / UA Revenue
components/benefits/ Member Benefits
components/settings/ Settings tabs
components/shared/ Status badges, analyte chips
components/ui/ shadcn/ui primitives (Base UI)
lib/types.ts Reference data model
lib/mock-data.ts Realistic sample data (fictional agency: Riverbend Recovery Services)
lib/lab-match.ts Lab Selection Tool logic
lib/format.ts Date/number helpers
Stack: Next.js 16 (App Router), React 19, Tailwind v4, shadcn/ui on Base UI, Recharts. All state is client-side mock; there is no API layer. Recommended production stack: Neon Postgres + Better Auth (or the agency's SSO), server actions or route handlers per section, nightly rollup job for analytics.