Spaces:
Sleeping
Sleeping
Create app.py
Browse files
app.py
ADDED
@@ -0,0 +1,297 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
pip install gradio pandas
|
2 |
+
python gematria_gradio.py
|
3 |
+
python
|
4 |
+
Copy
|
5 |
+
Edit
|
6 |
+
# gematria_gradio.py
|
7 |
+
# Hebrew Gematria Lab โ Gradio Edition
|
8 |
+
# Features:
|
9 |
+
# - Systems: Standard, Gadol (finals 500โ900), Ordinal (1โ22), Mispar Katan (reduced)
|
10 |
+
# - Cleans niqqud/cantillation, punctuation, spaces
|
11 |
+
# - Atbash & Albam transforms
|
12 |
+
# - Batch processing with optional target matching
|
13 |
+
# - CSV export
|
14 |
+
|
15 |
+
import re
|
16 |
+
import os
|
17 |
+
import unicodedata
|
18 |
+
import tempfile
|
19 |
+
import pandas as pd
|
20 |
+
import gradio as gr
|
21 |
+
|
22 |
+
# -----------------------------
|
23 |
+
# Unicode & Cleanup Utilities
|
24 |
+
# -----------------------------
|
25 |
+
HEBREW_LETTERS = "ืืืืืืืืืืืืืื ืกืขืคืฆืงืจืฉืชืืืืฃืฅ"
|
26 |
+
FINAL_FORMS = {"ื": "ื", "ื": "ื", "ื": "ื ", "ืฃ": "ืค", "ืฅ": "ืฆ"}
|
27 |
+
|
28 |
+
COMBINING_MARKS_PATTERN = re.compile(r"[\u0591-\u05BD\u05BF\u05C1-\u05C2\u05C4-\u05C5\u05C7]")
|
29 |
+
NON_HEBREW_PATTERN = re.compile(r"[^ื-ืชืืืืฃืฅ]+")
|
30 |
+
|
31 |
+
def strip_niqqud(text: str) -> str:
|
32 |
+
return COMBINING_MARKS_PATTERN.sub("", text)
|
33 |
+
|
34 |
+
def normalize_text(text: str, keep_spaces: bool = False) -> str:
|
35 |
+
t = unicodedata.normalize("NFC", text or "")
|
36 |
+
t = strip_niqqud(t)
|
37 |
+
if not keep_spaces:
|
38 |
+
t = t.replace(" ", "")
|
39 |
+
t = NON_HEBREW_PATTERN.sub("", t)
|
40 |
+
return t
|
41 |
+
|
42 |
+
# -----------------------------
|
43 |
+
# Letter Value Tables
|
44 |
+
# -----------------------------
|
45 |
+
STANDARD_VALUES = {
|
46 |
+
"ื":1, "ื":2, "ื":3, "ื":4, "ื":5, "ื":6, "ื":7, "ื":8, "ื":9,
|
47 |
+
"ื":10, "ื":20, "ื":30, "ื":40, "ื ":50, "ืก":60, "ืข":70, "ืค":80, "ืฆ":90,
|
48 |
+
"ืง":100, "ืจ":200, "ืฉ":300, "ืช":400,
|
49 |
+
"ื":20, "ื":40, "ื":50, "ืฃ":80, "ืฅ":90,
|
50 |
+
}
|
51 |
+
|
52 |
+
GADOL_VALUES = {
|
53 |
+
**STANDARD_VALUES,
|
54 |
+
"ื":500, "ื":600, "ื":700, "ืฃ":800, "ืฅ":900,
|
55 |
+
}
|
56 |
+
|
57 |
+
# Ordinal mapping
|
58 |
+
ORDINAL_MAP = {}
|
59 |
+
base_order = "ืืืืืืืืืืืืืื ืกืขืคืฆืงืจืฉืช"
|
60 |
+
for i, ch in enumerate(base_order, start=1):
|
61 |
+
ORDINAL_MAP[ch] = i
|
62 |
+
for f, b in FINAL_FORMS.items():
|
63 |
+
ORDINAL_MAP[f] = ORDINAL_MAP[b]
|
64 |
+
|
65 |
+
def mispar_katan_value(ch: str) -> int:
|
66 |
+
v = STANDARD_VALUES.get(ch, 0)
|
67 |
+
if v == 0:
|
68 |
+
return 0
|
69 |
+
return ((v - 1) % 9) + 1
|
70 |
+
|
71 |
+
# -----------------------------
|
72 |
+
# Transforms: Atbash & Albam
|
73 |
+
# -----------------------------
|
74 |
+
ATBASH_MAP = {}
|
75 |
+
pairs = list(zip(base_order, base_order[::-1]))
|
76 |
+
for a, b in pairs:
|
77 |
+
ATBASH_MAP[a] = b
|
78 |
+
ATBASH_MAP[b] = a
|
79 |
+
for f, b in FINAL_FORMS.items():
|
80 |
+
ATBASH_MAP[f] = ATBASH_MAP[b]
|
81 |
+
|
82 |
+
ALPH = list(base_order)
|
83 |
+
shift = 11
|
84 |
+
ALBAM_MAP = {}
|
85 |
+
for i, ch in enumerate(ALPH):
|
86 |
+
j = (i + shift) % len(ALPH)
|
87 |
+
ALBAM_MAP[ch] = ALPH[j]
|
88 |
+
ALBAM_MAP[ALPH[j]] = ch
|
89 |
+
for f, b in FINAL_FORMS.items():
|
90 |
+
ALBAM_MAP[f] = ALBAM_MAP[b]
|
91 |
+
|
92 |
+
def atbash(text: str) -> str:
|
93 |
+
t = normalize_text(text, keep_spaces=True)
|
94 |
+
return "".join(ATBASH_MAP.get(ch, ch) for ch in t)
|
95 |
+
|
96 |
+
def albam(text: str) -> str:
|
97 |
+
t = normalize_text(text, keep_spaces=True)
|
98 |
+
return "".join(ALBAM_MAP.get(ch, ch) for ch in t)
|
99 |
+
|
100 |
+
# -----------------------------
|
101 |
+
# Gematria Calculations
|
102 |
+
# -----------------------------
|
103 |
+
def digital_root(n: int) -> int:
|
104 |
+
if n == 0:
|
105 |
+
return 0
|
106 |
+
return 1 + (n - 1) % 9
|
107 |
+
|
108 |
+
def gematria_total(text: str, system: str = "standard") -> int:
|
109 |
+
t = normalize_text(text)
|
110 |
+
if system == "standard":
|
111 |
+
values = STANDARD_VALUES
|
112 |
+
return sum(values.get(ch, 0) for ch in t)
|
113 |
+
elif system == "gadol":
|
114 |
+
values = GADOL_VALUES
|
115 |
+
return sum(values.get(ch, 0) for ch in t)
|
116 |
+
elif system == "ordinal":
|
117 |
+
values = ORDINAL_MAP
|
118 |
+
return sum(values.get(ch, 0) for ch in t)
|
119 |
+
elif system == "katan":
|
120 |
+
return sum(mispar_katan_value(ch) for ch in t)
|
121 |
+
else:
|
122 |
+
raise ValueError("Unknown system")
|
123 |
+
|
124 |
+
def cross_system_table(text: str):
|
125 |
+
systems = ["standard", "gadol", "ordinal", "katan"]
|
126 |
+
rows = []
|
127 |
+
for s in systems:
|
128 |
+
total = gematria_total(text, s)
|
129 |
+
rows.append({
|
130 |
+
"System": s.title(),
|
131 |
+
"Total": total,
|
132 |
+
"Digital Root": digital_root(total)
|
133 |
+
})
|
134 |
+
return pd.DataFrame(rows)
|
135 |
+
|
136 |
+
# -----------------------------
|
137 |
+
# Gradio Callbacks
|
138 |
+
# -----------------------------
|
139 |
+
def calc_single(text, system, show_root):
|
140 |
+
cleaned = normalize_text(text, keep_spaces=True)
|
141 |
+
total = gematria_total(text, system)
|
142 |
+
root = digital_root(total) if show_root else None
|
143 |
+
df = cross_system_table(text)
|
144 |
+
main_line = f"Cleaned: `{cleaned}` โ Total ({system}): **{total}**"
|
145 |
+
if show_root:
|
146 |
+
main_line += f" โ Digital root: **{root}**"
|
147 |
+
return main_line, df
|
148 |
+
|
149 |
+
def do_batch(list_text, system, show_root, target_str, sort_by_total):
|
150 |
+
items = [line for line in (list_text or "").splitlines() if line.strip()]
|
151 |
+
data = []
|
152 |
+
for item in items:
|
153 |
+
cleaned = normalize_text(item, keep_spaces=True)
|
154 |
+
tot = gematria_total(item, system)
|
155 |
+
row = {"Original": item, "Cleaned": cleaned, f"Total ({system})": tot}
|
156 |
+
if show_root:
|
157 |
+
row["Digital Root"] = digital_root(tot)
|
158 |
+
data.append(row)
|
159 |
+
if not data:
|
160 |
+
df = pd.DataFrame(columns=["Original", "Cleaned", f"Total ({system})"] + (["Digital Root"] if show_root else []))
|
161 |
+
else:
|
162 |
+
df = pd.DataFrame(data)
|
163 |
+
if sort_by_total:
|
164 |
+
df = df.sort_values(by=[f"Total ({system})", "Cleaned"]).reset_index(drop=True)
|
165 |
+
|
166 |
+
# Optional highlight info text
|
167 |
+
highlight_info = ""
|
168 |
+
target_val = None
|
169 |
+
try:
|
170 |
+
target_val = int(target_str) if (target_str or "").strip() else None
|
171 |
+
except:
|
172 |
+
target_val = None
|
173 |
+
if target_val is not None:
|
174 |
+
matches = (df[f"Total ({system})"] == target_val).sum()
|
175 |
+
highlight_info = f"Target **{target_val}** โ matches: **{matches}**"
|
176 |
+
|
177 |
+
# Write CSV temp file for download
|
178 |
+
tmpdir = tempfile.mkdtemp(prefix="gematria_")
|
179 |
+
csv_path = os.path.join(tmpdir, "gematria_batch.csv")
|
180 |
+
df.to_csv(csv_path, index=False, encoding="utf-8-sig")
|
181 |
+
|
182 |
+
return df, highlight_info, csv_path
|
183 |
+
|
184 |
+
def do_transforms(text):
|
185 |
+
a = atbash(text or "")
|
186 |
+
b = albam(text or "")
|
187 |
+
rows = []
|
188 |
+
def totals_row(label, txt):
|
189 |
+
return {
|
190 |
+
"Form": label,
|
191 |
+
"Text": normalize_text(txt, keep_spaces=True),
|
192 |
+
"Standard": gematria_total(txt, "standard"),
|
193 |
+
"Gadol": gematria_total(txt, "gadol"),
|
194 |
+
"Ordinal": gematria_total(txt, "ordinal"),
|
195 |
+
"Katan": gematria_total(txt, "katan"),
|
196 |
+
}
|
197 |
+
rows.append(totals_row("Original", text or ""))
|
198 |
+
rows.append(totals_row("Atbash", a))
|
199 |
+
rows.append(totals_row("Albam", b))
|
200 |
+
return a, b, pd.DataFrame(rows)
|
201 |
+
|
202 |
+
# -----------------------------
|
203 |
+
# UI
|
204 |
+
# -----------------------------
|
205 |
+
with gr.Blocks(title="Hebrew Gematria Lab (Gradio)") as demo:
|
206 |
+
gr.Markdown("# Hebrew Gematria Lab โ Gradio")
|
207 |
+
gr.Markdown(
|
208 |
+
"Explore Hebrew words across **Standard**, **Gadol**, **Ordinal**, and **Mispar Katan** systems. "
|
209 |
+
"Use **Atbash** and **Albam** transforms, batch-process lists, and export to CSV."
|
210 |
+
)
|
211 |
+
|
212 |
+
with gr.Tabs():
|
213 |
+
with gr.Tab("Calculator"):
|
214 |
+
with gr.Row():
|
215 |
+
text_in = gr.Textbox(label="Enter Hebrew text", value="ืืงืจ")
|
216 |
+
with gr.Row():
|
217 |
+
system = gr.Dropdown(choices=["standard", "gadol", "ordinal", "katan"], value="standard", label="Gematria system")
|
218 |
+
show_root = gr.Checkbox(value=True, label="Show digital root")
|
219 |
+
calc_btn = gr.Button("Compute")
|
220 |
+
result_line = gr.Markdown()
|
221 |
+
result_df = gr.Dataframe(headers=["System", "Total", "Digital Root"], interactive=False)
|
222 |
+
|
223 |
+
calc_btn.click(
|
224 |
+
fn=calc_single,
|
225 |
+
inputs=[text_in, system, show_root],
|
226 |
+
outputs=[result_line, result_df]
|
227 |
+
)
|
228 |
+
|
229 |
+
with gr.Tab("Batch & Matching"):
|
230 |
+
batch_tb = gr.Textbox(
|
231 |
+
label="Paste list (one item per line)",
|
232 |
+
lines=10,
|
233 |
+
value="ืืงืจ\nืืืช\nืืื\nืืืื\nืืืช\nืืืื\nืืื\nืฉืืื"
|
234 |
+
)
|
235 |
+
with gr.Row():
|
236 |
+
system_b = gr.Dropdown(choices=["standard", "gadol", "ordinal", "katan"], value="standard", label="Gematria system")
|
237 |
+
show_root_b = gr.Checkbox(value=True, label="Show digital root")
|
238 |
+
target_val_tb = gr.Textbox(label="Target total (optional)", placeholder="e.g., 304")
|
239 |
+
sort_chk = gr.Checkbox(value=True, label="Sort by total")
|
240 |
+
run_batch_btn = gr.Button("Run Batch")
|
241 |
+
batch_df = gr.Dataframe(interactive=False)
|
242 |
+
match_info = gr.Markdown()
|
243 |
+
download_csv = gr.File(label="Download CSV")
|
244 |
+
|
245 |
+
run_batch_btn.click(
|
246 |
+
fn=do_batch,
|
247 |
+
inputs=[batch_tb, system_b, show_root_b, target_val_tb, sort_chk],
|
248 |
+
outputs=[batch_df, match_info, download_csv]
|
249 |
+
)
|
250 |
+
|
251 |
+
with gr.Tab("Transforms"):
|
252 |
+
t_input = gr.Textbox(label="Hebrew text", value="ืฉืืื")
|
253 |
+
t_btn = gr.Button("Transform")
|
254 |
+
atbash_out = gr.Textbox(label="Atbash", interactive=False)
|
255 |
+
albam_out = gr.Textbox(label="Albam", interactive=False)
|
256 |
+
trans_df = gr.Dataframe(interactive=False)
|
257 |
+
|
258 |
+
t_btn.click(
|
259 |
+
fn=do_transforms,
|
260 |
+
inputs=[t_input],
|
261 |
+
outputs=[atbash_out, albam_out, trans_df]
|
262 |
+
)
|
263 |
+
|
264 |
+
with gr.Tab("Reference"):
|
265 |
+
gr.Markdown(
|
266 |
+
"""
|
267 |
+
**Systems**
|
268 |
+
- **Standard (Mispar Hechrechi):** ืช=400; finals = base.
|
269 |
+
- **Gadol:** finals extended ื=500, ื=600, ื=700, ืฃ=800, ืฅ=900.
|
270 |
+
- **Ordinal:** ื=1 โฆ ืช=22 (finals use base).
|
271 |
+
- **Mispar Katan:** letters reduced to 1โ9, then summed.
|
272 |
+
|
273 |
+
**Transforms**
|
274 |
+
- **Atbash:** mirror pairs ืโืช, ืโืฉ, ืโืจ, โฆ
|
275 |
+
- **Albam:** shift by 11 (ืโื, ืโื, ืโื , โฆ, ืโืช).
|
276 |
+
|
277 |
+
**Cleanup**
|
278 |
+
- Removes niqqud/cantillation & non-Hebrew marks; normalizes to NFC.
|
279 |
+
"""
|
280 |
+
)
|
281 |
+
ex_df = pd.DataFrame(
|
282 |
+
[
|
283 |
+
("ืืงืจ (pierce)", "Standard", 304),
|
284 |
+
("ืืืช (damat)", "Standard", 444),
|
285 |
+
("ืืื (one)", "Standard", 13),
|
286 |
+
("ืืืื (love)", "Standard", 13),
|
287 |
+
],
|
288 |
+
columns=["Word", "System", "Total"]
|
289 |
+
)
|
290 |
+
gr.Dataframe(value=ex_df, interactive=False)
|
291 |
+
|
292 |
+
gr.Markdown(
|
293 |
+
"Tip: Use **Batch & Matching** to hunt for words/phrases that equal a target total (e.g., 304 for ืืงืจ)."
|
294 |
+
)
|
295 |
+
|
296 |
+
if __name__ == "__main__":
|
297 |
+
demo.launch()
|