def make_control_nonaction(self, experiment_thing, submission, group="control"):
        if(self.submission_acceptable(submission) == False):
            self.log.info("{0}: Submission {1} is unacceptable. Declining to make nonaction".format(
                self.__class__.__name__,
                submission.id))
            return None

        metadata      = json.loads(experiment_thing.metadata_json)
        treatment_arm = int(metadata['randomization']['treatment'])
        condition     = metadata['condition']
        
        experiment_action = ExperimentAction(
            experiment_id = self.experiment.id,
            praw_key_id = PrawKey.get_praw_id(ENV, self.experiment_name),
            action = "Intervention",
            action_object_type = ThingType.SUBMISSION.value,
            action_object_id = submission.id,
            metadata_json = json.dumps({"group":group, "condition":condition, "arm": "arm_"+str(treatment_arm)})
        )
        self.db_session.add(experiment_action)
        self.db_session.commit()
        self.log.info("{0}: Experiment {1} applied arm {2} to post {3} (condition = {4})".format(
            self.__class__.__name__,
            self.experiment_name, 
            treatment_arm,
            submission.id,
            condition
        ))
        return experiment_action.id
    def make_control_nonaction(self,
                               experiment_thing,
                               submission,
                               group="control"):
        if (self.submission_acceptable(submission) == False):
            return None

        metadata = json.loads(experiment_thing.metadata_json)
        treatment_arm = int(metadata['randomization']['treatment'])
        condition = metadata['condition']

        experiment_action = ExperimentAction(
            experiment_id=self.experiment.id,
            praw_key_id=PrawKey.get_praw_id(ENV, self.experiment_name),
            action="Intervention",
            action_object_type=ThingType.SUBMISSION.value,
            action_object_id=submission.id,
            metadata_json=json.dumps({
                "group": group,
                "condition": condition,
                "arm": "arm_" + str(treatment_arm)
            }))
        self.db_session.add(experiment_action)
        self.db_session.commit()
        self.log.info(
            "{0}: Experiment {1} applied arm {2} to post {3} (condition = {4})"
            .format(self.__class__.__name__, self.experiment_name,
                    treatment_arm, submission.id, condition))
        return experiment_action.id
Exemplo n.º 3
0
  def connect(self, controller="Main"):
    r = None #Praw Connection Object
    handler = MultiprocessHandler()

    # Check the Database for a Stored Praw Key
    db_praw_id = PrawKey.get_praw_id(self.env, controller)
    pk = self.db_session.query(PrawKey).filter_by(id=db_praw_id).first()
    r = praw.Reddit(user_agent="Test version of CivilServant by u/natematias", handler=handler)
    
    access_information = {}
    
    if(pk is None):
      with open(os.path.join(self.base_dir, "config","access_information_{environment}.pickle".format(environment=self.env)), "rb") as fp:
          access_information = pickle.load(fp)
    else:
      access_information['access_token'] = pk.access_token
      access_information['refresh_token'] = pk.refresh_token
      access_information['scope'] = json.loads(pk.scope)
    
    r.set_access_credentials(**access_information)

    new_access_information = {}
    update_praw_key = False

    # SAVE OR UPDATE THE ACCESS TOKEN IF NECESSARY
    if(pk is None):
      pk = PrawKey(id=db_praw_id,
                   access_token = r.access_token,
                   refresh_token = r.refresh_token,
                   scope = json.dumps(list(r._authentication)),
                   authorized_username = r.user.json_dict['name'],
                   authorized_user_id = r.user.json_dict['id'])
      self.db_session.add(pk)

    elif(access_information['access_token'] != r.access_token or
       access_information['refresh_token'] != r.refresh_token):
       pk.access_token = r.access_token
       pk.refresh_token = r.refresh_token
       pk.scope = json.dumps(list(r._authentication))
      
    self.db_session.commit()
    return r
    def submission_acceptable(self, submission):
        if (submission is None):
            ## TODO: Determine what to do if you can't find the post
            self.log.error("{0}: Can't find experiment {1} post {2}".format(
                self.__class__.__name__, self.subreddit, submission.id))
            return False

        ## Avoid Acting if the Intervention has already been recorded
        if (self.db_session.query(ExperimentAction).filter(
                and_(
                    ExperimentAction.experiment_id == self.experiment.id,
                    ExperimentAction.action_object_type
                    == ThingType.SUBMISSION.value,
                    ExperimentAction.action_object_id == submission.id,
                    ExperimentAction.action == "Intervention")).count() > 0):
            self.log.info(
                "{0}: Experiment {1} post {2} already has an Intervention recorded"
                .format(self.__class__.__name__, self.experiment_name,
                        submission.id))
            return False

        ## possible comment texts to avoid
        all_experiment_messages = []
        for label, condition in self.experiment_settings['conditions'].items():
            all_experiment_messages = all_experiment_messages + list(
                condition['arms'].values())

        ## Avoid Acting if an identical sticky comment already exists
        for comment in submission.comments:
            if (hasattr(comment, "stickied") and comment.stickied
                    and (comment.body in all_experiment_messages)):
                self.log.info(
                    "{0}: Experiment {1} post {2} already has a sticky comment {2}"
                    .format(self.__class__.__name__, self.experiment_name,
                            submission.id, comment.id))
                return False

        ## Avoid Acting if the submission is not recent enough
        curtime = time.time()
        #        if((curtime - submission.created_utc) > 10800):
        if ((curtime - submission.created_utc) > self.max_eligibility_age):
            self.log.info(
                "{0}: Submission created_utc {1} is {2} seconds greater than current time {3}, exceeding the max eligibility age of {4}. Declining to Add to the Experiment"
                .format(self.__class__.__name__, submission.created_utc,
                        curtime - submission.created_utc, curtime,
                        self.max_eligibility_age))
            experiment_action = ExperimentAction(
                experiment_id=self.experiment.id,
                praw_key_id=PrawKey.get_praw_id(ENV, self.experiment_name),
                action="NonIntervention:MaxAgeExceeded",
                action_object_type=ThingType.SUBMISSION.value,
                action_object_id=submission.id)
            return False
        return True
    def make_sticky_post(self, submission):
        if(self.submission_acceptable(submission) == False):
            return None

        ## FIRST STEP: DETERMINE AMA STATUS
        ## TODO: refactor to check this only once
        is_ama = self.is_ama(submission)
        if(is_ama):
            comment_text = self.ama_comment_text
        else:
            comment_text = self.nonama_comment_text     

        comment = submission.add_comment(comment_text)
        distinguish_results = comment.distinguish(sticky=True)
        self.log.info("Experiment {0} applied treatment to post {1} (AMA = {2}). Result: {3}".format(
            self.experiment_name, 
            submission.id,
            is_ama,
            str(distinguish_results)
        ))

        experiment_action = ExperimentAction(
            experiment_id = self.experiment.id,
            praw_key_id = PrawKey.get_praw_id(ENV, self.experiment_name),
            action_subject_type = ThingType.COMMENT.value,
            action_subject_id = comment.id,
            action = "Intervention",
            action_object_type = ThingType.SUBMISSION.value,
            action_object_id = submission.id,
            metadata_json = json.dumps({"group":"treatment", 
                "action_object_created_utc":comment.created_utc})
        )

        comment_thing = ExperimentThing(
            experiment_id = self.experiment.id,
            object_created = datetime.datetime.fromtimestamp(comment.created_utc),
            object_type = ThingType.COMMENT.value,
            id = comment.id,
            metadata_json = json.dumps({"group":"treatment","submission_id":submission.id})
        )

        self.db_session.add(comment_thing)
        self.db_session.add(experiment_action)
        self.db_session.commit()
        return distinguish_results
 def make_control_nonaction(self, submission):
     if(self.submission_acceptable(submission) == False):
         return None
     experiment_action = ExperimentAction(
         experiment_id = self.experiment.id,
         praw_key_id = PrawKey.get_praw_id(ENV, self.experiment_name),
         action = "Intervention",
         action_object_type = ThingType.SUBMISSION.value,
         action_object_id = submission.id,
         metadata_json = json.dumps({"group":"control"})
     )
     self.db_session.add(experiment_action)
     self.db_session.commit()
     self.log.info("Experiment {0} applied control condition to post {1}".format(
         self.experiment_name, 
         submission.id
     ))
     return experiment_action.id
    def submission_acceptable(self, submission):
        if(submission is None):
            ## TODO: Determine what to do if you can't find the post
            self.log.error("Can't find experiment {0} post {1}".format(self.subreddit, submission.id))
            return False            

        ## Avoid Acting if the Intervention has already been recorded
        if(self.db_session.query(ExperimentAction).filter(and_(
            ExperimentAction.experiment_id      == self.experiment.id,
            ExperimentAction.action_object_type == ThingType.SUBMISSION.value,
            ExperimentAction.action_object_id   == submission.id,
            ExperimentAction.action             == "Intervention")).count() > 0):
                self.log.info("Experiment {0} post {1} already has an Intervention recorded".format(
                    self.experiment_name, 
                    submission.id))            
                return False

        ## Avoid Acting if an identical sticky comment already exists
        for comment in submission.comments:
            if(comment.stickied and (comment.body == self.ama_comment_text or comment.body == self.nonama_comment_text)):
                self.log.info("Experiment {0} post {1} already has a sticky comment {2}".format(
                    self.experiment_name, 
                    submission.id,
                    comment.id))
                return False

        ## Avoid Acting if the submission is not recent enough
        curtime = time.time()
#        if((curtime - submission.created_utc) > 10800):
        if((curtime - submission.created_utc) > self.max_eligibility_age):
            self.log.info("Submission created_utc {0} is {1} seconds greater than current time {2}, exceeding the max eligibility age of {3}. Declining to Add to the Experiment".format(
                submission.created_utc,
                curtime - submission.created_utc,
                curtime,
                self.max_eligibility_age))
            experiment_action = ExperimentAction(
                experiment_id = self.experiment.id,
                praw_key_id = PrawKey.get_praw_id(ENV, self.experiment_name),
                action = "NonIntervention:MaxAgeExceeded",
                action_object_type = ThingType.SUBMISSION.value,
                action_object_id = submission.id
            )
            return False
        return True
Exemplo n.º 8
0
    def connect(self, controller="Main"):
        r = None  #Praw Connection Object
        handler = MultiprocessHandler()

        # Check the Database for a Stored Praw Key
        db_praw_id = PrawKey.get_praw_id(self.env, controller)
        pk = self.db_session.query(PrawKey).filter_by(id=db_praw_id).first()
        r = praw.Reddit(
            user_agent="Test version of CivilServant by u/natematias",
            handler=handler)

        access_information = {}

        if (pk is None):
            with open(
                    os.path.join(
                        self.base_dir, "config",
                        "access_information_{environment}.pickle".format(
                            environment=self.env)), "rb") as fp:
                access_information = pickle.load(fp)
        else:
            access_information['access_token'] = pk.access_token
            access_information['refresh_token'] = pk.refresh_token
            access_information['scope'] = json.loads(pk.scope)

        r.set_access_credentials(**access_information)

        new_access_information = {}
        update_praw_key = False

        # SAVE OR UPDATE THE ACCESS TOKEN IF NECESSARY
        if (pk is None):
            pk = PrawKey(id=db_praw_id,
                         access_token=r.access_token,
                         refresh_token=r.refresh_token,
                         scope=json.dumps(list(r._authentication)),
                         authorized_username=r.user.json_dict['name'],
                         authorized_user_id=r.user.json_dict['id'])
            self.db_session.add(pk)

        elif (access_information['access_token'] != r.access_token
              or access_information['refresh_token'] != r.refresh_token):
            pk.access_token = r.access_token
            pk.refresh_token = r.refresh_token
            pk.scope = json.dumps(list(r._authentication))

        self.db_session.commit()
        return r
    def set_stylesheet(self, condition, arm):
        arms = self.experiment_settings['conditions'][condition]['arms']
        intervention_line = arms[arm]

        found_code = False

        stylesheet_data = self.r.get_stylesheet(self.subreddit)
        if "stylesheet" in stylesheet_data.keys():
            line_list = []
            for line in stylesheet_data['stylesheet'].split("\n"):
                ## IF A LINE FROM THE STUDY IS FOUND,
                ## REPLACE IT WITH THE INTERVENTION
                if line in arms.values():
                    line = intervention_line
                    found_code = True
                line_list.append(line)

            ## IF THE CODE IS NOT FOUND, ADD IT TO THE END
            if (found_code != True):
                line_list.append("/* CivilServantBot Experiment CSS */")
                line_list.append(intervention_line)
                line_list.append("")
            new_stylesheet = "\n".join(line_list)

        result = self.r.set_stylesheet(self.subreddit, new_stylesheet)
        if ('errors' in result.keys() and len(result['errors']) == 0):

            self.log.info(
                "{0}: Experiment {1}: Applied Arm {3} of Condition {4} in {2}".
                format(self.__class__.__name__, self.experiment.id,
                       self.subreddit, arm, condition))
            experiment_action = ExperimentAction(
                experiment_id=self.experiment.id,
                praw_key_id=PrawKey.get_praw_id(ENV, self.experiment_name),
                action="Intervention",
                action_object_type=ThingType.STYLESHEET.value,
                action_object_id=None,
                metadata_json=json.dumps({
                    "arm": arm,
                    "condition": condition
                }))
            self.db_session.add(experiment_action)
            self.db_session.commit()
        else:
            self.log.error(
                "{0}: Experiment {1}: Failed to apply Arm {2} of Condition {3}. Reddit errors: {4}"
                .format(self.__class__.__name__, self.experiment.id,
                        self.subreddit, arm, condition,
                        ", ".join(result['errors'])))
            #            experiment_action = ExperimentAction(
            #                experiment_id = self.experiment.id,
            #                praw_key_id = PrawKey.get_praw_id(ENV, self.experiment_name),
            #                action = "NonIntervention:PrawError.{0}.{1}".format(condition,arm),
            #                action_object_type = ThingType.STYLESHEET.value,
            #                action_object_id = None
            #            )
            #            self.db_session.add(experiment_action)
            #            self.db_session.commit()
            ## IF WE FAILED TO APPLY THE INTERVENTION, ROLL BACK THAT RANDOMIZATION
            self.experiment_settings['conditions'][condname][
                'next_randomization'] -= 1
            self.experiment.settings_json = json.dumps(
                self.experiment_settings)
            self.db_session.commit()

        ## TO HELP WITH TESTING, RETURN THE FULL TEXT OF THE STYLESHEET
        return new_stylesheet
    def make_sticky_post(self, experiment_thing, submission):
        if (self.submission_acceptable(submission) == False):
            return None

        metadata = json.loads(experiment_thing.metadata_json)
        treatment_arm = int(metadata['randomization']['treatment'])
        condition = metadata['condition']

        comment_text = self.experiment_settings['conditions'][condition][
            'arms']["arm_" + str(treatment_arm)]

        ## THIS METHOD IS FOR USE WHEN INTENDING TO TEST AN EXPERIMENT WITHOUT
        ## MAKING ANY ACTUAL COMMENTS ON REDDIT
        #def _TEST_add_comment():
        #    import collections, random, string
        #    return collections.namedtuple("Comment", ["id", "created_utc"])(
        #        "_" + "".join(random.choice(string.ascii_lowercase) for i in range(6)),
        #        datetime.datetime.utcnow().timestamp())

        ## TO USE THIS DRY RUN TEST CODE, UNCOMMENT THE FOLLOWING TWO LINES
        ## AND COMMENT OUT THE TWO LINES AFTER IT
        #comment = _TEST_add_comment() #submission.add_comment(comment_text)
        ## distinguish_results = "DUMMY DISTINGUISH: Assume successful"

        comment = submission.add_comment(comment_text)
        distinguish_results = comment.distinguish(sticky=True)

        self.log.info(
            "{0}: Experiment {1} applied arm {2} to post {3} (condition = {4}). Result: {5}"
            .format(self.__class__.__name__, self.experiment_name,
                    treatment_arm, submission.id, condition,
                    str(distinguish_results)))

        experiment_action = ExperimentAction(
            experiment_id=self.experiment.id,
            praw_key_id=PrawKey.get_praw_id(ENV, self.experiment_name),
            action_subject_type=ThingType.COMMENT.value,
            action_subject_id=comment.id,
            action="Intervention",
            action_object_type=ThingType.SUBMISSION.value,
            action_object_id=submission.id,
            metadata_json=json.dumps({
                "group":
                "treatment",
                "condition":
                condition,
                "arm":
                "arm_" + str(treatment_arm),
                "randomization":
                metadata['randomization'],
                "action_object_created_utc":
                comment.created_utc
            }))

        comment_thing = ExperimentThing(
            experiment_id=self.experiment.id,
            object_created=datetime.datetime.fromtimestamp(
                comment.created_utc),
            object_type=ThingType.COMMENT.value,
            id=comment.id,
            metadata_json=json.dumps({
                "group": "treatment",
                "arm": "arm_" + str(treatment_arm),
                "condition": condition,
                "randomization": metadata['randomization'],
                "submission_id": submission.id
            }))

        self.db_session.add(comment_thing)
        self.db_session.add(experiment_action)
        self.db_session.commit()
        return distinguish_results
    def set_stylesheet(self, condition, arm):
        arms = self.experiment_settings['conditions'][condition]['arms']
        intervention_line = arms[arm]

        found_code = False

        stylesheet_data = self.r.get_stylesheet(self.subreddit)
        if "stylesheet" in stylesheet_data.keys():
            line_list = []
            for line in stylesheet_data['stylesheet'].split("\n"):
                ## IF A LINE FROM THE STUDY IS FOUND,
                ## REPLACE IT WITH THE INTERVENTION
                if line in arms.values():
                    line = intervention_line
                    found_code = True
                line_list.append(line)

            ## IF THE CODE IS NOT FOUND, ADD IT TO THE END
            if(found_code!=True):
                line_list.append("/* CivilServantBot Experiment CSS */")
                line_list.append(intervention_line)
                line_list.append("")
            new_stylesheet = "\n".join(line_list)

            
        
        result = self.r.set_stylesheet(self.subreddit, new_stylesheet)
        if('errors' in result.keys() and len(result['errors'])==0):

            self.log.info("{0}: Experiment {1}: Applied Arm {3} of Condition {4} in {2}".format(
                    self.__class__.__name__,
                    self.experiment.id,
                    self.subreddit, 
                    arm,
                    condition))
            experiment_action = ExperimentAction(
                experiment_id = self.experiment.id,
                praw_key_id = PrawKey.get_praw_id(ENV, self.experiment_name),
                action = "Intervention",
                action_object_type = ThingType.STYLESHEET.value,
                action_object_id = None,
                metadata_json  = json.dumps({"arm":arm, "condition":condition})

            )
            self.db_session.add(experiment_action)
            self.db_session.commit()
        else:
            self.log.error("{0}: Experiment {1}: Failed to apply Arm {2} of Condition {3}. Reddit errors: {4}".format(
                    self.__class__.__name__,
                    self.experiment.id,
                    self.subreddit, 
                    arm,condition, ", ".join(result['errors'])))
#            experiment_action = ExperimentAction(
#                experiment_id = self.experiment.id,
#                praw_key_id = PrawKey.get_praw_id(ENV, self.experiment_name),
#                action = "NonIntervention:PrawError.{0}.{1}".format(condition,arm),
#                action_object_type = ThingType.STYLESHEET.value,
#                action_object_id = None
#            )
#            self.db_session.add(experiment_action)
#            self.db_session.commit()
            ## IF WE FAILED TO APPLY THE INTERVENTION, ROLL BACK THAT RANDOMIZATION
            self.experiment_settings['conditions'][condname]['next_randomization'] -= 1
            self.experiment.settings_json = json.dumps(self.experiment_settings)
            self.db_session.commit()

        ## TO HELP WITH TESTING, RETURN THE FULL TEXT OF THE STYLESHEET
        return new_stylesheet
    def make_sticky_post(self, experiment_thing, submission):
        if(self.submission_acceptable(submission) == False):
            self.log.info("{0}: Submission {1} is unacceptable. Declining to make sticky post".format(
                self.__class__.__name__,
                submission.id))
            return None

        metadata = json.loads(experiment_thing.metadata_json)
        treatment_arm = int(metadata['randomization']['treatment'])
        condition     = metadata['condition']

        comment_text  = self.experiment_settings['conditions'][condition]['arms']["arm_" + str(treatment_arm)]

        ## THIS METHOD IS FOR USE WHEN INTENDING TO TEST AN EXPERIMENT WITHOUT
        ## MAKING ANY ACTUAL COMMENTS ON REDDIT
        #def _TEST_add_comment():
        #    import collections, random, string
        #    return collections.namedtuple("Comment", ["id", "created_utc"])(
        #        "_" + "".join(random.choice(string.ascii_lowercase) for i in range(6)),
        #        datetime.datetime.utcnow().timestamp())

        ## TO USE THIS DRY RUN TEST CODE, UNCOMMENT THE FOLLOWING TWO LINES
        ## AND COMMENT OUT THE TWO LINES AFTER IT
        #comment = _TEST_add_comment() #submission.add_comment(comment_text)
        ## distinguish_results = "DUMMY DISTINGUISH: Assume successful" 

        comment = submission.add_comment(comment_text)
        distinguish_results = comment.distinguish(sticky=True)

        self.log.info("{0}: Experiment {1} applied arm {2} to post {3} (condition = {4}). Result: {5}".format(
            self.__class__.__name__,            
            self.experiment_name,
            treatment_arm, 
            submission.id,
            condition,
            str(distinguish_results)
        ))

        experiment_action = ExperimentAction(
            experiment_id = self.experiment.id,
            praw_key_id = PrawKey.get_praw_id(ENV, self.experiment_name),
            action_subject_type = ThingType.COMMENT.value,
            action_subject_id = comment.id,
            action = "Intervention",
            action_object_type = ThingType.SUBMISSION.value,
            action_object_id = submission.id,
            metadata_json = json.dumps({"group":"treatment", "condition":condition,
                "arm":"arm_" + str(treatment_arm),
                "randomization": metadata['randomization'],
                "action_object_created_utc":comment.created_utc})
        )

        comment_thing = ExperimentThing(
            experiment_id = self.experiment.id,
            object_created = datetime.datetime.fromtimestamp(comment.created_utc),
            object_type = ThingType.COMMENT.value,
            id = comment.id,
            metadata_json = json.dumps({"group":"treatment", "arm":"arm_"+str(treatment_arm),
                                        "condition":condition,
                                        "randomization": metadata['randomization'],
                                        "submission_id":submission.id})
        )

        self.db_session.add(comment_thing)
        self.db_session.add(experiment_action)
        self.db_session.commit()
        return distinguish_results