yogl commited on
Commit
34bda3e
·
verified ·
1 Parent(s): 7723f09

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +51 -41
app.py CHANGED
@@ -82,13 +82,12 @@ except Exception:
82
 
83
 
84
  # ==========================
85
- # CSS: уменьшение шрифта таблицы до ~80%
86
  # ==========================
87
 
88
  st.markdown(
89
  """
90
  <style>
91
- /* уменьшение шрифта вокруг dataframes */
92
  div[data-testid="stDataFrame"] { font-size: 80% !important; }
93
  div[data-testid="stDataFrame"] * { font-size: 80% !important; }
94
 
@@ -123,11 +122,6 @@ def _safe_text(x: Any) -> str:
123
  return s
124
 
125
 
126
- def _safe_fragment(s: Any) -> str:
127
- t = _safe_text(s).replace("#", " ").replace("\n", " ").strip()
128
- return t
129
-
130
-
131
  def _norm_openalex_url(x: Any) -> str:
132
  s = _safe_text(x)
133
  if not s:
@@ -152,6 +146,12 @@ def _keyify(label: str) -> str:
152
  return "k_" + "".join(ch if ch.isalnum() else "_" for ch in label).strip("_")
153
 
154
 
 
 
 
 
 
 
155
  # ==========================
156
  # ФИЛЬТРЫ (UI)
157
  # ==========================
@@ -256,10 +256,9 @@ def load_oa_enrichment() -> pd.DataFrame:
256
  if "orcid_url" in df.columns:
257
  df["orcid_url"] = df["orcid_url"].map(_norm_orcid_url)
258
 
259
- # метрики сразу в numeric (иначе получите object + None)
260
  for c in ["h_index", "i10_index", "works_count", "cited_by_count"]:
261
  if c in df.columns:
262
- df[c] = pd.to_numeric(df[c], errors="coerce")
263
 
264
  keep = ["reg_norm", "openalex_url", "orcid_url", "h_index", "i10_index", "works_count", "cited_by_count"]
265
  keep = [c for c in keep if c in df.columns]
@@ -461,7 +460,6 @@ def run_search(
461
  results = search_core(query, top_k, mask=mask)
462
  df_raw = build_result_df(results)
463
 
464
- # OA join: критично держать числовые типы (иначе i10 сортируется как строка)
465
  if not df_raw.empty and not oa_enrich.empty:
466
  regs_norm = df_raw["registration_number"].map(_norm_regnum)
467
  en = oa_enrich.reindex(regs_norm).reset_index(drop=True)
@@ -474,11 +472,10 @@ def run_search(
474
  df_raw["openalex_url"] = _get("openalex_url", "").map(_norm_openalex_url)
475
  df_raw["orcid_url"] = _get("orcid_url", "").map(_norm_orcid_url)
476
 
477
- # только float + NaN: гарантирует корректную сортировку
478
- df_raw["h_index"] = pd.to_numeric(_get("h_index", np.nan), errors="coerce").astype("float64")
479
- df_raw["i10_index"] = pd.to_numeric(_get("i10_index", np.nan), errors="coerce").astype("float64")
480
- df_raw["works_count"] = pd.to_numeric(_get("works_count", np.nan), errors="coerce").astype("float64")
481
- df_raw["cited_by_count"] = pd.to_numeric(_get("cited_by_count", np.nan), errors="coerce").astype("float64")
482
  else:
483
  df_raw["openalex_url"] = ""
484
  df_raw["orcid_url"] = ""
@@ -487,31 +484,35 @@ def run_search(
487
  df_raw["works_count"] = np.nan
488
  df_raw["cited_by_count"] = np.nan
489
 
490
- # UI: ФИО (ссылка на ORCID если есть), Название (ссылка на ВАК если есть)
491
  fio_txt = df_raw["fio"].map(_safe_text)
492
- orcid_url = df_raw["orcid_url"].map(_safe_text)
493
- fio_cell = np.where(orcid_url != "", orcid_url + "#" + fio_txt.map(_safe_fragment), fio_txt)
494
 
495
  title_txt = df_raw["title"].map(_safe_text)
496
  vak_link = df_raw["vak_link"].map(_safe_text)
497
- title_cell = np.where(vak_link != "", vak_link + "#" + title_txt.map(_safe_fragment), title_txt)
498
 
499
  df_ui = pd.DataFrame(
500
  {
501
  "_rid": np.arange(len(df_raw)),
502
- "Сходство": pd.to_numeric(df_raw["score"], errors="coerce").astype("float64"),
503
- "ФИО": fio_cell,
504
- "Название диссертации": title_cell,
 
 
505
  "Организация": df_raw["author_org_short"].map(_safe_text),
506
- "Год": pd.to_numeric(df_raw["protection_year"], errors="coerce").astype("float64"),
507
  "OpenAlex": df_raw["openalex_url"].map(_safe_text),
508
- "h-index": df_raw["h_index"],
509
- "i10-index": df_raw["i10_index"],
510
- "Работ": df_raw["works_count"],
511
- "Цитат": df_raw["cited_by_count"],
512
  }
513
  ).set_index("_rid", drop=True)
514
 
 
 
 
 
515
  # Excel
516
  df_excel_ru = df_raw.rename(columns=COLUMN_LABELS_RU_EXCEL)
517
  output = io.BytesIO()
@@ -638,7 +639,9 @@ if isinstance(df_ui_saved, pd.DataFrame) and not df_ui_saved.empty:
638
  selection_mode="multi-row",
639
  column_order=[
640
  "Сходство",
 
641
  "ФИО",
 
642
  "Название диссертации",
643
  "Организация",
644
  "Год",
@@ -650,27 +653,34 @@ if isinstance(df_ui_saved, pd.DataFrame) and not df_ui_saved.empty:
650
  ],
651
  column_config={
652
  "Сходство": st.column_config.NumberColumn("Сходство", format="%.4f", width="small"),
653
- "ФИО": st.column_config.LinkColumn(
654
- "ФИО",
655
- display_text=r"(?:.*#)?(.*)$",
656
- width="medium",
657
- help="Если для автора найден ORCID — ФИО кликабельно и ведёт на ORCID.",
658
- ),
659
- "Название диссертации": st.column_config.LinkColumn(
660
- "Название диссертации",
661
- display_text=r"(?:.*#)?(.*)$",
662
- width="large",
663
- help="Если есть ссылка ВАК — название кликабельно и ведёт на ВАК.",
664
- ),
 
 
 
 
665
  "Организация": st.column_config.TextColumn("Организация", width="large"),
 
666
  "Год": st.column_config.NumberColumn("Год", format="%.0f", width="small"),
 
667
  "OpenAlex": st.column_config.LinkColumn(
668
  "OpenAlex",
669
  display_text=r"https://openalex\.org/(A\d+)",
670
  width="small",
671
  help="Профиль автора в OpenAlex (если найден).",
672
  ),
673
- # %.0f + NaN => пусто; главное — колонка numeric, сортировка правильная
 
674
  "h-index": st.column_config.NumberColumn("h-index", format="%.0f", width="small"),
675
  "i10-index": st.column_config.NumberColumn("i10-index", format="%.0f", width="small"),
676
  "Работ": st.column_config.NumberColumn("Работ", format="%.0f", width="small"),
@@ -682,7 +692,7 @@ if isinstance(df_ui_saved, pd.DataFrame) and not df_ui_saved.empty:
682
  if sel is not None and sel.rows is not None:
683
  st.session_state.selected_rids = list(sel.rows)
684
 
685
- # Блок с полными строками (вместо переноса в ячейках)
686
  if st.session_state.selected_rids:
687
  with st.expander("Полный текст (для выбранных строк)", expanded=False):
688
  for rid in st.session_state.selected_rids[:50]:
 
82
 
83
 
84
  # ==========================
85
+ # CSS (шрифт таблицы 80%)
86
  # ==========================
87
 
88
  st.markdown(
89
  """
90
  <style>
 
91
  div[data-testid="stDataFrame"] { font-size: 80% !important; }
92
  div[data-testid="stDataFrame"] * { font-size: 80% !important; }
93
 
 
122
  return s
123
 
124
 
 
 
 
 
 
125
  def _norm_openalex_url(x: Any) -> str:
126
  s = _safe_text(x)
127
  if not s:
 
146
  return "k_" + "".join(ch if ch.isalnum() else "_" for ch in label).strip("_")
147
 
148
 
149
+ def _as_float_series(s: Any) -> pd.Series:
150
+ """Гарантирует float64 + NaN (никаких None / object)."""
151
+ out = pd.to_numeric(s, errors="coerce")
152
+ return out.astype("float64")
153
+
154
+
155
  # ==========================
156
  # ФИЛЬТРЫ (UI)
157
  # ==========================
 
256
  if "orcid_url" in df.columns:
257
  df["orcid_url"] = df["orcid_url"].map(_norm_orcid_url)
258
 
 
259
  for c in ["h_index", "i10_index", "works_count", "cited_by_count"]:
260
  if c in df.columns:
261
+ df[c] = pd.to_numeric(df[c], errors="coerce").astype("float64")
262
 
263
  keep = ["reg_norm", "openalex_url", "orcid_url", "h_index", "i10_index", "works_count", "cited_by_count"]
264
  keep = [c for c in keep if c in df.columns]
 
460
  results = search_core(query, top_k, mask=mask)
461
  df_raw = build_result_df(results)
462
 
 
463
  if not df_raw.empty and not oa_enrich.empty:
464
  regs_norm = df_raw["registration_number"].map(_norm_regnum)
465
  en = oa_enrich.reindex(regs_norm).reset_index(drop=True)
 
472
  df_raw["openalex_url"] = _get("openalex_url", "").map(_norm_openalex_url)
473
  df_raw["orcid_url"] = _get("orcid_url", "").map(_norm_orcid_url)
474
 
475
+ df_raw["h_index"] = _as_float_series(_get("h_index", np.nan))
476
+ df_raw["i10_index"] = _as_float_series(_get("i10_index", np.nan))
477
+ df_raw["works_count"] = _as_float_series(_get("works_count", np.nan))
478
+ df_raw["cited_by_count"] = _as_float_series(_get("cited_by_count", np.nan))
 
479
  else:
480
  df_raw["openalex_url"] = ""
481
  df_raw["orcid_url"] = ""
 
484
  df_raw["works_count"] = np.nan
485
  df_raw["cited_by_count"] = np.nan
486
 
487
+ # UI: ФИО — текст; ORCID отдельная безымянная ссылка (пусто если нет)
488
  fio_txt = df_raw["fio"].map(_safe_text)
489
+ orcid_link = df_raw["orcid_url"].map(_safe_text)
 
490
 
491
  title_txt = df_raw["title"].map(_safe_text)
492
  vak_link = df_raw["vak_link"].map(_safe_text)
 
493
 
494
  df_ui = pd.DataFrame(
495
  {
496
  "_rid": np.arange(len(df_raw)),
497
+ "Сходство": _as_float_series(df_raw["score"]),
498
+ " ": orcid_link, # безымянная колонка-ссылка (ORCID)
499
+ "ФИО": fio_txt, # всегда текст (не ссылка)
500
+ " ": vak_link, # ещё одна безымянная колонка-ссылка (ВАК)
501
+ "Название диссертации": title_txt, # всегда текст (не ссылка)
502
  "Организация": df_raw["author_org_short"].map(_safe_text),
503
+ "Год": _as_float_series(df_raw["protection_year"]),
504
  "OpenAlex": df_raw["openalex_url"].map(_safe_text),
505
+ "h-index": _as_float_series(df_raw["h_index"]),
506
+ "i10-index": _as_float_series(df_raw["i10_index"]),
507
+ "Работ": _as_float_series(df_raw["works_count"]),
508
+ "Цитат": _as_float_series(df_raw["cited_by_count"]),
509
  }
510
  ).set_index("_rid", drop=True)
511
 
512
+ # гарантированно убрать "None" в текстовых колонках (если вдруг прилетело строкой)
513
+ for c in df_ui.select_dtypes(include=["object"]).columns:
514
+ df_ui[c] = df_ui[c].map(_safe_text)
515
+
516
  # Excel
517
  df_excel_ru = df_raw.rename(columns=COLUMN_LABELS_RU_EXCEL)
518
  output = io.BytesIO()
 
639
  selection_mode="multi-row",
640
  column_order=[
641
  "Сходство",
642
+ " ",
643
  "ФИО",
644
+ " ",
645
  "Название диссертации",
646
  "Организация",
647
  "Год",
 
653
  ],
654
  column_config={
655
  "Сходство": st.column_config.NumberColumn("Сходство", format="%.4f", width="small"),
656
+
657
+ # ORCID: ссылка только если URL не пустой
658
+ " ": st.column_config.LinkColumn("",
659
+ display_text="ORCID",
660
+ width="small",
661
+ help="Профиль ORCID (если найден)."),
662
+
663
+ "ФИО": st.column_config.TextColumn("ФИО", width="medium"),
664
+
665
+ # ВАК: ссылка только если URL не пустой
666
+ " ": st.column_config.LinkColumn("",
667
+ display_text="ВАК",
668
+ width="small",
669
+ help="Открыть запись на сайте ВАК (если есть ссылка)."),
670
+
671
+ "Название диссертации": st.column_config.TextColumn("Название диссертации", width="large"),
672
  "Организация": st.column_config.TextColumn("Организация", width="large"),
673
+
674
  "Год": st.column_config.NumberColumn("Год", format="%.0f", width="small"),
675
+
676
  "OpenAlex": st.column_config.LinkColumn(
677
  "OpenAlex",
678
  display_text=r"https://openalex\.org/(A\d+)",
679
  width="small",
680
  help="Профиль автора в OpenAlex (если найден).",
681
  ),
682
+
683
+ # Форматирование + NaN => пустая ячейка; сортировка numeric
684
  "h-index": st.column_config.NumberColumn("h-index", format="%.0f", width="small"),
685
  "i10-index": st.column_config.NumberColumn("i10-index", format="%.0f", width="small"),
686
  "Работ": st.column_config.NumberColumn("Работ", format="%.0f", width="small"),
 
692
  if sel is not None and sel.rows is not None:
693
  st.session_state.selected_rids = list(sel.rows)
694
 
695
+ # Компенсация отсутствия word-wrap: показываем полный текст выбранных строк
696
  if st.session_state.selected_rids:
697
  with st.expander("Полный текст (для выбранных строк)", expanded=False):
698
  for rid in st.session_state.selected_rids[:50]: