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