sovai 0.2.17__tar.gz → 0.2.18__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 (92) hide show
  1. {sovai-0.2.17 → sovai-0.2.18}/PKG-INFO +1 -1
  2. {sovai-0.2.17 → sovai-0.2.18}/pyproject.toml +1 -1
  3. {sovai-0.2.17 → sovai-0.2.18}/sovai/__init__.py +5 -3
  4. sovai-0.2.18/sovai/extensions/ask_df_llm.py +311 -0
  5. sovai-0.2.18/sovai/extensions/filter_df.py +513 -0
  6. {sovai-0.2.17 → sovai-0.2.18}/sovai/extensions/pairwise.py +34 -9
  7. {sovai-0.2.17 → sovai-0.2.18}/sovai/extensions/pandas_extensions.py +32 -2
  8. {sovai-0.2.17 → sovai-0.2.18}/sovai/get_data.py +158 -0
  9. sovai-0.2.18/sovai/get_plots.py +225 -0
  10. {sovai-0.2.17 → sovai-0.2.18}/sovai/get_reports.py +13 -2
  11. {sovai-0.2.17 → sovai-0.2.18}/sovai/get_tools.py +10 -0
  12. {sovai-0.2.17 → sovai-0.2.18}/sovai/plots/accounting/accounting_plots.py +3 -3
  13. {sovai-0.2.17 → sovai-0.2.18}/sovai/plots/bankruptcy/bankruptcy_plots.py +21 -14
  14. {sovai-0.2.17 → sovai-0.2.18}/sovai/plots/corp_risk/corp_risk_plots.py +23 -2
  15. {sovai-0.2.17 → sovai-0.2.18}/sovai/plots/earnings_surprise/earnings_surprise_plots.py +2 -1
  16. {sovai-0.2.17 → sovai-0.2.18}/sovai/plots/insider/insider_plots.py +5 -5
  17. {sovai-0.2.17 → sovai-0.2.18}/sovai/plots/institutional/institutional_plots.py +9 -34
  18. {sovai-0.2.17 → sovai-0.2.18}/sovai/plots/news/news_plots.py +13 -25
  19. {sovai-0.2.17 → sovai-0.2.18}/sovai/plots/ratios/ratios_plots.py +2 -1
  20. {sovai-0.2.17 → sovai-0.2.18}/sovai/reports/accounting/accounting_balance_sheet.py +6 -2
  21. {sovai-0.2.17 → sovai-0.2.18}/sovai/reports/general/general_plots.py +3 -1
  22. sovai-0.2.18/sovai/tools/sec/llm_code_generator.py +101 -0
  23. {sovai-0.2.17 → sovai-0.2.18}/sovai/utils/client_side.py +51 -12
  24. {sovai-0.2.17 → sovai-0.2.18}/sovai/utils/file_management.py +35 -0
  25. {sovai-0.2.17 → sovai-0.2.18}/sovai/utils/helpers.py +2 -0
  26. sovai-0.2.18/sovai/utils/port_manager.py +25 -0
  27. sovai-0.2.17/sovai/get_plots.py +0 -201
  28. {sovai-0.2.17 → sovai-0.2.18}/LICENSE +0 -0
  29. {sovai-0.2.17 → sovai-0.2.18}/README.md +0 -0
  30. {sovai-0.2.17 → sovai-0.2.18}/sovai/api_config.py +0 -0
  31. {sovai-0.2.17 → sovai-0.2.18}/sovai/assets/dropdown.css +0 -0
  32. {sovai-0.2.17 → sovai-0.2.18}/sovai/assets/features_mapping.parq +0 -0
  33. {sovai-0.2.17 → sovai-0.2.18}/sovai/basic_auth.py +0 -0
  34. {sovai-0.2.17 → sovai-0.2.18}/sovai/computations/__init__.py +0 -0
  35. {sovai-0.2.17 → sovai-0.2.18}/sovai/computations/functions.py +0 -0
  36. {sovai-0.2.17 → sovai-0.2.18}/sovai/errors/__init__.py +0 -0
  37. {sovai-0.2.17 → sovai-0.2.18}/sovai/errors/sovai_errors.py +0 -0
  38. {sovai-0.2.17 → sovai-0.2.18}/sovai/extensions/__init__.py +0 -0
  39. {sovai-0.2.17 → sovai-0.2.18}/sovai/extensions/anomalies.py +0 -0
  40. {sovai-0.2.17 → sovai-0.2.18}/sovai/extensions/change_point_generator.py +0 -0
  41. {sovai-0.2.17 → sovai-0.2.18}/sovai/extensions/clustering.py +0 -0
  42. {sovai-0.2.17 → sovai-0.2.18}/sovai/extensions/core_kshape.py +0 -0
  43. {sovai-0.2.17 → sovai-0.2.18}/sovai/extensions/cum_sum_torch.py +0 -0
  44. {sovai-0.2.17 → sovai-0.2.18}/sovai/extensions/cusum.py +0 -0
  45. {sovai-0.2.17 → sovai-0.2.18}/sovai/extensions/dimensionality_reduction.py +0 -0
  46. {sovai-0.2.17 → sovai-0.2.18}/sovai/extensions/feature_extraction.py +0 -0
  47. {sovai-0.2.17 → sovai-0.2.18}/sovai/extensions/feature_importance.py +0 -0
  48. {sovai-0.2.17 → sovai-0.2.18}/sovai/extensions/feature_neutralizer.py +0 -0
  49. {sovai-0.2.17 → sovai-0.2.18}/sovai/extensions/fractional_differencing.py +0 -0
  50. {sovai-0.2.17 → sovai-0.2.18}/sovai/extensions/nowcasting.py +0 -0
  51. {sovai-0.2.17 → sovai-0.2.18}/sovai/extensions/parallel_functions.py +0 -0
  52. {sovai-0.2.17 → sovai-0.2.18}/sovai/extensions/pfa_feature_selector.py +0 -0
  53. {sovai-0.2.17 → sovai-0.2.18}/sovai/extensions/regime_change.py +0 -0
  54. {sovai-0.2.17 → sovai-0.2.18}/sovai/extensions/regime_change_pca.py +0 -0
  55. {sovai-0.2.17 → sovai-0.2.18}/sovai/extensions/shapley_global_importance.py +0 -0
  56. {sovai-0.2.17 → sovai-0.2.18}/sovai/extensions/shapley_importance.py +0 -0
  57. {sovai-0.2.17 → sovai-0.2.18}/sovai/extensions/signal_evaluation.py +0 -0
  58. {sovai-0.2.17 → sovai-0.2.18}/sovai/extensions/technical_indicators.py +0 -0
  59. {sovai-0.2.17 → sovai-0.2.18}/sovai/extensions/time_decomposition.py +0 -0
  60. {sovai-0.2.17 → sovai-0.2.18}/sovai/extensions/weight_optimization.py +0 -0
  61. {sovai-0.2.17 → sovai-0.2.18}/sovai/get_compute.py +0 -0
  62. {sovai-0.2.17 → sovai-0.2.18}/sovai/plots/__init__.py +0 -0
  63. {sovai-0.2.17 → sovai-0.2.18}/sovai/plots/accounting/__init__.py +0 -0
  64. {sovai-0.2.17 → sovai-0.2.18}/sovai/plots/allocation/__init__.py +0 -0
  65. {sovai-0.2.17 → sovai-0.2.18}/sovai/plots/allocation/allocation_plots.py +0 -0
  66. {sovai-0.2.17 → sovai-0.2.18}/sovai/plots/bankruptcy/__init__.py +0 -0
  67. {sovai-0.2.17 → sovai-0.2.18}/sovai/plots/breakout/__init__.py +0 -0
  68. {sovai-0.2.17 → sovai-0.2.18}/sovai/plots/breakout/breakout_plots.py +0 -0
  69. {sovai-0.2.17 → sovai-0.2.18}/sovai/plots/corp_risk/__init__.py +0 -0
  70. {sovai-0.2.17 → sovai-0.2.18}/sovai/plots/earnings_surprise/__init__.py +0 -0
  71. {sovai-0.2.17 → sovai-0.2.18}/sovai/plots/insider/__init__.py +0 -0
  72. {sovai-0.2.17 → sovai-0.2.18}/sovai/plots/institutional/__init__.py +0 -0
  73. {sovai-0.2.17 → sovai-0.2.18}/sovai/plots/news/__init__.py +0 -0
  74. {sovai-0.2.17 → sovai-0.2.18}/sovai/plots/ratios/__init__.py +0 -0
  75. {sovai-0.2.17 → sovai-0.2.18}/sovai/reports/accounting/__inti__.py +0 -0
  76. {sovai-0.2.17 → sovai-0.2.18}/sovai/reports/bankruptcy/__inti__.py +0 -0
  77. {sovai-0.2.17 → sovai-0.2.18}/sovai/reports/bankruptcy/bankruptcy_monthly_top.py +0 -0
  78. {sovai-0.2.17 → sovai-0.2.18}/sovai/reports/general/__inti__.py +0 -0
  79. {sovai-0.2.17 → sovai-0.2.18}/sovai/reports/news/__inti__.py +0 -0
  80. {sovai-0.2.17 → sovai-0.2.18}/sovai/reports/news/news_econometric_analysis.py +0 -0
  81. {sovai-0.2.17 → sovai-0.2.18}/sovai/studies/nowcasting.py +0 -0
  82. {sovai-0.2.17 → sovai-0.2.18}/sovai/token_auth.py +0 -0
  83. {sovai-0.2.17 → sovai-0.2.18}/sovai/tools/sec/__inti__.py +0 -0
  84. {sovai-0.2.17 → sovai-0.2.18}/sovai/tools/sec/sec_10_k_8_k_filings.py +0 -0
  85. {sovai-0.2.17 → sovai-0.2.18}/sovai/tools/sec/sec_edgar_search.py +0 -0
  86. {sovai-0.2.17 → sovai-0.2.18}/sovai/utils/__init__.py +0 -0
  87. {sovai-0.2.17 → sovai-0.2.18}/sovai/utils/converter.py +0 -0
  88. {sovai-0.2.17 → sovai-0.2.18}/sovai/utils/datetime_formats.py +0 -0
  89. {sovai-0.2.17 → sovai-0.2.18}/sovai/utils/get_tickers.py +0 -0
  90. {sovai-0.2.17 → sovai-0.2.18}/sovai/utils/pandas_extensions copy.py +0 -0
  91. {sovai-0.2.17 → sovai-0.2.18}/sovai/utils/plot.py +0 -0
  92. {sovai-0.2.17 → sovai-0.2.18}/sovai/utils/stream.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: sovai
3
- Version: 0.2.17
3
+ Version: 0.2.18
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.17"
3
+ version = "0.2.18"
4
4
  description = "python for asset management"
5
5
  authors = ["Derek Snow <d.snow@outlook.com>"]
6
6
  license = "Proprietary License"
@@ -5,20 +5,22 @@ from .api_config import read_key, save_key, ApiConfig
5
5
  from .get_data import data
6
6
  from .basic_auth import basic_auth
7
7
  from .token_auth import token_auth
8
- from .utils.file_management import save_or_update_tickers
8
+ from .utils.file_management import save_or_update_tickers, save_or_update_codes
9
9
 
10
10
  __version__ = "0.1.25"
11
11
 
12
12
  # Call the function to ensure tickers data is up-to-date
13
13
  save_or_update_tickers()
14
14
 
15
+ save_or_update_codes()
16
+
15
17
  try:
16
18
  from .get_plots import plot
17
19
  from .get_reports import report
18
20
  from .get_compute import compute
19
21
  from .studies.nowcasting import nowcast
20
22
  from .extensions.pandas_extensions import CustomDataFrame as extension
21
- from .get_tools import sec_search, sec_filing
23
+ from .get_tools import sec_search, sec_filing, code
22
24
  HAS_FULL_INSTALL = True
23
25
  except ImportError:
24
26
  HAS_FULL_INSTALL = False
@@ -26,4 +28,4 @@ except ImportError:
26
28
  __all__ = ['read_key', 'save_key', 'ApiConfig', 'data', 'basic_auth', 'token_auth']
27
29
 
28
30
  if HAS_FULL_INSTALL:
29
- __all__ += ['plot', 'report', 'compute', 'nowcast', 'extension', 'sec_search', 'sec_filing']
31
+ __all__ += ['plot', 'report', 'compute', 'nowcast', 'extension', 'sec_search', 'sec_filing', 'code']
@@ -0,0 +1,311 @@
1
+ import pandas as pd
2
+ import openai
3
+ from textwrap import dedent
4
+ import re
5
+ from typing import Dict, Any, List, Union, Tuple
6
+ import io
7
+ from sovai.tools.sec.llm_code_generator import get_openai_key
8
+ import logging
9
+ from rapidfuzz import process, fuzz
10
+ import time
11
+
12
+ # Add these lines at the beginning of the file
13
+ logging.getLogger("httpx").setLevel(logging.WARNING)
14
+
15
+ _ask_cache: Dict[str, Any] = {}
16
+
17
+ verbose = False
18
+ mutable = False
19
+ models = ['gpt-3.5-turbo', 'gpt-4']
20
+ completion_config = {}
21
+
22
+ def sample_unique_values(df, max_samples=5):
23
+ sample_data = {}
24
+ for column in df.columns:
25
+ unique_values = df[column].dropna().unique()
26
+ sample = unique_values[:max_samples].tolist()
27
+ sample_data[column] = sample
28
+ return sample_data
29
+
30
+ def format_sample_data(sample_data):
31
+ formatted_data = "Sample data from the DataFrame:\n\n"
32
+ for column, values in sample_data.items():
33
+ formatted_data += f"{column}:\n"
34
+ for value in values:
35
+ formatted_data += f" - {value}\n"
36
+ formatted_data += "\n"
37
+ return formatted_data
38
+
39
+ def find_best_match(query: str, choices: List[str], threshold: int = 80) -> Union[str, None]:
40
+ best_match = process.extractOne(query, choices, scorer=fuzz.ratio, score_cutoff=threshold)
41
+ return best_match[0] if best_match else None
42
+
43
+ def parse_value(value: str) -> Union[float, int]:
44
+ multipliers = {'k': 1e3, 'm': 1e6, 'b': 1e9}
45
+ if value.endswith('%'):
46
+ return float(value.rstrip('%')) / 100
47
+ for suffix, multiplier in multipliers.items():
48
+ if value.lower().endswith(suffix):
49
+ return float(value[:-1]) * multiplier
50
+ return float(value)
51
+
52
+ def create_standard_query(condition: str, df: pd.DataFrame) -> Tuple[str, str]:
53
+ pattern = r'(\w+)\s*([<>=]+|like|contains)\s*(.+)'
54
+ match = re.match(pattern, condition, re.IGNORECASE)
55
+ if not match:
56
+ raise ValueError(f"Invalid condition format: {condition}")
57
+
58
+ column, operator, value = match.groups()
59
+ best_match = find_best_match(column, df.columns.tolist())
60
+ if not best_match:
61
+ raise ValueError(f"No close match found for column: {column}")
62
+
63
+ original_column = column
64
+ column = best_match
65
+
66
+ operator = operator.lower().strip()
67
+ value = value.strip()
68
+
69
+ is_string_type = df[column].dtype == 'object'
70
+
71
+ if operator in ['like', 'contains'] or (operator == '=' and is_string_type):
72
+ query = f"`{column}`.str.contains('{value}', case=False, na=False)"
73
+ display_operator = 'like'
74
+ else:
75
+ parsed_value = parse_value(value)
76
+ query = f"`{column}` {operator} {parsed_value}"
77
+ display_operator = operator
78
+
79
+ display_condition = f"{column} ({original_column}) {display_operator} {value}" if column != original_column else condition
80
+ return query, display_condition
81
+
82
+ def is_simple_condition(condition: str) -> bool:
83
+ simple_pattern = r'\w+\s*([<>=]+|like|contains)\s*.+'
84
+ return bool(re.match(simple_pattern, condition, re.IGNORECASE))
85
+
86
+ def split_conditions(condition: str) -> List[str]:
87
+ pattern = r'\s+(?:and|or)\s+(?![^()]*\))'
88
+ conditions = re.split(pattern, condition, flags=re.IGNORECASE)
89
+ return [cond.strip() for cond in conditions if cond.strip()]
90
+
91
+ class Ask:
92
+ def __init__(self, *, verbose=None, mutable=None):
93
+ self.verbose = verbose if verbose is not None else globals().get('verbose', False)
94
+ self.mutable = mutable if mutable is not None else globals().get('mutable', False)
95
+
96
+ @staticmethod
97
+ def _fill_template(template, **kw):
98
+ result = dedent(template.lstrip('\n').rstrip())
99
+ for k, v in kw.items():
100
+ result = result.replace(f'{{{k}}}', str(v))
101
+ m = re.match(r'\{[a-zA-Z0-9_]*\}', result)
102
+ if m:
103
+ raise Exception(f'Expected variable: {m.group(0)}')
104
+ return result
105
+
106
+ def _get_prompt(self, goal, arg):
107
+ if isinstance(arg, pd.DataFrame):
108
+ buf = io.StringIO()
109
+ arg.info(buf=buf)
110
+ df_info = buf.getvalue()
111
+ columns = ", ".join(arg.columns)
112
+ sample_data = sample_unique_values(arg)
113
+ formatted_sample_data = format_sample_data(sample_data)
114
+ else:
115
+ raise ValueError("Input must be a pandas DataFrame for filtering operations.")
116
+
117
+ return self._fill_template(TEMPLATE, df_info=df_info.strip(), columns=columns, sample_data=formatted_sample_data, goal=goal.strip())
118
+
119
+ def _run_prompt(self, prompt):
120
+ global _ask_cache
121
+ if prompt in _ask_cache:
122
+ return _ask_cache[prompt]
123
+
124
+ openai.api_key = get_openai_key()
125
+
126
+ for model in models:
127
+ try:
128
+ completion = openai.chat.completions.create(
129
+ model=model,
130
+ messages=[
131
+ {"role": "system", "content": "You are an AI assistant that writes Python code for DataFrame filtering operations."},
132
+ {"role": "user", "content": prompt},
133
+ ],
134
+ **completion_config,
135
+ )
136
+ result = completion.choices[0].message.content
137
+ _ask_cache[prompt] = result
138
+ return result
139
+ except Exception as e:
140
+ print(f"Error with model {model}: {str(e)}. Trying next model if available.")
141
+ if model == models[-1]:
142
+ raise Exception("All models failed to generate a response.")
143
+
144
+ raise Exception("Unexpected error: No models were tried.")
145
+
146
+ def _extract_code_block(self, text):
147
+ pattern = r'```(\s*(py|python)\s*\n)?([\s\S]*?)```'
148
+ m = re.search(pattern, text)
149
+ if not m:
150
+ return text
151
+ return m.group(3)
152
+
153
+ ## if you want verbose, print Source here.
154
+ def _eval(self, source, *args):
155
+ _args_ = args
156
+ scope = dict(_args_=args)
157
+ try:
158
+ exec(self._fill_template('''
159
+ import pandas as pd
160
+ {source}
161
+ _result_ = process(*_args_)
162
+ ''', source=source), scope)
163
+ result = scope['_result_']
164
+ if not isinstance(result, pd.DataFrame):
165
+ raise ValueError("The function did not return a DataFrame.")
166
+ if len(result) == 0:
167
+ print("Warning: Filtering resulted in an empty DataFrame. Returning original DataFrame.")
168
+ return args[0]
169
+ if len(result) == len(args[0]):
170
+ print("Warning: Filtering did not remove any rows. Check if the condition was applied correctly.")
171
+ return result
172
+ except Exception as e:
173
+ print(f"Error in filtering: {str(e)}. Returning original DataFrame.")
174
+ return args[0]
175
+
176
+ def _code(self, goal, arg):
177
+ prompt = self._get_prompt(goal, arg)
178
+ result = self._run_prompt(prompt)
179
+ if self.verbose:
180
+ print()
181
+ print(result)
182
+ return self._extract_code_block(result)
183
+
184
+ def code(self, *args):
185
+ print(self._code(*args))
186
+
187
+ def prompt(self, *args):
188
+ print(self._get_prompt(*args))
189
+
190
+ def __call__(self, goal, *args):
191
+ source = self._code(goal, *args)
192
+ return self._eval(source, *args)
193
+
194
+ def apply_condition_with_retry(self, df: pd.DataFrame, condition: str, max_retries: int = 3) -> pd.DataFrame:
195
+ original_len = len(df)
196
+
197
+ sub_conditions = split_conditions(condition)
198
+ if len(sub_conditions) > 1:
199
+ if self.verbose:
200
+ print(f"Detected multiple conditions: {sub_conditions}")
201
+ for sub_condition in sub_conditions:
202
+ df = self.apply_condition_with_retry(df, sub_condition, max_retries)
203
+ return df
204
+
205
+ if is_simple_condition(condition):
206
+ try:
207
+ query, display_condition = create_standard_query(condition, df)
208
+ filtered_df = df.query(query)
209
+ if len(filtered_df) < original_len:
210
+ if self.verbose:
211
+ print(f"Applied standard filter: {display_condition}")
212
+ return filtered_df
213
+ except Exception as e:
214
+ if self.verbose:
215
+ print(f"Standard filtering failed: {str(e)}. Falling back to GPT method.")
216
+
217
+ for attempt in range(max_retries):
218
+ try:
219
+ filtered_df = self(condition, df)
220
+ if isinstance(filtered_df, pd.DataFrame) and len(filtered_df) < original_len:
221
+ return filtered_df
222
+ elif self.verbose:
223
+ print(f"Attempt {attempt + 1}: GPT filtering didn't remove any rows. Retrying...")
224
+
225
+ alternative_conditions = [f"filter {condition}", f"select {condition}", f"find {condition}"]
226
+ for alt_condition in alternative_conditions:
227
+ try:
228
+ filtered_df = self(alt_condition, df)
229
+ if isinstance(filtered_df, pd.DataFrame) and len(filtered_df) < original_len:
230
+ return filtered_df
231
+ except Exception:
232
+ pass
233
+
234
+ columns_str = ", ".join(df.columns)
235
+ general_condition = f"Based on the columns {columns_str}, {condition}"
236
+ filtered_df = self(general_condition, df)
237
+ if isinstance(filtered_df, pd.DataFrame) and len(filtered_df) < original_len:
238
+ return filtered_df
239
+ except Exception as e:
240
+ if self.verbose:
241
+ print(f"Error on GPT attempt {attempt + 1}: {str(e)}")
242
+ time.sleep(1)
243
+
244
+ if self.verbose:
245
+ print("All filtering attempts failed. Returning original DataFrame.")
246
+ return df
247
+
248
+ if not hasattr(pd.DataFrame, 'ask'):
249
+ @pd.api.extensions.register_dataframe_accessor('ask')
250
+ class AskAccessor:
251
+ def __init__(self, pandas_obj):
252
+ self._validate(pandas_obj)
253
+ self._obj = pandas_obj
254
+
255
+ @staticmethod
256
+ def _validate(obj):
257
+ if not isinstance(obj, pd.DataFrame):
258
+ raise AttributeError("'ask' accessor is only available for DataFrames.")
259
+
260
+ def _ask(self, **kw):
261
+ return Ask(**kw)
262
+
263
+ def _data(self, **kw):
264
+ if not globals().get('mutable', False) and not kw.get('mutable'):
265
+ return self._obj.copy()
266
+ return self._obj
267
+
268
+ def __call__(self, goal, *args, **kw):
269
+ ask = self._ask(**kw)
270
+ data = self._data(**kw)
271
+ return ask(goal, data, *args)
272
+
273
+ def code(self, goal, *args, **kw):
274
+ ask = self._ask(**kw)
275
+ data = self._data(**kw)
276
+ return ask.code(goal, data, *args)
277
+
278
+ def prompt(self, goal, *args, **kw):
279
+ ask = self._ask(**kw)
280
+ data = self._data(**kw)
281
+ return ask.prompt(goal, data, *args)
282
+
283
+ def apply_condition_with_retry(self, condition, max_retries=3, **kw):
284
+ ask = self._ask(**kw)
285
+ data = self._data(**kw)
286
+ return ask.apply_condition_with_retry(data, condition, max_retries)
287
+ else:
288
+ pass
289
+
290
+ TEMPLATE = """
291
+ Write a Python function `process(df)` which takes a pandas DataFrame as input.
292
+ DataFrame information:
293
+ {df_info}
294
+
295
+ Available columns: {columns}
296
+
297
+ Sample data:
298
+ {sample_data}
299
+
300
+ This function should filter the DataFrame based on the following goal: {goal}
301
+
302
+ Important instructions:
303
+ 1. Only use columns that are listed in the 'Available columns'.
304
+ 2. The function must return a filtered DataFrame.
305
+ 3. If no filtering is possible or if the filtering results in an empty DataFrame, return the original DataFrame with a comment explaining why.
306
+ 4. Do not use any external libraries other than pandas.
307
+ 5. Ensure that your filtering logic is correct and doesn't inadvertently return an empty DataFrame.
308
+ 6. Only define the function without trying to call it!
309
+
310
+ Write the function in a Python code block with all necessary imports and no example usage.
311
+ """