<commit_before>from collections import defaultdict
from datetime import datetime
import logging
from typing import Dict

from sqlalchemy.orm import joinedload, Session

from rdr_service.domain_model.response import Response
from rdr_service.dao.questionnaire_response_dao import QuestionnaireResponseDao
from rdr_service.model.code import Code
from rdr_service.model.survey import Survey, SurveyQuestion, SurveyQuestionOption
from rdr_service.services.response_validation.validation import ResponseValidator
from rdr_service.services.slack_utils import SlackMessageHandler


class ResponseValidationController:
    def __init__(self, session: Session, since_date: datetime, slack_webhook=None):
        self._session = session
        self._since_date = since_date
        self._error_list = defaultdict(list)
        self._slack_webhook = slack_webhook
        self._summarize_results = slack_webhook is not None

        self._response_validator_map: Dict[str, ResponseValidator] = {}

    def run_validation(self):
        response_list = QuestionnaireResponseDao.get_responses_to_surveys(
            session=self._session,
            created_start_datetime=self._since_date
        )
        for participant_id, participant_responses in response_list.items():
            for response in participant_responses.responses.values():
                self._check_response(response, participant_id=participant_id)

        self._send_error_message()

    def _check_response(self, response: Response, participant_id):
        # look at the response map, use validator if it's there, build it if it's not
        # store the errors for later handling
        if response.survey_code not in self._response_validator_map:
            self._response_validator_map[response.survey_code] = self._build_validator(survey_code=response.survey_code)

        validator = self._response_validator_map.get(response.survey_code)
        if validator:
            for error_list in validator.get_errors_in_response(response).values():
                for error in error_list:
                    self._error_list[(response.survey_code, error.question_code, error.reason)].append(
                        f'P{participant_id} {response.survey_code} answer id {error.answer_id[0]} '
                        f'- question "{error.question_code}" Error: {error.reason}'
                    )

    def _build_validator(self, survey_code):
        query = (
            self._session.query(Survey)
            .join(Code)
            .filter(
                Survey.replacedTime.is_(None),
                Code.value == survey_code,
                Survey.redcapProjectId.isnot(None)
            ).options(
                joinedload(Survey.questions).joinedload(SurveyQuestion.code),
                joinedload(Survey.questions).joinedload(SurveyQuestion.options).joinedload(SurveyQuestionOption.code)
            )
        )
        survey = query.one_or_none()

        if survey:
            return ResponseValidator(survey_definition=survey, session=self._session)
        else:
            return None

    def _send_error_message(self):
        if not self._error_list:
            self._output_result(f'No validation errors were found since {self._since_date}')
            return

        result_text = f'Validation errors for survey responses received since {self._since_date.date()}\n'
        if self._summarize_results:
            for key, error_list in self._error_list.items():
                survey_code, question_code, error_str = key
                result_text += (
                    f'{survey_code} "{question_code}" Error: {error_str}, number affected answers: {len(error_list)}\n'
                )
        else:
            for error_list in self._error_list.values():
                for error in error_list:
                    result_text += f'{error}\n'

        self._output_result(result_text)

    def _output_result(self, result_str):
        if self._slack_webhook:
            client = SlackMessageHandler(webhook_url=self._slack_webhook)
            client.send_message_to_webhook(
                message_data={
                    'text': result_str
                }
            )
        else:
            logging.info(result_str)<commit_msg>[DA-2769] Iterating on survey validation (#3036)<commit_after>from collections import defaultdict
from datetime import datetime
import logging
from typing import Dict

from sqlalchemy.orm import joinedload, Session

from rdr_service.domain_model.response import Response
from rdr_service.dao.questionnaire_response_dao import QuestionnaireResponseDao
from rdr_service.model.code import Code
from rdr_service.model.survey import Survey, SurveyQuestion, SurveyQuestionOption
from rdr_service.services.response_validation.validation import ResponseValidator
from rdr_service.services.slack_utils import SlackMessageHandler


class ResponseValidationController:
    def __init__(self, session: Session, since_date: datetime, slack_webhook=None):
        self._session = session
        self._since_date = since_date
        self._error_list = defaultdict(list)
        self._slack_webhook = slack_webhook
        self._summarize_results = slack_webhook is not None

        self._response_validator_map: Dict[str, ResponseValidator] = {}

    def run_validation(self):
        response_list = QuestionnaireResponseDao.get_responses_to_surveys(
            session=self._session,
            created_start_datetime=self._since_date
        )
        for participant_id, participant_responses in response_list.items():
            for response in participant_responses.responses.values():
                self._check_response(response, participant_id=participant_id)

        self._send_error_message()

    def _check_response(self, response: Response, participant_id):
        # look at the response map, use validator if it's there, build it if it's not
        # store the errors for later handling
        if response.survey_code not in self._response_validator_map:
            self._response_validator_map[response.survey_code] = self._build_validator(survey_code=response.survey_code)

        validator = self._response_validator_map.get(response.survey_code)
        if validator:
            for error_list in validator.get_errors_in_response(response).values():
                for error in error_list:
                    if response.survey_code != 'EHRConsentPII':
                        # TODO: implement a way to detect when validation needed for sensitive EHR
                        self._error_list[(response.survey_code, error.question_code, error.reason)].append(
                            f'{response.survey_code} - question "{error.question_code}" '
                            f'Error: {error.reason} (P{participant_id}, ansID {error.answer_id[0]})'
                        )

    def _build_validator(self, survey_code):
        query = (
            self._session.query(Survey)
            .join(Code)
            .filter(
                Survey.replacedTime.is_(None),
                Code.value == survey_code,
                Survey.redcapProjectId.isnot(None)
            ).options(
                joinedload(Survey.questions).joinedload(SurveyQuestion.code),
                joinedload(Survey.questions).joinedload(SurveyQuestion.options).joinedload(SurveyQuestionOption.code)
            )
        )
        survey = query.one_or_none()

        if survey:
            return ResponseValidator(survey_definition=survey, session=self._session)
        else:
            return None

    def _send_error_message(self):
        if not self._error_list:
            self._output_result(f'No validation errors were found since {self._since_date}')
            return

        result_text = f'Validation errors for survey responses received since {self._since_date.date()}\n'
        result_list = []
        if self._summarize_results:
            for key, error_list in self._error_list.items():
                survey_code, question_code, error_str = key
                result_list.append(
                    f'{survey_code} "{question_code}" Error: {error_str}, number affected answers: {len(error_list)}'
                )
        else:
            for error_list in self._error_list.values():
                for error in error_list:
                    result_list.append(error)

        result_text += '\n'.join(sorted(result_list))
        self._output_result(result_text)

    def _output_result(self, result_str):
        if self._slack_webhook:
            client = SlackMessageHandler(webhook_url=self._slack_webhook)
            client.send_message_to_webhook(
                message_data={
                    'text': result_str
                }
            )
        else:
            logging.info(result_str)
{'fraction_non_alphanumeric': 0.07487237663074305, 'fraction_numerical': 0.0011344299489506524, 'mean_word_length': 2.5095541401273884, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 3, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4543572', 'n_tokens_mistral': 2398, 'n_tokens_neox': 2328, 'n_words': 493}