feat: Patient Progress Page (#22474)

* feat: add patient progress page

* feat: patient progress sidebar

* feat: Patient Progress Charts

* feat: set up sidebar links

* feat: added heatmap chart for patient interactions

* fix: styles

* fix: add markers for max score in assessment charts

* fix(style): mobile view css

* fix: heatmap and percentage chart filters

* feat: add time span filters to line charts

* fix: make date fields mandatory in healthcare doctypes for better analytics

* fix: title and filter styles

* fix: handle null state for charts

* feat: add Patient Progress Page to desk

* feat: add date range filter to all charts

* fix: code clean-up

* fix: assign roles for Patient Progress Page

Co-authored-by: Nabin Hait <nabinhait@gmail.com>
This commit is contained in:
Rucha Mahabal 2020-07-24 10:49:04 +05:30 committed by GitHub
parent ecb1460440
commit 1010feefe0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 1046 additions and 6 deletions

View File

@ -38,7 +38,7 @@
{
"hidden": 0,
"label": "Records and History",
"links": "[\n\t{\n\t\t\"type\": \"page\",\n\t\t\"name\": \"patient_history\",\n\t\t\"label\": \"Patient History\"\n\t},\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Patient Medical Record\",\n\t\t\"label\": \"Patient Medical Record\"\n\t},\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Inpatient Record\",\n\t\t\"label\": \"Inpatient Record\"\n\t}\n]"
"links": "[\n\t{\n\t\t\"type\": \"page\",\n\t\t\"name\": \"patient_history\",\n\t\t\"label\": \"Patient History\"\n\t},\n\t{\n\t\t\"type\": \"page\",\n\t\t\"name\": \"patient-progress\",\n\t\t\"label\": \"Patient Progress\"\n\t},\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Patient Medical Record\",\n\t\t\"label\": \"Patient Medical Record\"\n\t},\n\t{\n\t\t\"type\": \"doctype\",\n\t\t\"name\": \"Inpatient Record\",\n\t\t\"label\": \"Inpatient Record\"\n\t}\n]"
},
{
"hidden": 0,
@ -64,7 +64,7 @@
"idx": 0,
"is_standard": 1,
"label": "Healthcare",
"modified": "2020-05-28 19:02:28.824995",
"modified": "2020-06-25 23:50:56.951698",
"modified_by": "Administrator",
"module": "Healthcare",
"name": "Healthcare",

View File

@ -172,3 +172,15 @@ def get_patient_detail(patient):
if vital_sign:
details.update(vital_sign[0])
return details
def get_timeline_data(doctype, name):
"""Return timeline data from medical records"""
return dict(frappe.db.sql('''
SELECT
unix_timestamp(communication_date), count(*)
FROM
`tabPatient Medical Record`
WHERE
patient=%s
and `communication_date` > date_sub(curdate(), interval 1 year)
GROUP BY communication_date''', name))

View File

@ -63,7 +63,8 @@
{
"fieldname": "assessment_datetime",
"fieldtype": "Datetime",
"label": "Assessment Datetime"
"label": "Assessment Datetime",
"reqd": 1
},
{
"fieldname": "section_break_7",
@ -139,7 +140,7 @@
],
"is_submittable": 1,
"links": [],
"modified": "2020-05-25 14:38:38.302399",
"modified": "2020-06-25 00:25:13.208400",
"modified_by": "Administrator",
"module": "Healthcare",
"name": "Patient Assessment",

View File

@ -5,6 +5,7 @@
from __future__ import unicode_literals
import frappe
from frappe.model.document import Document
from frappe.utils import today
class TherapyPlan(Document):
def validate(self):
@ -45,4 +46,6 @@ def make_therapy_session(therapy_plan, patient, therapy_type):
therapy_session.rate = therapy_type.rate
therapy_session.exercises = therapy_type.exercises
if frappe.flags.in_test:
therapy_session.start_date = today()
return therapy_session.as_dict()

View File

@ -154,7 +154,8 @@
{
"fieldname": "start_date",
"fieldtype": "Date",
"label": "Start Date"
"label": "Start Date",
"reqd": 1
},
{
"fieldname": "start_time",
@ -219,7 +220,7 @@
],
"is_submittable": 1,
"links": [],
"modified": "2020-06-29 14:33:34.836594",
"modified": "2020-06-30 10:56:10.354268",
"modified_by": "Administrator",
"module": "Healthcare",
"name": "Therapy Session",

View File

@ -0,0 +1,165 @@
/* sidebar */
.layout-side-section .frappe-control[data-fieldname='patient'] {
max-width: 300px;
}
.patient-image-container {
margin-top: 17px;
}
.patient-image {
display: inline-block;
width: 100%;
height: 0;
padding: 50% 0px;
background-size: cover;
background-repeat: no-repeat;
background-position: center center;
border-radius: 4px;
}
.patient-details {
margin: -5px 5px;
}
.important-links {
margin: 30px 5px;
}
.patient-name {
font-size: 20px;
}
/* heatmap */
.heatmap-container {
height: 170px;
}
.patient-heatmap {
width: 80%;
display: inline-block;
}
.patient-heatmap .chart-container {
margin-left: 30px;
}
.patient-heatmap .frappe-chart {
margin-top: 5px;
}
.patient-heatmap .frappe-chart .chart-legend {
display: none;
}
.heatmap-container .chart-filter {
position: relative;
top: 5px;
margin-right: 10px;
}
/* percentage chart */
.percentage-chart-container {
height: 130px;
}
.percentage-chart-container .chart-filter {
position: relative;
top: 5px;
margin-right: 10px;
}
.therapy-session-percentage-chart .frappe-chart {
position: absolute;
top: 5px;
}
/* line charts */
.date-field .clearfix {
display: none;
}
.date-field .help-box {
display: none;
}
.date-field .frappe-control {
margin-bottom: 0px !important;
}
.date-field .form-group {
margin-bottom: 0px !important;
}
/* common */
text.title {
text-transform: uppercase;
font-size: 11px;
margin-left: 20px;
margin-top: 20px;
display: block;
}
.chart-filter-search {
margin-left: 35px;
width: 25%;
}
.chart-column-container {
border-bottom: 1px solid #d1d8dd;
margin: 5px 0;
}
.line-chart-container .frappe-chart {
margin-top: -20px;
}
.line-chart-container {
margin-bottom: 20px;
}
.chart-control {
align-self: center;
display: flex;
flex-direction: row-reverse;
margin-top: -25px;
}
.chart-control > * {
margin-right: 10px;
}
/* mobile */
@media (max-width: 991px) {
.patient-progress-sidebar {
display: flex;
}
.percentage-chart-container {
border-top: 1px solid #d1d8dd;
}
.percentage-chart-container .chart-filter {
position: relative;
top: 12px;
margin-right: 10px;
}
.patient-progress-sidebar .important-links {
margin: 0;
}
.patient-progress-sidebar .patient-details {
width: 50%;
}
.chart-filter-search {
width: 40%;
}
}

View File

@ -0,0 +1,68 @@
<div class="row patient-progress">
<div class="col-md-12">
<div class="progress-graphs">
<div class="chart-column-container heatmap-container hidden-xs hidden-sm">
<div class="patient-heatmap"></div>
</div>
<div class="chart-column-container percentage-chart-container">
<div class="therapy-session-percentage-chart"></div>
</div>
<div class="therapy-progress">
<div class="chart-head">
<text class="title" text-anchor="start">Therapy Progress</text>
<div class="chart-control pull-right"></div>
</div>
<div class="row">
<div class="chart-filter-search therapy-type-search"></div>
</div>
<div class="col-md-12 chart-column-container line-chart-container">
<div class="therapy-progress-line-chart">
</div>
</div>
</div>
<div class="assessment-results">
<div class="chart-head">
<text class="title" text-anchor="start">Assessment Results</text>
<div class="chart-control pull-right"></div>
</div>
<div class="row">
<div class="chart-filter-search assessment-template-search"></div>
</div>
<div class="col-md-12 chart-column-container line-chart-container">
<div class="assessment-results-line-chart">
</div>
</div>
</div>
<div class="therapy-assessment-correlation progress-line-chart">
<div class="chart-head">
<text class="title" text-anchor="start">Therapy Type and Assessment Correlation</text>
<div class="chart-control pull-right"></div>
</div>
<div class="row">
<div class="chart-filter-search assessment-correlation-template-search"></div>
</div>
<div class="col-md-12 chart-column-container line-chart-container">
<div class="therapy-assessment-correlation-chart">
</div>
</div>
</div>
<div class="assessment-parameter-progress progress-line-chart">
<div class="chart-head">
<text class="title" text-anchor="start">Assessment Parameter Wise Progress</text>
<div class="chart-control pull-right"></div>
</div>
<div class="row">
<div class="chart-filter-search assessment-parameter-search"></div>
</div>
<div class="col-md-12 line-chart-container">
<div class="assessment-parameter-progress-chart">
</div>
</div>
</div>
</div>
</div>
</div>

View File

@ -0,0 +1,531 @@
frappe.pages['patient-progress'].on_page_load = function(wrapper) {
frappe.ui.make_app_page({
parent: wrapper,
title: __('Patient Progress')
});
let patient_progress = new PatientProgress(wrapper);
$(wrapper).bind('show', ()=> {
patient_progress.show();
});
};
class PatientProgress {
constructor(wrapper) {
this.wrapper = $(wrapper);
this.page = wrapper.page;
this.sidebar = this.wrapper.find('.layout-side-section');
this.main_section = this.wrapper.find('.layout-main-section');
}
show() {
frappe.breadcrumbs.add('Healthcare');
this.sidebar.empty();
let me = this;
let patient = frappe.ui.form.make_control({
parent: me.sidebar,
df: {
fieldtype: 'Link',
options: 'Patient',
fieldname: 'patient',
placeholder: __('Select Patient'),
only_select: true,
change: () => {
me.patient_id = '';
if (me.patient_id != patient.get_value() && patient.get_value()) {
me.start = 0;
me.patient_id = patient.get_value();
me.make_patient_profile();
}
}
}
});
patient.refresh();
if (frappe.route_options && !this.patient) {
patient.set_value(frappe.route_options.patient);
this.patient_id = frappe.route_options.patient;
}
this.sidebar.find('[data-fieldname="patient"]').append('<div class="patient-info"></div>');
}
make_patient_profile() {
this.page.set_title(__('Patient Progress'));
this.main_section.empty().append(frappe.render_template('patient_progress'));
this.render_patient_details();
this.render_heatmap();
this.render_percentage_chart('therapy_type', 'Therapy Type Distribution');
this.create_percentage_chart_filters();
this.show_therapy_progress();
this.show_assessment_results();
this.show_therapy_assessment_correlation();
this.show_assessment_parameter_progress();
}
get_patient_info() {
return frappe.xcall('frappe.client.get', {
doctype: 'Patient',
name: this.patient_id
}).then((patient) => {
if (patient) {
this.patient = patient;
}
});
}
get_therapy_sessions_count() {
return frappe.xcall(
'erpnext.healthcare.page.patient_progress.patient_progress.get_therapy_sessions_count', {
patient: this.patient_id,
}
).then(data => {
if (data) {
this.total_therapy_sessions = data.total_therapy_sessions;
this.therapy_sessions_this_month = data.therapy_sessions_this_month;
}
});
}
render_patient_details() {
this.get_patient_info().then(() => {
this.get_therapy_sessions_count().then(() => {
$('.patient-info').empty().append(frappe.render_template('patient_progress_sidebar', {
patient_image: this.patient.image,
patient_name: this.patient.patient_name,
patient_gender: this.patient.sex,
patient_mobile: this.patient.mobile,
total_therapy_sessions: this.total_therapy_sessions,
therapy_sessions_this_month: this.therapy_sessions_this_month
}));
this.setup_patient_profile_links();
});
});
}
setup_patient_profile_links() {
this.wrapper.find('.patient-profile-link').on('click', () => {
frappe.set_route('Form', 'Patient', this.patient_id);
});
this.wrapper.find('.therapy-plan-link').on('click', () => {
frappe.route_options = {
'patient': this.patient_id,
'docstatus': 1
};
frappe.set_route('List', 'Therapy Plan');
});
this.wrapper.find('.patient-history').on('click', () => {
frappe.route_options = {
'patient': this.patient_id
};
frappe.set_route('patient_history');
});
}
render_heatmap() {
this.heatmap = new frappe.Chart('.patient-heatmap', {
type: 'heatmap',
countLabel: 'Interactions',
data: {},
discreteDomains: 0
});
this.update_heatmap_data();
this.create_heatmap_chart_filters();
}
update_heatmap_data(date_from) {
frappe.xcall('erpnext.healthcare.page.patient_progress.patient_progress.get_patient_heatmap_data', {
patient: this.patient_id,
date: date_from || frappe.datetime.year_start(),
}).then((data) => {
this.heatmap.update( {dataPoints: data} );
});
}
create_heatmap_chart_filters() {
this.get_patient_info().then(() => {
let filters = [
{
label: frappe.dashboard_utils.get_year(frappe.datetime.now_date()),
options: frappe.dashboard_utils.get_years_since_creation(this.patient.creation),
action: (selected_item) => {
this.update_heatmap_data(frappe.datetime.obj_to_str(selected_item));
}
},
];
frappe.dashboard_utils.render_chart_filters(filters, 'chart-filter', '.heatmap-container');
});
}
render_percentage_chart(field, title) {
frappe.xcall(
'erpnext.healthcare.page.patient_progress.patient_progress.get_therapy_sessions_distribution_data', {
patient: this.patient_id,
field: field
}
).then(chart => {
if (chart.labels.length) {
this.percentage_chart = new frappe.Chart('.therapy-session-percentage-chart', {
title: title,
type: 'percentage',
data: {
labels: chart.labels,
datasets: chart.datasets
},
truncateLegends: 1,
barOptions: {
height: 11,
depth: 1
},
height: 160,
maxSlices: 8,
colors: ['#5e64ff', '#743ee2', '#ff5858', '#ffa00a', '#feef72', '#28a745', '#98d85b', '#a9a7ac'],
});
} else {
this.wrapper.find('.percentage-chart-container').hide();
}
});
}
create_percentage_chart_filters() {
let filters = [
{
label: 'Therapy Type',
options: ['Therapy Type', 'Exercise Type'],
fieldnames: ['therapy_type', 'exercise_type'],
action: (selected_item, fieldname) => {
let title = selected_item + ' Distribution';
this.render_percentage_chart(fieldname, title);
}
},
];
frappe.dashboard_utils.render_chart_filters(filters, 'chart-filter', '.percentage-chart-container');
}
create_time_span_filters(action_method, parent) {
let chart_control = $(parent).find('.chart-control');
let filters = [
{
label: 'Last Month',
options: ['Select Date Range', 'Last Week', 'Last Month', 'Last Quarter', 'Last Year'],
action: (selected_item) => {
if (selected_item === 'Select Date Range') {
this.render_date_range_fields(action_method, chart_control);
} else {
// hide date range field if visible
let date_field = $(parent).find('.date-field');
if (date_field.is(':visible')) {
date_field.hide();
}
this[action_method](selected_item);
}
}
}
];
frappe.dashboard_utils.render_chart_filters(filters, 'chart-filter', chart_control, 1);
}
render_date_range_fields(action_method, parent) {
let date_field = $(parent).find('.date-field');
if (!date_field.length) {
let date_field_wrapper = $(
`<div class="date-field pull-right"></div>`
).appendTo(parent);
let date_range_field = frappe.ui.form.make_control({
df: {
fieldtype: 'DateRange',
fieldname: 'from_date',
placeholder: 'Date Range',
input_class: 'input-xs',
reqd: 1,
change: () => {
let selected_date_range = date_range_field.get_value();
if (selected_date_range && selected_date_range.length === 2) {
this[action_method](selected_date_range);
}
}
},
parent: date_field_wrapper,
render_input: 1
});
} else if (!date_field.is(':visible')) {
date_field.show();
}
}
show_therapy_progress() {
let me = this;
let therapy_type = frappe.ui.form.make_control({
parent: $('.therapy-type-search'),
df: {
fieldtype: 'Link',
options: 'Therapy Type',
fieldname: 'therapy_type',
placeholder: __('Select Therapy Type'),
only_select: true,
change: () => {
if (me.therapy_type != therapy_type.get_value() && therapy_type.get_value()) {
me.therapy_type = therapy_type.get_value();
me.render_therapy_progress_chart();
}
}
}
});
therapy_type.refresh();
this.create_time_span_filters('render_therapy_progress_chart', '.therapy-progress');
}
render_therapy_progress_chart(time_span='Last Month') {
if (!this.therapy_type) return;
frappe.xcall(
'erpnext.healthcare.page.patient_progress.patient_progress.get_therapy_progress_data', {
patient: this.patient_id,
therapy_type: this.therapy_type,
time_span: time_span
}
).then(chart => {
let data = {
labels: chart.labels,
datasets: chart.datasets
}
let parent = '.therapy-progress-line-chart';
if (!chart.labels.length) {
this.show_null_state(parent);
} else {
if (!this.therapy_line_chart) {
this.therapy_line_chart = new frappe.Chart(parent, {
type: 'axis-mixed',
height: 250,
data: data,
lineOptions: {
regionFill: 1
},
axisOptions: {
xIsSeries: 1
},
});
} else {
$(parent).find('.chart-container').show();
$(parent).find('.chart-empty-state').hide();
this.therapy_line_chart.update(data);
}
}
});
}
show_assessment_results() {
let me = this;
let assessment_template = frappe.ui.form.make_control({
parent: $('.assessment-template-search'),
df: {
fieldtype: 'Link',
options: 'Patient Assessment Template',
fieldname: 'assessment_template',
placeholder: __('Select Assessment Template'),
only_select: true,
change: () => {
if (me.assessment_template != assessment_template.get_value() && assessment_template.get_value()) {
me.assessment_template = assessment_template.get_value();
me.render_assessment_result_chart();
}
}
}
});
assessment_template.refresh();
this.create_time_span_filters('render_assessment_result_chart', '.assessment-results');
}
render_assessment_result_chart(time_span='Last Month') {
if (!this.assessment_template) return;
frappe.xcall(
'erpnext.healthcare.page.patient_progress.patient_progress.get_patient_assessment_data', {
patient: this.patient_id,
assessment_template: this.assessment_template,
time_span: time_span
}
).then(chart => {
let data = {
labels: chart.labels,
datasets: chart.datasets,
yMarkers: [
{ label: 'Max Score', value: chart.max_score }
],
}
let parent = '.assessment-results-line-chart';
if (!chart.labels.length) {
this.show_null_state(parent);
} else {
if (!this.assessment_line_chart) {
this.assessment_line_chart = new frappe.Chart(parent, {
type: 'axis-mixed',
height: 250,
data: data,
lineOptions: {
regionFill: 1
},
axisOptions: {
xIsSeries: 1
},
tooltipOptions: {
formatTooltipY: d => d + __(' out of ') + chart.max_score
}
});
} else {
$(parent).find('.chart-container').show();
$(parent).find('.chart-empty-state').hide();
this.assessment_line_chart.update(data);
}
}
});
}
show_therapy_assessment_correlation() {
let me = this;
let assessment = frappe.ui.form.make_control({
parent: $('.assessment-correlation-template-search'),
df: {
fieldtype: 'Link',
options: 'Patient Assessment Template',
fieldname: 'assessment',
placeholder: __('Select Assessment Template'),
only_select: true,
change: () => {
if (me.assessment != assessment.get_value() && assessment.get_value()) {
me.assessment = assessment.get_value();
me.render_therapy_assessment_correlation_chart();
}
}
}
});
assessment.refresh();
this.create_time_span_filters('render_therapy_assessment_correlation_chart', '.therapy-assessment-correlation');
}
render_therapy_assessment_correlation_chart(time_span='Last Month') {
if (!this.assessment) return;
frappe.xcall(
'erpnext.healthcare.page.patient_progress.patient_progress.get_therapy_assessment_correlation_data', {
patient: this.patient_id,
assessment_template: this.assessment,
time_span: time_span
}
).then(chart => {
let data = {
labels: chart.labels,
datasets: chart.datasets,
yMarkers: [
{ label: 'Max Score', value: chart.max_score }
],
}
let parent = '.therapy-assessment-correlation-chart';
if (!chart.labels.length) {
this.show_null_state(parent);
} else {
if (!this.correlation_chart) {
this.correlation_chart = new frappe.Chart(parent, {
type: 'axis-mixed',
height: 300,
data: data,
axisOptions: {
xIsSeries: 1
}
});
} else {
$(parent).find('.chart-container').show();
$(parent).find('.chart-empty-state').hide();
this.correlation_chart.update(data);
}
}
});
}
show_assessment_parameter_progress() {
let me = this;
let parameter = frappe.ui.form.make_control({
parent: $('.assessment-parameter-search'),
df: {
fieldtype: 'Link',
options: 'Patient Assessment Parameter',
fieldname: 'assessment',
placeholder: __('Select Assessment Parameter'),
only_select: true,
change: () => {
if (me.parameter != parameter.get_value() && parameter.get_value()) {
me.parameter = parameter.get_value();
me.render_assessment_parameter_progress_chart();
}
}
}
});
parameter.refresh();
this.create_time_span_filters('render_assessment_parameter_progress_chart', '.assessment-parameter-progress');
}
render_assessment_parameter_progress_chart(time_span='Last Month') {
if (!this.parameter) return;
frappe.xcall(
'erpnext.healthcare.page.patient_progress.patient_progress.get_assessment_parameter_data', {
patient: this.patient_id,
parameter: this.parameter,
time_span: time_span
}
).then(chart => {
let data = {
labels: chart.labels,
datasets: chart.datasets
}
let parent = '.assessment-parameter-progress-chart';
if (!chart.labels.length) {
this.show_null_state(parent);
} else {
if (!this.parameter_chart) {
this.parameter_chart = new frappe.Chart(parent, {
type: 'line',
height: 250,
data: data,
lineOptions: {
regionFill: 1
},
axisOptions: {
xIsSeries: 1
},
tooltipOptions: {
formatTooltipY: d => d + '%'
}
});
} else {
$(parent).find('.chart-container').show();
$(parent).find('.chart-empty-state').hide();
this.parameter_chart.update(data);
}
}
});
}
show_null_state(parent) {
let null_state = $(parent).find('.chart-empty-state');
if (null_state.length) {
$(null_state).show();
} else {
null_state = $(
`<div class="chart-empty-state text-muted text-center" style="margin-bottom: 20px;">${__(
"No Data..."
)}</div>`
);
$(parent).append(null_state);
}
$(parent).find('.chart-container').hide();
}
}

View File

@ -0,0 +1,33 @@
{
"content": null,
"creation": "2020-06-12 15:46:23.111928",
"docstatus": 0,
"doctype": "Page",
"idx": 0,
"modified": "2020-07-23 21:45:45.540055",
"modified_by": "Administrator",
"module": "Healthcare",
"name": "patient-progress",
"owner": "Administrator",
"page_name": "patient-progress",
"restrict_to_domain": "Healthcare",
"roles": [
{
"role": "Healthcare Administrator"
},
{
"role": "Physician"
},
{
"role": "Patient"
},
{
"role": "System Manager"
}
],
"script": null,
"standard": "Yes",
"style": null,
"system_page": 0,
"title": "Patient Progress"
}

View File

@ -0,0 +1,197 @@
import frappe
from datetime import datetime
from frappe import _
from frappe.utils import getdate, get_timespan_date_range
import json
@frappe.whitelist()
def get_therapy_sessions_count(patient):
total = frappe.db.count('Therapy Session', filters={
'docstatus': 1,
'patient': patient
})
month_start = datetime.today().replace(day=1)
this_month = frappe.db.count('Therapy Session', filters={
'creation': ['>', month_start],
'docstatus': 1,
'patient': patient
})
return {
'total_therapy_sessions': total,
'therapy_sessions_this_month': this_month
}
@frappe.whitelist()
def get_patient_heatmap_data(patient, date):
return dict(frappe.db.sql("""
SELECT
unix_timestamp(communication_date), count(*)
FROM
`tabPatient Medical Record`
WHERE
communication_date > subdate(%(date)s, interval 1 year) and
communication_date < subdate(%(date)s, interval -1 year) and
patient = %(patient)s
GROUP BY communication_date
ORDER BY communication_date asc""", {'date': date, 'patient': patient}))
@frappe.whitelist()
def get_therapy_sessions_distribution_data(patient, field):
if field == 'therapy_type':
result = frappe.db.get_all('Therapy Session',
filters = {'patient': patient, 'docstatus': 1},
group_by = field,
order_by = field,
fields = [field, 'count(*)'],
as_list = True)
elif field == 'exercise_type':
data = frappe.db.get_all('Therapy Session', filters={
'docstatus': 1,
'patient': patient
}, as_list=True)
therapy_sessions = [entry[0] for entry in data]
result = frappe.db.get_all('Exercise',
filters = {
'parenttype': 'Therapy Session',
'parent': ['in', therapy_sessions],
'docstatus': 1
},
group_by = field,
order_by = field,
fields = [field, 'count(*)'],
as_list = True)
return {
'labels': [r[0] for r in result if r[0] != None],
'datasets': [{
'values': [r[1] for r in result]
}]
}
@frappe.whitelist()
def get_therapy_progress_data(patient, therapy_type, time_span):
date_range = get_date_range(time_span)
query_values = {'from_date': date_range[0], 'to_date': date_range[1], 'therapy_type': therapy_type, 'patient': patient}
result = frappe.db.sql("""
SELECT
start_date, total_counts_targeted, total_counts_completed
FROM
`tabTherapy Session`
WHERE
start_date BETWEEN %(from_date)s AND %(to_date)s and
docstatus = 1 and
therapy_type = %(therapy_type)s and
patient = %(patient)s
ORDER BY start_date""", query_values, as_list=1)
return {
'labels': [r[0] for r in result if r[0] != None],
'datasets': [
{ 'name': _('Targetted'), 'values': [r[1] for r in result if r[0] != None] },
{ 'name': _('Completed'), 'values': [r[2] for r in result if r[0] != None] }
]
}
@frappe.whitelist()
def get_patient_assessment_data(patient, assessment_template, time_span):
date_range = get_date_range(time_span)
query_values = {'from_date': date_range[0], 'to_date': date_range[1], 'assessment_template': assessment_template, 'patient': patient}
result = frappe.db.sql("""
SELECT
assessment_datetime, total_score, total_score_obtained
FROM
`tabPatient Assessment`
WHERE
DATE(assessment_datetime) BETWEEN %(from_date)s AND %(to_date)s and
docstatus = 1 and
assessment_template = %(assessment_template)s and
patient = %(patient)s
ORDER BY assessment_datetime""", query_values, as_list=1)
return {
'labels': [getdate(r[0]) for r in result if r[0] != None],
'datasets': [
{ 'name': _('Score Obtained'), 'values': [r[2] for r in result if r[0] != None] }
],
'max_score': result[0][1] if result else None
}
@frappe.whitelist()
def get_therapy_assessment_correlation_data(patient, assessment_template, time_span):
date_range = get_date_range(time_span)
query_values = {'from_date': date_range[0], 'to_date': date_range[1], 'assessment': assessment_template, 'patient': patient}
result = frappe.db.sql("""
SELECT
therapy.therapy_type, count(*), avg(assessment.total_score_obtained), total_score
FROM
`tabPatient Assessment` assessment INNER JOIN `tabTherapy Session` therapy
ON
assessment.therapy_session = therapy.name
WHERE
DATE(assessment.assessment_datetime) BETWEEN %(from_date)s AND %(to_date)s and
assessment.docstatus = 1 and
assessment.patient = %(patient)s and
assessment.assessment_template = %(assessment)s
GROUP BY therapy.therapy_type
""", query_values, as_list=1)
return {
'labels': [r[0] for r in result if r[0] != None],
'datasets': [
{ 'name': _('Sessions'), 'chartType': 'bar', 'values': [r[1] for r in result if r[0] != None] },
{ 'name': _('Average Score'), 'chartType': 'line', 'values': [round(r[2], 2) for r in result if r[0] != None] }
],
'max_score': result[0][1] if result else None
}
@frappe.whitelist()
def get_assessment_parameter_data(patient, parameter, time_span):
date_range = get_date_range(time_span)
query_values = {'from_date': date_range[0], 'to_date': date_range[1], 'parameter': parameter, 'patient': patient}
results = frappe.db.sql("""
SELECT
assessment.assessment_datetime,
sheet.score,
template.scale_max
FROM
`tabPatient Assessment Sheet` sheet
INNER JOIN `tabPatient Assessment` assessment
ON sheet.parent = assessment.name
INNER JOIN `tabPatient Assessment Template` template
ON template.name = assessment.assessment_template
WHERE
DATE(assessment.assessment_datetime) BETWEEN %(from_date)s AND %(to_date)s and
assessment.docstatus = 1 and
sheet.parameter = %(parameter)s and
assessment.patient = %(patient)s
ORDER BY
assessment.assessment_datetime asc
""", query_values, as_list=1)
score_percentages = []
for r in results:
if r[2] != 0 and r[0] != None:
score = round((int(r[1]) / int(r[2])) * 100, 2)
score_percentages.append(score)
return {
'labels': [getdate(r[0]) for r in results if r[0] != None],
'datasets': [
{ 'name': _('Score'), 'values': score_percentages }
]
}
def get_date_range(time_span):
try:
time_span = json.loads(time_span)
return time_span
except json.decoder.JSONDecodeError:
return get_timespan_date_range(time_span.lower())

View File

@ -0,0 +1,29 @@
<div class="patient-progress-sidebar">
<div class="patient-image-container">
{% if patient_image %}
<div class="patient-image" src={{patient_image}} style="background-image: url(\'{%= patient_image %}\')"></div>
{% endif %}
</div>
<div class="patient-details">
{% if patient_name %}
<p class="patient-name bold">{{patient_name}}</p>
{% endif %}
{% if patient_gender %}
<p class="patient-gender text-muted">{%=__("Gender: ") %} {{patient_gender}}</p>
{% endif %}
{% if patient_mobile %}
<p class="patient-mobile text-muted">{%=__("Contact: ") %} {{patient_mobile}}</p>
{% endif %}
{% if total_therapy_sessions %}
<p class="patient-sessions text-muted">{%=__("Total Therapy Sessions: ") %} {{total_therapy_sessions}}</p>
{% endif %}
{% if therapy_sessions_this_month %}
<p class="patient-sessions text-muted">{%=__("Monthly Therapy Sessions: ") %} {{therapy_sessions_this_month}}</p>
{% endif %}
</div>
<div class="important-links">
<p><a class="patient-profile-link">{%=__("Patient Profile") %}</a></p>
<p><a class="therapy-plan-link">{%=__("Therapy Plan") %}</a></p>
<p><a class="patient-history">{%=__("Patient History") %}</a></p>
</div>
</div>