sovai 0.2.34__tar.gz → 0.2.36__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 (97) hide show
  1. {sovai-0.2.34 → sovai-0.2.36}/PKG-INFO +2 -1
  2. {sovai-0.2.34 → sovai-0.2.36}/pyproject.toml +2 -1
  3. {sovai-0.2.34 → sovai-0.2.36}/sovai/get_data.py +35 -21
  4. {sovai-0.2.34 → sovai-0.2.36}/sovai/tools/authentication/authentication.py +56 -4
  5. {sovai-0.2.34 → sovai-0.2.36}/sovai/utils/client_side_s3.py +8 -0
  6. sovai-0.2.36/sovai/utils/client_side_s3_part_high.py +321 -0
  7. {sovai-0.2.34 → sovai-0.2.36}/LICENSE +0 -0
  8. {sovai-0.2.34 → sovai-0.2.36}/README.md +0 -0
  9. {sovai-0.2.34 → sovai-0.2.36}/sovai/.DS_Store +0 -0
  10. {sovai-0.2.34 → sovai-0.2.36}/sovai/__init__.py +0 -0
  11. {sovai-0.2.34 → sovai-0.2.36}/sovai/api_config.py +0 -0
  12. {sovai-0.2.34 → sovai-0.2.36}/sovai/assets/dropdown.css +0 -0
  13. {sovai-0.2.34 → sovai-0.2.36}/sovai/assets/features_mapping.parq +0 -0
  14. {sovai-0.2.34 → sovai-0.2.36}/sovai/basic_auth.py +0 -0
  15. {sovai-0.2.34 → sovai-0.2.36}/sovai/computations/__init__.py +0 -0
  16. {sovai-0.2.34 → sovai-0.2.36}/sovai/computations/functions.py +0 -0
  17. {sovai-0.2.34 → sovai-0.2.36}/sovai/errors/__init__.py +0 -0
  18. {sovai-0.2.34 → sovai-0.2.36}/sovai/errors/sovai_errors.py +0 -0
  19. {sovai-0.2.34 → sovai-0.2.36}/sovai/extensions/__init__.py +0 -0
  20. {sovai-0.2.34 → sovai-0.2.36}/sovai/extensions/anomalies.py +0 -0
  21. {sovai-0.2.34 → sovai-0.2.36}/sovai/extensions/ask_df_llm.py +0 -0
  22. {sovai-0.2.34 → sovai-0.2.36}/sovai/extensions/change_point_generator.py +0 -0
  23. {sovai-0.2.34 → sovai-0.2.36}/sovai/extensions/clustering.py +0 -0
  24. {sovai-0.2.34 → sovai-0.2.36}/sovai/extensions/core_kshape.py +0 -0
  25. {sovai-0.2.34 → sovai-0.2.36}/sovai/extensions/cum_sum_torch.py +0 -0
  26. {sovai-0.2.34 → sovai-0.2.36}/sovai/extensions/cusum.py +0 -0
  27. {sovai-0.2.34 → sovai-0.2.36}/sovai/extensions/dimensionality_reduction.py +0 -0
  28. {sovai-0.2.34 → sovai-0.2.36}/sovai/extensions/feature_extraction.py +0 -0
  29. {sovai-0.2.34 → sovai-0.2.36}/sovai/extensions/feature_importance.py +0 -0
  30. {sovai-0.2.34 → sovai-0.2.36}/sovai/extensions/feature_neutralizer.py +0 -0
  31. {sovai-0.2.34 → sovai-0.2.36}/sovai/extensions/filter_df.py +0 -0
  32. {sovai-0.2.34 → sovai-0.2.36}/sovai/extensions/fractional_differencing.py +0 -0
  33. {sovai-0.2.34 → sovai-0.2.36}/sovai/extensions/nowcasting.py +0 -0
  34. {sovai-0.2.34 → sovai-0.2.36}/sovai/extensions/pairwise.py +0 -0
  35. {sovai-0.2.34 → sovai-0.2.36}/sovai/extensions/pandas_extensions.py +0 -0
  36. {sovai-0.2.34 → sovai-0.2.36}/sovai/extensions/parallel_functions.py +0 -0
  37. {sovai-0.2.34 → sovai-0.2.36}/sovai/extensions/pfa_feature_selector.py +0 -0
  38. {sovai-0.2.34 → sovai-0.2.36}/sovai/extensions/regime_change.py +0 -0
  39. {sovai-0.2.34 → sovai-0.2.36}/sovai/extensions/regime_change_pca.py +0 -0
  40. {sovai-0.2.34 → sovai-0.2.36}/sovai/extensions/shapley_global_importance.py +0 -0
  41. {sovai-0.2.34 → sovai-0.2.36}/sovai/extensions/shapley_importance.py +0 -0
  42. {sovai-0.2.34 → sovai-0.2.36}/sovai/extensions/signal_evaluation.py +0 -0
  43. {sovai-0.2.34 → sovai-0.2.36}/sovai/extensions/technical_indicators.py +0 -0
  44. {sovai-0.2.34 → sovai-0.2.36}/sovai/extensions/time_decomposition.py +0 -0
  45. {sovai-0.2.34 → sovai-0.2.36}/sovai/extensions/weight_optimization.py +0 -0
  46. {sovai-0.2.34 → sovai-0.2.36}/sovai/get_compute.py +0 -0
  47. {sovai-0.2.34 → sovai-0.2.36}/sovai/get_plots.py +0 -0
  48. {sovai-0.2.34 → sovai-0.2.36}/sovai/get_reports.py +0 -0
  49. {sovai-0.2.34 → sovai-0.2.36}/sovai/get_tools.py +0 -0
  50. {sovai-0.2.34 → sovai-0.2.36}/sovai/plots/__init__.py +0 -0
  51. {sovai-0.2.34 → sovai-0.2.36}/sovai/plots/accounting/__init__.py +0 -0
  52. {sovai-0.2.34 → sovai-0.2.36}/sovai/plots/accounting/accounting_plots.py +0 -0
  53. {sovai-0.2.34 → sovai-0.2.36}/sovai/plots/allocation/__init__.py +0 -0
  54. {sovai-0.2.34 → sovai-0.2.36}/sovai/plots/allocation/allocation_plots.py +0 -0
  55. {sovai-0.2.34 → sovai-0.2.36}/sovai/plots/bankruptcy/__init__.py +0 -0
  56. {sovai-0.2.34 → sovai-0.2.36}/sovai/plots/bankruptcy/bankruptcy_plots.py +0 -0
  57. {sovai-0.2.34 → sovai-0.2.36}/sovai/plots/breakout/__init__.py +0 -0
  58. {sovai-0.2.34 → sovai-0.2.36}/sovai/plots/breakout/breakout_plots.py +0 -0
  59. {sovai-0.2.34 → sovai-0.2.36}/sovai/plots/corp_risk/__init__.py +0 -0
  60. {sovai-0.2.34 → sovai-0.2.36}/sovai/plots/corp_risk/corp_risk_plots.py +0 -0
  61. {sovai-0.2.34 → sovai-0.2.36}/sovai/plots/earnings_surprise/__init__.py +0 -0
  62. {sovai-0.2.34 → sovai-0.2.36}/sovai/plots/earnings_surprise/earnings_surprise_plots.py +0 -0
  63. {sovai-0.2.34 → sovai-0.2.36}/sovai/plots/insider/__init__.py +0 -0
  64. {sovai-0.2.34 → sovai-0.2.36}/sovai/plots/insider/insider_plots.py +0 -0
  65. {sovai-0.2.34 → sovai-0.2.36}/sovai/plots/institutional/__init__.py +0 -0
  66. {sovai-0.2.34 → sovai-0.2.36}/sovai/plots/institutional/institutional_plots.py +0 -0
  67. {sovai-0.2.34 → sovai-0.2.36}/sovai/plots/news/__init__.py +0 -0
  68. {sovai-0.2.34 → sovai-0.2.36}/sovai/plots/news/news_plots.py +0 -0
  69. {sovai-0.2.34 → sovai-0.2.36}/sovai/plots/ratios/__init__.py +0 -0
  70. {sovai-0.2.34 → sovai-0.2.36}/sovai/plots/ratios/ratios_plots.py +0 -0
  71. {sovai-0.2.34 → sovai-0.2.36}/sovai/reports/accounting/__init__.py +0 -0
  72. {sovai-0.2.34 → sovai-0.2.36}/sovai/reports/accounting/accounting_balance_sheet.py +0 -0
  73. {sovai-0.2.34 → sovai-0.2.36}/sovai/reports/bankruptcy/__init__.py +0 -0
  74. {sovai-0.2.34 → sovai-0.2.36}/sovai/reports/bankruptcy/bankruptcy_monthly_top.py +0 -0
  75. {sovai-0.2.34 → sovai-0.2.36}/sovai/reports/general/__init__.py +0 -0
  76. {sovai-0.2.34 → sovai-0.2.36}/sovai/reports/general/general_plots.py +0 -0
  77. {sovai-0.2.34 → sovai-0.2.36}/sovai/reports/news/__inti__.py +0 -0
  78. {sovai-0.2.34 → sovai-0.2.36}/sovai/reports/news/news_econometric_analysis.py +0 -0
  79. {sovai-0.2.34 → sovai-0.2.36}/sovai/studies/nowcasting.py +0 -0
  80. {sovai-0.2.34 → sovai-0.2.36}/sovai/token_auth.py +0 -0
  81. {sovai-0.2.34 → sovai-0.2.36}/sovai/tools/authentication/__init__.py +0 -0
  82. {sovai-0.2.34 → sovai-0.2.36}/sovai/tools/sec/__init__.py +0 -0
  83. {sovai-0.2.34 → sovai-0.2.36}/sovai/tools/sec/graphs.py +0 -0
  84. {sovai-0.2.34 → sovai-0.2.36}/sovai/tools/sec/llm_code_generator.py +0 -0
  85. {sovai-0.2.34 → sovai-0.2.36}/sovai/tools/sec/sec_10_k_8_k_filings.py +0 -0
  86. {sovai-0.2.34 → sovai-0.2.36}/sovai/tools/sec/sec_edgar_search.py +0 -0
  87. {sovai-0.2.34 → sovai-0.2.36}/sovai/utils/__init__.py +0 -0
  88. {sovai-0.2.34 → sovai-0.2.36}/sovai/utils/client_side.py +0 -0
  89. {sovai-0.2.34 → sovai-0.2.36}/sovai/utils/converter.py +0 -0
  90. {sovai-0.2.34 → sovai-0.2.36}/sovai/utils/datetime_formats.py +0 -0
  91. {sovai-0.2.34 → sovai-0.2.36}/sovai/utils/file_management.py +0 -0
  92. {sovai-0.2.34 → sovai-0.2.36}/sovai/utils/get_tickers.py +0 -0
  93. {sovai-0.2.34 → sovai-0.2.36}/sovai/utils/helpers.py +0 -0
  94. {sovai-0.2.34 → sovai-0.2.36}/sovai/utils/pandas_extensions copy.py +0 -0
  95. {sovai-0.2.34 → sovai-0.2.36}/sovai/utils/plot.py +0 -0
  96. {sovai-0.2.34 → sovai-0.2.36}/sovai/utils/port_manager.py +0 -0
  97. {sovai-0.2.34 → sovai-0.2.36}/sovai/utils/stream.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: sovai
3
- Version: 0.2.34
3
+ Version: 0.2.36
4
4
  Summary: python for asset management
5
5
  License: Proprietary License
6
6
  Author: Derek Snow
@@ -39,6 +39,7 @@ Requires-Dist: pyvis (>=0.3.2,<0.4.0)
39
39
  Requires-Dist: rapidfuzz (>=3.9.7,<4.0.0)
40
40
  Requires-Dist: requests (>=2.20)
41
41
  Requires-Dist: ruptures (>=1.0) ; extra == "full"
42
+ Requires-Dist: s3fs (>=2024.10.0,<2025.0.0)
42
43
  Requires-Dist: scikit-learn (>=1.0) ; extra == "full"
43
44
  Requires-Dist: scipy (>=1.0) ; extra == "full"
44
45
  Requires-Dist: seaborn (>=0.13.2,<0.14.0)
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "sovai"
3
- version = "0.2.34"
3
+ version = "0.2.36"
4
4
  description = "python for asset management"
5
5
  authors = ["Derek Snow <d.snow@outlook.com>"]
6
6
  license = "Proprietary License"
@@ -65,6 +65,7 @@ yachalk = "^0.1.6"
65
65
  pyvis = "^0.3.2"
66
66
  networkx = "^3.3"
67
67
  seaborn = "^0.13.2"
68
+ s3fs = "^2024.10.0"
68
69
 
69
70
 
70
71
  [tool.poetry.extras]
@@ -20,8 +20,11 @@ from sovai.errors.sovai_errors import InvalidInputData
20
20
  from sovai.utils.converter import convert_data2df
21
21
  from sovai.utils.stream import stream_data, stream_data_pyarrow
22
22
  from sovai.utils.datetime_formats import datetime_format
23
+
23
24
  from sovai.utils.client_side import client_side_frame
24
25
  from sovai.utils.client_side_s3 import load_frame_s3
26
+ from sovai.utils.client_side_s3_part_high import load_frame_s3_paritioned_high
27
+
25
28
 
26
29
  ## Note run these for TEST - IT COULD BE HERE some silent problems
27
30
  try:
@@ -267,6 +270,10 @@ endpoint_to_ticker = {
267
270
  "/market/closeadj": "",
268
271
  "/lobbying/public": "",
269
272
 
273
+ "/liquidity/price_improvement":"",
274
+ "/liquidity/market_opportunity":"",
275
+
276
+
270
277
 
271
278
  ## remember to add leading "/"
272
279
  "/trials/predict": "",
@@ -292,6 +299,9 @@ def normalize_endpoint(endpoint):
292
299
 
293
300
  ## If you also want access by full file, please also add to endpoint_to_ticker
294
301
 
302
+
303
+
304
+ # Define your endpoint sets
295
305
  client_side_endpoints_gcs = {
296
306
  "ratios/relative",
297
307
  "market/prices",
@@ -302,7 +312,6 @@ client_side_endpoints_gcs = {
302
312
  "lobbying/public",
303
313
  }
304
314
 
305
- ## If you also want access by full file, please also add to endpoint_to_ticker
306
315
  client_side_endpoints_s3 = {
307
316
  "sec/10k",
308
317
  "trials/predict",
@@ -310,9 +319,18 @@ client_side_endpoints_s3 = {
310
319
  "trials/all/predict",
311
320
  "trials/all/decribe",
312
321
  "trials/all",
322
+ }
313
323
 
324
+ client_side_endpoints_s3_part_high = {
325
+ "patents/applications"
314
326
  }
315
327
 
328
+ # Create a list of handlers
329
+ handlers = [
330
+ (client_side_endpoints_gcs, client_side_frame, "Grabbing GCS client side"),
331
+ (client_side_endpoints_s3, load_frame_s3, "Grabbing S3 client side"),
332
+ (client_side_endpoints_s3_part_high, load_frame_s3_paritioned_high, "Grabbing S3 Partitioned High client side"),
333
+ ]
316
334
 
317
335
 
318
336
  # In your main function or wherever this logic is implemented:
@@ -600,26 +618,22 @@ def data(
600
618
 
601
619
  normalized_endpoint = normalize_endpoint(endpoint)
602
620
 
603
- if (
604
- normalized_endpoint in client_side_endpoints_gcs
605
- and tickers is not None
606
- and frequency is None
607
- ):
608
- verbose_mode.vprint("Grabbing gcs client side")
609
- _query_cache[cache_key] = client_side_frame(
610
- normalized_endpoint, tickers, columns, start_date, end_date
611
- )
612
- return _query_cache[cache_key]
621
+ # ACTAULLY I DON'T CARE, TICKERS CAN BE NONE FOR PATENTS
622
+
623
+ # Iterate through the handlers to find a matching handler
624
+ for endpoint_set, handler_func, message in handlers:
625
+ if (
626
+ normalized_endpoint in endpoint_set and
627
+ (tickers is not None or start_date is not None) and
628
+ frequency is None
629
+ ):
630
+ verbose_mode.vprint(message)
631
+ _query_cache[cache_key] = handler_func(
632
+ normalized_endpoint, tickers, columns, start_date, end_date
633
+ )
634
+ return _query_cache[cache_key]
635
+
613
636
 
614
- elif (
615
- normalized_endpoint in client_side_endpoints_s3
616
- and tickers is not None
617
- and frequency is None
618
- ):
619
- verbose_mode.vprint("Grabbing s3 client side")
620
- _query_cache[cache_key] = load_frame_s3(
621
- normalized_endpoint, tickers, columns, start_date, end_date)
622
- return _query_cache[cache_key]
623
637
  try:
624
638
  if tickers is not None and not is_all(tickers):
625
639
  params = ticker_mapper(params,verbose)
@@ -745,7 +759,7 @@ def data(
745
759
  return None
746
760
  return data
747
761
  except Exception as err:
748
- verbose_mode.vprint("An error occurred, check dictaionries for 'data/name':'None':", err)
762
+ verbose_mode.vprint("An error occurred, check dictionaries for 'data/name':'None':", err)
749
763
  return data
750
764
  if res.status_code == 404:
751
765
  msg = res.json()
@@ -3,7 +3,9 @@ from sovai.api_config import ApiConfig
3
3
  import requests
4
4
  import pickle
5
5
  import base64
6
- from pyarrow.fs import S3FileSystem
6
+ from pyarrow.fs import S3FileSystem as S3File
7
+ import s3fs
8
+
7
9
 
8
10
  def get_s3_filesystem_pickle(storage_provider: str, verbose: bool = False):
9
11
  """
@@ -38,6 +40,56 @@ def get_s3_filesystem_pickle(storage_provider: str, verbose: bool = False):
38
40
  print(f"Failed to retrieve S3FileSystem for {storage_provider}: {e}")
39
41
  return None
40
42
 
43
+
44
+
45
+ def get_s3fs_filesystem_json(storage_provider: str, verbose: bool = False):
46
+ """
47
+ Retrieve the S3FileSystem for the specified storage provider using JSON.
48
+
49
+ :param storage_provider: Either 'wasabi' or 'digitalocean'
50
+ :param verbose: If True, print additional information about the request
51
+ :return: S3FileSystem object, or None if the request fails
52
+ """
53
+ if storage_provider.lower() not in ['wasabi', 'digitalocean']:
54
+ raise ValueError("storage_provider must be either 'wasabi' or 'digitalocean'")
55
+
56
+ url = f"{ApiConfig.base_url}/credentials/get_s3_filesystem_json"
57
+ headers = {
58
+ "Authorization": f"Bearer {ApiConfig.token}",
59
+ "Content-Type": "application/json"
60
+ }
61
+ params = {"storage_provider": storage_provider.lower()}
62
+
63
+ try:
64
+ response = requests.get(url, headers=headers, params=params)
65
+ response.raise_for_status()
66
+
67
+ cred = response.json()
68
+
69
+ if verbose:
70
+ print(f"Successfully retrieved S3 credentials for {storage_provider}")
71
+
72
+ s3fsa = s3fs.S3FileSystem(
73
+ key=cred['access_key'],
74
+ secret=cred['secret_key'],
75
+ client_kwargs={'endpoint_url': cred['endpoint']}
76
+ )
77
+
78
+ try:
79
+ s3fsa.ls('/')
80
+ if verbose:
81
+ print(f"Successfully verified S3FileSystem for {storage_provider}")
82
+ return s3fsa
83
+ except Exception as e:
84
+ if verbose:
85
+ print(f"Failed to verify S3FileSystem: {str(e)}")
86
+ return None
87
+
88
+ except requests.RequestException as e:
89
+ if verbose:
90
+ print(f"Failed to retrieve S3 credentials for {storage_provider}: {e}")
91
+ return None
92
+
41
93
  def get_s3_filesystem_json(storage_provider: str, verbose: bool = False):
42
94
  """
43
95
  Retrieve the S3FileSystem for the specified storage provider using JSON.
@@ -65,17 +117,17 @@ def get_s3_filesystem_json(storage_provider: str, verbose: bool = False):
65
117
  if verbose:
66
118
  print(f"Successfully retrieved S3 credentials for {storage_provider}")
67
119
 
68
- s3fs = S3FileSystem(
120
+ s3fsa = S3File(
69
121
  access_key=cred['access_key'],
70
122
  secret_key=cred['secret_key'],
71
123
  endpoint_override=cred['endpoint']
72
124
  )
73
125
 
74
126
  try:
75
- s3fs.ls('/')
127
+ s3fsa.ls('/')
76
128
  if verbose:
77
129
  print(f"Successfully verified S3FileSystem for {storage_provider}")
78
- return s3fs
130
+ return s3fsa
79
131
  except Exception as e:
80
132
  if verbose:
81
133
  print(f"Failed to verify S3FileSystem: {str(e)}")
@@ -182,6 +182,14 @@ def load_frame_s3(endpoint, tickers=None, columns=None, start_date=None, end_dat
182
182
  "partitioned": True,
183
183
 
184
184
  },
185
+ "patents/applications": {
186
+ "storage_provider": {
187
+ "digitalocean": "sovai/sovai-patents-export/applications",
188
+ "wasabi": "sovai-patents-export/applications"
189
+ },
190
+ "identifier_columns": ["date_partitioned", "ticker_partitioned"],
191
+ "partitioned": True,
192
+ },
185
193
  "trials/predict": {
186
194
  "storage_provider": {
187
195
  "digitalocean": "sovai/sovai-clinical-trials-export/prediction_public",
@@ -0,0 +1,321 @@
1
+ import pyarrow.dataset as ds
2
+ from pyarrow.fs import S3FileSystem
3
+ import pyarrow as pa
4
+ import pandas as pd
5
+ from functools import lru_cache
6
+ from concurrent.futures import ThreadPoolExecutor, as_completed
7
+ from sovai.tools.authentication import authentication
8
+ import os
9
+ import pandas as pd
10
+ import pyarrow as pa
11
+ import pyarrow.dataset as ds
12
+ import s3fs
13
+ import logging
14
+ import datetime
15
+ from functools import lru_cache
16
+ from concurrent.futures import ThreadPoolExecutor, as_completed
17
+ from tqdm import tqdm
18
+ from pyarrow.fs import S3FileSystem
19
+
20
+
21
+ # Try to import CustomDataFrame, use regular DataFrame if not available
22
+ try:
23
+ from sovai.extensions.pandas_extensions import CustomDataFrame
24
+ HAS_CUSTOM_DATAFRAME = True
25
+ except ImportError:
26
+ HAS_CUSTOM_DATAFRAME = False
27
+ CustomDataFrame = pd.DataFrame # Fallback to regular DataFrame
28
+
29
+
30
+ @lru_cache(maxsize=2)
31
+ def get_cached_s3_filesystem(storage_provider):
32
+ return authentication.get_s3_filesystem_pickle(storage_provider, verbose=True)
33
+
34
+ @lru_cache(maxsize=2)
35
+ def get_cached_s3fs_filesystem(storage_provider):
36
+ return authentication.get_s3fs_filesystem_json(storage_provider, verbose=True)
37
+
38
+
39
+ # Configure logging to output to both console and a log file
40
+ logging.basicConfig(
41
+ level=logging.INFO,
42
+ format='%(asctime)s - %(levelname)s - %(message)s',
43
+ handlers=[
44
+ logging.FileHandler("data_operations.log"),
45
+ logging.StreamHandler()
46
+ ]
47
+ )
48
+ logger = logging.getLogger(__name__)
49
+
50
+ # -------------------------------
51
+ # Helper Functions
52
+ # -------------------------------
53
+
54
+ def construct_s3_path(type_name, provider, partition_type, ticker=None, publish_date=None, year=None, all_dates_after=False):
55
+ """
56
+ Construct the S3 path based on the partitioning scheme.
57
+
58
+ Parameters:
59
+ - type_name (str): Type of data (e.g., 'applications')
60
+ - provider (str): 'wasabi' or 'digitalocean'
61
+ - partition_type (str): 'date' or 'ticker'
62
+ - ticker (str, optional): Ticker symbol
63
+ - publish_date (str, optional): Publish date in 'YYYY-MM-DD' format
64
+ - year (int, optional): Year extracted from publish_date
65
+ - all_dates_after (bool, optional): If True, scan all date partitions after a certain date
66
+
67
+ Returns:
68
+ - str: Constructed S3 path
69
+ """
70
+ if provider=="digitalocean":
71
+ bucket ="sovai/sovai-patents-export"
72
+ else:
73
+ bucket = "sovai-patents-export"
74
+
75
+
76
+ if partition_type == "date":
77
+ if all_dates_after:
78
+ # Return the parent directory to scan all date partitions
79
+ path = f"{type_name}/date/"
80
+ else:
81
+ if not publish_date:
82
+ raise ValueError("publish_date must be provided for date partitioning.")
83
+ path = f"{type_name}/date/date_partitioned={publish_date}/"
84
+ elif partition_type == "ticker":
85
+ if not ticker or not year:
86
+ raise ValueError("Both ticker and year must be provided for ticker partitioning.")
87
+ path = f"{type_name}/ticker/ticker_partitioned={ticker}/year={year}/"
88
+ else:
89
+ raise ValueError("Invalid partition_type. Choose 'date' or 'ticker'.")
90
+
91
+ return f"{bucket}/{path}"
92
+
93
+
94
+ def list_date_partitions(provider, type_name):
95
+ s3_path = construct_s3_path(type_name, provider, partition_type="date", all_dates_after=True)
96
+ fs = get_cached_s3fs_filesystem(provider)
97
+ try:
98
+ partitions = fs.ls(s3_path)
99
+ dates = []
100
+ for partition in partitions:
101
+ basename = os.path.basename(partition.rstrip('/'))
102
+ if basename.startswith('date_partitioned='):
103
+ date_str = basename.split('=')[1]
104
+ try:
105
+ date_obj = datetime.datetime.strptime(date_str, '%Y-%m-%d').date()
106
+ dates.append((date_obj, partition))
107
+ except ValueError:
108
+ logger.warning(f"Unable to parse date from partition {partition}")
109
+ return dates
110
+ except Exception as e:
111
+ logger.error(f"Error listing date partitions in {s3_path} ({provider}): {e}")
112
+ return []
113
+
114
+ def load_parquet_from_s3(s3_path, provider, columns=None, start_date=None):
115
+ """
116
+ Load a Parquet file from S3 into a pandas DataFrame with optional date filtering.
117
+
118
+ Parameters:
119
+ - s3_path (str): S3 path to the Parquet file or directory
120
+ - provider (str): 'wasabi' or 'digitalocean'
121
+ - columns (list, optional): List of columns to select
122
+ - start_date (str, optional): Minimum date in 'YYYY-MM-DD' format to filter the data
123
+
124
+ Returns:
125
+ - pandas.DataFrame: Loaded DataFrame
126
+ """
127
+
128
+ fs = get_cached_s3_filesystem(provider)
129
+
130
+ try:
131
+ dataset = ds.dataset(s3_path, filesystem=fs, format='parquet')
132
+
133
+ if start_date:
134
+ # Convert start_date string to pyarrow date32 scalar
135
+ min_date_pa = pa.scalar(pd.to_datetime(start_date).date(), type=pa.date32())
136
+ # Use ds.field to construct the filter expression
137
+ filter_expr = ds.field('date') >= min_date_pa
138
+ dataset = dataset.filter(filter_expr)
139
+
140
+ table = dataset.to_table(columns=columns)
141
+
142
+ # Log the schema for debugging
143
+ logger.info(f"Schema for {s3_path} ({provider}):")
144
+ logger.info(table.schema)
145
+
146
+ df = table.to_pandas()
147
+
148
+ partition_cols = ['ticker', 'date']
149
+ existing_cols = [col for col in partition_cols if col in df.columns]
150
+ other_cols = [col for col in df.columns if col not in partition_cols]
151
+ df = df[existing_cols + other_cols]
152
+
153
+ return df
154
+ except Exception as e:
155
+ logger.error(f"Error loading data from {s3_path} ({provider}): {e}")
156
+ return pd.DataFrame()
157
+
158
+ # -------------------------------
159
+ # Loading Functions
160
+ # -------------------------------
161
+
162
+ def load_data_by_ticker(
163
+ type_name,
164
+ providers=['wasabi', 'digitalocean'],
165
+ tickers=None,
166
+ start_date=None,
167
+ columns=None,
168
+ max_workers=4
169
+ ):
170
+ """
171
+ Load data for specified tickers and/or dates from S3 storage providers.
172
+
173
+ Parameters:
174
+ - type_name (str): Type of data (e.g., 'applications', 'prediction_all', etc.)
175
+ - providers (list, optional): List of storage providers to load from ('wasabi', 'digitalocean')
176
+ - tickers (str or list, optional): Ticker symbol(s) to load data for
177
+ - start_date (str, optional): Minimum publishDate to filter data by (format 'YYYY-MM-DD') (only for date partitioning)
178
+ - columns (list, optional): List of columns to select
179
+ - max_workers (int, optional): Number of parallel threads
180
+
181
+ Returns:
182
+ - pandas.DataFrame: Concatenated DataFrame containing the loaded data
183
+ """
184
+ date_tasks = []
185
+ ticker_tasks = []
186
+ results = []
187
+
188
+ # Normalize input parameters
189
+ if tickers and isinstance(tickers, str):
190
+ tickers = [tickers]
191
+ if not tickers:
192
+ tickers = []
193
+
194
+ # If both tickers and min_date are provided
195
+ if tickers and start_date:
196
+ try:
197
+ min_date_obj = datetime.datetime.strptime(start_date, '%Y-%m-%d').date()
198
+ min_year = min_date_obj.year
199
+ logger.info(f"Minimum date provided: {min_date_obj} (Year: {min_year})")
200
+ except ValueError:
201
+ logger.error(f"Invalid min_date format: {start_date}. Expected 'YYYY-MM-DD'.")
202
+ return pd.DataFrame()
203
+
204
+ for provider in providers:
205
+ for ticker in tickers:
206
+ # Determine the range of years to load based on start_date
207
+ # Assuming data starts from 2011
208
+ start_year = min_year
209
+ current_year = datetime.datetime.now().year
210
+ for year in range(start_year, current_year + 1):
211
+ s3_path = construct_s3_path(
212
+ type_name, provider, partition_type="ticker",
213
+ ticker=ticker, year=year
214
+ )
215
+ ticker_tasks.append((s3_path, provider, columns, None)) # No additional filtering here
216
+
217
+ # If only min_date is provided
218
+ elif start_date and not tickers:
219
+ try:
220
+ min_date_obj = datetime.datetime.strptime(start_date, '%Y-%m-%d').date()
221
+ logger.info(f"Minimum date provided: {min_date_obj}")
222
+ except ValueError:
223
+ logger.error(f"Invalid start_date format: {start_date}. Expected 'YYYY-MM-DD'.")
224
+ return pd.DataFrame()
225
+
226
+ for provider in providers:
227
+ # List all date-based partitions
228
+ date_partitions = list_date_partitions(provider, type_name)
229
+ for date_obj, partition_path in date_partitions:
230
+ if date_obj >= min_date_obj:
231
+ date_tasks.append((partition_path, provider, columns, None))
232
+
233
+ # If only tickers are provided
234
+ elif tickers and not start_date:
235
+ for provider in providers:
236
+ for ticker in tickers:
237
+ # Assuming data starts from 2011
238
+ start_year = 2010 ## Change this to 2008 eventually
239
+ current_year = datetime.datetime.now().year
240
+ for year in range(start_year, current_year + 1):
241
+ s3_path = construct_s3_path(
242
+ type_name, provider, partition_type="ticker",
243
+ ticker=ticker, year=year
244
+ )
245
+ ticker_tasks.append((s3_path, provider, columns, None))
246
+
247
+ else:
248
+ logger.warning("No tickers or start_date provided. Please provide at least one.")
249
+ return pd.DataFrame()
250
+
251
+ # Combine all tasks
252
+ all_tasks = ticker_tasks + date_tasks
253
+
254
+ if not all_tasks:
255
+ logger.warning("No tasks to process. Exiting.")
256
+ return pd.DataFrame()
257
+
258
+ def load_task(task):
259
+ s3_path, provider, columns, ticker_filter = task
260
+ df = load_parquet_from_s3(s3_path, provider, columns, start_date=start_date if ticker_filter is None else None)
261
+ if not df.empty and ticker_filter:
262
+ if 'ticker' in df.columns:
263
+ df = df[df['ticker'].isin(ticker_filter)]
264
+ else:
265
+ logger.warning(f"'ticker' column not found in data from {s3_path} ({provider}). Skipping ticker filter.")
266
+ return df
267
+
268
+ with ThreadPoolExecutor(max_workers=max_workers) as executor:
269
+ future_to_task = {executor.submit(load_task, task): task for task in all_tasks}
270
+ for future in tqdm(as_completed(future_to_task), total=len(all_tasks), desc="Loading data"):
271
+ df = future.result()
272
+ if not df.empty:
273
+ results.append(df)
274
+
275
+ if results:
276
+ final_df = pd.concat(results, ignore_index=True)
277
+ logger.info(f"Successfully loaded data with {len(final_df)} records.")
278
+ else:
279
+ final_df = pd.DataFrame()
280
+ logger.warning("No data found.")
281
+
282
+ return final_df
283
+
284
+ # -------------------------------
285
+ # Example Usage
286
+ # -------------------------------
287
+
288
+
289
+ def load_frame_s3_paritioned_high(endpoint, tickers=None, columns=None, start_date=None, end_date=None):
290
+
291
+ endpoint_config = {
292
+ "patents/applications": {
293
+ "bucket": {
294
+ "digitalocean": "sovai/sovai-patents-export",
295
+ "wasabi": "sovai-patents-export"
296
+ },
297
+ "type": "applications"
298
+ },
299
+ }
300
+
301
+ if endpoint not in endpoint_config:
302
+ raise ValueError(f"Invalid endpoint: {endpoint}")
303
+
304
+ config = endpoint_config[endpoint]
305
+
306
+
307
+ loaded_df_both = load_data_by_ticker(
308
+ type_name=config["type"],
309
+ providers=['digitalocean'],
310
+ tickers=tickers,
311
+ start_date=start_date,
312
+ columns=columns,
313
+ max_workers=8 # Adjust based on your system's capabilities
314
+ )
315
+
316
+
317
+ if HAS_CUSTOM_DATAFRAME:
318
+ return CustomDataFrame(loaded_df_both)
319
+ else:
320
+ return loaded_df_both # Returns a regular pandas DataFrame if CustomDataFrame is not available
321
+
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