feat: complete academic symbol lexicon heal, 2-column Cooper extraction, and updated assets
This commit is contained in:
@@ -0,0 +1,246 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Fix Tarot grouping and rebuild lexicon + cross-system parallels.
|
||||
|
||||
Колесо Фортуны было с другим group key. У «Мира» раздутый cluster.
|
||||
"""
|
||||
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"
|
||||
|
||||
ROMAN = ["0", "I", "II", "III", "IV", "V", "VI", "VII", "VIII", "IX", "X",
|
||||
"XI", "XII", "XIII", "XIV", "XV", "XVI", "XVII", "XVIII", "XIX", "XX", "XXI"]
|
||||
|
||||
# station_id -> extra lexicon words (primary is already on the station)
|
||||
CLUSTERS = {
|
||||
"fool": ["путь", "начало", "маска", "воздух"],
|
||||
"magician": ["творец", "воля", "огонь", "вода", "земля", "воздух"],
|
||||
"high_priestess": ["тайна", "завеса", "луна", "книга", "порог"],
|
||||
"empress": ["земля", "рост", "росток", "сад", "плод"],
|
||||
"emperor": ["закон", "корона", "сила"],
|
||||
"hierophant": ["храм", "закон", "книга"],
|
||||
"lovers": ["союз", "сердце", "связь", "кольцо"],
|
||||
"chariot": ["путь", "дорога", "воин", "воля"],
|
||||
"strength": ["смирение", "лев", "огонь"],
|
||||
"hermit": ["мудрец", "лампа", "путь", "покой"],
|
||||
"wheel_of_fortune": ["цикл", "судьба", "карма", "круг"],
|
||||
"justice": ["закон", "весы"],
|
||||
"hanged_man": ["покой", "путь"],
|
||||
"death": ["гроб", "тень", "цикл", "коса"],
|
||||
"temperance": ["вода", "мера"],
|
||||
"devil": ["цепь", "змей", "маска"],
|
||||
"tower": ["удар", "молния"],
|
||||
"star": ["надежда", "искра", "свет"],
|
||||
"moon": ["зеркало", "туман", "затмение", "вода"],
|
||||
"sun": ["свет", "золото", "космос"],
|
||||
"judgement": ["ангел", "свет"],
|
||||
"world": ["космос", "земля", "ось", "круг", "покой"],
|
||||
}
|
||||
|
||||
# station_id -> [(system, station_id)]
|
||||
PARALLELS = {
|
||||
"fool": [("runes", "raidho")],
|
||||
"magician": [("runes", "kenaz"), ("iching", "qian"), ("alchemy", "fire")],
|
||||
"high_priestess": [("lenormand", "card_26"), ("lenormand", "card_32")],
|
||||
"empress": [("runes", "berkano"), ("alchemy", "earth"), ("lenormand", "card_20")],
|
||||
"emperor": [("runes", "tiwaz"), ("runes", "othala")],
|
||||
"hierophant": [("runes", "ansuz"), ("lenormand", "card_26")],
|
||||
"lovers": [("runes", "gebo"), ("lenormand", "card_24"), ("lenormand", "card_25")],
|
||||
"chariot": [("runes", "raidho"), ("runes", "ehwaz")],
|
||||
"strength": [("runes", "uruz")],
|
||||
"hermit": [("runes", "isa"), ("runes", "perthro"), ("lenormand", "card_33")],
|
||||
"wheel_of_fortune": [("runes", "jera"), ("runes", "raidho"), ("iching", "hex_24_fu")],
|
||||
"justice": [("runes", "tiwaz"), ("iching", "hex_06_song")],
|
||||
"hanged_man": [("runes", "eihwaz")],
|
||||
"death": [("alchemy", "nigredo"), ("lenormand", "card_8"), ("lenormand", "card_10")],
|
||||
"temperance": [("runes", "laguz"), ("alchemy", "water")],
|
||||
"devil": [("runes", "nauthiz"), ("runes", "thurisaz")],
|
||||
"tower": [("runes", "hagalaz"), ("lenormand", "card_19")],
|
||||
"star": [("runes", "sowilo"), ("lenormand", "card_16")],
|
||||
"moon": [("lenormand", "card_32"), ("runes", "laguz")],
|
||||
"sun": [("runes", "sowilo"), ("lenormand", "card_31"), ("alchemy", "citrinitas")],
|
||||
"judgement": [("runes", "dagaz"), ("iching", "hex_06_song")],
|
||||
"world": [("runes", "othala"), ("alchemy", "earth"), ("iching", "hex_11_tai")],
|
||||
}
|
||||
|
||||
SYS_TITLE = {
|
||||
"runes": "Руны",
|
||||
"lenormand": "Ленорман",
|
||||
"iching": "И Цзин",
|
||||
"alchemy": "Алхимия",
|
||||
"tarot": "Таро",
|
||||
}
|
||||
|
||||
TAROT_TAG_RE = re.compile(r"\[Таро:[^\]]*\]\s*")
|
||||
|
||||
|
||||
def payload(entry: dict) -> dict:
|
||||
return {
|
||||
"id": entry["id"],
|
||||
"word": entry["word"],
|
||||
"emoji": entry.get("emoji", ""),
|
||||
"pos": entry.get("pos", ""),
|
||||
"category": entry.get("category", ""),
|
||||
"desc": "",
|
||||
}
|
||||
|
||||
|
||||
def find_station(canon: dict, system: str, sid: str) -> dict | None:
|
||||
for st in canon.get(system, {}).get("stations") or []:
|
||||
if st.get("id") == sid:
|
||||
return st
|
||||
return None
|
||||
|
||||
|
||||
def main() -> None:
|
||||
canon = json.loads(CANON.read_text(encoding="utf-8"))
|
||||
lex = {e["word"]: e for e in json.loads(LEX.read_text(encoding="utf-8"))}
|
||||
tarot = canon["tarot"]
|
||||
|
||||
# normalize ids / groups
|
||||
for i, st in enumerate(tarot["stations"]):
|
||||
if st["id"] == "wheel":
|
||||
st["id"] = "wheel_of_fortune"
|
||||
rest = re.sub(r"^(?:[0-9]+|[IVXLCDM]+)\s+", "", st["name"])
|
||||
st["group"] = f"Старшие Арканы · {ROMAN[i]} {rest}"
|
||||
|
||||
word_to_tags: dict[str, list[str]] = defaultdict(list)
|
||||
|
||||
for n, st in enumerate(tarot["stations"]):
|
||||
sid = st["id"]
|
||||
prim_w = (st.get("primary") or {}).get("word")
|
||||
cluster_words = []
|
||||
for w in CLUSTERS.get(sid, []):
|
||||
if w == prim_w or w not in lex:
|
||||
continue
|
||||
if w not in cluster_words:
|
||||
cluster_words.append(w)
|
||||
st["cluster"] = [payload(lex[w]) for w in cluster_words[:6]]
|
||||
|
||||
parallels = []
|
||||
for sys, psid in PARALLELS.get(sid, []):
|
||||
other = find_station(canon, sys, psid)
|
||||
if not other:
|
||||
print("missing parallel", sys, psid)
|
||||
continue
|
||||
parallels.append(
|
||||
{
|
||||
"system": sys,
|
||||
"system_name": SYS_TITLE.get(sys, sys),
|
||||
"id": other["id"],
|
||||
"name": other["name"],
|
||||
"symbol": other.get("symbol", ""),
|
||||
}
|
||||
)
|
||||
st["parallels"] = parallels
|
||||
|
||||
tag = f"[Таро: {st['name']}]"
|
||||
if prim_w:
|
||||
word_to_tags[prim_w].append(tag)
|
||||
for w in cluster_words[:6]:
|
||||
word_to_tags[w].append(tag)
|
||||
|
||||
tarot["description"] = (
|
||||
"22 Старших Аркана одной колодой: сводное значение, сочетания, "
|
||||
"пересечения со словарём 999 и параллели с рунами, Ленорман, Ицзином, алхимией."
|
||||
)
|
||||
|
||||
text = json.dumps(canon, ensure_ascii=False, indent=2) + "\n"
|
||||
CANON.write_text(text, encoding="utf-8")
|
||||
ASSET.write_text(text, encoding="utf-8")
|
||||
|
||||
# stamp dart
|
||||
dart_lines = DART.read_text(encoding="utf-8").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 = TAROT_TAG_RE.sub("", dm.group(2)).strip()
|
||||
tags = word_to_tags.get(word)
|
||||
if tags:
|
||||
prefix = " ".join(tags)
|
||||
dart_lines[j] = f"{dm.group(1)}{prefix} {body}{dm.group(3)}\n"
|
||||
else:
|
||||
dart_lines[j] = f"{dm.group(1)}{body}{dm.group(3)}\n"
|
||||
nstamp += 1
|
||||
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 m:
|
||||
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")
|
||||
dart_descs = dict(re.findall(r'word: "([^"]+)"[\s\S]*?description: "([^"]*)"', dart2))
|
||||
|
||||
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("tarot stations", len(tarot["stations"]))
|
||||
print("words tagged", len(word_to_tags), "dart desc lines", nstamp)
|
||||
for st in tarot["stations"]:
|
||||
print(
|
||||
f" {st['id']:20} group={st['group']!r:42} "
|
||||
f"cl={len(st['cluster'])} par={len(st['parallels'])} "
|
||||
f"prim={(st.get('primary') or {}).get('word')}"
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user