feat: complete academic symbol lexicon heal, 2-column Cooper extraction, and updated assets
This commit is contained in:
@@ -0,0 +1,332 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Chinese zodiac: hanzi, earthly branches, lore, 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"
|
||||
YAML = ROOT / "oracle_enricher" / "data" / "sources" / "chinese_zodiac.yaml"
|
||||
|
||||
ZODIAC_TAG_RE = re.compile(r"\[Китайский гороскоп:[^\]]*\]\s*")
|
||||
|
||||
DART_RESTORE = {
|
||||
"вызвать": "обезьяна",
|
||||
}
|
||||
RESTORE_META = {
|
||||
"обезьяна": (
|
||||
"существительное",
|
||||
"Тотем / Архетип",
|
||||
"Ловкий ум ветвей, игра и трюк, существо, что снимает маску с порядка и собирает её заново.",
|
||||
),
|
||||
}
|
||||
|
||||
# n, id, ru, pinyin, animal_hanzi, branch_hanzi, branch_py, emoji, element, polarity, primary, cluster, lore
|
||||
STATIONS = [
|
||||
(
|
||||
1, "zodiac_1", "Крыса", "Шу", "鼠", "子", "Цзы", "🐀", "Вода", "Ян",
|
||||
"крыса", ["начало", "цикл", "вода"],
|
||||
"Первая земная ветвь, полночь и север: янская вода, что просачивается в любую щель. "
|
||||
"Крыса открывает двенадцатилетний круг — нюх на перемену, запас на чёрный день, ум, "
|
||||
"который выживает там, где сила ломается. В раскладе — чуткость к скрытой выгоде и скрытой "
|
||||
"утечке; тень знака — мелочность, подозрительность и грызня запасов.",
|
||||
),
|
||||
(
|
||||
2, "zodiac_2", "Бык", "Ню", "牛", "丑", "Чоу", "🐂", "Земля", "Инь",
|
||||
"бык", ["труд", "земля", "сила"],
|
||||
"Вторая ветвь, иньская земля плуга. Бык тянет борозду, пока другие уже спят: упорство, "
|
||||
"долг, выносливость, которая не нуждается в зрителях. Знак обещает плод только труду и "
|
||||
"времени. Тень — косность, упрямство и неспособность вовремя остановить плуг.",
|
||||
),
|
||||
(
|
||||
3, "zodiac_3", "Тигр", "Ху", "虎", "寅", "Инь", "🐅", "Дерево", "Ян",
|
||||
"тигр", ["охотник", "воля", "страсть"],
|
||||
"Третья ветвь, янское дерево весны: тигр сходит с горы. Доблесть, царственный прыжок, "
|
||||
"пыл, который рвёт преграду, а не обходит её. В раскладе — лидерский порыв и право на риск. "
|
||||
"Тень — вспышка без расчёта, охота ради охоты, ярость, что жрёт своих.",
|
||||
),
|
||||
(
|
||||
4, "zodiac_4", "Кролик", "Ту", "兔", "卯", "Мао", "🐇", "Дерево", "Инь",
|
||||
"заяц", ["мир", "луна", "дерево"],
|
||||
"Четвёртая ветвь, иньское дерево рассвета. Кролик (заяц восточного круга) живёт чуткостью: "
|
||||
"грация, дипломатия, умение не попасть под удар. Знак советует мягкий ход и верный момент. "
|
||||
"Тень — трусость, вечный обход и бегство в нору, когда пора стоять.",
|
||||
),
|
||||
(
|
||||
5, "zodiac_5", "Дракон", "Лун", "龍", "辰", "Чэнь", "🐉", "Земля", "Ян",
|
||||
"дракон", ["царь", "власть", "небо"],
|
||||
"Пятая ветвь, янская земля, но животное небесное. Дракон — единственный из двенадцати, "
|
||||
"кто не ходит по пыли: удача, харизма, преображение, дождь, который зовут, когда земля трескается. "
|
||||
"Знак даёт размах и требует меры. Тень — мания величия и власть без служения.",
|
||||
),
|
||||
(
|
||||
6, "zodiac_6", "Змея", "Шэ", "蛇", "巳", "Сы", "🐍", "Огонь", "Инь",
|
||||
"змей", ["мудрость", "тайна", "яд"],
|
||||
"Шестая ветвь, иньский огонь, что тлеет под кожей, а не пылает в лицо. Змея сбрасывает "
|
||||
"кожу и помнит нору: проницательность, мистика, холодный ум трансформации. В раскладе — "
|
||||
"тайна, которую нельзя брать силой. Тень — яд, ревность и коварство без мудрости.",
|
||||
),
|
||||
(
|
||||
7, "zodiac_7", "Лошадь", "Ма", "馬", "午", "У", "🐎", "Огонь", "Ян",
|
||||
"конь", ["свобода", "путь", "огонь"],
|
||||
"Седьмая ветвь, полдень, янский огонь скачки. Конь не стоит в стойле: свобода, путь, "
|
||||
"благородный ход, энтузиазм, который сжигает версту. Знак велит двигаться и не отдавать "
|
||||
"поводья страху. Тень — нетерпение, бег от себя и ветер, который не знает яслей.",
|
||||
),
|
||||
(
|
||||
8, "zodiac_8", "Коза", "Ян", "羊", "未", "Вэй", "🐐", "Земля", "Инь",
|
||||
"коза", ["мир", "плод", "земля"],
|
||||
"Восьмая ветвь, иньская земля пастбища. Коза (овца круга) держит вкус, стадо и мягкий нрав: "
|
||||
"художество, сочувствие, гармония, которую не куют мечом. Знак просит беречь хрупкое. "
|
||||
"Тень — каприз, жалость к себе и пассивность, когда пастбище уже вытоптано.",
|
||||
),
|
||||
(
|
||||
9, "zodiac_9", "Обезьяна", "Хоу", "猴", "申", "Шэнь", "🐒", "Металл", "Ян",
|
||||
"обезьяна", ["игра", "маска", "смех"],
|
||||
"Девятая ветвь, янский металл ветвей. Обезьяна — трикстер круга: ловкость, быстрый ум, "
|
||||
"игра, что снимает корону с идола и ставит её обратно уже как шутку. Знак даёт выход "
|
||||
"из тупика через смекалку. Тень — лукавство, насмешка вместо дела и верность, которая длится до скуки.",
|
||||
),
|
||||
(
|
||||
10, "zodiac_10", "Петух", "Цзи", "雞", "酉", "Ю", "🐓", "Металл", "Инь",
|
||||
"петух", ["рассвет", "железо", "страж"],
|
||||
"Десятая ветвь, иньский металл зари. Петух кричит, когда ещё темно: бдительность, прямота, "
|
||||
"порядок двора, встреча света. Знак велит назвать вещь своим именем и стоять на часах. "
|
||||
"Тень — хвастовство, придирка и крик, который будит дом впустую.",
|
||||
),
|
||||
(
|
||||
11, "zodiac_11", "Собака", "Гоу", "狗", "戌", "Сюй", "🐕", "Земля", "Ян",
|
||||
"собака", ["друг", "страж", "защита"],
|
||||
"Одиннадцатая ветвь, янская земля порога. Собака сторожит своих: верность, честность, "
|
||||
"чутьё на неправду, служба, которая не торгуется. В раскладе — союз, которому можно верить. "
|
||||
"Тень — тревога, пессимизм и преданность, уже слепая к тому, кого сторожит.",
|
||||
),
|
||||
(
|
||||
12, "zodiac_12", "Свинья", "Чжу", "豬", "亥", "Хай", "🐗", "Вода", "Инь",
|
||||
"кабан", ["изобилие", "пир", "вода"],
|
||||
"Двенадцатая ветвь, иньская вода, что закрывает круг и кормит зиму. Свинья (кабан) — "
|
||||
"прямодушие, щедрость, достаток без позы, напор того, кто не играет в тонкость. Знак "
|
||||
"даёт сытость и искренность. Тень — наивность, чревоугодие и неумение сказать «нет».",
|
||||
),
|
||||
]
|
||||
|
||||
|
||||
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 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)
|
||||
missing = []
|
||||
stations = []
|
||||
|
||||
for rec in STATIONS:
|
||||
(
|
||||
n, sid, ru, py, hanzi, branch, bpy, emoji, element, pol,
|
||||
prim, extra, lore,
|
||||
) = rec
|
||||
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:
|
||||
continue
|
||||
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))
|
||||
name = f"{n} {ru} ({py})"
|
||||
st = {
|
||||
"id": sid,
|
||||
"name": name,
|
||||
"symbol": emoji,
|
||||
"hanzi": f"{hanzi} · {branch}",
|
||||
"group": f"Земные ветви · {branch} {bpy} · {pol}-{element}",
|
||||
"lore": lore,
|
||||
"primary": payload(prim, dart_by_word, lex_by_word),
|
||||
"cluster": cluster,
|
||||
}
|
||||
stations.append(st)
|
||||
tag = f"[Китайский гороскоп: {n}/12 {ru} {hanzi} ({py} / {bpy})]"
|
||||
word_to_tags[prim].append(tag)
|
||||
for c in cluster:
|
||||
word_to_tags[c["word"]].append(tag)
|
||||
|
||||
if missing:
|
||||
print("WARN", missing)
|
||||
|
||||
z = canon["zodiac"]
|
||||
z["name"] = "Китайский Зодиак (十二生肖)"
|
||||
z["count"] = len(stations)
|
||||
z["description"] = (
|
||||
"Двенадцать земных ветвей (地支): животные восточного круга с классическими иероглифами, "
|
||||
"стихией У-син, полярностью инь/ян и пересечениями словаря 999."
|
||||
)
|
||||
z["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 = ZODIAC_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")
|
||||
|
||||
yaml_lines = [
|
||||
"# 12 Animals of Chinese Zodiac (十二生肖 / 12 земных ветвей)",
|
||||
"system: chinese_zodiac",
|
||||
'version: "2.0"',
|
||||
"animals:",
|
||||
]
|
||||
yaml_ids = [
|
||||
"rat", "ox", "tiger", "rabbit", "dragon", "snake",
|
||||
"horse", "goat", "monkey", "rooster", "dog", "pig",
|
||||
]
|
||||
for rec, yid in zip(STATIONS, yaml_ids):
|
||||
n, sid, ru, py, hanzi, branch, bpy, emoji, element, pol, prim, extra, lore = rec
|
||||
yaml_lines.append(f" {yid}:")
|
||||
yaml_lines.append(f" number: {n}")
|
||||
yaml_lines.append(f' name: "{ru}"')
|
||||
yaml_lines.append(f' hanzi: "{hanzi}"')
|
||||
yaml_lines.append(f' pinyin: "{py}"')
|
||||
yaml_lines.append(f' branch: "{bpy} ({branch})"')
|
||||
yaml_lines.append(f' element: "{element}"')
|
||||
yaml_lines.append(f' polarity: "{pol}"')
|
||||
yaml_lines.append(f' lore: "{lore}"')
|
||||
yaml_lines.append(" names_ru:")
|
||||
yaml_lines.append(f" - {prim}")
|
||||
for w in extra:
|
||||
yaml_lines.append(f" - {w}")
|
||||
YAML.write_text("\n".join(yaml_lines) + "\n", encoding="utf-8")
|
||||
|
||||
print(f"zodiac {len(stations)} stamped {nstamp} tagged {len(word_to_tags)}")
|
||||
for st in stations:
|
||||
cl = [c["word"] for c in st["cluster"]]
|
||||
print(f" {st['id']:10} {st['hanzi']:8} {st['name']:22} prim={st['primary']['word']:10} {cl}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user