diff --git a/prisma/migrations/20230619073305_ocr_fields_added/migration.sql b/prisma/migrations/20230619073305_ocr_fields_added/migration.sql deleted file mode 100644 index 9fb819c..0000000 --- a/prisma/migrations/20230619073305_ocr_fields_added/migration.sql +++ /dev/null @@ -1,21 +0,0 @@ --- AlterTable -ALTER TABLE "Asset" ADD COLUMN "password" TEXT, -ADD COLUMN "port" INTEGER, -ADD COLUMN "username" TEXT; - --- CreateTable -CREATE TABLE "Bed" ( - "id" SERIAL NOT NULL, - "patientExternalId" TEXT NOT NULL, - "x" INTEGER NOT NULL, - "y" INTEGER NOT NULL, - "zoom" INTEGER NOT NULL DEFAULT 0, - "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, - "updatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, - "deleted" BOOLEAN NOT NULL DEFAULT false, - - CONSTRAINT "Bed_pkey" PRIMARY KEY ("id") -); - --- CreateIndex -CREATE UNIQUE INDEX "Bed_patientExternalId_key" ON "Bed"("patientExternalId"); diff --git a/prisma/migrations/20230818140017_add_bed/migration.sql b/prisma/migrations/20230818140017_add_bed/migration.sql new file mode 100644 index 0000000..302210e --- /dev/null +++ b/prisma/migrations/20230818140017_add_bed/migration.sql @@ -0,0 +1,44 @@ +-- CreateEnum +CREATE TYPE "AssetType" AS ENUM ('CAMERA', 'MONITOR'); + +-- AlterTable +ALTER TABLE "Asset" ADD COLUMN "bedId" INTEGER, +ADD COLUMN "password" TEXT, +ADD COLUMN "port" INTEGER DEFAULT 80, +ADD COLUMN "type" "AssetType" NOT NULL DEFAULT 'MONITOR', +ADD COLUMN "username" TEXT; + +-- CreateTable +CREATE TABLE "Bed" ( + "id" SERIAL NOT NULL, + "name" TEXT NOT NULL, + "externalId" TEXT NOT NULL, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "deleted" BOOLEAN NOT NULL DEFAULT false, + + CONSTRAINT "Bed_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "Preset" ( + "id" SERIAL NOT NULL, + "x" INTEGER NOT NULL, + "y" INTEGER NOT NULL, + "zoom" INTEGER NOT NULL DEFAULT 0, + "bedId" INTEGER NOT NULL, + + CONSTRAINT "Preset_pkey" PRIMARY KEY ("id") +); + +-- CreateIndex +CREATE UNIQUE INDEX "Bed_externalId_key" ON "Bed"("externalId"); + +-- CreateIndex +CREATE UNIQUE INDEX "Preset_bedId_key" ON "Preset"("bedId"); + +-- AddForeignKey +ALTER TABLE "Asset" ADD CONSTRAINT "Asset_bedId_fkey" FOREIGN KEY ("bedId") REFERENCES "Bed"("id") ON DELETE SET NULL ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "Preset" ADD CONSTRAINT "Preset_bedId_fkey" FOREIGN KEY ("bedId") REFERENCES "Bed"("id") ON DELETE RESTRICT ON UPDATE CASCADE; diff --git a/prisma/schema.prisma b/prisma/schema.prisma index e2e5bf9..bcde4ba 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -10,9 +10,15 @@ datasource db { url = env("DATABASE_URL") } +enum AssetType { + CAMERA + MONITOR +} + model Asset { id Int @id @default(autoincrement()) name String + type AssetType @default(MONITOR) description String externalId String @unique ipAddress String @unique @@ -21,21 +27,32 @@ model Asset { deleted Boolean @default(false) username String? password String? - port Int? + port Int? @default(80) DailyRound DailyRound[] + Bed Bed? @relation(fields: [bedId], references: [id]) + bedId Int? @@index([externalId, ipAddress]) } model Bed { - id Int @id @default(autoincrement()) - patientExternalId String @unique - x Int - y Int - zoom Int @default(0) - createdAt DateTime @default(now()) - updatedAt DateTime @default(now()) - deleted Boolean @default(false) + id Int @id @default(autoincrement()) + name String + externalId String @unique + monitorPreset Preset? + assets Asset[] + createdAt DateTime @default(now()) + updatedAt DateTime @default(now()) + deleted Boolean @default(false) +} + +model Preset { + id Int @id @default(autoincrement()) + x Int + y Int + zoom Int @default(0) + bed Bed @relation(fields: [bedId], references: [id]) + bedId Int @unique } model DailyRound { diff --git a/src/automation/autoDataExtractor.js b/src/automation/autoDataExtractor.js index c936307..c4357e0 100644 --- a/src/automation/autoDataExtractor.js +++ b/src/automation/autoDataExtractor.js @@ -60,39 +60,42 @@ const getSanitizedData = (data)=>{ } -const extractData = async (camParams, patientId)=>{ - - const coordinates = await getMonitorCoordinates(patientId) - await CameraUtils.absoluteMove({ camParams, ...coordinates }) - - const snapshotUrl = await CameraUtils.getSnapshotUri({ camParams }); - - const fileName = "image-" + new Date().getTime() + ".jpeg" - const imagePath = path.resolve("images", fileName) - await downloadImage(snapshotUrl.uri, imagePath, camParams.username, camParams.password) - // const testImg = path.resolve("images", "test.png") - - // POST request with image to ocr - const bodyFormData = new FormData(); - bodyFormData.append('image', fs.createReadStream(imagePath)); - - const response = await axios.post(OCR_URL, bodyFormData, { - headers: { - ...bodyFormData.getHeaders() - } - }) - - // delete image - fs.unlink(imagePath, (err) => { - if (err) { - // TODO: Critical logger setup - console.error(err) - } - }) - - return getSanitizedData(response.data.data) +const extractData = async (camParams, bedId) => { + const coordinates = await getMonitorCoordinates(bedId); + await CameraUtils.absoluteMove({ camParams, ...coordinates }); + + const snapshotUrl = await CameraUtils.getSnapshotUri({ camParams }); + + const fileName = "image-" + new Date().getTime() + ".jpeg"; + const imagePath = path.resolve("images", fileName); + await downloadImage( + snapshotUrl.uri, + imagePath, + camParams.username, + camParams.password + ); + // const testImg = path.resolve("images", "test.png") + + // POST request with image to ocr + const bodyFormData = new FormData(); + bodyFormData.append("image", fs.createReadStream(imagePath)); + + const response = await axios.post(OCR_URL, bodyFormData, { + headers: { + ...bodyFormData.getHeaders(), + }, + }); + + // delete image + fs.unlink(imagePath, (err) => { + if (err) { + // TODO: Critical logger setup + console.error(err); + } + }); -} + return getSanitizedData(response.data.data); +}; const _getCamParams = (params) => { @@ -109,26 +112,23 @@ const _getCamParams = (params) => { return camParams; }; -export const updateObservationAuto = async (cameraParams, patientId)=> -{ - try{ - const cameraParamsSanitized = _getCamParams(cameraParams) +export const updateObservationAuto = async (cameraParams, bedId) => { + try { + const cameraParamsSanitized = _getCamParams(cameraParams); - const payload = await extractData(cameraParamsSanitized, patientId) + const payload = await extractData(cameraParamsSanitized, bedId); - return payload - } - catch(err) - { - console.log(err) + return payload; + } catch (err) { + console.log(err); return { - "spo2": null, - "ventilator_spo2": null, - "resp": null, - "pulse": null, - "temperature": null, - "bp": null - } + spo2: null, + ventilator_spo2: null, + resp: null, + pulse: null, + temperature: null, + bp: null, + }; } -} +}; diff --git a/src/automation/helper/getMonitorCoordinates.js b/src/automation/helper/getMonitorCoordinates.js index 9951531..f51bc91 100644 --- a/src/automation/helper/getMonitorCoordinates.js +++ b/src/automation/helper/getMonitorCoordinates.js @@ -2,45 +2,37 @@ import { PrismaClient } from "@prisma/client" const prisma = new PrismaClient() -export const getMonitorCoordinates = async (patientId) => { - - try{ - const bed = await prisma.bed.findFirst({ +export const getMonitorCoordinates = async (bedId) => { + try { + const preset = await prisma.preset.findFirst({ where: { - patientExternalId: { - equals: patientId - }, - deleted: { - equals: false - } - } - }) + bedId, + deleted: false, + }, + }); - if(!bed) - { + if (!preset) { + console.log(`Preset for bed ${bedId} not found`); - console.log("Bed not found") return { x: 0, y: 0, - zoom: 0 - } + zoom: 0, + }; } return { - x: bed.x, - y: bed.y, - zoom: bed.zoom - } - } - catch(err) - { - console.log(err?.response?.data) + x: preset.x, + y: preset.y, + zoom: preset.zoom, + }; + } catch (err) { + console.log(err?.response?.data); return { - x: 0, - y: 0, - zoom: 0 - } + x: 0, + y: 0, + zoom: 0, + }; } -} \ No newline at end of file +}; \ No newline at end of file diff --git a/src/controller/AssetConfigController.js b/src/controller/AssetConfigController.js index ca71a17..a26fc10 100644 --- a/src/controller/AssetConfigController.js +++ b/src/controller/AssetConfigController.js @@ -7,52 +7,95 @@ const prisma = new PrismaClient() export class AssetConfigController { static listAssets = async (req, res) => { - prisma.asset.findMany({ - where: { - deleted: { - equals: false - } - }, - orderBy: [ - { - updatedAt: "desc" - } - ] - }).then(assets => { - res.render("pages/assetList", { dayjs, assets, errors: req.flash("error") }); - }).catch(err => { - res.render("pages/assetList", { dayjs, assets: [], errors: [err.message] }); - }) + try { + const assets = await prisma.asset.findMany({ + where: { + deleted: false, + }, + orderBy: [{ updatedAt: "desc" }], + }); + + const beds = await prisma.bed.findMany({ + where: { + deleted: false, + }, + orderBy: [{ updatedAt: "desc" }], + }); + + res.render("pages/assetList", { + dayjs, + assets, + beds, + errors: req.flash("error"), + }); + } catch (err) { + res.render("pages/assetList", { + dayjs, + assets: [], + beds: [], + errors: [err.message], + }); + } } static createAsset = async (req, res) => { - const { name, description, ipAddress, externalId } = req.body; - prisma.asset.create({ - data: { - name, - description, - ipAddress, - externalId - } - }).then(_ => { - res.redirect("/assets"); - }).catch(err => { - req.flash("error", err.message); - res.redirect("/assets"); - }); + const { + name, + type, + description, + ipAddress, + externalId, + username, + password, + port, + } = req.body; + prisma.asset + .create({ + data: { + name, + type, + description, + ipAddress, + externalId, + username, + password, + port: Number(port), + }, + }) + .then((_) => { + res.redirect("/assets"); + }) + .catch((err) => { + req.flash("error", err.message); + res.redirect("/assets"); + }); } static updateAssetForm = async (req, res) => { - prisma.asset.findUnique({ - where: { - id: Number(req.params.id) - } - }).then(asset => { - res.render("pages/assetForm", { dayjs, asset, errors: req.flash("error") }); - }).catch(err => { + try { + const asset = await prisma.asset.findUniqueOrThrow({ + where: { + id: Number(req.params.id), + }, + }); + + const beds = await prisma.bed.findMany({ + where: { + deleted: false, + }, + orderBy: [{ updatedAt: "desc" }], + }); + + res.render("pages/assetForm", { + dayjs, + asset, + beds, + errors: req.flash("error"), + }); + } catch (err) { req.flash("error", err.message); res.redirect("/assets"); - }) + } } static updateAsset = async (req, res) => { diff --git a/src/controller/BedController.js b/src/controller/BedController.js new file mode 100644 index 0000000..6c00b68 --- /dev/null +++ b/src/controller/BedController.js @@ -0,0 +1,148 @@ +import { PrismaClient } from "@prisma/client"; +import dayjs from "dayjs"; + +const prisma = new PrismaClient(); + +export class BedController { + static list = async (req, res) => { + try { + const beds = await prisma.bed.findMany({ + where: { + deleted: false, + }, + include: { + monitorPreset: true, + }, + orderBy: [{ updatedAt: "desc" }], + }); + + res.render("pages/beds/list", { + dayjs, + beds, + errors: req.flash("error"), + }); + } catch (err) { + res.render("pages/beds/list", { + dayjs, + beds: [], + errors: [err.message], + }); + } + }; + + static create = async (req, res) => { + const { name, externalId, preset_x, preset_y, preset_zoom } = req.body; + + console.log(preset_x, preset_y, preset_zoom); + try { + await prisma.bed.create({ + data: { + name, + externalId, + monitorPreset: { + create: { + x: Number(preset_x), + y: Number(preset_y), + zoom: Number(preset_zoom), + }, + }, + }, + }); + + res.redirect("/beds"); + } catch (err) { + req.flash("error", err.message); + res.redirect("/beds"); + } + }; + + static show = async (req, res) => { + const { id } = req.params; + + try { + const bed = await prisma.bed.findUnique({ + where: { + id: Number(id), + }, + include: { + monitorPreset: true, + }, + }); + + res.render("pages/beds/edit", { + dayjs, + bed, + errors: req.flash("error"), + }); + } catch (err) { + req.flash("error", err.message); + res.redirect("/beds"); + } + }; + + static edit = async (req, res) => { + const { id } = req.params; + const { name, externalId, preset_x, preset_y, preset_zoom } = req.body; + + try { + await prisma.bed.update({ + where: { + id: Number(id), + }, + data: { + name, + externalId, + monitorPreset: { + update: { + x: Number(preset_x), + y: Number(preset_y), + zoom: Number(preset_zoom), + }, + }, + }, + }); + + res.redirect("/beds"); + } catch (err) { + req.flash("error", err.message); + res.redirect("/beds"); + } + }; + + static confirmDelete = async (req, res) => { + const { id } = req.params; + + try { + const bed = await prisma.bed.findUnique({ + where: { + id: Number(id), + }, + }); + + res.render("pages/beds/delete", { dayjs, bed }); + } catch (err) { + req.flash("error", err.message); + res.redirect("/beds"); + } + }; + + static delete = async (req, res) => { + const { id } = req.params; + + try { + await prisma.bed.update({ + where: { + id: Number(id), + }, + data: { + deleted: true, + }, + }); + + res.redirect("/beds"); + } catch (err) { + req.flash("error", err.message); + res.redirect("/beds"); + } + }; +} diff --git a/src/controller/ObservationController.js b/src/controller/ObservationController.js index 31a292f..820fe12 100644 --- a/src/controller/ObservationController.js +++ b/src/controller/ObservationController.js @@ -1,4 +1,8 @@ -import { getAsset, getPatientId } from "../utils/dailyRoundUtils.js"; +import { + getAsset, + getCameraByBedId, + getPatientId, +} from "../utils/dailyRoundUtils.js"; import { BadRequestException } from "../Exception/BadRequestException.js"; import { NotFoundException } from "../Exception/NotFoundException.js"; @@ -177,7 +181,7 @@ const updateObservationsToCare = async () => { continue; } - const { consultation_id, patient_id } = await getPatientId( + const { consultation_id, patient_id, bed_id } = await getPatientId( asset.externalId ); if (!patient_id) { @@ -286,24 +290,18 @@ const updateObservationsToCare = async () => { payload.rounds_type = "AUTOMATED"; try { - // make a JSON dump of payload comparision between the v1 and v2(auto) api + const camera = await getCameraByBedId(bed_id); const cameraParams = { - // TODO: change in prod - hostname: asset.ipAddress, - // hostname: "192.168.1.64", - // TODO: change in prod - username: asset.username, - // username: "remote_user", - // TODO: change in prod - password: asset.password, - // password: "2jCkrCRSeahzKEU", - port: asset.port ?? 80, + hostname: camera.ipAddress, + username: camera.username, + password: camera.password, + port: camera.port, }; console.log("updateObservationsToCare:cameraParams", cameraParams); - const v2Payload = await updateObservationAuto(cameraParams, patient_id); + const v2Payload = await updateObservationAuto(cameraParams, bed_id); await makeDataDumpToJson( payload, v2Payload, diff --git a/src/router/bedRouter.js b/src/router/bedRouter.js new file mode 100644 index 0000000..198c9ec --- /dev/null +++ b/src/router/bedRouter.js @@ -0,0 +1,13 @@ +import express from "express"; +import { BedController } from "../controller/BedController.js"; + +const router = express.Router(); + +router.get("/", BedController.list); +router.post("/", BedController.create); +router.get("/:id", BedController.show); +router.post("/:id", BedController.edit); +router.get("/:id/delete", BedController.confirmDelete); +router.post("/:id/delete", BedController.delete); + +export { router as bedRouter }; diff --git a/src/server.js b/src/server.js index 2ca1747..0ce3c07 100644 --- a/src/server.js +++ b/src/server.js @@ -26,6 +26,7 @@ import { swaggerSpec } from "./swagger/swagger.js"; import { morganWithWs } from "./middleware/morganWithWs.js"; import { serverStatusRouter } from "./router/serverStatusRouter.js"; import { healthRouter } from "./router/healthRouter.js"; +import { bedRouter } from "./router/bedRouter.js"; import { ServerStatusController } from "./controller/ServerStatusController.js"; import { getWs } from "./middleware/getWs.js"; @@ -41,14 +42,16 @@ app.set("view engine", "ejs"); app.set("views", path.join(path.resolve(), "src/views")); // flash messages -app.use(session({ - cookieName: "session", - secret: "ufhq7s-o1%^bn7j6wasec04-mjb*zv^&0@$lb3%9%w3t5pq3^3", - httpOnly: true, - maxAge: 1000 * 60 * 30, - resave: true, - saveUninitialized: true -})); +app.use( + session({ + cookieName: "session", + secret: "ufhq7s-o1%^bn7j6wasec04-mjb*zv^&0@$lb3%9%w3t5pq3^3", + httpOnly: true, + maxAge: 1000 * 60 * 30, + resave: true, + saveUninitialized: true, + }) +); app.use(flash()); app.use(getWs(ws)); @@ -57,7 +60,7 @@ app.use(cors()); app.options("*", cors()); app.use(helmet({ contentSecurityPolicy: false })); -app.use(express.json({limit: '50mb'})); +app.use(express.json({ limit: "50mb" })); // Sentry Sentry.init({ @@ -101,6 +104,7 @@ app.use(configRouter); app.use(assetConfigRouter); app.use(serverStatusRouter); app.use(healthRouter); +app.use("/beds", bedRouter); app.get("/.well-known/openid-configuration", openidConfigController); diff --git a/src/utils/dailyRoundUtils.js b/src/utils/dailyRoundUtils.js index 71c6fe2..6b44364 100644 --- a/src/utils/dailyRoundUtils.js +++ b/src/utils/dailyRoundUtils.js @@ -28,3 +28,29 @@ export const getPatientId = async (assetExternalId) => { return {} }) } + +// export const getAssetsByBedId = async (bedId) => { +// return await axios +// .get(`${careApi}/api/v1/assetbed/?bed=${bedId}`, { +// headers: await generateHeaders(), +// }) +// .then((res) => res.data) +// .catch((err) => { +// console.log(err?.response?.data); +// return {}; +// }); +// }; + + +export const getCameraByBedId = async (bedId) => { + return prisma.asset.findFirst({ + where: { + bedId, + assetType: "CAMERA", + deleted: false, + }, + include: { + Bed: true, + }, + }) +}; diff --git a/src/views/pages/assetForm.ejs b/src/views/pages/assetForm.ejs index b4e3c68..4413204 100644 --- a/src/views/pages/assetForm.ejs +++ b/src/views/pages/assetForm.ejs @@ -33,6 +33,14 @@ +
+ + +
@@ -45,6 +53,27 @@
+
+ + +
+
+ + +
+
+ + +
+
+ + +
diff --git a/src/views/pages/assetList.ejs b/src/views/pages/assetList.ejs index e767e01..93fe42e 100644 --- a/src/views/pages/assetList.ejs +++ b/src/views/pages/assetList.ejs @@ -23,11 +23,24 @@ Add New Asset

-
- - - - +
+ + + + + + + + +
@@ -36,6 +49,7 @@ ID Name + Type External ID IP address @@ -44,8 +58,10 @@ <% for (var i = 0; i < assets.length; i++) { %> + <% console.log(assets[i]) %> <%= assets[i].id %> <%= assets[i].name %> + <%= assets[i].type %> <%= assets[i].externalId %> <%= assets[i].ipAddress %> diff --git a/src/views/pages/beds/delete.ejs b/src/views/pages/beds/delete.ejs new file mode 100644 index 0000000..1897f37 --- /dev/null +++ b/src/views/pages/beds/delete.ejs @@ -0,0 +1,40 @@ + + + + <%- include('../../partials/head.ejs', { title: 'Teleicu Middleware | Delete Bed'}) + %> + + + <%- include("../../partials/header") %> +
+ + + Back To Beds + +

Delete Bed

+ + + +
+
+ + +
+
+ + +
+
+ Cancel + +
+
+
+ <%- include("../../partials/footer") %> + + \ No newline at end of file diff --git a/src/views/pages/beds/edit.ejs b/src/views/pages/beds/edit.ejs new file mode 100644 index 0000000..45d5d0a --- /dev/null +++ b/src/views/pages/beds/edit.ejs @@ -0,0 +1,62 @@ + + + + <%- include('../../partials/head.ejs', { title: 'Teleicu Middleware | Update Bed'}) + %> + + + <%- include("../../partials/header") %> +
+ + + Back To Beds + +

Update Bed

+ + + + <% if(errors.length > 0){ %> + + <% } %> + +
+
+ + +
+
+ + +
+ +

Update Monitor Preset

+
+ + +
+
+ + +
+
+ + +
+ +
+ +
+ +

+ <%- include("../../partials/footer") %> + + diff --git a/src/views/pages/beds/list.ejs b/src/views/pages/beds/list.ejs new file mode 100644 index 0000000..2967624 --- /dev/null +++ b/src/views/pages/beds/list.ejs @@ -0,0 +1,72 @@ + + + + <%- include('../../partials/head.ejs', { title: 'Teleicu Middleware | Bed Config'}) + %> + + + <%- include("../../partials/header") %> +
+ <%- include("../../partials/pageTitle",{title:"Bed Config"}) %> + + <% if(errors.length > 0){ %> + + <% } %> + +
+

+

+ Add New Bed +

+

+
+ + + +
+

Add Monitor Preset

+ + + + +
+ + +
+
+ + + + + + + + + + + <% beds.forEach((bed) => { %> + + + + + <% if (bed.monitorPreset) { %> + + <% } else { %> + + <% } %> + + + + <% }) %> +
IDNameExternal IDMonitor PresetUpdated AtActions
<%= bed.id %><%= bed.name %><%= bed.externalId %><%= bed.monitorPreset.x %>, <%= bed.monitorPreset.y %>, <%= bed.monitorPreset.zoom %>Not Available<%= dayjs(bed.updatedAt).format('DD/MM/YYYY hh:mm:ss a') %> + Edit + Delete +
+
+ <%- include("../../partials/footer") %> + +