Spaces:
Sleeping
Sleeping
# gematria_gradio.py | |
# Hebrew Gematria Lab โ Gradio Edition | |
# Features: | |
# - Systems: Standard, Gadol (finals 500โ900), Ordinal (1โ22), Mispar Katan (reduced) | |
# - Cleans niqqud/cantillation, punctuation, spaces | |
# - Atbash & Albam transforms | |
# - Batch processing with optional target matching | |
# - CSV export | |
import re | |
import os | |
import unicodedata | |
import tempfile | |
import pandas as pd | |
import gradio as gr | |
# ----------------------------- | |
# Unicode & Cleanup Utilities | |
# ----------------------------- | |
HEBREW_LETTERS = "ืืืืืืืืืืืืืื ืกืขืคืฆืงืจืฉืชืืืืฃืฅ" | |
FINAL_FORMS = {"ื": "ื", "ื": "ื", "ื": "ื ", "ืฃ": "ืค", "ืฅ": "ืฆ"} | |
COMBINING_MARKS_PATTERN = re.compile(r"[\u0591-\u05BD\u05BF\u05C1-\u05C2\u05C4-\u05C5\u05C7]") | |
NON_HEBREW_PATTERN = re.compile(r"[^ื-ืชืืืืฃืฅ]+") | |
def strip_niqqud(text: str) -> str: | |
return COMBINING_MARKS_PATTERN.sub("", text) | |
def normalize_text(text: str, keep_spaces: bool = False) -> str: | |
t = unicodedata.normalize("NFC", text or "") | |
t = strip_niqqud(t) | |
if not keep_spaces: | |
t = t.replace(" ", "") | |
t = NON_HEBREW_PATTERN.sub("", t) | |
return t | |
# ----------------------------- | |
# Letter Value Tables | |
# ----------------------------- | |
STANDARD_VALUES = { | |
"ื":1, "ื":2, "ื":3, "ื":4, "ื":5, "ื":6, "ื":7, "ื":8, "ื":9, | |
"ื":10, "ื":20, "ื":30, "ื":40, "ื ":50, "ืก":60, "ืข":70, "ืค":80, "ืฆ":90, | |
"ืง":100, "ืจ":200, "ืฉ":300, "ืช":400, | |
"ื":20, "ื":40, "ื":50, "ืฃ":80, "ืฅ":90, | |
} | |
GADOL_VALUES = { | |
**STANDARD_VALUES, | |
"ื":500, "ื":600, "ื":700, "ืฃ":800, "ืฅ":900, | |
} | |
# Ordinal mapping | |
ORDINAL_MAP = {} | |
base_order = "ืืืืืืืืืืืืืื ืกืขืคืฆืงืจืฉืช" | |
for i, ch in enumerate(base_order, start=1): | |
ORDINAL_MAP[ch] = i | |
for f, b in FINAL_FORMS.items(): | |
ORDINAL_MAP[f] = ORDINAL_MAP[b] | |
def mispar_katan_value(ch: str) -> int: | |
v = STANDARD_VALUES.get(ch, 0) | |
if v == 0: | |
return 0 | |
return ((v - 1) % 9) + 1 | |
# ----------------------------- | |
# Transforms: Atbash & Albam | |
# ----------------------------- | |
ATBASH_MAP = {} | |
pairs = list(zip(base_order, base_order[::-1])) | |
for a, b in pairs: | |
ATBASH_MAP[a] = b | |
ATBASH_MAP[b] = a | |
for f, b in FINAL_FORMS.items(): | |
ATBASH_MAP[f] = ATBASH_MAP[b] | |
ALPH = list(base_order) | |
shift = 11 | |
ALBAM_MAP = {} | |
for i, ch in enumerate(ALPH): | |
j = (i + shift) % len(ALPH) | |
ALBAM_MAP[ch] = ALPH[j] | |
ALBAM_MAP[ALPH[j]] = ch | |
for f, b in FINAL_FORMS.items(): | |
ALBAM_MAP[f] = ALBAM_MAP[b] | |
def atbash(text: str) -> str: | |
t = normalize_text(text, keep_spaces=True) | |
return "".join(ATBASH_MAP.get(ch, ch) for ch in t) | |
def albam(text: str) -> str: | |
t = normalize_text(text, keep_spaces=True) | |
return "".join(ALBAM_MAP.get(ch, ch) for ch in t) | |
# ----------------------------- | |
# Gematria Calculations | |
# ----------------------------- | |
def digital_root(n: int) -> int: | |
if n == 0: | |
return 0 | |
return 1 + (n - 1) % 9 | |
def gematria_total(text: str, system: str = "standard") -> int: | |
t = normalize_text(text) | |
if system == "standard": | |
values = STANDARD_VALUES | |
return sum(values.get(ch, 0) for ch in t) | |
elif system == "gadol": | |
values = GADOL_VALUES | |
return sum(values.get(ch, 0) for ch in t) | |
elif system == "ordinal": | |
values = ORDINAL_MAP | |
return sum(values.get(ch, 0) for ch in t) | |
elif system == "katan": | |
return sum(mispar_katan_value(ch) for ch in t) | |
else: | |
raise ValueError("Unknown system") | |
def cross_system_table(text: str): | |
systems = ["standard", "gadol", "ordinal", "katan"] | |
rows = [] | |
for s in systems: | |
total = gematria_total(text, s) | |
rows.append({ | |
"System": s.title(), | |
"Total": total, | |
"Digital Root": digital_root(total) | |
}) | |
return pd.DataFrame(rows) | |
# ----------------------------- | |
# Gradio Callbacks | |
# ----------------------------- | |
def calc_single(text, system, show_root): | |
cleaned = normalize_text(text, keep_spaces=True) | |
total = gematria_total(text, system) | |
root = digital_root(total) if show_root else None | |
df = cross_system_table(text) | |
main_line = f"Cleaned: `{cleaned}` โ Total ({system}): **{total}**" | |
if show_root: | |
main_line += f" โ Digital root: **{root}**" | |
return main_line, df | |
def do_batch(list_text, system, show_root, target_str, sort_by_total): | |
items = [line for line in (list_text or "").splitlines() if line.strip()] | |
data = [] | |
for item in items: | |
cleaned = normalize_text(item, keep_spaces=True) | |
tot = gematria_total(item, system) | |
row = {"Original": item, "Cleaned": cleaned, f"Total ({system})": tot} | |
if show_root: | |
row["Digital Root"] = digital_root(tot) | |
data.append(row) | |
if not data: | |
df = pd.DataFrame(columns=["Original", "Cleaned", f"Total ({system})"] + (["Digital Root"] if show_root else [])) | |
else: | |
df = pd.DataFrame(data) | |
if sort_by_total: | |
df = df.sort_values(by=[f"Total ({system})", "Cleaned"]).reset_index(drop=True) | |
# Optional highlight info text | |
highlight_info = "" | |
target_val = None | |
try: | |
target_val = int(target_str) if (target_str or "").strip() else None | |
except: | |
target_val = None | |
if target_val is not None: | |
matches = (df[f"Total ({system})"] == target_val).sum() | |
highlight_info = f"Target **{target_val}** โ matches: **{matches}**" | |
# Write CSV temp file for download | |
tmpdir = tempfile.mkdtemp(prefix="gematria_") | |
csv_path = os.path.join(tmpdir, "gematria_batch.csv") | |
df.to_csv(csv_path, index=False, encoding="utf-8-sig") | |
return df, highlight_info, csv_path | |
def do_transforms(text): | |
a = atbash(text or "") | |
b = albam(text or "") | |
rows = [] | |
def totals_row(label, txt): | |
return { | |
"Form": label, | |
"Text": normalize_text(txt, keep_spaces=True), | |
"Standard": gematria_total(txt, "standard"), | |
"Gadol": gematria_total(txt, "gadol"), | |
"Ordinal": gematria_total(txt, "ordinal"), | |
"Katan": gematria_total(txt, "katan"), | |
} | |
rows.append(totals_row("Original", text or "")) | |
rows.append(totals_row("Atbash", a)) | |
rows.append(totals_row("Albam", b)) | |
return a, b, pd.DataFrame(rows) | |
# ----------------------------- | |
# UI | |
# ----------------------------- | |
with gr.Blocks(title="Hebrew Gematria Lab (Gradio)") as demo: | |
gr.Markdown("# Hebrew Gematria Lab โ Gradio") | |
gr.Markdown( | |
"Explore Hebrew words across **Standard**, **Gadol**, **Ordinal**, and **Mispar Katan** systems. " | |
"Use **Atbash** and **Albam** transforms, batch-process lists, and export to CSV." | |
) | |
with gr.Tabs(): | |
with gr.Tab("Calculator"): | |
with gr.Row(): | |
text_in = gr.Textbox(label="Enter Hebrew text", value="ืืงืจ") | |
with gr.Row(): | |
system = gr.Dropdown(choices=["standard", "gadol", "ordinal", "katan"], value="standard", label="Gematria system") | |
show_root = gr.Checkbox(value=True, label="Show digital root") | |
calc_btn = gr.Button("Compute") | |
result_line = gr.Markdown() | |
result_df = gr.Dataframe(headers=["System", "Total", "Digital Root"], interactive=False) | |
calc_btn.click( | |
fn=calc_single, | |
inputs=[text_in, system, show_root], | |
outputs=[result_line, result_df] | |
) | |
with gr.Tab("Batch & Matching"): | |
batch_tb = gr.Textbox( | |
label="Paste list (one item per line)", | |
lines=10, | |
value="ืืงืจ\nืืืช\nืืื\nืืืื\nืืืช\nืืืื\nืืื\nืฉืืื" | |
) | |
with gr.Row(): | |
system_b = gr.Dropdown(choices=["standard", "gadol", "ordinal", "katan"], value="standard", label="Gematria system") | |
show_root_b = gr.Checkbox(value=True, label="Show digital root") | |
target_val_tb = gr.Textbox(label="Target total (optional)", placeholder="e.g., 304") | |
sort_chk = gr.Checkbox(value=True, label="Sort by total") | |
run_batch_btn = gr.Button("Run Batch") | |
batch_df = gr.Dataframe(interactive=False) | |
match_info = gr.Markdown() | |
download_csv = gr.File(label="Download CSV") | |
run_batch_btn.click( | |
fn=do_batch, | |
inputs=[batch_tb, system_b, show_root_b, target_val_tb, sort_chk], | |
outputs=[batch_df, match_info, download_csv] | |
) | |
with gr.Tab("Transforms"): | |
t_input = gr.Textbox(label="Hebrew text", value="ืฉืืื") | |
t_btn = gr.Button("Transform") | |
atbash_out = gr.Textbox(label="Atbash", interactive=False) | |
albam_out = gr.Textbox(label="Albam", interactive=False) | |
trans_df = gr.Dataframe(interactive=False) | |
t_btn.click( | |
fn=do_transforms, | |
inputs=[t_input], | |
outputs=[atbash_out, albam_out, trans_df] | |
) | |
with gr.Tab("Reference"): | |
gr.Markdown( | |
""" | |
**Systems** | |
- **Standard (Mispar Hechrechi):** ืช=400; finals = base. | |
- **Gadol:** finals extended ื=500, ื=600, ื=700, ืฃ=800, ืฅ=900. | |
- **Ordinal:** ื=1 โฆ ืช=22 (finals use base). | |
- **Mispar Katan:** letters reduced to 1โ9, then summed. | |
**Transforms** | |
- **Atbash:** mirror pairs ืโืช, ืโืฉ, ืโืจ, โฆ | |
- **Albam:** shift by 11 (ืโื, ืโื, ืโื , โฆ, ืโืช). | |
**Cleanup** | |
- Removes niqqud/cantillation & non-Hebrew marks; normalizes to NFC. | |
""" | |
) | |
ex_df = pd.DataFrame( | |
[ | |
("ืืงืจ (pierce)", "Standard", 304), | |
("ืืืช (damat)", "Standard", 444), | |
("ืืื (one)", "Standard", 13), | |
("ืืืื (love)", "Standard", 13), | |
], | |
columns=["Word", "System", "Total"] | |
) | |
gr.Dataframe(value=ex_df, interactive=False) | |
gr.Markdown( | |
"Tip: Use **Batch & Matching** to hunt for words/phrases that equal a target total (e.g., 304 for ืืงืจ)." | |
) | |
if __name__ == "__main__": | |
demo.launch() |