diff --git a/portal/models/assessment_status.py b/portal/models/assessment_status.py
index cb86669116..bcde6d9da3 100644
--- a/portal/models/assessment_status.py
+++ b/portal/models/assessment_status.py
@@ -11,11 +11,12 @@
from .user import User
-def recent_qnr_status(user, questionnaire_name):
+def recent_qnr_status(user, questionnaire_name, qb):
"""Look up recent status/timestamp for matching QuestionnaireResponse
:param user: Patient to whom completed QuestionnaireResponses belong
:param questionnaire_name: name of associated questionnaire
+ :param qb: QuestionnaireBank of associated questionnaire
:return: dictionary with authored (timestamp) of the most recent
QuestionnaireResponse keyed by status found
@@ -26,7 +27,8 @@ def recent_qnr_status(user, questionnaire_name):
).filter(
QuestionnaireResponse.document[
('questionnaire', 'reference')
- ].astext.endswith(questionnaire_name)
+ ].astext.endswith(questionnaire_name),
+ QuestionnaireResponse.questionnaire_bank_id == qb.id
).order_by(
QuestionnaireResponse.status,
QuestionnaireResponse.authored).limit(9).with_entities(
@@ -96,7 +98,7 @@ def qb_status_dict(user, questionnaire_bank, as_of_date):
expired = questionnaire_bank.calculated_expiry(
trigger_date, as_of_date=as_of_date)
for q in questionnaire_bank.questionnaires:
- recents = recent_qnr_status(user, q.name)
+ recents = recent_qnr_status(user, q.name, questionnaire_bank)
d[q.name] = status_from_recents(
recents, start, overdue, expired, as_of_date=as_of_date)
trace("QuestionnaireBank status for {}:".format(questionnaire_bank.name))
diff --git a/portal/models/communication.py b/portal/models/communication.py
index fd289fccdd..ccf4e1c93a 100644
--- a/portal/models/communication.py
+++ b/portal/models/communication.py
@@ -8,7 +8,6 @@
from sqlalchemy.dialects.postgresql import ENUM
from string import Formatter
-from .assessment_status import AssessmentStatus # avoid cycle
from .app_text import MailResource
from ..audit import auditable_event
from ..database import db
@@ -38,16 +37,7 @@ def load_template_args(user, questionnaire_bank_id=None):
"""
def ae_link():
- now = datetime.utcnow()
- assessment_status = AssessmentStatus(user=user, as_of_date=now)
- link_url = url_for(
- 'assessment_engine_api.present_assessment',
- instrument_id=assessment_status.
- instruments_needing_full_assessment(classification='all'),
- resume_instrument_id=assessment_status.
- instruments_in_progress(classification='all'),
- _external=True)
- return link_url
+ return url_for('assessment_engine_api.present_needed', _external=True)
def make_button(text):
inline = False
diff --git a/portal/models/fhir.py b/portal/models/fhir.py
index d500282936..56daf0d14e 100644
--- a/portal/models/fhir.py
+++ b/portal/models/fhir.py
@@ -572,6 +572,30 @@ def aggregate_responses(instrument_ids, current_user):
return bundle
+
+def qnr_document_id(
+ subject_id, questionnaire_bank_id, questionnaire_name, status):
+ """Return document['identifier'] for matching QuestionnaireResponse
+
+ Using the given filter data to look for a matching QuestionnaireResponse.
+ Expecting to find exactly one, or raises NoResultFound
+
+ :return: the document identifier value, typically a string
+
+ """
+ qnr = QuestionnaireResponse.query.filter(
+ QuestionnaireResponse.status == status).filter(
+ QuestionnaireResponse.subject_id == subject_id).filter(
+ QuestionnaireResponse.document[
+ ('questionnaire', 'reference')
+ ].astext.endswith(questionnaire_name)).filter(
+ QuestionnaireResponse.questionnaire_bank_id ==
+ questionnaire_bank_id).with_entities(
+ QuestionnaireResponse.document[(
+ 'identifier', 'value')]).one()
+ return qnr[0]
+
+
def generate_qnr_csv(qnr_bundle):
"""Generate a CSV from a bundle of QuestionnaireResponses"""
diff --git a/portal/models/intervention_strategies.py b/portal/models/intervention_strategies.py
index cdadbe7234..4982a53fb8 100644
--- a/portal/models/intervention_strategies.py
+++ b/portal/models/intervention_strategies.py
@@ -369,13 +369,7 @@ def completed_card_html(assessment_status):
link_label = 'Continue questionnaire' if (
assessment_status.overall_status == 'In Progress') else (
'Go to questionnaire')
- link_url = url_for(
- 'assessment_engine_api.present_assessment',
- instrument_id=assessment_status.
- instruments_needing_full_assessment(classification='all'),
- resume_instrument_id=assessment_status.
- instruments_in_progress(classification='all'))
-
+ link_url = url_for('assessment_engine_api.present_needed')
header = _(u"Open Questionnaire")
card_html = u"""
{intro}
@@ -399,11 +393,7 @@ def completed_card_html(assessment_status):
link_label = _(u'Continue questionnaire') if (
indefinite_questionnaires[1]) else (
_(u'Go to questionnaire'))
- link_url = url_for(
- 'assessment_engine_api.present_assessment',
- instrument_id=indefinite_questionnaires[0],
- resume_instrument_id=indefinite_questionnaires[1])
-
+ link_url = url_for('assessment_engine_api.present_needed')
header = _(u"Open Questionnaire")
card_html = u"""
{intro}
diff --git a/portal/templates/profile_macros.html b/portal/templates/profile_macros.html
index 56def2bada..752f883f26 100644
--- a/portal/templates/profile_macros.html
+++ b/portal/templates/profile_macros.html
@@ -1849,11 +1849,7 @@
{{ _("Enter questionnaire manually on patient's behalf")
};
$(document).ready(function() {
- var continueToAssessment = function(method, completionDate, linkUrl) {
- /*
- * note the linkUrl is present will override the existing asssessment link
- */
- var assessment_url = linkUrl || {%if person.assessment_link%}"{{person.assessment_link|safe}}"{%else%}""{%endif%};
+ var continueToAssessment = function(method, completionDate, assessment_url) {
if (hasValue(assessment_url)) {
var still_needed = false;
@@ -1886,7 +1882,7 @@
{{ _("Enter questionnaire manually on patient's behalf")
}, 2000);
setTimeout(function() { window.location = winLocation;}, 100);
-
+
} else {
$("#manualEntryMessageContainer").html("{%trans%}The user does not have a valid assessment link.{%endtrans%}");
};
@@ -2050,9 +2046,11 @@
{{ _("Enter questionnaire manually on patient's behalf")
var method = $("input[name='entryMethod']:checked").val();
var completionDate = $("#qCompletionDate").val();
+ var linkUrl = "{{url_for('assessment_engine_api.present_needed', subject_id=person.id)}}";
if (method != "") {
$("#manualEntryMessageContainer").text("");
+
if (method === "paper") {
/*
@@ -2081,33 +2079,13 @@
{{ _("Enter questionnaire manually on patient's behalf")
};
if (!hasValue(errorMsg)) {
-
- /*
- * retrieve instrument ids from present questionnaire bank
- * only if the date is backdated, i.e. before today's date
- */
- var d = $("#qCompletionDay").val();
- var m = $("#qCompletionMonth").val();
- var y = $("#qCompletionYear").val();
- var todayObj = tnthDates.getTodayDateObj();
- var td = todayObj.displayDay, tm = todayObj.displayMonth, ty = todayObj.displayYear;
-
- var instruments_string = "";
- var linkUrl = "";
-
- if (td+tm+ty != (pad(d)+pad(m)+pad(y))) {
- /*
- * note the API will redirect to AE with the necessary instruments information
- */
- linkUrl = "{{url_for('assessment_engine_api.present_needed')}}" + "?subject_id={{person.id}}";
- };
continueToAssessment(method, completionDate, linkUrl);
};
};
});
} else {
- continueToAssessment(method);
+ continueToAssessment(method, completionDate, linkUrl);
};
} else {
$("#manualEntryMessageContainer").html("{%trans%}You must select a method.{%endtrans%}");
@@ -2118,7 +2096,8 @@
{{ _("Enter questionnaire manually on patient's behalf")
tnthAjax.assessmentStatus({{person.id}}, function(data) {
if (!data.error) {
- if ((data.assessment_status).toUpperCase() == "COMPLETED") {
+ if (((data.assessment_status).toUpperCase() == "COMPLETED") &&
+ (parseInt(data.outstanding_indefinite_work) === 0)) {
$("#assessmentLink").attr("disabled", true);
$("#enterManualInfoContainer").text("{%trans%}All available questionnaires have been completed.{%endtrans%}");
};
diff --git a/portal/templates/settings.html b/portal/templates/settings.html
index b2b9b0f9dd..92daa3f507 100644
--- a/portal/templates/settings.html
+++ b/portal/templates/settings.html
@@ -12,7 +12,9 @@
{{ _("System Settings and Status") }}
{{ form.hidden_tag() }}
{{ render_field(form.timeout, tabindex=5) }}
- {{ render_field(form.import_orgs, tabindex=7) }}
+ {{ render_field(form.patient_id, tabindex=6) }}
+ {{ render_field(form.timestamp, tabindex=7) }}
+ {{ render_field(form.import_orgs, tabindex=8) }}
{{ trace_data }}
diff --git a/portal/views/assessment_engine.py b/portal/views/assessment_engine.py
index ec9896b88c..d1a4430149 100644
--- a/portal/views/assessment_engine.py
+++ b/portal/views/assessment_engine.py
@@ -16,7 +16,12 @@
from ..models.assessment_status import invalidate_assessment_status_cache
from ..models.assessment_status import overall_assessment_status
from ..models.auth import validate_origin
-from ..models.fhir import QuestionnaireResponse, EC, aggregate_responses, generate_qnr_csv
+from ..models.fhir import (
+ aggregate_responses,
+ EC,
+ generate_qnr_csv,
+ QuestionnaireResponse,
+ qnr_document_id)
from ..models.intervention import INTERVENTION
from ..models.questionnaire import Questionnaire
from ..models.questionnaire_bank import QuestionnaireBank
@@ -1355,24 +1360,30 @@ def present_needed():
if subject != current_user():
current_user().check_role(permission='edit', other_id=subject_id)
- as_of_date = FHIR_datetime.parse(request.args.get('authored'))
+ authored = request.args.get('authored')
+ as_of_date = FHIR_datetime.parse(authored) if authored else None
assessment_status = AssessmentStatus(subject, as_of_date=as_of_date)
args = dict(request.args.items())
args['instrument_id'] = (
assessment_status.instruments_needing_full_assessment(
classification='all'))
- # As the AssessmentEngine isn't yet equipped to restart out
- # of sequence instruments, treat all as new if as_of_date
- # isn't today.
- if as_of_date and as_of_date.date() != datetime.utcnow().date():
- args['instrument_id'] += (
- assessment_status.instruments_in_progress(
- classification='all'))
- else:
- args['resume_instrument_id'] = (
- assessment_status.instruments_in_progress(
- classification='all'))
+ # If we find any instruments_in_progress, need to fetch their
+ # identifiers for reliable resume behavior on the AE side.
+ # This is also done now to avoid the overhead of looking up
+ # when generating reports and reminders.
+ resume_ids = []
+ for questionnaire_name in assessment_status.instruments_in_progress(
+ classification='all'):
+ resume_ids.append(
+ qnr_document_id(
+ subject_id=subject_id,
+ questionnaire_bank_id=assessment_status.qb_data.qb.id,
+ questionnaire_name=questionnaire_name,
+ status='in-progress'))
+
+ if resume_ids:
+ args['resume_identifier'] = resume_ids
url = url_for('.present_assessment', **args)
return redirect(url, code=303)
@@ -1453,6 +1464,7 @@ def present_assessment(instruments=None):
queued_instruments = request.args.getlist('instrument_id')
resume_instruments = request.args.getlist('resume_instrument_id')
+ resume_identifiers = request.args.getlist('resume_identifier')
# Hack to allow deprecated API to piggyback
# Remove when deprecated_present_assessment() is fully removed
@@ -1477,6 +1489,7 @@ def present_assessment(instruments=None):
assessment_params = {
"project": ",".join(common_instruments),
"resume_instrument_id": ",".join(resume_instruments),
+ "resume_identifier": ",".join(resume_identifiers),
"subject_id": request.args.get('subject_id'),
"authored": request.args.get('authored'),
}
@@ -1680,7 +1693,17 @@ def patient_assessment_status(patient_id):
assessment_overall_status = (
assessment_status.overall_status if assessment_status else
None)
- return jsonify(assessment_status=assessment_overall_status)
+
+ # indefinite assessments don't affect overall status, but need to
+ # be available if unfinished
+ outstanding_indefinite_work = len(
+ assessment_status.instruments_needing_full_assessment(
+ classification='indefinite') +
+ assessment_status.instruments_in_progress(
+ classification='indefinite'))
+
+ return jsonify(assessment_status=assessment_overall_status,
+ outstanding_indefinite_work=outstanding_indefinite_work)
else:
abort(400, "invalid patient id")
diff --git a/portal/views/patients.py b/portal/views/patients.py
index 75bf48b70f..0fdda48bc3 100644
--- a/portal/views/patients.py
+++ b/portal/views/patients.py
@@ -168,10 +168,6 @@ def patient_profile(patient_id):
if (display.access and display.link_url is not None and
display.link_label is not None):
user_interventions.append({"name": intervention.name})
- if intervention.name == 'assessment_engine':
- # Need to extend with subject_id as the staff user is driving
- patient.assessment_link = '{url}&subject_id={id}'.format(
- url=display.link_url, id=patient.id)
return render_template(
'profile.html', user=patient,
diff --git a/portal/views/portal.py b/portal/views/portal.py
index ddeb77cf87..23a272b305 100644
--- a/portal/views/portal.py
+++ b/portal/views/portal.py
@@ -18,6 +18,7 @@
from ..audit import auditable_event
from .crossdomain import crossdomain
from ..database import db
+from ..date_tools import FHIR_datetime
from ..factories.celery import create_celery
from ..extensions import oauth, user_manager
from ..models.app_text import (
@@ -30,9 +31,11 @@
UserInviteEmail_ATMA,
VersionedResource
)
+from ..models.assessment_status import invalidate_assessment_status_cache
from ..models.auth import validate_origin
from ..models.communication import load_template_args, Communication
from ..models.coredata import Coredata
+from ..models.fhir import QuestionnaireResponse
from ..models.i18n import get_locale
from ..models.identifier import Identifier
from ..models.message import EmailMessage
@@ -42,7 +45,7 @@
from ..models.table_preference import TablePreference
from ..models.user import current_user, get_user, User
from ..system_uri import SHORTCUT_ALIAS
-from ..trace import establish_trace, dump_trace
+from ..trace import establish_trace, dump_trace, trace
portal = Blueprint('portal', __name__)
@@ -572,6 +575,8 @@ def contact_sent(message_id):
class SettingsForm(FlaskForm):
timeout = IntegerField('Session Timeout for This Web Browser (in seconds)',
validators=[validators.DataRequired()])
+ patient_id = IntegerField('Patient to edit', validators=[validators.optional()])
+ timestamp = StringField("Datetime string for patient's questionnaire_responses, format YYYY-MM-DD")
import_orgs = BooleanField('Import Organizations from Site Persistence')
@@ -605,7 +610,6 @@ def settings():
wide_container="true")
if form.import_orgs.data:
- from ..trace import dump_trace, establish_trace, trace
from ..config.model_persistence import ModelPersistence
establish_trace("Initiate import...")
try:
@@ -620,6 +624,26 @@ def settings():
OrgTree().invalidate_cache()
organization_consents = Organization.consent_agreements()
+ if form.patient_id.data and form.timestamp.data:
+ patient = get_user(form.patient_id.data)
+ if not patient:
+ trace("Patient Not found {}".format(form.patient_id.data))
+ try:
+ dt = FHIR_datetime.parse(form.timestamp.data)
+ for qnr in QuestionnaireResponse.query.filter_by(subject_id=patient.id):
+ qnr.authored = dt
+ document = qnr.document
+ document['authored'] = FHIR_datetime.as_fhir(dt)
+ # Due to the infancy of JSON support in POSTGRES and SQLAlchemy
+ # one must force the update to get a JSON field change to stick
+ db.session.query(QuestionnaireResponse).filter(
+ QuestionnaireResponse.id == qnr.id).update({"document": document})
+ db.session.commit()
+ invalidate_assessment_status_cache(patient.id)
+ except ValueError as e:
+ trace("Invalid date format {}".format(form.timestamp.data))
+ trace("ERROR: {}".format(e))
+
# make max_age outlast the browser session
max_age = 60 * 60 * 24 * 365 * 5
response = make_response(render_template(
diff --git a/tests/test_assessment_status.py b/tests/test_assessment_status.py
index 19819ba183..3aee34da5b 100644
--- a/tests/test_assessment_status.py
+++ b/tests/test_assessment_status.py
@@ -2,13 +2,16 @@
from datetime import datetime
from dateutil.relativedelta import relativedelta
from flask_webtest import SessionScope
+from random import choice
+from sqlalchemy.orm.exc import NoResultFound
+from string import ascii_letters
from portal.extensions import db
from portal.models.assessment_status import invalidate_assessment_status_cache
from portal.models.assessment_status import AssessmentStatus
from portal.models.audit import Audit
from portal.models.encounter import Encounter
-from portal.models.fhir import CC, QuestionnaireResponse
+from portal.models.fhir import CC, QuestionnaireResponse, qnr_document_id
from portal.models.intervention import INTERVENTION
from portal.models.organization import Organization
from portal.models.questionnaire import Questionnaire
@@ -17,35 +20,48 @@
from portal.models.recur import Recur
from portal.models.research_protocol import ResearchProtocol
from portal.models.role import ROLE
+from portal.models.user import get_user
from tests import TestCase, TEST_USER_ID
-def mock_qr(user_id, instrument_id, status='completed', timestamp=None):
+def mock_qr(
+ instrument_id, status='completed', timestamp=None, qb=None,
+ doc_id=None):
+ if not doc_id:
+ doc_id = ''.join(choice(ascii_letters) for _ in range(10))
timestamp = timestamp or datetime.utcnow()
qr_document = {
"questionnaire": {
"display": "Additional questions",
"reference":
"https://{}/api/questionnaires/{}".format(
- 'SERVER_NAME', instrument_id)
- }
+ 'SERVER_NAME', instrument_id)},
+ "identifier": {
+ "use": "official",
+ "label": "cPRO survey session ID",
+ "value": doc_id,
+ "system": "https://stg-ae.us.truenth.org/eproms-demo"}
}
+
enc = Encounter(status='planned', auth_method='url_authenticated',
user_id=TEST_USER_ID, start_time=timestamp)
with SessionScope(db):
db.session.add(enc)
db.session.commit()
enc = db.session.merge(enc)
+ qb = qb or QuestionnaireBank.most_current_qb(get_user(TEST_USER_ID),
+ timestamp).questionnaire_bank
qr = QuestionnaireResponse(
subject_id=TEST_USER_ID,
status=status,
authored=timestamp,
document=qr_document,
- encounter_id=enc.id)
+ encounter_id=enc.id,
+ questionnaire_bank=qb)
with SessionScope(db):
db.session.add(qr)
db.session.commit()
- invalidate_assessment_status_cache(user_id)
+ invalidate_assessment_status_cache(TEST_USER_ID)
localized_instruments = set(['eproms_add', 'epic26', 'comorb'])
@@ -297,6 +313,25 @@ def mark_metastatic(self):
class TestAssessmentStatus(TestQuestionnaireSetup):
+
+ def test_qnr_id(self):
+ qb = QuestionnaireBank.query.first()
+ mock_qr(
+ instrument_id='irondemog',
+ status='in-progress', qb=qb,
+ doc_id='two11')
+ qb = db.session.merge(qb)
+ result = qnr_document_id(
+ TEST_USER_ID, qb.id, 'irondemog', 'in-progress')
+ self.assertEquals(result, 'two11')
+
+ def test_qnr_id_missing(self):
+ qb = QuestionnaireBank.query.first()
+ qb = db.session.merge(qb)
+ with self.assertRaises(NoResultFound):
+ result = qnr_document_id(
+ TEST_USER_ID, qb.id, 'irondemog', 'in-progress')
+
def test_enrolled_in_metastatic(self):
"""metastatic should include baseline and indefinite"""
self.bless_with_basics()
@@ -332,9 +367,9 @@ def test_localized_on_time(self):
# User finished both on time
self.bless_with_basics() # pick up a consent, etc.
self.mark_localized()
- mock_qr(user_id=TEST_USER_ID, instrument_id='eproms_add')
- mock_qr(user_id=TEST_USER_ID, instrument_id='epic26')
- mock_qr(user_id=TEST_USER_ID, instrument_id='comorb')
+ mock_qr(instrument_id='eproms_add')
+ mock_qr(instrument_id='epic26')
+ mock_qr(instrument_id='comorb')
self.test_user = db.session.merge(self.test_user)
a_s = AssessmentStatus(user=self.test_user, as_of_date=None)
@@ -348,12 +383,9 @@ def test_localized_inprogress_on_time(self):
# User finished both on time
self.bless_with_basics() # pick up a consent, etc.
self.mark_localized()
- mock_qr(user_id=TEST_USER_ID, instrument_id='eproms_add',
- status='in-progress')
- mock_qr(user_id=TEST_USER_ID, instrument_id='epic26',
- status='in-progress')
- mock_qr(user_id=TEST_USER_ID, instrument_id='comorb',
- status='in-progress')
+ mock_qr(instrument_id='eproms_add', status='in-progress')
+ mock_qr(instrument_id='epic26', status='in-progress')
+ mock_qr(instrument_id='comorb', status='in-progress')
self.test_user = db.session.merge(self.test_user)
a_s = AssessmentStatus(user=self.test_user, as_of_date=None)
@@ -368,7 +400,7 @@ def test_localized_in_process(self):
# User finished one, time remains for other
self.bless_with_basics() # pick up a consent, etc.
self.mark_localized()
- mock_qr(user_id=TEST_USER_ID, instrument_id='eproms_add')
+ mock_qr(instrument_id='eproms_add')
self.test_user = db.session.merge(self.test_user)
a_s = AssessmentStatus(user=self.test_user, as_of_date=None)
@@ -384,11 +416,13 @@ def test_localized_in_process(self):
def test_metastatic_on_time(self):
# User finished both on time
self.bless_with_basics() # pick up a consent, etc.
+ self.mark_metastatic()
for i in metastatic_baseline_instruments:
- mock_qr(user_id=TEST_USER_ID, instrument_id=i)
- mock_qr(user_id=TEST_USER_ID, instrument_id='irondemog')
+ mock_qr(instrument_id=i)
+ mi_qb = QuestionnaireBank.query.filter_by(
+ name='metastatic_indefinite').first()
+ mock_qr(instrument_id='irondemog', qb=mi_qb)
- self.mark_metastatic()
self.test_user = db.session.merge(self.test_user)
a_s = AssessmentStatus(user=self.test_user, as_of_date=None)
self.assertEquals(a_s.overall_status, "Completed")
@@ -421,11 +455,11 @@ def test_localized_overdue(self):
# if the user completed something on time, and nothing else
# is due, should see the thankyou message.
- # backdate so the baseline q's have expired
- mock_qr(user_id=TEST_USER_ID, instrument_id='epic26',
- status='in-progress')
self.bless_with_basics(backdate=relativedelta(months=3))
self.mark_localized()
+ # backdate so the baseline q's have expired
+ mock_qr(instrument_id='epic26', status='in-progress')
+
self.test_user = db.session.merge(self.test_user)
a_s = AssessmentStatus(user=self.test_user, as_of_date=None)
self.assertEquals(a_s.overall_status, "Partially Completed")
@@ -440,12 +474,12 @@ def test_localized_as_of_date(self):
# backdating consent beyond expired and the status lookup date
# within a valid window should show available assessments.
- # backdate so the baseline q's have expired
- mock_qr(user_id=TEST_USER_ID, instrument_id='epic26',
- status='in-progress',
- timestamp=datetime.utcnow() - relativedelta(months=3))
self.bless_with_basics(backdate=relativedelta(months=3))
self.mark_localized()
+ # backdate so the baseline q's have expired
+ mock_qr(instrument_id='epic26', status='in-progress',
+ timestamp=datetime.utcnow() - relativedelta(months=3))
+
self.test_user = db.session.merge(self.test_user)
as_of_date = datetime.utcnow() - relativedelta(months=2, days=28)
a_s = AssessmentStatus(user=self.test_user, as_of_date=as_of_date)
@@ -462,12 +496,12 @@ def test_metastatic_as_of_date(self):
# backdating consent beyond expired and the status lookup date
# within a valid window should show available assessments.
- # backdate so the baseline q's have expired
- mock_qr(user_id=TEST_USER_ID, instrument_id='epic23',
- status='in-progress',
- timestamp=datetime.utcnow() - relativedelta(months=3))
self.bless_with_basics(backdate=relativedelta(months=3))
self.mark_metastatic()
+ # backdate so the baseline q's have expired
+ mock_qr(instrument_id='epic23', status='in-progress',
+ timestamp=datetime.utcnow() - relativedelta(months=3))
+
self.test_user = db.session.merge(self.test_user)
as_of_date = datetime.utcnow() - relativedelta(months=2, days=28)
a_s = AssessmentStatus(user=self.test_user, as_of_date=as_of_date)
@@ -494,10 +528,41 @@ def test_initial_recur_due(self):
set(a_s.instruments_needing_full_assessment()),
metastatic_3)
+ def test_initial_recur_baseline_done(self):
+ # backdate to be within the first recurrence window
+
+ self.bless_with_basics(backdate=relativedelta(months=3, days=2))
+ self.mark_metastatic()
+
+ # add baseline QNRs, as if submitted nearly 3 months ago, during
+ # baseline window
+ backdated = datetime.utcnow() - relativedelta(months=2, days=25)
+ baseline = QuestionnaireBank.query.filter_by(
+ name='metastatic').one()
+ for instrument in metastatic_baseline_instruments:
+ mock_qr(instrument, qb=baseline, timestamp=backdated)
+
+ self.test_user = db.session.merge(self.test_user)
+ # Check status during baseline window
+ a_s_baseline = AssessmentStatus(
+ user=self.test_user, as_of_date=backdated)
+ self.assertEquals(a_s_baseline.overall_status, "Completed")
+ self.assertFalse(a_s_baseline.instruments_needing_full_assessment())
+
+ # Whereas "current" status for the initial recurrence show due.
+ a_s = AssessmentStatus(user=self.test_user, as_of_date=None)
+ self.assertEquals(a_s.overall_status, "Due")
+
+ # in the initial window w/ no questionnaires submitted
+ # should include all from initial recur
+ self.assertEquals(
+ set(a_s.instruments_needing_full_assessment()),
+ metastatic_3)
+
def test_secondary_recur_due(self):
# backdate so baseline q's have expired, and we are within the
- # second recurrance window
+ # second recurrence window
self.bless_with_basics(backdate=relativedelta(months=6))
self.mark_metastatic()
self.test_user = db.session.merge(self.test_user)
@@ -553,9 +618,7 @@ def test_boundry_in_progress(self):
self.bless_with_basics(backdate=relativedelta(months=3, hours=-1))
self.mark_localized()
for instrument in localized_instruments:
- mock_qr(
- user_id=TEST_USER_ID, instrument_id=instrument,
- status='in-progress')
+ mock_qr(instrument_id=instrument, status='in-progress')
self.test_user = db.session.merge(self.test_user)
a_s = AssessmentStatus(user=self.test_user, as_of_date=None)
self.assertEquals(a_s.overall_status, 'In Progress')
@@ -565,9 +628,7 @@ def test_boundry_in_progress_expired(self):
self.bless_with_basics(backdate=relativedelta(months=3))
self.mark_localized()
for instrument in localized_instruments:
- mock_qr(
- user_id=TEST_USER_ID, instrument_id=instrument,
- status='in-progress')
+ mock_qr(instrument_id=instrument, status='in-progress')
self.test_user = db.session.merge(self.test_user)
a_s = AssessmentStatus(user=self.test_user, as_of_date=None)
self.assertEquals(a_s.overall_status, 'Partially Completed')
diff --git a/tests/test_communication.py b/tests/test_communication.py
index 3c6e5a35a9..ae524e0fc5 100644
--- a/tests/test_communication.py
+++ b/tests/test_communication.py
@@ -161,12 +161,9 @@ def test_nearready_message(self):
self.bless_with_basics(backdate=timedelta(days=13))
self.promote_user(role_name=ROLE.PATIENT)
self.mark_localized()
- mock_qr(user_id=TEST_USER_ID, instrument_id='eproms_add',
- status='in-progress')
- mock_qr(user_id=TEST_USER_ID, instrument_id='epic26',
- status='in-progress')
- mock_qr(user_id=TEST_USER_ID, instrument_id='comorb',
- status='in-progress')
+ mock_qr(instrument_id='eproms_add', status='in-progress')
+ mock_qr(instrument_id='epic26', status='in-progress')
+ mock_qr(instrument_id='comorb', status='in-progress')
update_patient_loop(update_cache=False, queue_messages=True)
expected = Communication.query.first()
@@ -184,12 +181,9 @@ def test_ready_message(self):
self.bless_with_basics(backdate=timedelta(days=14))
self.promote_user(role_name=ROLE.PATIENT)
self.mark_localized()
- mock_qr(user_id=TEST_USER_ID, instrument_id='eproms_add',
- status='in-progress')
- mock_qr(user_id=TEST_USER_ID, instrument_id='epic26',
- status='in-progress')
- mock_qr(user_id=TEST_USER_ID, instrument_id='comorb',
- status='in-progress')
+ mock_qr(instrument_id='eproms_add', status='in-progress')
+ mock_qr(instrument_id='epic26', status='in-progress')
+ mock_qr(instrument_id='comorb', status='in-progress')
update_patient_loop(update_cache=False, queue_messages=True)
expected = Communication.query.first()
@@ -259,9 +253,9 @@ def test_done_message(self):
self.bless_with_basics(backdate=timedelta(days=14))
self.promote_user(role_name=ROLE.PATIENT)
self.mark_localized()
- mock_qr(user_id=TEST_USER_ID, instrument_id='eproms_add')
- mock_qr(user_id=TEST_USER_ID, instrument_id='epic26')
- mock_qr(user_id=TEST_USER_ID, instrument_id='comorb')
+ mock_qr(instrument_id='eproms_add')
+ mock_qr(instrument_id='epic26')
+ mock_qr(instrument_id='comorb')
update_patient_loop(update_cache=False, queue_messages=True)
expected = Communication.query.first()
@@ -350,7 +344,7 @@ def test_st_done(self):
QuestionnaireBank.qbs_for_user(self.test_user, 'baseline'))
for instrument in symptom_tracker_instruments:
- mock_qr(user_id=TEST_USER_ID, instrument_id=instrument)
+ mock_qr(instrument_id=instrument)
# With all q's done, shouldn't generate a message
update_patient_loop(update_cache=False, queue_messages=True)
@@ -371,7 +365,7 @@ def test_st_undone(self):
QuestionnaireBank.qbs_for_user(self.test_user, 'baseline'))
# With most q's undone, should generate a message
- mock_qr(user_id=TEST_USER_ID, instrument_id='epic26')
+ mock_qr(instrument_id='epic26')
a_s, _ = overall_assessment_status(TEST_USER_ID)
self.assertEquals('In Progress', a_s)
update_patient_loop(update_cache=False, queue_messages=True)
@@ -395,7 +389,7 @@ def test_st_metastatic(self):
QuestionnaireBank.qbs_for_user(self.test_user, 'baseline'))
# shouldn't generate a message either
- mock_qr(user_id=TEST_USER_ID, instrument_id='epic26')
+ mock_qr(instrument_id='epic26')
update_patient_loop(update_cache=False, queue_messages=True)
expected = Communication.query.first()
self.assertFalse(expected)
diff --git a/tests/test_intervention.py b/tests/test_intervention.py
index 6ddc2e46af..688b2ed452 100644
--- a/tests/test_intervention.py
+++ b/tests/test_intervention.py
@@ -16,6 +16,7 @@
from portal.models.intervention_strategies import AccessStrategy
from portal.models.message import EmailMessage
from portal.models.organization import Organization
+from portal.models.questionnaire_bank import QuestionnaireBank
from portal.models.role import ROLE
from portal.models.user import add_role
from portal.system_uri import DECISION_SUPPORT_GROUP, SNOMED
@@ -402,8 +403,10 @@ def test_card_html_update(self):
dt = datetime(2017, 6, 10, 20, 00, 00, 000000)
# Add a fake assessments and see a change
for i in metastatic_baseline_instruments:
- mock_qr(user_id=TEST_USER_ID, instrument_id=i, timestamp=dt)
- mock_qr(user_id=TEST_USER_ID, instrument_id='irondemog', timestamp=dt)
+ mock_qr(instrument_id=i, timestamp=dt)
+ mi_qb = QuestionnaireBank.query.filter_by(
+ name='metastatic_indefinite').first()
+ mock_qr(instrument_id='irondemog', timestamp=dt, qb=mi_qb)
user, ae = map(db.session.merge, (self.test_user, ae))