starfriend commited on
Commit
b15889e
·
verified ·
1 Parent(s): cbdf505

Update backend/paper_linking.py

Browse files
Files changed (1) hide show
  1. backend/paper_linking.py +76 -10
backend/paper_linking.py CHANGED
@@ -2,6 +2,7 @@ from __future__ import annotations
2
 
3
  import hashlib
4
  import re
 
5
 
6
  from local_rag.summaries import load_manifest, summary_path
7
 
@@ -69,7 +70,12 @@ def parse_authors_from_filename(file_name: str) -> list[str]:
69
  author_part = match.group(1).strip()
70
  if _ET_AL_RE.search(author_part):
71
  return [author_part]
72
- return [part.strip() for part in re.split(r"\s+and\s+", author_part, flags=re.I) if part.strip()]
 
 
 
 
 
73
 
74
 
75
  def parse_authors_from_rp(rp: str) -> str:
@@ -144,47 +150,107 @@ def format_authors(authors: str, *, max_names: int | None = None) -> str:
144
  """Display string for UI cards. Default shows every parsed author."""
145
  if not authors:
146
  return ""
147
- parts = [part.strip() for part in authors.split(";") if part.strip()]
 
 
 
 
 
 
148
  if max_names is None or len(parts) <= max_names:
149
  return "; ".join(parts)
 
150
  return "; ".join(parts[:max_names]) + "; et al."
151
 
152
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
153
  def document_id_for_txt_file(txt_file: str) -> str | None:
 
 
 
 
 
 
 
 
 
154
  name = (txt_file or "").strip()
 
155
  if not name:
156
  return None
157
 
158
  manifest = load_manifest()
159
  files = manifest.get("files", [])
160
 
 
161
  for entry in files:
162
  if entry.get("file_name") == name:
163
  return entry.get("document_id")
164
 
165
- lowered = name.lower()
 
 
166
  for entry in files:
167
  file_name = entry.get("file_name", "")
168
- if file_name.lower() == lowered:
 
169
  return entry.get("document_id")
170
 
171
- if not name.endswith(".txt"):
 
172
  with_txt = f"{name}.txt"
 
 
173
  for entry in files:
174
- if entry.get("file_name") == with_txt:
 
 
175
  return entry.get("document_id")
176
 
 
 
 
177
  if summary_path(name) is not None:
178
- stem = name[:-4] if name.lower().endswith(".txt") else name
179
- return hashlib.sha1(stem.encode("utf-8")).hexdigest()[:16]
 
 
 
 
 
 
 
180
 
181
  return None
182
 
183
 
184
  def enrich_metadata_row(row: dict, row_index: int) -> dict:
185
- txt_file = (row.get("TXT_file") or row.get("txt_file") or "").strip()
 
 
 
 
 
186
  document_id = document_id_for_txt_file(txt_file)
187
  authors = resolve_authors(row)
 
188
  return {
189
  "metadata_row_index": row_index,
190
  "title": (row.get("TI") or "").strip(),
@@ -196,4 +262,4 @@ def enrich_metadata_row(row: dict, row_index: int) -> dict:
196
  "txt_file": txt_file,
197
  "document_id": document_id,
198
  "has_chat": document_id is not None,
199
- }
 
2
 
3
  import hashlib
4
  import re
5
+ import unicodedata
6
 
7
  from local_rag.summaries import load_manifest, summary_path
8
 
 
70
  author_part = match.group(1).strip()
71
  if _ET_AL_RE.search(author_part):
72
  return [author_part]
73
+
74
+ return [
75
+ part.strip()
76
+ for part in re.split(r"\s+and\s+", author_part, flags=re.I)
77
+ if part.strip()
78
+ ]
79
 
80
 
81
  def parse_authors_from_rp(rp: str) -> str:
 
150
  """Display string for UI cards. Default shows every parsed author."""
151
  if not authors:
152
  return ""
153
+
154
+ parts = [
155
+ part.strip()
156
+ for part in authors.split(";")
157
+ if part.strip()
158
+ ]
159
+
160
  if max_names is None or len(parts) <= max_names:
161
  return "; ".join(parts)
162
+
163
  return "; ".join(parts[:max_names]) + "; et al."
164
 
165
 
166
+ def _normalize_filename(name: str) -> str:
167
+ """
168
+ Normalize filenames for reliable matching across Unicode forms.
169
+
170
+ Example:
171
+ Türkiye
172
+ Türkiye
173
+
174
+ Both normalize to the same value.
175
+
176
+ NFKC is used to stay consistent with paper_figures.py.
177
+ """
178
+ return unicodedata.normalize(
179
+ "NFKC",
180
+ (name or "").strip(),
181
+ ).casefold()
182
+
183
+
184
  def document_id_for_txt_file(txt_file: str) -> str | None:
185
+ """
186
+ Resolve a metadata TXT filename to the document_id in manifest.json.
187
+
188
+ Matching priority:
189
+ 1. Exact filename match
190
+ 2. Unicode-normalized filename match
191
+ 3. Unicode-normalized match after adding .txt
192
+ 4. Existing summary/hash fallback
193
+ """
194
  name = (txt_file or "").strip()
195
+
196
  if not name:
197
  return None
198
 
199
  manifest = load_manifest()
200
  files = manifest.get("files", [])
201
 
202
+ # 1. Exact filename match
203
  for entry in files:
204
  if entry.get("file_name") == name:
205
  return entry.get("document_id")
206
 
207
+ # 2. Unicode-normalized filename match
208
+ normalized_name = _normalize_filename(name)
209
+
210
  for entry in files:
211
  file_name = entry.get("file_name", "")
212
+
213
+ if _normalize_filename(file_name) == normalized_name:
214
  return entry.get("document_id")
215
 
216
+ # 3. If metadata omitted ".txt", try again with the extension
217
+ if not name.lower().endswith(".txt"):
218
  with_txt = f"{name}.txt"
219
+ normalized_with_txt = _normalize_filename(with_txt)
220
+
221
  for entry in files:
222
+ file_name = entry.get("file_name", "")
223
+
224
+ if _normalize_filename(file_name) == normalized_with_txt:
225
  return entry.get("document_id")
226
 
227
+ # 4. Preserve the original fallback behavior
228
+ #
229
+ # Only reached when no corresponding manifest entry was found.
230
  if summary_path(name) is not None:
231
+ stem = (
232
+ name[:-4]
233
+ if name.lower().endswith(".txt")
234
+ else name
235
+ )
236
+
237
+ return hashlib.sha1(
238
+ stem.encode("utf-8")
239
+ ).hexdigest()[:16]
240
 
241
  return None
242
 
243
 
244
  def enrich_metadata_row(row: dict, row_index: int) -> dict:
245
+ txt_file = (
246
+ row.get("TXT_file")
247
+ or row.get("txt_file")
248
+ or ""
249
+ ).strip()
250
+
251
  document_id = document_id_for_txt_file(txt_file)
252
  authors = resolve_authors(row)
253
+
254
  return {
255
  "metadata_row_index": row_index,
256
  "title": (row.get("TI") or "").strip(),
 
262
  "txt_file": txt_file,
263
  "document_id": document_id,
264
  "has_chat": document_id is not None,
265
+ }