User Experience
The ZOL Intelligent Search presents two distinct user-experience surfaces. The patient-facing chatbot at /chat (and its admin-search variant /search) is the primary surface that 100 K monthly visitors interact with. The administrative surface — the Knowledge Base wizard at /knowledge-base, the Operations and Feedback dashboards, and the Value Dashboard — is what hospital staff use to keep that chatbot running. This page documents both, mapped one-to-one against the running frontend code so an examiner who opens the system sees the same labels and routes documented here.
Patient-Facing Chat (/chat)
The patient-facing entry point is PublicChatPage, deliberately named "public" because it requires no authentication: a hospital visitor lands on it directly from the website. The interface is a single-column conversation view designed for the diverse user base — from tech-savyy young adults to elderly patients who may be less comfortable with technology.
The page composes six interactive React components:
| Component | Purpose |
|---|---|
MessageList + StreamingResponse | Renders the conversation, including streamed-token rendering during generation. |
SkeletonLoader | Shows the five-stage Dutch progress labels while the backend works (no debug badges — see "Why no debug badges on /chat" below). |
SourcesList | Renders citations as clickable cards under each assistant turn. |
FollowUpChips | Three context-aware follow-up suggestions appended after each answer. |
ClarificationCards | Department selection cards when the query is classified as AMBIGUOUS_SYMPTOM_DESCRIPTION and ≥3 departments handle it (see Clarifying Questions). |
AIDisclaimer + SessionFeedbackPrompt + NegativeFeedbackChips | Mandatory medical-advice disclaimer plus a session-level "Was this useful?" prompt with negative-feedback follow-up. |
WebSocket transport is handled by the usePublicWebSocket hook, which generates a UUID session ID and conversation ID, stores both in sessionStorage, and includes them on every message. The session ID maps to a deterministic anonymous user ID so the same browser tab always sees its own history; closing the tab discards both IDs (no PII is persisted client-side beyond the tab lifetime).
Why no debug badges on /chat
/chat and /search use SkeletonLoader, not the legacy PipelineProgress component. They render the five Dutch stage labels listed below and nothing else — no intent badges, no "Tier 2 model" annotations, no retrieval candidate counts. The badged debug surface only exists in the legacy QueryPage route (no sidebar entry) where showDebugInfo={true} is hard-coded. This avoids leaking system internals to public visitors and keeps the patient-facing UI free of jargon.
Multi-turn conversation example
Conversation memory for follow-up queries
Follow-up queries like "En op welke campus?" are meaningless without conversation history. The system maintains conversation memory across turns through several mechanisms:
| Layer | Storage | Scope | Purpose |
|---|---|---|---|
| Session ID | Browser sessionStorage | Tab lifetime | Anonymous user identifier |
| Conversation ID | Browser sessionStorage + PostgreSQL | Tab lifetime | Groups messages into a thread |
| Message history | PostgreSQL (messages table) | Persistent | Q&A pairs with citations |
When a follow-up arrives, the backend uses several mechanisms in cascade — primary LLM rewrite (Tier 2), nano fallback for ≤6-word follow-ups, citation-based topic enrichment as a final fallback, +25 % document-context boost on previously-cited sources, category-filter bypass, short-query heuristic for unknown-intent classifier outputs, and the full last-5-turn history is passed to the response LLM as user/assistant pairs. The full mechanism list is documented in Query Pipeline. Conversation-IR foundations follow @sacks1974turntaking for turn-taking and @gao2024ragsurvey for the conversational RAG pattern.
Real-time progress indicator
End-to-end response time has a measured median of 7,829 ms (thesis Chapter 4, Table 4.3 — full distribution P90 12,182 ms, P99 20,925 ms). Without feedback, users would face a blank screen for that duration — far above @nielsen1993responsetimes's 1-second flow threshold. The WebSocket-based progress indicator solves this by reporting pipeline stages in real time:
Pipeline Stages Shown to User
The labels below are the exact Dutch strings rendered by SkeletonLoader (sourced from frontend/src/i18n/nl.json:1477-1481). An examiner running the system in Dutch will see this exact text:
| Stage | Dutch Label | Approx. Duration | What Happens |
|---|---|---|---|
| 1 | "Uw vraag analyseren..." | ~700 ms | Intent classification + query rewriting |
| 2 | "Relevante documenten zoeken..." | ~800 ms | Vector + BM25 + taxonomy retrieval |
| 3 | "Context verwerken..." | ~100 ms | Metadata boosting + ranking + Value Framework |
| 4 | "Antwoord genereren..." | ~3,500 ms | LLM response generation (streamed) |
| 5 | "Kwaliteit controleren..." | ~600 ms | Fast quality gate |
ADR-0009 documents the deliberate UX decision behind this five-stage indicator: user testing showed that progress stages transformed perceived wait time from "the system is broken" to "the system is working hard for me."
Source citations
Every response includes source citations — clickable links to the original hospital web pages or brochure PDFs. Citations serve four purposes:
- Verifiability: Users can confirm the information at the source.
- Trust: Visible sources increase confidence in the response.
- Navigation: Sources often contain additional relevant information.
- Transparency: Users understand that the system retrieves rather than generates knowledge (@gao2024ragsurvey).
Feedback widget
Each response includes a feedback widget with three interaction options:
- Thumbs up: Signals a helpful response. Logged for quality analytics.
- Thumbs down: Signals an unhelpful response. Triggers the "Think Harder" option.
- Think Harder: Initiates an escalated search with more candidates and reranking.
Admin Surface — route map
The administrative surface is structured around a sidebar with four sections (frontend/src/components/AppShell/navigation.ts). All routes below HospitalContextGuard require an authenticated user with a selected hospital tenant; platform routes additionally require OwnerRoute. Several routes were rebadged in May 2026 (the old /analytics is now /value; the deep operations page now sits at /analytics/system with the sidebar label "Operations"), so this map is the canonical name-to-route reference:
| Sidebar section | Sidebar label | Route | Component | Guards |
|---|---|---|---|---|
| Overview | Value | /value | ValueDashboardPage | HospitalContextGuard |
| Overview | Dashboard | /dashboard | DashboardPage | HospitalContextGuard |
| Overview | Search | /search | SearchPage | HospitalContextGuard |
| Content | Documents | /documents | DocumentsPage | HospitalContextGuard |
| Content | Knowledge Base | /knowledge-base | TaxonomyPipelinePage | HospitalContextGuard |
| Analytics | Operations | /analytics/system | Analytics | HospitalContextGuard; Costs tab gated by isOwner |
| Analytics | Feedback | /feedback | FeedbackDashboardPage | HospitalContextGuard |
| Admin | Pipeline | /pipeline | PipelineDebugPage | HospitalContextGuard |
| Admin | Ingestion History | /ingestion-history | IngestionHistoryPage | HospitalContextGuard |
| Admin | Diagnostics | /diagnostics | DatabaseDiagnosticsPage | HospitalContextGuard |
| Admin | Settings | /settings | SettingsPage | HospitalContextGuard |
| Platform (owner-only) | Platform Dashboard | /platform | PlatformDashboardPage | OwnerRoute |
| Platform (owner-only) | Platform Value | /platform/value | PlatformValueDashboardPage | OwnerRoute |
Backwards-compatibility redirects: /analytics → /value, /taxonomy → /knowledge-base, /pipeline-debug → /pipeline (frontend/src/App.tsx:140-165).
The multi-tenant separation (HospitalContextGuard, tenant-scoped routes) follows the multi-tenant SaaS patterns surveyed in @bezemer2010multitenant.
Admin Experience: Knowledge Base Wizard (/knowledge-base)
The Knowledge Base wizard at /knowledge-base is the operator surface for ingesting a hospital's website, extracting taxonomy entities, reviewing relationships, and publishing the result to production. The component tree is TaxonomyPipelinePage → PipelineWizard, and the canonical stage list is the STAGE_ORDER constant in PipelineWizard.tsx:17:
const STAGE_ORDER: Stage[] = [
'setup', 'crawl', 'process', 'hub-pages',
'entity-table', 'relationships', 'merge-suggestions', 'graph', 'publish'
];
The sidebar (PipelineSidebar.tsx) groups these into six top-level numbered steps, with the four taxonomy-review sub-stages nested under step 5:
| Step | Stage ID | Sidebar label | Component | What the operator does |
|---|---|---|---|---|
| 1 | setup | "1. Hospital Setup" | SetupChecklist | Confirm hospital metadata (name, hospital_id, timezone), entry sitemap, language hint. Pass-through if pre-seeded. |
| 2 | crawl | "2. Crawl" | CrawlDashboard | Trigger / monitor sitemap-driven URL discovery and HTTP fetch. Surfaces fail classes (timeout / 404 / empty content / boilerplate-only) per nightly-ingest spec. |
| 3 | process | "3. Process" | ProcessDashboard | Drive document → markdown → chunk → embed pipeline, applying the seven dedup layers (Content Deduplication). |
| 4 | hub-pages | "4. Hub Pages" | HubCandidateList | Review LLM-flagged hub pages (department index, doctor list) and accept / reject for entity extraction. |
| 5 | entity-table | "5. Taxonomy Review" | EntityTable | Browse extracted entities (doctors, departments, conditions, treatments, campuses) with bulk-action controls. |
| 5a | relationships | " Relationships" | RelationshipBrowser | Review WORKS_IN / TREATS / OFFERS / LOCATED_AT edges and patch low-confidence ones. |
| 5b | merge-suggestions | " Merge Suggestions" | MergeSuggestions | Approve / reject fuzzy-dedup merge candidates surfaced by the SP-7 pipeline. |
| 5c | graph | " Graph View" | GraphView | Visualise the taxonomy as a force-directed graph for sanity-checking the topology. |
| 6 | publish | "6. Publish" | PublishPage | Compare draft vs current production, review diff, atomically publish. Logs an immutable version row (VersionHistory). |
The wizard's "navigation rail" at the top of every stage exposes Previous / Next buttons that walk STAGE_ORDER linearly; the sidebar allows skipping ahead once a stage's prerequisite has shipped. Stage status badges (○ not started, ● in progress, ✓ complete) are pulled from the getPipelineStatus(hospitalId) endpoint and refresh every 30 s (PipelineSidebar.tsx:50).
This nine-stage structure replaces a deleted six-step ingestion wizard that lived inside DocumentsPage until commit d6a5e0db. The new wizard expanded the post-ingest lifecycle (hub-page review, taxonomy review, merge suggestions, graph visualisation, atomic publish) that the older wizard collapsed into a single "Results & Quality" step. The route is gated by HospitalContextGuard only — any authenticated user with a selected hospital can drive it; backend endpoints enforce the admin-only checks (the frontend relies on backend 403s rather than role-gating the route).
Admin Experience: Value Dashboard (/value)
The Value Dashboard is the customer-ROI surface — it converts query traffic, voice deflection, and cost-model attribution into the executive answer to "is the system paying for itself?" The page composes:
| Component | Purpose |
|---|---|
HeadlineTiles | Calls handled (total / 24 h), minutes deflected, after-hours coverage, daily query volume — the four executive KPIs. |
DailyVolumeTrend | Time-series of daily queries pulled from /api/v1/admin/value/trend. |
CostModelBanner | Per-tenant cost attribution; opens CostModelEditModal for owner role. |
| Period / channel filters | Slice by 7 d / 30 d / 90 d, all-channel / chat-only / voice-only. |
Data source is the nightly daily_tenant_metrics aggregation populated by an APScheduler job at 02:30 UTC. The /platform/value variant rolls the same metrics up across all tenants for the platform-owner view.
Admin Experience: Operations (/analytics/system)
The Operations page (sidebar label "Operations" — historically "Analytics") aggregates retrieval-health and system-health visualisations:
| Tab | Visible to | What it shows |
|---|---|---|
| Overview | All admins | Top-level system health, recent activity, error rates. |
| Costs | isOwner only | LLM spend rollup, CategoryMismatchTrend, DiagnosticAccuracyTrend. |
| Feedback metrics | All admins | P95 latency comparison, Think Harder funnel, flag-for-review actions. |
The Costs tab is conditionally rendered (Analytics.tsx:333) — non-owner admins do not see the LLM-spend or category-mismatch panels even though the page route itself is open to any admin under HospitalContextGuard. This matches the principle of least-privilege expectations of @iso27001_2022 for tenant administrators.
Admin Experience: Feedback Dashboard (/feedback)
The Feedback Dashboard is the closed-loop quality surface. It shows feedback telemetry (positive / negative / flagged ratios over time, with P95 latency comparisons by feedback class), surfaces individual flagged conversations, and triggers the v2 Diagnostic / Investigation flow:
Admin clicks "Investigate" →
FeedbackDashboardPage triggers analysis →
InvestigationViewV2 renders dimensional cards
(correctness / safety / memory / tool_use / latency) →
Operator clicks 👍 / ⚠ / 👎 →
InvestigationFeedback POSTs to
/api/v1/admin/voice-calls/{convId}/investigation/feedback →
DiagnosticAccuracyTrend picks up the vote in nightly aggregation
The dimensional rubric and 👍/⚠/👎 operator-rating loop align with the LLM-as-judge methodology of @zheng2023llmjudge and the RAG-evaluation framing of @es2023ragas. Audit-trail fields (original value, new value, admin user, timestamp, optional justification) on every override satisfy @iso27001_2022 audit-logging requirements.
Language and tone
The patient-facing interface operates in Dutch (Flemish). Following @nielsen1993responsetimes's usability heuristics, the system's language style is calibrated to be:
- Professional: Appropriate for a hospital context.
- Accessible: Avoiding excessive medical jargon in navigation responses.
- Warm: Polite forms and empathetic phrasing.
- Clear: Short sentences, structured responses with bullet points where appropriate.
- Safe: Always including disclaimers and referrals to medical professionals.
The voice channel adds three additional output languages (English, French, Italian) with parallel safety-prompt coverage; see Voice Stack Compendium for the multi-language voice architecture.
User journey
Accessibility considerations
The chat interface follows web accessibility best practices:
- Keyboard navigation: All interactive elements (input, send, feedback buttons, follow-up chips, clarification cards) are keyboard-accessible.
- Screen reader compatibility: ARIA labels on dynamic content updates (streaming tokens, stage progression).
- Color contrast: All text meets WCAG 2.1 AA contrast requirements (4.5:1 minimum).
- Focus management: Focus moves to new responses as they appear.
- Reduced motion: Respects
prefers-reduced-motionfor stage-progression animations.
References
- @nielsen1993responsetimes — Response-time thresholds (0.1 s, 1 s, 10 s) underlying the streaming-progress design.
- @gao2024ragsurvey — Conversational RAG taxonomy (Naive / Advanced / Modular).
- @sacks1974turntaking — Turn-taking foundations for multi-turn dialogue.
- @bezemer2010multitenant — Multi-tenant SaaS architecture patterns underlying the per-tenant route structure.
- @iso27001_2022 — Audit-trail and least-privilege requirements satisfied by the Feedback Dashboard override loop and the owner-gated Costs tab.
- @es2023ragas, @zheng2023llmjudge — Evaluation-rubric framing for the v2 Investigation dimensional cards.
- W3C. (2023). Web Content Accessibility Guidelines (WCAG) 2.2. https://www.w3.org/TR/WCAG22/