feat: complete academic symbol lexicon heal, 2-column Cooper extraction, and updated assets

This commit is contained in:
2026-10-09 15:26:58 +05:00
parent 7f91366201
commit 0b12e4583b
244 changed files with 611461 additions and 1617 deletions
+662
View File
@@ -0,0 +1,662 @@
#!/usr/bin/env python3
"""Build I Ching canon: 8 Ba Gua trigrams + 64 King Wen hexagrams.
Updates:
oracle_enricher/data/traditions_canon.json
assets/data/traditions_canon.json
lexicon_viewer.html (embedded TRADITIONS_CANON)
oracle_enricher/data/sources/iching.yaml
"""
from __future__ import annotations
import json
import re
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
CANON_PATH = ROOT / "oracle_enricher" / "data" / "traditions_canon.json"
ASSET_PATH = ROOT / "assets" / "data" / "traditions_canon.json"
HTML_PATH = ROOT / "lexicon_viewer.html"
LEXICON_PATH = ROOT / "oracle_enricher" / "data" / "lexicon.json"
DART_PATH = ROOT / "lib" / "data" / "words_data.dart"
YAML_PATH = ROOT / "oracle_enricher" / "data" / "sources" / "iching.yaml"
# ---------------------------------------------------------------------------
# 8 trigrams (Ba Gua)
# ---------------------------------------------------------------------------
TRIGRAMS = [
{
"id": "qian",
"name": "Триграмма ☰ Цянь (Творчество / Небо)",
"symbol": "☰",
"group": "Ба Гуа · Отец",
"lore": "Первичная созидающая сила неба: неутомимая крепость духа, высшая воля, свет, лидерство и власть закона.",
"names": ["небо", "небосвод", "творец", "воля", "свет"],
"aspects": ["чистый ян", "несокрушимая сила", "непрерывное движение", "семя замысла", "свет духа"],
"shadow": ["гордыня", "выжигание без опоры", "тирания жесткости"],
"yaml_name": "Творчество (Небо)",
"hanzi": "乾",
},
{
"id": "kun",
"name": "Триграмма ☷ Кунь (Исполнение / Земля)",
"symbol": "☷",
"group": "Ба Гуа · Мать",
"lore": "Самоотверженное принятие, великое земное плодородие, мягкость, питание всех существ, верность и покой.",
"names": ["земля", "мать", "покой", "лоно"],
"aspects": ["чистый инь", "восприимчивость", "питающее лоно", "преданное следование", "уступчивость"],
"shadow": ["косность", "инертность", "потеря формы"],
"yaml_name": "Исполнение (Земля)",
"hanzi": "坤",
},
{
"id": "zhen",
"name": "Триграмма ☳ Чжэнь (Возбуждение / Гром)",
"symbol": "☳",
"group": "Ба Гуа · Старший сын",
"lore": "Пробуждающий удар грома, импульсивный прорыв весны, сокрушение оцепенения, страх, ведущий к осмотрительности.",
"names": ["гром", "молния", "удар"],
"aspects": ["возбуждение", "внезапный толчок", "раскат небес", "шок пробуждения", "первое движение"],
"shadow": ["паника", "сотрясение без плода", "слепая ярость"],
"yaml_name": "Пробуждение (Гром)",
"hanzi": "震",
},
{
"id": "kan",
"name": "Триграмма ☵ Кань (Погружение / Вода)",
"symbol": "☵",
"group": "Ба Гуа · Средний сын",
"lore": "Опасность стремнины, глубина бездны, неостановимый бег воды, верность своей природе среди теснин.",
"names": ["вода", "бездна", "опасность"],
"aspects": ["глубина", "текучесть в расселинах", "опасный поток", "искренность сердца в испытании", "неиссякаемый исток"],
"shadow": ["утопление в страхе", "холод отчаяния", "затягивающий водоворот"],
"yaml_name": "Погружение (Вода)",
"hanzi": "坎",
},
{
"id": "gen",
"name": "Триграмма ☶ Гэнь (Пребывание / Гора)",
"symbol": "☶",
"group": "Ба Гуа · Младший сын",
"lore": "Неподвижность священной горы, способность вовремя остановиться, завершение движения, крепость духа.",
"names": ["гора", "скала", "покой", "спина"],
"aspects": ["незыблемая остановка", "покой спины", "граница движения", "молчание скалы", "завершение фазы"],
"shadow": ["каменный застой", "глухота к переменам", "черствость"],
"yaml_name": "Пребывание (Гора)",
"hanzi": "艮",
},
{
"id": "xun",
"name": "Триграмма ☴ Сюнь (Утончение / Ветер)",
"symbol": "☴",
"group": "Ба Гуа · Старшая дочь",
"lore": "Мягкое всепроникающее дыхание ветра, гибкость древесных ветвей, постепенное незаметное влияние.",
"names": ["ветер", "дыхание", "воздух", "дерево"],
"aspects": ["проникновение", "мягкая гибкость", "незаметное влияние", "постоянство дыхания", "корни дерева"],
"shadow": ["размывание границ", "уклончивость", "бессилие удара"],
"yaml_name": "Утончение (Ветер)",
"hanzi": "巽",
},
{
"id": "li",
"name": "Триграмма ☲ Ли (Сцепление / Огонь)",
"symbol": "☲",
"group": "Ба Гуа · Средняя дочь",
"lore": "Очищающее пламя, свет познания, ясность видения, тепло очага, зависимость пламени от питающей древесины.",
"names": ["огонь", "пламя", "сияние", "свет"],
"aspects": ["ясность видения", "солярный свет", "преображающее тепло", "приверженность истине", "зависимость от топлива"],
"shadow": ["выгорание", "ослепляющая иллюзия", "пожирающая страсть"],
"yaml_name": "Сцепление (Огонь)",
"hanzi": "離",
},
{
"id": "dui",
"name": "Триграмма ☱ Дуй (Разрешение / Озеро)",
"symbol": "☱",
"group": "Ба Гуа · Младшая дочь",
"lore": "Зеркальная гладь спокойного озера, открытая радость общения, благожелательность, поэзия и вдохновение.",
"names": ["озеро", "радость", "смех", "рот", "зеркало"],
"aspects": ["зеркало вод", "открытая радость", "плодотворный диалог", "улыбка сердца", "чистое отражение"],
"shadow": ["пустая болтовня", "разлив соблазна", "легкомыслие"],
"yaml_name": "Разрешение (Озеро)",
"hanzi": "兌",
},
]
# ---------------------------------------------------------------------------
# 64 hexagrams — King Wen order, Shchutsky Russian names
# group key before ' · ' becomes the atlas section header
# ---------------------------------------------------------------------------
def _hex(n: int) -> str:
return chr(0x4DC0 + (n - 1))
def _grp(n: int) -> str:
start = ((n - 1) // 8) * 8 + 1
end = start + 7
return f"Книга Перемен · {start:02d}–{end:02d}"
HEX_HANZI = {
1: "乾", 2: "坤", 3: "屯", 4: "蒙", 5: "需", 6: "訟", 7: "師", 8: "比",
9: "小畜", 10: "履", 11: "泰", 12: "否", 13: "同人", 14: "大有", 15: "謙", 16: "豫",
17: "隨", 18: "蠱", 19: "臨", 20: "觀", 21: "噬嗑", 22: "賁", 23: "剝", 24: "復",
25: "無妄", 26: "大畜", 27: "頤", 28: "大過", 29: "坎", 30: "離", 31: "咸", 32: "恆",
33: "遯", 34: "大壯", 35: "晉", 36: "明夷", 37: "家人", 38: "睽", 39: "蹇", 40: "解",
41: "損", 42: "益", 43: "夬", 44: "姤", 45: "萃", 46: "升", 47: "困", 48: "井",
49: "革", 50: "鼎", 51: "震", 52: "艮", 53: "漸", 54: "歸妹", 55: "豐", 56: "旅",
57: "巽", 58: "兌", 59: "渙", 60: "節", 61: "中孚", 62: "小過", 63: "既濟", 64: "未濟",
}
HEXAGRAMS = [
dict(n=1, pid="qian", ru="Творчество", py="Цянь",
lore="Небо над небом: чистый ян без перерыва. Неутомимое творчество, крепость духа и ход, который сам себе закон. Знак велит держаться замысла до конца и не искать опоры внизу.",
names=["творец", "небо", "воля", "свет", "небосвод"],
aspects=["сухое небо", "непрерывное творчество", "настойчивость до конца", "свет духа", "закон силы"],
shadow=["жёсткость без земли", "гордыня творца", "выжигание без опоры"]),
dict(n=2, pid="kun", ru="Исполнение", py="Кунь",
lore="Земля над землёй: чистое принятие. Плодородие, преданное следование и сила кобылы, которая несёт, не выбирая всадника. Знак велит служить и вынашивать, а не захватывать ход.",
names=["земля", "мать", "покой"],
aspects=["чистый приём", "вместилище", "плодородие", "следование", "кобыла пути"],
shadow=["инертность", "косность", "потеря формы"]),
dict(n=3, pid="zhun", ru="Начальная трудность", py="Чжунь",
lore="Вода над громом: росток ещё под мерзлой землёй. Первые всходы требуют корней, а не рывка. Знак держит хаос перед порядком и верность зародышу.",
names=["росток", "семя", "начать", "бездна", "гром"],
aspects=["пробивание мерзлой земли", "накопление корней", "верность зародышу", "хаос перед порядком"],
shadow=["преждевременный рывок", "увядание на корню", "страх перед миром"]),
dict(n=4, pid="meng", ru="Недоразвитость", py="Мэн",
lore="Гора над водой: родник у подножия, закрытый туманом. Ученичество, искренний вопрос и поиск учителя. Знак рассеивает неведение только тому, кто спрашивает трижды и слушает.",
names=["ученик", "туман", "учиться", "гора", "источник"],
aspects=["родник у горы", "искреннее вопрошание", "смирение ученичества", "поиск учителя"],
shadow=["упрямое невежество", "дерзость дилетанта", "пустые споры"]),
dict(n=5, pid="xu", ru="Необходимость ждать", py="Сюй",
lore="Вода над небом: облака ещё не стали дождём. Священное ожидание, накопление и пир веры, пока час не созрел. Знак велит не торопить тучу.",
names=["ждать", "терпение", "туча", "дождь", "пир"],
aspects=["облака в ожидании дождя", "накопление потенциала", "священный пир веры", "созревание часа"],
shadow=["парализующая лень", "отчаяние ожидания", "упущенный момент"]),
dict(n=6, pid="song", ru="Суд", py="Сун",
lore="Небо над водой: верх и глубина разошлись. Тяжба, проверка совести и поиск беспристрастного суда. Знак велит остановиться на полпути и признать предел правоты.",
names=["суд", "закон", "справедливость", "спор"],
aspects=["расхождение Неба и Воды", "проверка совести", "беспристрастный суд", "признание предела"],
shadow=["бесконечная вражда", "жажда мести", "гордыня правоты"]),
dict(n=7, pid="shi", ru="Войско", py="Ши",
lore="Земля над водой: скрытая сила в недрах. Дисциплина, единая цель и зрелый полководец. Знак даёт справедливость силы и требует, чтобы войско служило порядку, а не жажде крови.",
names=["войско", "воин", "солдат", "земля", "вода"],
aspects=["скрытая сила воды в земле", "суровая дисциплина", "единство цели", "зрелый полководец"],
shadow=["жестокость приказа", "напрасные жертвы", "войско без вождя"]),
dict(n=8, pid="bi", ru="Приближение", py="Би",
lore="Вода над землёй: добровольное единение. Союз, взаимная опора и выбор соратников. Знак зовёт встать рядом вовремя — опоздание к союзу уже тень.",
names=["союз", "близкий", "связь", "вода", "земля"],
aspects=["вода орошает землю", "добровольное единение", "верность союзу", "выбор соратников"],
shadow=["опоздание к союзу", "фальшивая лесть", "зависимость"]),
dict(n=9, pid="xiao_xu", ru="Воспитание малым", py="Сяо-чу",
lore="Ветер над небом: малая туча ещё не льёт. Сдерживание великого малым, накопление капли за каплей. Знак учит мягкой узде, а не удару.",
names=["ветер", "небо", "терпение", "дыхание"],
aspects=["малая туча на небе", "сдерживание великого", "накопление по капле", "мягкая узда"],
shadow=["бессилие узды", "мелочный контроль", "туча без дождя"]),
dict(n=10, pid="lu", ru="Поступь", py="Люй",
lore="Небо над озером: ступать по хвосту тигра и не быть укушенным. Осторожный шаг в присутствии силы. Знак требует меры в каждом ходе.",
names=["шаг", "ход", "путь", "тигр", "мера"],
aspects=["ступня на хвосте тигра", "осторожный этикет", "мера каждого хода", "опасная близость силы"],
shadow=["дерзость шага", "укус тигра", "потеря лица"]),
dict(n=11, pid="tai", ru="Расцвет", py="Тай",
lore="Земля над небом: священный брак низа и верха. Малое уходит, великое приходит. Золотой век согласия, плодородие и расцвет — пока не забыли об упадке.",
names=["мир", "изобилие", "весна", "благодать", "земля"],
aspects=["земля над небом", "золотой век согласия", "малое уходит великое приходит", "плодородие сил"],
shadow=["беспечность благополучия", "забывчивость об упадке", "сытая слепота"]),
dict(n=12, pid="pi", ru="Упадок", py="Пи",
lore="Небо над землёй: миры удалились друг от друга. Застой, сокрытие мудреца и сбережение зерна. Знак велит не служить недостойным и ждать поворота.",
names=["уныние", "слабость", "отчаяние", "небо", "земля"],
aspects=["разрыв связи миров", "сокрытие мудреца", "сбережение зерна", "терпеливая аскеза"],
shadow=["отчаяние как дом", "подчинение недостойным", "утрата веры"]),
dict(n=13, pid="tong_ren", ru="Родня", py="Тун-жэнь",
lore="Небо над огнём: люди одного света собираются в открытом поле. Содружество по духу, а не по крови. Знак требует широкого круга и прямого сердца.",
names=["род", "брат", "союз", "огонь", "небо"],
aspects=["содружество в открытом поле", "единство по духу", "широкий круг", "прямое сердце"],
shadow=["клан против чужих", "ложное братство", "огонь кружка"]),
dict(n=14, pid="da_you", ru="Великое обладание", py="Да-ю",
lore="Огонь над небом: солнце в зените владеет всем, что видит. Богатство, ясность и великая мера. Знак велит держать изобилие светлым, иначе оно слепит.",
names=["изобилие", "золото", "успех", "огонь", "небо"],
aspects=["солнце в зените", "владение ясностью", "великая мера", "светлое богатство"],
shadow=["слепящая роскошь", "жадность к свету", "обладание без меры"]),
dict(n=15, pid="qian_mod", ru="Смирение", py="Цянь",
lore="Земля над горой: высокая внутри, низкая снаружи. Смирение как закон силы, которая не выпирает. Знак уравнивает избыток и восполняет недостаток.",
names=["смирение", "гора", "земля"],
aspects=["высокая внутри низкая снаружи", "уравнивание избытка", "сила без выпирания", "скромный ход"],
shadow=["ложное смирение", "самоуничижение", "гора под спудом"]),
dict(n=16, pid="yu", ru="Вольность", py="Юй",
lore="Гром над землёй: музыка предвидения, радость готовности. Знак зовёт собирать войско в настроении праздника — вольность, которая знает срок удара.",
names=["радость", "гром", "свобода", "земля"],
aspects=["гром над землёй", "музыка предвидения", "радость готовности", "сбор войска в празднике"],
shadow=["разгул без срока", "праздность вместо удара", "ложная вольность"]),
dict(n=17, pid="sui", ru="Последование", py="Суй",
lore="Озеро над громом: радость следует за возбуждением. Знак велит идти за тем, что истинно движется, и отпускать то, что уже не ведёт.",
names=["идти", "вести", "радость", "гром"],
aspects=["радость за возбуждением", "следование истинному движению", "отпускание мёртвого вождя"],
shadow=["слепое следование", "стадный ход", "потеря своего удара"]),
dict(n=18, pid="gu", ru="Исправление порчи", py="Гу",
lore="Гора над ветром: ветер заперт и гниёт. Расчистка наследственных завалов, три дня до и три дня после. Знак требует чистить порчу, а не жить в ней.",
names=["скверна", "яд", "целитель", "гора", "ветер"],
aspects=["ветер под горой", "расчистка наследства", "три дня до и после", "очищающая реформа"],
shadow=["отравление прошлым", "страх растревожить гниль", "бессилие перед традицией"]),
dict(n=19, pid="lin", ru="Посещение", py="Линь",
lore="Земля над озером: приближение великого к малому. Надзор, забота и срок восьмого месяца. Знак даёт приход силы — и предупреждает, что приход не вечен.",
names=["гость", "озеро", "земля", "приближение"],
aspects=["приближение великого", "надзор и забота", "срок восьмого месяца", "приход силы"],
shadow=["навязчивый надзор", "гость, который не уходит", "забота как захват"]),
dict(n=20, pid="guan", ru="Созерцание", py="Гуань",
lore="Ветер над землёй: башня взора, чистота помыслов перед жертвой. Знак ставит пример без слов — созерцать закон космоса и быть видимым как маяк.",
names=["созерцание", "взгляд", "маяк", "ветер", "земля"],
aspects=["башня визионерского взора", "чистота перед жертвой", "безмолвный пример", "постижение космоса"],
shadow=["праздное любопытство", "оторванность от действия", "холод наблюдателя"]),
dict(n=21, pid="shi_he", ru="Стиснутые зубы", py="Ши-хэ",
lore="Огонь над громом: стиснуть и перекусить преграду. Судебная ясность, удар по помехе, глоток правды. Знак велит разгрызть узел, а не обходить его.",
names=["удар", "закон", "огонь", "гром", "суд"],
aspects=["перекусить преграду", "судебная ясность", "удар по помехе", "глоток правды"],
shadow=["ломание зубов о камень", "жестокость приговора", "гнев вместо суда"]),
dict(n=22, pid="bi_grace", ru="Убранство", py="Би",
lore="Гора над огнём: огонь освещает форму горы. Красота как оболочка сути, не как суть. Знак даёт убранство малому и велит не принимать позолоту за кость.",
names=["сияние", "маска", "гора", "огонь", "форма"],
aspects=["огонь освещает гору", "красота как оболочка", "убранство малого", "форма без подмены сути"],
shadow=["позолота вместо кости", "суета украшения", "маска, съевшая лицо"]),
dict(n=23, pid="bo", ru="Разорение", py="Бо",
lore="Гора над землёй: верх осыпается, остаётся ложе. Сдирание ветхой формы, конец цикла, обнажение основы. Знак велит не удерживать то, что уже осыпается.",
names=["угасание", "гора", "земля", "конец"],
aspects=["осыпание верха", "сдирание ветхой формы", "обнажение основы", "конец цикла"],
shadow=["цепляться за осыпь", "разрушение без остатка", "паника конца"]),
dict(n=24, pid="fu", ru="Возврат", py="Фу",
lore="Земля над громом: первый ян в глубине тьмы. Поворот колеса, семь дней обновления, закрытые врата покоя. Знак велит дать ростку срок и не рвать его на свет.",
names=["вернуться", "искра", "цикл", "гром", "земля"],
aspects=["первый ян во тьме", "поворот колеса года", "семь дней обновления", "покой закрытых врат"],
shadow=["поспешность незрелого света", "страх темноты", "рецидив старой ошибки"]),
dict(n=25, pid="wu_wang", ru="Беспорочность", py="У-ван",
lore="Небо над громом: движение без кривды. Невинный удар, который не рассчитывает выгоду. Знак бережёт чистоту намерения и гасит ход, затеянный ради добычи.",
names=["чистота", "небо", "гром", "истина"],
aspects=["движение без кривды", "невинный удар", "чистота намерения", "ход не ради добычи"],
shadow=["наивность как слепота", "удар без расчёта", "чистота как предлог"]),
dict(n=26, pid="da_xu", ru="Воспитание великим", py="Да-чу",
lore="Гора над небом: небо удержано в горе. Великое накопление, кормление мудрости, сила в упряжи. Знак велит копить и воспитывать, а не выпускать всё сразу.",
names=["гора", "небо", "сила", "мудрость"],
aspects=["небо удержано в горе", "великое накопление", "кормление мудрости", "сила в упряжи"],
shadow=["скупость накопления", "сила без выпуска", "гора как тюрьма неба"]),
dict(n=27, pid="yi_jaws", ru="Питание", py="И",
lore="Гора над громом: челюсти, которые кормят или пожирают. Знак спрашивает, чем ты питаешь себя и других — речью, хлебом, вниманием — и велит смотреть на рот.",
names=["хлеб", "голод", "рот", "гора", "гром"],
aspects=["челюсти питания", "чем кормишь себя", "речь как пища", "взгляд на рот"],
shadow=["пожирание", "пустой рот", "питание чужим"]),
dict(n=28, pid="da_guo", ru="Переразвитие великого", py="Да-го",
lore="Озеро над ветром: балка прогнулась под тяжестью крыши. Избыток великого, крайний срок подпорок. Знак велит действовать сейчас: стоять или рухнуть.",
names=["озеро", "ветер", "дерево", "верёвка"],
aspects=["прогнувшаяся балка", "избыток великого", "крайний срок подпорок", "стоять или рухнуть"],
shadow=["обвал крыши", "упрямство в избытке", "поздняя подпорка"]),
dict(n=29, pid="kan_hex", ru="Повторная опасность", py="Кань",
lore="Вода над водой: бездна в бездне. Испытание верности через риск, стремление не замирать в омуте. Знак закаливает сердце, если оно остаётся искренним в порогах.",
names=["бездна", "опасность", "вода"],
aspects=["вода над водой", "испытание через риск", "не останавливаться в стремнине", "глубинная закалка"],
shadow=["ужас глубины", "паника в порогах", "предательство себя"]),
dict(n=30, pid="li_hex", ru="Сияние", py="Ли",
lore="Огонь над огнём: двойная ясность, зависимость от топлива. Сцепление света с тем, что его кормит. Знак даёт видение и велит не выгореть посреди дня.",
names=["огонь", "сияние", "свет", "пламя"],
aspects=["двойная ясность", "сцепление с топливом", "видение дня", "свет, который держится за опору"],
shadow=["выгорание середины", "ослепление", "пожар без очага"]),
dict(n=31, pid="xian", ru="Взаимодействие", py="Сянь",
lore="Озеро над горой: влияние, которое сходит сверху на неподвижное. Притяжение, отклик сердца, священный брак чувств. Знак велит принять воздействие и ответить без захвата.",
names=["любовь", "встреча", "союз", "озеро", "гора"],
aspects=["влияние сверху на гору", "притяжение сердец", "священный отклик", "принять и ответить"],
shadow=["захват чувством", "слепая влюблённость", "влияние как власть"]),
dict(n=32, pid="heng", ru="Постоянство", py="Хэн",
lore="Гром над ветром: долгий брак движения и проникновения. Выдержка, клятва на срок, ритм, который не срывается. Знак велит держать курс, а не искать новый ветер каждый час.",
names=["цикл", "клятва", "вечность", "гром", "ветер"],
aspects=["долгий брак грома и ветра", "выдержка", "клятва на срок", "ритм без срыва"],
shadow=["рутина вместо верности", "упрямство курса", "клятва мёртвому"]),
dict(n=33, pid="dun", ru="Бегство", py="Дунь",
lore="Небо над горой: мудрец отступает, пока сила тьмы ещё мала. Стратегический уход, сохранение ядра. Знак велит уйти вовремя — не геройствовать на гиблом поле.",
names=["бежать", "гора", "небо", "изгнанник"],
aspects=["отступление мудреца", "уход пока тьма мала", "сохранение ядра", "стратегический тыл"],
shadow=["паническое бегство", "уход от срока", "оставление поля слишком рано"]),
dict(n=34, pid="da_zhuang", ru="Мощь великого", py="Да-чжуан",
lore="Гром над небом: сила в зените, рога баранов. Великая мощь требует прямоты — иначе рога застрянут в изгороди. Знак даёт удар и велит не бодать без пути.",
names=["сила", "гром", "небо", "баран"],
aspects=["сила в зените", "рога баранов", "прямота удара", "мощь, которая знает путь"],
shadow=["бодание изгороди", "слепая мощь", "сила без прямоты"]),
dict(n=35, pid="jin", ru="Восход", py="Цзинь",
lore="Огонь над землёй: солнце всходит над почвой. Продвижение, признание, свет, который уже виден всем. Знак даёт восход заслуги — и предупреждает не принимать рассвет за полдень.",
names=["рассвет", "свет", "огонь", "земля", "успех"],
aspects=["солнце над почвой", "продвижение заслуги", "свет, видимый всем", "восход, не полдень"],
shadow=["жажда признания", "рассвет как корона", "свет без корня"]),
dict(n=36, pid="ming_yi", ru="Поражение света", py="Мин-и",
lore="Земля над огнём: свет ранен и спрятан в земле. Затмение, мудрость в изгнании, крыло, которое прячут. Знак велит хранить ясность внутри, когда снаружи правят тьма и рана.",
names=["затмение", "тьма", "рана", "земля", "огонь"],
aspects=["свет спрятан в земле", "мудрость в изгнании", "спрятанное крыло", "ясность внутри раны"],
shadow=["угасание духа", "сотрудничество с тьмой", "рана как характер"]),
dict(n=37, pid="jia_ren", ru="Домашние", py="Цзя-жэнь",
lore="Ветер над огнём: огонь очага, ветер речей семьи. Порядок дома, роли, слово, которое держит кров. Знак велит начать правление с собственного очага.",
names=["дом", "семья", "очаг", "ветер", "огонь"],
aspects=["огонь очага", "ветер семейных речей", "порядок ролей", "правление с дома"],
shadow=["тирания дома", "сплетня очага", "семья как клетка"]),
dict(n=38, pid="kui", ru="Разлад", py="Куй",
lore="Огонь над озером: два света смотрят врозь. Отчуждение, несходство, огонь и озеро не смешиваются. Знак учит малому согласию внутри большого разлада — и не требует слияния силой.",
names=["враг", "огонь", "озеро", "двойник"],
aspects=["два света врозь", "отчуждение", "малое согласие в разладе", "несходство без слияния"],
shadow=["вражда ради вражды", "принудительное слияние", "огонь, высушивший озеро"]),
dict(n=39, pid="jian", ru="Препятствие", py="Цзянь",
lore="Вода над горой: стремнина на хребте. Преграда, которая велит идти не вперёд, а за советом. Знак останавливает прямой ход и открывает юг — путь к мудрому.",
names=["гора", "вода", "пропасть", "скала"],
aspects=["стремнина на хребте", "преграда прямого хода", "идти за советом", "юг к мудрому"],
shadow=["упрямство в тупике", "штурм пропасти", "отказ от совета"]),
dict(n=40, pid="xie", ru="Разрешение", py="Се",
lore="Гром над водой: гроза разряжает бездну. Снятие пут, прощение, стрела, вынутая из раны. Знак велит освободить вовремя — держать узел после грозы уже порча.",
names=["гром", "вода", "свобода", "прощение"],
aspects=["гроза над бездной", "снятие пут", "стрела из раны", "освобождение в срок"],
shadow=["разрешение слишком рано", "месть вместо прощения", "узел, который не развязали"]),
dict(n=41, pid="sun", ru="Убыль", py="Сунь",
lore="Гора над озером: озеро отдаёт воде горе. Уменьшение низа ради верха, искренняя жертва двух чаш. Знак велит убавить простое, чтобы укрепить главное — и знать меру убыли.",
names=["жертва", "отлив", "гора", "озеро", "мера"],
aspects=["озеро отдаёт горе", "убыль низа ради верха", "две чаши жертвы", "мера уменьшения"],
shadow=["самоистязание", "убыль без укрепления", "жертва впустую"]),
dict(n=42, pid="yi_increase", ru="Прибыль", py="И",
lore="Ветер над громом: верхи убавляются, низы прибывают. Дар свыше, прилив сил, пора сеять. Знак велит брать прибыль в работу, пока ветер несёт семя.",
names=["дар", "изобилие", "прилив", "ветер", "гром"],
aspects=["верхи убавляются низы прибывают", "дар свыше", "пора сеять", "прибыль в работу"],
shadow=["жадность к дару", "прибыль без сева", "ветер, разнёсший семя"]),
dict(n=43, pid="guai", ru="Выход", py="Гуай",
lore="Озеро над небом: прорыв плотины, решительное объявление. Пять ян вытесняют последнее инь. Знак велит говорить правду в лицо и не применять тайного оружия.",
names=["выход", "озеро", "небо", "правда"],
aspects=["прорыв плотины", "решительное объявление", "пять ян против одного инь", "правда в лицо"],
shadow=["слом плотины без берега", "насилие выхода", "тайное оружие"]),
dict(n=44, pid="gou", ru="Перечение", py="Гоу",
lore="Небо над ветром: встреча с тем, чего не звали. Один инь входит снизу в пять ян. Знак предупреждает: не выдавать за жениха то, что пришло как искушение.",
names=["встреча", "искушение", "ветер", "небо"],
aspects=["встреча незваного", "один инь в пяти ян", "искушение снизу", "не выдавать за союз"],
shadow=["брак с порчей", "слепое влечение", "ветер, подточивший небо"]),
dict(n=45, pid="cui", ru="Воссоединение", py="Цуй",
lore="Озеро над землёй: воды собираются в низине. Сбор людей, жертва вождю, большое собрание. Знак велит соединиться вокруг достойного центра — иначе толпа станет болотом.",
names=["союз", "встреча", "озеро", "земля", "пир"],
aspects=["воды в низине", "сбор людей", "жертва вождю", "центр собрания"],
shadow=["толпа без центра", "болото собрания", "вождь как идол"]),
dict(n=46, pid="sheng", ru="Подъём", py="Шэн",
lore="Земля над ветром: дерево растёт из почвы, не торопя день. Постепенный подъём, покорность земле, шаг за шагом вверх. Знак велит расти из корня, а не прыгать на вершину.",
names=["рост", "дерево", "земля", "ветер"],
aspects=["дерево из почвы", "постепенный подъём", "шаг за шагом", "рост из корня"],
shadow=["прыжок без корня", "подъём чужими плечами", "спешка роста"]),
dict(n=47, pid="kun_exhaust", ru="Истощение", py="Кунь",
lore="Озеро над водой: озеро утекло, осталась яма. Сжатие, нужда, слово, которое не слышат. Знак велит хранить внутреннюю правду, когда внешняя сила иссякла.",
names=["голод", "нужда", "озеро", "вода"],
aspects=["утекающее озеро", "сжатие сил", "слово, которое не слышат", "правда внутри нужды"],
shadow=["отчаяние ямы", "молчание как смерть", "нужда как закон"]),
dict(n=48, pid="jing", ru="Колодец", py="Цзин",
lore="Вода над ветром: вода поднимается по дереву. Неиссякаемый исток общины, кувшин у устья, глубина, которую не переносят с места на место. Знак велит беречь колодец, а не менять город.",
names=["колодец", "источник", "родник", "вода", "дерево"],
aspects=["вода по дереву", "исток общины", "непереносимая глубина", "кувшин у устья"],
shadow=["заиленный колодец", "разбитый кувшин", "жадность к воде"]),
dict(n=49, pid="ge", ru="Смена", py="Гэ",
lore="Озеро над огнём: огонь вскипает в озере, змея меняет кожу. Переворот календаря, линька порядка. Знак велит менять шкуру, когда срок созрел, и иметь проект на место старого.",
names=["змей", "лава", "феникс", "озеро", "огонь"],
aspects=["огонь в озере", "смена шкуры", "обновление календаря", "линька порядка"],
shadow=["бунт ради бунта", "разрушение без проекта", "запоздалое обновление"]),
dict(n=50, pid="ding", ru="Котел", py="Дин",
lore="Огонь над ветром: дерево кормит огонь под треножником. Священный котёл, превращение грубого в пищу духа, три ножки устойчивости. Знак велит варить культуру, а не опрокидывать сосуд.",
names=["котёл", "сосуд", "алтарь", "огонь", "ветер"],
aspects=["огонь под деревом", "алхимический сосуд", "жертвенная трапеза", "три ножки треножника"],
shadow=["опрокинутый котёл", "осквернение пищи", "пустая ритуальность"]),
dict(n=51, pid="zhen_hex", ru="Возбуждение", py="Чжэнь",
lore="Гром над громом: двойной удар, священный страх. Пробуждение, осмотрительность после раската, жертвенное вино, которое не проливают. Знак трясёт, чтобы вернуть в себя.",
names=["гром", "молния", "удар"],
aspects=["двойной гром", "священный страх", "осмотрительность после раската", "пробуждение в себя"],
shadow=["паника раската", "пролитое вино", "страх как характер"]),
dict(n=52, pid="gen_hex", ru="Сосредоточенность", py="Гэнь",
lore="Гора над горой: спина к спине, остановка каждой черты. Молчание, предел движения, храм покоя. Знак велит остановиться в своём месте и не следить за чужим телом.",
names=["гора", "покой", "спина"],
aspects=["гора над горой", "остановка каждой черты", "молчание спины", "предел движения"],
shadow=["застой как добродетель", "глухота покоя", "остановка чужой жизни"]),
dict(n=53, pid="jian_gradual", ru="Течение", py="Цзянь",
lore="Ветер над горой: дерево на склоне растёт годами. Постепенность брака, дикого гуся, ступеней. Знак велит идти как течение — не прыжком, а укоренением на каждой полке склона.",
names=["течение", "путь", "гора", "ветер", "дерево"],
aspects=["дерево на склоне", "постепенность", "дикий гусь ступенями", "укоренение на полке"],
shadow=["прыжок через ступень", "застой под видом постепенности", "течение в никуда"]),
dict(n=54, pid="gui_mei", ru="Невеста", py="Гуй-мэй",
lore="Гром над озером: младшая идёт в дом. Неравный союз, срок, который нельзя назначать самой. Знак предупреждает о браке без места и о радости, которая не стала правом.",
names=["жена", "дочь", "озеро", "гром"],
aspects=["младшая идёт в дом", "неравный союз", "срок не по своей воле", "радость без права"],
shadow=["брак без места", "самовольный союз", "радость, ставшая унижением"]),
dict(n=55, pid="feng", ru="Изобилие", py="Фэн",
lore="Гром над огнём: полнота дня, которая уже держит тень. Изобилие в зените, ясность среди затмения, встреча царя. Знак велит действовать в полный час — после зенита свет сядет.",
names=["изобилие", "гром", "огонь", "полдень"],
aspects=["полнота дня", "ясность среди затмения", "встреча в зените", "час, который не длится"],
shadow=["слепота изобилия", "зенит как вечность", "пир после света"]),
dict(n=56, pid="lu_wander", ru="Странствие", py="Люй",
lore="Огонь над горой: костёр на хребте, ночлег не свой. Странник, мера гостя, гнездо, которое не строят. Знак велит идти легко, не ссориться на постоялом дворе и помнить, что огонь чужой.",
names=["странник", "путь", "огонь", "гора", "гость"],
aspects=["костёр на хребте", "мера гостя", "гнездо, которое не строят", "лёгкий ход"],
shadow=["ссора на постоялом дворе", "захват чужого очага", "странствие как бегство"]),
dict(n=57, pid="xun_hex", ru="Проникновение", py="Сюнь",
lore="Ветер над ветром: двойное проникновение без удара. Приказ, который разносится как дыхание, корень в щели камня. Знак побеждает гибкостью и повтором, не единоборством.",
names=["ветер", "дыхание", "дерево"],
aspects=["двойной ветер", "приказ как дыхание", "корень в щели", "гибкость и повтор"],
shadow=["размывание воли", "уклончивость", "бессилие удара"]),
dict(n=58, pid="dui_hex", ru="Радость", py="Дуй",
lore="Озеро над озером: двойная радость открытых уст. Обмен речью, обучение через удовольствие, зеркало вод. Знак даёт радость как закон — и предупреждает о разливе болтовни.",
names=["радость", "озеро", "смех", "рот"],
aspects=["двойное озеро", "открытые уста", "обучение через радость", "зеркало вод"],
shadow=["пустая болтовня", "разлив соблазна", "радость как бегство"]),
dict(n=59, pid="huan", ru="Раздробление", py="Хуань",
lore="Ветер над водой: ветер разгоняет лёд реки. Рассеяние эго, храм как общее тело, растворение затора. Знак велит разобрать сплочённую тьму — не копить её в себе.",
names=["ветер", "вода", "туман", "храм"],
aspects=["ветер разгоняет лёд", "рассеяние эго", "храм как общее тело", "растворение затора"],
shadow=["распад без сбора", "растворение себя", "ветер, разнёсший храм"]),
dict(n=60, pid="jie", ru="Ограничение", py="Цзе",
lore="Вода над озером: озеро знает свой берег. Мера, устав, сладкое ограничение, которое не превращает жизнь в тюрьму. Знак велит поставить предел — и не затягивать узел до удушья.",
names=["мера", "закон", "озеро", "вода", "берег"],
aspects=["озеро знает берег", "сладкое ограничение", "устав меры", "предел без тюрьмы"],
shadow=["удушье устава", "мера как клетка", "берег, съевший озеро"]),
dict(n=61, pid="zhong_fu", ru="Внутренняя правда", py="Чжун-фу",
lore="Ветер над озером: пустота в середине держит целое. Искренность, которая двигает даже свинью и рыбу. Знак велит совпасть внутри — внешний обряд без ядра пуст.",
names=["правда", "истина", "сердце", "ветер", "озеро"],
aspects=["пустота в середине", "искренность, движущая тварь", "ядро вместо обряда", "совпадение внутри"],
shadow=["правда как поза", "пустота без ядра", "обряд без сердца"]),
dict(n=62, pid="xiao_guo", ru="Переразвитие малого", py="Сяо-го",
lore="Гром над горой: птица не должна взлетать слишком высоко. Малое превышение, внимание к низу, птица в гнезде. Знак велит скромный ход: лучше недолет, чем срыв в вышине.",
names=["гром", "гора", "мера", "птицы"],
aspects=["птица не слишком высоко", "малое превышение", "внимание к низу", "гнездо важнее взлёта"],
shadow=["срыв в вышине", "мелочность как добродетель", "полёт без гнезда"]),
dict(n=63, pid="ji_ji", ru="Уже конец", py="Цзи-цзи",
lore="Вода над огнём: каждая черта на месте, переправа свершилась. Совершенный порядок на пике цикла. Знак велит бдительность лисы на льду: конец уже содержит начало распада.",
names=["итог", "мост", "берег", "конец", "вода"],
aspects=["вода над огнём", "успешная переправа", "строгий порядок черт", "бдительность лисы на льду"],
shadow=["самоуспокоение", "начало распада", "подмоченный хвост"]),
dict(n=64, pid="wei_ji", ru="Ещё не конец", py="Вэй-цзи",
lore="Огонь над водой: огонь стремится вверх, вода вниз — дело не закрыто. Лисенок ещё в реке. Знак держит вечную незавершённость как потенциал: не объявлять финал, пока хвост мокр.",
names=["цикл", "путь", "порог", "начало", "огонь"],
aspects=["огонь над водой", "вечная незавершённость", "лисёнок в реке", "потенциал нового шага"],
shadow=["бессилие довести", "вечные колебания", "страх финальной точки"]),
]
def load_lexicon():
lex = json.loads(LEXICON_PATH.read_text(encoding="utf-8"))
by_word = {e["word"]: e for e in lex}
return lex, by_word
def load_dart_desc():
text = DART_PATH.read_text(encoding="utf-8")
desc = {}
for m in re.finditer(
r'word:\s*"([^"]+)"[\s\S]*?description:\s*"([^"]*)"',
text,
):
desc[m.group(1)] = m.group(2)
return desc
def payload(entry: dict, dart_desc: dict) -> dict:
word = entry["word"]
return {
"id": entry["id"],
"word": word,
"emoji": entry.get("emoji", ""),
"pos": entry.get("pos", ""),
"category": entry.get("category", ""),
"desc": dart_desc.get(word, ""),
}
def resolve(names: list[str], by_word: dict, dart_desc: dict):
found = []
seen = set()
for n in names:
if not n or n in seen:
continue
entry = by_word.get(n)
if entry:
seen.add(n)
found.append(payload(entry, dart_desc))
primary = found[0] if found else None
cluster = found[1:]
return primary, cluster
def yaml_quote(s: str) -> str:
if any(c in s for c in ":#{}[]&*!|>'\"%@`"):
return json.dumps(s, ensure_ascii=False)
return s
def yaml_list(items: list[str], indent: int) -> str:
pad = " " * indent
lines = []
for it in items:
lines.append(f"{pad}- {yaml_quote(it)}")
return "\n".join(lines)
def build_yaml(trigrams, hexagrams) -> str:
chunks = []
for t in trigrams:
chunks.append(
"\n".join(
[
f"- id: {t['id']}",
f" name: {yaml_quote(t['yaml_name'])}",
" names_ru:",
yaml_list(t["names"], 4),
" aspects:",
yaml_list(t["aspects"], 4),
" shadow:",
yaml_list(t["shadow"], 4),
"",
]
)
)
for h in hexagrams:
hid = f"hex_{h['n']:02d}_{h['pid']}"
name = f"{h['n']}. {h['ru']} ({h['py']})"
chunks.append(
"\n".join(
[
f"- id: {hid}",
f" name: {yaml_quote(name)}",
" names_ru:",
yaml_list(h["names"], 4),
" aspects:",
yaml_list(h["aspects"], 4),
" shadow:",
yaml_list(h["shadow"], 4),
"",
]
)
)
return "\n".join(chunks).rstrip() + "\n"
def build_stations(by_word, dart_desc):
stations = []
missing = []
for t in TRIGRAMS:
primary, cluster = resolve(t["names"], by_word, dart_desc)
if primary is None:
missing.append(t["id"])
stations.append(
{
"id": t["id"],
"name": t["name"],
"symbol": t["symbol"],
"hanzi": t.get("hanzi", ""),
"group": t["group"],
"lore": t["lore"],
"primary": primary,
"cluster": cluster,
}
)
for h in HEXAGRAMS:
primary, cluster = resolve(h["names"], by_word, dart_desc)
hid = f"hex_{h['n']:02d}_{h['pid']}"
if primary is None:
missing.append(hid)
stations.append(
{
"id": hid,
"name": f"Гексаграмма {h['n']:02d} {_hex(h['n'])} {h['py']} ({h['ru']})",
"symbol": _hex(h["n"]),
"hanzi": HEX_HANZI.get(h["n"], ""),
"group": _grp(h["n"]),
"lore": h["lore"],
"primary": primary,
"cluster": cluster,
}
)
return stations, missing
def patch_html(canon: dict) -> None:
html = HTML_PATH.read_text(encoding="utf-8")
start = html.find(" const TRADITIONS_CANON = ")
end = html.find(" const WORDS_DATA = ")
if start < 0 or end < 0 or end <= start:
raise SystemExit("не нашёл TRADITIONS_CANON / WORDS_DATA в lexicon_viewer.html")
blob = " const TRADITIONS_CANON = " + json.dumps(canon, ensure_ascii=False, indent=2) + ";\n\n"
HTML_PATH.write_text(html[:start] + blob + html[end:], encoding="utf-8")
def main() -> None:
_, by_word = load_lexicon()
dart_desc = load_dart_desc()
print(f"lexicon={len(by_word)} dart_desc={len(dart_desc)}")
stations, missing = build_stations(by_word, dart_desc)
if missing:
print("WARNING no primary:", ", ".join(missing))
iching = {
"id": "iching",
"name": "И Цзин (Книга Перемен)",
"icon": "☯️",
"count": len(stations),
"description": "8 триграмм Ба Гуа и 64 гексаграммы Книги Перемен в порядке царя Вэня: первородные силы Инь и Ян и полные оракульные фигуры из двух триграмм.",
"stations": stations,
}
canon = json.loads(CANON_PATH.read_text(encoding="utf-8"))
canon["iching"] = iching
text = json.dumps(canon, ensure_ascii=False, indent=2) + "\n"
CANON_PATH.write_text(text, encoding="utf-8")
ASSET_PATH.write_text(text, encoding="utf-8")
patch_html(canon)
YAML_PATH.write_text(build_yaml(TRIGRAMS, HEXAGRAMS), encoding="utf-8")
n_tri = sum(1 for s in stations if s["id"] in {t["id"] for t in TRIGRAMS})
n_hex = len(stations) - n_tri
n_prim = sum(1 for s in stations if s.get("primary"))
print(f"iching stations={len(stations)} trigrams={n_tri} hexagrams={n_hex} with_primary={n_prim}")
print("wrote", CANON_PATH.relative_to(ROOT))
print("wrote", ASSET_PATH.relative_to(ROOT))
print("patched", HTML_PATH.relative_to(ROOT))
print("wrote", YAML_PATH.relative_to(ROOT))
if __name__ == "__main__":
main()