sovai 0.2.45__tar.gz → 0.2.46__tar.gz

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.
Files changed (101) hide show
  1. {sovai-0.2.45 → sovai-0.2.46}/PKG-INFO +1 -1
  2. sovai-0.2.46/data/tickers.parq +0 -0
  3. {sovai-0.2.45 → sovai-0.2.46}/pyproject.toml +1 -1
  4. {sovai-0.2.45 → sovai-0.2.46}/sovai/get_data.py +101 -4
  5. {sovai-0.2.45 → sovai-0.2.46}/sovai/utils/client_side_s3_part_high.py +191 -70
  6. sovai-0.2.45/data/tickers.parq +0 -0
  7. {sovai-0.2.45 → sovai-0.2.46}/LICENSE +0 -0
  8. {sovai-0.2.45 → sovai-0.2.46}/README.md +0 -0
  9. {sovai-0.2.45 → sovai-0.2.46}/data/codes.parq +0 -0
  10. {sovai-0.2.45 → sovai-0.2.46}/sovai/.DS_Store +0 -0
  11. {sovai-0.2.45 → sovai-0.2.46}/sovai/__init__.py +0 -0
  12. {sovai-0.2.45 → sovai-0.2.46}/sovai/api_config.py +0 -0
  13. {sovai-0.2.45 → sovai-0.2.46}/sovai/assets/dropdown.css +0 -0
  14. {sovai-0.2.45 → sovai-0.2.46}/sovai/assets/features_mapping.parq +0 -0
  15. {sovai-0.2.45 → sovai-0.2.46}/sovai/basic_auth.py +0 -0
  16. {sovai-0.2.45 → sovai-0.2.46}/sovai/computations/__init__.py +0 -0
  17. {sovai-0.2.45 → sovai-0.2.46}/sovai/computations/functions.py +0 -0
  18. {sovai-0.2.45 → sovai-0.2.46}/sovai/errors/__init__.py +0 -0
  19. {sovai-0.2.45 → sovai-0.2.46}/sovai/errors/sovai_errors.py +0 -0
  20. {sovai-0.2.45 → sovai-0.2.46}/sovai/extensions/__init__.py +0 -0
  21. {sovai-0.2.45 → sovai-0.2.46}/sovai/extensions/anomalies.py +0 -0
  22. {sovai-0.2.45 → sovai-0.2.46}/sovai/extensions/ask_df_llm.py +0 -0
  23. {sovai-0.2.45 → sovai-0.2.46}/sovai/extensions/change_point_generator.py +0 -0
  24. {sovai-0.2.45 → sovai-0.2.46}/sovai/extensions/clustering.py +0 -0
  25. {sovai-0.2.45 → sovai-0.2.46}/sovai/extensions/core_kshape.py +0 -0
  26. {sovai-0.2.45 → sovai-0.2.46}/sovai/extensions/cum_sum_torch.py +0 -0
  27. {sovai-0.2.45 → sovai-0.2.46}/sovai/extensions/cusum.py +0 -0
  28. {sovai-0.2.45 → sovai-0.2.46}/sovai/extensions/dimensionality_reduction.py +0 -0
  29. {sovai-0.2.45 → sovai-0.2.46}/sovai/extensions/feature_extraction.py +0 -0
  30. {sovai-0.2.45 → sovai-0.2.46}/sovai/extensions/feature_importance.py +0 -0
  31. {sovai-0.2.45 → sovai-0.2.46}/sovai/extensions/feature_neutralizer.py +0 -0
  32. {sovai-0.2.45 → sovai-0.2.46}/sovai/extensions/filter_df.py +0 -0
  33. {sovai-0.2.45 → sovai-0.2.46}/sovai/extensions/fractional_differencing.py +0 -0
  34. {sovai-0.2.45 → sovai-0.2.46}/sovai/extensions/nowcasting.py +0 -0
  35. {sovai-0.2.45 → sovai-0.2.46}/sovai/extensions/pairwise.py +0 -0
  36. {sovai-0.2.45 → sovai-0.2.46}/sovai/extensions/pandas_extensions.py +0 -0
  37. {sovai-0.2.45 → sovai-0.2.46}/sovai/extensions/parallel_functions.py +0 -0
  38. {sovai-0.2.45 → sovai-0.2.46}/sovai/extensions/pfa_feature_selector.py +0 -0
  39. {sovai-0.2.45 → sovai-0.2.46}/sovai/extensions/regime_change.py +0 -0
  40. {sovai-0.2.45 → sovai-0.2.46}/sovai/extensions/regime_change_pca.py +0 -0
  41. {sovai-0.2.45 → sovai-0.2.46}/sovai/extensions/shapley_global_importance.py +0 -0
  42. {sovai-0.2.45 → sovai-0.2.46}/sovai/extensions/shapley_importance.py +0 -0
  43. {sovai-0.2.45 → sovai-0.2.46}/sovai/extensions/signal_evaluation.py +0 -0
  44. {sovai-0.2.45 → sovai-0.2.46}/sovai/extensions/technical_indicators.py +0 -0
  45. {sovai-0.2.45 → sovai-0.2.46}/sovai/extensions/time_decomposition.py +0 -0
  46. {sovai-0.2.45 → sovai-0.2.46}/sovai/extensions/weight_optimization.py +0 -0
  47. {sovai-0.2.45 → sovai-0.2.46}/sovai/get_compute.py +0 -0
  48. {sovai-0.2.45 → sovai-0.2.46}/sovai/get_data copy.py +0 -0
  49. {sovai-0.2.45 → sovai-0.2.46}/sovai/get_plots.py +0 -0
  50. {sovai-0.2.45 → sovai-0.2.46}/sovai/get_reports.py +0 -0
  51. {sovai-0.2.45 → sovai-0.2.46}/sovai/get_tools.py +0 -0
  52. {sovai-0.2.45 → sovai-0.2.46}/sovai/plots/__init__.py +0 -0
  53. {sovai-0.2.45 → sovai-0.2.46}/sovai/plots/accounting/__init__.py +0 -0
  54. {sovai-0.2.45 → sovai-0.2.46}/sovai/plots/accounting/accounting_plots.py +0 -0
  55. {sovai-0.2.45 → sovai-0.2.46}/sovai/plots/allocation/__init__.py +0 -0
  56. {sovai-0.2.45 → sovai-0.2.46}/sovai/plots/allocation/allocation_plots.py +0 -0
  57. {sovai-0.2.45 → sovai-0.2.46}/sovai/plots/bankruptcy/__init__.py +0 -0
  58. {sovai-0.2.45 → sovai-0.2.46}/sovai/plots/bankruptcy/bankruptcy_plots.py +0 -0
  59. {sovai-0.2.45 → sovai-0.2.46}/sovai/plots/breakout/__init__.py +0 -0
  60. {sovai-0.2.45 → sovai-0.2.46}/sovai/plots/breakout/breakout_plots.py +0 -0
  61. {sovai-0.2.45 → sovai-0.2.46}/sovai/plots/corp_risk/__init__.py +0 -0
  62. {sovai-0.2.45 → sovai-0.2.46}/sovai/plots/corp_risk/corp_risk_plots.py +0 -0
  63. {sovai-0.2.45 → sovai-0.2.46}/sovai/plots/earnings_surprise/__init__.py +0 -0
  64. {sovai-0.2.45 → sovai-0.2.46}/sovai/plots/earnings_surprise/earnings_surprise_plots.py +0 -0
  65. {sovai-0.2.45 → sovai-0.2.46}/sovai/plots/insider/__init__.py +0 -0
  66. {sovai-0.2.45 → sovai-0.2.46}/sovai/plots/insider/insider_plots.py +0 -0
  67. {sovai-0.2.45 → sovai-0.2.46}/sovai/plots/institutional/__init__.py +0 -0
  68. {sovai-0.2.45 → sovai-0.2.46}/sovai/plots/institutional/institutional_plots.py +0 -0
  69. {sovai-0.2.45 → sovai-0.2.46}/sovai/plots/news/__init__.py +0 -0
  70. {sovai-0.2.45 → sovai-0.2.46}/sovai/plots/news/news_plots.py +0 -0
  71. {sovai-0.2.45 → sovai-0.2.46}/sovai/plots/ratios/__init__.py +0 -0
  72. {sovai-0.2.45 → sovai-0.2.46}/sovai/plots/ratios/ratios_plots.py +0 -0
  73. {sovai-0.2.45 → sovai-0.2.46}/sovai/reports/accounting/__init__.py +0 -0
  74. {sovai-0.2.45 → sovai-0.2.46}/sovai/reports/accounting/accounting_balance_sheet.py +0 -0
  75. {sovai-0.2.45 → sovai-0.2.46}/sovai/reports/bankruptcy/__init__.py +0 -0
  76. {sovai-0.2.45 → sovai-0.2.46}/sovai/reports/bankruptcy/bankruptcy_monthly_top.py +0 -0
  77. {sovai-0.2.45 → sovai-0.2.46}/sovai/reports/general/__init__.py +0 -0
  78. {sovai-0.2.45 → sovai-0.2.46}/sovai/reports/general/general_plots.py +0 -0
  79. {sovai-0.2.45 → sovai-0.2.46}/sovai/reports/news/__inti__.py +0 -0
  80. {sovai-0.2.45 → sovai-0.2.46}/sovai/reports/news/news_econometric_analysis.py +0 -0
  81. {sovai-0.2.45 → sovai-0.2.46}/sovai/studies/nowcasting.py +0 -0
  82. {sovai-0.2.45 → sovai-0.2.46}/sovai/token_auth.py +0 -0
  83. {sovai-0.2.45 → sovai-0.2.46}/sovai/tools/authentication/__init__.py +0 -0
  84. {sovai-0.2.45 → sovai-0.2.46}/sovai/tools/authentication/authentication.py +0 -0
  85. {sovai-0.2.45 → sovai-0.2.46}/sovai/tools/sec/__init__.py +0 -0
  86. {sovai-0.2.45 → sovai-0.2.46}/sovai/tools/sec/graphs.py +0 -0
  87. {sovai-0.2.45 → sovai-0.2.46}/sovai/tools/sec/llm_code_generator.py +0 -0
  88. {sovai-0.2.45 → sovai-0.2.46}/sovai/tools/sec/sec_10_k_8_k_filings.py +0 -0
  89. {sovai-0.2.45 → sovai-0.2.46}/sovai/tools/sec/sec_edgar_search.py +0 -0
  90. {sovai-0.2.45 → sovai-0.2.46}/sovai/utils/__init__.py +0 -0
  91. {sovai-0.2.45 → sovai-0.2.46}/sovai/utils/client_side.py +0 -0
  92. {sovai-0.2.45 → sovai-0.2.46}/sovai/utils/client_side_s3.py +0 -0
  93. {sovai-0.2.45 → sovai-0.2.46}/sovai/utils/converter.py +0 -0
  94. {sovai-0.2.45 → sovai-0.2.46}/sovai/utils/datetime_formats.py +0 -0
  95. {sovai-0.2.45 → sovai-0.2.46}/sovai/utils/file_management.py +0 -0
  96. {sovai-0.2.45 → sovai-0.2.46}/sovai/utils/get_tickers.py +0 -0
  97. {sovai-0.2.45 → sovai-0.2.46}/sovai/utils/helpers.py +0 -0
  98. {sovai-0.2.45 → sovai-0.2.46}/sovai/utils/pandas_extensions copy.py +0 -0
  99. {sovai-0.2.45 → sovai-0.2.46}/sovai/utils/plot.py +0 -0
  100. {sovai-0.2.45 → sovai-0.2.46}/sovai/utils/port_manager.py +0 -0
  101. {sovai-0.2.45 → sovai-0.2.46}/sovai/utils/stream.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: sovai
3
- Version: 0.2.45
3
+ Version: 0.2.46
4
4
  Summary: python for asset management
5
5
  License: Proprietary License
6
6
  Author: Derek Snow
Binary file
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "sovai"
3
- version = "0.2.45"
3
+ version = "0.2.46"
4
4
  description = "python for asset management"
5
5
  authors = ["Derek Snow <d.snow@outlook.com>"]
6
6
  license = "Proprietary License"
@@ -308,7 +308,9 @@ ENDPOINT_TO_TICKER = {
308
308
  "/news/pronouns": "",
309
309
  "/news/word_count": "",
310
310
  "/news/sentiment_score": "", # was None before?
311
+
311
312
  "/insider/trading": "",
313
+
312
314
  "/wikipedia/views": "",
313
315
  "/accounting/weekly": "",
314
316
  "/visas/h1b": "",
@@ -346,6 +348,8 @@ ENDPOINT_TO_TICKER = {
346
348
 
347
349
  "/clinical/trials": "",
348
350
 
351
+
352
+
349
353
  "/spending/awards": "",
350
354
  "/spending/compensation": "",
351
355
  "/spending/competition": "",
@@ -399,6 +403,8 @@ CLIENT_SIDE_ENDPOINTS_S3_PART_HIGH = {
399
403
 
400
404
  "accounting/weekly",
401
405
 
406
+ "insider/trading",
407
+
402
408
  "ratios/normal",
403
409
  "ratios/relative",
404
410
 
@@ -414,15 +420,42 @@ CLIENT_SIDE_ENDPOINTS_S3_PART_HIGH = {
414
420
 
415
421
  "breakout",
416
422
 
417
- "corprisk/risks",
423
+ "corprisk/risks",
424
+ "corprisk/accounting",
425
+ "corprisk/events",
426
+ "corprisk/misstatements",
418
427
 
419
428
  "visas/h1b",
420
429
 
421
- "wikipedia/views"
430
+ "wikipedia/views",
431
+
432
+ "short/volume",
433
+ "short/maker",
434
+ "short/over_shorted",
435
+
436
+ "institutional/trading"
422
437
 
423
438
  }
424
439
 
425
440
 
441
+
442
+ ENDPOINT_ALIASES = {
443
+ "clinical/predict": {
444
+ "target_endpoint": "clinical/trials",
445
+ "columns": [
446
+ 'ticker', 'date', 'source', 'subsidiary', 'sponsor',
447
+ 'trial_id', 'official_title', 'success_prediction',
448
+ 'economic_effect', 'duration_prediction', 'success_composite'
449
+ ]
450
+ }
451
+ # --- Add other aliases here in the future ---
452
+ # "some/alias": {
453
+ # "target_endpoint": "real/endpoint",
454
+ # "columns": ["col1", "col2", "date", "ticker"] # Ensure essential columns are included
455
+ # }
456
+ }
457
+
458
+
426
459
  # Define handlers for different endpoint types
427
460
  ENDPOINT_HANDLERS = [
428
461
  (CLIENT_SIDE_ENDPOINTS_GCS, client_side_frame, "Grabbing GCS client side"),
@@ -1095,6 +1128,38 @@ def data(
1095
1128
  params = params or {}
1096
1129
  params = map_synonyms(params)
1097
1130
 
1131
+ # --- MODIFIED SECTION: Alias Handling ---
1132
+ original_endpoint_request = endpoint # Keep track for logging
1133
+ forced_columns = None # Will store columns if an alias forces them
1134
+ normalized_req_endpoint = normalize_endpoint(endpoint)
1135
+
1136
+ if normalized_req_endpoint in ENDPOINT_ALIASES:
1137
+ alias_config = ENDPOINT_ALIASES[normalized_req_endpoint]
1138
+ target_endpoint = alias_config['target_endpoint']
1139
+ forced_columns_list = alias_config['columns']
1140
+ verbose_mode.log(f"Endpoint alias detected: '{original_endpoint_request}' maps to '{target_endpoint}' with predefined columns.")
1141
+
1142
+ endpoint = target_endpoint # *Overwrite* endpoint for the rest of the function
1143
+ columns = forced_columns_list # *Overwrite* columns parameter
1144
+
1145
+ # Ensure the 'columns' value in the main 'params' dict is also updated
1146
+ # This is crucial for cache key generation and potentially for client-side handlers
1147
+ # Convert list to string if needed for _prepare_params consistency
1148
+ params['columns'] = ",".join(forced_columns_list)
1149
+
1150
+ else:
1151
+ # If not an alias, handle the columns parameter normally
1152
+ # Convert list to string if provided as list initially
1153
+ if isinstance(columns, list):
1154
+ columns_str = ",".join(columns)
1155
+ else:
1156
+ columns_str = columns # It's already a string or None
1157
+ # Update params dict if columns were passed directly to the function
1158
+ if columns is not None:
1159
+ params['columns'] = columns_str
1160
+ # --- END MODIFIED SECTION ---
1161
+
1162
+
1098
1163
  # Force full_history if date range is specified
1099
1164
  if start_date is not None or end_date is not None:
1100
1165
  full_history = True
@@ -1102,6 +1167,14 @@ def data(
1102
1167
 
1103
1168
  # Update params with prepared values
1104
1169
  try:
1170
+
1171
+ # Make sure to use the potentially updated 'columns' value from params dict
1172
+ # if it exists, otherwise use the function argument (which might be None or overridden)
1173
+ current_columns = params.get("columns", columns)
1174
+ # Convert back to string if it was forced as a list earlier
1175
+ if isinstance(current_columns, list):
1176
+ current_columns = ",".join(current_columns)
1177
+
1105
1178
  params.update(
1106
1179
  _prepare_params(
1107
1180
  tickers=params.get("tickers", tickers),
@@ -1150,6 +1223,10 @@ def data(
1150
1223
 
1151
1224
  # Try client-side handlers for specific endpoints
1152
1225
  handler_result = None
1226
+
1227
+ # Get the columns value to pass to handlers (use the string version from params)
1228
+ handler_columns_arg = params.get('columns')
1229
+
1153
1230
  for endpoint_set, handler_func, message in ENDPOINT_HANDLERS:
1154
1231
  if (
1155
1232
  normalized_endpoint in endpoint_set and
@@ -1160,7 +1237,7 @@ def data(
1160
1237
  verbose_mode.log(f"Calling client-side handler with tickers={tickers}, start_date={start_date}, end_date={end_date}")
1161
1238
  try:
1162
1239
  handler_result = handler_func(
1163
- normalized_endpoint, tickers, columns, start_date, end_date
1240
+ normalized_endpoint, tickers, handler_columns_arg, start_date, end_date
1164
1241
  )
1165
1242
  _query_cache[cache_key] = handler_result
1166
1243
  result_data = handler_result
@@ -1241,11 +1318,15 @@ def data(
1241
1318
  logger.error(error_msg)
1242
1319
  raise Exception(error_msg)
1243
1320
 
1321
+ filter_columns_arg = columns
1322
+ if isinstance(columns, str): # filter_data might prefer list
1323
+ filter_columns_arg = [c.strip() for c in columns.split(',')] if columns else None
1324
+
1244
1325
  # Apply filters
1245
1326
  verbose_mode.log("Applying filters to data")
1246
1327
  data_result = filter_data(
1247
1328
  data_result,
1248
- columns=columns,
1329
+ columns=filter_columns_arg,
1249
1330
  start_date=start_date,
1250
1331
  end_date=end_date,
1251
1332
  use_polars=use_polars
@@ -1331,6 +1412,22 @@ def data(
1331
1412
 
1332
1413
  result_data = process_dataframe(result_data)
1333
1414
 
1415
+
1416
+ # --- Final Check: Ensure alias columns are applied if data wasn't filtered before ---
1417
+ # This is a safety net in case a data path (e.g., direct API return without 'all tickers')
1418
+ # didn't explicitly filter columns based on the overridden parameter.
1419
+ if forced_columns is not None and result_data is not None and isinstance(result_data, (pd.DataFrame, pl.DataFrame)):
1420
+ # Check if columns need filtering (some might be missing if already filtered)
1421
+ current_cols = list(result_data.columns)
1422
+ cols_to_keep = [col for col in forced_columns_list if col in current_cols]
1423
+ if set(cols_to_keep) != set(current_cols):
1424
+ verbose_mode.log(f"Applying final column filter for alias: {cols_to_keep}")
1425
+ if isinstance(result_data, pd.DataFrame):
1426
+ result_data = result_data[cols_to_keep]
1427
+ elif isinstance(result_data, pl.DataFrame):
1428
+ result_data = result_data.select(cols_to_keep)
1429
+
1430
+
1334
1431
  return result_data
1335
1432
 
1336
1433
 
@@ -32,16 +32,23 @@ from sovai.tools.authentication import authentication
32
32
  # Logging Configuration
33
33
  # =========================================================================
34
34
 
35
+ # Near top of load_frame_s3_partitioned_high.py
35
36
  logger = logging.getLogger(__name__)
36
37
  if not logger.handlers:
37
- logging.basicConfig(
38
- level=logging.INFO,
39
- format='%(asctime)s - %(levelname)s - %(message)s',
40
- handlers=[
41
- logging.FileHandler("data_operations.log"),
42
- logging.StreamHandler()
43
- ]
44
- )
38
+ log_formatter = logging.Formatter('%(asctime)s - %(levelname)s - [%(funcName)s] - %(message)s') # Added function name
39
+
40
+ # File Handler
41
+ file_handler = logging.FileHandler("data_operations.log")
42
+ file_handler.setFormatter(log_formatter)
43
+
44
+ # Console Handler (add this)
45
+ console_handler = logging.StreamHandler()
46
+ console_handler.setFormatter(log_formatter)
47
+
48
+ logger.addHandler(file_handler)
49
+ logger.addHandler(console_handler) # Add console output
50
+ logger.setLevel(logging.INFO) # Set level (INFO or DEBUG)
51
+ # To see DEBUG level messages, set logger.setLevel(logging.DEBUG)
45
52
 
46
53
  # =========================================================================
47
54
  # Filesystem Access
@@ -295,10 +302,13 @@ class PartitionFinder:
295
302
  # =========================================================================
296
303
  # Data Loading
297
304
  # =========================================================================
305
+ # =========================================================================
306
+ # Data Loading
307
+ # =========================================================================
298
308
 
299
309
  class DataLoader:
300
310
  """Core data loading functionality with filtering and parallelism."""
301
-
311
+
302
312
  @staticmethod
303
313
  def load_partition(
304
314
  path: str,
@@ -308,77 +318,127 @@ class DataLoader:
308
318
  end_date: Optional[str] = None
309
319
  ) -> pd.DataFrame:
310
320
  """
311
- Load and filter data from a single partition.
312
-
321
+ Load and filter data from a single partition. Added logging for debugging column selection.
322
+
313
323
  Args:
314
324
  path: S3 path to the partition
315
325
  ticker_filter: Optional list of tickers to filter by
316
326
  columns: Optional list of columns to load
317
327
  start_date: Optional start date for filtering
318
328
  end_date: Optional end date for filtering
319
-
329
+
320
330
  Returns:
321
331
  Filtered pandas DataFrame
322
332
  """
323
333
  fs = get_s3_filesystem()
324
-
334
+ logger.info(f"--- Loading Partition Start: {path} ---")
335
+ logger.debug(f"Parameters received: ticker_filter={ticker_filter}, columns={columns}, start_date={start_date}, end_date={end_date}")
336
+
325
337
  try:
326
338
  # Create dataset
327
- dataset = ds.dataset(path, filesystem=fs, format='parquet')
328
-
339
+ logger.debug(f"[{path}] Creating dataset object...")
340
+ dataset = ds.dataset(path, filesystem=fs, format='parquet', partitioning="hive") # Assuming Hive partitioning
341
+ logger.info(f"[{path}] Initial dataset schema: {dataset.schema}")
342
+
329
343
  # Build filter expressions
330
344
  filters = DataLoader._build_date_filters(start_date, end_date)
331
-
332
- # Apply filters to dataset
345
+ logger.debug(f"[{path}] Built date filters: {filters}")
346
+
347
+ # Apply filters to dataset before reading columns
333
348
  if filters:
334
349
  filter_expr = filters[0]
335
350
  for f in filters[1:]:
336
351
  filter_expr = filter_expr & f
337
- dataset = dataset.filter(filter_expr)
338
-
339
- # Load data
340
- table = dataset.to_table(columns=columns, use_threads=True)
352
+ try:
353
+ logger.info(f"[{path}] Applying filter expression: {filter_expr}")
354
+ dataset = dataset.filter(filter_expr)
355
+ # Note: Checking row count here might be slow as it forces some reading
356
+ # fragment_count = len(list(dataset.get_fragments()))
357
+ # logger.info(f"[{path}] Filter applied. Found {fragment_count} fragments. Schema after filtering: {dataset.schema}")
358
+ logger.info(f"[{path}] Filter applied. Schema after filtering: {dataset.schema}")
359
+ except Exception as filter_e:
360
+ logger.error(f"[{path}] Error applying filters: {filter_e}", exc_info=True)
361
+ logger.info(f"--- Loading Partition Failed (Filter Error): {path} ---")
362
+ return pd.DataFrame()
363
+
364
+ # Load data using to_table
365
+ logger.info(f"[{path}] Calling dataset.to_table() with columns: {columns}")
366
+ try:
367
+ # Explicitly handle None columns case for logging clarity
368
+ cols_to_load = columns if columns else "ALL"
369
+ table = dataset.to_table(columns=columns, use_threads=True) # Pass None or list
370
+ logger.info(f"[{path}] dataset.to_table(columns={cols_to_load}) successful.")
371
+ logger.info(f"[{path}] Resulting Arrow Table shape: {table.shape}")
372
+ logger.debug(f"[{path}] Resulting Arrow Table columns: {table.column_names}")
373
+
374
+ # Check if table is empty AFTER the potentially selective read
375
+ if table.num_rows == 0:
376
+ logger.warning(f"[{path}] Arrow Table is empty after to_table(columns={cols_to_load}). "
377
+ f"This might be expected if date filter removed all rows, "
378
+ f"or problematic if columns were requested but not found/read.")
379
+ # Proceed to convert empty table to empty DataFrame
380
+
381
+ except Exception as to_table_e:
382
+ logger.error(f"[{path}] Error during dataset.to_table(columns={columns}): {to_table_e}", exc_info=True)
383
+ logger.error(f"[{path}] Dataset schema at time of error: {dataset.schema}")
384
+ logger.info(f"--- Loading Partition Failed (to_table Error): {path} ---")
385
+ return pd.DataFrame()
386
+
387
+ # Convert to pandas
388
+ logger.debug(f"[{path}] Converting Arrow Table to pandas DataFrame...")
341
389
  df = table.to_pandas()
342
-
343
- # Apply ticker filter if needed
390
+ logger.info(f"[{path}] Converted to pandas DataFrame. Shape: {df.shape}")
391
+ if df.empty and table.num_rows > 0:
392
+ logger.warning(f"[{path}] Conversion to pandas resulted in empty DataFrame, but Arrow table had {table.num_rows} rows.")
393
+ logger.debug(f"[{path}] Pandas DataFrame columns: {list(df.columns)}")
394
+
395
+ # Apply ticker filter if needed (post-load)
344
396
  if ticker_filter and 'ticker' in df.columns:
397
+ logger.info(f"[{path}] Applying post-load ticker filter: {ticker_filter}")
398
+ initial_rows = len(df)
345
399
  df = df[df['ticker'].isin(ticker_filter)]
346
-
400
+ logger.info(f"[{path}] Ticker filter applied. Rows reduced from {initial_rows} to {len(df)}")
401
+ elif ticker_filter:
402
+ logger.warning(f"[{path}] Ticker filter {ticker_filter} requested but 'ticker' column not found in loaded columns: {list(df.columns)}.")
403
+
404
+ logger.info(f"--- Loading Partition End: {path} --- Returning DataFrame shape: {df.shape}")
347
405
  return df
406
+
348
407
  except Exception as e:
349
- logger.error(f"Error loading data from {path}: {e}")
408
+ # Catch-all for other errors (e.g., ds.dataset creation failed)
409
+ logger.error(f"Unhandled error processing partition {path}: {e}", exc_info=True)
410
+ logger.info(f"--- Loading Partition Failed (Unhandled Error): {path} ---")
350
411
  return pd.DataFrame()
351
-
412
+
413
+
352
414
  @staticmethod
353
415
  def _build_date_filters(
354
- start_date: Optional[str],
416
+ start_date: Optional[str],
355
417
  end_date: Optional[str]
356
418
  ) -> List[ds.Expression]:
357
419
  """
358
- Build PyArrow date filter expressions.
359
-
360
- Args:
361
- start_date: Optional start date string
362
- end_date: Optional end date string
363
-
364
- Returns:
365
- List of PyArrow filter expressions
420
+ Build PyArrow date filter expressions. (Ensure 'date' column exists)
366
421
  """
367
422
  filters = []
368
-
369
- # Date filters
370
- if start_date:
371
- start_date_obj = pd.to_datetime(start_date).date()
372
- start_date_pa = pa.scalar(start_date_obj, type=pa.date32())
373
- filters.append(ds.field('date') >= start_date_pa)
374
-
375
- if end_date:
376
- end_date_obj = pd.to_datetime(end_date).date()
377
- end_date_pa = pa.scalar(end_date_obj, type=pa.date32())
378
- filters.append(ds.field('date') <= end_date_pa)
379
-
423
+ try:
424
+ # Date filters - Assuming 'date' column exists and is date32 or similar
425
+ if start_date:
426
+ start_date_obj = pd.to_datetime(start_date).date()
427
+ # Use pa.date32() explicitly assuming the Parquet schema uses it
428
+ start_date_pa = pa.scalar(start_date_obj, type=pa.date32())
429
+ filters.append(ds.field('date') >= start_date_pa)
430
+
431
+ if end_date:
432
+ end_date_obj = pd.to_datetime(end_date).date()
433
+ end_date_pa = pa.scalar(end_date_obj, type=pa.date32())
434
+ filters.append(ds.field('date') <= end_date_pa)
435
+ except Exception as e:
436
+ logger.error(f"Failed to build date filters (start={start_date}, end={end_date}): {e}", exc_info=True)
437
+ # Decide if you want to raise or return empty filters
438
+ # return [] # Return empty filters if parsing fails?
439
+
380
440
  return filters
381
-
441
+
382
442
  @staticmethod
383
443
  def load_data_parallel(
384
444
  tasks: List[Tuple],
@@ -386,34 +446,42 @@ class DataLoader:
386
446
  ) -> List[pd.DataFrame]:
387
447
  """
388
448
  Process loading tasks in parallel with progress tracking.
389
-
390
- Args:
391
- tasks: List of (path, ticker_filter, columns, start_date, end_date) tuples
392
- max_workers: Maximum number of concurrent loading threads
393
-
394
- Returns:
395
- List of loaded DataFrames
396
449
  """
397
450
  results = []
398
-
451
+ if not tasks:
452
+ logger.warning("load_data_parallel received no tasks.")
453
+ return results
454
+
455
+ logger.info(f"Starting parallel load of {len(tasks)} tasks with max_workers={max_workers}")
456
+
399
457
  # Process tasks in parallel
400
458
  with ThreadPoolExecutor(max_workers=max_workers) as executor:
401
- future_to_task = {
402
- executor.submit(DataLoader.load_partition, path, ticker_filter, columns, start_date, end_date): path
403
- for path, ticker_filter, columns, start_date, end_date in tasks
459
+ # Prepare arguments properly for submit
460
+ future_to_path = {
461
+ executor.submit(DataLoader.load_partition, path, ticker_filter, task_columns, task_start_date, task_end_date): path
462
+ for path, ticker_filter, task_columns, task_start_date, task_end_date in tasks # Unpack task tuple
404
463
  }
405
-
406
- for future in tqdm(as_completed(future_to_task), total=len(tasks), desc="Loading data"):
464
+
465
+ # Use tqdm for progress bar
466
+ for future in tqdm(as_completed(future_to_path), total=len(tasks), desc="Loading partitions", unit="partition"):
467
+ path = future_to_path[future]
407
468
  try:
408
- df = future.result()
409
- if not df.empty:
410
- results.append(df)
469
+ df_partition = future.result() # Get result from load_partition
470
+ if df_partition is not None and not df_partition.empty:
471
+ logger.debug(f"Successfully loaded non-empty DataFrame from {path}, shape: {df_partition.shape}")
472
+ results.append(df_partition)
473
+ elif df_partition is not None:
474
+ logger.info(f"Loaded empty DataFrame from {path}")
475
+ else:
476
+ # Should not happen if load_partition always returns a DataFrame
477
+ logger.warning(f"Load task for {path} returned None.")
478
+
411
479
  except Exception as e:
412
- path = future_to_task[future]
413
- logger.error(f"Task failed for {path}: {e}")
414
-
415
- return results
480
+ # This catches errors raised *after* submit, e.g., during future.result() processing if not caught inside load_partition
481
+ logger.error(f"Task failed for partition {path} during result retrieval: {e}", exc_info=True)
416
482
 
483
+ logger.info(f"Parallel loading finished. Collected results from {len(results)} non-empty partitions.")
484
+ return results
417
485
  # =========================================================================
418
486
  # Main Loading Interface
419
487
  # =========================================================================
@@ -698,7 +766,55 @@ ENDPOINT_CONFIG = {
698
766
  "ticker_path": "sovai/sovai-wiki/views/partitioned/ticker",
699
767
  "date_path": "sovai/sovai-wiki/views/partitioned/date",
700
768
  "has_year": False
701
- }
769
+ },
770
+
771
+ "insider/trading": {
772
+ "ticker_path": "sovai/sovai-insider/partitioned/ticker",
773
+ "date_path": "sovai/sovai-insider/partitioned/date",
774
+ "has_year": False
775
+ },
776
+
777
+ "corprisk/risks": {
778
+ "ticker_path": "sovai/sovai-flags/risks/partitioned/ticker",
779
+ "date_path": "sovai/sovai-flags/risks/partitioned/date",
780
+ "has_year": False
781
+ },
782
+ "corprisk/accounting": {
783
+ "ticker_path": "sovai/sovai-flags/accounting/partitioned/ticker",
784
+ "date_path": "sovai/sovai-flags/accounting/partitioned/date",
785
+ "has_year": False
786
+ },
787
+ "corprisk/events": {
788
+ "ticker_path": "sovai/sovai-flags/events/partitioned/ticker",
789
+ "date_path": "sovai/sovai-flags/events/partitioned/date",
790
+ "has_year": False
791
+ },
792
+ "corprisk/misstatements": {
793
+ "ticker_path": "sovai/sovai-flags/misstatements/partitioned/ticker",
794
+ "date_path": "sovai/sovai-flags/misstatements/partitioned/date",
795
+ "has_year": False
796
+ },
797
+ "short/volume": {
798
+ "ticker_path": "sovai/sovai-short/short_volume_weekly/partitioned/ticker",
799
+ "date_path": "sovai/sovai-short/short_volume_weekly/partitioned/date",
800
+ "has_year": False
801
+ },
802
+ ## probably more to do with short later.
803
+ "short/maker": {
804
+ "ticker_path": "sovai/sovai-short/over_shorted/partitioned/ticker",
805
+ "date_path": "sovai/sovai-short/over_shorted/partitioned/date",
806
+ "has_year": False
807
+ },
808
+ "short/over_shorted": {
809
+ "ticker_path": "sovai/sovai-short/over_shorted/partitioned/ticker",
810
+ "date_path": "sovai/sovai-short/over_shorted/partitioned/date",
811
+ "has_year": False
812
+ },
813
+ "institutional/trading": {
814
+ "ticker_path": "sovai/sovai-institutional/trading/partitioned/ticker",
815
+ "date_path": "sovai/sovai-institutional/trading/partitioned/date",
816
+ "has_year": False
817
+ },
702
818
 
703
819
  }
704
820
 
@@ -788,10 +904,15 @@ def load_frame_s3_partitioned_high(
788
904
  tickers=tickers,
789
905
  start_date=start_date,
790
906
  end_date=end_date,
791
- columns=columns,
907
+ columns=[col.strip() for col in columns.split(',')] ,
792
908
  max_workers=8,
793
909
  post_process=post_process
794
910
  )
911
+
912
+ # print(columns)
913
+ # columns = [col.strip() for col in columns.split(',')]
914
+
915
+ # df = df[columns] if columns else df
795
916
 
796
917
  # Convert to CustomDataFrame if available
797
918
  if HAS_CUSTOM_DATAFRAME:
Binary file
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes