javi-forge 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.gitignore.template +105 -0
- package/.releaserc +44 -0
- package/README.md +45 -0
- package/ai-config/.skillignore +15 -0
- package/ai-config/AUTO_INVOKE.md +300 -0
- package/ai-config/agents/_TEMPLATE.md +93 -0
- package/ai-config/agents/business/api-designer.md +1657 -0
- package/ai-config/agents/business/business-analyst.md +1331 -0
- package/ai-config/agents/business/product-strategist.md +206 -0
- package/ai-config/agents/business/project-manager.md +178 -0
- package/ai-config/agents/business/requirements-analyst.md +1277 -0
- package/ai-config/agents/business/technical-writer.md +1679 -0
- package/ai-config/agents/creative/ux-designer.md +205 -0
- package/ai-config/agents/data-ai/ai-engineer.md +487 -0
- package/ai-config/agents/data-ai/analytics-engineer.md +953 -0
- package/ai-config/agents/data-ai/data-engineer.md +173 -0
- package/ai-config/agents/data-ai/data-scientist.md +672 -0
- package/ai-config/agents/data-ai/mlops-engineer.md +814 -0
- package/ai-config/agents/data-ai/prompt-engineer.md +772 -0
- package/ai-config/agents/development/angular-expert.md +620 -0
- package/ai-config/agents/development/backend-architect.md +795 -0
- package/ai-config/agents/development/database-specialist.md +212 -0
- package/ai-config/agents/development/frontend-specialist.md +686 -0
- package/ai-config/agents/development/fullstack-engineer.md +668 -0
- package/ai-config/agents/development/golang-pro.md +338 -0
- package/ai-config/agents/development/java-enterprise.md +400 -0
- package/ai-config/agents/development/javascript-pro.md +422 -0
- package/ai-config/agents/development/nextjs-pro.md +474 -0
- package/ai-config/agents/development/python-pro.md +570 -0
- package/ai-config/agents/development/react-pro.md +487 -0
- package/ai-config/agents/development/rust-pro.md +246 -0
- package/ai-config/agents/development/spring-boot-4-expert.md +326 -0
- package/ai-config/agents/development/typescript-pro.md +336 -0
- package/ai-config/agents/development/vue-specialist.md +605 -0
- package/ai-config/agents/infrastructure/cloud-architect.md +472 -0
- package/ai-config/agents/infrastructure/deployment-manager.md +358 -0
- package/ai-config/agents/infrastructure/devops-engineer.md +455 -0
- package/ai-config/agents/infrastructure/incident-responder.md +519 -0
- package/ai-config/agents/infrastructure/kubernetes-expert.md +705 -0
- package/ai-config/agents/infrastructure/monitoring-specialist.md +674 -0
- package/ai-config/agents/infrastructure/performance-engineer.md +658 -0
- package/ai-config/agents/orchestrator.md +241 -0
- package/ai-config/agents/quality/accessibility-auditor.md +1204 -0
- package/ai-config/agents/quality/code-reviewer-compact.md +123 -0
- package/ai-config/agents/quality/code-reviewer.md +363 -0
- package/ai-config/agents/quality/dependency-manager.md +743 -0
- package/ai-config/agents/quality/e2e-test-specialist.md +1005 -0
- package/ai-config/agents/quality/performance-tester.md +1086 -0
- package/ai-config/agents/quality/security-auditor.md +133 -0
- package/ai-config/agents/quality/test-engineer.md +453 -0
- package/ai-config/agents/specialists/api-designer.md +87 -0
- package/ai-config/agents/specialists/backend-architect.md +73 -0
- package/ai-config/agents/specialists/code-reviewer.md +77 -0
- package/ai-config/agents/specialists/db-optimizer.md +75 -0
- package/ai-config/agents/specialists/devops-engineer.md +83 -0
- package/ai-config/agents/specialists/documentation-writer.md +78 -0
- package/ai-config/agents/specialists/frontend-developer.md +75 -0
- package/ai-config/agents/specialists/performance-analyst.md +82 -0
- package/ai-config/agents/specialists/refactor-specialist.md +74 -0
- package/ai-config/agents/specialists/security-auditor.md +74 -0
- package/ai-config/agents/specialists/test-engineer.md +81 -0
- package/ai-config/agents/specialists/ux-consultant.md +76 -0
- package/ai-config/agents/specialized/agent-generator.md +1190 -0
- package/ai-config/agents/specialized/blockchain-developer.md +149 -0
- package/ai-config/agents/specialized/code-migrator.md +892 -0
- package/ai-config/agents/specialized/context-manager.md +978 -0
- package/ai-config/agents/specialized/documentation-writer.md +1078 -0
- package/ai-config/agents/specialized/ecommerce-expert.md +1756 -0
- package/ai-config/agents/specialized/embedded-engineer.md +1714 -0
- package/ai-config/agents/specialized/error-detective.md +1034 -0
- package/ai-config/agents/specialized/fintech-specialist.md +1659 -0
- package/ai-config/agents/specialized/freelance-project-planner-v2.md +1988 -0
- package/ai-config/agents/specialized/freelance-project-planner-v3.md +2136 -0
- package/ai-config/agents/specialized/freelance-project-planner-v4.md +4503 -0
- package/ai-config/agents/specialized/freelance-project-planner.md +722 -0
- package/ai-config/agents/specialized/game-developer.md +1963 -0
- package/ai-config/agents/specialized/healthcare-dev.md +1620 -0
- package/ai-config/agents/specialized/mobile-developer.md +188 -0
- package/ai-config/agents/specialized/parallel-plan-executor.md +506 -0
- package/ai-config/agents/specialized/plan-executor.md +485 -0
- package/ai-config/agents/specialized/solo-dev-planner-modular/00-INDEX.md +485 -0
- package/ai-config/agents/specialized/solo-dev-planner-modular/01-CORE.md +3493 -0
- package/ai-config/agents/specialized/solo-dev-planner-modular/02-SELF-CORRECTION.md +778 -0
- package/ai-config/agents/specialized/solo-dev-planner-modular/03-PROGRESSIVE-SETUP.md +918 -0
- package/ai-config/agents/specialized/solo-dev-planner-modular/04-DEPLOYMENT.md +1537 -0
- package/ai-config/agents/specialized/solo-dev-planner-modular/05-TESTING.md +2633 -0
- package/ai-config/agents/specialized/solo-dev-planner-modular/06-OPERATIONS.md +5610 -0
- package/ai-config/agents/specialized/solo-dev-planner-modular/INSTALL.md +335 -0
- package/ai-config/agents/specialized/solo-dev-planner-modular/QUICK-REFERENCE.txt +215 -0
- package/ai-config/agents/specialized/solo-dev-planner-modular/README.md +260 -0
- package/ai-config/agents/specialized/solo-dev-planner-modular/START-HERE.md +379 -0
- package/ai-config/agents/specialized/solo-dev-planner-modular/WORKFLOW-DIAGRAM.md +355 -0
- package/ai-config/agents/specialized/solo-dev-planner-modular/solo-dev-planner.md +279 -0
- package/ai-config/agents/specialized/template-writer.md +347 -0
- package/ai-config/agents/specialized/test-runner.md +99 -0
- package/ai-config/agents/specialized/vibekanban-smart-worker.md +244 -0
- package/ai-config/agents/specialized/wave-executor.md +138 -0
- package/ai-config/agents/specialized/workflow-optimizer.md +1114 -0
- package/ai-config/commands/git/changelog.md +32 -0
- package/ai-config/commands/git/ci-local.md +70 -0
- package/ai-config/commands/git/commit.md +35 -0
- package/ai-config/commands/git/fix-issue.md +23 -0
- package/ai-config/commands/git/pr-create.md +42 -0
- package/ai-config/commands/git/pr-review.md +50 -0
- package/ai-config/commands/git/worktree.md +39 -0
- package/ai-config/commands/refactoring/cleanup.md +24 -0
- package/ai-config/commands/refactoring/dead-code.md +40 -0
- package/ai-config/commands/refactoring/extract.md +31 -0
- package/ai-config/commands/testing/e2e.md +30 -0
- package/ai-config/commands/testing/tdd.md +36 -0
- package/ai-config/commands/testing/test-coverage.md +30 -0
- package/ai-config/commands/testing/test-fix.md +24 -0
- package/ai-config/commands/workflow/generate-agents-md.md +85 -0
- package/ai-config/commands/workflow/planning.md +47 -0
- package/ai-config/commands/workflows/compound.md +89 -0
- package/ai-config/commands/workflows/plan.md +77 -0
- package/ai-config/commands/workflows/review.md +78 -0
- package/ai-config/commands/workflows/work.md +75 -0
- package/ai-config/config.yaml +18 -0
- package/ai-config/hooks/_TEMPLATE.md +96 -0
- package/ai-config/hooks/block-dangerous-commands.md +75 -0
- package/ai-config/hooks/commit-guard.md +90 -0
- package/ai-config/hooks/context-loader.md +73 -0
- package/ai-config/hooks/improve-prompt.md +91 -0
- package/ai-config/hooks/learning-log.md +72 -0
- package/ai-config/hooks/model-router.md +86 -0
- package/ai-config/hooks/secret-scanner.md +64 -0
- package/ai-config/hooks/skill-validator.md +102 -0
- package/ai-config/hooks/task-artifact.md +114 -0
- package/ai-config/hooks/validate-workflow.md +100 -0
- package/ai-config/prompts/base.md +71 -0
- package/ai-config/prompts/modes/debug.md +34 -0
- package/ai-config/prompts/modes/deploy.md +40 -0
- package/ai-config/prompts/modes/research.md +32 -0
- package/ai-config/prompts/modes/review.md +33 -0
- package/ai-config/prompts/review-policy.md +79 -0
- package/ai-config/skills/_TEMPLATE.md +157 -0
- package/ai-config/skills/backend/api-gateway/SKILL.md +254 -0
- package/ai-config/skills/backend/bff-concepts/SKILL.md +239 -0
- package/ai-config/skills/backend/bff-spring/SKILL.md +364 -0
- package/ai-config/skills/backend/chi-router/SKILL.md +396 -0
- package/ai-config/skills/backend/error-handling/SKILL.md +255 -0
- package/ai-config/skills/backend/exceptions-spring/SKILL.md +323 -0
- package/ai-config/skills/backend/fastapi/SKILL.md +302 -0
- package/ai-config/skills/backend/gateway-spring/SKILL.md +390 -0
- package/ai-config/skills/backend/go-backend/SKILL.md +457 -0
- package/ai-config/skills/backend/gradle-multimodule/SKILL.md +274 -0
- package/ai-config/skills/backend/graphql-concepts/SKILL.md +352 -0
- package/ai-config/skills/backend/graphql-spring/SKILL.md +398 -0
- package/ai-config/skills/backend/grpc-concepts/SKILL.md +283 -0
- package/ai-config/skills/backend/grpc-spring/SKILL.md +445 -0
- package/ai-config/skills/backend/jwt-auth/SKILL.md +412 -0
- package/ai-config/skills/backend/notifications-concepts/SKILL.md +259 -0
- package/ai-config/skills/backend/recommendations-concepts/SKILL.md +261 -0
- package/ai-config/skills/backend/search-concepts/SKILL.md +263 -0
- package/ai-config/skills/backend/search-spring/SKILL.md +375 -0
- package/ai-config/skills/backend/spring-boot-4/SKILL.md +172 -0
- package/ai-config/skills/backend/websockets/SKILL.md +532 -0
- package/ai-config/skills/data-ai/ai-ml/SKILL.md +423 -0
- package/ai-config/skills/data-ai/analytics-concepts/SKILL.md +195 -0
- package/ai-config/skills/data-ai/analytics-spring/SKILL.md +340 -0
- package/ai-config/skills/data-ai/duckdb-analytics/SKILL.md +440 -0
- package/ai-config/skills/data-ai/langchain/SKILL.md +238 -0
- package/ai-config/skills/data-ai/mlflow/SKILL.md +302 -0
- package/ai-config/skills/data-ai/onnx-inference/SKILL.md +290 -0
- package/ai-config/skills/data-ai/powerbi/SKILL.md +352 -0
- package/ai-config/skills/data-ai/pytorch/SKILL.md +274 -0
- package/ai-config/skills/data-ai/scikit-learn/SKILL.md +321 -0
- package/ai-config/skills/data-ai/vector-db/SKILL.md +301 -0
- package/ai-config/skills/database/graph-databases/SKILL.md +218 -0
- package/ai-config/skills/database/graph-spring/SKILL.md +361 -0
- package/ai-config/skills/database/pgx-postgres/SKILL.md +512 -0
- package/ai-config/skills/database/redis-cache/SKILL.md +343 -0
- package/ai-config/skills/database/sqlite-embedded/SKILL.md +388 -0
- package/ai-config/skills/database/timescaledb/SKILL.md +320 -0
- package/ai-config/skills/docs/api-documentation/SKILL.md +293 -0
- package/ai-config/skills/docs/docs-spring/SKILL.md +377 -0
- package/ai-config/skills/docs/mustache-templates/SKILL.md +190 -0
- package/ai-config/skills/docs/technical-docs/SKILL.md +447 -0
- package/ai-config/skills/frontend/astro-ssr/SKILL.md +441 -0
- package/ai-config/skills/frontend/frontend-design/SKILL.md +54 -0
- package/ai-config/skills/frontend/frontend-web/SKILL.md +368 -0
- package/ai-config/skills/frontend/mantine-ui/SKILL.md +396 -0
- package/ai-config/skills/frontend/tanstack-query/SKILL.md +439 -0
- package/ai-config/skills/frontend/zod-validation/SKILL.md +417 -0
- package/ai-config/skills/frontend/zustand-state/SKILL.md +350 -0
- package/ai-config/skills/infrastructure/chaos-engineering/SKILL.md +244 -0
- package/ai-config/skills/infrastructure/chaos-spring/SKILL.md +378 -0
- package/ai-config/skills/infrastructure/devops-infra/SKILL.md +435 -0
- package/ai-config/skills/infrastructure/docker-containers/SKILL.md +420 -0
- package/ai-config/skills/infrastructure/kubernetes/SKILL.md +456 -0
- package/ai-config/skills/infrastructure/opentelemetry/SKILL.md +546 -0
- package/ai-config/skills/infrastructure/traefik-proxy/SKILL.md +474 -0
- package/ai-config/skills/infrastructure/woodpecker-ci/SKILL.md +315 -0
- package/ai-config/skills/mobile/ionic-capacitor/SKILL.md +504 -0
- package/ai-config/skills/mobile/mobile-ionic/SKILL.md +448 -0
- package/ai-config/skills/prompt-improver/SKILL.md +125 -0
- package/ai-config/skills/quality/ghagga-review/SKILL.md +216 -0
- package/ai-config/skills/references/hooks-patterns/SKILL.md +238 -0
- package/ai-config/skills/references/mcp-servers/SKILL.md +275 -0
- package/ai-config/skills/references/plugins-reference/SKILL.md +110 -0
- package/ai-config/skills/references/skills-reference/SKILL.md +420 -0
- package/ai-config/skills/references/subagent-templates/SKILL.md +193 -0
- package/ai-config/skills/systems-iot/modbus-protocol/SKILL.md +410 -0
- package/ai-config/skills/systems-iot/mqtt-rumqttc/SKILL.md +408 -0
- package/ai-config/skills/systems-iot/rust-systems/SKILL.md +386 -0
- package/ai-config/skills/systems-iot/tokio-async/SKILL.md +324 -0
- package/ai-config/skills/testing/playwright-e2e/SKILL.md +289 -0
- package/ai-config/skills/testing/testcontainers/SKILL.md +299 -0
- package/ai-config/skills/testing/vitest-testing/SKILL.md +381 -0
- package/ai-config/skills/workflow/ci-local-guide/SKILL.md +118 -0
- package/ai-config/skills/workflow/claude-automation-recommender/SKILL.md +299 -0
- package/ai-config/skills/workflow/claude-md-improver/SKILL.md +158 -0
- package/ai-config/skills/workflow/finishing-a-development-branch/SKILL.md +117 -0
- package/ai-config/skills/workflow/git-github/SKILL.md +334 -0
- package/ai-config/skills/workflow/git-github/references/examples.md +160 -0
- package/ai-config/skills/workflow/git-workflow/SKILL.md +214 -0
- package/ai-config/skills/workflow/ide-plugins/SKILL.md +277 -0
- package/ai-config/skills/workflow/ide-plugins-intellij/SKILL.md +401 -0
- package/ai-config/skills/workflow/obsidian-brain-workflow/SKILL.md +199 -0
- package/ai-config/skills/workflow/using-git-worktrees/SKILL.md +100 -0
- package/ai-config/skills/workflow/verification-before-completion/SKILL.md +73 -0
- package/ai-config/skills/workflow/wave-workflow/SKILL.md +178 -0
- package/ci-local/README.md +170 -0
- package/ci-local/ci-local.sh +297 -0
- package/ci-local/hooks/commit-msg +74 -0
- package/ci-local/hooks/pre-commit +162 -0
- package/ci-local/hooks/pre-push +41 -0
- package/ci-local/install.sh +49 -0
- package/ci-local/semgrep.yml +214 -0
- package/dist/commands/analyze.d.ts +9 -0
- package/dist/commands/analyze.d.ts.map +1 -0
- package/dist/commands/analyze.js +55 -0
- package/dist/commands/analyze.js.map +1 -0
- package/dist/commands/analyze.test.d.ts +2 -0
- package/dist/commands/analyze.test.d.ts.map +1 -0
- package/dist/commands/analyze.test.js +145 -0
- package/dist/commands/analyze.test.js.map +1 -0
- package/dist/commands/doctor.d.ts +7 -0
- package/dist/commands/doctor.d.ts.map +1 -0
- package/dist/commands/doctor.js +158 -0
- package/dist/commands/doctor.js.map +1 -0
- package/dist/commands/doctor.test.d.ts +2 -0
- package/dist/commands/doctor.test.d.ts.map +1 -0
- package/dist/commands/doctor.test.js +200 -0
- package/dist/commands/doctor.test.js.map +1 -0
- package/dist/commands/init.d.ts +9 -0
- package/dist/commands/init.d.ts.map +1 -0
- package/dist/commands/init.js +283 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/commands/init.test.d.ts +2 -0
- package/dist/commands/init.test.d.ts.map +1 -0
- package/dist/commands/init.test.js +271 -0
- package/dist/commands/init.test.js.map +1 -0
- package/dist/commands/sync.d.ts +8 -0
- package/dist/commands/sync.d.ts.map +1 -0
- package/dist/commands/sync.js +201 -0
- package/dist/commands/sync.js.map +1 -0
- package/dist/constants.d.ts +21 -0
- package/dist/constants.d.ts.map +1 -0
- package/dist/constants.js +57 -0
- package/dist/constants.js.map +1 -0
- package/dist/e2e/aggressive.e2e.test.d.ts +2 -0
- package/dist/e2e/aggressive.e2e.test.d.ts.map +1 -0
- package/dist/e2e/aggressive.e2e.test.js +350 -0
- package/dist/e2e/aggressive.e2e.test.js.map +1 -0
- package/dist/e2e/commands.e2e.test.d.ts +2 -0
- package/dist/e2e/commands.e2e.test.d.ts.map +1 -0
- package/dist/e2e/commands.e2e.test.js +213 -0
- package/dist/e2e/commands.e2e.test.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +82 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/common.d.ts +17 -0
- package/dist/lib/common.d.ts.map +1 -0
- package/dist/lib/common.js +111 -0
- package/dist/lib/common.js.map +1 -0
- package/dist/lib/common.test.d.ts +2 -0
- package/dist/lib/common.test.d.ts.map +1 -0
- package/dist/lib/common.test.js +316 -0
- package/dist/lib/common.test.js.map +1 -0
- package/dist/lib/frontmatter.d.ts +18 -0
- package/dist/lib/frontmatter.d.ts.map +1 -0
- package/dist/lib/frontmatter.js +61 -0
- package/dist/lib/frontmatter.js.map +1 -0
- package/dist/lib/frontmatter.test.d.ts +2 -0
- package/dist/lib/frontmatter.test.d.ts.map +1 -0
- package/dist/lib/frontmatter.test.js +257 -0
- package/dist/lib/frontmatter.test.js.map +1 -0
- package/dist/lib/template.d.ts +24 -0
- package/dist/lib/template.d.ts.map +1 -0
- package/dist/lib/template.js +78 -0
- package/dist/lib/template.js.map +1 -0
- package/dist/lib/template.test.d.ts +2 -0
- package/dist/lib/template.test.d.ts.map +1 -0
- package/dist/lib/template.test.js +201 -0
- package/dist/lib/template.test.js.map +1 -0
- package/dist/types/index.d.ts +48 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +2 -0
- package/dist/types/index.js.map +1 -0
- package/dist/ui/AnalyzeUI.d.ts +7 -0
- package/dist/ui/AnalyzeUI.d.ts.map +1 -0
- package/dist/ui/AnalyzeUI.js +100 -0
- package/dist/ui/AnalyzeUI.js.map +1 -0
- package/dist/ui/App.d.ts +13 -0
- package/dist/ui/App.d.ts.map +1 -0
- package/dist/ui/App.js +100 -0
- package/dist/ui/App.js.map +1 -0
- package/dist/ui/CIContext.d.ts +9 -0
- package/dist/ui/CIContext.d.ts.map +1 -0
- package/dist/ui/CIContext.js +9 -0
- package/dist/ui/CIContext.js.map +1 -0
- package/dist/ui/CISelector.d.ts +8 -0
- package/dist/ui/CISelector.d.ts.map +1 -0
- package/dist/ui/CISelector.js +45 -0
- package/dist/ui/CISelector.js.map +1 -0
- package/dist/ui/Doctor.d.ts +3 -0
- package/dist/ui/Doctor.d.ts.map +1 -0
- package/dist/ui/Doctor.js +89 -0
- package/dist/ui/Doctor.js.map +1 -0
- package/dist/ui/Header.d.ts +8 -0
- package/dist/ui/Header.d.ts.map +1 -0
- package/dist/ui/Header.js +30 -0
- package/dist/ui/Header.js.map +1 -0
- package/dist/ui/MemorySelector.d.ts +8 -0
- package/dist/ui/MemorySelector.d.ts.map +1 -0
- package/dist/ui/MemorySelector.js +46 -0
- package/dist/ui/MemorySelector.js.map +1 -0
- package/dist/ui/NameInput.d.ts +8 -0
- package/dist/ui/NameInput.d.ts.map +1 -0
- package/dist/ui/NameInput.js +69 -0
- package/dist/ui/NameInput.js.map +1 -0
- package/dist/ui/OptionSelector.d.ts +12 -0
- package/dist/ui/OptionSelector.d.ts.map +1 -0
- package/dist/ui/OptionSelector.js +69 -0
- package/dist/ui/OptionSelector.js.map +1 -0
- package/dist/ui/Progress.d.ts +11 -0
- package/dist/ui/Progress.d.ts.map +1 -0
- package/dist/ui/Progress.js +58 -0
- package/dist/ui/Progress.js.map +1 -0
- package/dist/ui/StackSelector.d.ts +9 -0
- package/dist/ui/StackSelector.d.ts.map +1 -0
- package/dist/ui/StackSelector.js +65 -0
- package/dist/ui/StackSelector.js.map +1 -0
- package/dist/ui/Summary.d.ts +12 -0
- package/dist/ui/Summary.d.ts.map +1 -0
- package/dist/ui/Summary.js +114 -0
- package/dist/ui/Summary.js.map +1 -0
- package/dist/ui/SyncUI.d.ts +10 -0
- package/dist/ui/SyncUI.d.ts.map +1 -0
- package/dist/ui/SyncUI.js +64 -0
- package/dist/ui/SyncUI.js.map +1 -0
- package/dist/ui/Welcome.d.ts +7 -0
- package/dist/ui/Welcome.d.ts.map +1 -0
- package/dist/ui/Welcome.js +45 -0
- package/dist/ui/Welcome.js.map +1 -0
- package/dist/ui/theme.d.ts +10 -0
- package/dist/ui/theme.d.ts.map +1 -0
- package/dist/ui/theme.js +9 -0
- package/dist/ui/theme.js.map +1 -0
- package/modules/engram/.gitignore-snippet.txt +6 -0
- package/modules/engram/.mcp-config-snippet.json +11 -0
- package/modules/engram/README.md +146 -0
- package/modules/engram/install-engram.sh +216 -0
- package/modules/ghagga/.env.example +43 -0
- package/modules/ghagga/README.md +153 -0
- package/modules/ghagga/docker-compose.yml +80 -0
- package/modules/ghagga/setup-ghagga.sh +139 -0
- package/modules/memory-simple/.project/NOTES.md +22 -0
- package/modules/memory-simple/README.md +23 -0
- package/modules/obsidian-brain/.obsidian/app.json +23 -0
- package/modules/obsidian-brain/.obsidian/appearance.json +5 -0
- package/modules/obsidian-brain/.obsidian/bookmarks.json +34 -0
- package/modules/obsidian-brain/.obsidian/community-plugins.json +1 -0
- package/modules/obsidian-brain/.obsidian/core-plugins-migration.json +21 -0
- package/modules/obsidian-brain/.obsidian/core-plugins.json +18 -0
- package/modules/obsidian-brain/.obsidian/daily-notes.json +5 -0
- package/modules/obsidian-brain/.obsidian/graph.json +37 -0
- package/modules/obsidian-brain/.obsidian/hotkeys.json +14 -0
- package/modules/obsidian-brain/.obsidian/plugins/dataview/data.json +25 -0
- package/modules/obsidian-brain/.obsidian/plugins/obsidian-kanban/data.json +29 -0
- package/modules/obsidian-brain/.obsidian/plugins/templater-obsidian/data.json +18 -0
- package/modules/obsidian-brain/.obsidian/snippets/project-memory.css +71 -0
- package/modules/obsidian-brain/.obsidian-gitignore-snippet.txt +8 -0
- package/modules/obsidian-brain/.project/Attachments/.gitkeep +0 -0
- package/modules/obsidian-brain/.project/Memory/BLOCKERS.md +78 -0
- package/modules/obsidian-brain/.project/Memory/CONTEXT.md +102 -0
- package/modules/obsidian-brain/.project/Memory/DASHBOARD.md +73 -0
- package/modules/obsidian-brain/.project/Memory/DECISIONS.md +87 -0
- package/modules/obsidian-brain/.project/Memory/KANBAN.md +15 -0
- package/modules/obsidian-brain/.project/Memory/README.md +61 -0
- package/modules/obsidian-brain/.project/Memory/WAVES.md +78 -0
- package/modules/obsidian-brain/.project/Sessions/TEMPLATE.md +99 -0
- package/modules/obsidian-brain/.project/Templates/ADR.md +33 -0
- package/modules/obsidian-brain/.project/Templates/Blocker.md +21 -0
- package/modules/obsidian-brain/.project/Templates/Session.md +88 -0
- package/modules/obsidian-brain/README.md +268 -0
- package/modules/obsidian-brain/new-wave.sh +182 -0
- package/package.json +51 -0
- package/schemas/agent.schema.json +34 -0
- package/schemas/ai-config.schema.json +28 -0
- package/schemas/skill.schema.json +44 -0
- package/src/commands/analyze.test.ts +145 -0
- package/src/commands/analyze.ts +69 -0
- package/src/commands/doctor.test.ts +208 -0
- package/src/commands/doctor.ts +163 -0
- package/src/commands/init.test.ts +298 -0
- package/src/commands/init.ts +285 -0
- package/src/constants.ts +69 -0
- package/src/e2e/aggressive.e2e.test.ts +557 -0
- package/src/e2e/commands.e2e.test.ts +298 -0
- package/src/index.tsx +106 -0
- package/src/lib/common.test.ts +318 -0
- package/src/lib/common.ts +127 -0
- package/src/lib/frontmatter.test.ts +291 -0
- package/src/lib/frontmatter.ts +77 -0
- package/src/lib/template.test.ts +226 -0
- package/src/lib/template.ts +99 -0
- package/src/types/index.ts +53 -0
- package/src/ui/AnalyzeUI.tsx +133 -0
- package/src/ui/App.tsx +175 -0
- package/src/ui/CIContext.tsx +25 -0
- package/src/ui/CISelector.tsx +72 -0
- package/src/ui/Doctor.tsx +122 -0
- package/src/ui/Header.tsx +48 -0
- package/src/ui/MemorySelector.tsx +73 -0
- package/src/ui/NameInput.tsx +82 -0
- package/src/ui/OptionSelector.tsx +100 -0
- package/src/ui/Progress.tsx +88 -0
- package/src/ui/StackSelector.tsx +101 -0
- package/src/ui/Summary.tsx +134 -0
- package/src/ui/Welcome.tsx +54 -0
- package/src/ui/theme.ts +10 -0
- package/stryker.config.json +19 -0
- package/tasks/_TEMPLATE/files-edited.md +3 -0
- package/tasks/_TEMPLATE/plan.md +3 -0
- package/tasks/_TEMPLATE/research.md +3 -0
- package/tasks/_TEMPLATE/verification.md +5 -0
- package/templates/common/dependabot/cargo.yml +11 -0
- package/templates/common/dependabot/github-actions.yml +16 -0
- package/templates/common/dependabot/gomod.yml +15 -0
- package/templates/common/dependabot/gradle.yml +15 -0
- package/templates/common/dependabot/header.yml +3 -0
- package/templates/common/dependabot/maven.yml +15 -0
- package/templates/common/dependabot/npm.yml +20 -0
- package/templates/common/dependabot/pip.yml +11 -0
- package/templates/dependabot.yml +162 -0
- package/templates/github/ci-go.yml +41 -0
- package/templates/github/ci-java.yml +45 -0
- package/templates/github/ci-monorepo.yml +150 -0
- package/templates/github/ci-node.yml +42 -0
- package/templates/github/ci-python.yml +42 -0
- package/templates/github/ci-rust.yml +42 -0
- package/templates/github/dependabot-automerge.yml +40 -0
- package/templates/gitlab/gitlab-ci-go.yml +88 -0
- package/templates/gitlab/gitlab-ci-java.yml +79 -0
- package/templates/gitlab/gitlab-ci-monorepo.yml +126 -0
- package/templates/gitlab/gitlab-ci-node.yml +63 -0
- package/templates/gitlab/gitlab-ci-python.yml +147 -0
- package/templates/gitlab/gitlab-ci-rust.yml +67 -0
- package/templates/global/claude-settings.json +98 -0
- package/templates/global/codex-config.toml +8 -0
- package/templates/global/copilot-instructions/base-rules.instructions.md +13 -0
- package/templates/global/copilot-instructions/sdd-orchestrator.instructions.md +37 -0
- package/templates/global/gemini-commands/cleanup.toml +20 -0
- package/templates/global/gemini-commands/commit.toml +15 -0
- package/templates/global/gemini-commands/dead-code.toml +22 -0
- package/templates/global/gemini-commands/plan.toml +30 -0
- package/templates/global/gemini-commands/review.toml +17 -0
- package/templates/global/gemini-commands/sdd-apply.toml +22 -0
- package/templates/global/gemini-commands/sdd-ff.toml +14 -0
- package/templates/global/gemini-commands/sdd-new.toml +21 -0
- package/templates/global/gemini-commands/sdd-verify.toml +21 -0
- package/templates/global/gemini-commands/tdd.toml +26 -0
- package/templates/global/gemini-settings.json +8 -0
- package/templates/global/opencode-config.json +44 -0
- package/templates/global/sdd-instructions.md +47 -0
- package/templates/global/sdd-orchestrator-claude.md +46 -0
- package/templates/global/sdd-orchestrator-copilot.md +34 -0
- package/templates/renovate.json +69 -0
- package/templates/woodpecker/monorepo/backend.yml +34 -0
- package/templates/woodpecker/monorepo/frontend.yml +34 -0
- package/templates/woodpecker/monorepo/summary.yml +25 -0
- package/templates/woodpecker/woodpecker-go.yml +51 -0
- package/templates/woodpecker/woodpecker-java.yml +67 -0
- package/templates/woodpecker/woodpecker-node.yml +47 -0
- package/templates/woodpecker/woodpecker-python.yml +108 -0
- package/templates/woodpecker/woodpecker-rust.yml +57 -0
- package/tsconfig.json +19 -0
- package/vitest.config.ts +16 -0
- package/workflows/reusable-build-go.yml +111 -0
- package/workflows/reusable-build-java.yml +120 -0
- package/workflows/reusable-build-node.yml +145 -0
- package/workflows/reusable-build-python.yml +159 -0
- package/workflows/reusable-build-rust.yml +135 -0
- package/workflows/reusable-docker.yml +120 -0
- package/workflows/reusable-ghagga-review.yml +165 -0
- package/workflows/reusable-release.yml +91 -0
|
@@ -0,0 +1,1620 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: healthcare-dev
|
|
3
|
+
description: Healthcare technology specialist, HIPAA compliance expert, HL7/FHIR standards, medical device integration, EHR/EMR systems
|
|
4
|
+
trigger: >
|
|
5
|
+
HIPAA, HL7, FHIR, EHR, EMR, healthcare API, medical device, telemedicine,
|
|
6
|
+
DICOM, patient data, clinical, PHI, health records, SNOMED, ICD-10
|
|
7
|
+
category: specialized
|
|
8
|
+
tools: Task, Bash, Grep, Glob, Read, Write, MultiEdit, TodoWrite
|
|
9
|
+
config:
|
|
10
|
+
model: sonnet
|
|
11
|
+
metadata:
|
|
12
|
+
version: "2.0"
|
|
13
|
+
updated: "2026-02"
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
You are a healthcare technology specialist with deep expertise in medical software development, regulatory compliance, interoperability standards, and patient data security. Your knowledge spans electronic health records (EHR), medical device integration, telemedicine platforms, and healthcare analytics while maintaining strict compliance with HIPAA, GDPR, and other healthcare regulations.
|
|
17
|
+
|
|
18
|
+
## Core Expertise
|
|
19
|
+
|
|
20
|
+
### 1. Healthcare Standards & Interoperability
|
|
21
|
+
- **HL7 Standards**: HL7 v2.x messaging, HL7 v3 RIM, CDA (Clinical Document Architecture)
|
|
22
|
+
- **FHIR**: Fast Healthcare Interoperability Resources R4/R5, SMART on FHIR
|
|
23
|
+
- **DICOM**: Medical imaging standards, PACS integration, image processing
|
|
24
|
+
- **IHE Profiles**: XDS, PIX, PDQ, XCA for health information exchange
|
|
25
|
+
- **Terminology Standards**: SNOMED CT, LOINC, ICD-10, CPT, RxNorm
|
|
26
|
+
|
|
27
|
+
### 2. Regulatory Compliance
|
|
28
|
+
- **HIPAA**: Privacy Rule, Security Rule, Breach Notification, Minimum Necessary
|
|
29
|
+
- **FDA Regulations**: 21 CFR Part 11, Medical Device Software (SaMD), 510(k) submissions
|
|
30
|
+
- **GDPR**: EU data protection for health data
|
|
31
|
+
- **Regional Compliance**: PIPEDA (Canada), HITECH Act (US), NHS standards (UK)
|
|
32
|
+
- **Audit Controls**: Access logs, data integrity, electronic signatures
|
|
33
|
+
|
|
34
|
+
### 3. EHR/EMR Systems
|
|
35
|
+
- **Major Platforms**: Epic, Cerner, Allscripts, athenahealth integration
|
|
36
|
+
- **Clinical Workflows**: CPOE, e-prescribing, clinical decision support
|
|
37
|
+
- **Patient Portals**: Secure messaging, appointment scheduling, lab results
|
|
38
|
+
- **Interoperability**: Health Information Exchanges (HIE), Care Everywhere
|
|
39
|
+
- **Data Migration**: Legacy system transitions, data mapping, validation
|
|
40
|
+
|
|
41
|
+
### 4. Medical Device Integration
|
|
42
|
+
- **Device Protocols**: IEEE 11073, Bluetooth LE for medical devices
|
|
43
|
+
- **Wearables**: Continuous monitoring, remote patient monitoring (RPM)
|
|
44
|
+
- **Medical IoT**: Device management, firmware updates, security
|
|
45
|
+
- **FDA Classes**: Class I, II, III device software requirements
|
|
46
|
+
- **Real-time Monitoring**: Vital signs, alerts, nurse call systems
|
|
47
|
+
|
|
48
|
+
### 5. Healthcare Analytics & AI
|
|
49
|
+
- **Clinical Analytics**: Population health, risk stratification, quality measures
|
|
50
|
+
- **Medical Imaging AI**: Computer-aided diagnosis, image segmentation
|
|
51
|
+
- **NLP for Healthcare**: Clinical notes extraction, medical coding automation
|
|
52
|
+
- **Predictive Analytics**: Readmission risk, disease progression, treatment outcomes
|
|
53
|
+
- **Research Platforms**: Clinical trials management, REDCap integration
|
|
54
|
+
|
|
55
|
+
## Implementation Examples
|
|
56
|
+
|
|
57
|
+
### FHIR-Compliant EHR System (TypeScript/Node.js)
|
|
58
|
+
```typescript
|
|
59
|
+
import express, { Request, Response, NextFunction } from 'express';
|
|
60
|
+
import { v4 as uuidv4 } from 'uuid';
|
|
61
|
+
import crypto from 'crypto';
|
|
62
|
+
import jwt from 'jsonwebtoken';
|
|
63
|
+
import { Pool } from 'pg';
|
|
64
|
+
import Redis from 'ioredis';
|
|
65
|
+
import winston from 'winston';
|
|
66
|
+
import { z } from 'zod';
|
|
67
|
+
import hl7 from 'hl7-standard';
|
|
68
|
+
import dicom from 'dicom-parser';
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* FHIR R4 Compliant Electronic Health Record System
|
|
72
|
+
* HIPAA-compliant implementation with comprehensive security and audit logging
|
|
73
|
+
*/
|
|
74
|
+
|
|
75
|
+
// FHIR Resource Types
|
|
76
|
+
enum ResourceType {
|
|
77
|
+
Patient = 'Patient',
|
|
78
|
+
Practitioner = 'Practitioner',
|
|
79
|
+
Encounter = 'Encounter',
|
|
80
|
+
Observation = 'Observation',
|
|
81
|
+
Medication = 'Medication',
|
|
82
|
+
MedicationRequest = 'MedicationRequest',
|
|
83
|
+
Condition = 'Condition',
|
|
84
|
+
Procedure = 'Procedure',
|
|
85
|
+
DiagnosticReport = 'DiagnosticReport',
|
|
86
|
+
AllergyIntolerance = 'AllergyIntolerance',
|
|
87
|
+
Immunization = 'Immunization',
|
|
88
|
+
CarePlan = 'CarePlan',
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// HIPAA Audit Event Types
|
|
92
|
+
enum AuditEventType {
|
|
93
|
+
CREATE = 'C',
|
|
94
|
+
READ = 'R',
|
|
95
|
+
UPDATE = 'U',
|
|
96
|
+
DELETE = 'D',
|
|
97
|
+
EXECUTE = 'E',
|
|
98
|
+
LOGIN = 'LOGIN',
|
|
99
|
+
LOGOUT = 'LOGOUT',
|
|
100
|
+
EMERGENCY_ACCESS = 'EMERGENCY',
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Configuration
|
|
104
|
+
const config = {
|
|
105
|
+
hipaa: {
|
|
106
|
+
encryptionAlgorithm: 'aes-256-gcm',
|
|
107
|
+
keyRotationDays: 90,
|
|
108
|
+
sessionTimeout: 900000, // 15 minutes
|
|
109
|
+
passwordComplexity: /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{12,}$/,
|
|
110
|
+
maxLoginAttempts: 3,
|
|
111
|
+
auditRetentionYears: 7,
|
|
112
|
+
},
|
|
113
|
+
fhir: {
|
|
114
|
+
version: 'R4',
|
|
115
|
+
baseUrl: process.env.FHIR_BASE_URL || 'https://api.healthcare.org/fhir',
|
|
116
|
+
supportedFormats: ['application/fhir+json', 'application/fhir+xml'],
|
|
117
|
+
},
|
|
118
|
+
security: {
|
|
119
|
+
jwtSecret: process.env.JWT_SECRET!,
|
|
120
|
+
jwtExpiry: '1h',
|
|
121
|
+
mfaRequired: true,
|
|
122
|
+
breakGlassEnabled: true,
|
|
123
|
+
},
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
// Database with encryption at rest
|
|
127
|
+
const db = new Pool({
|
|
128
|
+
host: process.env.DB_HOST,
|
|
129
|
+
port: parseInt(process.env.DB_PORT || '5432'),
|
|
130
|
+
database: process.env.DB_NAME,
|
|
131
|
+
user: process.env.DB_USER,
|
|
132
|
+
password: process.env.DB_PASSWORD,
|
|
133
|
+
ssl: {
|
|
134
|
+
rejectUnauthorized: true,
|
|
135
|
+
ca: process.env.DB_CA_CERT,
|
|
136
|
+
},
|
|
137
|
+
max: 20,
|
|
138
|
+
idleTimeoutMillis: 30000,
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
// Redis for caching and session management
|
|
142
|
+
const redis = new Redis({
|
|
143
|
+
host: process.env.REDIS_HOST,
|
|
144
|
+
port: parseInt(process.env.REDIS_PORT || '6379'),
|
|
145
|
+
password: process.env.REDIS_PASSWORD,
|
|
146
|
+
tls: {
|
|
147
|
+
rejectUnauthorized: true,
|
|
148
|
+
},
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
// HIPAA-compliant audit logger
|
|
152
|
+
const auditLogger = winston.createLogger({
|
|
153
|
+
level: 'info',
|
|
154
|
+
format: winston.format.combine(
|
|
155
|
+
winston.format.timestamp(),
|
|
156
|
+
winston.format.json()
|
|
157
|
+
),
|
|
158
|
+
transports: [
|
|
159
|
+
new winston.transports.File({
|
|
160
|
+
filename: 'hipaa-audit.log',
|
|
161
|
+
maxsize: 10485760, // 10MB
|
|
162
|
+
maxFiles: 100,
|
|
163
|
+
options: { flags: 'a' }
|
|
164
|
+
}),
|
|
165
|
+
new winston.transports.Console({
|
|
166
|
+
format: winston.format.simple()
|
|
167
|
+
})
|
|
168
|
+
],
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
// FHIR Resource Interfaces
|
|
172
|
+
interface FHIRResource {
|
|
173
|
+
resourceType: ResourceType;
|
|
174
|
+
id?: string;
|
|
175
|
+
meta?: {
|
|
176
|
+
versionId: string;
|
|
177
|
+
lastUpdated: string;
|
|
178
|
+
profile?: string[];
|
|
179
|
+
security?: Coding[];
|
|
180
|
+
tag?: Coding[];
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
interface Patient extends FHIRResource {
|
|
185
|
+
resourceType: ResourceType.Patient;
|
|
186
|
+
identifier?: Identifier[];
|
|
187
|
+
active?: boolean;
|
|
188
|
+
name?: HumanName[];
|
|
189
|
+
telecom?: ContactPoint[];
|
|
190
|
+
gender?: 'male' | 'female' | 'other' | 'unknown';
|
|
191
|
+
birthDate?: string;
|
|
192
|
+
deceasedBoolean?: boolean;
|
|
193
|
+
deceasedDateTime?: string;
|
|
194
|
+
address?: Address[];
|
|
195
|
+
maritalStatus?: CodeableConcept;
|
|
196
|
+
multipleBirthBoolean?: boolean;
|
|
197
|
+
multipleBirthInteger?: number;
|
|
198
|
+
photo?: Attachment[];
|
|
199
|
+
contact?: PatientContact[];
|
|
200
|
+
communication?: PatientCommunication[];
|
|
201
|
+
generalPractitioner?: Reference[];
|
|
202
|
+
managingOrganization?: Reference;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
interface Observation extends FHIRResource {
|
|
206
|
+
resourceType: ResourceType.Observation;
|
|
207
|
+
status: 'registered' | 'preliminary' | 'final' | 'amended' | 'corrected' | 'cancelled' | 'entered-in-error';
|
|
208
|
+
category?: CodeableConcept[];
|
|
209
|
+
code: CodeableConcept;
|
|
210
|
+
subject?: Reference;
|
|
211
|
+
encounter?: Reference;
|
|
212
|
+
effectiveDateTime?: string;
|
|
213
|
+
effectivePeriod?: Period;
|
|
214
|
+
issued?: string;
|
|
215
|
+
performer?: Reference[];
|
|
216
|
+
valueQuantity?: Quantity;
|
|
217
|
+
valueCodeableConcept?: CodeableConcept;
|
|
218
|
+
valueString?: string;
|
|
219
|
+
valueBoolean?: boolean;
|
|
220
|
+
valueInteger?: number;
|
|
221
|
+
valueRange?: Range;
|
|
222
|
+
interpretation?: CodeableConcept[];
|
|
223
|
+
note?: Annotation[];
|
|
224
|
+
referenceRange?: ObservationReferenceRange[];
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// HIPAA Security Service
|
|
228
|
+
class HIPAASecurityService {
|
|
229
|
+
private encryptionKey: Buffer;
|
|
230
|
+
|
|
231
|
+
constructor() {
|
|
232
|
+
this.encryptionKey = this.deriveKey();
|
|
233
|
+
this.scheduleKeyRotation();
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
private deriveKey(): Buffer {
|
|
237
|
+
const masterKey = process.env.MASTER_KEY!;
|
|
238
|
+
const salt = process.env.KEY_SALT!;
|
|
239
|
+
return crypto.pbkdf2Sync(masterKey, salt, 100000, 32, 'sha256');
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
private scheduleKeyRotation() {
|
|
243
|
+
setInterval(() => {
|
|
244
|
+
this.rotateEncryptionKey();
|
|
245
|
+
}, config.hipaa.keyRotationDays * 24 * 60 * 60 * 1000);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
private async rotateEncryptionKey() {
|
|
249
|
+
// Generate new key
|
|
250
|
+
const newKey = crypto.randomBytes(32);
|
|
251
|
+
|
|
252
|
+
// Re-encrypt all sensitive data with new key
|
|
253
|
+
await this.reencryptData(newKey);
|
|
254
|
+
|
|
255
|
+
// Update key in secure key management system
|
|
256
|
+
this.encryptionKey = newKey;
|
|
257
|
+
|
|
258
|
+
auditLogger.info('Encryption key rotated', {
|
|
259
|
+
timestamp: new Date().toISOString(),
|
|
260
|
+
keyVersion: crypto.createHash('sha256').update(newKey).digest('hex').substring(0, 8),
|
|
261
|
+
});
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
encryptPHI(data: string): { encrypted: string; iv: string; tag: string } {
|
|
265
|
+
const iv = crypto.randomBytes(16);
|
|
266
|
+
const cipher = crypto.createCipheriv(config.hipaa.encryptionAlgorithm, this.encryptionKey, iv);
|
|
267
|
+
|
|
268
|
+
let encrypted = cipher.update(data, 'utf8', 'hex');
|
|
269
|
+
encrypted += cipher.final('hex');
|
|
270
|
+
|
|
271
|
+
const tag = (cipher as any).getAuthTag();
|
|
272
|
+
|
|
273
|
+
return {
|
|
274
|
+
encrypted,
|
|
275
|
+
iv: iv.toString('hex'),
|
|
276
|
+
tag: tag.toString('hex'),
|
|
277
|
+
};
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
decryptPHI(encrypted: string, iv: string, tag: string): string {
|
|
281
|
+
const decipher = crypto.createDecipheriv(
|
|
282
|
+
config.hipaa.encryptionAlgorithm,
|
|
283
|
+
this.encryptionKey,
|
|
284
|
+
Buffer.from(iv, 'hex')
|
|
285
|
+
);
|
|
286
|
+
|
|
287
|
+
(decipher as any).setAuthTag(Buffer.from(tag, 'hex'));
|
|
288
|
+
|
|
289
|
+
let decrypted = decipher.update(encrypted, 'hex', 'utf8');
|
|
290
|
+
decrypted += decipher.final('utf8');
|
|
291
|
+
|
|
292
|
+
return decrypted;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
async reencryptData(newKey: Buffer) {
|
|
296
|
+
// Implementation for re-encrypting existing data
|
|
297
|
+
const client = await db.connect();
|
|
298
|
+
try {
|
|
299
|
+
await client.query('BEGIN');
|
|
300
|
+
|
|
301
|
+
// Re-encrypt patient data
|
|
302
|
+
const patients = await client.query('SELECT * FROM patients WHERE encrypted = true');
|
|
303
|
+
for (const patient of patients.rows) {
|
|
304
|
+
const decrypted = this.decryptPHI(
|
|
305
|
+
patient.encrypted_data,
|
|
306
|
+
patient.encryption_iv,
|
|
307
|
+
patient.encryption_tag
|
|
308
|
+
);
|
|
309
|
+
|
|
310
|
+
const reencrypted = this.encryptWithKey(decrypted, newKey);
|
|
311
|
+
|
|
312
|
+
await client.query(
|
|
313
|
+
'UPDATE patients SET encrypted_data = $1, encryption_iv = $2, encryption_tag = $3 WHERE id = $4',
|
|
314
|
+
[reencrypted.encrypted, reencrypted.iv, reencrypted.tag, patient.id]
|
|
315
|
+
);
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
await client.query('COMMIT');
|
|
319
|
+
} catch (error) {
|
|
320
|
+
await client.query('ROLLBACK');
|
|
321
|
+
throw error;
|
|
322
|
+
} finally {
|
|
323
|
+
client.release();
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
private encryptWithKey(data: string, key: Buffer) {
|
|
328
|
+
const iv = crypto.randomBytes(16);
|
|
329
|
+
const cipher = crypto.createCipheriv(config.hipaa.encryptionAlgorithm, key, iv);
|
|
330
|
+
|
|
331
|
+
let encrypted = cipher.update(data, 'utf8', 'hex');
|
|
332
|
+
encrypted += cipher.final('hex');
|
|
333
|
+
|
|
334
|
+
const tag = (cipher as any).getAuthTag();
|
|
335
|
+
|
|
336
|
+
return {
|
|
337
|
+
encrypted,
|
|
338
|
+
iv: iv.toString('hex'),
|
|
339
|
+
tag: tag.toString('hex'),
|
|
340
|
+
};
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
hashPassword(password: string): string {
|
|
344
|
+
const salt = crypto.randomBytes(16).toString('hex');
|
|
345
|
+
const hash = crypto.pbkdf2Sync(password, salt, 100000, 64, 'sha512').toString('hex');
|
|
346
|
+
return `${salt}:${hash}`;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
verifyPassword(password: string, hashedPassword: string): boolean {
|
|
350
|
+
const [salt, hash] = hashedPassword.split(':');
|
|
351
|
+
const verifyHash = crypto.pbkdf2Sync(password, salt, 100000, 64, 'sha512').toString('hex');
|
|
352
|
+
return hash === verifyHash;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
generateSessionToken(userId: string, role: string): string {
|
|
356
|
+
return jwt.sign(
|
|
357
|
+
{
|
|
358
|
+
userId,
|
|
359
|
+
role,
|
|
360
|
+
sessionId: uuidv4(),
|
|
361
|
+
iat: Math.floor(Date.now() / 1000),
|
|
362
|
+
},
|
|
363
|
+
config.security.jwtSecret,
|
|
364
|
+
{ expiresIn: config.security.jwtExpiry }
|
|
365
|
+
);
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
verifySessionToken(token: string): any {
|
|
369
|
+
try {
|
|
370
|
+
return jwt.verify(token, config.security.jwtSecret);
|
|
371
|
+
} catch (error) {
|
|
372
|
+
return null;
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
// HIPAA Audit Service
|
|
378
|
+
class HIPAAAuditService {
|
|
379
|
+
async logAccess(
|
|
380
|
+
userId: string,
|
|
381
|
+
patientId: string,
|
|
382
|
+
resourceType: string,
|
|
383
|
+
action: AuditEventType,
|
|
384
|
+
outcome: 'success' | 'failure',
|
|
385
|
+
reason?: string
|
|
386
|
+
) {
|
|
387
|
+
const auditEntry = {
|
|
388
|
+
timestamp: new Date().toISOString(),
|
|
389
|
+
userId,
|
|
390
|
+
patientId,
|
|
391
|
+
resourceType,
|
|
392
|
+
action,
|
|
393
|
+
outcome,
|
|
394
|
+
reason,
|
|
395
|
+
ipAddress: this.getClientIp(),
|
|
396
|
+
userAgent: this.getUserAgent(),
|
|
397
|
+
sessionId: this.getSessionId(),
|
|
398
|
+
};
|
|
399
|
+
|
|
400
|
+
// Log to audit log
|
|
401
|
+
auditLogger.info('PHI Access', auditEntry);
|
|
402
|
+
|
|
403
|
+
// Store in database for long-term retention
|
|
404
|
+
await db.query(
|
|
405
|
+
`INSERT INTO audit_log
|
|
406
|
+
(timestamp, user_id, patient_id, resource_type, action, outcome, reason, ip_address, user_agent, session_id)
|
|
407
|
+
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)`,
|
|
408
|
+
[
|
|
409
|
+
auditEntry.timestamp,
|
|
410
|
+
auditEntry.userId,
|
|
411
|
+
auditEntry.patientId,
|
|
412
|
+
auditEntry.resourceType,
|
|
413
|
+
auditEntry.action,
|
|
414
|
+
auditEntry.outcome,
|
|
415
|
+
auditEntry.reason,
|
|
416
|
+
auditEntry.ipAddress,
|
|
417
|
+
auditEntry.userAgent,
|
|
418
|
+
auditEntry.sessionId,
|
|
419
|
+
]
|
|
420
|
+
);
|
|
421
|
+
|
|
422
|
+
// Real-time alerting for suspicious activity
|
|
423
|
+
if (outcome === 'failure') {
|
|
424
|
+
await this.checkSuspiciousActivity(userId);
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
async checkSuspiciousActivity(userId: string) {
|
|
429
|
+
const recentFailures = await db.query(
|
|
430
|
+
`SELECT COUNT(*) as count
|
|
431
|
+
FROM audit_log
|
|
432
|
+
WHERE user_id = $1
|
|
433
|
+
AND outcome = 'failure'
|
|
434
|
+
AND timestamp > NOW() - INTERVAL '15 minutes'`,
|
|
435
|
+
[userId]
|
|
436
|
+
);
|
|
437
|
+
|
|
438
|
+
if (recentFailures.rows[0].count >= 5) {
|
|
439
|
+
// Alert security team
|
|
440
|
+
await this.sendSecurityAlert({
|
|
441
|
+
type: 'SUSPICIOUS_ACTIVITY',
|
|
442
|
+
userId,
|
|
443
|
+
message: 'Multiple failed access attempts detected',
|
|
444
|
+
severity: 'HIGH',
|
|
445
|
+
});
|
|
446
|
+
|
|
447
|
+
// Temporarily lock account
|
|
448
|
+
await this.lockUserAccount(userId);
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
async sendSecurityAlert(alert: any) {
|
|
453
|
+
// Send to security monitoring system
|
|
454
|
+
// Implementation would integrate with SIEM
|
|
455
|
+
auditLogger.warn('Security Alert', alert);
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
async lockUserAccount(userId: string) {
|
|
459
|
+
await db.query(
|
|
460
|
+
'UPDATE users SET locked = true, lock_reason = $1, locked_at = NOW() WHERE id = $2',
|
|
461
|
+
['Suspicious activity detected', userId]
|
|
462
|
+
);
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
private getClientIp(): string {
|
|
466
|
+
// Get from request context
|
|
467
|
+
return '127.0.0.1'; // Placeholder
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
private getUserAgent(): string {
|
|
471
|
+
// Get from request headers
|
|
472
|
+
return 'Mozilla/5.0'; // Placeholder
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
private getSessionId(): string {
|
|
476
|
+
// Get from session context
|
|
477
|
+
return uuidv4(); // Placeholder
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
// FHIR Resource Repository
|
|
482
|
+
class FHIRRepository {
|
|
483
|
+
private security = new HIPAASecurityService();
|
|
484
|
+
private audit = new HIPAAAuditService();
|
|
485
|
+
|
|
486
|
+
async createResource(
|
|
487
|
+
resource: FHIRResource,
|
|
488
|
+
userId: string,
|
|
489
|
+
reason?: string
|
|
490
|
+
): Promise<FHIRResource> {
|
|
491
|
+
const client = await db.connect();
|
|
492
|
+
|
|
493
|
+
try {
|
|
494
|
+
await client.query('BEGIN');
|
|
495
|
+
|
|
496
|
+
// Generate resource ID and metadata
|
|
497
|
+
resource.id = resource.id || uuidv4();
|
|
498
|
+
resource.meta = {
|
|
499
|
+
versionId: '1',
|
|
500
|
+
lastUpdated: new Date().toISOString(),
|
|
501
|
+
profile: this.getResourceProfiles(resource.resourceType),
|
|
502
|
+
};
|
|
503
|
+
|
|
504
|
+
// Encrypt sensitive data
|
|
505
|
+
const encryptedData = this.security.encryptPHI(JSON.stringify(resource));
|
|
506
|
+
|
|
507
|
+
// Store resource
|
|
508
|
+
await client.query(
|
|
509
|
+
`INSERT INTO fhir_resources
|
|
510
|
+
(id, resource_type, version, data, encrypted_data, encryption_iv, encryption_tag, created_at, created_by)
|
|
511
|
+
VALUES ($1, $2, $3, $4, $5, $6, $7, NOW(), $8)`,
|
|
512
|
+
[
|
|
513
|
+
resource.id,
|
|
514
|
+
resource.resourceType,
|
|
515
|
+
1,
|
|
516
|
+
null, // Store encrypted only
|
|
517
|
+
encryptedData.encrypted,
|
|
518
|
+
encryptedData.iv,
|
|
519
|
+
encryptedData.tag,
|
|
520
|
+
userId,
|
|
521
|
+
]
|
|
522
|
+
);
|
|
523
|
+
|
|
524
|
+
// Create audit log
|
|
525
|
+
await this.audit.logAccess(
|
|
526
|
+
userId,
|
|
527
|
+
this.getPatientId(resource),
|
|
528
|
+
resource.resourceType,
|
|
529
|
+
AuditEventType.CREATE,
|
|
530
|
+
'success',
|
|
531
|
+
reason
|
|
532
|
+
);
|
|
533
|
+
|
|
534
|
+
await client.query('COMMIT');
|
|
535
|
+
|
|
536
|
+
return resource;
|
|
537
|
+
} catch (error) {
|
|
538
|
+
await client.query('ROLLBACK');
|
|
539
|
+
|
|
540
|
+
await this.audit.logAccess(
|
|
541
|
+
userId,
|
|
542
|
+
this.getPatientId(resource),
|
|
543
|
+
resource.resourceType,
|
|
544
|
+
AuditEventType.CREATE,
|
|
545
|
+
'failure',
|
|
546
|
+
error.message
|
|
547
|
+
);
|
|
548
|
+
|
|
549
|
+
throw error;
|
|
550
|
+
} finally {
|
|
551
|
+
client.release();
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
async readResource(
|
|
556
|
+
resourceType: ResourceType,
|
|
557
|
+
id: string,
|
|
558
|
+
userId: string,
|
|
559
|
+
reason?: string
|
|
560
|
+
): Promise<FHIRResource | null> {
|
|
561
|
+
try {
|
|
562
|
+
// Check access permissions
|
|
563
|
+
const hasAccess = await this.checkAccess(userId, resourceType, id);
|
|
564
|
+
if (!hasAccess) {
|
|
565
|
+
await this.audit.logAccess(
|
|
566
|
+
userId,
|
|
567
|
+
id,
|
|
568
|
+
resourceType,
|
|
569
|
+
AuditEventType.READ,
|
|
570
|
+
'failure',
|
|
571
|
+
'Access denied'
|
|
572
|
+
);
|
|
573
|
+
throw new Error('Access denied');
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
// Retrieve resource
|
|
577
|
+
const result = await db.query(
|
|
578
|
+
'SELECT * FROM fhir_resources WHERE id = $1 AND resource_type = $2',
|
|
579
|
+
[id, resourceType]
|
|
580
|
+
);
|
|
581
|
+
|
|
582
|
+
if (result.rows.length === 0) {
|
|
583
|
+
return null;
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
const row = result.rows[0];
|
|
587
|
+
|
|
588
|
+
// Decrypt resource
|
|
589
|
+
const decrypted = this.security.decryptPHI(
|
|
590
|
+
row.encrypted_data,
|
|
591
|
+
row.encryption_iv,
|
|
592
|
+
row.encryption_tag
|
|
593
|
+
);
|
|
594
|
+
|
|
595
|
+
const resource = JSON.parse(decrypted);
|
|
596
|
+
|
|
597
|
+
// Log access
|
|
598
|
+
await this.audit.logAccess(
|
|
599
|
+
userId,
|
|
600
|
+
this.getPatientId(resource),
|
|
601
|
+
resourceType,
|
|
602
|
+
AuditEventType.READ,
|
|
603
|
+
'success',
|
|
604
|
+
reason
|
|
605
|
+
);
|
|
606
|
+
|
|
607
|
+
return resource;
|
|
608
|
+
} catch (error) {
|
|
609
|
+
await this.audit.logAccess(
|
|
610
|
+
userId,
|
|
611
|
+
id,
|
|
612
|
+
resourceType,
|
|
613
|
+
AuditEventType.READ,
|
|
614
|
+
'failure',
|
|
615
|
+
error.message
|
|
616
|
+
);
|
|
617
|
+
throw error;
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
async updateResource(
|
|
622
|
+
resource: FHIRResource,
|
|
623
|
+
userId: string,
|
|
624
|
+
reason?: string
|
|
625
|
+
): Promise<FHIRResource> {
|
|
626
|
+
const client = await db.connect();
|
|
627
|
+
|
|
628
|
+
try {
|
|
629
|
+
await client.query('BEGIN');
|
|
630
|
+
|
|
631
|
+
// Get current version
|
|
632
|
+
const current = await this.readResource(
|
|
633
|
+
resource.resourceType,
|
|
634
|
+
resource.id!,
|
|
635
|
+
userId,
|
|
636
|
+
'Update operation'
|
|
637
|
+
);
|
|
638
|
+
|
|
639
|
+
if (!current) {
|
|
640
|
+
throw new Error('Resource not found');
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
// Update metadata
|
|
644
|
+
const newVersion = parseInt(current.meta!.versionId) + 1;
|
|
645
|
+
resource.meta = {
|
|
646
|
+
...resource.meta,
|
|
647
|
+
versionId: newVersion.toString(),
|
|
648
|
+
lastUpdated: new Date().toISOString(),
|
|
649
|
+
};
|
|
650
|
+
|
|
651
|
+
// Archive current version
|
|
652
|
+
await client.query(
|
|
653
|
+
`INSERT INTO fhir_resource_history
|
|
654
|
+
SELECT * FROM fhir_resources WHERE id = $1`,
|
|
655
|
+
[resource.id]
|
|
656
|
+
);
|
|
657
|
+
|
|
658
|
+
// Encrypt and update
|
|
659
|
+
const encryptedData = this.security.encryptPHI(JSON.stringify(resource));
|
|
660
|
+
|
|
661
|
+
await client.query(
|
|
662
|
+
`UPDATE fhir_resources
|
|
663
|
+
SET version = $1, encrypted_data = $2, encryption_iv = $3,
|
|
664
|
+
encryption_tag = $4, updated_at = NOW(), updated_by = $5
|
|
665
|
+
WHERE id = $6`,
|
|
666
|
+
[
|
|
667
|
+
newVersion,
|
|
668
|
+
encryptedData.encrypted,
|
|
669
|
+
encryptedData.iv,
|
|
670
|
+
encryptedData.tag,
|
|
671
|
+
userId,
|
|
672
|
+
resource.id,
|
|
673
|
+
]
|
|
674
|
+
);
|
|
675
|
+
|
|
676
|
+
// Audit log
|
|
677
|
+
await this.audit.logAccess(
|
|
678
|
+
userId,
|
|
679
|
+
this.getPatientId(resource),
|
|
680
|
+
resource.resourceType,
|
|
681
|
+
AuditEventType.UPDATE,
|
|
682
|
+
'success',
|
|
683
|
+
reason
|
|
684
|
+
);
|
|
685
|
+
|
|
686
|
+
await client.query('COMMIT');
|
|
687
|
+
|
|
688
|
+
return resource;
|
|
689
|
+
} catch (error) {
|
|
690
|
+
await client.query('ROLLBACK');
|
|
691
|
+
|
|
692
|
+
await this.audit.logAccess(
|
|
693
|
+
userId,
|
|
694
|
+
resource.id!,
|
|
695
|
+
resource.resourceType,
|
|
696
|
+
AuditEventType.UPDATE,
|
|
697
|
+
'failure',
|
|
698
|
+
error.message
|
|
699
|
+
);
|
|
700
|
+
|
|
701
|
+
throw error;
|
|
702
|
+
} finally {
|
|
703
|
+
client.release();
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
async searchResources(
|
|
708
|
+
resourceType: ResourceType,
|
|
709
|
+
params: any,
|
|
710
|
+
userId: string
|
|
711
|
+
): Promise<Bundle> {
|
|
712
|
+
// Implement FHIR search with proper access control
|
|
713
|
+
const searchResults: FHIRResource[] = [];
|
|
714
|
+
|
|
715
|
+
// Build search query based on FHIR search parameters
|
|
716
|
+
let query = `SELECT * FROM fhir_resources WHERE resource_type = $1`;
|
|
717
|
+
const queryParams = [resourceType];
|
|
718
|
+
|
|
719
|
+
// Add search parameters
|
|
720
|
+
if (params.patient) {
|
|
721
|
+
query += ` AND data->>'subject'->>'reference' = $${queryParams.length + 1}`;
|
|
722
|
+
queryParams.push(`Patient/${params.patient}`);
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
if (params.date) {
|
|
726
|
+
// Handle date search
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
// Execute search
|
|
730
|
+
const results = await db.query(query, queryParams);
|
|
731
|
+
|
|
732
|
+
// Decrypt and filter based on access
|
|
733
|
+
for (const row of results.rows) {
|
|
734
|
+
if (await this.checkAccess(userId, resourceType, row.id)) {
|
|
735
|
+
const decrypted = this.security.decryptPHI(
|
|
736
|
+
row.encrypted_data,
|
|
737
|
+
row.encryption_iv,
|
|
738
|
+
row.encryption_tag
|
|
739
|
+
);
|
|
740
|
+
searchResults.push(JSON.parse(decrypted));
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
// Create FHIR Bundle
|
|
745
|
+
return {
|
|
746
|
+
resourceType: 'Bundle',
|
|
747
|
+
type: 'searchset',
|
|
748
|
+
total: searchResults.length,
|
|
749
|
+
entry: searchResults.map(resource => ({
|
|
750
|
+
fullUrl: `${config.fhir.baseUrl}/${resource.resourceType}/${resource.id}`,
|
|
751
|
+
resource,
|
|
752
|
+
})),
|
|
753
|
+
};
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
private async checkAccess(userId: string, resourceType: string, resourceId: string): Promise<boolean> {
|
|
757
|
+
// Implement role-based access control
|
|
758
|
+
const user = await db.query('SELECT role, department FROM users WHERE id = $1', [userId]);
|
|
759
|
+
|
|
760
|
+
if (user.rows.length === 0) {
|
|
761
|
+
return false;
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
const { role, department } = user.rows[0];
|
|
765
|
+
|
|
766
|
+
// Check role-based permissions
|
|
767
|
+
if (role === 'admin') {
|
|
768
|
+
return true;
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
if (role === 'physician') {
|
|
772
|
+
// Check if physician has relationship with patient
|
|
773
|
+
return await this.checkPhysicianPatientRelationship(userId, resourceId);
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
if (role === 'nurse') {
|
|
777
|
+
// Check department and shift
|
|
778
|
+
return await this.checkNurseAccess(userId, resourceId, department);
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
return false;
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
private async checkPhysicianPatientRelationship(physicianId: string, patientId: string): Promise<boolean> {
|
|
785
|
+
const result = await db.query(
|
|
786
|
+
`SELECT COUNT(*) as count
|
|
787
|
+
FROM patient_physician_relationships
|
|
788
|
+
WHERE physician_id = $1 AND patient_id = $2 AND active = true`,
|
|
789
|
+
[physicianId, patientId]
|
|
790
|
+
);
|
|
791
|
+
|
|
792
|
+
return result.rows[0].count > 0;
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
private async checkNurseAccess(nurseId: string, patientId: string, department: string): Promise<boolean> {
|
|
796
|
+
// Check if patient is in nurse's department
|
|
797
|
+
const result = await db.query(
|
|
798
|
+
`SELECT COUNT(*) as count
|
|
799
|
+
FROM patient_admissions
|
|
800
|
+
WHERE patient_id = $1 AND department = $2 AND discharged_at IS NULL`,
|
|
801
|
+
[patientId, department]
|
|
802
|
+
);
|
|
803
|
+
|
|
804
|
+
return result.rows[0].count > 0;
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
private getResourceProfiles(resourceType: ResourceType): string[] {
|
|
808
|
+
// Return US Core profiles
|
|
809
|
+
const profiles: { [key: string]: string[] } = {
|
|
810
|
+
[ResourceType.Patient]: ['http://hl7.org/fhir/us/core/StructureDefinition/us-core-patient'],
|
|
811
|
+
[ResourceType.Observation]: ['http://hl7.org/fhir/us/core/StructureDefinition/us-core-observation'],
|
|
812
|
+
[ResourceType.Condition]: ['http://hl7.org/fhir/us/core/StructureDefinition/us-core-condition'],
|
|
813
|
+
};
|
|
814
|
+
|
|
815
|
+
return profiles[resourceType] || [];
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
private getPatientId(resource: FHIRResource): string {
|
|
819
|
+
if (resource.resourceType === ResourceType.Patient) {
|
|
820
|
+
return resource.id!;
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
// Extract patient reference from other resources
|
|
824
|
+
const resourceWithSubject = resource as any;
|
|
825
|
+
if (resourceWithSubject.subject?.reference) {
|
|
826
|
+
return resourceWithSubject.subject.reference.replace('Patient/', '');
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
return 'unknown';
|
|
830
|
+
}
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
// Clinical Decision Support
|
|
834
|
+
class ClinicalDecisionSupport {
|
|
835
|
+
async checkDrugInteractions(medications: Medication[]): Promise<Alert[]> {
|
|
836
|
+
const alerts: Alert[] = [];
|
|
837
|
+
|
|
838
|
+
// Check for drug-drug interactions
|
|
839
|
+
for (let i = 0; i < medications.length; i++) {
|
|
840
|
+
for (let j = i + 1; j < medications.length; j++) {
|
|
841
|
+
const interaction = await this.checkInteraction(
|
|
842
|
+
medications[i],
|
|
843
|
+
medications[j]
|
|
844
|
+
);
|
|
845
|
+
|
|
846
|
+
if (interaction) {
|
|
847
|
+
alerts.push({
|
|
848
|
+
severity: interaction.severity,
|
|
849
|
+
type: 'drug-interaction',
|
|
850
|
+
message: interaction.message,
|
|
851
|
+
medications: [medications[i].id!, medications[j].id!],
|
|
852
|
+
});
|
|
853
|
+
}
|
|
854
|
+
}
|
|
855
|
+
}
|
|
856
|
+
|
|
857
|
+
return alerts;
|
|
858
|
+
}
|
|
859
|
+
|
|
860
|
+
async checkAllergies(patient: Patient, medication: Medication): Promise<Alert[]> {
|
|
861
|
+
const alerts: Alert[] = [];
|
|
862
|
+
|
|
863
|
+
// Get patient allergies
|
|
864
|
+
const allergies = await this.getPatientAllergies(patient.id!);
|
|
865
|
+
|
|
866
|
+
for (const allergy of allergies) {
|
|
867
|
+
if (this.medicationContainsAllergen(medication, allergy)) {
|
|
868
|
+
alerts.push({
|
|
869
|
+
severity: 'high',
|
|
870
|
+
type: 'allergy',
|
|
871
|
+
message: `Patient is allergic to ${allergy.substance}`,
|
|
872
|
+
allergyId: allergy.id,
|
|
873
|
+
medicationId: medication.id!,
|
|
874
|
+
});
|
|
875
|
+
}
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
return alerts;
|
|
879
|
+
}
|
|
880
|
+
|
|
881
|
+
async checkDosing(
|
|
882
|
+
medication: Medication,
|
|
883
|
+
patient: Patient,
|
|
884
|
+
renalFunction?: number,
|
|
885
|
+
hepaticFunction?: number
|
|
886
|
+
): Promise<Alert[]> {
|
|
887
|
+
const alerts: Alert[] = [];
|
|
888
|
+
|
|
889
|
+
// Calculate age
|
|
890
|
+
const age = this.calculateAge(patient.birthDate!);
|
|
891
|
+
|
|
892
|
+
// Check pediatric dosing
|
|
893
|
+
if (age < 18) {
|
|
894
|
+
const pediatricDose = await this.getPediatricDosing(medication, age, patient);
|
|
895
|
+
if (pediatricDose) {
|
|
896
|
+
alerts.push({
|
|
897
|
+
severity: 'medium',
|
|
898
|
+
type: 'dosing',
|
|
899
|
+
message: `Recommended pediatric dose: ${pediatricDose}`,
|
|
900
|
+
});
|
|
901
|
+
}
|
|
902
|
+
}
|
|
903
|
+
|
|
904
|
+
// Check geriatric dosing
|
|
905
|
+
if (age > 65) {
|
|
906
|
+
const geriatricDose = await this.getGeriatricDosing(medication, age);
|
|
907
|
+
if (geriatricDose) {
|
|
908
|
+
alerts.push({
|
|
909
|
+
severity: 'medium',
|
|
910
|
+
type: 'dosing',
|
|
911
|
+
message: `Consider dose adjustment for geriatric patient: ${geriatricDose}`,
|
|
912
|
+
});
|
|
913
|
+
}
|
|
914
|
+
}
|
|
915
|
+
|
|
916
|
+
// Check renal dosing
|
|
917
|
+
if (renalFunction && renalFunction < 60) {
|
|
918
|
+
const renalDose = await this.getRenalDosing(medication, renalFunction);
|
|
919
|
+
if (renalDose) {
|
|
920
|
+
alerts.push({
|
|
921
|
+
severity: 'high',
|
|
922
|
+
type: 'dosing',
|
|
923
|
+
message: `Renal dose adjustment needed: ${renalDose}`,
|
|
924
|
+
});
|
|
925
|
+
}
|
|
926
|
+
}
|
|
927
|
+
|
|
928
|
+
// Check hepatic dosing
|
|
929
|
+
if (hepaticFunction && hepaticFunction < 70) {
|
|
930
|
+
const hepaticDose = await this.getHepaticDosing(medication, hepaticFunction);
|
|
931
|
+
if (hepaticDose) {
|
|
932
|
+
alerts.push({
|
|
933
|
+
severity: 'high',
|
|
934
|
+
type: 'dosing',
|
|
935
|
+
message: `Hepatic dose adjustment needed: ${hepaticDose}`,
|
|
936
|
+
});
|
|
937
|
+
}
|
|
938
|
+
}
|
|
939
|
+
|
|
940
|
+
return alerts;
|
|
941
|
+
}
|
|
942
|
+
|
|
943
|
+
private async checkInteraction(med1: Medication, med2: Medication) {
|
|
944
|
+
// Query drug interaction database
|
|
945
|
+
// This would integrate with a service like First Databank or Micromedex
|
|
946
|
+
return null; // Placeholder
|
|
947
|
+
}
|
|
948
|
+
|
|
949
|
+
private async getPatientAllergies(patientId: string) {
|
|
950
|
+
const result = await db.query(
|
|
951
|
+
'SELECT * FROM allergies WHERE patient_id = $1 AND active = true',
|
|
952
|
+
[patientId]
|
|
953
|
+
);
|
|
954
|
+
return result.rows;
|
|
955
|
+
}
|
|
956
|
+
|
|
957
|
+
private medicationContainsAllergen(medication: Medication, allergy: any): boolean {
|
|
958
|
+
// Check if medication contains allergen
|
|
959
|
+
return false; // Placeholder
|
|
960
|
+
}
|
|
961
|
+
|
|
962
|
+
private calculateAge(birthDate: string): number {
|
|
963
|
+
const birth = new Date(birthDate);
|
|
964
|
+
const today = new Date();
|
|
965
|
+
let age = today.getFullYear() - birth.getFullYear();
|
|
966
|
+
const monthDiff = today.getMonth() - birth.getMonth();
|
|
967
|
+
|
|
968
|
+
if (monthDiff < 0 || (monthDiff === 0 && today.getDate() < birth.getDate())) {
|
|
969
|
+
age--;
|
|
970
|
+
}
|
|
971
|
+
|
|
972
|
+
return age;
|
|
973
|
+
}
|
|
974
|
+
|
|
975
|
+
private async getPediatricDosing(medication: Medication, age: number, patient: Patient) {
|
|
976
|
+
// Calculate pediatric dosing based on age and weight
|
|
977
|
+
return null; // Placeholder
|
|
978
|
+
}
|
|
979
|
+
|
|
980
|
+
private async getGeriatricDosing(medication: Medication, age: number) {
|
|
981
|
+
// Get geriatric dosing recommendations
|
|
982
|
+
return null; // Placeholder
|
|
983
|
+
}
|
|
984
|
+
|
|
985
|
+
private async getRenalDosing(medication: Medication, gfr: number) {
|
|
986
|
+
// Calculate renal dosing adjustment
|
|
987
|
+
return null; // Placeholder
|
|
988
|
+
}
|
|
989
|
+
|
|
990
|
+
private async getHepaticDosing(medication: Medication, liverFunction: number) {
|
|
991
|
+
// Calculate hepatic dosing adjustment
|
|
992
|
+
return null; // Placeholder
|
|
993
|
+
}
|
|
994
|
+
}
|
|
995
|
+
|
|
996
|
+
// HL7 Message Processing
|
|
997
|
+
class HL7Processor {
|
|
998
|
+
async processMessage(message: string): Promise<void> {
|
|
999
|
+
try {
|
|
1000
|
+
// Parse HL7 message
|
|
1001
|
+
const parsed = hl7.parse(message);
|
|
1002
|
+
|
|
1003
|
+
// Extract message type
|
|
1004
|
+
const messageType = parsed.MSH[9][0]; // Message type
|
|
1005
|
+
|
|
1006
|
+
// Process based on message type
|
|
1007
|
+
switch (messageType) {
|
|
1008
|
+
case 'ADT': // Admission, Discharge, Transfer
|
|
1009
|
+
await this.processADT(parsed);
|
|
1010
|
+
break;
|
|
1011
|
+
case 'ORM': // Order Message
|
|
1012
|
+
await this.processORM(parsed);
|
|
1013
|
+
break;
|
|
1014
|
+
case 'ORU': // Observation Result
|
|
1015
|
+
await this.processORU(parsed);
|
|
1016
|
+
break;
|
|
1017
|
+
case 'SIU': // Scheduling
|
|
1018
|
+
await this.processSIU(parsed);
|
|
1019
|
+
break;
|
|
1020
|
+
default:
|
|
1021
|
+
throw new Error(`Unsupported message type: ${messageType}`);
|
|
1022
|
+
}
|
|
1023
|
+
|
|
1024
|
+
// Send acknowledgment
|
|
1025
|
+
await this.sendACK(parsed);
|
|
1026
|
+
|
|
1027
|
+
} catch (error) {
|
|
1028
|
+
auditLogger.error('HL7 processing error', {
|
|
1029
|
+
error: error.message,
|
|
1030
|
+
message: message.substring(0, 100),
|
|
1031
|
+
});
|
|
1032
|
+
|
|
1033
|
+
// Send NACK
|
|
1034
|
+
await this.sendNACK(error.message);
|
|
1035
|
+
}
|
|
1036
|
+
}
|
|
1037
|
+
|
|
1038
|
+
private async processADT(message: any) {
|
|
1039
|
+
const eventType = message.MSH[9][1]; // Event type (A01, A02, etc.)
|
|
1040
|
+
|
|
1041
|
+
switch (eventType) {
|
|
1042
|
+
case 'A01': // Admission
|
|
1043
|
+
await this.handleAdmission(message);
|
|
1044
|
+
break;
|
|
1045
|
+
case 'A03': // Discharge
|
|
1046
|
+
await this.handleDischarge(message);
|
|
1047
|
+
break;
|
|
1048
|
+
case 'A02': // Transfer
|
|
1049
|
+
await this.handleTransfer(message);
|
|
1050
|
+
break;
|
|
1051
|
+
}
|
|
1052
|
+
}
|
|
1053
|
+
|
|
1054
|
+
private async processORM(message: any) {
|
|
1055
|
+
// Process order message
|
|
1056
|
+
const order = {
|
|
1057
|
+
patientId: message.PID[3][0],
|
|
1058
|
+
orderNumber: message.ORC[2][0],
|
|
1059
|
+
orderDate: message.ORC[9][0],
|
|
1060
|
+
orderingProvider: message.ORC[12][0],
|
|
1061
|
+
orderType: message.OBR[4][0],
|
|
1062
|
+
};
|
|
1063
|
+
|
|
1064
|
+
// Store order in database
|
|
1065
|
+
await db.query(
|
|
1066
|
+
`INSERT INTO orders (patient_id, order_number, order_date, provider_id, order_type)
|
|
1067
|
+
VALUES ($1, $2, $3, $4, $5)`,
|
|
1068
|
+
[order.patientId, order.orderNumber, order.orderDate, order.orderingProvider, order.orderType]
|
|
1069
|
+
);
|
|
1070
|
+
}
|
|
1071
|
+
|
|
1072
|
+
private async processORU(message: any) {
|
|
1073
|
+
// Process observation result
|
|
1074
|
+
const observation = {
|
|
1075
|
+
patientId: message.PID[3][0],
|
|
1076
|
+
observationId: message.OBR[3][0],
|
|
1077
|
+
observationDate: message.OBR[7][0],
|
|
1078
|
+
results: [],
|
|
1079
|
+
};
|
|
1080
|
+
|
|
1081
|
+
// Extract results from OBX segments
|
|
1082
|
+
for (const obx of message.OBX || []) {
|
|
1083
|
+
observation.results.push({
|
|
1084
|
+
type: obx[3][0],
|
|
1085
|
+
value: obx[5][0],
|
|
1086
|
+
units: obx[6][0],
|
|
1087
|
+
referenceRange: obx[7][0],
|
|
1088
|
+
abnormalFlag: obx[8][0],
|
|
1089
|
+
});
|
|
1090
|
+
}
|
|
1091
|
+
|
|
1092
|
+
// Convert to FHIR Observation and store
|
|
1093
|
+
const fhirObservation = await this.convertToFHIRObservation(observation);
|
|
1094
|
+
// Store observation
|
|
1095
|
+
}
|
|
1096
|
+
|
|
1097
|
+
private async processSIU(message: any) {
|
|
1098
|
+
// Process scheduling message
|
|
1099
|
+
const appointment = {
|
|
1100
|
+
patientId: message.PID[3][0],
|
|
1101
|
+
appointmentId: message.SCH[1][0],
|
|
1102
|
+
startTime: message.SCH[11][0],
|
|
1103
|
+
duration: message.SCH[9][0],
|
|
1104
|
+
providerId: message.AIP[3][0],
|
|
1105
|
+
location: message.AIL[3][0],
|
|
1106
|
+
};
|
|
1107
|
+
|
|
1108
|
+
// Store appointment
|
|
1109
|
+
await db.query(
|
|
1110
|
+
`INSERT INTO appointments
|
|
1111
|
+
(patient_id, appointment_id, start_time, duration, provider_id, location)
|
|
1112
|
+
VALUES ($1, $2, $3, $4, $5, $6)`,
|
|
1113
|
+
[
|
|
1114
|
+
appointment.patientId,
|
|
1115
|
+
appointment.appointmentId,
|
|
1116
|
+
appointment.startTime,
|
|
1117
|
+
appointment.duration,
|
|
1118
|
+
appointment.providerId,
|
|
1119
|
+
appointment.location,
|
|
1120
|
+
]
|
|
1121
|
+
);
|
|
1122
|
+
}
|
|
1123
|
+
|
|
1124
|
+
private async handleAdmission(message: any) {
|
|
1125
|
+
// Handle patient admission
|
|
1126
|
+
const admission = {
|
|
1127
|
+
patientId: message.PID[3][0],
|
|
1128
|
+
admissionDate: message.PV1[44][0],
|
|
1129
|
+
department: message.PV1[3][0],
|
|
1130
|
+
room: message.PV1[3][2],
|
|
1131
|
+
bed: message.PV1[3][3],
|
|
1132
|
+
attendingPhysician: message.PV1[7][0],
|
|
1133
|
+
};
|
|
1134
|
+
|
|
1135
|
+
await db.query(
|
|
1136
|
+
`INSERT INTO admissions
|
|
1137
|
+
(patient_id, admission_date, department, room, bed, attending_physician)
|
|
1138
|
+
VALUES ($1, $2, $3, $4, $5, $6)`,
|
|
1139
|
+
Object.values(admission)
|
|
1140
|
+
);
|
|
1141
|
+
}
|
|
1142
|
+
|
|
1143
|
+
private async handleDischarge(message: any) {
|
|
1144
|
+
// Handle patient discharge
|
|
1145
|
+
const discharge = {
|
|
1146
|
+
patientId: message.PID[3][0],
|
|
1147
|
+
dischargeDate: message.PV1[45][0],
|
|
1148
|
+
dischargeDisposition: message.PV1[36][0],
|
|
1149
|
+
};
|
|
1150
|
+
|
|
1151
|
+
await db.query(
|
|
1152
|
+
`UPDATE admissions
|
|
1153
|
+
SET discharge_date = $1, discharge_disposition = $2
|
|
1154
|
+
WHERE patient_id = $3 AND discharge_date IS NULL`,
|
|
1155
|
+
[discharge.dischargeDate, discharge.dischargeDisposition, discharge.patientId]
|
|
1156
|
+
);
|
|
1157
|
+
}
|
|
1158
|
+
|
|
1159
|
+
private async handleTransfer(message: any) {
|
|
1160
|
+
// Handle patient transfer
|
|
1161
|
+
const transfer = {
|
|
1162
|
+
patientId: message.PID[3][0],
|
|
1163
|
+
fromDepartment: message.PV1[6][0],
|
|
1164
|
+
toDepartment: message.PV1[3][0],
|
|
1165
|
+
transferDate: message.EVN[6][0],
|
|
1166
|
+
};
|
|
1167
|
+
|
|
1168
|
+
await db.query(
|
|
1169
|
+
`INSERT INTO transfers
|
|
1170
|
+
(patient_id, from_department, to_department, transfer_date)
|
|
1171
|
+
VALUES ($1, $2, $3, $4)`,
|
|
1172
|
+
Object.values(transfer)
|
|
1173
|
+
);
|
|
1174
|
+
}
|
|
1175
|
+
|
|
1176
|
+
private async convertToFHIRObservation(hl7Observation: any): Promise<Observation> {
|
|
1177
|
+
// Convert HL7 observation to FHIR format
|
|
1178
|
+
return {
|
|
1179
|
+
resourceType: ResourceType.Observation,
|
|
1180
|
+
status: 'final',
|
|
1181
|
+
code: {
|
|
1182
|
+
coding: [{
|
|
1183
|
+
system: 'http://loinc.org',
|
|
1184
|
+
code: hl7Observation.type,
|
|
1185
|
+
}],
|
|
1186
|
+
},
|
|
1187
|
+
effectiveDateTime: hl7Observation.observationDate,
|
|
1188
|
+
valueQuantity: {
|
|
1189
|
+
value: parseFloat(hl7Observation.results[0]?.value),
|
|
1190
|
+
unit: hl7Observation.results[0]?.units,
|
|
1191
|
+
},
|
|
1192
|
+
};
|
|
1193
|
+
}
|
|
1194
|
+
|
|
1195
|
+
private async sendACK(originalMessage: any) {
|
|
1196
|
+
// Send HL7 acknowledgment
|
|
1197
|
+
const ack = {
|
|
1198
|
+
MSH: {
|
|
1199
|
+
...originalMessage.MSH,
|
|
1200
|
+
9: ['ACK'],
|
|
1201
|
+
},
|
|
1202
|
+
MSA: {
|
|
1203
|
+
1: 'AA', // Application Accept
|
|
1204
|
+
2: originalMessage.MSH[10], // Message control ID
|
|
1205
|
+
},
|
|
1206
|
+
};
|
|
1207
|
+
|
|
1208
|
+
// Send ACK message
|
|
1209
|
+
}
|
|
1210
|
+
|
|
1211
|
+
private async sendNACK(error: string) {
|
|
1212
|
+
// Send negative acknowledgment
|
|
1213
|
+
const nack = {
|
|
1214
|
+
MSA: {
|
|
1215
|
+
1: 'AE', // Application Error
|
|
1216
|
+
3: error,
|
|
1217
|
+
},
|
|
1218
|
+
};
|
|
1219
|
+
|
|
1220
|
+
// Send NACK message
|
|
1221
|
+
}
|
|
1222
|
+
}
|
|
1223
|
+
|
|
1224
|
+
// DICOM Image Processing
|
|
1225
|
+
class DICOMProcessor {
|
|
1226
|
+
async processImage(buffer: Buffer): Promise<void> {
|
|
1227
|
+
try {
|
|
1228
|
+
// Parse DICOM file
|
|
1229
|
+
const dataSet = dicom.parseDicom(buffer);
|
|
1230
|
+
|
|
1231
|
+
// Extract metadata
|
|
1232
|
+
const metadata = {
|
|
1233
|
+
patientId: dataSet.string('x00100020'),
|
|
1234
|
+
patientName: dataSet.string('x00100010'),
|
|
1235
|
+
studyInstanceUID: dataSet.string('x0020000d'),
|
|
1236
|
+
seriesInstanceUID: dataSet.string('x0020000e'),
|
|
1237
|
+
sopInstanceUID: dataSet.string('x00080018'),
|
|
1238
|
+
modality: dataSet.string('x00080060'),
|
|
1239
|
+
studyDate: dataSet.string('x00080020'),
|
|
1240
|
+
studyDescription: dataSet.string('x00081030'),
|
|
1241
|
+
};
|
|
1242
|
+
|
|
1243
|
+
// Store metadata in database
|
|
1244
|
+
await this.storeDICOMMetadata(metadata);
|
|
1245
|
+
|
|
1246
|
+
// Store image in PACS
|
|
1247
|
+
await this.storeInPACS(buffer, metadata);
|
|
1248
|
+
|
|
1249
|
+
// Apply de-identification if needed
|
|
1250
|
+
if (process.env.DEIDENTIFY_IMAGES === 'true') {
|
|
1251
|
+
await this.deidentifyImage(dataSet);
|
|
1252
|
+
}
|
|
1253
|
+
|
|
1254
|
+
} catch (error) {
|
|
1255
|
+
auditLogger.error('DICOM processing error', {
|
|
1256
|
+
error: error.message,
|
|
1257
|
+
});
|
|
1258
|
+
throw error;
|
|
1259
|
+
}
|
|
1260
|
+
}
|
|
1261
|
+
|
|
1262
|
+
private async storeDICOMMetadata(metadata: any) {
|
|
1263
|
+
await db.query(
|
|
1264
|
+
`INSERT INTO dicom_studies
|
|
1265
|
+
(patient_id, study_uid, series_uid, sop_uid, modality, study_date, description)
|
|
1266
|
+
VALUES ($1, $2, $3, $4, $5, $6, $7)`,
|
|
1267
|
+
[
|
|
1268
|
+
metadata.patientId,
|
|
1269
|
+
metadata.studyInstanceUID,
|
|
1270
|
+
metadata.seriesInstanceUID,
|
|
1271
|
+
metadata.sopInstanceUID,
|
|
1272
|
+
metadata.modality,
|
|
1273
|
+
metadata.studyDate,
|
|
1274
|
+
metadata.studyDescription,
|
|
1275
|
+
]
|
|
1276
|
+
);
|
|
1277
|
+
}
|
|
1278
|
+
|
|
1279
|
+
private async storeInPACS(buffer: Buffer, metadata: any) {
|
|
1280
|
+
// Store image in Picture Archiving and Communication System
|
|
1281
|
+
// This would integrate with a PACS server
|
|
1282
|
+
}
|
|
1283
|
+
|
|
1284
|
+
private async deidentifyImage(dataSet: any) {
|
|
1285
|
+
// Remove patient identifying information
|
|
1286
|
+
const tagsToRemove = [
|
|
1287
|
+
'x00100010', // Patient Name
|
|
1288
|
+
'x00100020', // Patient ID
|
|
1289
|
+
'x00100030', // Patient Birth Date
|
|
1290
|
+
'x00100040', // Patient Sex
|
|
1291
|
+
'x00101010', // Patient Age
|
|
1292
|
+
];
|
|
1293
|
+
|
|
1294
|
+
for (const tag of tagsToRemove) {
|
|
1295
|
+
delete dataSet.elements[tag];
|
|
1296
|
+
}
|
|
1297
|
+
}
|
|
1298
|
+
}
|
|
1299
|
+
|
|
1300
|
+
// Telemedicine Platform
|
|
1301
|
+
class TelemedicineService {
|
|
1302
|
+
async createVideoConsultation(
|
|
1303
|
+
patientId: string,
|
|
1304
|
+
providerId: string,
|
|
1305
|
+
scheduledTime: Date
|
|
1306
|
+
): Promise<VideoConsultation> {
|
|
1307
|
+
// Generate secure room
|
|
1308
|
+
const roomId = uuidv4();
|
|
1309
|
+
const roomToken = this.generateSecureToken();
|
|
1310
|
+
|
|
1311
|
+
// Create consultation record
|
|
1312
|
+
const consultation = {
|
|
1313
|
+
id: uuidv4(),
|
|
1314
|
+
patientId,
|
|
1315
|
+
providerId,
|
|
1316
|
+
roomId,
|
|
1317
|
+
scheduledTime,
|
|
1318
|
+
status: 'scheduled',
|
|
1319
|
+
patientToken: this.generatePatientToken(roomId, patientId),
|
|
1320
|
+
providerToken: this.generateProviderToken(roomId, providerId),
|
|
1321
|
+
};
|
|
1322
|
+
|
|
1323
|
+
// Store consultation
|
|
1324
|
+
await db.query(
|
|
1325
|
+
`INSERT INTO video_consultations
|
|
1326
|
+
(id, patient_id, provider_id, room_id, scheduled_time, status, created_at)
|
|
1327
|
+
VALUES ($1, $2, $3, $4, $5, $6, NOW())`,
|
|
1328
|
+
[
|
|
1329
|
+
consultation.id,
|
|
1330
|
+
consultation.patientId,
|
|
1331
|
+
consultation.providerId,
|
|
1332
|
+
consultation.roomId,
|
|
1333
|
+
consultation.scheduledTime,
|
|
1334
|
+
consultation.status,
|
|
1335
|
+
]
|
|
1336
|
+
);
|
|
1337
|
+
|
|
1338
|
+
// Send notifications
|
|
1339
|
+
await this.sendConsultationNotifications(consultation);
|
|
1340
|
+
|
|
1341
|
+
return consultation;
|
|
1342
|
+
}
|
|
1343
|
+
|
|
1344
|
+
async startConsultation(consultationId: string, userId: string): Promise<void> {
|
|
1345
|
+
// Update consultation status
|
|
1346
|
+
await db.query(
|
|
1347
|
+
'UPDATE video_consultations SET status = $1, started_at = NOW() WHERE id = $2',
|
|
1348
|
+
['in_progress', consultationId]
|
|
1349
|
+
);
|
|
1350
|
+
|
|
1351
|
+
// Start recording if required for documentation
|
|
1352
|
+
if (process.env.RECORD_CONSULTATIONS === 'true') {
|
|
1353
|
+
await this.startRecording(consultationId);
|
|
1354
|
+
}
|
|
1355
|
+
|
|
1356
|
+
// Log for audit
|
|
1357
|
+
auditLogger.info('Video consultation started', {
|
|
1358
|
+
consultationId,
|
|
1359
|
+
userId,
|
|
1360
|
+
timestamp: new Date().toISOString(),
|
|
1361
|
+
});
|
|
1362
|
+
}
|
|
1363
|
+
|
|
1364
|
+
async endConsultation(
|
|
1365
|
+
consultationId: string,
|
|
1366
|
+
userId: string,
|
|
1367
|
+
notes?: string
|
|
1368
|
+
): Promise<void> {
|
|
1369
|
+
// Update consultation status
|
|
1370
|
+
await db.query(
|
|
1371
|
+
`UPDATE video_consultations
|
|
1372
|
+
SET status = $1, ended_at = NOW(), clinical_notes = $2
|
|
1373
|
+
WHERE id = $3`,
|
|
1374
|
+
['completed', notes, consultationId]
|
|
1375
|
+
);
|
|
1376
|
+
|
|
1377
|
+
// Stop recording
|
|
1378
|
+
await this.stopRecording(consultationId);
|
|
1379
|
+
|
|
1380
|
+
// Generate consultation summary
|
|
1381
|
+
await this.generateConsultationSummary(consultationId);
|
|
1382
|
+
|
|
1383
|
+
// Create billing record
|
|
1384
|
+
await this.createBillingRecord(consultationId);
|
|
1385
|
+
}
|
|
1386
|
+
|
|
1387
|
+
private generateSecureToken(): string {
|
|
1388
|
+
return crypto.randomBytes(32).toString('base64url');
|
|
1389
|
+
}
|
|
1390
|
+
|
|
1391
|
+
private generatePatientToken(roomId: string, patientId: string): string {
|
|
1392
|
+
return jwt.sign(
|
|
1393
|
+
{
|
|
1394
|
+
roomId,
|
|
1395
|
+
patientId,
|
|
1396
|
+
role: 'patient',
|
|
1397
|
+
permissions: ['join', 'video', 'audio', 'chat'],
|
|
1398
|
+
},
|
|
1399
|
+
config.security.jwtSecret,
|
|
1400
|
+
{ expiresIn: '2h' }
|
|
1401
|
+
);
|
|
1402
|
+
}
|
|
1403
|
+
|
|
1404
|
+
private generateProviderToken(roomId: string, providerId: string): string {
|
|
1405
|
+
return jwt.sign(
|
|
1406
|
+
{
|
|
1407
|
+
roomId,
|
|
1408
|
+
providerId,
|
|
1409
|
+
role: 'provider',
|
|
1410
|
+
permissions: ['join', 'video', 'audio', 'chat', 'record', 'screenshare'],
|
|
1411
|
+
},
|
|
1412
|
+
config.security.jwtSecret,
|
|
1413
|
+
{ expiresIn: '2h' }
|
|
1414
|
+
);
|
|
1415
|
+
}
|
|
1416
|
+
|
|
1417
|
+
private async sendConsultationNotifications(consultation: any) {
|
|
1418
|
+
// Send email/SMS notifications to patient and provider
|
|
1419
|
+
}
|
|
1420
|
+
|
|
1421
|
+
private async startRecording(consultationId: string) {
|
|
1422
|
+
// Start video recording for documentation
|
|
1423
|
+
// Must comply with consent and privacy regulations
|
|
1424
|
+
}
|
|
1425
|
+
|
|
1426
|
+
private async stopRecording(consultationId: string) {
|
|
1427
|
+
// Stop and securely store recording
|
|
1428
|
+
}
|
|
1429
|
+
|
|
1430
|
+
private async generateConsultationSummary(consultationId: string) {
|
|
1431
|
+
// Generate clinical summary document
|
|
1432
|
+
}
|
|
1433
|
+
|
|
1434
|
+
private async createBillingRecord(consultationId: string) {
|
|
1435
|
+
// Create billing record for telemedicine consultation
|
|
1436
|
+
// Include appropriate CPT codes for telehealth
|
|
1437
|
+
}
|
|
1438
|
+
}
|
|
1439
|
+
|
|
1440
|
+
// Type definitions
|
|
1441
|
+
interface Identifier {
|
|
1442
|
+
system?: string;
|
|
1443
|
+
value: string;
|
|
1444
|
+
}
|
|
1445
|
+
|
|
1446
|
+
interface HumanName {
|
|
1447
|
+
family: string;
|
|
1448
|
+
given: string[];
|
|
1449
|
+
}
|
|
1450
|
+
|
|
1451
|
+
interface ContactPoint {
|
|
1452
|
+
system: 'phone' | 'email';
|
|
1453
|
+
value: string;
|
|
1454
|
+
}
|
|
1455
|
+
|
|
1456
|
+
interface Address {
|
|
1457
|
+
line: string[];
|
|
1458
|
+
city: string;
|
|
1459
|
+
state: string;
|
|
1460
|
+
postalCode: string;
|
|
1461
|
+
country: string;
|
|
1462
|
+
}
|
|
1463
|
+
|
|
1464
|
+
interface CodeableConcept {
|
|
1465
|
+
coding?: Coding[];
|
|
1466
|
+
text?: string;
|
|
1467
|
+
}
|
|
1468
|
+
|
|
1469
|
+
interface Coding {
|
|
1470
|
+
system: string;
|
|
1471
|
+
code: string;
|
|
1472
|
+
display?: string;
|
|
1473
|
+
}
|
|
1474
|
+
|
|
1475
|
+
interface Reference {
|
|
1476
|
+
reference: string;
|
|
1477
|
+
display?: string;
|
|
1478
|
+
}
|
|
1479
|
+
|
|
1480
|
+
interface Period {
|
|
1481
|
+
start: string;
|
|
1482
|
+
end?: string;
|
|
1483
|
+
}
|
|
1484
|
+
|
|
1485
|
+
interface Quantity {
|
|
1486
|
+
value: number;
|
|
1487
|
+
unit: string;
|
|
1488
|
+
system?: string;
|
|
1489
|
+
code?: string;
|
|
1490
|
+
}
|
|
1491
|
+
|
|
1492
|
+
interface Range {
|
|
1493
|
+
low: Quantity;
|
|
1494
|
+
high: Quantity;
|
|
1495
|
+
}
|
|
1496
|
+
|
|
1497
|
+
interface Annotation {
|
|
1498
|
+
text: string;
|
|
1499
|
+
time?: string;
|
|
1500
|
+
}
|
|
1501
|
+
|
|
1502
|
+
interface ObservationReferenceRange {
|
|
1503
|
+
low?: Quantity;
|
|
1504
|
+
high?: Quantity;
|
|
1505
|
+
text?: string;
|
|
1506
|
+
}
|
|
1507
|
+
|
|
1508
|
+
interface Attachment {
|
|
1509
|
+
contentType: string;
|
|
1510
|
+
data?: string;
|
|
1511
|
+
url?: string;
|
|
1512
|
+
}
|
|
1513
|
+
|
|
1514
|
+
interface PatientContact {
|
|
1515
|
+
relationship?: CodeableConcept[];
|
|
1516
|
+
name?: HumanName;
|
|
1517
|
+
telecom?: ContactPoint[];
|
|
1518
|
+
}
|
|
1519
|
+
|
|
1520
|
+
interface PatientCommunication {
|
|
1521
|
+
language: CodeableConcept;
|
|
1522
|
+
preferred?: boolean;
|
|
1523
|
+
}
|
|
1524
|
+
|
|
1525
|
+
interface Bundle {
|
|
1526
|
+
resourceType: 'Bundle';
|
|
1527
|
+
type: 'searchset' | 'document' | 'message' | 'transaction' | 'batch';
|
|
1528
|
+
total?: number;
|
|
1529
|
+
entry?: BundleEntry[];
|
|
1530
|
+
}
|
|
1531
|
+
|
|
1532
|
+
interface BundleEntry {
|
|
1533
|
+
fullUrl?: string;
|
|
1534
|
+
resource: FHIRResource;
|
|
1535
|
+
}
|
|
1536
|
+
|
|
1537
|
+
interface Medication extends FHIRResource {
|
|
1538
|
+
resourceType: ResourceType.Medication;
|
|
1539
|
+
code: CodeableConcept;
|
|
1540
|
+
}
|
|
1541
|
+
|
|
1542
|
+
interface Alert {
|
|
1543
|
+
severity: 'low' | 'medium' | 'high';
|
|
1544
|
+
type: string;
|
|
1545
|
+
message: string;
|
|
1546
|
+
[key: string]: any;
|
|
1547
|
+
}
|
|
1548
|
+
|
|
1549
|
+
interface VideoConsultation {
|
|
1550
|
+
id: string;
|
|
1551
|
+
patientId: string;
|
|
1552
|
+
providerId: string;
|
|
1553
|
+
roomId: string;
|
|
1554
|
+
scheduledTime: Date;
|
|
1555
|
+
status: string;
|
|
1556
|
+
patientToken: string;
|
|
1557
|
+
providerToken: string;
|
|
1558
|
+
}
|
|
1559
|
+
|
|
1560
|
+
// Export services
|
|
1561
|
+
export {
|
|
1562
|
+
HIPAASecurityService,
|
|
1563
|
+
HIPAAAuditService,
|
|
1564
|
+
FHIRRepository,
|
|
1565
|
+
ClinicalDecisionSupport,
|
|
1566
|
+
HL7Processor,
|
|
1567
|
+
DICOMProcessor,
|
|
1568
|
+
TelemedicineService,
|
|
1569
|
+
};
|
|
1570
|
+
```
|
|
1571
|
+
|
|
1572
|
+
## Best Practices
|
|
1573
|
+
|
|
1574
|
+
### 1. Regulatory Compliance
|
|
1575
|
+
- Implement comprehensive HIPAA Security Rule controls
|
|
1576
|
+
- Maintain detailed audit logs for all PHI access
|
|
1577
|
+
- Use encryption for data at rest and in transit
|
|
1578
|
+
- Implement role-based access control (RBAC)
|
|
1579
|
+
- Regular security risk assessments
|
|
1580
|
+
|
|
1581
|
+
### 2. Interoperability
|
|
1582
|
+
- Follow HL7 FHIR standards for data exchange
|
|
1583
|
+
- Implement standard terminologies (SNOMED, LOINC, ICD)
|
|
1584
|
+
- Support multiple exchange protocols (HL7 v2, FHIR, CDA)
|
|
1585
|
+
- Validate all incoming and outgoing messages
|
|
1586
|
+
- Maintain mapping tables for code systems
|
|
1587
|
+
|
|
1588
|
+
### 3. Data Security
|
|
1589
|
+
- Implement defense in depth strategy
|
|
1590
|
+
- Use secure key management systems
|
|
1591
|
+
- Regular security audits and penetration testing
|
|
1592
|
+
- Implement data loss prevention (DLP) measures
|
|
1593
|
+
- Maintain business associate agreements (BAAs)
|
|
1594
|
+
|
|
1595
|
+
### 4. Clinical Safety
|
|
1596
|
+
- Implement clinical decision support carefully
|
|
1597
|
+
- Validate all medical calculations
|
|
1598
|
+
- Maintain drug interaction databases
|
|
1599
|
+
- Implement allergy checking
|
|
1600
|
+
- Provide clear audit trails for clinical decisions
|
|
1601
|
+
|
|
1602
|
+
### 5. Performance & Reliability
|
|
1603
|
+
- Design for high availability (99.99% uptime)
|
|
1604
|
+
- Implement disaster recovery procedures
|
|
1605
|
+
- Use caching for frequently accessed data
|
|
1606
|
+
- Optimize database queries for large datasets
|
|
1607
|
+
- Implement proper backup and recovery
|
|
1608
|
+
|
|
1609
|
+
## Common Patterns
|
|
1610
|
+
|
|
1611
|
+
1. **Audit Trail**: Comprehensive logging of all PHI access
|
|
1612
|
+
2. **Break Glass**: Emergency access procedures with extra auditing
|
|
1613
|
+
3. **Consent Management**: Patient consent tracking and enforcement
|
|
1614
|
+
4. **Master Patient Index**: Patient identity management and matching
|
|
1615
|
+
5. **Clinical Repository**: Centralized storage for clinical data
|
|
1616
|
+
6. **Terminology Services**: Code system mapping and validation
|
|
1617
|
+
7. **Order Entry**: Computerized physician order entry (CPOE)
|
|
1618
|
+
8. **Results Routing**: Laboratory and imaging result distribution
|
|
1619
|
+
|
|
1620
|
+
Remember: Healthcare technology requires extreme attention to patient safety, data privacy, and regulatory compliance. Always consult with clinical, legal, and compliance teams when implementing healthcare systems.
|