Sayedyousef commited on
Commit
f4768fc
·
verified ·
1 Parent(s): 210b6c0

Deploy Gradio demo

Browse files
Files changed (3) hide show
  1. README.md +59 -6
  2. app.py +267 -0
  3. requirements.txt +2 -0
README.md CHANGED
@@ -1,12 +1,65 @@
1
  ---
2
- title: Arabnamer Demo
3
- emoji: 🚀
4
- colorFrom: purple
5
- colorTo: pink
6
  sdk: gradio
7
- sdk_version: 6.12.0
8
  app_file: app.py
9
  pinned: false
 
 
 
 
 
 
 
 
 
 
10
  ---
11
 
12
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  ---
2
+ title: arabnamer demo
3
+ emoji: 🕌
4
+ colorFrom: green
5
+ colorTo: blue
6
  sdk: gradio
7
+ sdk_version: 4.44.0
8
  app_file: app.py
9
  pinned: false
10
+ license: mit
11
+ tags:
12
+ - arabic
13
+ - transliteration
14
+ - name-matching
15
+ - arabic-nlp
16
+ - mena
17
+ - kyc
18
+ - entity-resolution
19
+ short_description: Offline Arabic name transliteration & fuzzy similarity.
20
  ---
21
 
22
+ # arabnamer live demo
23
+
24
+ This Space runs the [arabnamer](https://github.com/sayedyousef/arabnamer) Python
25
+ library in an interactive UI.
26
+
27
+ ## Three tabs
28
+
29
+ 1. **Transliterate** — English name → Arabic name, with selectable engine
30
+ (XGBoost model / rule-based / hybrid) and optional reference scoring.
31
+ 2. **Similarity** — Arabic ↔ Arabic lenient fuzzy matching, insensitive to
32
+ tashkeel, hamza variants, taa-marbuta, and alef-maksura.
33
+ 3. **Batch** — paste a list of English names, get a table + CSV output.
34
+
35
+ ## Offline by design
36
+
37
+ No external API calls, no LLM, names never leave this Space container. The
38
+ entire pipeline runs on the 38 MB bundled XGBoost model + deterministic
39
+ rule-based engine.
40
+
41
+ ## Install locally
42
+
43
+ ```bash
44
+ pip install arabnamer
45
+ ```
46
+
47
+ Then:
48
+
49
+ ```python
50
+ from arabnamer import translit, similarity
51
+ print(translit("Mohammed Ali").arabic) # → 'محمد علي'
52
+ print(similarity("أحمد حسن", "احمد حسن")) # → (True, 100)
53
+ ```
54
+
55
+ ## Links
56
+
57
+ - 🔗 [GitHub repo](https://github.com/sayedyousef/arabnamer)
58
+ - 🔗 [PyPI package](https://pypi.org/project/arabnamer/)
59
+ - 🔗 [Model (this Space loads it transitively via the library)](https://huggingface.co/Sayedyousef/arabnamer-xgboost)
60
+ - 🔗 [Training dataset](https://huggingface.co/datasets/Sayedyousef/arabic-name-pairs)
61
+
62
+ ## License
63
+
64
+ - Code (this app): MIT
65
+ - Bundled model weights + training dictionary: CC-BY-4.0
app.py ADDED
@@ -0,0 +1,267 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Gradio demo for arabnamer — live on Hugging Face Spaces.
2
+
3
+ Three tabs:
4
+ 1. Transliterate — English name -> Arabic name (XGBoost / rules / hybrid engines)
5
+ 2. Similarity — two Arabic strings -> lenient similarity score
6
+ 3. Batch — paste many English names -> CSV-style output
7
+
8
+ Runs fully offline inside the Space container. No external API calls.
9
+ """
10
+ from __future__ import annotations
11
+
12
+ import csv
13
+ import io
14
+
15
+ import gradio as gr
16
+
17
+ from arabnamer import Transliterator, similarity
18
+
19
+ # Lazy singletons — load once, reuse for all requests
20
+ _XGB = Transliterator(engine="model", threshold=85)
21
+ _RULES = Transliterator(engine="rules", threshold=85)
22
+ _HYBRID = Transliterator(engine="hybrid", threshold=85)
23
+
24
+
25
+ def _get_engine(name: str) -> Transliterator:
26
+ return {"model (XGBoost)": _XGB, "rules (deterministic)": _RULES, "hybrid": _HYBRID}[name]
27
+
28
+
29
+ def translit_single(name_en: str, engine: str, reference: str | None, threshold: int) -> tuple[str, str, str]:
30
+ """Transliterate a single English name.
31
+
32
+ Returns: (arabic, score_display, details_markdown)
33
+ """
34
+ if not name_en or not name_en.strip():
35
+ return "", "—", "Enter an English name above."
36
+
37
+ t = _get_engine(engine)
38
+ t.threshold = threshold
39
+ ref = reference.strip() if reference and reference.strip() else None
40
+ r = t.translit(name_en, reference=ref)
41
+
42
+ if ref:
43
+ score_display = f"{r.score:.1f} / 100" + (" ✅ accepted" if r.accepted else " ❌ below threshold")
44
+ else:
45
+ score_display = "— (no reference supplied)"
46
+
47
+ details = f"""**Engine used:** `{r.engine}`
48
+
49
+ **Input:** `{r.input}`
50
+ **Predicted Arabic:** `{r.arabic}`
51
+ **Reference:** {f'`{r.reference}`' if r.reference else '_not provided_'}
52
+
53
+ {'**Score:** ' + str(r.score) + ' (threshold ' + str(threshold) + ')' if ref else ''}
54
+ """
55
+ return r.arabic, score_display, details
56
+
57
+
58
+ def similarity_pair(a: str, b: str, threshold: int) -> tuple[str, str, str]:
59
+ """Score Arabic-to-Arabic similarity with the lenient normalizer."""
60
+ if not a or not b:
61
+ return "—", "—", "Enter two Arabic strings above."
62
+
63
+ passed, score = similarity(a, b, threshold=threshold)
64
+ verdict = "✅ match" if passed else "❌ not a match (below threshold)"
65
+
66
+ # Normalized forms (for debugging / transparency)
67
+ from arabnamer.scoring import normalize_arabic
68
+ na, nb = normalize_arabic(a), normalize_arabic(b)
69
+ details = f"""**Input A:** `{a}`
70
+ **Input A (normalized):** `{na}`
71
+
72
+ **Input B:** `{b}`
73
+ **Input B (normalized):** `{nb}`
74
+
75
+ **Score:** {score} / 100 (threshold: {threshold})
76
+ """
77
+ return verdict, f"{score} / 100", details
78
+
79
+
80
+ def batch_transliterate(input_text: str, engine: str) -> tuple[str, str]:
81
+ """Run a list of English names through the selected engine.
82
+
83
+ Input: one name per line.
84
+ Output: markdown table + CSV string.
85
+ """
86
+ if not input_text or not input_text.strip():
87
+ return "Paste English names above (one per line).", ""
88
+
89
+ names = [line.strip() for line in input_text.splitlines() if line.strip()]
90
+ t = _get_engine(engine)
91
+
92
+ rows = [t.translit(n) for n in names]
93
+
94
+ # Markdown table
95
+ md_lines = ["| English | Arabic | Engine |", "|---|---|---|"]
96
+ for r in rows:
97
+ md_lines.append(f"| `{r.input}` | `{r.arabic}` | `{r.engine}` |")
98
+ md = "\n".join(md_lines)
99
+
100
+ # CSV string
101
+ buf = io.StringIO()
102
+ w = csv.writer(buf)
103
+ w.writerow(["name_en", "name_ar", "engine"])
104
+ for r in rows:
105
+ w.writerow([r.input, r.arabic, r.engine])
106
+
107
+ return md, buf.getvalue()
108
+
109
+
110
+ # ---------------------------------------------------------------------------
111
+ # UI
112
+ # ---------------------------------------------------------------------------
113
+
114
+ with gr.Blocks(title="arabnamer — Arabic name transliteration & similarity") as demo:
115
+ gr.Markdown(
116
+ """
117
+ # arabnamer — Arabic name transliteration & similarity
118
+
119
+ **Offline** English → Arabic name transliteration and Arabic-to-Arabic fuzzy matching.
120
+ No LLM, no external API, names never leave this Space. Bundled with a 38 MB pruned
121
+ XGBoost model trained on 22,798 English-Arabic name pairs.
122
+
123
+ **Install on your own machine:**
124
+ ```bash
125
+ pip install arabnamer
126
+ ```
127
+
128
+ 🔗 [GitHub](https://github.com/sayedyousef/arabnamer) ·
129
+ 🔗 [PyPI](https://pypi.org/project/arabnamer/) ·
130
+ 🔗 [Model](https://huggingface.co/Sayedyousef/arabnamer-xgboost) ·
131
+ 🔗 [Dataset](https://huggingface.co/datasets/Sayedyousef/arabic-name-pairs)
132
+ """
133
+ )
134
+
135
+ with gr.Tab("1. Transliterate"):
136
+ gr.Markdown("### English → Arabic")
137
+ with gr.Row():
138
+ with gr.Column():
139
+ name_in = gr.Textbox(
140
+ label="English name",
141
+ placeholder="Mohammed Ali",
142
+ lines=1,
143
+ )
144
+ engine_pick = gr.Radio(
145
+ ["model (XGBoost)", "rules (deterministic)", "hybrid"],
146
+ value="model (XGBoost)",
147
+ label="Engine",
148
+ )
149
+ ref_in = gr.Textbox(
150
+ label="Reference Arabic (optional — enables scoring)",
151
+ placeholder="محمد علي",
152
+ lines=1,
153
+ )
154
+ thresh_t = gr.Slider(
155
+ minimum=0, maximum=100, value=85, step=1,
156
+ label="Pass threshold (lenient score)",
157
+ )
158
+ btn_t = gr.Button("Transliterate", variant="primary")
159
+
160
+ with gr.Column():
161
+ ar_out = gr.Textbox(label="Predicted Arabic", lines=1)
162
+ score_out = gr.Textbox(label="Score (vs reference)", lines=1)
163
+ details_out = gr.Markdown()
164
+
165
+ btn_t.click(
166
+ fn=translit_single,
167
+ inputs=[name_in, engine_pick, ref_in, thresh_t],
168
+ outputs=[ar_out, score_out, details_out],
169
+ )
170
+
171
+ gr.Examples(
172
+ examples=[
173
+ ["Mohammed Ali", "model (XGBoost)", "محمد علي", 85],
174
+ ["Ayman El Desouky", "model (XGBoost)", "أيمن الدسوقي", 85],
175
+ ["Ahmad Hassan", "hybrid", "أحمد حسن", 90],
176
+ ["Tariq Da'na", "model (XGBoost)", "طارق دعنا", 85],
177
+ ["Abdennour Benantar", "rules (deterministic)", "", 85],
178
+ ],
179
+ inputs=[name_in, engine_pick, ref_in, thresh_t],
180
+ )
181
+
182
+ with gr.Tab("2. Similarity"):
183
+ gr.Markdown("### Arabic ↔ Arabic fuzzy similarity")
184
+ gr.Markdown(
185
+ "Scoring is lenient — tashkeel stripped, hamza/taa-marbuta/alef-maksura unified, "
186
+ "then `max(fuzz.ratio, fuzz.partial_ratio)` via rapidfuzz."
187
+ )
188
+ with gr.Row():
189
+ with gr.Column():
190
+ a_in = gr.Textbox(label="Arabic string A", placeholder="أحمد حسن", lines=1)
191
+ b_in = gr.Textbox(label="Arabic string B", placeholder="احمد حسن", lines=1)
192
+ thresh_s = gr.Slider(
193
+ minimum=0, maximum=100, value=85, step=1,
194
+ label="Pass threshold",
195
+ )
196
+ btn_s = gr.Button("Compare", variant="primary")
197
+
198
+ with gr.Column():
199
+ verdict_out = gr.Textbox(label="Result", lines=1)
200
+ sim_score_out = gr.Textbox(label="Score", lines=1)
201
+ sim_details_out = gr.Markdown()
202
+
203
+ btn_s.click(
204
+ fn=similarity_pair,
205
+ inputs=[a_in, b_in, thresh_s],
206
+ outputs=[verdict_out, sim_score_out, sim_details_out],
207
+ )
208
+
209
+ gr.Examples(
210
+ examples=[
211
+ ["أحمد حسن", "احمد حسن", 85],
212
+ ["مروة فرج", "مروه فرج", 85],
213
+ ["محمد علي", "محمد علي", 85],
214
+ ["أدهم ساولي", "أدهم الصولي", 85],
215
+ ],
216
+ inputs=[a_in, b_in, thresh_s],
217
+ )
218
+
219
+ with gr.Tab("3. Batch"):
220
+ gr.Markdown("### Batch transliteration")
221
+ gr.Markdown("Paste one English name per line. Output is a markdown table + downloadable CSV.")
222
+ with gr.Row():
223
+ with gr.Column():
224
+ batch_in = gr.Textbox(
225
+ label="English names (one per line)",
226
+ placeholder="Mohammed Ali\nAhmad Hassan\nMarwa Farag",
227
+ lines=10,
228
+ )
229
+ batch_engine = gr.Radio(
230
+ ["model (XGBoost)", "rules (deterministic)", "hybrid"],
231
+ value="model (XGBoost)",
232
+ label="Engine",
233
+ )
234
+ btn_b = gr.Button("Transliterate batch", variant="primary")
235
+
236
+ with gr.Column():
237
+ batch_md = gr.Markdown()
238
+ batch_csv = gr.Textbox(
239
+ label="CSV output (copy / paste)",
240
+ lines=10,
241
+ )
242
+
243
+ btn_b.click(
244
+ fn=batch_transliterate,
245
+ inputs=[batch_in, batch_engine],
246
+ outputs=[batch_md, batch_csv],
247
+ )
248
+
249
+ gr.Markdown(
250
+ """
251
+ ---
252
+
253
+ **About:** arabnamer is an open-source Python library extracted from an MSc-thesis
254
+ project on Arabic name handling. The model, dataset, and training code are all public
255
+ and reproducible. Built for KYC / compliance / on-premise entity resolution where
256
+ names cannot be sent to cloud APIs.
257
+
258
+ **License:** code MIT · dataset + model weights CC-BY-4.0.
259
+
260
+ Maintained by [Elsayed Yousef](mailto:elsayed.yousef@gmail.com) ·
261
+ [Commercial support available](mailto:elsayed.yousef@gmail.com).
262
+ """
263
+ )
264
+
265
+
266
+ if __name__ == "__main__":
267
+ demo.launch()
requirements.txt ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ arabnamer>=0.1.2
2
+ gradio>=4.0