edsl 0.1.40.dev2__py3-none-any.whl → 0.1.42__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- edsl/__init__.py +1 -0
- edsl/__version__.py +1 -1
- edsl/agents/Agent.py +1 -1
- edsl/agents/Invigilator.py +6 -4
- edsl/agents/InvigilatorBase.py +2 -1
- edsl/agents/QuestionTemplateReplacementsBuilder.py +7 -2
- edsl/coop/coop.py +37 -2
- edsl/data/Cache.py +7 -0
- edsl/data/RemoteCacheSync.py +16 -16
- edsl/enums.py +3 -0
- edsl/exceptions/jobs.py +1 -9
- edsl/exceptions/language_models.py +8 -4
- edsl/exceptions/questions.py +8 -11
- edsl/inference_services/DeepSeekService.py +18 -0
- edsl/inference_services/registry.py +2 -0
- edsl/jobs/AnswerQuestionFunctionConstructor.py +1 -1
- edsl/jobs/Jobs.py +42 -34
- edsl/jobs/JobsPrompts.py +11 -1
- edsl/jobs/JobsRemoteInferenceHandler.py +1 -0
- edsl/jobs/JobsRemoteInferenceLogger.py +1 -1
- edsl/jobs/interviews/Interview.py +2 -6
- edsl/jobs/interviews/InterviewExceptionEntry.py +14 -4
- edsl/jobs/loggers/HTMLTableJobLogger.py +6 -1
- edsl/jobs/results_exceptions_handler.py +2 -7
- edsl/jobs/runners/JobsRunnerAsyncio.py +18 -6
- edsl/jobs/runners/JobsRunnerStatus.py +2 -1
- edsl/jobs/tasks/TaskHistory.py +49 -17
- edsl/language_models/LanguageModel.py +7 -4
- edsl/language_models/ModelList.py +1 -1
- edsl/language_models/key_management/KeyLookupBuilder.py +7 -3
- edsl/language_models/model.py +49 -0
- edsl/questions/QuestionBudget.py +2 -2
- edsl/questions/QuestionDict.py +343 -0
- edsl/questions/QuestionExtract.py +1 -1
- edsl/questions/__init__.py +1 -0
- edsl/questions/answer_validator_mixin.py +29 -0
- edsl/questions/derived/QuestionLinearScale.py +1 -1
- edsl/questions/descriptors.py +49 -5
- edsl/questions/question_registry.py +1 -1
- edsl/questions/templates/dict/__init__.py +0 -0
- edsl/questions/templates/dict/answering_instructions.jinja +21 -0
- edsl/questions/templates/dict/question_presentation.jinja +1 -0
- edsl/results/Result.py +25 -3
- edsl/results/Results.py +17 -5
- edsl/scenarios/FileStore.py +32 -0
- edsl/scenarios/PdfExtractor.py +3 -6
- edsl/scenarios/Scenario.py +2 -1
- edsl/scenarios/handlers/csv.py +11 -0
- edsl/surveys/Survey.py +5 -1
- edsl/templates/error_reporting/base.html +2 -4
- edsl/templates/error_reporting/exceptions_table.html +35 -0
- edsl/templates/error_reporting/interview_details.html +67 -53
- edsl/templates/error_reporting/interviews.html +4 -17
- edsl/templates/error_reporting/overview.html +31 -5
- edsl/templates/error_reporting/performance_plot.html +1 -1
- {edsl-0.1.40.dev2.dist-info → edsl-0.1.42.dist-info}/METADATA +1 -1
- {edsl-0.1.40.dev2.dist-info → edsl-0.1.42.dist-info}/RECORD +59 -53
- {edsl-0.1.40.dev2.dist-info → edsl-0.1.42.dist-info}/LICENSE +0 -0
- {edsl-0.1.40.dev2.dist-info → edsl-0.1.42.dist-info}/WHEEL +0 -0
edsl/results/Results.py
CHANGED
@@ -90,6 +90,7 @@ class Results(UserList, Mixins, Base):
|
|
90
90
|
"comment",
|
91
91
|
"generated_tokens",
|
92
92
|
"cache_used",
|
93
|
+
"cache_keys",
|
93
94
|
]
|
94
95
|
|
95
96
|
def __init__(
|
@@ -109,6 +110,7 @@ class Results(UserList, Mixins, Base):
|
|
109
110
|
:param created_columns: A list of strings that are created columns.
|
110
111
|
:param job_uuid: A string representing the job UUID.
|
111
112
|
:param total_results: An integer representing the total number of results.
|
113
|
+
:cache: A Cache object.
|
112
114
|
"""
|
113
115
|
super().__init__(data)
|
114
116
|
from edsl.data.Cache import Cache
|
@@ -138,6 +140,16 @@ class Results(UserList, Mixins, Base):
|
|
138
140
|
}
|
139
141
|
return d
|
140
142
|
|
143
|
+
def _cache_keys(self):
|
144
|
+
cache_keys = []
|
145
|
+
for result in self:
|
146
|
+
cache_keys.extend(list(result["cache_keys"].values()))
|
147
|
+
return cache_keys
|
148
|
+
|
149
|
+
def relevant_cache(self, cache: Cache) -> Cache:
|
150
|
+
cache_keys = self._cache_keys()
|
151
|
+
return cache.subset(cache_keys)
|
152
|
+
|
141
153
|
def insert(self, item):
|
142
154
|
item_order = getattr(item, "order", None)
|
143
155
|
if item_order is not None:
|
@@ -170,12 +182,12 @@ class Results(UserList, Mixins, Base):
|
|
170
182
|
"""
|
171
183
|
total_cost = 0
|
172
184
|
for result in self:
|
173
|
-
for key in result
|
185
|
+
for key in result["raw_model_response"]:
|
174
186
|
if key.endswith("_cost"):
|
175
|
-
result_cost = result
|
187
|
+
result_cost = result["raw_model_response"][key]
|
176
188
|
|
177
189
|
question_name = key.removesuffix("_cost")
|
178
|
-
cache_used = result
|
190
|
+
cache_used = result["cache_used_dict"][question_name]
|
179
191
|
|
180
192
|
if isinstance(result_cost, (int, float)):
|
181
193
|
if include_cached_responses_in_cost:
|
@@ -349,7 +361,7 @@ class Results(UserList, Mixins, Base):
|
|
349
361
|
self,
|
350
362
|
sort: bool = False,
|
351
363
|
add_edsl_version: bool = False,
|
352
|
-
include_cache: bool =
|
364
|
+
include_cache: bool = True,
|
353
365
|
include_task_history: bool = False,
|
354
366
|
include_cache_info: bool = True,
|
355
367
|
) -> dict[str, Any]:
|
@@ -635,7 +647,7 @@ class Results(UserList, Mixins, Base):
|
|
635
647
|
|
636
648
|
>>> r = Results.example()
|
637
649
|
>>> r.model_keys
|
638
|
-
['frequency_penalty', 'logprobs', 'max_tokens', 'model', 'model_index', 'presence_penalty', 'temperature', 'top_logprobs', 'top_p']
|
650
|
+
['frequency_penalty', 'inference_service', 'logprobs', 'max_tokens', 'model', 'model_index', 'presence_penalty', 'temperature', 'top_logprobs', 'top_p']
|
639
651
|
"""
|
640
652
|
return sorted(self._data_type_to_keys["model"])
|
641
653
|
|
edsl/scenarios/FileStore.py
CHANGED
@@ -327,6 +327,38 @@ class FileStore(Scenario):
|
|
327
327
|
|
328
328
|
return ConstructDownloadLink(self).create_link(custom_filename, style)
|
329
329
|
|
330
|
+
def to_pandas(self):
|
331
|
+
"""
|
332
|
+
Convert the file content to a pandas DataFrame if supported by the file handler.
|
333
|
+
|
334
|
+
Returns:
|
335
|
+
pandas.DataFrame: The data from the file as a DataFrame
|
336
|
+
|
337
|
+
Raises:
|
338
|
+
AttributeError: If the file type's handler doesn't support pandas conversion
|
339
|
+
"""
|
340
|
+
handler = FileMethods.get_handler(self.suffix)
|
341
|
+
if handler and hasattr(handler, "to_pandas"):
|
342
|
+
return handler(self.path).to_pandas()
|
343
|
+
raise AttributeError(
|
344
|
+
f"Converting {self.suffix} files to pandas DataFrame is not supported"
|
345
|
+
)
|
346
|
+
|
347
|
+
def __getattr__(self, name):
|
348
|
+
"""
|
349
|
+
Delegate pandas DataFrame methods to the underlying DataFrame if this is a CSV file
|
350
|
+
"""
|
351
|
+
if self.suffix == "csv":
|
352
|
+
# Get the pandas DataFrame
|
353
|
+
df = self.to_pandas()
|
354
|
+
# Check if the requested attribute exists in the DataFrame
|
355
|
+
if hasattr(df, name):
|
356
|
+
return getattr(df, name)
|
357
|
+
# If not a CSV or attribute doesn't exist in DataFrame, raise AttributeError
|
358
|
+
raise AttributeError(
|
359
|
+
f"'{self.__class__.__name__}' object has no attribute '{name}'"
|
360
|
+
)
|
361
|
+
|
330
362
|
|
331
363
|
class CSVFileStore(FileStore):
|
332
364
|
@classmethod
|
edsl/scenarios/PdfExtractor.py
CHANGED
@@ -2,14 +2,11 @@ import os
|
|
2
2
|
|
3
3
|
|
4
4
|
class PdfExtractor:
|
5
|
-
def __init__(self, pdf_path: str
|
5
|
+
def __init__(self, pdf_path: str):
|
6
6
|
self.pdf_path = pdf_path
|
7
|
-
self.constructor = parent_object.__class__
|
7
|
+
#self.constructor = parent_object.__class__
|
8
8
|
|
9
|
-
def
|
10
|
-
return self.constructor(self._get_pdf_dict())
|
11
|
-
|
12
|
-
def _get_pdf_dict(self) -> dict:
|
9
|
+
def get_pdf_dict(self) -> dict:
|
13
10
|
# Ensure the file exists
|
14
11
|
import fitz
|
15
12
|
|
edsl/scenarios/Scenario.py
CHANGED
@@ -358,7 +358,8 @@ class Scenario(Base, UserDict, ScenarioHtmlMixin):
|
|
358
358
|
def from_pdf(cls, pdf_path: str):
|
359
359
|
from edsl.scenarios.PdfExtractor import PdfExtractor
|
360
360
|
|
361
|
-
|
361
|
+
extractor = PdfExtractor(pdf_path)
|
362
|
+
return Scenario(extractor.get_pdf_dict())
|
362
363
|
|
363
364
|
@classmethod
|
364
365
|
def from_docx(cls, docx_path: str) -> "Scenario":
|
edsl/scenarios/handlers/csv.py
CHANGED
@@ -36,3 +36,14 @@ class CsvMethods(FileMethods):
|
|
36
36
|
with tempfile.NamedTemporaryFile(delete=False, suffix=".csv") as f:
|
37
37
|
df.to_csv(f.name, index=False)
|
38
38
|
return f.name
|
39
|
+
|
40
|
+
def to_pandas(self):
|
41
|
+
"""
|
42
|
+
Convert the CSV file to a pandas DataFrame.
|
43
|
+
|
44
|
+
Returns:
|
45
|
+
pandas.DataFrame: The data from the CSV as a DataFrame
|
46
|
+
"""
|
47
|
+
import pandas as pd
|
48
|
+
|
49
|
+
return pd.read_csv(self.path)
|
edsl/surveys/Survey.py
CHANGED
@@ -391,6 +391,10 @@ class Survey(SurveyExportMixin, Base):
|
|
391
391
|
|
392
392
|
if (class_name := pass_dict.get("edsl_class_name")) == "QuestionBase":
|
393
393
|
return QuestionBase
|
394
|
+
elif pass_dict.get("edsl_class_name") == "QuestionDict":
|
395
|
+
from edsl.questions.QuestionDict import QuestionDict
|
396
|
+
|
397
|
+
return QuestionDict
|
394
398
|
elif class_name == "Instruction":
|
395
399
|
from edsl.surveys.instructions.Instruction import Instruction
|
396
400
|
|
@@ -1277,4 +1281,4 @@ if __name__ == "__main__":
|
|
1277
1281
|
import doctest
|
1278
1282
|
|
1279
1283
|
# doctest.testmod(optionflags=doctest.ELLIPSIS | doctest.SKIP)
|
1280
|
-
doctest.testmod(optionflags=doctest.ELLIPSIS)
|
1284
|
+
doctest.testmod(optionflags=doctest.ELLIPSIS)
|
@@ -3,7 +3,7 @@
|
|
3
3
|
<head>
|
4
4
|
<meta charset="UTF-8">
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
6
|
-
<title>
|
6
|
+
<title>Exceptions Report</title>
|
7
7
|
<style>
|
8
8
|
{{ css }}
|
9
9
|
</style>
|
@@ -15,9 +15,7 @@
|
|
15
15
|
</head>
|
16
16
|
<body>
|
17
17
|
{% include 'overview.html' %}
|
18
|
-
{% include '
|
19
|
-
{% include 'exceptions_by_model.html' %}
|
20
|
-
{% include 'exceptions_by_question_name.html' %}
|
18
|
+
{% include 'exceptions_table.html' %}
|
21
19
|
{% include 'interviews.html' %}
|
22
20
|
{% include 'performance_plot.html' %}
|
23
21
|
</body>
|
@@ -0,0 +1,35 @@
|
|
1
|
+
<style>
|
2
|
+
th, td {
|
3
|
+
padding: 0 10px; /* This applies the padding uniformly to all td elements */
|
4
|
+
}
|
5
|
+
</style>
|
6
|
+
|
7
|
+
<table border="1">
|
8
|
+
<thead>
|
9
|
+
<tr>
|
10
|
+
<th>Exception Type</th>
|
11
|
+
<th>Service</th>
|
12
|
+
<th>Model</th>
|
13
|
+
<th>Question Name</th>
|
14
|
+
<th>Total</th>
|
15
|
+
</tr>
|
16
|
+
</thead>
|
17
|
+
<tbody>
|
18
|
+
{% for (exception_type, service, model, question_name), count in exceptions_table.items() %}
|
19
|
+
<tr>
|
20
|
+
<td>{{ exception_type }}</td>
|
21
|
+
<td>{{ service }}</td>
|
22
|
+
<td>{{ model }}</td>
|
23
|
+
<td>{{ question_name }}</td>
|
24
|
+
<td>{{ count }}</td>
|
25
|
+
</tr>
|
26
|
+
{% endfor %}
|
27
|
+
</tbody>
|
28
|
+
</table>
|
29
|
+
<p>
|
30
|
+
<i>Note:</i> You may encounter repeated exceptions where retries were attempted.
|
31
|
+
You can modify the maximum number of attempts for failed API calls in `edsl/config.py`.
|
32
|
+
</p>
|
33
|
+
<p>
|
34
|
+
Click to expand the details below for information about each exception, including code for reproducing it.
|
35
|
+
</p>
|
@@ -1,43 +1,67 @@
|
|
1
|
-
<
|
2
|
-
|
1
|
+
<style>
|
2
|
+
td {
|
3
|
+
padding: 0 10px; /* This applies the padding uniformly to all td elements */
|
4
|
+
}
|
5
|
+
.toggle-btn {
|
6
|
+
background-color: #4CAF50;
|
7
|
+
color: white;
|
8
|
+
border: none;
|
9
|
+
padding: 10px 20px;
|
10
|
+
text-align: center;
|
11
|
+
text-decoration: none;
|
12
|
+
display: inline-block;
|
13
|
+
font-size: 16px;
|
14
|
+
margin: 4px 2px;
|
15
|
+
cursor: pointer;
|
16
|
+
border-radius: 8px;
|
17
|
+
white-space: nowrap;
|
18
|
+
}
|
19
|
+
.toggle-btn span.collapse {
|
20
|
+
display: none;
|
21
|
+
}
|
22
|
+
.exception-content {
|
23
|
+
max-width: 100%; /* Adjust this value based on your layout */
|
24
|
+
overflow-x: auto; /* Enables horizontal scrolling */
|
25
|
+
}
|
26
|
+
</style>
|
3
27
|
|
4
|
-
<
|
28
|
+
<div class="question">question_name: {{ question }}</div>
|
5
29
|
|
6
30
|
{% for exception_message in exceptions %}
|
7
31
|
<div class="exception-detail">
|
8
|
-
|
32
|
+
<div class="exception-header">
|
9
33
|
<span class="exception-exception">Exception: {{ exception_message.name }}</span>
|
10
|
-
<button class="toggle-btn"
|
11
|
-
|
12
|
-
|
34
|
+
<button id="toggleBtn" class="toggle-btn" onclick="toggleButton(this)" aria-expanded="false">
|
35
|
+
<span class="expand"> ▼ </span>
|
36
|
+
</button>
|
37
|
+
</div>
|
38
|
+
<div class="exception-content">
|
13
39
|
<table border="1">
|
14
|
-
<tr>
|
15
|
-
<th>Key</th>
|
16
|
-
<th>Value</th>
|
17
|
-
</tr>
|
18
40
|
<tr>
|
19
41
|
<td>Interview ID (index in results)</td>
|
20
42
|
<td>{{ index }}</td>
|
21
43
|
</tr>
|
22
44
|
<tr>
|
23
|
-
<td>Question name
|
45
|
+
<td>Question name</td>
|
24
46
|
<td>{{ question }}</td>
|
25
47
|
</tr>
|
26
|
-
|
27
48
|
<tr>
|
28
|
-
<td>Question type
|
49
|
+
<td>Question type</td>
|
29
50
|
<td>{{ exception_message.question_type }}</td>
|
30
51
|
</tr>
|
31
|
-
|
32
52
|
<tr>
|
33
53
|
<td>Human-readable question</td>
|
34
54
|
<td>{{ interview.survey._get_question_by_name(question).html(
|
35
55
|
scenario = interview.scenario,
|
36
56
|
agent = interview.agent,
|
37
|
-
answers = exception_message.answers
|
38
|
-
|
57
|
+
answers = exception_message.answers
|
58
|
+
)
|
39
59
|
}}</td>
|
40
60
|
</tr>
|
61
|
+
<tr>
|
62
|
+
<td>User Prompt</td>
|
63
|
+
<td><pre>{{ exception_message.rendered_prompts['user_prompt'] }}</pre></td>
|
64
|
+
</tr>
|
41
65
|
<tr>
|
42
66
|
<td>Scenario</td>
|
43
67
|
<td>{{ interview.scenario.__repr__() }}</td>
|
@@ -47,24 +71,20 @@
|
|
47
71
|
<td>{{ interview.agent.__repr__() }}</td>
|
48
72
|
</tr>
|
49
73
|
<tr>
|
50
|
-
<td>
|
51
|
-
<td>{{
|
74
|
+
<td>System Prompt</td>
|
75
|
+
<td><pre>{{ exception_message.rendered_prompts['system_prompt'] }}</pre></td>
|
52
76
|
</tr>
|
53
77
|
<tr>
|
54
78
|
<td>Inference service</td>
|
55
79
|
<td>{{ interview.model._inference_service_ }}</td>
|
56
80
|
</tr>
|
57
81
|
<tr>
|
58
|
-
<td>Model
|
59
|
-
<td>{{ interview.model.
|
60
|
-
</tr>
|
61
|
-
<tr>
|
62
|
-
<td>User Prompt</td>
|
63
|
-
<td><pre>{{ exception_message.rendered_prompts['user_prompt'] }}</pre></td>
|
82
|
+
<td>Model name</td>
|
83
|
+
<td>{{ interview.model.model }}</td>
|
64
84
|
</tr>
|
65
85
|
<tr>
|
66
|
-
<td>
|
67
|
-
<td
|
86
|
+
<td>Model parameters</td>
|
87
|
+
<td>{{ interview.model.__repr__() }}</td>
|
68
88
|
</tr>
|
69
89
|
<tr>
|
70
90
|
<td>Raw model response</td>
|
@@ -77,7 +97,7 @@
|
|
77
97
|
</td>
|
78
98
|
</tr>
|
79
99
|
<tr>
|
80
|
-
<td>Code to
|
100
|
+
<td>Code likely to reproduce the error</td>
|
81
101
|
<td>
|
82
102
|
<textarea id="codeToCopy" rows="10" cols="90">{{ exception_message.code_to_reproduce }}</textarea>
|
83
103
|
<button onclick="copyCode()">Copy</button>
|
@@ -85,32 +105,26 @@
|
|
85
105
|
</tr>
|
86
106
|
|
87
107
|
</table>
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
</
|
105
|
-
|
106
|
-
|
107
|
-
<div class="exception-time">Time: {{ exception_message.time }}</div>
|
108
|
-
<div class="exception-traceback">Traceback:
|
109
|
-
<text>
|
110
|
-
<pre>{{ exception_message.traceback }}</pre>
|
111
|
-
</text>
|
112
|
-
</div>
|
108
|
+
|
109
|
+
{% if exception_message.exception.__class__.__name__ == 'QuestionAnswerValidationError' %}
|
110
|
+
<h3>Answer validation details</h3>
|
111
|
+
<table border="1">
|
112
|
+
{% for field, (open_tag, close_tag, value) in exception_message.exception.to_html_dict().items() %}
|
113
|
+
<tr>
|
114
|
+
<td>{{ field }}</td>
|
115
|
+
<td><{{ open_tag }}> {{ value | escape }} <{{ close_tag }}></td>
|
116
|
+
</tr>
|
117
|
+
{% endfor %}
|
118
|
+
</table>
|
119
|
+
{% endif %}
|
120
|
+
<br><br>
|
121
|
+
<div class="exception-time">Time: {{ exception_message.time }}</div>
|
122
|
+
<div class="exception-traceback">Traceback:
|
123
|
+
<text>
|
124
|
+
<pre>{{ exception_message.traceback }}</pre>
|
125
|
+
</text>
|
113
126
|
</div>
|
114
127
|
</div>
|
128
|
+
</div>
|
115
129
|
|
116
130
|
{% endfor %}
|
@@ -1,19 +1,6 @@
|
|
1
|
-
|
2
|
-
{% if interviews|length > max_interviews %}
|
3
|
-
<h1>Only showing the first {{ max_interviews }} interviews with errors</h1>
|
4
|
-
{% else %}
|
5
|
-
<h1>Showing all interviews</h1>
|
6
|
-
{% endif %}
|
7
|
-
|
1
|
+
<h2>Exceptions Details</h2>
|
8
2
|
{% for index, interview in interviews.items() %}
|
9
|
-
{%
|
10
|
-
{%
|
11
|
-
|
12
|
-
Model: {{ interview.model.model }}
|
13
|
-
<h1>Failing questions</h1>
|
14
|
-
{% endif %}
|
15
|
-
{% for question, exceptions in interview.exceptions.items() %}
|
16
|
-
{% include 'interview_details.html' %}
|
17
|
-
{% endfor %}
|
18
|
-
{% endif %}
|
3
|
+
{% for question, exceptions in interview.exceptions.items() %}
|
4
|
+
{% include 'interview_details.html' %}
|
5
|
+
{% endfor %}
|
19
6
|
{% endfor %}
|
@@ -1,5 +1,31 @@
|
|
1
|
-
<
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
1
|
+
<style>
|
2
|
+
td {
|
3
|
+
padding: 0 10px; /* This applies the padding uniformly to all td elements */
|
4
|
+
}
|
5
|
+
</style>
|
6
|
+
|
7
|
+
<h1>Exceptions Report</h1>
|
8
|
+
<p>
|
9
|
+
This report summarizes exceptions encountered in the job that was run.
|
10
|
+
</p>
|
11
|
+
<p>
|
12
|
+
For advice on dealing with exceptions, please see the EDSL <a href="https://docs.expectedparrot.com/en/latest/exceptions.html">documentation</a> page. <br>
|
13
|
+
You can also post a question at the Expected Parrot <a href="https://discord.com/invite/mxAYkjfy9m">Discord channel</a>, open an issue on <a href="https://github.com/expectedparrot/edsl">GitHub</a>, or send an email to <a href="mailto:info@expectedparrot.com">info@expectedparrot.com</a>.
|
14
|
+
</p>
|
15
|
+
|
16
|
+
<h2>Overview</h2>
|
17
|
+
<table border="1">
|
18
|
+
<tbody>
|
19
|
+
<tr>
|
20
|
+
<td>Total interviews</td>
|
21
|
+
<td>{{ interviews|length }}</td>
|
22
|
+
</tr>
|
23
|
+
<tr>
|
24
|
+
<td>Interviews with exceptions</td>
|
25
|
+
<td>{{ num_exceptions }}</td>
|
26
|
+
</tr>
|
27
|
+
</tbody>
|
28
|
+
</table>
|
29
|
+
<p>
|
30
|
+
An "interview" is the result of one survey, taken by one agent, with one model and one scenario (if any).
|
31
|
+
</p>
|
@@ -1,2 +1,2 @@
|
|
1
|
-
<
|
1
|
+
<h2>Performance Plot</h2>
|
2
2
|
{{ performance_plot_html }}
|