reddgr commited on
Commit
8d0d868
·
1 Parent(s): 432b263

company tab v0.1

Browse files
Files changed (2) hide show
  1. app.py +153 -39
  2. src/app_utils.py +33 -0
app.py CHANGED
@@ -11,6 +11,7 @@ You may obtain a copy of the License at
11
  App URL: https://huggingface.co/spaces/reddgr/sss
12
  '''
13
 
 
14
  # cd C:\Users\david\Documents\git\miax-tfm-dgr; python app.py
15
  from pathlib import Path
16
  from typing import Tuple
@@ -38,7 +39,6 @@ tokens = env_options.check_env(use_dotenv=USE_DOTENV, dotenv_path=DOTENV_PATH, e
38
 
39
  emb_model = SentenceTransformer(EMB_MODEL_PATH, token = tokens.get("HF_TOKEN"))
40
 
41
-
42
  #### CONEXIÓN DE DUCKDB CON EL DATASET PARA INDEXAR ####
43
  print("Initializing DuckDB connection...")
44
  con = duckdb.connect()
@@ -67,14 +67,16 @@ last_search_query: str = ""
67
  last_column_filters: list[tuple[str, str]] = []
68
  last_sort_col_label: str = ""
69
  last_sort_dir: str = ""
 
70
 
71
  # ---------------------------------------------------------------------------
72
  # CONFIG --------------------------------------------------------------------
73
  # ---------------------------------------------------------------------------
74
- app_dataset = load_dataset(DATASET_PATH, split="train", token = tokens.get("HF_TOKEN")).to_pandas()
75
 
 
76
  dh_app = fdh.FrontDatasetHandler(app_dataset=app_dataset)
77
  maestro = dh_app.app_dataset[dh_app.app_dataset['quoteType']=='EQUITY'].copy()
 
78
  maestro_etf = dh_app.app_dataset[dh_app.app_dataset['quoteType']=='ETF'].copy()
79
 
80
  with open(JSON_PATH / "app_column_config.json", "r") as f:
@@ -295,8 +297,20 @@ def reset_initial() -> tuple[pd.DataFrame,str,int,str,str,str]:
295
  # DATOS INICIALES -----------------------------------------------------------
296
  # ---------------------------------------------------------------------------
297
 
 
 
 
298
  last_result_df = utils.format_results(maestro[caracteristicas].head(MAX_ROWS).copy(), rename_columns)
299
  _initial_slice, _initial_label = _paginate(last_result_df, 1)
 
 
 
 
 
 
 
 
 
300
 
301
  # ---------------------------------------------------------------------------
302
  # UI ------------------------------------------------------------------------
@@ -313,7 +327,7 @@ with gr.Blocks(title="Swift Stock Screener, by Reddgr") as front:
313
  # ---------------------- TOP INPUT -------------------------------------
314
  with gr.Row(equal_height=True):
315
  theme_input = gr.Textbox(show_label=False, placeholder="Search a theme. i.e. 'lithium'", scale=2)
316
- ticker_input = gr.Textbox(show_label=False, placeholder="Enter a ticker symbol", scale=1)
317
  buscar_button = gr.Button("Search")
318
  gr.HTML("<div></div>")
319
  reset_button = gr.Button("Reset", elem_classes="small-btn")
@@ -323,38 +337,132 @@ with gr.Blocks(title="Swift Stock Screener, by Reddgr") as front:
323
  # ---------------------- SEARCH SUMMARY ------------------------
324
  summary_display = gr.Markdown("", elem_classes="search-spec")
325
 
326
- # ---------------------- DATAFRAME & PAGINATION ------------------------
327
-
328
- output_df = gr.Dataframe(
329
- value=_initial_slice,
330
- interactive=False,
331
- elem_classes="clickable-columns",
332
- # max_height=500
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
333
  )
334
 
 
 
335
 
336
- # ---------------------- PAGINATION AND SORT CONTROLS ---------------------
337
- with gr.Row():
338
- btn_prev = gr.Button("Previous", elem_classes="small-btn")
339
- pagination_label = gr.Markdown(_initial_label)
340
- btn_next = gr.Button("Next", elem_classes="small-btn")
341
- gr.Markdown("&nbsp;" * 20)
342
- # merged sort controls on right
343
- sort_col = gr.Dropdown(
344
- choices=[rename_columns.get(c, c) for c in caracteristicas],
345
- value=None,
346
- label="Reset and sort by:",
347
- allow_custom_value=False,
348
- scale=2,
 
 
 
 
 
 
 
349
  )
350
- sort_dir = gr.Radio(
351
- choices=["Ascending", "Descending"],
352
- value="Descending",
353
- label="",
354
- scale=1,
 
 
 
 
 
355
  )
356
 
357
- page_state = gr.State(1)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
358
 
359
  # ---------------------- EXCLUSION FILTER TOGGLES --------------------------------
360
  # De momento excluimos esta funcionalidad, al menos en la tabla de acciones,
@@ -394,7 +502,7 @@ with gr.Blocks(title="Swift Stock Screener, by Reddgr") as front:
394
  )
395
 
396
  ticker_input.submit(
397
- reset_page, None, page_state
398
  ).then(
399
  search_all,
400
  inputs=inputs,
@@ -402,7 +510,7 @@ with gr.Blocks(title="Swift Stock Screener, by Reddgr") as front:
402
  )
403
 
404
  theme_input.submit(
405
- reset_page, None, page_state
406
  ).then(
407
  search_all,
408
  inputs=inputs,
@@ -411,7 +519,7 @@ with gr.Blocks(title="Swift Stock Screener, by Reddgr") as front:
411
 
412
  random_button.click(
413
  random_action,
414
- None,
415
  [ticker_input, page_state, theme_input]
416
  ).then(
417
  search_all,
@@ -421,7 +529,7 @@ with gr.Blocks(title="Swift Stock Screener, by Reddgr") as front:
421
 
422
  reset_button.click(
423
  reset_initial,
424
- None,
425
  [output_df, pagination_label, page_state, ticker_input, theme_input, sort_col, summary_display]
426
  )
427
 
@@ -453,6 +561,18 @@ with gr.Blocks(title="Swift Stock Screener, by Reddgr") as front:
453
  outputs=[output_df, pagination_label, page_state, summary_display]
454
  )
455
 
 
 
 
 
 
 
 
 
 
 
 
 
456
  # ---------------------- FILTERS BY COLUMN ------------------ #
457
  filterable_columns = [rename_columns.get(c, c) for c in cat_cols]
458
 
@@ -472,12 +592,6 @@ with gr.Blocks(title="Swift Stock Screener, by Reddgr") as front:
472
  summary = _compose_summary()
473
  return slice_df, label, 1, summary
474
 
475
-
476
- output_df.select(
477
- filter_by_column,
478
- outputs=[output_df, pagination_label, page_state, summary_display]
479
- )
480
-
481
  # ---------------------------------------------------------------------------
482
  # LAUNCH --------------------------------------------------------------------
483
  # ---------------------------------------------------------------------------
 
11
  App URL: https://huggingface.co/spaces/reddgr/sss
12
  '''
13
 
14
+ ### DEBUGGING COMMAND (DGR):
15
  # cd C:\Users\david\Documents\git\miax-tfm-dgr; python app.py
16
  from pathlib import Path
17
  from typing import Tuple
 
39
 
40
  emb_model = SentenceTransformer(EMB_MODEL_PATH, token = tokens.get("HF_TOKEN"))
41
 
 
42
  #### CONEXIÓN DE DUCKDB CON EL DATASET PARA INDEXAR ####
43
  print("Initializing DuckDB connection...")
44
  con = duckdb.connect()
 
67
  last_column_filters: list[tuple[str, str]] = []
68
  last_sort_col_label: str = ""
69
  last_sort_dir: str = ""
70
+ selected_ticker: str = ""
71
 
72
  # ---------------------------------------------------------------------------
73
  # CONFIG --------------------------------------------------------------------
74
  # ---------------------------------------------------------------------------
 
75
 
76
+ app_dataset = load_dataset(DATASET_PATH, split="train", token = tokens.get("HF_TOKEN")).to_pandas()
77
  dh_app = fdh.FrontDatasetHandler(app_dataset=app_dataset)
78
  maestro = dh_app.app_dataset[dh_app.app_dataset['quoteType']=='EQUITY'].copy()
79
+ print("maestro_columns", maestro.columns.to_list())
80
  maestro_etf = dh_app.app_dataset[dh_app.app_dataset['quoteType']=='ETF'].copy()
81
 
82
  with open(JSON_PATH / "app_column_config.json", "r") as f:
 
297
  # DATOS INICIALES -----------------------------------------------------------
298
  # ---------------------------------------------------------------------------
299
 
300
+ #last_result_df = utils.format_results(maestro[caracteristicas].head(MAX_ROWS).copy(), rename_columns)
301
+ #_initial_slice, _initial_label = _paginate(last_result_df, 1)
302
+
303
  last_result_df = utils.format_results(maestro[caracteristicas].head(MAX_ROWS).copy(), rename_columns)
304
  _initial_slice, _initial_label = _paginate(last_result_df, 1)
305
+ # Ticker por defecto es el primero de la lista
306
+ if not last_result_df.empty:
307
+ selected_ticker = last_result_df.iloc[0][rename_columns.get('ticker','ticker')]
308
+ # Fetch initial company info
309
+ if selected_ticker:
310
+ init_name, init_summary, init_details = utils.get_company_info(maestro, selected_ticker, rename_columns)
311
+ else:
312
+ init_name, init_summary, init_details = "", "", pd.DataFrame()
313
+
314
 
315
  # ---------------------------------------------------------------------------
316
  # UI ------------------------------------------------------------------------
 
327
  # ---------------------- TOP INPUT -------------------------------------
328
  with gr.Row(equal_height=True):
329
  theme_input = gr.Textbox(show_label=False, placeholder="Search a theme. i.e. 'lithium'", scale=2)
330
+ ticker_input = gr.Textbox(show_label=False, placeholder="Enter a ticker symbol. i.e. 'nvda'", scale=1)
331
  buscar_button = gr.Button("Search")
332
  gr.HTML("<div></div>")
333
  reset_button = gr.Button("Reset", elem_classes="small-btn")
 
337
  # ---------------------- SEARCH SUMMARY ------------------------
338
  summary_display = gr.Markdown("", elem_classes="search-spec")
339
 
340
+ # ---------------------- RESULTS COMPANY TABS ----------------------------
341
+ with gr.Tabs(selected=0) as main_tabs: # 0 = “Results”
342
+ # ---- TAB 1: GRID --------------------------------------------------
343
+ with gr.TabItem("Grid"):
344
+ output_df = gr.Dataframe(
345
+ value=_initial_slice,
346
+ interactive=False,
347
+ elem_classes="df-cells",
348
+ )
349
+
350
+ with gr.Row():
351
+ btn_prev = gr.Button("Previous", elem_classes="small-btn")
352
+ pagination_label = gr.Markdown(_initial_label)
353
+ btn_next = gr.Button("Next", elem_classes="small-btn")
354
+ gr.Markdown("&nbsp;" * 20)
355
+ sort_col = gr.Dropdown(
356
+ [rename_columns.get(c, c) for c in caracteristicas],
357
+ value=None,
358
+ label="Reset and sort by:",
359
+ allow_custom_value=False,
360
+ scale=2,
361
+ )
362
+ sort_dir = gr.Radio(
363
+ ["Ascending", "Descending"],
364
+ value="Descending",
365
+ label="",
366
+ scale=1,
367
+ )
368
+
369
+ # ---- TAB 2: COMPANY --------------------------------------------------
370
+ with gr.TabItem("Company details")as company_tab: ####
371
+ company_title = gr.Markdown(f"## {init_name}" if init_name else "### Company Name")
372
+ company_summary = gr.Markdown(init_summary)
373
+ company_details = gr.Dataframe(value=init_details, interactive=False)
374
+
375
+ def on_company_tab(evt: gr.SelectData):
376
+ global selected_ticker
377
+ if evt.selected and selected_ticker:
378
+ maestro_details = maestro.copy()
379
+ maestro_details.drop(columns=["embeddings"], inplace=True, errors="ignore")
380
+ name, summary, details_df = utils.get_company_info(maestro_details, selected_ticker, rename_columns)
381
+ return (
382
+ gr.update(value=f"## {name}"),
383
+ gr.update(value=summary),
384
+ gr.update(value=details_df)
385
+ )
386
+ return gr.update(), gr.update(), gr.update()
387
+
388
+ company_tab.select(
389
+ on_company_tab,
390
+ inputs=[],
391
+ outputs=[company_title, company_summary, company_details]
392
  )
393
 
394
+
395
+ page_state = gr.State(1)
396
 
397
+
398
+ # ---------------------- COMPANY DETAIL ---------------------
399
+ def on_table_select(evt: gr.SelectData):
400
+ print(f"DEBUG on_table_select called: index={evt.index}, value={evt.value}")
401
+ global last_result_df, selected_ticker
402
+ row_i, col_i = evt.index
403
+ if col_i == 0:
404
+ ticker = evt.value
405
+ print(f"DEBUG ticker extracted: {ticker}")
406
+ selected_ticker = ticker
407
+ elif col_i == 1 or (4 <= col_i <= 10):
408
+ display_col = rename_columns.get("ticker", "ticker")
409
+ ticker = last_result_df.iloc[row_i][display_col]
410
+ print(f"DEBUG ticker extracted: {ticker}")
411
+ else:
412
+ return filter_by_column(evt) + (
413
+ gr.update(selected=0), "", "", pd.DataFrame()
414
+ )
415
+ name, summary, details_df = utils.get_company_info(
416
+ maestro, ticker, rename_columns
417
  )
418
+ print(f"DEBUG ➡ selected ticker={ticker}, name={name}")
419
+ return (
420
+ last_result_df,
421
+ pagination_label,
422
+ page_state,
423
+ summary_display,
424
+ gr.update(selected=1), # ← change here
425
+ gr.update(value=f"## {name}"),
426
+ gr.update(value=summary),
427
+ gr.update(value=details_df)
428
  )
429
 
430
+
431
+ output_df.select(
432
+ on_table_select,
433
+ inputs=[],
434
+ outputs=[
435
+ output_df, pagination_label, page_state, summary_display,
436
+ main_tabs, company_title, company_summary, company_details
437
+ ]
438
+ )
439
+
440
+ # — Update company‑details whenever the table’s first row changes —
441
+ def on_df_first_row_change(df: pd.DataFrame):
442
+ global selected_ticker
443
+ # if table is empty, do nothing
444
+ if df is None or df.empty:
445
+ return gr.update(), gr.update(), gr.update()
446
+ # extract ticker from first row
447
+ ticker_col = rename_columns.get('ticker','ticker')
448
+ new_ticker = df.iloc[0][ticker_col]
449
+ # if it really changed, fetch new info
450
+ if new_ticker != selected_ticker:
451
+ selected_ticker = new_ticker
452
+ name, summary, details_df = utils.get_company_info(maestro, selected_ticker, rename_columns)
453
+ return (
454
+ gr.update(value=f"## {name}"),
455
+ gr.update(value=summary),
456
+ gr.update(value=details_df)
457
+ )
458
+ # otherwise leave components as‑is
459
+ return gr.update(), gr.update(), gr.update()
460
+
461
+ output_df.change(
462
+ on_df_first_row_change,
463
+ inputs=[output_df],
464
+ outputs=[company_title, company_summary, company_details]
465
+ )
466
 
467
  # ---------------------- EXCLUSION FILTER TOGGLES --------------------------------
468
  # De momento excluimos esta funcionalidad, al menos en la tabla de acciones,
 
502
  )
503
 
504
  ticker_input.submit(
505
+ reset_page, [], page_state
506
  ).then(
507
  search_all,
508
  inputs=inputs,
 
510
  )
511
 
512
  theme_input.submit(
513
+ reset_page, [], page_state
514
  ).then(
515
  search_all,
516
  inputs=inputs,
 
519
 
520
  random_button.click(
521
  random_action,
522
+ [],
523
  [ticker_input, page_state, theme_input]
524
  ).then(
525
  search_all,
 
529
 
530
  reset_button.click(
531
  reset_initial,
532
+ [],
533
  [output_df, pagination_label, page_state, ticker_input, theme_input, sort_col, summary_display]
534
  )
535
 
 
561
  outputs=[output_df, pagination_label, page_state, summary_display]
562
  )
563
 
564
+
565
+ def on_tab_change(tab_index):
566
+ if tab_index == 1 and selected_ticker:
567
+ name, summary, details_df = utils.get_company_info(maestro, selected_ticker, rename_columns)
568
+ return (
569
+ gr.update(value=f"## {name}"),
570
+ gr.update(value=summary),
571
+ gr.update(value=details_df)
572
+ )
573
+ return gr.update(), gr.update(), gr.update()
574
+
575
+
576
  # ---------------------- FILTERS BY COLUMN ------------------ #
577
  filterable_columns = [rename_columns.get(c, c) for c in cat_cols]
578
 
 
592
  summary = _compose_summary()
593
  return slice_df, label, 1, summary
594
 
 
 
 
 
 
 
595
  # ---------------------------------------------------------------------------
596
  # LAUNCH --------------------------------------------------------------------
597
  # ---------------------------------------------------------------------------
src/app_utils.py CHANGED
@@ -93,4 +93,37 @@ def get_company_info(
93
  })
94
  df["Field"] = df["Field"].map(lambda c: rename_columns.get(c, c))
95
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
96
  return name, summary, df
 
93
  })
94
  df["Field"] = df["Field"].map(lambda c: rename_columns.get(c, c))
95
 
96
+ # Round _norm fields to 3 decimal places
97
+ for i, field in enumerate(df["Field"]):
98
+ if field.endswith("_norm"):
99
+ value = df.iloc[i]["Value"]
100
+ if isinstance(value, (int, float)) and not pd.isna(value):
101
+ df.iloc[i, df.columns.get_loc("Value")] = round(value, 3)
102
+ # Process numeric fields using format_results function
103
+ # Extract numeric fields (excluding already processed _norm fields)
104
+ numeric_fields = []
105
+ numeric_values = []
106
+ numeric_indices = []
107
+
108
+ for i, (display_field, value) in enumerate(zip(df["Field"], df["Value"])):
109
+ if not display_field.endswith("_norm") and isinstance(value, (int, float)) and not pd.isna(value):
110
+ # Get original field name using inverse rename dictionary
111
+ orig_field = next((k for k, v in rename_columns.items() if v == display_field), display_field)
112
+ numeric_fields.append(orig_field)
113
+ numeric_values.append(value)
114
+ numeric_indices.append(i)
115
+
116
+ if numeric_fields:
117
+ # Create a single-row dataframe with original field names
118
+ temp_df = pd.DataFrame([numeric_values], columns=numeric_fields)
119
+
120
+ # Apply format_results function
121
+ formatted_df = format_results(temp_df, rename_columns)
122
+
123
+ # Put formatted values back into the original dataframe
124
+ for i, field in zip(numeric_indices, numeric_fields):
125
+ display_field = rename_columns.get(field, field)
126
+ df.iloc[i, df.columns.get_loc("Value")] = formatted_df.iloc[0][display_field]
127
+
128
+
129
  return name, summary, df