# 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()