yogl commited on
Commit
c329945
·
verified ·
1 Parent(s): 811ff6d

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +60 -99
app.py CHANGED
@@ -7,21 +7,19 @@ import pandas as pd
7
  import gradio as gr
8
  from sentence_transformers import SentenceTransformer
9
 
10
- # ==== ПУТИ К ФАЙЛАМ (ВНУТРИ SPACE) ====
11
  ALL_DATA_PATH = "ALL_data.xlsx" # лежит в корне репозитория
12
  EMBEDDINGS_PATH = "embeddings_full.json" # общий файл эмбеддингов
13
- # =======================================
14
 
15
- # ==== ЗАГРУЗКА МЕТАДАННЫХ И ЭМБЕДДИНГОВ ====
16
 
17
  print("Загружаю Excel с метаданными...")
18
  df_all = pd.read_excel(ALL_DATA_PATH)
19
 
20
- # registration_number приводим к строке и используем как индекс
21
  df_all["registration_number"] = df_all["registration_number"].astype(str)
22
  df_all = df_all.set_index("registration_number", drop=False)
23
 
24
- # Порядок и состав колонок, которые хотим видеть в выдаче
25
  COLUMNS_ORDER = [
26
  "registration_number",
27
  "name",
@@ -41,39 +39,29 @@ with open(EMBEDDINGS_PATH, "r", encoding="utf-8") as f:
41
  reg_nums = [str(item["registration_number"]) for item in emb_data]
42
  emb_matrix = np.array([item["embedding"] for item in emb_data], dtype="float32")
43
 
44
- # Нормализуем эмбеддинги (если уже нормализованы – не повредит)
45
  norms = np.linalg.norm(emb_matrix, axis=1, keepdims=True)
46
  emb_matrix = emb_matrix / np.maximum(norms, 1e-8)
47
 
48
- # Модель эмбеддингов (максимальное качество)
49
  MODEL_NAME = "intfloat/multilingual-e5-base"
50
  print(f"Загружаю модель {MODEL_NAME}...")
51
  model = SentenceTransformer(MODEL_NAME)
52
 
53
 
54
- # ==== ПОИСК ПО ЭМБЕДДИНГАМ ====
55
 
56
  def search_core(query: str, top_k: int = 10):
57
- """
58
- Возвращает список словарей с:
59
- - rank
60
- - score
61
- - registration_number
62
- """
63
  query = query.strip()
64
  if not query:
65
  return []
66
 
67
- # Для e5: документы кодировали как "passage: ...", запрос – как "query: ..."
68
  query_text = "query: " + query
69
 
70
  q_emb = model.encode(
71
  query_text,
72
  normalize_embeddings=True,
73
- ) # shape (dim,)
74
 
75
- # Косинусное сходство = скалярное произведение нормализованных векторов
76
- scores = emb_matrix @ q_emb # shape (N,)
77
 
78
  top_k = min(int(top_k), len(scores))
79
  top_idx = np.argsort(-scores)[:top_k]
@@ -90,11 +78,6 @@ def search_core(query: str, top_k: int = 10):
90
 
91
 
92
  def build_result_df(results):
93
- """
94
- Превращает список результатов (rank, score, registration_number)
95
- в финальный DataFrame с нужными колонками:
96
- №, score, registration_number, name, author_..., abstract.
97
- """
98
  rows = []
99
 
100
  for r in results:
@@ -104,7 +87,6 @@ def build_result_df(results):
104
  if reg in df_all.index:
105
  meta = df_all.loc[reg]
106
  else:
107
- # На случай, если registration_number нет в Excel
108
  meta = pd.Series({}, index=df_all.columns)
109
 
110
  row = {
@@ -112,7 +94,6 @@ def build_result_df(results):
112
  "score": round(score, 4),
113
  }
114
 
115
- # Добавляем запрошенные метаданные
116
  for col in COLUMNS_ORDER:
117
  if col in meta.index:
118
  row[col] = meta[col]
@@ -129,100 +110,80 @@ def build_result_df(results):
129
  return df_res
130
 
131
 
132
- # ==== ОБЁРТКА ДЛЯ GRADIO ====
133
-
134
- def search_gradio(query: str, top_k: int):
135
  """
136
- Функция для Gradio:
137
- - получает текст запроса и top_k,
138
- - считает эмбеддинг запроса,
139
- - находит top_k близких диссертаций,
140
- - строит таблицу,
141
- - сохраняет её в Excel и PDF,
142
- - возвращает таблицу + два файла.
143
  """
144
  query = query.strip()
145
  if not query:
146
  empty_df = pd.DataFrame(columns=["№", "score"] + COLUMNS_ORDER)
147
- return empty_df, None, None
148
 
149
  results = search_core(query, top_k)
150
  df_res = build_result_df(results)
151
 
152
- # === Сохраняем в Excel ===
153
- excel_fd, excel_path = tempfile.mkstemp(suffix=".xlsx")
154
- os.close(excel_fd)
155
  df_res.to_excel(excel_path, index=False)
156
 
157
- # === Сохраняем в PDF (через reportlab) ===
158
- try:
159
- from reportlab.platypus import SimpleDocTemplate, Table, TableStyle
160
- from reportlab.lib import colors
161
- from reportlab.lib.pagesizes import A4, landscape
162
-
163
- pdf_fd, pdf_path = tempfile.mkstemp(suffix=".pdf")
164
- os.close(pdf_fd)
165
 
166
- doc = SimpleDocTemplate(pdf_path, pagesize=landscape(A4))
167
 
168
- # Данные для таблицы: заголовки + строки
169
- data = [list(df_res.columns)]
170
- for _, row in df_res.iterrows():
171
- data.append([str(x) if x is not None else "" for x in row.tolist()])
172
-
173
- table = Table(data, repeatRows=1)
174
- table.setStyle(TableStyle([
175
- ("BACKGROUND", (0, 0), (-1, 0), colors.lightgrey),
176
- ("GRID", (0, 0), (-1, -1), 0.25, colors.grey),
177
- ("FONTSIZE", (0, 0), (-1, -1), 8),
178
- ("ALIGN", (0, 0), (1, -1), "CENTER"), # № и score
179
- ("VALIGN", (0, 0), (-1, -1), "TOP"),
180
- ]))
181
-
182
- doc.build([table])
183
-
184
- except ImportError:
185
- # Если reportlab не установлен по какой-то причине – PDF не отдаём
186
- pdf_path = None
187
 
188
- return df_res, excel_path, pdf_path
189
 
 
190
 
191
- # ==== GRADIO ИНТЕРФЕЙС ====
 
192
 
193
- demo = gr.Interface(
194
- fn=search_gradio,
195
- inputs=[
196
- gr.Textbox(
197
  label="Запрос",
198
  lines=3,
199
  placeholder="Например: пластификаторы для самоуплотняющихся бетонов",
200
- ),
201
- gr.Slider(
202
- label="Сколько результатов показать",
203
- minimum=1,
204
- maximum=100,
205
- step=1,
206
- value=20,
207
- ),
208
- ],
209
- outputs=[
210
- gr.Dataframe(
211
- label="Результаты поиска",
212
- interactive=False, # но текст можно выделять и копировать
213
- ),
214
- gr.File(label="Скачать таблицу (Excel)"),
215
- gr.File(label="Скачать таблицу (PDF)"),
216
- ],
217
- title=оиск релевантных диссертаций",
218
- description=(
219
- "Вводите текст запроса (по-русски),\n"
220
- "получайте таблицу с №, score, registration_number, названием, автором, "
221
- "организацией, типом, датой и аннотацией.\n"
222
- "Текст из таблицы можно копировать мышкой; "
223
- "результаты можно сохранить в Excel или PDF."
224
- ),
225
- )
 
 
 
 
 
 
 
 
226
 
227
  if __name__ == "__main__":
228
  demo.launch()
 
7
  import gradio as gr
8
  from sentence_transformers import SentenceTransformer
9
 
10
+ # ==== ФАЙЛЫ ВНУТРИ SPACE ====
11
  ALL_DATA_PATH = "ALL_data.xlsx" # лежит в корне репозитория
12
  EMBEDDINGS_PATH = "embeddings_full.json" # общий файл эмбеддингов
13
+ # =============================
14
 
15
+ # ==== ЗАГРУЖАЕМ ДАННЫЕ ====
16
 
17
  print("Загружаю Excel с метаданными...")
18
  df_all = pd.read_excel(ALL_DATA_PATH)
19
 
 
20
  df_all["registration_number"] = df_all["registration_number"].astype(str)
21
  df_all = df_all.set_index("registration_number", drop=False)
22
 
 
23
  COLUMNS_ORDER = [
24
  "registration_number",
25
  "name",
 
39
  reg_nums = [str(item["registration_number"]) for item in emb_data]
40
  emb_matrix = np.array([item["embedding"] for item in emb_data], dtype="float32")
41
 
 
42
  norms = np.linalg.norm(emb_matrix, axis=1, keepdims=True)
43
  emb_matrix = emb_matrix / np.maximum(norms, 1e-8)
44
 
 
45
  MODEL_NAME = "intfloat/multilingual-e5-base"
46
  print(f"Загружаю модель {MODEL_NAME}...")
47
  model = SentenceTransformer(MODEL_NAME)
48
 
49
 
50
+ # ==== ЛОГИКА ПОИСКА ====
51
 
52
  def search_core(query: str, top_k: int = 10):
 
 
 
 
 
 
53
  query = query.strip()
54
  if not query:
55
  return []
56
 
 
57
  query_text = "query: " + query
58
 
59
  q_emb = model.encode(
60
  query_text,
61
  normalize_embeddings=True,
62
+ )
63
 
64
+ scores = emb_matrix @ q_emb
 
65
 
66
  top_k = min(int(top_k), len(scores))
67
  top_idx = np.argsort(-scores)[:top_k]
 
78
 
79
 
80
  def build_result_df(results):
 
 
 
 
 
81
  rows = []
82
 
83
  for r in results:
 
87
  if reg in df_all.index:
88
  meta = df_all.loc[reg]
89
  else:
 
90
  meta = pd.Series({}, index=df_all.columns)
91
 
92
  row = {
 
94
  "score": round(score, 4),
95
  }
96
 
 
97
  for col in COLUMNS_ORDER:
98
  if col in meta.index:
99
  row[col] = meta[col]
 
110
  return df_res
111
 
112
 
113
+ def run_search(query: str, top_k: int):
 
 
114
  """
115
+ Возвращает:
116
+ - DataFrame с результатами
117
+ - путь к Excel-файлу с этими результатами
 
 
 
 
118
  """
119
  query = query.strip()
120
  if not query:
121
  empty_df = pd.DataFrame(columns=["№", "score"] + COLUMNS_ORDER)
122
+ return empty_df, None
123
 
124
  results = search_core(query, top_k)
125
  df_res = build_result_df(results)
126
 
127
+ # Сохраняем во временный Excel
128
+ fd, excel_path = tempfile.mkstemp(suffix=".xlsx")
129
+ os.close(fd)
130
  df_res.to_excel(excel_path, index=False)
131
 
132
+ return df_res, excel_path
 
 
 
 
 
 
 
133
 
 
134
 
135
+ def clear_all():
136
+ """
137
+ Очистка: пустой запрос, пустая таблица, отсутствие файла.
138
+ """
139
+ empty_df = pd.DataFrame(columns=["№", "score"] + COLUMNS_ORDER)
140
+ return "", empty_df, None
 
 
 
 
 
 
 
 
 
 
 
 
 
141
 
 
142
 
143
+ # ==== UI НА BLOCKS ====
144
 
145
+ with gr.Blocks() as demo:
146
+ gr.Markdown("## Поиск релевантных диссертаций")
147
 
148
+ with gr.Row():
149
+ query = gr.Textbox(
 
 
150
  label="Запрос",
151
  lines=3,
152
  placeholder="Например: пластификаторы для самоуплотняющихся бетонов",
153
+ )
154
+
155
+ top_k = gr.Slider(
156
+ label="Сколько результатов показать",
157
+ minimum=1,
158
+ maximum=100,
159
+ step=1,
160
+ value=20,
161
+ )
162
+
163
+ with gr.Row():
164
+ search_btn = gr.Button("Поиск")
165
+ clear_btn = gr.Button("Очистить")
166
+
167
+ # ТАБЛИЦА ПОД КНОПКАМИ
168
+ results_df = gr.Dataframe(
169
+ label="Результаты поиска",
170
+ interactive=True, # можно выделять и копировать текст
171
+ )
172
+ excel_file = gr.File(label="Скачать таблицу (Excel)")
173
+
174
+ # Клик по оиск"
175
+ search_btn.click(
176
+ fn=run_search,
177
+ inputs=[query, top_k],
178
+ outputs=[results_df, excel_file],
179
+ )
180
+
181
+ # Клик по "Очистить"
182
+ clear_btn.click(
183
+ fn=clear_all,
184
+ inputs=[],
185
+ outputs=[query, results_df, excel_file],
186
+ )
187
 
188
  if __name__ == "__main__":
189
  demo.launch()