edsl 0.1.39__py3-none-any.whl → 0.1.39.dev1__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.
Files changed (212) hide show
  1. edsl/Base.py +116 -197
  2. edsl/__init__.py +7 -15
  3. edsl/__version__.py +1 -1
  4. edsl/agents/Agent.py +147 -351
  5. edsl/agents/AgentList.py +73 -211
  6. edsl/agents/Invigilator.py +50 -101
  7. edsl/agents/InvigilatorBase.py +70 -62
  8. edsl/agents/PromptConstructor.py +225 -143
  9. edsl/agents/__init__.py +1 -0
  10. edsl/agents/prompt_helpers.py +3 -3
  11. edsl/auto/AutoStudy.py +5 -18
  12. edsl/auto/StageBase.py +40 -53
  13. edsl/auto/StageQuestions.py +1 -2
  14. edsl/auto/utilities.py +6 -0
  15. edsl/config.py +2 -22
  16. edsl/conversation/car_buying.py +1 -2
  17. edsl/coop/PriceFetcher.py +1 -1
  18. edsl/coop/coop.py +47 -125
  19. edsl/coop/utils.py +14 -14
  20. edsl/data/Cache.py +27 -45
  21. edsl/data/CacheEntry.py +15 -12
  22. edsl/data/CacheHandler.py +12 -31
  23. edsl/data/RemoteCacheSync.py +46 -154
  24. edsl/data/__init__.py +3 -4
  25. edsl/data_transfer_models.py +1 -2
  26. edsl/enums.py +0 -27
  27. edsl/exceptions/__init__.py +50 -50
  28. edsl/exceptions/agents.py +0 -12
  29. edsl/exceptions/questions.py +6 -24
  30. edsl/exceptions/scenarios.py +0 -7
  31. edsl/inference_services/AnthropicService.py +19 -38
  32. edsl/inference_services/AwsBedrock.py +2 -0
  33. edsl/inference_services/AzureAI.py +2 -0
  34. edsl/inference_services/GoogleService.py +12 -7
  35. edsl/inference_services/InferenceServiceABC.py +85 -18
  36. edsl/inference_services/InferenceServicesCollection.py +79 -120
  37. edsl/inference_services/MistralAIService.py +3 -0
  38. edsl/inference_services/OpenAIService.py +35 -47
  39. edsl/inference_services/PerplexityService.py +3 -0
  40. edsl/inference_services/TestService.py +10 -11
  41. edsl/inference_services/TogetherAIService.py +3 -5
  42. edsl/jobs/Answers.py +14 -1
  43. edsl/jobs/Jobs.py +431 -356
  44. edsl/jobs/JobsChecks.py +10 -35
  45. edsl/jobs/JobsPrompts.py +4 -6
  46. edsl/jobs/JobsRemoteInferenceHandler.py +133 -205
  47. edsl/jobs/buckets/BucketCollection.py +3 -44
  48. edsl/jobs/buckets/TokenBucket.py +21 -53
  49. edsl/jobs/interviews/Interview.py +408 -143
  50. edsl/jobs/runners/JobsRunnerAsyncio.py +403 -88
  51. edsl/jobs/runners/JobsRunnerStatus.py +165 -133
  52. edsl/jobs/tasks/QuestionTaskCreator.py +19 -21
  53. edsl/jobs/tasks/TaskHistory.py +18 -38
  54. edsl/jobs/tasks/task_status_enum.py +2 -0
  55. edsl/language_models/KeyLookup.py +30 -0
  56. edsl/language_models/LanguageModel.py +236 -194
  57. edsl/language_models/ModelList.py +19 -28
  58. edsl/language_models/__init__.py +2 -1
  59. edsl/language_models/registry.py +190 -0
  60. edsl/language_models/repair.py +2 -2
  61. edsl/language_models/unused/ReplicateBase.py +83 -0
  62. edsl/language_models/utilities.py +4 -5
  63. edsl/notebooks/Notebook.py +14 -19
  64. edsl/prompts/Prompt.py +39 -29
  65. edsl/questions/{answer_validator_mixin.py → AnswerValidatorMixin.py} +2 -47
  66. edsl/questions/QuestionBase.py +214 -68
  67. edsl/questions/{question_base_gen_mixin.py → QuestionBaseGenMixin.py} +50 -57
  68. edsl/questions/QuestionBasePromptsMixin.py +3 -7
  69. edsl/questions/QuestionBudget.py +1 -1
  70. edsl/questions/QuestionCheckBox.py +3 -3
  71. edsl/questions/QuestionExtract.py +7 -5
  72. edsl/questions/QuestionFreeText.py +3 -2
  73. edsl/questions/QuestionList.py +18 -10
  74. edsl/questions/QuestionMultipleChoice.py +23 -67
  75. edsl/questions/QuestionNumerical.py +4 -2
  76. edsl/questions/QuestionRank.py +17 -7
  77. edsl/questions/{response_validator_abc.py → ResponseValidatorABC.py} +26 -40
  78. edsl/questions/SimpleAskMixin.py +3 -4
  79. edsl/questions/__init__.py +1 -2
  80. edsl/questions/derived/QuestionLinearScale.py +3 -6
  81. edsl/questions/derived/QuestionTopK.py +1 -1
  82. edsl/questions/descriptors.py +3 -17
  83. edsl/questions/question_registry.py +1 -1
  84. edsl/results/CSSParameterizer.py +1 -1
  85. edsl/results/Dataset.py +7 -170
  86. edsl/results/DatasetExportMixin.py +305 -168
  87. edsl/results/DatasetTree.py +8 -28
  88. edsl/results/Result.py +206 -298
  89. edsl/results/Results.py +131 -149
  90. edsl/results/ResultsDBMixin.py +238 -0
  91. edsl/results/ResultsExportMixin.py +0 -2
  92. edsl/results/{results_selector.py → Selector.py} +13 -23
  93. edsl/results/TableDisplay.py +171 -98
  94. edsl/results/__init__.py +1 -1
  95. edsl/scenarios/FileStore.py +239 -150
  96. edsl/scenarios/Scenario.py +193 -90
  97. edsl/scenarios/ScenarioHtmlMixin.py +3 -4
  98. edsl/scenarios/{scenario_join.py → ScenarioJoin.py} +6 -10
  99. edsl/scenarios/ScenarioList.py +244 -415
  100. edsl/scenarios/ScenarioListExportMixin.py +7 -0
  101. edsl/scenarios/ScenarioListPdfMixin.py +37 -15
  102. edsl/scenarios/__init__.py +2 -1
  103. edsl/study/ObjectEntry.py +1 -1
  104. edsl/study/SnapShot.py +1 -1
  105. edsl/study/Study.py +12 -5
  106. edsl/surveys/Rule.py +4 -5
  107. edsl/surveys/RuleCollection.py +27 -25
  108. edsl/surveys/Survey.py +791 -270
  109. edsl/surveys/SurveyCSS.py +8 -20
  110. edsl/surveys/{SurveyFlowVisualization.py → SurveyFlowVisualizationMixin.py} +9 -11
  111. edsl/surveys/__init__.py +2 -4
  112. edsl/surveys/descriptors.py +2 -6
  113. edsl/surveys/instructions/ChangeInstruction.py +2 -1
  114. edsl/surveys/instructions/Instruction.py +13 -4
  115. edsl/surveys/instructions/InstructionCollection.py +6 -11
  116. edsl/templates/error_reporting/interview_details.html +1 -1
  117. edsl/templates/error_reporting/report.html +1 -1
  118. edsl/tools/plotting.py +1 -1
  119. edsl/utilities/utilities.py +23 -35
  120. {edsl-0.1.39.dist-info → edsl-0.1.39.dev1.dist-info}/METADATA +10 -12
  121. edsl-0.1.39.dev1.dist-info/RECORD +277 -0
  122. {edsl-0.1.39.dist-info → edsl-0.1.39.dev1.dist-info}/WHEEL +1 -1
  123. edsl/agents/QuestionInstructionPromptBuilder.py +0 -128
  124. edsl/agents/QuestionTemplateReplacementsBuilder.py +0 -137
  125. edsl/agents/question_option_processor.py +0 -172
  126. edsl/coop/CoopFunctionsMixin.py +0 -15
  127. edsl/coop/ExpectedParrotKeyHandler.py +0 -125
  128. edsl/exceptions/inference_services.py +0 -5
  129. edsl/inference_services/AvailableModelCacheHandler.py +0 -184
  130. edsl/inference_services/AvailableModelFetcher.py +0 -215
  131. edsl/inference_services/ServiceAvailability.py +0 -135
  132. edsl/inference_services/data_structures.py +0 -134
  133. edsl/jobs/AnswerQuestionFunctionConstructor.py +0 -223
  134. edsl/jobs/FetchInvigilator.py +0 -47
  135. edsl/jobs/InterviewTaskManager.py +0 -98
  136. edsl/jobs/InterviewsConstructor.py +0 -50
  137. edsl/jobs/JobsComponentConstructor.py +0 -189
  138. edsl/jobs/JobsRemoteInferenceLogger.py +0 -239
  139. edsl/jobs/RequestTokenEstimator.py +0 -30
  140. edsl/jobs/async_interview_runner.py +0 -138
  141. edsl/jobs/buckets/TokenBucketAPI.py +0 -211
  142. edsl/jobs/buckets/TokenBucketClient.py +0 -191
  143. edsl/jobs/check_survey_scenario_compatibility.py +0 -85
  144. edsl/jobs/data_structures.py +0 -120
  145. edsl/jobs/decorators.py +0 -35
  146. edsl/jobs/jobs_status_enums.py +0 -9
  147. edsl/jobs/loggers/HTMLTableJobLogger.py +0 -304
  148. edsl/jobs/results_exceptions_handler.py +0 -98
  149. edsl/language_models/ComputeCost.py +0 -63
  150. edsl/language_models/PriceManager.py +0 -127
  151. edsl/language_models/RawResponseHandler.py +0 -106
  152. edsl/language_models/ServiceDataSources.py +0 -0
  153. edsl/language_models/key_management/KeyLookup.py +0 -63
  154. edsl/language_models/key_management/KeyLookupBuilder.py +0 -273
  155. edsl/language_models/key_management/KeyLookupCollection.py +0 -38
  156. edsl/language_models/key_management/__init__.py +0 -0
  157. edsl/language_models/key_management/models.py +0 -131
  158. edsl/language_models/model.py +0 -256
  159. edsl/notebooks/NotebookToLaTeX.py +0 -142
  160. edsl/questions/ExceptionExplainer.py +0 -77
  161. edsl/questions/HTMLQuestion.py +0 -103
  162. edsl/questions/QuestionMatrix.py +0 -265
  163. edsl/questions/data_structures.py +0 -20
  164. edsl/questions/loop_processor.py +0 -149
  165. edsl/questions/response_validator_factory.py +0 -34
  166. edsl/questions/templates/matrix/__init__.py +0 -1
  167. edsl/questions/templates/matrix/answering_instructions.jinja +0 -5
  168. edsl/questions/templates/matrix/question_presentation.jinja +0 -20
  169. edsl/results/MarkdownToDocx.py +0 -122
  170. edsl/results/MarkdownToPDF.py +0 -111
  171. edsl/results/TextEditor.py +0 -50
  172. edsl/results/file_exports.py +0 -252
  173. edsl/results/smart_objects.py +0 -96
  174. edsl/results/table_data_class.py +0 -12
  175. edsl/results/table_renderers.py +0 -118
  176. edsl/scenarios/ConstructDownloadLink.py +0 -109
  177. edsl/scenarios/DocumentChunker.py +0 -102
  178. edsl/scenarios/DocxScenario.py +0 -16
  179. edsl/scenarios/PdfExtractor.py +0 -40
  180. edsl/scenarios/directory_scanner.py +0 -96
  181. edsl/scenarios/file_methods.py +0 -85
  182. edsl/scenarios/handlers/__init__.py +0 -13
  183. edsl/scenarios/handlers/csv.py +0 -49
  184. edsl/scenarios/handlers/docx.py +0 -76
  185. edsl/scenarios/handlers/html.py +0 -37
  186. edsl/scenarios/handlers/json.py +0 -111
  187. edsl/scenarios/handlers/latex.py +0 -5
  188. edsl/scenarios/handlers/md.py +0 -51
  189. edsl/scenarios/handlers/pdf.py +0 -68
  190. edsl/scenarios/handlers/png.py +0 -39
  191. edsl/scenarios/handlers/pptx.py +0 -105
  192. edsl/scenarios/handlers/py.py +0 -294
  193. edsl/scenarios/handlers/sql.py +0 -313
  194. edsl/scenarios/handlers/sqlite.py +0 -149
  195. edsl/scenarios/handlers/txt.py +0 -33
  196. edsl/scenarios/scenario_selector.py +0 -156
  197. edsl/surveys/ConstructDAG.py +0 -92
  198. edsl/surveys/EditSurvey.py +0 -221
  199. edsl/surveys/InstructionHandler.py +0 -100
  200. edsl/surveys/MemoryManagement.py +0 -72
  201. edsl/surveys/RuleManager.py +0 -172
  202. edsl/surveys/Simulator.py +0 -75
  203. edsl/surveys/SurveyToApp.py +0 -141
  204. edsl/utilities/PrettyList.py +0 -56
  205. edsl/utilities/is_notebook.py +0 -18
  206. edsl/utilities/is_valid_variable_name.py +0 -11
  207. edsl/utilities/remove_edsl_version.py +0 -24
  208. edsl-0.1.39.dist-info/RECORD +0 -358
  209. /edsl/questions/{register_questions_meta.py → RegisterQuestionsMeta.py} +0 -0
  210. /edsl/results/{results_fetch_mixin.py → ResultsFetchMixin.py} +0 -0
  211. /edsl/results/{results_tools_mixin.py → ResultsToolsMixin.py} +0 -0
  212. {edsl-0.1.39.dist-info → edsl-0.1.39.dev1.dist-info}/LICENSE +0 -0
@@ -1,100 +0,0 @@
1
- from typing import TYPE_CHECKING
2
- from dataclasses import dataclass
3
-
4
-
5
- @dataclass
6
- class SeparatedComponents:
7
- true_questions: list
8
- instruction_names_to_instructions: dict
9
- pseudo_indices: dict
10
-
11
-
12
- class InstructionHandler:
13
- def __init__(self, survey):
14
- self.survey = survey
15
-
16
- @staticmethod
17
- def separate_questions_and_instructions(questions_and_instructions: list) -> tuple:
18
- """
19
- The 'pseudo_indices' attribute is a dictionary that maps question names to pseudo-indices
20
- that are used to order questions and instructions in the survey.
21
- Only questions get real indices; instructions get pseudo-indices.
22
- However, the order of the pseudo-indices is the same as the order questions and instructions are added to the survey.
23
-
24
- We don't have to know how many instructions there are to calculate the pseudo-indices because they are
25
- calculated by the inverse of one minus the sum of 1/2^n for n in the number of instructions run so far.
26
-
27
- >>> from edsl import Survey
28
- >>> from edsl import Instruction
29
- >>> i = Instruction(text = "Pay attention to the following questions.", name = "intro")
30
- >>> i2 = Instruction(text = "How are you feeling today?", name = "followon_intro")
31
- >>> from edsl import QuestionFreeText; q1 = QuestionFreeText.example()
32
- >>> from edsl import QuestionMultipleChoice; q2 = QuestionMultipleChoice.example()
33
- >>> s = Survey([q1, i, i2, q2])
34
- >>> len(s._instruction_names_to_instructions)
35
- 2
36
- >>> s._pseudo_indices
37
- {'how_are_you': 0, 'intro': 0.5, 'followon_intro': 0.75, 'how_feeling': 1}
38
-
39
- >>> from edsl import ChangeInstruction
40
- >>> q3 = QuestionFreeText(question_text = "What is your favorite color?", question_name = "color")
41
- >>> i_change = ChangeInstruction(drop = ["intro"])
42
- >>> s = Survey([q1, i, q2, i_change, q3])
43
- >>> [i.name for i in s._relevant_instructions(q1)]
44
- []
45
- >>> [i.name for i in s._relevant_instructions(q2)]
46
- ['intro']
47
- >>> [i.name for i in s._relevant_instructions(q3)]
48
- []
49
-
50
- >>> i_change = ChangeInstruction(keep = ["poop"], drop = [])
51
- >>> s = Survey([q1, i, q2, i_change])
52
- Traceback (most recent call last):
53
- ...
54
- ValueError: ChangeInstruction change_instruction_0 references instruction poop which does not exist.
55
- """
56
- from edsl.surveys.instructions.Instruction import Instruction
57
- from edsl.surveys.instructions.ChangeInstruction import ChangeInstruction
58
- from edsl.questions.QuestionBase import QuestionBase
59
-
60
- true_questions = []
61
- instruction_names_to_instructions = {}
62
-
63
- num_change_instructions = 0
64
- pseudo_indices = {}
65
- instructions_run_length = 0
66
- for entry in questions_and_instructions:
67
- if isinstance(entry, Instruction) or isinstance(entry, ChangeInstruction):
68
- if isinstance(entry, ChangeInstruction):
69
- entry.add_name(num_change_instructions)
70
- num_change_instructions += 1
71
- for prior_instruction in entry.keep + entry.drop:
72
- if prior_instruction not in instruction_names_to_instructions:
73
- raise ValueError(
74
- f"ChangeInstruction {entry.name} references instruction {prior_instruction} which does not exist."
75
- )
76
- instructions_run_length += 1
77
- delta = 1 - 1.0 / (2.0**instructions_run_length)
78
- pseudo_index = (len(true_questions) - 1) + delta
79
- entry.pseudo_index = pseudo_index
80
- instruction_names_to_instructions[entry.name] = entry
81
- elif isinstance(entry, QuestionBase):
82
- pseudo_index = len(true_questions)
83
- instructions_run_length = 0
84
- true_questions.append(entry)
85
- else:
86
- raise ValueError(
87
- f"Entry {repr(entry)} is not a QuestionBase or an Instruction."
88
- )
89
-
90
- pseudo_indices[entry.name] = pseudo_index
91
-
92
- return SeparatedComponents(
93
- true_questions, instruction_names_to_instructions, pseudo_indices
94
- )
95
-
96
-
97
- if __name__ == "__main__":
98
- import doctest
99
-
100
- doctest.testmod()
@@ -1,72 +0,0 @@
1
- from __future__ import annotations
2
- from typing import Callable, Union, List, TYPE_CHECKING
3
-
4
- if TYPE_CHECKING:
5
- from edsl.questions.QuestionBase import QuestionBase
6
-
7
-
8
- class MemoryManagement:
9
- def __init__(self, survey):
10
- self.survey = survey
11
-
12
- def _set_memory_plan(self, prior_questions_func: Callable) -> None:
13
- """Set memory plan based on a provided function determining prior questions.
14
- :param prior_questions_func: A function that takes an index and returns a list of prior questions.
15
- """
16
- for i, question_name in enumerate(self.survey.question_names):
17
- self.survey.memory_plan.add_memory_collection(
18
- focal_question=question_name,
19
- prior_questions=prior_questions_func(i),
20
- )
21
-
22
- def add_targeted_memory(
23
- self,
24
- focal_question: Union[QuestionBase, str],
25
- prior_question: Union[QuestionBase, str],
26
- ) -> "Survey":
27
- """Add instructions to a survey than when answering focal_question.
28
-
29
- :param focal_question: The question that the agent is answering.
30
- :param prior_question: The question that the agent should remember when answering the focal question.
31
-
32
- Here we add instructions to a survey than when answering q2 they should remember q1:
33
- """
34
- focal_question_name = self.survey.question_names[
35
- self.survey._get_question_index(focal_question)
36
- ]
37
- prior_question_name = self.survey.question_names[
38
- self.survey._get_question_index(prior_question)
39
- ]
40
-
41
- self.survey.memory_plan.add_single_memory(
42
- focal_question=focal_question_name,
43
- prior_question=prior_question_name,
44
- )
45
-
46
- return self.survey
47
-
48
- def add_memory_collection(
49
- self,
50
- focal_question: Union[QuestionBase, str],
51
- prior_questions: List[Union[QuestionBase, str]],
52
- ) -> "Survey":
53
- """Add prior questions and responses so the agent has them when answering.
54
-
55
- This adds instructions to a survey than when answering focal_question, the agent should also remember the answers to prior_questions listed in prior_questions.
56
-
57
- :param focal_question: The question that the agent is answering.
58
- :param prior_questions: The questions that the agent should remember when answering the focal question.
59
- """
60
- focal_question_name = self.survey.question_names[
61
- self.survey._get_question_index(focal_question)
62
- ]
63
-
64
- prior_question_names = [
65
- self.survey.question_names[self.survey._get_question_index(prior_question)]
66
- for prior_question in prior_questions
67
- ]
68
-
69
- self.survey.memory_plan.add_memory_collection(
70
- focal_question=focal_question_name, prior_questions=prior_question_names
71
- )
72
- return self.survey
@@ -1,172 +0,0 @@
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
edsl/surveys/Simulator.py DELETED
@@ -1,75 +0,0 @@
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()
@@ -1,141 +0,0 @@
1
- from fastapi import FastAPI
2
- from pydantic import BaseModel, create_model
3
- from typing import Callable, Optional, Type, Dict, Any, List, Union
4
-
5
-
6
- class SurveyToApp:
7
- def __init__(self, survey):
8
- self.survey = survey
9
- self.app = FastAPI()
10
-
11
- def parameters(self):
12
- return self.survey.parameters
13
-
14
- def create_input(self) -> Type[BaseModel]:
15
- """
16
- Creates a Pydantic model based on the survey parameters.
17
- Returns:
18
- Type[BaseModel]: A dynamically created Pydantic model class
19
- """
20
- # Get parameters from survey - now calling the method
21
- params = self.parameters()
22
-
23
- # Create field definitions dictionary
24
- fields: Dict[str, Any] = {}
25
-
26
- # Since params is a set, we'll handle each parameter directly
27
- # Assuming each parameter in the set has the necessary attributes
28
- for param in params:
29
- # You might need to adjust these based on the actual parameter object structure
30
- param_name = getattr(param, "name", str(param))
31
- param_type = getattr(param, "type", "string")
32
- is_required = getattr(param, "required", True)
33
-
34
- # Map survey parameter types to Python types
35
- type_mapping = {
36
- "string": str,
37
- "integer": int,
38
- "float": float,
39
- "boolean": bool,
40
- "array": List,
41
- # Add more type mappings as needed
42
- }
43
-
44
- # Get the Python type from mapping
45
- python_type = type_mapping.get(param_type, str)
46
-
47
- if is_required:
48
- fields[param_name] = (python_type, ...)
49
- else:
50
- fields[param_name] = (Optional[python_type], None)
51
-
52
- # Add the template variable 'name' that's used in the question text
53
- fields["name"] = (str, ...)
54
-
55
- # Create and return the Pydantic model
56
- model_name = f"{self.survey.__class__.__name__}Model"
57
- return create_model(model_name, **fields)
58
-
59
- def create_route(self) -> Callable:
60
- """
61
- Creates a FastAPI route handler for the survey.
62
- Returns:
63
- Callable: A route handler function
64
- """
65
- input_model = self.create_input()
66
-
67
- async def route_handler(input_data: input_model):
68
- """
69
- Handles the API route by processing the input data through the survey.
70
- Args:
71
- input_data: The validated input data matching the created Pydantic model
72
- Returns:
73
- dict: The processed survey results
74
- """
75
- # Convert Pydantic model to dict
76
- data = input_data.dict()
77
- print(data)
78
- from edsl.scenarios.Scenario import Scenario
79
-
80
- # Process the data through the survey
81
- try:
82
- s = Scenario(data)
83
- results = self.survey.by(s).run()
84
- return {
85
- "status": "success",
86
- "data": results.select("answer.*").to_scenario_list().to_dict(),
87
- }
88
- except Exception as e:
89
- return {"status": "error", "message": str(e)}
90
-
91
- return route_handler
92
-
93
- def add_to_app(
94
- self, app: FastAPI, path: str = "/survey", methods: List[str] = ["POST", "GET"]
95
- ):
96
- """
97
- Adds the survey route to a FastAPI application.
98
- Args:
99
- app (FastAPI): The FastAPI application instance
100
- path (str): The API endpoint path
101
- methods (List[str]): HTTP methods to support
102
- """
103
- route_handler = self.create_route()
104
- input_model = self.create_input()
105
-
106
- app.add_api_route(
107
- path, route_handler, methods=methods, response_model=Dict[str, Any]
108
- )
109
-
110
- def create_app(self, path: str = "/survey", methods: List[str] = ["POST", "GET"]):
111
- """
112
- Creates a FastAPI application with the survey route.
113
- Args:
114
- path (str): The API endpoint path
115
- methods (List[str]): HTTP methods to support
116
- Returns:
117
- FastAPI: The FastAPI application instance
118
- """
119
- app = FastAPI()
120
- self.add_to_app(app, path=path, methods=methods)
121
- return app
122
-
123
-
124
- from edsl import QuestionFreeText, QuestionList
125
-
126
- # q = QuestionFreeText(
127
- # question_name="name_gender",
128
- # question_text="Is this customarily a boy's name or a girl's name: {{ name}}",
129
- # )
130
-
131
- q = QuestionList(
132
- question_name="examples",
133
- question_text="Give me {{ num }} examples of {{ thing }}",
134
- )
135
-
136
- survey_app = SurveyToApp(q.to_survey())
137
-
138
- if __name__ == "__main__":
139
- import uvicorn
140
-
141
- uvicorn.run(survey_app.create_app(path="/examples"), host="127.0.0.1", port=8000)
@@ -1,56 +0,0 @@
1
- from collections import UserList
2
- from edsl.results.Dataset import Dataset
3
-
4
-
5
- class PrettyList(UserList):
6
- def __init__(self, data=None, columns=None):
7
- super().__init__(data)
8
- self.columns = columns
9
-
10
- def _repr_html_(self):
11
- if isinstance(self[0], list) or isinstance(self[0], tuple):
12
- num_cols = len(self[0])
13
- else:
14
- num_cols = 1
15
-
16
- if self.columns:
17
- columns = self.columns
18
- else:
19
- columns = list(range(num_cols))
20
-
21
- d = {}
22
- for column in columns:
23
- d[column] = []
24
-
25
- for row in self:
26
- for index, column in enumerate(columns):
27
- if isinstance(row, list) or isinstance(row, tuple):
28
- d[column].append(row[index])
29
- else:
30
- d[column].append(row)
31
- # raise ValueError(d)
32
- return Dataset([{key: entry} for key, entry in d.items()])._repr_html_()
33
-
34
- if num_cols > 1:
35
- return (
36
- "<pre><table>"
37
- + "".join(["<th>" + str(column) + "</th>" for column in columns])
38
- + "".join(
39
- [
40
- "<tr>"
41
- + "".join(["<td>" + str(x) + "</td>" for x in row])
42
- + "</tr>"
43
- for row in self
44
- ]
45
- )
46
- + "</table></pre>"
47
- )
48
- else:
49
- return (
50
- "<pre><table>"
51
- + "".join(["<th>" + str(index) + "</th>" for index in columns])
52
- + "".join(
53
- ["<tr>" + "<td>" + str(row) + "</td>" + "</tr>" for row in self]
54
- )
55
- + "</table></pre>"
56
- )
@@ -1,18 +0,0 @@
1
- def is_notebook() -> bool:
2
- """Check if the code is running in a Jupyter notebook or Google Colab."""
3
- try:
4
- shell = get_ipython().__class__.__name__
5
- if shell == "ZMQInteractiveShell":
6
- return True # Jupyter notebook or qtconsole
7
- elif shell == "Shell": # Google Colab's shell class
8
- import sys
9
-
10
- if "google.colab" in sys.modules:
11
- return True # Running in Google Colab
12
- return False
13
- elif shell == "TerminalInteractiveShell":
14
- return False # Terminal running IPython
15
- else:
16
- return False # Other type
17
- except NameError:
18
- return False # Probably standard Python interpreter
@@ -1,11 +0,0 @@
1
- import keyword
2
-
3
-
4
- def is_valid_variable_name(name, allow_name=True):
5
- """Check if a string is a valid variable name."""
6
- if allow_name:
7
- return name.isidentifier() and not keyword.iskeyword(name)
8
- else:
9
- return (
10
- name.isidentifier() and not keyword.iskeyword(name) and not name == "name"
11
- )
@@ -1,24 +0,0 @@
1
- from functools import wraps
2
-
3
-
4
- def remove_edsl_version(func):
5
- """
6
- Decorator for the EDSL objects' `from_dict` method.
7
- - Removes the EDSL version and class name from the dictionary.
8
- - Ensures backwards compatibility with older versions of EDSL.
9
- """
10
-
11
- @wraps(func)
12
- def wrapper(cls, data, *args, **kwargs):
13
- data_copy = dict(data)
14
- edsl_version = data_copy.pop("edsl_version", None)
15
- edsl_classname = data_copy.pop("edsl_class_name", None)
16
-
17
- # Version- and class-specific logic here
18
- if edsl_classname == "Survey":
19
- if edsl_version is None or edsl_version <= "0.1.20":
20
- data_copy["question_groups"] = {}
21
-
22
- return func(cls, data_copy, *args, **kwargs)
23
-
24
- return wrapper