edsl 0.1.39.dev3__py3-none-any.whl → 0.1.39.dev4__py3-none-any.whl
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.
- edsl/Base.py +413 -332
- edsl/BaseDiff.py +260 -260
- edsl/TemplateLoader.py +24 -24
- edsl/__init__.py +57 -49
- edsl/__version__.py +1 -1
- edsl/agents/Agent.py +1071 -867
- edsl/agents/AgentList.py +551 -413
- edsl/agents/Invigilator.py +284 -233
- edsl/agents/InvigilatorBase.py +257 -270
- edsl/agents/PromptConstructor.py +272 -354
- edsl/agents/QuestionInstructionPromptBuilder.py +128 -0
- edsl/agents/QuestionTemplateReplacementsBuilder.py +137 -0
- edsl/agents/__init__.py +2 -3
- edsl/agents/descriptors.py +99 -99
- edsl/agents/prompt_helpers.py +129 -129
- edsl/agents/question_option_processor.py +172 -0
- edsl/auto/AutoStudy.py +130 -117
- edsl/auto/StageBase.py +243 -230
- edsl/auto/StageGenerateSurvey.py +178 -178
- edsl/auto/StageLabelQuestions.py +125 -125
- edsl/auto/StagePersona.py +61 -61
- edsl/auto/StagePersonaDimensionValueRanges.py +88 -88
- edsl/auto/StagePersonaDimensionValues.py +74 -74
- edsl/auto/StagePersonaDimensions.py +69 -69
- edsl/auto/StageQuestions.py +74 -73
- edsl/auto/SurveyCreatorPipeline.py +21 -21
- edsl/auto/utilities.py +218 -224
- edsl/base/Base.py +279 -279
- edsl/config.py +177 -157
- edsl/conversation/Conversation.py +290 -290
- edsl/conversation/car_buying.py +59 -58
- edsl/conversation/chips.py +95 -95
- edsl/conversation/mug_negotiation.py +81 -81
- edsl/conversation/next_speaker_utilities.py +93 -93
- edsl/coop/CoopFunctionsMixin.py +15 -0
- edsl/coop/ExpectedParrotKeyHandler.py +125 -0
- edsl/coop/PriceFetcher.py +54 -54
- edsl/coop/__init__.py +2 -2
- edsl/coop/coop.py +1106 -1028
- edsl/coop/utils.py +131 -131
- edsl/data/Cache.py +573 -555
- edsl/data/CacheEntry.py +230 -233
- edsl/data/CacheHandler.py +168 -149
- edsl/data/RemoteCacheSync.py +186 -78
- edsl/data/SQLiteDict.py +292 -292
- edsl/data/__init__.py +5 -4
- edsl/data/hack.py +10 -0
- edsl/data/orm.py +10 -10
- edsl/data_transfer_models.py +74 -73
- edsl/enums.py +202 -175
- edsl/exceptions/BaseException.py +21 -21
- edsl/exceptions/__init__.py +54 -54
- edsl/exceptions/agents.py +54 -42
- edsl/exceptions/cache.py +5 -5
- edsl/exceptions/configuration.py +16 -16
- edsl/exceptions/coop.py +10 -10
- edsl/exceptions/data.py +14 -14
- edsl/exceptions/general.py +34 -34
- edsl/exceptions/inference_services.py +5 -0
- edsl/exceptions/jobs.py +33 -33
- edsl/exceptions/language_models.py +63 -63
- edsl/exceptions/prompts.py +15 -15
- edsl/exceptions/questions.py +109 -91
- edsl/exceptions/results.py +29 -29
- edsl/exceptions/scenarios.py +29 -22
- edsl/exceptions/surveys.py +37 -37
- edsl/inference_services/AnthropicService.py +106 -87
- edsl/inference_services/AvailableModelCacheHandler.py +184 -0
- edsl/inference_services/AvailableModelFetcher.py +215 -0
- edsl/inference_services/AwsBedrock.py +118 -120
- edsl/inference_services/AzureAI.py +215 -217
- edsl/inference_services/DeepInfraService.py +18 -18
- edsl/inference_services/GoogleService.py +143 -148
- edsl/inference_services/GroqService.py +20 -20
- edsl/inference_services/InferenceServiceABC.py +80 -147
- edsl/inference_services/InferenceServicesCollection.py +138 -97
- edsl/inference_services/MistralAIService.py +120 -123
- edsl/inference_services/OllamaService.py +18 -18
- edsl/inference_services/OpenAIService.py +236 -224
- edsl/inference_services/PerplexityService.py +160 -163
- edsl/inference_services/ServiceAvailability.py +135 -0
- edsl/inference_services/TestService.py +90 -89
- edsl/inference_services/TogetherAIService.py +172 -170
- edsl/inference_services/data_structures.py +134 -0
- edsl/inference_services/models_available_cache.py +118 -118
- edsl/inference_services/rate_limits_cache.py +25 -25
- edsl/inference_services/registry.py +41 -41
- edsl/inference_services/write_available.py +10 -10
- edsl/jobs/AnswerQuestionFunctionConstructor.py +223 -0
- edsl/jobs/Answers.py +43 -56
- edsl/jobs/FetchInvigilator.py +47 -0
- edsl/jobs/InterviewTaskManager.py +98 -0
- edsl/jobs/InterviewsConstructor.py +50 -0
- edsl/jobs/Jobs.py +823 -898
- edsl/jobs/JobsChecks.py +172 -147
- edsl/jobs/JobsComponentConstructor.py +189 -0
- edsl/jobs/JobsPrompts.py +270 -268
- edsl/jobs/JobsRemoteInferenceHandler.py +311 -239
- edsl/jobs/JobsRemoteInferenceLogger.py +239 -0
- edsl/jobs/RequestTokenEstimator.py +30 -0
- edsl/jobs/__init__.py +1 -1
- edsl/jobs/async_interview_runner.py +138 -0
- edsl/jobs/buckets/BucketCollection.py +104 -63
- edsl/jobs/buckets/ModelBuckets.py +65 -65
- edsl/jobs/buckets/TokenBucket.py +283 -251
- edsl/jobs/buckets/TokenBucketAPI.py +211 -0
- edsl/jobs/buckets/TokenBucketClient.py +191 -0
- edsl/jobs/check_survey_scenario_compatibility.py +85 -0
- edsl/jobs/data_structures.py +120 -0
- edsl/jobs/decorators.py +35 -0
- edsl/jobs/interviews/Interview.py +396 -661
- edsl/jobs/interviews/InterviewExceptionCollection.py +99 -99
- edsl/jobs/interviews/InterviewExceptionEntry.py +186 -186
- edsl/jobs/interviews/InterviewStatistic.py +63 -63
- edsl/jobs/interviews/InterviewStatisticsCollection.py +25 -25
- edsl/jobs/interviews/InterviewStatusDictionary.py +78 -78
- edsl/jobs/interviews/InterviewStatusLog.py +92 -92
- edsl/jobs/interviews/ReportErrors.py +66 -66
- edsl/jobs/interviews/interview_status_enum.py +9 -9
- edsl/jobs/jobs_status_enums.py +9 -0
- edsl/jobs/loggers/HTMLTableJobLogger.py +304 -0
- edsl/jobs/results_exceptions_handler.py +98 -0
- edsl/jobs/runners/JobsRunnerAsyncio.py +151 -466
- edsl/jobs/runners/JobsRunnerStatus.py +297 -330
- edsl/jobs/tasks/QuestionTaskCreator.py +244 -242
- edsl/jobs/tasks/TaskCreators.py +64 -64
- edsl/jobs/tasks/TaskHistory.py +470 -450
- edsl/jobs/tasks/TaskStatusLog.py +23 -23
- edsl/jobs/tasks/task_status_enum.py +161 -163
- edsl/jobs/tokens/InterviewTokenUsage.py +27 -27
- edsl/jobs/tokens/TokenUsage.py +34 -34
- edsl/language_models/ComputeCost.py +63 -0
- edsl/language_models/LanguageModel.py +626 -668
- edsl/language_models/ModelList.py +164 -155
- edsl/language_models/PriceManager.py +127 -0
- edsl/language_models/RawResponseHandler.py +106 -0
- edsl/language_models/RegisterLanguageModelsMeta.py +184 -184
- edsl/language_models/ServiceDataSources.py +0 -0
- edsl/language_models/__init__.py +2 -3
- edsl/language_models/fake_openai_call.py +15 -15
- edsl/language_models/fake_openai_service.py +61 -61
- edsl/language_models/key_management/KeyLookup.py +63 -0
- edsl/language_models/key_management/KeyLookupBuilder.py +273 -0
- edsl/language_models/key_management/KeyLookupCollection.py +38 -0
- edsl/language_models/key_management/__init__.py +0 -0
- edsl/language_models/key_management/models.py +131 -0
- edsl/language_models/model.py +256 -0
- edsl/language_models/repair.py +156 -156
- edsl/language_models/utilities.py +65 -64
- edsl/notebooks/Notebook.py +263 -258
- edsl/notebooks/NotebookToLaTeX.py +142 -0
- edsl/notebooks/__init__.py +1 -1
- edsl/prompts/Prompt.py +352 -362
- edsl/prompts/__init__.py +2 -2
- edsl/questions/ExceptionExplainer.py +77 -0
- edsl/questions/HTMLQuestion.py +103 -0
- edsl/questions/QuestionBase.py +518 -664
- edsl/questions/QuestionBasePromptsMixin.py +221 -217
- edsl/questions/QuestionBudget.py +227 -227
- edsl/questions/QuestionCheckBox.py +359 -359
- edsl/questions/QuestionExtract.py +180 -182
- edsl/questions/QuestionFreeText.py +113 -114
- edsl/questions/QuestionFunctional.py +166 -166
- edsl/questions/QuestionList.py +223 -231
- edsl/questions/QuestionMatrix.py +265 -0
- edsl/questions/QuestionMultipleChoice.py +330 -286
- edsl/questions/QuestionNumerical.py +151 -153
- edsl/questions/QuestionRank.py +314 -324
- edsl/questions/Quick.py +41 -41
- edsl/questions/SimpleAskMixin.py +74 -73
- edsl/questions/__init__.py +27 -26
- edsl/questions/{AnswerValidatorMixin.py → answer_validator_mixin.py} +334 -289
- edsl/questions/compose_questions.py +98 -98
- edsl/questions/data_structures.py +20 -0
- edsl/questions/decorators.py +21 -21
- edsl/questions/derived/QuestionLikertFive.py +76 -76
- edsl/questions/derived/QuestionLinearScale.py +90 -87
- edsl/questions/derived/QuestionTopK.py +93 -93
- edsl/questions/derived/QuestionYesNo.py +82 -82
- edsl/questions/descriptors.py +427 -413
- edsl/questions/loop_processor.py +149 -0
- edsl/questions/prompt_templates/question_budget.jinja +13 -13
- edsl/questions/prompt_templates/question_checkbox.jinja +32 -32
- edsl/questions/prompt_templates/question_extract.jinja +11 -11
- edsl/questions/prompt_templates/question_free_text.jinja +3 -3
- edsl/questions/prompt_templates/question_linear_scale.jinja +11 -11
- edsl/questions/prompt_templates/question_list.jinja +17 -17
- edsl/questions/prompt_templates/question_multiple_choice.jinja +33 -33
- edsl/questions/prompt_templates/question_numerical.jinja +36 -36
- edsl/questions/{QuestionBaseGenMixin.py → question_base_gen_mixin.py} +168 -161
- edsl/questions/question_registry.py +177 -177
- edsl/questions/{RegisterQuestionsMeta.py → register_questions_meta.py} +71 -71
- edsl/questions/{ResponseValidatorABC.py → response_validator_abc.py} +188 -174
- edsl/questions/response_validator_factory.py +34 -0
- edsl/questions/settings.py +12 -12
- edsl/questions/templates/budget/answering_instructions.jinja +7 -7
- edsl/questions/templates/budget/question_presentation.jinja +7 -7
- edsl/questions/templates/checkbox/answering_instructions.jinja +10 -10
- edsl/questions/templates/checkbox/question_presentation.jinja +22 -22
- edsl/questions/templates/extract/answering_instructions.jinja +7 -7
- edsl/questions/templates/likert_five/answering_instructions.jinja +10 -10
- edsl/questions/templates/likert_five/question_presentation.jinja +11 -11
- edsl/questions/templates/linear_scale/answering_instructions.jinja +5 -5
- edsl/questions/templates/linear_scale/question_presentation.jinja +5 -5
- edsl/questions/templates/list/answering_instructions.jinja +3 -3
- edsl/questions/templates/list/question_presentation.jinja +5 -5
- edsl/questions/templates/matrix/__init__.py +1 -0
- edsl/questions/templates/matrix/answering_instructions.jinja +5 -0
- edsl/questions/templates/matrix/question_presentation.jinja +20 -0
- edsl/questions/templates/multiple_choice/answering_instructions.jinja +9 -9
- edsl/questions/templates/multiple_choice/question_presentation.jinja +11 -11
- edsl/questions/templates/numerical/answering_instructions.jinja +6 -6
- edsl/questions/templates/numerical/question_presentation.jinja +6 -6
- edsl/questions/templates/rank/answering_instructions.jinja +11 -11
- edsl/questions/templates/rank/question_presentation.jinja +15 -15
- edsl/questions/templates/top_k/answering_instructions.jinja +8 -8
- edsl/questions/templates/top_k/question_presentation.jinja +22 -22
- edsl/questions/templates/yes_no/answering_instructions.jinja +6 -6
- edsl/questions/templates/yes_no/question_presentation.jinja +11 -11
- edsl/results/CSSParameterizer.py +108 -108
- edsl/results/Dataset.py +587 -424
- edsl/results/DatasetExportMixin.py +594 -731
- edsl/results/DatasetTree.py +295 -275
- edsl/results/MarkdownToDocx.py +122 -0
- edsl/results/MarkdownToPDF.py +111 -0
- edsl/results/Result.py +557 -465
- edsl/results/Results.py +1183 -1165
- edsl/results/ResultsExportMixin.py +45 -43
- edsl/results/ResultsGGMixin.py +121 -121
- edsl/results/TableDisplay.py +125 -198
- edsl/results/TextEditor.py +50 -0
- edsl/results/__init__.py +2 -2
- edsl/results/file_exports.py +252 -0
- edsl/results/{ResultsFetchMixin.py → results_fetch_mixin.py} +33 -33
- edsl/results/{Selector.py → results_selector.py} +145 -135
- edsl/results/{ResultsToolsMixin.py → results_tools_mixin.py} +98 -98
- edsl/results/smart_objects.py +96 -0
- edsl/results/table_data_class.py +12 -0
- edsl/results/table_display.css +77 -77
- edsl/results/table_renderers.py +118 -0
- edsl/results/tree_explore.py +115 -115
- edsl/scenarios/ConstructDownloadLink.py +109 -0
- edsl/scenarios/DocumentChunker.py +102 -0
- edsl/scenarios/DocxScenario.py +16 -0
- edsl/scenarios/FileStore.py +511 -632
- edsl/scenarios/PdfExtractor.py +40 -0
- edsl/scenarios/Scenario.py +498 -601
- edsl/scenarios/ScenarioHtmlMixin.py +65 -64
- edsl/scenarios/ScenarioList.py +1458 -1287
- edsl/scenarios/ScenarioListExportMixin.py +45 -52
- edsl/scenarios/ScenarioListPdfMixin.py +239 -261
- edsl/scenarios/__init__.py +3 -4
- edsl/scenarios/directory_scanner.py +96 -0
- edsl/scenarios/file_methods.py +85 -0
- edsl/scenarios/handlers/__init__.py +13 -0
- edsl/scenarios/handlers/csv.py +38 -0
- edsl/scenarios/handlers/docx.py +76 -0
- edsl/scenarios/handlers/html.py +37 -0
- edsl/scenarios/handlers/json.py +111 -0
- edsl/scenarios/handlers/latex.py +5 -0
- edsl/scenarios/handlers/md.py +51 -0
- edsl/scenarios/handlers/pdf.py +68 -0
- edsl/scenarios/handlers/png.py +39 -0
- edsl/scenarios/handlers/pptx.py +105 -0
- edsl/scenarios/handlers/py.py +294 -0
- edsl/scenarios/handlers/sql.py +313 -0
- edsl/scenarios/handlers/sqlite.py +149 -0
- edsl/scenarios/handlers/txt.py +33 -0
- edsl/scenarios/{ScenarioJoin.py → scenario_join.py} +131 -127
- edsl/scenarios/scenario_selector.py +156 -0
- edsl/shared.py +1 -1
- edsl/study/ObjectEntry.py +173 -173
- edsl/study/ProofOfWork.py +113 -113
- edsl/study/SnapShot.py +80 -80
- edsl/study/Study.py +521 -528
- edsl/study/__init__.py +4 -4
- edsl/surveys/ConstructDAG.py +92 -0
- edsl/surveys/DAG.py +148 -148
- edsl/surveys/EditSurvey.py +221 -0
- edsl/surveys/InstructionHandler.py +100 -0
- edsl/surveys/Memory.py +31 -31
- edsl/surveys/MemoryManagement.py +72 -0
- edsl/surveys/MemoryPlan.py +244 -244
- edsl/surveys/Rule.py +327 -326
- edsl/surveys/RuleCollection.py +385 -387
- edsl/surveys/RuleManager.py +172 -0
- edsl/surveys/Simulator.py +75 -0
- edsl/surveys/Survey.py +1280 -1801
- edsl/surveys/SurveyCSS.py +273 -261
- edsl/surveys/SurveyExportMixin.py +259 -259
- edsl/surveys/{SurveyFlowVisualizationMixin.py → SurveyFlowVisualization.py} +181 -179
- edsl/surveys/SurveyQualtricsImport.py +284 -284
- edsl/surveys/SurveyToApp.py +141 -0
- edsl/surveys/__init__.py +5 -3
- edsl/surveys/base.py +53 -53
- edsl/surveys/descriptors.py +60 -56
- edsl/surveys/instructions/ChangeInstruction.py +48 -49
- edsl/surveys/instructions/Instruction.py +56 -65
- edsl/surveys/instructions/InstructionCollection.py +82 -77
- edsl/templates/error_reporting/base.html +23 -23
- edsl/templates/error_reporting/exceptions_by_model.html +34 -34
- edsl/templates/error_reporting/exceptions_by_question_name.html +16 -16
- edsl/templates/error_reporting/exceptions_by_type.html +16 -16
- edsl/templates/error_reporting/interview_details.html +115 -115
- edsl/templates/error_reporting/interviews.html +19 -19
- edsl/templates/error_reporting/overview.html +4 -4
- edsl/templates/error_reporting/performance_plot.html +1 -1
- edsl/templates/error_reporting/report.css +73 -73
- edsl/templates/error_reporting/report.html +117 -117
- edsl/templates/error_reporting/report.js +25 -25
- edsl/test_h +1 -0
- edsl/tools/__init__.py +1 -1
- edsl/tools/clusters.py +192 -192
- edsl/tools/embeddings.py +27 -27
- edsl/tools/embeddings_plotting.py +118 -118
- edsl/tools/plotting.py +112 -112
- edsl/tools/summarize.py +18 -18
- edsl/utilities/PrettyList.py +56 -0
- edsl/utilities/SystemInfo.py +28 -28
- edsl/utilities/__init__.py +22 -22
- edsl/utilities/ast_utilities.py +25 -25
- edsl/utilities/data/Registry.py +6 -6
- edsl/utilities/data/__init__.py +1 -1
- edsl/utilities/data/scooter_results.json +1 -1
- edsl/utilities/decorators.py +77 -77
- edsl/utilities/gcp_bucket/cloud_storage.py +96 -96
- edsl/utilities/gcp_bucket/example.py +50 -0
- edsl/utilities/interface.py +627 -627
- edsl/utilities/is_notebook.py +18 -0
- edsl/utilities/is_valid_variable_name.py +11 -0
- edsl/utilities/naming_utilities.py +263 -263
- edsl/utilities/remove_edsl_version.py +24 -0
- edsl/utilities/repair_functions.py +28 -28
- edsl/utilities/restricted_python.py +70 -70
- edsl/utilities/utilities.py +436 -424
- {edsl-0.1.39.dev3.dist-info → edsl-0.1.39.dev4.dist-info}/LICENSE +21 -21
- {edsl-0.1.39.dev3.dist-info → edsl-0.1.39.dev4.dist-info}/METADATA +13 -11
- edsl-0.1.39.dev4.dist-info/RECORD +361 -0
- edsl/language_models/KeyLookup.py +0 -30
- edsl/language_models/registry.py +0 -190
- edsl/language_models/unused/ReplicateBase.py +0 -83
- edsl/results/ResultsDBMixin.py +0 -238
- edsl-0.1.39.dev3.dist-info/RECORD +0 -277
- {edsl-0.1.39.dev3.dist-info → edsl-0.1.39.dev4.dist-info}/WHEEL +0 -0
@@ -1,284 +1,284 @@
|
|
1
|
-
import json
|
2
|
-
import html
|
3
|
-
import re
|
4
|
-
|
5
|
-
from edsl import Question
|
6
|
-
from edsl import Survey
|
7
|
-
|
8
|
-
qualtrics_codes = {
|
9
|
-
"TE": "free_text",
|
10
|
-
"MC": "multiple_choice",
|
11
|
-
"Matrix": "matrix",
|
12
|
-
"DB": "instruction", # not quite right, but for now
|
13
|
-
"Timing": "free_text", # not quite right, but for now
|
14
|
-
}
|
15
|
-
# TE (Text Entry): Allows respondents to input a text response.
|
16
|
-
# MC (Multiple Choice): Provides respondents with a list of options to choose from.
|
17
|
-
# DB (Descriptive Text or Information): Displays text or information without requiring a response.
|
18
|
-
# Matrix: A grid-style question where respondents can evaluate multiple items using the same set of response options.
|
19
|
-
|
20
|
-
|
21
|
-
def clean_html(raw_html):
|
22
|
-
# Unescape HTML entities
|
23
|
-
clean_text = html.unescape(raw_html)
|
24
|
-
# Remove HTML tags
|
25
|
-
clean_text = re.sub(r"<.*?>", "", clean_text)
|
26
|
-
# Replace non-breaking spaces with regular spaces
|
27
|
-
clean_text = clean_text.replace("\xa0", " ")
|
28
|
-
# Optionally, strip leading/trailing spaces
|
29
|
-
clean_text = clean_text.strip()
|
30
|
-
return clean_text
|
31
|
-
|
32
|
-
|
33
|
-
class QualtricsQuestion:
|
34
|
-
def __init__(self, question_json, debug=False):
|
35
|
-
self.debug = debug
|
36
|
-
self.question_json = question_json
|
37
|
-
if self.element != "SQ":
|
38
|
-
raise ValueError("Invalid question element type")
|
39
|
-
|
40
|
-
@property
|
41
|
-
def element(self):
|
42
|
-
return self.question_json["Element"]
|
43
|
-
|
44
|
-
@property
|
45
|
-
def selector(self):
|
46
|
-
return self.question_json.get("Selector", None)
|
47
|
-
|
48
|
-
@property
|
49
|
-
def question_name(self):
|
50
|
-
return self.question_json["PrimaryAttribute"]
|
51
|
-
|
52
|
-
@property
|
53
|
-
def question_text(self):
|
54
|
-
return clean_html(self.question_json["Payload"]["QuestionText"])
|
55
|
-
|
56
|
-
@property
|
57
|
-
def raw_question_type(self):
|
58
|
-
return self.question_json["Payload"]["QuestionType"]
|
59
|
-
|
60
|
-
@property
|
61
|
-
def question_type(self):
|
62
|
-
q_type = qualtrics_codes.get(self.raw_question_type, None)
|
63
|
-
if q_type is None:
|
64
|
-
print(f"Unknown question type: {self.raw_question_type}")
|
65
|
-
return None
|
66
|
-
return q_type
|
67
|
-
|
68
|
-
@property
|
69
|
-
def choices(self):
|
70
|
-
if "Choices" in self.question_json["Payload"]:
|
71
|
-
return [
|
72
|
-
choice["Display"]
|
73
|
-
for choice in self.question_json["Payload"]["Choices"].values()
|
74
|
-
]
|
75
|
-
return None
|
76
|
-
|
77
|
-
@property
|
78
|
-
def answers(self):
|
79
|
-
if "Answers" in self.question_json["Payload"]:
|
80
|
-
return [
|
81
|
-
choice["Display"]
|
82
|
-
for choice in self.question_json["Payload"]["Choices"].values()
|
83
|
-
]
|
84
|
-
return None
|
85
|
-
|
86
|
-
def to_edsl(self):
|
87
|
-
if self.question_type == "instruction":
|
88
|
-
from edsl import Instruction
|
89
|
-
|
90
|
-
return [Instruction(text=self.question_text, name=self.question_name)]
|
91
|
-
|
92
|
-
if self.question_type == "free_text":
|
93
|
-
try:
|
94
|
-
q = Question(
|
95
|
-
**{
|
96
|
-
"question_type": self.question_type,
|
97
|
-
"question_text": self.question_text,
|
98
|
-
"question_name": self.question_name,
|
99
|
-
}
|
100
|
-
)
|
101
|
-
return [q]
|
102
|
-
except Exception as e:
|
103
|
-
return []
|
104
|
-
|
105
|
-
if self.question_type == "multiple_choice":
|
106
|
-
# Let's figure of it it's actually a checkbox question
|
107
|
-
if self.selector == "MAVR" or self.selector == "MULTIPLE":
|
108
|
-
try:
|
109
|
-
q = Question(
|
110
|
-
**{
|
111
|
-
"question_type": "checkbox",
|
112
|
-
"question_text": self.question_text,
|
113
|
-
"question_name": self.question_name,
|
114
|
-
"question_options": self.choices,
|
115
|
-
}
|
116
|
-
)
|
117
|
-
return [q]
|
118
|
-
except Exception as e:
|
119
|
-
return []
|
120
|
-
|
121
|
-
# maybe it's a linear scale!
|
122
|
-
if "<br>" in self.choices[0]:
|
123
|
-
option_labels = {}
|
124
|
-
question_options = []
|
125
|
-
for choice in self.choices:
|
126
|
-
if "<br>" in choice:
|
127
|
-
option_label, question_option = choice.split("<br>")
|
128
|
-
option_labels[int(question_option)] = option_label
|
129
|
-
question_options.append(int(question_option))
|
130
|
-
else:
|
131
|
-
question_options.append(int(choice))
|
132
|
-
try:
|
133
|
-
q = Question(
|
134
|
-
**{
|
135
|
-
"question_type": "linear_scale",
|
136
|
-
"question_text": self.question_text,
|
137
|
-
"question_name": self.question_name,
|
138
|
-
"question_options": question_options,
|
139
|
-
"option_labels": option_labels,
|
140
|
-
}
|
141
|
-
)
|
142
|
-
return [q]
|
143
|
-
except Exception as e:
|
144
|
-
if self.debug:
|
145
|
-
raise e
|
146
|
-
else:
|
147
|
-
print(e)
|
148
|
-
return []
|
149
|
-
|
150
|
-
try:
|
151
|
-
q = Question(
|
152
|
-
**{
|
153
|
-
"question_type": self.question_type,
|
154
|
-
"question_text": self.question_text,
|
155
|
-
"question_name": self.question_name,
|
156
|
-
"question_options": self.choices,
|
157
|
-
}
|
158
|
-
)
|
159
|
-
return [q]
|
160
|
-
except Exception as e:
|
161
|
-
return []
|
162
|
-
|
163
|
-
if self.question_type == "matrix":
|
164
|
-
questions = []
|
165
|
-
for index, choice in enumerate(self.choices):
|
166
|
-
try:
|
167
|
-
q = Question(
|
168
|
-
**{
|
169
|
-
"question_type": "multiple_choice",
|
170
|
-
"question_text": self.question_text + f" ({choice})",
|
171
|
-
"question_name": self.question_name + f"_{index}",
|
172
|
-
"question_options": self.answers,
|
173
|
-
}
|
174
|
-
)
|
175
|
-
questions.append(q)
|
176
|
-
except Exception as e:
|
177
|
-
continue
|
178
|
-
|
179
|
-
return questions
|
180
|
-
|
181
|
-
raise ValueError(f"Invalid question type: {self.question_type}")
|
182
|
-
|
183
|
-
|
184
|
-
class SurveyQualtricsImport:
|
185
|
-
def __init__(self, qsf_file_name: str):
|
186
|
-
self.qsf_file_name = qsf_file_name
|
187
|
-
self.question_data = self.extract_questions_from_json()
|
188
|
-
|
189
|
-
def create_survey(self):
|
190
|
-
questions = []
|
191
|
-
for qualtrics_questions in self.question_data:
|
192
|
-
questions.extend(qualtrics_questions.to_edsl())
|
193
|
-
return Survey(questions)
|
194
|
-
|
195
|
-
@property
|
196
|
-
def survey_data(self):
|
197
|
-
with open(self.qsf_file_name, "r") as f:
|
198
|
-
survey_data = json.load(f)
|
199
|
-
return survey_data
|
200
|
-
|
201
|
-
def extract_questions_from_json(self):
|
202
|
-
questions = self.survey_data["SurveyElements"]
|
203
|
-
|
204
|
-
extracted_questions = []
|
205
|
-
|
206
|
-
for question in questions:
|
207
|
-
if question["Element"] == "SQ":
|
208
|
-
extracted_questions.append(QualtricsQuestion(question))
|
209
|
-
|
210
|
-
return extracted_questions
|
211
|
-
|
212
|
-
def extract_blocks_from_json(self):
|
213
|
-
blocks = []
|
214
|
-
|
215
|
-
for element in self.survey_data["SurveyElements"]:
|
216
|
-
if element["Element"] == "BL":
|
217
|
-
for block_payload in element["Payload"]:
|
218
|
-
block_elements = [
|
219
|
-
BlockElement(be["Type"], be["QuestionID"])
|
220
|
-
for be in block_payload["BlockElements"]
|
221
|
-
]
|
222
|
-
options_data = block_payload.get("Options", {})
|
223
|
-
options = BlockOptions(
|
224
|
-
options_data.get("BlockLocking", "false"),
|
225
|
-
options_data.get("RandomizeQuestions", "false"),
|
226
|
-
options_data.get("BlockVisibility", "Collapsed"),
|
227
|
-
)
|
228
|
-
|
229
|
-
block = SurveyBlock(
|
230
|
-
block_payload["Type"],
|
231
|
-
block_payload["Description"],
|
232
|
-
block_payload["ID"],
|
233
|
-
block_elements,
|
234
|
-
options,
|
235
|
-
)
|
236
|
-
blocks.append(block)
|
237
|
-
|
238
|
-
return blocks
|
239
|
-
|
240
|
-
|
241
|
-
class SurveyBlock:
|
242
|
-
def __init__(self, block_type, description, block_id, block_elements, options):
|
243
|
-
self.block_type = block_type
|
244
|
-
self.description = description
|
245
|
-
self.block_id = block_id
|
246
|
-
self.block_elements = block_elements
|
247
|
-
self.options = options
|
248
|
-
|
249
|
-
def __repr__(self):
|
250
|
-
return f"SurveyBlock(type={self.block_type}, description={self.description}, id={self.block_id})"
|
251
|
-
|
252
|
-
|
253
|
-
class BlockElement:
|
254
|
-
def __init__(self, element_type, question_id):
|
255
|
-
self.element_type = element_type
|
256
|
-
self.question_id = question_id
|
257
|
-
|
258
|
-
def __repr__(self):
|
259
|
-
return f"BlockElement(type={self.element_type}, question_id={self.question_id})"
|
260
|
-
|
261
|
-
|
262
|
-
class BlockOptions:
|
263
|
-
def __init__(self, block_locking, randomize_questions, block_visibility):
|
264
|
-
self.block_locking = block_locking
|
265
|
-
self.randomize_questions = randomize_questions
|
266
|
-
self.block_visibility = block_visibility
|
267
|
-
|
268
|
-
def __repr__(self):
|
269
|
-
return (
|
270
|
-
f"BlockOptions(block_locking={self.block_locking}, "
|
271
|
-
f"randomize_questions={self.randomize_questions}, "
|
272
|
-
f"block_visibility={self.block_visibility})"
|
273
|
-
)
|
274
|
-
|
275
|
-
|
276
|
-
if __name__ == "__main__":
|
277
|
-
survey_creator = SurveyQualtricsImport("example.qsf")
|
278
|
-
# print(survey_creator.question_data)
|
279
|
-
# survey = survey_creator.create_survey()
|
280
|
-
# info = survey.push()
|
281
|
-
# print(info)
|
282
|
-
# questions = survey.extract_questions_from_json()
|
283
|
-
# for question in questions:
|
284
|
-
# print(question)
|
1
|
+
import json
|
2
|
+
import html
|
3
|
+
import re
|
4
|
+
|
5
|
+
from edsl import Question
|
6
|
+
from edsl import Survey
|
7
|
+
|
8
|
+
qualtrics_codes = {
|
9
|
+
"TE": "free_text",
|
10
|
+
"MC": "multiple_choice",
|
11
|
+
"Matrix": "matrix",
|
12
|
+
"DB": "instruction", # not quite right, but for now
|
13
|
+
"Timing": "free_text", # not quite right, but for now
|
14
|
+
}
|
15
|
+
# TE (Text Entry): Allows respondents to input a text response.
|
16
|
+
# MC (Multiple Choice): Provides respondents with a list of options to choose from.
|
17
|
+
# DB (Descriptive Text or Information): Displays text or information without requiring a response.
|
18
|
+
# Matrix: A grid-style question where respondents can evaluate multiple items using the same set of response options.
|
19
|
+
|
20
|
+
|
21
|
+
def clean_html(raw_html):
|
22
|
+
# Unescape HTML entities
|
23
|
+
clean_text = html.unescape(raw_html)
|
24
|
+
# Remove HTML tags
|
25
|
+
clean_text = re.sub(r"<.*?>", "", clean_text)
|
26
|
+
# Replace non-breaking spaces with regular spaces
|
27
|
+
clean_text = clean_text.replace("\xa0", " ")
|
28
|
+
# Optionally, strip leading/trailing spaces
|
29
|
+
clean_text = clean_text.strip()
|
30
|
+
return clean_text
|
31
|
+
|
32
|
+
|
33
|
+
class QualtricsQuestion:
|
34
|
+
def __init__(self, question_json, debug=False):
|
35
|
+
self.debug = debug
|
36
|
+
self.question_json = question_json
|
37
|
+
if self.element != "SQ":
|
38
|
+
raise ValueError("Invalid question element type")
|
39
|
+
|
40
|
+
@property
|
41
|
+
def element(self):
|
42
|
+
return self.question_json["Element"]
|
43
|
+
|
44
|
+
@property
|
45
|
+
def selector(self):
|
46
|
+
return self.question_json.get("Selector", None)
|
47
|
+
|
48
|
+
@property
|
49
|
+
def question_name(self):
|
50
|
+
return self.question_json["PrimaryAttribute"]
|
51
|
+
|
52
|
+
@property
|
53
|
+
def question_text(self):
|
54
|
+
return clean_html(self.question_json["Payload"]["QuestionText"])
|
55
|
+
|
56
|
+
@property
|
57
|
+
def raw_question_type(self):
|
58
|
+
return self.question_json["Payload"]["QuestionType"]
|
59
|
+
|
60
|
+
@property
|
61
|
+
def question_type(self):
|
62
|
+
q_type = qualtrics_codes.get(self.raw_question_type, None)
|
63
|
+
if q_type is None:
|
64
|
+
print(f"Unknown question type: {self.raw_question_type}")
|
65
|
+
return None
|
66
|
+
return q_type
|
67
|
+
|
68
|
+
@property
|
69
|
+
def choices(self):
|
70
|
+
if "Choices" in self.question_json["Payload"]:
|
71
|
+
return [
|
72
|
+
choice["Display"]
|
73
|
+
for choice in self.question_json["Payload"]["Choices"].values()
|
74
|
+
]
|
75
|
+
return None
|
76
|
+
|
77
|
+
@property
|
78
|
+
def answers(self):
|
79
|
+
if "Answers" in self.question_json["Payload"]:
|
80
|
+
return [
|
81
|
+
choice["Display"]
|
82
|
+
for choice in self.question_json["Payload"]["Choices"].values()
|
83
|
+
]
|
84
|
+
return None
|
85
|
+
|
86
|
+
def to_edsl(self):
|
87
|
+
if self.question_type == "instruction":
|
88
|
+
from edsl import Instruction
|
89
|
+
|
90
|
+
return [Instruction(text=self.question_text, name=self.question_name)]
|
91
|
+
|
92
|
+
if self.question_type == "free_text":
|
93
|
+
try:
|
94
|
+
q = Question(
|
95
|
+
**{
|
96
|
+
"question_type": self.question_type,
|
97
|
+
"question_text": self.question_text,
|
98
|
+
"question_name": self.question_name,
|
99
|
+
}
|
100
|
+
)
|
101
|
+
return [q]
|
102
|
+
except Exception as e:
|
103
|
+
return []
|
104
|
+
|
105
|
+
if self.question_type == "multiple_choice":
|
106
|
+
# Let's figure of it it's actually a checkbox question
|
107
|
+
if self.selector == "MAVR" or self.selector == "MULTIPLE":
|
108
|
+
try:
|
109
|
+
q = Question(
|
110
|
+
**{
|
111
|
+
"question_type": "checkbox",
|
112
|
+
"question_text": self.question_text,
|
113
|
+
"question_name": self.question_name,
|
114
|
+
"question_options": self.choices,
|
115
|
+
}
|
116
|
+
)
|
117
|
+
return [q]
|
118
|
+
except Exception as e:
|
119
|
+
return []
|
120
|
+
|
121
|
+
# maybe it's a linear scale!
|
122
|
+
if "<br>" in self.choices[0]:
|
123
|
+
option_labels = {}
|
124
|
+
question_options = []
|
125
|
+
for choice in self.choices:
|
126
|
+
if "<br>" in choice:
|
127
|
+
option_label, question_option = choice.split("<br>")
|
128
|
+
option_labels[int(question_option)] = option_label
|
129
|
+
question_options.append(int(question_option))
|
130
|
+
else:
|
131
|
+
question_options.append(int(choice))
|
132
|
+
try:
|
133
|
+
q = Question(
|
134
|
+
**{
|
135
|
+
"question_type": "linear_scale",
|
136
|
+
"question_text": self.question_text,
|
137
|
+
"question_name": self.question_name,
|
138
|
+
"question_options": question_options,
|
139
|
+
"option_labels": option_labels,
|
140
|
+
}
|
141
|
+
)
|
142
|
+
return [q]
|
143
|
+
except Exception as e:
|
144
|
+
if self.debug:
|
145
|
+
raise e
|
146
|
+
else:
|
147
|
+
print(e)
|
148
|
+
return []
|
149
|
+
|
150
|
+
try:
|
151
|
+
q = Question(
|
152
|
+
**{
|
153
|
+
"question_type": self.question_type,
|
154
|
+
"question_text": self.question_text,
|
155
|
+
"question_name": self.question_name,
|
156
|
+
"question_options": self.choices,
|
157
|
+
}
|
158
|
+
)
|
159
|
+
return [q]
|
160
|
+
except Exception as e:
|
161
|
+
return []
|
162
|
+
|
163
|
+
if self.question_type == "matrix":
|
164
|
+
questions = []
|
165
|
+
for index, choice in enumerate(self.choices):
|
166
|
+
try:
|
167
|
+
q = Question(
|
168
|
+
**{
|
169
|
+
"question_type": "multiple_choice",
|
170
|
+
"question_text": self.question_text + f" ({choice})",
|
171
|
+
"question_name": self.question_name + f"_{index}",
|
172
|
+
"question_options": self.answers,
|
173
|
+
}
|
174
|
+
)
|
175
|
+
questions.append(q)
|
176
|
+
except Exception as e:
|
177
|
+
continue
|
178
|
+
|
179
|
+
return questions
|
180
|
+
|
181
|
+
raise ValueError(f"Invalid question type: {self.question_type}")
|
182
|
+
|
183
|
+
|
184
|
+
class SurveyQualtricsImport:
|
185
|
+
def __init__(self, qsf_file_name: str):
|
186
|
+
self.qsf_file_name = qsf_file_name
|
187
|
+
self.question_data = self.extract_questions_from_json()
|
188
|
+
|
189
|
+
def create_survey(self):
|
190
|
+
questions = []
|
191
|
+
for qualtrics_questions in self.question_data:
|
192
|
+
questions.extend(qualtrics_questions.to_edsl())
|
193
|
+
return Survey(questions)
|
194
|
+
|
195
|
+
@property
|
196
|
+
def survey_data(self):
|
197
|
+
with open(self.qsf_file_name, "r") as f:
|
198
|
+
survey_data = json.load(f)
|
199
|
+
return survey_data
|
200
|
+
|
201
|
+
def extract_questions_from_json(self):
|
202
|
+
questions = self.survey_data["SurveyElements"]
|
203
|
+
|
204
|
+
extracted_questions = []
|
205
|
+
|
206
|
+
for question in questions:
|
207
|
+
if question["Element"] == "SQ":
|
208
|
+
extracted_questions.append(QualtricsQuestion(question))
|
209
|
+
|
210
|
+
return extracted_questions
|
211
|
+
|
212
|
+
def extract_blocks_from_json(self):
|
213
|
+
blocks = []
|
214
|
+
|
215
|
+
for element in self.survey_data["SurveyElements"]:
|
216
|
+
if element["Element"] == "BL":
|
217
|
+
for block_payload in element["Payload"]:
|
218
|
+
block_elements = [
|
219
|
+
BlockElement(be["Type"], be["QuestionID"])
|
220
|
+
for be in block_payload["BlockElements"]
|
221
|
+
]
|
222
|
+
options_data = block_payload.get("Options", {})
|
223
|
+
options = BlockOptions(
|
224
|
+
options_data.get("BlockLocking", "false"),
|
225
|
+
options_data.get("RandomizeQuestions", "false"),
|
226
|
+
options_data.get("BlockVisibility", "Collapsed"),
|
227
|
+
)
|
228
|
+
|
229
|
+
block = SurveyBlock(
|
230
|
+
block_payload["Type"],
|
231
|
+
block_payload["Description"],
|
232
|
+
block_payload["ID"],
|
233
|
+
block_elements,
|
234
|
+
options,
|
235
|
+
)
|
236
|
+
blocks.append(block)
|
237
|
+
|
238
|
+
return blocks
|
239
|
+
|
240
|
+
|
241
|
+
class SurveyBlock:
|
242
|
+
def __init__(self, block_type, description, block_id, block_elements, options):
|
243
|
+
self.block_type = block_type
|
244
|
+
self.description = description
|
245
|
+
self.block_id = block_id
|
246
|
+
self.block_elements = block_elements
|
247
|
+
self.options = options
|
248
|
+
|
249
|
+
def __repr__(self):
|
250
|
+
return f"SurveyBlock(type={self.block_type}, description={self.description}, id={self.block_id})"
|
251
|
+
|
252
|
+
|
253
|
+
class BlockElement:
|
254
|
+
def __init__(self, element_type, question_id):
|
255
|
+
self.element_type = element_type
|
256
|
+
self.question_id = question_id
|
257
|
+
|
258
|
+
def __repr__(self):
|
259
|
+
return f"BlockElement(type={self.element_type}, question_id={self.question_id})"
|
260
|
+
|
261
|
+
|
262
|
+
class BlockOptions:
|
263
|
+
def __init__(self, block_locking, randomize_questions, block_visibility):
|
264
|
+
self.block_locking = block_locking
|
265
|
+
self.randomize_questions = randomize_questions
|
266
|
+
self.block_visibility = block_visibility
|
267
|
+
|
268
|
+
def __repr__(self):
|
269
|
+
return (
|
270
|
+
f"BlockOptions(block_locking={self.block_locking}, "
|
271
|
+
f"randomize_questions={self.randomize_questions}, "
|
272
|
+
f"block_visibility={self.block_visibility})"
|
273
|
+
)
|
274
|
+
|
275
|
+
|
276
|
+
if __name__ == "__main__":
|
277
|
+
survey_creator = SurveyQualtricsImport("example.qsf")
|
278
|
+
# print(survey_creator.question_data)
|
279
|
+
# survey = survey_creator.create_survey()
|
280
|
+
# info = survey.push()
|
281
|
+
# print(info)
|
282
|
+
# questions = survey.extract_questions_from_json()
|
283
|
+
# for question in questions:
|
284
|
+
# print(question)
|