File size: 10,302 Bytes
28e98a4
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
# 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()