Kulcare Docs

Hospital PDF-First Lab QA

Manual checks for report intake, clinical review, readiness, OT gates, and safe recovery.

Preconditions And Evidence

Use synthetic patients and reports. Do not run these checks during actual patient care. Record backend/frontend commits, environment, database, facility, preset, timezone, actor grants, policy version, case IDs, report time, and evidence links. Use the developer contract for exact access and API rules. U4 is in PR 2746. A local pass is not deployment or clinical approval. See the release record for local results, the tested source commit, the completed scoped external follow-up, and the remaining acceptance gates. D2 blocks staging/production deployment until storage, scanning, and document policy are accepted. D3 blocks clinical activation until the clinical owner accepts the policy and timestamp/freshness rules. Never enable a test-only clean scanner on staging to work around these gates.

Prepare one authorized Lab manager, one queue-only viewer, one actor without Lab access, and a second facility/tenant. Use the shell for grants and Admin for the app flag. Hospital must not contain those management views. Prepare an approved synthetic Lab requirement, a separate clinical blocker, one booked case, and one unbooked case. Do not make them ready through raw database updates.

Local Browser Reproduction

Use a fresh, owned test database for each run. Never point this harness at kulcare_development, a shared test database, or staging. The fixture checks RAILS_ENV=test, an explicit opt-in, and a database name of kulcare_hospital_u4_<digits>_browser before it loads the test harness. It refuses a database with tenant data. It writes the synthetic session to a new private file, not to console output. Wait for database preparation to finish before creating the fixture or starting the server.

From backend/, with the repository's local PostgreSQL and Ruby setup:

export PGHOST=localhost
export PGUSER="${PGUSER:-$USER}"
export HOSPITAL_LAB_DB="kulcare_hospital_u4_$(date +%Y%m%d%H%M%S)_browser"
export DATABASE_URL="postgresql://${PGUSER}@${PGHOST}/${HOSPITAL_LAB_DB}"
export HOSPITAL_LAB_SESSION="/tmp/hospital-u4-session-$(date +%Y%m%d%H%M%S).json"
createdb -h "$PGHOST" -U "$PGUSER" "$HOSPITAL_LAB_DB"
RAILS_ENV=test bin/rails db:schema:load db:environment:set db:test:prepare
RAILS_ENV=test PARALLEL_WORKERS=1 identity_access_token_ttl_seconds=7200 HOSPITAL_LAB_BROWSER_FIXTURE=true HOSPITAL_LAB_BROWSER_SESSION="$HOSPITAL_LAB_SESSION" bin/rails runner domains/clinical/test/support/hospital_lab_journey_fixture.rb

The fixture uses the policy publisher, case/booking services, and an isolated authenticated test session. It defaults to Surgery Center. To repeat with Hospital 50, use another fresh database/session path and set HOSPITAL_LAB_PRESET=hospital_50 for fixture creation. It does not add a public login or setup route. Its approval reference says test-only. Keep the same DATABASE_URL when starting the local backend:

RAILS_ENV=test HOSPITAL_LAB_BROWSER_SERVER=true CLINICAL_DOCUMENT_UPLOADS_ENABLED=true CLINICAL_DOCUMENT_POLICY_REFERENCE=TEST-ONLY-NOT-D2 CLINICAL_DOCUMENT_MAX_BYTES=4096 bin/rails runner domains/clinical/test/support/hospital_lab_browser_server.rb

This server binds only 127.0.0.1:4014. Its inline scanner returns synthetic outcomes. It is not a malware scanner, not a D2 approval, and not a staging configuration. Do not run it where that port belongs to another process.

From frontend/, in a separate terminal:

VITE_API_URL=http://127.0.0.1:4014 VITE_HOSPITAL_MOCK_MODE=false pnpm --filter @kulcare/shell exec vite --host 127.0.0.1 --port 3018 --strictPort

Run node scripts/hospital-lab-journey.mjs with HOSPITAL_LAB_SESSION set to the private session path. Use the installed workspace playwright and pdf-lib modules. If they are outside normal module resolution, set HOSPITAL_PLAYWRIGHT_MODULE and HOSPITAL_PDF_MODULE to their installed paths; no new application dependencies are required. The script uses real Chrome and HTTP responses. It creates a valid synthetic PDF, checks authorized preview and Blob revocation, reviews evidence, reloads, checks mobile layout, enters OT, and replaces the report. It writes screenshots and a safe result to /tmp/hospital-u4-browser-evidence, or HOSPITAL_LAB_EVIDENCE when set. Do not attach session files to a PR or review. After QA, stop these two local servers and remove only the owned synthetic database/session artifacts. Manual permission, expiry, conflict, scope-switch, and staging checks below remain separate evidence.

U1: Evidence And Access

  • A queue-only viewer sees authorized case/requirement state but no filenames, report controls, or private history.
  • The viewer cannot POST a link/review/invalidation, open command history, or download a PDF through direct API requests.
  • An actor without view_lab_readiness sees no Lab navigation entry. Direct entry is also denied.
  • Foreign tenant/facility/case/report selectors reveal no private data.
  • A manager links the exact report to the correct case and requirement with a reason and confirmed offset-bearing timestamp.
  • Reject a future or ambiguous timestamp. No evidence, command, event, or audit side effect remains after a rejected write.
  • Reload evidence history. Preserve actor, reason, report revision, and report time. No storage URL or key is exposed.

U2: Source, Review, And OT Gates

  • Upload a valid PDF. Show stored upload and pending scan separately. The requirement stays not started.
  • Pending, failed, or rejected scan evidence cannot be reviewed as ready or overridden.
  • After a real clean scan, the requirement still needs explicit clinical review.
  • Accept the displayed evidence with a reason. Reload queue, case detail/list, and OT schedule; all agree on current state.
  • Reject the clinical report. The requirement becomes blocked and history preserves the decision.
  • Register a successor to a reviewed report. The old link loses clearance before the successor becomes clean.
  • Link/review the successor. The old report and decision remain in history.
  • Re-upload a report with the same confirmed report time. Its approved expiry must not move later.
  • At the exact approved expiry, show stale state across the queue and case views. The next OT transition is blocked.
  • Invalidate a wrong link with a reason, then attach the correct report and review again. Preserve both histories.
  • A separate unresolved clinical requirement still blocks OT entry after Lab clearance.
  • Race two same-key requests: one effect and one historical replay. Race different keys at one expected version: at most one new binding.

U3: Queue And Effective Settings

  • With no date filter, include the unbooked nonterminal case. A missing booking is not an invented appointment.
  • Search by case, patient, and requirement. Filter each current state before pagination. Counts cover the same filtered set.
  • Reload status/date/page filters and case/requirement selection. Preserve them, but clear free-text search. A patient search must not enter page URLs, browser history/storage, or Rails request logs. Verify proxy/access-log redaction before deployment.
  • Test a facility-local date boundary. Scheduled from is inclusive; scheduled before is exclusive.
  • Test the next page and an empty result. A failed request must show an error, not zero or ready.
  • Missing approved policy appears as a setup gap, not an absent successful row.
  • Save PDF-first mode with the current configuration version. Reload and confirm the same mode and unchanged other settings.
  • Reject stale settings writes and new unsupported integrated modes.
  • A legacy unsupported mode shows unavailable. It must not silently fall back to manual intake.
  • Lab management alone cannot edit configuration. Suspension or terminal case state disables writes but keeps authorized history.
  • Revoking a module or app flag denies direct APIs as well as page navigation.

U4: UI And Recovery

  • Open Lab from a case requirement. Keep caseId and requirementId; the return link opens the same case.
  • Show patient, case, and requirement next to report/review controls. A stored unlinked report is not ready.
  • Use the shared date/time/select controls with keyboard and pointer. Check labels, focus, narrow/wide layout, and long filenames.
  • Change evidence while a review form is open. The stale command fails; refetch and require a new review of current evidence.
  • Lose an upload response, reload, then check its key. Reuse stored success or retry the same file/key; do not create a duplicate.
  • Lose a link/review response, reload, then check its key. Do not replay with a new key or changed reason.
  • Revoke permission while recovery is unresolved. Keep the original recovery record; do not allow a replacement write.
  • Switch facility/profile during upload, history, or PDF delivery. Cancel old requests and remove the old preview and drafts.
  • Close a PDF preview. Revoke its Blob URL. Reloaded bytes still require authenticated, audited access.
  • Corrupt local retry data. Show a support/reconciliation state, not a new write button.
  • In settings, distinguish connected API, unavailable PDF intake, and clinical activation approval. Do not label a missing scanner connected.

U5: Acceptance Record

  • Run the automated Lab journey, RLS, concurrency, U3 regression, RBAC, and policy matrix tests with no new skips.
  • Compare the migrations and additive SQL snapshot in separate disposable databases.
  • Run affected frontend tests, lint, types, build, and the full backend/frontend gates. Record actual limits and failures.
  • Run the real-HTTP browser journey and reload checks on the same candidate. Mock-backed component tests are not integration acceptance.
  • Complete code review and fix valid findings. Record the review scope and excluded files.
  • After D2, Simmi records the deployed candidate and real scanner/storage verification. QA records the staging results.
  • The clinical owner records D3 approval and the allowed clinical scope. Keep actual patient care disabled until that decision.
  • QA closes the tracked issue only after verification. A merge or test pass must not close it automatically.

On this page