Developer Spec

DW

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:

  1. 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.
  2. 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.
  3. Results become actions. Positives, dilutes, first-ever positives, diagnosis mismatches, and late reports fire alerts so counselors know what to do next.
  4. 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

RoleSeesCan do
Admin (Executive Director)Everything incl. billing, integrations, usersConfigure org, connectors, roles; all clinical actions
Clinical SupervisorAll clients, all programs, all alertsCreate orders, review reports, ack/resolve alerts, run randomizer, manage alert rules
CounselorOwn caseload onlyCreate orders for own clients, review own reports, ack alerts on own clients
Front DeskCheck-in, scheduling; no resultsRun kiosk check-in, mark specimens collected, print requisitions
BillingOrders and invoices; no clinical detailExport 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):

  1. Hero banner (navy): greeting, count of open alerts, "Rising this month" analyte strip, and the primary Create Drug Test button.
  2. Summary tiles (4): Tests this month (with delta), Positivity rate, Active alerts (critical count), Avg turnaround (delta). Each tile links to its section.
  3. Drug of Choice (large card): horizontal bar chart of positivity by analyte, last 30 days, high-risk analytes in red. Links to /analytics.
  4. Positivity trend (6 months) and Co-occurrence (which analytes appear together) side by side.
  5. 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:

  1. Filter: accepts payer type; some panel covers every analyte of concern; meets max TAT if set; if analogs requested, panel must include analogs.
  2. 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%.
  3. 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:

TypeSeverityFires when
high-risk-drugCriticalPositive for FEN, XYL, NIT, or any analyte on the program's high-risk list
first-positiveHighPositive for an analyte not in patient.history.knownPositives
diagnosis-mismatchHighPositive analyte class not consistent with the client's ICD-10 diagnoses (e.g. F10.20 alcohol-only client positive for opioids)
dilute-specimenMediumvalidity.dilute true (creatinine < 20 mg/dL and SG < 1.003)
profile-inconsistentMediumPrescribed medication expected (e.g. BUP for MAT client) but result negative, or vice versa
outstanding-reportMediumnow > order.expectedBy + 12h and no report
Missed random windowMedium (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=true and status=scheduled.

Self check-in — Proposal. The roadmap marks this "important if feasible" with no defined workflow. Proposed v1:

  1. Client is notified (SMS/app push) at draw time with the collection window. No PHI in the message.
  2. Client arrives; front-desk kiosk shows a rotating QR. Client scans, or staff enters the 6-digit code from the client's message.
  3. Identity confirmation: last name + DOB (v1). Optional v2: photo match to EHR photo, staff attestation.
  4. The pre-created Random order moves to collected with 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

SystemDirectionMethod (prototype assumption)Notes
Emerald National Laboratory (LIMSABC)Orders out, results inHL7 v2 ORM/ORU over VPNReference connector; historical pain points should be documented by the team before rebuild
Precision Tox Partners (Orchard Harvest)Orders out, results inREST API + results webhook
ClearWater Clinical LabsResults inManual PDF uploadTAT measured at upload; see 8.2
Riverbend EHR (Kipu)Demographics + diagnoses in; results/documents outFHIR R4 Patient, Condition, DiagnosticReportDiagnoses feed the mismatch rule
Other EHRs (TheraNest, SimplePractice, Netsmart)SameConnector per system, reused across agencies
DrugTrends.usSignals inInternal APIRegional/national/emerging signals with analyte codes
NotificationsOutEmail (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 lastVerifiedAt date 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.

  1. 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.ts are a starting point.
  2. 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.
  3. 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.
  4. 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.
  5. Counselor scope. Prototype assumes counselors see only their own caseload. Confirm whether program-level visibility is needed for coverage when a counselor is out.
  6. What is public on DrugTrends.us. Prototype: regional/national/emerging signals public; program-vs-benchmark members-only. Confirm.
  7. Grant-funded payer handling. Treated like cash-pay for lab matching (price-weighted). Confirm whether grant contracts dictate specific labs.
  8. Randomizer fairness rules. "No client exempt more than two consecutive draws" is the prototype's rule. Confirm with court/program contracts.
  9. Digest recipients and timing. 07:00 local, supervisors get all programs, counselors get caseload. Confirm.
  10. 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.