-
Notifications
You must be signed in to change notification settings - Fork 22
Handle a question whose type changes but name doesn't. #187
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
base: main
Are you sure you want to change the base?
Changes from 12 commits
7fb1ad9
c15fc18
5af7218
9652455
5953cde
cc58e06
f4809a4
dc69313
3985489
a042146
3b0db4d
dc05c1d
50b5a2e
16a896a
d1c5429
f0ddf7d
bd25c51
785ebda
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -49,11 +49,21 @@ def _get_version_id_from_submission(self, submission): | |
|
|
||
| def _calculate_stats(self, submissions, fields, versions, lang): | ||
|
|
||
| metrics = {field.name: Counter() for field in fields} | ||
| metrics = {field.contextual_name: Counter() for field in fields} | ||
|
|
||
| submissions_count = 0 | ||
| submission_counts_by_version = Counter() | ||
|
|
||
| # When form contains questions with the same name with different types, | ||
| # Older found versions are pushed at the end of the list `fields` | ||
| # Because we want to match submission values with fields, we need to try | ||
| # to match with older version first. | ||
| # For example: Form contains two versions with one question. | ||
| # `reversed_fields` look this: | ||
| # [<FormField type="text" contextual_name="question_text_v123456">, | ||
| # <FormField type="integer" contextual_name="question">] | ||
| reversed_fields = list(reversed(fields)) | ||
|
|
||
| for entry in submissions: | ||
|
|
||
| version_id = self._get_version_id_from_submission(entry) | ||
|
|
@@ -62,14 +72,37 @@ def _calculate_stats(self, submissions, fields, versions, lang): | |
|
|
||
| submissions_count += 1 | ||
| submission_counts_by_version[version_id] += 1 | ||
| fields_to_skip = [] | ||
|
|
||
| # TODO: do we really need FormSubmission ? | ||
| entry = FormSubmission(entry).data | ||
| for field in fields: | ||
| if field.has_stats: | ||
| counter = metrics[field.name] | ||
|
|
||
| for field in reversed_fields: | ||
| if field.has_stats and field.name not in fields_to_skip: | ||
| counter = metrics[field.contextual_name] | ||
| raw_value = entry.get(field.path) | ||
| field_contextual_name = "{}_{}_{}".format( | ||
|
||
| field.name, | ||
| field.data_type, | ||
| version_id | ||
| ) | ||
|
|
||
| if raw_value is not None: | ||
| # Reminder `field.contextual_name` equals `<name>_<type>_<version_uid>`, | ||
| # If `field.contextual_name` matches this pattern, it's only valid for submissions of | ||
| # the same `<version_uid>`. | ||
| # Thanks to `reversed_fields`, we compared those fields before the ones of the latest version | ||
| if field.contextual_name.startswith("{}_{}_v".format( | ||
| field.name, | ||
| field.data_type | ||
| )): | ||
| # We have a match, we won't evaluate other fields with same name | ||
| # for this submission. | ||
| if field.contextual_name == field_contextual_name: | ||
| fields_to_skip.append(field.name) | ||
| else: | ||
| continue | ||
|
|
||
| values = list(field.parse_values(raw_value)) | ||
| counter.update(values) | ||
| counter['__submissions__'] += 1 | ||
|
|
@@ -80,7 +113,7 @@ def stats_generator(): | |
| for field in fields: | ||
| yield (field, | ||
| field.get_labels(lang)[0], | ||
| field.get_stats(metrics[field.name], lang=lang)) | ||
| field.get_stats(metrics[field.contextual_name], lang=lang)) | ||
|
|
||
| return AutoReportStats(self, stats_generator(), submissions_count, | ||
| submission_counts_by_version) | ||
|
|
@@ -110,7 +143,7 @@ def _disaggregate_stats(self, submissions, fields, versions, lang, split_by_fiel | |
| # field_name2...}, | ||
| # ...} | ||
| # | ||
| metrics = {f.name: defaultdict(Counter) for f in fields} | ||
| metrics = {f.contextual_name: defaultdict(Counter) for f in fields} | ||
|
|
||
| for sbmssn in submissions: | ||
|
|
||
|
|
@@ -142,7 +175,7 @@ def _disaggregate_stats(self, submissions, fields, versions, lang, split_by_fiel | |
| else: | ||
| values = (None,) | ||
|
|
||
| value_metrics = metrics[field.name] | ||
| value_metrics = metrics[field.contextual_name] | ||
|
|
||
| for value in values: | ||
| counters = value_metrics[value] | ||
|
|
@@ -177,7 +210,7 @@ def _disaggregate_stats(self, submissions, fields, versions, lang, split_by_fiel | |
|
|
||
| def stats_generator(): | ||
| for field in fields: | ||
| stats = field.get_disaggregated_stats(metrics[field.name], lang=lang, | ||
| stats = field.get_disaggregated_stats(metrics[field.contextual_name], lang=lang, | ||
| top_splitters=top_splitters) | ||
| yield (field, field.get_labels(lang)[0], stats) | ||
|
|
||
|
|
@@ -194,11 +227,11 @@ def get_stats(self, submissions, fields=(), lang=UNSPECIFIED_TRANSLATION, split_ | |
| fields = all_fields | ||
| else: | ||
| fields.add(split_by) | ||
| fields = [field for field in all_fields if field.name in fields] | ||
| fields = [field for field in all_fields if field.contextual_name in fields] | ||
|
|
||
| if split_by: | ||
| try: | ||
| split_by_field = next(f for f in fields if f.name == split_by) | ||
| split_by_field = next(f for f in fields if f.contextual_name == split_by) | ||
| except StopIteration: | ||
| raise ValueError('No field matching name "%s" ' | ||
| 'for split_by' % split_by) | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -23,15 +23,26 @@ class FormDataDef(object): | |
|
|
||
| def __init__(self, name, labels=None, has_stats=False, *args, **kwargs): | ||
| self.name = name | ||
| self.unique_name = name | ||
| self.use_unique_name = False | ||
| self.labels = labels or {} | ||
| self.value_names = self.get_value_names() | ||
| self.has_stats = has_stats | ||
|
|
||
| def __repr__(self): | ||
| return "<%s name='%s'>" % (self.__class__.__name__, self.name) | ||
| return "<%s name='%s'>" % (self.__class__.__name__, self.contextual_name) | ||
|
||
|
|
||
| @property | ||
| def contextual_name(self): | ||
| if self.use_unique_name: | ||
| return self.unique_name | ||
| return self.name | ||
|
|
||
| @property | ||
| def value_names(self): | ||
jnm marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| return self.get_value_names() | ||
|
|
||
| def get_value_names(self): | ||
| return [self.name] | ||
| return [self.contextual_name] | ||
|
|
||
| @classmethod | ||
| def from_json_definition(cls, definition, translations=None): | ||
|
|
@@ -48,6 +59,9 @@ def _extract_json_labels(cls, definition, translations): | |
| labels = {} | ||
| return labels | ||
|
|
||
| def create_unique_name(self, suffix): | ||
| pass | ||
|
|
||
|
|
||
| class FormGroup(FormDataDef): # useful to get __repr__ | ||
| pass | ||
|
|
@@ -79,7 +93,7 @@ def from_json_definition(cls, definition, hierarchy=(None,), parent=None, | |
| return cls(definition['name'], labels, hierarchy=hierarchy, parent=parent) | ||
|
|
||
| def get_label(self, lang=UNSPECIFIED_TRANSLATION): | ||
| return [self.labels.get(lang) or self.name] | ||
| return [self.labels.get(lang) or self.contextual_name] | ||
|
|
||
| def __repr__(self): | ||
| parent_name = getattr(self.parent, 'name', None) | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.