edsl 0.1.32__py3-none-any.whl → 0.1.33__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 (181) hide show
  1. edsl/Base.py +9 -3
  2. edsl/TemplateLoader.py +24 -0
  3. edsl/__init__.py +8 -3
  4. edsl/__version__.py +1 -1
  5. edsl/agents/Agent.py +40 -8
  6. edsl/agents/AgentList.py +43 -0
  7. edsl/agents/Invigilator.py +135 -219
  8. edsl/agents/InvigilatorBase.py +148 -59
  9. edsl/agents/{PromptConstructionMixin.py → PromptConstructor.py} +138 -89
  10. edsl/agents/__init__.py +1 -0
  11. edsl/auto/AutoStudy.py +117 -0
  12. edsl/auto/StageBase.py +230 -0
  13. edsl/auto/StageGenerateSurvey.py +178 -0
  14. edsl/auto/StageLabelQuestions.py +125 -0
  15. edsl/auto/StagePersona.py +61 -0
  16. edsl/auto/StagePersonaDimensionValueRanges.py +88 -0
  17. edsl/auto/StagePersonaDimensionValues.py +74 -0
  18. edsl/auto/StagePersonaDimensions.py +69 -0
  19. edsl/auto/StageQuestions.py +73 -0
  20. edsl/auto/SurveyCreatorPipeline.py +21 -0
  21. edsl/auto/utilities.py +224 -0
  22. edsl/config.py +47 -56
  23. edsl/coop/PriceFetcher.py +58 -0
  24. edsl/coop/coop.py +50 -7
  25. edsl/data/Cache.py +35 -1
  26. edsl/data_transfer_models.py +73 -38
  27. edsl/enums.py +4 -0
  28. edsl/exceptions/language_models.py +25 -1
  29. edsl/exceptions/questions.py +62 -5
  30. edsl/exceptions/results.py +4 -0
  31. edsl/inference_services/AnthropicService.py +13 -11
  32. edsl/inference_services/AwsBedrock.py +19 -17
  33. edsl/inference_services/AzureAI.py +37 -20
  34. edsl/inference_services/GoogleService.py +16 -12
  35. edsl/inference_services/GroqService.py +2 -0
  36. edsl/inference_services/InferenceServiceABC.py +58 -3
  37. edsl/inference_services/MistralAIService.py +120 -0
  38. edsl/inference_services/OpenAIService.py +48 -54
  39. edsl/inference_services/TestService.py +80 -0
  40. edsl/inference_services/TogetherAIService.py +170 -0
  41. edsl/inference_services/models_available_cache.py +0 -6
  42. edsl/inference_services/registry.py +6 -0
  43. edsl/jobs/Answers.py +10 -12
  44. edsl/jobs/FailedQuestion.py +78 -0
  45. edsl/jobs/Jobs.py +37 -22
  46. edsl/jobs/buckets/BucketCollection.py +24 -15
  47. edsl/jobs/buckets/TokenBucket.py +93 -14
  48. edsl/jobs/interviews/Interview.py +366 -78
  49. edsl/jobs/interviews/{interview_exception_tracking.py → InterviewExceptionCollection.py} +14 -68
  50. edsl/jobs/interviews/InterviewExceptionEntry.py +85 -19
  51. edsl/jobs/runners/JobsRunnerAsyncio.py +146 -175
  52. edsl/jobs/runners/JobsRunnerStatus.py +331 -0
  53. edsl/jobs/tasks/QuestionTaskCreator.py +30 -23
  54. edsl/jobs/tasks/TaskHistory.py +148 -213
  55. edsl/language_models/LanguageModel.py +261 -156
  56. edsl/language_models/ModelList.py +2 -2
  57. edsl/language_models/RegisterLanguageModelsMeta.py +14 -29
  58. edsl/language_models/fake_openai_call.py +15 -0
  59. edsl/language_models/fake_openai_service.py +61 -0
  60. edsl/language_models/registry.py +23 -6
  61. edsl/language_models/repair.py +0 -19
  62. edsl/language_models/utilities.py +61 -0
  63. edsl/notebooks/Notebook.py +20 -2
  64. edsl/prompts/Prompt.py +52 -2
  65. edsl/questions/AnswerValidatorMixin.py +23 -26
  66. edsl/questions/QuestionBase.py +330 -249
  67. edsl/questions/QuestionBaseGenMixin.py +133 -0
  68. edsl/questions/QuestionBasePromptsMixin.py +266 -0
  69. edsl/questions/QuestionBudget.py +99 -41
  70. edsl/questions/QuestionCheckBox.py +227 -35
  71. edsl/questions/QuestionExtract.py +98 -27
  72. edsl/questions/QuestionFreeText.py +52 -29
  73. edsl/questions/QuestionFunctional.py +7 -0
  74. edsl/questions/QuestionList.py +141 -22
  75. edsl/questions/QuestionMultipleChoice.py +159 -65
  76. edsl/questions/QuestionNumerical.py +88 -46
  77. edsl/questions/QuestionRank.py +182 -24
  78. edsl/questions/Quick.py +41 -0
  79. edsl/questions/RegisterQuestionsMeta.py +31 -12
  80. edsl/questions/ResponseValidatorABC.py +170 -0
  81. edsl/questions/__init__.py +3 -4
  82. edsl/questions/decorators.py +21 -0
  83. edsl/questions/derived/QuestionLikertFive.py +10 -5
  84. edsl/questions/derived/QuestionLinearScale.py +15 -2
  85. edsl/questions/derived/QuestionTopK.py +10 -1
  86. edsl/questions/derived/QuestionYesNo.py +24 -3
  87. edsl/questions/descriptors.py +43 -7
  88. edsl/questions/prompt_templates/question_budget.jinja +13 -0
  89. edsl/questions/prompt_templates/question_checkbox.jinja +32 -0
  90. edsl/questions/prompt_templates/question_extract.jinja +11 -0
  91. edsl/questions/prompt_templates/question_free_text.jinja +3 -0
  92. edsl/questions/prompt_templates/question_linear_scale.jinja +11 -0
  93. edsl/questions/prompt_templates/question_list.jinja +17 -0
  94. edsl/questions/prompt_templates/question_multiple_choice.jinja +33 -0
  95. edsl/questions/prompt_templates/question_numerical.jinja +37 -0
  96. edsl/questions/question_registry.py +6 -2
  97. edsl/questions/templates/__init__.py +0 -0
  98. edsl/questions/templates/budget/__init__.py +0 -0
  99. edsl/questions/templates/budget/answering_instructions.jinja +7 -0
  100. edsl/questions/templates/budget/question_presentation.jinja +7 -0
  101. edsl/questions/templates/checkbox/__init__.py +0 -0
  102. edsl/questions/templates/checkbox/answering_instructions.jinja +10 -0
  103. edsl/questions/templates/checkbox/question_presentation.jinja +22 -0
  104. edsl/questions/templates/extract/__init__.py +0 -0
  105. edsl/questions/templates/extract/answering_instructions.jinja +7 -0
  106. edsl/questions/templates/extract/question_presentation.jinja +1 -0
  107. edsl/questions/templates/free_text/__init__.py +0 -0
  108. edsl/questions/templates/free_text/answering_instructions.jinja +0 -0
  109. edsl/questions/templates/free_text/question_presentation.jinja +1 -0
  110. edsl/questions/templates/likert_five/__init__.py +0 -0
  111. edsl/questions/templates/likert_five/answering_instructions.jinja +10 -0
  112. edsl/questions/templates/likert_five/question_presentation.jinja +12 -0
  113. edsl/questions/templates/linear_scale/__init__.py +0 -0
  114. edsl/questions/templates/linear_scale/answering_instructions.jinja +5 -0
  115. edsl/questions/templates/linear_scale/question_presentation.jinja +5 -0
  116. edsl/questions/templates/list/__init__.py +0 -0
  117. edsl/questions/templates/list/answering_instructions.jinja +4 -0
  118. edsl/questions/templates/list/question_presentation.jinja +5 -0
  119. edsl/questions/templates/multiple_choice/__init__.py +0 -0
  120. edsl/questions/templates/multiple_choice/answering_instructions.jinja +9 -0
  121. edsl/questions/templates/multiple_choice/html.jinja +0 -0
  122. edsl/questions/templates/multiple_choice/question_presentation.jinja +12 -0
  123. edsl/questions/templates/numerical/__init__.py +0 -0
  124. edsl/questions/templates/numerical/answering_instructions.jinja +8 -0
  125. edsl/questions/templates/numerical/question_presentation.jinja +7 -0
  126. edsl/questions/templates/rank/__init__.py +0 -0
  127. edsl/questions/templates/rank/answering_instructions.jinja +11 -0
  128. edsl/questions/templates/rank/question_presentation.jinja +15 -0
  129. edsl/questions/templates/top_k/__init__.py +0 -0
  130. edsl/questions/templates/top_k/answering_instructions.jinja +8 -0
  131. edsl/questions/templates/top_k/question_presentation.jinja +22 -0
  132. edsl/questions/templates/yes_no/__init__.py +0 -0
  133. edsl/questions/templates/yes_no/answering_instructions.jinja +6 -0
  134. edsl/questions/templates/yes_no/question_presentation.jinja +12 -0
  135. edsl/results/Dataset.py +20 -0
  136. edsl/results/DatasetExportMixin.py +46 -48
  137. edsl/results/DatasetTree.py +145 -0
  138. edsl/results/Result.py +32 -5
  139. edsl/results/Results.py +135 -46
  140. edsl/results/ResultsDBMixin.py +3 -3
  141. edsl/results/Selector.py +118 -0
  142. edsl/results/tree_explore.py +115 -0
  143. edsl/scenarios/FileStore.py +71 -10
  144. edsl/scenarios/Scenario.py +96 -25
  145. edsl/scenarios/ScenarioImageMixin.py +2 -2
  146. edsl/scenarios/ScenarioList.py +361 -39
  147. edsl/scenarios/ScenarioListExportMixin.py +9 -0
  148. edsl/scenarios/ScenarioListPdfMixin.py +150 -4
  149. edsl/study/SnapShot.py +8 -1
  150. edsl/study/Study.py +32 -0
  151. edsl/surveys/Rule.py +10 -1
  152. edsl/surveys/RuleCollection.py +21 -5
  153. edsl/surveys/Survey.py +637 -311
  154. edsl/surveys/SurveyExportMixin.py +71 -9
  155. edsl/surveys/SurveyFlowVisualizationMixin.py +2 -1
  156. edsl/surveys/SurveyQualtricsImport.py +75 -4
  157. edsl/surveys/instructions/ChangeInstruction.py +47 -0
  158. edsl/surveys/instructions/Instruction.py +34 -0
  159. edsl/surveys/instructions/InstructionCollection.py +77 -0
  160. edsl/surveys/instructions/__init__.py +0 -0
  161. edsl/templates/error_reporting/base.html +24 -0
  162. edsl/templates/error_reporting/exceptions_by_model.html +35 -0
  163. edsl/templates/error_reporting/exceptions_by_question_name.html +17 -0
  164. edsl/templates/error_reporting/exceptions_by_type.html +17 -0
  165. edsl/templates/error_reporting/interview_details.html +116 -0
  166. edsl/templates/error_reporting/interviews.html +10 -0
  167. edsl/templates/error_reporting/overview.html +5 -0
  168. edsl/templates/error_reporting/performance_plot.html +2 -0
  169. edsl/templates/error_reporting/report.css +74 -0
  170. edsl/templates/error_reporting/report.html +118 -0
  171. edsl/templates/error_reporting/report.js +25 -0
  172. edsl/utilities/utilities.py +9 -1
  173. {edsl-0.1.32.dist-info → edsl-0.1.33.dist-info}/METADATA +5 -2
  174. edsl-0.1.33.dist-info/RECORD +295 -0
  175. edsl/jobs/interviews/InterviewTaskBuildingMixin.py +0 -286
  176. edsl/jobs/interviews/retry_management.py +0 -37
  177. edsl/jobs/runners/JobsRunnerStatusMixin.py +0 -333
  178. edsl/utilities/gcp_bucket/simple_example.py +0 -9
  179. edsl-0.1.32.dist-info/RECORD +0 -209
  180. {edsl-0.1.32.dist-info → edsl-0.1.33.dist-info}/LICENSE +0 -0
  181. {edsl-0.1.32.dist-info → edsl-0.1.33.dist-info}/WHEEL +0 -0
@@ -1,7 +1,10 @@
1
- from edsl.jobs.tasks.task_status_enum import TaskStatus
2
1
  from typing import List, Optional
3
2
  from io import BytesIO
3
+ import webbrowser
4
+ import os
4
5
  import base64
6
+ from importlib import resources
7
+ from edsl.jobs.tasks.task_status_enum import TaskStatus
5
8
 
6
9
 
7
10
  class TaskHistory:
@@ -30,7 +33,12 @@ class TaskHistory:
30
33
 
31
34
  from edsl.config import CONFIG
32
35
 
33
- results = j.run(print_exceptions=False, skip_retry=True, cache=False)
36
+ results = j.run(
37
+ print_exceptions=False,
38
+ skip_retry=True,
39
+ cache=False,
40
+ raise_validation_errors=True,
41
+ )
34
42
 
35
43
  return cls(results.task_history.total_interviews)
36
44
 
@@ -42,6 +50,18 @@ class TaskHistory:
42
50
  """
43
51
  return [i.exceptions for k, i in self._interviews.items() if i.exceptions != {}]
44
52
 
53
+ @property
54
+ def unfixed_exceptions(self):
55
+ """
56
+ >>> len(TaskHistory.example().unfixed_exceptions)
57
+ 4
58
+ """
59
+ return [
60
+ i.exceptions
61
+ for k, i in self._interviews.items()
62
+ if i.exceptions.num_unfixed() > 0
63
+ ]
64
+
45
65
  @property
46
66
  def indices(self):
47
67
  return [k for k, i in self._interviews.items() if i.exceptions != {}]
@@ -70,6 +90,11 @@ class TaskHistory:
70
90
  """
71
91
  return len(self.exceptions) > 0
72
92
 
93
+ @property
94
+ def has_unfixed_exceptions(self) -> bool:
95
+ """Return True if there are any exceptions."""
96
+ return len(self.unfixed_exceptions) > 0
97
+
73
98
  def _repr_html_(self):
74
99
  """Return an HTML representation of the TaskHistory."""
75
100
  from edsl.utilities.utilities import data_to_html
@@ -188,58 +213,14 @@ class TaskHistory:
188
213
  plt.show()
189
214
 
190
215
  def css(self):
191
- return """
192
- body {
193
- font-family: Arial, sans-serif;
194
- line-height: 1.6;
195
- background-color: #f9f9f9;
196
- color: #333;
197
- margin: 20px;
198
- }
199
-
200
- .interview {
201
- font-size: 1.5em;
202
- margin-bottom: 10px;
203
- padding: 10px;
204
- background-color: #e3f2fd;
205
- border-left: 5px solid #2196f3;
206
- }
207
-
208
- .question {
209
- font-size: 1.2em;
210
- margin-bottom: 10px;
211
- padding: 10px;
212
- background-color: #fff9c4;
213
- border-left: 5px solid #ffeb3b;
214
- }
215
-
216
- .exception-detail {
217
- margin-bottom: 10px;
218
- padding: 10px;
219
- background-color: #ffebee;
220
- border-left: 5px solid #f44336;
221
- }
222
-
223
- .question-detail {
224
- border: 3px solid black; /* Adjust the thickness by changing the number */
225
- padding: 10px; /* Optional: Adds some padding inside the border */
226
- }
216
+ env = resources.files("edsl").joinpath("templates/error_reporting")
217
+ css = env.joinpath("report.css").read_text()
218
+ return css
227
219
 
228
- .exception-detail div {
229
- margin-bottom: 5px;
230
- }
231
-
232
- .exception-exception {
233
- font-weight: bold;
234
- color: #d32f2f;
235
- }
236
-
237
- .exception-time,
238
- .exception-traceback {
239
- font-style: italic;
240
- color: #555;
241
- }
242
- """
220
+ def javascript(self):
221
+ env = resources.files("edsl").joinpath("templates/error_reporting")
222
+ js = env.joinpath("report.js").read_text()
223
+ return js
243
224
 
244
225
  @property
245
226
  def exceptions_by_type(self) -> dict:
@@ -248,206 +229,136 @@ class TaskHistory:
248
229
  for interview in self.total_interviews:
249
230
  for question_name, exceptions in interview.exceptions.items():
250
231
  for exception in exceptions:
251
- exception_type = exception["exception"]
232
+ exception_type = exception.exception.__class__.__name__
233
+ # exception_type = exception["exception"]
234
+ # breakpoint()
252
235
  if exception_type in exceptions_by_type:
253
236
  exceptions_by_type[exception_type] += 1
254
237
  else:
255
238
  exceptions_by_type[exception_type] = 1
256
239
  return exceptions_by_type
257
240
 
241
+ @property
242
+ def exceptions_by_service(self) -> dict:
243
+ """Return a dictionary of exceptions tallied by service."""
244
+ exceptions_by_service = {}
245
+ for interview in self.total_interviews:
246
+ service = interview.model._inference_service_
247
+ if service not in exceptions_by_service:
248
+ exceptions_by_service[service] = 0
249
+ if interview.exceptions != {}:
250
+ exceptions_by_service[service] += len(interview.exceptions)
251
+ return exceptions_by_service
252
+
258
253
  @property
259
254
  def exceptions_by_question_name(self) -> dict:
260
255
  """Return a dictionary of exceptions tallied by question name."""
261
256
  exceptions_by_question_name = {}
262
257
  for interview in self.total_interviews:
263
258
  for question_name, exceptions in interview.exceptions.items():
264
- if question_name not in exceptions_by_question_name:
265
- exceptions_by_question_name[question_name] = 0
266
- exceptions_by_question_name[question_name] += len(exceptions)
259
+ question_type = interview.survey.get_question(
260
+ question_name
261
+ ).question_type
262
+ # breakpoint()
263
+ if (question_name, question_type) not in exceptions_by_question_name:
264
+ exceptions_by_question_name[(question_name, question_type)] = 0
265
+ exceptions_by_question_name[(question_name, question_type)] += len(
266
+ exceptions
267
+ )
267
268
 
268
269
  for question in self.total_interviews[0].survey.questions:
269
- if question.question_name not in exceptions_by_question_name:
270
- exceptions_by_question_name[question.question_name] = 0
271
- return exceptions_by_question_name
270
+ if (
271
+ question.question_name,
272
+ question.question_type,
273
+ ) not in exceptions_by_question_name:
274
+ exceptions_by_question_name[
275
+ (question.question_name, question.question_type)
276
+ ] = 0
277
+
278
+ sorted_exceptions_by_question_name = {
279
+ k: v
280
+ for k, v in sorted(
281
+ exceptions_by_question_name.items(),
282
+ key=lambda item: item[1],
283
+ reverse=True,
284
+ )
285
+ }
286
+ return sorted_exceptions_by_question_name
272
287
 
273
288
  @property
274
289
  def exceptions_by_model(self) -> dict:
275
290
  """Return a dictionary of exceptions tallied by model and question name."""
276
291
  exceptions_by_model = {}
277
292
  for interview in self.total_interviews:
278
- model = interview.model
279
- if model not in exceptions_by_model:
280
- exceptions_by_model[model.model] = 0
293
+ model = interview.model.model
294
+ service = interview.model._inference_service_
295
+ if (service, model) not in exceptions_by_model:
296
+ exceptions_by_model[(service, model)] = 0
281
297
  if interview.exceptions != {}:
282
- exceptions_by_model[model.model] += len(interview.exceptions)
283
- return exceptions_by_model
284
-
285
- def html(
286
- self,
287
- filename: Optional[str] = None,
288
- return_link=False,
289
- css=None,
290
- cta="Open Report in New Tab",
291
- ):
292
- """Return an HTML report."""
293
-
294
- from IPython.display import display, HTML
295
- import tempfile
296
- import os
297
- from edsl.utilities.utilities import is_notebook
298
- from jinja2 import Template
298
+ exceptions_by_model[(service, model)] += len(interview.exceptions)
299
+
300
+ # sort the exceptions by model
301
+ sorted_exceptions_by_model = {
302
+ k: v
303
+ for k, v in sorted(
304
+ exceptions_by_model.items(), key=lambda item: item[1], reverse=True
305
+ )
306
+ }
307
+ return sorted_exceptions_by_model
299
308
 
309
+ def generate_html_report(self, css: Optional[str]):
300
310
  performance_plot_html = self.plot(num_periods=100, get_embedded_html=True)
301
311
 
302
312
  if css is None:
303
313
  css = self.css()
304
314
 
305
- models_used = set([i.model for index, i in self._interviews.items()])
315
+ models_used = set([i.model.model for index, i in self._interviews.items()])
306
316
 
307
- template = Template(
308
- """
309
- <!DOCTYPE html>
310
- <html lang="en">
311
- <head>
312
- <meta charset="UTF-8">
313
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
314
- <title>Exception Details</title>
315
- <style>
316
- {{ css }}
317
- </style>
318
- </head>
319
- <body>
320
- <h1>Overview</h1>
321
- <p>There were {{ interviews|length }} total interviews. The number of interviews with exceptions was {{ num_exceptions }}.</p>
322
- <p>The models used were: {{ models_used }}.</p>
323
- <p>For documentation on dealing with exceptions on Expected Parrot,
324
- see <a href="https://docs.expectedparrot.com/en/latest/exceptions.html">here</a>.</p>
325
-
326
- <h2>Exceptions by Type</h2>
327
- <table>
328
- <thead>
329
- <tr>
330
- <th>Exception Type</th>
331
- <th>Number</th>
332
- </tr>
333
- </thead>
334
- <tbody>
335
- {% for exception_type, exceptions in exceptions_by_type.items() %}
336
- <tr>
337
- <td>{{ exception_type }}</td>
338
- <td>{{ exceptions }}</td>
339
- </tr>
340
- {% endfor %}
341
- </tbody>
342
- </table>
343
-
344
-
345
- <h2>Exceptions by Model</h2>
346
- <table>
347
- <thead>
348
- <tr>
349
- <th>Model</th>
350
- <th>Number</th>
351
- </tr>
352
- </thead>
353
- <tbody>
354
- {% for model, exceptions in exceptions_by_model.items() %}
355
- <tr>
356
- <td>{{ model }}</td>
357
- <td>{{ exceptions }}</td>
358
- </tr>
359
- {% endfor %}
360
- </tbody>
361
- </table>
362
-
363
-
364
- <h2>Exceptions by Question Name</h2>
365
- <table>
366
- <thead>
367
- <tr>
368
- <th>Question Name</th>
369
- <th>Number of Exceptions</th>
370
- </tr>
371
- </thead>
372
- <tbody>
373
- {% for question_name, exception_count in exceptions_by_question_name.items() %}
374
- <tr>
375
- <td>{{ question_name }}</td>
376
- <td>{{ exception_count }}</td>
377
- </tr>
378
- {% endfor %}
379
- </tbody>
380
- </table>
381
-
382
-
383
- {% for index, interview in interviews.items() %}
384
- {% if interview.exceptions != {} %}
385
- <div class="interview">Interview: {{ index }} </div>
386
- <h1>Failing questions</h1>
387
- {% endif %}
388
- {% for question, exceptions in interview.exceptions.items() %}
389
- <div class="question">question_name: {{ question }}</div>
390
-
391
- <h2>Question</h2>
392
- <div class="question-detail">
393
- {{ interview.survey.get_question(question).html() }}
394
- </div>
395
-
396
- <h2>Scenario</h2>
397
- <div class="scenario">
398
- {{ interview.scenario._repr_html_() }}
399
- </div>
400
-
401
- <h2>Agent</h2>
402
- <div class="agent">
403
- {{ interview.agent._repr_html_() }}
404
- </div>
405
-
406
- <h2>Model</h2>
407
- <div class="model">
408
- {{ interview.model._repr_html_() }}
409
- </div>
410
-
411
- <h2>Exception details</h2>
412
-
413
- {% for exception_message in exceptions %}
414
- <div class="exception-detail">
415
- <div class="exception-exception">Exception: {{ exception_message.exception }}</div>
416
- <div class="exception-time">Time: {{ exception_message.time }}</div>
417
- <div class="exception-traceback">Traceback: <pre>{{ exception_message.traceback }} </pre></div>
418
- </div>
419
- {% endfor %}
420
- {% endfor %}
421
- {% endfor %}
422
-
423
- <h1>Performance Plot</h1>
424
- {{ performance_plot_html }}
425
- </body>
426
- </html>
427
- """
428
- )
317
+ from jinja2 import Environment, FileSystemLoader
318
+ from edsl.TemplateLoader import TemplateLoader
429
319
 
430
- # breakpoint()
320
+ env = Environment(loader=TemplateLoader("edsl", "templates/error_reporting"))
321
+
322
+ # Load and render a template
323
+ template = env.get_template("base.html")
324
+ # rendered_template = template.render(your_data=your_data)
431
325
 
432
326
  # Render the template with data
433
327
  output = template.render(
434
328
  interviews=self._interviews,
435
329
  css=css,
330
+ javascript=self.javascript(),
436
331
  num_exceptions=len(self.exceptions),
437
332
  performance_plot_html=performance_plot_html,
438
333
  exceptions_by_type=self.exceptions_by_type,
439
334
  exceptions_by_question_name=self.exceptions_by_question_name,
440
335
  exceptions_by_model=self.exceptions_by_model,
336
+ exceptions_by_service=self.exceptions_by_service,
441
337
  models_used=models_used,
442
338
  )
339
+ return output
340
+
341
+ def html(
342
+ self,
343
+ filename: Optional[str] = None,
344
+ return_link=False,
345
+ css=None,
346
+ cta="Open Report in New Tab",
347
+ open_in_browser=True,
348
+ ):
349
+ """Return an HTML report."""
350
+
351
+ from IPython.display import display, HTML
352
+ import tempfile
353
+ import os
354
+ from edsl.utilities.utilities import is_notebook
355
+
356
+ output = self.generate_html_report(css)
443
357
 
444
358
  # Save the rendered output to a file
445
359
  with open("output.html", "w") as f:
446
360
  f.write(output)
447
361
 
448
- if css is None:
449
- css = self.css()
450
-
451
362
  if filename is None:
452
363
  current_directory = os.getcwd()
453
364
  filename = tempfile.NamedTemporaryFile(
@@ -456,10 +367,7 @@ class TaskHistory:
456
367
 
457
368
  with open(filename, "w") as f:
458
369
  with open(filename, "w") as f:
459
- # f.write(html_header)
460
- # f.write(self._repr_html_())
461
370
  f.write(output)
462
- # f.write(html_footer)
463
371
 
464
372
  if is_notebook():
465
373
  import html
@@ -472,17 +380,44 @@ class TaskHistory:
472
380
  <iframe srcdoc="{ escaped_output }" style="width: 800px; height: 600px;"></iframe>
473
381
  """
474
382
  display(HTML(iframe))
475
- # display(HTML(output))
476
383
  else:
477
384
  print(f"Exception report saved to {filename}")
478
- import webbrowser
479
- import os
480
385
 
386
+ if open_in_browser:
481
387
  webbrowser.open(f"file://{os.path.abspath(filename)}")
482
388
 
483
389
  if return_link:
484
390
  return filename
485
391
 
392
+ def notebook(self):
393
+ """Create a notebook with the HTML content embedded in the first cell, then delete the cell content while keeping the output."""
394
+ from nbformat import v4 as nbf
395
+ from nbconvert.preprocessors import ExecutePreprocessor
396
+ import nbformat
397
+ import os
398
+
399
+ # Use the existing html method to generate the HTML content
400
+ output_html = self.generate_html_report(css=None)
401
+ nb = nbf.new_notebook()
402
+
403
+ # Add a code cell that renders the HTML content
404
+ code_cell = nbf.new_code_cell(
405
+ f"""
406
+ from IPython.display import HTML, display
407
+ display(HTML('''{output_html}'''))
408
+ """
409
+ )
410
+ nb.cells.append(code_cell)
411
+
412
+ # Execute the notebook
413
+ ep = ExecutePreprocessor(timeout=600, kernel_name="python3")
414
+ ep.preprocess(nb, {"metadata": {"path": os.getcwd()}})
415
+
416
+ # After execution, clear the cell's source code
417
+ nb.cells[0].source = ""
418
+
419
+ return nb
420
+
486
421
 
487
422
  if __name__ == "__main__":
488
423
  import doctest