mcli-framework 7.1.0__py3-none-any.whl → 7.1.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 (94) hide show
  1. mcli/app/completion_cmd.py +59 -49
  2. mcli/app/completion_helpers.py +60 -138
  3. mcli/app/logs_cmd.py +46 -13
  4. mcli/app/main.py +17 -14
  5. mcli/app/model_cmd.py +19 -4
  6. mcli/chat/chat.py +3 -2
  7. mcli/lib/search/cached_vectorizer.py +1 -0
  8. mcli/lib/services/data_pipeline.py +12 -5
  9. mcli/lib/services/lsh_client.py +69 -58
  10. mcli/ml/api/app.py +28 -36
  11. mcli/ml/api/middleware.py +8 -16
  12. mcli/ml/api/routers/admin_router.py +3 -1
  13. mcli/ml/api/routers/auth_router.py +32 -56
  14. mcli/ml/api/routers/backtest_router.py +3 -1
  15. mcli/ml/api/routers/data_router.py +3 -1
  16. mcli/ml/api/routers/model_router.py +35 -74
  17. mcli/ml/api/routers/monitoring_router.py +3 -1
  18. mcli/ml/api/routers/portfolio_router.py +3 -1
  19. mcli/ml/api/routers/prediction_router.py +60 -65
  20. mcli/ml/api/routers/trade_router.py +6 -2
  21. mcli/ml/api/routers/websocket_router.py +12 -9
  22. mcli/ml/api/schemas.py +10 -2
  23. mcli/ml/auth/auth_manager.py +49 -114
  24. mcli/ml/auth/models.py +30 -15
  25. mcli/ml/auth/permissions.py +12 -19
  26. mcli/ml/backtesting/backtest_engine.py +134 -108
  27. mcli/ml/backtesting/performance_metrics.py +142 -108
  28. mcli/ml/cache.py +12 -18
  29. mcli/ml/cli/main.py +37 -23
  30. mcli/ml/config/settings.py +29 -12
  31. mcli/ml/dashboard/app.py +122 -130
  32. mcli/ml/dashboard/app_integrated.py +283 -152
  33. mcli/ml/dashboard/app_supabase.py +176 -108
  34. mcli/ml/dashboard/app_training.py +212 -206
  35. mcli/ml/dashboard/cli.py +14 -5
  36. mcli/ml/data_ingestion/api_connectors.py +51 -81
  37. mcli/ml/data_ingestion/data_pipeline.py +127 -125
  38. mcli/ml/data_ingestion/stream_processor.py +72 -80
  39. mcli/ml/database/migrations/env.py +3 -2
  40. mcli/ml/database/models.py +112 -79
  41. mcli/ml/database/session.py +6 -5
  42. mcli/ml/experimentation/ab_testing.py +149 -99
  43. mcli/ml/features/ensemble_features.py +9 -8
  44. mcli/ml/features/political_features.py +6 -5
  45. mcli/ml/features/recommendation_engine.py +15 -14
  46. mcli/ml/features/stock_features.py +7 -6
  47. mcli/ml/features/test_feature_engineering.py +8 -7
  48. mcli/ml/logging.py +10 -15
  49. mcli/ml/mlops/data_versioning.py +57 -64
  50. mcli/ml/mlops/experiment_tracker.py +49 -41
  51. mcli/ml/mlops/model_serving.py +59 -62
  52. mcli/ml/mlops/pipeline_orchestrator.py +203 -149
  53. mcli/ml/models/base_models.py +8 -7
  54. mcli/ml/models/ensemble_models.py +6 -5
  55. mcli/ml/models/recommendation_models.py +7 -6
  56. mcli/ml/models/test_models.py +18 -14
  57. mcli/ml/monitoring/drift_detection.py +95 -74
  58. mcli/ml/monitoring/metrics.py +10 -22
  59. mcli/ml/optimization/portfolio_optimizer.py +172 -132
  60. mcli/ml/predictions/prediction_engine.py +235 -0
  61. mcli/ml/preprocessing/data_cleaners.py +6 -5
  62. mcli/ml/preprocessing/feature_extractors.py +7 -6
  63. mcli/ml/preprocessing/ml_pipeline.py +3 -2
  64. mcli/ml/preprocessing/politician_trading_preprocessor.py +11 -10
  65. mcli/ml/preprocessing/test_preprocessing.py +4 -4
  66. mcli/ml/scripts/populate_sample_data.py +36 -16
  67. mcli/ml/tasks.py +82 -83
  68. mcli/ml/tests/test_integration.py +86 -76
  69. mcli/ml/tests/test_training_dashboard.py +169 -142
  70. mcli/mygroup/test_cmd.py +2 -1
  71. mcli/self/self_cmd.py +38 -18
  72. mcli/self/test_cmd.py +2 -1
  73. mcli/workflow/dashboard/dashboard_cmd.py +13 -6
  74. mcli/workflow/lsh_integration.py +46 -58
  75. mcli/workflow/politician_trading/commands.py +576 -427
  76. mcli/workflow/politician_trading/config.py +7 -7
  77. mcli/workflow/politician_trading/connectivity.py +35 -33
  78. mcli/workflow/politician_trading/data_sources.py +72 -71
  79. mcli/workflow/politician_trading/database.py +18 -16
  80. mcli/workflow/politician_trading/demo.py +4 -3
  81. mcli/workflow/politician_trading/models.py +5 -5
  82. mcli/workflow/politician_trading/monitoring.py +13 -13
  83. mcli/workflow/politician_trading/scrapers.py +332 -224
  84. mcli/workflow/politician_trading/scrapers_california.py +116 -94
  85. mcli/workflow/politician_trading/scrapers_eu.py +70 -71
  86. mcli/workflow/politician_trading/scrapers_uk.py +118 -90
  87. mcli/workflow/politician_trading/scrapers_us_states.py +125 -92
  88. mcli/workflow/politician_trading/workflow.py +98 -71
  89. {mcli_framework-7.1.0.dist-info → mcli_framework-7.1.2.dist-info}/METADATA +2 -2
  90. {mcli_framework-7.1.0.dist-info → mcli_framework-7.1.2.dist-info}/RECORD +94 -93
  91. {mcli_framework-7.1.0.dist-info → mcli_framework-7.1.2.dist-info}/WHEEL +0 -0
  92. {mcli_framework-7.1.0.dist-info → mcli_framework-7.1.2.dist-info}/entry_points.txt +0 -0
  93. {mcli_framework-7.1.0.dist-info → mcli_framework-7.1.2.dist-info}/licenses/LICENSE +0 -0
  94. {mcli_framework-7.1.0.dist-info → mcli_framework-7.1.2.dist-info}/top_level.txt +0 -0
@@ -6,19 +6,19 @@ import asyncio
6
6
  import logging
7
7
  import uuid
8
8
  from datetime import datetime, timedelta
9
- from typing import List, Dict, Any, Optional
9
+ from typing import Any, Dict, List, Optional
10
10
 
11
11
  from .config import WorkflowConfig
12
12
  from .database import PoliticianTradingDB
13
- from .models import DataPullJob, Politician, TradingDisclosure, PoliticianRole
13
+ from .models import DataPullJob, Politician, PoliticianRole, TradingDisclosure
14
14
  from .scrapers import (
15
15
  CongressTradingScraper,
16
- QuiverQuantScraper,
17
16
  EUParliamentScraper,
18
17
  PoliticianMatcher,
19
- run_uk_parliament_workflow,
18
+ QuiverQuantScraper,
20
19
  run_california_workflow,
21
20
  run_eu_member_states_workflow,
21
+ run_uk_parliament_workflow,
22
22
  run_us_states_workflow,
23
23
  )
24
24
 
@@ -79,7 +79,9 @@ class PoliticianTradingWorkflow:
79
79
  # Run EU member states collection
80
80
  eu_states_results = await self._collect_eu_member_states_data()
81
81
  results["jobs"]["eu_member_states"] = eu_states_results
82
- results["summary"]["total_new_disclosures"] += eu_states_results.get("new_disclosures", 0)
82
+ results["summary"]["total_new_disclosures"] += eu_states_results.get(
83
+ "new_disclosures", 0
84
+ )
83
85
  results["summary"]["total_updated_disclosures"] += eu_states_results.get(
84
86
  "updated_disclosures", 0
85
87
  )
@@ -87,7 +89,9 @@ class PoliticianTradingWorkflow:
87
89
  # Run US states collection
88
90
  us_states_results = await self._collect_us_states_data()
89
91
  results["jobs"]["us_states"] = us_states_results
90
- results["summary"]["total_new_disclosures"] += us_states_results.get("new_disclosures", 0)
92
+ results["summary"]["total_new_disclosures"] += us_states_results.get(
93
+ "new_disclosures", 0
94
+ )
91
95
  results["summary"]["total_updated_disclosures"] += us_states_results.get(
92
96
  "updated_disclosures", 0
93
97
  )
@@ -147,7 +151,9 @@ class PoliticianTradingWorkflow:
147
151
 
148
152
  async def _collect_us_congress_data(self) -> Dict[str, Any]:
149
153
  """Collect US Congress trading data"""
150
- job_id = await self.db.create_data_pull_job("us_congress", self.config.to_serializable_dict())
154
+ job_id = await self.db.create_data_pull_job(
155
+ "us_congress", self.config.to_serializable_dict()
156
+ )
151
157
 
152
158
  job_result = {
153
159
  "job_id": job_id,
@@ -198,19 +204,21 @@ class PoliticianTradingWorkflow:
198
204
  logger.warning("Skipping disclosure with empty politician name")
199
205
  job.records_failed += 1
200
206
  continue
201
-
207
+
202
208
  # Filter out obviously invalid politician names
203
209
  if self._is_invalid_politician_name(politician_name):
204
- logger.warning(f"Skipping disclosure with invalid politician name: {politician_name}")
210
+ logger.warning(
211
+ f"Skipping disclosure with invalid politician name: {politician_name}"
212
+ )
205
213
  job.records_failed += 1
206
214
  continue
207
-
215
+
208
216
  politician = matcher.find_politician(politician_name)
209
217
 
210
218
  if not politician:
211
219
  # Create new politician with real name from scraper
212
220
  logger.info(f"Creating new politician for: {politician_name}")
213
-
221
+
214
222
  # Parse real name into first/last components
215
223
  name_parts = politician_name.strip().split()
216
224
  if len(name_parts) >= 2:
@@ -219,7 +227,7 @@ class PoliticianTradingWorkflow:
219
227
  else:
220
228
  first_name = politician_name.strip()
221
229
  last_name = ""
222
-
230
+
223
231
  # Create politician with real name - use generic role for now
224
232
  new_politician = Politician(
225
233
  first_name=first_name,
@@ -283,7 +291,9 @@ class PoliticianTradingWorkflow:
283
291
 
284
292
  async def _collect_eu_parliament_data(self) -> Dict[str, Any]:
285
293
  """Collect EU Parliament trading/financial data"""
286
- job_id = await self.db.create_data_pull_job("eu_parliament", self.config.to_serializable_dict())
294
+ job_id = await self.db.create_data_pull_job(
295
+ "eu_parliament", self.config.to_serializable_dict()
296
+ )
287
297
 
288
298
  job_result = {
289
299
  "job_id": job_id,
@@ -358,7 +368,9 @@ class PoliticianTradingWorkflow:
358
368
 
359
369
  async def _collect_uk_parliament_data(self) -> Dict[str, Any]:
360
370
  """Collect UK Parliament financial interests data"""
361
- job_id = await self.db.create_data_pull_job("uk_parliament", self.config.to_serializable_dict())
371
+ job_id = await self.db.create_data_pull_job(
372
+ "uk_parliament", self.config.to_serializable_dict()
373
+ )
362
374
 
363
375
  job_result = {
364
376
  "job_id": job_id,
@@ -379,9 +391,9 @@ class PoliticianTradingWorkflow:
379
391
  # Collect UK Parliament financial interests
380
392
  logger.info("Starting UK Parliament financial interests collection")
381
393
  uk_disclosures = await run_uk_parliament_workflow(self.config.scraping)
382
-
394
+
383
395
  job.records_found = len(uk_disclosures)
384
-
396
+
385
397
  # Process each disclosure
386
398
  matcher = PoliticianMatcher(self.politicians)
387
399
 
@@ -391,11 +403,13 @@ class PoliticianTradingWorkflow:
391
403
  if not disclosure.politician_id:
392
404
  # Extract real politician name from raw data
393
405
  politician_name = disclosure.raw_data.get("politician_name", "")
394
-
406
+
395
407
  if not politician_name or politician_name.strip() == "":
396
408
  # Fallback to using member ID if no name available
397
409
  if disclosure.raw_data.get("uk_member_id"):
398
- logger.warning(f"Using member ID as fallback for UK disclosure: {disclosure.raw_data.get('uk_member_id')}")
410
+ logger.warning(
411
+ f"Using member ID as fallback for UK disclosure: {disclosure.raw_data.get('uk_member_id')}"
412
+ )
399
413
  uk_politician = Politician(
400
414
  first_name="UK",
401
415
  last_name="MP",
@@ -406,19 +420,23 @@ class PoliticianTradingWorkflow:
406
420
  politician_id = await self.db.upsert_politician(uk_politician)
407
421
  disclosure.politician_id = politician_id
408
422
  else:
409
- logger.warning("Skipping UK disclosure with no politician name or member ID")
423
+ logger.warning(
424
+ "Skipping UK disclosure with no politician name or member ID"
425
+ )
410
426
  job.records_failed += 1
411
427
  continue
412
428
  else:
413
429
  # Filter out obviously invalid politician names
414
430
  if self._is_invalid_politician_name(politician_name):
415
- logger.warning(f"Skipping UK disclosure with invalid politician name: {politician_name}")
431
+ logger.warning(
432
+ f"Skipping UK disclosure with invalid politician name: {politician_name}"
433
+ )
416
434
  job.records_failed += 1
417
435
  continue
418
-
436
+
419
437
  # Try to find existing politician
420
438
  politician = matcher.find_politician(politician_name)
421
-
439
+
422
440
  if not politician:
423
441
  # Create new politician with real name from scraper
424
442
  # Parse real name into first/last components
@@ -429,7 +447,7 @@ class PoliticianTradingWorkflow:
429
447
  else:
430
448
  first_name = politician_name.strip()
431
449
  last_name = ""
432
-
450
+
433
451
  # Create politician with REAL name
434
452
  uk_politician = Politician(
435
453
  first_name=first_name,
@@ -478,7 +496,9 @@ class PoliticianTradingWorkflow:
478
496
 
479
497
  async def _collect_california_data(self) -> Dict[str, Any]:
480
498
  """Collect California NetFile and state disclosure data"""
481
- job_id = await self.db.create_data_pull_job("california", self.config.to_serializable_dict())
499
+ job_id = await self.db.create_data_pull_job(
500
+ "california", self.config.to_serializable_dict()
501
+ )
482
502
 
483
503
  job_result = {
484
504
  "job_id": job_id,
@@ -499,9 +519,9 @@ class PoliticianTradingWorkflow:
499
519
  # Collect California financial disclosures
500
520
  logger.info("Starting California financial disclosures collection")
501
521
  california_disclosures = await run_california_workflow(self.config.scraping)
502
-
522
+
503
523
  job.records_found = len(california_disclosures)
504
-
524
+
505
525
  # Process each disclosure
506
526
  matcher = PoliticianMatcher(self.politicians)
507
527
 
@@ -557,11 +577,13 @@ class PoliticianTradingWorkflow:
557
577
 
558
578
  async def _collect_eu_member_states_data(self) -> Dict[str, Any]:
559
579
  """Collect EU member states financial disclosure data"""
560
- job_id = await self.db.create_data_pull_job("eu_member_states", self.config.to_serializable_dict())
580
+ job_id = await self.db.create_data_pull_job(
581
+ "eu_member_states", self.config.to_serializable_dict()
582
+ )
561
583
 
562
584
  job_result = {
563
585
  "job_id": job_id,
564
- "status": "running",
586
+ "status": "running",
565
587
  "new_disclosures": 0,
566
588
  "updated_disclosures": 0,
567
589
  "errors": [],
@@ -578,9 +600,9 @@ class PoliticianTradingWorkflow:
578
600
  # Collect EU member states financial disclosures
579
601
  logger.info("Starting EU member states financial disclosures collection")
580
602
  eu_states_disclosures = await run_eu_member_states_workflow(self.config.scraping)
581
-
603
+
582
604
  job.records_found = len(eu_states_disclosures)
583
-
605
+
584
606
  # Process each disclosure
585
607
  matcher = PoliticianMatcher(self.politicians)
586
608
 
@@ -591,18 +613,18 @@ class PoliticianTradingWorkflow:
591
613
  # Extract politician details from raw data
592
614
  country = disclosure.raw_data.get("country", "Unknown")
593
615
  source = disclosure.raw_data.get("source", "unknown")
594
-
616
+
595
617
  # Map country to appropriate role
596
618
  role_map = {
597
619
  "Germany": PoliticianRole.GERMAN_BUNDESTAG,
598
620
  "France": PoliticianRole.FRENCH_DEPUTY,
599
621
  "Italy": PoliticianRole.ITALIAN_DEPUTY,
600
622
  "Spain": PoliticianRole.SPANISH_DEPUTY,
601
- "Netherlands": PoliticianRole.DUTCH_MP
623
+ "Netherlands": PoliticianRole.DUTCH_MP,
602
624
  }
603
-
625
+
604
626
  politician_role = role_map.get(country, PoliticianRole.EU_MEP)
605
-
627
+
606
628
  # Create placeholder politician
607
629
  eu_politician = Politician(
608
630
  first_name=country,
@@ -652,7 +674,7 @@ class PoliticianTradingWorkflow:
652
674
 
653
675
  job_result = {
654
676
  "job_id": job_id,
655
- "status": "running",
677
+ "status": "running",
656
678
  "new_disclosures": 0,
657
679
  "updated_disclosures": 0,
658
680
  "errors": [],
@@ -669,9 +691,9 @@ class PoliticianTradingWorkflow:
669
691
  # Collect US states financial disclosures
670
692
  logger.info("Starting US states financial disclosures collection")
671
693
  us_states_disclosures = await run_us_states_workflow(self.config.scraping)
672
-
694
+
673
695
  job.records_found = len(us_states_disclosures)
674
-
696
+
675
697
  # Process each disclosure
676
698
  matcher = PoliticianMatcher(self.politicians)
677
699
 
@@ -682,24 +704,28 @@ class PoliticianTradingWorkflow:
682
704
  # Extract real politician name from raw data
683
705
  politician_name = disclosure.raw_data.get("politician_name", "")
684
706
  if not politician_name or politician_name.strip() == "":
685
- logger.warning("Skipping US states disclosure with empty politician name")
707
+ logger.warning(
708
+ "Skipping US states disclosure with empty politician name"
709
+ )
686
710
  job.records_failed += 1
687
711
  continue
688
-
712
+
689
713
  # Filter out obviously invalid politician names
690
714
  if self._is_invalid_politician_name(politician_name):
691
- logger.warning(f"Skipping US states disclosure with invalid politician name: {politician_name}")
715
+ logger.warning(
716
+ f"Skipping US states disclosure with invalid politician name: {politician_name}"
717
+ )
692
718
  job.records_failed += 1
693
719
  continue
694
-
720
+
695
721
  # Try to find existing politician
696
722
  politician = matcher.find_politician(politician_name)
697
-
723
+
698
724
  if not politician:
699
725
  # Create new politician with real name from scraper
700
726
  state = disclosure.raw_data.get("state", "Unknown")
701
727
  source = disclosure.raw_data.get("source", "unknown")
702
-
728
+
703
729
  # Map state to appropriate role
704
730
  role_map = {
705
731
  "Texas": PoliticianRole.TEXAS_STATE_OFFICIAL,
@@ -708,11 +734,11 @@ class PoliticianTradingWorkflow:
708
734
  "Illinois": PoliticianRole.ILLINOIS_STATE_OFFICIAL,
709
735
  "Pennsylvania": PoliticianRole.PENNSYLVANIA_STATE_OFFICIAL,
710
736
  "Massachusetts": PoliticianRole.MASSACHUSETTS_STATE_OFFICIAL,
711
- "California": PoliticianRole.CALIFORNIA_STATE_OFFICIAL
737
+ "California": PoliticianRole.CALIFORNIA_STATE_OFFICIAL,
712
738
  }
713
-
739
+
714
740
  politician_role = role_map.get(state, PoliticianRole.US_HOUSE_REP)
715
-
741
+
716
742
  # Parse real name into first/last components
717
743
  name_parts = politician_name.strip().split()
718
744
  if len(name_parts) >= 2:
@@ -721,7 +747,7 @@ class PoliticianTradingWorkflow:
721
747
  else:
722
748
  first_name = politician_name.strip()
723
749
  last_name = ""
724
-
750
+
725
751
  # Create politician with REAL name
726
752
  state_politician = Politician(
727
753
  first_name=first_name,
@@ -795,47 +821,48 @@ class PoliticianTradingWorkflow:
795
821
  """Check if a name is obviously not a politician name"""
796
822
  if not name or len(name.strip()) < 2:
797
823
  return True
798
-
824
+
799
825
  # Check for proper name structure first (before converting to uppercase)
800
826
  original_name = name.strip()
801
827
  import re
802
- if not re.search(r'[A-Za-z]', original_name): # Should have at least one letter
828
+
829
+ if not re.search(r"[A-Za-z]", original_name): # Should have at least one letter
803
830
  return True
804
- if re.search(r'^\d+', original_name): # Starting with numbers
831
+ if re.search(r"^\d+", original_name): # Starting with numbers
805
832
  return True
806
-
833
+
807
834
  # Now convert to uppercase for pattern matching
808
835
  name = original_name.upper()
809
-
836
+
810
837
  # Filter out obvious non-names
811
838
  invalid_patterns = [
812
839
  # Asset tickers and financial instruments
813
- r'^-.*CT$', # -ETHEREUMCT, -DOGCT patterns
814
- r'^[A-Z]{2,5}$', # Short all-caps (likely tickers)
815
- r'^\$', # Starting with $
840
+ r"^-.*CT$", # -ETHEREUMCT, -DOGCT patterns
841
+ r"^[A-Z]{2,5}$", # Short all-caps (likely tickers)
842
+ r"^\$", # Starting with $
816
843
  # Municipal and financial terms
817
- r'MUNICIPAL',
818
- r'BOND',
819
- r'TRUST',
820
- r'FUND',
821
- r'CORP',
822
- r'INC\.$',
823
- r'LLC$',
824
- r'LP$',
844
+ r"MUNICIPAL",
845
+ r"BOND",
846
+ r"TRUST",
847
+ r"FUND",
848
+ r"CORP",
849
+ r"INC\.$",
850
+ r"LLC$",
851
+ r"LP$",
825
852
  # Common non-name patterns
826
- r'^UNKNOWN',
827
- r'^TEST',
828
- r'^SAMPLE',
853
+ r"^UNKNOWN",
854
+ r"^TEST",
855
+ r"^SAMPLE",
829
856
  # Crypto/financial asset patterns
830
- r'ETHEREUM',
831
- r'BITCOIN',
832
- r'CRYPTO',
857
+ r"ETHEREUM",
858
+ r"BITCOIN",
859
+ r"CRYPTO",
833
860
  ]
834
-
861
+
835
862
  for pattern in invalid_patterns:
836
863
  if re.search(pattern, name):
837
864
  return True
838
-
865
+
839
866
  return False
840
867
 
841
868
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mcli-framework
3
- Version: 7.1.0
3
+ Version: 7.1.2
4
4
  Summary: 🚀 High-performance CLI framework with Rust extensions, AI chat, and stunning visuals
5
5
  Author-email: Luis Fernandez de la Vara <luis@lefv.io>
6
6
  Maintainer-email: Luis Fernandez de la Vara <luis@lefv.io>
@@ -63,7 +63,7 @@ Requires-Dist: uvloop>=0.19.0
63
63
  Requires-Dist: aiosqlite>=0.20.0
64
64
  Requires-Dist: redis>=5.0.0
65
65
  Requires-Dist: aiohttp-sse-client>=0.2.1
66
- Requires-Dist: asyncio-mqtt>=0.13.0
66
+ Requires-Dist: aiomqtt>=2.0.0
67
67
  Requires-Dist: opencv-python>=4.11.0.86
68
68
  Requires-Dist: pillow>=11.2.1
69
69
  Requires-Dist: numpy<2.0.0,>=1.24.0