def test_equal_na_vs_non_na(self): """Test that consensus_vote() returns an evaluation when an equal number of N/A and non-N/A votes have been cast.""" for vote in [ Eval.very_inconsistent, Eval.neutral, Eval.very_consistent ]: self.assertEqual(aggregate_vote([vote, Eval.not_applicable]), vote) self.assertEqual(aggregate_vote([Eval.not_applicable, vote]), vote)
def test_round_toward_neutral(self): """Test that consensus_vote() rounds the vote toward a neutral assessment.""" self.assertEqual( aggregate_vote([Eval.consistent, Eval.very_consistent]), Eval.consistent) self.assertEqual( aggregate_vote([Eval.inconsistent, Eval.very_inconsistent]), Eval.inconsistent)
def detail(request, board_id, dummy_board_slug=None): """Return a detail view for the given board. Evidence is sorted in order of diagnosticity. Hypotheses are sorted in order of consistency. """ # NOTE: Django's page cache considers full URL including dummy_board_slug. In the future, we may want to adjust # the page key to only consider the id and the query parameters. # https://docs.djangoproject.com/en/1.10/topics/cache/#the-per-view-cache # NOTE: cannot cache page for logged in users b/c comments section contains CSRF and other protection mechanisms. view_type = ( "aggregate" if request.GET.get("view_type") is None else request.GET["view_type"] ) board = get_object_or_404(Board, pk=board_id) permissions = board.permissions.for_user(request.user) if "read_board" not in permissions: raise PermissionDenied() if view_type == "comparison" and not request.user.is_authenticated: raise PermissionDenied() collaborator_ids = board.collaborator_ids() vote_type = request.GET.get( "vote_type", default=("collab" if request.user.id in collaborator_ids else "all"), ) # calculate aggregate and disagreement for each evidence/hypothesis pair all_votes = list(board.evaluation_set.all()) agg_votes = ( [x for x in all_votes if x.user_id in collaborator_ids] if vote_type == "collab" else all_votes ) def _pair_key(evaluation): return evaluation.evidence_id, evaluation.hypothesis_id keyed = defaultdict(list) for vote in agg_votes: keyed[_pair_key(vote)].append(Eval(vote.value)) aggregate = {k: aggregate_vote(v) for k, v in keyed.items()} disagreement = {k: calc_disagreement(v) for k, v in keyed.items()} user_votes = ( {_pair_key(v): Eval(v.value) for v in all_votes if v.user_id == request.user.id} if request.user.is_authenticated else None ) # augment hypotheses and evidence with diagnosticity and consistency def _group(first, second, func, key): return [(f, func([keyed[key(f, s)] for s in second])) for f in first] hypotheses = list(board.hypothesis_set.filter(removed=False)) evidence = list(board.evidence_set.filter(removed=False)) hypothesis_consistency = _group( hypotheses, evidence, hypothesis_sort_key, key=lambda h, e: (e.id, h.id) ) evidence_diagnosticity = _group( evidence, hypotheses, evidence_sort_key, key=lambda e, h: (e.id, h.id) ) return render( request, "boards/detail.html", { "board": board, "permissions": permissions, "evidences": sorted(evidence_diagnosticity, key=lambda e: e[1]), "hypotheses": sorted(hypothesis_consistency, key=lambda h: h[1]), "view_type": view_type, "vote_type": vote_type, "votes": aggregate, "user_votes": user_votes, "disagreement": disagreement, "meta_description": board.board_desc, "allow_share": not getattr(settings, "ACCOUNT_REQUIRED", False), "debug_stats": DEBUG, }, )
def detail(request, board_id, dummy_board_slug=None): """Return a detail view for the given board. Evidence is sorted in order of diagnosticity. Hypotheses are sorted in order of consistency. """ # NOTE: Django's page cache considers full URL including dummy_board_slug. In the future, we may want to adjust # the page key to only consider the id and the query parameters. # https://docs.djangoproject.com/en/1.10/topics/cache/#the-per-view-cache # NOTE: cannot cache page for logged in users b/c comments section contains CSRF and other protection mechanisms. view_type = 'aggregate' if request.GET.get('view_type') is None else request.GET['view_type'] board = get_object_or_404(Board, pk=board_id) permissions = board.permissions.for_user(request.user) if 'read_board' not in permissions: raise PermissionDenied() if view_type == 'comparison' and not request.user.is_authenticated: raise PermissionDenied() vote_type = request.GET.get('vote_type', default=( 'collab' # rewrite to avoid unnecessary lookup if key is present? if board.permissions.collaborators.filter(pk=request.user.id).exists() else 'all' )) all_votes = list(board.evaluation_set.all()) # calculate aggregate and disagreement for each evidence/hypothesis pair agg_votes = all_votes if vote_type == 'collab': collaborators = set([c.id for c in board.permissions.collaborators.all()]) agg_votes = [v for v in all_votes if v.user_id in collaborators] def _pair_key(evaluation): return evaluation.evidence_id, evaluation.hypothesis_id keyed = defaultdict(list) for vote in agg_votes: keyed[_pair_key(vote)].append(Eval(vote.value)) aggregate = {k: aggregate_vote(v) for k, v in keyed.items()} disagreement = {k: calc_disagreement(v) for k, v in keyed.items()} user_votes = ( {_pair_key(v): Eval(v.value) for v in all_votes if v.user_id == request.user.id} if request.user.is_authenticated else None ) # augment hypotheses and evidence with diagnosticity and consistency def _group(first, second, func, key): return [(f, func([keyed[key(f, s)] for s in second])) for f in first] hypotheses = list(board.hypothesis_set.filter(removed=False)) evidence = list(board.evidence_set.filter(removed=False)) hypothesis_consistency = _group(hypotheses, evidence, hypothesis_sort_key, key=lambda h, e: (e.id, h.id)) evidence_diagnosticity = _group(evidence, hypotheses, evidence_sort_key, key=lambda e, h: (e.id, h.id)) context = { 'board': board, 'permissions': permissions, 'evidences': sorted(evidence_diagnosticity, key=lambda e: e[1]), 'hypotheses': sorted(hypothesis_consistency, key=lambda h: h[1]), 'view_type': view_type, 'vote_type': vote_type, 'votes': aggregate, 'user_votes': user_votes, 'disagreement': disagreement, 'meta_description': board.board_desc, 'allow_share': not getattr(settings, 'ACCOUNT_REQUIRED', False), 'debug_stats': DEBUG, } return render(request, 'boards/detail.html', context)
def test_no_votes_has_no_consensus(self): """Test that aggregate_vote() returns None if no votes have been cast.""" self.assertIsNone(aggregate_vote([]))
def test_none_na_consensus_for_single_vote(self): """Test that aggregate_vote() returns the evaluation if only a single vote is cast.""" self.assertEqual(aggregate_vote([Eval.consistent]), Eval.consistent)
def test_na_consensus_for_single_vote(self): """Test that aggregate_vote() returns N/A if only a single N/A vote is cast""" self.assertEqual(aggregate_vote([Eval.not_applicable]), Eval.not_applicable)