mcli-framework 7.0.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.

Files changed (186) hide show
  1. mcli/app/chat_cmd.py +42 -0
  2. mcli/app/commands_cmd.py +226 -0
  3. mcli/app/completion_cmd.py +216 -0
  4. mcli/app/completion_helpers.py +288 -0
  5. mcli/app/cron_test_cmd.py +697 -0
  6. mcli/app/logs_cmd.py +419 -0
  7. mcli/app/main.py +492 -0
  8. mcli/app/model/model.py +1060 -0
  9. mcli/app/model_cmd.py +227 -0
  10. mcli/app/redis_cmd.py +269 -0
  11. mcli/app/video/video.py +1114 -0
  12. mcli/app/visual_cmd.py +303 -0
  13. mcli/chat/chat.py +2409 -0
  14. mcli/chat/command_rag.py +514 -0
  15. mcli/chat/enhanced_chat.py +652 -0
  16. mcli/chat/system_controller.py +1010 -0
  17. mcli/chat/system_integration.py +1016 -0
  18. mcli/cli.py +25 -0
  19. mcli/config.toml +20 -0
  20. mcli/lib/api/api.py +586 -0
  21. mcli/lib/api/daemon_client.py +203 -0
  22. mcli/lib/api/daemon_client_local.py +44 -0
  23. mcli/lib/api/daemon_decorator.py +217 -0
  24. mcli/lib/api/mcli_decorators.py +1032 -0
  25. mcli/lib/auth/auth.py +85 -0
  26. mcli/lib/auth/aws_manager.py +85 -0
  27. mcli/lib/auth/azure_manager.py +91 -0
  28. mcli/lib/auth/credential_manager.py +192 -0
  29. mcli/lib/auth/gcp_manager.py +93 -0
  30. mcli/lib/auth/key_manager.py +117 -0
  31. mcli/lib/auth/mcli_manager.py +93 -0
  32. mcli/lib/auth/token_manager.py +75 -0
  33. mcli/lib/auth/token_util.py +1011 -0
  34. mcli/lib/config/config.py +47 -0
  35. mcli/lib/discovery/__init__.py +1 -0
  36. mcli/lib/discovery/command_discovery.py +274 -0
  37. mcli/lib/erd/erd.py +1345 -0
  38. mcli/lib/erd/generate_graph.py +453 -0
  39. mcli/lib/files/files.py +76 -0
  40. mcli/lib/fs/fs.py +109 -0
  41. mcli/lib/lib.py +29 -0
  42. mcli/lib/logger/logger.py +611 -0
  43. mcli/lib/performance/optimizer.py +409 -0
  44. mcli/lib/performance/rust_bridge.py +502 -0
  45. mcli/lib/performance/uvloop_config.py +154 -0
  46. mcli/lib/pickles/pickles.py +50 -0
  47. mcli/lib/search/cached_vectorizer.py +479 -0
  48. mcli/lib/services/data_pipeline.py +460 -0
  49. mcli/lib/services/lsh_client.py +441 -0
  50. mcli/lib/services/redis_service.py +387 -0
  51. mcli/lib/shell/shell.py +137 -0
  52. mcli/lib/toml/toml.py +33 -0
  53. mcli/lib/ui/styling.py +47 -0
  54. mcli/lib/ui/visual_effects.py +634 -0
  55. mcli/lib/watcher/watcher.py +185 -0
  56. mcli/ml/api/app.py +215 -0
  57. mcli/ml/api/middleware.py +224 -0
  58. mcli/ml/api/routers/admin_router.py +12 -0
  59. mcli/ml/api/routers/auth_router.py +244 -0
  60. mcli/ml/api/routers/backtest_router.py +12 -0
  61. mcli/ml/api/routers/data_router.py +12 -0
  62. mcli/ml/api/routers/model_router.py +302 -0
  63. mcli/ml/api/routers/monitoring_router.py +12 -0
  64. mcli/ml/api/routers/portfolio_router.py +12 -0
  65. mcli/ml/api/routers/prediction_router.py +267 -0
  66. mcli/ml/api/routers/trade_router.py +12 -0
  67. mcli/ml/api/routers/websocket_router.py +76 -0
  68. mcli/ml/api/schemas.py +64 -0
  69. mcli/ml/auth/auth_manager.py +425 -0
  70. mcli/ml/auth/models.py +154 -0
  71. mcli/ml/auth/permissions.py +302 -0
  72. mcli/ml/backtesting/backtest_engine.py +502 -0
  73. mcli/ml/backtesting/performance_metrics.py +393 -0
  74. mcli/ml/cache.py +400 -0
  75. mcli/ml/cli/main.py +398 -0
  76. mcli/ml/config/settings.py +394 -0
  77. mcli/ml/configs/dvc_config.py +230 -0
  78. mcli/ml/configs/mlflow_config.py +131 -0
  79. mcli/ml/configs/mlops_manager.py +293 -0
  80. mcli/ml/dashboard/app.py +532 -0
  81. mcli/ml/dashboard/app_integrated.py +738 -0
  82. mcli/ml/dashboard/app_supabase.py +560 -0
  83. mcli/ml/dashboard/app_training.py +615 -0
  84. mcli/ml/dashboard/cli.py +51 -0
  85. mcli/ml/data_ingestion/api_connectors.py +501 -0
  86. mcli/ml/data_ingestion/data_pipeline.py +567 -0
  87. mcli/ml/data_ingestion/stream_processor.py +512 -0
  88. mcli/ml/database/migrations/env.py +94 -0
  89. mcli/ml/database/models.py +667 -0
  90. mcli/ml/database/session.py +200 -0
  91. mcli/ml/experimentation/ab_testing.py +845 -0
  92. mcli/ml/features/ensemble_features.py +607 -0
  93. mcli/ml/features/political_features.py +676 -0
  94. mcli/ml/features/recommendation_engine.py +809 -0
  95. mcli/ml/features/stock_features.py +573 -0
  96. mcli/ml/features/test_feature_engineering.py +346 -0
  97. mcli/ml/logging.py +85 -0
  98. mcli/ml/mlops/data_versioning.py +518 -0
  99. mcli/ml/mlops/experiment_tracker.py +377 -0
  100. mcli/ml/mlops/model_serving.py +481 -0
  101. mcli/ml/mlops/pipeline_orchestrator.py +614 -0
  102. mcli/ml/models/base_models.py +324 -0
  103. mcli/ml/models/ensemble_models.py +675 -0
  104. mcli/ml/models/recommendation_models.py +474 -0
  105. mcli/ml/models/test_models.py +487 -0
  106. mcli/ml/monitoring/drift_detection.py +676 -0
  107. mcli/ml/monitoring/metrics.py +45 -0
  108. mcli/ml/optimization/portfolio_optimizer.py +834 -0
  109. mcli/ml/preprocessing/data_cleaners.py +451 -0
  110. mcli/ml/preprocessing/feature_extractors.py +491 -0
  111. mcli/ml/preprocessing/ml_pipeline.py +382 -0
  112. mcli/ml/preprocessing/politician_trading_preprocessor.py +569 -0
  113. mcli/ml/preprocessing/test_preprocessing.py +294 -0
  114. mcli/ml/scripts/populate_sample_data.py +200 -0
  115. mcli/ml/tasks.py +400 -0
  116. mcli/ml/tests/test_integration.py +429 -0
  117. mcli/ml/tests/test_training_dashboard.py +387 -0
  118. mcli/public/oi/oi.py +15 -0
  119. mcli/public/public.py +4 -0
  120. mcli/self/self_cmd.py +1246 -0
  121. mcli/workflow/daemon/api_daemon.py +800 -0
  122. mcli/workflow/daemon/async_command_database.py +681 -0
  123. mcli/workflow/daemon/async_process_manager.py +591 -0
  124. mcli/workflow/daemon/client.py +530 -0
  125. mcli/workflow/daemon/commands.py +1196 -0
  126. mcli/workflow/daemon/daemon.py +905 -0
  127. mcli/workflow/daemon/daemon_api.py +59 -0
  128. mcli/workflow/daemon/enhanced_daemon.py +571 -0
  129. mcli/workflow/daemon/process_cli.py +244 -0
  130. mcli/workflow/daemon/process_manager.py +439 -0
  131. mcli/workflow/daemon/test_daemon.py +275 -0
  132. mcli/workflow/dashboard/dashboard_cmd.py +113 -0
  133. mcli/workflow/docker/docker.py +0 -0
  134. mcli/workflow/file/file.py +100 -0
  135. mcli/workflow/gcloud/config.toml +21 -0
  136. mcli/workflow/gcloud/gcloud.py +58 -0
  137. mcli/workflow/git_commit/ai_service.py +328 -0
  138. mcli/workflow/git_commit/commands.py +430 -0
  139. mcli/workflow/lsh_integration.py +355 -0
  140. mcli/workflow/model_service/client.py +594 -0
  141. mcli/workflow/model_service/download_and_run_efficient_models.py +288 -0
  142. mcli/workflow/model_service/lightweight_embedder.py +397 -0
  143. mcli/workflow/model_service/lightweight_model_server.py +714 -0
  144. mcli/workflow/model_service/lightweight_test.py +241 -0
  145. mcli/workflow/model_service/model_service.py +1955 -0
  146. mcli/workflow/model_service/ollama_efficient_runner.py +425 -0
  147. mcli/workflow/model_service/pdf_processor.py +386 -0
  148. mcli/workflow/model_service/test_efficient_runner.py +234 -0
  149. mcli/workflow/model_service/test_example.py +315 -0
  150. mcli/workflow/model_service/test_integration.py +131 -0
  151. mcli/workflow/model_service/test_new_features.py +149 -0
  152. mcli/workflow/openai/openai.py +99 -0
  153. mcli/workflow/politician_trading/commands.py +1790 -0
  154. mcli/workflow/politician_trading/config.py +134 -0
  155. mcli/workflow/politician_trading/connectivity.py +490 -0
  156. mcli/workflow/politician_trading/data_sources.py +395 -0
  157. mcli/workflow/politician_trading/database.py +410 -0
  158. mcli/workflow/politician_trading/demo.py +248 -0
  159. mcli/workflow/politician_trading/models.py +165 -0
  160. mcli/workflow/politician_trading/monitoring.py +413 -0
  161. mcli/workflow/politician_trading/scrapers.py +966 -0
  162. mcli/workflow/politician_trading/scrapers_california.py +412 -0
  163. mcli/workflow/politician_trading/scrapers_eu.py +377 -0
  164. mcli/workflow/politician_trading/scrapers_uk.py +350 -0
  165. mcli/workflow/politician_trading/scrapers_us_states.py +438 -0
  166. mcli/workflow/politician_trading/supabase_functions.py +354 -0
  167. mcli/workflow/politician_trading/workflow.py +852 -0
  168. mcli/workflow/registry/registry.py +180 -0
  169. mcli/workflow/repo/repo.py +223 -0
  170. mcli/workflow/scheduler/commands.py +493 -0
  171. mcli/workflow/scheduler/cron_parser.py +238 -0
  172. mcli/workflow/scheduler/job.py +182 -0
  173. mcli/workflow/scheduler/monitor.py +139 -0
  174. mcli/workflow/scheduler/persistence.py +324 -0
  175. mcli/workflow/scheduler/scheduler.py +679 -0
  176. mcli/workflow/sync/sync_cmd.py +437 -0
  177. mcli/workflow/sync/test_cmd.py +314 -0
  178. mcli/workflow/videos/videos.py +242 -0
  179. mcli/workflow/wakatime/wakatime.py +11 -0
  180. mcli/workflow/workflow.py +37 -0
  181. mcli_framework-7.0.0.dist-info/METADATA +479 -0
  182. mcli_framework-7.0.0.dist-info/RECORD +186 -0
  183. mcli_framework-7.0.0.dist-info/WHEEL +5 -0
  184. mcli_framework-7.0.0.dist-info/entry_points.txt +7 -0
  185. mcli_framework-7.0.0.dist-info/licenses/LICENSE +21 -0
  186. mcli_framework-7.0.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,165 @@
1
+ """
2
+ Data models for politician trading information
3
+ """
4
+
5
+ from dataclasses import dataclass, field
6
+ from datetime import datetime
7
+ from enum import Enum
8
+ from typing import List, Optional, Dict, Any
9
+ from decimal import Decimal
10
+
11
+
12
+ class PoliticianRole(Enum):
13
+ """Political roles"""
14
+
15
+ US_HOUSE_REP = "us_house_representative"
16
+ US_SENATOR = "us_senator"
17
+ UK_MP = "uk_member_of_parliament"
18
+ EU_MEP = "eu_parliament_member"
19
+ EU_COMMISSIONER = "eu_commissioner"
20
+ EU_COUNCIL_MEMBER = "eu_council_member"
21
+
22
+ # EU Member State Roles
23
+ GERMAN_BUNDESTAG = "german_bundestag_member"
24
+ FRENCH_DEPUTY = "french_national_assembly_deputy"
25
+ ITALIAN_DEPUTY = "italian_chamber_deputy"
26
+ ITALIAN_SENATOR = "italian_senate_member"
27
+ SPANISH_DEPUTY = "spanish_congress_deputy"
28
+ DUTCH_MP = "dutch_tweede_kamer_member"
29
+
30
+ # US State Roles
31
+ TEXAS_STATE_OFFICIAL = "texas_state_official"
32
+ NEW_YORK_STATE_OFFICIAL = "new_york_state_official"
33
+ FLORIDA_STATE_OFFICIAL = "florida_state_official"
34
+ ILLINOIS_STATE_OFFICIAL = "illinois_state_official"
35
+ PENNSYLVANIA_STATE_OFFICIAL = "pennsylvania_state_official"
36
+ MASSACHUSETTS_STATE_OFFICIAL = "massachusetts_state_official"
37
+ CALIFORNIA_STATE_OFFICIAL = "california_state_official"
38
+
39
+
40
+ class TransactionType(Enum):
41
+ """Types of financial transactions"""
42
+
43
+ PURCHASE = "purchase"
44
+ SALE = "sale"
45
+ EXCHANGE = "exchange"
46
+ OPTION_PURCHASE = "option_purchase"
47
+ OPTION_SALE = "option_sale"
48
+
49
+
50
+ class DisclosureStatus(Enum):
51
+ """Status of disclosure processing"""
52
+
53
+ PENDING = "pending"
54
+ PROCESSED = "processed"
55
+ FAILED = "failed"
56
+ DUPLICATE = "duplicate"
57
+
58
+
59
+ @dataclass
60
+ class Politician:
61
+ """Politician information"""
62
+
63
+ id: Optional[str] = None
64
+ first_name: str = ""
65
+ last_name: str = ""
66
+ full_name: str = ""
67
+ role: PoliticianRole = PoliticianRole.US_HOUSE_REP
68
+ party: str = ""
69
+ state_or_country: str = ""
70
+ district: Optional[str] = None
71
+ term_start: Optional[datetime] = None
72
+ term_end: Optional[datetime] = None
73
+
74
+ # External identifiers
75
+ bioguide_id: Optional[str] = None # US Congress bioguide ID
76
+ eu_id: Optional[str] = None # EU Parliament ID
77
+
78
+ created_at: datetime = field(default_factory=datetime.utcnow)
79
+ updated_at: datetime = field(default_factory=datetime.utcnow)
80
+
81
+
82
+ @dataclass
83
+ class TradingDisclosure:
84
+ """Individual trading disclosure"""
85
+
86
+ id: Optional[str] = None
87
+ politician_id: str = ""
88
+
89
+ # Transaction details
90
+ transaction_date: datetime = field(default_factory=datetime.utcnow)
91
+ disclosure_date: datetime = field(default_factory=datetime.utcnow)
92
+ transaction_type: TransactionType = TransactionType.PURCHASE
93
+
94
+ # Asset information
95
+ asset_name: str = ""
96
+ asset_ticker: Optional[str] = None
97
+ asset_type: str = "" # stock, bond, option, etc.
98
+
99
+ # Financial details
100
+ amount_range_min: Optional[Decimal] = None
101
+ amount_range_max: Optional[Decimal] = None
102
+ amount_exact: Optional[Decimal] = None
103
+
104
+ # Source information
105
+ source_url: str = ""
106
+ source_document_id: Optional[str] = None
107
+ raw_data: Dict[str, Any] = field(default_factory=dict)
108
+
109
+ # Processing status
110
+ status: DisclosureStatus = DisclosureStatus.PENDING
111
+ processing_notes: str = ""
112
+
113
+ created_at: datetime = field(default_factory=datetime.utcnow)
114
+ updated_at: datetime = field(default_factory=datetime.utcnow)
115
+
116
+
117
+ @dataclass
118
+ class DataPullJob:
119
+ """Information about data pull jobs"""
120
+
121
+ id: Optional[str] = None
122
+ job_type: str = "" # "us_congress", "eu_parliament", etc.
123
+ status: str = "pending" # pending, running, completed, failed
124
+
125
+ started_at: Optional[datetime] = None
126
+ completed_at: Optional[datetime] = None
127
+
128
+ # Results
129
+ records_found: int = 0
130
+ records_processed: int = 0
131
+ records_new: int = 0
132
+ records_updated: int = 0
133
+ records_failed: int = 0
134
+
135
+ # Error information
136
+ error_message: Optional[str] = None
137
+ error_details: Dict[str, Any] = field(default_factory=dict)
138
+
139
+ # Configuration used
140
+ config_snapshot: Dict[str, Any] = field(default_factory=dict)
141
+
142
+ created_at: datetime = field(default_factory=datetime.utcnow)
143
+
144
+
145
+ @dataclass
146
+ class DataSource:
147
+ """Information about data sources"""
148
+
149
+ id: Optional[str] = None
150
+ name: str = ""
151
+ url: str = ""
152
+ source_type: str = "" # "official", "aggregator", "api"
153
+ region: str = "" # "us", "eu"
154
+
155
+ # Status tracking
156
+ is_active: bool = True
157
+ last_successful_pull: Optional[datetime] = None
158
+ last_attempt: Optional[datetime] = None
159
+ consecutive_failures: int = 0
160
+
161
+ # Configuration
162
+ request_config: Dict[str, Any] = field(default_factory=dict)
163
+
164
+ created_at: datetime = field(default_factory=datetime.utcnow)
165
+ updated_at: datetime = field(default_factory=datetime.utcnow)
@@ -0,0 +1,413 @@
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 Dict, List, Any, Optional
9
+
10
+ from rich.console import Console
11
+ from rich.table import Table
12
+ from rich.panel import Panel
13
+ from rich.progress import Progress, SpinnerColumn, TextColumn
14
+
15
+ from .database import PoliticianTradingDB
16
+ from .config import WorkflowConfig
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 = (
207
+ self.db.client.table("trading_disclosures").select("id").execute()
208
+ )
209
+
210
+ jobs_result = self.db.client.table("data_pull_jobs").select("id").execute()
211
+
212
+ # Get breakdown by region/role
213
+ us_politicians = (
214
+ self.db.client.table("politicians")
215
+ .select("id")
216
+ .in_("role", ["us_house_rep", "us_senator"])
217
+ .execute()
218
+ )
219
+
220
+ eu_politicians = (
221
+ self.db.client.table("politicians")
222
+ .select("id")
223
+ .eq("role", "eu_mep")
224
+ .execute()
225
+ )
226
+
227
+ # Get recent activity (last 30 days)
228
+ thirty_days_ago = datetime.utcnow() - timedelta(days=30)
229
+ recent_disclosures = (
230
+ self.db.client.table("trading_disclosures")
231
+ .select("id")
232
+ .gte("created_at", thirty_days_ago.isoformat())
233
+ .execute()
234
+ )
235
+
236
+ # Get top assets by volume
237
+ top_assets = (
238
+ self.db.client.table("trading_disclosures")
239
+ .select("asset_ticker")
240
+ .not_.is_("asset_ticker", "null")
241
+ .limit(100)
242
+ .execute()
243
+ )
244
+
245
+ return {
246
+ "total_counts": {
247
+ "politicians": len(politicians_result.data) if politicians_result.data else 0,
248
+ "disclosures": len(disclosures_result.data) if disclosures_result.data else 0,
249
+ "jobs": len(jobs_result.data) if jobs_result.data else 0,
250
+ },
251
+ "politician_breakdown": {
252
+ "us_total": len(us_politicians.data) if us_politicians.data else 0,
253
+ "eu_total": len(eu_politicians.data) if eu_politicians.data else 0,
254
+ },
255
+ "recent_activity": {
256
+ "disclosures_last_30_days": len(recent_disclosures.data) if recent_disclosures.data else 0
257
+ },
258
+ "top_assets": top_assets.data if top_assets.data else [],
259
+ "generated_at": datetime.utcnow().isoformat(),
260
+ }
261
+
262
+ except Exception as e:
263
+ logger.error(f"Failed to get detailed stats: {e}")
264
+ return {"error": str(e)}
265
+
266
+ def display_health_report(self, health: Dict[str, Any]):
267
+ """Display a formatted health report"""
268
+ console.print("\nšŸ›ļø Politician Trading Monitor - System Health", style="bold cyan")
269
+
270
+ # Overall status
271
+ status_color = {
272
+ "healthy": "green",
273
+ "degraded": "yellow",
274
+ "critical": "red",
275
+ "unknown": "white",
276
+ }.get(health["status"], "white")
277
+
278
+ status_panel = Panel(
279
+ f"Overall Status: [{status_color}]{health['status'].upper()}[/{status_color}]\n"
280
+ f"Last Check: {health['timestamp']}",
281
+ title="šŸŽÆ System Status",
282
+ border_style=status_color,
283
+ )
284
+ console.print(status_panel)
285
+
286
+ # Database health
287
+ db_status = health.get("database", {})
288
+ db_color = "green" if db_status.get("status") == "healthy" else "red"
289
+
290
+ db_panel = Panel(
291
+ f"Connection: [{db_color}]{db_status.get('connection', 'unknown')}[/{db_color}]\n"
292
+ f"Last Check: {db_status.get('last_check', 'unknown')}",
293
+ title="šŸ’¾ Database",
294
+ border_style=db_color,
295
+ )
296
+ console.print(db_panel)
297
+
298
+ # Data freshness
299
+ freshness = health.get("data_freshness", {})
300
+ fresh_color = {"healthy": "green", "degraded": "yellow", "critical": "red"}.get(
301
+ freshness.get("status"), "white"
302
+ )
303
+
304
+ fresh_text = (
305
+ f"Status: [{fresh_color}]{freshness.get('status', 'unknown')}[/{fresh_color}]\n"
306
+ )
307
+
308
+ if freshness.get("hours_since_disclosure"):
309
+ fresh_text += (
310
+ f"Hours since last disclosure: {freshness['hours_since_disclosure']:.1f}\n"
311
+ )
312
+ if freshness.get("hours_since_job"):
313
+ fresh_text += f"Hours since last job: {freshness['hours_since_job']:.1f}"
314
+
315
+ fresh_panel = Panel(fresh_text, title="šŸ•’ Data Freshness", border_style=fresh_color)
316
+ console.print(fresh_panel)
317
+
318
+ # Recent jobs
319
+ jobs = health.get("recent_jobs", {})
320
+ job_color = {"healthy": "green", "degraded": "yellow", "critical": "red"}.get(
321
+ jobs.get("status"), "white"
322
+ )
323
+
324
+ job_text = f"Status: [{job_color}]{jobs.get('status', 'unknown')}[/{job_color}]\n"
325
+ if jobs.get("job_count"):
326
+ job_text += f"Jobs (24h): {jobs['job_count']}\n"
327
+ job_text += f"Success Rate: {jobs.get('success_rate', 0)}%"
328
+
329
+ job_panel = Panel(job_text, title="šŸ”„ Recent Jobs", border_style=job_color)
330
+ console.print(job_panel)
331
+
332
+ # Errors
333
+ if health.get("errors"):
334
+ error_panel = Panel("\n".join(health["errors"]), title="āš ļø Errors", border_style="red")
335
+ console.print(error_panel)
336
+
337
+ def display_stats_report(self, stats: Dict[str, Any]):
338
+ """Display detailed statistics"""
339
+ if "error" in stats:
340
+ console.print(f"āŒ Failed to generate stats: {stats['error']}", style="red")
341
+ return
342
+
343
+ console.print("\nšŸ“Š Detailed Statistics", style="bold blue")
344
+
345
+ # Summary table
346
+ summary_table = Table(title="Summary Counts")
347
+ summary_table.add_column("Metric", style="cyan")
348
+ summary_table.add_column("Count", justify="right", style="green")
349
+
350
+ total = stats.get("total_counts", {})
351
+ breakdown = stats.get("politician_breakdown", {})
352
+
353
+ summary_table.add_row("Total Politicians", f"{total.get('politicians', 0):,}")
354
+ summary_table.add_row("- US Politicians", f"{breakdown.get('us_total', 0):,}")
355
+ summary_table.add_row("- EU Politicians", f"{breakdown.get('eu_total', 0):,}")
356
+ summary_table.add_row("Total Disclosures", f"{total.get('disclosures', 0):,}")
357
+ summary_table.add_row(
358
+ "Recent Disclosures (30d)",
359
+ f"{stats.get('recent_activity', {}).get('disclosures_last_30_days', 0):,}",
360
+ )
361
+ summary_table.add_row("Total Jobs", f"{total.get('jobs', 0):,}")
362
+
363
+ console.print(summary_table)
364
+
365
+ # Top assets
366
+ if stats.get("top_assets"):
367
+ assets_table = Table(title="Recent Assets")
368
+ assets_table.add_column("Asset Ticker", style="cyan")
369
+
370
+ # Group by asset ticker to count occurrences
371
+ from collections import Counter
372
+ asset_counts = Counter(asset.get("asset_ticker", "Unknown") for asset in stats["top_assets"])
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())