Update app.py
Browse files
app.py
CHANGED
|
@@ -71,6 +71,7 @@ DISPLAY_COLUMNS_ALL = [
|
|
| 71 |
# - Название диссертации -> ссылка на ВАК (если есть vak_link)
|
| 72 |
# - после "Год": OpenAlex (ID+ссылка), ORCID (ID+ссылка)
|
| 73 |
# - Тип по умолчанию скрыт
|
|
|
|
| 74 |
# ==========================
|
| 75 |
|
| 76 |
UI_TABLE_COLUMNS = [
|
|
@@ -82,15 +83,18 @@ UI_TABLE_COLUMNS = [
|
|
| 82 |
"Год",
|
| 83 |
"OpenAlex",
|
| 84 |
"ORCID",
|
| 85 |
-
|
| 86 |
"h-index",
|
| 87 |
"i10-index",
|
| 88 |
"Работ",
|
| 89 |
"Цитат",
|
| 90 |
]
|
| 91 |
|
| 92 |
-
# По умолчанию: все включены, кроме "Организация"
|
| 93 |
-
DEFAULT_VISIBLE_UI = {
|
|
|
|
|
|
|
|
|
|
| 94 |
|
| 95 |
|
| 96 |
# ==========================
|
|
@@ -513,7 +517,6 @@ def run_search(
|
|
| 513 |
):
|
| 514 |
mask = build_filter_mask(candidate_selected, doctor_selected, science_selected, year_range)
|
| 515 |
|
| 516 |
-
# С запасом, чтобы после OA/ORCID фильтров осталось до top_k
|
| 517 |
prefetch_k = min(max(int(top_k) * 5, int(top_k)), 500)
|
| 518 |
results = search_core(query, prefetch_k, mask=mask)
|
| 519 |
|
|
@@ -530,7 +533,6 @@ def run_search(
|
|
| 530 |
|
| 531 |
df_raw["reg_norm"] = df_raw["registration_number"].map(_norm_regnum)
|
| 532 |
|
| 533 |
-
# OA/ORCID enrichment
|
| 534 |
if not oa_enrich.empty:
|
| 535 |
en = oa_enrich.reindex(df_raw["reg_norm"]).reset_index(drop=True)
|
| 536 |
|
|
@@ -542,19 +544,18 @@ def run_search(
|
|
| 542 |
df_raw["openalex_url"] = _get("openalex_url", "").map(_norm_openalex_url)
|
| 543 |
df_raw["orcid_url"] = _get("orcid_url", "").map(_norm_orcid_url)
|
| 544 |
|
| 545 |
-
df_raw["h_index"] = pd.to_numeric(_get("h_index",
|
| 546 |
-
df_raw["i10_index"] = pd.to_numeric(_get("i10_index",
|
| 547 |
-
df_raw["works_count"] = pd.to_numeric(_get("works_count",
|
| 548 |
-
df_raw["cited_by_count"] = pd.to_numeric(_get("cited_by_count",
|
| 549 |
else:
|
| 550 |
df_raw["openalex_url"] = ""
|
| 551 |
df_raw["orcid_url"] = ""
|
| 552 |
-
df_raw["h_index"] =
|
| 553 |
-
df_raw["i10_index"] =
|
| 554 |
-
df_raw["works_count"] =
|
| 555 |
-
df_raw["cited_by_count"] =
|
| 556 |
|
| 557 |
-
# Фильтры "только с ..."
|
| 558 |
if only_openalex:
|
| 559 |
df_raw = df_raw[df_raw["openalex_url"].map(_safe_text) != ""]
|
| 560 |
if only_orcid:
|
|
@@ -564,15 +565,12 @@ def run_search(
|
|
| 564 |
if len(df_raw) > int(top_k):
|
| 565 |
df_raw = df_raw.iloc[: int(top_k)].copy()
|
| 566 |
|
| 567 |
-
# Дедуп и индекс-идентификатор для стабильного выбора
|
| 568 |
df_raw["reg_norm"] = df_raw["registration_number"].map(_norm_regnum)
|
| 569 |
df_raw = df_raw.drop_duplicates(subset=["reg_norm"]).set_index("reg_norm", drop=True)
|
| 570 |
|
| 571 |
-
# Перенумерация "№"
|
| 572 |
if "№" in df_raw.columns:
|
| 573 |
df_raw["№"] = np.arange(1, len(df_raw) + 1)
|
| 574 |
|
| 575 |
-
# ====== UI значения со ссылками ======
|
| 576 |
fio_txt = df_raw["fio"].map(_safe_text)
|
| 577 |
|
| 578 |
title_txt = df_raw["title"].map(_safe_text)
|
|
@@ -591,13 +589,13 @@ def run_search(
|
|
| 591 |
df_ui = pd.DataFrame(
|
| 592 |
{
|
| 593 |
"Сходство": pd.to_numeric(df_raw["score"], errors="coerce").astype("float64"),
|
| 594 |
-
"ФИО": fio_txt,
|
| 595 |
-
"Название диссертации": title_cell,
|
| 596 |
"Организация": df_raw["author_org_short"].map(_safe_text),
|
| 597 |
"Тип": df_raw["dissertation_type"].map(_safe_text),
|
| 598 |
"Год": pd.to_numeric(df_raw["protection_year"], errors="coerce").astype("float64"),
|
| 599 |
-
"OpenAlex": openalex_cell,
|
| 600 |
-
"ORCID": orcid_cell,
|
| 601 |
"Регистрационный номер": df_raw["registration_number"].map(_safe_text),
|
| 602 |
"h-index": df_raw["h_index"],
|
| 603 |
"i10-index": df_raw["i10_index"],
|
|
@@ -608,7 +606,6 @@ def run_search(
|
|
| 608 |
)
|
| 609 |
df_ui.index.name = "reg_norm"
|
| 610 |
|
| 611 |
-
# Excel
|
| 612 |
df_excel_ru = df_raw.reset_index(drop=True).rename(columns=COLUMN_LABELS_RU_EXCEL)
|
| 613 |
output = io.BytesIO()
|
| 614 |
with pd.ExcelWriter(output, engine="xlsxwriter") as writer:
|
|
@@ -662,11 +659,9 @@ if "last_df_raw" not in st.session_state:
|
|
| 662 |
if "last_excel" not in st.session_state:
|
| 663 |
st.session_state.last_excel = None
|
| 664 |
|
| 665 |
-
# выбор диссертаций хранится по reg_norm (стабильный ключ)
|
| 666 |
if "selected_regnorms" not in st.session_state:
|
| 667 |
st.session_state.selected_regnorms = set()
|
| 668 |
|
| 669 |
-
# ключ для data_editor (обновляем на новый поиск)
|
| 670 |
if "search_id" not in st.session_state:
|
| 671 |
st.session_state.search_id = 0
|
| 672 |
|
|
@@ -734,10 +729,9 @@ with st.form("search_form"):
|
|
| 734 |
with c2:
|
| 735 |
do_search = st.form_submit_button("🔍 Поиск", type="primary", use_container_width=True)
|
| 736 |
|
| 737 |
-
# выбранные для отображения колонки (после формы)
|
| 738 |
visible_ui_cols = [c for c in UI_TABLE_COLUMNS if st.session_state.get(_show_col_key(c), True)]
|
| 739 |
if not visible_ui_cols:
|
| 740 |
-
visible_ui_cols = [c for c in UI_TABLE_COLUMNS if c not in {"Организация", "Тип"}]
|
| 741 |
|
| 742 |
if do_search:
|
| 743 |
if not candidate_selected and not doctor_selected:
|
|
@@ -759,7 +753,6 @@ if do_search:
|
|
| 759 |
st.session_state.last_df_raw = df_raw
|
| 760 |
st.session_state.last_excel = excel_bytes
|
| 761 |
|
| 762 |
-
# выбор не сбрасываем — оставляем только те, что есть в новых результатах
|
| 763 |
if isinstance(df_ui, pd.DataFrame) and not df_ui.empty:
|
| 764 |
st.session_state.selected_regnorms = set(st.session_state.selected_regnorms) & set(df_ui.index)
|
| 765 |
else:
|
|
@@ -777,7 +770,6 @@ if isinstance(df_ui_saved, pd.DataFrame) and not df_ui_saved.empty:
|
|
| 777 |
selected_set = set(st.session_state.selected_regnorms) & set(df_ui_saved.index)
|
| 778 |
st.session_state.selected_regnorms = selected_set
|
| 779 |
|
| 780 |
-
# таблица + колонка выбора
|
| 781 |
df_display = df_ui_saved.copy()
|
| 782 |
df_display.insert(0, "Выбрать", df_display.index.map(lambda x: x in selected_set))
|
| 783 |
|
|
@@ -830,7 +822,6 @@ if isinstance(df_ui_saved, pd.DataFrame) and not df_ui_saved.empty:
|
|
| 830 |
key=f"editor_{st.session_state.search_id}",
|
| 831 |
)
|
| 832 |
|
| 833 |
-
# синхронизация выбора
|
| 834 |
if isinstance(edited, pd.DataFrame) and "Выбрать" in edited.columns:
|
| 835 |
st.session_state.selected_regnorms = set(edited.index[edited["Выбрать"] == True].tolist())
|
| 836 |
|
|
|
|
| 71 |
# - Название диссертации -> ссылка на ВАК (если есть vak_link)
|
| 72 |
# - после "Год": OpenAlex (ID+ссылка), ORCID (ID+ссылка)
|
| 73 |
# - Тип по умолчанию скрыт
|
| 74 |
+
# - Регистрационный номер по умолчанию скрыт
|
| 75 |
# ==========================
|
| 76 |
|
| 77 |
UI_TABLE_COLUMNS = [
|
|
|
|
| 83 |
"Год",
|
| 84 |
"OpenAlex",
|
| 85 |
"ORCID",
|
| 86 |
+
"Регистрационный номер",
|
| 87 |
"h-index",
|
| 88 |
"i10-index",
|
| 89 |
"Работ",
|
| 90 |
"Цитат",
|
| 91 |
]
|
| 92 |
|
| 93 |
+
# По умолчанию: все включены, кроме "Организация", "Тип" и "Регистрационный номер"
|
| 94 |
+
DEFAULT_VISIBLE_UI = {
|
| 95 |
+
c: (c not in {"Организация", "Тип", "Регистрационный номер"})
|
| 96 |
+
for c in UI_TABLE_COLUMNS
|
| 97 |
+
}
|
| 98 |
|
| 99 |
|
| 100 |
# ==========================
|
|
|
|
| 517 |
):
|
| 518 |
mask = build_filter_mask(candidate_selected, doctor_selected, science_selected, year_range)
|
| 519 |
|
|
|
|
| 520 |
prefetch_k = min(max(int(top_k) * 5, int(top_k)), 500)
|
| 521 |
results = search_core(query, prefetch_k, mask=mask)
|
| 522 |
|
|
|
|
| 533 |
|
| 534 |
df_raw["reg_norm"] = df_raw["registration_number"].map(_norm_regnum)
|
| 535 |
|
|
|
|
| 536 |
if not oa_enrich.empty:
|
| 537 |
en = oa_enrich.reindex(df_raw["reg_norm"]).reset_index(drop=True)
|
| 538 |
|
|
|
|
| 544 |
df_raw["openalex_url"] = _get("openalex_url", "").map(_norm_openalex_url)
|
| 545 |
df_raw["orcid_url"] = _get("orcid_url", "").map(_norm_orcid_url)
|
| 546 |
|
| 547 |
+
df_raw["h_index"] = pd.to_numeric(_get("h_index", np.nan), errors="coerce").astype("float64")
|
| 548 |
+
df_raw["i10_index"] = pd.to_numeric(_get("i10_index", np.nan), errors="coerce").astype("float64")
|
| 549 |
+
df_raw["works_count"] = pd.to_numeric(_get("works_count", np.nan), errors="coerce").astype("float64")
|
| 550 |
+
df_raw["cited_by_count"] = pd.to_numeric(_get("cited_by_count", np.nan), errors="coerce").astype("float64")
|
| 551 |
else:
|
| 552 |
df_raw["openalex_url"] = ""
|
| 553 |
df_raw["orcid_url"] = ""
|
| 554 |
+
df_raw["h_index"] = np.nan
|
| 555 |
+
df_raw["i10_index"] = np.nan
|
| 556 |
+
df_raw["works_count"] = np.nan
|
| 557 |
+
df_raw["cited_by_count"] = np.nan
|
| 558 |
|
|
|
|
| 559 |
if only_openalex:
|
| 560 |
df_raw = df_raw[df_raw["openalex_url"].map(_safe_text) != ""]
|
| 561 |
if only_orcid:
|
|
|
|
| 565 |
if len(df_raw) > int(top_k):
|
| 566 |
df_raw = df_raw.iloc[: int(top_k)].copy()
|
| 567 |
|
|
|
|
| 568 |
df_raw["reg_norm"] = df_raw["registration_number"].map(_norm_regnum)
|
| 569 |
df_raw = df_raw.drop_duplicates(subset=["reg_norm"]).set_index("reg_norm", drop=True)
|
| 570 |
|
|
|
|
| 571 |
if "№" in df_raw.columns:
|
| 572 |
df_raw["№"] = np.arange(1, len(df_raw) + 1)
|
| 573 |
|
|
|
|
| 574 |
fio_txt = df_raw["fio"].map(_safe_text)
|
| 575 |
|
| 576 |
title_txt = df_raw["title"].map(_safe_text)
|
|
|
|
| 589 |
df_ui = pd.DataFrame(
|
| 590 |
{
|
| 591 |
"Сходство": pd.to_numeric(df_raw["score"], errors="coerce").astype("float64"),
|
| 592 |
+
"ФИО": fio_txt,
|
| 593 |
+
"Название диссертации": title_cell,
|
| 594 |
"Организация": df_raw["author_org_short"].map(_safe_text),
|
| 595 |
"Тип": df_raw["dissertation_type"].map(_safe_text),
|
| 596 |
"Год": pd.to_numeric(df_raw["protection_year"], errors="coerce").astype("float64"),
|
| 597 |
+
"OpenAlex": openalex_cell,
|
| 598 |
+
"ORCID": orcid_cell,
|
| 599 |
"Регистрационный номер": df_raw["registration_number"].map(_safe_text),
|
| 600 |
"h-index": df_raw["h_index"],
|
| 601 |
"i10-index": df_raw["i10_index"],
|
|
|
|
| 606 |
)
|
| 607 |
df_ui.index.name = "reg_norm"
|
| 608 |
|
|
|
|
| 609 |
df_excel_ru = df_raw.reset_index(drop=True).rename(columns=COLUMN_LABELS_RU_EXCEL)
|
| 610 |
output = io.BytesIO()
|
| 611 |
with pd.ExcelWriter(output, engine="xlsxwriter") as writer:
|
|
|
|
| 659 |
if "last_excel" not in st.session_state:
|
| 660 |
st.session_state.last_excel = None
|
| 661 |
|
|
|
|
| 662 |
if "selected_regnorms" not in st.session_state:
|
| 663 |
st.session_state.selected_regnorms = set()
|
| 664 |
|
|
|
|
| 665 |
if "search_id" not in st.session_state:
|
| 666 |
st.session_state.search_id = 0
|
| 667 |
|
|
|
|
| 729 |
with c2:
|
| 730 |
do_search = st.form_submit_button("🔍 Поиск", type="primary", use_container_width=True)
|
| 731 |
|
|
|
|
| 732 |
visible_ui_cols = [c for c in UI_TABLE_COLUMNS if st.session_state.get(_show_col_key(c), True)]
|
| 733 |
if not visible_ui_cols:
|
| 734 |
+
visible_ui_cols = [c for c in UI_TABLE_COLUMNS if c not in {"Организация", "Тип", "Регистрационный номер"}]
|
| 735 |
|
| 736 |
if do_search:
|
| 737 |
if not candidate_selected and not doctor_selected:
|
|
|
|
| 753 |
st.session_state.last_df_raw = df_raw
|
| 754 |
st.session_state.last_excel = excel_bytes
|
| 755 |
|
|
|
|
| 756 |
if isinstance(df_ui, pd.DataFrame) and not df_ui.empty:
|
| 757 |
st.session_state.selected_regnorms = set(st.session_state.selected_regnorms) & set(df_ui.index)
|
| 758 |
else:
|
|
|
|
| 770 |
selected_set = set(st.session_state.selected_regnorms) & set(df_ui_saved.index)
|
| 771 |
st.session_state.selected_regnorms = selected_set
|
| 772 |
|
|
|
|
| 773 |
df_display = df_ui_saved.copy()
|
| 774 |
df_display.insert(0, "Выбрать", df_display.index.map(lambda x: x in selected_set))
|
| 775 |
|
|
|
|
| 822 |
key=f"editor_{st.session_state.search_id}",
|
| 823 |
)
|
| 824 |
|
|
|
|
| 825 |
if isinstance(edited, pd.DataFrame) and "Выбрать" in edited.columns:
|
| 826 |
st.session_state.selected_regnorms = set(edited.index[edited["Выбрать"] == True].tolist())
|
| 827 |
|