Update app.py
Browse files
app.py
CHANGED
|
@@ -82,12 +82,13 @@ except Exception:
|
|
| 82 |
|
| 83 |
|
| 84 |
# ==========================
|
| 85 |
-
# CSS
|
| 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,6 +123,11 @@ def _safe_text(x: Any) -> str:
|
|
| 122 |
return s
|
| 123 |
|
| 124 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 125 |
def _norm_openalex_url(x: Any) -> str:
|
| 126 |
s = _safe_text(x)
|
| 127 |
if not s:
|
|
@@ -146,12 +152,6 @@ def _keyify(label: str) -> str:
|
|
| 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 |
# ==========================
|
|
@@ -258,7 +258,7 @@ def load_oa_enrichment() -> pd.DataFrame:
|
|
| 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")
|
| 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]
|
|
@@ -428,13 +428,18 @@ def build_result_df(results):
|
|
| 428 |
|
| 429 |
protection_year = extract_year_int(meta.get("protection_date", None))
|
| 430 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 431 |
rows.append(
|
| 432 |
{
|
| 433 |
"№": r["rank"],
|
| 434 |
"score": float(round(score, 4)),
|
| 435 |
"fio": meta.get("fio", None),
|
| 436 |
"title": meta.get("title", None),
|
| 437 |
-
"author_org_short":
|
| 438 |
"dissertation_type": meta.get("dissertation_type", None),
|
| 439 |
"protection_year": protection_year,
|
| 440 |
"registration_number": meta.get("registration_number", reg),
|
|
@@ -472,10 +477,10 @@ def run_search(
|
|
| 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"] =
|
| 476 |
-
df_raw["i10_index"] =
|
| 477 |
-
df_raw["works_count"] =
|
| 478 |
-
df_raw["cited_by_count"] =
|
| 479 |
else:
|
| 480 |
df_raw["openalex_url"] = ""
|
| 481 |
df_raw["orcid_url"] = ""
|
|
@@ -484,36 +489,31 @@ def run_search(
|
|
| 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 |
-
|
|
|
|
| 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 |
-
"Сходство":
|
| 498 |
-
"
|
| 499 |
-
"
|
| 500 |
-
|
| 501 |
-
"Название диссертации": title_txt, # всегда текст (не ссылка)
|
| 502 |
"Организация": df_raw["author_org_short"].map(_safe_text),
|
| 503 |
-
"Год":
|
| 504 |
"OpenAlex": df_raw["openalex_url"].map(_safe_text),
|
| 505 |
-
"h-index":
|
| 506 |
-
"i10-index":
|
| 507 |
-
"Работ":
|
| 508 |
-
"Цитат":
|
| 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()
|
| 519 |
with pd.ExcelWriter(output, engine="xlsxwriter") as writer:
|
|
@@ -639,9 +639,7 @@ if isinstance(df_ui_saved, pd.DataFrame) and not df_ui_saved.empty:
|
|
| 639 |
selection_mode="multi-row",
|
| 640 |
column_order=[
|
| 641 |
"Сходство",
|
| 642 |
-
" ",
|
| 643 |
"ФИО",
|
| 644 |
-
" ",
|
| 645 |
"Название диссертации",
|
| 646 |
"Организация",
|
| 647 |
"Год",
|
|
@@ -653,34 +651,26 @@ if isinstance(df_ui_saved, pd.DataFrame) and not df_ui_saved.empty:
|
|
| 653 |
],
|
| 654 |
column_config={
|
| 655 |
"Сходство": st.column_config.NumberColumn("Сходство", format="%.4f", width="small"),
|
| 656 |
-
|
| 657 |
-
|
| 658 |
-
|
| 659 |
-
|
| 660 |
-
|
| 661 |
-
|
| 662 |
-
|
| 663 |
-
|
| 664 |
-
|
| 665 |
-
|
| 666 |
-
|
| 667 |
-
|
| 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,7 +682,6 @@ if isinstance(df_ui_saved, pd.DataFrame) and not df_ui_saved.empty:
|
|
| 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]:
|
|
|
|
| 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 |
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 |
return "k_" + "".join(ch if ch.isalnum() else "_" for ch in label).strip("_")
|
| 153 |
|
| 154 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 155 |
# ==========================
|
| 156 |
# ФИЛЬТРЫ (UI)
|
| 157 |
# ==========================
|
|
|
|
| 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")
|
| 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]
|
|
|
|
| 428 |
|
| 429 |
protection_year = extract_year_int(meta.get("protection_date", None))
|
| 430 |
|
| 431 |
+
# ВАЖНО: используем author_org_short (fallback на author_org_name если вдруг короткого нет)
|
| 432 |
+
org_short = meta.get("author_org_short", None)
|
| 433 |
+
if org_short is None or (isinstance(org_short, float) and pd.isna(org_short)) or str(org_short).lower() in {"none", "nan"}:
|
| 434 |
+
org_short = meta.get("author_org_name", None)
|
| 435 |
+
|
| 436 |
rows.append(
|
| 437 |
{
|
| 438 |
"№": r["rank"],
|
| 439 |
"score": float(round(score, 4)),
|
| 440 |
"fio": meta.get("fio", None),
|
| 441 |
"title": meta.get("title", None),
|
| 442 |
+
"author_org_short": org_short,
|
| 443 |
"dissertation_type": meta.get("dissertation_type", None),
|
| 444 |
"protection_year": protection_year,
|
| 445 |
"registration_number": meta.get("registration_number", reg),
|
|
|
|
| 477 |
df_raw["openalex_url"] = _get("openalex_url", "").map(_norm_openalex_url)
|
| 478 |
df_raw["orcid_url"] = _get("orcid_url", "").map(_norm_orcid_url)
|
| 479 |
|
| 480 |
+
df_raw["h_index"] = pd.to_numeric(_get("h_index", np.nan), errors="coerce").astype("float64")
|
| 481 |
+
df_raw["i10_index"] = pd.to_numeric(_get("i10_index", np.nan), errors="coerce").astype("float64")
|
| 482 |
+
df_raw["works_count"] = pd.to_numeric(_get("works_count", np.nan), errors="coerce").astype("float64")
|
| 483 |
+
df_raw["cited_by_count"] = pd.to_numeric(_get("cited_by_count", np.nan), errors="coerce").astype("float64")
|
| 484 |
else:
|
| 485 |
df_raw["openalex_url"] = ""
|
| 486 |
df_raw["orcid_url"] = ""
|
|
|
|
| 489 |
df_raw["works_count"] = np.nan
|
| 490 |
df_raw["cited_by_count"] = np.nan
|
| 491 |
|
|
|
|
| 492 |
fio_txt = df_raw["fio"].map(_safe_text)
|
| 493 |
+
orcid_url = df_raw["orcid_url"].map(_safe_text)
|
| 494 |
+
fio_cell = np.where(orcid_url != "", orcid_url + "#" + fio_txt.map(_safe_fragment), fio_txt)
|
| 495 |
|
| 496 |
title_txt = df_raw["title"].map(_safe_text)
|
| 497 |
vak_link = df_raw["vak_link"].map(_safe_text)
|
| 498 |
+
title_cell = np.where(vak_link != "", vak_link + "#" + title_txt.map(_safe_fragment), title_txt)
|
| 499 |
|
| 500 |
df_ui = pd.DataFrame(
|
| 501 |
{
|
| 502 |
"_rid": np.arange(len(df_raw)),
|
| 503 |
+
"Сходство": pd.to_numeric(df_raw["score"], errors="coerce").astype("float64"),
|
| 504 |
+
"ФИО": fio_cell,
|
| 505 |
+
"Название диссертации": title_cell,
|
| 506 |
+
# ВАЖНО: отображаем author_org_short
|
|
|
|
| 507 |
"Организация": df_raw["author_org_short"].map(_safe_text),
|
| 508 |
+
"Год": pd.to_numeric(df_raw["protection_year"], errors="coerce").astype("float64"),
|
| 509 |
"OpenAlex": df_raw["openalex_url"].map(_safe_text),
|
| 510 |
+
"h-index": df_raw["h_index"],
|
| 511 |
+
"i10-index": df_raw["i10_index"],
|
| 512 |
+
"Работ": df_raw["works_count"],
|
| 513 |
+
"Цитат": df_raw["cited_by_count"],
|
| 514 |
}
|
| 515 |
).set_index("_rid", drop=True)
|
| 516 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 517 |
df_excel_ru = df_raw.rename(columns=COLUMN_LABELS_RU_EXCEL)
|
| 518 |
output = io.BytesIO()
|
| 519 |
with pd.ExcelWriter(output, engine="xlsxwriter") as writer:
|
|
|
|
| 639 |
selection_mode="multi-row",
|
| 640 |
column_order=[
|
| 641 |
"Сходство",
|
|
|
|
| 642 |
"ФИО",
|
|
|
|
| 643 |
"Название диссертации",
|
| 644 |
"Организация",
|
| 645 |
"Год",
|
|
|
|
| 651 |
],
|
| 652 |
column_config={
|
| 653 |
"Сходство": st.column_config.NumberColumn("Сходство", format="%.4f", width="small"),
|
| 654 |
+
"ФИО": st.column_config.LinkColumn(
|
| 655 |
+
"ФИО",
|
| 656 |
+
display_text=r"(?:.*#)?(.*)$",
|
| 657 |
+
width="medium",
|
| 658 |
+
help="Если для автора найден ORCID — ФИО кликабельно и ведёт на ORCID.",
|
| 659 |
+
),
|
| 660 |
+
"Название диссертации": st.column_config.LinkColumn(
|
| 661 |
+
"Название диссертации",
|
| 662 |
+
display_text=r"(?:.*#)?(.*)$",
|
| 663 |
+
width="large",
|
| 664 |
+
help="Если есть ссылка ВАК — название кликабельно и ведёт на ВАК.",
|
| 665 |
+
),
|
|
|
|
|
|
|
|
|
|
|
|
|
| 666 |
"Организация": st.column_config.TextColumn("Организация", width="large"),
|
|
|
|
| 667 |
"Год": st.column_config.NumberColumn("Год", format="%.0f", width="small"),
|
|
|
|
| 668 |
"OpenAlex": st.column_config.LinkColumn(
|
| 669 |
"OpenAlex",
|
| 670 |
display_text=r"https://openalex\.org/(A\d+)",
|
| 671 |
width="small",
|
| 672 |
help="Профиль автора в OpenAlex (если найден).",
|
| 673 |
),
|
|
|
|
|
|
|
| 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 |
if sel is not None and sel.rows is not None:
|
| 683 |
st.session_state.selected_rids = list(sel.rows)
|
| 684 |
|
|
|
|
| 685 |
if st.session_state.selected_rids:
|
| 686 |
with st.expander("Полный текст (для выбранных строк)", expanded=False):
|
| 687 |
for rid in st.session_state.selected_rids[:50]:
|