edsl 0.1.39.dev1__py3-none-any.whl → 0.1.39.dev3__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- edsl/Base.py +332 -332
- edsl/BaseDiff.py +260 -260
- edsl/TemplateLoader.py +24 -24
- edsl/__init__.py +49 -49
- edsl/__version__.py +1 -1
- edsl/agents/Agent.py +867 -867
- edsl/agents/AgentList.py +413 -413
- edsl/agents/Invigilator.py +233 -233
- edsl/agents/InvigilatorBase.py +270 -265
- edsl/agents/PromptConstructor.py +354 -354
- edsl/agents/__init__.py +3 -3
- edsl/agents/descriptors.py +99 -99
- edsl/agents/prompt_helpers.py +129 -129
- edsl/auto/AutoStudy.py +117 -117
- edsl/auto/StageBase.py +230 -230
- edsl/auto/StageGenerateSurvey.py +178 -178
- edsl/auto/StageLabelQuestions.py +125 -125
- edsl/auto/StagePersona.py +61 -61
- edsl/auto/StagePersonaDimensionValueRanges.py +88 -88
- edsl/auto/StagePersonaDimensionValues.py +74 -74
- edsl/auto/StagePersonaDimensions.py +69 -69
- edsl/auto/StageQuestions.py +73 -73
- edsl/auto/SurveyCreatorPipeline.py +21 -21
- edsl/auto/utilities.py +224 -224
- edsl/base/Base.py +279 -279
- edsl/config.py +157 -157
- edsl/conversation/Conversation.py +290 -290
- edsl/conversation/car_buying.py +58 -58
- edsl/conversation/chips.py +95 -95
- edsl/conversation/mug_negotiation.py +81 -81
- edsl/conversation/next_speaker_utilities.py +93 -93
- edsl/coop/PriceFetcher.py +54 -54
- edsl/coop/__init__.py +2 -2
- edsl/coop/coop.py +1028 -1028
- edsl/coop/utils.py +131 -131
- edsl/data/Cache.py +555 -555
- edsl/data/CacheEntry.py +233 -233
- edsl/data/CacheHandler.py +149 -149
- edsl/data/RemoteCacheSync.py +78 -78
- edsl/data/SQLiteDict.py +292 -292
- edsl/data/__init__.py +4 -4
- edsl/data/orm.py +10 -10
- edsl/data_transfer_models.py +73 -73
- edsl/enums.py +175 -175
- edsl/exceptions/BaseException.py +21 -21
- edsl/exceptions/__init__.py +54 -54
- edsl/exceptions/agents.py +42 -42
- edsl/exceptions/cache.py +5 -5
- edsl/exceptions/configuration.py +16 -16
- edsl/exceptions/coop.py +10 -10
- edsl/exceptions/data.py +14 -14
- edsl/exceptions/general.py +34 -34
- edsl/exceptions/jobs.py +33 -33
- edsl/exceptions/language_models.py +63 -63
- edsl/exceptions/prompts.py +15 -15
- edsl/exceptions/questions.py +91 -91
- edsl/exceptions/results.py +29 -29
- edsl/exceptions/scenarios.py +22 -22
- edsl/exceptions/surveys.py +37 -37
- edsl/inference_services/AnthropicService.py +87 -87
- edsl/inference_services/AwsBedrock.py +120 -120
- edsl/inference_services/AzureAI.py +217 -217
- edsl/inference_services/DeepInfraService.py +18 -18
- edsl/inference_services/GoogleService.py +148 -148
- edsl/inference_services/GroqService.py +20 -20
- edsl/inference_services/InferenceServiceABC.py +147 -147
- edsl/inference_services/InferenceServicesCollection.py +97 -97
- edsl/inference_services/MistralAIService.py +123 -123
- edsl/inference_services/OllamaService.py +18 -18
- edsl/inference_services/OpenAIService.py +224 -224
- edsl/inference_services/PerplexityService.py +163 -163
- edsl/inference_services/TestService.py +89 -89
- edsl/inference_services/TogetherAIService.py +170 -170
- edsl/inference_services/models_available_cache.py +118 -118
- edsl/inference_services/rate_limits_cache.py +25 -25
- edsl/inference_services/registry.py +41 -41
- edsl/inference_services/write_available.py +10 -10
- edsl/jobs/Answers.py +56 -56
- edsl/jobs/Jobs.py +898 -898
- edsl/jobs/JobsChecks.py +147 -147
- edsl/jobs/JobsPrompts.py +268 -268
- edsl/jobs/JobsRemoteInferenceHandler.py +239 -239
- edsl/jobs/__init__.py +1 -1
- edsl/jobs/buckets/BucketCollection.py +63 -63
- edsl/jobs/buckets/ModelBuckets.py +65 -65
- edsl/jobs/buckets/TokenBucket.py +251 -251
- edsl/jobs/interviews/Interview.py +661 -661
- edsl/jobs/interviews/InterviewExceptionCollection.py +99 -99
- edsl/jobs/interviews/InterviewExceptionEntry.py +186 -186
- edsl/jobs/interviews/InterviewStatistic.py +63 -63
- edsl/jobs/interviews/InterviewStatisticsCollection.py +25 -25
- edsl/jobs/interviews/InterviewStatusDictionary.py +78 -78
- edsl/jobs/interviews/InterviewStatusLog.py +92 -92
- edsl/jobs/interviews/ReportErrors.py +66 -66
- edsl/jobs/interviews/interview_status_enum.py +9 -9
- edsl/jobs/runners/JobsRunnerAsyncio.py +466 -466
- edsl/jobs/runners/JobsRunnerStatus.py +330 -330
- edsl/jobs/tasks/QuestionTaskCreator.py +242 -242
- edsl/jobs/tasks/TaskCreators.py +64 -64
- edsl/jobs/tasks/TaskHistory.py +450 -450
- edsl/jobs/tasks/TaskStatusLog.py +23 -23
- edsl/jobs/tasks/task_status_enum.py +163 -163
- edsl/jobs/tokens/InterviewTokenUsage.py +27 -27
- edsl/jobs/tokens/TokenUsage.py +34 -34
- edsl/language_models/KeyLookup.py +30 -30
- edsl/language_models/LanguageModel.py +668 -668
- edsl/language_models/ModelList.py +155 -155
- edsl/language_models/RegisterLanguageModelsMeta.py +184 -184
- edsl/language_models/__init__.py +3 -3
- edsl/language_models/fake_openai_call.py +15 -15
- edsl/language_models/fake_openai_service.py +61 -61
- edsl/language_models/registry.py +190 -190
- edsl/language_models/repair.py +156 -156
- edsl/language_models/unused/ReplicateBase.py +83 -83
- edsl/language_models/utilities.py +64 -64
- edsl/notebooks/Notebook.py +258 -258
- edsl/notebooks/__init__.py +1 -1
- edsl/prompts/Prompt.py +362 -362
- edsl/prompts/__init__.py +2 -2
- edsl/questions/AnswerValidatorMixin.py +289 -289
- edsl/questions/QuestionBase.py +664 -664
- edsl/questions/QuestionBaseGenMixin.py +161 -161
- edsl/questions/QuestionBasePromptsMixin.py +217 -217
- edsl/questions/QuestionBudget.py +227 -227
- edsl/questions/QuestionCheckBox.py +359 -359
- edsl/questions/QuestionExtract.py +182 -182
- edsl/questions/QuestionFreeText.py +114 -114
- edsl/questions/QuestionFunctional.py +166 -166
- edsl/questions/QuestionList.py +231 -231
- edsl/questions/QuestionMultipleChoice.py +286 -286
- edsl/questions/QuestionNumerical.py +153 -153
- edsl/questions/QuestionRank.py +324 -324
- edsl/questions/Quick.py +41 -41
- edsl/questions/RegisterQuestionsMeta.py +71 -71
- edsl/questions/ResponseValidatorABC.py +174 -174
- edsl/questions/SimpleAskMixin.py +73 -73
- edsl/questions/__init__.py +26 -26
- edsl/questions/compose_questions.py +98 -98
- edsl/questions/decorators.py +21 -21
- edsl/questions/derived/QuestionLikertFive.py +76 -76
- edsl/questions/derived/QuestionLinearScale.py +87 -87
- edsl/questions/derived/QuestionTopK.py +93 -93
- edsl/questions/derived/QuestionYesNo.py +82 -82
- edsl/questions/descriptors.py +413 -413
- edsl/questions/prompt_templates/question_budget.jinja +13 -13
- edsl/questions/prompt_templates/question_checkbox.jinja +32 -32
- edsl/questions/prompt_templates/question_extract.jinja +11 -11
- edsl/questions/prompt_templates/question_free_text.jinja +3 -3
- edsl/questions/prompt_templates/question_linear_scale.jinja +11 -11
- edsl/questions/prompt_templates/question_list.jinja +17 -17
- edsl/questions/prompt_templates/question_multiple_choice.jinja +33 -33
- edsl/questions/prompt_templates/question_numerical.jinja +36 -36
- edsl/questions/question_registry.py +177 -177
- edsl/questions/settings.py +12 -12
- edsl/questions/templates/budget/answering_instructions.jinja +7 -7
- edsl/questions/templates/budget/question_presentation.jinja +7 -7
- edsl/questions/templates/checkbox/answering_instructions.jinja +10 -10
- edsl/questions/templates/checkbox/question_presentation.jinja +22 -22
- edsl/questions/templates/extract/answering_instructions.jinja +7 -7
- edsl/questions/templates/likert_five/answering_instructions.jinja +10 -10
- edsl/questions/templates/likert_five/question_presentation.jinja +11 -11
- edsl/questions/templates/linear_scale/answering_instructions.jinja +5 -5
- edsl/questions/templates/linear_scale/question_presentation.jinja +5 -5
- edsl/questions/templates/list/answering_instructions.jinja +3 -3
- edsl/questions/templates/list/question_presentation.jinja +5 -5
- edsl/questions/templates/multiple_choice/answering_instructions.jinja +9 -9
- edsl/questions/templates/multiple_choice/question_presentation.jinja +11 -11
- edsl/questions/templates/numerical/answering_instructions.jinja +6 -6
- edsl/questions/templates/numerical/question_presentation.jinja +6 -6
- edsl/questions/templates/rank/answering_instructions.jinja +11 -11
- edsl/questions/templates/rank/question_presentation.jinja +15 -15
- edsl/questions/templates/top_k/answering_instructions.jinja +8 -8
- edsl/questions/templates/top_k/question_presentation.jinja +22 -22
- edsl/questions/templates/yes_no/answering_instructions.jinja +6 -6
- edsl/questions/templates/yes_no/question_presentation.jinja +11 -11
- edsl/results/CSSParameterizer.py +108 -108
- edsl/results/Dataset.py +424 -424
- edsl/results/DatasetExportMixin.py +731 -731
- edsl/results/DatasetTree.py +275 -275
- edsl/results/Result.py +465 -465
- edsl/results/Results.py +1165 -1165
- edsl/results/ResultsDBMixin.py +238 -238
- edsl/results/ResultsExportMixin.py +43 -43
- edsl/results/ResultsFetchMixin.py +33 -33
- edsl/results/ResultsGGMixin.py +121 -121
- edsl/results/ResultsToolsMixin.py +98 -98
- edsl/results/Selector.py +135 -135
- edsl/results/TableDisplay.py +198 -198
- edsl/results/__init__.py +2 -2
- edsl/results/table_display.css +77 -77
- edsl/results/tree_explore.py +115 -115
- edsl/scenarios/FileStore.py +632 -632
- edsl/scenarios/Scenario.py +601 -601
- edsl/scenarios/ScenarioHtmlMixin.py +64 -64
- edsl/scenarios/ScenarioJoin.py +127 -127
- edsl/scenarios/ScenarioList.py +1287 -1287
- edsl/scenarios/ScenarioListExportMixin.py +52 -52
- edsl/scenarios/ScenarioListPdfMixin.py +261 -261
- edsl/scenarios/__init__.py +4 -4
- edsl/shared.py +1 -1
- edsl/study/ObjectEntry.py +173 -173
- edsl/study/ProofOfWork.py +113 -113
- edsl/study/SnapShot.py +80 -80
- edsl/study/Study.py +528 -528
- edsl/study/__init__.py +4 -4
- edsl/surveys/DAG.py +148 -148
- edsl/surveys/Memory.py +31 -31
- edsl/surveys/MemoryPlan.py +244 -244
- edsl/surveys/Rule.py +326 -326
- edsl/surveys/RuleCollection.py +387 -387
- edsl/surveys/Survey.py +1801 -1801
- edsl/surveys/SurveyCSS.py +261 -261
- edsl/surveys/SurveyExportMixin.py +259 -259
- edsl/surveys/SurveyFlowVisualizationMixin.py +179 -179
- edsl/surveys/SurveyQualtricsImport.py +284 -284
- edsl/surveys/__init__.py +3 -3
- edsl/surveys/base.py +53 -53
- edsl/surveys/descriptors.py +56 -56
- edsl/surveys/instructions/ChangeInstruction.py +49 -49
- edsl/surveys/instructions/Instruction.py +65 -65
- edsl/surveys/instructions/InstructionCollection.py +77 -77
- edsl/templates/error_reporting/base.html +23 -23
- edsl/templates/error_reporting/exceptions_by_model.html +34 -34
- edsl/templates/error_reporting/exceptions_by_question_name.html +16 -16
- edsl/templates/error_reporting/exceptions_by_type.html +16 -16
- edsl/templates/error_reporting/interview_details.html +115 -115
- edsl/templates/error_reporting/interviews.html +19 -19
- edsl/templates/error_reporting/overview.html +4 -4
- edsl/templates/error_reporting/performance_plot.html +1 -1
- edsl/templates/error_reporting/report.css +73 -73
- edsl/templates/error_reporting/report.html +117 -117
- edsl/templates/error_reporting/report.js +25 -25
- edsl/tools/__init__.py +1 -1
- edsl/tools/clusters.py +192 -192
- edsl/tools/embeddings.py +27 -27
- edsl/tools/embeddings_plotting.py +118 -118
- edsl/tools/plotting.py +112 -112
- edsl/tools/summarize.py +18 -18
- edsl/utilities/SystemInfo.py +28 -28
- edsl/utilities/__init__.py +22 -22
- edsl/utilities/ast_utilities.py +25 -25
- edsl/utilities/data/Registry.py +6 -6
- edsl/utilities/data/__init__.py +1 -1
- edsl/utilities/data/scooter_results.json +1 -1
- edsl/utilities/decorators.py +77 -77
- edsl/utilities/gcp_bucket/cloud_storage.py +96 -96
- edsl/utilities/interface.py +627 -627
- edsl/utilities/naming_utilities.py +263 -263
- edsl/utilities/repair_functions.py +28 -28
- edsl/utilities/restricted_python.py +70 -70
- edsl/utilities/utilities.py +424 -424
- {edsl-0.1.39.dev1.dist-info → edsl-0.1.39.dev3.dist-info}/LICENSE +21 -21
- {edsl-0.1.39.dev1.dist-info → edsl-0.1.39.dev3.dist-info}/METADATA +1 -1
- edsl-0.1.39.dev3.dist-info/RECORD +277 -0
- edsl-0.1.39.dev1.dist-info/RECORD +0 -277
- {edsl-0.1.39.dev1.dist-info → edsl-0.1.39.dev3.dist-info}/WHEEL +0 -0
@@ -1,179 +1,179 @@
|
|
1
|
-
"""A mixin for visualizing the flow of a survey with parameter nodes."""
|
2
|
-
|
3
|
-
from typing import Optional
|
4
|
-
from edsl.surveys.base import RulePriority, EndOfSurvey
|
5
|
-
import tempfile
|
6
|
-
import os
|
7
|
-
|
8
|
-
|
9
|
-
class SurveyFlowVisualizationMixin:
|
10
|
-
"""A mixin for visualizing the flow of a survey with parameter visualization."""
|
11
|
-
|
12
|
-
def show_flow(self, filename: Optional[str] = None):
|
13
|
-
"""Create an image showing the flow of users through the survey and question parameters."""
|
14
|
-
# Create a graph object
|
15
|
-
import pydot
|
16
|
-
|
17
|
-
graph = pydot.Dot(graph_type="digraph")
|
18
|
-
|
19
|
-
# First collect all unique parameters and answer references
|
20
|
-
params_and_refs = set()
|
21
|
-
param_to_questions = {} # Keep track of which questions use each parameter
|
22
|
-
answer_refs = set() # Track answer references between questions
|
23
|
-
|
24
|
-
# First pass: collect parameters and their question associations
|
25
|
-
for index, question in enumerate(self.questions):
|
26
|
-
# Add the main question node
|
27
|
-
question_node = pydot.Node(
|
28
|
-
f"Q{index}", label=f"{question.question_name}", shape="ellipse"
|
29
|
-
)
|
30
|
-
graph.add_node(question_node)
|
31
|
-
|
32
|
-
if hasattr(question, "parameters"):
|
33
|
-
for param in question.parameters:
|
34
|
-
# Check if this is an answer reference (contains '.answer')
|
35
|
-
if ".answer" in param:
|
36
|
-
answer_refs.add((param.split(".")[0], index))
|
37
|
-
else:
|
38
|
-
params_and_refs.add(param)
|
39
|
-
if param not in param_to_questions:
|
40
|
-
param_to_questions[param] = []
|
41
|
-
param_to_questions[param].append(index)
|
42
|
-
|
43
|
-
# Create parameter nodes and connect them to questions
|
44
|
-
for param in params_and_refs:
|
45
|
-
param_node_name = f"param_{param}"
|
46
|
-
param_node = pydot.Node(
|
47
|
-
param_node_name,
|
48
|
-
label=f"{{{{ {param} }}}}",
|
49
|
-
shape="box",
|
50
|
-
style="filled",
|
51
|
-
fillcolor="lightgrey",
|
52
|
-
fontsize="10",
|
53
|
-
)
|
54
|
-
graph.add_node(param_node)
|
55
|
-
|
56
|
-
# Connect this parameter to all questions that use it
|
57
|
-
for q_index in param_to_questions[param]:
|
58
|
-
param_edge = pydot.Edge(
|
59
|
-
param_node_name,
|
60
|
-
f"Q{q_index}",
|
61
|
-
style="dotted",
|
62
|
-
color="grey",
|
63
|
-
arrowsize="0.5",
|
64
|
-
)
|
65
|
-
graph.add_edge(param_edge)
|
66
|
-
|
67
|
-
# Add edges for answer references
|
68
|
-
for source_q_name, target_q_index in answer_refs:
|
69
|
-
# Find the source question index by name
|
70
|
-
source_q_index = next(
|
71
|
-
i
|
72
|
-
for i, q in enumerate(self.questions)
|
73
|
-
if q.question_name == source_q_name
|
74
|
-
)
|
75
|
-
ref_edge = pydot.Edge(
|
76
|
-
f"Q{source_q_index}",
|
77
|
-
f"Q{target_q_index}",
|
78
|
-
style="dashed",
|
79
|
-
color="purple",
|
80
|
-
label="answer reference",
|
81
|
-
)
|
82
|
-
graph.add_edge(ref_edge)
|
83
|
-
|
84
|
-
# Add an "EndOfSurvey" node
|
85
|
-
graph.add_node(
|
86
|
-
pydot.Node("EndOfSurvey", label="End of Survey", shape="rectangle")
|
87
|
-
)
|
88
|
-
|
89
|
-
# Add edges for normal flow through the survey
|
90
|
-
num_questions = len(self.questions)
|
91
|
-
for index in range(num_questions - 1):
|
92
|
-
graph.add_edge(pydot.Edge(f"Q{index}", f"Q{index+1}"))
|
93
|
-
|
94
|
-
graph.add_edge(pydot.Edge(f"Q{num_questions-1}", "EndOfSurvey"))
|
95
|
-
|
96
|
-
relevant_rules = [
|
97
|
-
rule
|
98
|
-
for rule in self.rule_collection
|
99
|
-
if rule.priority > RulePriority.DEFAULT.value
|
100
|
-
]
|
101
|
-
|
102
|
-
# edge-colors to cycle through
|
103
|
-
colors = [
|
104
|
-
"blue",
|
105
|
-
"red",
|
106
|
-
"orange",
|
107
|
-
"purple",
|
108
|
-
"brown",
|
109
|
-
"cyan",
|
110
|
-
"green",
|
111
|
-
]
|
112
|
-
rule_colors = {
|
113
|
-
rule: colors[i % len(colors)] for i, rule in enumerate(relevant_rules)
|
114
|
-
}
|
115
|
-
|
116
|
-
for rule in relevant_rules:
|
117
|
-
color = rule_colors[rule]
|
118
|
-
edge_label = f"if {rule.expression}"
|
119
|
-
source_node = f"Q{rule.current_q}"
|
120
|
-
target_node = (
|
121
|
-
f"Q{rule.next_q}"
|
122
|
-
if rule.next_q != EndOfSurvey and rule.next_q < num_questions
|
123
|
-
else "EndOfSurvey"
|
124
|
-
)
|
125
|
-
if rule.before_rule:
|
126
|
-
edge = pydot.Edge(
|
127
|
-
source_node,
|
128
|
-
target_node,
|
129
|
-
label=edge_label,
|
130
|
-
color=color,
|
131
|
-
fontcolor=color,
|
132
|
-
tailport="n",
|
133
|
-
headport="n",
|
134
|
-
)
|
135
|
-
else:
|
136
|
-
edge = pydot.Edge(
|
137
|
-
source_node,
|
138
|
-
target_node,
|
139
|
-
label=edge_label,
|
140
|
-
color=color,
|
141
|
-
fontcolor=color,
|
142
|
-
)
|
143
|
-
|
144
|
-
graph.add_edge(edge)
|
145
|
-
|
146
|
-
if filename is not None:
|
147
|
-
graph.write_png(filename)
|
148
|
-
print(f"Flowchart saved to {filename}")
|
149
|
-
return
|
150
|
-
|
151
|
-
with tempfile.NamedTemporaryFile(delete=False, suffix=".png") as tmp_file:
|
152
|
-
try:
|
153
|
-
graph.write_png(tmp_file.name)
|
154
|
-
except FileNotFoundError:
|
155
|
-
print(
|
156
|
-
"""File not found. Most likely it's because you don't have graphviz installed. Please install it and try again.
|
157
|
-
It's
|
158
|
-
$ sudo apt-get install graphviz
|
159
|
-
on Ubuntu.
|
160
|
-
"""
|
161
|
-
)
|
162
|
-
from edsl.utilities.utilities import is_notebook
|
163
|
-
|
164
|
-
if is_notebook():
|
165
|
-
from IPython.display import Image
|
166
|
-
|
167
|
-
display(Image(tmp_file.name))
|
168
|
-
else:
|
169
|
-
import os
|
170
|
-
import sys
|
171
|
-
|
172
|
-
if os.name == "nt": # Windows
|
173
|
-
os.system(f"start {tmp_file.name}")
|
174
|
-
elif os.name == "posix": # macOS, Linux, Unix, etc.
|
175
|
-
os.system(
|
176
|
-
f"open {tmp_file.name}"
|
177
|
-
if sys.platform == "darwin"
|
178
|
-
else f"xdg-open {tmp_file.name}"
|
179
|
-
)
|
1
|
+
"""A mixin for visualizing the flow of a survey with parameter nodes."""
|
2
|
+
|
3
|
+
from typing import Optional
|
4
|
+
from edsl.surveys.base import RulePriority, EndOfSurvey
|
5
|
+
import tempfile
|
6
|
+
import os
|
7
|
+
|
8
|
+
|
9
|
+
class SurveyFlowVisualizationMixin:
|
10
|
+
"""A mixin for visualizing the flow of a survey with parameter visualization."""
|
11
|
+
|
12
|
+
def show_flow(self, filename: Optional[str] = None):
|
13
|
+
"""Create an image showing the flow of users through the survey and question parameters."""
|
14
|
+
# Create a graph object
|
15
|
+
import pydot
|
16
|
+
|
17
|
+
graph = pydot.Dot(graph_type="digraph")
|
18
|
+
|
19
|
+
# First collect all unique parameters and answer references
|
20
|
+
params_and_refs = set()
|
21
|
+
param_to_questions = {} # Keep track of which questions use each parameter
|
22
|
+
answer_refs = set() # Track answer references between questions
|
23
|
+
|
24
|
+
# First pass: collect parameters and their question associations
|
25
|
+
for index, question in enumerate(self.questions):
|
26
|
+
# Add the main question node
|
27
|
+
question_node = pydot.Node(
|
28
|
+
f"Q{index}", label=f"{question.question_name}", shape="ellipse"
|
29
|
+
)
|
30
|
+
graph.add_node(question_node)
|
31
|
+
|
32
|
+
if hasattr(question, "parameters"):
|
33
|
+
for param in question.parameters:
|
34
|
+
# Check if this is an answer reference (contains '.answer')
|
35
|
+
if ".answer" in param:
|
36
|
+
answer_refs.add((param.split(".")[0], index))
|
37
|
+
else:
|
38
|
+
params_and_refs.add(param)
|
39
|
+
if param not in param_to_questions:
|
40
|
+
param_to_questions[param] = []
|
41
|
+
param_to_questions[param].append(index)
|
42
|
+
|
43
|
+
# Create parameter nodes and connect them to questions
|
44
|
+
for param in params_and_refs:
|
45
|
+
param_node_name = f"param_{param}"
|
46
|
+
param_node = pydot.Node(
|
47
|
+
param_node_name,
|
48
|
+
label=f"{{{{ {param} }}}}",
|
49
|
+
shape="box",
|
50
|
+
style="filled",
|
51
|
+
fillcolor="lightgrey",
|
52
|
+
fontsize="10",
|
53
|
+
)
|
54
|
+
graph.add_node(param_node)
|
55
|
+
|
56
|
+
# Connect this parameter to all questions that use it
|
57
|
+
for q_index in param_to_questions[param]:
|
58
|
+
param_edge = pydot.Edge(
|
59
|
+
param_node_name,
|
60
|
+
f"Q{q_index}",
|
61
|
+
style="dotted",
|
62
|
+
color="grey",
|
63
|
+
arrowsize="0.5",
|
64
|
+
)
|
65
|
+
graph.add_edge(param_edge)
|
66
|
+
|
67
|
+
# Add edges for answer references
|
68
|
+
for source_q_name, target_q_index in answer_refs:
|
69
|
+
# Find the source question index by name
|
70
|
+
source_q_index = next(
|
71
|
+
i
|
72
|
+
for i, q in enumerate(self.questions)
|
73
|
+
if q.question_name == source_q_name
|
74
|
+
)
|
75
|
+
ref_edge = pydot.Edge(
|
76
|
+
f"Q{source_q_index}",
|
77
|
+
f"Q{target_q_index}",
|
78
|
+
style="dashed",
|
79
|
+
color="purple",
|
80
|
+
label="answer reference",
|
81
|
+
)
|
82
|
+
graph.add_edge(ref_edge)
|
83
|
+
|
84
|
+
# Add an "EndOfSurvey" node
|
85
|
+
graph.add_node(
|
86
|
+
pydot.Node("EndOfSurvey", label="End of Survey", shape="rectangle")
|
87
|
+
)
|
88
|
+
|
89
|
+
# Add edges for normal flow through the survey
|
90
|
+
num_questions = len(self.questions)
|
91
|
+
for index in range(num_questions - 1):
|
92
|
+
graph.add_edge(pydot.Edge(f"Q{index}", f"Q{index+1}"))
|
93
|
+
|
94
|
+
graph.add_edge(pydot.Edge(f"Q{num_questions-1}", "EndOfSurvey"))
|
95
|
+
|
96
|
+
relevant_rules = [
|
97
|
+
rule
|
98
|
+
for rule in self.rule_collection
|
99
|
+
if rule.priority > RulePriority.DEFAULT.value
|
100
|
+
]
|
101
|
+
|
102
|
+
# edge-colors to cycle through
|
103
|
+
colors = [
|
104
|
+
"blue",
|
105
|
+
"red",
|
106
|
+
"orange",
|
107
|
+
"purple",
|
108
|
+
"brown",
|
109
|
+
"cyan",
|
110
|
+
"green",
|
111
|
+
]
|
112
|
+
rule_colors = {
|
113
|
+
rule: colors[i % len(colors)] for i, rule in enumerate(relevant_rules)
|
114
|
+
}
|
115
|
+
|
116
|
+
for rule in relevant_rules:
|
117
|
+
color = rule_colors[rule]
|
118
|
+
edge_label = f"if {rule.expression}"
|
119
|
+
source_node = f"Q{rule.current_q}"
|
120
|
+
target_node = (
|
121
|
+
f"Q{rule.next_q}"
|
122
|
+
if rule.next_q != EndOfSurvey and rule.next_q < num_questions
|
123
|
+
else "EndOfSurvey"
|
124
|
+
)
|
125
|
+
if rule.before_rule:
|
126
|
+
edge = pydot.Edge(
|
127
|
+
source_node,
|
128
|
+
target_node,
|
129
|
+
label=edge_label,
|
130
|
+
color=color,
|
131
|
+
fontcolor=color,
|
132
|
+
tailport="n",
|
133
|
+
headport="n",
|
134
|
+
)
|
135
|
+
else:
|
136
|
+
edge = pydot.Edge(
|
137
|
+
source_node,
|
138
|
+
target_node,
|
139
|
+
label=edge_label,
|
140
|
+
color=color,
|
141
|
+
fontcolor=color,
|
142
|
+
)
|
143
|
+
|
144
|
+
graph.add_edge(edge)
|
145
|
+
|
146
|
+
if filename is not None:
|
147
|
+
graph.write_png(filename)
|
148
|
+
print(f"Flowchart saved to {filename}")
|
149
|
+
return
|
150
|
+
|
151
|
+
with tempfile.NamedTemporaryFile(delete=False, suffix=".png") as tmp_file:
|
152
|
+
try:
|
153
|
+
graph.write_png(tmp_file.name)
|
154
|
+
except FileNotFoundError:
|
155
|
+
print(
|
156
|
+
"""File not found. Most likely it's because you don't have graphviz installed. Please install it and try again.
|
157
|
+
It's
|
158
|
+
$ sudo apt-get install graphviz
|
159
|
+
on Ubuntu.
|
160
|
+
"""
|
161
|
+
)
|
162
|
+
from edsl.utilities.utilities import is_notebook
|
163
|
+
|
164
|
+
if is_notebook():
|
165
|
+
from IPython.display import Image
|
166
|
+
|
167
|
+
display(Image(tmp_file.name))
|
168
|
+
else:
|
169
|
+
import os
|
170
|
+
import sys
|
171
|
+
|
172
|
+
if os.name == "nt": # Windows
|
173
|
+
os.system(f"start {tmp_file.name}")
|
174
|
+
elif os.name == "posix": # macOS, Linux, Unix, etc.
|
175
|
+
os.system(
|
176
|
+
f"open {tmp_file.name}"
|
177
|
+
if sys.platform == "darwin"
|
178
|
+
else f"xdg-open {tmp_file.name}"
|
179
|
+
)
|