Compare commits
3 Commits
7f5ed98386
...
2c28b85a10
Author | SHA1 | Date | |
---|---|---|---|
2c28b85a10 | |||
cc1b8341d6 | |||
4196581a95 |
@ -37,8 +37,9 @@
|
|||||||
<button type="button" class="btn btn-sm btn-primary" @click="x_date = timeshift_month(x_date, 24*(reversed ? -1 : +1))">🡢</button>
|
<button type="button" class="btn btn-sm btn-primary" @click="x_date = timeshift_month(x_date, 24*(reversed ? -1 : +1))">🡢</button>
|
||||||
</template>
|
</template>
|
||||||
<template v-if="x_range == 'Range'">
|
<template v-if="x_range == 'Range'">
|
||||||
|
<input type="date" class="form-control" v-model="disp_date_end" v-if="direction < 0" />
|
||||||
<input type="date" class="form-control" v-model="disp_date" />
|
<input type="date" class="form-control" v-model="disp_date" />
|
||||||
<input type="date" class="form-control" v-model="disp_date_end" />
|
<input type="date" class="form-control" v-model="disp_date_end" v-if="direction >= 0" />
|
||||||
<DateRangePickerRange v-model="x_range" :direction="direction" />
|
<DateRangePickerRange v-model="x_range" :direction="direction" />
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
|
@ -14,7 +14,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="card mb-3 shadow">
|
<div class="card mb-3 shadow">
|
||||||
<div class="card-header d-flex justify-content-between align-items-center">
|
<div class="card-header d-flex justify-content-between align-items-center">
|
||||||
<span>Vitals</span>
|
<span>Data</span>
|
||||||
<DateRangePicker range="1M" direction="-1" v-model:date="vitals_date" v-model:date_end="vitals_date_begin" />
|
<DateRangePicker range="1M" direction="-1" v-model:date="vitals_date" v-model:date_end="vitals_date_begin" />
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
|
@ -79,7 +79,7 @@
|
|||||||
</style>
|
</style>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { uniq, groupByArray, quantile_sorted } from './util.mjs';
|
import { uniq, groupByArray, quantile_sorted, inherit } from './util.mjs';
|
||||||
|
|
||||||
function isNumeric(x) {
|
function isNumeric(x) {
|
||||||
return (x !== '') && (x !== false) && (x !== null) && (!isNaN(x));
|
return (x !== '') && (x !== false) && (x !== null) && (!isNaN(x));
|
||||||
@ -133,6 +133,14 @@
|
|||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: false
|
default: false
|
||||||
},
|
},
|
||||||
|
constants: {
|
||||||
|
type: Object,
|
||||||
|
default: {}
|
||||||
|
},
|
||||||
|
calculations: {
|
||||||
|
type: Array,
|
||||||
|
default: []
|
||||||
|
},
|
||||||
reports: {
|
reports: {
|
||||||
type: Array,
|
type: Array,
|
||||||
default: []
|
default: []
|
||||||
@ -142,8 +150,32 @@
|
|||||||
return {};
|
return {};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
resultset_all() {
|
||||||
|
return this.resultset_calculated.length > 0 ? this.resultset.concat(this.resultset_calculated) : this.resultset;
|
||||||
|
},
|
||||||
|
resultset_calculated() {
|
||||||
|
var self = this, snapshots = [], results = [], history, update, item;
|
||||||
|
groupByArray(this.resultset, x => x.time).map(group => group.values.reduce(((acc, x) => (acc.values[x.name] = x, acc)), { key: group.key, datehdr: date_header(group.key), values: {}})).sort((a, b) => (a.key > b.key) - (a.key < b.key)).forEach(function(group) {
|
||||||
|
snapshots.push({ key: group.key, values: history = Object.assign(snapshots.length > 0 ? inherit(snapshots[snapshots.length - 1].values) : inherit(self.constants), update = group.values) });
|
||||||
|
history['Time'] = update['Time'] = { time: group.key, value: group.key };
|
||||||
|
for(var i = 0; i < self.calculations.length; ++i) {
|
||||||
|
var calculation = self.calculations[i], deps = calculation.deps;
|
||||||
|
for(var j = deps.length - 1, satisfied = true, updated = false; j >= 0; --j) if(!history[deps[j]]) { satisfied = false; break; }
|
||||||
|
else if(update[deps[j]]) updated = true;
|
||||||
|
if((satisfied) && (updated)) {
|
||||||
|
item = calculation.calc(...calculation.deps.map(x => history[x].value), history[calculation.name] && history[calculation.name].value);
|
||||||
|
if((item !== undefined) && (item !== null)) {
|
||||||
|
results.push(history[calculation.name] = update[calculation.name] = Object.assign({ time: group.key, value: item }, calculation));
|
||||||
|
if((item.hasOwnProperty('rangeL')) && (item.value < item.rangeL)) item.flag = 'L';
|
||||||
|
else if((item.hasOwnProperty('rangeH')) && (item.value > item.rangeH)) item.flag = 'H';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return results;
|
||||||
|
},
|
||||||
groups() {
|
groups() {
|
||||||
if(this.daily) return groupByArray(this.resultset, x => new Date(x.time.getFullYear(), x.time.getMonth(), x.time.getDate())).map(function(group) {
|
if(this.daily) return groupByArray(this.resultset_all, x => new Date(x.time.getFullYear(), x.time.getMonth(), x.time.getDate())).map(function(group) {
|
||||||
group = group.values.reduce(((acc, x) => ((acc.values[x.name] || (acc.values[x.name] = [])).push(x), acc)), { key: group.key, datehdr: date_header(group.key), values: {}});
|
group = group.values.reduce(((acc, x) => ((acc.values[x.name] || (acc.values[x.name] = [])).push(x), acc)), { key: group.key, datehdr: date_header(group.key), values: {}});
|
||||||
for(var k in group.values) if(group.values.hasOwnProperty(k)) {
|
for(var k in group.values) if(group.values.hasOwnProperty(k)) {
|
||||||
var items = group.values[k].sort((a, b) => a.time - b.time);
|
var items = group.values[k].sort((a, b) => a.time - b.time);
|
||||||
@ -165,10 +197,10 @@
|
|||||||
}
|
}
|
||||||
return group;
|
return group;
|
||||||
}).sort((a, b) => (a.key > b.key) - (a.key < b.key));
|
}).sort((a, b) => (a.key > b.key) - (a.key < b.key));
|
||||||
else return groupByArray(this.resultset, x => x.time).map(group => group.values.reduce(((acc, x) => (acc.values[x.name] = x, acc)), { key: group.key, datehdr: date_header(group.key), values: {}})).sort((a, b) => (a.key > b.key) - (a.key < b.key));
|
else return groupByArray(this.resultset_all, x => x.time).map(group => group.values.reduce(((acc, x) => (acc.values[x.name] = x, acc)), { key: group.key, datehdr: date_header(group.key), values: {}})).sort((a, b) => (a.key > b.key) - (a.key < b.key));
|
||||||
},
|
},
|
||||||
names() {
|
names() {
|
||||||
var res = uniq(this.resultset.map(x => x.name));
|
var res = uniq(this.resultset_all.map(x => x.name));
|
||||||
return res.reduce((acc, x) => (acc[x] = true, acc), res);
|
return res.reduce((acc, x) => (acc[x] = true, acc), res);
|
||||||
},
|
},
|
||||||
names_excluded() {
|
names_excluded() {
|
||||||
@ -177,7 +209,7 @@
|
|||||||
return res;
|
return res;
|
||||||
},
|
},
|
||||||
statistics() {
|
statistics() {
|
||||||
return statistics(this.resultset);
|
return statistics(this.resultset_all);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
|
@ -3,8 +3,11 @@
|
|||||||
<label class="form-check form-check-inline form-switch form-check-label" v-for="report in reports">
|
<label class="form-check form-check-inline form-switch form-check-label" v-for="report in reports">
|
||||||
<input class="form-check-input" type="checkbox" v-model="report.selected" /> {{report.name}}
|
<input class="form-check-input" type="checkbox" v-model="report.selected" /> {{report.name}}
|
||||||
</label>
|
</label>
|
||||||
|
<label class="form-check form-check-inline form-switch form-check-label">
|
||||||
|
<input class="form-check-input" type="checkbox" v-model="calculate" /> Calculate
|
||||||
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<ViewData :resultset="resultset" :daily="true" :reports="reports_selected" />
|
<ViewData :resultset="resultset" :daily="true" :constants="calculate ? constants : []" :calculations="calculate ? calculations : []" :reports="reports_selected" />
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
@ -12,10 +15,19 @@
|
|||||||
|
|
||||||
import ViewData from './ViewData.vue';
|
import ViewData from './ViewData.vue';
|
||||||
|
|
||||||
|
const time_min = new Date(1700, 0, 1);
|
||||||
|
|
||||||
|
const calculations = [
|
||||||
|
{ name: 'Age', unit: 'yr', deps: ['Time', 'DOB'], calc(Time, DOB, prev) { var x = Math.floor((Time - DOB.getTime())/3.15576e10); return x != prev ? x : undefined; } },
|
||||||
|
{ name: 'BMI', unit: 'kg/m²', rangeL: 18.5, rangeH: 24.9, range: '18.5 - 24.9', deps: ['Ht', 'Wt'], calc: (Ht, Wt) => (10000*Wt/(Ht*Ht)).toPrecision(3) },
|
||||||
|
{ name: 'BSA', unit: 'm²', deps: ['Ht', 'Wt'], calc: (Ht, Wt) => (0.007184*Math.pow(Ht, 0.725)*Math.pow(Wt, 0.425)).toPrecision(3) },
|
||||||
|
{ name: 'CrCl', unit: 'mL/min', deps: ['Age', 'Sex', 'Wt', 'CREATININE'], calc: (Age, Sex, Wt, CREATININE) => (((140 - Age) * Wt)/(72*CREATININE)*(Sex == 'M' ? 1 : 0.85)).toPrecision(4) }
|
||||||
|
];
|
||||||
|
|
||||||
const reports = [
|
const reports = [
|
||||||
{ name: 'Vitals', value: ['T', 'P', 'R', 'SBP', 'DBP', 'Pulse Oximetry', 'Wt', 'Ht', 'Pain'], selected: true },
|
{ name: 'Vitals', value: ['T', 'P', 'R', 'SBP', 'DBP', 'Pulse Oximetry', 'Wt', 'Ht', 'BMI', 'BSA', 'Pain'], selected: true },
|
||||||
{ name: 'CBC', value: ['HGB', 'MCV', 'PLT', 'WBC', 'NEUTROPHIL#'], selected: false },
|
{ name: 'CBC', value: ['HGB', 'MCV', 'PLT', 'WBC', 'NEUTROPHIL#'], selected: false },
|
||||||
{ name: 'Renal', value: ['CREATININE', 'UREA NITROGEN', 'EGFR CKD-EPI 2021', 'Estimated GFR dc\'d 3/30/2022'], selected: false },
|
{ name: 'Renal', value: ['CREATININE', 'UREA NITROGEN', 'EGFR CKD-EPI 2021', 'Estimated GFR dc\'d 3/30/2022', 'CrCl'], selected: false },
|
||||||
{ name: 'Hepatic', value: ['SGOT', 'SGPT', 'LDH', 'ALKALINE PHOSPHATASE', 'GAMMA-GTP', 'TOT. BILIRUBIN', 'DIR. BILIRUBIN', 'ALBUMIN'], selected: false },
|
{ name: 'Hepatic', value: ['SGOT', 'SGPT', 'LDH', 'ALKALINE PHOSPHATASE', 'GAMMA-GTP', 'TOT. BILIRUBIN', 'DIR. BILIRUBIN', 'ALBUMIN'], selected: false },
|
||||||
{ name: 'Electrolytes', value: ['SODIUM', 'CHLORIDE', 'CO2', 'CALCIUM', 'IONIZED CALCIUM (LABCORP)', 'POTASSIUM', 'MAGNESIUM', 'PO4', 'ANION GAP', 'OSMOBLD'], selected: false },
|
{ name: 'Electrolytes', value: ['SODIUM', 'CHLORIDE', 'CO2', 'CALCIUM', 'IONIZED CALCIUM (LABCORP)', 'POTASSIUM', 'MAGNESIUM', 'PO4', 'ANION GAP', 'OSMOBLD'], selected: false },
|
||||||
{ name: 'Coagulation', value: ['PT', 'INR', 'PTT'], selected: false },
|
{ name: 'Coagulation', value: ['PT', 'INR', 'PTT'], selected: false },
|
||||||
@ -71,20 +83,29 @@
|
|||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
|
patientinfo: null,
|
||||||
resultset: null,
|
resultset: null,
|
||||||
reports
|
calculate: true,
|
||||||
|
calculations, reports
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
params() {
|
params() {
|
||||||
return { dfn: this.dfn, date_begin: strftime_vista(this.date_begin), date_end: strftime_vista(this.date_end) };
|
return { dfn: this.dfn, date_begin: strftime_vista(this.date_begin), date_end: strftime_vista(this.date_end) };
|
||||||
},
|
},
|
||||||
|
constants() {
|
||||||
|
return this.patientinfo ? {
|
||||||
|
DOB: { time: time_min, value: strptime_vista(this.patientinfo.dob) },
|
||||||
|
Sex: { time: time_min, value: this.patientinfo.sex }
|
||||||
|
} : {};
|
||||||
|
},
|
||||||
reports_selected() {
|
reports_selected() {
|
||||||
return this.reports.filter(x => x.selected).map(x => x.value);
|
return this.reports.filter(x => x.selected).map(x => x.value);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
async params(value, oldvalue) {
|
async params(value, oldvalue) {
|
||||||
|
this.patientinfo = await this.client.ORWPT16_ID_INFO(value.dfn);
|
||||||
this.resultset = vitals_normalize(await this.client.GMV_EXTRACT_REC(value.dfn, value.date_end, value.date_begin)).concat(labs_normalize(await this.client.ORWLRR_INTERIM_RESULTS(value.dfn, value.date_end, value.date_begin)));
|
this.resultset = vitals_normalize(await this.client.GMV_EXTRACT_REC(value.dfn, value.date_end, value.date_begin)).concat(labs_normalize(await this.client.ORWLRR_INTERIM_RESULTS(value.dfn, value.date_end, value.date_begin)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -134,7 +134,7 @@ export function measurement_parse(data) {
|
|||||||
res.datetime = new Date(row.substring(idx + 1, idx = row.indexOf(' ', idx)));
|
res.datetime = new Date(row.substring(idx + 1, idx = row.indexOf(' ', idx)));
|
||||||
res.name = row.substring(idx + 3, idx = row.indexOf(': ', idx));
|
res.name = row.substring(idx + 3, idx = row.indexOf(': ', idx));
|
||||||
value = row.substring(idx + 4, idx = row.indexOf(' _', idx));
|
value = row.substring(idx + 4, idx = row.indexOf(' _', idx));
|
||||||
res.user = row.substring(idx + 3);
|
res.user = row.substring(idx + 2);
|
||||||
m = value.match(/^(?:(.*?)(?: (\S+))?)(\*)?(?: \((?:(.*?)(?: (\S+))?)\))?\s*$/);
|
m = value.match(/^(?:(.*?)(?: (\S+))?)(\*)?(?: \((?:(.*?)(?: (\S+))?)\))?\s*$/);
|
||||||
res.value = m[4] ? m[4] : m[1];
|
res.value = m[4] ? m[4] : m[1];
|
||||||
res.unit = m[4] ? m[5] : m[2];
|
res.unit = m[4] ? m[5] : m[2];
|
||||||
|
@ -77,3 +77,9 @@ export function debounce(fn, delay) {
|
|||||||
clock = setTimeout(function() { fn.apply(self, args) }, delay);
|
clock = setTimeout(function() { fn.apply(self, args) }, delay);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function Descendant() {}
|
||||||
|
export function inherit(obj) {
|
||||||
|
Descendant.prototype = obj;
|
||||||
|
return new Descendant();
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user