@gen.engine
  def _CheckBadViewpointAccounting(self, viewpoint, ep_photos_list, accounting_list, callback):
    act_dict = {}

    def _IncrementAccountingWith(hash_key, sort_key, increment_from):
      # Do not create entries if stats are zero.
      if increment_from.num_photos == 0:
        return
      key = (hash_key, sort_key)
      act_dict.setdefault(key, Accounting(hash_key, sort_key)).IncrementStatsFrom(increment_from)

    for episode, photos, posts, user_posts in ep_photos_list:
      act = Accounting()
      act.IncrementFromPhotos([photo for photo, post in zip(photos, posts) if not post.IsRemoved()])
      if viewpoint.IsDefault():
        # Default viewpoint: compute viewpoint-level owned-by:<user> and user-level owned-by.
        _IncrementAccountingWith('%s:%s' % (Accounting.VIEWPOINT_SIZE, viewpoint.viewpoint_id),
                                 '%s:%d' % (Accounting.OWNED_BY, episode.user_id),
                                 act)
        _IncrementAccountingWith('%s:%d' % (Accounting.USER_SIZE, viewpoint.user_id),
                                 Accounting.OWNED_BY, act)
      else:
        # Shared viewpoint: compute viewpoint-level shared-by:<user> and visible-to. User-level
        # accounting for those categories sums multiple viewpoint-level entries, so cannot be computed here.
        _IncrementAccountingWith('%s:%s' % (Accounting.VIEWPOINT_SIZE, viewpoint.viewpoint_id),
                                 '%s:%d' % (Accounting.SHARED_BY, episode.user_id),
                                 act)
        _IncrementAccountingWith('%s:%s' % (Accounting.VIEWPOINT_SIZE, viewpoint.viewpoint_id),
                                 Accounting.VISIBLE_TO,
                                 act)

    # Iterate over all existing accounting entries.
    for act in accounting_list:
      key = (act.hash_key, act.sort_key)
      built_act = act_dict.get(key, None)
      if built_act is None:
        # Entries can drop to 0 when photos get removed or unshared. However, we need to keep
        # such entries around to properly handle operation replays.
        built_act = Accounting(hash_key=act.hash_key, sort_key=act.sort_key)

      if not built_act.StatsEqual(act):
        yield gen.Task(self._ReportCorruption, viewpoint.viewpoint_id,
                       'wrong accounting', '%s != %s' % (act, built_act),
                       partial(self._RepairBadViewpointAccounting, 'update', built_act))

      # Remove entry from the built accounting entries.
      act_dict.pop(key, None)

    # Iterate over the remaining built accounting entries.
    for built_act in act_dict.values():
      yield gen.Task(self._ReportCorruption, viewpoint.viewpoint_id, 'missing accounting', built_act,
                     partial(self._RepairBadViewpointAccounting, 'add', built_act))

    callback()
Compute all accounting entries from ep_photos_list and verify that they match the entries in 'accounting'.
dataset/ETHPy150Open viewfinderco/viewfinder/backend/db/tools/dbchk.py/DatabaseChecker._CheckBadViewpointAccounting