mcli-framework 7.1.2__py3-none-any.whl → 7.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.
Potentially problematic release.
This version of mcli-framework might be problematic. Click here for more details.
- mcli/app/main.py +10 -0
- mcli/lib/custom_commands.py +424 -0
- mcli/lib/paths.py +12 -0
- mcli/ml/dashboard/app.py +13 -13
- mcli/ml/dashboard/app_integrated.py +1949 -70
- mcli/ml/dashboard/app_supabase.py +46 -21
- mcli/ml/dashboard/app_training.py +14 -14
- mcli/ml/dashboard/components/charts.py +258 -0
- mcli/ml/dashboard/components/metrics.py +125 -0
- mcli/ml/dashboard/components/tables.py +228 -0
- mcli/ml/dashboard/pages/cicd.py +382 -0
- mcli/ml/dashboard/pages/predictions_enhanced.py +820 -0
- mcli/ml/dashboard/pages/scrapers_and_logs.py +1060 -0
- mcli/ml/dashboard/pages/workflows.py +533 -0
- mcli/ml/training/train_model.py +569 -0
- mcli/self/self_cmd.py +322 -94
- mcli/workflow/politician_trading/data_sources.py +259 -1
- mcli/workflow/politician_trading/models.py +159 -1
- mcli/workflow/politician_trading/scrapers_corporate_registry.py +846 -0
- mcli/workflow/politician_trading/scrapers_free_sources.py +516 -0
- mcli/workflow/politician_trading/scrapers_third_party.py +391 -0
- mcli/workflow/politician_trading/seed_database.py +539 -0
- mcli/workflow/workflow.py +8 -27
- {mcli_framework-7.1.2.dist-info → mcli_framework-7.2.0.dist-info}/METADATA +1 -1
- {mcli_framework-7.1.2.dist-info → mcli_framework-7.2.0.dist-info}/RECORD +29 -25
- mcli/workflow/daemon/api_daemon.py +0 -800
- mcli/workflow/daemon/commands.py +0 -1196
- mcli/workflow/dashboard/dashboard_cmd.py +0 -120
- mcli/workflow/file/file.py +0 -100
- mcli/workflow/git_commit/commands.py +0 -430
- mcli/workflow/politician_trading/commands.py +0 -1939
- mcli/workflow/scheduler/commands.py +0 -493
- mcli/workflow/sync/sync_cmd.py +0 -437
- mcli/workflow/videos/videos.py +0 -242
- {mcli_framework-7.1.2.dist-info → mcli_framework-7.2.0.dist-info}/WHEEL +0 -0
- {mcli_framework-7.1.2.dist-info → mcli_framework-7.2.0.dist-info}/entry_points.txt +0 -0
- {mcli_framework-7.1.2.dist-info → mcli_framework-7.2.0.dist-info}/licenses/LICENSE +0 -0
- {mcli_framework-7.1.2.dist-info → mcli_framework-7.2.0.dist-info}/top_level.txt +0 -0
mcli/app/main.py
CHANGED
|
@@ -423,6 +423,16 @@ def _add_lazy_commands(app: click.Group):
|
|
|
423
423
|
app.add_command(lazy_cmd)
|
|
424
424
|
logger.debug(f"Added lazy command: {cmd_name}")
|
|
425
425
|
|
|
426
|
+
# Load custom user commands from ~/.mcli/commands/ AFTER all groups are added
|
|
427
|
+
try:
|
|
428
|
+
from mcli.lib.custom_commands import load_custom_commands
|
|
429
|
+
|
|
430
|
+
loaded_count = load_custom_commands(app)
|
|
431
|
+
if loaded_count > 0:
|
|
432
|
+
logger.info(f"Loaded {loaded_count} custom user command(s)")
|
|
433
|
+
except Exception as e:
|
|
434
|
+
logger.debug(f"Could not load custom commands: {e}")
|
|
435
|
+
|
|
426
436
|
|
|
427
437
|
def create_app() -> click.Group:
|
|
428
438
|
"""Create and configure the Click application with clean top-level commands."""
|
|
@@ -0,0 +1,424 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Custom command storage and loading for mcli.
|
|
3
|
+
|
|
4
|
+
This module provides functionality to store user-created commands in a portable
|
|
5
|
+
format in ~/.mcli/commands/ and automatically load them at startup.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import json
|
|
9
|
+
import importlib.util
|
|
10
|
+
import sys
|
|
11
|
+
import tempfile
|
|
12
|
+
from datetime import datetime
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
from typing import Any, Dict, List, Optional
|
|
15
|
+
|
|
16
|
+
import click
|
|
17
|
+
|
|
18
|
+
from mcli.lib.logger.logger import get_logger
|
|
19
|
+
from mcli.lib.paths import get_custom_commands_dir
|
|
20
|
+
|
|
21
|
+
logger = get_logger()
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class CustomCommandManager:
|
|
25
|
+
"""Manages custom user commands stored in JSON format."""
|
|
26
|
+
|
|
27
|
+
def __init__(self):
|
|
28
|
+
self.commands_dir = get_custom_commands_dir()
|
|
29
|
+
self.loaded_commands: Dict[str, Any] = {}
|
|
30
|
+
self.lockfile_path = self.commands_dir / "commands.lock.json"
|
|
31
|
+
|
|
32
|
+
def save_command(
|
|
33
|
+
self,
|
|
34
|
+
name: str,
|
|
35
|
+
code: str,
|
|
36
|
+
description: str = "",
|
|
37
|
+
group: Optional[str] = None,
|
|
38
|
+
metadata: Optional[Dict[str, Any]] = None,
|
|
39
|
+
) -> Path:
|
|
40
|
+
"""
|
|
41
|
+
Save a custom command to the commands directory.
|
|
42
|
+
|
|
43
|
+
Args:
|
|
44
|
+
name: Command name
|
|
45
|
+
code: Python code for the command
|
|
46
|
+
description: Command description
|
|
47
|
+
group: Optional command group
|
|
48
|
+
metadata: Additional metadata
|
|
49
|
+
|
|
50
|
+
Returns:
|
|
51
|
+
Path to the saved command file
|
|
52
|
+
"""
|
|
53
|
+
command_data = {
|
|
54
|
+
"name": name,
|
|
55
|
+
"code": code,
|
|
56
|
+
"description": description,
|
|
57
|
+
"group": group,
|
|
58
|
+
"created_at": datetime.utcnow().isoformat() + "Z",
|
|
59
|
+
"updated_at": datetime.utcnow().isoformat() + "Z",
|
|
60
|
+
"version": "1.0",
|
|
61
|
+
"metadata": metadata or {},
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
# Save as JSON file
|
|
65
|
+
command_file = self.commands_dir / f"{name}.json"
|
|
66
|
+
with open(command_file, "w") as f:
|
|
67
|
+
json.dump(command_data, f, indent=2)
|
|
68
|
+
|
|
69
|
+
logger.info(f"Saved custom command: {name} to {command_file}")
|
|
70
|
+
|
|
71
|
+
# Update lockfile
|
|
72
|
+
self.update_lockfile()
|
|
73
|
+
|
|
74
|
+
return command_file
|
|
75
|
+
|
|
76
|
+
def load_command(self, command_file: Path) -> Optional[Dict[str, Any]]:
|
|
77
|
+
"""
|
|
78
|
+
Load a command from a JSON file.
|
|
79
|
+
|
|
80
|
+
Args:
|
|
81
|
+
command_file: Path to the command JSON file
|
|
82
|
+
|
|
83
|
+
Returns:
|
|
84
|
+
Command data dictionary or None if loading failed
|
|
85
|
+
"""
|
|
86
|
+
try:
|
|
87
|
+
with open(command_file, "r") as f:
|
|
88
|
+
command_data = json.load(f)
|
|
89
|
+
return command_data
|
|
90
|
+
except Exception as e:
|
|
91
|
+
logger.error(f"Failed to load command from {command_file}: {e}")
|
|
92
|
+
return None
|
|
93
|
+
|
|
94
|
+
def load_all_commands(self) -> List[Dict[str, Any]]:
|
|
95
|
+
"""
|
|
96
|
+
Load all custom commands from the commands directory.
|
|
97
|
+
|
|
98
|
+
Returns:
|
|
99
|
+
List of command data dictionaries
|
|
100
|
+
"""
|
|
101
|
+
commands = []
|
|
102
|
+
for command_file in self.commands_dir.glob("*.json"):
|
|
103
|
+
# Skip the lockfile
|
|
104
|
+
if command_file.name == "commands.lock.json":
|
|
105
|
+
continue
|
|
106
|
+
|
|
107
|
+
command_data = self.load_command(command_file)
|
|
108
|
+
if command_data:
|
|
109
|
+
commands.append(command_data)
|
|
110
|
+
return commands
|
|
111
|
+
|
|
112
|
+
def delete_command(self, name: str) -> bool:
|
|
113
|
+
"""
|
|
114
|
+
Delete a custom command.
|
|
115
|
+
|
|
116
|
+
Args:
|
|
117
|
+
name: Command name
|
|
118
|
+
|
|
119
|
+
Returns:
|
|
120
|
+
True if deleted successfully, False otherwise
|
|
121
|
+
"""
|
|
122
|
+
command_file = self.commands_dir / f"{name}.json"
|
|
123
|
+
if command_file.exists():
|
|
124
|
+
command_file.unlink()
|
|
125
|
+
logger.info(f"Deleted custom command: {name}")
|
|
126
|
+
self.update_lockfile() # Update lockfile after deletion
|
|
127
|
+
return True
|
|
128
|
+
return False
|
|
129
|
+
|
|
130
|
+
def generate_lockfile(self) -> Dict[str, Any]:
|
|
131
|
+
"""
|
|
132
|
+
Generate a lockfile containing metadata about all custom commands.
|
|
133
|
+
|
|
134
|
+
Returns:
|
|
135
|
+
Dictionary containing lockfile data
|
|
136
|
+
"""
|
|
137
|
+
commands = self.load_all_commands()
|
|
138
|
+
|
|
139
|
+
lockfile_data = {
|
|
140
|
+
"version": "1.0",
|
|
141
|
+
"generated_at": datetime.utcnow().isoformat() + "Z",
|
|
142
|
+
"commands": {},
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
for command_data in commands:
|
|
146
|
+
name = command_data["name"]
|
|
147
|
+
lockfile_data["commands"][name] = {
|
|
148
|
+
"name": name,
|
|
149
|
+
"description": command_data.get("description", ""),
|
|
150
|
+
"group": command_data.get("group"),
|
|
151
|
+
"version": command_data.get("version", "1.0"),
|
|
152
|
+
"created_at": command_data.get("created_at", ""),
|
|
153
|
+
"updated_at": command_data.get("updated_at", ""),
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
return lockfile_data
|
|
157
|
+
|
|
158
|
+
def update_lockfile(self) -> bool:
|
|
159
|
+
"""
|
|
160
|
+
Update the lockfile with current command state.
|
|
161
|
+
|
|
162
|
+
Returns:
|
|
163
|
+
True if successful, False otherwise
|
|
164
|
+
"""
|
|
165
|
+
try:
|
|
166
|
+
lockfile_data = self.generate_lockfile()
|
|
167
|
+
with open(self.lockfile_path, "w") as f:
|
|
168
|
+
json.dump(lockfile_data, f, indent=2)
|
|
169
|
+
logger.debug(f"Updated lockfile: {self.lockfile_path}")
|
|
170
|
+
return True
|
|
171
|
+
except Exception as e:
|
|
172
|
+
logger.error(f"Failed to update lockfile: {e}")
|
|
173
|
+
return False
|
|
174
|
+
|
|
175
|
+
def load_lockfile(self) -> Optional[Dict[str, Any]]:
|
|
176
|
+
"""
|
|
177
|
+
Load the lockfile.
|
|
178
|
+
|
|
179
|
+
Returns:
|
|
180
|
+
Lockfile data dictionary or None if not found
|
|
181
|
+
"""
|
|
182
|
+
if not self.lockfile_path.exists():
|
|
183
|
+
return None
|
|
184
|
+
|
|
185
|
+
try:
|
|
186
|
+
with open(self.lockfile_path, "r") as f:
|
|
187
|
+
return json.load(f)
|
|
188
|
+
except Exception as e:
|
|
189
|
+
logger.error(f"Failed to load lockfile: {e}")
|
|
190
|
+
return None
|
|
191
|
+
|
|
192
|
+
def verify_lockfile(self) -> Dict[str, Any]:
|
|
193
|
+
"""
|
|
194
|
+
Verify that the current command state matches the lockfile.
|
|
195
|
+
|
|
196
|
+
Returns:
|
|
197
|
+
Dictionary with verification results:
|
|
198
|
+
- 'valid': bool indicating if lockfile is valid
|
|
199
|
+
- 'missing': list of commands in lockfile but not in filesystem
|
|
200
|
+
- 'extra': list of commands in filesystem but not in lockfile
|
|
201
|
+
- 'modified': list of commands with different metadata
|
|
202
|
+
"""
|
|
203
|
+
result = {
|
|
204
|
+
"valid": True,
|
|
205
|
+
"missing": [],
|
|
206
|
+
"extra": [],
|
|
207
|
+
"modified": [],
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
lockfile_data = self.load_lockfile()
|
|
211
|
+
if not lockfile_data:
|
|
212
|
+
result["valid"] = False
|
|
213
|
+
return result
|
|
214
|
+
|
|
215
|
+
current_commands = {cmd["name"]: cmd for cmd in self.load_all_commands()}
|
|
216
|
+
lockfile_commands = lockfile_data.get("commands", {})
|
|
217
|
+
|
|
218
|
+
# Check for missing commands (in lockfile but not in filesystem)
|
|
219
|
+
for name in lockfile_commands:
|
|
220
|
+
if name not in current_commands:
|
|
221
|
+
result["missing"].append(name)
|
|
222
|
+
result["valid"] = False
|
|
223
|
+
|
|
224
|
+
# Check for extra commands (in filesystem but not in lockfile)
|
|
225
|
+
for name in current_commands:
|
|
226
|
+
if name not in lockfile_commands:
|
|
227
|
+
result["extra"].append(name)
|
|
228
|
+
result["valid"] = False
|
|
229
|
+
|
|
230
|
+
# Check for modified commands (different metadata)
|
|
231
|
+
for name in set(current_commands.keys()) & set(lockfile_commands.keys()):
|
|
232
|
+
current = current_commands[name]
|
|
233
|
+
locked = lockfile_commands[name]
|
|
234
|
+
|
|
235
|
+
if current.get("updated_at") != locked.get("updated_at"):
|
|
236
|
+
result["modified"].append(name)
|
|
237
|
+
result["valid"] = False
|
|
238
|
+
|
|
239
|
+
return result
|
|
240
|
+
|
|
241
|
+
def register_command_with_click(
|
|
242
|
+
self, command_data: Dict[str, Any], target_group: click.Group
|
|
243
|
+
) -> bool:
|
|
244
|
+
"""
|
|
245
|
+
Dynamically register a custom command with a Click group.
|
|
246
|
+
|
|
247
|
+
Args:
|
|
248
|
+
command_data: Command data dictionary
|
|
249
|
+
target_group: Click group to register the command with
|
|
250
|
+
|
|
251
|
+
Returns:
|
|
252
|
+
True if successful, False otherwise
|
|
253
|
+
"""
|
|
254
|
+
try:
|
|
255
|
+
name = command_data["name"]
|
|
256
|
+
code = command_data["code"]
|
|
257
|
+
|
|
258
|
+
# Create a temporary module to execute the command code
|
|
259
|
+
module_name = f"mcli_custom_{name}"
|
|
260
|
+
|
|
261
|
+
# Create a temporary file to store the code
|
|
262
|
+
with tempfile.NamedTemporaryFile(
|
|
263
|
+
mode="w", suffix=".py", delete=False
|
|
264
|
+
) as temp_file:
|
|
265
|
+
temp_file.write(code)
|
|
266
|
+
temp_file_path = temp_file.name
|
|
267
|
+
|
|
268
|
+
try:
|
|
269
|
+
# Load the module from the temporary file
|
|
270
|
+
spec = importlib.util.spec_from_file_location(module_name, temp_file_path)
|
|
271
|
+
if spec and spec.loader:
|
|
272
|
+
module = importlib.util.module_from_spec(spec)
|
|
273
|
+
sys.modules[module_name] = module
|
|
274
|
+
spec.loader.exec_module(module)
|
|
275
|
+
|
|
276
|
+
# Look for a command or command group in the module
|
|
277
|
+
command_obj = None
|
|
278
|
+
for attr_name in dir(module):
|
|
279
|
+
attr = getattr(module, attr_name)
|
|
280
|
+
if isinstance(attr, (click.Command, click.Group)):
|
|
281
|
+
command_obj = attr
|
|
282
|
+
break
|
|
283
|
+
|
|
284
|
+
if command_obj:
|
|
285
|
+
# Register with the target group
|
|
286
|
+
target_group.add_command(command_obj, name=name)
|
|
287
|
+
self.loaded_commands[name] = command_obj
|
|
288
|
+
logger.info(f"Registered custom command: {name}")
|
|
289
|
+
return True
|
|
290
|
+
else:
|
|
291
|
+
logger.warning(
|
|
292
|
+
f"No Click command found in custom command: {name}"
|
|
293
|
+
)
|
|
294
|
+
return False
|
|
295
|
+
finally:
|
|
296
|
+
# Clean up temporary file
|
|
297
|
+
Path(temp_file_path).unlink(missing_ok=True)
|
|
298
|
+
|
|
299
|
+
except Exception as e:
|
|
300
|
+
logger.error(f"Failed to register custom command {name}: {e}")
|
|
301
|
+
return False
|
|
302
|
+
|
|
303
|
+
def export_commands(self, export_path: Path) -> bool:
|
|
304
|
+
"""
|
|
305
|
+
Export all custom commands to a single JSON file.
|
|
306
|
+
|
|
307
|
+
Args:
|
|
308
|
+
export_path: Path to export file
|
|
309
|
+
|
|
310
|
+
Returns:
|
|
311
|
+
True if successful, False otherwise
|
|
312
|
+
"""
|
|
313
|
+
try:
|
|
314
|
+
commands = self.load_all_commands()
|
|
315
|
+
with open(export_path, "w") as f:
|
|
316
|
+
json.dump(commands, f, indent=2)
|
|
317
|
+
logger.info(f"Exported {len(commands)} commands to {export_path}")
|
|
318
|
+
return True
|
|
319
|
+
except Exception as e:
|
|
320
|
+
logger.error(f"Failed to export commands: {e}")
|
|
321
|
+
return False
|
|
322
|
+
|
|
323
|
+
def import_commands(
|
|
324
|
+
self, import_path: Path, overwrite: bool = False
|
|
325
|
+
) -> Dict[str, bool]:
|
|
326
|
+
"""
|
|
327
|
+
Import commands from a JSON file.
|
|
328
|
+
|
|
329
|
+
Args:
|
|
330
|
+
import_path: Path to import file
|
|
331
|
+
overwrite: Whether to overwrite existing commands
|
|
332
|
+
|
|
333
|
+
Returns:
|
|
334
|
+
Dictionary mapping command names to success status
|
|
335
|
+
"""
|
|
336
|
+
results = {}
|
|
337
|
+
try:
|
|
338
|
+
with open(import_path, "r") as f:
|
|
339
|
+
commands = json.load(f)
|
|
340
|
+
|
|
341
|
+
for command_data in commands:
|
|
342
|
+
name = command_data["name"]
|
|
343
|
+
command_file = self.commands_dir / f"{name}.json"
|
|
344
|
+
|
|
345
|
+
if command_file.exists() and not overwrite:
|
|
346
|
+
logger.warning(f"Command {name} already exists, skipping")
|
|
347
|
+
results[name] = False
|
|
348
|
+
continue
|
|
349
|
+
|
|
350
|
+
# Update timestamp
|
|
351
|
+
command_data["updated_at"] = datetime.utcnow().isoformat() + "Z"
|
|
352
|
+
|
|
353
|
+
with open(command_file, "w") as f:
|
|
354
|
+
json.dump(command_data, f, indent=2)
|
|
355
|
+
|
|
356
|
+
results[name] = True
|
|
357
|
+
logger.info(f"Imported command: {name}")
|
|
358
|
+
|
|
359
|
+
return results
|
|
360
|
+
except Exception as e:
|
|
361
|
+
logger.error(f"Failed to import commands: {e}")
|
|
362
|
+
return results
|
|
363
|
+
|
|
364
|
+
|
|
365
|
+
# Global instance
|
|
366
|
+
_command_manager: Optional[CustomCommandManager] = None
|
|
367
|
+
|
|
368
|
+
|
|
369
|
+
def get_command_manager() -> CustomCommandManager:
|
|
370
|
+
"""Get the global custom command manager instance."""
|
|
371
|
+
global _command_manager
|
|
372
|
+
if _command_manager is None:
|
|
373
|
+
_command_manager = CustomCommandManager()
|
|
374
|
+
return _command_manager
|
|
375
|
+
|
|
376
|
+
|
|
377
|
+
def load_custom_commands(target_group: click.Group) -> int:
|
|
378
|
+
"""
|
|
379
|
+
Load all custom commands and register them with the target Click group.
|
|
380
|
+
|
|
381
|
+
Args:
|
|
382
|
+
target_group: Click group to register commands with
|
|
383
|
+
|
|
384
|
+
Returns:
|
|
385
|
+
Number of commands successfully loaded
|
|
386
|
+
"""
|
|
387
|
+
manager = get_command_manager()
|
|
388
|
+
commands = manager.load_all_commands()
|
|
389
|
+
|
|
390
|
+
loaded_count = 0
|
|
391
|
+
for command_data in commands:
|
|
392
|
+
# Check if command should be nested under a group
|
|
393
|
+
group_name = command_data.get("group")
|
|
394
|
+
|
|
395
|
+
if group_name:
|
|
396
|
+
# Find or create the group
|
|
397
|
+
group_cmd = target_group.commands.get(group_name)
|
|
398
|
+
|
|
399
|
+
# Handle LazyGroup - force loading
|
|
400
|
+
if group_cmd and hasattr(group_cmd, "_load_group"):
|
|
401
|
+
logger.debug(f"Loading lazy group: {group_name}")
|
|
402
|
+
group_cmd = group_cmd._load_group()
|
|
403
|
+
# Update the command in the parent group
|
|
404
|
+
target_group.commands[group_name] = group_cmd
|
|
405
|
+
|
|
406
|
+
if not group_cmd:
|
|
407
|
+
# Create the group if it doesn't exist
|
|
408
|
+
group_cmd = click.Group(name=group_name, help=f"{group_name.capitalize()} commands")
|
|
409
|
+
target_group.add_command(group_cmd)
|
|
410
|
+
logger.info(f"Created command group: {group_name}")
|
|
411
|
+
|
|
412
|
+
# Register the command under the group
|
|
413
|
+
if isinstance(group_cmd, click.Group):
|
|
414
|
+
if manager.register_command_with_click(command_data, group_cmd):
|
|
415
|
+
loaded_count += 1
|
|
416
|
+
else:
|
|
417
|
+
# Register at top level
|
|
418
|
+
if manager.register_command_with_click(command_data, target_group):
|
|
419
|
+
loaded_count += 1
|
|
420
|
+
|
|
421
|
+
if loaded_count > 0:
|
|
422
|
+
logger.info(f"Loaded {loaded_count} custom commands")
|
|
423
|
+
|
|
424
|
+
return loaded_count
|
mcli/lib/paths.py
CHANGED
|
@@ -80,3 +80,15 @@ def get_cache_dir() -> Path:
|
|
|
80
80
|
cache_dir = get_mcli_home() / "cache"
|
|
81
81
|
cache_dir.mkdir(parents=True, exist_ok=True)
|
|
82
82
|
return cache_dir
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def get_custom_commands_dir() -> Path:
|
|
86
|
+
"""
|
|
87
|
+
Get the custom commands directory for mcli.
|
|
88
|
+
|
|
89
|
+
Returns:
|
|
90
|
+
Path to custom commands directory (e.g., ~/.mcli/commands), created if it doesn't exist
|
|
91
|
+
"""
|
|
92
|
+
commands_dir = get_mcli_home() / "commands"
|
|
93
|
+
commands_dir.mkdir(parents=True, exist_ok=True)
|
|
94
|
+
return commands_dir
|
mcli/ml/dashboard/app.py
CHANGED
|
@@ -283,7 +283,7 @@ def show_overview():
|
|
|
283
283
|
model_data = get_model_performance()
|
|
284
284
|
if not model_data.empty:
|
|
285
285
|
fig = px.bar(model_data, x="name", y="accuracy", title="Model Accuracy Comparison")
|
|
286
|
-
st.plotly_chart(fig,
|
|
286
|
+
st.plotly_chart(fig, width="stretch", config={"responsive": True})
|
|
287
287
|
else:
|
|
288
288
|
st.info("No model performance data available")
|
|
289
289
|
|
|
@@ -295,7 +295,7 @@ def show_overview():
|
|
|
295
295
|
fig = px.histogram(
|
|
296
296
|
pred_data, x="confidence", title="Prediction Confidence Distribution"
|
|
297
297
|
)
|
|
298
|
-
st.plotly_chart(fig,
|
|
298
|
+
st.plotly_chart(fig, width="stretch", config={"responsive": True})
|
|
299
299
|
else:
|
|
300
300
|
st.info("No recent predictions available")
|
|
301
301
|
|
|
@@ -309,13 +309,13 @@ def show_models():
|
|
|
309
309
|
|
|
310
310
|
if not model_data.empty:
|
|
311
311
|
st.subheader("Model Performance")
|
|
312
|
-
st.dataframe(model_data,
|
|
312
|
+
st.dataframe(model_data, width="stretch")
|
|
313
313
|
|
|
314
314
|
# Model accuracy chart
|
|
315
315
|
fig = px.line(
|
|
316
316
|
model_data, x="created_at", y="accuracy", color="name", title="Model Accuracy Over Time"
|
|
317
317
|
)
|
|
318
|
-
st.plotly_chart(fig,
|
|
318
|
+
st.plotly_chart(fig, width="stretch", config={"responsive": True})
|
|
319
319
|
else:
|
|
320
320
|
st.warning("No model data available")
|
|
321
321
|
|
|
@@ -349,7 +349,7 @@ def show_predictions():
|
|
|
349
349
|
|
|
350
350
|
# Display filtered data
|
|
351
351
|
st.subheader("Filtered Predictions")
|
|
352
|
-
st.dataframe(filtered_data,
|
|
352
|
+
st.dataframe(filtered_data, width="stretch")
|
|
353
353
|
|
|
354
354
|
# Charts
|
|
355
355
|
col1, col2 = st.columns(2)
|
|
@@ -362,7 +362,7 @@ def show_predictions():
|
|
|
362
362
|
color="ticker",
|
|
363
363
|
title="Confidence vs Predicted Return",
|
|
364
364
|
)
|
|
365
|
-
st.plotly_chart(fig,
|
|
365
|
+
st.plotly_chart(fig, width="stretch", config={"responsive": True})
|
|
366
366
|
|
|
367
367
|
with col2:
|
|
368
368
|
# Group by ticker and show average return
|
|
@@ -373,7 +373,7 @@ def show_predictions():
|
|
|
373
373
|
y="predicted_return",
|
|
374
374
|
title="Average Predicted Return by Ticker",
|
|
375
375
|
)
|
|
376
|
-
st.plotly_chart(fig,
|
|
376
|
+
st.plotly_chart(fig, width="stretch", config={"responsive": True})
|
|
377
377
|
|
|
378
378
|
else:
|
|
379
379
|
st.warning("No prediction data available")
|
|
@@ -388,7 +388,7 @@ def show_portfolios():
|
|
|
388
388
|
if not portfolio_data.empty:
|
|
389
389
|
# Portfolio metrics
|
|
390
390
|
st.subheader("Portfolio Summary")
|
|
391
|
-
st.dataframe(portfolio_data,
|
|
391
|
+
st.dataframe(portfolio_data, width="stretch")
|
|
392
392
|
|
|
393
393
|
# Performance charts
|
|
394
394
|
col1, col2 = st.columns(2)
|
|
@@ -397,7 +397,7 @@ def show_portfolios():
|
|
|
397
397
|
fig = px.bar(
|
|
398
398
|
portfolio_data, x="name", y="total_return", title="Total Return by Portfolio"
|
|
399
399
|
)
|
|
400
|
-
st.plotly_chart(fig,
|
|
400
|
+
st.plotly_chart(fig, width="stretch", config={"responsive": True})
|
|
401
401
|
|
|
402
402
|
with col2:
|
|
403
403
|
fig = px.scatter(
|
|
@@ -408,7 +408,7 @@ def show_portfolios():
|
|
|
408
408
|
hover_data=["name"],
|
|
409
409
|
title="Risk-Return Analysis",
|
|
410
410
|
)
|
|
411
|
-
st.plotly_chart(fig,
|
|
411
|
+
st.plotly_chart(fig, width="stretch", config={"responsive": True})
|
|
412
412
|
|
|
413
413
|
else:
|
|
414
414
|
st.warning("No portfolio data available")
|
|
@@ -467,7 +467,7 @@ def show_system_health():
|
|
|
467
467
|
)
|
|
468
468
|
|
|
469
469
|
fig.update_layout(height=500, title_text="System Resource Usage (24h)")
|
|
470
|
-
st.plotly_chart(fig,
|
|
470
|
+
st.plotly_chart(fig, width="stretch", config={"responsive": True})
|
|
471
471
|
|
|
472
472
|
|
|
473
473
|
def show_live_monitoring():
|
|
@@ -509,13 +509,13 @@ def show_live_monitoring():
|
|
|
509
509
|
"Time": [datetime.now() - timedelta(seconds=x * 10) for x in range(5)],
|
|
510
510
|
}
|
|
511
511
|
)
|
|
512
|
-
st.dataframe(new_preds,
|
|
512
|
+
st.dataframe(new_preds, width="stretch")
|
|
513
513
|
|
|
514
514
|
# Model status
|
|
515
515
|
with model_placeholder.container():
|
|
516
516
|
model_data = get_model_performance()
|
|
517
517
|
if not model_data.empty:
|
|
518
|
-
st.dataframe(model_data[["name", "accuracy"]],
|
|
518
|
+
st.dataframe(model_data[["name", "accuracy"]], width="stretch")
|
|
519
519
|
|
|
520
520
|
time.sleep(5)
|
|
521
521
|
|