markytools commited on
Commit
4970bec
·
1 Parent(s): cf43659

fixed chatbot LLM

Browse files
Files changed (4) hide show
  1. .gitignore +7 -1
  2. app.py +396 -167
  3. requirements.txt +12 -6
  4. sklearn_wine.csv +179 -0
.gitignore CHANGED
@@ -1,3 +1,9 @@
1
  .streamlit/
2
  app.ipynb
3
- .ipynb_checkpoints/
 
 
 
 
 
 
 
1
  .streamlit/
2
  app.ipynb
3
+ .ipynb_checkpoints/
4
+ app_local.py
5
+ .vscode/
6
+ ecommerce_duckdb.ipynb
7
+ openrouter_testings.ipynb
8
+ appBackup.py
9
+ prompt_instructions.txt
app.py CHANGED
@@ -2,22 +2,277 @@ import streamlit as st
2
  from tempfile import NamedTemporaryFile
3
 
4
  import pprint
 
5
  import os
6
- from dotenv import load_dotenv, find_dotenv
7
- import os
8
- from langchain_openai import ChatOpenAI
9
  from langchain_community.document_loaders import PyPDFLoader
10
  from langchain.document_loaders.csv_loader import CSVLoader
11
  from langchain.document_loaders import WebBaseLoader
 
12
 
13
  import pandas as pd
14
  import numpy as np
15
  import pprint
 
 
16
 
17
  defaultGoogleURL = "https://www.google.com/search?q=google+earnings"
 
 
 
 
18
  OPEN_ROUTER_KEY = st.secrets["OPEN_ROUTER_KEY"]
19
- OPEN_ROUTER_MODEL = "meta-llama/llama-3.1-70b-instruct:free"
20
- isPswdValid = False # Set to True to temporarily disable password checking
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
21
 
22
  def pretty_print_columns(text):
23
  """
@@ -31,172 +286,146 @@ def pretty_print_columns(text):
31
  """
32
  return " ".join([line.strip() for line in text.splitlines() if line.strip()])
33
 
34
- try:
35
- pswdVal = st.experimental_get_query_params()['pwd'][0]
36
- if pswdVal==st.secrets["PSWD"]:
37
- isPswdValid = True
38
- except:
39
- pass
40
-
41
- if not isPswdValid:
42
- st.write("Invalid Password")
43
- else:
44
- radioButtonList = ["E-commerce CSV (https://www.kaggle.com/datasets/mervemenekse/ecommerce-dataset)",
45
- "Upload my own CSV",
46
- "Upload my own PDF",
47
- f"URL Chat with Google's Latest Earnings ({defaultGoogleURL})",
48
- "Enter my own URL"]
49
-
50
- # Add some designs to the radio buttons
51
- st.markdown("""
52
- <style>
53
- .stRadio {
54
- padding: 10px;
55
- border-radius: 5px;
56
- background-color: #f5f5f5;
57
- }
58
-
59
- .stRadio input[type="radio"] {
60
- position: absolute;
61
- opacity: 0;
62
- cursor: pointer;
63
- }
64
-
65
- .stRadio label {
66
- display: flex;
67
- justify-content: center;
68
- align-items: center;
69
- cursor: pointer;
70
- font-size: 16px;
71
- color: #333;
72
- }
73
-
74
- .stRadio label:hover {
75
- color: #000;
76
- }
77
-
78
- .stRadio.st-selected input[type="radio"] ~ label {
79
- color: #000;
80
- background-color: #d9d9d9;
81
- }
82
- </style>
83
- """, unsafe_allow_html=True)
84
-
85
- genre = st.radio(
86
- "Tired of reading your files? Chat with it using AI! Choose dataset to finetune", radioButtonList, index=0
87
- )
88
-
89
- # Initialize language model
90
- load_dotenv(find_dotenv()) # read local .env file
91
- llm = ChatOpenAI(model=OPEN_ROUTER_MODEL, temperature=0.1, openai_api_key=OPEN_ROUTER_KEY, openai_api_base="https://openrouter.ai/api/v1")
92
-
93
- pdfCSVURLText = ""
94
- if genre==radioButtonList[1]:
95
- pdfCSVURLText = "CSV"
96
- exampleQuestion = "What are the data columns?"
97
- elif genre==radioButtonList[2]:
98
- pdfCSVURLText = "PDF"
99
- exampleQuestion = "Can you summarize the contents?"
100
- elif genre==radioButtonList[3]:
101
- pdfCSVURLText = "URL"
102
- exampleQuestion = "What is Google's latest earnings?"
103
- elif genre==radioButtonList[4]:
104
- pdfCSVURLText = "URL"
105
- exampleQuestion = "Can you summarize the contents?"
106
- else: # Default, E-commerce CSV
107
- pdfCSVURLText = "CSV"
108
- exampleQuestion = "Question1: What was the most sold item? Question2: What was the most common payment?"
109
- loader = CSVLoader(file_path='EcommerceDataset.csv')
110
- csv_data = loader.load()
111
-
112
- isCustomURL = genre==radioButtonList[4]
113
- urlInput = st.text_input('Enter your own URL', '', placeholder=f"Type your URL here (e.g. {defaultGoogleURL})", disabled=not isCustomURL)
114
-
115
- isCustomPDF = genre==radioButtonList[1] or genre==radioButtonList[2]
116
- uploaded_file = st.file_uploader(f"Upload your own {pdfCSVURLText} here", type=pdfCSVURLText.lower(), disabled=not isCustomPDF)
117
- uploadedFilename = ""
118
- if uploaded_file is not None:
119
- with NamedTemporaryFile(dir='.', suffix=f'.{pdfCSVURLText.lower()}') as f:
120
- f.write(uploaded_file.getbuffer())
121
- uploadedFilename = f.name
122
- if genre==radioButtonList[1]: # Custom CSV Upload
123
- loader = CSVLoader(file_path=uploadedFilename)
124
- csv_data = loader.load()
125
- elif genre==radioButtonList[2]: # Custom PDF Upload
126
- loader = PyPDFLoader(uploadedFilename)
127
- pdf_pages = loader.load_and_split()
128
-
129
- enableChatBox = False
130
  if genre==radioButtonList[1]: # Custom CSV Upload
131
- enableChatBox = uploadedFilename[-4:]==".csv"
 
 
 
132
  elif genre==radioButtonList[2]: # Custom PDF Upload
133
- enableChatBox = uploadedFilename[-4:]==".pdf"
134
- elif genre==radioButtonList[3]: # Google Alphabet URL Earnings Report
135
- enableChatBox = True
136
- elif genre==radioButtonList[4]: # Custom URL
137
- enableChatBox = True
138
- else: # E-commerce CSV
139
- enableChatBox = True
140
-
141
- chatTextStr = st.text_input(f'Ask me anything about this {pdfCSVURLText}', '', placeholder=f"Type here (e.g. {exampleQuestion})", disabled=not enableChatBox)
142
- chatWithPDFButton = "CLICK HERE TO START CHATTING"
143
- if st.button(chatWithPDFButton, disabled=not enableChatBox and not chatTextStr): # Button Cliked
144
- if genre==radioButtonList[0]: # E-commerce CSV
145
- # Initializing the agent
146
- answer = llm.predict(f'''
147
- Do not reply with a python code.
148
- I have CSV file contents below:
149
-
150
- {str(csv_data)}
151
-
152
- {chatTextStr}
153
- ''')
154
- st.write(answer)
155
-
156
- elif genre==radioButtonList[1]: # Custom CSV Upload
157
- # Initializing the agent
158
- answer = llm.predict(f'''
159
- Do not reply with a python code.
160
- I have CSV file contents below:
161
-
162
- {str(csv_data)}
163
-
164
- {chatTextStr}
165
- ''')
166
- st.write(answer)
167
-
168
- elif genre==radioButtonList[2]: # Custom PDF Upload
169
- pdf_answer = llm.predict(f'''
170
- Do not reply with a python code.
171
- I have PDF file contents below:
172
-
173
- {str(pdf_pages)}
174
-
175
- {chatTextStr}
176
- ''')
177
- st.write(pdf_answer)
178
- elif genre==radioButtonList[3]: # Google Alphabet URL Earnings Report
179
- loader = WebBaseLoader(defaultGoogleURL)
180
- web_data = loader.load()
181
- answer = llm.predict(f'''
182
- Do not reply with a python code.
183
- I have website contents below:
184
 
185
- {str(web_data)}
 
 
 
 
 
 
 
 
 
 
186
 
187
- {chatTextStr}
188
- ''')
 
 
 
189
 
190
- st.write(answer)
191
- elif genre==radioButtonList[4]: # Custom URL
 
 
 
 
 
 
 
 
 
 
 
 
 
192
  loader = WebBaseLoader(urlInput)
193
  web_data = loader.load()
194
- answer = llm.predict(f'''
195
- Do not reply with a python code.
196
- I have website contents below:
197
-
198
- {str(web_data)}
199
-
200
- {chatTextStr}
201
- ''')
202
- st.write(answer)
 
2
  from tempfile import NamedTemporaryFile
3
 
4
  import pprint
5
+ import re
6
  import os
 
 
 
7
  from langchain_community.document_loaders import PyPDFLoader
8
  from langchain.document_loaders.csv_loader import CSVLoader
9
  from langchain.document_loaders import WebBaseLoader
10
+ import duckdb
11
 
12
  import pandas as pd
13
  import numpy as np
14
  import pprint
15
+ import requests
16
+ import json
17
 
18
  defaultGoogleURL = "https://www.google.com/search?q=google+earnings"
19
+ OPEN_ROUTER_MODEL = "meta-llama/llama-3.3-70b-instruct:free"
20
+ DEFAULT_ECOMMERCE_CSV = "EcommerceDataset.csv"
21
+
22
+ # Input for OpenRouter API Key
23
  OPEN_ROUTER_KEY = st.secrets["OPEN_ROUTER_KEY"]
24
+
25
+ if not OPEN_ROUTER_KEY:
26
+ st.warning("Please enter your OpenRouter API Key to proceed.")
27
+ st.stop()
28
+
29
+ def call_openrouter(content: str) -> str:
30
+ """Send a chat request to OpenRouter and return a safe string response."""
31
+ try:
32
+ response = requests.post(
33
+ url="https://openrouter.ai/api/v1/chat/completions",
34
+ headers={
35
+ "Authorization": f"Bearer {OPEN_ROUTER_KEY}",
36
+ "Content-Type": "application/json"
37
+ },
38
+ data=json.dumps({
39
+ "model": OPEN_ROUTER_MODEL,
40
+ "messages": [
41
+ {
42
+ "role": "user",
43
+ "content": content
44
+ }
45
+ ]
46
+ }),
47
+ timeout=60,
48
+ )
49
+ except Exception as exc:
50
+ return f"Request error: {exc}"
51
+
52
+ if not response.ok:
53
+ # Return status code plus body so the user knows what went wrong.
54
+ return f"Request failed ({response.status_code}): {response.text}"
55
+
56
+ try:
57
+ data = response.json()
58
+ except Exception as exc:
59
+ return f"Invalid JSON response: {exc} | body: {response.text}"
60
+
61
+ try:
62
+ return data["choices"][0]["message"]["content"]
63
+ except Exception:
64
+ return f"Unexpected response format: {data}"
65
+
66
+ def call_openrouter_messages(messages) -> str:
67
+ """Generic OpenRouter call that accepts a messages list."""
68
+ try:
69
+ response = requests.post(
70
+ url="https://openrouter.ai/api/v1/chat/completions",
71
+ headers={
72
+ "Authorization": f"Bearer {OPEN_ROUTER_KEY}",
73
+ "Content-Type": "application/json"
74
+ },
75
+ data=json.dumps({
76
+ "model": OPEN_ROUTER_MODEL,
77
+ "messages": messages
78
+ }),
79
+ timeout=60,
80
+ )
81
+ except Exception as exc:
82
+ return f"Request error: {exc}"
83
+
84
+ if not response.ok:
85
+ return f"Request failed ({response.status_code}): {response.text}"
86
+
87
+ try:
88
+ data = response.json()
89
+ except Exception as exc:
90
+ return f"Invalid JSON response: {exc} | body: {response.text}"
91
+
92
+ try:
93
+ return data["choices"][0]["message"]["content"]
94
+ except Exception:
95
+ return f"Unexpected response format: {data}"
96
+
97
+ def ask_llm(question: str, schema_text: str) -> str:
98
+ """Ask the model to generate a DuckDB SQL query for the given question and schema."""
99
+ messages = [
100
+ {
101
+ "role": "system",
102
+ "content": f"""
103
+ You are a data analyst.
104
+
105
+ You MUST use ONLY this table:
106
+ - Table name: data
107
+
108
+ Schema:
109
+ {schema_text}
110
+
111
+ Rules:
112
+ - Use ONLY table name "data"
113
+ - Return ONE valid DuckDB SQL query
114
+ - Do NOT explain
115
+ - Do NOT use markdown
116
+ """
117
+ },
118
+ {"role": "user", "content": question},
119
+ ]
120
+ return call_openrouter_messages(messages)
121
+
122
+ def explain_result(question: str, df: pd.DataFrame) -> str:
123
+ """Ask the model to explain the result set in plain language."""
124
+ try:
125
+ result_text = df.to_string(index=False)
126
+ except Exception:
127
+ result_text = str(df)
128
+
129
+ messages = [
130
+ {
131
+ "role": "system",
132
+ "content": """
133
+ You are a data analyst.
134
+
135
+ Given a user's question and a query result,
136
+ produce a concise, human-like explanation.
137
+
138
+ Rules:
139
+ - Do NOT mention SQL, databases, or tables
140
+ - Do NOT explain how the data was computed
141
+ - Be clear and business-friendly
142
+ """
143
+ },
144
+ {
145
+ "role": "user",
146
+ "content": f"""
147
+ Question:
148
+ {question}
149
+
150
+ Query Result:
151
+ {result_text}
152
+ """
153
+ },
154
+ ]
155
+ return call_openrouter_messages(messages)
156
+
157
+ def sanitize_dataframe(df: pd.DataFrame):
158
+ """Return a copy of df with column names sanitized for SQL identifiers."""
159
+ if df is None or not isinstance(df, pd.DataFrame):
160
+ return df, {}
161
+ rename_map = {}
162
+ used = set()
163
+ for col in df.columns:
164
+ new_col = re.sub(r"[^0-9a-zA-Z_]+", "_", str(col))
165
+ new_col = new_col.strip("_")
166
+ if re.match(r"^[0-9]", new_col):
167
+ new_col = f"col_{new_col}"
168
+ if not new_col:
169
+ new_col = "col"
170
+ base = new_col
171
+ idx = 1
172
+ while new_col in used:
173
+ new_col = f"{base}_{idx}"
174
+ idx += 1
175
+ used.add(new_col)
176
+ rename_map[col] = new_col
177
+ return df.rename(columns=rename_map), rename_map
178
+
179
+ def run_duckdb_qa(question: str, dataframe: pd.DataFrame) -> str:
180
+ """Generate SQL via LLM, run it on DuckDB, and explain the result."""
181
+ if not question.strip():
182
+ return "Please enter a question."
183
+ if dataframe is None or not isinstance(dataframe, pd.DataFrame):
184
+ return "No CSV data loaded."
185
+ clean_df, rename_map = sanitize_dataframe(dataframe)
186
+ con = duckdb.connect()
187
+ try:
188
+ con.register("data", clean_df)
189
+ schema_df = con.execute("DESCRIBE data").fetch_df()
190
+ schema_text = schema_df.to_string(index=False)
191
+ sql = ask_llm(question, schema_text)
192
+ if not isinstance(sql, str):
193
+ return f"Unexpected SQL response: {sql}"
194
+ sql = sql.strip().strip(";")
195
+ sql = re.sub(r"\bSTDEV\s*\(", "STDDEV(", sql, flags=re.IGNORECASE)
196
+ result_df = con.execute(sql).fetch_df()
197
+ except Exception as exc:
198
+ return f"SQL error: {exc}\nSQL used:\n{locals().get('sql', 'N/A')}"
199
+ finally:
200
+ con.close()
201
+
202
+ return explain_result(question, result_df)
203
+
204
+ def format_data_preview(data, max_chars: int = 12000) -> str:
205
+ """Return a trimmed, human-friendly preview to keep prompts under token limits."""
206
+ if data is None:
207
+ return "No data loaded."
208
+ try:
209
+ if isinstance(data, pd.DataFrame):
210
+ preview = data.head(20).to_csv(index=False)
211
+ elif isinstance(data, list):
212
+ chunks = []
213
+ for doc in data[:5]:
214
+ text = getattr(doc, "page_content", str(doc))
215
+ if len(text) > 1500:
216
+ text = text[:1500] + "...[truncated]"
217
+ chunks.append(text)
218
+ preview = "\n\n".join(chunks)
219
+ else:
220
+ preview = str(data)
221
+ except Exception as exc:
222
+ preview = f"Could not format data preview: {exc}"
223
+ if len(preview) > max_chars:
224
+ preview = preview[:max_chars] + "...[truncated]"
225
+ return preview
226
+
227
+ def summarize_csv(dataframe: pd.DataFrame) -> str:
228
+ """Build a compact summary (top items, payment mix) from a CSV DataFrame."""
229
+ if dataframe is None or not isinstance(dataframe, pd.DataFrame):
230
+ return ""
231
+ summary_lines = []
232
+
233
+ quantity_col = next((c for c in dataframe.columns if c.lower().startswith("quantity")), None)
234
+ desc_col = None
235
+ for candidate in ("Description", "Product", "Item", "Product_Name"):
236
+ if candidate in dataframe.columns:
237
+ desc_col = candidate
238
+ break
239
+ payment_col = next((c for c in dataframe.columns if "payment" in c.lower()), None)
240
+
241
+ if quantity_col and desc_col:
242
+ try:
243
+ top_items = (
244
+ dataframe.groupby(desc_col)[quantity_col]
245
+ .sum()
246
+ .sort_values(ascending=False)
247
+ .head(10)
248
+ )
249
+ summary_lines.append("Top items by quantity (sum):")
250
+ summary_lines.append(top_items.to_string())
251
+ except Exception as exc:
252
+ summary_lines.append(f"Could not compute top items: {exc}")
253
+
254
+ if payment_col:
255
+ try:
256
+ payment_counts = dataframe[payment_col].value_counts().head(10)
257
+ summary_lines.append("\nPayment method counts:")
258
+ summary_lines.append(payment_counts.to_string())
259
+ except Exception as exc:
260
+ summary_lines.append(f"Could not compute payment counts: {exc}")
261
+
262
+ return "\n".join(summary_lines)
263
+
264
+ def build_prompt(label: str, data, question: str, summary: str = "") -> str:
265
+ preview = format_data_preview(data)
266
+ summary_text = summary.strip()
267
+ summary_block = f"\nData summary:\n{summary_text}\n" if summary_text else ""
268
+ return f"""Do not reply with a python code.
269
+ Data preview ({label}, truncated to avoid context limits):
270
+
271
+ {preview}
272
+
273
+ {summary_block}
274
+ User question: {question}
275
+ """
276
 
277
  def pretty_print_columns(text):
278
  """
 
286
  """
287
  return " ".join([line.strip() for line in text.splitlines() if line.strip()])
288
 
289
+ radioButtonList = ["E-commerce CSV (https://www.kaggle.com/datasets/mervemenekse/ecommerce-dataset)",
290
+ "Upload my own CSV",
291
+ "Upload my own PDF",
292
+ f"URL Chat with Google's Latest Earnings ({defaultGoogleURL})",
293
+ "Enter my own URL"]
294
+
295
+ # Add some designs to the radio buttons
296
+ st.markdown("""
297
+ <style>
298
+ .stRadio {
299
+ padding: 10px;
300
+ border-radius: 5px;
301
+ background-color: #f5f5f5;
302
+ }
303
+
304
+ .stRadio input[type="radio"] {
305
+ position: absolute;
306
+ opacity: 0;
307
+ cursor: pointer;
308
+ }
309
+
310
+ .stRadio label {
311
+ display: flex;
312
+ justify-content: center;
313
+ align-items: center;
314
+ cursor: pointer;
315
+ font-size: 16px;
316
+ color: #333;
317
+ }
318
+
319
+ .stRadio label:hover {
320
+ color: #000;
321
+ }
322
+
323
+ .stRadio.st-selected input[type="radio"] ~ label {
324
+ color: #000;
325
+ background-color: #d9d9d9;
326
+ }
327
+ </style>
328
+ """, unsafe_allow_html=True)
329
+
330
+ genre = st.radio(
331
+ "Tired of reading your files? Chat with it using AI! Choose dataset to finetune", radioButtonList, index=0
332
+ )
333
+
334
+ pdfCSVURLText = ""
335
+ exampleQuestion = ""
336
+ csv_data = None
337
+ pdf_pages = None
338
+
339
+ if genre==radioButtonList[1]:
340
+ pdfCSVURLText = "CSV"
341
+ exampleQuestion = "What are the data columns?"
342
+ elif genre==radioButtonList[2]:
343
+ pdfCSVURLText = "PDF"
344
+ exampleQuestion = "Can you summarize the contents?"
345
+ elif genre==radioButtonList[3]:
346
+ pdfCSVURLText = "URL"
347
+ exampleQuestion = "What is Google's latest earnings?"
348
+ elif genre==radioButtonList[4]:
349
+ pdfCSVURLText = "URL"
350
+ exampleQuestion = "Can you summarize the contents?"
351
+ else: # Default, E-commerce CSV
352
+ pdfCSVURLText = "CSV"
353
+ exampleQuestion = "Question1: What was the most sold item? Question2: What was the most common payment?"
354
+ if os.path.exists(DEFAULT_ECOMMERCE_CSV):
355
+ try:
356
+ csv_data = pd.read_csv(DEFAULT_ECOMMERCE_CSV)
357
+ except Exception as exc:
358
+ st.warning(f"Problem loading {DEFAULT_ECOMMERCE_CSV} ({exc}). Falling back to a small sample dataset.")
359
+ if csv_data is None:
360
+ # Keep a tiny inline sample so the app still works even when the CSV is missing locally.
361
+ csv_data = pd.DataFrame(
362
+ [
363
+ {"InvoiceNo": "536365", "StockCode": "85123A", "Description": "White hanging heart", "Quantity": 6, "UnitPrice": 2.55, "Country": "United Kingdom"},
364
+ {"InvoiceNo": "536366", "StockCode": "71053", "Description": "White metal lantern", "Quantity": 6, "UnitPrice": 3.39, "Country": "United Kingdom"},
365
+ {"InvoiceNo": "536367", "StockCode": "84406B", "Description": "Pink mini hanging heart", "Quantity": 8, "UnitPrice": 1.65, "Country": "United Kingdom"},
366
+ ]
367
+ )
368
+ st.info(f"{DEFAULT_ECOMMERCE_CSV} not found. Using an inline sample instead. Upload your own CSV if you need the full dataset.")
369
+
370
+ isCustomURL = genre==radioButtonList[4]
371
+ urlInput = st.text_input('Enter your own URL', '', placeholder=f"Type your URL here (e.g. {defaultGoogleURL})", disabled=not isCustomURL)
372
+
373
+ isCustomUpload = genre==radioButtonList[1] or genre==radioButtonList[2]
374
+ uploaded_file = st.file_uploader(f"Upload your own {pdfCSVURLText} here", type=pdfCSVURLText.lower(), disabled=not isCustomUpload)
375
+ uploadedFilename = ""
376
+ if uploaded_file is not None:
 
 
 
 
 
 
 
 
377
  if genre==radioButtonList[1]: # Custom CSV Upload
378
+ try:
379
+ csv_data = pd.read_csv(uploaded_file)
380
+ except Exception as exc:
381
+ st.error(f"Could not read uploaded CSV: {exc}")
382
  elif genre==radioButtonList[2]: # Custom PDF Upload
383
+ with NamedTemporaryFile(dir='.', suffix=f'.{pdfCSVURLText.lower()}', delete=False) as f:
384
+ f.write(uploaded_file.getbuffer())
385
+ uploadedFilename = f.name
386
+ try:
387
+ loader = PyPDFLoader(uploadedFilename)
388
+ pdf_pages = loader.load_and_split()
389
+ except Exception as exc:
390
+ st.error(f"Could not read uploaded PDF: {exc}")
391
+ finally:
392
+ if uploadedFilename and os.path.exists(uploadedFilename):
393
+ os.remove(uploadedFilename)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
394
 
395
+ enableChatBox = False
396
+ if genre==radioButtonList[1]: # Custom CSV Upload
397
+ enableChatBox = isinstance(csv_data, pd.DataFrame)
398
+ elif genre==radioButtonList[2]: # Custom PDF Upload
399
+ enableChatBox = pdf_pages is not None
400
+ elif genre==radioButtonList[3]: # Google Alphabet URL Earnings Report
401
+ enableChatBox = True
402
+ elif genre==radioButtonList[4]: # Custom URL
403
+ enableChatBox = True
404
+ else: # E-commerce CSV
405
+ enableChatBox = True
406
 
407
+ chatTextStr = st.text_input(f'Ask me anything about this {pdfCSVURLText}', '', placeholder=f"Type here (e.g. {exampleQuestion})", disabled=not enableChatBox)
408
+ chatWithPDFButton = "CLICK HERE TO START CHATTING"
409
+ if st.button(chatWithPDFButton, disabled=not enableChatBox and not chatTextStr): # Button Cliked
410
+ if genre==radioButtonList[0]: # E-commerce CSV
411
+ st.write(run_duckdb_qa(chatTextStr, csv_data))
412
 
413
+ elif genre==radioButtonList[1]: # Custom CSV Upload
414
+ st.write(run_duckdb_qa(chatTextStr, csv_data))
415
+
416
+ elif genre==radioButtonList[2]: # Custom PDF Upload
417
+ content = build_prompt("Uploaded PDF", pdf_pages, chatTextStr)
418
+ st.write(call_openrouter(content))
419
+ elif genre==radioButtonList[3]: # Google Alphabet URL Earnings Report
420
+ loader = WebBaseLoader(defaultGoogleURL)
421
+ web_data = loader.load()
422
+ content = build_prompt("Google earnings URL", web_data, chatTextStr)
423
+ st.write(call_openrouter(content))
424
+ elif genre==radioButtonList[4]: # Custom URL
425
+ if not urlInput.strip():
426
+ st.warning("Please enter a URL first.")
427
+ else:
428
  loader = WebBaseLoader(urlInput)
429
  web_data = loader.load()
430
+ content = build_prompt("Custom URL", web_data, chatTextStr)
431
+ st.write(call_openrouter(content))
 
 
 
 
 
 
 
requirements.txt CHANGED
@@ -1,6 +1,12 @@
1
- pdf2image
2
- langchain_openai==0.2.9
3
- python-dotenv
4
- langchain==0.3.8
5
- langchain_community==0.3.8
6
- beautifulsoup4
 
 
 
 
 
 
 
1
+ streamlit==1.52.1
2
+ pandas==2.3.3
3
+ numpy==1.26.4
4
+ requests==2.32.5
5
+ langchain==0.3.8
6
+ langchain_community==0.3.8
7
+ langchain_openai==0.2.9
8
+ beautifulsoup4==4.14.3
9
+ pypdf==6.4.1
10
+ duckdb==1.4.3
11
+ pdf2image==1.17.0
12
+ python-dotenv==1.2.1
sklearn_wine.csv ADDED
@@ -0,0 +1,179 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ alcohol,malic_acid,ash,alcalinity_of_ash,magnesium,total_phenols,flavanoids,nonflavanoid_phenols,proanthocyanins,color_intensity,hue,od280/od315_of_diluted_wines,proline,target
2
+ 14.23,1.71,2.43,15.6,127.0,2.8,3.06,0.28,2.29,5.64,1.04,3.92,1065.0,0
3
+ 13.2,1.78,2.14,11.2,100.0,2.65,2.76,0.26,1.28,4.38,1.05,3.4,1050.0,0
4
+ 13.16,2.36,2.67,18.6,101.0,2.8,3.24,0.3,2.81,5.68,1.03,3.17,1185.0,0
5
+ 14.37,1.95,2.5,16.8,113.0,3.85,3.49,0.24,2.18,7.8,0.86,3.45,1480.0,0
6
+ 13.24,2.59,2.87,21.0,118.0,2.8,2.69,0.39,1.82,4.32,1.04,2.93,735.0,0
7
+ 14.2,1.76,2.45,15.2,112.0,3.27,3.39,0.34,1.97,6.75,1.05,2.85,1450.0,0
8
+ 14.39,1.87,2.45,14.6,96.0,2.5,2.52,0.3,1.98,5.25,1.02,3.58,1290.0,0
9
+ 14.06,2.15,2.61,17.6,121.0,2.6,2.51,0.31,1.25,5.05,1.06,3.58,1295.0,0
10
+ 14.83,1.64,2.17,14.0,97.0,2.8,2.98,0.29,1.98,5.2,1.08,2.85,1045.0,0
11
+ 13.86,1.35,2.27,16.0,98.0,2.98,3.15,0.22,1.85,7.22,1.01,3.55,1045.0,0
12
+ 14.1,2.16,2.3,18.0,105.0,2.95,3.32,0.22,2.38,5.75,1.25,3.17,1510.0,0
13
+ 14.12,1.48,2.32,16.8,95.0,2.2,2.43,0.26,1.57,5.0,1.17,2.82,1280.0,0
14
+ 13.75,1.73,2.41,16.0,89.0,2.6,2.76,0.29,1.81,5.6,1.15,2.9,1320.0,0
15
+ 14.75,1.73,2.39,11.4,91.0,3.1,3.69,0.43,2.81,5.4,1.25,2.73,1150.0,0
16
+ 14.38,1.87,2.38,12.0,102.0,3.3,3.64,0.29,2.96,7.5,1.2,3.0,1547.0,0
17
+ 13.63,1.81,2.7,17.2,112.0,2.85,2.91,0.3,1.46,7.3,1.28,2.88,1310.0,0
18
+ 14.3,1.92,2.72,20.0,120.0,2.8,3.14,0.33,1.97,6.2,1.07,2.65,1280.0,0
19
+ 13.83,1.57,2.62,20.0,115.0,2.95,3.4,0.4,1.72,6.6,1.13,2.57,1130.0,0
20
+ 14.19,1.59,2.48,16.5,108.0,3.3,3.93,0.32,1.86,8.7,1.23,2.82,1680.0,0
21
+ 13.64,3.1,2.56,15.2,116.0,2.7,3.03,0.17,1.66,5.1,0.96,3.36,845.0,0
22
+ 14.06,1.63,2.28,16.0,126.0,3.0,3.17,0.24,2.1,5.65,1.09,3.71,780.0,0
23
+ 12.93,3.8,2.65,18.6,102.0,2.41,2.41,0.25,1.98,4.5,1.03,3.52,770.0,0
24
+ 13.71,1.86,2.36,16.6,101.0,2.61,2.88,0.27,1.69,3.8,1.11,4.0,1035.0,0
25
+ 12.85,1.6,2.52,17.8,95.0,2.48,2.37,0.26,1.46,3.93,1.09,3.63,1015.0,0
26
+ 13.5,1.81,2.61,20.0,96.0,2.53,2.61,0.28,1.66,3.52,1.12,3.82,845.0,0
27
+ 13.05,2.05,3.22,25.0,124.0,2.63,2.68,0.47,1.92,3.58,1.13,3.2,830.0,0
28
+ 13.39,1.77,2.62,16.1,93.0,2.85,2.94,0.34,1.45,4.8,0.92,3.22,1195.0,0
29
+ 13.3,1.72,2.14,17.0,94.0,2.4,2.19,0.27,1.35,3.95,1.02,2.77,1285.0,0
30
+ 13.87,1.9,2.8,19.4,107.0,2.95,2.97,0.37,1.76,4.5,1.25,3.4,915.0,0
31
+ 14.02,1.68,2.21,16.0,96.0,2.65,2.33,0.26,1.98,4.7,1.04,3.59,1035.0,0
32
+ 13.73,1.5,2.7,22.5,101.0,3.0,3.25,0.29,2.38,5.7,1.19,2.71,1285.0,0
33
+ 13.58,1.66,2.36,19.1,106.0,2.86,3.19,0.22,1.95,6.9,1.09,2.88,1515.0,0
34
+ 13.68,1.83,2.36,17.2,104.0,2.42,2.69,0.42,1.97,3.84,1.23,2.87,990.0,0
35
+ 13.76,1.53,2.7,19.5,132.0,2.95,2.74,0.5,1.35,5.4,1.25,3.0,1235.0,0
36
+ 13.51,1.8,2.65,19.0,110.0,2.35,2.53,0.29,1.54,4.2,1.1,2.87,1095.0,0
37
+ 13.48,1.81,2.41,20.5,100.0,2.7,2.98,0.26,1.86,5.1,1.04,3.47,920.0,0
38
+ 13.28,1.64,2.84,15.5,110.0,2.6,2.68,0.34,1.36,4.6,1.09,2.78,880.0,0
39
+ 13.05,1.65,2.55,18.0,98.0,2.45,2.43,0.29,1.44,4.25,1.12,2.51,1105.0,0
40
+ 13.07,1.5,2.1,15.5,98.0,2.4,2.64,0.28,1.37,3.7,1.18,2.69,1020.0,0
41
+ 14.22,3.99,2.51,13.2,128.0,3.0,3.04,0.2,2.08,5.1,0.89,3.53,760.0,0
42
+ 13.56,1.71,2.31,16.2,117.0,3.15,3.29,0.34,2.34,6.13,0.95,3.38,795.0,0
43
+ 13.41,3.84,2.12,18.8,90.0,2.45,2.68,0.27,1.48,4.28,0.91,3.0,1035.0,0
44
+ 13.88,1.89,2.59,15.0,101.0,3.25,3.56,0.17,1.7,5.43,0.88,3.56,1095.0,0
45
+ 13.24,3.98,2.29,17.5,103.0,2.64,2.63,0.32,1.66,4.36,0.82,3.0,680.0,0
46
+ 13.05,1.77,2.1,17.0,107.0,3.0,3.0,0.28,2.03,5.04,0.88,3.35,885.0,0
47
+ 14.21,4.04,2.44,18.9,111.0,2.85,2.65,0.3,1.25,5.24,0.87,3.33,1080.0,0
48
+ 14.38,3.59,2.28,16.0,102.0,3.25,3.17,0.27,2.19,4.9,1.04,3.44,1065.0,0
49
+ 13.9,1.68,2.12,16.0,101.0,3.1,3.39,0.21,2.14,6.1,0.91,3.33,985.0,0
50
+ 14.1,2.02,2.4,18.8,103.0,2.75,2.92,0.32,2.38,6.2,1.07,2.75,1060.0,0
51
+ 13.94,1.73,2.27,17.4,108.0,2.88,3.54,0.32,2.08,8.9,1.12,3.1,1260.0,0
52
+ 13.05,1.73,2.04,12.4,92.0,2.72,3.27,0.17,2.91,7.2,1.12,2.91,1150.0,0
53
+ 13.83,1.65,2.6,17.2,94.0,2.45,2.99,0.22,2.29,5.6,1.24,3.37,1265.0,0
54
+ 13.82,1.75,2.42,14.0,111.0,3.88,3.74,0.32,1.87,7.05,1.01,3.26,1190.0,0
55
+ 13.77,1.9,2.68,17.1,115.0,3.0,2.79,0.39,1.68,6.3,1.13,2.93,1375.0,0
56
+ 13.74,1.67,2.25,16.4,118.0,2.6,2.9,0.21,1.62,5.85,0.92,3.2,1060.0,0
57
+ 13.56,1.73,2.46,20.5,116.0,2.96,2.78,0.2,2.45,6.25,0.98,3.03,1120.0,0
58
+ 14.22,1.7,2.3,16.3,118.0,3.2,3.0,0.26,2.03,6.38,0.94,3.31,970.0,0
59
+ 13.29,1.97,2.68,16.8,102.0,3.0,3.23,0.31,1.66,6.0,1.07,2.84,1270.0,0
60
+ 13.72,1.43,2.5,16.7,108.0,3.4,3.67,0.19,2.04,6.8,0.89,2.87,1285.0,0
61
+ 12.37,0.94,1.36,10.6,88.0,1.98,0.57,0.28,0.42,1.95,1.05,1.82,520.0,1
62
+ 12.33,1.1,2.28,16.0,101.0,2.05,1.09,0.63,0.41,3.27,1.25,1.67,680.0,1
63
+ 12.64,1.36,2.02,16.8,100.0,2.02,1.41,0.53,0.62,5.75,0.98,1.59,450.0,1
64
+ 13.67,1.25,1.92,18.0,94.0,2.1,1.79,0.32,0.73,3.8,1.23,2.46,630.0,1
65
+ 12.37,1.13,2.16,19.0,87.0,3.5,3.1,0.19,1.87,4.45,1.22,2.87,420.0,1
66
+ 12.17,1.45,2.53,19.0,104.0,1.89,1.75,0.45,1.03,2.95,1.45,2.23,355.0,1
67
+ 12.37,1.21,2.56,18.1,98.0,2.42,2.65,0.37,2.08,4.6,1.19,2.3,678.0,1
68
+ 13.11,1.01,1.7,15.0,78.0,2.98,3.18,0.26,2.28,5.3,1.12,3.18,502.0,1
69
+ 12.37,1.17,1.92,19.6,78.0,2.11,2.0,0.27,1.04,4.68,1.12,3.48,510.0,1
70
+ 13.34,0.94,2.36,17.0,110.0,2.53,1.3,0.55,0.42,3.17,1.02,1.93,750.0,1
71
+ 12.21,1.19,1.75,16.8,151.0,1.85,1.28,0.14,2.5,2.85,1.28,3.07,718.0,1
72
+ 12.29,1.61,2.21,20.4,103.0,1.1,1.02,0.37,1.46,3.05,0.906,1.82,870.0,1
73
+ 13.86,1.51,2.67,25.0,86.0,2.95,2.86,0.21,1.87,3.38,1.36,3.16,410.0,1
74
+ 13.49,1.66,2.24,24.0,87.0,1.88,1.84,0.27,1.03,3.74,0.98,2.78,472.0,1
75
+ 12.99,1.67,2.6,30.0,139.0,3.3,2.89,0.21,1.96,3.35,1.31,3.5,985.0,1
76
+ 11.96,1.09,2.3,21.0,101.0,3.38,2.14,0.13,1.65,3.21,0.99,3.13,886.0,1
77
+ 11.66,1.88,1.92,16.0,97.0,1.61,1.57,0.34,1.15,3.8,1.23,2.14,428.0,1
78
+ 13.03,0.9,1.71,16.0,86.0,1.95,2.03,0.24,1.46,4.6,1.19,2.48,392.0,1
79
+ 11.84,2.89,2.23,18.0,112.0,1.72,1.32,0.43,0.95,2.65,0.96,2.52,500.0,1
80
+ 12.33,0.99,1.95,14.8,136.0,1.9,1.85,0.35,2.76,3.4,1.06,2.31,750.0,1
81
+ 12.7,3.87,2.4,23.0,101.0,2.83,2.55,0.43,1.95,2.57,1.19,3.13,463.0,1
82
+ 12.0,0.92,2.0,19.0,86.0,2.42,2.26,0.3,1.43,2.5,1.38,3.12,278.0,1
83
+ 12.72,1.81,2.2,18.8,86.0,2.2,2.53,0.26,1.77,3.9,1.16,3.14,714.0,1
84
+ 12.08,1.13,2.51,24.0,78.0,2.0,1.58,0.4,1.4,2.2,1.31,2.72,630.0,1
85
+ 13.05,3.86,2.32,22.5,85.0,1.65,1.59,0.61,1.62,4.8,0.84,2.01,515.0,1
86
+ 11.84,0.89,2.58,18.0,94.0,2.2,2.21,0.22,2.35,3.05,0.79,3.08,520.0,1
87
+ 12.67,0.98,2.24,18.0,99.0,2.2,1.94,0.3,1.46,2.62,1.23,3.16,450.0,1
88
+ 12.16,1.61,2.31,22.8,90.0,1.78,1.69,0.43,1.56,2.45,1.33,2.26,495.0,1
89
+ 11.65,1.67,2.62,26.0,88.0,1.92,1.61,0.4,1.34,2.6,1.36,3.21,562.0,1
90
+ 11.64,2.06,2.46,21.6,84.0,1.95,1.69,0.48,1.35,2.8,1.0,2.75,680.0,1
91
+ 12.08,1.33,2.3,23.6,70.0,2.2,1.59,0.42,1.38,1.74,1.07,3.21,625.0,1
92
+ 12.08,1.83,2.32,18.5,81.0,1.6,1.5,0.52,1.64,2.4,1.08,2.27,480.0,1
93
+ 12.0,1.51,2.42,22.0,86.0,1.45,1.25,0.5,1.63,3.6,1.05,2.65,450.0,1
94
+ 12.69,1.53,2.26,20.7,80.0,1.38,1.46,0.58,1.62,3.05,0.96,2.06,495.0,1
95
+ 12.29,2.83,2.22,18.0,88.0,2.45,2.25,0.25,1.99,2.15,1.15,3.3,290.0,1
96
+ 11.62,1.99,2.28,18.0,98.0,3.02,2.26,0.17,1.35,3.25,1.16,2.96,345.0,1
97
+ 12.47,1.52,2.2,19.0,162.0,2.5,2.27,0.32,3.28,2.6,1.16,2.63,937.0,1
98
+ 11.81,2.12,2.74,21.5,134.0,1.6,0.99,0.14,1.56,2.5,0.95,2.26,625.0,1
99
+ 12.29,1.41,1.98,16.0,85.0,2.55,2.5,0.29,1.77,2.9,1.23,2.74,428.0,1
100
+ 12.37,1.07,2.1,18.5,88.0,3.52,3.75,0.24,1.95,4.5,1.04,2.77,660.0,1
101
+ 12.29,3.17,2.21,18.0,88.0,2.85,2.99,0.45,2.81,2.3,1.42,2.83,406.0,1
102
+ 12.08,2.08,1.7,17.5,97.0,2.23,2.17,0.26,1.4,3.3,1.27,2.96,710.0,1
103
+ 12.6,1.34,1.9,18.5,88.0,1.45,1.36,0.29,1.35,2.45,1.04,2.77,562.0,1
104
+ 12.34,2.45,2.46,21.0,98.0,2.56,2.11,0.34,1.31,2.8,0.8,3.38,438.0,1
105
+ 11.82,1.72,1.88,19.5,86.0,2.5,1.64,0.37,1.42,2.06,0.94,2.44,415.0,1
106
+ 12.51,1.73,1.98,20.5,85.0,2.2,1.92,0.32,1.48,2.94,1.04,3.57,672.0,1
107
+ 12.42,2.55,2.27,22.0,90.0,1.68,1.84,0.66,1.42,2.7,0.86,3.3,315.0,1
108
+ 12.25,1.73,2.12,19.0,80.0,1.65,2.03,0.37,1.63,3.4,1.0,3.17,510.0,1
109
+ 12.72,1.75,2.28,22.5,84.0,1.38,1.76,0.48,1.63,3.3,0.88,2.42,488.0,1
110
+ 12.22,1.29,1.94,19.0,92.0,2.36,2.04,0.39,2.08,2.7,0.86,3.02,312.0,1
111
+ 11.61,1.35,2.7,20.0,94.0,2.74,2.92,0.29,2.49,2.65,0.96,3.26,680.0,1
112
+ 11.46,3.74,1.82,19.5,107.0,3.18,2.58,0.24,3.58,2.9,0.75,2.81,562.0,1
113
+ 12.52,2.43,2.17,21.0,88.0,2.55,2.27,0.26,1.22,2.0,0.9,2.78,325.0,1
114
+ 11.76,2.68,2.92,20.0,103.0,1.75,2.03,0.6,1.05,3.8,1.23,2.5,607.0,1
115
+ 11.41,0.74,2.5,21.0,88.0,2.48,2.01,0.42,1.44,3.08,1.1,2.31,434.0,1
116
+ 12.08,1.39,2.5,22.5,84.0,2.56,2.29,0.43,1.04,2.9,0.93,3.19,385.0,1
117
+ 11.03,1.51,2.2,21.5,85.0,2.46,2.17,0.52,2.01,1.9,1.71,2.87,407.0,1
118
+ 11.82,1.47,1.99,20.8,86.0,1.98,1.6,0.3,1.53,1.95,0.95,3.33,495.0,1
119
+ 12.42,1.61,2.19,22.5,108.0,2.0,2.09,0.34,1.61,2.06,1.06,2.96,345.0,1
120
+ 12.77,3.43,1.98,16.0,80.0,1.63,1.25,0.43,0.83,3.4,0.7,2.12,372.0,1
121
+ 12.0,3.43,2.0,19.0,87.0,2.0,1.64,0.37,1.87,1.28,0.93,3.05,564.0,1
122
+ 11.45,2.4,2.42,20.0,96.0,2.9,2.79,0.32,1.83,3.25,0.8,3.39,625.0,1
123
+ 11.56,2.05,3.23,28.5,119.0,3.18,5.08,0.47,1.87,6.0,0.93,3.69,465.0,1
124
+ 12.42,4.43,2.73,26.5,102.0,2.2,2.13,0.43,1.71,2.08,0.92,3.12,365.0,1
125
+ 13.05,5.8,2.13,21.5,86.0,2.62,2.65,0.3,2.01,2.6,0.73,3.1,380.0,1
126
+ 11.87,4.31,2.39,21.0,82.0,2.86,3.03,0.21,2.91,2.8,0.75,3.64,380.0,1
127
+ 12.07,2.16,2.17,21.0,85.0,2.6,2.65,0.37,1.35,2.76,0.86,3.28,378.0,1
128
+ 12.43,1.53,2.29,21.5,86.0,2.74,3.15,0.39,1.77,3.94,0.69,2.84,352.0,1
129
+ 11.79,2.13,2.78,28.5,92.0,2.13,2.24,0.58,1.76,3.0,0.97,2.44,466.0,1
130
+ 12.37,1.63,2.3,24.5,88.0,2.22,2.45,0.4,1.9,2.12,0.89,2.78,342.0,1
131
+ 12.04,4.3,2.38,22.0,80.0,2.1,1.75,0.42,1.35,2.6,0.79,2.57,580.0,1
132
+ 12.86,1.35,2.32,18.0,122.0,1.51,1.25,0.21,0.94,4.1,0.76,1.29,630.0,2
133
+ 12.88,2.99,2.4,20.0,104.0,1.3,1.22,0.24,0.83,5.4,0.74,1.42,530.0,2
134
+ 12.81,2.31,2.4,24.0,98.0,1.15,1.09,0.27,0.83,5.7,0.66,1.36,560.0,2
135
+ 12.7,3.55,2.36,21.5,106.0,1.7,1.2,0.17,0.84,5.0,0.78,1.29,600.0,2
136
+ 12.51,1.24,2.25,17.5,85.0,2.0,0.58,0.6,1.25,5.45,0.75,1.51,650.0,2
137
+ 12.6,2.46,2.2,18.5,94.0,1.62,0.66,0.63,0.94,7.1,0.73,1.58,695.0,2
138
+ 12.25,4.72,2.54,21.0,89.0,1.38,0.47,0.53,0.8,3.85,0.75,1.27,720.0,2
139
+ 12.53,5.51,2.64,25.0,96.0,1.79,0.6,0.63,1.1,5.0,0.82,1.69,515.0,2
140
+ 13.49,3.59,2.19,19.5,88.0,1.62,0.48,0.58,0.88,5.7,0.81,1.82,580.0,2
141
+ 12.84,2.96,2.61,24.0,101.0,2.32,0.6,0.53,0.81,4.92,0.89,2.15,590.0,2
142
+ 12.93,2.81,2.7,21.0,96.0,1.54,0.5,0.53,0.75,4.6,0.77,2.31,600.0,2
143
+ 13.36,2.56,2.35,20.0,89.0,1.4,0.5,0.37,0.64,5.6,0.7,2.47,780.0,2
144
+ 13.52,3.17,2.72,23.5,97.0,1.55,0.52,0.5,0.55,4.35,0.89,2.06,520.0,2
145
+ 13.62,4.95,2.35,20.0,92.0,2.0,0.8,0.47,1.02,4.4,0.91,2.05,550.0,2
146
+ 12.25,3.88,2.2,18.5,112.0,1.38,0.78,0.29,1.14,8.21,0.65,2.0,855.0,2
147
+ 13.16,3.57,2.15,21.0,102.0,1.5,0.55,0.43,1.3,4.0,0.6,1.68,830.0,2
148
+ 13.88,5.04,2.23,20.0,80.0,0.98,0.34,0.4,0.68,4.9,0.58,1.33,415.0,2
149
+ 12.87,4.61,2.48,21.5,86.0,1.7,0.65,0.47,0.86,7.65,0.54,1.86,625.0,2
150
+ 13.32,3.24,2.38,21.5,92.0,1.93,0.76,0.45,1.25,8.42,0.55,1.62,650.0,2
151
+ 13.08,3.9,2.36,21.5,113.0,1.41,1.39,0.34,1.14,9.4,0.57,1.33,550.0,2
152
+ 13.5,3.12,2.62,24.0,123.0,1.4,1.57,0.22,1.25,8.6,0.59,1.3,500.0,2
153
+ 12.79,2.67,2.48,22.0,112.0,1.48,1.36,0.24,1.26,10.8,0.48,1.47,480.0,2
154
+ 13.11,1.9,2.75,25.5,116.0,2.2,1.28,0.26,1.56,7.1,0.61,1.33,425.0,2
155
+ 13.23,3.3,2.28,18.5,98.0,1.8,0.83,0.61,1.87,10.52,0.56,1.51,675.0,2
156
+ 12.58,1.29,2.1,20.0,103.0,1.48,0.58,0.53,1.4,7.6,0.58,1.55,640.0,2
157
+ 13.17,5.19,2.32,22.0,93.0,1.74,0.63,0.61,1.55,7.9,0.6,1.48,725.0,2
158
+ 13.84,4.12,2.38,19.5,89.0,1.8,0.83,0.48,1.56,9.01,0.57,1.64,480.0,2
159
+ 12.45,3.03,2.64,27.0,97.0,1.9,0.58,0.63,1.14,7.5,0.67,1.73,880.0,2
160
+ 14.34,1.68,2.7,25.0,98.0,2.8,1.31,0.53,2.7,13.0,0.57,1.96,660.0,2
161
+ 13.48,1.67,2.64,22.5,89.0,2.6,1.1,0.52,2.29,11.75,0.57,1.78,620.0,2
162
+ 12.36,3.83,2.38,21.0,88.0,2.3,0.92,0.5,1.04,7.65,0.56,1.58,520.0,2
163
+ 13.69,3.26,2.54,20.0,107.0,1.83,0.56,0.5,0.8,5.88,0.96,1.82,680.0,2
164
+ 12.85,3.27,2.58,22.0,106.0,1.65,0.6,0.6,0.96,5.58,0.87,2.11,570.0,2
165
+ 12.96,3.45,2.35,18.5,106.0,1.39,0.7,0.4,0.94,5.28,0.68,1.75,675.0,2
166
+ 13.78,2.76,2.3,22.0,90.0,1.35,0.68,0.41,1.03,9.58,0.7,1.68,615.0,2
167
+ 13.73,4.36,2.26,22.5,88.0,1.28,0.47,0.52,1.15,6.62,0.78,1.75,520.0,2
168
+ 13.45,3.7,2.6,23.0,111.0,1.7,0.92,0.43,1.46,10.68,0.85,1.56,695.0,2
169
+ 12.82,3.37,2.3,19.5,88.0,1.48,0.66,0.4,0.97,10.26,0.72,1.75,685.0,2
170
+ 13.58,2.58,2.69,24.5,105.0,1.55,0.84,0.39,1.54,8.66,0.74,1.8,750.0,2
171
+ 13.4,4.6,2.86,25.0,112.0,1.98,0.96,0.27,1.11,8.5,0.67,1.92,630.0,2
172
+ 12.2,3.03,2.32,19.0,96.0,1.25,0.49,0.4,0.73,5.5,0.66,1.83,510.0,2
173
+ 12.77,2.39,2.28,19.5,86.0,1.39,0.51,0.48,0.64,9.899999,0.57,1.63,470.0,2
174
+ 14.16,2.51,2.48,20.0,91.0,1.68,0.7,0.44,1.24,9.7,0.62,1.71,660.0,2
175
+ 13.71,5.65,2.45,20.5,95.0,1.68,0.61,0.52,1.06,7.7,0.64,1.74,740.0,2
176
+ 13.4,3.91,2.48,23.0,102.0,1.8,0.75,0.43,1.41,7.3,0.7,1.56,750.0,2
177
+ 13.27,4.28,2.26,20.0,120.0,1.59,0.69,0.43,1.35,10.2,0.59,1.56,835.0,2
178
+ 13.17,2.59,2.37,20.0,120.0,1.65,0.68,0.53,1.46,9.3,0.6,1.62,840.0,2
179
+ 14.13,4.1,2.74,24.5,96.0,2.05,0.76,0.56,1.35,9.2,0.61,1.6,560.0,2