simstack 0.2.0__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 (91) hide show
  1. simstack/__init__.py +0 -0
  2. simstack/_version.py +34 -0
  3. simstack/core/__init__.py +0 -0
  4. simstack/core/artifacts.py +447 -0
  5. simstack/core/asnyc_helper.py +14 -0
  6. simstack/core/config_file.py +60 -0
  7. simstack/core/context.py +167 -0
  8. simstack/core/data.py +139 -0
  9. simstack/core/definitions.py +21 -0
  10. simstack/core/engine.py +116 -0
  11. simstack/core/find_simstack_modules.py +43 -0
  12. simstack/core/hash.py +177 -0
  13. simstack/core/model_table.py +199 -0
  14. simstack/core/node.py +911 -0
  15. simstack/core/node_runner.py +254 -0
  16. simstack/core/node_table.py +380 -0
  17. simstack/core/recompute_artifacts.py +120 -0
  18. simstack/core/reset_db.py +12 -0
  19. simstack/core/resources.py +101 -0
  20. simstack/core/run_node.py +69 -0
  21. simstack/core/runner.py +435 -0
  22. simstack/core/simstack_result.py +41 -0
  23. simstack/core/submit_node.py +143 -0
  24. simstack/core/task_id.py +22 -0
  25. simstack/data/__init__.py +0 -0
  26. simstack/methods/__init__.py +0 -0
  27. simstack/methods/get_file.py +10 -0
  28. simstack/methods/multiple_executor.py +307 -0
  29. simstack/models/__init__.py +37 -0
  30. simstack/models/array_list.py +48 -0
  31. simstack/models/array_storage.py +54 -0
  32. simstack/models/artifact_models.py +127 -0
  33. simstack/models/base_types.py +208 -0
  34. simstack/models/charts_artifact.py +411 -0
  35. simstack/models/dataset.py +432 -0
  36. simstack/models/dataset_metadata.py +303 -0
  37. simstack/models/file_instance.py +77 -0
  38. simstack/models/file_list.py +321 -0
  39. simstack/models/files.py +229 -0
  40. simstack/models/log_entry_model.py +37 -0
  41. simstack/models/models.py +40 -0
  42. simstack/models/node_registry.py +105 -0
  43. simstack/models/pandas_model.py +210 -0
  44. simstack/models/parameters.py +388 -0
  45. simstack/models/pickle_models.py +311 -0
  46. simstack/models/runner_model.py +39 -0
  47. simstack/models/simple_table.py +32 -0
  48. simstack/models/simstack_model.py +97 -0
  49. simstack/models/slurm_info.py +17 -0
  50. simstack/models/table_artifact.py +87 -0
  51. simstack/util/__init__.py +0 -0
  52. simstack/util/async_zip_utils.py +18 -0
  53. simstack/util/cleaned_json_schema.py +20 -0
  54. simstack/util/config_reader.py +273 -0
  55. simstack/util/custom_model_dump.py +75 -0
  56. simstack/util/db.py +457 -0
  57. simstack/util/db_logger.py +117 -0
  58. simstack/util/default_from_dict.py +57 -0
  59. simstack/util/directory_iterator.py +216 -0
  60. simstack/util/file_hashing.py +68 -0
  61. simstack/util/files.py +396 -0
  62. simstack/util/find_python_files.py +33 -0
  63. simstack/util/generate_ui_schema.py +80 -0
  64. simstack/util/geometry_utils.py +118 -0
  65. simstack/util/get_module_path.py +26 -0
  66. simstack/util/git_pull.py +65 -0
  67. simstack/util/git_repository_status.py +93 -0
  68. simstack/util/import_module.py +65 -0
  69. simstack/util/importer.py +163 -0
  70. simstack/util/make_graph.py +107 -0
  71. simstack/util/make_table.py +827 -0
  72. simstack/util/minimal_route_finder.py +72 -0
  73. simstack/util/mock_db.py +144 -0
  74. simstack/util/module_path_checker.py +38 -0
  75. simstack/util/path_manager.py +234 -0
  76. simstack/util/project_root_finder.py +36 -0
  77. simstack/util/queue_watcher.py +262 -0
  78. simstack/util/route_finder.py +74 -0
  79. simstack/util/runner_util.py +162 -0
  80. simstack/util/safe_code_executor.py +194 -0
  81. simstack/util/sample_config.py +98 -0
  82. simstack/util/sensitive_content_filter.py +48 -0
  83. simstack/util/setup_logging.py +49 -0
  84. simstack/util/submit_to_watchdog.py +106 -0
  85. simstack/util/ui_tools.py +213 -0
  86. simstack-0.2.0.dist-info/METADATA +361 -0
  87. simstack-0.2.0.dist-info/RECORD +91 -0
  88. simstack-0.2.0.dist-info/WHEEL +5 -0
  89. simstack-0.2.0.dist-info/entry_points.txt +10 -0
  90. simstack-0.2.0.dist-info/licenses/LICENSE +21 -0
  91. simstack-0.2.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,208 @@
1
+ from odmantic import Model, Reference
2
+ from pydantic import model_validator
3
+
4
+ from simstack.models import simstack_model
5
+
6
+
7
+ @simstack_model
8
+ class IntData(Model):
9
+ field_name: str = "int"
10
+ value: int
11
+
12
+ @model_validator(mode="before")
13
+ @classmethod
14
+ def ensure_fieldname(cls, data):
15
+ """Ensure fieldname is set for existing documents"""
16
+ if isinstance(data, dict) and "field_name" not in data:
17
+ data["field_name"] = "int"
18
+ return data
19
+
20
+ def make_table_entries(
21
+ self,
22
+ max_recursion_level=1,
23
+ drop_id=True,
24
+ current_level=0,
25
+ visited=None,
26
+ field_prefix="",
27
+ ):
28
+ return {self.field_name: self.value}
29
+
30
+ def make_column_defs_instance(
31
+ self,
32
+ table_name=None,
33
+ max_recursion_level=1,
34
+ drop_id=True,
35
+ current_level=0,
36
+ visited=None,
37
+ field_prefix="",
38
+ ):
39
+ return [
40
+ {
41
+ "field": self.field_name,
42
+ "headerName": self.field_name,
43
+ "type": "numericColumn",
44
+ }
45
+ ]
46
+
47
+ @classmethod
48
+ def ui_schema(cls, **kwargs) -> dict:
49
+ return {
50
+ "ui:field": "IntDataField"
51
+ } # This tells RJSF to use the custom component
52
+
53
+
54
+ @simstack_model
55
+ class FloatData(Model):
56
+ field_name: str = "float"
57
+ value: float
58
+
59
+ @model_validator(mode="before")
60
+ @classmethod
61
+ def ensure_fieldname(cls, data):
62
+ """Ensure fieldname is set for existing documents"""
63
+ if isinstance(data, dict) and "field_name" not in data:
64
+ data["field_name"] = "float"
65
+ return data
66
+
67
+ def make_table_entries(
68
+ self,
69
+ max_recursion_level=1,
70
+ drop_id=True,
71
+ current_level=0,
72
+ visited=None,
73
+ field_prefix="",
74
+ ):
75
+ return {self.field_name: self.value}
76
+
77
+ def make_column_defs_instance(
78
+ self,
79
+ table_name=None,
80
+ max_recursion_level=1,
81
+ drop_id=True,
82
+ current_level=0,
83
+ visited=None,
84
+ field_prefix="",
85
+ ):
86
+ return [
87
+ {
88
+ "field": self.field_name,
89
+ "headerName": self.field_name,
90
+ "type": "numericColumn",
91
+ }
92
+ ]
93
+
94
+ @classmethod
95
+ def ui_schema(cls, **kwargs) -> dict:
96
+ return {
97
+ "ui:field": "FloatDataField"
98
+ } # This tells RJSF to use the custom component
99
+
100
+
101
+ @simstack_model
102
+ class StringData(Model):
103
+ field_name: str = "text"
104
+ value: str
105
+
106
+ @model_validator(mode="before")
107
+ @classmethod
108
+ def ensure_fieldname(cls, data):
109
+ """Ensure fieldname is set for existing documents"""
110
+ if isinstance(data, dict) and "field_name" not in data:
111
+ data["field_name"] = "text"
112
+ return data
113
+
114
+ def make_table_entries(
115
+ self,
116
+ max_recursion_level=1,
117
+ drop_id=True,
118
+ current_level=0,
119
+ visited=None,
120
+ field_prefix="",
121
+ ):
122
+ return {self.field_name: self.value}
123
+
124
+ def make_column_defs_instance(
125
+ self,
126
+ table_name=None,
127
+ max_recursion_level=1,
128
+ drop_id=True,
129
+ current_level=0,
130
+ visited=None,
131
+ field_prefix="",
132
+ ):
133
+ return [
134
+ {
135
+ "field": self.field_name,
136
+ "headerName": self.field_name,
137
+ "type": "textColumn",
138
+ }
139
+ ]
140
+
141
+ @classmethod
142
+ def ui_schema(cls, **kwargs) -> dict:
143
+ return {
144
+ "ui:field": "StringDataField"
145
+ } # This tells RJSF to use the custom component
146
+
147
+
148
+ @simstack_model
149
+ class BooleanData(Model):
150
+ field_name: str = "boolean"
151
+ value: bool
152
+
153
+ @model_validator(mode="before")
154
+ @classmethod
155
+ def ensure_fieldname(cls, data):
156
+ """Ensure fieldname is set for existing documents"""
157
+ if isinstance(data, dict) and "field_name" not in data:
158
+ data["field_name"] = "boolean"
159
+ return data
160
+
161
+ def make_table_entries(
162
+ self,
163
+ max_recursion_level=1,
164
+ drop_id=True,
165
+ current_level=0,
166
+ visited=None,
167
+ field_prefix="",
168
+ ):
169
+ return {self.field_name: self.value}
170
+
171
+ def make_column_defs_instance(
172
+ self,
173
+ table_name=None,
174
+ max_recursion_level=1,
175
+ drop_id=True,
176
+ current_level=0,
177
+ visited=None,
178
+ field_prefix="",
179
+ ):
180
+ return [
181
+ {
182
+ "field": self.field_name,
183
+ "headerName": self.field_name,
184
+ "type": "booleanColumn",
185
+ }
186
+ ]
187
+
188
+ @classmethod
189
+ def ui_schema(cls, **kwargs) -> dict:
190
+ return {
191
+ "ui:field": "BooleanDataField"
192
+ } # This tells RJSF to use the custom component
193
+
194
+
195
+ @simstack_model
196
+ class BinaryOperationInput(Model):
197
+ arg1: FloatData = Reference()
198
+ arg2: FloatData = Reference()
199
+
200
+ def __str__(self):
201
+ return f"BinaryOperationInput(arg1={self.arg1}, arg2={self.arg2})"
202
+
203
+
204
+ @simstack_model
205
+ class IteratorInput(Model):
206
+ start: int
207
+ stop: int
208
+ generator: str = "range"
@@ -0,0 +1,411 @@
1
+ from typing import List, Union, Literal, Optional, Dict, Any
2
+
3
+ from odmantic import Model, Field, EmbeddedModel, ObjectId
4
+
5
+
6
+ # Chart Series Definitions
7
+ class AGChartSeriesBase(EmbeddedModel):
8
+ """Base class for AG-Charts series configuration."""
9
+
10
+ type: str = Field(..., description="Chart series type")
11
+ xKey: str = Field(..., description="Key for x-axis data")
12
+ yKey: str = Field(..., description="Key for y-axis data")
13
+ visible: Optional[bool] = Field(
14
+ default=True, description="Whether series is visible"
15
+ )
16
+ showInLegend: Optional[bool] = Field(default=True, description="Show in legend")
17
+ title: Optional[str] = Field(default=None, description="Series title")
18
+ # Chart data
19
+ data: List[Dict[str, Any]] = Field(default_factory=list, description="Chart data")
20
+
21
+
22
+ class AGLineSeriesConfig(AGChartSeriesBase):
23
+ """AG-Charts line series configuration."""
24
+
25
+ type: Literal["line"] = "line"
26
+ strokeWidth: Optional[float] = Field(default=2, description="Line stroke width")
27
+ strokeOpacity: Optional[float] = Field(default=1, description="Line stroke opacity")
28
+ lineDash: Optional[List[float]] = Field(
29
+ default=None, description="Line dash pattern"
30
+ )
31
+ marker: Optional[Dict[str, Any]] = Field(
32
+ default=None, description="Marker configuration"
33
+ )
34
+ tooltip: Optional[Dict[str, Any]] = Field(
35
+ default=None, description="Tooltip configuration"
36
+ )
37
+
38
+
39
+ class AGBarSeriesConfig(AGChartSeriesBase):
40
+ """AG-Charts bar series configuration."""
41
+
42
+ type: Literal["bar"] = "bar"
43
+ fillOpacity: Optional[float] = Field(default=1, description="Bar fill opacity")
44
+ strokeWidth: Optional[float] = Field(default=0, description="Bar stroke width")
45
+ cornerRadius: Optional[float] = Field(default=0, description="Bar corner radius")
46
+ tooltip: Optional[Dict[str, Any]] = Field(
47
+ default=None, description="Tooltip configuration"
48
+ )
49
+
50
+
51
+ class AGColumnSeriesConfig(AGChartSeriesBase):
52
+ """AG-Charts column series configuration."""
53
+
54
+ type: Literal["column"] = "column"
55
+ fillOpacity: Optional[float] = Field(default=1, description="Column fill opacity")
56
+ strokeWidth: Optional[float] = Field(default=0, description="Column stroke width")
57
+ cornerRadius: Optional[float] = Field(default=0, description="Column corner radius")
58
+ tooltip: Optional[Dict[str, Any]] = Field(
59
+ default=None, description="Tooltip configuration"
60
+ )
61
+
62
+
63
+ class AGAreaSeriesConfig(AGChartSeriesBase):
64
+ """AG-Charts area series configuration."""
65
+
66
+ type: Literal["area"] = "area"
67
+ fillOpacity: Optional[float] = Field(default=0.8, description="Area fill opacity")
68
+ strokeWidth: Optional[float] = Field(default=2, description="Area stroke width")
69
+ marker: Optional[Dict[str, Any]] = Field(
70
+ default=None, description="Marker configuration"
71
+ )
72
+ tooltip: Optional[Dict[str, Any]] = Field(
73
+ default=None, description="Tooltip configuration"
74
+ )
75
+
76
+
77
+ class AGScatterSeriesConfig(AGChartSeriesBase):
78
+ """AG-Charts scatter series configuration."""
79
+
80
+ type: Literal["scatter"] = "scatter"
81
+ marker: Optional[Dict[str, Any]] = Field(
82
+ default=None, description="Marker configuration"
83
+ )
84
+ tooltip: Optional[Dict[str, Any]] = Field(
85
+ default=None, description="Tooltip configuration"
86
+ )
87
+
88
+
89
+ class AGPieSeriesConfig(EmbeddedModel):
90
+ """AG-Charts pie series configuration."""
91
+
92
+ type: Literal["pie"] = "pie"
93
+ angleKey: str = Field(..., description="Key for pie slice angles")
94
+ radiusKey: Optional[str] = Field(
95
+ default=None, description="Key for pie slice radius"
96
+ )
97
+ labelKey: Optional[str] = Field(
98
+ default=None, description="Key for pie slice labels"
99
+ )
100
+ visible: Optional[bool] = Field(
101
+ default=True, description="Whether series is visible"
102
+ )
103
+ showInLegend: Optional[bool] = Field(default=True, description="Show in legend")
104
+ title: Optional[str] = Field(default=None, description="Series title")
105
+ tooltip: Optional[Dict[str, Any]] = Field(
106
+ default=None, description="Tooltip configuration"
107
+ )
108
+
109
+
110
+ class AGDonutSeriesConfig(EmbeddedModel):
111
+ """AG-Charts donut series configuration."""
112
+
113
+ type: Literal["donut"] = "donut"
114
+ angleKey: str = Field(..., description="Key for donut slice angles")
115
+ radiusKey: Optional[str] = Field(
116
+ default=None, description="Key for donut slice radius"
117
+ )
118
+ labelKey: Optional[str] = Field(
119
+ default=None, description="Key for donut slice labels"
120
+ )
121
+ innerRadiusRatio: Optional[float] = Field(
122
+ default=0.6, description="Inner radius ratio"
123
+ )
124
+ visible: Optional[bool] = Field(
125
+ default=True, description="Whether series is visible"
126
+ )
127
+ showInLegend: Optional[bool] = Field(default=True, description="Show in legend")
128
+ title: Optional[str] = Field(default=None, description="Series title")
129
+ tooltip: Optional[Dict[str, Any]] = Field(
130
+ default=None, description="Tooltip configuration"
131
+ )
132
+
133
+
134
+ # Union type for all series configurations
135
+ AGChartSeries = Union[
136
+ AGLineSeriesConfig,
137
+ AGBarSeriesConfig,
138
+ AGColumnSeriesConfig,
139
+ AGAreaSeriesConfig,
140
+ AGScatterSeriesConfig,
141
+ AGPieSeriesConfig,
142
+ AGDonutSeriesConfig,
143
+ ]
144
+
145
+
146
+ # Axis Configurations
147
+ class AGChartAxisConfig(EmbeddedModel):
148
+ """AG-Charts axis configuration."""
149
+
150
+ type: Literal["category", "number", "time", "log"] = Field(
151
+ ..., description="Axis type"
152
+ )
153
+ position: Literal["top", "right", "bottom", "left"] = Field(
154
+ ..., description="Axis position"
155
+ )
156
+ title: Optional[str] = Field(default=None, description="Axis title")
157
+ min: Optional[float] = Field(default=None, description="Minimum axis value")
158
+ max: Optional[float] = Field(default=None, description="Maximum axis value")
159
+ tick: Optional[Dict[str, Any]] = Field(
160
+ default=None, description="Tick configuration"
161
+ )
162
+ label: Optional[Dict[str, Any]] = Field(
163
+ default=None, description="Label configuration"
164
+ )
165
+ gridStyle: Optional[List[Dict[str, Any]]] = Field(
166
+ default=None, description="Grid line style"
167
+ )
168
+
169
+
170
+ # Legend Configuration
171
+ class AGChartLegendConfig(EmbeddedModel):
172
+ """AG-Charts legend configuration."""
173
+
174
+ enabled: bool = Field(default=True, description="Enable legend")
175
+ position: Literal["top", "right", "bottom", "left"] = Field(
176
+ default="right", description="Legend position"
177
+ )
178
+ spacing: float = Field(default=20, description="Legend spacing")
179
+ item: Dict[str, Any] = Field(
180
+ default_factory=dict, description="Legend item configuration"
181
+ )
182
+
183
+
184
+ # Title Configuration
185
+ class AGChartTitleConfig(EmbeddedModel):
186
+ """AG-Charts title configuration."""
187
+
188
+ text: str = Field("Chart Title", description="Title text")
189
+ # fontSize: Optional[int] = Field(default=16, description="Title font size")
190
+ # fontWeight: Optional[str] = Field(default="bold", description="Title font weight")
191
+ # color: Optional[str] = Field(default=None, description="Title color")
192
+
193
+
194
+ # Subtitle Configuration
195
+ class AGChartSubtitleConfig(EmbeddedModel):
196
+ """AG-Charts subtitle configuration."""
197
+
198
+ text: str = Field(..., description="Subtitle text")
199
+ fontSize: Optional[int] = Field(default=12, description="Subtitle font size")
200
+ color: Optional[str] = Field(default=None, description="Subtitle color")
201
+
202
+
203
+ # Main Chart Model
204
+ class ChartArtifactModel(Model):
205
+ """AG-Charts configuration model."""
206
+
207
+ parent_id: ObjectId = Field(default=None, description="ID of the node registry")
208
+
209
+ # Chart data
210
+ data: List[Dict[str, Any]] = Field(default_factory=list, description="Chart data")
211
+
212
+ # Chart configuration
213
+ title: AGChartTitleConfig = Field(..., description="Chart title configuration")
214
+ # subtitle: AGChartSubtitleConfig = Field(..., description="Chart subtitle configuration")
215
+
216
+ # Series configuration
217
+ series: List[AGChartSeries] = Field(
218
+ default_factory=list, description="Chart series configurations"
219
+ )
220
+
221
+ # Axes configuration
222
+ axes: List[AGChartAxisConfig] = Field(
223
+ default_factory=list, description="Chart axes configurations"
224
+ )
225
+
226
+ # # Legend configuration
227
+ legend: AGChartLegendConfig = Field(
228
+ default=AGChartLegendConfig(), description="Legend configuration"
229
+ )
230
+ #
231
+ # Chart styling and behavior
232
+ width: int = Field(default=800, description="Chart width in pixels")
233
+ height: int = Field(default=400, description="Chart height in pixels")
234
+ padding: Optional[Dict[str, int]] = Field(default=None, description="Chart padding")
235
+ background: Optional[Dict[str, Any]] = Field(
236
+ default=None, description="Background configuration"
237
+ )
238
+
239
+ # Animation
240
+ animation: Optional[Dict[str, Any]] = Field(
241
+ default=None, description="Animation configuration"
242
+ )
243
+
244
+ # Tooltip
245
+ tooltip: Optional[Dict[str, Any]] = Field(
246
+ default=None, description="Global tooltip configuration"
247
+ )
248
+
249
+ # Theme
250
+ theme: Optional[str] = Field(default="ag-default", description="Chart theme")
251
+
252
+ # Additional options
253
+ options: Optional[Dict[str, Any]] = Field(
254
+ default_factory=dict, description="Additional chart options"
255
+ )
256
+
257
+
258
+ # Helper functions for creating specific chart types
259
+ def create_simple_line_chart(
260
+ data: List[Dict[str, Any]],
261
+ x_key: str,
262
+ y_key: str,
263
+ title: Optional[str] = None,
264
+ parent_id: ObjectId = None,
265
+ ) -> ChartArtifactModel:
266
+ """Create a simple line chart."""
267
+ chart_title = (
268
+ AGChartTitleConfig(text=title) if title else AGChartTitleConfig(text="Chart")
269
+ )
270
+ # subtitle = AGChartSubtitleConfig(text="Subtitle")
271
+
272
+ series = [
273
+ AGLineSeriesConfig(
274
+ type="line", xKey=x_key, yKey=y_key, title=y_key.title(), data=data
275
+ )
276
+ ]
277
+
278
+ axes = [
279
+ AGChartAxisConfig(type="number", position="bottom", title=x_key.title()),
280
+ AGChartAxisConfig(type="number", position="left", title=y_key.title()),
281
+ ]
282
+
283
+ return ChartArtifactModel(
284
+ parent_id=parent_id,
285
+ data=data,
286
+ title=chart_title,
287
+ # subtitle=subtitle,
288
+ series=series,
289
+ axes=axes,
290
+ )
291
+
292
+
293
+ def create_simple_bar_chart(
294
+ data: List[Dict[str, Any]],
295
+ x_key: str,
296
+ y_key: str,
297
+ title: Optional[str] = None,
298
+ parent_id: Optional[ObjectId] = None,
299
+ ) -> ChartArtifactModel:
300
+ """Create a simple bar chart."""
301
+ chart_title = AGChartTitleConfig(text=title) if title else None
302
+
303
+ series = [
304
+ AGColumnSeriesConfig(type="column", xKey=x_key, yKey=y_key, title=y_key.title())
305
+ ]
306
+
307
+ axes = [
308
+ AGChartAxisConfig(type="category", position="bottom", title=x_key.title()),
309
+ AGChartAxisConfig(type="number", position="left", title=y_key.title()),
310
+ ]
311
+
312
+ return ChartArtifactModel(
313
+ parent_id=parent_id, data=data, title=chart_title, series=series, axes=axes
314
+ )
315
+
316
+
317
+ def create_simple_pie_chart(
318
+ data: List[Dict[str, Any]],
319
+ angle_key: str,
320
+ label_key: str,
321
+ title: Optional[str] = None,
322
+ parent_id: Optional[ObjectId] = None,
323
+ ) -> ChartArtifactModel:
324
+ """Create a simple pie chart."""
325
+ chart_title = AGChartTitleConfig(text=title) if title else None
326
+
327
+ series = [
328
+ AGPieSeriesConfig(
329
+ type="pie", angleKey=angle_key, labelKey=label_key, title="Distribution"
330
+ )
331
+ ]
332
+
333
+ return ChartArtifactModel(
334
+ parent_id=parent_id, data=data, title=chart_title, series=series
335
+ )
336
+
337
+
338
+ def create_simple_area_chart(
339
+ data: List[Dict[str, Any]],
340
+ x_key: str,
341
+ y_key: str,
342
+ title: Optional[str] = None,
343
+ parent_id: Optional[ObjectId] = None,
344
+ ) -> ChartArtifactModel:
345
+ """Create a simple area chart."""
346
+ chart_title = AGChartTitleConfig(text=title) if title else None
347
+
348
+ series = [
349
+ AGAreaSeriesConfig(type="area", xKey=x_key, yKey=y_key, title=y_key.title())
350
+ ]
351
+
352
+ axes = [
353
+ AGChartAxisConfig(type="category", position="bottom", title=x_key.title()),
354
+ AGChartAxisConfig(type="number", position="left", title=y_key.title()),
355
+ ]
356
+
357
+ return ChartArtifactModel(
358
+ parent_id=parent_id, data=data, title=chart_title, series=series, axes=axes
359
+ )
360
+
361
+
362
+ def create_simple_scatter_chart(
363
+ data: List[Dict[str, Any]],
364
+ x_key: str,
365
+ y_key: str,
366
+ title: Optional[str] = None,
367
+ parent_id: Optional[ObjectId] = None,
368
+ ) -> ChartArtifactModel:
369
+ """Create a simple scatter chart."""
370
+ chart_title = AGChartTitleConfig(text=title) if title else None
371
+
372
+ series = [
373
+ AGScatterSeriesConfig(
374
+ type="scatter", xKey=x_key, yKey=y_key, title=y_key.title()
375
+ )
376
+ ]
377
+
378
+ axes = [
379
+ AGChartAxisConfig(type="number", position="bottom", title=x_key.title()),
380
+ AGChartAxisConfig(type="number", position="left", title=y_key.title()),
381
+ ]
382
+
383
+ return ChartArtifactModel(
384
+ parent_id=parent_id, data=data, title=chart_title, series=series, axes=axes
385
+ )
386
+
387
+
388
+ def create_simple_donut_chart(
389
+ data: List[Dict[str, Any]],
390
+ angle_key: str,
391
+ label_key: str,
392
+ title: Optional[str] = None,
393
+ inner_radius_ratio: float = 0.6,
394
+ parent_id: Optional[ObjectId] = None,
395
+ ) -> ChartArtifactModel:
396
+ """Create a simple donut chart."""
397
+ chart_title = AGChartTitleConfig(text=title) if title else None
398
+
399
+ series = [
400
+ AGDonutSeriesConfig(
401
+ type="donut",
402
+ angleKey=angle_key,
403
+ labelKey=label_key,
404
+ innerRadiusRatio=inner_radius_ratio,
405
+ title="Distribution",
406
+ )
407
+ ]
408
+
409
+ return ChartArtifactModel(
410
+ parent_id=parent_id, data=data, title=chart_title, series=series
411
+ )