Creado apartir del commit b888de6c92056de294456f581a56e25e734d4ab7 de develop

This commit is contained in:
jrojas
2026-06-26 09:52:35 +02:00
commit 1ffaa733a6
746 changed files with 106610 additions and 0 deletions
+25
View File
@@ -0,0 +1,25 @@
**/.classpath
**/.dockerignore
**/.env
**/.git
**/.gitignore
**/.project
**/.settings
**/.toolstarget
**/.vs
**/.vscode
**/*.*proj.user
**/*.dbmdl
**/*.jfm
**/azds.yaml
**/bin
**/charts
**/docker-compose*
**/Dockerfile*
**/node_modules
**/npm-debug.log
**/obj
**/secrets.dev.yaml
**/values.dev.yaml
LICENSE
README.md
+381
View File
@@ -0,0 +1,381 @@
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
##
## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
# User-specific files
*.rsuser
*.suo
*.user
*.userosscache
*.sln.docstates
# User-specific files (MonoDevelop/Xamarin Studio)
*.userprefs
# Mono auto generated files
mono_crash.*
# Build results
[Dd]ebug/
[Dd]ebugPublic/
[Rr]elease/
[Rr]eleases/
x64/
x86/
[Ww][Ii][Nn]32/
[Aa][Rr][Mm]/
[Aa][Rr][Mm]64/
bld/
[Bb]in/
[Oo]bj/
[Oo]ut/
[Ll]og/
[Ll]ogs/
# Visual Studio 2015/2017 cache/options directory
.vs/
# Uncomment if you have tasks that create the project's static files in wwwroot
#wwwroot/
# Visual Studio 2017 auto generated files
Generated\ Files/
# MSTest test Results
[Tt]est[Rr]esult*/
[Bb]uild[Ll]og.*
# NUnit
*.VisualState.xml
TestResult.xml
nunit-*.xml
# Build Results of an ATL Project
[Dd]ebugPS/
[Rr]eleasePS/
dlldata.c
bin/
obj/
Debug/
Release/
*.tlog
packages
*.log
TestResult.xml
*.VisualState.xml
Version.cs
Version.h
Version.cpp
.vs
/adas-core/Logs
# Benchmark Results
BenchmarkDotNet.Artifacts/
# .NET Core
project.lock.json
project.fragment.lock.json
artifacts/
# ASP.NET Scaffolding
ScaffoldingReadMe.txt
# StyleCop
StyleCopReport.xml
# Files built by Visual Studio
*_i.c
*_p.c
*_h.h
*.ilk
*.meta
*.obj
*.iobj
*.pch
*.pdb
*.ipdb
*.pgc
*.pgd
*.rsp
*.sbr
*.tlb
*.tli
*.tlh
*.tmp
*.tmp_proj
*_wpftmp.csproj
*.log
*.vspscc
*.vssscc
.builds
*.pidb
*.svclog
*.scc
# Chutzpah Test files
_Chutzpah*
# Visual C++ cache files
ipch/
*.aps
*.ncb
*.opendb
*.opensdf
*.sdf
*.cachefile
*.VC.db
*.VC.VC.opendb
# Visual Studio profiler
*.psess
*.vsp
*.vspx
*.sap
# Visual Studio Trace Files
*.e2e
# TFS 2012 Local Workspace
$tf/
# Guidance Automation Toolkit
*.gpState
# ReSharper is a .NET coding add-in
_ReSharper*/
*.[Rr]e[Ss]harper
*.DotSettings.user
# TeamCity is a build add-in
_TeamCity*
# DotCover is a Code Coverage Tool
*.dotCover
# AxoCover is a Code Coverage Tool
.axoCover/*
!.axoCover/settings.json
# Coverlet is a free, cross platform Code Coverage Tool
coverage*.json
coverage*.xml
coverage*.info
# Visual Studio code coverage results
*.coverage
*.coveragexml
# NCrunch
_NCrunch_*
.*crunch*.local.xml
nCrunchTemp_*
# MightyMoose
*.mm.*
AutoTest.Net/
# Web workbench (sass)
.sass-cache/
# Installshield output folder
[Ee]xpress/
# DocProject is a documentation generator add-in
DocProject/buildhelp/
DocProject/Help/*.HxT
DocProject/Help/*.HxC
DocProject/Help/*.hhc
DocProject/Help/*.hhk
DocProject/Help/*.hhp
DocProject/Help/Html2
DocProject/Help/html
# Click-Once directory
publish/
# Publish Web Output
*.[Pp]ublish.xml
*.azurePubxml
# Note: Comment the next line if you want to checkin your web deploy settings,
# but database connection strings (with potential passwords) will be unencrypted
*.pubxml
*.publishproj
# Microsoft Azure Web App publish settings. Comment the next line if you want to
# checkin your Azure Web App publish settings, but sensitive information contained
# in these scripts will be unencrypted
PublishScripts/
# NuGet Packages
*.nupkg
# NuGet Symbol Packages
*.snupkg
# The packages folder can be ignored because of Package Restore
**/[Pp]ackages/*
# except build/, which is used as an MSBuild target.
!**/[Pp]ackages/build/
# Uncomment if necessary however generally it will be regenerated when needed
#!**/[Pp]ackages/repositories.config
# NuGet v3's project.json files produces more ignorable files
*.nuget.props
*.nuget.targets
# Microsoft Azure Build Output
csx/
*.build.csdef
# Microsoft Azure Emulator
ecf/
rcf/
# Windows Store app package directories and files
AppPackages/
BundleArtifacts/
Package.StoreAssociation.xml
_pkginfo.txt
*.appx
*.appxbundle
*.appxupload
# Visual Studio cache files
# files ending in .cache can be ignored
*.[Cc]ache
# but keep track of directories ending in .cache
!?*.[Cc]ache/
# Others
ClientBin/
~$*
*~
*.dbmdl
*.dbproj.schemaview
*.jfm
*.pfx
*.publishsettings
orleans.codegen.cs
# Including strong name files can present a security risk
# (https://github.com/github/gitignore/pull/2483#issue-259490424)
#*.snk
# Since there are multiple workflows, uncomment next line to ignore bower_components
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
#bower_components/
# RIA/Silverlight projects
Generated_Code/
# Backup & report files from converting an old project file
# to a newer Visual Studio version. Backup files are not needed,
# because we have git ;-)
_UpgradeReport_Files/
Backup*/
UpgradeLog*.XML
UpgradeLog*.htm
ServiceFabricBackup/
*.rptproj.bak
# SQL Server files
*.mdf
*.ldf
*.ndf
# Business Intelligence projects
*.rdl.data
*.bim.layout
*.bim_*.settings
*.rptproj.rsuser
*- [Bb]ackup.rdl
*- [Bb]ackup ([0-9]).rdl
*- [Bb]ackup ([0-9][0-9]).rdl
# Microsoft Fakes
FakesAssemblies/
# GhostDoc plugin setting file
*.GhostDoc.xml
# Node.js Tools for Visual Studio
.ntvs_analysis.dat
node_modules/
# Visual Studio 6 build log
*.plg
# Visual Studio 6 workspace options file
*.opt
# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
*.vbw
# Visual Studio LightSwitch build output
**/*.HTMLClient/GeneratedArtifacts
**/*.DesktopClient/GeneratedArtifacts
**/*.DesktopClient/ModelManifest.xml
**/*.Server/GeneratedArtifacts
**/*.Server/ModelManifest.xml
_Pvt_Extensions
# Paket dependency manager
.paket/paket.exe
paket-files/
# FAKE - F# Make
.fake/
# CodeRush personal settings
.cr/personal
# Python Tools for Visual Studio (PTVS)
__pycache__/
*.pyc
# Cake - Uncomment if you are using it
# tools/**
# !tools/packages.config
# Tabs Studio
*.tss
# Telerik's JustMock configuration file
*.jmconfig
# BizTalk build output
*.btp.cs
*.btm.cs
*.odx.cs
*.xsd.cs
# OpenCover UI analysis results
OpenCover/
# Azure Stream Analytics local run output
ASALocalRun/
# MSBuild Binary and Structured Log
*.binlog
# NVidia Nsight GPU debugger configuration file
*.nvuser
# MFractors (Xamarin productivity tool) working folder
.mfractor/
# Local History for Visual Studio
.localhistory/
# BeatPulse healthcheck temp database
healthchecksdb
# Backup folder for Package Reference Convert tool in Visual Studio 2017
MigrationBackup/
# Ionide (cross platform F# VS Code tools) working folder
.ionide/
# Fody - auto-generated XML schema
FodyWeavers.xsd
.idea
+70
View File
@@ -0,0 +1,70 @@
steps:
test:
image: mcr.microsoft.com/dotnet/sdk:8.0
when:
event:
- push
- commit
commands:
- (dotnet nuget add source $${ADAS_NUGET_URL} -n "ADAS Nugget Server") | 2>/dev/null
- dotnet build --configuration Debug /p:Platform=x64 /nowarn:1701,1702,IL2121,CS1591,IL2121,1701,1702,CS1587,CA1416
"adas-core/adas-core.csproj"
- dotnet test --configuration Debug /p:Platform=x64 /nowarn:1701,1702,IL2121,CS1591,IL2121,1701,1702,CS1587,CA1416
"./adas-core.Test/adas-core.Test.csproj"
environment:
ADAS_NUGET_URL:
from_secret: ADAS_NUGET_URL
upload:
image: docker:dind
volumes:
- /var/run/docker.sock:/var/run/docker.sock
when:
event:
- push
- commit
branch:
- main
commands:
- docker login -u $${CI_REGISTRY_USER} -p $${CI_REGISTRY_PASSWORD} $${CI_REGISTRY}
- docker build -t $${CI_REGISTRY}/adas/adas-core/adas-core:latest .
- docker push $${CI_REGISTRY}/adas/adas-core/adas-core:latest
environment:
CI_REGISTRY_USER:
from_secret: CI_REGISTRY_USER
CI_REGISTRY_PASSWORD:
from_secret: CI_REGISTRY_PASSWORD
CI_REGISTRY:
from_secret: CI_REGISTRY
ADAS_HYDE_SSH_KEY:
from_secret: ADAS_HYDE_SSH_KEY
SSH_SERVER_HOSTKEYS:
from_secret: SSH_SERVER_HOSTKEYS
restart-docker-service:
image: alpine
when:
branch:
- main
commands:
- command -v ssh-agent >/dev/null || ( apk add --update openssh )
- eval $(ssh-agent -s)
- echo "$${ADAS_HYDE_SSH_KEY}" | tr -d '\r' | ssh-add -
- mkdir -p ~/.ssh
- chmod 700 ~/.ssh
- echo "$${SSH_SERVER_HOSTKEYS}" >> ~/.ssh/known_hosts
- chmod 644 ~/.ssh/known_hosts
- ssh -o "StrictHostKeyChecking no" gitlabci@10.0.20.67 -p 22 "cd /opt/smartuci;docker-compose
stop adas-core;docker-compose rm -f adas-core;docker-compose pull adas-core;docker-compose
up -d adas-core" || ssh -o "StrictHostKeyChecking no" gitlabci@red1.epigram.es
-p 1522 "cd /opt/adascore;docker-compose stop adas-core;docker-compose rm
-f adas-core;docker-compose pull adas-core;docker-compose up -d adas-core"
environment:
CI_REGISTRY_USER:
from_secret: CI_REGISTRY_USER
CI_REGISTRY_PASSWORD:
from_secret: CI_REGISTRY_PASSWORD
CI_REGISTRY:
from_secret: CI_REGISTRY
ADAS_HYDE_SSH_KEY:
from_secret: ADAS_HYDE_SSH_KEY
SSH_SERVER_HOSTKEYS:
from_secret: SSH_SERVER_HOSTKEYS
+57
View File
@@ -0,0 +1,57 @@
# --------------------------------------------------------------------------------------
# 1. ETAPA DE CONSTRUCCIÓN (BUILD STAGE)
# Utiliza la imagen SDK para compilar la aplicación.
# --------------------------------------------------------------------------------------
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
WORKDIR /src
COPY NuGet.Config .
# Copia los archivos de proyecto (.csproj) de cada subdirectorio y restáuralos.
# Esto aprovecha el caché de Docker si solo cambian los paquetes, no el código.
COPY ["adas-core/adas-core.csproj", "adas-core/"]
COPY ["adas-core.Application/adas-core.Application.csproj", "adas-core.Application/"]
COPY ["adas-core.Authentication/adas-core.Authentication.csproj", "adas-core.Authentication/"]
COPY ["adas-core.Domain/adas-core.Domain.csproj", "adas-core.Domain/"]
COPY ["adas-core.Infrastructure/adas-core.Infrastructure.csproj", "adas-core.Infrastructure/"]
COPY ["adas-core.LdapLogin/adas-core.LdapLogin.csproj", "adas-core.LdapLogin/"]
COPY ["adas-core.LocalLogin/adas-core.LocalLogin.csproj", "adas-core.LocalLogin/"]
COPY ["adas-core.module.LightBeacons/adas-core.module.LightBeacons.csproj", "adas-core.module.LightBeacons/"]
COPY ["adas-core.module.ProxyDevices/adas-core.module.ProxyDevices.csproj", "adas-core.module.ProxyDevices/"]
COPY ["adas-core.module.Relays/adas-core.module.Relays.csproj", "adas-core.module.Relays/"]
COPY ["adas-core.Test/adas-core.Test.csproj", "adas-core.Test/"]
# ... (Añade COPY para el resto de proyectos .csproj: LdapLogin, LocalLogin, modules, Test si son dependencias)
# Copia el archivo de solución (.sln) si existe, si no, usa el siguiente comando con los .csproj
# En este caso, asumiremos que se restaura y compila por proyecto principal.
# Si tienes un archivo de solución, es mejor copiarlo aquí.
# Restaura las dependencias (NuGet)
RUN dotnet restore "adas-core/adas-core.csproj"
# Copia el resto de los archivos fuente (todo el código)
COPY . .
# Cambia al directorio del proyecto principal
WORKDIR /src/adas-core
# Publica la aplicación final en modo Release
# El argumento -o /app/publish indica dónde se guardará el resultado de la publicación.
RUN dotnet publish "adas-core.csproj" -c Release -o /app/publish /p:UseAppHost=false
# --------------------------------------------------------------------------------------
# 2. ETAPA FINAL (RUNTIME STAGE)
# Utiliza la imagen de ejecución, que es más pequeña y no contiene herramientas de compilación.
# --------------------------------------------------------------------------------------
FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS final
WORKDIR /app
# Copia los archivos publicados desde la etapa 'build'
COPY --from=build /app/publish .
# Define el punto de entrada de la aplicación
# Este es el nombre del ensamblado DLL principal
ENTRYPOINT ["dotnet", "adas-core.dll"]
# El puerto de escucha por defecto de ASP.NET Core en Docker es el 8080 (o 80 en versiones anteriores)
ENV ASPNETCORE_URLS=http://+:80
EXPOSE 80
@@ -0,0 +1,343 @@
steps:
collect_inputs:
image: alpine:3.20
# Se ejecuta en todas las ramas: features, env/adas/dev, env/adas/pre
commands:
- set -eu
- apk add --no-cache jq coreutils git findutils
- mkdir -p ci-evidence/test-results
- SHORT_SHA="$(printf "%s" "${DRONE_COMMIT_SHA}" | cut -c1-12)"
- BUILD_TIME="$(date -u +"%Y-%m-%dT%H:%M:%SZ")"
- ENV_NAME="$(printf "%s" "${DRONE_BRANCH}" | sed 's|env/adas/||')"
- RELEASE_ID="${RELEASE_ID:-REL-${ENV_NAME}-${DRONE_BUILD_NUMBER}-${SHORT_SHA}}"
- echo "$BUILD_TIME" > ci-evidence/build-time.txt
- echo "${DRONE_COMMIT_SHA}" > ci-evidence/commit-sha.txt
- echo "${SHORT_SHA}" > ci-evidence/commit-short.txt
- echo "${DRONE_BRANCH}" > ci-evidence/branch.txt
- echo "${ENV_NAME}" > ci-evidence/env-name.txt
- echo "${DRONE_BUILD_NUMBER}" > ci-evidence/pipeline-id.txt
- echo "${RELEASE_ID}" > ci-evidence/release-id.txt
- echo "Teralevel CI" > ci-evidence/ci-tool.txt
- git rev-parse --short HEAD > ci-evidence/git-short-head.txt
- git rev-parse HEAD > ci-evidence/git-full-head.txt
- git status --short > ci-evidence/git-status.txt || true
- |
find . \
-maxdepth 4 \
-type f \
\( -name 'Dockerfile' -o -name '.teralevel-ci.yml' -o -name '*.sln' -o -name '*.csproj' -o -name 'nuget.config' \) \
! -path '*/bin/*' \
! -path '*/obj/*' \
-exec sha256sum {} \; > ci-evidence/input-checksums.txt || true
- |
jq -n \
--arg release_id "$RELEASE_ID" \
--arg ur_prd_version "${UR_PRD_VERSION:-PENDIENTE}" \
--arg srs_version "${SRS_VERSION:-PENDIENTE}" \
--arg rmf_version "${RMF_VERSION:-PENDIENTE}" \
--arg trace_version "${TRACE_MATRIX_VERSION:-PENDIENTE}" \
--arg sbom_ref "ci-evidence/sbom-cyclonedx.json" \
'{
release_id: $release_id,
ur_prd_version: $ur_prd_version,
srs_version: $srs_version,
rmf_version: $rmf_version,
traceability_matrix_version: $trace_version,
sbom_reference: $sbom_ref
}' > ci-evidence/release-baseline-ref.json
test_backend:
image: mcr.microsoft.com/dotnet/sdk:8.0
# Se ejecuta en todas las ramas: features, env/adas/dev, env/adas/pre
environment:
ADAS_NUGET_URL:
from_secret: ADAS_NUGET_URL
commands:
- set -eu
- mkdir -p ci-evidence/test-results/dotnet
- dotnet --info > ci-evidence/dotnet-info.txt
- (dotnet nuget add source "${ADAS_NUGET_URL}" -n "ADAS Nugget Server") 2>/dev/null || true
- dotnet restore "adas-core/adas-core.csproj"
- dotnet restore "./adas-core.Test/adas-core.Test.csproj"
- set +e
- dotnet build --configuration Debug /p:Platform=x64 /nowarn:1701,1702,IL2121,CS1591,CS1587,CA1416 "adas-core/adas-core.csproj"
- BUILD_EXIT=$?
- dotnet test --configuration Debug /p:Platform=x64 /nowarn:1701,1702,IL2121,CS1591,CS1587,CA1416 "./adas-core.Test/adas-core.Test.csproj" --results-directory ci-evidence/test-results/dotnet --logger "trx;LogFileName=adas-core-tests.trx"
- TEST_EXIT=$?
- set -e
- |
cat > ci-evidence/test-results/dotnet-summary.json <<EOF
{
"status": "executed",
"build_exit_code": ${BUILD_EXIT},
"test_exit_code": ${TEST_EXIT},
"build_target": "adas-core/adas-core.csproj",
"test_target": "./adas-core.Test/adas-core.Test.csproj"
}
EOF
- test "$BUILD_EXIT" -eq 0
- test "$TEST_EXIT" -eq 0
build_and_push:
# Requiere que el runner CI tenga /var/run/docker.sock montado (daemon Docker en CI)
image: docker:27-cli
when:
branch:
- env/adas/dev
- env/adas/pre
environment:
CI_REGISTRY_USER:
from_secret: CI_REGISTRY_USER
CI_REGISTRY_PASSWORD:
from_secret: CI_REGISTRY_PASSWORD
CI_REGISTRY:
from_secret: CI_REGISTRY
commands:
- set -eu
- mkdir -p ci-evidence
- docker login "${CI_REGISTRY}" -u "${CI_REGISTRY_USER}" -p "${CI_REGISTRY_PASSWORD}"
- SHORT_SHA="$(cat ci-evidence/commit-short.txt)"
- PIPELINE_ID="$(cat ci-evidence/pipeline-id.txt)"
- ENV_NAME="$(cat ci-evidence/env-name.txt)"
- IMAGE_REPO="${CI_REGISTRY}/adas/adas-core/adas-core"
- IMAGE_TAG_IMMUTABLE="${ENV_NAME}-${PIPELINE_ID}-${SHORT_SHA}"
- IMAGE_REF_IMMUTABLE="${IMAGE_REPO}:${IMAGE_TAG_IMMUTABLE}"
- IMAGE_REF_MUTABLE="${IMAGE_REPO}:${ENV_NAME}-latest"
- echo "$IMAGE_REF_IMMUTABLE" > ci-evidence/image-ref-immutable.txt
- echo "$IMAGE_REF_MUTABLE" > ci-evidence/image-ref-mutable.txt
- docker version > ci-evidence/docker-version.txt
- |
docker build \
--label org.opencontainers.image.revision="${DRONE_COMMIT_SHA}" \
--label org.opencontainers.image.version="${IMAGE_TAG_IMMUTABLE}" \
--label org.opencontainers.image.created="$(cat ci-evidence/build-time.txt)" \
-t "${IMAGE_REF_IMMUTABLE}" \
-t "${IMAGE_REF_MUTABLE}" \
-f Dockerfile \
. > ci-evidence/docker-build.log 2>&1 || { cat ci-evidence/docker-build.log; exit 1; }
- docker push "${IMAGE_REF_IMMUTABLE}" 2>&1 | tee -a ci-evidence/docker-build.log
- docker push "${IMAGE_REF_MUTABLE}" 2>&1 | tee -a ci-evidence/docker-build.log
- |
docker image inspect "${IMAGE_REF_IMMUTABLE}" \
--format '{{index .RepoDigests 0}}' > ci-evidence/image-digest.txt 2>/dev/null \
|| docker image inspect "${IMAGE_REF_IMMUTABLE}" \
--format '{{.Id}}' > ci-evidence/image-digest.txt
- cat ci-evidence/docker-build.log
generate_sbom:
image: anchore/syft:v1.18.1
when:
branch:
- env/adas/dev
- env/adas/pre
environment:
CI_REGISTRY_USER:
from_secret: CI_REGISTRY_USER
CI_REGISTRY_PASSWORD:
from_secret: CI_REGISTRY_PASSWORD
CI_REGISTRY:
from_secret: CI_REGISTRY
commands:
- set -eu
- mkdir -p /root/.docker ci-evidence
- |
cat > /root/.docker/config.json <<EOF
{
"auths": {
"${CI_REGISTRY}": {
"username": "${CI_REGISTRY_USER}",
"password": "${CI_REGISTRY_PASSWORD}"
}
}
}
EOF
- IMAGE_REF_IMMUTABLE="$(cat ci-evidence/image-ref-immutable.txt)"
- syft version > ci-evidence/syft-version.txt
- syft "${IMAGE_REF_IMMUTABLE}" -o cyclonedx-json=ci-evidence/sbom-cyclonedx.json
- syft "${IMAGE_REF_IMMUTABLE}" -o spdx-json=ci-evidence/sbom-spdx.json
assemble_ci_evidence_predeploy:
image: alpine:3.20
when:
branch:
- env/adas/dev
- env/adas/pre
commands:
- set -eu
- apk add --no-cache jq coreutils
- BUILD_TIME="$(cat ci-evidence/build-time.txt)"
- COMMIT_SHA="$(cat ci-evidence/commit-sha.txt)"
- SHORT_SHA="$(cat ci-evidence/commit-short.txt)"
- BRANCH="$(cat ci-evidence/branch.txt)"
- ENV_NAME="$(cat ci-evidence/env-name.txt)"
- PIPELINE_ID="$(cat ci-evidence/pipeline-id.txt)"
- RELEASE_ID="$(cat ci-evidence/release-id.txt)"
- IMAGE_REF_IMMUTABLE="$(cat ci-evidence/image-ref-immutable.txt)"
- IMAGE_REF_MUTABLE="$(cat ci-evidence/image-ref-mutable.txt)"
- IMAGE_DIGEST="$(cat ci-evidence/image-digest.txt)"
- DOTNET_SUMMARY="$(cat ci-evidence/test-results/dotnet-summary.json)"
- |
jq -n \
--arg project "ADAS" \
--arg component "adas-core" \
--arg environment "$ENV_NAME" \
--arg release_id "$RELEASE_ID" \
--arg build_time_utc "$BUILD_TIME" \
--arg commit_sha "$COMMIT_SHA" \
--arg commit_short "$SHORT_SHA" \
--arg branch "$BRANCH" \
--arg pipeline_id "$PIPELINE_ID" \
--arg ci_tool "Teralevel CI" \
--arg image_ref_immutable "$IMAGE_REF_IMMUTABLE" \
--arg image_ref_mutable "$IMAGE_REF_MUTABLE" \
--arg image_digest "$IMAGE_DIGEST" \
--argjson backend_tests "$DOTNET_SUMMARY" \
'{
project: $project,
component: $component,
environment: $environment,
release_id: $release_id,
build_time_utc: $build_time_utc,
commit_sha: $commit_sha,
commit_short: $commit_short,
branch: $branch,
pipeline_id: $pipeline_id,
ci_tool: $ci_tool,
image_ref_immutable: $image_ref_immutable,
image_ref_mutable: $image_ref_mutable,
image_digest: $image_digest,
tests: {
backend: $backend_tests
}
}' > ci-evidence/build-metadata.json
- sha256sum ci-evidence/* > ci-evidence/checksums-predeploy.txt || true
deploy_adas_dev:
image: alpine:3.20
when:
branch:
- env/adas/dev
environment:
ADAS_SSH_KEY:
from_secret: ADAS_SSH_KEY
SSH_SERVER_HOSTKEYS:
from_secret: SSH_SERVER_HOSTKEYS
DEPLOY_HOST_DEV:
from_secret: DEPLOY_HOST_DEV
DEPLOY_USER:
from_secret: DEPLOY_USER
commands:
- set -eu
- apk add --no-cache openssh-client jq
- mkdir -p ~/.ssh
- echo "$ADAS_SSH_KEY" > ~/.ssh/id_ed25519
- chmod 600 ~/.ssh/id_ed25519
- echo "$SSH_SERVER_HOSTKEYS" >> ~/.ssh/known_hosts
- chmod 644 ~/.ssh/known_hosts
- IMAGE_REF_MUTABLE="$(cat ci-evidence/image-ref-mutable.txt)"
- |
ssh -o "StrictHostKeyChecking yes" "${DEPLOY_USER}@${DEPLOY_HOST_DEV}" \
"cd /opt/adas-core-dev && \
docker compose stop adas-core && \
docker compose rm -f adas-core && \
docker compose pull adas-core && \
docker compose up -d adas-core"
- |
REMOTE_DIGEST="$(ssh -o "StrictHostKeyChecking yes" "${DEPLOY_USER}@${DEPLOY_HOST_DEV}" \
"docker image inspect ${IMAGE_REF_MUTABLE} --format='{{index .RepoDigests 0}}'")"
- |
jq -n \
--arg environment "dev" \
--arg host "${DEPLOY_HOST_DEV}" \
--arg deployed_ref "$IMAGE_REF_MUTABLE" \
--arg deployed_digest "$REMOTE_DIGEST" \
'{
environment: $environment,
host: $host,
deployed_ref: $deployed_ref,
deployed_digest: $deployed_digest
}' > ci-evidence/deploy-info.json
deploy_adas_pre:
image: alpine:3.20
when:
branch:
- env/adas/pre
environment:
ADAS_SSH_KEY:
from_secret: ADAS_SSH_KEY
SSH_SERVER_HOSTKEYS:
from_secret: SSH_SERVER_HOSTKEYS
DEPLOY_HOST_PRE:
from_secret: DEPLOY_HOST_PRE
DEPLOY_USER:
from_secret: DEPLOY_USER
commands:
- set -eu
- apk add --no-cache openssh-client jq
- mkdir -p ~/.ssh
- echo "$ADAS_SSH_KEY" > ~/.ssh/id_ed25519
- chmod 600 ~/.ssh/id_ed25519
- echo "$SSH_SERVER_HOSTKEYS" >> ~/.ssh/known_hosts
- chmod 644 ~/.ssh/known_hosts
- IMAGE_REF_MUTABLE="$(cat ci-evidence/image-ref-mutable.txt)"
- |
ssh -o "StrictHostKeyChecking yes" "${DEPLOY_USER}@${DEPLOY_HOST_PRE}" \
"cd /opt/adas-core-pre && \
docker compose stop adas-core && \
docker compose rm -f adas-core && \
docker compose pull adas-core && \
docker compose up -d adas-core"
- |
REMOTE_DIGEST="$(ssh -o "StrictHostKeyChecking yes" "${DEPLOY_USER}@${DEPLOY_HOST_PRE}" \
"docker image inspect ${IMAGE_REF_MUTABLE} --format='{{index .RepoDigests 0}}'")"
- |
jq -n \
--arg environment "pre" \
--arg host "${DEPLOY_HOST_PRE}" \
--arg deployed_ref "$IMAGE_REF_MUTABLE" \
--arg deployed_digest "$REMOTE_DIGEST" \
'{
environment: $environment,
host: $host,
deployed_ref: $deployed_ref,
deployed_digest: $deployed_digest
}' > ci-evidence/deploy-info.json
finalize_and_publish_ci_evidence:
image: alpine:3.20
when:
branch:
- env/adas/dev
- env/adas/pre
environment:
ADAS_SSH_KEY:
from_secret: ADAS_SSH_KEY
SSH_SERVER_HOSTKEYS:
from_secret: SSH_SERVER_HOSTKEYS
DEPLOY_HOST_DEV:
from_secret: DEPLOY_HOST_DEV
DEPLOY_HOST_PRE:
from_secret: DEPLOY_HOST_PRE
DEPLOY_USER:
from_secret: DEPLOY_USER
commands:
- set -eu
- apk add --no-cache openssh-client jq coreutils tar gzip
- mkdir -p ~/.ssh
- echo "$ADAS_SSH_KEY" > ~/.ssh/id_ed25519
- chmod 600 ~/.ssh/id_ed25519
- echo "$SSH_SERVER_HOSTKEYS" >> ~/.ssh/known_hosts
- chmod 644 ~/.ssh/known_hosts
- RELEASE_ID="$(cat ci-evidence/release-id.txt)"
- ENV_NAME="$(cat ci-evidence/env-name.txt)"
- sha256sum ci-evidence/* > ci-evidence/checksums-final.txt || true
- tar -czf "ci-evidence-${RELEASE_ID}.tar.gz" ci-evidence
- |
if [ "${ENV_NAME}" = "dev" ]; then
EVIDENCE_HOST="${DEPLOY_HOST_DEV}"
else
EVIDENCE_HOST="${DEPLOY_HOST_PRE}"
fi
- ssh -o "StrictHostKeyChecking yes" "${DEPLOY_USER}@${EVIDENCE_HOST}" "mkdir -p /opt/adas-ci-evidence/core/${ENV_NAME}"
- scp -o "StrictHostKeyChecking yes" "ci-evidence-${RELEASE_ID}.tar.gz" "${DEPLOY_USER}@${EVIDENCE_HOST}:/opt/adas-ci-evidence/core/${ENV_NAME}/"
@@ -0,0 +1,413 @@
steps:
collect_inputs:
image: alpine:3.20
# Se ejecuta en todas las ramas: features, env/adas/dev, env/adas/pre
commands:
- set -eu
- apk add --no-cache jq coreutils git findutils
- mkdir -p ci-evidence/test-results
- SHORT_SHA="$(printf "%s" "${DRONE_COMMIT_SHA}" | cut -c1-12)"
- BUILD_TIME="$(date -u +"%Y-%m-%dT%H:%M:%SZ")"
- ENV_NAME="$(printf "%s" "${DRONE_BRANCH}" | sed 's|env/adas/||')"
- RELEASE_ID="${RELEASE_ID:-REL-${ENV_NAME}-${DRONE_BUILD_NUMBER}-${SHORT_SHA}}"
- echo "$BUILD_TIME" > ci-evidence/build-time.txt
- echo "${DRONE_COMMIT_SHA}" > ci-evidence/commit-sha.txt
- echo "${SHORT_SHA}" > ci-evidence/commit-short.txt
- echo "${DRONE_BRANCH}" > ci-evidence/branch.txt
- echo "${ENV_NAME}" > ci-evidence/env-name.txt
- echo "${DRONE_BUILD_NUMBER}" > ci-evidence/pipeline-id.txt
- echo "${RELEASE_ID}" > ci-evidence/release-id.txt
- echo "Teralevel CI" > ci-evidence/ci-tool.txt
- git rev-parse --short HEAD > ci-evidence/git-short-head.txt
- git rev-parse HEAD > ci-evidence/git-full-head.txt
- git status --short > ci-evidence/git-status.txt || true
- |
find . \
-maxdepth 4 \
-type f \
\( -name 'Dockerfile' -o -name '.teralevel-ci.yml' -o -name 'package-lock.json' -o -name 'angular.json' -o -name '*.sln' -o -name '*.csproj' -o -name 'nuget.config' \) \
! -path '*/node_modules/*' \
! -path '*/bin/*' \
! -path '*/obj/*' \
-exec sha256sum {} \; > ci-evidence/input-checksums.txt || true
- |
jq -n \
--arg release_id "$RELEASE_ID" \
--arg ur_prd_version "${UR_PRD_VERSION:-PENDIENTE}" \
--arg srs_version "${SRS_VERSION:-PENDIENTE}" \
--arg rmf_version "${RMF_VERSION:-PENDIENTE}" \
--arg trace_version "${TRACE_MATRIX_VERSION:-PENDIENTE}" \
--arg sbom_ref "ci-evidence/sbom-cyclonedx.json" \
'{
release_id: $release_id,
ur_prd_version: $ur_prd_version,
srs_version: $srs_version,
rmf_version: $rmf_version,
traceability_matrix_version: $trace_version,
sbom_reference: $sbom_ref
}' > ci-evidence/release-baseline-ref.json
test_backend:
image: mcr.microsoft.com/dotnet/sdk:8.0
# Se ejecuta en todas las ramas: features, env/adas/dev, env/adas/pre
commands:
- set -eu
- mkdir -p ci-evidence/test-results/dotnet
- dotnet --info > ci-evidence/dotnet-info.txt
- SOLUTION="$(find . -maxdepth 4 -type f -name '*.sln' ! -path '*/obj/*' ! -path '*/bin/*' | head -n 1 || true)"
- |
if [ -z "$SOLUTION" ]; then
TEST_PROJECTS="$(find . -maxdepth 5 -type f \( -name '*Test*.csproj' -o -name '*Tests*.csproj' \) ! -path '*/obj/*' ! -path '*/bin/*' | tr '\n' ' ' || true)"
else
TEST_PROJECTS=""
fi
- |
if [ -n "$SOLUTION" ]; then
dotnet restore "$SOLUTION"
set +e
dotnet test "$SOLUTION" \
--configuration Release \
--results-directory ci-evidence/test-results/dotnet \
--logger "trx;LogFileName=dotnet-tests.trx"
TEST_EXIT=$?
set -e
elif [ -n "$TEST_PROJECTS" ]; then
TEST_EXIT=0
for proj in $TEST_PROJECTS; do
dotnet restore "$proj"
set +e
dotnet test "$proj" \
--configuration Release \
--results-directory ci-evidence/test-results/dotnet \
--logger "trx;LogFileName=$(basename "$proj" .csproj)-tests.trx"
CUR_EXIT=$?
set -e
if [ "$CUR_EXIT" -ne 0 ]; then TEST_EXIT=$CUR_EXIT; fi
done
else
TEST_EXIT=0
fi
- |
if [ -n "$SOLUTION" ] || [ -n "${TEST_PROJECTS:-}" ]; then
STATUS="executed"
else
STATUS="skipped"
fi
- |
cat > ci-evidence/test-results/dotnet-summary.json <<EOF
{
"status": "${STATUS}",
"exit_code": ${TEST_EXIT},
"target": "$(if [ -n "$SOLUTION" ]; then printf "%s" "$SOLUTION"; else printf "%s" "${TEST_PROJECTS:-none}"; fi)"
}
EOF
- test "$TEST_EXIT" -eq 0
test_frontend:
# NOTA: Chromium se instala en cada ejecucion (~1-3 min adicionales).
# Cuando este disponible, sustituir por una imagen base propia:
# git.teralevel.io/adas/node-chrome:20 (node:20-bookworm-slim + chromium pre-instalado)
# Eso eliminara el apt-get de este paso y reducira el tiempo total del pipeline.
image: node:20-bookworm-slim
# Se ejecuta en todas las ramas: features, env/adas/dev, env/adas/pre
commands:
- set -eu
- apt-get update
- apt-get install -y --no-install-recommends chromium jq ca-certificates
- rm -rf /var/lib/apt/lists/*
- mkdir -p ci-evidence/test-results/frontend
- node --version > ci-evidence/node-version.txt
- npm --version > ci-evidence/npm-version.txt
- FRONTEND_DIR="$(find . -maxdepth 4 -type f -name 'angular.json' ! -path '*/node_modules/*' -exec dirname {} \; | head -n 1 || true)"
- |
if [ -z "$FRONTEND_DIR" ]; then
FRONTEND_DIR="$(find . -maxdepth 4 -type f -name 'package.json' ! -path '*/node_modules/*' -exec dirname {} \; | head -n 1 || true)"
fi
- |
if [ -n "$FRONTEND_DIR" ]; then
cd "$FRONTEND_DIR"
export CI=true
export CHROME_BIN=/usr/bin/chromium
npm ci
set +e
npm test -- --watch=false --browsers=ChromeHeadless --progress=false
TEST_EXIT=$?
set -e
cd -
STATUS="executed"
else
TEST_EXIT=0
STATUS="skipped"
fi
- |
cat > ci-evidence/test-results/frontend-summary.json <<EOF
{
"status": "${STATUS}",
"exit_code": ${TEST_EXIT},
"target": "${FRONTEND_DIR:-none}"
}
EOF
- test "$TEST_EXIT" -eq 0
build_and_push:
# Requiere que el runner CI tenga /var/run/docker.sock montado (daemon Docker en CI)
image: docker:27-cli
when:
branch:
- env/adas/dev
- env/adas/pre
environment:
REGISTRY_USER:
from_secret: REGISTRY_USER
REGISTRY_PASS:
from_secret: REGISTRY_PASS
commands:
- set -eu
- mkdir -p ci-evidence
- docker login "git.teralevel.io" -u "${REGISTRY_USER}" -p "${REGISTRY_PASS}"
- SHORT_SHA="$(cat ci-evidence/commit-short.txt)"
- PIPELINE_ID="$(cat ci-evidence/pipeline-id.txt)"
- ENV_NAME="$(cat ci-evidence/env-name.txt)"
- IMAGE_REPO="git.teralevel.io/adas/adas-webdisplay"
- IMAGE_TAG_IMMUTABLE="${ENV_NAME}-${PIPELINE_ID}-${SHORT_SHA}"
- IMAGE_REF_IMMUTABLE="${IMAGE_REPO}:${IMAGE_TAG_IMMUTABLE}"
- IMAGE_REF_MUTABLE="${IMAGE_REPO}:${ENV_NAME}-latest"
- echo "$IMAGE_REF_IMMUTABLE" > ci-evidence/image-ref-immutable.txt
- echo "$IMAGE_REF_MUTABLE" > ci-evidence/image-ref-mutable.txt
- docker version > ci-evidence/docker-version.txt
- |
docker build \
--label org.opencontainers.image.revision="${DRONE_COMMIT_SHA}" \
--label org.opencontainers.image.version="${IMAGE_TAG_IMMUTABLE}" \
--label org.opencontainers.image.created="$(cat ci-evidence/build-time.txt)" \
-t "${IMAGE_REF_IMMUTABLE}" \
-t "${IMAGE_REF_MUTABLE}" \
-f Dockerfile \
. > ci-evidence/docker-build.log 2>&1 || { cat ci-evidence/docker-build.log; exit 1; }
- docker push "${IMAGE_REF_IMMUTABLE}" 2>&1 | tee -a ci-evidence/docker-build.log
- docker push "${IMAGE_REF_MUTABLE}" 2>&1 | tee -a ci-evidence/docker-build.log
- |
docker image inspect "${IMAGE_REF_IMMUTABLE}" \
--format '{{index .RepoDigests 0}}' > ci-evidence/image-digest.txt 2>/dev/null \
|| docker image inspect "${IMAGE_REF_IMMUTABLE}" \
--format '{{.Id}}' > ci-evidence/image-digest.txt
- cat ci-evidence/docker-build.log
generate_sbom:
image: anchore/syft:v1.18.1
when:
branch:
- env/adas/dev
- env/adas/pre
environment:
REGISTRY_USER:
from_secret: REGISTRY_USER
REGISTRY_PASS:
from_secret: REGISTRY_PASS
commands:
- set -eu
- mkdir -p /root/.docker ci-evidence
- |
cat > /root/.docker/config.json <<EOF
{
"auths": {
"git.teralevel.io": {
"username": "$REGISTRY_USER",
"password": "$REGISTRY_PASS"
}
}
}
EOF
- IMAGE_REF_IMMUTABLE="$(cat ci-evidence/image-ref-immutable.txt)"
- syft version > ci-evidence/syft-version.txt
- syft "${IMAGE_REF_IMMUTABLE}" -o cyclonedx-json=ci-evidence/sbom-cyclonedx.json
- syft "${IMAGE_REF_IMMUTABLE}" -o spdx-json=ci-evidence/sbom-spdx.json
assemble_ci_evidence_predeploy:
image: alpine:3.20
when:
branch:
- env/adas/dev
- env/adas/pre
commands:
- set -eu
- apk add --no-cache jq coreutils
- BUILD_TIME="$(cat ci-evidence/build-time.txt)"
- COMMIT_SHA="$(cat ci-evidence/commit-sha.txt)"
- SHORT_SHA="$(cat ci-evidence/commit-short.txt)"
- BRANCH="$(cat ci-evidence/branch.txt)"
- ENV_NAME="$(cat ci-evidence/env-name.txt)"
- PIPELINE_ID="$(cat ci-evidence/pipeline-id.txt)"
- RELEASE_ID="$(cat ci-evidence/release-id.txt)"
- IMAGE_REF_IMMUTABLE="$(cat ci-evidence/image-ref-immutable.txt)"
- IMAGE_REF_MUTABLE="$(cat ci-evidence/image-ref-mutable.txt)"
- IMAGE_DIGEST="$(cat ci-evidence/image-digest.txt)"
- DOTNET_SUMMARY="$(cat ci-evidence/test-results/dotnet-summary.json)"
- FRONTEND_SUMMARY="$(cat ci-evidence/test-results/frontend-summary.json)"
- |
jq -n \
--arg project "ADAS" \
--arg component "adas-webdisplay" \
--arg environment "$ENV_NAME" \
--arg release_id "$RELEASE_ID" \
--arg build_time_utc "$BUILD_TIME" \
--arg commit_sha "$COMMIT_SHA" \
--arg commit_short "$SHORT_SHA" \
--arg branch "$BRANCH" \
--arg pipeline_id "$PIPELINE_ID" \
--arg ci_tool "Teralevel CI" \
--arg image_ref_immutable "$IMAGE_REF_IMMUTABLE" \
--arg image_ref_mutable "$IMAGE_REF_MUTABLE" \
--arg image_digest "$IMAGE_DIGEST" \
--argjson dotnet_tests "$DOTNET_SUMMARY" \
--argjson frontend_tests "$FRONTEND_SUMMARY" \
'{
project: $project,
component: $component,
environment: $environment,
release_id: $release_id,
build_time_utc: $build_time_utc,
commit_sha: $commit_sha,
commit_short: $commit_short,
branch: $branch,
pipeline_id: $pipeline_id,
ci_tool: $ci_tool,
image_ref_immutable: $image_ref_immutable,
image_ref_mutable: $image_ref_mutable,
image_digest: $image_digest,
tests: {
backend: $dotnet_tests,
frontend: $frontend_tests
}
}' > ci-evidence/build-metadata.json
- sha256sum ci-evidence/* > ci-evidence/checksums-predeploy.txt || true
deploy_webdisplay_dev:
image: alpine:3.20
when:
branch:
- env/adas/dev
environment:
ADAS_SSH_KEY:
from_secret: ADAS_SSH_KEY
SSH_SERVER_HOSTKEYS:
from_secret: SSH_SERVER_HOSTKEYS
DEPLOY_HOST_DEV:
from_secret: DEPLOY_HOST_DEV
DEPLOY_USER:
from_secret: DEPLOY_USER
commands:
- set -eu
- apk add --no-cache openssh-client jq
- mkdir -p ~/.ssh
- echo "$ADAS_SSH_KEY" > ~/.ssh/id_ed25519
- chmod 600 ~/.ssh/id_ed25519
- echo "$SSH_SERVER_HOSTKEYS" >> ~/.ssh/known_hosts
- chmod 644 ~/.ssh/known_hosts
- IMAGE_REF_MUTABLE="$(cat ci-evidence/image-ref-mutable.txt)"
- |
ssh -o "StrictHostKeyChecking yes" "${DEPLOY_USER}@${DEPLOY_HOST_DEV}" \
"cd /opt/adas-icu-dev && \
docker compose stop webdisplay-dev && \
docker compose rm -f webdisplay-dev && \
docker compose pull webdisplay-dev && \
docker compose up -d webdisplay-dev"
- |
REMOTE_DIGEST="$(ssh -o "StrictHostKeyChecking yes" "${DEPLOY_USER}@${DEPLOY_HOST_DEV}" \
"docker image inspect ${IMAGE_REF_MUTABLE} --format='{{index .RepoDigests 0}}'")"
- |
jq -n \
--arg environment "dev" \
--arg host "${DEPLOY_HOST_DEV}" \
--arg deployed_ref "$IMAGE_REF_MUTABLE" \
--arg deployed_digest "$REMOTE_DIGEST" \
'{
environment: $environment,
host: $host,
deployed_ref: $deployed_ref,
deployed_digest: $deployed_digest
}' > ci-evidence/deploy-info.json
deploy_webdisplay_pre:
image: alpine:3.20
when:
branch:
- env/adas/pre
environment:
ADAS_SSH_KEY:
from_secret: ADAS_SSH_KEY
SSH_SERVER_HOSTKEYS:
from_secret: SSH_SERVER_HOSTKEYS
DEPLOY_HOST_PRE:
from_secret: DEPLOY_HOST_PRE
DEPLOY_USER:
from_secret: DEPLOY_USER
commands:
- set -eu
- apk add --no-cache openssh-client jq
- mkdir -p ~/.ssh
- echo "$ADAS_SSH_KEY" > ~/.ssh/id_ed25519
- chmod 600 ~/.ssh/id_ed25519
- echo "$SSH_SERVER_HOSTKEYS" >> ~/.ssh/known_hosts
- chmod 644 ~/.ssh/known_hosts
- IMAGE_REF_MUTABLE="$(cat ci-evidence/image-ref-mutable.txt)"
- |
ssh -o "StrictHostKeyChecking yes" "${DEPLOY_USER}@${DEPLOY_HOST_PRE}" \
"cd /opt/adas-icu-pre && \
docker compose stop webdisplay-pre && \
docker compose rm -f webdisplay-pre && \
docker compose pull webdisplay-pre && \
docker compose up -d webdisplay-pre"
- |
REMOTE_DIGEST="$(ssh -o "StrictHostKeyChecking yes" "${DEPLOY_USER}@${DEPLOY_HOST_PRE}" \
"docker image inspect ${IMAGE_REF_MUTABLE} --format='{{index .RepoDigests 0}}'")"
- |
jq -n \
--arg environment "pre" \
--arg host "${DEPLOY_HOST_PRE}" \
--arg deployed_ref "$IMAGE_REF_MUTABLE" \
--arg deployed_digest "$REMOTE_DIGEST" \
'{
environment: $environment,
host: $host,
deployed_ref: $deployed_ref,
deployed_digest: $deployed_digest
}' > ci-evidence/deploy-info.json
finalize_and_publish_ci_evidence:
image: alpine:3.20
when:
branch:
- env/adas/dev
- env/adas/pre
environment:
ADAS_SSH_KEY:
from_secret: ADAS_SSH_KEY
SSH_SERVER_HOSTKEYS:
from_secret: SSH_SERVER_HOSTKEYS
DEPLOY_HOST_DEV:
from_secret: DEPLOY_HOST_DEV
DEPLOY_HOST_PRE:
from_secret: DEPLOY_HOST_PRE
DEPLOY_USER:
from_secret: DEPLOY_USER
commands:
- set -eu
- apk add --no-cache openssh-client jq coreutils tar gzip
- mkdir -p ~/.ssh
- echo "$ADAS_SSH_KEY" > ~/.ssh/id_ed25519
- chmod 600 ~/.ssh/id_ed25519
- echo "$SSH_SERVER_HOSTKEYS" >> ~/.ssh/known_hosts
- chmod 644 ~/.ssh/known_hosts
- RELEASE_ID="$(cat ci-evidence/release-id.txt)"
- ENV_NAME="$(cat ci-evidence/env-name.txt)"
- sha256sum ci-evidence/* > ci-evidence/checksums-final.txt || true
- tar -czf "ci-evidence-${RELEASE_ID}.tar.gz" ci-evidence
- |
if [ "${ENV_NAME}" = "dev" ]; then
EVIDENCE_HOST="${DEPLOY_HOST_DEV}"
else
EVIDENCE_HOST="${DEPLOY_HOST_PRE}"
fi
- ssh -o "StrictHostKeyChecking yes" "${DEPLOY_USER}@${EVIDENCE_HOST}" "mkdir -p /opt/adas-ci-evidence/webdisplay/${ENV_NAME}"
- scp -o "StrictHostKeyChecking yes" "ci-evidence-${RELEASE_ID}.tar.gz" "${DEPLOY_USER}@${EVIDENCE_HOST}:/opt/adas-ci-evidence/webdisplay/${ENV_NAME}/"
Binary file not shown.
+7
View File
@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<packageSources>
<add key="smacs" value="https://smacs-nuget.epigramdev.com/v3/index.json" allowInsecureConnections="True" />
<add key="nugget" value="https://api.nuget.org/v3/index.json" />
</packageSources>
</configuration>
+1
View File
@@ -0,0 +1 @@
ADAS CORE API
@@ -0,0 +1,104 @@
using adas_core.Application.Services.Interfaces;
using adas_core.Domain.Enums;
using adas_core.Domain.Models;
using adas_core.Domain.Models.Pumps;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using MongoDB.Bson;
namespace adas_core.Application.Customizations.BD;
public class CalculatedObservations(IServiceProvider serviceProvider) : ICalculatedObservations
{
private readonly ILogger<CalculatedObservations> _logger =
serviceProvider.GetRequiredService<ILogger<CalculatedObservations>>();
private readonly Lazy<IObservationService> _observationService =
serviceProvider.GetRequiredService<Lazy<IObservationService>>();
public Task<T?> Map<T>(T obs, bool onlyByName = false) where T : BasePatientObservation
{
return Task.FromResult(obs)!;
}
public Task<PatientTreatment> Map(PatientTreatment treatment)
{
return Task.FromResult(treatment);
}
public Task<PatientDiagnosis> Map(PatientDiagnosis diagnosis)
{
return Task.FromResult(diagnosis);
}
public async Task<PumpObservation> Map(PumpObservation pumpObservation)
{
if (pumpObservation.Code == "IHE PCD-04")
await CreatePumpAlarmObservation(pumpObservation);
return pumpObservation;
}
public Task CalculateMedicineObservation(List<Medicine> activeMedicines, ObjectId patientId)
{
return Task.CompletedTask;
}
public Task CalculateActiveBolus(ObjectId patientId)
{
return Task.CompletedTask;
}
public Task<IEnumerable<PatientTreatment?>> GetActiveTreatmentsByPatient(ObjectId id)
{
throw new NotImplementedException();
}
public Task<PatientObservation?> FixTimeInconsistencyWithLast(PatientObservation newObservation)
{
throw new NotImplementedException();
}
public Task<List<PatientObservation>> PreMapList(List<PatientObservation> listToInsert)
{
return Task.FromResult(listToInsert);
}
public Task<PatientObservation> MapSourceAlarm(PatientObservation obs, PatientObservationAlarm alarmToInsert)
{
throw new NotImplementedException();
}
public Task SendAlarm(PatientObservation obs, string name, AlarmEnum.Name? code)
{
throw new NotImplementedException();
}
private Task CreatePumpAlarmObservation(PumpObservation pumpObservation)
{
PatientObservation patientObservationAlarm = new()
{
CodingSystem = "ADAS_ALARM",
Code = pumpObservation.AlarmState,
Name = $"Alarm_Pump_{pumpObservation.AlarmType}",
Value = pumpObservation.DeviceId != null
? $"Device Id: {pumpObservation.DeviceId}"
: $"Infusion Id {pumpObservation.InfusionId}",
Time = pumpObservation.Time
};
if (pumpObservation.PatientId.HasValue) patientObservationAlarm.PatientId = pumpObservation.PatientId.Value;
//CheckAlarmConfig(patientObservationAlarm);
try
{
_ = _observationService.Value.InsertObservation(patientObservationAlarm, mapObs: false);
}
catch (Exception e)
{
_logger.LogError("Error inserting Pump Observation Alarm. Exception: {e}", e);
}
return Task.CompletedTask;
}
}
@@ -0,0 +1,80 @@
using adas_core.Application.Services.Interfaces;
using adas_core.Domain.Enums;
using adas_core.Domain.Models;
using adas_core.Domain.Models.Pumps;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using MongoDB.Bson;
namespace adas_core.Application.Customizations.CHUO;
public class CalculatedObservations(IServiceProvider serviceProvider) : ICalculatedObservations
{
private readonly ILogger<CalculatedObservations> _logger =
serviceProvider.GetRequiredService<ILogger<CalculatedObservations>>();
public Task CalculateActiveBolus(ObjectId patientId)
{
return Task.CompletedTask;
}
public async Task<PumpObservation> Map(PumpObservation pumpObservation)
{
return await Task.FromResult(pumpObservation);
}
public Task CalculateMedicineObservation(List<Medicine> activeMedicines, ObjectId patientId)
{
return Task.CompletedTask;
}
public Task<PatientObservation?> FixTimeInconsistencyWithLast(PatientObservation newObservation)
{
throw new NotImplementedException();
}
public Task<IEnumerable<PatientTreatment?>> GetActiveTreatmentsByPatient(ObjectId id)
{
throw new NotImplementedException();
}
public Task<T?> Map<T>(T obs, bool onlyByName = false) where T : BasePatientObservation
{
_logger.LogTrace("Mapping {name} observation {obs}", obs.Name, obs);
return Task.FromResult<T?>(obs);
}
public Task<PatientTreatment> Map(PatientTreatment treatment)
{
throw new NotImplementedException();
}
public Task<PatientDiagnosis> Map(PatientDiagnosis diagnosis)
{
throw new NotImplementedException();
}
public Task<List<PatientObservation>> PreMapList(List<PatientObservation> listToInsert)
{
return Task.FromResult(listToInsert);
}
public Task<PatientObservation> MapSourceAlarm(PatientObservation obs, PatientObservationAlarm alarmToInsert)
{
return Task.FromResult(obs);
}
public Task SendAlarm(PatientObservation obs, string name, AlarmEnum.Name? code)
{
return Task.CompletedTask;
}
//public Task<List<PatientObservation>> MapList(List<PatientObservation> obsToInsert)
//{
// throw new NotImplementedException();
//}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,133 @@
using adas_core.Application.Services.Interfaces;
using adas_core.Domain.Enums;
using adas_core.Domain.Models;
using adas_core.Domain.Models.AppSettings;
using adas_core.Domain.Models.Pumps;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using MongoDB.Bson;
namespace adas_core.Application.Customizations.HGM;
//Custom for Hospital Gregorio Marañón
public class CalculatedObservations : ICalculatedObservations
{
private readonly List<string> _highFrequencyVentilation = [];
private readonly List<string> _invasiveVentilation = [];
private readonly ILogger<CalculatedObservations> _logger;
private readonly List<string> _nonInvasiveVentilation = [];
private readonly Lazy<IObservationService> _observationService;
public CalculatedObservations(IServiceProvider serviceProvider)
{
var apiSettings = serviceProvider.GetRequiredService<IOptions<ApiSettings>>().Value;
_logger = serviceProvider.GetRequiredService<ILogger<CalculatedObservations>>();
_observationService = serviceProvider.GetRequiredService<Lazy<IObservationService>>();
var highFrequencyVentilation = apiSettings.HighFrequencyVentilation ?? null;
highFrequencyVentilation?.ForEach(x => _highFrequencyVentilation.Add(x.Trim()));
var invasiveVentilation = apiSettings.InvasiveVentilation ?? null;
invasiveVentilation?.ForEach(x => _invasiveVentilation.Add(x.Trim()));
var nonInvasiveVentilation = apiSettings.NonInvasiveVentilation ?? null;
nonInvasiveVentilation?.ForEach(x => _nonInvasiveVentilation.Add(x.Trim()));
}
public async Task<T?> Map<T>(T obs, bool onlyByName = false) where T : BasePatientObservation
{
if (string.IsNullOrEmpty(obs.Name)) return obs;
switch (obs.Name)
{
case "Resp_Mode":
await CalculateVentilationMode(obs);
break;
}
return obs;
}
public Task<PatientTreatment> Map(PatientTreatment treatment)
{
throw new NotImplementedException();
}
public Task<PatientDiagnosis> Map(PatientDiagnosis diagnosis)
{
throw new NotImplementedException();
}
public async Task<PumpObservation> Map(PumpObservation pumpObservation)
{
return await Task.FromResult(pumpObservation);
}
public Task CalculateActiveBolus(ObjectId patientId)
{
throw new NotImplementedException();
}
public Task CalculateMedicineObservation(List<Medicine> activeMedicines, ObjectId patientId)
{
throw new NotImplementedException();
}
public Task<PatientObservation?> FixTimeInconsistencyWithLast(PatientObservation newObservation)
{
throw new NotImplementedException();
}
public Task<IEnumerable<PatientTreatment?>> GetActiveTreatmentsByPatient(ObjectId id)
{
throw new NotImplementedException();
}
public Task<List<PatientObservation>> PreMapList(List<PatientObservation> listToInsert)
{
return Task.FromResult(listToInsert);
}
public Task<PatientObservation> MapSourceAlarm(PatientObservation obs, PatientObservationAlarm alarmToInsert)
{
return Task.FromResult(obs);
}
public Task SendAlarm(PatientObservation obs, string name, AlarmEnum.Name? code)
{
throw new NotImplementedException();
}
private async Task CalculateVentilationMode(BasePatientObservation obs)
{
var pobs = (PatientObservation)obs;
var respTypeObs = new PatientObservation
{
Name = "Resp_Type",
CodingSystem = "ADAS",
PatientId = obs.PatientId,
Time = obs.Time
};
var strValue = pobs.Value.ToString();
if (string.IsNullOrEmpty(strValue))
respTypeObs.Value = nameof(RespirationType.None);
else
respTypeObs.Value = _highFrequencyVentilation.Contains(strValue)
? nameof(RespirationType.HighFrequencyVentilation)
: _invasiveVentilation.Contains(strValue)
? respTypeObs.Value = nameof(RespirationType.Invasive)
: _nonInvasiveVentilation.Contains(strValue)
? respTypeObs.Value = nameof(RespirationType.NonInvasive)
: nameof(RespirationType.None);
var wasInserted = await _observationService.Value.InsertIfChanged("Resp_Type", respTypeObs);
if (wasInserted)
_logger.LogDebug("Calculated Resp_Type for patientid: {respTypeObsPatientid} value: {respType}",
respTypeObs.PatientId, respTypeObs.Value);
}
}
@@ -0,0 +1,641 @@
using adas_core.Application.Services.Interfaces;
using adas_core.Domain.Enums;
using adas_core.Domain.Models;
using adas_core.Domain.Models.AppSettings;
using adas_core.Domain.Models.Pumps;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using MongoDB.Bson;
using Serilog;
namespace adas_core.Application.Customizations.HPAZ;
public class CalculatedObservations(IServiceProvider serviceProvider) : ICalculatedObservations
{
private readonly Lazy<IAlarmService> _alarmService = serviceProvider.GetRequiredService<Lazy<IAlarmService>>();
private readonly ApiSettings
_apiSettings = serviceProvider.GetRequiredService<IOptions<ApiSettings>>().Value; //apiSettings;
private readonly IConfigObservationService _configObservationService =
serviceProvider.GetRequiredService<IConfigObservationService>(); //configObservationService;
private readonly ILogger<CalculatedObservations> _logger =
serviceProvider.GetRequiredService<ILogger<CalculatedObservations>>();
//Use GroupedObservation to avoid circular dependency with _observationService
private readonly Lazy<IObservationService> _observationService =
serviceProvider.GetRequiredService<Lazy<IObservationService>>(); //observationService;
private readonly List<string> _pressBloodArteryMean = ["TAm"];
public Task CalculateActiveBolus(ObjectId patientId)
{
return Task.CompletedTask;
}
public Task CalculateMedicineObservation(List<Medicine> activeMedicines, ObjectId patientId)
{
return Task.CompletedTask;
}
public Task<IEnumerable<PatientTreatment?>> GetActiveTreatmentsByPatient(ObjectId id)
{
return Task.FromResult<IEnumerable<PatientTreatment?>>(new List<PatientTreatment?>());
}
/// <summary>
/// Maps the obs to an event or alarm depending on its type and inserts it if needed
/// </summary>
/// <typeparam name="T">Type</typeparam>
/// <param name="source">Observation</param>
/// <param name="onlyByName">True if the observation needs to generate an alert</param>
/// <returns>Mapped observation</returns>
public async Task<T?> Map<T>(T source, bool onlyByName) where T : BasePatientObservation
{
_logger.LogDebug("Mapping {obsName} mode observation {obs}", source.Name, source);
if (source is not BasePatientObservationValue obs) return source;
var name = obs is PatientObservationAlarm obsAlarm ? obsAlarm.Event : obs.Name;
name ??= string.Empty;
//No generamos alertas si viene onlyByName
if (name == "Resp_Mode")
obs = CalculateVentilationMode(obs);
if (name is "Sattc" or "FiO2") await CalculateSf(obs, name);
if (name is "FiO2" or "P_VAM" or "PaO2_Tidal") await CalculateOxygenationIndex(obs, name);
if (name is "FiO2" or "PaO2_Tidal") await CalculatePf(obs, name);
if (name == "TEST_ALARMA") await _alarmService.Value.CalculateAlarmTest(obs, name);
obs = await CheckObsWithSameTimeExistsAndIncrementTime(obs);
if (obs is PatientObservation pobs)
{
pobs = await CheckAlarmConfig(pobs);
return pobs as T;
}
return obs as T;
}
public async Task<PumpObservation> Map(PumpObservation pumpObservation)
{
try
{
if (pumpObservation.Status is PumpEnum.Status.Alarm or PumpEnum.Status.Warning)
{
PatientObservation? patientObservation = null;
string? name = null;
//La oclusión es para todos los tipos
var volumetricAirOcclusionAlarm = _apiSettings.VolumetricAirOcclusionAlarm ?? null;
if (volumetricAirOcclusionAlarm != null && pumpObservation.AlarmType != null &&
volumetricAirOcclusionAlarm.Contains(pumpObservation.AlarmType.ToString() ?? string.Empty))
{
name = "VolumetricAirOcclusion";
//alerta oclusión aire para bombas volumétricas
patientObservation = await CreatePumpAlarmObservation(name, pumpObservation);
}
var listInotropicMedicines = _apiSettings.InotropicMedicines ?? null;
if (listInotropicMedicines is { Count: > 0 } && pumpObservation.DrugName != null &&
listInotropicMedicines.Contains(pumpObservation.DrugName)) //INOTRÓPICOS
{
var inotropicEndInfusionAlarm = _apiSettings.InotropicEndInfusionAlarm ?? null;
if (inotropicEndInfusionAlarm != null && pumpObservation.AlarmType != null &&
inotropicEndInfusionAlarm.Contains(pumpObservation.AlarmType.ToString() ?? string.Empty))
{
//Inotrópicos próxima a fin de infusión
// Y
//proximo fin de infusión, oclusión, y presencia aire….
//Solo inotrópicos
//Está en la línea 19 del excel y abarca la que hay en la línea 9
// TODO: revisar en los logs que nombre de type (Buscar por crud xmlns)
name = "InotropicEndInfusion";
patientObservation = await CreatePumpAlarmObservation(name, pumpObservation);
}
}
else
{
var pumPressureAlarm = _apiSettings.PumPressureAlarm ?? null;
if (pumPressureAlarm != null && pumpObservation.AlarmType != null &&
pumPressureAlarm.Contains(pumpObservation.AlarmType.ToString() ?? string.Empty))
{
name = "Pressure";
//alerta de presión(la alerta de la bomba se programa + -30mmHg por encima de la presión de la línea).
patientObservation = await CreatePumpAlarmObservation(name, pumpObservation);
}
var endContinuousInfusionAlarm = _apiSettings.EndContinuousInfusionAlarm ?? null;
if (endContinuousInfusionAlarm != null && pumpObservation.AlarmType != null &&
endContinuousInfusionAlarm.Contains(pumpObservation.AlarmType.ToString() ?? string.Empty))
{
name = "EndContinuousInfusion";
//próximo a fin de infusión /fin de infusión
//información de bombas solo perfusión continua
patientObservation = await CreatePumpAlarmObservation(name, pumpObservation);
}
var volumetricAirInLineAlarm = _apiSettings.VolumetricAirInLineAlarm ?? null;
if (volumetricAirInLineAlarm != null && pumpObservation.AlarmType != null &&
volumetricAirInLineAlarm.Contains(pumpObservation.AlarmType.ToString() ?? string.Empty))
{
name = "VolumetricAirInLine";
//alerta aire para bombas volumétricas (burbujas de aire para la bomba)
patientObservation = await CreatePumpAlarmObservation(name, pumpObservation);
}
}
if (patientObservation != null && name != null)
{
_ = _observationService.Value.InsertObservation(patientObservation);
_ = _alarmService.Value.SendAlarm(patientObservation, name, AlarmEnum.Name.Pump,
AlarmEnum.Severity.None, AlarmEnum.Type.Auto);
}
}
}
catch (Exception ex)
{
_logger.LogError("Error mapping alarm pump observation. Exception: {exMessage}", ex.Message);
}
return pumpObservation;
}
public Task<PatientTreatment> Map(PatientTreatment treatment)
{
throw new NotImplementedException();
}
public Task<PatientDiagnosis> Map(PatientDiagnosis diagnosis)
{
throw new NotImplementedException();
}
public async Task<PatientObservation?> FixTimeInconsistencyWithLast(PatientObservation newObservation)
{
if (string.IsNullOrEmpty(newObservation.Name))
{
_logger.LogError(
"Error FixTimeInconsistencyWithLast new observation name is null or empty: {newObservation}.",
newObservation);
return newObservation;
}
var lasObservations = await _observationService.Value.FindLastObservations(newObservation.PatientId, 1,
[newObservation.Name]);
var lastObservation = lasObservations.FirstOrDefault();
if (lastObservation != null && DateTime.Compare(
new DateTime(lastObservation.Time.Year, lastObservation.Time.Month,
lastObservation.Time.Day, lastObservation.Time.Hour,
lastObservation.Time.Minute, lastObservation.Time.Second),
new DateTime(newObservation.Time.Year, newObservation.Time.Month,
newObservation.Time.Day, newObservation.Time.Hour,
newObservation.Time.Minute, newObservation.Time.Second)
) >= 0)
newObservation.Time = lastObservation.Time.AddSeconds(1);
return newObservation;
}
public Task<List<PatientObservation>> PreMapList(List<PatientObservation> listToMap)
{
var mappedObsList = listToMap
.Select(obs => obs.DeepCopy())
.Select(obs => _configObservationService.Map(obs).Result)
.Where(obs => obs != null)
.ToList();
// mappedObsList contains observation mapped
// Check blue code
_ = CalculateBlueCodeList(mappedObsList);
return Task.FromResult(listToMap);
}
public Task<PatientObservation> MapSourceAlarm(PatientObservation obs, PatientObservationAlarm alarmToInsert)
{
if (!string.IsNullOrEmpty(alarmToInsert.EventId))
obs.Code = alarmToInsert.EventId;
if (!string.IsNullOrEmpty(alarmToInsert.Event))
obs.Name = alarmToInsert.Event;
obs.Value = alarmToInsert.Value;
return Task.FromResult(obs);
}
Task ICalculatedObservations.SendAlarm(PatientObservation obs, string name, AlarmEnum.Name? code)
{
throw new NotImplementedException();
}
private async Task SendAlarm(PatientObservation obs, string name, AlarmEnum.Name? code, AlarmEnum.Type type)
{
await _alarmService.Value.SendAlarm(obs, name, code, AlarmEnum.Severity.None, type);
}
/// <summary>
/// Initial check to ignore old observations generating blue codes
/// </summary>
/// <param name="obs">Observation</param>
/// <returns>True if it needs to be ignored</returns>
private bool CheckIfObservationIsOlderAndShouldBeIgnored(PatientObservation obs)
{
if (obs.Time.ToUniversalTime().AddMinutes(_apiSettings.IgnoreCalcObservationsOlderInMinutesThan) <
DateTime.UtcNow)
{
Log.Debug(
"ignoring calc old observation: name:{obsName} obs_time:{obsTime} system_time {DateTimeNow} id:{obsId}",
obs.Name ?? "null", obs.Time, DateTime.Now, obs.Id);
return true;
}
return false;
}
private async Task<PatientObservation> CheckAlarmConfig(PatientObservation pobs)
{
var configObs = await _configObservationService.Get(new PatientObservation
{
Name = pobs.Name,
PatientId = pobs.PatientId
});
pobs.Alarm = configObs?.Alarm ?? null;
return pobs;
}
private static PatientObservation CreateAlarmObservation(string name, PatientObservation pobs)
{
return new PatientObservation
{
CodingSystem = "ADAS_ALARM",
Code = name,
Name = $"Alarm_{name}",
Value = pobs.Value,
PatientId = pobs.PatientId,
Time = pobs.Time,
Alarm = pobs.Alarm
};
}
/// <summary>
/// Checks if a blue code observation Lists needs to be generated and inserts it.
/// </summary>
/// <param name="obsList"></param>
public async Task CalculateBlueCodeList(List<PatientObservation?>? obsList)
{
//Las 3 observaciones vienen siempre juntas en el mismo mensaje
//Solo se comprueba si se cumplen 2 de las 3 condiciones
var condition = 0;
var patientId = obsList?.FirstOrDefault()?.PatientId;
if (patientId == null) return;
var fcObs = obsList?.FirstOrDefault(o => o is { Name: "FC" });
var sattcObs = obsList?.FirstOrDefault(o => o is { Name: "Sattc" });
var tamObs = obsList?.FirstOrDefault(o => o is { Name: "TAm" });
// FC < 60
if (fcObs != null && !CheckIfObservationIsOlderAndShouldBeIgnored(fcObs) &&
double.TryParse(fcObs.Value.ToString(), out var cardiacBeatRate) && cardiacBeatRate < 60)
{
Log.Debug("BlueCode condition: FC {fc} for patient: {obsPatientid} fc increase limit", cardiacBeatRate,
fcObs.PatientId);
condition++;
}
// SpO2 < 80 (Sattc)
if (sattcObs != null && !CheckIfObservationIsOlderAndShouldBeIgnored(sattcObs) &&
double.TryParse(sattcObs.Value.ToString(), out var spO2Value) && spO2Value < 80)
{
Log.Debug("Blue code condition: SpO2 {sp} for patient: {obsPatientid} Sattc increase limit", spO2Value,
sattcObs.PatientId);
condition++;
}
if (condition == 0) return;
// Early exit if 2 conditions are met
if (condition == 2)
{
_ = SendBlueCode(fcObs);
return;
}
// TAm actual es un 50% por debajo de la medición de 30 seg antes
if (tamObs != null && !CheckIfObservationIsOlderAndShouldBeIgnored(tamObs) &&
double.TryParse(tamObs.Value.ToString(), out var tamObsValue))
{
// Buscamos el anterior
var lastPressBloodObsList =
await _observationService.Value.FindLastObservations(tamObs.PatientId, 1, _pressBloodArteryMean);
var lastPressBloodObs = lastPressBloodObsList.FirstOrDefault();
// Diferencia entre la última observación es < 30s
if (lastPressBloodObs != null &&
double.TryParse(lastPressBloodObs.Value.ToString(), out var lastPressBloodValue) &&
(tamObs.Time - lastPressBloodObs.Time).TotalSeconds <= 35 &&
tamObsValue <= lastPressBloodValue / 2)
{
Log.Debug("Blue code condition: TAm decrease limit over 30s for patient: {obsPatientid}",
tamObs.PatientId);
condition++;
}
}
if (condition >= 2) _ = SendBlueCode(tamObs);
}
private async Task SendBlueCode(PatientObservation? pobs)
{
if (pobs == null)
return;
Log.Debug("Blue code Alarm for patient: {obsPatientid}", pobs.PatientId);
var blueCodeObservation = CreateAlarmObservation("BlueCode", pobs);
blueCodeObservation = await CheckAlarmConfig(blueCodeObservation);
_ = _observationService.Value.InsertObservation(blueCodeObservation);
_ = SendAlarm(blueCodeObservation, "BlueCode", AlarmEnum.Name.Blue, AlarmEnum.Type.Auto);
}
//PRVC : donde aparezca cambiar y mostrar en su lugar: VCRP
//FLUJO ALTO: poner OAF en su lugar
private BasePatientObservationValue CalculateVentilationMode(BasePatientObservationValue obs)
{
var value = obs is PatientObservationAlarm oAlarm ? oAlarm.Value
: obs is PatientObservation o ? o.Value : null;
if (value != null && value.ToString()!.Contains("PRVC"))
obs.Value = value.ToString()!.Replace("PRVC", "VCRP");
else if (value != null && value.ToString()!.Contains("FLUJ.ALTO")) obs.Value = "OAF";
_logger.LogDebug("Calculating patient id: {id} ventilation mode: {obsName}, value : {obsValue}", obs.PatientId,
obs.Name, obs.Value);
return obs;
}
//TODO duplicado con el calculated del 12o, sacar a un utils
private async Task<BasePatientObservationValue> CheckObsWithSameTimeExistsAndIncrementTime(
BasePatientObservationValue obs)
{
if (obs is not PatientObservation pobs) return obs;
var existObsWithSameTime =
await _observationService.Value.FindAnyWithSameDate(pobs.PatientId, pobs.Time, pobs.Name);
if (existObsWithSameTime != null && existObsWithSameTime.Any()) pobs.Time = pobs.Time.AddSeconds(1);
return pobs;
}
//S/F Calc ( craneal saturation / FiO2
private async Task CalculateSf(BasePatientObservationValue obs, string name)
{
try
{
PatientObservation? saturation = null;
PatientObservation? fio2 = null;
var values = await _observationService.Value.FindLastObservations(obs.PatientId, 1, ["FiO2", "Sattc"]);
switch (name)
{
case "FiO2":
fio2 = (PatientObservation)obs;
saturation = values.FirstOrDefault(o => o.Name == "Sattc");
break;
case "Sattc":
saturation = (PatientObservation)obs;
fio2 = values.FirstOrDefault(o => o.Name == "FiO2");
break;
}
if (fio2 != null && saturation != null && !fio2.Expired &&
double.TryParse(fio2.Value.ToString(), out var fio) && !saturation.Expired &&
double.TryParse(saturation.Value.ToString(), out var sat) && sat <= 97)
{
if (fio == 0)
{
_logger.LogError("Error calculating SF. FiO2: {fio}", fio);
return;
}
var sfValue = sat / fio;
sfValue = Math.Round(sfValue, 4);
var sfObs = new PatientObservation
{
PatientId = obs.PatientId,
Name = "Sattc_FiO2",
CodingSystem = "ADAS",
Value = sfValue,
Time = obs.Time
};
await _observationService.Value.InsertObservation(sfObs);
}
}
catch (Exception ex)
{
_logger.LogError("Error calculate S/F observation. Exception: {exMessage}", ex.Message);
}
}
/// <summary>
/// Oxygenation index is calculated => P_VAM x FiO2 x 100 / PaO2_Tidal
/// the method is called when it receives P_VAM or FiO2 or PaO2_Tidal and tries to take the other values to perform the
/// calculation if they are not
/// in database then does nothing.
/// </summary>
public async Task CalculateOxygenationIndex(BasePatientObservationValue obs, string name)
{
var toSearchList = new List<string>();
PatientObservation? pVam = null;
PatientObservation? fiO2 = null;
PatientObservation? paO2 = null;
toSearchList.AddRange(new List<string> { "P_VAM", "FiO2", "PaO2_Tidal" });
var values = await _observationService.Value.FindLastObservations(obs.PatientId, 1, toSearchList);
try
{
switch (name)
{
case "P_VAM":
pVam = (PatientObservation)obs;
fiO2 = values.FirstOrDefault(o => o.Name == "FiO2");
paO2 = values.FirstOrDefault(o => o.Name == "PaO2_Tidal");
break;
case "FiO2":
fiO2 = (PatientObservation)obs;
pVam = values.FirstOrDefault(o => o.Name == "P_VAM");
paO2 = values.FirstOrDefault(o => o.Name == "PaO2_Tidal");
break;
case "PaO2_Tidal":
paO2 = (PatientObservation)obs;
fiO2 = values.FirstOrDefault(o => o.Name == "FiO2");
pVam = values.FirstOrDefault(o => o.Name == "P_VAM");
break;
}
//Controlar que FiO2 no haya expirada al recogerla
if (fiO2 != null && paO2 != null && pVam != null &&
int.TryParse(pVam.Value.ToString(), out var pvam) &&
int.TryParse(fiO2.Value.ToString(), out var fio2) &&
int.TryParse(paO2.Value.ToString(), out var pao2))
{
if (pao2 == 0)
{
_logger.LogError(
"Error calculate Oxygenation Index observation. PaO2: {pao2}, observation: {obsName}", pao2,
name);
return;
}
var oxygenationIndexValue = pvam * fio2 * 100 / pao2;
var oxygenationIndexObs = new PatientObservation
{
PatientId = obs.PatientId,
Name = "Oxygenation_Index",
CodingSystem = "ADAS",
Value = oxygenationIndexValue,
Time = obs.Time
};
await _observationService.Value.InsertObservation(oxygenationIndexObs);
}
}
catch (Exception ex)
{
_logger.LogError("Error calculate Oxygenation Index observation. Exception: {exMessage}", ex.Message);
}
}
//P/F Calc ( PaO2_Tidal / FiO2
private async Task CalculatePf(BasePatientObservationValue obs, string name)
{
try
{
PatientObservation? paO2 = null;
PatientObservation? fio2 = null;
var values = await _observationService.Value.FindLastObservations(obs.PatientId, 1,
["FiO2", "PaO2_Tidal"]);
switch (name)
{
case "FiO2":
fio2 = (PatientObservation)obs;
paO2 = values.FirstOrDefault(o => o.Name == "PaO2_Tidal");
break;
case "PaO2_Tidal":
paO2 = (PatientObservation)obs;
fio2 = values.FirstOrDefault(o => o.Name == "FiO2");
break;
}
if (fio2 != null && paO2 != null && !fio2.Expired && !paO2.Expired &&
double.TryParse(paO2.Value.ToString(), out var pa) &&
double.TryParse(fio2.Value.ToString(), out var fi))
{
if (fi == 0)
{
_logger.LogError("Error Calculating PF. FiO2 value is 0. Observation:{obs}", obs);
return;
}
var sfValue = pa / fi;
sfValue = Math.Round(sfValue, 4);
var sfObs = new PatientObservation
{
PatientId = obs.PatientId,
Name = "PaO2_FiO2",
CodingSystem = "ADAS",
Value = sfValue,
Time = obs.Time
};
await _observationService.Value.InsertObservation(sfObs);
}
}
catch (Exception ex)
{
_logger.LogError("Error calculate P/F observation. Exception: {exMessage}", ex.Message);
}
}
private async Task<PatientObservation?> CreatePumpAlarmObservation(string name,
PumpObservation pumpObservation)
{
var positionPump = pumpObservation.IsAux != null && !pumpObservation.IsAux.Value
? $"Rack {pumpObservation.GatewayNumber} Bomba {pumpObservation.Number}"
: $"Rack Aux {pumpObservation.GatewayNumber} Bomba {pumpObservation.Number}";
PatientObservation patientObservation = new()
{
CodingSystem = "ADAS_ALARM",
Code = $"Pump_{name}",
Name = $"Alarm_Pump_{name}",
Value = pumpObservation.DrugName ?? positionPump,
Time = pumpObservation.Time
};
if (pumpObservation.PatientId != null)
patientObservation.PatientId = pumpObservation.PatientId.Value;
return await CheckAlarmConfig(patientObservation);
}
}
@@ -0,0 +1,883 @@
using adas_core.Application.Services.Interfaces;
using adas_core.Domain.Enums;
using adas_core.Domain.Models;
using adas_core.Domain.Models.AppSettings;
using adas_core.Domain.Models.MongoModels;
using adas_core.Domain.Models.Pumps;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using MongoDB.Bson;
namespace adas_core.Application.Customizations.HRYC;
public class CalculatedObservations : ICalculatedObservations
{
private readonly IOptions<ApiSettings> _apiSettings;
private readonly List<string> _codesForInvasiveVentilation = [];
private readonly IConfigObservationService _configObservationService;
//private readonly Lazy<IBoxService> _boxService;
private readonly List<string> _highFrequencyVentilation = [];
private readonly Lazy<ILightBeaconService> _lightBeaconService;
private readonly ILogger<CalculatedObservations> _logger;
private readonly List<string> _nonInvasiveVentilation = [];
private readonly Lazy<IObservationService> _observationService;
private readonly Lazy<IPatientService> _patientService;
public CalculatedObservations(IServiceProvider serviceProvider)
{
_observationService = serviceProvider.GetRequiredService<Lazy<IObservationService>>(); //observationService;
_patientService = serviceProvider.GetRequiredService<Lazy<IPatientService>>();
_apiSettings = serviceProvider.GetRequiredService<IOptions<ApiSettings>>(); //apiSettings;
//_boxService = serviceProvider.GetRequiredService<Lazy<IBoxService>>();
_logger = serviceProvider.GetRequiredService<ILogger<CalculatedObservations>>();
_lightBeaconService = serviceProvider.GetRequiredService<Lazy<ILightBeaconService>>();
_configObservationService =
serviceProvider.GetRequiredService<IConfigObservationService>(); //configObservationService;
var highFrenquencyVentilation = _apiSettings.Value.HighFrequencyVentilation ?? null;
highFrenquencyVentilation?.ForEach(x => _highFrequencyVentilation.Add(x.Trim()));
var nonInvasiveVentilation = _apiSettings.Value.NonInvasiveVentilation ?? null;
nonInvasiveVentilation?.ForEach(x => _nonInvasiveVentilation.Add(x.Trim()));
var codesForInvasiveVentilation = //TODO comprobar si se refiere a InvasiveVentilation
_apiSettings.Value.InvasiveVentilation ?? null;
codesForInvasiveVentilation?.ForEach(x => _codesForInvasiveVentilation.Add(x.Trim()));
}
public async Task<T?> Map<T>(T obs, bool onlyByName) where T : BasePatientObservation
{
_logger.LogTrace("Mapping {name} observation {obs}", obs.Name, obs);
switch (obs.Name)
{
case "Resp_Mode":
_logger.LogTrace("Mapping Resp mode observation {obs}", obs);
await CalculateVentilationMode(obs);
break;
case "Diuresis":
case "Weight_Current":
if (obs.Name == "Weight_Current") await CalculateWeight_DiffObservation(obs);
await CalculateDiureis_WeightObservation(obs);
break;
case "AllergiesObs":
await CalculateAllergiesObservation(obs);
break;
case "DrainagesObs":
await CalculateDrainagesObservation(obs);
break;
case "PEEP":
case "Pleateu_Pressure":
await CalculateDelta_PressureObservation(obs);
break;
case "Daily_Balance":
await CalculateDaily_BalanceObservation(obs);
break;
case "Hydric_Balance":
await CalculateHydricBalanceCalculated(obs);
obs = (T)await CalculateHydricBalance(obs);
break;
case "Hour_Balance":
obs = (T)await CalculateHourBalance(obs);
break;
case "FR":
await CalculateRespRate(obs);
await CalculateIrox(obs);
break;
case "Vent_Rate":
await CalculateRespRate(obs);
break;
case "SpO2":
case "FiO2":
await CalculateIrox(obs);
break;
case "NEWS":
await CalculateNews(obs);
break;
}
return obs;
}
public Task<PatientTreatment> Map(PatientTreatment treatment)
{
throw new NotImplementedException();
}
public async Task<PumpObservation> Map(PumpObservation pumpObservation)
{
return await Task.FromResult(pumpObservation);
}
public Task CalculateMedicineObservation(List<Medicine> activeMedicines, ObjectId patientId)
{
return Task.CompletedTask;
}
public Task CalculateActiveBolus(ObjectId patientId)
{
return Task.CompletedTask;
}
public Task<IEnumerable<PatientTreatment?>> GetActiveTreatmentsByPatient(ObjectId id)
{
return Task.FromResult(new List<PatientTreatment?>().AsEnumerable());
}
public Task<PatientDiagnosis> Map(PatientDiagnosis diagnosis)
{
return Task.FromResult(diagnosis);
}
public async Task<PatientObservation?> FixTimeInconsistencyWithLast(PatientObservation newObservation)
{
if (string.IsNullOrEmpty(newObservation.Name))
{
_logger.LogError(
"Error FixTimeInconsistencyWithLast newObservation name ia null or empty. Observation: {newObservation}",
newObservation);
return newObservation;
}
var lastObservations = await _observationService.Value.FindLastObservations(newObservation.PatientId, 1,
[newObservation.Name]);
var lastObservation = lastObservations.FirstOrDefault();
if (lastObservation != null && DateTime.Compare(
new DateTime(lastObservation.Time.Year, lastObservation.Time.Month,
lastObservation.Time.Day, lastObservation.Time.Hour,
lastObservation.Time.Minute, lastObservation.Time.Second),
new DateTime(newObservation.Time.Year, newObservation.Time.Month,
newObservation.Time.Day, newObservation.Time.Hour,
newObservation.Time.Minute, newObservation.Time.Second)
) >= 0)
newObservation.Time = lastObservation.Time.AddSeconds(1);
return newObservation;
}
public async Task<List<PatientObservation>> PreMapList(List<PatientObservation> listToInsert)
{
var ventRate = listToInsert.FirstOrDefault(obs => obs.Name == "MDC_VENT_RESP_RATE");
var respRate = listToInsert.FirstOrDefault(obs => obs.Name == "MDC_RESP_RATE");
// Check if obs for Vent_Rate and FR is on the list to insert
if (ventRate == null || respRate == null) return listToInsert;
var ventRateValue = double.TryParse(ventRate.Value.ToString(), out var v1) ? v1 : 0;
// ventRate is on the list to insert but its value is 0
if (ventRateValue == 0)
{
_logger.LogDebug("Find ventRate on PreMap request not processed because its value is 0");
return listToInsert;
}
var respRateValue = double.TryParse(ventRate.Value.ToString(), out var v2) ? v2 : 0;
// respRate is on the list to insert but its value is 0
if (respRateValue == 0)
{
_logger.LogDebug("Find respRate on PreMap request not processed because its value is 0");
return listToInsert;
}
// Exists both obs and have value != 0
// In that case we need to calculate Resp_Rate_Calculated just with the value of Vent_Rate
try
{
_logger.LogDebug(
"Find on PreMap Vent_Rate and FR in the same request process Vent_Rate first to calculate Resp_Rate_Calculated");
await _observationService.Value.InsertObservation(ventRate);
listToInsert.Remove(ventRate);
}
catch (Exception e)
{
_logger.LogError("Error while PreMap on insert obs: {ventRate} exception: {e}", ventRate, e);
}
return listToInsert;
}
public Task<PatientObservation> MapSourceAlarm(PatientObservation obs, PatientObservationAlarm alarmToInsert)
{
return Task.FromResult(obs);
}
public Task SendAlarm(PatientObservation obs, string name, AlarmEnum.Name? code)
{
throw new NotImplementedException();
}
/// <summary>
/// IF news greater or equal than 5 and less than 7 is warning beacon and if greater or equal than 7 alert
/// </summary>
/// <param name="obs"></param>
private async Task CalculateNews(BasePatientObservation obs)
{
var pobs = (PatientObservation)obs;
var parsed = int.TryParse(pobs.Value.ToString(), out var valueParsed);
var patient = await _patientService.Value.FindById(obs.PatientId);
if (patient == null) return;
//var beaconConfig = await _boxService.Value.GetBeaconConfig(new PatientLocation(patient.PointOfCare, patient.Bed));
//power off beacon
//recuperar la beacon del paciente y encenderla apagarla si el valor es > 5&6 o 7 alert
if ((parsed && valueParsed < 5) || !parsed)
//await _balizaService.Value.PowerOffLed(beaconCnf);
//await _observationService.Value.SendObsBroadcast(obs);
await SendAlarm(obs, AlarmEnum.Name.NewsOff);
else
switch (valueParsed)
{
case >= 5 and < 7:
_logger.LogDebug("beacon yellow alert by NEWS for patient: {patientid}", obs.PatientId);
await SendAlarm(obs, AlarmEnum.Name.NewsWarning);
//await _balizaService.Value.SendBeaconColor(patient, LightBeaconColor.YELLOW);
break;
case >= 7:
_logger.LogDebug("beacon red alert by NEWS for patient: {patientid}", obs.PatientId);
await SendAlarm(obs, AlarmEnum.Name.NewsAlert);
//await _balizaService.Value.SendBeaconColor(patient, LightBeaconColor.RED);
break;
}
//if (obsAlarm == null) return;
}
private async Task SendAlarm(BasePatientObservation obs, AlarmEnum.Name name)
{
var pobs = (PatientObservation)obs;
PatientObservation nObs = new()
{
CodingSystem = "ADAS_ALARM",
Code = name.ToString(),
Name = $"Alarm_{name}",
PatientId = pobs.PatientId,
Time = pobs.Time
};
try
{
//ConfigObservations
var configObs = await _configObservationService.Get(new PatientObservation
{
Name = nObs.Name,
PatientId = obs.PatientId
}
);
if (configObs == null)
{
_logger.LogError("Config observation is null on send alarm NEWS for {nObs}", nObs);
return;
}
if (configObs is { Alarm.Enabled: true })
{
nObs.Alarm = configObs.Alarm;
if (configObs.Alarm.Beacon is { Enabled: true })
{
var patient = await _patientService.Value.FindById(obs.PatientId);
if (patient != null)
{
_logger.LogDebug(
"PatientId: {nObsPatientId}. Send Beacon alarmName {configObsAlarmBeaconBeaconColorValue}",
nObs.PatientId, configObs.Alarm.Beacon.BeaconColor);
nObs.Value = configObs.Alarm.Beacon.BeaconColor.ToString();
SendBeaconCode(configObs.Alarm.Beacon.BeaconColor, patient);
}
}
}
_logger.LogDebug(
"Insert obs alarm for news at: {DateTimeNow} patientId: {nObsPatientId} color value: {nObsValue}",
DateTime.Now, nObs.PatientId, nObs.Value);
await _observationService.Value.InsertObservation(nObs);
}
catch (Exception ex)
{
_logger.LogError("Exception sending alarm: {exMessage}", ex.Message);
}
}
private void SendBeaconCode(AlarmEnum.BeaconColor color, Patient patient)
{
if (!patient.PointOfCareId.HasValue)
{
_logger.LogError("Error sending beacon color on calculateObservations poc id on patient is null {Patient}",
patient.ToString());
return;
}
switch (color)
{
case AlarmEnum.BeaconColor.Blue:
_lightBeaconService.Value.SendColor(patient.PointOfCareId.Value, LightBeaconColor.Blue);
break;
case AlarmEnum.BeaconColor.Yellow:
_lightBeaconService.Value.SendColor(patient.PointOfCareId.Value, LightBeaconColor.Yellow);
break;
case AlarmEnum.BeaconColor.Red:
_lightBeaconService.Value.SendColor(patient.PointOfCareId.Value, LightBeaconColor.Red);
break;
case AlarmEnum.BeaconColor.None:
_lightBeaconService.Value.SendColor(patient.PointOfCareId.Value, LightBeaconColor.Off);
break;
}
}
//(SpO2/FiO2)/FR IROX formula. Only calculate when all observations are in last 10 minutes.
private async Task CalculateIrox(BasePatientObservation obs)
{
try
{
var pob = (PatientObservation)obs;
var obsToCalc = new List<PatientObservation> { pob };
switch (pob.Name)
{
case "SpO2":
var fio2Frvalues = await _observationService.Value.FindLastObservations(obs.PatientId, 1,
["FiO2", "FR"]);
if (fio2Frvalues.Count > 0) obsToCalc.AddRange(fio2Frvalues);
break;
case "FiO2":
var spo2Frvalues = await _observationService.Value.FindLastObservations(obs.PatientId, 1,
["SpO2", "FR"]);
if (spo2Frvalues.Count > 0) obsToCalc.AddRange(spo2Frvalues);
break;
case "FR":
var fio2Spo2Values = await _observationService.Value.FindLastObservations(obs.PatientId, 1,
["FiO2", "SpO2"]);
if (fio2Spo2Values.Count > 0) obsToCalc.AddRange(fio2Spo2Values);
break;
}
if (!obsToCalc.All(o => o.Time.CompareTo(DateTime.UtcNow.AddMinutes(10)) <= 0)) return;
var fio2 = obsToCalc.FirstOrDefault(o => o.Name == "FiO2");
var fr = obsToCalc.FirstOrDefault(o => o.Name == "FR");
var spo2 = obsToCalc.FirstOrDefault(o => o.Name == "SpO2");
if (fio2 == null || fr == null || spo2 == null) return;
if (!double.TryParse(spo2.Value.ToString(), out var nSpo2) ||
!double.TryParse(fio2.Value.ToString(), out var nFio2) ||
!double.TryParse(fr.Value.ToString(), out var nFr))
{
_logger.LogWarning("Error parsing values. fio2: {fio2}, fr: {fr}, spo2: {spo2}", fio2, fr, spo2);
return;
}
var spo2Value = nSpo2;
var fio2Value = nFio2;
var frValue = nFr;
if (spo2Value == 0 || fio2Value == 0 || frValue == 0) return;
var value = nSpo2 / nFio2 / nFr;
var irox = new PatientObservation
{
Name = "IROX",
CodingSystem = "ADAS",
PatientId = obs.PatientId,
Time = obs.Time,
Value = value
};
await _observationService.Value.InsertObservation(irox);
}
catch (Exception e)
{
_logger.LogError("Exception calculating IROX, error: {eMessage}", e.Message);
}
}
/*
*
RespRate solo se tiene que poner cuando no hay ningún ventRate en los últimos min y veinte segundos y el último no es un 0.
*/
//static readonly SemaphoreSlim semaphoreCalculateRespRate = new(1, 1);
private async Task CalculateRespRate(BasePatientObservation obs)
{
var pobs = (PatientObservation)obs;
_logger.LogDebug("CalculateRespRate {obsPatientid} wait {obsName} {obsTime} {pobsValue}", obs.PatientId,
obs.Name, obs.Time, pobs.Value);
if (Convert.ToDouble(pobs.Value) == 0) return;
if (Convert.ToDouble(pobs.Value) == 0) return;
var insert = true;
if (obs.Name == "FR")
{
// Insertamos si no existe una obs Vent_Rate o si es existe tiene más de X" y su valor es > 0
var lastVentRate =
(await _observationService.Value.FindLastObservations(obs.PatientId, 1, ["Vent_Rate"]))
.FirstOrDefault();
if (lastVentRate == null || (double.TryParse(lastVentRate.Value.ToString(), out var v) ? v : 0) == 0)
{
_logger.LogDebug("CalculateRespRate {id} lastVentRate: null", obs.PatientId);
}
else
{
var diff = DateTime.UtcNow.Subtract(lastVentRate.Time.ToUniversalTime());
insert =
lastVentRate.Time.ToUniversalTime().AddMinutes(_apiSettings.Value.CalculateRespRateVentExpires) <
DateTime.UtcNow;
_logger.LogDebug(
"CalculateRespRate {patientid} lastVentRate: {value} - {time} passed {diff} {insert}",
obs.PatientId, lastVentRate.Value, lastVentRate.Time, diff, insert ? "Inserted" : "Not inserted");
}
}
if (insert)
{
_logger.LogDebug(
"CalculateRespRate {patientid} Insert Resp_Rate_Calculated with {name} - {time} value {value}",
obs.PatientId, obs.Name, obs.Time, pobs.Value);
var calculatedRespRate = new PatientObservation
{
Id = new ObjectId(),
Name = "Resp_Rate_Calculated",
CodingSystem = "ADAS",
PatientId = obs.PatientId,
Time = obs.Name == "FR" ? obs.Time : obs.Time.AddSeconds(1),
Value = pobs.Value,
ParentData = new ParentDataClass { Name = obs.Name }
};
await _observationService.Value.InsertObservation(calculatedRespRate);
}
else
{
_logger.LogDebug(
"CalculateRespRate {patientid} Insert Resp_Rate_Calculated with {name} - {time} value {value}",
obs.PatientId, obs.Name, obs.Time, pobs.Value);
}
_logger.LogDebug("CalculateRespRate {patientid} release {name} {time} {value}", obs.PatientId,
obs.Name, obs.Time, pobs.Value);
}
private async Task<BasePatientObservation> CalculateHydricBalance(BasePatientObservation obs)
{
var pobs = (PatientObservation)obs;
var lastHydricBalanceFromHour =
await _observationService.Value.FindLastBeforeDate(pobs.PatientId, pobs.Time.AddMinutes(59),
"Hydric_Balance");
if (lastHydricBalanceFromHour != null &&
DateTime.Compare(new DateTime(lastHydricBalanceFromHour.Time.Year, lastHydricBalanceFromHour.Time.Month,
lastHydricBalanceFromHour.Time.Day, lastHydricBalanceFromHour.Time.Hour, 0, 0)
, new DateTime(pobs.Time.Year, pobs.Time.Month, pobs.Time.Day, pobs.Time.Hour, 0, 0)) == 0)
pobs.Time = lastHydricBalanceFromHour.Time.AddSeconds(1);
return obs;
}
private async Task<BasePatientObservation> CalculateHourBalance(BasePatientObservation obs)
{
var pobs = (PatientObservation)obs;
var lastHourBalanceFromHour =
await _observationService.Value.FindLastBeforeDate(pobs.PatientId, pobs.Time.AddMinutes(59),
"Hour_Balance");
if (lastHourBalanceFromHour != null &&
DateTime.Compare(new DateTime(lastHourBalanceFromHour.Time.Year, lastHourBalanceFromHour.Time.Month,
lastHourBalanceFromHour.Time.Day, lastHourBalanceFromHour.Time.Hour, 0, 0)
, new DateTime(pobs.Time.Year, pobs.Time.Month, pobs.Time.Day, pobs.Time.Hour, 0, 0)) == 0)
pobs.Time = lastHourBalanceFromHour.Time.AddSeconds(1);
return obs;
}
private async Task CalculateHydricBalanceCalculated(BasePatientObservation obs)
{
//Hydric_Balance_Calculated
//No guardamos como calculadas nunca horas futuras. En el obx siempre vienen con la hora y los minutos a 00
var pobs = (PatientObservation)obs;
var nowUniversalTime = DateTime.Now.ToUniversalTime();
//La hora de la observación es mayor que la actual. No generamos la calculada
if (DateTime.Compare(new DateTime(obs.Time.Year, obs.Time.Month, obs.Time.Day, obs.Time.Hour, 0, 0),
new DateTime(nowUniversalTime.Year, nowUniversalTime.Month, nowUniversalTime.Day, nowUniversalTime.Hour,
0, 0)) > 0
)
{
_logger.LogDebug(
"HYDRIC BALANCE: La hora de la observación es mayor que la actual. No generamos la calculada {obs}",
obs);
return;
}
//Para la calculada. Si la última que tenemos es de la hora actual y lo que viene es de una hora anterior no entra como calculada.
var lastHydricBalanceList = await _observationService.Value.FindLastObservations(obs.PatientId, 1,
["Hydric_Balance_Calculated"]);
var lastHydricBalance = lastHydricBalanceList.FirstOrDefault();
if (lastHydricBalance != null && lastHydricBalance.Time.Hour == DateTime.UtcNow.Hour &&
obs.Time.Hour != DateTime.UtcNow.Hour)
{
_logger.LogDebug(
"HYDRIC BALANCE: ultima que tenemos es de la hora actual y lo que viene es de una hora anterior no entra como calculada {obs}",
obs);
return;
}
//Si el último hydric balance es de una hora posterior a la observación que acaba de llegar no entra como calculada.
if (lastHydricBalance != null &&
DateTime.Compare(new DateTime(lastHydricBalance.Time.Year, lastHydricBalance.Time.Month,
lastHydricBalance.Time.Day, lastHydricBalance.Time.Hour, 0, 0)
, new DateTime(pobs.Time.Year, pobs.Time.Month, pobs.Time.Day, pobs.Time.Hour, 0, 0)) == 1
)
{
_logger.LogDebug(
"HYDRIC BALANCE: Si el ultimo hydric balance es de una hora posterior a la observación que acaba de llegar no entra como calculada. {obs}",
obs);
return;
}
var calculatedHydricBalance = new PatientObservation
{
Name = "Hydric_Balance_Calculated",
CodingSystem = "ADAS",
PatientId = obs.PatientId,
Time = obs.Time.AddSeconds(1), //To discriminate updates.
Value = pobs.Value,
Units = obs.Units
};
if (lastHydricBalance != null && DateTime.Compare(
new DateTime(lastHydricBalance.Time.Year, lastHydricBalance.Time.Month, lastHydricBalance.Time.Day,
lastHydricBalance.Time.Hour, 0, 0)
, new DateTime(pobs.Time.Year, pobs.Time.Month, pobs.Time.Day, pobs.Time.Hour, 0, 0)) == 0)
calculatedHydricBalance.Time = lastHydricBalance.Time.AddSeconds(1);
await _observationService.Value.InsertObservation(calculatedHydricBalance);
}
private async Task CalculateVentilationMode(BasePatientObservation obs)
{
_logger.LogDebug("CalculateVentilationMode {obs}", obs);
var pobs = (PatientObservation)obs;
var respTypeObs = new PatientObservation
{
Name = "Resp_Type",
CodingSystem = "ADAS",
PatientId = obs.PatientId,
Time = obs.Time
};
//https://epigram.teamwork.com/#/tasks/34765750
if (obs.Code != null && _codesForInvasiveVentilation.Contains(obs.Code))
{
//añadir que sea solo cuando el valor es invasiva
respTypeObs.Value = nameof(RespirationType.Invasive);
respTypeObs.Time = obs.Time.AddSeconds(1);
await _observationService.Value.InsertIfChanged("Resp_Type", respTypeObs);
}
else
{
var strValue = pobs.Value.ToString();
if (strValue == null)
return;
//si es en espera tendrá que insertar que es en espera
if (!"EnESPERA".Equals(strValue))
{
respTypeObs.Value = _highFrequencyVentilation.Contains(strValue)
? nameof(RespirationType.HighFrequencyVentilation)
: _nonInvasiveVentilation.Contains(strValue)
? respTypeObs.Value = nameof(RespirationType.NonInvasive)
: nameof(RespirationType.Invasive);
await _observationService.Value.InsertIfChanged("Resp_Type", respTypeObs);
}
}
}
private async Task CalculateWeight_DiffObservation(BasePatientObservation obs)
{
var toSearchList = new List<string>();
toSearchList.AddRange(new List<string> { "Weight_Current" });
var values = await _observationService.Value.FindLastObservations(obs.PatientId, 1, toSearchList);
var pWeight = values.FirstOrDefault(o => o.Name == "Weight_Current");
var newWeightObs = (PatientObservation)obs;
if (pWeight?.Value != null && double.TryParse(pWeight.Value.ToString(), out var weight) &&
double.TryParse(newWeightObs.Value.ToString(), out var newWeight))
{
var weightDiffValue = Math.Round(newWeight - weight, 2);
var weightDiff = new PatientObservation
{
Name = "Weight_Diff",
CodingSystem = "ADAS",
PatientId = obs.PatientId,
Time = obs.Time,
Value = weightDiffValue,
Units = obs.Units
};
await _observationService.Value.InsertObservation(weightDiff);
}
}
private async Task CalculateDiureis_WeightObservation(BasePatientObservation obs)
{
var toSearchList = new List<string>();
PatientObservation? diuresisObs;
PatientObservation? weightObs;
toSearchList.AddRange(new List<string> { "Diuresis", "Weight_Current" });
var values = await _observationService.Value.FindLastObservations(obs.PatientId, 1, toSearchList);
if (obs.Name == "Diuresis")
{
diuresisObs = (PatientObservation)obs;
weightObs = values.FirstOrDefault(o => o.Name == "Weight_Current");
}
else
{
weightObs = (PatientObservation)obs;
diuresisObs = values.FirstOrDefault(o => o.Name == "Diuresis");
if (diuresisObs == null || CheckExpired(diuresisObs)) return;
}
if (weightObs?.Value != null)
if (double.TryParse(weightObs.Value.ToString(), out var nWeight) && nWeight != 0 &&
double.TryParse(diuresisObs.Value.ToString(), out var diuresis))
{
var diruesisWeightValue = Math.Round(diuresis / nWeight, 2);
var diruesisWeight = new PatientObservation
{
Name = "Diuresis_Weight",
CodingSystem = "ADAS",
PatientId = obs.PatientId,
Time = obs.Time,
Value = diruesisWeightValue,
Units = "ml/kg"
};
await _observationService.Value.InsertObservation(diruesisWeight);
}
}
private async Task CalculateAllergiesObservation(BasePatientObservation obs)
{
try
{
var pobs = (PatientObservation)obs;
var allergiesObs = new PatientObservation
{
Name = "Allergies",
CodingSystem = "ADAS",
PatientId = obs.PatientId,
Time = obs.Time,
MessageTime = pobs.MessageTime
};
var patientAllergiesValues =
new List<PatientAllergiesValue>((IEnumerable<PatientAllergiesValue>)pobs.Value);
var allergiesValues = patientAllergiesValues.GroupBy(o => o.Type)
.Select(x =>
new PatientAllergiesValue
{
Type = x.Key,
Value = string.Join(", ", x.Select(v => v.Value)),
Notes = string.Join(", ", x.Select(n => n.Notes))
}).ToList();
var farmacosType = false;
var farmacosValues = new List<string>();
var values = new List<string>();
foreach (var allergies in allergiesValues)
{
if (allergies.Value == null)
continue;
var type = allergies.Type?.Replace("Alergia a ", "").Replace("Alergia ", "").ToUpper();
if (type != null)
switch (type)
{
case "FÁRMACOS":
farmacosType = true;
farmacosValues.Add(allergies.Value.ToUpper());
break;
default:
values.Add(type);
break;
}
}
if (farmacosType)
{
values.Add($"FÁRMACOS ({string.Join(", ", farmacosValues)})");
allergiesObs.Status = StatusEnum.Type.Alert;
}
allergiesObs.Value = string.Join(", ", values);
await _observationService.Value.InsertObservation(allergiesObs);
}
catch (InvalidCastException)
{
_logger.LogError("CalculateAllergiesObservation {obs}", obs);
}
}
private async Task CalculateDrainagesObservation(BasePatientObservation obs)
{
var pobs = (PatientObservation)obs;
var patientDraingesValue = (PatientDrainagesValue)pobs.Value;
if (patientDraingesValue.Type == "Drenaje ventricular")
{
var dve = new PatientObservation
{
Name = "DVE",
CodingSystem = "ADAS",
PatientId = obs.PatientId,
Time = obs.Time,
Units = obs.Units
};
var drainageHeight = new PatientObservation
{
Name = "Drainage_Height",
CodingSystem = "ADAS",
PatientId = obs.PatientId,
Time = obs.Time
};
if (patientDraingesValue.Volume != null)
{
dve.Value = patientDraingesValue.Volume;
await _observationService.Value.InsertObservation(dve);
}
if (patientDraingesValue.Height != null)
{
drainageHeight.Value = patientDraingesValue.Height;
await _observationService.Value.InsertObservation(drainageHeight);
}
}
}
private async Task CalculateDelta_PressureObservation(BasePatientObservation obs)
{
var toSearchList = new List<string>();
PatientObservation? peep;
PatientObservation? pleateuPressure;
toSearchList.AddRange(new List<string> { "PEEP", "Pleateu_Pressure" });
var values = await _observationService.Value.FindLastObservations(obs.PatientId, 1, toSearchList);
var time = obs.Time;
double pleateuPressureValue = 0;
if (obs.Name == "PEEP")
{
peep = (PatientObservation)obs;
pleateuPressure = values.FirstOrDefault(o => o.Name == "Pleateu_Pressure");
if (pleateuPressure?.Time != null && time.CompareTo(pleateuPressure.Time) > 0) time = pleateuPressure.Time;
}
else
{
pleateuPressure = (PatientObservation)obs;
peep = values.FirstOrDefault(o => o.Name == "PEEP");
if (peep?.Time != null && time.CompareTo(peep.Time) > 0) time = peep.Time;
}
if (peep?.Value != null && pleateuPressure?.Value != null)
{
var peepSuccess = double.TryParse(peep.Value.ToString(), out var peepValue);
if (!peepSuccess) peepValue = 0;
if (!peepSuccess) pleateuPressureValue = 0;
var deltaPressure = Math.Round(pleateuPressureValue - peepValue, 2);
var deltaPressureObs = new PatientObservation
{
Name = "Delta_Pressure",
CodingSystem = "ADAS",
PatientId = obs.PatientId,
Time = time,
Value = deltaPressure
};
await _observationService.Value.InsertObservation(deltaPressureObs);
}
}
private async Task CalculateDaily_BalanceObservation(BasePatientObservation obs)
{
if (obs.Time.ToLocalTime().Hour == 8)
{
var dailyBalanceCalculated = new PatientObservation
{
Name = "Daily_Balance_Calculated",
CodingSystem = "ADAS",
PatientId = obs.PatientId,
Time = DateTime.Now,
Units = obs.Units,
Value = ((PatientObservation)obs).Value
};
await _observationService.Value.InsertObservation(dailyBalanceCalculated);
}
}
private static bool CheckExpired(PatientObservation obs)
{
if (obs.Expires == null) return false;
var timeExpire = obs.Time.ToLocalTime().AddSeconds(Convert.ToDouble(obs.Expires));
return DateTime.Now.CompareTo(timeExpire) > 0;
}
}
@@ -0,0 +1,695 @@
using adas_core.Application.Services.Interfaces;
using adas_core.Domain.Enums;
using adas_core.Domain.Models;
using adas_core.Domain.Models.AppSettings;
using adas_core.Domain.Models.Pumps;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using MongoDB.Bson;
using MongoDB.Driver;
namespace adas_core.Application.Customizations.HUVH.UCIA;
public class CalculatedObservations : ICalculatedObservations
{
private const string CodingSystem = "ADAS";
private readonly List<string>? _antibioticList;
private readonly List<string>? _antidepressantsList;
private readonly List<string>? _antihypertensivesList;
private readonly List<string>? _antipsicoticList;
private readonly List<string>? _anxiolyticsList;
private readonly List<string>? _inotropicMedicines;
private readonly ILogger<CalculatedObservations> _logger;
private readonly Lazy<IMedicineService> _medicineService;
private readonly List<string>? _neuroMedicationList;
private readonly Lazy<IObservationService> _observationService;
private readonly List<string>? _sedationList;
private readonly List<string>? _serumColloidList;
private readonly List<string>? _serumCrystalloidList;
private readonly Lazy<ITreatmentService> _treatmentService;
public CalculatedObservations(IServiceProvider serviceProvider)
{
_treatmentService = serviceProvider.GetRequiredService<Lazy<ITreatmentService>>();
_medicineService = serviceProvider.GetRequiredService<Lazy<IMedicineService>>();
_observationService = serviceProvider.GetRequiredService<Lazy<IObservationService>>();
_logger = serviceProvider.GetRequiredService<ILogger<CalculatedObservations>>();
var apiSettings = serviceProvider.GetRequiredService<IOptions<ApiSettings>>(); //apiSettings;
_sedationList = apiSettings.Value.Sedation;
_inotropicMedicines = apiSettings.Value.InotropicMedicines;
_antibioticList = apiSettings.Value.AntibioticList;
_anxiolyticsList = apiSettings.Value.AnxiolyticList;
_antipsicoticList = apiSettings.Value.AntipsicoticList;
_antidepressantsList = apiSettings.Value.AntidepressantsList;
_neuroMedicationList = apiSettings.Value.NeuroMedicationList;
_serumCrystalloidList = apiSettings.Value.SerumCrystalloidList;
_serumColloidList = apiSettings.Value.SerumColloidList;
_antihypertensivesList = apiSettings.Value.AntihypertensivesList;
}
public Task CalculateActiveBolus(ObjectId patientId)
{
throw new NotImplementedException();
}
public async Task<T?> Map<T>(T obs, bool onlyByName = false) where T : BasePatientObservation
{
if (obs.Name is "PSI" or "RASS" or "TS") _ = CalculateOverSedation(obs);
if (obs.Name is "Inspiratory_Pressure" or "Compliancia" or "Air_Flow") _ = CalculateVentilationMode(obs);
if (obs.Name is "ECMO_Location" or "Location_Drainage_Cannula" or "Location_Return_Cannula")
obs = (T)await CalculateEcmoLocation(obs);
if (obs.Name is "EVN" or "ANI" or "ESCID") _ = CalculateOverAnalgesia(obs);
if (obs.Name is "Pmeset" or "PEEP") _ = CalculateDrivingPressure(obs);
if (obs.Name is "SpO2_FiO2_Ratio" or "FR") _ = CalculateRoxIndex(obs);
if (obs.Name is "Diuresis") _ = CalculateDiuresis(obs);
if (obs.Name is "Rehabilitation") obs = (T)await CalculateRehabilitationAlarm(obs);
return obs;
}
public async Task<PatientTreatment> Map(PatientTreatment treatment)
{
var order = treatment.PlacerOrder?.EntityIdentifier; //aquí almacenamos el número de orden
if (string.IsNullOrEmpty(order)) return treatment;
//comprobamos el estado de los tratamientos anteriores
var oldTreatments = await CheckExpiredPatientTreatments(treatment);
treatment.OrderControl = oldTreatments.Any() ? OrderControlType.Xo : OrderControlType.Nw;
if (treatment.EndTime != null)
//el tratamiento ha expirado
treatment.OrderControl = OrderControlType.Dc;
await CheckTreatmentMedicines(treatment);
return treatment;
}
public async Task<PumpObservation> Map(PumpObservation pumpObservation)
{
return await Task.FromResult(pumpObservation);
}
public Task CalculateMedicineObservation(List<Medicine> activeMedicines, ObjectId patientId)
{
//Esto viene del schedulerService para chequear los medicamentos activos
CheckMedicationCategories(patientId, activeMedicines);
return Task.CompletedTask;
}
public async Task<IEnumerable<PatientTreatment?>> GetActiveTreatmentsByPatient(ObjectId id)
{
var activeTreatments = await _treatmentService.Value.GetActiveTreatmentsByPatient(id);
return activeTreatments;
}
public Task<PatientDiagnosis> Map(PatientDiagnosis diagnosis)
{
throw new NotImplementedException();
}
public async Task<PatientObservation?> FixTimeInconsistencyWithLast(PatientObservation newObservation)
{
if (string.IsNullOrEmpty(newObservation.Name))
{
_logger.LogError(
"Error FixTimeInconsistencyWithLast. Observation name is null or empty. Observation: {newObservation}",
newObservation);
return newObservation;
}
var lastObservations = await _observationService.Value.FindLastObservations(newObservation.PatientId, 1,
[newObservation.Name]);
var lastObservation = lastObservations.FirstOrDefault();
if (lastObservation != null && DateTime.Compare(
new DateTime(lastObservation.Time.Year, lastObservation.Time.Month,
lastObservation.Time.Day, lastObservation.Time.Hour,
lastObservation.Time.Minute, lastObservation.Time.Second),
new DateTime(newObservation.Time.Year, newObservation.Time.Month,
newObservation.Time.Day, newObservation.Time.Hour,
newObservation.Time.Minute, newObservation.Time.Second)
) >= 0)
newObservation.Time = lastObservation.Time.AddSeconds(1);
return newObservation;
}
public Task<List<PatientObservation>> PreMapList(List<PatientObservation> listToInsert)
{
return Task.FromResult(listToInsert);
}
public Task<PatientObservation> MapSourceAlarm(PatientObservation obs, PatientObservationAlarm alarmToInsert)
{
return Task.FromResult(obs);
}
public Task SendAlarm(PatientObservation obs, string name, AlarmEnum.Name? code)
{
throw new NotImplementedException();
}
private async Task<BasePatientObservation> CalculateRehabilitationAlarm(BasePatientObservation obs)
{
//En rojo si el grado es de 0 a 2 incluidos, por más de 7 días
if (obs is not PatientObservation pobs || !int.TryParse(pobs.Value.ToString(), out var pobsValue) ||
pobsValue > 2) return obs;
var result = await _observationService.Value.FindLastObservations(obs.PatientId, 6, ["Rehabilitation"]);
var obsInAlert = result.Count(o =>
int.TryParse(o.Value.ToString(), out var oValue) && oValue is >= 0 and <= 2);
if (obsInAlert == 6) pobs.Status = StatusEnum.Type.Alert;
return obs;
}
private async Task CalculateDiuresis(BasePatientObservation obs)
{
// Calculado como el sumatorio de la Diuresis de las últimas 6h
// entre el último peso medido del paciente y entre 6. Medido en ml/kg/h
// Valor = sum(Diuresis últimas 6 horas) / Weight / 6
if (obs is not PatientObservation pobs)
return;
double weightValue = 0;
var numValues = pobs.Name switch
{
"Diuresis" => 2, // Si la observación entrante es "Diuresis", necesitamos las 2 últimas para completar 6h.
"Weight" => 3, // Si la observación entrante es "Weight", necesitamos las 3 últimas de "Diuresis".
_ => 0
};
if (numValues == 0)
return; // Si no es ni Diuresis ni Weight, no aplicamos cálculo.
// Obtener las observaciones de diuresis necesarias
var diuresisObservations =
await _observationService.Value.FindLastObservations(pobs.PatientId, numValues, ["Diuresis"]);
var diuresisValues = diuresisObservations
.Select(o => double.TryParse(o.Value.ToString(), out var value) ? value : 0)
.ToList();
if (pobs.Name == "Diuresis")
{
// Si la observación entrante es Diuresis, la agregamos
if (double.TryParse(pobs.Value.ToString(), out var currentDiuresis))
diuresisValues.Insert(0, currentDiuresis);
// Buscar el último peso registrado
var weightObservations =
await _observationService.Value.FindLastObservations(pobs.PatientId, 1, ["Weight"]);
var weightObs = weightObservations.FirstOrDefault();
if (weightObs != null && double.TryParse(weightObs.Value.ToString(), out var wObsValue))
weightValue = wObsValue;
}
else if (pobs.Name == "Weight" && double.TryParse(pobs.Value.ToString(), out var wValue))
{
// Si la observación entrante es Weight, asignamos su valor directamente.
weightValue = wValue;
}
// Validaciones antes de calcular
if (diuresisValues.Count < 3)
{
_logger.LogWarning("Calculated Diuresis lacks sufficient values. Required: 3, Found: {count}",
diuresisValues.Count);
return;
}
if (weightValue <= 0)
{
_logger.LogWarning("Calculated Diuresis aborted due to invalid weight value: {weight}", weightValue);
return;
}
// Cálculo del índice de diuresis
var diuresisIndex = diuresisValues.Sum() / weightValue / 6;
var diuresisObs = new PatientObservation
{
PatientId = pobs.PatientId,
Time = DateTime.UtcNow,
CodingSystem = CodingSystem,
Name = "Calculated_Diuresis",
Value = diuresisIndex
};
await _observationService.Value.InsertObservation(diuresisObs, mapObs: false);
}
private async Task CalculateRoxIndex(BasePatientObservation obs)
{
// Cálculo dividiendo el ratio SpO2/FiO2 entre la FR
// Valor = (SpO2_FiO2_Ratio) / FR
if (obs is not PatientObservation pobs)
return;
List<string>? requiredObservations = pobs.Name switch
{
"SpO2_FiO2_Ratio" => ["FR"], // Si la observación actual es SpO2_FiO2_Ratio, necesitamos FR
"FR" => ["SpO2_FiO2_Ratio"], // Si la observación actual es FR, necesitamos SpO2_FiO2_Ratio
_ => null
};
if (requiredObservations is null)
return;
var result = await _observationService.Value.FindLastObservations(pobs.PatientId, 1, requiredObservations);
var targetObs = result.FirstOrDefault();
if (targetObs == null || !double.TryParse(targetObs.Value.ToString(), out var targetValue))
return;
// Determinar los valores necesarios para el cálculo
var spo2FiO2Ratio = pobs.Name == "SpO2_FiO2_Ratio"
? double.TryParse(pobs.Value.ToString(), out var ratio) ? ratio : 0
: targetValue;
var fr = pobs.Name == "FR"
? double.TryParse(pobs.Value.ToString(), out var frValue) ? frValue : 0
: targetValue;
if (fr == 0) // Evita división por cero
return;
var roxIndexObs = new PatientObservation
{
PatientId = pobs.PatientId,
Time = DateTime.UtcNow,
CodingSystem = CodingSystem,
Name = "Rox_Index",
Value = spo2FiO2Ratio / fr
};
await _observationService.Value.InsertObservation(roxIndexObs, mapObs: false);
}
private async Task CalculateDrivingPressure(BasePatientObservation obs)
{
//Diferencia entre la Pmeset y la PEEP
//Calculo Valor = Pmeset - PEEP
try
{
if (obs is not PatientObservation pobs)
return;
var targetObservation = pobs.Name switch
{
"Pmeset" => "PEEP",
"PEEP" => "Pmeset",
_ => null
};
if (targetObservation is null)
return;
var resultList =
await _observationService.Value.FindLastObservations(obs.PatientId, 1, [targetObservation]);
var targetObs = resultList.FirstOrDefault();
if (targetObs == null || !int.TryParse(targetObs.Value.ToString(), out var targetValue))
return;
var peepValue = pobs.Name == "PEEP"
? int.TryParse(pobs.Value.ToString(), out var peep) ? peep : 0
: targetValue;
var pmesetValue = pobs.Name == "Pmeset"
? int.TryParse(pobs.Value.ToString(), out var pmeset) ? pmeset : 0
: targetValue;
var drivingPressureObs = new PatientObservation
{
PatientId = obs.PatientId,
Time = DateTime.UtcNow,
CodingSystem = CodingSystem,
Name = "Driving_Pressure",
Value = pmesetValue - peepValue
};
await _observationService.Value.InsertObservation(drivingPressureObs, mapObs: false);
}
catch (Exception e)
{
Console.WriteLine(e);
throw;
}
}
private async Task CalculateOverAnalgesia(BasePatientObservation? obs, ObjectId? patientId = null)
{
// Mandar observación 'SOBREANALGESIA'
// 0 <= EVN <= 3 o ESCID = 3 o ANI > 70
// y
// Perfusiones de morfina, remifentanilo y fentanilo sostenidas > 24h.
try
{
List<string> requiredObservations = ["EVN", "ESCID", "ANI"];
// Si patientId es null, la información proviene de una observación.
// Si tiene valor, proviene de un tratamiento.
if (!patientId.HasValue)
{
if (obs is not PatientObservation pobs || !int.TryParse(pobs.Value.ToString(), out _))
return;
patientId = pobs.PatientId;
}
var observations =
await _observationService.Value.FindLastObservations(patientId.Value, 1, requiredObservations);
// Verificar si alguna observación cumple la condición
var condition1 = observations.Any(observation =>
int.TryParse(observation.Value.ToString(), out var obsValue) && observation.Name switch
{
"EVN" when obsValue is >= 0 and <= 3 => true,
"ESCID" when obsValue == 3 => true,
"ANI" when obsValue > 70 => true,
_ => false
});
if (!condition1)
return;
// Verificar tratamientos activos y umbral de analgesia
var treatments = await _treatmentService.Value.GetActiveTreatmentsByPatient(patientId.Value);
var patientTreatments = treatments.ToList();
if (!patientTreatments.Any()) return;
var morfina =
patientTreatments.FirstOrDefault(t => t != null && t.RequestedGiveCodes.Any(c => c.Text is "Morfina"));
var remifentanilo = patientTreatments.FirstOrDefault(t =>
t != null && t.RequestedGiveCodes.Any(c => c.Text is "Remifentanilo"));
var fentanilo =
patientTreatments.FirstOrDefault(t =>
t != null && t.RequestedGiveCodes.Any(c => c.Text is "Fentanilo"));
if (morfina != null && !ExceedsAnalgesiaThreshold(morfina) &&
remifentanilo != null && !ExceedsAnalgesiaThreshold(remifentanilo) &&
fentanilo != null && !ExceedsAnalgesiaThreshold(fentanilo))
return;
var overAnalgesiaObs = new PatientObservation
{
PatientId = patientId.Value,
Time = DateTime.UtcNow,
CodingSystem = CodingSystem,
Name = "Over_Analgesia",
Value = bool.TrueString
};
await _observationService.Value.InsertObservation(overAnalgesiaObs, mapObs: false);
}
catch (Exception ex)
{
_logger.LogError("Error calculating Over_Analgesia. Exception: {ex}", ex);
throw;
}
}
private Task<BasePatientObservation> CalculateEcmoLocation(BasePatientObservation obs)
{
//Las opciones pueden ser VV, VA o ECMO.
//Cuando llegue VV se muestra VV.
//Cuando llegue VA se muestra VA.
//Cuando llegue los siguientes parámetros, se debe mostrar ECMO: ECCO2r, VVDL, VA+V, VVDL+V, VV+V, VVA
if (obs is not PatientObservation pobs)
return Task.FromResult(obs);
var ecmoValues = new List<string> { "ECCO2r", "VVDL", "VA+V", "VVDL+V", "VV+V", "VVA" };
var obsValue = pobs.Value.ToString();
if (obsValue != null && ecmoValues.Contains(obsValue)) pobs.Value = "ECMO";
return Task.FromResult(obs);
}
private async Task CalculateVentilationMode(BasePatientObservation obs)
{
//- Si llega valor de PI pero NO COMPL, el tipo de ventilación es VMNI
//- Si llega valor de PS pero no llega COMPL, el tipo de ventilación es VMNI
//- Si llega COMPL, el tipo de ventilación es VMI
//- Si llega Flujo, leer el tipo de ventilación de CCC(CNAF o CTAF)
var mode = string.Empty;
switch (obs.Name)
{
case "Inspiratory_Pressure":
var complObs = await _observationService.Value.FindLastObservations(obs.PatientId, 1, ["Compliancia"]);
if (!complObs.Any())
mode = "VMNI";
break;
case "Compliancia":
mode = "VMI";
break;
case "Air_Flow":
var cccVentMode =
await _observationService.Value.FindByPatientIdAndCodingSystemAsync(obs.PatientId, "CCC",
"Ventilation_Mode");
if (!await cccVentMode.AnyAsync())
return;
mode = cccVentMode.First().Value.ToString() ?? string.Empty;
break;
default:
return;
}
if (string.IsNullOrEmpty(mode)) return;
var ventilationModeObservation = new PatientObservation
{
PatientId = obs.PatientId,
Time = DateTime.UtcNow,
CodingSystem = CodingSystem,
Name = "Ventilation_Mode",
Value = mode
};
_ = _observationService.Value.InsertObservation(ventilationModeObservation, mapObs: false);
}
private async Task CalculateOverSedation(BasePatientObservation? obs, ObjectId? patientId = null)
{
//Mandar observación 'SOBRESEDACIÓN'
//cuando:
//(RASS =-4 o RASS =-5) y
//PSI < 25 y
//TS > 5 y
//(Propofol > 3 o Midazolam > 0,05 ó Isoflorano > 10)
try
{
var pobsName = string.Empty;
//si patientId viene null viene la observación
//Si tiene valor viene de un tratamiento
if (!patientId.HasValue)
{
if (obs is not PatientObservation pobs || !int.TryParse(pobs.Value.ToString(), out var pobsValue))
return;
patientId = pobs.PatientId;
pobsName = pobs.Name;
switch (pobsName)
{
case "RASS" when pobsValue is not (-4 or -5):
case "PSI" when pobsValue >= 25:
case "TS" when pobsValue <= 5:
return;
}
}
var requiredObservations = pobsName switch
{
"RASS" => ["PSI", "TS"],
"PSI" => ["RASS", "TS"],
"TS" => ["RASS", "PSI"],
_ => new List<string> { "RASS", "PSI", "TS" }
};
var result = await _observationService.Value.FindLastObservations(patientId.Value, 1, requiredObservations);
var observations = result.ToDictionary(r => r.Name?.ToString() ?? "null");
if ((pobsName != "RASS" && (!observations.TryGetValue("RASS", out var rassObs) ||
!int.TryParse(rassObs.Value.ToString(), out var rassValue) ||
rassValue is not (-4 or -5))) ||
(pobsName != "PSI" && (!observations.TryGetValue("PSI", out var psiObs) ||
!int.TryParse(psiObs.Value.ToString(), out var psiValue) || psiValue >= 25)) ||
(pobsName != "TS" && (!observations.TryGetValue("TS", out var tsObs) ||
!int.TryParse(tsObs.Value.ToString(), out var tsValue) || tsValue <= 5)))
return;
var treatments = await _treatmentService.Value.GetActiveTreatmentsByPatient(patientId.Value);
if (!treatments.Any(ExceedsSedationThreshold)) return;
var overSedationObs = new PatientObservation
{
PatientId = patientId.Value,
Time = DateTime.UtcNow,
CodingSystem = CodingSystem,
Name = "Over_Sedation",
Value = bool.TrueString
};
await _observationService.Value.InsertObservation(overSedationObs, mapObs: false);
}
catch (Exception ex)
{
_logger.LogError("Error calculating Over_Sedation. Exception: {ex}", ex);
throw;
}
}
private static bool ExceedsAnalgesiaThreshold(PatientTreatment? treatment)
{
// Perfusiones de morfina, remifentanilo o fentanilo sostenidas > 24 h
return treatment is { StartTime: not null } &&
treatment.StartTime.Value.AddSeconds(86400) < DateTime.UtcNow;
}
private static bool ExceedsSedationThreshold(PatientTreatment? treatment)
{
return (treatment != null &&
treatment.RequestedGiveCodes.Any(c =>
c.Text == "Propofol" && treatment.RequestedGiveAmountMinimum > 3)) ||
(treatment != null && treatment.RequestedGiveCodes.Any(c =>
c.Text == "Midazolam" && treatment.RequestedGiveAmountMinimum > 0.05)) ||
(treatment != null && treatment.RequestedGiveCodes.Any(c =>
c.Text == "Isoflorano" && treatment.RequestedGiveAmountMinimum > 10));
}
private async Task<List<PatientTreatment>> CheckExpiredPatientTreatments(PatientTreatment treatment)
{
//si el tratamiento ha expirado actualizamos el OrderControl a DC y devolvemos las que siguen activas
var result = new List<PatientTreatment>();
var oldTreatments = await _treatmentService.Value.GetActiveTreatmentsByPatient(treatment.PatientId);
foreach (var t in oldTreatments.Where(t => t != null))
if (t is { EndTime: not null } && t.EndTime > DateTime.UtcNow)
{
t.OrderControl = OrderControlType.Dc;
_ = _treatmentService.Value.UpdateTreatment(t);
}
else if (t != null)
{
result.Add(t);
}
return result;
}
private async Task CheckTreatmentMedicines(PatientTreatment treatment)
{
var newMedicines = (await Task.WhenAll(treatment.RequestedGiveCodes
.Select(async code => await _medicineService.Value.GetByCode(code.Identifier))))
.Where(m => m != null)
.ToList();
if (newMedicines.Count == 0) return;
var activeTreatments = (await GetActiveTreatmentsByPatient(treatment.PatientId)).ToList();
var activeMedicines = (await _medicineService.Value.GetMedicinesOfTreatments(activeTreatments)).ToList();
// Combina ambas listas y selecciona solo elementos únicos con el mismo nombre
var uniqueMedicines = newMedicines
.Concat(activeMedicines)
.GroupBy(m => m?.Name)
.Select(g => g.First())
.ToList();
// uniqueMedicines contiene la lista de medicamentos activos de un paciente
CheckMedicationCategories(treatment.PatientId, uniqueMedicines as List<Medicine>);
}
private void CheckMedicationCategories(ObjectId patientId, List<Medicine> uniqueMedicines)
{
var medicationCategories = new Dictionary<string, List<string>?>
{
{ "Sedation_Medication_Multivalue", _sedationList },
{ "Inotropic_Medication_Multivalue", _inotropicMedicines },
{ "Antibiotic_Medication_Multivalue", _antibioticList },
{ "Anxiolytic_Medication_Multivalue", _anxiolyticsList },
{ "Antipsychotic_Medication_Multivalue", _antipsicoticList },
{ "Antidepressants_Medication_Multivalue", _antidepressantsList },
{ "Neuro_Medication_Multivalue", _neuroMedicationList },
{ "Crystalloid_Serum_Medication_Multivalue", _serumCrystalloidList },
{ "Colloid_Serum_Medication_Multivalue", _serumColloidList },
{ "Antihypertensives_Medication_Multivalue", _antihypertensivesList }
};
foreach (var category in medicationCategories)
{
var observations = uniqueMedicines?
.Where(m => category.Value != null && !string.IsNullOrEmpty(m.Name) && m.Name != null &&
category.Value.Contains(m.Name))
.ToList();
if (observations != null && observations.Any())
_ = CreateMedicationMultivalueObservation(observations, patientId, category.Key);
}
}
private async Task CreateMedicationMultivalueObservation(IEnumerable<Medicine?> medications, ObjectId patientId,
string obsName)
{
var newObs = new PatientObservation
{
PatientId = patientId,
Name = obsName,
CodingSystem = CodingSystem,
Value = medications.Select(m => m?.Name).ToArray(),
Time = DateTime.Now
};
var lastObsList = await _observationService.Value.FindLastObservations(patientId, 1, [obsName]);
var lastObs = lastObsList.FirstOrDefault();
if (lastObs is { Expired: false })
{
//expiramos la anterior
lastObs.Expired = true;
_ = _observationService.Value.UpdateObservation(lastObs);
}
await _observationService.Value.InsertObservation(newObs,
mapObs: false); //mapObs:no volvemos a mapear la observación
}
}
@@ -0,0 +1,711 @@
using adas_core.Application.Services.Interfaces;
using adas_core.Domain.Enums;
using adas_core.Domain.Models;
using adas_core.Domain.Models.AppSettings;
using adas_core.Domain.Models.Pumps;
using adas_core.Domain.Utils;
using adas_core.Domain.Utils.Interfaces;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using MongoDB.Bson;
namespace adas_core.Application.Customizations.HUVH.UCIN;
public class CalculatedObservations : ICalculatedObservations
{
private const string Spo2PreObservationName = "SpO2";
private const string Spo2PostObservationName = "SpO2_Post";
private const string Spo2PrePostObservationName = "SpO2_Pre_Post";
private const string CodingSystem = "ADAS";
private readonly List<string> _complexityObservations =
[
"Respiratory_Device_Multivalue", "Intravenous_Routes_Multivalue", "Medication_Multivalue",
"System_Multivalue", "Newborn_Weight", "Surgery_Multivalue"
];
private readonly ILogger<CalculatedObservations> _logger;
private readonly IMappingUtils _mappingUtils;
private readonly Lazy<IMedicineService> _medicineService;
private readonly Lazy<IObservationService> _observationService;
private readonly Lazy<ITreatmentService> _treatmentService;
public CalculatedObservations(IServiceProvider serviceProvider)
{
_treatmentService = serviceProvider.GetRequiredService<Lazy<ITreatmentService>>();
_medicineService = serviceProvider.GetRequiredService<Lazy<IMedicineService>>();
_observationService = serviceProvider.GetRequiredService<Lazy<IObservationService>>();
_logger = serviceProvider.GetRequiredService<ILogger<CalculatedObservations>>();
var apiSettings = serviceProvider.GetRequiredService<IOptions<ApiSettings>>(); //apiSettings;
_mappingUtils = new MappingUtils(apiSettings);
}
public Task CalculateActiveBolus(ObjectId patientId)
{
throw new NotImplementedException();
}
public async Task<T?> Map<T>(T obs, bool onlyByName = false) where T : BasePatientObservation
{
if (obs.Name is "Intervention_In" or "Intervention_Out") await CalculateInterventionMultiValueObservation(obs);
if (obs.Name is "Surgery") await CalculateMultiValueObservation(obs);
if (obs.Name != null && _complexityObservations.Contains(obs.Name))
{
_logger.LogDebug("Mapping observation Complexity {obs}", obs);
//TODO ver si hay un máximo de puntuación por grupo de observaciones
_ = CalculateComplexity(obs);
}
if (obs.Name is Spo2PreObservationName or Spo2PostObservationName)
{
_logger.LogDebug("Pre-post saturation observation {obs}", obs);
_ = CalculateSaturation_DiffObservation(obs);
}
if (obs.Name is "ALPS" or "NPASS_Sedation" or "NPASS_Analgesia") _ = CalculatePainScale(obs);
if (obs.Name is "Last_Defecation") obs = (T)CalculateLastDefecationValue(obs);
return obs;
}
public async Task<PatientTreatment> Map(PatientTreatment treatment)
{
var order = treatment.PlacerOrder?.EntityIdentifier; //aquí almacenamos el número de orden
if (string.IsNullOrEmpty(order)) return treatment;
//comprobamos el estado de los tratamientos anteriores
var oldTreatments = await CheckExpiredPatientTreatments(treatment);
treatment.OrderControl = oldTreatments.Any() ? OrderControlType.Xo : OrderControlType.Nw;
if (treatment.EndTime != null)
//el tratamiento ha expirado
treatment.OrderControl = OrderControlType.Dc;
await CheckTreatmentMedicines(treatment);
return treatment;
}
public async Task<PumpObservation> Map(PumpObservation pumpObservation)
{
return await Task.FromResult(pumpObservation);
}
public async Task CalculateMedicineObservation(List<Medicine> activeMedicines, ObjectId patientId)
{
var medicineObs = new PatientObservation
{
PatientId = patientId,
Name = "Medication_Multivalue",
CodingSystem = CodingSystem,
Value = activeMedicines.Select(m => m.Name ?? string.Empty).Distinct().ToArray(),
Time = DateTime.Now
};
var lastMedicationsObs =
await _observationService.Value.FindLastObservations(patientId, 1, ["Medication_Multivalue"]);
var lastMedicationObs = lastMedicationsObs.FirstOrDefault();
if (lastMedicationObs != null)
{
//expiramos la anterior
lastMedicationObs.Expired = true;
_ = _observationService.Value.UpdateObservation(lastMedicationObs);
}
await _observationService.Value
.InsertObservation(
medicineObs); //mapObs:volvemos a mapear la observación para que pase por el cálculo de la complejidad
}
public async Task<IEnumerable<PatientTreatment?>> GetActiveTreatmentsByPatient(ObjectId id)
{
var activeTreatments = await _treatmentService.Value.GetActiveTreatmentsByPatient(id);
return activeTreatments;
}
public Task<PatientDiagnosis> Map(PatientDiagnosis diagnosis)
{
throw new NotImplementedException();
}
public async Task<PatientObservation?> FixTimeInconsistencyWithLast(PatientObservation newObservation)
{
if (string.IsNullOrEmpty(newObservation.Name))
{
_logger.LogError(
"Error FixTimeInconsistencyWithLast. Observation name is null or empty. Observation: {newObservation}",
newObservation);
return newObservation;
}
var lastObservations = await _observationService.Value.FindLastObservations(newObservation.PatientId, 1,
[newObservation.Name]);
var lastObservation = lastObservations.FirstOrDefault();
if (lastObservation != null && DateTime.Compare(
new DateTime(lastObservation.Time.Year, lastObservation.Time.Month,
lastObservation.Time.Day, lastObservation.Time.Hour,
lastObservation.Time.Minute, lastObservation.Time.Second),
new DateTime(newObservation.Time.Year, newObservation.Time.Month,
newObservation.Time.Day, newObservation.Time.Hour,
newObservation.Time.Minute, newObservation.Time.Second)
) >= 0)
newObservation.Time = lastObservation.Time.AddSeconds(1);
return newObservation;
}
public Task<List<PatientObservation>> PreMapList(List<PatientObservation> listToInsert)
{
return Task.FromResult(listToInsert);
}
public Task<PatientObservation> MapSourceAlarm(PatientObservation obs, PatientObservationAlarm alarmToInsert)
{
return Task.FromResult(obs);
}
public Task SendAlarm(PatientObservation obs, string name, AlarmEnum.Name? code)
{
throw new NotImplementedException();
}
private async Task CalculatePainScale(BasePatientObservation obs)
{
if (obs is not PatientObservation pobs) return;
object valueObs;
switch (pobs.Name)
{
case "ALPS":
valueObs = pobs.Value;
break;
case "NPASS_Sedation":
{
//Buscamos la complementaria "NPASS Analgesia"
var analgesiaObs =
await _observationService.Value.FindLastObservations(obs.PatientId, 1, ["NPASS_Analgesia"]);
var analgesiaValue = analgesiaObs.FirstOrDefault()?.Value.ToString() ?? "0";
valueObs = $"{pobs.Value}/{analgesiaValue}";
}
break;
case "NPASS_Analgesia":
{
//Buscamos la complementaria "NPASS Sedation"
var sedationObs =
await _observationService.Value.FindLastObservations(obs.PatientId, 1, ["NPASS_Sedation"]);
var sedationValue = sedationObs.FirstOrDefault()?.Value.ToString() ?? "0";
valueObs = $"{sedationValue}/{pobs.Value}";
}
break;
default:
return;
}
var painScaleObs = new PatientObservation
{
PatientId = obs.PatientId,
Time = DateTime.UtcNow,
CodingSystem = "ADAS",
Name = "Pain_Scale",
Value = valueObs
};
_ = _observationService.Value.InsertObservation(painScaleObs, mapObs: false);
}
private static BasePatientObservation CalculateLastDefecationValue(BasePatientObservation obs)
{
if (obs is not PatientObservation pobs) return obs;
//Assign obs time to value
//Convert the UTC DateTime to local time.
var localTime = pobs.Time.ToLocalTime();
//Format the local time into a short date/time string.
pobs.Value = localTime.ToString("dd/MM/yyyy HH:mm");
return pobs;
}
private async Task CalculateMultiValueObservation(BasePatientObservation obs)
{
//El valor de la observación es un array de strings
if (obs is not PatientObservation pobs || string.IsNullOrEmpty(obs.Name)) return;
var newObsName = $"{obs.Name}_Multivalue";
var actualObsInBd =
await _observationService.Value.FindLastObservations(obs.PatientId, 1, [newObsName]);
var newValue = new List<string>();
if (pobs.Value is not string stringValueName) return;
if (actualObsInBd.Count > 0)
if (actualObsInBd.First().Value is string[] oldValue)
{
oldValue = oldValue.Where(x => x != stringValueName).ToArray();
newValue.AddRange(oldValue);
}
newValue.Add(stringValueName);
if (newValue.Count == 0) return;
//Expiramos la anterior
var actualObs = actualObsInBd.FirstOrDefault();
if (actualObs != null)
{
actualObs.Expired = true;
await _observationService.Value.UpdateObservation(actualObs);
}
var newObservation = new PatientObservation
{
PatientId = obs.PatientId,
Time = DateTime.UtcNow,
CodingSystem = CodingSystem,
Name = newObsName,
Value = newValue.ToArray()
};
_logger.LogDebug(
"Calculate {name}_Multivalue {outpatient} Insert Calculated {time} value {value}",
newObsName, newObservation.PatientId, newObservation.Time,
newObservation.Value.ToJson());
await _observationService.Value.InsertObservation(newObservation);
}
private async Task CalculateInterventionMultiValueObservation(BasePatientObservation obs)
{
if (obs is not PatientObservation pobs || string.IsNullOrEmpty(obs.Name) ||
pobs.Value is not string valueObs) return;
if (!GetInterventionValue(valueObs, out var r) || r == null) return;
var obsType = r.Value.type; //"Intravenous_Routes";
var obsGroup = r.Value.Name; // "Vía arterial";
var newObsName = $"{obsType}_Multivalue";
// Obtener la última observación multivalue
var result = await _observationService.Value.FindLastObservations(obs.PatientId, 1, [newObsName]);
var actualMultiValueObsInDb = result.FirstOrDefault(o => !o.Expired);
var oldValue = actualMultiValueObsInDb?.Value as string[] ?? [];
var newValue = new List<string>(oldValue);
if (obs.Name.Contains("_In"))
{
//Por seguridad solo tomamos el último valor que nos llega
//Puede ser que no se acuerden de cancelar el anterior antes de añadir un nuevo dispositivo y no puede haber dos dispositivos del mismo tipo activos al mismo tiempo
if (obsType == "Respiratory_Device")
newValue.Clear();
// Inserción dispositivo
if (!oldValue.Contains(obsGroup))
newValue.Add(obsGroup);
}
else if (obs.Name.Contains("_Out"))
{
// Retirada del dispositivo
//Buscamos la inserción
var name = $"{obs.Name.Split('_')[0]}_In";
var inObs = await _observationService.Value.FindLastNotExpiredObservatonsByPatient(obs.PatientId, name);
var sameValueObs = inObs.Where(o => o.Value == pobs.Value).ToList();
if (sameValueObs.Any())
// Si hay más de una observación del mismo tipo, no eliminar del multivalue
if (sameValueObs.Count > 1)
return;
// Actualizar valor eliminando el grupo
newValue.Remove(obsGroup);
}
else
{
return;
}
//Expiramos la anterior
if (actualMultiValueObsInDb is { Expired: false })
{
actualMultiValueObsInDb.Expired = true;
await _observationService.Value.UpdateObservation(actualMultiValueObsInDb);
}
// Si no hay valores nuevos, no se crea una nueva observación
if (!newValue.Any())
return;
// Crear y registrar nueva observación
var newObservation = new PatientObservation
{
PatientId = obs.PatientId,
Time = DateTime.UtcNow,
CodingSystem = CodingSystem,
Name = newObsName,
Value = newValue.ToArray()
};
_logger.LogDebug(
"Calculate {name}_Multivalue {patientid} Insert Calculated {time} value {value}",
newObsName, newObservation.PatientId, newObservation.Time,
newObservation.Value.ToJson()
);
await _observationService.Value.InsertObservation(newObservation);
}
private bool GetInterventionValue(string valueObs, out (string type, string Name, string Group)? r)
{
var code = valueObs.Split(" ")[0]; //
if (code.EndsWith(".")) // Verifica si termina con un punto
code = code.Substring(0, code.Length - 1).Replace('.', ','); // Remueve el último carácter
if (!double.TryParse(code, out var codeParsed))
{
r = null;
return false;
}
r = _mappingUtils.SearchByCode(codeParsed, "Intervention"); //TODO validar este parámetro con Lola
return r != null;
}
/**
* Calculamos la complejidad basándonos en el valor de Respiratorio/Vías/Medicación/Sistemas/Peso nacimiento/cirugía
* cuando entra una nueva observación que afecte a complejidad, se recalcula de 0 para no tener que estar pendiente de qué puntos corresponden a qué observación
* Valor máximo 46 Guardamos la observación que nos llega y recalculamos complejidad.
*/
private async Task CalculateComplexity(BasePatientObservation obs)
{
var pobs = obs as PatientObservation;
if (pobs == null) return;
var complexity = new PatientObservation
{
PatientId = obs.PatientId,
Time = DateTime.UtcNow,
CodingSystem = CodingSystem,
Name = "Complexity",
Value = 0
};
var complexityValue = 0;
// Diccionario para manejar las operaciones basadas en el nombre de la observación
var operations = new Dictionary<string, Func<ObjectId, PatientObservation?, Task<int>>>
{
{
"Respiratory_Device_Multivalue",
(patientId, observation) =>
CalculateMultivalueObservationComplexityValue(patientId, "Respiratory_Device_Multivalue",
observation)
},
{
"Intravenous_Routes_Multivalue",
(patientId, observation) =>
CalculateMultivalueObservationComplexityValue(patientId, "Intravenous_Routes_Multivalue",
observation)
},
{
"Medication_Multivalue",
(patientId, observation) =>
CalculateMultivalueObservationComplexityValue(patientId, "Medication_Multivalue", observation)
},
{
"System_Multivalue",
(patientId, observation) =>
CalculateMultivalueObservationComplexityValue(patientId, "System_Multivalue", observation)
},
{ "Newborn_Weight", CalculateWeightNewBornValue },
{
"Surgery_Multivalue",
(patientId, observation) =>
CalculateMultivalueObservationComplexityValue(patientId, "Surgery_Multivalue", observation)
}
};
// Realizar cálculos iterando sobre las operaciones
foreach (var operation in operations)
complexityValue += await operation.Value(pobs.PatientId, operation.Key.Equals(pobs.Name) ? pobs : null);
complexity.Value = Math.Min(complexityValue, 46); // Limitar el valor máximo a 46
_logger.LogDebug(
"complexity for patient {patientid} => inserting complexity value: {complexityValue} for patient: {complexityPatientid} at time {fixedTimeComplexityTime}",
complexity.PatientId, complexity.Value, complexity.PatientId, complexity.Time);
await _observationService.Value.InsertObservation(complexity, mapObs: false);
}
private async Task<int> CalculateMultivalueObservationComplexityValue(ObjectId patientId, string observationName,
PatientObservation? observation = null)
{
var result =
await _observationService.Value.FindLastObservations(patientId, 1, [observationName]);
var actualObsInBd = result.FirstOrDefault();
var valueObj = observation == null ? actualObsInBd?.Value : observation.Value;
var valueList = valueObj as string[];
var valueToReturn = valueList?.Sum(v => _mappingUtils.GetComplexityValue(v)) ?? 0;
_logger.LogDebug(
"complexity for patient {patientid} => observation: {observationName} plus complexity: {Value}", patientId,
observationName, valueToReturn);
return valueToReturn;
}
private async Task<int> CalculateWeightNewBornValue(ObjectId patientId, PatientObservation? pobs = null)
{
var result =
await _observationService.Value.FindLastObservations(patientId, 1, ["Newborn_Weight"]);
var actualObsInBd = result.FirstOrDefault();
string? strValue;
string? obsName;
if (pobs == null)
{
if (actualObsInBd?.Value == null) return 0;
strValue = actualObsInBd.Value.ToString();
obsName = actualObsInBd.Name;
}
else
{
if (pobs.Units == "kg") pobs = ParseWeight(pobs);
strValue = pobs.Value.ToString();
obsName = pobs.Name;
}
if (!double.TryParse(strValue, out var valueParsed) || obsName == null)
return 0;
var weightNewBornValue = _mappingUtils.GetComplexityValue(obsName, valueParsed);
_logger.LogDebug(
"complexity for patient {patientid} => Weight_Newborn plus complexity: {Weight_NewbornValue}",
patientId, weightNewBornValue);
return weightNewBornValue;
}
private PatientObservation ParseWeight(PatientObservation obs)
{
if (!double.TryParse(obs.Value.ToString(), out var dValue))
{
_logger.LogError("Error casting weight Observation {obs}:", obs);
return obs;
}
obs.Units = "gr";
obs.Value = dValue * 1000;
return obs;
}
private async Task CalculateSaturation_DiffObservation(BasePatientObservation obs)
{
var toSearchList = new List<string>();
PatientObservation? pre;
PatientObservation? post;
toSearchList.AddRange(new List<string> { Spo2PreObservationName, Spo2PostObservationName });
var values = await _observationService.Value.FindLastObservations(obs.PatientId, 1, toSearchList);
var time = obs.Time;
if (obs.Name == Spo2PreObservationName)
{
pre = (PatientObservation)obs;
post = values.FirstOrDefault(o => o.Name == Spo2PostObservationName);
if (post?.Time != null && time.CompareTo(post.Time) > 0) time = post.Time;
}
else
{
post = (PatientObservation)obs;
pre = values.FirstOrDefault(o => o.Name == Spo2PreObservationName);
if (pre?.Time != null && time.CompareTo(pre.Time) > 0) time = pre.Time;
}
if (pre?.Value != null && post?.Value != null)
{
var preSuccess = double.TryParse(pre.Value.ToString(), out var preValue);
if (!preSuccess) preValue = 0;
var postSuccess = double.TryParse(post.Value.ToString(), out var postValue);
if (!postSuccess) postValue = 0;
var saturationDiff = Math.Round(preValue - postValue, 2);
var saturationDiffObs = new PatientObservation
{
Name = "SpO2_Diff",
CodingSystem = CodingSystem,
PatientId = obs.PatientId,
Time = time,
Value = saturationDiff
};
var saturationPrePost = new PatientObservation
{
Name = Spo2PrePostObservationName,
CodingSystem = CodingSystem,
PatientId = obs.PatientId,
Time = time,
Value = pre.Value + "/" + post.Value
};
await _observationService.Value.InsertObservation(saturationDiffObs);
await _observationService.Value.InsertObservation(saturationPrePost);
}
}
private async Task<List<PatientTreatment>> CheckExpiredPatientTreatments(PatientTreatment treatment)
{
//si el tratamiento ha expirado actualizamos el OrderControl a DC y devolvemos las que siguen activas
var result = new List<PatientTreatment>();
var oldTreatments = await _treatmentService.Value.GetActiveTreatmentsByPatient(treatment.PatientId);
foreach (var t in oldTreatments)
{
if (t == null) continue;
if (t.PlacerOrder?.EntityIdentifier == treatment.PlacerOrder?.EntityIdentifier)
{
//Es el mismo tratamiento actualizamos
if (t.EndTime != null && t.EndTime > DateTime.UtcNow) treatment.OrderControl = OrderControlType.Dc;
treatment.Id = t.Id; //Asignamos el ID del tratamiento existente para actualizarlo
_ = _treatmentService.Value.UpdateTreatment(treatment);
}
else
{
result.Add(t);
}
}
return result;
}
private async Task CheckTreatmentMedicines(PatientTreatment treatment)
{
var newMedicines = new List<Medicine>();
var hasVasoactives = false;
foreach (var code in treatment.RequestedGiveCodes)
{
var m = await _medicineService.Value.GetByCode(code.Identifier);
if (m == null) continue;
//Si el código es de tipo nutrición entonces creamos una observación de ese tipo que tendrá el valor mapeado con el nombre correspondiente
//CreateNutritionObservation y NO agregamos a medicamentos
var r = _mappingUtils.SearchByCode(m.Codes.First(), "Treatment");
if (r is { type: "Nutrition" })
{
await CreateNutritionObservation(r.Value.group, treatment.PatientId);
continue;
}
if (r is { type: "Medication", group: "Drogas vasoactivas" })
hasVasoactives = true;
newMedicines.Add(m);
}
if (hasVasoactives)
{
var vObs = new PatientObservation
{
Name = "HasVasoactive",
Value = true,
Time = DateTime.UtcNow,
CodingSystem = CodingSystem
};
await _observationService.Value.InsertObservation(vObs, mapObs: false);
}
if (newMedicines.Count == 0)
return;
var _ = await GetActiveTreatmentsByPatient(treatment.PatientId);
var activeTreatments = _.ToList();
var __ = await _medicineService.Value.GetMedicinesOfTreatments(activeTreatments); //Todo revisar
var activeMedicines = __.ToList();
// Combina ambas listas y selecciona solo elementos únicos con el mismo nombre
var uniqueMedicines = newMedicines
.Concat(activeMedicines)
.GroupBy(m => m.Name)
.Select(g => g.First())
.ToList();
await CalculateMedicineObservation(uniqueMedicines, treatment.PatientId);
}
private async Task CreateNutritionObservation(string value, ObjectId patientId)
{
var nutritionObs = new PatientObservation
{
PatientId = patientId,
Name = "Feeding_Type",
CodingSystem = CodingSystem,
Value = value,
Time = DateTime.Now
};
if (value.ToLower().Contains("parenteral"))
{
nutritionObs.Name = "Parenteral";
nutritionObs.Value = "SI";
}
var lastNutritionObsList =
await _observationService.Value.FindLastObservations(patientId, 1, [nutritionObs.Name]);
var lastNutritionObs = lastNutritionObsList.FirstOrDefault();
if (lastNutritionObs is { Expired: false })
{
//expiramos la anterior
lastNutritionObs.Expired = true;
_ = _observationService.Value.UpdateObservation(lastNutritionObs);
}
await _observationService.Value.InsertObservation(nutritionObs);
}
}
@@ -0,0 +1,71 @@
using adas_core.Application.Services.Interfaces;
using adas_core.Domain.Enums;
using adas_core.Domain.Models;
using adas_core.Domain.Models.Pumps;
using Microsoft.Extensions.DependencyInjection;
using MongoDB.Bson;
namespace adas_core.Application.Customizations.NursePlan;
public class CalculatedObservations(IServiceProvider serviceProvider) : ICalculatedObservations
{
private readonly Lazy<IObservationService> _observationService =
new(serviceProvider.GetRequiredService<IObservationService>);
public Task CalculateActiveBolus(ObjectId patientId)
{
throw new NotImplementedException();
}
public async Task<PumpObservation> Map(PumpObservation pumpObservation)
{
return await Task.FromResult(pumpObservation);
}
public Task CalculateMedicineObservation(List<Medicine> activeMedicines, ObjectId patientId)
{
throw new NotImplementedException();
}
public Task<PatientObservation?> FixTimeInconsistencyWithLast(PatientObservation newObservation)
{
throw new NotImplementedException();
}
public Task<IEnumerable<PatientTreatment?>> GetActiveTreatmentsByPatient(ObjectId id)
{
throw new NotImplementedException();
}
public Task<T?> Map<T>(T obs, bool onlyByName = false) where T : BasePatientObservation
{
return Task.FromResult(obs)!;
}
public Task<PatientTreatment> Map(PatientTreatment treatment)
{
return Task.FromResult(treatment);
}
public Task<PatientDiagnosis> Map(PatientDiagnosis diagnosis)
{
return Task.FromResult(diagnosis);
}
public Task<PatientObservation> MapSourceAlarm(PatientObservation obs, PatientObservationAlarm alarmToInsert)
{
return Task.FromResult(obs);
}
public Task SendAlarm(PatientObservation obs, string name, AlarmEnum.Name? code)
{
return Task.CompletedTask;
}
public Task<List<PatientObservation>> PreMapList(List<PatientObservation> listToInsert)
{
return Task.FromResult(listToInsert);
}
}
@@ -0,0 +1,3 @@
namespace adas_core.Application.Exceptions;
public class ApiRequestException(string message) : Exception($"Api request exception: {message}");
@@ -0,0 +1,14 @@
using adas_core.Domain.Enums;
namespace adas_core.Application.Exceptions;
public class BadRequestException : Exception
{
public BadRequestException(HttpEnum.ErrorMessage message) : base(((int)message).ToString())
{
}
public BadRequestException(string message) : base(message)
{
}
}
@@ -0,0 +1,14 @@
using adas_core.Domain.Enums;
namespace adas_core.Application.Exceptions;
public class ConflictException : Exception
{
public ConflictException(HttpEnum.ErrorMessage message) : base(((int)message).ToString())
{
}
public ConflictException(string message) : base(message)
{
}
}
@@ -0,0 +1,14 @@
using adas_core.Domain.Enums;
namespace adas_core.Application.Exceptions;
public class CustomArgumentException : Exception
{
public CustomArgumentException(HttpEnum.ErrorMessage message) : base(((int)message).ToString())
{
}
public CustomArgumentException(string message) : base(message)
{
}
}
@@ -0,0 +1,14 @@
using adas_core.Domain.Enums;
namespace adas_core.Application.Exceptions;
public class ForbbidenException : Exception
{
public ForbbidenException(HttpEnum.ErrorMessage message) : base(((int)message).ToString())
{
}
public ForbbidenException(string message) : base(message)
{
}
}
@@ -0,0 +1,14 @@
using adas_core.Domain.Enums;
namespace adas_core.Application.Exceptions;
public class InvalidFormatException : Exception
{
public InvalidFormatException(HttpEnum.ErrorMessage message) : base(((int)message).ToString())
{
}
public InvalidFormatException(string message) : base(message)
{
}
}
@@ -0,0 +1,18 @@
using adas_core.Domain.Enums;
namespace adas_core.Application.Exceptions;
public class NotFoundException : Exception
{
public NotFoundException()
{
}
public NotFoundException(HttpEnum.ErrorMessage message) : base(((int)message).ToString())
{
}
public NotFoundException(string message) : base(message)
{
}
}
@@ -0,0 +1,3 @@
namespace adas_core.Application.Exceptions;
public class TokenException(string message) : Exception(message);
@@ -0,0 +1,14 @@
using adas_core.Domain.Enums;
namespace adas_core.Application.Exceptions;
public class UnauthorizedException : Exception
{
public UnauthorizedException(HttpEnum.ErrorMessage message) : base(((int)message).ToString())
{
}
public UnauthorizedException(string message) : base(message)
{
}
}
@@ -0,0 +1,14 @@
using adas_core.Domain.Enums;
namespace adas_core.Application.Exceptions;
public class UnprocessableEntityException : Exception
{
public UnprocessableEntityException(HttpEnum.ErrorMessage message) : base(((int)message).ToString())
{
}
public UnprocessableEntityException(string message) : base(message)
{
}
}
@@ -0,0 +1,150 @@
using adas_core.Domain.Models;
using adas_core.Domain.Models.MongoModels;
using adas_core.Domain.Models.Providers;
using Microsoft.Extensions.Options;
using Newtonsoft.Json;
using Serilog;
namespace adas_core.Application.Providers;
public class AdasProvider(IOptions<ProvidersSettings> providerSettings, IHttpClientFactory httpClientFactory)
: BaseProvider(providerSettings, httpClientFactory)
{
/*
* Get observations of:
* Predict of UCI stay duration
* Predict of medications for patients
*/
public override async Task<List<PatientObservation>> GetObservations(Patient patient)
{
var calculatedObservations = new List<PatientObservation>();
//get data from two endpoints one from UCI stay another for medication
var predictOfStay = await GetPredictOfStay(patient.PatientNumber);
if (predictOfStay == null || predictOfStay.Result.ToLower().Contains("error"))
Log.Error("Error retrieving predict of stay in ADAS result, message: {predictOfStay}", predictOfStay);
else
{
var durationPredicted1 = predictOfStay.Payload.MaxBy(i => i.PercentageMin)!.Duration;
var durationPredicted2 = predictOfStay.Payload.MinBy(i => i.PercentageMin)!.Duration;
calculatedObservations.Add(
new PatientObservation
{
PatientId = patient.Id,
CodingSystem = "ADAS",
Name = "Stay_Predict_Days_Most_Confident",
//get the most confident interval to show the most probable scenario
Value = durationPredicted1,
Time = DateTime.UtcNow
});
calculatedObservations.Add(new PatientObservation
{
PatientId = patient.Id,
CodingSystem = "ADAS",
Name = "Stay_Predict_Days_Less_Confident",
//get the less confident interval to show the less probable scenario
Value = durationPredicted2,
Time = DateTime.UtcNow
});
}
var predictMedication = await GetPredictMedications(patient.PatientNumber);
if (predictMedication != null && predictMedication.Any())
calculatedObservations.Add(new PatientObservation
{
PatientId = patient.Id,
CodingSystem = "ADAS",
Name = "Medication_Predict",
Value = ParseAdasMedicationsToMedicationObservation(predictMedication),
Time = DateTime.UtcNow
});
return calculatedObservations;
}
private static string ParseAdasMedicationsToMedicationObservation(
List<ResultModelPredictMedicationAdas> medicationPredict)
{
return string.Join("^", medicationPredict.Take(5));
}
private async Task<ResultModelPredictOfStayAdas?> GetPredictOfStay(string? patientNumber)
{
if (patientNumber == null)
{
Log.Error("Error retrieving ADAS predict of stay patient number is null");
return null;
}
try
{
var client = GetClient();
var response = await client.GetAsync($"predictStay?patientNumber={patientNumber}");
if (response.IsSuccessStatusCode)
{
var content = await response.Content.ReadAsStringAsync();
return JsonConvert.DeserializeObject<ResultModelPredictOfStayAdas>(content);
}
Log.Error(
"Error retrieving calculated ADAS observation length of stay for patient: {patientNumber}, status code: {response.StatusCode} ",
patientNumber, response.StatusCode);
return null;
}
catch (Exception e)
{
Log.Error("Exception retrieving ADAS predict of stay for patient: {patientNumber} exception: {e}",
patientNumber, e.Message);
return null;
}
}
private async Task<List<ResultModelPredictMedicationAdas>?> GetPredictMedications(string? patientNumber)
{
if (patientNumber == null)
{
Log.Error("Error retrieving ADAS predict of stay for patient number is null");
return null;
}
try
{
var client = GetClient();
var response = await client.GetAsync($"predictMedicationNeedsPharmacy?patientNumber={patientNumber}");
if (response.IsSuccessStatusCode)
{
var content = await response.Content.ReadAsStringAsync();
return JsonConvert.DeserializeObject<List<ResultModelPredictMedicationAdas>>(content);
}
Log.Error(
"Error retrieving calculated ADAS medicines for patient: {patientNumber}, status code: {response.StatusCode}",
patientNumber, response.StatusCode);
return null;
}
catch (Exception e)
{
Log.Error("Exception retrieving ADAS predict medicines for patient: {patientNumber} exception: {e}",
patientNumber, e.Message);
return null;
}
}
private HttpClient GetClient()
{
var client = HttpClientFactory.CreateClient();
client.BaseAddress = new Uri(Url);
return client;
}
}
@@ -0,0 +1,16 @@
using adas_core.Domain.Models;
using adas_core.Domain.Models.MongoModels;
using adas_core.Domain.Models.Providers;
using Microsoft.Extensions.Options;
namespace adas_core.Application.Providers;
public abstract class BaseProvider(
IOptions<ProvidersSettings> providerSettings,
IHttpClientFactory httpClientFactory)
{
protected IHttpClientFactory HttpClientFactory = httpClientFactory;
protected string Url { get; set; } = providerSettings.Value.Url;
public abstract Task<List<PatientObservation>> GetObservations(Patient patient);
}
@@ -0,0 +1,42 @@
using adas_core.Domain.Models;
using adas_core.Domain.Models.DTO;
using adas_core.Domain.Models.Masters;
using adas_core.Domain.Models.MongoModels;
using MongoDB.Bson;
namespace adas_core.Application.Repositories.Interfaces;
public interface IAdmissionRepository : IMongoRepository<Admission>
{
Task Delete(ObjectId id);
Task Update(Admission admission);
Task UpdateLocation(ObjectId id, ObjectId newLocation);
Task UpdatePatient(ObjectId id, Person patient);
Task<IEnumerable<Admission>> FindAll();
Task<Admission?> FindById(ObjectId id);
Task<Admission?> FindByNhc(string nhc);
//Task<IEnumerable<Admission>?> FindByUnit(string unit);
Task<List<Admission>> FindByLocation(PatientLocation location);
Task<IEnumerable<Admission>?> FindByOrigin(string origin);
Task<Admission?> InsertOneAsyncAndReturn(Admission origin);
Task<Admission?> SearchByPatientNumberAndDistinctUnit(string patientNumber, ObjectId unitId);
Task<List<Admission>> GetAdmissionByUnitIdWithOutPoC(ObjectId unitId);
Task<List<Admission>> FindByPointOfCareId(ObjectId pocId);
Task<List<Admission>> FindByUnitIds(List<ObjectId> unitIds);
Task<long> CountByUnitId(ObjectId unitId);
Task<IEnumerable<Admission>> UpdateMasterListOption(List<ObjectId> unitIds, UpdateOptionMasterListDto opt,
string typeName);
Task<IEnumerable<Admission>> DeleteMasterListOption(List<ObjectId> unitIds, OptionList opt, string typeName);
Task<bool> DeleteAdmissionsByUnitId(ObjectId unitId);
}
@@ -0,0 +1,15 @@
using adas_core.Domain.Models;
using adas_core.Domain.Models.GroupedObservations;
using adas_core.Domain.Models.MongoModels;
using MongoDB.Bson;
namespace adas_core.Application.Repositories.Interfaces;
public interface IAlarmRepository : IMongoRepository<PatientObservationAlarm>
{
Task<List<PatientObservationAlarm>> AggregatedPatientLastObservationsByField(ObjectId patientId,
List<Field>? filterObservations = null);
Task<List<PatientObservationAlarm>> AggregatedPatientNotExpiredObservationsByField(ObjectId patientId,
List<Field>? filterObservations, List<ConfigObservation> configAlarm);
}
@@ -0,0 +1,10 @@
using adas_core.Domain.Models;
namespace adas_core.Application.Repositories.Interfaces;
public interface IAppointmentArchiveRepository
{
Task InsertOneAsync(PatientAppointment patientAppointment);
Task DeleteBeforeDate(DateTime date);
Task<long> InsertBatch(IEnumerable<PatientAppointment> appointment);
}
@@ -0,0 +1,22 @@
using adas_core.Domain.Models;
using adas_core.Domain.Models.MongoModels;
using MongoDB.Bson;
using MongoDB.Driver;
namespace adas_core.Application.Repositories.Interfaces;
public interface IAppointmentRepository : IMongoRepository<PatientAppointment>
{
Task<List<PatientAppointment>> GetByPatient(ObjectId patientId);
new Task InsertOneAsync(PatientAppointment appointment);
Task Update(PatientAppointment appointment);
Task UpdateManyObjectId(string nameId, ObjectId id, ObjectId oldId);
new Task DeleteAsync(ObjectId id);
Task<IAsyncCursor<PatientAppointment>> FindByPatientIdAsync(ObjectId patientId);
Task DeleteByPatientId(ObjectId patientId);
Task<PatientAppointment?> FindByPatientAndVisitNumber(ObjectId patientId, string visitNumber);
Task<PatientAppointment?> FindByPatientAndReason(ObjectId patientId, string? appointmentReason);
Task<List<PatientAppointment>> FindByLocation(PatientLocation location);
Task<List<PatientAppointment>> FindByPoC(PointOfCare poc);
}
@@ -0,0 +1,17 @@
using adas_core.Domain.Models.MongoModels;
using MongoDB.Bson;
namespace adas_core.Application.Repositories.Interfaces;
public interface IArchivePatientCarePlanRepository : IMongoRepository<PatientCarePlan>
{
Task<List<PatientCarePlan>?> FindByPatientId(ObjectId patientId);
Task<List<PatientCarePlan>?> FindByPatientId(string patientId);
Task<List<PatientCarePlan>?> FindByPatientNumber(string patientId);
Task<List<PatientCarePlan>> FindAll();
Task<List<PatientCarePlan>?> FindByIds(ObjectId oldPatientId, string? oldPatientPatientId,
string? oldPatientPatientNumber);
Task InsertManyAsync(List<PatientCarePlan> patientCarePla);
}
@@ -0,0 +1,16 @@
using adas_core.Domain.Models.MongoModels;
using MongoDB.Bson;
namespace adas_core.Application.Repositories.Interfaces;
public interface IAuthorityRepository : IMongoRepository<Authorization>
{
public Task<Authorization> GetById(ObjectId authId);
void CreateNewAuthority(string roleName, ObjectId userId);
public Task<List<Authorization>> GetUserAuthorities(ObjectId userId);
public Task<List<Authorization>> GetAllAuthorities();
public Task<bool> DeleteAllAuthoritiesByUser(ObjectId userId);
Task<bool> DeleteAllAuthoritiesByUnit(ObjectId unitId);
Task<bool> DeleteAllAuthoritiesByDisplay(ObjectId displayId);
Task<List<Authorization>> GetByUnitId(ObjectId unitId);
}
@@ -0,0 +1,17 @@
using adas_core.Domain.Models.Filter;
using adas_core.Domain.Models.MongoModels;
using MongoDB.Bson;
using MongoDB.Driver;
namespace adas_core.Application.Repositories.Interfaces;
public interface ICameraRepository : IMongoRepository<Camera>
{
Task<Camera?> GetById(ObjectId cameraId);
Task<Camera?> GetByName(string name);
List<Camera> GetCameraInList(List<ObjectId> configurationRelayList);
IFindFluent<Camera, Camera> GetPaginatedCameras(PaginationFilter request);
Task<Camera?> InsertOneCamera(Camera camera);
Task<Camera?> UpdateCameraAsync(ObjectId objectId, Camera camera);
Task<List<Camera>> GetSearchByNameCameras(string textToSearch);
}
@@ -0,0 +1,30 @@
using adas_core.Domain.Models.Filter;
using adas_core.Domain.Models.MongoModels;
using MongoDB.Bson;
namespace adas_core.Application.Repositories.Interfaces;
public interface IConfigObservationRepository : IMongoRepository<ConfigObservation>
{
Task<ConfigObservation?> FindById(ObjectId id);
Task<ConfigObservation?> Update(ConfigObservation configObservation);
Task<ConfigObservation?> Delete(ObjectId id);
Task<List<ObjectId>> FindAllIds();
Task<ICollection<ConfigObservation>> FindAll();
Task<List<string>> GetConfigNames(string id);
Task<List<string>> GetConfigNames();
Task<long> Count();
Task<ICollection<ConfigObservation>> GetPaginatedItems(PaginationFilter filter);
Task<ConfigObservation?> FindByName(string name);
Task<ConfigObservation?> GetByCodeSysAndCode(string? codingSystem, string? code);
Task<ConfigObservation> InsertOneAsyncAndReturn(ConfigObservation configObservationItem);
Task<List<ConfigObservation>> FindAllByName(string name);
Task<ConfigObservation?> GetSingleConfigObservationItem(string? code, string? codingSystem, string? name,
string? originalName);
}
@@ -0,0 +1,12 @@
using adas_core.Domain.Models.MongoModels;
namespace adas_core.Application.Repositories.Interfaces;
public interface IConfigPumpsRepository : IMongoRepository<ConfigPumps>
{
Task<ConfigPumps?> FindById(string id);
Task<List<ConfigPumps>?> GetAllConfigs();
Task<ConfigPumps?> UpdateConfig(ConfigPumps config);
Task<bool> DeleteConfig(ConfigPumps config);
}
@@ -0,0 +1,8 @@
using adas_core.Domain.Models.MongoModels;
namespace adas_core.Application.Repositories.Interfaces;
public interface IConfigUnitsRepository : IMongoRepository<ConfigUnits>
{
Task<ConfigUnits?> FindById(string id);
}
@@ -0,0 +1,14 @@
using adas_core.Domain.Models.DTO;
using adas_core.Domain.Models.MongoModels;
using MongoDB.Bson;
namespace adas_core.Application.Repositories.Interfaces;
public interface IDeviceRepository : IMongoRepository<Device>
{
Task<Device?> FindByMacAddr(string deviceDtoMacAddr);
Task<Device?> FindBySerialNumber(string deviceDtoSerialNumber);
Task<Device?> FindByUuid(string deviceDtoUuid);
Task<Device?> FindByKey(string deviceDtoKey);
Task UpdateDeviceStats(ObjectId id,DeviceDto deviceExist);
}
@@ -0,0 +1,10 @@
using adas_core.Domain.Models;
namespace adas_core.Application.Repositories.Interfaces;
public interface IDiagnosisArchiveRepository
{
Task InsertOneAsync(PatientDiagnosis patientDiagnosis);
Task DeleteBeforeDate(DateTime date);
Task<long> InsertBatch(IEnumerable<PatientDiagnosis> diagnosis);
}
@@ -0,0 +1,17 @@
using adas_core.Domain.Models;
using MongoDB.Bson;
using MongoDB.Driver;
namespace adas_core.Application.Repositories.Interfaces;
public interface IDiagnosisRepository : IMongoRepository<PatientDiagnosis>
{
Task<List<PatientDiagnosis>> GetByPatient(ObjectId patientId);
new Task InsertOneAsync(PatientDiagnosis diagnosis);
new Task DeleteAsync(ObjectId id);
Task UpdateManyObjectId(string nameId, ObjectId id, ObjectId oldId);
Task<IAsyncCursor<PatientDiagnosis>> FindByPatientIdAsync(ObjectId patientId);
Task DeleteByPatientId(ObjectId patientId);
Task<PatientDiagnosis?> FindByPatientIdAndCode(ObjectId patientId, string? diagnosisCode, string? codingSystem);
}
@@ -0,0 +1,44 @@
using adas_core.Domain.Models;
using adas_core.Domain.Models.DTO;
using adas_core.Domain.Models.Masters;
using adas_core.Domain.Models.MongoModels;
using MongoDB.Bson;
namespace adas_core.Application.Repositories.Interfaces;
public interface IDischargeRepository : IMongoRepository<Discharge>
{
Task Delete(ObjectId id);
Task Update(Discharge discharge);
Task UpdateUnit(ObjectId id, string unit);
Task UpdatePatient(ObjectId id, Patient patient);
Task<IEnumerable<Discharge>> FindAll();
Task<Discharge?> FindById(ObjectId id);
Task<IEnumerable<Discharge>?> FindByUnit(string unit);
Task<long> CountByUnitId(ObjectId unitId);
Task<IEnumerable<Discharge>?> FindByDestination(string destination);
Task<IEnumerable<Discharge>?> FindByService(string service);
Task<IEnumerable<Discharge>?> GetDischargesByUnitIds(IEnumerable<ObjectId>? unitIds);
Task<Discharge?> GetDischargeByLocation(PatientLocation location);
Task<Discharge?> GetByPatientId(ObjectId patientId);
Task<IEnumerable<Discharge>?> FindByPoCId(ObjectId pocId);
Task<Discharge?> GetDischargeByPointOfCareId(ObjectId poc);
Task<IEnumerable<Discharge>> UpdateMasterListOption(List<ObjectId> unitIds, UpdateOptionMasterListDto opt,
string typeName);
Task<IEnumerable<Discharge>> DeleteMasterListOption(List<ObjectId> unitIds, OptionList opt, string typeName);
Task<bool> DeleteByUnitId(ObjectId unitId);
}
@@ -0,0 +1,14 @@
using adas_core.Domain.Models.MongoModels;
using adas_core.Domain.Models.Responses;
using MongoDB.Bson;
namespace adas_core.Application.Repositories.Interfaces;
public interface IDisplayCardConfigRepository : IMongoRepository<CardConfig>
{
Task<List<CardConfig>> GetAll();
Task<CardConfig?> GetById(ObjectId configId);
Task<CardConfig?> InsertOneAsyncAndReturn(CardConfig config);
Task<UpdateResponse<CardConfig?>> UpdateOne(CardConfig? config);
Task<CardConfig?> DeleteOne(ObjectId configId);
}
@@ -0,0 +1,14 @@
using adas_core.Domain.Models.MongoModels;
using adas_core.Domain.Models.Responses;
using MongoDB.Bson;
namespace adas_core.Application.Repositories.Interfaces;
public interface IDisplayChartConfigRepository : IMongoRepository<ChartConfig>
{
Task<List<ChartConfig>> GetAll();
Task<ChartConfig?> GetById(ObjectId configId);
Task<ChartConfig?> InsertOneAsyncAndReturn(ChartConfig config);
Task<UpdateResponse<ChartConfig?>> UpdateOne(ChartConfig? config);
Task<ChartConfig?> DeleteOne(ObjectId configId);
}
@@ -0,0 +1,42 @@
using adas_core.Domain.Enums;
using adas_core.Domain.Models.DTO.Display;
using adas_core.Domain.Models.Filter;
using adas_core.Domain.Models.GroupedObservations;
using adas_core.Domain.Models.MongoModels;
using adas_core.Domain.Models.Responses;
using MongoDB.Bson;
using MongoDB.Driver;
namespace adas_core.Application.Repositories.Interfaces;
public interface IDisplayConfigRepository : IMongoRepository<DisplayConfig>
{
Task<List<DisplayConfig>> GetAll();
IFindFluent<DisplayConfig, DisplayConfigSummary> GetAllPaginated(PaginationFilter request);
Task<List<DisplayConfig>> GetByType(DisplayConfigEnums.DisplayType type);
Task<DisplayConfig?> GetById(ObjectId id);
Task<DisplayConfig?> GetDefault(DisplayConfigEnums.DisplayType type);
Task<DisplayConfig?> InsertOneAsyncAndReturn(DisplayConfig config);
Task<SmartDisplay?> UpdateSmartDisplay(ObjectId displayConfigId, SmartDisplay? newDisplayConfig);
Task<DisplayNurse?> UpdateDisplayNurse(ObjectId displayConfigId, DisplayNurseDto? newDisplayConfig,
List<string> nurseObs);
Task<DisplayConfig?> GetDefaultByUnitIdAndType(ObjectId unitId, DisplayConfigEnums.DisplayType displayType);
Task<bool> UpdateConfigColor(ObjectId objectIdConfigDisplay, ColorConfig colorConfig);
Task<bool> UpdateSetHomeBanner(ObjectId objectIdConfigDisplay, List<BannerItem> bannerItems);
Task<bool> UpdateBaseConfig(ObjectId objectIdConfigDisplay, DisplayConfig baseConfig);
Task<bool> UpdateHeaderConfig(ObjectId objectIdConfigDisplay, HeaderConfig headerConfig);
Task<bool> UpdateDisplayConfigHospitalName(ObjectId objectIdConfigDisplay, string name);
Task<bool> UpdateFieldList(ObjectId objectIdConfigDisplay, List<Field> fields);
Task<DisplayConfig?> DeleteDisplayConfig(ObjectId objectIdConfigDisplay);
Task<List<DisplayConfigMinimalResponse>> GetAllCompact();
Task<List<ObjectId>> GetAllByCardConfigId(ObjectId cardConfigId);
Task<List<ObjectId>> GetAllByCardConfigIdAndRotating(ObjectId cardConfigId);
Task<bool> UpdateCardConfigId(ObjectId? displayConfigId, ObjectId? resultId);
Task<bool> AddChartId(ObjectId? displayConfigId, ObjectId newChartIdToAdd);
Task<UpdateResult> UpdateDeletedChartConfig(ObjectId deletedId);
Task<ChartConfig> GetChartConfig(ObjectId objectIdConfigChart);
Task<bool> UpdateDetailConfigId(ObjectId? displayConfigId, ObjectId? resultId);
Task<List<ObjectId>> GetAllByCardDetailConfigId(ObjectId baseConfigId);
}
@@ -0,0 +1,14 @@
using adas_core.Domain.Models.MongoModels;
using adas_core.Domain.Models.Responses;
using MongoDB.Bson;
namespace adas_core.Application.Repositories.Interfaces;
public interface IDisplayDetailConfigRepository : IMongoRepository<CardDetailsConfig>
{
Task<List<CardDetailsConfig>> GetAll();
Task<CardDetailsConfig?> GetById(ObjectId configId);
Task<CardDetailsConfig?> InsertOneAsyncAndReturn(CardDetailsConfig config);
Task<UpdateResponse<CardDetailsConfig?>> UpdateOne(CardDetailsConfig? config);
Task<CardDetailsConfig?> DeleteOne(ObjectId configId);
}
@@ -0,0 +1,35 @@
using adas_core.Domain.Models.Filter;
using adas_core.Domain.Models.MongoModels;
using MongoDB.Bson;
using MongoDB.Driver;
namespace adas_core.Application.Repositories.Interfaces;
public interface IDisplayRepository : IMongoRepository<Display>
{
Task<List<Display>> GetAll();
// ¿FilterByAuthorities / FindByUser
// Delete
// FindByUnit
// UpdateConfig -> algo en específico? ¿Toda la colección?
Task<List<Display>> GetByPointOfCare(PointOfCare pointOfCare);
// Task<List<Display>> GetByUser();
Task<Display?> GetByName(string name);
Task<Display?> GetById(ObjectId id);
Task<Display?> GetByIdWithConfigDisplay(ObjectId id);
Task<List<Display>> GetByUnitId(ObjectId id);
Task<List<Display>> GetByConfigId(ObjectId id);
Task<Display?> UpdatePointOfCareList(ObjectId objectId, List<ObjectId> listPocObId);
Task<Display?> UpdateConfig(ObjectId oldDisplayId, DisplayConfig newDisplayConfigCast);
Task<Display?> UpdateConfigPreset(ObjectId objectIdDisplay, ObjectId objectIdConfigDisplay);
Task<Display> UpdateName(Display display, string name);
IFindFluent<Display, Display> GetPaginatedDisplays(PaginationFilter filter);
Task<long> IsDisplayConfigInUse(ObjectId displayConfigId);
Task<Display?> UpdateConfigId(ObjectId objectId, ObjectId displayConfigId);
Task<long> CountByUnitId(ObjectId unitId);
Task<bool> DeleteManyByUnitId(ObjectId unitId);
Task<List<Display>> GetByCardConfigId(ObjectId configId);
}
@@ -0,0 +1,25 @@
using adas_core.Domain.Enums;
using adas_core.Domain.Models.MongoModels;
using MongoDB.Bson;
namespace adas_core.Application.Repositories.Interfaces;
public interface IHistoricalConfigChangesRepository : IMongoRepository<HistoricalConfigChanges>
{
Task<HistoricalConfigChanges?> FindById(ObjectId id);
Task<HistoricalConfigChanges?> Update(HistoricalConfigChanges historicalConfigChange);
Task<HistoricalConfigChanges?> Delete(ObjectId id);
Task<List<ObjectId>> FindAllIds();
Task<ICollection<HistoricalConfigChanges>> FindAll();
new Task<HistoricalConfigChanges?> InsertOneAsync(HistoricalConfigChanges patientObservation);
Task<ICollection<HistoricalConfigChanges>> FindLastHistoricalConfigChangesByType(
DisplayConfigEnums.ConfigTypes cfgType, int num = 10);
Task<ICollection<HistoricalConfigChanges>> FindLastHistoricalConfigChangesByUser(string user,
DisplayConfigEnums.ConfigTypes? cfgType = null, int num = 10);
}
@@ -0,0 +1,16 @@
using adas_core.Domain.Models.Filter;
using adas_core.Domain.Models.MongoModels;
using MongoDB.Bson;
using MongoDB.Driver;
namespace adas_core.Application.Repositories.Interfaces;
public interface ILightBeaconRepository : IMongoRepository<LightBeacon>
{
List<LightBeacon> GetLightBeaconInList(List<ObjectId> configurationRelayList);
Task<LightBeacon?> GetById(ObjectId relayId);
Task<LightBeacon?> GetByName(string name);
Task<LightBeacon?> InsertOneAsyncAndReturn(LightBeacon beacon);
IFindFluent<LightBeacon, LightBeacon> GetPaginatedRelays(PaginationFilter filter);
Task<List<LightBeacon>> GetSearchByName(string textToSearch);
}
@@ -0,0 +1,34 @@
using adas_core.Domain.Models.DTO;
using adas_core.Domain.Models.Filter;
using adas_core.Domain.Models.Masters;
using MongoDB.Bson;
using MongoDB.Driver;
namespace adas_core.Application.Repositories.Interfaces;
public interface IMasterListRepository<T> : IMongoRepository<T> where T : MasterList
{
Task Delete(ObjectId id);
Task Update(T list);
Task<T?> FindById(ObjectId id, LocaleEnum? locale);
Task<T?> FindById(ObjectId id);
Task<OptionList?> FindOptionItemById(ObjectId masterId, ObjectId optionId, LocaleEnum locale);
Task<OptionList?> FindOptionItemById(ObjectId masterId, ObjectId optionId);
Task<IEnumerable<T>> GetAll();
Task<IEnumerable<MasterListDto>> GetAllWithoutOptions();
Task<T?> FindByName(string name);
Task<List<OptionList>> GetMasterListByIdAndTextSearchContaining(ObjectId id, string? textSearch);
Task<OptionList?> AddOptionToMasterList(ObjectId id, FilterOptionListElement opt);
Task<OptionList?> UpdateMasterListOption(ObjectId id, OptionList newOpt, LocaleEnum locale);
Task<OptionList?> UpdateFullMasterListOption(ObjectId id, OptionList newOpt);
Task<OptionList?> UpdateMasterListOption(ObjectId id, OptionList newOpt);
Task<bool> DeleteMasterListOption(ObjectId id, ObjectId deleteOptId);
Task<UpdateMasterListDetailsDto?> UpdateOptionDetailsToMasterList(ObjectId id, UpdateMasterListDetailsDto opt);
Task<bool> UpdateMasterListName(ObjectId id, string name);
Task<bool> UpdateMasterListDescription(ObjectId id, string description);
Task<bool> RemoveMasterListOption(ObjectId id, OptionList oldOpt);
IFindFluent<T, T> GetPaginatedMasterList(PaginationFilter filter);
Task<List<OptionList>> GetPaginatedOptions(PaginationFilter filter, ObjectId listId);
Task<int> Count();
Task<List<OptionList>> GetMasterListByIdAndSearchOptions(ObjectId id, FilterOptionListElement? filterOption);
}
@@ -0,0 +1,22 @@
using adas_core.Domain.Models;
using adas_core.Domain.Models.Filter;
using MongoDB.Bson;
using MongoDB.Driver;
namespace adas_core.Application.Repositories.Interfaces;
public interface IMedicineRepository : IMongoRepository<Medicine>
{
Task<Medicine?> GetMedicine(string code);
Task<List<Medicine>> GetMedicineByCodeOrNote(List<string> codeNotes);
Task<Medicine?> GetMedicineByName(string name);
Task<List<Medicine>> GetAll();
Task<Medicine?> GetMedicineById(ObjectId medicineId);
Task<Medicine?> PostMedicine(Medicine medicine);
Task<Medicine?> UpdateMedicine(Medicine medicine);
Task DeleteMedicineById(ObjectId medicineId);
IAggregateFluent<BsonDocument> GetDistinctFieldDataQuery(string field);
IFindFluent<Medicine, Medicine> GetPaginatedMedicines(PaginationFilter filter);
}
@@ -0,0 +1,14 @@
using MongoDB.Bson;
using MongoDB.Driver;
namespace adas_core.Application.Repositories.Interfaces;
public interface IMongoRepository<T>
{
IMongoCollection<T> Collection { get; }
string GetCollectionName();
Task InsertOneAsync(T obj);
Task<T?> DeleteAsync(ObjectId id);
Task UpdateOneAsync(ObjectId id, T obj);
}
@@ -0,0 +1,15 @@
using adas_core.Domain.Models.MongoModels;
using MongoDB.Bson;
namespace adas_core.Application.Repositories.Interfaces;
public interface INoticeRepository : IMongoRepository<Notice>
{
Task Delete(ObjectId id);
Task Update(Notice notice);
Task<IEnumerable<Notice>> FindAll();
Task<Notice?> FindById(ObjectId id);
Task<IEnumerable<Notice>?> FindByDate(DateTime date);
Task<IEnumerable<Notice>?> FindByType(string type);
Task<IEnumerable<Notice>?> FindByDisplayId(ObjectId displayId);
}
@@ -0,0 +1,16 @@
using adas_core.Domain.Models;
using MongoDB.Bson;
namespace adas_core.Application.Repositories.Interfaces;
public interface IObservationArchiveRepository : IMongoRepository<PatientObservation>
{
new Task InsertOneAsync(PatientObservation patientObservation);
Task DeleteBeforeDate(DateTime date);
Task<long> InsertBatch(IEnumerable<PatientObservation> observations);
Task<List<PatientObservation>> AggregatedPatientLastObservations(ObjectId patientId, int num, DateTime lastDate,
List<string> filterObservations);
Task<List<PatientObservation>> FindAllFromPatient(ObjectId patientId);
}
@@ -0,0 +1,74 @@
using adas_core.Domain.Models;
using adas_core.Domain.Models.Filter;
using adas_core.Domain.Models.GroupedObservations;
using adas_core.Domain.Models.MongoModels;
using MongoDB.Bson;
using MongoDB.Driver;
namespace adas_core.Application.Repositories.Interfaces;
public interface IObservationRepository : IMongoRepository<PatientObservation>
{
Task<List<BsonDocument>> AggregatedPatientGroupedObservations(ObjectId patientId, GroupedField groupedField);
Task<List<PatientObservation>> AggregatedPatientLastObservations(ObjectId patientId, int num,
List<string>? filterObservations = null);
Task<List<PatientObservation>> AggregatedPatientLastObservationsByLastDate(ObjectId patientId, int num,
DateTime lastDate, List<string>? filterObservations = null);
Task<List<PatientObservation>> AggregatedPatientLastObservationsByField(ObjectId patientId,
List<Field>? filterObservations = null);
Task<IEnumerable<PatientObservation>> FindLastObservations(ObjectId patientId, string codingSystem, string code,
int num = 2);
Task<List<PatientObservation>> FindLastObservationsByCodingSystem(ObjectId patientId, string codingSystem,
int num = 10);
Task<PatientObservation?> FindLastObservationBeforeDate(ObjectId patientId, string? name, DateTime date);
Task UpdateExpiredObservations(List<PatientObservation> expiredObservations);
Task<List<PatientObservation>?> FindAnyWithSameDate(ObjectId patientId, string? name, DateTime date);
Task<List<PatientObservation>> FindAnyBeforeDate(ObjectId patientId, DateTime date);
Task<List<PatientObservation>> FindLatestUniqueValuesByName(ObjectId patientId, string name, int? expires);
new Task InsertOneAsync(PatientObservation patientObservation);
Task DeleteByPatientId(ObjectId id);
Task<IAsyncCursor<PatientObservation>> FindByPatientIdAsync(ObjectId patientId);
Task<IAsyncCursor<PatientObservation>> FindByPatientIdAndCodingSystemAsync(ObjectId patientId, string codingSystem,
string name);
new Task DeleteAsync(ObjectId id);
Task<List<PatientObservation>> DeleteOlderDaysAsync(string name, int retentionPolicyValue);
Task<List<PatientObservation>> DeleteOlderNumberAsync(string name, int retentionPolicyValue);
Task<bool> ExistBySystemId(ObjectId patientid, string systemId);
Task<List<PatientObservation>> DeleteOlderSecondsAsync(string name, int value);
Task<List<PatientObservation?>> AggregatedPatientActiveIntravenousLinesObservations(ObjectId patientId);
Task<Dictionary<ObjectId, DateTime>> FindAllLastPatientObservationTime();
Task<PatientObservation?> FindById(ObjectId id);
Task<List<PatientObservation>?> FindByPatientId(ObjectId id);
Task Update(PatientObservation observation);
Task UpdateManyObjectId(string nameId, ObjectId id, ObjectId oldId);
Task<IEnumerable<PatientObservation>> FindNotExpired(List<string?>? filterObservations);
Task<IEnumerable<PatientObservation>> FindByName(string name, DateTime? date);
Task UpdateMany(IEnumerable<PatientObservation> patientObservations, UpdateDefinition<PatientObservation> update);
Task<IEnumerable<PatientObservation>> FindAll();
Task ExpireExpiredObservations(List<ConfigObservation> configObservationsToExpire);
//Task<PaginationResponse<PatientObservation>> GetPaginatedObservations(PaginationFilter filter);
IFindFluent<PatientObservation, PatientObservation> GetPaginatedObservations(PaginationFilter filter);
Task<IEnumerable<PatientObservation>> FindLastNotExpiredObservatonsByPatient(ObjectId patientId, string name,
int? endAfter = null, int? num = null);
}
@@ -0,0 +1,12 @@
using adas_core.Domain.Models.MongoModels;
using MongoDB.Bson;
namespace adas_core.Application.Repositories.Interfaces;
public interface IPatientArchiveRepository : IMongoRepository<Patient>
{
Task<Patient?> FindByPatientNumber(string patientNumber);
Task<List<Patient>> FindAll();
Task<Patient?> SearchByPatientNumberAndDistinctUnit(string patientNumber, ObjectId unitId);
}
@@ -0,0 +1,12 @@
using adas_core.Domain.Models.MongoModels;
using MongoDB.Bson;
namespace adas_core.Application.Repositories.Interfaces;
public interface IPatientCarePlanRepository : IMongoRepository<PatientCarePlan>
{
Task<List<PatientCarePlan>> FindByPatientId(ObjectId patientId);
Task<List<PatientCarePlan>> FindByUserId(ObjectId userId);
Task<List<PatientCarePlan>> FindAll();
Task UpdateManyObjectId(string patientid, ObjectId patientId, ObjectId oldId);
}
@@ -0,0 +1,45 @@
using adas_core.Domain.Models;
using adas_core.Domain.Models.DTO;
using adas_core.Domain.Models.Filter;
using adas_core.Domain.Models.Masters;
using adas_core.Domain.Models.MongoModels;
using MongoDB.Bson;
using MongoDB.Driver;
namespace adas_core.Application.Repositories.Interfaces;
public interface IPatientRepository : IMongoRepository<Patient>
{
Task Delete(ObjectId id);
Task<Patient?> FindByLocation(PatientLocation location);
Task<Patient?> FindById(ObjectId id);
Task UpdateLocation(ObjectId id, PatientLocation location);
Task UpdateLocation(ObjectId id, ObjectId location);
Task UpdateAttendingDoctor(ObjectId id, Person attendingDoctor);
Task UpdatePatientData(ObjectId id, string patientNumber, Person data, bool updatePatientNumber = true);
Task Update(Patient patient);
Task<Patient?> FindByPatientNumber(string patientNumber);
Task<Patient?> FindByPatientId(string patientId);
Task<List<Patient>> FindAll();
Task<List<Patient>> FindByPointOfCare(string pointOfCare);
Task<List<Patient>> FindByPointOfCare(ObjectId pointOfCare);
Task<Patient?> FindByPointOfCareId(ObjectId pointOfCare);
Task<List<Patient>> FindDischargedPatients();
Task<Patient?> UpdateOne(Patient updatedPatient);
Task<List<Patient>> FindInActivePoC();
Task<List<Patient>> FindInInactivePoC();
Task<List<Patient>> FindPatientsNotUpdatedSince(DateTime date);
Task<Patient?> FindByPatientId(ObjectId patientId);
Task<Patient?> UpdatePatientIncomingData(ObjectId patientId, Patient person);
Task<Patient?> UpdatePatientDemographicData(ObjectId patientId, Patient person);
Task<Patient> FindByUnitAndPocId(ObjectId unit, ObjectId pointOfCare);
Task<long> CountByUnitId(ObjectId unitId);
Task<Patient?> SearchByPatientNumberAndDistinctUnit(string patientNumber, ObjectId unitId);
IFindFluent<Patient, Patient> GetPaginatedPatients(PaginationFilter filter);
Task<List<Patient>> FindAllPatientWithFinishedProcedures(int archiveProcedureEndDateAfterMinutes);
Task<List<Patient>> FindAllPatientWithFinishedTests(int archiveTestEndDateAfterMinutes);
Task<List<Patient>> FindAllPatientWithFinishedTreatment(int archiveTreatmentEndDateAfterMinutes);
Task<List<Patient>> UpdateMasterListOption(List<ObjectId> unitIds, UpdateOptionMasterListDto opt, string typeName);
Task<List<Patient>> GetPatientsByUnitIds(List<ObjectId> unitIds, string typeName);
Task<IEnumerable<Patient>> DeleteMasterListOption(List<ObjectId> unitIds, OptionList opt, string typeName);
}
@@ -0,0 +1,9 @@
using adas_core.Domain.Models.MongoModels;
namespace adas_core.Application.Repositories.Interfaces;
public interface IPoCMappingRepository
{
Task<PoCMapping?> FindByKey(string key);
string GetCollectionName();
}
@@ -0,0 +1,18 @@
using adas_core.Domain.Models;
using adas_core.Domain.Models.MongoModels;
using MongoDB.Bson;
namespace adas_core.Application.Repositories.Interfaces;
public interface IPoCSettingsRepository : IMongoRepository<PoCSettings>
{
Task Delete(ObjectId id);
Task<PoCSettings?> FindByLocation(PatientLocation location);
Task<PoCSettings?> FindById(ObjectId id);
Task Update(PoCSettings pocSettings);
Task<List<PoCSettings>> FindAll();
}
@@ -0,0 +1,58 @@
using adas_core.Domain.Enums;
using adas_core.Domain.Models;
using adas_core.Domain.Models.DTO;
using adas_core.Domain.Models.Filter;
using adas_core.Domain.Models.MongoModels;
using MongoDB.Bson;
using MongoDB.Driver;
namespace adas_core.Application.Repositories.Interfaces;
public interface IPointOfCareRepository : IMongoRepository<PointOfCare>
{
Task Delete(ObjectId id);
Task Update(PointOfCare pointOfCare);
Task UpdateUnitId(ObjectId id, Unit unit);
Task UpdateConfiguration(ObjectId id, PointOfCareConfiguration configuration);
Task<PointOfCare?> FindById(ObjectId id);
Task<IEnumerable<PointOfCare>?> FindAllByUnitId(ObjectId unit);
Task<IEnumerable<PointOfCare>?> FindByRoom(string room);
Task<List<PointOfCare>> FindByFilter(FilterDefinition<PointOfCare> filter,
ProjectionDefinition<PointOfCare>? projection = null);
Task<IEnumerable<PointOfCare>?> FindByBed(string bed);
Task<List<PointOfCare>?> GetAll();
Task<List<PointOfCare>?> GetAllConfigs();
Task<List<PointOfCare>?> GetAllLocationInfo();
Task<PointOfCare?> GetPoCConfiguration(ObjectId pocId);
Task<PointOfCare?> FindByBedAndUnitId(string? bed, ObjectId unitId);
Task<PointOfCare?> FindByPatientLocation(PatientLocation patientLocation);
Task<IEnumerable<PointOfCare>> FindByUnitAndStatus(ObjectId unitId, StatusEnum.PointOfCare status,
bool excludeVirtual = false);
Task UpdateRelayConfig(ObjectId pocId, List<Relay> relayConfig);
Task UpdateRelayConfig(ObjectId pocId, List<ObjectId> relayConfig);
IFindFluent<PointOfCare, PointOfCare> GetPaginatedPoCs(PaginationFilter filter);
Task<long> CountByUnitId(ObjectId unitId);
Task<long> CountVirtualsByUnitId(ObjectId unitId);
Task<bool> DeleteManyByUnitId(ObjectId unitId);
Task<PointOfCare?> FindByIdAllConfig(ObjectId id);
Task<HashSet<ObjectId>> FindAllIdCamerasInUse();
Task<HashSet<ObjectId>> FindAllIdRelaysInUse();
Task<HashSet<ObjectId>> FindAllIdBeaconsInUse();
Task<IEnumerable<PointOfCare>?> FindAllByUnitIdWithDevices(ObjectId unitId);
}
@@ -0,0 +1,22 @@
using adas_core.Domain.Models.Pumps;
using MongoDB.Bson;
namespace adas_core.Application.Repositories.Interfaces;
public interface IPumpAlarmEventRepository
{
Task InsertAsync(PumpAlarmEvent alarmEvent);
Task<IEnumerable<PumpAlarmEvent>> FindByDeviceIdAsync(
string deviceId,
DateTime? from = null,
DateTime? to = null,
int? limit = null);
Task<PumpAlarmEvent?> FindLastByDeviceIdAsync(string deviceId);
// cleanup
Task DeleteByPatientId(ObjectId patientId);
Task<long> UpdateManyObjectIdByFiledNameAsync(string fieldName, ObjectId newId, ObjectId oldId);
}
@@ -0,0 +1,27 @@
using adas_core.Domain.Enums;
using adas_core.Domain.Models.Pumps;
using MongoDB.Bson;
namespace adas_core.Application.Repositories.Interfaces;
public interface IPumpAlarmStateRepository
{
Task<PumpAlarmState?> FindActiveAsync(
string deviceId,
PumpEnum.AlarmType? alarmType,
string? alarmCodeMdc = null);
Task<IEnumerable<PumpAlarmState>> FindAllActiveByDeviceAsync(string deviceId);
Task UpsertActiveAsync(PumpAlarmState state);
Task RemoveAsync(
string deviceId,
PumpEnum.AlarmType? alarmType,
string? alarmCodeMdc = null);
//cleanup
Task DeleteByPatientId(ObjectId patientId);
Task<long> UpdateManyObjectIdByFieldNameAsync(string fieldName, ObjectId newId, ObjectId oldId);
}
@@ -0,0 +1,30 @@
using MongoDB.Bson;
using MongoDB.Driver;
using adas_core.Domain.Models.Pumps;
namespace adas_core.Application.Repositories.Interfaces
{
public interface IPumpArchiveRepository
{
/// <summary>
/// Devuelve la colección subyacente
/// </summary>
IMongoCollection<PumpObservation> Collection { get; }
/// <summary>
/// Inserta una observación de bomba en la colección de archivo.
/// </summary>
Task InsertAsync(PumpObservation obs);
/// <summary>
/// Inserta múltiples observaciones en la colección de archivo.
/// </summary>
Task InsertManyAsync(IEnumerable<PumpObservation> observations);
/// <summary>
/// Búsqueda por paciente para auditoría o restauración.
/// </summary>
Task<IEnumerable<PumpObservation>> FindByPatientIdAsync(ObjectId patientId,
DateTime? from = null, DateTime? to = null, int? limit = null);
}
}
@@ -0,0 +1,28 @@
using adas_core.Domain.Models.Pumps;
using MongoDB.Bson;
namespace adas_core.Application.Repositories.Interfaces;
public interface IPumpObservationRepository
{
Task InsertAsync(PumpObservation obs);
Task InsertManyAsync(IEnumerable<PumpObservation> observations);
Task<IEnumerable<PumpObservation>> FindByDeviceIdAsync(
string deviceId,
DateTime? from = null,
DateTime? to = null,
int? limit = null);
Task<PumpObservation?> FindLastByDeviceIdAsync(string deviceId);
Task<Dictionary<ObjectId, DateTime>> FindAllLastPatientObservationTimeAsync();
Task<IEnumerable<PumpObservation>> FindByPatientId(ObjectId? patientId);
Task<List<PumpObservation>> AggregatedPatientLastObservations(ObjectId patientId, int num = 100);
Task DeleteByPatientId(ObjectId? patientId);
Task<long> DeleteOlderThanDaysAsync(int days, string? name = null);
Task<long> DeleteKeepLastNAsync(int maxCount);
Task<long> UpdateManyObjectIdByFieldAsync(string fieldName, ObjectId newId, ObjectId? oldId);
}
@@ -0,0 +1,12 @@
using adas_core.Domain.Models.Pumps;
namespace adas_core.Application.Repositories.Interfaces;
public interface IPumpStateRepository
{
Task<PumpState?> FindByDeviceIdAsync(string deviceId);
Task UpsertAsync(PumpState state);
Task<IEnumerable<PumpState>> GetAllAsync();
}
@@ -0,0 +1,10 @@
using adas_core.Domain.Models;
namespace adas_core.Application.Repositories.Interfaces;
public interface IRecordingAlertArchiveRepository : IMongoRepository<PatientRecordingAlert>
{
new Task InsertOneAsync(PatientRecordingAlert recordingAlert);
Task DeleteBeforeDate(DateTime date);
Task<long> InsertBatch(IEnumerable<PatientRecordingAlert> recordingAlerts);
}
@@ -0,0 +1,18 @@
using adas_core.Domain.Models;
using MongoDB.Bson;
using MongoDB.Driver;
namespace adas_core.Application.Repositories.Interfaces;
public interface IRecordingAlertRepository : IMongoRepository<PatientRecordingAlert>
{
Task<List<PatientRecordingAlert>> AggregatedPatientLastObservations(ObjectId patientId, int num);
Task<List<PatientRecordingAlert>> FindLastObservations(ObjectId patientId, string name, int num = 2);
new Task InsertOneAsync(PatientRecordingAlert patientRecordingAlert);
Task DeleteOlderDaysAsync(string name, int value);
Task DeleteOlderNumberAsync(string name, int value);
Task DeleteByPatientId(ObjectId patientId);
Task UpdateManyObjectId(string nameId, ObjectId id, ObjectId oldId);
Task<IAsyncCursor<PatientRecordingAlert>> FindByPatientIdAsync(ObjectId patientId);
}
@@ -0,0 +1,18 @@
using adas_core.Domain.Enums;
using adas_core.Domain.Models.Filter;
using adas_core.Domain.Models.MongoModels;
using MongoDB.Bson;
using MongoDB.Driver;
namespace adas_core.Application.Repositories.Interfaces;
public interface IRelayRepository : IMongoRepository<Relay>
{
Task<Relay?> GetById(ObjectId relayId);
List<Relay> GetRelayByTypeInList(List<ObjectId> configurationRelayList, RelayEnum.Type type);
List<Relay> GetRelayInList(List<ObjectId> configurationRelayList);
IFindFluent<Relay, Relay> GetPaginatedRelays(PaginationFilter filter);
Task<Relay?> InsertOneRelayAsync(Relay request);
Task<Relay?> UpdateRelayAsync(ObjectId objectId, Relay relay);
Task<Relay?> GetByName(string? requestRelayName);
}

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