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,350 @@
1
+ """
2
+ UK Parliament API scraper for financial interests register data
3
+
4
+ This module implements scrapers for the UK Parliament's Register of Interests API
5
+ to collect MP financial disclosure data.
6
+ """
7
+
8
+ import asyncio
9
+ import logging
10
+ from datetime import datetime
11
+ from typing import List, Dict, Any, Optional
12
+ import aiohttp
13
+
14
+ from .scrapers import BaseScraper
15
+ from .models import TradingDisclosure, Politician, PoliticianRole, TransactionType
16
+
17
+ logger = logging.getLogger(__name__)
18
+
19
+
20
+ class UKParliamentScraper(BaseScraper):
21
+ """Scraper for UK Parliament Register of Interests API"""
22
+
23
+ def __init__(self, config):
24
+ super().__init__(config)
25
+ self.base_url = "https://interests-api.parliament.uk/api/v1"
26
+ self.session: Optional[aiohttp.ClientSession] = None
27
+
28
+ async def __aenter__(self):
29
+ """Async context manager entry"""
30
+ self.session = aiohttp.ClientSession(
31
+ timeout=aiohttp.ClientTimeout(total=self.config.timeout),
32
+ headers={'User-Agent': self.config.user_agent}
33
+ )
34
+ return self
35
+
36
+ async def __aexit__(self, exc_type, exc_val, exc_tb):
37
+ """Async context manager exit"""
38
+ if self.session:
39
+ await self.session.close()
40
+
41
+ async def fetch_members_interests(self) -> List[TradingDisclosure]:
42
+ """Fetch all MP financial interests from the API"""
43
+ logger.info("Starting UK Parliament financial interests collection")
44
+
45
+ if not self.session:
46
+ raise RuntimeError("Session not initialized. Use async context manager.")
47
+
48
+ disclosures = []
49
+
50
+ try:
51
+ # First, get all interest categories to understand what types of interests exist
52
+ categories = await self._fetch_categories()
53
+ logger.info(f"Found {len(categories)} interest categories")
54
+
55
+ # Get all interests for financial/investment categories
56
+ financial_categories = self._filter_financial_categories(categories)
57
+
58
+ for category in financial_categories:
59
+ category_disclosures = await self._fetch_interests_by_category(category)
60
+ disclosures.extend(category_disclosures)
61
+
62
+ # Rate limiting
63
+ await asyncio.sleep(self.config.request_delay)
64
+
65
+ logger.info(f"Collected {len(disclosures)} UK Parliament financial interests")
66
+ return disclosures
67
+
68
+ except Exception as e:
69
+ logger.error(f"Failed to fetch UK Parliament interests: {e}")
70
+ raise
71
+
72
+ async def _fetch_categories(self) -> List[Dict[str, Any]]:
73
+ """Fetch all interest categories from the API"""
74
+ url = f"{self.base_url}/Categories"
75
+ params = {"Take": 100} # Get up to 100 categories
76
+
77
+ async with self.session.get(url, params=params) as response:
78
+ response.raise_for_status()
79
+ data = await response.json()
80
+ return data.get("items", [])
81
+
82
+ def _filter_financial_categories(self, categories: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
83
+ """Filter categories to include only financial/investment related ones"""
84
+ financial_keywords = [
85
+ "shareholding", "share", "investment", "financial", "company",
86
+ "directorship", "employment", "remuneration", "sponsorship",
87
+ "gift", "benefit", "land", "property"
88
+ ]
89
+
90
+ financial_categories = []
91
+ for category in categories:
92
+ category_name = category.get("name", "").lower()
93
+ if any(keyword in category_name for keyword in financial_keywords):
94
+ financial_categories.append(category)
95
+ logger.debug(f"Including financial category: {category.get('name')}")
96
+
97
+ return financial_categories
98
+
99
+ async def _fetch_interests_by_category(self, category: Dict[str, Any]) -> List[TradingDisclosure]:
100
+ """Fetch interests for a specific category"""
101
+ category_id = category.get("id")
102
+ category_name = category.get("name")
103
+
104
+ logger.debug(f"Fetching interests for category: {category_name} (ID: {category_id})")
105
+
106
+ disclosures = []
107
+ skip = 0
108
+ take = 50
109
+
110
+ while True:
111
+ url = f"{self.base_url}/Interests"
112
+ params = {
113
+ "categoryId": category_id,
114
+ "Skip": skip,
115
+ "Take": take
116
+ }
117
+
118
+ try:
119
+ async with self.session.get(url, params=params) as response:
120
+ response.raise_for_status()
121
+ data = await response.json()
122
+
123
+ interests = data.get("items", [])
124
+ if not interests:
125
+ break
126
+
127
+ for interest in interests:
128
+ disclosure = await self._parse_uk_interest(interest, category_name)
129
+ if disclosure:
130
+ disclosures.append(disclosure)
131
+
132
+ skip += take
133
+
134
+ # If we got fewer results than requested, we're done
135
+ if len(interests) < take:
136
+ break
137
+
138
+ except Exception as e:
139
+ logger.error(f"Failed to fetch interests for category {category_name}: {e}")
140
+ break
141
+
142
+ logger.debug(f"Found {len(disclosures)} interests in category: {category_name}")
143
+ return disclosures
144
+
145
+ async def _parse_uk_interest(self, interest: Dict[str, Any], category_name: str) -> Optional[TradingDisclosure]:
146
+ """Parse a UK Parliament interest into a TradingDisclosure"""
147
+ try:
148
+ # Extract member information from the new API structure
149
+ member_data = interest.get("member")
150
+ if not member_data:
151
+ return None
152
+
153
+ member_id = member_data.get("id")
154
+ politician_name = member_data.get("nameDisplayAs", "")
155
+
156
+ # Get interest details
157
+ interest_id = interest.get("id")
158
+ description = interest.get("summary", "")
159
+ registered_date = interest.get("registrationDate")
160
+
161
+ # Parse dates
162
+ transaction_date = self._parse_date(registered_date) if registered_date else datetime.now()
163
+ disclosure_date = transaction_date # UK system doesn't separate these
164
+
165
+ # Determine transaction type from description
166
+ transaction_type = self._infer_transaction_type(description, category_name)
167
+
168
+ # Extract asset information from fields and description
169
+ asset_name, asset_ticker = self._extract_asset_info_from_fields(interest, description, category_name)
170
+
171
+ # Extract amount information (if available)
172
+ amount_min, amount_max, amount_exact = self._extract_amount_info(description)
173
+
174
+ disclosure = TradingDisclosure(
175
+ id=f"uk_parliament_{interest_id}",
176
+ politician_id="", # Will be filled during politician matching
177
+ transaction_date=transaction_date,
178
+ disclosure_date=disclosure_date,
179
+ transaction_type=transaction_type,
180
+ asset_name=asset_name,
181
+ asset_ticker=asset_ticker,
182
+ asset_type="shareholding", # Most UK disclosures are shareholdings
183
+ amount_range_min=amount_min,
184
+ amount_range_max=amount_max,
185
+ amount_exact=amount_exact,
186
+ source_url=f"https://www.parliament.uk/mps-lords-and-offices/standards-and-financial-interests/",
187
+ raw_data={
188
+ "uk_interest_id": interest_id,
189
+ "uk_member_id": member_id,
190
+ "description": description,
191
+ "category_name": category_name,
192
+ "registered_date": registered_date,
193
+ "source": "uk_parliament_api",
194
+ "politician_name": politician_name
195
+ }
196
+ )
197
+
198
+ return disclosure
199
+
200
+ except Exception as e:
201
+ logger.error(f"Failed to parse UK interest: {e}")
202
+ return None
203
+
204
+ async def _fetch_mp_name(self, member_id: int) -> str:
205
+ """Fetch MP name from the Parliament API using member ID"""
206
+ if not self.session:
207
+ return ""
208
+
209
+ try:
210
+ # Try the Members endpoint to get MP details
211
+ member_url = f"{self.base_url}/Members/{member_id}"
212
+
213
+ async with self.session.get(member_url) as response:
214
+ if response.status == 200:
215
+ data = await response.json()
216
+
217
+ # Extract name from the response
218
+ name = data.get("name", "")
219
+ if not name:
220
+ # Try alternative field names
221
+ name = data.get("displayAs", "")
222
+ if not name:
223
+ # Combine first and last name if available
224
+ first_name = data.get("nameGiven", "")
225
+ last_name = data.get("nameFull", "") or data.get("nameFamily", "")
226
+ if first_name and last_name:
227
+ name = f"{first_name} {last_name}"
228
+
229
+ if name:
230
+ logger.debug(f"Found MP name for ID {member_id}: {name}")
231
+ return name.strip()
232
+
233
+ else:
234
+ logger.debug(f"Could not fetch MP details for ID {member_id}: HTTP {response.status}")
235
+
236
+ except Exception as e:
237
+ logger.debug(f"Failed to fetch MP name for ID {member_id}: {e}")
238
+
239
+ return ""
240
+
241
+ def _parse_date(self, date_str: str) -> datetime:
242
+ """Parse UK Parliament API date format"""
243
+ try:
244
+ # UK Parliament API uses ISO format
245
+ return datetime.fromisoformat(date_str.replace("Z", "+00:00"))
246
+ except Exception:
247
+ return datetime.now()
248
+
249
+ def _infer_transaction_type(self, description: str, category_name: str) -> TransactionType:
250
+ """Infer transaction type from description and category"""
251
+ description_lower = description.lower()
252
+ category_lower = category_name.lower()
253
+
254
+ # UK Parliament disclosures are mostly about holdings, not transactions
255
+ # But we can infer some information
256
+ if any(word in description_lower for word in ["sold", "disposed", "divested"]):
257
+ return TransactionType.SALE
258
+ elif any(word in description_lower for word in ["acquired", "purchased", "bought"]):
259
+ return TransactionType.PURCHASE
260
+ elif "shareholding" in category_lower:
261
+ return TransactionType.PURCHASE # Assume shareholding disclosure is a purchase
262
+ else:
263
+ return TransactionType.PURCHASE # Default assumption
264
+
265
+ def _extract_asset_info_from_fields(self, interest: Dict[str, Any], description: str, category_name: str) -> tuple[str, Optional[str]]:
266
+ """Extract asset name and ticker from interest fields"""
267
+ # Look for OrganisationName in fields
268
+ fields = interest.get("fields", [])
269
+ organization_name = None
270
+
271
+ for field in fields:
272
+ if field.get("name") == "OrganisationName":
273
+ organization_name = field.get("value")
274
+ break
275
+
276
+ # Use organization name if available, otherwise fall back to description
277
+ if organization_name:
278
+ return organization_name, None
279
+ else:
280
+ return self._extract_asset_info(description, category_name)
281
+
282
+ def _extract_asset_info(self, description: str, category_name: str) -> tuple[str, Optional[str]]:
283
+ """Extract asset name and ticker from description"""
284
+ # UK descriptions often contain company names
285
+ # This is a simplified extraction - could be enhanced with NLP
286
+
287
+ if "shareholding" in category_name.lower():
288
+ # Try to extract company name from shareholding descriptions
289
+ # Format often like: "Shareholding in [Company Name] Ltd"
290
+ if " in " in description:
291
+ parts = description.split(" in ", 1)
292
+ if len(parts) > 1:
293
+ asset_name = parts[1].strip().rstrip(".")
294
+ return asset_name, None
295
+
296
+ # Fallback: use description as asset name
297
+ return description[:100], None # Truncate to reasonable length
298
+
299
+ def _extract_amount_info(self, description: str) -> tuple[Optional[float], Optional[float], Optional[float]]:
300
+ """Extract amount information from description"""
301
+ # UK Parliament disclosures often don't include specific amounts
302
+ # They use threshold categories (£70,000+, etc.)
303
+
304
+ description_lower = description.lower()
305
+
306
+ # Look for UK threshold amounts
307
+ if "£70,000" in description_lower or "70000" in description_lower:
308
+ return 70000.0, None, None
309
+ elif "£" in description_lower:
310
+ # Try to extract specific amounts
311
+ import re
312
+ amount_pattern = r'£([\d,]+)'
313
+ matches = re.findall(amount_pattern, description)
314
+ if matches:
315
+ try:
316
+ amount = float(matches[0].replace(',', ''))
317
+ return amount, None, amount
318
+ except ValueError:
319
+ pass
320
+
321
+ return None, None, None
322
+
323
+ async def get_politicians(self) -> List[Politician]:
324
+ """Fetch current MPs from the Members API"""
325
+ logger.info("Fetching current UK MPs")
326
+
327
+ # For now, return empty list - would need Members API integration
328
+ # This would require calling https://members-api.parliament.uk/
329
+ return []
330
+
331
+
332
+ async def run_uk_parliament_collection(config) -> List[TradingDisclosure]:
333
+ """Main function to run UK Parliament data collection"""
334
+ async with UKParliamentScraper(config) as scraper:
335
+ return await scraper.fetch_members_interests()
336
+
337
+
338
+ # Example usage for testing
339
+ if __name__ == "__main__":
340
+ from .config import WorkflowConfig
341
+
342
+ async def main():
343
+ config = WorkflowConfig.default()
344
+ disclosures = await run_uk_parliament_collection(config.scraping)
345
+ print(f"Collected {len(disclosures)} UK Parliament financial interests")
346
+
347
+ for disclosure in disclosures[:3]: # Show first 3
348
+ print(f"- {disclosure.asset_name} by {disclosure.raw_data.get('politician_name', 'Unknown')}")
349
+
350
+ asyncio.run(main())