Files
999/scripts/rebuild_geomancy_intersections.py
T

241 lines
10 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env python3
"""Geomancy: structured dots, flatten fake groups, lexicon intersections."""
from __future__ import annotations
import json
import re
from collections import defaultdict
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
CANON = ROOT / "oracle_enricher" / "data" / "traditions_canon.json"
ASSET = ROOT / "assets" / "data" / "traditions_canon.json"
LEX = ROOT / "oracle_enricher" / "data" / "lexicon.json"
DART = ROOT / "lib" / "data" / "words_data.dart"
HTML = ROOT / "lexicon_viewer.html"
MD = ROOT / "words_alphabetical.md"
GEO_TAG_RE = re.compile(r"\[Геомантия:[^\]]*\]\s*")
# head → feet (Fire, Air, Water, Earth): 1 = single (odd), 2 = double (even)
DOTS = {
"via": [1, 1, 1, 1],
"populus": [2, 2, 2, 2],
"conjunctio": [2, 1, 1, 2],
"carcer": [1, 2, 2, 1],
"fortuna_major": [2, 2, 1, 1],
"fortuna_minor": [1, 1, 2, 2],
"acquisitio": [2, 1, 2, 1],
"amissio": [1, 2, 1, 2],
"laetitia": [1, 2, 2, 2],
"tristitia": [2, 2, 2, 1],
"puella": [1, 2, 1, 1],
"puer": [1, 1, 2, 1],
"albus": [2, 2, 1, 2],
"rubeus": [2, 1, 2, 2],
"caput_draconis": [2, 1, 1, 1],
"cauda_draconis": [1, 1, 1, 2],
}
META = {
"via": ("Вода", "Луна", "путь", ["дорога", "тропа"]),
"populus": ("Вода", "Луна", "народ", ["встреча", "мир"]),
"conjunctio": ("Воздух", "Меркурий", "союз", ["встреча", "связь"]),
"carcer": ("Земля", "Сатурн", "верёвка", ["плен", "цепь", "граница"]),
"fortuna_major": ("Земля", "Солнце", "победа", ["защита", "солнце"]),
"fortuna_minor": ("Огонь", "Солнце", "успех", ["случай", "помощь"]),
"acquisitio": ("Воздух", "Юпитер", "дар", ["золото", "изобилие"]),
"amissio": ("Огонь", "Венера", "жертва", ["вдова", "конец"]),
"laetitia": ("Воздух", "Юпитер", "радость", ["смех", "пир", "свет"]),
"tristitia": ("Земля", "Сатурн", "печаль", ["скорбь", "холод"]),
"puella": ("Вода", "Венера", "девушка", ["луна", "чистота"]),
"puer": ("Огонь", "Марс", "мальчик", ["огонь", "воля", "страсть"]),
"albus": ("Воздух", "Меркурий", "белый", ["чистота", "мудрость", "покой"]),
"rubeus": ("Огонь", "Марс", "красный", ["огонь", "кровь", "страсть"]),
"caput_draconis": ("Земля", "Северный Узел", "начало", ["вход", "порог", "рост"]),
"cauda_draconis": ("Огонь", "Южный Узел", "конец", ["выход", "жертва", "цикл"]),
}
def symbol_from(dots: list[int]) -> str:
return "\n".join("·" * n for n in dots)
def payload(word: str, dart_by_word: dict, lex_by_word: dict, old: dict | None = None) -> dict:
lex = lex_by_word.get(word) or {}
old = old or {}
return {
"id": dart_by_word[word],
"word": word,
"emoji": lex.get("emoji") or old.get("emoji") or "",
"pos": lex.get("pos") or old.get("pos") or "существительное",
"category": lex.get("category") or old.get("category") or "",
"desc": "",
}
def main() -> None:
dart_text = DART.read_text(encoding="utf-8")
dart_by_word = {
w: int(i)
for i, w in re.findall(r'WordItem\(\s*id:\s*(\d+),\s*word:\s*"([^"]+)"', dart_text)
}
lex_by_word = {e["word"]: e for e in json.loads(LEX.read_text(encoding="utf-8"))}
canon = json.loads(CANON.read_text(encoding="utf-8"))
geo = canon["geomancy"]
word_to_tags: dict[str, list[str]] = defaultdict(list)
missing = []
for st in geo["stations"]:
sid = st["id"]
dots = DOTS[sid]
element, planet, prim_w, extra_names = META[sid]
st["dots"] = dots
st["symbol"] = symbol_from(dots)
st["group"] = f"Геомантия · {element} · {planet}"
rec = {
"via": ("الطريق", "ат-Тарик", "Via", "Путь"),
"populus": ("الجماعة", "аль-Джама'а", "Populus", "Народ"),
"conjunctio": ("الاجتماع", "аль-Иджтима'", "Conjunctio", "Соединение"),
"carcer": ("الحبس", "аль-Хабс", "Carcer", "Тюрьма"),
"fortuna_major": ("النصرة الداخلة", "ан-Насра ад-Дахиля", "Fortuna Major", "Большая Удача"),
"fortuna_minor": ("النصرة الخارجة", "ан-Насра аль-Хариджа", "Fortuna Minor", "Малая Удача"),
"acquisitio": ("القبض الداخل", "аль-Кабд ад-Дахиль", "Acquisitio", "Приобретение"),
"amissio": ("القبض الخارج", "аль-Кабд аль-Харидж", "Amissio", "Потеря"),
"laetitia": ("الفرح", "аль-Фарах", "Laetitia", "Радость"),
"tristitia": ("الحزن", "аль-Хузн", "Tristitia", "Печаль"),
"puella": ("نقي الخد", "наки аль-хадд", "Puella", "Девушка"),
"puer": ("الانكيس", "аль-Анкийс", "Puer", "Юноша"),
"albus": ("البياض", "аль-Байяд", "Albus", "Белый"),
"rubeus": ("الحمرة", "аль-Хумра", "Rubeus", "Красный"),
"caput_draconis": ("رأس الجوزهر", "ра'с аль-джузхар", "Caput Draconis", "Голова Дракона"),
"cauda_draconis": ("ذنب الجوزهر", "занаб аль-джузхар", "Cauda Draconis", "Хвост Дракона"),
}.get(sid)
if rec:
script, latn, latin, ru = rec
st["arabic"] = script
n = int("".join(ch for ch in st.get("name", "") if ch.isdigit()) or "0") or (
list(DOTS).index(sid) + 1
)
st["name"] = f"{n} {latin} ({latn} / {ru})"
if prim_w not in dart_by_word:
missing.append(f"primary {sid}/{prim_w}")
continue
old_p = st.get("primary") or {}
st["primary"] = payload(prim_w, dart_by_word, lex_by_word, old_p)
extra = []
seen = {prim_w}
for w in extra_names:
if w in seen:
continue
if w not in dart_by_word:
missing.append(f"cluster {sid}/{w}")
continue
seen.add(w)
extra.append(w)
st["cluster"] = [payload(w, dart_by_word, lex_by_word) for w in extra]
tag = f"[Геомантия: {st['name']}]"
word_to_tags[prim_w].append(tag)
for w in extra:
word_to_tags[w].append(tag)
if missing:
print("WARN", missing)
geo["description"] = (
"16 классических фигур 'Илм ар-рамл: четыре ряда точек (голова → стопы), "
"стихия и планета. Пересечения со словарём 999."
)
geo["count"] = len(geo["stations"])
text = json.dumps(canon, ensure_ascii=False, indent=2) + "\n"
CANON.write_text(text, encoding="utf-8")
ASSET.write_text(text, encoding="utf-8")
dart_lines = dart_text.splitlines(keepends=True)
nstamp = 0
i = 0
while i < len(dart_lines):
wm = re.match(r'(\s*word: ")([^"]+)(",\s*)$', dart_lines[i])
if wm:
word = wm.group(2)
for j in range(i + 1, min(i + 12, len(dart_lines))):
raw = dart_lines[j].rstrip("\n")
dm = re.match(r'^(\s*description: ")(.*)(",)\s*$', raw)
if dm:
body = GEO_TAG_RE.sub("", dm.group(2)).strip()
tags = word_to_tags.get(word)
if tags:
dart_lines[j] = f"{dm.group(1)}{' '.join(tags)} {body}{dm.group(3)}\n"
nstamp += 1
else:
dart_lines[j] = f"{dm.group(1)}{body}{dm.group(3)}\n"
break
if dart_lines[j].strip().startswith("WordItem("):
break
i += 1
dart2 = "".join(dart_lines)
DART.write_text(dart2, encoding="utf-8")
html = HTML.read_text(encoding="utf-8")
start = html.find(" const TRADITIONS_CANON = ")
end = html.find(" const WORDS_DATA = ")
if start > 0 and end > start:
html = (
html[:start]
+ " const TRADITIONS_CANON = "
+ json.dumps(canon, ensure_ascii=False, indent=2)
+ ";\n\n"
+ html[end:]
)
m = re.search(r"const WORDS_DATA = (\[.*?\]);", html, flags=re.DOTALL)
if not m:
raise SystemExit("WORDS_DATA not found")
words = json.loads(m.group(1))
dart_descs = dict(re.findall(r'word: "([^"]+)"[\s\S]*?description: "([^"]*)"', dart2))
for w in words:
wd = w.get("word")
if wd in dart_descs:
w["description"] = dart_descs[wd]
html = html[: m.start(1)] + json.dumps(words, ensure_ascii=False) + html[m.end(1) :]
HTML.write_text(html, encoding="utf-8")
if MD.exists():
md = MD.read_text(encoding="utf-8")
def repl_block(mm: re.Match) -> str:
word = mm.group(1)
body = mm.group(3)
if word not in dart_descs:
return mm.group(0)
body2 = re.sub(
r"\* \*\*Толкование:\*\* .+",
f"* **Толкование:** {dart_descs[word]}",
body,
count=1,
)
return f"### {word} `{mm.group(2)}`\n{body2}"
md2 = re.sub(
r"### ([^\s`]+) `(#[^`]+)`\n([\s\S]*?)(?=\n### |\n## |\Z)",
repl_block,
md,
)
MD.write_text(md2, encoding="utf-8")
print(f"stamped {nstamp} tagged {len(word_to_tags)}")
keys = defaultdict(list)
for st in geo["stations"]:
gkey = st["group"].split(" · ", 1)[0]
keys[gkey].append(st["id"])
cl = [c["word"] for c in st["cluster"]]
print(
f" {st['id']:16} {st['dots']} {st['group']:32} "
f"prim={st['primary']['word']:12} {cl}"
)
print("group keys", {k: len(v) for k, v in keys.items()})
if __name__ == "__main__":
main()