docassemble.InterviewStats 0.5.0__tar.gz → 0.6.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- docassemble_interviewstats-0.6.0/MANIFEST.in +18 -0
- {docassemble.InterviewStats-0.5.0/docassemble.InterviewStats.egg-info → docassemble_interviewstats-0.6.0}/PKG-INFO +13 -18
- {docassemble.InterviewStats-0.5.0 → docassemble_interviewstats-0.6.0}/README.md +3 -11
- docassemble_interviewstats-0.6.0/docassemble/InterviewStats/__init__.py +1 -0
- {docassemble.InterviewStats-0.5.0 → docassemble_interviewstats-0.6.0}/docassemble/InterviewStats/data/questions/data_report.yml +17 -10
- docassemble_interviewstats-0.6.0/docassemble/InterviewStats/data/questions/stats.yml +373 -0
- {docassemble.InterviewStats-0.5.0 → docassemble_interviewstats-0.6.0}/docassemble/InterviewStats/data/questions/subscribe.yml +3 -2
- {docassemble.InterviewStats-0.5.0 → docassemble_interviewstats-0.6.0}/docassemble/InterviewStats/data/questions/test_create_snapshot_data.yml +11 -3
- {docassemble.InterviewStats-0.5.0 → docassemble_interviewstats-0.6.0}/docassemble/InterviewStats/data/questions/test_survey_data_generator.yml +4 -0
- docassemble_interviewstats-0.6.0/docassemble/InterviewStats/data/static/sortable.js +56 -0
- docassemble_interviewstats-0.6.0/docassemble/InterviewStats/data/static/summary.css +8 -0
- docassemble_interviewstats-0.6.0/docassemble/InterviewStats/data_report.py +179 -0
- docassemble_interviewstats-0.6.0/docassemble/InterviewStats/generate_test_data.py +52 -0
- docassemble_interviewstats-0.6.0/docassemble/InterviewStats/snapshot_statistics.py +319 -0
- docassemble_interviewstats-0.6.0/docassemble/InterviewStats/test_generate_data.py +9 -0
- {docassemble.InterviewStats-0.5.0 → docassemble_interviewstats-0.6.0/docassemble.InterviewStats.egg-info}/PKG-INFO +13 -18
- {docassemble.InterviewStats-0.5.0 → docassemble_interviewstats-0.6.0}/docassemble.InterviewStats.egg-info/SOURCES.txt +4 -7
- docassemble_interviewstats-0.6.0/docassemble.InterviewStats.egg-info/requires.txt +3 -0
- {docassemble.InterviewStats-0.5.0 → docassemble_interviewstats-0.6.0}/docassemble.InterviewStats.egg-info/top_level.txt +1 -0
- docassemble_interviewstats-0.6.0/pyproject.toml +45 -0
- docassemble_interviewstats-0.6.0/setup.cfg +4 -0
- docassemble.InterviewStats-0.5.0/MANIFEST.in +0 -1
- docassemble.InterviewStats-0.5.0/docassemble/InterviewStats/__init__.py +0 -1
- docassemble.InterviewStats-0.5.0/docassemble/InterviewStats/cache_geography.py +0 -89
- docassemble.InterviewStats-0.5.0/docassemble/InterviewStats/data/questions/stats.yml +0 -318
- docassemble.InterviewStats-0.5.0/docassemble/InterviewStats/data_report.py +0 -140
- docassemble.InterviewStats-0.5.0/docassemble/InterviewStats/generate_test_data.py +0 -26
- docassemble.InterviewStats-0.5.0/docassemble/InterviewStats/snapshot_geography.py +0 -239
- docassemble.InterviewStats-0.5.0/docassemble/InterviewStats/snapshot_statistics.py +0 -87
- docassemble.InterviewStats-0.5.0/docassemble/__init__.py +0 -5
- docassemble.InterviewStats-0.5.0/docassemble.InterviewStats.egg-info/namespace_packages.txt +0 -1
- docassemble.InterviewStats-0.5.0/docassemble.InterviewStats.egg-info/not-zip-safe +0 -1
- docassemble.InterviewStats-0.5.0/docassemble.InterviewStats.egg-info/requires.txt +0 -6
- docassemble.InterviewStats-0.5.0/setup.cfg +0 -7
- docassemble.InterviewStats-0.5.0/setup.py +0 -60
- {docassemble.InterviewStats-0.5.0 → docassemble_interviewstats-0.6.0}/LICENSE +0 -0
- {docassemble.InterviewStats-0.5.0 → docassemble_interviewstats-0.6.0}/docassemble/InterviewStats/data/sources/README.md +0 -0
- {docassemble.InterviewStats-0.5.0 → docassemble_interviewstats-0.6.0}/docassemble/InterviewStats/data/static/README.md +0 -0
- {docassemble.InterviewStats-0.5.0 → docassemble_interviewstats-0.6.0}/docassemble/InterviewStats/data/static/styles.css +0 -0
- {docassemble.InterviewStats-0.5.0 → docassemble_interviewstats-0.6.0}/docassemble/InterviewStats/data/templates/README.md +0 -0
- {docassemble.InterviewStats-0.5.0 → docassemble_interviewstats-0.6.0}/docassemble.InterviewStats.egg-info/dependency_links.txt +0 -0
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
include README.md
|
|
2
|
+
graft docassemble/InterviewStats/data
|
|
3
|
+
recursive-exclude * *.egg-info
|
|
4
|
+
recursive-exclude .git *
|
|
5
|
+
recursive-exclude venv *
|
|
6
|
+
recursive-exclude .github *
|
|
7
|
+
recursive-exclude .pytest_cache *
|
|
8
|
+
recursive-exclude .vscode *
|
|
9
|
+
recursive-exclude build *
|
|
10
|
+
recursive-exclude dist *
|
|
11
|
+
recursive-exclude * __pycache__
|
|
12
|
+
recursive-exclude * *.pyc
|
|
13
|
+
recursive-exclude * *.pyo
|
|
14
|
+
recursive-exclude * *.orig
|
|
15
|
+
recursive-exclude * *~
|
|
16
|
+
recursive-exclude * *.bak
|
|
17
|
+
recursive-exclude * *.swp
|
|
18
|
+
|
|
@@ -1,16 +1,21 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: docassemble.InterviewStats
|
|
3
|
-
Version: 0.
|
|
4
|
-
Summary: A docassemble
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
License: The MIT License (MIT)
|
|
3
|
+
Version: 0.6.0
|
|
4
|
+
Summary: A docassemble interview to view statistics from other saved interview responses.
|
|
5
|
+
Author-email: Suffolk Legal Innovation and Technology Lab <litlab@suffolk.edu>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: GitHub, https://github.com/SuffolkLITLab/docassemble-InterviewStats
|
|
9
8
|
Description-Content-Type: text/markdown
|
|
10
9
|
License-File: LICENSE
|
|
10
|
+
Requires-Dist: numpy>=1.0.4
|
|
11
|
+
Requires-Dist: pandas>=1.4.2
|
|
12
|
+
Requires-Dist: requests>=2.27.1
|
|
13
|
+
Dynamic: license-file
|
|
11
14
|
|
|
12
15
|
# docassemble.InterviewStats
|
|
13
16
|
|
|
17
|
+
[](https://badge.fury.io/py/docassemble.InterviewStats)
|
|
18
|
+
|
|
14
19
|
A docassemble interview that lets you view statistics from other saved interview responses.
|
|
15
20
|
|
|
16
21
|
## Getting Started
|
|
@@ -51,20 +56,10 @@ visiting this url:
|
|
|
51
56
|
They will be directed immediately to download an XlSX file containing the
|
|
52
57
|
statistics.
|
|
53
58
|
|
|
54
|
-
##
|
|
55
|
-
|
|
56
|
-

|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
## Roadmap
|
|
60
|
-
|
|
61
|
-
Currently, we can show simple grouping over all the data points in the interview stats. However, the
|
|
62
|
-
special visualization are only shown on based on the `zip` attribute. We're working to expand the
|
|
63
|
-
features available here in conjunction with our [EFiling Integration](https://github.com/SuffolkLITLab/EfileProxyServer).
|
|
59
|
+
## Contributing
|
|
64
60
|
|
|
65
61
|
If you have specific feature requests, feel free to open an issue or make a PR!
|
|
66
62
|
|
|
67
|
-
|
|
68
63
|
## Authors
|
|
69
64
|
|
|
70
65
|
* Quinten Steenhuis
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# docassemble.InterviewStats
|
|
2
2
|
|
|
3
|
+
[](https://badge.fury.io/py/docassemble.InterviewStats)
|
|
4
|
+
|
|
3
5
|
A docassemble interview that lets you view statistics from other saved interview responses.
|
|
4
6
|
|
|
5
7
|
## Getting Started
|
|
@@ -40,20 +42,10 @@ visiting this url:
|
|
|
40
42
|
They will be directed immediately to download an XlSX file containing the
|
|
41
43
|
statistics.
|
|
42
44
|
|
|
43
|
-
##
|
|
44
|
-
|
|
45
|
-

|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
## Roadmap
|
|
49
|
-
|
|
50
|
-
Currently, we can show simple grouping over all the data points in the interview stats. However, the
|
|
51
|
-
special visualization are only shown on based on the `zip` attribute. We're working to expand the
|
|
52
|
-
features available here in conjunction with our [EFiling Integration](https://github.com/SuffolkLITLab/EfileProxyServer).
|
|
45
|
+
## Contributing
|
|
53
46
|
|
|
54
47
|
If you have specific feature requests, feel free to open an issue or make a PR!
|
|
55
48
|
|
|
56
|
-
|
|
57
49
|
## Authors
|
|
58
50
|
|
|
59
51
|
* Quinten Steenhuis
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.6.0"
|
|
@@ -37,6 +37,7 @@ code: |
|
|
|
37
37
|
# Question screens
|
|
38
38
|
#--------------------------------------------------------------------------
|
|
39
39
|
---
|
|
40
|
+
id: data-reporting
|
|
40
41
|
question: |
|
|
41
42
|
Data Reporting
|
|
42
43
|
subquestion: |
|
|
@@ -55,6 +56,7 @@ fields:
|
|
|
55
56
|
code: |
|
|
56
57
|
[{interview.get('filename'): interview.get('title')} for interview in interview_menu()] or get_filenames()
|
|
57
58
|
---
|
|
59
|
+
id: quick-summary-screen
|
|
58
60
|
question: |
|
|
59
61
|
Quick Summary for ${ interview_title }
|
|
60
62
|
subquestion: |
|
|
@@ -65,10 +67,8 @@ subquestion: |
|
|
|
65
67
|
% if summary_stats[0] > 10000:
|
|
66
68
|
You have a lot of sessions; some operations might take a long time.
|
|
67
69
|
% endif
|
|
68
|
-
---
|
|
69
|
-
Do you want to filter your data records by date?
|
|
70
70
|
fields:
|
|
71
|
-
-
|
|
71
|
+
- Do you want to filter your data records by date?: filter_date
|
|
72
72
|
datatype: yesnoradio
|
|
73
73
|
default: False
|
|
74
74
|
required: False
|
|
@@ -117,15 +117,17 @@ subquestion: |
|
|
|
117
117
|
The following fields are from single-valued questions, except those whose answer lack statistic value, such as user-typed answer or numeric input.
|
|
118
118
|
|
|
119
119
|
Select a field to see its table:<br>
|
|
120
|
-
% for
|
|
121
|
-
[${
|
|
120
|
+
% for col_single in col_headings_single:
|
|
121
|
+
[${col_single}](${ url_action('show_filtered_data_single', filter_by = col_single) })
|
|
122
122
|
% endfor
|
|
123
123
|
% endif
|
|
124
124
|
|
|
125
125
|
#### All questions (excluding text/area/number fields)
|
|
126
|
-
|
|
126
|
+
|
|
127
|
+
Click this link to see [all the question tables on one screen](${ url_action('display_filtered_data_all') }).
|
|
127
128
|
|
|
128
129
|
#### Download an excel file:
|
|
130
|
+
|
|
129
131
|
To extract complete data including text/area/numeric fields, make an excel file:<br>
|
|
130
132
|
[:file-excel: Make Excel Download](${ url_ask('generate_excel') })
|
|
131
133
|
---
|
|
@@ -201,6 +203,7 @@ code: |
|
|
|
201
203
|
no_reportable_data
|
|
202
204
|
---
|
|
203
205
|
event: no_interview_stats
|
|
206
|
+
id: interview-has-no-data
|
|
204
207
|
question: Interview has no data!
|
|
205
208
|
subquestion: |
|
|
206
209
|
The interview you selected is <br>
|
|
@@ -211,6 +214,7 @@ buttons:
|
|
|
211
214
|
- Restart: restart
|
|
212
215
|
---
|
|
213
216
|
event: no_reportable_data
|
|
217
|
+
id: interview-has-no-reportable-data
|
|
214
218
|
question: Interview has no reportable data, you should use Stats
|
|
215
219
|
subquestion: |
|
|
216
220
|
The interview you selected is <br>
|
|
@@ -244,6 +248,7 @@ code: |
|
|
|
244
248
|
no_filtered_stats
|
|
245
249
|
---
|
|
246
250
|
event: no_filtered_stats
|
|
251
|
+
id: no-records-in-this-period
|
|
247
252
|
question: No records in this period
|
|
248
253
|
subquestion: |
|
|
249
254
|
% if date_op == 'between':
|
|
@@ -351,14 +356,15 @@ code: |
|
|
|
351
356
|
xlsx_output.initialize(filename=proper_name + ".xlsx")
|
|
352
357
|
writer = pd.ExcelWriter(xlsx_output.path(),
|
|
353
358
|
engine='xlsxwriter',
|
|
354
|
-
|
|
359
|
+
engine_kwargs={'options': {'remove_timezone': True}})
|
|
355
360
|
excel_formatted_data.to_excel(writer, index=False)
|
|
356
|
-
writer.
|
|
361
|
+
writer.close()
|
|
357
362
|
xlsx_output.commit()
|
|
358
363
|
del writer
|
|
359
364
|
show_excel_download
|
|
360
365
|
---
|
|
361
366
|
event: show_excel_download
|
|
367
|
+
id: show-excel-download
|
|
362
368
|
question: |
|
|
363
369
|
Excel Download
|
|
364
370
|
subquestion: |
|
|
@@ -374,9 +380,9 @@ code: |
|
|
|
374
380
|
xlsx_output.initialize(filename=proper_name + ".xlsx")
|
|
375
381
|
writer = pd.ExcelWriter(xlsx_output.path(),
|
|
376
382
|
engine='xlsxwriter',
|
|
377
|
-
|
|
383
|
+
engine_kwargs={'options': {'remove_timezone': True}})
|
|
378
384
|
bg_data.to_excel(writer, index=False)
|
|
379
|
-
writer.
|
|
385
|
+
writer.close()
|
|
380
386
|
xlsx_output.commit()
|
|
381
387
|
del writer
|
|
382
388
|
background_response_action('excel_resp', the_excel=xlsx_output)
|
|
@@ -390,6 +396,7 @@ subquestion: |
|
|
|
390
396
|
reload: True
|
|
391
397
|
---
|
|
392
398
|
event: show_failed_screen
|
|
399
|
+
id: failed-screen
|
|
393
400
|
question: |
|
|
394
401
|
Sorry, something went wrong
|
|
395
402
|
subquestion: |
|
|
@@ -0,0 +1,373 @@
|
|
|
1
|
+
---
|
|
2
|
+
metadata:
|
|
3
|
+
title: All Interview Statistics
|
|
4
|
+
short title: View Stats
|
|
5
|
+
temporary session: True
|
|
6
|
+
required privileges:
|
|
7
|
+
- admin
|
|
8
|
+
- developer
|
|
9
|
+
- stats_viewer
|
|
10
|
+
---
|
|
11
|
+
features:
|
|
12
|
+
javascript:
|
|
13
|
+
- sortable.js
|
|
14
|
+
css:
|
|
15
|
+
- summary.css
|
|
16
|
+
question help button: True
|
|
17
|
+
question back button: True
|
|
18
|
+
---
|
|
19
|
+
default screen parts:
|
|
20
|
+
footer: |
|
|
21
|
+
[:sync-alt: Restart](${ url_of('restart') } )
|
|
22
|
+
---
|
|
23
|
+
modules:
|
|
24
|
+
- .snapshot_statistics
|
|
25
|
+
---
|
|
26
|
+
objects:
|
|
27
|
+
- xlsx_output: DAFile.using(filename="stats.xlsx")
|
|
28
|
+
---
|
|
29
|
+
#mandatory: True
|
|
30
|
+
code: |
|
|
31
|
+
message(get_filenames())
|
|
32
|
+
---
|
|
33
|
+
id: interview order
|
|
34
|
+
mandatory: True
|
|
35
|
+
code: |
|
|
36
|
+
# intro
|
|
37
|
+
filename
|
|
38
|
+
simple_data
|
|
39
|
+
formatted_data
|
|
40
|
+
display_grouped_data
|
|
41
|
+
# viewed_filtered
|
|
42
|
+
---
|
|
43
|
+
event: generate_excel
|
|
44
|
+
code: |
|
|
45
|
+
if stats_size > 10000:
|
|
46
|
+
if make_excel_task.ready():
|
|
47
|
+
if make_excel_task.failed():
|
|
48
|
+
show_failed_screen
|
|
49
|
+
show_excel_download
|
|
50
|
+
else:
|
|
51
|
+
waiting_screen
|
|
52
|
+
else:
|
|
53
|
+
proper_name = space_to_underscore(filename) + "_stats"
|
|
54
|
+
xlsx_output.initialize(filename=proper_name + ".xlsx")
|
|
55
|
+
writer = pd.ExcelWriter(xlsx_output.path(),
|
|
56
|
+
engine='xlsxwriter',
|
|
57
|
+
engine_kwargs={'options': {'remove_timezone': True}})
|
|
58
|
+
formatted_data.to_excel(writer, index=False)
|
|
59
|
+
writer.close()
|
|
60
|
+
xlsx_output.commit()
|
|
61
|
+
del writer
|
|
62
|
+
show_excel_download
|
|
63
|
+
generate_excel = True
|
|
64
|
+
---
|
|
65
|
+
event: show_excel_download
|
|
66
|
+
id: show-excel-download
|
|
67
|
+
question: |
|
|
68
|
+
Excel Download
|
|
69
|
+
subquestion: |
|
|
70
|
+
[:file-excel: Download](${ xlsx_output.url_for() })
|
|
71
|
+
---
|
|
72
|
+
code: |
|
|
73
|
+
make_excel_task = background_action('excel_task', data=formatted_data)
|
|
74
|
+
---
|
|
75
|
+
event: excel_task
|
|
76
|
+
code: |
|
|
77
|
+
bg_data = action_argument('data')
|
|
78
|
+
proper_name = space_to_underscore(filename) + "_stats"
|
|
79
|
+
xlsx_output.initialize(filename=proper_name + ".xlsx")
|
|
80
|
+
writer = pd.ExcelWriter(xlsx_output.path(),
|
|
81
|
+
engine='xlsxwriter',
|
|
82
|
+
engine_kwargs={'options': {'remove_timezone': True}})
|
|
83
|
+
bg_data.to_excel(writer, index=False)
|
|
84
|
+
writer.close()
|
|
85
|
+
xlsx_output.commit()
|
|
86
|
+
del writer
|
|
87
|
+
background_response_action('excel_resp', the_excel=xlsx_output)
|
|
88
|
+
---
|
|
89
|
+
event: generate_summary_excel
|
|
90
|
+
code: |
|
|
91
|
+
import pandas as pd
|
|
92
|
+
proper_name = 'snapshot_summary'
|
|
93
|
+
xlsx_output.initialize(filename=proper_name + ".xlsx")
|
|
94
|
+
df = pd.DataFrame(summary_by_filename)
|
|
95
|
+
writer = pd.ExcelWriter(xlsx_output.path(),
|
|
96
|
+
engine='xlsxwriter',
|
|
97
|
+
engine_kwargs={'options': {'remove_timezone': True}})
|
|
98
|
+
df.to_excel(writer, index=False)
|
|
99
|
+
writer.close()
|
|
100
|
+
xlsx_output.commit()
|
|
101
|
+
del writer
|
|
102
|
+
show_excel_download
|
|
103
|
+
---
|
|
104
|
+
event: generate_sessions_excel
|
|
105
|
+
code: |
|
|
106
|
+
import pandas as pd
|
|
107
|
+
proper_name = 'sessions_summary'
|
|
108
|
+
xlsx_output.initialize(filename=proper_name + ".xlsx")
|
|
109
|
+
df = pd.DataFrame(session_summary_by_filename)
|
|
110
|
+
writer = pd.ExcelWriter(xlsx_output.path(),
|
|
111
|
+
engine='xlsxwriter',
|
|
112
|
+
engine_kwargs={'options': {'remove_timezone': True}})
|
|
113
|
+
df.to_excel(writer, index=False)
|
|
114
|
+
writer.close()
|
|
115
|
+
xlsx_output.commit()
|
|
116
|
+
del writer
|
|
117
|
+
show_excel_download
|
|
118
|
+
---
|
|
119
|
+
event: waiting_screen
|
|
120
|
+
id: waiting screen
|
|
121
|
+
question: |
|
|
122
|
+
We're generating your excel sheet.
|
|
123
|
+
subquestion: |
|
|
124
|
+
This may take a few minutes, so leave this tab open. Thanks for waiting.
|
|
125
|
+
reload: True
|
|
126
|
+
---
|
|
127
|
+
event: show_failed_screen
|
|
128
|
+
id: failed-screen
|
|
129
|
+
question: |
|
|
130
|
+
Sorry, something went wrong
|
|
131
|
+
subquestion: |
|
|
132
|
+
We had some trouble trying to assemble your excel sheet.
|
|
133
|
+
|
|
134
|
+
${ make_excel_task.result() }
|
|
135
|
+
buttons:
|
|
136
|
+
- Restart: restart
|
|
137
|
+
---
|
|
138
|
+
event: excel_resp
|
|
139
|
+
code: |
|
|
140
|
+
xlsx_output = action_argument('the_excel')
|
|
141
|
+
background_response()
|
|
142
|
+
---
|
|
143
|
+
code: |
|
|
144
|
+
summary_stats = get_summary_stats(filename, date_from=stats_date_from if filter_by_date else None, date_to=stats_date_to if filter_by_date else None)
|
|
145
|
+
---
|
|
146
|
+
reconsider: summary_stats
|
|
147
|
+
id: summary-data-screen
|
|
148
|
+
question: |
|
|
149
|
+
Summary data for ${ interview_title }
|
|
150
|
+
subquestion: |
|
|
151
|
+
* Total sessions: ${ summary_stats[0] }
|
|
152
|
+
* First session: ${ format_date(summary_stats[1], format='MM/dd/yyyy') }
|
|
153
|
+
* Latest session: ${ format_date(summary_stats[2], format='MM/dd/yyyy') }
|
|
154
|
+
|
|
155
|
+
% if summary_stats[0] > 10000:
|
|
156
|
+
You have a lot of sessions; some operations might
|
|
157
|
+
take a long time.
|
|
158
|
+
% endif
|
|
159
|
+
continue button field: simple_data
|
|
160
|
+
---
|
|
161
|
+
# question: Interview Statistics
|
|
162
|
+
# subquestion: |
|
|
163
|
+
# We have two features in this repo:
|
|
164
|
+
|
|
165
|
+
# 1. Stats - it generates statistics, maps, charts and a downadable excel file.
|
|
166
|
+
# - If you have any inteview that uses `store_variables_snapshot(data={'zip': users[0].address.zip})` to save zip data, you should use Stats.
|
|
167
|
+
# - You can filter your data by date or by state.
|
|
168
|
+
# - Click the **Continue button** below to use this feature.
|
|
169
|
+
# 1. Data Reporting - it generates statistics tables and a downloadable excel file.
|
|
170
|
+
# - If you have any interview that uses `save_input_data()` to save reportable data, you should use Data Reporting.
|
|
171
|
+
# - You can filter your data by date.
|
|
172
|
+
# - [Click here to use Data Reporting](${interview_url(i=user_info().package + ":data_report.yml")})
|
|
173
|
+
|
|
174
|
+
# field: intro
|
|
175
|
+
---
|
|
176
|
+
code: |
|
|
177
|
+
overall_stats = get_overall_stats()
|
|
178
|
+
summary_by_filename = get_summary_stats_by_filename()
|
|
179
|
+
session_summary_by_filename = get_session_summary_stats_by_filename()
|
|
180
|
+
---
|
|
181
|
+
id: what-interview-do-you-want-to-view-statistics-for
|
|
182
|
+
question: |
|
|
183
|
+
What interview do you want to view statistics for?
|
|
184
|
+
under: |
|
|
185
|
+
#### Summary statistics
|
|
186
|
+
|
|
187
|
+
Keep in mind:
|
|
188
|
+
|
|
189
|
+
% if isinstance(get_config("interview delete days"), int):
|
|
190
|
+
Docassemble sessions on this server are kept for only ${ get_config("interview delete days") } days.
|
|
191
|
+
% else:
|
|
192
|
+
Docassemble sessions typically are kept for only 90 days.
|
|
193
|
+
% endif
|
|
194
|
+
Snapshotted sessions
|
|
195
|
+
may or may not be set to be kept longer. Statistics below will reflect the settings
|
|
196
|
+
on your server and interviews.
|
|
197
|
+
|
|
198
|
+
Total sessions with saved statistics across all interviews: ${ overall_stats[0] }
|
|
199
|
+
|
|
200
|
+
% if summary_by_filename:
|
|
201
|
+
<div style="margin-bottom:0.5rem">
|
|
202
|
+
<a href="${ url_ask('generate_summary_excel') }" class="btn btn-link">
|
|
203
|
+
<i class="fa fa-file-excel" aria-hidden="true"></i>
|
|
204
|
+
Download snapshot summary
|
|
205
|
+
</a>
|
|
206
|
+
</div>
|
|
207
|
+
<h2 class="h4">Snapshotted sessions by interview title</h2>
|
|
208
|
+
<table id="summary-table" class="table table-striped table-bordered sortable">
|
|
209
|
+
<thead>
|
|
210
|
+
<tr>
|
|
211
|
+
<th>Interview (click to sort)</th>
|
|
212
|
+
<th data-type="number">30 days</th>
|
|
213
|
+
<th data-type="number">90 days</th>
|
|
214
|
+
<th data-type="number">365 days</th>
|
|
215
|
+
<th data-type="number">All time</th>
|
|
216
|
+
<th data-type="date">Last (30d)</th>
|
|
217
|
+
<th data-type="date">Latest</th>
|
|
218
|
+
</tr>
|
|
219
|
+
</thead>
|
|
220
|
+
<tbody>
|
|
221
|
+
% for row in summary_by_filename:
|
|
222
|
+
<tr>
|
|
223
|
+
<td title="${ row['filename'] }"><span class="short-name">${ shorten_filename(row['filename']) }</span></td>
|
|
224
|
+
<td>${ row.get('count_30d', 0) }</td>
|
|
225
|
+
<td>${ row.get('count_90d', 0) }</td>
|
|
226
|
+
<td>${ row.get('count_365d', 0) }</td>
|
|
227
|
+
<td>${ row.get('count_all', 0) }</td>
|
|
228
|
+
<td>${ format_date(row.get('max_30d'), format='MM/dd/yyyy') if row.get('max_30d') else '' }</td>
|
|
229
|
+
<td>${ format_date(row.get('max_all'), format='MM/dd/yyyy') if row.get('max_all') else '' }</td>
|
|
230
|
+
</tr>
|
|
231
|
+
% endfor
|
|
232
|
+
</tbody>
|
|
233
|
+
</table>
|
|
234
|
+
% endif
|
|
235
|
+
|
|
236
|
+
<p><a href="${ url_ask('session_data_screen') }">View all sessions by interview title</a></p>
|
|
237
|
+
fields:
|
|
238
|
+
- Filename: filename
|
|
239
|
+
datatype: combobox
|
|
240
|
+
code: |
|
|
241
|
+
get_combined_filename_list()
|
|
242
|
+
- Filter by date range?: filter_by_date
|
|
243
|
+
datatype: yesno
|
|
244
|
+
default: False
|
|
245
|
+
- From date: stats_date_from
|
|
246
|
+
datatype: date
|
|
247
|
+
show if: filter_by_date
|
|
248
|
+
- To date: stats_date_to
|
|
249
|
+
datatype: date
|
|
250
|
+
show if: filter_by_date
|
|
251
|
+
---
|
|
252
|
+
code: |
|
|
253
|
+
session_summary_by_filename = get_session_summary_stats_by_filename()
|
|
254
|
+
---
|
|
255
|
+
event: session_data_screen
|
|
256
|
+
id: session-data-screen
|
|
257
|
+
question: |
|
|
258
|
+
All sessions by interview title
|
|
259
|
+
subquestion: |
|
|
260
|
+
% if session_summary_by_filename:
|
|
261
|
+
<div style="margin-bottom:0.5rem">
|
|
262
|
+
<a href="${ url_ask('generate_sessions_excel') }" class="btn btn-link">
|
|
263
|
+
<i class="fa fa-file-excel" aria-hidden="true"></i>
|
|
264
|
+
Download session summary
|
|
265
|
+
</a>
|
|
266
|
+
</div>
|
|
267
|
+
<table id="sessions-table" class="table table-striped table-bordered sortable">
|
|
268
|
+
<thead>
|
|
269
|
+
<tr>
|
|
270
|
+
<th>Interview</th>
|
|
271
|
+
<th data-type="number">30 days</th>
|
|
272
|
+
<th data-type="number">90 days</th>
|
|
273
|
+
<th data-type="number">365 days</th>
|
|
274
|
+
<th data-type="number">All time</th>
|
|
275
|
+
<th data-type="date">Latest</th>
|
|
276
|
+
</tr>
|
|
277
|
+
</thead>
|
|
278
|
+
<tbody>
|
|
279
|
+
% for row in session_summary_by_filename:
|
|
280
|
+
<tr>
|
|
281
|
+
<td title="${ row['filename'] }"><span class="short-name">${ shorten_filename(row['filename']) }</span></td>
|
|
282
|
+
<td>${ row.get('count_30d', 0) }</td>
|
|
283
|
+
<td>${ row.get('count_90d', 0) }</td>
|
|
284
|
+
<td>${ row.get('count_365d', 0) }</td>
|
|
285
|
+
<td>${ row.get('count_all', 0) }</td>
|
|
286
|
+
<td>${ format_date(row.get('max_all'), format='MM/dd/yyyy') if row.get('max_all') else '' }</td>
|
|
287
|
+
</tr>
|
|
288
|
+
% endfor
|
|
289
|
+
</tbody>
|
|
290
|
+
</table>
|
|
291
|
+
% else:
|
|
292
|
+
No session data found.
|
|
293
|
+
% endif
|
|
294
|
+
|
|
295
|
+
[Back to summary](${ url_ask('filename') })
|
|
296
|
+
---
|
|
297
|
+
code: |
|
|
298
|
+
if filename is not None:
|
|
299
|
+
if 'data/questions' in filename or 'playground' in filename:
|
|
300
|
+
stats = get_stats(filename, date_from=stats_date_from if filter_by_date else None, date_to=stats_date_to if filter_by_date else None)
|
|
301
|
+
else:
|
|
302
|
+
stats = get_stats(filename.replace(':',':data/questions/'), date_from=stats_date_from if filter_by_date else None, date_to=stats_date_to if filter_by_date else None)
|
|
303
|
+
else:
|
|
304
|
+
no_interview_stats
|
|
305
|
+
---
|
|
306
|
+
code: |
|
|
307
|
+
try:
|
|
308
|
+
interview_title = next(iter([interview.get('title') for interview in interview_menu() if interview.get('filename') == filename]), filename)
|
|
309
|
+
except:
|
|
310
|
+
interview_title = filename
|
|
311
|
+
# A survey interview's title is saved in Stats, yet interview_title is used on Summary page which comes before we kick out survey interviews, so we set the value here.
|
|
312
|
+
if stats and 'title' in stats[0].keys():
|
|
313
|
+
interview_title = stats[0]['title']
|
|
314
|
+
---
|
|
315
|
+
id: there-are-no-interviews-to-look-for-data-in
|
|
316
|
+
event: no_interview_stats
|
|
317
|
+
question: There are no interviews to look for data in!
|
|
318
|
+
subquestion: |
|
|
319
|
+
There aren't any interviews that we can look for data in. There should be at least one interview
|
|
320
|
+
listed in the 'dispatch' section of the configuration. See
|
|
321
|
+
[the docassemble documentation](https://docassemble.org/docs/config.html#dispatch) for more information
|
|
322
|
+
buttons:
|
|
323
|
+
- Exit: exit
|
|
324
|
+
---
|
|
325
|
+
need: stats
|
|
326
|
+
code: |
|
|
327
|
+
columns = get_columns(stats)
|
|
328
|
+
my_col = next(iter(columns), None)
|
|
329
|
+
groupable_columns = list(set(columns).difference(set(['modtime'])))
|
|
330
|
+
possible_states = get_column_values(stats, 'state')
|
|
331
|
+
---
|
|
332
|
+
code: |
|
|
333
|
+
import pandas as pd
|
|
334
|
+
---
|
|
335
|
+
code: |
|
|
336
|
+
formatted_data = pd.DataFrame()
|
|
337
|
+
stats_size = 0
|
|
338
|
+
if stats is not None:
|
|
339
|
+
formatted_data = pd.DataFrame(stats)
|
|
340
|
+
stats_size = formatted_data.shape[0]
|
|
341
|
+
---
|
|
342
|
+
event: show_grouped_data
|
|
343
|
+
code: |
|
|
344
|
+
my_col = action_argument('group_by')
|
|
345
|
+
force_ask('display_grouped_data')
|
|
346
|
+
---
|
|
347
|
+
id: stats-for
|
|
348
|
+
question: |
|
|
349
|
+
Stats for ${interview_title}
|
|
350
|
+
subquestion: |
|
|
351
|
+
Total submissions: ${ len(stats) }
|
|
352
|
+
|
|
353
|
+
% if len(stats):
|
|
354
|
+
Group by:
|
|
355
|
+
% for col in groupable_columns:
|
|
356
|
+
[${col}](${ url_action('show_grouped_data', group_by = col) })
|
|
357
|
+
% endfor
|
|
358
|
+
|
|
359
|
+
<%
|
|
360
|
+
grouped = formatted_data.groupby(by=my_col).count()
|
|
361
|
+
grouped = grouped.sort_values(grouped.columns[0], ascending=False)
|
|
362
|
+
total_groups = len(grouped)
|
|
363
|
+
%>
|
|
364
|
+
% if total_groups > 50:
|
|
365
|
+
Showing the top 50 of ${ total_groups } groups by count. Download the Excel file below for the full breakdown.
|
|
366
|
+
% endif
|
|
367
|
+
${ grouped.head(50).to_html(classes=('table','table-striped')) }
|
|
368
|
+
|
|
369
|
+
[:file-excel: Download](${ url_ask('generate_excel') })
|
|
370
|
+
% else:
|
|
371
|
+
No records found for this date range. Try a wider range or clear the date filter.
|
|
372
|
+
% endif
|
|
373
|
+
event: display_grouped_data
|
|
@@ -72,6 +72,7 @@ code: |
|
|
|
72
72
|
formatted_data = pd.DataFrame(stats)
|
|
73
73
|
---
|
|
74
74
|
event: no_interview_stats
|
|
75
|
+
id: there-are-no-interviews-to-look-for-data-in
|
|
75
76
|
question: There are no interviews to look for data in!
|
|
76
77
|
subquestion: |
|
|
77
78
|
There aren't any interviews that we can look for data in. There should be at least one interview
|
|
@@ -85,9 +86,9 @@ code: |
|
|
|
85
86
|
xlsx_output.initialize(filename=proper_name + ".xlsx")
|
|
86
87
|
writer = pd.ExcelWriter(xlsx_output.path(),
|
|
87
88
|
engine='xlsxwriter',
|
|
88
|
-
|
|
89
|
+
engine_kwargs={'options': {'remove_timezone': True}})
|
|
89
90
|
formatted_data.to_excel(writer, index=False)
|
|
90
|
-
writer.
|
|
91
|
+
writer.close()
|
|
91
92
|
xlsx_output.commit()
|
|
92
93
|
del writer
|
|
93
94
|
saved_data = True
|
|
@@ -20,12 +20,16 @@ code: |
|
|
|
20
20
|
if what_to_do == 'generate':
|
|
21
21
|
number_of_data_points
|
|
22
22
|
# Edit your data here
|
|
23
|
-
ma_zips = ['
|
|
24
|
-
|
|
23
|
+
ma_zips = list(set([f'{random.randint(75001, 79999):05}' for _ in range(300)])) \
|
|
24
|
+
+ list(set([f'{random.randint(1001,2799):05}' for _ in range(300)]))
|
|
25
|
+
import datetime
|
|
26
|
+
datetimes = [datetime.datetime.now(datetime.timezone.utc).replace(tzinfo=None) - datetime.timedelta(days=i) for i in range(30)]
|
|
25
27
|
def my_generator():
|
|
26
28
|
for _ in range(number_of_data_points):
|
|
27
29
|
yield {'zip': random.choice(ma_zips), 'state': 'MA'}
|
|
28
|
-
|
|
30
|
+
def modtime_generator():
|
|
31
|
+
return random.choice(datetimes)
|
|
32
|
+
write_random_user(my_generator(), modtime_generator=modtime_generator)
|
|
29
33
|
elif what_to_do == 'delete':
|
|
30
34
|
which_to_delete
|
|
31
35
|
delete_data_from_interview(which_to_delete)
|
|
@@ -36,6 +40,7 @@ code: |
|
|
|
36
40
|
users[0].name.last = 'Brown'
|
|
37
41
|
users[0].address
|
|
38
42
|
---
|
|
43
|
+
id: snapshot-generator
|
|
39
44
|
question: |
|
|
40
45
|
Snapshot Generator
|
|
41
46
|
subquestion: |
|
|
@@ -49,6 +54,7 @@ fields:
|
|
|
49
54
|
- Generate: generate
|
|
50
55
|
- Delete: delete
|
|
51
56
|
---
|
|
57
|
+
id: how-many-data-points-do-you-want-to-generate
|
|
52
58
|
question: |
|
|
53
59
|
How many data points do you want to generate?
|
|
54
60
|
fields:
|
|
@@ -58,6 +64,7 @@ fields:
|
|
|
58
64
|
min: 1
|
|
59
65
|
max: 400000
|
|
60
66
|
---
|
|
67
|
+
id: what-interview-do-you-want-to-delete-all-statistics-from
|
|
61
68
|
question: |
|
|
62
69
|
What interview do you want to delete all statistics from?
|
|
63
70
|
fields:
|
|
@@ -73,6 +80,7 @@ validation code: |
|
|
|
73
80
|
if which_to_delete_confirm != which_to_delete:
|
|
74
81
|
validation_error("Are you sure you are deleting the right one?")
|
|
75
82
|
---
|
|
83
|
+
id: snapshot-ending-screen
|
|
76
84
|
event: ending_screen
|
|
77
85
|
question: |
|
|
78
86
|
% if what_to_do == 'generate':
|
|
@@ -29,6 +29,7 @@ code: |
|
|
|
29
29
|
# Question screens
|
|
30
30
|
#--------------------------------------------------------------------------
|
|
31
31
|
---
|
|
32
|
+
id: survey data generator
|
|
32
33
|
question: |
|
|
33
34
|
Survey Data Generator
|
|
34
35
|
subquestion: |
|
|
@@ -42,6 +43,7 @@ fields:
|
|
|
42
43
|
- Generate: generate
|
|
43
44
|
- Delete: delete
|
|
44
45
|
---
|
|
46
|
+
id: how-many-data-points-do-you-want-to-generate
|
|
45
47
|
question: |
|
|
46
48
|
How many data points do you want to generate?
|
|
47
49
|
fields:
|
|
@@ -52,6 +54,7 @@ fields:
|
|
|
52
54
|
min: 1
|
|
53
55
|
max: 4000
|
|
54
56
|
---
|
|
57
|
+
id: which-survey-interviews-do-you-want-to-delete?
|
|
55
58
|
question: |
|
|
56
59
|
Which survey interviews do you want to delete?
|
|
57
60
|
fields:
|
|
@@ -67,6 +70,7 @@ validation code: |
|
|
|
67
70
|
if which_to_delete_confirm != which_to_delete:
|
|
68
71
|
validation_error("Are you sure you are deleting the right one?")
|
|
69
72
|
---
|
|
73
|
+
id: ending-screen
|
|
70
74
|
event: ending_screen
|
|
71
75
|
question: |
|
|
72
76
|
% if what_to_do == 'generate':
|