BibexPy 2.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (171) hide show
  1. bibex_core/MergeDB.py +1189 -0
  2. bibex_core/__init__.py +13 -0
  3. bibex_core/modules/__init__.py +35 -0
  4. bibex_core/modules/api_utils.py +1172 -0
  5. bibex_core/modules/c1_utils.py +423 -0
  6. bibex_core/modules/file_utils.py +71 -0
  7. bibex_core/modules/ml_utils.py +332 -0
  8. bibex_core/modules/post_process.py +50 -0
  9. bibex_core/modules/stats_utils.py +216 -0
  10. bibex_core/scp2xlsx.py +235 -0
  11. bibex_core/wos2xlsx.py +159 -0
  12. bibex_core/xlsx2vos.py +185 -0
  13. bibexpy/__init__.py +9 -0
  14. bibexpy/__main__.py +6 -0
  15. bibexpy/_server/__init__.py +1 -0
  16. bibexpy/_server/config.py +92 -0
  17. bibexpy/_server/jobs/__init__.py +3 -0
  18. bibexpy/_server/jobs/runner.py +301 -0
  19. bibexpy/_server/main.py +211 -0
  20. bibexpy/_server/models/__init__.py +0 -0
  21. bibexpy/_server/models/project.py +25 -0
  22. bibexpy/_server/routers/__init__.py +0 -0
  23. bibexpy/_server/routers/audit.py +65 -0
  24. bibexpy/_server/routers/convert.py +101 -0
  25. bibexpy/_server/routers/disambiguate.py +177 -0
  26. bibexpy/_server/routers/downloads.py +26 -0
  27. bibexpy/_server/routers/enrich.py +42 -0
  28. bibexpy/_server/routers/export.py +116 -0
  29. bibexpy/_server/routers/export_folder.py +97 -0
  30. bibexpy/_server/routers/filter.py +131 -0
  31. bibexpy/_server/routers/jobs.py +72 -0
  32. bibexpy/_server/routers/merge.py +437 -0
  33. bibexpy/_server/routers/prepare.py +241 -0
  34. bibexpy/_server/routers/projects.py +49 -0
  35. bibexpy/_server/routers/quality.py +288 -0
  36. bibexpy/_server/routers/records.py +204 -0
  37. bibexpy/_server/routers/report.py +111 -0
  38. bibexpy/_server/routers/settings.py +383 -0
  39. bibexpy/_server/routers/system.py +209 -0
  40. bibexpy/_server/routers/tools.py +257 -0
  41. bibexpy/_server/routers/upload.py +110 -0
  42. bibexpy/_server/services/__init__.py +0 -0
  43. bibexpy/_server/services/analyses.py +364 -0
  44. bibexpy/_server/services/audit.py +340 -0
  45. bibexpy/_server/services/bibex_adapter.py +47 -0
  46. bibexpy/_server/services/bibtex_writer.py +104 -0
  47. bibexpy/_server/services/converter.py +205 -0
  48. bibexpy/_server/services/disambiguation/__init__.py +19 -0
  49. bibexpy/_server/services/disambiguation/blocking.py +581 -0
  50. bibexpy/_server/services/disambiguation/cache.py +47 -0
  51. bibexpy/_server/services/disambiguation/deepseek_client.py +203 -0
  52. bibexpy/_server/services/disambiguation/orcid.py +261 -0
  53. bibexpy/_server/services/disambiguation/pipeline.py +985 -0
  54. bibexpy/_server/services/disambiguation/similarity.py +91 -0
  55. bibexpy/_server/services/enricher.py +338 -0
  56. bibexpy/_server/services/exporter.py +112 -0
  57. bibexpy/_server/services/filter_engine.py +300 -0
  58. bibexpy/_server/services/merger.py +202 -0
  59. bibexpy/_server/services/methodology.py +102 -0
  60. bibexpy/_server/services/report_export.py +300 -0
  61. bibexpy/_server/services/ris_writer.py +62 -0
  62. bibexpy/_server/services/smart_merger.py +967 -0
  63. bibexpy/_server/services/storage.py +165 -0
  64. bibexpy/_web/404/index.html +1 -0
  65. bibexpy/_web/404.html +1 -0
  66. bibexpy/_web/_next/static/chunks/117-7ad5186126fa076d.js +2 -0
  67. bibexpy/_web/_next/static/chunks/156-75beffb8ae92a7f3.js +1 -0
  68. bibexpy/_web/_next/static/chunks/169-301c04fcec22686f.js +4 -0
  69. bibexpy/_web/_next/static/chunks/2-ecc1c36a5632ac61.js +1 -0
  70. bibexpy/_web/_next/static/chunks/244-be343e0f8aa00fb5.js +1 -0
  71. bibexpy/_web/_next/static/chunks/367-b7457c2b95f6932b.js +1 -0
  72. bibexpy/_web/_next/static/chunks/382-9cd163eed632e241.js +1 -0
  73. bibexpy/_web/_next/static/chunks/392-afa8bfa4c1a9893d.js +1 -0
  74. bibexpy/_web/_next/static/chunks/507-63607ded9f83c1aa.js +1 -0
  75. bibexpy/_web/_next/static/chunks/624-cae565632f47217d.js +1 -0
  76. bibexpy/_web/_next/static/chunks/739-a00c6b9c0f11813e.js +1 -0
  77. bibexpy/_web/_next/static/chunks/862-8e1bd5f93c082c02.js +1 -0
  78. bibexpy/_web/_next/static/chunks/app/_not-found/page-873ba59b5eb7c342.js +1 -0
  79. bibexpy/_web/_next/static/chunks/app/layout-0d5fc68ac8971a04.js +1 -0
  80. bibexpy/_web/_next/static/chunks/app/page-cf2bea5edc74459a.js +1 -0
  81. bibexpy/_web/_next/static/chunks/app/projects/[id]/convert/page-ab6dd645ae113b64.js +1 -0
  82. bibexpy/_web/_next/static/chunks/app/projects/[id]/disambiguate/page-dd98482cc47344e9.js +1 -0
  83. bibexpy/_web/_next/static/chunks/app/projects/[id]/enrich/page-4727e431468514e9.js +1 -0
  84. bibexpy/_web/_next/static/chunks/app/projects/[id]/export/page-f9005d164bb22cb0.js +1 -0
  85. bibexpy/_web/_next/static/chunks/app/projects/[id]/layout-07755fda55b3ae1f.js +1 -0
  86. bibexpy/_web/_next/static/chunks/app/projects/[id]/merge/page-45b95318ea7beccf.js +1 -0
  87. bibexpy/_web/_next/static/chunks/app/projects/[id]/records/page-bfad80457da9eb0b.js +1 -0
  88. bibexpy/_web/_next/static/chunks/app/projects/[id]/report/page-f05767e74158e995.js +1 -0
  89. bibexpy/_web/_next/static/chunks/app/projects/[id]/upload/page-8df78ca9cdecbcb8.js +1 -0
  90. bibexpy/_web/_next/static/chunks/app/projects/page-99960711ac6a13e9.js +1 -0
  91. bibexpy/_web/_next/static/chunks/app/settings/page-002df809d6c5a227.js +1 -0
  92. bibexpy/_web/_next/static/chunks/app/tools/page-27eee1c6a590859f.js +1 -0
  93. bibexpy/_web/_next/static/chunks/fd9d1056-f2718be92181f426.js +1 -0
  94. bibexpy/_web/_next/static/chunks/framework-f66176bb897dc684.js +1 -0
  95. bibexpy/_web/_next/static/chunks/main-app-d138a6e2f8c847f6.js +1 -0
  96. bibexpy/_web/_next/static/chunks/main-c4b9ddbf514503ef.js +1 -0
  97. bibexpy/_web/_next/static/chunks/pages/_app-72b849fbd24ac258.js +1 -0
  98. bibexpy/_web/_next/static/chunks/pages/_error-7ba65e1336b92748.js +1 -0
  99. bibexpy/_web/_next/static/chunks/polyfills-42372ed130431b0a.js +1 -0
  100. bibexpy/_web/_next/static/chunks/webpack-c81f7fd28659d64f.js +1 -0
  101. bibexpy/_web/_next/static/css/1c4638f3e39cf127.css +3 -0
  102. bibexpy/_web/_next/static/ypx2IQAkZiSaFiKWnzZGB/_buildManifest.js +1 -0
  103. bibexpy/_web/_next/static/ypx2IQAkZiSaFiKWnzZGB/_ssgManifest.js +1 -0
  104. bibexpy/_web/images/PROMPTS.md +83 -0
  105. bibexpy/_web/images/about-illustration.png +0 -0
  106. bibexpy/_web/images/authors/alperen-sahin.jpg +0 -0
  107. bibexpy/_web/images/authors/burak-can-kara.jpg +0 -0
  108. bibexpy/_web/images/authors/taskin-dirsehan.jpg +0 -0
  109. bibexpy/_web/images/bibexpy-logo-full.png +0 -0
  110. bibexpy/_web/images/bibexpy-logo-header.png +0 -0
  111. bibexpy/_web/images/bibexpy-logo-in.png +0 -0
  112. bibexpy/_web/images/bibexpy-logo.webp +0 -0
  113. bibexpy/_web/images/hero-illustration.png +0 -0
  114. bibexpy/_web/images/loader.gif +0 -0
  115. bibexpy/_web/images/workflow-diagram.png +0 -0
  116. bibexpy/_web/index.html +1 -0
  117. bibexpy/_web/index.txt +11 -0
  118. bibexpy/_web/projects/_/convert/index.html +1 -0
  119. bibexpy/_web/projects/_/convert/index.txt +12 -0
  120. bibexpy/_web/projects/_/disambiguate/index.html +1 -0
  121. bibexpy/_web/projects/_/disambiguate/index.txt +12 -0
  122. bibexpy/_web/projects/_/enrich/index.html +1 -0
  123. bibexpy/_web/projects/_/enrich/index.txt +12 -0
  124. bibexpy/_web/projects/_/export/index.html +1 -0
  125. bibexpy/_web/projects/_/export/index.txt +12 -0
  126. bibexpy/_web/projects/_/merge/index.html +1 -0
  127. bibexpy/_web/projects/_/merge/index.txt +12 -0
  128. bibexpy/_web/projects/_/records/index.html +1 -0
  129. bibexpy/_web/projects/_/records/index.txt +12 -0
  130. bibexpy/_web/projects/_/report/index.html +1 -0
  131. bibexpy/_web/projects/_/report/index.txt +12 -0
  132. bibexpy/_web/projects/_/upload/index.html +1 -0
  133. bibexpy/_web/projects/_/upload/index.txt +12 -0
  134. bibexpy/_web/projects/index.html +1 -0
  135. bibexpy/_web/projects/index.txt +11 -0
  136. bibexpy/_web/settings/index.html +1 -0
  137. bibexpy/_web/settings/index.txt +11 -0
  138. bibexpy/_web/tools/README.md +65 -0
  139. bibexpy/_web/tools/bibexcel.svg +6 -0
  140. bibexpy/_web/tools/bibliometrix.png +0 -0
  141. bibexpy/_web/tools/biblioshiny.svg +12 -0
  142. bibexpy/_web/tools/citavi.svg +6 -0
  143. bibexpy/_web/tools/citespace.svg +7 -0
  144. bibexpy/_web/tools/citnetexplorer.svg +8 -0
  145. bibexpy/_web/tools/endnote.svg +6 -0
  146. bibexpy/_web/tools/excel.svg +1 -0
  147. bibexpy/_web/tools/gephi.svg +66 -0
  148. bibexpy/_web/tools/histcite.svg +7 -0
  149. bibexpy/_web/tools/index.html +1 -0
  150. bibexpy/_web/tools/index.txt +11 -0
  151. bibexpy/_web/tools/jabref.svg +122 -0
  152. bibexpy/_web/tools/latex.svg +1 -0
  153. bibexpy/_web/tools/mendeley.svg +1 -0
  154. bibexpy/_web/tools/openrefine.svg +15 -0
  155. bibexpy/_web/tools/overleaf.svg +1 -0
  156. bibexpy/_web/tools/papers.svg +7 -0
  157. bibexpy/_web/tools/powerbi.svg +1 -0
  158. bibexpy/_web/tools/python.svg +1 -0
  159. bibexpy/_web/tools/r.svg +1 -0
  160. bibexpy/_web/tools/refworks.svg +5 -0
  161. bibexpy/_web/tools/scite.svg +5 -0
  162. bibexpy/_web/tools/tableau.svg +1 -0
  163. bibexpy/_web/tools/vosviewer.png +0 -0
  164. bibexpy/_web/tools/zotero.svg +1 -0
  165. bibexpy/cli.py +242 -0
  166. bibexpy-2.0.0.dist-info/METADATA +105 -0
  167. bibexpy-2.0.0.dist-info/RECORD +171 -0
  168. bibexpy-2.0.0.dist-info/WHEEL +5 -0
  169. bibexpy-2.0.0.dist-info/entry_points.txt +2 -0
  170. bibexpy-2.0.0.dist-info/licenses/LICENSE +674 -0
  171. bibexpy-2.0.0.dist-info/top_level.txt +2 -0
@@ -0,0 +1,47 @@
1
+ """Çekirdek bibex_core modüllerini web katmanından temiz çağırmak için sarmalayıcı.
2
+
3
+ - CLI'a özgü print/input çağrılarını yutmaz; sadece stdout/stderr'i bastırır
4
+ - DataFrame'leri JSON-friendly hale getirir
5
+ - Hataları HTTPException olarak fırlatır
6
+
7
+ Faz 1'de sadece yapı kuruluyor; gerçek çağrılar Faz 2-6'da eklenecek.
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ import contextlib
13
+ import io
14
+ import math
15
+ from typing import Any
16
+
17
+ from fastapi import HTTPException
18
+
19
+
20
+ @contextlib.contextmanager
21
+ def _suppress_stdio():
22
+ buf_out, buf_err = io.StringIO(), io.StringIO()
23
+ import sys
24
+ old_out, old_err = sys.stdout, sys.stderr
25
+ sys.stdout, sys.stderr = buf_out, buf_err
26
+ try:
27
+ yield buf_out, buf_err
28
+ finally:
29
+ sys.stdout, sys.stderr = old_out, old_err
30
+
31
+
32
+ def df_to_records(df, max_rows: int | None = None) -> list[dict[str, Any]]:
33
+ """Pandas DataFrame -> JSON-serializable kayıt listesi (NaN -> None)."""
34
+ if df is None:
35
+ return []
36
+ if max_rows is not None:
37
+ df = df.head(max_rows)
38
+ records = df.to_dict(orient="records")
39
+ for rec in records:
40
+ for k, v in rec.items():
41
+ if isinstance(v, float) and math.isnan(v):
42
+ rec[k] = None
43
+ return records
44
+
45
+
46
+ def http_error(message: str, status: int = 500) -> HTTPException:
47
+ return HTTPException(status_code=status, detail=message)
@@ -0,0 +1,104 @@
1
+ """DataFrame -> BibTeX (.bib) yazıcı.
2
+
3
+ BibexPy alan adlarını BibTeX'e eşler:
4
+ AU -> author (semicolon -> ' and ')
5
+ TI -> title
6
+ SO -> journal
7
+ PY -> year
8
+ VL -> volume, IS -> number, AR -> articleno
9
+ DI -> doi, URL -> url, AB -> abstract
10
+ DE -> keywords (semicolon -> ', ')
11
+ PU -> publisher
12
+ """
13
+
14
+ from __future__ import annotations
15
+
16
+ import re
17
+ from pathlib import Path
18
+
19
+ import pandas as pd
20
+
21
+
22
+ def _escape(value: str) -> str:
23
+ if not value:
24
+ return ""
25
+ s = str(value)
26
+ # Yaygın LaTeX kaçırma — minimum (literal tutmak için)
27
+ s = s.replace("\\", "\\textbackslash{}")
28
+ for ch in "&%$#_{}":
29
+ s = s.replace(ch, f"\\{ch}")
30
+ s = s.replace("~", "\\textasciitilde{}")
31
+ s = s.replace("^", "\\textasciicircum{}")
32
+ return s
33
+
34
+
35
+ def _normalize_authors(au: str) -> str:
36
+ parts = [a.strip() for a in re.split(r";|\band\b", str(au)) if a.strip()]
37
+ return " and ".join(parts)
38
+
39
+
40
+ def _normalize_keywords(de: str) -> str:
41
+ parts = [k.strip() for k in str(de).split(";") if k.strip()]
42
+ return ", ".join(parts)
43
+
44
+
45
+ def _slug(text: str, maxlen: int = 30) -> str:
46
+ s = re.sub(r"[^a-zA-Z0-9]+", "", str(text))
47
+ return s[:maxlen] or "anon"
48
+
49
+
50
+ def _entry_type(dt: str) -> str:
51
+ dt = str(dt).upper()
52
+ if "REVIEW" in dt: return "article"
53
+ if "CONFERENCE" in dt or "PROCEEDING" in dt: return "inproceedings"
54
+ if "BOOK" in dt: return "book"
55
+ if "CHAPTER" in dt: return "incollection"
56
+ return "article"
57
+
58
+
59
+ def write_bibtex(df: pd.DataFrame, output: Path) -> Path:
60
+ used_keys: set[str] = set()
61
+ lines: list[str] = []
62
+ for i, row in df.iterrows():
63
+ au = row.get("AU", "") or ""
64
+ py = str(row.get("PY", "") or "").strip()
65
+ ti = row.get("TI", "") or ""
66
+ # cite key: SmithEtAl2020Title
67
+ first_author = re.split(r";|,|\band\b", str(au))[0].strip()
68
+ first_author = _slug(first_author.split()[0] if first_author else "Anon", 20)
69
+ key = f"{first_author}{py}{_slug(ti, 12)}"
70
+ # Uniqueness
71
+ base, n = key, 1
72
+ while key in used_keys:
73
+ key = f"{base}_{n}"; n += 1
74
+ used_keys.add(key)
75
+
76
+ etype = _entry_type(row.get("DT", "") or "")
77
+ lines.append(f"@{etype}{{{key},")
78
+ fields = {
79
+ "author": _normalize_authors(au),
80
+ "title": str(ti),
81
+ "journal": str(row.get("SO", "") or ""),
82
+ "year": py,
83
+ "volume": str(row.get("VL", "") or ""),
84
+ "number": str(row.get("IS", "") or ""),
85
+ "pages": str(row.get("PG", "") or ""),
86
+ "doi": str(row.get("DI", "") or ""),
87
+ "url": str(row.get("URL", "") or ""),
88
+ "abstract": str(row.get("AB", "") or ""),
89
+ "keywords": _normalize_keywords(row.get("DE", "") or ""),
90
+ "publisher": str(row.get("PU", "") or ""),
91
+ }
92
+ for k, v in fields.items():
93
+ v = v.strip()
94
+ if not v or v.lower() == "nan":
95
+ continue
96
+ lines.append(f" {k} = {{{_escape(v)}}},")
97
+ # son virgülü temizle
98
+ if lines[-1].endswith(","):
99
+ lines[-1] = lines[-1][:-1]
100
+ lines.append("}")
101
+ lines.append("")
102
+
103
+ output.write_text("\n".join(lines), encoding="utf-8")
104
+ return output
@@ -0,0 +1,205 @@
1
+ """bibex_core dönüşüm fonksiyonlarını web ortamında kullanmak için sarmal.
2
+
3
+ - Tüm çıktılar `<project>/processed/` altına yazılır
4
+ - Stdout/stderr bastırılır (bibex_core print kullanıyor)
5
+ - Hatalar HTTPException olarak fırlatılır
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ from pathlib import Path
11
+ from typing import Iterable
12
+
13
+ from fastapi import HTTPException
14
+
15
+ from services import storage
16
+ from services.bibex_adapter import _suppress_stdio
17
+
18
+
19
+ def _project_paths(project_id: str) -> tuple[Path, Path, Path]:
20
+ meta = storage.get_project(project_id)
21
+ if meta is None:
22
+ raise HTTPException(404, "Proje bulunamadı")
23
+ root = storage.project_dir(project_id)
24
+ return root, root / "raw", root / "processed"
25
+
26
+
27
+ def _raw_file(raw_dir: Path, name: str) -> Path:
28
+ name = Path(name).name
29
+ p = raw_dir / name
30
+ if not p.exists() or not p.is_file():
31
+ raise HTTPException(404, f"Ham dosya bulunamadı: {name}")
32
+ return p
33
+
34
+
35
+ def csv_to_xlsx(project_id: str, source_names: Iterable[str], output_name: str = "scopus_merged.xlsx") -> Path:
36
+ """Scopus CSV(ler) → tek XLSX."""
37
+ from bibex_core.scp2xlsx import save_to_excel
38
+
39
+ _, raw, processed = _project_paths(project_id)
40
+ sources = [str(_raw_file(raw, n)) for n in source_names]
41
+ if not sources:
42
+ raise HTTPException(400, "En az bir CSV dosyası gerekli")
43
+ output = processed / Path(output_name).name
44
+ with _suppress_stdio():
45
+ ok = save_to_excel(sources, str(output))
46
+ if not ok or not output.exists():
47
+ raise HTTPException(500, "CSV → XLSX dönüşümü başarısız")
48
+ storage.touch_project(project_id)
49
+ return output
50
+
51
+
52
+ def wos_to_xlsx(project_id: str, source_names: Iterable[str], output_name: str = "wos_merged.xlsx") -> Path:
53
+ """WoS TXT(ler) → tek XLSX. Birden fazla TXT verilirse önce birleştirir."""
54
+ from bibex_core.wos2xlsx import save_to_excel
55
+
56
+ _, raw, processed = _project_paths(project_id)
57
+ sources = [_raw_file(raw, n) for n in source_names]
58
+ if not sources:
59
+ raise HTTPException(400, "En az bir TXT dosyası gerekli")
60
+
61
+ # Birleştir
62
+ merged_txt = processed / "_wos_merged.tmp.txt"
63
+ with merged_txt.open("w", encoding="utf-8") as out:
64
+ for src in sources:
65
+ out.write(src.read_text(encoding="utf-8", errors="replace"))
66
+ out.write("\n")
67
+
68
+ output = processed / Path(output_name).name
69
+ with _suppress_stdio():
70
+ ok = save_to_excel(str(merged_txt), str(output))
71
+ merged_txt.unlink(missing_ok=True)
72
+ if not ok or not output.exists():
73
+ raise HTTPException(500, "WoS → XLSX dönüşümü başarısız")
74
+ storage.touch_project(project_id)
75
+ return output
76
+
77
+
78
+ def auto_prepare(project_id: str, ctx=None) -> dict:
79
+ """Ham CSV/TXT'leri konsolide `processed/*.xlsx`'e çevirir — ÖRTÜK hazırlık.
80
+
81
+ Smart Merge job'unun ilk fazı olarak çağrılır; ayrı "Prepare" adımı yok.
82
+ Skip-if-fresh: bir kaynağı yalnız en yeni ham dosyası ilgili processed
83
+ XLSX'ten yeni ise (veya XLSX yoksa) yeniden çevirir. Hata FIRLATMAZ —
84
+ sorunları `skipped`'a ekler ki tek-kaynak merge yine yürüyebilsin.
85
+
86
+ ctx: opsiyonel JobContext benzeri (.log()/.progress()); tip bağımlılığı
87
+ eklememek için gevşek bırakıldı.
88
+ """
89
+ _, raw, processed = _project_paths(project_id)
90
+ report: dict = {
91
+ "scopus_xlsx": None, "wos_xlsx": None,
92
+ "csv": 0, "txt": 0, "skipped": [], "did_convert": False,
93
+ }
94
+ if not raw.exists():
95
+ return report
96
+
97
+ csv_files = [f for f in raw.iterdir() if f.is_file() and f.suffix.lower() == ".csv"]
98
+ txt_files = [f for f in raw.iterdir() if f.is_file() and f.suffix.lower() in (".txt", ".isi")]
99
+ report["csv"] = len(csv_files)
100
+ report["txt"] = len(txt_files)
101
+
102
+ def _fresh(out_name: str, raw_paths: list[Path]) -> bool:
103
+ out = processed / out_name
104
+ if not out.exists():
105
+ return False
106
+ try:
107
+ newest_raw = max((p.stat().st_mtime for p in raw_paths), default=0.0)
108
+ return out.stat().st_mtime >= newest_raw
109
+ except OSError:
110
+ return False
111
+
112
+ def _convert(kind: str, out_name: str, legacy: str, raw_paths: list[Path], fn) -> None:
113
+ if _fresh(out_name, raw_paths):
114
+ report[f"{kind}_xlsx"] = out_name
115
+ if ctx:
116
+ ctx.log(f"{kind.capitalize()} already prepared (up to date)")
117
+ return
118
+ old = processed / legacy
119
+ if old.exists():
120
+ try:
121
+ old.unlink()
122
+ except OSError:
123
+ pass
124
+ try:
125
+ if ctx:
126
+ ctx.log(f"Preparing {kind} — {len(raw_paths)} file(s) → {out_name}")
127
+ out = fn(project_id, [p.name for p in raw_paths], out_name)
128
+ report[f"{kind}_xlsx"] = out.name
129
+ report["did_convert"] = True
130
+ except Exception as e: # noqa: BLE001 — tek-kaynak merge yürüsün diye yutulur
131
+ report["skipped"].append(f"{kind}: {e}")
132
+
133
+ if csv_files:
134
+ _convert("scopus", "scopus.xlsx", "scopus_merged.xlsx", csv_files, csv_to_xlsx)
135
+ if ctx:
136
+ ctx.progress(0.12)
137
+ if txt_files:
138
+ _convert("wos", "wos.xlsx", "wos_merged.xlsx", txt_files, wos_to_xlsx)
139
+ if ctx:
140
+ ctx.progress(0.18)
141
+
142
+ return report
143
+
144
+
145
+ def xlsx_to_wos_txt(project_id: str, source_name: str, output_name: str | None = None) -> Path:
146
+ """XLSX → WoS-format TXT (bibliometrix için)."""
147
+ from bibex_core.xlsx2vos import convert_excel_to_wos
148
+
149
+ _, _, processed = _project_paths(project_id)
150
+ src = processed / Path(source_name).name
151
+ if not src.exists():
152
+ # raw'da da bakalım, kullanıcı doğrudan yüklediyse
153
+ raw = storage.project_dir(project_id) / "raw"
154
+ alt = raw / Path(source_name).name
155
+ if alt.exists():
156
+ src = alt
157
+ else:
158
+ raise HTTPException(404, f"XLSX bulunamadı: {source_name}")
159
+
160
+ output = processed / (output_name or (src.stem + "_wos.txt"))
161
+ with _suppress_stdio():
162
+ convert_excel_to_wos(str(src), str(output))
163
+ if not output.exists():
164
+ raise HTTPException(500, "XLSX → WoS TXT dönüşümü başarısız")
165
+ storage.touch_project(project_id)
166
+ return output
167
+
168
+
169
+ def xlsx_to_tsv(project_id: str, source_name: str, output_name: str | None = None, sep: str = "\t") -> Path:
170
+ """XLSX → düz TSV/CSV (pandas)."""
171
+ import pandas as pd
172
+
173
+ _, _, processed = _project_paths(project_id)
174
+ src = processed / Path(source_name).name
175
+ if not src.exists():
176
+ raw = storage.project_dir(project_id) / "raw"
177
+ alt = raw / Path(source_name).name
178
+ if alt.exists():
179
+ src = alt
180
+ else:
181
+ raise HTTPException(404, f"XLSX bulunamadı: {source_name}")
182
+
183
+ ext = "tsv" if sep == "\t" else "csv"
184
+ output = processed / (output_name or (src.stem + f".{ext}"))
185
+ df = pd.read_excel(src)
186
+ df.to_csv(output, sep=sep, index=False, encoding="utf-8")
187
+ storage.touch_project(project_id)
188
+ return output
189
+
190
+
191
+ def list_processed(project_id: str) -> list[dict]:
192
+ """processed/ klasöründeki dosyaları listele."""
193
+ _, _, processed = _project_paths(project_id)
194
+ if not processed.exists():
195
+ return []
196
+ out = []
197
+ for f in sorted(processed.iterdir()):
198
+ if not f.is_file():
199
+ continue
200
+ out.append({
201
+ "name": f.name,
202
+ "size": f.stat().st_size,
203
+ "relative_path": str(f.relative_to(storage.settings.storage_path)),
204
+ })
205
+ return out
@@ -0,0 +1,19 @@
1
+ """Deneysel LLM tabanlı yazar/affiliation disambiguation."""
2
+
3
+ from .blocking import build_author_blocks, build_affiliation_blocks
4
+ from .similarity import jaro_winkler, normalize_name
5
+ from .deepseek_client import DeepSeekClient
6
+ from .cache import DisambiguationCache
7
+ from .pipeline import run_author_disambiguation, run_affiliation_disambiguation, apply_clusters
8
+
9
+ __all__ = [
10
+ "build_author_blocks",
11
+ "build_affiliation_blocks",
12
+ "jaro_winkler",
13
+ "normalize_name",
14
+ "DeepSeekClient",
15
+ "DisambiguationCache",
16
+ "run_author_disambiguation",
17
+ "run_affiliation_disambiguation",
18
+ "apply_clusters",
19
+ ]