From 1010feefe023d7908e25c9dd32dd7ba47bdadc34 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Fri, 24 Jul 2020 10:49:04 +0530 Subject: [PATCH] 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 --- .../desk_page/healthcare/healthcare.json | 4 +- erpnext/healthcare/doctype/patient/patient.py | 12 + .../patient_assessment.json | 5 +- .../doctype/therapy_plan/therapy_plan.py | 3 + .../therapy_session/therapy_session.json | 5 +- .../page/patient_progress/__init__.py | 0 .../patient_progress/patient_progress.css | 165 ++++++ .../patient_progress/patient_progress.html | 68 +++ .../page/patient_progress/patient_progress.js | 531 ++++++++++++++++++ .../patient_progress/patient_progress.json | 33 ++ .../page/patient_progress/patient_progress.py | 197 +++++++ .../patient_progress_sidebar.html | 29 + 12 files changed, 1046 insertions(+), 6 deletions(-) create mode 100644 erpnext/healthcare/page/patient_progress/__init__.py create mode 100644 erpnext/healthcare/page/patient_progress/patient_progress.css create mode 100644 erpnext/healthcare/page/patient_progress/patient_progress.html create mode 100644 erpnext/healthcare/page/patient_progress/patient_progress.js create mode 100644 erpnext/healthcare/page/patient_progress/patient_progress.json create mode 100644 erpnext/healthcare/page/patient_progress/patient_progress.py create mode 100644 erpnext/healthcare/page/patient_progress/patient_progress_sidebar.html diff --git a/erpnext/healthcare/desk_page/healthcare/healthcare.json b/erpnext/healthcare/desk_page/healthcare/healthcare.json index 334b65563bc..6546b08db99 100644 --- a/erpnext/healthcare/desk_page/healthcare/healthcare.json +++ b/erpnext/healthcare/desk_page/healthcare/healthcare.json @@ -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", diff --git a/erpnext/healthcare/doctype/patient/patient.py b/erpnext/healthcare/doctype/patient/patient.py index 30a1e45f0ee..63dd8d4793a 100644 --- a/erpnext/healthcare/doctype/patient/patient.py +++ b/erpnext/healthcare/doctype/patient/patient.py @@ -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)) diff --git a/erpnext/healthcare/doctype/patient_assessment/patient_assessment.json b/erpnext/healthcare/doctype/patient_assessment/patient_assessment.json index 15c94344e9b..eb0021ff758 100644 --- a/erpnext/healthcare/doctype/patient_assessment/patient_assessment.json +++ b/erpnext/healthcare/doctype/patient_assessment/patient_assessment.json @@ -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", diff --git a/erpnext/healthcare/doctype/therapy_plan/therapy_plan.py b/erpnext/healthcare/doctype/therapy_plan/therapy_plan.py index c19be17ba8d..e0f015f3d7b 100644 --- a/erpnext/healthcare/doctype/therapy_plan/therapy_plan.py +++ b/erpnext/healthcare/doctype/therapy_plan/therapy_plan.py @@ -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() \ No newline at end of file diff --git a/erpnext/healthcare/doctype/therapy_session/therapy_session.json b/erpnext/healthcare/doctype/therapy_session/therapy_session.json index c75d9342ef1..dc0cafcf9c7 100644 --- a/erpnext/healthcare/doctype/therapy_session/therapy_session.json +++ b/erpnext/healthcare/doctype/therapy_session/therapy_session.json @@ -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", diff --git a/erpnext/healthcare/page/patient_progress/__init__.py b/erpnext/healthcare/page/patient_progress/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/erpnext/healthcare/page/patient_progress/patient_progress.css b/erpnext/healthcare/page/patient_progress/patient_progress.css new file mode 100644 index 00000000000..5d85a7487fd --- /dev/null +++ b/erpnext/healthcare/page/patient_progress/patient_progress.css @@ -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%; + } +} diff --git a/erpnext/healthcare/page/patient_progress/patient_progress.html b/erpnext/healthcare/page/patient_progress/patient_progress.html new file mode 100644 index 00000000000..c20537ea81d --- /dev/null +++ b/erpnext/healthcare/page/patient_progress/patient_progress.html @@ -0,0 +1,68 @@ +
+
+
+ +
+
+
+ +
+
+ Therapy Progress +
+
+
+ +
+
+
+
+
+
+ +
+
+ Assessment Results +
+
+
+ +
+
+
+
+
+
+ +
+
+ Therapy Type and Assessment Correlation +
+
+
+ +
+
+
+
+
+
+ +
+
+ Assessment Parameter Wise Progress +
+
+
+ +
+
+
+
+
+
+
+
+
\ No newline at end of file diff --git a/erpnext/healthcare/page/patient_progress/patient_progress.js b/erpnext/healthcare/page/patient_progress/patient_progress.js new file mode 100644 index 00000000000..2410b0ce845 --- /dev/null +++ b/erpnext/healthcare/page/patient_progress/patient_progress.js @@ -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('
'); + } + + 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 = $( + `
` + ).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 = $( + `
${__( + "No Data..." + )}
` + ); + $(parent).append(null_state); + } + $(parent).find('.chart-container').hide(); + } +} \ No newline at end of file diff --git a/erpnext/healthcare/page/patient_progress/patient_progress.json b/erpnext/healthcare/page/patient_progress/patient_progress.json new file mode 100644 index 00000000000..0175cb9c457 --- /dev/null +++ b/erpnext/healthcare/page/patient_progress/patient_progress.json @@ -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" +} \ No newline at end of file diff --git a/erpnext/healthcare/page/patient_progress/patient_progress.py b/erpnext/healthcare/page/patient_progress/patient_progress.py new file mode 100644 index 00000000000..a04fb2b592a --- /dev/null +++ b/erpnext/healthcare/page/patient_progress/patient_progress.py @@ -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()) + diff --git a/erpnext/healthcare/page/patient_progress/patient_progress_sidebar.html b/erpnext/healthcare/page/patient_progress/patient_progress_sidebar.html new file mode 100644 index 00000000000..cd62dd39035 --- /dev/null +++ b/erpnext/healthcare/page/patient_progress/patient_progress_sidebar.html @@ -0,0 +1,29 @@ +
+
+ {% if patient_image %} +
+ {% endif %} +
+
+ {% if patient_name %} +

{{patient_name}}

+ {% endif %} + {% if patient_gender %} +

{%=__("Gender: ") %} {{patient_gender}}

+ {% endif %} + {% if patient_mobile %} +

{%=__("Contact: ") %} {{patient_mobile}}

+ {% endif %} + {% if total_therapy_sessions %} +

{%=__("Total Therapy Sessions: ") %} {{total_therapy_sessions}}

+ {% endif %} + {% if therapy_sessions_this_month %} +

{%=__("Monthly Therapy Sessions: ") %} {{therapy_sessions_this_month}}

+ {% endif %} +
+ +
\ No newline at end of file