feat: complete academic symbol lexicon heal, 2-column Cooper extraction, and updated assets
This commit is contained in:
@@ -0,0 +1,579 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""Repair oracle canon inside the closed thousand.
|
||||
|
||||
Swaps sacred names onto junk slots, retargets planet chips onto metals,
|
||||
resyncs station descriptions, rewrites stock dossiers, and re-embeds the
|
||||
HTML atlas so geomancy symbols stay valid JavaScript.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import re
|
||||
import shutil
|
||||
from copy import deepcopy
|
||||
from pathlib import Path
|
||||
|
||||
from dossier_laws import LAWS, NIGHTS
|
||||
|
||||
ROOT = Path(__file__).resolve().parent.parent
|
||||
DART = ROOT / "lib" / "data" / "words_data.dart"
|
||||
LEX = ROOT / "oracle_enricher" / "data" / "lexicon.json"
|
||||
KB_ASSETS = ROOT / "assets" / "data" / "oracle_kb.json"
|
||||
KB_OUT = ROOT / "oracle_enricher" / "out" / "oracle_kb.json"
|
||||
CANON_A = ROOT / "assets" / "data" / "traditions_canon.json"
|
||||
CANON_B = ROOT / "oracle_enricher" / "data" / "traditions_canon.json"
|
||||
HTML = ROOT / "lexicon_viewer.html"
|
||||
ALPHA_MD = ROOT / "words_alphabetical.md"
|
||||
ALPHA_TXT = ROOT / "words_alphabetical.txt"
|
||||
|
||||
STOCK_CORE = (
|
||||
"сакральный символ",
|
||||
"архетипический образ в поле судьбы",
|
||||
"ключевую сферу кристаллизации",
|
||||
"вектор трансформации",
|
||||
"модус судьбы",
|
||||
"активный процесс духовной динамики",
|
||||
)
|
||||
STOCK_SHADOW = ("искажение первообраза", "суета, поспешность")
|
||||
STOCK_ASK = ("готов ли ты принять этот урок", "созерцать символ")
|
||||
|
||||
# Sacred word replaces this living junk word. One death per restore.
|
||||
SWAPS = [
|
||||
("умеренность", "начаться", "⚖️", "Образ / Архетип"),
|
||||
("росток", "рассказать", "🌱", "Образ / Архетип"),
|
||||
("ученик", "спросить", "📖", "Образ / Архетип"),
|
||||
("терпение", "принести", "🪨", "Образ / Архетип"),
|
||||
("войско", "помочь", "⚔️", "Образ / Архетип"),
|
||||
("уныние", "пройти", "🌧️", "Образ / Архетип"),
|
||||
("созерцание", "назвать", "👁️", "Образ / Архетип"),
|
||||
("опасность", "связать", "⚠️", "Образ / Архетип"),
|
||||
("свежесть", "написать", "💧", "Образ / Архетип"),
|
||||
("осина", "более", "🌳", "Образ / Архетип"),
|
||||
("дрок", "пол", "🌼", "Образ / Архетип"),
|
||||
("лоза", "мужик", "🍇", "Образ / Архетип"),
|
||||
("бузина", "ребята", "🌿", "Образ / Архетип"),
|
||||
("императрица", "вновь", "👑", "Образ / Архетип"),
|
||||
("император", "наверное", "👑", "Образ / Архетип"),
|
||||
("влюбленные", "опять", "💕", "Образ / Архетип"),
|
||||
("колесница", "постоянно", "🛞", "Священный Предмет"),
|
||||
("дьявол", "тоже", "😈", "Образ / Архетип"),
|
||||
("космос", "чуть", "🌌", "Космический Знак"),
|
||||
("рябина", "зачем", "🌳", "Образ / Архетип"),
|
||||
("ольха", "тут", "🌳", "Образ / Архетип"),
|
||||
("ясень", "бывать", "🌳", "Образ / Архетип"),
|
||||
("боярышник", "возникать", "🌿", "Образ / Архетип"),
|
||||
("остролист", "значить", "🌿", "Образ / Архетип"),
|
||||
("орешник", "казаться", "🌰", "Образ / Архетип"),
|
||||
("яблоня", "находиться", "🍎", "Образ / Архетип"),
|
||||
("тростник", "прийтись", "🌾", "Образ / Архетип"),
|
||||
("сосна", "хотеться", "🌲", "Образ / Архетип"),
|
||||
("вереск", "показаться", "🌸", "Образ / Архетип"),
|
||||
("тис", "выглядеть", "🌲", "Образ / Архетип"),
|
||||
]
|
||||
|
||||
ALIAS = {
|
||||
"марс": "железо",
|
||||
"меркурий": "ртуть",
|
||||
"юпитер": "олово",
|
||||
"венера": "медь",
|
||||
"сатурн": "свинец",
|
||||
"оранжевый": "золотой",
|
||||
}
|
||||
|
||||
TAG_OF = {
|
||||
"runes": "Руны",
|
||||
"ogham": "Огам",
|
||||
"kabbalah": "Каббала",
|
||||
"alchemy": "Алхимия",
|
||||
"tarot": "Таро",
|
||||
"lenormand": "Ленорман",
|
||||
"iching": "И Цзин",
|
||||
"geomancy": "Геомантия",
|
||||
"western_zodiac": "Додекада Созвездий",
|
||||
"zodiac": "Китайский гороскоп",
|
||||
"cosmology": "Космология",
|
||||
"days_of_week": "Септенер",
|
||||
"months_of_year": "Додекада",
|
||||
}
|
||||
|
||||
COSMO_LABEL = {
|
||||
"upper_world": "Верхний Мир",
|
||||
"middle_world": "Срединный Мир",
|
||||
"lower_world": "Нижний Мир",
|
||||
"axis_mundi": "Мировая Ось",
|
||||
}
|
||||
|
||||
DAY_SCRIPT = {
|
||||
"monday": ("יום שני", "月曜", "الإثنين", "йом шени · юэяо · аль-иснайн"),
|
||||
"tuesday": ("יום שלישי", "火曜", "الثلاثاء", "йом шлиши · хояо · ас-суляса"),
|
||||
"wednesday": ("יום רביעי", "水曜", "الأربعاء", "йом ревии · шуйяо · аль-арбиа"),
|
||||
"thursday": ("יום חמישי", "木曜", "الخميس", "йом хамиши · муяо · аль-хамис"),
|
||||
"friday": ("יום שישי", "金曜", "الجمعة", "йом шиши · цзиньяо · аль-джумуа"),
|
||||
"saturday": ("שבת", "土曜", "السبت", "шаббат · туяо · ас-сабт"),
|
||||
"sunday": ("יום ראשון", "日曜", "الأحد", "йом ришон · жияо · аль-ахад"),
|
||||
}
|
||||
|
||||
MONTH_SCRIPT = {
|
||||
"january": ("ינואר", "一月", "كانون الثاني", "януар · и-юэ · канун ас-сани"),
|
||||
"february": ("פברואר", "二月", "شباط", "февруар · эр-юэ · шубат"),
|
||||
"march": ("מרץ", "三月", "آذار", "мерц · сань-юэ · азар"),
|
||||
"april": ("אפריל", "四月", "نيسان", "апрель · сы-юэ · нисан"),
|
||||
"may": ("מאי", "五月", "أيار", "май · у-юэ · айяр"),
|
||||
"june": ("יוני", "六月", "حزيران", "юни · лю-юэ · хузейран"),
|
||||
"july": ("יולי", "七月", "تموز", "юли · ци-юэ · таммуз"),
|
||||
"august": ("אוגוסט", "八月", "آب", "огуст · ба-юэ · аб"),
|
||||
"september": ("ספטמבר", "九月", "أيلول", "септембер · цзю-юэ · айлюль"),
|
||||
"october": ("אוקטובר", "十月", "تشرين الأول", "октобер · ши-юэ · тишрин аль-авваль"),
|
||||
"november": ("נובמבר", "十一月", "تشرين الثاني", "новембер · шии-юэ · тишрин ас-сани"),
|
||||
"december": ("דצמבר", "十二月", "كانون الأول", "децембер · шиэр-юэ · канун аль-авваль"),
|
||||
}
|
||||
|
||||
|
||||
def dart_unescape(s: str) -> str:
|
||||
out = []
|
||||
i = 0
|
||||
while i < len(s):
|
||||
if s[i] == "\\" and i + 1 < len(s):
|
||||
n = s[i + 1]
|
||||
out.append({"n": "\n", "t": "\t", '"': '"', "\\": "\\", "$": "$"}.get(n, n))
|
||||
i += 2
|
||||
else:
|
||||
out.append(s[i])
|
||||
i += 1
|
||||
return "".join(out)
|
||||
|
||||
|
||||
def dart_escape(s: str) -> str:
|
||||
return (
|
||||
s.replace("\\", "\\\\")
|
||||
.replace('"', '\\"')
|
||||
.replace("$", "\\$")
|
||||
.replace("\n", " ")
|
||||
.replace("\r", " ")
|
||||
)
|
||||
|
||||
|
||||
def grab(block: str, key: str) -> str:
|
||||
m = re.search(rf"{key}:\s*\"((?:\\.|[^\"\\])*)\"", block)
|
||||
if not m:
|
||||
raise SystemExit(f"no {key} in block")
|
||||
return dart_unescape(m.group(1))
|
||||
|
||||
|
||||
def low_has(text: str, needles) -> bool:
|
||||
t = (text or "").lower()
|
||||
return any(n in t for n in needles)
|
||||
|
||||
|
||||
def split_tags(desc: str):
|
||||
tags = re.findall(r"\[[^\]]+\]", desc)
|
||||
rest = re.sub(r"\[[^\]]+\]", " ", desc)
|
||||
rest = re.sub(r"\s+", " ", rest).strip()
|
||||
return tags, rest
|
||||
|
||||
|
||||
def join_desc(tags, rest: str) -> str:
|
||||
body = rest.strip()
|
||||
if tags and body:
|
||||
return " ".join(tags) + " " + body
|
||||
if tags:
|
||||
return " ".join(tags)
|
||||
return body
|
||||
|
||||
|
||||
def ensure_tag(desc: str, tag: str) -> str:
|
||||
if tag in desc:
|
||||
return desc
|
||||
return (tag + " " + desc).strip()
|
||||
|
||||
|
||||
def clean_tags(desc: str) -> str:
|
||||
desc = desc.replace("[Планетарный Септенер:", "[Септенер:")
|
||||
desc = desc.replace("[Зодиак:", "[Додекада Созвездий:")
|
||||
# Нумерология и Матрица — живые теги традиции numerology. Не разворачивать в прозу.
|
||||
return re.sub(r"\s+", " ", desc).strip()
|
||||
|
||||
|
||||
def load_dart():
|
||||
text = DART.read_text(encoding="utf-8")
|
||||
blocks = re.findall(r" WordItem\(\n(?:.*\n)*? \),", text)
|
||||
if len(blocks) != 1000:
|
||||
raise SystemExit(f"expected 1000 WordItems, got {len(blocks)}")
|
||||
items = []
|
||||
for b in blocks:
|
||||
freq = re.search(r"frequency:\s*([0-9.]+)", b).group(1)
|
||||
items.append(
|
||||
{
|
||||
"id": int(re.search(r"id:\s*(\d+)", b).group(1)),
|
||||
"word": grab(b, "word"),
|
||||
"pos": grab(b, "partOfSpeech"),
|
||||
"freq": freq,
|
||||
"cat": grab(b, "category"),
|
||||
"desc": grab(b, "description"),
|
||||
}
|
||||
)
|
||||
items.sort(key=lambda x: x["id"])
|
||||
return text, items
|
||||
|
||||
|
||||
def write_dart(text: str, items):
|
||||
chunks = []
|
||||
for it in items:
|
||||
chunks.append(
|
||||
" WordItem(\n"
|
||||
f" id: {it['id']},\n"
|
||||
f" word: \"{dart_escape(it['word'])}\",\n"
|
||||
f" partOfSpeech: \"{dart_escape(it['pos'])}\",\n"
|
||||
f" frequency: {it['freq']},\n"
|
||||
f" category: \"{dart_escape(it['cat'])}\",\n"
|
||||
f" description: \"{dart_escape(it['desc'])}\",\n"
|
||||
" ),"
|
||||
)
|
||||
new_body = "\n".join(chunks)
|
||||
new_text, n = re.subn(
|
||||
r" WordItem\(\n(?:.*\n)*? \),",
|
||||
lambda m, repl=iter(chunks): next(repl),
|
||||
text,
|
||||
count=1000,
|
||||
)
|
||||
if n != 1000:
|
||||
raise SystemExit(f"rewrote {n} blocks")
|
||||
# The lambda above consumes chunks via closure; verify count of WordItem.
|
||||
if new_text.count("WordItem(") != 1000:
|
||||
raise SystemExit("dart word count drifted")
|
||||
DART.write_text(new_text, encoding="utf-8")
|
||||
|
||||
|
||||
def apply_swaps(items, lexicon, kb):
|
||||
by_word = {it["word"]: it for it in items}
|
||||
deaths = []
|
||||
for new, death, emoji, cat in SWAPS:
|
||||
if new in by_word and death not in by_word:
|
||||
continue
|
||||
if new in by_word and death in by_word:
|
||||
raise SystemExit(f"both live: {new} and {death}")
|
||||
if death not in by_word:
|
||||
raise SystemExit(f"no slot for {new}, missing {death}")
|
||||
it = by_word.pop(death)
|
||||
it["word"] = new
|
||||
it["pos"] = "существительное"
|
||||
it["cat"] = cat
|
||||
by_word[new] = it
|
||||
row = next(r for r in lexicon if r["id"] == it["id"])
|
||||
row["word"] = new
|
||||
row["emoji"] = emoji
|
||||
row["pos"] = "существительное"
|
||||
row["category"] = cat
|
||||
old = kb.pop(death, None) or {}
|
||||
kb.pop(str(it["id"]), None)
|
||||
core, shadow, ask, step = LAWS[new]
|
||||
entry = deepcopy(old) if isinstance(old, dict) else {}
|
||||
entry.update(
|
||||
{
|
||||
"id": it["id"],
|
||||
"word": new,
|
||||
"emoji": emoji,
|
||||
"pos": "существительное",
|
||||
"category": cat,
|
||||
"operator": "substance",
|
||||
"valence": entry.get("valence") or "liminal",
|
||||
"has_canon": True,
|
||||
"user_edited": False,
|
||||
"core": core,
|
||||
"oracle": f"{core} {shadow}",
|
||||
"shadow": shadow,
|
||||
"asks": ask,
|
||||
"step": step,
|
||||
"grammar": {
|
||||
"modifies": "подставляет соседнему знаку тело образа",
|
||||
"operator": "substance",
|
||||
"valence": "liminal",
|
||||
},
|
||||
}
|
||||
)
|
||||
kb[new] = entry
|
||||
deaths.append(f"{death} → {new} (#{it['id']:03d})")
|
||||
return deaths
|
||||
|
||||
|
||||
def apply_laws(kb, items):
|
||||
by_word = {it["word"]: it for it in items}
|
||||
for word, (core, shadow, ask, step) in LAWS.items():
|
||||
if word not in by_word:
|
||||
continue
|
||||
entry = kb.get(word)
|
||||
if not isinstance(entry, dict):
|
||||
entry = {"id": by_word[word]["id"], "word": word}
|
||||
kb[word] = entry
|
||||
entry["word"] = word
|
||||
entry["id"] = by_word[word]["id"]
|
||||
entry["core"] = core
|
||||
entry["shadow"] = shadow
|
||||
entry["asks"] = ask
|
||||
entry["step"] = step
|
||||
entry["oracle"] = f"{core} {shadow}"
|
||||
entry["has_canon"] = True
|
||||
for word, (shadow, ask, step) in NIGHTS.items():
|
||||
if word not in by_word or word in LAWS:
|
||||
continue
|
||||
entry = kb.get(word)
|
||||
if not isinstance(entry, dict):
|
||||
continue
|
||||
if low_has(entry.get("shadow", ""), STOCK_SHADOW):
|
||||
entry["shadow"] = shadow
|
||||
if low_has(entry.get("asks", ""), STOCK_ASK) or low_has(entry.get("step", ""), STOCK_ASK):
|
||||
entry["asks"] = ask
|
||||
entry["step"] = step
|
||||
|
||||
|
||||
def rewrite_descriptions(items, kb):
|
||||
cores = {}
|
||||
for word, entry in kb.items():
|
||||
if isinstance(entry, dict) and not str(word).isdigit():
|
||||
cores[entry.get("word") or word] = entry.get("core") or ""
|
||||
for it in items:
|
||||
desc = clean_tags(it["desc"])
|
||||
tags, rest = split_tags(desc)
|
||||
if low_has(rest, STOCK_CORE) or low_has(rest, ("кристаллизации вашего опыта",)):
|
||||
if it["word"] in LAWS:
|
||||
rest = LAWS[it["word"]][0]
|
||||
else:
|
||||
core = cores.get(it["word"], "")
|
||||
if core and not low_has(core, STOCK_CORE):
|
||||
rest = core
|
||||
it["desc"] = join_desc(tags, rest)
|
||||
|
||||
|
||||
def stamp_from_canon(items, canon):
|
||||
by_word = {it["word"]: it for it in items}
|
||||
for tid, trad in canon.items():
|
||||
label = TAG_OF.get(tid)
|
||||
if not label:
|
||||
continue
|
||||
for st in trad.get("stations") or []:
|
||||
nodes = []
|
||||
if st.get("primary"):
|
||||
nodes.append(st["primary"])
|
||||
nodes.extend(st.get("cluster") or [])
|
||||
for node in nodes:
|
||||
word = ALIAS.get(node.get("word"), node.get("word"))
|
||||
if word == "начать" or word not in by_word:
|
||||
continue
|
||||
# Only force a fresh station tag onto restored words and cosmology.
|
||||
if tid == "cosmology":
|
||||
short = COSMO_LABEL.get(st.get("id"), st.get("name", "Космология"))
|
||||
tag = f"[Космология: {short}]"
|
||||
by_word[word]["desc"] = ensure_tag(by_word[word]["desc"], tag)
|
||||
elif word in LAWS and word not in {it["word"] for it in items if False}:
|
||||
pass
|
||||
restored = {new for new, *_ in SWAPS}
|
||||
for tid, trad in canon.items():
|
||||
label = TAG_OF.get(tid)
|
||||
if not label:
|
||||
continue
|
||||
for st in trad.get("stations") or []:
|
||||
words = []
|
||||
if st.get("primary"):
|
||||
words.append(st["primary"].get("word"))
|
||||
words.extend(c.get("word") for c in st.get("cluster") or [])
|
||||
for raw in words:
|
||||
word = ALIAS.get(raw, raw)
|
||||
if word not in restored or word not in by_word:
|
||||
continue
|
||||
tag = f"[{label}: {st.get('name', st.get('id', ''))}]"
|
||||
by_word[word]["desc"] = ensure_tag(by_word[word]["desc"], tag)
|
||||
|
||||
|
||||
def attach_scripts(station, pack):
|
||||
hebrew, hanzi, arabic, say = pack
|
||||
station["hebrew"] = hebrew
|
||||
station["hanzi"] = hanzi
|
||||
station["arabic"] = arabic
|
||||
group = station.get("group") or ""
|
||||
if say not in group:
|
||||
station["group"] = f"{group} · {say}" if group else say
|
||||
|
||||
|
||||
def repair_canon(canon, items, lex_by_id):
|
||||
kab = canon["kabbalah"]["stations"]
|
||||
for st in kab:
|
||||
if st["id"] == "kether" and " · " not in st.get("group", ""):
|
||||
st["group"] = "Высшая триада · Ацилут"
|
||||
for st in canon["days_of_week"]["stations"]:
|
||||
if st["id"] in DAY_SCRIPT:
|
||||
attach_scripts(st, DAY_SCRIPT[st["id"]])
|
||||
for st in canon["months_of_year"]["stations"]:
|
||||
if st["id"] in MONTH_SCRIPT:
|
||||
attach_scripts(st, MONTH_SCRIPT[st["id"]])
|
||||
|
||||
by_word = {it["word"]: it for it in items}
|
||||
missing = []
|
||||
for trad in canon.values():
|
||||
for st in trad.get("stations") or []:
|
||||
if st.get("primary"):
|
||||
_retarget(st["primary"], by_word, lex_by_id, missing, st["id"])
|
||||
cluster = []
|
||||
for node in st.get("cluster") or []:
|
||||
if node.get("word") == "начать":
|
||||
continue
|
||||
_retarget(node, by_word, lex_by_id, missing, st["id"])
|
||||
cluster.append(node)
|
||||
st["cluster"] = cluster
|
||||
if missing:
|
||||
raise SystemExit("canon words still missing: " + ", ".join(missing[:30]))
|
||||
|
||||
|
||||
def _retarget(node, by_word, lex_by_id, missing, station_id):
|
||||
word = node.get("word")
|
||||
word = ALIAS.get(word, word)
|
||||
if word not in by_word:
|
||||
missing.append(f"{station_id}:{word}")
|
||||
return
|
||||
it = by_word[word]
|
||||
lex = lex_by_id[it["id"]]
|
||||
node["word"] = word
|
||||
node["id"] = it["id"]
|
||||
node["emoji"] = lex.get("emoji") or node.get("emoji") or ""
|
||||
node["pos"] = it["pos"]
|
||||
node["category"] = it["cat"]
|
||||
node["desc"] = it["desc"]
|
||||
|
||||
|
||||
def mirror_kb(kb, items):
|
||||
by_word = {it["word"]: it for it in items}
|
||||
out = {}
|
||||
for it in items:
|
||||
entry = kb.get(it["word"])
|
||||
if not isinstance(entry, dict):
|
||||
raise SystemExit(f"kb missing {it['word']}")
|
||||
entry["id"] = it["id"]
|
||||
entry["word"] = it["word"]
|
||||
entry["pos"] = it["pos"]
|
||||
entry["category"] = it["cat"]
|
||||
entry["description"] = it["desc"]
|
||||
out[it["word"]] = entry
|
||||
out[str(it["id"])] = deepcopy(entry)
|
||||
return out
|
||||
|
||||
|
||||
def js_json(obj, indent=2):
|
||||
raw = json.dumps(obj, ensure_ascii=False, indent=indent)
|
||||
return raw.replace("</", "<\\/").replace("\u2028", "\\u2028").replace("\u2029", "\\u2029")
|
||||
|
||||
|
||||
def embed_html(canon, words_data):
|
||||
html = HTML.read_text(encoding="utf-8")
|
||||
start = html.find("const TRADITIONS_CANON = ")
|
||||
end = html.find("\n const CANONICAL_SYSTEMS")
|
||||
if start < 0 or end < 0 or end < start:
|
||||
raise SystemExit("html canon markers not found")
|
||||
block = (
|
||||
"const TRADITIONS_CANON = "
|
||||
+ js_json(canon, 2)
|
||||
+ ";\n\n const WORDS_DATA = "
|
||||
+ js_json(words_data, None)
|
||||
+ ";"
|
||||
)
|
||||
html = html[:start] + block + html[end:]
|
||||
html = html.replace("10 великих систем", "${trads.length} традиций")
|
||||
HTML.write_text(html, encoding="utf-8")
|
||||
|
||||
|
||||
def write_alpha(lexicon):
|
||||
rows = sorted(lexicon, key=lambda w: w["word"].lower().replace("ё", "е9"))
|
||||
lines = ["# Алфавитный список 1000 слов Оракула", "", "| № | Слово | Эмодзи | ID | Часть речи | Категория |", "|---|---|---|---|---|---|"]
|
||||
txt = []
|
||||
for i, w in enumerate(rows, 1):
|
||||
lines.append(
|
||||
f"| {i} | **{w['word']}** | {w.get('emoji','')} | #{w['id']:03d} | {w.get('pos','')} | {w.get('category','')} |"
|
||||
)
|
||||
txt.append(f"{i}. {w['word']} {w.get('emoji','')} (id: {w['id']})")
|
||||
ALPHA_MD.write_text("\n".join(lines) + "\n", encoding="utf-8")
|
||||
ALPHA_TXT.write_text("\n".join(txt) + "\n", encoding="utf-8")
|
||||
|
||||
|
||||
def assert_clean(items, kb, canon):
|
||||
words = [it["word"] for it in items]
|
||||
if len(words) != 1000 or len(set(words)) != 1000:
|
||||
raise SystemExit("lexicon is not 1000 unique words")
|
||||
if [it["id"] for it in items] != list(range(1000)):
|
||||
raise SystemExit("ids are not 0..999")
|
||||
by_word = set(words)
|
||||
ghosts = []
|
||||
for tid, trad in canon.items():
|
||||
for st in trad["stations"]:
|
||||
nodes = [st["primary"]] if st.get("primary") else []
|
||||
nodes += st.get("cluster") or []
|
||||
for n in nodes:
|
||||
if n["word"] not in by_word or n["id"] != next(it["id"] for it in items if it["word"] == n["word"]):
|
||||
ghosts.append(f"{tid}:{st['id']}:{n['word']}")
|
||||
if ghosts:
|
||||
raise SystemExit("ghosts remain: " + ", ".join(ghosts[:20]))
|
||||
bad = []
|
||||
for w, entry in kb.items():
|
||||
if str(w).isdigit() or not isinstance(entry, dict):
|
||||
continue
|
||||
if low_has(entry.get("core", ""), STOCK_CORE):
|
||||
bad.append("core:" + w)
|
||||
if low_has(entry.get("shadow", ""), STOCK_SHADOW):
|
||||
bad.append("shadow:" + w)
|
||||
if low_has(entry.get("asks", ""), ("готов ли ты принять этот урок",)):
|
||||
bad.append("ask:" + w)
|
||||
if low_has(entry.get("step", ""), ("созерцать символ",)):
|
||||
bad.append("step:" + w)
|
||||
if bad:
|
||||
raise SystemExit("stock remains: " + ", ".join(bad[:20]))
|
||||
# id twins match word cores
|
||||
for it in items:
|
||||
twin = kb[str(it["id"])]
|
||||
if twin.get("core") != kb[it["word"]].get("core"):
|
||||
raise SystemExit(f"id twin mismatch {it['word']}")
|
||||
|
||||
|
||||
def main():
|
||||
text, items = load_dart()
|
||||
lexicon = json.loads(LEX.read_text(encoding="utf-8"))
|
||||
kb = json.loads(KB_ASSETS.read_text(encoding="utf-8"))
|
||||
canon = json.loads(CANON_A.read_text(encoding="utf-8"))
|
||||
|
||||
deaths = apply_swaps(items, lexicon, kb)
|
||||
apply_laws(kb, items)
|
||||
rewrite_descriptions(items, kb)
|
||||
stamp_from_canon(items, canon)
|
||||
lex_by_id = {r["id"]: r for r in lexicon}
|
||||
repair_canon(canon, items, lex_by_id)
|
||||
kb = mirror_kb(kb, items)
|
||||
assert_clean(items, kb, canon)
|
||||
|
||||
write_dart(text, items)
|
||||
LEX.write_text(json.dumps(lexicon, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
|
||||
payload = json.dumps(kb, ensure_ascii=False, indent=2) + "\n"
|
||||
KB_ASSETS.write_text(payload, encoding="utf-8")
|
||||
KB_OUT.write_text(payload, encoding="utf-8")
|
||||
canon_raw = json.dumps(canon, ensure_ascii=False, indent=2) + "\n"
|
||||
CANON_A.write_text(canon_raw, encoding="utf-8")
|
||||
CANON_B.write_text(canon_raw, encoding="utf-8")
|
||||
words_data = []
|
||||
for it in items:
|
||||
words_data.append(
|
||||
{
|
||||
"id": it["id"],
|
||||
"word": it["word"],
|
||||
"emoji": lex_by_id[it["id"]].get("emoji", ""),
|
||||
"pos": it["pos"],
|
||||
"category": it["cat"],
|
||||
"description": it["desc"],
|
||||
}
|
||||
)
|
||||
embed_html(canon, words_data)
|
||||
write_alpha(lexicon)
|
||||
print(f"deaths {len(deaths)}")
|
||||
for line in deaths:
|
||||
print(" ", line)
|
||||
print("words", len(items), "kb", len(kb), "stations", sum(len(t['stations']) for t in canon.values()))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user