edsl 0.1.39.dev3__py3-none-any.whl → 0.1.39.dev5__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/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/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/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.dev5.dist-info}/LICENSE +21 -21
- {edsl-0.1.39.dev3.dist-info → edsl-0.1.39.dev5.dist-info}/METADATA +13 -11
- edsl-0.1.39.dev5.dist-info/RECORD +358 -0
- {edsl-0.1.39.dev3.dist-info → edsl-0.1.39.dev5.dist-info}/WHEEL +1 -1
- 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
@@ -0,0 +1,172 @@
|
|
1
|
+
from typing import Union, TYPE_CHECKING
|
2
|
+
|
3
|
+
if TYPE_CHECKING:
|
4
|
+
from edsl.questions.QuestionBase import QuestionBase
|
5
|
+
|
6
|
+
from edsl.surveys.Rule import Rule
|
7
|
+
from .base import RulePriority, EndOfSurvey
|
8
|
+
from edsl.exceptions.surveys import SurveyError, SurveyCreationError
|
9
|
+
|
10
|
+
|
11
|
+
class ValidatedString(str):
|
12
|
+
def __new__(cls, content):
|
13
|
+
if "<>" in content:
|
14
|
+
raise SurveyCreationError(
|
15
|
+
"The expression contains '<>', which is not allowed. You probably mean '!='."
|
16
|
+
)
|
17
|
+
return super().__new__(cls, content)
|
18
|
+
|
19
|
+
|
20
|
+
class RuleManager:
|
21
|
+
def __init__(self, survey):
|
22
|
+
self.survey = survey
|
23
|
+
|
24
|
+
def _get_question_index(
|
25
|
+
self, q: Union["QuestionBase", str, EndOfSurvey.__class__]
|
26
|
+
) -> Union[int, EndOfSurvey.__class__]:
|
27
|
+
"""Return the index of the question or EndOfSurvey object.
|
28
|
+
|
29
|
+
:param q: The question or question name to get the index of.
|
30
|
+
|
31
|
+
It can handle it if the user passes in the question name, the question object, or the EndOfSurvey object.
|
32
|
+
|
33
|
+
>>> from edsl.questions import QuestionFreeText
|
34
|
+
>>> from edsl import Survey
|
35
|
+
>>> s = Survey.example()
|
36
|
+
>>> s._get_question_index("q0")
|
37
|
+
0
|
38
|
+
|
39
|
+
This doesnt' work with questions that don't exist:
|
40
|
+
|
41
|
+
>>> s._get_question_index("poop")
|
42
|
+
Traceback (most recent call last):
|
43
|
+
...
|
44
|
+
edsl.exceptions.surveys.SurveyError: Question name poop not found in survey. The current question names are {'q0': 0, 'q1': 1, 'q2': 2}.
|
45
|
+
...
|
46
|
+
"""
|
47
|
+
if q == EndOfSurvey:
|
48
|
+
return EndOfSurvey
|
49
|
+
else:
|
50
|
+
question_name = q if isinstance(q, str) else q.question_name
|
51
|
+
if question_name not in self.survey.question_name_to_index:
|
52
|
+
raise SurveyError(
|
53
|
+
f"""Question name {question_name} not found in survey. The current question names are {self.survey.question_name_to_index}."""
|
54
|
+
)
|
55
|
+
return self.survey.question_name_to_index[question_name]
|
56
|
+
|
57
|
+
def _get_new_rule_priority(
|
58
|
+
self, question_index: int, before_rule: bool = False
|
59
|
+
) -> int:
|
60
|
+
"""Return the priority for the new rule.
|
61
|
+
|
62
|
+
:param question_index: The index of the question to add the rule to.
|
63
|
+
:param before_rule: Whether the rule is evaluated before the question is answered.
|
64
|
+
|
65
|
+
>>> from edsl import Survey
|
66
|
+
>>> s = Survey.example()
|
67
|
+
>>> RuleManager(s)._get_new_rule_priority(0)
|
68
|
+
1
|
69
|
+
"""
|
70
|
+
current_priorities = [
|
71
|
+
rule.priority
|
72
|
+
for rule in self.survey.rule_collection.applicable_rules(
|
73
|
+
question_index, before_rule
|
74
|
+
)
|
75
|
+
]
|
76
|
+
if len(current_priorities) == 0:
|
77
|
+
return RulePriority.DEFAULT.value + 1
|
78
|
+
|
79
|
+
max_priority = max(current_priorities)
|
80
|
+
# newer rules take priority over older rules
|
81
|
+
new_priority = (
|
82
|
+
RulePriority.DEFAULT.value
|
83
|
+
if len(current_priorities) == 0
|
84
|
+
else max_priority + 1
|
85
|
+
)
|
86
|
+
return new_priority
|
87
|
+
|
88
|
+
def add_rule(
|
89
|
+
self,
|
90
|
+
question: Union["QuestionBase", str],
|
91
|
+
expression: str,
|
92
|
+
next_question: Union["QuestionBase", str, int],
|
93
|
+
before_rule: bool = False,
|
94
|
+
) -> "Survey":
|
95
|
+
"""
|
96
|
+
Add a rule to a Question of the Survey with the appropriate priority.
|
97
|
+
|
98
|
+
:param question: The question to add the rule to.
|
99
|
+
:param expression: The expression to evaluate.
|
100
|
+
:param next_question: The next question to go to if the rule is true.
|
101
|
+
:param before_rule: Whether the rule is evaluated before the question is answered.
|
102
|
+
|
103
|
+
|
104
|
+
- The last rule added for the question will have the highest priority.
|
105
|
+
- If there are no rules, the rule added gets priority -1.
|
106
|
+
"""
|
107
|
+
question_index = self.survey._get_question_index(question) # Fix
|
108
|
+
|
109
|
+
# Might not have the name of the next question yet
|
110
|
+
if isinstance(next_question, int):
|
111
|
+
next_question_index = next_question
|
112
|
+
else:
|
113
|
+
next_question_index = self._get_question_index(next_question)
|
114
|
+
|
115
|
+
new_priority = self._get_new_rule_priority(question_index, before_rule) # fix
|
116
|
+
|
117
|
+
self.survey.rule_collection.add_rule(
|
118
|
+
Rule(
|
119
|
+
current_q=question_index,
|
120
|
+
expression=expression,
|
121
|
+
next_q=next_question_index,
|
122
|
+
question_name_to_index=self.survey.question_name_to_index,
|
123
|
+
priority=new_priority,
|
124
|
+
before_rule=before_rule,
|
125
|
+
)
|
126
|
+
)
|
127
|
+
|
128
|
+
return self.survey
|
129
|
+
|
130
|
+
def add_stop_rule(
|
131
|
+
self, question: Union["QuestionBase", str], expression: str
|
132
|
+
) -> "Survey":
|
133
|
+
"""Add a rule that stops the survey.
|
134
|
+
The rule is evaluated *after* the question is answered. If the rule is true, the survey ends.
|
135
|
+
|
136
|
+
:param question: The question to add the stop rule to.
|
137
|
+
:param expression: The expression to evaluate.
|
138
|
+
|
139
|
+
If this rule is true, the survey ends.
|
140
|
+
|
141
|
+
Here, answering "yes" to q0 ends the survey:
|
142
|
+
|
143
|
+
>>> from edsl import Survey
|
144
|
+
>>> s = Survey.example().add_stop_rule("q0", "q0 == 'yes'")
|
145
|
+
>>> s.next_question("q0", {"q0": "yes"})
|
146
|
+
EndOfSurvey
|
147
|
+
|
148
|
+
By comparison, answering "no" to q0 does not end the survey:
|
149
|
+
|
150
|
+
>>> s.next_question("q0", {"q0": "no"}).question_name
|
151
|
+
'q1'
|
152
|
+
|
153
|
+
>>> s.add_stop_rule("q0", "q1 <> 'yes'")
|
154
|
+
Traceback (most recent call last):
|
155
|
+
...
|
156
|
+
edsl.exceptions.surveys.SurveyCreationError: The expression contains '<>', which is not allowed. You probably mean '!='.
|
157
|
+
...
|
158
|
+
"""
|
159
|
+
expression = ValidatedString(expression)
|
160
|
+
prior_question_appears = False
|
161
|
+
for prior_question in self.survey.questions:
|
162
|
+
if prior_question.question_name in expression:
|
163
|
+
prior_question_appears = True
|
164
|
+
|
165
|
+
if not prior_question_appears:
|
166
|
+
import warnings
|
167
|
+
|
168
|
+
warnings.warn(
|
169
|
+
f"The expression {expression} does not contain any prior question names. This is probably a mistake."
|
170
|
+
)
|
171
|
+
self.survey.add_rule(question, expression, EndOfSurvey)
|
172
|
+
return self.survey
|
@@ -0,0 +1,75 @@
|
|
1
|
+
from typing import Callable
|
2
|
+
|
3
|
+
|
4
|
+
class Simulator:
|
5
|
+
def __init__(self, survey):
|
6
|
+
self.survey = survey
|
7
|
+
|
8
|
+
@classmethod
|
9
|
+
def random_survey(cls):
|
10
|
+
"""Create a random survey."""
|
11
|
+
from edsl.questions import QuestionMultipleChoice, QuestionFreeText
|
12
|
+
from random import choice
|
13
|
+
from edsl.surveys.Survey import Survey
|
14
|
+
|
15
|
+
num_questions = 10
|
16
|
+
questions = []
|
17
|
+
for i in range(num_questions):
|
18
|
+
if choice([True, False]):
|
19
|
+
q = QuestionMultipleChoice(
|
20
|
+
question_text="nothing",
|
21
|
+
question_name="q_" + str(i),
|
22
|
+
question_options=list(range(3)),
|
23
|
+
)
|
24
|
+
questions.append(q)
|
25
|
+
else:
|
26
|
+
questions.append(
|
27
|
+
QuestionFreeText(
|
28
|
+
question_text="nothing", question_name="q_" + str(i)
|
29
|
+
)
|
30
|
+
)
|
31
|
+
s = Survey(questions)
|
32
|
+
start_index = choice(range(num_questions - 1))
|
33
|
+
end_index = choice(range(start_index + 1, 10))
|
34
|
+
s = s.add_rule(f"q_{start_index}", "True", f"q_{end_index}")
|
35
|
+
question_to_delete = choice(range(num_questions))
|
36
|
+
s.delete_question(f"q_{question_to_delete}")
|
37
|
+
return s
|
38
|
+
|
39
|
+
def simulate(self) -> dict:
|
40
|
+
"""Simulate the survey and return the answers."""
|
41
|
+
i = self.survey.gen_path_through_survey()
|
42
|
+
q = next(i)
|
43
|
+
num_passes = 0
|
44
|
+
while True:
|
45
|
+
num_passes += 1
|
46
|
+
try:
|
47
|
+
answer = q._simulate_answer()
|
48
|
+
q = i.send({q.question_name: answer["answer"]})
|
49
|
+
except StopIteration:
|
50
|
+
break
|
51
|
+
|
52
|
+
if num_passes > 100:
|
53
|
+
print("Too many passes.")
|
54
|
+
raise Exception("Too many passes.")
|
55
|
+
return self.survey.answers
|
56
|
+
|
57
|
+
def create_agent(self) -> "Agent":
|
58
|
+
"""Create an agent from the simulated answers."""
|
59
|
+
answers_dict = self.survey.simulate()
|
60
|
+
from edsl.agents.Agent import Agent
|
61
|
+
|
62
|
+
def construct_answer_dict_function(traits: dict) -> Callable:
|
63
|
+
def func(self, question: "QuestionBase", scenario=None):
|
64
|
+
return traits.get(question.question_name, None)
|
65
|
+
|
66
|
+
return func
|
67
|
+
|
68
|
+
return Agent(traits=answers_dict).add_direct_question_answering_method(
|
69
|
+
construct_answer_dict_function(answers_dict)
|
70
|
+
)
|
71
|
+
|
72
|
+
def simulate_results(self) -> "Results":
|
73
|
+
"""Simulate the survey and return the results."""
|
74
|
+
a = self.create_agent()
|
75
|
+
return self.survey.by([a]).run()
|