Skip to content

Commit fc24582

Browse files
authored
bring dev changes to master; prepare for release v24.12.12 (#4428)
2 parents 89eb8ef + dd7fc3a commit fc24582

12 files changed

+98
-21
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
"""remove non english rows from report data (IRONN-264)
2+
3+
Revision ID: 4f5daa2b48db
4+
Revises: 5a300be640fb
5+
Create Date: 2024-11-25 13:48:01.321510
6+
7+
"""
8+
from alembic import op
9+
10+
11+
# revision identifiers, used by Alembic.
12+
revision = '4f5daa2b48db'
13+
down_revision = '5a300be640fb'
14+
15+
16+
def upgrade():
17+
connection = op.get_bind()
18+
connection.execute("DELETE FROM research_data WHERE NOT (data->>'timepoint' ~ '^(Baseline|Month)');")
19+
# next run of `cache_research_data()` will pick up those just deleted.
20+
21+
22+
def downgrade():
23+
# no point in bringing those back
24+
pass

portal/models/qb_timeline.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -1316,8 +1316,8 @@ def qb_status_visit_name(user_id, research_study_id, as_of_date):
13161316
13171317
:returns: dictionary with key/values for:
13181318
status: string like 'expired'
1319-
visit_name: for the period, i.e. '3 months'
1320-
action_state: 'not applicable', or status of follow up action
1319+
visit_name: for the period, i.e. '3 months'. ALWAYS in english, clients must translate
1320+
action_state: 'not applicable', or status of follow-up action
13211321
13221322
"""
13231323
from .research_study import EMPRO_RS_ID

portal/models/questionnaire_bank.py

+18-4
Original file line numberDiff line numberDiff line change
@@ -592,6 +592,10 @@ def qbs_by_rp(rp_id, classification):
592592

593593

594594
def visit_name(qbd):
595+
"""returns string repr of visit, i.e. 'Month 3' or 'Baseline'
596+
597+
NB - only returns english version. See `translate_visit_name()`
598+
"""
595599
from .research_study import (
596600
EMPRO_RS_ID,
597601
research_study_id_from_questionnaire,
@@ -617,12 +621,22 @@ def visit_name(qbd):
617621
clm += (clrd.years * 12) if clrd.years else 0
618622
total = clm * qbd.iteration + sm
619623
if rs_id == EMPRO_RS_ID:
620-
return _('Month %(month_total)d', month_total=total+1)
621-
return _('Month %(month_total)d', month_total=total)
624+
return f'Month {total+1}'
625+
return f'Month {total}'
622626

623627
if rs_id == EMPRO_RS_ID:
624-
return _('Month %(month_total)d', month_total=1)
625-
return _(qbd.questionnaire_bank.classification.title())
628+
return 'Month 1'
629+
return qbd.questionnaire_bank.classification.title()
630+
631+
632+
def translate_visit_name(visit_name):
633+
"""parse the english version of visit name for front end translation needs"""
634+
if not visit_name:
635+
return visit_name
636+
if visit_name.startswith('Month '):
637+
number = int(visit_name[6:])
638+
return _('Month %(month_total)d', month_total=number)
639+
return _(visit_name)
626640

627641

628642
def add_static_questionnaire_bank():

portal/models/questionnaire_response.py

+10-4
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
QuestionnaireBank,
2929
QuestionnaireBankQuestionnaire,
3030
trigger_date,
31+
translate_visit_name,
3132
visit_name,
3233
)
3334
from .research_data import ResearchData
@@ -401,7 +402,7 @@ def extensions(self):
401402
relative_start=None, iteration=self.qb_iteration,
402403
recur_id=recur_id, qb_id=self.questionnaire_bank_id)
403404
results.append({
404-
'visit_name': visit_name(qbd),
405+
'visit_name': translate_visit_name(visit_name(qbd)),
405406
'url': TRUENTH_VISIT_NAME_EXTENSION})
406407

407408
expires_at = expires(self.subject_id, qbd)
@@ -909,11 +910,16 @@ def qnr_document_id(
909910
QuestionnaireResponse.subject_id == subject_id).filter(
910911
QuestionnaireResponse.document[
911912
('questionnaire', 'reference')
912-
].astext.endswith(questionnaire_name)).filter(
913-
QuestionnaireResponse.questionnaire_bank_id ==
914-
questionnaire_bank_id).with_entities(
913+
].astext.endswith(questionnaire_name)).with_entities(
915914
QuestionnaireResponse.document[(
916915
'identifier', 'value')])
916+
if questionnaire_name != 'irondemog_v3':
917+
# Another special indefinite workaround. irondemog_v3 happens to live
918+
# in multiple questionnaire banks, thus the lookup will fail when
919+
# restricted by QB.id, should the org have transitioned since the user
920+
# left work incomplete from the previous protocol (TN-2747)
921+
qnr = qnr.filter(QuestionnaireResponse.questionnaire_bank_id == questionnaire_bank_id)
922+
917923
if iteration is not None:
918924
qnr = qnr.filter(QuestionnaireResponse.qb_iteration == iteration)
919925
else:

portal/models/reporting.py

+14-2
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66

77
from flask import current_app
88
from flask_babel import force_locale
9-
from flask_login import login_manager
109
from werkzeug.exceptions import Unauthorized
1110

1211
from ..audit import auditable_event
@@ -30,12 +29,25 @@
3029
qnr_csv_column_headers,
3130
generate_qnr_csv,
3231
)
33-
from .research_study import BASE_RS_ID, EMPRO_RS_ID
32+
from .research_study import BASE_RS_ID, EMPRO_RS_ID, ResearchStudy
3433
from .role import ROLE, Role
3534
from .user import User, UserRoles, patients_query
3635
from .user_consent import consent_withdrawal_dates
3736

3837

38+
def update_patient_adherence_data(patient_id):
39+
"""Cache invalidation and force rebuild for given patient's adherence data
40+
41+
NB - any timeline or questionnaire response data changes are invalidated and
42+
updated as part of `invalidate_users_QBT()`. This function is for edge cases
43+
such as changing a user's study-id.
44+
"""
45+
patient = User.query.get(patient_id)
46+
AdherenceData.query.filter(AdherenceData.patient_id==patient_id).delete()
47+
for rs_id in ResearchStudy.assigned_to(patient):
48+
single_patient_adherence_data(patient_id=patient_id, research_study_id=rs_id)
49+
50+
3951
def single_patient_adherence_data(patient_id, research_study_id):
4052
"""Update any missing (from cache) adherence data for patient
4153

portal/static/js/src/admin.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -608,6 +608,7 @@ let requestTimerId = 0;
608608
this.filterOptionsList.forEach((o) => {
609609
for (const [key, values] of Object.entries(o)) {
610610
values.forEach((value) => {
611+
if (!value[1]) return true;
611612
if (
612613
$(
613614
`#adminTable .bootstrap-table-filter-control-${key} option[value='${value[0]}']`
@@ -1412,7 +1413,7 @@ let requestTimerId = 0;
14121413
max_attempts: 1,
14131414
},
14141415
function (result) {
1415-
if (!result?.error) self.currentTablePreference = data;
1416+
if (result && !result.error) self.currentTablePreference = data;
14161417
if (callback) callback();
14171418
}
14181419
);

portal/templates/admin/admin_base.html

+1
Original file line numberDiff line numberDiff line change
@@ -107,5 +107,6 @@
107107
// placeholder variables
108108
var qStatusFilterOptions = {};
109109
var clinicianActionStateFilterOptions = {};
110+
var visitOptions = {};
110111
</script>
111112
{%- endmacro -%}

portal/templates/admin/patients_by_org.html

+1-1
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ <h4 class="tnth-headline">{{_("Patient List")}}</h4>
5656
<th data-field="email" data-sortable="true" data-class="email-field" data-filter-control="input">{{ _("Email") }}</th>
5757
{% if 'status' in config.PATIENT_LIST_ADDL_FIELDS %}
5858
<th data-field="questionnaire_status" data-sortable="true" data-card-visible="false" data-searchable="true" data-width="5%" data-class="status-field" data-filter-control="select" data-filter-strict-search="true" data-filter-data="var:qStatusFilterOptions">{{ _("Questionnaire Status") }}</th>
59-
<th data-field="visit" data-sortable="true" data-card-visible="false" data-searchable="true" data-width="5%" data-class="visit-field" data-filter-control="input">{{ _("Visit") }}</th>
59+
<th data-field="visit" data-sortable="true" data-card-visible="false" data-searchable="true" data-width="5%" data-class="visit-field" data-filter-control="select" data-filter-data="var:visitOptions">{{ _("Visit") }}</th>
6060
{% endif %}
6161
{% if 'study_id' in config.PATIENT_LIST_ADDL_FIELDS %}<th data-field="study_id" data-sortable="true" data-searchable="true" data-class="study-id-field" data-filter-control="input" data-sorter="tnthTables.alphanumericSorter" data-width="5%">{{ _("Study ID") }}</th>{% endif %}
6262
<th data-field="consentdate" data-sortable="true" data-card-visible="false" data-sorter="tnthTables.dateSorter" data-searchable="true" data-class="consentdate-field text-center">{{ _("Study Consent Date") }}</th>

portal/templates/admin/patients_substudy.html

+1-1
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ <h2>{{list_title}}</h2>
5959
<th data-field="birthdate" data-sortable="true" data-class="birthdate-field" data-visible="false">{{ _("Date of Birth") }}</th>
6060
<th data-field="clinician" data-sortable="true" data-class="clinician-field" data-filter-control="input">{{ _("Treating Clinician") }}</th>
6161
<th data-field="empro_status" data-sortable="true" data-card-visible="true" data-searchable="true" data-width="5%" data-class="status-field" data-filter-control="select" data-filter-strict-search="true" data-filter-data="var:qStatusFilterOptions">{{_("EMPRO Questionnaire Status")}}</th>
62-
<th data-field="empro_visit" data-sortable="true" data-card-visible="false" data-searchable="true" data-width="5%" data-class="visit-field" data-filter-control="input" data-visible="false">{{ _("Visit") }}</th>
62+
<th data-field="empro_visit" data-sortable="true" data-card-visible="false" data-searchable="true" data-width="5%" data-class="visit-field" data-filter-control="select" data-visible="false" data-filter-data="var:visitOptions">{{ _("Visit") }}</th>
6363
<th data-field="action_state" data-sortable="true" data-class="intervention-actions-field" data-filter-control="select" data-filter-data="var:clinicianActionStateFilterOptions">{{ _("Clinician Action Status") }}</th>
6464
<th data-field="study_id" data-sortable="true" data-searchable="true" data-class="study-id-field" data-filter-control="input" data-visible="false" data-sorter="tnthTables.alphanumericSorter" data-width="5%">{{ _("Study ID") }}</th>
6565
<th data-field="empro_consentdate" data-sortable="true" data-card-visible="false" data-sorter="tnthTables.dateSorter" data-searchable="true" data-visible="false" data-class="consentdate-field text-center" data-visible="true">{{ _("Study Consent Date") }}</th>

portal/views/demographics.py

+6-2
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,9 @@
77
from ..database import db
88
from ..extensions import oauth
99
from ..models.reference import MissingReference
10+
from ..models.reporting import update_patient_adherence_data
1011
from ..models.user import current_user, get_user
12+
from ..models.role import ROLE
1113
from .crossdomain import crossdomain
1214

1315
demographics_api = Blueprint('demographics_api', __name__, url_prefix='/api')
@@ -176,7 +178,9 @@ def demographics_set(patient_id):
176178
patient_id, json.dumps(request.json)), user_id=current_user().id,
177179
subject_id=patient_id, context='user')
178180

179-
# update the patient_table cache with any change from above
180-
patient_list_update_patient(patient_id)
181+
# update the respective cache tables with any change from above
182+
if patient.has_role(ROLE.PATIENT.value):
183+
patient_list_update_patient(patient_id)
184+
update_patient_adherence_data(patient_id)
181185

182186
return jsonify(patient.as_fhir(include_empties=False))

portal/views/patient.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -328,7 +328,7 @@ def patient_timeline(patient_id):
328328
)
329329
from ..models.qbd import QBD
330330
from ..models.qb_status import QB_Status
331-
from ..models.questionnaire_bank import visit_name
331+
from ..models.questionnaire_bank import translate_visit_name, visit_name
332332
from ..models.questionnaire_response import aggregate_responses
333333
from ..models.research_protocol import ResearchProtocol
334334
from ..tasks import cache_single_patient_adherence_data
@@ -392,7 +392,7 @@ def patient_timeline(patient_id):
392392
'at': FHIR_datetime.as_fhir(qbt.at),
393393
'qb (id, iteration)': "{} ({}, {})".format(
394394
qbd.questionnaire_bank.name, qbd.qb_id, qbd.iteration),
395-
'visit': visit_name(qbd)}
395+
'visit': translate_visit_name(visit_name(qbd))}
396396
if qbt.status == OverallStatus.due:
397397
data['questionnaires'] = ','.join(
398398
[q.name for q in qbd.questionnaire_bank.questionnaires])

portal/views/patients.py

+17-2
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
from ..models.intervention import Intervention
1818
from ..models.organization import Organization, OrgTree
1919
from ..models.patient_list import PatientList
20+
from ..models.questionnaire_bank import translate_visit_name
2021
from ..models.qb_status import patient_research_study_status
2122
from ..models.role import ROLE
2223
from ..models.research_study import EMPRO_RS_ID, ResearchStudy
@@ -181,11 +182,25 @@ def requested_orgs(user, research_study_id):
181182
distinct_action = PatientList.query.distinct(PatientList.action_state).with_entities(
182183
PatientList.action_state)
183184
options.append({"action_state": [(state[0], _(state[0])) for state in distinct_action]})
185+
distinct_visits = PatientList.query.distinct(PatientList.empro_visit).with_entities(
186+
PatientList.empro_visit)
187+
sorted_visits = sorted(
188+
[v[0] for v in distinct_visits if v[0]],
189+
key=lambda x: (0 if not x.split()[-1].isdigit() else int(x.split()[-1]))
190+
)
191+
options.append({"empro_visit": [(visit, translate_visit_name(visit)) for visit in sorted_visits]})
184192
else:
185193
distinct_status = PatientList.query.distinct(
186194
PatientList.questionnaire_status).with_entities(PatientList.questionnaire_status)
187195
options.append(
188196
{"questionnaire_status": [(status[0], _(status[0])) for status in distinct_status]})
197+
distinct_visits = PatientList.query.distinct(PatientList.visit).with_entities(
198+
PatientList.visit)
199+
sorted_visits = sorted(
200+
[v[0] for v in distinct_visits if v[0]],
201+
key=lambda x: (0 if not x.split()[-1].isdigit() else int(x.split()[-1]))
202+
)
203+
options.append({"visit": [(visit, translate_visit_name(visit)) for visit in sorted_visits]})
189204

190205
viewable_orgs = requested_orgs(user, research_study_id)
191206
query = PatientList.query.filter(PatientList.org_id.in_(viewable_orgs))
@@ -222,8 +237,8 @@ def requested_orgs(user, research_study_id):
222237
"questionnaire_status": _(row.questionnaire_status),
223238
"empro_status": _(row.empro_status),
224239
"action_state": _(row.action_state),
225-
"visit": row.visit,
226-
"empro_visit": row.empro_visit,
240+
"visit": translate_visit_name(row.visit),
241+
"empro_visit": translate_visit_name(row.empro_visit),
227242
"study_id": row.study_id,
228243
"consentdate": row.consentdate,
229244
"empro_consentdate": row.empro_consentdate,

0 commit comments

Comments
 (0)