surveyflow 0.1.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.
Files changed (35) hide show
  1. surveyflow-0.1.0/.gitignore +52 -0
  2. surveyflow-0.1.0/PKG-INFO +162 -0
  3. surveyflow-0.1.0/README.md +150 -0
  4. surveyflow-0.1.0/pyproject.toml +20 -0
  5. surveyflow-0.1.0/requirements.txt +2 -0
  6. surveyflow-0.1.0/run_vn8947.py +181 -0
  7. surveyflow-0.1.0/surveyflow/__init__.py +6 -0
  8. surveyflow-0.1.0/surveyflow/core/__init__.py +4 -0
  9. surveyflow-0.1.0/surveyflow/core/base.py +29 -0
  10. surveyflow-0.1.0/surveyflow/core/config.py +29 -0
  11. surveyflow-0.1.0/surveyflow/pipeline.py +0 -0
  12. surveyflow-0.1.0/surveyflow/steps/__init__.py +0 -0
  13. surveyflow-0.1.0/surveyflow/steps/analysis/__init__.py +0 -0
  14. surveyflow-0.1.0/surveyflow/steps/analysis/analysis_step.py +0 -0
  15. surveyflow-0.1.0/surveyflow/steps/analysis/request_parser.py +0 -0
  16. surveyflow-0.1.0/surveyflow/steps/ingestion/__init__.py +3 -0
  17. surveyflow-0.1.0/surveyflow/steps/ingestion/data_parser.py +229 -0
  18. surveyflow-0.1.0/surveyflow/steps/ingestion/ingestion_step.py +96 -0
  19. surveyflow-0.1.0/surveyflow/steps/ingestion/metadata_parser.py +149 -0
  20. surveyflow-0.1.0/surveyflow/steps/quality/__init__.py +0 -0
  21. surveyflow-0.1.0/surveyflow/steps/quality/checker.py +0 -0
  22. surveyflow-0.1.0/surveyflow/steps/quality/quality_step.py +0 -0
  23. surveyflow-0.1.0/surveyflow/steps/table/__init__.py +0 -0
  24. surveyflow-0.1.0/surveyflow/steps/table/banner_builder.py +0 -0
  25. surveyflow-0.1.0/surveyflow/steps/table/table_generator.py +0 -0
  26. surveyflow-0.1.0/surveyflow/steps/table/table_step.py +0 -0
  27. surveyflow-0.1.0/surveyflow/utils/__init__.py +0 -0
  28. surveyflow-0.1.0/surveyflow/utils/io.py +29 -0
  29. surveyflow-0.1.0/surveyflow/utils/validators.py +0 -0
  30. surveyflow-0.1.0/tests/conftest.py +0 -0
  31. surveyflow-0.1.0/tests/test_analysis.py +0 -0
  32. surveyflow-0.1.0/tests/test_ingestion.py +121 -0
  33. surveyflow-0.1.0/tests/test_quality.py +0 -0
  34. surveyflow-0.1.0/tests/test_table.py +0 -0
  35. surveyflow-0.1.0/vn8947_def.json +0 -0
@@ -0,0 +1,52 @@
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *.pyo
5
+ *.pyd
6
+ *.so
7
+ *.egg
8
+ *.egg-info/
9
+ dist/
10
+ build/
11
+ .eggs/
12
+
13
+ # Virtual environments
14
+ .venv/
15
+ venv/
16
+ env/
17
+
18
+ # Hatch / pip
19
+ .hatch/
20
+ *.dist-info/
21
+
22
+ # Testing
23
+ .pytest_cache/
24
+ .coverage
25
+ htmlcov/
26
+ coverage.xml
27
+
28
+ # Output / test data
29
+ output_test/
30
+ output/
31
+ *.csv
32
+ *.xlsx
33
+
34
+ # IDE
35
+ .vscode/
36
+ .idea/
37
+ *.swp
38
+ *.swo
39
+
40
+ # OS
41
+ .DS_Store
42
+ Thumbs.db
43
+
44
+ # Claude Code internals
45
+ .claude/
46
+
47
+ # GitHub workflows (not needed in repo)
48
+ .github/
49
+
50
+ # Secrets / config
51
+ .env
52
+ *.env.local
@@ -0,0 +1,162 @@
1
+ Metadata-Version: 2.4
2
+ Name: surveyflow
3
+ Version: 0.1.0
4
+ Summary: Survey data pipeline: parse survey definitions and responses into rawdata.csv and metadata.json
5
+ Requires-Python: >=3.10
6
+ Requires-Dist: openpyxl>=3.1
7
+ Requires-Dist: pandas>=2.0
8
+ Provides-Extra: dev
9
+ Requires-Dist: pytest-cov; extra == 'dev'
10
+ Requires-Dist: pytest>=7.0; extra == 'dev'
11
+ Description-Content-Type: text/markdown
12
+
13
+ # surveyflow
14
+
15
+ A Python library for processing survey data — parse survey definitions and responses into structured outputs ready for analysis.
16
+
17
+ ## Features
18
+
19
+ - Parse survey **definition** (question structure, types, positions) into `metadata.json`
20
+ - Parse survey **response rows** into `rawdata.csv` with numeric codes
21
+ - Single-choice → integer code (e.g. `1`)
22
+ - Multi-choice / ranking → semicolon-separated codes (e.g. `"1;3;5"`)
23
+ - Open-ended / matrix / number → raw text
24
+ - Filter responses by status (default: `approved` only)
25
+ - Consistent columns between `rawdata.csv` and `metadata.json`
26
+
27
+ ## Installation
28
+
29
+ ```bash
30
+ pip install surveyflow
31
+ ```
32
+
33
+ ## Quick Start
34
+
35
+ ```python
36
+ from surveyflow.steps.ingestion import IngestionStep
37
+
38
+ # definition: dict from your survey platform's definition API
39
+ # rows_pages: list of paginated response pages from your survey platform
40
+
41
+ step = IngestionStep()
42
+ context = step.run({
43
+ "definition": definition,
44
+ "rows_pages": rows_pages,
45
+ "output_dir": "./output",
46
+ })
47
+
48
+ df = context["rawdata"] # pandas DataFrame
49
+ metadata = context["metadata"] # dict with question info + value labels
50
+ ```
51
+
52
+ ## Output
53
+
54
+ ### `rawdata.csv`
55
+
56
+ | task_id | date_time | q6 | q7 | q10 | q18 |
57
+ |---|---|---|---|---|---|
58
+ | task_001 | 2026-03-01 | 1 | 2 | 1 | 1;3 |
59
+ | task_002 | 2026-03-01 | 2 | 1 | 2 | 2 |
60
+
61
+ ### `metadata.json`
62
+
63
+ ```json
64
+ {
65
+ "survey_id": 12345,
66
+ "questions": {
67
+ "q6": {
68
+ "position": 6,
69
+ "english_question": "Please provide your current address",
70
+ "answer_type": "singlechoice",
71
+ "values": { "1": "Ward 1", "2": "Ward 2", "3": "Ward 3" }
72
+ },
73
+ "q18": {
74
+ "position": 18,
75
+ "english_question": "Who do you live with",
76
+ "answer_type": "multiplechoice",
77
+ "values": { "1": "Spouse", "2": "Parents", "3": "Children" }
78
+ }
79
+ }
80
+ }
81
+ ```
82
+
83
+ ## Input Format
84
+
85
+ ### `definition`
86
+
87
+ ```python
88
+ {
89
+ "survey": { "survey_id": 12345, "title": "...", ... },
90
+ "questions": [
91
+ {
92
+ "question_id": 1001,
93
+ "position": 6,
94
+ "question": "...",
95
+ "english_question": "Please provide your current address",
96
+ "type": 2, # 2=singlechoice, 3=multiplechoice, 6=ranking, 4=matrix, ...
97
+ "input_type": 0,
98
+ "mandatory": True,
99
+ "status": 1
100
+ },
101
+ ...
102
+ ]
103
+ }
104
+ ```
105
+
106
+ ### `rows_pages`
107
+
108
+ ```python
109
+ [
110
+ { # page 1
111
+ "rows": [
112
+ {
113
+ "task_id": "task_001",
114
+ "date_time": "2026-03-01 09:00:00",
115
+ "profile_status": "approved",
116
+ "questions": [
117
+ { "type": "singlechoice", "question": "Please provide your current address", "answer": "Ward 1" },
118
+ { "type": "multiplechoice", "question": "Who do you live with",
119
+ "answer": [{"answer_name": "Spouse"}, {"answer_name": "Children"}] },
120
+ ...
121
+ ]
122
+ },
123
+ ...
124
+ ]
125
+ },
126
+ # page 2, page 3, ...
127
+ ]
128
+ ```
129
+
130
+ ## Answer Types
131
+
132
+ | `type` value | `answer_type` | Encoded in rawdata? |
133
+ |---|---|---|
134
+ | 2 | `singlechoice` | Yes → int |
135
+ | 3 | `multiplechoice` | Yes → `"1;3;5"` |
136
+ | 6 | `ranking` | Yes → `"2;1;3"` |
137
+ | 4 | `matrix` | No → `"row:col\|row:col"` |
138
+ | 1 + input_type=100 | `multiplenumber` | No → `"label:num\|label:num"` |
139
+ | 1 | `freetext` | No → raw text |
140
+ | 1 + input_type=3 | `singlenumber` | No → raw number |
141
+ | 1109 | `area` | No → raw text |
142
+
143
+ Excluded from output: `audio`, `user-name`, `user-phone`, `instruction`, `reward`.
144
+
145
+ ## Profile Status Filter
146
+
147
+ ```python
148
+ # Default: approved only
149
+ step.run({ ..., "profile_status": ["approved"] })
150
+
151
+ # Include all statuses
152
+ step.run({ ..., "profile_status": [] })
153
+
154
+ # Custom filter
155
+ step.run({ ..., "profile_status": ["approved", "pending"] })
156
+ ```
157
+
158
+ ## Requirements
159
+
160
+ - Python >= 3.10
161
+ - pandas >= 2.0
162
+ - openpyxl >= 3.1
@@ -0,0 +1,150 @@
1
+ # surveyflow
2
+
3
+ A Python library for processing survey data — parse survey definitions and responses into structured outputs ready for analysis.
4
+
5
+ ## Features
6
+
7
+ - Parse survey **definition** (question structure, types, positions) into `metadata.json`
8
+ - Parse survey **response rows** into `rawdata.csv` with numeric codes
9
+ - Single-choice → integer code (e.g. `1`)
10
+ - Multi-choice / ranking → semicolon-separated codes (e.g. `"1;3;5"`)
11
+ - Open-ended / matrix / number → raw text
12
+ - Filter responses by status (default: `approved` only)
13
+ - Consistent columns between `rawdata.csv` and `metadata.json`
14
+
15
+ ## Installation
16
+
17
+ ```bash
18
+ pip install surveyflow
19
+ ```
20
+
21
+ ## Quick Start
22
+
23
+ ```python
24
+ from surveyflow.steps.ingestion import IngestionStep
25
+
26
+ # definition: dict from your survey platform's definition API
27
+ # rows_pages: list of paginated response pages from your survey platform
28
+
29
+ step = IngestionStep()
30
+ context = step.run({
31
+ "definition": definition,
32
+ "rows_pages": rows_pages,
33
+ "output_dir": "./output",
34
+ })
35
+
36
+ df = context["rawdata"] # pandas DataFrame
37
+ metadata = context["metadata"] # dict with question info + value labels
38
+ ```
39
+
40
+ ## Output
41
+
42
+ ### `rawdata.csv`
43
+
44
+ | task_id | date_time | q6 | q7 | q10 | q18 |
45
+ |---|---|---|---|---|---|
46
+ | task_001 | 2026-03-01 | 1 | 2 | 1 | 1;3 |
47
+ | task_002 | 2026-03-01 | 2 | 1 | 2 | 2 |
48
+
49
+ ### `metadata.json`
50
+
51
+ ```json
52
+ {
53
+ "survey_id": 12345,
54
+ "questions": {
55
+ "q6": {
56
+ "position": 6,
57
+ "english_question": "Please provide your current address",
58
+ "answer_type": "singlechoice",
59
+ "values": { "1": "Ward 1", "2": "Ward 2", "3": "Ward 3" }
60
+ },
61
+ "q18": {
62
+ "position": 18,
63
+ "english_question": "Who do you live with",
64
+ "answer_type": "multiplechoice",
65
+ "values": { "1": "Spouse", "2": "Parents", "3": "Children" }
66
+ }
67
+ }
68
+ }
69
+ ```
70
+
71
+ ## Input Format
72
+
73
+ ### `definition`
74
+
75
+ ```python
76
+ {
77
+ "survey": { "survey_id": 12345, "title": "...", ... },
78
+ "questions": [
79
+ {
80
+ "question_id": 1001,
81
+ "position": 6,
82
+ "question": "...",
83
+ "english_question": "Please provide your current address",
84
+ "type": 2, # 2=singlechoice, 3=multiplechoice, 6=ranking, 4=matrix, ...
85
+ "input_type": 0,
86
+ "mandatory": True,
87
+ "status": 1
88
+ },
89
+ ...
90
+ ]
91
+ }
92
+ ```
93
+
94
+ ### `rows_pages`
95
+
96
+ ```python
97
+ [
98
+ { # page 1
99
+ "rows": [
100
+ {
101
+ "task_id": "task_001",
102
+ "date_time": "2026-03-01 09:00:00",
103
+ "profile_status": "approved",
104
+ "questions": [
105
+ { "type": "singlechoice", "question": "Please provide your current address", "answer": "Ward 1" },
106
+ { "type": "multiplechoice", "question": "Who do you live with",
107
+ "answer": [{"answer_name": "Spouse"}, {"answer_name": "Children"}] },
108
+ ...
109
+ ]
110
+ },
111
+ ...
112
+ ]
113
+ },
114
+ # page 2, page 3, ...
115
+ ]
116
+ ```
117
+
118
+ ## Answer Types
119
+
120
+ | `type` value | `answer_type` | Encoded in rawdata? |
121
+ |---|---|---|
122
+ | 2 | `singlechoice` | Yes → int |
123
+ | 3 | `multiplechoice` | Yes → `"1;3;5"` |
124
+ | 6 | `ranking` | Yes → `"2;1;3"` |
125
+ | 4 | `matrix` | No → `"row:col\|row:col"` |
126
+ | 1 + input_type=100 | `multiplenumber` | No → `"label:num\|label:num"` |
127
+ | 1 | `freetext` | No → raw text |
128
+ | 1 + input_type=3 | `singlenumber` | No → raw number |
129
+ | 1109 | `area` | No → raw text |
130
+
131
+ Excluded from output: `audio`, `user-name`, `user-phone`, `instruction`, `reward`.
132
+
133
+ ## Profile Status Filter
134
+
135
+ ```python
136
+ # Default: approved only
137
+ step.run({ ..., "profile_status": ["approved"] })
138
+
139
+ # Include all statuses
140
+ step.run({ ..., "profile_status": [] })
141
+
142
+ # Custom filter
143
+ step.run({ ..., "profile_status": ["approved", "pending"] })
144
+ ```
145
+
146
+ ## Requirements
147
+
148
+ - Python >= 3.10
149
+ - pandas >= 2.0
150
+ - openpyxl >= 3.1
@@ -0,0 +1,20 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "surveyflow"
7
+ version = "0.1.0"
8
+ description = "Survey data pipeline: parse survey definitions and responses into rawdata.csv and metadata.json"
9
+ readme = "README.md"
10
+ requires-python = ">=3.10"
11
+ dependencies = [
12
+ "pandas>=2.0",
13
+ "openpyxl>=3.1",
14
+ ]
15
+
16
+ [project.optional-dependencies]
17
+ dev = ["pytest>=7.0", "pytest-cov"]
18
+
19
+ [tool.hatch.build.targets.wheel]
20
+ packages = ["surveyflow"]
@@ -0,0 +1,2 @@
1
+ pandas>=2.0
2
+ openpyxl>=3.1
@@ -0,0 +1,181 @@
1
+ """Run IngestionStep for VN8947."""
2
+ import sys, json, logging
3
+ logging.basicConfig(level=logging.INFO, format='%(levelname)s %(message)s')
4
+ sys.path.insert(0, r'C:\Users\PC\OneDrive\DevZone\PyPackages\surveyflow')
5
+
6
+ from surveyflow.steps.ingestion.ingestion_step import IngestionStep
7
+
8
+ # ── definition ──
9
+ definition = {
10
+ "survey": {
11
+ "survey_id": 723122,
12
+ "title": "VN8947 - Khảo sát dân cư khu vực Vũng Tàu.",
13
+ "english_title": "VN8947 - Khảo sát dân cư khu vực Vũng Tàu.",
14
+ "status": "2",
15
+ "start_date": "2026-03-17 15:59:42",
16
+ "end_date": ""
17
+ },
18
+ "questions": [
19
+ {"question_id":795781,"position":1,"question":"Please select the user name","english_question":"Please select the user name who invited you to this survey","type":1,"input_type":73,"mandatory":True,"status":1},
20
+ {"question_id":795780,"position":2,"question":"Record","english_question":"Record","type":40,"input_type":40,"mandatory":True,"status":1},
21
+ {"question_id":795570,"position":3,"question":"Anh/chị tên gì","english_question":"What is your name?","type":1106,"input_type":51,"mandatory":True,"status":1},
22
+ {"question_id":795571,"position":4,"question":"Số điện thoại","english_question":"What is your phone number?","type":1107,"input_type":52,"mandatory":True,"status":1},
23
+ {"question_id":795572,"position":5,"question":"Địa điểm phỏng vấn","english_question":"Input the address","type":1,"input_type":0,"mandatory":True,"status":1},
24
+ {"question_id":795699,"position":6,"question":"Địa chỉ sinh sống","english_question":"Please provide your current address","type":2,"input_type":100,"mandatory":True,"status":1},
25
+ {"question_id":795493,"position":7,"question":"Khoảng cách","english_question":"(Input by fieldworker) Distance from the target store","type":2,"input_type":0,"mandatory":True,"status":1},
26
+ {"question_id":795494,"position":8,"question":"Hướng","english_question":"(Input by fieldworker) Direction from the target store","type":2,"input_type":0,"mandatory":True,"status":1},
27
+ {"question_id":795495,"position":9,"question":"Người quyết định mua sắm","english_question":"Are you the decision-maker when it comes to purchasing items (such as food, clothing, household appliances, electronics, etc.) for yourself/your family?","type":2,"input_type":0,"mandatory":True,"status":1},
28
+ {"question_id":795496,"position":10,"question":"Giới tính","english_question":"Respondent gender","type":2,"input_type":0,"mandatory":True,"status":1},
29
+ {"question_id":795603,"position":11,"question":"Tuổi thực","english_question":"How old are you?","type":1101,"input_type":50,"mandatory":True,"status":1},
30
+ {"question_id":795497,"position":12,"question":"Khoảng tuổi","english_question":"Age","type":2,"input_type":0,"mandatory":True,"status":1},
31
+ {"question_id":795498,"position":13,"question":"Nghề nghiệp","english_question":"What is your occupation","type":2,"input_type":0,"mandatory":True,"status":1},
32
+ {"question_id":795499,"position":14,"question":"Sở hữu ô tô","english_question":"Do you own a car","type":2,"input_type":0,"mandatory":True,"status":1},
33
+ {"question_id":795500,"position":15,"question":"Sở hữu xe máy","english_question":"Do you own motorbike(s)?","type":2,"input_type":0,"mandatory":True,"status":1},
34
+ {"question_id":795501,"position":16,"question":"Số thành viên gia đình","english_question":"Number of people in a house (including yourself)","type":2,"input_type":0,"mandatory":True,"status":1},
35
+ {"question_id":795593,"position":17,"question":"Tình trạng hôn nhân","english_question":"Marital status","type":2,"input_type":0,"mandatory":True,"status":1},
36
+ {"question_id":795502,"position":18,"question":"Sống cùng ai","english_question":"Who do you live with","type":3,"input_type":0,"mandatory":True,"status":1},
37
+ {"question_id":795503,"position":19,"question":"Tuổi con","english_question":"Children age","type":3,"input_type":0,"mandatory":True,"status":1},
38
+ {"question_id":795504,"position":20,"question":"Tuổi chủ hộ","english_question":"Who is the age group of house owner","type":2,"input_type":0,"mandatory":True,"status":1},
39
+ {"question_id":795505,"position":21,"question":"Nuôi thú cưng","english_question":"Do you have a pet?","type":2,"input_type":0,"mandatory":True,"status":1},
40
+ {"question_id":795506,"position":22,"question":"Loại thú cưng","english_question":"What pets do you have?","type":3,"input_type":0,"mandatory":True,"status":1},
41
+ {"question_id":795507,"position":23,"question":"Sở hữu nhà","english_question":"Please select the type of house ownership","type":2,"input_type":0,"mandatory":True,"status":1},
42
+ {"question_id":795508,"position":24,"question":"Loại nhà","english_question":"What types of housing do you live in?","type":2,"input_type":0,"mandatory":True,"status":1},
43
+ {"question_id":795509,"position":25,"question":"Thời gian sinh sống","english_question":"How long do you live in the current housing?","type":2,"input_type":0,"mandatory":True,"status":1},
44
+ {"question_id":795510,"position":26,"question":"Báo điện tử","english_question":"What are the news site that you use often?","type":3,"input_type":0,"mandatory":True,"status":1},
45
+ {"question_id":795511,"position":27,"question":"Thẻ tín dụng","english_question":"What banks of credit cards do you own?","type":3,"input_type":0,"mandatory":True,"status":1},
46
+ {"question_id":795512,"position":28,"question":"Thu nhập hộ gia đình","english_question":"Average monthly household income (including the amount that has been sent over from someone, or won from the the other business","type":2,"input_type":0,"mandatory":True,"status":1},
47
+ {"question_id":795513,"position":29,"question":"Phương tiện đi mua sắm","english_question":"What transportation do you usually use for shopping?","type":5,"input_type":0,"mandatory":True,"status":1},
48
+ {"question_id":796159,"position":30,"question":"Phương thức thanh toán","english_question":"Please select the payment method that you use","type":3,"input_type":0,"mandatory":True,"status":1},
49
+ {"question_id":795514,"position":31,"question":"Thanh toán thường xuyên nhất","english_question":"What is the payment method you use the most","type":2,"input_type":0,"mandatory":True,"status":1},
50
+ {"question_id":795515,"position":32,"question":"Tần suất mua online","english_question":"How often do you buy online in a month","type":2,"input_type":0,"mandatory":True,"status":1},
51
+ {"question_id":795516,"position":33,"question":"Mua online mặt hàng gì","english_question":"What kind of categories do you buy online?","type":3,"input_type":0,"mandatory":True,"status":1},
52
+ {"question_id":795517,"position":34,"question":"Thời gian rảnh","english_question":"How do you usually spend your free-time","type":5,"input_type":0,"mandatory":True,"status":1},
53
+ {"question_id":795518,"position":35,"question":"Nơi mua thực phẩm top 2","english_question":"Please select top 2 stores that you go to buy foods products","type":3,"input_type":0,"mandatory":True,"status":1},
54
+ {"question_id":795519,"position":36,"question":"Tần suất mua thực phẩm","english_question":"How often do you buy foods products in a week?","type":2,"input_type":0,"mandatory":True,"status":1},
55
+ {"question_id":795520,"position":37,"question":"Chi tiêu thực phẩm","english_question":"How much do you spend on food per one shopping?","type":2,"input_type":0,"mandatory":True,"status":1},
56
+ {"question_id":795574,"position":38,"question":"Lý do chọn nơi mua thực phẩm","english_question":"What are the reasons that you use for the selected 2 stores","type":5,"input_type":0,"mandatory":True,"status":1},
57
+ {"question_id":795521,"position":39,"question":"Ăn ngoài","english_question":"Please select the dishes/restaurants you have visited when eating out.","type":3,"input_type":0,"mandatory":True,"status":1},
58
+ {"question_id":795522,"position":40,"question":"Tần suất ăn ngoài","english_question":"How often do you eat the following cuisine outside of your house?","type":4,"input_type":0,"mandatory":True,"status":1},
59
+ {"question_id":795594,"position":41,"question":"Quán ăn thường xuyên","english_question":"Interviewers select dishes/restaurants that respondents had eaten at least once every 2-3 months:","type":3,"input_type":0,"mandatory":True,"status":1},
60
+ {"question_id":795523,"position":42,"question":"Chi tiêu ăn ngoài","english_question":"How much do you spend at these restaurant per visit per time?","type":4,"input_type":0,"mandatory":True,"status":1},
61
+ {"question_id":795524,"position":43,"question":"Đi cùng ai","english_question":"Who do you go with?","type":4,"input_type":0,"mandatory":True,"status":1},
62
+ {"question_id":795525,"position":44,"question":"Loại nhà hàng hay đến","english_question":"Please select the restaurants that you go often","type":3,"input_type":0,"mandatory":True,"status":1},
63
+ {"question_id":795526,"position":45,"question":"Nhà hàng mong muốn","english_question":"Please share us if there are any restaurant you wish to have around the area","type":1,"input_type":0,"mandatory":True,"status":1},
64
+ {"question_id":795527,"position":46,"question":"Tần suất mua quần áo","english_question":"How often do you buy fashion items?","type":2,"input_type":0,"mandatory":True,"status":1},
65
+ {"question_id":795528,"position":47,"question":"Chi tiêu quần áo","english_question":"How much do you pay per one time shopping for fashion items?","type":1,"input_type":3,"mandatory":True,"status":1},
66
+ {"question_id":795529,"position":48,"question":"Kênh mua quần áo","english_question":"What channels do you purchase fashion items at?","type":3,"input_type":0,"mandatory":True,"status":1},
67
+ {"question_id":795530,"position":49,"question":"Yếu tố mua quần áo","english_question":"What are the things you pay attention to in buying clothes?","type":3,"input_type":0,"mandatory":True,"status":1},
68
+ {"question_id":795531,"position":50,"question":"Thương hiệu thời trang yêu thích","english_question":"What are your favorite fashion brands","type":3,"input_type":0,"mandatory":True,"status":1},
69
+ {"question_id":795532,"position":51,"question":"Thương hiệu thời trang mong muốn","english_question":"Please share us if there are any fashion brand shop you wish to have around the area","type":1,"input_type":0,"mandatory":True,"status":1},
70
+ {"question_id":795533,"position":52,"question":"Tần suất mua quần áo trẻ em","english_question":"How often do you buy kids fashion item","type":2,"input_type":0,"mandatory":True,"status":1},
71
+ {"question_id":795534,"position":53,"question":"Chi tiêu quần áo trẻ em","english_question":"How much do you pay per one time shopping for the cateogry?","type":1,"input_type":3,"mandatory":True,"status":1},
72
+ {"question_id":795535,"position":54,"question":"Kênh mua quần áo trẻ em","english_question":"Please share us if there are any specific stores that you buy children fashion items with","type":3,"input_type":0,"mandatory":True,"status":1},
73
+ {"question_id":795536,"position":55,"question":"Yếu tố mua quần áo trẻ em","english_question":"What are the things that you pay attention to for the kids fashion items","type":3,"input_type":0,"mandatory":True,"status":1},
74
+ {"question_id":795537,"position":56,"question":"Kênh mua đồ chơi","english_question":"What channels do you purchase toy items at?","type":3,"input_type":0,"mandatory":True,"status":1},
75
+ {"question_id":795538,"position":57,"question":"Yếu tố mua đồ chơi","english_question":"What are the things that you pay attention to for purchasing toys","type":3,"input_type":0,"mandatory":True,"status":1},
76
+ {"question_id":795539,"position":58,"question":"Tần suất mua mỹ phẩm","english_question":"How often do you buy beauty items?","type":2,"input_type":0,"mandatory":True,"status":1},
77
+ {"question_id":795540,"position":59,"question":"Chi tiêu mỹ phẩm","english_question":"How much do you pay per one time shopping for beauty items?","type":1,"input_type":3,"mandatory":True,"status":1},
78
+ {"question_id":795541,"position":60,"question":"Kênh mua mỹ phẩm","english_question":"What channels do you purchase beauty items at?","type":3,"input_type":0,"mandatory":True,"status":1},
79
+ {"question_id":795542,"position":61,"question":"Yếu tố mua mỹ phẩm","english_question":"What are the things you pay attention to in buying beauty items","type":3,"input_type":0,"mandatory":True,"status":1},
80
+ {"question_id":795543,"position":62,"question":"Sản phẩm mỹ phẩm đang dùng","english_question":"Please select the beauty items that you own","type":3,"input_type":0,"mandatory":True,"status":1},
81
+ {"question_id":795544,"position":63,"question":"Thiết bị gia dụng đang dùng","english_question":"Please share us the home appliance / electronics / IT products that you own","type":3,"input_type":0,"mandatory":True,"status":1},
82
+ {"question_id":795545,"position":64,"question":"Kênh mua thiết bị gia dụng","english_question":"What channels do you purchase such home appliance / electronics / IT products at?","type":3,"input_type":0,"mandatory":True,"status":1},
83
+ {"question_id":795546,"position":65,"question":"Yếu tố mua thiết bị gia dụng","english_question":"What are the things you pay attention to in buying electronics / home appliances / IT","type":3,"input_type":0,"mandatory":True,"status":1},
84
+ {"question_id":795547,"position":66,"question":"Thương hiệu điện thoại","english_question":"Please select the smartphone brand that you own","type":3,"input_type":0,"mandatory":True,"status":1},
85
+ {"question_id":795548,"position":67,"question":"Thương hiệu máy lạnh","english_question":"Please select the Air conditioner brand that you own","type":3,"input_type":0,"mandatory":True,"status":1},
86
+ {"question_id":795549,"position":68,"question":"Thương hiệu tủ lạnh","english_question":"Please select the Refrigerator brand that you own","type":3,"input_type":0,"mandatory":True,"status":1},
87
+ {"question_id":795550,"position":69,"question":"Thương hiệu máy giặt","english_question":"Please select the Washing machine brand that you own","type":3,"input_type":0,"mandatory":True,"status":1},
88
+ {"question_id":795551,"position":70,"question":"Thương hiệu TV","english_question":"Please select the TV brand that you own","type":3,"input_type":0,"mandatory":True,"status":1},
89
+ {"question_id":795552,"position":71,"question":"Ngân sách thiết bị gia dụng","english_question":"Please select your budget in case you are to purchase the below","type":4,"input_type":0,"mandatory":True,"status":1},
90
+ {"question_id":795553,"position":72,"question":"Nội thất đang dùng","english_question":"Please share us the furniture that you own","type":3,"input_type":0,"mandatory":True,"status":1},
91
+ {"question_id":795554,"position":73,"question":"Kênh mua nội thất","english_question":"What channels do you purchase such furniture at?","type":3,"input_type":0,"mandatory":True,"status":1},
92
+ {"question_id":795555,"position":74,"question":"Yếu tố mua nội thất","english_question":"What are the things you pay attention to in buying furniture","type":3,"input_type":0,"mandatory":True,"status":1},
93
+ {"question_id":795556,"position":75,"question":"Ngân sách nội thất","english_question":"Please select your budget in case you are to purchase the below","type":4,"input_type":0,"mandatory":True,"status":1},
94
+ {"question_id":795557,"position":76,"question":"Hoạt động của con hiện tại","english_question":"What are the things that your children learn","type":3,"input_type":0,"mandatory":True,"status":1},
95
+ {"question_id":795558,"position":77,"question":"Hoạt động con tương lai","english_question":"Are there anything that you would like your children to learn in the future?","type":3,"input_type":0,"mandatory":True,"status":1},
96
+ {"question_id":795559,"position":78,"question":"Tần suất xem phim","english_question":"How often do you watch movies at cinema?","type":2,"input_type":0,"mandatory":True,"status":1},
97
+ {"question_id":795560,"position":79,"question":"Biết TTTM nào","english_question":"Please select the mall that you aware aware in this city","type":3,"input_type":0,"mandatory":True,"status":1},
98
+ {"question_id":795561,"position":80,"question":"Tần suất đến TTTM","english_question":"How often do you visit the following mall (in the last 90 days)","type":4,"input_type":0,"mandatory":True,"status":1},
99
+ {"question_id":795562,"position":81,"question":"TTTM thường xuyên nhất","english_question":"Please select the shopping mall you visit the most","type":2,"input_type":0,"mandatory":True,"status":1},
100
+ {"question_id":795563,"position":82,"question":"Lý do đến TTTM đó","english_question":"What are the reasons that you visit the most ","type":3,"input_type":0,"mandatory":True,"status":1},
101
+ {"question_id":795577,"position":83,"question":"TTTM đến ít nhất 2-3 tháng","english_question":"Interviewer selected supermarkets/shopping malls that respondents had visited at least once in the past 2-3 months.","type":3,"input_type":0,"mandatory":True,"status":1},
102
+ {"question_id":795564,"position":84,"question":"Mua gì ở TTTM","english_question":"What do you shop at the selected malls","type":5,"input_type":0,"mandatory":True,"status":1},
103
+ {"question_id":795579,"position":85,"question":"Mức độ hài lòng TTTM","english_question":"How much are you satisfied with the following malls","type":4,"input_type":0,"mandatory":True,"status":1},
104
+ {"question_id":795581,"position":86,"question":"Hài lòng điều gì ở TTTM","english_question":"What are the things that you are satisfied with about these malls","type":5,"input_type":0,"mandatory":True,"status":1},
105
+ {"question_id":795567,"position":87,"question":"Cần cải thiện ở TTTM","english_question":"Please select if there are anything that you wish to improve about the following mall","type":5,"input_type":0,"mandatory":True,"status":1},
106
+ {"question_id":795568,"position":88,"question":"Mong muốn thêm ở TTTM","english_question":"Please share us about the types of stores and services you wish shopping centers to have more facilities or services","type":3,"input_type":0,"mandatory":True,"status":1},
107
+ {"question_id":795575,"position":89,"question":"reward","english_question":"reward","type":1,"input_type":68,"mandatory":True,"status":1},
108
+ ],
109
+ "question_count": 89
110
+ }
111
+
112
+ # ── row pages ──
113
+ pages = []
114
+ for fpath in [
115
+ r'C:\Users\PC\.claude\projects\C--Users-PC-OneDrive-DevZone-PyPackages-surveyflow--claude-worktrees-clever-boyd\c1b76edf-5bd6-4519-a4cb-f85c372c459f\tool-results\mcp-92b3762a-2e81-47a2-9b3e-095c0b4486d7-get_survey_rows-1775202854470.txt',
116
+ r'C:\Users\PC\.claude\projects\C--Users-PC-OneDrive-DevZone-PyPackages-surveyflow--claude-worktrees-clever-boyd\c1b76edf-5bd6-4519-a4cb-f85c372c459f\tool-results\mcp-92b3762a-2e81-47a2-9b3e-095c0b4486d7-get_survey_rows-1775203012244.txt',
117
+ ]:
118
+ with open(fpath, encoding='utf-8') as f:
119
+ pages.append(json.load(f))
120
+
121
+ # page 3 — 1 row (already fetched inline)
122
+ pages.append({
123
+ "rows": [{
124
+ "date_time": "2026-03-26 14:35:40", "Key_in_date": "2026-03-26 14:35:40",
125
+ "lastmodified_date": "2026-04-02 10:48:58", "task_id": "426_723122_2968182",
126
+ "profile_status": "approved",
127
+ "questions": [
128
+ {"type":"freetext","question":"Input the address","answer":"160 do luong"},
129
+ {"type":"singlechoice","question":"Please provide your current address","answer":"Ward 11"},
130
+ {"type":"singlechoice","question":"(Input by fieldworker) Distance from the target store","answer":"0 - 2.49 km"},
131
+ {"type":"singlechoice","question":"(Input by fieldworker) Direction from the target store","answer":"East"},
132
+ {"type":"singlechoice","question":"Are you the decision-maker when it comes to purchasing items (such as food, clothing, household appliances, electronics, etc.) for yourself/your family?","answer":"I am the main decision maker for some of the above items"},
133
+ {"type":"singlechoice","question":"Respondent gender","answer":"Female"},
134
+ {"type":"singlechoice","question":"Age","answer":"35-39"},
135
+ {"type":"singlechoice","question":"What is your occupation","answer":"Company Employee"},
136
+ {"type":"singlechoice","question":"Do you own a car","answer":"No"},
137
+ {"type":"singlechoice","question":"Do you own motorbike(s)?","answer":"Yes - Own 2"},
138
+ {"type":"singlechoice","question":"Number of people in a house (including yourself)","answer":"5"},
139
+ {"type":"singlechoice","question":"Marital status","answer":"Married (with children)"},
140
+ {"type":"multiplechoice","question":"Who do you live with","answer":[{"answer_name":"Spouse","answer_id":5548964}]},
141
+ {"type":"singlechoice","question":"Who is the age group of house owner","answer":"Over 60s"},
142
+ {"type":"singlechoice","question":"Do you have a pet?","answer":"No"},
143
+ {"type":"singlechoice","question":"Please select the type of house ownership","answer":"Owned (Parents)"},
144
+ {"type":"singlechoice","question":"What types of housing do you live in?","answer":"Detached House"},
145
+ {"type":"singlechoice","question":"How long do you live in the current housing?","answer":"Over 20 years"},
146
+ {"type":"singlechoice","question":"Average monthly household income (including the amount that has been sent over from someone, or won from the the other business","answer":"25M VND~29.9M VND"},
147
+ {"type":"singlechoice","question":"What is the payment method you use the most","answer":"By cash"},
148
+ {"type":"singlechoice","question":"How often do you buy online in a month","answer":"Less than once/month"},
149
+ {"type":"singlechoice","question":"How often do you buy foods products in a week?","answer":"More than 4 times"},
150
+ {"type":"singlechoice","question":"How much do you spend on food per one shopping?","answer":"101-200K VND"},
151
+ {"type":"singlechoice","question":"How often do you buy fashion items?","answer":"Less than once every 2-3 months"},
152
+ {"type":"singlechoice","question":"How often do you buy kids fashion item","answer":"Less than once every 2-3 months"},
153
+ {"type":"singlechoice","question":"How often do you buy beauty items?","answer":"About 2-3 times a month"},
154
+ {"type":"singlechoice","question":"Please select the shopping mall you visit the most","answer":"Co.opmart Vung Tau"},
155
+ {"type":"singlechoice","question":"How often do you watch movies at cinema?","answer":"Have been only once"},
156
+ ]
157
+ }]
158
+ })
159
+
160
+ total_rows = sum(len(p["rows"]) for p in pages)
161
+ print(f"Pages: {len(pages)}, Total rows: {total_rows}")
162
+
163
+ # ── run ──
164
+ step = IngestionStep()
165
+ ctx = step.run({
166
+ "definition": definition,
167
+ "rows_pages": pages,
168
+ "output_dir": r"C:\Users\PC\OneDrive\DevZone\PyPackages\surveyflow\output_test\VN8947",
169
+ "profile_status": ["approved"],
170
+ })
171
+
172
+ df = ctx["rawdata"]
173
+ meta = ctx["metadata"]
174
+ print(f"\nDataFrame : {df.shape[0]} rows x {df.shape[1]} cols")
175
+ print(f"Questions : {len(meta['questions'])} in metadata")
176
+ print(f"rawdata : {ctx['rawdata_path']}")
177
+ print(f"metadata : {ctx['metadata_path']}")
178
+
179
+ # sample
180
+ print("\nSample row (q6, q10, q12):")
181
+ print(df[["task_id","q6","q10","q12"]].head(3).to_string(index=False))
@@ -0,0 +1,6 @@
1
+ """SurveyFlow – Survey data pipeline."""
2
+
3
+ from surveyflow.steps.ingestion.ingestion_step import IngestionStep
4
+
5
+ __all__ = ["IngestionStep"]
6
+ __version__ = "0.1.0"
@@ -0,0 +1,4 @@
1
+ from surveyflow.core.config import PipelineConfig
2
+ from surveyflow.core.base import Step
3
+
4
+ __all__ = ["PipelineConfig", "Step"]
@@ -0,0 +1,29 @@
1
+ """Base classes for pipeline steps."""
2
+
3
+ from abc import ABC, abstractmethod
4
+ from typing import Any
5
+
6
+
7
+ class Step(ABC):
8
+ """Abstract base class for all pipeline steps."""
9
+
10
+ @abstractmethod
11
+ def run(self, context: dict[str, Any]) -> dict[str, Any]:
12
+ """
13
+ Execute the step.
14
+
15
+ Parameters
16
+ ----------
17
+ context : dict
18
+ Shared pipeline context. Steps read inputs from and write
19
+ outputs into this dict.
20
+
21
+ Returns
22
+ -------
23
+ dict
24
+ Updated context after this step.
25
+ """
26
+ ...
27
+
28
+ def __repr__(self) -> str:
29
+ return f"{self.__class__.__name__}()"