edsl 0.1.15__py3-none-any.whl → 0.1.40__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 (407) hide show
  1. edsl/Base.py +348 -38
  2. edsl/BaseDiff.py +260 -0
  3. edsl/TemplateLoader.py +24 -0
  4. edsl/__init__.py +45 -10
  5. edsl/__version__.py +1 -1
  6. edsl/agents/Agent.py +842 -144
  7. edsl/agents/AgentList.py +521 -25
  8. edsl/agents/Invigilator.py +250 -374
  9. edsl/agents/InvigilatorBase.py +257 -0
  10. edsl/agents/PromptConstructor.py +272 -0
  11. edsl/agents/QuestionInstructionPromptBuilder.py +128 -0
  12. edsl/agents/QuestionTemplateReplacementsBuilder.py +137 -0
  13. edsl/agents/descriptors.py +43 -13
  14. edsl/agents/prompt_helpers.py +129 -0
  15. edsl/agents/question_option_processor.py +172 -0
  16. edsl/auto/AutoStudy.py +130 -0
  17. edsl/auto/StageBase.py +243 -0
  18. edsl/auto/StageGenerateSurvey.py +178 -0
  19. edsl/auto/StageLabelQuestions.py +125 -0
  20. edsl/auto/StagePersona.py +61 -0
  21. edsl/auto/StagePersonaDimensionValueRanges.py +88 -0
  22. edsl/auto/StagePersonaDimensionValues.py +74 -0
  23. edsl/auto/StagePersonaDimensions.py +69 -0
  24. edsl/auto/StageQuestions.py +74 -0
  25. edsl/auto/SurveyCreatorPipeline.py +21 -0
  26. edsl/auto/utilities.py +218 -0
  27. edsl/base/Base.py +279 -0
  28. edsl/config.py +115 -113
  29. edsl/conversation/Conversation.py +290 -0
  30. edsl/conversation/car_buying.py +59 -0
  31. edsl/conversation/chips.py +95 -0
  32. edsl/conversation/mug_negotiation.py +81 -0
  33. edsl/conversation/next_speaker_utilities.py +93 -0
  34. edsl/coop/CoopFunctionsMixin.py +15 -0
  35. edsl/coop/ExpectedParrotKeyHandler.py +125 -0
  36. edsl/coop/PriceFetcher.py +54 -0
  37. edsl/coop/__init__.py +1 -0
  38. edsl/coop/coop.py +1029 -134
  39. edsl/coop/utils.py +131 -0
  40. edsl/data/Cache.py +560 -89
  41. edsl/data/CacheEntry.py +230 -0
  42. edsl/data/CacheHandler.py +168 -0
  43. edsl/data/RemoteCacheSync.py +186 -0
  44. edsl/data/SQLiteDict.py +292 -0
  45. edsl/data/__init__.py +5 -3
  46. edsl/data/orm.py +6 -33
  47. edsl/data_transfer_models.py +74 -27
  48. edsl/enums.py +165 -8
  49. edsl/exceptions/BaseException.py +21 -0
  50. edsl/exceptions/__init__.py +52 -46
  51. edsl/exceptions/agents.py +33 -15
  52. edsl/exceptions/cache.py +5 -0
  53. edsl/exceptions/coop.py +8 -0
  54. edsl/exceptions/general.py +34 -0
  55. edsl/exceptions/inference_services.py +5 -0
  56. edsl/exceptions/jobs.py +15 -0
  57. edsl/exceptions/language_models.py +46 -1
  58. edsl/exceptions/questions.py +80 -5
  59. edsl/exceptions/results.py +16 -5
  60. edsl/exceptions/scenarios.py +29 -0
  61. edsl/exceptions/surveys.py +13 -10
  62. edsl/inference_services/AnthropicService.py +106 -0
  63. edsl/inference_services/AvailableModelCacheHandler.py +184 -0
  64. edsl/inference_services/AvailableModelFetcher.py +215 -0
  65. edsl/inference_services/AwsBedrock.py +118 -0
  66. edsl/inference_services/AzureAI.py +215 -0
  67. edsl/inference_services/DeepInfraService.py +18 -0
  68. edsl/inference_services/GoogleService.py +143 -0
  69. edsl/inference_services/GroqService.py +20 -0
  70. edsl/inference_services/InferenceServiceABC.py +80 -0
  71. edsl/inference_services/InferenceServicesCollection.py +138 -0
  72. edsl/inference_services/MistralAIService.py +120 -0
  73. edsl/inference_services/OllamaService.py +18 -0
  74. edsl/inference_services/OpenAIService.py +236 -0
  75. edsl/inference_services/PerplexityService.py +160 -0
  76. edsl/inference_services/ServiceAvailability.py +135 -0
  77. edsl/inference_services/TestService.py +90 -0
  78. edsl/inference_services/TogetherAIService.py +172 -0
  79. edsl/inference_services/data_structures.py +134 -0
  80. edsl/inference_services/models_available_cache.py +118 -0
  81. edsl/inference_services/rate_limits_cache.py +25 -0
  82. edsl/inference_services/registry.py +41 -0
  83. edsl/inference_services/write_available.py +10 -0
  84. edsl/jobs/AnswerQuestionFunctionConstructor.py +223 -0
  85. edsl/jobs/Answers.py +21 -20
  86. edsl/jobs/FetchInvigilator.py +47 -0
  87. edsl/jobs/InterviewTaskManager.py +98 -0
  88. edsl/jobs/InterviewsConstructor.py +50 -0
  89. edsl/jobs/Jobs.py +684 -206
  90. edsl/jobs/JobsChecks.py +172 -0
  91. edsl/jobs/JobsComponentConstructor.py +189 -0
  92. edsl/jobs/JobsPrompts.py +270 -0
  93. edsl/jobs/JobsRemoteInferenceHandler.py +311 -0
  94. edsl/jobs/JobsRemoteInferenceLogger.py +239 -0
  95. edsl/jobs/RequestTokenEstimator.py +30 -0
  96. edsl/jobs/async_interview_runner.py +138 -0
  97. edsl/jobs/buckets/BucketCollection.py +104 -0
  98. edsl/jobs/buckets/ModelBuckets.py +65 -0
  99. edsl/jobs/buckets/TokenBucket.py +283 -0
  100. edsl/jobs/buckets/TokenBucketAPI.py +211 -0
  101. edsl/jobs/buckets/TokenBucketClient.py +191 -0
  102. edsl/jobs/check_survey_scenario_compatibility.py +85 -0
  103. edsl/jobs/data_structures.py +120 -0
  104. edsl/jobs/decorators.py +35 -0
  105. edsl/jobs/interviews/Interview.py +392 -0
  106. edsl/jobs/interviews/InterviewExceptionCollection.py +99 -0
  107. edsl/jobs/interviews/InterviewExceptionEntry.py +186 -0
  108. edsl/jobs/interviews/InterviewStatistic.py +63 -0
  109. edsl/jobs/interviews/InterviewStatisticsCollection.py +25 -0
  110. edsl/jobs/interviews/InterviewStatusDictionary.py +78 -0
  111. edsl/jobs/interviews/InterviewStatusLog.py +92 -0
  112. edsl/jobs/interviews/ReportErrors.py +66 -0
  113. edsl/jobs/interviews/interview_status_enum.py +9 -0
  114. edsl/jobs/jobs_status_enums.py +9 -0
  115. edsl/jobs/loggers/HTMLTableJobLogger.py +304 -0
  116. edsl/jobs/results_exceptions_handler.py +98 -0
  117. edsl/jobs/runners/JobsRunnerAsyncio.py +151 -110
  118. edsl/jobs/runners/JobsRunnerStatus.py +298 -0
  119. edsl/jobs/tasks/QuestionTaskCreator.py +244 -0
  120. edsl/jobs/tasks/TaskCreators.py +64 -0
  121. edsl/jobs/tasks/TaskHistory.py +470 -0
  122. edsl/jobs/tasks/TaskStatusLog.py +23 -0
  123. edsl/jobs/tasks/task_status_enum.py +161 -0
  124. edsl/jobs/tokens/InterviewTokenUsage.py +27 -0
  125. edsl/jobs/tokens/TokenUsage.py +34 -0
  126. edsl/language_models/ComputeCost.py +63 -0
  127. edsl/language_models/LanguageModel.py +507 -386
  128. edsl/language_models/ModelList.py +164 -0
  129. edsl/language_models/PriceManager.py +127 -0
  130. edsl/language_models/RawResponseHandler.py +106 -0
  131. edsl/language_models/RegisterLanguageModelsMeta.py +184 -0
  132. edsl/language_models/__init__.py +1 -8
  133. edsl/language_models/fake_openai_call.py +15 -0
  134. edsl/language_models/fake_openai_service.py +61 -0
  135. edsl/language_models/key_management/KeyLookup.py +63 -0
  136. edsl/language_models/key_management/KeyLookupBuilder.py +273 -0
  137. edsl/language_models/key_management/KeyLookupCollection.py +38 -0
  138. edsl/language_models/key_management/__init__.py +0 -0
  139. edsl/language_models/key_management/models.py +131 -0
  140. edsl/language_models/model.py +256 -0
  141. edsl/language_models/repair.py +109 -41
  142. edsl/language_models/utilities.py +65 -0
  143. edsl/notebooks/Notebook.py +263 -0
  144. edsl/notebooks/NotebookToLaTeX.py +142 -0
  145. edsl/notebooks/__init__.py +1 -0
  146. edsl/prompts/Prompt.py +222 -93
  147. edsl/prompts/__init__.py +1 -1
  148. edsl/questions/ExceptionExplainer.py +77 -0
  149. edsl/questions/HTMLQuestion.py +103 -0
  150. edsl/questions/QuestionBase.py +518 -0
  151. edsl/questions/QuestionBasePromptsMixin.py +221 -0
  152. edsl/questions/QuestionBudget.py +164 -67
  153. edsl/questions/QuestionCheckBox.py +281 -62
  154. edsl/questions/QuestionDict.py +343 -0
  155. edsl/questions/QuestionExtract.py +136 -50
  156. edsl/questions/QuestionFreeText.py +79 -55
  157. edsl/questions/QuestionFunctional.py +138 -41
  158. edsl/questions/QuestionList.py +184 -57
  159. edsl/questions/QuestionMatrix.py +265 -0
  160. edsl/questions/QuestionMultipleChoice.py +293 -69
  161. edsl/questions/QuestionNumerical.py +109 -56
  162. edsl/questions/QuestionRank.py +244 -49
  163. edsl/questions/Quick.py +41 -0
  164. edsl/questions/SimpleAskMixin.py +74 -0
  165. edsl/questions/__init__.py +9 -6
  166. edsl/questions/{AnswerValidatorMixin.py → answer_validator_mixin.py} +153 -38
  167. edsl/questions/compose_questions.py +13 -7
  168. edsl/questions/data_structures.py +20 -0
  169. edsl/questions/decorators.py +21 -0
  170. edsl/questions/derived/QuestionLikertFive.py +28 -26
  171. edsl/questions/derived/QuestionLinearScale.py +41 -28
  172. edsl/questions/derived/QuestionTopK.py +34 -26
  173. edsl/questions/derived/QuestionYesNo.py +40 -27
  174. edsl/questions/descriptors.py +228 -74
  175. edsl/questions/loop_processor.py +149 -0
  176. edsl/questions/prompt_templates/question_budget.jinja +13 -0
  177. edsl/questions/prompt_templates/question_checkbox.jinja +32 -0
  178. edsl/questions/prompt_templates/question_extract.jinja +11 -0
  179. edsl/questions/prompt_templates/question_free_text.jinja +3 -0
  180. edsl/questions/prompt_templates/question_linear_scale.jinja +11 -0
  181. edsl/questions/prompt_templates/question_list.jinja +17 -0
  182. edsl/questions/prompt_templates/question_multiple_choice.jinja +33 -0
  183. edsl/questions/prompt_templates/question_numerical.jinja +37 -0
  184. edsl/questions/question_base_gen_mixin.py +168 -0
  185. edsl/questions/question_registry.py +130 -46
  186. edsl/questions/register_questions_meta.py +71 -0
  187. edsl/questions/response_validator_abc.py +188 -0
  188. edsl/questions/response_validator_factory.py +34 -0
  189. edsl/questions/settings.py +5 -2
  190. edsl/questions/templates/__init__.py +0 -0
  191. edsl/questions/templates/budget/__init__.py +0 -0
  192. edsl/questions/templates/budget/answering_instructions.jinja +7 -0
  193. edsl/questions/templates/budget/question_presentation.jinja +7 -0
  194. edsl/questions/templates/checkbox/__init__.py +0 -0
  195. edsl/questions/templates/checkbox/answering_instructions.jinja +10 -0
  196. edsl/questions/templates/checkbox/question_presentation.jinja +22 -0
  197. edsl/questions/templates/dict/__init__.py +0 -0
  198. edsl/questions/templates/dict/answering_instructions.jinja +21 -0
  199. edsl/questions/templates/dict/question_presentation.jinja +1 -0
  200. edsl/questions/templates/extract/__init__.py +0 -0
  201. edsl/questions/templates/extract/answering_instructions.jinja +7 -0
  202. edsl/questions/templates/extract/question_presentation.jinja +1 -0
  203. edsl/questions/templates/free_text/__init__.py +0 -0
  204. edsl/questions/templates/free_text/answering_instructions.jinja +0 -0
  205. edsl/questions/templates/free_text/question_presentation.jinja +1 -0
  206. edsl/questions/templates/likert_five/__init__.py +0 -0
  207. edsl/questions/templates/likert_five/answering_instructions.jinja +10 -0
  208. edsl/questions/templates/likert_five/question_presentation.jinja +12 -0
  209. edsl/questions/templates/linear_scale/__init__.py +0 -0
  210. edsl/questions/templates/linear_scale/answering_instructions.jinja +5 -0
  211. edsl/questions/templates/linear_scale/question_presentation.jinja +5 -0
  212. edsl/questions/templates/list/__init__.py +0 -0
  213. edsl/questions/templates/list/answering_instructions.jinja +4 -0
  214. edsl/questions/templates/list/question_presentation.jinja +5 -0
  215. edsl/questions/templates/matrix/__init__.py +1 -0
  216. edsl/questions/templates/matrix/answering_instructions.jinja +5 -0
  217. edsl/questions/templates/matrix/question_presentation.jinja +20 -0
  218. edsl/questions/templates/multiple_choice/__init__.py +0 -0
  219. edsl/questions/templates/multiple_choice/answering_instructions.jinja +9 -0
  220. edsl/questions/templates/multiple_choice/html.jinja +0 -0
  221. edsl/questions/templates/multiple_choice/question_presentation.jinja +12 -0
  222. edsl/questions/templates/numerical/__init__.py +0 -0
  223. edsl/questions/templates/numerical/answering_instructions.jinja +7 -0
  224. edsl/questions/templates/numerical/question_presentation.jinja +7 -0
  225. edsl/questions/templates/rank/__init__.py +0 -0
  226. edsl/questions/templates/rank/answering_instructions.jinja +11 -0
  227. edsl/questions/templates/rank/question_presentation.jinja +15 -0
  228. edsl/questions/templates/top_k/__init__.py +0 -0
  229. edsl/questions/templates/top_k/answering_instructions.jinja +8 -0
  230. edsl/questions/templates/top_k/question_presentation.jinja +22 -0
  231. edsl/questions/templates/yes_no/__init__.py +0 -0
  232. edsl/questions/templates/yes_no/answering_instructions.jinja +6 -0
  233. edsl/questions/templates/yes_no/question_presentation.jinja +12 -0
  234. edsl/results/CSSParameterizer.py +108 -0
  235. edsl/results/Dataset.py +550 -19
  236. edsl/results/DatasetExportMixin.py +594 -0
  237. edsl/results/DatasetTree.py +295 -0
  238. edsl/results/MarkdownToDocx.py +122 -0
  239. edsl/results/MarkdownToPDF.py +111 -0
  240. edsl/results/Result.py +477 -173
  241. edsl/results/Results.py +987 -269
  242. edsl/results/ResultsExportMixin.py +28 -125
  243. edsl/results/ResultsGGMixin.py +83 -15
  244. edsl/results/TableDisplay.py +125 -0
  245. edsl/results/TextEditor.py +50 -0
  246. edsl/results/__init__.py +1 -1
  247. edsl/results/file_exports.py +252 -0
  248. edsl/results/results_fetch_mixin.py +33 -0
  249. edsl/results/results_selector.py +145 -0
  250. edsl/results/results_tools_mixin.py +98 -0
  251. edsl/results/smart_objects.py +96 -0
  252. edsl/results/table_data_class.py +12 -0
  253. edsl/results/table_display.css +78 -0
  254. edsl/results/table_renderers.py +118 -0
  255. edsl/results/tree_explore.py +115 -0
  256. edsl/scenarios/ConstructDownloadLink.py +109 -0
  257. edsl/scenarios/DocumentChunker.py +102 -0
  258. edsl/scenarios/DocxScenario.py +16 -0
  259. edsl/scenarios/FileStore.py +543 -0
  260. edsl/scenarios/PdfExtractor.py +40 -0
  261. edsl/scenarios/Scenario.py +431 -62
  262. edsl/scenarios/ScenarioHtmlMixin.py +65 -0
  263. edsl/scenarios/ScenarioList.py +1415 -45
  264. edsl/scenarios/ScenarioListExportMixin.py +45 -0
  265. edsl/scenarios/ScenarioListPdfMixin.py +239 -0
  266. edsl/scenarios/__init__.py +2 -0
  267. edsl/scenarios/directory_scanner.py +96 -0
  268. edsl/scenarios/file_methods.py +85 -0
  269. edsl/scenarios/handlers/__init__.py +13 -0
  270. edsl/scenarios/handlers/csv.py +49 -0
  271. edsl/scenarios/handlers/docx.py +76 -0
  272. edsl/scenarios/handlers/html.py +37 -0
  273. edsl/scenarios/handlers/json.py +111 -0
  274. edsl/scenarios/handlers/latex.py +5 -0
  275. edsl/scenarios/handlers/md.py +51 -0
  276. edsl/scenarios/handlers/pdf.py +68 -0
  277. edsl/scenarios/handlers/png.py +39 -0
  278. edsl/scenarios/handlers/pptx.py +105 -0
  279. edsl/scenarios/handlers/py.py +294 -0
  280. edsl/scenarios/handlers/sql.py +313 -0
  281. edsl/scenarios/handlers/sqlite.py +149 -0
  282. edsl/scenarios/handlers/txt.py +33 -0
  283. edsl/scenarios/scenario_join.py +131 -0
  284. edsl/scenarios/scenario_selector.py +156 -0
  285. edsl/shared.py +1 -0
  286. edsl/study/ObjectEntry.py +173 -0
  287. edsl/study/ProofOfWork.py +113 -0
  288. edsl/study/SnapShot.py +80 -0
  289. edsl/study/Study.py +521 -0
  290. edsl/study/__init__.py +4 -0
  291. edsl/surveys/ConstructDAG.py +92 -0
  292. edsl/surveys/DAG.py +92 -11
  293. edsl/surveys/EditSurvey.py +221 -0
  294. edsl/surveys/InstructionHandler.py +100 -0
  295. edsl/surveys/Memory.py +9 -4
  296. edsl/surveys/MemoryManagement.py +72 -0
  297. edsl/surveys/MemoryPlan.py +156 -35
  298. edsl/surveys/Rule.py +221 -74
  299. edsl/surveys/RuleCollection.py +241 -61
  300. edsl/surveys/RuleManager.py +172 -0
  301. edsl/surveys/Simulator.py +75 -0
  302. edsl/surveys/Survey.py +1079 -339
  303. edsl/surveys/SurveyCSS.py +273 -0
  304. edsl/surveys/SurveyExportMixin.py +235 -40
  305. edsl/surveys/SurveyFlowVisualization.py +181 -0
  306. edsl/surveys/SurveyQualtricsImport.py +284 -0
  307. edsl/surveys/SurveyToApp.py +141 -0
  308. edsl/surveys/__init__.py +4 -2
  309. edsl/surveys/base.py +19 -3
  310. edsl/surveys/descriptors.py +17 -6
  311. edsl/surveys/instructions/ChangeInstruction.py +48 -0
  312. edsl/surveys/instructions/Instruction.py +56 -0
  313. edsl/surveys/instructions/InstructionCollection.py +82 -0
  314. edsl/surveys/instructions/__init__.py +0 -0
  315. edsl/templates/error_reporting/base.html +24 -0
  316. edsl/templates/error_reporting/exceptions_by_model.html +35 -0
  317. edsl/templates/error_reporting/exceptions_by_question_name.html +17 -0
  318. edsl/templates/error_reporting/exceptions_by_type.html +17 -0
  319. edsl/templates/error_reporting/interview_details.html +116 -0
  320. edsl/templates/error_reporting/interviews.html +19 -0
  321. edsl/templates/error_reporting/overview.html +5 -0
  322. edsl/templates/error_reporting/performance_plot.html +2 -0
  323. edsl/templates/error_reporting/report.css +74 -0
  324. edsl/templates/error_reporting/report.html +118 -0
  325. edsl/templates/error_reporting/report.js +25 -0
  326. edsl/tools/__init__.py +1 -0
  327. edsl/tools/clusters.py +192 -0
  328. edsl/tools/embeddings.py +27 -0
  329. edsl/tools/embeddings_plotting.py +118 -0
  330. edsl/tools/plotting.py +112 -0
  331. edsl/tools/summarize.py +18 -0
  332. edsl/utilities/PrettyList.py +56 -0
  333. edsl/utilities/SystemInfo.py +5 -0
  334. edsl/utilities/__init__.py +21 -20
  335. edsl/utilities/ast_utilities.py +3 -0
  336. edsl/utilities/data/Registry.py +2 -0
  337. edsl/utilities/decorators.py +41 -0
  338. edsl/utilities/gcp_bucket/__init__.py +0 -0
  339. edsl/utilities/gcp_bucket/cloud_storage.py +96 -0
  340. edsl/utilities/interface.py +310 -60
  341. edsl/utilities/is_notebook.py +18 -0
  342. edsl/utilities/is_valid_variable_name.py +11 -0
  343. edsl/utilities/naming_utilities.py +263 -0
  344. edsl/utilities/remove_edsl_version.py +24 -0
  345. edsl/utilities/repair_functions.py +28 -0
  346. edsl/utilities/restricted_python.py +70 -0
  347. edsl/utilities/utilities.py +203 -13
  348. edsl-0.1.40.dist-info/METADATA +111 -0
  349. edsl-0.1.40.dist-info/RECORD +362 -0
  350. {edsl-0.1.15.dist-info → edsl-0.1.40.dist-info}/WHEEL +1 -1
  351. edsl/agents/AgentListExportMixin.py +0 -24
  352. edsl/coop/old.py +0 -31
  353. edsl/data/Database.py +0 -141
  354. edsl/data/crud.py +0 -121
  355. edsl/jobs/Interview.py +0 -435
  356. edsl/jobs/JobsRunner.py +0 -63
  357. edsl/jobs/JobsRunnerStatusMixin.py +0 -115
  358. edsl/jobs/base.py +0 -47
  359. edsl/jobs/buckets.py +0 -178
  360. edsl/jobs/runners/JobsRunnerDryRun.py +0 -19
  361. edsl/jobs/runners/JobsRunnerStreaming.py +0 -54
  362. edsl/jobs/task_management.py +0 -215
  363. edsl/jobs/token_tracking.py +0 -78
  364. edsl/language_models/DeepInfra.py +0 -69
  365. edsl/language_models/OpenAI.py +0 -98
  366. edsl/language_models/model_interfaces/GeminiPro.py +0 -66
  367. edsl/language_models/model_interfaces/LanguageModelOpenAIFour.py +0 -8
  368. edsl/language_models/model_interfaces/LanguageModelOpenAIThreeFiveTurbo.py +0 -8
  369. edsl/language_models/model_interfaces/LlamaTwo13B.py +0 -21
  370. edsl/language_models/model_interfaces/LlamaTwo70B.py +0 -21
  371. edsl/language_models/model_interfaces/Mixtral8x7B.py +0 -24
  372. edsl/language_models/registry.py +0 -81
  373. edsl/language_models/schemas.py +0 -15
  374. edsl/language_models/unused/ReplicateBase.py +0 -83
  375. edsl/prompts/QuestionInstructionsBase.py +0 -6
  376. edsl/prompts/library/agent_instructions.py +0 -29
  377. edsl/prompts/library/agent_persona.py +0 -17
  378. edsl/prompts/library/question_budget.py +0 -26
  379. edsl/prompts/library/question_checkbox.py +0 -32
  380. edsl/prompts/library/question_extract.py +0 -19
  381. edsl/prompts/library/question_freetext.py +0 -14
  382. edsl/prompts/library/question_linear_scale.py +0 -20
  383. edsl/prompts/library/question_list.py +0 -22
  384. edsl/prompts/library/question_multiple_choice.py +0 -44
  385. edsl/prompts/library/question_numerical.py +0 -31
  386. edsl/prompts/library/question_rank.py +0 -21
  387. edsl/prompts/prompt_config.py +0 -33
  388. edsl/prompts/registry.py +0 -185
  389. edsl/questions/Question.py +0 -240
  390. edsl/report/InputOutputDataTypes.py +0 -134
  391. edsl/report/RegressionMixin.py +0 -28
  392. edsl/report/ReportOutputs.py +0 -1228
  393. edsl/report/ResultsFetchMixin.py +0 -106
  394. edsl/report/ResultsOutputMixin.py +0 -14
  395. edsl/report/demo.ipynb +0 -645
  396. edsl/results/ResultsDBMixin.py +0 -184
  397. edsl/surveys/SurveyFlowVisualizationMixin.py +0 -92
  398. edsl/trackers/Tracker.py +0 -91
  399. edsl/trackers/TrackerAPI.py +0 -196
  400. edsl/trackers/TrackerTasks.py +0 -70
  401. edsl/utilities/pastebin.py +0 -141
  402. edsl-0.1.15.dist-info/METADATA +0 -69
  403. edsl-0.1.15.dist-info/RECORD +0 -142
  404. /edsl/{language_models/model_interfaces → inference_services}/__init__.py +0 -0
  405. /edsl/{report/__init__.py → jobs/runners/JobsRunnerStatusData.py} +0 -0
  406. /edsl/{trackers/__init__.py → language_models/ServiceDataSources.py} +0 -0
  407. {edsl-0.1.15.dist-info → edsl-0.1.40.dist-info}/LICENSE +0 -0
@@ -0,0 +1,265 @@
1
+ from __future__ import annotations
2
+ from typing import Union, Optional, Dict, List, Any
3
+
4
+ from pydantic import BaseModel, Field, field_validator
5
+ from jinja2 import Template
6
+ import random
7
+ from edsl.questions.QuestionBase import QuestionBase
8
+ from edsl.questions.descriptors import (
9
+ QuestionOptionsDescriptor,
10
+ OptionLabelDescriptor,
11
+ QuestionTextDescriptor,
12
+ )
13
+ from edsl.questions.response_validator_abc import ResponseValidatorABC
14
+ from edsl.questions.decorators import inject_exception
15
+ from edsl.exceptions.questions import (
16
+ QuestionAnswerValidationError,
17
+ QuestionCreationValidationError,
18
+ )
19
+
20
+
21
+ def create_matrix_response(
22
+ question_items: List[str],
23
+ question_options: List[Union[int, str, float]],
24
+ permissive: bool = False,
25
+ ):
26
+ """Create a response model for matrix questions.
27
+
28
+ The response model validates that:
29
+ 1. All question items are answered
30
+ 2. Each answer is from the allowed options
31
+ """
32
+
33
+ if permissive:
34
+
35
+ class MatrixResponse(BaseModel):
36
+ answer: Dict[str, Any]
37
+ comment: Optional[str] = None
38
+ generated_tokens: Optional[Any] = None
39
+
40
+ else:
41
+
42
+ class MatrixResponse(BaseModel):
43
+ answer: Dict[str, Union[int, str, float]] = Field(
44
+ ..., description="Mapping of items to selected options"
45
+ )
46
+ comment: Optional[str] = None
47
+ generated_tokens: Optional[Any] = None
48
+
49
+ @field_validator("answer")
50
+ def validate_answer(cls, v, values, **kwargs):
51
+ # Check that all items have responses
52
+ if not all(item in v for item in question_items):
53
+ missing = set(question_items) - set(v.keys())
54
+ raise ValueError(f"Missing responses for items: {missing}")
55
+
56
+ # Check that all responses are valid options
57
+ if not all(answer in question_options for answer in v.values()):
58
+ invalid = [ans for ans in v.values() if ans not in question_options]
59
+ raise ValueError(f"Invalid options selected: {invalid}")
60
+ return v
61
+
62
+ return MatrixResponse
63
+
64
+
65
+ class MatrixResponseValidator(ResponseValidatorABC):
66
+ required_params = ["question_items", "question_options", "permissive"]
67
+
68
+ valid_examples = [
69
+ (
70
+ {"answer": {"Item1": 1, "Item2": 2}},
71
+ {
72
+ "question_items": ["Item1", "Item2"],
73
+ "question_options": [1, 2, 3],
74
+ },
75
+ )
76
+ ]
77
+
78
+ invalid_examples = [
79
+ (
80
+ {"answer": {"Item1": 1}},
81
+ {
82
+ "question_items": ["Item1", "Item2"],
83
+ "question_options": [1, 2, 3],
84
+ },
85
+ "Missing responses for some items",
86
+ ),
87
+ (
88
+ {"answer": {"Item1": 4, "Item2": 5}},
89
+ {
90
+ "question_items": ["Item1", "Item2"],
91
+ "question_options": [1, 2, 3],
92
+ },
93
+ "Invalid options selected",
94
+ ),
95
+ ]
96
+
97
+ def fix(self, response, verbose=False):
98
+ if verbose:
99
+ print(f"Fixing matrix response: {response}")
100
+
101
+ # If we have generated tokens, try to parse them
102
+ if "generated_tokens" in response:
103
+ try:
104
+ import json
105
+
106
+ fixed = json.loads(response["generated_tokens"])
107
+ if isinstance(fixed, dict):
108
+ # Map numeric keys to question items
109
+ mapped_answer = {}
110
+ for idx, item in enumerate(self.question_items):
111
+ if str(idx) in fixed:
112
+ mapped_answer[item] = fixed[str(idx)]
113
+ if (
114
+ mapped_answer
115
+ ): # Only return if we successfully mapped some answers
116
+ return {"answer": mapped_answer}
117
+ except:
118
+ pass
119
+
120
+ # If answer uses numeric keys, map them to question items
121
+ if "answer" in response and isinstance(response["answer"], dict):
122
+ if all(str(key).isdigit() for key in response["answer"].keys()):
123
+ mapped_answer = {}
124
+ for idx, item in enumerate(self.question_items):
125
+ if str(idx) in response["answer"]:
126
+ mapped_answer[item] = response["answer"][str(idx)]
127
+ if mapped_answer: # Only update if we successfully mapped some answers
128
+ response["answer"] = mapped_answer
129
+
130
+ return response
131
+
132
+
133
+ class QuestionMatrix(QuestionBase):
134
+ """A question that presents a matrix/grid where multiple items are rated using the same scale."""
135
+
136
+ question_type = "matrix"
137
+ question_text: str = QuestionTextDescriptor()
138
+ question_items: List[str] = QuestionOptionsDescriptor()
139
+ question_options: List[Union[int, str, float]] = QuestionOptionsDescriptor()
140
+ option_labels: Optional[Dict[Union[int, str, float], str]] = OptionLabelDescriptor()
141
+
142
+ _response_model = None
143
+ response_validator_class = MatrixResponseValidator
144
+
145
+ def __init__(
146
+ self,
147
+ question_name: str,
148
+ question_text: str,
149
+ question_items: List[str],
150
+ question_options: List[Union[int, str, float]],
151
+ option_labels: Optional[Dict[Union[int, str, float], str]] = None,
152
+ include_comment: bool = True,
153
+ answering_instructions: Optional[str] = None,
154
+ question_presentation: Optional[str] = None,
155
+ permissive: bool = False,
156
+ ):
157
+ """Initialize a matrix question.
158
+
159
+ Args:
160
+ question_name: The name of the question
161
+ question_text: The text of the question
162
+ question_items: List of items to be rated
163
+ question_options: List of rating options
164
+ option_labels: Optional mapping of options to their labels
165
+ include_comment: Whether to include a comment field
166
+ answering_instructions: Optional custom instructions
167
+ question_presentation: Optional custom presentation
168
+ permissive: Whether to strictly validate responses
169
+ """
170
+ self.question_name = question_name
171
+
172
+ try:
173
+ self.question_text = question_text
174
+ except Exception as e:
175
+ raise QuestionCreationValidationError(
176
+ "question_text cannot be empty or too short!"
177
+ ) from e
178
+
179
+ self.question_items = question_items
180
+ self.question_options = question_options
181
+ self.option_labels = option_labels or {}
182
+
183
+ self.include_comment = include_comment
184
+ self.answering_instructions = answering_instructions
185
+ self.question_presentation = question_presentation
186
+ self.permissive = permissive
187
+
188
+ def create_response_model(self):
189
+ return create_matrix_response(
190
+ self.question_items, self.question_options, self.permissive
191
+ )
192
+
193
+ @property
194
+ def question_html_content(self) -> str:
195
+ """Generate HTML representation of the matrix question."""
196
+ template = Template(
197
+ """
198
+ <table class="matrix-question">
199
+ <tr>
200
+ <th></th>
201
+ {% for option in question_options %}
202
+ <th>
203
+ {{ option }}
204
+ {% if option in option_labels %}
205
+ <br>
206
+ <small>{{ option_labels[option] }}</small>
207
+ {% endif %}
208
+ </th>
209
+ {% endfor %}
210
+ </tr>
211
+ {% for item in question_items %}
212
+ <tr>
213
+ <td>{{ item }}</td>
214
+ {% for option in question_options %}
215
+ <td>
216
+ <input type="radio"
217
+ name="{{ question_name }}_{{ item }}"
218
+ value="{{ option }}"
219
+ id="{{ question_name }}_{{ item }}_{{ option }}">
220
+ </td>
221
+ {% endfor %}
222
+ </tr>
223
+ {% endfor %}
224
+ </table>
225
+ """
226
+ )
227
+
228
+ return template.render(
229
+ question_name=self.question_name,
230
+ question_items=self.question_items,
231
+ question_options=self.question_options,
232
+ option_labels=self.option_labels,
233
+ )
234
+
235
+ @classmethod
236
+ @inject_exception
237
+ def example(cls) -> QuestionMatrix:
238
+ """Return an example matrix question."""
239
+ return cls(
240
+ question_name="child_happiness",
241
+ question_text="How happy would you be with different numbers of children?",
242
+ question_items=[
243
+ "No children",
244
+ "1 child",
245
+ "2 children",
246
+ "3 or more children",
247
+ ],
248
+ question_options=[1, 2, 3, 4, 5],
249
+ option_labels={1: "Very sad", 3: "Neutral", 5: "Extremely happy"},
250
+ )
251
+
252
+ def _simulate_answer(self) -> dict:
253
+ """Simulate a random valid answer."""
254
+ return {
255
+ "answer": {
256
+ item: random.choice(self.question_options)
257
+ for item in self.question_items
258
+ }
259
+ }
260
+
261
+
262
+ if __name__ == "__main__":
263
+ import doctest
264
+
265
+ doctest.testmod(optionflags=doctest.ELLIPSIS)
@@ -1,106 +1,330 @@
1
1
  from __future__ import annotations
2
- import random
3
- import textwrap
2
+ from typing import Union, Literal, Optional, List, Any
3
+
4
4
  from jinja2 import Template
5
- from typing import Optional, Union
6
- from edsl.utilities import random_string
5
+ from pydantic import BaseModel, Field
6
+
7
+ from edsl.scenarios.Scenario import Scenario
8
+ from edsl.questions.QuestionBase import QuestionBase
7
9
  from edsl.questions.descriptors import QuestionOptionsDescriptor
8
- from edsl.questions.Question import Question
9
- from edsl.scenarios import Scenario
10
+ from edsl.questions.decorators import inject_exception
11
+ from edsl.questions.response_validator_abc import ResponseValidatorABC
10
12
 
11
13
 
12
- class QuestionMultipleChoice(Question):
14
+ def create_response_model(choices: List[str], permissive: bool = False):
15
+ """
16
+ Create a ChoiceResponse model class with a predefined list of choices.
17
+
18
+ :param choices: A list of allowed values for the answer field.
19
+ :param permissive: If True, any value will be accepted as an answer.
20
+ :return: A new Pydantic model class.
13
21
  """
14
- This question asks the user to select one option from a list of options.
22
+ choice_tuple = tuple(choices)
23
+
24
+ if not permissive:
25
+
26
+ class ChoiceResponse(BaseModel):
27
+ answer: Literal[choice_tuple] = Field(description="Selected choice")
28
+ comment: Optional[str] = Field(None, description="Optional comment field")
29
+ generated_tokens: Optional[Any] = Field(
30
+ None, description="Generated tokens"
31
+ )
32
+
33
+ class Config:
34
+ @staticmethod
35
+ def json_schema_extra(schema: dict, model: BaseModel) -> None:
36
+ for prop in schema.get("properties", {}).values():
37
+ if prop.get("title") == "answer":
38
+ prop["enum"] = choices
39
+
40
+ else:
41
+
42
+ class ChoiceResponse(BaseModel):
43
+ answer: Any = Field(description="Selected choice (can be any value)")
44
+ comment: Optional[str] = Field(None, description="Optional comment field")
45
+ generated_tokens: Optional[Any] = Field(
46
+ None, description="Generated tokens"
47
+ )
48
+
49
+ class Config:
50
+ @staticmethod
51
+ def json_schema_extra(schema: dict, model: BaseModel) -> None:
52
+ for prop in schema.get("properties", {}).values():
53
+ if prop.get("title") == "answer":
54
+ prop["description"] += f". Suggested choices are: {choices}"
55
+ schema["title"] += " (Permissive)"
56
+
57
+ return ChoiceResponse
58
+
59
+
60
+ class MultipleChoiceResponseValidator(ResponseValidatorABC):
61
+ required_params = ["question_options", "use_code"]
62
+
63
+ def fix(self, response, verbose=False):
64
+ response_text = str(response.get("answer"))
65
+ if response_text is None:
66
+ response_text = response.get("generated_tokens", "")
15
67
 
16
- Arguments:
17
- - `question_name` is the name of the question (string)
18
- - `question_options` are the options the user should select from (list of strings)
19
- - `question_text` is the text of the question (string)
68
+ if verbose:
69
+ print(f"Invalid generated tokens was: {response_text}")
20
70
 
21
- Optional arguments:
22
- - `instructions` are the instructions for the question (string). If not provided, the default instructions are used. To view them, run `QuestionMultipleChoice.default_instructions`
23
- - `short_names_dict` maps question_options to short names (dictionary mapping strings to strings)
71
+ matches = []
72
+ for idx, option in enumerate(self.question_options):
73
+ if verbose:
74
+ print("The options are: ", self.question_options)
75
+ if str(option) in response_text:
76
+ if verbose:
77
+ print("Match found with option ", option)
78
+ if option not in matches:
79
+ matches.append(option)
80
+
81
+ if verbose:
82
+ print("The matches are: ", matches)
83
+ if len(matches) == 1:
84
+ proposed_data = {
85
+ "answer": matches[0],
86
+ "generated_tokens": response.get("generated_tokens", None),
87
+ }
88
+ try:
89
+ self.response_model(**proposed_data)
90
+ return proposed_data
91
+ except Exception as e:
92
+ if verbose:
93
+ print(f"Proposed solution {proposed_data} is invalid. Error: {e}")
94
+ return response
95
+
96
+ valid_examples = [
97
+ ({"answer": 1}, {"question_options": ["Good", "Great", "OK", "Bad"]})
98
+ ]
99
+
100
+ invalid_examples = [
101
+ (
102
+ {"answer": -1},
103
+ {"question_options": ["Good", "Great", "OK", "Bad"]},
104
+ "Answer code must be a non-negative integer",
105
+ ),
106
+ (
107
+ {"answer": None},
108
+ {"question_options": ["Good", "Great", "OK", "Bad"]},
109
+ "Answer code must not be missing.",
110
+ ),
111
+ ]
112
+
113
+
114
+ class QuestionMultipleChoice(QuestionBase):
115
+ """This question prompts the agent to select one option from a list of options.
116
+
117
+ https://docs.expectedparrot.com/en/latest/questions.html#questionmultiplechoice-class
24
118
 
25
- For an example, run `QuestionMultipleChoice.example()`
26
119
  """
27
120
 
28
121
  question_type = "multiple_choice"
29
122
  purpose = "When options are known and limited"
30
- question_options: list[str] = QuestionOptionsDescriptor()
123
+ question_options: Union[list[str], list[list], list[float], list[int]] = (
124
+ QuestionOptionsDescriptor()
125
+ )
126
+ _response_model = None
127
+ response_validator_class = MultipleChoiceResponseValidator
31
128
 
32
129
  def __init__(
33
130
  self,
34
- question_text: str,
35
- question_options: list[str],
36
131
  question_name: str,
37
- short_names_dict: Optional[dict[str, str]] = None,
132
+ question_text: str,
133
+ question_options: Union[list[str], list[list], list[float], list[int]],
134
+ include_comment: bool = True,
135
+ use_code: bool = False,
136
+ answering_instructions: Optional[str] = None,
137
+ question_presentation: Optional[str] = None,
138
+ permissive: bool = False,
38
139
  ):
140
+ """Instantiate a new QuestionMultipleChoice.
141
+
142
+ :param question_name: The name of the question.
143
+ :param question_text: The text of the question.
144
+ :param question_options: The options the agent should select from.
145
+ :param include_comment: Whether to include a comment field.
146
+ :param use_code: Whether to use code for the options.
147
+ :param answering_instructions: Instructions for the question.
148
+ :param question_presentation: The presentation of the question.
149
+ :param permissive: Whether to force the answer to be one of the options.
150
+
151
+ """
152
+ self.question_name = question_name
39
153
  self.question_text = question_text
40
154
  self.question_options = question_options
41
- self.question_name = question_name
42
- self.short_names_dict = short_names_dict or dict()
155
+
156
+ self._include_comment = include_comment
157
+ self.use_code = use_code
158
+ self.answering_instructions = answering_instructions
159
+ self.question_presentation = question_presentation
160
+ self.permissive = permissive
43
161
 
44
162
  ################
45
163
  # Answer methods
46
164
  ################
47
- def validate_answer(
48
- self, answer: dict[str, Union[str, int]]
49
- ) -> dict[str, Union[str, int]]:
50
- """Validates the answer"""
51
- self.validate_answer_template_basic(answer)
52
- self.validate_answer_multiple_choice(answer)
53
- return answer
54
-
55
- def translate_answer_code_to_answer(self, answer_code, scenario: Scenario = None):
56
- """Translates the answer code to the actual answer."""
57
- scenario = scenario or Scenario()
58
- translated_options = [
59
- Template(str(option)).render(scenario) for option in self.question_options
60
- ]
61
- return translated_options[int(answer_code)]
62
-
63
- def simulate_answer(
64
- self, human_readable: bool = True
65
- ) -> dict[str, Union[int, str]]:
66
- """Simulates a valid answer for debugging purposes"""
67
- if human_readable:
68
- answer = random.choice(self.question_options)
165
+
166
+ def create_response_model(self, replacement_dict: dict = None):
167
+ if replacement_dict is None:
168
+ replacement_dict = {}
169
+ # The replacement dict that could be from scenario, current answers, etc. to populate the response model
170
+
171
+ if self.use_code:
172
+ return create_response_model(
173
+ list(range(len(self.question_options))), self.permissive
174
+ )
175
+ else:
176
+ return create_response_model(self.question_options, self.permissive)
177
+
178
+ @staticmethod
179
+ def _translate_question_options(
180
+ question_options, substitution_dict: dict
181
+ ) -> list[str]:
182
+
183
+ if isinstance(question_options, str):
184
+ # If dynamic options are provided like {{ options }}, render them with the scenario
185
+ # We can check if it's in the Scenario.
186
+ from jinja2 import Environment, meta
187
+
188
+ env = Environment()
189
+ parsed_content = env.parse(question_options)
190
+ template_variables = list(meta.find_undeclared_variables(parsed_content))
191
+ # print("The template variables are: ", template_variables)
192
+ question_option_key = template_variables[0]
193
+ # We need to deal with possibility it's actually an answer to a question.
194
+ potential_replacement = substitution_dict.get(question_option_key, None)
195
+
196
+ if isinstance(potential_replacement, list):
197
+ # translated_options = potential_replacement
198
+ return potential_replacement
199
+
200
+ if isinstance(potential_replacement, QuestionBase):
201
+ if hasattr(potential_replacement, "answer") and isinstance(
202
+ potential_replacement.answer, list
203
+ ):
204
+ return potential_replacement.answer
205
+ # translated_options = potential_replacement.answer
206
+
207
+ # if not isinstance(potential_replacement, list):
208
+ # translated_options = potential_replacement
209
+
210
+ if potential_replacement is None:
211
+ # Nope - maybe it's in the substition dict?
212
+ raise ValueError(
213
+ f"Could not find the key '{question_option_key}' in the scenario."
214
+ f"The substition dict was: '{substitution_dict}.'"
215
+ f"The question options were: '{question_options}'."
216
+ )
217
+ else:
218
+ translated_options = [
219
+ Template(str(option)).render(substitution_dict)
220
+ for option in question_options
221
+ ]
222
+ return translated_options
223
+
224
+ def _translate_answer_code_to_answer(
225
+ self, answer_code: int, replacements_dict: Optional[dict] = None
226
+ ):
227
+ """Translate the answer code to the actual answer.
228
+
229
+ It is used to translate the answer code to the actual answer.
230
+ The question options might be templates, so they need to be rendered with the scenario.
231
+
232
+ >>> q = QuestionMultipleChoice.example()
233
+ >>> q._translate_answer_code_to_answer('Good', {})
234
+ 'Good'
235
+
236
+ >>> q = QuestionMultipleChoice(question_name="how_feeling", question_text="How are you?", question_options=["{{emotion[0]}}", "emotion[1]"])
237
+ >>> q._translate_answer_code_to_answer('Happy', {"emotion": ["Happy", "Sad"]})
238
+ 'Happy'
239
+
240
+ """
241
+ if replacements_dict is None:
242
+ replacements_dict = {}
243
+ translated_options = self._translate_question_options(
244
+ self.question_options, replacements_dict
245
+ )
246
+
247
+ if self._use_code:
248
+ try:
249
+ return translated_options[int(answer_code)]
250
+ except IndexError:
251
+ raise ValueError(
252
+ f"Answer code is out of range. The answer code index was: {int(answer_code)}. The options were: {translated_options}."
253
+ )
254
+ except TypeError:
255
+ raise ValueError(
256
+ f"The answer code was: '{answer_code}.'",
257
+ f"The options were: '{translated_options}'.",
258
+ )
69
259
  else:
70
- answer = random.choice(range(len(self.question_options)))
71
- return {
72
- "answer": answer,
73
- "comment": random_string(),
74
- }
260
+ # return translated_options[answer_code]
261
+ return answer_code
262
+
263
+ @property
264
+ def question_html_content(self) -> str:
265
+ """Return the HTML version of the question."""
266
+ if hasattr(self, "option_labels"):
267
+ option_labels = self.option_labels
268
+ else:
269
+ option_labels = {}
270
+ question_html_content = Template(
271
+ """
272
+ {% for option in question_options %}
273
+ <div>
274
+ <input type="radio" id="{{ option }}" name="{{ question_name }}" value="{{ option }}">
275
+ <label for="{{ option }}">
276
+ {{ option }}
277
+ {% if option in option_labels %}
278
+ : {{ option_labels[option] }}
279
+ {% endif %}
280
+ </label>
281
+ </div>
282
+ {% endfor %}
283
+ """
284
+ ).render(
285
+ question_name=self.question_name,
286
+ question_options=self.question_options,
287
+ option_labels=option_labels,
288
+ )
289
+ return question_html_content
75
290
 
76
291
  ################
77
292
  # Example
78
293
  ################
79
294
  @classmethod
80
- def example(cls) -> QuestionMultipleChoice:
295
+ @inject_exception
296
+ def example(cls, include_comment=False, use_code=False) -> QuestionMultipleChoice:
297
+ """Return an example instance."""
81
298
  return cls(
82
299
  question_text="How are you?",
83
300
  question_options=["Good", "Great", "OK", "Bad"],
84
301
  question_name="how_feeling",
85
- short_names_dict={"Good": "g", "Great": "gr", "OK": "ok", "Bad": "b"},
302
+ include_comment=include_comment,
303
+ use_code=use_code,
86
304
  )
87
305
 
88
306
 
89
- def main():
90
- from edsl.questions.QuestionMultipleChoice import QuestionMultipleChoice
91
-
92
- q = QuestionMultipleChoice.example()
93
- q.question_text
94
- q.question_options
95
- q.question_name
96
- q.short_names_dict
97
- # validate an answer
98
- q.validate_answer({"answer": 0, "comment": "I like custard"})
99
- # translate answer code
100
- q.translate_answer_code_to_answer(0, {})
101
- # simulate answer
102
- q.simulate_answer()
103
- q.simulate_answer(human_readable=False)
104
- # serialization (inherits from Question)
105
- q.to_dict()
106
- assert q.from_dict(q.to_dict()) == q
307
+ # def main():
308
+ # """Create an example QuestionMultipleChoice and test its methods."""
309
+ # from edsl.questions.QuestionMultipleChoice import QuestionMultipleChoice
310
+
311
+ # q = QuestionMultipleChoice.example()
312
+ # q.question_text
313
+ # q.question_options
314
+ # q.question_name
315
+ # # validate an answer
316
+ # q._validate_answer({"answer": 0, "comment": "I like custard"})
317
+ # # translate answer code
318
+ # q._translate_answer_code_to_answer(0, {})
319
+ # # simulate answer
320
+ # q._simulate_answer()
321
+ # q._simulate_answer(human_readable=False)
322
+ # # serialization (inherits from Question)
323
+ # q.to_dict()
324
+ # assert q.from_dict(q.to_dict()) == q
325
+
326
+
327
+ if __name__ == "__main__":
328
+ import doctest
329
+
330
+ doctest.testmod(optionflags=doctest.ELLIPSIS)