Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix progress for conditional questions in datasets #1107

Merged
merged 7 commits into from
Aug 13, 2024
81 changes: 51 additions & 30 deletions rdmo/projects/progress.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,42 +8,49 @@
from rdmo.questions.models import Page, Question, QuestionSet


def resolve_conditions(project, values):
# get all conditions for this catalog
pages_conditions_subquery = Page.objects.filter_by_catalog(project.catalog) \
def get_catalog_conditions(catalog):
pages_conditions_subquery = Page.objects.filter_by_catalog(catalog) \
.filter(conditions=OuterRef('pk'))
questionsets_conditions_subquery = QuestionSet.objects.filter_by_catalog(project.catalog) \
questionsets_conditions_subquery = QuestionSet.objects.filter_by_catalog(catalog) \
.filter(conditions=OuterRef('pk'))
questions_conditions_subquery = Question.objects.filter_by_catalog(project.catalog) \
questions_conditions_subquery = Question.objects.filter_by_catalog(catalog) \
.filter(conditions=OuterRef('pk'))

catalog_conditions = Condition.objects.annotate(has_page=Exists(pages_conditions_subquery)) \
.annotate(has_questionset=Exists(questionsets_conditions_subquery)) \
.annotate(has_question=Exists(questions_conditions_subquery)) \
.filter(Q(has_page=True) | Q(has_questionset=True) | Q(has_question=True)) \
.distinct().select_related('source', 'target_option')
return Condition.objects.annotate(
has_page=Exists(pages_conditions_subquery),
has_questionset=Exists(questionsets_conditions_subquery),
has_question=Exists(questions_conditions_subquery)
).filter(
Q(has_page=True) | Q(has_questionset=True) | Q(has_question=True)
).distinct().select_related('source', 'target_option')


def resolve_conditions(catalog, values):
# resolve conditions and return for each condition the set_indexes which resolved true
conditions = defaultdict(set)
for condition in get_catalog_conditions(catalog):
resolved_set_indexes = {value.set_index for value in values if condition.resolve([value])}
MyPyDavid marked this conversation as resolved.
Show resolved Hide resolved
conditions[condition.id].update(resolved_set_indexes)
return conditions

# evaluate conditions
conditions = set()
for condition in catalog_conditions:
if condition.resolve(values):
conditions.add(condition.id)

# return all true conditions for this project
return conditions
def compute_sets(values):
# compute sets from values (including empty values)
sets = defaultdict(lambda: defaultdict(list))
for attribute, set_prefix, set_index in values.distinct_list():
sets[attribute][set_prefix].append(set_index)
return sets


def compute_navigation(section, project, snapshot=None):
# get all values for this project and snapshot
values = project.values.filter(snapshot=snapshot).select_related('attribute', 'option')

# get true conditions
conditions = resolve_conditions(project, values)
# resolve all conditions to get a dict mapping conditions to set_indexes
conditions = resolve_conditions(project.catalog, values)

# compute sets from values (including empty values)
sets = defaultdict(lambda: defaultdict(list))
for attribute, set_prefix, set_index in values.distinct_list():
sets[attribute][set_prefix].append(set_index)
sets = compute_sets(values)

# query distinct, non empty set values
values_list = values.exclude_empty().distinct_list()
Expand Down Expand Up @@ -94,13 +101,11 @@ def compute_progress(project, snapshot=None):
# get all values for this project and snapshot
values = project.values.filter(snapshot=snapshot).select_related('attribute', 'option')

# get true conditions
conditions = resolve_conditions(project, values)
# resolve all conditions to get a dict mapping conditions to set_indexes
conditions = resolve_conditions(project.catalog, values)

# compute sets from values (including empty values)
sets = defaultdict(lambda: defaultdict(list))
for attribute, set_prefix, set_index in values.distinct_list():
sets[attribute][set_prefix].append(set_index)
sets = compute_sets(values)

# query distinct, non empty set values
values_list = values.exclude_empty().distinct_list()
Expand Down Expand Up @@ -141,6 +146,7 @@ def count_questions(element, sets, conditions):

set_count = len(counted_sets)
else:
counted_sets = set()
set_count = 1

# loop over all children of this element
Expand All @@ -149,9 +155,13 @@ def count_questions(element, sets, conditions):
if isinstance(child, (Page, QuestionSet, Question)):
child_conditions = {condition.id for condition in child.conditions.all()}
else:
child_conditions = []
child_conditions = set()

# compute the intersection of the condition of this child with the full set of conditions
condition_intersection = child_conditions.intersection(conditions)

if not child_conditions or child_conditions.intersection(conditions):
# check if the element either has no condition or its conditions intersect with the full set of conditions
if not child_conditions or condition_intersection:
if isinstance(child, Question):
# for regular questions add the set_count to the counts dict, since the
# question should be answered in every set
Expand All @@ -163,7 +173,18 @@ def count_questions(element, sets, conditions):
child_count = sum(len(set_indexes) for set_indexes in sets[child.attribute.id].values())
counts[child.attribute.id] = max(counts[child.attribute.id], child_count)
else:
counts[child.attribute.id] = max(counts[child.attribute.id], set_count)
if condition_intersection:
# update the set_count for the current child element
# count only the sets that have conditions resolved to true
resolved_set_indexes = set()
for condition in condition_intersection:
resolved_set_indexes.update(conditions[condition])

current_count = len(counted_sets & resolved_set_indexes)
else:
current_count = set_count

counts[child.attribute.id] = max(counts[child.attribute.id], current_count)
else:
# for everything else, call this function recursively
counts.update(count_questions(child, sets, conditions))
Expand Down
Loading