yogl commited on
Commit
e66288a
·
verified ·
1 Parent(s): 3af7305

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +97 -73
app.py CHANGED
@@ -1,12 +1,12 @@
1
  import os
2
  import io
3
- import json
4
 
5
  import numpy as np
6
  import pandas as pd
7
  import streamlit as st
8
  from sentence_transformers import SentenceTransformer
9
- from huggingface_hub import hf_hub_download
 
10
 
11
  # ==========================
12
  # НАСТРОЙКИ ПРИЛОЖЕНИЯ
@@ -17,43 +17,33 @@ st.set_page_config(
17
  layout="wide",
18
  )
19
 
20
- # ---- Hugging Face ----
21
  HF_TOKEN = os.getenv("HF_TOKEN")
22
- HF_DATA_REPO = os.getenv("HF_DATA_REPO", "yogl/dissertation-search-data")
23
-
24
- ALL_DATA_FILENAME = "ALL_data.xlsx"
25
- EMBEDDINGS_FILENAME = "embeddings_full.json"
26
 
27
  MODEL_NAME = os.getenv("MODEL_NAME", "intfloat/multilingual-e5-base")
28
 
29
- # Порядок колонок в выдаче (внутренние имена)
30
- COLUMNS_ORDER = [
31
- "registration_number",
32
- "name",
33
- "author_surname",
34
- "author_name",
35
- "author_patronymic",
36
- "author_organization__short_name",
37
- "dissertation_type",
38
- "created_date",
39
- "abstract",
40
- ]
41
-
42
  # Отображаемые заголовки (русские имена колонок)
43
  COLUMN_LABELS_RU = {
44
  "№": "№",
45
  "score": "Сходство",
46
- "registration_number": "Регистрационный номер",
47
- "name": "Название работы",
48
- "author_surname": "Фамилия",
49
- "author_name": "Имя",
50
- "author_patronymic": "Отчество",
51
- "author_organization__short_name": "Организация",
52
  "dissertation_type": "Тип",
53
- "created_date": "Дата создания",
54
- "abstract": "Аннотация",
55
  }
56
 
 
 
 
 
 
 
 
 
 
 
57
  if HF_TOKEN is None:
58
  st.error(
59
  "Не найден секрет `HF_TOKEN`. "
@@ -61,43 +51,48 @@ if HF_TOKEN is None:
61
  )
62
  st.stop()
63
 
 
 
 
 
 
 
 
64
  # ==========================
65
  # ЗАГРУЗКА ДАННЫХ И МОДЕЛИ
66
  # ==========================
67
 
68
- @st.cache_data(show_spinner="Скачиваю и загружаю данные из приватного датасета...")
69
  def load_data():
70
- all_data_path = hf_hub_download(
71
- repo_id=HF_DATA_REPO,
72
- filename=ALL_DATA_FILENAME,
73
- repo_type="dataset",
74
- token=HF_TOKEN,
75
  )
76
-
77
- embeddings_path = hf_hub_download(
78
- repo_id=HF_DATA_REPO,
79
- filename=EMBEDDINGS_FILENAME,
80
- repo_type="dataset",
81
- token=HF_TOKEN,
 
 
 
82
  )
 
83
 
84
- df_all = pd.read_excel(all_data_path)
85
- df_all["registration_number"] = df_all["registration_number"].astype(str)
86
- df_all = df_all.set_index("registration_number", drop=False)
87
-
88
- with open(embeddings_path, "r", encoding="utf-8") as f:
89
- emb_data = json.load(f)
90
 
91
- reg_nums = [str(item["registration_number"]) for item in emb_data]
92
- emb_matrix = np.array(
93
- [item["embedding"] for item in emb_data],
94
- dtype="float32",
95
- )
96
 
 
97
  norms = np.linalg.norm(emb_matrix, axis=1, keepdims=True)
98
  emb_matrix = emb_matrix / np.maximum(norms, 1e-8)
99
 
100
- return df_all, reg_nums, emb_matrix
101
 
102
 
103
  @st.cache_resource(show_spinner="Загружаю модель для эмбеддингов...")
@@ -123,6 +118,7 @@ def search_core(query: str, top_k: int = 10):
123
  if not query:
124
  return []
125
 
 
126
  query_text = "query: " + query
127
 
128
  q_emb = model.encode(
@@ -160,46 +156,70 @@ def build_result_df(results):
160
  else:
161
  meta = pd.Series({}, index=df_all.columns)
162
 
 
163
  row = {
164
  "№": r["rank"],
165
  "score": round(score, 4),
 
 
 
 
 
 
 
166
  }
167
 
168
- for col in COLUMNS_ORDER:
169
- row[col] = meta.get(col, None)
170
-
171
  rows.append(row)
172
 
173
  if not rows:
174
- return pd.DataFrame(columns=["", "score"] + COLUMNS_ORDER)
175
 
176
  df_res = pd.DataFrame(rows)
177
- df_res = df_res[["№", "score"] + COLUMNS_ORDER]
 
178
  return df_res
179
 
180
 
181
  def run_search(query: str, top_k: int):
182
  query = query.strip()
183
  if not query:
184
- empty_df = pd.DataFrame(columns=["", "score"] + COLUMNS_ORDER)
185
  empty_df_ru = empty_df.rename(columns=COLUMN_LABELS_RU)
186
- df_display = (
187
- empty_df_ru.set_index("№") if "№" in empty_df_ru.columns else empty_df_ru
188
- )
189
- return df_display, None
190
 
191
  results = search_core(query, top_k)
192
  df_res = build_result_df(results)
193
 
194
- df_res_ru = df_res.rename(columns=COLUMN_LABELS_RU)
195
- df_display = df_res_ru.set_index("№")
 
 
 
 
 
 
 
 
 
 
 
 
 
196
 
 
 
 
 
 
 
 
 
197
  output = io.BytesIO()
198
  with pd.ExcelWriter(output, engine="xlsxwriter") as writer:
199
- df_res_ru.to_excel(writer, index=False)
200
  output.seek(0)
201
 
202
- return df_display, output
203
 
204
 
205
  # ==========================
@@ -240,21 +260,25 @@ with btn_col3:
240
 
241
  if clear:
242
  st.session_state.clear()
243
- st.experimental_rerun()
 
 
 
244
 
245
  # Результаты
246
  if do_search:
247
  with st.spinner("Идёт поиск по базе диссертаций..."):
248
- df_res, excel_bytes = run_search(query, top_k)
249
 
250
- if df_res.empty:
251
  st.warning("Ничего не найдено. Попробуйте изменить формулировку запроса.")
252
  else:
253
- st.success(f"Найдено записей: {len(df_res)}")
254
 
255
- st.dataframe(
256
- df_res,
257
- use_container_width=True,
 
258
  )
259
 
260
  if excel_bytes is not None:
 
1
  import os
2
  import io
 
3
 
4
  import numpy as np
5
  import pandas as pd
6
  import streamlit as st
7
  from sentence_transformers import SentenceTransformer
8
+ from datasets import load_dataset
9
+ from huggingface_hub import login
10
 
11
  # ==========================
12
  # НАСТРОЙКИ ПРИЛОЖЕНИЯ
 
17
  layout="wide",
18
  )
19
 
 
20
  HF_TOKEN = os.getenv("HF_TOKEN")
21
+ HF_MERGED_REPO = os.getenv("HF_MERGED_REPO", "yogl/dissertations-merged")
22
+ HF_EMB_REPO = os.getenv("HF_EMB_REPO", "yogl/rosrid-dissertations-embeddings")
 
 
23
 
24
  MODEL_NAME = os.getenv("MODEL_NAME", "intfloat/multilingual-e5-base")
25
 
 
 
 
 
 
 
 
 
 
 
 
 
 
26
  # Отображаемые заголовки (русские имена колонок)
27
  COLUMN_LABELS_RU = {
28
  "№": "№",
29
  "score": "Сходство",
30
+ "fio": "ФИО",
31
+ "title": "Название диссертации",
32
+ "author_org_short": "Организация",
 
 
 
33
  "dissertation_type": "Тип",
34
+ "registration_number": "Регистрационный номер",
 
35
  }
36
 
37
+ DISPLAY_COLUMNS = [
38
+ "№",
39
+ "score",
40
+ "fio",
41
+ "title",
42
+ "author_org_short",
43
+ "dissertation_type",
44
+ "registration_number",
45
+ ]
46
+
47
  if HF_TOKEN is None:
48
  st.error(
49
  "Не найден секрет `HF_TOKEN`. "
 
51
  )
52
  st.stop()
53
 
54
+ # Логин к Hugging Face Hub (для приватных датасетов, если нужно)
55
+ try:
56
+ login(token=HF_TOKEN)
57
+ except Exception:
58
+ # Если что-то не так с токеном — Streamlit ниже отловит при загрузке датасета
59
+ pass
60
+
61
  # ==========================
62
  # ЗАГРУЗКА ДАННЫХ И МОДЕЛИ
63
  # ==========================
64
 
65
+ @st.cache_data(show_spinner="Скачиваю и загружаю данные из Hugging Face датасетов...")
66
  def load_data():
67
+ # 1) МЕТА-ДАННЫЕ: yogl/dissertations-merged
68
+ ds_meta = load_dataset(
69
+ HF_MERGED_REPO,
70
+ split="train",
 
71
  )
72
+ df_meta = ds_meta.to_pandas()
73
+ # Приводим registration_number к строке и делаем индексом
74
+ df_meta["registration_number"] = df_meta["registration_number"].astype(str)
75
+ df_meta = df_meta.set_index("registration_number", drop=False)
76
+
77
+ # 2) ЭМБЕДДИНГИ: yogl/rosrid-dissertations-embeddings
78
+ ds_emb = load_dataset(
79
+ HF_EMB_REPO,
80
+ split="train",
81
  )
82
+ df_emb = ds_emb.to_pandas()
83
 
84
+ # Ожидаем поля: registration_number, embedding (список float)
85
+ df_emb["registration_number"] = df_emb["registration_number"].astype(str)
86
+ reg_nums = df_emb["registration_number"].tolist()
 
 
 
87
 
88
+ # Превращаем список листов в матрицу
89
+ emb_matrix = np.vstack(df_emb["embedding"].to_list()).astype("float32")
 
 
 
90
 
91
+ # Нормализуем (на всякий случай, даже если уже нормализовано при расчёте)
92
  norms = np.linalg.norm(emb_matrix, axis=1, keepdims=True)
93
  emb_matrix = emb_matrix / np.maximum(norms, 1e-8)
94
 
95
+ return df_meta, reg_nums, emb_matrix
96
 
97
 
98
  @st.cache_resource(show_spinner="Загружаю модель для эмбеддингов...")
 
118
  if not query:
119
  return []
120
 
121
+ # e5: запросы с префиксом "query:"
122
  query_text = "query: " + query
123
 
124
  q_emb = model.encode(
 
156
  else:
157
  meta = pd.Series({}, index=df_all.columns)
158
 
159
+ # Базовые поля
160
  row = {
161
  "№": r["rank"],
162
  "score": round(score, 4),
163
+ "fio": meta.get("fio", None),
164
+ "title": meta.get("title", None),
165
+ "author_org_short": meta.get("author_org_short", None),
166
+ "dissertation_type": meta.get("dissertation_type", None),
167
+ "registration_number": meta.get("registration_number", reg),
168
+ # Скрытая колонка для построения ссылок
169
+ "vak_link": meta.get("vak_link", ""),
170
  }
171
 
 
 
 
172
  rows.append(row)
173
 
174
  if not rows:
175
+ return pd.DataFrame(columns=DISPLAY_COLUMNS + ["vak_link"])
176
 
177
  df_res = pd.DataFrame(rows)
178
+ # Жёстко задаём порядок колонок
179
+ df_res = df_res[DISPLAY_COLUMNS + ["vak_link"]]
180
  return df_res
181
 
182
 
183
  def run_search(query: str, top_k: int):
184
  query = query.strip()
185
  if not query:
186
+ empty_df = pd.DataFrame(columns=DISPLAY_COLUMNS + ["vak_link"])
187
  empty_df_ru = empty_df.rename(columns=COLUMN_LABELS_RU)
188
+ return empty_df_ru, None
 
 
 
189
 
190
  results = search_core(query, top_k)
191
  df_res = build_result_df(results)
192
 
193
+ if df_res.empty:
194
+ empty_df_ru = df_res.rename(columns=COLUMN_LABELS_RU)
195
+ return empty_df_ru, None
196
+
197
+ # --- Разделяем датафреймы для отображения и для Excel ---
198
+ df_excel = df_res.drop(columns=["vak_link"]).copy()
199
+ df_display = df_res.copy()
200
+
201
+ # В колонке "Тип" делаем HTML-ссылку, если есть vak_link
202
+ def make_type_cell(row):
203
+ t = row.get("dissertation_type", "")
204
+ link = row.get("vak_link") or ""
205
+ if isinstance(t, str) and t and link:
206
+ return f'<a href="{link}" target="_blank">{t}</a>'
207
+ return t
208
 
209
+ df_display["dissertation_type"] = df_display.apply(make_type_cell, axis=1)
210
+ df_display = df_display.drop(columns=["vak_link"])
211
+
212
+ # Переименуем колонки в русские заголовки
213
+ df_display_ru = df_display.rename(columns=COLUMN_LABELS_RU)
214
+ df_excel_ru = df_excel.rename(columns=COLUMN_LABELS_RU)
215
+
216
+ # Готовим Excel-байты (без HTML-тегов)
217
  output = io.BytesIO()
218
  with pd.ExcelWriter(output, engine="xlsxwriter") as writer:
219
+ df_excel_ru.to_excel(writer, index=False)
220
  output.seek(0)
221
 
222
+ return df_display_ru, output
223
 
224
 
225
  # ==========================
 
260
 
261
  if clear:
262
  st.session_state.clear()
263
+ try:
264
+ st.experimental_rerun()
265
+ except Exception:
266
+ st.rerun()
267
 
268
  # Результаты
269
  if do_search:
270
  with st.spinner("Идёт поиск по базе диссертаций..."):
271
+ df_res_ru, excel_bytes = run_search(query, top_k)
272
 
273
+ if df_res_ru.empty:
274
  st.warning("Ничего не найдено. Попробуйте изменить формулировку запроса.")
275
  else:
276
+ st.success(f"Найдено записей: {len(df_res_ru)}")
277
 
278
+ # Показываем таблицу с HTML-ссылками в колонке "Тип"
279
+ st.markdown(
280
+ df_res_ru.to_html(escape=False, index=False),
281
+ unsafe_allow_html=True,
282
  )
283
 
284
  if excel_bytes is not None: