@gen.engine
  def _CheckMissingActivities(self, viewpoint, activities, followers, ep_photos_list, comments, callback):
    # Index the activity contents.
    index = set()

    # Viewpoint creator is automatically a follower.
    index.add(viewpoint.user_id)

    has_share_new = False
    for activity in activities:
      if activity.name == 'share_new':
        has_share_new = True

      invalidate = json.loads(activity.json)
      if activity.name in ['add_followers', 'share_new']:
        [index.add(f_id) for f_id in invalidate['follower_ids']]
      if activity.name == 'merge_accounts':
        [index.add(invalidate['target_user_id'])]
      if activity.name == 'post_comment':
        index.add(invalidate['comment_id'])
      if activity.name in ['share_existing', 'share_new', 'save_photos']:
        for item in invalidate['episodes']:
          index.add(item['episode_id'])
          [index.add(ph_id) for ph_id in item['photo_ids']]
      if activity.name in ['upload_episode']:
        index.add(invalidate['episode_id'])
        [index.add(ph_id) for ph_id in invalidate['photo_ids']]

    # Iterate through each of the viewpoint assets and make sure they're "covered" by an activity.
    missing_follower_ids = [f.user_id for f in followers if f.user_id not in index]

    # Only check viewpoints with content.
    if len(ep_photos_list) > 0 or len(comments) > 0:
      for episode, photos, _, _ in ep_photos_list:
        if episode.episode_id not in index:
          if viewpoint.IsDefault():
            # Default viewpoints have upload_episode and save_photos activities.
            if episode.parent_ep_id is None:
              yield gen.Task(self._ReportCorruption, viewpoint.viewpoint_id,
                             'missing upload_episode activity', episode.episode_id,
                             partial(self._RepairMissingActivities, episode.user_id, viewpoint,
                                     'upload_episode', (episode, photos)))
            else:
              yield gen.Task(self._ReportCorruption, viewpoint.viewpoint_id,
                             'missing save_photos activity', episode.episode_id,
                             partial(self._RepairMissingActivities, episode.user_id, viewpoint,
                                     'save_photos', (episode, photos)))
          elif not has_share_new:
            # First share activity is share_new.
            has_share_new = True
            yield gen.Task(self._ReportCorruption, viewpoint.viewpoint_id,
                           'missing share_new activity', episode.episode_id,
                           partial(self._RepairMissingActivities, episode.user_id, viewpoint,
                                   'share_new', (episode, photos, missing_follower_ids)))
            missing_follower_ids = None
          else:
            yield gen.Task(self._ReportCorruption, viewpoint.viewpoint_id,
                           'missing share_existing activity', episode.episode_id,
                           partial(self._RepairMissingActivities, episode.user_id, viewpoint,
                                   'share_existing', (episode, photos, None)))

      if missing_follower_ids:
        yield gen.Task(self._ReportCorruption, viewpoint.viewpoint_id,
                       'missing add_followers activity', missing_follower_ids,
                       partial(self._RepairMissingActivities, viewpoint.user_id, viewpoint,
                               'add_followers', missing_follower_ids))

      for comment in comments:
        if comment.comment_id not in index:
          yield gen.Task(self._ReportCorruption, viewpoint.viewpoint_id,
                         'missing post_comment activity', comment.comment_id,
                         partial(self._RepairMissingActivities, comment.user_id, viewpoint,
                                 'post_comment', comment.comment_id))

    callback()
Checks that every follower, episode, photo, and comment is
dataset/ETHPy150Open viewfinderco/viewfinder/backend/db/tools/dbchk.py/DatabaseChecker._CheckMissingActivities