mcli-framework 7.10.1__py3-none-any.whl → 7.10.2__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.

Files changed (99) hide show
  1. mcli/lib/custom_commands.py +10 -0
  2. mcli/lib/optional_deps.py +240 -0
  3. mcli/workflow/git_commit/ai_service.py +13 -2
  4. mcli/workflow/notebook/converter.py +375 -0
  5. mcli/workflow/notebook/notebook_cmd.py +441 -0
  6. mcli/workflow/notebook/schema.py +402 -0
  7. mcli/workflow/notebook/validator.py +313 -0
  8. mcli/workflow/workflow.py +14 -0
  9. {mcli_framework-7.10.1.dist-info → mcli_framework-7.10.2.dist-info}/METADATA +36 -2
  10. {mcli_framework-7.10.1.dist-info → mcli_framework-7.10.2.dist-info}/RECORD +14 -94
  11. mcli/__init__.py +0 -160
  12. mcli/__main__.py +0 -14
  13. mcli/app/__init__.py +0 -23
  14. mcli/app/model/__init__.py +0 -0
  15. mcli/app/video/__init__.py +0 -5
  16. mcli/chat/__init__.py +0 -34
  17. mcli/lib/__init__.py +0 -0
  18. mcli/lib/api/__init__.py +0 -0
  19. mcli/lib/auth/__init__.py +0 -1
  20. mcli/lib/config/__init__.py +0 -1
  21. mcli/lib/erd/__init__.py +0 -25
  22. mcli/lib/files/__init__.py +0 -0
  23. mcli/lib/fs/__init__.py +0 -1
  24. mcli/lib/logger/__init__.py +0 -3
  25. mcli/lib/performance/__init__.py +0 -17
  26. mcli/lib/pickles/__init__.py +0 -1
  27. mcli/lib/secrets/__init__.py +0 -10
  28. mcli/lib/shell/__init__.py +0 -0
  29. mcli/lib/toml/__init__.py +0 -1
  30. mcli/lib/watcher/__init__.py +0 -0
  31. mcli/ml/__init__.py +0 -16
  32. mcli/ml/api/__init__.py +0 -30
  33. mcli/ml/api/routers/__init__.py +0 -27
  34. mcli/ml/auth/__init__.py +0 -41
  35. mcli/ml/backtesting/__init__.py +0 -33
  36. mcli/ml/cli/__init__.py +0 -5
  37. mcli/ml/config/__init__.py +0 -33
  38. mcli/ml/configs/__init__.py +0 -16
  39. mcli/ml/dashboard/__init__.py +0 -12
  40. mcli/ml/dashboard/components/__init__.py +0 -7
  41. mcli/ml/dashboard/pages/__init__.py +0 -6
  42. mcli/ml/data_ingestion/__init__.py +0 -29
  43. mcli/ml/database/__init__.py +0 -40
  44. mcli/ml/experimentation/__init__.py +0 -29
  45. mcli/ml/features/__init__.py +0 -39
  46. mcli/ml/features/political_features.py +0 -677
  47. mcli/ml/mlops/__init__.py +0 -19
  48. mcli/ml/models/__init__.py +0 -90
  49. mcli/ml/monitoring/__init__.py +0 -25
  50. mcli/ml/optimization/__init__.py +0 -27
  51. mcli/ml/predictions/__init__.py +0 -5
  52. mcli/ml/preprocessing/__init__.py +0 -24
  53. mcli/ml/preprocessing/politician_trading_preprocessor.py +0 -570
  54. mcli/ml/scripts/__init__.py +0 -1
  55. mcli/ml/serving/__init__.py +0 -1
  56. mcli/ml/trading/__init__.py +0 -63
  57. mcli/ml/training/__init__.py +0 -7
  58. mcli/mygroup/__init__.py +0 -3
  59. mcli/public/__init__.py +0 -1
  60. mcli/public/commands/__init__.py +0 -2
  61. mcli/self/__init__.py +0 -3
  62. mcli/workflow/__init__.py +0 -0
  63. mcli/workflow/daemon/__init__.py +0 -15
  64. mcli/workflow/dashboard/__init__.py +0 -5
  65. mcli/workflow/docker/__init__.py +0 -0
  66. mcli/workflow/file/__init__.py +0 -0
  67. mcli/workflow/gcloud/__init__.py +0 -1
  68. mcli/workflow/git_commit/__init__.py +0 -0
  69. mcli/workflow/interview/__init__.py +0 -0
  70. mcli/workflow/politician_trading/__init__.py +0 -4
  71. mcli/workflow/politician_trading/config.py +0 -134
  72. mcli/workflow/politician_trading/connectivity.py +0 -492
  73. mcli/workflow/politician_trading/data_sources.py +0 -654
  74. mcli/workflow/politician_trading/database.py +0 -412
  75. mcli/workflow/politician_trading/demo.py +0 -249
  76. mcli/workflow/politician_trading/models.py +0 -327
  77. mcli/workflow/politician_trading/monitoring.py +0 -413
  78. mcli/workflow/politician_trading/scrapers.py +0 -1074
  79. mcli/workflow/politician_trading/scrapers_california.py +0 -434
  80. mcli/workflow/politician_trading/scrapers_corporate_registry.py +0 -797
  81. mcli/workflow/politician_trading/scrapers_eu.py +0 -376
  82. mcli/workflow/politician_trading/scrapers_free_sources.py +0 -509
  83. mcli/workflow/politician_trading/scrapers_third_party.py +0 -373
  84. mcli/workflow/politician_trading/scrapers_uk.py +0 -378
  85. mcli/workflow/politician_trading/scrapers_us_states.py +0 -471
  86. mcli/workflow/politician_trading/seed_database.py +0 -520
  87. mcli/workflow/politician_trading/supabase_functions.py +0 -354
  88. mcli/workflow/politician_trading/workflow.py +0 -879
  89. mcli/workflow/registry/__init__.py +0 -0
  90. mcli/workflow/repo/__init__.py +0 -0
  91. mcli/workflow/scheduler/__init__.py +0 -25
  92. mcli/workflow/search/__init__.py +0 -0
  93. mcli/workflow/sync/__init__.py +0 -5
  94. mcli/workflow/videos/__init__.py +0 -1
  95. mcli/workflow/wakatime/__init__.py +0 -80
  96. {mcli_framework-7.10.1.dist-info → mcli_framework-7.10.2.dist-info}/WHEEL +0 -0
  97. {mcli_framework-7.10.1.dist-info → mcli_framework-7.10.2.dist-info}/entry_points.txt +0 -0
  98. {mcli_framework-7.10.1.dist-info → mcli_framework-7.10.2.dist-info}/licenses/LICENSE +0 -0
  99. {mcli_framework-7.10.1.dist-info → mcli_framework-7.10.2.dist-info}/top_level.txt +0 -0
@@ -1,413 +0,0 @@
1
- """
2
- Monitoring and status reporting for politician trading data collection
3
- """
4
-
5
- import asyncio
6
- import logging
7
- from datetime import datetime, timedelta, timezone
8
- from typing import Any, Dict, List, Optional
9
-
10
- from rich.console import Console
11
- from rich.panel import Panel
12
- from rich.progress import Progress, SpinnerColumn, TextColumn
13
- from rich.table import Table
14
-
15
- from .config import WorkflowConfig
16
- from .database import PoliticianTradingDB
17
-
18
- logger = logging.getLogger(__name__)
19
- console = Console()
20
-
21
-
22
- class PoliticianTradingMonitor:
23
- """Monitor and report on politician trading data collection"""
24
-
25
- def __init__(self, config: WorkflowConfig = None):
26
- self.config = config or WorkflowConfig.default()
27
- self.db = PoliticianTradingDB(self.config)
28
-
29
- async def get_system_health(self) -> Dict[str, Any]:
30
- """Get overall system health status"""
31
- health = {
32
- "timestamp": datetime.utcnow().isoformat(),
33
- "status": "unknown",
34
- "database": {"status": "unknown"},
35
- "data_freshness": {"status": "unknown"},
36
- "recent_jobs": {"status": "unknown"},
37
- "errors": [],
38
- }
39
-
40
- try:
41
- # Test database connection
42
- db_health = await self._check_database_health()
43
- health["database"] = db_health
44
-
45
- # Check data freshness
46
- freshness = await self._check_data_freshness()
47
- health["data_freshness"] = freshness
48
-
49
- # Check recent job status
50
- job_health = await self._check_recent_jobs()
51
- health["recent_jobs"] = job_health
52
-
53
- # Determine overall status
54
- if all(h["status"] == "healthy" for h in [db_health, freshness, job_health]):
55
- health["status"] = "healthy"
56
- elif any(h["status"] == "critical" for h in [db_health, freshness, job_health]):
57
- health["status"] = "critical"
58
- else:
59
- health["status"] = "degraded"
60
-
61
- except Exception as e:
62
- logger.error(f"Health check failed: {e}")
63
- health["status"] = "critical"
64
- health["errors"].append(str(e))
65
-
66
- return health
67
-
68
- async def _check_database_health(self) -> Dict[str, Any]:
69
- """Check database connectivity and basic operations"""
70
- try:
71
- # Test connection with a simple query
72
- result = self.db.client.table("data_pull_jobs").select("count").execute()
73
-
74
- return {
75
- "status": "healthy",
76
- "connection": "ok",
77
- "last_check": datetime.utcnow().isoformat(),
78
- }
79
-
80
- except Exception as e:
81
- return {
82
- "status": "critical",
83
- "connection": "failed",
84
- "error": str(e),
85
- "last_check": datetime.utcnow().isoformat(),
86
- }
87
-
88
- async def _check_data_freshness(self) -> Dict[str, Any]:
89
- """Check how fresh our data is"""
90
- try:
91
- # Get most recent disclosure
92
- recent_disclosures = (
93
- self.db.client.table("trading_disclosures")
94
- .select("created_at")
95
- .order("created_at", desc=True)
96
- .limit(1)
97
- .execute()
98
- )
99
-
100
- # Get most recent successful job
101
- recent_jobs = (
102
- self.db.client.table("data_pull_jobs")
103
- .select("completed_at")
104
- .eq("status", "completed")
105
- .order("completed_at", desc=True)
106
- .limit(1)
107
- .execute()
108
- )
109
-
110
- now = datetime.now(timezone.utc)
111
- status = "healthy"
112
-
113
- # Check if we have recent data
114
- if recent_disclosures.data:
115
- last_disclosure = datetime.fromisoformat(
116
- recent_disclosures.data[0]["created_at"].replace("Z", "+00:00")
117
- )
118
- hours_since_disclosure = (now - last_disclosure).total_seconds() / 3600
119
-
120
- if hours_since_disclosure > 168: # 1 week
121
- status = "critical"
122
- elif hours_since_disclosure > 48: # 2 days
123
- status = "degraded"
124
- else:
125
- status = "critical" # No disclosures at all
126
-
127
- # Check recent job success
128
- if recent_jobs.data:
129
- last_job = datetime.fromisoformat(
130
- recent_jobs.data[0]["completed_at"].replace("Z", "+00:00")
131
- )
132
- hours_since_job = (now - last_job).total_seconds() / 3600
133
-
134
- if hours_since_job > 24: # Should run at least daily
135
- status = "degraded" if status == "healthy" else status
136
- else:
137
- status = "critical" # No successful jobs
138
-
139
- return {
140
- "status": status,
141
- "last_disclosure": (
142
- recent_disclosures.data[0]["created_at"] if recent_disclosures.data else None
143
- ),
144
- "last_successful_job": (
145
- recent_jobs.data[0]["completed_at"] if recent_jobs.data else None
146
- ),
147
- "hours_since_disclosure": (
148
- hours_since_disclosure if recent_disclosures.data else None
149
- ),
150
- "hours_since_job": hours_since_job if recent_jobs.data else None,
151
- }
152
-
153
- except Exception as e:
154
- return {"status": "critical", "error": str(e)}
155
-
156
- async def _check_recent_jobs(self) -> Dict[str, Any]:
157
- """Check recent job execution status"""
158
- try:
159
- # Get jobs from last 24 hours
160
- yesterday = datetime.now(timezone.utc) - timedelta(hours=24)
161
-
162
- recent_jobs = (
163
- self.db.client.table("data_pull_jobs")
164
- .select("*")
165
- .gte("started_at", yesterday.isoformat())
166
- .order("started_at", desc=True)
167
- .execute()
168
- )
169
-
170
- if not recent_jobs.data:
171
- return {
172
- "status": "degraded",
173
- "message": "No jobs executed in last 24 hours",
174
- "job_count": 0,
175
- }
176
-
177
- jobs = recent_jobs.data
178
- total_jobs = len(jobs)
179
- failed_jobs = len([j for j in jobs if j["status"] == "failed"])
180
- success_rate = (total_jobs - failed_jobs) / total_jobs if total_jobs > 0 else 0
181
-
182
- if success_rate >= 0.8:
183
- status = "healthy"
184
- elif success_rate >= 0.5:
185
- status = "degraded"
186
- else:
187
- status = "critical"
188
-
189
- return {
190
- "status": status,
191
- "job_count": total_jobs,
192
- "failed_jobs": failed_jobs,
193
- "success_rate": round(success_rate * 100, 1),
194
- "recent_jobs": jobs[:5], # Last 5 jobs
195
- }
196
-
197
- except Exception as e:
198
- return {"status": "critical", "error": str(e)}
199
-
200
- async def get_detailed_stats(self) -> Dict[str, Any]:
201
- """Get detailed statistics about the data collection"""
202
- try:
203
- # Get total counts
204
- politicians_result = self.db.client.table("politicians").select("id").execute()
205
-
206
- disclosures_result = self.db.client.table("trading_disclosures").select("id").execute()
207
-
208
- jobs_result = self.db.client.table("data_pull_jobs").select("id").execute()
209
-
210
- # Get breakdown by region/role
211
- us_politicians = (
212
- self.db.client.table("politicians")
213
- .select("id")
214
- .in_("role", ["us_house_rep", "us_senator"])
215
- .execute()
216
- )
217
-
218
- eu_politicians = (
219
- self.db.client.table("politicians").select("id").eq("role", "eu_mep").execute()
220
- )
221
-
222
- # Get recent activity (last 30 days)
223
- thirty_days_ago = datetime.utcnow() - timedelta(days=30)
224
- recent_disclosures = (
225
- self.db.client.table("trading_disclosures")
226
- .select("id")
227
- .gte("created_at", thirty_days_ago.isoformat())
228
- .execute()
229
- )
230
-
231
- # Get top assets by volume
232
- top_assets = (
233
- self.db.client.table("trading_disclosures")
234
- .select("asset_ticker")
235
- .not_.is_("asset_ticker", "null")
236
- .limit(100)
237
- .execute()
238
- )
239
-
240
- return {
241
- "total_counts": {
242
- "politicians": len(politicians_result.data) if politicians_result.data else 0,
243
- "disclosures": len(disclosures_result.data) if disclosures_result.data else 0,
244
- "jobs": len(jobs_result.data) if jobs_result.data else 0,
245
- },
246
- "politician_breakdown": {
247
- "us_total": len(us_politicians.data) if us_politicians.data else 0,
248
- "eu_total": len(eu_politicians.data) if eu_politicians.data else 0,
249
- },
250
- "recent_activity": {
251
- "disclosures_last_30_days": (
252
- len(recent_disclosures.data) if recent_disclosures.data else 0
253
- )
254
- },
255
- "top_assets": top_assets.data if top_assets.data else [],
256
- "generated_at": datetime.utcnow().isoformat(),
257
- }
258
-
259
- except Exception as e:
260
- logger.error(f"Failed to get detailed stats: {e}")
261
- return {"error": str(e)}
262
-
263
- def display_health_report(self, health: Dict[str, Any]):
264
- """Display a formatted health report"""
265
- console.print("\n🏛️ Politician Trading Monitor - System Health", style="bold cyan")
266
-
267
- # Overall status
268
- status_color = {
269
- "healthy": "green",
270
- "degraded": "yellow",
271
- "critical": "red",
272
- "unknown": "white",
273
- }.get(health["status"], "white")
274
-
275
- status_panel = Panel(
276
- f"Overall Status: [{status_color}]{health['status'].upper()}[/{status_color}]\n"
277
- f"Last Check: {health['timestamp']}",
278
- title="🎯 System Status",
279
- border_style=status_color,
280
- )
281
- console.print(status_panel)
282
-
283
- # Database health
284
- db_status = health.get("database", {})
285
- db_color = "green" if db_status.get("status") == "healthy" else "red"
286
-
287
- db_panel = Panel(
288
- f"Connection: [{db_color}]{db_status.get('connection', 'unknown')}[/{db_color}]\n"
289
- f"Last Check: {db_status.get('last_check', 'unknown')}",
290
- title="💾 Database",
291
- border_style=db_color,
292
- )
293
- console.print(db_panel)
294
-
295
- # Data freshness
296
- freshness = health.get("data_freshness", {})
297
- fresh_color = {"healthy": "green", "degraded": "yellow", "critical": "red"}.get(
298
- freshness.get("status"), "white"
299
- )
300
-
301
- fresh_text = (
302
- f"Status: [{fresh_color}]{freshness.get('status', 'unknown')}[/{fresh_color}]\n"
303
- )
304
-
305
- if freshness.get("hours_since_disclosure"):
306
- fresh_text += (
307
- f"Hours since last disclosure: {freshness['hours_since_disclosure']:.1f}\n"
308
- )
309
- if freshness.get("hours_since_job"):
310
- fresh_text += f"Hours since last job: {freshness['hours_since_job']:.1f}"
311
-
312
- fresh_panel = Panel(fresh_text, title="🕒 Data Freshness", border_style=fresh_color)
313
- console.print(fresh_panel)
314
-
315
- # Recent jobs
316
- jobs = health.get("recent_jobs", {})
317
- job_color = {"healthy": "green", "degraded": "yellow", "critical": "red"}.get(
318
- jobs.get("status"), "white"
319
- )
320
-
321
- job_text = f"Status: [{job_color}]{jobs.get('status', 'unknown')}[/{job_color}]\n"
322
- if jobs.get("job_count"):
323
- job_text += f"Jobs (24h): {jobs['job_count']}\n"
324
- job_text += f"Success Rate: {jobs.get('success_rate', 0)}%"
325
-
326
- job_panel = Panel(job_text, title="🔄 Recent Jobs", border_style=job_color)
327
- console.print(job_panel)
328
-
329
- # Errors
330
- if health.get("errors"):
331
- error_panel = Panel("\n".join(health["errors"]), title="⚠️ Errors", border_style="red")
332
- console.print(error_panel)
333
-
334
- def display_stats_report(self, stats: Dict[str, Any]):
335
- """Display detailed statistics"""
336
- if "error" in stats:
337
- console.print(f"❌ Failed to generate stats: {stats['error']}", style="red")
338
- return
339
-
340
- console.print("\n📊 Detailed Statistics", style="bold blue")
341
-
342
- # Summary table
343
- summary_table = Table(title="Summary Counts")
344
- summary_table.add_column("Metric", style="cyan")
345
- summary_table.add_column("Count", justify="right", style="green")
346
-
347
- total = stats.get("total_counts", {})
348
- breakdown = stats.get("politician_breakdown", {})
349
-
350
- summary_table.add_row("Total Politicians", f"{total.get('politicians', 0):,}")
351
- summary_table.add_row("- US Politicians", f"{breakdown.get('us_total', 0):,}")
352
- summary_table.add_row("- EU Politicians", f"{breakdown.get('eu_total', 0):,}")
353
- summary_table.add_row("Total Disclosures", f"{total.get('disclosures', 0):,}")
354
- summary_table.add_row(
355
- "Recent Disclosures (30d)",
356
- f"{stats.get('recent_activity', {}).get('disclosures_last_30_days', 0):,}",
357
- )
358
- summary_table.add_row("Total Jobs", f"{total.get('jobs', 0):,}")
359
-
360
- console.print(summary_table)
361
-
362
- # Top assets
363
- if stats.get("top_assets"):
364
- assets_table = Table(title="Recent Assets")
365
- assets_table.add_column("Asset Ticker", style="cyan")
366
-
367
- # Group by asset ticker to count occurrences
368
- from collections import Counter
369
-
370
- asset_counts = Counter(
371
- asset.get("asset_ticker", "Unknown") for asset in stats["top_assets"]
372
- )
373
-
374
- for asset_ticker, count in asset_counts.most_common(5): # Top 5
375
- assets_table.add_row(asset_ticker)
376
-
377
- console.print(assets_table)
378
-
379
-
380
- async def run_health_check() -> Dict[str, Any]:
381
- """Standalone function to run health check"""
382
- monitor = PoliticianTradingMonitor()
383
- return await monitor.get_system_health()
384
-
385
-
386
- async def run_stats_report() -> Dict[str, Any]:
387
- """Standalone function to generate stats report"""
388
- monitor = PoliticianTradingMonitor()
389
- return await monitor.get_detailed_stats()
390
-
391
-
392
- if __name__ == "__main__":
393
- # Allow running this file directly for testing
394
- async def main():
395
- monitor = PoliticianTradingMonitor()
396
-
397
- with Progress(
398
- SpinnerColumn(),
399
- TextColumn("[progress.description]{task.description}"),
400
- console=console,
401
- ) as progress:
402
- task = progress.add_task("Checking system health...", total=None)
403
-
404
- health = await monitor.get_system_health()
405
- progress.update(task, description="Generating detailed stats...")
406
-
407
- stats = await monitor.get_detailed_stats()
408
- progress.remove_task(task)
409
-
410
- monitor.display_health_report(health)
411
- monitor.display_stats_report(stats)
412
-
413
- asyncio.run(main())