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,410 @@
1
+ """
2
+ Database client and schema management for politician trading data
3
+ """
4
+
5
+ import asyncio
6
+ import logging
7
+ from datetime import datetime
8
+ from typing import List, Optional, Dict, Any
9
+ from uuid import uuid4
10
+
11
+ from supabase import create_client, Client
12
+ from postgrest.exceptions import APIError
13
+
14
+ from .config import WorkflowConfig
15
+ from .models import Politician, TradingDisclosure, DataPullJob, DataSource
16
+
17
+ logger = logging.getLogger(__name__)
18
+
19
+
20
+ class PoliticianTradingDB:
21
+ """Database client for politician trading data"""
22
+
23
+ def __init__(self, config: WorkflowConfig):
24
+ self.config = config
25
+ self.client: Optional[Client] = None
26
+ self._init_client()
27
+
28
+ def _init_client(self):
29
+ """Initialize Supabase client"""
30
+ try:
31
+ self.client = create_client(self.config.supabase.url, self.config.supabase.key)
32
+ logger.info("Supabase client initialized successfully")
33
+ except Exception as e:
34
+ logger.error(f"Failed to initialize Supabase client: {e}")
35
+ raise
36
+
37
+ async def ensure_schema(self) -> bool:
38
+ """Ensure database schema exists"""
39
+ try:
40
+ # Check if tables exist by trying to query them
41
+ await self._check_table_exists("politicians")
42
+ await self._check_table_exists("trading_disclosures")
43
+ await self._check_table_exists("data_pull_jobs")
44
+ await self._check_table_exists("data_sources")
45
+ logger.info("Database schema verified")
46
+ return True
47
+ except Exception as e:
48
+ logger.error(f"Schema check failed: {e}")
49
+ logger.info("You'll need to create the database schema manually")
50
+ return False
51
+
52
+ async def _check_table_exists(self, table_name: str):
53
+ """Check if table exists"""
54
+ try:
55
+ result = self.client.table(table_name).select("*").limit(1).execute()
56
+ return True
57
+ except Exception as e:
58
+ logger.warning(f"Table {table_name} may not exist: {e}")
59
+ raise
60
+
61
+ # Politician management
62
+ async def get_politician(self, politician_id: str) -> Optional[Politician]:
63
+ """Get politician by ID"""
64
+ try:
65
+ result = self.client.table("politicians").select("*").eq("id", politician_id).execute()
66
+ if result.data:
67
+ return self._dict_to_politician(result.data[0])
68
+ return None
69
+ except Exception as e:
70
+ logger.error(f"Failed to get politician {politician_id}: {e}")
71
+ return None
72
+
73
+ async def find_politician_by_name(
74
+ self, first_name: str, last_name: str
75
+ ) -> Optional[Politician]:
76
+ """Find politician by name"""
77
+ try:
78
+ result = (
79
+ self.client.table("politicians")
80
+ .select("*")
81
+ .eq("first_name", first_name)
82
+ .eq("last_name", last_name)
83
+ .execute()
84
+ )
85
+ if result.data:
86
+ return self._dict_to_politician(result.data[0])
87
+ return None
88
+ except Exception as e:
89
+ logger.error(f"Failed to find politician {first_name} {last_name}: {e}")
90
+ return None
91
+
92
+ async def upsert_politician(self, politician: Politician) -> str:
93
+ """Insert or update politician"""
94
+ try:
95
+ # First, try to find an existing politician
96
+ existing = await self.find_politician_by_name(
97
+ politician.first_name, politician.last_name
98
+ )
99
+
100
+ if existing:
101
+ # Update existing politician (but don't change ID)
102
+ politician_dict = self._politician_to_dict(politician)
103
+ politician_dict["id"] = existing.id # Keep existing ID
104
+ politician_dict["updated_at"] = datetime.utcnow().isoformat()
105
+
106
+ result = self.client.table("politicians").update(
107
+ politician_dict
108
+ ).eq("id", existing.id).execute()
109
+
110
+ if result.data:
111
+ return result.data[0]["id"]
112
+ return existing.id
113
+ else:
114
+ # Insert new politician
115
+ politician_dict = self._politician_to_dict(politician)
116
+ if not politician_dict.get("id"):
117
+ politician_dict["id"] = str(uuid4())
118
+
119
+ politician_dict["created_at"] = datetime.utcnow().isoformat()
120
+ politician_dict["updated_at"] = datetime.utcnow().isoformat()
121
+
122
+ result = self.client.table("politicians").insert(
123
+ politician_dict
124
+ ).execute()
125
+
126
+ if result.data:
127
+ return result.data[0]["id"]
128
+ return politician_dict["id"]
129
+
130
+ except Exception as e:
131
+ logger.error(f"Failed to upsert politician: {e}")
132
+ # For debugging: log the politician data that caused the error
133
+ logger.error(f"Politician data: {politician.first_name} {politician.last_name}")
134
+ return "" # Return empty string instead of raising to prevent cascade failures
135
+
136
+ # Trading disclosure management
137
+ async def get_disclosure(self, disclosure_id: str) -> Optional[TradingDisclosure]:
138
+ """Get trading disclosure by ID"""
139
+ try:
140
+ result = (
141
+ self.client.table("trading_disclosures")
142
+ .select("*")
143
+ .eq("id", disclosure_id)
144
+ .execute()
145
+ )
146
+ if result.data:
147
+ return self._dict_to_disclosure(result.data[0])
148
+ return None
149
+ except Exception as e:
150
+ logger.error(f"Failed to get disclosure {disclosure_id}: {e}")
151
+ return None
152
+
153
+ async def find_disclosure_by_transaction(
154
+ self, politician_id: str, transaction_date: datetime, asset_name: str, transaction_type: str
155
+ ) -> Optional[TradingDisclosure]:
156
+ """Find existing disclosure by transaction details"""
157
+ try:
158
+ result = (
159
+ self.client.table("trading_disclosures")
160
+ .select("*")
161
+ .eq("politician_id", politician_id)
162
+ .eq("transaction_date", transaction_date.isoformat())
163
+ .eq("asset_name", asset_name)
164
+ .eq("transaction_type", transaction_type)
165
+ .execute()
166
+ )
167
+ if result.data:
168
+ return self._dict_to_disclosure(result.data[0])
169
+ return None
170
+ except Exception as e:
171
+ logger.error(f"Failed to find disclosure: {e}")
172
+ return None
173
+
174
+ async def insert_disclosure(self, disclosure: TradingDisclosure) -> str:
175
+ """Insert new trading disclosure"""
176
+ try:
177
+ disclosure_dict = self._disclosure_to_dict(disclosure)
178
+ if not disclosure_dict.get("id"):
179
+ disclosure_dict["id"] = str(uuid4())
180
+
181
+ result = self.client.table("trading_disclosures").insert(disclosure_dict).execute()
182
+ if result.data:
183
+ return result.data[0]["id"]
184
+ return disclosure_dict["id"]
185
+ except Exception as e:
186
+ logger.error(f"Failed to insert disclosure: {e}")
187
+ raise
188
+
189
+ async def update_disclosure(self, disclosure: TradingDisclosure) -> bool:
190
+ """Update existing trading disclosure"""
191
+ try:
192
+ disclosure_dict = self._disclosure_to_dict(disclosure)
193
+ disclosure_dict["updated_at"] = datetime.utcnow().isoformat()
194
+
195
+ result = (
196
+ self.client.table("trading_disclosures")
197
+ .update(disclosure_dict)
198
+ .eq("id", disclosure.id)
199
+ .execute()
200
+ )
201
+ return len(result.data) > 0
202
+ except Exception as e:
203
+ logger.error(f"Failed to update disclosure: {e}")
204
+ return False
205
+
206
+ async def get_recent_disclosures(self, limit: int = 100) -> List[TradingDisclosure]:
207
+ """Get recent trading disclosures"""
208
+ try:
209
+ result = (
210
+ self.client.table("trading_disclosures")
211
+ .select("*")
212
+ .order("disclosure_date", desc=True)
213
+ .limit(limit)
214
+ .execute()
215
+ )
216
+ return [self._dict_to_disclosure(d) for d in result.data]
217
+ except Exception as e:
218
+ logger.error(f"Failed to get recent disclosures: {e}")
219
+ return []
220
+
221
+ # Data pull job management
222
+ async def create_data_pull_job(self, job_type: str, config_snapshot: Dict[str, Any]) -> str:
223
+ """Create new data pull job"""
224
+ try:
225
+ job = DataPullJob(
226
+ id=str(uuid4()),
227
+ job_type=job_type,
228
+ status="pending",
229
+ config_snapshot=config_snapshot,
230
+ started_at=datetime.utcnow(),
231
+ )
232
+
233
+ job_dict = self._job_to_dict(job)
234
+ result = self.client.table("data_pull_jobs").insert(job_dict).execute()
235
+ if result.data:
236
+ return result.data[0]["id"]
237
+ return job.id
238
+ except Exception as e:
239
+ logger.error(f"Failed to create data pull job: {e}")
240
+ raise
241
+
242
+ async def update_data_pull_job(self, job: DataPullJob) -> bool:
243
+ """Update data pull job"""
244
+ try:
245
+ job_dict = self._job_to_dict(job)
246
+ result = self.client.table("data_pull_jobs").update(job_dict).eq("id", job.id).execute()
247
+ return len(result.data) > 0
248
+ except Exception as e:
249
+ logger.error(f"Failed to update data pull job: {e}")
250
+ return False
251
+
252
+ async def get_job_status(self) -> Dict[str, Any]:
253
+ """Get current job status summary"""
254
+ try:
255
+ # Get recent jobs
256
+ result = (
257
+ self.client.table("data_pull_jobs")
258
+ .select("*")
259
+ .order("created_at", desc=True)
260
+ .limit(10)
261
+ .execute()
262
+ )
263
+
264
+ jobs = result.data
265
+
266
+ # Calculate summary statistics
267
+ total_disclosures = (
268
+ self.client.table("trading_disclosures").select("id", count="exact").execute()
269
+ ).count
270
+
271
+ recent_disclosures = (
272
+ self.client.table("trading_disclosures")
273
+ .select("id", count="exact")
274
+ .gte(
275
+ "created_at",
276
+ (datetime.utcnow().replace(hour=0, minute=0, second=0)).isoformat(),
277
+ )
278
+ .execute()
279
+ ).count
280
+
281
+ return {
282
+ "total_disclosures": total_disclosures,
283
+ "recent_disclosures_today": recent_disclosures,
284
+ "recent_jobs": jobs,
285
+ "last_update": datetime.utcnow().isoformat(),
286
+ }
287
+ except Exception as e:
288
+ logger.error(f"Failed to get job status: {e}")
289
+ return {"error": str(e)}
290
+
291
+ # Helper methods for data conversion
292
+ def _politician_to_dict(self, politician: Politician) -> Dict[str, Any]:
293
+ """Convert Politician to dictionary"""
294
+ return {
295
+ "id": politician.id,
296
+ "first_name": politician.first_name,
297
+ "last_name": politician.last_name,
298
+ "full_name": politician.full_name,
299
+ "role": politician.role.value if politician.role else None,
300
+ "party": politician.party,
301
+ "state_or_country": politician.state_or_country,
302
+ "district": politician.district,
303
+ "term_start": politician.term_start.isoformat() if politician.term_start else None,
304
+ "term_end": politician.term_end.isoformat() if politician.term_end else None,
305
+ "bioguide_id": politician.bioguide_id,
306
+ "eu_id": politician.eu_id,
307
+ "created_at": politician.created_at.isoformat(),
308
+ "updated_at": politician.updated_at.isoformat(),
309
+ }
310
+
311
+ def _dict_to_politician(self, data: Dict[str, Any]) -> Politician:
312
+ """Convert dictionary to Politician"""
313
+ from .models import PoliticianRole
314
+
315
+ return Politician(
316
+ id=data.get("id"),
317
+ first_name=data.get("first_name", ""),
318
+ last_name=data.get("last_name", ""),
319
+ full_name=data.get("full_name", ""),
320
+ role=PoliticianRole(data.get("role", "us_house_representative")),
321
+ party=data.get("party", ""),
322
+ state_or_country=data.get("state_or_country", ""),
323
+ district=data.get("district"),
324
+ term_start=(
325
+ datetime.fromisoformat(data["term_start"]) if data.get("term_start") else None
326
+ ),
327
+ term_end=datetime.fromisoformat(data["term_end"]) if data.get("term_end") else None,
328
+ bioguide_id=data.get("bioguide_id"),
329
+ eu_id=data.get("eu_id"),
330
+ created_at=datetime.fromisoformat(data["created_at"]),
331
+ updated_at=datetime.fromisoformat(data["updated_at"]),
332
+ )
333
+
334
+ def _disclosure_to_dict(self, disclosure: TradingDisclosure) -> Dict[str, Any]:
335
+ """Convert TradingDisclosure to dictionary"""
336
+ return {
337
+ "id": disclosure.id,
338
+ "politician_id": disclosure.politician_id,
339
+ "transaction_date": disclosure.transaction_date.isoformat(),
340
+ "disclosure_date": disclosure.disclosure_date.isoformat(),
341
+ "transaction_type": (
342
+ disclosure.transaction_type.value if disclosure.transaction_type else None
343
+ ),
344
+ "asset_name": disclosure.asset_name,
345
+ "asset_ticker": disclosure.asset_ticker,
346
+ "asset_type": disclosure.asset_type,
347
+ "amount_range_min": (
348
+ float(disclosure.amount_range_min) if disclosure.amount_range_min else None
349
+ ),
350
+ "amount_range_max": (
351
+ float(disclosure.amount_range_max) if disclosure.amount_range_max else None
352
+ ),
353
+ "amount_exact": float(disclosure.amount_exact) if disclosure.amount_exact else None,
354
+ "source_url": disclosure.source_url,
355
+ "source_document_id": disclosure.source_document_id,
356
+ "raw_data": disclosure.raw_data,
357
+ "status": disclosure.status.value if disclosure.status else None,
358
+ "processing_notes": disclosure.processing_notes,
359
+ "created_at": disclosure.created_at.isoformat(),
360
+ "updated_at": disclosure.updated_at.isoformat(),
361
+ }
362
+
363
+ def _dict_to_disclosure(self, data: Dict[str, Any]) -> TradingDisclosure:
364
+ """Convert dictionary to TradingDisclosure"""
365
+ from .models import TransactionType, DisclosureStatus
366
+ from decimal import Decimal
367
+
368
+ return TradingDisclosure(
369
+ id=data.get("id"),
370
+ politician_id=data.get("politician_id", ""),
371
+ transaction_date=datetime.fromisoformat(data["transaction_date"]),
372
+ disclosure_date=datetime.fromisoformat(data["disclosure_date"]),
373
+ transaction_type=TransactionType(data.get("transaction_type", "purchase")),
374
+ asset_name=data.get("asset_name", ""),
375
+ asset_ticker=data.get("asset_ticker"),
376
+ asset_type=data.get("asset_type", ""),
377
+ amount_range_min=(
378
+ Decimal(str(data["amount_range_min"])) if data.get("amount_range_min") else None
379
+ ),
380
+ amount_range_max=(
381
+ Decimal(str(data["amount_range_max"])) if data.get("amount_range_max") else None
382
+ ),
383
+ amount_exact=Decimal(str(data["amount_exact"])) if data.get("amount_exact") else None,
384
+ source_url=data.get("source_url", ""),
385
+ source_document_id=data.get("source_document_id"),
386
+ raw_data=data.get("raw_data", {}),
387
+ status=DisclosureStatus(data.get("status", "pending")),
388
+ processing_notes=data.get("processing_notes", ""),
389
+ created_at=datetime.fromisoformat(data["created_at"]),
390
+ updated_at=datetime.fromisoformat(data["updated_at"]),
391
+ )
392
+
393
+ def _job_to_dict(self, job: DataPullJob) -> Dict[str, Any]:
394
+ """Convert DataPullJob to dictionary"""
395
+ return {
396
+ "id": job.id,
397
+ "job_type": job.job_type,
398
+ "status": job.status,
399
+ "started_at": job.started_at.isoformat() if job.started_at else None,
400
+ "completed_at": job.completed_at.isoformat() if job.completed_at else None,
401
+ "records_found": job.records_found,
402
+ "records_processed": job.records_processed,
403
+ "records_new": job.records_new,
404
+ "records_updated": job.records_updated,
405
+ "records_failed": job.records_failed,
406
+ "error_message": job.error_message,
407
+ "error_details": job.error_details,
408
+ "config_snapshot": job.config_snapshot,
409
+ "created_at": job.created_at.isoformat(),
410
+ }
@@ -0,0 +1,248 @@
1
+ """
2
+ Demonstration script showing politician trading workflow execution and data creation
3
+ """
4
+
5
+ import asyncio
6
+ import json
7
+ import uuid
8
+ from datetime import datetime
9
+ from rich.console import Console
10
+ from rich.table import Table
11
+ from rich.panel import Panel
12
+ from rich.json import JSON
13
+
14
+ from .workflow import run_politician_trading_collection
15
+ from .connectivity import SupabaseConnectivityValidator
16
+ from .monitoring import run_health_check, run_stats_report
17
+
18
+ console = Console()
19
+
20
+
21
+ async def demonstrate_workflow_execution():
22
+ """Comprehensive demonstration of the workflow execution"""
23
+
24
+ console.print("šŸ›ļø Politician Trading Data Collection - Full Demonstration", style="bold cyan")
25
+ console.print("=" * 80, style="dim")
26
+
27
+ # Step 1: Show what would happen with connectivity validation
28
+ console.print("\nšŸ“‹ STEP 1: Supabase Connectivity Validation", style="bold blue")
29
+ console.print("This step validates database connectivity and operations...")
30
+
31
+ validator = SupabaseConnectivityValidator()
32
+
33
+ # Show the types of tests that would run
34
+ tests_info = [
35
+ ("Basic Connection", "Tests fundamental database connectivity"),
36
+ ("Read Operations", "Validates ability to query all required tables"),
37
+ ("Write Operations", "Creates, updates, and deletes test records"),
38
+ ("Table Access", "Verifies schema and table structure"),
39
+ ("Job Tracking", "Tests job status and history tracking"),
40
+ ("Real-time Sync", "Validates immediate write/read consistency"),
41
+ ]
42
+
43
+ test_table = Table(title="Connectivity Tests")
44
+ test_table.add_column("Test", style="cyan")
45
+ test_table.add_column("Description", style="white")
46
+
47
+ for test_name, description in tests_info:
48
+ test_table.add_row(test_name, description)
49
+
50
+ console.print(test_table)
51
+
52
+ # Step 2: Show database schema that would be created
53
+ console.print("\nšŸ“‹ STEP 2: Database Schema Requirements", style="bold blue")
54
+
55
+ schema_info = [
56
+ (
57
+ "politicians",
58
+ "Stores politician information (US Congress, EU Parliament)",
59
+ "~1000 records",
60
+ ),
61
+ ("trading_disclosures", "Individual trading transactions/disclosures", "~50,000+ records"),
62
+ ("data_pull_jobs", "Job execution tracking and status", "~100 records"),
63
+ ("data_sources", "Data source configuration and health", "~10 records"),
64
+ ]
65
+
66
+ schema_table = Table(title="Database Tables")
67
+ schema_table.add_column("Table", style="cyan")
68
+ schema_table.add_column("Purpose", style="white")
69
+ schema_table.add_column("Expected Size", style="yellow")
70
+
71
+ for table_name, purpose, size in schema_info:
72
+ schema_table.add_row(table_name, purpose, size)
73
+
74
+ console.print(schema_table)
75
+
76
+ # Step 3: Demonstrate workflow execution
77
+ console.print("\nšŸ“‹ STEP 3: Workflow Execution Simulation", style="bold blue")
78
+ console.print("Running the politician trading collection workflow...")
79
+
80
+ try:
81
+ # This will attempt to run the workflow (may fail due to schema)
82
+ workflow_result = await run_politician_trading_collection()
83
+
84
+ # Show what the result structure looks like
85
+ console.print("\nšŸ” Workflow Result Structure:", style="bold")
86
+
87
+ # Create a mock successful result to show what it would look like
88
+ mock_successful_result = {
89
+ "started_at": "2024-09-02T09:00:00.000Z",
90
+ "completed_at": "2024-09-02T09:05:30.150Z",
91
+ "status": "completed",
92
+ "jobs": {
93
+ "us_congress": {
94
+ "job_id": "job_12345",
95
+ "status": "completed",
96
+ "new_disclosures": 15,
97
+ "updated_disclosures": 3,
98
+ "errors": [],
99
+ },
100
+ "eu_parliament": {
101
+ "job_id": "job_12346",
102
+ "status": "completed",
103
+ "new_disclosures": 8,
104
+ "updated_disclosures": 1,
105
+ "errors": [],
106
+ },
107
+ },
108
+ "summary": {"total_new_disclosures": 23, "total_updated_disclosures": 4, "errors": []},
109
+ }
110
+
111
+ console.print(JSON.from_data(mock_successful_result))
112
+
113
+ # Show the actual result we got
114
+ console.print("\nšŸ” Actual Workflow Result:", style="bold")
115
+ console.print(JSON.from_data(workflow_result))
116
+
117
+ except Exception as e:
118
+ console.print(f"\nāš ļø Workflow execution encountered expected issues: {e}", style="yellow")
119
+ console.print("This is normal when database schema hasn't been created yet.", style="dim")
120
+
121
+ # Step 4: Show what data would be created
122
+ console.print("\nšŸ“‹ STEP 4: Sample Data That Would Be Created", style="bold blue")
123
+
124
+ # Sample politician records
125
+ console.print("\nšŸ‘„ Sample Politician Records:", style="bold")
126
+ sample_politicians = [
127
+ {
128
+ "full_name": "Nancy Pelosi",
129
+ "role": "us_house_representative",
130
+ "party": "Democratic",
131
+ "state_or_country": "CA",
132
+ "district": "5",
133
+ "bioguide_id": "P000197",
134
+ },
135
+ {
136
+ "full_name": "Ted Cruz",
137
+ "role": "us_senator",
138
+ "party": "Republican",
139
+ "state_or_country": "TX",
140
+ "bioguide_id": "C001098",
141
+ },
142
+ ]
143
+
144
+ for politician in sample_politicians:
145
+ console.print(JSON.from_data(politician))
146
+
147
+ # Sample trading disclosures
148
+ console.print("\nšŸ’° Sample Trading Disclosure Records:", style="bold")
149
+ sample_disclosures = [
150
+ {
151
+ "politician_id": str(uuid.uuid4()),
152
+ "transaction_date": "2024-08-15T00:00:00Z",
153
+ "disclosure_date": "2024-08-20T00:00:00Z",
154
+ "transaction_type": "purchase",
155
+ "asset_name": "Apple Inc.",
156
+ "asset_ticker": "AAPL",
157
+ "asset_type": "stock",
158
+ "amount_range_min": 15001.00,
159
+ "amount_range_max": 50000.00,
160
+ "source_url": "https://disclosures-clerk.house.gov",
161
+ "status": "processed",
162
+ },
163
+ {
164
+ "politician_id": "pol_2",
165
+ "transaction_date": "2024-08-10T00:00:00Z",
166
+ "disclosure_date": "2024-08-25T00:00:00Z",
167
+ "transaction_type": "sale",
168
+ "asset_name": "Microsoft Corporation",
169
+ "asset_ticker": "MSFT",
170
+ "asset_type": "stock",
171
+ "amount_range_min": 1001.00,
172
+ "amount_range_max": 15000.00,
173
+ "source_url": "https://efdsearch.senate.gov",
174
+ "status": "processed",
175
+ },
176
+ ]
177
+
178
+ for disclosure in sample_disclosures:
179
+ console.print(JSON.from_data(disclosure))
180
+
181
+ # Step 5: Show job tracking
182
+ console.print("\nšŸ“‹ STEP 5: Job Tracking and Monitoring", style="bold blue")
183
+
184
+ sample_job_record = {
185
+ "id": "job_12345",
186
+ "job_type": "us_congress",
187
+ "status": "completed",
188
+ "started_at": "2024-09-02T09:00:00Z",
189
+ "completed_at": "2024-09-02T09:03:45Z",
190
+ "records_found": 20,
191
+ "records_processed": 18,
192
+ "records_new": 15,
193
+ "records_updated": 3,
194
+ "records_failed": 2,
195
+ "config_snapshot": {
196
+ "supabase_url": "https://uljsqvwkomdrlnofmlad.supabase.co",
197
+ "request_delay": 1.0,
198
+ "max_retries": 3,
199
+ },
200
+ }
201
+
202
+ console.print("šŸ“Š Sample Job Record:", style="bold")
203
+ console.print(JSON.from_data(sample_job_record))
204
+
205
+ # Step 6: Show CLI commands for management
206
+ console.print("\nšŸ“‹ STEP 6: CLI Commands for Management", style="bold blue")
207
+
208
+ commands_info = [
209
+ ("politician-trading setup --create-tables", "Create database schema"),
210
+ ("politician-trading connectivity", "Test Supabase connectivity"),
211
+ ("politician-trading run", "Execute data collection"),
212
+ ("politician-trading status", "Check system status"),
213
+ ("politician-trading health", "System health monitoring"),
214
+ ("politician-trading stats", "View detailed statistics"),
215
+ ("politician-trading test-workflow -v", "Run full workflow test"),
216
+ ("politician-trading connectivity --continuous", "Continuous monitoring"),
217
+ ("politician-trading cron-job --create", "Setup automated scheduling"),
218
+ ]
219
+
220
+ commands_table = Table(title="Available CLI Commands")
221
+ commands_table.add_column("Command", style="cyan")
222
+ commands_table.add_column("Description", style="white")
223
+
224
+ for command, description in commands_info:
225
+ commands_table.add_row(command, description)
226
+
227
+ console.print(commands_table)
228
+
229
+ # Summary
230
+ console.print("\nšŸ“‹ SUMMARY", style="bold green")
231
+ console.print("āœ… Workflow validates Supabase connectivity with 6 comprehensive tests")
232
+ console.print("āœ… Creates and manages 4 database tables with proper indexing")
233
+ console.print("āœ… Scrapes data from US Congress and EU Parliament sources")
234
+ console.print("āœ… Tracks job execution with detailed status and metrics")
235
+ console.print("āœ… Provides comprehensive CLI for management and monitoring")
236
+ console.print("āœ… Supports automated scheduling via Supabase cron jobs")
237
+ console.print("āœ… Includes real-time monitoring and health checks")
238
+
239
+ console.print("\nšŸš€ Next Steps to Deploy:", style="bold blue")
240
+ console.print("1. Execute the schema.sql in your Supabase SQL editor")
241
+ console.print("2. Run: politician-trading setup --verify")
242
+ console.print("3. Run: politician-trading connectivity")
243
+ console.print("4. Run: politician-trading test-workflow --verbose")
244
+ console.print("5. Setup cron job: politician-trading cron-job --create")
245
+
246
+
247
+ if __name__ == "__main__":
248
+ asyncio.run(demonstrate_workflow_execution())