def _setup_questionnaires(self): """Locates questionnaires and verifies that they have the appropriate questions in them.""" questionnaire_dao = QuestionnaireDao() code_dao = CodeDao() question_code_to_questionnaire_id = {} self._questionnaire_to_questions = collections.defaultdict(list) self._question_code_to_answer_codes = {} # Populate maps of questionnaire ID/version to [(question_code, link ID)] and # question code to answer codes. for concept in _QUESTIONNAIRE_CONCEPTS: code = code_dao.get_code(PPI_SYSTEM, concept) if code is None: raise BadRequest( 'Code missing: %s; import data and clear cache.' % concept) questionnaire = questionnaire_dao.get_latest_questionnaire_with_concept( code.codeId) if questionnaire is None: raise BadRequest( 'Questionnaire for code %s missing; import data.' % concept) questionnaire_id_and_version = (questionnaire.questionnaireId, questionnaire.version) if concept == CONSENT_FOR_STUDY_ENROLLMENT_MODULE: self._consent_questionnaire_id_and_version = questionnaire_id_and_version elif concept == THE_BASICS_PPI_MODULE: self._the_basics_questionnaire_id_and_version = questionnaire_id_and_version questions = self._questionnaire_to_questions[ questionnaire_id_and_version] if questions: # We already handled this questionnaire. continue for question in questionnaire.questions: question_code = code_dao.get(question.codeId) if (question_code.value in _QUESTION_CODES) or (question_code.value in self._answer_specs): question_code_to_questionnaire_id[ question_code.value] = questionnaire.questionnaireId questions.append((question_code.value, question.linkId)) if question_code.value in _QUESTION_CODES: answer_codes = self._get_answer_codes(question_code) all_codes = (answer_codes + _CONSTANT_CODES ) if answer_codes else _CONSTANT_CODES self._question_code_to_answer_codes[ question_code.value] = all_codes # Log warnings for any question codes not found in the questionnaires. for code_value in _QUESTION_CODES + self._answer_specs.keys(): questionnaire_id = question_code_to_questionnaire_id.get( code_value) if not questionnaire_id: logging.warning( 'Question for code %s missing; import questionnaires', code_value)
def _update_participant_summary(self, session, questionnaire_response, code_ids, questions, questionnaire_history, resource_json): """Updates the participant summary based on questions answered and modules completed in the questionnaire response. If no participant summary exists already, only a response to the study enrollment consent questionnaire can be submitted, and it must include first and last name and e-mail address. """ # Block on other threads modifying the participant or participant summary. participant = ParticipantDao().get_for_update( session, questionnaire_response.participantId) if participant is None: raise BadRequest('Participant with ID %d is not found.' % questionnaire_response.participantId) participant_summary = participant.participantSummary code_ids.extend( [concept.codeId for concept in questionnaire_history.concepts]) code_dao = CodeDao() something_changed = False # If no participant summary exists, make sure this is the study enrollment consent. if not participant_summary: consent_code = code_dao.get_code( PPI_SYSTEM, CONSENT_FOR_STUDY_ENROLLMENT_MODULE) if not consent_code: raise BadRequest( 'No study enrollment consent code found; import codebook.') if not consent_code.codeId in code_ids: raise BadRequest( "Can't submit order for participant %s without consent" % questionnaire_response.participantId) raise_if_withdrawn(participant) participant_summary = ParticipantDao.create_summary_for_participant( participant) something_changed = True else: raise_if_withdrawn(participant_summary) # Fetch the codes for all questions and concepts codes = code_dao.get_with_ids(code_ids) code_map = { code.codeId: code for code in codes if code.system == PPI_SYSTEM } question_map = { question.questionnaireQuestionId: question for question in questions } race_code_ids = [] ehr_consent = False dvehr_consent = QuestionnaireStatus.SUBMITTED_NO_CONSENT # Set summary fields for answers that have questions with codes found in QUESTION_CODE_TO_FIELD for answer in questionnaire_response.answers: question = question_map.get(answer.questionId) if question: code = code_map.get(question.codeId) if code: summary_field = QUESTION_CODE_TO_FIELD.get(code.value) if summary_field: if something_changed: self._update_field(participant_summary, summary_field[0], summary_field[1], answer) else: something_changed = self._update_field( participant_summary, summary_field[0], summary_field[1], answer) elif code.value == RACE_QUESTION_CODE: race_code_ids.append(answer.valueCodeId) elif code.value == DVEHR_SHARING_QUESTION_CODE: code = code_dao.get(answer.valueCodeId) if code and code.value == DVEHRSHARING_CONSENT_CODE_YES: dvehr_consent = QuestionnaireStatus.SUBMITTED elif code and code.value == DVEHRSHARING_CONSENT_CODE_NOT_SURE: dvehr_consent = QuestionnaireStatus.SUBMITTED_NOT_SURE elif code.value == EHR_CONSENT_QUESTION_CODE: code = code_dao.get(answer.valueCodeId) if code and code.value == CONSENT_PERMISSION_YES_CODE: ehr_consent = True elif code.value == CABOR_SIGNATURE_QUESTION_CODE: if answer.valueUri or answer.valueString: # TODO: validate the URI? [DA-326] if not participant_summary.consentForCABoR: participant_summary.consentForCABoR = True participant_summary.consentForCABoRTime = questionnaire_response.created something_changed = True # If race was provided in the response in one or more answers, set the new value. if race_code_ids: race_codes = [code_dao.get(code_id) for code_id in race_code_ids] race = get_race(race_codes) if race != participant_summary.race: participant_summary.race = race something_changed = True # Set summary fields to SUBMITTED for questionnaire concepts that are found in # QUESTIONNAIRE_MODULE_CODE_TO_FIELD module_changed = False for concept in questionnaire_history.concepts: code = code_map.get(concept.codeId) if code: summary_field = QUESTIONNAIRE_MODULE_CODE_TO_FIELD.get( code.value) if summary_field: new_status = QuestionnaireStatus.SUBMITTED if code.value == CONSENT_FOR_ELECTRONIC_HEALTH_RECORDS_MODULE and not ehr_consent: new_status = QuestionnaireStatus.SUBMITTED_NO_CONSENT elif code.value == CONSENT_FOR_DVEHR_MODULE: new_status = dvehr_consent elif code.value == CONSENT_FOR_STUDY_ENROLLMENT_MODULE: # set language of consent to participant summary for extension in resource_json.get('extension', []): if extension.get('url') == _LANGUAGE_EXTENSION and \ extension.get('valueCode') in LANGUAGE_OF_CONSENT: if participant_summary.primaryLanguage != extension.get( 'valueCode'): participant_summary.primaryLanguage = extension.get( 'valueCode') something_changed = True break elif extension.get('url') == _LANGUAGE_EXTENSION and \ extension.get('valueCode') not in LANGUAGE_OF_CONSENT: logging.warn( 'consent language %s not recognized.' % extension.get('valueCode')) if getattr(participant_summary, summary_field) != new_status: setattr(participant_summary, summary_field, new_status) setattr(participant_summary, summary_field + 'Time', questionnaire_response.created) something_changed = True module_changed = True if module_changed: participant_summary.numCompletedBaselinePPIModules = \ count_completed_baseline_ppi_modules(participant_summary) participant_summary.numCompletedPPIModules = \ count_completed_ppi_modules(participant_summary) if something_changed: first_last = (participant_summary.firstName, participant_summary.lastName) email_phone = (participant_summary.email, participant_summary.loginPhoneNumber) if not all(first_last): raise BadRequest( 'First name (%s), and last name (%s) required for consenting.' % tuple([ 'present' if part else 'missing' for part in first_last ])) if not any(email_phone): raise BadRequest( 'Email address (%s), or phone number (%s) required for consenting.' % tuple([ 'present' if part else 'missing' for part in email_phone ])) ParticipantSummaryDao().update_enrollment_status( participant_summary) participant_summary.lastModified = clock.CLOCK.now() session.merge(participant_summary) # switch account to test account if the phone number is start with 444 # this is a requirement from PTSC if participant_summary.loginPhoneNumber is not None and \ participant_summary.loginPhoneNumber.startswith(TEST_LOGIN_PHONE_NUMBER_PREFIX): ParticipantDao().switch_to_test_account( session, participant_summary.participantId)
def _update_participant_summary( self, session, questionnaire_response, code_ids, questions, questionnaire_history): """Updates the participant summary based on questions answered and modules completed in the questionnaire response. If no participant summary exists already, only a response to the study enrollment consent questionnaire can be submitted, and it must include first and last name and e-mail address. """ # Block on other threads modifying the participant or participant summary. participant = ParticipantDao().get_for_update(session, questionnaire_response.participantId) if participant is None: raise BadRequest('Participant with ID %d is not found.' % questionnaire_response.participantId) participant_summary = participant.participantSummary code_ids.extend([concept.codeId for concept in questionnaire_history.concepts]) code_dao = CodeDao() something_changed = False # If no participant summary exists, make sure this is the study enrollment consent. if not participant_summary: consent_code = code_dao.get_code(PPI_SYSTEM, CONSENT_FOR_STUDY_ENROLLMENT_MODULE) if not consent_code: raise BadRequest('No study enrollment consent code found; import codebook.') if not consent_code.codeId in code_ids: raise BadRequest("Can't submit order for participant %s without consent" % questionnaire_response.participantId) raise_if_withdrawn(participant) participant_summary = ParticipantDao.create_summary_for_participant(participant) something_changed = True else: raise_if_withdrawn(participant_summary) # Fetch the codes for all questions and concepts codes = code_dao.get_with_ids(code_ids) code_map = {code.codeId: code for code in codes if code.system == PPI_SYSTEM} question_map = {question.questionnaireQuestionId: question for question in questions} race_code_ids = [] ehr_consent = False # Set summary fields for answers that have questions with codes found in QUESTION_CODE_TO_FIELD for answer in questionnaire_response.answers: question = question_map.get(answer.questionId) if question: code = code_map.get(question.codeId) if code: summary_field = QUESTION_CODE_TO_FIELD.get(code.value) if summary_field: something_changed = self._update_field(participant_summary, summary_field[0], summary_field[1], answer) elif code.value == RACE_QUESTION_CODE: race_code_ids.append(answer.valueCodeId) elif code.value == EHR_CONSENT_QUESTION_CODE: code = code_dao.get(answer.valueCodeId) if code and code.value == CONSENT_PERMISSION_YES_CODE: ehr_consent = True elif code.value == CABOR_SIGNATURE_QUESTION_CODE: if answer.valueUri or answer.valueString: # TODO: validate the URI? [DA-326] if not participant_summary.consentForCABoR: participant_summary.consentForCABoR = True participant_summary.consentForCABoRTime = questionnaire_response.created something_changed = True # If race was provided in the response in one or more answers, set the new value. if race_code_ids: race_codes = [code_dao.get(code_id) for code_id in race_code_ids] race = get_race(race_codes) if race != participant_summary.race: participant_summary.race = race something_changed = True # Set summary fields to SUBMITTED for questionnaire concepts that are found in # QUESTIONNAIRE_MODULE_CODE_TO_FIELD module_changed = False for concept in questionnaire_history.concepts: code = code_map.get(concept.codeId) if code: summary_field = QUESTIONNAIRE_MODULE_CODE_TO_FIELD.get(code.value) if summary_field: new_status = QuestionnaireStatus.SUBMITTED if code.value == CONSENT_FOR_ELECTRONIC_HEALTH_RECORDS_MODULE and not ehr_consent: new_status = QuestionnaireStatus.SUBMITTED_NO_CONSENT if getattr(participant_summary, summary_field) != new_status: setattr(participant_summary, summary_field, new_status) setattr(participant_summary, summary_field + 'Time', questionnaire_response.created) something_changed = True module_changed = True if module_changed: participant_summary.numCompletedBaselinePPIModules = \ count_completed_baseline_ppi_modules(participant_summary) participant_summary.numCompletedPPIModules = \ count_completed_ppi_modules(participant_summary) if something_changed: first_last_email = ( participant_summary.firstName, participant_summary.lastName, participant_summary.email) if not all(first_last_email): raise BadRequest( 'First name (%s), last name (%s), and email address (%s) required for consenting.' % tuple(['present' if part else 'missing' for part in first_last_email])) ParticipantSummaryDao().update_enrollment_status(participant_summary) participant_summary.lastModified = clock.CLOCK.now() session.merge(participant_summary)
class CodeDaoTest(SqlTestBase): def setUp(self): super(CodeDaoTest, self).setUp() self.code_book_dao = CodeBookDao() self.code_dao = CodeDao() self.code_history_dao = CodeHistoryDao() def test_get_before_insert(self): self.assertIsNone(self.code_book_dao.get(1)) self.assertIsNone(self.code_dao.get(1)) self.assertIsNone(self.code_history_dao.get(1)) def test_insert_without_codebook_or_parent(self): code = Code(system="a", value="b", display=u"c", topic=u"d", codeType=CodeType.MODULE, mapped=True) with FakeClock(TIME): self.code_dao.insert(code) expected_code = Code(codeId=1, system="a", value="b", display=u"c", topic=u"d", codeType=CodeType.MODULE, mapped=True, created=TIME) self.assertEquals(expected_code.asdict(), self.code_dao.get(1).asdict()) expected_code_history = CodeHistory(codeHistoryId=1, codeId=1, system="a", value="b", display=u"c", topic=u"d", codeType=CodeType.MODULE, mapped=True, created=TIME) self.assertEquals(expected_code_history.asdict(), self.code_history_dao.get(1).asdict()) def test_insert_with_codebook_and_parent(self): code_book_1 = CodeBook(name="pmi", version="v1", system="a") with FakeClock(TIME): self.code_book_dao.insert(code_book_1) expected_code_book = CodeBook(codeBookId=1, latest=True, created=TIME, name="pmi", version="v1", system="a") self.assertEquals(expected_code_book.asdict(), self.code_book_dao.get(1).asdict()) code_1 = Code(codeBookId=1, system="a", value="b", display=u"c", topic=u"d", codeType=CodeType.MODULE, mapped=True) with FakeClock(TIME_2): self.code_dao.insert(code_1) expected_code = Code(codeBookId=1, codeId=1, system="a", value="b", display=u"c", topic=u"d", codeType=CodeType.MODULE, mapped=True, created=TIME_2) self.assertEquals(expected_code.asdict(), self.code_dao.get(1).asdict()) expected_code_history = CodeHistory(codeBookId=1, codeHistoryId=1, codeId=1, system="a", value=u"b", display=u"c", topic=u"d", codeType=CodeType.MODULE, mapped=True, created=TIME_2) self.assertEquals(expected_code_history.asdict(), self.code_history_dao.get(1).asdict()) code_2 = Code(codeBookId=1, system="x", value="y", display=u"z", topic=u"q", codeType=CodeType.QUESTION, mapped=False, parentId=1) with FakeClock(TIME_3): self.code_dao.insert(code_2) expected_code_2 = Code(codeBookId=1, codeId=2, system="x", value="y", display=u"z", topic=u"q", codeType=CodeType.QUESTION, mapped=False, created=TIME_3, parentId=1) self.assertEquals(expected_code_2.asdict(), self.code_dao.get(2).asdict()) def test_insert_second_codebook_same_system(self): code_book_1 = CodeBook(name="pmi", version="v1", system="a") with FakeClock(TIME): self.code_book_dao.insert(code_book_1) code_book_2 = CodeBook(name="pmi", version="v2", system="a") with FakeClock(TIME_2): self.code_book_dao.insert(code_book_2) expected_code_book = CodeBook(codeBookId=1, latest=False, created=TIME, name="pmi", version="v1", system="a") self.assertEquals(expected_code_book.asdict(), self.code_book_dao.get(1).asdict()) expected_code_book_2 = CodeBook(codeBookId=2, latest=True, created=TIME_2, name="pmi", version="v2", system="a") self.assertEquals(expected_code_book_2.asdict(), self.code_book_dao.get(2).asdict()) def test_insert_second_codebook_different_system(self): code_book_1 = CodeBook(name="pmi", version="v1", system="a") with FakeClock(TIME): self.code_book_dao.insert(code_book_1) code_book_2 = CodeBook(name="pmi", version="v2", system="b") with FakeClock(TIME_2): self.code_book_dao.insert(code_book_2) expected_code_book = CodeBook(codeBookId=1, latest=True, created=TIME, name="pmi", version="v1", system="a") self.assertEquals(expected_code_book.asdict(), self.code_book_dao.get(1).asdict()) expected_code_book_2 = CodeBook(codeBookId=2, latest=True, created=TIME_2, name="pmi", version="v2", system="b") self.assertEquals(expected_code_book_2.asdict(), self.code_book_dao.get(2).asdict()) def test_insert_second_codebook_same_system_same_version(self): code_book_1 = CodeBook(name="pmi", version="v1", system="a") self.code_book_dao.insert(code_book_1) code_book_2 = CodeBook(name="pmi", version="v1", system="a") with self.assertRaises(BadRequest): self.code_book_dao.insert(code_book_2) def test_update_codes_no_codebook_id(self): code_book_1 = CodeBook(name="pmi", version="v1", system="c") with FakeClock(TIME): self.code_book_dao.insert(code_book_1) code_1 = Code(codeBookId=1, system="a", value="b", display=u"c", topic=u"d", codeType=CodeType.MODULE, mapped=True) with FakeClock(TIME_2): self.code_dao.insert(code_1) new_code_1 = Code(codeId=1, system="x", value="b", display=u"c", topic=u"d", codeType=CodeType.MODULE, mapped=True) with self.assertRaises(BadRequest): self.code_dao.update(new_code_1) def test_update_codes_same_codebook_id(self): code_book_1 = CodeBook(name="pmi", version="v1", system="c") with FakeClock(TIME): self.code_book_dao.insert(code_book_1) code_1 = Code(codeBookId=1, system="a", value="b", display=u"c", topic=u"d", codeType=CodeType.MODULE, mapped=True) with FakeClock(TIME_2): self.code_dao.insert(code_1) new_code_1 = Code(codeBookId=1, codeId=1, system="x", value="b", display=u"c", topic=u"d", codeType=CodeType.MODULE, mapped=True) with self.assertRaises(BadRequest): self.code_dao.update(new_code_1) def test_update_codes_new_codebook_id(self): code_book_1 = CodeBook(name="pmi", version="v1", system="a") with FakeClock(TIME): self.code_book_dao.insert(code_book_1) code_1 = Code(codeBookId=1, system="a", value="b", display=u"c", topic=u"d", codeType=CodeType.MODULE, mapped=True) with FakeClock(TIME_2): self.code_dao.insert(code_1) code_book_2 = CodeBook(name="pmi", version="v2", system="a") with FakeClock(TIME_3): self.code_book_dao.insert(code_book_2) new_code_1 = Code(codeBookId=2, codeId=1, system="x", value="b", display=u"c", topic=u"d", codeType=CodeType.MODULE, mapped=True) with FakeClock(TIME_4): self.code_dao.update(new_code_1) expected_code = Code(codeBookId=2, codeId=1, system="x", value="b", display=u"c", topic=u"d", codeType=CodeType.MODULE, mapped=True, created=TIME_2) self.assertEquals(expected_code.asdict(), self.code_dao.get(1).asdict()) expected_code_history = CodeHistory(codeBookId=1, codeHistoryId=1, codeId=1, system="a", value="b", display=u"c", topic=u"d", codeType=CodeType.MODULE, mapped=True, created=TIME_2) self.assertEquals(expected_code_history.asdict(), self.code_history_dao.get(1).asdict()) expected_code_history_2 = CodeHistory(codeHistoryId=2, codeBookId=2, codeId=1, system="x", value="b", display=u"c", topic=u"d", codeType=CodeType.MODULE, mapped=True, created=TIME_2) self.assertEquals(expected_code_history_2.asdict(), self.code_history_dao.get(2).asdict()) def test_import_codebook(self): answer_1 = _make_concept(u"t1", "Answer", "c1", u"d1") answer_2 = _make_concept(u"t2", "Answer", "c2", u"d2") answer_3 = _make_concept(u"t2", "Answer", "c3", u"d3") question_1 = _make_concept(u"t1", "Question", "q1", u"d4", [answer_1]) question_2 = _make_concept(u"t2", "Question", "q2", u"d5", [answer_2, answer_3]) topic_1 = _make_concept(u"t1", "Topic", "t1", u"d6", [question_1]) module_1 = _make_concept(u"mt1", "Module Name", "m1", u"d7", [topic_1]) module_2 = _make_concept(u"mt2", "Module Name", "m2", u"d8", [question_2]) system = 'http://blah/foo' codebook = { 'name': 'pmi', 'version': 'v1', 'url': system, 'concept': [module_1, module_2] } with FakeClock(TIME): self.code_book_dao.import_codebook(codebook) expectedCodeBook = CodeBook(codeBookId=1, latest=True, created=TIME, name="pmi", version="v1", system=system) self.assertEquals(expectedCodeBook.asdict(), self.code_book_dao.get(1).asdict()) expectedModule1 = Code(codeBookId=1, codeId=1, system=system, value="m1", shortValue="m1", display=u"d7", topic=u"mt1", codeType=CodeType.MODULE, mapped=True, created=TIME) self.assertEquals(expectedModule1.asdict(), self.code_dao.get(1).asdict()) expectedModuleHistory1 = CodeHistory(codeHistoryId=1, codeBookId=1, codeId=1, system=system, value="m1", shortValue="m1", display=u"d7", topic=u"mt1", codeType=CodeType.MODULE, mapped=True, created=TIME) self.assertEquals(expectedModuleHistory1.asdict(), self.code_history_dao.get(1).asdict()) expectedTopic1 = Code(codeBookId=1, codeId=2, system=system, value="t1", shortValue="t1", display=u"d6", topic=u"t1", codeType=CodeType.TOPIC, mapped=True, created=TIME, parentId=1) self.assertEquals(expectedTopic1.asdict(), self.code_dao.get(2).asdict()) expectedQuestion1 = Code(codeBookId=1, codeId=3, system=system, value="q1", shortValue="q1", display=u"d4", topic=u"t1", codeType=CodeType.QUESTION, mapped=True, created=TIME, parentId=2) self.assertEquals(expectedQuestion1.asdict(), self.code_dao.get(3).asdict()) expectedAnswer1 = Code(codeBookId=1, codeId=4, system=system, value="c1", shortValue="c1", display=u"d1", topic=u"t1", codeType=CodeType.ANSWER, mapped=True, created=TIME, parentId=3) self.assertEquals(expectedAnswer1.asdict(), self.code_dao.get(4).asdict())