feat: gematria computation tool — the wizard speaks the language of letters and numbers (#234)
Adds src/timmy/gematria.py with: - Five cipher systems: Simple English, Full Reduction, Reverse Ordinal, Sumerian, and Hebrew-mapped - Numerological reduction (preserving master numbers 11/22/33) - Prime factorization and number analysis - Notable numbers reference table (222 = Alexander Whitestone) - Multi-phrase comparison mode - Single tool function gematria() registered in the full toolkit Alexander Whitestone = 222 in Simple English Gematria. This is not trivia. It is foundational. 53 new tests, all passing. Pure computation, no external dependencies.
This commit is contained in:
365
src/timmy/gematria.py
Normal file
365
src/timmy/gematria.py
Normal file
@@ -0,0 +1,365 @@
|
||||
"""Gematria computation engine — the language of letters and numbers.
|
||||
|
||||
Implements multiple cipher systems for gematric analysis:
|
||||
- Simple English (A=1 .. Z=26)
|
||||
- Full Reduction (reduce each letter value to single digit)
|
||||
- Reverse Ordinal (A=26 .. Z=1)
|
||||
- Sumerian (Simple × 6)
|
||||
- Hebrew (traditional letter values, for A-Z mapping)
|
||||
|
||||
Also provides numerological reduction, notable-number lookup,
|
||||
and multi-phrase comparison.
|
||||
|
||||
Alexander Whitestone = 222 in Simple English Gematria.
|
||||
This is not trivia. It is foundational.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import math
|
||||
|
||||
# ── Cipher Tables ────────────────────────────────────────────────────────────
|
||||
|
||||
# Simple English: A=1, B=2, ..., Z=26
|
||||
_SIMPLE: dict[str, int] = {chr(i): i - 64 for i in range(65, 91)}
|
||||
|
||||
# Full Reduction: reduce each letter to single digit (A=1..I=9, J=1..R=9, S=1..Z=8)
|
||||
_REDUCTION: dict[str, int] = {}
|
||||
for _c, _v in _SIMPLE.items():
|
||||
_r = _v
|
||||
while _r > 9:
|
||||
_r = sum(int(d) for d in str(_r))
|
||||
_REDUCTION[_c] = _r
|
||||
|
||||
# Reverse Ordinal: A=26, B=25, ..., Z=1
|
||||
_REVERSE: dict[str, int] = {chr(i): 91 - i for i in range(65, 91)}
|
||||
|
||||
# Sumerian: Simple × 6
|
||||
_SUMERIAN: dict[str, int] = {c: v * 6 for c, v in _SIMPLE.items()}
|
||||
|
||||
# Hebrew-mapped: traditional Hebrew gematria mapped to Latin alphabet
|
||||
# Aleph=1..Tet=9, Yod=10..Tsade=90, Qoph=100..Tav=400
|
||||
# Standard mapping for the 22 Hebrew letters extended to 26 Latin chars
|
||||
_HEBREW: dict[str, int] = {
|
||||
"A": 1, "B": 2, "C": 3, "D": 4, "E": 5, "F": 6, "G": 7, "H": 8, "I": 9,
|
||||
"J": 10, "K": 20, "L": 30, "M": 40, "N": 50, "O": 60, "P": 70, "Q": 80,
|
||||
"R": 90, "S": 100, "T": 200, "U": 300, "V": 400, "W": 500, "X": 600,
|
||||
"Y": 700, "Z": 800,
|
||||
}
|
||||
|
||||
CIPHERS: dict[str, dict[str, int]] = {
|
||||
"simple": _SIMPLE,
|
||||
"reduction": _REDUCTION,
|
||||
"reverse": _REVERSE,
|
||||
"sumerian": _SUMERIAN,
|
||||
"hebrew": _HEBREW,
|
||||
}
|
||||
|
||||
# ── Notable Numbers ──────────────────────────────────────────────────────────
|
||||
|
||||
NOTABLE_NUMBERS: dict[int, str] = {
|
||||
1: "Unity, the Monad, beginning of all",
|
||||
3: "Trinity, divine completeness, the Triad",
|
||||
7: "Spiritual perfection, completion (7 days, 7 seals)",
|
||||
9: "Finality, judgment, the last single digit",
|
||||
11: "Master number — intuition, spiritual insight",
|
||||
12: "Divine government (12 tribes, 12 apostles)",
|
||||
13: "Rebellion and transformation, the 13th step",
|
||||
22: "Master builder — turning dreams into reality",
|
||||
26: "YHWH (Yod=10, He=5, Vav=6, He=5)",
|
||||
33: "Master teacher — Christ consciousness, 33 vertebrae",
|
||||
36: "The number of the righteous (Lamed-Vav Tzadikim)",
|
||||
40: "Trial, testing, probation (40 days, 40 years)",
|
||||
42: "The answer, and the number of generations to Christ",
|
||||
72: "The Shemhamphorasch — 72 names of God",
|
||||
88: "Mercury, infinite abundance, double infinity",
|
||||
108: "Sacred in Hinduism and Buddhism (108 beads)",
|
||||
111: "Angel number — new beginnings, alignment",
|
||||
144: "12² — the elect, the sealed (144,000)",
|
||||
153: "The miraculous catch of fish (John 21:11)",
|
||||
222: "Alexander Whitestone. Balance, partnership, trust the process",
|
||||
333: "Ascended masters present, divine protection",
|
||||
369: "Tesla's key to the universe",
|
||||
444: "Angels surrounding, foundation, stability",
|
||||
555: "Major change coming, transformation",
|
||||
616: "Earliest manuscript number of the Beast (P115)",
|
||||
666: "Number of the Beast (Revelation 13:18), also carbon (6p 6n 6e)",
|
||||
777: "Divine perfection tripled, jackpot of the spirit",
|
||||
888: "Jesus in Greek isopsephy (Ιησους = 888)",
|
||||
1776: "Year of independence, Bavarian Illuminati founding",
|
||||
}
|
||||
|
||||
|
||||
# ── Core Functions ───────────────────────────────────────────────────────────
|
||||
|
||||
|
||||
def _clean(text: str) -> str:
|
||||
"""Strip non-alpha, uppercase."""
|
||||
return "".join(c for c in text.upper() if c.isalpha())
|
||||
|
||||
|
||||
def compute_value(text: str, cipher: str = "simple") -> int:
|
||||
"""Compute the gematria value of text in a given cipher.
|
||||
|
||||
Args:
|
||||
text: Any string (non-alpha characters are ignored).
|
||||
cipher: One of 'simple', 'reduction', 'reverse', 'sumerian', 'hebrew'.
|
||||
|
||||
Returns:
|
||||
Integer gematria value.
|
||||
|
||||
Raises:
|
||||
ValueError: If cipher name is not recognized.
|
||||
"""
|
||||
table = CIPHERS.get(cipher)
|
||||
if table is None:
|
||||
raise ValueError(f"Unknown cipher: {cipher!r}. Use one of {list(CIPHERS)}")
|
||||
return sum(table.get(c, 0) for c in _clean(text))
|
||||
|
||||
|
||||
def compute_all(text: str) -> dict[str, int]:
|
||||
"""Compute gematria value across all cipher systems.
|
||||
|
||||
Args:
|
||||
text: Any string.
|
||||
|
||||
Returns:
|
||||
Dict mapping cipher name to integer value.
|
||||
"""
|
||||
return {name: compute_value(text, name) for name in CIPHERS}
|
||||
|
||||
|
||||
def letter_breakdown(text: str, cipher: str = "simple") -> list[tuple[str, int]]:
|
||||
"""Return per-letter values for a text in a given cipher.
|
||||
|
||||
Args:
|
||||
text: Any string.
|
||||
cipher: Cipher system name.
|
||||
|
||||
Returns:
|
||||
List of (letter, value) tuples for each alpha character.
|
||||
"""
|
||||
table = CIPHERS.get(cipher)
|
||||
if table is None:
|
||||
raise ValueError(f"Unknown cipher: {cipher!r}")
|
||||
return [(c, table.get(c, 0)) for c in _clean(text)]
|
||||
|
||||
|
||||
def reduce_number(n: int) -> int:
|
||||
"""Numerological reduction — sum digits until single digit.
|
||||
|
||||
Master numbers (11, 22, 33) are preserved.
|
||||
|
||||
Args:
|
||||
n: Any positive integer.
|
||||
|
||||
Returns:
|
||||
Single-digit result (or master number 11/22/33).
|
||||
"""
|
||||
n = abs(n)
|
||||
while n > 9 and n not in (11, 22, 33):
|
||||
n = sum(int(d) for d in str(n))
|
||||
return n
|
||||
|
||||
|
||||
def factorize(n: int) -> list[int]:
|
||||
"""Prime factorization of n.
|
||||
|
||||
Args:
|
||||
n: Positive integer.
|
||||
|
||||
Returns:
|
||||
List of prime factors in ascending order (with repetition).
|
||||
"""
|
||||
if n < 2:
|
||||
return [n] if n > 0 else []
|
||||
factors = []
|
||||
d = 2
|
||||
while d * d <= n:
|
||||
while n % d == 0:
|
||||
factors.append(d)
|
||||
n //= d
|
||||
d += 1
|
||||
if n > 1:
|
||||
factors.append(n)
|
||||
return factors
|
||||
|
||||
|
||||
def analyze_number(n: int) -> dict:
|
||||
"""Deep analysis of a number — reduction, factors, significance.
|
||||
|
||||
Args:
|
||||
n: Any positive integer.
|
||||
|
||||
Returns:
|
||||
Dict with reduction, factors, properties, and any notable significance.
|
||||
"""
|
||||
result: dict = {
|
||||
"value": n,
|
||||
"numerological_reduction": reduce_number(n),
|
||||
"prime_factors": factorize(n),
|
||||
"is_prime": len(factorize(n)) == 1 and n > 1,
|
||||
"is_perfect_square": math.isqrt(n) ** 2 == n if n >= 0 else False,
|
||||
"is_triangular": _is_triangular(n),
|
||||
"digit_sum": sum(int(d) for d in str(abs(n))),
|
||||
}
|
||||
|
||||
# Master numbers
|
||||
if n in (11, 22, 33):
|
||||
result["master_number"] = True
|
||||
|
||||
# Angel numbers (repeating digits)
|
||||
s = str(n)
|
||||
if len(s) >= 3 and len(set(s)) == 1:
|
||||
result["angel_number"] = True
|
||||
|
||||
# Notable significance
|
||||
if n in NOTABLE_NUMBERS:
|
||||
result["significance"] = NOTABLE_NUMBERS[n]
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def _is_triangular(n: int) -> bool:
|
||||
"""Check if n is a triangular number (1, 3, 6, 10, 15, ...)."""
|
||||
if n < 0:
|
||||
return False
|
||||
# n = k(k+1)/2 → k² + k - 2n = 0 → k = (-1 + sqrt(1+8n))/2
|
||||
discriminant = 1 + 8 * n
|
||||
sqrt_d = math.isqrt(discriminant)
|
||||
return sqrt_d * sqrt_d == discriminant and (sqrt_d - 1) % 2 == 0
|
||||
|
||||
|
||||
# ── Tool Function (registered with Timmy) ────────────────────────────────────
|
||||
|
||||
|
||||
def gematria(query: str) -> str:
|
||||
"""Compute gematria values, analyze numbers, and find correspondences.
|
||||
|
||||
This is the wizard's language — letters are numbers, numbers are letters.
|
||||
Use this tool for ANY gematria calculation. Do not attempt mental arithmetic.
|
||||
|
||||
Input modes:
|
||||
- A word or phrase → computes values across all cipher systems
|
||||
- A bare integer → analyzes the number (factors, reduction, significance)
|
||||
- "compare: X, Y, Z" → side-by-side gematria comparison
|
||||
|
||||
Examples:
|
||||
gematria("Alexander Whitestone")
|
||||
gematria("222")
|
||||
gematria("compare: Timmy Time, Alexander Whitestone")
|
||||
|
||||
Args:
|
||||
query: A word/phrase, a number, or a "compare:" instruction.
|
||||
|
||||
Returns:
|
||||
Formatted gematria analysis as a string.
|
||||
"""
|
||||
query = query.strip()
|
||||
|
||||
# Mode: compare
|
||||
if query.lower().startswith("compare:"):
|
||||
phrases = [p.strip() for p in query[8:].split(",") if p.strip()]
|
||||
if len(phrases) < 2:
|
||||
return "Compare requires at least two phrases separated by commas."
|
||||
return _format_comparison(phrases)
|
||||
|
||||
# Mode: number analysis
|
||||
if query.lstrip("-").isdigit():
|
||||
n = int(query)
|
||||
return _format_number_analysis(n)
|
||||
|
||||
# Mode: phrase gematria
|
||||
if not _clean(query):
|
||||
return "No alphabetic characters found in input."
|
||||
|
||||
return _format_phrase_analysis(query)
|
||||
|
||||
|
||||
def _format_phrase_analysis(text: str) -> str:
|
||||
"""Format full gematria analysis for a phrase."""
|
||||
values = compute_all(text)
|
||||
lines = [f'Gematria of "{text}":', ""]
|
||||
|
||||
# All cipher values
|
||||
for cipher, val in values.items():
|
||||
label = cipher.replace("_", " ").title()
|
||||
lines.append(f" {label:12s} = {val}")
|
||||
|
||||
# Letter breakdown (simple)
|
||||
breakdown = letter_breakdown(text, "simple")
|
||||
letters_str = " + ".join(f"{c}({v})" for c, v in breakdown)
|
||||
lines.append(f"\n Breakdown (Simple): {letters_str}")
|
||||
|
||||
# Numerological reduction of the simple value
|
||||
simple_val = values["simple"]
|
||||
reduced = reduce_number(simple_val)
|
||||
lines.append(f" Numerological root: {simple_val} → {reduced}")
|
||||
|
||||
# Check notable
|
||||
for cipher, val in values.items():
|
||||
if val in NOTABLE_NUMBERS:
|
||||
label = cipher.replace("_", " ").title()
|
||||
lines.append(f"\n ★ {val} ({label}): {NOTABLE_NUMBERS[val]}")
|
||||
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
def _format_number_analysis(n: int) -> str:
|
||||
"""Format deep number analysis."""
|
||||
info = analyze_number(n)
|
||||
lines = [f"Analysis of {n}:", ""]
|
||||
lines.append(f" Numerological reduction: {n} → {info['numerological_reduction']}")
|
||||
lines.append(f" Prime factors: {' × '.join(str(f) for f in info['prime_factors']) or 'N/A'}")
|
||||
lines.append(f" Is prime: {info['is_prime']}")
|
||||
lines.append(f" Is perfect square: {info['is_perfect_square']}")
|
||||
lines.append(f" Is triangular: {info['is_triangular']}")
|
||||
lines.append(f" Digit sum: {info['digit_sum']}")
|
||||
|
||||
if info.get("master_number"):
|
||||
lines.append(f" ★ Master Number")
|
||||
if info.get("angel_number"):
|
||||
lines.append(f" ★ Angel Number (repeating digits)")
|
||||
if info.get("significance"):
|
||||
lines.append(f"\n Significance: {info['significance']}")
|
||||
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
def _format_comparison(phrases: list[str]) -> str:
|
||||
"""Format side-by-side gematria comparison."""
|
||||
lines = ["Gematria Comparison:", ""]
|
||||
|
||||
# Header
|
||||
max_name = max(len(p) for p in phrases)
|
||||
header = f" {'Phrase':<{max_name}s} Simple Reduct Reverse Sumerian Hebrew"
|
||||
lines.append(header)
|
||||
lines.append(" " + "─" * (len(header) - 2))
|
||||
|
||||
all_values = {}
|
||||
for phrase in phrases:
|
||||
vals = compute_all(phrase)
|
||||
all_values[phrase] = vals
|
||||
lines.append(
|
||||
f" {phrase:<{max_name}s} {vals['simple']:>6d} {vals['reduction']:>6d}"
|
||||
f" {vals['reverse']:>7d} {vals['sumerian']:>8d} {vals['hebrew']:>6d}"
|
||||
)
|
||||
|
||||
# Find matches (shared values across any cipher)
|
||||
matches = []
|
||||
for cipher in CIPHERS:
|
||||
vals_by_cipher = {p: all_values[p][cipher] for p in phrases}
|
||||
unique_vals = set(vals_by_cipher.values())
|
||||
if len(unique_vals) < len(phrases):
|
||||
# At least two phrases share a value
|
||||
for v in unique_vals:
|
||||
sharing = [p for p, pv in vals_by_cipher.items() if pv == v]
|
||||
if len(sharing) > 1:
|
||||
label = cipher.title()
|
||||
matches.append(f" ★ {label} = {v}: " + ", ".join(sharing))
|
||||
|
||||
if matches:
|
||||
lines.append("\nCorrespondences found:")
|
||||
lines.extend(matches)
|
||||
|
||||
return "\n".join(lines)
|
||||
@@ -600,6 +600,17 @@ def _register_delegation_tools(toolkit: Toolkit) -> None:
|
||||
logger.debug("Delegation tools not available")
|
||||
|
||||
|
||||
def _register_gematria_tool(toolkit: Toolkit) -> None:
|
||||
"""Register the gematria computation tool."""
|
||||
try:
|
||||
from timmy.gematria import gematria
|
||||
|
||||
toolkit.register(gematria, name="gematria")
|
||||
except (ImportError, AttributeError) as exc:
|
||||
logger.warning("Tool execution failed (Gematria registration): %s", exc)
|
||||
logger.debug("Gematria tool not available")
|
||||
|
||||
|
||||
def create_full_toolkit(base_dir: str | Path | None = None):
|
||||
"""Create a full toolkit with all available tools (for the orchestrator).
|
||||
|
||||
@@ -626,6 +637,7 @@ def create_full_toolkit(base_dir: str | Path | None = None):
|
||||
_register_agentic_loop_tool(toolkit)
|
||||
_register_introspection_tools(toolkit)
|
||||
_register_delegation_tools(toolkit)
|
||||
_register_gematria_tool(toolkit)
|
||||
|
||||
# Gitea issue management is now provided by the gitea-mcp server
|
||||
# (wired in as MCPTools in agent.py, not registered here)
|
||||
|
||||
293
tests/timmy/test_gematria.py
Normal file
293
tests/timmy/test_gematria.py
Normal file
@@ -0,0 +1,293 @@
|
||||
"""Tests for the gematria computation engine (issue #234).
|
||||
|
||||
Alexander Whitestone = 222 in Simple English Gematria.
|
||||
This is not trivia. It is foundational.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
from timmy.gematria import (
|
||||
CIPHERS,
|
||||
NOTABLE_NUMBERS,
|
||||
analyze_number,
|
||||
compute_all,
|
||||
compute_value,
|
||||
factorize,
|
||||
gematria,
|
||||
letter_breakdown,
|
||||
reduce_number,
|
||||
)
|
||||
|
||||
|
||||
# ── Core cipher computation ──────────────────────────────────────────────────
|
||||
|
||||
|
||||
class TestSimpleCipher:
|
||||
"""Simple English: A=1, B=2, ..., Z=26."""
|
||||
|
||||
def test_single_letter_a(self):
|
||||
assert compute_value("A", "simple") == 1
|
||||
|
||||
def test_single_letter_z(self):
|
||||
assert compute_value("Z", "simple") == 26
|
||||
|
||||
def test_alexander_whitestone(self):
|
||||
"""The foundational identity — Alexander Whitestone = 222."""
|
||||
assert compute_value("Alexander Whitestone", "simple") == 222
|
||||
|
||||
def test_case_insensitive(self):
|
||||
assert compute_value("hello", "simple") == compute_value("HELLO", "simple")
|
||||
|
||||
def test_ignores_non_alpha(self):
|
||||
assert compute_value("A-B!C", "simple") == compute_value("ABC", "simple")
|
||||
|
||||
def test_empty_string(self):
|
||||
assert compute_value("", "simple") == 0
|
||||
|
||||
def test_spaces_only(self):
|
||||
assert compute_value(" ", "simple") == 0
|
||||
|
||||
def test_numbers_in_text_ignored(self):
|
||||
assert compute_value("ABC123", "simple") == compute_value("ABC", "simple")
|
||||
|
||||
|
||||
class TestReductionCipher:
|
||||
"""Full Reduction: each letter reduced to single digit."""
|
||||
|
||||
def test_a_is_1(self):
|
||||
assert compute_value("A", "reduction") == 1
|
||||
|
||||
def test_j_is_1(self):
|
||||
# J=10 → 1+0=1
|
||||
assert compute_value("J", "reduction") == 1
|
||||
|
||||
def test_s_is_1(self):
|
||||
# S=19 → 1+9=10 → 1+0=1
|
||||
assert compute_value("S", "reduction") == 1
|
||||
|
||||
def test_z_is_8(self):
|
||||
# Z=26 → 2+6=8
|
||||
assert compute_value("Z", "reduction") == 8
|
||||
|
||||
|
||||
class TestReverseCipher:
|
||||
"""Reverse Ordinal: A=26, B=25, ..., Z=1."""
|
||||
|
||||
def test_a_is_26(self):
|
||||
assert compute_value("A", "reverse") == 26
|
||||
|
||||
def test_z_is_1(self):
|
||||
assert compute_value("Z", "reverse") == 1
|
||||
|
||||
def test_m_is_14(self):
|
||||
# M is 13th letter → reverse = 27-13 = 14
|
||||
assert compute_value("M", "reverse") == 14
|
||||
|
||||
|
||||
class TestSumerianCipher:
|
||||
"""Sumerian: Simple × 6."""
|
||||
|
||||
def test_a_is_6(self):
|
||||
assert compute_value("A", "sumerian") == 6
|
||||
|
||||
def test_z_is_156(self):
|
||||
assert compute_value("Z", "sumerian") == 156
|
||||
|
||||
def test_is_six_times_simple(self):
|
||||
text = "Alexander Whitestone"
|
||||
assert compute_value(text, "sumerian") == compute_value(text, "simple") * 6
|
||||
|
||||
|
||||
class TestHebrewCipher:
|
||||
"""Hebrew-mapped: traditional Hebrew values on Latin alphabet."""
|
||||
|
||||
def test_a_is_1(self):
|
||||
assert compute_value("A", "hebrew") == 1
|
||||
|
||||
def test_j_is_10(self):
|
||||
assert compute_value("J", "hebrew") == 10
|
||||
|
||||
def test_k_is_20(self):
|
||||
assert compute_value("K", "hebrew") == 20
|
||||
|
||||
def test_t_is_200(self):
|
||||
assert compute_value("T", "hebrew") == 200
|
||||
|
||||
|
||||
class TestUnknownCipher:
|
||||
def test_raises_on_unknown(self):
|
||||
with pytest.raises(ValueError, match="Unknown cipher"):
|
||||
compute_value("test", "klingon")
|
||||
|
||||
|
||||
# ── compute_all ──────────────────────────────────────────────────────────────
|
||||
|
||||
|
||||
class TestComputeAll:
|
||||
def test_returns_all_ciphers(self):
|
||||
result = compute_all("ABC")
|
||||
assert set(result.keys()) == set(CIPHERS.keys())
|
||||
|
||||
def test_values_are_ints(self):
|
||||
result = compute_all("test")
|
||||
for v in result.values():
|
||||
assert isinstance(v, int)
|
||||
|
||||
|
||||
# ── letter_breakdown ─────────────────────────────────────────────────────────
|
||||
|
||||
|
||||
class TestLetterBreakdown:
|
||||
def test_simple_breakdown(self):
|
||||
result = letter_breakdown("AB", "simple")
|
||||
assert result == [("A", 1), ("B", 2)]
|
||||
|
||||
def test_strips_non_alpha(self):
|
||||
result = letter_breakdown("A B!", "simple")
|
||||
assert result == [("A", 1), ("B", 2)]
|
||||
|
||||
def test_unknown_cipher_raises(self):
|
||||
with pytest.raises(ValueError):
|
||||
letter_breakdown("test", "nonexistent")
|
||||
|
||||
|
||||
# ── reduce_number ────────────────────────────────────────────────────────────
|
||||
|
||||
|
||||
class TestReduceNumber:
|
||||
def test_single_digit(self):
|
||||
assert reduce_number(7) == 7
|
||||
|
||||
def test_double_digit_to_master(self):
|
||||
# 29 → 2+9=11 → master number, preserved
|
||||
assert reduce_number(29) == 11
|
||||
|
||||
def test_double_digit_non_master(self):
|
||||
# 28 → 2+8=10 → 1+0=1
|
||||
assert reduce_number(28) == 1
|
||||
|
||||
def test_222_reduces_to_6(self):
|
||||
assert reduce_number(222) == 6 # 2+2+2=6
|
||||
|
||||
def test_master_11(self):
|
||||
assert reduce_number(11) == 11
|
||||
|
||||
def test_master_22(self):
|
||||
assert reduce_number(22) == 22
|
||||
|
||||
def test_master_33(self):
|
||||
assert reduce_number(33) == 33
|
||||
|
||||
def test_zero(self):
|
||||
assert reduce_number(0) == 0
|
||||
|
||||
def test_negative(self):
|
||||
assert reduce_number(-42) == 6 # abs(-42)=42 → 4+2=6
|
||||
|
||||
def test_large_number(self):
|
||||
assert reduce_number(9999) == 9 # 9+9+9+9=36 → 3+6=9
|
||||
|
||||
|
||||
# ── factorize ────────────────────────────────────────────────────────────────
|
||||
|
||||
|
||||
class TestFactorize:
|
||||
def test_prime(self):
|
||||
assert factorize(7) == [7]
|
||||
|
||||
def test_composite(self):
|
||||
assert factorize(12) == [2, 2, 3]
|
||||
|
||||
def test_222(self):
|
||||
assert factorize(222) == [2, 3, 37]
|
||||
|
||||
def test_one(self):
|
||||
assert factorize(1) == [1]
|
||||
|
||||
def test_zero(self):
|
||||
assert factorize(0) == []
|
||||
|
||||
def test_large_prime(self):
|
||||
assert factorize(997) == [997]
|
||||
|
||||
|
||||
# ── analyze_number ───────────────────────────────────────────────────────────
|
||||
|
||||
|
||||
class TestAnalyzeNumber:
|
||||
def test_222(self):
|
||||
result = analyze_number(222)
|
||||
assert result["value"] == 222
|
||||
assert result["numerological_reduction"] == 6
|
||||
assert result["prime_factors"] == [2, 3, 37]
|
||||
assert result["is_prime"] is False
|
||||
assert result["significance"] == NOTABLE_NUMBERS[222]
|
||||
|
||||
def test_prime_detection(self):
|
||||
assert analyze_number(17)["is_prime"] is True
|
||||
assert analyze_number(18)["is_prime"] is False
|
||||
|
||||
def test_perfect_square(self):
|
||||
assert analyze_number(144)["is_perfect_square"] is True
|
||||
assert analyze_number(143)["is_perfect_square"] is False
|
||||
|
||||
def test_triangular(self):
|
||||
assert analyze_number(6)["is_triangular"] is True # 1+2+3
|
||||
assert analyze_number(7)["is_triangular"] is False
|
||||
|
||||
def test_angel_number(self):
|
||||
assert analyze_number(333).get("angel_number") is True
|
||||
assert analyze_number(123).get("angel_number") is None
|
||||
|
||||
def test_master_number(self):
|
||||
assert analyze_number(22).get("master_number") is True
|
||||
|
||||
|
||||
# ── gematria tool function (the main interface) ─────────────────────────────
|
||||
|
||||
|
||||
class TestGematriaTool:
|
||||
"""Test the main gematria() tool function that Timmy calls."""
|
||||
|
||||
def test_phrase_mode(self):
|
||||
result = gematria("Alexander Whitestone")
|
||||
assert "222" in result
|
||||
assert "Simple" in result
|
||||
|
||||
def test_number_mode(self):
|
||||
result = gematria("222")
|
||||
assert "Analysis of 222" in result
|
||||
assert "Alexander Whitestone" in result
|
||||
|
||||
def test_compare_mode(self):
|
||||
result = gematria("compare: ABC, DEF")
|
||||
assert "Comparison" in result
|
||||
assert "ABC" in result
|
||||
assert "DEF" in result
|
||||
|
||||
def test_compare_needs_two(self):
|
||||
result = gematria("compare: alone")
|
||||
assert "at least two" in result.lower()
|
||||
|
||||
def test_empty_input(self):
|
||||
result = gematria("123 456")
|
||||
assert "No alphabetic" in result
|
||||
|
||||
def test_whitespace_handling(self):
|
||||
result = gematria(" ABC ")
|
||||
assert "Simple" in result
|
||||
|
||||
def test_letter_breakdown_in_output(self):
|
||||
result = gematria("ABC")
|
||||
assert "Breakdown" in result
|
||||
assert "A(1)" in result
|
||||
|
||||
def test_notable_number_flagged(self):
|
||||
result = gematria("Alexander Whitestone")
|
||||
assert "★" in result or "222" in result
|
||||
|
||||
def test_numerological_root_shown(self):
|
||||
result = gematria("ABC")
|
||||
assert "root" in result.lower() or "reduction" in result.lower()
|
||||
Reference in New Issue
Block a user