sovai 0.2.35__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 (98) hide show
  1. {sovai-0.2.35 → sovai-0.2.36}/PKG-INFO +1 -1
  2. {sovai-0.2.35 → sovai-0.2.36}/pyproject.toml +1 -1
  3. {sovai-0.2.35 → sovai-0.2.36}/sovai/get_data.py +30 -20
  4. {sovai-0.2.35 → sovai-0.2.36}/sovai/tools/authentication/authentication.py +3 -3
  5. sovai-0.2.36/sovai/utils/client_side_s3_part_high.py +321 -0
  6. sovai-0.2.35/sovai/utils/client_side_s3 copy.py +0 -206
  7. {sovai-0.2.35 → sovai-0.2.36}/LICENSE +0 -0
  8. {sovai-0.2.35 → sovai-0.2.36}/README.md +0 -0
  9. {sovai-0.2.35 → sovai-0.2.36}/sovai/.DS_Store +0 -0
  10. {sovai-0.2.35 → sovai-0.2.36}/sovai/__init__.py +0 -0
  11. {sovai-0.2.35 → sovai-0.2.36}/sovai/api_config.py +0 -0
  12. {sovai-0.2.35 → sovai-0.2.36}/sovai/assets/dropdown.css +0 -0
  13. {sovai-0.2.35 → sovai-0.2.36}/sovai/assets/features_mapping.parq +0 -0
  14. {sovai-0.2.35 → sovai-0.2.36}/sovai/basic_auth.py +0 -0
  15. {sovai-0.2.35 → sovai-0.2.36}/sovai/computations/__init__.py +0 -0
  16. {sovai-0.2.35 → sovai-0.2.36}/sovai/computations/functions.py +0 -0
  17. {sovai-0.2.35 → sovai-0.2.36}/sovai/errors/__init__.py +0 -0
  18. {sovai-0.2.35 → sovai-0.2.36}/sovai/errors/sovai_errors.py +0 -0
  19. {sovai-0.2.35 → sovai-0.2.36}/sovai/extensions/__init__.py +0 -0
  20. {sovai-0.2.35 → sovai-0.2.36}/sovai/extensions/anomalies.py +0 -0
  21. {sovai-0.2.35 → sovai-0.2.36}/sovai/extensions/ask_df_llm.py +0 -0
  22. {sovai-0.2.35 → sovai-0.2.36}/sovai/extensions/change_point_generator.py +0 -0
  23. {sovai-0.2.35 → sovai-0.2.36}/sovai/extensions/clustering.py +0 -0
  24. {sovai-0.2.35 → sovai-0.2.36}/sovai/extensions/core_kshape.py +0 -0
  25. {sovai-0.2.35 → sovai-0.2.36}/sovai/extensions/cum_sum_torch.py +0 -0
  26. {sovai-0.2.35 → sovai-0.2.36}/sovai/extensions/cusum.py +0 -0
  27. {sovai-0.2.35 → sovai-0.2.36}/sovai/extensions/dimensionality_reduction.py +0 -0
  28. {sovai-0.2.35 → sovai-0.2.36}/sovai/extensions/feature_extraction.py +0 -0
  29. {sovai-0.2.35 → sovai-0.2.36}/sovai/extensions/feature_importance.py +0 -0
  30. {sovai-0.2.35 → sovai-0.2.36}/sovai/extensions/feature_neutralizer.py +0 -0
  31. {sovai-0.2.35 → sovai-0.2.36}/sovai/extensions/filter_df.py +0 -0
  32. {sovai-0.2.35 → sovai-0.2.36}/sovai/extensions/fractional_differencing.py +0 -0
  33. {sovai-0.2.35 → sovai-0.2.36}/sovai/extensions/nowcasting.py +0 -0
  34. {sovai-0.2.35 → sovai-0.2.36}/sovai/extensions/pairwise.py +0 -0
  35. {sovai-0.2.35 → sovai-0.2.36}/sovai/extensions/pandas_extensions.py +0 -0
  36. {sovai-0.2.35 → sovai-0.2.36}/sovai/extensions/parallel_functions.py +0 -0
  37. {sovai-0.2.35 → sovai-0.2.36}/sovai/extensions/pfa_feature_selector.py +0 -0
  38. {sovai-0.2.35 → sovai-0.2.36}/sovai/extensions/regime_change.py +0 -0
  39. {sovai-0.2.35 → sovai-0.2.36}/sovai/extensions/regime_change_pca.py +0 -0
  40. {sovai-0.2.35 → sovai-0.2.36}/sovai/extensions/shapley_global_importance.py +0 -0
  41. {sovai-0.2.35 → sovai-0.2.36}/sovai/extensions/shapley_importance.py +0 -0
  42. {sovai-0.2.35 → sovai-0.2.36}/sovai/extensions/signal_evaluation.py +0 -0
  43. {sovai-0.2.35 → sovai-0.2.36}/sovai/extensions/technical_indicators.py +0 -0
  44. {sovai-0.2.35 → sovai-0.2.36}/sovai/extensions/time_decomposition.py +0 -0
  45. {sovai-0.2.35 → sovai-0.2.36}/sovai/extensions/weight_optimization.py +0 -0
  46. {sovai-0.2.35 → sovai-0.2.36}/sovai/get_compute.py +0 -0
  47. {sovai-0.2.35 → sovai-0.2.36}/sovai/get_plots.py +0 -0
  48. {sovai-0.2.35 → sovai-0.2.36}/sovai/get_reports.py +0 -0
  49. {sovai-0.2.35 → sovai-0.2.36}/sovai/get_tools.py +0 -0
  50. {sovai-0.2.35 → sovai-0.2.36}/sovai/plots/__init__.py +0 -0
  51. {sovai-0.2.35 → sovai-0.2.36}/sovai/plots/accounting/__init__.py +0 -0
  52. {sovai-0.2.35 → sovai-0.2.36}/sovai/plots/accounting/accounting_plots.py +0 -0
  53. {sovai-0.2.35 → sovai-0.2.36}/sovai/plots/allocation/__init__.py +0 -0
  54. {sovai-0.2.35 → sovai-0.2.36}/sovai/plots/allocation/allocation_plots.py +0 -0
  55. {sovai-0.2.35 → sovai-0.2.36}/sovai/plots/bankruptcy/__init__.py +0 -0
  56. {sovai-0.2.35 → sovai-0.2.36}/sovai/plots/bankruptcy/bankruptcy_plots.py +0 -0
  57. {sovai-0.2.35 → sovai-0.2.36}/sovai/plots/breakout/__init__.py +0 -0
  58. {sovai-0.2.35 → sovai-0.2.36}/sovai/plots/breakout/breakout_plots.py +0 -0
  59. {sovai-0.2.35 → sovai-0.2.36}/sovai/plots/corp_risk/__init__.py +0 -0
  60. {sovai-0.2.35 → sovai-0.2.36}/sovai/plots/corp_risk/corp_risk_plots.py +0 -0
  61. {sovai-0.2.35 → sovai-0.2.36}/sovai/plots/earnings_surprise/__init__.py +0 -0
  62. {sovai-0.2.35 → sovai-0.2.36}/sovai/plots/earnings_surprise/earnings_surprise_plots.py +0 -0
  63. {sovai-0.2.35 → sovai-0.2.36}/sovai/plots/insider/__init__.py +0 -0
  64. {sovai-0.2.35 → sovai-0.2.36}/sovai/plots/insider/insider_plots.py +0 -0
  65. {sovai-0.2.35 → sovai-0.2.36}/sovai/plots/institutional/__init__.py +0 -0
  66. {sovai-0.2.35 → sovai-0.2.36}/sovai/plots/institutional/institutional_plots.py +0 -0
  67. {sovai-0.2.35 → sovai-0.2.36}/sovai/plots/news/__init__.py +0 -0
  68. {sovai-0.2.35 → sovai-0.2.36}/sovai/plots/news/news_plots.py +0 -0
  69. {sovai-0.2.35 → sovai-0.2.36}/sovai/plots/ratios/__init__.py +0 -0
  70. {sovai-0.2.35 → sovai-0.2.36}/sovai/plots/ratios/ratios_plots.py +0 -0
  71. {sovai-0.2.35 → sovai-0.2.36}/sovai/reports/accounting/__init__.py +0 -0
  72. {sovai-0.2.35 → sovai-0.2.36}/sovai/reports/accounting/accounting_balance_sheet.py +0 -0
  73. {sovai-0.2.35 → sovai-0.2.36}/sovai/reports/bankruptcy/__init__.py +0 -0
  74. {sovai-0.2.35 → sovai-0.2.36}/sovai/reports/bankruptcy/bankruptcy_monthly_top.py +0 -0
  75. {sovai-0.2.35 → sovai-0.2.36}/sovai/reports/general/__init__.py +0 -0
  76. {sovai-0.2.35 → sovai-0.2.36}/sovai/reports/general/general_plots.py +0 -0
  77. {sovai-0.2.35 → sovai-0.2.36}/sovai/reports/news/__inti__.py +0 -0
  78. {sovai-0.2.35 → sovai-0.2.36}/sovai/reports/news/news_econometric_analysis.py +0 -0
  79. {sovai-0.2.35 → sovai-0.2.36}/sovai/studies/nowcasting.py +0 -0
  80. {sovai-0.2.35 → sovai-0.2.36}/sovai/token_auth.py +0 -0
  81. {sovai-0.2.35 → sovai-0.2.36}/sovai/tools/authentication/__init__.py +0 -0
  82. {sovai-0.2.35 → sovai-0.2.36}/sovai/tools/sec/__init__.py +0 -0
  83. {sovai-0.2.35 → sovai-0.2.36}/sovai/tools/sec/graphs.py +0 -0
  84. {sovai-0.2.35 → sovai-0.2.36}/sovai/tools/sec/llm_code_generator.py +0 -0
  85. {sovai-0.2.35 → sovai-0.2.36}/sovai/tools/sec/sec_10_k_8_k_filings.py +0 -0
  86. {sovai-0.2.35 → sovai-0.2.36}/sovai/tools/sec/sec_edgar_search.py +0 -0
  87. {sovai-0.2.35 → sovai-0.2.36}/sovai/utils/__init__.py +0 -0
  88. {sovai-0.2.35 → sovai-0.2.36}/sovai/utils/client_side.py +0 -0
  89. {sovai-0.2.35 → sovai-0.2.36}/sovai/utils/client_side_s3.py +0 -0
  90. {sovai-0.2.35 → sovai-0.2.36}/sovai/utils/converter.py +0 -0
  91. {sovai-0.2.35 → sovai-0.2.36}/sovai/utils/datetime_formats.py +0 -0
  92. {sovai-0.2.35 → sovai-0.2.36}/sovai/utils/file_management.py +0 -0
  93. {sovai-0.2.35 → sovai-0.2.36}/sovai/utils/get_tickers.py +0 -0
  94. {sovai-0.2.35 → sovai-0.2.36}/sovai/utils/helpers.py +0 -0
  95. {sovai-0.2.35 → sovai-0.2.36}/sovai/utils/pandas_extensions copy.py +0 -0
  96. {sovai-0.2.35 → sovai-0.2.36}/sovai/utils/plot.py +0 -0
  97. {sovai-0.2.35 → sovai-0.2.36}/sovai/utils/port_manager.py +0 -0
  98. {sovai-0.2.35 → 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.35
3
+ Version: 0.2.36
4
4
  Summary: python for asset management
5
5
  License: Proprietary License
6
6
  Author: Derek Snow
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "sovai"
3
- version = "0.2.35"
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"
@@ -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:
@@ -296,6 +299,9 @@ def normalize_endpoint(endpoint):
296
299
 
297
300
  ## If you also want access by full file, please also add to endpoint_to_ticker
298
301
 
302
+
303
+
304
+ # Define your endpoint sets
299
305
  client_side_endpoints_gcs = {
300
306
  "ratios/relative",
301
307
  "market/prices",
@@ -306,7 +312,6 @@ client_side_endpoints_gcs = {
306
312
  "lobbying/public",
307
313
  }
308
314
 
309
- ## If you also want access by full file, please also add to endpoint_to_ticker
310
315
  client_side_endpoints_s3 = {
311
316
  "sec/10k",
312
317
  "trials/predict",
@@ -314,9 +319,18 @@ client_side_endpoints_s3 = {
314
319
  "trials/all/predict",
315
320
  "trials/all/decribe",
316
321
  "trials/all",
322
+ }
317
323
 
324
+ client_side_endpoints_s3_part_high = {
325
+ "patents/applications"
318
326
  }
319
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
+ ]
320
334
 
321
335
 
322
336
  # In your main function or wherever this logic is implemented:
@@ -604,26 +618,22 @@ def data(
604
618
 
605
619
  normalized_endpoint = normalize_endpoint(endpoint)
606
620
 
607
- if (
608
- normalized_endpoint in client_side_endpoints_gcs
609
- and tickers is not None
610
- and frequency is None
611
- ):
612
- verbose_mode.vprint("Grabbing gcs client side")
613
- _query_cache[cache_key] = client_side_frame(
614
- normalized_endpoint, tickers, columns, start_date, end_date
615
- )
616
- 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
+
617
636
 
618
- elif (
619
- normalized_endpoint in client_side_endpoints_s3
620
- and tickers is not None
621
- and frequency is None
622
- ):
623
- verbose_mode.vprint("Grabbing s3 client side")
624
- _query_cache[cache_key] = load_frame_s3(
625
- normalized_endpoint, tickers, columns, start_date, end_date)
626
- return _query_cache[cache_key]
627
637
  try:
628
638
  if tickers is not None and not is_all(tickers):
629
639
  params = ticker_mapper(params,verbose)
@@ -7,7 +7,7 @@ from pyarrow.fs import S3FileSystem as S3File
7
7
  import s3fs
8
8
 
9
9
 
10
- def get_s3fs_filesystem_pickle(storage_provider: str, verbose: bool = False):
10
+ def get_s3_filesystem_pickle(storage_provider: str, verbose: bool = False):
11
11
  """
12
12
  Retrieve the S3FileSystem for the specified storage provider using pickle.
13
13
 
@@ -70,8 +70,8 @@ def get_s3fs_filesystem_json(storage_provider: str, verbose: bool = False):
70
70
  print(f"Successfully retrieved S3 credentials for {storage_provider}")
71
71
 
72
72
  s3fsa = s3fs.S3FileSystem(
73
- access_key=cred['access_key'],
74
- secret_key=cred['secret_key'],
73
+ key=cred['access_key'],
74
+ secret=cred['secret_key'],
75
75
  client_kwargs={'endpoint_url': cred['endpoint']}
76
76
  )
77
77
 
@@ -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
+
@@ -1,206 +0,0 @@
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
-
9
-
10
- # Try to import CustomDataFrame, use regular DataFrame if not available
11
- try:
12
- from sovai.extensions.pandas_extensions import CustomDataFrame
13
- HAS_CUSTOM_DATAFRAME = True
14
- except ImportError:
15
- HAS_CUSTOM_DATAFRAME = False
16
- CustomDataFrame = pd.DataFrame # Fallback to regular DataFrame
17
-
18
-
19
- @lru_cache(maxsize=2)
20
- def get_cached_s3_filesystem(storage_provider):
21
- return authentication.get_s3_filesystem_pickle(storage_provider, verbose=True)
22
-
23
- def process_partition(storage_provider, base_path, identifier_column, identifier=None, columns=None, filters=None):
24
- s3 = get_cached_s3_filesystem(storage_provider)
25
-
26
- if identifier:
27
- base_path += f"/{identifier_column}={identifier}"
28
-
29
- dataset = ds.dataset(base_path, filesystem=s3, format='parquet')
30
-
31
- if filters:
32
- operator_map = {
33
- '==': lambda a, b: a == b,
34
- '!=': lambda a, b: a != b,
35
- '<': lambda a, b: a < b,
36
- '<=': lambda a, b: a <= b,
37
- '>': lambda a, b: a > b,
38
- '>=': lambda a, b: a >= b,
39
- 'in': lambda a, b: a.isin(b),
40
- 'not in': lambda a, b: ~a.isin(b),
41
- 'like': lambda a, b: a.match_substring(b),
42
- }
43
- schema = dataset.schema
44
- filter_expr = None
45
- for col, op, val in filters:
46
- if op not in operator_map:
47
- raise ValueError(f"Unsupported operator '{op}' in filters.")
48
- field = ds.field(col)
49
- field_type = schema.field(col).type
50
- if pa.types.is_timestamp(field_type) or pa.types.is_date(field_type):
51
- val = pd.to_datetime(val)
52
- elif pa.types.is_integer(field_type):
53
- val = int(val)
54
- elif pa.types.is_floating(field_type):
55
- val = float(val)
56
- condition = operator_map[op](field, val)
57
- filter_expr = condition if filter_expr is None else filter_expr & condition
58
- else:
59
- filter_expr = None
60
-
61
- table = dataset.to_table(columns=columns, filter=filter_expr, use_threads=True)
62
- df = table.to_pandas(use_threads=True)
63
-
64
- if identifier:
65
- df[identifier_column] = identifier
66
-
67
- df = df[[identifier_column] + [col for col in df.columns if col != identifier_column]]
68
-
69
-
70
- return df
71
-
72
- import numpy as np
73
-
74
- def client_side_s3_frame(config, identifiers, columns, start_date, end_date):
75
- storage_provider = "digitalocean" # Default to DigitalOcean
76
- base_path = config["storage_provider"][storage_provider]
77
- identifier_column = config.get("identifier_column")
78
-
79
- filters = []
80
- if start_date:
81
- filters.append(('date', '>=', start_date))
82
- if end_date:
83
- filters.append(('date', '<=', end_date))
84
-
85
- if not identifiers:
86
- # Load the entire database
87
- return process_partition(storage_provider, base_path, identifier_column, columns=columns, filters=filters)
88
- else:
89
- identifiers = map_identifier(identifiers, input_identifier='ticker', output_identifier=identifier_column)
90
-
91
- # Use numpy.isscalar to check if identifiers is a single value
92
- if np.isscalar(identifiers):
93
- # Single identifier
94
- identifier = identifiers
95
- return process_partition(storage_provider, base_path, identifier_column, identifier=identifier, columns=columns, filters=filters)
96
-
97
- # Multiple identifiers
98
- with ThreadPoolExecutor() as executor:
99
- futures = []
100
- for identifier in identifiers:
101
- futures.append(executor.submit(process_partition, storage_provider, base_path, identifier_column, identifier, columns, filters))
102
-
103
- results = []
104
- for future in as_completed(futures):
105
- results.append(future.result())
106
-
107
- df = pd.concat(results, ignore_index=True)
108
- return df
109
-
110
- # if 'filing_date' in df.columns:
111
- # df['date'] = pd.to_datetime(df['filing_date'])
112
- # elif 'date' not in df.columns:
113
- # raise ValueError("Neither 'filing_date' nor 'date' column found in the data")
114
-
115
- # df.set_index([identifier_column, 'date'], inplace=True)
116
- # df.sort_index(inplace=True)
117
-
118
- return df
119
-
120
- import pandas as pd
121
- import numpy as np
122
-
123
- def map_identifier(input_values, input_identifier='ticker', output_identifier='cik', trailing_zero_removal=True):
124
- """
125
- Map from one identifier to another using the df_codes DataFrame.
126
- Handles string, single-item list, and multiple-item lists as input.
127
- Uses vectorization for improved performance.
128
-
129
- :param input_values: The input identifier value(s) to map from (string, list, or array-like)
130
- :param input_identifier: The type of input identifier (default: 'ticker')
131
- :param output_identifier: The type of output identifier (default: 'cik')
132
- :param trailing_zero_removal: If True, convert CIK to int (default: True)
133
- :return: Mapped identifier value(s), same type as input
134
- """
135
- df_codes = pd.read_parquet("data/codes.parq")
136
-
137
- # Check if the identifier columns exist in df_codes
138
- if input_identifier not in df_codes.columns or output_identifier not in df_codes.columns:
139
- raise ValueError(f"Identifier '{input_identifier}' or '{output_identifier}' not found in the DataFrame")
140
-
141
- # Convert input to numpy array for consistent handling
142
- if isinstance(input_values, str):
143
- input_array = np.array([input_values])
144
- input_is_string = True
145
- else:
146
- input_array = np.array(input_values)
147
- input_is_string = False
148
-
149
- # Ensure all input values are strings
150
- input_array = np.char.strip(input_array.astype(str))
151
-
152
- # Create a dictionary for fast lookup
153
- mapping_dict = dict(zip(df_codes[input_identifier], df_codes[output_identifier]))
154
-
155
- # Vectorized mapping
156
- result = np.vectorize(mapping_dict.get)(input_array)
157
-
158
- # Convert to int if trailing_zero_removal is True and output is CIK
159
- if trailing_zero_removal and output_identifier.lower() == 'cik':
160
- result = np.where(result == None, 0, result) # Replace None with 0
161
- result = result.astype(np.int32) # Convert to int64
162
-
163
- # Return result in the same format as input
164
- if input_is_string:
165
- return result[0]
166
- elif len(input_array) == 1:
167
- return result[0]
168
- else:
169
- return result.tolist()
170
-
171
-
172
- def load_frame_s3(endpoint, tickers=None, columns=None, start_date=None, end_date=None):
173
-
174
- endpoint_config = {
175
-
176
- "patents/applications": {
177
- "storage_provider": {
178
- "digitalocean": "sovai/sovai-patents-export/applications",
179
- "wasabi": "sovai-patents-export/applications"
180
- },
181
- "identifier_columns": ["date_partitioned", "ticker_partitioned"],
182
- "partitioned": True,
183
- },
184
-
185
- }
186
-
187
- if endpoint not in endpoint_config:
188
- raise ValueError(f"Invalid endpoint: {endpoint}")
189
-
190
- config = endpoint_config[endpoint]
191
-
192
-
193
- df_frame = client_side_s3_frame(
194
- config,
195
- tickers,
196
- columns,
197
- start_date,
198
- end_date
199
- )
200
-
201
-
202
- if HAS_CUSTOM_DATAFRAME:
203
- return CustomDataFrame(df_frame)
204
- else:
205
- return df_frame # Returns a regular pandas DataFrame if CustomDataFrame is not available
206
-
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