feat: complete academic symbol lexicon heal, 2-column Cooper extraction, and updated assets
This commit is contained in:
@@ -0,0 +1,450 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Rebuild alchemy atlas: real groups, metals, denser clusters, [Алхимия:] stamps."""
|
||||
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"
|
||||
YAML = ROOT / "oracle_enricher" / "data" / "sources" / "alchemy.yaml"
|
||||
|
||||
ALCHEMY_TAG_RE = re.compile(r"\[Алхимия:[^\]]*\]\s*")
|
||||
|
||||
DART_RESTORE = {
|
||||
"ладан": "серебро",
|
||||
"мирра": "ртуть",
|
||||
"старик": "медь",
|
||||
"парень": "железо",
|
||||
"девочка": "олово",
|
||||
"дома": "свинец",
|
||||
"маленький": "жёлтый",
|
||||
}
|
||||
|
||||
RESTORE_META = {
|
||||
"серебро": (
|
||||
"существительное",
|
||||
"Священный Предмет",
|
||||
"Чистый металл Луны, зеркало души и холодный проводник ночного света. "
|
||||
"Серебро отталкивает ложь, очищает темные воды и хранит память о первозданной чистоте. "
|
||||
"В раскладе говорит о главенстве интуиции, необходимости отразить реальность без искажений и довериться тонким предчувствиям.",
|
||||
),
|
||||
"ртуть": (
|
||||
"существительное",
|
||||
"Священный Предмет",
|
||||
"Жидкий металл алхимиков, живое серебро, неуловимый дух связи и трансмутации. "
|
||||
"Ртуть не удерживается пальцами — она скользит между состояниями, связывая плотное и тонкое. "
|
||||
"Знак указывает на высокую адаптивность, необходимость быстрой реакции и способность переплавить тупик в новое качество.",
|
||||
),
|
||||
"медь": (
|
||||
"существительное",
|
||||
"Священный Предмет",
|
||||
"Теплый металл Венеры, проводник сердечного огня, сплавов и созидательного ремесла. "
|
||||
"Медь придает устойчивость нежным формам, гармонизирует среду и резонирует с живой вибрацией. "
|
||||
"В раскладе знаменует союз, целебное тепло, раскрытие творческого дара и плодотворное сотрудничество.",
|
||||
),
|
||||
"железо": (
|
||||
"существительное",
|
||||
"Священный Предмет",
|
||||
"Кровный металл Марса, основа плуга, меча и несокрушимой брони. "
|
||||
"Железо куется только в сильном жаре и закаляется в воде, воплощая несгибаемую стойкость и тяжелый труд. "
|
||||
"Знак требует твердости характера, практического упорства и готовности защищать рубежи своего мира.",
|
||||
),
|
||||
"олово": (
|
||||
"существительное",
|
||||
"Священный Предмет",
|
||||
"Звонкий металл Юпитера, податливый для пайки, соединяющий разрозненные элементы в единое целое. "
|
||||
"Олово воплощает верность слову, мягкую силу благородства и способность соединить то, что казалось несовместимым. "
|
||||
"В раскладе указывает на примирение, надежный союз и доброе посредничество.",
|
||||
),
|
||||
"свинец": (
|
||||
"существительное",
|
||||
"Священный Предмет",
|
||||
"Тяжелая первоматерия Сатурна, темная руда предела, экранирующая любые лучи. "
|
||||
"Свинец символизирует грубое бремя материи, непроницаемость и точку наибольшего сгущения духа. "
|
||||
"Знак указывает на тяжесть долга, кармический груз или замедление; именно в свинце сокрыто зерно будущего золота.",
|
||||
),
|
||||
"жёлтый": (
|
||||
"прилагательное",
|
||||
"Свойство / Качество",
|
||||
"Цвет полуденного солнца, золота интеллекта, проницательности и озарения. "
|
||||
"Жёлтый рассеивает туман неопределенности, высвечивая логику вещей и даруя ясность взгляда. "
|
||||
"В раскладе указывает на необходимость ясного понимания, ученичества, передачи знания и трезвого расчета.",
|
||||
),
|
||||
}
|
||||
|
||||
# id, name, symbol, group, lore, primary, cluster
|
||||
STATIONS = [
|
||||
(
|
||||
"nigredo",
|
||||
"Нигредо (Чернота)",
|
||||
"⬛ 💀",
|
||||
"Magnum Opus · Стадия 1",
|
||||
"Чёрная работа: гниение старой формы, смерть эго, погружение в первоматерию. "
|
||||
"Пока тело Делания не станет пеплом, золото не начнётся.",
|
||||
"смерть",
|
||||
["тьма", "пепел", "черный", "хаос", "ворон"],
|
||||
),
|
||||
(
|
||||
"albedo",
|
||||
"Альбедо (Белизна)",
|
||||
"⬜ 🕊️",
|
||||
"Magnum Opus · Стадия 2",
|
||||
"Лунное омовение: смывание шлака, серебряный свет, кристальная чистота. "
|
||||
"После черноты душа промывается до белизны, в которой уже можно различить форму.",
|
||||
"белый",
|
||||
["чистота", "луна", "кристалл", "роса"],
|
||||
),
|
||||
(
|
||||
"citrinitas",
|
||||
"Цитринитас (Желтизна)",
|
||||
"🟨 🌅",
|
||||
"Magnum Opus · Стадия 3",
|
||||
"Солнечный рассвет Делания: свинец желтеет, разум просыпается, золото ещё не красное, но уже светит. "
|
||||
"Стадия озарения, ученичества и созревания света.",
|
||||
"рассвет",
|
||||
["свет", "солнце", "золото", "жёлтый"],
|
||||
),
|
||||
(
|
||||
"rubedo",
|
||||
"Рубедо (Краснота)",
|
||||
"🟥 👑",
|
||||
"Magnum Opus · Стадия 4",
|
||||
"Венец Великого Делания: Философский Камень, Феникс из пепла, соединение духа и плоти. "
|
||||
"Красное золото — не металл витрины, а целостность, прошедшая смерть.",
|
||||
"красный",
|
||||
["камень", "феникс", "корона", "золотой"],
|
||||
),
|
||||
(
|
||||
"fire",
|
||||
"Огонь (Ignis)",
|
||||
"🔥 ⚡",
|
||||
"Стихии · Огонь",
|
||||
"Сухой активный жар Делания, мужской принцип, очищение и горение. "
|
||||
"Без огня нет ни гниения, ни камня — только сырая материя.",
|
||||
"огонь",
|
||||
["пламя", "искра", "жар"],
|
||||
),
|
||||
(
|
||||
"water",
|
||||
"Вода (Aqua)",
|
||||
"💧 🌊",
|
||||
"Стихии · Вода",
|
||||
"Универсальный растворитель (Alkahest), влажный женский принцип, колыбель тайн. "
|
||||
"Вода растворяет форму, чтобы её можно было собрать заново.",
|
||||
"вода",
|
||||
["волна", "роса", "луна"],
|
||||
),
|
||||
(
|
||||
"air",
|
||||
"Воздух (Aer)",
|
||||
"💨 🕊️",
|
||||
"Стихии · Воздух",
|
||||
"Летучий медиатор между небом и землёй, дыхание духа, связующая среда мысли. "
|
||||
"Воздух носит ртуть: без него металлы не сообщаются.",
|
||||
"воздух",
|
||||
["ветер", "дыхание"],
|
||||
),
|
||||
(
|
||||
"earth",
|
||||
"Земля (Terra)",
|
||||
"🌍 🪨",
|
||||
"Стихии · Земля",
|
||||
"Плотная опора воплощения, сосуд созревания, соль формы. "
|
||||
"Земля держит то, что огонь, вода и воздух только обещают.",
|
||||
"земля",
|
||||
["камень", "кристалл", "соль"],
|
||||
),
|
||||
(
|
||||
"gold",
|
||||
"Золото (Sol)",
|
||||
"🪙 ☀️",
|
||||
"Металлы · Солнце",
|
||||
"Солнечный металл совершенства, нетленный свет, цель Делания. "
|
||||
"Золото не ржавеет: знак высшей ценности и кристальной чистоты намерения.",
|
||||
"золото",
|
||||
["солнце", "корона"],
|
||||
),
|
||||
(
|
||||
"silver",
|
||||
"Серебро (Luna)",
|
||||
"🥈 🌙",
|
||||
"Металлы · Луна",
|
||||
"Лунный металл души, зеркало без лжи, проводник ночного света. "
|
||||
"Серебро — белизна Альбедо, отражение, ещё не ставшее золотом.",
|
||||
"серебро",
|
||||
["луна", "чистота"],
|
||||
),
|
||||
(
|
||||
"mercury",
|
||||
"Ртуть (Mercurius)",
|
||||
"💧 ⚗️",
|
||||
"Металлы · Меркурий",
|
||||
"Живое серебро, дух связи и трансмутации, одно из Трёх Начал. "
|
||||
"Ртуть скользит между состояниями и связывает серу с солью, дух с телом.",
|
||||
"ртуть",
|
||||
["сосуд", "воздух"],
|
||||
),
|
||||
(
|
||||
"copper",
|
||||
"Медь (Venus)",
|
||||
"🥉 ♀️",
|
||||
"Металлы · Венера",
|
||||
"Тёплый металл Венеры, проводник сердечного огня и сплавов. "
|
||||
"Медь гармонизирует и соединяет то, что железо только режет.",
|
||||
"медь",
|
||||
["сердце", "союз"],
|
||||
),
|
||||
(
|
||||
"iron",
|
||||
"Железо (Mars)",
|
||||
"⚙️ ⚔️",
|
||||
"Металлы · Марс",
|
||||
"Кровный металл Марса, плуг и меч, стойкость, выкованная в жаре. "
|
||||
"Железо требует удара и закалки — без них это только руда.",
|
||||
"железо",
|
||||
["меч", "воин", "кузница"],
|
||||
),
|
||||
(
|
||||
"tin",
|
||||
"Олово (Jupiter)",
|
||||
"🪞 ⚡",
|
||||
"Металлы · Юпитер",
|
||||
"Звонкий металл Юпитера, мягкая пайка разрозненного в целое. "
|
||||
"Олово не куёт империй — оно держит швы мира.",
|
||||
"олово",
|
||||
["закон", "дар"],
|
||||
),
|
||||
(
|
||||
"lead",
|
||||
"Свинец (Saturnus)",
|
||||
"🧱 ⏳",
|
||||
"Металлы · Сатурн",
|
||||
"Первоматерия Сатурна, тяжёлая руда предела, зерно будущего золота. "
|
||||
"Свинец — Нигредо металлов: бремя, из которого Делание начинает путь.",
|
||||
"свинец",
|
||||
["камень", "смерть", "холод"],
|
||||
),
|
||||
(
|
||||
"vessel",
|
||||
"Сосуд (Vas Hermeticum)",
|
||||
"🏺 🫙",
|
||||
"Орудия · Реторта",
|
||||
"Герметически закрытое пространство преображения, тигель души. "
|
||||
"Пока сосуд открыт, летучее уходит и Делание срывается.",
|
||||
"сосуд",
|
||||
["кристалл", "яйцо"],
|
||||
),
|
||||
(
|
||||
"athanor",
|
||||
"Атанор (Athanor)",
|
||||
"🔥 🏺",
|
||||
"Орудия · Печь",
|
||||
"Печь непрерывного Делания: ровный священный жар, который не вспыхивает и не гаснет. "
|
||||
"Атанор — терпение огня, кузница, где время само является топливом.",
|
||||
"кузница",
|
||||
["огонь", "жар", "железо"],
|
||||
),
|
||||
]
|
||||
|
||||
|
||||
def restore_dart(text: str) -> str:
|
||||
for old, new in DART_RESTORE.items():
|
||||
pos, cat, desc = RESTORE_META[new]
|
||||
desc_esc = desc.replace("\\", "\\\\").replace('"', '\\"')
|
||||
|
||||
def repl(m: re.Match, new=new, pos=pos, cat=cat, desc_esc=desc_esc) -> str:
|
||||
return (
|
||||
f"{m.group(1)}{new}{m.group(2)}"
|
||||
f"{m.group(3)}{pos}{m.group(4)}"
|
||||
f"{m.group(5)}{cat}{m.group(6)}"
|
||||
f"{m.group(7)}{desc_esc}{m.group(8)}"
|
||||
)
|
||||
|
||||
pat = re.compile(
|
||||
rf'(word: "){re.escape(old)}(",\s*\n)'
|
||||
rf'(\s*partOfSpeech: ")[^"]+(",\s*\n)'
|
||||
rf'(\s*frequency: [^\n]+\n\s*category: ")[^"]+(",\s*\n)'
|
||||
rf'(\s*description: ")[^"]*(")',
|
||||
)
|
||||
if f'word: "{new}",' in text and f'word: "{old}",' not in text:
|
||||
continue
|
||||
text2, n = pat.subn(repl, text, count=1)
|
||||
if n != 1:
|
||||
raise SystemExit(f"failed to restore {old} -> {new} (n={n})")
|
||||
text = text2
|
||||
return text
|
||||
|
||||
|
||||
def payload(word: str, dart_by_word: dict, lex_by_word: dict) -> dict:
|
||||
lex = lex_by_word.get(word) or {}
|
||||
return {
|
||||
"id": dart_by_word[word],
|
||||
"word": word,
|
||||
"emoji": lex.get("emoji") or "",
|
||||
"pos": lex.get("pos") or "существительное",
|
||||
"category": lex.get("category") or "",
|
||||
"desc": "",
|
||||
}
|
||||
|
||||
|
||||
def tag_for(name: str) -> str:
|
||||
return f"[Алхимия: {name}]"
|
||||
|
||||
|
||||
def stamp_html_md(canon: dict, dart2: str) -> None:
|
||||
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))
|
||||
dart_words = set(dart_descs)
|
||||
for w in words:
|
||||
wd = w.get("word")
|
||||
if wd in dart_descs:
|
||||
w["description"] = dart_descs[wd]
|
||||
# HTML still had lexicon names for restored cells; keep them, descriptions already match.
|
||||
# Also rename HTML words that dart restored (should already be metals in HTML).
|
||||
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")
|
||||
_ = dart_words
|
||||
|
||||
|
||||
def main() -> None:
|
||||
dart_text = restore_dart(DART.read_text(encoding="utf-8"))
|
||||
DART.write_text(dart_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"))
|
||||
|
||||
word_to_tags: dict[str, list[str]] = defaultdict(list)
|
||||
stations = []
|
||||
missing = []
|
||||
for sid, name, symbol, group, lore, prim, extra in STATIONS:
|
||||
if prim not in dart_by_word:
|
||||
missing.append(f"primary {sid}/{prim}")
|
||||
continue
|
||||
cluster = []
|
||||
seen = {prim}
|
||||
for w in extra:
|
||||
if w in seen or w not in dart_by_word:
|
||||
if w not in dart_by_word:
|
||||
missing.append(f"cluster {sid}/{w}")
|
||||
continue
|
||||
seen.add(w)
|
||||
cluster.append(payload(w, dart_by_word, lex_by_word))
|
||||
st = {
|
||||
"id": sid,
|
||||
"name": name,
|
||||
"symbol": symbol,
|
||||
"group": group,
|
||||
"lore": lore,
|
||||
"primary": payload(prim, dart_by_word, lex_by_word),
|
||||
"cluster": cluster,
|
||||
}
|
||||
stations.append(st)
|
||||
tag = tag_for(name)
|
||||
word_to_tags[prim].append(tag)
|
||||
for c in cluster:
|
||||
word_to_tags[c["word"]].append(tag)
|
||||
|
||||
if missing:
|
||||
print("WARN missing", missing)
|
||||
|
||||
al = canon["alchemy"]
|
||||
al["name"] = "Алхимия (Magnum Opus)"
|
||||
al["count"] = len(stations)
|
||||
al["description"] = (
|
||||
"Великое Делание: 4 стадии (Нигредо, Альбедо, Цитринитас, Рубедо), "
|
||||
"4 стихии, 7 металлов планетного септенера и орудия сосуда и атанора."
|
||||
)
|
||||
al["stations"] = 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 = ALCHEMY_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")
|
||||
|
||||
stamp_html_md(canon, dart2)
|
||||
|
||||
print(f"alchemy stations {len(stations)} stamped {nstamp} tagged {len(word_to_tags)}")
|
||||
groups: dict[str, list[str]] = defaultdict(list)
|
||||
for st in stations:
|
||||
g = st["group"].split(" · ", 1)[0]
|
||||
cl = [c["word"] for c in st["cluster"]]
|
||||
groups[g].append(st["id"])
|
||||
print(f" {st['id']:12} {st['group']:24} prim={st['primary']['word']:12} {cl}")
|
||||
print("groups", {k: len(v) for k, v in groups.items()})
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user