yogl commited on
Commit
b699a2f
·
verified ·
1 Parent(s): 2aa2a22

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +75 -42
app.py CHANGED
@@ -2,9 +2,8 @@ import os
2
  import io
3
  import json
4
  import uuid
5
- import re
6
- from typing import Optional, Tuple, List, Dict, Any
7
  from datetime import datetime, timezone
 
8
 
9
  import numpy as np
10
  import pandas as pd
@@ -30,7 +29,7 @@ HF_REQUESTS_REPO = os.getenv("HF_REQUESTS_REPO", "PlanetExpress2125/PostDocReque
30
  HF_REQUESTS_REPO_TYPE = os.getenv("HF_REQUESTS_REPO_TYPE", "dataset") # dataset | model
31
  HF_WRITE_TOKEN = os.getenv("HF_WRITE_TOKEN") # write token (рекомендуется)
32
 
33
- # Компактное OA-обогащение (вы уже собрали)
34
  OA_ENRICH_REPO = os.getenv("OA_ENRICH_REPO", "yogl/postdoc_oa_enrichment")
35
 
36
  SLIDER_MIN_YEAR = 2005 # нижняя граница диапазона лет
@@ -84,6 +83,22 @@ except Exception:
84
  pass
85
 
86
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
87
  # ==========================
88
  # СПРАВОЧНИКИ ФИЛЬТРОВ (UI)
89
  # ==========================
@@ -112,12 +127,7 @@ SCIENCE_LABELS = [
112
  ]
113
  SCIENCE_LABELS = sorted(list(dict.fromkeys(SCIENCE_LABELS)), key=lambda s: s.casefold())
114
 
115
- DEFAULT_SCIENCES = {
116
- "Технические",
117
- "Физико-математические",
118
- "Химические",
119
- "Биологические",
120
- }
121
 
122
  SCIENCE_PATTERNS = {
123
  "Архитектура": ["архитектур"],
@@ -143,10 +153,6 @@ SCIENCE_PATTERNS = {
143
  }
144
 
145
 
146
- def _keyify(label: str) -> str:
147
- return "k_" + "".join(ch if ch.isalnum() else "_" for ch in label).strip("_")
148
-
149
-
150
  # ==========================
151
  # ЗАПИСЬ ЗАЯВОК В HF REPO
152
  # ==========================
@@ -186,10 +192,24 @@ def load_oa_enrichment() -> pd.DataFrame:
186
  """
187
  ds = load_dataset(OA_ENRICH_REPO, split="train")
188
  df = ds.to_pandas()
 
189
  if "registration_number" not in df.columns:
190
- return pd.DataFrame().set_index(pd.Index([], name="registration_number"))
191
- df["registration_number"] = df["registration_number"].astype(str)
192
- return df.set_index("registration_number", drop=True)
 
 
 
 
 
 
 
 
 
 
 
 
 
193
 
194
 
195
  oa_enrich = load_oa_enrichment()
@@ -203,12 +223,12 @@ oa_enrich = load_oa_enrichment()
203
  def load_data():
204
  ds_meta = load_dataset(HF_MERGED_REPO, split="train")
205
  df_meta = ds_meta.to_pandas()
206
- df_meta["registration_number"] = df_meta["registration_number"].astype(str)
207
  df_meta = df_meta.set_index("registration_number", drop=False)
208
 
209
  ds_emb = load_dataset(HF_EMB_REPO, split="train")
210
  df_emb = ds_emb.to_pandas()
211
- df_emb["registration_number"] = df_emb["registration_number"].astype(str)
212
 
213
  reg_nums = df_emb["registration_number"].tolist()
214
 
@@ -340,7 +360,7 @@ def extract_year_int(value) -> Optional[int]:
340
  def build_result_df(results):
341
  rows = []
342
  for r in results:
343
- reg = r["registration_number"]
344
  score = r["score"]
345
 
346
  if reg in df_all.index:
@@ -384,27 +404,34 @@ def run_search(
384
  results = search_core(query, top_k, mask=mask)
385
  df_raw = build_result_df(results)
386
 
387
- # OA enrichment join (по registration_number)
388
  if not df_raw.empty and not oa_enrich.empty:
389
- regs = df_raw["registration_number"].astype(str)
390
- en = oa_enrich.reindex(regs)
391
-
392
- def _col(name: str):
393
- return en[name] if name in en.columns else pd.Series([np.nan] * len(en), index=en.index)
394
-
395
- df_raw["openalex_url"] = _col("openalex_url").fillna("")
396
- df_raw["orcid_url"] = _col("orcid_url").fillna("")
397
- df_raw["h_index"] = _col("h_index")
398
- df_raw["i10_index"] = _col("i10_index")
399
- df_raw["works_count"] = _col("works_count")
400
- df_raw["cited_by_count"] = _col("cited_by_count")
 
 
 
 
 
 
 
401
  else:
402
  df_raw["openalex_url"] = ""
403
  df_raw["orcid_url"] = ""
404
- df_raw["h_index"] = np.nan
405
- df_raw["i10_index"] = np.nan
406
- df_raw["works_count"] = np.nan
407
- df_raw["cited_by_count"] = np.nan
408
 
409
  # UI: скрываем №, registration_number, dissertation_type
410
  df_ui = df_raw.copy().reset_index(drop=True)
@@ -457,8 +484,6 @@ st.markdown(
457
  )
458
 
459
  # session_state — чтобы результаты не исчезали на rerun
460
- if "last_df_ui" not in st.session_state:
461
- st.session_state.last_df_ui = None
462
  if "last_df_raw" not in st.session_state:
463
  st.session_state.last_df_raw = None
464
  if "last_excel" not in st.session_state:
@@ -533,11 +558,9 @@ if do_search:
533
  year_range=year_range,
534
  )
535
 
536
- st.session_state.last_df_ui = df_ui
537
  st.session_state.last_df_raw = df_raw
538
  st.session_state.last_excel = excel_bytes
539
 
540
- # подготовим таблицу с чекбоксом
541
  if not df_ui.empty:
542
  df_table = df_ui[
543
  ["Сходство", "ORCID", "ФИО", "Название диссертации", "Организация", "Год",
@@ -548,7 +571,7 @@ if do_search:
548
  else:
549
  st.session_state.table_df = None
550
 
551
- st.session_state.search_id += 1 # чтобы сбрасывать state редактора на новый поиск
552
 
553
  # Отображение результатов
554
  df_table_saved = st.session_state.table_df
@@ -558,7 +581,6 @@ excel_saved = st.session_state.last_excel
558
  if isinstance(df_table_saved, pd.DataFrame) and not df_table_saved.empty:
559
  st.success(f"Найдено записей: {len(df_table_saved)}")
560
 
561
- # data_editor: сортировка по клику на заголовок + чекбокс в строке
562
  editor_key = f"results_editor_{st.session_state.search_id}"
563
 
564
  edited = st.data_editor(
@@ -574,6 +596,7 @@ if isinstance(df_table_saved, pd.DataFrame) and not df_table_saved.empty:
574
  "ORCID",
575
  display_text=r"https://orcid\.org/([0-9X\-]+)",
576
  width="small",
 
577
  ),
578
  "ФИО": st.column_config.TextColumn("ФИО", width="medium"),
579
  "Название диссертации": st.column_config.TextColumn("Название диссертации", width="large"),
@@ -584,6 +607,7 @@ if isinstance(df_table_saved, pd.DataFrame) and not df_table_saved.empty:
584
  "OpenAlex",
585
  display_text=r"https://openalex\.org/(A\d+)",
586
  width="small",
 
587
  ),
588
  "h-index": st.column_config.NumberColumn("h-index", width="small"),
589
  "i10-index": st.column_config.NumberColumn("i10-index", width="small"),
@@ -595,6 +619,15 @@ if isinstance(df_table_saved, pd.DataFrame) and not df_table_saved.empty:
595
  # сохраняем чекбоксы
596
  st.session_state.table_df = edited
597
 
 
 
 
 
 
 
 
 
 
598
  if excel_saved is not None:
599
  st.download_button(
600
  label="💾 Скачать результаты в Excel",
 
2
  import io
3
  import json
4
  import uuid
 
 
5
  from datetime import datetime, timezone
6
+ from typing import Optional, Tuple, List, Any
7
 
8
  import numpy as np
9
  import pandas as pd
 
29
  HF_REQUESTS_REPO_TYPE = os.getenv("HF_REQUESTS_REPO_TYPE", "dataset") # dataset | model
30
  HF_WRITE_TOKEN = os.getenv("HF_WRITE_TOKEN") # write token (рекомендуется)
31
 
32
+ # Компактное OA-обогащение (собрано вами)
33
  OA_ENRICH_REPO = os.getenv("OA_ENRICH_REPO", "yogl/postdoc_oa_enrichment")
34
 
35
  SLIDER_MIN_YEAR = 2005 # нижняя граница диапазона лет
 
83
  pass
84
 
85
 
86
+ # ==========================
87
+ # ВСПОМОГАТЕЛЬНЫЕ ФУНКЦИИ
88
+ # ==========================
89
+
90
+ def _norm_regnum(x: Any) -> str:
91
+ s = "" if x is None else str(x).strip()
92
+ # иногда номера могут попасть как float "123.0"
93
+ if s.endswith(".0") and s[:-2].isdigit():
94
+ s = s[:-2]
95
+ return s
96
+
97
+
98
+ def _keyify(label: str) -> str:
99
+ return "k_" + "".join(ch if ch.isalnum() else "_" for ch in label).strip("_")
100
+
101
+
102
  # ==========================
103
  # СПРАВОЧНИКИ ФИЛЬТРОВ (UI)
104
  # ==========================
 
127
  ]
128
  SCIENCE_LABELS = sorted(list(dict.fromkeys(SCIENCE_LABELS)), key=lambda s: s.casefold())
129
 
130
+ DEFAULT_SCIENCES = {"Технические", "Физико-математические", "Химические", "Биологические"}
 
 
 
 
 
131
 
132
  SCIENCE_PATTERNS = {
133
  "Архитектура": ["архитектур"],
 
153
  }
154
 
155
 
 
 
 
 
156
  # ==========================
157
  # ЗАПИСЬ ЗАЯВОК В HF REPO
158
  # ==========================
 
192
  """
193
  ds = load_dataset(OA_ENRICH_REPO, split="train")
194
  df = ds.to_pandas()
195
+
196
  if "registration_number" not in df.columns:
197
+ return pd.DataFrame().set_index(pd.Index([], name="reg_norm"))
198
+
199
+ df["reg_norm"] = df["registration_number"].apply(_norm_regnum)
200
+
201
+ # совместимость, если в датасете использовались другие имена колонок
202
+ if "openalex_url" not in df.columns and "openalex" in df.columns:
203
+ df["openalex_url"] = df["openalex"]
204
+ if "orcid_url" not in df.columns and "orcid" in df.columns:
205
+ df["orcid_url"] = df["orcid"]
206
+
207
+ keep = ["reg_norm", "openalex_url", "orcid_url", "h_index", "i10_index", "works_count", "cited_by_count"]
208
+ keep = [c for c in keep if c in df.columns]
209
+ df = df[keep].copy()
210
+
211
+ df = df.drop_duplicates(subset=["reg_norm"]).set_index("reg_norm", drop=True)
212
+ return df
213
 
214
 
215
  oa_enrich = load_oa_enrichment()
 
223
  def load_data():
224
  ds_meta = load_dataset(HF_MERGED_REPO, split="train")
225
  df_meta = ds_meta.to_pandas()
226
+ df_meta["registration_number"] = df_meta["registration_number"].astype(str).map(_norm_regnum)
227
  df_meta = df_meta.set_index("registration_number", drop=False)
228
 
229
  ds_emb = load_dataset(HF_EMB_REPO, split="train")
230
  df_emb = ds_emb.to_pandas()
231
+ df_emb["registration_number"] = df_emb["registration_number"].astype(str).map(_norm_regnum)
232
 
233
  reg_nums = df_emb["registration_number"].tolist()
234
 
 
360
  def build_result_df(results):
361
  rows = []
362
  for r in results:
363
+ reg = _norm_regnum(r["registration_number"])
364
  score = r["score"]
365
 
366
  if reg in df_all.index:
 
404
  results = search_core(query, top_k, mask=mask)
405
  df_raw = build_result_df(results)
406
 
407
+ # OA enrichment join (выровнять по позициям, а не по индексу!)
408
  if not df_raw.empty and not oa_enrich.empty:
409
+ regs_norm = df_raw["registration_number"].apply(_norm_regnum)
410
+ en = oa_enrich.reindex(regs_norm).reset_index(drop=True) # <-- КЛЮЧЕВО
411
+
412
+ def _get(col: str, default):
413
+ if col in en.columns:
414
+ return en[col]
415
+ return pd.Series([default] * len(df_raw))
416
+
417
+ df_raw["openalex_url"] = _get("openalex_url", "").fillna("")
418
+ df_raw["orcid_url"] = _get("orcid_url", "").fillna("")
419
+
420
+ for src, dst in [
421
+ ("h_index", "h_index"),
422
+ ("i10_index", "i10_index"),
423
+ ("works_count", "works_count"),
424
+ ("cited_by_count", "cited_by_count"),
425
+ ]:
426
+ s = _get(src, np.nan)
427
+ df_raw[dst] = pd.to_numeric(s, errors="coerce").astype("Int64")
428
  else:
429
  df_raw["openalex_url"] = ""
430
  df_raw["orcid_url"] = ""
431
+ df_raw["h_index"] = pd.Series([pd.NA] * len(df_raw), dtype="Int64")
432
+ df_raw["i10_index"] = pd.Series([pd.NA] * len(df_raw), dtype="Int64")
433
+ df_raw["works_count"] = pd.Series([pd.NA] * len(df_raw), dtype="Int64")
434
+ df_raw["cited_by_count"] = pd.Series([pd.NA] * len(df_raw), dtype="Int64")
435
 
436
  # UI: скрываем №, registration_number, dissertation_type
437
  df_ui = df_raw.copy().reset_index(drop=True)
 
484
  )
485
 
486
  # session_state — чтобы результаты не исчезали на rerun
 
 
487
  if "last_df_raw" not in st.session_state:
488
  st.session_state.last_df_raw = None
489
  if "last_excel" not in st.session_state:
 
558
  year_range=year_range,
559
  )
560
 
 
561
  st.session_state.last_df_raw = df_raw
562
  st.session_state.last_excel = excel_bytes
563
 
 
564
  if not df_ui.empty:
565
  df_table = df_ui[
566
  ["Сходство", "ORCID", "ФИО", "Название диссертации", "Организация", "Год",
 
571
  else:
572
  st.session_state.table_df = None
573
 
574
+ st.session_state.search_id += 1 # сброс state редактора на новый поиск
575
 
576
  # Отображение результатов
577
  df_table_saved = st.session_state.table_df
 
581
  if isinstance(df_table_saved, pd.DataFrame) and not df_table_saved.empty:
582
  st.success(f"Найдено записей: {len(df_table_saved)}")
583
 
 
584
  editor_key = f"results_editor_{st.session_state.search_id}"
585
 
586
  edited = st.data_editor(
 
596
  "ORCID",
597
  display_text=r"https://orcid\.org/([0-9X\-]+)",
598
  width="small",
599
+ help="Профиль автора в ORCID (если найден)",
600
  ),
601
  "ФИО": st.column_config.TextColumn("ФИО", width="medium"),
602
  "Название диссертации": st.column_config.TextColumn("Название диссертации", width="large"),
 
607
  "OpenAlex",
608
  display_text=r"https://openalex\.org/(A\d+)",
609
  width="small",
610
+ help="Профиль автора в OpenAlex (если найден)",
611
  ),
612
  "h-index": st.column_config.NumberColumn("h-index", width="small"),
613
  "i10-index": st.column_config.NumberColumn("i10-index", width="small"),
 
619
  # сохраняем чекбоксы
620
  st.session_state.table_df = edited
621
 
622
+ # Диагностика соответствия reg -> OA (можно убрать позже)
623
+ with st.expander("Диагностика OpenAlex", expanded=False):
624
+ regs_norm = df_raw_saved["registration_number"].apply(_norm_regnum)
625
+ matched = regs_norm.isin(oa_enrich.index).sum() if not oa_enrich.empty else 0
626
+ st.write(f"Совпало registration_number → OA: {matched} из {len(df_raw_saved)}")
627
+ if matched == 0:
628
+ st.write("Пример registration_number из выдачи:", regs_norm.head(10).tolist())
629
+ st.write("Пример registration_number из enrichment:", list(oa_enrich.index[:10]) if not oa_enrich.empty else [])
630
+
631
  if excel_saved is not None:
632
  st.download_button(
633
  label="💾 Скачать результаты в Excel",