Compare commits

...
10 Commits
Author SHA1 Message Date
voltsrage 2a3ef62a7d Add deployment
CI / backend (push) Failing after 6m27s
CI / frontend (push) Failing after 57s
2026-08-05 00:26:20 +08:00
voltsrage 9e88ff6113 fix: Acknowledge closing on alert polling update 2026-06-26 03:02:15 +08:00
voltsrage 131a04630d Add license 2026-06-25 15:30:21 +08:00
voltsrage 26bef39aae Update handoff test 2026-06-25 15:25:24 +08:00
voltsrage d48e1737b5 Add dashboard chart guide 2026-06-25 15:24:08 +08:00
voltsrage fdcc646fae fix: No token refresh or revocation mechanism~ 2026-06-25 14:14:20 +08:00
voltsrage a8964381a2 feature: MIMIC-IV Replay Scenario Generator 2026-06-25 13:35:09 +08:00
voltsrage 069881991a Fix issues with initial seeding 2026-06-25 02:53:21 +08:00
voltsrage 09a84f34ba Fix issues with critical alerts breaking the simulation 2026-06-25 02:06:35 +08:00
voltsrage df6fbed401 chore: update docs 2026-06-25 00:46:54 +08:00
175 changed files with 922531 additions and 479 deletions
+22
View File
@@ -0,0 +1,22 @@
**/bin/
**/obj/
**/node_modules/
**/dist/
**/TestResults/
**/data-protection-keys/
.git/
.vs/
.idea/
.claude/
.cursor/
docs/
scripts/
infra/
VigilCareClinicalAPI.Tests/
VigilCare.WardGateway.Tests/
VigilCare.ClinicalContracts.Tests/
VigilCare.Simulator/
vigilcare-dashboard/
*.log
.env
.env.*
+70
View File
@@ -0,0 +1,70 @@
# ---- image coordinates ----
REGISTRY=gitea.example.com/vigilcare
IMAGE_TAG=v1.0.0
# ---- exposed ports on the production host ----
API_PORT=5270
GATEWAY_PORT=5081
DASHBOARD_PORT=8080
DASHBOARD_ORIGIN=https://vigilcare.example.com
# ---- external PostgreSQL ----
# Runtime (DML-only) connection used by the API container.
PG_CONNECTION=Host=pg.internal;Port=5432;Database=vigilcare;Username=vigilcare_app;Password=CHANGE_ME;SSL Mode=Require;Trust Server Certificate=false
# DDL-privileged connection used ONLY by the EF migration bundle (Step 6 / CD migrate job).
# Never put this credential in the API container environment.
PG_CONNECTION_DDL=Host=pg.internal;Port=5432;Database=vigilcare;Username=vigilcare_migrator;Password=CHANGE_ME;SSL Mode=Require;Trust Server Certificate=false
GATEWAY_PG_CONNECTION=Host=pg.internal;Port=5432;Database=vigilcare_ward;Username=vigilcare_app;Password=CHANGE_ME;SSL Mode=Require
# ---- external Redis ----
REDIS_CONNECTION=redis.internal:6379,password=CHANGE_ME,ssl=True,abortConnect=false
GATEWAY_REDIS_CONNECTION=redis.internal:6379,password=CHANGE_ME,ssl=True,abortConnect=false,defaultDatabase=1
# ---- external Seq ----
SEQ_URL=https://seq.internal
SEQ_API_KEY=CHANGE_ME
# ---- external Kafka ----
KAFKA_BOOTSTRAP=kafka1.internal:9093,kafka2.internal:9093,kafka3.internal:9093
KAFKA_REPLICATION_FACTOR=3
KAFKA_SECURITY_PROTOCOL=SaslSsl
KAFKA_SASL_MECHANISM=ScramSha512
KAFKA_SASL_USERNAME=vigilcare
KAFKA_SASL_PASSWORD=CHANGE_ME
# ---- external Elasticsearch ----
ES_URI=https://es.internal:9200
ES_API_KEY=CHANGE_ME
# or ES_USERNAME / ES_PASSWORD
# ---- external RabbitMQ ----
RABBITMQ_HOST=rabbit.internal
RABBITMQ_PORT=5671
RABBITMQ_USERNAME=vigilcare
RABBITMQ_PASSWORD=CHANGE_ME
RABBITMQ_USE_SSL=true
GATEWAY_RABBITMQ_HOST=rabbit.internal
GATEWAY_RABBITMQ_PORT=5671
GATEWAY_RABBITMQ_USERNAME=vigilcare_gw
GATEWAY_RABBITMQ_PASSWORD=CHANGE_ME
GATEWAY_RABBITMQ_USE_SSL=true
# ---- external MinIO ----
MINIO_ENDPOINT=minio.internal:9000
MINIO_ACCESS_KEY=CHANGE_ME
MINIO_SECRET_KEY=CHANGE_ME
MINIO_USE_SSL=true
# ---- application secrets (generate with: openssl rand -base64 48) ----
JWT_SIGNING_KEY=CHANGE_ME_MINIMUM_32_BYTES
# WARNING: rotating PHI_SEARCH_TOKEN_KEY invalidates every stored patient
# search token. See docs/ops/phi-encryption-runbook.md before changing it.
PHI_SEARCH_TOKEN_KEY=CHANGE_ME_32_BYTES
GATEWAY_API_KEY=CHANGE_ME
FHIR_API_KEY=CHANGE_ME
GATEWAY_JWT_SIGNING_KEY=CHANGE_ME_MINIMUM_32_BYTES
# ---- gateway identity ----
GATEWAY_ID=00000000-0000-0000-0000-000000000000
GATEWAY_SITE_ID=00000000-0000-0000-0000-000000000000
GATEWAY_DEPARTMENT=ICU
+174
View File
@@ -0,0 +1,174 @@
# Phase 36 Step 12 — build images, migrate, deploy on version tags.
# Requires act_runner with docker, curl, ssh, scp, bash and label ubuntu-latest.
#
# Secrets: REGISTRY_USERNAME, REGISTRY_TOKEN, PG_CONNECTION_DDL,
# DEPLOY_HOST, DEPLOY_USER, DEPLOY_SSH_KEY
# Variables: PROD_API_URL, REGISTRY (optional; defaults below)
name: CD
on:
push:
tags: ["v*"]
workflow_dispatch:
inputs:
image_tag:
description: "Image tag to deploy (defaults to the pushed tag)"
required: false
env:
REGISTRY: gitea.example.com/vigilcare
jobs:
build-and-push:
runs-on: ubuntu-latest
outputs:
tag: ${{ steps.meta.outputs.tag }}
steps:
- uses: actions/checkout@v4
- name: Resolve tag and registry
id: meta
run: |
if [ -n "${{ vars.REGISTRY }}" ]; then
echo "REGISTRY=${{ vars.REGISTRY }}" >> "$GITHUB_ENV"
fi
if [ -n "${{ inputs.image_tag }}" ]; then
echo "tag=${{ inputs.image_tag }}" >> "$GITHUB_OUTPUT"
elif [[ "${GITHUB_REF}" == refs/tags/* ]]; then
echo "tag=${GITHUB_REF_NAME}" >> "$GITHUB_OUTPUT"
else
echo "image_tag input is required for workflow_dispatch without a tag" >&2
exit 1
fi
- name: Log in to the Gitea registry
run: |
echo "${{ secrets.REGISTRY_TOKEN }}" \
| docker login "${REGISTRY%%/*}" \
-u "${{ secrets.REGISTRY_USERNAME }}" --password-stdin
- name: Build and push clinical-api
# Context MUST be repo root — ClinicalContracts is a sibling ProjectReference.
run: |
docker build -f VigilCareClinicalAPI/Dockerfile \
-t "${REGISTRY}/clinical-api:${{ steps.meta.outputs.tag }}" \
-t "${REGISTRY}/clinical-api:latest" .
docker push "${REGISTRY}/clinical-api:${{ steps.meta.outputs.tag }}"
docker push "${REGISTRY}/clinical-api:latest"
- name: Build and push ward-gateway
# Same repo-root context as docker-compose.yml's ward-gateway-api service.
run: |
docker build -f VigilCare.WardGateway/Dockerfile \
-t "${REGISTRY}/ward-gateway:${{ steps.meta.outputs.tag }}" \
-t "${REGISTRY}/ward-gateway:latest" .
docker push "${REGISTRY}/ward-gateway:${{ steps.meta.outputs.tag }}"
docker push "${REGISTRY}/ward-gateway:latest"
- name: Build and push dashboard
# Context is vigilcare-dashboard/ — package.json and nginx.conf live there.
run: |
docker build -f vigilcare-dashboard/Dockerfile \
--build-arg VITE_API_URL="${{ vars.PROD_API_URL }}" \
-t "${REGISTRY}/dashboard:${{ steps.meta.outputs.tag }}" \
-t "${REGISTRY}/dashboard:latest" vigilcare-dashboard
docker push "${REGISTRY}/dashboard:${{ steps.meta.outputs.tag }}"
docker push "${REGISTRY}/dashboard:latest"
migrate:
needs: build-and-push
runs-on: ubuntu-latest
container:
image: mcr.microsoft.com/dotnet/sdk:8.0
steps:
- uses: actions/checkout@v4
- name: Build migration bundle
run: |
dotnet tool install --global dotnet-ef --version 8.0.4 \
|| dotnet tool update --global dotnet-ef --version 8.0.4
export PATH="$PATH:/root/.dotnet/tools"
bash ./scripts/build-api-migration-bundle.sh
cp -f ./artifacts/migrate-api ./migrate-api
chmod +x ./migrate-api
# Runs while the previous release is still serving traffic, so every
# migration must be backwards-compatible with the outgoing image.
# See Step 6 — expand-then-contract.
- name: Apply migrations
run: ./migrate-api --connection "${{ secrets.PG_CONNECTION_DDL }}"
deploy:
needs: [build-and-push, migrate]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Configure SSH
run: |
mkdir -p ~/.ssh
echo "${{ secrets.DEPLOY_SSH_KEY }}" > ~/.ssh/id_ed25519
chmod 600 ~/.ssh/id_ed25519
ssh-keyscan -H "${{ secrets.DEPLOY_HOST }}" >> ~/.ssh/known_hosts
- name: Copy compose file
run: |
scp -i ~/.ssh/id_ed25519 docker-compose.prod.yml \
"${{ secrets.DEPLOY_USER }}@${{ secrets.DEPLOY_HOST }}:/opt/vigilcare/docker-compose.prod.yml"
- name: Deploy
env:
IMAGE_TAG: ${{ needs.build-and-push.outputs.tag }}
run: |
ssh -i ~/.ssh/id_ed25519 \
"${{ secrets.DEPLOY_USER }}@${{ secrets.DEPLOY_HOST }}" \
IMAGE_TAG="$IMAGE_TAG" bash -euo pipefail <<'EOF'
cd /opt/vigilcare
# Record the currently deployed tag so a rollback has a target.
grep '^IMAGE_TAG=' .env > .env.previous || true
if grep -q '^IMAGE_TAG=' .env; then
sed -i "s|^IMAGE_TAG=.*|IMAGE_TAG=${IMAGE_TAG}|" .env
else
echo "IMAGE_TAG=${IMAGE_TAG}" >> .env
fi
docker compose -f docker-compose.prod.yml --env-file .env pull
docker compose -f docker-compose.prod.yml --env-file .env up -d --remove-orphans
docker image prune -f
EOF
- name: Smoke test
run: |
ssh -i ~/.ssh/id_ed25519 \
"${{ secrets.DEPLOY_USER }}@${{ secrets.DEPLOY_HOST }}" bash -euo pipefail <<'EOF'
for i in $(seq 1 30); do
if curl -fsS http://localhost:5270/health/ready >/dev/null; then
echo "Ready check passed."
curl -fsS http://localhost:5081/health/live >/dev/null && echo "Gateway live."
curl -fsS http://localhost:8080/ >/dev/null && echo "Dashboard serving."
exit 0
fi
sleep 5
done
echo "Ready check never passed — dumping API logs:"
docker compose -f /opt/vigilcare/docker-compose.prod.yml --env-file /opt/vigilcare/.env logs --tail 100 api
exit 1
EOF
- name: Roll back on failure
if: failure()
run: |
ssh -i ~/.ssh/id_ed25519 \
"${{ secrets.DEPLOY_USER }}@${{ secrets.DEPLOY_HOST }}" bash -euo pipefail <<'EOF'
cd /opt/vigilcare
# Restores the previous image tag only. Schema changes are NOT
# reverted — this is why migrations must be backwards-compatible.
if [ -f .env.previous ]; then
PREV=$(cut -d= -f2 .env.previous)
sed -i "s|^IMAGE_TAG=.*|IMAGE_TAG=${PREV}|" .env
docker compose -f docker-compose.prod.yml --env-file .env up -d
echo "Rolled back to ${PREV}"
fi
EOF
+98
View File
@@ -0,0 +1,98 @@
# Phase 36 Step 11 — build and test on every push/PR.
# Fixtures read ConnectionStrings__* / Redis__* / RabbitMq__* / Kafka__* from the
# environment (see ApiFixture / GatewayApiFixture). Dependencies come from
# docker-compose.yml so Kafka, ES, and MinIO match local integration tests —
# ApiFixture starts hosted services that require those brokers.
name: CI
on:
push:
branches: [master]
pull_request:
branches: [master]
jobs:
backend:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Start dependency stack
run: |
docker compose up -d postgres redis rabbitmq kafka elasticsearch minio
docker compose --profile ward-gateway up -d ward-gateway-db ward-gateway-redis ward-gateway-rabbitmq
- name: Wait for Postgres (API + gateway)
run: |
for i in $(seq 1 60); do
docker compose exec -T postgres pg_isready -U postgres && break
sleep 2
done
docker compose exec -T postgres pg_isready -U postgres
docker compose exec -T ward-gateway-db pg_isready -U postgres
- name: Create test databases
run: |
docker compose exec -T postgres psql -U postgres -tc "SELECT 1 FROM pg_database WHERE datname='vigilcare_test'" \
| grep -q 1 \
|| docker compose exec -T postgres psql -U postgres -c "CREATE DATABASE vigilcare_test"
docker compose exec -T ward-gateway-db psql -U postgres -tc "SELECT 1 FROM pg_database WHERE datname='vigilcare_ward_test'" \
| grep -q 1 \
|| docker compose exec -T ward-gateway-db psql -U postgres -c "CREATE DATABASE vigilcare_ward_test"
- name: Setup .NET 8
uses: actions/setup-dotnet@v4
with:
dotnet-version: "8.0.x"
- name: Restore
run: dotnet restore VigilCareClinical.sln
- name: Build
run: dotnet build VigilCareClinical.sln -c Release --no-restore
- name: Test
env:
# Defaults match docker-compose.yml published ports; fixtures fall back to these anyway.
ConnectionStrings__DefaultConnection: "Host=localhost;Port=5436;Database=vigilcare_test;Username=postgres;Password=password"
ConnectionStrings__GatewayDb: "Host=localhost;Port=5437;Database=vigilcare_ward_test;Username=postgres;Password=password"
Redis__ConnectionString: "localhost:6382,defaultDatabase=1,allowAdmin=true"
Gateway__Redis__ConnectionString: "localhost:6383,defaultDatabase=2,allowAdmin=true"
RabbitMq__Host: "localhost"
RabbitMq__Port: "5674"
Gateway__RabbitMq__Port: "5675"
Kafka__BootstrapServers: "localhost:9092"
Kafka__ReplicationFactor: "1"
Elasticsearch__Uri: "http://localhost:9200"
Minio__Endpoint: "localhost:9005"
run: |
dotnet test VigilCareClinical.sln -c Release --no-build \
--logger "trx;LogFileName=test-results.trx" \
--results-directory ./TestResults
- name: Publish test results
if: always()
uses: actions/upload-artifact@v3
with:
name: test-results
path: ./TestResults
- name: Tear down stack
if: always()
run: docker compose --profile ward-gateway down -v
frontend:
runs-on: ubuntu-latest
container:
image: node:22-alpine
steps:
- uses: actions/checkout@v4
- name: Install
working-directory: vigilcare-dashboard
run: npm ci
- name: Test
working-directory: vigilcare-dashboard
run: npm run test
- name: Build
working-directory: vigilcare-dashboard
run: npm run build
+1
View File
@@ -5,6 +5,7 @@ bin/
obj/
out/
publish/
artifacts/
# =========================
# User-specific files
+89
View File
@@ -0,0 +1,89 @@
Proprietary Software License Agreement
Copyright (c) 2024-2026 voltsrage. All Rights Reserved.
NOTICE: This software and all associated documentation, source code, object
code, APIs, designs, algorithms, data models, and related materials
(collectively, the "Software") are the exclusive property of voltsrage and
are protected by copyright law, trade secret law, and international treaties.
1. LICENSE GRANT
No license, right, or interest in the Software is granted except under a
separate, signed commercial license agreement between voltsrage and the
licensee. Possession of a copy of the Software does not convey any rights
to use, modify, distribute, sublicense, or create derivative works from
the Software.
2. RESTRICTIONS
Without a valid commercial license, you may NOT:
a. Use the Software or any portion thereof for any purpose, including but
not limited to commercial, personal, educational, or evaluation use;
b. Copy, reproduce, or duplicate the Software in whole or in part;
c. Modify, adapt, translate, reverse engineer, decompile, disassemble, or
create derivative works based on the Software;
d. Distribute, sublicense, lease, rent, loan, sell, or otherwise transfer
the Software or any rights therein to any third party;
e. Remove, alter, or obscure any proprietary notices, labels, or marks on
the Software;
f. Use the Software to provide services to third parties (including but not
limited to SaaS, hosting, or managed services) without a separate
service provider license.
3. CONFIDENTIALITY
The Software contains trade secrets and proprietary information of
voltsrage. You agree to hold the Software in strict confidence and not to
disclose it to any third party without prior written consent from
voltsrage.
4. INTELLECTUAL PROPERTY
All title, ownership rights, and intellectual property rights in and to the
Software, including but not limited to patents, copyrights, trademarks,
trade secrets, and any improvements or modifications thereto, shall remain
the sole and exclusive property of voltsrage.
5. COMMERCIAL LICENSING
Commercial licenses for the Software, including licenses for individual
components, modules, or the complete system, are available from voltsrage.
Contact voltsrage for licensing terms, pricing, and permitted use.
6. TERMINATION
Any unauthorized use, reproduction, or distribution of the Software
automatically terminates any implied rights and may result in civil and
criminal penalties. voltsrage reserves the right to pursue all available
legal remedies.
7. WARRANTY DISCLAIMER
THE SOFTWARE IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE, AND NONINFRINGEMENT. VOLTSRAGE DOES NOT
WARRANT THAT THE SOFTWARE WILL BE ERROR-FREE, UNINTERRUPTED, OR FREE OF
HARMFUL COMPONENTS.
8. LIMITATION OF LIABILITY
IN NO EVENT SHALL VOLTSRAGE BE LIABLE FOR ANY INDIRECT, INCIDENTAL,
SPECIAL, CONSEQUENTIAL, OR PUNITIVE DAMAGES, OR ANY LOSS OF PROFITS,
REVENUE, DATA, OR USE, ARISING OUT OF OR RELATED TO THE SOFTWARE, EVEN IF
ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
9. GOVERNING LAW
This Agreement shall be governed by and construed in accordance with
applicable law, without regard to conflict of law principles.
10. ENTIRE AGREEMENT
This License constitutes the entire agreement regarding the Software and
supersedes all prior agreements, understandings, and representations. No
modification of this License shall be binding unless in writing and signed
by voltsrage.
For licensing inquiries, contact: voltsrage
+140 -47
View File
@@ -2,11 +2,11 @@
A production-quality clinical backend built with ASP.NET Core 8, PostgreSQL, Apache Kafka, RabbitMQ, Elasticsearch, Redis, and MinIO. The domain models the observe-alert-acknowledge lifecycle at the center of any clinical monitoring system: patient encounters, continuous vital sign and lab result ingest, real-time sepsis and NEWS2 scoring, and clinician notification with automatic escalation.
**Implementation status:** Thirty-one planned phases are complete through Phase 33 (plus Phases 2023) — from schema and CRUD through Kafka, Elasticsearch CQRS, sepsis detection, RabbitMQ paging with DLQ escalation, reconciliation jobs, Prometheus/Grafana observability, the MinIO Parquet data lake, clinical data model expansion, warning alerts and orders, the NEWS2 composite scoring engine, trend detection with alert suppression, qSOFA bedside screening, medication administration with alert correlation annotations, the console replay simulator, the **Vue 3 ward dashboard**, clinician feedback mode, **Glasgow Coma Scale (GCS) scoring**, **SOFA organ-dysfunction scoring with baseline tracking and delta sepsis alerts**, the **Sepsis-3 clinical refactor** (SIRS removed, qSOFA repositioned as screening, SOFA delta ≥ 2 triggers bundles), **frontend GCS entry and SOFA display**, **expanded simulator scenarios with clinical validation**, the **Site & Gateway Registry** with dual authentication, shared clinical sync contracts, and fleet health Prometheus gauges, the **Ward Gateway Service** (local-first clinical path with offline buffering and central sync), the **FHIR R4 Inbound Facade** for EHR integration, **Role-Based Access Control (RBAC) with clinical audit logging**, the **Dashboard Gap Analysis Fixes** (SOFA/GCS/qSOFA history charts, patient banner, encounter timeline, medication markers on vital charts), the **Enhanced Dashboard** (department overview, sepsis bundle board, critical alert notifications, shift handoff reports, vitals entry form, sortable/filterable ward table), **Degraded Operations Visibility** (gateway fleet operations panel, stale gateway auto-detection, discharge summary API, admin panels for user/threshold/audit/reconciliation management, degraded-mode banner), and **Alert Quality Analytics** (server-side clinician feedback with `AlertFeedback` entity, `AlertQualityAggregatorService` background metrics, quality metrics API, Grafana alert quality dashboard). Post-phase hardening includes health check endpoints, Kafka poison pill protection, outbox dead-letter with retry tracking, data lake partial-commit safety, MRN sequence-based generation, FHIR bundle transaction rollback, **FHIR R4 read/search endpoints** (Patient and Encounter), **alert threshold deletion with audit trail**, **FHIR API key rotation** (constant-time multi-key validation), **authorization failure logging** with Prometheus metrics, **JWT signing key validation** at startup, and **concurrency hardening** (transactional sepsis bundle creation, unique active encounter constraint). See [Implemented Phases](#implemented-phases) for the full breakdown. Guides: [dashboard-guide.md](docs/dashboard-guide.md) (technical), [clinical-testing-guide.md](docs/clinical-testing-guide.md) (doctors & nurses).
**Implementation status:** Thirty-three planned phases are complete through Phase 35 (plus Phases 2023) — from schema and CRUD through Kafka, Elasticsearch CQRS, sepsis detection, RabbitMQ paging with DLQ escalation, reconciliation jobs, Prometheus/Grafana observability, the MinIO Parquet data lake, clinical data model expansion, warning alerts and orders, the NEWS2 composite scoring engine, trend detection with alert suppression, qSOFA bedside screening, medication administration with alert correlation annotations, the console replay simulator, the **Vue 3 ward dashboard**, clinician feedback mode, **Glasgow Coma Scale (GCS) scoring**, **SOFA organ-dysfunction scoring with baseline tracking and delta sepsis alerts**, the **Sepsis-3 clinical refactor** (SIRS removed, qSOFA repositioned as screening, SOFA delta ≥ 2 triggers bundles), **frontend GCS entry and SOFA display**, **expanded simulator scenarios with clinical validation**, the **Site & Gateway Registry** with dual authentication, shared clinical sync contracts, and fleet health Prometheus gauges, the **Ward Gateway Service** (local-first clinical path with offline buffering and central sync), the **FHIR R4 Inbound Facade** for EHR integration, **Role-Based Access Control (RBAC) with clinical audit logging**, the **Dashboard Gap Analysis Fixes** (SOFA/GCS/qSOFA history charts, patient banner, encounter timeline, medication markers on vital charts), the **Enhanced Dashboard** (department overview, sepsis bundle board, critical alert notifications, shift handoff reports, vitals entry form, sortable/filterable ward table), **Degraded Operations Visibility** (gateway fleet operations panel, stale gateway auto-detection, discharge summary API, admin panels for user/threshold/audit/reconciliation management, degraded-mode banner), **Alert Quality Analytics** (server-side clinician feedback with `AlertFeedback` entity, `AlertQualityAggregatorService` background metrics, quality metrics API, Grafana alert quality dashboard), **Explainable Alerts** (immutable JSONB `explanation` on composite alerts with score contributors, trend context, structured medication context, and bedside `NarrativeSummary`; `AlertResponse` DTO on GET/list/acknowledge/resolve; dashboard `AlertReasoning.vue`; ES indexer and data lake propagation; ward gateway sync), and **MIMIC-IV Replay Scenario Generator** (offline CLI tool converting real de-identified ICU data from MIT PhysioNet into VigilCare scenario JSONs; streaming CSV parser for 668K-row chartevents; 17 chart + 8 lab item ID mappings to VigilCare observation codes; GCS text-to-numeric conversion; Fahrenheit-to-Celsius; blood pressure deduplication preferring non-invasive over arterial; 10-observation cluster limit enforcement; `mimic-list` and `mimic-generate` CLI commands with Spectre.Console output; 100 patients / 140 ICU stays available for replay through NEWS2, SOFA, GCS, qSOFA, trend detection, and alerting). Post-phase hardening includes health check endpoints, Kafka poison pill protection, outbox dead-letter with retry tracking, data lake partial-commit safety, MRN sequence-based generation, FHIR bundle transaction rollback, **FHIR R4 read/search endpoints** (Patient and Encounter), **alert threshold deletion with audit trail**, **FHIR API key rotation** (constant-time multi-key validation), **authorization failure logging** with Prometheus metrics, **JWT signing key validation** at startup, **token refresh and revocation** (short-lived access tokens with rotating refresh tokens, server-side logout, proactive frontend refresh), and **concurrency hardening** (transactional sepsis bundle creation, unique active encounter constraint). See [Implemented Phases](#implemented-phases) for the full breakdown. Guides: [dashboard-guide.md](docs/dashboard-guide.md) (technical), [clinical-testing-guide.md](docs/clinical-testing-guide.md) (doctors & nurses).
## Domain Model — How It Maps to a Real Clinical System
In a hospital, a patient presents for care and an encounter is opened. Bedside monitors and lab systems post observations continuously against that encounter — either directly via the REST API, through the FHIR R4 facade that maps HL7 FHIR resources from integration engines (Mirth Connect, Rhapsody), or via ward gateway edge nodes that buffer observations locally during connectivity loss and sync to the central API when the link recovers. The FHIR facade also exposes read and search interactions so EHR systems can query patient and encounter data back in standard FHIR R4 format. A rules engine evaluates each observation against configured thresholds and flags abnormal values as clinical alerts. Composite scoring engines (NEWS2, GCS, SOFA, qSOFA) aggregate multiple vitals and labs into acuity scores. The sepsis pathway follows Sepsis-3 consensus: qSOFA ≥ 2 creates a bedside screening alert recommending SOFA labs; when SOFA delta ≥ 2 from baseline confirms organ dysfunction, a `SOFA_SEPSIS` alert triggers the treatment bundle. Clinicians authenticate via JWT, and role-based access control (RBAC) gates every endpoint by clinical role (Nurse, Physician, Admin, Integration). Clinicians acknowledge and resolve alerts. If a critical alert goes unacknowledged for five minutes, the system escalates to the on-call backup. All clinical write actions — patient registration, alert acknowledgment, threshold changes, encounter transitions — are recorded in an append-only audit log with user identity, IP address, correlation ID, and before/after state. All events flow through Kafka so the Elasticsearch dashboard, scoring engines, and data lake writer consume the same stream independently.
In a hospital, a patient presents for care and an encounter is opened. Bedside monitors and lab systems post observations continuously against that encounter — either directly via the REST API, through the FHIR R4 facade that maps HL7 FHIR resources from integration engines (Mirth Connect, Rhapsody), or via ward gateway edge nodes that buffer observations locally during connectivity loss and sync to the central API when the link recovers. The FHIR facade also exposes read and search interactions so EHR systems can query patient and encounter data back in standard FHIR R4 format. A rules engine evaluates each observation against configured thresholds and flags abnormal values as clinical alerts. Composite scoring engines (NEWS2, GCS, SOFA, qSOFA) aggregate multiple vitals and labs into acuity scores. The sepsis pathway follows Sepsis-3 consensus: qSOFA ≥ 2 creates a bedside screening alert recommending SOFA labs; when SOFA delta ≥ 2 from baseline confirms organ dysfunction, a `SOFA_SEPSIS` alert triggers the treatment bundle. Clinicians authenticate via JWT with short-lived access tokens (15 min) and rotating refresh tokens (7 days), and role-based access control (RBAC) gates every endpoint by clinical role (Nurse, Physician, Admin, Integration). Clinicians acknowledge and resolve alerts. If a critical alert goes unacknowledged for five minutes, the system escalates to the on-call backup. All clinical write actions — patient registration, alert acknowledgment, threshold changes, encounter transitions — are recorded in an append-only audit log with user identity, IP address, correlation ID, and before/after state. All events flow through Kafka so the Elasticsearch dashboard, scoring engines, and data lake writer consume the same stream independently.
```
Patient ─────────────────────────── one patient = one MRN, many lifetime encounters
@@ -38,7 +38,7 @@ An `idempotencyKey` (partial unique index) prevents duplicate observations when
### ClinicalAlert
A `ClinicalAlert` is generated when an observation breaches a threshold, when the qSOFA engine detects two or more organ-dysfunction criteria (screening), when SOFA delta ≥ 2 from baseline confirms sepsis, or when the NEWS2 engine computes a medium/high-risk composite score (or a single-parameter score of 3). Lifecycle: `open → acknowledged → resolved` (or `escalated` after a five-minute NACK cycle through the RabbitMQ dead-letter queue). Alerts carry an audit trail: who acknowledged, when, and with what note. Only `SOFA_SEPSIS` alerts trigger automatic sepsis bundle creation.
A `ClinicalAlert` is generated when an observation breaches a threshold, when the qSOFA engine detects two or more organ-dysfunction criteria (screening), when SOFA delta ≥ 2 from baseline confirms sepsis, or when the NEWS2 engine computes a medium/high-risk composite score (or a single-parameter score of 3). Lifecycle: `open → acknowledged → resolved` (or `escalated` after a five-minute NACK cycle through the RabbitMQ dead-letter queue). Alerts carry an audit trail: who acknowledged, when, and with what note. Composite alerts from NEWS2, SOFA, GCS, and trend detection also carry an immutable JSONB `explanation` snapshot — score contributors, trend context, medication context, and a bedside narrative — frozen at alert creation time. The human-readable `details` string remains for backward compatibility. Only `SOFA_SEPSIS` alerts trigger automatic sepsis bundle creation.
### SepsisBundle
@@ -70,13 +70,13 @@ An `OutboxEvent` is written in the same transaction as any observation or alert,
- **Trend Detection Engine** — `TrendAnalyzerService` Kafka consumer (`trend-analyzer`) tracks rate-of-change for five vital parameters (`HEART_RATE`, `RESP_RATE`, `SYSTOLIC_BP`, `TEMP_C`, `SPO2`) using Redis sliding-window history; when velocity exceeds configured thresholds (e.g. 72→95 bpm in 30 min), creates a `RAPID_DETERIORATION` alert even if the current value is below warning thresholds; Prometheus `trend_alerts_total` and `trend_analysis_duration_seconds`
- **Alert Suppression Windows** — acknowledging a suppressible alert (`WARNING_*`, `NEWS2_WARNING`) sets a Redis key `suppress:{encounterId}:{alertType}` with a configurable TTL (default 30 min from `AlertSuppression` config; optional per-code override via `alert_thresholds.suppression_window_minutes`); `WarningEvaluator` and `News2Detector` check suppression before creating new warning alerts; critical alerts (`CRITICAL_*`, `NEWS2_EMERGENCY`, `SEPSIS_WARNING`, `RAPID_DETERIORATION`) are never suppressed; observations and NEWS2 scores continue to persist during suppression; Prometheus `alert_suppressions_total`
- **Medication Administration** — `POST /encounters/:id/medications` records drug administrations (name, dose, route, timestamp, administered-by); `GET /encounters/:id/medications` lists with optional `since` filter; `GET /medications/:id` detail; active-encounter guard; FluentValidation on request DTOs
- **Medication Correlation Annotations** — `MedicationCorrelationHelper` appends medication context to warning and NEWS2 alert details when a mapped drug was administered within the correlation window (default 90 min); drug-to-vital mappings in `MedicationCorrelation` config (`appsettings.json`); annotates rather than suppresses — alerts still fire; sepsis, trend, and critical sync-path alerts are never annotated; design rationale in `docs/decisions/medication-correlation-design.md`
- **Medication Correlation Annotations** — `MedicationCorrelationHelper` appends medication context to warning alert `details` when a mapped drug was administered within the correlation window (default 90 min); explainable alerts (NEWS2, SOFA, GCS, rapid deterioration) receive structured `MedicationContext` in the JSONB `explanation` via `TryGetContextAsync()`; drug-to-vital mappings in `MedicationCorrelation` config (`appsettings.json`); annotates rather than suppresses — alerts still fire; sepsis, trend, and critical sync-path alerts are never annotated; design rationale in `docs/decisions/medication-correlation-design.md`
- **Ward Dashboard APIs** — `GET /encounters` returns paginated `WardEncounterSummary` rows (patient name/MRN, room/bed, department, status, latest NEWS2 score, live qSOFA criteria count from Redis, sepsis bundle status, open alert count, SOFA score/delta, GCS score/classification, attending physician, admitted-at, last observation time); filterable by `status` and `department`; `GET /encounters/:id/qsofa/current` exposes Redis-backed qSOFA state; `GET /sepsis-bundles` lists bundles hospital-wide with optional `status` filter (returns `SepsisBundleSummary` with patient demographics, elements, and deadlines); CORS policy `Dashboard` allows configured origins (default `http://localhost:5173`)
- **Ward Dashboard Frontend** — Vue 3 SPA (`vigilcare-dashboard/`) with virtual ward table (multi-column sortable, patient search, quick-filters for critical/alerts/sepsis), patient detail (vitals, scores, alerts, orders, sepsis bundle, GCS entry form, SOFA score panel, patient banner with demographics/allergies/emergency contact, encounter timeline, vitals entry form for manual observation recording, discharge summary panel), alert center (global acknowledge/resolve with role-aware modal and acknowledgment note preview), department overview (unit-level snapshot cards with acuity bars, patient/alert/bundle counts per department), sepsis bundle board (real-time countdown timers, on-track/at-risk/overdue urgency sorting), critical alert banner with browser notifications and audible tone, shift handoff report generator (SBAR format with ward summary, exportable via print/PDF), vital sign trend charts with medication administration markers and local replay scrubbing, NEWS2 history chart, SOFA history chart with organ-system breakdown, GCS history chart with component tracking, qSOFA evaluation history, alert reasoning with optional medication context, clinician feedback on every alert, admin panels (threshold management, user management, audit log viewer, reconciliation viewer), gateway operations dashboard with degraded-mode banner, and alert quality analytics with quality charts; role-aware sidebar navigation; polls API every 510 s; guides in `docs/dashboard-guide.md` and `docs/clinical-testing-guide.md`
- **Ward Dashboard Frontend** — Vue 3 SPA (`vigilcare-dashboard/`) with virtual ward table (multi-column sortable, patient search, quick-filters for critical/alerts/sepsis), patient detail (vitals, scores, alerts, orders, sepsis bundle, GCS entry form, SOFA score panel, patient banner with demographics/allergies/emergency contact, encounter timeline, vitals entry form for manual observation recording, discharge summary panel), alert center (global acknowledge/resolve with role-aware modal and acknowledgment note preview), department overview (unit-level snapshot cards with acuity bars, patient/alert/bundle counts per department), sepsis bundle board (real-time countdown timers, on-track/at-risk/overdue urgency sorting), critical alert banner with browser notifications and audible tone, shift handoff report generator (SBAR format with ward summary, exportable via print/PDF), vital sign trend charts with medication administration markers and local replay scrubbing, NEWS2 history chart, SOFA history chart with organ-system breakdown, GCS history chart with component tracking, qSOFA evaluation history, structured alert reasoning (`AlertReasoning.vue` — score contributors, trend context, medication context, narrative summary from `explanation`), clinician feedback on every alert, admin panels (threshold management, user management, audit log viewer, reconciliation viewer), gateway operations dashboard with degraded-mode banner, and alert quality analytics with quality charts; role-aware sidebar navigation; polls API every 510 s; guides in `docs/dashboard-guide.md` and `docs/clinical-testing-guide.md`
- **FHIR R4 Inbound Facade** — `POST /fhir/R4/{Patient,Encounter,Observation,MedicationAdministration}` accepts FHIR R4 JSON resources (`application/fhir+json`); `POST /fhir/R4` processes transaction Bundles (Patient → Encounter → Observation in dependency order); `GET /fhir/R4/metadata` returns a CapabilityStatement; LOINC-to-internal code mapping (19 observation codes + SNOMED CT fallbacks); Fahrenheit-to-Celsius unit conversion; `ExternalResourceIdentifier` table links hospital MRNs and visit numbers to internal UUIDs for idempotent upserts; `FhirApiKeyOrJwtMiddleware` authenticates via JWT bearer or `X-Api-Key` header (supports multiple keys via `Fhir:ApiKeys` array for zero-downtime rotation; constant-time comparison via `CryptographicOperations.FixedTimeEquals`); `FhirExceptionFilter` returns FHIR `OperationOutcome` on errors; configurable identifier systems, department codes, and encounter class mappings via `Fhir` config section; Prometheus `fhir_ingest_total` and `fhir_mapping_errors_total`; integration guide for Mirth Connect HL7v2→FHIR channels in `docs/integration/mirth-fhir-channels.md`
- **FHIR R4 Read/Search** — `GET /fhir/R4/Patient/{id}` reads a Patient by internal ID; `GET /fhir/R4/Patient` searches by `identifier` (system|value) or lists all patients; `GET /fhir/R4/Encounter/{id}` reads an Encounter by internal ID; `GET /fhir/R4/Encounter` searches by `patient` (UUID) and/or `status` (`in-progress`, `finished`, `cancelled`); all return FHIR R4 JSON (`application/fhir+json`); search endpoints return `Bundle.type=searchset`; requires `fhir:read` permission (Admin and Integration roles); internal resources mapped back to FHIR via `PatientFhirMapper.ToFhirResponse` / `EncounterFhirMapper.ToFhirResponse` with hospital identifier resolution; Prometheus `fhir_read_total` counter with `resource_type`, `interaction`, `outcome` labels
- **Role-Based Access Control (RBAC)** — JWT bearer authentication (`POST /auth/login`); four clinical roles (`Nurse`, `Physician`, `Admin`, `Integration`) with 18 granular permissions (`patients:read`, `alerts:acknowledge`, `alerts:feedback`, `thresholds:write`, `fhir:ingest`, `fhir:read`, `audit:read`, etc.); `AuthorizePermission` attribute on every controller action; `PermissionAuthorizationHandler` resolves role → permission at runtime from `ClinicalRolePermissionMap` and logs authorization failures with structured details (user, role, permission, endpoint) plus `authorization_failures_total` Prometheus counter; `CurrentUserService` exposes authenticated identity (user ID, display name, role, IP address) to services; nurses and physicians get clinical read/write permissions; admins additionally get `thresholds:write`, `fhir:read`, `audit:read`, and `users:admin`; integration accounts get FHIR ingest and read access; FHIR endpoints accept both JWT and `X-Api-Key` authentication via `FhirApiKeyOrJwtMiddleware`; alert `acknowledgedBy` is set from the authenticated user identity, not the request body; startup validates JWT signing key is at least 256 bits (HMAC-SHA256 minimum); four seeded demo users (`nurse.demo`, `physician.demo`, `admin.demo`, `integration.mirth`); frontend login page with `localStorage` token persistence and automatic `Authorization: Bearer` header injection
- **Clinical Audit Logging** — append-only `clinical_audit_logs` table records clinical write actions with user identity, entity type/ID, before/after state (JSONB), reason, IP address, and correlation ID; ten audit actions (`THRESHOLD_CREATED`, `THRESHOLD_UPDATED`, `THRESHOLD_DELETED`, `ALERT_ACKNOWLEDGED`, `ALERT_RESOLVED`, `ENCOUNTER_STATUS_CHANGED`, `PATIENT_REGISTERED`, `SUPPRESSION_WINDOW_SET`, `USER_LOGIN`, `AUTHORIZATION_DENIED`); `AuditService` writes log entries inline with domain operations; `GET /audit-logs` admin-only query endpoint with filters by entity type, entity ID, user ID, action, and time range; indexed on entity type, entity ID, user ID, and timestamp
- **Role-Based Access Control (RBAC)** — JWT bearer authentication (`POST /auth/login`); four clinical roles (`Nurse`, `Physician`, `Admin`, `Integration`) with 18 granular permissions (`patients:read`, `alerts:acknowledge`, `alerts:feedback`, `thresholds:write`, `fhir:ingest`, `fhir:read`, `audit:read`, etc.); `AuthorizePermission` attribute on every controller action; `PermissionAuthorizationHandler` resolves role → permission at runtime from `ClinicalRolePermissionMap` and logs authorization failures with structured details (user, role, permission, endpoint) plus `authorization_failures_total` Prometheus counter; `CurrentUserService` exposes authenticated identity (user ID, display name, role, IP address) to services; nurses and physicians get clinical read/write permissions; admins additionally get `thresholds:write`, `fhir:read`, `audit:read`, and `users:admin`; integration accounts get FHIR ingest and read access; FHIR endpoints accept both JWT and `X-Api-Key` authentication via `FhirApiKeyOrJwtMiddleware`; alert `acknowledgedBy` is set from the authenticated user identity, not the request body; startup validates JWT signing key is at least 256 bits (HMAC-SHA256 minimum); **token refresh and revocation** — short-lived access tokens (15 min) paired with rotating opaque refresh tokens (7 days) stored in the `refresh_tokens` table; `POST /auth/refresh` exchanges a valid refresh token for a new access + refresh token pair (rotation on every use revokes the previous token); `POST /auth/logout` revokes the refresh token server-side with `USER_LOGOUT` audit log; frontend auto-refreshes 1 minute before expiry, retries on 401, and redirects to login when the refresh token is exhausted; logout button in header, sidebar, and mobile nav; four seeded demo users (`nurse.demo`, `physician.demo`, `admin.demo`, `integration.mirth`)
- **Clinical Audit Logging** — append-only `clinical_audit_logs` table records clinical write actions with user identity, entity type/ID, before/after state (JSONB), reason, IP address, and correlation ID; twelve audit actions (`THRESHOLD_CREATED`, `THRESHOLD_UPDATED`, `THRESHOLD_DELETED`, `ALERT_ACKNOWLEDGED`, `ALERT_RESOLVED`, `ENCOUNTER_STATUS_CHANGED`, `PATIENT_REGISTERED`, `SUPPRESSION_WINDOW_SET`, `USER_LOGIN`, `AUTHORIZATION_DENIED`, `USER_LOGOUT`, `TOKEN_REFRESHED`); `AuditService` writes log entries inline with domain operations; `GET /audit-logs` admin-only query endpoint with filters by entity type, entity ID, user ID, action, and time range; indexed on entity type, entity ID, user ID, and timestamp
- **Site & Gateway Registry** — `ClinicalSite` and `WardGateway` domain entities model ward edge nodes that buffer clinical data during connectivity loss; `POST /sites` creates clinical sites; `POST /sites/{siteId}/gateways` registers gateways under a site; `PATCH /gateways/{gatewayId}/heartbeat` (gateway API key auth) updates status (`ONLINE`, `DEGRADED`, `OFFLINE`) and reported buffer depth; `GET /sites/{siteId}/gateways` lists gateways with optional `?department=` filter; dual authentication — JWT + RBAC (`users:admin`) for admin CRUD, `GatewayApiKeyAuthenticationHandler` (`X-Api-Key` + `X-Gateway-Id`) for gateway heartbeat and future sync upload; constant-time key comparison via `CryptographicOperations.FixedTimeEquals`; `VigilCare.ClinicalContracts` shared class library with sync DTOs (`ClinicalSyncBatchRequest`, `SyncedObservation`, `SyncedAlertEvent`, `GatewayHeartbeatRequest`) consumed by both central API and ward gateway projects; Prometheus `ward_gateways_offline_gauge` and `ward_gateway_buffer_depth` via `WardGatewayMetricsCollector` (60s periodic); `GatewayRegistrySeeder` provides demo site and gateway for Docker Compose and tests; FluentValidation on all request DTOs; `GatewayRegistryTests` and `ClinicalContractsTests` integration tests
- **Ward Gateway Service** — `VigilCare.WardGateway` (`http://localhost:5081`) is a standalone ASP.NET Core 8 deployable with its own PostgreSQL, Redis, and RabbitMQ; ingests observations locally via `POST /encounters/:id/observations` with plausibility validation, Redis-cached threshold evaluation, and synchronous critical alert creation; `LocalWarningEvaluator` creates warning-range alerts; `BufferedSyncWriter` writes all clinical events to `buffered_sync_items` for central upload; `EncounterReplicaSyncService` pulls patient/encounter data from central API; `ThresholdCacheLoader` fetches thresholds from central into local Redis; `CentralReachabilityService` tracks central API connectivity; `GatewayHeartbeatService` reports status and buffer depth; `SyncUploaderService` batches and uploads buffered items when online; local RabbitMQ paging and escalation queues; `GET /encounters` ward list and `GET /encounters/:id` detail; `GET /health/live` and `GET /health/ready` (Redis, RabbitMQ, encounter replica readiness); Docker Compose `ward-gateway` profile
- **Health Check Endpoints** — `GET /health/live` (liveness — always returns 200 if the process is running) and `GET /health/ready` (readiness — checks PostgreSQL, Redis, Kafka, RabbitMQ, and Elasticsearch connectivity); both return structured JSON with per-check status and duration; anonymous access; suitable for Kubernetes probes and load balancer health checks
@@ -88,10 +88,12 @@ An `OutboxEvent` is written in the same transaction as any observation or alert,
- **FHIR Bundle Transaction Rollback** — `FhirBundleProcessor` wraps all bundle entry processing in a database transaction; on any entry failure, the transaction is rolled back and the response includes the `OperationOutcome` for the failed entry; prevents partial state from orphaned Patient/Encounter records
- **Clinician Feedback Mode** — six quick ratings per alert (useful, too early, too late, false positive, missing context, would act); optional notes; server-side `AlertFeedback` entity persisted per user per alert (`POST /alerts/{id}/feedback`); `alerts:feedback` permission for Nurse, Physician, and Admin roles; Feedback Summary with aggregate stats and JSON/CSV export; client-side persistence for product research
- **Alert Quality Analytics** — `AlertQualityAggregatorService` periodically computes per-alert-type quality metrics (acknowledgement rate, false positive rate, useful rate, would-act rate, avg seconds to acknowledge/resolve) into `alert_quality_metrics` table; `AlertQualityMetricsController` exposes `GET /alerts/quality-metrics` (time-range filterable, optional alert type) and `GET /alerts/quality-metrics/summary`; Grafana alert quality dashboard (`infra/grafana/dashboards/alert-quality-dashboard.json`); frontend `AlertQualityAnalytics.vue` with `AlertQualityChart.vue`; Prometheus `alert_quality_useful_rate` and `alert_quality_false_positive_rate` gauges
- **Explainable Alerts** — `AlertExplanation` value object (`ScoreContributor`, `TrendContext`, `MedicationContext`, `NarrativeSummary`) serialized as JSONB on `ClinicalAlert.Explanation` at creation time; `AlertExplanationBuilder` and contributor builders (NEWS2, SOFA, GCS, trend) assemble explanation from scoring outputs; `ClinicalAlertFactory` idempotent INSERT with explanation; NEWS2, SOFA, GCS, and `TrendDetector` wire explanation and include `explanation` in `alert.generated` outbox payloads; `AlertResponse` DTO exposes optional `Explanation` on GET/list/acknowledge/resolve; Elasticsearch indexes `NarrativeSummary`; data lake Parquet includes `explanation_json`; ward gateway `LocalClinicalAlert.ExplanationJson` synced via `ClinicalSyncBatchProcessor`; dashboard `AlertReasoning.vue` + `alertExplanation.js` composable render structured reasoning; simulator `ExpectedOutcomeValidator` supports `narrativeContains` on key scenarios; `ExplainableAlertsTests` (10 tests) + `run-phase34-verification.sh`
- **Degraded Operations Visibility** — `GatewayStaleDetectorService` auto-marks gateways OFFLINE when heartbeat exceeds configurable `StaleThresholdMinutes`; `OperationsController` (`GET /operations/gateways`, `GET /operations/gateways/{id}`, `GET /operations/sites/{siteId}/summary`) provides fleet management API; `DischargeSummaryService` with `GET /encounters/{id}/discharge-summary` (info) and `GET /encounters/{id}/discharge-summary/content` (MinIO PDF download); `DischargeSummaryPanel.vue` on patient detail; `DegradedModeBanner.vue` warns when gateways are offline; `GatewayOperations.vue` operations dashboard
- **User Management** — `UsersController` (`GET /users`, `POST /users`, `PATCH /users/{id}`) for admin user account CRUD; `UserService` with BCrypt password hashing; `UserManagementView.vue` with `UserFormModal.vue` (create/edit users, role assignment, active toggle)
- **Admin Dashboard Panels** — `ThresholdManagementView.vue` with `ThresholdFormModal.vue` (create/edit alert thresholds); `AuditLogView.vue` (filterable audit log viewer with action/entity/user/date filters); `ReconciliationView.vue` (safety finding viewer); sidebar navigation with role-aware admin section; `CollapsibleSection.vue` and `SeverityBadge.vue` UI components
- **Console Replay Simulator** — standalone `VigilCare.Simulator` .NET console app replays JSON scenario files against the live API with configurable speed (`--speed 0` instant, `60` = 60× faster); commands: `replay`, `replay-all`, `validate`, `dry-run`; optional `--poll` shows alerts, NEWS2, GCS, SOFA, and sepsis bundle state during replay; `--gateway` targets the ward gateway (`http://localhost:5081`) with `--encounter-id`, `--skip-setup`, and `--gateway-token`; `alert_ack` events poll for open alerts on central before acknowledging (handles async alert pipeline at `--speed 0`); twelve sample scenarios in `VigilCare.Simulator/Scenarios/List/` (including ward outage reconnect, GCS neurological decline, SOFA sepsis progression, and SpO₂/FiO₂ fallback); user guide in `docs/simulator-guide.md`
- **Console Replay Simulator** — standalone `VigilCare.Simulator` .NET console app replays JSON scenario files against the live API with configurable speed (`--speed 0` instant, `60` = 60× faster); commands: `replay`, `replay-all`, `validate`, `dry-run`, `mimic-list`, `mimic-generate`; optional `--poll` shows alerts, NEWS2, GCS, SOFA, and sepsis bundle state during replay; `--gateway` targets the ward gateway (`http://localhost:5081`) with `--encounter-id`, `--skip-setup`, and `--gateway-token`; `alert_ack` events poll for open alerts on central before acknowledging (handles async alert pipeline at `--speed 0`); `ExpectedOutcomeValidator` validates alert `narrativeContains` on key scenarios; twelve sample scenarios in `VigilCare.Simulator/Scenarios/List/` (including ward outage reconnect, GCS neurological decline, SOFA sepsis progression, and SpO₂/FiO₂ fallback); MIMIC-IV scenario generation from real ICU data; user guide in `docs/simulator-guide.md`
- **MIMIC-IV Scenario Generator** — offline CLI tool that reads MIMIC-IV CSV files (`docs/MIMIC-IV/`, 100 patients, 140 ICU stays, 668K chart events, 107K lab events) and generates VigilCare scenario JSONs; `mimic-list <dir>` displays a Spectre.Console table of available stays with demographics, care unit, LOS, and outcome; `mimic-generate <dir> --stay-id <id>` produces a scenario with options for `--max-hours`, `--no-medications`, `--no-labs`, `--validate`; streaming CSV parser (`MimicCsvReader`) filters 668K-row chartevents by stay_id and item ID set at the string level before allocating records; `MimicItemMap` maps 17 chart event items (vitals, GCS, FiO₂, PaO₂, labs) and 8 lab event items to VigilCare observation codes; GCS text labels ("Obeys Commands" → 6, "To Speech" → 3) resolved from `valuenum` with text-to-numeric fallback dictionary; Fahrenheit temperature converted to Celsius; blood pressure deduplication prefers non-invasive (NBP) over arterial (ABP); 10-observation-per-cluster limit enforced by priority-based splitting (vitals first, labs spill to next offset); medications from prescriptions with dose parsing; generated scenarios pass `ScenarioValidator` and replay through the standard `replay` command
- **RabbitMQ Notification Workers** — `NotificationPublisherService` reads `alert.generated` from Kafka and publishes paging jobs to `alerts.paging.queue`; `PagingWorkerService` sends the page and waits for acknowledgment; if no ack arrives before timeout it NACKs to `alerts.paging.dlq` with `x-message-ttl = 300000ms`; if the host is stopping, in-flight paging messages are NACKed with `requeue=true` so they are retried after restart and do not false-escalate; `EscalationWorkerService` pages the on-call backup and sets alert status to `escalated`; `DischargeSummaryWorkerService` reads `encounter.status.changed`, generates a discharge summary, and stores it in MinIO under `/discharge-summaries/{encounterId}/summary.pdf`
- **Data Lake Writer** — `DataLakeWriterService` (consumer group `data-lake-writer`) buffers `observation.recorded`, `alert.generated`, and `encounter.status.changed` events, flushes date-partitioned Parquet files to MinIO (`/observations/`, `/alerts/`, `/encounters/`), and commits Kafka offsets only for topic-partitions where all uploads succeeded; failed partition buffers are retained in memory and retried on the next flush cycle (prevents data loss from partial upload failures); shutdown flush uses an uncanceled token so MinIO writes complete on Ctrl+C; `kafka_partition` and `kafka_offset` columns provide audit lineage
- **Reconciliation Jobs** — three scheduled checks: (1) unacknowledged CRITICAL alerts older than 30 minutes, (2) pending orders without results after 4 hours, (3) active inpatients with no observation in 2 hours; each finding creates a `reconciliation_alerts` row and publishes to RabbitMQ
@@ -113,6 +115,7 @@ HTTP request
→ Controllers (REST API + FHIR R4 ingest + Site/Gateway registry)
→ Services
├── CurrentUserService (authenticated user identity from JWT claims)
├── AuthService (login, refresh token rotation, logout with revocation)
├── AuditService (append-only clinical_audit_logs on write actions)
├── PostgreSQL (EF Core — writes, keyed reads)
├── Redis (threshold cache, qSOFA state, NEWS2 parameter state, GCS state, SOFA lab cache, trend history, alert suppression keys)
@@ -202,7 +205,7 @@ VigilCareClinicalAPI/
├── Program.cs # Service registration, middleware, seed on startup
├── appsettings.json # Connection strings, Kafka, Elasticsearch, RabbitMQ, MinIO, Serilog, ReconciliationJobs
├── Controllers/
│ ├── AuthController.cs # JWT login + authenticated user profile (GET /auth/me)
│ ├── AuthController.cs # JWT login, token refresh, logout, authenticated user profile
│ ├── AuditLogsController.cs # Clinical audit log query (Admin only)
│ ├── PatientsController.cs # Patient CRUD, search by name/MRN
│ ├── EncountersController.cs # Encounter list (ward summary), get, status PATCH, timeline
@@ -210,7 +213,7 @@ VigilCareClinicalAPI/
│ ├── QsofaController.cs # Current qSOFA criteria count (Redis-backed) + cursor-paginated evaluation history
│ ├── ObservationsController.cs # Ingest POST, cursor-paginated GET
│ ├── AlertThresholdsController.cs # Threshold CRUD + cache invalidation
│ ├── AlertsController.cs # Alert list (global + per-encounter), acknowledge, resolve
│ ├── AlertsController.cs # Alert list (global + per-encounter), get by ID, acknowledge, resolve → AlertResponse
│ ├── OrdersController.cs # Order create, list, get, status transition, record result
│ ├── News2Controller.cs # Current NEWS2 score and cursor-paginated history
│ ├── GcsController.cs # Latest GCS score and cursor-paginated history per encounter
@@ -231,7 +234,7 @@ VigilCareClinicalAPI/
│ │ ├── Encounter.cs # Status machine; SetStatus() enforces transition matrix
│ │ ├── AlertThreshold.cs
│ │ ├── Observation.cs # Append-only; IdempotencyKey; partial unique index
│ │ ├── ClinicalAlert.cs # open → acknowledged → resolved / escalated
│ │ ├── ClinicalAlert.cs # open → acknowledged → resolved / escalated; JSONB Explanation snapshot
│ │ ├── Order.cs
│ │ ├── News2Score.cs # Composite score with seven component scores + risk level
│ │ ├── GcsScore.cs # Eye/verbal/motor components, total, classification
@@ -246,13 +249,16 @@ VigilCareClinicalAPI/
│ │ ├── ClinicalSite.cs # Hospital site with site code, name, address
│ │ ├── WardGateway.cs # Ward edge node with status, buffer depth, heartbeat, sync timestamps
│ │ ├── ClinicalUser.cs # Username, BCrypt password hash, display name, role, active flag
│ │ ├── RefreshToken.cs # Opaque refresh token with user FK, expiry, revocation timestamp
│ │ ├── ClinicalAuditLog.cs # Append-only audit trail: action, entity, user, before/after JSONB, IP, correlation ID
│ │ ├── AlertFeedback.cs # Clinician feedback per alert (one per user per alert)
│ │ └── AlertQualityMetric.cs # Per-alert-type quality metric snapshots (acknowledgement/false-positive/useful rates)
│ ├── ValueObjects/
│ │ └── AlertExplanation.cs # ScoreContributor, TrendContext, MedicationContext, NarrativeSummary
│ └── Enums/
│ ├── EncounterStatus.cs # Scheduled, Active, Discharged, Cancelled
│ ├── ClinicalRole.cs # Nurse, Physician, Admin, Integration
│ ├── AuditAction.cs # ThresholdCreated/Updated/Deleted, AlertAcknowledged/Resolved, EncounterStatusChanged, PatientRegistered, SuppressionWindowSet, UserLogin, AuthorizationDenied
│ ├── AuditAction.cs # ThresholdCreated/Updated/Deleted, AlertAcknowledged/Resolved, EncounterStatusChanged, PatientRegistered, SuppressionWindowSet, UserLogin, AuthorizationDenied, UserLogout, TokenRefreshed
│ ├── EncounterType.cs # Inpatient, Outpatient, Emergency
│ ├── AlertSeverity.cs # Warning, Critical
│ ├── AlertStatus.cs # Open, Acknowledged, Resolved, Escalated
@@ -297,7 +303,7 @@ VigilCareClinicalAPI/
│ └── GatewayApiKeyAuthenticationHandler.cs # X-Api-Key + X-Gateway-Id auth for gateway heartbeat/sync routes
├── Services/
│ ├── Interfaces/ # IPatientService, IEncounterService, IAuditService, IAuthService, ICurrentUserService, ISiteService, IGatewayRegistryService, …
│ ├── AuthService.cs # Login (BCrypt verify), JWT generation, login audit log
│ ├── AuthService.cs # Login (BCrypt verify), JWT generation, refresh token rotation, logout revocation, audit logging
│ ├── AuditService.cs # Append-only clinical audit log writer (user, entity, before/after, IP, correlation ID)
│ ├── CurrentUserService.cs # Extracts authenticated user identity from JWT claims (HttpContext)
│ ├── PatientService.cs
@@ -305,7 +311,7 @@ VigilCareClinicalAPI/
│ ├── AlertThresholdService.cs # CRUD + Redis write-through invalidation
│ ├── ObservationService.cs # Ingest transaction: idempotency → plausibility → threshold → alert → outbox; emits Prometheus counters
│ ├── ObservationQueryService.cs # Cursor-paginated history
│ ├── AlertService.cs # Acknowledge (sets suppression), resolve, list
│ ├── AlertService.cs # Acknowledge (sets suppression), resolve, list → AlertResponse with optional Explanation
│ ├── AlertSuppressionService.cs # Redis suppress:{enc}:{type} TTL keys
│ ├── OrderService.cs # Order lifecycle; status machine; calls SepsisBundleService.OnOrderResultedAsync on result
│ ├── News2Service.cs # Current score + cursor-paginated history from PostgreSQL
@@ -324,11 +330,14 @@ VigilCareClinicalAPI/
│ ├── WarningEvaluator.cs # Warning-range evaluation; suppression + medication annotation; idempotent INSERT
│ ├── AnalyticsService.cs # Elasticsearch query wrappers
│ └── PlausibilityValidator.cs # Per-code numeric range guard
├── Alerts/
│ ├── ClinicalAlertFactory.cs # Idempotent alert INSERT with explanation JSON; outbox payload serialization
│ └── AlertExplanationBuilder.cs # Assembles explanation from contributors, trend, medication context
├── Trend/
│ ├── TrendCalculator.cs # Pure static rate-of-change logic
│ └── TrendDetector.cs # Redis history + RAPID_DETERIORATION alert creation
├── Medication/
│ └── MedicationCorrelationHelper.cs # Appends drug context to warning/NEWS2 alert details
│ └── MedicationCorrelationHelper.cs # String annotation on warning details; structured MedicationContext for explanations
├── Validators/ # FluentValidation — RegisterPatient, OpenEncounter, IngestObservation, CreateMedicationAdministration, CreateSiteRequest, RegisterGatewayRequest, GatewayHeartbeatRequest, …
├── Observability/
│ └── Metrics/
@@ -379,7 +388,7 @@ VigilCareClinicalAPI/
│ ├── MedicationCorrelationOptions.cs # Drug-vital mappings + correlation window
│ ├── PatientOptions.cs # MRN prefix + digit count for sequence-based generation
│ ├── FhirOptions.cs # API key (single + rotation array), identifier systems, department/class maps, defaults
│ ├── JwtOptions.cs # Issuer, audience, signing key, expiration (default 8 hours)
│ ├── JwtOptions.cs # Issuer, audience, signing key, access token expiration (15 min), refresh token expiration (7 days)
│ ├── DashboardOptions.cs # CORS origins for ward dashboard frontend
│ ├── GatewayMonitoringOptions.cs # Stale gateway detection interval and threshold
│ └── AlertQualityOptions.cs # Alert quality aggregation interval
@@ -422,7 +431,7 @@ VigilCareClinicalAPI/
│ └── Sepsis/QsofaResult.cs, QsofaOutcome.cs # qSOFA detector result and screening outcome enum
├── Data/
│ ├── AppDbContext.cs # EF Core context — entity configs, indexes, constraints
│ ├── Configurations/ # IEntityTypeConfiguration per entity; ClinicalUserConfiguration, ClinicalAuditLogConfiguration, ClinicalSiteConfiguration, WardGatewayConfiguration, QsofaEvaluationConfiguration, AlertFeedbackConfiguration, AlertQualityMetricConfiguration; ElasticsearchOptions, ElasticIndexOptions
│ ├── Configurations/ # IEntityTypeConfiguration per entity; ClinicalUserConfiguration, RefreshTokenConfiguration, ClinicalAuditLogConfiguration, ClinicalSiteConfiguration, WardGatewayConfiguration, QsofaEvaluationConfiguration, AlertFeedbackConfiguration, AlertQualityMetricConfiguration; ElasticsearchOptions, ElasticIndexOptions
│ └── Seed/
│ ├── DataSeeder.cs # Seeds patients, encounters, thresholds, observations
│ ├── GatewayRegistrySeeder.cs # Seeds demo site (SITE-DEMO) and gateway (GW-ICU-3B) with fixed GUIDs
@@ -485,7 +494,8 @@ tests/
├── OperationsApiTests.cs # Operations fleet listing, gateway detail, site summary
├── Helpers/GatewayAuthHelper.cs # WithGatewayApiKey extension method for test clients
├── Alerts/
── AlertQualityAnalyticsTests.cs # Alert feedback submission, quality aggregation, metrics API
── AlertQualityAnalyticsTests.cs # Alert feedback submission, quality aggregation, metrics API
│ └── ExplainableAlertsTests.cs # Explanation JSONB, AlertResponse mapping, medication context, legacy null
├── Auth/
│ └── RbacTests.cs # RBAC — unauthenticated 401, nurse 403 on threshold write, admin audit log creation
└── Fhir/
@@ -562,34 +572,44 @@ VigilCare.WardGateway.Tests/ # Phase 21 — ward gateway i
├── WardGatewayLocalPathTests.cs # Local observation ingest, warning alerts, buffered sync items
└── WardGatewayPartitionTests.cs # Network partition simulation — offline buffering and sync upload
VigilCare.Simulator/ # Phase 16 — console replay simulator (HTTP-only, no direct DB/Kafka)
├── Program.cs # CLI: replay, replay-all, validate, dry-run
VigilCare.Simulator/ # Phase 16, 29, 34, 35 — console replay simulator (HTTP-only, no direct DB/Kafka)
├── Program.cs # CLI: replay, replay-all, validate, dry-run, mimic-list, mimic-generate
├── Commands/ # System.CommandLine command handlers
├── Client/VigilCareApiClient.cs # Typed HTTP client for all API endpoints (incl. GCS, SOFA)
├── Client/
│ ├── VigilCareApiClient.cs # Typed HTTP client for all API endpoints (incl. GCS, SOFA)
│ └── Models/AlertExplanation.cs # Explanation DTO for poll/validation
├── Engine/ReplayEngine.cs # Scenario replay with speed multiplier + event logging
├── Output/SimulatorConsole.cs # Colored output with GCS/SOFA score display
├── Output/SimulatorConsole.cs # Colored output with GCS/SOFA score + explanation display
├── Polling/ApiPoller.cs # Optional post-event alert/score/bundle/GCS/SOFA polling
├── Scenarios/ # schema.json, ScenarioLoader, ScenarioValidator
└── Scenarios/List/ # Twelve sample scenarios (sepsis, GCS, SOFA, NEWS2, stable, ward outage, …)
├── Mimic/ # Phase 35 — MIMIC-IV scenario generator
│ ├── MimicCsvReader.cs # Streaming CSV parser with header-index lookup and filter predicate
│ ├── MimicItemMap.cs # MIMIC item ID → VigilCare observation code mappings (17 chart + 8 lab)
│ ├── MimicCareUnitMap.cs # ICU care unit → department + tag mapping
│ ├── MimicDataLoader.cs # Data access layer with streaming filters for chartevents/labevents
│ ├── MimicScenarioBuilder.cs # Core algorithm: BP dedup, cluster limits, offset conversion
│ ├── MimicListCommand.cs # CLI: mimic-list — Spectre.Console table of available stays
│ └── MimicGenerateCommand.cs # CLI: mimic-generate — produces scenario JSON from a stay ID
├── Scenarios/ # schema.json, ScenarioLoader, ScenarioValidator, ExpectedOutcomeValidator
└── Scenarios/List/ # Twelve sample scenarios + MIMIC-generated scenarios
vigilcare-dashboard/ # Phases 1719, 22, 23, 2728, 31, 33 — Vue 3 ward dashboard SPA
vigilcare-dashboard/ # Phases 1719, 22, 23, 2728, 31, 3334 — Vue 3 ward dashboard SPA
├── src/
│ ├── api/ # HTTP client (auto Bearer header), encounters, clinical (GCS, SOFA, qSOFA history), alerts, analytics, sepsis, thresholds, users, audit, reconciliation, operations, alertQuality, normalize
│ ├── api/ # HTTP client (auto Bearer header, 401 auto-refresh), encounters, clinical (GCS, SOFA, qSOFA history), alerts, analytics, sepsis, thresholds, users, audit, reconciliation, operations, alertQuality, normalize
│ ├── components/
│ │ ├── admin/ # ThresholdFormModal, UserFormModal (admin CRUD modals)
│ │ ├── alerts/ # AlertCard, AcknowledgeModal (role-aware), CriticalAlertBanner (browser notifications + audible tone)
│ │ ├── alerts/ # AlertCard, AlertReasoning (structured explanation), AcknowledgeModal (role-aware), CriticalAlertBanner (browser notifications + audible tone)
│ │ ├── charts/ # SofaHistory, GcsHistory, QsofaHistory, VitalChart with medication markers, AlertQualityChart
│ │ ├── departments/ # DepartmentCard, AcuityBar (unit-level snapshot)
│ │ ├── feedback/ # FeedbackButtons, FeedbackSummary
│ │ ├── layout/ # AppShell, AppHeader, AppSidebar (role-aware admin section)
│ │ ├── layout/ # AppShell, AppHeader (user + logout), AppSidebar (user + logout + role-aware admin), MobileNav (logout)
│ │ ├── patient/ # GcsEntryForm, SofaScorePanel, PatientBanner, EncounterTimeline, VitalsEntryForm, VitalsPanel, AlertsList, DischargeSummaryPanel
│ │ ├── replay/ # ReplayControls
│ │ ├── sepsis/ # SepsisBundleTable, SepsisBundleRow, SepsisBundleCard (countdown timer)
│ │ ├── ward/ # WardTable (sortable headers), PatientRow, PatientCard, WardToolbar, SortableHeader, HandoffReport (SBAR + print)
│ │ └── ui/ # Button, Card, Badge, Skeleton, EmptyState, Modal, CollapsibleSection, SeverityBadge, DegradedModeBanner
│ ├── composables/ # useChartData, useReplayControls, usePolling, useFeedback, useGcs, useSofa, useApiMode, useChartTheme, useFocusTrap, chartFormat, patientFormat, timelineFormat, chartMedications, wardSort, wardFilter, departmentFormat, sepsisFormat, alertAcknowledge, criticalAlertDetect, useAlertNotification, useCriticalAlertPolling, handoffReport, vitalsForm, roleAccess, auditFormat, reconciliationFormat, thresholdForm, userForm
│ ├── composables/ # useChartData, useReplayControls, usePolling, useFeedback, useGcs, useSofa, useApiMode, useChartTheme, useFocusTrap, chartFormat, patientFormat, timelineFormat, chartMedications, wardSort, wardFilter, departmentFormat, sepsisFormat, alertAcknowledge, alertExplanation, criticalAlertDetect, useAlertNotification, useCriticalAlertPolling, handoffReport, vitalsForm, roleAccess, auditFormat, reconciliationFormat, thresholdForm, userForm
│ ├── plugins/ # medicationMarkerPlugin (Chart.js plugin for medication administration markers on vital charts)
│ ├── stores/ # Pinia — ward (sort + filter + search), alerts (banner + polling), settings (sort prefs + sound mute), feedback, scoring, auth, departments, sepsis, operationsStore, alertQuality
│ ├── stores/ # Pinia — ward (sort + filter + search), alerts (banner + polling), settings (sort prefs + sound mute), feedback, scoring, auth (login + refresh token rotation + logout + expiry redirect), departments, sepsis, operationsStore, alertQuality
│ ├── views/ # LoginView, WardDashboard, PatientDetail, AlertCenter, FeedbackSummary, DepartmentOverviewView, SepsisBoardView, ThresholdManagementView, UserManagementView, AuditLogView, ReconciliationView, GatewayOperations, AlertQualityAnalytics
│ └── __tests__/ # Vitest — tests (store, feedback, replay, charts, alerts, ward, GCS, SOFA, qSOFA, PatientBanner, EncounterTimeline, patientFormat, timelineFormat, chartMedications, wardSort, wardFilter, departmentFormat, sepsisFormat, alertAcknowledge, criticalAlertDetect, HandoffReport, handoffReport, VitalsEntryForm, vitalsForm, useAlertStore, useWardStore, DepartmentOverviewView, SepsisBoardView, AcknowledgeModal, CriticalAlertBanner, roleAccess, ThresholdManagementView, thresholdForm, DischargeSummaryPanel, GatewayOperations, alertQuality)
├── vite.config.js
@@ -623,6 +643,7 @@ scripts/
├── run-phase31-verification.sh # Phase 31 — RBAC integration tests + JWT login + audit log query
├── run-phase23-verification.sh # Phase 23 — Degraded operations visibility + gateway fleet + admin panels
├── run-phase33-verification.sh # Phase 33 — Alert quality analytics integration tests
├── run-phase34-verification.sh # Phase 34 — Explainable alerts integration tests
├── demo-network-partition.sh # Gateway network partition demo script
└── mint-gateway-jwt.sh # JWT minting helper for gateway testing
@@ -820,6 +841,21 @@ dotnet run --project VigilCare.Simulator -- replay \
Other commands: `validate <file>`, `dry-run <file>`, `replay-all <directory>`. See `docs/simulator-guide.md` for the full user guide.
**MIMIC-IV real patient data (Phase 35):** generate and replay scenarios from de-identified ICU records:
```bash
# List 140 available ICU stays across 100 patients
dotnet run --project VigilCare.Simulator -- mimic-list docs/MIMIC-IV/
# Generate a 24-hour scenario from a CVICU patient
dotnet run --project VigilCare.Simulator -- mimic-generate docs/MIMIC-IV/ \
--stay-id 32604416 --max-hours 24 --validate
# Replay the generated scenario
dotnet run --project VigilCare.Simulator -- replay \
VigilCare.Simulator/Scenarios/List/mimic-s32604416.json --speed 0 --poll
```
**Ward outage reconnect (Phase 24):** scenario `ward-outage-reconnect-01.json` exercises critical hyperkalemia alerting and nurse acknowledgment during a central outage, then sync back to central when connectivity returns. Manual procedure in `docs/simulator-guide.md` §10; automated end-to-end check:
```bash
@@ -914,6 +950,7 @@ With the API running (`dotnet run`) and Docker Compose up:
./scripts/run-phase31-verification.sh # RBAC integration tests + JWT login + audit log query
./scripts/run-phase23-verification.sh # Degraded operations visibility + gateway fleet + admin panels
./scripts/run-phase33-verification.sh # Alert quality analytics integration tests
./scripts/run-phase34-verification.sh # Explainable alerts integration tests
```
Phase 25 — GCS scoring (requires running API + Docker Compose; set an active encounter UUID):
@@ -980,6 +1017,12 @@ Phase 33 alert quality analytics tests only:
dotnet test --filter "FullyQualifiedName~AlertQuality"
```
Phase 34 explainable alerts tests only:
```bash
dotnet test --filter "FullyQualifiedName~ExplainableAlerts"
```
Per-phase test runners (subset of `dotnet test`):
```bash
@@ -1231,11 +1274,13 @@ Uses cursor pagination on `(recorded_at DESC, id DESC)` — offset pagination wo
|---|---|---|
| GET | `/encounters/{id}/alerts` | Paginated alert list for an encounter |
| GET | `/alerts` | Global alert list; optional `status`, `severity`, `department` filter |
| GET | `/alerts/{id}` | Alert detail |
| POST | `/alerts/{id}/acknowledge` | Acknowledge with clinician ID and optional note |
| POST | `/alerts/{id}/resolve` | Resolve (must be acknowledged first) |
| GET | `/alerts/{id}` | Alert detail (`AlertResponse` with optional `explanation`) |
| POST | `/alerts/{id}/acknowledge` | Acknowledge with clinician ID and optional note; returns `AlertResponse` |
| POST | `/alerts/{id}/resolve` | Resolve (must be acknowledged first); returns `AlertResponse` |
| POST | `/alerts/{id}/feedback` | Submit clinician feedback (one per user per alert); requires `alerts:feedback` |
**Alert response shape:** list, get, acknowledge, and resolve endpoints return `AlertResponse` — alert fields plus optional `explanation` (`scoreContributors`, `trend`, `medicationContext`, `narrativeSummary`). Omitted on legacy and threshold-only alerts.
**Alert lifecycle:**
```
@@ -1416,10 +1461,12 @@ When a correlated drug was given within the `MedicationCorrelation.CorrelationWi
### Authentication
| Method | Path | Description |
|---|---|---|
| POST | `/auth/login` | Authenticate with username/password; returns JWT bearer token |
| GET | `/auth/me` | Returns the authenticated user's profile (user ID, username, display name, role) |
| Method | Path | Auth | Description |
|---|---|---|---|
| POST | `/auth/login` | Anonymous | Authenticate with username/password; returns access + refresh tokens |
| POST | `/auth/refresh` | Anonymous | Exchange a valid refresh token for a new access + refresh token pair |
| POST | `/auth/logout` | JWT | Revoke the refresh token and end the session |
| GET | `/auth/me` | JWT | Returns the authenticated user's profile (user ID, username, display name, role) |
**POST `/auth/login` body:**
@@ -1428,17 +1475,42 @@ When a correlated drug was given within the `MedicationCorrelation.CorrelationWi
| `username` | string | yes | Username |
| `password` | string | yes | Password |
**Response:**
**Login response:**
| Field | Type | Description |
|---|---|---|
| `accessToken` | string | JWT bearer token |
| `expiresAt` | DateTimeOffset | Token expiration (default 8 hours) |
| `accessToken` | string | JWT bearer token (default 15 min) |
| `refreshToken` | string | Opaque refresh token (default 7 days) |
| `expiresAt` | DateTimeOffset | Access token expiration |
| `userId` | Guid | User ID |
| `username` | string | Username |
| `displayName` | string | Display name |
| `role` | string | `NURSE`, `PHYSICIAN`, `ADMIN`, `INTEGRATION` |
**POST `/auth/refresh` body:**
| Field | Type | Required | Description |
|---|---|---|---|
| `refreshToken` | string | yes | The current refresh token |
**Refresh response:**
| Field | Type | Description |
|---|---|---|
| `accessToken` | string | New JWT bearer token |
| `refreshToken` | string | New refresh token (previous one is revoked) |
| `expiresAt` | DateTimeOffset | New access token expiration |
Refresh tokens rotate on every use — each call revokes the previous refresh token and issues a new one. If the refresh token is expired, revoked, or the user account is deactivated, the endpoint returns 422 and the client must re-authenticate via login.
**POST `/auth/logout` body:**
| Field | Type | Required | Description |
|---|---|---|---|
| `refreshToken` | string | yes | The refresh token to revoke |
Returns `204 No Content`. Revokes the refresh token server-side and creates a `USER_LOGOUT` audit log entry. The access token remains valid until its natural expiration (15 min max).
**Seeded demo users:**
| Username | Password | Role |
@@ -1481,7 +1553,7 @@ All endpoints except `POST /auth/login` and `GET /fhir/R4/metadata` require auth
**GET `/audit-logs` query params:** `entityType`, `entityId`, `userId`, `action`, `from`, `to`, `page`, `pageSize`
**Audit actions:** `THRESHOLD_CREATED`, `THRESHOLD_UPDATED`, `THRESHOLD_DELETED`, `ALERT_ACKNOWLEDGED`, `ALERT_RESOLVED`, `ENCOUNTER_STATUS_CHANGED`, `PATIENT_REGISTERED`, `SUPPRESSION_WINDOW_SET`, `USER_LOGIN`, `AUTHORIZATION_DENIED`
**Audit actions:** `THRESHOLD_CREATED`, `THRESHOLD_UPDATED`, `THRESHOLD_DELETED`, `ALERT_ACKNOWLEDGED`, `ALERT_RESOLVED`, `ENCOUNTER_STATUS_CHANGED`, `PATIENT_REGISTERED`, `SUPPRESSION_WINDOW_SET`, `USER_LOGIN`, `AUTHORIZATION_DENIED`, `USER_LOGOUT`, `TOKEN_REFRESHED`
Each audit log entry includes `action`, `entityType`, `entityId`, `userId`, `userDisplayName`, `previousValueJson` (JSONB), `newValueJson` (JSONB), `reason`, `ipAddress`, `correlationId`, and `createdAt`.
@@ -1656,6 +1728,7 @@ observationId Guid? FK → Observation (null for NEWS2, GCS, SOFA composite
alertType string e.g. CRITICAL_HEART_RATE, QSOFA_SCREEN, SOFA_SEPSIS, NEWS2_WARNING, NEWS2_EMERGENCY, GCS_CRITICAL
severity string WARNING | CRITICAL
details text required
explanation jsonb? immutable structured explanation snapshot (score contributors, trend, medication context, narrative); null on legacy/threshold-only alerts
observationCode string? observation code that triggered this alert (e.g. HEART_RATE) — enables direct lookups without LIKE pattern matching
status string open | acknowledged | resolved | escalated (default: open)
acknowledgedAt DateTimeOffset?
@@ -1846,11 +1919,24 @@ createdAt DateTimeOffset
lastLoginAt DateTimeOffset?
```
### RefreshToken
```
id Guid PK
token string required, unique (max 256) — opaque base64 token (64 random bytes)
userId Guid FK → ClinicalUser (CASCADE)
expiresAt DateTimeOffset required
createdAt DateTimeOffset
revokedAt DateTimeOffset? — set on refresh rotation or explicit logout
```
Indexes: unique `(token)`, `(user_id)`
### ClinicalAuditLog
```
id Guid PK
action string required (max 50) — THRESHOLD_CREATED | THRESHOLD_UPDATED | THRESHOLD_DELETED | ALERT_ACKNOWLEDGED | ALERT_RESOLVED | ENCOUNTER_STATUS_CHANGED | PATIENT_REGISTERED | SUPPRESSION_WINDOW_SET | USER_LOGIN | AUTHORIZATION_DENIED
action string required (max 50) — THRESHOLD_CREATED | THRESHOLD_UPDATED | THRESHOLD_DELETED | ALERT_ACKNOWLEDGED | ALERT_RESOLVED | ENCOUNTER_STATUS_CHANGED | PATIENT_REGISTERED | SUPPRESSION_WINDOW_SET | USER_LOGIN | AUTHORIZATION_DENIED | USER_LOGOUT | TOKEN_REFRESHED
entityType string required (max 100) — e.g. AlertThreshold, ClinicalAlert, Encounter, Patient, ClinicalUser
entityId Guid required
userId Guid? FK → ClinicalUser (null for system-initiated actions)
@@ -2206,7 +2292,7 @@ Observation history uses cursor pagination on `(recorded_at DESC, id DESC)`. Off
## Implemented Phases
Thirty-one phases from the project roadmap are implemented and verified, including the **Site & Gateway Registry** (Phase 20), the **Ward Gateway Service** (Phase 21), the **Dashboard Gap Analysis Fixes** (Phase 22), the **Degraded Operations Visibility** (Phase 23), the **Sepsis-3 clinical refactor** (Phases 2729), the **FHIR R4 Inbound Facade** (Phase 30), **RBAC with clinical audit logging** (Phase 31), the **Alert Quality Analytics** (Phase 33), and the **Enhanced Dashboard** (department overview, sepsis bundle board, critical alert notifications, shift handoff reports, vitals entry, sortable/filterable ward table). Integration tests (`dotnet test`) and per-phase verification scripts cover Phases 815, 2023, 2531, 33. Phases 1719 add the Vue dashboard and clinician feedback (Vitest in `vigilcare-dashboard/`).
Thirty-three phases from the project roadmap are implemented and verified, including the **Site & Gateway Registry** (Phase 20), the **Ward Gateway Service** (Phase 21), the **Dashboard Gap Analysis Fixes** (Phase 22), the **Degraded Operations Visibility** (Phase 23), the **Sepsis-3 clinical refactor** (Phases 2729), the **FHIR R4 Inbound Facade** (Phase 30), **RBAC with clinical audit logging** (Phase 31), the **Alert Quality Analytics** (Phase 33), the **Explainable Alerts** (Phase 34), the **MIMIC-IV Replay Scenario Generator** (Phase 35), and the **Enhanced Dashboard** (department overview, sepsis bundle board, critical alert notifications, shift handoff reports, vitals entry, sortable/filterable ward table). Integration tests (`dotnet test`) and per-phase verification scripts cover Phases 815, 2023, 2531, 3335. Phases 1719 add the Vue dashboard and clinician feedback (Vitest in `vigilcare-dashboard/`).
| Phase | Feature | Status |
|---|---|---|
@@ -2238,11 +2324,13 @@ Thirty-one phases from the project roadmap are implemented and verified, includi
| 28 | **Frontend GCS + SOFA + sepsis UI refactor** — `GcsEntryForm.vue` (bedside GCS component entry); `SofaScorePanel.vue` (organ-system breakdown with staleness indicators); `useGcs` / `useSofa` composables; `scoring` Pinia store; `ScoresPanel` updated with GCS/SOFA display; `SepsisBundlePanel` and `AlertReasoning` refactored for Sepsis-3 alert types; Vitest tests for GCS entry, SOFA panel, scores panel, alert labels; `run-phase28-verification.sh` | Done |
| 29 | **Simulator scenario expansion + clinical validation** — three new scenarios (`neurological-decline-gcs-01`, `sepsis-sofa-progression-01`, `sofa-partial-spo2-fallback-01`); existing scenarios enriched with GCS/SOFA observations; `ScenarioReplayHelper` for end-to-end test replay; `ClinicalRefactorEndToEndTests` validates qSOFA screen → SOFA labs → bundle workflow; simulator polls GCS/SOFA scores; `run-phase29-verification.sh` | Done |
| 30 | **FHIR R4 Inbound Facade** — `FhirIngestController` (`POST /fhir/R4/{Patient,Encounter,Observation,MedicationAdministration}`); `FhirMetadataController` (CapabilityStatement); `FhirBundleProcessor` (transaction Bundles in dependency order); `LoincCodeMapper` (19 LOINC + 3 SNOMED CT → internal codes); `FhirUnitConverter` (°F→°C); `ExternalResourceIdentifier` table + `ExternalIdentifierService` for hospital MRN/visit number ↔ internal UUID linking; `FhirApiKeyMiddleware` (`X-Api-Key` auth); `FhirExceptionFilter` (→ OperationOutcome); `PatientFhirMapper`, `EncounterFhirMapper`, `ObservationFhirMapper`, `MedicationAdministrationFhirMapper`, `FhirReferenceResolver`; idempotent patient/encounter upserts (`RegisterOrUpdateByIdentifierAsync`, `OpenOrUpdateByIdentifierAsync`); configurable identifier systems, department codes, encounter class maps (`FhirOptions`); Prometheus `fhir_ingest_total`, `fhir_mapping_errors_total`; Mirth Connect integration guide; `FhirIngestTests`; `run-phase30-verification.sh` | Done |
| 31 | **RBAC + Clinical Audit Logging** — JWT bearer authentication (`AuthService`, `AuthController`); four clinical roles (`Nurse`, `Physician`, `Admin`, `Integration`) with 18 granular permissions; `AuthorizePermission` attribute on all controller actions; `PermissionAuthorizationHandler` + `PermissionPolicyProvider` resolve `perm:*` policies; `CurrentUserService` extracts identity from JWT claims; `ClinicalUser` entity with BCrypt password hashing; `ClinicalAuditLog` append-only table with before/after JSONB, user identity, IP, and correlation ID; `AuditService` writes log entries on clinical write actions (10 audit actions); `AuditLogsController` admin-only query with filters; `FhirApiKeyOrJwtMiddleware` dual auth for FHIR routes (JWT or X-Api-Key with multi-key rotation); alert `acknowledgedBy` set from authenticated user, not request body; four seeded demo users; frontend `LoginView` + `auth` Pinia store with `localStorage` token persistence; Vue router auth guard; `RbacTests`; `run-phase31-verification.sh` | Done |
| 31 | **RBAC + Clinical Audit Logging + Token Refresh** — JWT bearer authentication (`AuthService`, `AuthController`); four clinical roles (`Nurse`, `Physician`, `Admin`, `Integration`) with 18 granular permissions; `AuthorizePermission` attribute on all controller actions; `PermissionAuthorizationHandler` + `PermissionPolicyProvider` resolve `perm:*` policies; `CurrentUserService` extracts identity from JWT claims; `ClinicalUser` entity with BCrypt password hashing; `RefreshToken` entity with DB-backed opaque token storage, rotation on use, and server-side revocation; short-lived access tokens (15 min) paired with long-lived refresh tokens (7 days); `POST /auth/refresh` and `POST /auth/logout` endpoints; `ClinicalAuditLog` append-only table with before/after JSONB, user identity, IP, and correlation ID; `AuditService` writes log entries on clinical write actions (12 audit actions including `USER_LOGOUT` and `TOKEN_REFRESHED`); `AuditLogsController` admin-only query with filters; `FhirApiKeyOrJwtMiddleware` dual auth for FHIR routes (JWT or X-Api-Key with multi-key rotation); alert `acknowledgedBy` set from authenticated user, not request body; four seeded demo users; frontend `LoginView` + `auth` Pinia store with proactive token refresh, 401 auto-retry, session expiry redirect, and logout button in header/sidebar/mobile nav; Vue router auth guard; `RbacTests`; `run-phase31-verification.sh` | Done |
| 23 | **Degraded Operations Visibility** — `GatewayStaleDetectorService` background service auto-marks gateways OFFLINE when heartbeat exceeds configurable `StaleThresholdMinutes`; `OperationsController` exposes gateway fleet listing (`GET /operations/gateways` with status/site filters), gateway detail (`GET /operations/gateways/{id}`), and site summary (`GET /operations/sites/{siteId}/summary`); `DischargeSummaryService` with `GET /encounters/{id}/discharge-summary` (info) and `GET /encounters/{id}/discharge-summary/content` (MinIO PDF download); `UsersController` (`GET /users`, `POST /users`, `PATCH /users/{id}`) for admin user account management; frontend: `GatewayOperations.vue` operations dashboard, `DegradedModeBanner.vue` warning banner, `DischargeSummaryPanel.vue` on patient detail, `ThresholdManagementView.vue` with `ThresholdFormModal.vue`, `UserManagementView.vue` with `UserFormModal.vue`, `AuditLogView.vue`, `ReconciliationView.vue`; role-aware admin sidebar navigation; `roleAccess.js` composable; `useChartTheme.js`, `useFocusTrap.js`, `useApiMode.js` composables; `CollapsibleSection.vue`, `SeverityBadge.vue` UI components; `OperationsApiTests`; `run-phase23-verification.sh` | Done |
| 33 | **Alert Quality Analytics** — `AlertFeedback` entity with per-user-per-alert constraint; `POST /alerts/{id}/feedback` server-side feedback submission with `alerts:feedback` permission (Nurse, Physician, Admin); `AlertQualityMetric` entity stores per-alert-type quality snapshots (acknowledgement rate, false positive rate, useful rate, would-act rate, avg seconds to acknowledge/resolve); `AlertQualityAggregatorService` background service computes metrics periodically; `AlertQualityMetricsController` exposes `GET /alerts/quality-metrics` (time-range + alert type filter) and `GET /alerts/quality-metrics/summary`; `AlertFeedbackConfiguration` and `AlertQualityMetricConfiguration` EF Core configs; `SubmitAlertFeedbackRequestValidator`; Prometheus `alert_quality_useful_rate` and `alert_quality_false_positive_rate` gauges; Grafana `alert-quality-dashboard.json`; frontend `AlertQualityAnalytics.vue` with `AlertQualityChart.vue` and `alertQuality` Pinia store; `AlertQualityAnalyticsTests`; `run-phase33-verification.sh` | Done |
| 34 | **Explainable Alerts** — `AlertExplanation` value object (`ScoreContributor`, `TrendContext`, `MedicationContext`, `NarrativeSummary`); JSONB `ClinicalAlert.Explanation` column (immutable at creation); contributor builders for NEWS2, SOFA, GCS; `TrendContextBuilder`; `AlertExplanationBuilder` + `ClinicalAlertFactory`; NEWS2, SOFA, GCS, and `TrendDetector` wire explanation and include `explanation` in `alert.generated` outbox; `MedicationCorrelationHelper.TryGetContextAsync()` for structured medication context; `AlertResponse` DTO + `AlertResponseMapper`; GET/list/acknowledge/resolve return `AlertResponse`; ES indexer projects `NarrativeSummary`; data lake Parquet `explanation_json`; ward gateway `LocalClinicalAlert.ExplanationJson` + sync; dashboard `AlertReasoning.vue` + `alertExplanation.js`; simulator `ExpectedOutcomeValidator` with `narrativeContains`; `ExplainableAlertsTests`; `run-phase34-verification.sh` | Done |
| 35 | **MIMIC-IV Replay Scenario Generator** — offline CLI tool in `VigilCare.Simulator/Mimic/` that reads MIMIC-IV CSV files (100 patients, 140 ICU stays, 668K chart events, 107K lab events from `docs/MIMIC-IV/`) and generates standard VigilCare scenario JSONs; `MimicCsvReader` streaming CSV parser with header-index lookup and filter predicate (memory-efficient for large files); `MimicItemMap` maps 17 chart event items (vitals, GCS, FiO₂, PaO₂, labs) and 8 lab event items to VigilCare observation codes with GCS text-to-numeric fallback dictionary, Fahrenheit-to-Celsius conversion, and blood pressure priority (non-invasive preferred over arterial); `MimicCareUnitMap` maps care units to departments and generates tags; `MimicDataLoader` streams chartevents/labevents/prescriptions filtered by stay_id + item ID set; `MimicScenarioBuilder` deduplicates BP, enforces 10-observation-per-cluster limit with priority-based splitting, computes offsetMinutes, parses medication doses; `MimicListCommand` (`mimic-list`) displays Spectre.Console table of available stays; `MimicGenerateCommand` (`mimic-generate`) produces scenario JSON with `--max-hours`, `--no-medications`, `--no-labs`, `--validate` options; generated scenarios pass `ScenarioValidator` and replay through the standard `replay` command | Done |
**Ward dashboard:** backend APIs (`GET /encounters` ward list with extended summary fields including SOFA/GCS/attending/admitted-at, `GET /qsofa/current`, `GET /qsofa/history`, `GET /gcs/history`, `GET /sepsis-bundles` hospital-wide list, `GET /operations/gateways` fleet management, `GET /users` user management, `GET /alerts/quality-metrics` alert quality, CORS) and frontend SPA — `EncountersListTests`, `QsofaCurrentTests`, `GapAnalysisFixTests`, `OperationsApiTests`, `AlertQualityAnalyticsTests`, `vigilcare-dashboard` Vitest suite (replay scrubbing, feedback store, FeedbackButtons, FeedbackSummary, alert components, charts, ward table, ward sort, ward filter, department format, sepsis format, alert acknowledge, critical alert detect, handoff report, vitals form, GCS entry/history, SOFA panel/history, qSOFA history, scores panel, alert labels, PatientBanner, EncounterTimeline, medication chart markers, AcknowledgeModal, CriticalAlertBanner, DepartmentOverviewView, SepsisBoardView, VitalsEntryForm, useAlertStore, useWardStore, roleAccess, ThresholdManagementView, DischargeSummaryPanel, GatewayOperations, alertQuality).
**Ward dashboard:** backend APIs (`GET /encounters` ward list with extended summary fields including SOFA/GCS/attending/admitted-at, `GET /qsofa/current`, `GET /qsofa/history`, `GET /gcs/history`, `GET /sepsis-bundles` hospital-wide list, `GET /operations/gateways` fleet management, `GET /users` user management, `GET /alerts/quality-metrics` alert quality, `GET /alerts/{id}` with structured explanation, CORS) and frontend SPA — `EncountersListTests`, `QsofaCurrentTests`, `GapAnalysisFixTests`, `OperationsApiTests`, `AlertQualityAnalyticsTests`, `ExplainableAlertsTests`, `vigilcare-dashboard` Vitest suite (replay scrubbing, feedback store, FeedbackButtons, FeedbackSummary, alert components, AlertReasoning, charts, ward table, ward sort, ward filter, department format, sepsis format, alert acknowledge, critical alert detect, handoff report, vitals form, GCS entry/history, SOFA panel/history, qSOFA history, scores panel, alert labels, PatientBanner, EncounterTimeline, medication chart markers, AcknowledgeModal, CriticalAlertBanner, DepartmentOverviewView, SepsisBoardView, VitalsEntryForm, useAlertStore, useWardStore, roleAccess, ThresholdManagementView, DischargeSummaryPanel, GatewayOperations, alertQuality).
**Enhanced Dashboard (post-Phase 22):** Major dashboard feature expansion addressing clinical workflow gaps. **Department Overview** (`/departments`) — unit-level snapshot cards showing patient count, critical/alert/bundle totals per department with acuity distribution bars; click-through to ward filtered by department. **Sepsis Bundle Board** (`/sepsis`) — real-time bundle compliance tracking with countdown timers to 1-hour deadline, urgency-sorted (overdue → at-risk → on-track), live 1-second tick updates. **Critical Alert Notifications** — `CriticalAlertBanner` surfaces new critical alerts from polling cycle with audible 880Hz two-tone alert, browser title flash, and native `Notification` API integration; mute toggle persisted in settings. **Shift Handoff Report** — `HandoffReport.vue` generates SBAR-format (Situation, Background, Assessment, Recommendation) structured reports for all ward patients, enriched with latest vitals, open alerts, pending orders, and sepsis bundle status; ward summary with department stats; print/PDF export. **Vitals Entry Form** — `VitalsEntryForm.vue` on patient detail page enables manual observation recording (7 vital parameters with AVPU dropdown) with client-side plausibility validation matching server-side ranges. **Ward Table Enhancements** — multi-column sorting (room, patient, department, NEWS2, qSOFA, sepsis, alerts) with sortable column headers, debounced patient search (name/MRN), quick-filter toggles (critical, has alerts, active sepsis), clear-all filters. **Acknowledge Modal** — role-aware acknowledgment with clinician identity pre-populated from JWT, role-specific guidance text, and acknowledgment note preview. Backend additions: `GET /sepsis-bundles` paginated hospital-wide list with `SepsisBundleSummary` (patient demographics, elements, deadlines); `WardEncounterSummary` extended with `sofaScore`, `sofaDelta`, `gcsScore`, `gcsClassification`, `lastObservationAt`, `attendingPhysician`, `admittedAt`.
@@ -2258,20 +2346,25 @@ Thirty-one phases from the project roadmap are implemented and verified, includi
**FHIR R4 integration (Phase 30):** Inbound facade accepts FHIR R4 JSON from integration engines (Mirth Connect, Rhapsody). Supports per-resource endpoints and transaction Bundles for ADT admit workflows. LOINC/SNOMED code mapping, Fahrenheit conversion, and external identifier linking enable drop-in EHR integration without changing the internal clinical pipeline.
**RBAC + audit logging (Phase 31):** JWT authentication with role-based permission gating on every endpoint. Four clinical roles with 18 granular permissions. Append-only audit logging records who did what, when, and why — with before/after state snapshots for compliance and incident review. Frontend login page with token-based session management.
**RBAC + audit logging + token refresh (Phase 31):** JWT authentication with role-based permission gating on every endpoint. Four clinical roles with 18 granular permissions. Short-lived access tokens (15 min) paired with rotating opaque refresh tokens (7 days) stored in PostgreSQL — `POST /auth/refresh` rotates tokens, `POST /auth/logout` revokes server-side. Frontend auto-refreshes before expiry, retries on 401, and redirects to login on refresh failure; logout button in header, sidebar, and mobile nav. Append-only audit logging records who did what, when, and why — with before/after state snapshots for compliance and incident review, including `USER_LOGOUT` and `TOKEN_REFRESHED` actions.
**Degraded Operations Visibility (Phase 23):** Gateway fleet operations panel with stale gateway auto-detection (`GatewayStaleDetectorService`), discharge summary API with MinIO PDF retrieval, admin panels for user management, threshold management, audit log browsing, and reconciliation viewing. Frontend adds role-aware sidebar navigation, degraded-mode banner for offline gateways, and comprehensive admin CRUD views.
**Alert Quality Analytics (Phase 33):** Server-side clinician feedback persisted as `AlertFeedback` entities (one per user per alert, six feedback types). `AlertQualityAggregatorService` periodically computes per-alert-type quality metrics (acknowledgement rate, false positive rate, useful rate, would-act rate, response times). REST API exposes quality metric snapshots and aggregate summaries. Grafana dashboard visualizes alert quality trends. Frontend analytics view with quality charts.
**Explainable Alerts (Phase 34):** Composite alerts (NEWS2, SOFA, GCS, rapid deterioration) carry an immutable JSONB `explanation` snapshot at creation — score contributors with raw values and normal ranges, trend context (percent change, duration, direction), structured medication context, and a bedside `NarrativeSummary`. `AlertResponse` exposes explanation on GET/list/acknowledge/resolve. Downstream consumers (Elasticsearch indexer, data lake Parquet, ward gateway sync, Kafka `alert.generated`) propagate explanation without breaking legacy consumers. Dashboard `AlertReasoning.vue` renders structured reasoning. Simulator validates `narrativeContains` on key scenarios.
**MIMIC-IV Replay Scenario Generator (Phase 35):** Offline CLI tool that converts real de-identified ICU data from MIT's MIMIC-IV dataset into VigilCare scenario JSONs. `mimic-list` browses 140 ICU stays across 100 patients with demographics, care unit, LOS, and outcome. `mimic-generate` produces a scenario from a specific stay ID with options for duration capping, medication/lab exclusion, and inline validation. The streaming CSV parser handles 668K-row chartevents efficiently by filtering at the string level before allocating records. Item mappings cover 17 chart event items (vitals, GCS text-to-numeric, FiO₂, PaO₂, ICU labs) and 8 lab event items (creatinine, platelets, bilirubin, lactate, WBC, potassium, glucose, PaO₂). Blood pressure deduplication prefers non-invasive over arterial readings. The 10-observation-per-cluster limit is enforced by priority-based splitting (vitals first, labs spill to the next offset). Generated scenarios are structurally identical to hand-crafted ones and replay through the existing `replay` command, driving NEWS2, SOFA, GCS, qSOFA, trend detection, and alerting on real patient trajectories.
**Post-phase hardening (after Phase 31):**
- **FHIR R4 read/search** — `FhirReadController` adds `GET /fhir/R4/Patient/{id}`, `GET /fhir/R4/Patient` (search by `identifier`), `GET /fhir/R4/Encounter/{id}`, `GET /fhir/R4/Encounter` (search by `patient`/`status`); new `fhir:read` permission for Admin and Integration roles; CapabilityStatement updated to advertise `read` and `searchType` interactions for Patient and Encounter
- **Alert threshold deletion** — `DELETE /alert-thresholds/{id}` with `THRESHOLD_DELETED` audit action and Redis cache invalidation
- **FHIR API key rotation** — `Fhir:ApiKeys` array alongside existing `Fhir:ApiKey` for zero-downtime key rotation; constant-time comparison via `CryptographicOperations.FixedTimeEquals` prevents timing attacks
- **Authorization failure logging** — `PermissionAuthorizationHandler` logs denied requests with structured details (username, user ID, role, required permission, endpoint); Prometheus `authorization_failures_total` counter with `permission` and `role` labels
- **JWT signing key validation** — startup guard rejects keys shorter than 256 bits (HMAC-SHA256 minimum); prevents silent misconfiguration that would weaken token verification
- **Token refresh and revocation** — `RefreshToken` entity with DB-backed opaque token storage; `POST /auth/refresh` rotates access + refresh tokens (previous refresh token revoked on each use); `POST /auth/logout` revokes refresh token server-side; access token reduced from 8 hours to 15 minutes; refresh token valid for 7 days; frontend auto-refreshes 1 minute before expiry with 401 retry fallback; logout button in header, sidebar, and mobile nav with session redirect; `USER_LOGOUT` and `TOKEN_REFRESHED` audit actions
- **Concurrency hardening** — `SepsisBundleService.TryCreateBundleAsync` wraps order + bundle creation in a single database transaction so the unique constraint rollback also reverts orphaned orders; `PatientService.OpenEncounterAsync` enforced by new partial unique index `ix_encounters_patient_active_type` on `(patient_id, encounter_type) WHERE status = 'ACTIVE'` with constraint-violation catch returning 409 Conflict; `ConcurrencyTests` validates parallel patient registration, sepsis bundle creation, observation idempotency, and encounter open race conditions
- **New Prometheus metrics** — `fhir_read_total` (resource_type, interaction, outcome), `authorization_failures_total` (permission, role)
- **New audit actions** — `THRESHOLD_DELETED`, `AUTHORIZATION_DENIED`
- **New audit actions** — `THRESHOLD_DELETED`, `AUTHORIZATION_DENIED`, `USER_LOGOUT`, `TOKEN_REFRESHED`
**Optional follow-up:** execute and document the Kafka replay demonstration for the data lake (reset `data-lake-writer` offsets, clear MinIO prefixes, restart API, confirm Parquet rebuild). See `docs/plans/phase-9-plan.md` § Replay demonstration.
@@ -5,6 +5,9 @@
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<RootNamespace>VigilCare.ClinicalContracts</RootNamespace>
<Copyright>Copyright (c) 2024-2026 voltsrage. All Rights Reserved.</Copyright>
<Authors>voltsrage</Authors>
<PackageLicenseFile>LICENSE</PackageLicenseFile>
</PropertyGroup>
</Project>
@@ -38,7 +38,12 @@ public class VigilCareApiClient
public async Task<PatientResponse> RegisterPatientAsync(RegisterPatientRequest req)
{
var response = await _http.PostAsJsonAsync("/api/v1/patients", req);
response.EnsureSuccessStatusCode();
if (!response.IsSuccessStatusCode)
{
var body = await response.Content.ReadAsStringAsync();
throw new HttpRequestException(
$"Register patient failed ({(int)response.StatusCode} {response.StatusCode}): {body}");
}
var envelope = await response.Content.ReadFromJsonAsync<ApiResponse<PatientResponse>>();
return envelope!.Data!;
}
@@ -0,0 +1,37 @@
public static class MimicCareUnitMap
{
public static string ToVigilCareDepartment(string mimicCareUnit)
{
// All MIMIC ICU stays map to ICU in VigilCare
return "Icu";
}
public static string ToVigilCareEncounterType(string admissionType)
{
if (admissionType.Contains("EMER", StringComparison.OrdinalIgnoreCase))
return "Emergency";
return "Inpatient";
}
public static List<string> GetTags(string mimicCareUnit, int hospitalExpireFlag)
{
var tags = new List<string> { "mimic-iv", "real-data", "icu" };
var unit = mimicCareUnit.ToUpperInvariant();
if (unit.Contains("CARDIAC") || unit.Contains("CVICU") || unit.Contains("CCU") || unit.Contains("CORONARY"))
tags.Add("cardiac");
if (unit.Contains("NEURO"))
tags.Add("neuro");
if (unit.Contains("SURG") || unit.Contains("TSICU"))
tags.Add("surgical");
if (unit.Contains("TRAUMA"))
tags.Add("trauma");
if (unit.Contains("MICU") || unit.Contains("MEDICAL"))
tags.Add("medical");
if (hospitalExpireFlag == 1)
tags.Add("expired");
return tags;
}
}
@@ -0,0 +1,70 @@
public static class MimicCsvReader
{
public static IEnumerable<T> Read<T>(
string filePath,
Func<string[], Dictionary<string, int>, T?> parser,
Func<string[], Dictionary<string, int>, bool>? filter = null)
{
using var reader = new StreamReader(filePath);
var headerLine = reader.ReadLine();
if (headerLine is null) yield break;
var headers = BuildHeaderIndex(headerLine);
while (reader.ReadLine() is { } line)
{
if (string.IsNullOrWhiteSpace(line)) continue;
var fields = line.Split(',');
if (filter is not null && !filter(fields, headers))
continue;
var record = parser(fields, headers);
if (record is not null)
yield return record;
}
}
public static List<T> ReadAll<T>(
string filePath,
Func<string[], Dictionary<string, int>, T?> parser)
{
return Read(filePath, parser).ToList();
}
private static Dictionary<string, int> BuildHeaderIndex(string headerLine)
{
var headers = new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase);
var columns = headerLine.Split(',');
for (var i = 0; i < columns.Length; i++)
headers[columns[i].Trim()] = i;
return headers;
}
public static string Col(string[] fields, Dictionary<string, int> headers, string name)
{
return headers.TryGetValue(name, out var idx) && idx < fields.Length
? fields[idx].Trim()
: string.Empty;
}
public static int? ColInt(string[] fields, Dictionary<string, int> headers, string name)
{
var val = Col(fields, headers, name);
return int.TryParse(val, out var result) ? result : null;
}
public static decimal? ColDecimal(string[] fields, Dictionary<string, int> headers, string name)
{
var val = Col(fields, headers, name);
return decimal.TryParse(val, System.Globalization.NumberStyles.Any,
System.Globalization.CultureInfo.InvariantCulture, out var result) ? result : null;
}
public static DateTime? ColDateTime(string[] fields, Dictionary<string, int> headers, string name)
{
var val = Col(fields, headers, name);
return DateTime.TryParse(val, System.Globalization.CultureInfo.InvariantCulture,
System.Globalization.DateTimeStyles.None, out var result) ? result : null;
}
}
@@ -0,0 +1,191 @@
public record MimicPatient(int SubjectId, string Gender, int AnchorAge, int AnchorYear, string? Dod);
public record MimicAdmission(
int SubjectId, int HadmId,
DateTime AdmitTime, DateTime DischTime, DateTime? DeathTime,
string AdmissionType, string? AdmissionLocation, string? DischargeLocation,
int HospitalExpireFlag);
public record MimicIcuStay(
int SubjectId, int HadmId, int StayId,
string FirstCareUnit, string LastCareUnit,
DateTime InTime, DateTime OutTime, decimal Los);
public record MimicChartEvent(
int StayId, DateTime ChartTime, int ItemId,
string? TextValue, decimal? ValueNum);
public record MimicLabEvent(
int HadmId, DateTime ChartTime, int ItemId,
decimal? ValueNum, string? ValueUom);
public record MimicPrescription(
int HadmId, DateTime StartTime,
string Drug, string? DoseValRx, string? DoseUnitRx, string? Route);
public class MimicDataLoader
{
private readonly string _dataDir;
public MimicDataLoader(string dataDir)
{
if (!Directory.Exists(dataDir))
throw new DirectoryNotFoundException($"MIMIC data directory not found: {dataDir}");
_dataDir = dataDir;
}
private string Path(string fileName) => System.IO.Path.Combine(_dataDir, fileName);
public List<MimicPatient> LoadPatients()
{
return MimicCsvReader.ReadAll(Path("patients.csv"), (f, h) =>
{
var id = MimicCsvReader.ColInt(f, h, "subject_id");
var age = MimicCsvReader.ColInt(f, h, "anchor_age");
var year = MimicCsvReader.ColInt(f, h, "anchor_year");
if (id is null || age is null || year is null) return null;
return new MimicPatient(
id.Value,
MimicCsvReader.Col(f, h, "gender"),
age.Value,
year.Value,
MimicCsvReader.Col(f, h, "dod") is { Length: > 0 } dod ? dod : null);
});
}
public List<MimicAdmission> LoadAdmissions()
{
return MimicCsvReader.ReadAll(Path("admissions.csv"), (f, h) =>
{
var subjectId = MimicCsvReader.ColInt(f, h, "subject_id");
var hadmId = MimicCsvReader.ColInt(f, h, "hadm_id");
var admitTime = MimicCsvReader.ColDateTime(f, h, "admittime");
var dischTime = MimicCsvReader.ColDateTime(f, h, "dischtime");
if (subjectId is null || hadmId is null || admitTime is null || dischTime is null)
return null;
return new MimicAdmission(
subjectId.Value, hadmId.Value,
admitTime.Value, dischTime.Value,
MimicCsvReader.ColDateTime(f, h, "deathtime"),
MimicCsvReader.Col(f, h, "admission_type"),
MimicCsvReader.Col(f, h, "admission_location") is { Length: > 0 } loc ? loc : null,
MimicCsvReader.Col(f, h, "discharge_location") is { Length: > 0 } dloc ? dloc : null,
MimicCsvReader.ColInt(f, h, "hospital_expire_flag") ?? 0);
});
}
public List<MimicIcuStay> LoadIcuStays()
{
return MimicCsvReader.ReadAll(Path("icustays.csv"), (f, h) =>
{
var subjectId = MimicCsvReader.ColInt(f, h, "subject_id");
var hadmId = MimicCsvReader.ColInt(f, h, "hadm_id");
var stayId = MimicCsvReader.ColInt(f, h, "stay_id");
var inTime = MimicCsvReader.ColDateTime(f, h, "intime");
var outTime = MimicCsvReader.ColDateTime(f, h, "outtime");
if (subjectId is null || hadmId is null || stayId is null
|| inTime is null || outTime is null)
return null;
return new MimicIcuStay(
subjectId.Value, hadmId.Value, stayId.Value,
MimicCsvReader.Col(f, h, "first_careunit"),
MimicCsvReader.Col(f, h, "last_careunit"),
inTime.Value, outTime.Value,
MimicCsvReader.ColDecimal(f, h, "los") ?? 0m);
});
}
public IEnumerable<MimicChartEvent> StreamChartEvents(int stayId)
{
var stayIdStr = stayId.ToString();
return MimicCsvReader.Read(
Path("chartevents.csv"),
parser: (f, h) =>
{
var itemId = MimicCsvReader.ColInt(f, h, "itemid");
if (itemId is null || !MimicItemMap.AllChartItemIds.Contains(itemId.Value))
return null;
var chartTime = MimicCsvReader.ColDateTime(f, h, "charttime");
if (chartTime is null) return null;
var valueNum = MimicCsvReader.ColDecimal(f, h, "valuenum");
var textValue = MimicCsvReader.Col(f, h, "value");
if (MimicItemMap.IsGcsItem(itemId.Value))
{
var gcsVal = MimicItemMap.ResolveGcsValue(itemId.Value, textValue, valueNum);
if (gcsVal is null) return null;
return new MimicChartEvent(stayId, chartTime.Value, itemId.Value, textValue, gcsVal);
}
if (valueNum is null) return null;
return new MimicChartEvent(stayId, chartTime.Value, itemId.Value, textValue, valueNum);
},
filter: (f, h) =>
{
var sid = MimicCsvReader.Col(f, h, "stay_id");
return sid == stayIdStr;
});
}
public IEnumerable<MimicLabEvent> StreamLabEvents(int hadmId, DateTime? after = null, DateTime? before = null)
{
var hadmIdStr = hadmId.ToString();
return MimicCsvReader.Read(
Path("labevents.csv"),
parser: (f, h) =>
{
var itemId = MimicCsvReader.ColInt(f, h, "itemid");
if (itemId is null || !MimicItemMap.AllLabItemIds.Contains(itemId.Value))
return null;
var chartTime = MimicCsvReader.ColDateTime(f, h, "charttime");
if (chartTime is null) return null;
if (after.HasValue && chartTime.Value < after.Value) return null;
if (before.HasValue && chartTime.Value > before.Value) return null;
var valueNum = MimicCsvReader.ColDecimal(f, h, "valuenum");
if (valueNum is null) return null;
return new MimicLabEvent(
hadmId, chartTime.Value, itemId.Value, valueNum,
MimicCsvReader.Col(f, h, "valueuom") is { Length: > 0 } uom ? uom : null);
},
filter: (f, h) =>
{
var hid = MimicCsvReader.Col(f, h, "hadm_id");
return hid == hadmIdStr;
});
}
public IEnumerable<MimicPrescription> StreamPrescriptions(
int hadmId, DateTime? after = null, DateTime? before = null)
{
var hadmIdStr = hadmId.ToString();
return MimicCsvReader.Read(
Path("prescriptions.csv"),
parser: (f, h) =>
{
var startTime = MimicCsvReader.ColDateTime(f, h, "starttime");
if (startTime is null) return null;
if (after.HasValue && startTime.Value < after.Value) return null;
if (before.HasValue && startTime.Value > before.Value) return null;
var drug = MimicCsvReader.Col(f, h, "drug");
if (string.IsNullOrWhiteSpace(drug)) return null;
return new MimicPrescription(
hadmId, startTime.Value, drug,
MimicCsvReader.Col(f, h, "dose_val_rx") is { Length: > 0 } d ? d : null,
MimicCsvReader.Col(f, h, "dose_unit_rx") is { Length: > 0 } u ? u : null,
MimicCsvReader.Col(f, h, "route") is { Length: > 0 } r ? r : null);
},
filter: (f, h) =>
{
var hid = MimicCsvReader.Col(f, h, "hadm_id");
return hid == hadmIdStr;
});
}
}
@@ -0,0 +1,136 @@
using System.CommandLine;
using System.Text.Json;
using Spectre.Console;
public static class MimicGenerateCommand
{
public static Command Create()
{
var dataDirArg = new Argument<DirectoryInfo>("mimic-dir",
"Path to directory containing MIMIC-IV CSV files");
var stayIdOpt = new Option<int>("--stay-id", "ICU stay ID to generate scenario for")
{ IsRequired = true };
var maxHoursOpt = new Option<int?>("--max-hours", "Limit scenario duration in hours");
var noMedsOpt = new Option<bool>("--no-medications", () => false,
"Exclude medication events");
var noLabsOpt = new Option<bool>("--no-labs", () => false,
"Exclude lab observations");
var outputOpt = new Option<string?>("--output", "Output file path (default: auto-named)");
var validateOpt = new Option<bool>("--validate", () => false,
"Run scenario validation after generation");
var command = new Command("mimic-generate",
"Generate a VigilCare scenario JSON from a MIMIC-IV ICU stay")
{
dataDirArg, stayIdOpt, maxHoursOpt, noMedsOpt, noLabsOpt, outputOpt, validateOpt
};
command.SetHandler(context =>
{
var dataDir = context.ParseResult.GetValueForArgument(dataDirArg);
var stayId = context.ParseResult.GetValueForOption(stayIdOpt);
var maxHours = context.ParseResult.GetValueForOption(maxHoursOpt);
var noMeds = context.ParseResult.GetValueForOption(noMedsOpt);
var noLabs = context.ParseResult.GetValueForOption(noLabsOpt);
var outputPath = context.ParseResult.GetValueForOption(outputOpt);
var validate = context.ParseResult.GetValueForOption(validateOpt);
var loader = new MimicDataLoader(dataDir.FullName);
AnsiConsole.MarkupLine($"[bold]Loading MIMIC-IV data for stay {stayId}...[/]");
var stays = loader.LoadIcuStays();
var stay = stays.FirstOrDefault(s => s.StayId == stayId);
if (stay is null)
{
AnsiConsole.MarkupLine($"[red]ICU stay {stayId} not found.[/]");
var available = stays.Select(s => s.StayId).OrderBy(x => x).ToList();
AnsiConsole.MarkupLine($"Available stay IDs: {string.Join(", ", available.Take(20))}...");
return;
}
var admissions = loader.LoadAdmissions();
var admission = admissions.FirstOrDefault(a => a.HadmId == stay.HadmId);
if (admission is null)
{
AnsiConsole.MarkupLine($"[red]Admission {stay.HadmId} not found.[/]");
return;
}
var patients = loader.LoadPatients();
var patient = patients.FirstOrDefault(p => p.SubjectId == stay.SubjectId);
if (patient is null)
{
AnsiConsole.MarkupLine($"[red]Patient {stay.SubjectId} not found.[/]");
return;
}
AnsiConsole.MarkupLine(
$" Patient: [cyan]{patient.SubjectId}[/] ({patient.Gender}, ~{patient.AnchorAge}y)");
AnsiConsole.MarkupLine(
$" Stay: [cyan]{stay.StayId}[/] in {stay.FirstCareUnit}");
AnsiConsole.MarkupLine(
$" LOS: {stay.Los:F1} days ({stay.InTime:g} → {stay.OutTime:g})");
AnsiConsole.MarkupLine(
$" Outcome: {(admission.HospitalExpireFlag == 1 ? "[red]Expired[/]" : "Survived")}");
var options = new MimicGenerateOptions(
MaxHours: maxHours,
IncludeMedications: !noMeds,
IncludeLabs: !noLabs);
AnsiConsole.MarkupLine("\n[bold]Generating scenario...[/]");
var builder = new MimicScenarioBuilder(loader);
var (scenario, warnings) = builder.Build(stay, admission, patient, options);
foreach (var warning in warnings)
AnsiConsole.MarkupLine($" [yellow]WARNING:[/] {Markup.Escape(warning)}");
var obsCount = scenario.Events.Count(e => e.Type == "observation");
var medCount = scenario.Events.Count(e => e.Type == "medication");
AnsiConsole.MarkupLine(
$" Events: [green]{obsCount}[/] observations, [green]{medCount}[/] medications");
AnsiConsole.MarkupLine(
$" Duration: {scenario.Scenario.DurationMinutes} minutes " +
$"({scenario.Scenario.DurationMinutes / 60.0:F1} hours)");
if (validate)
{
var errors = ScenarioValidator.Validate(scenario);
if (errors.Count == 0)
{
AnsiConsole.MarkupLine(" [green]Validation: PASSED[/]");
}
else
{
AnsiConsole.MarkupLine($" [red]Validation: {errors.Count} error(s)[/]");
foreach (var err in errors)
AnsiConsole.MarkupLine($" [red]• {Markup.Escape(err)}[/]");
}
}
var jsonOptions = new JsonSerializerOptions
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
WriteIndented = true,
Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping
};
var json = JsonSerializer.Serialize(scenario, jsonOptions);
var filePath = outputPath
?? Path.Combine("Scenarios", "List", $"mimic-s{stayId}.json");
var dir = Path.GetDirectoryName(filePath);
if (!string.IsNullOrEmpty(dir) && !Directory.Exists(dir))
Directory.CreateDirectory(dir);
File.WriteAllText(filePath, json);
AnsiConsole.MarkupLine($"\n[bold green]Scenario written to:[/] {filePath}");
AnsiConsole.MarkupLine($" Replay with: [dim]dotnet run -- replay {filePath}[/]");
});
return command;
}
}
+110
View File
@@ -0,0 +1,110 @@
public static class MimicItemMap
{
public record ChartItemMapping(
string Code, string Unit, string Source,
int Priority = 0,
bool ConvertFahrenheit = false);
public record LabItemMapping(string Code, string Unit);
private static readonly Dictionary<int, ChartItemMapping> ChartMappings = new()
{
[220045] = new("HEART_RATE", "bpm", "Device"),
[220210] = new("RESP_RATE", "/min", "Device"),
[220179] = new("SYSTOLIC_BP", "mmHg", "Device"),
[220180] = new("DIASTOLIC_BP", "mmHg", "Device"),
[220050] = new("SYSTOLIC_BP", "mmHg", "Device", Priority: 1),
[220051] = new("DIASTOLIC_BP", "mmHg", "Device", Priority: 1),
[223762] = new("TEMP_C", "°C", "Manual"),
[223761] = new("TEMP_C", "°C", "Manual", ConvertFahrenheit: true),
[220277] = new("SPO2", "%", "Device"),
[223835] = new("FIO2_PCT", "%", "Device"),
[220739] = new("GCS_EYE", "score", "Manual"),
[223900] = new("GCS_VERBAL", "score", "Manual"),
[223901] = new("GCS_MOTOR", "score", "Manual"),
[220615] = new("CREATININE_MG_DL", "mg/dL", "Lab"),
[225690] = new("BILIRUBIN_MG_DL", "mg/dL", "Lab"),
[225678] = new("PLATELET_K_UL", "k/µL", "Lab"),
[220224] = new("PAO2_MMHG", "mmHg", "Lab"),
};
private static readonly Dictionary<int, LabItemMapping> LabMappings = new()
{
[50912] = new("CREATININE_MG_DL", "mg/dL"),
[51704] = new("PLATELET_K_UL", "k/µL"),
[50885] = new("BILIRUBIN_MG_DL", "mg/dL"),
[50813] = new("LACTATE_MMOL_L", "mmol/L"),
[51301] = new("WBC_K_UL", "k/µL"),
[50971] = new("POTASSIUM_MEQ_L", "mEq/L"),
[50931] = new("GLUCOSE_MG_DL", "mg/dL"),
[50821] = new("PAO2_MMHG", "mmHg"),
};
// GCS text → numeric fallback (in case valuenum is missing)
private static readonly Dictionary<string, int> GcsEyeText = new(StringComparer.OrdinalIgnoreCase)
{
["None"] = 1, ["No Response"] = 1,
["To Pain"] = 2,
["To Speech"] = 3,
["Spontaneously"] = 4,
};
private static readonly Dictionary<string, int> GcsVerbalText = new(StringComparer.OrdinalIgnoreCase)
{
["No Response"] = 1, ["No Response-ETT"] = 1,
["Incomprehensible sounds"] = 2, ["Incomprehensible Sounds"] = 2,
["Inappropriate Words"] = 3,
["Confused"] = 4,
["Oriented"] = 5,
};
private static readonly Dictionary<string, int> GcsMotorText = new(StringComparer.OrdinalIgnoreCase)
{
["No response"] = 1, ["No Response"] = 1,
["Abnormal extension"] = 2, ["Abnormal Extension"] = 2,
["Abnormal Flexion"] = 3,
["Flex-withdraws"] = 3, ["Flex-Withdraws"] = 3,
["Localizes Pain"] = 4,
["Obeys Commands"] = 6,
};
public static readonly HashSet<int> AllChartItemIds = new(ChartMappings.Keys);
public static readonly HashSet<int> AllLabItemIds = new(LabMappings.Keys);
public static bool TryMapChartEvent(int itemId, out ChartItemMapping mapping)
=> ChartMappings.TryGetValue(itemId, out mapping!);
public static bool TryMapLabEvent(int itemId, out LabItemMapping mapping)
=> LabMappings.TryGetValue(itemId, out mapping!);
public static decimal ConvertValue(decimal rawValue, ChartItemMapping mapping)
{
if (mapping.ConvertFahrenheit)
return Math.Round((rawValue - 32m) * 5m / 9m, 1);
return rawValue;
}
public static decimal? ResolveGcsValue(int itemId, string? textValue, decimal? numericValue)
{
if (numericValue.HasValue && numericValue.Value > 0)
return numericValue.Value;
if (string.IsNullOrWhiteSpace(textValue))
return null;
var lookup = itemId switch
{
220739 => GcsEyeText,
223900 => GcsVerbalText,
223901 => GcsMotorText,
_ => null
};
if (lookup is not null && lookup.TryGetValue(textValue, out var score))
return score;
return null;
}
public static bool IsGcsItem(int itemId) => itemId is 220739 or 223900 or 223901;
}
@@ -0,0 +1,75 @@
using System.CommandLine;
using Spectre.Console;
public static class MimicListCommand
{
public static Command Create()
{
var dataDirArg = new Argument<DirectoryInfo>("mimic-dir",
"Path to directory containing MIMIC-IV CSV files");
var subjectIdOpt = new Option<int?>("--subject-id", "Filter to a specific patient");
var stayIdOpt = new Option<int?>("--stay-id", "Filter to a specific ICU stay");
var command = new Command("mimic-list",
"List available MIMIC-IV patients and ICU stays")
{
dataDirArg, subjectIdOpt, stayIdOpt
};
command.SetHandler(context =>
{
var dataDir = context.ParseResult.GetValueForArgument(dataDirArg);
var subjectId = context.ParseResult.GetValueForOption(subjectIdOpt);
var stayId = context.ParseResult.GetValueForOption(stayIdOpt);
var loader = new MimicDataLoader(dataDir.FullName);
var patients = loader.LoadPatients().ToDictionary(p => p.SubjectId);
var admissions = loader.LoadAdmissions().ToDictionary(a => a.HadmId);
var stays = loader.LoadIcuStays();
if (subjectId.HasValue)
stays = stays.Where(s => s.SubjectId == subjectId.Value).ToList();
if (stayId.HasValue)
stays = stays.Where(s => s.StayId == stayId.Value).ToList();
var table = new Table()
.Border(TableBorder.Rounded)
.Title("[bold]MIMIC-IV ICU Stays[/]");
table.AddColumn("StayId");
table.AddColumn("SubjectId");
table.AddColumn("HadmId");
table.AddColumn("Gender");
table.AddColumn("Age");
table.AddColumn("Care Unit");
table.AddColumn("Admission");
table.AddColumn("LOS (d)");
table.AddColumn("Expired");
foreach (var stay in stays.OrderBy(s => s.SubjectId).ThenBy(s => s.InTime))
{
var pt = patients.GetValueOrDefault(stay.SubjectId);
var adm = admissions.GetValueOrDefault(stay.HadmId);
table.AddRow(
stay.StayId.ToString(),
stay.SubjectId.ToString(),
stay.HadmId.ToString(),
pt?.Gender ?? "?",
pt?.AnchorAge.ToString() ?? "?",
Markup.Escape(stay.FirstCareUnit),
adm?.AdmissionType ?? "?",
stay.Los.ToString("F1"),
adm?.HospitalExpireFlag == 1 ? "[red]Yes[/]" : "No");
}
AnsiConsole.Write(table);
var patientCount = stays.Select(s => s.SubjectId).Distinct().Count();
AnsiConsole.MarkupLine(
$"\nFound [bold]{stays.Count}[/] ICU stays across [bold]{patientCount}[/] patients.");
});
return command;
}
}
@@ -0,0 +1,322 @@
using System.Text.Json;
public record MimicGenerateOptions(
int? MaxHours = null,
bool IncludeMedications = true,
bool IncludeLabs = true);
public class MimicScenarioBuilder
{
private readonly MimicDataLoader _loader;
private static readonly string[] ObservationPriority =
[
"HEART_RATE", "RESP_RATE", "SYSTOLIC_BP", "DIASTOLIC_BP", "TEMP_C", "SPO2",
"GCS_EYE", "GCS_VERBAL", "GCS_MOTOR",
"FIO2_PCT", "PAO2_MMHG",
"CREATININE_MG_DL", "BILIRUBIN_MG_DL", "PLATELET_K_UL",
"WBC_K_UL", "POTASSIUM_MEQ_L", "LACTATE_MMOL_L", "GLUCOSE_MG_DL",
"URINE_OUTPUT_ML_H"
];
public MimicScenarioBuilder(MimicDataLoader loader)
{
_loader = loader;
}
public (ScenarioFile Scenario, List<string> Warnings) Build(
MimicIcuStay stay, MimicAdmission admission, MimicPatient patient,
MimicGenerateOptions options)
{
var warnings = new List<string>();
var scenarioStart = stay.InTime;
var scenarioEnd = stay.OutTime;
if (options.MaxHours.HasValue)
{
var maxEnd = scenarioStart.AddHours(options.MaxHours.Value);
if (maxEnd < scenarioEnd)
scenarioEnd = maxEnd;
}
var scenarioPatient = BuildPatient(patient, admission);
var encounter = BuildEncounter(stay, admission);
var events = BuildEvents(stay, admission, scenarioStart, scenarioEnd, options, warnings);
var meta = BuildMeta(stay, admission, patient, scenarioStart, scenarioEnd, events);
var scenario = new ScenarioFile(meta, scenarioPatient, encounter, events, ExpectedOutcomes: null);
return (scenario, warnings);
}
private static ScenarioPatient BuildPatient(MimicPatient patient, MimicAdmission admission)
{
var birthYear = DateTime.UtcNow.Year - patient.AnchorAge;
var dob = new DateTime(birthYear, 7, 1);
return new ScenarioPatient(
FirstName: $"MIMIC-{patient.SubjectId}",
LastName: $"S{admission.HadmId}",
DateOfBirth: dob.ToString("yyyy-MM-dd"),
Gender: patient.Gender == "F" ? "Female" : "Male");
}
private static ScenarioEncounter BuildEncounter(MimicIcuStay stay, MimicAdmission admission)
{
return new ScenarioEncounter(
Department: MimicCareUnitMap.ToVigilCareDepartment(stay.FirstCareUnit),
EncounterType: MimicCareUnitMap.ToVigilCareEncounterType(admission.AdmissionType),
AttendingPhysician: "MIMIC-Physician",
RoomBed: $"ICU-{stay.StayId % 100:D2}",
AdmissionReason: $"MIMIC-IV admission ({admission.AdmissionType}, from {admission.AdmissionLocation ?? "unknown"})");
}
private List<ScenarioEvent> BuildEvents(
MimicIcuStay stay, MimicAdmission admission,
DateTime scenarioStart, DateTime scenarioEnd,
MimicGenerateOptions options, List<string> warnings)
{
var rawObs = CollectObservations(stay, admission, scenarioStart, scenarioEnd, options);
var deduplicated = DeduplicateBloodPressure(rawObs);
var events = new List<ScenarioEvent>();
foreach (var obs in deduplicated)
{
var offsetMinutes = Math.Round((obs.ChartTime - scenarioStart).TotalMinutes);
if (offsetMinutes < 0) offsetMinutes = 0;
var data = JsonSerializer.SerializeToElement(new
{
code = obs.Code,
value = obs.Value,
unit = obs.Unit,
source = obs.Source
}, SerializerOptions);
events.Add(new ScenarioEvent(offsetMinutes, "observation", data, null));
}
if (options.IncludeMedications)
{
var meds = CollectMedications(admission, scenarioStart, scenarioEnd, warnings);
events.AddRange(meds);
}
events = events.OrderBy(e => e.OffsetMinutes).ToList();
events = EnforceClusterLimit(events, warnings);
return events;
}
private List<RawObservation> CollectObservations(
MimicIcuStay stay, MimicAdmission admission,
DateTime scenarioStart, DateTime scenarioEnd,
MimicGenerateOptions options)
{
var observations = new List<RawObservation>();
foreach (var ce in _loader.StreamChartEvents(stay.StayId))
{
if (ce.ChartTime < scenarioStart || ce.ChartTime > scenarioEnd) continue;
if (!MimicItemMap.TryMapChartEvent(ce.ItemId, out var mapping)) continue;
if (ce.ValueNum is null) continue;
var value = MimicItemMap.ConvertValue(ce.ValueNum.Value, mapping);
observations.Add(new RawObservation(
ce.ChartTime, mapping.Code, value, mapping.Unit, mapping.Source, mapping.Priority));
}
if (options.IncludeLabs)
{
var labCodes = new HashSet<(DateTime time, string code)>(
observations.Select(o => (o.ChartTime, o.Code)));
foreach (var le in _loader.StreamLabEvents(admission.HadmId, scenarioStart, scenarioEnd))
{
if (!MimicItemMap.TryMapLabEvent(le.ItemId, out var mapping)) continue;
if (le.ValueNum is null) continue;
if (labCodes.Contains((le.ChartTime, mapping.Code)))
continue;
observations.Add(new RawObservation(
le.ChartTime, mapping.Code, le.ValueNum.Value, mapping.Unit, "Lab", 0));
}
}
return observations.OrderBy(o => o.ChartTime).ToList();
}
private static List<RawObservation> DeduplicateBloodPressure(List<RawObservation> observations)
{
var bpGroups = observations
.Where(o => o.Code is "SYSTOLIC_BP" or "DIASTOLIC_BP")
.GroupBy(o => (Time: RoundToMinute(o.ChartTime), o.Code));
var removals = new HashSet<RawObservation>();
foreach (var group in bpGroups)
{
var items = group.ToList();
if (items.Count <= 1) continue;
var hasPrimary = items.Any(i => i.Priority == 0);
if (hasPrimary)
{
foreach (var fallback in items.Where(i => i.Priority > 0))
removals.Add(fallback);
}
}
return removals.Count > 0
? observations.Where(o => !removals.Contains(o)).ToList()
: observations;
}
private List<ScenarioEvent> CollectMedications(
MimicAdmission admission, DateTime scenarioStart, DateTime scenarioEnd,
List<string> warnings)
{
var events = new List<ScenarioEvent>();
var count = 0;
var skipped = 0;
foreach (var rx in _loader.StreamPrescriptions(admission.HadmId, scenarioStart, scenarioEnd))
{
var dose = ParseDose(rx.DoseValRx);
if (dose is null || string.IsNullOrWhiteSpace(rx.DoseUnitRx)
|| string.IsNullOrWhiteSpace(rx.Route))
{
skipped++;
continue;
}
var offsetMinutes = Math.Round((rx.StartTime - scenarioStart).TotalMinutes);
if (offsetMinutes < 0) offsetMinutes = 0;
var data = JsonSerializer.SerializeToElement(new
{
drugName = rx.Drug,
dose = dose.Value,
doseUnit = rx.DoseUnitRx,
route = rx.Route,
administeredBy = "MIMIC-RN"
}, SerializerOptions);
events.Add(new ScenarioEvent(offsetMinutes, "medication", data, null));
count++;
}
if (skipped > 0)
warnings.Add($"Skipped {skipped} prescriptions with missing dose/unit/route data");
return events;
}
private static List<ScenarioEvent> EnforceClusterLimit(
List<ScenarioEvent> events, List<string> warnings)
{
var result = new List<ScenarioEvent>();
var clusters = events.GroupBy(e => e.OffsetMinutes).OrderBy(g => g.Key).ToList();
var spillover = new List<(double offset, ScenarioEvent evt)>();
foreach (var cluster in clusters)
{
var obsInCluster = cluster.Where(e => e.Type == "observation").ToList();
var otherInCluster = cluster.Where(e => e.Type != "observation").ToList();
// Add any spillover from previous clusters at this offset
var spilled = spillover.Where(s => s.offset == cluster.Key).Select(s => s.evt).ToList();
spillover.RemoveAll(s => s.offset == cluster.Key);
obsInCluster.AddRange(spilled);
if (obsInCluster.Count > 10)
{
var sorted = obsInCluster
.OrderBy(e => GetObservationPriority(e))
.ToList();
var keep = sorted.Take(10).ToList();
var overflow = sorted.Skip(10).ToList();
warnings.Add(
$"Offset {cluster.Key}: split {obsInCluster.Count} observations " +
$"(moved {overflow.Count} to offset {cluster.Key + 1})");
foreach (var evt in overflow)
spillover.Add((cluster.Key + 1,
new ScenarioEvent(cluster.Key + 1, evt.Type, evt.Data, evt.Note)));
obsInCluster = keep;
}
result.AddRange(obsInCluster);
result.AddRange(otherInCluster);
}
// Handle any remaining spillover
foreach (var (offset, evt) in spillover.OrderBy(s => s.offset))
result.Add(evt);
return result.OrderBy(e => e.OffsetMinutes).ToList();
}
private static int GetObservationPriority(ScenarioEvent evt)
{
var code = evt.Data.TryGetProperty("code", out var codeProp)
? codeProp.GetString() : null;
if (code is null) return 999;
var idx = Array.IndexOf(ObservationPriority, code);
return idx >= 0 ? idx : 999;
}
private static ScenarioMeta BuildMeta(
MimicIcuStay stay, MimicAdmission admission, MimicPatient patient,
DateTime scenarioStart, DateTime scenarioEnd,
List<ScenarioEvent> events)
{
var durationMinutes = (int)(scenarioEnd - scenarioStart).TotalMinutes;
var obsCount = events.Count(e => e.Type == "observation");
var medCount = events.Count(e => e.Type == "medication");
var description =
$"Real de-identified MIMIC-IV data. " +
$"Subject {patient.SubjectId}, stay {stay.StayId}. " +
$"{(patient.Gender == "F" ? "Female" : "Male")}, age ~{patient.AnchorAge}. " +
$"ICU LOS: {stay.Los:F1} days. Care unit: {stay.FirstCareUnit}. " +
$"{obsCount} observations, {medCount} medications. " +
(admission.HospitalExpireFlag == 1
? "Patient expired during hospitalization."
: $"Discharged to {admission.DischargeLocation ?? "unknown"}.");
return new ScenarioMeta(
Id: $"mimic-s{stay.StayId}",
Name: $"MIMIC-IV — {stay.FirstCareUnit} ({patient.Gender}, ~{patient.AnchorAge}y)",
Description: description,
DurationMinutes: durationMinutes,
Tags: MimicCareUnitMap.GetTags(stay.FirstCareUnit, admission.HospitalExpireFlag));
}
private static decimal? ParseDose(string? doseValRx)
{
if (string.IsNullOrWhiteSpace(doseValRx)) return null;
var val = doseValRx.Trim();
var dashIdx = val.IndexOf('-');
if (dashIdx > 0) val = val[..dashIdx];
return decimal.TryParse(val, System.Globalization.NumberStyles.Any,
System.Globalization.CultureInfo.InvariantCulture, out var d) && d > 0
? d
: null;
}
private static DateTime RoundToMinute(DateTime dt)
=> new(dt.Year, dt.Month, dt.Day, dt.Hour, dt.Minute, 0);
private static readonly JsonSerializerOptions SerializerOptions = new()
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
};
private record RawObservation(
DateTime ChartTime, string Code, decimal Value,
string Unit, string Source, int Priority);
}
+2
View File
@@ -6,5 +6,7 @@ rootCommand.AddCommand(ReplayCommand.Create());
rootCommand.AddCommand(ReplayAllCommand.Create());
rootCommand.AddCommand(ValidateCommand.Create());
rootCommand.AddCommand(DryRunCommand.Create());
rootCommand.AddCommand(MimicListCommand.Create());
rootCommand.AddCommand(MimicGenerateCommand.Create());
return await rootCommand.InvokeAsync(args);
File diff suppressed because it is too large Load Diff
@@ -5,6 +5,9 @@
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<Copyright>Copyright (c) 2024-2026 voltsrage. All Rights Reserved.</Copyright>
<Authors>voltsrage</Authors>
<PackageLicenseFile>LICENSE</PackageLicenseFile>
</PropertyGroup>
<ItemGroup>
@@ -8,8 +8,15 @@ using StackExchange.Redis;
public class GatewayApiFixture : WebApplicationFactory<Program>, IAsyncLifetime
{
public const string TestConnectionString =
"Host=localhost;Port=5437;Database=vigilcare_ward_test;Username=postgres;Password=password";
// Prefer CI/env overrides; fall back to ward-gateway compose profile ports.
public static string TestConnectionString { get; } =
Environment.GetEnvironmentVariable("ConnectionStrings__GatewayDb")
?? "Host=localhost;Port=5437;Database=vigilcare_ward_test;Username=postgres;Password=password";
public static string RedisConnection { get; } =
Environment.GetEnvironmentVariable("Gateway__Redis__ConnectionString")
?? Environment.GetEnvironmentVariable("Redis__ConnectionString")
?? "localhost:6383,defaultDatabase=2,allowAdmin=true";
protected override void ConfigureWebHost(IWebHostBuilder builder)
{
@@ -19,11 +26,15 @@ public class GatewayApiFixture : WebApplicationFactory<Program>, IAsyncLifetime
config.AddInMemoryCollection(new Dictionary<string, string?>
{
["ConnectionStrings:GatewayDb"] = TestConnectionString,
["Redis:ConnectionString"] = "localhost:6383,allowAdmin=true",
["RabbitMq:Host"] = "localhost",
["RabbitMq:Port"] = "5675",
["RabbitMq:Username"] = "guest",
["RabbitMq:Password"] = "guest",
["Redis:ConnectionString"] = RedisConnection,
["RabbitMq:Host"] = Environment.GetEnvironmentVariable("Gateway__RabbitMq__Host")
?? Environment.GetEnvironmentVariable("RabbitMq__Host")
?? "localhost",
["RabbitMq:Port"] = Environment.GetEnvironmentVariable("Gateway__RabbitMq__Port")
?? Environment.GetEnvironmentVariable("RabbitMq__Port")
?? "5675",
["RabbitMq:Username"] = Environment.GetEnvironmentVariable("RabbitMq__Username") ?? "guest",
["RabbitMq:Password"] = Environment.GetEnvironmentVariable("RabbitMq__Password") ?? "guest",
["RabbitMq:PagingAckTimeoutMs"] = "5000",
["CentralApi:BaseUrl"] = "http://127.0.0.1:1",
["Gateway:GatewayId"] = "22222222-2222-2222-2222-222222222222",
@@ -33,6 +44,7 @@ public class GatewayApiFixture : WebApplicationFactory<Program>, IAsyncLifetime
["Gateway:CentralReachabilityIntervalSeconds"] = "1",
["Gateway:HeartbeatIntervalSeconds"] = "1",
["Gateway:SyncBatchSize"] = "500",
["Gateway:AutoMigrate"] = "false",
["ApiKey:Gateway"] = "dev-gateway-key-change-in-production",
["Jwt:SigningKey"] = "dev-signing-key-minimum-32-bytes-long!!",
["Jwt:Issuer"] = "vigilcare-gateway",
@@ -66,7 +78,12 @@ public class GatewayApiFixture : WebApplicationFactory<Program>, IAsyncLifetime
using var scope = Services.CreateScope();
var redis = scope.ServiceProvider.GetRequiredService<IConnectionMultiplexer>();
var server = redis.GetServer(redis.GetEndPoints().First());
await server.FlushAllDatabasesAsync();
// Flush only the gateway test DB index — do not FlushAll (would wipe API test DB 1).
var dbIndex = 2;
var cfg = RedisConnection.Split(',').FirstOrDefault(p => p.StartsWith("defaultDatabase=", StringComparison.OrdinalIgnoreCase));
if (cfg is not null && int.TryParse(cfg.Split('=')[1], out var parsed))
dbIndex = parsed;
await server.FlushDatabaseAsync(dbIndex);
}
protected override void ConfigureClient(HttpClient client)
@@ -25,14 +25,7 @@ public sealed class LocalEscalationWorkerService : BackgroundService
await Task.Delay(TimeSpan.FromSeconds(5), stoppingToken);
var o = _opts.Value;
var factory = new ConnectionFactory
{
HostName = o.Host,
Port = o.Port,
UserName = o.Username,
Password = o.Password,
DispatchConsumersAsync = true,
};
var factory = RabbitMqConnectionFactory.Create(o, dispatchConsumersAsync: true);
using var connection = factory.CreateConnection("gateway-escalation-worker");
using var channel = connection.CreateModel();
@@ -26,14 +26,7 @@ public sealed class LocalPagingWorkerService : BackgroundService
await Task.Delay(TimeSpan.FromSeconds(5), stoppingToken);
var o = _opts.Value;
var factory = new ConnectionFactory
{
HostName = o.Host,
Port = o.Port,
UserName = o.Username,
Password = o.Password,
DispatchConsumersAsync = true,
};
var factory = RabbitMqConnectionFactory.Create(o, dispatchConsumersAsync: true);
using var connection = factory.CreateConnection("gateway-paging-worker");
using var channel = connection.CreateModel();
@@ -8,4 +8,11 @@ public sealed class GatewayOptions
public int CentralReachabilityIntervalSeconds { get; init; } = 30;
public int HeartbeatIntervalSeconds { get; init; } = 60;
public int SyncBatchSize { get; init; } = 500;
/// <summary>
/// When true (default), applies EF migrations at startup. The gateway is
/// single-instance by design, so startup migration is safe. Set false if
/// migrations are applied out-of-band (e.g. an EF migration bundle).
/// </summary>
public bool AutoMigrate { get; init; } = true;
}
@@ -8,4 +8,9 @@ public sealed class RabbitMqOptions
// Drives both the paging worker poll timeout and the DLQ x-message-ttl.
// In production: 300000 (5 min). In tests: 5000 (5 sec).
public int PagingAckTimeoutMs { get; init; } = 300000;
/// <summary>
/// When true, enables TLS on the AMQP connection (typical production port 5671).
/// </summary>
public bool UseSsl { get; init; } = false;
}
+25 -2
View File
@@ -1,14 +1,37 @@
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
WORKDIR /src
COPY VigilCareClinical.sln ./
COPY VigilCare.ClinicalContracts/VigilCare.ClinicalContracts.csproj VigilCare.ClinicalContracts/
COPY VigilCare.WardGateway/VigilCare.WardGateway.csproj VigilCare.WardGateway/
RUN dotnet restore VigilCare.WardGateway/VigilCare.WardGateway.csproj
COPY VigilCare.ClinicalContracts/ VigilCare.ClinicalContracts/
COPY VigilCare.WardGateway/ VigilCare.WardGateway/
RUN dotnet restore VigilCare.WardGateway/VigilCare.WardGateway.csproj
RUN dotnet publish VigilCare.WardGateway/VigilCare.WardGateway.csproj -c Release -o /app/publish --no-restore
RUN dotnet publish VigilCare.WardGateway/VigilCare.WardGateway.csproj \
-c Release -o /app/publish --no-restore
# Scrub development secrets (Step 4) — production supplies Jwt / ApiKey via env.
RUN sed -i \
-e 's/"SigningKey": "[^"]*"/"SigningKey": ""/' \
-e 's/"Gateway": "dev-[^"]*"/"Gateway": ""/' \
/app/publish/appsettings.json
FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS runtime
WORKDIR /app
RUN apt-get update \
&& apt-get install -y --no-install-recommends curl \
&& rm -rf /var/lib/apt/lists/*
COPY --from=build /app/publish .
RUN chown -R app:app /app
USER app
ENV ASPNETCORE_URLS=http://+:8080
EXPOSE 8080
HEALTHCHECK --interval=30s --timeout=5s --start-period=40s --retries=3 \
CMD curl -fsS http://localhost:8080/health/live || exit 1
ENTRYPOINT ["dotnet", "VigilCare.WardGateway.dll"]
@@ -11,13 +11,7 @@ public sealed class RabbitMqHealthCheck : IHealthCheck
public async Task<HealthCheckResult> CheckHealthAsync(
HealthCheckContext context, CancellationToken cancellationToken = default)
{
var factory = new ConnectionFactory
{
HostName = _options.Host,
Port = _options.Port,
UserName = _options.Username,
Password = _options.Password
};
var factory = RabbitMqConnectionFactory.Create(_options);
using var connection = await Task.Run(() => factory.CreateConnection(), cancellationToken);
var data = new Dictionary<string, object> { ["endpoint"] = connection.Endpoint.ToString() };
@@ -0,0 +1,27 @@
using RabbitMQ.Client;
public static class RabbitMqConnectionFactory
{
public static ConnectionFactory Create(RabbitMqOptions o, bool dispatchConsumersAsync = false)
{
var factory = new ConnectionFactory
{
HostName = o.Host,
Port = o.Port,
UserName = o.Username,
Password = o.Password,
DispatchConsumersAsync = dispatchConsumersAsync,
};
if (o.UseSsl)
{
factory.Ssl = new SslOption
{
Enabled = true,
ServerName = o.Host,
};
}
return factory;
}
}
@@ -85,12 +85,6 @@ public sealed class RabbitMqTopologyProvisioner : IHostedService
public Task StopAsync(CancellationToken ct) => Task.CompletedTask;
public ConnectionFactory BuildFactory() => new()
{
HostName = _opts.Host,
Port = _opts.Port,
UserName = _opts.Username,
Password = _opts.Password,
DispatchConsumersAsync = true,
};
public ConnectionFactory BuildFactory() =>
RabbitMqConnectionFactory.Create(_opts, dispatchConsumersAsync: true);
}
+8 -6
View File
@@ -120,13 +120,15 @@ try
ResponseWriter = HealthCheckResponseWriter.WriteAsync
});
if (!app.Environment.IsEnvironment("Testing"))
// Startup migration is the proven path for this single-instance edge host.
// Gate with Gateway:AutoMigrate so production can switch to an out-of-band
// EF migration bundle later without a code change (Phase 36 Step 6).
if (!app.Environment.IsEnvironment("Testing")
&& builder.Configuration.GetValue("Gateway:AutoMigrate", true))
{
using (var scope = app.Services.CreateScope())
{
var db = scope.ServiceProvider.GetRequiredService<GatewayDbContext>();
await db.Database.MigrateAsync();
}
using var scope = app.Services.CreateScope();
var db = scope.ServiceProvider.GetRequiredService<GatewayDbContext>();
await db.Database.MigrateAsync();
}
app.Run();
@@ -18,13 +18,7 @@ public sealed class LocalPagingPublisher
{
try
{
var factory = new ConnectionFactory
{
HostName = _opts.Host,
Port = _opts.Port,
UserName = _opts.Username,
Password = _opts.Password
};
var factory = RabbitMqConnectionFactory.Create(_opts);
using var conn = factory.CreateConnection("gateway-publisher");
using var channel = conn.CreateModel();
@@ -4,6 +4,9 @@
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<Copyright>Copyright (c) 2024-2026 voltsrage. All Rights Reserved.</Copyright>
<Authors>voltsrage</Authors>
<PackageLicenseFile>LICENSE</PackageLicenseFile>
</PropertyGroup>
<ItemGroup>
@@ -12,6 +15,11 @@
<PackageReference Include="prometheus-net.AspNetCore" Version="8.2.1" />
<PackageReference Include="RabbitMQ.Client" Version="6.8.1" />
<PackageReference Include="Serilog.AspNetCore" Version="10.0.0" />
<PackageReference Include="Serilog.Enrichers.Environment" Version="2.3.0" />
<PackageReference Include="Serilog.Enrichers.Thread" Version="3.1.0" />
<PackageReference Include="Serilog.Formatting.Compact" Version="3.0.0" />
<PackageReference Include="Serilog.Sinks.Console" Version="6.1.1" />
<PackageReference Include="Serilog.Sinks.Seq" Version="9.1.0" />
<PackageReference Include="StackExchange.Redis" Version="3.0.0" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="8.0.27" />
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="8.0.27" />
@@ -0,0 +1,26 @@
{
"Serilog": {
"Using": [ "Serilog.Sinks.Console", "Serilog.Sinks.Seq" ],
"MinimumLevel": {
"Default": "Information",
"Override": {
"Microsoft.AspNetCore": "Warning",
"Microsoft.EntityFrameworkCore.Database.Command": "Warning"
}
},
"WriteTo": [
{ "Name": "Console", "Args": { "formatter": "Serilog.Formatting.Compact.CompactJsonFormatter, Serilog.Formatting.Compact" } },
{ "Name": "Seq", "Args": { "serverUrl": "http://localhost:5341" } }
],
"Enrich": [ "FromLogContext", "WithMachineName", "WithThreadId" ],
"Properties": {
"Application": "VigilCare.WardGateway"
}
},
"Gateway": {
"AutoMigrate": true
},
"RabbitMq": {
"UseSsl": true
}
}
+4 -2
View File
@@ -10,7 +10,8 @@
"Port": 5675,
"Username": "guest",
"Password": "guest",
"PagingAckTimeoutMs": 300000
"PagingAckTimeoutMs": 300000,
"UseSsl": false
},
"CentralApi": {
"BaseUrl": "http://localhost:5270"
@@ -22,7 +23,8 @@
"EncounterSyncIntervalMinutes": 5,
"CentralReachabilityIntervalSeconds": 30,
"HeartbeatIntervalSeconds": 60,
"SyncBatchSize": 500
"SyncBatchSize": 500,
"AutoMigrate": true
},
"ApiKey": {
"Gateway": "dev-gateway-key-change-in-production"
@@ -8,6 +8,21 @@ using StackExchange.Redis;
public class ApiFixture : WebApplicationFactory<Program>, IAsyncLifetime
{
// Prefer CI/env overrides; fall back to local docker-compose.yml host ports.
public static string PgConnection { get; } =
Environment.GetEnvironmentVariable("ConnectionStrings__DefaultConnection")
?? "Host=localhost;Port=5436;Database=vigilcare_test;Username=postgres;Password=password";
public static string RedisConnection { get; } =
Environment.GetEnvironmentVariable("Redis__ConnectionString")
?? "localhost:6382,defaultDatabase=1,allowAdmin=true";
public static string RabbitHost { get; } =
Environment.GetEnvironmentVariable("RabbitMq__Host") ?? "localhost";
public static int RabbitPort { get; } =
int.TryParse(Environment.GetEnvironmentVariable("RabbitMq__Port"), out var p) ? p : 5674;
// Override configuration to point at a test database — never run tests against
// the development database; a botched rollback could corrupt seed data.
protected override void ConfigureWebHost(IWebHostBuilder builder)
@@ -17,17 +32,24 @@ public class ApiFixture : WebApplicationFactory<Program>, IAsyncLifetime
{
config.AddInMemoryCollection(new Dictionary<string, string?>
{
["ConnectionStrings:DefaultConnection"] =
"Host=localhost;Port=5436;Database=vigilcare_test;Username=postgres;Password=password",
["Redis:ConnectionString"] = "localhost:6382,defaultDatabase=1,allowAdmin=true",
["RabbitMq:Host"] = "localhost",
["RabbitMq:Port"] = "5674",
["RabbitMq:Username"] = "guest",
["RabbitMq:Password"] = "guest",
["ConnectionStrings:DefaultConnection"] = PgConnection,
["Redis:ConnectionString"] = RedisConnection,
["RabbitMq:Host"] = RabbitHost,
["RabbitMq:Port"] = RabbitPort.ToString(),
["RabbitMq:Username"] = Environment.GetEnvironmentVariable("RabbitMq__Username") ?? "guest",
["RabbitMq:Password"] = Environment.GetEnvironmentVariable("RabbitMq__Password") ?? "guest",
["RabbitMq:PagingAckTimeoutMs"] = "5000",
["RabbitMq:VirtualHost"] = "vigilcare_test",
["Kafka:BootstrapServers"] =
Environment.GetEnvironmentVariable("Kafka__BootstrapServers") ?? "localhost:9092",
["Kafka:ReplicationFactor"] =
Environment.GetEnvironmentVariable("Kafka__ReplicationFactor") ?? "1",
["Kafka:NotificationPublisherGroupId"] = "notification-publisher-integration-test",
["Kafka:NotificationPublisherAutoOffsetReset"] = "Latest",
["Elasticsearch:Uri"] =
Environment.GetEnvironmentVariable("Elasticsearch__Uri") ?? "http://localhost:9200",
["Minio:Endpoint"] =
Environment.GetEnvironmentVariable("Minio__Endpoint") ?? "localhost:9005",
["Fhir:ApiKey"] = "dev-integration-key-change-in-production",
["ApiKey:Gateway"] = GatewayAuthHelper.DevGatewayKey,
});
@@ -52,9 +74,8 @@ public class ApiFixture : WebApplicationFactory<Program>, IAsyncLifetime
// Apply migrations and clean stale data before the host starts — background
// services such as ThresholdCacheLoader query the database during StartAsync,
// so the reset must happen while no hosted service holds a lock.
var connectionString = "Host=localhost;Port=5436;Database=vigilcare_test;Username=postgres;Password=password";
var options = new DbContextOptionsBuilder<AppDbContext>()
.UseNpgsql(connectionString)
.UseNpgsql(PgConnection)
.Options;
await using (var migrateDb = new AppDbContext(options))
{
@@ -64,10 +85,10 @@ public class ApiFixture : WebApplicationFactory<Program>, IAsyncLifetime
await RabbitMqTestHelper.EnsureVirtualHostAsync(new RabbitMqOptions
{
Host = "localhost",
Port = 5674,
Username = "guest",
Password = "guest",
Host = RabbitHost,
Port = RabbitPort,
Username = Environment.GetEnvironmentVariable("RabbitMq__Username") ?? "guest",
Password = Environment.GetEnvironmentVariable("RabbitMq__Password") ?? "guest",
VirtualHost = "vigilcare_test",
});
@@ -39,7 +39,7 @@ public class EsIndexerService : BackgroundService
// Consumers must be idempotent. See idempotency contract above.
EnableAutoCommit = false,
EnablePartitionEof = false
};
}.ApplySecurity(_kafkaOptions);
using var consumer = new ConsumerBuilder<string, string>(config).Build();
@@ -26,7 +26,7 @@ public class GcsScoringService : BackgroundService
GroupId = "gcs-scoring",
AutoOffsetReset = AutoOffsetReset.Earliest,
EnableAutoCommit = false
};
}.ApplySecurity(_kafkaOptions);
using var consumer = new ConsumerBuilder<string, string>(config).Build();
consumer.Subscribe(_kafkaOptions.Topics.ObservationRecorded);
@@ -18,7 +18,7 @@ public class KafkaTopicProvisioner : IHostedService
using var admin = new AdminClientBuilder(new AdminClientConfig
{
BootstrapServers = _options.BootstrapServers
}).Build();
}.ApplySecurity(_options)).Build();
var topicNames = new[]
{
@@ -48,7 +48,8 @@ public sealed class KafkaConsumerLagCollector : BackgroundService
private async Task CollectGroupLagAsync(string groupId, CancellationToken ct)
{
var adminConfig = new AdminClientConfig
{ BootstrapServers = _kafkaOptions.BootstrapServers };
{ BootstrapServers = _kafkaOptions.BootstrapServers }
.ApplySecurity(_kafkaOptions);
using var admin = new AdminClientBuilder(adminConfig).Build();
@@ -69,7 +70,7 @@ public sealed class KafkaConsumerLagCollector : BackgroundService
{
BootstrapServers = _kafkaOptions.BootstrapServers,
GroupId = $"__lag-probe",
}).Build();
}.ApplySecurity(_kafkaOptions)).Build();
long totalLag = 0;
foreach (var tpo in partitions)
@@ -26,7 +26,7 @@ public class News2ScoringService : BackgroundService
GroupId = "news2-scoring",
AutoOffsetReset = AutoOffsetReset.Earliest,
EnableAutoCommit = false
};
}.ApplySecurity(_kafkaOptions);
using var consumer = new ConsumerBuilder<string, string>(config).Build();
consumer.Subscribe(_kafkaOptions.Topics.ObservationRecorded);
@@ -42,7 +42,7 @@ public sealed class NotificationPublisherService : BackgroundService
AutoOffsetReset = Enum.Parse<AutoOffsetReset>(
_kafkaOptions.NotificationPublisherAutoOffsetReset, ignoreCase: true),
EnableAutoCommit = false,
};
}.ApplySecurity(_kafkaOptions);
using var consumer = new ConsumerBuilder<string, string>(consumerConfig).Build();
consumer.Subscribe(new[]
@@ -43,7 +43,7 @@ public class OutboxRelayService : BackgroundService
EnableIdempotence = true,
MessageSendMaxRetries = 3,
RetryBackoffMs = 100
}).Build();
}.ApplySecurity(_options)).Build();
var factory = RabbitMqConnectionFactory.Create(_rabbitOpts);
_rabbitConnection = factory.CreateConnection("outbox-relay");
@@ -31,7 +31,7 @@ public class SepsisEngineService : BackgroundService
GroupId = "sepsis-engine",
AutoOffsetReset = AutoOffsetReset.Earliest,
EnableAutoCommit = false
};
}.ApplySecurity(_kafkaOptions);
using var consumer = new ConsumerBuilder<string, string>(config).Build();
consumer.Subscribe(_kafkaOptions.Topics.ObservationRecorded);
@@ -26,7 +26,7 @@ public class SofaScoringService : BackgroundService
GroupId = "sofa-scoring",
AutoOffsetReset = AutoOffsetReset.Earliest,
EnableAutoCommit = false
};
}.ApplySecurity(_kafkaOptions);
using var consumer = new ConsumerBuilder<string, string>(config).Build();
consumer.Subscribe(new[]
@@ -26,7 +26,7 @@ public class TrendAnalyzerService : BackgroundService
GroupId = "trend-analyzer",
AutoOffsetReset = AutoOffsetReset.Earliest,
EnableAutoCommit = false
};
}.ApplySecurity(_kafkaOptions);
using var consumer = new ConsumerBuilder<string, string>(config).Build();
consumer.Subscribe(_kafkaOptions.Topics.ObservationRecorded);
@@ -26,7 +26,7 @@ public class WarningAlertService : BackgroundService
GroupId = "warning-evaluator",
AutoOffsetReset = AutoOffsetReset.Earliest,
EnableAutoCommit = false
};
}.ApplySecurity(_kafkaOptions);
using var consumer = new ConsumerBuilder<string, string>(config).Build();
consumer.Subscribe(_kafkaOptions.Topics.ObservationRecorded);
@@ -0,0 +1,81 @@
using Microsoft.EntityFrameworkCore;
/// <summary>
/// One-shot bootstrap: creates the first Admin user when the ClinicalUsers
/// table is empty. Refuses to run if any user already exists so it cannot
/// be used as a backdoor after the system is in use.
///
/// Usage:
/// dotnet VigilCareClinicalAPI.dll create-admin --username &lt;u&gt; --password &lt;p&gt; --display-name "&lt;n&gt;"
/// </summary>
public static class CreateAdminCommand
{
public static async Task<int> RunAsync(IServiceProvider services, string[] args)
{
var username = RequireArg(args, "--username");
var password = RequireArg(args, "--password");
var displayName = RequireArg(args, "--display-name");
if (username.Length > 100)
{
Console.Error.WriteLine("Username must be 100 characters or fewer.");
return 1;
}
if (password.Length < 8)
{
Console.Error.WriteLine("Password must be at least 8 characters.");
return 1;
}
if (displayName.Length > 200)
{
Console.Error.WriteLine("Display name must be 200 characters or fewer.");
return 1;
}
using var scope = services.CreateScope();
var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();
if (await db.ClinicalUsers.AnyAsync())
{
Console.Error.WriteLine(
"Refusing to create admin: ClinicalUsers already contains at least one row. " +
"create-admin is a one-shot bootstrap and cannot be used as a backdoor.");
return 1;
}
var user = new ClinicalUser
{
Id = Guid.NewGuid(),
Username = username.Trim(),
PasswordHash = BCrypt.Net.BCrypt.HashPassword(password),
DisplayName = displayName.Trim(),
Role = ClinicalRole.Admin,
IsActive = true,
CreatedAt = DateTimeOffset.UtcNow,
};
db.ClinicalUsers.Add(user);
await db.SaveChangesAsync();
Console.WriteLine($"Created admin user '{user.Username}' (id={user.Id}).");
return 0;
}
private static string RequireArg(string[] args, string name)
{
for (var i = 0; i < args.Length - 1; i++)
{
if (string.Equals(args[i], name, StringComparison.OrdinalIgnoreCase))
{
var value = args[i + 1];
if (string.IsNullOrWhiteSpace(value) || value.StartsWith("--", StringComparison.Ordinal))
break;
return value;
}
}
throw new ArgumentException($"Missing required argument: {name}");
}
}
@@ -0,0 +1,125 @@
using Microsoft.EntityFrameworkCore;
/// <summary>
/// One-shot / idempotent registration of a clinical site + ward gateway with
/// caller-supplied IDs. Production cannot rely on GatewayRegistrySeeder (demo
/// data is gated off); the gateway authenticates using Gateway:GatewayId /
/// Gateway:SiteId from its environment, so those GUIDs must exist in the API DB.
///
/// Usage:
/// dotnet VigilCareClinicalAPI.dll register-gateway \
/// --site-id &lt;guid&gt; --gateway-id &lt;guid&gt; \
/// --site-code SITE-01 --site-name "General Hospital" \
/// --gateway-code GW-ICU-1 --department ICU \
/// [--address "123 Main St"]
///
/// Re-running with the same IDs is a no-op success. Conflicting codes or IDs fail.
/// </summary>
public static class RegisterGatewayCommand
{
public static async Task<int> RunAsync(IServiceProvider services, string[] args)
{
var siteId = RequireGuid(args, "--site-id");
var gatewayId = RequireGuid(args, "--gateway-id");
var siteCode = RequireArg(args, "--site-code").Trim();
var siteName = RequireArg(args, "--site-name").Trim();
var gatewayCode = RequireArg(args, "--gateway-code").Trim();
var department = RequireArg(args, "--department").Trim();
var address = GetArg(args, "--address")?.Trim();
using var scope = services.CreateScope();
var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();
var existingGateway = await db.WardGateways
.Include(g => g.Site)
.FirstOrDefaultAsync(g => g.Id == gatewayId);
if (existingGateway is not null)
{
if (existingGateway.SiteId != siteId
|| !string.Equals(existingGateway.GatewayCode, gatewayCode, StringComparison.Ordinal)
|| !string.Equals(existingGateway.Department, department, StringComparison.Ordinal))
{
Console.Error.WriteLine(
$"Gateway id {gatewayId} already exists with different site/code/department. Aborting.");
return 1;
}
Console.WriteLine(
$"Gateway '{existingGateway.GatewayCode}' (id={gatewayId}) already registered — nothing to do.");
return 0;
}
var existingSite = await db.ClinicalSites.FirstOrDefaultAsync(s => s.Id == siteId);
if (existingSite is null)
{
var codeTaken = await db.ClinicalSites.AnyAsync(s => s.SiteCode == siteCode);
if (codeTaken)
{
Console.Error.WriteLine($"Site code '{siteCode}' is already registered under a different id.");
return 1;
}
var site = new ClinicalSite(siteCode, siteName, address);
db.Entry(site).Property(nameof(ClinicalSite.Id)).CurrentValue = siteId;
db.ClinicalSites.Add(site);
Console.WriteLine($"Created site '{siteCode}' (id={siteId}).");
}
else if (!string.Equals(existingSite.SiteCode, siteCode, StringComparison.Ordinal))
{
Console.Error.WriteLine(
$"Site id {siteId} already exists as code '{existingSite.SiteCode}', " +
$"not '{siteCode}'. Aborting.");
return 1;
}
var duplicateCode = await db.WardGateways.AnyAsync(g =>
g.SiteId == siteId && g.GatewayCode == gatewayCode);
if (duplicateCode)
{
Console.Error.WriteLine(
$"Gateway code '{gatewayCode}' already exists for site {siteId} under a different id.");
return 1;
}
var gateway = new WardGateway(siteId, gatewayCode, department);
db.Entry(gateway).Property(nameof(WardGateway.Id)).CurrentValue = gatewayId;
db.WardGateways.Add(gateway);
await db.SaveChangesAsync();
Console.WriteLine($"Registered gateway '{gatewayCode}' (id={gatewayId}) on site {siteId}.");
return 0;
}
private static Guid RequireGuid(string[] args, string name)
{
var raw = RequireArg(args, name);
if (!Guid.TryParse(raw, out var id) || id == Guid.Empty)
throw new ArgumentException($"{name} must be a non-empty GUID.");
return id;
}
private static string RequireArg(string[] args, string name)
{
var value = GetArg(args, name);
if (string.IsNullOrWhiteSpace(value))
throw new ArgumentException($"Missing required argument: {name}");
return value;
}
private static string? GetArg(string[] args, string name)
{
for (var i = 0; i < args.Length - 1; i++)
{
if (string.Equals(args[i], name, StringComparison.OrdinalIgnoreCase))
{
var value = args[i + 1];
if (string.IsNullOrWhiteSpace(value) || value.StartsWith("--", StringComparison.Ordinal))
return null;
return value;
}
}
return null;
}
}
@@ -5,5 +5,6 @@ public class JwtOptions
public string Issuer { get; set; } = "VigilCareClinical";
public string Audience { get; set; } = "VigilCareClinical.Dashboard";
public string SigningKey { get; set; } = null!;
public int ExpirationMinutes { get; set; } = 480;
public int ExpirationMinutes { get; set; } = 15;
public int RefreshTokenExpirationDays { get; set; } = 7;
}
@@ -11,4 +11,11 @@ public class KafkaOptions
public int MaxPoisonRetries { get; set; } = 5;
public string NotificationPublisherGroupId { get; set; } = "notification-publisher";
public string NotificationPublisherAutoOffsetReset { get; set; } = "Earliest";
/// <summary>Plaintext for local compose; SaslSsl (etc.) for production.</summary>
public string SecurityProtocol { get; set; } = "Plaintext";
public string? SaslMechanism { get; set; }
public string? SaslUsername { get; set; }
public string? SaslPassword { get; set; }
public string? SslCaLocation { get; set; }
}
@@ -0,0 +1,29 @@
using Confluent.Kafka;
public static class KafkaSecurityExtensions
{
/// <summary>
/// Applies the configured security protocol and SASL credentials to any
/// Confluent client config. Called by every producer, consumer, and admin
/// client so credentials are configured in exactly one place.
/// </summary>
public static T ApplySecurity<T>(this T config, KafkaOptions options)
where T : ClientConfig
{
if (Enum.TryParse<SecurityProtocol>(options.SecurityProtocol, true, out var protocol))
config.SecurityProtocol = protocol;
if (!string.IsNullOrWhiteSpace(options.SaslMechanism)
&& Enum.TryParse<SaslMechanism>(options.SaslMechanism, true, out var mechanism))
{
config.SaslMechanism = mechanism;
config.SaslUsername = options.SaslUsername;
config.SaslPassword = options.SaslPassword;
}
if (!string.IsNullOrWhiteSpace(options.SslCaLocation))
config.SslCaLocation = options.SslCaLocation;
return config;
}
}
@@ -9,4 +9,10 @@ public sealed class RabbitMqOptions
// Drives both the paging worker poll timeout and the DLQ x-message-ttl.
// In production: 300000 (5 min). In tests: 5000 (5 sec).
public int PagingAckTimeoutMs { get; init; } = 300000;
/// <summary>
/// When true, enables TLS on the AMQP connection (typical production port 5671).
/// Local compose uses plaintext on 5674/5672 — leave false.
/// </summary>
public bool UseSsl { get; init; } = false;
}
@@ -2,7 +2,7 @@ using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
/// <summary>
/// JWT authentication: login and current-user profile.
/// JWT authentication: login, token refresh, logout, and current-user profile.
/// </summary>
[ApiController]
[Route("api/v1/auth")]
@@ -24,6 +24,29 @@ public class AuthController : ControllerBase
return Ok(ApiResponse<LoginResponse>.Ok(result));
}
/// <summary>Exchange a refresh token for a new access + refresh token pair.</summary>
[HttpPost("refresh")]
[AllowAnonymous]
[ProducesResponseType(typeof(ApiResponse<RefreshResponse>), StatusCodes.Status200OK)]
[ProducesResponseType(typeof(ApiResponse<object>), StatusCodes.Status422UnprocessableEntity)]
public async Task<IActionResult> Refresh([FromBody] RefreshRequest req)
{
var result = await _auth.RefreshAsync(req.RefreshToken);
return Ok(ApiResponse<RefreshResponse>.Ok(result));
}
/// <summary>Revoke the refresh token and end the session.</summary>
[HttpPost("logout")]
[Authorize]
[ProducesResponseType(StatusCodes.Status204NoContent)]
public async Task<IActionResult> Logout(
[FromBody] LogoutRequest req,
[FromServices] ICurrentUserService currentUser)
{
await _auth.LogoutAsync(req.RefreshToken, currentUser.UserId!.Value);
return NoContent();
}
/// <summary>Returns the authenticated user's profile.</summary>
[HttpGet("me")]
[Authorize]
@@ -36,6 +36,7 @@ public class AppDbContext : DbContext
public DbSet<ClinicalSyncConflict> ClinicalSyncConflicts => Set<ClinicalSyncConflict>();
public DbSet<AlertFeedback> AlertFeedbacks => Set<AlertFeedback>();
public DbSet<AlertQualityMetric> AlertQualityMetrics => Set<AlertQualityMetric>();
public DbSet<RefreshToken> RefreshTokens => Set<RefreshToken>();
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
@@ -3,4 +3,12 @@ public class ElasticsearchOptions
public const string Section = "Elasticsearch";
public string Uri { get; set; } = null!;
public ElasticIndexOptions Indices { get; set; } = null!;
// Production clusters run with xpack.security enabled. Supply either an
// API key (preferred) or basic credentials; leave all null for the
// security-disabled development cluster.
public string? ApiKey { get; set; }
public string? Username { get; set; }
public string? Password { get; set; }
public bool DisableCertificateValidation { get; set; }
}
@@ -0,0 +1,25 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
public class RefreshTokenConfiguration : IEntityTypeConfiguration<RefreshToken>
{
public void Configure(EntityTypeBuilder<RefreshToken> builder)
{
builder.ToTable("refresh_tokens");
builder.HasKey(t => t.Id);
builder.Property(t => t.Id).HasColumnName("id").HasDefaultValueSql("gen_random_uuid()");
builder.Property(t => t.Token).HasColumnName("token").HasMaxLength(256).IsRequired();
builder.Property(t => t.UserId).HasColumnName("user_id").IsRequired();
builder.Property(t => t.ExpiresAt).HasColumnName("expires_at").IsRequired();
builder.Property(t => t.CreatedAt).HasColumnName("created_at").HasDefaultValueSql("NOW()");
builder.Property(t => t.RevokedAt).HasColumnName("revoked_at");
builder.HasIndex(t => t.Token).IsUnique();
builder.HasIndex(t => t.UserId);
builder.HasOne(t => t.User)
.WithMany()
.HasForeignKey(t => t.UserId)
.OnDelete(DeleteBehavior.Cascade);
}
}
@@ -92,6 +92,10 @@ public static class DataSeeder
db.Observations.AddRange(observations);
await db.SaveChangesAsync();
await db.Database.ExecuteSqlRawAsync(
"SELECT setval('mrn_seq', COALESCE((SELECT MAX(CAST(SUBSTRING(mrn FROM 5) AS bigint)) FROM patients WHERE mrn ~ '^MRN-[0-9]+$'), 0))");
await CacheThresholdsAsync(redis, thresholds);
}
@@ -40,7 +40,7 @@ public sealed class DataLakeWriterService : BackgroundService
GroupId = "data-lake-writer",
AutoOffsetReset = AutoOffsetReset.Earliest,
EnableAutoCommit = false,
};
}.ApplySecurity(_kafkaOptions);
using var consumer = new ConsumerBuilder<string, string>(consumerConfig).Build();
consumer.Subscribe(new[]
+47
View File
@@ -0,0 +1,47 @@
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
WORKDIR /src
# Restore layer — copy only project files so NuGet restore is cached
# independently of source changes. ClinicalContracts must be present
# because VigilCareClinicalAPI.csproj ProjectReferences it.
COPY VigilCareClinical.sln ./
COPY VigilCare.ClinicalContracts/VigilCare.ClinicalContracts.csproj VigilCare.ClinicalContracts/
COPY VigilCareClinicalAPI/VigilCareClinicalAPI.csproj VigilCareClinicalAPI/
RUN dotnet restore VigilCareClinicalAPI/VigilCareClinicalAPI.csproj
COPY VigilCare.ClinicalContracts/ VigilCare.ClinicalContracts/
COPY VigilCareClinicalAPI/ VigilCareClinicalAPI/
RUN dotnet publish VigilCareClinicalAPI/VigilCareClinicalAPI.csproj \
-c Release -o /app/publish --no-restore
# Scrub development secrets from the published appsettings.json (Step 4).
# Production supplies Jwt / PHI / API keys via environment variables only.
RUN sed -i \
-e 's/"SigningKey": "[^"]*"/"SigningKey": ""/' \
-e 's/"SearchTokenKey": "[^"]*"/"SearchTokenKey": ""/' \
-e 's/"Gateway": "dev-[^"]*"/"Gateway": ""/' \
-e 's/"ApiKey": "dev-integration[^"]*"/"ApiKey": ""/' \
/app/publish/appsettings.json
FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS runtime
WORKDIR /app
# curl is required by the container healthcheck; the aspnet image does not ship it.
RUN apt-get update \
&& apt-get install -y --no-install-recommends curl \
&& rm -rf /var/lib/apt/lists/*
COPY --from=build /app/publish .
# The keyring directory must be owned by the runtime user — see Step 10.
# The aspnet:8.0 image ships a non-root `app` user (uid 1654).
RUN mkdir -p /app/data-protection-keys && chown -R app:app /app
USER app
ENV ASPNETCORE_URLS=http://+:8080
EXPOSE 8080
HEALTHCHECK --interval=30s --timeout=5s --start-period=40s --retries=3 \
CMD curl -fsS http://localhost:8080/health/live || exit 1
ENTRYPOINT ["dotnet", "VigilCareClinicalAPI.dll"]
@@ -0,0 +1,11 @@
public class RefreshToken
{
public Guid Id { get; set; }
public string Token { get; set; } = null!;
public Guid UserId { get; set; }
public DateTimeOffset ExpiresAt { get; set; }
public DateTimeOffset CreatedAt { get; set; }
public DateTimeOffset? RevokedAt { get; set; }
public ClinicalUser User { get; set; } = null!;
}
@@ -12,6 +12,8 @@ public enum AuditAction
UserLogin,
AuthorizationDenied,
AlertFeedbackSubmitted,
UserLogout,
TokenRefreshed,
}
public static class AuditActionExtensions
@@ -30,6 +32,8 @@ public static class AuditActionExtensions
AuditAction.UserLogin => "USER_LOGIN",
AuditAction.AuthorizationDenied => "AUTHORIZATION_DENIED",
AuditAction.AlertFeedbackSubmitted => "ALERT_FEEDBACK_SUBMITTED",
AuditAction.UserLogout => "USER_LOGOUT",
AuditAction.TokenRefreshed => "TOKEN_REFRESHED",
_ => throw new ArgumentOutOfRangeException(nameof(a))
};
@@ -47,6 +51,8 @@ public static class AuditActionExtensions
"USER_LOGIN" => AuditAction.UserLogin,
"AUTHORIZATION_DENIED" => AuditAction.AuthorizationDenied,
"ALERT_FEEDBACK_SUBMITTED" => AuditAction.AlertFeedbackSubmitted,
"USER_LOGOUT" => AuditAction.UserLogout,
"TOKEN_REFRESHED" => AuditAction.TokenRefreshed,
_ => throw new ArgumentOutOfRangeException(nameof(v))
};
}
@@ -0,0 +1,11 @@
using System.Text.Json;
using System.Text.Json.Serialization;
public sealed class AlertSeverityJsonConverter : JsonConverter<AlertSeverity>
{
public override AlertSeverity Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
=> AlertSeverityExtensions.FromDbString(reader.GetString()!);
public override void Write(Utf8JsonWriter writer, AlertSeverity value, JsonSerializerOptions options)
=> writer.WriteStringValue(value.ToDbString());
}
@@ -0,0 +1,11 @@
using System.Text.Json;
using System.Text.Json.Serialization;
public sealed class AlertStatusJsonConverter : JsonConverter<AlertStatus>
{
public override AlertStatus Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
=> AlertStatusExtensions.FromDbString(reader.GetString()!);
public override void Write(Utf8JsonWriter writer, AlertStatus value, JsonSerializerOptions options)
=> writer.WriteStringValue(value.ToDbString());
}
@@ -0,0 +1,11 @@
using System.Text.Json;
using System.Text.Json.Serialization;
public sealed class AlertTypeJsonConverter : JsonConverter<AlertType>
{
public override AlertType Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
=> AlertTypeExtensions.FromDbString(reader.GetString()!);
public override void Write(Utf8JsonWriter writer, AlertType value, JsonSerializerOptions options)
=> writer.WriteStringValue(value.ToDbString());
}
@@ -14,7 +14,7 @@ public sealed class KafkaHealthCheck : IHealthCheck
using var admin = new AdminClientBuilder(new AdminClientConfig
{
BootstrapServers = _options.BootstrapServers
}).Build();
}.ApplySecurity(_options)).Build();
var metadata = await Task.Run(
() => admin.GetMetadata(TimeSpan.FromSeconds(5)), cancellationToken);
@@ -44,7 +44,10 @@ namespace VigilCareClinicalAPI.Migrations
table: "patients");
migrationBuilder.Sql(
"ALTER TABLE patients ALTER COLUMN date_of_birth TYPE date USING date_of_birth::date;");
"""
UPDATE patients SET date_of_birth = '1900-01-01' WHERE date_of_birth !~ '^\d{4}-\d{2}-\d{2}$';
ALTER TABLE patients ALTER COLUMN date_of_birth TYPE date USING date_of_birth::date;
""");
}
}
}
@@ -56,6 +56,14 @@ namespace VigilCareClinicalAPI.Migrations
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.Sql(
"""
UPDATE patients SET last_name = LEFT(last_name, 100);
UPDATE patients SET first_name = LEFT(first_name, 100);
UPDATE patients SET emergency_contact_phone = LEFT(emergency_contact_phone, 20);
UPDATE patients SET emergency_contact_name = LEFT(emergency_contact_name, 200);
""");
migrationBuilder.AlterColumn<string>(
name: "last_name",
table: "patients",
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,55 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace VigilCareClinicalAPI.Migrations
{
/// <inheritdoc />
public partial class AddRefreshTokens : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "refresh_tokens",
columns: table => new
{
id = table.Column<Guid>(type: "uuid", nullable: false, defaultValueSql: "gen_random_uuid()"),
token = table.Column<string>(type: "character varying(256)", maxLength: 256, nullable: false),
user_id = table.Column<Guid>(type: "uuid", nullable: false),
expires_at = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: false),
created_at = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: false, defaultValueSql: "NOW()"),
revoked_at = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_refresh_tokens", x => x.id);
table.ForeignKey(
name: "FK_refresh_tokens_clinical_users_user_id",
column: x => x.user_id,
principalTable: "clinical_users",
principalColumn: "id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateIndex(
name: "IX_refresh_tokens_token",
table: "refresh_tokens",
column: "token",
unique: true);
migrationBuilder.CreateIndex(
name: "IX_refresh_tokens_user_id",
table: "refresh_tokens",
column: "user_id");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "refresh_tokens");
}
}
}
@@ -1377,6 +1377,48 @@ namespace VigilCareClinicalAPI.Migrations
});
});
modelBuilder.Entity("RefreshToken", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid")
.HasColumnName("id")
.HasDefaultValueSql("gen_random_uuid()");
b.Property<DateTimeOffset>("CreatedAt")
.ValueGeneratedOnAdd()
.HasColumnType("timestamp with time zone")
.HasColumnName("created_at")
.HasDefaultValueSql("NOW()");
b.Property<DateTimeOffset>("ExpiresAt")
.HasColumnType("timestamp with time zone")
.HasColumnName("expires_at");
b.Property<DateTimeOffset?>("RevokedAt")
.HasColumnType("timestamp with time zone")
.HasColumnName("revoked_at");
b.Property<string>("Token")
.IsRequired()
.HasMaxLength(256)
.HasColumnType("character varying(256)")
.HasColumnName("token");
b.Property<Guid>("UserId")
.HasColumnType("uuid")
.HasColumnName("user_id");
b.HasKey("Id");
b.HasIndex("Token")
.IsUnique();
b.HasIndex("UserId");
b.ToTable("refresh_tokens", (string)null);
});
modelBuilder.Entity("SepsisBundle", b =>
{
b.Property<Guid>("Id")
@@ -1769,6 +1811,17 @@ namespace VigilCareClinicalAPI.Migrations
b.Navigation("Patient");
});
modelBuilder.Entity("RefreshToken", b =>
{
b.HasOne("ClinicalUser", "User")
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("User");
});
modelBuilder.Entity("SepsisBundle", b =>
{
b.HasOne("Encounter", "Encounter")
@@ -1,5 +1,6 @@
public record LoginResponse(
string AccessToken,
string RefreshToken,
DateTimeOffset ExpiresAt,
Guid UserId,
string Username,
@@ -0,0 +1 @@
public record LogoutRequest(string RefreshToken);
@@ -0,0 +1 @@
public record RefreshRequest(string RefreshToken);
@@ -0,0 +1,4 @@
public record RefreshResponse(
string AccessToken,
string RefreshToken,
DateTimeOffset ExpiresAt);
@@ -2,13 +2,27 @@ using RabbitMQ.Client;
public static class RabbitMqConnectionFactory
{
public static ConnectionFactory Create(RabbitMqOptions o, bool dispatchConsumersAsync = false) => new()
public static ConnectionFactory Create(RabbitMqOptions o, bool dispatchConsumersAsync = false)
{
HostName = o.Host,
Port = o.Port,
UserName = o.Username,
Password = o.Password,
VirtualHost = o.VirtualHost,
DispatchConsumersAsync = dispatchConsumersAsync,
};
var factory = new ConnectionFactory
{
HostName = o.Host,
Port = o.Port,
UserName = o.Username,
Password = o.Password,
VirtualHost = o.VirtualHost,
DispatchConsumersAsync = dispatchConsumersAsync,
};
if (o.UseSsl)
{
factory.Ssl = new SslOption
{
Enabled = true,
ServerName = o.Host,
};
}
return factory;
}
}
+70 -9
View File
@@ -94,8 +94,21 @@ try
.GetSection(ElasticsearchOptions.Section)
.Get<ElasticsearchOptions>()!;
builder.Services.AddSingleton(
new ElasticsearchClient(new Uri(esOptions.Uri)));
builder.Services.AddSingleton(_ =>
{
var settings = new ElasticsearchClientSettings(new Uri(esOptions.Uri));
if (!string.IsNullOrWhiteSpace(esOptions.ApiKey))
settings = settings.Authentication(new Elastic.Transport.ApiKey(esOptions.ApiKey));
else if (!string.IsNullOrWhiteSpace(esOptions.Username))
settings = settings.Authentication(
new Elastic.Transport.BasicAuthentication(esOptions.Username, esOptions.Password ?? ""));
if (esOptions.DisableCertificateValidation)
settings = settings.ServerCertificateValidationCallback((_, _, _, _) => true);
return new ElasticsearchClient(settings);
});
builder.Services.Configure<RabbitMqOptions>(
builder.Configuration.GetSection(RabbitMqOptions.Section));
@@ -258,9 +271,12 @@ try
opts.JsonSerializerOptions.Converters.Add(new BloodTypeJsonConverter());
opts.JsonSerializerOptions.Converters.Add(new AuditActionJsonConverter());
opts.JsonSerializerOptions.Converters.Add(new SepsisBundleComplianceStatusJsonConverter());
opts.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter());
opts.JsonSerializerOptions.Converters.Add(new AlertTypeJsonConverter());
opts.JsonSerializerOptions.Converters.Add(new AlertSeverityJsonConverter());
opts.JsonSerializerOptions.Converters.Add(new AlertStatusJsonConverter());
opts.JsonSerializerOptions.Converters.Add(new ObservationSourceJsonConverter());
opts.JsonSerializerOptions.Converters.Add(new DepartmentJsonConverter());
opts.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter());
});
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen(options =>
@@ -323,7 +339,21 @@ try
var app = builder.Build();
if (!app.Environment.IsEnvironment("Testing"))
// One-shot CLI verbs exit before hosting. Skip demo seeding for them so
// create-admin / register-gateway never race the demo UserSeeder.
var isCliCommand = args.Contains("encrypt-phi")
|| args.Contains("create-admin")
|| args.Contains("register-gateway");
// Demo data — including the seeded demo users with well-known passwords —
// must never be created in production. Seeding:EnableDemoData defaults to
// true so local development and the existing verification scripts are
// unaffected; appsettings.Production.json sets it to false.
var enableDemoData = builder.Configuration.GetValue("Seeding:EnableDemoData", true)
&& !app.Environment.IsEnvironment("Testing")
&& !isCliCommand;
if (enableDemoData)
{
using var scope = app.Services.CreateScope();
var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();
@@ -342,11 +372,14 @@ try
});
}
app.UseSwagger();
app.UseSwaggerUI(options =>
if (builder.Configuration.GetValue("Swagger:Enabled", !app.Environment.IsProduction()))
{
options.SwaggerEndpoint("/swagger/v1/swagger.json", "VigilCare Clinical API v1");
});
app.UseSwagger();
app.UseSwaggerUI(options =>
{
options.SwaggerEndpoint("/swagger/v1/swagger.json", "VigilCare Clinical API v1");
});
}
app.UseMiddleware<CorrelationIdMiddleware>();
app.UseMiddleware<FhirApiKeyOrJwtMiddleware>();
@@ -369,7 +402,7 @@ try
ResponseWriter = HealthCheckResponseWriter.WriteAsync
}).AllowAnonymous();
app.MapMetrics("/metrics");
app.MapMetrics("/metrics").AllowAnonymous();
app.MapControllers();
if (args.Contains("encrypt-phi"))
@@ -378,6 +411,34 @@ try
return;
}
if (args.Contains("create-admin"))
{
try
{
Environment.ExitCode = await CreateAdminCommand.RunAsync(app.Services, args);
}
catch (ArgumentException ex)
{
Console.Error.WriteLine(ex.Message);
Environment.ExitCode = 1;
}
return;
}
if (args.Contains("register-gateway"))
{
try
{
Environment.ExitCode = await RegisterGatewayCommand.RunAsync(app.Services, args);
}
catch (ArgumentException ex)
{
Console.Error.WriteLine(ex.Message);
Environment.ExitCode = 1;
}
return;
}
app.Run();
}
+87 -3
View File
@@ -1,5 +1,6 @@
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Security.Cryptography;
using System.Text;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Options;
@@ -40,10 +41,12 @@ public class AuthService : IAuthService
await _db.SaveChangesAsync();
var expires = DateTimeOffset.UtcNow.AddMinutes(_jwt.ExpirationMinutes);
var token = GenerateToken(user, expires);
var accessToken = GenerateAccessToken(user, expires);
var refreshToken = await CreateRefreshTokenAsync(user.Id);
return new LoginResponse(
token,
accessToken,
refreshToken.Token,
expires,
user.Id,
user.Username,
@@ -51,7 +54,88 @@ public class AuthService : IAuthService
user.Role.ToDbString());
}
private string GenerateToken(ClinicalUser user, DateTimeOffset expires)
public async Task<RefreshResponse> RefreshAsync(string refreshToken)
{
var stored = await _db.RefreshTokens
.Include(t => t.User)
.FirstOrDefaultAsync(t => t.Token == refreshToken);
if (stored is null || stored.RevokedAt is not null || stored.ExpiresAt < DateTimeOffset.UtcNow)
throw new ValidationException("Invalid or expired refresh token.", "INVALID_REFRESH_TOKEN");
if (!stored.User.IsActive)
throw new ValidationException("Account is deactivated.", "ACCOUNT_DEACTIVATED");
stored.RevokedAt = DateTimeOffset.UtcNow;
var newRefreshToken = await CreateRefreshTokenAsync(stored.UserId);
var expires = DateTimeOffset.UtcNow.AddMinutes(_jwt.ExpirationMinutes);
var accessToken = GenerateAccessToken(stored.User, expires);
_db.ClinicalAuditLogs.Add(new ClinicalAuditLog
{
Id = Guid.NewGuid(),
Action = AuditAction.TokenRefreshed,
EntityType = "ClinicalUser",
EntityId = stored.UserId,
UserId = stored.UserId,
UserDisplayName = stored.User.DisplayName,
CreatedAt = DateTimeOffset.UtcNow
});
await _db.SaveChangesAsync();
return new RefreshResponse(accessToken, newRefreshToken.Token, expires);
}
public async Task LogoutAsync(string refreshToken, Guid userId)
{
var stored = await _db.RefreshTokens
.Include(t => t.User)
.FirstOrDefaultAsync(t => t.Token == refreshToken && t.UserId == userId);
if (stored is not null && stored.RevokedAt is null)
{
stored.RevokedAt = DateTimeOffset.UtcNow;
_db.ClinicalAuditLogs.Add(new ClinicalAuditLog
{
Id = Guid.NewGuid(),
Action = AuditAction.UserLogout,
EntityType = "ClinicalUser",
EntityId = userId,
UserId = userId,
UserDisplayName = stored.User.DisplayName,
CreatedAt = DateTimeOffset.UtcNow
});
await _db.SaveChangesAsync();
}
}
private async Task<RefreshToken> CreateRefreshTokenAsync(Guid userId)
{
var token = new RefreshToken
{
Id = Guid.NewGuid(),
Token = GenerateOpaqueToken(),
UserId = userId,
ExpiresAt = DateTimeOffset.UtcNow.AddDays(_jwt.RefreshTokenExpirationDays),
CreatedAt = DateTimeOffset.UtcNow
};
_db.RefreshTokens.Add(token);
await _db.SaveChangesAsync();
return token;
}
private static string GenerateOpaqueToken()
{
var bytes = RandomNumberGenerator.GetBytes(64);
return Convert.ToBase64String(bytes);
}
private string GenerateAccessToken(ClinicalUser user, DateTimeOffset expires)
{
var claims = new[]
{
@@ -1,4 +1,6 @@
public interface IAuthService
{
Task<LoginResponse> LoginAsync(LoginRequest req);
Task<RefreshResponse> RefreshAsync(string refreshToken);
Task LogoutAsync(string refreshToken, Guid userId);
}
@@ -6,6 +6,9 @@
<ImplicitUsings>enable</ImplicitUsings>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<NoWarn>$(NoWarn);1591</NoWarn>
<Copyright>Copyright (c) 2024-2026 voltsrage. All Rights Reserved.</Copyright>
<Authors>voltsrage</Authors>
<PackageLicenseFile>LICENSE</PackageLicenseFile>
</PropertyGroup>
<ItemGroup>
@@ -34,6 +37,7 @@
<PackageReference Include="Serilog.AspNetCore" Version="10.0.0" />
<PackageReference Include="Serilog.Enrichers.Environment" Version="2.3.0" />
<PackageReference Include="Serilog.Enrichers.Thread" Version="3.1.0" />
<PackageReference Include="Serilog.Formatting.Compact" Version="3.0.0" />
<PackageReference Include="Serilog.Sinks.Console" Version="6.1.1" />
<PackageReference Include="Serilog.Sinks.Seq" Version="9.1.0" />
<PackageReference Include="StackExchange.Redis" Version="3.0.0" />
@@ -0,0 +1,43 @@
{
"Serilog": {
"Using": [ "Serilog.Sinks.Console", "Serilog.Sinks.Seq" ],
"MinimumLevel": {
"Default": "Information",
"Override": {
"Microsoft.AspNetCore": "Warning",
"Microsoft.EntityFrameworkCore.Database.Command": "Warning"
}
},
"WriteTo": [
{ "Name": "Console", "Args": { "formatter": "Serilog.Formatting.Compact.CompactJsonFormatter, Serilog.Formatting.Compact" } },
{ "Name": "Seq", "Args": { "serverUrl": "http://localhost:5341" } }
],
"Enrich": [ "FromLogContext", "WithMachineName", "WithThreadId" ],
"Properties": {
"Application": "VigilCareClinicalAPI"
}
},
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning",
"Microsoft.EntityFrameworkCore.Database.Command": "Warning"
}
},
"Kafka": {
"ReplicationFactor": 3,
"NumPartitions": 6,
"SecurityProtocol": "SaslSsl"
},
"Minio": {
"UseSSL": true
},
"RabbitMq": {
"UseSsl": true
},
"PhiEncryption": {
"LogListAccess": true
},
"Swagger": { "Enabled": false },
"Seeding": { "EnableDemoData": false }
}
+12 -2
View File
@@ -48,6 +48,8 @@
"GcsScored": "gcs.scored"
},
"NumPartitions": 6,
"ReplicationFactor": 1,
"SecurityProtocol": "Plaintext",
"OutboxBatchSize": 100,
"OutboxPollIntervalMs": 1000
},
@@ -64,7 +66,8 @@
"Port": 5674,
"Username": "guest",
"Password": "guest",
"PagingAckTimeoutMs": 300000
"PagingAckTimeoutMs": 300000,
"UseSsl": false
},
"Minio": {
"Endpoint": "localhost:9005",
@@ -195,7 +198,8 @@
"Issuer": "VigilCareClinical",
"Audience": "VigilCareClinical.Dashboard",
"SigningKey": "DEV-ONLY-REPLACE-WITH-256-BIT-SECRET-IN-PRODUCTION-abc123xyz",
"ExpirationMinutes": 480
"ExpirationMinutes": 15,
"RefreshTokenExpirationDays": 7
},
"PhiEncryption": {
"ProtectorPurpose": "VigilCare.PatientPhi.v1",
@@ -218,5 +222,11 @@
"AlertQuality": {
"IntervalMinutes": 60,
"WindowHours": 1
},
"Swagger": {
"Enabled": true
},
"Seeding": {
"EnableDemoData": true
}
}
+126
View File
@@ -0,0 +1,126 @@
# Production overlay. Runs ONLY the three application containers — Postgres,
# Redis, Seq, Kafka, Elasticsearch, RabbitMQ, MinIO, Prometheus and Grafana are
# pre-existing external services referenced through .env.
#
# docker compose -f docker-compose.prod.yml --env-file .env up -d
#
# Does not extend docker-compose.yml. Local-dev infra stays in that file;
# this one ships only the three deployable apps.
name: vigilcare
services:
api:
image: ${REGISTRY}/clinical-api:${IMAGE_TAG}
container_name: vigilcare_api
restart: unless-stopped
ports:
- "${API_PORT:-5270}:8080"
environment:
ASPNETCORE_ENVIRONMENT: Production
ConnectionStrings__DefaultConnection: "${PG_CONNECTION}"
Redis__ConnectionString: "${REDIS_CONNECTION}"
Seq__ServerUrl: "${SEQ_URL}"
Serilog__WriteTo__1__Args__serverUrl: "${SEQ_URL}"
Serilog__WriteTo__1__Args__apiKey: "${SEQ_API_KEY}"
Kafka__BootstrapServers: "${KAFKA_BOOTSTRAP}"
Kafka__ReplicationFactor: "${KAFKA_REPLICATION_FACTOR:-3}"
Kafka__SecurityProtocol: "${KAFKA_SECURITY_PROTOCOL:-Plaintext}"
Kafka__SaslMechanism: "${KAFKA_SASL_MECHANISM:-}"
Kafka__SaslUsername: "${KAFKA_SASL_USERNAME:-}"
Kafka__SaslPassword: "${KAFKA_SASL_PASSWORD:-}"
Elasticsearch__Uri: "${ES_URI}"
Elasticsearch__Username: "${ES_USERNAME:-}"
Elasticsearch__Password: "${ES_PASSWORD:-}"
Elasticsearch__ApiKey: "${ES_API_KEY:-}"
RabbitMq__Host: "${RABBITMQ_HOST}"
RabbitMq__Port: "${RABBITMQ_PORT:-5672}"
RabbitMq__Username: "${RABBITMQ_USERNAME}"
RabbitMq__Password: "${RABBITMQ_PASSWORD}"
RabbitMq__UseSsl: "${RABBITMQ_USE_SSL:-true}"
Minio__Endpoint: "${MINIO_ENDPOINT}"
Minio__AccessKey: "${MINIO_ACCESS_KEY}"
Minio__SecretKey: "${MINIO_SECRET_KEY}"
Minio__UseSSL: "${MINIO_USE_SSL:-true}"
Jwt__SigningKey: "${JWT_SIGNING_KEY}"
Jwt__Issuer: "${JWT_ISSUER:-VigilCareClinical}"
Jwt__Audience: "${JWT_AUDIENCE:-VigilCareClinical.Dashboard}"
PhiEncryption__SearchTokenKey: "${PHI_SEARCH_TOKEN_KEY}"
DataProtection__KeyPath: "/app/data-protection-keys"
ApiKey__Gateway: "${GATEWAY_API_KEY}"
Fhir__ApiKey: "${FHIR_API_KEY}"
Dashboard__CorsOrigins__0: "${DASHBOARD_ORIGIN}"
Seeding__EnableDemoData: "false"
Swagger__Enabled: "false"
volumes:
# CRITICAL: the Data Protection keyring encrypts patient PHI. If this
# volume is lost, every encrypted patient record becomes unreadable.
# With `name: vigilcare` above, Docker creates vigilcare_dp_keys.
# Backed up by scripts/backup-dp-keys.sh — see Step 10.
- dp_keys:/app/data-protection-keys
networks:
- vigilcare_prod
- monitoring
logging:
driver: json-file
options: { max-size: "50m", max-file: "5" }
deploy:
resources:
limits: { memory: 2G }
gateway:
image: ${REGISTRY}/ward-gateway:${IMAGE_TAG}
container_name: vigilcare_gateway
restart: unless-stopped
ports:
- "${GATEWAY_PORT:-5081}:8080"
environment:
ASPNETCORE_ENVIRONMENT: Production
ConnectionStrings__GatewayDb: "${GATEWAY_PG_CONNECTION}"
Redis__ConnectionString: "${GATEWAY_REDIS_CONNECTION}"
RabbitMq__Host: "${GATEWAY_RABBITMQ_HOST}"
RabbitMq__Port: "${GATEWAY_RABBITMQ_PORT:-5672}"
RabbitMq__Username: "${GATEWAY_RABBITMQ_USERNAME}"
RabbitMq__Password: "${GATEWAY_RABBITMQ_PASSWORD}"
RabbitMq__UseSsl: "${GATEWAY_RABBITMQ_USE_SSL:-true}"
CentralApi__BaseUrl: "http://api:8080"
Gateway__GatewayId: "${GATEWAY_ID}"
Gateway__SiteId: "${GATEWAY_SITE_ID}"
Gateway__Department: "${GATEWAY_DEPARTMENT:-ICU}"
ApiKey__Gateway: "${GATEWAY_API_KEY}"
Jwt__SigningKey: "${GATEWAY_JWT_SIGNING_KEY}"
Jwt__Issuer: "${GATEWAY_JWT_ISSUER:-vigilcare-gateway}"
Jwt__Audience: "${GATEWAY_JWT_AUDIENCE:-vigilcare-dashboard}"
depends_on:
api:
condition: service_healthy
networks:
- vigilcare_prod
- monitoring
logging:
driver: json-file
options: { max-size: "50m", max-file: "5" }
dashboard:
image: ${REGISTRY}/dashboard:${IMAGE_TAG}
container_name: vigilcare_dashboard
restart: unless-stopped
ports:
- "${DASHBOARD_PORT:-8080}:80"
networks:
- vigilcare_prod
- monitoring
logging:
driver: json-file
options: { max-size: "20m", max-file: "3" }
volumes:
dp_keys:
# Named volume backed by a host bind is preferable if the host has a
# backed-up filesystem path; see Step 10. Full Docker name: vigilcare_dp_keys.
networks:
vigilcare_prod:
driver: bridge
monitoring:
external: true
+276
View File
@@ -0,0 +1,276 @@
subject_id,hadm_id,admittime,dischtime,deathtime,admission_type,admit_provider_id,admission_location,discharge_location,insurance,language,marital_status,race,edregtime,edouttime,hospital_expire_flag
10004235,24181354,2196-02-24 14:38:00,2196-03-04 14:02:00,,URGENT,P03YMR,TRANSFER FROM HOSPITAL,SKILLED NURSING FACILITY,Medicaid,ENGLISH,SINGLE,BLACK/CAPE VERDEAN,2196-02-24 12:15:00,2196-02-24 17:07:00,0
10009628,25926192,2153-09-17 17:08:00,2153-09-25 13:20:00,,URGENT,P41R5N,TRANSFER FROM HOSPITAL,HOME HEALTH CARE,Medicaid,?,MARRIED,HISPANIC/LATINO - PUERTO RICAN,,,0
10018081,23983182,2134-08-18 02:02:00,2134-08-23 19:35:00,,URGENT,P233F6,TRANSFER FROM HOSPITAL,SKILLED NURSING FACILITY,Medicare,ENGLISH,MARRIED,WHITE,2134-08-17 16:24:00,2134-08-18 03:15:00,0
10006053,22942076,2111-11-13 23:39:00,2111-11-15 17:20:00,2111-11-15 17:20:00,URGENT,P38TI6,TRANSFER FROM HOSPITAL,DIED,Medicaid,ENGLISH,,UNKNOWN,,,1
10031404,21606243,2113-08-04 18:46:00,2113-08-06 20:57:00,,URGENT,P07HDB,TRANSFER FROM HOSPITAL,HOME,Other,ENGLISH,WIDOWED,WHITE,,,0
10005817,20626031,2132-12-12 01:43:00,2132-12-20 15:04:00,,URGENT,P41R5N,TRANSFER FROM HOSPITAL,HOME HEALTH CARE,Medicare,ENGLISH,MARRIED,WHITE,,,0
10019385,20297618,2180-02-15 20:28:00,2180-02-25 13:45:00,,URGENT,P536JC,TRANSFER FROM HOSPITAL,HOME HEALTH CARE,Other,ENGLISH,MARRIED,WHITE,,,0
10002495,24982426,2141-05-22 20:17:00,2141-05-29 17:41:00,,URGENT,P79SJ2,TRANSFER FROM HOSPITAL,SKILLED NURSING FACILITY,Medicare,ENGLISH,MARRIED,UNKNOWN,,,0
10038081,20755971,2115-09-27 20:40:00,2115-10-12 00:00:00,2115-10-12 22:20:00,URGENT,P48GIG,TRANSFER FROM HOSPITAL,DIED,Other,?,SINGLE,UNKNOWN,,,1
10019917,22585261,2182-01-07 23:25:00,2182-01-10 16:52:00,,URGENT,P3529J,TRANSFER FROM HOSPITAL,HOME,Other,ENGLISH,SINGLE,OTHER,,,0
10037861,24256866,2115-10-09 20:28:00,2115-10-18 16:50:00,,URGENT,P64TGC,TRANSFER FROM HOSPITAL,HOME HEALTH CARE,Medicare,ENGLISH,MARRIED,UNKNOWN,,,0
10026354,24547356,2119-10-26 07:11:00,2119-11-06 12:30:00,,URGENT,P77BSD,TRANSFER FROM HOSPITAL,SKILLED NURSING FACILITY,Other,?,,WHITE,2119-10-26 06:00:00,2119-10-26 06:37:00,0
10015860,26352758,2192-09-24 09:19:00,2192-09-28 19:52:00,,URGENT,P54RLA,TRANSFER FROM SKILLED NURSING FACILITY,SKILLED NURSING FACILITY,Other,ENGLISH,SINGLE,WHITE,2192-09-23 22:43:00,2192-09-24 09:38:00,0
10020740,23831430,2150-03-11 15:34:00,2150-04-25 13:50:00,,URGENT,P7554I,TRANSFER FROM HOSPITAL,SKILLED NURSING FACILITY,Other,ENGLISH,SINGLE,WHITE,,,0
10012552,27089790,2140-03-22 17:18:00,2140-03-30 14:10:00,,URGENT,P47E1G,TRANSFER FROM HOSPITAL,HOME HEALTH CARE,Medicare,ENGLISH,MARRIED,UNKNOWN,,,0
10014354,28335091,2147-04-26 16:44:00,2147-04-29 15:30:00,,URGENT,P79SJ2,TRANSFER FROM HOSPITAL,HOME HEALTH CARE,Other,ENGLISH,SINGLE,WHITE,,,0
10014354,27487226,2148-06-30 01:09:00,2148-07-13 19:35:00,,URGENT,P44WVR,TRANSFER FROM HOSPITAL,HOME HEALTH CARE,Other,ENGLISH,SINGLE,WHITE,2148-06-29 21:06:00,2148-06-30 02:27:00,0
10039708,25864431,2142-03-26 06:08:00,2142-04-11 21:00:00,,URGENT,P81KFM,TRANSFER FROM HOSPITAL,HOME HEALTH CARE,Other,ENGLISH,SINGLE,BLACK/AFRICAN AMERICAN,2142-03-25 22:22:00,2142-03-26 08:08:00,0
10000032,22595853,2180-05-06 22:23:00,2180-05-07 17:15:00,,URGENT,P874LG,TRANSFER FROM HOSPITAL,HOME,Other,ENGLISH,WIDOWED,WHITE,2180-05-06 19:17:00,2180-05-06 23:30:00,0
10020786,23488445,2189-06-09 12:45:00,2189-06-13 17:20:00,,URGENT,P031HZ,TRANSFER FROM HOSPITAL,HOME,Medicare,ENGLISH,WIDOWED,WHITE,,,0
10027445,27488741,2145-12-08 19:47:00,2145-12-19 14:36:00,,URGENT,P38RSS,TRANSFER FROM HOSPITAL,HOME HEALTH CARE,Other,ENGLISH,WIDOWED,WHITE,,,0
10027445,29163082,2142-08-27 21:05:00,2142-09-05 17:33:00,,URGENT,P509SB,TRANSFER FROM HOSPITAL,HOME HEALTH CARE,Other,ENGLISH,WIDOWED,WHITE,,,0
10015931,22130791,2177-03-24 21:47:00,2177-03-29 14:15:00,2177-03-29 14:15:00,URGENT,P031HZ,TRANSFER FROM HOSPITAL,DIED,Medicare,ENGLISH,MARRIED,WHITE,,,1
10036156,28019404,2157-07-01 04:52:00,2157-07-03 15:08:00,,URGENT,P94V16,TRANSFER FROM HOSPITAL,HOME,Other,ENGLISH,,UNKNOWN,,,0
10021312,25020332,2113-08-16 00:32:00,2113-08-18 17:35:00,,URGENT,P31A9M,TRANSFER FROM HOSPITAL,HOME,Other,ENGLISH,,UNKNOWN,,,0
10021312,28829452,2113-09-12 14:42:00,2113-09-20 18:40:00,,URGENT,P72G4H,TRANSFER FROM HOSPITAL,ACUTE HOSPITAL,Other,ENGLISH,,UNKNOWN,,,0
10003400,23559586,2137-08-04 00:07:00,2137-09-02 17:05:00,2137-09-02 17:05:00,URGENT,P99U21,TRANSFER FROM HOSPITAL,DIED,Medicare,ENGLISH,MARRIED,BLACK/AFRICAN AMERICAN,,,1
10003400,20214994,2137-02-24 10:00:00,2137-03-19 15:45:00,,URGENT,P60ZCO,TRANSFER FROM SKILLED NURSING FACILITY,CHRONIC/LONG TERM ACUTE CARE,Medicare,ENGLISH,MARRIED,BLACK/AFRICAN AMERICAN,,,0
10023117,24244087,2174-06-07 23:25:00,2174-06-12 15:55:00,,URGENT,P878WT,TRANSFER FROM HOSPITAL,HOME,Medicare,ENGLISH,WIDOWED,WHITE,2174-06-07 16:24:00,2174-06-08 01:02:00,0
10038992,22797747,2185-11-02 18:26:00,2185-11-08 16:22:00,,URGENT,P534S8,TRANSFER FROM HOSPITAL,HOME HEALTH CARE,Medicare,ENGLISH,MARRIED,UNKNOWN,,,0
10037975,27617929,2185-01-17 19:11:00,2185-01-22 14:25:00,2185-01-22 14:25:00,URGENT,P17MK7,TRANSFER FROM HOSPITAL,DIED,Medicare,ENGLISH,MARRIED,UNKNOWN,,,1
10035185,22580999,2120-05-12 12:53:00,2120-05-17 16:00:00,,URGENT,P41R5N,TRANSFER FROM HOSPITAL,HOME HEALTH CARE,Other,ENGLISH,MARRIED,WHITE,,,0
10002930,28301173,2197-04-08 19:37:00,2197-04-15 12:01:00,,URGENT,P3763P,INTERNAL TRANSFER TO OR FROM PSYCH,HOME,Medicare,ENGLISH,SINGLE,BLACK/AFRICAN AMERICAN,,,0
10002930,22733922,2198-04-22 16:17:00,2198-05-04 13:20:00,,URGENT,P3763P,INTERNAL TRANSFER TO OR FROM PSYCH,HOME,Medicare,ENGLISH,SINGLE,BLACK/AFRICAN AMERICAN,,,0
10007795,28477357,2136-04-10 20:33:00,2136-05-02 16:35:00,,URGENT,P172D4,TRANSFER FROM HOSPITAL,CHRONIC/LONG TERM ACUTE CARE,Medicare,ENGLISH,SINGLE,WHITE,,,0
10007795,25135483,2136-05-04 20:20:00,2136-05-12 17:12:00,,URGENT,P99LA7,TRANSFER FROM HOSPITAL,REHAB,Medicare,ENGLISH,SINGLE,WHITE,,,0
10004733,27411876,2174-12-04 11:28:00,2174-12-27 14:00:00,,URGENT,P48KFD,TRANSFER FROM HOSPITAL,HOME HEALTH CARE,Medicaid,ENGLISH,SINGLE,UNKNOWN,,,0
10021118,24490144,2161-11-15 20:10:00,2161-11-23 16:00:00,,URGENT,P96FGV,TRANSFER FROM HOSPITAL,HOME HEALTH CARE,Other,ENGLISH,MARRIED,WHITE,,,0
10006580,24159665,2137-08-10 11:00:00,2137-08-15 13:25:00,,ELECTIVE,P76K54,PHYSICIAN REFERRAL,HOME,Medicaid,?,MARRIED,HISPANIC/LATINO - SALVADORAN,,,0
10023771,20044587,2113-08-25 07:15:00,2113-08-30 14:15:00,,ELECTIVE,P47E1G,PHYSICIAN REFERRAL,SKILLED NURSING FACILITY,Medicare,ENGLISH,MARRIED,WHITE,,,0
10005348,29496232,2128-09-05 08:30:00,2128-09-12 16:55:00,,ELECTIVE,P786Y5,PHYSICIAN REFERRAL,HOME HEALTH CARE,Other,ENGLISH,MARRIED,WHITE,,,0
10019172,24997044,2118-11-15 14:00:00,2118-11-21 18:31:00,,ELECTIVE,P41R5N,PHYSICIAN REFERRAL,HOME HEALTH CARE,Other,ENGLISH,,UNABLE TO OBTAIN,,,0
10019172,21540783,2118-10-08 14:00:00,2118-10-11 20:15:00,,ELECTIVE,P41R5N,PHYSICIAN REFERRAL,HOME,Other,ENGLISH,,UNABLE TO OBTAIN,,,0
10021487,20429160,2117-07-16 07:15:00,2117-07-25 12:34:00,,ELECTIVE,P96Y5O,PHYSICIAN REFERRAL,HOME,Other,ENGLISH,DIVORCED,WHITE,,,0
10013049,22675517,2114-06-20 10:15:00,2114-06-24 16:45:00,,ELECTIVE,P41R5N,PHYSICIAN REFERRAL,HOME HEALTH CARE,Other,ENGLISH,MARRIED,WHITE,,,0
10035631,20385771,2112-12-04 00:00:00,2112-12-27 16:24:00,,ELECTIVE,P45GUA,PHYSICIAN REFERRAL,HOME HEALTH CARE,Other,ENGLISH,MARRIED,WHITE,,,0
10035631,24912093,2112-10-22 00:00:00,2112-10-28 12:16:00,,ELECTIVE,P45GUA,PHYSICIAN REFERRAL,HOME HEALTH CARE,Other,ENGLISH,MARRIED,UNKNOWN,,,0
10019003,26226543,2155-10-17 18:01:00,2155-11-03 18:00:00,,ELECTIVE,P7280F,PHYSICIAN REFERRAL,HOME HEALTH CARE,Other,ENGLISH,WIDOWED,WHITE,,,0
10019003,28003918,2148-12-21 07:15:00,2148-12-24 17:10:00,,ELECTIVE,P80J89,PHYSICIAN REFERRAL,HOME,Medicare,ENGLISH,WIDOWED,WHITE,,,0
10009035,28324362,2161-04-27 07:15:00,2161-05-01 13:45:00,,ELECTIVE,P41R5N,PHYSICIAN REFERRAL,HOME HEALTH CARE,Other,ENGLISH,SINGLE,WHITE,,,0
10040025,25933959,2147-12-29 19:36:00,2148-01-09 17:38:00,,ELECTIVE,P43BTJ,PHYSICIAN REFERRAL,SKILLED NURSING FACILITY,Other,ENGLISH,DIVORCED,WHITE,,,0
10014729,23300884,2125-03-19 16:58:00,2125-03-28 13:37:00,,EW EMER.,P76K54,EMERGENCY ROOM,HOME HEALTH CARE,Other,ENGLISH,SINGLE,WHITE - OTHER EUROPEAN,2125-03-19 12:36:00,2125-03-19 18:45:00,0
10026255,20437651,2200-09-17 22:53:00,2200-09-29 18:25:00,,EW EMER.,P233F6,EMERGENCY ROOM,HOME,Other,ENGLISH,MARRIED,WHITE,2200-09-17 18:38:00,2200-09-18 00:57:00,0
10026255,22059910,2201-07-07 18:15:00,2201-07-13 23:27:00,2201-07-13 23:27:00,EW EMER.,P48KFD,EMERGENCY ROOM,DIED,Other,ENGLISH,MARRIED,WHITE,2201-07-07 12:31:00,2201-07-07 19:40:00,1
10007058,22954658,2167-11-07 19:05:00,2167-11-11 14:23:00,,EW EMER.,P76K54,EMERGENCY ROOM,HOME,Other,ENGLISH,MARRIED,WHITE,2167-11-07 17:57:00,2167-11-07 20:22:00,0
10018845,21101111,2184-10-08 02:28:00,2184-10-11 17:00:00,,EW EMER.,P34SFE,EMERGENCY ROOM,REHAB,Other,ENGLISH,MARRIED,WHITE,2184-10-07 22:35:00,2184-10-08 04:09:00,0
10020640,27984218,2153-02-13 00:22:00,2153-02-20 13:52:00,,EW EMER.,P7554I,EMERGENCY ROOM,SKILLED NURSING FACILITY,Other,ENGLISH,WIDOWED,WHITE,2153-02-12 21:59:00,2153-02-13 01:38:00,0
10018081,25973915,2134-09-06 15:57:00,2134-09-18 15:50:00,,EW EMER.,P13ZRJ,EMERGENCY ROOM,CHRONIC/LONG TERM ACUTE CARE,Medicare,ENGLISH,MARRIED,WHITE,2134-09-05 16:14:00,2134-09-06 16:49:00,0
10018081,21027282,2133-12-18 16:58:00,2134-01-12 11:00:00,,EW EMER.,P19SWB,EMERGENCY ROOM,SKILLED NURSING FACILITY,Medicare,ENGLISH,MARRIED,WHITE,2133-12-18 12:09:00,2133-12-18 17:10:00,0
10021666,22756440,2172-03-12 23:47:00,2172-03-23 15:40:00,,EW EMER.,P99698,EMERGENCY ROOM,CHRONIC/LONG TERM ACUTE CARE,Medicare,ENGLISH,MARRIED,WHITE,2172-03-12 20:52:00,2172-03-13 01:46:00,0
10015272,27993466,2137-06-12 18:36:00,2137-06-18 15:45:00,,EW EMER.,P3529J,EMERGENCY ROOM,HOME HEALTH CARE,Medicare,ENGLISH,,WHITE,2137-06-12 16:54:00,2137-06-12 20:37:00,0
10016810,20973395,2185-06-16 01:31:00,2185-06-21 15:55:00,,EW EMER.,P3638G,EMERGENCY ROOM,HOME,Other,ENGLISH,,UNKNOWN,2185-06-15 23:08:00,2185-06-16 02:16:00,0
10021938,23112364,2181-10-13 01:48:00,2181-10-14 17:40:00,,EW EMER.,P3529J,EMERGENCY ROOM,HOME,Medicare,ENGLISH,SINGLE,WHITE,2181-10-12 20:17:00,2181-10-13 02:52:00,0
10021938,27154822,2181-10-25 10:44:00,2181-10-27 15:30:00,,EW EMER.,P99BGS,EMERGENCY ROOM,HOME,Medicare,ENGLISH,SINGLE,WHITE,2181-10-25 09:23:00,2181-10-25 11:35:00,0
10001725,25563031,2110-04-11 15:08:00,2110-04-14 15:00:00,,EW EMER.,P35SU0,PACU,HOME,Other,ENGLISH,MARRIED,WHITE,,,0
10001217,24597018,2157-11-18 22:56:00,2157-11-25 18:00:00,,EW EMER.,P4645A,EMERGENCY ROOM,HOME HEALTH CARE,Other,?,MARRIED,WHITE,2157-11-18 17:38:00,2157-11-19 01:24:00,0
10023239,29295881,2137-06-19 17:35:00,2137-06-22 14:57:00,,EW EMER.,P44WVR,EMERGENCY ROOM,HOME,Other,ENGLISH,SINGLE,WHITE,2137-06-19 15:05:00,2137-06-19 19:09:00,0
10023239,21759936,2140-10-03 09:06:00,2140-10-08 15:28:00,,EW EMER.,P031HZ,TRANSFER FROM HOSPITAL,HOME,Other,ENGLISH,MARRIED,WHITE,2140-10-03 06:20:00,2140-10-03 11:04:00,0
10020944,29974575,2131-02-27 15:34:00,2131-03-13 17:01:00,,EW EMER.,P27588,EMERGENCY ROOM,SKILLED NURSING FACILITY,Medicare,ENGLISH,,UNKNOWN,2131-02-27 13:16:00,2131-02-27 16:40:00,0
10027602,28166872,2201-10-30 12:05:00,2201-11-20 14:45:00,,EW EMER.,P47SIK,EMERGENCY ROOM,REHAB,Other,ENGLISH,SINGLE,WHITE,2201-10-30 10:48:00,2201-10-30 12:25:00,0
10032725,25177949,2143-02-17 14:20:00,2143-03-16 17:15:00,,EW EMER.,P39043,EMERGENCY ROOM,CHRONIC/LONG TERM ACUTE CARE,Other,ENGLISH,SINGLE,BLACK/AFRICAN AMERICAN,2143-02-17 07:19:00,2143-02-17 15:18:00,0
10032725,20611640,2143-03-22 04:59:00,2143-03-25 13:00:00,,EW EMER.,P7554I,EMERGENCY ROOM,HOSPICE,Other,ENGLISH,SINGLE,BLACK/AFRICAN AMERICAN,2143-03-22 01:19:00,2143-03-22 06:42:00,0
10005348,25239799,2130-10-26 17:03:00,2130-11-02 16:00:00,,EW EMER.,P38RSS,PROCEDURE SITE,SKILLED NURSING FACILITY,Other,ENGLISH,MARRIED,WHITE,,,0
10037861,24540843,2117-03-14 16:34:00,2117-03-24 00:01:00,2117-03-24 23:08:00,EW EMER.,P01LRQ,EMERGENCY ROOM,DIED,Medicare,ENGLISH,MARRIED,UNKNOWN,2117-03-14 15:19:00,2117-03-14 17:53:00,1
10010471,29842315,2155-12-02 19:36:00,2155-12-07 15:30:00,2155-12-07 15:30:00,EW EMER.,P04X8Y,EMERGENCY ROOM,DIED,Medicare,ENGLISH,WIDOWED,WHITE,2155-12-02 16:00:00,2155-12-02 20:33:00,1
10005866,27167814,2148-03-10 16:16:00,2148-03-21 18:30:00,,EW EMER.,P28OI9,EMERGENCY ROOM,SKILLED NURSING FACILITY,Medicaid,ENGLISH,SINGLE,PORTUGUESE,2148-03-10 04:46:00,2148-03-10 11:27:00,0
10005866,26134779,2149-09-13 07:36:00,2149-09-19 18:00:00,,EW EMER.,P95QHH,EMERGENCY ROOM,HOME,Medicaid,ENGLISH,SINGLE,PORTUGUESE,2149-09-12 15:31:00,2149-09-13 09:02:00,0
10005866,26158160,2146-06-06 00:50:00,2146-06-09 16:45:00,,EW EMER.,P94RT6,EMERGENCY ROOM,HOME,Medicaid,ENGLISH,SINGLE,PORTUGUESE,2146-06-05 22:26:00,2146-06-06 01:45:00,0
10024043,24717014,2117-04-11 20:46:00,2117-04-16 18:55:00,,EW EMER.,P5487F,EMERGENCY ROOM,HOME HEALTH CARE,Medicare,ENGLISH,SINGLE,BLACK/AFRICAN AMERICAN,2117-04-11 17:23:00,2117-04-11 22:05:00,0
10021487,28998349,2116-12-03 00:23:00,2116-12-28 13:19:00,,EW EMER.,P61T4I,EMERGENCY ROOM,HOME,Other,ENGLISH,MARRIED,WHITE,2116-12-02 22:57:00,2116-12-03 01:02:00,0
10021487,21928381,2117-12-03 17:07:00,2117-12-06 17:30:00,,EW EMER.,P96Y5O,PROCEDURE SITE,HOME,Other,ENGLISH,DIVORCED,WHITE,,,0
10021487,26321862,2117-01-28 00:19:00,2117-02-05 15:40:00,,EW EMER.,P82B0E,EMERGENCY ROOM,HOME,Other,ENGLISH,MARRIED,WHITE,2117-01-27 18:19:00,2117-01-28 01:23:00,0
10021487,27112038,2117-10-25 22:22:00,2117-10-29 14:40:00,,EW EMER.,P233F6,EMERGENCY ROOM,HOME,Other,ENGLISH,DIVORCED,WHITE,2117-10-25 15:49:00,2117-10-25 22:53:00,0
10015860,24698912,2192-05-12 07:42:00,2192-05-27 18:50:00,,EW EMER.,P03YMR,PHYSICIAN REFERRAL,SKILLED NURSING FACILITY,Other,ENGLISH,SINGLE,WHITE,2192-05-11 16:26:00,2192-05-12 09:31:00,0
10015860,28236161,2187-09-15 18:49:00,2187-09-19 14:50:00,,EW EMER.,P62A9I,EMERGENCY ROOM,HOME HEALTH CARE,Other,ENGLISH,SINGLE,WHITE,2187-09-15 14:14:00,2187-09-15 20:49:00,0
10015860,20790339,2189-05-23 02:14:00,2189-05-25 14:45:00,,EW EMER.,P00P3O,EMERGENCY ROOM,HOME HEALTH CARE,Other,ENGLISH,SINGLE,WHITE,2189-05-22 23:18:00,2189-05-23 03:38:00,0
10015860,25085565,2186-09-15 16:12:00,2186-09-29 18:05:00,,EW EMER.,P56SP9,EMERGENCY ROOM,REHAB,Other,ENGLISH,SINGLE,WHITE,2186-09-15 12:56:00,2186-09-15 17:15:00,0
10015860,20854119,2188-08-06 00:49:00,2188-08-12 17:49:00,,EW EMER.,P29CGZ,EMERGENCY ROOM,HOME HEALTH CARE,Other,ENGLISH,SINGLE,WHITE,2188-08-05 20:36:00,2188-08-06 03:24:00,0
10018423,29366372,2167-05-03 21:24:00,2167-05-11 12:57:00,,EW EMER.,P204LI,EMERGENCY ROOM,HOME HEALTH CARE,Other,ENGLISH,MARRIED,WHITE,2167-05-03 15:18:00,2167-05-03 22:50:00,0
10007928,20338077,2129-04-05 22:56:00,2129-04-11 17:25:00,,EW EMER.,P825SO,EMERGENCY ROOM,HOME,Other,ENGLISH,DIVORCED,WHITE,2129-04-05 21:18:00,2129-04-06 00:25:00,0
10014078,25809882,2166-08-21 23:09:00,2166-08-26 14:48:00,,EW EMER.,P64JK6,EMERGENCY ROOM,HOME HEALTH CARE,Medicaid,ENGLISH,,UNABLE TO OBTAIN,2166-08-21 21:39:00,2166-08-22 00:36:00,0
10020740,25826145,2150-06-03 20:12:00,2150-06-07 15:05:00,,EW EMER.,P80QIV,PACU,SKILLED NURSING FACILITY,Other,ENGLISH,SINGLE,WHITE,,,0
10008454,20291550,2110-11-30 06:31:00,2110-12-10 15:53:00,,EW EMER.,P77BSD,EMERGENCY ROOM,HOME HEALTH CARE,Other,ENGLISH,SINGLE,WHITE,2110-11-30 04:45:00,2110-11-30 08:03:00,0
10022281,29642388,2125-06-17 04:11:00,2125-06-19 15:25:00,,EW EMER.,P76K54,EMERGENCY ROOM,HOME,Other,ENGLISH,MARRIED,OTHER,2125-06-16 20:32:00,2125-06-17 05:14:00,0
10014354,24357615,2150-05-09 16:09:00,2150-05-10 15:59:00,,EW EMER.,P15PLY,PHYSICIAN REFERRAL,HOME HEALTH CARE,Other,ENGLISH,SINGLE,WHITE,2150-05-08 22:30:00,2150-05-09 19:09:00,0
10014354,22741225,2146-10-08 23:47:00,2146-10-12 18:20:00,,EW EMER.,P5487F,EMERGENCY ROOM,HOME HEALTH CARE,Other,ENGLISH,MARRIED,WHITE,2146-10-08 21:27:00,2146-10-09 01:08:00,0
10014354,26013492,2147-11-14 22:12:00,2147-11-16 15:00:00,,EW EMER.,P424IO,EMERGENCY ROOM,HOME HEALTH CARE,Other,ENGLISH,SINGLE,WHITE,2147-11-14 18:22:00,2147-11-14 23:09:00,0
10014354,29757856,2150-04-10 02:40:00,2150-04-15 18:00:00,,EW EMER.,P08904,WALK-IN/SELF REFERRAL,HOME HEALTH CARE,Other,ENGLISH,SINGLE,WHITE,2150-04-09 19:50:00,2150-04-10 03:46:00,0
10038933,25129047,2148-09-10 12:09:00,2148-09-23 12:18:00,,EW EMER.,P47KFL,EMERGENCY ROOM,REHAB,Other,ENGLISH,SINGLE,WHITE,2148-09-10 09:23:00,2148-09-10 13:19:00,0
10016150,29374560,2142-05-10 15:05:00,2142-05-15 19:49:00,,EW EMER.,P1037P,PROCEDURE SITE,REHAB,Medicare,ENGLISH,SINGLE,WHITE,,,0
10022041,28909879,2187-05-18 17:08:00,2187-05-23 17:20:00,,EW EMER.,P09IS0,EMERGENCY ROOM,HOME HEALTH CARE,Medicare,ENGLISH,MARRIED,OTHER,2187-05-18 14:59:00,2187-05-18 18:39:00,0
10039708,20572787,2138-10-30 23:30:00,2138-11-06 23:30:00,,EW EMER.,P39SWK,EMERGENCY ROOM,AGAINST ADVICE,Other,ENGLISH,SINGLE,BLACK/AFRICAN AMERICAN,2138-10-30 17:27:00,2138-10-31 00:19:00,0
10039708,28258130,2140-01-23 16:19:00,2140-02-26 18:15:00,,EW EMER.,P44WVR,EMERGENCY ROOM,CHRONIC/LONG TERM ACUTE CARE,Other,ENGLISH,SINGLE,BLACK/AFRICAN AMERICAN,2140-01-23 12:35:00,2140-01-23 18:08:00,0
10000032,22841357,2180-06-26 18:27:00,2180-06-27 18:49:00,,EW EMER.,P09Q6Y,EMERGENCY ROOM,HOME,Medicaid,ENGLISH,WIDOWED,WHITE,2180-06-26 15:54:00,2180-06-26 21:31:00,0
10000032,25742920,2180-08-05 23:44:00,2180-08-07 17:50:00,,EW EMER.,P60CC5,EMERGENCY ROOM,HOSPICE,Medicaid,ENGLISH,WIDOWED,WHITE,2180-08-05 20:58:00,2180-08-06 01:44:00,0
10000032,29079034,2180-07-23 12:35:00,2180-07-25 17:55:00,,EW EMER.,P30KEH,EMERGENCY ROOM,HOME,Medicaid,ENGLISH,WIDOWED,WHITE,2180-07-23 05:54:00,2180-07-23 14:00:00,0
10026406,25260176,2129-01-03 15:55:00,2129-01-05 14:10:00,,EW EMER.,P51MA2,EMERGENCY ROOM,AGAINST ADVICE,Other,ENGLISH,DIVORCED,WHITE,2129-01-02 23:41:00,2129-01-03 18:33:00,0
10037928,22326517,2177-12-21 20:47:00,2177-12-23 17:45:00,,EW EMER.,P51MA2,EMERGENCY ROOM,HOME HEALTH CARE,Medicare,?,WIDOWED,HISPANIC/LATINO - CUBAN,2177-12-21 16:49:00,2177-12-21 22:26:00,0
10037928,22490490,2177-07-14 16:55:00,2177-07-24 13:33:00,,EW EMER.,P44WVR,EMERGENCY ROOM,HOME HEALTH CARE,Medicare,?,WIDOWED,HISPANIC/LATINO - CUBAN,2177-07-14 14:52:00,2177-07-14 20:38:00,0
10037928,20192635,2177-09-04 12:05:00,2177-09-07 16:10:00,,EW EMER.,P072C5,EMERGENCY ROOM,HOME HEALTH CARE,Medicare,?,WIDOWED,HISPANIC/LATINO - CUBAN,2177-09-04 06:29:00,2177-09-04 14:44:00,0
10037928,29802992,2179-07-25 00:06:00,2179-07-28 15:54:00,,EW EMER.,P450WM,EMERGENCY ROOM,HOME HEALTH CARE,Medicare,?,WIDOWED,HISPANIC/LATINO - CUBAN,2179-07-24 18:21:00,2179-07-25 01:17:00,0
10037928,23721604,2179-03-27 18:27:00,2179-04-04 19:40:00,,EW EMER.,P80GYA,EMERGENCY ROOM,HOME HEALTH CARE,Medicare,?,WIDOWED,HISPANIC/LATINO - CUBAN,2179-03-27 14:15:00,2179-03-27 19:48:00,0
10037928,24225421,2178-09-28 23:05:00,2178-10-02 17:13:00,,EW EMER.,P30AMB,EMERGENCY ROOM,HOME,Medicare,?,WIDOWED,HISPANIC/LATINO - CUBAN,2178-09-28 20:29:00,2178-09-29 00:43:00,0
10037928,24656677,2178-12-21 05:30:00,2178-12-26 18:35:00,,EW EMER.,P30SZF,EMERGENCY ROOM,HOME HEALTH CARE,Medicare,?,WIDOWED,HISPANIC/LATINO - CUBAN,2178-12-21 03:17:00,2178-12-21 08:27:00,0
10029484,20764029,2160-11-08 04:16:00,2160-11-11 11:40:00,,EW EMER.,P38TI6,EMERGENCY ROOM,HOME,Other,ENGLISH,SINGLE,WHITE,2160-11-07 19:15:00,2160-11-08 05:23:00,0
10027445,26275841,2142-07-31 00:32:00,2142-08-09 17:30:00,,EW EMER.,P3529J,EMERGENCY ROOM,HOME HEALTH CARE,Other,ENGLISH,WIDOWED,WHITE,2142-07-30 23:33:00,2142-07-31 01:41:00,0
10012853,22539296,2176-06-06 18:09:00,2176-06-08 18:30:00,,EW EMER.,P172D4,EMERGENCY ROOM,HOME,Medicare,ENGLISH,WIDOWED,BLACK/AFRICAN AMERICAN,2176-06-06 13:33:00,2176-06-06 20:18:00,0
10012853,26369609,2175-04-05 15:36:00,2175-04-10 16:55:00,,EW EMER.,P623U2,EMERGENCY ROOM,HOME,Other,ENGLISH,WIDOWED,BLACK/AFRICAN AMERICAN,2175-04-05 06:22:00,2175-04-05 17:10:00,0
10012853,27882036,2176-11-25 21:28:00,2176-12-03 15:24:00,,EW EMER.,P450WM,EMERGENCY ROOM,SKILLED NURSING FACILITY,Medicare,ENGLISH,WIDOWED,BLACK/AFRICAN AMERICAN,2176-11-25 18:34:00,2176-11-25 23:51:00,0
10019003,29279905,2153-03-27 23:25:00,2153-04-07 16:20:00,,EW EMER.,P60F78,EMERGENCY ROOM,HOME HEALTH CARE,Other,ENGLISH,WIDOWED,WHITE,2153-03-27 21:28:00,2153-03-28 02:21:00,0
10019003,21457723,2155-07-10 17:48:00,2155-07-18 16:59:00,,EW EMER.,P44WVR,TRANSFER FROM HOSPITAL,HOME,Other,ENGLISH,WIDOWED,WHITE,2155-07-10 12:46:00,2155-07-10 19:03:00,0
10019003,27525946,2153-04-12 19:07:00,2153-04-20 17:09:00,,EW EMER.,P56B4N,EMERGENCY ROOM,HOME,Other,ENGLISH,WIDOWED,WHITE,2153-04-12 13:03:00,2153-04-12 21:40:00,0
10019003,26703331,2155-06-10 23:09:00,2155-06-15 16:30:00,,EW EMER.,P203BW,TRANSFER FROM HOSPITAL,HOME HEALTH CARE,Other,ENGLISH,WIDOWED,WHITE,2155-06-10 20:37:00,2155-06-11 01:53:00,0
10019777,27738145,2187-02-10 18:57:00,2187-02-27 13:22:00,,EW EMER.,P08BV8,EMERGENCY ROOM,HOSPICE,Other,ENGLISH,DIVORCED,WHITE,2187-02-10 15:11:00,2187-02-10 20:34:00,0
10018501,28479513,2141-07-30 22:34:00,2141-08-05 18:06:00,,EW EMER.,P99698,EMERGENCY ROOM,SKILLED NURSING FACILITY,Medicare,ENGLISH,,WHITE,2141-07-30 18:53:00,2141-07-31 00:01:00,0
10004422,21255400,2111-01-15 14:55:00,2111-01-25 15:00:00,,EW EMER.,P1037P,PROCEDURE SITE,HOME HEALTH CARE,Medicare,ENGLISH,WIDOWED,WHITE,,,0
10003400,27296885,2136-12-31 21:40:00,2137-01-03 17:05:00,,EW EMER.,P14622,EMERGENCY ROOM,SKILLED NURSING FACILITY,Medicare,ENGLISH,MARRIED,BLACK/AFRICAN AMERICAN,2136-12-31 13:41:00,2137-01-01 00:35:00,0
10003400,29483621,2136-11-04 20:43:00,2136-11-12 17:40:00,,EW EMER.,P37QNF,EMERGENCY ROOM,SKILLED NURSING FACILITY,Medicare,ENGLISH,MARRIED,BLACK/AFRICAN AMERICAN,2136-11-04 16:08:00,2136-11-04 22:12:00,0
10003400,22390287,2137-02-07 19:42:00,2137-02-18 18:30:00,,EW EMER.,P42DF5,EMERGENCY ROOM,SKILLED NURSING FACILITY,Medicare,ENGLISH,MARRIED,BLACK/AFRICAN AMERICAN,2137-02-07 13:06:00,2137-02-07 21:44:00,0
10003400,26090619,2134-06-06 02:25:00,2134-06-07 15:05:00,,EW EMER.,P77SO2,EMERGENCY ROOM,HOME HEALTH CARE,Medicare,ENGLISH,MARRIED,BLACK/AFRICAN AMERICAN,2134-06-05 21:42:00,2134-06-06 03:44:00,0
10003400,26467376,2136-12-09 14:44:00,2136-12-15 16:00:00,,EW EMER.,P14622,EMERGENCY ROOM,SKILLED NURSING FACILITY,Medicare,ENGLISH,MARRIED,BLACK/AFRICAN AMERICAN,2136-12-09 13:16:00,2136-12-09 18:45:00,0
10023117,28872262,2171-11-07 21:37:00,2171-11-22 15:30:00,,EW EMER.,P89ZCW,EMERGENCY ROOM,HOME HEALTH CARE,Medicare,ENGLISH,WIDOWED,WHITE,2171-11-07 17:40:00,2171-11-07 22:50:00,0
10023117,29858644,2173-04-16 22:15:00,2173-04-20 16:40:00,,EW EMER.,P23URY,EMERGENCY ROOM,HOME,Medicare,ENGLISH,WIDOWED,WHITE,2173-04-16 17:00:00,2173-04-16 23:18:00,0
10017492,27417763,2116-06-26 18:25:00,2116-07-05 08:05:00,2116-07-05 08:05:00,EW EMER.,P3417E,EMERGENCY ROOM,DIED,Medicaid,?,SINGLE,PATIENT DECLINED TO ANSWER,2116-06-26 14:29:00,2116-06-26 21:46:00,1
10017492,27672872,2114-03-19 20:05:00,2114-04-02 18:30:00,,EW EMER.,P68RKH,EMERGENCY ROOM,HOME HEALTH CARE,Other,?,SINGLE,PATIENT DECLINED TO ANSWER,2114-03-19 15:57:00,2114-03-19 21:38:00,0
10038999,29026789,2132-05-17 23:32:00,2132-05-23 13:01:00,,EW EMER.,P64JOR,TRANSFER FROM HOSPITAL,REHAB,Medicare,ENGLISH,SINGLE,WHITE,2132-05-17 19:56:00,2132-05-18 01:36:00,0
10040025,25172300,2145-07-03 23:46:00,2145-07-05 13:57:00,,EW EMER.,P73Y1N,EMERGENCY ROOM,HOME,Other,ENGLISH,DIVORCED,WHITE,2145-07-03 20:02:00,2145-07-04 10:58:00,0
10040025,25384176,2145-10-19 23:48:00,2145-10-24 14:20:00,,EW EMER.,P1904G,EMERGENCY ROOM,HOME,Other,ENGLISH,DIVORCED,WHITE,2145-10-19 19:23:00,2145-10-20 00:54:00,0
10040025,27876215,2147-11-09 08:02:00,2147-11-14 18:53:00,,EW EMER.,P059D0,EMERGENCY ROOM,SKILLED NURSING FACILITY,Other,ENGLISH,DIVORCED,WHITE,2147-11-09 04:17:00,2147-11-09 10:56:00,0
10040025,27553957,2145-07-24 19:00:00,2145-07-31 14:12:00,,EW EMER.,P38RSS,EMERGENCY ROOM,HOME,Other,ENGLISH,DIVORCED,WHITE,2145-07-24 11:30:00,2145-07-24 20:32:00,0
10009049,22995465,2174-05-26 08:21:00,2174-05-31 14:15:00,,EW EMER.,P3529J,EMERGENCY ROOM,HOME,Other,ENGLISH,MARRIED,WHITE,2174-05-26 04:20:00,2174-05-26 09:18:00,0
10016742,29281842,2178-07-03 21:13:00,2178-07-08 20:20:00,,EW EMER.,P031HZ,EMERGENCY ROOM,SKILLED NURSING FACILITY,Medicaid,ENGLISH,SINGLE,BLACK/AFRICAN AMERICAN,2178-07-03 17:39:00,2178-07-03 22:45:00,0
10004457,28723315,2141-08-12 16:02:00,2141-08-13 17:47:00,,EW EMER.,P46WR5,EMERGENCY ROOM,HOME,Medicare,ENGLISH,SINGLE,WHITE,2141-08-12 12:08:00,2141-08-12 17:20:00,0
10029291,22205327,2123-02-20 01:59:00,2123-03-10 15:30:00,,EW EMER.,P44UQI,EMERGENCY ROOM,HOME HEALTH CARE,Medicare,ENGLISH,DIVORCED,WHITE,2123-02-19 23:57:00,2123-02-20 04:13:00,0
10004720,22081550,2186-11-12 18:01:00,2186-11-17 18:30:00,2186-11-17 18:30:00,EW EMER.,P3529J,INFORMATION NOT AVAILABLE,DIED,Medicare,ENGLISH,SINGLE,WHITE,2186-11-12 16:09:00,2186-11-12 19:55:00,1
10019568,28710730,2120-01-30 21:07:00,2120-02-02 15:40:00,,EW EMER.,P80LEI,EMERGENCY ROOM,HOME,Medicare,ENGLISH,SINGLE,WHITE,2120-01-30 19:44:00,2120-01-30 22:51:00,0
10002930,25922998,2198-04-17 19:38:00,2198-04-22 16:02:00,,EW EMER.,P30KEH,EMERGENCY ROOM,PSYCH FACILITY,Medicare,ENGLISH,SINGLE,BLACK/AFRICAN AMERICAN,2198-04-17 11:42:00,2198-04-17 21:24:00,0
10002930,25696644,2196-04-14 12:25:00,2196-04-17 15:28:00,,EW EMER.,P43A1R,EMERGENCY ROOM,PSYCH FACILITY,Medicare,ENGLISH,SINGLE,BLACK/AFRICAN AMERICAN,2196-04-14 05:55:00,2196-04-14 13:40:00,0
10007795,22051341,2136-09-22 20:51:00,2136-09-24 14:20:00,,EW EMER.,P99LA7,EMERGENCY ROOM,HOME,Medicare,ENGLISH,SINGLE,WHITE,2136-09-22 17:07:00,2136-09-22 22:30:00,0
10007795,20285402,2136-08-04 22:16:00,2136-08-11 19:20:00,,EW EMER.,P74QAJ,EMERGENCY ROOM,HOME HEALTH CARE,Medicare,ENGLISH,SINGLE,WHITE,2136-08-04 16:26:00,2136-08-05 00:17:00,0
10010867,22429197,2147-12-30 08:40:00,2148-01-11 17:55:00,,EW EMER.,P3417E,EMERGENCY ROOM,REHAB,Other,ENGLISH,SINGLE,WHITE - BRAZILIAN,2147-12-30 06:45:00,2147-12-30 09:33:00,0
10025463,24470193,2137-10-08 21:20:00,2137-10-09 15:30:00,2137-10-09 15:30:00,EW EMER.,P01LRQ,EMERGENCY ROOM,DIED,Other,ENGLISH,MARRIED,WHITE,2137-10-08 18:16:00,2137-10-08 20:44:00,1
10002428,28662225,2156-04-12 14:16:00,2156-04-29 16:26:00,,EW EMER.,P64TOH,EMERGENCY ROOM,SKILLED NURSING FACILITY,Medicare,ENGLISH,WIDOWED,WHITE,2156-04-12 09:56:00,2156-04-12 17:11:00,0
10002428,20321825,2156-04-30 20:35:00,2156-05-03 16:36:00,,EW EMER.,P825SO,EMERGENCY ROOM,CHRONIC/LONG TERM ACUTE CARE,Medicare,ENGLISH,WIDOWED,WHITE,2156-04-30 18:30:00,2156-04-30 21:53:00,0
10002428,23473524,2156-05-11 14:49:00,2156-05-22 14:16:00,,EW EMER.,P3529J,EMERGENCY ROOM,CHRONIC/LONG TERM ACUTE CARE,Medicare,ENGLISH,WIDOWED,WHITE,2156-05-11 11:29:00,2156-05-11 16:53:00,0
10007818,22987108,2146-06-10 16:37:00,2146-07-12 00:00:00,2146-07-12 20:50:00,DIRECT EMER.,P48GIG,PHYSICIAN REFERRAL,DIED,Medicare,ENGLISH,MARRIED,WHITE,,,1
10004235,22187210,2196-06-20 21:11:00,2196-06-22 13:30:00,,DIRECT EMER.,P98TAU,PHYSICIAN REFERRAL,HOME HEALTH CARE,Medicaid,ENGLISH,SINGLE,BLACK/CAPE VERDEAN,,,0
10001217,27703517,2157-12-18 16:58:00,2157-12-24 14:55:00,,DIRECT EMER.,P99698,PHYSICIAN REFERRAL,HOME HEALTH CARE,Other,?,MARRIED,WHITE,,,0
10021487,27660781,2117-03-03 15:59:00,2117-03-27 16:40:00,,DIRECT EMER.,P82B0E,CLINIC REFERRAL,HOME,Other,ENGLISH,DIVORCED,WHITE,,,0
10015860,28613200,2188-03-29 14:14:00,2188-04-02 16:30:00,,DIRECT EMER.,P62A9I,CLINIC REFERRAL,HOME HEALTH CARE,Other,ENGLISH,SINGLE,WHITE,,,0
10031757,28477280,2137-10-12 22:43:00,2137-10-24 17:30:00,,DIRECT EMER.,P94RT6,CLINIC REFERRAL,HOSPICE,Other,ENGLISH,DIVORCED,WHITE,,,0
10014354,27562275,2148-07-18 02:31:00,2148-07-20 17:47:00,,DIRECT EMER.,P90LIK,PHYSICIAN REFERRAL,HOME HEALTH CARE,Other,ENGLISH,SINGLE,WHITE,2148-07-17 20:36:00,2148-07-18 04:46:00,0
10035631,27496788,2113-08-26 17:07:00,2113-08-29 15:18:00,,DIRECT EMER.,P45GUA,CLINIC REFERRAL,HOME,Other,ENGLISH,MARRIED,WHITE,,,0
10035631,29462354,2112-09-17 19:13:00,2112-10-17 01:41:00,,DIRECT EMER.,P45GUA,PHYSICIAN REFERRAL,HOME,Other,ENGLISH,MARRIED,UNKNOWN,2112-09-17 18:46:00,2112-09-17 19:50:00,0
10035631,22732862,2112-11-10 15:55:00,2112-11-20 16:20:00,,DIRECT EMER.,P11CH5,CLINIC REFERRAL,HOME,Other,ENGLISH,MARRIED,UNKNOWN,,,0
10035631,21599196,2116-02-13 11:17:00,2116-02-15 17:09:00,,DIRECT EMER.,P15Q8N,PHYSICIAN REFERRAL,HOME,Other,ENGLISH,MARRIED,WHITE,,,0
10019003,26529390,2155-05-17 00:02:00,2155-05-19 18:27:00,,DIRECT EMER.,P73PF1,PHYSICIAN REFERRAL,HOME,Other,ENGLISH,WIDOWED,WHITE,2155-05-17 21:03:00,2155-05-18 03:03:00,0
10023117,28887654,2174-12-16 13:25:00,2174-12-20 10:27:00,,DIRECT EMER.,P94CCF,PHYSICIAN REFERRAL,HOME,Medicare,ENGLISH,WIDOWED,WHITE,,,0
10002930,23688993,2193-08-05 11:45:00,2193-08-11 09:25:00,,DIRECT EMER.,P356OC,PHYSICIAN REFERRAL,HOME,Medicare,ENGLISH,SINGLE,BLACK/AFRICAN AMERICAN,2193-08-05 05:04:00,2193-08-05 12:00:00,0
10025463,27327816,2136-10-31 07:15:00,2136-11-02 16:50:00,,DIRECT EMER.,P38W5P,PHYSICIAN REFERRAL,HOME,Other,ENGLISH,MARRIED,WHITE,,,0
10023239,28984460,2140-10-22 22:04:00,2140-10-23 17:01:00,,EU OBSERVATION,P76VMW,PHYSICIAN REFERRAL,,Other,ENGLISH,MARRIED,WHITE,2140-10-22 18:05:00,2140-10-23 17:01:00,0
10024043,25561728,2117-02-03 20:10:00,2117-02-04 11:53:00,,EU OBSERVATION,P19QFH,EMERGENCY ROOM,,Medicare,ENGLISH,SINGLE,BLACK/AFRICAN AMERICAN,2117-02-03 17:13:00,2117-02-04 11:53:00,0
10015860,27670224,2187-12-14 15:48:00,2187-12-16 14:38:00,,EU OBSERVATION,P25XDB,EMERGENCY ROOM,,Other,ENGLISH,SINGLE,WHITE,2187-12-14 12:49:00,2187-12-14 17:48:00,0
10015860,22607171,2190-05-15 08:16:00,2190-05-16 15:44:00,,EU OBSERVATION,P73Y1N,EMERGENCY ROOM,,Other,ENGLISH,SINGLE,WHITE,2190-05-14 17:11:00,2190-05-15 10:00:00,0
10015860,22416954,2193-05-03 22:45:00,2193-05-05 02:13:00,,EU OBSERVATION,P21E4S,TRANSFER FROM HOSPITAL,,Other,ENGLISH,SINGLE,WHITE,2193-05-03 19:12:00,2193-05-05 02:13:00,0
10020740,23143086,2151-01-15 15:25:00,2151-01-16 02:38:00,,EU OBSERVATION,P607HY,EMERGENCY ROOM,,Other,ENGLISH,SINGLE,WHITE,2151-01-15 11:47:00,2151-01-16 02:38:00,0
10020740,23199774,2150-09-15 14:09:00,2150-09-15 17:09:00,,EU OBSERVATION,P30KYN,EMERGENCY ROOM,,Other,ENGLISH,SINGLE,WHITE,2150-09-15 09:31:00,2150-09-15 17:09:00,0
10014354,24096336,2149-06-19 22:54:00,2149-06-20 13:48:00,,EU OBSERVATION,P83K3D,PHYSICIAN REFERRAL,,Other,ENGLISH,SINGLE,WHITE,2149-06-19 17:01:00,2149-06-20 13:48:00,0
10014354,26722126,2146-11-09 01:53:00,2146-11-09 13:13:00,,EU OBSERVATION,P187JB,EMERGENCY ROOM,,Other,ENGLISH,MARRIED,WHITE,2146-11-08 21:24:00,2146-11-09 13:13:00,0
10014354,20900955,2149-03-04 23:14:00,2149-03-05 14:59:00,,EU OBSERVATION,P63AD6,WALK-IN/SELF REFERRAL,,Other,ENGLISH,SINGLE,WHITE,2149-03-04 20:24:00,2149-03-05 14:59:00,0
10014354,22502504,2147-09-12 05:06:00,2147-09-12 19:00:00,,EU OBSERVATION,P04X8Y,EMERGENCY ROOM,,Other,ENGLISH,SINGLE,WHITE,2147-09-12 02:01:00,2147-09-12 06:37:00,0
10014354,26173805,2149-09-17 22:54:00,2149-09-18 10:45:00,,EU OBSERVATION,P57NY9,WALK-IN/SELF REFERRAL,,Other,ENGLISH,SINGLE,WHITE,2149-09-17 09:08:00,2149-09-17 23:55:00,0
10014354,27494880,2147-06-04 00:42:00,2147-06-04 15:00:00,,EU OBSERVATION,P79SJ2,EMERGENCY ROOM,,Other,ENGLISH,SINGLE,WHITE,2147-06-03 22:39:00,2147-06-04 09:54:00,0
10039708,27504040,2142-07-06 09:08:00,2142-07-07 16:40:00,,EU OBSERVATION,P94RT6,PHYSICIAN REFERRAL,,Other,ENGLISH,SINGLE,BLACK/AFRICAN AMERICAN,2142-07-06 07:21:00,2142-07-06 08:27:00,0
10026406,25166559,2133-03-01 19:30:00,2133-03-04 17:05:00,,EU OBSERVATION,P42K8Q,WALK-IN/SELF REFERRAL,,Other,ENGLISH,DIVORCED,WHITE,2133-03-01 16:42:00,2133-03-04 17:05:00,0
10037928,22228639,2183-08-04 04:04:00,2183-08-04 16:07:00,,EU OBSERVATION,P850UN,PHYSICIAN REFERRAL,,Medicare,?,WIDOWED,HISPANIC/LATINO - CUBAN,2183-08-03 19:27:00,2183-08-04 16:07:00,0
10037928,28543425,2175-10-26 01:31:00,2175-10-26 12:48:00,,EU OBSERVATION,P40IUG,EMERGENCY ROOM,,Medicare,?,SINGLE,HISPANIC OR LATINO,2175-10-25 19:55:00,2175-10-26 12:48:00,0
10012853,22896692,2176-08-11 15:17:00,2176-08-11 17:35:00,,EU OBSERVATION,P76VMW,EMERGENCY ROOM,,Medicare,ENGLISH,WIDOWED,BLACK/AFRICAN AMERICAN,2176-08-11 08:36:00,2176-08-11 17:35:00,0
10039997,21390688,2135-11-07 02:42:00,2135-11-07 06:27:00,,EU OBSERVATION,P03N1K,EMERGENCY ROOM,,Medicare,ENGLISH,WIDOWED,BLACK/AFRICAN AMERICAN,2135-11-06 21:44:00,2135-11-07 06:27:00,0
10020306,26332470,2133-05-05 05:43:00,2133-05-05 15:37:00,,EU OBSERVATION,P45LON,CLINIC REFERRAL,,Medicare,ENGLISH,WIDOWED,BLACK/AFRICAN AMERICAN,2133-05-04 20:52:00,2133-05-05 15:37:00,0
10002930,25282382,2197-04-17 02:01:00,2197-04-17 09:48:00,,EU OBSERVATION,P33NK4,EMERGENCY ROOM,,Medicare,ENGLISH,SINGLE,BLACK/AFRICAN AMERICAN,2197-04-16 22:57:00,2197-04-17 09:48:00,0
10002930,28697806,2200-06-05 05:43:00,2200-06-05 10:26:00,,EU OBSERVATION,P68U3G,PHYSICIAN REFERRAL,,Medicare,ENGLISH,SINGLE,BLACK/AFRICAN AMERICAN,2200-06-04 16:38:00,2200-06-05 10:26:00,0
10002930,20846853,2201-02-12 16:58:00,2201-02-13 11:11:00,,EU OBSERVATION,P2358X,PHYSICIAN REFERRAL,,Medicare,ENGLISH,SINGLE,BLACK/AFRICAN AMERICAN,2201-02-12 15:11:00,2201-02-13 11:11:00,0
10002930,22380825,2193-08-05 06:18:00,2193-08-05 11:44:00,,EU OBSERVATION,P38XXV,EMERGENCY ROOM,,Medicare,ENGLISH,SINGLE,BLACK/AFRICAN AMERICAN,2193-08-05 05:04:00,2193-08-05 12:00:00,0
10002930,23720373,2199-02-17 21:45:00,2199-02-19 13:38:00,,EU OBSERVATION,P59HPG,EMERGENCY ROOM,,Medicare,ENGLISH,SINGLE,BLACK/AFRICAN AMERICAN,2199-02-17 14:32:00,2199-02-19 13:38:00,0
10002930,20282368,2201-03-23 19:15:00,2201-03-26 14:24:00,,EU OBSERVATION,P850UN,PHYSICIAN REFERRAL,,Medicare,ENGLISH,SINGLE,BLACK/AFRICAN AMERICAN,2201-03-23 12:04:00,2201-03-26 14:24:00,0
10002930,28477649,2197-04-07 06:56:00,2197-04-08 19:37:00,,EU OBSERVATION,P11RCJ,EMERGENCY ROOM,,Medicare,ENGLISH,SINGLE,BLACK/AFRICAN AMERICAN,2197-04-07 03:20:00,2197-04-08 20:14:00,0
10002428,28676446,2157-07-16 04:09:00,2157-07-18 16:49:00,,EU OBSERVATION,P08S8F,EMERGENCY ROOM,,Medicare,ENGLISH,WIDOWED,WHITE,2157-07-16 01:50:00,2157-07-16 09:51:00,0
10002428,25797028,2155-07-14 19:15:00,2155-07-15 18:37:00,,EU OBSERVATION,P64TOH,EMERGENCY ROOM,,Medicare,ENGLISH,WIDOWED,WHITE,2155-07-14 16:58:00,2155-07-14 20:04:00,0
10002428,26549334,2160-07-15 23:37:00,2160-07-16 18:49:00,,EU OBSERVATION,P607HY,EMERGENCY ROOM,,Medicare,ENGLISH,WIDOWED,WHITE,2160-07-15 17:34:00,2160-07-16 18:49:00,0
10018328,26706939,2154-02-05 21:58:00,2154-02-09 15:15:00,,OBSERVATION ADMIT,P233F6,TRANSFER FROM HOSPITAL,HOME HEALTH CARE,Other,ENGLISH,DIVORCED,WHITE,2154-02-05 17:09:00,2154-02-05 22:54:00,0
10005817,28661809,2135-01-03 21:54:00,2135-01-19 18:36:00,2135-01-19 18:36:00,OBSERVATION ADMIT,P7554I,TRANSFER FROM HOSPITAL,DIED,Medicare,ENGLISH,MARRIED,WHITE,,,1
10019385,20611796,2180-03-04 01:16:00,2180-03-06 18:32:00,,OBSERVATION ADMIT,P95P90,EMERGENCY ROOM,HOME HEALTH CARE,Other,ENGLISH,MARRIED,WHITE,2180-03-03 20:26:00,2180-03-04 02:24:00,0
10020187,24104168,2169-01-15 04:04:00,2169-01-24 17:20:00,,OBSERVATION ADMIT,P898NM,EMERGENCY ROOM,HOME HEALTH CARE,Other,?,MARRIED,HISPANIC/LATINO - SALVADORAN,2169-01-14 23:26:00,2169-01-15 04:56:00,0
10010471,21322534,2155-05-08 17:05:00,2155-05-10 18:55:00,,OBSERVATION ADMIT,P89T1L,EMERGENCY ROOM,HOME,Medicare,ENGLISH,WIDOWED,WHITE,2155-05-08 13:48:00,2155-05-08 18:16:00,0
10005866,22589518,2149-02-11 21:49:00,2149-02-14 16:45:00,,OBSERVATION ADMIT,P874LG,EMERGENCY ROOM,HOME,Medicaid,ENGLISH,SINGLE,PORTUGUESE,2149-02-11 08:52:00,2149-02-11 23:09:00,0
10005866,21636229,2149-09-20 14:30:00,2149-09-26 15:05:00,,OBSERVATION ADMIT,P96Y5O,EMERGENCY ROOM,HOME,Medicaid,ENGLISH,SINGLE,PORTUGUESE,2149-09-20 05:50:00,2149-09-20 15:53:00,0
10005866,23514107,2149-06-20 19:27:00,2149-06-25 15:55:00,,OBSERVATION ADMIT,P28OI9,EMERGENCY ROOM,HOME,Medicaid,ENGLISH,SINGLE,PORTUGUESE,2149-06-20 10:20:00,2149-06-20 20:59:00,0
10005866,20364112,2149-10-01 18:56:00,2149-10-25 18:50:00,,OBSERVATION ADMIT,P94RT6,PHYSICIAN REFERRAL,SKILLED NURSING FACILITY,Medicaid,ENGLISH,SINGLE,PORTUGUESE,2149-10-01 02:07:00,2149-10-01 15:59:00,0
10015860,25103777,2192-07-31 16:05:00,2192-08-06 16:37:00,,OBSERVATION ADMIT,P104SU,TRANSFER FROM SKILLED NURSING FACILITY,SKILLED NURSING FACILITY,Other,ENGLISH,SINGLE,WHITE,2192-07-31 11:00:00,2192-07-31 17:10:00,0
10015860,22413744,2191-01-15 01:55:00,2191-01-30 17:09:00,,OBSERVATION ADMIT,P172D4,EMERGENCY ROOM,SKILLED NURSING FACILITY,Other,ENGLISH,SINGLE,WHITE,2191-01-14 21:18:00,2191-01-15 03:20:00,0
10015860,28196804,2193-11-23 19:15:00,2193-11-27 21:58:00,,OBSERVATION ADMIT,P54UVQ,WALK-IN/SELF REFERRAL,SKILLED NURSING FACILITY,Other,ENGLISH,SINGLE,WHITE,2193-11-23 12:38:00,2193-11-23 20:59:00,0
10014354,22508257,2148-05-10 23:29:00,2148-05-20 14:30:00,,OBSERVATION ADMIT,P57B0F,EMERGENCY ROOM,HOME HEALTH CARE,Other,ENGLISH,SINGLE,WHITE,2148-05-10 20:25:00,2148-05-11 01:22:00,0
10014354,29600294,2148-08-14 22:57:00,2148-08-18 21:12:00,,OBSERVATION ADMIT,P723L9,PHYSICIAN REFERRAL,AGAINST ADVICE,Other,ENGLISH,SINGLE,WHITE,2148-08-14 16:32:00,2148-08-15 00:27:00,0
10014354,26228185,2150-04-30 20:19:00,2150-05-07 14:10:00,,OBSERVATION ADMIT,P17YHV,TRANSFER FROM HOSPITAL,HOME HEALTH CARE,Other,ENGLISH,SINGLE,WHITE,2150-04-30 14:26:00,2150-04-30 21:54:00,0
10014354,24980601,2150-02-04 20:12:00,2150-02-08 14:10:00,,OBSERVATION ADMIT,P42VJP,PHYSICIAN REFERRAL,AGAINST ADVICE,Other,ENGLISH,SINGLE,WHITE,2150-02-04 14:50:00,2150-02-04 22:44:00,0
10014354,26486158,2148-08-22 15:18:00,2148-09-08 12:00:00,,OBSERVATION ADMIT,P17YHV,CLINIC REFERRAL,HOME HEALTH CARE,Other,ENGLISH,SINGLE,WHITE,,,0
10014354,29780751,2147-11-26 00:39:00,2147-11-30 16:54:00,,OBSERVATION ADMIT,P21X87,EMERGENCY ROOM,HOME HEALTH CARE,Other,ENGLISH,SINGLE,WHITE,2147-11-25 19:39:00,2147-11-26 01:30:00,0
10014354,23132022,2148-06-24 15:22:00,2148-06-28 13:54:00,,OBSERVATION ADMIT,P259GB,PHYSICIAN REFERRAL,HOME,Other,ENGLISH,SINGLE,WHITE,2148-06-24 09:22:00,2148-06-24 16:12:00,0
10005909,20199380,2144-10-28 23:20:00,2144-11-02 15:23:00,,OBSERVATION ADMIT,P43BTJ,EMERGENCY ROOM,HOME,Other,ENGLISH,MARRIED,WHITE,2144-10-28 18:29:00,2144-10-29 00:10:00,0
10039708,26793610,2140-09-25 04:17:00,2140-09-26 17:40:00,,OBSERVATION ADMIT,P91LM4,EMERGENCY ROOM,HOME,Other,ENGLISH,SINGLE,BLACK/AFRICAN AMERICAN,2140-09-24 21:17:00,2140-09-25 05:57:00,0
10039708,29488258,2144-01-19 12:07:00,2144-01-21 21:20:00,,OBSERVATION ADMIT,P8785Z,WALK-IN/SELF REFERRAL,HOME,Other,ENGLISH,SINGLE,BLACK/AFRICAN AMERICAN,2144-01-18 11:53:00,2144-01-19 13:59:00,0
10039708,24928679,2143-09-19 18:36:00,2143-09-22 23:00:00,,OBSERVATION ADMIT,P8785Z,CLINIC REFERRAL,HOME HEALTH CARE,Other,ENGLISH,SINGLE,BLACK/AFRICAN AMERICAN,2143-09-19 10:57:00,2143-09-19 20:57:00,0
10039708,23819016,2140-06-18 00:22:00,2140-06-22 17:40:00,,OBSERVATION ADMIT,P48KFD,EMERGENCY ROOM,HOME,Other,ENGLISH,SINGLE,BLACK/AFRICAN AMERICAN,2140-06-17 17:47:00,2140-06-18 01:41:00,0
10039708,20093566,2143-09-26 18:24:00,2143-09-30 20:00:00,,OBSERVATION ADMIT,P04ZFH,CLINIC REFERRAL,HOME HEALTH CARE,Other,ENGLISH,SINGLE,BLACK/AFRICAN AMERICAN,2143-09-26 08:16:00,2143-09-26 19:52:00,0
10037928,24885579,2182-04-29 04:29:00,2182-05-03 17:20:00,,OBSERVATION ADMIT,P3417E,WALK-IN/SELF REFERRAL,HOME,Medicare,?,WIDOWED,HISPANIC/LATINO - CUBAN,2182-04-28 17:25:00,2182-04-29 07:49:00,0
10035631,21476294,2115-11-08 13:54:00,2115-12-08 17:31:00,,OBSERVATION ADMIT,P45GUA,EMERGENCY ROOM,HOME,Other,ENGLISH,MARRIED,WHITE,2115-11-08 12:02:00,2115-11-08 15:43:00,0
10035631,29276678,2116-02-27 20:55:00,2116-03-12 07:45:00,2116-03-12 07:45:00,OBSERVATION ADMIT,P17YHV,EMERGENCY ROOM,DIED,Other,ENGLISH,MARRIED,WHITE,2116-02-27 15:33:00,2116-02-27 22:03:00,1
10015931,24420677,2176-12-16 23:31:00,2176-12-31 17:35:00,,OBSERVATION ADMIT,P49NLZ,EMERGENCY ROOM,HOME HEALTH CARE,Medicare,ENGLISH,MARRIED,WHITE,2176-12-16 18:24:00,2176-12-17 01:07:00,0
10015931,28157142,2176-11-14 18:02:00,2176-11-27 13:30:00,,OBSERVATION ADMIT,P20N5X,EMERGENCY ROOM,SKILLED NURSING FACILITY,Medicare,ENGLISH,MARRIED,WHITE,2176-11-14 02:27:00,2176-11-14 19:51:00,0
10019003,25508812,2155-05-22 21:46:00,2155-05-30 03:30:00,,OBSERVATION ADMIT,P88A32,PHYSICIAN REFERRAL,HOME,Other,ENGLISH,WIDOWED,WHITE,,,0
10039997,22484749,2137-03-15 22:08:00,2137-03-18 16:34:00,,OBSERVATION ADMIT,P70N8P,PHYSICIAN REFERRAL,REHAB,Medicare,ENGLISH,WIDOWED,BLACK/AFRICAN AMERICAN,2137-03-15 15:27:00,2137-03-16 00:11:00,0
10023117,21607814,2175-07-06 15:57:00,2175-07-20 00:00:00,2175-07-20 22:50:00,OBSERVATION ADMIT,P49NLZ,EMERGENCY ROOM,DIED,Medicare,ENGLISH,WIDOWED,WHITE,2175-07-06 14:02:00,2175-07-06 17:41:00,1
10023117,21133938,2175-03-20 23:29:00,2175-03-29 16:00:00,,OBSERVATION ADMIT,P66Z67,TRANSFER FROM HOSPITAL,HOME,Medicare,ENGLISH,WIDOWED,WHITE,,,0
10020306,23052851,2135-01-15 20:55:00,2135-02-07 17:50:00,,OBSERVATION ADMIT,P54UVQ,PHYSICIAN REFERRAL,SKILLED NURSING FACILITY,Other,ENGLISH,WIDOWED,BLACK/AFRICAN AMERICAN,2135-01-15 16:12:00,2135-01-15 22:38:00,0
10038999,27189241,2131-05-22 21:49:00,2131-06-04 13:43:00,,OBSERVATION ADMIT,P44UQI,EMERGENCY ROOM,HOME,Medicare,ENGLISH,SINGLE,WHITE,2131-05-22 20:33:00,2131-05-22 21:28:00,0
10040025,21791856,2147-06-16 22:00:00,2147-06-22 16:15:00,,OBSERVATION ADMIT,P7634X,EMERGENCY ROOM,HOME HEALTH CARE,Other,ENGLISH,DIVORCED,WHITE,2147-06-16 18:58:00,2147-06-16 23:06:00,0
10040025,22251969,2147-08-03 02:58:00,2147-08-06 16:50:00,,OBSERVATION ADMIT,P85BWS,EMERGENCY ROOM,HOME,Other,ENGLISH,DIVORCED,WHITE,2147-08-02 18:14:00,2147-08-03 02:41:00,0
10040025,27259207,2147-12-04 20:48:00,2147-12-18 16:43:00,,OBSERVATION ADMIT,P43BTJ,EMERGENCY ROOM,SKILLED NURSING FACILITY,Other,ENGLISH,DIVORCED,WHITE,2147-12-04 13:11:00,2147-12-05 02:28:00,0
10040025,27996267,2148-01-23 12:18:00,2148-02-04 20:51:00,,OBSERVATION ADMIT,P10WWR,TRANSFER FROM SKILLED NURSING FACILITY,HOSPICE,Other,ENGLISH,DIVORCED,WHITE,2148-01-22 14:47:00,2148-01-23 09:42:00,0
10016742,28506150,2178-07-13 05:40:00,2178-07-16 02:00:00,,OBSERVATION ADMIT,P031HZ,EMERGENCY ROOM,SKILLED NURSING FACILITY,Medicaid,ENGLISH,SINGLE,BLACK/AFRICAN AMERICAN,2178-07-13 04:37:00,2178-07-13 08:16:00,0
10016742,27568122,2178-07-22 07:19:00,2178-07-25 16:30:00,,OBSERVATION ADMIT,P513EK,EMERGENCY ROOM,CHRONIC/LONG TERM ACUTE CARE,Medicaid,ENGLISH,SINGLE,BLACK/AFRICAN AMERICAN,2178-07-22 05:41:00,2178-07-22 08:19:00,0
10010867,22196214,2148-03-07 23:30:00,2148-03-13 15:50:00,,OBSERVATION ADMIT,P233F6,EMERGENCY ROOM,REHAB,Other,ENGLISH,DIVORCED,WHITE - BRAZILIAN,2148-03-07 18:21:00,2148-03-08 00:50:00,0
10010867,22950920,2148-01-25 22:58:00,2148-01-30 11:23:00,,OBSERVATION ADMIT,P39FGY,EMERGENCY ROOM,REHAB,Other,ENGLISH,DIVORCED,WHITE - BRAZILIAN,2148-01-25 18:46:00,2148-01-26 01:27:00,0
10002428,28295257,2160-04-14 12:30:00,2160-04-18 16:00:00,,OBSERVATION ADMIT,P18I28,EMERGENCY ROOM,SKILLED NURSING FACILITY,Medicare,ENGLISH,WIDOWED,WHITE,2160-04-14 09:01:00,2160-04-14 14:28:00,0
10005348,29176490,2129-05-22 16:00:00,2129-05-23 11:30:00,,DIRECT OBSERVATION,P132L1,PHYSICIAN REFERRAL,,Other,ENGLISH,MARRIED,WHITE,,,0
10020740,29820177,2150-07-09 22:09:00,2150-07-12 18:00:00,,DIRECT OBSERVATION,P1942H,CLINIC REFERRAL,,Other,ENGLISH,SINGLE,WHITE,,,0
10039708,22999601,2142-05-15 17:14:00,2142-05-15 18:21:00,,DIRECT OBSERVATION,P42K8Q,PHYSICIAN REFERRAL,,Other,ENGLISH,SINGLE,BLACK/AFRICAN AMERICAN,2142-05-15 10:47:00,2142-05-15 18:21:00,0
10012853,20457729,2177-11-03 09:30:00,2177-11-04 15:06:00,,DIRECT OBSERVATION,P05BRR,PHYSICIAN REFERRAL,,Medicare,ENGLISH,WIDOWED,BLACK/AFRICAN AMERICAN,,,0
10012853,26739864,2177-02-11 07:15:00,2177-02-12 16:20:00,,DIRECT OBSERVATION,P05BRR,PHYSICIAN REFERRAL,,Medicare,ENGLISH,WIDOWED,BLACK/AFRICAN AMERICAN,,,0
10004457,25559382,2148-09-14 14:19:00,2148-09-15 12:45:00,,DIRECT OBSERVATION,P466EI,PHYSICIAN REFERRAL,,Medicare,ENGLISH,DIVORCED,WHITE,,,0
10007795,27962747,2136-07-24 17:15:00,2136-07-27 14:27:00,,DIRECT OBSERVATION,P99LA7,PHYSICIAN REFERRAL,,Medicare,ENGLISH,SINGLE,WHITE,,,0
10035631,29654498,2113-07-17 17:15:00,2113-07-18 14:55:00,,AMBULATORY OBSERVATION,P48P6U,PACU,,Other,ENGLISH,MARRIED,WHITE,,,0
10020306,28778757,2129-10-29 08:00:00,2129-10-30 13:20:00,,AMBULATORY OBSERVATION,P75JMU,PACU,,Medicare,ENGLISH,SINGLE,BLACK/AFRICAN AMERICAN,,,0
10040025,27125816,2143-03-18 12:34:00,2143-03-19 12:00:00,,AMBULATORY OBSERVATION,P623U2,PROCEDURE SITE,,Other,ENGLISH,DIVORCED,WHITE,,,0
10004457,21039249,2140-09-17 13:41:00,2140-09-18 12:40:00,,AMBULATORY OBSERVATION,P99YFT,PROCEDURE SITE,,Medicare,ENGLISH,SINGLE,WHITE,,,0
10004457,21216581,2143-03-09 11:10:00,2143-03-10 11:35:00,,AMBULATORY OBSERVATION,P89ZCW,PROCEDURE SITE,,Medicare,ENGLISH,DIVORCED,WHITE,,,0
10011398,27505812,2146-12-15 07:15:00,2146-12-19 13:37:00,,SURGICAL SAME DAY ADMISSION,P47E1G,PHYSICIAN REFERRAL,HOME HEALTH CARE,Other,ENGLISH,MARRIED,HISPANIC OR LATINO,,,0
10014729,28889419,2125-02-27 07:15:00,2125-03-06 14:25:00,,SURGICAL SAME DAY ADMISSION,P17BJ5,PHYSICIAN REFERRAL,HOME HEALTH CARE,Other,ENGLISH,SINGLE,WHITE - OTHER EUROPEAN,,,0
10004235,25970245,2196-06-14 08:30:00,2196-06-19 14:54:00,,SURGICAL SAME DAY ADMISSION,P96Y5O,PHYSICIAN REFERRAL,HOME HEALTH CARE,Medicaid,ENGLISH,SINGLE,BLACK/CAPE VERDEAN,,,0
10039831,26924951,2115-12-28 07:15:00,2116-01-02 14:34:00,,SURGICAL SAME DAY ADMISSION,P82B0E,PHYSICIAN REFERRAL,HOME HEALTH CARE,Other,ENGLISH,MARRIED,UNABLE TO OBTAIN,,,0
10018328,23786647,2154-04-24 03:15:00,2154-05-03 14:00:00,,SURGICAL SAME DAY ADMISSION,P898NM,PHYSICIAN REFERRAL,HOME HEALTH CARE,Other,ENGLISH,DIVORCED,WHITE,,,0
10018081,28861356,2134-08-02 07:15:00,2134-08-13 17:34:00,,SURGICAL SAME DAY ADMISSION,P3417E,PHYSICIAN REFERRAL,SKILLED NURSING FACILITY,Medicare,ENGLISH,MARRIED,WHITE,,,0
10027602,21477991,2201-12-11 12:00:00,2201-12-17 13:45:00,,SURGICAL SAME DAY ADMISSION,P898NM,PHYSICIAN REFERRAL,REHAB,Other,ENGLISH,SINGLE,WHITE,,,0
10020187,26842957,2170-02-24 00:00:00,2170-02-25 15:00:00,,SURGICAL SAME DAY ADMISSION,P898NM,PHYSICIAN REFERRAL,HOME,Other,?,MARRIED,HISPANIC/LATINO - SALVADORAN,,,0
10003046,26048429,2154-01-02 07:15:00,2154-01-09 11:53:00,,SURGICAL SAME DAY ADMISSION,P2720E,PHYSICIAN REFERRAL,HOME HEALTH CARE,Other,ENGLISH,SINGLE,WHITE,,,0
10022017,22342963,2189-09-10 00:00:00,2189-09-16 15:00:00,,SURGICAL SAME DAY ADMISSION,P47E1G,PHYSICIAN REFERRAL,HOME HEALTH CARE,Other,ENGLISH,SINGLE,WHITE,,,0
10025612,23403708,2125-09-25 07:15:00,2125-10-03 12:24:00,,SURGICAL SAME DAY ADMISSION,P786Y5,PHYSICIAN REFERRAL,HOME HEALTH CARE,Medicare,ENGLISH,MARRIED,WHITE,,,0
10039997,24294180,2134-09-07 12:00:00,2134-09-11 13:10:00,,SURGICAL SAME DAY ADMISSION,P47SIK,PHYSICIAN REFERRAL,HOME,Medicare,ENGLISH,MARRIED,BLACK/AFRICAN AMERICAN,,,0
10023117,29839885,2170-10-08 07:15:00,2170-10-09 16:30:00,,SURGICAL SAME DAY ADMISSION,P89ZCW,PHYSICIAN REFERRAL,HOME HEALTH CARE,Medicare,ENGLISH,WIDOWED,WHITE,,,0
10038992,24745425,2187-07-29 01:05:00,2187-08-03 17:02:00,,SURGICAL SAME DAY ADMISSION,P41R5N,PHYSICIAN REFERRAL,SKILLED NURSING FACILITY,Medicare,ENGLISH,MARRIED,WHITE,,,0
10008287,22168393,2145-09-28 01:17:00,2145-10-02 13:35:00,,SURGICAL SAME DAY ADMISSION,P898NM,PHYSICIAN REFERRAL,HOME HEALTH CARE,Other,ENGLISH,SINGLE,WHITE,,,0
10022880,27708593,2177-03-12 07:15:00,2177-03-19 14:25:00,,SURGICAL SAME DAY ADMISSION,P99698,PHYSICIAN REFERRAL,HOME,Medicare,ENGLISH,MARRIED,WHITE,,,0
10004457,23251352,2141-12-17 11:00:00,2141-12-21 15:56:00,,SURGICAL SAME DAY ADMISSION,P41R5N,PHYSICIAN REFERRAL,REHAB,Medicare,ENGLISH,SINGLE,OTHER,,,0
10004457,28108313,2147-12-19 00:00:00,2147-12-21 16:10:00,,SURGICAL SAME DAY ADMISSION,P10WWR,PHYSICIAN REFERRAL,SKILLED NURSING FACILITY,Medicare,ENGLISH,DIVORCED,WHITE,,,0
1 subject_id hadm_id admittime dischtime deathtime admission_type admit_provider_id admission_location discharge_location insurance language marital_status race edregtime edouttime hospital_expire_flag
2 10004235 24181354 2196-02-24 14:38:00 2196-03-04 14:02:00 URGENT P03YMR TRANSFER FROM HOSPITAL SKILLED NURSING FACILITY Medicaid ENGLISH SINGLE BLACK/CAPE VERDEAN 2196-02-24 12:15:00 2196-02-24 17:07:00 0
3 10009628 25926192 2153-09-17 17:08:00 2153-09-25 13:20:00 URGENT P41R5N TRANSFER FROM HOSPITAL HOME HEALTH CARE Medicaid ? MARRIED HISPANIC/LATINO - PUERTO RICAN 0
4 10018081 23983182 2134-08-18 02:02:00 2134-08-23 19:35:00 URGENT P233F6 TRANSFER FROM HOSPITAL SKILLED NURSING FACILITY Medicare ENGLISH MARRIED WHITE 2134-08-17 16:24:00 2134-08-18 03:15:00 0
5 10006053 22942076 2111-11-13 23:39:00 2111-11-15 17:20:00 2111-11-15 17:20:00 URGENT P38TI6 TRANSFER FROM HOSPITAL DIED Medicaid ENGLISH UNKNOWN 1
6 10031404 21606243 2113-08-04 18:46:00 2113-08-06 20:57:00 URGENT P07HDB TRANSFER FROM HOSPITAL HOME Other ENGLISH WIDOWED WHITE 0
7 10005817 20626031 2132-12-12 01:43:00 2132-12-20 15:04:00 URGENT P41R5N TRANSFER FROM HOSPITAL HOME HEALTH CARE Medicare ENGLISH MARRIED WHITE 0
8 10019385 20297618 2180-02-15 20:28:00 2180-02-25 13:45:00 URGENT P536JC TRANSFER FROM HOSPITAL HOME HEALTH CARE Other ENGLISH MARRIED WHITE 0
9 10002495 24982426 2141-05-22 20:17:00 2141-05-29 17:41:00 URGENT P79SJ2 TRANSFER FROM HOSPITAL SKILLED NURSING FACILITY Medicare ENGLISH MARRIED UNKNOWN 0
10 10038081 20755971 2115-09-27 20:40:00 2115-10-12 00:00:00 2115-10-12 22:20:00 URGENT P48GIG TRANSFER FROM HOSPITAL DIED Other ? SINGLE UNKNOWN 1
11 10019917 22585261 2182-01-07 23:25:00 2182-01-10 16:52:00 URGENT P3529J TRANSFER FROM HOSPITAL HOME Other ENGLISH SINGLE OTHER 0
12 10037861 24256866 2115-10-09 20:28:00 2115-10-18 16:50:00 URGENT P64TGC TRANSFER FROM HOSPITAL HOME HEALTH CARE Medicare ENGLISH MARRIED UNKNOWN 0
13 10026354 24547356 2119-10-26 07:11:00 2119-11-06 12:30:00 URGENT P77BSD TRANSFER FROM HOSPITAL SKILLED NURSING FACILITY Other ? WHITE 2119-10-26 06:00:00 2119-10-26 06:37:00 0
14 10015860 26352758 2192-09-24 09:19:00 2192-09-28 19:52:00 URGENT P54RLA TRANSFER FROM SKILLED NURSING FACILITY SKILLED NURSING FACILITY Other ENGLISH SINGLE WHITE 2192-09-23 22:43:00 2192-09-24 09:38:00 0
15 10020740 23831430 2150-03-11 15:34:00 2150-04-25 13:50:00 URGENT P7554I TRANSFER FROM HOSPITAL SKILLED NURSING FACILITY Other ENGLISH SINGLE WHITE 0
16 10012552 27089790 2140-03-22 17:18:00 2140-03-30 14:10:00 URGENT P47E1G TRANSFER FROM HOSPITAL HOME HEALTH CARE Medicare ENGLISH MARRIED UNKNOWN 0
17 10014354 28335091 2147-04-26 16:44:00 2147-04-29 15:30:00 URGENT P79SJ2 TRANSFER FROM HOSPITAL HOME HEALTH CARE Other ENGLISH SINGLE WHITE 0
18 10014354 27487226 2148-06-30 01:09:00 2148-07-13 19:35:00 URGENT P44WVR TRANSFER FROM HOSPITAL HOME HEALTH CARE Other ENGLISH SINGLE WHITE 2148-06-29 21:06:00 2148-06-30 02:27:00 0
19 10039708 25864431 2142-03-26 06:08:00 2142-04-11 21:00:00 URGENT P81KFM TRANSFER FROM HOSPITAL HOME HEALTH CARE Other ENGLISH SINGLE BLACK/AFRICAN AMERICAN 2142-03-25 22:22:00 2142-03-26 08:08:00 0
20 10000032 22595853 2180-05-06 22:23:00 2180-05-07 17:15:00 URGENT P874LG TRANSFER FROM HOSPITAL HOME Other ENGLISH WIDOWED WHITE 2180-05-06 19:17:00 2180-05-06 23:30:00 0
21 10020786 23488445 2189-06-09 12:45:00 2189-06-13 17:20:00 URGENT P031HZ TRANSFER FROM HOSPITAL HOME Medicare ENGLISH WIDOWED WHITE 0
22 10027445 27488741 2145-12-08 19:47:00 2145-12-19 14:36:00 URGENT P38RSS TRANSFER FROM HOSPITAL HOME HEALTH CARE Other ENGLISH WIDOWED WHITE 0
23 10027445 29163082 2142-08-27 21:05:00 2142-09-05 17:33:00 URGENT P509SB TRANSFER FROM HOSPITAL HOME HEALTH CARE Other ENGLISH WIDOWED WHITE 0
24 10015931 22130791 2177-03-24 21:47:00 2177-03-29 14:15:00 2177-03-29 14:15:00 URGENT P031HZ TRANSFER FROM HOSPITAL DIED Medicare ENGLISH MARRIED WHITE 1
25 10036156 28019404 2157-07-01 04:52:00 2157-07-03 15:08:00 URGENT P94V16 TRANSFER FROM HOSPITAL HOME Other ENGLISH UNKNOWN 0
26 10021312 25020332 2113-08-16 00:32:00 2113-08-18 17:35:00 URGENT P31A9M TRANSFER FROM HOSPITAL HOME Other ENGLISH UNKNOWN 0
27 10021312 28829452 2113-09-12 14:42:00 2113-09-20 18:40:00 URGENT P72G4H TRANSFER FROM HOSPITAL ACUTE HOSPITAL Other ENGLISH UNKNOWN 0
28 10003400 23559586 2137-08-04 00:07:00 2137-09-02 17:05:00 2137-09-02 17:05:00 URGENT P99U21 TRANSFER FROM HOSPITAL DIED Medicare ENGLISH MARRIED BLACK/AFRICAN AMERICAN 1
29 10003400 20214994 2137-02-24 10:00:00 2137-03-19 15:45:00 URGENT P60ZCO TRANSFER FROM SKILLED NURSING FACILITY CHRONIC/LONG TERM ACUTE CARE Medicare ENGLISH MARRIED BLACK/AFRICAN AMERICAN 0
30 10023117 24244087 2174-06-07 23:25:00 2174-06-12 15:55:00 URGENT P878WT TRANSFER FROM HOSPITAL HOME Medicare ENGLISH WIDOWED WHITE 2174-06-07 16:24:00 2174-06-08 01:02:00 0
31 10038992 22797747 2185-11-02 18:26:00 2185-11-08 16:22:00 URGENT P534S8 TRANSFER FROM HOSPITAL HOME HEALTH CARE Medicare ENGLISH MARRIED UNKNOWN 0
32 10037975 27617929 2185-01-17 19:11:00 2185-01-22 14:25:00 2185-01-22 14:25:00 URGENT P17MK7 TRANSFER FROM HOSPITAL DIED Medicare ENGLISH MARRIED UNKNOWN 1
33 10035185 22580999 2120-05-12 12:53:00 2120-05-17 16:00:00 URGENT P41R5N TRANSFER FROM HOSPITAL HOME HEALTH CARE Other ENGLISH MARRIED WHITE 0
34 10002930 28301173 2197-04-08 19:37:00 2197-04-15 12:01:00 URGENT P3763P INTERNAL TRANSFER TO OR FROM PSYCH HOME Medicare ENGLISH SINGLE BLACK/AFRICAN AMERICAN 0
35 10002930 22733922 2198-04-22 16:17:00 2198-05-04 13:20:00 URGENT P3763P INTERNAL TRANSFER TO OR FROM PSYCH HOME Medicare ENGLISH SINGLE BLACK/AFRICAN AMERICAN 0
36 10007795 28477357 2136-04-10 20:33:00 2136-05-02 16:35:00 URGENT P172D4 TRANSFER FROM HOSPITAL CHRONIC/LONG TERM ACUTE CARE Medicare ENGLISH SINGLE WHITE 0
37 10007795 25135483 2136-05-04 20:20:00 2136-05-12 17:12:00 URGENT P99LA7 TRANSFER FROM HOSPITAL REHAB Medicare ENGLISH SINGLE WHITE 0
38 10004733 27411876 2174-12-04 11:28:00 2174-12-27 14:00:00 URGENT P48KFD TRANSFER FROM HOSPITAL HOME HEALTH CARE Medicaid ENGLISH SINGLE UNKNOWN 0
39 10021118 24490144 2161-11-15 20:10:00 2161-11-23 16:00:00 URGENT P96FGV TRANSFER FROM HOSPITAL HOME HEALTH CARE Other ENGLISH MARRIED WHITE 0
40 10006580 24159665 2137-08-10 11:00:00 2137-08-15 13:25:00 ELECTIVE P76K54 PHYSICIAN REFERRAL HOME Medicaid ? MARRIED HISPANIC/LATINO - SALVADORAN 0
41 10023771 20044587 2113-08-25 07:15:00 2113-08-30 14:15:00 ELECTIVE P47E1G PHYSICIAN REFERRAL SKILLED NURSING FACILITY Medicare ENGLISH MARRIED WHITE 0
42 10005348 29496232 2128-09-05 08:30:00 2128-09-12 16:55:00 ELECTIVE P786Y5 PHYSICIAN REFERRAL HOME HEALTH CARE Other ENGLISH MARRIED WHITE 0
43 10019172 24997044 2118-11-15 14:00:00 2118-11-21 18:31:00 ELECTIVE P41R5N PHYSICIAN REFERRAL HOME HEALTH CARE Other ENGLISH UNABLE TO OBTAIN 0
44 10019172 21540783 2118-10-08 14:00:00 2118-10-11 20:15:00 ELECTIVE P41R5N PHYSICIAN REFERRAL HOME Other ENGLISH UNABLE TO OBTAIN 0
45 10021487 20429160 2117-07-16 07:15:00 2117-07-25 12:34:00 ELECTIVE P96Y5O PHYSICIAN REFERRAL HOME Other ENGLISH DIVORCED WHITE 0
46 10013049 22675517 2114-06-20 10:15:00 2114-06-24 16:45:00 ELECTIVE P41R5N PHYSICIAN REFERRAL HOME HEALTH CARE Other ENGLISH MARRIED WHITE 0
47 10035631 20385771 2112-12-04 00:00:00 2112-12-27 16:24:00 ELECTIVE P45GUA PHYSICIAN REFERRAL HOME HEALTH CARE Other ENGLISH MARRIED WHITE 0
48 10035631 24912093 2112-10-22 00:00:00 2112-10-28 12:16:00 ELECTIVE P45GUA PHYSICIAN REFERRAL HOME HEALTH CARE Other ENGLISH MARRIED UNKNOWN 0
49 10019003 26226543 2155-10-17 18:01:00 2155-11-03 18:00:00 ELECTIVE P7280F PHYSICIAN REFERRAL HOME HEALTH CARE Other ENGLISH WIDOWED WHITE 0
50 10019003 28003918 2148-12-21 07:15:00 2148-12-24 17:10:00 ELECTIVE P80J89 PHYSICIAN REFERRAL HOME Medicare ENGLISH WIDOWED WHITE 0
51 10009035 28324362 2161-04-27 07:15:00 2161-05-01 13:45:00 ELECTIVE P41R5N PHYSICIAN REFERRAL HOME HEALTH CARE Other ENGLISH SINGLE WHITE 0
52 10040025 25933959 2147-12-29 19:36:00 2148-01-09 17:38:00 ELECTIVE P43BTJ PHYSICIAN REFERRAL SKILLED NURSING FACILITY Other ENGLISH DIVORCED WHITE 0
53 10014729 23300884 2125-03-19 16:58:00 2125-03-28 13:37:00 EW EMER. P76K54 EMERGENCY ROOM HOME HEALTH CARE Other ENGLISH SINGLE WHITE - OTHER EUROPEAN 2125-03-19 12:36:00 2125-03-19 18:45:00 0
54 10026255 20437651 2200-09-17 22:53:00 2200-09-29 18:25:00 EW EMER. P233F6 EMERGENCY ROOM HOME Other ENGLISH MARRIED WHITE 2200-09-17 18:38:00 2200-09-18 00:57:00 0
55 10026255 22059910 2201-07-07 18:15:00 2201-07-13 23:27:00 2201-07-13 23:27:00 EW EMER. P48KFD EMERGENCY ROOM DIED Other ENGLISH MARRIED WHITE 2201-07-07 12:31:00 2201-07-07 19:40:00 1
56 10007058 22954658 2167-11-07 19:05:00 2167-11-11 14:23:00 EW EMER. P76K54 EMERGENCY ROOM HOME Other ENGLISH MARRIED WHITE 2167-11-07 17:57:00 2167-11-07 20:22:00 0
57 10018845 21101111 2184-10-08 02:28:00 2184-10-11 17:00:00 EW EMER. P34SFE EMERGENCY ROOM REHAB Other ENGLISH MARRIED WHITE 2184-10-07 22:35:00 2184-10-08 04:09:00 0
58 10020640 27984218 2153-02-13 00:22:00 2153-02-20 13:52:00 EW EMER. P7554I EMERGENCY ROOM SKILLED NURSING FACILITY Other ENGLISH WIDOWED WHITE 2153-02-12 21:59:00 2153-02-13 01:38:00 0
59 10018081 25973915 2134-09-06 15:57:00 2134-09-18 15:50:00 EW EMER. P13ZRJ EMERGENCY ROOM CHRONIC/LONG TERM ACUTE CARE Medicare ENGLISH MARRIED WHITE 2134-09-05 16:14:00 2134-09-06 16:49:00 0
60 10018081 21027282 2133-12-18 16:58:00 2134-01-12 11:00:00 EW EMER. P19SWB EMERGENCY ROOM SKILLED NURSING FACILITY Medicare ENGLISH MARRIED WHITE 2133-12-18 12:09:00 2133-12-18 17:10:00 0
61 10021666 22756440 2172-03-12 23:47:00 2172-03-23 15:40:00 EW EMER. P99698 EMERGENCY ROOM CHRONIC/LONG TERM ACUTE CARE Medicare ENGLISH MARRIED WHITE 2172-03-12 20:52:00 2172-03-13 01:46:00 0
62 10015272 27993466 2137-06-12 18:36:00 2137-06-18 15:45:00 EW EMER. P3529J EMERGENCY ROOM HOME HEALTH CARE Medicare ENGLISH WHITE 2137-06-12 16:54:00 2137-06-12 20:37:00 0
63 10016810 20973395 2185-06-16 01:31:00 2185-06-21 15:55:00 EW EMER. P3638G EMERGENCY ROOM HOME Other ENGLISH UNKNOWN 2185-06-15 23:08:00 2185-06-16 02:16:00 0
64 10021938 23112364 2181-10-13 01:48:00 2181-10-14 17:40:00 EW EMER. P3529J EMERGENCY ROOM HOME Medicare ENGLISH SINGLE WHITE 2181-10-12 20:17:00 2181-10-13 02:52:00 0
65 10021938 27154822 2181-10-25 10:44:00 2181-10-27 15:30:00 EW EMER. P99BGS EMERGENCY ROOM HOME Medicare ENGLISH SINGLE WHITE 2181-10-25 09:23:00 2181-10-25 11:35:00 0
66 10001725 25563031 2110-04-11 15:08:00 2110-04-14 15:00:00 EW EMER. P35SU0 PACU HOME Other ENGLISH MARRIED WHITE 0
67 10001217 24597018 2157-11-18 22:56:00 2157-11-25 18:00:00 EW EMER. P4645A EMERGENCY ROOM HOME HEALTH CARE Other ? MARRIED WHITE 2157-11-18 17:38:00 2157-11-19 01:24:00 0
68 10023239 29295881 2137-06-19 17:35:00 2137-06-22 14:57:00 EW EMER. P44WVR EMERGENCY ROOM HOME Other ENGLISH SINGLE WHITE 2137-06-19 15:05:00 2137-06-19 19:09:00 0
69 10023239 21759936 2140-10-03 09:06:00 2140-10-08 15:28:00 EW EMER. P031HZ TRANSFER FROM HOSPITAL HOME Other ENGLISH MARRIED WHITE 2140-10-03 06:20:00 2140-10-03 11:04:00 0
70 10020944 29974575 2131-02-27 15:34:00 2131-03-13 17:01:00 EW EMER. P27588 EMERGENCY ROOM SKILLED NURSING FACILITY Medicare ENGLISH UNKNOWN 2131-02-27 13:16:00 2131-02-27 16:40:00 0
71 10027602 28166872 2201-10-30 12:05:00 2201-11-20 14:45:00 EW EMER. P47SIK EMERGENCY ROOM REHAB Other ENGLISH SINGLE WHITE 2201-10-30 10:48:00 2201-10-30 12:25:00 0
72 10032725 25177949 2143-02-17 14:20:00 2143-03-16 17:15:00 EW EMER. P39043 EMERGENCY ROOM CHRONIC/LONG TERM ACUTE CARE Other ENGLISH SINGLE BLACK/AFRICAN AMERICAN 2143-02-17 07:19:00 2143-02-17 15:18:00 0
73 10032725 20611640 2143-03-22 04:59:00 2143-03-25 13:00:00 EW EMER. P7554I EMERGENCY ROOM HOSPICE Other ENGLISH SINGLE BLACK/AFRICAN AMERICAN 2143-03-22 01:19:00 2143-03-22 06:42:00 0
74 10005348 25239799 2130-10-26 17:03:00 2130-11-02 16:00:00 EW EMER. P38RSS PROCEDURE SITE SKILLED NURSING FACILITY Other ENGLISH MARRIED WHITE 0
75 10037861 24540843 2117-03-14 16:34:00 2117-03-24 00:01:00 2117-03-24 23:08:00 EW EMER. P01LRQ EMERGENCY ROOM DIED Medicare ENGLISH MARRIED UNKNOWN 2117-03-14 15:19:00 2117-03-14 17:53:00 1
76 10010471 29842315 2155-12-02 19:36:00 2155-12-07 15:30:00 2155-12-07 15:30:00 EW EMER. P04X8Y EMERGENCY ROOM DIED Medicare ENGLISH WIDOWED WHITE 2155-12-02 16:00:00 2155-12-02 20:33:00 1
77 10005866 27167814 2148-03-10 16:16:00 2148-03-21 18:30:00 EW EMER. P28OI9 EMERGENCY ROOM SKILLED NURSING FACILITY Medicaid ENGLISH SINGLE PORTUGUESE 2148-03-10 04:46:00 2148-03-10 11:27:00 0
78 10005866 26134779 2149-09-13 07:36:00 2149-09-19 18:00:00 EW EMER. P95QHH EMERGENCY ROOM HOME Medicaid ENGLISH SINGLE PORTUGUESE 2149-09-12 15:31:00 2149-09-13 09:02:00 0
79 10005866 26158160 2146-06-06 00:50:00 2146-06-09 16:45:00 EW EMER. P94RT6 EMERGENCY ROOM HOME Medicaid ENGLISH SINGLE PORTUGUESE 2146-06-05 22:26:00 2146-06-06 01:45:00 0
80 10024043 24717014 2117-04-11 20:46:00 2117-04-16 18:55:00 EW EMER. P5487F EMERGENCY ROOM HOME HEALTH CARE Medicare ENGLISH SINGLE BLACK/AFRICAN AMERICAN 2117-04-11 17:23:00 2117-04-11 22:05:00 0
81 10021487 28998349 2116-12-03 00:23:00 2116-12-28 13:19:00 EW EMER. P61T4I EMERGENCY ROOM HOME Other ENGLISH MARRIED WHITE 2116-12-02 22:57:00 2116-12-03 01:02:00 0
82 10021487 21928381 2117-12-03 17:07:00 2117-12-06 17:30:00 EW EMER. P96Y5O PROCEDURE SITE HOME Other ENGLISH DIVORCED WHITE 0
83 10021487 26321862 2117-01-28 00:19:00 2117-02-05 15:40:00 EW EMER. P82B0E EMERGENCY ROOM HOME Other ENGLISH MARRIED WHITE 2117-01-27 18:19:00 2117-01-28 01:23:00 0
84 10021487 27112038 2117-10-25 22:22:00 2117-10-29 14:40:00 EW EMER. P233F6 EMERGENCY ROOM HOME Other ENGLISH DIVORCED WHITE 2117-10-25 15:49:00 2117-10-25 22:53:00 0
85 10015860 24698912 2192-05-12 07:42:00 2192-05-27 18:50:00 EW EMER. P03YMR PHYSICIAN REFERRAL SKILLED NURSING FACILITY Other ENGLISH SINGLE WHITE 2192-05-11 16:26:00 2192-05-12 09:31:00 0
86 10015860 28236161 2187-09-15 18:49:00 2187-09-19 14:50:00 EW EMER. P62A9I EMERGENCY ROOM HOME HEALTH CARE Other ENGLISH SINGLE WHITE 2187-09-15 14:14:00 2187-09-15 20:49:00 0
87 10015860 20790339 2189-05-23 02:14:00 2189-05-25 14:45:00 EW EMER. P00P3O EMERGENCY ROOM HOME HEALTH CARE Other ENGLISH SINGLE WHITE 2189-05-22 23:18:00 2189-05-23 03:38:00 0
88 10015860 25085565 2186-09-15 16:12:00 2186-09-29 18:05:00 EW EMER. P56SP9 EMERGENCY ROOM REHAB Other ENGLISH SINGLE WHITE 2186-09-15 12:56:00 2186-09-15 17:15:00 0
89 10015860 20854119 2188-08-06 00:49:00 2188-08-12 17:49:00 EW EMER. P29CGZ EMERGENCY ROOM HOME HEALTH CARE Other ENGLISH SINGLE WHITE 2188-08-05 20:36:00 2188-08-06 03:24:00 0
90 10018423 29366372 2167-05-03 21:24:00 2167-05-11 12:57:00 EW EMER. P204LI EMERGENCY ROOM HOME HEALTH CARE Other ENGLISH MARRIED WHITE 2167-05-03 15:18:00 2167-05-03 22:50:00 0
91 10007928 20338077 2129-04-05 22:56:00 2129-04-11 17:25:00 EW EMER. P825SO EMERGENCY ROOM HOME Other ENGLISH DIVORCED WHITE 2129-04-05 21:18:00 2129-04-06 00:25:00 0
92 10014078 25809882 2166-08-21 23:09:00 2166-08-26 14:48:00 EW EMER. P64JK6 EMERGENCY ROOM HOME HEALTH CARE Medicaid ENGLISH UNABLE TO OBTAIN 2166-08-21 21:39:00 2166-08-22 00:36:00 0
93 10020740 25826145 2150-06-03 20:12:00 2150-06-07 15:05:00 EW EMER. P80QIV PACU SKILLED NURSING FACILITY Other ENGLISH SINGLE WHITE 0
94 10008454 20291550 2110-11-30 06:31:00 2110-12-10 15:53:00 EW EMER. P77BSD EMERGENCY ROOM HOME HEALTH CARE Other ENGLISH SINGLE WHITE 2110-11-30 04:45:00 2110-11-30 08:03:00 0
95 10022281 29642388 2125-06-17 04:11:00 2125-06-19 15:25:00 EW EMER. P76K54 EMERGENCY ROOM HOME Other ENGLISH MARRIED OTHER 2125-06-16 20:32:00 2125-06-17 05:14:00 0
96 10014354 24357615 2150-05-09 16:09:00 2150-05-10 15:59:00 EW EMER. P15PLY PHYSICIAN REFERRAL HOME HEALTH CARE Other ENGLISH SINGLE WHITE 2150-05-08 22:30:00 2150-05-09 19:09:00 0
97 10014354 22741225 2146-10-08 23:47:00 2146-10-12 18:20:00 EW EMER. P5487F EMERGENCY ROOM HOME HEALTH CARE Other ENGLISH MARRIED WHITE 2146-10-08 21:27:00 2146-10-09 01:08:00 0
98 10014354 26013492 2147-11-14 22:12:00 2147-11-16 15:00:00 EW EMER. P424IO EMERGENCY ROOM HOME HEALTH CARE Other ENGLISH SINGLE WHITE 2147-11-14 18:22:00 2147-11-14 23:09:00 0
99 10014354 29757856 2150-04-10 02:40:00 2150-04-15 18:00:00 EW EMER. P08904 WALK-IN/SELF REFERRAL HOME HEALTH CARE Other ENGLISH SINGLE WHITE 2150-04-09 19:50:00 2150-04-10 03:46:00 0
100 10038933 25129047 2148-09-10 12:09:00 2148-09-23 12:18:00 EW EMER. P47KFL EMERGENCY ROOM REHAB Other ENGLISH SINGLE WHITE 2148-09-10 09:23:00 2148-09-10 13:19:00 0
101 10016150 29374560 2142-05-10 15:05:00 2142-05-15 19:49:00 EW EMER. P1037P PROCEDURE SITE REHAB Medicare ENGLISH SINGLE WHITE 0
102 10022041 28909879 2187-05-18 17:08:00 2187-05-23 17:20:00 EW EMER. P09IS0 EMERGENCY ROOM HOME HEALTH CARE Medicare ENGLISH MARRIED OTHER 2187-05-18 14:59:00 2187-05-18 18:39:00 0
103 10039708 20572787 2138-10-30 23:30:00 2138-11-06 23:30:00 EW EMER. P39SWK EMERGENCY ROOM AGAINST ADVICE Other ENGLISH SINGLE BLACK/AFRICAN AMERICAN 2138-10-30 17:27:00 2138-10-31 00:19:00 0
104 10039708 28258130 2140-01-23 16:19:00 2140-02-26 18:15:00 EW EMER. P44WVR EMERGENCY ROOM CHRONIC/LONG TERM ACUTE CARE Other ENGLISH SINGLE BLACK/AFRICAN AMERICAN 2140-01-23 12:35:00 2140-01-23 18:08:00 0
105 10000032 22841357 2180-06-26 18:27:00 2180-06-27 18:49:00 EW EMER. P09Q6Y EMERGENCY ROOM HOME Medicaid ENGLISH WIDOWED WHITE 2180-06-26 15:54:00 2180-06-26 21:31:00 0
106 10000032 25742920 2180-08-05 23:44:00 2180-08-07 17:50:00 EW EMER. P60CC5 EMERGENCY ROOM HOSPICE Medicaid ENGLISH WIDOWED WHITE 2180-08-05 20:58:00 2180-08-06 01:44:00 0
107 10000032 29079034 2180-07-23 12:35:00 2180-07-25 17:55:00 EW EMER. P30KEH EMERGENCY ROOM HOME Medicaid ENGLISH WIDOWED WHITE 2180-07-23 05:54:00 2180-07-23 14:00:00 0
108 10026406 25260176 2129-01-03 15:55:00 2129-01-05 14:10:00 EW EMER. P51MA2 EMERGENCY ROOM AGAINST ADVICE Other ENGLISH DIVORCED WHITE 2129-01-02 23:41:00 2129-01-03 18:33:00 0
109 10037928 22326517 2177-12-21 20:47:00 2177-12-23 17:45:00 EW EMER. P51MA2 EMERGENCY ROOM HOME HEALTH CARE Medicare ? WIDOWED HISPANIC/LATINO - CUBAN 2177-12-21 16:49:00 2177-12-21 22:26:00 0
110 10037928 22490490 2177-07-14 16:55:00 2177-07-24 13:33:00 EW EMER. P44WVR EMERGENCY ROOM HOME HEALTH CARE Medicare ? WIDOWED HISPANIC/LATINO - CUBAN 2177-07-14 14:52:00 2177-07-14 20:38:00 0
111 10037928 20192635 2177-09-04 12:05:00 2177-09-07 16:10:00 EW EMER. P072C5 EMERGENCY ROOM HOME HEALTH CARE Medicare ? WIDOWED HISPANIC/LATINO - CUBAN 2177-09-04 06:29:00 2177-09-04 14:44:00 0
112 10037928 29802992 2179-07-25 00:06:00 2179-07-28 15:54:00 EW EMER. P450WM EMERGENCY ROOM HOME HEALTH CARE Medicare ? WIDOWED HISPANIC/LATINO - CUBAN 2179-07-24 18:21:00 2179-07-25 01:17:00 0
113 10037928 23721604 2179-03-27 18:27:00 2179-04-04 19:40:00 EW EMER. P80GYA EMERGENCY ROOM HOME HEALTH CARE Medicare ? WIDOWED HISPANIC/LATINO - CUBAN 2179-03-27 14:15:00 2179-03-27 19:48:00 0
114 10037928 24225421 2178-09-28 23:05:00 2178-10-02 17:13:00 EW EMER. P30AMB EMERGENCY ROOM HOME Medicare ? WIDOWED HISPANIC/LATINO - CUBAN 2178-09-28 20:29:00 2178-09-29 00:43:00 0
115 10037928 24656677 2178-12-21 05:30:00 2178-12-26 18:35:00 EW EMER. P30SZF EMERGENCY ROOM HOME HEALTH CARE Medicare ? WIDOWED HISPANIC/LATINO - CUBAN 2178-12-21 03:17:00 2178-12-21 08:27:00 0
116 10029484 20764029 2160-11-08 04:16:00 2160-11-11 11:40:00 EW EMER. P38TI6 EMERGENCY ROOM HOME Other ENGLISH SINGLE WHITE 2160-11-07 19:15:00 2160-11-08 05:23:00 0
117 10027445 26275841 2142-07-31 00:32:00 2142-08-09 17:30:00 EW EMER. P3529J EMERGENCY ROOM HOME HEALTH CARE Other ENGLISH WIDOWED WHITE 2142-07-30 23:33:00 2142-07-31 01:41:00 0
118 10012853 22539296 2176-06-06 18:09:00 2176-06-08 18:30:00 EW EMER. P172D4 EMERGENCY ROOM HOME Medicare ENGLISH WIDOWED BLACK/AFRICAN AMERICAN 2176-06-06 13:33:00 2176-06-06 20:18:00 0
119 10012853 26369609 2175-04-05 15:36:00 2175-04-10 16:55:00 EW EMER. P623U2 EMERGENCY ROOM HOME Other ENGLISH WIDOWED BLACK/AFRICAN AMERICAN 2175-04-05 06:22:00 2175-04-05 17:10:00 0
120 10012853 27882036 2176-11-25 21:28:00 2176-12-03 15:24:00 EW EMER. P450WM EMERGENCY ROOM SKILLED NURSING FACILITY Medicare ENGLISH WIDOWED BLACK/AFRICAN AMERICAN 2176-11-25 18:34:00 2176-11-25 23:51:00 0
121 10019003 29279905 2153-03-27 23:25:00 2153-04-07 16:20:00 EW EMER. P60F78 EMERGENCY ROOM HOME HEALTH CARE Other ENGLISH WIDOWED WHITE 2153-03-27 21:28:00 2153-03-28 02:21:00 0
122 10019003 21457723 2155-07-10 17:48:00 2155-07-18 16:59:00 EW EMER. P44WVR TRANSFER FROM HOSPITAL HOME Other ENGLISH WIDOWED WHITE 2155-07-10 12:46:00 2155-07-10 19:03:00 0
123 10019003 27525946 2153-04-12 19:07:00 2153-04-20 17:09:00 EW EMER. P56B4N EMERGENCY ROOM HOME Other ENGLISH WIDOWED WHITE 2153-04-12 13:03:00 2153-04-12 21:40:00 0
124 10019003 26703331 2155-06-10 23:09:00 2155-06-15 16:30:00 EW EMER. P203BW TRANSFER FROM HOSPITAL HOME HEALTH CARE Other ENGLISH WIDOWED WHITE 2155-06-10 20:37:00 2155-06-11 01:53:00 0
125 10019777 27738145 2187-02-10 18:57:00 2187-02-27 13:22:00 EW EMER. P08BV8 EMERGENCY ROOM HOSPICE Other ENGLISH DIVORCED WHITE 2187-02-10 15:11:00 2187-02-10 20:34:00 0
126 10018501 28479513 2141-07-30 22:34:00 2141-08-05 18:06:00 EW EMER. P99698 EMERGENCY ROOM SKILLED NURSING FACILITY Medicare ENGLISH WHITE 2141-07-30 18:53:00 2141-07-31 00:01:00 0
127 10004422 21255400 2111-01-15 14:55:00 2111-01-25 15:00:00 EW EMER. P1037P PROCEDURE SITE HOME HEALTH CARE Medicare ENGLISH WIDOWED WHITE 0
128 10003400 27296885 2136-12-31 21:40:00 2137-01-03 17:05:00 EW EMER. P14622 EMERGENCY ROOM SKILLED NURSING FACILITY Medicare ENGLISH MARRIED BLACK/AFRICAN AMERICAN 2136-12-31 13:41:00 2137-01-01 00:35:00 0
129 10003400 29483621 2136-11-04 20:43:00 2136-11-12 17:40:00 EW EMER. P37QNF EMERGENCY ROOM SKILLED NURSING FACILITY Medicare ENGLISH MARRIED BLACK/AFRICAN AMERICAN 2136-11-04 16:08:00 2136-11-04 22:12:00 0
130 10003400 22390287 2137-02-07 19:42:00 2137-02-18 18:30:00 EW EMER. P42DF5 EMERGENCY ROOM SKILLED NURSING FACILITY Medicare ENGLISH MARRIED BLACK/AFRICAN AMERICAN 2137-02-07 13:06:00 2137-02-07 21:44:00 0
131 10003400 26090619 2134-06-06 02:25:00 2134-06-07 15:05:00 EW EMER. P77SO2 EMERGENCY ROOM HOME HEALTH CARE Medicare ENGLISH MARRIED BLACK/AFRICAN AMERICAN 2134-06-05 21:42:00 2134-06-06 03:44:00 0
132 10003400 26467376 2136-12-09 14:44:00 2136-12-15 16:00:00 EW EMER. P14622 EMERGENCY ROOM SKILLED NURSING FACILITY Medicare ENGLISH MARRIED BLACK/AFRICAN AMERICAN 2136-12-09 13:16:00 2136-12-09 18:45:00 0
133 10023117 28872262 2171-11-07 21:37:00 2171-11-22 15:30:00 EW EMER. P89ZCW EMERGENCY ROOM HOME HEALTH CARE Medicare ENGLISH WIDOWED WHITE 2171-11-07 17:40:00 2171-11-07 22:50:00 0
134 10023117 29858644 2173-04-16 22:15:00 2173-04-20 16:40:00 EW EMER. P23URY EMERGENCY ROOM HOME Medicare ENGLISH WIDOWED WHITE 2173-04-16 17:00:00 2173-04-16 23:18:00 0
135 10017492 27417763 2116-06-26 18:25:00 2116-07-05 08:05:00 2116-07-05 08:05:00 EW EMER. P3417E EMERGENCY ROOM DIED Medicaid ? SINGLE PATIENT DECLINED TO ANSWER 2116-06-26 14:29:00 2116-06-26 21:46:00 1
136 10017492 27672872 2114-03-19 20:05:00 2114-04-02 18:30:00 EW EMER. P68RKH EMERGENCY ROOM HOME HEALTH CARE Other ? SINGLE PATIENT DECLINED TO ANSWER 2114-03-19 15:57:00 2114-03-19 21:38:00 0
137 10038999 29026789 2132-05-17 23:32:00 2132-05-23 13:01:00 EW EMER. P64JOR TRANSFER FROM HOSPITAL REHAB Medicare ENGLISH SINGLE WHITE 2132-05-17 19:56:00 2132-05-18 01:36:00 0
138 10040025 25172300 2145-07-03 23:46:00 2145-07-05 13:57:00 EW EMER. P73Y1N EMERGENCY ROOM HOME Other ENGLISH DIVORCED WHITE 2145-07-03 20:02:00 2145-07-04 10:58:00 0
139 10040025 25384176 2145-10-19 23:48:00 2145-10-24 14:20:00 EW EMER. P1904G EMERGENCY ROOM HOME Other ENGLISH DIVORCED WHITE 2145-10-19 19:23:00 2145-10-20 00:54:00 0
140 10040025 27876215 2147-11-09 08:02:00 2147-11-14 18:53:00 EW EMER. P059D0 EMERGENCY ROOM SKILLED NURSING FACILITY Other ENGLISH DIVORCED WHITE 2147-11-09 04:17:00 2147-11-09 10:56:00 0
141 10040025 27553957 2145-07-24 19:00:00 2145-07-31 14:12:00 EW EMER. P38RSS EMERGENCY ROOM HOME Other ENGLISH DIVORCED WHITE 2145-07-24 11:30:00 2145-07-24 20:32:00 0
142 10009049 22995465 2174-05-26 08:21:00 2174-05-31 14:15:00 EW EMER. P3529J EMERGENCY ROOM HOME Other ENGLISH MARRIED WHITE 2174-05-26 04:20:00 2174-05-26 09:18:00 0
143 10016742 29281842 2178-07-03 21:13:00 2178-07-08 20:20:00 EW EMER. P031HZ EMERGENCY ROOM SKILLED NURSING FACILITY Medicaid ENGLISH SINGLE BLACK/AFRICAN AMERICAN 2178-07-03 17:39:00 2178-07-03 22:45:00 0
144 10004457 28723315 2141-08-12 16:02:00 2141-08-13 17:47:00 EW EMER. P46WR5 EMERGENCY ROOM HOME Medicare ENGLISH SINGLE WHITE 2141-08-12 12:08:00 2141-08-12 17:20:00 0
145 10029291 22205327 2123-02-20 01:59:00 2123-03-10 15:30:00 EW EMER. P44UQI EMERGENCY ROOM HOME HEALTH CARE Medicare ENGLISH DIVORCED WHITE 2123-02-19 23:57:00 2123-02-20 04:13:00 0
146 10004720 22081550 2186-11-12 18:01:00 2186-11-17 18:30:00 2186-11-17 18:30:00 EW EMER. P3529J INFORMATION NOT AVAILABLE DIED Medicare ENGLISH SINGLE WHITE 2186-11-12 16:09:00 2186-11-12 19:55:00 1
147 10019568 28710730 2120-01-30 21:07:00 2120-02-02 15:40:00 EW EMER. P80LEI EMERGENCY ROOM HOME Medicare ENGLISH SINGLE WHITE 2120-01-30 19:44:00 2120-01-30 22:51:00 0
148 10002930 25922998 2198-04-17 19:38:00 2198-04-22 16:02:00 EW EMER. P30KEH EMERGENCY ROOM PSYCH FACILITY Medicare ENGLISH SINGLE BLACK/AFRICAN AMERICAN 2198-04-17 11:42:00 2198-04-17 21:24:00 0
149 10002930 25696644 2196-04-14 12:25:00 2196-04-17 15:28:00 EW EMER. P43A1R EMERGENCY ROOM PSYCH FACILITY Medicare ENGLISH SINGLE BLACK/AFRICAN AMERICAN 2196-04-14 05:55:00 2196-04-14 13:40:00 0
150 10007795 22051341 2136-09-22 20:51:00 2136-09-24 14:20:00 EW EMER. P99LA7 EMERGENCY ROOM HOME Medicare ENGLISH SINGLE WHITE 2136-09-22 17:07:00 2136-09-22 22:30:00 0
151 10007795 20285402 2136-08-04 22:16:00 2136-08-11 19:20:00 EW EMER. P74QAJ EMERGENCY ROOM HOME HEALTH CARE Medicare ENGLISH SINGLE WHITE 2136-08-04 16:26:00 2136-08-05 00:17:00 0
152 10010867 22429197 2147-12-30 08:40:00 2148-01-11 17:55:00 EW EMER. P3417E EMERGENCY ROOM REHAB Other ENGLISH SINGLE WHITE - BRAZILIAN 2147-12-30 06:45:00 2147-12-30 09:33:00 0
153 10025463 24470193 2137-10-08 21:20:00 2137-10-09 15:30:00 2137-10-09 15:30:00 EW EMER. P01LRQ EMERGENCY ROOM DIED Other ENGLISH MARRIED WHITE 2137-10-08 18:16:00 2137-10-08 20:44:00 1
154 10002428 28662225 2156-04-12 14:16:00 2156-04-29 16:26:00 EW EMER. P64TOH EMERGENCY ROOM SKILLED NURSING FACILITY Medicare ENGLISH WIDOWED WHITE 2156-04-12 09:56:00 2156-04-12 17:11:00 0
155 10002428 20321825 2156-04-30 20:35:00 2156-05-03 16:36:00 EW EMER. P825SO EMERGENCY ROOM CHRONIC/LONG TERM ACUTE CARE Medicare ENGLISH WIDOWED WHITE 2156-04-30 18:30:00 2156-04-30 21:53:00 0
156 10002428 23473524 2156-05-11 14:49:00 2156-05-22 14:16:00 EW EMER. P3529J EMERGENCY ROOM CHRONIC/LONG TERM ACUTE CARE Medicare ENGLISH WIDOWED WHITE 2156-05-11 11:29:00 2156-05-11 16:53:00 0
157 10007818 22987108 2146-06-10 16:37:00 2146-07-12 00:00:00 2146-07-12 20:50:00 DIRECT EMER. P48GIG PHYSICIAN REFERRAL DIED Medicare ENGLISH MARRIED WHITE 1
158 10004235 22187210 2196-06-20 21:11:00 2196-06-22 13:30:00 DIRECT EMER. P98TAU PHYSICIAN REFERRAL HOME HEALTH CARE Medicaid ENGLISH SINGLE BLACK/CAPE VERDEAN 0
159 10001217 27703517 2157-12-18 16:58:00 2157-12-24 14:55:00 DIRECT EMER. P99698 PHYSICIAN REFERRAL HOME HEALTH CARE Other ? MARRIED WHITE 0
160 10021487 27660781 2117-03-03 15:59:00 2117-03-27 16:40:00 DIRECT EMER. P82B0E CLINIC REFERRAL HOME Other ENGLISH DIVORCED WHITE 0
161 10015860 28613200 2188-03-29 14:14:00 2188-04-02 16:30:00 DIRECT EMER. P62A9I CLINIC REFERRAL HOME HEALTH CARE Other ENGLISH SINGLE WHITE 0
162 10031757 28477280 2137-10-12 22:43:00 2137-10-24 17:30:00 DIRECT EMER. P94RT6 CLINIC REFERRAL HOSPICE Other ENGLISH DIVORCED WHITE 0
163 10014354 27562275 2148-07-18 02:31:00 2148-07-20 17:47:00 DIRECT EMER. P90LIK PHYSICIAN REFERRAL HOME HEALTH CARE Other ENGLISH SINGLE WHITE 2148-07-17 20:36:00 2148-07-18 04:46:00 0
164 10035631 27496788 2113-08-26 17:07:00 2113-08-29 15:18:00 DIRECT EMER. P45GUA CLINIC REFERRAL HOME Other ENGLISH MARRIED WHITE 0
165 10035631 29462354 2112-09-17 19:13:00 2112-10-17 01:41:00 DIRECT EMER. P45GUA PHYSICIAN REFERRAL HOME Other ENGLISH MARRIED UNKNOWN 2112-09-17 18:46:00 2112-09-17 19:50:00 0
166 10035631 22732862 2112-11-10 15:55:00 2112-11-20 16:20:00 DIRECT EMER. P11CH5 CLINIC REFERRAL HOME Other ENGLISH MARRIED UNKNOWN 0
167 10035631 21599196 2116-02-13 11:17:00 2116-02-15 17:09:00 DIRECT EMER. P15Q8N PHYSICIAN REFERRAL HOME Other ENGLISH MARRIED WHITE 0
168 10019003 26529390 2155-05-17 00:02:00 2155-05-19 18:27:00 DIRECT EMER. P73PF1 PHYSICIAN REFERRAL HOME Other ENGLISH WIDOWED WHITE 2155-05-17 21:03:00 2155-05-18 03:03:00 0
169 10023117 28887654 2174-12-16 13:25:00 2174-12-20 10:27:00 DIRECT EMER. P94CCF PHYSICIAN REFERRAL HOME Medicare ENGLISH WIDOWED WHITE 0
170 10002930 23688993 2193-08-05 11:45:00 2193-08-11 09:25:00 DIRECT EMER. P356OC PHYSICIAN REFERRAL HOME Medicare ENGLISH SINGLE BLACK/AFRICAN AMERICAN 2193-08-05 05:04:00 2193-08-05 12:00:00 0
171 10025463 27327816 2136-10-31 07:15:00 2136-11-02 16:50:00 DIRECT EMER. P38W5P PHYSICIAN REFERRAL HOME Other ENGLISH MARRIED WHITE 0
172 10023239 28984460 2140-10-22 22:04:00 2140-10-23 17:01:00 EU OBSERVATION P76VMW PHYSICIAN REFERRAL Other ENGLISH MARRIED WHITE 2140-10-22 18:05:00 2140-10-23 17:01:00 0
173 10024043 25561728 2117-02-03 20:10:00 2117-02-04 11:53:00 EU OBSERVATION P19QFH EMERGENCY ROOM Medicare ENGLISH SINGLE BLACK/AFRICAN AMERICAN 2117-02-03 17:13:00 2117-02-04 11:53:00 0
174 10015860 27670224 2187-12-14 15:48:00 2187-12-16 14:38:00 EU OBSERVATION P25XDB EMERGENCY ROOM Other ENGLISH SINGLE WHITE 2187-12-14 12:49:00 2187-12-14 17:48:00 0
175 10015860 22607171 2190-05-15 08:16:00 2190-05-16 15:44:00 EU OBSERVATION P73Y1N EMERGENCY ROOM Other ENGLISH SINGLE WHITE 2190-05-14 17:11:00 2190-05-15 10:00:00 0
176 10015860 22416954 2193-05-03 22:45:00 2193-05-05 02:13:00 EU OBSERVATION P21E4S TRANSFER FROM HOSPITAL Other ENGLISH SINGLE WHITE 2193-05-03 19:12:00 2193-05-05 02:13:00 0
177 10020740 23143086 2151-01-15 15:25:00 2151-01-16 02:38:00 EU OBSERVATION P607HY EMERGENCY ROOM Other ENGLISH SINGLE WHITE 2151-01-15 11:47:00 2151-01-16 02:38:00 0
178 10020740 23199774 2150-09-15 14:09:00 2150-09-15 17:09:00 EU OBSERVATION P30KYN EMERGENCY ROOM Other ENGLISH SINGLE WHITE 2150-09-15 09:31:00 2150-09-15 17:09:00 0
179 10014354 24096336 2149-06-19 22:54:00 2149-06-20 13:48:00 EU OBSERVATION P83K3D PHYSICIAN REFERRAL Other ENGLISH SINGLE WHITE 2149-06-19 17:01:00 2149-06-20 13:48:00 0
180 10014354 26722126 2146-11-09 01:53:00 2146-11-09 13:13:00 EU OBSERVATION P187JB EMERGENCY ROOM Other ENGLISH MARRIED WHITE 2146-11-08 21:24:00 2146-11-09 13:13:00 0
181 10014354 20900955 2149-03-04 23:14:00 2149-03-05 14:59:00 EU OBSERVATION P63AD6 WALK-IN/SELF REFERRAL Other ENGLISH SINGLE WHITE 2149-03-04 20:24:00 2149-03-05 14:59:00 0
182 10014354 22502504 2147-09-12 05:06:00 2147-09-12 19:00:00 EU OBSERVATION P04X8Y EMERGENCY ROOM Other ENGLISH SINGLE WHITE 2147-09-12 02:01:00 2147-09-12 06:37:00 0
183 10014354 26173805 2149-09-17 22:54:00 2149-09-18 10:45:00 EU OBSERVATION P57NY9 WALK-IN/SELF REFERRAL Other ENGLISH SINGLE WHITE 2149-09-17 09:08:00 2149-09-17 23:55:00 0
184 10014354 27494880 2147-06-04 00:42:00 2147-06-04 15:00:00 EU OBSERVATION P79SJ2 EMERGENCY ROOM Other ENGLISH SINGLE WHITE 2147-06-03 22:39:00 2147-06-04 09:54:00 0
185 10039708 27504040 2142-07-06 09:08:00 2142-07-07 16:40:00 EU OBSERVATION P94RT6 PHYSICIAN REFERRAL Other ENGLISH SINGLE BLACK/AFRICAN AMERICAN 2142-07-06 07:21:00 2142-07-06 08:27:00 0
186 10026406 25166559 2133-03-01 19:30:00 2133-03-04 17:05:00 EU OBSERVATION P42K8Q WALK-IN/SELF REFERRAL Other ENGLISH DIVORCED WHITE 2133-03-01 16:42:00 2133-03-04 17:05:00 0
187 10037928 22228639 2183-08-04 04:04:00 2183-08-04 16:07:00 EU OBSERVATION P850UN PHYSICIAN REFERRAL Medicare ? WIDOWED HISPANIC/LATINO - CUBAN 2183-08-03 19:27:00 2183-08-04 16:07:00 0
188 10037928 28543425 2175-10-26 01:31:00 2175-10-26 12:48:00 EU OBSERVATION P40IUG EMERGENCY ROOM Medicare ? SINGLE HISPANIC OR LATINO 2175-10-25 19:55:00 2175-10-26 12:48:00 0
189 10012853 22896692 2176-08-11 15:17:00 2176-08-11 17:35:00 EU OBSERVATION P76VMW EMERGENCY ROOM Medicare ENGLISH WIDOWED BLACK/AFRICAN AMERICAN 2176-08-11 08:36:00 2176-08-11 17:35:00 0
190 10039997 21390688 2135-11-07 02:42:00 2135-11-07 06:27:00 EU OBSERVATION P03N1K EMERGENCY ROOM Medicare ENGLISH WIDOWED BLACK/AFRICAN AMERICAN 2135-11-06 21:44:00 2135-11-07 06:27:00 0
191 10020306 26332470 2133-05-05 05:43:00 2133-05-05 15:37:00 EU OBSERVATION P45LON CLINIC REFERRAL Medicare ENGLISH WIDOWED BLACK/AFRICAN AMERICAN 2133-05-04 20:52:00 2133-05-05 15:37:00 0
192 10002930 25282382 2197-04-17 02:01:00 2197-04-17 09:48:00 EU OBSERVATION P33NK4 EMERGENCY ROOM Medicare ENGLISH SINGLE BLACK/AFRICAN AMERICAN 2197-04-16 22:57:00 2197-04-17 09:48:00 0
193 10002930 28697806 2200-06-05 05:43:00 2200-06-05 10:26:00 EU OBSERVATION P68U3G PHYSICIAN REFERRAL Medicare ENGLISH SINGLE BLACK/AFRICAN AMERICAN 2200-06-04 16:38:00 2200-06-05 10:26:00 0
194 10002930 20846853 2201-02-12 16:58:00 2201-02-13 11:11:00 EU OBSERVATION P2358X PHYSICIAN REFERRAL Medicare ENGLISH SINGLE BLACK/AFRICAN AMERICAN 2201-02-12 15:11:00 2201-02-13 11:11:00 0
195 10002930 22380825 2193-08-05 06:18:00 2193-08-05 11:44:00 EU OBSERVATION P38XXV EMERGENCY ROOM Medicare ENGLISH SINGLE BLACK/AFRICAN AMERICAN 2193-08-05 05:04:00 2193-08-05 12:00:00 0
196 10002930 23720373 2199-02-17 21:45:00 2199-02-19 13:38:00 EU OBSERVATION P59HPG EMERGENCY ROOM Medicare ENGLISH SINGLE BLACK/AFRICAN AMERICAN 2199-02-17 14:32:00 2199-02-19 13:38:00 0
197 10002930 20282368 2201-03-23 19:15:00 2201-03-26 14:24:00 EU OBSERVATION P850UN PHYSICIAN REFERRAL Medicare ENGLISH SINGLE BLACK/AFRICAN AMERICAN 2201-03-23 12:04:00 2201-03-26 14:24:00 0
198 10002930 28477649 2197-04-07 06:56:00 2197-04-08 19:37:00 EU OBSERVATION P11RCJ EMERGENCY ROOM Medicare ENGLISH SINGLE BLACK/AFRICAN AMERICAN 2197-04-07 03:20:00 2197-04-08 20:14:00 0
199 10002428 28676446 2157-07-16 04:09:00 2157-07-18 16:49:00 EU OBSERVATION P08S8F EMERGENCY ROOM Medicare ENGLISH WIDOWED WHITE 2157-07-16 01:50:00 2157-07-16 09:51:00 0
200 10002428 25797028 2155-07-14 19:15:00 2155-07-15 18:37:00 EU OBSERVATION P64TOH EMERGENCY ROOM Medicare ENGLISH WIDOWED WHITE 2155-07-14 16:58:00 2155-07-14 20:04:00 0
201 10002428 26549334 2160-07-15 23:37:00 2160-07-16 18:49:00 EU OBSERVATION P607HY EMERGENCY ROOM Medicare ENGLISH WIDOWED WHITE 2160-07-15 17:34:00 2160-07-16 18:49:00 0
202 10018328 26706939 2154-02-05 21:58:00 2154-02-09 15:15:00 OBSERVATION ADMIT P233F6 TRANSFER FROM HOSPITAL HOME HEALTH CARE Other ENGLISH DIVORCED WHITE 2154-02-05 17:09:00 2154-02-05 22:54:00 0
203 10005817 28661809 2135-01-03 21:54:00 2135-01-19 18:36:00 2135-01-19 18:36:00 OBSERVATION ADMIT P7554I TRANSFER FROM HOSPITAL DIED Medicare ENGLISH MARRIED WHITE 1
204 10019385 20611796 2180-03-04 01:16:00 2180-03-06 18:32:00 OBSERVATION ADMIT P95P90 EMERGENCY ROOM HOME HEALTH CARE Other ENGLISH MARRIED WHITE 2180-03-03 20:26:00 2180-03-04 02:24:00 0
205 10020187 24104168 2169-01-15 04:04:00 2169-01-24 17:20:00 OBSERVATION ADMIT P898NM EMERGENCY ROOM HOME HEALTH CARE Other ? MARRIED HISPANIC/LATINO - SALVADORAN 2169-01-14 23:26:00 2169-01-15 04:56:00 0
206 10010471 21322534 2155-05-08 17:05:00 2155-05-10 18:55:00 OBSERVATION ADMIT P89T1L EMERGENCY ROOM HOME Medicare ENGLISH WIDOWED WHITE 2155-05-08 13:48:00 2155-05-08 18:16:00 0
207 10005866 22589518 2149-02-11 21:49:00 2149-02-14 16:45:00 OBSERVATION ADMIT P874LG EMERGENCY ROOM HOME Medicaid ENGLISH SINGLE PORTUGUESE 2149-02-11 08:52:00 2149-02-11 23:09:00 0
208 10005866 21636229 2149-09-20 14:30:00 2149-09-26 15:05:00 OBSERVATION ADMIT P96Y5O EMERGENCY ROOM HOME Medicaid ENGLISH SINGLE PORTUGUESE 2149-09-20 05:50:00 2149-09-20 15:53:00 0
209 10005866 23514107 2149-06-20 19:27:00 2149-06-25 15:55:00 OBSERVATION ADMIT P28OI9 EMERGENCY ROOM HOME Medicaid ENGLISH SINGLE PORTUGUESE 2149-06-20 10:20:00 2149-06-20 20:59:00 0
210 10005866 20364112 2149-10-01 18:56:00 2149-10-25 18:50:00 OBSERVATION ADMIT P94RT6 PHYSICIAN REFERRAL SKILLED NURSING FACILITY Medicaid ENGLISH SINGLE PORTUGUESE 2149-10-01 02:07:00 2149-10-01 15:59:00 0
211 10015860 25103777 2192-07-31 16:05:00 2192-08-06 16:37:00 OBSERVATION ADMIT P104SU TRANSFER FROM SKILLED NURSING FACILITY SKILLED NURSING FACILITY Other ENGLISH SINGLE WHITE 2192-07-31 11:00:00 2192-07-31 17:10:00 0
212 10015860 22413744 2191-01-15 01:55:00 2191-01-30 17:09:00 OBSERVATION ADMIT P172D4 EMERGENCY ROOM SKILLED NURSING FACILITY Other ENGLISH SINGLE WHITE 2191-01-14 21:18:00 2191-01-15 03:20:00 0
213 10015860 28196804 2193-11-23 19:15:00 2193-11-27 21:58:00 OBSERVATION ADMIT P54UVQ WALK-IN/SELF REFERRAL SKILLED NURSING FACILITY Other ENGLISH SINGLE WHITE 2193-11-23 12:38:00 2193-11-23 20:59:00 0
214 10014354 22508257 2148-05-10 23:29:00 2148-05-20 14:30:00 OBSERVATION ADMIT P57B0F EMERGENCY ROOM HOME HEALTH CARE Other ENGLISH SINGLE WHITE 2148-05-10 20:25:00 2148-05-11 01:22:00 0
215 10014354 29600294 2148-08-14 22:57:00 2148-08-18 21:12:00 OBSERVATION ADMIT P723L9 PHYSICIAN REFERRAL AGAINST ADVICE Other ENGLISH SINGLE WHITE 2148-08-14 16:32:00 2148-08-15 00:27:00 0
216 10014354 26228185 2150-04-30 20:19:00 2150-05-07 14:10:00 OBSERVATION ADMIT P17YHV TRANSFER FROM HOSPITAL HOME HEALTH CARE Other ENGLISH SINGLE WHITE 2150-04-30 14:26:00 2150-04-30 21:54:00 0
217 10014354 24980601 2150-02-04 20:12:00 2150-02-08 14:10:00 OBSERVATION ADMIT P42VJP PHYSICIAN REFERRAL AGAINST ADVICE Other ENGLISH SINGLE WHITE 2150-02-04 14:50:00 2150-02-04 22:44:00 0
218 10014354 26486158 2148-08-22 15:18:00 2148-09-08 12:00:00 OBSERVATION ADMIT P17YHV CLINIC REFERRAL HOME HEALTH CARE Other ENGLISH SINGLE WHITE 0
219 10014354 29780751 2147-11-26 00:39:00 2147-11-30 16:54:00 OBSERVATION ADMIT P21X87 EMERGENCY ROOM HOME HEALTH CARE Other ENGLISH SINGLE WHITE 2147-11-25 19:39:00 2147-11-26 01:30:00 0
220 10014354 23132022 2148-06-24 15:22:00 2148-06-28 13:54:00 OBSERVATION ADMIT P259GB PHYSICIAN REFERRAL HOME Other ENGLISH SINGLE WHITE 2148-06-24 09:22:00 2148-06-24 16:12:00 0
221 10005909 20199380 2144-10-28 23:20:00 2144-11-02 15:23:00 OBSERVATION ADMIT P43BTJ EMERGENCY ROOM HOME Other ENGLISH MARRIED WHITE 2144-10-28 18:29:00 2144-10-29 00:10:00 0
222 10039708 26793610 2140-09-25 04:17:00 2140-09-26 17:40:00 OBSERVATION ADMIT P91LM4 EMERGENCY ROOM HOME Other ENGLISH SINGLE BLACK/AFRICAN AMERICAN 2140-09-24 21:17:00 2140-09-25 05:57:00 0
223 10039708 29488258 2144-01-19 12:07:00 2144-01-21 21:20:00 OBSERVATION ADMIT P8785Z WALK-IN/SELF REFERRAL HOME Other ENGLISH SINGLE BLACK/AFRICAN AMERICAN 2144-01-18 11:53:00 2144-01-19 13:59:00 0
224 10039708 24928679 2143-09-19 18:36:00 2143-09-22 23:00:00 OBSERVATION ADMIT P8785Z CLINIC REFERRAL HOME HEALTH CARE Other ENGLISH SINGLE BLACK/AFRICAN AMERICAN 2143-09-19 10:57:00 2143-09-19 20:57:00 0
225 10039708 23819016 2140-06-18 00:22:00 2140-06-22 17:40:00 OBSERVATION ADMIT P48KFD EMERGENCY ROOM HOME Other ENGLISH SINGLE BLACK/AFRICAN AMERICAN 2140-06-17 17:47:00 2140-06-18 01:41:00 0
226 10039708 20093566 2143-09-26 18:24:00 2143-09-30 20:00:00 OBSERVATION ADMIT P04ZFH CLINIC REFERRAL HOME HEALTH CARE Other ENGLISH SINGLE BLACK/AFRICAN AMERICAN 2143-09-26 08:16:00 2143-09-26 19:52:00 0
227 10037928 24885579 2182-04-29 04:29:00 2182-05-03 17:20:00 OBSERVATION ADMIT P3417E WALK-IN/SELF REFERRAL HOME Medicare ? WIDOWED HISPANIC/LATINO - CUBAN 2182-04-28 17:25:00 2182-04-29 07:49:00 0
228 10035631 21476294 2115-11-08 13:54:00 2115-12-08 17:31:00 OBSERVATION ADMIT P45GUA EMERGENCY ROOM HOME Other ENGLISH MARRIED WHITE 2115-11-08 12:02:00 2115-11-08 15:43:00 0
229 10035631 29276678 2116-02-27 20:55:00 2116-03-12 07:45:00 2116-03-12 07:45:00 OBSERVATION ADMIT P17YHV EMERGENCY ROOM DIED Other ENGLISH MARRIED WHITE 2116-02-27 15:33:00 2116-02-27 22:03:00 1
230 10015931 24420677 2176-12-16 23:31:00 2176-12-31 17:35:00 OBSERVATION ADMIT P49NLZ EMERGENCY ROOM HOME HEALTH CARE Medicare ENGLISH MARRIED WHITE 2176-12-16 18:24:00 2176-12-17 01:07:00 0
231 10015931 28157142 2176-11-14 18:02:00 2176-11-27 13:30:00 OBSERVATION ADMIT P20N5X EMERGENCY ROOM SKILLED NURSING FACILITY Medicare ENGLISH MARRIED WHITE 2176-11-14 02:27:00 2176-11-14 19:51:00 0
232 10019003 25508812 2155-05-22 21:46:00 2155-05-30 03:30:00 OBSERVATION ADMIT P88A32 PHYSICIAN REFERRAL HOME Other ENGLISH WIDOWED WHITE 0
233 10039997 22484749 2137-03-15 22:08:00 2137-03-18 16:34:00 OBSERVATION ADMIT P70N8P PHYSICIAN REFERRAL REHAB Medicare ENGLISH WIDOWED BLACK/AFRICAN AMERICAN 2137-03-15 15:27:00 2137-03-16 00:11:00 0
234 10023117 21607814 2175-07-06 15:57:00 2175-07-20 00:00:00 2175-07-20 22:50:00 OBSERVATION ADMIT P49NLZ EMERGENCY ROOM DIED Medicare ENGLISH WIDOWED WHITE 2175-07-06 14:02:00 2175-07-06 17:41:00 1
235 10023117 21133938 2175-03-20 23:29:00 2175-03-29 16:00:00 OBSERVATION ADMIT P66Z67 TRANSFER FROM HOSPITAL HOME Medicare ENGLISH WIDOWED WHITE 0
236 10020306 23052851 2135-01-15 20:55:00 2135-02-07 17:50:00 OBSERVATION ADMIT P54UVQ PHYSICIAN REFERRAL SKILLED NURSING FACILITY Other ENGLISH WIDOWED BLACK/AFRICAN AMERICAN 2135-01-15 16:12:00 2135-01-15 22:38:00 0
237 10038999 27189241 2131-05-22 21:49:00 2131-06-04 13:43:00 OBSERVATION ADMIT P44UQI EMERGENCY ROOM HOME Medicare ENGLISH SINGLE WHITE 2131-05-22 20:33:00 2131-05-22 21:28:00 0
238 10040025 21791856 2147-06-16 22:00:00 2147-06-22 16:15:00 OBSERVATION ADMIT P7634X EMERGENCY ROOM HOME HEALTH CARE Other ENGLISH DIVORCED WHITE 2147-06-16 18:58:00 2147-06-16 23:06:00 0
239 10040025 22251969 2147-08-03 02:58:00 2147-08-06 16:50:00 OBSERVATION ADMIT P85BWS EMERGENCY ROOM HOME Other ENGLISH DIVORCED WHITE 2147-08-02 18:14:00 2147-08-03 02:41:00 0
240 10040025 27259207 2147-12-04 20:48:00 2147-12-18 16:43:00 OBSERVATION ADMIT P43BTJ EMERGENCY ROOM SKILLED NURSING FACILITY Other ENGLISH DIVORCED WHITE 2147-12-04 13:11:00 2147-12-05 02:28:00 0
241 10040025 27996267 2148-01-23 12:18:00 2148-02-04 20:51:00 OBSERVATION ADMIT P10WWR TRANSFER FROM SKILLED NURSING FACILITY HOSPICE Other ENGLISH DIVORCED WHITE 2148-01-22 14:47:00 2148-01-23 09:42:00 0
242 10016742 28506150 2178-07-13 05:40:00 2178-07-16 02:00:00 OBSERVATION ADMIT P031HZ EMERGENCY ROOM SKILLED NURSING FACILITY Medicaid ENGLISH SINGLE BLACK/AFRICAN AMERICAN 2178-07-13 04:37:00 2178-07-13 08:16:00 0
243 10016742 27568122 2178-07-22 07:19:00 2178-07-25 16:30:00 OBSERVATION ADMIT P513EK EMERGENCY ROOM CHRONIC/LONG TERM ACUTE CARE Medicaid ENGLISH SINGLE BLACK/AFRICAN AMERICAN 2178-07-22 05:41:00 2178-07-22 08:19:00 0
244 10010867 22196214 2148-03-07 23:30:00 2148-03-13 15:50:00 OBSERVATION ADMIT P233F6 EMERGENCY ROOM REHAB Other ENGLISH DIVORCED WHITE - BRAZILIAN 2148-03-07 18:21:00 2148-03-08 00:50:00 0
245 10010867 22950920 2148-01-25 22:58:00 2148-01-30 11:23:00 OBSERVATION ADMIT P39FGY EMERGENCY ROOM REHAB Other ENGLISH DIVORCED WHITE - BRAZILIAN 2148-01-25 18:46:00 2148-01-26 01:27:00 0
246 10002428 28295257 2160-04-14 12:30:00 2160-04-18 16:00:00 OBSERVATION ADMIT P18I28 EMERGENCY ROOM SKILLED NURSING FACILITY Medicare ENGLISH WIDOWED WHITE 2160-04-14 09:01:00 2160-04-14 14:28:00 0
247 10005348 29176490 2129-05-22 16:00:00 2129-05-23 11:30:00 DIRECT OBSERVATION P132L1 PHYSICIAN REFERRAL Other ENGLISH MARRIED WHITE 0
248 10020740 29820177 2150-07-09 22:09:00 2150-07-12 18:00:00 DIRECT OBSERVATION P1942H CLINIC REFERRAL Other ENGLISH SINGLE WHITE 0
249 10039708 22999601 2142-05-15 17:14:00 2142-05-15 18:21:00 DIRECT OBSERVATION P42K8Q PHYSICIAN REFERRAL Other ENGLISH SINGLE BLACK/AFRICAN AMERICAN 2142-05-15 10:47:00 2142-05-15 18:21:00 0
250 10012853 20457729 2177-11-03 09:30:00 2177-11-04 15:06:00 DIRECT OBSERVATION P05BRR PHYSICIAN REFERRAL Medicare ENGLISH WIDOWED BLACK/AFRICAN AMERICAN 0
251 10012853 26739864 2177-02-11 07:15:00 2177-02-12 16:20:00 DIRECT OBSERVATION P05BRR PHYSICIAN REFERRAL Medicare ENGLISH WIDOWED BLACK/AFRICAN AMERICAN 0
252 10004457 25559382 2148-09-14 14:19:00 2148-09-15 12:45:00 DIRECT OBSERVATION P466EI PHYSICIAN REFERRAL Medicare ENGLISH DIVORCED WHITE 0
253 10007795 27962747 2136-07-24 17:15:00 2136-07-27 14:27:00 DIRECT OBSERVATION P99LA7 PHYSICIAN REFERRAL Medicare ENGLISH SINGLE WHITE 0
254 10035631 29654498 2113-07-17 17:15:00 2113-07-18 14:55:00 AMBULATORY OBSERVATION P48P6U PACU Other ENGLISH MARRIED WHITE 0
255 10020306 28778757 2129-10-29 08:00:00 2129-10-30 13:20:00 AMBULATORY OBSERVATION P75JMU PACU Medicare ENGLISH SINGLE BLACK/AFRICAN AMERICAN 0
256 10040025 27125816 2143-03-18 12:34:00 2143-03-19 12:00:00 AMBULATORY OBSERVATION P623U2 PROCEDURE SITE Other ENGLISH DIVORCED WHITE 0
257 10004457 21039249 2140-09-17 13:41:00 2140-09-18 12:40:00 AMBULATORY OBSERVATION P99YFT PROCEDURE SITE Medicare ENGLISH SINGLE WHITE 0
258 10004457 21216581 2143-03-09 11:10:00 2143-03-10 11:35:00 AMBULATORY OBSERVATION P89ZCW PROCEDURE SITE Medicare ENGLISH DIVORCED WHITE 0
259 10011398 27505812 2146-12-15 07:15:00 2146-12-19 13:37:00 SURGICAL SAME DAY ADMISSION P47E1G PHYSICIAN REFERRAL HOME HEALTH CARE Other ENGLISH MARRIED HISPANIC OR LATINO 0
260 10014729 28889419 2125-02-27 07:15:00 2125-03-06 14:25:00 SURGICAL SAME DAY ADMISSION P17BJ5 PHYSICIAN REFERRAL HOME HEALTH CARE Other ENGLISH SINGLE WHITE - OTHER EUROPEAN 0
261 10004235 25970245 2196-06-14 08:30:00 2196-06-19 14:54:00 SURGICAL SAME DAY ADMISSION P96Y5O PHYSICIAN REFERRAL HOME HEALTH CARE Medicaid ENGLISH SINGLE BLACK/CAPE VERDEAN 0
262 10039831 26924951 2115-12-28 07:15:00 2116-01-02 14:34:00 SURGICAL SAME DAY ADMISSION P82B0E PHYSICIAN REFERRAL HOME HEALTH CARE Other ENGLISH MARRIED UNABLE TO OBTAIN 0
263 10018328 23786647 2154-04-24 03:15:00 2154-05-03 14:00:00 SURGICAL SAME DAY ADMISSION P898NM PHYSICIAN REFERRAL HOME HEALTH CARE Other ENGLISH DIVORCED WHITE 0
264 10018081 28861356 2134-08-02 07:15:00 2134-08-13 17:34:00 SURGICAL SAME DAY ADMISSION P3417E PHYSICIAN REFERRAL SKILLED NURSING FACILITY Medicare ENGLISH MARRIED WHITE 0
265 10027602 21477991 2201-12-11 12:00:00 2201-12-17 13:45:00 SURGICAL SAME DAY ADMISSION P898NM PHYSICIAN REFERRAL REHAB Other ENGLISH SINGLE WHITE 0
266 10020187 26842957 2170-02-24 00:00:00 2170-02-25 15:00:00 SURGICAL SAME DAY ADMISSION P898NM PHYSICIAN REFERRAL HOME Other ? MARRIED HISPANIC/LATINO - SALVADORAN 0
267 10003046 26048429 2154-01-02 07:15:00 2154-01-09 11:53:00 SURGICAL SAME DAY ADMISSION P2720E PHYSICIAN REFERRAL HOME HEALTH CARE Other ENGLISH SINGLE WHITE 0
268 10022017 22342963 2189-09-10 00:00:00 2189-09-16 15:00:00 SURGICAL SAME DAY ADMISSION P47E1G PHYSICIAN REFERRAL HOME HEALTH CARE Other ENGLISH SINGLE WHITE 0
269 10025612 23403708 2125-09-25 07:15:00 2125-10-03 12:24:00 SURGICAL SAME DAY ADMISSION P786Y5 PHYSICIAN REFERRAL HOME HEALTH CARE Medicare ENGLISH MARRIED WHITE 0
270 10039997 24294180 2134-09-07 12:00:00 2134-09-11 13:10:00 SURGICAL SAME DAY ADMISSION P47SIK PHYSICIAN REFERRAL HOME Medicare ENGLISH MARRIED BLACK/AFRICAN AMERICAN 0
271 10023117 29839885 2170-10-08 07:15:00 2170-10-09 16:30:00 SURGICAL SAME DAY ADMISSION P89ZCW PHYSICIAN REFERRAL HOME HEALTH CARE Medicare ENGLISH WIDOWED WHITE 0
272 10038992 24745425 2187-07-29 01:05:00 2187-08-03 17:02:00 SURGICAL SAME DAY ADMISSION P41R5N PHYSICIAN REFERRAL SKILLED NURSING FACILITY Medicare ENGLISH MARRIED WHITE 0
273 10008287 22168393 2145-09-28 01:17:00 2145-10-02 13:35:00 SURGICAL SAME DAY ADMISSION P898NM PHYSICIAN REFERRAL HOME HEALTH CARE Other ENGLISH SINGLE WHITE 0
274 10022880 27708593 2177-03-12 07:15:00 2177-03-19 14:25:00 SURGICAL SAME DAY ADMISSION P99698 PHYSICIAN REFERRAL HOME Medicare ENGLISH MARRIED WHITE 0
275 10004457 23251352 2141-12-17 11:00:00 2141-12-21 15:56:00 SURGICAL SAME DAY ADMISSION P41R5N PHYSICIAN REFERRAL REHAB Medicare ENGLISH SINGLE OTHER 0
276 10004457 28108313 2147-12-19 00:00:00 2147-12-21 16:10:00 SURGICAL SAME DAY ADMISSION P10WWR PHYSICIAN REFERRAL SKILLED NURSING FACILITY Medicare ENGLISH DIVORCED WHITE 0
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+35836
View File
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+141
View File
@@ -0,0 +1,141 @@
subject_id,hadm_id,stay_id,first_careunit,last_careunit,intime,outtime,los
10018328,23786647,31269608,Neuro Stepdown,Neuro Stepdown,2154-04-24 23:03:44,2154-05-02 15:55:21,7.7025115740740739
10020187,24104168,37509585,Neuro Surgical Intensive Care Unit (Neuro SICU),Neuro Stepdown,2169-01-15 04:56:00,2169-01-20 15:47:50,5.4526620370370367
10020187,26842957,32554129,Neuro Intermediate,Neuro Intermediate,2170-02-24 18:18:46,2170-02-25 15:15:26,0.87268518518518523
10012853,27882036,31338022,Trauma SICU (TSICU),Trauma SICU (TSICU),2176-11-26 02:34:49,2176-11-29 20:58:54,3.766724537037037
10020740,25826145,32145159,Trauma SICU (TSICU),Trauma SICU (TSICU),2150-06-03 20:12:32,2150-06-04 21:05:58,1.0371064814814817
10039708,23819016,38559363,Trauma SICU (TSICU),Trauma SICU (TSICU),2140-06-18 01:41:00,2140-06-19 21:47:16,1.8376851851851852
10020306,23052851,38540883,Trauma SICU (TSICU),Trauma SICU (TSICU),2135-01-21 17:01:57,2135-01-24 21:47:45,3.1984722222222222
10019568,28710730,30876334,Trauma SICU (TSICU),Trauma SICU (TSICU),2120-01-30 22:51:00,2120-01-31 18:25:12,0.81541666666666668
10018081,28861356,38333427,Trauma SICU (TSICU),Trauma SICU (TSICU),2134-08-05 14:53:33,2134-08-07 17:32:43,2.1105324074074074
10018081,21027282,37293400,Trauma SICU (TSICU),Trauma SICU (TSICU),2133-12-18 17:10:00,2134-01-01 14:44:53,13.899224537037037
10010867,22429197,39880770,Trauma SICU (TSICU),Trauma SICU (TSICU),2147-12-30 09:33:00,2148-01-08 18:14:21,9.3620486111111116
10021487,28998349,38197705,Trauma SICU (TSICU),Trauma SICU (TSICU),2116-12-03 01:02:00,2116-12-18 17:34:03,15.688923611111109
10003046,26048429,35514836,Trauma SICU (TSICU),Trauma SICU (TSICU),2154-01-02 15:57:15,2154-01-04 15:19:56,1.9740856481481481
10017492,27417763,36035031,Trauma SICU (TSICU),Trauma SICU (TSICU),2116-06-27 17:35:34,2116-06-27 20:26:18,0.1185648148148148
10017492,27417763,39543480,Trauma SICU (TSICU),Trauma SICU (TSICU),2116-06-26 20:35:09,2116-06-27 15:44:27,0.79812499999999986
10018501,28479513,35446858,Trauma SICU (TSICU),Trauma SICU (TSICU),2141-07-31 00:01:00,2141-08-01 22:36:21,1.9412152777777776
10008454,20291550,31959184,Trauma SICU (TSICU),Trauma SICU (TSICU),2110-11-30 17:11:36,2110-12-05 16:48:24,4.983888888888889
10026354,24547356,36091287,Trauma SICU (TSICU),Trauma SICU (TSICU),2119-10-26 08:33:32,2119-10-27 17:50:50,1.387013888888889
10029291,22205327,36059427,Coronary Care Unit (CCU),Coronary Care Unit (CCU),2123-02-20 04:13:00,2123-02-26 12:03:56,6.3270370370370363
10027445,29163082,36084484,Coronary Care Unit (CCU),Coronary Care Unit (CCU),2142-08-30 13:20:56,2142-08-31 18:19:36,1.2074074074074075
10010471,29842315,32119961,Coronary Care Unit (CCU),Coronary Care Unit (CCU),2155-12-02 20:33:00,2155-12-07 18:19:18,4.9071527777777781
10023117,21607814,30955999,Coronary Care Unit (CCU),Coronary Care Unit (CCU),2175-07-06 17:41:00,2175-07-19 22:50:08,13.214675925925926
10002495,24982426,36753294,Coronary Care Unit (CCU),Coronary Care Unit (CCU),2141-05-22 20:18:01,2141-05-27 22:24:02,5.0875115740740737
10031404,21606243,35544374,Coronary Care Unit (CCU),Coronary Care Unit (CCU),2113-08-04 18:47:42,2113-08-05 23:45:02,1.2064814814814815
10017492,27417763,36871784,Coronary Care Unit (CCU),Coronary Care Unit (CCU),2116-07-05 06:50:16,2116-07-05 13:06:59,0.26160879629629624
10026255,22059910,31248398,Coronary Care Unit (CCU),Coronary Care Unit (CCU),2201-07-07 19:40:00,2201-07-08 15:43:15,0.83559027777777783
10015931,28157142,39544395,Coronary Care Unit (CCU),Coronary Care Unit (CCU),2176-11-19 14:24:52,2176-11-20 19:18:13,1.2037152777777778
10038999,27189241,39711498,Coronary Care Unit (CCU),Coronary Care Unit (CCU),2131-05-22 21:50:33,2131-05-31 17:55:04,8.8364699074074071
10029291,22205327,35146796,Coronary Care Unit (CCU),Coronary Care Unit (CCU),2123-02-26 12:12:32,2123-03-04 23:36:14,6.4747916666666674
10023117,21133938,38554095,Medical Intensive Care Unit (MICU),Coronary Care Unit (CCU),2175-03-21 03:20:53,2175-03-27 17:52:59,6.605625
10023117,28872262,30057454,Cardiac Vascular Intensive Care Unit (CVICU),Coronary Care Unit (CCU),2171-11-14 10:06:41,2171-11-18 20:49:43,4.4465509259259264
10014354,27487226,38017367,Neuro Surgical Intensive Care Unit (Neuro SICU),Coronary Care Unit (CCU),2148-07-07 15:48:09,2148-07-10 18:25:51,3.1095138888888889
10003400,23559586,38383343,Coronary Care Unit (CCU),Medical Intensive Care Unit (MICU),2137-08-17 17:36:37,2137-09-02 19:17:11,16.069837962962961
10004235,24181354,34100191,Coronary Care Unit (CCU),Medical Intensive Care Unit (MICU),2196-02-24 17:07:00,2196-02-29 15:58:02,4.9521064814814819
10002930,25696644,37049133,Medical Intensive Care Unit (MICU),Medical Intensive Care Unit (MICU),2196-04-14 13:40:00,2196-04-15 16:54:44,1.1352314814814815
10002428,20321825,34807493,Medical Intensive Care Unit (MICU),Medical Intensive Care Unit (MICU),2156-04-30 21:53:00,2156-05-02 22:27:20,2.0238425925925925
10007928,20338077,35128235,Medical Intensive Care Unit (MICU),Medical Intensive Care Unit (MICU),2129-04-06 00:25:00,2129-04-08 21:02:55,2.8596643518518516
10020944,29974575,30757476,Medical Intensive Care Unit (MICU),Medical Intensive Care Unit (MICU),2131-02-27 16:40:00,2131-03-08 18:30:38,9.0768287037037041
10002428,28662225,38875437,Medical Intensive Care Unit (MICU),Medical Intensive Care Unit (MICU),2156-04-19 18:11:19,2156-04-26 18:58:41,7.0328935185185193
10026406,25260176,30864406,Medical Intensive Care Unit (MICU),Medical Intensive Care Unit (MICU),2129-01-05 02:37:19,2129-01-05 14:11:03,0.48175925925925928
10020740,23831430,35889503,Medical Intensive Care Unit (MICU),Medical Intensive Care Unit (MICU),2150-03-25 18:22:56,2150-03-28 22:20:47,3.1651736111111113
10036156,28019404,38587181,Medical Intensive Care Unit (MICU),Medical Intensive Care Unit (MICU),2157-07-01 04:52:37,2157-07-02 14:18:55,1.3932638888888889
10006053,22942076,34617352,Medical Intensive Care Unit (MICU),Medical Intensive Care Unit (MICU),2111-11-14 00:19:12,2111-11-15 18:21:10,1.7513657407407408
10006053,22942076,32895909,Medical Intensive Care Unit (MICU),Medical Intensive Care Unit (MICU),2111-11-13 23:40:00,2111-11-14 00:14:10,0.02372685185185185
10000032,29079034,39553978,Medical Intensive Care Unit (MICU),Medical Intensive Care Unit (MICU),2180-07-23 14:00:00,2180-07-23 23:50:47,0.4102662037037037
10019777,27738145,34578020,Medical Intensive Care Unit (MICU),Medical Intensive Care Unit (MICU),2187-02-10 20:34:00,2187-02-17 23:38:57,7.1284375
10021312,28829452,37507305,Medical Intensive Care Unit (MICU),Medical Intensive Care Unit (MICU),2113-09-14 10:19:17,2113-09-15 12:43:00,1.0998032407407408
10007818,22987108,32359580,Medical Intensive Care Unit (MICU),Medical Intensive Care Unit (MICU),2146-06-22 11:46:29,2146-07-13 00:27:47,20.528680555555557
10020740,23831430,31077365,Medical Intensive Care Unit (MICU),Medical Intensive Care Unit (MICU),2150-03-30 07:57:10,2150-04-04 10:58:43,5.1260763888888894
10021938,23112364,39492446,Medical Intensive Care Unit (MICU),Medical Intensive Care Unit (MICU),2181-10-13 02:52:00,2181-10-14 18:03:28,1.6329629629629629
10016742,28506150,32314488,Medical Intensive Care Unit (MICU),Medical Intensive Care Unit (MICU),2178-07-13 08:16:00,2178-07-16 14:41:31,3.2677199074074075
10019917,22585261,34324099,Medical Intensive Care Unit (MICU),Medical Intensive Care Unit (MICU),2182-01-07 23:26:32,2182-01-09 07:09:31,1.3215162037037038
10037975,27617929,39061571,Medical Intensive Care Unit (MICU),Medical Intensive Care Unit (MICU),2185-01-17 19:12:12,2185-01-22 16:16:52,4.8782407407407407
10002428,28662225,33987268,Medical Intensive Care Unit (MICU),Medical Intensive Care Unit (MICU),2156-04-12 16:24:18,2156-04-17 15:57:08,4.9811342592592593
10015931,24420677,38137964,Medical Intensive Care Unit (MICU),Medical Intensive Care Unit (MICU),2176-12-22 16:24:33,2176-12-23 20:14:53,1.1599537037037038
10021938,27154822,33083787,Medical Intensive Care Unit (MICU),Medical Intensive Care Unit (MICU),2181-10-25 11:35:00,2181-10-26 20:53:57,1.3881597222222224
10027445,26275841,34499716,Medical Intensive Care Unit (MICU),Medical Intensive Care Unit (MICU),2142-07-31 01:41:00,2142-08-03 21:05:58,3.8090046296296296
10015860,25085565,32496174,Medical Intensive Care Unit (MICU),Medical Intensive Care Unit (MICU),2186-09-15 17:15:00,2186-09-16 11:17:40,0.75185185185185188
10016742,27568122,30425410,Medical Intensive Care Unit (MICU),Medical Intensive Care Unit (MICU),2178-07-22 08:19:00,2178-07-25 16:42:43,3.3498032407407403
10029484,20764029,35396193,Medical Intensive Care Unit (MICU),Medical Intensive Care Unit (MICU),2160-11-08 05:23:00,2160-11-08 21:04:55,0.65410879629629626
10038081,20755971,38430513,Medical Intensive Care Unit (MICU),Medical Intensive Care Unit (MICU),2115-10-09 10:15:25,2115-10-13 03:01:17,3.6985185185185188
10004720,22081550,35009126,Surgical Intensive Care Unit (SICU),Medical Intensive Care Unit (MICU),2186-11-12 19:55:00,2186-11-17 21:15:55,5.05619212962963
10002428,23473524,35479615,Surgical Intensive Care Unit (SICU),Medical Intensive Care Unit (MICU),2156-05-11 14:49:34,2156-05-22 14:16:46,10.977222222222222
10005866,20364112,34170353,Trauma SICU (TSICU),Surgical Intensive Care Unit (SICU),2149-10-02 12:48:08,2149-10-04 17:48:36,2.2086574074074075
10026255,22059910,38229329,Surgical Intensive Care Unit (SICU),Surgical Intensive Care Unit (SICU),2201-07-10 10:10:47,2201-07-11 13:48:02,1.1508680555555555
10008287,22168393,33348260,Surgical Intensive Care Unit (SICU),Surgical Intensive Care Unit (SICU),2145-09-28 20:59:43,2145-09-30 00:34:15,1.1489814814814816
10001217,24597018,37067082,Surgical Intensive Care Unit (SICU),Surgical Intensive Care Unit (SICU),2157-11-20 19:18:02,2157-11-21 22:08:00,1.1180324074074075
10022041,28909879,30913302,Surgical Intensive Care Unit (SICU),Surgical Intensive Care Unit (SICU),2187-05-18 18:39:00,2187-05-20 16:04:02,1.8923842592592592
10016810,20973395,35436337,Surgical Intensive Care Unit (SICU),Surgical Intensive Care Unit (SICU),2185-06-16 02:16:00,2185-06-18 14:00:02,2.488912037037037
10024043,24717014,32374504,Surgical Intensive Care Unit (SICU),Surgical Intensive Care Unit (SICU),2117-04-11 22:05:00,2117-04-14 14:36:11,2.6883217592592596
10018845,21101111,36427705,Surgical Intensive Care Unit (SICU),Surgical Intensive Care Unit (SICU),2184-10-08 04:09:00,2184-10-09 15:55:53,1.4908912037037034
10014078,25809882,38907302,Surgical Intensive Care Unit (SICU),Surgical Intensive Care Unit (SICU),2166-08-22 00:36:00,2166-08-24 13:12:44,2.5255092592592594
10016150,29374560,33652203,Surgical Intensive Care Unit (SICU),Surgical Intensive Care Unit (SICU),2142-05-10 16:39:15,2142-05-10 20:23:49,0.15594907407407407
10021487,27660781,35065627,Surgical Intensive Care Unit (SICU),Surgical Intensive Care Unit (SICU),2117-03-07 23:06:21,2117-03-09 18:01:57,1.7886111111111109
10015860,24698912,36734659,Surgical Intensive Care Unit (SICU),Surgical Intensive Care Unit (SICU),2192-05-12 09:31:00,2192-05-13 00:55:45,0.6421875
10027602,21477991,32453351,Surgical Intensive Care Unit (SICU),Surgical Intensive Care Unit (SICU),2201-12-11 20:11:52,2201-12-13 18:29:00,1.9285648148148147
10039831,26924951,39142259,Surgical Intensive Care Unit (SICU),Surgical Intensive Care Unit (SICU),2115-12-28 19:36:43,2115-12-30 16:31:48,1.8715856481481483
10025463,24470193,38275267,Surgical Intensive Care Unit (SICU),Surgical Intensive Care Unit (SICU),2137-10-09 02:51:25,2137-10-09 17:32:37,0.61194444444444451
10039997,24294180,36893762,Surgical Intensive Care Unit (SICU),Surgical Intensive Care Unit (SICU),2134-09-07 18:03:58,2134-09-08 22:11:05,1.1716087962962962
10016742,29281842,37057036,Surgical Intensive Care Unit (SICU),Surgical Intensive Care Unit (SICU),2178-07-03 22:45:00,2178-07-08 20:34:44,4.9095370370370377
10039708,24928679,37323533,Surgical Intensive Care Unit (SICU),Surgical Intensive Care Unit (SICU),2143-09-19 19:40:55,2143-09-20 16:29:51,0.86731481481481476
10025612,23403708,32587226,Surgical Intensive Care Unit (SICU),Surgical Intensive Care Unit (SICU),2125-09-25 13:23:24,2125-09-30 18:54:33,5.2299652777777776
10031757,28477280,33244906,Surgical Intensive Care Unit (SICU),Surgical Intensive Care Unit (SICU),2137-10-15 17:29:21,2137-10-17 22:16:51,2.1996527777777777
10001217,27703517,34592300,Surgical Intensive Care Unit (SICU),Surgical Intensive Care Unit (SICU),2157-12-19 15:42:24,2157-12-20 14:27:41,0.94811342592592585
10040025,27996267,36107367,Surgical Intensive Care Unit (SICU),Surgical Intensive Care Unit (SICU),2148-01-24 04:50:17,2148-01-30 17:45:09,6.5381018518518523
10038933,25129047,32166508,Surgical Intensive Care Unit (SICU),Surgical Intensive Care Unit (SICU),2148-09-10 13:19:00,2148-09-15 21:50:29,5.35519675925926
10014354,22741225,37200209,Surgical Intensive Care Unit (SICU),Surgical Intensive Care Unit (SICU),2146-10-09 01:08:00,2146-10-09 21:20:50,0.84224537037037039
10021666,22756440,35475449,Surgical Intensive Care Unit (SICU),Surgical Intensive Care Unit (SICU),2172-03-13 01:46:00,2172-03-13 16:33:34,0.6163657407407408
10022880,27708593,39623478,Surgical Intensive Care Unit (SICU),Surgical Intensive Care Unit (SICU),2177-03-15 05:49:26,2177-03-15 22:47:09,0.70674768518518516
10027602,28166872,32391858,Surgical Intensive Care Unit (SICU),Surgical Intensive Care Unit (SICU),2201-10-30 12:25:00,2201-11-12 18:37:10,13.258449074074074
10031757,28477280,30458995,Surgical Intensive Care Unit (SICU),Surgical Intensive Care Unit (SICU),2137-10-12 22:44:57,2137-10-14 17:08:34,1.766400462962963
10019172,24997044,32283063,Cardiac Vascular Intensive Care Unit (CVICU),Cardiac Vascular Intensive Care Unit (CVICU),2118-11-16 09:38:22,2118-11-19 20:34:51,3.455891203703704
10035185,22580999,39084876,Cardiac Vascular Intensive Care Unit (CVICU),Cardiac Vascular Intensive Care Unit (CVICU),2120-05-13 14:27:34,2120-05-14 16:28:21,1.0838773148148149
10013049,22675517,35679826,Cardiac Vascular Intensive Care Unit (CVICU),Cardiac Vascular Intensive Care Unit (CVICU),2114-06-20 09:32:58,2114-06-21 13:47:44,1.1769212962962963
10022017,22342963,39497668,Cardiac Vascular Intensive Care Unit (CVICU),Cardiac Vascular Intensive Care Unit (CVICU),2189-09-10 10:05:24,2189-09-14 21:27:28,4.4736574074074076
10009049,22995465,35636875,Cardiac Vascular Intensive Care Unit (CVICU),Cardiac Vascular Intensive Care Unit (CVICU),2174-05-26 09:18:00,2174-05-27 14:31:12,1.2175
10004457,23251352,31494479,Cardiac Vascular Intensive Care Unit (CVICU),Cardiac Vascular Intensive Care Unit (CVICU),2141-12-17 10:24:25,2141-12-18 14:16:17,1.1610185185185184
10038992,24745425,37127068,Cardiac Vascular Intensive Care Unit (CVICU),Cardiac Vascular Intensive Care Unit (CVICU),2187-07-29 09:41:11,2187-07-30 13:42:51,1.1678240740740742
10004422,21255400,32155744,Cardiac Vascular Intensive Care Unit (CVICU),Cardiac Vascular Intensive Care Unit (CVICU),2111-01-17 09:44:50,2111-01-23 18:18:46,6.3568981481481472
10007058,22954658,32506122,Cardiac Vascular Intensive Care Unit (CVICU),Cardiac Vascular Intensive Care Unit (CVICU),2167-11-07 20:22:00,2167-11-09 20:55:04,2.0229629629629629
10006580,24159665,38329661,Cardiac Vascular Intensive Care Unit (CVICU),Cardiac Vascular Intensive Care Unit (CVICU),2137-08-10 16:23:10,2137-08-11 13:56:56,0.89844907407407415
10005909,20199380,36496303,Cardiac Vascular Intensive Care Unit (CVICU),Cardiac Vascular Intensive Care Unit (CVICU),2144-10-29 23:09:03,2144-11-02 15:24:29,3.6773842592592594
10011398,27505812,37648963,Cardiac Vascular Intensive Care Unit (CVICU),Cardiac Vascular Intensive Care Unit (CVICU),2146-12-15 09:54:58,2146-12-16 10:53:06,1.0403703703703704
10018423,29366372,30665396,Cardiac Vascular Intensive Care Unit (CVICU),Cardiac Vascular Intensive Care Unit (CVICU),2167-05-05 12:54:06,2167-05-06 18:40:10,1.240324074074074
10009035,28324362,38507547,Cardiac Vascular Intensive Care Unit (CVICU),Cardiac Vascular Intensive Care Unit (CVICU),2161-04-27 10:38:12,2161-04-28 15:06:17,1.1861689814814815
10023771,20044587,33177122,Cardiac Vascular Intensive Care Unit (CVICU),Cardiac Vascular Intensive Care Unit (CVICU),2113-08-25 09:32:41,2113-08-27 16:27:53,2.2883333333333331
10022281,29642388,30585761,Cardiac Vascular Intensive Care Unit (CVICU),Cardiac Vascular Intensive Care Unit (CVICU),2125-06-17 04:12:54,2125-06-18 14:55:55,1.4465393518518521
10005348,25239799,34629895,Cardiac Vascular Intensive Care Unit (CVICU),Cardiac Vascular Intensive Care Unit (CVICU),2130-10-27 12:06:00,2130-10-29 12:05:02,1.9993287037037037
10012552,27089790,33383124,Cardiac Vascular Intensive Care Unit (CVICU),Cardiac Vascular Intensive Care Unit (CVICU),2140-03-25 14:37:26,2140-03-28 18:25:54,3.1586574074074072
10019385,20297618,39268883,Cardiac Vascular Intensive Care Unit (CVICU),Cardiac Vascular Intensive Care Unit (CVICU),2180-02-21 08:34:06,2180-02-22 16:05:14,1.3132870370370371
10014729,28889419,33558396,Cardiac Vascular Intensive Care Unit (CVICU),Cardiac Vascular Intensive Care Unit (CVICU),2125-02-27 10:03:08,2125-03-01 21:21:37,2.4711689814814815
10009628,25926192,35258379,Cardiac Vascular Intensive Care Unit (CVICU),Cardiac Vascular Intensive Care Unit (CVICU),2153-09-19 09:54:49,2153-09-21 16:39:06,2.2807523148148148
10005817,20626031,32604416,Cardiac Vascular Intensive Care Unit (CVICU),Cardiac Vascular Intensive Care Unit (CVICU),2132-12-15 09:29:01,2132-12-17 18:06:07,2.3590972222222222
10021118,24490144,36558922,Cardiac Vascular Intensive Care Unit (CVICU),Cardiac Vascular Intensive Care Unit (CVICU),2161-11-19 10:04:04,2161-11-20 21:45:42,1.4872453703703703
10037861,24540843,34531557,Neuro Surgical Intensive Care Unit (Neuro SICU),Neuro Surgical Intensive Care Unit (Neuro SICU),2117-03-14 16:34:58,2117-03-25 02:35:08,10.416782407407407
10005817,28661809,31316840,Medical Intensive Care Unit (MICU),Medical/Surgical Intensive Care Unit (MICU/SICU),2135-01-03 21:55:32,2135-01-19 21:16:23,15.972812499999998
10015272,27993466,37267577,Cardiac Vascular Intensive Care Unit (CVICU),Medical/Surgical Intensive Care Unit (MICU/SICU),2137-06-12 18:37:22,2137-06-14 20:25:41,2.0752199074074071
10019003,29279905,34107647,Medical/Surgical Intensive Care Unit (MICU/SICU),Medical/Surgical Intensive Care Unit (MICU/SICU),2153-03-28 02:21:00,2153-03-31 16:59:04,3.6097685185185182
10037928,22490490,31552399,Medical/Surgical Intensive Care Unit (MICU/SICU),Medical/Surgical Intensive Care Unit (MICU/SICU),2177-07-14 20:38:00,2177-07-15 16:08:36,0.81291666666666662
10039708,28258130,33281088,Medical/Surgical Intensive Care Unit (MICU/SICU),Medical/Surgical Intensive Care Unit (MICU/SICU),2140-01-23 18:08:00,2140-02-08 22:28:20,16.180787037037035
10002930,25922998,35629889,Medical/Surgical Intensive Care Unit (MICU/SICU),Medical/Surgical Intensive Care Unit (MICU/SICU),2198-04-17 21:24:00,2198-04-18 13:41:43,0.67896990740740737
10014354,27487226,34600477,Medical/Surgical Intensive Care Unit (MICU/SICU),Medical/Surgical Intensive Care Unit (MICU/SICU),2148-06-30 02:27:00,2148-07-01 20:58:50,1.7721064814814815
10019003,21457723,35727289,Medical/Surgical Intensive Care Unit (MICU/SICU),Medical/Surgical Intensive Care Unit (MICU/SICU),2155-07-10 17:48:57,2155-07-13 15:04:42,2.8859375000000003
10014354,29600294,39864867,Medical/Surgical Intensive Care Unit (MICU/SICU),Medical/Surgical Intensive Care Unit (MICU/SICU),2148-08-16 08:57:26,2148-08-17 14:45:17,1.2415625
10004733,27411876,39635619,Medical/Surgical Intensive Care Unit (MICU/SICU),Medical/Surgical Intensive Care Unit (MICU/SICU),2174-12-04 11:28:24,2174-12-12 20:03:01,8.3573726851851848
10007795,28477357,31921355,Medical/Surgical Intensive Care Unit (MICU/SICU),Medical/Surgical Intensive Care Unit (MICU/SICU),2136-04-22 18:01:13,2136-04-23 19:13:58,1.0505208333333333
10020640,27984218,30849778,Medical/Surgical Intensive Care Unit (MICU/SICU),Medical/Surgical Intensive Care Unit (MICU/SICU),2153-02-13 01:38:00,2153-02-15 15:00:57,2.5576041666666667
10020740,23831430,35044342,Medical/Surgical Intensive Care Unit (MICU/SICU),Medical/Surgical Intensive Care Unit (MICU/SICU),2150-03-19 04:41:33,2150-03-23 20:49:33,4.6722222222222225
10023239,29295881,33846653,Medical/Surgical Intensive Care Unit (MICU/SICU),Medical/Surgical Intensive Care Unit (MICU/SICU),2137-06-19 19:09:00,2137-06-22 14:57:32,2.8253703703703703
10023239,21759936,35024147,Medical/Surgical Intensive Care Unit (MICU/SICU),Medical/Surgical Intensive Care Unit (MICU/SICU),2140-10-03 09:07:56,2140-10-05 19:31:27,2.4329976851851853
10001725,25563031,31205490,Medical/Surgical Intensive Care Unit (MICU/SICU),Medical/Surgical Intensive Care Unit (MICU/SICU),2110-04-11 15:52:22,2110-04-12 23:59:56,1.3385879629629629
10003400,23559586,34577403,Medical/Surgical Intensive Care Unit (MICU/SICU),Medical/Surgical Intensive Care Unit (MICU/SICU),2137-08-10 19:54:51,2137-08-13 17:54:54,2.9167013888888889
10019003,27525946,35214014,Medical/Surgical Intensive Care Unit (MICU/SICU),Medical/Surgical Intensive Care Unit (MICU/SICU),2153-04-13 19:45:30,2153-04-16 21:15:15,3.0623263888888892
10035631,29276678,30932571,Medical/Surgical Intensive Care Unit (MICU/SICU),Medical/Surgical Intensive Care Unit (MICU/SICU),2116-02-28 18:43:20,2116-03-10 06:35:04,10.494259259259259
10003400,20214994,32128372,Medical/Surgical Intensive Care Unit (MICU/SICU),Medical/Surgical Intensive Care Unit (MICU/SICU),2137-02-25 23:37:19,2137-03-10 21:29:36,12.91130787037037
10020786,23488445,33683112,Medical/Surgical Intensive Care Unit (MICU/SICU),Medical/Surgical Intensive Care Unit (MICU/SICU),2189-06-09 12:46:30,2189-06-10 22:58:09,1.4247569444444446
10020740,23831430,35026312,Medical/Surgical Intensive Care Unit (MICU/SICU),Medical/Surgical Intensive Care Unit (MICU/SICU),2150-03-11 15:34:56,2150-03-19 02:17:47,7.4464236111111113
10032725,20611640,30101877,Medical/Surgical Intensive Care Unit (MICU/SICU),Medical/Surgical Intensive Care Unit (MICU/SICU),2143-03-22 06:42:00,2143-03-25 15:05:33,3.3496875
10037928,24656677,39804682,Medical/Surgical Intensive Care Unit (MICU/SICU),Medical/Surgical Intensive Care Unit (MICU/SICU),2178-12-21 06:05:18,2178-12-22 02:16:08,0.8408564814814814
10015931,22130791,37093652,Medical/Surgical Intensive Care Unit (MICU/SICU),Medical/Surgical Intensive Care Unit (MICU/SICU),2177-03-24 21:48:07,2177-03-29 18:03:36,4.8440856481481482
1 subject_id hadm_id stay_id first_careunit last_careunit intime outtime los
2 10018328 23786647 31269608 Neuro Stepdown Neuro Stepdown 2154-04-24 23:03:44 2154-05-02 15:55:21 7.7025115740740739
3 10020187 24104168 37509585 Neuro Surgical Intensive Care Unit (Neuro SICU) Neuro Stepdown 2169-01-15 04:56:00 2169-01-20 15:47:50 5.4526620370370367
4 10020187 26842957 32554129 Neuro Intermediate Neuro Intermediate 2170-02-24 18:18:46 2170-02-25 15:15:26 0.87268518518518523
5 10012853 27882036 31338022 Trauma SICU (TSICU) Trauma SICU (TSICU) 2176-11-26 02:34:49 2176-11-29 20:58:54 3.766724537037037
6 10020740 25826145 32145159 Trauma SICU (TSICU) Trauma SICU (TSICU) 2150-06-03 20:12:32 2150-06-04 21:05:58 1.0371064814814817
7 10039708 23819016 38559363 Trauma SICU (TSICU) Trauma SICU (TSICU) 2140-06-18 01:41:00 2140-06-19 21:47:16 1.8376851851851852
8 10020306 23052851 38540883 Trauma SICU (TSICU) Trauma SICU (TSICU) 2135-01-21 17:01:57 2135-01-24 21:47:45 3.1984722222222222
9 10019568 28710730 30876334 Trauma SICU (TSICU) Trauma SICU (TSICU) 2120-01-30 22:51:00 2120-01-31 18:25:12 0.81541666666666668
10 10018081 28861356 38333427 Trauma SICU (TSICU) Trauma SICU (TSICU) 2134-08-05 14:53:33 2134-08-07 17:32:43 2.1105324074074074
11 10018081 21027282 37293400 Trauma SICU (TSICU) Trauma SICU (TSICU) 2133-12-18 17:10:00 2134-01-01 14:44:53 13.899224537037037
12 10010867 22429197 39880770 Trauma SICU (TSICU) Trauma SICU (TSICU) 2147-12-30 09:33:00 2148-01-08 18:14:21 9.3620486111111116
13 10021487 28998349 38197705 Trauma SICU (TSICU) Trauma SICU (TSICU) 2116-12-03 01:02:00 2116-12-18 17:34:03 15.688923611111109
14 10003046 26048429 35514836 Trauma SICU (TSICU) Trauma SICU (TSICU) 2154-01-02 15:57:15 2154-01-04 15:19:56 1.9740856481481481
15 10017492 27417763 36035031 Trauma SICU (TSICU) Trauma SICU (TSICU) 2116-06-27 17:35:34 2116-06-27 20:26:18 0.1185648148148148
16 10017492 27417763 39543480 Trauma SICU (TSICU) Trauma SICU (TSICU) 2116-06-26 20:35:09 2116-06-27 15:44:27 0.79812499999999986
17 10018501 28479513 35446858 Trauma SICU (TSICU) Trauma SICU (TSICU) 2141-07-31 00:01:00 2141-08-01 22:36:21 1.9412152777777776
18 10008454 20291550 31959184 Trauma SICU (TSICU) Trauma SICU (TSICU) 2110-11-30 17:11:36 2110-12-05 16:48:24 4.983888888888889
19 10026354 24547356 36091287 Trauma SICU (TSICU) Trauma SICU (TSICU) 2119-10-26 08:33:32 2119-10-27 17:50:50 1.387013888888889
20 10029291 22205327 36059427 Coronary Care Unit (CCU) Coronary Care Unit (CCU) 2123-02-20 04:13:00 2123-02-26 12:03:56 6.3270370370370363
21 10027445 29163082 36084484 Coronary Care Unit (CCU) Coronary Care Unit (CCU) 2142-08-30 13:20:56 2142-08-31 18:19:36 1.2074074074074075
22 10010471 29842315 32119961 Coronary Care Unit (CCU) Coronary Care Unit (CCU) 2155-12-02 20:33:00 2155-12-07 18:19:18 4.9071527777777781
23 10023117 21607814 30955999 Coronary Care Unit (CCU) Coronary Care Unit (CCU) 2175-07-06 17:41:00 2175-07-19 22:50:08 13.214675925925926
24 10002495 24982426 36753294 Coronary Care Unit (CCU) Coronary Care Unit (CCU) 2141-05-22 20:18:01 2141-05-27 22:24:02 5.0875115740740737
25 10031404 21606243 35544374 Coronary Care Unit (CCU) Coronary Care Unit (CCU) 2113-08-04 18:47:42 2113-08-05 23:45:02 1.2064814814814815
26 10017492 27417763 36871784 Coronary Care Unit (CCU) Coronary Care Unit (CCU) 2116-07-05 06:50:16 2116-07-05 13:06:59 0.26160879629629624
27 10026255 22059910 31248398 Coronary Care Unit (CCU) Coronary Care Unit (CCU) 2201-07-07 19:40:00 2201-07-08 15:43:15 0.83559027777777783
28 10015931 28157142 39544395 Coronary Care Unit (CCU) Coronary Care Unit (CCU) 2176-11-19 14:24:52 2176-11-20 19:18:13 1.2037152777777778
29 10038999 27189241 39711498 Coronary Care Unit (CCU) Coronary Care Unit (CCU) 2131-05-22 21:50:33 2131-05-31 17:55:04 8.8364699074074071
30 10029291 22205327 35146796 Coronary Care Unit (CCU) Coronary Care Unit (CCU) 2123-02-26 12:12:32 2123-03-04 23:36:14 6.4747916666666674
31 10023117 21133938 38554095 Medical Intensive Care Unit (MICU) Coronary Care Unit (CCU) 2175-03-21 03:20:53 2175-03-27 17:52:59 6.605625
32 10023117 28872262 30057454 Cardiac Vascular Intensive Care Unit (CVICU) Coronary Care Unit (CCU) 2171-11-14 10:06:41 2171-11-18 20:49:43 4.4465509259259264
33 10014354 27487226 38017367 Neuro Surgical Intensive Care Unit (Neuro SICU) Coronary Care Unit (CCU) 2148-07-07 15:48:09 2148-07-10 18:25:51 3.1095138888888889
34 10003400 23559586 38383343 Coronary Care Unit (CCU) Medical Intensive Care Unit (MICU) 2137-08-17 17:36:37 2137-09-02 19:17:11 16.069837962962961
35 10004235 24181354 34100191 Coronary Care Unit (CCU) Medical Intensive Care Unit (MICU) 2196-02-24 17:07:00 2196-02-29 15:58:02 4.9521064814814819
36 10002930 25696644 37049133 Medical Intensive Care Unit (MICU) Medical Intensive Care Unit (MICU) 2196-04-14 13:40:00 2196-04-15 16:54:44 1.1352314814814815
37 10002428 20321825 34807493 Medical Intensive Care Unit (MICU) Medical Intensive Care Unit (MICU) 2156-04-30 21:53:00 2156-05-02 22:27:20 2.0238425925925925
38 10007928 20338077 35128235 Medical Intensive Care Unit (MICU) Medical Intensive Care Unit (MICU) 2129-04-06 00:25:00 2129-04-08 21:02:55 2.8596643518518516
39 10020944 29974575 30757476 Medical Intensive Care Unit (MICU) Medical Intensive Care Unit (MICU) 2131-02-27 16:40:00 2131-03-08 18:30:38 9.0768287037037041
40 10002428 28662225 38875437 Medical Intensive Care Unit (MICU) Medical Intensive Care Unit (MICU) 2156-04-19 18:11:19 2156-04-26 18:58:41 7.0328935185185193
41 10026406 25260176 30864406 Medical Intensive Care Unit (MICU) Medical Intensive Care Unit (MICU) 2129-01-05 02:37:19 2129-01-05 14:11:03 0.48175925925925928
42 10020740 23831430 35889503 Medical Intensive Care Unit (MICU) Medical Intensive Care Unit (MICU) 2150-03-25 18:22:56 2150-03-28 22:20:47 3.1651736111111113
43 10036156 28019404 38587181 Medical Intensive Care Unit (MICU) Medical Intensive Care Unit (MICU) 2157-07-01 04:52:37 2157-07-02 14:18:55 1.3932638888888889
44 10006053 22942076 34617352 Medical Intensive Care Unit (MICU) Medical Intensive Care Unit (MICU) 2111-11-14 00:19:12 2111-11-15 18:21:10 1.7513657407407408
45 10006053 22942076 32895909 Medical Intensive Care Unit (MICU) Medical Intensive Care Unit (MICU) 2111-11-13 23:40:00 2111-11-14 00:14:10 0.02372685185185185
46 10000032 29079034 39553978 Medical Intensive Care Unit (MICU) Medical Intensive Care Unit (MICU) 2180-07-23 14:00:00 2180-07-23 23:50:47 0.4102662037037037
47 10019777 27738145 34578020 Medical Intensive Care Unit (MICU) Medical Intensive Care Unit (MICU) 2187-02-10 20:34:00 2187-02-17 23:38:57 7.1284375
48 10021312 28829452 37507305 Medical Intensive Care Unit (MICU) Medical Intensive Care Unit (MICU) 2113-09-14 10:19:17 2113-09-15 12:43:00 1.0998032407407408
49 10007818 22987108 32359580 Medical Intensive Care Unit (MICU) Medical Intensive Care Unit (MICU) 2146-06-22 11:46:29 2146-07-13 00:27:47 20.528680555555557
50 10020740 23831430 31077365 Medical Intensive Care Unit (MICU) Medical Intensive Care Unit (MICU) 2150-03-30 07:57:10 2150-04-04 10:58:43 5.1260763888888894
51 10021938 23112364 39492446 Medical Intensive Care Unit (MICU) Medical Intensive Care Unit (MICU) 2181-10-13 02:52:00 2181-10-14 18:03:28 1.6329629629629629
52 10016742 28506150 32314488 Medical Intensive Care Unit (MICU) Medical Intensive Care Unit (MICU) 2178-07-13 08:16:00 2178-07-16 14:41:31 3.2677199074074075
53 10019917 22585261 34324099 Medical Intensive Care Unit (MICU) Medical Intensive Care Unit (MICU) 2182-01-07 23:26:32 2182-01-09 07:09:31 1.3215162037037038
54 10037975 27617929 39061571 Medical Intensive Care Unit (MICU) Medical Intensive Care Unit (MICU) 2185-01-17 19:12:12 2185-01-22 16:16:52 4.8782407407407407
55 10002428 28662225 33987268 Medical Intensive Care Unit (MICU) Medical Intensive Care Unit (MICU) 2156-04-12 16:24:18 2156-04-17 15:57:08 4.9811342592592593
56 10015931 24420677 38137964 Medical Intensive Care Unit (MICU) Medical Intensive Care Unit (MICU) 2176-12-22 16:24:33 2176-12-23 20:14:53 1.1599537037037038
57 10021938 27154822 33083787 Medical Intensive Care Unit (MICU) Medical Intensive Care Unit (MICU) 2181-10-25 11:35:00 2181-10-26 20:53:57 1.3881597222222224
58 10027445 26275841 34499716 Medical Intensive Care Unit (MICU) Medical Intensive Care Unit (MICU) 2142-07-31 01:41:00 2142-08-03 21:05:58 3.8090046296296296
59 10015860 25085565 32496174 Medical Intensive Care Unit (MICU) Medical Intensive Care Unit (MICU) 2186-09-15 17:15:00 2186-09-16 11:17:40 0.75185185185185188
60 10016742 27568122 30425410 Medical Intensive Care Unit (MICU) Medical Intensive Care Unit (MICU) 2178-07-22 08:19:00 2178-07-25 16:42:43 3.3498032407407403
61 10029484 20764029 35396193 Medical Intensive Care Unit (MICU) Medical Intensive Care Unit (MICU) 2160-11-08 05:23:00 2160-11-08 21:04:55 0.65410879629629626
62 10038081 20755971 38430513 Medical Intensive Care Unit (MICU) Medical Intensive Care Unit (MICU) 2115-10-09 10:15:25 2115-10-13 03:01:17 3.6985185185185188
63 10004720 22081550 35009126 Surgical Intensive Care Unit (SICU) Medical Intensive Care Unit (MICU) 2186-11-12 19:55:00 2186-11-17 21:15:55 5.05619212962963
64 10002428 23473524 35479615 Surgical Intensive Care Unit (SICU) Medical Intensive Care Unit (MICU) 2156-05-11 14:49:34 2156-05-22 14:16:46 10.977222222222222
65 10005866 20364112 34170353 Trauma SICU (TSICU) Surgical Intensive Care Unit (SICU) 2149-10-02 12:48:08 2149-10-04 17:48:36 2.2086574074074075
66 10026255 22059910 38229329 Surgical Intensive Care Unit (SICU) Surgical Intensive Care Unit (SICU) 2201-07-10 10:10:47 2201-07-11 13:48:02 1.1508680555555555
67 10008287 22168393 33348260 Surgical Intensive Care Unit (SICU) Surgical Intensive Care Unit (SICU) 2145-09-28 20:59:43 2145-09-30 00:34:15 1.1489814814814816
68 10001217 24597018 37067082 Surgical Intensive Care Unit (SICU) Surgical Intensive Care Unit (SICU) 2157-11-20 19:18:02 2157-11-21 22:08:00 1.1180324074074075
69 10022041 28909879 30913302 Surgical Intensive Care Unit (SICU) Surgical Intensive Care Unit (SICU) 2187-05-18 18:39:00 2187-05-20 16:04:02 1.8923842592592592
70 10016810 20973395 35436337 Surgical Intensive Care Unit (SICU) Surgical Intensive Care Unit (SICU) 2185-06-16 02:16:00 2185-06-18 14:00:02 2.488912037037037
71 10024043 24717014 32374504 Surgical Intensive Care Unit (SICU) Surgical Intensive Care Unit (SICU) 2117-04-11 22:05:00 2117-04-14 14:36:11 2.6883217592592596
72 10018845 21101111 36427705 Surgical Intensive Care Unit (SICU) Surgical Intensive Care Unit (SICU) 2184-10-08 04:09:00 2184-10-09 15:55:53 1.4908912037037034
73 10014078 25809882 38907302 Surgical Intensive Care Unit (SICU) Surgical Intensive Care Unit (SICU) 2166-08-22 00:36:00 2166-08-24 13:12:44 2.5255092592592594
74 10016150 29374560 33652203 Surgical Intensive Care Unit (SICU) Surgical Intensive Care Unit (SICU) 2142-05-10 16:39:15 2142-05-10 20:23:49 0.15594907407407407
75 10021487 27660781 35065627 Surgical Intensive Care Unit (SICU) Surgical Intensive Care Unit (SICU) 2117-03-07 23:06:21 2117-03-09 18:01:57 1.7886111111111109
76 10015860 24698912 36734659 Surgical Intensive Care Unit (SICU) Surgical Intensive Care Unit (SICU) 2192-05-12 09:31:00 2192-05-13 00:55:45 0.6421875
77 10027602 21477991 32453351 Surgical Intensive Care Unit (SICU) Surgical Intensive Care Unit (SICU) 2201-12-11 20:11:52 2201-12-13 18:29:00 1.9285648148148147
78 10039831 26924951 39142259 Surgical Intensive Care Unit (SICU) Surgical Intensive Care Unit (SICU) 2115-12-28 19:36:43 2115-12-30 16:31:48 1.8715856481481483
79 10025463 24470193 38275267 Surgical Intensive Care Unit (SICU) Surgical Intensive Care Unit (SICU) 2137-10-09 02:51:25 2137-10-09 17:32:37 0.61194444444444451
80 10039997 24294180 36893762 Surgical Intensive Care Unit (SICU) Surgical Intensive Care Unit (SICU) 2134-09-07 18:03:58 2134-09-08 22:11:05 1.1716087962962962
81 10016742 29281842 37057036 Surgical Intensive Care Unit (SICU) Surgical Intensive Care Unit (SICU) 2178-07-03 22:45:00 2178-07-08 20:34:44 4.9095370370370377
82 10039708 24928679 37323533 Surgical Intensive Care Unit (SICU) Surgical Intensive Care Unit (SICU) 2143-09-19 19:40:55 2143-09-20 16:29:51 0.86731481481481476
83 10025612 23403708 32587226 Surgical Intensive Care Unit (SICU) Surgical Intensive Care Unit (SICU) 2125-09-25 13:23:24 2125-09-30 18:54:33 5.2299652777777776
84 10031757 28477280 33244906 Surgical Intensive Care Unit (SICU) Surgical Intensive Care Unit (SICU) 2137-10-15 17:29:21 2137-10-17 22:16:51 2.1996527777777777
85 10001217 27703517 34592300 Surgical Intensive Care Unit (SICU) Surgical Intensive Care Unit (SICU) 2157-12-19 15:42:24 2157-12-20 14:27:41 0.94811342592592585
86 10040025 27996267 36107367 Surgical Intensive Care Unit (SICU) Surgical Intensive Care Unit (SICU) 2148-01-24 04:50:17 2148-01-30 17:45:09 6.5381018518518523
87 10038933 25129047 32166508 Surgical Intensive Care Unit (SICU) Surgical Intensive Care Unit (SICU) 2148-09-10 13:19:00 2148-09-15 21:50:29 5.35519675925926
88 10014354 22741225 37200209 Surgical Intensive Care Unit (SICU) Surgical Intensive Care Unit (SICU) 2146-10-09 01:08:00 2146-10-09 21:20:50 0.84224537037037039
89 10021666 22756440 35475449 Surgical Intensive Care Unit (SICU) Surgical Intensive Care Unit (SICU) 2172-03-13 01:46:00 2172-03-13 16:33:34 0.6163657407407408
90 10022880 27708593 39623478 Surgical Intensive Care Unit (SICU) Surgical Intensive Care Unit (SICU) 2177-03-15 05:49:26 2177-03-15 22:47:09 0.70674768518518516
91 10027602 28166872 32391858 Surgical Intensive Care Unit (SICU) Surgical Intensive Care Unit (SICU) 2201-10-30 12:25:00 2201-11-12 18:37:10 13.258449074074074
92 10031757 28477280 30458995 Surgical Intensive Care Unit (SICU) Surgical Intensive Care Unit (SICU) 2137-10-12 22:44:57 2137-10-14 17:08:34 1.766400462962963
93 10019172 24997044 32283063 Cardiac Vascular Intensive Care Unit (CVICU) Cardiac Vascular Intensive Care Unit (CVICU) 2118-11-16 09:38:22 2118-11-19 20:34:51 3.455891203703704
94 10035185 22580999 39084876 Cardiac Vascular Intensive Care Unit (CVICU) Cardiac Vascular Intensive Care Unit (CVICU) 2120-05-13 14:27:34 2120-05-14 16:28:21 1.0838773148148149
95 10013049 22675517 35679826 Cardiac Vascular Intensive Care Unit (CVICU) Cardiac Vascular Intensive Care Unit (CVICU) 2114-06-20 09:32:58 2114-06-21 13:47:44 1.1769212962962963
96 10022017 22342963 39497668 Cardiac Vascular Intensive Care Unit (CVICU) Cardiac Vascular Intensive Care Unit (CVICU) 2189-09-10 10:05:24 2189-09-14 21:27:28 4.4736574074074076
97 10009049 22995465 35636875 Cardiac Vascular Intensive Care Unit (CVICU) Cardiac Vascular Intensive Care Unit (CVICU) 2174-05-26 09:18:00 2174-05-27 14:31:12 1.2175
98 10004457 23251352 31494479 Cardiac Vascular Intensive Care Unit (CVICU) Cardiac Vascular Intensive Care Unit (CVICU) 2141-12-17 10:24:25 2141-12-18 14:16:17 1.1610185185185184
99 10038992 24745425 37127068 Cardiac Vascular Intensive Care Unit (CVICU) Cardiac Vascular Intensive Care Unit (CVICU) 2187-07-29 09:41:11 2187-07-30 13:42:51 1.1678240740740742
100 10004422 21255400 32155744 Cardiac Vascular Intensive Care Unit (CVICU) Cardiac Vascular Intensive Care Unit (CVICU) 2111-01-17 09:44:50 2111-01-23 18:18:46 6.3568981481481472
101 10007058 22954658 32506122 Cardiac Vascular Intensive Care Unit (CVICU) Cardiac Vascular Intensive Care Unit (CVICU) 2167-11-07 20:22:00 2167-11-09 20:55:04 2.0229629629629629
102 10006580 24159665 38329661 Cardiac Vascular Intensive Care Unit (CVICU) Cardiac Vascular Intensive Care Unit (CVICU) 2137-08-10 16:23:10 2137-08-11 13:56:56 0.89844907407407415
103 10005909 20199380 36496303 Cardiac Vascular Intensive Care Unit (CVICU) Cardiac Vascular Intensive Care Unit (CVICU) 2144-10-29 23:09:03 2144-11-02 15:24:29 3.6773842592592594
104 10011398 27505812 37648963 Cardiac Vascular Intensive Care Unit (CVICU) Cardiac Vascular Intensive Care Unit (CVICU) 2146-12-15 09:54:58 2146-12-16 10:53:06 1.0403703703703704
105 10018423 29366372 30665396 Cardiac Vascular Intensive Care Unit (CVICU) Cardiac Vascular Intensive Care Unit (CVICU) 2167-05-05 12:54:06 2167-05-06 18:40:10 1.240324074074074
106 10009035 28324362 38507547 Cardiac Vascular Intensive Care Unit (CVICU) Cardiac Vascular Intensive Care Unit (CVICU) 2161-04-27 10:38:12 2161-04-28 15:06:17 1.1861689814814815
107 10023771 20044587 33177122 Cardiac Vascular Intensive Care Unit (CVICU) Cardiac Vascular Intensive Care Unit (CVICU) 2113-08-25 09:32:41 2113-08-27 16:27:53 2.2883333333333331
108 10022281 29642388 30585761 Cardiac Vascular Intensive Care Unit (CVICU) Cardiac Vascular Intensive Care Unit (CVICU) 2125-06-17 04:12:54 2125-06-18 14:55:55 1.4465393518518521
109 10005348 25239799 34629895 Cardiac Vascular Intensive Care Unit (CVICU) Cardiac Vascular Intensive Care Unit (CVICU) 2130-10-27 12:06:00 2130-10-29 12:05:02 1.9993287037037037
110 10012552 27089790 33383124 Cardiac Vascular Intensive Care Unit (CVICU) Cardiac Vascular Intensive Care Unit (CVICU) 2140-03-25 14:37:26 2140-03-28 18:25:54 3.1586574074074072
111 10019385 20297618 39268883 Cardiac Vascular Intensive Care Unit (CVICU) Cardiac Vascular Intensive Care Unit (CVICU) 2180-02-21 08:34:06 2180-02-22 16:05:14 1.3132870370370371
112 10014729 28889419 33558396 Cardiac Vascular Intensive Care Unit (CVICU) Cardiac Vascular Intensive Care Unit (CVICU) 2125-02-27 10:03:08 2125-03-01 21:21:37 2.4711689814814815
113 10009628 25926192 35258379 Cardiac Vascular Intensive Care Unit (CVICU) Cardiac Vascular Intensive Care Unit (CVICU) 2153-09-19 09:54:49 2153-09-21 16:39:06 2.2807523148148148
114 10005817 20626031 32604416 Cardiac Vascular Intensive Care Unit (CVICU) Cardiac Vascular Intensive Care Unit (CVICU) 2132-12-15 09:29:01 2132-12-17 18:06:07 2.3590972222222222
115 10021118 24490144 36558922 Cardiac Vascular Intensive Care Unit (CVICU) Cardiac Vascular Intensive Care Unit (CVICU) 2161-11-19 10:04:04 2161-11-20 21:45:42 1.4872453703703703
116 10037861 24540843 34531557 Neuro Surgical Intensive Care Unit (Neuro SICU) Neuro Surgical Intensive Care Unit (Neuro SICU) 2117-03-14 16:34:58 2117-03-25 02:35:08 10.416782407407407
117 10005817 28661809 31316840 Medical Intensive Care Unit (MICU) Medical/Surgical Intensive Care Unit (MICU/SICU) 2135-01-03 21:55:32 2135-01-19 21:16:23 15.972812499999998
118 10015272 27993466 37267577 Cardiac Vascular Intensive Care Unit (CVICU) Medical/Surgical Intensive Care Unit (MICU/SICU) 2137-06-12 18:37:22 2137-06-14 20:25:41 2.0752199074074071
119 10019003 29279905 34107647 Medical/Surgical Intensive Care Unit (MICU/SICU) Medical/Surgical Intensive Care Unit (MICU/SICU) 2153-03-28 02:21:00 2153-03-31 16:59:04 3.6097685185185182
120 10037928 22490490 31552399 Medical/Surgical Intensive Care Unit (MICU/SICU) Medical/Surgical Intensive Care Unit (MICU/SICU) 2177-07-14 20:38:00 2177-07-15 16:08:36 0.81291666666666662
121 10039708 28258130 33281088 Medical/Surgical Intensive Care Unit (MICU/SICU) Medical/Surgical Intensive Care Unit (MICU/SICU) 2140-01-23 18:08:00 2140-02-08 22:28:20 16.180787037037035
122 10002930 25922998 35629889 Medical/Surgical Intensive Care Unit (MICU/SICU) Medical/Surgical Intensive Care Unit (MICU/SICU) 2198-04-17 21:24:00 2198-04-18 13:41:43 0.67896990740740737
123 10014354 27487226 34600477 Medical/Surgical Intensive Care Unit (MICU/SICU) Medical/Surgical Intensive Care Unit (MICU/SICU) 2148-06-30 02:27:00 2148-07-01 20:58:50 1.7721064814814815
124 10019003 21457723 35727289 Medical/Surgical Intensive Care Unit (MICU/SICU) Medical/Surgical Intensive Care Unit (MICU/SICU) 2155-07-10 17:48:57 2155-07-13 15:04:42 2.8859375000000003
125 10014354 29600294 39864867 Medical/Surgical Intensive Care Unit (MICU/SICU) Medical/Surgical Intensive Care Unit (MICU/SICU) 2148-08-16 08:57:26 2148-08-17 14:45:17 1.2415625
126 10004733 27411876 39635619 Medical/Surgical Intensive Care Unit (MICU/SICU) Medical/Surgical Intensive Care Unit (MICU/SICU) 2174-12-04 11:28:24 2174-12-12 20:03:01 8.3573726851851848
127 10007795 28477357 31921355 Medical/Surgical Intensive Care Unit (MICU/SICU) Medical/Surgical Intensive Care Unit (MICU/SICU) 2136-04-22 18:01:13 2136-04-23 19:13:58 1.0505208333333333
128 10020640 27984218 30849778 Medical/Surgical Intensive Care Unit (MICU/SICU) Medical/Surgical Intensive Care Unit (MICU/SICU) 2153-02-13 01:38:00 2153-02-15 15:00:57 2.5576041666666667
129 10020740 23831430 35044342 Medical/Surgical Intensive Care Unit (MICU/SICU) Medical/Surgical Intensive Care Unit (MICU/SICU) 2150-03-19 04:41:33 2150-03-23 20:49:33 4.6722222222222225
130 10023239 29295881 33846653 Medical/Surgical Intensive Care Unit (MICU/SICU) Medical/Surgical Intensive Care Unit (MICU/SICU) 2137-06-19 19:09:00 2137-06-22 14:57:32 2.8253703703703703
131 10023239 21759936 35024147 Medical/Surgical Intensive Care Unit (MICU/SICU) Medical/Surgical Intensive Care Unit (MICU/SICU) 2140-10-03 09:07:56 2140-10-05 19:31:27 2.4329976851851853
132 10001725 25563031 31205490 Medical/Surgical Intensive Care Unit (MICU/SICU) Medical/Surgical Intensive Care Unit (MICU/SICU) 2110-04-11 15:52:22 2110-04-12 23:59:56 1.3385879629629629
133 10003400 23559586 34577403 Medical/Surgical Intensive Care Unit (MICU/SICU) Medical/Surgical Intensive Care Unit (MICU/SICU) 2137-08-10 19:54:51 2137-08-13 17:54:54 2.9167013888888889
134 10019003 27525946 35214014 Medical/Surgical Intensive Care Unit (MICU/SICU) Medical/Surgical Intensive Care Unit (MICU/SICU) 2153-04-13 19:45:30 2153-04-16 21:15:15 3.0623263888888892
135 10035631 29276678 30932571 Medical/Surgical Intensive Care Unit (MICU/SICU) Medical/Surgical Intensive Care Unit (MICU/SICU) 2116-02-28 18:43:20 2116-03-10 06:35:04 10.494259259259259
136 10003400 20214994 32128372 Medical/Surgical Intensive Care Unit (MICU/SICU) Medical/Surgical Intensive Care Unit (MICU/SICU) 2137-02-25 23:37:19 2137-03-10 21:29:36 12.91130787037037
137 10020786 23488445 33683112 Medical/Surgical Intensive Care Unit (MICU/SICU) Medical/Surgical Intensive Care Unit (MICU/SICU) 2189-06-09 12:46:30 2189-06-10 22:58:09 1.4247569444444446
138 10020740 23831430 35026312 Medical/Surgical Intensive Care Unit (MICU/SICU) Medical/Surgical Intensive Care Unit (MICU/SICU) 2150-03-11 15:34:56 2150-03-19 02:17:47 7.4464236111111113
139 10032725 20611640 30101877 Medical/Surgical Intensive Care Unit (MICU/SICU) Medical/Surgical Intensive Care Unit (MICU/SICU) 2143-03-22 06:42:00 2143-03-25 15:05:33 3.3496875
140 10037928 24656677 39804682 Medical/Surgical Intensive Care Unit (MICU/SICU) Medical/Surgical Intensive Care Unit (MICU/SICU) 2178-12-21 06:05:18 2178-12-22 02:16:08 0.8408564814814814
141 10015931 22130791 37093652 Medical/Surgical Intensive Care Unit (MICU/SICU) Medical/Surgical Intensive Care Unit (MICU/SICU) 2177-03-24 21:48:07 2177-03-29 18:03:36 4.8440856481481482
File diff suppressed because it is too large Load Diff
+101
View File
@@ -0,0 +1,101 @@
subject_id,gender,anchor_age,anchor_year,anchor_year_group,dod
10014729,F,21,2125,2011 - 2013,
10003400,F,72,2134,2011 - 2013,2137-09-02
10002428,F,80,2155,2011 - 2013,
10032725,F,38,2143,2011 - 2013,2143-03-30
10027445,F,48,2142,2011 - 2013,2146-02-09
10037928,F,78,2175,2011 - 2013,
10001725,F,46,2110,2011 - 2013,
10040025,F,64,2143,2011 - 2013,2148-02-07
10008454,F,26,2110,2011 - 2013,
10020640,F,91,2153,2011 - 2013,2154-02-04
10014078,F,60,2166,2011 - 2013,
10007795,F,53,2136,2011 - 2013,
10001217,F,55,2157,2011 - 2013,
10019172,F,63,2118,2011 - 2013,
10039831,F,57,2115,2011 - 2013,
10007928,F,59,2129,2011 - 2013,
10019003,F,65,2148,2011 - 2013,2155-12-03
10002930,F,48,2193,2011 - 2013,2201-12-24
10010471,F,89,2155,2014 - 2016,2155-12-07
10027602,F,71,2201,2014 - 2016,
10031757,F,67,2137,2014 - 2016,2137-10-31
10016742,F,58,2178,2014 - 2016,
10039997,F,67,2134,2014 - 2016,
10012853,F,91,2175,2014 - 2016,
10000032,F,52,2180,2014 - 2016,2180-09-09
10031404,F,82,2113,2014 - 2016,
10029291,F,50,2123,2014 - 2016,
10005909,F,40,2144,2014 - 2016,
10039708,F,46,2138,2014 - 2016,
10019568,F,59,2120,2014 - 2016,
10020187,F,63,2169,2014 - 2016,
10016810,F,66,2185,2014 - 2016,
10038081,F,63,2115,2014 - 2016,2115-10-12
10021312,F,55,2113,2014 - 2016,
10006580,F,63,2137,2014 - 2016,
10010867,F,28,2147,2014 - 2016,
10020786,F,86,2189,2014 - 2016,
10020306,F,74,2129,2014 - 2016,
10023239,F,29,2137,2014 - 2016,
10018328,F,83,2154,2014 - 2016,
10008287,F,43,2145,2014 - 2016,
10036156,F,88,2157,2014 - 2016,
10015272,F,78,2137,2014 - 2016,
10022281,M,84,2125,2011 - 2013,
10035631,M,63,2112,2011 - 2013,2116-03-12
10024043,M,67,2117,2011 - 2013,2117-06-26
10025612,M,82,2125,2011 - 2013,
10003046,M,64,2154,2011 - 2013,
10021666,M,87,2172,2011 - 2013,2172-04-19
10018423,M,37,2162,2011 - 2013,
10025463,M,66,2136,2011 - 2013,2137-10-09
10017492,M,84,2114,2011 - 2013,2116-07-05
10004422,M,78,2111,2011 - 2013,
10011398,M,67,2146,2011 - 2013,
10021938,M,65,2181,2011 - 2013,2182-10-16
10009628,M,58,2153,2011 - 2013,
10004457,M,65,2140,2011 - 2013,
10015860,M,53,2186,2011 - 2013,
10021487,M,43,2116,2011 - 2013,
10013049,M,52,2114,2011 - 2013,
10026255,M,66,2200,2011 - 2013,2201-07-13
10023771,M,70,2113,2011 - 2013,
10023117,M,53,2170,2011 - 2013,2175-07-20
10022041,M,64,2187,2011 - 2013,
10005348,M,76,2128,2011 - 2013,
10009035,M,28,2161,2011 - 2013,
10018081,M,79,2133,2011 - 2013,2134-10-28
10038933,M,34,2148,2011 - 2013,
10037975,M,60,2185,2014 - 2016,2185-01-22
10035185,M,70,2120,2014 - 2016,
10014354,M,60,2146,2014 - 2016,
10022880,M,66,2177,2014 - 2016,
10004720,M,61,2183,2014 - 2016,2186-11-17
10019385,M,44,2180,2014 - 2016,
10029484,M,64,2160,2014 - 2016,
10037861,M,77,2115,2014 - 2016,2117-03-24
10026406,M,45,2129,2014 - 2016,
10007818,M,69,2146,2014 - 2016,2146-07-12
10020740,M,56,2150,2014 - 2016,
10038999,M,45,2131,2014 - 2016,
10004235,M,47,2196,2014 - 2016,
10022017,M,59,2189,2014 - 2016,
10002495,M,81,2141,2014 - 2016,
10018845,M,91,2184,2014 - 2016,2184-11-22
10020944,M,72,2131,2014 - 2016,2131-04-28
10016150,M,69,2142,2014 - 2016,
10005866,M,57,2146,2014 - 2016,2149-11-21
10026354,M,34,2119,2014 - 2016,
10019917,M,44,2182,2014 - 2016,
10019777,M,51,2187,2014 - 2016,2187-04-29
10005817,M,66,2132,2014 - 2016,2135-01-19
10009049,M,56,2174,2014 - 2016,
10015931,M,87,2176,2014 - 2016,2177-03-29
10006053,M,52,2111,2014 - 2016,2111-11-15
10012552,M,78,2140,2014 - 2016,
10004733,M,51,2174,2014 - 2016,
10021118,M,62,2161,2014 - 2016,
10018501,M,83,2141,2014 - 2016,
10007058,M,48,2167,2014 - 2016,
10038992,M,70,2185,2014 - 2016,
1 subject_id gender anchor_age anchor_year anchor_year_group dod
2 10014729 F 21 2125 2011 - 2013
3 10003400 F 72 2134 2011 - 2013 2137-09-02
4 10002428 F 80 2155 2011 - 2013
5 10032725 F 38 2143 2011 - 2013 2143-03-30
6 10027445 F 48 2142 2011 - 2013 2146-02-09
7 10037928 F 78 2175 2011 - 2013
8 10001725 F 46 2110 2011 - 2013
9 10040025 F 64 2143 2011 - 2013 2148-02-07
10 10008454 F 26 2110 2011 - 2013
11 10020640 F 91 2153 2011 - 2013 2154-02-04
12 10014078 F 60 2166 2011 - 2013
13 10007795 F 53 2136 2011 - 2013
14 10001217 F 55 2157 2011 - 2013
15 10019172 F 63 2118 2011 - 2013
16 10039831 F 57 2115 2011 - 2013
17 10007928 F 59 2129 2011 - 2013
18 10019003 F 65 2148 2011 - 2013 2155-12-03
19 10002930 F 48 2193 2011 - 2013 2201-12-24
20 10010471 F 89 2155 2014 - 2016 2155-12-07
21 10027602 F 71 2201 2014 - 2016
22 10031757 F 67 2137 2014 - 2016 2137-10-31
23 10016742 F 58 2178 2014 - 2016
24 10039997 F 67 2134 2014 - 2016
25 10012853 F 91 2175 2014 - 2016
26 10000032 F 52 2180 2014 - 2016 2180-09-09
27 10031404 F 82 2113 2014 - 2016
28 10029291 F 50 2123 2014 - 2016
29 10005909 F 40 2144 2014 - 2016
30 10039708 F 46 2138 2014 - 2016
31 10019568 F 59 2120 2014 - 2016
32 10020187 F 63 2169 2014 - 2016
33 10016810 F 66 2185 2014 - 2016
34 10038081 F 63 2115 2014 - 2016 2115-10-12
35 10021312 F 55 2113 2014 - 2016
36 10006580 F 63 2137 2014 - 2016
37 10010867 F 28 2147 2014 - 2016
38 10020786 F 86 2189 2014 - 2016
39 10020306 F 74 2129 2014 - 2016
40 10023239 F 29 2137 2014 - 2016
41 10018328 F 83 2154 2014 - 2016
42 10008287 F 43 2145 2014 - 2016
43 10036156 F 88 2157 2014 - 2016
44 10015272 F 78 2137 2014 - 2016
45 10022281 M 84 2125 2011 - 2013
46 10035631 M 63 2112 2011 - 2013 2116-03-12
47 10024043 M 67 2117 2011 - 2013 2117-06-26
48 10025612 M 82 2125 2011 - 2013
49 10003046 M 64 2154 2011 - 2013
50 10021666 M 87 2172 2011 - 2013 2172-04-19
51 10018423 M 37 2162 2011 - 2013
52 10025463 M 66 2136 2011 - 2013 2137-10-09
53 10017492 M 84 2114 2011 - 2013 2116-07-05
54 10004422 M 78 2111 2011 - 2013
55 10011398 M 67 2146 2011 - 2013
56 10021938 M 65 2181 2011 - 2013 2182-10-16
57 10009628 M 58 2153 2011 - 2013
58 10004457 M 65 2140 2011 - 2013
59 10015860 M 53 2186 2011 - 2013
60 10021487 M 43 2116 2011 - 2013
61 10013049 M 52 2114 2011 - 2013
62 10026255 M 66 2200 2011 - 2013 2201-07-13
63 10023771 M 70 2113 2011 - 2013
64 10023117 M 53 2170 2011 - 2013 2175-07-20
65 10022041 M 64 2187 2011 - 2013
66 10005348 M 76 2128 2011 - 2013
67 10009035 M 28 2161 2011 - 2013
68 10018081 M 79 2133 2011 - 2013 2134-10-28
69 10038933 M 34 2148 2011 - 2013
70 10037975 M 60 2185 2014 - 2016 2185-01-22
71 10035185 M 70 2120 2014 - 2016
72 10014354 M 60 2146 2014 - 2016
73 10022880 M 66 2177 2014 - 2016
74 10004720 M 61 2183 2014 - 2016 2186-11-17
75 10019385 M 44 2180 2014 - 2016
76 10029484 M 64 2160 2014 - 2016
77 10037861 M 77 2115 2014 - 2016 2117-03-24
78 10026406 M 45 2129 2014 - 2016
79 10007818 M 69 2146 2014 - 2016 2146-07-12
80 10020740 M 56 2150 2014 - 2016
81 10038999 M 45 2131 2014 - 2016
82 10004235 M 47 2196 2014 - 2016
83 10022017 M 59 2189 2014 - 2016
84 10002495 M 81 2141 2014 - 2016
85 10018845 M 91 2184 2014 - 2016 2184-11-22
86 10020944 M 72 2131 2014 - 2016 2131-04-28
87 10016150 M 69 2142 2014 - 2016
88 10005866 M 57 2146 2014 - 2016 2149-11-21
89 10026354 M 34 2119 2014 - 2016
90 10019917 M 44 2182 2014 - 2016
91 10019777 M 51 2187 2014 - 2016 2187-04-29
92 10005817 M 66 2132 2014 - 2016 2135-01-19
93 10009049 M 56 2174 2014 - 2016
94 10015931 M 87 2176 2014 - 2016 2177-03-29
95 10006053 M 52 2111 2014 - 2016 2111-11-15
96 10012552 M 78 2140 2014 - 2016
97 10004733 M 51 2174 2014 - 2016
98 10021118 M 62 2161 2014 - 2016
99 10018501 M 83 2141 2014 - 2016
100 10007058 M 48 2167 2014 - 2016
101 10038992 M 70 2185 2014 - 2016
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+43 -26
View File
@@ -243,27 +243,55 @@ Grafana dashboard JSON template for alert quality overview.
| Clinical Usage | 10 |
| Composite | 9.0 |
> **Status:** Complete — see [phase-34-plan.md](plans/phase-34-plan.md) for full implementation detail, tests, and file inventory.
Architecture: Each alert carries a structured explanation payload alongside the existing `Details` string. A new `AlertExplanation` value object captures score contributors, trend context, and medication context at alert creation time. Scoring consumers (NEWS2, SOFA, GCS) emit contributor breakdowns. The `TrendDetector` attaches trend summaries. `MedicationCorrelationHelper` attaches recent medication context. All explanation data is serialized as JSONB on the `ClinicalAlert` row so the explanation is immutable — it reflects the state at alert time, not query time.
> **Prerequisite:** Phase 33 for feedback loop. No hard technical dependency but sequencing allows feedback data to inform which explanations clinicians value.
### What exists
After Phase 33:
After Phase 34 (implemented):
- `ClinicalAlert.Details` is a free-text string, sometimes containing score values
- `TrendDetector` returns `TrendOutcome` enum but no narrative description
- `MedicationCorrelationHelper.TryAnnotateDetailsAsync()` appends drug info to the details string
- NEWS2, SOFA, GCS scoring returns aggregate scores but not per-component breakdowns to the alert layer
- No structured explanation model
- `AlertExplanation` value object with `ScoreContributor`, `TrendContext`, `MedicationContext`, and `NarrativeSummary`
- `ClinicalAlert.Explanation` — nullable JSONB column, immutable at alert creation
- NEWS2, SOFA, GCS, and Trend detectors assemble explanation at alert creation; `alert.generated` outbox/Kafka payloads include `explanation`
- `MedicationCorrelationHelper.TryGetContextAsync()` — structured medication context (replaces string-append to `Details`)
- Scoring results carry contributors: `News2Result`, `SofaScoringResult`, `GcsResult`, `TrendResult`
- `AlertResponse` DTO with `Explanation`; GET/list/acknowledge/resolve endpoints return it
- Dashboard `AlertReasoning.vue` and alert surfaces consume structured explanation; legacy alerts fall back to `Details`
- Simulator `AlertResponse.Explanation` + `ExpectedOutcomeValidator` with `narrativeContains` on key scenarios
- Elasticsearch indexes `NarrativeSummary`; data lake Parquet includes `explanation_json`; ward gateway sync forwards explanation
- `ExplainableAlertsTests` (10 tests) + `scripts/run-phase34-verification.sh`
- `Details` string unchanged for backward compatibility with threshold-only and legacy alerts
### What needs to be built
Six steps, in order.
Nothing — Phase 34 is complete. **Next:** [Phase 35 — Alert Lifecycle Analytics](#phase-35--alert-lifecycle-analytics).
### Verification Checklist (Phase 34)
- [x] NEWS2 alert includes per-component score contributors in explanation
- [x] SOFA alert includes per-organ-system contributors
- [x] GCS alert includes Eye/Verbal/Motor breakdown
- [x] Trend-triggered alerts include trend context with percent change and duration
- [x] Medication-correlated alerts include drug context
- [x] Narrative summary is human-readable and accurate
- [x] Existing alerts with null explanation still serialize correctly
- [x] Alert GET endpoints return explanation object
- [x] No change to scoring algorithm outputs (same scores, same thresholds)
- [x] Migration applies cleanly on existing data
- [x] Dashboard and simulator consume explanation
- [x] Downstream consumers (ES, data lake, gateway sync) include explanation fields
---
#### Step 1 — Explanation Value Objects
#### Implementation reference (Steps 17)
The step-by-step design below is retained for interview prep and onboarding. All steps are implemented; see [phase-34-plan.md](plans/phase-34-plan.md) for code paths and tests.
<details>
<summary>Original step-by-step design (Steps 16)</summary>
**`Domains/ValueObjects/AlertExplanation.cs`** (NEW):
@@ -380,20 +408,7 @@ Extend alert GET endpoints to include the `Explanation` object. The explanation
}
```
---
### Verification Checklist (Phase 34)
- [ ] NEWS2 alert includes per-component score contributors in explanation
- [ ] SOFA alert includes per-organ-system contributors
- [ ] GCS alert includes Eye/Verbal/Motor breakdown
- [ ] Trend-triggered alerts include trend context with percent change and duration
- [ ] Medication-correlated alerts include drug context
- [ ] Narrative summary is human-readable and accurate
- [ ] Existing alerts with null explanation still serialize correctly
- [ ] Alert GET endpoints return explanation object
- [ ] No change to scoring algorithm outputs (same scores, same thresholds)
- [ ] Migration applies cleanly on existing data
</details>
---
@@ -415,6 +430,8 @@ After Phase 34:
- `ClinicalAlert` has `Status` transitions: Open → Acknowledged → Resolved, Open → Escalated → Acknowledged → Resolved
- `AcknowledgedAt`, `ResolvedAt` timestamps exist on the entity
- `ClinicalAlert.Explanation` JSONB with immutable score contributors, trend, and medication context at alert time
- `AlertResponse` exposes explanation to dashboard and simulator; `Details` retained for legacy consumers
- `ClinicalAuditLog` captures some transitions but is not structured for time-series analytics
- No dedicated lifecycle event log
- No computed lifecycle metrics (median ack time, escalation rate)
@@ -673,7 +690,7 @@ Authorized for `Admin` role only. PUT upserts a rule and invalidates the Redis c
Architecture: A correlation engine groups alerts that fire within a configurable time window for the same patient into a single clinical narrative. Correlated alerts are linked by a shared `CorrelationGroupId`. The first alert in a group becomes the primary; subsequent alerts within the window attach as secondary. The group carries a composite explanation built from Phase 34 individual explanations. Clinicians see one bundled notification with the full picture instead of multiple independent alerts.
> **Prerequisite:** Phase 34 (Explainable Alerts) for structured explanations to compose into bundles.
> **Prerequisite:** Phase 34 (Explainable Alerts) **complete** structured explanations compose into bundle narratives.
### What exists
@@ -681,9 +698,9 @@ After Phase 36:
- Alerts fire independently per scoring consumer and trend detector
- No correlation between simultaneous alerts for the same patient
- Phase 34 `AlertExplanation` provides structured per-alert context
- Phase 34 `AlertExplanation` **shipped** — per-alert `NarrativeSummary`, score contributors, trend, and medication context available on `AlertResponse`
- `AlertSuppressionService` prevents duplicate alert types but not cross-type bundling
- Medication correlation annotates individual alerts but does not group them
- Medication correlation attaches structured context to individual alerts but does not group them
### What needs to be built
@@ -953,7 +970,7 @@ After Phase 38:
- qSOFA scoring implementation in dedicated service
- GCS scoring implementation in dedicated service
- Each has different input shapes, output shapes, and integration points
- Phase 34 added contributor extraction but each scoring system returns it differently
- Phase 34 added contributor extraction but each scoring system returns it differently (**implemented** — unify via `ScoreResult.Contributors` in Phase 39)
### What needs to be built
+3 -20
View File
@@ -565,26 +565,9 @@ Any compromised container on the Docker network can read/write/delete clinical d
---
## P3 — No token refresh or revocation mechanism
## ~~P3 — No token refresh or revocation mechanism~~ DONE
### Problem
JWT tokens are issued with a configurable expiration but there is no refresh token flow and no token revocation/blacklist. A compromised token remains valid until natural expiration. There is no `POST /auth/refresh` or `POST /auth/revoke` endpoint.
### Why fix
Clinical sessions may last entire shifts (8-12 hours). Short token lifetimes require frequent re-authentication, disrupting clinical workflows. Long lifetimes without revocation mean a stolen token grants extended access. Compromised accounts cannot be locked out until the token expires.
### How to fix
1. Add refresh token support: issue a long-lived opaque refresh token stored in the database alongside the access token.
2. Add `POST /api/v1/auth/refresh` — validate refresh token, issue new access token.
3. Add `POST /api/v1/auth/revoke` — invalidate refresh token and optionally blacklist the access token (via Redis TTL set matching remaining token lifetime).
4. Add `LastLoginAt` update on token refresh (already exists on `ClinicalUser`).
**Files:** `AuthController.cs`, `AuthService.cs`, `ClinicalUser.cs` (add `RefreshToken`, `RefreshTokenExpiresAt`), migration.
**Dependency:** None.
Implemented: `RefreshToken` entity with DB-backed storage, `POST /api/v1/auth/refresh` (rotate refresh token + issue new access token), `POST /api/v1/auth/logout` (revoke refresh token server-side). Access token reduced to 15 min, refresh token 7 days. Frontend auto-refreshes before expiry, retries on 401, and redirects to login on refresh failure. Logout button in header, sidebar, and mobile nav. Audit logged as `USER_LOGOUT` and `TOKEN_REFRESHED`.
---
@@ -743,7 +726,7 @@ Different hospitals and clinical settings have different protocols. CMS Sepsis S
| 19 | JWT key not validated on startup | P3 | D | Open |
| 20 | No authorization failure audit | P3 | D | Open |
| 21 | Elasticsearch security disabled | P3 | D | Open |
| 22 | No token refresh/revocation | P3 | D | Open |
| 22 | ~~No token refresh/revocation~~ | P3 | D | **Done** |
| 23 | No request timing metrics | P5 | E | Open |
| 24 | Background service error metrics | P5 | E | Open |
| 25 | Thin concurrent/resilience tests | P5 | E | Open |
Binary file not shown.
+295
View File
@@ -0,0 +1,295 @@
# VigilCare Dashboard — Charts & Visualizations Guide
This document describes every chart in the VigilCare Clinical dashboard: what it shows, how to read it, and what the colors and thresholds mean.
---
## Table of Contents
1. [Vital Signs Trends (5 Charts)](#1-vital-signs-trends)
2. [NEWS2 History](#2-news2-history-chart)
3. [GCS History](#3-gcs-history-chart)
4. [qSOFA Screen History](#4-qsofa-screen-history-chart)
5. [SOFA History](#5-sofa-history-chart)
6. [Alert Quality](#6-alert-quality-chart)
7. [Department Acuity Bar](#7-department-acuity-bar)
---
## 1. Vital Signs Trends
**Location:** Patient Detail view — rendered as a 2×3 grid of line charts
**Component:** `TrendsGrid.vue``VitalTrendChart.vue``VitalChart.vue`
### Overview
Five individual line charts, one per vital sign, showing how each measurement has changed over time. Each chart plots recorded observations on the X-axis (time) and the vital value on the Y-axis.
### Individual Charts
| Vital Sign | Y-Axis Range | Line Color | Unit |
|---|---|---|---|
| Heart Rate | 30 180 | Red (#ef4444) | bpm |
| Respiratory Rate | 0 40 | Blue (#3b82f6) | breaths/min |
| Systolic Blood Pressure | 60 250 | Purple (#8b5cf6) | mmHg |
| SpO₂ (Oxygen Saturation) | 80 100 | Cyan (#06b6d4) | % |
| Temperature | 34 42 | Amber (#f59e0b) | °C |
### How to Read
- **Each dot** on the line represents a single recorded observation at a specific time.
- **The line** connecting the dots shows the trend direction — rising, falling, or stable.
- **Y-axis** is fixed to a clinically relevant range so that values near the edges indicate an abnormal reading.
- **X-axis** labels show timestamps of when each observation was recorded.
### Medication Markers
Dashed purple vertical lines may appear on any vital chart. These mark the time a medication was administered.
- **Hover over** a purple line to see the drug name, dose, route, and timestamp.
- Medication markers help correlate treatment events with changes in vital signs (e.g., did a dose of antihypertensive cause blood pressure to drop?).
- The tooltip activates within a 15-minute window of the marker.
---
## 2. NEWS2 History Chart
**Location:** Patient Detail view
**Component:** `News2History.vue`
### What It Shows
The **National Early Warning Score 2 (NEWS2)** over time. NEWS2 is an aggregate score (020) calculated from six physiological parameters: respiratory rate, oxygen saturation, systolic blood pressure, pulse rate, consciousness level, and temperature. A higher score indicates greater clinical deterioration.
### How to Read
- **Blue line** traces the NEWS2 score at each assessment point.
- **Y-axis:** 0 to 20 (total NEWS2 range).
- **X-axis:** Time of each assessment.
- **Each dot** (radius 4px) is one assessment — hover for the exact score.
### Background Color Bands (Severity Zones)
The chart background is color-filled to show the severity of the score:
| Background Color | Score Range | Meaning |
|---|---|---|
| **Green** | 0 4 | **Low risk** — routine monitoring is appropriate |
| **Amber** | 5 6 | **Medium risk** — increased frequency of monitoring; urgent clinical review needed |
| **Red** | 7 20 | **High risk** — emergency assessment required; continuous monitoring |
**Key insight:** If the line enters the amber or red zone, the patient's condition is deteriorating and requires escalated care. A line that stays in or returns to the green zone suggests clinical stability or improvement.
---
## 3. GCS History Chart
**Location:** Patient Detail view
**Component:** `GcsHistory.vue`
### What It Shows
The **Glasgow Coma Scale (GCS)** over time. GCS measures a patient's level of consciousness across three components: Eye opening (14), Verbal response (15), and Motor response (16). The total ranges from 3 (deep unresponsiveness) to 15 (fully alert).
### How to Read
This chart displays **four lines**:
| Line | Color | Style | Range |
|---|---|---|---|
| **GCS Total** | Dark accent | Solid, thick (2px) | 3 15 |
| Eye component | Blue (#3b82f6) | Dashed | 1 4 |
| Verbal component | Purple (#8b5cf6) | Dashed | 1 5 |
| Motor component | Cyan (#06b6d4) | Dashed | 1 6 |
- **Focus on the solid total line** for an overall consciousness assessment.
- **Use the dashed component lines** to identify which specific domain is changing (e.g., verbal response dropping while motor stays stable may indicate different clinical concerns than all three declining together).
### Point Colors on the Total Line (Severity)
The dots on the GCS total line change color to indicate severity:
| Point & Fill Color | Score Range | Severity |
|---|---|---|
| **Green** (#22c55e) | 13 15 | **Mild** — patient is alert and oriented |
| **Amber** (#f59e0b) | 9 12 | **Moderate** — impaired consciousness |
| **Red** (#dc2626) | 3 8 | **Severe** — patient may need intubation/airway protection |
**Key insight:** A downward trend (especially into the red zone) signals worsening neurological status. The tooltip footer displays the severity label for each point.
---
## 4. qSOFA Screen History Chart
**Location:** Patient Detail view
**Component:** `QsofaHistory.vue`
### What It Shows
The **quick Sequential Organ Failure Assessment (qSOFA)** screen results over time. qSOFA is a bedside screening tool for sepsis risk using three criteria:
1. **Respiratory rate ≥ 22 breaths/min**
2. **Systolic blood pressure ≤ 100 mmHg**
3. **Altered mentation** (GCS < 15)
Each criterion is scored as either present (1) or absent (0), giving a total of 03 active criteria.
### How to Read
This is a **dual-axis chart**:
**Left Y-axis — Active Criteria Count (03):**
- The solid stepped line shows how many qSOFA criteria are met at each assessment.
- Points are color-coded by risk level.
**Right Y-axis — Individual Criteria (normalized 01):**
- Three dashed lines show whether each individual criterion is present (1) or absent (0):
- **Respiratory rate:** Blue (#3b82f6) dashed
- **Systolic BP:** Purple (#8b5cf6) dashed
- **Altered mentation:** Pink (#ec4899) dashed
### Point Colors & Shapes
| Color | Count | Meaning |
|---|---|---|
| **Green** (#22c55e) | 0 criteria | **Screen negative** — low sepsis risk |
| **Amber** (#f59e0b) | 1 criterion | **Screen negative** — one criterion present, monitor closely |
| **Red** (#dc2626) | ≥ 2 criteria | **Screen positive** — suspected sepsis; escalate care |
- **Circle points:** Standard assessment.
- **Star points (larger, radius 7):** An alert was fired at this assessment time.
**Key insight:** A score of ≥ 2 is the critical threshold — this is a **positive qSOFA screen** suggesting possible sepsis. The tooltip shows whether the screen was positive/negative and whether an alert fired.
---
## 5. SOFA History Chart
**Location:** Patient Detail view
**Component:** `SofaHistory.vue`
### What It Shows
The **Sequential Organ Failure Assessment (SOFA)** score over time. SOFA provides a more detailed assessment of organ dysfunction than qSOFA, scoring six organ systems on a 04 scale each, for a total range of 024.
### How to Read
This chart has **two visual layers**:
#### Layer 1: Stacked Area (Organ System Contributions)
Six semi-transparent colored areas stacked on top of each other, showing how much each organ system contributes to the total SOFA score:
| Organ System | Color |
|---|---|
| Respiratory | Blue (#3b82f6) |
| Coagulation | Purple (#8b5cf6) |
| Liver | Amber (#f59e0b) |
| Cardiovascular | Red (#ef4444) |
| CNS (Central Nervous System) | Pink (#ec4899) |
| Renal | Cyan (#06b6d4) |
The **height of each colored band** represents that organ system's individual score (04). The **total stack height** equals the total SOFA score.
#### Layer 2: Total SOFA Line (Overlaid)
A solid line with color-coded dots sits on top of the stacked areas:
| Point Color | Score Range | Risk Level |
|---|---|---|
| **Green** (#22c55e) | 0 5 | **Low** — minimal organ dysfunction |
| **Amber** (#f59e0b) | 6 9 | **Moderate** — significant organ dysfunction |
| **Red** (#dc2626) | ≥ 10 | **High** — severe organ dysfunction; high mortality risk |
**Key insight:** This chart answers two questions at once: (1) How severe is overall organ dysfunction? (read the total line) and (2) Which organ systems are driving the score? (read the colored stacked bands). For example, if the respiratory (blue) band is growing while others stay flat, the lungs are the primary concern.
---
## 6. Alert Quality Chart
**Location:** Alert Quality Analytics view
**Component:** `AlertQualityChart.vue`
### What It Shows
Quality and performance metrics for the clinical alerting system over time. This chart evaluates how useful and accurate the alerts are. It can display one of four metrics at a time (controlled by the view):
| Metric | What It Measures |
|---|---|
| **Useful Rate %** | Percentage of alerts that clinicians rated as clinically useful |
| **False Positive Rate %** | Percentage of alerts that turned out to be false alarms |
| **Acknowledgement Rate %** | Percentage of alerts that were acknowledged by staff |
| **Would Act Rate %** | Percentage of alerts where a clinician said they would take clinical action |
### How to Read
- **Blue line** traces the metric value over time.
- **Light blue fill** below the line provides visual weight.
- **Y-axis:** 0% to 100%.
- **X-axis:** Alert types or time windows (sorted by alert window start time).
- **Each dot** is one data point — hover for the exact percentage.
**Key insight:**
- **Useful Rate / Would Act Rate:** Higher is better — clinicians find the alerts actionable.
- **False Positive Rate:** Lower is better — high values indicate alert fatigue risk.
- **Acknowledgement Rate:** Higher is better — low values may indicate staff are ignoring alerts.
This chart is used by system administrators and quality improvement teams to tune alerting thresholds and reduce alert fatigue.
---
## 7. Department Acuity Bar
**Location:** Department Overview view (inside each Department Card)
**Component:** `AcuityBar.vue`
### What It Shows
A horizontal stacked bar showing the **distribution of patient acuity levels** within a department at a glance.
### How to Read
The bar is divided into up to three colored segments, each proportional to the number of patients at that acuity level:
| Segment Color | Acuity Level | Meaning |
|---|---|---|
| **Green** (#22c55e) | Low | Stable patients requiring routine care |
| **Amber** (#f59e0b) | Medium | Patients requiring increased monitoring or clinical attention |
| **Red** (#dc2626) | High | Critical patients requiring urgent or intensive care |
| **Gray** (#d1d5db) | Empty | No patients (shown when department has capacity) |
- **Segment width** is proportional to the percentage of patients at each level.
- **Count labels** below the bar show the exact number of patients in each category.
- **Hover** over a segment to see the exact count.
**Key insight:** A bar that is mostly red indicates a department under heavy clinical pressure. A bar that shifts from green to amber/red over time suggests a department's workload is escalating.
---
## Universal Visual Conventions
### Color Coding (Traffic Light System)
Across all clinical charts, the dashboard uses a consistent traffic-light color scheme:
| Color | Hex | Meaning |
|---|---|---|
| **Green** | #22c55e | Safe / Low risk / Normal |
| **Amber** | #f59e0b | Warning / Medium risk / Monitor |
| **Red** | #dc2626 or #ef4444 | Critical / High risk / Escalate |
### Dark Mode
All charts support dark mode. Grid lines, text, and backgrounds automatically adjust:
- **Light mode:** Dark text (#6b7280), subtle gridlines
- **Dark mode:** Light text (#9ca3af), subdued gridlines
### Interactivity
- **Hover tooltips** are available on all charts — hover over any data point for exact values.
- **Legends** appear at the bottom of multi-dataset charts (GCS, qSOFA, SOFA).
- **Medication markers** (purple dashed lines) can appear on any vital signs chart.
### Responsiveness
All charts maintain a 16:9 aspect ratio and have a minimum height of 220px for readability on smaller screens. Animations are disabled when the user has the "prefers reduced motion" accessibility setting enabled.
@@ -0,0 +1,268 @@
# Guide 16: PHI Encryption with Data Protection API
## What is PHI and Why Encrypt It?
**PHI** stands for **Protected Health Information** — any data that can identify a patient and relates to their health. This includes names, dates of birth, medical record numbers, allergies, and emergency contact details. Healthcare regulations (HIPAA in the US, GDPR in the EU) require that PHI be **encrypted at rest** — meaning even if someone steals the database files or a backup, they can't read the patient data without the encryption key.
**Encryption at rest** protects against scenarios like:
- A database backup is accidentally uploaded to a public S3 bucket
- A disgruntled employee copies the database files
- The database server's hard drive is stolen or improperly disposed of
Without encryption, the database stores plaintext: `first_name = "Sarah"`. With encryption: `first_name = "CfDJ8Nrq7...long encrypted string..."`. The application decrypts transparently when reading, and encrypts transparently when writing.
## What is the Data Protection API?
**.NET's Data Protection API (DPAPI)** is a built-in framework for encrypting and decrypting data. It manages encryption keys, handles key rotation, and provides a simple `Protect()`/`Unprotect()` interface. You don't need to pick cipher algorithms or manage IVs manually — DPAPI handles the cryptographic details.
Key concepts:
- **`IDataProtectionProvider`**: The factory that creates protectors. Registered with DI at startup.
- **`IDataProtector`**: An instance tied to a specific **purpose string**. A protector created with purpose `"VigilCare.PatientPhi.v1"` can only decrypt data that was encrypted with the same purpose. This prevents accidentally decrypting data meant for a different part of the application.
- **Key ring**: DPAPI stores encryption keys on disk (configurable path). Keys are automatically rotated and expired on a schedule. Old keys are kept so previously encrypted data can still be decrypted.
---
## How PHI Encryption Works in This Project
```
Application reads patient.FirstName
EF Core Value Converter
│ calls crypto.Decrypt(ciphertext)
PhiEncryptionService.Decrypt()
│ calls _protector.Unprotect(ciphertext)
Returns "Sarah" to the application
```
```
Application writes patient.FirstName = "Sarah"
EF Core Value Converter
│ calls crypto.Encrypt("Sarah")
PhiEncryptionService.Encrypt()
│ calls _protector.Protect("Sarah")
Stores "CfDJ8Nrq7..." in PostgreSQL
```
The application code never sees ciphertext — it works with plaintext strings as usual. The encryption and decryption happen inside EF Core value converters (see Guide 4), invisible to the rest of the codebase.
---
## Configuration
### PhiEncryptionOptions
```csharp
public class PhiEncryptionOptions
{
public const string Section = "PhiEncryption";
public string ProtectorPurpose { get; set; } = "VigilCare.PatientPhi.v1";
public string SearchTokenKey { get; set; } = null!;
public bool LogListAccess { get; set; } = true;
}
```
| Setting | Purpose |
|---------|---------|
| `ProtectorPurpose` | The purpose string for the data protector. Changing this creates a new encryption scope — old data can't be decrypted with a new purpose without migration. |
| `SearchTokenKey` | HMAC key for generating searchable name tokens (explained below). Separate from the encryption key. |
| `LogListAccess` | Whether to log PHI access for list/search operations (compliance auditing). |
### appsettings.json
```json
{
"PhiEncryption": {
"ProtectorPurpose": "VigilCare.PatientPhi.v1",
"SearchTokenKey": "DEV-ONLY-HMAC-KEY-REPLACE-IN-PRODUCTION-32bytes!!",
"LogListAccess": true
},
"DataProtection": {
"KeyPath": "./data-protection-keys"
}
}
```
### Registration in Program.cs
```csharp
builder.Services.AddDataProtection()
.PersistKeysToFileSystem(new DirectoryInfo(
builder.Configuration["DataProtection:KeyPath"]
?? "./data-protection-keys"))
.SetApplicationName("VigilCareClinical");
```
`PersistKeysToFileSystem` stores the encryption keys in a local directory. In production, you'd use `PersistKeysToAzureBlobStorage()` or `PersistKeysToStackExchangeRedis()` so keys survive container restarts. `SetApplicationName` ensures all instances of the application share the same key ring.
---
## The PhiEncryptionService
```csharp
public class PhiEncryptionService : IPhiEncryptionService
{
private readonly IDataProtector _protector;
private readonly byte[] _searchKey;
public PhiEncryptionService(
IDataProtectionProvider provider,
IOptions<PhiEncryptionOptions> options)
{
var opts = options.Value;
_protector = provider.CreateProtector(opts.ProtectorPurpose);
_searchKey = /* derived from SearchTokenKey */;
}
public string Encrypt(string plaintext)
{
if (string.IsNullOrEmpty(plaintext)) return plaintext;
return _protector.Protect(plaintext);
}
public string Decrypt(string ciphertext)
{
if (string.IsNullOrEmpty(ciphertext)) return ciphertext;
if (!IsEncrypted(ciphertext)) return ciphertext; // plaintext rows still readable
return _protector.Unprotect(ciphertext);
}
public bool IsEncrypted(string value) =>
value.StartsWith("CfDJ8", StringComparison.Ordinal) || value.Length > 50;
}
```
**The `IsEncrypted` check**: During the migration from plaintext to encrypted data, some rows may still contain plaintext. The `Decrypt` method checks if the value looks encrypted (DPAPI-encrypted values start with `"CfDJ8"` and are much longer than typical names). If not, it returns the value as-is. This lets the application work correctly with a partially-migrated database.
### How EF Core Uses the Service
The `PatientPhiConverterConfigurator` (from Guide 4) wires the service into EF Core value converters:
```csharp
entity.Property(p => p.FirstName)
.HasConversion(
v => crypto.Encrypt(v), // called on every INSERT/UPDATE
v => crypto.Decrypt(v)); // called on every SELECT
entity.Property(p => p.DateOfBirth)
.HasConversion(
v => crypto.Encrypt(v.ToString("yyyy-MM-dd")),
v => DateOnly.Parse(crypto.Decrypt(v)));
```
Encrypted columns: `first_name`, `last_name`, `date_of_birth`, `allergies`, `emergency_contact_name`, `emergency_contact_phone`.
---
## The Search Problem: HMAC Name Tokens
**The problem**: If names are encrypted, you can't search for patients by name. SQL `WHERE first_name LIKE '%Sarah%'` doesn't work on ciphertext because each encryption of "Sarah" produces a different ciphertext (DPAPI uses random IVs).
**The solution**: Store a deterministic, one-way hash of the name in a separate column (`name_search_token`). To search, hash the query the same way and compare hashes.
```csharp
public string ComputeNameSearchToken(string firstName, string lastName)
{
var normalized = $"{firstName.Trim().ToLowerInvariant()}|{lastName.Trim().ToLowerInvariant()}";
using var hmac = new HMACSHA256(_searchKey);
var hash = hmac.ComputeHash(Encoding.UTF8.GetBytes(normalized));
return Convert.ToHexString(hash).ToLowerInvariant();
}
```
**What is HMAC?** HMAC (Hash-based Message Authentication Code) is a keyed hash function. Unlike plain SHA-256 (where anyone can compute the same hash), HMAC requires a secret key. Without the key, an attacker who has the hash can't reverse it to find the name, and can't compute hashes for other names to test against.
**Why not just use SHA-256?** Plain SHA-256 is vulnerable to rainbow table attacks — an attacker precomputes hashes for common names ("Sarah Smith" → hash, "John Doe" → hash) and compares them against the stored hashes. HMAC with a secret key makes this impossible because the attacker would need the key to compute valid hashes.
The search flow:
1. User searches for "Sarah Smith"
2. Application computes `HMACSHA256("sarah|smith")``"a1b2c3d4..."`
3. SQL query: `WHERE name_search_token = 'a1b2c3d4...'`
4. Matching rows are returned, and EF Core's value converter decrypts the actual names
---
## PHI Access Logging
Every access to patient data is logged for compliance auditing:
```csharp
public class PhiAccessLogService : IPhiAccessLogService
{
public async Task LogViewAsync(Guid patientId, string resourcePath) =>
await WriteAsync(PhiAccessType.View, patientId, resourcePath);
public async Task LogListAsync(string resourcePath, int resultCount,
string? searchQuery = null)
{
var accessType = string.IsNullOrWhiteSpace(searchQuery)
? PhiAccessType.List
: PhiAccessType.Search;
await WriteAsync(accessType, null, resourcePath, resultCount, searchQuery);
}
private async Task WriteAsync(PhiAccessType accessType, Guid? patientId, ...)
{
_db.PhiAccessLogs.Add(new PhiAccessLog
{
AccessType = accessType,
PatientId = patientId,
UserId = _currentUser.UserId!.Value,
UserDisplayName = _currentUser.DisplayName,
ResourcePath = resourcePath,
SearchQueryHash = searchQuery is null ? null : HashQuery(searchQuery),
IpAddress = _currentUser.IpAddress,
CorrelationId = /* from middleware */,
AccessedAt = DateTimeOffset.UtcNow
});
await _db.SaveChangesAsync();
_metrics.PhiAccessLogsTotal.WithLabels(accessType.ToDbString()).Inc();
}
}
```
The log records who accessed what, when, from where, and what they searched for. Search queries are hashed (not stored in plaintext) to avoid storing potentially sensitive search terms.
---
## One-Time Migration: EncryptPhiCommand
For existing databases with plaintext patient data, a CLI command encrypts all rows in place:
```csharp
public static class EncryptPhiCommand
{
public static async Task RunAsync(IServiceProvider services)
{
var patients = await db.Patients.ToListAsync();
foreach (var p in patients)
{
p.NameSearchToken = crypto.ComputeNameSearchToken(p.FirstName, p.LastName);
}
await db.SaveChangesAsync();
Console.WriteLine($"Encrypted {patients.Count} patient records.");
}
}
```
Run via: `dotnet run -- encrypt-phi`
The EF Core value converters handle the actual encryption — loading each patient triggers `Decrypt` (which passes plaintext through via `IsEncrypted` check), and saving triggers `Encrypt` (which encrypts the now-plaintext values). The command also computes `NameSearchToken` for every patient to enable encrypted name search.
---
## Key Takeaways
- **Encryption at rest protects against data breaches** — even if the database is stolen, patient data is unreadable without the encryption keys
- **EF Core value converters make encryption transparent** — application code works with plaintext; encryption/decryption happens automatically on every read and write
- **DPAPI handles key management** — key generation, rotation, and storage are built-in. You don't manage cryptographic primitives directly.
- **Searchable encryption uses HMAC tokens** — deterministic, keyed hashes enable exact-match search on encrypted columns without decrypting every row
- **PHI access logging creates an audit trail** — every view, search, and modification of patient data is recorded with user identity, timestamp, and IP address
- **The purpose string isolates encryption scopes** — data encrypted with `"VigilCare.PatientPhi.v1"` can only be decrypted with the same purpose, preventing cross-contamination between different parts of the application
+201
View File
@@ -0,0 +1,201 @@
# Guide 17: Password Hashing with BCrypt
## What is Password Hashing?
When a user creates an account with the password `"DemoNurse1!"`, the application must store something that lets it verify the password later — but it should **never store the password itself**. If the database is compromised, plaintext passwords would be immediately usable by the attacker.
**Hashing** converts a password into a fixed-length string of random-looking characters using a one-way mathematical function. "One-way" means you can compute the hash from the password, but you can't compute the password from the hash:
```
"DemoNurse1!" → hash() → "$2a$12$xK7W3M...long hash string..."
```
When the user logs in, you hash the submitted password and compare it to the stored hash. If they match, the password is correct — without ever storing the actual password.
## Why BCrypt Specifically?
Not all hash functions are created equal. General-purpose hash functions like SHA-256 are designed to be **fast** — billions of hashes per second on modern hardware. That's a problem for passwords: an attacker who steals the hashed passwords can try billions of guesses per second.
**BCrypt** is specifically designed for password hashing with two key properties:
1. **It's intentionally slow**: BCrypt has a configurable "cost factor" (also called "work factor") that controls how many iterations the algorithm performs. Cost factor 12 (the default) means 2^12 = 4,096 iterations, making each hash take ~250ms. Fast enough that a single login is imperceptible, but an attacker trying 1 million passwords would need ~70 hours.
2. **It includes a built-in salt**: A **salt** is a random value mixed into the hash. Without a salt, two users with the same password would have the same hash — an attacker could build a precomputed table (a "rainbow table") of common passwords and their hashes, then look up matches instantly. BCrypt generates a random salt for each password and embeds it in the output, so identical passwords produce different hashes.
A BCrypt hash looks like this:
```
$2a$12$xK7W3MqQ5Z6Y8B9A0C1D2EfGhIjKlMnOpQrStUvWxYz0123456789Ab
│ │ │ │
│ │ │ └── The hash itself
│ │ └── The salt (22 chars)
│ └── Cost factor (12 = 2^12 iterations)
└── Algorithm version
```
The salt and cost factor are stored right in the hash string, so you don't need a separate column for them.
---
## How BCrypt is Used in This Project
### NuGet Package
```xml
<PackageReference Include="BCrypt.Net-Next" Version="4.0.3" />
```
`BCrypt.Net-Next` is a .NET implementation of the BCrypt algorithm. It provides two key methods: `HashPassword()` and `Verify()`.
### Hashing on Account Creation
When a new user is created, the plaintext password is hashed before storage:
```csharp
public async Task<ClinicalUserResponse> CreateAsync(CreateUserRequest req)
{
// Validate the password meets minimum requirements
ValidatePassword(req.Password);
var user = new ClinicalUser
{
Id = Guid.NewGuid(),
Username = req.Username.Trim(),
PasswordHash = BCrypt.Net.BCrypt.HashPassword(req.Password), // hash here
DisplayName = req.DisplayName.Trim(),
Role = role,
IsActive = true,
CreatedAt = DateTimeOffset.UtcNow,
};
_db.ClinicalUsers.Add(user);
await _db.SaveChangesAsync();
return Map(user);
}
```
`HashPassword(req.Password)` generates a random salt, applies BCrypt with the default cost factor (12), and returns the full hash string. Each call with the same password produces a different hash (because the salt is random).
### Password Validation Rules
```csharp
private static void ValidatePassword(string password)
{
if (string.IsNullOrWhiteSpace(password))
throw new ValidationException("Password is required.", "PASSWORD_REQUIRED");
if (password.Length < 8)
throw new ValidationException(
"Password must be at least 8 characters.", "PASSWORD_TOO_SHORT");
}
```
The minimum length of 8 characters is a baseline. In production, you'd typically also require uppercase, lowercase, digits, and special characters — but the BCrypt hash itself doesn't care about password complexity.
### Verification on Login
When a user logs in, the submitted password is verified against the stored hash:
```csharp
public async Task<LoginResponse> LoginAsync(LoginRequest req)
{
var user = await _db.ClinicalUsers
.FirstOrDefaultAsync(u => u.Username == req.Username && u.IsActive);
if (user is null || !BCrypt.Net.BCrypt.Verify(req.Password, user.PasswordHash))
throw new ValidationException(
"Invalid username or password.", "INVALID_CREDENTIALS");
// ... generate JWT token
}
```
**How does `Verify` work?**
1. Extract the salt and cost factor from the stored hash string
2. Hash the submitted password using the same salt and cost factor
3. Compare the result to the stored hash
4. If they match, the password is correct
**Security note**: The error message says "Invalid username or password" — it does not distinguish between "user not found" and "wrong password." This prevents an attacker from enumerating valid usernames by observing different error messages.
### Seed Data (Development Only)
The user seeder creates demo accounts with hashed passwords:
```csharp
new ClinicalUser
{
Username = "nurse.demo",
PasswordHash = BCrypt.Net.BCrypt.HashPassword("DemoNurse1!"),
DisplayName = "Demo Nurse",
Role = ClinicalRole.Nurse,
},
new ClinicalUser
{
Username = "physician.demo",
PasswordHash = BCrypt.Net.BCrypt.HashPassword("DemoPhysician1!"),
DisplayName = "Dr. Demo Physician",
Role = ClinicalRole.Physician,
},
new ClinicalUser
{
Username = "admin.demo",
PasswordHash = BCrypt.Net.BCrypt.HashPassword("DemoAdmin1!"),
DisplayName = "Demo Admin",
Role = ClinicalRole.Admin,
},
```
Even in development, passwords are never stored in plaintext in the database. The passwords themselves (`"DemoNurse1!"` etc.) appear in the seeder code, but they're only used during the initial seeding and don't persist as plaintext anywhere.
### Database Schema
```csharp
builder.Property(u => u.PasswordHash).HasColumnName("password_hash")
.HasMaxLength(500).IsRequired();
```
`HasMaxLength(500)` accommodates the BCrypt hash string (typically ~60 characters) with room for future algorithm changes that might produce longer hashes.
---
## Cost Factor Considerations
The default cost factor of 12 is a good balance for 2024-era hardware:
| Cost Factor | Iterations | Approximate Time | Use Case |
|-------------|-----------|------------------|----------|
| 10 | 1,024 | ~65ms | Minimum for production |
| 11 | 2,048 | ~130ms | Reasonable for high-traffic APIs |
| **12** | **4,096** | **~250ms** | **Default — good balance** |
| 13 | 8,192 | ~500ms | More security, but login feels slower |
| 14 | 16,384 | ~1s | High-security environments |
The cost factor should be increased over time as hardware gets faster. What takes 250ms today might take 25ms in 10 years. The industry recommendation: choose the highest cost factor that keeps login time under ~500ms for your hardware.
You can customize the cost factor:
```csharp
BCrypt.Net.BCrypt.HashPassword(password, workFactor: 13);
```
Existing hashes with a lower cost factor continue to verify correctly — BCrypt reads the cost factor from the hash string.
---
## What BCrypt Does NOT Protect Against
- **Weak passwords**: BCrypt slows down brute-force attacks, but "password123" will still be cracked quickly. Enforce password complexity rules at the application level.
- **Phishing**: If a user gives their password to an attacker directly, hashing doesn't help.
- **Memory dumps**: While the application is running, the plaintext password exists briefly in memory (during the Verify call). In extremely sensitive environments, you'd use secure memory handling.
- **Credential stuffing**: If a user reuses their password from another breached site, BCrypt can't help. Multi-factor authentication (MFA) addresses this.
---
## Key Takeaways
- **Never store plaintext passwords** — always hash them before writing to the database. There is no valid reason to store or log a user's actual password.
- **BCrypt is purposefully slow** — the cost factor makes brute-force attacks impractical while keeping legitimate logins fast
- **Each hash includes its own salt** — even identical passwords produce different hashes, defeating rainbow table attacks
- **The cost factor is embedded in the hash** — you can increase the cost factor for new passwords without invalidating existing ones
- **Give generic error messages** — "Invalid username or password" prevents username enumeration. Never reveal whether the username or the password was wrong.
- **BCrypt handles the hard parts** — salt generation, iteration count, and comparison are all managed by the library. You call `HashPassword()` and `Verify()` — nothing else.
@@ -0,0 +1,376 @@
# Guide 18: REST API Design & Conventions
## What is a REST API?
A **REST API** (Representational State Transfer) is the most common way to build web APIs. It uses standard HTTP methods and URLs to perform operations on resources. If you've ever used a URL like `GET /api/patients/123`, you've interacted with a REST API.
Key principles:
- **Resources** are the "nouns" — patients, encounters, observations, alerts. Each resource has a URL (called an endpoint).
- **HTTP methods** are the "verbs" — GET (read), POST (create), PUT (replace), PATCH (partial update), DELETE (remove).
- **Status codes** tell the caller what happened — 200 (success), 201 (created), 400 (bad request), 404 (not found), 500 (server error).
- **Stateless** — each request contains all the information the server needs. The server doesn't remember previous requests (that's what JWT tokens are for — they carry identity in every request).
---
## URL Conventions
### Resource Naming
URLs use lowercase, plural nouns with hyphens between words:
```
/api/v1/encounters — collection of encounters
/api/v1/encounters/{encounterId} — one encounter
/api/v1/encounters/{encounterId}/observations — observations within an encounter
/api/v1/alert-thresholds — hyphenated multi-word resource
/api/v1/sepsis-bundles/{bundleId}/elements — nested sub-resource
```
**Why plural?** `GET /api/v1/encounters` returns a list, and `GET /api/v1/encounters/123` returns one item from that list. Using the plural form for both keeps URLs consistent.
**Why `v1`?** Version prefixing lets you introduce breaking changes in a `v2` without breaking existing clients.
### Controller Declaration
```csharp
[ApiController]
[Route("api/v1/encounters")]
[Produces("application/json")]
[Authorize]
public class EncountersController : ControllerBase
```
- **`[ApiController]`** enables automatic model validation, `[FromBody]` inference, and `ProblemDetails` error responses
- **`[Route("api/v1/encounters")]`** sets the base URL for all actions in this controller
- **`[Produces("application/json")]`** declares that all responses are JSON
- **`[Authorize]`** requires authentication for all actions (overridable per-action)
---
## The Response Envelope
Every API response uses a consistent wrapper called `ApiResponse<T>`:
```csharp
public record ApiResponse<T>(bool Success, int StatusCode, T? Data, ApiError? Error)
{
public static ApiResponse<T> Ok(T data) =>
new(true, 200, data, null);
public static ApiResponse<T> Created(T data) =>
new(true, 201, data, null);
public static ApiResponse<T> Fail(int statusCode, string message, string code) =>
new(false, statusCode, default, new ApiError(message, code));
}
public record ApiError(string Message, string Code);
```
**Why an envelope?** Without a wrapper, successful and error responses have completely different shapes, making it harder for clients to parse. With the envelope, every response has the same top-level structure:
```json
// Success
{
"success": true,
"statusCode": 200,
"data": { "id": "...", "status": "Active", ... },
"error": null
}
// Error
{
"success": false,
"statusCode": 400,
"data": null,
"error": {
"message": "Invalid status filter.",
"code": "INVALID_STATUS"
}
}
```
The `code` field (like `"INVALID_STATUS"`) is a machine-readable error identifier. The `message` is human-readable. Clients can switch on the code without parsing the message string, which may change or be localized.
---
## HTTP Methods and Status Codes
### GET — Read (never modifies data)
```csharp
[HttpGet]
[AuthorizePermission(ClinicalPermissions.EncountersRead)]
public async Task<IActionResult> List(
[FromQuery] string? status,
[FromQuery] string? department,
[FromQuery] int page = 1,
[FromQuery] int pageSize = 20)
{
var result = await _encounters.ListAsync(parsedStatus, parsedDepartment, page, pageSize);
return Ok(ApiResponse<object>.Ok(new
{
items = result.Items,
page = result.Page,
pageSize = result.PageSize,
totalCount = result.TotalCount,
totalPages = result.TotalPages
}));
}
```
- Returns **200 OK** on success
- Filter parameters go in query strings (`?status=ACTIVE&department=ICU`)
- Pagination parameters are also query strings (`?page=1&pageSize=20`)
### POST — Create a new resource
```csharp
[HttpPost]
[AuthorizePermission(ClinicalPermissions.ThresholdsWrite)]
public async Task<IActionResult> Create([FromBody] AlertThresholdRequest req)
{
var threshold = await _thresholds.CreateAsync(req);
return StatusCode(201, ApiResponse<AlertThreshold>.Created(threshold));
}
```
- Request body is JSON (`[FromBody]`)
- Returns **201 Created** on success (not 200 — 201 is semantically correct for creation)
- Returns **409 Conflict** if the resource already exists
### POST for Ingest (Batch Operations)
```csharp
[HttpPost]
[AuthorizePermission(ClinicalPermissions.ObservationsIngest)]
public async Task<IActionResult> Ingest(Guid encounterId, [FromBody] BatchIngestRequest req)
{
if (req.Observations.Count == 0)
return BadRequest(ApiResponse<object>.Fail(400,
"At least one observation is required.", "EMPTY_BATCH"));
if (req.Observations.Count > 10)
return BadRequest(ApiResponse<object>.Fail(400,
"Batch size cannot exceed 10 observations.", "BATCH_TOO_LARGE"));
// Process each observation...
return StatusCode(201, ApiResponse<object>.Created(results));
}
```
Batch endpoints accept arrays but enforce limits to prevent abuse or accidental huge payloads.
### PATCH — Partial update
```csharp
[HttpPatch("{alertId:guid}/acknowledge")]
[AuthorizePermission(ClinicalPermissions.AlertsAcknowledge)]
public async Task<IActionResult> Acknowledge(Guid alertId, ...)
```
- PATCH means "modify part of the resource" (only the fields you send are changed)
- Returns **200 OK** with the updated resource
- Returns **404 Not Found** if the resource doesn't exist
### PUT — Full replace
```csharp
[HttpPut("{id:guid}")]
[AuthorizePermission(ClinicalPermissions.ThresholdsWrite)]
public async Task<IActionResult> Update(Guid id, [FromBody] AlertThresholdRequest req)
```
- PUT means "replace the entire resource with this new version"
- Returns **200 OK** with the updated resource
---
## Error Handling
The `ExceptionHandlerMiddleware` (from Guide 3) catches all exceptions and converts them to consistent API responses. The controller code throws domain exceptions, and the middleware translates them:
| Exception | HTTP Status | When Used |
|-----------|-------------|-----------|
| `NotFoundException` | 404 Not Found | Resource doesn't exist |
| `BadRequestException` | 400 Bad Request | Invalid input that FluentValidation didn't catch |
| `ValidationException` | 422 Unprocessable Entity | Business rule violation |
| `ConflictException` | 409 Conflict | Duplicate resource (e.g., duplicate MRN) |
| `Exception` (unhandled) | 500 Internal Server Error | Unexpected bugs |
Client errors (4xx) are logged as **Warning** — they're expected. Server errors (5xx) are logged as **Error** with the full stack trace.
The 500 response never leaks exception details to the client:
```json
{
"success": false,
"statusCode": 500,
"data": null,
"error": {
"message": "An unexpected error occurred",
"code": "INTERNAL_ERROR"
}
}
```
---
## Pagination: Offset-Based vs Cursor-Based
This project uses two pagination strategies depending on the use case.
### Offset-Based (Page Number)
For encounter lists where the client needs "page 3 of 10":
```
GET /api/v1/encounters?page=2&pageSize=20
```
```json
{
"items": [...],
"page": 2,
"pageSize": 20,
"totalCount": 157,
"totalPages": 8
}
```
**How it works**: `OFFSET (page - 1) * pageSize LIMIT pageSize`. Simple but has a known limitation — if data is inserted between page requests, items can be duplicated or skipped.
### Cursor-Based (Keyset Pagination)
For observation history where data is frequently appended:
```
GET /api/v1/encounters/{id}/observations?limit=20
GET /api/v1/encounters/{id}/observations?limit=20&cursor=eyJ0Ijoi...
```
```json
{
"items": [...],
"nextCursor": "eyJ0IjoiMjAyNi0wNi0yNFQxNDoyMzowMFoiLCJpIjoiYWJjLTEyMyJ9",
"hasMore": true
}
```
**What is a cursor?** An opaque token that encodes the position of the last item. The server decodes it to construct a `WHERE` clause that fetches the next batch:
```csharp
public async Task<CursorPage<Observation>> GetHistoryAsync(
Guid encounterId, string? code,
DateTimeOffset? from, DateTimeOffset? to,
int limit, string? cursorToken)
{
var cursor = ObservationCursor.Decode(cursorToken);
var query = _db.Observations
.Where(o => o.EncounterId == encounterId);
if (cursor is not null)
{
// Keyset condition: ORDER BY recorded_at DESC, id DESC
// "Give me rows AFTER this position"
query = query.Where(o =>
o.RecordedAt < cursor.RecordedAt ||
(o.RecordedAt == cursor.RecordedAt && o.Id.CompareTo(cursor.Id) < 0));
}
var items = await query
.OrderByDescending(o => o.RecordedAt)
.ThenByDescending(o => o.Id)
.Take(limit + 1) // fetch one extra to detect "has more"
.ToListAsync();
var hasMore = items.Count > limit;
if (hasMore) items.RemoveAt(limit);
var nextCursor = hasMore
? new ObservationCursor(items[^1].RecordedAt, items[^1].Id).Encode()
: null;
return new CursorPage<Observation>(items, nextCursor, hasMore);
}
```
**Why cursor-based for observations?** Observations are append-only and ordered by timestamp. Offset pagination (`OFFSET 100`) gets slower as the offset grows (PostgreSQL must scan and skip 100 rows). Keyset pagination (`WHERE recorded_at < '2026-06-24T14:23:00Z'`) uses the index directly and performs consistently regardless of how deep you paginate.
**The `Take(limit + 1)` trick**: Fetch one more item than requested. If you get `limit + 1` items, there's a next page — remove the extra item and return `hasMore: true`. If you get `limit` or fewer, there's no next page.
---
## Idempotency
**What is idempotency?** An operation is idempotent if performing it multiple times produces the same result as performing it once. `GET` is naturally idempotent (reading doesn't change anything). `POST` is not (creating a resource twice creates two resources).
For observation ingest, the API supports an optional `Idempotency-Key` header:
```
POST /api/v1/encounters/{id}/observations
Idempotency-Key: device-123-reading-456
{ "observationCode": "HEART_RATE", "value": 82, ... }
```
If the same idempotency key is sent twice, the second request returns the original observation instead of creating a duplicate. This is essential for device integrations where network retries are common — a device might send the same reading twice if it doesn't receive an acknowledgment.
The implementation uses a partial unique index in PostgreSQL (see Guide 4) — only non-null idempotency keys are checked for uniqueness.
---
## Route Parameter Constraints
```csharp
[HttpGet("{id:guid}")]
public async Task<IActionResult> Get(Guid id)
```
The `:guid` constraint means ASP.NET Core only matches this route if the `{id}` segment is a valid GUID. A request to `/api/v1/encounters/not-a-guid` returns 404 instead of reaching the controller and failing during GUID parsing.
---
## Swagger / OpenAPI Documentation
The API is self-documenting via Swagger:
```csharp
app.UseSwagger();
app.UseSwaggerUI(options =>
{
options.SwaggerEndpoint("/swagger/v1/swagger.json", "VigilCare Clinical API v1");
});
```
Available at `http://localhost:5270/swagger/ui` during development. XML documentation comments on controller actions (the `<summary>` blocks) appear in the Swagger UI, making it easy for frontend developers to understand each endpoint without reading the C# code.
```csharp
/// <summary>
/// Returns cursor-paginated observation history for an encounter.
/// </summary>
/// <param name="encounterId">Encounter id.</param>
/// <param name="code">Optional observation code filter.</param>
/// <param name="limit">Maximum items per page.</param>
/// <param name="cursor">Opaque cursor from a previous page.</param>
[HttpGet]
[ProducesResponseType(typeof(ApiResponse<object>), StatusCodes.Status200OK)]
public async Task<IActionResult> History(...)
```
`[ProducesResponseType]` tells Swagger which response shapes are possible, generating accurate API documentation.
---
## Key Takeaways
- **Consistent envelope** (`ApiResponse<T>`) makes every response predictable — clients always know where to find the data, error message, and status code
- **Machine-readable error codes** (like `"INVALID_STATUS"`) let clients handle errors programmatically without parsing human-readable messages
- **Use the right HTTP method** — GET reads, POST creates, PATCH updates partially, PUT replaces fully. This isn't just convention; proxies, caches, and browsers treat these differently.
- **Use the right status code** — 201 for creation, 409 for conflicts, 422 for validation failures. Don't use 200 for everything.
- **Cursor pagination for append-only data** — performs consistently regardless of dataset size, handles concurrent inserts correctly
- **Offset pagination for browsable lists** — simpler for UI that needs "page X of Y" navigation
- **Idempotency keys prevent duplicate resources** — essential for unreliable networks where requests may be retried
- **Swagger documents the API automatically** — XML comments on controllers become interactive API documentation
@@ -0,0 +1,295 @@
# Guide 19: FHIR R4 Integration Facade
## What is FHIR?
**FHIR** (Fast Healthcare Interoperability Resources, pronounced "fire") is a standard for exchanging healthcare data between systems. If your hospital has an EHR (Electronic Health Record) like Epic or Cerner, a lab system, a pharmacy system, and a monitoring platform, they all need to share patient data. FHIR defines a common language for this — standard data formats (called **resources**) and standard ways to send them (RESTful HTTP endpoints).
Key concepts:
- **Resource**: A structured JSON object representing a healthcare concept. Examples: `Patient`, `Encounter`, `Observation`, `MedicationAdministration`. Each resource type has a defined set of fields and data types.
- **FHIR R4**: The fourth major release of the FHIR standard (Release 4). R4 is the current normative version used by most healthcare systems.
- **Identifier**: A system+value pair that identifies a resource in an external system. For example, `system: "http://hospital.example/mrn"`, `value: "MRN-001"`. A patient might have different identifiers in different systems — FHIR uses these to match records across systems.
- **Coding**: A code from a standard terminology. `system: "http://loinc.org"`, `code: "8867-4"` means "Heart rate" in the LOINC vocabulary. `system: "http://snomed.info/sct"`, `code: "364075005"` also means "Heart rate" in SNOMED CT.
- **Bundle**: A collection of resources sent together. A "transaction Bundle" is like a database transaction — all entries succeed or all fail.
- **CapabilityStatement**: A resource that describes what a FHIR server can do — which resource types it supports and which operations (create, read, search).
- **OperationOutcome**: A FHIR-standard error response. Instead of returning `{"error": "..."}`, FHIR servers return a structured `OperationOutcome` resource with severity, issue type, and diagnostic details.
**What is a "facade"?** This project doesn't implement a full FHIR server. It implements a **facade** — a thin translation layer that accepts FHIR-formatted requests, maps them to the internal data model, and returns FHIR-formatted responses. The internal database schema is not FHIR-native; the facade translates between the two worlds.
---
## Why FHIR in This Project?
Hospital integration engines (like Mirth Connect or Rhapsody) speak FHIR. They send ADT (Admit/Discharge/Transfer) messages with Patient and Encounter resources, vital signs as Observation resources, and medication records as MedicationAdministration resources. By implementing FHIR endpoints, VigilCareClinical can receive data from any FHIR-capable system without custom integration code for each one.
---
## Architecture Overview
```
Hospital Systems VigilCareClinical
┌──────────────┐ ┌──────────────────────────────┐
│ EHR (Epic) │ │ FHIR Ingest Controller │
│ │ POST /fhir/R4/ │ │ │
│ Mirth Connect│ Patient │ ▼ │
│ (integration │ ────────────────► │ PatientFhirMapper │
│ engine) │ │ │ FHIR Patient → internal │
│ │ POST /fhir/R4/ │ ▼ │
│ Lab System │ Observation │ PatientService │
│ │ ────────────────► │ │ save to PostgreSQL │
│ Pharmacy │ │ ▼ │
│ │ POST /fhir/R4/ │ FhirMapper (reverse) │
│ │ Bundle │ │ internal → FHIR response│
│ │ ────────────────► │ ▼ │
│ │ │ Return FHIR JSON │
└──────────────┘ └──────────────────────────────┘
```
---
## Supported Resources and Interactions
The `CapabilityStatement` (available at `GET /fhir/R4/metadata`, no auth required) declares:
| Resource | Create | Read | Search |
|----------|--------|------|--------|
| Patient | ✓ | ✓ | ✓ (by identifier) |
| Encounter | ✓ | ✓ | ✓ (by patient, status) |
| Observation | ✓ | | |
| MedicationAdministration | ✓ | | |
| Bundle (transaction) | ✓ | | |
All FHIR endpoints use `application/fhir+json` as the content type (not `application/json`), following the FHIR specification.
---
## LOINC Code Mapping
FHIR Observations use standardized codes (LOINC, SNOMED CT) to identify what's being measured. The internal data model uses simpler codes like `"HEART_RATE"`. The `LoincCodeMapper` translates between them:
```csharp
public static class LoincCodeMapper
{
private static readonly Dictionary<string, LoincMapping> _map = new()
{
["8867-4"] = new("HEART_RATE", "/min"),
["8310-5"] = new("TEMP_C", "Cel", AllowFahrenheit: true),
["2823-3"] = new("POTASSIUM_MEQ_L", "mmol/L"),
["2708-6"] = new("SPO2", "%"),
["9279-1"] = new("RESP_RATE", "/min"),
["6690-2"] = new("WBC_K_UL", "10*3/uL"),
["8480-6"] = new("SYSTOLIC_BP", "mm[Hg]"),
["8462-4"] = new("DIASTOLIC_BP", "mm[Hg]"),
["2524-7"] = new("LACTATE_MMOL_L", "mmol/L"),
["777-3"] = new("PLATELET_K_UL", "10*3/uL"),
["1975-2"] = new("BILIRUBIN_MG_DL", "mg/dL"),
["2160-0"] = new("CREATININE_MG_DL", "mg/dL"),
["2703-7"] = new("PAO2_MMHG", "mm[Hg]"),
["80288-7"] = new("GCS_EYE", "{score}"),
["80289-5"] = new("GCS_VERBAL", "{score}"),
["80290-3"] = new("GCS_MOTOR", "{score}"),
// ... 19 mappings total
};
// SNOMED CT fallbacks for systems that don't use LOINC
private static readonly Dictionary<string, LoincMapping> _snomedMap = new()
{
["364075005"] = new("HEART_RATE", "/min"),
["431314004"] = new("SPO2", "%"),
["86290005"] = new("RESP_RATE", "/min"),
};
public static bool TryMap(string system, string code, out LoincMapping mapping)
{
if (system.Contains("loinc") && _map.TryGetValue(code, out mapping!))
return true;
if (system.Contains("snomed") && _snomedMap.TryGetValue(code, out mapping!))
return true;
mapping = null!;
return false;
}
}
```
When a FHIR Observation arrives with code `system: "http://loinc.org"`, `code: "8867-4"`, the mapper translates it to internal code `"HEART_RATE"` with expected unit `"/min"`.
### Unit Conversion
Some hospitals send temperatures in Fahrenheit. The `FhirUnitConverter` handles this:
```csharp
public static (decimal Value, string Unit) Normalize(
string internalCode, decimal value, string? fhirUnit, bool allowFahrenheit)
{
if (internalCode == "TEMP_C" && allowFahrenheit &&
(fhirUnit == "[degF]" || fhirUnit == "degF"))
{
var celsius = (value - 32m) * 5m / 9m;
return (Math.Round(celsius, 2), "Cel");
}
return (value, fhirUnit ?? "1");
}
```
The internal system always stores temperature in Celsius. If a FHIR Observation arrives in Fahrenheit, it's converted transparently.
---
## The Observation Mapper
The `ObservationFhirMapper` handles the most complex mapping because FHIR Observations can have multiple formats:
```csharp
public async Task<IReadOnlyList<MappedObservation>> ToIngestRequestsAsync(
Hl7.Fhir.Model.Observation fhir)
{
var encounterId = await _refs.ResolveEncounterReferenceAsync(fhir.Encounter);
var source = MapSource(fhir); // vital-signs → Device, laboratory → Lab
var recordedAt = fhir.Effective.ToUtcDateTimeOffset() ?? DateTimeOffset.UtcNow;
var idempotencyKey = fhir.Identifier?.FirstOrDefault()?.Value ?? fhir.Id;
// FHIR Observations can be single-value or multi-component
if (fhir.Component?.Count > 0)
{
// Multi-component (e.g., blood pressure with systolic + diastolic)
foreach (var component in fhir.Component)
results.AddRange(MapSingleCoding(component.Code, component.Value, ...));
}
else
{
// Single value (e.g., heart rate)
results.AddRange(MapSingleCoding(fhir.Code, fhir.Value, ...));
}
}
```
A FHIR blood pressure Observation has two **components** (systolic and diastolic). The mapper produces two internal observations from one FHIR resource.
---
## Transaction Bundle Processing
A transaction Bundle groups multiple resources into one atomic operation — like a database transaction. The `FhirBundleProcessor` handles this:
```csharp
public async Task<Bundle> ProcessTransactionAsync(Bundle transaction)
{
var response = new Bundle { Type = Bundle.BundleType.TransactionResponse };
// Sort entries: Patient first, then Encounter, then everything else
var entries = transaction.Entry
.OrderBy(e => Priority(e.Resource))
.ToList();
await using var tx = await _db.Database.BeginTransactionAsync();
foreach (var entry in entries)
{
try
{
var location = resource switch
{
Patient p => await ProcessPatientAsync(p),
Encounter e => await ProcessEncounterAsync(e),
Observation o => await ProcessObservationAsync(o),
MedicationAdministration m => await ProcessMedAsync(m),
_ => throw new FhirMappingException("Unsupported resource type")
};
response.Entry.Add(new Bundle.EntryComponent
{
Response = new Bundle.ResponseComponent
{ Status = "201 Created", Location = location }
});
}
catch (Exception ex)
{
await tx.RollbackAsync(); // all-or-nothing
response.Entry.Add(/* error entry */);
return response;
}
}
await tx.CommitAsync();
return response;
}
```
**Why sort by priority?** An Encounter references a Patient, and an Observation references an Encounter. If the Bundle contains all three, the Patient must be created first (so the Encounter can reference it), then the Encounter (so the Observation can reference it). The `Priority()` function ensures this ordering.
**All-or-nothing**: If any entry fails, the entire transaction rolls back — no partially-created data.
---
## FHIR Error Responses: OperationOutcome
FHIR has its own error format. Instead of the project's standard `ApiResponse<T>` envelope, FHIR endpoints return `OperationOutcome` resources:
```csharp
public static class FhirOperationOutcomeBuilder
{
public static OperationOutcome FromException(Exception ex) => ex switch
{
FhirMappingException fme => Create(fme.HttpStatus, fme.FhirIssueCode, fme.Message),
NotFoundException => Create(404, "not-found", ex.Message),
ValidationException => Create(422, "invalid", ex.Message),
ConflictException => Create(409, "conflict", ex.Message),
_ => Create(500, "exception", "An unexpected error occurred.")
};
}
```
The `FhirExceptionFilter` catches exceptions on `/fhir/*` paths and converts them to `OperationOutcome` responses with `Content-Type: application/fhir+json`:
```json
{
"resourceType": "OperationOutcome",
"issue": [{
"severity": "warning",
"code": "not-found",
"diagnostics": "Patient not found."
}]
}
```
---
## External Identifier Resolution
When a FHIR Encounter references a Patient as `"subject": {"reference": "Patient/MRN-001"}`, the system needs to find the internal UUID for that patient. The `ExternalResourceIdentifier` table maps between external identifiers (hospital MRNs, visit numbers) and internal UUIDs:
| resource_type | system | value | internal_id |
|--------------|--------|-------|-------------|
| Patient | `http://hospital.example/mrn` | MRN-001 | `3fa85f64-...` |
| Encounter | `http://hospital.example/visit` | VISIT-100 | `7e4b2a1f-...` |
This mapping is created when a resource is first ingested and used for all subsequent references. It's what makes the FHIR endpoints **idempotent** — sending the same Patient twice (same identifier) updates the existing record instead of creating a duplicate.
---
## Authentication for FHIR Endpoints
FHIR endpoints accept two authentication methods (see Guide 15):
1. **JWT token** — for admin users accessing via the dashboard or Swagger
2. **API key** — for integration engines like Mirth Connect
```
POST /fhir/R4/Observation
X-Api-Key: dev-integration-key-change-in-production
Content-Type: application/fhir+json
```
The FHIR API key creates an identity with the `Integration` role, which has permissions for `fhir:ingest`, `fhir:read`, `patients:write`, `encounters:write`, `observations:ingest`, and `medications:write`.
---
## Key Takeaways
- **FHIR is the healthcare data standard** — it defines how to represent patients, encounters, observations, and medications as JSON resources with standard coding systems
- **A facade translates, not stores** — the internal database uses its own schema; the FHIR layer maps between FHIR resources and internal entities
- **LOINC and SNOMED CT codes are translated to internal codes**`"8867-4"` (LOINC) becomes `"HEART_RATE"` internally; SNOMED fallbacks handle systems that don't use LOINC
- **Transaction Bundles are atomic** — Patient → Encounter → Observation ordering is enforced, and any failure rolls back the entire bundle
- **OperationOutcome is the FHIR error format** — FHIR endpoints return structured error resources instead of the project's standard API envelope
- **External identifiers enable idempotency** — the same resource sent twice (same identifier) updates rather than duplicates
- **Unit conversion happens transparently** — Fahrenheit temperatures are converted to Celsius during mapping so the internal system works with a single unit

Some files were not shown because too many files have changed in this diff Show More